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        info.window = Some(leaf.window);
542        let id = self.arrays.len() as u32;
543        self.arrays.push(info);
544        Ok(id)
545    }
546
547    /// Declares the random leaf `leaf` (idempotent).
548    fn declare_leaf(&mut self, leaf: &LeafRef<'_>) -> Result<Declared, BuildError> {
549        let key = (leaf.root.clone(), leaf.path.clone());
550        match leaf.kind {
551            LeafKind::Int(_) => {
552                if let Some(&id) = self.elem_ix.get(&key) {
553                    return Ok(Declared::Int(id));
554                }
555                let id = self.new_rand_var(
556                    leaf.root.clone(),
557                    leaf.name.clone(),
558                    leaf.vt,
559                    leaf.randc(),
560                    leaf.path.clone(),
561                    Some(leaf.window),
562                )?;
563                self.elem_ix.insert(key, id);
564                Ok(Declared::Int(id))
565            }
566            LeafKind::Real(kind) => {
567                if let Some(&id) = self.real_leaf_ix.get(&key) {
568                    return Ok(Declared::Real(id));
569                }
570                if leaf.randc() {
571                    return Err(BuildError::RealConstraint {
572                        what: format!(
573                            "`randc` variable `{}` is real; `randc` is for integral types only \
574                             (IEEE 1800-2023 18.4.2)",
575                            leaf.name
576                        ),
577                    });
578                }
579                let id = self.real_vars.len() as u32;
580                self.real_vars.push(RealVarInfo {
581                    symbol: leaf.root.clone(),
582                    name: leaf.name.clone(),
583                    kind,
584                    window: Some(leaf.window),
585                });
586                self.real_leaf_ix.insert(key, id);
587                Ok(Declared::Real(id))
588            }
589            LeafKind::Collection => {
590                let key = leaf.root.derive(&Self::leaf_tag(&leaf.path));
591                if let Some(&id) = self.array_ix.get(&key) {
592                    return Ok(Declared::Array(id));
593                }
594                if leaf.randc() {
595                    return Err(unsupported(
596                        format!("the `randc` array `{}`: per-element cycling", leaf.name),
597                        "C14",
598                    ));
599                }
600                let id = self.new_leaf_array(leaf, true)?;
601                self.array_ix.insert(key, id);
602                Ok(Declared::Array(id))
603            }
604            LeafKind::Vif | LeafKind::Skipped | LeafKind::Other => Err(missing(format!(
605                "a randomizable value for `{}` of type `{}`",
606                leaf.name,
607                leaf.vt.to_sv_string()
608            ))),
609        }
610    }
611
612    /// Follows `chain` to its leaf, or to the run-time choice among leaves an
613    /// index that is not a constant makes.
614    pub(super) fn resolve_chain<'d>(&mut self, chain: &Chain<'d>) -> Result<Sel<'d>, BuildError> {
615        let cur = self.cursor_of(chain.root)?;
616        self.walk(cur, &chain.steps)
617    }
618
619    fn walk<'d>(&mut self, cur: Cursor<'d>, steps: &[RawStep<'d>]) -> Result<Sel<'d>, BuildError> {
620        let Some((first, rest)) = steps.split_first() else {
621            return Ok(Sel::Leaf(cur.leaf()?));
622        };
623        match *first {
624            RawStep::Field(i) => self.walk(cur.field(i)?, rest),
625            RawStep::Const(i) => self.walk(cur.index(i)?, rest),
626            RawStep::Index(e) => {
627                let ix = self.expr(e)?;
628                if let Some(i) = Self::constant_index(&ix) {
629                    let (lo, hi, _) = cur.dim()?;
630                    if (lo..=hi).contains(&i) {
631                        return self.walk(cur.index(i)?, rest);
632                    }
633                }
634                self.not_in_function(
635                    "an element of an unpacked array property at a computed index",
636                )?;
637                self.arrays_allowed("an element of an unpacked array property")?;
638                let mut has_var = false;
639                ix.for_each_var(&mut |_, _| has_var = true);
640                if has_var {
641                    return Err(BuildError::BadArray {
642                        what: format!(
643                            "a random variable in the index of the array `{}`: an index is a \
644                             constant, a state variable, a loop variable or the size (IEEE \
645                             1800-2023 18.5.7.1)",
646                            cur.name
647                        ),
648                    });
649                }
650                // Every element is a candidate; the registry keeps the one the
651                // index names (or reports an index outside the array if the
652                // constraint is live).
653                let (lo, hi, _) = cur.dim()?;
654                let mut elems = Vec::new();
655                for i in lo..=hi {
656                    elems.push(self.walk(cur.index(i)?, rest)?);
657                }
658                Ok(Sel::Pick {
659                    index: ix,
660                    low: lo,
661                    elems,
662                })
663            }
664        }
665    }
666
667    /// The value of `ix` if it involves no variable, state or array node.
668    pub(super) fn constant_index(ix: &Expr) -> Option<i64> {
669        if ix.any_node(&mut |n| matches!(n, Expr::Var(..) | Expr::State(..) | Expr::Array(_))) {
670            return None;
671        }
672        let v = eval(ix, &Assignment::default()).ok()?;
673        const_i64(ix.ty(), &v)
674    }
675
676    /// The leaf `chain` names, and its id if it is a declared random variable;
677    /// `what` names the construct for a run-time index.
678    pub(super) fn declared_leaf<'d>(
679        &mut self,
680        chain: Chain<'d>,
681        what: &str,
682    ) -> Result<(LeafRef<'d>, Option<Declared>), BuildError> {
683        let Sel::Leaf(leaf) = self.resolve_chain(&chain)? else {
684            return Err(unsupported(
685                format!("{what} that selects an element at a computed index"),
686                "C14",
687            ));
688        };
689        let key = (leaf.root.clone(), leaf.path.clone());
690        let declared = match leaf.kind {
691            LeafKind::Int(_) => self.elem_ix.get(&key).map(|&id| Declared::Int(id)),
692            LeafKind::Real(_) => self.real_leaf_ix.get(&key).map(|&id| Declared::Real(id)),
693            LeafKind::Vif | LeafKind::Skipped | LeafKind::Collection | LeafKind::Other => {
694                return Err(BuildError::NonIntegral {
695                    what: format!("`{}` of type `{}`", leaf.name, leaf.vt.to_sv_string()),
696                });
697            }
698        };
699        Ok((leaf, declared))
700    }
701
702    /// The error for a random leaf nobody declared.
703    fn undeclared(leaf: &LeafRef<'_>) -> BuildError {
704        unsupported(
705            format!(
706                "the random variable `{}`, a leaf of an aggregate property that was not declared \
707                 random (`declare_rand_aggregate`)",
708                leaf.name
709            ),
710            "C14",
711        )
712    }
713
714    /// The state variable of the non-random leaf `leaf`, registered on first use.
715    fn leaf_state(&mut self, leaf: &LeafRef<'_>, ty: Ty) -> Expr {
716        let key = (leaf.root.clone(), leaf.path.clone());
717        if let Some(&id) = self.leaf_state_ix.get(&key) {
718            return Expr::State(id, self.states[id as usize].ty);
719        }
720        let id = self.states.len() as u32;
721        self.states.push(StateInfo {
722            root_bit: 0,
723            symbol: leaf.root.clone(),
724            name: leaf.name.clone(),
725            ty,
726            via: Vec::new(),
727            window: Some(leaf.window),
728            class_of: false,
729        });
730        self.leaf_state_ix.insert(key, id);
731        Expr::State(id, ty)
732    }
733
734    /// The expression for one integral leaf: its random variable, or its state.
735    fn leaf_expr(&mut self, leaf: &LeafRef<'_>) -> Result<Expr, BuildError> {
736        let LeafKind::Int(ty) = leaf.kind else {
737            return Err(BuildError::NonIntegral {
738                what: format!("`{}` of type `{}`", leaf.name, leaf.vt.to_sv_string()),
739            });
740        };
741        if leaf.mode == RandMode::None {
742            return Ok(self.leaf_state(leaf, ty));
743        }
744        let key = (leaf.root.clone(), leaf.path.clone());
745        let &id = self
746            .elem_ix
747            .get(&key)
748            .ok_or_else(|| Self::undeclared(leaf))?;
749        if self.frame.is_some() {
750            return Err(self.rand_read_in_function(&leaf.name));
751        }
752        Ok(Expr::Var(id, self.vars[id as usize].ty))
753    }
754
755    fn sel_expr(&mut self, sel: Sel<'_>, ty: Ty) -> Result<Expr, BuildError> {
756        match sel {
757            Sel::Leaf(leaf) => {
758                let x = self.leaf_expr(&leaf)?;
759                Ok(if x.ty() == ty {
760                    x
761                } else {
762                    Expr::Resize(Box::new(x), ty)
763                })
764            }
765            Sel::Pick { index, low, elems } => {
766                let elems = elems
767                    .into_iter()
768                    .map(|s| self.sel_expr(s, ty))
769                    .collect::<Result<Vec<_>, _>>()?;
770                Ok(Expr::Array(ArrayNode::Pick {
771                    index: Box::new(index),
772                    low,
773                    elems,
774                    ty,
775                }))
776            }
777        }
778    }
779
780    /// The integral value `chain` names, of type `ty`.
781    pub(super) fn aggregate_value(&mut self, chain: Chain<'_>, ty: Ty) -> Result<Expr, BuildError> {
782        let sel = self.resolve_chain(&chain)?;
783        self.sel_expr(sel, ty)
784    }
785
786    /// The bit-stream (LRM 6.24.3, 11.4.14) of the aggregate property `chain`
787    /// names: its integral leaves concatenated, the first struct member or the
788    /// array element at the left bound the most significant. A real, string or
789    /// handle leaf is an error (no bit pattern is solved for it).
790    pub(super) fn aggregate_bitstream(&mut self, chain: Chain<'_>) -> Result<Expr, BuildError> {
791        let mut parts = Vec::new();
792        let mut cur = self.cursor_of(chain.root)?;
793        // Walk the chain's own steps first (constant steps only: a computed
794        // index would pick among leaves, which a stream cannot).
795        let mut rest: &[RawStep<'_>] = &chain.steps;
796        while let Some((first, tail)) = rest.split_first() {
797            cur = match *first {
798                RawStep::Field(i) => cur.field(i)?,
799                RawStep::Const(i) => cur.index(i)?,
800                RawStep::Index(e) => {
801                    let ix = self.expr(e)?;
802                    let i = Self::constant_index(&ix).ok_or_else(|| {
803                        unsupported("a bit-stream cast of an element at a computed index", "C2")
804                    })?;
805                    cur.index(i)?
806                }
807            };
808            rest = tail;
809        }
810        self.stream_leaves(cur, &mut parts)?;
811        let w = parts.iter().map(|p| p.ty().width).sum();
812        Ok(Expr::Concat(parts, Ty::unsigned(w)))
813    }
814
815    fn stream_leaves(&mut self, cur: Cursor<'_>, out: &mut Vec<Expr>) -> Result<(), BuildError> {
816        let t = cur.ty.canonical();
817        if t.is_unpacked_struct() {
818            for i in 0..t.fields().count() {
819                self.stream_leaves(cur.field(i as u32)?, out)?;
820            }
821        } else if array_shape(t) == Some(ArrayShape::Fixed) {
822            let r = t.fixed_unpacked_array_range();
823            let (l, h) = (i64::from(r.left), i64::from(r.right));
824            let idx: Vec<i64> = if l <= h {
825                (l..=h).collect()
826            } else {
827                (h..=l).rev().collect()
828            };
829            for i in idx {
830                self.stream_leaves(cur.index(i)?, out)?;
831            }
832        } else {
833            let leaf = cur.leaf()?;
834            let LeafKind::Int(ty) = leaf.kind else {
835                return Err(BuildError::NonIntegral {
836                    what: format!(
837                        "`{}` of type `{}` in a bit-stream cast",
838                        leaf.name,
839                        leaf.vt.to_sv_string()
840                    ),
841                });
842            };
843            let x = self.leaf_expr(&leaf)?;
844            out.push(Expr::Resize(Box::new(x), Ty::unsigned(ty.width)));
845        }
846        Ok(())
847    }
848
849    /// Element `i` (a declared index) of the fixed-size array `arr` names, of
850    /// type `ty`.
851    pub(super) fn aggregate_element(
852        &mut self,
853        arr: Expression<'_>,
854        i: i64,
855        ty: Ty,
856    ) -> Result<Expr, BuildError> {
857        let mut chain = aggregate_chain(arr, &self.forced_roots).ok_or_else(|| {
858            unsupported(
859                "a method over an unpacked array that is not a property (or a member of a struct \
860                 property) of this class",
861                "C14",
862            )
863        })?;
864        chain.steps.push(RawStep::Const(i));
865        self.aggregate_value(chain, ty)
866    }
867
868    /// The real value `chain` names, of grid type `kind`.
869    pub(super) fn real_aggregate(
870        &mut self,
871        chain: Chain<'_>,
872        kind: Fk,
873    ) -> Result<RealExpr, BuildError> {
874        let leaf = match self.resolve_chain(&chain)? {
875            Sel::Leaf(leaf) => leaf,
876            Sel::Pick { .. } => {
877                return Err(BuildError::RealConstraint {
878                    what: "a real element of an array at a computed index (only an integral \
879                           element can be chosen by a state or loop variable)"
880                        .into(),
881                });
882            }
883        };
884        if !matches!(leaf.kind, LeafKind::Real(_)) {
885            return Err(missing(format!("a real value for `{}`", leaf.name)));
886        }
887        if leaf.mode == RandMode::None {
888            let st = self.leaf_state(&leaf, state_ty(kind));
889            return self.real_state(st, kind);
890        }
891        let key = (leaf.root.clone(), leaf.path.clone());
892        let &id = self
893            .real_leaf_ix
894            .get(&key)
895            .ok_or_else(|| Self::undeclared(&leaf))?;
896        Ok(RealExpr::Var(id))
897    }
898}