前回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->resetvec
はmyriscvx_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 // <-- ココ