Programmer’s Guide

Initialization

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

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

// Pseudo-code below
void plic_init() {
  // Configure priority
  // Note that PRIO0 register doesn't affect as intr_src_i[0] is tied to 0.
  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));
}

Handling Interrupt Request Events

If software receives an interrupt request, it is recommended to follow the steps shown below (assuming target 0 which uses CC0 for claim/complete).

  1. Claim the interrupts right after entering to the interrupt service routine by reading the CC0 register.
  2. Determine which interrupt should be serviced based on the values read from the CC0 register.
  3. Execute ISR, clearing the originating peripheral interrupt.
  4. Write Interrupt ID to CC0
  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() {
  uint32_t tid = /* ... */;
  uint32_t iid = *(CC + tid);
  if (iid == 0) {
    // Interrupt is claimed by one of other targets.
    return;
  }

  do {
    // Process interrupts...
    // ...

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

As a reference, default interrupt service routines are auto-generated for each IP, and are documented here.

Device Interface Functions (DIFs)

Registers

The RV_PLIC in the top level is generated by topgen tool so that the number of interrupt sources may be different.

  • 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