Skip to content

Preliminary support for MMU emulation #438

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Oct 28, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,10 @@ jobs:
run: |
make -C tests/system/alignment/
make distclean && make ENABLE_EXT_C=0 ENABLE_SYSTEM=1 misalign-in-blk-emu -j$(nproc)
- name: MMU test
run: |
make -C tests/system/mmu/
make distclean && make ENABLE_SYSTEM=1 mmu-test -j$(nproc)
- name: gdbstub test
run: |
make distclean && make ENABLE_GDBSTUB=1 gdbstub-test -j$(nproc)
Expand Down
14 changes: 14 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,10 @@ CFLAGS += $(CFLAGS_NO_CET)

OBJS_EXT :=

ifeq ($(call has, SYSTEM), 1)
OBJS_EXT += system.o
endif

# Integer Multiplication and Division instructions
ENABLE_EXT_M ?= 1
$(call set-feature, EXT_M)
Expand Down Expand Up @@ -299,6 +303,16 @@ misalign-in-blk-emu: $(BIN)
exit 1; \
fi;

EXPECTED_mmu = STORE PAGE FAULT TEST PASSED!
mmu-test: $(BIN)
$(Q)$(PRINTF) "Running vm.elf ... "; \
if [ "$(shell $(BIN) tests/system/mmu/vm.elf | tail -n 2)" = "$(strip $(EXPECTED_mmu)) inferior exit code 0" ]; then \
$(call notice, [OK]); \
else \
$(PRINTF) "Failed.\n"; \
exit 1; \
fi;

# Non-trivial demonstration programs
ifeq ($(call has, SDL), 1)
doom_action := (cd $(OUT); ../$(BIN) riscv32/doom)
Expand Down
5 changes: 2 additions & 3 deletions src/decode.c
Original file line number Diff line number Diff line change
Expand Up @@ -907,9 +907,8 @@ static inline bool op_system(rv_insn_t *ir, const uint32_t insn)
default: /* illegal instruction */
return false;
}
if (!csr_is_writable(ir->imm) && ir->rs1 != rv_reg_zero)
return false;
return true;

return csr_is_writable(ir->imm) || (ir->rs1 == rv_reg_zero);
}

/* MISC-MEM: I-type
Expand Down
230 changes: 98 additions & 132 deletions src/emulate.c
Original file line number Diff line number Diff line change
Expand Up @@ -41,130 +41,15 @@ extern struct target_ops gdbstub_ops;
#define IF_rs2(i, r) (i->rs2 == rv_reg_##r)
#define IF_imm(i, v) (i->imm == v)

/* RISC-V exception code list */
/* clang-format off */
#define RV_TRAP_LIST \
IIF(RV32_HAS(EXT_C))(, \
_(insn_misaligned, 0) /* Instruction address misaligned */ \
) \
_(illegal_insn, 2) /* Illegal instruction */ \
_(breakpoint, 3) /* Breakpoint */ \
_(load_misaligned, 4) /* Load address misaligned */ \
_(store_misaligned, 6) /* Store/AMO address misaligned */ \
IIF(RV32_HAS(SYSTEM))(, \
_(ecall_M, 11) /* Environment call from M-mode */ \
)
/* clang-format on */

enum {
#define _(type, code) rv_trap_code_##type = code,
RV_TRAP_LIST
#undef _
};

static void rv_trap_default_handler(riscv_t *rv)
{
rv->csr_mepc += rv->compressed ? 2 : 4;
rv->PC = rv->csr_mepc; /* mret */
}

/*
* Trap might occurs during block emulation. For instance, page fault.
* In order to handle trap, we have to escape from block and execute
* registered trap handler. This trap_handler function helps to execute
* the registered trap handler, PC by PC. Once the trap is handled,
* resume the previous execution flow where cause the trap.
*
* Since the system emulation has not yet included in rv32emu, the page
* fault is not practical in current test suite. Instead, we try to
* emulate the misaligned handling in the test suite.
*/
#if RV32_HAS(SYSTEM)
static void trap_handler(riscv_t *rv);
#endif

/* When a trap occurs in M-mode/S-mode, m/stval is either initialized to zero or
* populated with exception-specific details to assist software in managing
* the trap. Otherwise, the implementation never modifies m/stval, although
* software can explicitly write to it. The hardware platform will define
* which exceptions are required to informatively set mtval and which may
* consistently set it to zero.
*
* When a hardware breakpoint is triggered or an exception like address
* misalignment, access fault, or page fault occurs during an instruction
* fetch, load, or store operation, m/stval is updated with the virtual address
* that caused the fault. In the case of an illegal instruction trap, m/stval
* might be updated with the first XLEN or ILEN bits of the offending
* instruction. For all other traps, m/stval is simply set to zero. However,
* it is worth noting that a future standard could redefine how m/stval is
* handled for different types of traps.
*
* For simplicity and clarity, abstracting stval and mtval into a single
* identifier called tval, as both are handled by TRAP_HANDLER_IMPL.
*/
#define TRAP_HANDLER_IMPL(type, code) \
static void rv_trap_##type(riscv_t *rv, uint32_t tval) \
{ \
/* m/stvec (Machine/Supervisor Trap-Vector Base Address Register) \
* m/stvec[MXLEN-1:2]: vector base address \
* m/stvec[1:0] : vector mode \
* m/sepc (Machine/Supervisor Exception Program Counter) \
* m/stval (Machine/Supervisor Trap Value Register) \
* m/scause (Machine/Supervisor Cause Register): store exception code \
* m/sstatus (Machine/Supervisor Status Register): keep track of and \
* controls the hart’s current operating state \
*/ \
uint32_t base; \
uint32_t mode; \
/* user or supervisor */ \
if (RV_PRIV_IS_U_OR_S_MODE()) { \
const uint32_t sstatus_sie = \
(rv->csr_sstatus & SSTATUS_SIE) >> SSTATUS_SIE_SHIFT; \
rv->csr_sstatus |= (sstatus_sie << SSTATUS_SPIE_SHIFT); \
rv->csr_sstatus &= ~(SSTATUS_SIE); \
rv->csr_sstatus |= (rv->priv_mode << SSTATUS_SPP_SHIFT); \
rv->priv_mode = RV_PRIV_S_MODE; \
base = rv->csr_stvec & ~0x3; \
mode = rv->csr_stvec & 0x3; \
rv->csr_sepc = rv->PC; \
rv->csr_stval = tval; \
rv->csr_scause = code; \
} else { /* machine */ \
const uint32_t mstatus_mie = \
(rv->csr_mstatus & MSTATUS_MIE) >> MSTATUS_MIE_SHIFT; \
rv->csr_mstatus |= (mstatus_mie << MSTATUS_MPIE_SHIFT); \
rv->csr_mstatus &= ~(MSTATUS_MIE); \
rv->csr_mstatus |= (rv->priv_mode << MSTATUS_MPP_SHIFT); \
rv->priv_mode = RV_PRIV_M_MODE; \
base = rv->csr_mtvec & ~0x3; \
mode = rv->csr_mtvec & 0x3; \
rv->csr_mepc = rv->PC; \
rv->csr_mtval = tval; \
rv->csr_mcause = code; \
if (!rv->csr_mtvec) { /* in case CSR is not configured */ \
rv_trap_default_handler(rv); \
return; \
} \
} \
switch (mode) { \
/* DIRECT: All traps set PC to base */ \
case 0: \
rv->PC = base; \
break; \
/* VECTORED: Asynchronous traps set PC to base + 4 * code */ \
case 1: \
/* MSB of code is used to indicate whether the trap is interrupt \
* or exception, so it is not considered as the 'real' code */ \
rv->PC = base + 4 * (code & MASK(31)); \
break; \
} \
IIF(RV32_HAS(SYSTEM))(if (rv->is_trapped) trap_handler(rv);, ) \
}

/* RISC-V exception handlers */
#define _(type, code) TRAP_HANDLER_IMPL(type, code)
RV_TRAP_LIST
#undef _
static void __trap_handler(riscv_t *rv);
#endif /* RV32_HAS(SYSTEM) */

/* wrap load/store and insn misaligned handler
* @mask_or_pc: mask for load/store and pc for insn misaligned handler.
Expand All @@ -180,8 +65,8 @@ RV_TRAP_LIST
rv->compressed = compress; \
rv->csr_cycle = cycle; \
rv->PC = PC; \
IIF(RV32_HAS(SYSTEM))(rv->is_trapped = true, ); \
rv_trap_##type##_misaligned(rv, IIF(IO)(addr, mask_or_pc)); \
SET_CAUSE_AND_TVAL_THEN_TRAP(rv, type##_MISALIGNED, \
IIF(IO)(addr, mask_or_pc)); \
return false; \
}

Expand Down Expand Up @@ -531,8 +416,8 @@ static bool do_fuse3(riscv_t *rv, rv_insn_t *ir, uint64_t cycle, uint32_t PC)
*/
for (int i = 0; i < ir->imm2; i++) {
uint32_t addr = rv->X[fuse[i].rs1] + fuse[i].imm;
RV_EXC_MISALIGN_HANDLER(3, store, false, 1);
rv->io.mem_write_w(addr, rv->X[fuse[i].rs2]);
RV_EXC_MISALIGN_HANDLER(3, STORE, false, 1);
rv->io.mem_write_w(rv, addr, rv->X[fuse[i].rs2]);
}
PC += ir->imm2 * 4;
if (unlikely(RVOP_NO_NEXT(ir))) {
Expand All @@ -555,8 +440,8 @@ static bool do_fuse4(riscv_t *rv, rv_insn_t *ir, uint64_t cycle, uint32_t PC)
*/
for (int i = 0; i < ir->imm2; i++) {
uint32_t addr = rv->X[fuse[i].rs1] + fuse[i].imm;
RV_EXC_MISALIGN_HANDLER(3, load, false, 1);
rv->X[fuse[i].rd] = rv->io.mem_read_w(addr);
RV_EXC_MISALIGN_HANDLER(3, LOAD, false, 1);
rv->X[fuse[i].rd] = rv->io.mem_read_w(rv, addr);
}
PC += ir->imm2 * 4;
if (unlikely(RVOP_NO_NEXT(ir))) {
Expand Down Expand Up @@ -666,12 +551,12 @@ static void block_translate(riscv_t *rv, block_t *block)
prev_ir->next = ir;

/* fetch the next instruction */
const uint32_t insn = rv->io.mem_ifetch(block->pc_end);
const uint32_t insn = rv->io.mem_ifetch(rv, block->pc_end);

/* decode the instruction */
if (!rv_decode(ir, insn)) {
rv->compressed = is_compressed(insn);
rv_trap_illegal_insn(rv, insn);
SET_CAUSE_AND_TVAL_THEN_TRAP(rv, ILLEGAL_INSN, insn);
break;
}
ir->impl = dispatch_table[ir->opcode];
Expand Down Expand Up @@ -1122,15 +1007,14 @@ void rv_step(void *arg)
}

#if RV32_HAS(SYSTEM)
static void trap_handler(riscv_t *rv)
static void __trap_handler(riscv_t *rv)
{
rv_insn_t *ir = mpool_alloc(rv->block_ir_mp);
assert(ir);

/* set to false by sret/mret implementation */
uint32_t insn;
/* set to false by sret implementation */
while (rv->is_trapped && !rv_has_halted(rv)) {
insn = rv->io.mem_ifetch(rv->PC);
uint32_t insn = rv->io.mem_ifetch(rv, rv->PC);
assert(insn);

rv_decode(ir, insn);
Expand All @@ -1139,12 +1023,94 @@ static void trap_handler(riscv_t *rv)
ir->impl(rv, ir, rv->csr_cycle, rv->PC);
}
}
#endif
#endif /* RV32_HAS(SYSTEM) */

/* When a trap occurs in M-mode/S-mode, m/stval is either initialized to zero or
* populated with exception-specific details to assist software in managing
* the trap. Otherwise, the implementation never modifies m/stval, although
* software can explicitly write to it. The hardware platform will define
* which exceptions are required to informatively set mtval and which may
* consistently set it to zero.
*
* When a hardware breakpoint is triggered or an exception like address
* misalignment, access fault, or page fault occurs during an instruction
* fetch, load, or store operation, m/stval is updated with the virtual address
* that caused the fault. In the case of an illegal instruction trap, m/stval
* might be updated with the first XLEN or ILEN bits of the offending
* instruction. For all other traps, m/stval is simply set to zero. However,
* it is worth noting that a future standard could redefine how m/stval is
* handled for different types of traps.
*
*/
static void _trap_handler(riscv_t *rv)
{
/* m/stvec (Machine/Supervisor Trap-Vector Base Address Register)
* m/stvec[MXLEN-1:2]: vector base address
* m/stvec[1:0] : vector mode
* m/sepc (Machine/Supervisor Exception Program Counter)
* m/stval (Machine/Supervisor Trap Value Register)
* m/scause (Machine/Supervisor Cause Register): store exception code
* m/sstatus (Machine/Supervisor Status Register): keep track of and
* controls the hart’s current operating state
*
* m/stval and m/scause are set in SET_CAUSE_AND_TVAL_THEN_TRAP
*/
uint32_t base;
uint32_t mode;
uint32_t cause;
/* user or supervisor */
if (RV_PRIV_IS_U_OR_S_MODE()) {
const uint32_t sstatus_sie =
(rv->csr_sstatus & SSTATUS_SIE) >> SSTATUS_SIE_SHIFT;
rv->csr_sstatus |= (sstatus_sie << SSTATUS_SPIE_SHIFT);
rv->csr_sstatus &= ~(SSTATUS_SIE);
rv->csr_sstatus |= (rv->priv_mode << SSTATUS_SPP_SHIFT);
rv->priv_mode = RV_PRIV_S_MODE;
base = rv->csr_stvec & ~0x3;
mode = rv->csr_stvec & 0x3;
cause = rv->csr_scause;
rv->csr_sepc = rv->PC;
} else { /* machine */
const uint32_t mstatus_mie =
(rv->csr_mstatus & MSTATUS_MIE) >> MSTATUS_MIE_SHIFT;
rv->csr_mstatus |= (mstatus_mie << MSTATUS_MPIE_SHIFT);
rv->csr_mstatus &= ~(MSTATUS_MIE);
rv->csr_mstatus |= (rv->priv_mode << MSTATUS_MPP_SHIFT);
rv->priv_mode = RV_PRIV_M_MODE;
base = rv->csr_mtvec & ~0x3;
mode = rv->csr_mtvec & 0x3;
cause = rv->csr_mcause;
rv->csr_mepc = rv->PC;
if (!rv->csr_mtvec) { /* in case CSR is not configured */
rv_trap_default_handler(rv);
return;
}
}
switch (mode) {
/* DIRECT: All traps set PC to base */
case 0:
rv->PC = base;
break;
/* VECTORED: Asynchronous traps set PC to base + 4 * code */
case 1:
/* MSB of code is used to indicate whether the trap is interrupt
* or exception, so it is not considered as the 'real' code */
rv->PC = base + 4 * (cause & MASK(31));
break;
}
IIF(RV32_HAS(SYSTEM))(if (rv->is_trapped) __trap_handler(rv);, )
}

void trap_handler(riscv_t *rv)
{
assert(rv);
_trap_handler(rv);
}

void ebreak_handler(riscv_t *rv)
{
assert(rv);
rv_trap_breakpoint(rv, rv->PC);
SET_CAUSE_AND_TVAL_THEN_TRAP(rv, BREAKPOINT, rv->PC);
}

void ecall_handler(riscv_t *rv)
Expand All @@ -1154,7 +1120,7 @@ void ecall_handler(riscv_t *rv)
syscall_handler(rv);
rv->PC += 4;
#else
rv_trap_ecall_M(rv, 0);
SET_CAUSE_AND_TVAL_THEN_TRAP(rv, ECALL_M, 0);
syscall_handler(rv);
#endif
}
Expand Down
5 changes: 5 additions & 0 deletions src/feature.h
Original file line number Diff line number Diff line change
Expand Up @@ -63,5 +63,10 @@
#define RV32_FEATURE_T2C 0
#endif

/* System */
#ifndef RV32_FEATURE_SYSTEM
#define RV32_FEATURE_SYSTEM 0
#endif

/* Feature test macro */
#define RV32_HAS(x) RV32_FEATURE_##x
4 changes: 2 additions & 2 deletions src/gdbstub.c
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ static int rv_read_mem(void *args, size_t addr, size_t len, void *val)
* an invalid address. We may have to do error handling in the
* mem_read_* function directly.
*/
*((uint8_t *) val + i) = rv->io.mem_read_b(addr + i);
*((uint8_t *) val + i) = rv->io.mem_read_b(rv, addr + i);
}

return err;
Expand All @@ -66,7 +66,7 @@ static int rv_write_mem(void *args, size_t addr, size_t len, void *val)
riscv_t *rv = (riscv_t *) args;

for (size_t i = 0; i < len; i++)
rv->io.mem_write_b(addr + i, *((uint8_t *) val + i));
rv->io.mem_write_b(rv, addr + i, *((uint8_t *) val + i));

return 0;
}
Expand Down
Loading