sv_sim_sim/netlist/mod.rs
1//! NETLIST (`sv-sim-sim::netlist`) — the identity/dedup ID types and the
2//! `Netlist` hand-off surface (netlist-analysis.md §Key Data Structures /
3//! Public Interfaces) that CODEGEN, STATE, SCHED, RUNTIME and the debugger
4//! all address signals/instances through, plus (as of this wave's Task 1)
5//! the real, `sv-lang`-backed elaboration pipeline: a deterministic DFS over
6//! `Design::top_instances()` producing a `Netlist` that implements
7//! `NetlistView`. See `docs/superpowers/plans/2026-09-23-wave-A-netlist.md`.
8
9// Elaboration over the sv-lang `Design` is frontend-only (this crate's
10// `frontend` feature); the plain ID/key types below stay available without it.
11#[cfg(feature = "frontend")]
12mod dedup;
13// `dedup_algorithm` (Task 31, abi track, XC-045 partial closure) is
14// deliberately NOT behind `frontend`, unlike `dedup` just above: it is a
15// generic, sv-lang-free hash-then-verify algorithm (see its own module doc).
16mod dedup_algorithm;
17// Classes plan Task 2 (`docs/superpowers/plans/2026-10-03-classes.md`):
18// class-template dedup + member classification. Depends on `sv_lang::Type`/
19// `Symbol`, so `#[cfg(feature = "frontend")]`-gated like `dedup`/`elaborate`/
20// `template` above, not unconditional like `dedup_algorithm` just above.
21#[cfg(feature = "frontend")]
22mod classes;
23#[cfg(feature = "frontend")]
24mod elaborate;
25#[cfg(feature = "frontend")]
26mod error;
27#[cfg(feature = "frontend")]
28mod exprwalk;
29#[cfg(feature = "frontend")]
30mod instance;
31#[cfg(feature = "frontend")]
32mod items;
33#[cfg(feature = "frontend")]
34mod lattice;
35/// `docs/superpowers/plans/2026-10-03-sdf.md` Task 2: the specify-block/
36/// timing-check/specparam timing table (`TimingModel`). `pub`, not
37/// `pub(crate)` like the sibling modules above re-exported through this
38/// file's own flat `pub use` list below, so `sv-sim-bin`'s `specify`/
39/// `timingcheck`/`specify_eval` modules (Track A/B/C, this plan's later
40/// tasks) can name `netlist::specify::{TimingModel, PathEntry, CheckEntry}`
41/// directly.
42#[cfg(feature = "frontend")]
43pub mod specify;
44
45/// Whether `sym` heads code that never runs: an instance of a definition
46/// slang elaborated only as uninstantiated (not a top, never instantiated),
47/// or a generate block whose branch is not taken. Design-wide walks skip it.
48#[cfg(feature = "frontend")]
49pub fn is_uninstantiated_scope(sym: &sv_lang::Symbol<'_>) -> bool {
50 use sv_lang_kinds::SymbolKind;
51 // An accept-list test, not a classification of every kind.
52 if sym.kind() == SymbolKind::Instance {
53 sym.instance_body().is_some_and(|b| b.is_uninstantiated())
54 } else {
55 sym.kind() == SymbolKind::GenerateBlock && sym.generate_block_is_uninstantiated()
56 }
57}
58#[cfg(feature = "frontend")]
59pub mod itf;
60#[cfg(feature = "frontend")]
61mod template;
62#[cfg(feature = "frontend")]
63pub mod vif;
64
65#[cfg(feature = "frontend")]
66pub use classes::{
67 ClassDefinitionTable, ClassMembers, assign_class_template_ids, base_chain,
68 check_class_constraints, class_method_symbols, class_param_binding_hash,
69 class_specialization_key, class_template_key_of, class_templates_with_statics,
70 classes_with_dynamic_static_init, classify_members, collect_new_class_types,
71 flattened_interfaces, same_specialization,
72};
73#[cfg(feature = "frontend")]
74pub use dedup::{
75 ConfirmedMembers, MemberShape, confirm_and_assign_symbol_keys, merge_groups_within_bucket,
76};
77/// Task 31 (abi track, XC-045 partial closure): the generic, sv-lang-free
78/// hash-then-verify dedup algorithm — always available, not gated behind
79/// `frontend` (see `dedup_algorithm`'s own module doc for why it lives
80/// alongside, not inside, the `netlist`-track-owned `dedup` module above).
81pub use dedup_algorithm::hash_then_verify_dedup;
82/// Task 8 (`analysis::graph`'s own containing-symbol/read-set resolution):
83/// re-exported crate-internal, not part of this module's public surface,
84/// so `analysis::graph` reuses the SAME position-based `SymbolId ->
85/// SymbolKey` table `elaborate()`'s own dedup/cont-assign passes build,
86/// never a second, independently-reasoned re-derivation of that numbering.
87#[cfg(feature = "frontend")]
88pub(crate) use elaborate::symbol_key_map;
89#[cfg(feature = "frontend")]
90pub use elaborate::{Netlist, elaborate};
91#[cfg(feature = "frontend")]
92pub use error::ElabError;
93#[cfg(feature = "frontend")]
94pub use instance::{InstanceNode, InstanceTree};
95#[cfg(feature = "frontend")]
96pub use items::{
97 CheckerOutput, FreeVarKind, ProceduralChecker, checker_instance_body, checker_instances,
98 checker_output_of, checker_outputs, enclosing_instance_body, enclosing_package,
99 free_var_groups, free_var_kind, free_var_seed, generate_scopes, is_in_checker, is_in_program,
100 is_static_procedural_var, module_items, procedural_checkers, state_items,
101 static_procedural_vars, variable_initializer,
102};
103#[cfg(feature = "frontend")]
104pub use template::{
105 ContAssignTemplate, DriverEntry, MemoryDecl, PortDecl, PortDirection, ProcessTemplate,
106 SignalDecl, SignalKind, StaticSensitivity, SubInstanceSlot, Template, ValueDomain,
107};
108
109/// Opaque, dense, deterministic index into a design-wide symbol table (ledger
110/// ST-141, ST-143, ST-153, NL-012, NL-016, NL-017). Two symbols share a key iff
111/// they refer to the same deduplicated Template member.
112#[derive(Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Debug)]
113pub struct SymbolKey(pub u32);
114
115/// Undeduped instance identity — one per elaborated instance, distinct even when
116/// two instances share a compiled `Template`. This is what DEBUGGER/RUNTIME/VPI
117/// address (netlist-analysis.md Decision 3; ledger ST-115, ST-116, PR-075..
118/// PR-077).
119///
120/// `#[repr(transparent)]` (B-runtime Task 14 deviation, minimal and
121/// additive, documented in `docs/CONTRACTS.md`): RT-097's `svScope` ABI
122/// symbols (`sv_sim_runtime::dpi::scope::sv_rt_get_scope`/`sv_rt_set_scope`)
123/// are real `extern "C" fn`s that take/return this type by value. Without an
124/// explicit repr, `rustc`'s `improper_ctypes_definitions` lint (deny-by-
125/// default under this workspace's `-D warnings` gate) rejects any `extern
126/// "C"` signature naming it. `repr(transparent)` is a pure, backward-
127/// compatible layout GUARANTEE for this single-field tuple struct (adds a
128/// promise, changes no existing behavior, breaks no existing caller) — never
129/// a semantic change to `InstanceId` itself.
130#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, serde::Serialize, serde::Deserialize)]
131#[repr(transparent)]
132pub struct InstanceId(pub u32);
133
134/// A member as seen through one particular instance — what STATE's arena
135/// actually allocates storage for.
136#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
137pub struct InstantiatedSignalId {
138 pub instance: InstanceId,
139 pub member: SymbolKey,
140}
141
142#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
143pub struct TemplateId(pub u32);
144
145/// Canonicalization key = (Definition identity, ordered resolved parameter
146/// values/types) (netlist-analysis.md Decision 2: hash-then-verify, not pointer
147/// identity, because `DisableInstanceCaching` denies slang's normal body
148/// sharing; ledger CG-143, CG-145, PR-029, PR-031, PR-037, PR-117). `params_hash`
149/// stands in for the real `Vec<ResolvedParam>` the netlist track builds from
150/// `sv_lang::Symbol::parameters()`; kept opaque here since this crate does not
151/// depend on `sv-lang`.
152#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
153pub struct TemplateKey {
154 pub definition: u32,
155 pub params_hash: u64,
156}
157
158/// Classes plan (`docs/superpowers/plans/2026-10-03-classes.md`) Task 2's
159/// dense id for a class template (one per distinct `(generic class or plain
160/// class definition, resolved parameter binding)` — see `classes.rs`'s own
161/// doc comment for the full rationale). Defined here, unconditionally,
162/// rather than inside `classes.rs` itself (which is entirely
163/// `#[cfg(feature = "frontend")]`, like `dedup`/`template` above) for
164/// exactly the reason [`TemplateId`]/[`TemplateKey`] already live here
165/// instead of inside `dedup.rs`/`template.rs`: Task 3's object-heap runtime
166/// (`sv-sim-sim::objects`) stores a `ClassTemplateId` in every live object's
167/// entry and must stay usable from `sv-sim-runtime`, which depends on this
168/// crate with `default-features = false` and therefore never reaches
169/// `sv-lang` (see this crate's `Cargo.toml` comment on that dependency
170/// edge). A plain newtype has no sv-lang content to gate.
171#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
172pub struct ClassTemplateId(pub u32);
173
174/// Classes plan Task 2's candidate-bucketing key for class-template dedup —
175/// moved here alongside [`ClassTemplateId`] for the same frontend-independence
176/// reason (see that type's doc comment); the sv-lang-backed construction
177/// (`class_template_key_of`, `same_specialization`) stays in `classes.rs`,
178/// `#[cfg(feature = "frontend")]`-gated, same split as [`TemplateKey`]/
179/// `dedup.rs`.
180#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
181pub struct ClassTemplateKey {
182 pub definition: u32,
183 pub params_hash: u64,
184}
185
186/// The hand-off surface `netlist::elaborate()` produces (netlist-analysis.md's
187/// `impl<'d> Netlist<'d>`), reframed as a trait so the `netlist` track's
188/// concrete, `sv-lang`-backed type can implement it without this contracts crate
189/// needing the `sv-lang` dependency. `registration_order()`'s DFS order **is**
190/// SCHED's `registration_order` tie-break (no separate sort; ledger
191/// `registration_order`: SC-013, SC-063, SC-078, PR-118, NL-010, NL-024;
192/// `hierarchical_path`: CG-070, CG-071, CG-172, NL-021, NL-022, NL-024;
193/// `tick_ns_per_unit`: NL-025, NL-027).
194/// The one netlist query `sv-sim-runtime` needs (`%m`'s scope name), kept
195/// sv-lang-free and outside the `frontend` feature (CONTRACTS D4): the runtime
196/// depends on `sv-sim-sim` with `default-features = false`, where
197/// [`NetlistView`] (which exposes frontend-derived `InstanceNode`/`Template`)
198/// does not exist. Every `NetlistView` provides it via the blanket impl below.
199pub trait HierarchicalNames {
200 fn hierarchical_path(&self, id: InstanceId) -> &str;
201}
202
203/// Name -> signal resolution (VPI `vpi_handle_by_name`, debugger watch paths,
204/// STATE's `introspect::read_by_hier_path`), sv-lang-free and always built for
205/// the same reason as [`HierarchicalNames`]. Every `NetlistView` provides it
206/// via the blanket impl below.
207pub trait HierPathResolver {
208 fn resolve_hier_path(&self, path: &str) -> Option<InstantiatedSignalId>;
209}
210
211#[cfg(feature = "frontend")]
212impl<T: NetlistView + ?Sized> HierPathResolver for T {
213 fn resolve_hier_path(&self, path: &str) -> Option<InstantiatedSignalId> {
214 NetlistView::resolve_hier_path(self, path)
215 }
216}
217
218#[cfg(feature = "frontend")]
219impl<T: NetlistView + ?Sized> HierarchicalNames for T {
220 fn hierarchical_path(&self, id: InstanceId) -> &str {
221 NetlistView::hierarchical_path(self, id)
222 }
223}
224
225#[cfg(feature = "frontend")]
226pub trait NetlistView {
227 fn registration_order(&self) -> Vec<InstanceId>;
228 fn hierarchical_path(&self, id: InstanceId) -> &str;
229 fn resolve_hier_path(&self, path: &str) -> Option<InstantiatedSignalId>;
230 fn dedup_key(&self, tmpl: TemplateId) -> &TemplateKey;
231 /// The single source of truth for the scheduler's time precision
232 /// (netlist-analysis.md Decision 8).
233 fn tick_ns_per_unit(&self) -> u64;
234 /// NL-024 (Task 4): the instance-level query half of the public surface
235 /// netlist-analysis.md's `impl<'d> Netlist<'d>` specifies. Purely
236 /// additive to this trait (Deviation #1) — every existing method keeps
237 /// its exact signature.
238 fn instance(&self, id: InstanceId) -> &InstanceNode;
239 /// NL-024 (Task 4): the template-level query half.
240 fn template(&self, id: TemplateId) -> &Template;
241}
242
243#[cfg(test)]
244mod tests {
245 use super::{InstanceId, InstantiatedSignalId, SymbolKey};
246
247 /// XC-039 (**partial** — see `docs/CONTRACTS.md`'s "Partial closures
248 /// (wave A)" entry for the real scoping). This proves only the
249 /// type-level precondition Gate 3's per-instance FST diff depends on:
250 /// two instances elaborated from the same deduped `Template` (sharing a
251 /// dedup key, hence sharing the same member's `SymbolKey`) must never
252 /// collapse to one `InstantiatedSignalId` — the type system cannot
253 /// accidentally alias two deduped-template instances into one signal
254 /// identity. It does **not** exercise the FST/Gate-3 differential
255 /// behavior XC-039's literal acceptance names (no FST writer or Gate-3
256 /// harness exists anywhere in the workspace yet), so it cannot catch a
257 /// real merged-or-misattributed-signal bug once one becomes possible to
258 /// produce — only a regression in `InstantiatedSignalId`'s own equality.
259 #[test]
260 fn dedup_collapses_template_but_preserves_per_instance_identity() {
261 // Two instances of one deduped Template share a `TemplateKey`, so
262 // codegen emits one shared logic body/lowered unit for both — but
263 // STATE still needs two *distinct* signal identities for the same
264 // named member (`member: SymbolKey(7)`), one per instance.
265 let shared_member = SymbolKey(7);
266 let a = InstantiatedSignalId {
267 instance: InstanceId(1),
268 member: shared_member,
269 };
270 let b = InstantiatedSignalId {
271 instance: InstanceId(2),
272 member: shared_member,
273 };
274
275 assert_eq!(
276 a.member, b.member,
277 "both instances resolve to the same deduped Template member"
278 );
279 assert_ne!(
280 a, b,
281 "two instances of a deduped template must never alias to one \
282 InstantiatedSignalId — equality must also compare `instance`, \
283 never `member` alone"
284 );
285 }
286}