⏱️ Lectura: 11 min
Saving an interrupt’s context on a RISC-V microcontroller costs, in the best case, 44 clock cycles. An ARM Cortex-M0 core, designed more than a decade ago, handles the same task in 27 cycles. That gap, measured instruction by instruction, is the core of an extensive technical critique that embedded systems engineer Dmitry.GR published under the title “RISC-V: They Should Have Known Better”.
📑 En este artículo
The piece doesn’t attack the idea of an open, royalty-free instruction set (ISA); it targets specific design decisions that, according to the author, put RISC-V-based microcontrollers at a disadvantage against established competition in the low-cost segment, precisely the ground where the RISC-V ecosystem itself promises the most growth.
TL;DR
- Engineer Dmitry.GR published an extensive technical critique of the RISC-V architecture titled They Should Have Known Better.
- In RV32I with Zicsr, saving an interrupt’s context costs at least 21 cycles via CSRRW and 19 individual stores.
- Restoring the registers on interrupt exit adds at least 20 more cycles of individual loads.
- The minimum total cost per interrupt in RV32I is 44 cycles, before executing a single line of C.
- ARM Cortex-M0 handles entry in 15 cycles and exit in 12: 27 cycles total, with the handler written directly in C.
- With RV32E (16 registers instead of 32), the cost drops to 38 cycles: still a third slower than Cortex-M0.
- The author notes that extensions like CLIC were created to paper over this weakness in the RISC-V base ISA.
- Despite the critique, the author believes RISC-V will dominate cheap microcontrollers, replacing the 8051.
What Happened
Dmitry.GR, an engineer well known in the embedded systems community for low-level reverse engineering work, published an extensive essay organized into sections like “Everything for Everyone”, “Optionality”, “Missing Obvious Pieces”, “Ridiculous encoding”, and “Alleged Fixes”. The throughline is that RISC-V, by trying to simultaneously serve supercomputers, servers, and one-dollar microcontrollers, ends up not being optimal for any of those use cases.
His central argument in the “Everything for Everyone” section is simple: no architecture can be the best choice for everything. What a high-end processor needs is, in large part, the opposite of what a cost-focused cheap core needs. The author argues that these decisions aren’t just about microarchitecture: they end up affecting the instruction set itself.
To support the point, he describes the typical use case for a cheap microcontroller: interconnecting and reconfiguring dedicated hardware blocks inside a larger chip, as in an MP3 player, an SD card, or a USB flash drive. The heavy lifting is done by dedicated logic; the core just flips registers and reacts to events. Two things matter there: low interrupt latency and small code size, because these devices usually run from ROM or RAM (not from NOR flash, too expensive for mass production), where code density directly affects chip cost.
Context and History
RISC-V originated at the University of California, Berkeley, as an open, modular instruction set, and today it’s managed by the nonprofit organization RISC-V International. Unlike ARM’s proprietary licensing model, anyone can implement a RISC-V core without paying royalties for the ISA itself.
That openness comes with a particular design philosophy: a minimal base ISA (RV32I or RV64I) plus a catalog of optional extensions that each manufacturer can combine according to their use case: M for multiplication, A for atomics, F and D for floating point, C for 16-bit compressed instructions, and so on. There’s also RV32E, an “embedded” variant that trims the register file from 32 to 16 for even smaller chips.
One of those optional modules is Zicsr, which adds the instructions for reading and writing Control and Status Registers (CSRs). Without Zicsr, there’s no spec-compliant way to handle an interrupt, because there’s no temporary place to save a register before you can start saving the rest. MIPS, facing the same problem, simply reserved two registers for exclusive use, $k0 and $k1. RISC-V, instead, solves this with the mscratch or sscratch CSRs, and only if the core implements Zicsr.
On the competing side, ARM has spent more than ten years selling Cortex-M0 as the reference core for ultra-cheap microcontrollers, with mature silicon, established tooling, and interrupt handling resolved in hardware from the original design.
RISC-V Interrupts: Technical Details and Performance
The heart of the essay is an instruction-by-instruction cycle count of what it takes an RV32I core with Zicsr to service an interrupt and return to the interrupted code, compared to Cortex-M0. Neither is an out-of-order core: retiring one instruction per cycle is already a good result for both.
In RV32I, interrupt entry starts with a CSRRW to save a temporary register (say t0) and obtain the base address where the rest will be saved. Then ra, sp, gp, tp, t1 through t6, and a0 through a7 have to be saved one by one. Another CSRRW retrieves the original value of t0 and saves it too. That’s, according to the author, at least 21 cycles.
// Interrupt prologue in RV32I + Zicsr (pseudo-assembly)
csrrw t0, mscratch, t0 // swap t0 with the save base
sw ra, 0(t0)
sw sp, 4(t0)
sw gp, 8(t0)
sw tp, 12(t0)
sw t1, 16(t0)
// ... t2-t6, a0-a7 follow the same pattern
csrrw t1, mscratch, t1 // retrieves the original t0 and saves it
sw t1, 76(t0)
jal handler_interrupcion_en_c
On exit, the process is reversed: a CSRRW to retrieve the save address and 19 load instructions to restore each register, at least 20 more cycles. Adding the entry JAL and the exit RET, the author calculates a minimum cost of 44 cycles before a single line of the C handler runs.
Cortex-M0 handles the same thing with dedicated hardware: it automatically stacks the registers the calling convention (ABI) treats as volatile in 15 cycles on entry, and restores them in 12 cycles on exit. The developer writes the handler directly in C, with no manual assembly involved.
// Cortex-M0 interrupt handler: no manual prologue
void __attribute__((interrupt("IRQ"))) uart_rx_handler(void) {
uint8_t byte = UART0->DR;
ring_buffer_push(℞_buffer, byte);
}
With RV32E, which reduces the register file from 32 to 16, the savings amount to 6 cycles in each phase relative to RV32I, according to the author’s own calculation, which puts the total cost at 38 cycles: still more than a third above Cortex-M0.
| Core | Interrupt Entry | Interrupt Exit | Total | Manual Assembly? |
|---|---|---|---|---|
| RV32I + Zicsr | 21 cycles (CSRRW + 19 stores) | 20 cycles (CSRRW + 19 loads) | 44 cycles | Yes, mandatory |
| RV32E + Zicsr | 6 cycles less than RV32I | 6 cycles less than RV32I | 38 cycles | Yes, mandatory |
| ARM Cortex-M0 | 15 cycles (hardware) | 12 cycles (hardware) | 27 cycles | No, handler directly in C |
The piece also points out that the existence of CLIC (Core-Local Interrupt Controller) and proprietary “fast IRQ” or auto-stacking extensions across different manufacturers is, in itself, evidence of the problem: the base ISA forces every vendor to invent non-standard silicon to reach parity with a decade-old Cortex-M0, which in turn fragments the standard itself.
flowchart TD
A["Hardware interrupt"] --> B{"Core"}
subgraph RV32I ["RISC-V RV32I + Zicsr"]
C["CSRRW saves context: 21 cycles"] --> D["JAL to handler in C"]
D --> E["Handler executes in C"]
E --> F["Restores with loads: 20 cycles"]
F --> G["RET to interrupted code"]
end
subgraph CM0 ["ARM Cortex-M0"]
H["Hardware stacks registers: 15 cycles"] --> I["Handler executes in C"]
I --> J["Hardware restores: 12 cycles"]
end
B -->|"RISC-V"| C
B -->|"Cortex-M0"| H
💭 Key point: the problem isn’t that RISC-V lacks a fast solution for interrupts, it’s that this solution isn’t part of the base ISA: each manufacturer solves it their own way, and code stops being portable between different RISC-V cores.
How to Verify This
You can verify this analysis yourself by compiling a minimal handler and reading the assembly generated with objdump. First you need a cross toolchain for RISC-V.
# Linux (Debian/Ubuntu)
sudo apt install gcc-riscv64-unknown-elf gdb-multiarch
# macOS (Homebrew)
brew tap riscv-software-src/riscv
brew install riscv-tools
# Windows (via xPack, requires Node.js)
npm install --global xpm@latest
xpm install --global @xpack-dev-tools/riscv-none-elf-gcc@latest
With the toolchain installed, compile a simple interrupt handler and disassemble it:
riscv64-unknown-elf-gcc -march=rv32i_zicsr -mabi=ilp32 \
-O2 -c interrupt_handler.c -o interrupt_handler.o
riscv64-unknown-elf-objdump -d interrupt_handler.o | less
In the output you’ll see the csrrw sequence followed by a chain of sw (store word) instructions in the prologue, and its mirror of lw (load word) instructions in the epilogue. Counting those lines is, literally, reproducing the cycle count from the original article.
Impact and Analysis
The most frequently cited practical consequence of the essay is fragmentation. If every RISC-V silicon manufacturer needs its own fast-interrupt extension to compete with Cortex-M0, handler code stops being portable between a SiFive chip, an Espressif chip, and one from another vendor, even if they all say “RISC-V” on the box.
There’s a real trade-off in RV32E’s design: shrinking the register file from 32 to 16 improves code density and the cost of the interrupt prologue, but it also reduces how much the compiler can keep in registers during normal code, which can increase stack traffic in functions with many local variables. It’s not a free improvement: it’s a shift in balance.
⚠️ Heads up: comparing cycle counts across different architectures depends on the compiler, optimization flags, and the exact core implementation. The numbers in this article are those reported by the original author from his own manual count, not a standardized benchmark.
What’s Next
RISC-V International keeps ratifying extensions aimed at fast interrupts, CLIC among them, with the stated goal of standardizing what proprietary implementations currently solve case by case. Dmitry.GR himself is explicit that this doesn’t doom RISC-V in the cheap microcontroller segment: he predicts it will end up displacing the 8051, not because of the quality of its ISA, but because the 8051 sets an even lower bar. The underlying debate (whether an ISA with almost everything optional is better than one with fewer parts but better resolved out of the box) remains open in the embedded systems community.
📖 Summary on Telegram: View summary
Try it yourself: install the RISC-V toolchain, compile an interrupt handler with -march=rv32i_zicsr, and count the prologue’s sw/lw instructions with objdump -d to see these 44 cycles with your own eyes.
Frequently Asked Questions
What is RISC-V?
It’s an open, modular instruction set (ISA), managed by RISC-V International, that any manufacturer can implement without paying royalties for the ISA design itself.
What’s the difference between RV32I and RV32E?
RV32I is the base 32-bit ISA with 32 general-purpose registers. RV32E is a variant designed for very small microcontrollers that trims the register file to 16 registers to save silicon area.
Why doesn’t RISC-V automatically stack registers like Cortex-M0?
Because the base ISA doesn’t include that function in hardware: each manufacturer decides whether to implement it through their own extension, which produces different solutions across cores.
What is the Zicsr extension?
It’s the set of instructions for reading and writing Control and Status Registers. Without it, there’s no spec-compliant way to handle interrupts in RISC-V.
Will RISC-V replace ARM in cheap microcontrollers?
According to the author of the critique himself, yes, but in the single-purpose microcontroller segment it will mainly displace the 8051, not because its ISA is superior, but because the 8051 starts from an even older base.
Where can I read the full critique?
The original essay, written by Dmitry.GR, is published at dmitry.gr.
References
- RISC-V: They Should Have Known Better, by Dmitry.GR: the original essay with the full cycle count.
- RISC-V International: the organization that manages the RISC-V ISA specifications.
- RISC-V on Wikipedia: history and current status of the project.
- riscv-gnu-toolchain on GitHub: the official repository for building the cross toolchain.
- ARM Cortex-M0: official page for the core used as a comparison in the article.
📱 Enjoying this content? Follow @programacion on Telegram for daily tech content in Spanish: quick summaries, fresh content every day.
Imagen destacada: Foto de Bartosz Kwitkowski en Unsplash
0 Comments