M4 — N=3 stages, FPU optional (only on the "M4F" variant), and if present it is single-precision only.
M7 — N=6 stages (dual-issue), FPU optional, and can be single + double precision.
Why "by default" matters: even on an M4F/M7 the FPU is off at reset — you must set the CP10/CP11 bits in CPACR first (see Exercise 5.2).
Recall Solution 1.2
The NVIC (Nested Vectored Interrupt Controller). On an interrupt it automatically:
Pushes 8 registers (R0–R3, R12, LR, PC, xPSR) onto the current stack, and
Looks up the handler address in the vector table and jumps straight to it.
Both happen in a fixed, known number of cycles — that fixed-ness (not zero, but knowable) is what makes timing deterministic. See Interrupts and the NVIC.
What/why: use the pipeline-fill formula because we count cycles for a straight run of instructions.
Cycles=(N−1)+I=(2−1)+50=51.
The pipe costs 1 extra cycle to fill (only 1 stage beyond the first), then 50 instructions stream out one per cycle.
Recall Solution 2.2
Cycles:(3−1)+10=12 cycles.
Time:t=12/168=0.0714μs. Since 1μs=1000ns, that is 0.0714×1000≈71.4ns.
Why divide by 168: each cycle lasts 1/168μs, and we have 12 of them.
Recall Solution 2.3
The key insight: across iterations the pipe stays full, so you pay the fill cost once, not per loop.
Itotal=1000×10=10,000,Cycles=(3−1)+10,000=10,002.Not1000×12=12,000 — that would wrongly re-fill the pipe every pass.
The back-edge edge case, stated plainly: the loop's conditional branch is already inside the 10 instructions (it is counted in I), and because we assume perfect branch prediction it costs its normal one cycle with no flush. If, instead, the back-edge mispredicted even once per pass, you would add(N−1)=2 flush cycles per pass, giving 10,002+1000×2=12,002 cycles. So loop overhead is either "free-ish" (predicted) or a per-pass (N−1) tax (mispredicted) — never silently ignored.
Recall Solution 2.4
IPC=1+p(w−1)=1+0.4(2−1)=1.4.
Cycles ≈I/IPC=10,000/1.4=7142.86→7143 cycles (round up — you can't run a partial cycle).
Why divide: IPC is instructions per cycle, so cycles = instructions ÷ IPC.
(c) M4 SMLAD: two MACs per instruction ⇒32/2=16 cycles/sample.
Speedup M3 → SMLAD:64/16=4× — and this is per MHz, before the M4's higher clock helps further.
Why SIMD halves it: one 32-bit instruction operates on two packed 16-bit lanes at once (the blue and yellow lanes in the figure above). See Fixed-point vs floating-point DSP.
Recall Solution 3.2
M3 soft-float:5×40=200 cycles.
M4F hardware:5×2=10 cycles.
Ratio:200/10=20× faster.
double trap: the M4 FPU is single-precision only, so every double op silently falls back to software emulation — you're back near the 200-cycle regime. Use float literals (3.14f) and sinf, not sin.
Recall Solution 3.3
Lmax=2+12+30=44cycles.t=44/100=0.44μs.
Since 1μs=1000ns, that is 0.44×1000=440ns.
Why each term is bounded: detect is one sync stage, stack is a fixed 12-cycle push, and the blocking term is bounded because you know the longest higher-priority handler. Determinism = every term knowable, not zero.
Cycles per sample:64×0.5=32 cycles.
Deadline per sample:1/48000s=20.83μs.
Required clock: we need 32 cycles to fit in 20.83μs, so
f≥20.83μs32cycles=1.536MHz.
So even a modest ~2 MHz clock technically meets it — meaning the FIR is not the bottleneck; interrupts and other tasks dominate. (Real designs still pick ~100 MHz for headroom against jitter and other tasks. See RTOS task scheduling and context switching.)
Recall Solution 4.2
Cycles/second in switching:120×1000=120,000 cycles/s.
Cycles/second available:72×106.
Fraction:120,000/72,000,000=0.0016=0.167%.
Tiny — which is why the auto-stacking NVIC + two-stack (MSP/PSP) design is so valued: switching overhead stays negligible. See RTOS task scheduling and context switching.
Recall Solution 4.3
FFT / image stream → cacheable main memory. It benefits from cache hits and its timing only needs to be good on average, not worst-case-guaranteed.
Servo loop code + data → TCM (tightly-coupled memory). TCM has single-cycle, fixed access with no cache misses, so its worst-case timing is bounded — exactly what a hard 10 µs deadline needs.
Why not cache the servo? Because a cache miss makes access time data-dependent: a hit is fast, but a miss stalls the core for many cycles while it fetches from slow main memory. That variability means you can no longer prove the servo finishes within 10 µs in the worst case — and hard-real-time is judged on the worst case, never the average. TCM removes the gamble entirely: every access is a fixed single cycle, so the deadline is provable. Rule of thumb: determinism > average speed for anything with a hard deadline. See Cache vs TCM in real-time systems.
Design A: single-issue ⇒ IPC =1, so ≈100,000 cycles.
tA=100,000/120MHz=833.33μs.Design B:IPC=1+0.35(2−1)=1.35, so cycles =100,000/1.35=74,074.07→74,075 cycles.
tB=74,074.07/300=246.91μs.Speedup:tA/tB=833.33/246.91=3.375×.
Notice the win splits into two independent factors: clock (300/120=2.5×) and IPC (1.35×), and 2.5×1.35=3.375. Depth alone bought the clock; dual-issue bought the extra 1.35.
Recall Solution 5.2
Cause: the FPU is a coprocessor (CP10/CP11) that is disabled at reset. Your compiled code emitted real hardware FP instructions (because of -mfloat-abi=hard), but with the coprocessor disabled those instructions are undefined and trap as a HardFault.
Fix: in SystemInit (startup) set the CP10/CP11 access bits in the CPACR register — commonly:
SCB->CPACR |= (0xF << 20); (grants full access to CP10 and CP11), then a DSB/ISB barrier.
No arithmetic here, but it's the single most common "my float crashes" mystery.
Recall Solution 5.3
Does the stacking get wasted? No. Cortex-M uses late-arrival: when a higher-priority IRQ appears during the stacking already in progress, the CPU reuses that same register push and simply vectors to the higher-priority handler — it does not push a second time. So we count the stacking once, not twice.
Walk the timeline in order:
detect the pending interrupt: 2 cycles.
stack the 8 registers (shared, done exactly once thanks to late-arrival): 12 cycles.
high-priority handler body runs to completion: 20 cycles.
exception-return / unstack path before the servo body starts: 12 cycles.
L=2+12+20+12=46cycles.t=8046=0.575μs.
Since 1μs=1000ns, that is 0.575×1000=575ns.
The mastery point: the naive answer double-counts the push as 2+12+12+20+12=58 cycles. Late-arrival (and its sibling tail-chaining) is exactly the hardware mechanism that removes that duplicate 12-cycle push, keeping worst-case latency bounded and knowable even under nested preemption. See Interrupts and the NVIC.
Recall Self-test checklist (reveal after finishing)
Pipeline fill paid once per run? ::: Yes — add (N−1) a single time, not per loop (assuming no branch flushes).
IPC formula direction? ::: Cycles = Instructions ÷ IPC (IPC is instructions per cycle).
double on M4F? ::: Software emulation — slow. Single-precision float only in hardware.
Cycles → time? ::: Divide cycles by clock in MHz to get µs; multiply µs by 1000 to read ns.
Nested IRQ stacking counted how many times? ::: Once (tail-chain / late-arrival reuse), not per level.
What breaks the ideal (N−1)+I? ::: Branch mispredictions, data/memory stalls, exceptions — each adds a re-fill/bubble penalty.