NOP命令とJAL命令(その1)
#RISC-V #Verilog #FPGA #自作CPU
from マルチサイクル RISC-V CPU を作成したい
https://gyazo.com/b124d1b370cab549e518d1c8ee522388
https://gyazo.com/975566c04bc5617a8752737249714512
マルチサイクルなCPUを作成するにあたり、ひとまず NOP 命令と JAL 命令を実装し、ステージの切り替わり(IFステージ→EXステージ→IFステージ→...)がうまく動くことを確認する。
JAL 命令は、現在のアドレスからのオフセット値を指定してジャンプを行うが、今回は実装を簡単にするため、オフセット0、現在の行へのみジャンプできる(現在の行での無限ループしかできない) JAL 命令を実装する。
動かすプログラムはこんな感じ。NOPを三回繰り返して、4行目の JAL 命令で無限ループに入る。
code:verilog
instructions0 = 32'h00000013; // NOP
instructions1 = 32'h00000013; // NOP
instructions2 = 32'h00000013; // NOP
instructions3 = 32'h0000006F; // jal x0, 0 (ここで無限ループに入る)
実装は以下のとおり。
code:cpu.sv
module cpu(
input logic clk,
input logic reset_n,
// PicoRV32 Native Memory Interface
output logic mem_valid,
output logic mem_instr,
input logic mem_ready,
output logic 31:0 mem_addr,
output logic 31:0 mem_wdata,
output logic 3:0 mem_wstrb,
input logic 31:0 mem_rdata
);
//-------------------------------------
// デバッグ用モニタ
//-------------------------------------
initial begin
// デバッグ用モニタ
$monitor(
"%t: pc = %h, stage = %d, instr = %h, mem_valid = %b, mem_ready = %b, mem_addr = %h, mem_wdata = %h, mem_wstrb = %b, mem_rdata = %h",
$time,
pc_reg,
stage_reg,
instr_reg,
mem_valid,
mem_ready,
mem_addr,
mem_wdata,
mem_wstrb,
mem_rdata
);
end
// CPU ステージ
// IF_STAGE: 命令フェッチ
// EX_STAGE: 実行
// ERR_STAGE: エラー
typedef enum {
IF_STAGE,
EX_STAGE,
ERR_STAGE
} stage_t;
stage_t stage_reg, stage_next;
logic 31:0 pc_reg, pc_next; // PC
logic 31:0 instr_reg, instr_next; // フェッチした命令
assign mem_valid = (stage_reg == IF_STAGE) ? 1'b1 : 1'b0;
assign mem_instr = (stage_reg == IF_STAGE) ? 1'b1 : 1'b0;
assign mem_addr = (stage_reg == IF_STAGE) ? pc_reg : 32'h00000000;
assign mem_wdata = 32'h00000000; // TODO
assign mem_wstrb = 4'b0000; // TODO
always_ff @(posedge clk) begin
if (!reset_n) begin
stage_reg <= IF_STAGE;
pc_reg <= 0;
instr_reg <= 32'h00000000;
end else begin
stage_reg <= stage_next;
pc_reg <= pc_next;
instr_reg <= instr_next;
end
end
always_comb begin
// デフォルト値
stage_next = stage_reg;
instr_next = instr_reg;
pc_next = pc_reg;
case (stage_reg)
IF_STAGE: begin
if (mem_ready) begin
instr_next = mem_rdata;
stage_next = EX_STAGE;
end else begin
instr_next = instr_reg;
stage_next = IF_STAGE;
end
end
EX_STAGE: begin
if (instr_reg == 32'h0000006F) begin
// j x0, 0 の場合は、現在のアドレスで無限ループ
pc_next = pc_reg + 0;
end else begin
// それ以外の場合は次の命令へ
pc_next = pc_reg + 4;
end
stage_next = IF_STAGE;
end
ERR_STAGE: begin
// エラー処理については後で考える
stage_next = ERR_STAGE;
end
endcase
end
endmodule
テストベンチはこちら。
mem_monitor_on をアサートすることで、テストベンチからメモリへ書き込みできるようにしている。
code:cpu_test.sv
`include "instructions.sv"
// CPUのテストベンチ
module cpu_test;
// CPUのインスタンス
cpu cpu_inst(
.clk(clk),
.reset_n(reset_n),
.mem_valid(mem_valid),
.mem_instr(mem_instr),
.mem_ready(mem_ready),
.mem_addr(mem_addr),
.mem_wdata(mem_wdata),
.mem_wstrb(mem_wstrb),
.mem_rdata(mem_rdata)
);
bram_controller ram (
.clk(clk),
.reset_n(reset_n),
.mem_valid(mem_monitor_on ? mem_monitor_valid_reg : mem_valid),
.mem_ready(mem_ready),
.mem_addr(mem_monitor_on ? mem_monitor_addr_reg : mem_addr),
.mem_wdata(mem_monitor_on ? mem_monitor_wdata_reg : mem_wdata),
.mem_wstrb(mem_monitor_on ? mem_monitor_wstrb_reg : mem_wstrb),
.mem_rdata(mem_rdata)
);
// クロック信号
logic clk = 1'b0;
always begin
#5 clk = ~clk;
end
// リセット信号
logic reset_n;
initial begin
reset_n = 0;
#10 reset_n = 1;
end
// PicoRV32 Native Memory Interface
logic mem_valid, mem_instr, mem_ready;
logic 31:0 mem_addr;
logic 31:0 mem_wdata;
logic 3:0 mem_wstrb;
logic 31:0 mem_rdata;
// テストベンチからのメモリ操作用信号線
logic mem_monitor_on = 1'b0;
logic mem_monitor_valid_reg = 1'b0;
logic 31:0 mem_monitor_addr_reg;
logic 31:0 mem_monitor_wdata_reg;
logic 3:0 mem_monitor_wstrb_reg;
// 命令列
logic 31:0 instructions 0:255;
logic 31:0 addr;
initial begin
$dumpfile("cpu_test.vcd");
$dumpvars(0, cpu_inst);
// リセット
reset_n = 0;
#10 reset_n = 1;
$monitoroff; // プログラム書き込み中は $monitor を一時停止
/**
* プログラムの書き込み
*/
instructions0 = 32'h00000013; // nop
instructions1 = 32'h00000013; // nop
instructions2 = 32'h00000013; // nop
instructions3 = 32'h0000006F; // jal x0, 0(無限ループ)
mem_monitor_on = 1;
addr = 32'h00000000;
for (int i = 0; i < 255; i++) begin
mem_monitor_valid_reg = 1;
mem_monitor_addr_reg = addr;
mem_monitor_wdata_reg = instructionsi;
mem_monitor_wstrb_reg = 4'b1111;
#10;
wait(mem_ready);
mem_monitor_valid_reg = 0;
#10;
// 書き込みアドレスを進める
addr = addr + 4;
end
mem_monitor_on = 0;
$monitoron; // $monitor を再開
/**
* リセットして、0番地からプログラムを実行
*/
reset_n = 0;
#10;
reset_n = 1;
#10;
#500;
$finish;
end
endmodule
テストベンチ実行結果のpcの値を見ると、いい感じに無限ループできてそう。
code:sh
$ make test
cd rtl && iverilog -g 2012 -s cpu_test cpu_test.sv cpu.sv bram_controller.sv && ./a.out
0: pc = xxxxxxxx, stage = 0, instr = xxxxxxxx, mem_valid = x, mem_ready = x, mem_addr = xxxxxxxx, mem_wdata = xxxxxxxx, mem_wstrb = xxxx, mem_rdata = xxxxxxxx
5: pc = 00000000, stage = 0, instr = 00000000, mem_valid = 0, mem_ready = 0, mem_addr = 00000000, mem_wdata = 00000000, mem_wstrb = 0000, mem_rdata = 00000000
7655: pc = 00000000, stage = 0, instr = 00000000, mem_valid = 0, mem_ready = 0, mem_addr = 00000000, mem_wdata = 00000000, mem_wstrb = 0000, mem_rdata = 00000000
7665: pc = 00000000, stage = 0, instr = 00000000, mem_valid = 1, mem_ready = 0, mem_addr = 00000000, mem_wdata = 00000000, mem_wstrb = 0000, mem_rdata = 00000013
7685: pc = 00000000, stage = 0, instr = 00000000, mem_valid = 1, mem_ready = 1, mem_addr = 00000000, mem_wdata = 00000000, mem_wstrb = 0000, mem_rdata = 00000013
7695: pc = 00000000, stage = 1, instr = 00000013, mem_valid = 0, mem_ready = 0, mem_addr = 00000000, mem_wdata = 00000000, mem_wstrb = 0000, mem_rdata = 00000013
7705: pc = 00000004, stage = 0, instr = 00000013, mem_valid = 0, mem_ready = 0, mem_addr = 00000000, mem_wdata = 00000000, mem_wstrb = 0000, mem_rdata = 00000013
7715: pc = 00000004, stage = 0, instr = 00000013, mem_valid = 1, mem_ready = 0, mem_addr = 00000004, mem_wdata = 00000000, mem_wstrb = 0000, mem_rdata = 00000013
7735: pc = 00000004, stage = 0, instr = 00000013, mem_valid = 1, mem_ready = 1, mem_addr = 00000004, mem_wdata = 00000000, mem_wstrb = 0000, mem_rdata = 00000013
7745: pc = 00000004, stage = 1, instr = 00000013, mem_valid = 0, mem_ready = 0, mem_addr = 00000004, mem_wdata = 00000000, mem_wstrb = 0000, mem_rdata = 00000013
7755: pc = 00000008, stage = 0, instr = 00000013, mem_valid = 0, mem_ready = 0, mem_addr = 00000004, mem_wdata = 00000000, mem_wstrb = 0000, mem_rdata = 00000013
7765: pc = 00000008, stage = 0, instr = 00000013, mem_valid = 1, mem_ready = 0, mem_addr = 00000008, mem_wdata = 00000000, mem_wstrb = 0000, mem_rdata = 00000013
7785: pc = 00000008, stage = 0, instr = 00000013, mem_valid = 1, mem_ready = 1, mem_addr = 00000008, mem_wdata = 00000000, mem_wstrb = 0000, mem_rdata = 00000013
7795: pc = 00000008, stage = 1, instr = 00000013, mem_valid = 0, mem_ready = 0, mem_addr = 00000008, mem_wdata = 00000000, mem_wstrb = 0000, mem_rdata = 00000013
7805: pc = 0000000c, stage = 0, instr = 00000013, mem_valid = 0, mem_ready = 0, mem_addr = 00000008, mem_wdata = 00000000, mem_wstrb = 0000, mem_rdata = 00000013
7815: pc = 0000000c, stage = 0, instr = 00000013, mem_valid = 1, mem_ready = 0, mem_addr = 0000000c, mem_wdata = 00000000, mem_wstrb = 0000, mem_rdata = 00000013
7825: pc = 0000000c, stage = 0, instr = 00000013, mem_valid = 1, mem_ready = 0, mem_addr = 0000000c, mem_wdata = 00000000, mem_wstrb = 0000, mem_rdata = 0000006f
7835: pc = 0000000c, stage = 0, instr = 00000013, mem_valid = 1, mem_ready = 1, mem_addr = 0000000c, mem_wdata = 00000000, mem_wstrb = 0000, mem_rdata = 0000006f
7845: pc = 0000000c, stage = 1, instr = 0000006f, mem_valid = 0, mem_ready = 0, mem_addr = 0000000c, mem_wdata = 00000000, mem_wstrb = 0000, mem_rdata = 0000006f
7855: pc = 0000000c, stage = 0, instr = 0000006f, mem_valid = 0, mem_ready = 0, mem_addr = 0000000c, mem_wdata = 00000000, mem_wstrb = 0000, mem_rdata = 0000006f
7865: pc = 0000000c, stage = 0, instr = 0000006f, mem_valid = 1, mem_ready = 0, mem_addr = 0000000c, mem_wdata = 00000000, mem_wstrb = 0000, mem_rdata = 0000006f
7885: pc = 0000000c, stage = 0, instr = 0000006f, mem_valid = 1, mem_ready = 1, mem_addr = 0000000c, mem_wdata = 00000000, mem_wstrb = 0000, mem_rdata = 0000006f
7895: pc = 0000000c, stage = 1, instr = 0000006f, mem_valid = 0, mem_ready = 0, mem_addr = 0000000c, mem_wdata = 00000000, mem_wstrb = 0000, mem_rdata = 0000006f
7905: pc = 0000000c, stage = 0, instr = 0000006f, mem_valid = 0, mem_ready = 0, mem_addr = 0000000c, mem_wdata = 00000000, mem_wstrb = 0000, mem_rdata = 0000006f
7915: pc = 0000000c, stage = 0, instr = 0000006f, mem_valid = 1, mem_ready = 0, mem_addr = 0000000c, mem_wdata = 00000000, mem_wstrb = 0000, mem_rdata = 0000006f
7935: pc = 0000000c, stage = 0, instr = 0000006f, mem_valid = 1, mem_ready = 1, mem_addr = 0000000c, mem_wdata = 00000000, mem_wstrb = 0000, mem_rdata = 0000006f
7945: pc = 0000000c, stage = 1, instr = 0000006f, mem_valid = 0, mem_ready = 0, mem_addr = 0000000c, mem_wdata = 00000000, mem_wstrb = 0000, mem_rdata = 0000006f
7955: pc = 0000000c, stage = 0, instr = 0000006f, mem_valid = 0, mem_ready = 0, mem_addr = 0000000c, mem_wdata = 00000000, mem_wstrb = 0000, mem_rdata = 0000006f
7965: pc = 0000000c, stage = 0, instr = 0000006f, mem_valid = 1, mem_ready = 0, mem_addr = 0000000c, mem_wdata = 00000000, mem_wstrb = 0000, mem_rdata = 0000006f
7985: pc = 0000000c, stage = 0, instr = 0000006f, mem_valid = 1, mem_ready = 1, mem_addr = 0000000c, mem_wdata = 00000000, mem_wstrb = 0000, mem_rdata = 0000006f
7995: pc = 0000000c, stage = 1, instr = 0000006f, mem_valid = 0, mem_ready = 0, mem_addr = 0000000c, mem_wdata = 00000000, mem_wstrb = 0000, mem_rdata = 0000006f
8005: pc = 0000000c, stage = 0, instr = 0000006f, mem_valid = 0, mem_ready = 0, mem_addr = 0000000c, mem_wdata = 00000000, mem_wstrb = 0000, mem_rdata = 0000006f
8015: pc = 0000000c, stage = 0, instr = 0000006f, mem_valid = 1, mem_ready = 0, mem_addr = 0000000c, mem_wdata = 00000000, mem_wstrb = 0000, mem_rdata = 0000006f
8035: pc = 0000000c, stage = 0, instr = 0000006f, mem_valid = 1, mem_ready = 1, mem_addr = 0000000c, mem_wdata = 00000000, mem_wstrb = 0000, mem_rdata = 0000006f
8045: pc = 0000000c, stage = 1, instr = 0000006f, mem_valid = 0, mem_ready = 0, mem_addr = 0000000c, mem_wdata = 00000000, mem_wstrb = 0000, mem_rdata = 0000006f
8055: pc = 0000000c, stage = 0, instr = 0000006f, mem_valid = 0, mem_ready = 0, mem_addr = 0000000c, mem_wdata = 00000000, mem_wstrb = 0000, mem_rdata = 0000006f
8065: pc = 0000000c, stage = 0, instr = 0000006f, mem_valid = 1, mem_ready = 0, mem_addr = 0000000c, mem_wdata = 00000000, mem_wstrb = 0000, mem_rdata = 0000006f
8085: pc = 0000000c, stage = 0, instr = 0000006f, mem_valid = 1, mem_ready = 1, mem_addr = 0000000c, mem_wdata = 00000000, mem_wstrb = 0000, mem_rdata = 0000006f
8095: pc = 0000000c, stage = 1, instr = 0000006f, mem_valid = 0, mem_ready = 0, mem_addr = 0000000c, mem_wdata = 00000000, mem_wstrb = 0000, mem_rdata = 0000006f
8105: pc = 0000000c, stage = 0, instr = 0000006f, mem_valid = 0, mem_ready = 0, mem_addr = 0000000c, mem_wdata = 00000000, mem_wstrb = 0000, mem_rdata = 0000006f
8115: pc = 0000000c, stage = 0, instr = 0000006f, mem_valid = 1, mem_ready = 0, mem_addr = 0000000c, mem_wdata = 00000000, mem_wstrb = 0000, mem_rdata = 0000006f
8135: pc = 0000000c, stage = 0, instr = 0000006f, mem_valid = 1, mem_ready = 1, mem_addr = 0000000c, mem_wdata = 00000000, mem_wstrb = 0000, mem_rdata = 0000006f
8145: pc = 0000000c, stage = 1, instr = 0000006f, mem_valid = 0, mem_ready = 0, mem_addr = 0000000c, mem_wdata = 00000000, mem_wstrb = 0000, mem_rdata = 0000006f
8155: pc = 0000000c, stage = 0, instr = 0000006f, mem_valid = 0, mem_ready = 0, mem_addr = 0000000c, mem_wdata = 00000000, mem_wstrb = 0000, mem_rdata = 0000006f
8165: pc = 0000000c, stage = 0, instr = 0000006f, mem_valid = 1, mem_ready = 0, mem_addr = 0000000c, mem_wdata = 00000000, mem_wstrb = 0000, mem_rdata = 0000006f