Repository navigation
Owner decision: emitted Verilog carries widths that cannot be real, and the release build is the one that hides it #2566
Description
gen-verilog emits ranges no hardware can carry, exits 0, and prints nothing to stderr. Ten specs, three distinct mechanisms.
What the build flag decides
VerilogCodegen::range_decl(width: u32) (bootstrap/src/compiler.rs:9591) formats [width - 1 : 0] and guards only width == 1. A packed width of 0 underflows:
| build | outcome |
|---|---|
| debug (overflow-checks on) | panic, attempt to subtract with overflow |
| release (overflow-checks off) | function [4294967295:0] cover_point;, exit 0, stderr empty |
CI builds --release. So Corpus ratchet has been green over this, and the loud version of the bug is the one CI can never see. Reproduce:
cargo build --release -p t27c
t27c gen-verilog specs/fpga/formal.t27 | grep 4294967295
Three mechanisms, not one
| width | specs | reading |
|---|---|---|
4294967295 (u32::MAX) |
8 | 0 - 1 in u32. CoverPoint is a struct of four &str; strings have no bit representation, so packed_width returning 0 is correct — the backend inventing a range from it is not |
18446744073709551615 (u64::MAX) |
specs/memory/notebooklm.t27 |
the same underflow on a u64 path — a separate site |
4198431 |
specs/compiler/stdlib.t27 |
not obviously an underflow: a Map flattened to 4.2 million bits. Absurd as RTL either way, mechanism unconfirmed |
A detector keyed on the literal 4294967295 finds 8 of the 10. This one keys on "no real bus is a million bits wide" and finds all three classes.
Why this is yours and not mine
compiler.rs is sealed by stage0/FROZEN_HASH, and FROZEN.md reserves re-sealing for a deliberate M5 ceremony by maintainers. I have not touched it.
The design question is also yours: should a type with no bit representation be lowerable to Verilog at all? Refusing would be correct and would fail these ten specs, which is a ratchet decision, not a bug fix.
What I did instead
tools/check_verilog_widths.py with tools/verilog_width_baseline.txt ledgers the ten so the eleventh fails. It treats the debug panic and the release emission as the same finding, because a checker that scanned only emitted text would report CLEAN against a debug compiler — the compiler that panicked emitted nothing to scan, and absence of the string is not absence of the defect.
Boundary
specs/compiler/stdlib.t27specs/fpga/formal.t27specs/memory/notebooklm.t27bootstrap/src/compiler.rstools/check_verilog_widths.pytools/verilog_width_baseline.txt
User Scenarios
- Given a spec with a type that has no bit representation (e.g.,
CoverPointstruct of strings), whent27c gen-verilogruns in release mode, then it emits a Verilog range[4294967295:0]and exits 0 with empty stderr. - Given a spec that flattens a
Mapto 4.2 million bits, whent27c gen-verilogruns in release mode, then it emits a Verilog range[4198430:0]and exits 0 with empty stderr. - Given a spec exercising the u64 code path, when
t27c gen-verilogruns in release mode, then it emits a Verilog range[18446744073709551615:0]and exits 0 with empty stderr.
Requirements
- FR-001:
VerilogCodegen::range_declMUST rejectwidth == 0before underflow can occur, in both debug and release builds. - FR-002: The compiler MUST emit a diagnostic to stderr and exit non-zero when a type with no bit representation is passed to Verilog codegen.
- FR-003: The compiler MUST emit a diagnostic to stderr and exit non-zero when a packed width exceeds a configurable maximum (e.g., 1_000_000 bits).
- FR-004:
tools/check_verilog_widths.pyMUST fail (exit non-zero) when any emitted Verilog contains a range upper bound ≥ 1_000_000. - FR-005: The baseline
tools/verilog_width_baseline.txtMUST be updated to reflect the ten known offending specs as expected failures.
Success Criteria
cargo build --release -p t27c && t27c gen-verilog specs/fpga/formal.t27exits non-zero and prints a diagnostic mentioning width 0 or excessive width.cargo build --release -p t27c && t27c gen-verilog specs/memory/notebooklm.t27exits non-zero and prints a diagnostic mentioning width 0 or excessive width.cargo build --release -p t27c && t27c gen-verilog specs/compiler/stdlib.t27exits non-zero and prints a diagnostic mentioning excessive width.python tools/check_verilog_widths.pyexits 0 after the fix (baseline updated, no new findings).- Zero occurrences of
4294967295,18446744073709551615, or4198431as range upper bounds in anyt27c gen-verilogoutput across the three cited specs.
Activity
skill: `git log -S` without --all answered the wrong question
gate: no emitted Verilog signal is four billion bits wide (Refs #2566) (
verilog: clamp the zero width instead of refusing the struct
verilog: a zero width is not a width, and range_decl had no case for …
gHashTag commentedon Aug 23, 2026
Two of the three mechanisms are closed; the third is measured and is not a bug.
4294967295(u32::MAX)range_decl(0)— a struct of only&strsums to zero18446744073709551615(usize::MAX)total_width - 1wheretotal_width = dims[0] * elem_wanddims[0]is 0 —var notebooks : [0]Notebook, a legal empty list4198431The third is arithmetic that is correct
513 KiB in one packed register. The backend computed it faithfully. The spec asked for something with no hardware form.
So the remaining question is the same one this issue already carries about zero-width types, from the other end: should a type this large be lowerable to a packed register at all? Refusing would be defensible and would fail
specs/compiler/stdlib.t27, which is a ratchet decision with a cost — yours, not mine.The arithmetic is now written into
tools/verilog_width_baseline.txtbeside the entry, so the next reader does not go looking for a subtraction that is not there.What is left in the ledger
One line. The gate still fails on an eleventh spec acquiring an absurd width, which is what it is for.
verilog: a zero-length array is not a register of usize::MAX bits (#2605
gHashTag commentedon Sep 23, 2026
A boundary drafted from the paths this issue already names, so a bee can take it. Nothing is reserved until this is in the issue body - the Queen reads the body, not comments. Check the paths first: a wrong boundary reserves files the work does not own.
Boundary
specs/compiler/stdlib.t27specs/fpga/formal.t27specs/memory/notebooklm.t27bootstrap/src/compiler.rstools/check_verilog_widths.pytools/verilog_width_baseline.txtdmitrii-f-t27 commentedon Oct 3, 2026
Status on master f88f759: the two underflow mechanisms are fixed (formal.t27 and notebooklm.t27 emit no absurd ranges). The remaining entries in tools/verilog_width_baseline.txt are specs/compiler/stdlib.t27 and, since the const-extent fix, specs/base/ternary_memory.t27 (3801184 bits). The open item is the owner question in the title: should types with no realistic hardware width be lowerable at all?
Checked with t27c 0.4.0 built from f88f759; commands and inputs available on request.
gHashTag commentedon Oct 9, 2026
Filed under this issue: #8151 -- gen-verilog holds a module-level array of structs as one packed register (t27core's
nodesis 134,217,728 bits since #8020). The repair there is a memory,reg [W-1:0] name[0:N-1], the form arrays of primitives already get (W531). It does not touch the two struct-sized ledger lines (stdlib.t27, ternary_memory.t27): one element of millions of bits is the design question this issue still holds.gen-verilog: hold a module-level array of structs as a memory; t27cor…