RISC-V in Automotive: ADAS and Safety-Critical Control System Architectures
1. Introduction
An ADAS domain controller has to do two contradictory things at once. One side of the chip runs perception — camera and radar fusion, object detection, path planning — workloads that want throughput, caches, speculation, and a general-purpose OS underneath them. The other side runs the control loop that actually moves the brakes and the steering rack, and that side cares about exactly one thing: never miss a deadline, and never act on a wrong answer. Get the first side wrong and the car drives worse. Get the second side wrong and someone gets hurt.
RISC-V’s relevance to this problem isn’t that it’s open or that licensing is cheaper than Arm — those are business arguments. The engineering argument is that RISC-V is a modular ISA with a privileged architecture designed to be implemented differently at different privilege levels, which means a single vendor can build one core that runs Linux for perception and a structurally different, lockstepped core on the same die that runs the safety loop, sharing an interrupt fabric and a memory-protection scheme instead of two unrelated chips talking over a slow bus. RISC-V’s modular architecture allows the semiconductor industry to build heterogeneous SoCs combining high-performance cores with deterministic real-time cores and safety-certified lockstep cores, tailored to specific workloads. This article works through why that architecture looks the way it does, what the kernel actually touches versus what stays entirely in hardware, and builds a validated userspace demonstration of the software pattern — dual-channel comparison, deadline supervision, watchdog-triggered fail-safe — that a real lockstep core enforces below the instruction stream.
2. Historical Background
Automotive functional safety architecture predates RISC-V by decades. ISO 26262, first published in 2011 and revised in 2018, formalized Automotive Safety Integrity Levels (ASIL A through D) as a way to size the rigor of a design process — and the redundancy of the hardware — to the severity of what happens if the function fails. Braking and steering control land at ASIL-D, the strictest tier, which in practice has meant dual-core lockstep (DCLS) silicon: two cores executing the identical instruction stream with comparator logic watching for divergence, a pattern that goes back to Infineon’s TriCore Aurix and NXP’s Arm-based safety MCUs long before RISC-V automotive silicon existed.
What changed is that RISC-V’s privileged specification gives a chip vendor room to implement M-mode (machine mode) safety mechanisms — physical memory protection, trap delegation, core-local interrupt controllers — as fully custom hardware without touching the base ISA that userspace and the kernel compile against. That’s what let IP vendors start shipping ASIL-qualified RISC-V cores as a product category rather than a research exercise. Andes Technology’s D23-SE, built on the production-proven D23 core, brings Dual-Core Lockstep and Split-Lock operation to ASIL-B and ASIL-D automotive systems, and it isn’t alone — Nuclei Systems now licenses ASIL-D compliant CPUs to major automotive customers, and Rambus has taken the same DCLS discipline into the security co-processor with a RISC-V core certified ASIL-D ready by SGS-TÜV Saar for use in V2X communications, ADAS, and ECU platform management. The open-source side has moved too: the SafeLS project implemented an open-source lockstep RISC-V core based on Gaisler’s NOEL-V, integrated into an FPGA-synthesizable SoC assessed against automotive and railway safety requirements.
3. Systems-Level Problem
General-purpose Linux scheduling optimizes for average throughput. A control loop for automatic emergency braking needs the opposite guarantee: a bounded worst case, not a good average. If 999 cycles out of 1000 complete in 200 microseconds and one takes 12 milliseconds because of a page fault, an IRQ storm, or a scheduler decision that let a lower-priority task run, that one cycle is the one that matters, because it’s the one where a vehicle two meters from an obstacle didn’t get a brake command in time.
Three failure modes have to be handled, not just detected after the fact:
Timing faults — the control loop is logically correct but late. A correct answer delivered after the actuation window closes is equivalent to no answer.
Value faults — a bit flip from a cosmic-ray-induced single-event upset (SEU), a marginal voltage rail, or a genuine software bug produces a wrong but timely answer.
Silent faults — the system stops updating without an obvious crash: a livelocked thread, a stuck interrupt, a deadlocked lock that never times out.
Lockstep hardware addresses value faults. Real-time scheduling and interrupt discipline address timing faults. A watchdog — hardware or kernel-mediated — addresses silent faults. None of the three substitutes for the other two, and a production ADAS domain controller needs all three simultaneously, which is why the architecture ends up layered the way it does.
4. Linux Kernel Architecture
Here’s the part that’s easy to get wrong by assumption: Linux does not run the ASIL-D control loop. On a real automotive RISC-V SoC, the safety-rated lockstep core typically runs a small, statically-verified RTOS or a bare-metal safety kernel — not arch/riscv Linux — precisely because Linux’s memory allocator, page cache, and scheduler have failure modes (allocation stalls, RCU grace periods, cgroup accounting) that are extraordinarily hard to bound formally to the standard ISO 26262 demands. Automotive RISC-V platforms require hypervisor-certified separation for Linux and AUTOSAR coexistence, with deterministic interrupt behavior and memory protection enforced through PMP and hardware partitioning, and dedicated safety islands provide independent supervision of the main compute cluster in centralized autonomous platforms, separate from wherever Linux is running perception.
So what does arch/riscv Linux actually own in this picture? Three things, all visible in the architecture diagram above:
The non-safety compute cluster — perception, sensor fusion, path planning — where PREEMPT_RT-patched Linux with
SCHED_FIFO/SCHED_DEADLINEgets you soft real-time behavior good enough for a monitoring or advisory role, but not the certified control authority itself.The interrupt fabric — the RISC-V Platform-Level Interrupt Controller (PLIC), and increasingly the newer Core-Local Interrupt Controller (CLIC) for lower-latency vectored interrupts, both of which the kernel’s
drivers/irqchip/irq-riscv-intc.cand PLIC driver manage for the Linux-owned cores.PMP-aware memory mapping — Physical Memory Protection is configured in M-mode by firmware (OpenSBI) before S-mode Linux ever boots, carving out regions the kernel is permitted to touch and regions reserved for the safety island. Linux doesn’t configure PMP directly from S-mode; it lives inside the fence PMP has already drawn.
The safety loop itself — the thing this article’s demo simulates — runs on hardware that Linux can supervise (via a watchdog character device, via shared memory heartbeat, via CAN) but does not host. That distinction is the single most important thing to take from this section.
5. Internal Working
Dual-core lockstep works by running two instances of the same core — sometimes physically identical, sometimes a “delayed lockstep” pair offset by a few cycles to catch faults that a perfectly synchronous pair would miss — on the identical instruction stream and comparing outputs every cycle or every bus transaction. DCLS remains the dominant ASIL-D safety mechanism: two identical cores execute the same instruction stream while comparator logic detects divergence. When the comparator sees a mismatch, it doesn’t try to figure out which core is right — that’s not knowable from output comparison alone — it asserts a fault line that forces the whole subsystem into a predefined safe state, typically holding the last known-safe actuator command or commanding a controlled stop.
Split-lock mode, which Andes’ D23-SE also supports, lets the same silicon run as two independent cores for non-critical workloads when the full safety margin isn’t needed, then reconfigure into lockstep for the safety-critical phase — a way to reclaim performance without a second, dedicated safety die. RISC-V’s open specification also enables “flex-lockstep” designs, where cores transition between modes rather than being permanently wired one way, and custom ISA extensions can build software-defined hardware enclaves that provide spatial and temporal isolation for ASIL-D tasks on the same die as high-performance perception workloads — the single-die, mixed-criticality SoC that section 4’s architecture diagram shows.
Software can’t replicate cycle-level instruction comparison — that only exists in silicon. What software can replicate faithfully is the higher-level pattern: two independent computations of the same function from the same input, a bitwise comparator, and a fail-safe action on mismatch. That’s exactly the structure of this article’s demo, and it’s honest about the gap: the demo catches a corrupted floating-point register the same way DCLS catches a corrupted ALU output, but it does so at thread granularity measured in milliseconds, not instruction granularity measured in nanoseconds.
6. Step-by-Step Execution Flow
Github Link:
http://github.com/sysdr/howtech-p/tree/main/RISC-V-in-Automotive/adas-lab
Walking through one 5 ms control period as the demo implements it:
Snapshot. The orchestrating thread takes a fresh sensor reading (in the real system: fused radar/camera distance and closing speed; in the demo: a deterministic pseudo-random generator standing in for a live sensor bus) and writes it to a shared, read-only-during-compute structure.
Fork. Both channels are released from a
pthread_barrier_waitsimultaneously. This barrier is the software analog of the clock edge that releases both lockstep cores together.Independent compute. Each channel calls the identical pure function,
compute_brake_command(), on its own snapshot of the sensor frame. No shared mutable state is touched during compute — that’s what makes the two channels genuinely independent rather than incidentally correlated.Join. Both channels rendezvous at a second barrier before either result is read. This ordering matters: reading a result before both channels have written it isn’t a comparator, it’s a race.
Compare. The orchestrator does a
memcmpof the twocontrol_cmd_tstructs — not a floating-point epsilon comparison. Deterministic computation on identical bit-pattern input must produce identical bit-pattern output; any difference at all is a fault, not noise to be tolerated.Branch. Match → the command is eligible to reach the actuator. Diverge → the fault is logged and the system holds the fail-safe command (full brake, in this demo) rather than trusting either channel.
Deadline check. Elapsed wall-clock time for the cycle is compared against the 5 ms budget, independent of whether the values matched — a late-but-correct cycle is still a fault class of its own.
Heartbeat. A monotonic timestamp is published for the watchdog thread to observe.
Watchdog poll. A separate thread, decoupled from the control loop’s own scheduling, checks whether the heartbeat has advanced within the timeout window. If it hasn’t, that thread — not the possibly-wedged control loop — forces the safe state.
Sleep to next tick, and repeat.
7. Kernel Data Structures
The demo runs entirely in userspace, but every structure it uses maps to a real kernel-visible construct on the Linux side of a production system:
Demo construct Real kernel/hardware analog pthread_barrier_t rendezvous Hardware clock-edge synchronization between lockstep cores atomic_uint_fast64_t heartbeat A value written to a watchdog device via ioctl(fd, WDIOC_KEEPALIVE, 0), or hrtimer-driven kicking of /dev/watchdog Watchdog polling thread The kernel’s softlockup/hardlockup detector (kernel/watchdog.c), or an external hardware watchdog IC on the safety island sched_setscheduler(SCHED_FIFO) struct sched_rt_entity inside task_struct, or SCHED_DEADLINE‘s struct sched_dl_entity for a harder guarantee clock_gettime(CLOCK_MONOTONIC) ktime_get() inside the kernel, backed by the RISC-V rdtime CSR read (time / timeh) Fail-safe branch on divergence Comparator fault line asserted into the safety island’s mcause/trap path, typically routed to a dedicated NMI-equivalent
The one honest gap in this table: there’s no userspace equivalent of the PMP configuration that keeps the safety core’s memory region physically unreachable from the Linux-hosted cores. That protection is a hardware property enforced before any instruction on the Linux side executes, and no amount of software discipline in a demo replicates it — it’s the reason section 4 insists on keeping the safety loop off Linux entirely in a real design.
8. CPU-Level Behaviour
RISC-V’s privileged architecture defines three (sometimes two) privilege levels — Machine (M), Supervisor (S), and User (U) — and the safety story is largely about what happens at the M/S boundary. PMP is configured through a bank of pmpcfgN/pmpaddrN CSRs, writable only from M-mode, each entry describing a physical address range and a permission set (read/write/execute) plus a locking bit that, once set, cannot be cleared until the next reset. That lock bit is the mechanism: firmware sets up the memory partition between the safety-rated region and the general-purpose region at boot, locks it, and from that point forward not even a kernel bug in the Linux-hosted S-mode can widen its own permissions into the safety core’s memory.
Trap handling follows the same M/S split. An exception or interrupt on a RISC-V hart sets mcause and either handles it in M-mode or delegates it to S-mode via medeleg/mideleg. A lockstep comparator fault is architecturally similar to an NMI: it’s routed to demand immediate attention regardless of the current interrupt-enable state, because the alternative — waiting for a normal interrupt to be serviced behind other pending work — defeats the purpose of having lockstep at all.
At the microarchitecture level, delayed lockstep (running the shadow core a fixed number of cycles behind the primary and comparing with matching delay) catches a different fault population than fully synchronous lockstep: transient voltage droops and clock jitter that would otherwise correlate across two literally-synchronous cores and slip past a comparator that assumes independence. This is a real design tradeoff vendors make, not a detail — a fully synchronous lockstep pair sharing a clock tree and power rail is not, in the strictest sense, statistically independent, and functional-safety case documentation has to argue that point explicitly.
9. Performance Analysis
The demo measures three things every cycle: wall-clock cycle time (barrier-to-barrier), whether that time exceeded the 5 ms budget, and whether the two channels’ outputs matched. A clean run over 300 cycles on this article’s development machine:
=== ADAS Lockstep Control Loop Summary ===
cycles run: 300
faults injected: 1
faults detected: 1
deadline overruns: 0 (budget 5000000 ns)
avg cycle time: 0.009 ms
max cycle time: 0.055 ms
watchdog tripped: no
Sub-millisecond cycle times here are expected and not representative of a real ECU: this demo runs SCHED_FIFO on an otherwise-idle Linux host with no actual sensor I/O, no CAN bus round trip, and no perception workload competing for cache and memory bandwidth. What the numbers are useful for is relative comparison — how the same code behaves under different instrumentation, which is the subject of the next section, and it’s where the real finding in this build lives.
10. Debugging Techniques
Standard tooling applies directly: ftrace for scheduling latency (trace-cmd record -e sched_switch), perf sched latency for a statistical view of run-queue wait time, and for the RISC-V hardware itself, JTAG via OpenOCD for M-mode/S-mode register and CSR inspection when a debug adapter is available on the target.
But the interesting result from building this demo came from the mandatory validation gauntlet, not from the feature code. Running the identical binary under -fsanitize=thread and separately under valgrind --leak-check=full produced a watchdog trip — the 15 ms heartbeat timeout fired — on some runs, with zero data races reported by TSan and zero memory errors reported by valgrind:
$ ./adas_lockstep_tsan
[FAULT] cycle 150: lockstep divergence detected - entering fail-safe...
[WATCHDOG] heartbeat timeout - forcing safe state
=== ADAS Lockstep Control Loop Summary ===
...
watchdog tripped: yes
$ valgrind --leak-check=full --show-leak-kinds=all ./adas_lockstep
...
max cycle time: 3.465 ms
watchdog tripped: yes
==916== ERROR SUMMARY: 0 errors from 0 contexts
This is not a bug in the lockstep or comparator logic — faults injected still equal faults detected on every single run, sanitizer or not. It’s a genuine and useful finding: instrumentation overhead is itself a timing perturbation, and a watchdog budget sized against bare-metal or lightly-loaded execution can trip spuriously the moment you run the same code under a tool that adds tens-to-hundreds of x scheduling and memory-access overhead. Valgrind’s shadow-memory interpretation alone regularly produces 20–50x slowdowns; TSan’s happens-before tracking is cheaper but still substantial, and both are enough to blow a 15 ms budget derived from 5 ms nominal cycles.
The production lesson generalizes past this demo: never validate a watchdog timeout value under the same tool you use to validate correctness. Correctness tools (TSan, ASan, valgrind) are allowed — expected — to change timing arbitrarily. Timing validation belongs on unmodified, or at most lightly-traced (ftrace, hardware performance counters), builds. Conflating the two in one CI job is a realistic way to end up with a watchdog that either never trips in the lab (because the lab always runs under a slow debug build) or trips constantly in the lab and gets its timeout “fixed” upward until it’s useless in production.
11. Production Failure Scenarios
PMP misconfiguration at boot. If OpenSBI locks a PMP region with the wrong address range — off-by-one on a page boundary is the classic version — the Linux-hosted cores can end up with either read access into the safety core’s private memory (a security and certification problem) or, more insidiously, no access to a region they legitimately need, producing a boot-time fault that’s easy to misdiagnose as a driver bug rather than a firmware configuration error.
Lockstep divergence from correlated, not independent, faults. As noted in section 8, two cores sharing a clock tree and voltage rail aren’t fully statistically independent. A voltage droop severe enough to affect both cores identically can produce matching wrong output — the exact failure mode DCLS exists to prevent, defeated by the assumption of independence not holding under that specific stressor. This is why automotive safety cases require explicit dependent-failure analysis, not just “we have two cores.”
Watchdog timeout tuned against the wrong build. Section 10’s finding, promoted to a field scenario: a watchdog budget validated only under production-optimized builds can be too tight for the debug or OTA-update builds that occasionally run in the field during diagnostics, causing spurious safe-state entries that look like intermittent hardware failures to a service technician.
Silent hang without divergence. A control loop thread that deadlocks on a lock (rather than crashing or diverging) produces neither a comparator fault nor an obviously wrong value — it simply stops. This is precisely why the watchdog thread in this demo is architecturally separate from the control loop rather than a self-check inside it: a wedged thread cannot be trusted to detect its own wedging.
Interrupt storm starving the safety-critical hart. On a shared PLIC, a high-rate, low-priority interrupt source (a flaky sensor bus, for instance) misconfigured to a priority level that competes with the control loop’s own timer interrupt can introduce exactly the kind of tail-latency spike section 3 describes — correct code, correct hardware, still late.
Working demo Link:
12. Real-World Production Use Cases
RISC-V’s automotive safety footprint by 2026 spans the full stack, from crypto co-processors to full application cores. Rambus’s RT-645 crypto core safeguards SoCs used in V2X communications, ADAS, and infotainment — security and safety converging on the same certified RISC-V IP rather than being bolted on separately. On the compute side, Nuclei Systems reports more than 300 global licensees and billions of deployed SoCs, with its ASIL-D compliant CPUs already licensed to major automotive customers moving from Asian volume markets into Western automotive design wins. Toolchain vendors are building for this market specifically: TASKING’s certifiable, end-to-end toolchain targets automotive, aerospace, industrial, and robotics safety-critical development on RISC-V, which is a leading indicator — compiler and debugger certification investment doesn’t happen ahead of real production volume.
The centralized zonal-controller trend in modern EE architectures is where the mixed-criticality pattern from section 4 matters most in practice: rather than one MCU per function scattered around the vehicle, a single domain controller handles ADAS, body control, and increasingly infotainment on one SoC, which is exactly the environment where safety islands must provide independent supervision of the main compute cluster and where hypervisor-certified separation for Linux and AUTOSAR coexistence stops being an academic requirement and becomes the thing standing between a perception bug and a control-loop fault.
13. Hands-on Lab
The accompanying startup.sh --docker builds and validates the exact demo discussed throughout this article. It:
Detects the host distribution and kernel, and warns (without failing) if the host isn’t
riscv64— the demo validates the software pattern, not real RISC-V PMP/DCLS hardware.Installs
gcc,valgrind, andgdbviaapt-getif they’re not already present.Generates
adas_lockstep.cfrom an embedded heredoc — no external file dependency.Builds three binaries: a baseline (
-Wall -Wextra -Werror -O2), an ASan+UBSan build, and a ThreadSanitizer build.Runs all three, then runs the baseline under
valgrind --leak-check=full --show-leak-kinds=all.Reports a pass/fail summary for each stage, and specifically calls out if the watchdog trips under TSan — the expected, benign finding from section 10.
chmod +x startup.sh --docker
./startup.sh --docker # build, validate, run
Two things worth doing manually after the script runs: first, edit FAULT_INJECT_CYCLE in the generated source and rebuild to confirm the comparator catches a fault at any cycle, not just the one shipped by default. Second, run the TSan binary five or six times in a row and watch watchdog tripped flip between yes and no — that non-determinism is the section 10 finding, reproduced live rather than taken on faith.
14. Best Practices
Never host the ASIL-D control authority on general-purpose Linux. Use Linux for perception, monitoring, and the non-safety compute cluster; keep the certified control loop on a safety-rated core running an RTOS or bare-metal safety kernel, connected by a well-defined, narrow interface (shared memory heartbeat, CAN, or a dedicated mailbox) rather than a shared address space.
Treat PMP configuration as part of the safety case, not an implementation detail. Boot-time PMP startup belongs in the same review and test rigor as the control algorithm itself, because a PMP bug silently defeats every software isolation guarantee built on top of it.
Validate correctness and timing separately. Sanitizers and valgrind are for correctness; never derive or confirm a watchdog timeout, a deadline budget, or any other timing constant from a run made under heavy instrumentation.
Compare outputs bitwise, not with tolerance. A deterministic function given identical input must produce identical output; introducing an epsilon comparison into a lockstep-style check quietly converts a hard safety property into a soft one.
Decouple the watchdog from the thing it’s watching. A control loop cannot reliably detect its own hang; the supervisor needs independent scheduling, and ideally independent hardware.
Document dependent-failure analysis explicitly for any lockstep design sharing a clock or power rail between channels — “two cores” is not automatically “two independent cores.”
15. Summary
RISC-V’s contribution to automotive ADAS isn’t a faster core — it’s a privileged architecture flexible enough that a single vendor can build application-class, real-time, and safety-certified lockstep cores as genuinely different hardware on one die, connected through a small number of well-specified mechanisms: PMP for spatial isolation, a shared or bridged interrupt fabric, and a comparator fault line that behaves like an NMI. Linux’s role in that picture is real but bounded — it owns perception, non-safety compute, and supervisory monitoring, and it lives inside the memory partition that M-mode firmware draws before the kernel ever boots, not around it.
The demo built and validated here reproduces the software shape of that boundary — dual-channel computation, bitwise comparison, deadline supervision, an independent watchdog — cleanly enough to pass -Wall -Wextra -Werror -O2, ASan, UBSan, TSan, and valgrind’s full leak-check. Its one honest failure mode, a spurious watchdog trip under heavy sanitizer instrumentation, turned out to be the most useful thing it produced: a reminder that timing guarantees and correctness guarantees have to be validated on different builds, because the tools that give you one routinely cost you the other.


