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() || names.len() > 2 {
893 return Err(unsupported(
894 format!(
895 "a `foreach` with {} loop variables over a fixed-size array of dynamic \
896 arrays (at most two dimensions)",
897 names.len()
898 ),
899 "C11",
900 ));
901 }
902 if at
903 .canonical()
904 .element_type()
905 .and_then(|e| e.canonical().element_type())
906 .is_some_and(|r| array_shape(r).is_some())
907 {
908 return Err(unsupported(
909 "a `foreach` over an array nested more than two deep",
910 "C11",
911 ));
912 }
913 let chain = aggregate_chain(arr, &self.forced_roots).ok_or_else(|| {
914 unsupported(
915 "a `foreach` over a fixed-size array of dynamic arrays that is not a property \
916 of this class",
917 "C11",
918 )
919 })?;
920 let r = at.canonical().fixed_range();
921 let (left, right) = (i64::from(r.left), i64::from(r.right));
922 let idxs: Vec<i64> = if left >= right {
923 (right..=left).rev().collect()
924 } else {
925 (left..=right).collect()
926 };
927 let int = Ty::signed(32);
928 let mut out = Vec::new();
929 for k in idxs {
930 let outer_scope = (!names[0].is_empty()).then(|| LoopScope {
931 name: names[0].clone(),
932 value: LoopBind::Const(k),
933 index: None,
934 });
935 let depth = self.loops.len();
936 self.loops.extend(outer_scope);
937 let parts = self.fixed_outer_row(&chain, k, names.get(1), int, body);
938 self.loops.truncate(depth);
939 out.extend(parts?);
940 }
941 Ok(out)
942 }
943
944 fn fixed_outer_row(
947 &mut self,
948 chain: &super::aggregate::Chain<'_>,
949 k: i64,
950 inner: Option<&String>,
951 int: Ty,
952 body: Constraint<'_>,
953 ) -> Result<Vec<Expr>, BuildError> {
954 let Some(inner) = inner else {
955 return self.constraint(body);
956 };
957 self.arrays_allowed("a `foreach`")?;
958 let mut row_chain = chain.clone();
959 row_chain.steps.push(RawStep::Const(k));
960 let row = self.array_from_chain(row_chain)?;
961 let vj = self.fresh_loop();
962 let (r0, b0, rb0) = (
963 self.real_conds.len() + self.real_dists.len(),
964 self.before.len(),
965 self.real_before.len(),
966 );
967 let depth = self.loops.len();
968 if !inner.is_empty() {
969 self.loops.push(LoopScope {
970 name: inner.clone(),
971 value: LoopBind::Runtime(vj, int),
972 index: None,
973 });
974 }
975 let parts = self.constraint(body);
976 self.loops.truncate(depth);
977 let parts = parts?;
978 if self.real_conds.len() + self.real_dists.len() != r0
979 || self.before.len() != b0
980 || self.real_before.len() != rb0
981 {
982 return Err(unsupported(
983 "a real `dist` or condition, or a `solve ... before` constraint, inside a \
984 `foreach` over a dynamic array row (it would have to hold once per element)",
985 "C11",
986 ));
987 }
988 Ok(vec![Expr::Array(ArrayNode::Foreach {
989 array: row,
990 var: vj,
991 body: Box::new(and_all(parts)),
992 })])
993 }
994
995 fn foreach_nested(
1000 &mut self,
1001 arr: Expression<'_>,
1002 names: &[String],
1003 body: Constraint<'_>,
1004 ) -> Result<Vec<Expr>, BuildError> {
1005 self.arrays_allowed("a `foreach`")?;
1006 let (outer, rows) = self.array_ref(arr)?;
1007 if !rows.is_empty() || !self.arrays[outer as usize].nested {
1008 return Err(unsupported(
1009 "a `foreach` over an array whose elements are not dynamic arrays",
1010 "C11",
1011 ));
1012 }
1013 let dims = self.arrays[outer as usize].row_depth as usize + 1;
1014 if names.is_empty() || names.len() > dims {
1015 return Err(unsupported(
1016 format!(
1017 "a `foreach` with {} loop variables over an array of dynamic arrays with {} \
1018 dimensions",
1019 names.len(),
1020 dims
1021 ),
1022 "C11",
1023 ));
1024 }
1025 let int = Ty::signed(32);
1026 let loops: Vec<LoopId> = names.iter().map(|_| self.fresh_loop()).collect();
1029 let mut arrays = vec![outer];
1030 for _ in 1..names.len() {
1031 let t = self.row_template(*arrays.last().expect("one array"));
1032 arrays.push(t);
1033 }
1034 let scopes: Vec<LoopScope> = names
1035 .iter()
1036 .zip(&loops)
1037 .filter(|(n, _)| !n.is_empty())
1038 .map(|(n, &v)| LoopScope {
1039 name: n.clone(),
1040 value: LoopBind::Runtime(v, int),
1041 index: None,
1042 })
1043 .collect();
1044 let (r0, b0, rb0) = (
1045 self.real_conds.len() + self.real_dists.len(),
1046 self.before.len(),
1047 self.real_before.len(),
1048 );
1049 let depth = self.loops.len();
1050 self.loops.extend(scopes);
1051 let parts = self.constraint(body);
1052 self.loops.truncate(depth);
1053 let parts = parts?;
1054 if self.real_conds.len() + self.real_dists.len() != r0
1055 || self.before.len() != b0
1056 || self.real_before.len() != rb0
1057 {
1058 return Err(unsupported(
1059 "a real `dist` or condition, or a `solve ... before` constraint, inside a \
1060 `foreach` over an array of dynamic arrays (it would have to hold once per \
1061 element)",
1062 "C11",
1063 ));
1064 }
1065 let mut inner = and_all(parts);
1068 for k in (0..names.len()).rev() {
1069 inner = Expr::Array(ArrayNode::Foreach {
1070 array: arrays[k],
1071 var: loops[k],
1072 body: Box::new(inner),
1073 });
1074 if k > 0 {
1075 inner = Expr::Array(ArrayNode::RowScope {
1076 template: arrays[k],
1077 row: Box::new(Expr::Array(ArrayNode::Loop(loops[k - 1], int))),
1078 body: Box::new(inner),
1079 });
1080 }
1081 }
1082 Ok(vec![inner])
1083 }
1084
1085 fn static_dims(
1088 &self,
1089 ty: sv_lang::Type<'_>,
1090 names: &[String],
1091 ) -> Result<Vec<(String, i64, i64)>, BuildError> {
1092 let mut dims = Vec::new();
1093 let mut cur = ty;
1094 for (k, name) in names.iter().enumerate() {
1095 let c = cur.canonical();
1096 let (left, right) = match array_shape(cur) {
1097 Some(ArrayShape::Fixed) => {
1098 let r = c.fixed_range();
1099 (i64::from(r.left), i64::from(r.right))
1100 }
1101 None if c.is_integral() => {
1102 let width = u32::try_from(c.bit_width()).map_err(|_| BuildError::Missing {
1103 what: "representable width for a `foreach` array".into(),
1104 })?;
1105 let g = Geom::of(cur, width, "the `foreach` array")?;
1106 (g.left, g.right)
1107 }
1108 _ => {
1109 return Err(unsupported(
1110 format!(
1111 "a multi-dimensional `foreach`: dimension {} is a dynamic array, queue or associative array (only fixed-size dimensions unroll)",
1112 k + 1
1113 ),
1114 "C11",
1115 ));
1116 }
1117 };
1118 dims.push((name.clone(), left, right));
1119 if k + 1 < names.len() {
1120 cur = c.element_type().ok_or_else(|| {
1121 unsupported(
1122 format!(
1123 "a `foreach` with {} loop variables over an array with fewer \
1124 dimensions",
1125 names.len()
1126 ),
1127 "C11",
1128 )
1129 })?;
1130 }
1131 }
1132 Ok(dims)
1133 }
1134
1135 fn unroll_nd(
1138 &mut self,
1139 dims: &[(String, i64, i64)],
1140 body: Constraint<'_>,
1141 ) -> Result<Vec<Expr>, BuildError> {
1142 let Some((name, left, right)) = dims.first() else {
1143 return self.constraint(body);
1144 };
1145 if name.is_empty() {
1146 return self.unroll_nd(&dims[1..], body);
1147 }
1148 let idxs: Vec<i64> = if left >= right {
1149 (*right..=*left).rev().collect()
1150 } else {
1151 (*left..=*right).collect()
1152 };
1153 let mut out = Vec::new();
1154 for i in idxs {
1155 self.loops.push(LoopScope {
1156 name: name.clone(),
1157 value: LoopBind::Const(i),
1158 index: None,
1159 });
1160 let r = self.unroll_nd(&dims[1..], body);
1161 self.loops.pop();
1162 out.extend(r?);
1163 }
1164 Ok(out)
1165 }
1166
1167 fn unroll(
1170 &mut self,
1171 name: &str,
1172 left: i64,
1173 right: i64,
1174 body: Constraint<'_>,
1175 ) -> Result<Vec<Expr>, BuildError> {
1176 let idxs: Vec<i64> = if left >= right {
1177 (right..=left).rev().collect()
1178 } else {
1179 (left..=right).collect()
1180 };
1181 let mut out = Vec::new();
1182 for i in idxs {
1183 out.extend(self.in_scope(
1184 LoopScope {
1185 name: name.to_string(),
1186 value: LoopBind::Const(i),
1187 index: None,
1188 },
1189 body,
1190 )?);
1191 }
1192 Ok(out)
1193 }
1194
1195 pub(super) fn in_runtime_loop(&self) -> bool {
1199 self.loops
1200 .iter()
1201 .any(|s| matches!(s.value, LoopBind::Runtime(..)))
1202 }
1203
1204 fn scope_of(&self, name: &str) -> Option<&LoopScope> {
1205 self.loops.iter().rev().find(|s| s.name == name)
1206 }
1207
1208 fn bind_expr(&self, b: LoopBind, ty: Ty) -> Result<Expr, BuildError> {
1209 match b {
1210 LoopBind::Runtime(id, lt) => {
1211 let l = Expr::Array(ArrayNode::Loop(id, lt));
1212 Ok(if lt == ty {
1213 l
1214 } else {
1215 Expr::Resize(Box::new(l), ty)
1216 })
1217 }
1218 LoopBind::Const(i) => Ok(iconst(ty, i)),
1219 LoopBind::Elem(x) => Ok(if x.ty() == ty {
1220 x
1221 } else {
1222 Expr::Resize(Box::new(x), ty)
1223 }),
1224 }
1225 }
1226
1227 pub(super) fn loop_ref(&mut self, sym: Symbol<'_>, ty: Ty) -> Result<Expr, BuildError> {
1229 debug_assert_eq!(sym.kind(), SymbolKind::Iterator);
1230 let name = sym.name();
1231 let b = self
1232 .scope_of(name)
1233 .ok_or_else(|| BuildError::Missing {
1234 what: format!("an enclosing `foreach` or array method binding `{name}`"),
1235 })?
1236 .value
1237 .clone();
1238 if matches!(b, LoopBind::Runtime(..)) {
1239 self.arrays_allowed("a loop variable")?;
1240 }
1241 self.bind_expr(b, ty)
1242 }
1243
1244 pub(super) fn iterator_index(&mut self, e: Expression<'_>, ty: Ty) -> Result<Expr, BuildError> {
1246 let this = *e.operands().first().ok_or_else(|| BuildError::Missing {
1247 what: "iterator of `index`".into(),
1248 })?;
1249 let name = this
1250 .referenced_symbol()
1251 .map(|s| s.name().to_string())
1252 .ok_or_else(|| BuildError::Missing {
1253 what: "symbol of the iterator of `index`".into(),
1254 })?;
1255 let b = self
1256 .scope_of(&name)
1257 .and_then(|s| s.index.clone())
1258 .ok_or_else(|| BuildError::Missing {
1259 what: format!("an array method iterator `{name}` for `.index`"),
1260 })?;
1261 self.arrays_allowed("an iterator index")?;
1262 self.bind_expr(b, ty)
1263 }
1264
1265 pub(super) fn array_reduction(
1268 &mut self,
1269 e: Expression<'_>,
1270 op: ReduceOp,
1271 ty: Ty,
1272 ) -> Result<Expr, BuildError> {
1273 let arr = e
1274 .operands()
1275 .into_iter()
1276 .find(|o| expr_shape(*o).is_some())
1277 .ok_or_else(|| BuildError::Missing {
1278 what: "array operand of a reduction method".into(),
1279 })?;
1280 if expr_shape(arr) == Some(ArrayShape::Fixed) {
1281 return self.fixed_reduction(e, arr, op, ty);
1282 }
1283 self.arrays_allowed("an array reduction")?;
1284 let (array, rows) = self.array_ref(arr)?;
1285 self.reject_nested(array, "a reduction method")?;
1286 let elem_ty = self.arrays[array as usize].elem_ty;
1287 let var = self.fresh_loop();
1288 let index_var = self.fresh_loop();
1289 let body = match e.iterator_expr() {
1290 Some(w) => {
1291 let iter = e.iterator_var().ok_or_else(|| BuildError::Missing {
1292 what: "iterator variable of a reduction's `with` clause".into(),
1293 })?;
1294 self.loops.push(LoopScope {
1295 name: iter.name().to_string(),
1296 value: LoopBind::Runtime(var, elem_ty),
1297 index: Some(LoopBind::Runtime(index_var, Ty::signed(32))),
1298 });
1299 let b = self.expr(w);
1300 self.loops.pop();
1301 b?
1302 }
1303 None => Expr::Array(ArrayNode::Loop(var, elem_ty)),
1304 };
1305 let body = if body.ty() == ty {
1306 body
1307 } else {
1308 coerce(body, ty)
1309 };
1310 Ok(Self::row_scoped(
1311 rows,
1312 Expr::Array(ArrayNode::Reduce {
1313 op,
1314 array,
1315 var,
1316 index_var,
1317 body: Box::new(body),
1318 }),
1319 ))
1320 }
1321
1322 fn fixed_reduction(
1327 &mut self,
1328 e: Expression<'_>,
1329 arr: Expression<'_>,
1330 op: ReduceOp,
1331 ty: Ty,
1332 ) -> Result<Expr, BuildError> {
1333 let at = arr
1334 .expr_type()
1335 .ok_or_else(|| BuildError::Missing {
1336 what: "type of the reduced array".into(),
1337 })?
1338 .canonical();
1339 let r = at.fixed_unpacked_array_range();
1340 let (lo, hi) = (
1341 i64::from(r.left).min(i64::from(r.right)),
1342 i64::from(r.left).max(i64::from(r.right)),
1343 );
1344 let et = at.element_type().ok_or_else(|| BuildError::Missing {
1345 what: "element type of the reduced array".into(),
1346 })?;
1347 let elem_ty = ty_of(Some(et), "an element of the reduced array")?;
1348 let mut acc: Option<Expr> = None;
1349 for i in lo..=hi {
1350 let item = self.aggregate_element(arr, i, elem_ty)?;
1351 let body = match e.iterator_expr() {
1352 Some(w) => {
1353 let iter = e.iterator_var().ok_or_else(|| BuildError::Missing {
1354 what: "iterator variable of a reduction's `with` clause".into(),
1355 })?;
1356 self.loops.push(LoopScope {
1357 name: iter.name().to_string(),
1358 value: LoopBind::Elem(item),
1359 index: Some(LoopBind::Const(i)),
1360 });
1361 let b = self.expr(w);
1362 self.loops.pop();
1363 b?
1364 }
1365 None => item,
1366 };
1367 let body = if body.ty() == ty {
1368 body
1369 } else {
1370 coerce(body, ty)
1371 };
1372 acc = Some(match acc {
1373 None => body,
1374 Some(a) => {
1375 let bop = match op {
1376 ReduceOp::Sum => BinOp::Add,
1377 ReduceOp::Product => BinOp::Mul,
1378 ReduceOp::And => BinOp::And,
1379 ReduceOp::Or => BinOp::Or,
1380 ReduceOp::Xor => BinOp::Xor,
1381 };
1382 bin(bop, a, body, ty)
1383 }
1384 });
1385 }
1386 acc.ok_or_else(|| BuildError::Missing {
1387 what: "elements of the reduced array".into(),
1388 })
1389 }
1390
1391 pub(super) fn inside_array(
1398 &mut self,
1399 left: &Expr,
1400 arr: Expression<'_>,
1401 ) -> Result<Expr, BuildError> {
1402 let eq = |left: &Expr, x: Expr| {
1403 let t = common_ty(left.ty(), x.ty());
1404 bin(BinOp::Eq, coerce(left.clone(), t), coerce(x, t), Ty::BIT)
1405 };
1406 let at = arr.expr_type().ok_or_else(|| BuildError::Missing {
1407 what: "type of an `inside` array".into(),
1408 })?;
1409 match expr_shape(arr) {
1410 Some(ArrayShape::Fixed) => {
1411 let c = at.canonical();
1412 let r = c.fixed_unpacked_array_range();
1413 let (lo, hi) = (
1414 i64::from(r.left).min(i64::from(r.right)),
1415 i64::from(r.left).max(i64::from(r.right)),
1416 );
1417 let et = c.element_type().ok_or_else(|| BuildError::Missing {
1418 what: "element type of an `inside` array".into(),
1419 })?;
1420 let elem_ty = ty_of(Some(et), "an element of an `inside` array")?;
1421 let mut acc: Option<Expr> = None;
1422 for i in lo..=hi {
1423 let item = self.aggregate_element(arr, i, elem_ty)?;
1424 let term = eq(left, item);
1425 acc = Some(match acc {
1426 None => term,
1427 Some(prev) => bin(BinOp::LogOr, prev, term, Ty::BIT),
1428 });
1429 }
1430 acc.ok_or_else(|| BuildError::Missing {
1431 what: "elements of an `inside` array".into(),
1432 })
1433 }
1434 Some(ArrayShape::Collection) => {
1435 self.arrays_allowed("an `inside` over an array")?;
1436 let (array, rows) = self.array_ref(arr)?;
1437 self.reject_nested(array, "an `inside` set")?;
1438 let elem_ty = self.arrays[array as usize].elem_ty;
1439 let var = self.fresh_loop();
1440 let index_var = self.fresh_loop();
1441 let body = eq(left, Expr::Array(ArrayNode::Loop(var, elem_ty)));
1442 Ok(Self::row_scoped(
1443 rows,
1444 Expr::Array(ArrayNode::Reduce {
1445 op: ReduceOp::Or,
1446 array,
1447 var,
1448 index_var,
1449 body: Box::new(body),
1450 }),
1451 ))
1452 }
1453 Some(ArrayShape::Assoc) | None => Err(bad_array(
1454 "an associative array in an `inside` set: its elements are not constraint \
1455 operands (IEEE 1800-2023 18.4)",
1456 )),
1457 }
1458 }
1459
1460 pub(super) fn array_call(
1463 &mut self,
1464 e: Expression<'_>,
1465 ty: Ty,
1466 ) -> Option<Result<Expr, BuildError>> {
1467 let sub = e.system_subroutine()?;
1468 let op = match sub.known_name_id() {
1469 KnownSystemName::Size | KnownSystemName::ArraySize | KnownSystemName::Num => {
1470 let arr = Self::size_call_array(e)?;
1471 return Some(self.array_size(arr));
1472 }
1473 KnownSystemName::Index => return Some(self.iterator_index(e, ty)),
1474 KnownSystemName::Sum => ReduceOp::Sum,
1475 KnownSystemName::Product => ReduceOp::Product,
1476 KnownSystemName::And => ReduceOp::And,
1477 KnownSystemName::Or => ReduceOp::Or,
1478 KnownSystemName::XOr => ReduceOp::Xor,
1479 _ => return None,
1480 };
1481 Some(self.array_reduction(e, op, ty))
1482 }
1483}