Skip to content

Mini NPU

A systolic array inference accelerator in Verilog on a Tang Nano 9K. Runs INT8 MNIST inference on weights trained and exported by seatorch. The portfolio centerpiece — the hardware half of "no PyTorch, no black boxes."

Status: not started. Begins after seatorch's milestone 10 (weight export) ships — the NPU needs real quantized weights to load, not synthetic test data, or you'll build the wrong export format twice.

Board: Sipeed Tang Nano 9K (Gowin GW1NR-9, ~8640 LUTs, ~20 DSP blocks, BRAM, PSRAM, ~₹2000). Toolchain: Yosys, nextpnr, Gowin programmer, Icarus Verilog, GTKWave.


Goal

MNIST image → ESP32 → UART → FPGA systolic array → predicted digit, benchmarked against CPU inference, with LUT/BRAM/DSP usage reported.

Architecture (final)

ESP32 (host)
    │ SPI/UART
Tang Nano 9K
 ┌─────────────────────────┐
 │ UART Controller          │
 │ BRAM (input/weight/out)  │
 │ Controller FSM            │
 │ Systolic Array (PEs)      │
 │ Accumulator               │
 │ Activation (ReLU)         │
 │ Output Buffer             │
 └─────────────────────────┘

Controller FSM: IDLE → LOAD → COMPUTE → WRITE → DONE

Each PE per cycle: out += weight * activation — weights flow horizontally, activations flow vertically, partial sums flow through. This is the TPU-style dataflow at toy scale.

Milestones (risk-ordered — small working pieces, not the full architecture before any results)

# Milestone What it proves
1 HDLBits complete (gates, FSMs, counters) digital design fundamentals
2 Single MAC unit, verified (2×3+4=10 in simulation) arithmetic in RTL
3 Matrix multiplier (small, fixed-size, behavioral) dataflow before structure
4 2×2 systolic array of PEs the real architecture, smallest viable size
5 Controller FSM + BRAM interfaces wired to the 2×2 array microarchitecture integration
6 UART module, hand-written (not IP-generated) serial protocol from scratch
7 Weight loading over UART into BRAM host↔FPGA bridge working end to end
8 Tiny network inference on the 2×2 array (toy weights, not yet MNIST) full pipeline proof before scaling size or model
9 Scale to 4×4 systolic array, if LUTs/DSPs allow area/throughput tradeoff, real resource constraints
10 seatorch INT8 export → .hex import into BRAM quantization pipeline connected end to end
11 Full MNIST inference: image → ESP32 → UART → FPGA → prediction system integration, the actual headline result
12 Benchmark vs CPU, LUT/BRAM/DSP usage report performance methodology, what goes in the writeup

Risk note (kept from earlier planning): milestone 4 is where most RTL projects stall. If it's taking more than 3 weeks, stop and debug method — wrong abstraction, not just a bug — rather than pushing through. Getting a 2×2 array working is more valuable than a perfect one.

Ship checklist

  • Verilog source + C driver + UART bridge, one repo
  • Benchmark numbers: latency vs CPU, resource usage (LUT/BRAM/DSP)
  • Writeup: "What a small systolic array teaches you about why real NPU compilers tile the way they do" — specific numbers from milestone 12, real tradeoffs, not "I built an FPGA project"
  • Show HN, r/FPGA, r/ECE

Extensions (after milestone 12, not before)

  • Parameterized array size — 4×4 vs 8×8 area/speed comparison
  • Tiny ISA: LOAD / MATMUL / RELU / STORE
  • Convolution support for small CNNs

Decision log

  • Tang Nano 9K chosen over Tang Nano 1K — 1K's LUT/DSP budget too constrained for a systolic array of any useful size.
  • Fill in as you go: 2×2 vs going straight to 4×4, BRAM layout, UART baud rate / framing.

Explicitly not in scope (v1)

Float support (INT8 only — float is "expensive" in hardware, that tradeoff is part of the writeup), multi-model support, anything beyond a single fixed network topology per build.