{{% lowrisc-doc-hdr Interrupt Controller Technical Specification }} {{% regfile rv_plic.hjson }}

{{% section1 Overview }}

This document specifies the Interrupt Controller (RV_PLIC) functionality. This module conforms to the Comportable guideline for peripheral functionality. See that document for integration overview within the broader top level system.

{{% toc 3 }}

{{% section2 Features }}

  • RISC-V Platform-Level Interrupt Controller (PLIC) compliant interrupt controller
  • Support arbitrary number of interrupt vectors (up to 256) and targets
  • Support interrupt enable, interrupt status registers
  • Memory-mapped MSIP register per HART.

{{% section2 Description }}

The RV_PLIC module is designed to manage various interrupt sources from the peripherals. It receives interrupt events and detects edge or level of the signals then notifies the multiple targets within the RISC-V core.

{{% section2 Compatibility }}

The RV_PLIC is compatible with any RISC-V core implementing the RISC-V privilege specification.

{{% section1 Theory of Operations }}

{{% section2 Block Diagram }}

RV_PLIC Block Diagram

{{% section2 Details }}

Identifier

Each interrupt source has a unique ID assigned based upon its bit position within the input intr_src_i. ID counting ranges from 1 to N, the number of interrupt sources. intr_src_i[i] bit has an ID of i+1. This ID is used when targets “claim” the interrupt and to “complete” the interrupt event.

Priority and Threshold

Interrupt sources also have configurable priority values. The maximum value of the priority is configurable through the Verilog parameter MAX_PRIO. RV_PLIC chooses the highest priority interrupt among the pending interrupts if its value exceeds the threshold for the target.

Each target can configure the threshold value. Only interrupts that have priorities greater than the threshold are forwarded to the target. So, if a target wants to not receive any interrupts, it can configure threshold to the max value if turning off all interrupt sources requires multiple register writes.

MAX_PRIO parameter is most area contributing option in RV_PLIC. If MAX_PRIO is big, then finding the highest priority in Process module consumes a lot of logic gates.

Interrupt Gateways

Gateway module converts interrupt events from the peripherals to a common interrupt request format used in RISC-V PLIC. It is configurable to detect the interrupt event when the signal is changed from 0 to 1 (edge-triggered) or to notify every time when the signal is 1 (level). Gateway forwards the new interrupt request only after one of the targets claims the interrupt and completes it. If a target claims the interrupt event, Gateway de-asserts interrupt request (Interrupt Pending bit) but doesn‘t set IP, even it detects event from the peripheral. Gateway module in RV_PLIC doesn’t support counter for edge-triggered event. Any events happen in the time from claimed to completed are ignored.

Interrupt Enables

Each target has a set of Interrupt Enable (IE) registers. Each bit in the IE registers controls the corresponding interrupt source. RV_PLIC doesn't have global interrupt turn-on and off feature.

Interrupt Claims

“Claiming” an interrupt is done by a target reading the associated Claim/Completion (CC) register for the target. The return value of the !!CC read represents the pending interrupt that has the highest priority. If two or more pending interrupts have the same priority, RV_PLIC chooses the one with lowest ID.

Interrupt Completion

After an interrupt is claimed, the interrupt pending (IP) bit of the interrupt source is cleared, regardless of the status of the intr_src_i input value. Until a target “completes” the interrupt, it won't be re-set if a new event for the interrupt occurs. A target completes the interrupt by writing the ID of the interrupt to the !!CC register. The write event is forwarded to the Gateway logic, which resets the interrupt status to accept new interrupt event. The assumption is that the processor has cleaned up the originating interrupt event during the time between claim and complete such that the intr_src_i[ID-1] value is no longer true.

{ signal: [
  { name: 'clk',           wave: 'p...........' },
  { name: 'intr_src_i[i]', wave: '01.....0..1.', node:'.....a....b' },
  { name: 'irq_o',         wave: '0.1.0......1', node:'.....c.....d'},
  { name: 'irq_id_o',      wave: '=.=.=......=',
                           data: ["0","i+1","0","i+1"] },
  { name: 'claim',         wave: '0..10.......'},
  { name: 'complete',      wave: '0.......10..', node:'........e...'},
  ],
  head:{
    text: 'Interrupt Flow',
    tick: 0,
  },
}

{{% section2 Hardware Interfaces }}

{{% hwcfg rv_plic }}

{{% section1 Programmers Guide }}

{{% section2 Initialization }}

After reset, RV_PLIC doesn't generate any interrupts to any targets even if interrupt sources are set, as the priorities and thresholds are 0 by default and all IE values are 0. Software should configure the above three registers and the interrupt source type !!LE .

!!LE and !!PRIO0 .. !!PRIO31 registers are unique. So, only one of the targets shall configure them.

// Pseudo-code below
void plic_init() {
  // Set to level-triggered for interrupt sources
  for (int i = 0 ; i < ceil(N_SOURCE/32) ; i++) {
    *(LE+i) = 0;

  // Configure priority
  for (int i = 0 ; i < N_SOURCE ; i++) {
    *(PRIO+i) = value(i);

}

void plic_threshold(tid, threshold) {
  *(THRESHOLD+tid) = threshold;
}

void plic_enable(tid, iid) {
  // iid: 0-based ID
  int offset = ceil(N_SOURCE/32) * tid + (iid>>5);

  *(IE+offset) = *(IE+offset) | (1<<(iid%32));
}

{{% section2 Handling Interrupt Request Events }}

If software receives an interrupt request, it is recommended to follow the steps shown below.

  1. Claim right after entering to the interrupt service routine by reading !!CC register.
  2. Determine which interrupt should be serviced based on read-out !!CC register.
  3. Execute ISR, clear originating peripheral interrupt.
  4. Write Interrupt ID to !!CC
  5. Repeat as necessary for other pending interrupts.

It is possible to have multiple interrupt events claimed. If software claims one interrupt request, then the process module advertises any pending interrupts with lower priority unless new higher priority interrupt events occur. If a higher interrupt event occurs after previous interrupt is claimed, the RV_PLIC IP advertises the higher priority interrupt. Software may utilize an event manager inside a loop so that interrupt claiming and completion can be separated.

void interrupt_service() {
  // tid is predefined value.
  uint32 iid;
  iid = *(CC + tid);
  if (iid == 0) {
    // Interrupt is claimed by one of other targets
    return ;

  do {
    // Process interrupts
    // ...

    // Complete
    *(CC + tid) = iid;
    iid = *(CC + tid);
  } while (iid != 0);
}

{{% section2 Registers }}

Register description can be generated with reg_rv_plic.py script. The reason of having yet another script for register is that PLIC is configurable to the number of input sources and output targets. To implement it, some of the registers (see below IE) should be double nested in register description file. As of Jan. 2019, regtool.py supports only one nested multiple register format multireg.

Below register description doesn't match with Top Earlgrey RV_PLIC design. The RV_PLIC in the top_earlgrey is generated by topgen tool so that the number of interrupt sources is different.

  • LE: CEILING(N_SOURCE / DW) Value 1 indicates the interrupt source's behavior is edge-triggered It is used in the gateways module.
  • IE: CEILING(N_SOURCE / DW) X N_TARGET Each bit enables corresponding interrupt source. Each target has IE set.
  • PRIO: N_SOURCE Universal set across all targets. Lower n bits are valid. n is determined by MAX_PRIO parameter
  • THRESHOLD: N_TARGET Priority threshold per target. Only priority of the interrupt greater than threshold can raise interrupt notification to the target.
  • IP: CEILING(N_SOURCE / DW) Pending bits right after the gateways. Read-only
  • CC: N_TARGET Claim by read, complete by write

{{% registers x }}