diff --git a/NOW.md b/NOW.md index 02f62978c..e58762481 100644 --- a/NOW.md +++ b/NOW.md @@ -2,6 +2,13 @@ Last updated: 2026-08-08 +## gen-verilog: tuple literal elements and destructure regs width-cast (Refs #1948) + +- A tuple return `(1, true)` packed each element via gen_verilog_expr -- a bare literal is unsized and iverilog rejected it in the concatenation ("operand has indefinite width"). Elements are width-cast to their declared tuple-element type now +- Tuple-DESTRUCTURE binding regs (`(a,b,c,d) = call()`) were declared 64-bit each, so `{d,c,b,a} = <32-bit>` sliced the packed return wrongly. Each element reg is declared at its element width from the callee's return tuple type +- tri-net corpus: icarus 88 -> 94 (fpga_synthesis_report, hello, integration_tests, lite_crypto, mesh_routing, packet_loss_injection) +- FROZEN_HASH resealed + ## gen-verilog: hex/bin literals in array-literal text normalized; more SV keywords (Refs #1948) - The array-literal concat path works on raw source TEXT (not AST), so `0x38`/`0b..` element literals reached Verilog verbatim -- illegal (`8'(0x38)`). A text-level normalizer converts them to decimal diff --git a/bootstrap/src/compiler.rs b/bootstrap/src/compiler.rs index e3bb9019a..80a8eaba2 100644 --- a/bootstrap/src/compiler.rs +++ b/bootstrap/src/compiler.rs @@ -8397,25 +8397,47 @@ impl VerilogCodegen { )); } // Tuple-destructure binding `(a, b) = call()`: every - // element identifier needs a reg too (t27#1948). + // element identifier needs a reg at its ELEMENT width. + // A 64-bit default made `{d,c,b,a} = call()` slice the + // 32-bit packed return wrongly (t27#1948). if target.kind == NodeKind::ExprTuple { - let elems: Vec = target + // Element types from the callee's tuple return type. + let elem_types: Vec = stmt .children - .iter() - .filter(|e| { - e.kind == NodeKind::ExprIdentifier - && !e.name.is_empty() - && e.name != "_" + .get(1) + .filter(|c| c.kind == NodeKind::ExprCall) + .and_then(|c| self.fn_return_types.get(&c.name)) + .map(|rt| { + let t = rt.trim(); + if t.starts_with('(') && t.ends_with(')') { + t[1..t.len() - 1] + .split(',') + .map(|e| e.trim().to_string()) + .collect() + } else { + Vec::new() + } }) - .map(|e| e.name.clone()) - .collect(); - for name in elems { - if declared.insert(name.clone()) { + .unwrap_or_default(); + for (idx, e) in target.children.iter().enumerate() { + if e.kind != NodeKind::ExprIdentifier + || e.name.is_empty() + || e.name == "_" + { + continue; + } + if declared.insert(e.name.clone()) { + let (w, signed) = elem_types + .get(idx) + .map(|t| { + (self.packed_width(t).max(1), self.packed_signed(t)) + }) + .unwrap_or((64, false)); self.write_indent(); self.write_line(&format!( "{} {}; // t27#1948 tuple binding", - reg_decl(64, false), - Self::verilog_safe_identifier(&name) + reg_decl(w, signed), + Self::verilog_safe_identifier(&e.name) )); } } @@ -9836,14 +9858,42 @@ impl VerilogCodegen { NodeKind::ExprTuple => { // Tuple literal -> packed concatenation with element 0 in the // LSB: `(e0, e1)` -> `{e1, e0}`. Matches the packed_width sum and - // the destructuring slice order. + // the destructuring slice order. Each element is WIDTH-CAST to + // its declared type: a bare literal (`return (1, true)`) is + // unsized in Verilog and iverilog rejects it in a concatenation + // ("operand has indefinite width", t27#1948). + let elem_types: Option> = { + let t = self.current_fn_return_type.trim(); + if t.starts_with('(') && t.ends_with(')') && t.contains(',') { + Some( + t[1..t.len() - 1] + .split(',') + .map(|e| e.trim().to_string()) + .collect(), + ) + } else { + None + } + }; self.write("{"); let last = node.children.len().saturating_sub(1); for (i, child) in node.children.iter().enumerate().rev() { if i != last { self.write(", "); } - self.gen_verilog_expr(child); + let cast = elem_types + .as_ref() + .filter(|ts| ts.len() == node.children.len()) + .map(|ts| { + let ty = ts[i].trim(); + (self.packed_width(ty).max(1), self.packed_signed(ty)) + }); + if let Some((w, signed)) = cast { + let v = self.emit_packed_scalar_value(child, w, signed); + self.write(&v); + } else { + self.gen_verilog_expr(child); + } } self.write("}"); } diff --git a/bootstrap/stage0/FROZEN_HASH b/bootstrap/stage0/FROZEN_HASH index aa2d08387..925fbbe34 100644 --- a/bootstrap/stage0/FROZEN_HASH +++ b/bootstrap/stage0/FROZEN_HASH @@ -1 +1 @@ -1cb67e85167378d36862a7f1294e1798f9bb8b432e77cec5a30584bc6ad3bbf3 +3efebe5d3fb17c2f4de99d613d2bcc9ea3a41de0c1f2a4baa86a251aa5965406 diff --git a/docs/NOW.md b/docs/NOW.md index 2637faca2..185d2a733 100644 --- a/docs/NOW.md +++ b/docs/NOW.md @@ -2,6 +2,13 @@ Last updated: 2026-08-08 +## gen-verilog: tuple literal elements and destructure regs width-cast (Refs #1948) + +- A tuple return `(1, true)` packed each element via gen_verilog_expr -- a bare literal is unsized and iverilog rejected it in the concatenation ("operand has indefinite width"). Elements are width-cast to their declared tuple-element type now +- Tuple-DESTRUCTURE binding regs (`(a,b,c,d) = call()`) were declared 64-bit each, so `{d,c,b,a} = <32-bit>` sliced the packed return wrongly. Each element reg is declared at its element width from the callee's return tuple type +- tri-net corpus: icarus 88 -> 94 (fpga_synthesis_report, hello, integration_tests, lite_crypto, mesh_routing, packet_loss_injection) +- FROZEN_HASH resealed + ## gen-verilog: hex/bin literals in array-literal text normalized; more SV keywords (Refs #1948) - The array-literal concat path works on raw source TEXT (not AST), so `0x38`/`0b..` element literals reached Verilog verbatim -- illegal (`8'(0x38)`). A text-level normalizer converts them to decimal diff --git a/specs/fpga/bridge.v b/specs/fpga/bridge.v index 33d67667b..b495d6c6c 100644 --- a/specs/fpga/bridge.v +++ b/specs/fpga/bridge.v @@ -126,12 +126,12 @@ module FPGA_Bridge ( reg __t27_ret; __t27_ret = 1'b0; if ((tail == size)) begin - buffer_read = {0, 0}; + buffer_read = {32'd0, 8'd0}; __t27_ret = 1'b1; end else begin data = \buf [tail]; new_tail = ((tail + 1) % size); - buffer_read = {new_tail, data}; + buffer_read = {32'(new_tail), 8'(data)}; __t27_ret = 1'b1; end end