Chiselを使って、非常にシンプルなCPUを作ってみるプロジェクト、ある程度演算命令は動き始めたが、ちゃんと動作しているのかどうかを確かめたい。
分岐命令はALUで比較を実行して、その結果に応じてPCを切り替える。 RISC-Vには3種類のPCアップデートの方法があって、
- Branch Conditional式 : 比較の結果、PC+Immで分岐する。
- J式 : 比較をしない。PC+Immで分岐する。
- JALR : 比較をしない。レジスタ値で分岐する。
の大きく分けで3つで考えていればよい。Branchの場合は比較処理を実行して、そうでない場合は無条件でPCをアップデートする。 ChiselでもVerilogでも、書くことは一緒だ。
val dec_jalr_en = u_cpath.io.ctl.jalr val dec_jal_en = u_cpath.io.ctl.jal val dec_br_en = u_cpath.io.ctl.br & (u_alu.io.res === 1.S) val dec_jump_en = if_inst_en & (dec_jalr_en | dec_jal_en | dec_br_en) when(if_inst_en & dec_jalr_en) { if_inst_addr := dec_reg_op0.asUInt } .elsewhen (if_inst_en & dec_jal_en) { if_inst_addr := dec_inst_addr + dec_imm_j } .elsewhen (if_inst_en & dec_br_en) { if_inst_addr := dec_inst_addr + dec_imm_b_sext } .elsewhen(if_inst_en & io.inst_ack) { if_inst_addr := if_inst_addr + 4.U }
今のところはパイプラインとしてはたったの2段。データメモリもくっついていないけど、とりあえず演算命令は動き始めている。
ちなみにPCから命令を流しっぱなしだと、PCをアップデート後にすでに次のPCアドレスに移ってしまっているので、それを無効化する処理を入れている(1サイクルのバブル)。以下のコードで、`dec_inst_valid信号は、直前の命令がジャンプ命令ならば次の命令は無効化する。
when (if_inst_en & io.inst_ack) { dec_inst_data := io.inst_data dec_inst_addr := if_inst_addr dec_inst_valid := Mux(dec_jump_en, false.B, true.B) } .otherwise { dec_inst_valid := false.B }
とりあえずこれで演算命令、分岐命令は動作するようになった。次はメモリアクセス命令、CSRの実装、あとはパイプラインをより深くしていく。