FPGA開発日記

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

RISC-Vシミュレータ改造のTips

RISC-Vシミュレータで最も信頼できる実装はSpikeシミュレータである。 SpikeシミュレータはC++で書かれており、比較的簡単に解析ができるが、RTLとの実装の違いを確認したり、ソフトウェアの動作を確認したい場合に適用できるTipsがいろいろある。

各命令でアップデートされたレジスタ値をログに残したい

ビルド時に以下のオプションを追加することでレジスタ書き込みのログを残すことができる。 --enable-commitlog=yesをオプションに追加することで、ログを増やすことが可能だ。

  • build.sh
diff --git a/build-spike-pk.sh b/build-spike-pk.sh
index 02f3202..09e1512 100755
--- a/build-spike-pk.sh
+++ b/build-spike-pk.sh
@@ -14,7 +14,7 @@ fi
 echo "Starting RISC-V Toolchain build process"

 build_project riscv-fesvr --prefix=$RISCV
-build_project riscv-isa-sim --prefix=$RISCV --with-fesvr=$RISCV
+build_project riscv-isa-sim --prefix=$RISCV --with-fesvr=$RISCV --enable-commitlog=yes
 CC= CXX= build_project riscv-pk --prefix=$RISCV --host=riscv64-unknown-elf

 echo -e "\\nRISC-V Toolchain installation completed!"

ついでに、sed等でログを処理したい場合に備えて、レジスタアドレスを2桁で0埋めしておく。

  • riscv-isa-sim/riscv/execute.cc
@@ -52,7 +54,7 @@ static void commit_log_print_insn(state_t* state, reg_t pc, insn_t insn)
     bool fp = reg.addr & 1;
     int rd = reg.addr >> 1;
     int size = fp ? flen : xlen;
-    fprintf(stderr, ") %c%2d ", fp ? 'f' : 'x', rd);
+    fprintf(stderr, ") %c%02d ", fp ? 'f' : 'x', rd);
     commit_log_print_value(size, reg.data.v[1], reg.data.v[0]);
  • --enable-commitlog=no (ログ無し版)
core   0: 0xffffffe00092bfd8 (0x00001101) addi    sp, sp, -32
core   0: 0xffffffe00092bfda (0x0000ec06) sd      ra, 24(sp)
core   0: 0xffffffe00092bfdc (0x0000e822) sd      s0, 16(sp)
core   0: 0xffffffe00092bfde (0x0000e426) sd      s1, 8(sp)
core   0: 0xffffffe00092bfe0 (0x00001000) addi    s0, sp, 32
core   0: 0xffffffe00092bfe2 (0x003d5497) auipc   s1, 0x3d5
core   0: 0xffffffe00092bfe6 (0x69e48493) addi    s1, s1, 1694
core   0: 0xffffffe00092bfea (0x0000e088) sd      a0, 0(s1)
core   0: 0xffffffe00092bfec (0x6e6980ef) jal     pc + 0x986e6
  • --enable-commitlog=yes (ログあり版)
core   0:  143191650: 0xffffffe00092bfd8 (0x00001101) addi    sp, sp, -32
1 0xffffffe00092bfd8 (0x1101) x02 0xffffffe079a51ea0
core   0:  143191651: 0xffffffe00092bfda (0x0000ec06) sd      ra, 24(sp)
1 0xffffffe00092bfda (0xec06)
core   0:  143191652: 0xffffffe00092bfdc (0x0000e822) sd      s0, 16(sp)
1 0xffffffe00092bfdc (0xe822)
core   0:  143191653: 0xffffffe00092bfde (0x0000e426) sd      s1, 8(sp)
1 0xffffffe00092bfde (0xe426)
core   0:  143191654: 0xffffffe00092bfe0 (0x00001000) addi    s0, sp, 32
1 0xffffffe00092bfe0 (0x1000) x08 0xffffffe079a51ec0
core   0:  143191655: 0xffffffe00092bfe2 (0x003d5497) auipc   s1, 0x3d5
1 0xffffffe00092bfe2 (0x003d5497) x09 0xffffffe000d00fe2
core   0:  143191656: 0xffffffe00092bfe6 (0x69e48493) addi    s1, s1, 1694
1 0xffffffe00092bfe6 (0x69e48493) x09 0xffffffe000d01680
core   0:  143191657: 0xffffffe00092bfea (0x0000e088) sd      a0, 0(s1)
1 0xffffffe00092bfea (0xe088)
core   0:  143191658: 0xffffffe00092bfec (0x6e6980ef) jal     pc + 0x986e6
1 0xffffffe00092bfec (0x6e6980ef) x01 0xffffffe00092bff0
core   0:  143191659: 0xffffffe0009c46d2 (0x00007139) addi    sp, sp, -64
1 0xffffffe0009c46d2 (0x7139) x02 0xffffffe079a51e60
core   0:  143191660: 0xffffffe0009c46d4 (0x0000f822) sd      s0, 48(sp)
1 0xffffffe0009c46d4 (0xf822)

ちなみに、追加されたログの意味であるが、

1 0xffffffe00092bfd8 (0x1101) x02 0xffffffe079a51ea0
- ------------------  ------  --- ------------------
|          |             |     |          |
|          |             |     |          +--------- レジスタ書き込みデータ
|          |             |     +-------------------- レジスタ書き込みアドレス
|          |             +-------------------------- 命令Hex
|          +---------------------------------------- 命令アドレス
+--------------------------------------------------- プロセッサの実行モード(0:User, 1:Supervisor, 3:Machine)

仮想アドレスから物理アドレスへの変換過程を追いかける

これはmmu.cに実装されている。walk()関数を見てみよう。

  • riscv-isa-sim/riscv/mmu.cc
reg_t mmu_t::walk(reg_t addr, access_type type, reg_t mode)
{
  vm_info vm = decode_vm_info(proc->max_xlen, mode, proc->get_state()->satp);
  if (vm.levels == 0)
    return addr & ((reg_t(2) << (proc->xlen-1))-1); // zero-extend from xlen
...
      reg_t value = (ppn | (vpn & ((reg_t(1) << ptshift) - 1))) << PGSHIFT;
      return value;
    }
  }

fail:
  switch (type) {
    case FETCH: throw trap_instruction_page_fault(addr);
    case LOAD: throw trap_load_page_fault(addr);
...

時によって物理アドレスへの変換が行われていない?

それはTLBが実装されているから。TLBを使用する、Load/Store系の命令は以下で定義されている。

  • riscv-isa-sim/riscv/mmu.h
  #define load_func(type) \
    inline type##_t load_##type(reg_t addr) { \
      if (unlikely(addr & (sizeof(type##_t)-1))) \
        return misaligned_load(addr, sizeof(type##_t)); \
      reg_t vpn = addr >> PGSHIFT; \
      if (likely(tlb_load_tag[vpn % TLB_ENTRIES] == vpn)) \
        return *(type##_t*)(tlb_data[vpn % TLB_ENTRIES].host_offset + addr); \
      if (unlikely(tlb_load_tag[vpn % TLB_ENTRIES] == (vpn | TLB_CHECK_TRIGGERS))) { \
        type##_t data = *(type##_t*)(tlb_data[vpn % TLB_ENTRIES].host_offset + addr); \
        if (!matched_trigger) { \
          matched_trigger = trigger_exception(OPERATION_LOAD, addr, data); \
          if (matched_trigger) \
            throw *matched_trigger; \
        } \
        return data; \
      } \
      type##_t res; \
      load_slow_path(addr, sizeof(type##_t), (uint8_t*)&res); \
      return res; \
    }

  // load value from memory at aligned address; zero extend to register width
  load_func(uint8)
  load_func(uint16)
  load_func(uint32)
  load_func(uint64)

したがって、#define load_funcから、TLB関係の処理をすべて抜けば、仮想アドレスから物理アドレスの変換が常に行われることになる。

Trapはどのように実装されているの?

それでは、タイマ割り込みを見てみる。 RISC-Vにおいて、MIE(Machine Mode Interrupt Enable)とMIP(Machine Mode Interrupt Pending)レジスタで、同じビットが両方とも1が立っていれば割り込みが発生することになる。 実装はriscv/processor.ccに記述されている。

  • riscv-isa-sim/riscv/processor.cc
void processor_t::take_interrupt(reg_t pending_interrupts)
{
...
    throw trap_t(((reg_t)1 << (max_xlen-1)) | ctz(enabled_interrupts));
  }
}

最後にトラップが発生していることが分かる。 このトラップはriscv/execute.ccでCatchされる。take_trapで実際の割り込み処理が実行される。

  • riscv-isa-sim/riscv/execute.cc
// fetch/decode/execute loop
void processor_t::step(size_t n)
{
  if (state.dcsr.cause == DCSR_CAUSE_NONE) {
    if (halt_request) {
      enter_debug_mode(DCSR_CAUSE_DEBUGINT);
...
    catch(trap_t& t)
    {
      take_trap(t, pc);
      n = instret;

      if (unlikely(state.single_step == state.STEP_STEPPED)) {
        state.single_step = state.STEP_NONE;
        enter_debug_mode(DCSR_CAUSE_STEP);
      }
    }
f:id:msyksphinz:20180815233949p:plain