Skip to main content

vj_sim/state/
storage.rs

1//! The stored bit shape of every value type a signal or local can have.
2//!
3//! An integral value is its own bits. The 2-state non-integral types are
4//! stored as bits too: `real`/`realtime` (64) and `shortreal` (32) as their
5//! IEEE-754 encoding, `string` as a 64-bit interned id (`crate::strings`),
6//! `chandle` as a 64-bit pointer, a class handle as a 64-bit generational
7//! object handle (0 is `null`). An unpacked struct is the concatenation of
8//! its fields' stored bits, first field most significant (the packed-struct
9//! order); an unpacked union is as wide as its widest member, every member
10//! at bit 0; a fixed-size unpacked array (as a struct field) is its
11//! elements', lowest index least significant. So any of these is one flat
12//! vector of known width that the ordinary slot, frame and packed-value
13//! machinery stores, copies and compares. A queue, dynamic array or
14//! associative array variable is a 64-bit handle ([`is_collection`]), with
15//! no fixed-width stored form: it cannot be part of one of these vectors
16//! (copying the vector would not copy the collection).
17
18use sv_lang::{Bit, ConstantValue, Type};
19
20/// `ty`'s stored width in bits; `None` for a type with no fixed-width
21/// stored form (dynamic array, queue, event, ...).
22///
23/// A type with a width but no honest reference-counted form is refused
24/// here, so every declaration of one fails loudly as an unsupported type
25/// (classes plan Task 10; a premature free waiting to happen, since the
26/// stored bits would name an object nothing owns a count of):
27///
28/// - an unpacked union with a class-handle or collection member: the
29///   members overlay each other, so whether the stored bits currently are a
30///   handle (to retain on a copy, to release on an overwrite) is not known.
31pub fn storage_width(ty: &Type<'_>) -> Option<u64> {
32    if ty.is_integral() || ty.is_floating() {
33        return Some(ty.bit_width()).filter(|w| *w > 0);
34    }
35    // A string's id; a queue, dynamic or associative array's handle (a
36    // field of an unpacked struct, an element of a fixed-size array); a
37    // class handle (`crate::objects`, LRM 8.4), likewise a struct field or
38    // fixed-array element.
39    if ty.is_string()
40        || ty.is_chandle()
41        || ty.is_covergroup()
42        || ty.is_virtual_interface()
43        || ty.is_class()
44        || is_collection(ty)
45    {
46        return Some(64);
47    }
48    if ty.is_unpacked_struct() {
49        return ty
50            .fields()
51            .map(|f| storage_width(&f.value_type()?))
52            .sum::<Option<u64>>()
53            .filter(|w| *w > 0);
54    }
55    if ty.is_unpacked_union() {
56        // An untagged union cannot tell which overlaid member is live, so a
57        // refcounted member (retain on copy, release on overwrite) has no
58        // sound stored form; a tagged one's tag names the live member
59        // (LRM 1800-2023 7.3.2), so its handles are tag-guarded.
60        if !ty.is_tagged_union()
61            && ty
62                .fields()
63                .any(|f| f.value_type().is_some_and(|t| holds_refcounted(&t)))
64        {
65            return None;
66        }
67        return ty
68            .fields()
69            .map(|f| member_width(&f.value_type()?))
70            .try_fold(0u64, |m, w| Some(m.max(w?)))
71            .map(|payload| payload + tagged_tag_bits(ty))
72            .filter(|w| *w > 0);
73    }
74    if ty.is_unpacked_array() && !ty.is_dynamically_sized_array() {
75        let n = u64::from(ty.fixed_unpacked_array_range().width());
76        return Some(n * storage_width(&ty.element_type()?)?);
77    }
78    None
79}
80
81/// A union member's payload width: a `void` member (LRM 1800-2023 7.3.2)
82/// carries no value bits at all.
83fn member_width(ty: &Type<'_>) -> Option<u64> {
84    if ty.is_void() {
85        return Some(0);
86    }
87    storage_width(ty)
88}
89
90/// The tag width of a tagged union: `clog2(member count)` bits, the number
91/// slang itself reserves for a packed one (LRM 1800-2023 7.3.2); 0 for
92/// anything that is not a tagged union (and for a one-member tagged union,
93/// whose tag is always 0).
94pub fn tagged_tag_bits(ty: &Type<'_>) -> u64 {
95    if !ty.is_tagged_union() {
96        return 0;
97    }
98    if ty.is_packed_union() {
99        return u64::from(ty.packed_union_tag_bits());
100    }
101    let n = ty.fields().count() as u64;
102    u64::from(n.next_power_of_two().trailing_zeros())
103}
104
105/// `(lsb, width)` of the tag within the stored form of the tagged union
106/// `ty`: the tag sits above the payload (LRM 7.3.2: "the tag is in the
107/// most-significant bits"). `None` for a type that is not a tagged union.
108pub fn tagged_tag_slice(ty: &Type<'_>) -> Option<(u64, u64)> {
109    if !ty.is_tagged_union() {
110        return None;
111    }
112    let total = storage_width(ty)?;
113    let tag = tagged_tag_bits(ty);
114    Some((total - tag, tag))
115}
116
117/// Whether `ty` is an associative array indexed by a class handle: a type
118/// whose collection owns its keys (`KeyKind::Obj`, LRM 7.8.3, 8.29).
119pub fn is_class_keyed_assoc(ty: &Type<'_>) -> bool {
120    ty.is_associative_array() && ty.associative_index_type().is_some_and(|k| k.is_class())
121}
122
123/// Whether a value of type `ty` holds a class handle or a collection handle
124/// anywhere in its stored form (a field, an element, recursively).
125pub fn holds_refcounted(ty: &Type<'_>) -> bool {
126    if ty.is_class() || is_collection(ty) {
127        return true;
128    }
129    if ty.is_unpacked_struct() || ty.is_unpacked_union() {
130        return ty
131            .fields()
132            .any(|f| f.value_type().is_some_and(|t| holds_refcounted(&t)));
133    }
134    ty.is_unpacked_array() && ty.element_type().is_some_and(|e| holds_refcounted(&e))
135}
136
137/// Whether `ty` is a queue, dynamic array or associative array: stored as
138/// a 64-bit handle to a collection (`crate::collections`).
139pub fn is_collection(ty: &Type<'_>) -> bool {
140    ty.is_dynamically_sized_array()
141}
142
143/// Whether `ty` is an unpacked aggregate this module lays out as one flat
144/// vector: an unpacked struct or union.
145pub fn is_unpacked_aggregate(ty: &Type<'_>) -> bool {
146    ty.is_unpacked_struct() || ty.is_unpacked_union()
147}
148
149/// The unpacked dimensions of an `interconnect` net's type (LRM 1800-2023
150/// 6.6.8), outermost first as `(left, right)` bounds: `None` when `ty` is not
151/// an interconnect type (an `interconnect` is slang's untyped type, alone or
152/// as the element of fixed unpacked arrays). A scalar `interconnect` has no
153/// dimensions.
154pub fn interconnect_dims(ty: &Type<'_>) -> Option<Vec<(i32, i32)>> {
155    let mut dims = Vec::new();
156    let mut cur = *ty;
157    while cur.is_unpacked_array() && !is_collection(&cur) {
158        let r = cur.fixed_unpacked_array_range();
159        dims.push((r.left, r.right));
160        cur = cur.element_type()?;
161    }
162    cur.is_untyped_type().then_some(dims)
163}
164
165/// Whether `ty` is, or holds (as a struct or union member or an array
166/// element), an unpacked tagged union: such a type starts undefined, tag
167/// included, even when every one of its members is 2-state.
168pub fn contains_tagged_union(ty: &Type<'_>) -> bool {
169    if ty.is_unpacked_union() && ty.is_tagged_union() {
170        return true;
171    }
172    if ty.is_unpacked_struct() || ty.is_unpacked_union() {
173        return ty
174            .fields()
175            .any(|f| f.value_type().is_some_and(|t| contains_tagged_union(&t)));
176    }
177    if ty.is_unpacked_array() && !is_collection(ty) {
178        return ty.element_type().is_some_and(|t| contains_tagged_union(&t));
179    }
180    false
181}
182
183/// `(lsb, width)` of field number `index` (declaration order) within the
184/// stored form of the unpacked struct or union `ty`.
185pub fn field_slice(ty: &Type<'_>, index: u32) -> Option<(u64, u64)> {
186    let widths = ty
187        .fields()
188        .map(|f| member_width(&f.value_type()?))
189        .collect::<Option<Vec<u64>>>()?;
190    let width = *widths.get(index as usize)?;
191    if ty.is_unpacked_union() {
192        return Some((0, width));
193    }
194    // Later fields sit below earlier ones.
195    Some((widths[index as usize + 1..].iter().sum(), width))
196}
197
198/// The value a variable of type `ty` starts with, LSB first: X for every
199/// 4-state integral bit, 0 for every 2-state one (and for a real, string or
200/// chandle's stored bits). An unpacked union starts as its first member's
201/// initial value, padded with 0.
202pub fn default_bits(ty: &Type<'_>) -> Option<Vec<Bit>> {
203    let width = usize::try_from(storage_width(ty)?).ok()?;
204    if ty.is_integral() {
205        let fill = if ty.is_four_state() {
206            Bit::X
207        } else {
208            Bit::Zero
209        };
210        return Some(vec![fill; width]);
211    }
212    if ty.is_unpacked_struct() {
213        let mut out = Vec::with_capacity(width);
214        let fields: Vec<_> = ty.fields().collect();
215        for f in fields.iter().rev() {
216            let fty = f.value_type()?;
217            // A member's declared default (`struct { bit [3:0] lo = c; }`,
218            // LRM 1800-2023 7.2.2) replaces the type default when it folds.
219            let declared = f
220                .initializer()
221                .and_then(|init| init.constant_value())
222                .and_then(|cv| constant_bits(&cv, &fty));
223            out.extend(match declared {
224                Some(bits) => bits,
225                None => default_bits(&fty)?,
226            });
227        }
228        return Some(out);
229    }
230    if ty.is_unpacked_union() && ty.is_tagged_union() {
231        // An uninitialized tagged union is undefined, tag included (LRM
232        // 1800-2023 7.3.2): all X, even when every member is 2-state, so a
233        // tag test on it is never true and a member read of it errors. (A
234        // 2-state member read of it reads 0: `read_member` clears the X.)
235        return Some(vec![Bit::X; width]);
236    }
237    if ty.is_unpacked_union() {
238        let mut out = default_bits(&ty.fields().next()?.value_type()?)?;
239        out.resize(width, Bit::Zero);
240        return Some(out);
241    }
242    if ty.is_unpacked_array() && !is_collection(ty) {
243        let n = ty.fixed_unpacked_array_range().width() as usize;
244        return Some(default_bits(&ty.element_type()?)?.repeat(n));
245    }
246    // real / shortreal / string / chandle, and a collection (empty).
247    Some(vec![Bit::Zero; width])
248}
249
250/// The stored bits (LSB first) of the constant `cv` of type `ty`; `None`
251/// for a value this module cannot encode.
252pub fn constant_bits(cv: &ConstantValue, ty: &Type<'_>) -> Option<Vec<Bit>> {
253    let width = usize::try_from(storage_width(ty)?).ok()?;
254    let word_bits = |w: u64| -> Vec<Bit> {
255        (0..width)
256            .map(|i| {
257                if i < 64 && (w >> i) & 1 == 1 {
258                    Bit::One
259                } else {
260                    Bit::Zero
261                }
262            })
263            .collect()
264    };
265    match cv {
266        ConstantValue::Integer(i) => {
267            let mut bits = super::svint_bits(i)?.into_owned();
268            let fill = if i.is_signed() {
269                *bits.last()?
270            } else {
271                Bit::Zero
272            };
273            bits.resize(width, fill);
274            Some(bits)
275        }
276        ConstantValue::Real(f) if width == 32 => Some(word_bits(u64::from((*f as f32).to_bits()))),
277        ConstantValue::Real(f) => Some(word_bits(f.to_bits())),
278        ConstantValue::String(s) if ty.is_string() => {
279            Some(word_bits(crate::strings::intern(s.as_bytes())))
280        }
281        // LRM 7.3.2, 10.9.2: `tagged M v` is the tag above the member's
282        // value, zero padding between them -- the form `tagged::lower_tagged_expr`
283        // builds for the same expression.
284        ConstantValue::TaggedUnion { member, value, .. }
285            if ty.is_unpacked_union() && ty.is_tagged_union() =>
286        {
287            let field = ty.fields().nth(*member as usize)?;
288            let fty = field.value_type()?;
289            let mw = usize::try_from(member_width(&fty)?).ok()?;
290            let mut out = if mw == 0 {
291                Vec::new()
292            } else {
293                constant_bits(value, &fty)?
294            };
295            if out.len() != mw {
296                return None;
297            }
298            let tag = tagged_tag_bits(ty) as usize;
299            out.resize(width.checked_sub(tag)?, Bit::Zero);
300            out.extend((0..tag).map(|i| {
301                if (*member >> i) & 1 == 1 {
302                    Bit::One
303                } else {
304                    Bit::Zero
305                }
306            }));
307            Some(out)
308        }
309        _ => {
310            let elems = cv.as_array()?;
311            // A queue or dynamic array (a struct field's constant): a
312            // frozen handle to its elements (`crate::collections`).
313            if is_collection(ty) {
314                if ty.is_associative_array() {
315                    return None;
316                }
317                let elem_ty = ty.element_type()?;
318                let ew = (2 * storage_width(&elem_ty)?).div_ceil(64).max(1) as usize;
319                let mut words = Vec::with_capacity(elems.len() * ew);
320                for e in elems {
321                    let mut w = super::value::packed_words(&constant_bits(e, &elem_ty)?);
322                    w.resize(ew, 0);
323                    words.extend(w);
324                }
325                let h = if elems.is_empty() {
326                    0
327                } else {
328                    crate::collections::frozen_from_words(ew, &words)
329                };
330                return Some(word_bits(h));
331            }
332            if ty.is_unpacked_struct() {
333                let fields: Vec<_> = ty.fields().collect();
334                if fields.len() != elems.len() {
335                    return None;
336                }
337                let mut out = Vec::with_capacity(width);
338                for (f, e) in fields.iter().zip(elems).rev() {
339                    out.extend(constant_bits(e, &f.value_type()?)?);
340                }
341                return Some(out);
342            }
343            if ty.is_unpacked_array() {
344                let range = ty.fixed_unpacked_array_range();
345                let elem_ty = ty.element_type()?;
346                let mut rows = elems
347                    .iter()
348                    .map(|e| constant_bits(e, &elem_ty))
349                    .collect::<Option<Vec<_>>>()?;
350                // Constant elements run left bound to right bound; the
351                // lowest index is stored least significant.
352                if range.left > range.right {
353                    rows.reverse();
354                }
355                return Some(rows.concat());
356            }
357            None
358        }
359    }
360}