14.3 Interrupt-Driven Software and Verification
Integrated hardware produces events whether software is ready or not. Interrupt-driven firmware must finish urgent work before the next event, preserve shared state, and leave long processing for foreground code. Verification then supplies evidence that these properties hold under ordinary and faulty conditions.
Keep handlers bounded and shared buffers consistent
The capstone uses a ring buffer: a fixed array with a write index called head and a read index called tail. DMA or an ISR advances head when a sample arrives; foreground code advances tail after consuming a sample. When an index reaches the array end, it wraps to zero.
The buffer is empty when head==tail. One safe convention declares it full when advancing head would equal tail, deliberately leaving one slot unused. An invariant is a rule that must remain true in every reachable state; examples are “indices always remain inside the array” and “the producer never overwrites unread data.”
An ISR's worst-case execution time matters more than its average. If samples arrive every 1000 cycles but the handler can consume 1200 cycles, backlog grows even when individual tests seem correct. Priority does not create CPU capacity. A higher-priority UART ISR can also delay the sample ISR, so worst-case response time includes blocking and higher-priority interference.
Shared multi-byte fields can be inconsistent if an interrupt occurs between foreground reads. Software can briefly mask interrupts around a tiny critical section, use an atomic instruction supported by the processor, or adopt a producer/consumer protocol whose updates are naturally atomic. The scheduler lab makes overload, priority interference, buffer fill, and dropped events visible.
Verify requirements with traces, boundaries, and injected faults
Testing demonstrates behavior for selected cases. Verification connects each requirement to evidence and includes boundary conditions where defects hide. A traceability matrix maps requirement IDs to tests and results so an apparently complete test suite cannot silently omit a requirement.
Useful test classes include:
- Nominal tests: ordinary sampling, commands, and fan updates.
- Boundary tests: empty/full buffer, final address, maximum ADC code, simultaneous events, and exact deadline.
- Fault-injection tests: stuck
READY, parity error, missing EOI, masked DMA channel, overlapping decoder, and sensor clipping.
- Long-run tests: counter rollover, repeated auto-initialization, and slowly accumulating backlog.
A passing final output is insufficient evidence. If a buffer briefly overflowed and later recovered, the final fan voltage may look correct while data was lost. Verification observes intermediate state: request time, ISR entry, bus ownership, head/tail transitions, error flags, and output update time.
The verification console lets a learner choose requirements, scenarios, and injected faults, then shows which assertions detect them and which coverage remains missing. A deliberate fault that no test detects is a verification failure even before a product failure occurs.
Section 14.3 turned timing and state claims into evidence. Section 14.4 maps the same durable ideas onto modern x86, ARM, and systems-on-chip, where integration is denser but ownership and visibility remain essential.