4.2 - Zicsr

Instructions

The RISC-V specifications describe how interrupts should be handled in RISC-V. This is covered (among other things) in an extension: Zicsr. RISC-V refers to an external asynchronous event that may cause a RISC-V core to execute an unexpected transfer of control as interrupt. Similarly, an unusual condition occurring at the run time (due to invalid instructions etc.) is referred to as exceptions. The term trap is used to collectively refer to interrupt or exception.

A separate address space of 4096 Control and Status registers are associated with each hart. Six additional instructions are provided to interract with these registers.

These instructions perform atomic substitutions between the CSR register and either a register from the registerfile or an immediate value.

Register name Description
CSRRW rd, CSR_ID, rs1 Write rs1 to the register CRS_ID while reading the content of CRS_ID into rd
CSRRS rd, CSR_ID, rs1 Write CRS_ID OR rs1 to the register CRS_ID while reading the content of CRS_ID into rd
CSRRC rd, CSR_ID, rs1 Write CRS_ID AND not(rs1) to the register CRS_ID while reading the content of CRS_ID into rd
CSRRWI rd, CSR_ID, value identical to CSRRW but with an immediate value in stead of rs1
CSRRSI rd, CSR_ID, value identical to CSRRS but with an immediate value in stead of rs1 (setting bits)
CSRRCI rd, CSR_ID, value identical to CSRRC but with an immediate value in stead of rs1 (clearing bits)

Zicsr registers

Although not all 4096 registers that are addressable are used, in this course even fewer registers are discussed. The most relevant registers for this course are enumerated here.

  • CSR_MRO_mhartid (address: 0xF14): This register holds the ID of the RISC-V core
  • CSR_MRW_mstatus (address: 0x300): This register holds 32 bits of status bits
  • CSR_MRW_misa (address: 0x301): This register an identifier of the ISA
  • CSR_MRW_mie (address: 0x304): This register masks which interrupts are enabled
  • CSR_MRW_mtvec (address: 0x305): This register holds the address of the context vector
  • CSR_MRW_mstatush (address: 0x310): This register holds another 32 bits of status bits
  • CSR_MRW_mscratch (address: 0x340): This register hold the address for the context space
  • CSR_MRW_mepc (address: 0x341): This register serves as backup register for the program counter
  • CSR_MRW_mcause (address: 0x342): This register samples the cause of the interrupt
  • CSR_MRW_mip (address: 0x344): This register hold pending requests

When an interrupt presents itself …

When an interrupt presents itself the RISC-V needs to jump to the interrupt handler. However, when the interrupt is handled, the processor should continue where it left. This is achieved by the following steps

  1. [Program Counter]: Load the program counter with a pointer to the  trap vector , while making a backup of the program counter in CSR_MRW_mepc register of the Zicsr. This requires a modification in the hardware.
  2. [Stack Pointer]: The context of the processor is the state of all the registers. Before these registers can be backed up, one register needs to be freed. RISC-V uses the stack pointer for this. The current value is swapped with the CSR_MRW_mscratch register of the Zicsr.
    csrrw sp, mscratch, sp
  1. [Regfile backup]: With the stack pointer set, a backup of all 30 (!) registers can be made on the stack.
	sw x1,   0*4(sp)
	sw x3,   1*4(sp)
	sw x4,   2*4(sp)
    ...
	sw x31,   29*4(sp)
  1. [Cause]: Fetch the cause of the interrupt from the CSR_MRW_mcause register, store it in reg a0 and call the  interrupt handler .
    csrrc a0, mcause, zero
    jal ra, irq_handler
  1. [Restore Regfile]:
   lw x1,   0*4(sp)
   lw x3,   1*4(sp)
   lw x4,   2*4(sp)
   ...
   lw x31,   29*4(sp)
  1. [Restore SP]: The current value is swapped again with the CSR_MRW_mscratch register of the Zicsr.
    csrrw sp, mscratch, sp
  1. [Return from Trap Vector]: This can be done with a special instruction.
    mret

The software was organised as shown on the left. Upon reset, the initialisation was done in the  start  section. Here, all registers get initialised, the stack pointer is set to 0x1000 and a jump is done to the  main  function. All other  functions  are compiled and are linked somewhere in the instruction memory.

To allow for interrupts these sections have to move a little bit further down. This makes room for a  reset vector  and a  trap vector .

The  reset vector  is a very short set of instructions that mainly prepares the processor to  do reset . The reason for adding this section is that the processor can keep starting operation at address 0x0 and that the trap vector can start very early on a fixed address.

When an interrupt is present, the RISC-V should jump to the  trap vector . Here a backup-copy is made of all the registers to the stack, the interrupt handler is called, after which all registers are restored from the stack.

Informing the compiler

As the firmware also includes Zicsr instructions now, the compiler has to be notified of this. In the Makefile, the flags for the compiler can do this trick. The parameter arch needs to be changed from rv32i to rv32i_zicsr.

...
ARCHITECTURE = rv32i
...
...
ARCHITECTURE = rv32i_zicsr
...