Skip to main content

sv_sim_sim/constraints/build/
aggregate.rs

1//! Unpacked struct and fixed-size unpacked array class properties for the
2//! [`Builder`] (plan task C14; IEEE 1800-2023 18.4, 18.5.7, 18.8).
3//!
4//! An aggregate property is stored as one flat value (`state::storage`): a
5//! struct's fields concatenated, an array's elements lowest index first. Its
6//! *leaves* (an integral or real member or element) are the variables of the
7//! solve. A leaf is a [`Window`] of the property's value: it has its own
8//! [`VarInfo`] (or [`RealVarInfo`]), keyed by the property and the path of
9//! [`Step`]s from it, and the registry reads and writes the leaf through that
10//! window.
11//!
12//! * A leaf is random when the property is declared `rand`/`randc` (18.4: an
13//!   array declared `rand` makes every element random; a `rand` unpacked
14//!   struct solves every member concurrently) or when a struct member on its
15//!   path is. A member's own qualifier overrides the one it would inherit, so a
16//!   `randc` member of a `rand` struct is `randc` and a member that is not
17//!   `rand` of a non-`rand` struct is a state variable (18.3).
18//! * An unpacked union is never random; a `string` or `chandle` member has no
19//!   solved value and is skipped. A class handle, queue, dynamic array or
20//!   associative array *inside* a random aggregate is
21//!   [`BuildError::Unsupported`]: such a leaf would be a random object or a
22//!   random-size array nested in a flat value.
23//! * `a[i]` with a constant index names one leaf. An index that is a state
24//!   variable or loop variable names a *choice* among the leaves
25//!   ([`ArrayNode::Pick`]) the registry resolves once the index is known; an
26//!   index that is a random variable is [`BuildError::BadArray`] (18.5.7.1).
27//!   An index outside the array is an error only if the constraint it is in
28//!   is live: "users have to explicitly exclude these indices using
29//!   predicates".
30//! * `A.sum()` and the other reduction methods over a fixed-size array unroll
31//!   over its elements (`arrays`); `foreach` over one unrolls too.
32
33use super::arrays::{ArrayShape, array_shape, const_i64};
34use super::real::{real_kind, state_ty, strip_conversions};
35use super::*;
36use crate::constraints::ir::{ArrayNode, Assignment, eval};
37use crate::constraints::real_domain::{FloatKind as Fk, RealExpr};
38use crate::state::storage::{field_slice, storage_width};
39
40/// One step from an aggregate property to a leaf of it.
41#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
42pub enum Step {
43    /// The element at this declared index of a fixed-size array.
44    Index(i64),
45    /// The member at this position (declaration order) of an unpacked struct.
46    Field(u32),
47}
48
49/// A leaf declared as a random variable, in the namespace it lives in.
50#[derive(Clone, Copy, Debug, PartialEq, Eq)]
51pub enum Declared {
52    /// An integral leaf: an id of [`ConstraintSet::vars`].
53    Int(u32),
54    /// A `real`/`shortreal` leaf: an id of [`ConstraintSet::real_vars`].
55    Real(u32),
56}
57
58/// One step of an expression naming a leaf, before indices are evaluated.
59#[derive(Clone, Copy)]
60pub(super) enum RawStep<'d> {
61    /// `[expr]`, to be built.
62    Index(Expression<'d>),
63    /// `[constant]`.
64    Const(i64),
65    /// `.member`, by position.
66    Field(u32),
67}
68
69/// An expression naming a leaf of an aggregate property of this class.
70#[derive(Clone)]
71pub(super) struct Chain<'d> {
72    /// The class property.
73    pub root: Symbol<'d>,
74    /// The selects and member accesses from it, outermost first (none for the
75    /// property itself).
76    pub steps: Vec<RawStep<'d>>,
77}
78
79/// What `e` names, if it is a class property of this class or a member or
80/// element of one: `s`, `this.s`, `s.m`, `a[i]`, `s.m[i].n`, ... (anything
81/// reached through an object handle, a local or a union is `None`).
82pub(super) fn aggregate_chain<'d>(
83    e: Expression<'d>,
84    forced: &HashSet<SymbolId>,
85) -> Option<Chain<'d>> {
86    let e = strip_conversions(e);
87    let kind = e.kind();
88    // Every other kind of expression names no leaf of a property: `None`.
89    if kind == ExpressionKind::NamedValue {
90        let sym = e.referenced_symbol()?;
91        return (sym.kind() == SymbolKind::ClassProperty || forced.contains(&sym.uid())).then(
92            || Chain {
93                root: sym,
94                steps: Vec::new(),
95            },
96        );
97    }
98    if kind == ExpressionKind::MemberAccess {
99        let base = e.left()?;
100        let member = e.member_symbol()?;
101        let is_this = strip_conversions(base).kind() == ExpressionKind::NamedValue
102            && strip_conversions(base)
103                .referenced_symbol()
104                .is_some_and(|s| s.name() == "this");
105        if is_this {
106            return (member.kind() == SymbolKind::ClassProperty).then(|| Chain {
107                root: member,
108                steps: Vec::new(),
109            });
110        }
111        let bt = base.expr_type()?.canonical();
112        if !bt.is_unpacked_struct() {
113            return None;
114        }
115        let index = bt.fields().position(|f| f.name() == member.name())?;
116        let mut chain = aggregate_chain(base, forced)?;
117        chain.steps.push(RawStep::Field(u32::try_from(index).ok()?));
118        return Some(chain);
119    }
120    if kind == ExpressionKind::ElementSelect {
121        let base = e.select_value()?;
122        if array_shape(base.expr_type()?) != Some(ArrayShape::Fixed) {
123            return None;
124        }
125        let mut chain = aggregate_chain(base, forced)?;
126        chain.steps.push(RawStep::Index(e.selector()?));
127        return Some(chain);
128    }
129    None
130}
131
132/// Whether `t` is an unpacked struct or a fixed-size unpacked array: a type
133/// whose leaves are variables ([`Builder::declare_rand_aggregate`]).
134pub fn is_aggregate_type(t: Type<'_>) -> bool {
135    let c = t.canonical();
136    c.is_unpacked_struct() || array_shape(c) == Some(ArrayShape::Fixed)
137}
138
139/// Whether the class property `sym` has any random leaf: it is declared
140/// `rand`/`randc`, or an unpacked-struct member at any depth is. Such a
141/// property has a `rand_mode` (18.8) and is declared with
142/// [`Builder::declare_rand_aggregate`] (or [`Builder::declare_rand`] when it is
143/// not an aggregate).
144pub fn property_is_random(sym: Symbol<'_>) -> bool {
145    if sym.rand_mode() != RandMode::None {
146        return true;
147    }
148    sym.value_type().is_some_and(has_random_member)
149}
150
151fn has_random_member(t: Type<'_>) -> bool {
152    let c = t.canonical();
153    if c.is_unpacked_struct() {
154        return c.fields().any(|f| {
155            f.field_rand_mode() != RandMode::None || f.value_type().is_some_and(has_random_member)
156        });
157    }
158    array_shape(c) == Some(ArrayShape::Fixed) && c.element_type().is_some_and(has_random_member)
159}
160
161/// What kind of value a leaf is.
162#[derive(Clone, Copy, Debug)]
163pub(super) enum LeafKind {
164    /// An integral leaf.
165    Int(Ty),
166    /// A `real` or `shortreal` leaf.
167    Real(Fk),
168    /// A virtual interface: never randomizable.
169    Vif,
170    /// A `string` or `chandle`: stored, but with no solved value.
171    Skipped,
172    /// A class handle, queue, dynamic or associative array, or a union.
173    Other,
174}
175
176/// A leaf of an aggregate property.
177#[derive(Clone)]
178pub(super) struct LeafRef<'d> {
179    /// The property's key.
180    pub root: SymbolId,
181    /// `s.f[1]`: the property's name and the steps to the leaf.
182    pub name: String,
183    /// The steps from the property.
184    pub path: Vec<Step>,
185    /// Where the leaf sits in the property's value.
186    pub window: Window,
187    /// The effective `rand`/`randc` mode (none for a state leaf).
188    pub mode: RandMode,
189    /// The leaf's value kind.
190    pub kind: LeafKind,
191    /// The leaf's declared type.
192    pub vt: Type<'d>,
193}
194
195impl LeafRef<'_> {
196    pub(super) fn randc(&self) -> bool {
197        self.mode == RandMode::RandC
198    }
199}
200
201/// A resolved leaf expression: one leaf, or a run-time choice among several.
202pub(super) enum Sel<'d> {
203    Leaf(LeafRef<'d>),
204    Pick {
205        index: Expr,
206        low: i64,
207        elems: Vec<Sel<'d>>,
208    },
209}
210
211/// A position inside an aggregate property while its steps are followed.
212#[derive(Clone)]
213struct Cursor<'d> {
214    root: SymbolId,
215    /// The whole property's width.
216    whole: u64,
217    ty: Type<'d>,
218    mode: RandMode,
219    path: Vec<Step>,
220    name: String,
221    /// The LSB of the current value within the property's value.
222    bit: u64,
223}
224
225fn missing(what: impl Into<String>) -> BuildError {
226    BuildError::Missing { what: what.into() }
227}
228
229impl<'d> Cursor<'d> {
230    fn of(root: Symbol<'d>) -> Result<Cursor<'d>, BuildError> {
231        let ty = root
232            .value_type()
233            .ok_or_else(|| missing(format!("value type for `{}`", root.name())))?;
234        let whole = storage_width(&ty).ok_or_else(|| {
235            missing(format!(
236                "stored width of `{}` of type `{}`",
237                root.name(),
238                ty.to_sv_string()
239            ))
240        })?;
241        Ok(Cursor {
242            root: root.uid(),
243            whole,
244            ty,
245            mode: root.rand_mode(),
246            path: Vec::new(),
247            name: root.name().to_string(),
248            bit: 0,
249        })
250    }
251
252    /// Member number `i` of the struct this is at.
253    fn field(&self, i: u32) -> Result<Cursor<'d>, BuildError> {
254        let t = self.ty.canonical();
255        if !t.is_unpacked_struct() {
256            return Err(missing(format!(
257                "`{}` is not an unpacked struct",
258                self.name
259            )));
260        }
261        let f = t
262            .fields()
263            .nth(i as usize)
264            .ok_or_else(|| missing(format!("member {i} of `{}`", self.name)))?;
265        let (lsb, _) = field_slice(&t, i)
266            .ok_or_else(|| missing(format!("stored position of member {i} of `{}`", self.name)))?;
267        let ty = f
268            .value_type()
269            .ok_or_else(|| missing(format!("type of `{}.{}`", self.name, f.name())))?;
270        let mut path = self.path.clone();
271        path.push(Step::Field(i));
272        Ok(Cursor {
273            root: self.root.clone(),
274            whole: self.whole,
275            ty,
276            // A member's own qualifier replaces the one it would inherit.
277            mode: match f.field_rand_mode() {
278                RandMode::None => self.mode,
279                own => own,
280            },
281            path,
282            name: format!("{}.{}", self.name, f.name()),
283            bit: self.bit + lsb,
284        })
285    }
286
287    /// The declared bounds `(lo, hi)` and the element type of the fixed-size
288    /// array this is at.
289    fn dim(&self) -> Result<(i64, i64, Type<'d>), BuildError> {
290        let t = self.ty.canonical();
291        match array_shape(t) {
292            Some(ArrayShape::Fixed) => {}
293            Some(ArrayShape::Collection | ArrayShape::Assoc) => {
294                return Err(unsupported(
295                    format!(
296                        "`{}`, whose size is only known at run time (a dynamic array, queue or \
297                         associative array), as an element of an aggregate",
298                        self.name
299                    ),
300                    "C14",
301                ));
302            }
303            None => {
304                return Err(BuildError::OutOfRangeSelect {
305                    what: format!("`{}` is not an array, so it has no elements", self.name),
306                });
307            }
308        }
309        let r = t.fixed_unpacked_array_range();
310        let elem = t
311            .element_type()
312            .ok_or_else(|| missing(format!("element type of the array `{}`", self.name)))?;
313        Ok((
314            i64::from(r.left).min(i64::from(r.right)),
315            i64::from(r.left).max(i64::from(r.right)),
316            elem,
317        ))
318    }
319
320    /// The element at declared index `i` of the array this is at.
321    fn index(&self, i: i64) -> Result<Cursor<'d>, BuildError> {
322        let (lo, hi, elem) = self.dim()?;
323        if !(lo..=hi).contains(&i) {
324            let r = self.ty.canonical().fixed_unpacked_array_range();
325            return Err(BuildError::OutOfRangeSelect {
326                what: format!("`{}` index {i} outside [{}:{}]", self.name, r.left, r.right),
327            });
328        }
329        let ew = storage_width(&elem)
330            .ok_or_else(|| missing(format!("stored width of an element of `{}`", self.name)))?;
331        let mut path = self.path.clone();
332        path.push(Step::Index(i));
333        Ok(Cursor {
334            root: self.root.clone(),
335            whole: self.whole,
336            ty: elem,
337            mode: self.mode,
338            path,
339            name: format!("{}[{i}]", self.name),
340            bit: self.bit + (i - lo) as u64 * ew,
341        })
342    }
343
344    fn leaf(self) -> Result<LeafRef<'d>, BuildError> {
345        let width = storage_width(&self.ty)
346            .ok_or_else(|| missing(format!("stored width of `{}`", self.name)))?;
347        let narrow = |v: u64| {
348            u32::try_from(v).map_err(|_| {
349                missing(format!(
350                    "a window of `{}` within 32-bit offsets (the property is {} bits)",
351                    self.name, self.whole
352                ))
353            })
354        };
355        let window = Window {
356            bit: narrow(self.bit)?,
357            width: narrow(width)?,
358            field_width: narrow(self.whole)?,
359        };
360        let c = self.ty.canonical();
361        let kind = if let Some(k) = real_kind(Some(self.ty)) {
362            LeafKind::Real(k)
363        } else if c.is_virtual_interface() {
364            LeafKind::Vif
365        } else if c.is_integral() {
366            LeafKind::Int(ty_of(Some(self.ty), &format!("`{}`", self.name))?)
367        } else if c.is_string() || c.is_chandle() {
368            LeafKind::Skipped
369        } else {
370            LeafKind::Other
371        };
372        Ok(LeafRef {
373            root: self.root,
374            name: self.name,
375            path: self.path,
376            window,
377            mode: self.mode,
378            kind,
379            vt: self.ty,
380        })
381    }
382
383    /// Every random leaf at or below this, in declaration order.
384    fn random_leaves(self, out: &mut Vec<LeafRef<'d>>) -> Result<(), BuildError> {
385        let t = self.ty.canonical();
386        if t.is_unpacked_struct() {
387            for i in 0..t.fields().count() {
388                self.field(i as u32)?.random_leaves(out)?;
389            }
390            return Ok(());
391        }
392        if array_shape(t) == Some(ArrayShape::Fixed) {
393            let (lo, hi, _) = self.dim()?;
394            for i in lo..=hi {
395                self.index(i)?.random_leaves(out)?;
396            }
397            return Ok(());
398        }
399        if self.mode == RandMode::None || t.is_unpacked_union() {
400            return Ok(());
401        }
402        let leaf = self.leaf()?;
403        match leaf.kind {
404            LeafKind::Int(_) | LeafKind::Real(_) => out.push(leaf),
405            LeafKind::Skipped => {}
406            LeafKind::Vif => return Err(BuildError::VirtualInterfaceRand { name: leaf.name }),
407            LeafKind::Other => {
408                return Err(unsupported(
409                    format!(
410                        "the random member `{}` of type `{}` inside an unpacked struct or array \
411                         (a class handle, queue, dynamic array or associative array nested in a \
412                         flat aggregate cannot be randomized)",
413                        leaf.name,
414                        leaf.vt.to_sv_string()
415                    ),
416                    "C14",
417                ));
418            }
419        }
420        Ok(())
421    }
422}
423
424impl Builder<'_> {
425    /// The cursor at the root of `sym`; a variable declared by
426    /// [`Self::declare_aggregate_forced`] is random whatever its declaration.
427    fn cursor_of<'d>(&self, sym: Symbol<'d>) -> Result<Cursor<'d>, BuildError> {
428        let mut cur = Cursor::of(sym)?;
429        if self.forced_roots.contains(&cur.root) {
430            cur.mode = RandMode::Rand;
431        }
432        Ok(cur)
433    }
434
435    /// Declares every leaf of the unpacked-struct or fixed-size array variable
436    /// `sym` random for this call whether or not it is declared `rand` (LRM
437    /// 18.12: the arguments of `std::randomize` are random; 18.11 for a
438    /// property), and returns their ids. A member's own `rand`/`randc`
439    /// qualifier still decides its cyclic nature (18.4). The variable may then
440    /// be named in the constraints like a property (`a[i]`, `a.sum()`,
441    /// `foreach`, `s.m`).
442    pub fn declare_aggregate_forced(
443        &mut self,
444        sym: Symbol<'_>,
445    ) -> Result<Vec<Declared>, BuildError> {
446        self.forced_roots.insert(sym.uid());
447        self.declare_rand_aggregate(sym)
448    }
449
450    /// Declares every random leaf of the unpacked-struct or fixed-size
451    /// unpacked-array property `sym` as a random variable, in declaration
452    /// order, and returns their ids (idempotent). `sym` need not be `rand`
453    /// itself: a struct with a `rand` member has random leaves (LRM 18.4).
454    pub fn declare_rand_aggregate(&mut self, sym: Symbol<'_>) -> Result<Vec<Declared>, BuildError> {
455        let cur = self.cursor_of(sym)?;
456        if !is_aggregate_type(cur.ty) {
457            return Err(missing(format!(
458                "an unpacked struct or fixed-size array type for `{}` (use `declare_rand`)",
459                sym.name()
460            )));
461        }
462        let mut leaves = Vec::new();
463        cur.random_leaves(&mut leaves)?;
464        leaves.iter().map(|l| self.declare_leaf(l)).collect()
465    }
466
467    /// Declares element `idx` (declared indices, outermost dimension first) of
468    /// the `rand` or `randc` unpacked-array property `array` as a random
469    /// variable and returns its id (idempotent). It is named `a[2]` or `m[1][2]`.
470    pub fn declare_rand_element(
471        &mut self,
472        array: Symbol<'_>,
473        idx: &[i64],
474    ) -> Result<u32, BuildError> {
475        let mut cur = Cursor::of(array)?;
476        for &i in idx {
477            cur = cur.index(i)?;
478        }
479        if is_aggregate_type(cur.ty) {
480            return Err(BuildError::OutOfRangeSelect {
481                what: format!(
482                    "`{}` is still an aggregate: name one index per unpacked dimension",
483                    cur.name
484                ),
485            });
486        }
487        if cur.mode == RandMode::None {
488            return Err(BuildError::NotRand { name: cur.name });
489        }
490        if idx.is_empty() {
491            return Err(BuildError::OutOfRangeSelect {
492                what: format!(
493                    "`{}` is not an array element (use `declare_rand`)",
494                    cur.name
495                ),
496            });
497        }
498        let leaf = cur.leaf()?;
499        match self.declare_leaf(&leaf)? {
500            Declared::Int(id) => Ok(id),
501            Declared::Real(_) => Err(BuildError::RealConstraint {
502                what: format!(
503                    "`{}` is real: it is a real random variable, not an integral element",
504                    leaf.name
505                ),
506            }),
507        }
508    }
509
510    /// Declares the random leaf `leaf` (idempotent).
511    fn declare_leaf(&mut self, leaf: &LeafRef<'_>) -> Result<Declared, BuildError> {
512        let key = (leaf.root.clone(), leaf.path.clone());
513        match leaf.kind {
514            LeafKind::Int(_) => {
515                if let Some(&id) = self.elem_ix.get(&key) {
516                    return Ok(Declared::Int(id));
517                }
518                let id = self.new_rand_var(
519                    leaf.root.clone(),
520                    leaf.name.clone(),
521                    leaf.vt,
522                    leaf.randc(),
523                    leaf.path.clone(),
524                    Some(leaf.window),
525                )?;
526                self.elem_ix.insert(key, id);
527                Ok(Declared::Int(id))
528            }
529            LeafKind::Real(kind) => {
530                if let Some(&id) = self.real_leaf_ix.get(&key) {
531                    return Ok(Declared::Real(id));
532                }
533                if leaf.randc() {
534                    return Err(BuildError::RealConstraint {
535                        what: format!(
536                            "`randc` variable `{}` is real; `randc` is for integral types only \
537                             (IEEE 1800-2023 18.4.2)",
538                            leaf.name
539                        ),
540                    });
541                }
542                let id = self.real_vars.len() as u32;
543                self.real_vars.push(RealVarInfo {
544                    symbol: leaf.root.clone(),
545                    name: leaf.name.clone(),
546                    kind,
547                    window: Some(leaf.window),
548                });
549                self.real_leaf_ix.insert(key, id);
550                Ok(Declared::Real(id))
551            }
552            LeafKind::Vif | LeafKind::Skipped | LeafKind::Other => Err(missing(format!(
553                "a randomizable value for `{}` of type `{}`",
554                leaf.name,
555                leaf.vt.to_sv_string()
556            ))),
557        }
558    }
559
560    /// Follows `chain` to its leaf, or to the run-time choice among leaves an
561    /// index that is not a constant makes.
562    fn resolve_chain<'d>(&mut self, chain: &Chain<'d>) -> Result<Sel<'d>, BuildError> {
563        let cur = self.cursor_of(chain.root)?;
564        self.walk(cur, &chain.steps)
565    }
566
567    fn walk<'d>(&mut self, cur: Cursor<'d>, steps: &[RawStep<'d>]) -> Result<Sel<'d>, BuildError> {
568        let Some((first, rest)) = steps.split_first() else {
569            return Ok(Sel::Leaf(cur.leaf()?));
570        };
571        match *first {
572            RawStep::Field(i) => self.walk(cur.field(i)?, rest),
573            RawStep::Const(i) => self.walk(cur.index(i)?, rest),
574            RawStep::Index(e) => {
575                let ix = self.expr(e)?;
576                if let Some(i) = Self::constant_index(&ix) {
577                    let (lo, hi, _) = cur.dim()?;
578                    if (lo..=hi).contains(&i) {
579                        return self.walk(cur.index(i)?, rest);
580                    }
581                }
582                self.not_in_function(
583                    "an element of an unpacked array property at a computed index",
584                )?;
585                self.arrays_allowed("an element of an unpacked array property")?;
586                let mut has_var = false;
587                ix.for_each_var(&mut |_, _| has_var = true);
588                if has_var {
589                    return Err(BuildError::BadArray {
590                        what: format!(
591                            "a random variable in the index of the array `{}`: an index is a \
592                             constant, a state variable, a loop variable or the size (IEEE \
593                             1800-2023 18.5.7.1)",
594                            cur.name
595                        ),
596                    });
597                }
598                // Every element is a candidate; the registry keeps the one the
599                // index names (or reports an index outside the array if the
600                // constraint is live).
601                let (lo, hi, _) = cur.dim()?;
602                let mut elems = Vec::new();
603                for i in lo..=hi {
604                    elems.push(self.walk(cur.index(i)?, rest)?);
605                }
606                Ok(Sel::Pick {
607                    index: ix,
608                    low: lo,
609                    elems,
610                })
611            }
612        }
613    }
614
615    /// The value of `ix` if it involves no variable, state or array node.
616    pub(super) fn constant_index(ix: &Expr) -> Option<i64> {
617        if ix.any_node(&mut |n| matches!(n, Expr::Var(..) | Expr::State(..) | Expr::Array(_))) {
618            return None;
619        }
620        let v = eval(ix, &Assignment::default()).ok()?;
621        const_i64(ix.ty(), &v)
622    }
623
624    /// The leaf `chain` names, and its id if it is a declared random variable;
625    /// `what` names the construct for a run-time index.
626    pub(super) fn declared_leaf<'d>(
627        &mut self,
628        chain: Chain<'d>,
629        what: &str,
630    ) -> Result<(LeafRef<'d>, Option<Declared>), BuildError> {
631        let Sel::Leaf(leaf) = self.resolve_chain(&chain)? else {
632            return Err(unsupported(
633                format!("{what} that selects an element at a computed index"),
634                "C14",
635            ));
636        };
637        let key = (leaf.root.clone(), leaf.path.clone());
638        let declared = match leaf.kind {
639            LeafKind::Int(_) => self.elem_ix.get(&key).map(|&id| Declared::Int(id)),
640            LeafKind::Real(_) => self.real_leaf_ix.get(&key).map(|&id| Declared::Real(id)),
641            LeafKind::Vif | LeafKind::Skipped | LeafKind::Other => {
642                return Err(BuildError::NonIntegral {
643                    what: format!("`{}` of type `{}`", leaf.name, leaf.vt.to_sv_string()),
644                });
645            }
646        };
647        Ok((leaf, declared))
648    }
649
650    /// The error for a random leaf nobody declared.
651    fn undeclared(leaf: &LeafRef<'_>) -> BuildError {
652        unsupported(
653            format!(
654                "the random variable `{}`, a leaf of an aggregate property that was not declared \
655                 random (`declare_rand_aggregate`)",
656                leaf.name
657            ),
658            "C14",
659        )
660    }
661
662    /// The state variable of the non-random leaf `leaf`, registered on first use.
663    fn leaf_state(&mut self, leaf: &LeafRef<'_>, ty: Ty) -> Expr {
664        let key = (leaf.root.clone(), leaf.path.clone());
665        if let Some(&id) = self.leaf_state_ix.get(&key) {
666            return Expr::State(id, self.states[id as usize].ty);
667        }
668        let id = self.states.len() as u32;
669        self.states.push(StateInfo {
670            root_bit: 0,
671            symbol: leaf.root.clone(),
672            name: leaf.name.clone(),
673            ty,
674            via: Vec::new(),
675            window: Some(leaf.window),
676            class_of: false,
677        });
678        self.leaf_state_ix.insert(key, id);
679        Expr::State(id, ty)
680    }
681
682    /// The expression for one integral leaf: its random variable, or its state.
683    fn leaf_expr(&mut self, leaf: &LeafRef<'_>) -> Result<Expr, BuildError> {
684        let LeafKind::Int(ty) = leaf.kind else {
685            return Err(BuildError::NonIntegral {
686                what: format!("`{}` of type `{}`", leaf.name, leaf.vt.to_sv_string()),
687            });
688        };
689        if leaf.mode == RandMode::None {
690            return Ok(self.leaf_state(leaf, ty));
691        }
692        let key = (leaf.root.clone(), leaf.path.clone());
693        let &id = self
694            .elem_ix
695            .get(&key)
696            .ok_or_else(|| Self::undeclared(leaf))?;
697        if self.frame.is_some() {
698            return Err(self.rand_read_in_function(&leaf.name));
699        }
700        Ok(Expr::Var(id, self.vars[id as usize].ty))
701    }
702
703    fn sel_expr(&mut self, sel: Sel<'_>, ty: Ty) -> Result<Expr, BuildError> {
704        match sel {
705            Sel::Leaf(leaf) => {
706                let x = self.leaf_expr(&leaf)?;
707                Ok(if x.ty() == ty {
708                    x
709                } else {
710                    Expr::Resize(Box::new(x), ty)
711                })
712            }
713            Sel::Pick { index, low, elems } => {
714                let elems = elems
715                    .into_iter()
716                    .map(|s| self.sel_expr(s, ty))
717                    .collect::<Result<Vec<_>, _>>()?;
718                Ok(Expr::Array(ArrayNode::Pick {
719                    index: Box::new(index),
720                    low,
721                    elems,
722                    ty,
723                }))
724            }
725        }
726    }
727
728    /// The integral value `chain` names, of type `ty`.
729    pub(super) fn aggregate_value(&mut self, chain: Chain<'_>, ty: Ty) -> Result<Expr, BuildError> {
730        let sel = self.resolve_chain(&chain)?;
731        self.sel_expr(sel, ty)
732    }
733
734    /// The bit-stream (LRM 6.24.3, 11.4.14) of the aggregate property `chain`
735    /// names: its integral leaves concatenated, the first struct member or the
736    /// array element at the left bound the most significant. A real, string or
737    /// handle leaf is an error (no bit pattern is solved for it).
738    pub(super) fn aggregate_bitstream(&mut self, chain: Chain<'_>) -> Result<Expr, BuildError> {
739        let mut parts = Vec::new();
740        let mut cur = self.cursor_of(chain.root)?;
741        // Walk the chain's own steps first (constant steps only: a computed
742        // index would pick among leaves, which a stream cannot).
743        let mut rest: &[RawStep<'_>] = &chain.steps;
744        while let Some((first, tail)) = rest.split_first() {
745            cur = match *first {
746                RawStep::Field(i) => cur.field(i)?,
747                RawStep::Const(i) => cur.index(i)?,
748                RawStep::Index(e) => {
749                    let ix = self.expr(e)?;
750                    let i = Self::constant_index(&ix).ok_or_else(|| {
751                        unsupported("a bit-stream cast of an element at a computed index", "C2")
752                    })?;
753                    cur.index(i)?
754                }
755            };
756            rest = tail;
757        }
758        self.stream_leaves(cur, &mut parts)?;
759        let w = parts.iter().map(|p| p.ty().width).sum();
760        Ok(Expr::Concat(parts, Ty::unsigned(w)))
761    }
762
763    fn stream_leaves(&mut self, cur: Cursor<'_>, out: &mut Vec<Expr>) -> Result<(), BuildError> {
764        let t = cur.ty.canonical();
765        if t.is_unpacked_struct() {
766            for i in 0..t.fields().count() {
767                self.stream_leaves(cur.field(i as u32)?, out)?;
768            }
769        } else if array_shape(t) == Some(ArrayShape::Fixed) {
770            let r = t.fixed_unpacked_array_range();
771            let (l, h) = (i64::from(r.left), i64::from(r.right));
772            let idx: Vec<i64> = if l <= h {
773                (l..=h).collect()
774            } else {
775                (h..=l).rev().collect()
776            };
777            for i in idx {
778                self.stream_leaves(cur.index(i)?, out)?;
779            }
780        } else {
781            let leaf = cur.leaf()?;
782            let LeafKind::Int(ty) = leaf.kind else {
783                return Err(BuildError::NonIntegral {
784                    what: format!(
785                        "`{}` of type `{}` in a bit-stream cast",
786                        leaf.name,
787                        leaf.vt.to_sv_string()
788                    ),
789                });
790            };
791            let x = self.leaf_expr(&leaf)?;
792            out.push(Expr::Resize(Box::new(x), Ty::unsigned(ty.width)));
793        }
794        Ok(())
795    }
796
797    /// Element `i` (a declared index) of the fixed-size array `arr` names, of
798    /// type `ty`.
799    pub(super) fn aggregate_element(
800        &mut self,
801        arr: Expression<'_>,
802        i: i64,
803        ty: Ty,
804    ) -> Result<Expr, BuildError> {
805        let mut chain = aggregate_chain(arr, &self.forced_roots).ok_or_else(|| {
806            unsupported(
807                "a method over an unpacked array that is not a property (or a member of a struct \
808                 property) of this class",
809                "C14",
810            )
811        })?;
812        chain.steps.push(RawStep::Const(i));
813        self.aggregate_value(chain, ty)
814    }
815
816    /// The real value `chain` names, of grid type `kind`.
817    pub(super) fn real_aggregate(
818        &mut self,
819        chain: Chain<'_>,
820        kind: Fk,
821    ) -> Result<RealExpr, BuildError> {
822        let leaf = match self.resolve_chain(&chain)? {
823            Sel::Leaf(leaf) => leaf,
824            Sel::Pick { .. } => {
825                return Err(BuildError::RealConstraint {
826                    what: "a real element of an array at a computed index (only an integral \
827                           element can be chosen by a state or loop variable)"
828                        .into(),
829                });
830            }
831        };
832        if !matches!(leaf.kind, LeafKind::Real(_)) {
833            return Err(missing(format!("a real value for `{}`", leaf.name)));
834        }
835        if leaf.mode == RandMode::None {
836            let st = self.leaf_state(&leaf, state_ty(kind));
837            return self.real_state(st, kind);
838        }
839        let key = (leaf.root.clone(), leaf.path.clone());
840        let &id = self
841            .real_leaf_ix
842            .get(&key)
843            .ok_or_else(|| Self::undeclared(&leaf))?;
844        Ok(RealExpr::Var(id))
845    }
846}