vj-sim 0.3.0

Netlist, scheduler and process model of the vj SystemVerilog simulator.
Documentation
//! Task 1 — value-type reuse: STATE never defines a parallel four-state numeric
//! type (ledger ST-010, ST-011, ST-012, ST-099, ST-148). This module is the one
//! place `vj_abi::Bit4` (the accessor-ABI/canon 4-state encoding, ST-148's
//! single documented polarity table) meets `sv_lang`'s own value machinery
//! (`Bit`/`OwnedSVInt`/`ConstantValue`, ST-010/011/012) — every conversion below
//! goes through `Bit4`'s `to_val_unk`/`to_aval_bval` tables, never a hand-rolled
//! re-derivation of 0/1/X/Z polarity.

use sv_lang::{Bit, Compilation, ConstantValue, Design, Digit, LiteralBase, OwnedSVInt, Session};
use vj_abi::Bit4;

use super::{FourStateValue, four_state_bit_at};

/// The one place `sv_lang::Bit`'s discriminant order is asserted to equal
/// `Bit4`'s (per `bit4.rs`'s own doc comment) — an explicit, exhaustive match
/// rather than a transmute, so a future `sv_lang::Bit` variant addition fails to
/// compile here instead of silently mis-converting.
fn bit_to_bit4(b: Bit) -> Bit4 {
    match b {
        Bit::Zero => Bit4::Zero,
        Bit::One => Bit4::One,
        Bit::X => Bit4::X,
        Bit::Z => Bit4::Z,
    }
}

/// Packs a slice of `sv_lang::Bit` into STATE's cross-subsystem `FourStateValue`
/// (ST-010): `<= 64` bits pack a `Narrow(u128)` with `val` in the low 64 bits and
/// `unk`/xzmask in the high 64 bits (ARCHITECTURE §6.2 / ST-008's FIXED accessor
/// layout, independent of width), built bit-by-bit through
/// [`Bit4::to_val_unk`]; `> 64` bits pack `Wide{aval,bval}` word planes through
/// [`Bit4::to_aval_bval`] (ST-148's wide-plane polarity table) — `bits[0]` is bit
/// 0 of both representations.
pub fn bits_to_four_state(bits: &[Bit]) -> FourStateValue {
    if bits.len() <= 64 {
        let mut val: u64 = 0;
        let mut unk: u64 = 0;
        for (i, &b) in bits.iter().enumerate() {
            let (v, u) = bit_to_bit4(b).to_val_unk();
            if v {
                val |= 1u64 << i;
            }
            if u {
                unk |= 1u64 << i;
            }
        }
        FourStateValue::Narrow((val as u128) | ((unk as u128) << 64))
    } else {
        let (aval, bval) = bits_to_aval_bval(bits);
        FourStateValue::Wide { aval, bval }
    }
}

/// `bits` (LSB first) in codegen's packed form -- `(unk << W) | val` for
/// `W = bits.len()` -- as `ceil(2W / 64)` little-endian words (at least
/// one): a collection element's stored form (`crate::collections`).
pub fn packed_words(bits: &[Bit]) -> Vec<u64> {
    let w = bits.len();
    let mut out = vec![0u64; (2 * w).div_ceil(64).max(1)];
    for (i, &b) in bits.iter().enumerate() {
        let (v, u) = bit_to_bit4(b).to_val_unk();
        if v {
            out[i / 64] |= 1u64 << (i % 64);
        }
        if u {
            out[(w + i) / 64] |= 1u64 << ((w + i) % 64);
        }
    }
    out
}

/// `bits` (LSB first) as IEEE 1800 aval/bval word planes, at least one word.
pub fn bits_to_aval_bval(bits: &[Bit]) -> (Vec<u64>, Vec<u64>) {
    let words = bits.len().div_ceil(64).max(1);
    let mut aval = vec![0u64; words];
    let mut bval = vec![0u64; words];
    for (i, &b) in bits.iter().enumerate() {
        let (a, bv) = bit_to_bit4(b).to_aval_bval();
        let word = i / 64;
        let bit = i % 64;
        if a {
            aval[word] |= 1u64 << bit;
        }
        if bv {
            bval[word] |= 1u64 << bit;
        }
    }
    (aval, bval)
}

/// Builds a single-bit `OwnedSVInt` of a concrete known value (0 or 1) — the
/// one unambiguous case of `OwnedSVInt::from_digits` (a single `Binary` digit
/// for a single-bit width has no truncation/extension ambiguity to worry
/// about, unlike a multi-digit literal).
fn single_bit_known(value: bool) -> OwnedSVInt {
    OwnedSVInt::from_digits(
        1,
        LiteralBase::Binary,
        false,
        false,
        &[Digit::Value(value as u8)],
    )
    .expect("a single known binary digit at width 1 always constructs")
}

/// Builds a `sv_lang::OwnedSVInt` of the given `width`/`signed`ness from a
/// `FourStateValue`, reusing `sv_lang`'s own owned-integer type rather than a
/// locally-defined equivalent (ST-011): starts from
/// `OwnedSVInt::create_fill_x` (every bit X, matching this function's honest
/// default for a bit this loop hasn't visited yet) and threads each bit's
/// known (0/1) or Z state in via `set_range`/`create_fill_z` — an X bit is
/// left untouched (already correct from the fill), a 0/1/Z bit overwrites its
/// single-bit range.
pub fn four_state_to_owned_svint(v: &FourStateValue, width: u32, signed: bool) -> OwnedSVInt {
    let mut out =
        OwnedSVInt::create_fill_x(width, signed).expect("caller guarantees a nonzero width");
    for i in 0..width {
        match four_state_bit_at(v, i) {
            Bit4::X => {}
            Bit4::Zero => {
                let zero = single_bit_known(false);
                out.set_range(i as i32, i as i32, &zero)
                    .expect("single-bit range within [0,width) always matches a 1-bit value");
            }
            Bit4::One => {
                let one = single_bit_known(true);
                out.set_range(i as i32, i as i32, &one)
                    .expect("single-bit range within [0,width) always matches a 1-bit value");
            }
            Bit4::Z => {
                let z = OwnedSVInt::create_fill_z(1, false)
                    .expect("a single-bit fill-z always constructs");
                out.set_range(i as i32, i as i32, &z)
                    .expect("single-bit range within [0,width) always matches a 1-bit value");
            }
        }
    }
    out
}

/// Extracts a `FourStateValue` from a `sv_lang::ConstantValue`, reusing
/// `sv_lang`'s own constant-folding machinery rather than a parallel evaluator
/// (ST-012): `None` for any non-integer constant (real/string/null/etc — this
/// function only speaks the 4-state integer representation `FourStateValue`
/// exists for).
pub fn four_state_of_constant(cv: &ConstantValue) -> Option<FourStateValue> {
    Some(bits_to_four_state(&svint_bits(cv.as_integer()?)?))
}

/// The bits (LSB first) of the constant expression `e`, at its own type's
/// width. `None` when `e` is not an integral constant.
pub fn constant_expr_bits(e: &sv_lang::Expression<'_>) -> Option<Vec<Bit>> {
    Some(svint_bits(e.constant_value()?.as_integer()?)?.into_owned())
}

/// `v`'s bits, LSB first, at its full width (sv-lang 0.1.17 expands every
/// width; before it, values past 1024 bits came back empty). `None` if the
/// slice ever disagrees with `bit_width()` -- never a fabricated value.
pub fn svint_bits(v: &sv_lang::SVInt) -> Option<std::borrow::Cow<'_, [Bit]>> {
    (v.bits().len() == v.bit_width() as usize).then(|| std::borrow::Cow::Borrowed(v.bits()))
}

/// The inverse of [`four_state_to_owned_svint`] at read-time (Task 16 —
/// `state::rollback::StateEngine::reset_to_initial`'s own need to turn a
/// `SlotDescriptor.initial: Option<OwnedSVInt>` (Task 6) back into a
/// `FourStateValue` it can re-blit into the arena): reuses the exact same
/// `SVInt::bits()` + [`bits_to_four_state`] path [`four_state_of_constant`]
/// already uses for a `ConstantValue` (ST-010/011/012's "reuse `sv_lang`'s
/// own value machinery, never a parallel evaluator" discipline, applied to
/// the one other place this crate holds a `sv_lang`-owned integer value).
/// `None` exactly when [`svint_bits`] is.
pub(crate) fn owned_svint_to_four_state(v: &OwnedSVInt) -> Option<FourStateValue> {
    Some(bits_to_four_state(&svint_bits(&v.snapshot())?))
}

/// This track's own copy of `vj-codegen/src/frontend.rs::compile_source`
/// (kept local rather than depending on `vj-codegen` for a 4-line helper);
/// reused by every later task's `sv_lang` fixture tests in this crate.
pub fn compile_source(src: &str) -> Result<Design, sv_lang::Error> {
    let session = Session::new();
    let mut comp = Compilation::new(&session)?;
    comp.add_source(src)?;
    comp.compile()
}

#[cfg(test)]
mod tests {
    use super::*;

    /// ST-010/ST-148: proves `bits_to_four_state`'s narrow packing uses
    /// `Bit4::to_val_unk`'s table exactly, per-bit — fails on codegen's
    /// width-tight `(unk << width)` packing (which would only coincide at
    /// width 64) or a wrong per-bit order.
    #[test]
    fn bits_to_four_state_narrow_matches_bit4_table_for_zero_one_x_z() {
        let bits = [Bit::Zero, Bit::One, Bit::X, Bit::Z];
        let fv = bits_to_four_state(&bits);
        let expected: u128 = 0xA | (0xCu128 << 64);
        match fv {
            FourStateValue::Narrow(w) => assert_eq!(w, expected),
            FourStateValue::Wide { .. } => panic!("expected Narrow for 4 bits"),
        }
    }

    /// ST-148: proves the wide-plane packing goes through
    /// `Bit4::to_aval_bval`'s IEEE-1800 table (`X` => `aval=1,bval=1`), not a
    /// hand-rolled polarity (e.g. `aval=0` for X).
    #[test]
    fn bits_to_four_state_wide_uses_bit4_aval_bval_table() {
        let bits = [Bit::X; 65];
        let fv = bits_to_four_state(&bits);
        match fv {
            FourStateValue::Wide { aval, bval } => {
                assert_eq!(aval.len(), 2);
                assert_eq!(bval.len(), 2);
                assert_eq!(aval[0], u64::MAX);
                assert_eq!(bval[0], u64::MAX);
            }
            FourStateValue::Narrow(_) => panic!("expected Wide for 65 bits"),
        }
    }

    /// ST-011: proves `four_state_to_owned_svint` actually threads both the
    /// known-value mask and the unknown mask through `set_range`/
    /// `create_fill_x`, never emitting an all-zero value regardless of input.
    #[test]
    fn four_state_to_owned_svint_round_trips_known_and_unknown_bits() {
        let all_known = FourStateValue::Narrow(0b1010u128);
        let v = four_state_to_owned_svint(&all_known, 4, false);
        assert_eq!(v.snapshot().as_u64(), Some(0b1010));
        assert!(!v.snapshot().has_unknown());

        // bit 1 = X: val bit 1 cleared, unk bit 1 set (unk lives in the high
        // 64 bits of the Narrow word).
        let with_x = FourStateValue::Narrow(0b1000u128 | (0b0010u128 << 64));
        let v2 = four_state_to_owned_svint(&with_x, 4, false);
        assert!(v2.snapshot().has_unknown());
        assert_eq!(v2.snapshot().bit(1), Some(Bit::X));
    }

    /// ST-012: the one test proving ST-010/012's "reuse sv-lang's own constant
    /// machinery" end-to-end — compiles real source, folds a real constant via
    /// `sv_lang`, and round-trips it through `four_state_of_constant`. Fails if
    /// a parallel evaluator were used instead of slang's own fold and silently
    /// drifted.
    #[test]
    fn four_state_of_constant_extracts_bits_from_a_real_compiled_constant() {
        let design = compile_source("module m; localparam int p = 8'hAA; endmodule\n")
            .expect("valid SV source compiles");
        let body = design
            .top_instances()
            .next()
            .expect("one top instance")
            .instance_body()
            .expect("instance has a body");
        let p = body.find("p").expect("p is a member of m");
        let cv = p
            .initializer()
            .expect("p has an initializer")
            .constant_value()
            .expect("p's initializer folds to a constant");
        let fv = four_state_of_constant(&cv).expect("an integer constant yields Some");
        match fv {
            FourStateValue::Narrow(word) => {
                let low_byte = (word & 0xFF) as u8;
                assert_eq!(low_byte, 0xAA);
            }
            FourStateValue::Wide { .. } => panic!("expected Narrow for a 32-bit int constant"),
        }
    }

    /// Task 16: proves `owned_svint_to_four_state` actually reads back both
    /// the known-value bits and the unknown/X bits from a real
    /// `OwnedSVInt` (built here via `four_state_to_owned_svint`, the same
    /// round trip `SlotDescriptor.initial` goes through in the real layout
    /// builder) — fails if the function only ever returned `Some(Narrow(0))`
    /// regardless of input, or dropped the unknown-bit half.
    #[test]
    fn owned_svint_to_four_state_round_trips_known_and_unknown_bits() {
        let known = FourStateValue::Narrow(0xAAu128);
        let v = four_state_to_owned_svint(&known, 8, false);
        let back = owned_svint_to_four_state(&v).expect("a fully-known 8-bit value round-trips");
        match back {
            FourStateValue::Narrow(w) => {
                assert_eq!(w as u64 & 0xFF, 0xAA);
                assert_eq!((w >> 64) as u64 & 0xFF, 0, "no unknown bits expected");
            }
            FourStateValue::Wide { .. } => panic!("expected Narrow for an 8-bit value"),
        }

        // bit 0 = X: val bit 0 must come back cleared, unk bit 0 must come
        // back set.
        let with_x = FourStateValue::Narrow(0b1000u128 | (0b0001u128 << 64));
        let v2 = four_state_to_owned_svint(&with_x, 4, false);
        let back2 =
            owned_svint_to_four_state(&v2).expect("a partially-unknown 4-bit value round-trips");
        match back2 {
            FourStateValue::Narrow(w) => {
                assert_eq!(
                    (w >> 64) as u64 & 0b1111,
                    0b0001,
                    "bit 0's unknown-ness must survive the round trip"
                );
                assert_eq!(
                    w as u64 & 0b1110,
                    0b1000,
                    "the known bits must survive the round trip"
                );
            }
            FourStateValue::Wide { .. } => panic!("expected Narrow for a 4-bit value"),
        }
    }

    /// A constant wider than 1024 bits (sv-lang's old per-bit expansion cap)
    /// reads back exactly, uniform or not -- before sv-lang 0.1.17 `bits()`
    /// was empty there and only uniform fills or a structural rebuild worked.
    #[test]
    fn four_state_of_constant_reads_a_constant_wider_than_1024_bits() {
        let design = compile_source(
            "module m; localparam bit [1099:0] p = {1100{1'b1}};\n\
             localparam bit [1099:0] q = {550{2'b10}}; endmodule\n",
        )
        .expect("valid SV source compiles");
        let body = design
            .top_instances()
            .next()
            .expect("one top instance")
            .instance_body()
            .expect("instance has a body");
        let init = |name: &str| {
            body.find(name)
                .expect("a member of m")
                .initializer()
                .expect("an initializer")
        };
        let (p, q) = (init("p"), init("q"));
        let pv = p.constant_value().expect("p folds");
        assert_eq!(
            format!("{:?}", four_state_of_constant(&pv)),
            format!("{:?}", Some(bits_to_four_state(&[Bit::One; 1100]))),
        );
        let alternating: Vec<Bit> = (0..1100)
            .map(|i| if i % 2 == 1 { Bit::One } else { Bit::Zero })
            .collect();
        assert_eq!(constant_expr_bits(&q), Some(alternating.clone()));
        assert_eq!(
            format!(
                "{:?}",
                four_state_of_constant(&q.constant_value().expect("q folds"))
            ),
            format!("{:?}", Some(bits_to_four_state(&alternating))),
        );
    }
}