A complete 8×8 systolic array neural network accelerator for MNIST digit recognition, implemented in SystemVerilog and targeting Gowin FPGA devices.
This project demonstrates end-to-end hardware acceleration of a quantized neural network:
- Training: 2-layer fully-connected PyTorch model (784→128→10) trained on MNIST
- Quantization: Symmetric INT8 quantization of weights and activations
- Hardware: 8×8 systolic array with 64 processing elements (PEs), implemented in SystemVerilog
- Deployment: Synthesizes to 31% logic utilization on Gowin GW2AR-18 FPGA
- Performance: 148.4 MHz Fmax with 48% timing margin; ~5.3 ms inference latency (526k cycles at 100 MHz, dominated by per-tile SDRAM fetches)
Complete Inference Pipeline
- Real weight loading from ROM (101 KB of INT8 weights+biases)
- MNIST image input via Wishbone slave interface
- Systolic array computation with full 64-PE utilization
- Bias addition and ReLU activation
- Layer-wise INT8 requantization between layers
- Output logits (10 classes) read back via Wishbone
I/O Optimized for Small FPGA
- Reduced from 89 to ~45 I/O pins through targeted bus-width reductions
- Fits on Tang Nano 20K (53-pin device) or larger boards
- Optimizations: 8-bit SDRAM data, 12-bit Wishbone address, removed cycle counter
Production Ready
- Clean RTL synthesis with no errors or warnings
- Timing closure at 100 MHz (achieved 148.4 MHz)
- Successful place-and-route on hardware
- Golden model testbench for functional validation
- FPGA Tool: Gowin EDA v1.9+
- FPGA Board: Tang Nano 20K or larger Gowin device (GW2AR-18 minimum)
- Python (for quantization reference and testing): PyTorch, NumPy
- SystemVerilog Simulator (optional): Vivado, Quartus, or open-source tools
- Cocotb (optional, for testbenches):
pip install cocotb cocotb-tools
-
Create New FPGA Project in Gowin EDA
- Device: GW2AR-18 (or compatible)
- Package: QN88 (88-pin LQFP)
- Top Module:
nn_accelerator_top - Clock: 100 MHz
-
Add RTL Files
cp rtl/*.sv <gowin_project>/
-
Run Synthesis
- Expected pin count: ~45 (optimized from 89)
- Expected resource usage: 31% logic (6,412 LUTs + 31 ALUs)
- Timing: Should meet 100 MHz easily
-
Place & Route
- Should succeed with ~45 pins on 53-pin device
- No timing violations expected
-
Generate & Program Bitstream
gowin_program -device GW2AR-18 bitstream.fs
Write Access: Byte-enable (wb_sel[3:0]) determines which bytes of 32-bit word are written
Read Access: Full 32-bit reads (reading logits as INT32)
Training & Quantization (PyTorch)
- 2-layer FC network trained on MNIST with symmetric INT8 quantization
- Weights: INT8 (range [-128, 127])
- Activations: INT8 (range [-128, 127])
- Biases: INT32 (computed from floating-point training)
Layer 1 Requantization (Hardware)
scaled = acc_out * 2810 // quantization multiplier
scaled += (1 << 23) // rounding adjustment
requant = scaled >> 24 // fixed-point shift
clipped = clip(requant, -128, 127) // saturation
Layer 2 Output
- Logits output as INT32 (no requantization)
- Softmax typically computed on host or in software
- Grid: 8×8 = 64 processing elements
- Datapath: 8-bit × 8-bit → 32-bit (INT8 MACs)
- Throughput: 64 MACs per cycle (8 cycles per 128-neuron layer)
- Accumulation: All 64 PE outputs summed per neuron (full 8/8 utilization)
- Pipeline: 3-stage delay for activation distribution
Note: Fmax is synthesis-level; post-P&R timing may be more conservative.
Original design used 89 I/O pins, exceeding 53-pin Tang Nano 20K limit. Optimized through:
-
SDRAM Data Bus: 16 bits → 8 bits (saves 8 pins)
- Design only uses 8-bit byte-wise reads/writes
- No functional impact
-
Wishbone Address: 32 bits → 12 bits (saves 20 pins)
- Register map only spans 0x000–0x344 (~4 KB)
- Sufficient for all control, status, input, and output registers
- Logits at 0x318–0x33F; cycle counter at 0x340/0x344
-
Cycle Counter Removal: Deleted 32-bit performance counter (saves 16 pins)
- Profiling counter, non-critical for inference
- No impact on functionality
Result: Reduced to ~45 pins, fitting on 53-pin device with margin.
See IO_OPTIMIZATION.md for detailed analysis.
- Golden-model equivalence: full-system simulation boots weights_all.hex through the boot ROM into SDRAM, runs MNIST test images end to end, and produces logits that match golden_model.py bit-for-bit (tb/run_top.py)
- Cocotb suites pass: PE (13), systolic array (21), control FSM (13), reg interface (1)
- Icarus testbenches pass: SDRAM controller (vs behavioural model), tile cache, top-level smoke test
- RTL synthesis: Clean, no errors or warnings
- Resource utilization: 31% logic (6,412 LUTs), 28% registers (4,253 FFs)
- Timing closure: Synthesis shows 148.4 MHz Fmax vs 100 MHz target
- P&R success: Successfully placed-and-routed on Gowin FPGA board
- Design integration: All 6 inference stages connected and synthesized
- Quantization correctness: INT8 rounding and saturation implemented per spec
- Actual MNIST inference accuracy on hardware
- Post-P&R timing verification (only synthesis-level timing available)
- Physical inference latency measurement
- Floating-point baseline accuracy (for quantization loss analysis)