vj-sim 0.3.0

Netlist, scheduler and process model of the vj SystemVerilog simulator.
Documentation
//! The stored bit shape of every value type a signal or local can have.
//!
//! An integral value is its own bits. The 2-state non-integral types are
//! stored as bits too: `real`/`realtime` (64) and `shortreal` (32) as their
//! IEEE-754 encoding, `string` as a 64-bit interned id (`crate::strings`),
//! `chandle` as a 64-bit pointer, a class handle as a 64-bit generational
//! object handle (0 is `null`). An unpacked struct is the concatenation of
//! its fields' stored bits, first field most significant (the packed-struct
//! order); an unpacked union is as wide as its widest member, every member
//! at bit 0; a fixed-size unpacked array (as a struct field) is its
//! elements', lowest index least significant. So any of these is one flat
//! vector of known width that the ordinary slot, frame and packed-value
//! machinery stores, copies and compares. A queue, dynamic array or
//! associative array variable is a 64-bit handle ([`is_collection`]), with
//! no fixed-width stored form: it cannot be part of one of these vectors
//! (copying the vector would not copy the collection).

use sv_lang::{Bit, ConstantValue, Type};

/// `ty`'s stored width in bits; `None` for a type with no fixed-width
/// stored form (dynamic array, queue, event, ...).
///
/// A type with a width but no honest reference-counted form is refused
/// here, so every declaration of one fails loudly as an unsupported type
/// (classes plan Task 10; a premature free waiting to happen, since the
/// stored bits would name an object nothing owns a count of):
///
/// - an unpacked union with a class-handle or collection member: the
///   members overlay each other, so whether the stored bits currently are a
///   handle (to retain on a copy, to release on an overwrite) is not known.
pub fn storage_width(ty: &Type<'_>) -> Option<u64> {
    if ty.is_integral() || ty.is_floating() {
        return Some(ty.bit_width()).filter(|w| *w > 0);
    }
    // A string's id; a queue, dynamic or associative array's handle (a
    // field of an unpacked struct, an element of a fixed-size array); a
    // class handle (`crate::objects`, LRM 8.4), likewise a struct field or
    // fixed-array element.
    if ty.is_string()
        || ty.is_chandle()
        || ty.is_covergroup()
        || ty.is_virtual_interface()
        || ty.is_class()
        || is_collection(ty)
    {
        return Some(64);
    }
    if ty.is_unpacked_struct() {
        return ty
            .fields()
            .map(|f| storage_width(&f.value_type()?))
            .sum::<Option<u64>>()
            .filter(|w| *w > 0);
    }
    if ty.is_unpacked_union() {
        // An untagged union cannot tell which overlaid member is live, so a
        // refcounted member (retain on copy, release on overwrite) has no
        // sound stored form; a tagged one's tag names the live member
        // (LRM 1800-2023 7.3.2), so its handles are tag-guarded.
        if !ty.is_tagged_union()
            && ty
                .fields()
                .any(|f| f.value_type().is_some_and(|t| holds_refcounted(&t)))
        {
            return None;
        }
        return ty
            .fields()
            .map(|f| member_width(&f.value_type()?))
            .try_fold(0u64, |m, w| Some(m.max(w?)))
            .map(|payload| payload + tagged_tag_bits(ty))
            .filter(|w| *w > 0);
    }
    if ty.is_unpacked_array() && !ty.is_dynamically_sized_array() {
        let n = u64::from(ty.fixed_unpacked_array_range().width());
        return Some(n * storage_width(&ty.element_type()?)?);
    }
    None
}

/// A union member's payload width: a `void` member (LRM 1800-2023 7.3.2)
/// carries no value bits at all.
fn member_width(ty: &Type<'_>) -> Option<u64> {
    if ty.is_void() {
        return Some(0);
    }
    storage_width(ty)
}

/// The tag width of a tagged union: `clog2(member count)` bits, the number
/// slang itself reserves for a packed one (LRM 1800-2023 7.3.2); 0 for
/// anything that is not a tagged union (and for a one-member tagged union,
/// whose tag is always 0).
pub fn tagged_tag_bits(ty: &Type<'_>) -> u64 {
    if !ty.is_tagged_union() {
        return 0;
    }
    if ty.is_packed_union() {
        return u64::from(ty.packed_union_tag_bits());
    }
    let n = ty.fields().count() as u64;
    u64::from(n.next_power_of_two().trailing_zeros())
}

/// `(lsb, width)` of the tag within the stored form of the tagged union
/// `ty`: the tag sits above the payload (LRM 7.3.2: "the tag is in the
/// most-significant bits"). `None` for a type that is not a tagged union.
pub fn tagged_tag_slice(ty: &Type<'_>) -> Option<(u64, u64)> {
    if !ty.is_tagged_union() {
        return None;
    }
    let total = storage_width(ty)?;
    let tag = tagged_tag_bits(ty);
    Some((total - tag, tag))
}

/// Whether `ty` is an associative array indexed by a class handle: a type
/// whose collection owns its keys (`KeyKind::Obj`, LRM 7.8.3, 8.29).
pub fn is_class_keyed_assoc(ty: &Type<'_>) -> bool {
    ty.is_associative_array() && ty.associative_index_type().is_some_and(|k| k.is_class())
}

/// Whether a value of type `ty` holds a class handle or a collection handle
/// anywhere in its stored form (a field, an element, recursively).
pub fn holds_refcounted(ty: &Type<'_>) -> bool {
    if ty.is_class() || is_collection(ty) {
        return true;
    }
    if ty.is_unpacked_struct() || ty.is_unpacked_union() {
        return ty
            .fields()
            .any(|f| f.value_type().is_some_and(|t| holds_refcounted(&t)));
    }
    ty.is_unpacked_array() && ty.element_type().is_some_and(|e| holds_refcounted(&e))
}

/// Whether `ty` is a queue, dynamic array or associative array: stored as
/// a 64-bit handle to a collection (`crate::collections`).
pub fn is_collection(ty: &Type<'_>) -> bool {
    ty.is_dynamically_sized_array()
}

/// Whether `ty` is an unpacked aggregate this module lays out as one flat
/// vector: an unpacked struct or union.
pub fn is_unpacked_aggregate(ty: &Type<'_>) -> bool {
    ty.is_unpacked_struct() || ty.is_unpacked_union()
}

/// The unpacked dimensions of an `interconnect` net's type (LRM 1800-2023
/// 6.6.8), outermost first as `(left, right)` bounds: `None` when `ty` is not
/// an interconnect type (an `interconnect` is slang's untyped type, alone or
/// as the element of fixed unpacked arrays). A scalar `interconnect` has no
/// dimensions.
pub fn interconnect_dims(ty: &Type<'_>) -> Option<Vec<(i32, i32)>> {
    let mut dims = Vec::new();
    let mut cur = *ty;
    while cur.is_unpacked_array() && !is_collection(&cur) {
        let r = cur.fixed_unpacked_array_range();
        dims.push((r.left, r.right));
        cur = cur.element_type()?;
    }
    cur.is_untyped_type().then_some(dims)
}

/// Whether `ty` is, or holds (as a struct or union member or an array
/// element), an unpacked tagged union: such a type starts undefined, tag
/// included, even when every one of its members is 2-state.
pub fn contains_tagged_union(ty: &Type<'_>) -> bool {
    if ty.is_unpacked_union() && ty.is_tagged_union() {
        return true;
    }
    if ty.is_unpacked_struct() || ty.is_unpacked_union() {
        return ty
            .fields()
            .any(|f| f.value_type().is_some_and(|t| contains_tagged_union(&t)));
    }
    if ty.is_unpacked_array() && !is_collection(ty) {
        return ty.element_type().is_some_and(|t| contains_tagged_union(&t));
    }
    false
}

/// `(lsb, width)` of field number `index` (declaration order) within the
/// stored form of the unpacked struct or union `ty`.
pub fn field_slice(ty: &Type<'_>, index: u32) -> Option<(u64, u64)> {
    let widths = ty
        .fields()
        .map(|f| member_width(&f.value_type()?))
        .collect::<Option<Vec<u64>>>()?;
    let width = *widths.get(index as usize)?;
    if ty.is_unpacked_union() {
        return Some((0, width));
    }
    // Later fields sit below earlier ones.
    Some((widths[index as usize + 1..].iter().sum(), width))
}

/// The value a variable of type `ty` starts with, LSB first: X for every
/// 4-state integral bit, 0 for every 2-state one (and for a real, string or
/// chandle's stored bits). An unpacked union starts as its first member's
/// initial value, padded with 0.
pub fn default_bits(ty: &Type<'_>) -> Option<Vec<Bit>> {
    let width = usize::try_from(storage_width(ty)?).ok()?;
    if ty.is_integral() {
        let fill = if ty.is_four_state() {
            Bit::X
        } else {
            Bit::Zero
        };
        return Some(vec![fill; width]);
    }
    if ty.is_unpacked_struct() {
        let mut out = Vec::with_capacity(width);
        let fields: Vec<_> = ty.fields().collect();
        for f in fields.iter().rev() {
            let fty = f.value_type()?;
            // A member's declared default (`struct { bit [3:0] lo = c; }`,
            // LRM 1800-2023 7.2.2) replaces the type default when it folds.
            let declared = f
                .initializer()
                .and_then(|init| init.constant_value())
                .and_then(|cv| constant_bits(&cv, &fty));
            out.extend(match declared {
                Some(bits) => bits,
                None => default_bits(&fty)?,
            });
        }
        return Some(out);
    }
    if ty.is_unpacked_union() && ty.is_tagged_union() {
        // An uninitialized tagged union is undefined, tag included (LRM
        // 1800-2023 7.3.2): all X, even when every member is 2-state, so a
        // tag test on it is never true and a member read of it errors. (A
        // 2-state member read of it reads 0: `read_member` clears the X.)
        return Some(vec![Bit::X; width]);
    }
    if ty.is_unpacked_union() {
        let mut out = default_bits(&ty.fields().next()?.value_type()?)?;
        out.resize(width, Bit::Zero);
        return Some(out);
    }
    if ty.is_unpacked_array() && !is_collection(ty) {
        let n = ty.fixed_unpacked_array_range().width() as usize;
        return Some(default_bits(&ty.element_type()?)?.repeat(n));
    }
    // real / shortreal / string / chandle, and a collection (empty).
    Some(vec![Bit::Zero; width])
}

/// The stored bits (LSB first) of the constant `cv` of type `ty`; `None`
/// for a value this module cannot encode.
pub fn constant_bits(cv: &ConstantValue, ty: &Type<'_>) -> Option<Vec<Bit>> {
    let width = usize::try_from(storage_width(ty)?).ok()?;
    let word_bits = |w: u64| -> Vec<Bit> {
        (0..width)
            .map(|i| {
                if i < 64 && (w >> i) & 1 == 1 {
                    Bit::One
                } else {
                    Bit::Zero
                }
            })
            .collect()
    };
    match cv {
        ConstantValue::Integer(i) => {
            let mut bits = super::svint_bits(i)?.into_owned();
            let fill = if i.is_signed() {
                *bits.last()?
            } else {
                Bit::Zero
            };
            bits.resize(width, fill);
            Some(bits)
        }
        ConstantValue::Real(f) if width == 32 => Some(word_bits(u64::from((*f as f32).to_bits()))),
        ConstantValue::Real(f) => Some(word_bits(f.to_bits())),
        ConstantValue::String(s) if ty.is_string() => {
            Some(word_bits(crate::strings::intern(s.as_bytes())))
        }
        // LRM 7.3.2, 10.9.2: `tagged M v` is the tag above the member's
        // value, zero padding between them -- the form `tagged::lower_tagged_expr`
        // builds for the same expression.
        ConstantValue::TaggedUnion { member, value, .. }
            if ty.is_unpacked_union() && ty.is_tagged_union() =>
        {
            let field = ty.fields().nth(*member as usize)?;
            let fty = field.value_type()?;
            let mw = usize::try_from(member_width(&fty)?).ok()?;
            let mut out = if mw == 0 {
                Vec::new()
            } else {
                constant_bits(value, &fty)?
            };
            if out.len() != mw {
                return None;
            }
            let tag = tagged_tag_bits(ty) as usize;
            out.resize(width.checked_sub(tag)?, Bit::Zero);
            out.extend((0..tag).map(|i| {
                if (*member >> i) & 1 == 1 {
                    Bit::One
                } else {
                    Bit::Zero
                }
            }));
            Some(out)
        }
        _ => {
            let elems = cv.as_array()?;
            // A queue or dynamic array (a struct field's constant): a
            // frozen handle to its elements (`crate::collections`).
            if is_collection(ty) {
                if ty.is_associative_array() {
                    return None;
                }
                let elem_ty = ty.element_type()?;
                let ew = (2 * storage_width(&elem_ty)?).div_ceil(64).max(1) as usize;
                let mut words = Vec::with_capacity(elems.len() * ew);
                for e in elems {
                    let mut w = super::value::packed_words(&constant_bits(e, &elem_ty)?);
                    w.resize(ew, 0);
                    words.extend(w);
                }
                let h = if elems.is_empty() {
                    0
                } else {
                    crate::collections::frozen_from_words(ew, &words)
                };
                return Some(word_bits(h));
            }
            if ty.is_unpacked_struct() {
                let fields: Vec<_> = ty.fields().collect();
                if fields.len() != elems.len() {
                    return None;
                }
                let mut out = Vec::with_capacity(width);
                for (f, e) in fields.iter().zip(elems).rev() {
                    out.extend(constant_bits(e, &f.value_type()?)?);
                }
                return Some(out);
            }
            if ty.is_unpacked_array() {
                let range = ty.fixed_unpacked_array_range();
                let elem_ty = ty.element_type()?;
                let mut rows = elems
                    .iter()
                    .map(|e| constant_bits(e, &elem_ty))
                    .collect::<Option<Vec<_>>>()?;
                // Constant elements run left bound to right bound; the
                // lowest index is stored least significant.
                if range.left > range.right {
                    rows.reverse();
                }
                return Some(rows.concat());
            }
            None
        }
    }
}