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
8 changes: 8 additions & 0 deletions NOW.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,14 @@

Last updated: 2026-08-08

## gen-verilog: test-block for-loops emitted; loop-variant calls not CSE-hoisted; tail expressions (Refs #1948)

- `for i in ..` / `while` in a test block was dropped as `// (stmt: StmtForRange)`, silently voiding loop bodies that accumulate assertions. Loops emit now (loop var declared `integer`)
- The call-CSE pass hoisted a call from inside a loop into ONE temp evaluated before the loop with an uninitialized index -- the loop then tested a constant. predeclare no longer recurses into loop bodies, so loop-variant calls are emitted inline per iteration
- A Rust-style tail expression (a fn body ending in a bare expression) lowers to an implicit return assignment `<fn> = <expr>;` instead of a bare `expr;` (an unknown-task enable)
- tri-net corpus: icarus 97 -> 98 (m3_multihop); cross_layer_optimizer already joined
- FROZEN_HASH resealed

## gen-verilog: tuple-destructure temp declared in test blocks (Refs #1948)

- A test-block `let (a, b) = call()` slices a packed temp `__tup_l{line}`, but that temp (and the element regs) are declared only in emit_local's Decl phase, which the TB Init-only path skips -- the temp was referenced undeclared ("Could not find variable __tup_l242")
Expand Down
55 changes: 55 additions & 0 deletions bootstrap/src/compiler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5201,6 +5201,15 @@ impl VerilogCodegen {
}
}
}
// Do NOT recurse into loop bodies: a call there runs per iteration and
// may depend on the loop variable, so it must be emitted inline each
// pass, never hoisted into one loop-invariant temporary (t27#1948).
if matches!(
node.kind,
NodeKind::StmtForRange | NodeKind::StmtWhile | NodeKind::StmtFor
) {
return;
}
for child in &node.children {
self.predeclare_call_array_tmps(child, block_name);
}
Expand Down Expand Up @@ -8283,6 +8292,25 @@ impl VerilogCodegen {
self.write_line("end");
return;
}
// A final bare expression is a Rust-style tail expression --
// the function's implicit return value. Verilog has no tail
// expressions, so it emitted `expr;` (a bare statement iverilog
// reads as a task-enable). Lower the LAST StmtExpr to an implicit
// return assignment `<fn> = <expr>;` (t27#1948).
let is_tail_expr = idx + 1 == stmts.len()
&& stmt.kind == NodeKind::StmtExpr
&& !stmt.children.is_empty()
&& !self.current_fn_name.is_empty()
&& self.current_fn_return_type != "void";
if is_tail_expr {
self.write_indent();
let asn = if self.clocked_nonblocking { " <= " } else { " = " };
self.write(&self.current_fn_name.clone());
self.write(asn);
self.gen_verilog_expr(&stmt.children[0]);
self.write_line(";");
continue;
}
// #1741: top-level locals had their `reg` declaration hoisted, so
// emit only the assignment here.
if self.hoist_fn_locals && stmt.kind == NodeKind::StmtLocal {
Expand Down Expand Up @@ -8447,6 +8475,19 @@ impl VerilogCodegen {
}
}
}
// A `for i in ..` loop variable needs an `integer`
// declaration at the block top (t27#1948, TB for-loop
// support). The body statements are walked via `stack`.
if stmt.kind == NodeKind::StmtForRange
&& !stmt.name.is_empty()
&& declared.insert(stmt.name.clone())
{
self.write_indent();
self.write_line(&format!(
"integer {}; // t27#1948 loop variable",
Self::verilog_safe_identifier(&stmt.name)
));
}
// Untyped (or nested) `let` locals never reached a reg
// declaration either -- iverilog cannot bind them
// (t27#1948, the biggest testbench compile class).
Expand Down Expand Up @@ -8807,6 +8848,12 @@ impl VerilogCodegen {
self.write_indent();
self.gen_verilog_stmt(node);
}
NodeKind::StmtForRange
| NodeKind::StmtWhile
| NodeKind::StmtFor => {
self.materialize_call_array_tmps_in_expr(node);
self.gen_verilog_stmt(node);
}
_ => {
self.write_indent();
self.write_line(&format!("// (stmt: {:?})", node.kind));
Expand Down Expand Up @@ -8893,6 +8940,14 @@ impl VerilogCodegen {
// from a call (`array = create(...)`) stayed X in the TB.
self.gen_verilog_stmt(node);
}
NodeKind::StmtForRange
| NodeKind::StmtWhile
| NodeKind::StmtFor => {
// Control flow in a test block: a `for`/`while`/`if` was
// dropped as `// (stmt: StmtForRange)`, silently voiding
// loop bodies that accumulate assertions (t27#1948).
self.gen_verilog_stmt(node);
}
_ => {
self.write_indent();
self.write_line(&format!("// (stmt: {:?})", node.kind));
Expand Down
2 changes: 1 addition & 1 deletion bootstrap/stage0/FROZEN_HASH
Original file line number Diff line number Diff line change
@@ -1 +1 @@
df2991ce372f7f3ecb9de196116662a7a40f2490cdeeb75ca77b2df18dfbb5fd
871ec3135001a0270d7afecf1fc384595dfa5a2fd15dbf4e25a4997983a18605
8 changes: 8 additions & 0 deletions docs/NOW.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,14 @@

Last updated: 2026-08-08

## gen-verilog: test-block for-loops emitted; loop-variant calls not CSE-hoisted; tail expressions (Refs #1948)

- `for i in ..` / `while` in a test block was dropped as `// (stmt: StmtForRange)`, silently voiding loop bodies that accumulate assertions. Loops emit now (loop var declared `integer`)
- The call-CSE pass hoisted a call from inside a loop into ONE temp evaluated before the loop with an uninitialized index -- the loop then tested a constant. predeclare no longer recurses into loop bodies, so loop-variant calls are emitted inline per iteration
- A Rust-style tail expression (a fn body ending in a bare expression) lowers to an implicit return assignment `<fn> = <expr>;` instead of a bare `expr;` (an unknown-task enable)
- tri-net corpus: icarus 97 -> 98 (m3_multihop); cross_layer_optimizer already joined
- FROZEN_HASH resealed

## gen-verilog: tuple-destructure temp declared in test blocks (Refs #1948)

- A test-block `let (a, b) = call()` slices a packed temp `__tup_l{line}`, but that temp (and the element regs) are declared only in emit_local's Decl phase, which the TB Init-only path skips -- the temp was referenced undeclared ("Could not find variable __tup_l242")
Expand Down
Loading