Skip to main content

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}