Skip to main content

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