Visual walkthrough — Software-in-the-Loop (SIL) simulation — all software, simulated hardware
The parent note gave you the big picture of SIL: run your real code against a fake world made of maths. This page slows all the way down and builds the loop from nothing, one arrow at a time, until we can prove the single most important formula on the parent page — the fidelity criterion that tells you when the simulation's prediction can be trusted.
We start from absolutely zero. If you have never seen a feedback loop, a time step, or the word "solver", you are in the right place.
Step 1 — What is a "loop" at all?
WHAT. A loop here means a circle of cause and effect that keeps going round: the code looks at the world, decides something, changes the world, and then looks again. Round and round.
WHY. Every controller — a thermostat, a car's brakes, a drone — works this way. It cannot decide once and walk away; the world keeps moving, so the code must keep re-checking. We call this a closed loop: the output feeds back to become the next input.
PICTURE. Two boxes and two arrows forming a circle.
The two words you must own before anything else:
Controller
Plant
Step 2 — Cut the loop where the wires are
WHAT. In a real system, the two boxes are joined by physical wires: a sensor wire carries a measured number into the code, an actuator wire carries a command out. We give those two crossing points a name.
WHY. SIL's whole trick lives exactly at these two crossing points. If we know precisely where code touches hardware, we know precisely where to insert the fake. Every symbol below is one of these crossings.
PICTURE. The same circle, now with the two wires labelled and a scissors line marking where we will cut.
The layer of code that owns those two pins has a name — the Hardware Abstraction Layer (HAL). Think of it as a pair of doors: read_sensor() gives you , write_actuator(u) pushes out . Hold onto this; it is what we swap in Step 3.
Step 3 — Replace the world with maths (this is SIL)
WHAT. We keep the controller box exactly as it is and rip out the physical plant box. In its place we drop a plant model: a little bundle of equations that, given the current state and the command , produces the next state and the next reading .
WHY. This is the entire idea of SIL. The controller still calls read_sensor() and write_actuator() — the same doors — but behind those doors is now a Python/MATLAB routine instead of copper. Crucially, the controller cannot tell the difference: it hands over , it gets back , and it has no idea a real motor never moved.
PICTURE. Same loop, plant box now redrawn as a "maths cloud"; the HAL doors are the seam between real code (left) and simulation (right).
Reading the symbols right where they sit:
- — everything you must know to continue the story (speed, distance, temperature…). The subscript means "at step number ".
- — the command your controller produced at step .
- — the physics, written as arithmetic.
Step 4 — Time is not continuous inside a computer
WHAT. The real plant moves smoothly through time. Your computer cannot; it can only compute at instants. So we chop time into equal slices of width and only ever ask "what is the state at slice ?"
WHY. The controller already lives on a clock: it wakes up at a fixed rate (say 100 times a second) and does one calculation each time. We call the gap between wake-ups the sample period . To keep the plant in step with the controller, we make the plant advance by the same slice each round.
PICTURE. A smooth curve of true speed vs time, with the computer's samples shown as dots landing on a staircase — the continuous world seen through a stroboscope.
Step 5 — The cheapest way to step physics forward: Euler
WHAT. We need to turn the physicist's rate law ("state changes at rate ") into a rule the computer can run. The simplest: assume the rate stays constant across one slice, and just multiply.
WHY. Multiplication is cheap and instant. This is the forward Euler method. It answers the question "given how fast things are changing right now, where will we be one slice later?" — the crudest honest answer possible.
PICTURE. Zoom into one slice: the true curve bends, but Euler walks in a straight line along the starting slope. The gap between the straight step and the true curve is the discretisation error.
- — the rate of change at the start of the slice (from the physics). This is a derivative, evaluated once.
- — walk along that slope for one slice's worth of time.
- The result is only approximately right, because the true slope changed during the slice while we pretended it was frozen.
Step 6 — One full tick, symbol by symbol
WHAT. Now assemble Steps 3–5 into a single turn of the loop and watch the numbers move, using the parent note's braking example: car at km/h, obstacle at m.
WHY. Seeing one tick end-to-end proves the pieces fit: measurement in → command out → state advanced → new measurement. This is the SIL engine running.
PICTURE. The loop annotated with real numbers on every arrow for the very first tick.
One tick, s, using a simple plant and :
Let (a firm brake). Then:
- Plant → Software: m/s ( km/h), m.
- Software computes command (80 % pressure).
- Software → Plant: apply .
- Plant advances: m/s; m.
The controller now reads m/s and m — and the loop turns again. Every arrow you saw is a HAL call; the data flow is identical to production even though no wheel turned. (Test the decision logic of calc_brake_force itself with Unit Testing for Embedded Systems.)
Step 7 — The edge cases: where the loop misbehaves
WHAT. A derivation is only honest if it survives the extremes. Three degenerate inputs, three outcomes.
WHY. SIL's value is that these extremes are free to test. But each also exposes a limit of SIL. Look at all three.
PICTURE. Three mini-panels: (a) too large → numbers explode; (b) command saturates → state hits a wall; (c) a single freak sensor spike the model never produced.
- Case A — too large (instability). If a dynamic has time constant and you pick , Euler overshoots, then over-corrects harder, and swings to . The plant model is fine; your solver is the villain. Fix: shrink or use RK4 (a smarter 4-slope step).
- Case B — saturation (a healthy edge case). Command but a real actuator maxes out; the model clamps
if v < 0: v = 0(car can't reverse from braking). This is a good thing to simulate — it catches integrator windup in controllers. - Case C — the freak reading (SIL's blind spot). Real sensors sometimes emit a garbage value from electrical interference. If your model only ever produces smooth , SIL will never generate that spike — so a crash-on-bad-reading bug stays invisible. This is exactly the gap that Hardware-in-the-Loop (HIL) and real Sensor Fusion Algorithms must close.
Step 8 — Stack the three errors: the fidelity criterion
WHAT. We now prove the parent note's central formula. Every prediction SIL makes carries three separate, additive sources of error. Add them and you get the total gap between "SIL says" and "reality does".
WHY. This single inequality tells you when to trust SIL and when to move on to PIL/HIL. It is the payoff of the whole page.
PICTURE. Three stacked coloured bars — model, discretisation, abstraction — summing to a total error bar, with a dashed "acceptable" threshold line.
The one-picture summary
The whole page in one frame: your real controller (untouched) talks through the HAL doors to a plant model that steps forward one at a time via Euler, closing the loop — while three error bars quietly accumulate underneath, deciding whether the prediction can be trusted.
Recall Feynman retelling — say it back in plain words
I want to test my controller code without buying hardware. A controller just eats a measured number and spits out a command, forever, in a circle — that's a closed loop. The only places the code touches the world are two doors: one that gives it a reading, one that takes its command (the HAL). So I keep my code exactly as-is and put a bunch of equations behind those doors instead of real wires. The code can't tell the difference.
But a computer can't flow through time smoothly, so I chop time into equal slices . To move the physics forward one slice I use the crudest trick: look at how fast things are changing right now, assume that rate holds for the whole slice, and multiply — that's Euler. It's a little wrong every step, about wrong, and if I make too big the numbers explode. I run one tick — reading in, command out, state advanced — and repeat.
Then I check the extremes: too-big blows up (solver's fault), hitting a limit like a clamped brake is a good test, but a random garbage sensor spike is something my smooth equations will never produce — so SIL is blind to it. Adding up all my wrongness gives three bars: my equations are imperfect, my time-stepping is imperfect, and I ignored real-world chaos. If the last bar is big, SIL has done all it can, and it's time for real silicon and real I/O. SIL proves my logic is right — not that it survives reality.