Writing and Using RISC-V user mode exception handlers in RARS
Introduction
Interrupts are a way to break away from the current execution path and deal with a potentially time sensitive
issue and then return to the previous execution path. Interrupts fall into three categories: software, timer, and external.
Software interrupts are triggered by setting a bit in the interrupt pending CSR. Timer interrupts are triggered by a timer.
External interrupts come from outside the CPU. Both timer and external interrupts can be caused from tools from the tools menu.
Synchronous traps are caused by code not being able to continue on its current path without having additional actiion taken.
This can be because the code is incorrect (e.g. load access misaligned) or because action needs to be taken by the OS
(ecall, ebreak, page fault, etc).
Exception handlers allow programs to handle both of these cases. Every trap must be handled immediately either by the
program, or by RARS. System calls and breakpoints are normally handled by RARS, but other faults are generally handled
by printing an error message to the console. The program's exception handler doesn't have to print to console though.
Below is a simple handler which just skips over instructions generating traps:
.text
main:
la t0,handler
csrrw zero, 5, t0 # set utvec (5) to the handlers address
csrrsi zero, 0, 1 # set interrupt enable bit in ustatus (0)
lw zero, 0 # trigger trap for Load access fault
j main
handler: # Just ignore it by moving epc (65) to the next instruction
csrrw t0, 65, zero
addi t0, t0, 4
csrrw zero, 65, t0
uret
In order to have a working exception handler, the program must:
- Set utvec to the address of the handler code (the lowest two bits are special)
- Set the bits corresponding to the handled interrupts in uie
- Set the interrupt enable (lowest) bit in ustatus to enable the handler
And inside the handler, :
- The exception handler can return control to the program using
the uret instruction. This will place the uepc CSR value into the
Program Counter, so be sure to increment it by 4 before returning if you want
to skip over the instruction that caused the exception. It also resets some other
state to before the exception, so jumping to the value in uepc instead is not recommended
- ucause contains the reason that the exception handler was called.
- Exception types declared in rars.SimulatorException, but
not necessarily implemented, are INSTRUCTION_ADDR_MISALIGNED (0),
INSTRUCTION_ACCESS_FAULT (1), ILLEGAL_INSTRUCTION(2),
LOAD_ADDRESS_MISALIGNED(4), LOAD_ACCESS_FAULT(5),
STORE_ADDRESS_MISALIGNED(6), STORE_ACCESS_FAULT(7), and ENVIRONMENT_CALL(8)
- When writing a non-trivial exception handler, your handler must first save
general purpose register contents, then restore them before returning.
The implementation of user-mode exception handlers in RARS is not complete and does not fully conform to the RISC-V
privilidged specification, but it is based upon that and contributions to make it be more conformant would be appreciated.
For more details, refer to the RISC-V priviledged specification https://riscv.org/specifications/privileged-isa/;
contributions to pull in more details into this page would be appreciated.