int8 · Verilog · FPGA

I put neural networks into hardware.

Quantized networks on an Arduino UNO, in Verilog, and on a real FPGA, each checked bit-exact against a Python golden model. One of them is running live on this page: my int8 planner, balancing a pendulum.

10,000MNIST images bit-exact
273clocks per image
4,000decisions on a real FPGA
8public repos
θ 0 · ω 0 · LANE 0/32 · 1,281 CLK
32 imagined futures per tick · drag the pendulum

Almost all the work in a neural network is int8 dot products. I build the hardware that does them, and check every answer bit for bit, against Python.

tiny-tensor-core · Verilog · ECP5

One digit, 273 clocks.

My 6,634-parameter MNIST network as a chip. Scroll, and the figure runs the real int8 weights through the same schedule as the Verilog, clock by clock.

  1. 01

    Layer 1 · 197 clocks

    One pixel per clock is broadcast to 32 multiply-accumulate lanes, each holding one neuron's weights. 6,272 MACs, 32 at a time.

  2. 02

    Requantize · 32 clocks

    No floats. Each accumulator becomes a 0–127 activation with one fixed-point multiply and a shift: (acc · 31,942 + B + 2²³) ≫ 24. The clamp at 0 is the ReLU.

  3. 03

    Layer 2 · 33 clocks

    The 32 activations stream into 10 accumulators, one per digit.

  4. 04

    Argmax · 11 clocks

    Ten compares, and out comes the digit. The Arduino UNO needed about 491,000 clocks for the same network; the predictions match on all 10,000 test images.

    Draw your own digit →

input · 14×14idle
32 lanesacc → h
–
clock 0 / 273UNO: ~491,000
layer 1
requant
layer 2
max
Fig. — real MNIST test images, real int8 weights, integer math only.

Lab · resistors → AVR → RTL → FPGA

Five ways down to the metal.

Each project takes the one before it a level lower, and every one is checked against a golden model before it counts.

Open the planner bench →
Resistor network on a breadboard wired to an Arduino UNO

01 · analog · breadboard

analog-neuron

A neuron's weighted sum done by resistors instead of a processor. Inputs are voltages, weights are conductances, and Kirchhoff's current law does the multiply-accumulate.

within 2.3 ADC steps of ideal · weights matched to ~0.4%
ATmega328P 196 → 32 → 10 16 MHz · 2 KB SRAM

02 · C · PyTorch · AVR

tinyml-mnist-uno

A digit classifier trained in PyTorch, quantized to int8 and run on an 8-bit microcontroller with no FPU. The weights stream from flash a byte at a time.

96.5% int8 accuracy · 30.7 ms per image · 268 B of buffers
××××Σ+c a0·b0a1·b1a2·b2a3·b3d

03 · Verilog · iCE40

dp4a-unit

NVIDIA's DP4A instruction in Verilog: four int8 multiplies summed into a 32-bit accumulator in one operation, with one 9×9 multiplier covering every sign mode.

10,000 / 10,000 bit-exact · ~51.6 → ~88.5 MHz pipelined

04 · Verilog · ECP5

tiny-tensor-core

The whole MNIST network as a chip: 32 parallel MAC lanes, integer-only requantization between layers, and an argmax that outputs the digit.

273 clocks per image · 96.48%, no prediction differs

05 · Verilog · CUDA · Tang Nano 20K

int8-rollout-engine

Sampling-based model-predictive control with the rollouts in hardware: every tick, 32 int8 lanes imagine 32 futures of a pendulum and hand back the best plan.

4,000 / 4,000 decisions bit-exact on the FPGA · 1,281 clocks / decision

int8-rollout-engine · on the board · Oct 14, 2026

The simulator said yes. The chip said no.

The 8-lane planner went onto a Tang Nano 20K. It ran the right sequence, with the clock count exact every time, and got 0 of 20 answers right.

A/B builds narrowed it down. Turning off the carry chains changed nothing. Building the multipliers from LUTs instead of the chip's DSP blocks made it bit-exact. RTL simulation and the timing report never showed the fault. Checking the chip's answers did.

Then a logic analyzer on the busy pin: 186.000 µs per decision, which is 5,022 clocks at 27 MHz, exactly the RTL's count.

Read the board log →
default buildDSP on · carry on✗ 0 / 20
-noaluDSP on · carry off✗ same wrong bytes
-nodspLUT multipliers✓ 20 / 20
-nodsp replayevery recorded decision✓ 4,000 / 4,000
-nodsp closed loopFPGA picks every action✓ 20 / 20 episodes
busy · pin 73 · ~60 ms per tick one pulse, zoomed 186.000 µs 5,022 clocks @ 27 MHz

Also built

kickoffresearchideatespecbuildjudge·ship gate ABC

Claude Code plugin · JavaScript

hackforge

A hackathon harness: live research, an idea tournament scored against the event's rubric, a demo-first build with a separate QA judge, a mock judging panel, and a fact-checked write-up. A deadline clock runs throughout, and a human decides at three gates.

github.com/Jusman0/hackforge ↗
HC-SR04 sensor and buzzer on a breadboard with a hand approaching

Arduino · forensics

ultrasonic-proximity-alarm

An HC-SR04 alarm whose sketch was lost. I rebuilt it from a video of the serial monitor, down to the distance formula: the only common one that reproduces all 19 logged readings. A random formula would match about 1 time in 20,000.

github.com/Jusman0/ultrasonic-proximity-alarm ↗

Simulation said it worked. Timing said it worked. The chip said otherwise. So now I check the chip.

About

I work where machine learning meets digital logic: I quantize networks to int8, write the Verilog that runs them, and prove the two agree.

Verilog · Icarus · Yosys · nextpnr · ECP5 · iCE40 · Gowin · Arduino · PyTorch · NumPy · CUDA · Claude Code

Contact

Let's talk.

Jusman0 · 2026Both simulations run the real int8 weights in your browser. No frameworks, no web fonts.