FPGA開発日記

カテゴリ別記事インデックス https://msyksphinz.github.io/github_pages , English Version https://fpgadevdiary.hatenadiary.com/

Rocket-Chip/BOOMにおけるRVC命令の実装方法について

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)
f:id:msyksphinz:20210711230956p:plain

命令フィールドの[15:13]ビットを使ってテーブルを選択すれば、適切な変換関数を取得することができる。

それぞれの変換関数であるが、例えばc.ld命令であればld命令に変換するので、以下の関数を使っている。 この関数は、何ということはない、無理やりLD命令の機械語ビットを作り上げるだけである。

    def ld = inst(Cat(ldImm, rs1p, 3.U(3.W), rs2p, 0x03.U(7.W)), rs2p, rs1p, rs2p)
f:id:msyksphinz:20210711231208p:plain