Skip to main content

vj_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}