Module 5 Exercises — Multiply, Floating Point, and NEON
Back to the Course 3 syllabus. Read first: Smith 11–13 (+14–15 for the optimization mindset); H&H 5.3 for FP formats.
Work in course3/m5/ of the labs repo. This module is the DSP bridge: the kernels here are the ones Course 2 runs on the Cortex-M4F and Course 1 §3 analyzes numerically.
Exercises
Exercise 5.1 — Multiply-accumulate zoo. Small predict-verify set for the integer multiply family: MUL, MADD, MSUB, SMULH/UMULH (the high 64 bits), and a 64×64→128 full product assembled from MUL + UMULH. Verify the 128-bit product against __int128 arithmetic in a C harness. In notes.md: why does a fixed-point Q31 multiply need exactly the high-half instruction?
Exercise 5.2 — The dot product, four ways. The course’s central benchmark, on float vectors of length 4096:
- Scalar assembly, integers — Q15-style: 16-bit data, 64-bit accumulator.
- Scalar assembly, doubles —
LDR d,FMADD. - Hand NEON —
LD1four lanes at a time,FMLAvector accumulate, horizontalFADDPreduction at the end. clang -O2C — write the naive C loop, disassemble, and identify what the autovectorizer emitted before running it.
Benchmark all four (Module 2 discipline), then reconcile: expected 4× from vector width — what did you actually get, and what explains the gap (memory bandwidth, dependent accumulator, unrolling)?
| Version | ns / 4096 elements | Speedup vs. scalar double |
|---|---|---|
| Scalar int (Q15-style) | … | … |
| Scalar double | … | 1.0× |
| Hand NEON | … | … |
| clang -O2 autovectorized | … | … |
Exercise 5.3 — FMA vs. mul-then-add. Find a concrete (a, b, c) where fma(a, b, c) ≠ a*b + c in double precision (hint: near-cancellation). Demonstrate both results from assembly (FMADD vs. FMUL+FADD), print the full bit patterns, and explain the single-rounding difference in notes.md. Cross-reference Course 1 §3: this is floating-point conditioning made tangible in one instruction.
Exercise 5.4 — Saturating Q15 MAC. Implement a Q15 multiply-accumulate with saturation using SQDMULH (and compare a manual shift-and-clamp version). Feed it a signal that overflows a naive accumulator and show the saturated vs. wrapped outputs side by side. This is Exercise 1.5 grown up, and it is exactly what the Cortex-M4’s DSP extensions (Module 6) and Course 2’s fixed-point filters do per sample.
| Input drive | Wrapped output | Saturated output |
|---|---|---|
| 0.9 × full scale | … | … |
| 1.5 × full scale | … | … |
Exercise 5.5 — Read the master’s code. Disassemble libc strlen (or memcpy) on your Mac (otool -tv on the shared cache extract, or step into it in lldb). Annotate ~30 instructions in notes.md: identify the vector loop, the alignment prologue, the tail handling. Smith 15’s “reading and understanding code,” applied to production SIMD. Then revisit your Exercise 4.2 ratio.