vj-sim 0.3.0

Netlist, scheduler and process model of the vj SystemVerilog simulator.
Documentation
//! The undeduped instance tree (NL-002, NL-010): one `InstanceNode` per
//! elaborated instance, distinct even when two instances share a compiled
//! `Template` (dedup lands in a later task). Task 4 (NL-023) adds
//! `array_path` and its derivation, [`array_path_of`].

use crate::netlist::{ElabError, InstanceId, TemplateId};

/// One elaborated instance. `children` is populated in DFS visitation order
/// (== declaration order), never re-sorted.
#[derive(Debug, Clone)]
pub struct InstanceNode {
    pub id: InstanceId,
    pub template: TemplateId,
    pub parent: Option<InstanceId>,
    pub name: String,
    /// **Two distinct sources, both folded into one field** (NL-023;
    /// Deviation #7 in the plan): `Symbol::instance_array_path()` is
    /// non-empty only for genuine instance-array syntax (`modname
    /// name[hi:lo](...)` applied to the instance's own declaration) — it is
    /// empty for every instance inside a `generate for`, because a loop
    /// iteration's index lives on the *enclosing* `GenerateBlockArray`'s
    /// `GenerateBlock` entries, not on the child instance itself. So
    /// `array_path` is built by `array_path_of` (this module, crate-private) as:
    /// `instance_array_path().collect()` if non-empty (true instance-array
    /// syntax); **else** walk `Symbol::parent()` outward from the instance
    /// collecting `generate_block_array_index()` from every enclosing
    /// `GenerateBlock` produced by loop-generate (skipping non-loop
    /// `GenerateBlock`s, e.g. an `if`/`case`-generate branch, which have no
    /// array index), stopping at the innermost enclosing `InstanceBody` (or,
    /// for a top-level instance with no enclosing `InstanceBody` at all, at
    /// the design's `Root`), then reversing (outermost loop first) — empty
    /// `Vec` if the instance is neither array-instantiated nor inside any
    /// loop-generate.
    pub array_path: Vec<u32>,
    pub children: Vec<InstanceId>,
}

/// Builds `InstanceNode::array_path` per the derivation documented on that
/// field. `instance` is the `Instance`/`PrimitiveInstance`/`CheckerInstance`
/// symbol being visited during the DFS (Task 1). Like every other
/// `sv_lang_kinds::SymbolKind` match this pipeline performs, the parent-walk
/// below is exhaustive over the kinds that can legitimately appear and routes
/// anything else to a typed `ElabError::UnsupportedConstruct` — never a
/// silent no-op (plan "Global constraints"; ARCHITECTURE.md §7 item 9) — and
/// a folded genvar value this function cannot represent as a valid array
/// index (non-integer, or negative) is a typed `ElabError::Frontend`, never a
/// silently-wrapped `u32`.
pub(crate) fn array_path_of(instance: sv_lang::Symbol<'_>) -> Result<Vec<u32>, ElabError> {
    let via_instance_array: Vec<u32> = instance.instance_array_path().collect();
    if !via_instance_array.is_empty() {
        return Ok(via_instance_array);
    }

    // Not true instance-array syntax: walk outward through enclosing scopes
    // collecting the loop-iteration index of every `GenerateBlock` produced
    // by a `generate for`, stopping at the innermost enclosing
    // `InstanceBody` (never crossing into a *different* instance's own
    // scope) — or, for a *top-level* instance (no enclosing `InstanceBody`
    // at all: `Symbol::parent()` goes straight from the `Instance` to the
    // design's `Root`), stopping at `Root` instead. A `GenerateBlockArray`'s
    // own `.parent()` (if this walk passes through one on the way up) is not
    // itself a `GenerateBlock`, so it is explicitly named as a benign
    // pass-through kind and contributes nothing itself.
    // `InstanceBody`/`Root`/`GenerateBlockArray`/`GenerateBlock` are the only
    // kinds legal SV can ever interpose between an instance and its
    // enclosing module body (or the design root) on this walk; any other
    // kind reaching here would mean a construct this pass does not
    // understand is standing between the instance and its true array
    // position, so it is a typed error, not a silently-truncated
    // `array_path`.
    let mut indices: Vec<u32> = Vec::new();
    let mut current = instance.parent();
    while let Some(sym) = current {
        match sym.kind() {
            sv_lang_kinds::SymbolKind::InstanceBody | sv_lang_kinds::SymbolKind::Root => break,
            // Stepped over: contributes no index of its own.
            sv_lang_kinds::SymbolKind::GenerateBlockArray => {}
            sv_lang_kinds::SymbolKind::GenerateBlock => {
                // `generate_block_array_index()` is `None` for a
                // `GenerateBlock` produced by an `if`/`case`-generate branch
                // (no loop, hence no array index to contribute) — skipped,
                // per the field's documented derivation, not an error.
                if let Some(index) = sym.generate_block_array_index() {
                    let v = index.as_i64().ok_or_else(|| {
                        ElabError::Frontend(format!(
                            "generate block `{}` has a non-integer loop-iteration index",
                            sym.hierarchical_path()
                        ))
                    })?;
                    let v = u32::try_from(v).map_err(|_| ElabError::Frontend(format!(
                        "generate block `{}` has an out-of-range/negative loop-iteration index {v}",
                        sym.hierarchical_path()
                    )))?;
                    indices.push(v);
                }
            }
            other => {
                return Err(ElabError::UnsupportedConstruct {
                    hier_path: sym.hierarchical_path(),
                    detail: format!(
                        "unrecognized ancestor of kind {other:?} between instance `{}` and its enclosing InstanceBody",
                        instance.hierarchical_path()
                    ),
                });
            }
        }
        current = sym.parent();
    }
    indices.reverse();
    Ok(indices)
}

/// The full, undeduped instance hierarchy `elaborate()` produces. Indexed
/// densely by `InstanceId` (`node(id)` is `O(1)`); `nodes[i].id.0 == i` is
/// an invariant every push in `elaborate.rs` maintains.
#[derive(Debug, Default)]
pub struct InstanceTree {
    nodes: Vec<InstanceNode>,
}

impl InstanceTree {
    pub(crate) fn new() -> Self {
        InstanceTree { nodes: Vec::new() }
    }

    /// Appends a node and returns its dense index (== its `InstanceId.0`,
    /// which the caller is responsible for stamping onto `node.id` before
    /// or immediately after this call — `elaborate.rs`'s `visit_instance`
    /// is the sole caller and does both).
    pub(crate) fn push(&mut self, node: InstanceNode) -> usize {
        self.nodes.push(node);
        self.nodes.len() - 1
    }

    pub(crate) fn node_mut(&mut self, id: InstanceId) -> &mut InstanceNode {
        &mut self.nodes[id.0 as usize]
    }

    pub fn node(&self, id: InstanceId) -> &InstanceNode {
        &self.nodes[id.0 as usize]
    }

    pub fn len(&self) -> usize {
        self.nodes.len()
    }

    pub fn is_empty(&self) -> bool {
        self.nodes.is_empty()
    }
}