Chiselを使ったRISC-Vの勉強(5. 分岐命令の実装)
前回の記事と分けてしまったら内容がほとんど皆無になってしまいました。
ないようがないよう
(この辺りでみんなブラウザ閉じる)
年取るってこういうことなんだなぁ。
さて、本題です。
前回riscv-asからアセンブルした機械語をChiselプロジェクトにワンストップで導入する仕組みを構築しました。
分岐命令の相対アドレスを計算するのが大変なのと、immが命令タイプに応じて異なるため、ハンドアセンブルが厳しくなったからです。人為的ミスの低減にもなるはずです。
immの種類多すぎでは
今更感が非常に強いですが、immを整理しました。
命令セットのImmに対応するビット位置がまちまちなので、それをここで統一しました。
CSR関係のzimmもここで整理しました。なお、参考にしたのはこちら。
ざっとこんな感じです。riscv-miniとほとんど変わらない実装
object ImmGen { def apply(sel: UInt, inst: UInt): SInt = { val sign = Mux(sel === IMM_Z, 0.S, inst(31).asSInt) val b30_20 = Mux(sel === IMM_U, inst(30,20).asSInt, sign) val b19_12 = Mux(sel =/= IMM_U && sel =/= IMM_J, sign, inst(19,12).asSInt) val b11 = Mux(sel === IMM_U || sel === IMM_Z, 0.S, Mux(sel === IMM_J, inst(20).asSInt, Mux(sel === IMM_B, inst(7).asSInt, sign))) val b10_5 = Mux(sel === IMM_U || sel === IMM_Z, 0.U, inst(30,25)) val b4_1 = Mux(sel === IMM_U, 0.U, Mux(sel === IMM_S || sel === IMM_B, inst(11,8), Mux(sel === IMM_Z, inst(19,16), inst(24,21)))) val b0 = Mux(sel === IMM_S, inst(7), Mux(sel === IMM_I, inst(20), Mux(sel === IMM_Z, inst(15), 0.U))) Cat(sign, b30_20, b19_12, b11, b10_5, b4_1, b0).asSInt }
分岐命令
実装した分岐命令は以下に挙げる通りです。WLEN
は命令1ワードあたりのアドレスバイト長で、今回は4(=32bit)です。imm_X
はXタイプの即値immという意味です。
疑似コード
rd = pc + WLEN, pc = pc + imm_J
- jalr
疑似コード
rd = pc + WLEN, pc = rs1 + imm_I
- beq
疑似コード
if (rs1 == rs2) then pc = pc + imm_B else pc = pc + WLEN
- bne
疑似コード
if(rs1 =/= rs2)then pc = pc + imm_B else pc = pc + WLEN
- blt
疑似コード
if(rs1 < rs2) then pc = pc + imm_B else pc = pc + WLEN
- bltu
疑似コード
if(rs1.asUInt < rs2.asUInt) then pc = pc + imm_B else pc = pc + WLEN
- bge
疑似コード
if(rs1 >= rs2)then pc = pc + imm_B else pc = pc + WLEN
- bgeu
疑似コード
if(rs1.asUInt >= rs2.asUInt)then pc = pc + imm else pc = pc + WLEN
前回のコミットと比較し、すべての分岐命令においてALUを使うように修正しました。回路規模肥大化の抑制を期待できます。
バブルロジックの導入
next_inst_is_valid
が真の時はそのまま、偽の時は次アドレスの命令をバブルします。
JAL、JALR命令は特に条件なく分岐をしてくれるので、この命令があった場合は次にフェッチしている命令語をバブルします。
Bから始まる分岐命令の場合、条件が真となれば次段でフェッチしている命令をバブルします。偽の場合は分岐しませんので命令はそのまま残します。
// bubble logic next_inst_is_valid := true.B switch (id_ctrl.br_type) { is( BR_NE ) { when(val_rs1 =/= val_rs2) { next_inst_is_valid.:=(false.B)} // NEQ = true: bubble next inst & branch .otherwise { next_inst_is_valid.:=(true.B) } } is( BR_EQ ) { when(val_rs1 === val_rs2) { next_inst_is_valid.:=(false.B)} // EQ = true: bubble next inst & branch .otherwise { next_inst_is_valid.:=(true.B) } } is( BR_GE ) { when(val_rs1 > val_rs2) { next_inst_is_valid.:=(false.B)} // GE = true: bubble next inst & branc .otherwise { next_inst_is_valid.:=(true.B) } } is( BR_GEU ) { when(val_rs1.asUInt > val_rs2.asUInt) { next_inst_is_valid.:=(false.B)} // GE = true: bubble next inst & branch .otherwise { next_inst_is_valid.:=(true.B) } } is( BR_LT ) { when(val_rs1 < val_rs2){ next_inst_is_valid.:=(false.B)} // LT = true: bubble next inst & branch .otherwise { next_inst_is_valid.:=(true.B) } } is( BR_LTU ) { when(val_rs1.asUInt() < val_rs2.asUInt) { next_inst_is_valid.:=(false.B)} // LT = true: bubble next inst & branch .otherwise { next_inst_is_valid.:=(true.B) } } is( BR_J ) { next_inst_is_valid.:=(false.B) } // JAL is( BR_JR ) { next_inst_is_valid.:=(false.B) } // JALR }
命令語を間違えて解釈していた
Uが末尾につくアセンブラ命令。bltuやbgeu。
immがunsignedだと本気で誤解していました。
逝ってこいレジスタ。負が扱えないので帰ってこれない。すごい。作ってみてもいいかもですね。(ダメだろ)
正確には、rs1、rs2を比較時にunsignedとして扱う命令でした。ですよね。。immだったら、なんのためにauipc
命令あるんだよという話になります。
JAL/JALR命令のテスト
そんなこんないろいろ間違いを治しつつ、テストコードを書きました。
(ここに掲載したのはjalとjalrだけです。bから始まる分岐命令も一応動作を確認済みです)
早くriscv-testを走らせられるレベルになりたい。
_start0: nop nop _label1: addi x1, x0, 1 # x1 = x0 + 1 = 1 addi x2, x0, 2 # x2 = x0 + 1 = 2 addi x3, x0, 3 # x3 = x0 + 2 = 3 jal x4, _label4 # x4に次アドレス(_lavel2)を格納し, _label4へジャンプ _label2: addi x7, x0, 7 # x7 = 7 addi x8, x0, 8 # x8 = 8 (RETURN HERE) addi x9, x0, 9 # x9 = 9 addi x10, x0, 10 # x10= 0x0A addi x11, x0, 11 # x11= 0x0B _label3: jalr x0, x5,0 # x5に格納されているアドレス(= _label5)へジャンプ _label4: addi x12,x0,12 # x12= 0x0C jalr x5, x4, 4 # x5に次アドレス(_lavel5)を格納し, x4(= _label2) + 4のアドレス( addi x8, x0, 8のところ)へジャンプ→(addi x7, x0, 7)は実行されない _label5: jal x0, _label5 # 無限ループ
結果を見てみます。intelliJであれば画面下部のterminalのタブで
make clean make test
で動作します。
実行結果。
コード全文
コードは今後も漸進的に更新されるので、以下のように-bでタグを指定してcloneするのがいいと思います。
git clone http://github.com/panda5mt/KyogenRV -b 0.0.10.3 --depth 1 cd KyogenRV/
macOSな方はGNU odを使う関係上以下のようにしてcoreutilsをインストールしてください。
brew install coreutils
makefileでOSによってod/godをスイッチしています。
アセンブラは(プロジェクトフォルダ)/src/sw/test.sにあります。適宜書き換えてみてください。
nano src/sw/test.s
次回はデータRAMの実装に入ります。