Rocket-ChipはRISC-Vに対応したCPUコアであるが、RVCと呼ばれる16ビット短縮命令をサポートしている。 RVCは、RISC-VのISAアルファベット表記において"C"と表現される。例えばRV64IMAFDCだと、整数・乗算・除算とともに16ビット短縮命令がサポートされていることを意味する。
Rocket-ChipやBOOMはChiselで実装されているので、RVCがどのようにサポートされているのかについてはChiselの実装を読み解く必要がある。
フロントエンドにおいて、RVCのサポートは、RVCExpanderを使ってサポートされている。 デコードの前に、RVCのプリデコード回路を用いて16ビット命令を32ビットに展開した上でデコーダに渡すという方式になっている。
RVC命令はすべて32ビットの通常命令にマッピングすることができる。以下に表としてまとめた。 Quadrantというのは、命令フィールドの下位2ビットを示しており、これが00, 01, 10であればRVC命令であることを示している。
RVC命令 | Quadrant | 展開命令 |
---|---|---|
c.addi4spn rd', nzuimm | 00 | addi rd, x2, nzuimm |
c.fld rd', offset(rs1') | 00 | fld rd, offset(rs1) |
c.lw rd', offset(rs1') | 00 | lw rd, offset(rs1) |
c.flw rd', offset(rs1') | 00 | flw rd, offset(rs1) |
c.ld rd', offset(rs1') | 00 | ld rd, offset(rs1) |
c.fsd rd', offset(rs1') | 00 | fsd rs2, offset(rs1) |
c.sw rd', offset(rs1') | 00 | sw rs2, offset(rs1) |
c.fsw rd', offset(rs1') | 00 | fsw rs2, offset(rs1) |
c.sd rd', offset(rs1') | 00 | sd rs2, offset(rs1) |
c.nop | 01 | nop |
c.addi rd, nzimm | 01 | addi rd, rd, nzimm |
c.jal offset | 01 | jal x1, offset |
c.addiw rd, imm | 01 | addiw rd, rd, imm |
c.li rd, uimm | 01 | addi rd, x0, imm |
c.addi16sp rd',nzimm | 01 | addi x2, x2, nzimm |
c.lui rd, nzimm | 01 | lui rd, nzimm |
c.srli rd', uimm | 01 | srli rd, rd, shamt |
c.srli64 rd', uimm | 01 | srli rd, rd, shamt |
c.srai rd', uimm | 01 | srai rd, rd, shamt |
c.srai64 rd', uimm | 01 | srai rd, rd, shamt |
c.andi rd', uimm | 01 | andi rd, rd, imm |
c.sub rd', rd' | 01 | sub rd, rd, rs2 |
c.xor rd', rd' | 01 | xor rd, rd, rs2 |
c.or rd', rd' | 01 | or rd, rd, rs2 |
c.and rd', rd' | 01 | and rd, rd, rs2 |
c.subw rd', rs2 | 01 | subw rd, rd, rs2 |
c.addw rd', rs2 | 01 | addw rd, rd, rs2 |
c.j offset | 01 | jal x0, offset |
c.beqz rs1, offset | 01 | beq rs1, x0, offset |
c.bnez rs1, offset | 10 | bne rs1, x0, offset |
c.slli rd, uimm | 10 | slli rd, rd, shamt |
c.fldsp rd, offset(x2) | 10 | fld rd, offset(x2) |
c.lwsp rd, offset | 10 | lw rd, offset(x2) |
c.flwsp rd, offset | 10 | flw rd, offset(x2) |
c.ldsp rd, offset(x2) | 10 | ld rd, offset(x2) |
c.jr rd | 10 | jalr x0, 0(rs1) |
c.mv rd, rs2 | 10 | add rd, x0, rs2 |
c.ebreak | 10 | ebreak |
c.jalr rs1 | 10 | jalr x1, 0(rs1) |
c.add rd, rs2 | 10 | add rd, rd, rs2 |
c.fsdsp rs2, offset(x2) | 10 | fsd rs2, offset(sp) |
c.swsp rs2, offset(x2) | 10 | sw rs2, offset(sp) |
c.fswsp rs2, offset(x2) | 10 | fsw rs2, offset(sp) |
c.sdsp rs2, offset(x2) | 10 | sd rs2, offset(sp) |
Rocket-Chipの実装では、それぞれのQuadrantに応じてテーブルを使って変換を行っている。
src/main/scala/rocket/RVC.scala
def q0 = { def addi4spn = { val opc = Mux(x(12,5).orR, 0x13.U(7.W), 0x1F.U(7.W)) inst(Cat(addi4spnImm, sp, 0.U(3.W), rs2p, opc), rs2p, sp, rs2p) ... def q1 = { def addi = inst(Cat(addiImm, rd, 0.U(3.W), rd, 0x13.U(7.W)), rd, rd, rs2p) def addiw = { val opc = Mux(rd.orR, 0x1B.U(7.W), 0x1F.U(7.W)) ... def q2 = { val load_opc = Mux(rd.orR, 0x03.U(7.W), 0x1F.U(7.W)) def slli = inst(Cat(shamt, rd, 1.U(3.W), rd, 0x13.U(7.W)), rd, rd, rs2) def ldsp = inst(Cat(ldspImm, sp, 3.U(3.W), rd, load_opc), rd, sp, rs2)
例えば、q0
のテーブルであれば、以下のようなテーブルを作っており、以下のRVCの命令フィールドに応じて使用する変換関数を選択している。
Seq(addi4spn, fld, lw, flw, unimp, fsd, sw, fsw)
命令フィールドの[15:13]
ビットを使ってテーブルを選択すれば、適切な変換関数を取得することができる。
それぞれの変換関数であるが、例えばc.ld
命令であればld
命令に変換するので、以下の関数を使っている。
この関数は、何ということはない、無理やりLD命令の機械語ビットを作り上げるだけである。
def ld = inst(Cat(ldImm, rs1p, 3.U(3.W), rs2p, 0x03.U(7.W)), rs2p, rs1p, rs2p)