diff --git a/.github/workflows/wasm.yml b/.github/workflows/wasm.yml
index 3099e57..d154637 100644
--- a/.github/workflows/wasm.yml
+++ b/.github/workflows/wasm.yml
@@ -13,31 +13,23 @@ jobs:
steps:
- uses: actions/checkout@v4
- # The harness uses Verilator 5 APIs (VerilatedContext); build from source like rtl.yml.
- - name: Cache Verilator build
- id: cache-verilator
- uses: actions/cache@v4
- with:
- path: ~/verilator-install
- key: verilator-v5.036-ubuntu-24.04
-
- - name: Build Verilator v5.036 from source
- if: steps.cache-verilator.outputs.cache-hit != 'true'
+ # The same Verilator build as the project's .tools/setup.sh (CMake, v5.036 source, same patch). The runtime's
+ # version string is compiled into the engine, so the build method has to match the committed one.
+ - name: Build Verilator v5.036 (as .tools/setup.sh)
run: |
sudo apt-get update -qq
- sudo apt-get install -y autoconf flex bison libfl2 libfl-dev help2man perl build-essential
- git clone --depth 1 --branch v5.036 https://github.com/verilator/verilator.git /tmp/verilator
- cd /tmp/verilator
- autoconf
- ./configure --prefix="$HOME/verilator-install"
- make -j$(nproc)
- make install
+ sudo apt-get install -y cmake ninja-build autoconf flex bison libfl2 libfl-dev help2man perl build-essential
+ bash .tools/setup.sh
- name: Add Verilator to PATH
- run: echo "$HOME/verilator-install/bin" >> $GITHUB_PATH
+ run: echo "$GITHUB_WORKSPACE/.tools/verilator-install/bin" >> $GITHUB_PATH
+ # Pinned to the version the committed engine was built with: the compiled .wasm changes between Emscripten
+ # releases, and the freshness check below compares bytes.
- name: Set up Emscripten
uses: mymindstorm/setup-emsdk@v14
+ with:
+ version: '6.0.13'
- uses: actions/setup-node@v4
with:
@@ -54,6 +46,14 @@ jobs:
test -f web/public/spike/counter.wasm
echo "spike wasm: $(du -h web/public/spike/counter.wasm | cut -f1)"
+ # The committed engine (web/public, served by the site) must pass the golden fixtures as it is, before the
+ # rebuild below overwrites it. Byte-identical rebuilds across machines are not guaranteed, so behaviour is the check.
+ - name: Committed engine passes the golden fixtures
+ run: |
+ mkdir -p /tmp/committed
+ cp web/public/tiny_attn.mjs web/public/tiny_attn.wasm /tmp/committed/
+ ENGINE_DIR=/tmp/committed node wasm/test.mjs
+
# The engine: Verilate tiny_attn_top, build the module, run the golden fixtures in Node, check the size budget.
- name: Build engine WASM
run: bash wasm/build.sh
diff --git a/README.md b/README.md
index 9231f1f..cb0fa7e 100644
--- a/README.md
+++ b/README.md
@@ -1,81 +1,310 @@
-# TinyAttention
+
+
+
+
+
-A synthesizable **FlashAttention engine** in SystemVerilog, verified bit-exact against a golden model, compiled to
-WebAssembly, and running live in your browser.
+
FlashAttention in SystemVerilog
-**Live:** https://tiny-attention.vercel.app
+
+ A synthesizable FlashAttention engine, verified bit-exact against a golden model,
+ compiled to WebAssembly, and running live in your browser.
+
-
+
+ The tiled online-softmax schedule from the FlashAttention paper, as real RTL: the N × N score matrix never leaves the chip.
+ The same RTL runs in the browser and on an ECP5 FPGA.
+
-Fourth project in the series after [tiny-gpu](https://github.com/tinycomputers/tiny-gpu),
-[tiny-tpu](https://github.com/tinycomputers/tiny-tpu) and [tiny-cpu](https://github.com/tinycomputers/tiny-cpu). Same rule as the others: nothing on screen is faked.
-Every value comes from a debug port on the compiled RTL, or from the golden model the RTL is checked against.
+
+
+
+
+
+
+
+
+
-## What it does
+
+
+
+
+
+
-Attention computes `softmax(Q Kᵀ / √d) V`. The score matrix is N × N, and storing it is what makes standard attention
-expensive in memory. FlashAttention computes the output one tile at a time with an online softmax, so the matrix never
-exists in memory. This engine implements that schedule, plus the standard three-pass schedule for comparison, with
-optional overlap of the K/V loads with compute.
+
+
+
+
+
+
-## Verification
+---
+
+## Preview
+
+
+
+
+
+
+
+Pick a preset (a toy sentence, a previous-token head, an attention sink, and more), press **Start**, and step the clock.
+Every tile, buffer and byte in the screenshot is read from the compiled RTL running in your browser.
+
+---
+
+## What is FlashAttention?
-| What | Result |
-|---|---|
-| Engine vs golden model, end to end | Output, HBM bytes, tile count, K/V loads and busy cycles match on every run. Every sequence length 4 to 32, causal and not, every block size, both schedules, overlap on and off, random HBM latency, 200 seeds. |
-| Exhaustive unit coverage | exp2 is checked on every input in its range; reciprocal on every mantissa. DMA burst timing is pinned cycle by cycle. |
-| Output error vs float attention | Max 0.0028 to 0.0260 across the six presets (N = 12 to 16), in float units. The recorded 1000-seed study is in `sim/golden/study_results_1000seeds.txt`. |
-| HBM traffic at N = 32 | Non-causal: FLASH 2432 B (4-row blocks) or 896 B (16-row blocks), NAIVE 12672 B. Causal: FLASH 1536 B or 768 B, NAIVE 7296 B. |
-| WebAssembly | The compiled module, on golden fixtures for each schedule, matches the same quantities. 83 KB gzipped. |
-| Web app | Browser tests of every page, the visualizer at phone width and in both colour schemes. |
-| FPGA (ECP5 LFE5U-85F) | Synthesizes with no latches: 40,416 of 83,640 LUT4 positions, 8,726 flip-flops, 51 of 156 DSP blocks, no block RAM. fmax estimate 11.5 MHz, so timing is not closed at the ULX3S 25 MHz clock. The UART interface is verified in simulation; it has not run on a board. |
+Attention computes `softmax(Q Kᵀ / √d) V`. Standard implementations build the full `N × N` score matrix in memory,
+read it back for the softmax, and read it again for the product with `V`. At long sequence lengths that matrix dominates
+the memory traffic, and that traffic is what limits speed.
-Every row is a test in the repository, run by GitHub Actions on each pull request. The docs page
-[Verification](https://tiny-attention.vercel.app/docs/verification) lists each check and its numbers.
+**FlashAttention** computes the same output one tile at a time. A block of query rows stays on chip, key and value tiles
+stream past it, and an **online softmax** keeps a running maximum `m` and running sum `l` for each row. When a new tile
+raises the maximum, the old sum and the output accumulator are rescaled by `α = exp(m_old − m_new)`. The score matrix is never
+written to memory, and the result is exact.
-## Repository
+This project implements that schedule in hardware, along with the standard three-pass schedule (NAIVE) for comparison.
+Both run on the same arithmetic units, so the difference you see is the schedule, not the datapath.
+
+---
+
+## Highlights
+
+- **A real FlashAttention datapath in RTL.** Two 4 × 4 systolic arrays (`S = Q·Kᵀ` and `P·V`), scale and causal mask,
+ online softmax with an exp2 unit and a reciprocal, and an O accumulator that rescales by α. All of it is in `rtl/`.
+- **Bit-exact against a golden model.** The engine's output, HBM byte counts, tile counts and K/V loads match the Python
+ golden model on every end-to-end run, and busy cycles match a closed-form schedule model.
+- **Fixed point, end to end.** INT8 Q, K and V with per-tensor scales folded into one 24-bit `cfg_scale`; UQ1.15 exp2 and
+ reciprocal from packed lookup tables; every width is documented in [ARCHITECTURE](docs/ARCHITECTURE.md).
+- **Runs in the browser.** Verilator compiles the RTL to C++, Emscripten compiles that to a 84 KB (gzip) WebAssembly module,
+ and the visualizer reads every value from its debug ports. Nothing is reimplemented in JavaScript.
+- **Synthesizes for an FPGA.** ECP5 (LFE5U-85F, the ULX3S chip) with no inferred latches, behind a UART command interface.
+- **Teaching visualizer.** Four views: the softmax cell, the tile datapath, the tile schedule, and a naive-versus-flash
+ comparison with an N sweep and an error panel.
+
+---
+
+## How It Works
```
-rtl/ SystemVerilog: arithmetic blocks, tile core, memory, controllers, top level
-sim/ cocotb unit and end-to-end tests, the golden model (sim/golden), lint and run scripts
-wasm/ Verilator + Emscripten build, the C++ harness, and the Node test over golden fixtures
-web/ Astro + React site: the visualizer at /app, the docs, and the Vitest and Playwright tests
-docs/ Architecture, the plan, the state schema, and the media for this README
+ rtl/*.sv ──► Verilator ──► C++ model ──► Emscripten ──► tiny_attn.wasm ──► Astro + React visualizer
+ (the design) (cycle-accurate) (wasm/build.sh) (web/public) (reads debug ports)
+ │
+ ├── cocotb unit suites and end-to-end tests against sim/golden (Python, bit-exact)
+ ├── Verilator lint with -Wall, on every module and the full design
+ └── Yosys + nextpnr for ECP5 (fpga/ecp5)
```
-## Quick start
+The browser never computes attention itself. It steps the compiled hardware one clock at a time and draws what the
+hardware reports. If the RTL is wrong, the visualizer shows the wrong thing, and the test suites catch it first.
+
+---
+
+## Hardware
+
+**Tile core (`attn_tile.sv`).** One 4-row group against one 4-row K/V tile per step, with 20 cycles from tile start to
+tile done. MM1 computes `S` on a 4 × 4 weight-stationary array, the scale and causal mask apply, the online softmax updates
+`m`, `l` and `α`, and MM2 computes `P·V` on a second array. The O accumulator rescales by `α` and adds the new product.
+Five operations share this datapath: the FLASH step, and the four NAIVE passes.
+
+**Memory and control.** `hbm_model` is a 2,048-word memory with byte counters. `dma` moves bursts with a configurable
+first-word latency (0 to 64 cycles). K and V are double-buffered when overlap is on. `flash_ctrl` loops over Q blocks and
+K/V tiles, skips tiles above the causal diagonal, and normalizes and stores each block. `naive_ctrl` runs the three-pass
+softmax through HBM.
+
+**Formats.** INT8 inputs, 17-bit `S`, Q8.8 log-domain `Z` and `m`, UQ1.15 exp2 and `α`, UQ1.8 `P`, 18-bit `l`, 26-bit `O`,
+and a Q8.8 output. The full table, with the reasoning, is in the [fixed-point docs](https://tiny-attention.vercel.app/docs/fixed-point).
+
+### Schedule and traffic
+
+At `N = 32`, non-causal, the engine moves these bytes (from the golden traffic model, checked against the RTL counters):
+
+| Schedule | Q block | HBM bytes | vs NAIVE |
+|:---|:---:|---:|:---:|
+| NAIVE (three-pass, scores in HBM) | 4 rows | 12,672 | 1× |
+| FLASH | 4 rows | 2,432 | 5.2× less |
+| FLASH | 16 rows | 896 | 14.1× less |
+
+With the causal mask, the same schedules move 7,296 bytes (NAIVE), 1,536 (FLASH, 4 rows) and 768 (FLASH, 16 rows).
+A larger Q block means fewer K/V loads: the output does not depend on the block size, only the traffic does.
+
+### Cycles
+
+Busy cycles at `N = 32`, non-causal, 10 cycles of HBM latency (`sim/perf_baseline.json`):
+
+| Configuration | Busy cycles |
+|:---|---:|
+| FLASH, 4-row Q block, no overlap | 4,113 |
+| FLASH, 4-row Q block, K/V overlap | 2,937 |
+| FLASH, 16-row Q block, no overlap | 2,133 |
+| FLASH, 16-row Q block, K/V overlap | 1,657 |
+| NAIVE | 10,961 |
+
+The sequential FLASH numbers match a closed-form schedule model exactly. The overlap and NAIVE numbers are pinned from the
+RTL as regression baselines: a change to either fails the test suite.
+
+### Accuracy
+
+The engine's output against float attention on the same inputs, after dequantization. The error comes from INT8 input
+quantization, not the datapath:
+
+| Preset | N | Causal | Max error |
+|:---|---:|:---:|---:|
+| toy sentence | 12 | yes | 0.0049 |
+| previous token | 16 | yes | 0.0105 |
+| attention sink | 16 | yes | 0.0253 |
+| uniform | 16 | no | 0.0028 |
+| self | 16 | no | 0.0260 |
+| random (seeded) | 16 | no | 0.0163 |
+
+The 1,000-seed numerics study (`sim/golden/study_results_1000seeds.txt`) separates the datapath error from the total.
+At `N = 32`, the datapath error averages 0.07 of one INT8 step of V for FLASH and 0.10 for NAIVE.
+
+---
+
+## Verification
+
+Every row is a test in this repository, run in GitHub Actions on each pull request.
+
+| What | How it is checked | Scope |
+|:---|:---|:---|
+| Output, bytes, tiles, K/V loads and cycles | cocotb end-to-end test against the golden model | every N from 4 to 32, causal and not, every block size, both schedules, overlap on and off, random HBM latency, 200 seeds |
+| Arithmetic blocks | cocotb unit suites | exhaustive exp2 and reciprocal sweeps; DMA burst timing cycle by cycle |
+| Golden model | `pytest sim/golden` | 54 tests: quantization, exp2 and reciprocal, both schedules against float, traffic formulas, presets, and freshness of every generated table |
+| Lint | Verilator `-Wall` | every module and the full design |
+| WebAssembly | `node wasm/test.mjs` | the compiled engine on golden fixtures, both the committed build and a fresh build (CI) |
+| UART interface | cocotb test of the FPGA top | the engine driven over its serial protocol, bit-exact |
+| Web app | Vitest and Playwright | the quantizer and tile order against shared fixtures; every page in a real browser; the visualizer at phone width and in both colour schemes |
+
+---
+
+## FPGA
+
+The engine behind a UART (`fpga/ecp5/fpga_top.sv`) synthesizes for the ECP5 LFE5U-85F (CABGA381, speed grade 6, the ULX3S
+chip) with Yosys and nextpnr. `bash .tools/fpga.sh && bash fpga/ecp5/synth.sh` runs the flow; the `fpga.yml` workflow runs it in CI.
+
+| Resource (LFE5U-85F) | Used | Available | Share |
+|:---|---:|---:|---:|
+| LUT4 positions | 40,416 | 83,640 | 48% |
+| Flip-flops | 8,726 | 83,640 | 10% |
+| DSP (MULT18X18D) | 51 | 156 | 33% |
+| Block RAM | 0 | 208 | 0% |
+
+Synthesis reports no inferred latches. The fmax estimate from nextpnr is **11.5 MHz**, below the 25 MHz board clock, so
+**timing is not closed yet**. The estimate has no pin constraints. The memories read combinationally, so they map to LUT
+RAM rather than block RAM. Moving them to block RAM changes the cycle model. The UART interface is verified in
+simulation; the design has not run on a board yet.
+
+---
+
+## Quick Start
+
+Requirements: Verilator 5.036, Python 3.12 with cocotb, Node 22 with pnpm, and Emscripten for the WebAssembly build.
+The repo's `.tools/` scripts install the native and FPGA tools into the project.
```bash
-bash .tools/setup.sh && bash .tools/emsdk.sh # Verilator 5.036 + Emscripten (project-local)
+git clone https://github.com/tinycomputers/tiny-attention.git && cd tiny-attention
+bash .tools/setup.sh && bash .tools/emsdk.sh # Verilator 5.036 and Emscripten (project-local)
python3 -m venv .venv && .venv/bin/pip install -r sim/requirements.txt
source scripts/env.sh
bash sim/lint.sh # Verilator -Wall on every module
bash sim/run_unit.sh # cocotb unit suites
-SEEDS=200 bash sim/run_e2e.sh # engine vs golden, end to end
-pytest sim/golden -q # golden model, fixtures, generated tables
+SEEDS=200 bash sim/run_e2e.sh # engine against the golden model, end to end
+pytest sim/golden -q # golden model, fixtures and generated tables
-bash wasm/build.sh && node wasm/test.mjs # WebAssembly engine + golden fixtures
+bash wasm/build.sh && node wasm/test.mjs # the WebAssembly engine on golden fixtures
-cd web && pnpm install && pnpm dev # the site (dev server; see note below)
-pnpm build && pnpm test:e2e # production build and browser tests
+cd web && pnpm install && pnpm build && pnpm test:e2e # the site, production build, browser tests
```
-Note: `astro dev` hangs without output in some environments. `pnpm build` followed by any static file server over
+`astro dev` can hang without output in some environments. `pnpm build` followed by any static file server over
`web/dist` works.
-## Docs
+---
+
+## Repository Structure
+
+```text
+tiny-attention/
+│
+├── rtl/ SystemVerilog: arithmetic, tile core, memory, controllers, top level
+│ ├── tiny_attn_top.sv top level: FLASH or NAIVE, K/V overlap, debug ports
+│ ├── attn_tile.sv tile core: MM1, scale and mask, online softmax, MM2, O accumulator
+│ ├── flash_ctrl.sv FlashAttention schedule and K/V prefetch
+│ ├── naive_ctrl.sv three-pass softmax through HBM
+│ └── lut_pkg.sv exp2 and reciprocal tables (generated)
+│
+├── sim/ cocotb tests, golden model, runners
+│ ├── golden/ Python golden model: fixed.py, float_ref.py, traffic.py, schedule.py
+│ ├── test_e2e.py engine against the golden model, end to end
+│ ├── test_fpga_uart.py the FPGA top over its UART
+│ └── perf_baseline.json pinned busy cycles
+│
+├── wasm/ Verilator and Emscripten build, C++ harness, Node test
+├── fpga/ecp5/ UART wrapper, ECP5 synthesis script
+├── web/ Astro and React site: /app visualizer, /docs pages, tests
+└── docs/ ARCHITECTURE, PLAN, STATE_SCHEMA, media
+```
+
+---
+
+## Documentation
+
+| Page | What it covers |
+|:---|:---|
+| [What is attention](https://tiny-attention.vercel.app/docs/what-is-attention) | Queries, keys, values, the score matrix, causal masks. A live float demo. |
+| [Online softmax](https://tiny-attention.vercel.app/docs/online-softmax) | The running max and sum, the rescale, and the engine's row state recorded tile by tile. |
+| [FlashAttention](https://tiny-attention.vercel.app/docs/flashattention) | The tile schedule, why it saves memory traffic, and the byte counts of both schedules. |
+| [Fixed point](https://tiny-attention.vercel.app/docs/fixed-point) | Number formats, the exp2 unit, INT8 quantization with a live demo, and measured error. |
+| [Architecture](https://tiny-attention.vercel.app/docs/architecture) | The blocks of the RTL and how they connect. |
+| [Verification](https://tiny-attention.vercel.app/docs/verification) | Every check, the command that runs it, and the measured results. |
+
+The design plan and the status of each phase are in [`docs/PLAN.md`](docs/PLAN.md). The full architecture, with the
+measured schedule and traffic models, is in [`docs/ARCHITECTURE.md`](docs/ARCHITECTURE.md).
+
+---
+
+## Roadmap
+
+| Status | Item |
+|:---:|:---|
+| ✅ | RTL FlashAttention engine (FLASH, NAIVE, K/V overlap), bit-exact against the golden model |
+| ✅ | WebAssembly build and browser visualizer with four views |
+| ✅ | Docs site, landing page, and FPGA synthesis for ECP5 with a UART interface |
+| 🔲 | Timing closure at 25 MHz on the ECP5 |
+| 🔲 | Block RAM for the HBM model and the K/V buffers (changes the cycle model) |
+| 🔲 | ULX3S pin constraints and a run on the board |
+| 🔲 | Larger head dimension (`d` = 8, 16) by accumulating MM1 over d-chunks |
+| 🔲 | Decode mode: one query row against a growing K/V cache |
+
+---
+
+## Related Projects
+
+Part of the [TinyComputers](https://github.com/tinycomputers) series of small, real hardware projects, each verified against
+a reference model and visualized in the browser:
+
+- [tiny-gpu](https://github.com/tinycomputers/tiny-gpu): a SIMT GPU simulator in Python
+- [tiny-tpu](https://github.com/tinycomputers/tiny-tpu): a 4 × 4 systolic array in SystemVerilog, running in the browser
+- [tiny-compiler](https://github.com/tinycomputers/tiny-compiler): compiles one tensor program to tiny-tpu tile schedules or tiny-gpu kernels
+
+---
+
+## Contributing
+
+Issues and pull requests are welcome. Run the checks in the Quick Start before opening a pull request. The RTL, the golden
+model and the generated tables must stay in sync: `pytest sim/golden` fails if they drift.
-Six pages with live demos: [what attention computes](https://tiny-attention.vercel.app/docs/what-is-attention),
-[the online softmax](https://tiny-attention.vercel.app/docs/online-softmax),
-[the FlashAttention schedule](https://tiny-attention.vercel.app/docs/flashattention),
-[fixed-point formats and error](https://tiny-attention.vercel.app/docs/fixed-point),
-[the architecture](https://tiny-attention.vercel.app/docs/architecture), and
-[verification](https://tiny-attention.vercel.app/docs/verification).
+## References
-The plan and the status of each phase are in [`docs/PLAN.md`](docs/PLAN.md). The architecture, with the measured
-schedule and traffic models, is in [`docs/ARCHITECTURE.md`](docs/ARCHITECTURE.md).
+- Tri Dao, Daniel Y. Fu, Stefano Ermon, Atri Rudra, Christopher Ré. *FlashAttention: Fast and Memory-Efficient Exact
+ Attention with IO-Awareness.* 2022. [arXiv:2205.14135](https://arxiv.org/abs/2205.14135)
+- Maxim Milakov, Natalia Gimelshein. *Online normalizer calculation for softmax.* 2018. [arXiv:1805.02867](https://arxiv.org/abs/1805.02867)
## License
-MIT
+MIT. See [LICENSE](LICENSE).
diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md
index 4e7dd83..ff1be51 100644
--- a/docs/ARCHITECTURE.md
+++ b/docs/ARCHITECTURE.md
@@ -388,9 +388,9 @@ Checks: `node wasm/test.mjs` (engine vs golden, now including the K/V buffer in
browser checks use `astro build` + `astro preview`.
**Committed engine build.** `web/public/tiny_attn.{mjs,wasm}` are tracked: the Vercel build has no Emscripten, so `/app`
-needs the files in git. Rebuild with `bash wasm/build.sh` after any RTL or harness change and commit them with it. CI does
-not yet check that the committed files match a fresh build; the wasm workflow's `node wasm/test.mjs` checks behavior, not
-the files.
+needs the files in git. Rebuild with `bash wasm/build.sh` after any RTL or harness change and commit them with it. CI runs the golden
+fixtures against the committed files and against a fresh build. It does not require the bytes to match: the same sources
+built on different machines do not always give identical `.wasm` bytes, so behaviour is what is checked.
## 13. Phase 8: the other levels (L1, L2, L4)
diff --git a/docs/PLAN.md b/docs/PLAN.md
index ef4857a..d8d8102 100644
--- a/docs/PLAN.md
+++ b/docs/PLAN.md
@@ -4,8 +4,8 @@
> running live in the browser.** You can watch the online softmax keep its running max and sum,
> watch SRAM tiles fill and empty, and see why FlashAttention moves so much less data than standard attention.
>
-> This is the fourth project in the series, after [tiny-gpu](https://github.com/deaneeth/tiny-gpu),
-> [tiny-tpu](https://github.com/deaneeth/tiny-tpu) and tiny-cpu. It takes over the toolchain tiny-cpu
+> This is the fourth project in the series, after [tiny-gpu](https://github.com/tinycomputers/tiny-gpu),
+> [tiny-tpu](https://github.com/tinycomputers/tiny-tpu) and tiny-cpu. It takes over the toolchain tiny-cpu
> already proved:
> `rtl/*.sv → Verilator → C++ harness → Emscripten → web/public/tiny_attn.wasm → React island → SVG`.
@@ -526,7 +526,7 @@ cd web && pnpm lint && pnpm typecheck && pnpm test && pnpm build
## 13. First week, concretely
-1. Create `deaneeth/tiny-attention`. Copy `.tools/`, `scripts/env.sh`, `.editorconfig`, the workflow skeletons, `web/` config
+1. Create `tinycomputers/tiny-attention` (done). Copy `.tools/`, `scripts/env.sh`, `.editorconfig`, the workflow skeletons, `web/` config
and `wasm/emscripten_stubs.cpp` from tiny-cpu. Write `CLAUDE.md`, `AGENTS.md` and `CONTEXT.md`.
2. Spike: two independent `counter.sv` model instances in one WASM page; get CI green.
3. `float_ref.py` + `fixed.py` exp2 and recip with `gen_luts.py`; the first numerics plot (exp2 error vs LUT size).
diff --git a/docs/media/logo-dark.svg b/docs/media/logo-dark.svg
new file mode 100644
index 0000000..a35fbc3
--- /dev/null
+++ b/docs/media/logo-dark.svg
@@ -0,0 +1,22 @@
+
diff --git a/docs/media/logo-light.svg b/docs/media/logo-light.svg
new file mode 100644
index 0000000..b6c0504
--- /dev/null
+++ b/docs/media/logo-light.svg
@@ -0,0 +1,22 @@
+
diff --git a/wasm/build.sh b/wasm/build.sh
index f5ac9ef..a79cc0e 100755
--- a/wasm/build.sh
+++ b/wasm/build.sh
@@ -5,16 +5,17 @@
set -euo pipefail
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
-OBJ="$ROOT/wasm/build/obj"
-OUT="$ROOT/web/public"
+OBJ="wasm/build/obj" # relative to ROOT: the build must not depend on where the repo lives
+OUT="web/public"
+cd "$ROOT"
VROOT="$(verilator --getenv VERILATOR_ROOT)"
[ -f "$VROOT/include/verilated.h" ] || { echo "VERILATOR_ROOT not found: $VROOT" >&2; exit 1; }
RTL_TOP=tiny_attn_top
-SRCS=("$ROOT/rtl/lut_pkg.sv" "$ROOT/rtl/tiny_attn_pkg.sv")
-for f in "$ROOT"/rtl/*.sv; do
- case "$f" in */lut_pkg.sv|*/tiny_attn_pkg.sv) ;; *) SRCS+=("$f") ;; esac
+SRCS=("rtl/lut_pkg.sv" "rtl/tiny_attn_pkg.sv")
+for f in rtl/*.sv; do
+ case "$f" in rtl/lut_pkg.sv|rtl/tiny_attn_pkg.sv) ;; *) SRCS+=("$f") ;; esac
done
echo "[1/2] Verilating ${RTL_TOP}..."
@@ -25,13 +26,16 @@ verilator --cc "${SRCS[@]}" --top-module "$RTL_TOP" --prefix Vtiny_attn_top --Md
echo "[2/2] Compiling to WASM..."
mkdir -p "$OUT"
+# -ffile-prefix-map strips the checkout and Verilator install paths from __FILE__ and debug info, so the same
+# sources and toolchain give the same bytes on any machine (CI checks this against the committed engine).
em++ -O2 -std=c++17 -DVL_IGNORE_UNKNOWN_ARCH \
- -I "$OBJ" -I "$VROOT/include" -I "$VROOT/include/vltstd" -I "$ROOT/wasm/harness" \
- "$ROOT/wasm/harness/harness.cpp" "$ROOT/wasm/harness/bindings.cpp" \
+ -ffile-prefix-map="$ROOT"=. -ffile-prefix-map="$VROOT"=verilator \
+ -I "$OBJ" -I "$VROOT/include" -I "$VROOT/include/vltstd" -I "wasm/harness" \
+ "wasm/harness/harness.cpp" "wasm/harness/bindings.cpp" \
"$OBJ"/Vtiny_attn_top*.cpp \
"$VROOT/include/verilated.cpp" \
"$VROOT/include/verilated_threads.cpp" \
- "$ROOT/wasm/emscripten_stubs.cpp" \
+ "wasm/emscripten_stubs.cpp" \
-lembind \
-s MODULARIZE=1 -s EXPORT_ES6=1 -s ALLOW_MEMORY_GROWTH=1 \
-s EXPORT_NAME=createTinyAttn -s ENVIRONMENT=web,node \
diff --git a/wasm/test.mjs b/wasm/test.mjs
index c8c5c65..d4cfe47 100644
--- a/wasm/test.mjs
+++ b/wasm/test.mjs
@@ -8,7 +8,9 @@ import { dirname, join } from "node:path";
import { fileURLToPath } from "node:url";
const root = join(dirname(fileURLToPath(import.meta.url)), "..");
-const { default: createTinyAttn } = await import(join(root, "web/public/tiny_attn.mjs"));
+// ENGINE_DIR selects which build to test (CI tests the committed files before rebuilding them); default web/public.
+const engineDir = process.env.ENGINE_DIR ?? join(root, "web/public");
+const { default: createTinyAttn } = await import(join(engineDir, "tiny_attn.mjs"));
const mod = await createTinyAttn();
// engine runs only; quantize_cases.json holds quantization references for the web tests
diff --git a/web/public/favicon.svg b/web/public/favicon.svg
index ce622ea..f0aeee5 100644
--- a/web/public/favicon.svg
+++ b/web/public/favicon.svg
@@ -1 +1,19 @@
-
+
diff --git a/web/public/tiny_attn.wasm b/web/public/tiny_attn.wasm
index 6de0941..fa4fc79 100755
Binary files a/web/public/tiny_attn.wasm and b/web/public/tiny_attn.wasm differ