Functional Safety for Embedded Firmware: What IEC 61508 and ISO 26262 Actually Require from Your Code
Introduction
Neither IEC 61508 nor ISO 26262 contains a clause that says "write your code this way." Both express requirements indirectly: you classify a function's required integrity (a Safety Integrity Level in IEC 61508, an Automotive Safety Integrity Level in ISO 26262), and that classification selects entries from technique tables, sets diagnostic coverage targets, and fixes the verification rigor you must demonstrate. The engineering task is translation — converting "this function is ASIL D" into specific decisions about memory allocation, control-flow checking, self-test coverage, and the kind of structural testing you must prove you performed.
This post follows that translation chain. It assumes you already write embedded C and treats the standards as a source of concrete constraints on implementation and verification, not as a process-management topic.
The Two Standards and How They Relate
IEC 61508 is the generic, industry-independent functional safety standard for electrical/electronic/programmable electronic (E/E/PE) systems. ISO 26262 is its automotive derivative, adapted for road vehicles. Where IEC 61508 uses SIL 1–4 (4 being most demanding), ISO 26262 uses ASIL A–D (D being most demanding), plus QM ("Quality Management") for functions with no safety relevance.
The classifications are derived differently. A SIL is typically assigned from a target failure-rate band (e.g., for high-demand mode, SIL 3 corresponds to a probability of dangerous failure per hour between 10⁻⁸ and 10⁻⁷). An ASIL is derived from three factors evaluated per hazard: Severity (S0–S3), Exposure (E0–E4), and Controllability (C0–C3). The result drives every downstream requirement, including the ones that reach your source files.
For firmware specifically, the relevant parts are IEC 61508-3 (software requirements) and ISO 26262-6 (product development at the software level), supported by ISO 26262-8 (supporting processes, including tool qualification).
From Integrity Level to Code Requirements
The mechanism that binds your implementation is the recommendation table. Both standards present techniques in tables annotated with a recommendation strength per integrity level: typically "highly recommended" (HR), "recommended" (R), and "not recommended" (NR), and in some tables "++/+/o/-" gradations.
The practical reading is:
- Highly recommended at your level means you either apply the technique or formally justify and document its omission. Omitting an HR technique without a rationale is an audit finding.
- Not recommended means you must avoid the construct or argue, with evidence, why it is acceptable in your specific case.
These recommendations are what make the standards reach into code. "Use of language subsets" is HR at higher levels; "dynamic objects and variables without online checking" trends toward NR; "structured programming" and "defensive programming" are HR. None of these is phrased as code, but each one constrains what your .c files may contain.
Language and Coding-Standard Constraints
The most direct constraint is the mandated language subset. For C, this is almost always MISRA C (MISRA C:2012 and its amendments). The standards do not mandate MISRA by name in every clause, but MISRA is the recognized means of satisfying the "language subset" and "coding standard" requirements, and ISO 26262-6 explicitly references coding guidelines covering enforcement of low complexity, restricted use of pointers, and avoidance of constructs with undefined or implementation-defined behavior.
Constructs that are typically prohibited or tightly restricted in safety firmware:
- Dynamic memory allocation after initialization —
malloc/freeintroduce nondeterministic timing and fragmentation, and failure paths are hard to verify. Most safety code allocates statically. - Recursion — unbounded stack growth defeats worst-case stack analysis.
- Unbounded loops — every loop should have a provable upper iteration bound.
- Implementation-defined and undefined behavior — signed overflow, reliance on evaluation order, type-punning through unions in non-conforming ways.
A representative pattern: a deviation from a MISRA rule is permitted only when documented as a formal deviation with rationale and review.
/* MISRA C:2012 Rule 10.3 — no assignment to a narrower essential type.
* Here a deliberate, reviewed narrowing is annotated as a deviation. */
uint16_t adc_raw = read_adc(); /* 12-bit result in 16-bit container */
/* cppcheck-suppress misra-c2012-10.3 ; reviewed: value range proven <= 4095 */
uint8_t scaled = (uint8_t)(adc_raw >> 4); /* documented narrowing, range-checked upstream */
The point is not the suppression comment itself but the discipline it records: deviations exist, but each is individually justified and traceable.
Architecture-Level Demands on Code
Above the statement level, the standards require architectural properties that you implement in code and configuration.
Freedom from interference (ISO 26262 term) requires that elements of lower integrity cannot corrupt elements of higher integrity. In practice this means memory partitioning — using the MPU to isolate a safety task's memory from QM-level code — plus protection of timing and execution resources. A single linked binary mixing ASIL D and QM code without partitioning does not provide freedom from interference by assertion; you must show the mechanism.
Program-flow / control-flow monitoring detects corrupted execution order, such as a skipped safety check or a jump into the middle of a function. A common implementation accumulates a signature across checkpoints and verifies it at a sink:
/* Program-flow monitor: each checkpoint XORs in a unique compile-time token.
* A skipped or out-of-order checkpoint produces a mismatched final signature. */
static uint32_t cf_signature;
#define CF_CHECKPOINT(token) (cf_signature ^= (token))
void safety_cycle(void)
{
cf_signature = CF_INIT;
CF_CHECKPOINT(CF_TOK_ACQUIRE); acquire_inputs();
CF_CHECKPOINT(CF_TOK_EVALUATE); evaluate_limits();
CF_CHECKPOINT(CF_TOK_ACTUATE); drive_outputs();
if (cf_signature != CF_EXPECTED) /* token set proves the path executed in order */
enter_safe_state();
}
Windowed watchdog monitoring complements this temporally: the watchdog must be serviced not merely before a timeout but within an open window, so that a task running too fast (e.g., stuck in a tight loop) is also detected, not only one running too slowly.
Runtime Diagnostics and Self-Tests
The standards require online detection of hardware faults, and the firmware carries most of these tests. Their effectiveness is quantified by diagnostic coverage (DC) — the fraction of dangerous faults a diagnostic detects. DC feeds directly into the failure-rate calculation that justifies the claimed SIL/ASIL, so the choice of self-test is not cosmetic; it changes the safety case arithmetic.
Typical firmware-resident diagnostics:
- CPU core self-test — exercising registers, ALU, and the instruction set against known results, often using a vendor-supplied certified library (a Software Test Library).
- RAM tests — march algorithms (e.g., March C-) run at startup over the full array and periodically over partitions during runtime, detecting stuck-at and coupling faults.
- Flash/ROM integrity — CRC or signature over program memory, checked at startup and incrementally at runtime.
- Periodic plausibility checks on sensor inputs against physical models or redundant channels.
The startup-versus-runtime split matters: full-array tests are usually feasible only at boot, so runtime tests operate on partitions to bound execution time and preserve the cyclic deadline.
Verification the Standards Mandate
Verification rigor scales with integrity level, and the requirements are specific.
Structural coverage is graduated. ISO 26262-6 recommends statement coverage at lower ASILs, branch coverage as the level rises, and Modified Condition/Decision Coverage (MC/DC) as highly recommended at ASIL D. IEC 61508-3 follows the same escalation. MC/DC requires showing that each condition in a decision independently affects the outcome — a far stronger obligation than branch coverage and one that shapes how you write Boolean logic.
Static analysis is highly recommended at higher levels: MISRA enforcement, plus detection of runtime errors (division by zero, out-of-bounds access, overflow) through tools that perform sound analysis.
Requirements traceability must be bidirectional — every safety requirement maps to code and to the tests that verify it, and no safety-relevant code exists without a parent requirement.
Tool qualification is its own obligation. Under ISO 26262-8 you assess each tool's Tool Confidence Level (TCL) from its potential to introduce or fail to detect errors (Tool Impact) and the likelihood of catching such errors (Tool error Detection). A compiler or code generator that can introduce undetected faults requires qualification evidence; an unqualified tool in the chain weakens the entire argument.
SIL vs ASIL: Verification Rigor at a Glance
| Aspect | IEC 61508 (SIL) | ISO 26262 (ASIL) |
|---|---|---|
| Scale | SIL 1 → SIL 4 | ASIL A → ASIL D (plus QM) |
| Derivation | Target failure-rate band | Severity × Exposure × Controllability |
| Language subset | HR at higher SIL | Coding guidelines required, escalating with ASIL |
| Structural coverage | Statement → branch → MC/DC | Statement (A/B) → branch (C) → MC/DC (D, HR) |
| Dynamic memory | Online checking required / avoided | Restricted; avoidance typical |
| Tool qualification | Tool suitability assessment | TCL via Tool Impact + Detection |
| Primary software part | IEC 61508-3 | ISO 26262-6 (with -8 support) |
Conclusion
Both standards constrain firmware through an indirect but traceable chain: an integrity level selects techniques, sets diagnostic-coverage targets, and fixes verification rigor. The obligations that reach your source files are concrete — a MISRA-conformant language subset with documented deviations, static allocation and bounded loops, MPU-based partitioning for freedom from interference, control-flow and windowed-watchdog monitoring, runtime self-tests sized to a measurable DC, and structural coverage escalating to MC/DC at the top levels — all backed by bidirectional traceability and qualified tools.
This rigor is appropriate when a function's failure can cause harm at the severity the classification implies, and the cost is justified by that risk. It is misapplied in two common ways: teams treat an entire codebase as ASIL D when partitioning would confine the safety logic to a small, rigorously verified core, inflating cost without adding safety; or they defer safety work to late verification, where retrofitting MC/DC-amenable structure, partitioning, and self-tests onto existing code is far more expensive than designing for them. The integrity level should be established per function early, the architecture should isolate the safety-relevant portion, and the constraints above should shape the code as it is written rather than be audited onto it afterward.