Skip to main content

vj_sim/state/
value.rs

1//! Task 1 — value-type reuse: STATE never defines a parallel four-state numeric
2//! type (ledger ST-010, ST-011, ST-012, ST-099, ST-148). This module is the one
3//! place `vj_abi::Bit4` (the accessor-ABI/canon 4-state encoding, ST-148's
4//! single documented polarity table) meets `sv_lang`'s own value machinery
5//! (`Bit`/`OwnedSVInt`/`ConstantValue`, ST-010/011/012) — every conversion below
6//! goes through `Bit4`'s `to_val_unk`/`to_aval_bval` tables, never a hand-rolled
7//! re-derivation of 0/1/X/Z polarity.
8
9use sv_lang::{Bit, Compilation, ConstantValue, Design, Digit, LiteralBase, OwnedSVInt, Session};
10use vj_abi::Bit4;
11
12use super::{FourStateValue, four_state_bit_at};
13
14/// The one place `sv_lang::Bit`'s discriminant order is asserted to equal
15/// `Bit4`'s (per `bit4.rs`'s own doc comment) — an explicit, exhaustive match
16/// rather than a transmute, so a future `sv_lang::Bit` variant addition fails to
17/// compile here instead of silently mis-converting.
18fn bit_to_bit4(b: Bit) -> Bit4 {
19    match b {
20        Bit::Zero => Bit4::Zero,
21        Bit::One => Bit4::One,
22        Bit::X => Bit4::X,
23        Bit::Z => Bit4::Z,
24    }
25}
26
27/// Packs a slice of `sv_lang::Bit` into STATE's cross-subsystem `FourStateValue`
28/// (ST-010): `<= 64` bits pack a `Narrow(u128)` with `val` in the low 64 bits and
29/// `unk`/xzmask in the high 64 bits (ARCHITECTURE §6.2 / ST-008's FIXED accessor
30/// layout, independent of width), built bit-by-bit through
31/// [`Bit4::to_val_unk`]; `> 64` bits pack `Wide{aval,bval}` word planes through
32/// [`Bit4::to_aval_bval`] (ST-148's wide-plane polarity table) — `bits[0]` is bit
33/// 0 of both representations.
34pub fn bits_to_four_state(bits: &[Bit]) -> FourStateValue {
35    if bits.len() <= 64 {
36        let mut val: u64 = 0;
37        let mut unk: u64 = 0;
38        for (i, &b) in bits.iter().enumerate() {
39            let (v, u) = bit_to_bit4(b).to_val_unk();
40            if v {
41                val |= 1u64 << i;
42            }
43            if u {
44                unk |= 1u64 << i;
45            }
46        }
47        FourStateValue::Narrow((val as u128) | ((unk as u128) << 64))
48    } else {
49        let (aval, bval) = bits_to_aval_bval(bits);
50        FourStateValue::Wide { aval, bval }
51    }
52}
53
54/// `bits` (LSB first) in codegen's packed form -- `(unk << W) | val` for
55/// `W = bits.len()` -- as `ceil(2W / 64)` little-endian words (at least
56/// one): a collection element's stored form (`crate::collections`).
57pub fn packed_words(bits: &[Bit]) -> Vec<u64> {
58    let w = bits.len();
59    let mut out = vec![0u64; (2 * w).div_ceil(64).max(1)];
60    for (i, &b) in bits.iter().enumerate() {
61        let (v, u) = bit_to_bit4(b).to_val_unk();
62        if v {
63            out[i / 64] |= 1u64 << (i % 64);
64        }
65        if u {
66            out[(w + i) / 64] |= 1u64 << ((w + i) % 64);
67        }
68    }
69    out
70}
71
72/// `bits` (LSB first) as IEEE 1800 aval/bval word planes, at least one word.
73pub fn bits_to_aval_bval(bits: &[Bit]) -> (Vec<u64>, Vec<u64>) {
74    let words = bits.len().div_ceil(64).max(1);
75    let mut aval = vec![0u64; words];
76    let mut bval = vec![0u64; words];
77    for (i, &b) in bits.iter().enumerate() {
78        let (a, bv) = bit_to_bit4(b).to_aval_bval();
79        let word = i / 64;
80        let bit = i % 64;
81        if a {
82            aval[word] |= 1u64 << bit;
83        }
84        if bv {
85            bval[word] |= 1u64 << bit;
86        }
87    }
88    (aval, bval)
89}
90
91/// Builds a single-bit `OwnedSVInt` of a concrete known value (0 or 1) — the
92/// one unambiguous case of `OwnedSVInt::from_digits` (a single `Binary` digit
93/// for a single-bit width has no truncation/extension ambiguity to worry
94/// about, unlike a multi-digit literal).
95fn single_bit_known(value: bool) -> OwnedSVInt {
96    OwnedSVInt::from_digits(
97        1,
98        LiteralBase::Binary,
99        false,
100        false,
101        &[Digit::Value(value as u8)],
102    )
103    .expect("a single known binary digit at width 1 always constructs")
104}
105
106/// Builds a `sv_lang::OwnedSVInt` of the given `width`/`signed`ness from a
107/// `FourStateValue`, reusing `sv_lang`'s own owned-integer type rather than a
108/// locally-defined equivalent (ST-011): starts from
109/// `OwnedSVInt::create_fill_x` (every bit X, matching this function's honest
110/// default for a bit this loop hasn't visited yet) and threads each bit's
111/// known (0/1) or Z state in via `set_range`/`create_fill_z` — an X bit is
112/// left untouched (already correct from the fill), a 0/1/Z bit overwrites its
113/// single-bit range.
114pub fn four_state_to_owned_svint(v: &FourStateValue, width: u32, signed: bool) -> OwnedSVInt {
115    let mut out =
116        OwnedSVInt::create_fill_x(width, signed).expect("caller guarantees a nonzero width");
117    for i in 0..width {
118        match four_state_bit_at(v, i) {
119            Bit4::X => {}
120            Bit4::Zero => {
121                let zero = single_bit_known(false);
122                out.set_range(i as i32, i as i32, &zero)
123                    .expect("single-bit range within [0,width) always matches a 1-bit value");
124            }
125            Bit4::One => {
126                let one = single_bit_known(true);
127                out.set_range(i as i32, i as i32, &one)
128                    .expect("single-bit range within [0,width) always matches a 1-bit value");
129            }
130            Bit4::Z => {
131                let z = OwnedSVInt::create_fill_z(1, false)
132                    .expect("a single-bit fill-z always constructs");
133                out.set_range(i as i32, i as i32, &z)
134                    .expect("single-bit range within [0,width) always matches a 1-bit value");
135            }
136        }
137    }
138    out
139}
140
141/// Extracts a `FourStateValue` from a `sv_lang::ConstantValue`, reusing
142/// `sv_lang`'s own constant-folding machinery rather than a parallel evaluator
143/// (ST-012): `None` for any non-integer constant (real/string/null/etc — this
144/// function only speaks the 4-state integer representation `FourStateValue`
145/// exists for).
146pub fn four_state_of_constant(cv: &ConstantValue) -> Option<FourStateValue> {
147    Some(bits_to_four_state(&svint_bits(cv.as_integer()?)?))
148}
149
150/// The bits (LSB first) of the constant expression `e`, at its own type's
151/// width. `None` when `e` is not an integral constant.
152pub fn constant_expr_bits(e: &sv_lang::Expression<'_>) -> Option<Vec<Bit>> {
153    Some(svint_bits(e.constant_value()?.as_integer()?)?.into_owned())
154}
155
156/// `v`'s bits, LSB first, at its full width (sv-lang 0.1.17 expands every
157/// width; before it, values past 1024 bits came back empty). `None` if the
158/// slice ever disagrees with `bit_width()` -- never a fabricated value.
159pub fn svint_bits(v: &sv_lang::SVInt) -> Option<std::borrow::Cow<'_, [Bit]>> {
160    (v.bits().len() == v.bit_width() as usize).then(|| std::borrow::Cow::Borrowed(v.bits()))
161}
162
163/// The inverse of [`four_state_to_owned_svint`] at read-time (Task 16 —
164/// `state::rollback::StateEngine::reset_to_initial`'s own need to turn a
165/// `SlotDescriptor.initial: Option<OwnedSVInt>` (Task 6) back into a
166/// `FourStateValue` it can re-blit into the arena): reuses the exact same
167/// `SVInt::bits()` + [`bits_to_four_state`] path [`four_state_of_constant`]
168/// already uses for a `ConstantValue` (ST-010/011/012's "reuse `sv_lang`'s
169/// own value machinery, never a parallel evaluator" discipline, applied to
170/// the one other place this crate holds a `sv_lang`-owned integer value).
171/// `None` exactly when [`svint_bits`] is.
172pub(crate) fn owned_svint_to_four_state(v: &OwnedSVInt) -> Option<FourStateValue> {
173    Some(bits_to_four_state(&svint_bits(&v.snapshot())?))
174}
175
176/// This track's own copy of `vj-codegen/src/frontend.rs::compile_source`
177/// (kept local rather than depending on `vj-codegen` for a 4-line helper);
178/// reused by every later task's `sv_lang` fixture tests in this crate.
179pub fn compile_source(src: &str) -> Result<Design, sv_lang::Error> {
180    let session = Session::new();
181    let mut comp = Compilation::new(&session)?;
182    comp.add_source(src)?;
183    comp.compile()
184}
185
186#[cfg(test)]
187mod tests {
188    use super::*;
189
190    /// ST-010/ST-148: proves `bits_to_four_state`'s narrow packing uses
191    /// `Bit4::to_val_unk`'s table exactly, per-bit — fails on codegen's
192    /// width-tight `(unk << width)` packing (which would only coincide at
193    /// width 64) or a wrong per-bit order.
194    #[test]
195    fn bits_to_four_state_narrow_matches_bit4_table_for_zero_one_x_z() {
196        let bits = [Bit::Zero, Bit::One, Bit::X, Bit::Z];
197        let fv = bits_to_four_state(&bits);
198        let expected: u128 = 0xA | (0xCu128 << 64);
199        match fv {
200            FourStateValue::Narrow(w) => assert_eq!(w, expected),
201            FourStateValue::Wide { .. } => panic!("expected Narrow for 4 bits"),
202        }
203    }
204
205    /// ST-148: proves the wide-plane packing goes through
206    /// `Bit4::to_aval_bval`'s IEEE-1800 table (`X` => `aval=1,bval=1`), not a
207    /// hand-rolled polarity (e.g. `aval=0` for X).
208    #[test]
209    fn bits_to_four_state_wide_uses_bit4_aval_bval_table() {
210        let bits = [Bit::X; 65];
211        let fv = bits_to_four_state(&bits);
212        match fv {
213            FourStateValue::Wide { aval, bval } => {
214                assert_eq!(aval.len(), 2);
215                assert_eq!(bval.len(), 2);
216                assert_eq!(aval[0], u64::MAX);
217                assert_eq!(bval[0], u64::MAX);
218            }
219            FourStateValue::Narrow(_) => panic!("expected Wide for 65 bits"),
220        }
221    }
222
223    /// ST-011: proves `four_state_to_owned_svint` actually threads both the
224    /// known-value mask and the unknown mask through `set_range`/
225    /// `create_fill_x`, never emitting an all-zero value regardless of input.
226    #[test]
227    fn four_state_to_owned_svint_round_trips_known_and_unknown_bits() {
228        let all_known = FourStateValue::Narrow(0b1010u128);
229        let v = four_state_to_owned_svint(&all_known, 4, false);
230        assert_eq!(v.snapshot().as_u64(), Some(0b1010));
231        assert!(!v.snapshot().has_unknown());
232
233        // bit 1 = X: val bit 1 cleared, unk bit 1 set (unk lives in the high
234        // 64 bits of the Narrow word).
235        let with_x = FourStateValue::Narrow(0b1000u128 | (0b0010u128 << 64));
236        let v2 = four_state_to_owned_svint(&with_x, 4, false);
237        assert!(v2.snapshot().has_unknown());
238        assert_eq!(v2.snapshot().bit(1), Some(Bit::X));
239    }
240
241    /// ST-012: the one test proving ST-010/012's "reuse sv-lang's own constant
242    /// machinery" end-to-end — compiles real source, folds a real constant via
243    /// `sv_lang`, and round-trips it through `four_state_of_constant`. Fails if
244    /// a parallel evaluator were used instead of slang's own fold and silently
245    /// drifted.
246    #[test]
247    fn four_state_of_constant_extracts_bits_from_a_real_compiled_constant() {
248        let design = compile_source("module m; localparam int p = 8'hAA; endmodule\n")
249            .expect("valid SV source compiles");
250        let body = design
251            .top_instances()
252            .next()
253            .expect("one top instance")
254            .instance_body()
255            .expect("instance has a body");
256        let p = body.find("p").expect("p is a member of m");
257        let cv = p
258            .initializer()
259            .expect("p has an initializer")
260            .constant_value()
261            .expect("p's initializer folds to a constant");
262        let fv = four_state_of_constant(&cv).expect("an integer constant yields Some");
263        match fv {
264            FourStateValue::Narrow(word) => {
265                let low_byte = (word & 0xFF) as u8;
266                assert_eq!(low_byte, 0xAA);
267            }
268            FourStateValue::Wide { .. } => panic!("expected Narrow for a 32-bit int constant"),
269        }
270    }
271
272    /// Task 16: proves `owned_svint_to_four_state` actually reads back both
273    /// the known-value bits and the unknown/X bits from a real
274    /// `OwnedSVInt` (built here via `four_state_to_owned_svint`, the same
275    /// round trip `SlotDescriptor.initial` goes through in the real layout
276    /// builder) — fails if the function only ever returned `Some(Narrow(0))`
277    /// regardless of input, or dropped the unknown-bit half.
278    #[test]
279    fn owned_svint_to_four_state_round_trips_known_and_unknown_bits() {
280        let known = FourStateValue::Narrow(0xAAu128);
281        let v = four_state_to_owned_svint(&known, 8, false);
282        let back = owned_svint_to_four_state(&v).expect("a fully-known 8-bit value round-trips");
283        match back {
284            FourStateValue::Narrow(w) => {
285                assert_eq!(w as u64 & 0xFF, 0xAA);
286                assert_eq!((w >> 64) as u64 & 0xFF, 0, "no unknown bits expected");
287            }
288            FourStateValue::Wide { .. } => panic!("expected Narrow for an 8-bit value"),
289        }
290
291        // bit 0 = X: val bit 0 must come back cleared, unk bit 0 must come
292        // back set.
293        let with_x = FourStateValue::Narrow(0b1000u128 | (0b0001u128 << 64));
294        let v2 = four_state_to_owned_svint(&with_x, 4, false);
295        let back2 =
296            owned_svint_to_four_state(&v2).expect("a partially-unknown 4-bit value round-trips");
297        match back2 {
298            FourStateValue::Narrow(w) => {
299                assert_eq!(
300                    (w >> 64) as u64 & 0b1111,
301                    0b0001,
302                    "bit 0's unknown-ness must survive the round trip"
303                );
304                assert_eq!(
305                    w as u64 & 0b1110,
306                    0b1000,
307                    "the known bits must survive the round trip"
308                );
309            }
310            FourStateValue::Wide { .. } => panic!("expected Narrow for a 4-bit value"),
311        }
312    }
313
314    /// A constant wider than 1024 bits (sv-lang's old per-bit expansion cap)
315    /// reads back exactly, uniform or not -- before sv-lang 0.1.17 `bits()`
316    /// was empty there and only uniform fills or a structural rebuild worked.
317    #[test]
318    fn four_state_of_constant_reads_a_constant_wider_than_1024_bits() {
319        let design = compile_source(
320            "module m; localparam bit [1099:0] p = {1100{1'b1}};\n\
321             localparam bit [1099:0] q = {550{2'b10}}; endmodule\n",
322        )
323        .expect("valid SV source compiles");
324        let body = design
325            .top_instances()
326            .next()
327            .expect("one top instance")
328            .instance_body()
329            .expect("instance has a body");
330        let init = |name: &str| {
331            body.find(name)
332                .expect("a member of m")
333                .initializer()
334                .expect("an initializer")
335        };
336        let (p, q) = (init("p"), init("q"));
337        let pv = p.constant_value().expect("p folds");
338        assert_eq!(
339            format!("{:?}", four_state_of_constant(&pv)),
340            format!("{:?}", Some(bits_to_four_state(&[Bit::One; 1100]))),
341        );
342        let alternating: Vec<Bit> = (0..1100)
343            .map(|i| if i % 2 == 1 { Bit::One } else { Bit::Zero })
344            .collect();
345        assert_eq!(constant_expr_bits(&q), Some(alternating.clone()));
346        assert_eq!(
347            format!(
348                "{:?}",
349                four_state_of_constant(&q.constant_value().expect("q folds"))
350            ),
351            format!("{:?}", Some(bits_to_four_state(&alternating))),
352        );
353    }
354}