NBA Game Prediction on FPGA
A win-probability and point-spread predictor trained on 30K NBA games, quantized and compiled to RTL, and verified bit-exact on a Basys 3 board.
- 67%
- Accuracy
- 13 µs
- Latency @ 100 MHz
- 90.5%
- LUT utilization
- +0.171 ns
- WNS

The problem
Sportsbooks price NBA games with a line that’s hard to beat, and most student ML projects stop at “trained a model that gets close.” The harder problem — and the one that actually resembles hardware work — is taking that model off a laptop and onto silicon: quantizing it without losing accuracy, compiling it to RTL, and proving the board’s output matches the simulation bit-for-bit before trusting a single number it produces.
Approach
A dual-head MLP (96→64→32) predicts win probability and point spread from a 30K-game training set. 8-bit quantization-aware training (QAT) brought the model down with under 0.1% accuracy loss, and QKeras → hls4ml compiled the quantized graph to RTL, targeting a Basys 3 Artix-7.
Getting a prediction on and off the board needed a datapath, not just an inference core: a Verilog UART/AXI-Stream wrapper with packet framing, a checksum, and FSM control moves feature vectors in and predictions out. 17 cocotb tests and RTL co-simulation verify the wrapper independently of the model itself.
As a second path through the same problem, a depth-3 XGBoost gradient-boosted tree was mapped to comparator logic via conifer — trading the neural net for decision-tree logic entirely.
What broke
FIFO deadlock
The board hung on real hardware in a state that never showed up in C-simulation — the AXI-Stream FIFO could reach a fill level the C model never explored. Rebuilding the datapath around explicit AXI-Stream handshaking (rather than trusting the HLS-generated FIFO behavior) fixed it, and cocotb regression tests were extended to cover the fill states that caused it.
The other real constraint was area. The first synthesis run came in at 106% LUT utilization — over budget before timing closure was even in play. Shrinking the first hidden layer from 128 to 96 units and retraining recovered 0.24 points to 90.5% utilization with no accuracy loss, which only worked because the QAT pipeline made retraining cheap enough to iterate on.
Results
| Path | Latency | LUTs | DSPs | Notes |
|---|---|---|---|---|
| MLP (hls4ml) | 13 µs | 90.5% | — | 67% win accuracy, +0.171 ns WNS |
| GBDT (conifer) | 90 ns | 13.5% | 0 | Same accuracy, 150× faster, zero DSPs |
The GBDT path matched the MLP’s accuracy while using an order of magnitude fewer resources and no DSP slices at all — a reminder that the fanciest model isn’t always the right one to put on an FPGA.
FPGA output matched RTL simulation bit-exactly across a 50-game regression, with zero mismatches or timeouts.
What I’d do differently
The FIFO deadlock cost real time specifically because C-simulation looked clean — I’d add a directed cocotb test for FIFO-boundary fill states before board bring-up next time, not after a failure on hardware forced the issue.