Worked examples — Software-in-the-Loop (SIL) simulation — all software, simulated hardware
Before we start, one promise: every symbol is earned before use. Here are the only ingredients, defined once:
The scenario matrix
Every SIL simulation you will ever run falls into one of these cells. The examples below are labelled with the cell they cover.
| Cell | Case class | What makes it special | Covered by |
|---|---|---|---|
| A | Nominal, (too slow) | The happy path: converge upward | Example 1 |
| B | Sign flip, (too fast) | Command must go negative | Example 2 |
| C | Zero input, | Degenerate: nothing should move | Example 3 |
| D | Degenerate hardware model () | Instant plant — limiting behaviour | Example 4 |
| E | Numerical instability ( too large) | Solver diverges — a simulation bug, not a code bug | Example 5 |
| F | Real-world word problem | Braking-distance stopping question | Example 6 |
| G | Exam-style twist | "SIL passed but hardware failed" — abstraction error | Example 7 |
For the running examples (unless a cell overrides it) we fix: , s, s.
Figure s01 — the closed SIL loop. Sketch of the layout: a lavender rounded box on the left labelled "Software under test — ", and a mint rounded box on the right labelled "Simulated plant — ". A coral arrow points right from software to plant carrying the "command "; a lavender arrow loops back underneath, pointing left from plant to software carrying the "sensed speed ". A small butter-coloured arrow drops into the software box from above, marked " = target enters here". The caption underneath reads: the software never learns the plant is fake — it just reads . As you read each example, trace which arrow the numbers travel along.

Example 1 — Cell A: nominal, the motor is too slow
Step 1 — Compute the error . Why this step? The controller can't act until it knows "how far off". Error is the software's only input.
Step 2 — Controller computes the command .
Why this step? This is literally the line of production code under test: u = K * (v_ref - v). Positive error → positive push. Good.
Step 3 — Plant advances one Euler step. Why this step? This is the simulated hardware reacting. The plant uses the command directly (no extra ) — notice the software never sees this equation, it only sees the resulting next tick. That is the "in-the-loop" feedback.
Answer: rev/s.
Recall Was your forecast right?
It moves up by only , not near 10 ::: Correct — Euler with a small nudges gently; convergence takes many ticks, which is exactly why we simulate rather than eyeball.
Verify: Sign check — motor was too slow (), and increased (). Units check: is in rev/s, is dimensionless (s/s), so the increment is rev/s. ✓
Example 2 — Cell B: the sign flip, motor is too fast
Step 1 — Error is now negative. Why this step? We must show the reader the case where the sign of the input flips — the parent contract demands every sign. Negative error means "too fast".
Step 2 — Command flips sign.
Why this step? A correct proportional controller reverses its push automatically. If your code had a abs() or unsigned type bug, SIL would catch it right here — the sim would keep accelerating instead of braking.
Step 3 — Plant step. Why this step? The negative command drags down. Confirmed motion toward target.
Answer: , rev/s.
Verify: Symmetry sanity check against Example 1 — in Ex 1 error was ; here it's , so the command is exactly negated ( vs ). ✓ And decreased, correct for "too fast". ✓
Example 3 — Cell C: the zero / degenerate input
Step 1 — Error is zero. Why this step? The degenerate input is the one where the two inputs cancel. We must confirm the software forms exactly here — a subtraction bug or floating-point residue would show up as a tiny nonzero error, and this is where we'd catch it.
Step 2 — Command is zero.
Why this step? Zero error should mean zero push. This is the degenerate input every controller must handle. A divide-by-error bug or a NaN would surface here.
Step 3 — Plant step. Why this step? We advance the plant even though the command is zero, precisely to reveal that "no command" does not mean "no motion" — the drag term still acts. This is the whole point of the example, so we must run the step rather than assume nothing happens.
Answer: rev/s (it drifts below target).
Verify: Plug the true equilibrium: for to stay put we need with , i.e. . Solving: . So the loop settles at , not 10 — confirming the offset. ✓
Example 4 — Cell D: degenerate hardware, (instant motor)
Step 1 — Inspect the ratio. As , this ratio . Why this step? The whole update is scaled by . A limiting input must be inspected symbolically, not just numerically.
Step 2 — Restate the command, then take a concrete small . First recompute the controller output exactly as in Example 1, because we start from the same state: Now run one plant step with the shrunken time constant: Why this step? We must show, not assume, where comes from — it is the same controller line from Example 1. A concrete degenerate value then shows the danger before it explodes: one step already jumps all the way to the target and would overshoot the true continuous answer.
Step 3 — Recognise the failure mode. When , one Euler step overshoots the true continuous answer, and when it reaches 2 the simulation sits on the edge of oscillating and blowing up. A "faster" (smaller ) motor needs a smaller to stay accurate.
Answer: As the discrete plant becomes unstable unless shrinks in step; the "instant motor" is a degenerate model SIL cannot faithfully simulate without refining the time step.
Verify: Stability of Euler for requires , i.e. . At : — exactly on the unstable boundary. ✓
Example 5 — Cell E: numerical instability, a simulation bug not a code bug
Step 1 — Compute the growth factor. Why this step? From Example 4 we know means unstable. We're deliberately past the cliff, so we expect the next steps to diverge.
Step 2 — Tick 1. Why this step? We take the very first Euler step with the oversized ratio to see how badly a single update overshoots — already blows past the sensible resting value.
Step 3 — Tick 2. Why this step? We feed the overshot back in. Because it overshot, the correction term flips sign and over-corrects, flinging negative. This shows the divergence is a feedback effect of the bad step size, not a one-off.
Step 4 — Tick 3. Why this step? Repeating once more shows the swing is growing (), not settling. Three ticks is the minimum needed to prove the amplitude is increasing rather than a lucky bounce.
Figure s02 — stable vs unstable Euler. Sketch of the layout: one set of axes, horizontal axis "tick number ", vertical axis "simulated speed (rev/s)". A mint curve with round markers climbs smoothly and levels off near 5 — this is the safe run (, ratio ). A coral curve with square markers zig-zags violently: its points sit at (tick 1), then (tick 2), then (tick 3), each swing bigger than the last — this is the divergent run (, ratio ) we just computed by hand. A legend distinguishes "stable" (mint) from "diverges" (coral). Watch the coral markers land exactly on our three computed numbers.

Answer: — divergent oscillation caused by the solver, not the SUT.
Verify: The three values above, and predicting instability. ✓
Example 6 — Cell F: the real-world word problem
Step 1 — Recall the stopping-distance formula. Why this step? We use because it answers exactly "how far to reach zero speed under constant deceleration" — derived from energy: the car must shed kinetic-energy-per-mass at rate per metre.
Step 2 — Plug in (units: m/s and m/s²). Why this step? Substituting the actual numbers turns the abstract formula into the concrete distance we can compare against the gap. Units: . ✓
Step 3 — Compare to the gap. m but obstacle is at m. Margin m. Why this step? A margin is only meaningful as (available gap) minus (needed distance); a negative result is the unambiguous "collision" signal.
Answer: No — the car needs 75 m but has only 50 m; it fails by 25 m. The controller must brake earlier or harder.
Verify: Solve for the maximum safe speed at 50 m: m/s. Since , collision confirmed. ✓
Example 7 — Cell G: the exam twist ("SIL passed, hardware failed")
Step 1 — SIL command (fresh data ). Why this step? SIL modelled zero delay, so it uses the current true speed — this is the "ideal" command the code was verified against.
Step 2 — Hardware command (stale data ). Why this step? The real ADC hands over 2-tick-old data. The same code line runs, but its input is the stale , so we recompute with instead of to see what the hardware truly does.
Step 3 — Compare the two commands. Why this step? Subtracting isolates the pure effect of the delay — one full unit of extra, unwanted push — which is the number that quantifies the abstraction error.
Answer: SIL says ; real hardware says . The extra unit is the invisible timing bug SIL missed.
Verify: Difference , and hardware pushes twice as hard as SIL predicted. ✓
Wrap-up: the matrix, filled
Recall Which example covered each cell?
A→Ex1, B→Ex2, C→Ex3, D→Ex4, E→Ex5, F→Ex6, G→Ex7 ::: Every cell of the scenario matrix now has a fully worked, verified example.
Return to the parent topic.