sv_sim_sim/netlist/instance.rs
1//! The undeduped instance tree (NL-002, NL-010): one `InstanceNode` per
2//! elaborated instance, distinct even when two instances share a compiled
3//! `Template` (dedup lands in a later task). Task 4 (NL-023) adds
4//! `array_path` and its derivation, [`array_path_of`].
5
6use crate::netlist::{ElabError, InstanceId, TemplateId};
7
8/// One elaborated instance. `children` is populated in DFS visitation order
9/// (== declaration order), never re-sorted.
10#[derive(Debug, Clone)]
11pub struct InstanceNode {
12 pub id: InstanceId,
13 pub template: TemplateId,
14 pub parent: Option<InstanceId>,
15 pub name: String,
16 /// **Two distinct sources, both folded into one field** (NL-023;
17 /// Deviation #7 in the plan): `Symbol::instance_array_path()` is
18 /// non-empty only for genuine instance-array syntax (`modname
19 /// name[hi:lo](...)` applied to the instance's own declaration) — it is
20 /// empty for every instance inside a `generate for`, because a loop
21 /// iteration's index lives on the *enclosing* `GenerateBlockArray`'s
22 /// `GenerateBlock` entries, not on the child instance itself. So
23 /// `array_path` is built by `array_path_of` (this module, crate-private) as:
24 /// `instance_array_path().collect()` if non-empty (true instance-array
25 /// syntax); **else** walk `Symbol::parent()` outward from the instance
26 /// collecting `generate_block_array_index()` from every enclosing
27 /// `GenerateBlock` produced by loop-generate (skipping non-loop
28 /// `GenerateBlock`s, e.g. an `if`/`case`-generate branch, which have no
29 /// array index), stopping at the innermost enclosing `InstanceBody` (or,
30 /// for a top-level instance with no enclosing `InstanceBody` at all, at
31 /// the design's `Root`), then reversing (outermost loop first) — empty
32 /// `Vec` if the instance is neither array-instantiated nor inside any
33 /// loop-generate.
34 pub array_path: Vec<u32>,
35 pub children: Vec<InstanceId>,
36}
37
38/// Builds `InstanceNode::array_path` per the derivation documented on that
39/// field. `instance` is the `Instance`/`PrimitiveInstance`/`CheckerInstance`
40/// symbol being visited during the DFS (Task 1). Like every other
41/// `sv_lang_kinds::SymbolKind` match this pipeline performs, the parent-walk
42/// below is exhaustive over the kinds that can legitimately appear and routes
43/// anything else to a typed `ElabError::UnsupportedConstruct` — never a
44/// silent no-op (plan "Global constraints"; ARCHITECTURE.md §7 item 9) — and
45/// a folded genvar value this function cannot represent as a valid array
46/// index (non-integer, or negative) is a typed `ElabError::Frontend`, never a
47/// silently-wrapped `u32`.
48pub(crate) fn array_path_of(instance: sv_lang::Symbol<'_>) -> Result<Vec<u32>, ElabError> {
49 let via_instance_array: Vec<u32> = instance.instance_array_path().collect();
50 if !via_instance_array.is_empty() {
51 return Ok(via_instance_array);
52 }
53
54 // Not true instance-array syntax: walk outward through enclosing scopes
55 // collecting the loop-iteration index of every `GenerateBlock` produced
56 // by a `generate for`, stopping at the innermost enclosing
57 // `InstanceBody` (never crossing into a *different* instance's own
58 // scope) — or, for a *top-level* instance (no enclosing `InstanceBody`
59 // at all: `Symbol::parent()` goes straight from the `Instance` to the
60 // design's `Root`), stopping at `Root` instead. A `GenerateBlockArray`'s
61 // own `.parent()` (if this walk passes through one on the way up) is not
62 // itself a `GenerateBlock`, so it is explicitly named as a benign
63 // pass-through kind and contributes nothing itself.
64 // `InstanceBody`/`Root`/`GenerateBlockArray`/`GenerateBlock` are the only
65 // kinds legal SV can ever interpose between an instance and its
66 // enclosing module body (or the design root) on this walk; any other
67 // kind reaching here would mean a construct this pass does not
68 // understand is standing between the instance and its true array
69 // position, so it is a typed error, not a silently-truncated
70 // `array_path`.
71 let mut indices: Vec<u32> = Vec::new();
72 let mut current = instance.parent();
73 while let Some(sym) = current {
74 match sym.kind() {
75 sv_lang_kinds::SymbolKind::InstanceBody | sv_lang_kinds::SymbolKind::Root => break,
76 // Stepped over: contributes no index of its own.
77 sv_lang_kinds::SymbolKind::GenerateBlockArray => {}
78 sv_lang_kinds::SymbolKind::GenerateBlock => {
79 // `generate_block_array_index()` is `None` for a
80 // `GenerateBlock` produced by an `if`/`case`-generate branch
81 // (no loop, hence no array index to contribute) — skipped,
82 // per the field's documented derivation, not an error.
83 if let Some(index) = sym.generate_block_array_index() {
84 let v = index.as_i64().ok_or_else(|| {
85 ElabError::Frontend(format!(
86 "generate block `{}` has a non-integer loop-iteration index",
87 sym.hierarchical_path()
88 ))
89 })?;
90 let v = u32::try_from(v).map_err(|_| ElabError::Frontend(format!(
91 "generate block `{}` has an out-of-range/negative loop-iteration index {v}",
92 sym.hierarchical_path()
93 )))?;
94 indices.push(v);
95 }
96 }
97 other => {
98 return Err(ElabError::UnsupportedConstruct {
99 hier_path: sym.hierarchical_path(),
100 detail: format!(
101 "unrecognized ancestor of kind {other:?} between instance `{}` and its enclosing InstanceBody",
102 instance.hierarchical_path()
103 ),
104 });
105 }
106 }
107 current = sym.parent();
108 }
109 indices.reverse();
110 Ok(indices)
111}
112
113/// The full, undeduped instance hierarchy `elaborate()` produces. Indexed
114/// densely by `InstanceId` (`node(id)` is `O(1)`); `nodes[i].id.0 == i` is
115/// an invariant every push in `elaborate.rs` maintains.
116#[derive(Debug, Default)]
117pub struct InstanceTree {
118 nodes: Vec<InstanceNode>,
119}
120
121impl InstanceTree {
122 pub(crate) fn new() -> Self {
123 InstanceTree { nodes: Vec::new() }
124 }
125
126 /// Appends a node and returns its dense index (== its `InstanceId.0`,
127 /// which the caller is responsible for stamping onto `node.id` before
128 /// or immediately after this call — `elaborate.rs`'s `visit_instance`
129 /// is the sole caller and does both).
130 pub(crate) fn push(&mut self, node: InstanceNode) -> usize {
131 self.nodes.push(node);
132 self.nodes.len() - 1
133 }
134
135 pub(crate) fn node_mut(&mut self, id: InstanceId) -> &mut InstanceNode {
136 &mut self.nodes[id.0 as usize]
137 }
138
139 pub fn node(&self, id: InstanceId) -> &InstanceNode {
140 &self.nodes[id.0 as usize]
141 }
142
143 pub fn len(&self) -> usize {
144 self.nodes.len()
145 }
146
147 pub fn is_empty(&self) -> bool {
148 self.nodes.is_empty()
149 }
150}