04 · Functional Safety Testing (ISO 26262)¶
Everything so far has tested whether an ECU does the right thing. ISO 26262 asks a different, harder question: what happens when something goes wrong — a sensor fails, a microcontroller bit-flips, a communication link drops — and does the system fail into a safe state rather than a dangerous one? This module covers the ASIL model, the safety lifecycle's testing implications, and how fault-injection style thinking connects to the CAPL patterns already built.
About this module
No certified safety-case tooling or physical fault-injection rig was used to produce this content. The ASIL/safety-lifecycle model and CAPL patterns below reflect the documented ISO 26262 standard and common practice — a real safety case requires a qualified safety engineer and traceable evidence, which this module does not substitute for.
ASIL: risk-graded requirements, not a pass/fail badge¶
ISO 26262 assigns an Automotive Safety Integrity Level (QM, A, B, C, D — D is the strictest) to each hazard based on three factors:
| Factor | Question |
|---|---|
| Severity (S0–S3) | How bad is the injury if the hazard occurs? |
| Exposure (E0–E4) | How often is the vehicle in the situation where this hazard could occur? |
| Controllability (C0–C3) | Can the driver/system reasonably avoid harm once the hazard starts? |
A steering-assist ECU losing torque request capability at highway speed might be ASIL D (high severity, common exposure, low controllability); a courtesy-light ECU failing might be QM (no safety relevance at all — regular quality management is sufficient). The ASIL doesn't attach to the whole ECU — it attaches to specific safety goals, and different functions on the same ECU can carry different ASILs.
Testing implication: the rigor, independence, and coverage expected of a test scales with the ASIL of the safety goal under test. An ASIL D safety mechanism needs traceable, reviewed, often independently-verified test evidence; a QM function can be tested with the lighter-weight practices from earlier modules.
Safety mechanisms: what you're actually testing¶
A safety goal is met through safety mechanisms — specific, identifiable pieces of design meant to detect a fault and force a safe reaction. Common patterns:
| Mechanism | Detects | Safe reaction |
|---|---|---|
| Range check | Sensor value outside physically plausible bounds | Ignore reading, fall back to default/degraded mode |
| Plausibility/cross-check | Two independent sensors disagree beyond tolerance | Flag both suspect, degrade |
| Watchdog timeout | Software task hangs or stops running | Force ECU reset |
| E2E (end-to-end) protection | CAN/CAN-FD message corrupted, stale, or out of sequence | Reject message, treat signal as invalid |
| Diagnostic coverage via redundant computation | A calculation's result diverges from a redundant/simplified check | Trip a fault, transition to safe state |
Functional safety testing is, concretely, testing that these mechanisms fire correctly when the fault they're designed for actually happens — not testing the nominal function again.
CAPL-driven fault injection for a safety mechanism¶
// Illustrative — CAPL fault-injection idioms for a plausibility check.
// The two "independent" sensor signals are simulated on the bus;
// real systems may also require electrical-level fault injection
// (open/short) that CAPL alone cannot produce — see Module 6.
testcase tc_WheelSpeedPlausibilityFaultDetected()
{
testCaseTitle("Diverging wheel-speed sensors trigger plausibility fault within spec");
dword t0, t1;
setSignal(WheelSpeedFL, 60.0);
setSignal(WheelSpeedFR, 60.0);
testWaitForTimeout(200); // let the ECU settle on plausible baseline
t0 = timeNowNS() / 1000000;
setSignal(WheelSpeedFR, 10.0); // inject an implausible divergence
testWaitForSignal(PlausibilityFault_WheelSpeed, 1, 500);
t1 = timeNowNS() / 1000000;
testStepCheck("fault flagged within 500ms of divergence", (t1 - t0) <= 500);
testStepCheck("ECU entered degraded mode", getSignal(SteeringAssistMode) == DEGRADED);
}
testcase tc_PlausibilityFaultClearsOnRecovery()
{
testCaseTitle("Fault clears once sensors agree again, per the safety concept's recovery rule");
setSignal(WheelSpeedFL, 60.0);
setSignal(WheelSpeedFR, 10.0);
testWaitForSignal(PlausibilityFault_WheelSpeed, 1, 500);
setSignal(WheelSpeedFR, 60.0); // sensors agree again
testWaitForSignal(PlausibilityFault_WheelSpeed, 0, 1000);
testStepCheck("fault cleared per recovery timing spec", 1 == 1);
}
The second testcase matters as much as the first: a safety mechanism that never clears a fault (or clears it too eagerly, before the fault condition is genuinely gone) is its own defect, and the recovery behavior is usually spelled out explicitly in the safety concept document, not left to inference.
Diagnostic coverage and single-point vs. latent faults¶
ISO 26262 distinguishes:
| Fault class | Meaning | Test relevance |
|---|---|---|
| Single-point fault | A fault with no independent safety mechanism protecting against it — directly violates the safety goal | Highest testing priority; the mechanism protecting it (if any) must be proven to work |
| Multi-point fault | Requires a second, independent fault to become dangerous | Test the combination, not just each fault alone, where feasible |
| Latent fault | A fault that doesn't manifest immediately but defeats a safety mechanism silently (e.g., the plausibility checker's own comparator is broken) | Needs periodic self-test evidence, not just reactive fault injection |
A latent-fault example: if the plausibility checker in the example
above has a bug that always reports "plausible" regardless of input,
tc_WheelSpeedPlausibilityFaultDetected catches it directly — but a
checker that usually works and fails only under a specific timing
race needs a dedicated latent-fault test, often a self-test or built-in
diagnostic the ECU runs on its own schedule.
Traceability: why every safety test needs a requirement ID¶
Unlike a Level 1 exploratory test, a functional-safety test is worthless as safety evidence unless it traces to a specific safety requirement, and that requirement traces to a specific safety goal. Module 8 covers traceability tooling in depth; the safety-specific addition is that the trace must also record which ASIL the requirement carries, since audit and assessment activities scale with ASIL.
Cheat sheet¶
| Concept | Key point |
|---|---|
| ASIL | Attaches to a safety goal (via S/E/C), not to a whole ECU; drives required test rigor |
| Safety mechanism | The specific design element that detects a fault and forces a safe reaction — the actual test target |
| Recovery testing | Test fault-clear behavior explicitly, not just fault-detect |
| Single-point vs. latent fault | Latent faults need periodic self-test evidence, not just one-shot fault injection |
| Traceability | Every safety test needs a requirement ID and its ASIL recorded |
How It Actually Works: the FMEDA math a test's pass/fail actually feeds¶
ISO 26262's ASIL-specific rigor isn't just a documentation requirement — it traces to actual numeric metrics computed in a FMEDA (Failure Modes, Effects, and Diagnostic Analysis) that a safety case must satisfy with concrete thresholds per ASIL, and every fault-injection test in this module is generating one of the inputs to that calculation.
The core metric relevant here is diagnostic coverage (DC) for a given failure mode:
DC = (failure rate of faults detected by a safety mechanism)
/ (total failure rate of that failure mode)
When tc_WheelSpeedPlausibilityFaultDetected proves the plausibility
checker fires within 500 ms for a wheel-speed divergence, it is
supplying empirical evidence toward the numerator of this ratio for
the "wheel speed sensor gives an implausible reading" failure mode — the
safety case cites this kind of test result (or a statistical sample of
many injected fault variants, not just one) as justification for the
DC percentage claimed for that mechanism. ISO 26262 sets minimum DC
targets per ASIL for the Single-Point Fault Metric (SPFM) and
Latent Fault Metric (LFM) — roughly, ASIL D targets require ≥99%
coverage of single-point faults and ≥90% of latent faults, ASIL C/B
progressively less — which is the concrete, numeric reason ASIL D
testing must be far more exhaustive across fault variants (not just
one clean divergence value, but boundary-adjacent divergences, slowly
drifting divergences, intermittent ones) than a QM-level function ever
needs: each additional fault variant tested (or formally analyzed, when
exhaustive testing is impractical) either supports or undermines the DC
percentage the safety case is claiming.
This also explains precisely why the latent-fault row in this module's
table is qualitatively different work: a latent fault, by definition,
doesn't get detected by the mechanism under normal operation — proving
its coverage requires either a dedicated self-test the ECU runs
periodically (whose own execution needs to be tested independently,
since a self-test that silently stops running is itself a latent fault
one level up) or an argued analysis rather than a single fault-injection
testcase, which is why "test the checker's own comparator" can't be
satisfied the same way tc_WheelSpeedPlausibilityFaultDetected satisfies
single-point coverage — there's no external signal to watch that proves
the checker itself is still alive without instrumenting the checker's
self-test path directly.
Exercise¶
- Classify a brake-pedal-position sensor failing to a fixed mid-travel value (rather than an obviously out-of-range value) as a single-point, multi-point, or latent fault risk, and explain what safety mechanism would need to exist to catch it.
tc_PlausibilityFaultClearsOnRecoveryabove has a weak assertion (1 == 1) after thetestWaitForSignalcall. Rewrite it with a meaningful check, and explain why relying solely ontestWaitForSignal's built-in timeout-as-pass/fail is a common but fragile pattern.- Pick a safety mechanism from the table above other than plausibility checking, and write one testcase (detection) and one testcase (recovery) for it, stating what ASIL you'd assume and why that changes how rigorously you'd document the result.