SW命令
#自作CPU #RISC-V #Verilog #ULX3S
from マルチサイクル RISC-V CPU を作成したい
https://gyazo.com/ebb181d8b25ec330405182f4a6aa28ea
https://gyazo.com/41c11fbfcda5be725f053a61c1791bf0
次は、レジスタの値をメモリに格納するSW命令(store word)と、メモリからレジスタに値を読み出すLW命令(load word)を実装する。
SW命令のフォーマット
SW は S-type の命令
opcode
rs1
rs2
imm
opcode = 0100011
funct3 = 0x2
データ格納先のメモリアドレス: レジスタrs1の値 + オフセット値(即値)
格納するデータ:レジスタ rs2 の値
SWのデータの流れ
レジスタ rs1 から値を読み込み( rf_read_data1 )
レジスタ rs2 から値を読み込み( rf_read_data2 )
ALUで rf_read_data1 と imm を加算して、書き込み先のメモリアドレスとする
レジスタへの書き込みの有無
今は EX ステージの場合は常に、 rd レジスタへALUの結果の書き込みを行っている
書き込みが不要な場合もある
レジスタ書き込み有:Rタイプ命令、Iタイプ命令、Jタイプ命令、Uタイプ命令
レジスタ書き込み無:Sタイプ命令
現状では不明(あとで考える):Bタイプ命令
ステージの見直し
メモリの読み込み書き込みを行う箇所では mem_ready の待機を行っている。ステージを分けて待機しても良いかも
LWとSWの時のデコーダの動きを確認
SW命令
memWrite がアサートされる
LW命令
memToReg がアサートされる
現状のステージ
IF→EX
改修後のステージ(予定)
IF (instruction fetch) → EX (execute) → M (read/write memory) → W (writeback)
デジタル回路設計とコンピュータアーキテクチャ(7章)が参考になる
https://gyazo.com/20646eff97e8edc11363d174ca80a49e
swを追加
ひとまず sw を実装してみた。
code:diff
diff --git a/rtl/cpu.sv b/rtl/cpu.sv
index c7ff36c..c71cf71 100644
--- a/rtl/cpu.sv
+++ b/rtl/cpu.sv
@@ -35,11 +35,15 @@ module cpu(
// CPU ステージ
// IF_STAGE: 命令フェッチ
// EX_STAGE: 実行
+ // MEM_STAGE: メモリアクセス
+ // WB_STAGE: レジスタ書き戻し(writeback)
// ERR_STAGE: エラー
//-------------------------------------
typedef enum {
IF_STAGE,
EX_STAGE,
+ MEM_STAGE,
+ WB_STAGE,
ERR_STAGE
} stage_t;
stage_t stage_reg, stage_next;
@@ -50,12 +54,15 @@ module cpu(
//-------------------------------------
// 出力信号
//-------------------------------------
- assign mem_valid = (stage_reg == IF_STAGE) ? 1'b1 : 1'b0;
+ assign mem_valid = (stage_reg == IF_STAGE | stage_reg == MEM_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
-
+ assign mem_addr = (stage_reg == IF_STAGE) ? pc_reg :
+ (stage_reg == MEM_STAGE) ? alu_result
+ : 32'h00000000;
+ assign mem_wdata = (stage_reg == MEM_STAGE && dc_mem_write) ? rf_read_data2
+ : 32'h00000000;
+ assign mem_wstrb = (stage_reg == MEM_STAGE && dc_mem_write) ? 4'b1111
+ : 4'b0000;
//-------------------------------------
// デコーダ
@@ -135,7 +142,7 @@ module cpu(
assign rf_addr1 = instr_reg19:15; // rs1
assign rf_addr2 = instr_reg24:20; // rs2
assign rf_addr3 = instr_reg11:7; // rd
- assign rf_we3 = (stage_reg == EX_STAGE) && dc_reg_write;
+ assign rf_we3 = (stage_reg == WB_STAGE) && dc_reg_write;
assign rf_write_data3 = alu_result;
// assign rf_write_data3 = (dc_mem_to_reg) ? mem_rdata :
// (jump) ? pc_reg + 4
@@ -160,6 +167,7 @@ module cpu(
pc_next = pc_reg;
case (stage_reg)
+ // 命令フェッチ
IF_STAGE: begin
if (mem_ready) begin
instr_next = mem_rdata;
@@ -169,8 +177,23 @@ module cpu(
stage_next = IF_STAGE;
end
end
+ // 実行
EX_STAGE: begin
+ stage_next = MEM_STAGE;
+ end
+ // メモリアクセス
+ MEM_STAGE: begin
+ // メモリへの書き込み
+ if (dc_mem_write) begin
+ // TODO
+ end
+
+ stage_next = WB_STAGE;
+ end
+ // レジスタ書き戻し
+ WB_STAGE: begin
if (jump) begin
+ // jal 命令の場合
pc_next = pc_reg + imm;
end else begin
// それ以外の場合は次の命令へ
diff --git a/rtl/cpu_test.sv b/rtl/cpu_test.sv
index 4097b4c..aa6ef84 100644
--- a/rtl/cpu_test.sv
+++ b/rtl/cpu_test.sv
@@ -75,7 +75,8 @@ module cpu_test;
instructions0 = addi(1, 0, 10); // addi x1, x0, 10
instructions1 = add(2, 1, 1); // addi x2, x1, x1
instructions2 = add(3, 1, 2); // addi x3, x1, x2
- instructions3 = jal(0, -12 >> 1); // jal x0, -12 (0番地へ戻る)
+ instructions3 = sw(0, 3, 32'h80); // sw x3, 0x80(x0) (メモリの 0x80 番地へ x3 の値を格納する)
+ instructions4 = jal(0, -16 >> 1); // jal x0, -16 (0番地へ戻る)
mem_monitor_on = 1;
addr = 32'h00000000;
@@ -104,7 +105,19 @@ module cpu_test;
reset_n = 1;
#10;
- #500;
+ #1000;
+
+ // 実行が終わった頃合いを見て、メモリの 0x80 番地の内容を確認
+ mem_monitor_on = 1;
+ mem_monitor_valid_reg = 1;
+ mem_monitor_addr_reg = 32'h00000080;
+ mem_monitor_wstrb_reg = 4'b0000;
+ #10;
+ wait(mem_ready);
+ $display("mem0x80 = %d", mem_rdata);
+ mem_monitor_valid_reg = 0;
+ #10;
+
$finish;
end
以下のプログラムを実行してみる。
code:verilog
instructions0 = addi(1, 0, 10); // addi x1, x0, 10
instructions1 = add(2, 1, 1); // addi x2, x1, x1
instructions2 = add(3, 1, 2); // addi x3, x1, x2
instructions3 = sw(0, 3, 32'h80); // sw x3, 0x80(x0) (メモリの 0x80 番地へ x3 の値を格納する)
instructions4 = jal(0, -16 >> 1); // jal x0, -16 (0番地へ戻る)
メモリの0x80番地に x1 + x2 の結果の 30 が書き込まれている。いい感じに動いてそう。
code:sh
$ make test
...
mem0x80 = 30
...