戻る
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(よくない) |