Verilog as the fourth arm, a nightly that exhausts it, and the theory that says why - #2203
Merged
Conversation
Verilog is the target that goes to silicon and was the least verified of the four: verify_emit_bitexact.py samples it, while model/C/Rust became exhaustive in #2200. It now folds the same FNV-1a digest over the same domain under iverilog. tmul, negate four implementations agreeing on EVERY input full_adder, maj3 model/C/Rust exhaustive; Verilog on a labelled slice pack2 three-way, labelled: u64 return needs a wider fold Why slices, measured rather than assumed. iverilog is an interpreter and the cost per input is not uniform: tmul ~330,000/s, maj3 21,061/s, full_adder 2,972/s, because full_adder calls dot27 nine times per input at 27 lanes each. The whole domains are 13 and 94 minutes. The Verilog arm therefore has a budget and prints a labelled slice where the domain does not fit. A slice is never printed as exhaustive; --verilog-full runs the long version. Three of my own errors, all caught before this opened: I measured tmul's rate and extrapolated to full_adder, which does nine times the work per input. The run timed out at ten minutes. Measuring one thing and extrapolating to another is the error this line of work exists to catch. I then set the budget in INPUTS -- a unit whose cost varies 100x across these functions, so one number was seconds for tmul and eleven minutes for full_adder. The budget is in seconds now, converted per function from the measured rates. The summary line read 'ALL 5 PRIMITIVES AGREE EXHAUSTIVELY across every arm', false for three of the five, while the per-line detail above it was correct. A summary that overstates its own detail is the exact failure this session has been finding elsewhere. Closes #2202 Refs #2200 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Contributor
Contributor
|
📓 NotebookLM Notebook linked to this PR
This notebook contains session context, decisions, and artifacts for this work. |
…cedure The per-PR gate runs the Verilog arm on a labelled slice because iverilog needs 13 minutes for maj3 and 94 for full_adder at its measured 21,061 and 2,972 inputs/s. Unacceptable per push, fine once a day. exhaustive-nightly.yml runs --verilog-full at 03:17 UTC and closes the slice: four independent implementations agreeing on all 16,777,216 inputs instead of 0.4% of them. docs/EXHAUSTION_THEORY.md states why the wall-clock is worth spending. Exhaustive agreement over a finite domain is a decision procedure, not a test. For f_A, f_B : D -> R with D finite, checking equality on every x decides f_A == f_B. No induction, no invariants, no trusted prover kernel. On a finite domain it is the strongest statement available, and it is what HECTOR and ACL2 approximate when D is too large to walk. |D| is set by the representation, not the semantics: Claim. For k trit arguments declared in a w-bit type, enumeration costs (2^w / 3)^k times more than the semantic domain requires. Proof. Semantic domain 3^k, representational (2^w)^k, ratio (2^w/3)^k. Check. w=8, k=3 gives (256/3)^3 = 621,378.4, and 16,777,216/27 = 621,378.4 measured in this tree. Only 1.61e-6 of full_adder's enumerated space is a valid trit triple. Both readings of that are true: a 2-bit trit type would put full_adder at 64 inputs, 0.02s in iverilog rather than 94 minutes -- four million times cheaper from a type declaration with no algorithmic change. And the byte-wide enumeration verifies out-of-domain behaviour a caller can reach, since pack2 does not mask and values above 3 spill into the neighbouring lane. A 2-bit type makes those unrepresentable rather than verified: better, but a different guarantee, not the same one cheaper. A binary float add over two 32-bit operands is 1.8e19 inputs -- 58,561 years at 10^7/s. Enumeration is unavailable there at any budget, which is why sequential equivalence checking and theorem proving exist. Small alphabets sit below that line, and that is the one place choosing ternary buys a verification advantage rather than an area or energy claim -- and unlike the area claims withdrawn twice in this project's history, this one follows from counting. Closes #2204 Refs #2202 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Contributor
|
📓 NotebookLM Notebook linked to this PR
This notebook contains session context, decisions, and artifacts for this work. |
Contributor
PR DashboardGenerated at: 2026-08-18 15:03:12 UTC
Summary
Seal Status
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Verilog is the target that actually goes to silicon, and it was the least verified of the four:
verify_emit_bitexact.pychecks it by sample, while model/C/Rust are now exhaustive (#2200).This adds Verilog as a fourth arm, under
iverilog, folding the same FNV-1a digest over the same domain.Result:
tmulandnegatenow have four independent implementations agreeing on every input.Why the others are slices, measured rather than assumed. iverilog is an interpreter and the cost per input is not uniform:
tmulmaj3full_adderfull_addercallsdot27nine times per input at 27 lanes each. So the Verilog arm gets a budget and reports a labelled slice where the whole domain does not fit. A slice is never printed as exhaustive, and--verilog-fullruns the long version.Three mistakes of mine in this PR, all caught before it opened.
tmul's Verilog rate and extrapolated tofull_adder— which does nine times the work per input. The run timed out at ten minutes. Measuring one thing and extrapolating to another is the error this whole line of work exists to catch.tmuland eleven minutes forfull_adder. The budget is now in seconds, converted per function from the measured rates.ALL 5 PRIMITIVES AGREE EXHAUSTIVELY across every arm (no sampling)— false for three of the five, while the per-line detail above it was correct. A summary that overstates its own detail is the exact failure this session has been finding elsewhere.pack2returnsu64; the C and Rust folds take the low 32 bits and a matching Verilog fold needs a wider accumulator. Left three-way and labelled rather than quietly given a Verilog arm that compares something else.Refs #2200
Two things the previous PRs left open, plus the theory that says why they matter.
1.
--verilog-fullas a nightly job. #2202 gates on a labelled Verilog slice because iverilog needs 13 minutes formaj3and 94 forfull_adderat its measured rates (21,061 and 2,972 inputs/s). Unacceptable per push; fine once a day. The slice keeps gating; this closes it, giving four independent implementations agreeing on all 16,777,216 inputs rather than on 0.4 % of them.2.
docs/EXHAUSTION_THEORY.md— why this line of work is worth the wall-clock, stated precisely.The point, in three steps
Exhaustive agreement over a finite domain is a decision procedure, not a test. For
f_A, f_B : D → RwithDfinite, checking equality on everyx ∈ Ddecidesf_A ≡ f_B. No induction, no invariants, no trusted prover kernel. It is not a weaker substitute for formal EC — on a finite domain it is the strongest statement available, and it is what HECTOR and ACL2 approximate whenDis too large to walk.|D|is set by the representation, not the semantics. A trit carries three values; these specs declare itu8.Only 1.61 × 10⁻⁶ of
full_adder's enumerated space is a valid trit triple.Both readings of that are true. It is waste — a 2-bit trit type would put
full_adderat4³ = 64inputs, which iverilog finishes in 0.02 s instead of 94 minutes, a four-million-fold change from a type declaration with no algorithmic change. And it is coverage — nothing stops a caller passing200, the spec has defined behaviour there (pack2does not mask, so values above 3 spill into the next lane), and enumerating the byte-wide domain verifies it. A 2-bit type would make those inputs unrepresentable rather than verified: better, but a different guarantee, not the same one made cheaper.Why this is a property of the number system
|D|u8, k=2u8, k=3The line between enumerate and prove is crossed by representation width, and small alphabets sit below it. That is the one place choosing ternary buys a verification advantage rather than an area or energy claim — and unlike the area claims in this project's history, which were withdrawn twice, this one follows from counting and can be rechecked by anyone in seconds.
Refs #2202, #2200
Closes #2202
Closes #2204
🤖 Generated with Claude Code