use super::layout::{LayoutError, build_template_layout};
use super::{SlotClass, StateLayout};
use crate::netlist::TemplateKey;
use std::collections::HashMap;
use std::collections::hash_map::DefaultHasher;
use std::hash::{Hash, Hasher};
use sv_lang::kinds::SymbolKind;
use sv_lang::{Design, Symbol};
pub struct TemplateTable<'d> {
templates: Vec<(TemplateKey, StateLayout)>,
representatives: Vec<Symbol<'d>>,
instance_order: Vec<TemplateKey>,
}
impl<'d> TemplateTable<'d> {
pub fn build(design: &'d Design) -> Result<TemplateTable<'d>, LayoutError> {
let mut table = TemplateTable {
templates: Vec::new(),
representatives: Vec::new(),
instance_order: Vec::new(),
};
let mut definitions: Vec<Symbol<'d>> = Vec::new();
let mut buckets: HashMap<TemplateKey, usize> = HashMap::new();
for top in design.top_instances() {
table.register_instance(top, &mut definitions, &mut buckets)?;
}
Ok(table)
}
fn walk_children(
&mut self,
body: Symbol<'d>,
definitions: &mut Vec<Symbol<'d>>,
buckets: &mut HashMap<TemplateKey, usize>,
) -> Result<(), LayoutError> {
for member in body.members() {
match member.kind() {
SymbolKind::Instance => self.register_instance(member, definitions, buckets)?,
SymbolKind::InstanceArray => {
for elem in member.instance_array_elements() {
self.register_array_element(elem, definitions, buckets)?;
}
}
SymbolKind::GenerateBlock => {
if !member.generate_block_is_uninstantiated() {
self.walk_children(member, definitions, buckets)?;
}
}
SymbolKind::GenerateBlockArray => {
for entry in member.generate_block_array_entries() {
if !entry.generate_block_is_uninstantiated() {
self.walk_children(entry, definitions, buckets)?;
}
}
}
_ => {}
}
}
Ok(())
}
fn register_array_element(
&mut self,
elem: Symbol<'d>,
definitions: &mut Vec<Symbol<'d>>,
buckets: &mut HashMap<TemplateKey, usize>,
) -> Result<(), LayoutError> {
match elem.kind() {
SymbolKind::Instance => self.register_instance(elem, definitions, buckets)?,
SymbolKind::InstanceArray => {
for nested in elem.instance_array_elements() {
self.register_array_element(nested, definitions, buckets)?;
}
}
_ => {}
}
Ok(())
}
fn register_instance(
&mut self,
inst: Symbol<'d>,
definitions: &mut Vec<Symbol<'d>>,
buckets: &mut HashMap<TemplateKey, usize>,
) -> Result<(), LayoutError> {
let body = inst
.instance_body()
.expect("an Instance symbol always has a body");
let layout = build_template_layout(&body)?;
let definition = body
.instance_body_definition()
.expect("an InstanceBody symbol always has a Definition");
let definition_index = match definitions.iter().position(|d| *d == definition) {
Some(i) => i as u32,
None => {
definitions.push(definition);
(definitions.len() - 1) as u32
}
};
let mut key = TemplateKey {
definition: definition_index,
params_hash: structural_shape_hash(&layout),
};
let resolved_index = loop {
match buckets.get(&key) {
Some(&idx) if body.instance_body_has_same_type(&self.representatives[idx]) => {
break idx;
}
Some(_) => {
key.params_hash = key.params_hash.wrapping_add(0x9E37_79B9_7F4A_7C15);
}
None => {
let idx = self.templates.len();
self.templates.push((key, layout));
self.representatives.push(body);
buckets.insert(key, idx);
break idx;
}
}
};
let resolved_key = self.templates[resolved_index].0;
self.instance_order.push(resolved_key);
self.walk_children(body, definitions, buckets)
}
pub fn layout_of(&self, key: &TemplateKey) -> Option<&StateLayout> {
self.templates
.iter()
.find(|(k, _)| k == key)
.map(|(_, layout)| layout)
}
pub fn walk_order(&self) -> impl Iterator<Item = &TemplateKey> {
self.instance_order.iter()
}
}
fn slot_class_discriminant(class: SlotClass) -> u8 {
match class {
SlotClass::Inline => 0,
SlotClass::Wide => 1,
SlotClass::Memory => 2,
SlotClass::ForceWide => 3,
}
}
fn structural_shape_hash(layout: &StateLayout) -> u64 {
let mut hasher = DefaultHasher::new();
layout.slots.len().hash(&mut hasher);
for slot in &layout.slots {
slot_class_discriminant(slot.offset.class).hash(&mut hasher);
slot.offset.bit_width.hash(&mut hasher);
slot.offset.elem_word_count.hash(&mut hasher);
slot.offset.row_stride_words.hash(&mut hasher);
slot.offset.num_rows.hash(&mut hasher);
}
layout.inline_words.hash(&mut hasher);
layout.wide_words.hash(&mut hasher);
hasher.finish()
}
#[cfg(test)]
mod tests {
use super::*;
use crate::state::value::compile_source;
#[test]
fn two_textually_identical_instances_share_one_template() {
let design = compile_source(
"module leaf; logic x; endmodule\n module top; leaf a(); leaf b(); endmodule\n",
)
.expect("valid SV source compiles");
let table = TemplateTable::build(&design).expect("dedup walk builds");
assert_eq!(
table.templates.len(),
2,
"top's own (zero-slot) template plus leaf's one shared template"
);
let keys: Vec<TemplateKey> = table.walk_order().copied().collect();
assert_eq!(
keys.len(),
3,
"one TemplateKey per concrete instance: top itself, a, b"
);
assert_eq!(
keys[1], keys[2],
"leaf a() and leaf b() must dedup into the same template"
);
assert_ne!(
keys[0], keys[1],
"top's own template must be distinct from leaf's"
);
}
#[test]
fn differently_parameterized_instances_get_distinct_templates() {
let design = compile_source(
"module leaf #(parameter int W=1); logic [W-1:0] x; endmodule\n\
module top; leaf #(.W(4)) a(); leaf #(.W(8)) b(); endmodule\n",
)
.expect("valid SV source compiles");
let table = TemplateTable::build(&design).expect("dedup walk builds");
assert_eq!(
table.templates.len(),
3,
"top's own zero-slot template plus two differently-parameterized leaf templates"
);
let mut widths: Vec<u32> = table
.templates
.iter()
.filter_map(|(_, layout)| layout.slots.first().map(|s| s.offset.bit_width))
.collect();
widths.sort_unstable();
assert_eq!(
widths,
vec![4, 8],
"the two templates' single slot must reflect W=4 and W=8 respectively"
);
}
#[test]
fn building_twice_from_the_same_design_assigns_identical_offsets() {
let design = compile_source(
"module leaf #(parameter int W=1); logic [W-1:0] x; endmodule\n\
module top; leaf #(.W(4)) a(); leaf #(.W(8)) b(); leaf #(.W(4)) c(); endmodule\n",
)
.expect("valid SV source compiles");
let first = TemplateTable::build(&design).expect("first dedup walk builds");
let second = TemplateTable::build(&design).expect("second dedup walk builds");
assert_eq!(first.templates.len(), second.templates.len());
assert_eq!(first.instance_order, second.instance_order);
for ((k1, l1), (k2, l2)) in first.templates.iter().zip(second.templates.iter()) {
assert_eq!(k1, k2);
assert_eq!(l1.slots.len(), l2.slots.len());
assert_eq!(l1.inline_words, l2.inline_words);
assert_eq!(l1.wide_words, l2.wide_words);
for (s1, s2) in l1.slots.iter().zip(l2.slots.iter()) {
assert_eq!(s1.offset.class, s2.offset.class);
assert_eq!(s1.offset.word_offset, s2.offset.word_offset);
assert_eq!(s1.offset.bit_width, s2.offset.bit_width);
}
}
}
#[test]
fn sharing_a_template_means_sharing_layout_never_storage() {
let design = compile_source(
"module leaf; logic x; endmodule\n module top; leaf a(); leaf b(); endmodule\n",
)
.expect("valid SV source compiles");
let table = TemplateTable::build(&design).expect("dedup walk builds");
assert_eq!(
table.templates.len(),
2,
"top's own (zero-slot) template plus leaf's one shared template"
);
let keys: Vec<TemplateKey> = table.walk_order().copied().collect();
assert_eq!(
keys.len(),
3,
"one TemplateKey per concrete instance (top itself, a, b)"
);
assert_eq!(
keys[1], keys[2],
"a and b must resolve to the same TemplateKey"
);
let from_a = table
.layout_of(&keys[1])
.expect("a's template has a layout");
let from_b = table
.layout_of(&keys[2])
.expect("b's template has a layout");
assert!(
std::ptr::eq(from_a, from_b),
"layout_of must hand back the exact same StateLayout, never a re-derived copy"
);
}
#[test]
fn labeled_generate_for_loop_instances_are_discovered_and_deduped() {
let design = compile_source(
"module leaf; logic x; endmodule\n\
module top;\n\
genvar i;\n\
generate\n\
for (i = 0; i < 3; i = i + 1) begin : gf\n\
leaf u();\n\
end\n\
endgenerate\n\
endmodule\n",
)
.expect("valid SV source compiles");
let table = TemplateTable::build(&design).expect("dedup walk builds");
assert_eq!(
table.templates.len(),
2,
"top's own zero-slot template plus the one leaf template all 3 generate-for instances share"
);
assert_eq!(
table.instance_order.len(),
4,
"one TemplateKey per concrete instance: top itself plus 3 generate-for instances"
);
}
#[test]
fn unlabeled_generate_for_loop_instances_are_discovered() {
let design = compile_source(
"module leaf; logic x; endmodule\n\
module top;\n\
genvar i;\n\
generate\n\
for (i = 0; i < 3; i = i + 1) begin\n\
leaf u();\n\
end\n\
endgenerate\n\
endmodule\n",
)
.expect("valid SV source compiles");
let table = TemplateTable::build(&design).expect("dedup walk builds");
assert_eq!(
table.templates.len(),
2,
"top's own zero-slot template plus the one leaf template all 3 generate-for instances share"
);
assert_eq!(
table.instance_order.len(),
4,
"one TemplateKey per concrete instance: top itself plus 3 generate-for instances"
);
}
#[test]
fn if_generate_conditional_instance_is_discovered_and_untaken_branch_contributes_nothing() {
let design = compile_source(
"module leaf; logic x; endmodule\n\
module top;\n\
generate\n\
if (1) begin : g_taken\n\
leaf a();\n\
end else begin : g_untaken\n\
leaf b();\n\
end\n\
endgenerate\n\
endmodule\n",
)
.expect("valid SV source compiles");
let table = TemplateTable::build(&design).expect("dedup walk builds");
assert_eq!(
table.templates.len(),
2,
"top's own zero-slot template plus the taken if-generate branch's leaf template"
);
assert_eq!(
table.instance_order.len(),
2,
"top itself plus the one taken-branch instance; the untaken else-generate branch must contribute zero"
);
}
#[test]
fn multi_dimensional_instance_array_elements_are_discovered_without_panicking() {
let design = compile_source(
"module leaf; logic x; endmodule\n\
module top;\n\
leaf arr[1:0][1:0]();\n\
endmodule\n",
)
.expect("valid SV source compiles");
let table = TemplateTable::build(&design).expect("dedup walk builds");
assert_eq!(
table.templates.len(),
2,
"top's own zero-slot template plus the one template all 4 array elements dedup into"
);
assert_eq!(
table.instance_order.len(),
5,
"top itself plus one TemplateKey per concrete array element (2x2 = 4)"
);
}
#[test]
fn top_level_modules_own_members_get_a_state_layout() {
let design = compile_source(
"module leaf; logic y; endmodule\n\
module top; logic clk; leaf a(); endmodule\n",
)
.expect("valid SV source compiles");
let table = TemplateTable::build(&design).expect("dedup walk builds");
let keys: Vec<TemplateKey> = table.walk_order().copied().collect();
assert_eq!(
keys.len(),
2,
"one TemplateKey per concrete instance: top itself, a"
);
assert_ne!(
keys[0], keys[1],
"top's own template (with `clk`) must differ from leaf's (with `y`)"
);
let top_layout = table
.layout_of(&keys[0])
.expect("top's own body must have a registered StateLayout");
assert_eq!(
top_layout.slots.len(),
1,
"top's own `clk` variable must be captured as a slot, not silently dropped"
);
let leaf_layout = table
.layout_of(&keys[1])
.expect("leaf's body must have a registered StateLayout");
assert_eq!(
leaf_layout.slots.len(),
1,
"leaf's own `y` variable must still be captured as a slot"
);
}
}