VALIDとREADYにより待ち合わせを行うBRAMコントローラー
#Verilog #FPGA
VALIDとREADYにより待ち合わせを行うBRAMコントローラー。次に作るCPUで使う予定。
code:bram_controller.sv
// BRAM コントローラー
module bram_controller(
input wire clk,
input wire reset_n,
// TODO: あとで cs (chip select) を追加したい
input wire mem_valid,
output logic mem_ready,
input wire 31:0 mem_addr,
input wire 31:0 mem_wdata,
input wire 3:0 mem_wstrb, // 0'b0000 の場合は読み込み、0'b1111 の場合は書き込み
output logic 31:0 mem_rdata
);
typedef enum logic 2:0 {
STATE_IDLE,
STATE_MEMORY_RW_WAIT1,
STATE_MEMORY_RW_WAIT2,
STATE_SEND_READY
} state_t;
state_t state_reg, state_next;
//------------------------
// デバッグ用モニタの設定
//------------------------
initial begin
$monitor("%t: state = %d, reset_n = %b, mem_valid = %b, mem_ready = %b, mem_addr = %h, mem_wdata = %h, mem_wstrb = %b, mem_rdata = %h", $time, state_reg, reset_n, mem_valid, mem_ready, mem_addr, mem_wdata, mem_wstrb, mem_rdata);
end
logic 31:0 mem 0:1023;
assign mem_ready = (state_reg == STATE_SEND_READY) ? 1'b1 : 1'b0;
// メモリの初期化
initial begin
// mem0 = 32'b0101;
// mem1 = 32'b1010;
for (int i = 0; i < 1024; i++) begin
// 以下のような感じにメモリを初期化
// mem0x0000 = 0x0000
// mem0x0004 = 0x0001
// mem0x0008 = 0x0002
// mem0x000C = 0x0003
// mem0x0010 = 0x0004
// ...
memi = i;
end
end
always_ff @(posedge clk) begin
if (!reset_n)
state_reg <= STATE_IDLE;
else begin
state_reg <= state_next;
// メモリへの書き込み
// NOTE: 現状は1ワードの書き込みのみに対応
if (mem_wstrb == 4'b1111 && mem_valid) begin
mem[mem_addr9:2] <= mem_wdata;
end
// メモリからの読み込み
// NOTE: BRAM として推論させるため、クロックの立ち上がりで読み込みを行う
mem_rdata <= mem[mem_addr9:2];
end
end
always_comb begin
case (state_reg)
STATE_IDLE: begin
if (mem_valid) begin
state_next = STATE_MEMORY_RW_WAIT1;
end else begin
state_next = STATE_IDLE;
end
end
STATE_MEMORY_RW_WAIT1: begin
state_next = STATE_MEMORY_RW_WAIT2;
end
STATE_MEMORY_RW_WAIT2: begin
state_next = STATE_SEND_READY;
end
STATE_SEND_READY: begin
state_next = STATE_IDLE;
end
default: begin
state_next = STATE_IDLE;
end
endcase
end
endmodule
code:bram_controller_test.sv
// bram_controller のテストベンチ
// $ iverilog -g 2012 -s bram_controller_test bram_controller_test.sv bram_controller.sv && ./a.out
module bram_controller_test();
logic clk;
logic reset_n;
logic mem_valid;
logic mem_ready;
logic 31:0 mem_addr;
logic 31:0 mem_wdata;
logic 3:0 mem_wstrb;
logic 31:0 mem_rdata;
bram_controller dut (
.clk(clk),
.reset_n(reset_n),
.mem_valid(mem_valid),
.mem_ready(mem_ready),
.mem_addr(mem_addr),
.mem_wdata(mem_wdata),
.mem_wstrb(mem_wstrb),
.mem_rdata(mem_rdata)
);
initial begin
// dut の波形を出力
$dumpfile("bram_controller_test.vcd");
$dumpvars(0, bram_controller_test);
//--------------------------------------
// 制御変数の初期化
//--------------------------------------
#10
clk = 0;
reset_n = 1;
mem_valid = 0;
mem_addr = 0;
mem_wdata = 0;
mem_wstrb = 4'b0000;
#10
//--------------------------------------
// BRAMをリセット
//--------------------------------------
#10 reset_n = 0;
#10 reset_n = 1;
#10
//--------------------------------------
// メモリの 0x10 番地を読み込み
//--------------------------------------
#10
mem_valid = 1;
mem_addr = 32'h0010;
mem_wstrb = 4'b0000;
mem_wdata = 32'h0000;
#10
// mem_ready がアサートされるまで待機
wait (mem_ready == 1);
// mem_ready = 1、かつ mem_rdata = 0x0004 であること
assert(mem_ready == 1);
assert(mem_rdata == 32'h0004) else $error("mem_rdata = %h", mem_rdata);
// mem_valid をデアサート
mem_valid = 0;
#10
//--------------------------------------
// メモリの 0x20 番地を読み込み
//--------------------------------------
#10
mem_valid = 1;
mem_addr = 32'h0020;
mem_wstrb = 4'b0000;
mem_wdata = 32'h0000;
#10
// mem_ready がアサートされるまで待機
wait (mem_ready == 1);
// mem_ready = 1 かつ mem_rdata = 0x0008 であること
assert(mem_ready == 1) else $error("mem_ready = 0");
assert(mem_rdata == 32'h0008) else $error("mem_rdata = %h", mem_rdata);
// mem_valid をデアサート
mem_valid = 0;
#10
//--------------------------------------
// メモリの 0x10 番地へ書き込み
//--------------------------------------
#10
mem_valid = 1;
mem_wstrb = 4'b1111;
mem_addr = 32'h0010;
mem_wdata = 32'h0000FFFF;
#10
// mem_ready がアサートされるまで待機
wait (mem_ready == 1);
// mem_ready = 1、かつ mem_rdata = 0x0000FFFF であること
assert(mem_ready == 1);
assert(mem_rdata == 32'h0000FFFF) else $error("mem_rdata = %h", mem_rdata);
// mem_valid をデアサート
mem_valid = 0;
#10
//--------------------------------------
// メモリの 0x20 番地へ書き込み
//--------------------------------------
#10
mem_valid = 1;
mem_wstrb = 4'b1111;
mem_addr = 32'h0020;
mem_wdata = 32'h0000FF00;
#10
// mem_ready がアサートされるまで待機
wait (mem_ready == 1);
// mem_ready = 1、かつ mem_rdata = 0x000FF00 であること
assert(mem_ready == 1);
assert(mem_rdata == 32'h0000FF00) else $error("mem_rdata = %h", mem_rdata);
// mem_valid をデアサート
mem_valid = 0;
#10
//--------------------------------------
// メモリの 0x10 番地を読み込み
//--------------------------------------
#10
mem_valid = 1;
mem_addr = 32'h0010;
mem_wstrb = 4'b0000;
mem_wdata = 32'h0000;
#10
// mem_ready がアサートされるまで待機
wait (mem_ready == 1);
// mem_ready = 1、かつ mem_rdata = 0x0000FFFF であること
assert(mem_ready == 1);
assert(mem_rdata == 32'h0000FFFF) else $error("mem_rdata = %h", mem_rdata);
// mem_valid をデアサート
mem_valid = 0;
#10
//--------------------------------------
// メモリの 0x20 番地を読み込み
//--------------------------------------
#10
mem_valid = 1;
mem_wstrb = 4'b0000;
mem_addr = 32'h0020;
#10
// mem_ready がアサートされるまで待機
wait (mem_ready == 1);
// mem_ready = 1、かつ mem_rdata = 0x0000FFFF であること
assert(mem_ready == 1);
assert(mem_rdata == 32'h0000FF00) else $error("mem_rdata = %h", mem_rdata);
// mem_valid をデアサート
mem_valid = 0;
#10
$finish;
end
always #5 clk = ~clk;
endmodule
テストベンチの流し方
以下のコマンドでテストベンチを流す。
code:sh
$ iverilog -g 2012 -s bram_controller_test bram_controller_test.sv bram_controller.sv && ./a.out
GTKWaveで波形を見ると、いい感じに動いてそう。
https://gyazo.com/3bb60a70c2dea7033f16e29035a022a3