← All projects
ActiveSummer 2026 – PresentSolo

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 NBA FPGA Accelerator web interface, showing game-prediction controls — serial port, season/team filters, and a "Predict on FPGA" button that runs inference on the physical board.

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.