vj-sim 0.3.0

Netlist, scheduler and process model of the vj SystemVerilog simulator.
Documentation
//! The design-wide event-pool plan (LRM 1800-2023 15.5, 23.6).
//!
//! Whether a body keeps its events in a pool ([`super::event_pool`]) is
//! decided from the body's own code, but another instance's code can name
//! its events: a pool-mode body that writes `-> s.ev`, `@(s.ev)` or
//! `fire(s.ev)` reaches the owner's events through the owner's pool
//! (`pool_of_key`), so the owner must have one. A body alone cannot know
//! that, so [`EventPlanScope::enter`] walks the whole design once per run and
//! records which instance definitions are forced into pool mode (one layout
//! per deduplicated template, hence the decision is per definition).
//!
//! The plan is per-thread state (like the other design scopes), entered by
//! `DesignSyntaxScope::enter`, which every pipeline entry point holds; with
//! no scope entered nothing is forced.

use std::cell::RefCell;
use std::collections::HashSet;
use std::rc::Rc;

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

use super::event_pool::{event_count, needs_pool};

#[derive(Default)]
struct EventPlan {
    /// Definition names forced into pool mode.
    forced: HashSet<String>,
    /// Some code uses another instance's event as a HANDLE (assigns, compares
    /// or passes it): event ids are then design-wide (tagged with the pool
    /// they belong to), see `group_mode`.
    group: bool,
}

thread_local! {
    static PLAN: RefCell<Rc<EventPlan>> = RefCell::new(Rc::default());
}

/// While alive, `event_pool` honors the design-wide plan of the design
/// entered. Scopes nest; the outer plan returns on drop.
pub struct EventPlanScope(Rc<EventPlan>);

impl EventPlanScope {
    pub fn enter(design: &sv_lang::Design) -> Self {
        let plan = Rc::new(compute_plan(design));
        EventPlanScope(PLAN.with(|p| std::mem::replace(&mut *p.borrow_mut(), plan)))
    }
}

impl Drop for EventPlanScope {
    fn drop(&mut self) {
        let outer = std::mem::take(&mut self.0);
        PLAN.with(|p| *p.borrow_mut() = outer);
    }
}

/// The definition name of instance body `body`.
fn definition_of(body: &Symbol<'_>) -> Option<String> {
    if body.kind() != SymbolKind::InstanceBody {
        return None;
    }
    body.instance_body_parent_instance()?
        .instance_definition()
        .map(|d| d.name().to_string())
}

/// Event ids are design-wide: every pool-mode instance can reach every
/// other's pool and a handle carries its pool's tag (LRM 1800-2023 15.5.5:
/// an event handle names an event wherever it lives). Off for every design
/// that never uses another instance's event as a handle, whose code and image
/// stay exactly as without the feature.
pub fn group_mode() -> bool {
    PLAN.with(|p| p.borrow().group)
}

/// The plan forces `body`'s definition into pool mode.
pub(super) fn plan_forces_pool(body: &Symbol<'_>) -> bool {
    PLAN.with(|p| {
        let p = p.borrow();
        !p.forced.is_empty() && definition_of(body).is_some_and(|d| p.forced.contains(&d))
    })
}

/// One hierarchical event reference of a body's code: the static event
/// variable named, and whether it is used as an event handle (assigned,
/// compared, passed, tested: 15.5.5) rather than only triggered or waited on.
struct EventRef<'d> {
    var: Symbol<'d>,
    handle: bool,
}

/// The static event variable `e` names through a hierarchical reference
/// (`s.ev`, `s.ea[i]`), if it does.
fn hierarchical_event_operand<'d>(e: &Expression<'d>) -> Option<Symbol<'d>> {
    let mut e = *e;
    loop {
        if e.kind() == ExpressionKind::Conversion
            && let Some(inner) = e.conversion_operand()
        {
            e = inner;
        } else if e.kind() == ExpressionKind::ElementSelect
            && let Some(base) = e.select_value()
        {
            e = base;
        } else {
            break;
        }
    }
    if e.kind() != ExpressionKind::HierarchicalValue {
        return None;
    }
    let sym = e.referenced_symbol()?;
    sym.value_type().as_ref().and_then(event_count)?;
    Some(sym)
}

fn note<'d>(e: Option<Expression<'d>>, handle: bool, out: &mut Vec<EventRef<'d>>) {
    if let Some(var) = e.as_ref().and_then(hierarchical_event_operand) {
        out.push(EventRef { var, handle });
    }
}

/// Every hierarchical event reference in `body`'s procedural code.
fn hierarchical_event_refs<'d>(body: &Symbol<'d>) -> Vec<EventRef<'d>> {
    let mut out = Vec::new();
    for m in crate::netlist::module_items(body) {
        let kind = m.kind();
        if kind == SymbolKind::Variable && m.value_type().as_ref().and_then(event_count).is_some() {
            note(m.initializer(), true, &mut out);
        }
        if kind != SymbolKind::Subroutine && kind != SymbolKind::ProceduralBlock {
            continue;
        }
        let Some(code) = crate::process::cfg::proc_body(&m) else {
            continue;
        };
        let mut stack = vec![code.as_sem_node()];
        while let Some(n) = stack.pop() {
            if let Some(s) = n.as_statement() {
                note(s.condition(), true, &mut out);
                for block in crate::process::cfg::block_scopes(&s) {
                    for v in block.members() {
                        if v.value_type().as_ref().and_then(event_count).is_some() {
                            note(v.initializer(), true, &mut out);
                        }
                    }
                }
            }
            let kids = crate::process::cfg::live_children(&n);
            if let Some(e) = n.as_expression() {
                note(Some(e), false, &mut out);
                // An operand of an assignment, comparison, `!`, a Boolean
                // conversion or a call (an event argument) is used as a
                // handle (15.5.5).
                // `ev.triggered` reads the event's cell, it is not a handle use.
                let is_triggered = e.kind() == ExpressionKind::Call
                    && e.system_subroutine()
                        .is_some_and(|s| s.known_name_id() == KnownSystemName::Triggered);
                if !is_triggered
                    && matches!(
                        e.kind(),
                        ExpressionKind::Assignment
                            | ExpressionKind::BinaryOp
                            | ExpressionKind::UnaryOp
                            | ExpressionKind::Conversion
                            | ExpressionKind::Call
                    )
                {
                    for k in &kids {
                        note(k.as_expression(), true, &mut out);
                    }
                }
            }
            stack.extend(kids);
        }
    }
    out
}

fn collect_bodies<'d>(inst: Symbol<'d>, out: &mut Vec<Symbol<'d>>) {
    if inst.kind() == SymbolKind::InstanceArray {
        for e in inst.instance_array_elements() {
            collect_bodies(e, out);
        }
        return;
    }
    if inst.kind() != SymbolKind::Instance {
        return;
    }
    if let Some(body) = inst.instance_body() {
        out.push(body);
        for m in crate::netlist::module_items(&body) {
            collect_bodies(m, out);
        }
    }
}

fn compute_plan(design: &sv_lang::Design) -> EventPlan {
    let mut bodies = Vec::new();
    for top in design.top_instances() {
        collect_bodies(top, &mut bodies);
    }
    // (referrer definition, owner definition, handle use) per cross-instance
    // reference, and the definitions that are pool-mode on their own.
    let mut refs: Vec<(String, String, bool)> = Vec::new();
    let mut pooled: HashSet<String> = HashSet::new();
    for body in &bodies {
        let Some(def) = definition_of(body) else {
            continue;
        };
        if needs_pool(body) {
            pooled.insert(def.clone());
        }
        let own_path = body.hierarchical_path();
        for r in hierarchical_event_refs(body) {
            let Some(owner) = crate::netlist::enclosing_instance_body(&r.var) else {
                continue;
            };
            if owner.hierarchical_path() == own_path {
                continue;
            }
            if let Some(owner_def) = definition_of(&owner) {
                refs.push((def.clone(), owner_def, r.handle));
            }
        }
    }
    // A handle use forces both ends; any reference from a pool-mode body
    // forces the owner (its code cannot read a legacy cell, see module docs).
    let mut forced: HashSet<String> = HashSet::new();
    loop {
        let mut changed = false;
        for (from, owner, handle) in &refs {
            if *handle && forced.insert(from.clone()) {
                changed = true;
            }
            if (*handle || pooled.contains(from) || forced.contains(from))
                && forced.insert(owner.clone())
            {
                changed = true;
            }
        }
        if !changed {
            break;
        }
    }
    let group = refs.iter().any(|(_, _, handle)| *handle);
    EventPlan { forced, group }
}