Loading repository data…
Loading repository data…
deaneeth / repository
A TPU you can watch run - real SystemVerilog systolic array, compiled to WASM, visualized live in your browser.
A transparent discovery signal based on current public GitHub metadata.
This score does not audit code, security, maintainers, documentation quality, or suitability. Verify the repository and its current documentation before adoption.
Enter two int8 matrices. The browser executes the actual Verilog RTL (compiled to WebAssembly) cycle-by-cycle and animates every PE, every activation, and every partial sum straight from the hardware signals.
The core insight: Verilator and Emscripten, chained together, turn synthesizable SystemVerilog into a WebAssembly module any browser can execute. The React visualizer is purely downstream of this: it reads state out of the compiled hardware binary.
| What is real | Why it matters | |
|---|---|---|
| 🔵 Real RTL | rtl/*.sv is synthesizable SystemVerilog: always_ff/always_comb only, no delays, no initial blocks, no inferred latches | Drop it into any FPGA synthesis tool without modification |
| 🟣 Real WASM | Verilator compiles RTL to cycle-accurate C++. Emscripten compiles that C++ to WebAssembly. The browser runs compiled hardware | Not a JavaScript reimplementation of the math |
| 🟢 Real signals | PE weights, activations, partial sums, and FSM phase come from an explicit debug output bus on tiny_tpu_top | The visualizer fabricates nothing; every number is traceable to a hardware port |
A 4×4 weight-stationary systolic array computes C = A · B for signed int8 matrices in 14 clock cycles:
| Phase | Cycles | Timeline | What happens |
|---|---|---|---|
LOAD_WEIGHTS | 4 | ████░░░░░░░░░░ | Matrix B loaded column-by-column into the PE grid as stationary weights |
STREAM | 7 | ███████░░░░░░░ | Matrix A streams from the west edge with diagonal row-skew; MACs fire each cycle |
DRAIN | 3 | ███░░░░░░░░░░░ | Final partial sums propagate out the south edge to the result buffer |
| Total | 14 | ██████████████ | C = A · B complete. Signed int8 inputs, 32-bit accumulation. |
The MAC equation executed by every PE, every cycle:
psum_out <= psum_in + (weight_reg × act_in)
Row i of matrix A is delayed by i cycles (the diagonal skew) so each activation meets the correct stationary weight at precisely the right clock edge. For matrices larger than 4×4, the L3 view tiles into multiple 4×4 passes, each running on real RTL.
| Spec | Value |
|---|---|
| Array dimensions | 4 × 4 (16 processing elements) |
| MACs per cycle | Up to 16 (one per PE) |
| Input precision | Signed int8 |
| Accumulator width | Signed int32 |
| Synthesizable target | Any FPGA synthesis tool (no simulation-only constructs) |
Most hardware visualizers show a cartoon: a JavaScript reimplementation of the math dressed up with animations. TinyTPU does the opposite.
| Principle | In practice |
|---|---|
| 🔒 RTL is the only source of truth | The frontend never reimplements the matmul. It reads state out of the compiled WASM binary. If the RTL is wrong, the visualizer shows the wrong thing. |
| ✔️ Bit-exact golden verification | The cocotb test suite asserts bit-exact equality between RTL output and a numpy reference model across 20+ random matrix pairs before anything ships. A wrong matmul is a beautiful lie. TinyTPU refuses to tell it. |
| 🚫 No signal fabrication | PE weights, activations, and partial sums come from an explicit debug output bus on tiny_tpu_top, not from reconstructed state, not from a shadow model, not from public_flat. |
| 🏗️ Synthesizable by constraint | The Verilog is not a testbench hack; it is the actual design, constrained to always_ff/always_comb, lint-clean under -Wall, and free of all simulation-only constructs. |
All RTL tooling runs inside WSL2 Ubuntu. The frontend runs anywhere.
# WSL2 Ubuntu system dependencies
sudo apt-get install -y build-essential cmake python3 python3-pip python3-venv \
autoconf flex bison libfl2 libfl-dev
# Verilator 5.x (build from source)
git clone https://github.com/verilator/verilator && cd verilator
git checkout stable && autoconf && ./configure && make -j$(nproc) && sudo make install
# Emscripten SDK
git clone https://github.com/emscripten-core/emsdk && cd emsdk
./emsdk install latest && ./emsdk activate latest
source emsdk_env.sh
# Python virtualenv
python3 -m venv ~/.venvs/tinytpu && source ~/.venvs/tinytpu/bin/activate
pip install cocotb pytest numpy
# Node.js + pnpm
nvm install --lts && npm install -g pnpm
Step 1: RTL lint
verilator --lint-only -Wall rtl/*.sv
Step 2: Simulation and golden verification
source ~/.venvs/tinytpu/bin/activate
pytest sim/golden.py -q
Step 3: WASM build
bash wasm/build.sh
# outputs web/public/tiny_tpu.mjs + web/public/tiny_tpu.wasm
Step 4: Frontend dev server
cd web && pnpm install && pnpm dev # http://localhost:4321
| Layer | Technologies |
|---|---|
| RTL | |
| WASM | |
| Frontend | |
| Verification | |
| Deploy |
tiny-tpu/
│
├── rtl/ SystemVerilog source of truth
│ ├── pe.sv Single MAC cell (weight-stationary)
│ ├── systolic_array.sv 4×4 PE grid (generate loop)
│ ├── controller.sv FSM: IDLE, LOAD_WEIGHTS, STREAM, DRAIN, DONE
│ └── tiny_tpu_top.sv Top wrapper + debug output bus
│
├── sim/ cocotb verification suite
│ ├── golden.py numpy reference model (ground truth)
│ ├── test_pe.py PE-level unit tests
│ ├── test_systolic_array.py Array-level unit tests
│ └── test_top.py Full matmul + cycle count tests
│
├── wasm/ C++ harness to WASM bridge
│ ├── harness.cpp TinyTpuSim class, reads the debug bus
│ ├── bindings.cpp embind JS-callable surface
│ └── build.sh verilator --cc + em++ build script
│
├── web/ Astro + React + shadcn/ui frontend
│ ├── src/pages/ index.astro, app.astro, docs/
│ ├── src/components/ Visualizer, PEGrid, Controls, MatrixInput
│ ├── src/lib/ wasm-loader.ts, state-schema.ts
│ └── public/ tiny_tpu.wasm (compiled artifact)
│
└── docs/
└── STATE_SCHEMA.md Per-cycle state contract (keep in sync with state-schema.ts)
| Doc | What it covers |
|---|---|
| How it works | The full RTL to Verilator to WASM to browser pipeline. Why the browser runs real compiled hardware, not a JavaScript reimplementation. |
| The systolic array | Weight-stationary dataflow, the diagonal skew, the 14-cycle budget, and why TPUs use this structure. |
| Architecture | Monorepo layout, the CycleState data contract, build flow, and key design decisions. |
| Status | Feature |
|---|---|
| ✅ | 4×4 synthesizable systolic array, bit-exact golden-verified against numpy |
| ✅ | Real-time WASM execution in the browser (zero JS math reimplementation) |
| ✅ | L1 / L2 / L3 progressive disclosure: single MAC, full 4×4 grid, tiling |
| ✅ | Full SEO pass, production deploy on Vercel |
| Status | Feature |
|---|---|
| 🔲 | Configurable array size (N = 2 to 16) |
| 🔲 | Challenge mode: score your MAC utilization vs theoretical optimal |
| 🔲 | Dataflow modes: weight-stationary vs output-stationary toggle |
| 🔲 | int8 quantization visualizer |
| 🔲 | GPU-vs-TPU comparison view (cross-links TinyGPU) |
| 🔲 | Run a real nn.Linear layer: the ML to hardware bridge |
Invisible systems, made watchable, with the real implementation underneath.
| # | Project | Description | Status |
|---|---|---|---|
| 1 | TinyGPU | A minimal GPU in synthesizable RTL | ✅ Shipped |
| 2 | TinyTPU | This project, a minimal weight-stationary systolic array | ✅ Shipped |
MIT. See LICENSE.
Built by Deaneeth❤️ · SystemVerilog · Verilator · Emscripten · Astro · React