diff --git a/NOW.md b/NOW.md index 5c248c2f6c..bd4a66913c 100644 --- a/NOW.md +++ b/NOW.md @@ -2,6 +2,13 @@ Last updated: 2026-08-08 +## fix(gen-verilog-sim) -- test-block reg decls + 64-bit __mul_noop (this PR, Closes #1894, Closes #1886) + +- StmtAssign test-block bindings ('h = f(...);') now get hoisted reg declarations (width-inferred, 64-bit fallback) -- iverilog could not bind them before; unlocks 11 tri-net ring specs +- __mul_noop widened to 64-bit in/out (128-bit acc, 64 iterations) -- u64 products no longer truncate to 32 bits; unlocks tri_gft_arith + 6 more money-layer specs +- Remaining tri-net blockers are spec-side (Verilog reserved words 'class'/'small' as identifiers; one stale spec test) -- fixed in tri-net +- FROZEN_HASH resealed per ceremony + ## fix(gen-verilog-sim) -- lower plain assert(cond, "msg") in testbenches (this PR, Closes #1888) - `assert` is not a Verilog-2005 keyword and the 2-arg form is not SystemVerilog; the TB emitted it verbatim, iverilog rejected the file -- icarus-simulate unusable for specs using standard `assert()` tests diff --git a/bootstrap/src/compiler.rs b/bootstrap/src/compiler.rs index 28aca0abe5..3a7afe30ad 100644 --- a/bootstrap/src/compiler.rs +++ b/bootstrap/src/compiler.rs @@ -6324,7 +6324,7 @@ impl VerilogCodegen { // Section: R-SI-1 compliant multiplication helper. // OpenLane R-SI-1 forbids the `*` operator in synthesizable RTL. - // t27c emits `__mul_noop(a, b)` instead of `a * b`; this 32-bit + // t27c emits `__mul_noop(a, b)` instead of `a * b`; this 64-bit // shift-and-add ladder is unconditionally injected so any module is // self-contained. self.write_indent(); @@ -6334,27 +6334,27 @@ impl VerilogCodegen { self.write_indent(); self.write_line("// -------------------------------------------------------"); self.write_indent(); - self.write_line("function [31:0] __mul_noop;"); + self.write_line("function [63:0] __mul_noop; // t27#1886: 64-bit, u64 products no longer truncate"); self.write_indent(); - self.write_line(" input [31:0] a;"); + self.write_line(" input [63:0] a;"); self.write_indent(); - self.write_line(" input [31:0] b;"); + self.write_line(" input [63:0] b;"); self.write_indent(); self.write_line(" integer i;"); self.write_indent(); - self.write_line(" reg [63:0] acc;"); + self.write_line(" reg [127:0] acc;"); self.write_indent(); self.write_line(" begin"); self.write_indent(); - self.write_line(" acc = 64'd0;"); + self.write_line(" acc = 128'd0;"); self.write_indent(); - self.write_line(" for (i = 0; i < 32; i = i + 1) begin"); + self.write_line(" for (i = 0; i < 64; i = i + 1) begin"); self.write_indent(); - self.write_line(" if (b[i]) acc = acc + ({32'd0, a} << i);"); + self.write_line(" if (b[i]) acc = acc + ({64'd0, a} << i);"); self.write_indent(); self.write_line(" end"); self.write_indent(); - self.write_line(" __mul_noop = acc[31:0];"); + self.write_line(" __mul_noop = acc[63:0];"); self.write_indent(); self.write_line(" end"); self.write_indent(); @@ -7657,6 +7657,41 @@ impl VerilogCodegen { self.call_array_tmp_materialized.clear(); if self.emit_test_assertions { self.predeclare_call_array_tmps(node, block_name); + // t27#1894: named test-block bindings (`h = f(...);`) parse as + // StmtAssign, not StmtLocal, so they never got a reg declaration + // and iverilog could not bind them. Declare every plain-identifier + // assign target once, width-inferred from its value (64-bit + // fallback), before any procedural statement. + { + let mut declared: std::collections::HashSet = + block_locals.iter().cloned().collect(); + let mut stack: Vec<&Node> = node.children.iter().collect(); + while let Some(stmt) = stack.pop() { + if stmt.kind == NodeKind::StmtAssign && stmt.children.len() >= 2 { + let target = &stmt.children[0]; + if target.kind == NodeKind::ExprIdentifier + && !target.name.is_empty() + && declared.insert(target.name.clone()) + { + let (width, signed) = self + .expr_width_signed(&stmt.children[1]) + .unwrap_or((64, false)); + let signed_kw = if signed { " signed" } else { "" }; + let decl = if width == 1 { + format!("reg{}", signed_kw) + } else { + format!("reg{} [{}:0]", signed_kw, width - 1) + }; + self.write_indent(); + self.write_line(&format!( + "{} {}; // t27#1894 test-block binding", + decl, target.name + )); + } + } + stack.extend(stmt.children.iter()); + } + } let mut probe_idx = 0usize; for child in &node.children { if child.kind == NodeKind::StmtLocal diff --git a/bootstrap/stage0/FROZEN_HASH b/bootstrap/stage0/FROZEN_HASH index 783cf7fdee..e5afb64c8f 100644 --- a/bootstrap/stage0/FROZEN_HASH +++ b/bootstrap/stage0/FROZEN_HASH @@ -1 +1 @@ -f74459fc23c3c54e7a3c5c0e72b45b53f560bee4a0633b903d54ff7d0dc714e1 +2bd87d0d139e26dabe74fed28c121a5941e402d5cab4be71e652ef1fb18da0c6 diff --git a/docs/NOW.md b/docs/NOW.md index 9746919a28..6961c1d13a 100644 --- a/docs/NOW.md +++ b/docs/NOW.md @@ -1,7 +1,13 @@ -# NOW — docs: localized the shared-core deep path (GftSadd=54, GftSmul=44) (2026-08-08) +# NOW — fix(gen-verilog-sim): test-block reg decls + 64-bit __mul_noop (2026-08-08) Last updated: 2026-08-08 +## fix(gen-verilog-sim): test-block reg declarations + 64-bit __mul_noop (Closes #1894, Closes #1886) + +- Named test-block bindings (`h = f(...);`) parse as StmtAssign, not StmtLocal, so `gen_verilog_probe_prelude` never declared them and iverilog could not bind the names -- 21 of tri-net's 26 lowerable ring specs failed to compile. Every plain-identifier assign target is now declared once (recursively collected, width-inferred from the value expression, 64-bit fallback) before any procedural statement +- The injected R-SI-1 `__mul_noop` helper was `function [31:0]` with `[31:0]` inputs: every u64 multiply silently truncated, so GF-T32/64 mantissa products computed wrong in simulation while gen-rust and the silicon KATs are correct. Widened to `[63:0]` in/out with a `[127:0]` accumulator over 64 iterations; 32-bit callers unchanged +- tri-net icarus KAT run goes 5/26 -> 23/26 locally; the last three blockers are tri-net spec-side (Verilog reserved words `class`/`packed`/`small` as identifiers, one stale test) and fixed there. Bootstrap unit suite 1537/1537. FROZEN_HASH resealed per ceremony + ## docs: localized the shared-core critical depth — pipeline the normalize/round cascade, not the multiplier (Refs #1764) - Measured where the deep combinational path actually is, so the pipeline cut lands in the right place. `GftSmul` is purely combinational (`assign result = smul(a,b)`; clk/en/ready unused → no read-before-ready bug). Yosys `ltp` puts the shared-core critical depth at `GftSadd` = 54 and `GftSmul` = 44