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
82 changes: 82 additions & 0 deletions bootstrap/src/compiler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17889,6 +17889,81 @@ impl Compiler {
Ok(())
}

/// #2275: rewrite `[CONST_NAME]` array dimensions to their literal values
/// in every `extra_type`/`extra_size` string. Only module-level consts whose
/// initializer is a bare integer literal participate; anything else is left
/// exactly as written.
fn resolve_symbolic_dims(ast: &mut Node) {
let mut consts: std::collections::HashMap<String, String> =
std::collections::HashMap::new();
fn literal_value(decl: &Node) -> Option<String> {
let mut v = decl.value.trim().to_string();
if v.is_empty() {
for c in &decl.children {
let cv = c.value.trim();
if !cv.is_empty() {
v = cv.to_string();
break;
}
}
}
let cleaned: String = v.chars().filter(|c| *c != '_').collect();
cleaned.parse::<usize>().ok().map(|n| n.to_string())
}
fn collect(node: &Node, out: &mut std::collections::HashMap<String, String>) {
if node.kind == NodeKind::ConstDecl && !node.name.is_empty() {
if let Some(v) = literal_value(node) {
out.entry(node.name.clone()).or_insert(v);
}
}
for c in &node.children {
collect(c, out);
}
}
collect(ast, &mut consts);
if consts.is_empty() {
return;
}
fn subst(text: &str, consts: &std::collections::HashMap<String, String>) -> String {
if !text.contains('[') {
return text.to_string();
}
let mut out = String::with_capacity(text.len());
let mut rest = text;
while let Some(open) = rest.find('[') {
let Some(close_rel) = rest[open..].find(']') else {
break;
};
let close = open + close_rel;
let inner = rest[open + 1..close].trim();
out.push_str(&rest[..open + 1]);
match consts.get(inner) {
Some(v) if !inner.is_empty() => out.push_str(v),
_ => out.push_str(&rest[open + 1..close]),
}
out.push(']');
rest = &rest[close + 1..];
}
out.push_str(rest);
out
}
fn walk(node: &mut Node, consts: &std::collections::HashMap<String, String>) {
if node.extra_type.contains('[') {
node.extra_type = subst(&node.extra_type, consts);
}
if !node.extra_size.is_empty() {
node.extra_size = subst(&format!("[{}]", node.extra_size), consts)
.trim_start_matches('[')
.trim_end_matches(']')
.to_string();
}
for c in &mut node.children {
walk(c, consts);
}
}
walk(ast, &consts);
}

fn collect_struct_decls(
ast: &Node,
) -> std::collections::HashMap<String, Vec<(String, String)>> {
Expand Down Expand Up @@ -17944,6 +18019,13 @@ impl Compiler {
// Build the struct map from the *full* module AST so nested function bodies
// can still recognize module-level scalar structs as lowerable.
let structs = Self::collect_struct_decls(&ast);
// #2275 half two, layer one: array dimensions spelled as CONST NAMES
// ([NUM_MAC_UNITS]MACUnit) never parsed -- parse_array_type wants
// digits, so the whole packed-array machinery (decl width, literal
// lowering, element access) fell through to TODO placeholders and
// flattened names. Substitute integer-literal module consts into
// every type/dimension string BEFORE codegen, once, here.
Self::resolve_symbolic_dims(&mut ast);
Self::detect_unsupported_verilog_locals(&ast, &structs)?;
optimize(&mut ast, &OptConfig::default());
let mut codegen = VerilogCodegen::with_options(emit_test_assertions);
Expand Down
2 changes: 1 addition & 1 deletion bootstrap/stage0/FROZEN_HASH
Original file line number Diff line number Diff line change
@@ -1 +1 @@
7c99ba252ee218df2e00bb3708e53f96ea5f64ff66d80542aecf2010682272a9
ee73b41dba8f40eecd30057a6147bcb1f9fd470430fe4812a0de36fdbdcfb630
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# NOW -- symbolic array dims resolve; mac.v elaborates with zero errors (2026-08-22)

## const-name dimensions substitute before codegen (Closes #2275)

- The second half of the mac defect was one gate, not many: array dimensions
spelled as const names (`[NUM_MAC_UNITS]MACUnit`) never parsed -- the whole
packed-array machinery keys off `parse_array_type`, which wants digits -- so
the AoS declaration emitted `reg [31:0]` plus a TODO and every element-field
access flattened to an unbound name.
- `resolve_symbolic_dims` now substitutes integer-literal module consts into
every type/dimension string once, before codegen. mac_units becomes a real
`reg [1343:0]` (8x168) with per-element part-select initializers, and
**iverilog elaborates mac.v with zero errors** for the first time.
- The folded struct-literal initializer writes 0 with a comment; for this spec
that is exact (every field of the literal is zero / STATUS_READY = 0).
- Blast radius measured before landing: 13 specs corpus-wide carry symbolic
dims (2 under specs/fpga). Full 32-module yosys smoke stays 32/32. M5
performed.
- Unlocks #2241: the conformance vvp lane's blocker module now elaborates.
Loading