vj-sim 0.3.0

Netlist, scheduler and process model of the vj SystemVerilog simulator.
Documentation
//! A symbol's identity as a map key.
//!
//! `sv_lang::Symbol::id()` is the symbol's hierarchical path, which is not
//! unique: an unnamed `begin ... end` block (and the implicit block of a
//! `for`/`foreach`) contributes nothing to a path, so a process local `k`
//! has the same `sv_lang::SymbolId` (`top.k`) as a module variable `k`, and
//! two `for (int i ...)` loops' `i`s -- or a `foreach` index and a later
//! `for` index -- collide. [`SymbolId`] adds each ancestor's position in its
//! parent scope, which tells them apart; every map in this workspace keyed
//! by a symbol uses it, through [`SymbolUid::uid`].
//!
//! The key type itself is slang-free (and serializable, for the design
//! image's VPI section); only [`SymbolUid`] needs the frontend.

use serde::{Deserialize, Serialize};
#[cfg(feature = "frontend")]
use sv_lang::Symbol;

/// A unique, owned symbol key: the hierarchical path (for display and
/// ordering) plus the chain of member indices from the root down to the
/// symbol (for uniqueness).
#[derive(Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Debug, Serialize, Deserialize)]
pub struct SymbolId {
    path: String,
    chain: Vec<u32>,
}

impl SymbolId {
    /// A key that names no real symbol, for unit tests of code that only needs keys to
    /// compare (it equals no key [`SymbolUid::uid`] produces).
    pub fn synthetic(path: &str) -> SymbolId {
        SymbolId {
            path: path.to_string(),
            chain: vec![u32::MAX],
        }
    }

    /// The hierarchical path (not unique by itself -- see the module doc).
    pub fn path(&self) -> &str {
        &self.path
    }

    /// A key that names no symbol, for a value the simulator itself keeps in a
    /// symbol-keyed table (a procedural assertion's `const'(expr)` capture,
    /// `vj-codegen::sva::proc`). It never equals a real symbol's key: the
    /// member chain is the impossible `[u32::MAX]`.
    pub fn synthetic_tagged(tag: &str) -> SymbolId {
        SymbolId {
            path: format!("${tag}"),
            chain: vec![u32::MAX],
        }
    }

    /// A key for storage the simulator itself adds next to this symbol (a
    /// sampled-value history cell, `crate::sva::sampled`): this symbol's
    /// path plus `${tag}`, with the same member chain. Two symbols never
    /// collide (their chains differ), nor do one symbol's distinct tags
    /// (their paths differ); a derived key equals a real symbol's only if
    /// that symbol is this one and is literally named `{name}${tag}`.
    pub fn derive(&self, tag: &str) -> SymbolId {
        SymbolId {
            path: format!("{}${tag}", self.path),
            chain: self.chain.clone(),
        }
    }

    /// The path and member chain as one string: a unique, deterministic
    /// text form, for building a [`Self::derive`] tag from another symbol.
    pub fn key_string(&self) -> String {
        let chain: Vec<String> = self.chain.iter().map(u32::to_string).collect();
        format!("{}#{}", self.path, chain.join("."))
    }
}

impl core::fmt::Display for SymbolId {
    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
        f.write_str(&self.path)
    }
}

#[cfg(feature = "frontend")]
/// Whether an expression of `kind` names a value: a plain name (`x`) or a
/// hierarchical one (`b.valid`, `top.u.r`) -- either way
/// `referenced_symbol()` is the variable/net itself (a modport port for an
/// access through a modport), and the two lower alike.
pub fn is_value_name(kind: sv_lang_kinds::ExpressionKind) -> bool {
    matches!(
        kind,
        sv_lang_kinds::ExpressionKind::NamedValue
            | sv_lang_kinds::ExpressionKind::HierarchicalValue
            // A variable named where a system task's signature takes a scope
            // or object (`$dumpports(top, fname)`'s file name).
            | sv_lang_kinds::ExpressionKind::ArbitrarySymbol
    )
}

#[cfg(feature = "frontend")]
/// [`SymbolId`] for a symbol.
pub trait SymbolUid {
    /// This symbol's unique key.
    fn uid(&self) -> SymbolId;
}

#[cfg(feature = "frontend")]
type UidMemo = std::collections::HashMap<(usize, usize), SymbolId>;

#[cfg(feature = "frontend")]
thread_local! {
    /// While a design lowers ([`UidMemoGuard`]): [`SymbolUid::uid`] by symbol
    /// identity. `uid()` builds a hierarchical path and walks the parent
    /// chain over FFI per call; lowering asks for the same symbols' keys
    /// many thousands of times (`import uvm_pkg::*`).
    static UID_MEMO: std::cell::RefCell<Option<UidMemo>> = const { std::cell::RefCell::new(None) };
}

/// Enables the [`SymbolUid::uid`] memo on this thread for one design
/// lowering; symbols are only stable while the design lives, so the memo is
/// dropped with the guard.
#[cfg(feature = "frontend")]
pub struct UidMemoGuard;

#[cfg(feature = "frontend")]
impl UidMemoGuard {
    /// Starts an empty memo.
    pub fn enter() -> Self {
        UID_MEMO.with(|m| *m.borrow_mut() = Some(UidMemo::new()));
        UidMemoGuard
    }
}

#[cfg(feature = "frontend")]
impl Drop for UidMemoGuard {
    fn drop(&mut self) {
        UID_MEMO.with(|m| *m.borrow_mut() = None);
    }
}

/// `sym`'s identity: the `ptr` and `compilation` words that open
/// `sv_lang::Symbol`'s `#[repr(C)]` handle (equality there is those two).
#[cfg(feature = "frontend")]
pub fn symbol_identity(sym: &Symbol<'_>) -> (usize, usize) {
    const _: () =
        assert!(std::mem::size_of::<Symbol<'static>>() >= 2 * std::mem::size_of::<usize>());
    // SAFETY: `Symbol` is a `Copy` handle whose first two words are the
    // pointers `PartialEq` compares; the read is in bounds (asserted above).
    unsafe {
        let p = sym as *const Symbol<'_> as *const usize;
        (*p, *p.add(1))
    }
}

#[cfg(feature = "frontend")]
impl SymbolUid for Symbol<'_> {
    fn uid(&self) -> SymbolId {
        let key = symbol_identity(self);
        if let Some(hit) = UID_MEMO.with(|m| m.borrow().as_ref().and_then(|m| m.get(&key).cloned()))
        {
            return hit;
        }
        let id = self.uid_uncached();
        UID_MEMO.with(|m| {
            if let Some(m) = m.borrow_mut().as_mut() {
                m.insert(key, id.clone());
            }
        });
        id
    }
}

#[cfg(feature = "frontend")]
trait UidUncached {
    fn uid_uncached(&self) -> SymbolId;
}

#[cfg(feature = "frontend")]
impl UidUncached for Symbol<'_> {
    fn uid_uncached(&self) -> SymbolId {
        let mut chain = Vec::new();
        let mut cur = Some(*self);
        while let Some(s) = cur {
            let parent = s.parent();
            if parent.is_none() {
                break;
            }
            chain.push(s.lookup_location_before().index());
            cur = parent;
        }
        chain.reverse();
        SymbolId {
            path: self.hierarchical_path(),
            chain,
        }
    }
}