vj_sim/state/driver.rs
1//! Task 14 — Multi-driver resolution: strength lattice, K+1 slots,
2//! force/release (ledger ST-034, ST-055, ST-146).
3//!
4//! **Scope note (from the plan):** `DriverSet`/`combine_bit`/
5//! `resolve_net_step` are exercised exclusively against hand-built
6//! `DriverSet` fixtures — wiring a `DriverSet` to a *real* elaborated net's
7//! actual driver count/`NetKind` needs an `sv_lang` accessor the pinned
8//! `sv-lang=0.1.9` C API does not expose (see `state::mod`'s `NetLayout` doc
9//! comment); that connection is deferred, not this task's job. What this
10//! task delivers, and what ST-034/ST-055/ST-146's literal acceptance text
11//! asks for: the resolution logic itself lives in this crate (ST-034),
12//! `force`/`release` are real (ST-055), and the `AliasedSingleDriver`
13//! -materialization decision is made and documented (ST-146).
14//!
15//! **Deviations from the plan's literal text (documented once here, applying
16//! to everything below — all binding per this task's own unresolved-review-
17//! findings list):**
18//!
19//! 1. **`force_driver`/`release_driver` both take an added `set: &mut
20//! DriverSet` parameter**, not in the plan's literal signatures. The
21//! plan's own test for these two functions
22//! (`force_overrides_a_strong_wire_driver_release_restores_prior_value`)
23//! requires a subsequent `resolve_net_step(engine, &set, ..)` call, on
24//! the *same, caller-held* `DriverSet`, to observe the forced entry's
25//! presence/absence — structurally impossible if `force_driver`/
26//! `release_driver` cannot reach the caller's `DriverSet` at all (no
27//! `StateCellId -> DriverSet` registry exists anywhere in this plan; the
28//! Scope note above is explicit that `DriverSet` is exclusively
29//! caller-owned/hand-built this wave). `resolve_net_step` itself keeps
30//! its plan-pinned `set: &DriverSet` (it never mutates the driver list,
31//! only reads it and writes `set.resolved`'s own cell).
32//! 2. **`ST-146`'s documented decision below is phrased as STATE's own
33//! proposed default, not a bilaterally closed joint decision.** ST-146's
34//! literal acceptance text requires "a written decision... agreed by both
35//! STATE and CODEGEN owners" before CODEGEN's kernel-net-read lowering is
36//! implemented; no CODEGEN track exists this wave to actually agree, so a
37//! unilateral STATE decision cannot fully satisfy that acceptance text by
38//! itself. This task still makes the decision (materialize always,
39//! matching state.md's already-designed default) and documents it in
40//! full, exactly as the plan describes — but frames it explicitly as a
41//! proposal pending CODEGEN concurrence, the same posture ST-028 and the
42//! `NetKind` gap above are already given, rather than asserting the
43//! joint-decision row is fully closed by one side alone.
44//! 3. **`combine_bit` receives contributions as already Z-filtered-eligible
45//! `(Bit4, DriveStrength)` pairs** (the per-contributor
46//! `DriveStrengthPair -> DriveStrength` reduction the plan's prose
47//! describes as part of `combine_bit`'s own algorithm is actually
48//! performed by `resolve_net_step`, which is the only place that has
49//! both a contributor's pair *and* its currently-driven bit in scope at
50//! once); `combine_bit` itself still does the Z-drop, matching its own
51//! pinned signature `&[(Bit4, DriveStrength)]` (not `&[(Bit4,
52//! DriveStrengthPair)]`) literally.
53//! 4. **A local `pack_bits4` helper, not `state::value::bits_to_four_state`.**
54//! `bits_to_four_state` packs `&[sv_lang::Bit]`; `combine_bit`'s own
55//! output is already `Bit4` (the ABI-level type, not `sv_lang::Bit`, and
56//! this file does not otherwise need an `sv_lang` dependency). `pack_bits4`
57//! below still goes through `Bit4::to_val_unk`/`to_aval_bval` exactly as
58//! `bits_to_four_state` does (ST-148's "one table" discipline) — it only
59//! skips the unnecessary `sv_lang::Bit` round-trip.
60//! 5. **`state::value::four_state_bit_at` widened to `pub(crate)`** (edit to
61//! `value.rs`, not in this task's Files list): the same ST-148 "reuse the
62//! one table" discipline applied to the read direction — this file's
63//! per-bit extraction reuses that exact function rather than a second,
64//! parallel copy of its `Bit4::from_val_unk`/`from_aval_bval` logic.
65//! 6. **`StateEngine` gains a `trireg_charge: HashMap<StateCellId,
66//! TriregCharge>` field** (edit to `instance.rs`, not in this task's
67//! Files list): the plan's own prose places `TriregCharge` "in
68//! `StateEngine`'s own `HashMap<StateCellId, TriregCharge>` side table" —
69//! a direct field is the only place for that table to live, per ST-152's
70//! "every resource STATE owns is a direct field" discipline this crate
71//! already applies to every other field of `StateEngine`.
72//! 7. **(r2, blocking review finding 3) `wand`/`triand`/`wor`/`trior`
73//! resolution ignores drive strength entirely** (r1's own fix, made
74//! explicit and reconciled with state.md §4 step 3 here — the doc file
75//! itself is updated to match): matches real LRM/iverilog behavior for the
76//! ordinary multi-driver case, but it means a `force`d entry can no longer
77//! rely on its `Supply` strength alone to win outright on these four net
78//! kinds (a `Supply`-strength contributor is just one more live driver in
79//! the wired-AND/OR fold, and can be outvoted). `DriverEntry` therefore
80//! gains an explicit `forced: bool` tag, and `resolve_net_step`
81//! short-circuits to a forced entry's own value before running
82//! `combine_bit`'s fold at all, on every net kind — see `DriverEntry`'s
83//! and `resolve_net_step`'s own doc comments.
84//! 8. **(r2, blocking review finding 1) `Tri0`/`Tri1`'s implicit resistive
85//! pull, previously entirely omitted:** `combine_bit` now injects it as a
86//! `Pull`-strength live contributor whenever `net_kind` is `Tri0`/`Tri1` —
87//! see `combine_bit`'s own doc comment for the full LRM/iverilog argument.
88//! 9. **(r2, blocking review finding 2) `force_driver`'s Wide/Memory-class
89//! contribution cell no longer grows the ordinary wide plane
90//! (`Arena::alloc_wide_words`), which panics once `sv_get_wide` has sealed
91//! it:** it now grows a separate, never-sealed force-only plane pair
92//! (`Arena::alloc_force_wide_words`, `SlotClass::ForceWide`) that no
93//! accessor-ABI function ever exposes a raw pointer into, so it can never
94//! dangle an already-issued `WideView` regardless of seal state — see
95//! `SlotClass::ForceWide`'s and `Arena::alloc_force_wide_words`'s own doc
96//! comments. `mod.rs`, `arena.rs`, `write.rs`, `dedup.rs`, `instance.rs`,
97//! and `accessor_impl.rs` all needed a mechanical new match arm for the
98//! added `SlotClass` variant (none of them in this task's Files list).
99//! 10. **(Task 15 retrofit, binding per Task 15's own unresolved-review-
100//! findings list) `force_driver`/`release_driver` each call
101//! `StateEngine::notify_force_observed`** after doing their own work —
102//! `force_driver` with `net` (already asserted `== set.resolved`),
103//! `release_driver` with `set.resolved` (read after the entry is removed
104//! and the net re-resolved, since `release_driver`'s own `forced`
105//! parameter names the *driver contribution* cell being removed, not the
106//! net). This file's own Files list (this task's header) names only
107//! `state/driver.rs` (new) and `state/mod.rs`; Task 15's plan text
108//! describes this exact retrofit ("`driver::force_driver`/`release_driver`
109//! (Task 14) each call every registered `ForceObserved` listener") without
110//! listing `driver.rs` under its own Files section — documented here, on
111//! the retrofitted side, per that task's own binding review finding.
112
113use std::cmp::Ordering;
114
115use vj_abi::{Bit4, SimTime};
116
117use super::four_state_bit_at;
118use super::instance::StateEngine;
119use super::write::{ChangeResult, PreWriteHook, SlotLocation};
120use super::{FourStateValue, NetKind, SignalAccess, SlotClass, StateCellId};
121
122/// The 4-level drive-strength lattice (state.md §4 simplifies the LRM's
123/// 8-level charge/drive split to these 4 for resolution purposes).
124/// `Ord`/`PartialOrd` are hand-written, not derived: the plan pins
125/// `Supply > Strong > Pull > Weak`, the *reverse* of this enum's own
126/// declaration order (declaration order is what `#[derive(Ord)]` would use).
127#[derive(Clone, Copy, PartialEq, Eq, Debug, serde::Serialize, serde::Deserialize)]
128pub enum DriveStrength {
129 Supply,
130 Strong,
131 Pull,
132 Weak,
133 /// `highz0`/`highz1` (LRM 28.3): the driver drives nothing for that
134 /// value (it contributes `z`), the bottom of the lattice.
135 HighZ,
136}
137
138impl DriveStrength {
139 /// Higher rank == wins resolution. The one place this lattice's total
140 /// order is defined; `Ord`/`PartialOrd` below and every comparison
141 /// elsewhere in this file goes through this, never a second hand-rolled
142 /// ordering.
143 fn rank(self) -> u8 {
144 match self {
145 DriveStrength::HighZ => 0,
146 DriveStrength::Weak => 1,
147 DriveStrength::Pull => 2,
148 DriveStrength::Strong => 3,
149 DriveStrength::Supply => 4,
150 }
151 }
152}
153
154impl PartialOrd for DriveStrength {
155 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
156 Some(self.cmp(other))
157 }
158}
159
160impl Ord for DriveStrength {
161 fn cmp(&self, other: &Self) -> Ordering {
162 self.rank().cmp(&other.rank())
163 }
164}
165
166/// A driver's own strength for each value it might drive (state.md §4):
167/// `s0` applies when this driver is driving `0`, `s1` when driving `1`;
168/// driving `X` reduces to `min(s0, s1)` (see [`effective_strength`]).
169#[derive(Clone, Copy, PartialEq, Eq, Debug, serde::Serialize, serde::Deserialize)]
170pub struct DriveStrengthPair {
171 pub s0: DriveStrength,
172 pub s1: DriveStrength,
173}
174
175impl DriveStrengthPair {
176 /// The default drive strength: `(strong0, strong1)` (LRM 28.11).
177 pub const STRONG: DriveStrengthPair = DriveStrengthPair {
178 s0: DriveStrength::Strong,
179 s1: DriveStrength::Strong,
180 };
181}
182
183/// One driver's own private contribution slot plus its strength (ST-034):
184/// `source` is a [`StateCellId`] this driver alone writes to (never shared
185/// read-modify-write storage with any other driver or with `resolved`).
186///
187/// **Review fix (Task 14 r2, blocking finding 3) adds `forced`.** Before this
188/// fix, [`force_driver`] modeled `force` purely via `Supply`/`Supply`
189/// strength, relying on strength-grouping (the "every other kind" branch of
190/// [`combine_bit`]) to make it win outright by being the sole top-strength
191/// member. That assumption broke the moment r1 changed `wand`/`triand`/
192/// `wor`/`trior` resolution to skip strength-grouping entirely (matching real
193/// LRM/iverilog behavior for the *ordinary* multi-driver case) — on those net
194/// kinds a `Supply`-strength forced entry is just one more live contributor
195/// in the wired-AND/OR fold and can be outvoted. `forced` is an explicit,
196/// net-kind-independent tag: [`resolve_net_step`] short-circuits to a forced
197/// entry's own value directly (see its doc comment), the way `force`/
198/// `release` (ST-055) are specified to override *every* other driver outright
199/// regardless of net kind or strength. Ordinary drivers always set this
200/// `false`; only [`force_driver`]'s own synthetic entry ever sets it `true`.
201#[derive(Clone, Copy, PartialEq, Eq, Debug)]
202pub struct DriverEntry {
203 pub source: StateCellId,
204 pub strength: DriveStrengthPair,
205 pub forced: bool,
206}
207
208/// The **K+1 slots** design (ST-034): `resolved` is one more [`StateCellId`]
209/// (`StateEngine::alloc_cell`) than the `K` entries in `drivers` — never a
210/// single shared slot every driver writes through. Hand-built by every test
211/// in this task (see this module's own top doc comment, Scope note).
212#[derive(Clone, Debug)]
213pub struct DriverSet {
214 pub resolved: StateCellId,
215 pub drivers: Vec<DriverEntry>,
216 pub net_kind: NetKind,
217}
218
219/// The loud, typed rejection [`combine_bit`]/[`resolve_net_step`] return
220/// instead of panicking or silently guessing a winner for a net kind this
221/// fold cannot resolve (ARCHITECTURE §6.5's "hard-error, not silently
222/// wrong" for `nettype...with`, applied to this crate's own `NetKind` enum
223/// per ARCHITECTURE §7 item 9).
224#[derive(Debug, Clone, Copy, PartialEq, Eq)]
225pub enum DriverError {
226 UnresolvableUserDefinedNetType,
227}
228
229/// `trireg`'s own charge-decay state (state.md §4/§6): stored in
230/// `StateEngine`'s own side table (`instance.rs`'s `trireg_charge` field),
231/// never as a normal [`DriverEntry`] — `trireg` decay *timing* itself is a
232/// TIMING-subsystem hook this task only stores state for, not implemented
233/// here.
234#[derive(Clone, Debug)]
235pub struct TriregCharge {
236 pub last_value: FourStateValue,
237 pub strength: ChargeStrength,
238 pub last_drive_time: SimTime,
239}
240
241/// `trireg`'s charge-strength lattice — kept separate from [`DriveStrength`]
242/// (state.md §4: `trireg` charge decay is not part of the ordinary K+1
243/// drive-strength fold `combine_bit` performs).
244#[derive(Clone, Copy, PartialEq, Eq, Debug)]
245pub enum ChargeStrength {
246 Small,
247 Medium,
248 Large,
249}
250
251impl StateEngine {
252 /// Task 14 (ST-034): read-only lookup into this engine's own
253 /// `trireg_charge` side table.
254 pub fn trireg_charge(&self, cell: StateCellId) -> Option<&TriregCharge> {
255 self.trireg_charge.get(&cell)
256 }
257
258 /// Task 14 (ST-034): records/replaces `cell`'s stored charge state. No
259 /// decay-timing behavior happens here or anywhere in this crate this
260 /// wave — see [`TriregCharge`]'s own doc comment.
261 pub fn set_trireg_charge(&mut self, cell: StateCellId, charge: TriregCharge) {
262 self.trireg_charge.insert(cell, charge);
263 }
264}
265
266/// Reduces one driver's [`DriveStrengthPair`] plus its currently-driven bit
267/// to a single effective [`DriveStrength`] (state.md §4): driving `0` picks
268/// `s0`, driving `1` picks `s1`, driving `X` picks `min(s0, s1)` (this task's
269/// own pinned resolution of state.md's previously-open "driver outputting X"
270/// question — a v1 default, not re-opened here). A `Z`-driving contributor's
271/// strength is never actually consulted (`combine_bit` drops every `Z`
272/// contributor before grouping by strength) — this arm exists only so the
273/// function is total; its value is irrelevant by construction.
274fn effective_strength(bit: Bit4, pair: DriveStrengthPair) -> DriveStrength {
275 match bit {
276 Bit4::Zero => pair.s0,
277 Bit4::One => pair.s1,
278 // An `x` from a `highz` side still drives at the other side's level.
279 Bit4::X => match pair.s0.min(pair.s1) {
280 DriveStrength::HighZ => pair.s0.max(pair.s1),
281 weaker => weaker,
282 },
283 Bit4::Z => pair.s0,
284 }
285}
286
287/// The per-bit combinator (state.md §4). `net_kind == NetKind::UserDefined`
288/// returns `Err` immediately, regardless of `contributions` (even an empty
289/// slice) — v1 has no `nettype...with` resolution-function plugin, so this
290/// net kind can never be folded, not even trivially. Every other kind first
291/// drops `Z`-valued contributors, then — **`Tri0`/`Tri1` only** (review fix,
292/// Task 14 r2, blocking finding 1) — adds one *implicit* live contributor of
293/// its own: the LRM-mandated resistive pull-to-0 (`tri0`) / pull-to-1
294/// (`tri1`) at [`DriveStrength::Pull`], which always participates in
295/// strength-grouping below alongside any real contributions, even when there
296/// are none. This is what makes an undriven `tri0`/`tri1` net read `0`/`1`
297/// (not `Z`, unlike every other net kind) and what makes an explicit `Weak`
298/// driver lose to the implicit pull rather than winning outright — verified
299/// empirically against iverilog 12 (`tri0 w;` alone reads `0`; `tri0 w;
300/// assign (weak0,weak1) w=1;` still reads `0`). Zero remaining contributors
301/// after that (every other kind, all-`Z`/`K==0`) ⇒ `Z` — an emergent fold
302/// result, never a special case. From there two genuinely different
303/// algorithms apply, per IEEE 1800 Table 28-8:
304///
305/// - **`wand`/`triand`/`wor`/`trior`** (wired-logic net types): drive
306/// strength plays **no role** in resolution — every live (non-Z)
307/// contributor, at *any* strength, participates directly in the
308/// wired-AND/OR fold (`wand`/`triand`: `0` dominates, else `X` if any `X`,
309/// else `1`; `wor`/`trior`: `1` dominates, else `X` if any `X`, else `0`).
310/// Strength is never consulted for these four kinds, not even to pick a
311/// subset of participants. (`Tri0`/`Tri1`'s implicit pull, above, never
312/// applies here — it is not one of these four kinds.)
313/// - **every other kind** (`wire`/`tri`-family — including `Tri0`/`Tri1`'s
314/// own implicit-pull contributor above — `supply0`/`supply1`, `uwire`):
315/// group live contributors by (already-reduced, see
316/// [`effective_strength`]) strength, highest group wins; single winner or
317/// multiple agreeing in that group ⇒ its value; multiple disagreeing ⇒
318/// `X`.
319///
320/// Exhaustive over `NetKind`'s 13 variants, no wildcard arm (ARCHITECTURE §7
321/// item 9) — enforced by
322/// `combine_bit_and_resolve_net_step_reject_userdefined_net_kind_with_a_typed_error`
323/// below, not only by this doc comment.
324fn combine_bit(
325 contributions: &[(Bit4, DriveStrength)],
326 net_kind: NetKind,
327) -> Result<Bit4, DriverError> {
328 combine_bit_strength(contributions, net_kind).map(|(bit, _)| bit)
329}
330
331/// [`combine_bit`] plus the strength the result is driven at (LRM 28.12: the
332/// strength of the winning group; `None` for a `z`). A resolution that ends
333/// in `x` from disagreeing drivers keeps the strength of the group that
334/// disagreed. The single fold both the value and the `%v` strength view use.
335fn combine_bit_strength(
336 contributions: &[(Bit4, DriveStrength)],
337 net_kind: NetKind,
338) -> Result<(Bit4, Option<DriveStrength>), DriverError> {
339 if let NetKind::UserDefined = net_kind {
340 return Err(DriverError::UnresolvableUserDefinedNetType);
341 }
342
343 let mut live: Vec<(Bit4, DriveStrength)> = contributions
344 .iter()
345 .copied()
346 .filter(|(bit, s)| *bit != Bit4::Z && *s != DriveStrength::HighZ)
347 .collect();
348
349 // Review fix (Task 14 r2, blocking finding 1): Tri0/Tri1's implicit
350 // resistive pull is injected here, unconditionally, *before* the
351 // live-emptiness check below -- so it alone keeps an otherwise-undriven
352 // tri0/tri1 net out of the `Z` fold result, and so it genuinely competes
353 // in strength-grouping against any real (even Weak) contributors rather
354 // than only being consulted when nothing else drives the net.
355 match net_kind {
356 NetKind::Tri0 => live.push((Bit4::Zero, DriveStrength::Pull)),
357 NetKind::Tri1 => live.push((Bit4::One, DriveStrength::Pull)),
358 _ => {}
359 }
360
361 if live.is_empty() {
362 return Ok((Bit4::Z, None));
363 }
364
365 // Wired-logic net types: strength plays no role at all (IEEE 1800 Table
366 // 28-8) — every live driver folds through the wired-AND/OR truth table
367 // directly, never gated by a strength-grouping step first. This is a
368 // *different* algorithm from the strength-gated fold below, not that
369 // same fold merely restricted to a strength-selected subset.
370 if matches!(
371 net_kind,
372 NetKind::WAnd | NetKind::TriAnd | NetKind::WOr | NetKind::TriOr
373 ) {
374 let is_and = matches!(net_kind, NetKind::WAnd | NetKind::TriAnd);
375 let dominant = if is_and { Bit4::Zero } else { Bit4::One };
376 let recessive = if is_and { Bit4::One } else { Bit4::Zero };
377 let top = live.iter().map(|(_, s)| *s).max();
378 return Ok((
379 if live.iter().any(|(bit, _)| *bit == dominant) {
380 dominant
381 } else if live.iter().any(|(bit, _)| *bit == Bit4::X) {
382 Bit4::X
383 } else {
384 recessive
385 },
386 top,
387 ));
388 }
389
390 let max_strength = live
391 .iter()
392 .map(|(_, strength)| *strength)
393 .max()
394 .expect("live is non-empty, checked above");
395 let winners: Vec<Bit4> = live
396 .iter()
397 .filter(|(_, strength)| *strength == max_strength)
398 .map(|(bit, _)| *bit)
399 .collect();
400
401 let first = winners[0];
402 if winners.iter().all(|bit| *bit == first) {
403 return Ok((first, Some(max_strength)));
404 }
405
406 let x = match net_kind {
407 NetKind::Wire
408 | NetKind::Tri
409 | NetKind::Tri0
410 | NetKind::Tri1
411 | NetKind::TriReg
412 | NetKind::Supply0
413 | NetKind::Supply1
414 | NetKind::UWire => Bit4::X,
415 NetKind::WAnd | NetKind::TriAnd | NetKind::WOr | NetKind::TriOr => {
416 unreachable!("handled above, before strength grouping")
417 }
418 NetKind::UserDefined => unreachable!("returned Err above"),
419 };
420 Ok((x, Some(max_strength)))
421}
422
423/// Per-bit resolution of `values` like [`resolve_values`], keeping each
424/// bit's strength (LRM 28.12; `None` for `z`) for `%v` (LRM 21.2.1.5).
425/// Index 0 is bit 0. `supply0`/`supply1` nets are `Supply`.
426pub fn resolve_strengths(
427 values: &[(FourStateValue, DriveStrengthPair)],
428 width: u32,
429 net_kind: NetKind,
430) -> Result<Vec<(Bit4, Option<DriveStrength>)>, DriverError> {
431 (0..width)
432 .map(|i| match net_kind {
433 NetKind::Supply0 => Ok((Bit4::Zero, Some(DriveStrength::Supply))),
434 NetKind::Supply1 => Ok((Bit4::One, Some(DriveStrength::Supply))),
435 _ => {
436 let contributions: Vec<(Bit4, DriveStrength)> = values
437 .iter()
438 .map(|(v, strength)| {
439 let bit = four_state_bit_at(v, i);
440 (bit, effective_strength(bit, *strength))
441 })
442 .collect();
443 combine_bit_strength(&contributions, net_kind)
444 }
445 })
446 .collect()
447}
448
449/// Packs already-resolved [`Bit4`]s into a [`FourStateValue`] shaped for
450/// `class` (see this module's top doc comment, deviation 4, for why this is
451/// a local helper rather than `state::value::bits_to_four_state`) — goes
452/// through the exact same [`Bit4::to_val_unk`]/[`Bit4::to_aval_bval`] tables
453/// that function uses, never a hand-rolled polarity (ST-148).
454fn pack_bits4(class: SlotClass, elem_word_count: u32, bits: &[Bit4]) -> FourStateValue {
455 match class {
456 SlotClass::Inline => {
457 let mut val: u64 = 0;
458 let mut unk: u64 = 0;
459 for (i, bit) in bits.iter().enumerate() {
460 let (v, u) = bit.to_val_unk();
461 if v {
462 val |= 1u64 << i;
463 }
464 if u {
465 unk |= 1u64 << i;
466 }
467 }
468 FourStateValue::Narrow((val as u128) | ((unk as u128) << 64))
469 }
470 SlotClass::Wide | SlotClass::Memory => {
471 let words = elem_word_count as usize;
472 let mut aval = vec![0u64; words];
473 let mut bval = vec![0u64; words];
474 for (i, bit) in bits.iter().enumerate() {
475 let (a, b) = bit.to_aval_bval();
476 let word = i / 64;
477 let lane = i % 64;
478 if a {
479 aval[word] |= 1u64 << lane;
480 }
481 if b {
482 bval[word] |= 1u64 << lane;
483 }
484 }
485 FourStateValue::Wide { aval, bval }
486 }
487 // `class` here is always `set.resolved`'s own class (this function's
488 // only caller, `resolve_net_step`, passes `resolved_loc.class`); a
489 // `resolved` cell is never itself `SlotClass::ForceWide` -- see
490 // `force_driver`'s identically-justified `unreachable!` arm and
491 // `SlotClass::ForceWide`'s own doc comment.
492 SlotClass::ForceWide => {
493 unreachable!("pack_bits4: set.resolved is never itself SlotClass::ForceWide")
494 }
495 }
496}
497
498/// Resolves one `width`-bit net of `net_kind` from its drivers' current
499/// values and strengths (each full-width; a bit a driver does not drive is
500/// `z`), per bit through [`combine_bit`]. A `supply0`/`supply1` net is its
501/// constant unless a supply-strength driver disagrees (`x`). The result is shaped Narrow (up to 64 bits) or Wide.
502pub fn resolve_values(
503 values: &[(FourStateValue, DriveStrengthPair)],
504 width: u32,
505 net_kind: NetKind,
506) -> Result<FourStateValue, DriverError> {
507 let (class, words) = if width <= 64 {
508 (SlotClass::Inline, 1)
509 } else {
510 (SlotClass::Wide, width.div_ceil(64))
511 };
512 let mut bits = Vec::with_capacity(width as usize);
513 for i in 0..width {
514 let mut contributions: Vec<(Bit4, DriveStrength)> = values
515 .iter()
516 .map(|(v, strength)| {
517 let bit = four_state_bit_at(v, i);
518 (bit, effective_strength(bit, *strength))
519 })
520 .collect();
521 // A `supply0`/`supply1` net carries an implicit supply-strength
522 // driver of its constant (LRM 6.6.1), so it is that constant unless
523 // another driver is *also* supply-strength with the other value
524 // (Table 28-9: the conflict resolves to `x`).
525 if net_kind == NetKind::Supply0 {
526 contributions.push((Bit4::Zero, DriveStrength::Supply));
527 } else if net_kind == NetKind::Supply1 {
528 contributions.push((Bit4::One, DriveStrength::Supply));
529 }
530 let bit = combine_bit(&contributions, net_kind)?;
531 bits.push(bit);
532 }
533 Ok(pack_bits4(class, words, &bits))
534}
535
536/// Reads all `K` contribution cells via [`SignalAccess::read`], folds them
537/// bit-by-bit through [`combine_bit`] (propagating the first `Err` out via
538/// `?`, never partially committing some bits and rejecting others), and
539/// `commit_write`s the result into `set.resolved` only on a fully-resolved
540/// `Ok` — schedulable exactly like any other write (ARCHITECTURE §6.2: this
541/// function is the pure function ENGINE later wraps as a synthetic
542/// sensitivity-driven process, not the scheduling itself).
543///
544/// **Documented decision (ST-146, see this module's top doc comment,
545/// deviation 2, for the joint-sign-off caveat):** always materializes the
546/// resolved value into `set.resolved`, including for the trivial
547/// single-driver (`K==1`) case — the simpler, already-designed default from
548/// state.md, chosen over CODEGEN inlining a reader-side shortcut. This is
549/// STATE's own proposed default for this wave, not yet bilaterally agreed
550/// with CODEGEN (no CODEGEN track exists this wave to agree) — CODEGEN's own
551/// kernel-net-read wave may revisit it.
552///
553/// **Review fix (Task 14 r2, blocking finding 3):** before running
554/// [`combine_bit`]'s per-net-kind fold at all, checks whether any
555/// [`DriverEntry`] in `set.drivers` is [`DriverEntry::forced`] and, if so,
556/// short-circuits straight to that entry's own current value, verbatim —
557/// `force`/`release` (ST-055) override *every* other driver outright on
558/// *every* net kind, which the ordinary per-bit fold can no longer be relied
559/// on to produce merely by giving the forced entry `Supply` strength (see
560/// this module's top doc comment, deviation 7, and [`DriverEntry`]'s own doc
561/// comment, for why that stopped being sufficient once `wand`/`wor` stopped
562/// consulting strength at all). `NetKind::UserDefined` is still rejected
563/// first, even when forced — `force`/`release` are only ever exercised on
564/// ordinary, non-`UserDefined` nets in v1 (this module's top doc comment,
565/// Scope note), so this ordering makes no observable difference for any
566/// real caller, but keeps `resolve_net_step`'s `UnresolvableUserDefinedNetType`
567/// guarantee unconditional rather than force-dependent. At most one forced
568/// entry is expected at a time in v1 (`force_driver`'s own doc comment); if
569/// more than one were ever present, the first in `set.drivers` order wins,
570/// deterministically.
571pub fn resolve_net_step(
572 engine: &mut StateEngine,
573 set: &DriverSet,
574 hook: &mut dyn PreWriteHook,
575) -> Result<ChangeResult, DriverError> {
576 if let NetKind::UserDefined = set.net_kind {
577 return Err(DriverError::UnresolvableUserDefinedNetType);
578 }
579
580 if let Some(forced_entry) = set.drivers.iter().find(|entry| entry.forced) {
581 let forced_value = engine.read(forced_entry.source);
582 return Ok(engine.commit_write(set.resolved, forced_value, hook));
583 }
584
585 let resolved_loc: SlotLocation = engine.cells[set.resolved.0 as usize];
586 let width = resolved_loc.bit_width;
587
588 let mut resolved_bits: Vec<Bit4> = Vec::with_capacity(width as usize);
589 for i in 0..width {
590 let contributions: Vec<(Bit4, DriveStrength)> = set
591 .drivers
592 .iter()
593 .map(|entry| {
594 let value = engine.read(entry.source);
595 let bit = four_state_bit_at(&value, i);
596 (bit, effective_strength(bit, entry.strength))
597 })
598 .collect();
599 resolved_bits.push(combine_bit(&contributions, set.net_kind)?);
600 }
601
602 let new_value = pack_bits4(
603 resolved_loc.class,
604 resolved_loc.elem_word_count,
605 &resolved_bits,
606 );
607 Ok(engine.commit_write(set.resolved, new_value, hook))
608}
609
610/// `force`/`release`/`deassign` (ST-055), modeled as a synthetic,
611/// singleton-partition dynamic driver: allocates one more [`DriverEntry`]
612/// (`Supply`-strength, so it always wins outright per the lattice above,
613/// matching `force`'s LRM override semantics), seeds its private contribution
614/// cell with `value`, pushes it onto `set.drivers`, and returns its
615/// [`StateCellId`] so the caller can later [`release_driver`] it.
616///
617/// `net` must be `set`'s own `resolved` cell (this module's top doc comment,
618/// deviation 1, explains why `set` itself must also be passed — `net` alone,
619/// as the plan's literal signature had it, gives this function no way to
620/// reach the caller's `DriverSet` at all). Checked with a real `assert_eq!`
621/// (not `debug_assert_eq!`): this is a caller-contract precondition, not a
622/// debug-only sanity check, and `net` would otherwise never be read by this
623/// function's body at all.
624pub fn force_driver(
625 engine: &mut StateEngine,
626 set: &mut DriverSet,
627 net: StateCellId,
628 value: FourStateValue,
629) -> StateCellId {
630 assert_eq!(
631 net, set.resolved,
632 "force_driver: `net` must be this DriverSet's own resolved cell"
633 );
634
635 let resolved_loc: SlotLocation = engine.cells[set.resolved.0 as usize];
636 let source = match resolved_loc.class {
637 SlotClass::Inline => {
638 // SAFETY: `engine` is exclusively borrowed (`&mut StateEngine`)
639 // for the duration of this call; no other live borrow of the
640 // inline plane is held across this allocation, matching every
641 // other caller of `Arena::alloc_inline_word` in this crate.
642 let word_offset = unsafe { engine.arena.alloc_inline_word() };
643 SlotLocation {
644 class: SlotClass::Inline,
645 word_offset,
646 bit_width: resolved_loc.bit_width,
647 elem_word_count: 1,
648 row_stride_words: 0,
649 num_rows: 0,
650 }
651 }
652 SlotClass::Wide | SlotClass::Memory => {
653 // Review fix (Task 14 r2, blocking finding 2): allocate from
654 // `Arena`'s separate, never-sealed `force_wide_aval`/
655 // `force_wide_bval` planes (`SlotClass::ForceWide`), not the
656 // ordinary wide planes `alloc_wide_words` refuses to grow once
657 // `sv_get_wide` has sealed them. `force`/`release` (ST-055) must
658 // stay real, always-available runtime operations regardless of
659 // whether any wide signal has already been read through the
660 // accessor ABI — see `Arena::alloc_force_wide_words`'s doc
661 // comment for the full argument.
662 //
663 // SAFETY: same single-logical-writer/single-borrow argument as
664 // the `Inline` arm above, applied to the force-only wide planes.
665 let word_offset = unsafe {
666 engine
667 .arena
668 .alloc_force_wide_words(resolved_loc.elem_word_count)
669 };
670 SlotLocation {
671 class: SlotClass::ForceWide,
672 word_offset,
673 bit_width: resolved_loc.bit_width,
674 elem_word_count: resolved_loc.elem_word_count,
675 row_stride_words: 0,
676 num_rows: 0,
677 }
678 }
679 // `set.resolved` is always a real net's resolved cell (this
680 // function's own `assert_eq!` above); `SlotClass::ForceWide` is
681 // minted only by this same match's `Wide | Memory` arm, for a
682 // *contribution* cell, never for a `resolved` cell — see
683 // `SlotClass::ForceWide`'s own doc comment.
684 SlotClass::ForceWide => {
685 unreachable!("force_driver: set.resolved is never itself SlotClass::ForceWide")
686 }
687 };
688 let source = engine.alloc_cell(source);
689 // Review fix (Task 16 r1, blocking finding 2): overwrite `alloc_cell`'s
690 // conservative `CellReset::AllX` default for this synthetic contribution
691 // cell -- mirroring `allocate_instance`'s own overwrite for its real
692 // slots (`state::instance`) -- so `StateEngine::reset_to_initial` never
693 // blits a reset value over a live `force` contribution. See
694 // `CellReset::ForceContribution`'s own doc comment (`state::rollback`)
695 // for the silent-corruption hazard this closes.
696 engine.cell_reset[source.0 as usize] = super::rollback::CellReset::ForceContribution;
697 engine.commit_write(source, value, &mut ());
698
699 // Review fix (Task 14 r2, blocking finding 3): tag this synthetic entry
700 // as `forced` (not merely `Supply`-strength) — see `DriverEntry::forced`'s
701 // own doc comment and `resolve_net_step`'s doc comment for why the
702 // strength alone is no longer sufficient to guarantee force wins
703 // outright on every net kind.
704 set.drivers.push(DriverEntry {
705 source,
706 strength: DriveStrengthPair {
707 s0: DriveStrength::Supply,
708 s1: DriveStrength::Supply,
709 },
710 forced: true,
711 });
712
713 // Task 15 retrofit (ST-122's external-write half, see this module's top
714 // doc comment, deviation 10): `net` was already asserted `== set.resolved`
715 // above, so this names exactly the net a future partitioning pass would
716 // otherwise (wrongly) assume no external writer ever touches.
717 engine.notify_force_observed(net);
718
719 source
720}
721
722/// The other half of [`force_driver`]: removes `forced`'s one `DriverEntry`
723/// from `set.drivers` and re-runs [`resolve_net_step`], unwrapping its `Ok`
724/// — `force`/`release` are only ever exercised on ordinary, non-`UserDefined`
725/// nets in v1 (this module's top doc comment, Scope note), so the `Err` arm
726/// is unreachable from this function and it never wraps it further.
727pub fn release_driver(engine: &mut StateEngine, set: &mut DriverSet, forced: StateCellId) {
728 let before = set.drivers.len();
729 set.drivers.retain(|entry| entry.source != forced);
730 assert_eq!(
731 set.drivers.len(),
732 before - 1,
733 "release_driver: `forced` must be the `StateCellId` of a `DriverEntry` \
734 currently present in `set.drivers` (from a prior `force_driver` call on this same set)"
735 );
736
737 resolve_net_step(engine, set, &mut ()).expect(
738 "release_driver: force/release only ever targets a non-UserDefined net in v1, \
739 so resolve_net_step's Err arm is unreachable here",
740 );
741
742 // Task 15 retrofit (ST-122's external-write half, see this module's top
743 // doc comment, deviation 10): `forced` above names the removed *driver
744 // contribution* cell, not the net -- `set.resolved` is the actual net a
745 // future partitioning pass would otherwise (wrongly) assume no external
746 // writer ever touches.
747 engine.notify_force_observed(set.resolved);
748}
749
750#[cfg(test)]
751mod tests {
752 use super::*;
753 use crate::state::instance::TemplateRegistry;
754
755 fn fresh_engine() -> StateEngine {
756 StateEngine::new(TemplateRegistry::new())
757 }
758
759 /// Allocates a fresh, zero-initialized 1-bit inline cell — every driver
760 /// contribution and every `resolved` cell in this task's tests is a
761 /// scalar net.
762 fn scalar_cell(engine: &mut StateEngine) -> StateCellId {
763 // SAFETY: `engine` is a local, single-owned value with no other live
764 // borrow of the inline plane, and every test here is single-threaded
765 // with no concurrent allocation.
766 let word_offset = unsafe { engine.arena.alloc_inline_word() };
767 let loc = SlotLocation {
768 class: SlotClass::Inline,
769 word_offset,
770 bit_width: 1,
771 elem_word_count: 1,
772 row_stride_words: 0,
773 num_rows: 0,
774 };
775 engine.alloc_cell(loc)
776 }
777
778 /// A scalar cell pre-seeded (via a real `commit_write`) to drive `bit`.
779 fn seeded_scalar_cell(engine: &mut StateEngine, bit: Bit4) -> StateCellId {
780 let cell = scalar_cell(engine);
781 let (v, u) = bit.to_val_unk();
782 let word = (v as u128) | ((u as u128) << 64);
783 engine.commit_write(cell, FourStateValue::Narrow(word), &mut ());
784 cell
785 }
786
787 /// Reads bit 0 of `cell`'s currently-stored value back out as a `Bit4`.
788 fn read_bit(engine: &StateEngine, cell: StateCellId) -> Bit4 {
789 four_state_bit_at(&engine.read(cell), 0)
790 }
791
792 fn strong_pair() -> DriveStrengthPair {
793 DriveStrengthPair {
794 s0: DriveStrength::Strong,
795 s1: DriveStrength::Strong,
796 }
797 }
798
799 /// Allocates a fresh, zero-initialized 128-bit `SlotClass::Wide` cell
800 /// (`bit_width > 64`, `elem_word_count == 2`) — the storage shape
801 /// `force_driver`'s Wide/Memory branch (Task 14 r2, blocking review
802 /// finding 2) exercises.
803 fn wide_cell(engine: &mut StateEngine) -> StateCellId {
804 // SAFETY: `engine` is a local, single-owned value with no other live
805 // borrow of the wide plane, and every test here is single-threaded
806 // with no concurrent allocation.
807 let word_offset = unsafe { engine.arena.alloc_wide_words(2) };
808 let loc = SlotLocation {
809 class: SlotClass::Wide,
810 word_offset,
811 bit_width: 128,
812 elem_word_count: 2,
813 row_stride_words: 0,
814 num_rows: 0,
815 };
816 engine.alloc_cell(loc)
817 }
818
819 /// A 128-bit `SlotClass::Wide` cell pre-seeded (via a real
820 /// `commit_write`) with `aval0` as its low word's known-value bits (no
821 /// unknown bits, high word zero).
822 fn seeded_wide_cell(engine: &mut StateEngine, aval0: u64) -> StateCellId {
823 let cell = wide_cell(engine);
824 engine.commit_write(
825 cell,
826 FourStateValue::Wide {
827 aval: vec![aval0, 0],
828 bval: vec![0, 0],
829 },
830 &mut (),
831 );
832 cell
833 }
834
835 /// Reads a `SlotClass::Wide` cell's low `aval` word back out.
836 fn read_wide_aval0(engine: &StateEngine, cell: StateCellId) -> u64 {
837 match engine.read(cell) {
838 FourStateValue::Wide { aval, .. } => aval[0],
839 FourStateValue::Narrow(_) => panic!("expected a Wide value"),
840 }
841 }
842
843 /// ST-034: the concrete structural proof of "K+1, never a single shared
844 /// slot" — a `DriverSet` with 3 `DriverEntry`s has `drivers.len() == 3`
845 /// and `resolved` is a *4th*, distinct `StateCellId`.
846 #[test]
847 fn k_plus_1_slots_means_k_contribution_cells_plus_one_resolved_cell() {
848 let mut engine = fresh_engine();
849 let resolved = scalar_cell(&mut engine);
850 let d0 = seeded_scalar_cell(&mut engine, Bit4::Zero);
851 let d1 = seeded_scalar_cell(&mut engine, Bit4::One);
852 let d2 = seeded_scalar_cell(&mut engine, Bit4::X);
853 let set = DriverSet {
854 resolved,
855 drivers: vec![
856 DriverEntry {
857 source: d0,
858 strength: strong_pair(),
859 forced: false,
860 },
861 DriverEntry {
862 source: d1,
863 strength: strong_pair(),
864 forced: false,
865 },
866 DriverEntry {
867 source: d2,
868 strength: strong_pair(),
869 forced: false,
870 },
871 ],
872 net_kind: NetKind::Wire,
873 };
874
875 assert_eq!(set.drivers.len(), 3);
876 assert_ne!(set.resolved, d0);
877 assert_ne!(set.resolved, d1);
878 assert_ne!(set.resolved, d2);
879 }
880
881 /// state.md §4 step 3: two `Strong` drivers agreeing on `One` resolve to
882 /// `One` on an ordinary `Wire`.
883 #[test]
884 fn two_strong_drivers_agreeing_resolve_to_that_value_wire() {
885 let mut engine = fresh_engine();
886 let resolved = scalar_cell(&mut engine);
887 let d0 = seeded_scalar_cell(&mut engine, Bit4::One);
888 let d1 = seeded_scalar_cell(&mut engine, Bit4::One);
889 let set = DriverSet {
890 resolved,
891 drivers: vec![
892 DriverEntry {
893 source: d0,
894 strength: strong_pair(),
895 forced: false,
896 },
897 DriverEntry {
898 source: d1,
899 strength: strong_pair(),
900 forced: false,
901 },
902 ],
903 net_kind: NetKind::Wire,
904 };
905
906 resolve_net_step(&mut engine, &set, &mut ()).unwrap();
907 assert_eq!(read_bit(&engine, resolved), Bit4::One);
908 }
909
910 /// state.md §4 step 3: a `Strong`-driving-`0` beats a `Weak`-driving-`1`
911 /// on a `Wire` regardless of which order the two entries are registered
912 /// in `DriverSet.drivers` — proves the fold is order-independent, not
913 /// accidentally favoring registration order over strength.
914 #[test]
915 fn strong_beats_weak_on_a_wire_regardless_of_registration_order() {
916 for reversed in [false, true] {
917 let mut engine = fresh_engine();
918 let resolved = scalar_cell(&mut engine);
919 let strong0 = seeded_scalar_cell(&mut engine, Bit4::Zero);
920 let weak1 = seeded_scalar_cell(&mut engine, Bit4::One);
921 let mut drivers = vec![
922 DriverEntry {
923 source: strong0,
924 strength: strong_pair(),
925 forced: false,
926 },
927 DriverEntry {
928 source: weak1,
929 strength: DriveStrengthPair {
930 s0: DriveStrength::Weak,
931 s1: DriveStrength::Weak,
932 },
933 forced: false,
934 },
935 ];
936 if reversed {
937 drivers.reverse();
938 }
939 let set = DriverSet {
940 resolved,
941 drivers,
942 net_kind: NetKind::Wire,
943 };
944
945 resolve_net_step(&mut engine, &set, &mut ()).unwrap();
946 assert_eq!(
947 read_bit(&engine, resolved),
948 Bit4::Zero,
949 "reversed={reversed}: strong driver must win regardless of registration order"
950 );
951 }
952 }
953
954 /// Mutation regression (survived the mutation-testing pass): a
955 /// contributor driving `X` reduces its two-sided `DriveStrengthPair` to
956 /// a single [`DriveStrength`] via `effective_strength`, which
957 /// `combine_bit`'s own doc comment pins as `min(s0, s1)` (state.md §4's
958 /// "driver outputting X" resolution), never `max(s0, s1)`. Built so the
959 /// two reductions disagree observably: the `X`-driver's pair is
960 /// `{s0: Weak, s1: Strong}` (`min` = `Weak`, `max` = `Strong`) alongside
961 /// a `Zero`-driver at `Strong`/`Strong`. Under the correct `min`
962 /// reduction the `X`-driver's effective strength is `Weak`, strictly
963 /// below the `Zero`-driver's `Strong`, so `Zero` wins outright. Under a
964 /// `max` reduction the `X`-driver's effective strength would tie the
965 /// `Zero`-driver's at `Strong`, and two disagreeing winners in the same
966 /// strength group fold to `X` on an ordinary `Wire` (per
967 /// `two_strong_disagreeing_drivers_resolve_per_net_kind` above) — an
968 /// observably different, wrong result this test would catch.
969 #[test]
970 fn effective_strength_reduces_an_x_driving_contributor_by_the_weaker_of_its_two_strengths() {
971 let mut engine = fresh_engine();
972 let resolved = scalar_cell(&mut engine);
973 let x_driver = seeded_scalar_cell(&mut engine, Bit4::X);
974 let zero_driver = seeded_scalar_cell(&mut engine, Bit4::Zero);
975 let set = DriverSet {
976 resolved,
977 drivers: vec![
978 DriverEntry {
979 source: x_driver,
980 strength: DriveStrengthPair {
981 s0: DriveStrength::Weak,
982 s1: DriveStrength::Strong,
983 },
984 forced: false,
985 },
986 DriverEntry {
987 source: zero_driver,
988 strength: strong_pair(),
989 forced: false,
990 },
991 ],
992 net_kind: NetKind::Wire,
993 };
994
995 resolve_net_step(&mut engine, &set, &mut ()).unwrap();
996 assert_eq!(
997 read_bit(&engine, resolved),
998 Bit4::Zero,
999 "an X-driving contributor's effective strength must be min(s0,s1) = Weak here, \
1000 losing outright to the Strong zero driver -- not max(s0,s1) = Strong, which \
1001 would tie and fold to X"
1002 );
1003 }
1004
1005 /// Table-driven: two equal-strength, disagreeing (`0` vs `1`) drivers
1006 /// resolve per `net_kind`'s own family — `Wire` -> `X`, `WAnd` -> `0`,
1007 /// `WOr` -> `1`. Each sub-case is a concrete regression for a specific
1008 /// net-kind branch in `combine_bit`.
1009 #[test]
1010 fn two_strong_disagreeing_drivers_resolve_per_net_kind() {
1011 for (net_kind, expected) in [
1012 (NetKind::Wire, Bit4::X),
1013 (NetKind::WAnd, Bit4::Zero),
1014 (NetKind::WOr, Bit4::One),
1015 ] {
1016 let mut engine = fresh_engine();
1017 let resolved = scalar_cell(&mut engine);
1018 let d0 = seeded_scalar_cell(&mut engine, Bit4::Zero);
1019 let d1 = seeded_scalar_cell(&mut engine, Bit4::One);
1020 let set = DriverSet {
1021 resolved,
1022 drivers: vec![
1023 DriverEntry {
1024 source: d0,
1025 strength: strong_pair(),
1026 forced: false,
1027 },
1028 DriverEntry {
1029 source: d1,
1030 strength: strong_pair(),
1031 forced: false,
1032 },
1033 ],
1034 net_kind,
1035 };
1036
1037 resolve_net_step(&mut engine, &set, &mut ()).unwrap();
1038 assert_eq!(
1039 read_bit(&engine, resolved),
1040 expected,
1041 "net_kind={net_kind:?}"
1042 );
1043 }
1044 }
1045
1046 /// state.md §4 step 1 / "undriven net" emergent property: two `Z`-valued
1047 /// drivers never participate in the fold at all — `resolved` reads `Z`
1048 /// as a genuine fold result of zero remaining (post-Z-filter)
1049 /// contributors, not a hand-coded special case.
1050 #[test]
1051 fn z_valued_drivers_never_participate_undriven_net_reads_as_z() {
1052 let mut engine = fresh_engine();
1053 let resolved = scalar_cell(&mut engine);
1054 let d0 = seeded_scalar_cell(&mut engine, Bit4::Z);
1055 let d1 = seeded_scalar_cell(&mut engine, Bit4::Z);
1056 let set = DriverSet {
1057 resolved,
1058 drivers: vec![
1059 DriverEntry {
1060 source: d0,
1061 strength: strong_pair(),
1062 forced: false,
1063 },
1064 DriverEntry {
1065 source: d1,
1066 strength: strong_pair(),
1067 forced: false,
1068 },
1069 ],
1070 net_kind: NetKind::Wire,
1071 };
1072
1073 resolve_net_step(&mut engine, &set, &mut ()).unwrap();
1074 assert_eq!(read_bit(&engine, resolved), Bit4::Z);
1075 }
1076
1077 /// Regression for the review finding that `combine_bit`'s wand/wor fold
1078 /// dropped lower-strength drivers entirely (grouping by strength *before*
1079 /// applying the AND/OR fold, instead of folding every live driver
1080 /// regardless of strength, per IEEE 1800 Table 28-8). Mirrors an iverilog
1081 /// 12 repro exactly: on a `wand`, `strong1,strong0` driving `1` combined
1082 /// with `weak1,weak0` driving `0` must resolve to `0` (iverilog:
1083 /// `w=0`) — the pre-fix algorithm grouped by strength first, discarded
1084 /// the weak `0` before any AND happened, and left only the strong `1`
1085 /// group, producing `1`. Symmetric `wor` case: `strong` driving `0`
1086 /// combined with `weak` driving `1` must resolve to `1` (iverilog:
1087 /// `w=1`), not `0`.
1088 #[test]
1089 fn heterogeneous_strength_drivers_still_all_participate_in_wand_wor_fold() {
1090 let weak_pair = DriveStrengthPair {
1091 s0: DriveStrength::Weak,
1092 s1: DriveStrength::Weak,
1093 };
1094
1095 // wand: assign (strong1,strong0) w=1; assign (weak1,weak0) w=0;
1096 {
1097 let mut engine = fresh_engine();
1098 let resolved = scalar_cell(&mut engine);
1099 let strong1 = seeded_scalar_cell(&mut engine, Bit4::One);
1100 let weak0 = seeded_scalar_cell(&mut engine, Bit4::Zero);
1101 let set = DriverSet {
1102 resolved,
1103 drivers: vec![
1104 DriverEntry {
1105 source: strong1,
1106 strength: strong_pair(),
1107 forced: false,
1108 },
1109 DriverEntry {
1110 source: weak0,
1111 strength: weak_pair,
1112 forced: false,
1113 },
1114 ],
1115 net_kind: NetKind::WAnd,
1116 };
1117
1118 resolve_net_step(&mut engine, &set, &mut ()).unwrap();
1119 assert_eq!(
1120 read_bit(&engine, resolved),
1121 Bit4::Zero,
1122 "wand must AND-fold every live driver regardless of strength \
1123 (matches iverilog 12: w=0), not drop the weak 0 via strength-grouping"
1124 );
1125 }
1126
1127 // wor: assign (strong1,strong0) w=0; assign (weak1,weak0) w=1;
1128 {
1129 let mut engine = fresh_engine();
1130 let resolved = scalar_cell(&mut engine);
1131 let strong0 = seeded_scalar_cell(&mut engine, Bit4::Zero);
1132 let weak1 = seeded_scalar_cell(&mut engine, Bit4::One);
1133 let set = DriverSet {
1134 resolved,
1135 drivers: vec![
1136 DriverEntry {
1137 source: strong0,
1138 strength: strong_pair(),
1139 forced: false,
1140 },
1141 DriverEntry {
1142 source: weak1,
1143 strength: weak_pair,
1144 forced: false,
1145 },
1146 ],
1147 net_kind: NetKind::WOr,
1148 };
1149
1150 resolve_net_step(&mut engine, &set, &mut ()).unwrap();
1151 assert_eq!(
1152 read_bit(&engine, resolved),
1153 Bit4::One,
1154 "wor must OR-fold every live driver regardless of strength \
1155 (matches iverilog 12: w=1), not drop the weak 1 via strength-grouping"
1156 );
1157 }
1158 }
1159
1160 /// ST-055: `force_driver` on a `Strong`-driving-`0` wire overrides it
1161 /// outright (force wins regardless of the ordinary driver's own
1162 /// strength); `release_driver` removes the forced entry and reverts to
1163 /// the still-present strong driver's value. Fails if `force`/`release`
1164 /// were the archived hard-error stubs instead of a real synthetic
1165 /// driver.
1166 #[test]
1167 fn force_overrides_a_strong_wire_driver_release_restores_prior_value() {
1168 let mut engine = fresh_engine();
1169 let resolved = scalar_cell(&mut engine);
1170 let strong0 = seeded_scalar_cell(&mut engine, Bit4::Zero);
1171 let mut set = DriverSet {
1172 resolved,
1173 drivers: vec![DriverEntry {
1174 source: strong0,
1175 strength: strong_pair(),
1176 forced: false,
1177 }],
1178 net_kind: NetKind::Wire,
1179 };
1180
1181 let (v, u) = Bit4::One.to_val_unk();
1182 let one = FourStateValue::Narrow((v as u128) | ((u as u128) << 64));
1183 let forced = force_driver(&mut engine, &mut set, resolved, one);
1184
1185 resolve_net_step(&mut engine, &set, &mut ()).unwrap();
1186 assert_eq!(
1187 read_bit(&engine, resolved),
1188 Bit4::One,
1189 "force must win outright over the ordinary strong driver"
1190 );
1191
1192 release_driver(&mut engine, &mut set, forced);
1193 resolve_net_step(&mut engine, &set, &mut ()).unwrap();
1194 assert_eq!(
1195 read_bit(&engine, resolved),
1196 Bit4::Zero,
1197 "release must restore the still-present strong driver's value"
1198 );
1199 }
1200
1201 /// Mutation regression (survived the mutation-testing pass):
1202 /// `release_driver` must remove exactly the one `DriverEntry` named by
1203 /// its `forced: StateCellId` parameter, never every `forced`-tagged
1204 /// entry in `set.drivers` -- a `retain` keyed on `!entry.forced` instead
1205 /// of `entry.source != forced` happens to produce the same observable
1206 /// result while at most one forced entry is ever live (this crate's own
1207 /// v1 usage pattern), but is a different, wrong function the moment two
1208 /// are: two consecutive `force_driver` calls on the same `set` (with no
1209 /// `release_driver` between them, exactly the "if more than one were
1210 /// ever present, the first ... wins" case `resolve_net_step`'s own doc
1211 /// comment already accounts for) leave two `forced` entries; releasing
1212 /// the first-pushed one must leave the second-pushed one fully live.
1213 #[test]
1214 fn release_driver_removes_only_the_named_forced_entry_not_every_forced_entry() {
1215 let mut engine = fresh_engine();
1216 let resolved = scalar_cell(&mut engine);
1217 let ordinary = seeded_scalar_cell(&mut engine, Bit4::One);
1218 let mut set = DriverSet {
1219 resolved,
1220 drivers: vec![DriverEntry {
1221 source: ordinary,
1222 strength: strong_pair(),
1223 forced: false,
1224 }],
1225 net_kind: NetKind::Wire,
1226 };
1227
1228 let (v, u) = Bit4::Zero.to_val_unk();
1229 let zero = FourStateValue::Narrow((v as u128) | ((u as u128) << 64));
1230 let forced_a = force_driver(&mut engine, &mut set, resolved, zero);
1231
1232 let (v, u) = Bit4::X.to_val_unk();
1233 let x = FourStateValue::Narrow((v as u128) | ((u as u128) << 64));
1234 let _forced_b = force_driver(&mut engine, &mut set, resolved, x);
1235
1236 release_driver(&mut engine, &mut set, forced_a);
1237 assert_eq!(
1238 set.drivers.len(),
1239 2,
1240 "releasing forced_a must remove exactly that one entry, leaving the ordinary \
1241 driver and the still-live forced_b entry"
1242 );
1243 assert_eq!(
1244 read_bit(&engine, resolved),
1245 Bit4::X,
1246 "forced_b, pushed after forced_a and never released, must still be the entry \
1247 resolve_net_step's forced short-circuit picks -- not silently removed alongside \
1248 forced_a"
1249 );
1250 }
1251
1252 /// ST-146-adjacent: closes the `combine_bit`/`resolve_net_step`
1253 /// loud-error requirement in two sub-cases. (1) `combine_bit(&[],
1254 /// NetKind::UserDefined)` rejects even with zero contributions -- the
1255 /// rejection isn't conditioned on there being any drivers to fold. (2) A
1256 /// `DriverSet` with `net_kind: NetKind::UserDefined` and one real,
1257 /// otherwise-trivially-resolvable `DriverEntry` is rejected the same
1258 /// way, and `set.resolved`'s cell is left at its pre-call value -- no
1259 /// partial/guessed commit on the rejected path. Fails if the
1260 /// `UserDefined` arm panicked, silently fell through to another net
1261 /// kind's fold logic, or resolved to an arbitrary winner instead of
1262 /// returning the typed `Err`.
1263 #[test]
1264 fn combine_bit_and_resolve_net_step_reject_userdefined_net_kind_with_a_typed_error() {
1265 assert_eq!(
1266 combine_bit(&[], NetKind::UserDefined),
1267 Err(DriverError::UnresolvableUserDefinedNetType)
1268 );
1269
1270 let mut engine = fresh_engine();
1271 let resolved = scalar_cell(&mut engine);
1272 let d0 = seeded_scalar_cell(&mut engine, Bit4::One);
1273 let set = DriverSet {
1274 resolved,
1275 drivers: vec![DriverEntry {
1276 source: d0,
1277 strength: strong_pair(),
1278 forced: false,
1279 }],
1280 net_kind: NetKind::UserDefined,
1281 };
1282
1283 let before = read_bit(&engine, resolved);
1284 let result = resolve_net_step(&mut engine, &set, &mut ());
1285 assert_eq!(
1286 result.err(),
1287 Some(DriverError::UnresolvableUserDefinedNetType)
1288 );
1289 assert_eq!(
1290 read_bit(&engine, resolved),
1291 before,
1292 "no partial/guessed commit on the rejected path"
1293 );
1294 }
1295
1296 /// Regression for review finding 1 (blocking): `combine_bit` previously
1297 /// resolved `NetKind::Tri0`/`Tri1` identically to `Wire`/`Tri`, entirely
1298 /// omitting the LRM-mandated implicit resistive pull (tri0 pulls weakly
1299 /// to 0, tri1 to 1, whenever nothing stronger drives the net). Verified
1300 /// empirically against iverilog 12: an undriven `tri0 w;` reads `0`, an
1301 /// undriven `tri1 w;` reads `1` -- not `Z`, unlike every other net kind
1302 /// with zero live drivers.
1303 #[test]
1304 fn undriven_tri0_reads_zero_and_undriven_tri1_reads_one_via_implicit_pull() {
1305 for (net_kind, expected) in [(NetKind::Tri0, Bit4::Zero), (NetKind::Tri1, Bit4::One)] {
1306 let mut engine = fresh_engine();
1307 let resolved = scalar_cell(&mut engine);
1308 let set = DriverSet {
1309 resolved,
1310 drivers: vec![],
1311 net_kind,
1312 };
1313
1314 resolve_net_step(&mut engine, &set, &mut ()).unwrap();
1315 assert_eq!(
1316 read_bit(&engine, resolved),
1317 expected,
1318 "net_kind={net_kind:?}: undriven tri0/tri1 must read its implicit pull, not Z"
1319 );
1320 }
1321 }
1322
1323 /// Regression for review finding 1 (blocking): the implicit pull is at
1324 /// `Pull` strength, which must beat an explicit `Weak` driver rather than
1325 /// losing to it outright. Verified empirically against iverilog 12:
1326 /// `tri0 w; assign (weak0,weak1) w=1;` still reads `0`.
1327 #[test]
1328 fn tri0_tri1_implicit_pull_beats_an_explicit_weak_driver() {
1329 let weak_pair = DriveStrengthPair {
1330 s0: DriveStrength::Weak,
1331 s1: DriveStrength::Weak,
1332 };
1333 for (net_kind, weak_drives, expected) in [
1334 (NetKind::Tri0, Bit4::One, Bit4::Zero),
1335 (NetKind::Tri1, Bit4::Zero, Bit4::One),
1336 ] {
1337 let mut engine = fresh_engine();
1338 let resolved = scalar_cell(&mut engine);
1339 let weak_driver = seeded_scalar_cell(&mut engine, weak_drives);
1340 let set = DriverSet {
1341 resolved,
1342 drivers: vec![DriverEntry {
1343 source: weak_driver,
1344 strength: weak_pair,
1345 forced: false,
1346 }],
1347 net_kind,
1348 };
1349
1350 resolve_net_step(&mut engine, &set, &mut ()).unwrap();
1351 assert_eq!(
1352 read_bit(&engine, resolved),
1353 expected,
1354 "net_kind={net_kind:?}: the implicit Pull-strength pull must beat an \
1355 explicit Weak driver, matching iverilog 12"
1356 );
1357 }
1358 }
1359
1360 /// Regression for review finding 2 (blocking): `force_driver` used to
1361 /// panic for any `SlotClass::Wide`/`Memory` net (`bit_width > 64`) once
1362 /// `Arena::seal_wide_plane` had fired -- its Wide branch called
1363 /// `Arena::alloc_wide_words` unconditionally for its own contribution
1364 /// cell, which refuses any further growth once sealed (Task 9 r3's own
1365 /// review fix). `seal_wide_plane` is called for real the instant any wide
1366 /// signal is read through the compiled-kernel FFI accessor
1367 /// (`accessor_impl.rs::sv_get_wide`) -- the very ABI this crate exists to
1368 /// serve -- so any realistic simulation with a >64-bit signal would hit
1369 /// this before any `force` statement ever ran. This proves `force`/
1370 /// `release` keep working after the seal by growing the separate,
1371 /// never-sealed `SlotClass::ForceWide` plane instead.
1372 #[test]
1373 fn force_and_release_still_work_on_a_wide_net_after_the_wide_plane_is_sealed() {
1374 let mut engine = fresh_engine();
1375 let resolved = wide_cell(&mut engine);
1376 let strong_driver = seeded_wide_cell(&mut engine, 0xAAAA_AAAA);
1377 let mut set = DriverSet {
1378 resolved,
1379 drivers: vec![DriverEntry {
1380 source: strong_driver,
1381 strength: strong_pair(),
1382 forced: false,
1383 }],
1384 net_kind: NetKind::Wire,
1385 };
1386 resolve_net_step(&mut engine, &set, &mut ()).unwrap();
1387 assert_eq!(read_wide_aval0(&engine, resolved), 0xAAAA_AAAA);
1388
1389 // Simulate a prior `sv_get_wide` call elsewhere in the same
1390 // simulation, exactly as the review finding's own repro did.
1391 engine.arena.seal_wide_plane();
1392 assert!(engine.arena.is_wide_plane_sealed());
1393
1394 let forced_value = FourStateValue::Wide {
1395 aval: vec![0xFFFF_FFFF, 0],
1396 bval: vec![0, 0],
1397 };
1398 // Must not panic even though the ordinary wide plane is sealed.
1399 let forced = force_driver(&mut engine, &mut set, resolved, forced_value);
1400
1401 resolve_net_step(&mut engine, &set, &mut ()).unwrap();
1402 assert_eq!(
1403 read_wide_aval0(&engine, resolved),
1404 0xFFFF_FFFF,
1405 "force must win outright over the ordinary strong driver on a Wide net"
1406 );
1407
1408 release_driver(&mut engine, &mut set, forced);
1409 resolve_net_step(&mut engine, &set, &mut ()).unwrap();
1410 assert_eq!(
1411 read_wide_aval0(&engine, resolved),
1412 0xAAAA_AAAA,
1413 "release must restore the still-present strong driver's value"
1414 );
1415 }
1416
1417 /// Regression for review finding 3 (blocking): r1's fix made
1418 /// `wand`/`triand`/`wor`/`trior` resolution ignore drive strength
1419 /// entirely (matching real LRM/iverilog behavior for the ordinary
1420 /// multi-driver case), which silently broke `force`'s override guarantee
1421 /// on those net kinds -- a merely `Supply`-strength forced entry became
1422 /// just one more live contributor in the wired-AND/OR fold and could be
1423 /// outvoted by an ordinary driver of any strength. `force` must still win
1424 /// outright, independent of net kind.
1425 #[test]
1426 fn force_overrides_an_ordinary_driver_on_a_wand_net() {
1427 let mut engine = fresh_engine();
1428 let resolved = scalar_cell(&mut engine);
1429 let ordinary = seeded_scalar_cell(&mut engine, Bit4::Zero);
1430 let mut set = DriverSet {
1431 resolved,
1432 drivers: vec![DriverEntry {
1433 source: ordinary,
1434 strength: strong_pair(),
1435 forced: false,
1436 }],
1437 net_kind: NetKind::WAnd,
1438 };
1439
1440 let (v, u) = Bit4::One.to_val_unk();
1441 let one = FourStateValue::Narrow((v as u128) | ((u as u128) << 64));
1442 let forced = force_driver(&mut engine, &mut set, resolved, one);
1443
1444 resolve_net_step(&mut engine, &set, &mut ()).unwrap();
1445 assert_eq!(
1446 read_bit(&engine, resolved),
1447 Bit4::One,
1448 "force must win outright on a wand net even though the wired-AND fold of \
1449 the ordinary driver's 0 and the forced 1 would otherwise resolve to 0"
1450 );
1451
1452 release_driver(&mut engine, &mut set, forced);
1453 resolve_net_step(&mut engine, &set, &mut ()).unwrap();
1454 assert_eq!(
1455 read_bit(&engine, resolved),
1456 Bit4::Zero,
1457 "release must restore the still-present ordinary driver's value"
1458 );
1459 }
1460}