ブロックRAMの使い方

平成14年10月12日
 このページではXILINXのWebPACKを使って、SpartanIIのブロックRAMという機能を使う方法を紹介します。
  1. ブロックRAMという機能
  2. ブロックRAMの容量とビット幅
  3. プリミティブを使う方法
    1. ブロックRAMを使用することの宣言
    2. ブロックRAMのシンボル
    3. 基本的な使い方
  4. 配列で書く方法
    1. 分散RAMもブロックRAMも使わない
    2. 分散RAM
    3. ブロックRAM
    4. まとめ
戻る

ブロックRAMという機能

 FPGA中にメモリを作ろうとしたとき、 普通に配列で作ると、ロジック回路に使うCLBという領域を使います。 このメモリは分散RAMとよばれ、容量などは特に決まっていませんが、 貴重なCLBを湯水のごとく消費するので勿体ない使い方といえます。

 SpartanIIやVirtexEにはブロックRAMという機能が備わっています。 ブロックRAMは4096ビットのデュアルポートメモリです。 このメモリはプリミティブとして用意されているので、 使ってもFPGAのCLBは消費しません。 大きな塊のメモリが欲しい時につかうと、とてもFPGAを 効率よく使うことができます。

ブロックRAMの容量とビット幅

 SpartanIIのブロックRAMは1個4096ビットになります。したがって、1つのブロックRAMでは以下の構成が可能です。

データバス幅アドレスバス幅深さ
1bit12ビット4096
2bit11ビット2048
4bit10ビット1024
8bit9ビット512
16bit8ビット256

 1つのFPGAにブロックRAMが何個入っているかは、FPGAの容量によって総数が決まっています。それによって必然的に作れる最大メモリ容量は決まります。
 SpartanIIブロックRAMの総容量は最大でも56kbitしかありません。バイトに換算すると、7kバイトです。
デバイスブロックRAMの数最大容量[bit]
XC2S15416,384
XC2S30624,576
XC2S50832,768
XC2S1001040,960
XC2S1501249,152
XC2S2001457,344

プリミティブを使う方法

 次の節ではVHDLでプリミティブ使う方法を紹介します。プリミティブを使うには宣言をし、port mapでピンをつなぎます。この方法だときめ細かくブロックRAMを使うことができます。

ブロックRAMを使用することの宣言

 BRAM4_S#_S#あるいは、BRAM4_S#というシンボルがあります。これを使うには

LIBRARY UNISIM;
USE UNISIM.Vcomponents.ALL;

で宣言してもよいですし、USE文で組み込まなくても

  component RAMB4_S1
    port (DI     : in STD_LOGIC_VECTOR (0 downto 0);
          EN     : in STD_ULOGIC;
          WE     : in STD_ULOGIC;
          RST    : in STD_ULOGIC;
          CLK    : in STD_ULOGIC;
          ADDR   : in STD_LOGIC_VECTOR (11 downto 0);
          DO     : out STD_LOGIC_VECTOR (0 downto 0)
    ); 

などと宣言しておけば、論理合成中に自動的に組み込まれます。

 コンポーネント名と、対応するアドレス、データバス幅は次の表のとおりです。

コンポーネント名ポートAポートB
アドレスデータアドレスデータ
RAMB4_S1ADDR[11:0]DI/DO[0:0]N/AN/A
RAMB4_S1_S1ADDRA[11:0]DIA/DOA[0:0]ADDRB[11:0]DIB/DOB[0:0]
RAMB4_S1_S2ADDRA[11:0]DIA/DOA[0:0]ADDRB[10:0]DIB/DOB[1:0]
RAMB4_S1_S4ADDRA[11:0]DIA/DOA[0:0]ADDRB[9:0]DIB/DOB[3:0]
RAMB4_S1_S8ADDRA[11:0]DIA/DOA[0:0]ADDRB[8:0]DIB/DOB[7:0]
RAMB4_S1_S16ADDRA[11:0]DIA/DOA[0:0]ADDRB[7:0]DIB/DOB[15:0]
RAMB4_S2ADDR[10:0]DI/DO[1:0]N/AN/A
RAMB4_S2_S2ADDRA[10:0]DIA/DOA[1:0]ADDRB[10:0]DIB/DOB[1:0]
RAMB4_S2_S4ADDRA[10:0]DIA/DOA[1:0]ADDRB[9:0]DIB/DOB[3:0]
RAMB4_S2_S8ADDRA[10:0]DIA/DOA[1:0]ADDRB[8:0]DIB/DOB[7:0]
RAMB4_S2_S16ADDRA[10:0]DIA/DOA[1:0]ADDRB[7:0]DIB/DOB[15:0]
RAMB4_S4ADDR[9:0]DI/DO[3:0]N/AN/A
RAMB4_S4_S4ADDRA[9:0]DIA/DOA[3:0]ADDRB[9:0]DIB/DOB[3:0]
RAMB4_S4_S8ADDRA[9:0]DIA/DOA[3:0]ADDRB[8:0]DIB/DOB[7:0]
RAMB4_S4_S16ADDRA[9:0]DIA/DOA[3:0]ADDRB[7:0]DIB/DOB[15:0]
RAMB4_S8ADDR[8:0]DI/DO[7:0]N/AN/A
RAMB4_S8_S8ADDRA[8:0]DIA/DOA[7:0]ADDRB[8:0]DIB/DOB[7:0]
RAMB4_S8_S16ADDRA[8:0]DIA/DOA[7:0]ADDRB[7:0]DIB/DOB[15:0]
RAMB4_S16ADDR[7:0]DI/DO[15:0]N/AN/A
RAMB4_S16_S16ADDRA[7:0]DIA/DOA[15:0]ADDRB[7:0]DIB/DOB[15:0]

 UNISIMに関しては、WebPACKのディレクトリにunisimというディレクトリがあるので、その中からunisim_VCOMP.vhdというファイルがあるので、眺めてみましょう。

ブロックRAMのシンボル

 このシンボルはEN、WE、RST、CLK、ADDR、DIという入力ピンがあります。CLKにクロックをいれます。ENはイネーブル信号で、読み書きリセットはクロックに同期して行われますが、イネーブル信号でコントロールできます。
 RSTは出力データバスを強制的にゼロにします。タイミングは同期式です。出力バスに対して行われる機能ですので、メモリの中身には影響しません。


 デュアルポートとして使うときには、各ピン名の後ろにAまたはBをつけます。


基本的な使い方

 使い方で特に難しいことはありません。 単純につなぐだけです。

library ieee;
--library UNISIM;
use ieee.std_logic_1164.ALL;
use ieee.numeric_std.ALL;
--use UNISIM.Vcomponents.ALL;

entity Main is
   port ( MEMAD    :   IN  STD_LOGIC_VECTOR (8 DOWNTO 0); 
          MEMCLK   :   IN  STD_LOGIC; 
          MEMDIN   :   IN  STD_LOGIC_VECTOR (7 DOWNTO 0); 
          MEMEN    :   IN  STD_LOGIC; 
          MEMRST   :   IN  STD_LOGIC; 
          MEMWE    :   IN  STD_LOGIC; 
          MEMDOUT  :   OUT STD_LOGIC_VECTOR (7 DOWNTO 0));
end Main;

architecture RTL of Main is
  component RAMB4_S8
    port (DI     : in STD_LOGIC_VECTOR (7 downto 0);
          EN     : in STD_ULOGIC;
          WE     : in STD_ULOGIC;
          RST    : in STD_ULOGIC;
          CLK    : in STD_ULOGIC;
          ADDR   : in STD_LOGIC_VECTOR (8 downto 0);
          DO     : out STD_LOGIC_VECTOR (7 downto 0)
    ); 
  end component;
begin
   BLOCKRAM1 : RAMB4_S8
      port map (
          ADDR(8)  =>  MEMAD(8),
          ADDR(7)  =>  MEMAD(7),
          ADDR(6)  =>  MEMAD(6),
          ADDR(5)  =>  MEMAD(5),
          ADDR(4)  =>  MEMAD(4),
          ADDR(3)  =>  MEMAD(3),
          ADDR(2)  =>  MEMAD(2),
          ADDR(1)  =>  MEMAD(1),
          ADDR(0)  =>  MEMAD(0),
          DI(7)    =>  MEMDIN(7),
          DI(6)    =>  MEMDIN(6),
          DI(5)    =>  MEMDIN(5),
          DI(4)    =>  MEMDIN(4),
          DI(3)    =>  MEMDIN(3),
          DI(2)    =>  MEMDIN(2),
          DI(1)    =>  MEMDIN(1),
          DI(0)    =>  MEMDIN(0),
          CLK      =>  MEMCLK,
          EN       =>  MEMEN,
          RST      =>  MEMRST,
          WE       =>  MEMWE,
          DO(7)    =>  MEMDOUT(7),
          DO(6)    =>  MEMDOUT(6),
          DO(5)    =>  MEMDOUT(5),
          DO(4)    =>  MEMDOUT(4),
          DO(3)    =>  MEMDOUT(3),
          DO(2)    =>  MEMDOUT(2),
          DO(1)    =>  MEMDOUT(1),
          DO(0)    =>  MEMDOUT(0)
      );
end RTL;

配列で書く方法

 宣言をしたり、プリミティブを使うのがちょっと面倒くさいときには、配列を使って分散RAMやブロックRAMを記述することができます。配列で書くと、どのプリミティブを使うかは自動で選んでくれますし、4096ビットの境界をこえても、自動的に2つ以上のブロックRAMを連結しれくれます。
 しかしながら、分散RAMやブロックRAMを使ってくれるか、配列を1ビットのフリップフロップで正直につくるかはCADまかせです。WebPACKでは次のような結果になりました。

分散RAMもブロックRAMも使わない

 書きこみを、システムクロックではなくWR信号を使う場合、配列を正直に実装します。いわゆる普通の非同期式SRAM(62256とか)の動作です。


library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.STD_LOGIC_ARITH.ALL;
use IEEE.STD_LOGIC_UNSIGNED.ALL;

entity main is
    Port ( CLK : in std_logic;
           DIN : in std_logic_vector(7 downto 0);
           DOUT : out std_logic_vector(7 downto 0);
           WR : in std_logic;
           ADDR_IN : in std_logic_vector(8 downto 0));
end main;

architecture RTL of main is
  type ram_type is array (511 downto 0) of std_logic_vector (7 downto 0); 
    signal RAM : ram_type; 
    signal ADDR_REG : std_logic_vector(8 downto 0); 
begin
  process(WR) begin
    if (WR'event and WR = '1') then
        RAM(CONV_INTEGER(ADDR_IN)) <= DIN; 
    end if;
  end process;

  DOUT <= RAM(CONV_INTEGER(ADDR_IN));
end RTL;

 次の表に論理合成の結果を載せます。フリップフロップがいっぱいあるぞ、とSynthesizeの段階で怒られています。XILINXにはDistributeRAMやブロックRAMがあるのになんで使わないんだ、と言っています。これではSliceが足りなくなるので合成できません。

WARNING:Xst:738 - 4096 flip-flops were inferred for signal . You may be trying to describe a RAM in a way that is incompatible with block and distributed RAM resources available on Xilinx devices, or with a specific template that is not supported. Please review the Xilinx resources documentation and the XST user manual for coding guidelines. Taking advantage of RAM resources will lead to improved device usage and reduced synthesis time.

分散RAM

 書きこみタイミングをクロックに同期させ、読み出しアドレスをIOピン直結にすると、分散RAMになります。


library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.STD_LOGIC_ARITH.ALL;
use IEEE.STD_LOGIC_UNSIGNED.ALL;

entity main is
    Port ( CLK : in std_logic;
           DIN : in std_logic_vector(7 downto 0);
           DOUT : out std_logic_vector(7 downto 0);
           WR : in std_logic;
           ADDR_IN : in std_logic_vector(8 downto 0));
end main;

architecture RTL of main is
  type ram_type is array (511 downto 0) of std_logic_vector (7 downto 0); 
    signal RAM : ram_type; 
    signal ADDR_REG : std_logic_vector(8 downto 0); 
begin
  process(CLK) begin
    if (CLK'event and CLK = '1') then
      if (WR = '1') then 
        RAM(CONV_INTEGER(ADDR_IN)) <= DIN; 
      end if; 
      ADDR_REG <= ADDR_IN; 
    end if; 
  end process;
  DOUT <= RAM(CONV_INTEGER(ADDR_IN)); -- 分散RAMにするにはここを組み合わせ信号or入力ピンにする。
end RTL;

 XC2S15で512*8ビットのRAMを実装したところ、リソースを96%消費しました。1個のSLICEに2個のCLBが入っていて、1個のCLBが16ビットなので、SLICEの消費は128個のはずですが、効率的なこともあって185個消費しています。

   Number of External GCLKIOBs         1 out of 4      25%
   Number of External IOBs            26 out of 86     30%
      Number of LOCed External IOBs    0 out of 26      0%

   Number of SLICEs                  185 out of 192    96%

   Number of GCLKs                     1 out of 4      25%

ブロックRAM

 書きこみをクロックに同期させ、読み出しアドレスをクロックにいったん同期させると、ブロックRAMになります。


library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.STD_LOGIC_ARITH.ALL;
use IEEE.STD_LOGIC_UNSIGNED.ALL;

entity main is
    Port ( CLK : in std_logic;
           DIN : in std_logic_vector(7 downto 0);
           DOUT : out std_logic_vector(7 downto 0);
           WR : in std_logic;
           ADDR_IN : in std_logic_vector(8 downto 0));
end main;

architecture RTL of main is
  type ram_type is array (511 downto 0) of std_logic_vector (7 downto 0); 
    signal RAM : ram_type; 
    signal ADDR_REG : std_logic_vector(8 downto 0); 
begin
  process(CLK) begin
    if (CLK'event and CLK = '1') then
      if (WR = '1') then 
        RAM(CONV_INTEGER(ADDR_IN)) <= DIN; 
      end if; 
      ADDR_REG <= ADDR_IN; 
    end if; 
  end process;
  DOUT <= RAM(CONV_INTEGER(ADDR_REG)); -- ブロックRAMにするにはここをレジスタ信号にする。
end RTL;

 ごらんの通り、SLICEは1個も消費しません。読み出しアドレス信号を同期化するためのレジスタはIOBに内蔵されているレジスタを使用しているため、おいしい部分のCLBは消費しませんでした。
 今回はブロックRAMにぴったり納まる大きさのメモリですが、容量が4096ビットを超えると2つ以上のブロックRAMを連結してくれます。
 容量がFPGA中の全メモリ容量を越えると、超えた分は分散RAMでつくってくれるわけではなく、ブロックRAMが足りないとしてエラーになります。

Device utilization summary:

   Number of External GCLKIOBs         1 out of 4      25%
   Number of External IOBs            26 out of 86     30%
      Number of LOCed External IOBs    0 out of 26      0%

   Number of BLOCKRAMs                 1 out of 4      25%

   Number of GCLKs                     1 out of 4      25%

まとめ

 書きこみアドレスには入力ピンを直接入れ、読み出しアドレスにはレジスタ出力を入れると、ブロックRAMができました。
 書きこみアドレスにレジスタ出力を入れると分散RAMができてしまいます。しかも使用効率がよくないのでおすすめできません。
 また、読み書きアドレスのビット幅と配列のサイズが一致しなかったり、細かいミスがあると、分散RAMかフリップフロップになってしまいます。
WR書きこみアドレス読み出しアドレス結果
非同期--フリップフロップ
同期非同期非同期分散RAM
同期非同期同期ブロックRAM
同期同期非同期分散RAM(よくない)
同期同期同期分散RAM(よくない)

 プリミティブを使う際のオプション指定や、初期値の指定方法、バス幅の違うデュアルポートメモリなどのついては次回解説します。
戻る