From 682f172732ed0d6c26d9ebd590971a5de1b140c4 Mon Sep 17 00:00:00 2001 From: Dineth Hettiarachchi Date: Tue, 27 Oct 2026 06:49:30 +0530 Subject: [PATCH 1/6] docs: rewrite the README with the logo, verified figures and links; add a WASM freshness check - README in the series style: logo (light and dark), badges for the four CI workflows, what FlashAttention is, hardware, the traffic, cycle and accuracy results from the golden model and baselines, the verification table, FPGA numbers, quick start, repo map, docs, roadmap, related projects, references. - Logo and favicon: the 4x4 causal tile grid in the visualizer's colours. - docs/PLAN.md: series links point at the tinycomputers org. - wasm.yml: fail if a fresh build differs from the committed web/public/tiny_attn.{mjs,wasm}. --- .github/workflows/wasm.yml | 5 + README.md | 327 +++++++++++++++++++++++++++++++------ docs/PLAN.md | 6 +- docs/media/logo-dark.svg | 22 +++ docs/media/logo-light.svg | 22 +++ web/public/favicon.svg | 20 ++- 6 files changed, 349 insertions(+), 53 deletions(-) create mode 100644 docs/media/logo-dark.svg create mode 100644 docs/media/logo-light.svg diff --git a/.github/workflows/wasm.yml b/.github/workflows/wasm.yml index 3099e57..ad44c8d 100644 --- a/.github/workflows/wasm.yml +++ b/.github/workflows/wasm.yml @@ -58,6 +58,11 @@ jobs: - name: Build engine WASM run: bash wasm/build.sh + # The committed engine (web/public, served by the site) must be the build of the RTL in this commit. + # Fails if a fresh build differs from the committed files: rebuild with `bash wasm/build.sh` and commit. + - name: Committed engine matches a fresh build + run: git diff --exit-code -- web/public/tiny_attn.mjs web/public/tiny_attn.wasm + - name: Engine golden fixtures (Node) run: node wasm/test.mjs diff --git a/README.md b/README.md index 7a1ef6b..8dff424 100644 --- a/README.md +++ b/README.md @@ -1,81 +1,310 @@ -# TinyAttention +
+ + + 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 visualizer running one FLASH pass](docs/media/visualizer.png) +

+ 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/deaneeth/tiny-gpu), -[tiny-tpu](https://github.com/deaneeth/tiny-tpu) and 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. +

+ MIT License + RTL: SystemVerilog + Runtime: WebAssembly + Verified: cocotb and a golden model + FPGA: ECP5 LFE5U-85F + Frontend: Astro and React + TypeScript strict +

-## What it does +

+ RTL CI + WASM Build + Web CI + FPGA synthesis +

-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. +

+ Open Live Visualizer +   + Read the Docs +

+
-## Verification +--- + +## Preview + +
+ +![The TinyAttention visualizer after one FLASH pass: schedule, tile values, HBM traffic and the output](docs/media/visualizer.png) + +
+ +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; the committed build must match 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/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 @@ + + + + + + + + + + + + + + + + + + + + + TinyAttention + 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 @@ + + + + + + + + + + + + + + + + + + + + + TinyAttention + 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 @@ - + + + + + + + + + + + + + + + + + + + From 85e7045b393b16c70666400988d7044902ad67e4 Mon Sep 17 00:00:00 2001 From: Dineth Hettiarachchi Date: Tue, 27 Oct 2026 06:51:18 +0530 Subject: [PATCH 2/6] ci: pin Emscripten to 6.0.13 so the committed engine can be checked byte for byte The first run failed: the latest Emscripten compiled a different .wasm than the committed build (the JavaScript glue matched). 6.0.13 is the version the committed engine was built with. --- .github/workflows/wasm.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/wasm.yml b/.github/workflows/wasm.yml index ad44c8d..866b4ec 100644 --- a/.github/workflows/wasm.yml +++ b/.github/workflows/wasm.yml @@ -36,8 +36,12 @@ jobs: - name: Add Verilator to PATH run: echo "$HOME/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: From 270ddefd43b89ef02595222fc93b7a2133b249f0 Mon Sep 17 00:00:00 2001 From: Dineth Hettiarachchi Date: Tue, 27 Oct 2026 06:55:20 +0530 Subject: [PATCH 3/6] build: make the WASM build independent of the checkout path The committed engine embedded the local checkout and Verilator install paths, so CI's build (other paths) could never match byte for byte. Verilator now runs on repo-relative paths and em++ remaps the root and the Verilator install to fixed names. The same sources now give identical bytes from different directories (checked here), and the rebuilt engine passes wasm/test.mjs. --- wasm/build.sh | 20 ++++++++++++-------- web/public/tiny_attn.wasm | Bin 216175 -> 216018 bytes 2 files changed, 12 insertions(+), 8 deletions(-) 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/web/public/tiny_attn.wasm b/web/public/tiny_attn.wasm index 6de09417fd17a3e3005183b58cdb988db43bffcc..fa4fc790aca3b3241f2a2c3fe78c1b9ea5b9a4eb 100755 GIT binary patch delta 21171 zcmbtc4_s7L_J8-j0f7M-5kV1Q7!^?YpW^@IYMN#imbq=)u9;f?OHJ9bzcU&Yn^xGM zmkMhvGFq|Ng>9&1S4=G0C~?;|QYtDeD%!9gTU56GzURL8#z}2IyY1MU`@QF$bMCq4 zp8NL=eNi6uR~c1vSs3>mu3ZrJ2VKvM_}Uv8Ih?ob_2x#VQu6c2WOvJ6CbWF6*`1nj ztQ?_Ku-HJh%D2dJ??l?7TX&{WMtW3^&OV!x25w8!Ms_HH`pfc_m1Q4vL{u!CXPJG1Sf0IL1d3pBL$l1 zHWX;0&ocMfaK_xCzKUgP+DuykA;BhTny7M#8Xt4m?4l)A^recLG@*%)(nO;8gpm5G zO%k7_iD3>#JWR~(NfW(kB1t4WxUf0)QjJdKxm58thtNe|nkY^eDKJX?bLbYA%j7x` zBW$8HUHHmYSP8Xv*l7vxyS6#-j*&p(?#0ZTrpN4#5YZIyg&WXtM#(7AU^t^WL-b~d>P)oYgG@0x$Qiqn#PLiq2F?Hv z6J432J5ywc%+tQeB0Es1e9@CBPG*YiAUjNt<(OOtV#3H7S;Cj)eQn^7CzD-c5ssoCnGwk~KMl+Gsj;plR0@2Yl+u6{We}*Nqy!3X-E5V&Y8JuPO3; zxuPo9>x@%XeC7CeRSYrZKBuNDL%gfwhRlR%+Xh+`5q-94LruB*$a8VoL&%@rzr<~w zRXiD-PUff{iJWOk6f74f5; zyItsHd_586xZW8G#uRQBy?dDABc_kyCEpZZ>mEEHCc~4V($2ci7hE27I~W!6CS3$$N`$>3U!7$e#;yi z#8*!57gIHQJ3PCHclNJ+ID4<&?^|PPt(|vXg`U-`2D)H3!d*R8 zXaX$81kh9EjUPXZZLS?RekIE~86??fNOJNn7v;H1k_)gHl6_U) z*C&wVXA`ap%PM|`CR(FT&ojYwRvVI<0E;2%dB(fw0!VtdiEMr}&RcjeJn z@4frQNbj!c2|rl{|2i6GjPPDP zYSvDa`-U^T6F$T7HFs}c-nlfH z0cIM)gAMVnxg3@}bY&j@F4Eh0`8f^wPsF{qFDUqlRiDSAQS%7zHCLRoQ~#6Nd0m`R zk+krqtJvmHD%$@C6^9KKkNoVXTd|*f;i_GDedhjuN*K-4JiB@?LR`izB(^n#Z%+Ep~9X z{a}`?GbFhHiy_%o<(;*7>F`cV#HgXc;sf2_>%eTfcoav{xA;}}!vnE1UWg_2Z!u0L zY%3LhHVimyrQ>Iye@XOk1N$O@BMqMIgdGO{9pP95?;$+cz<(i}Xy6i5B%IJ=f0i-s z9*$$rDwpmpSQ2xtRx)GGFk6X(f@M8g=~=QltJ`WN9Mfeu#uRBa8q5S(j0SgAd2^O- z&e|HZZj>17hE+?h>IEjiVu+MHMB?p&6{k^E`d&Jb(}4kgzvx~^XcZ^*A*zN)l; zY{NL=bcU`fO6hNhr+861Zx~k_dF#hie_r zPgKJf$zIQ{G(yiM*H-U(I+a>`MRR`tL8`SetBOJdPFUEX8qM|n{rnix($S^NS^9}G zXZL5yoaF9w?{%LlbLu`-m5u&PwJK1qh}*kYk*QHPIm5l#pQU)yKi}mp-Af(k;@CCN zuqfGU9-loH=ra$)jsrL^;6$s=Z;ZBdTh;j+& z-wsX3g~f$Yq@K7<385?%T}{}(YK{k4g6MAYKKt$9Omm-ZGxzL{U$}6=fT*%jFQ!@B zc(d61f8UP3`T>_yr@8A1ngE=-v1Q)`A}DspQaF686aQg%?7}&JH*-9~oEi*5LG#n9 zS0iR&;BQqJEF}sUB=0@nrC((bTnrNj3sD+&uqbZwA*KvLMiOTz#LzeV!bAk_5NH~N z0hm9cSK`=M@w7MWWYX|nI0esHZp2NFLF-g*oY@(0<7o-QjgGBMv;RqFA#UzAV*~BL za3Fa$3*k;xquP?a-e*pxa)j04llNX~Fr--Flmag^EO9FGjDv_%TP66fG1B;4TY(NW z)LCc7po@4>oA>GO^Ehfa_Wh{5GgRAgbO6ao|Df{r8!1c$hN)^G#&GdoZ~ni=xx48^ zCr+w~8X~cDl-7l!qGLlwaoc!qGf?Ds9_iE|YQ#EeGJH)V9@E2rH!2Nhg~D6#pSR~< zadu;-z}XTl8gMGXZ46Vm!o#rs(}2TRHO3oy*%+ZN4lDAxgR&?!h9_DH2E^yB=mW%@ z*)29xW|b=ptyhlVmPbNYx%$naNy=%P6-Ck>XE0{J7gl zgx2j(v%H0YR^IlXdV|(QC+xb<9=(b(O8#*mz`6t`-2hs8qg6(a;HHR*YcG0-I|; zAzJjCpQ8S7Y8W}BE1J6Pw8WHii9ok0rXt(iYMh?LXpGolp%D+5iEAH z>F&E-%2HJ2Bw(jX?HOceO^}_yrk#Xfr{i(-cM-lf*oztGZv9ScdSph$@)DhG>~GGf z7VU`6$}O61^4)4}GH6;X$@xB7GASfMStZK~>3do*=z z+f-Saz+&CsW8AbMx*7*M28b?zy$-Bbnj=Q^B2(5M8IN0>(i{WDRv#1h2NOIXY$1Xs zPBAc)BmXJgDSAA2ic^YI3DWyS0?R2KJapY$LYL7Vd^QUADSY~YPPehAB0$%K8`)-L z4-E*gSzJdzewoMyR|m2U14P(#Ql>uc>BOo&nj=c}?s}bPN$C`w4zT zum+HceFWP8x%kImK+j7Wu4?Ze0$2rT6MrDs3K$~(O0e%Gyw9Lwwt-az!?qLFu#E5J1!0vRNq0-u9}t5`)v#Z*CNeL^E{kV=Y?9yeNiAEq=8K zxk>c9F+nXh}kEKysoyYR~n~LVr;*J`7@>pJE zMCHts0ZyK&`DXevrm-cQw?_N733dUa{(NgXbCGf1O=qF1gY*m8Qcfi+=CILJvV9KA z(~0ov#cVI78|SebsNmyy>^jChGv%e1vU1+mBtN;7RdU}!|AU2WGUq)P_**Vxi$aJs z^GY^0jDOMe^pb0^EP3YmmtBiK;ZHjK6-(H0s?LAcb?g?~z(djE?s(jeJ8yLL8!9Se z#MANq=v8b-Kfc51e|kN0Faj@ZU`tT*tNx^4v)OF?R`Y^DZT^rEJ$uoah#2=QdUIeG z*rg+|K*bdLBBx!uE~*;j&YRdmzO_pJ^CrZVZ?4R|nQb9cUc4ETD1fZKg$+wIv?H>y zGD3^TeWUQ~(`QBL@UT373wxC~Yi?z~;MEN>>NYkx;Yb5cYzXCa-rj(B5$Qb4rPIa% z<=5WEF64C$a?fpSjKQZ{d{{feT-@aAxs6>GPI^*w(byp8-p+=Q`V$YZA<}(2bB%6n zzqs?6a9$vcXzTv zvPE~Z`}#u-iE8s;Cn|i@&8|70RO?iSRT-Kewi0gu>~#35HzYW=rG#l~y2_lDV7Nv?=YtcwnqE6un7q=rFPxwHR1!U$7B8 zM};qMW@+vQ)1?BT=;ZT;(#{Gr)oscvq6bQ_#l}l#0M(lzy_oJsnDM3C9)|LlaVWG0W!AQd4Zy_FD1j z+D;U+jQrkxY!t^Y8XOqgXsp6vr`wNW6ojeEi-0fs_Q}idX4ku@q1d9kF@991>H^LZ z`b|YHu5NoUF52&At5e$lOv6R>hB**Iz^N6vLKPcEz>0fVqsxq2#N<5e3Mp~{&x+hk z8r8Sn%L)sPP;!RS$OJaFT87tDbnunxJ_3D!3yJmE?vdj#VpQai5RD!+E-;=jTxCKD z`&lU3Li54RCG5py7_QXA1=I#LoSn8P{2ByIFuU+KY_H)3+i5RQ+s^Pp@_nqx@B%I~ zF$hdAASRr)ATQM2#|{srIzwO`W0Q;0V>dG|Q34R6jFkn5rnmY4n^i@r|{UBWbE5 zC+0lJt{!59=N!y-7+@45Xf@i6`Rl0%*+|RBtq-zCX`Z{GlwG2HO_MQvr`k;&<7B6O zeYBKqbDLdmqu!$~SCe2+mutp^fVCUGr7lO~4EryP3Was%d@L;KrgvnVWOFP}ER(@w#b)#$ z&6-Xg=s*AYRs4G^3dMjT}G?ATTMH?*=ah95r2I}Wq8_R_z>ZL1NvC;#gI`Y`R&$`8m-{>G;8 z)*~|Q2)j3AQh4jf%n+=Y})pC$D_=JeUp=ROZ2lBO+>C1rEhC1&nOU2oXILq z4U>O)pN;28^$*zOoKwRbRSXv^7)OWV0F^pwO)PaFAF$zbx>V!yxf3PK*b<>9AWBpD`k}@FFAX`NU5Kx?HtoV2k@lErcOz5z;{7t> zLso)ayua*2wu{j=e*AHEg3{3+8RwO^Kf;L#y*c`0R?8_|-una>ZY2l(k)N_L{oEaI zYjDOaCoUiiBv{dUGN>04Fk?MuzAg2g`gEBqMA}=jXw4-74dr zMS@=h3;rWmFp{eOBv`J?$dK4)!D62Vi-j46o(LA~IW72q0l|q-^JS1Evi}Sg>OC#= zbwCJAU%f5Ye91;de-q5y_qKocmuz@!D4Z%zzU@C0$`>=<@wPuNjOQ{trV3i)L+Ir6 zMI;|4w}kUy2?iB!VKF7+r%O)UjIl_a4zO}Y6i@em8qUAvXUb0cH)4K>P1P-lNK&-L z!7FK%${oOSDHfIv;ANPK4*I{1;i3JMx!=C+4^7}h1GcAS@#~fCrP(~h&QB#tIfXX7FE<0y%@Hl7fAdhk~RFcxpt)Lu|5!Q8hhG7R=zW{%bGbdvwet{^;5KI&LpJ zVBYPO9*~}k`0$WD@if0xUBvU;7$qqfKG;apfWfWlE(}mkgT^8Q2FO|pUO4S1Z5XvW z<^*-a0%eXWtS%T~pwOBmLUEd~6RUA&SPXzt^d!wm1WoODW7mk^2AH*i&8iinbi-Ue zooqK$lpgRm&gBbj{E>M7;6gqqirSa`OFlG&C>LML7e@{#IjW^=TE5oL*-vabs*Td5 zb0d#Ww6ApBjom>c&4m3#=}{WIlQbLf)}#ItYj7M*Z9b-r(zWsWU}vakG_eD?-o#J#LBxAFNw#`g;{zF&~>u=AK70!6m|-wjhP zx)&QN(L2n4mxoVr^lyHNIqJE3bt@WQlCPI?uf^y$8P{y%zmDc@FUjLRzLvMXBp2@D z)A$Q9^28pV8rHy9Vh8?0jQ`*+K03lAXCU<_SM$vg=T~)7&(rmOn4^c|J(B@{i=|bw zPof`7S0147z%f-rOkrb==)z8kR`yCvzt8jEg&Q2W&6E%O`S78EtdisE9b*WcI;#%I zxBPrUc$Lyf{DbTH-XQTe>iO_-5J!<2l8_;kg7?07J(vbSGqQF4oNBv}NHJ&8LQ~3S2X_eZwD^syw*VIWk$*~{zRM4|0-tjmm zrwX4t(8}sJu&w(-iPr!sK(wCy%<+=$IG~F<T07pA=Mj(j}i=*=m zhJd09sk9@&+l5^5r%Ic<+hQhSSAeAlx$e&tORvR2w7vjGALR@sCoK-5^#nLPhfry^ z(oqbk0MR@Lnd#B8LvFJVOF@6OO`D9X;}XC&H6+AVORT~IR9F&_uQ_CjSA(3PdLF=Y zTvb(xLc09w2%+1rdP{>3pfsSt-GFSp(W#9V3(-6n^+vZhAy@OIQrT>A5UnJ@(SlrY zui|L6IEYpn;5dR@&p#DMo5ex2a(95G9oZw_E0zw6g=mcdj!xvd{-rp&EDoa41Z9}o zja>UF#nEGN5Un-9(Mvgx+VJ)fhq@diVq1XaByvrg6pIJ1vy?WXbp$wyLGIqHI7%!I zqICs0wjx(ntT;+74x;r0ILeS~xSOf^%Pkfn_61lfk?ZEoP!s1(@rQ^Qi%cKSVKEFj7)*6@pydcS=n|E9P5@DWcT`WE+t)WJMDo zaTuahaO9+tXtqR&Rv!>;L9X*2<&{=b)GdhUIl#;wJc3;Ny^5vHWD!Iw32E zYT=uscw493{Wf=U&vDuOHoq$BFEN;Sjg{1QTxK8Tlilx}CePjH5s-)WTDEuPOeDWv~@+jPLhDTALJlYJVIxCiHEQfZyZaEalbZ8?g z@@%0l$NU3~8*1wQHoztWb^^8=um`Z$fF}Wqw<_L}7Bt0xWq{QN^Z_;-uo|$(fc1c- z_bag`z&ZoA0=Bsg+z#Ajz%IZ(1NH)zJfP$}u-!CbD`2@nEeEVJU?pI^0c!x84cGwK zX252^E(0C`>@%Pn4l`mG4ioSTc^V0M8vZ)N(-3L(IszKjs`Lv-GiF8 z#;ZsJ`i(-6G-`yTaD#)YM52bv8)sID0#)Tg{3P0{veBxtDo`bmS!F3u$%D#i6@X<1 zgsf31WCd7MK)u22ZnP7!XS5Rxs?r2a98{IkJrs~xMOhWIN8mSFRSdIPf_)_Z`ZK`Q z46YV6krUAr0FeZcC;_N_(-d$Q5H#6*hF&C=-p+vDHgNSAdO?iB*BS_+_it`oLE95iLOiL{kDpl0ahBP!%VS58~YSpuFQl-qeg6-v;}g5pFQJIkqkaGt zg%t(66Z)x%U3?x~h*ewh?4%#^e%>kUYXpzk>I=cFGsN;>(jd9;f3nd{X`xbG&MsnWA1RYAN&^Iq*#T8aGKuult%8 zIJ$Nci%Z7>jnbCKtdDnwM!xT`Prz#- z2o_h$;#2&(QGKdaJIJsn+8c+pkK*pYVj)J*nFV>ihki=}+?To0hF#v3haQ z+V%e9y6$8_`I#a5Lt=WE)P_=7|G*zl|~aYKwEY29Y(+|gf8j%X~@wA;Z^JwT4? zuji?JJ@Q=xes#8;3y@_Ieg(1*O)E^PCD=)q_ zccxNL+8a>ZHHdm+f_I845IECAseA2gjzj99*WV^6fBhA0Y;Ili{n&#wAnIy zpgtysNDUy7_h%z5oGbqo11+Tk^-y;a2v$E(Avs9(1O8~-{sE`%vB*}D)eS_qx`E0{ z;1G+~@;&+8q7jefcQSrmyr#Kxa~G^wbBib{T66h|8(<(ATZ#huEJs>+1_PDXfKH83 z<#T_?fVSMU4rLzjSnVQ=HjzpdZ(XSV;p1yd)GBQZhx>3Fm>Cf<7iRb5duEIll z`%pIGVokf`@}lz=Ey~NwS+r=w^2O`rQ$zJ!75oMA*ie0jf+Ylp$SH}YsjbO!b)r5? zl_knmiF$%OoTz82W-NEgjA8mP`w|*<6q2Qi;8KP4X5n(Hu)`^DAEvu_dEt&>dP+o* zMbDA14bz8%EDJ%mt0{t%c~WFzlH!*>D!9QCXibr;&4M1%bcTt+7Ack@jzOK-BCdf6Xe*6b`&6R#j0gxOBh)K_>UK@Hz1`zP@@G_mOw@I-@b zqc1JoLErSJODKB|Wh_P$e|$yaXO_d0zeK)&0*Vt7=o=sW8Pk)^V2TBEvz+JD6C&QB zd>sDg9nJn5ow}VTcv3a30KC`XxgHPo*jCVd^W_c~+V4w~FS_*e`Ra7}Em9UKCl1#q zt2L=84V^9T7_Mh4msh39-w)UGV`x_HMG<|}Ku?q3TJ$Nn&kv$cAED=~INb<3YT7(P zzl0^q!z1*8^AbmC+I`^Nf~T3pkdMXF4m^Q$;4=bvE7JS%G~-9D^bqX<)(z*3O6;VAnJxJXB3P z_}kOuM0a_~Kjhhpm*}2unu!g{n{rdCp2r*C z^#37MzY;Hd+hlaQ9-rvUK+_>RBkPI-nL+ zqndpalWQ`tSaqlCOL)yOIe(O%7UvkRX?vie8qZ!l^eGxI9~h-)a$mduPos35hgEiJ z_Ro1szHA<&XYs1>vTuxz|KrbizcWK0#HLkztl7U$t3FTD4nevV4{4&t*Mp{A9B5Sj z>a|N2uUKC!YWQ=c}3qGRk>gT#@LI!b5DmP~!9xfph5>dCx(tc=Ri zM<=LN{u~nRW9595tJv!tD>r57Ni0M9vh*=ZR{{}{gDup_EIpq4#>qk1rvAo% z!@MqAk7u#6B->OxHvh*I50=NX^*COVFTb}GSN$ln(Xj4wm!n~E^3fbU)=lnBMCXy8 z%$Tvn6xIbM;B83ro#$9&FQ*2{Pq8IA>a(K2}XN-6j*s+a}7~ae8d@^~;J>!;Rqm6c7BpIFMO&2m!K zG>I%69H|y9H5s%_k}JmQE=+q{$C?VK;6O>A;C6c^%QwfG)|H#(rxoHyFLJ>+J$t%s z3Z@CrFTgVs&n!F_;+c(S4j$_24DirL$72n@BG9P1(kb$AK1N9UIDNc}=BnFKV8-3V zq7^qRGbbx$difMNE?;dpuF2Q4vF|9&*OOgT#e}I;!MUsOOr(F2>5%@p^LR((^U#mk|4vsbZ0-q8N1g zoI^#<1brGDw{C)do|>0NOgo#kKo(Balg8xG=AQg98$6~za&vB2yJp!fmPZWP@@X<; zqMoPp)&H2jG%TYVCz`cp6#Pi7L*;uD^+@h1ke^!`eLu?E|K`961Y@&6E}o>vDI@yM zDN;H~zf|?WY$5NTOjF_{-Ja_a#@xQe^dbJrR=eNyqYq{D3HD=`gq|UC{$wmY9^02Y^t81>aUn_cKvlT6O-BQ6E^CGx}^eW08&TaQ;vSAqzp zU(VL^Q!0UdFmV^2C-6LprxFiM_BA9d>t^d?+@*jWcy7`zSVQfhd=H*$O?(n~iHXA? zy4=LEz>7`n1m0lc4B#RYPXJzF;tPORnz#^nwSlK;S0i(SS+E>#2G&*{8}T7TvOL*`gI|u3L+n;HMCNc=d(9F0)79@KN`> zA)1}IotlQf$?T@mMcAP3#?|OALB1N#;H&UjaJL&JxLK@5Nf>t1>Vy3$vf*_0p$`-k zKMl5Pz>l;)DOijr{VIH62-Hy_>TW_BLJH`<7B|nS8!ty>EyPi5InzeokUer~_DJgA Tu#vMTjhr`WBucd~zLWhw16?$$ delta 21489 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 From 0f8e8065372c2181ee6d72809e225a7fab8741d5 Mon Sep 17 00:00:00 2001 From: Dineth Hettiarachchi Date: Tue, 27 Oct 2026 07:02:04 +0530 Subject: [PATCH 4/6] ci: temporary diagnostic for the engine freshness check --- .github/workflows/wasm.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.github/workflows/wasm.yml b/.github/workflows/wasm.yml index 866b4ec..b597f3b 100644 --- a/.github/workflows/wasm.yml +++ b/.github/workflows/wasm.yml @@ -64,6 +64,11 @@ jobs: # The committed engine (web/public, served by the site) must be the build of the RTL in this commit. # Fails if a fresh build differs from the committed files: rebuild with `bash wasm/build.sh` and commit. + - name: TEMP diagnostic (remove after the freshness check is green) + run: | + echo "paths in CI build:"; strings -n 6 web/public/tiny_attn.wasm | grep -E "/home|/runner|/media|/usr|verilator|emsdk" | sort | uniq -c | sort -rn | head -20 + echo "size: $(stat -c %s web/public/tiny_attn.wasm)"; sha1sum web/public/tiny_attn.wasm + - name: Committed engine matches a fresh build run: git diff --exit-code -- web/public/tiny_attn.mjs web/public/tiny_attn.wasm From 8eb78dc88b9b1d3e9438d3fa48691c97ec0db8d0 Mon Sep 17 00:00:00 2001 From: Dineth Hettiarachchi Date: Tue, 27 Oct 2026 07:04:02 +0530 Subject: [PATCH 5/6] ci: build Verilator the same way as the project (.tools/setup.sh), so the engine matches byte for byte The runtime's version string is compiled into the engine. The configure-based build in CI reported a different version than the CMake build used locally, so the committed engine could not be reproduced in CI. Removes the temporary diagnostic step. --- .github/workflows/wasm.yml | 29 ++++++----------------------- 1 file changed, 6 insertions(+), 23 deletions(-) diff --git a/.github/workflows/wasm.yml b/.github/workflows/wasm.yml index b597f3b..dca4478 100644 --- a/.github/workflows/wasm.yml +++ b/.github/workflows/wasm.yml @@ -13,28 +13,16 @@ 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. @@ -64,11 +52,6 @@ jobs: # The committed engine (web/public, served by the site) must be the build of the RTL in this commit. # Fails if a fresh build differs from the committed files: rebuild with `bash wasm/build.sh` and commit. - - name: TEMP diagnostic (remove after the freshness check is green) - run: | - echo "paths in CI build:"; strings -n 6 web/public/tiny_attn.wasm | grep -E "/home|/runner|/media|/usr|verilator|emsdk" | sort | uniq -c | sort -rn | head -20 - echo "size: $(stat -c %s web/public/tiny_attn.wasm)"; sha1sum web/public/tiny_attn.wasm - - name: Committed engine matches a fresh build run: git diff --exit-code -- web/public/tiny_attn.mjs web/public/tiny_attn.wasm From 798c73ae835d7d774d0d377ee4273acaee92be5f Mon Sep 17 00:00:00 2001 From: Dineth Hettiarachchi Date: Tue, 27 Oct 2026 07:09:53 +0530 Subject: [PATCH 6/6] ci: test the committed engine's behaviour instead of comparing bytes Byte-identical .wasm builds are not reproducible across these machines (CI and this laptop produce different bytes from the same sources, even with the same Emscripten and a CMake-built Verilator). CI now runs the golden fixtures against the committed files before rebuilding, and against the fresh build. wasm/test.mjs takes ENGINE_DIR. --- .github/workflows/wasm.yml | 13 ++++++++----- README.md | 2 +- docs/ARCHITECTURE.md | 6 +++--- wasm/test.mjs | 4 +++- 4 files changed, 15 insertions(+), 10 deletions(-) diff --git a/.github/workflows/wasm.yml b/.github/workflows/wasm.yml index dca4478..d154637 100644 --- a/.github/workflows/wasm.yml +++ b/.github/workflows/wasm.yml @@ -46,15 +46,18 @@ 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 - # The committed engine (web/public, served by the site) must be the build of the RTL in this commit. - # Fails if a fresh build differs from the committed files: rebuild with `bash wasm/build.sh` and commit. - - name: Committed engine matches a fresh build - run: git diff --exit-code -- web/public/tiny_attn.mjs web/public/tiny_attn.wasm - - name: Engine golden fixtures (Node) run: node wasm/test.mjs diff --git a/README.md b/README.md index 8dff424..cb0fa7e 100644 --- a/README.md +++ b/README.md @@ -175,7 +175,7 @@ Every row is a test in this repository, run in GitHub Actions on each pull reque | 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; the committed build must match a fresh build (CI) | +| 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 | 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/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