A systolic array passes data between neighboring cells — each cell does one MAC and hands the result to its neighbor, without reading from a shared memory on every step:
Weight row 0: [w00] [w01] [w02] [w03] ← loaded once, stationary
↑ ↑ ↑ ↑
Activation: [a0] → MAC → MAC → MAC → MAC → partial sum out
[a1] → MAC → MAC → MAC → MAC → partial sum out
[a2] → MAC → MAC → MAC → MAC → partial sum out
acc += a_in * w; passes a_in to its right neighbor, passes acc down.Your design vs. systolic:
The systolic idea is directly applicable to your Project 3: you could load one column of weights into each MAC unit and stream activations from left to right — a mini systolic array, 4 cells wide.
The Ethos-U55 is Arm's production TinyML accelerator, targeting Cortex-M systems:
What is the same as your design:
What is different:
Instead of a separate peripheral, RVV adds vector registers to the CPU:
# RVV assembly for a length-64 int8 dot product
vsetvli t0, a2, e8, m1, ta, ma # set vector length (up to VLEN/8 elements)
vle8.v v0, (a0) # load 8-element vector from a[]
vle8.v v1, (a1) # load 8-element vector from b[]
vmul.vv v2, v0, v1 # element-wise multiply
vredsum.vs v3, v2, v3 # reduce: v3[0] += sum(v2)
A single vmul.vv instruction multiplies 8 (or 16 or 32) pairs in one cycle. The vector unit lives inside the CPU pipeline — no bus transactions, no polling.
Why we did not build RVV for this course: implementing a vector unit requires changes to the register file, decode, and datapath that would double the complexity of Project 1. A separate memory-mapped accelerator decouples the hardware design from the ISA — easier to implement, easier to reason about, and directly teachable.
From A Practical Introduction to Hardware/Software Codesign (Ch. 11–12):
Step 1 — Programmer's model: what does software see? (register map, semantics of each register, timing of CONTROL→STATUS→result)
Step 2 — Hardware interface: what signals cross the boundary? (bus protocol, address decode, ready/valid handshake)
Step 3 — Datapath: what computation happens inside? (MAC array, accumulator, saturation)
Step 4 — Control: what FSM drives the datapath? (IDLE → LOAD → COMPUTE → DONE)
This order matters: define the software interface before the hardware, so you know what you are building toward. In M04A04 we did Step 1 (register map). Today and M05A02 we do Steps 2–4.
Walking through all 4 steps for the KWS Conv2D accelerator:
Step 1 — Programmer's model (done in M04A04):
INPUT_ADDR, LENGTH, then sets CONTROL[0] (start).STATUS[0] (done). Reads result from output buffer.Step 2 — Hardware interface:
sel_accel from address decoder.dmem_addr and dmem_re; receives dmem_rdata and dmem_ready.Step 3 — Datapath:
requant module: bias + (acc × mult) >> shift + zero_point, clamped to int8.Step 4 — Control FSM:
IDLE → LOAD → COMPUTE → DONEOne key property of this framework: Steps 1–2 are the contract between hardware and software. Once they are agreed upon, Steps 3–4 can be implemented independently — and the testbench (Step 2 simulator) is what validates Step 3–4.
Your team has budget for one architectural improvement to Project 3. Which do you choose?
Option A: Double the MAC array width from 4 to 8 lanes.
Option B: Add a DMA engine so the CPU does not stall during activation fetch.
Option C: Add a second weight BRAM row (double buffer): load row
Questions (10 min):
Expected: (1) A, if BRAM bandwidth matches. (2) B or C, since they reduce overhead that Amdahl's law can't hide. (3) C — double buffering eliminates the LOAD stall, which is a memory-bandwidth bottleneck.
Before integration, verify the accelerator in isolation:
// In the Verilator testbench:
// 1. Write the same input vector and weight matrix as the C test vector
// 2. Assert CONTROL[0]
// 3. Wait for STATUS[0] == 1
// 4. Read the output
// 5. Compare bit-for-bit against the C kernel output
if (hw_output[i] != c_kernel_output[i]) begin
$error("Mismatch at output[%0d]: got %0d, expected %0d",
i, hw_output[i], c_kernel_output[i]);
end
Bit-exact match is the requirement. If the C kernel outputs 42 for a given input, your accelerator must output 42 — not 41, not 43. Quantization is brittle: a 1-ULP error in the requantization step propagates and changes the classification.
Implementing the Accelerator: the int8 MAC unit, accumulator, local BRAM buffer, and the IDLE→LOAD→COMPUTE→DONE control FSM — all in SystemVerilog, with a Verilator testbench that verifies against the C kernel.