vj-sim 0.3.0

Netlist, scheduler and process model of the vj SystemVerilog simulator.
Documentation
//! Task 7 (NL-035): `expression_read_set` — a **deliberate, narrow exception
//! to Decision 1** (documented on `lattice.rs` and here): a bound
//! port-connection expression (`PortConnection::expression() ->
//! Option<Expression<'d>>`, `ast.rs:5743`) has no enclosing `always`/
//! `initial`/`final`/subroutine/continuous-assign `Symbol` to hand to
//! `EvalSession::run_dataflow` — `run_dataflow`'s own signature
//! (`fn run_dataflow<L: Lattice>(&self, procedure: Symbol<'_>) ->
//! Result<L, Error>`, `dataflow.rs:436`) and its own doc comment ("a
//! procedure symbol: an `always`/`initial`/`final` block, a subroutine, or
//! a continuous assign") together mean there is no `Symbol` a bare
//! port-connection expression can be wrapped as. A port-connection
//! expression also has no control flow of its own (no `if`/`case`/loop can
//! appear inside it — it's a single expression), so a full CFG-walking
//! `Lattice` pass would be doing strictly more work than the problem needs;
//! this small recursive walker is the narrow, justified exception.
//!
//! Built entirely on already-verified `Expression` accessors: `operands()`
//! (`ast.rs:18292`, the immediate sub-expressions, non-expression children
//! skipped) and `referenced_symbol()` (`ast.rs:16794`, the symbol a leaf
//! expression directly names, if any).

use crate::netlist::SymbolKey;
use crate::symid::SymbolUid;
use std::collections::{BTreeSet, HashMap};

/// Recursively collects every signal `expr` reads, resolved to a
/// `SymbolKey` via `scope_keys` — a `SymbolId -> SymbolKey` table for the
/// scope `expr`'s symbols are declared in (the caller's responsibility to
/// build against the right scope: the PARENT instantiation's own body for a
/// port-connection expression, per NL-035's "read-set resolved at the
/// parent scope" requirement).
///
/// **Deviation from the plan's literal signature** (`fn
/// expression_read_set(expr: Expression<'_>) -> Vec<SymbolKey>`, no
/// `scope_keys` parameter) — the same root cause `lattice.rs`'s own doc
/// comment documents for `ReadSetLattice`: resolving an arbitrary `Symbol`
/// to a `SymbolKey` is inherently scope-relative (`SymbolKey` is a
/// position within ONE scope's `members()`), and nothing about a bare
/// `Expression` names which scope's table to consult, so the caller
/// (`elaborate.rs`) must supply it.
///
/// Recursive walk: if `expr.referenced_symbol()` resolves to a signal
/// already interned in `scope_keys`, record it (a leaf reference);
/// regardless, recurse into `expr.operands()` and union their read-sets too
/// — a reference expression, e.g. a bit-select `p[i]`, both names `p`
/// directly AND has `i` as a sub-operand, so this is "record, then always
/// recurse", never an if/else branch. Terminates because `operands()`
/// strictly decreases expression depth (a leaf expression's `operands()` is
/// empty). Returned sorted and deduplicated (a `BTreeSet` collected to
/// `Vec`) for deterministic, order-independent comparison.
pub(crate) fn expression_read_set(
    expr: sv_lang::Expression<'_>,
    scope_keys: &HashMap<crate::symid::SymbolId, SymbolKey>,
) -> Vec<SymbolKey> {
    let mut out = BTreeSet::new();
    collect(expr, scope_keys, &mut out);
    out.into_iter().collect()
}

fn collect(
    expr: sv_lang::Expression<'_>,
    scope_keys: &HashMap<crate::symid::SymbolId, SymbolKey>,
    out: &mut BTreeSet<SymbolKey>,
) {
    if let Some(sym) = expr.referenced_symbol()
        && let Some(&key) = scope_keys.get(&sym.uid())
    {
        out.insert(key);
    }
    for operand in expr.operands() {
        collect(operand, scope_keys, out);
    }
}

/// Whether `expr` contains a pattern-matching conditional (`e matches p ?
/// a : b`, LRM 12.6.2).
pub(crate) fn has_pattern_conditional(expr: sv_lang::Expression<'_>) -> bool {
    use sv_lang_kinds::ExpressionKind;
    let mut stack = vec![expr.as_sem_node()];
    while let Some(n) = stack.pop() {
        if let Some(e) = n.as_expression()
            && e.kind() == ExpressionKind::ConditionalOp
            && e.conditions().iter().any(|c| c.pattern().is_some())
        {
            return true;
        }
        stack.extend(n.children());
    }
    false
}

/// The read-set of a continuous assignment's right-hand side that contains a
/// pattern-matching conditional, by a plain tree walk. slang's dataflow
/// analysis (`run_dataflow`) dereferences a null state on such a
/// conditional (it merges the arms of a `?:` whose condition binds pattern
/// variables), so it cannot be asked. A function call in the same
/// expression is refused: what the callee reads is the dataflow's job, and
/// guessing it would silently drop a sensitivity.
pub(crate) fn pattern_rhs_read_set(
    expr: sv_lang::Expression<'_>,
    scope_keys: &HashMap<crate::symid::SymbolId, SymbolKey>,
) -> Result<Vec<SymbolKey>, String> {
    use sv_lang_kinds::ExpressionKind;
    let mut out = BTreeSet::new();
    let mut stack = vec![expr.as_sem_node()];
    while let Some(n) = stack.pop() {
        if let Some(e) = n.as_expression() {
            if e.kind() == ExpressionKind::Call && e.is_system_call() != Some(true) {
                return Err(
                    "a pattern-matching conditional in a continuous assignment whose right-hand \
                     side also calls a subroutine (its sensitivity cannot be derived)"
                        .to_string(),
                );
            }
            if crate::symid::is_value_name(e.kind())
                && let Some(sym) = e.referenced_symbol()
                && let Some(&key) = scope_keys.get(&sym.uid())
            {
                out.insert(key);
            }
        }
        stack.extend(n.children());
    }
    Ok(out.into_iter().collect())
}