FPGA開発日記

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

QEMUに入門してみる(15. QEMUにおけるリセットベクタの追加方法)

前回QEMUのトレース情報を取得しながらシミュレーションをスタートできるようになったが、よく見てみると最初の命令でいきなりIllegal Instructionで例外に飛んでいる。当たり前だ、そこに命令は配置されておらず、しかもデコーダもろくに実装していないので例外に飛んでいるのは至極順当どいえよう。

次にやらなければならないのは2つ:

  • リセットベクタを正しく設定し、最初のフェッチを成功させる。
  • 命令デコーダを追加し、命令を正しく実行できるようにする。

まずはリセットベクタの位置を修正しなければ始まらない。この実装方法について調査する。

まず、QEMUにおけるCPUが立ち上がる(リセット解除ではなくインスタンス化される)のはおそらくxxx_cpu_realize()という関数が担当している。一方で、リセットが行われて命令実行が開始されるのはxxx_cpu_reset()という関数の役割である。すると、CPUのリセット時にリセットベクタを設定するのが良さそうだ。

static void myriscvx_cpu_reset(DeviceState *dev)
{
  CPUState *cs = CPU(dev);
  MYRISCVXCPU *cpu = MYRISCVX_CPU(cs);
  MYRISCVXCPUClass *mcc = MYRISCVX_CPU_GET_CLASS(cpu);
  CPUMYRISCVXState *env = &cpu->env;

#ifndef CONFIG_USER_ONLY
  env->priv = PRV_M;
  env->mstatus &= ~(MSTATUS_MIE | MSTATUS_MPRV);
  env->mcause = 0;
  env->pc = env->resetvec;        // リセットベクタの設定
#endif
  cs->exception_index = EXCP_NONE;
  env->load_res = -1;

  mcc->parent_reset(dev);
  cs->exception_index = EXCP_NONE;
}

ここでenv->resetvecmyriscvx_cpu_realize()実行時に設定される。設定される値は、マクロDEFAULT_RSTVECで設定されている。

static void myriscvx_cpu_realize(DeviceState *dev, Error **errp)
{
  CPUState *cs = CPU(dev);
  MYRISCVXCPU *cpu = MYRISCVX_CPU(dev);
  CPUMYRISCVXState *env = &cpu->env;
  MYRISCVXCPUClass *mcc = MYRISCVX_CPU_GET_CLASS(dev);
...
  // set_priv_version(env, priv_version);
  set_resetvec(env, DEFAULT_RSTVEC);
...
  • qemu/target/myriscvx/cpu_bits.h
/* Default Reset Vector adress */
#define DEFAULT_RSTVEC      0x1000

この状態でQEMUをビルドしてトレースログを取ってみる。無事に0x1000からフェッチが開始された。ただしまだIllegal Instructionで例外に飛ぶ。デコーダを追加していないので当然である。

QEMU 5.0.0 monitor - type 'help' for more information
(qemu) ----------------
IN:
Priv: 3; Virt: 0
0x0000000000001000:
OBJD-T: 97020000

29197@1595039322.887998:myriscvx_trap hart:0, async:0, cause:2, epc:0x1000, tval:0x0, desc=illegal_instruction
----------------
IN:

ちなみに、0x1000にはhw/myriscvx/virt.cで設定したリセットベクタ用のブートROMが格納されている、はずだ。

  • 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: */
  };

  /* copy in the reset vector in little_endian byte order */
  for (i = 0; i < sizeof(reset_vec) >> 2; i++) {
    reset_vec[i] = cpu_to_le32(reset_vec[i]);
  }
  rom_add_blob_fixed_as("mrom.reset", reset_vec, sizeof(reset_vec),
                        memmap[VIRT_MROM].base, &address_space_memory);

一応、最初の0x0000_0297がトレースログで見えているので、動いてはいるのだろう。ということはこのトレースログはビッグエンディアンで表示しているのか?

Priv: 3; Virt: 0
0x0000000000001000:
OBJD-T: 97020000        // <-- ココ