4.3 节 RISC-V 架构的 Difftest (Spike) 问题
taoky opened this issue · 6 comments
标准RISC-V的分页机制需要在S模式及U模式下才能开启, 而在M模式下的访存并不会进行MMU的地址转换. 但我们在NEMU中进行了简化, 允许M模式的访存也进行地址转换, 这样可以避免引入S模式相关的细节……
然而在 nemu/tools/spike-diff/repo/riscv/mmu.h 的 decode_vm_info()
函数中,如果目前模式是 M 模式,那么会直接返回全零的 vm_info
:
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 不会去做地址转换:
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跑的
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那边不做也可以
回到了完成 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
, 你可以试试这样是否能解决问题吗?
这确实是个问题, 感觉需要在进入
__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 也还是会出错的原因。