Skip to content

Commit

Permalink
Merge pull request #438 from ChinYikMing/mmu
Browse files Browse the repository at this point in the history
Preliminary support for MMU emulation
  • Loading branch information
jserv authored Oct 28, 2024
2 parents 5e67220 + 6317605 commit 4599b1d
Show file tree
Hide file tree
Showing 17 changed files with 1,526 additions and 236 deletions.
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

0 comments on commit 4599b1d

Please sign in to comment.