Chapter 7: Deterministic Co-Simulation with VirtMCU
If you have ever tried to test a drone flight controller by compiling the firmware and running it inside a standard QEMU emulator on your Ubuntu or Windows laptop, you have probably noticed something infuriating: occasionally, for absolutely no reason, your simulated drone just falls out of the sky.
You spend hours digging through your C code, checking your PID math and your RTOS task priorities. Everything looks perfect. So why did it crash? It crashed because you trusted the clock on your desktop computer.
In this chapter, we are going to learn how to tear the concept of “time” away from the host operating system and hand it over to a deterministic physics engine using VirtMCU.
7.1 The Wall-Clock Jitter Problem
To understand why standard emulators fail at hard real-time validation, we have to look at how they track time.
Standard QEMU is designed to emulate servers and desktop PCs. To do this, it relies heavily on the host operating system’s wall-clock time to drive the virtual hardware timers inside the guest machine. If you configure a virtual timer to fire an interrupt every 1 millisecond, QEMU asks the Linux or Windows kernel to wake it up in 1 millisecond so it can inject that interrupt into your firmware.
But as we discussed in Chapter 5, standard desktop operating systems are not real-time systems. They are optimized for average throughput, not deterministic latency. While your QEMU process is running, the Linux scheduler might decide to preempt it to run a garbage collection routine in a background web browser, or pause it to handle a massive burst of incoming Wi-Fi packets.
This introduces scheduling jitter. Your firmware’s 1-millisecond timer interrupt might actually fire after 1.2 milliseconds, or 5 milliseconds.
In a desktop application, a 5-millisecond delay is completely invisible. But in a Cyber-Physical System (CPS), it is catastrophic. If your firmware on MCU-A reads a sensor and sends a CAN frame to MCU-B, the delivery time in your simulation must be a function of strict virtual time, not host wall-clock scheduling jitter. If the physics engine (simulating gravity and aerodynamics) expects a motor actuation command exactly every 1,000 microseconds, and your QEMU process gets paused by the host OS for 5,000 microseconds, the physics engine assumes the motors have stalled. The drone drops like a rock.
WARNING: Standard Emulation is Useless for Control Loops You cannot validate a hard real-time physical control loop using an emulator that free-runs on wall-clock time. If the time it takes your firmware to respond to a simulated sensor depends on how many Chrome tabs you have open on your host machine, your test results are not reproducible.
To build a true “digital twin” of a physical system, we need to completely isolate the simulation from the chaotic reality of the host machine’s processor.
7.2 Virtual Time in VirtMCU
To fix the wall-clock jitter problem, we have to look at how hardware engineers have been simulating silicon for decades: Discrete-Event (DE) Simulation.
As detailed by H. Patel in SystemC Kernel Extensions and D. Grobe in Quality-Driven SystemC Design, discrete-event simulators (like the SystemC kernel) do not use a real-time wall clock. Instead, they use an Evaluate-Update paradigm driven by an event queue. In a DE simulation, “time” is just an integer variable. The simulator processes every event scheduled for the current microsecond, and only when there is absolutely no more work left to do does it advance the simulation time to the next pending event in the queue. If evaluating a complex event takes 10 seconds of real-world processing power, the virtual clock remains completely frozen.
The VirtMCU FirmwareStudio brings this exact concept into the QEMU emulation layer.
VirtMCU dictates that QEMU’s virtual clock must never free-run. Instead, it uses cooperative time slaving to lock-step the execution of your firmware with an external continuous-time physics engine, such as MuJoCo. The physics engine acts as the supreme Time Authority.
In this architecture, QEMU is entirely blocked at the boundary of a time quantum. It waits for the physics engine to calculate the physical state of the world (e.g., updating the angle of an inverted pendulum) and explicitly grant a time quantum. Only then does QEMU allow your firmware to execute instructions, ensuring that the firmware never runs ahead of or behind the simulated physical world.
The Clock Modes: slaved-suspend vs. slaved-icount
VirtMCU’s clock backbone (implemented natively as a QOM plugin in hw/rust/backbone/clock) provides two primary cooperative time slaving modes to balance performance and strict determinism:
1. The slaved-suspend Mode
This is the default mode used for most FirmwareStudio testing. In slaved-suspend, QEMU executes instructions at the full speed of the host CPU (using dynamic translation) until it consumes the granted time quantum, at which point it suspends itself and waits for the next grant. Because it leverages QEMU’s highly optimized execution loop, it delivers about 95% of native simulation throughput. This is excellent for testing high-level RTOS logic and distributed networking.
2. The slaved-icount Mode
When you need absolute, cycle-accurate determinism, you drop into slaved-icount mode. In this mode, VirtMCU translates the exact number of executed machine instructions directly into nanoseconds of virtual time.
If you configure your virtual ARM Cortex-M processor to run at 100 MHz, the slaved-icount mode mathematically guarantees that exactly 100 instructions will advance the virtual clock by exactly 1 microsecond. If your firmware is doing something highly sensitive to sub-quantum intervals—such as measuring the exact pulse width of a PWM signal, implementing bit-banged I/O, or managing microsecond-precision DMA transfers—slaved-icount provides exact nanosecond virtual time.
TIP: Determinism by Construction In VirtMCU, if your firmware sends a network packet to another node, that packet is stamped with a precise virtual arrival time. Even if the host OS scheduler pauses one QEMU instance for a full second, the packet is buffered and injected into the destination’s virtual NIC only when the destination’s virtual time matches the stamped arrival time. By treating time as a strict logical construct, your simulation becomes 100% deterministic, reproducible, and immune to host jitter.