前回の続き。所望の場所から命令をフェッチできるようになったので、次は命令デコーダを追加していきたい。QEMUにおける命令デコーダはDSLで記述されており、MYRISCVXの場合はqemu/target/myriscvx/insn32.decode
に置いている。これはRISC-Vにおけるデコーダの配置場所と全く同じだ。
qemu/target/myriscvx/insn32.decode
# Formats lw ............ ..... 010 ..... 0000011 @i sw ....... ..... ..... 010 ..... 0100011 @s
とりあえずリセットベクタの最初の数命令が動いてほしいので、AUIPC
, LUI
, ADDI
などを追加した。CSR命令はとりあえず省略だ。
# *** RV32I Base Instruction Set *** lui .................... ..... 0110111 @u auipc .................... ..... 0010111 @u jal .................... ..... 1101111 @j jalr ............ ..... 000 ..... 1100111 @i lw ............ ..... 010 ..... 0000011 @i sw ....... ..... ..... 010 ..... 0100011 @s addi ............ ..... 000 ..... 0010011 @i
と、こんな感じで命令デコーダを追加すると自動的に命令テンプレートが生成されるので、これを追加していかなければならない。命令のテンプレートはTCGを利用して命令の動作を記述していく。これは普通のシミュレータと非常に似ている。
static bool trans_lui(DisasContext *ctx, arg_lui *a) { if (a->rd != 0) { tcg_gen_movi_tl(cpu_gpr[a->rd], a->imm); } return true; } static bool trans_auipc(DisasContext *ctx, arg_auipc *a) { if (a->rd != 0) { tcg_gen_movi_tl(cpu_gpr[a->rd], a->imm + ctx->base.pc_next); } return true; }
例えばADDI
命令の場合は以下のようなTCGを組み合わせることになる。
static bool trans_addi(DisasContext *ctx, arg_addi *a) { TCGv source1; source1 = tcg_temp_new(); gen_get_gpr(source1, a->rs1); tcg_gen_addi_tl(source1, source1, a->imm); gen_set_gpr(a->rd, source1); tcg_temp_free(source1); return true; }
trans_addi
ではレジスタを取得、加算のためのTCGを追加して、レジスタに結果を格納する。これでADDI命令が実行可能となる。
これで再度QEMUをビルドして動作を確認する。この際、CSR命令が嫌なのでリセットベクタを以下のように急遽変更した。
qemu/hw/myriscvx/virt.c
static void myriscvx_virt_board_init(MachineState *machine) { const struct MemmapEntry *memmap = virt_memmap; MYRISCVXVirtState *s = MYRISCVX_VIRT_MACHINE(machine); MemoryRegion *system_memory = get_system_memory(); ... // /* reset vector */ // uint32_t reset_vec[8] = { // 0x00000297, /* 1: auipc t0, %pcrel_hi(dtb) */ // 0x02028593, /* addi a1, t0, %pcrel_lo(1b) */ // 0xf1402573, /* csrr a0, mhartid */ //#if defined(TARGET_MYRISCVX32) // 0x0182a283, /* lw t0, 24(t0) */ //#elif defined(TARGET_MYRISCVX64) // 0x0182b283, /* ld t0, 24(t0) */ //#endif // 0x00028067, /* jr t0 */ // 0x00000000, // start_addr, /* start: .dword */ // 0x00000000, // /* dtb: */ // }; uint32_t reset_vec[8] = { 0x00000297, /* 1: auipc t0, %pcrel_hi(dtb) */ 0x02028593, /* addi a1, t0, %pcrel_lo(1b) */ 0x00128593, 0x00228593, 0x00328593, 0x00428593, 0x00528593, start_addr, /* start: .dword */ };
$ ${QEMU_BUILD}/myriscvx64-softmmu/qemu-system-myriscvx64 --machine virt \ --d in_asm --nographic \ --d op \ # TCGオペレーションのログを表示する --trace myriscvx_trap \ # myriscvx_trapを追加する --kernel ${RISCV}/riscv64-unknown-elf/share/riscv-tests/isa/rv64ui-p-simple
QEMU 5.0.0 monitor - type 'help' for more information (qemu) OP: ld_i32 tmp0,env,$0xfffffffffffffff0 movi_i32 tmp1,$0x0 brcond_i32 tmp0,tmp1,lt,$L0 ---- 0000000000001000 movi_i64 x5/t0,$0x1000 ---- 0000000000001004 mov_i64 tmp2,x5/t0 movi_i64 tmp3,$0x20 add_i64 tmp2,tmp2,tmp3 mov_i64 x11/a1,tmp2 ---- 0000000000001008 mov_i64 tmp2,x5/t0 movi_i64 tmp3,$0x1 add_i64 tmp2,tmp2,tmp3 mov_i64 x11/a1,tmp2 ---- 000000000000100c mov_i64 tmp2,x5/t0 movi_i64 tmp3,$0x2 add_i64 tmp2,tmp2,tmp3 mov_i64 x11/a1,tmp2 ---- 0000000000001010 mov_i64 tmp2,x5/t0
うまく実行できていることが確認できた。では、引き続きTCGの追加を進めていこう。
ちなみにtrans_addi()
のコンパイル結果は以下のような感じだ。結構長く感じるのはデバッグビルドだからか。リリースビルドなら、おそらくインライン展開されてどこに配置されているのかもわからなくなるだろう。