SiFive - July 12, 2018

Interrupts on the SiFive E2 Series

Last week SiFive launched the new E2 Series RISC-V Core IP. The E2 Series represents SiFive’s smallest, most efficient Core IP Series and is targeted specifically for embedded microcontroller designs. One of the reasons it is great for microcontroller applications is because of its extremely small area footprint, just 0.023mm2 in 28nm for the entire E20 Standard Core! Another reason it's great for the embedded market is its configurability. The E2 Series can be configured even smaller than the E20 Standard Core by removing things like the Interrupt Controller and support for the M extension. Another major reason the E2 Series is great for microcontroller applications is its support for the new RISC-V Core Local Interrupt Controller (CLIC) which allows for extremely low latency interrupt operation, hardware preemption, and hardware prioritization of all interrupts. The CLIC specification is a result of collaboration between RISC-V members in the RISC-V Foundation’s Fast Interrupts Technical Group and the draft specification can be found here.

Core Local Interrupt Controller

Hardware vectoring is an important aspect of traditional microcontroller applications. Vectoring allows for each interrupt to trap into a unique handler. This is important for performance as it reduces latency by moving the complexity of determining the source of the interrupt from software to hardware. The CLIC vector table, which is relocatable, contains addresses of interrupt handlers. Thanks to toolchain support for the interrupt attribute (see the section below), this means that the vector table can be populated with addresses of C functions. The CLIC supports two modes of operation, Direct or Vectored. In Vectored mode, all interrupts trap to unique handlers as described above. In Direct mode, all interrupts trap to a single handler. However, while in Direct mode, it is also possible to enable selectively vectored interrupts, where all interrupts trap to a single handler except for specifically selected interrupts which vector to their own unique handlers. Hardware preemption is supported regardless of the CLIC mode.

When interrupt vectoring is coupled with the E2 Series's efficient pipeline and guaranteed single cycle TIM access latency, it is possible to execute the first instruction of an interrupt's C handler as few as six cycles.

The CLIC specification supports up to 16 levels of preemption and programmable priorities within each level. The bits, which determine the number of supported levels and priorities, are configurable in the E2 Series with the E21 implementing four bits. The split between bits used for levels and priorities are software defined using the CLIC's configuration register.

Simple Software

Gone are the days of pushing and popping registers in assembly! RISC-V GCC now supports the interrupt function attribute. This attribute tells the compiler that the specified function is an interrupt handler, which results in the function saving the registers it needs upon entry, and executing an mret upon exit. With interrupt entry and exit now taken care of by the compiler, it becomes possible for the vector table to consist entirely of function pointers.

The example below defines a vector table (localISR), populates all interrupts with a safe default handler, and then points the CLIC to it.

typedef void (*interrupt_function_ptr_t) (void);
interrupt_function_ptr_t localISR[CLIC_NUM_INTERRUPTS] __attribute__((aligned(64)));

...
void default_handler(void)__attribute__((interrupt));;
void default_handler(void) {
  while(1);
}

...
// initialize vector table
int i = 0;
while (i < CLIC_NUM_INTERRUPTS) {
  localISR[i++] = default_handler;
}
// point mtvt to localISR
write_csr(mtvt, localISR);

Notice that the mtvt 64 byte alignment requirements are also able to be implemented using the aligned function attribute.

The interrupt attribute does not enable preemption as it leaves interrupts disabled until exiting the handler with mret. Thankfully enabling pre-emption is as simple using a different function attribute: SiFive-CLIC-preemptible. The SiFive-CLIC-preemptible attribute does the same thing as the interrupt attribute, but it first saves mcause and mepc, and then re-enables interrupts making the handler preemptible as soon as possible.

Toolchain support for the interrupt and SiFive-CLIC-preemptible attributes can be found in SiFive's pre-built RISC-V GCC toolchain, or in source form in Freedom-E-SDK.

Code Examples - The CLIC in Action

The example below, taken from Freedom-E-SDK's clic_vectored application, demonstrates a very simple but real interrupt routine that increments a counter and clears the interrupt.

void msi_isr()__attribute((interrupt));
void msi_isr() {
  // clear the  SW interrupt
  CLIC0_REG8(CLIC_INTIP + CSIPID) = 0;
  // increment COUNT
  COUNT++;
}

The resulting assembly code consists of only 13 instructions including the mret to exit the handler. An entire C code handler in just 13 instructions! This can be further reduced 12 cycles if using the RISC-V atomic add instruction which allows you to perform the load/add/store in two instructions instead of three. So six cycles to get into the handler, plus 12 cycles to execute the handler, means an entire interrupt routine on the E2 Series can execute in just 18 cycles!

          csip_isr:
8000813c:   addi    sp,sp,-16
8000813e:   sw      a4,12(sp)
80008140:   sw      a5,8(sp)
117         CLIC0_REG8(CLIC_INTIP + CSIPID) = 0;
80008142:   lui     a5,0x2800
80008146:   sb      zero,12(a5) # 0x280000c
118         COUNT++;
8000814a:   addi    a4,gp,-1804
8000814e:   lw      a5,0(a4)
80008150:   addi    a5,a5,1
80008152:   sw      a5,0(a4)
80008154:   lw      a4,12(sp)
80008156:   lw      a5,8(sp)
80008158:   addi    sp,sp,16
8000815a:   mret

The example also demonstrates hardware interrupt preemption using the buttons on the Arty board. These buttons are wired directly into the local interrupts, so a given interrupt will stay asserted as long as the button is being pushed. The C handlers are defined using the SiFive-CLIC-preemptible attribute allowing for preemption and given different interrupt levels (button 0 is lower than button 1 which is lower than button 2). The code for button 2 is shown below.

void button_2_isr(void) __attribute__((interrupt("SiFive-CLIC-preemptible")));
void button_2_isr(void) {
  // Toggle Red LED
  uint8_t level = clic_get_int_level(&clic, (LOCALINTIDBASE + LOCAL_INT_BTN_2));
  printf("Button 2 pressed, interrupt level %d. Pending CSIPID and toggle Green.\n", level);
  GPIO_REG(GPIO_OUTPUT_VAL) = GPIO_REG(GPIO_OUTPUT_VAL) ^ (0x1 << GREEN_LED_OFFSET);
  // pend a software interrupt
  clic_set_pending(&clic, CSIPID);
  wait_ms(1000);
  GPIO_REG(GPIO_OUTPUT_VAL) = GPIO_REG(GPIO_OUTPUT_VAL) ^ (0x1 << GREEN_LED_OFFSET);
}

void button_2_setup(void) {
  clic_install_handler(&clic, (LOCALINTIDBASE + LOCAL_INT_BTN_2), button_2_isr);
  clic_set_int_level(&clic, (LOCALINTIDBASE + LOCAL_INT_BTN_2), 3);
  clic_enable_interrupt(&clic, (LOCALINTIDBASE + LOCAL_INT_BTN_2));
}

Pressing button 0 will trigger a 10-second interrupt handler. While in button 0's handler, it is possible to press button 1. Since button 1 is a higher interrupt level, it will preempt button 0's handler and begin executing its own five-second handler. Button 2 is higher priority still, and pressing its button will trigger another preemption to execute button 2's handler. The handlers will then return in order from button 2's handler to button 1's, and then from button 1's to button 0's, and eventually from button 0's back to regular code execution.

Note that the button 2 handler also triggers the CLIC's software interrupt. In the example code, the software interrupt is set to a lower interrupt level (level 1) which will result in the CSIP handler being executed prior to returning to normal code execution. This is a common scenario in Real Time Operating Systems. For example a high level interrupt may trigger a context switch, but it could be undesirable to execute the context switch from the handler's interrupt level (blocking all lower levels) and it's inconvenient to wait for the next OS tick.

Putting it all Together

The CLIC is an advanced interrupt controller well suited to all embedded applications thanks to its capabilities and configurability. When taken together with the performance and efficiency of the E2 Series Core, the E2 Series is a very compelling choice for your next embedded application. The SiFive E2 Series has free RTL and FPGA evaluations available today for both the E21 and E20 Standard Cores.