戻る
FPGA中にメモリを作ろうとしたとき、 普通に配列で作ると、ロジック回路に使うCLBという領域を使います。 このメモリは分散RAMとよばれ、容量などは特に決まっていませんが、 貴重なCLBを湯水のごとく消費するので勿体ない使い方といえます。
SpartanIIやVirtexEにはブロックRAMという機能が備わっています。 ブロックRAMは4096ビットのデュアルポートメモリです。 このメモリはプリミティブとして用意されているので、 使ってもFPGAのCLBは消費しません。 大きな塊のメモリが欲しい時につかうと、とてもFPGAを 効率よく使うことができます。
SpartanIIのブロックRAMは1個4096ビットになります。したがって、1つのブロックRAMでは以下の構成が可能です。
データバス幅 | アドレスバス幅 | 深さ |
---|---|---|
1bit | 12ビット | 4096 |
2bit | 11ビット | 2048 |
4bit | 10ビット | 1024 |
8bit | 9ビット | 512 |
16bit | 8ビット | 256 |
1つのFPGAにブロックRAMが何個入っているかは、FPGAの容量によって総数が決まっています。それによって必然的に作れる最大メモリ容量は決まります。
SpartanIIブロックRAMの総容量は最大でも56kbitしかありません。バイトに換算すると、7kバイトです。
デバイス | ブロックRAMの数 | 最大容量[bit] |
---|---|---|
XC2S15 | 4 | 16,384 |
XC2S30 | 6 | 24,576 |
XC2S50 | 8 | 32,768 |
XC2S100 | 10 | 40,960 |
XC2S150 | 12 | 49,152 |
XC2S200 | 14 | 57,344 |
次の節ではVHDLでプリミティブ使う方法を紹介します。プリミティブを使うには宣言をし、port mapでピンをつなぎます。この方法だときめ細かくブロック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_S1 | ADDR[11:0] | DI/DO[0:0] | N/A | N/A |
RAMB4_S1_S1 | ADDRA[11:0] | DIA/DOA[0:0] | ADDRB[11:0] | DIB/DOB[0:0] |
RAMB4_S1_S2 | ADDRA[11:0] | DIA/DOA[0:0] | ADDRB[10:0] | DIB/DOB[1:0] |
RAMB4_S1_S4 | ADDRA[11:0] | DIA/DOA[0:0] | ADDRB[9:0] | DIB/DOB[3:0] |
RAMB4_S1_S8 | ADDRA[11:0] | DIA/DOA[0:0] | ADDRB[8:0] | DIB/DOB[7:0] |
RAMB4_S1_S16 | ADDRA[11:0] | DIA/DOA[0:0] | ADDRB[7:0] | DIB/DOB[15:0] |
RAMB4_S2 | ADDR[10:0] | DI/DO[1:0] | N/A | N/A |
RAMB4_S2_S2 | ADDRA[10:0] | DIA/DOA[1:0] | ADDRB[10:0] | DIB/DOB[1:0] |
RAMB4_S2_S4 | ADDRA[10:0] | DIA/DOA[1:0] | ADDRB[9:0] | DIB/DOB[3:0] |
RAMB4_S2_S8 | ADDRA[10:0] | DIA/DOA[1:0] | ADDRB[8:0] | DIB/DOB[7:0] |
RAMB4_S2_S16 | ADDRA[10:0] | DIA/DOA[1:0] | ADDRB[7:0] | DIB/DOB[15:0] |
RAMB4_S4 | ADDR[9:0] | DI/DO[3:0] | N/A | N/A |
RAMB4_S4_S4 | ADDRA[9:0] | DIA/DOA[3:0] | ADDRB[9:0] | DIB/DOB[3:0] |
RAMB4_S4_S8 | ADDRA[9:0] | DIA/DOA[3:0] | ADDRB[8:0] | DIB/DOB[7:0] |
RAMB4_S4_S16 | ADDRA[9:0] | DIA/DOA[3:0] | ADDRB[7:0] | DIB/DOB[15:0] |
RAMB4_S8 | ADDR[8:0] | DI/DO[7:0] | N/A | N/A |
RAMB4_S8_S8 | ADDRA[8:0] | DIA/DOA[7:0] | ADDRB[8:0] | DIB/DOB[7:0] |
RAMB4_S8_S16 | ADDRA[8:0] | DIA/DOA[7:0] | ADDRB[7:0] | DIB/DOB[15:0] |
RAMB4_S16 | ADDR[7:0] | DI/DO[15:0] | N/A | N/A |
RAMB4_S16_S16 | ADDRA[7:0] | DIA/DOA[15:0] | ADDRB[7:0] | DIB/DOB[15:0] |
UNISIMに関しては、WebPACKのディレクトリにunisimというディレクトリがあるので、その中からunisim_VCOMP.vhdというファイルがあるので、眺めてみましょう。
このシンボルは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; |
書きこみを、システムクロックではなく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 |
書きこみタイミングをクロックに同期させ、読み出しアドレスを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になります。
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% |
WR | 書きこみアドレス | 読み出しアドレス | 結果 |
---|---|---|---|
非同期 | - | - | フリップフロップ |
同期 | 非同期 | 非同期 | 分散RAM |
同期 | 非同期 | 同期 | ブロックRAM |
同期 | 同期 | 非同期 | 分散RAM(よくない) |
同期 | 同期 | 同期 | 分散RAM(よくない) |