RISC-V architecture
Overview
System calls
| Service | Trap Code | Input | Output | Notes |
|---|---|---|---|---|
| print_int | a7 = 1 | a0 = int to be printed | Print a0 to display | |
| print_float | a7 = 2 | fa0 = float to be printed | Print fa0 to display | |
| print_double | a7 = 3 | fa0 = double to be printed | Print fa0 to display | |
| print_string | a7 = 4 | a0 = 1st char's address | Print string in the display | |
| read_int | a7 = 5 | Read integer in a0 | ||
| read_float | a7 = 6 | Read float to fa0 | ||
| read_double | a7 = 7 | Read double to fa0 | ||
| read_string | a7 = 8 | a0 = buffer address, a1= buffer length | Read string | |
| sbrk | a7 = 9 | a0 = number of bytes | a0 points to the allocated memory | Allocation from heap |
| exit | a7 = 10 | End of execution | ||
| print_char | a7 = 11 | a0 = ASCII code | Print a0 to display | |
| read_char | a7 = 12 | Read char to a0 |
Interrupts
The RISC-V-32 ISA defines different privilege modes that determine the level of access and control a process has over the system's resources levels, in particular, access to Control Status Registers (CSRs), the privileged instruction set, and privileged ISA extensions. The instruction subset to control CSRs is provided in the Zicsr extension @RISCVUnprivileged, chapter 7. The ISA defines three levels: User/Application (U), Supervisor (S), and Machine (M), allowing implementations to provide one, two, or three of them (There are only three supported combinations: M-level, M-level and U-level, or all three). M-level is the highest privilege level, while U-level is intended for conventional applications and S-level for operating systems @RISCVPrivileged, chapers 1 and 2, @Bulić2024 section 2.5.1.
RISC-V handles exceptions and interrupts by generating traps.
As a trap involves elevating the privilege level, e.g. requesting an OS system call from a user program, both M and S privilege levels include a set of CSRs for handling them#footnote[As they are virtually the same, only the M-level set will be described.].
The mstatus (Machine Status) register (@fig:mstatus) keeps track of and controls the CPU's current operating state.
In this register, there are several interrupt-related fields: field MIE (Machine Interrupt Enable) controls whether interrupts are globally enabled or disabled for that privilege mode, field MPP (Machine Previous Privilege Mode) holds the previous privilege mode, and, to allow nesting of interrupts, field MPIE stores the previous value of MIE when an interrupt occurs.
The mie (Machine Interrupt Enable) register (@fig:mie) allows for a finer control of interrupts, allowing the programmer to set which types of interrupts are enabled: field MSIE (Machine Software Interrupt Enable), MTIE (Machine Timer Interrupt Enable), and MEIE (Machine External Interrupt Enable) control software, timer, and external interrupts, respectively.
The mip (Machine Interrupt Pending) register (@fig:mip) indicates the interrrupts that are currently pending. Similarly to register mie, it specifies the type of interrupt on specific fields: MSIP (Machine Software Interrupt Pending), MTIP (Machine Timer Interrupt Pending), and MEIP (Machine External Interrupt Pending).
Each of these fields may be writable or may be read-only. Register mcause (Machine Cause, @fig:mcause) provides information about the event that caused the trap. This register contains a field I specifying if the cause was an interrupt or an exception, while the rest of the bits are reserved for the exception code. RISC-V defines some of these exception codes (see @tab:rv32-excodes), while others are left for the implementation to use.
Register mtvec (Machine Trap-Vector Base-Address, @fig:mtvec) holds the trap vector configuration.
Depending on the value of the MODE field, RISC-V allows for polled interrupts (direct mode, with a value of 0) or vectored interrupts (vectored mode, with a value of 1).
In direct mode, all traps cause the PC to be set to the address in the BASE field, while on vectored mode, traps set the PC to address $mono("BASE") + 4 times italic("cause")$, cause being the exception code found in mcause.
Finally, register mepc (Machine Exception Program Counter) holds the address of the instruction that generated the exception while it is handled @RISCVUnprivileged section 3.1, @Bulić2024 section 2.5.2.
When a trap is taken to M-mode, the corresponding flag in mip is set, and the MIE field in mstatus and the corresponding flag in mie are checked to determine if that interrupt is enabled.
If it's enabled, the MIE field is copied into the MPIE field of mstatus and MIE is cleared, disabling further interrupts.
Then, the previous privilege mode is stored in the MPP field of mstatus, the cause of the exception is encoded into mcause, the current PC is stored into the mepc register, and the PC is set to the address specified by mtvec.
When the interrupt handler finishes, it calls mret, which resets the privilege mode (reading the MPP field in mstatus), re-enables interrupts (by copying back field MPIE to MIE in mstatus), and sets the PC to the value of mepc.
With regard to the mip register, the ISA states that if the field is writable, a pending interrupt can be cleared by clearing the field, but if the field is read-only, the implementation must provide some other mechanism for clearing the pending interrupt @RISCVUnprivileged section 3.3, @Bulić2024 section 2.5.4.
More details in the Master Thesis "Implementing Interrupts, Timers, and Memory-Mapped I/O in CREATOR" By Luis Daniel Casais Mezquida .