An interrupt lets a device get the processor's attention without the processor having to keep checking. It can arrive between any two instructions, so a running program must be stopped cleanly, served, and resumed without noticing. How long that takes, the interrupt latency, can decide whether a motor controller works or a packet is lost. This article explains how RISC-V enables and takes interrupts, how they are prioritized, what makes up the latency, and how designers keep the worst case low.

12 min read | Intermediate

What You'll Learn

  • Explain how the mie, mip and mstatus registers control interrupts
  • Describe how a pipeline takes an interrupt at a clean point
  • Break interrupt latency into its parts and estimate it
  • Compare direct and vectored handlers and the wfi instruction

When an Interrupt Can Fire

Polling, Interrupts and DMA explains why systems use interrupts rather than repeatedly checking a device. Here we look inside the processor. A RISC-V core has separate machine-level interrupt lines for three sources: software (a message from another core), the timer, and external devices. Three things must all be true before one fires:

  1. the request is pending, shown by a bit in the CSR mip;
  2. that kind of interrupt is enabled, by a bit in mie;
  3. interrupts are enabled globally, by the MIE bit in mstatus, or the processor is running at a lower privilege level.
Sourcemip / mie bitmcause code
machine software interrupt3 (MSIP / MSIE)0x8000_0003
machine timer interrupt7 (MTIP / MTIE)0x8000_0007
machine external interrupt11 (MEIP / MEIE)0x8000_000B

Bit 31 of mcause is what marks these as interrupts rather than exceptions. A chip with many devices combines them with an interrupt controller, such as the RISC-V PLIC. It takes dozens of device lines, picks the highest-priority one, and drives the single external interrupt line. The handler then asks the controller which device it was. The SoC Design tutorial covers that hardware.

Most interrupt lines are level-sensitive: the request stays active until software clears the cause in the device. If the handler forgets, the interrupt fires again the instant mret turns interrupts back on, and the processor loops in the handler forever. Edge-triggered lines fire once per event, which avoids this but can lose an event if two come close together.

Taking an Interrupt in a Pipeline

An interrupt has no instruction of its own. The processor has to choose a point between two instructions, and treat it as if a trap happened right there. The usual method mirrors precise exceptions: the interrupt is attached to one instruction in the pipeline, typically the oldest not yet committed, which is then flushed along with those behind it. Then mepc takes that instruction's address, so that it runs after the handler returns. Earlier instructions finish normally. Work done by flushed instructions has to be repeated, which costs a few cycles but is always safe.

One rule cannot be broken: an instruction must either finish entirely or look as if it never started. A store that has already written to memory must count as done. A load from a device register, which might have side effects, must not run twice. So an instruction that has already reached memory or a device must finish before the interrupt is taken.

If several interrupts are pending at once, the RISC-V manual gives a fixed order for machine-level ones: external beats software, which beats timer. A design must also decide what happens when an interrupt and an exception arrive together. A common choice is to take the interrupt first at the instruction boundary, and if the instruction still faults when it reruns, the exception is taken then.

Direct and Vectored Modes

The CSR mtvec has two modes. In direct mode, all traps jump to one address, and the handler reads mcause to find out why, which takes some instructions. In vectored mode, an interrupt jumps straight to the base address plus 4 times the cause number, where there is usually one jump instruction per source. Vectoring saves a few instructions of decoding, and on a fast control loop those few instructions can be worth it.

Quick Check

Q: With mtvec in vectored mode at base 0x100, where does a machine timer interrupt jump?

Show Answer

The cause number is 7, so 0x100 + 4 x 7 = 0x11C.

What Is Interrupt Latency?

Interrupt latency is the time from a device raising its request to the first useful instruction of its handler. It adds up from several parts:

A timeline from the device raising its line to useful work: the request reaches the core, the core finishes or flushes current work, jumps to the handler, saves registers, and finds the source
  • Getting to the core: synchronizing the signal into the core's clock domain, and passing through the interrupt controller. A few cycles.
  • Reaching a clean point: the instruction in progress must complete or be flushed. Usually a few cycles, but a long operation, such as a divide or a load from slow memory, can add many.
  • Entering the handler: flushing the pipeline and fetching from mtvec, perhaps with a cache miss.
  • Saving context: software stores the registers the handler needs.
  • Finding the source: reading the cause and asking the interrupt controller.

Here is a rough estimate at 100 MHz. These numbers are assumptions for this example. 3 cycles to reach the core, 5 to reach a clean point, 3 to flush and fetch, and the 11-instruction handler from Traps and Precise Exceptions, which reaches useful work after saving 2 registers, about 5 instructions in. That is about 16 cycles, or 160 ns. The return path costs about as much again. So a full round trip for one simple interrupt is about 30 to 40 cycles, and a high rate of interrupts can eat a large share of a small processor. At 1 million interrupts per second, a 100 MHz core would spend a third of its time just entering and leaving.

The Worst Case Is What Matters

For real-time systems, average latency hardly matters. What counts is the longest possible latency, and that is much larger than the typical one:

  • Code that has turned interrupts off, for example inside a critical section, delays everything until it turns them on again.
  • A higher-priority interrupt may already be running.
  • A cache miss on the handler's code, or on its stack, can add hundreds of cycles. So can a TLB miss on a large system.
  • A long-running instruction may have to finish.

The variation in latency is called jitter. A motor controller or an audio system may tolerate a long delay, as long as it is always the same. That is why real-time designers often put interrupt handlers and stacks in a tightly coupled memory, whose timing is always the same, and keep critical sections short.

Where Is This Used?

Microcontroller cores are built around low, predictable interrupt latency. Arm's Cortex-M3 and M4 save eight registers in hardware on entry, and with zero-wait-state memory they reach a handler written as an ordinary C function in 12 cycles. Slower memory or bus traffic adds to that. On RISC-V, some embedded cores use fast-interrupt extensions that do similar saving in hardware or switch to a shadow set of registers. A large server core goes the other way: it has interrupts that may take thousands of cycles, and it reduces how many it takes, by batching network packets, because it cares about throughput rather than one response.

Priorities and Nesting

When a handler is running, interrupts are normally off, so a more urgent event has to wait. A handler can turn interrupts back on once it has saved mepc, mcause and the old mstatus, which allows a higher-priority interrupt to preempt it. This is interrupt nesting. Priorities in the controller decide who may interrupt whom. Each nested level needs stack space, which must be planned for, as How a Program Runs explains.

A common way to keep latency low is to split the work: a very short top half handler reads the device and clears the request, then a lower-priority task does the slow part with interrupts on.

Waiting for an Interrupt: wfi

When there is no work, a processor should not spin in a loop, burning power. The instruction wfi, wait for interrupt, tells the core that it may stop until an interrupt is pending. The clock to most of the core can be gated, so it uses very little power. This is how battery-powered systems spend nearly all of their time.

A subtle detail makes this reliable. An interrupt that arrives just before wfi must not be missed, or the core could sleep forever. So wfi wakes up if an interrupt is pending and enabled in mie, even if the global MIE bit is off. Software can turn off MIE, check whether there is work, run wfi, turn MIE on, and be sure of waking up. The specification also allows an implementation to treat wfi as a nop, so it must always be used in a loop that checks for work.

Quick Check

Q: A handler returns but forgets to clear the device's level-sensitive request. What happens?

Show Answer

As soon as mret turns interrupts back on, the request is still pending, so the interrupt fires again. The processor runs the handler over and over, and the main program makes little or no progress.

Designing for Low Latency

A designer who needs low latency has several tools. Keeping the pipeline short reduces the flush cost. Making divide and other long operations interruptible, so that they restart after the handler, removes their contribution to the worst case. Registers can be saved in hardware, or a second set of registers can be switched in, so that the handler needs no stack at all. A tiny dedicated core can serve the fastest interrupts, and leave the main processor undisturbed. Each of these costs area or complexity, and the right choice depends on the product's deadlines rather than on any average figure.

Common Mistakes

Mistake #1: Long critical sections

Code that keeps interrupts off for a long time sets the system's worst-case latency, whatever the hardware does. Measure the longest section, and keep it to a few instructions.

Mistake #2: Forgetting a shared variable is volatile

A variable changed by a handler and read by the main loop must be declared volatile in C, or the compiler may keep it in a register and never see the change.

Mistake #3: Clearing the request in the wrong order

If a handler clears the device's request after it has processed the data, an event arriving in between may be lost. Clear first, then handle, and re-check if more than one event can arrive.

Practice Exercise

Challenge: Can the Core Keep Up?

A core at 200 MHz takes a timer interrupt every 100 microseconds. These numbers are assumptions for this exercise. Entry and exit together cost 60 cycles, and the handler's useful work is 400 cycles. (a) What share of the processor does this use? (b) A second interrupt arrives every 20 microseconds with 40 cycles of work and the same 60-cycle overhead. What is the total share now? (c) The main loop has a critical section of 1,000 cycles with interrupts off. What is the worst-case wait for the second interrupt, in microseconds, ignoring the first? (d) Would that matter if the device must be served within 10 microseconds?

Hint

In 100 microseconds, the core runs 20,000 cycles. In 20 microseconds, 4,000 cycles.

Solution

(a) 460 / 20,000 = 2.3 percent. (b) The second takes 100 / 4,000 = 2.5 percent, so 4.8 percent in total. (c) 1,000 cycles at 5 ns is 5 microseconds, plus the entry time. (d) It would meet 10 microseconds by itself, but add the 460 cycles of the first interrupt (2.3 microseconds) if it can run first, and the margin shrinks to under 3 microseconds. Real designs add up all such delays in a worst-case analysis.

Summary

  • An interrupt fires when it is pending, enabled in mie, and globally enabled, or when the core is at a lower privilege.
  • A pipeline takes an interrupt at a clean boundary, flushing later work, and resumes it after mret.
  • Latency adds up from getting to the core, reaching a clean point, entering the handler, saving registers and finding the source.
  • Worst-case latency, set by critical sections and cache misses, matters more than the average in real-time work.
  • wfi sleeps until an interrupt, and it wakes correctly even with global interrupts off.
Did you find what you were looking for?

Quiz

No quiz questions available for this article.