FPGA開発日記

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

Binary Translation型エミュレータを作る(Compressed命令の実装検討)

Binary Translation型のエミュレータをRustで作るプロジェクト、ある程度ベンチマークテストも動くようになってきたので、次なる大物はCompressed命令のサポートだろう。Compressed命令はRISC-Vの命令系列の中でも16ビット長のもので、一般的な32ビットの命令と異なりフェッチ幅を削減するために使用されている。

この命令については、C++で開発したRISC-V命令セットシミュレータではサポートしている。C++のシミュレータでは、デコーダは自動生成させたのでこちらも対応しているはずだ。これをRustに置き換えて実装する。

その結果、現状の32ビットの命令系列の生成に加えて、16ビット長の命令もデコードできるように拡張した。

fn decode_inst_ld_01_f3_001_r3_00000_f2_00_r2_00000_r1_00001 (inst: u32) -> Option<(RiscvInstId, usize)> {
    let field_rd = ((inst as u64) >> 7) & (((1 as u64) << 5) - 1);
    return match field_rd {
        0x00 | 0x01 | 0x02 | 0x03 | 0x04 | 0x05 | 0x06 | 0x07 =>
        Some((RiscvInstId::C_SRLI64, 2)),
        0x08 | 0x09 | 0x0a | 0x0b | 0x0c | 0x0d | 0x0e | 0x0f =>
        Some((RiscvInstId::C_SRAI64, 2)),
        0x10 | 0x11 | 0x12 | 0x13 | 0x14 | 0x15 | 0x16 | 0x17 =>
        Some((RiscvInstId::C_ANDI, 2)),
        0x18 | 0x19 | 0x1a | 0x1b | 0x1c | 0x1d | 0x1e | 0x1f =>
        // Remaining Instruction is 2
        // c.subw     cr[9:7],r[6:2]
        // c.addw     cr[9:7],r[6:2]
        decode_inst_ld_01_f3_001_r3_00000_f2_00_r2_00000_r1_00001_rd_11000 (inst),
      _ => None,
    }
}
fn decode_inst_ld_01_f3_001_r3_00000_f2_00_r2_00000_r1_00001_rd_11000 (inst: u32) -> Option<(RiscvInstId, usize)> {
    let field_op = ((inst as u64) >> 2) & (((1 as u64) << 5) - 1);
    return match field_op {
        0x00 | 0x01 | 0x02 | 0x03 | 0x04 | 0x05 | 0x06 | 0x07 =>
        Some((RiscvInstId::C_SUBW, 2)),
        0x08 | 0x09 | 0x0a | 0x0b | 0x0c | 0x0d | 0x0e | 0x0f =>
        Some((RiscvInstId::C_ADDW, 2)),
      _ => None,
    }
}

これまで命令デコーダはデコード結果の命令IDを返すだけだったが、これに加えて命令バイト長も返すようにした。これにより次に進めるべきPCの値を設定できる。

とりあえず1つ目の命令として、C.ADDI4SPN命令を実装してみた。

    pub fn translate_c_addi4spn(&mut self, inst: &InstrInfo) -> Vec<TCGOp> {
        let imm_const: u64 = get_nzuimm!(inst.inst as i32);
        let rs1_addr= 2;  // sp
        let rd_addr = get_c_rd_addr!((inst.inst >> 2) & 0x7);

        let mut tcg_lists = vec![];

        if rd_addr == 0 {
            return vec![];
        }

        let source1 = self.tcg_temp_new();

        tcg_lists.push(TCGOp::tcg_get_gpr(source1, rs1_addr));
        tcg_lists.push(TCGOp::new_3op(TCGOpcode::ADD_64BIT, source1, source1, TCGv::new_imm(imm_const)));
        tcg_lists.push(TCGOp::tcg_set_gpr(rd_addr, source1));

        self.tcg_temp_free(source1);
        tcg_lists
    }

こんな感じで中身は単なる加算命令なので、加算命令のTCG生成を使用しつつ即値フィールドの切り出し方法と、レジスタのデコード方法を切り替えるようにした。

この結果、以下のように命令の実行が確認できた。

========= BLOCK START =========
50: Guest PC Address = 80002016
 0000000080002016:0000000080002016 Hostcode 13b71fe8 : c.addi4spn a0, sp, 1020
00007F967D5B0000 488B9518000000       mov       0x18(%rbp),%rdx
00007F967D5B0007 4881C2FC030000       add       $0x3FC,%rdx
00007F967D5B000E 48899558000000       mov       %rdx,0x58(%rbp)
00007F967D5B0015 E9F5FF2B00           jmp       0x0000_7F96_7D87_000F
x00(zero ) = 0000000000000000  x01(ra   ) = 0000000000000000  x02(sp   ) = 0000000000001234  x03(gp   ) = 0000000000000002
x04(tp   ) = 0000000000000000  x05(t0   ) = 00000000800000fc  x06(t1   ) = 0000000000000000  x07(t2   ) = 000000000000029b
x08(s0/fp) = 0000000000000000  x09(s1   ) = 0000000000000000  x10(a0   ) = 0000000000001630  x11(a1   ) = 000000000000029b
x12(a2   ) = 0000000000000000  x13(a3   ) = 0000000000000000  x14(a4   ) = 0000000000000000  x15(a5   ) = 0000000000000000
x16(a6   ) = 0000000000000000  x17(a7   ) = 0000000000000000  x18(s2   ) = 0000000000000000  x19(s3   ) = 0000000000000000
x20(s4   ) = 0000000000000000  x21(s5   ) = 0000000000000000  x22(s6   ) = 0000000000000000  x23(s7   ) = 0000000000000000
x24(s8   ) = 0000000000000000  x25(s9   ) = 0000000000000000  x26(s10  ) = 0000000000000000  x27(s11  ) = 0000000000000000
x28(t3   ) = 0000000000000000  x29(t4   ) = 0000000000000000  x30(t5   ) = 0000000000000000  x31(t6   ) = 0000000000000000

フレームワークは一通り完成したので、今後はこれにサポート命令を増やしていく作業に入る。