vj-sim 0.3.0

Netlist, scheduler and process model of the vj SystemVerilog simulator.
Documentation
//! Sequence end points read by procedural code (LRM 1800-2023 16.9.11, 9.4.2.4):
//! `wait (s.triggered)`, `if (s.triggered)`, `@(s)`.
//!
//! The assertion engine decides an instance's end points in the Observed region.
//! For a procedural reader each distinct instance a procedural block reads is
//! *watched*: a hidden cell of the instance body holds, like a named `event`
//! (`crate::state::EVENT_CELL_WIDTH`), a match count in its low 64 bits and
//! `tick + 1` of the latest end point in its high 64 bits. A cover-sequence
//! engine over the instance (`vj-codegen`'s watcher job) stamps it when the
//! instance reaches an end point; `s.triggered` is then "the stamp is this time
//! step's", and `@(s)` an any-change wait on the cell. Nothing clears it.
//!
//! [`watchers`] is the enumeration both slot builders append, in its order.

use std::collections::BTreeSet;

use sv_lang::kinds::{ExpressionKind, StatementKind, SymbolKind, TimingControlKind};
use sv_lang::{Expression, KnownSystemName, Symbol};

use crate::symid::{SymbolId, SymbolUid};

/// One watched sequence instance of a procedural block.
#[derive(Clone, Copy)]
pub struct Watcher<'d> {
    /// The procedural block that reads it.
    pub block: Symbol<'d>,
    /// The `AssertionInstance` expression.
    pub inst: Expression<'d>,
}

impl<'d> Watcher<'d> {
    /// The hidden cell's id ([`watcher_id`]).
    pub fn id(&self) -> SymbolId {
        watcher_id(&self.block.uid(), &self.inst)
    }
}

/// The id of the hidden cell of instance `inst`, read by the block `owner`.
pub fn watcher_id(owner: &SymbolId, inst: &Expression<'_>) -> SymbolId {
    let s = inst.source_range().start;
    owner.derive(&format!("ep{}.{}", s.buffer, s.offset))
}

/// The sequence instance `e` reads the end point of, when `e` is
/// `inst.triggered` on an `AssertionInstance`.
pub fn watched_call<'d>(e: &Expression<'d>) -> Option<Expression<'d>> {
    if e.kind() != ExpressionKind::Call
        || e.system_subroutine()?.known_name_id() != KnownSystemName::Triggered
    {
        return None;
    }
    let op = *e.operands().first()?;
    (op.kind() == ExpressionKind::AssertionInstance).then_some(op)
}

/// Every watched instance of procedural block `block` -- its own, then those
/// of the user subroutines of the same instance it calls (transitively), which
/// read the cell through the instance handle -- in a deterministic order, one
/// per source position and owner. A subroutine's watchers are owned by it, so
/// several callers share them.
pub fn block_watchers<'d>(block: &Symbol<'d>) -> Vec<Watcher<'d>> {
    let mut out = own_watchers(block);
    let instance = crate::netlist::enclosing_instance_body(block).map(|b| b.uid());
    let mut visited = BTreeSet::new();
    let mut work = vec![*block];
    while let Some(owner) = work.pop() {
        let Some(body) = crate::process::cfg::proc_body(&owner) else {
            continue;
        };
        let mut calls = Vec::new();
        let mut stack = vec![body.as_sem_node()];
        while let Some(n) = stack.pop() {
            if let Some(e) = n.as_expression()
                && e.kind() == ExpressionKind::Call
                && e.is_system_call() != Some(true)
                && let Some(callee) = crate::netlist::itf::call_target(&e)
                && callee.kind() == SymbolKind::Subroutine
                && visited.insert(callee.uid())
                && crate::netlist::enclosing_instance_body(&callee).map(|b| b.uid()) == instance
            {
                calls.push(callee);
            }
            let mut kids = crate::process::cfg::live_children(&n);
            kids.reverse();
            stack.extend(kids);
        }
        for callee in calls {
            out.extend(own_watchers(&callee));
            work.push(callee);
        }
    }
    out
}

/// Every watched instance read by the code of `block` itself, in a
/// deterministic walk order, one per source position. An assertion
/// statement's own end-point methods are the assertion engine's (not watched).
fn own_watchers<'d>(block: &Symbol<'d>) -> Vec<Watcher<'d>> {
    let Some(body) = crate::process::cfg::proc_body(block) else {
        return Vec::new();
    };
    let mut out = Vec::new();
    let mut seen = BTreeSet::new();
    let mut push = |inst: Expression<'d>, out: &mut Vec<Watcher<'d>>| {
        let s = inst.source_range().start;
        if seen.insert((s.buffer, s.offset)) {
            out.push(Watcher {
                block: *block,
                inst,
            });
        }
    };
    let mut stack = vec![body.as_sem_node()];
    while let Some(n) = stack.pop() {
        if let Some(s) = n.as_statement()
            && s.kind() == StatementKind::ConcurrentAssertion
        {
            // The assertion's own end-point methods are the engine's, but a
            // sequence used as its clocking event (`@seq`, 16.5.2) ticks it at
            // the sequence's end points: that sequence is watched.
            for inst in clock_sequences(n) {
                push(inst, &mut out);
            }
            continue;
        }
        if let Some(e) = n.as_expression()
            && let Some(inst) = watched_call(&e)
        {
            push(inst, &mut out);
            continue;
        }
        if let Some(tc) = n.as_timing_control()
            && tc.kind() == TimingControlKind::SignalEvent
            && let Some(e) = tc.children().first().and_then(|c| c.as_expression())
            && e.kind() == ExpressionKind::AssertionInstance
        {
            push(e, &mut out);
            continue;
        }
        let mut kids = crate::process::cfg::live_children(&n);
        kids.reverse();
        stack.extend(kids);
    }
    out
}

/// The sequence instances used as clocking events (`@seq`, LRM 16.5.2) anywhere
/// in the concurrent assertion statement `root`, in a deterministic walk order.
fn clock_sequences<'d>(root: sv_lang::SemNode<'d>) -> Vec<Expression<'d>> {
    let mut out = Vec::new();
    let mut stack = vec![root];
    while let Some(n) = stack.pop() {
        if let Some(tc) = n.as_timing_control()
            && tc.kind() == TimingControlKind::SignalEvent
            && let Some(e) = tc.children().first().and_then(|c| c.as_expression())
            && e.kind() == ExpressionKind::AssertionInstance
        {
            out.push(e);
            continue;
        }
        let mut kids = crate::process::cfg::live_children(&n);
        kids.reverse();
        stack.extend(kids);
    }
    out
}

/// Every watched instance of the procedural blocks of instance body `body` (the
/// enumeration the state layout and the codegen signal map both append).
pub fn watchers<'d>(body: &Symbol<'d>) -> Vec<Watcher<'d>> {
    let mut seen = BTreeSet::new();
    crate::netlist::module_items(body)
        .iter()
        .filter(|item| item.kind() == SymbolKind::ProceduralBlock)
        .flat_map(block_watchers)
        .filter(|w| seen.insert(w.id()))
        .collect()
}