FPGA開発日記

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

QEMUに入門してみる(16. 命令デコーダの追加)

前回の続き。所望の場所から命令をフェッチできるようになったので、次は命令デコーダを追加していきたい。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()のコンパイル結果は以下のような感じだ。結構長く感じるのはデバッグビルドだからか。リリースビルドなら、おそらくインライン展開されてどこに配置されているのかもわからなくなるだろう。

f:id:msyksphinz:20200718125338p:plain