Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions NOW.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
53 changes: 44 additions & 9 deletions bootstrap/src/compiler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand All @@ -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();
Expand Down Expand Up @@ -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<String> =
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
Expand Down
2 changes: 1 addition & 1 deletion bootstrap/stage0/FROZEN_HASH
Original file line number Diff line number Diff line change
@@ -1 +1 @@
f74459fc23c3c54e7a3c5c0e72b45b53f560bee4a0633b903d54ff7d0dc714e1
2bd87d0d139e26dabe74fed28c121a5941e402d5cab4be71e652ef1fb18da0c6
8 changes: 7 additions & 1 deletion docs/NOW.md
Original file line number Diff line number Diff line change
@@ -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
Expand Down
Loading