We ran the same STM32 firmware on Renode vs QEMU: here's what diverged

Same ELF. Same firmware, byte for byte. Two simulators, both claiming to emulate an STM32F4. One boots and prints "Hello world!" like nothing happened. The other dies before it gets anywhere near my code, with a fatal error I'd never seen before and a full register dump.
Neither one is lying.
The firmware
Nothing exotic. examples/bare-metal/stm32f4-discovery from the Chiplab
repo, unmodified. Same file I used to
plant a real crash and watch nothing happen.
Rust, no_std, no_main, one job: bring up USART2 on PA2/PA3 at 115,200 baud and
print "Hello world!" once, then loop forever. 22,680 bytes, thumbv7m-none-eabi
release build.
//! Hello world over USART2 (PA2/PA3) on the STM32F4 Discovery.
#![no_main]
#![no_std]
use cortex_m_rt::entry;
use panic_halt as _;
use stm32f4xx_hal::{pac, prelude::*, rcc::Config, serial::Serial};
#[entry]
fn main() -> ! {
let dp = pac::Peripherals::take().unwrap();
let mut rcc = dp.RCC.freeze(Config::hsi().sysclk(16.MHz()));
let gpioa = dp.GPIOA.split(&mut rcc);
let tx = gpioa.pa2;
let rx = gpioa.pa3;
let serial = Serial::new(
dp.USART2,
(tx, rx),
stm32f4xx_hal::serial::Config::default().baudrate(115_200.bps()),
&mut rcc,
)
.unwrap();
let (mut tx, _) = serial.split();
let msg = b"Hello world!\n";
for &byte in msg {
let _ = nb::block!(tx.write(byte));
}
loop {}
}
No bugs planted this time. I wanted the boring case: does the same binary behave the same way on two different simulators of "the same" chip?
Short answer: no.
Run 1: Chiplab, Renode 1.16.1 underneath
Board: stm32f4_discovery, the exact machine this firmware targets. I uploaded the
ELF, ran it, and got exactly what the code is supposed to do.
10:10:45.0412 [INFO] machine-0: Machine started.
10:10:45.0499 [WARNING] flash_controller: Unhandled write to offset 0x0. Unhandled bits: [8-10] when writing value 0x700. Tags: PRFTEN (0x1), ICEN (0x1), DCEN (0x1).
10:10:45.0982 [INFO] usart2: [host: 0.2s (+0.2s)|virt: 0s (+0s)] Hello world!
10:10:50.0412 [INFO] machine-0: Machine paused.
Boots. Warns about a flash-controller register it doesn't fully model—the HAL sets PRFTEN, ICEN, and DCEN, prefetch and cache enable bits, during clock setup. Prints. Idles for the rest of its 5-second run budget.
Chiplab runs on Renode under the hood, the same emulator Interrupt has written about
for exactly this kind of firmware-without-hardware workflow.1
Peripheral behavior, including which registers exist and which bits Renode doesn't
know what to do with, comes from .repl platform description files.2 Notice what
it did here: it didn't silently swallow the write, and it didn't crash. It logged a
WARNING naming the exact unhandled bits and kept executing. That's a real design
choice, and for most of what people use Renode for, running firmware far enough to
see the behavior you actually care about, it's the right one.
Run 2: QEMU 11.0.3, and there's no such board
QEMU doesn't ship an stm32f4-discovery machine.3 The closest F4-family machine is netduinoplus2, which models an STM32F405, not the F407 this board and firmware target.4 That's a different peripheral map—exactly the gap covered in what Renode and QEMU each actually model: QEMU hardcodes its boards, Renode lets you edit the platform description. I ran the same ELF on netduinoplus2 anyway.
qemu-system-arm -M netduinoplus2 -kernel hello-stm32f4-discovery -nographic -serial mon:stdio
qemu: fatal: Lockup: can't escalate 3 to HardFault (current priority -1)
R00=00000000 R01=00000000 R02=00000000 R03=00000000
R04=00000000 R05=00000000 R06=00000000 R07=00000000
R08=00000000 R09=00000000 R10=00000000 R11=00000000
R12=00000000 R13=2002ffe0 R14=fffffff9 R15=08001d6c
XPSR=41000003 -Z-- T handler
(Register dump continues with zeroed FPU registers, trimmed here.)
The exact line is Lockup: can't escalate 3 to HardFault, QEMU's way of saying it hit a fault it can't escalate and decided that's fatal. I ran it twice to rule out a fluke. Both runs produced byte-identical output, md5-matched.
That dump does tell us something, even without a debugger attached. R14 is 0xFFFFFFF9, an EXC_RETURN value, which means the core was already inside an exception handler when this happened. The XPSR handler bit is set. R15, the program counter, is 0x08001d6c, which sits inside the firmware's own flash, not some QEMU internal void. So this isn't QEMU choking on a malformed ELF or a broken vector table. It's a fault occurring while a fault is already being handled, with nowhere left to escalate to, during early init.
I didn't bisect this. There's no single-stepped trace pointing at a guilty line. What I have is a register dump consistent with running an F407 binary's early boot sequence—RCC and GPIO and USART setup—on a machine modeling an F405 with a different peripheral map. That's a plausible story, not a proven one.

Renode vs QEMU: what actually diverged
| Chiplab (Renode 1.16.1) | QEMU 11.0.3 | |
|---|---|---|
| Machine for this board | stm32f4_discovery, exact match | No such machine exists; closest is netduinoplus2 (STM32F405, not F407) |
| Boot result | Boots, runs to the time budget | Fatal lockup during early exception handling |
| UART output | Hello world! printed once | None |
| Behavior at an unmodeled register | Logs a WARNING, keeps running | N/A here, but its peripheral models are their own, separate set from Renode's |
| Fault philosophy | Faults aren't escalated on this board at all5 | An unescalatable fault is fatal: halt and dump every register |
| What "success" means | Firmware ran far enough to observe real behavior | Either the firmware runs clean, or the emulator gives up loudly |
Read that fault-philosophy row against the post where I tried to force a HardFault. I planted four different, real bugs on this exact board and firmware, on Chiplab, specifically to trigger a HardFault: a raw write into flash, an invalid branch, an undefined instruction, an unaligned read. Zero HardFaults fired, every time. The chip that would not die. Now here's QEMU, on a different machine modeling a related chip, hitting a fault situation nobody even provoked on purpose, and refusing to execute another instruction.
Same silicon family. Opposite instincts. Renode's stance, on this board, today, is "if I don't understand the fault, I don't stop you." QEMU's stance is "if a fault can't be delivered, that's it, we're done, here's every register." Neither one is the STM32F407 sitting on my desk. Both are telling the truth about what they themselves do at the edge of their own model.

The part that's actually useful
I ran this to see what "the same firmware behaves differently on two simulators" looks like in practice. The answer isn't noise—it's information.
If Renode and QEMU had agreed—boot clean, print the banner, idle—that agreement would have been a weak signal that early init doesn't touch anything either simulator models shakily. Two simulators converging on the easy path doesn't prove the hard path is fine.
The disagreement is worth more. It's a map. It tells me exactly where the two models diverge: Renode doesn't escalate CPU faults on this board, QEMU escalates faults hard and unconditionally, and QEMU doesn't even have the right board to start from. None of that tells me what my actual STM32F407 will do. That's still a job for real hardware, the same case I make in why HIL testing won't scale to the AI era on its own. What the simulators do tell me is which questions I can't trust either one to answer yet, and it's not a coincidence that other embedded engineers have raised exactly this tradeoff when weighing Renode against QEMU for MCU-level work.6
That's the actual lesson: know your STM32 simulator's failure philosophy before you trust its silence or its crash. A green checkmark from a tool that never escalates faults and a red X from a tool that escalates too eagerly on the wrong board are both telling you less about your firmware than they appear to. The two runs disagreeing told me more than either run alone.
Can QEMU emulate an STM32F4 Discovery board?
Not directly. QEMU ships no stm32f4-discovery machine model. The closest is
netduinoplus2, which emulates an STM32F405, a related but different chip from the
STM32F407 this board actually carries. If your firmware only touches USART, GPIO,
and RCC the way this one does, running it on netduinoplus2 might get you close.
It won't get you a faithful STM32 simulator of this exact board, and this post is
what that gap looks like when you actually run the binary.
- Repo & examples: github.com/veecle/chiplab
- Discord: discord.com/invite/F6GwZJ6ktP
- Chiplab: veecle.ai/chiplab
Sources
Footnotes
-
Baldassari, François. "Cortex-M MCU Emulation with Renode." Interrupt by Memfault, March 23, 2020. https://interrupt.memfault.com/blog/intro-to-renode ↩
-
"Platform Description Format." Renode documentation. https://renode.readthedocs.io/en/latest/advanced/platform_description_format.html ↩
-
"QEMU System Emulation for ARM." QEMU documentation. https://www.qemu.org/docs/master/system/target-arm.html ↩
-
"STM32 boards (netduino2, netduinoplus2, olimex-stm32-h405)." QEMU documentation. https://www.qemu.org/docs/master/system/arm/stm32.html ↩
-
"An agent tried to fix a HardFault. There was no HardFault to fix." Veecle. /blog/11-an-agent-fixed-a-hardfault ↩
-
Eplankton. "Idea: Introduce Renode as an alternative to QEMU." vivoblueos/kernel, GitHub issue #13, August 1, 2025. https://github.com/vivoblueos/kernel/issues/13 ↩
