Verilog: FPGA の Block RAM の使用

VerilogFPGAのブロックメモリを使った。メモリがどういったものか、そしてFPGAでメモリをどうやって用いるかを調べるのに若干の苦労をしたので、記事にしておいた。もし誤謬があれば指摘願う。




メモリーとは

メモリーとは、アドレスとデータのペアの集合だ。配列やリストのようなものだと思うといい。

例えば、アドレス2bit, データ8bitの場合、

アドレス00にデータ00000000
アドレス01にデータ10101001
アドレス10にデータ11110010
アドレス11にデータ10101010

などと合計32bit格納できる。このとき、深さ2^2=4 (アドレスの個数)、幅8bitのメモリなどと言ったりする。もしくは、8bitデータ x 4ワードのメモリなどと言ったりもする。

また、わかりにくいことに、データ幅が32bitとなった場合でも、アドレス1個あたりは8bitしか入らない。CPUが8bitを単位としてデータを扱うためだ。データを16進数で表すと、8bitは2桁で表せる。また、次のようにワードが定義される。

ワード0 アドレス0-3にデータ32bit  0x 10 10 10 10
ワード1 アドレス4-7にデータ32bit  0x 00 10 00 11
ワード2 アドレス8-11にデータ32bit 0x 10 00 11 11
ワード3 アドレス12-15にデータ32bit 0x 11 11 00 11

メモリでは、通常、32bit = 4byteをまとめて扱うので、アドレス4箇所使って格納するのが自然だ。

リトルエンディアンとビッグエンディアン

ビット列では、一番左の桁をMSB (Most Significant Bit), 一番右の桁をLSB (Least Significant Bit)と呼ぶ。

MSB 01010100101 LSB

さて、32bit列を8bitメモリに格納したい。このとき、アドレスは0から3までの4つを用いればよいが、アドレス0にMSBから順に入れるか、LSBから順に入れるかの2パターンがある。前者がビッグ・エンディアン、後者がリトル・エンディアンと呼ばれる。

例えば、32bit列が次のようなとき

0b 0000 0000 0010 0001

となる。頭の0bは2進数の合図だ。16進数に直すと、

0x 00 00 02 01

となる。頭の0xは16進数の合図だ。16進数を意味するhexadecimalのxをとったらしいが、なぜhではなかったのか不明。

上の8桁をメモリに格納するときに、MSBから格納すれば (ビッグ・エンディアン)、

アドレス 小 <---------->大
...... 00 00 02 01 ........

となるが、LSBから格納すれば (リトル・エンディアン)、

アドレス 小 <---------->大
...... 01 02 00 00 ........

となる。

リトルエンディアンでは、格納するデータのビット数が増えても、次のアドレスに格納すればいいだけなので、拡張しやすい。

Intel, DECはリトル・エンディアン、Motorola, IBMはビッグ・エンディアンを用いていた。今のCPUは、リトル・エンディアンとビッグ・エンディアンを動的に切り替えられるようになっているものが多い。


分散RAMとブロックRAMの違い

この通り、
http://www.cqpub.co.jp/dwm/contents/0126/dwm012601570.pdf

簡単には、組み合わせ回路や順序回路に使われるConfigurable Logic Block (CLB) と同じ部分を消費するのが分散メモリで、容量は少ないが非同期でデータを取り出せる。それと異なり、別に大容量が使えるが、同期読み出ししかできないのがブロックRAMだ。あまりに多いデータを分散RAMで作ると、ログで「フリップフロップ作りすぎだ、FPGAにはブロックRAMが用意されているぞ」と注意を受けるらしい。

試験用ソースコード

追記 (2015年8月20日): 下のソースコードでアドレスとなっている部分は、ワードの間違い。訂正しました。

IP Integratorを使う必要はない。初期化では、ワード0-3に、0-3を、8bitに左から0詰めて代入した。そして、ワード0-4を取り出し、取り出すたびに0を代入した。そして、再びワード0-4を取り出し、0になっているか確認した。
作ったRAMは8bit32bit。we, reはwrite enable, read enableのこと。動的に書き込み、読み込みの可能、不可能を制御する。今回はいずれも常時1にした。

--------    bram.v    --------------------------------------

module bram(
    input ck,
    input wire [31:0] addr,
    input [7:0] din,
    output reg [7:0] dout,
    input we,
    input re
    );
    
    // 8bit * 128words = 128byte memory. 8bit data for each 128bit address

    //<--------------- 8 bit ----------->
    //  0000_0000    1st word
    //     :         
    //     :         
    //  0000_1010    32nd word

    //wire [7:0] temp;
    // temp = b_memory[100];  to get address 100. 

    (* ram_style = "BLOCK" *) reg [7:0] bram[0:31]; 
    
    initial begin
        bram[32'd0] = 8'd0;
        bram[32'd1] = 8'd1;
        bram[32'd2] = 8'd2;
        bram[32'd3] = 8'd3;
    end

    always @(posedge ck) begin
        dout <= bram[addr];
        bram[addr] <= din;
    end


endmodule

--------    bram_tp.v    ------------------------------------
module bram_tp(

    );
    reg ck;
    reg [31:0] addr;
    reg [7:0] din;
    wire [7:0] dout;
    reg we;
    reg re;
    parameter STEP = 25; // 25 nsec periods = 40MHz
 
    initial begin
    ck <= 0;
    forever #(STEP/2) ck <= ~ck;
    end
 
    bram bram(.ck(ck), .addr(addr), .din(din), .dout(dout), .we(we), .re(re));
 
    initial begin
              we = 1;
              re = 1;
              addr = 32'd0; din=8'd0;
        #STEP addr = 32'd1; din=8'd0;
        #STEP addr = 32'd2; din=8'd0;
        #STEP addr = 32'd3; din=8'd0;
        #STEP addr = 32'd4; din=8'd0;
        #STEP addr = 32'd0; din=8'd0;
        #STEP addr = 32'd0; din=8'd0;
        #STEP addr = 32'd1; din=8'd0;
        #STEP addr = 32'd2; din=8'd0;
        #STEP addr = 32'd3; din=8'd0;
    end
 
    initial $monitor($stime, "nsec, addr=%h, dout=%h", addr, dout);
endmodule

結果



デザイン通りの挙動をしていた。最初、アドレス0-3からは、0-3が取り出され、初期化していなかったアドレス4からは不定値xが出ている。そして、アドレス0-4を皆0に上書き後、アドレス0-4からは0が取り出されている。


bramを確認すると


ネットリストでは、


たしかにブロックRAMを使えていた。


参考文献

メモリの深さ、MSB, LSBにはここがわかりやすい。
趣味から始める電気回路 メモリとDフリップフロップ2

メモリとCPU、リトル・エンディアンとビッグ・エンディアンについてはこちらが役に立つ (pdf)
http://www.cqpub.co.jp/interface/sample/200602/if0602_chap1.pdf

FPGAの分散RAMとブロックRAMの違いはここが詳しい。 (pdf)
http://www.cqpub.co.jp/dwm/contents/0126/dwm012601570.pdf

ワードとアドレスの違いについてはこちらの図も役に立つ
LSI Design Contest

Verilogのレジスタ配列についてはここの説明がわかりやすい
Verilog-HDL:文法(3)
Verilog - Verilogのコーディング

Verilogによるブロックメモリ記述方法についてはこちらを参考にした。
入門Verilog : FPGAの部屋

外部ファイルによるメモリの初期化$readmemh, $readmembについて
Verilog - $readmemhと$readmemb


2015年8月18日 コードにalwaysブロックが欠けていたのを修正

コメント

  1. 質問です.
    initial begin
    bram[32'd0] = 8'd0;
    bram[32'd1] = 8'd1;
    bram[32'd2] = 8'd2;
    bram[32'd3] = 8'd3;
    end
    の部分では,bitstreamにしたときは,BRAMの初期値として設定されるのですか?

    返信削除

コメントを投稿

人気の投稿