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}