1use super::aggregate::{RawStep, Sel, aggregate_chain};
43use super::real::strip_conversions;
44use super::*;
45use crate::constraints::ir::{ArrayId, ArrayNode, LoopId, ReduceOp};
46use sv_lang::KnownSystemName;
47use sv_lang::kinds::SymbolKind;
48use sv_lang::nodes::LoopConstraintSyntax;
49
50#[derive(Clone, Debug, PartialEq, Eq)]
52pub struct ArrayInfo {
53 pub symbol: SymbolId,
55 pub name: String,
57 pub elem_ty: Ty,
59 pub rand: bool,
63 pub elem_domain: Option<Vec<BigUint>>,
66 pub assoc: bool,
69 pub nested: bool,
73 pub template_of: Option<ArrayId>,
77 pub inner_fixed: u32,
81 pub row_depth: u32,
86 pub window: Option<Window>,
89}
90
91pub(super) fn expr_shape(e: Expression<'_>) -> Option<ArrayShape> {
95 let shape = array_shape(e.expr_type()?);
96 if shape == Some(ArrayShape::Fixed) {
97 let inner = strip_conversions(e);
98 if inner.kind() == ExpressionKind::ElementSelect
99 && inner
100 .select_value()
101 .and_then(|v| v.expr_type())
102 .and_then(array_shape)
103 == Some(ArrayShape::Collection)
104 {
105 return Some(ArrayShape::Collection);
106 }
107 }
108 shape
109}
110
111pub(super) type ArrayRef = (ArrayId, Vec<(ArrayId, Expr)>);
114
115#[derive(Clone, Debug)]
117pub(super) enum LoopBind {
118 Runtime(LoopId, Ty),
120 Const(i64),
122 Elem(Expr),
125}
126
127#[derive(Clone, Debug)]
129pub(super) struct LoopScope {
130 name: String,
131 value: LoopBind,
134 index: Option<LoopBind>,
136}
137
138#[derive(Clone, Copy, Debug, PartialEq, Eq)]
140pub(super) enum ArrayShape {
141 Collection,
143 Assoc,
145 Fixed,
147}
148
149pub(super) fn array_shape(t: Type<'_>) -> Option<ArrayShape> {
150 let c = t.canonical();
151 if c.is_associative_array() {
152 Some(ArrayShape::Assoc)
153 } else if c.is_dynamically_sized_array() || c.is_queue() {
154 Some(ArrayShape::Collection)
155 } else if c.is_unpacked_array() {
156 Some(ArrayShape::Fixed)
157 } else {
158 None
159 }
160}
161
162fn bad_array(what: impl Into<String>) -> BuildError {
163 BuildError::BadArray { what: what.into() }
164}
165
166pub(super) fn const_i64(ty: Ty, v: &BigUint) -> Option<i64> {
168 let signed = BigInt::from(v.clone());
169 let signed = if ty.signed && v.bit(u64::from(ty.width) - 1) {
170 signed - (BigInt::one() << ty.width)
171 } else {
172 signed
173 };
174 i64::try_from(signed).ok()
175}
176
177impl Builder<'_> {
178 pub(super) fn arrays_allowed(&self, what: &str) -> Result<(), BuildError> {
179 match self.no_arrays {
180 None => Ok(()),
181 Some(inside) => Err(unsupported(
182 format!("{what} inside {inside}"),
183 "C14 (array-level values have no weight or bound at build time)",
184 )),
185 }
186 }
187
188 pub fn declare_rand_array(&mut self, sym: Symbol<'_>) -> Result<u32, BuildError> {
192 let uid = sym.uid();
193 let name = sym.name().to_string();
194 match sym.rand_mode() {
195 RandMode::None => return Err(BuildError::NotRand { name }),
196 RandMode::RandC => {
197 return Err(unsupported(
198 format!("the `randc` array `{name}`: per-element cycling"),
199 "C14",
200 ));
201 }
202 RandMode::Rand => {}
203 }
204 if let Some(&id) = self.array_ix.get(&uid) {
205 self.arrays[id as usize].rand = true;
206 return Ok(id);
207 }
208 let vt = sym.value_type().ok_or_else(|| BuildError::Missing {
209 what: format!("value type for `{name}`"),
210 })?;
211 let info = self.array_info(uid.clone(), name, vt, true)?;
212 let id = self.arrays.len() as u32;
213 self.arrays.push(info);
214 self.array_ix.insert(uid, id);
215 Ok(id)
216 }
217
218 pub fn declare_array_forced(&mut self, sym: Symbol<'_>) -> Result<u32, BuildError> {
223 let uid = sym.uid();
224 let name = sym.name().to_string();
225 if matches!(sym.rand_mode(), RandMode::RandC) {
226 return Err(unsupported(
227 format!("the `randc` array `{name}`: per-element cycling"),
228 "C14",
229 ));
230 }
231 if let Some(&id) = self.array_ix.get(&uid) {
232 self.arrays[id as usize].rand = true;
233 return Ok(id);
234 }
235 let vt = sym.value_type().ok_or_else(|| BuildError::Missing {
236 what: format!("value type for `{name}`"),
237 })?;
238 let info = self.array_info(uid.clone(), name, vt, true)?;
239 let id = self.arrays.len() as u32;
240 self.arrays.push(info);
241 self.array_ix.insert(uid, id);
242 Ok(id)
243 }
244
245 pub(super) fn array_info(
246 &self,
247 symbol: SymbolId,
248 name: String,
249 vt: Type<'_>,
250 rand: bool,
251 ) -> Result<ArrayInfo, BuildError> {
252 match array_shape(vt) {
253 Some(ArrayShape::Collection) => {}
254 Some(ArrayShape::Assoc) if rand => {}
258 Some(ArrayShape::Assoc) => return Err(Self::assoc_operand(&name)),
259 Some(ArrayShape::Fixed) => {
263 return Err(unsupported(
264 format!(
265 "the fixed-size unpacked array `{name}` as a whole-array constraint \
266 operand (name its elements, or use a reduction method or `foreach`)"
267 ),
268 "C14",
269 ));
270 }
271 None => {
272 return Err(BuildError::NonIntegral {
273 what: format!(
274 "`{name}` of type `{}`, which is not an array",
275 vt.to_sv_string()
276 ),
277 });
278 }
279 }
280 let et = vt
281 .canonical()
282 .element_type()
283 .ok_or_else(|| BuildError::Missing {
284 what: format!("element type of the array `{name}`"),
285 })?;
286 let mut row_depth = 0u32;
291 let mut inner_fixed = 0u32;
292 let mut leaf = et;
293 if array_shape(vt) == Some(ArrayShape::Collection) {
294 let mut cur = vt;
295 loop {
296 let elem = cur
297 .canonical()
298 .element_type()
299 .ok_or_else(|| BuildError::Missing {
300 what: format!("element type of the array `{name}`"),
301 })?;
302 match array_shape(elem) {
303 None => {
304 leaf = elem;
305 break;
306 }
307 Some(ArrayShape::Collection) => {
308 row_depth += 1;
309 cur = elem;
310 }
311 Some(ArrayShape::Fixed) => {
312 let row = elem.canonical().element_type();
313 let Some(r) = row.filter(|r| array_shape(*r).is_none()) else {
314 return Err(unsupported(
315 format!(
316 "`{name}`: a fixed-size row whose elements are arrays (a \
317 fixed-size dimension in the middle of an array of arrays)"
318 ),
319 "C14",
320 ));
321 };
322 let range = elem.canonical().fixed_unpacked_array_range();
326 let (lo, hi) = (
327 i64::from(range.left).min(i64::from(range.right)),
328 i64::from(range.left).max(i64::from(range.right)),
329 );
330 if lo != 0 {
331 return Err(unsupported(
332 format!(
333 "`{name}`: a fixed-size row whose lowest index is not 0 \
334 (`[{}:{}]`)",
335 range.left, range.right
336 ),
337 "C14",
338 ));
339 }
340 row_depth += 1;
341 inner_fixed = (hi + 1) as u32;
342 leaf = r;
343 break;
344 }
345 Some(ArrayShape::Assoc) => {
346 return Err(unsupported(
347 format!(
348 "`{name}`: a dynamic array or queue whose elements are \
349 fixed-size or associative arrays"
350 ),
351 "C14",
352 ));
353 }
354 }
355 }
356 }
357 let nested = row_depth > 0;
358 let elem_ty = ty_of(Some(leaf), &format!("an element of the array `{name}`"))?;
359 let elem_domain = Self::enum_values(leaf, elem_ty)?;
360 Ok(ArrayInfo {
361 symbol,
362 name,
363 elem_ty,
364 rand,
365 elem_domain,
366 assoc: array_shape(vt) == Some(ArrayShape::Assoc),
367 nested,
368 template_of: None,
369 inner_fixed,
370 row_depth,
371 window: None,
372 })
373 }
374
375 fn assoc_operand(name: &str) -> BuildError {
376 bad_array(format!(
377 "`{name}` is an associative array: its size and indices are not randomizable and \
378 its elements are not constraint operands (IEEE 1800-2023 18.4)"
379 ))
380 }
381
382 pub(super) fn array_of(&mut self, e: Expression<'_>) -> Result<ArrayId, BuildError> {
386 match self.array_ref(e)? {
387 (id, rows) if rows.is_empty() && self.arrays[id as usize].nested => Err(unsupported(
388 format!(
389 "the array of arrays `{}` as a whole-array operand here (supported: `.size`, \
390 `foreach`, and its rows' sizes, elements and reductions)",
391 self.arrays[id as usize].name
392 ),
393 "C14",
394 )),
395 (id, rows) if rows.is_empty() => Ok(id),
396 (id, _) => Err(unsupported(
397 format!(
398 "a row of the array of arrays `{}` as a whole-array operand (name its \
399 elements `a[i][j]`, or use `.size`, a reduction method, `inside` or \
400 `foreach` on the row)",
401 self.arrays[self.arrays[id as usize].template_of.unwrap_or(id) as usize].name
402 ),
403 "C14",
404 )),
405 }
406 }
407
408 pub(super) fn row_template(&mut self, outer: ArrayId) -> ArrayId {
410 if let Some(&t) = self.templates.get(&outer) {
411 return t;
412 }
413 let o = &self.arrays[outer as usize];
414 let info = ArrayInfo {
415 symbol: o.symbol.clone(),
416 name: format!("{}[]", o.name),
417 elem_ty: o.elem_ty,
418 rand: false,
419 elem_domain: o.elem_domain.clone(),
420 assoc: false,
421 nested: o.row_depth > 1,
422 template_of: Some(outer),
423 inner_fixed: 0,
424 row_depth: o.row_depth.saturating_sub(1),
425 window: None,
426 };
427 let id = self.arrays.len() as ArrayId;
428 self.arrays.push(info);
429 self.templates.insert(outer, id);
430 id
431 }
432
433 pub(super) fn array_ref(&mut self, e: Expression<'_>) -> Result<ArrayRef, BuildError> {
438 let e = strip_conversions(e);
439 if e.kind() == ExpressionKind::ElementSelect
440 && let Some(id) = self.array_leaf(e)?
441 {
442 return Ok((id, Vec::new()));
443 }
444 if e.kind() == ExpressionKind::ElementSelect {
445 let base = e.select_value().ok_or_else(|| BuildError::Missing {
446 what: "base of a row select".into(),
447 })?;
448 let sel = e.selector().ok_or_else(|| BuildError::Missing {
449 what: "index of a row select".into(),
450 })?;
451 let (outer, mut chain) = self.array_ref(base)?;
452 if !self.arrays[outer as usize].nested {
453 return Err(unsupported(
454 format!(
455 "an element select `{}[..]` as an array operand: it is not an array of \
456 arrays",
457 self.arrays[outer as usize].name
458 ),
459 "C14",
460 ));
461 }
462 self.arrays_allowed("a row of an array of arrays")?;
463 let row = self.expr(sel)?;
464 let mut has_var = false;
465 row.for_each_var(&mut |_, _| has_var = true);
466 if has_var {
467 return Err(bad_array(format!(
468 "a random variable in the row index of the array `{}`: an index is a \
469 constant, a state variable, a loop variable or the size (IEEE 1800-2023 \
470 18.5.7.1)",
471 self.arrays[outer as usize].name
472 )));
473 }
474 let t = self.row_template(outer);
475 chain.push((t, row));
476 return Ok((t, chain));
477 }
478 Ok((self.array_named(e)?, Vec::new()))
479 }
480
481 fn reject_nested(&self, a: ArrayId, what: &str) -> Result<(), BuildError> {
482 if self.arrays[a as usize].nested {
483 return Err(unsupported(
484 format!(
485 "{what} over the array of arrays `{}` itself (name a row `a[i]`)",
486 self.arrays[a as usize].name
487 ),
488 "C14",
489 ));
490 }
491 Ok(())
492 }
493
494 fn row_scoped(chain: Vec<(ArrayId, Expr)>, body: Expr) -> Expr {
496 chain.into_iter().rev().fold(body, |body, (t, row)| {
497 Expr::Array(ArrayNode::RowScope {
498 template: t,
499 row: Box::new(row),
500 body: Box::new(body),
501 })
502 })
503 }
504
505 fn array_leaf(&mut self, e: Expression<'_>) -> Result<Option<ArrayId>, BuildError> {
510 if e.expr_type().and_then(array_shape) != Some(ArrayShape::Collection) {
511 return Ok(None);
512 }
513 let Some(chain) = aggregate_chain(e, &self.forced_roots) else {
514 return Ok(None);
515 };
516 if chain.steps.is_empty() {
517 return Ok(None);
518 }
519 self.array_from_chain(chain).map(Some)
520 }
521
522 fn array_from_chain(
524 &mut self,
525 chain: super::aggregate::Chain<'_>,
526 ) -> Result<ArrayId, BuildError> {
527 let leaf = match self.resolve_chain(&chain)? {
528 Sel::Leaf(leaf) => leaf,
529 Sel::Pick { .. } => {
530 return Err(unsupported(
531 "a dynamic array or queue element of a fixed-size array selected at a \
532 computed index (use a constant index or a `foreach` variable)",
533 "C14",
534 ));
535 }
536 };
537 let key = leaf.root.derive(&Self::leaf_tag(&leaf.path));
538 if let Some(&id) = self.array_ix.get(&key) {
539 return Ok(id);
540 }
541 if leaf.mode != RandMode::None {
542 return Err(unsupported(
543 format!(
544 "the random array `{}`, a leaf of an aggregate property that was not \
545 declared random",
546 leaf.name
547 ),
548 "C14",
549 ));
550 }
551 let id = self.new_leaf_array(&leaf, false)?;
552 self.array_ix.insert(key, id);
553 Ok(id)
554 }
555
556 fn array_named(&mut self, e: Expression<'_>) -> Result<ArrayId, BuildError> {
557 if let Some(id) = self.array_leaf(e)? {
558 return Ok(id);
559 }
560 let sym = match e.kind() {
561 ExpressionKind::NamedValue | ExpressionKind::HierarchicalValue => e.referenced_symbol(),
562 ExpressionKind::MemberAccess => {
563 let base = Self::operand(e, "a member access")?;
564 let is_this = base.kind() == ExpressionKind::NamedValue
565 && base.referenced_symbol().is_some_and(|s| s.name() == "this");
566 if !is_this {
567 return Err(BuildError::ForeignObjectAccess {
568 what: format!(
569 "the array `.{}` of another object",
570 e.member_symbol()
571 .map_or(String::new(), |m| m.name().to_string())
572 ),
573 });
574 }
575 e.member_symbol()
576 }
577 other => {
578 return Err(unsupported(
579 format!("a `{other:?}` expression as an array operand"),
580 "C14",
581 ));
582 }
583 };
584 let sym = sym.ok_or_else(|| BuildError::Missing {
585 what: "symbol of an array operand".into(),
586 })?;
587 let uid = sym.uid();
588 if let Some(&id) = self.array_ix.get(&uid) {
589 if self.arrays[id as usize].assoc {
590 return Err(Self::assoc_operand(&self.arrays[id as usize].name));
591 }
592 return Ok(id);
593 }
594 let vt = e.expr_type().ok_or_else(|| BuildError::Missing {
595 what: format!("type of the array `{}`", sym.name()),
596 })?;
597 let info = self.array_info(uid.clone(), sym.name().to_string(), vt, false)?;
598 let id = self.arrays.len() as u32;
599 self.arrays.push(info);
600 self.array_ix.insert(uid, id);
601 Ok(id)
602 }
603
604 pub(super) fn size_call_array<'d>(e: Expression<'d>) -> Option<Expression<'d>> {
607 let sub = e.system_subroutine()?;
608 if !matches!(
609 sub.known_name_id(),
610 KnownSystemName::Size | KnownSystemName::ArraySize | KnownSystemName::Num
611 ) {
612 return None;
613 }
614 let arr = *e.operands().first()?;
615 expr_shape(arr).map(|_| arr)
616 }
617
618 pub(super) fn array_size(&mut self, arr: Expression<'_>) -> Result<Expr, BuildError> {
620 self.arrays_allowed("an array size")?;
621 let (id, rows) = self.array_ref(arr)?;
622 Ok(Self::row_scoped(rows, Expr::Array(ArrayNode::Size(id))))
623 }
624
625 pub(super) fn array_element(
627 &mut self,
628 e: Expression<'_>,
629 base: Expression<'_>,
630 ty: Ty,
631 ) -> Result<Expr, BuildError> {
632 let mut shape = base.expr_type().and_then(array_shape);
633 if shape == Some(ArrayShape::Fixed) {
636 let inner = strip_conversions(base);
637 if inner.kind() == ExpressionKind::ElementSelect
638 && inner
639 .select_value()
640 .and_then(|v| v.expr_type())
641 .and_then(array_shape)
642 == Some(ArrayShape::Collection)
643 {
644 shape = Some(ArrayShape::Collection);
645 }
646 }
647 let sel = e.selector().ok_or_else(|| BuildError::Missing {
648 what: "index of an element select".into(),
649 })?;
650 match shape {
651 Some(ArrayShape::Collection) => {
652 self.arrays_allowed("an array element")?;
653 let (id, rows) = self.array_ref(base)?;
654 let index = self.expr(sel)?;
655 let mut has_var = false;
656 index.for_each_var(&mut |_, _| has_var = true);
657 if has_var {
658 return Err(bad_array(format!(
659 "a random variable in the index of the array `{}`: an index is a \
660 constant, a state variable, a loop variable or the size (IEEE \
661 1800-2023 18.5.7.1)",
662 self.arrays[id as usize].name
663 )));
664 }
665 let elem_ty = self.arrays[id as usize].elem_ty;
666 if self.arrays[id as usize].nested {
667 return Err(unsupported(
668 format!(
669 "a row `{}[..]` of an array of arrays as a value (name an element \
670 `a[i][j]`)",
671 self.arrays[id as usize].name
672 ),
673 "C14",
674 ));
675 }
676 let el = Self::row_scoped(
677 rows,
678 Expr::Array(ArrayNode::Elem {
679 array: id,
680 index: Box::new(index),
681 ty: elem_ty,
682 }),
683 );
684 Ok(if elem_ty == ty {
685 el
686 } else {
687 Expr::Resize(Box::new(el), ty)
688 })
689 }
690 Some(ArrayShape::Assoc) => Err(bad_array(
691 "an element of an associative array: its indices are not randomizable and it \
692 has no constraint form (IEEE 1800-2023 18.4)",
693 )),
694 Some(ArrayShape::Fixed) => self.fixed_element(e),
695 None => Err(BuildError::Missing {
696 what: "array type of an element select base".into(),
697 }),
698 }
699 }
700
701 fn fixed_element(&mut self, e: Expression<'_>) -> Result<Expr, BuildError> {
706 let what = e
707 .select_value()
708 .map(strip_conversions)
709 .and_then(|b| b.referenced_symbol().or_else(|| b.member_symbol()))
710 .map_or_else(String::new, |s| s.name().to_string());
711 Err(unsupported(
712 format!(
713 "an element of the unpacked array `{what}`, which is not a property (or a \
714 member of a struct property) of this class"
715 ),
716 "C14",
717 ))
718 }
719
720 fn foreach_vars(&self, c: Constraint<'_>) -> Result<Vec<String>, BuildError> {
723 for tree in self.trees {
724 if let Some(node) = c.syntax(tree) {
725 let l = LoopConstraintSyntax::cast(node).ok_or_else(|| BuildError::Missing {
726 what: "`foreach` syntax for a Foreach constraint".into(),
727 })?;
728 return Ok(l
729 .loop_list()
730 .loop_variables()
731 .iter()
732 .map(|v| v.syntax().text().trim().to_string())
733 .collect());
734 }
735 }
736 Err(BuildError::Missing {
737 what: "syntax node for a constraint (is its syntax tree passed to the builder?)".into(),
738 })
739 }
740
741 pub(super) fn fresh_loop(&mut self) -> LoopId {
742 let id = self.next_loop;
743 self.next_loop += 1;
744 id
745 }
746
747 fn in_scope(
749 &mut self,
750 scope: LoopScope,
751 body: Constraint<'_>,
752 ) -> Result<Vec<Expr>, BuildError> {
753 self.loops.push(scope);
754 let r = self.constraint(body);
755 self.loops.pop();
756 r
757 }
758
759 pub(super) fn foreach(&mut self, c: Constraint<'_>) -> Result<Vec<Expr>, BuildError> {
761 let names = self.foreach_vars(c)?;
762 let kids = c.children();
763 let arr =
764 kids.iter()
765 .find_map(|n| n.as_expression())
766 .ok_or_else(|| BuildError::Missing {
767 what: "array expression of a `foreach` constraint".into(),
768 })?;
769 let body =
770 kids.iter()
771 .find_map(|n| n.as_constraint())
772 .ok_or_else(|| BuildError::Missing {
773 what: "body of a `foreach` constraint".into(),
774 })?;
775 let at = arr.expr_type().ok_or_else(|| BuildError::Missing {
776 what: "type of the `foreach` array".into(),
777 })?;
778 let shape = array_shape(at);
779 let nested_fixed = shape == Some(ArrayShape::Fixed)
784 && at
785 .canonical()
786 .element_type()
787 .is_some_and(|elem| array_shape(elem) == Some(ArrayShape::Fixed));
788 let nested_dyn = shape == Some(ArrayShape::Collection)
789 && at.canonical().element_type().is_some_and(|elem| {
790 matches!(
791 array_shape(elem),
792 Some(ArrayShape::Collection | ArrayShape::Fixed)
793 )
794 });
795 if nested_dyn {
796 return self.foreach_nested(arr, &names, body);
797 }
798 let fixed_of_dyn = shape == Some(ArrayShape::Fixed)
801 && at
802 .canonical()
803 .element_type()
804 .is_some_and(|elem| array_shape(elem) == Some(ArrayShape::Collection));
805 if fixed_of_dyn {
806 return self.foreach_fixed_outer(arr, at, &names, body);
807 }
808 if names.len() > 1 || nested_fixed {
809 let dims = self.static_dims(at, &names)?;
810 return self.unroll_nd(&dims, body);
811 }
812 let [name] = names.as_slice() else {
813 return Err(BuildError::Missing {
814 what: "a loop variable of a `foreach` constraint".into(),
815 });
816 };
817 if shape.is_some()
818 && at
819 .canonical()
820 .element_type()
821 .is_some_and(|elem| array_shape(elem).is_some())
822 {
823 return Err(unsupported("a `foreach` over an array of arrays", "C11"));
824 }
825 let int = Ty::signed(32);
826 match shape {
827 Some(ArrayShape::Collection) => {
828 self.arrays_allowed("a `foreach`")?;
829 let array = self.array_of(arr)?;
830 let var = self.fresh_loop();
831 let (r0, b0, rb0) = (
834 self.real_conds.len() + self.real_dists.len(),
835 self.before.len(),
836 self.real_before.len(),
837 );
838 let parts = self.in_scope(
839 LoopScope {
840 name: name.clone(),
841 value: LoopBind::Runtime(var, int),
842 index: None,
843 },
844 body,
845 )?;
846 if self.real_conds.len() + self.real_dists.len() != r0
847 || self.before.len() != b0
848 || self.real_before.len() != rb0
849 {
850 return Err(unsupported(
851 "a real `dist` or condition, or a `solve ... before` constraint, inside a \
852 `foreach` over a dynamic array or queue (it would have to hold once per \
853 element)",
854 "C11",
855 ));
856 }
857 Ok(vec![Expr::Array(ArrayNode::Foreach {
858 array,
859 var,
860 body: Box::new(and_all(parts)),
861 })])
862 }
863 Some(ArrayShape::Assoc) => Err(bad_array(
864 "a `foreach` over an associative array: its indices are not randomizable and \
865 it has no constraint form (IEEE 1800-2023 18.4)",
866 )),
867 Some(ArrayShape::Fixed) => {
868 let r = at.canonical().fixed_range();
869 self.unroll(name, i64::from(r.left), i64::from(r.right), body)
870 }
871 None => {
872 let width = u32::try_from(at.bit_width()).map_err(|_| BuildError::Missing {
874 what: "representable width for a `foreach` array".into(),
875 })?;
876 let g = Geom::of(at, width, "the `foreach` array")?;
877 self.unroll(name, g.left, g.right, body)
878 }
879 }
880 }
881
882 fn foreach_fixed_outer(
886 &mut self,
887 arr: Expression<'_>,
888 at: Type<'_>,
889 names: &[String],
890 body: Constraint<'_>,
891 ) -> Result<Vec<Expr>, BuildError> {
892 if names.is_empty() {
893 return Err(unsupported(
894 "a `foreach` with no loop variable over a fixed-size array of dynamic arrays",
895 "C11",
896 ));
897 }
898 let chain = aggregate_chain(arr, &self.forced_roots).ok_or_else(|| {
899 unsupported(
900 "a `foreach` over a fixed-size array of dynamic arrays that is not a property \
901 of this class",
902 "C11",
903 )
904 })?;
905 let r = at.canonical().fixed_range();
906 let (left, right) = (i64::from(r.left), i64::from(r.right));
907 let idxs: Vec<i64> = if left >= right {
908 (right..=left).rev().collect()
909 } else {
910 (left..=right).collect()
911 };
912 let int = Ty::signed(32);
913 let mut out = Vec::new();
914 for k in idxs {
915 let outer_scope = (!names[0].is_empty()).then(|| LoopScope {
916 name: names[0].clone(),
917 value: LoopBind::Const(k),
918 index: None,
919 });
920 let depth = self.loops.len();
921 self.loops.extend(outer_scope);
922 let parts = self.fixed_outer_row(&chain, k, &names[1..], int, body);
923 self.loops.truncate(depth);
924 out.extend(parts?);
925 }
926 Ok(out)
927 }
928
929 fn fixed_outer_row(
932 &mut self,
933 chain: &super::aggregate::Chain<'_>,
934 k: i64,
935 rest: &[String],
936 int: Ty,
937 body: Constraint<'_>,
938 ) -> Result<Vec<Expr>, BuildError> {
939 if rest.is_empty() {
940 return self.constraint(body);
941 }
942 self.arrays_allowed("a `foreach`")?;
943 let mut row_chain = chain.clone();
944 row_chain.steps.push(RawStep::Const(k));
945 let row = self.array_from_chain(row_chain)?;
946 if self.arrays[row as usize].nested {
947 return self.foreach_rows(row, rest, body);
950 }
951 if rest.len() > 1 {
952 return Err(unsupported(
953 format!(
954 "a `foreach` with {} loop variables over a fixed-size array of dynamic \
955 arrays (at most two dimensions)",
956 rest.len() + 1
957 ),
958 "C11",
959 ));
960 }
961 let inner = &rest[0];
962 let vj = self.fresh_loop();
963 let (r0, b0, rb0) = (
964 self.real_conds.len() + self.real_dists.len(),
965 self.before.len(),
966 self.real_before.len(),
967 );
968 let depth = self.loops.len();
969 if !inner.is_empty() {
970 self.loops.push(LoopScope {
971 name: inner.clone(),
972 value: LoopBind::Runtime(vj, int),
973 index: None,
974 });
975 }
976 let parts = self.constraint(body);
977 self.loops.truncate(depth);
978 let parts = parts?;
979 if self.real_conds.len() + self.real_dists.len() != r0
980 || self.before.len() != b0
981 || self.real_before.len() != rb0
982 {
983 return Err(unsupported(
984 "a real `dist` or condition, or a `solve ... before` constraint, inside a \
985 `foreach` over a dynamic array row (it would have to hold once per element)",
986 "C11",
987 ));
988 }
989 Ok(vec![Expr::Array(ArrayNode::Foreach {
990 array: row,
991 var: vj,
992 body: Box::new(and_all(parts)),
993 })])
994 }
995
996 fn foreach_nested(
1001 &mut self,
1002 arr: Expression<'_>,
1003 names: &[String],
1004 body: Constraint<'_>,
1005 ) -> Result<Vec<Expr>, BuildError> {
1006 self.arrays_allowed("a `foreach`")?;
1007 let (outer, rows) = self.array_ref(arr)?;
1008 if !rows.is_empty() || !self.arrays[outer as usize].nested {
1009 return Err(unsupported(
1010 "a `foreach` over an array whose elements are not dynamic arrays",
1011 "C11",
1012 ));
1013 }
1014 self.foreach_rows(outer, names, body)
1015 }
1016
1017 fn foreach_rows(
1020 &mut self,
1021 outer: ArrayId,
1022 names: &[String],
1023 body: Constraint<'_>,
1024 ) -> Result<Vec<Expr>, BuildError> {
1025 let dims = self.arrays[outer as usize].row_depth as usize + 1;
1026 if names.is_empty() || names.len() > dims {
1027 return Err(unsupported(
1028 format!(
1029 "a `foreach` with {} loop variables over an array of dynamic arrays with {} \
1030 dimensions",
1031 names.len(),
1032 dims
1033 ),
1034 "C11",
1035 ));
1036 }
1037 let int = Ty::signed(32);
1038 let loops: Vec<LoopId> = names.iter().map(|_| self.fresh_loop()).collect();
1041 let mut arrays = vec![outer];
1042 for _ in 1..names.len() {
1043 let t = self.row_template(*arrays.last().expect("one array"));
1044 arrays.push(t);
1045 }
1046 let scopes: Vec<LoopScope> = names
1047 .iter()
1048 .zip(&loops)
1049 .filter(|(n, _)| !n.is_empty())
1050 .map(|(n, &v)| LoopScope {
1051 name: n.clone(),
1052 value: LoopBind::Runtime(v, int),
1053 index: None,
1054 })
1055 .collect();
1056 let (r0, b0, rb0) = (
1057 self.real_conds.len() + self.real_dists.len(),
1058 self.before.len(),
1059 self.real_before.len(),
1060 );
1061 let depth = self.loops.len();
1062 self.loops.extend(scopes);
1063 let parts = self.constraint(body);
1064 self.loops.truncate(depth);
1065 let parts = parts?;
1066 if self.real_conds.len() + self.real_dists.len() != r0
1067 || self.before.len() != b0
1068 || self.real_before.len() != rb0
1069 {
1070 return Err(unsupported(
1071 "a real `dist` or condition, or a `solve ... before` constraint, inside a \
1072 `foreach` over an array of dynamic arrays (it would have to hold once per \
1073 element)",
1074 "C11",
1075 ));
1076 }
1077 let mut inner = and_all(parts);
1080 for k in (0..names.len()).rev() {
1081 inner = Expr::Array(ArrayNode::Foreach {
1082 array: arrays[k],
1083 var: loops[k],
1084 body: Box::new(inner),
1085 });
1086 if k > 0 {
1087 inner = Expr::Array(ArrayNode::RowScope {
1088 template: arrays[k],
1089 row: Box::new(Expr::Array(ArrayNode::Loop(loops[k - 1], int))),
1090 body: Box::new(inner),
1091 });
1092 }
1093 }
1094 Ok(vec![inner])
1095 }
1096
1097 fn static_dims(
1100 &self,
1101 ty: sv_lang::Type<'_>,
1102 names: &[String],
1103 ) -> Result<Vec<(String, i64, i64)>, BuildError> {
1104 let mut dims = Vec::new();
1105 let mut cur = ty;
1106 for (k, name) in names.iter().enumerate() {
1107 let c = cur.canonical();
1108 let (left, right) = match array_shape(cur) {
1109 Some(ArrayShape::Fixed) => {
1110 let r = c.fixed_range();
1111 (i64::from(r.left), i64::from(r.right))
1112 }
1113 None if c.is_integral() => {
1114 let width = u32::try_from(c.bit_width()).map_err(|_| BuildError::Missing {
1115 what: "representable width for a `foreach` array".into(),
1116 })?;
1117 let g = Geom::of(cur, width, "the `foreach` array")?;
1118 (g.left, g.right)
1119 }
1120 _ => {
1121 return Err(unsupported(
1122 format!(
1123 "a multi-dimensional `foreach`: dimension {} is a dynamic array, queue or associative array (only fixed-size dimensions unroll)",
1124 k + 1
1125 ),
1126 "C11",
1127 ));
1128 }
1129 };
1130 dims.push((name.clone(), left, right));
1131 if k + 1 < names.len() {
1132 cur = c.element_type().ok_or_else(|| {
1133 unsupported(
1134 format!(
1135 "a `foreach` with {} loop variables over an array with fewer \
1136 dimensions",
1137 names.len()
1138 ),
1139 "C11",
1140 )
1141 })?;
1142 }
1143 }
1144 Ok(dims)
1145 }
1146
1147 fn unroll_nd(
1150 &mut self,
1151 dims: &[(String, i64, i64)],
1152 body: Constraint<'_>,
1153 ) -> Result<Vec<Expr>, BuildError> {
1154 let Some((name, left, right)) = dims.first() else {
1155 return self.constraint(body);
1156 };
1157 if name.is_empty() {
1158 return self.unroll_nd(&dims[1..], body);
1159 }
1160 let idxs: Vec<i64> = if left >= right {
1161 (*right..=*left).rev().collect()
1162 } else {
1163 (*left..=*right).collect()
1164 };
1165 let mut out = Vec::new();
1166 for i in idxs {
1167 self.loops.push(LoopScope {
1168 name: name.clone(),
1169 value: LoopBind::Const(i),
1170 index: None,
1171 });
1172 let r = self.unroll_nd(&dims[1..], body);
1173 self.loops.pop();
1174 out.extend(r?);
1175 }
1176 Ok(out)
1177 }
1178
1179 fn unroll(
1182 &mut self,
1183 name: &str,
1184 left: i64,
1185 right: i64,
1186 body: Constraint<'_>,
1187 ) -> Result<Vec<Expr>, BuildError> {
1188 let idxs: Vec<i64> = if left >= right {
1189 (right..=left).rev().collect()
1190 } else {
1191 (left..=right).collect()
1192 };
1193 let mut out = Vec::new();
1194 for i in idxs {
1195 out.extend(self.in_scope(
1196 LoopScope {
1197 name: name.to_string(),
1198 value: LoopBind::Const(i),
1199 index: None,
1200 },
1201 body,
1202 )?);
1203 }
1204 Ok(out)
1205 }
1206
1207 pub(super) fn in_runtime_loop(&self) -> bool {
1211 self.loops
1212 .iter()
1213 .any(|s| matches!(s.value, LoopBind::Runtime(..)))
1214 }
1215
1216 fn scope_of(&self, name: &str) -> Option<&LoopScope> {
1217 self.loops.iter().rev().find(|s| s.name == name)
1218 }
1219
1220 fn bind_expr(&self, b: LoopBind, ty: Ty) -> Result<Expr, BuildError> {
1221 match b {
1222 LoopBind::Runtime(id, lt) => {
1223 let l = Expr::Array(ArrayNode::Loop(id, lt));
1224 Ok(if lt == ty {
1225 l
1226 } else {
1227 Expr::Resize(Box::new(l), ty)
1228 })
1229 }
1230 LoopBind::Const(i) => Ok(iconst(ty, i)),
1231 LoopBind::Elem(x) => Ok(if x.ty() == ty {
1232 x
1233 } else {
1234 Expr::Resize(Box::new(x), ty)
1235 }),
1236 }
1237 }
1238
1239 pub(super) fn loop_ref(&mut self, sym: Symbol<'_>, ty: Ty) -> Result<Expr, BuildError> {
1241 debug_assert_eq!(sym.kind(), SymbolKind::Iterator);
1242 let name = sym.name();
1243 let b = self
1244 .scope_of(name)
1245 .ok_or_else(|| BuildError::Missing {
1246 what: format!("an enclosing `foreach` or array method binding `{name}`"),
1247 })?
1248 .value
1249 .clone();
1250 if matches!(b, LoopBind::Runtime(..)) {
1251 self.arrays_allowed("a loop variable")?;
1252 }
1253 self.bind_expr(b, ty)
1254 }
1255
1256 pub(super) fn iterator_index(&mut self, e: Expression<'_>, ty: Ty) -> Result<Expr, BuildError> {
1258 let this = *e.operands().first().ok_or_else(|| BuildError::Missing {
1259 what: "iterator of `index`".into(),
1260 })?;
1261 let name = this
1262 .referenced_symbol()
1263 .map(|s| s.name().to_string())
1264 .ok_or_else(|| BuildError::Missing {
1265 what: "symbol of the iterator of `index`".into(),
1266 })?;
1267 let b = self
1268 .scope_of(&name)
1269 .and_then(|s| s.index.clone())
1270 .ok_or_else(|| BuildError::Missing {
1271 what: format!("an array method iterator `{name}` for `.index`"),
1272 })?;
1273 self.arrays_allowed("an iterator index")?;
1274 self.bind_expr(b, ty)
1275 }
1276
1277 pub(super) fn array_reduction(
1280 &mut self,
1281 e: Expression<'_>,
1282 op: ReduceOp,
1283 ty: Ty,
1284 ) -> Result<Expr, BuildError> {
1285 let arr = e
1286 .operands()
1287 .into_iter()
1288 .find(|o| expr_shape(*o).is_some())
1289 .ok_or_else(|| BuildError::Missing {
1290 what: "array operand of a reduction method".into(),
1291 })?;
1292 if expr_shape(arr) == Some(ArrayShape::Fixed) {
1293 return self.fixed_reduction(e, arr, op, ty);
1294 }
1295 self.arrays_allowed("an array reduction")?;
1296 let (array, rows) = self.array_ref(arr)?;
1297 self.reject_nested(array, "a reduction method")?;
1298 let elem_ty = self.arrays[array as usize].elem_ty;
1299 let var = self.fresh_loop();
1300 let index_var = self.fresh_loop();
1301 let body = match e.iterator_expr() {
1302 Some(w) => {
1303 let iter = e.iterator_var().ok_or_else(|| BuildError::Missing {
1304 what: "iterator variable of a reduction's `with` clause".into(),
1305 })?;
1306 self.loops.push(LoopScope {
1307 name: iter.name().to_string(),
1308 value: LoopBind::Runtime(var, elem_ty),
1309 index: Some(LoopBind::Runtime(index_var, Ty::signed(32))),
1310 });
1311 let b = self.expr(w);
1312 self.loops.pop();
1313 b?
1314 }
1315 None => Expr::Array(ArrayNode::Loop(var, elem_ty)),
1316 };
1317 let body = if body.ty() == ty {
1318 body
1319 } else {
1320 coerce(body, ty)
1321 };
1322 Ok(Self::row_scoped(
1323 rows,
1324 Expr::Array(ArrayNode::Reduce {
1325 op,
1326 array,
1327 var,
1328 index_var,
1329 body: Box::new(body),
1330 }),
1331 ))
1332 }
1333
1334 fn fixed_reduction(
1339 &mut self,
1340 e: Expression<'_>,
1341 arr: Expression<'_>,
1342 op: ReduceOp,
1343 ty: Ty,
1344 ) -> Result<Expr, BuildError> {
1345 let at = arr
1346 .expr_type()
1347 .ok_or_else(|| BuildError::Missing {
1348 what: "type of the reduced array".into(),
1349 })?
1350 .canonical();
1351 let r = at.fixed_unpacked_array_range();
1352 let (lo, hi) = (
1353 i64::from(r.left).min(i64::from(r.right)),
1354 i64::from(r.left).max(i64::from(r.right)),
1355 );
1356 let et = at.element_type().ok_or_else(|| BuildError::Missing {
1357 what: "element type of the reduced array".into(),
1358 })?;
1359 let elem_ty = ty_of(Some(et), "an element of the reduced array")?;
1360 let mut acc: Option<Expr> = None;
1361 for i in lo..=hi {
1362 let item = self.aggregate_element(arr, i, elem_ty)?;
1363 let body = match e.iterator_expr() {
1364 Some(w) => {
1365 let iter = e.iterator_var().ok_or_else(|| BuildError::Missing {
1366 what: "iterator variable of a reduction's `with` clause".into(),
1367 })?;
1368 self.loops.push(LoopScope {
1369 name: iter.name().to_string(),
1370 value: LoopBind::Elem(item),
1371 index: Some(LoopBind::Const(i)),
1372 });
1373 let b = self.expr(w);
1374 self.loops.pop();
1375 b?
1376 }
1377 None => item,
1378 };
1379 let body = if body.ty() == ty {
1380 body
1381 } else {
1382 coerce(body, ty)
1383 };
1384 acc = Some(match acc {
1385 None => body,
1386 Some(a) => {
1387 let bop = match op {
1388 ReduceOp::Sum => BinOp::Add,
1389 ReduceOp::Product => BinOp::Mul,
1390 ReduceOp::And => BinOp::And,
1391 ReduceOp::Or => BinOp::Or,
1392 ReduceOp::Xor => BinOp::Xor,
1393 };
1394 bin(bop, a, body, ty)
1395 }
1396 });
1397 }
1398 acc.ok_or_else(|| BuildError::Missing {
1399 what: "elements of the reduced array".into(),
1400 })
1401 }
1402
1403 pub(super) fn inside_array(
1410 &mut self,
1411 left: &Expr,
1412 arr: Expression<'_>,
1413 ) -> Result<Expr, BuildError> {
1414 let eq = |left: &Expr, x: Expr| {
1415 let t = common_ty(left.ty(), x.ty());
1416 bin(BinOp::Eq, coerce(left.clone(), t), coerce(x, t), Ty::BIT)
1417 };
1418 let at = arr.expr_type().ok_or_else(|| BuildError::Missing {
1419 what: "type of an `inside` array".into(),
1420 })?;
1421 match expr_shape(arr) {
1422 Some(ArrayShape::Fixed) => {
1423 let c = at.canonical();
1424 let r = c.fixed_unpacked_array_range();
1425 let (lo, hi) = (
1426 i64::from(r.left).min(i64::from(r.right)),
1427 i64::from(r.left).max(i64::from(r.right)),
1428 );
1429 let et = c.element_type().ok_or_else(|| BuildError::Missing {
1430 what: "element type of an `inside` array".into(),
1431 })?;
1432 let elem_ty = ty_of(Some(et), "an element of an `inside` array")?;
1433 let mut acc: Option<Expr> = None;
1434 for i in lo..=hi {
1435 let item = self.aggregate_element(arr, i, elem_ty)?;
1436 let term = eq(left, item);
1437 acc = Some(match acc {
1438 None => term,
1439 Some(prev) => bin(BinOp::LogOr, prev, term, Ty::BIT),
1440 });
1441 }
1442 acc.ok_or_else(|| BuildError::Missing {
1443 what: "elements of an `inside` array".into(),
1444 })
1445 }
1446 Some(ArrayShape::Collection) => {
1447 self.arrays_allowed("an `inside` over an array")?;
1448 let (array, rows) = self.array_ref(arr)?;
1449 self.reject_nested(array, "an `inside` set")?;
1450 let elem_ty = self.arrays[array as usize].elem_ty;
1451 let var = self.fresh_loop();
1452 let index_var = self.fresh_loop();
1453 let body = eq(left, Expr::Array(ArrayNode::Loop(var, elem_ty)));
1454 Ok(Self::row_scoped(
1455 rows,
1456 Expr::Array(ArrayNode::Reduce {
1457 op: ReduceOp::Or,
1458 array,
1459 var,
1460 index_var,
1461 body: Box::new(body),
1462 }),
1463 ))
1464 }
1465 Some(ArrayShape::Assoc) | None => Err(bad_array(
1466 "an associative array in an `inside` set: its elements are not constraint \
1467 operands (IEEE 1800-2023 18.4)",
1468 )),
1469 }
1470 }
1471
1472 pub(super) fn array_call(
1475 &mut self,
1476 e: Expression<'_>,
1477 ty: Ty,
1478 ) -> Option<Result<Expr, BuildError>> {
1479 let sub = e.system_subroutine()?;
1480 let op = match sub.known_name_id() {
1481 KnownSystemName::Size | KnownSystemName::ArraySize | KnownSystemName::Num => {
1482 let arr = Self::size_call_array(e)?;
1483 return Some(self.array_size(arr));
1484 }
1485 KnownSystemName::Index => return Some(self.iterator_index(e, ty)),
1486 KnownSystemName::Sum => ReduceOp::Sum,
1487 KnownSystemName::Product => ReduceOp::Product,
1488 KnownSystemName::And => ReduceOp::And,
1489 KnownSystemName::Or => ReduceOp::Or,
1490 KnownSystemName::XOr => ReduceOp::Xor,
1491 _ => return None,
1492 };
1493 Some(self.array_reduction(e, op, ty))
1494 }
1495}