sv_sim_sim/state/mod.rs
1//! STATE (`sv-sim-sim::state`) — cross-track contract only: the Rust-owned 4-state
2//! arena's SoA layout query shape, its value-change fan-out primitive, and the one
3//! canonical signal read/write/subscribe interface (ARCHITECTURE §2: "Signal
4//! read/write/subscribe: STATE owns the one canonical primitive; VPI/FST/debugger
5//! are pure consumers"). The arena implementation itself belongs to the `state`
6//! track, not this contracts wave.
7//!
8//! Reconciliation: state.md calls the value-change listener trait `ChangeListener`;
9//! scheduler.md independently sketches the same contract under the name
10//! `ValueChangeSink`. Per the task's naming and because scheduler.md's signature is
11//! the more concrete of the two, this crate uses `ValueChangeSink` (ledger SC-071,
12//! RT-005) and does not separately define a `ChangeListener`.
13//!
14//! **Task 20 (ST-130) — dependency direction is an invariant, not an accident.**
15//! RUNTIME consumes STATE's reference read/write API
16//! ([`SignalAccess`]/[`ValueChangeSink`]) and its `Bit`/`OwnedSVInt`-adjacent
17//! [`FourStateValue`] type, never the reverse: FST/VPI/`$display` are consumers,
18//! not producers, of these interfaces, and no code anywhere in `sv-sim-sim::state`
19//! calls into `sv-sim-runtime`'s FST/VPI/`$display` formatting. This crate's own
20//! `Cargo.toml` (`crates/sv-sim-sim/Cargo.toml`) lists no `sv-sim-runtime`
21//! dependency, which is the mechanically-checkable half of this invariant.
22//!
23//! This is not a hypothetical edge: `sv-sim-runtime`'s `Cargo.toml` already
24//! depends on `sv-sim-sim` (since the very first crate-skeleton commit), and
25//! `sv-sim-runtime::signal_consumer::RuntimeSignalConsumer` already consumes
26//! [`SignalAccess`] directly. Both halves of the invariant — the real, present
27//! edge in one direction and the absent edge in the other — are guarded by an
28//! automated test: `crates/sv-sim-sim/tests/dependency_direction.rs`. Confirm
29//! live with `cargo tree -p sv-sim-sim -i sv-sim-runtime` (expected to report no
30//! path) or `cargo test -p sv-sim-sim --test dependency_direction`; see
31//! `docs/CONTRACTS.md`'s "Wave A — wA-state notes" section for the fuller
32//! writeup, including for RUNTIME's own wave to keep honoring as it grows more
33//! dependencies.
34
35use sv_sim_abi::{Bit4, Region, SimTime};
36
37// sv-lang value conversions, the layout builder and template dedup walk the
38// frontend `Design`, so they exist only with this crate's `frontend` feature.
39#[cfg(feature = "frontend")]
40pub(crate) mod value;
41#[cfg(feature = "frontend")]
42pub use value::*;
43
44mod arena;
45pub use arena::*;
46
47#[cfg(feature = "frontend")]
48pub mod storage;
49
50#[cfg(feature = "frontend")]
51pub mod event_pool;
52
53#[cfg(feature = "frontend")]
54mod layout;
55#[cfg(feature = "frontend")]
56pub use layout::*;
57
58#[cfg(feature = "frontend")]
59mod dedup;
60#[cfg(feature = "frontend")]
61pub use dedup::*;
62
63mod instance;
64pub use instance::*;
65
66mod write;
67
68mod huge_mirror;
69pub use huge_mirror::{
70 HugeCommit, NbaWindow, huge_commit, huge_commit_full_done, huge_mark, huge_nba_window,
71 huge_prime,
72};
73
74pub mod force;
75pub use write::*;
76
77mod driver;
78pub use driver::*;
79
80pub use crate::driver_counts::*;
81
82mod region_check;
83pub use region_check::*;
84
85mod accessor_impl;
86pub use accessor_impl::*;
87
88mod alias;
89pub use alias::{AliasChange, AliasWindow, splice_window, window_value};
90
91mod dispatch;
92pub use dispatch::*;
93
94mod rollback;
95pub use rollback::*;
96
97mod snapshot;
98pub use snapshot::*;
99
100mod frame;
101pub use frame::*;
102
103mod introspect;
104pub use introspect::*;
105
106mod kernel_ctx;
107pub use kernel_ctx::*;
108
109mod event;
110// `event`'s only top-level item is `EVENT_CELL_WIDTH`; R-run Task 16's
111// `alloc_event_cell`/`trigger_event`/`clear_event_pulses` are `impl
112// StateEngine` methods on the already-`pub use`d `instance::StateEngine`.
113pub use event::*;
114
115mod gvt;
116// `gvt` adds no new `pub` top-level items of its own -- Task 12's
117// `advance_gvt`/`drain_committed_changes` are `impl StateEngine` methods on
118// the already-`pub use`d `instance::StateEngine` type, so this re-export
119// currently has nothing to bring in; kept for symmetry with every other
120// submodule here and so a future `pub` item added to `gvt.rs` is exported
121// automatically.
122#[allow(unused_imports)]
123pub use gvt::*;
124
125/// Opaque per-signal handle, STATE-assigned (scheduler.md: "STATE-owned opaque
126/// handle"; ledger ST-109, ST-111, SC-037). Distinct from [`sv_sim_abi::SlotId`]:
127/// `SlotId` is the ABI-facing, compile-time-constant index CODEGEN bakes into IR;
128/// `StateCellId` is the in-process (non-FFI) handle the scheduler/determinism/
129/// runtime Rust code passes around at simulation time. STATE's own implementation
130/// is free to make them numerically related, but the two names stay distinct
131/// contract types because their callers (CODEGEN vs. the scheduler) are on
132/// opposite sides of the ABI boundary.
133#[derive(Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Debug)]
134pub struct StateCellId(pub u64);
135
136/// The storage tiers state.md §1 splits every slot into (ledger ST-020..ST-024,
137/// CG-107, CG-163).
138///
139/// **Review fix (Task 14 r2, blocking finding 2) adds `ForceWide`:** the
140/// Wide/Memory-class counterpart of `driver::force_driver`'s dynamically
141/// -allocated contribution cell, addressing `Arena`'s separate, never-sealed
142/// `force_wide_aval`/`force_wide_bval` planes (`state::arena`'s
143/// `alloc_force_wide_words` doc comment has the full argument) instead of the
144/// ordinary `wide_aval`/`wide_bval` planes `sv_get_wide` can seal. No
145/// `SlotDescriptor`/`SlotOffset` the layout builder (Task 4/6) ever produces
146/// for a real elaborated signal is `ForceWide` — it is minted exclusively by
147/// `force_driver` for its own synthetic, caller-invisible contribution cell,
148/// never observed by CODEGEN/the accessor ABI at all.
149#[derive(Clone, Copy, PartialEq, Eq, Debug)]
150pub enum SlotClass {
151 Inline,
152 Wide,
153 Memory,
154 ForceWide,
155}
156
157/// Compile-time layout query result STATE hands to CODEGEN (state.md §3: "baked
158/// into compiled kernels as LLVM constants"; ledger ST-027, ST-037, ST-129,
159/// ST-134, ST-135). Pure Rust (not `#[repr(C)]`) — this crosses the CODEGEN/STATE
160/// boundary as ordinary Rust values at *compile* time, not as bytes across the
161/// compiled-kernel ABI, so it has no FFI-layout obligation.
162/// Task 7 adds `PartialEq` (previously `Clone, Copy, Debug` only): `slot_of`
163/// (ST-129) is required to return a byte-identical `SlotOffset` across
164/// repeated calls regardless of intervening `allocate_instance` calls, and
165/// the direct field-by-field way to prove that in a test is a structural
166/// equality check, not a `Debug`-string comparison. Every field is itself
167/// `PartialEq` (`SlotClass` already derives it; the rest are plain integers),
168/// so this is a safe, non-lossy derive addition.
169///
170/// **Task 10 (ST-027):** state.md's original state-pointer ABI design — CODEGEN
171/// inlining a raw compile-time `base + word_offset` pointer directly, bypassing
172/// the accessor calls above — is **superseded** by ARCHITECTURE §7 item 1's
173/// accessor-ABI (ST-026). This struct's fields stay `pub` and readable (CODEGEN
174/// needs them to size/typecheck a slot at compile time — width, class, element
175/// count), but no function anywhere in `sv-sim-sim::state` hands a caller a raw
176/// `*mut`/`*const` pointer it could add one of these fields to itself: the
177/// actual address computation (`InstanceBase` + `SlotOffset`) happens only
178/// inside `accessor_impl::slot_location_for`/`write::commit_write_at`, never
179/// re-derived by a caller from these fields against a raw base pointer it also
180/// holds.
181#[derive(Clone, Copy, PartialEq, Debug)]
182pub struct SlotOffset {
183 pub class: SlotClass,
184 pub word_offset: u32,
185 pub bit_width: u32,
186 pub elem_word_count: u32,
187 pub row_stride_words: u32,
188 pub num_rows: u32,
189}
190
191/// Mirrors `sv_lang::EdgeKind`'s four variants (ledger SC-045, SC-067, SC-071,
192/// NL-047, NL-083). Kept as this crate's own copy (rather than a re-export)
193/// because `sv-sim-sim`'s cross-track contract layer does not depend on `sv-lang`
194/// in this wave (see `docs/CONTRACTS.md` open questions); the `netlist` track owns
195/// the real conversion once it wires the `sv-lang` dependency for its own
196/// elaboration work.
197#[derive(Clone, Copy, PartialEq, Eq, Debug)]
198pub enum EdgeKind {
199 None,
200 Posedge,
201 Negedge,
202 BothEdges,
203}
204
205/// Lives here, not in the frontend-only `value` module (CONTRACTS D4): the
206/// driver resolver needs it with or without the `frontend` feature.
207///
208/// Reads bit `i` of a `FourStateValue` back out as a `Bit4` — the inverse
209/// per-bit step of [`bits_to_four_state`], used by
210/// [`four_state_to_owned_svint`] to thread known/unknown bits into an
211/// `sv_lang::OwnedSVInt` one bit (or run) at a time.
212///
213/// `pub(crate)` (Task 14 deviation, not in that task's own Files list):
214/// `state::driver::resolve_net_step`'s bit-by-bit fold needs this exact same
215/// per-bit extraction (ST-148's "one table" discipline — reusing this
216/// function, not a second hand-rolled copy of `Bit4::from_val_unk`/
217/// `from_aval_bval`'s polarity). Widening from module-private to
218/// `pub(crate)` only exposes it to this crate's own `state` submodules,
219/// never outside `sv-sim-sim`.
220pub(crate) fn four_state_bit_at(v: &FourStateValue, i: u32) -> Bit4 {
221 match v {
222 FourStateValue::Narrow(word) => {
223 let val = *word as u64;
224 let unk = (*word >> 64) as u64;
225 Bit4::from_val_unk((val >> i) & 1 != 0, (unk >> i) & 1 != 0)
226 }
227 FourStateValue::Wide { aval, bval } => {
228 let word = (i / 64) as usize;
229 let bit = i % 64;
230 let a = aval.get(word).copied().unwrap_or(0);
231 let b = bval.get(word).copied().unwrap_or(0);
232 Bit4::from_aval_bval((a >> bit) & 1 != 0, (b >> bit) & 1 != 0)
233 }
234 }
235}
236
237/// STATE's cross-subsystem four-state value representation (state.md §3/§5,
238/// ledger ST-008, ST-009, ST-099, ST-120): `Narrow` mirrors the accessor ABI's
239/// FIXED inline `u128` word (ARCHITECTURE §6.2 / ST-008: `val` in the low 64
240/// bits, `unk`/xzmask in the high 64 bits, regardless of `bit_width` —
241/// [`sv_sim_abi::bit4`]-compatible; **not** `sv-sim-codegen::value`'s internal,
242/// width-tight `i(2N)` LLVM packing, which uses a different bit offset for any
243/// width < 64 — see `sv_sim_abi::accessor`'s doc comment); `Wide` mirrors the
244/// `aval`/`bval` word planes ([`sv_sim_abi::WideView`]-compatible).
245/// Reference/introspection shape only (VPI/`$display`/debugger/harness) —
246/// never the codegen-emitted hot path, which calls the accessor ABI directly.
247#[derive(Clone, Debug, PartialEq)]
248pub enum FourStateValue {
249 Narrow(u128),
250 Wide { aval: Vec<u64>, bval: Vec<u64> },
251}
252
253impl FourStateValue {
254 /// The value as `(aval, bval)` plane words (`aval = val ^ unk`, `bval =
255 /// unk`); a narrow value is one word.
256 pub fn planes(&self) -> (Vec<u64>, Vec<u64>) {
257 match self {
258 FourStateValue::Narrow(w) => {
259 let (val, unk) = (*w as u64, (*w >> 64) as u64);
260 let (a, b) = sv_sim_abi::bit4::narrow_word_to_wide(val, unk);
261 (vec![a], vec![b])
262 }
263 FourStateValue::Wide { aval, bval } => (aval.clone(), bval.clone()),
264 }
265 }
266}
267
268/// The fixed, small listener-role table state.md §5 dispatches value changes to.
269#[derive(Clone, Copy, PartialEq, Eq, Debug)]
270pub enum ListenerRole {
271 Scheduler,
272 Waveform,
273 Vpi,
274}
275
276/// Called by STATE/NETS after any committed write or net resolution
277/// (scheduler.md §4: "the single, uniform fan-out point for variable writes AND
278/// net resolution"; ledger SC-071, RT-005).
279pub trait ValueChangeSink {
280 fn notify_change(&mut self, cell: StateCellId, edge: EdgeKind, at: SimTime, region: Region);
281
282 /// Whether the sink would use a [`ChangeDelta`] for `cell` (a process
283 /// waits on a bit/part select of it, LRM 9.4.2): a caller builds the delta
284 /// only when this is true, so a write to an unwatched signal stays free of
285 /// the per-write copy.
286 fn wants_delta(&self, cell: StateCellId) -> bool {
287 let _ = cell;
288 false
289 }
290
291 /// The old and new plane words of the update the NEXT
292 /// [`Self::notify_change`] on `cell` reports. Offered only when
293 /// [`Self::wants_delta`] said so.
294 fn note_delta(&mut self, cell: StateCellId, delta: ChangeDelta) {
295 let _ = (cell, delta);
296 }
297}
298
299/// The bits one committed update changed, as plane words (`aval`/`bval`, the
300/// `Wide` encoding): `lo_word` is the plane-word index of `old_a[0]`, so a
301/// windowed update of a huge vector carries only the words it touched. A
302/// bit/part-select event control (LRM 9.4.2) compares the selected bits of
303/// `old` and `new` to decide whether *its* operand changed, and takes an edge
304/// on the select's least-significant bit.
305#[derive(Clone, Debug, Default)]
306pub struct ChangeDelta {
307 pub lo_word: usize,
308 pub old_a: Vec<u64>,
309 pub old_b: Vec<u64>,
310 pub new_a: Vec<u64>,
311 pub new_b: Vec<u64>,
312}
313
314impl ChangeDelta {
315 /// The delta between two whole values (the narrow inline word and the
316 /// wide planes both decode to `aval`/`bval`).
317 pub fn between(old: &FourStateValue, new: &FourStateValue) -> Self {
318 let (old_a, old_b) = old.planes();
319 let (new_a, new_b) = new.planes();
320 Self {
321 lo_word: 0,
322 old_a,
323 old_b,
324 new_a,
325 new_b,
326 }
327 }
328
329 fn word(plane: &[u64], lo_word: usize, word: usize) -> Option<u64> {
330 word.checked_sub(lo_word)
331 .and_then(|i| plane.get(i))
332 .copied()
333 }
334
335 /// Bit `index` of the value before and after, as `(aval, bval)` pairs; a
336 /// bit outside the carried words did not change (`None`).
337 pub fn bit(&self, index: u32) -> Option<((bool, bool), (bool, bool))> {
338 let (w, b) = ((index / 64) as usize, index % 64);
339 let pick = |p: &[u64]| Self::word(p, self.lo_word, w).map(|x| (x >> b) & 1 != 0);
340 Some((
341 (pick(&self.old_a)?, pick(&self.old_b)?),
342 (pick(&self.new_a)?, pick(&self.new_b)?),
343 ))
344 }
345
346 /// Whether any bit of `[lsb, lsb + width)` differs, in either plane.
347 pub fn range_changed(&self, lsb: u32, width: u32) -> bool {
348 let (lo, hi) = (lsb as usize, lsb as usize + width as usize);
349 let first = lo / 64;
350 let last = hi.saturating_sub(1) / 64;
351 for w in first..=last {
352 let (Some(oa), Some(ob), Some(na), Some(nb)) = (
353 Self::word(&self.old_a, self.lo_word, w),
354 Self::word(&self.old_b, self.lo_word, w),
355 Self::word(&self.new_a, self.lo_word, w),
356 Self::word(&self.new_b, self.lo_word, w),
357 ) else {
358 continue;
359 };
360 let lo_bit = lo.saturating_sub(w * 64);
361 let hi_bit = (hi - w * 64).min(64);
362 let mask = if hi_bit - lo_bit >= 64 {
363 u64::MAX
364 } else {
365 ((1u64 << (hi_bit - lo_bit)) - 1) << lo_bit
366 };
367 if ((oa ^ na) | (ob ^ nb)) & mask != 0 {
368 return true;
369 }
370 }
371 false
372 }
373}
374
375/// STATE owns the one canonical signal read/write/subscribe primitive
376/// (ARCHITECTURE §2); RUNTIME's VPI/FST/`$display`/debugger hosts are pure
377/// consumers of this trait, never a second implementation of signal access
378/// (`sv-sim-runtime::signal_consumer` demonstrates this by depending on it).
379pub trait SignalAccess {
380 fn read(&self, cell: StateCellId) -> FourStateValue;
381 /// Returns `true` iff the write actually changed the stored value (the
382 /// old-vs-new compare state.md §3 requires before any VC record is emitted).
383 fn write(&mut self, cell: StateCellId, value: FourStateValue) -> bool;
384 /// One row of a memory cell (`SlotClass::Memory`). Only implemented by
385 /// the real `StateEngine`; a test double that never stages a memory NBA
386 /// never reaches this.
387 fn read_row(&self, cell: StateCellId, row: u64) -> FourStateValue {
388 let _ = row;
389 unreachable!("read_row({cell:?}) on a SignalAccess without memory rows")
390 }
391 /// Writes one row of a memory cell; `true` iff the row changed.
392 fn write_row(&mut self, cell: StateCellId, row: u64, value: FourStateValue) -> bool {
393 let _ = (row, value);
394 unreachable!("write_row({cell:?}) on a SignalAccess without memory rows")
395 }
396 /// The plane words `[lo_word, lo_word + words)` of a wide cell, as
397 /// `(aval, bval)`. The default reads the whole value; `StateEngine`
398 /// reads just the window (a nonblocking write to a few bits of a huge
399 /// vector must not cost `O(N)`).
400 fn read_wide_window(
401 &self,
402 cell: StateCellId,
403 lo_word: usize,
404 words: usize,
405 ) -> (Vec<u64>, Vec<u64>) {
406 match self.read(cell) {
407 FourStateValue::Wide { aval, bval } => (
408 aval[lo_word..lo_word + words].to_vec(),
409 bval[lo_word..lo_word + words].to_vec(),
410 ),
411 FourStateValue::Narrow(_) => {
412 unreachable!("read_wide_window({cell:?}) on a narrow cell")
413 }
414 }
415 }
416 /// Stores the plane words `aval`/`bval` at `lo_word..` of a wide cell;
417 /// `true` iff the value changed. The default patches the whole value;
418 /// `StateEngine` stores just the window.
419 fn write_wide_window(
420 &mut self,
421 cell: StateCellId,
422 lo_word: usize,
423 aval: &[u64],
424 bval: &[u64],
425 ) -> bool {
426 write_wide_window_whole(self, cell, lo_word, aval, bval)
427 }
428 fn subscribe(
429 &mut self,
430 cell: StateCellId,
431 role: ListenerRole,
432 sink: Box<dyn ValueChangeSink + Send>,
433 );
434 /// Changes of other names for storage the last write touched (a window
435 /// alias of a ref port, `state::alias`), drained by the scheduler after
436 /// each commit so it can notify their waiters. Empty for every engine
437 /// that has no window alias.
438 fn take_alias_changes(&mut self) -> Vec<AliasChange> {
439 Vec::new()
440 }
441}
442
443/// [`SignalAccess::write_wide_window`] through the whole-value `write`: the
444/// default, and what `StateEngine` falls back to for a forced cell or one
445/// whose changes are recorded.
446pub(crate) fn write_wide_window_whole<A: SignalAccess + ?Sized>(
447 acc: &mut A,
448 cell: StateCellId,
449 lo_word: usize,
450 aval: &[u64],
451 bval: &[u64],
452) -> bool {
453 let FourStateValue::Wide {
454 aval: mut a,
455 bval: mut b,
456 } = acc.read(cell)
457 else {
458 unreachable!("write_wide_window({cell:?}) on a narrow cell")
459 };
460 a[lo_word..lo_word + aval.len()].copy_from_slice(aval);
461 b[lo_word..lo_word + bval.len()].copy_from_slice(bval);
462 acc.write(cell, FourStateValue::Wide { aval: a, bval: b })
463}
464
465/// Compile-time layout for one unique `(module,params)` template (state.md Key
466/// Data Structures; ledger ST-140). One `StateLayout` per distinct
467/// [`crate::netlist::TemplateKey`] (`Task 5`'s `TemplateTable` maps keys to
468/// these); `inline_words`/`wide_words` are the template-relative word counts
469/// [`Arena`] bulk-allocators (Task 7) reserve per instance.
470#[derive(Clone, Debug, PartialEq)]
471pub struct StateLayout {
472 pub slots: Vec<SlotDescriptor>,
473 pub inline_words: u32,
474 pub wide_words: u32,
475}
476
477/// One signal's slot within a [`StateLayout`] (state.md Key Data Structures;
478/// ledger ST-141).
479///
480/// **Reconciliation (ST-153):** state.md's `{key, offset, initial, net_info}`
481/// (adopted here) vs. runtime.md's independently drafted `{name, byte_offset,
482/// bit_width, is_4state, kind}` are reconciled by *composition, not
483/// duplication*, not by picking one draft wholesale:
484/// - `name` is available by composing `key` with
485/// [`crate::netlist::NetlistView::hierarchical_path`] — never stored a
486/// second time here, since a second copy could drift from the netlist
487/// track's own name table.
488/// - `byte_offset`/`bit_width` are `offset.word_offset`/`offset.bit_width`
489/// (already present on [`SlotOffset`] — ST-144 — no renaming needed).
490/// - `is_4state` is dropped, not reconciled 1:1: 4-state-ness is a
491/// whole-arena [`BuildMode`] (Task 2/10), not a per-slot property, so
492/// runtime.md's per-slot field would be redundant with the arena's own
493/// mode.
494/// - `kind` is `net_info`'s presence/absence (`Some` => net, `None` =>
495/// variable/port) plus [`NetLayout::kind`] when present.
496///
497/// This is the one, documented `SlotDescriptor` shape this plan's every later
498/// task consumes identically, and the one CODEGEN/RUNTIME are expected to
499/// consume once their own waves start.
500///
501/// `initial` is STATE's own sv-lang-free [`FourStateValue`], not
502/// `sv_lang::OwnedSVInt`: this type is consumed by `sv-sim-runtime`, which must
503/// never reach the sv-lang family (ARCHITECTURE §3). The frontend-side layout
504/// builder converts constants with `four_state_of_constant` before storing them.
505#[derive(Clone, Debug, PartialEq)]
506pub struct SlotDescriptor {
507 pub key: crate::netlist::SymbolKey,
508 pub offset: SlotOffset,
509 pub initial: Option<FourStateValue>,
510 pub net_info: Option<NetLayout>,
511}
512
513/// A net member's electrical classification, when known (ledger ST-141).
514///
515/// **Deferred conversion (see Task 3/6 of the wave-A `state` plan and the
516/// plan's Deferred section):** verified against the pinned `sv-lang=0.1.9` C
517/// API (`source/capi/CApiAst.cpp`) — while `slang::ast::NetSymbol` has a
518/// `netType` field and slang's own Python bindings expose it, `CApiAst.cpp`
519/// exposes no `slang_symbol_net_type`/equivalent accessor for it, so no
520/// function in the pinned `sv-lang` Rust crate can recover a declared `Net`
521/// symbol's real `NetKind` this wave. `SlotDescriptor::net_info` therefore
522/// stays structurally correct (an `Option`, `None` for every real member this
523/// wave's layout builder produces, `Net`-kind included) but is never `Some`
524/// for a *real* elaborated net in this plan; every `Some(NetLayout{..})` this
525/// plan's own tests construct (Task 14/15) is hand-built, exercising the
526/// multi-driver resolution *logic*, not a connection to real elaboration.
527#[derive(Clone, Copy, PartialEq, Eq, Debug)]
528pub struct NetLayout {
529 pub kind: NetKind,
530 pub shape: NetShape,
531}
532
533/// The multi-driver resolution shape a net's storage takes (ledger ST-141):
534/// how many distinct strength-bearing drivers [`NetLayout`]'s owning net has
535/// and whether the full K+1 strength lattice must be evaluated to resolve it.
536#[derive(Clone, Copy, PartialEq, Eq, Debug)]
537pub enum NetShape {
538 AliasedSingleDriver,
539 MultiDriverUniformStrength,
540 FullStrengthLattice,
541}
542
543/// Mirrors `sv_lang::NetKind`'s variant set minus `Interconnect` (state.md §4:
544/// an interconnect net never gets a slot at all, so it has no `NetLayout`
545/// value to represent) and minus the `Unknown` error placeholder (never a
546/// real declared net's kind). Kept as this crate's own copy rather than a
547/// re-export of `sv_lang::NetKind` so this module's exhaustive-match
548/// discipline (ARCHITECTURE §7 item 9) applies to a type this crate owns,
549/// independent of `sv_lang`'s own variant set ever changing underneath it.
550#[derive(Clone, Copy, PartialEq, Eq, Debug, serde::Serialize, serde::Deserialize)]
551pub enum NetKind {
552 Wire,
553 WAnd,
554 WOr,
555 Tri,
556 TriAnd,
557 TriOr,
558 Tri0,
559 Tri1,
560 TriReg,
561 Supply0,
562 Supply1,
563 UWire,
564 UserDefined,
565}
566
567#[cfg(test)]
568mod tests {
569 use super::*;
570 use sv_sim_abi::Bit4;
571
572 #[test]
573 fn four_state_narrow_round_trips_via_abi_bit4() {
574 // Exercise the ONE documented sv_sim_abi::bit4 conversion through STATE's
575 // own FourStateValue::Narrow representation, proving the two crates agree
576 // on the packed-word shape: the FIXED accessor-ABI offset (unk at bit
577 // 64), not codegen's width-tight `<< width` packing (review fix -- the
578 // previous version of this test hardcoded `<< 1`, which only "worked"
579 // because it never exercised a width other than 1, where the two
580 // conventions alias).
581 for b in [Bit4::Zero, Bit4::One, Bit4::X, Bit4::Z] {
582 let (val, unk) = b.to_val_unk();
583 let word: u128 = (val as u128) | ((unk as u128) << 64);
584 let fv = FourStateValue::Narrow(word);
585 match fv {
586 FourStateValue::Narrow(w) => assert_eq!(w, word),
587 FourStateValue::Wide { .. } => panic!("expected Narrow"),
588 }
589 }
590 }
591
592 /// Review fix: a width other than 1 that actually distinguishes the FIXED
593 /// accessor layout from codegen's width-tight `i(2N)` packing.
594 #[test]
595 fn four_state_narrow_at_width_8_uses_fixed_64_offset_not_width_offset() {
596 let width: u32 = 8;
597 let val: u64 = 0b1011_0010;
598 let unk: u64 = 0b0000_1000;
599
600 let fixed_word: u128 = (val as u128) | ((unk as u128) << 64);
601 let width_tight_word: u128 = (val as u128) | ((unk as u128) << width);
602 assert_ne!(fixed_word, width_tight_word);
603
604 let fv = FourStateValue::Narrow(fixed_word);
605 match fv {
606 FourStateValue::Narrow(w) => {
607 assert_eq!((w >> 64) as u64, unk);
608 assert_eq!((w & u128::from(u64::MAX)) as u64, val);
609 }
610 FourStateValue::Wide { .. } => panic!("expected Narrow"),
611 }
612 }
613
614 /// ST-140: a compile-level proof that `StateLayout` has exactly the three
615 /// contracted fields, in the contracted names/order — this line fails to
616 /// compile (not merely fails an assertion) if a field were added, removed,
617 /// or renamed.
618 #[test]
619 fn state_layout_has_exactly_the_three_contracted_fields() {
620 let layout = StateLayout {
621 slots: vec![],
622 inline_words: 0,
623 wide_words: 0,
624 };
625 assert!(layout.slots.is_empty());
626 assert_eq!(layout.inline_words, 0);
627 assert_eq!(layout.wide_words, 0);
628 }
629
630 /// ST-141: a literal contract example for Task 4's real layout builder to
631 /// test against later (that test asserts the *builder* produces `None` for
632 /// a `logic` variable, not merely that the type allows it).
633 #[test]
634 fn slot_descriptor_net_info_is_none_for_a_non_net_signal() {
635 let descriptor = SlotDescriptor {
636 key: crate::netlist::SymbolKey(0),
637 offset: SlotOffset {
638 class: SlotClass::Inline,
639 word_offset: 0,
640 bit_width: 1,
641 elem_word_count: 0,
642 row_stride_words: 0,
643 num_rows: 0,
644 },
645 initial: None,
646 net_info: None,
647 };
648 assert!(descriptor.net_info.is_none());
649 }
650
651 /// ST-141/ST-153: an exhaustive `match` over `NetShape`'s 3 variants and
652 /// `NetKind`'s 13 variants, with no wildcard arm — ARCHITECTURE §7 item 9's
653 /// discipline applied to this crate's own enums, not just `sv_lang`'s.
654 /// Adding or removing a variant from either enum breaks this file at
655 /// compile time, which is the actual guard this test provides.
656 #[test]
657 fn net_shape_and_net_kind_cover_the_documented_variant_sets() {
658 fn describe_shape(shape: NetShape) -> &'static str {
659 match shape {
660 NetShape::AliasedSingleDriver => "aliased-single-driver",
661 NetShape::MultiDriverUniformStrength => "multi-driver-uniform-strength",
662 NetShape::FullStrengthLattice => "full-strength-lattice",
663 }
664 }
665
666 fn describe_kind(kind: NetKind) -> &'static str {
667 match kind {
668 NetKind::Wire => "wire",
669 NetKind::WAnd => "wand",
670 NetKind::WOr => "wor",
671 NetKind::Tri => "tri",
672 NetKind::TriAnd => "triand",
673 NetKind::TriOr => "trior",
674 NetKind::Tri0 => "tri0",
675 NetKind::Tri1 => "tri1",
676 NetKind::TriReg => "trireg",
677 NetKind::Supply0 => "supply0",
678 NetKind::Supply1 => "supply1",
679 NetKind::UWire => "uwire",
680 NetKind::UserDefined => "user-defined",
681 }
682 }
683
684 for shape in [
685 NetShape::AliasedSingleDriver,
686 NetShape::MultiDriverUniformStrength,
687 NetShape::FullStrengthLattice,
688 ] {
689 assert!(!describe_shape(shape).is_empty());
690 }
691
692 for kind in [
693 NetKind::Wire,
694 NetKind::WAnd,
695 NetKind::WOr,
696 NetKind::Tri,
697 NetKind::TriAnd,
698 NetKind::TriOr,
699 NetKind::Tri0,
700 NetKind::Tri1,
701 NetKind::TriReg,
702 NetKind::Supply0,
703 NetKind::Supply1,
704 NetKind::UWire,
705 NetKind::UserDefined,
706 ] {
707 assert!(!describe_kind(kind).is_empty());
708 }
709
710 let layout = NetLayout {
711 kind: NetKind::Tri,
712 shape: NetShape::FullStrengthLattice,
713 };
714 assert_eq!(layout.kind, NetKind::Tri);
715 assert_eq!(layout.shape, NetShape::FullStrengthLattice);
716 }
717}