vj_sim/symid.rs
1//! A symbol's identity as a map key.
2//!
3//! `sv_lang::Symbol::id()` is the symbol's hierarchical path, which is not
4//! unique: an unnamed `begin ... end` block (and the implicit block of a
5//! `for`/`foreach`) contributes nothing to a path, so a process local `k`
6//! has the same `sv_lang::SymbolId` (`top.k`) as a module variable `k`, and
7//! two `for (int i ...)` loops' `i`s -- or a `foreach` index and a later
8//! `for` index -- collide. [`SymbolId`] adds each ancestor's position in its
9//! parent scope, which tells them apart; every map in this workspace keyed
10//! by a symbol uses it, through [`SymbolUid::uid`].
11//!
12//! The key type itself is slang-free (and serializable, for the design
13//! image's VPI section); only [`SymbolUid`] needs the frontend.
14
15use serde::{Deserialize, Serialize};
16#[cfg(feature = "frontend")]
17use sv_lang::Symbol;
18
19/// A unique, owned symbol key: the hierarchical path (for display and
20/// ordering) plus the chain of member indices from the root down to the
21/// symbol (for uniqueness).
22#[derive(Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Debug, Serialize, Deserialize)]
23pub struct SymbolId {
24 path: String,
25 chain: Vec<u32>,
26}
27
28impl SymbolId {
29 /// A key that names no real symbol, for unit tests of code that only needs keys to
30 /// compare (it equals no key [`SymbolUid::uid`] produces).
31 pub fn synthetic(path: &str) -> SymbolId {
32 SymbolId {
33 path: path.to_string(),
34 chain: vec![u32::MAX],
35 }
36 }
37
38 /// The hierarchical path (not unique by itself -- see the module doc).
39 pub fn path(&self) -> &str {
40 &self.path
41 }
42
43 /// A key that names no symbol, for a value the simulator itself keeps in a
44 /// symbol-keyed table (a procedural assertion's `const'(expr)` capture,
45 /// `vj-codegen::sva::proc`). It never equals a real symbol's key: the
46 /// member chain is the impossible `[u32::MAX]`.
47 pub fn synthetic_tagged(tag: &str) -> SymbolId {
48 SymbolId {
49 path: format!("${tag}"),
50 chain: vec![u32::MAX],
51 }
52 }
53
54 /// A key for storage the simulator itself adds next to this symbol (a
55 /// sampled-value history cell, `crate::sva::sampled`): this symbol's
56 /// path plus `${tag}`, with the same member chain. Two symbols never
57 /// collide (their chains differ), nor do one symbol's distinct tags
58 /// (their paths differ); a derived key equals a real symbol's only if
59 /// that symbol is this one and is literally named `{name}${tag}`.
60 pub fn derive(&self, tag: &str) -> SymbolId {
61 SymbolId {
62 path: format!("{}${tag}", self.path),
63 chain: self.chain.clone(),
64 }
65 }
66
67 /// The path and member chain as one string: a unique, deterministic
68 /// text form, for building a [`Self::derive`] tag from another symbol.
69 pub fn key_string(&self) -> String {
70 let chain: Vec<String> = self.chain.iter().map(u32::to_string).collect();
71 format!("{}#{}", self.path, chain.join("."))
72 }
73}
74
75impl core::fmt::Display for SymbolId {
76 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
77 f.write_str(&self.path)
78 }
79}
80
81#[cfg(feature = "frontend")]
82/// Whether an expression of `kind` names a value: a plain name (`x`) or a
83/// hierarchical one (`b.valid`, `top.u.r`) -- either way
84/// `referenced_symbol()` is the variable/net itself (a modport port for an
85/// access through a modport), and the two lower alike.
86pub fn is_value_name(kind: sv_lang_kinds::ExpressionKind) -> bool {
87 matches!(
88 kind,
89 sv_lang_kinds::ExpressionKind::NamedValue
90 | sv_lang_kinds::ExpressionKind::HierarchicalValue
91 // A variable named where a system task's signature takes a scope
92 // or object (`$dumpports(top, fname)`'s file name).
93 | sv_lang_kinds::ExpressionKind::ArbitrarySymbol
94 )
95}
96
97#[cfg(feature = "frontend")]
98/// [`SymbolId`] for a symbol.
99pub trait SymbolUid {
100 /// This symbol's unique key.
101 fn uid(&self) -> SymbolId;
102}
103
104#[cfg(feature = "frontend")]
105type UidMemo = std::collections::HashMap<(usize, usize), SymbolId>;
106
107#[cfg(feature = "frontend")]
108thread_local! {
109 /// While a design lowers ([`UidMemoGuard`]): [`SymbolUid::uid`] by symbol
110 /// identity. `uid()` builds a hierarchical path and walks the parent
111 /// chain over FFI per call; lowering asks for the same symbols' keys
112 /// many thousands of times (`import uvm_pkg::*`).
113 static UID_MEMO: std::cell::RefCell<Option<UidMemo>> = const { std::cell::RefCell::new(None) };
114}
115
116/// Enables the [`SymbolUid::uid`] memo on this thread for one design
117/// lowering; symbols are only stable while the design lives, so the memo is
118/// dropped with the guard.
119#[cfg(feature = "frontend")]
120pub struct UidMemoGuard;
121
122#[cfg(feature = "frontend")]
123impl UidMemoGuard {
124 /// Starts an empty memo.
125 pub fn enter() -> Self {
126 UID_MEMO.with(|m| *m.borrow_mut() = Some(UidMemo::new()));
127 UidMemoGuard
128 }
129}
130
131#[cfg(feature = "frontend")]
132impl Drop for UidMemoGuard {
133 fn drop(&mut self) {
134 UID_MEMO.with(|m| *m.borrow_mut() = None);
135 }
136}
137
138/// `sym`'s identity: the `ptr` and `compilation` words that open
139/// `sv_lang::Symbol`'s `#[repr(C)]` handle (equality there is those two).
140#[cfg(feature = "frontend")]
141pub fn symbol_identity(sym: &Symbol<'_>) -> (usize, usize) {
142 const _: () =
143 assert!(std::mem::size_of::<Symbol<'static>>() >= 2 * std::mem::size_of::<usize>());
144 // SAFETY: `Symbol` is a `Copy` handle whose first two words are the
145 // pointers `PartialEq` compares; the read is in bounds (asserted above).
146 unsafe {
147 let p = sym as *const Symbol<'_> as *const usize;
148 (*p, *p.add(1))
149 }
150}
151
152#[cfg(feature = "frontend")]
153impl SymbolUid for Symbol<'_> {
154 fn uid(&self) -> SymbolId {
155 let key = symbol_identity(self);
156 if let Some(hit) = UID_MEMO.with(|m| m.borrow().as_ref().and_then(|m| m.get(&key).cloned()))
157 {
158 return hit;
159 }
160 let id = self.uid_uncached();
161 UID_MEMO.with(|m| {
162 if let Some(m) = m.borrow_mut().as_mut() {
163 m.insert(key, id.clone());
164 }
165 });
166 id
167 }
168}
169
170#[cfg(feature = "frontend")]
171trait UidUncached {
172 fn uid_uncached(&self) -> SymbolId;
173}
174
175#[cfg(feature = "frontend")]
176impl UidUncached for Symbol<'_> {
177 fn uid_uncached(&self) -> SymbolId {
178 let mut chain = Vec::new();
179 let mut cur = Some(*self);
180 while let Some(s) = cur {
181 let parent = s.parent();
182 if parent.is_none() {
183 break;
184 }
185 chain.push(s.lookup_location_before().index());
186 cur = parent;
187 }
188 chain.reverse();
189 SymbolId {
190 path: self.hierarchical_path(),
191 chain,
192 }
193 }
194}