Repository navigation
fpga: wasm_board_ax7203 P5 -- one 64 KiB linear memory page, ten memory opcodes (Closes #12202) - #12304
Conversation
…ry opcodes (Closes #12202) The board cell now executes i32.load (0x28), load8_s/u (0x2C/0x2D), load16_s/u (0x2E/0x2F), i32.store (0x36), store8 (0x3A), store16 (0x3B), memory.size (0x3F) and memory.grow (0x40) over one `var mem : [MEM_BYTES]u8` with MEM_BYTES = 65536. - Little-endian; a 2- or 4-byte access is a byte-per-step microsequence (mem_run) with one read site and one write site, both addressed by the register mem_addr; the extra steps spend no fuel (one unit per fetch). - The memarg is two LEB128 u32 (align ignored, offset kept); a fifth continuation byte answers ERR_LEB; scan_class steps over it, so a branch that skips a load with a two-byte offset ending in 0x0B lands correctly. - The effective address is a 33-bit sum; an access of width w traps when it exceeds 65536 - w (ERR_MEM = 10, RESP_BADOP on the wire) before any byte is written; no wrap, and the next request works. - load8_s/load16_s sign-extend, load8_u/load16_u zero-extend; store8 and store16 leave every other byte alone. - memory.size pushes 1; memory.grow pushes 0xFFFFFFFF for every delta, 0 included (a documented deviation from the WASM spec: it grows nothing). - Ten new cell tests (42 in all), placed before the P2..P4 page-load tests, which stay last. cell_runs, alu_unexecuted_bytes_are_opcode_errors, wire_is_the_node_link, machine_consts and cf_negative_controls updated (the unexecuted probe byte is now 0x29, since 0x28 is a class-6 memarg op). - wasm_opcodes: fn executed_beyond_alu grows from 21 to 31 bytes, and the executed-bytes law is renamed and re-pinned for 55 (24 ALU + 31). No row is added and no pinned const changes: the eight memory rows keep interp_ok = false, because the live interpreter has no memory. Measured: t27c test-report on the cell 42 of 42, 0 vacuous; on the table 16 tests pass, 1 invariant proved, 0 vacuous. Known gap, measured and not hidden: yosys 0.67 read_verilog on the generated cell answers "Replacing memory \mem with list of registers", so the page is not block RAM in this lowering (and read_verilog + hierarchy ran for more than ten minutes against 5.7 s for the base). gen-verilog emits no initial block for `[0] ** MEM_BYTES`, so an iverilog run reads X from an unwritten byte; the new tests store before they load. Both are t27c emitter gaps, not fixed here (this change touches only the two specs). Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
… with the Zig run (Refs #12202) The first form of mem_fits summed three `as u64` casts and compared the 33-bit result with MEM_BYTES. The Zig run passed (42 of 42), the vector-level Verilog run did not (41 of 42): gen-verilog drops the u64 casts and emits `((base + off) + w) > MEM_BYTES` over 32-bit inputs, so 0xFFFFFFFF + 1 wrapped to a legal address and mem_access_past_the_page_traps lost its wrap cases (ten failed asserts). mem_fits now says the same thing with two 32-bit compares that cannot overflow: with lim = MEM_BYTES - w, the access fits exactly when base <= lim and off <= lim - base. mem_ea adds base + off without a cast, because it is only reached after mem_fits said yes. Measured after the change: t27c test-report 42 of 42, 0 vacuous; the emitted mem_fits carries the two compares; iverilog -g2012 elaboration 0 errors. The emitter gap itself is filed separately and not fixed here. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…e (Refs #12202) Both seals come from a master-built t27c run at the lane root (`t27c seal <spec> --save`, then `tri seals sync-twins`); the cell seal now records 42 of 42 tests, the table seal its new gen hashes. No path under a home directory in either file. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…to lane/wasm-memory-12202
Summary by t27-beesChecked by the t27 compilert27c parse, parse-complete and typecheck on each changed .t27 file at
Read by the modelThis PR adds a 64 KiB linear memory page and ten memory opcodes (load/store variants, memory.size, memory.grow) to the WASM board cell for AX7203. The implementation uses a byte-per-step microsequence for multi-byte accesses, little-endian encoding, LEB128 memarg parsing (align ignored), and bounds checking that traps with ERR_MEM before any write occurs. The page is shaped for block RAM inference but yosys 0.67 lowers it to 65536 registers; gen-verilog emits no initial block, so simulation reads X from unwritten bytes. Worth a look
Read: 4 files, +922 -52, the whole diff. Head |
…to lane/wasm-memory-12202
…to lane/wasm-memory-12202
…arity/alu_eval in the cell, the table oracle calls them (Closes #12011) (#12060) * fpga(wasm): the 24 single-clock i32 ALU opcodes have one home -- alu_arity/alu_eval in the cell, the table oracle calls them (Refs #12011) The cell's step_all took 24 hand-copied ALU branches; they are now one arm through alu_arity(op) / alu_eval(op, a, b), written once in wasm_board_ax7203. wasm_opcodes uses the cell and its eval_byte calls the same two functions, so the oracle and the cell cannot differ. Deviation from FR-005 of #12011 ("exist once, in the table spec"): they exist once in the cell spec. t27c gen-verilog imports only enums and structs from a used module, never a function body; the splice of used function bodies happens only in test-report / t27b test. The direction is forced by the lowering. Tests: cell 21/21, table 16/16 + invariant, 0 vacuous in both. Law 14 pins the cell's accepted byte set against the table's i32 ALU rows in both directions (24 bytes: 4 unary, 20 binary); law 15 pins the 37 executed bytes. Behavior change: eval_byte returns 0 for i64 rows (it used to evaluate their i32 twin) and for div/rem rows, as before. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> * fpga(wasm): seals re-saved for the cell and the table after the ALU one-home change (Refs #12011) t27c seal --save on both specs from the master-built binary; tri seals sync-twins reports 497 twinned specs already consistent, 0 written. Cell 21/21, table 16/16 plus the signed_order_holds invariant. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * fpga(wasm): park the cell spec and its seal at the merge base so master merges clean (Refs #12011) Master's P3/P4 const floors (i64.const 0x42, f32.const 0x43, f64.const 0x44) and the #12042 reseal landed on the same lines this lane rewrote for the one-home ALU, so a plain merge stops in two files. The pre-commit gate measures a hand-committed merge by its staged diff against the lane tip, where master's own Rust reads as lines added here, and that gate is never bypassed. So this commit puts specs/fpga/wasm_board_ax7203.t27 and .trinity/seals/fpga_fpga_wasm_board.json back to 09bcdb0, the next commit is git's own clean merge of origin/master, and the commit after it re-applies this lane's cell over master's, resolved by hand. The three together are the one merge; the PR's squash keeps only the result. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * fpga(wasm): re-apply the one-home ALU cell over master's P3/P4 const floors, 40 executed bytes (Refs #12011) Completes the merge of origin/master that the two commits before it prepared (the park and git's clean merge). Resolution by hand, master's bytes kept verbatim: the P3/P4 constants and their notes after OP_I32_CONST; the i32.const arm now also takes OP_I64_CONST; the f32/f64 arm sits between the const arm and the i32.mul arm, with its closing brace at the end of the chain; the two wire tests p3_i64_wire_programs and p4_f32_wire_programs sit before the #12011 block. The executed-byte count goes 37 -> 40 in both specs: cell_runs and executed_beyond_alu take 0x41-0x44 as one range, the sweep asserts 40 runs / 216 tried, law 15 is the_40_executed_bytes_are_all_generated_bytes with beyond == 16, the cost note says 14 other arms. The two seals are this lane's old ones for now; they are re-saved from a master-built compiler in the next commit. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * seal(fpga): re-save the wasm board and opcode-table seals after the P3/P4 merge, 23/23 and 16/16 (Refs #12011) The two seals are re-saved from a compiler built at master commit 19a89f1, whose compiler tree (bootstrap, cli, Cargo.lock, Cargo.toml) is byte-identical to origin/master at 7273ad3; the previous seals named 94adbb1 and predated master's gen-rust change (#12042, resealed on master in #12062). built_by, spec_hash and the four gen hashes move; the board seal's test count moves 21 -> 23 with master's two const-floor tests; the seals carry no home path. Run: t27c seal --save, then t27c test-report, both specs, 0 vacuous. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * tools(dupe): lower the duplicate-bodies ledger by the two groups this lane removed (Refs #12011) The table's sar32 and lt_s were copies of the cell's; this lane deleted them, so the groups are gone and the ratchet refuses a ceiling left above the real number. Re-blessed with tools/dupe_scan.py --bless: the diff is two deleted lines, no group added, none grown. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * chore(fpga): park the table and its seal at the merge base before merging master (Refs #12011) Master dropped the copied lt_s and sar32 bodies from wasm_opcodes.t27 in favour of an import (#11885) and re-sealed the table; this branch rewrote the same region. The table and its seal go back to the merge-base version for one commit so the merge of master is clean, and the branch's change is re-applied on top in the next commit. The cell is untouched. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * fix(fpga): re-apply the one-home table on top of master's lt_s/sar32 import drop (Refs #12011) The merge of master brought the table back to master's text, where the copied lt_s and sar32 bodies were replaced by an import (#11885). This branch removes every restated ALU body from the table (the ALU lives once in the cell, issue #12011), so the import is not needed either: the table is byte-for-byte the version this branch carried before the merge, and so is its seal. A fresh `t27c seal` from a master-built t27c on the merged tree reproduces every hash in that seal (spec, zig, verilog, c, rust; 16 of 16 tests); only the built_by and sealed_at lines would change, so the previous file is kept and the stacked lanes merge this branch without touching the table again. The cell and its seal are untouched by master. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * dupe: trigit verbs write through handoff's put_lit, one copy of the loop (Closes #12419) #12388 added put_lit to specs/tri/handoff/handoff.t27 with the same loop body as put_s in specs/tri/trigit/verbs.t27, so the Duplicate Body Ratchet went red on every PR: "put_lit was copied [2] and is now copied [2, 2]". verbs.t27 already reads handoff.t27 (put_span, create_record, ...), so the one body stays in handoff and verbs' put_s delegates to it. put_lit now takes []const u8, like its sibling put_span: gen-rust lowered `string` to &'static str, and verbs passes runtime byte slices. String callers get .as_bytes() / b"..." from gen-rust; the bytes written are unchanged. dupe_scan: 1849 bodies in 691 groups (was 1851 in 692), ledger unchanged. test-report: handoff 12/12, verbs 17/17, handoff_view 4/4, 0 vacuous. Seals for the three specs re-saved by t27c built at master 672aaca. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * seals: drop the stale FpgaHdmiGrab seal that #12430 re-added (Closes #12461) Master commit b1a5857 (PR #12430) added .trinity/seals/fpga_FpgaHdmiGrab.json as a new file sealed on 2026-10-20 against a shape of specs/fpga/hdmi_grab.t27 that #12059 split up this morning. Since then `coverage` (seal-coverage.yml) and `spec-guards` (check_seal_currency.py) are red on master and on every PR that merged it: one seal, all four gen_hashes stale. Master had no seal for this spec before that commit, so the file is removed; a fresh seal from a master-built t27c is a follow-up PR. Local check_seal_coverage.py on this branch: OK, 3079 seals, 2966 hold, 113 known-broken listed in seal_baseline.txt. Refs #12387 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * numeric: drop the dead braceless invariant the parser discarded in zphi_alu.t27, reseal (Closes #12464) Master commit b1a5857 (PR #12430) added specs/numeric/zphi_alu.t27. Its `invariant sign_conj_negation` is written in the braceless form with an `if` statement inside the body; `parse_bdd_clauses` does not lower that shape, so the parser restores its checkpoint and drops the whole body (61 tokens, lines 483-493). The Corpus Ratchet's `parse-no-discard` phase refuses such a file, so every branch that carries b1a5857 is red on `Corpus Ratchet`. The invariant was never checked, and it cannot be made live as written: with the file's own `sign` and `conj`, sign(3, 5) = 1, conj(3, 5) = (3, -5), sign(3, -5) = 1, so the asserted `sc == -s` is 1 == -1. The claim is not a Z[phi] theorem either (the conjugate of 1 is 1). The block and its comment line are deleted; nothing else in the spec changes. After the edit `t27c parse-complete --show specs/numeric/zphi_alu.t27` reports "nothing discarded" and `t27c check` passes. The seal .trinity/seals/numeric_NumericZphiAlu.json is regenerated in the same commit from a master-built t27c, so `coverage` and `spec-guards` stay green after the spec edit. No ledger entry, no parser change. Refs #12387 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * fix(seal): re-save the T27osFrameGate seal that #12467 left stale (Closes #12486) #12467 (e376a06, merged 2026-10-25T15:20:07Z) changed specs/t27os/frame_gate.t27 without re-saving .trinity/seals/t27os_T27osFrameGate.json, so tools/check_seal_currency.py reports one STALE generated-code hash on master and every PR with base master is red on spec-guards. Re-saved with a master-built t27c (0.5.2+672aaca7d, newer than master's last bootstrap/src change 2b1858e) from the branch root: tests 12/12, gen_hash_rust cebbd2f90560, gen_hash_zig 5877f763f823, gen_hash_c d7aba556f229, gen_hash_verilog 575cb51da761, the four values the check computes from the spec. The spec is unchanged; the seal carries no home path. Refs #11640 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * ci(elab): baseline rows for the five specs/fpga modules the #12430 merge started emitting (Closes #12491) b1a5857 (the #12430 merge, 2026-10-25T13:29Z) brought specs/fpga/ hdmi_timing_640x480.t27, i2c_master.t27, lucas_accumulator.t27, sii9134_init.t27 and tmds_encoder.t27 onto master without rows in tools/elab_baseline.txt, so tools/check_elab_ratchet.py reads them as NEW and fpga-conformance is red on master (run 40197819164) and on every lane behind it (#12304, #12356, #12405). Five rows with the counts CI printed on that run (0, 20, 8, 2, 0; 384 -> 414) and one dated note. No existing row changes, no spec, generator or checker change. Refs #9367, #12387, #2325 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * gates(dupes): ledger row for the same group, the renamed remainder of the groups #12458 shrank (Closes #12498) #12458 (4f1c99a) deleted the bytes_equal, eq_s and mem_eql copies of the byte-equality body and dropped their ledger rows. The two copies it left, specs/tri/crypto/hex.t27:98 fn same and specs/igla/coder/tokenizer.t27:469 fn same_bytes, were members of those groups; tools/dupe_scan.py names a group by its alphabetically first member, so the remainder now reads `same 2` and was in no ledger. Neither file changed since 2026-10-23. One row added, nothing else. Replacing both bodies with std::mem::bytes_eq (and dropping the row again) stays with #12444. Refs #9367, #12387, #12444 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * port(scripts): move the build_tree use inside the 29 gen_w6NN modules so the parser keeps their closing brace; reseal (Closes #12497) #12458 (4f1c99a) replaced the local fn build_tree in gen_w631..657, 662 and 680 with `use port::scripts::gen_w_flat::build_tree;` on line 8, before `module gen_wNNN {` on line 9. The parser takes that form and drops the module's final `}` without an error (t27c parse-complete --show: 1 token DISCARDED, line 66); t27c check is blind to it, so the Corpus Ratchet reports 29 UNEXPECTED FAILURES [parse-no-discard] on master and on every lane behind it. The use line now sits first inside each module body; the constants, functions and tests do not change. The 29 seals are re-saved with the master-built t27c from the repository root. The parser defect itself (bootstrap/) is out of scope here. Refs #9367, #12387, #12444 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * seals(t27os): re-save the five t27os seals that #12373 added against older spec states (Closes #12509) #12373 (squashed to 70e3073) added seals for T27osDoctor, T27osGlassWindow, T27osLabPublish, T27osPtyServer and T27osReceiptName that were computed on its own branch, while master's copies of the five specs had moved on; they were stale the moment they landed, so coverage and spec-guards are red on master and on every PR that carries 70e3073 (FAIL: 6 seal(s) newly do not hold; STALE generated-code hash: 6). The five seals are re-saved with the master-built t27c 0.5.2 from the repository root at 70e3073; the gen hashes now equal the current= values spec-guards printed on master. Nothing else changes. The sixth stale seal, T27osSessionStore, is carried by #12495. Refs #9367, #12387 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
8466c74
into
lane/wasm-control-flow-12010
…rge started emitting (Closes #12491) (#12492) * ci(elab): baseline rows for the five specs/fpga modules the #12430 merge started emitting (Closes #12491) b1a5857 (the #12430 merge, 2026-10-25T13:29Z) brought specs/fpga/ hdmi_timing_640x480.t27, i2c_master.t27, lucas_accumulator.t27, sii9134_init.t27 and tmds_encoder.t27 onto master without rows in tools/elab_baseline.txt, so tools/check_elab_ratchet.py reads them as NEW and fpga-conformance is red on master (run 40197819164) and on every lane behind it (#12304, #12356, #12405). Five rows with the counts CI printed on that run (0, 20, 8, 2, 0; 384 -> 414) and one dated note. No existing row changes, no spec, generator or checker change. Refs #9367, #12387, #2325 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * gates(dupes): ledger row for the same group, the renamed remainder of the groups #12458 shrank (Closes #12498) #12458 (4f1c99a) deleted the bytes_equal, eq_s and mem_eql copies of the byte-equality body and dropped their ledger rows. The two copies it left, specs/tri/crypto/hex.t27:98 fn same and specs/igla/coder/tokenizer.t27:469 fn same_bytes, were members of those groups; tools/dupe_scan.py names a group by its alphabetically first member, so the remainder now reads `same 2` and was in no ledger. Neither file changed since 2026-10-23. One row added, nothing else. Replacing both bodies with std::mem::bytes_eq (and dropping the row again) stays with #12444. Refs #9367, #12387, #12444 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * port(scripts): move the build_tree use inside the 29 gen_w6NN modules so the parser keeps their closing brace; reseal (Closes #12497) #12458 (4f1c99a) replaced the local fn build_tree in gen_w631..657, 662 and 680 with `use port::scripts::gen_w_flat::build_tree;` on line 8, before `module gen_wNNN {` on line 9. The parser takes that form and drops the module's final `}` without an error (t27c parse-complete --show: 1 token DISCARDED, line 66); t27c check is blind to it, so the Corpus Ratchet reports 29 UNEXPECTED FAILURES [parse-no-discard] on master and on every lane behind it. The use line now sits first inside each module body; the constants, functions and tests do not change. The 29 seals are re-saved with the master-built t27c from the repository root. The parser defect itself (bootstrap/) is out of scope here. Refs #9367, #12387, #12444 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * seals(t27os): re-save the five t27os seals that #12373 added against older spec states (Closes #12509) #12373 (squashed to 70e3073) added seals for T27osDoctor, T27osGlassWindow, T27osLabPublish, T27osPtyServer and T27osReceiptName that were computed on its own branch, while master's copies of the five specs had moved on; they were stale the moment they landed, so coverage and spec-guards are red on master and on every PR that carries 70e3073 (FAIL: 6 seal(s) newly do not hold; STALE generated-code hash: 6). The five seals are re-saved with the master-built t27c 0.5.2 from the repository root at 70e3073; the gen hashes now equal the current= values spec-guards printed on master. Nothing else changes. The sixth stale seal, T27osSessionStore, is carried by #12495. Refs #9367, #12387 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
…near memory, calls, i64 pairs (Closes #12371) (#12597) * fpga(wasm): the 24 single-clock i32 ALU opcodes have one home -- alu_arity/alu_eval in the cell, the table oracle calls them (Refs #12011) The cell's step_all took 24 hand-copied ALU branches; they are now one arm through alu_arity(op) / alu_eval(op, a, b), written once in wasm_board_ax7203. wasm_opcodes uses the cell and its eval_byte calls the same two functions, so the oracle and the cell cannot differ. Deviation from FR-005 of #12011 ("exist once, in the table spec"): they exist once in the cell spec. t27c gen-verilog imports only enums and structs from a used module, never a function body; the splice of used function bodies happens only in test-report / t27b test. The direction is forced by the lowering. Tests: cell 21/21, table 16/16 + invariant, 0 vacuous in both. Law 14 pins the cell's accepted byte set against the table's i32 ALU rows in both directions (24 bytes: 4 unary, 20 binary); law 15 pins the 37 executed bytes. Behavior change: eval_byte returns 0 for i64 rows (it used to evaluate their i32 twin) and for div/rem rows, as before. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> * fpga(wasm): seals re-saved for the cell and the table after the ALU one-home change (Refs #12011) t27c seal --save on both specs from the master-built binary; tri seals sync-twins reports 497 twinned specs already consistent, 0 written. Cell 21/21, table 16/16 plus the signed_order_holds invariant. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * fpga(wasm): block/loop/br/br_if/br_table and the label stack in the board cell -- scan submachine, fuel guard, ERR_LABEL/ERR_FUEL, five baked control-flow programs (Refs #12010) Five opcodes join the cell's executed set (37 -> 42): block 0x02, loop 0x03, br 0x0C, br_if 0x0D, br_table 0x0E. A label stack of LABEL_MAX 8 (kind, target, stack height, arity); a scan submachine that finds a block's matching end by skipping immediates (one-LEB opcodes, nested block/loop, br_table vectors; an unknown byte is ERR_OPCODE); a FUEL_MAX 65536 guard with ERR_FUEL 9 so a looping program cannot hang the wire, ERR_LABEL 8 for a branch past the stack; host_run_user awaits 8 * FUEL_MAX. Five reference programs baked into the store: CF_SUM 0x00 (30 bytes, sum 1..100 = 5050), CF_TAB 0x1E (26 bytes, br_table -> 10/20/30/10), CF_NEST 0x38, CF_FUEL 0x59, CF_VAL 0x6D (a branch unwinds the value stack: 43, not 5); CF_END 0x7C. Cell tests 30/30 (t27c test-report and t27b test agree, 0 vacuous); table 16/16 plus invariant, law 15 renamed to the_42_executed_bytes_are_all_generated_bytes. Dispatch arms 18 = the 13 of (#12011), one recipe. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * fpga(wasm): park the cell spec and its seal at the merge base so master merges clean (Refs #12011) Master's P3/P4 const floors (i64.const 0x42, f32.const 0x43, f64.const 0x44) and the #12042 reseal landed on the same lines this lane rewrote for the one-home ALU, so a plain merge stops in two files. The pre-commit gate measures a hand-committed merge by its staged diff against the lane tip, where master's own Rust reads as lines added here, and that gate is never bypassed. So this commit puts specs/fpga/wasm_board_ax7203.t27 and .trinity/seals/fpga_fpga_wasm_board.json back to 09bcdb0, the next commit is git's own clean merge of origin/master, and the commit after it re-applies this lane's cell over master's, resolved by hand. The three together are the one merge; the PR's squash keeps only the result. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * fpga(wasm): re-apply the one-home ALU cell over master's P3/P4 const floors, 40 executed bytes (Refs #12011) Completes the merge of origin/master that the two commits before it prepared (the park and git's clean merge). Resolution by hand, master's bytes kept verbatim: the P3/P4 constants and their notes after OP_I32_CONST; the i32.const arm now also takes OP_I64_CONST; the f32/f64 arm sits between the const arm and the i32.mul arm, with its closing brace at the end of the chain; the two wire tests p3_i64_wire_programs and p4_f32_wire_programs sit before the #12011 block. The executed-byte count goes 37 -> 40 in both specs: cell_runs and executed_beyond_alu take 0x41-0x44 as one range, the sweep asserts 40 runs / 216 tried, law 15 is the_40_executed_bytes_are_all_generated_bytes with beyond == 16, the cost note says 14 other arms. The two seals are this lane's old ones for now; they are re-saved from a master-built compiler in the next commit. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * fpga(wasm): park the cell and the table at the merge base so the ALU lane merges clean (Refs #12010) The ALU lane (lane/wasm-alu-one-home-12011, PR #12060) now carries master's P3/P4 const floors and master's own Rust; a plain merge of it stops in the cell and the table on the executed-byte counts. The pre-commit gate measures a hand-committed merge by its staged diff against the lane tip, where master's Rust reads as lines added here, and that gate is never bypassed. So this commit puts specs/fpga/wasm_board_ax7203.t27 and specs/fpga/wasm_opcodes.t27 back to 49c4dcb, the next commit is git's own clean merge of the ALU lane, and the commit after it re-applies the control-flow cell and table over it, resolved by hand. The three together are the one merge; the PR's squash keeps only the result. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * fpga(wasm): re-apply the control-flow cell and table over the ALU lane's P3/P4 merge, 45 executed bytes (Refs #12010) Completes the merge of lane/wasm-alu-one-home-12011 that the two commits before it prepared (the park and git's clean merge). The dispatch chain merged by itself: the four control-flow arms (block/loop, br, br_if, br_table) sit before local.get, the const arm takes i32.const and i64.const, the float arm follows it. Resolved by hand, only the counts: cell_runs and executed_beyond_alu take 0x41-0x44 as one range plus the five control-flow bytes, the sweep asserts 45 runs / 211 tried, law 15 is the_45_executed_bytes_are_all_generated_bytes with beyond == 21, the cost note says a 45-byte decode with 18 other arms. The two seals are this lane's old ones for now; they are re-saved from a master-built compiler in the next commit. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * seal(fpga): re-save the wasm board and opcode-table seals after the P3/P4 merge, 23/23 and 16/16 (Refs #12011) The two seals are re-saved from a compiler built at master commit 19a89f1, whose compiler tree (bootstrap, cli, Cargo.lock, Cargo.toml) is byte-identical to origin/master at 7273ad3; the previous seals named 94adbb1 and predated master's gen-rust change (#12042, resealed on master in #12062). built_by, spec_hash and the four gen hashes move; the board seal's test count moves 21 -> 23 with master's two const-floor tests; the seals carry no home path. Run: t27c seal --save, then t27c test-report, both specs, 0 vacuous. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * seal(fpga): re-save the wasm board and opcode-table seals for the control-flow cell, 32/32 and 16/16 (Refs #12010) Same compiler as the ALU lane's reseal: built at master commit 19a89f1, compiler tree byte-identical to origin/master at 7273ad3. The board seal's test count moves 30 -> 32 with master's two const-floor tests; built_by, spec_hash and the four gen hashes move; no home path. Run: t27c seal --save, then t27c test-report, both specs, 0 vacuous. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * tools(dupe): lower the duplicate-bodies ledger by the two groups this lane removed (Refs #12011) The table's sar32 and lt_s were copies of the cell's; this lane deleted them, so the groups are gone and the ratchet refuses a ceiling left above the real number. Re-blessed with tools/dupe_scan.py --bless: the diff is two deleted lines, no group added, none grown. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * fpga(wasm): move the P2..P4 page-load tests back below the cf_ suite so the Verilog run keeps the baked programs (Refs #12010) In the simulation Verilog every test block is an initial block over one shared code[]. The P2..P4 section loads pages over the baked programs and its header says it stays last, but the cf_ suite had been appended below it, so cf_fuel_then_link_alive and cf_restore_keeps_the_baked_programs read a cleared ID_ANSWER and ID_TEE_DUP in vvp (30 of 32) while the interpreter, one process per test, gave 32 of 32. This is a move of 956 lines plus a two-line comment stating the rule; no assert changed. Measured with gen-verilog-for-simulation, the two "save = undefined;" lines of master's P3/P4 tests deleted first (t27#12179), iverilog -g2005 and vvp -n: 32 starting, 32 PASSED, 0 FAILED. test-report: 32 of 32 and 16 of 16, 0 vacuous. Both seals re-saved from a master-equivalent compiler. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * fpga: wasm_board_ax7203 P5 -- one 64 KiB linear memory page, ten memory opcodes (Closes #12202) The board cell now executes i32.load (0x28), load8_s/u (0x2C/0x2D), load16_s/u (0x2E/0x2F), i32.store (0x36), store8 (0x3A), store16 (0x3B), memory.size (0x3F) and memory.grow (0x40) over one `var mem : [MEM_BYTES]u8` with MEM_BYTES = 65536. - Little-endian; a 2- or 4-byte access is a byte-per-step microsequence (mem_run) with one read site and one write site, both addressed by the register mem_addr; the extra steps spend no fuel (one unit per fetch). - The memarg is two LEB128 u32 (align ignored, offset kept); a fifth continuation byte answers ERR_LEB; scan_class steps over it, so a branch that skips a load with a two-byte offset ending in 0x0B lands correctly. - The effective address is a 33-bit sum; an access of width w traps when it exceeds 65536 - w (ERR_MEM = 10, RESP_BADOP on the wire) before any byte is written; no wrap, and the next request works. - load8_s/load16_s sign-extend, load8_u/load16_u zero-extend; store8 and store16 leave every other byte alone. - memory.size pushes 1; memory.grow pushes 0xFFFFFFFF for every delta, 0 included (a documented deviation from the WASM spec: it grows nothing). - Ten new cell tests (42 in all), placed before the P2..P4 page-load tests, which stay last. cell_runs, alu_unexecuted_bytes_are_opcode_errors, wire_is_the_node_link, machine_consts and cf_negative_controls updated (the unexecuted probe byte is now 0x29, since 0x28 is a class-6 memarg op). - wasm_opcodes: fn executed_beyond_alu grows from 21 to 31 bytes, and the executed-bytes law is renamed and re-pinned for 55 (24 ALU + 31). No row is added and no pinned const changes: the eight memory rows keep interp_ok = false, because the live interpreter has no memory. Measured: t27c test-report on the cell 42 of 42, 0 vacuous; on the table 16 tests pass, 1 invariant proved, 0 vacuous. Known gap, measured and not hidden: yosys 0.67 read_verilog on the generated cell answers "Replacing memory \mem with list of registers", so the page is not block RAM in this lowering (and read_verilog + hierarchy ran for more than ten minutes against 5.7 s for the base). gen-verilog emits no initial block for `[0] ** MEM_BYTES`, so an iverilog run reads X from an unwritten byte; the new tests store before they load. Both are t27c emitter gaps, not fixed here (this change touches only the two specs). Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> * fpga(wasm): bounds check in 32-bit compares so the Verilog run agrees with the Zig run (Refs #12202) The first form of mem_fits summed three `as u64` casts and compared the 33-bit result with MEM_BYTES. The Zig run passed (42 of 42), the vector-level Verilog run did not (41 of 42): gen-verilog drops the u64 casts and emits `((base + off) + w) > MEM_BYTES` over 32-bit inputs, so 0xFFFFFFFF + 1 wrapped to a legal address and mem_access_past_the_page_traps lost its wrap cases (ten failed asserts). mem_fits now says the same thing with two 32-bit compares that cannot overflow: with lim = MEM_BYTES - w, the access fits exactly when base <= lim and off <= lim - base. mem_ea adds base + off without a cast, because it is only reached after mem_fits said yes. Measured after the change: t27c test-report 42 of 42, 0 vacuous; the emitted mem_fits carries the two compares; iverilog -g2012 elaboration 0 errors. The emitter gap itself is filed separately and not fixed here. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * seals: wasm_board_ax7203 and wasm_opcodes re-saved for the memory page (Refs #12202) Both seals come from a master-built t27c run at the lane root (`t27c seal <spec> --save`, then `tri seals sync-twins`); the cell seal now records 42 of 42 tests, the table seal its new gen hashes. No path under a home directory in either file. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * feat(fpga): wasm_board_ax7203 P6 -- if/else, call/return, call_indirect, globals on the existing frames (Closes #12323) Adds the call machinery to the board cell, on the label stack and the one-byte-per-step forward scan that #12010 introduced. wasm_board_ax7203.t27 - Six new executed bytes: if 0x04, else 0x05, call 0x10, call_indirect 0x11, global.get 0x23, global.set 0x24. `return` 0x0F now unwinds to the current call frame; the function-level `end` returns to the caller while a frame is open and ends the run otherwise. - Call stack of CALL_MAX = 4 frames (pc, sp, label height, locals base, code window); locals are one flat array of LOCALS_TOTAL = 20 entries addressed from the frame base. A function returns a result iff the operand stack is higher than at entry, otherwise it is void. - Function table (FUNC_MAX = 8 rows: off, np, nl, ty), element table (ELEM_MAX = 4, NO_FUNC marks null) and globals (GLOBAL_MAX = 4) are cell arrays, loadable over the wire with the new op LOAD_TABLES 0xFC. - New errors ERR_CALL 11, ERR_FUNC 12, ERR_TYPE 13, ERR_GLOBAL 14, each with a const and a wire test answering RESP_BADOP. - Fuel stays one unit per fetch. Runaway recursion ends with ERR_CALL at the depth limit; the next request works. - cell_runs is 1 for exactly 61 bytes. - 22 new tests with the prefix cl_, above the P2 header comment, each one behaviour with real WASM bytes and a closed-form golden. Hand traces with fuel counts for cl_if_takes_else_arm, cl_call_recursive_fact_to_depth and cl_return_unwinds_labels_and_frame. wasm_opcodes.t27 - executed_beyond_alu returns 1 for the six new bytes; the executed-bytes law is re-pinned: 24 ALU + 37 beyond = 61 (was 24 + 31 = 55). No row is added and no interp_ok value changes. Nothing was synthesised, simulated, flashed or run on the board in this commit; the tests were typechecked with t27_check only, and the Zig and Verilog runs are reported by the orchestrator. Closes #12323 Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> * seals: wasm_board_ax7203 and wasm_opcodes re-saved for P6 calls and if/else (Refs #12323) Both seals come from a master-built t27c run at the lane root (`t27c seal <spec> --save`, then `tri seals sync-twins`); the cell seal now records 64 of 64 tests, the table seal its new gen hashes for the 61-byte executed pin. No path under a home directory in either file. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * chore(fpga): park the table and its seal at the merge base before merging master (Refs #12011) Master dropped the copied lt_s and sar32 bodies from wasm_opcodes.t27 in favour of an import (#11885) and re-sealed the table; this branch rewrote the same region. The table and its seal go back to the merge-base version for one commit so the merge of master is clean, and the branch's change is re-applied on top in the next commit. The cell is untouched. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * fix(fpga): re-apply the one-home table on top of master's lt_s/sar32 import drop (Refs #12011) The merge of master brought the table back to master's text, where the copied lt_s and sar32 bodies were replaced by an import (#11885). This branch removes every restated ALU body from the table (the ALU lives once in the cell, issue #12011), so the import is not needed either: the table is byte-for-byte the version this branch carried before the merge, and so is its seal. A fresh `t27c seal` from a master-built t27c on the merged tree reproduces every hash in that seal (spec, zig, verilog, c, rust; 16 of 16 tests); only the built_by and sealed_at lines would change, so the previous file is kept and the stacked lanes merge this branch without touching the table again. The cell and its seal are untouched by master. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * feat(fpga): i64 pairs on the operand stack for DOOM's FixedMul in wasm_board_ax7203 (Closes #12371) An i64 is two u32 stack slots, HIGH deeper at st[sp-2], LOW on top at st[sp-1], so the root end still answers the low word and the P3 answers (987654321, -5) stand. Nine bytes join the cell behind one is_p7_op gate: i32.wrap_i64 0xA7, i64.extend_i32_s/u 0xAC/0xAD, i64.add/sub/mul 0x7C-0x7E, i64.shl/shr_s/shr_u 0x86-0x88. i64.mul is a 64-step shift-add microsequence (mul_run = 64) over 32-bit limbs; add, sub and the shifts finish in one step. No u64 value is read beyond its low 32 bits (#12296): a carry is "sum < addend", a borrow is "a < b", and only wrap32 masks. i64.const now decodes up to ten LEB bytes into a pair (the tenth byte may not continue, else ERR_LEB); scan_class gives it class 1 and the nine others class 0. wasm_opcodes.t27 pins the executed set 61 -> 70 bytes (46 beyond the 24 ALU bytes). The cell suite grows 64 -> 83 tests (64 + 19): consts, encoder, every LEB length, extends, wrap, add, sub, mul, the three shifts, FixedMul vectors plus FixedMul inside a call, stack traps, a limb oracle and a 64-pair mul sweep. No synthesis was run and nothing was flashed; seals are not re-saved here. Refs #9367 Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> * fix(fpga): i32.const 4-byte SLEB128 sign bit is bit 27 in wasm_board_ax7203 (Closes #12395) The final-byte arm for a 4-byte i32.const tested bit 23 and filled 0xF8000000, but a 4-byte SLEB128 carries 28 payload bits: the sign bit is bit 27 (0x8000000) and the fill is 0xF0000000. 41 80 80 80 04 gave 0xF8800000 (want 8388608) and 41 80 80 80 78 gave 0x0F000000 (want -16777216). New test i32_const_four_byte_sleb_sign pins both, plus 41 80 80 80 7C = -8388608 as the negative control that was already right. The i64 helpers and the sc_run scan are unchanged and correct. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> * chore(seals): re-save fpga_fpga_wasm_board and fpga_fpga_wasm_opcodes after the i64 pairs (Refs #12371) The two specs moved in the previous commit; their seals are re-saved from the lane root with a master-built t27c and the twins synced. No spec change. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * dupe: trigit verbs write through handoff's put_lit, one copy of the loop (Closes #12419) #12388 added put_lit to specs/tri/handoff/handoff.t27 with the same loop body as put_s in specs/tri/trigit/verbs.t27, so the Duplicate Body Ratchet went red on every PR: "put_lit was copied [2] and is now copied [2, 2]". verbs.t27 already reads handoff.t27 (put_span, create_record, ...), so the one body stays in handoff and verbs' put_s delegates to it. put_lit now takes []const u8, like its sibling put_span: gen-rust lowered `string` to &'static str, and verbs passes runtime byte slices. String callers get .as_bytes() / b"..." from gen-rust; the bytes written are unchanged. dupe_scan: 1849 bodies in 691 groups (was 1851 in 692), ledger unchanged. test-report: handoff 12/12, verbs 17/17, handoff_view 4/4, 0 vacuous. Seals for the three specs re-saved by t27c built at master 672aaca. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * seals: drop the stale FpgaHdmiGrab seal that #12430 re-added (Closes #12461) Master commit b1a5857 (PR #12430) added .trinity/seals/fpga_FpgaHdmiGrab.json as a new file sealed on 2026-10-20 against a shape of specs/fpga/hdmi_grab.t27 that #12059 split up this morning. Since then `coverage` (seal-coverage.yml) and `spec-guards` (check_seal_currency.py) are red on master and on every PR that merged it: one seal, all four gen_hashes stale. Master had no seal for this spec before that commit, so the file is removed; a fresh seal from a master-built t27c is a follow-up PR. Local check_seal_coverage.py on this branch: OK, 3079 seals, 2966 hold, 113 known-broken listed in seal_baseline.txt. Refs #12387 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * numeric: drop the dead braceless invariant the parser discarded in zphi_alu.t27, reseal (Closes #12464) Master commit b1a5857 (PR #12430) added specs/numeric/zphi_alu.t27. Its `invariant sign_conj_negation` is written in the braceless form with an `if` statement inside the body; `parse_bdd_clauses` does not lower that shape, so the parser restores its checkpoint and drops the whole body (61 tokens, lines 483-493). The Corpus Ratchet's `parse-no-discard` phase refuses such a file, so every branch that carries b1a5857 is red on `Corpus Ratchet`. The invariant was never checked, and it cannot be made live as written: with the file's own `sign` and `conj`, sign(3, 5) = 1, conj(3, 5) = (3, -5), sign(3, -5) = 1, so the asserted `sc == -s` is 1 == -1. The claim is not a Z[phi] theorem either (the conjugate of 1 is 1). The block and its comment line are deleted; nothing else in the spec changes. After the edit `t27c parse-complete --show specs/numeric/zphi_alu.t27` reports "nothing discarded" and `t27c check` passes. The seal .trinity/seals/numeric_NumericZphiAlu.json is regenerated in the same commit from a master-built t27c, so `coverage` and `spec-guards` stay green after the spec edit. No ledger entry, no parser change. Refs #12387 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * fix(seal): re-save the T27osFrameGate seal that #12467 left stale (Closes #12486) #12467 (e376a06, merged 2026-10-25T15:20:07Z) changed specs/t27os/frame_gate.t27 without re-saving .trinity/seals/t27os_T27osFrameGate.json, so tools/check_seal_currency.py reports one STALE generated-code hash on master and every PR with base master is red on spec-guards. Re-saved with a master-built t27c (0.5.2+672aaca7d, newer than master's last bootstrap/src change 2b1858e) from the branch root: tests 12/12, gen_hash_rust cebbd2f90560, gen_hash_zig 5877f763f823, gen_hash_c d7aba556f229, gen_hash_verilog 575cb51da761, the four values the check computes from the spec. The spec is unchanged; the seal carries no home path. Refs #11640 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * ci(elab): baseline rows for the five specs/fpga modules the #12430 merge started emitting (Closes #12491) b1a5857 (the #12430 merge, 2026-10-25T13:29Z) brought specs/fpga/ hdmi_timing_640x480.t27, i2c_master.t27, lucas_accumulator.t27, sii9134_init.t27 and tmds_encoder.t27 onto master without rows in tools/elab_baseline.txt, so tools/check_elab_ratchet.py reads them as NEW and fpga-conformance is red on master (run 40197819164) and on every lane behind it (#12304, #12356, #12405). Five rows with the counts CI printed on that run (0, 20, 8, 2, 0; 384 -> 414) and one dated note. No existing row changes, no spec, generator or checker change. Refs #9367, #12387, #2325 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * gates(dupes): ledger row for the same group, the renamed remainder of the groups #12458 shrank (Closes #12498) #12458 (4f1c99a) deleted the bytes_equal, eq_s and mem_eql copies of the byte-equality body and dropped their ledger rows. The two copies it left, specs/tri/crypto/hex.t27:98 fn same and specs/igla/coder/tokenizer.t27:469 fn same_bytes, were members of those groups; tools/dupe_scan.py names a group by its alphabetically first member, so the remainder now reads `same 2` and was in no ledger. Neither file changed since 2026-10-23. One row added, nothing else. Replacing both bodies with std::mem::bytes_eq (and dropping the row again) stays with #12444. Refs #9367, #12387, #12444 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * port(scripts): move the build_tree use inside the 29 gen_w6NN modules so the parser keeps their closing brace; reseal (Closes #12497) #12458 (4f1c99a) replaced the local fn build_tree in gen_w631..657, 662 and 680 with `use port::scripts::gen_w_flat::build_tree;` on line 8, before `module gen_wNNN {` on line 9. The parser takes that form and drops the module's final `}` without an error (t27c parse-complete --show: 1 token DISCARDED, line 66); t27c check is blind to it, so the Corpus Ratchet reports 29 UNEXPECTED FAILURES [parse-no-discard] on master and on every lane behind it. The use line now sits first inside each module body; the constants, functions and tests do not change. The 29 seals are re-saved with the master-built t27c from the repository root. The parser defect itself (bootstrap/) is out of scope here. Refs #9367, #12387, #12444 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * seals(t27os): re-save the five t27os seals that #12373 added against older spec states (Closes #12509) #12373 (squashed to 70e3073) added seals for T27osDoctor, T27osGlassWindow, T27osLabPublish, T27osPtyServer and T27osReceiptName that were computed on its own branch, while master's copies of the five specs had moved on; they were stale the moment they landed, so coverage and spec-guards are red on master and on every PR that carries 70e3073 (FAIL: 6 seal(s) newly do not hold; STALE generated-code hash: 6). The five seals are re-saved with the master-built t27c 0.5.2 from the repository root at 70e3073; the gen hashes now equal the current= values spec-guards printed on master. Nothing else changes. The sixth stale seal, T27osSessionStore, is carried by #12495. Refs #9367, #12387 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * fpga(wasm): restore the lane's board spec after the master merge (Refs #12371) The merge of master 0ff9452 into the top lane brought the squash twin of W1 (#12060, 128c830). The two specs on master equal W1 at 3b68aca and every W1 commit that touched them is in this branch's history, so the lane's side was taken for the eight content conflicts, and the two pieces git re-added as plain additions, one ok_rsp() in step_all and a second copy of the #12011 test block at the end of the file, are removed again here by restoring the lane's file byte for byte. The seals of the lane stay valid. Refs #12010 Refs #12202 Refs #12323 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
Closes #12202. Refs #9367 (WASM-on-AX7203 tracker). Epic: see Trinity-S-AI-DNA/trinity-fpga#917. Stacked on #12061 (
lane/wasm-control-flow-12010), which is stacked on #12060; retarget to master after those merge.What
Before this PR the board cell could run arithmetic and control flow over a stack and nothing could be stored. This PR gives
specs/fpga/wasm_board_ax7203.t27one 64 KiB linear memory page (var mem : [MEM_BYTES]u8,MEM_BYTES = 65536) and ten memory opcodes:i32.load(0x28),i32.load8_s/u(0x2C/0x2D),i32.load16_s/u(0x2E/0x2F),i32.store(0x36),i32.store8(0x3A),i32.store16(0x3B),memory.size(0x3F),memory.grow(0x40).mem_run) with one read site and one write site, both addressed by the registermem_addr; the extra steps spend no fuel.ERR_LEB;scan_classsteps over the memarg, so a branch that skips a load with a two-byte offset ending in 0x0B lands correctly.wtraps withERR_MEM = 10(RESP_BADOPon the wire) whenbase + offset > 65536 - w, before any byte is written; no wrap, and the next request works.load8_s/load16_ssign-extend,load8_u/load16_uzero-extend;store8/store16leave every other byte alone.memory.sizepushes 1;memory.growpushes 0xFFFFFFFF for every delta (it grows nothing; a documented deviation from the WASM spec).Ten new cell tests (42 in all), placed before the P2..P4 page-load tests, which stay last (shared
code[], see the spec header).cell_runsgrows from 45 to 55 bytes. Inspecs/fpga/wasm_opcodes.t27,fn executed_beyond_alugrows from 21 to 31 bytes and the executed-bytes law is re-pinned for 55 (24 ALU + 31); no row is added and no pinned const changes, the eight memory rows keepinterp_ok = falsebecause the live interpreter has no memory. Both seals re-saved from a master-builtt27c.Measured (commands as run, 2026-10-25, master-built t27c, iverilog 12.0, yosys 0.67)
t27c test-report specs/fpga/wasm_board_ax7203.t27t27c test-report specs/fpga/wasm_opcodes.t27t27c gen-verilog specs/fpga/wasm_board_ax7203.t27iverilog -g2012 -DSIMULATION -o /dev/null wasm_board_ax7203.vwasm_board_ax7203 0, unchanged)iverilog -g2012 -DSIMULATION -o /dev/null wasm_opcodes.vwasm_opcodes 149, unchanged)t27c gen-verilog-for-simulation+sed '/= undefined;/d'(#12179) +iverilog -g2005+vvp -nassert failedlines, vvp exit 08d7b63311e(before the bounds-check rewrite)mem_access_past_the_page_traps, 10 asserts)python3 tools/dupe_scan.pytri seals sync-twins.trinity/sealsgit diff --stat lane/wasm-control-flow-12010...HEADNot measured: nothing was synthesised or flashed. Until a bitstream is loaded with
openFPGALoader -c digilent_hs2on the AX7203 and the UART answers, every run above is a simulation.The issue's block-RAM criterion: refuted, under its own clause
Issue #12202 asked for the page to infer as block RAM. yosys 0.67
read_verilogon the emitted cell answersReplacing memory \mem with list of registers, andread_verilog+hierarchyran for more than ten minutes against 5.7 s for the base cell. The page is lowered as 65536 registers inside the singlealways @(posedge clk or negedge rst_n)block, which no block-RAM template infers. The spec is in the inference shape (one read site, one write site, one address register), the emitter is not; this is not block RAM in this lowering, reported as the issue's refutation clause allows, and filed as #12297. Do not run yosys synthesis on this cell until #12297 lands.Disclosure: three t27c gen-verilog gaps found here, none fixed here
as u64casts emit the operand verbatim, so the 33-bit bounds sum of the first commit wrapped in 32 bits. The Zig run passed 42 of 42 while the Verilog run failed one test; the second commit rewritesmem_fitsas two 32-bit compares that cannot overflow, and both runs now agree. The spec comment atmem_fitssays why it has that form.alwaysblock and becomes registers (above).[0] ** 65536gets an emptyinitial begin end, so an unwritten byte reads X in iverilog and 0 in Zig; the new tests store before they load.Collision: #12181 (
spec/wasm-board-p5-float-arith, a pure insertion into the same cell) names its section P5 too; whichever lands second merges the other by hand.Next on this path (same cell file, so sequential):
if/else,call/return,call_indirect, locals and globals on the existing frames (#9367 order), theni64.mul/i64.shr_sfor DOOM'sFixedMul; the loader of #12278 fillsmemfrom the module's data segments.🤖 Generated with Claude Code