|
|
|
RAM の書き方
|
|
|
|
============
|
|
|
|
|
|
|
|
## RAM の種類
|
|
|
|
|
|
|
|
FPGA 上で RAM を構成する方法には、フリップフロップ、分散RAM 、ブロックRAM の三種類があります。
|
|
|
|
|
|
|
|
作り方によって回路面積や読み出しのタイミングなどが異なりますが、フリップフロップは回路面積を大きく消費するため、基本的には 分散RAM か ブロックRAM を使うようにしましょう。
|
|
|
|
|
|
|
|
分散RAM と ブロックRAM の違いは、簡単に言ってしまえば、あらかじめ用意されている RAM を使うのが ブロックRAM 、LUT を用いて構成されるのが 分散RAM です。
|
|
|
|
|
|
|
|
回路面積を消費しないのは ブロックRAM ですが、その分すこし制約がかかります。
|
|
|
|
|
|
|
|
|
|
|
|
## 分散RAM
|
|
|
|
|
|
|
|
1 read / 1 write の 分散RAM の記述例です。
|
|
|
|
|
|
|
|
重要なのは、**書き込みのタイミングをクロックと同期する**ということです。
|
|
|
|
|
|
|
|
```verilog
|
|
|
|
module ram(clk, we, r_addr, r_data, w_addr, w_data);
|
|
|
|
input clk, we;
|
|
|
|
input [4:0] r_addr, w_addr;
|
|
|
|
input [31:0] w_data;
|
|
|
|
output [31:0] r_data;
|
|
|
|
reg [31:0] mem [0:31]; //32bitのレジスタが32個(アドレスは5bit)
|
|
|
|
always @(posedge clk) begin
|
|
|
|
if(we) mem[w_addr] <= w_data; //クロックと同期して書き込まれる
|
|
|
|
end
|
|
|
|
assign r_data = mem[r_addr];
|
|
|
|
endmodule
|
|
|
|
```
|
|
|
|
|
|
|
|
同時に読める数(read port 数)を増やすのも難しくはありません。単に入出力の信号線を増やし、`assign`文を複数にするだけです。
|
|
|
|
同時に書ける数(write port 数)を増やすのも同様であり、記述上は[^1]難しくありません。当たり前ですが、同じ場所に複数同時に書き込んだりするとおかしなことになります。
|
|
|
|
|
|
|
|
[^1]:実際にどういった回路が組みあがるか、という話になると難しいですが、その辺は論理合成ツールが頑張ってくれるので気にする必要はありません。
|
|
|
|
|
|
|
|
## ブロックRAM
|
|
|
|
|
|
|
|
1 read / 1 write の ブロックRAM の記述例です。
|
|
|
|
|
|
|
|
重要なのは、**書き込みのタイミングと読み出しアドレスをそれぞれクロックと同期する**ということです。
|
|
|
|
|
|
|
|
```verilog
|
|
|
|
module ram(clk, we, r_addr, r_data, w_addr, w_data);
|
|
|
|
input clk, we;
|
|
|
|
input [4:0] r_addr, w_addr;
|
|
|
|
input [31:0] w_data;
|
|
|
|
output [31:0] r_data;
|
|
|
|
reg [4:0] addr_reg;
|
|
|
|
reg [31:0] mem [0:31];
|
|
|
|
always @(posedge clk) begin
|
|
|
|
if(we) mem[w_addr] <= w_data; //書き込みのタイミングを同期
|
|
|
|
addr_reg <= r_addr; //読み出しアドレスを同期
|
|
|
|
end
|
|
|
|
assign r_data = mem[addr_reg];
|
|
|
|
endmodule
|
|
|
|
```
|
|
|
|
|
|
|
|
port 数の増やし方は、分散RAM の時と同様です。
|
|
|
|
|
|
|
|
## RAM の初期化
|
|
|
|
|
|
|
|
RAM の初期化は、モジュール内に次の記述をすることで行うことができます(もちろん、パスは適宜読み替えてください)。
|
|
|
|
|
|
|
|
```verilog
|
|
|
|
initial $readmemh("/home/username/workspace/test.hex", mem)
|
|
|
|
```
|
|
|
|
|
|
|
|
`$readmemh` の引数には初期化したいデータが書かれた**テキストファイル**へのパスを記述します(バイナリデータではありません。十六進数で記述したテキストデータです)。
|
|
|
|
|
|
|
|
```verilog
|
|
|
|
0C00000000000000
|
|
|
|
08_01_04_00 //アンダーバーを入れてもOK
|
|
|
|
```
|
|
|
|
|
|
|
|
`readmemh` の二進数版である `readmemb` もあります。この場合、参照するファイルには
|
|
|
|
|
|
|
|
```verilog
|
|
|
|
00001100000000000000000000000000
|
|
|
|
00000100000000010000010000000000
|
|
|
|
00000100000000100000000001001000
|
|
|
|
000001_00000_00011_0000000001100101
|
|
|
|
```
|
|
|
|
|
|
|
|
などと記述します。
|
|
|
|
|
|
|
|
次のような記述も可能です。
|
|
|
|
|
|
|
|
```verilog
|
|
|
|
integer i;
|
|
|
|
initial begin
|
|
|
|
for(i=0;i<32;i=i+1)
|
|
|
|
mem[i]=0;
|
|
|
|
end
|
|
|
|
```
|
|
|
|
|
|
|
|
```verilog
|
|
|
|
initial begin
|
|
|
|
mem[0]=32'h12345678;
|
|
|
|
mem[1]=32'h87654321;
|
|
|
|
// ...
|
|
|
|
end
|
|
|
|
```
|
|
|
|
|
|
|
|
今回の実験ではプログラムをビルドすると、ICache と DCache のそれぞれの初期化データとして`code.hex`、`data.hex`が生成されるようになっています。
|
|
|
|
従って、ICache と DCache それぞれで`readmemh`を用いて`.hex`を読み込んでください。
|
|
|
|
|
|
|
|
|
|
|
|
## ROM の記述
|
|
|
|
|
|
|
|
ROM の場合も、write 線がないだけで RAM と同様です。すなわち、読み出しを同期すれば ブロックRAM で構成されます。
|
|
|
|
|
|
|
|
```verilog
|
|
|
|
module rom(clk, r_addr, r_data);
|
|
|
|
input clk;
|
|
|
|
input [4:0] r_addr;
|
|
|
|
output [31:0] r_data;
|
|
|
|
reg [4:0] addr_reg;
|
|
|
|
reg [31:0] mem [0:31]
|
|
|
|
always @(posedge clk) begin
|
|
|
|
addr_reg <= r_addr; //読み出しアドレスを同期
|
|
|
|
end
|
|
|
|
assign r_data = mem[addr_reg];
|
|
|
|
endmodule
|
|
|
|
```
|
|
|
|
|
|
|
|
--- |