Skip to content

Commit

Permalink
Merge pull request #463 from ChinYikMing/trap
Browse files Browse the repository at this point in the history
Preliminary support for trap handling during block emulation
  • Loading branch information
jserv authored Oct 19, 2024
2 parents a00c49f + a4f6dab commit edb5a1b
Show file tree
Hide file tree
Showing 17 changed files with 509 additions and 75 deletions.
4 changes: 4 additions & 0 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,10 @@ jobs:
make distclean && make ENABLE_EXT_F=0 check -j$(nproc)
make distclean && make ENABLE_EXT_C=0 check -j$(nproc)
make distclean && make ENABLE_SDL=0 check -j$(nproc)
- name: misalignment test in block emulation
run: |
make -C tests/system/alignment/
make distclean && make ENABLE_EXT_C=0 ENABLE_SYSTEM=1 misalign-in-blk-emu -j$(nproc)
- name: gdbstub test
run: |
make distclean && make ENABLE_GDBSTUB=1 gdbstub-test -j$(nproc)
Expand Down
15 changes: 14 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ CFLAGS = -std=gnu99 -O2 -Wall -Wextra
CFLAGS += -Wno-unused-label
CFLAGS += -include src/common.h

ENABLE_SYSTEM ?= 0
$(call set-feature, SYSTEM)

# Enable link-time optimization (LTO)
ENABLE_LTO ?= 1
ifeq ($(call has, LTO), 1)
Expand Down Expand Up @@ -134,7 +137,7 @@ endif
ENABLE_JIT ?= 0
$(call set-feature, JIT)
ifeq ($(call has, JIT), 1)
OBJS_EXT += jit.o
OBJS_EXT += jit.o
# tier-2 JIT compiler powered LLVM
LLVM_CONFIG = llvm-config-17
LLVM_CONFIG := $(shell which $(LLVM_CONFIG))
Expand Down Expand Up @@ -286,6 +289,16 @@ misalign: $(BIN) artifact
$(PRINTF) "Failed.\n"; \
fi

EXPECTED_misalign = MISALIGNED INSTRUCTION FETCH TEST PASSED!
misalign-in-blk-emu: $(BIN)
$(Q)$(PRINTF) "Running misalign.elf ... "; \
if [ "$(shell $(BIN) tests/system/alignment/misalign.elf | tail -n 2)" = "$(strip $(EXPECTED_misalign)) 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
2 changes: 2 additions & 0 deletions src/common.h
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@

#define ARRAYS_SIZE(arr) (sizeof(arr) / sizeof(arr[0]))

#define MASK(n) (~((~0U << (n))))

/* Alignment macro */
#if defined(__GNUC__) || defined(__clang__)
#define __ALIGNED(x) __attribute__((aligned(x)))
Expand Down
2 changes: 2 additions & 0 deletions src/decode.c
Original file line number Diff line number Diff line change
Expand Up @@ -841,9 +841,11 @@ static inline bool op_system(rv_insn_t *ir, const uint32_t insn)
case 0x202: /* HRET: return from traps in H-mode */
/* illegal instruction */
return false;
#if RV32_HAS(SYSTEM)
case 0x102: /* SRET: return from traps in S-mode */
ir->opcode = rv_insn_sret;
break;
#endif
case 0x302: /* MRET */
ir->opcode = rv_insn_mret;
break;
Expand Down
4 changes: 3 additions & 1 deletion src/decode.h
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,9 @@ enum op_field {
/* RISC-V Privileged Instruction */ \
_(wfi, 0, 4, 0, ENC(rs1, rd)) \
_(uret, 0, 4, 0, ENC(rs1, rd)) \
_(sret, 1, 4, 0, ENC(rs1, rd)) \
IIF(RV32_HAS(SYSTEM))( \
_(sret, 1, 4, 0, ENC(rs1, rd)) \
) \
_(hret, 0, 4, 0, ENC(rs1, rd)) \
_(mret, 1, 4, 0, ENC(rs1, rd)) \
_(sfencevma, 1, 4, 0, ENC(rs1, rs2, rd)) \
Expand Down
202 changes: 150 additions & 52 deletions src/emulate.c
Original file line number Diff line number Diff line change
Expand Up @@ -43,82 +43,127 @@ extern struct target_ops gdbstub_ops;

/* RISC-V exception code list */
/* clang-format off */
#define RV_EXCEPTION_LIST \
#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 */ \
_(ecall_M, 11) /* Environment call from M-mode */
IIF(RV32_HAS(SYSTEM))(, \
_(ecall_M, 11) /* Environment call from M-mode */ \
)
/* clang-format on */

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

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

/* When a trap occurs in M-mode, mtval is either initialized to zero or
/*
* 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 mtval, although
* 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, mtval is updated with the virtual address
* that caused the fault. In the case of an illegal instruction trap, mtval
* 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, mtval is simply set to zero. However,
* it is worth noting that a future standard could redefine how mtval is
* 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 EXCEPTION_HANDLER_IMPL(type, code) \
static void rv_except_##type(riscv_t *rv, uint32_t mtval) \
{ \
/* mtvec (Machine Trap-Vector Base Address Register) \
* mtvec[MXLEN-1:2]: vector base address \
* mtvec[1:0] : vector mode \
*/ \
const uint32_t base = rv->csr_mtvec & ~0x3; \
const uint32_t mode = rv->csr_mtvec & 0x3; \
/* mepc (Machine Exception Program Counter) \
* mtval (Machine Trap Value Register) \
* mcause (Machine Cause Register): store exception code \
* mstatus (Machine Status Register): keep track of and controls the \
* hart’s current operating state \
*/ \
rv->csr_mepc = rv->PC; \
rv->csr_mtval = mtval; \
rv->csr_mcause = code; \
rv->csr_mstatus = MSTATUS_MPP; /* set privilege mode */ \
if (!rv->csr_mtvec) { /* in case CSR is not configured */ \
rv_exception_default_handler(rv); \
return; \
} \
switch (mode) { \
case 0: /* DIRECT: All exceptions set PC to base */ \
rv->PC = base; \
break; \
/* VECTORED: Asynchronous interrupts set PC to base + 4 * code */ \
case 1: \
rv->PC = base + 4 * code; \
break; \
} \
#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) EXCEPTION_HANDLER_IMPL(type, code)
RV_EXCEPTION_LIST
#define _(type, code) TRAP_HANDLER_IMPL(type, code)
RV_TRAP_LIST
#undef _

/* wrap load/store and insn misaligned handler
Expand All @@ -135,7 +180,8 @@ RV_EXCEPTION_LIST
rv->compressed = compress; \
rv->csr_cycle = cycle; \
rv->PC = PC; \
rv_except_##type##_misaligned(rv, IIF(IO)(addr, mask_or_pc)); \
IIF(RV32_HAS(SYSTEM))(rv->is_trapped = true, ); \
rv_trap_##type##_misaligned(rv, IIF(IO)(addr, mask_or_pc)); \
return false; \
}

Expand Down Expand Up @@ -164,6 +210,10 @@ static uint32_t *csr_get_ptr(riscv_t *rv, uint32_t csr)
return (uint32_t *) (&rv->csr_misa);

/* Machine Trap Handling */
case CSR_MEDELEG: /* Machine Exception Delegation Register */
return (uint32_t *) (&rv->csr_medeleg);
case CSR_MIDELEG: /* Machine Interrupt Delegation Register */
return (uint32_t *) (&rv->csr_mideleg);
case CSR_MSCRATCH: /* Machine Scratch Register */
return (uint32_t *) (&rv->csr_mscratch);
case CSR_MEPC: /* Machine Exception Program Counter */
Expand Down Expand Up @@ -196,6 +246,26 @@ static uint32_t *csr_get_ptr(riscv_t *rv, uint32_t csr)
case CSR_FCSR:
return (uint32_t *) (&rv->csr_fcsr);
#endif
case CSR_SSTATUS:
return (uint32_t *) (&rv->csr_sstatus);
case CSR_SIE:
return (uint32_t *) (&rv->csr_sie);
case CSR_STVEC:
return (uint32_t *) (&rv->csr_stvec);
case CSR_SCOUNTEREN:
return (uint32_t *) (&rv->csr_scounteren);
case CSR_SSCRATCH:
return (uint32_t *) (&rv->csr_sscratch);
case CSR_SEPC:
return (uint32_t *) (&rv->csr_sepc);
case CSR_SCAUSE:
return (uint32_t *) (&rv->csr_scause);
case CSR_STVAL:
return (uint32_t *) (&rv->csr_stval);
case CSR_SIP:
return (uint32_t *) (&rv->csr_sip);
case CSR_SATP:
return (uint32_t *) (&rv->csr_satp);
default:
return NULL;
}
Expand Down Expand Up @@ -377,9 +447,10 @@ enum {
};

#if RV32_HAS(GDBSTUB)
#define RVOP_NO_NEXT(ir) (!ir->next | rv->debug_mode)
#define RVOP_NO_NEXT(ir) \
(!ir->next | rv->debug_mode IIF(RV32_HAS(SYSTEM))(| rv->is_trapped, ))
#else
#define RVOP_NO_NEXT(ir) (!ir->next)
#define RVOP_NO_NEXT(ir) (!ir->next IIF(RV32_HAS(SYSTEM))(| rv->is_trapped, ))
#endif

/* record whether the branch is taken or not during emulation */
Expand Down Expand Up @@ -565,8 +636,10 @@ FORCE_INLINE bool insn_is_unconditional_branch(uint8_t opcode)
case rv_insn_ebreak:
case rv_insn_jal:
case rv_insn_jalr:
case rv_insn_sret:
case rv_insn_mret:
#if RV32_HAS(SYSTEM)
case rv_insn_sret:
#endif
#if RV32_HAS(EXT_C)
case rv_insn_cj:
case rv_insn_cjalr:
Expand Down Expand Up @@ -598,7 +671,7 @@ static void block_translate(riscv_t *rv, block_t *block)
/* decode the instruction */
if (!rv_decode(ir, insn)) {
rv->compressed = is_compressed(insn);
rv_except_illegal_insn(rv, insn);
rv_trap_illegal_insn(rv, insn);
break;
}
ir->impl = dispatch_table[ir->opcode];
Expand Down Expand Up @@ -1048,17 +1121,42 @@ void rv_step(void *arg)
#endif
}

#if RV32_HAS(SYSTEM)
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;
while (rv->is_trapped && !rv_has_halted(rv)) {
insn = rv->io.mem_ifetch(rv->PC);
assert(insn);

rv_decode(ir, insn);
ir->impl = dispatch_table[ir->opcode];
rv->compressed = is_compressed(insn);
ir->impl(rv, ir, rv->csr_cycle, rv->PC);
}
}
#endif

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

void ecall_handler(riscv_t *rv)
{
assert(rv);
rv_except_ecall_M(rv, 0);
#if RV32_HAS(SYSTEM)
syscall_handler(rv);
rv->PC += 4;
#else
rv_trap_ecall_M(rv, 0);
syscall_handler(rv);
#endif
}

void memset_handler(riscv_t *rv)
Expand Down
14 changes: 14 additions & 0 deletions src/riscv.c
Original file line number Diff line number Diff line change
Expand Up @@ -303,6 +303,20 @@ riscv_t *rv_create(riscv_user_t rv_attr)
#endif
#endif

#if RV32_HAS(SYSTEM)
/*
* System simulation defaults to S-mode as
* it does not rely on M-mode software like OpenSBI.
*/
rv->priv_mode = RV_PRIV_S_MODE;

/* not being trapped */
rv->is_trapped = false;
#else
/* ISA simulation defaults to M-mode */
rv->priv_mode = RV_PRIV_M_MODE;
#endif

return rv;
}

Expand Down
Loading

0 comments on commit edb5a1b

Please sign in to comment.