NJU-ProjectN/ics-pa-gitbook

4.3 节 RISC-V 架构的 Difftest (Spike) 问题

taoky opened this issue · 6 comments

taoky commented

标准RISC-V的分页机制需要在S模式及U模式下才能开启, 而在M模式下的访存并不会进行MMU的地址转换. 但我们在NEMU中进行了简化, 允许M模式的访存也进行地址转换, 这样可以避免引入S模式相关的细节……

然而在 nemu/tools/spike-diff/repo/riscv/mmu.h 的 decode_vm_info() 函数中,如果目前模式是 M 模式,那么会直接返回全零的 vm_info

https://github.com/NJU-ProjectN/riscv-isa-sim/blob/4740dedcfd58f1b881d570cca0f8b697e17b8a5f/riscv/mmu.h#L496-L499

inline vm_info decode_vm_info(int xlen, bool stage2, reg_t prv, reg_t satp)
{
  if (prv == PRV_M) {
    return {0, 0, 0, 0, 0};

这导致了 M 模式下作为对照的 Spike 不会去做地址转换:

https://github.com/NJU-ProjectN/riscv-isa-sim/blob/4740dedcfd58f1b881d570cca0f8b697e17b8a5f/riscv/mmu.cc#L384-L390

reg_t mmu_t::walk(reg_t addr, access_type type, reg_t mode, bool virt, bool hlvx)
{
  reg_t page_mask = (reg_t(1) << PGSHIFT) - 1;
  reg_t satp = (virt) ? proc->get_state()->vsatp : proc->get_state()->satp;
  vm_info vm = decode_vm_info(proc->max_xlen, false, mode, satp);
  if (vm.levels == 0)
    return s2xlate(addr, addr & ((reg_t(2) << (proc->xlen-1))-1), type, type, virt, hlvx) & ~page_mask; // zero-extend from xlen

从而导致 difftest 出错,但是 gitbook 中似乎没有提到这一点。如果 PA 的 NEMU 不打算去实现 S 模式的话,这里的修改恐怕是必须的,可能可以加一个 macro 来弄。

AM的代码里面设置了MPRV, Spike应该会做地址转换, 我这边是可以开difftest跑的

taoky commented

AM的代码里面设置了MPRV, Spike应该会做地址转换, 我这边是可以开difftest跑的

emm 看来我得回到之前的 commit 看一下。另外 https://github.com/NJU-ProjectN/riscv-isa-sim/blob/4740dedcfd58f1b881d570cca0f8b697e17b8a5f/riscv/mmu.cc#L58-L71

  reg_t mode = proc->state.prv;
  if (type != FETCH) {
    if (!proc->state.debug_mode && get_field(proc->state.mstatus, MSTATUS_MPRV)) {
      mode = get_field(proc->state.mstatus, MSTATUS_MPP);
      if (get_field(proc->state.mstatus, MSTATUS_MPV) && mode != PRV_M)
        virt = true;
    }
    if (xlate_flags & RISCV_XLATE_VIRT) {
      virt = true;
      mode = get_field(proc->state.hstatus, HSTATUS_SPVP);
    }
  }

  reg_t paddr = walk(addr, type, mode, virt, hlvx) | (addr & (PGSIZE-1));

从代码看,即使设置了 MPRV,取指仍然不会做地址转换。

在内核中取指是恒等映射, 转换前后的结果一样, Spike那边不做也可以

taoky commented

回到了完成 4.4 之前的 commit 看了一下,在 diff_init() 里加上 p->debug = true 之后报错如下:

core   0: 0x000000004000f938 (0x02065613) srli    a2, a2, 32
core   0: 0x000000004000f93c (0x00000073) ecall
core   0: exception trap_user_ecall, epc 0x000000004000f93c
core   0: 0x0000000080001c00 (0xee010113) addi    sp, sp, -288
core   0: 0x0000000080001c04 (0x00113423) sd      ra, 8(sp)
core   0: exception trap_store_access_fault, epc 0x0000000080001c04
core   0:           tval 0x000000007ffffd98

所以情况应该是,在完成 4.4 节的内容之前 U 模式 ecall 到 M 模式之后 __am_asm_trap 使用的 sp 还是用户栈,但是状态已经是 M 模式了,所以访问出错。

这确实是个问题, 感觉需要在进入__am_asm_trap之后马上设置MPRV位, 然后再访问sp, 你可以试试这样是否能解决问题吗?

taoky commented

这确实是个问题, 感觉需要在进入__am_asm_trap之后马上设置MPRV位, 然后再访问sp, 你可以试试这样是否能解决问题吗?

因为进入 __am_asm_trap 的时候不能用通用寄存器临时放数据,并且 csr 寄存器的立即数指令都只能五位无符号扩展,所以我改成了这样子:

__am_asm_trap:
  csrrw a0, mscratch, a0  // swap a0 and mscratch
  li a0, (1 << 17)  // MPRV
  csrs mstatus, a0
  csrrw a0, mscratch, a0  // swap back
  // ...

实现 mscratch 寄存器之后看起来可以跑,但是有另一个我自己实现的小问题:我自己实现的 klib 的 printf 系列函数用了 GNU 的嵌套函数扩展,代码大概像这样:

static int vprintf_base(int (*put)(char s), const char *fmt, va_list ap) {
  // ...
}

void __riscv_flush_icache() {
  // nested function support requires this function
  // the nemu does not have icache/dcache now
  // so just ignore it
}

int putch_i(char ch) {
  putch(ch);
  return 0;
}

int printf(const char *fmt, ...) {
  va_list ap;

  va_start(ap, fmt);
  int ret = vprintf_base(putch_i, fmt, ap);
  va_end(ap);

  return ret;
}

int vsprintf(char *out, const char *fmt, va_list ap) {
  int put_out(char s) {
    *out = s; out++;
    return 0;
  }
  return vprintf_base(put_out, fmt, ap);
}

修改之后发现会在栈上 trap_instruction_access_fault:

core   0: 0x0000000080002264 (0x000900e7) jalr    s2
core   0: exception trap_instruction_access_fault, epc 0x000000007ffffbd8
core   0:           tval 0x000000007ffffbd8
...
pc: 0x7ffffbdc
$0: 0x00000000	ra: 0x80002268	sp: 0x7ffffb20	gp: 0x00000000
tp: 0x00000000	t0: 0x00000008	t1: 0x7ffffcc0	t2: 0x7ffffbd8
s0: 0x00000000	s1: 0x80003f01	a0: 0x00000057	a1: 0x80003f00
a2: 0x7ffffc90	a3: 0x0000012c	a4: 0x00000025	a5: 0x00000057
a6: 0x80000a34	a7: 0x00000000	s2: 0x7ffffbd8	s3: 0x7ffffc90
s4: 0xffffffff800004d1	s5: 0x8000480c	s6: 0x7ffffb30	s7: 0x00000009
s8: 0x00000000	s9: 0x00000000	s10: 0x00000000	s11: 0x00000000
t3: 0x00000000	t4: 0x00000000	t5: 0x00000000	t6: 0x00000000

我还没有仔细看它的汇编,但是搜索发现这个扩展似乎需要在栈上运行代码 (trampoline),这有可能是我的实现在 difftest 即使一开始就加上 MPRV 也还是会出错的原因。