use super::real::strip_conversions;
use super::*;
use crate::constraints::ir::{ArrayId, ArrayNode, LoopId, ReduceOp};
use sv_lang::KnownSystemName;
use sv_lang::kinds::SymbolKind;
use sv_lang::nodes::LoopConstraintSyntax;
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct ArrayInfo {
pub symbol: SymbolId,
pub name: String,
pub elem_ty: Ty,
pub rand: bool,
pub elem_domain: Option<Vec<BigUint>>,
pub assoc: bool,
}
#[derive(Clone, Debug)]
pub(super) enum LoopBind {
Runtime(LoopId, Ty),
Const(i64),
Elem(Expr),
}
#[derive(Clone, Debug)]
pub(super) struct LoopScope {
name: String,
value: LoopBind,
index: Option<LoopBind>,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(super) enum ArrayShape {
Collection,
Assoc,
Fixed,
}
pub(super) fn array_shape(t: Type<'_>) -> Option<ArrayShape> {
let c = t.canonical();
if c.is_associative_array() {
Some(ArrayShape::Assoc)
} else if c.is_dynamically_sized_array() || c.is_queue() {
Some(ArrayShape::Collection)
} else if c.is_unpacked_array() {
Some(ArrayShape::Fixed)
} else {
None
}
}
fn bad_array(what: impl Into<String>) -> BuildError {
BuildError::BadArray { what: what.into() }
}
pub(super) fn const_i64(ty: Ty, v: &BigUint) -> Option<i64> {
let signed = BigInt::from(v.clone());
let signed = if ty.signed && v.bit(u64::from(ty.width) - 1) {
signed - (BigInt::one() << ty.width)
} else {
signed
};
i64::try_from(signed).ok()
}
impl Builder<'_> {
pub(super) fn arrays_allowed(&self, what: &str) -> Result<(), BuildError> {
match self.no_arrays {
None => Ok(()),
Some(inside) => Err(unsupported(
format!("{what} inside {inside}"),
"C14 (array-level values have no weight or bound at build time)",
)),
}
}
pub fn declare_rand_array(&mut self, sym: Symbol<'_>) -> Result<u32, BuildError> {
let uid = sym.uid();
let name = sym.name().to_string();
match sym.rand_mode() {
RandMode::None => return Err(BuildError::NotRand { name }),
RandMode::RandC => {
return Err(unsupported(
format!("the `randc` array `{name}`: per-element cycling"),
"C14",
));
}
RandMode::Rand => {}
}
if let Some(&id) = self.array_ix.get(&uid) {
self.arrays[id as usize].rand = true;
return Ok(id);
}
let vt = sym.value_type().ok_or_else(|| BuildError::Missing {
what: format!("value type for `{name}`"),
})?;
let info = self.array_info(uid.clone(), name, vt, true)?;
let id = self.arrays.len() as u32;
self.arrays.push(info);
self.array_ix.insert(uid, id);
Ok(id)
}
pub fn declare_array_forced(&mut self, sym: Symbol<'_>) -> Result<u32, BuildError> {
let uid = sym.uid();
let name = sym.name().to_string();
if matches!(sym.rand_mode(), RandMode::RandC) {
return Err(unsupported(
format!("the `randc` array `{name}`: per-element cycling"),
"C14",
));
}
if let Some(&id) = self.array_ix.get(&uid) {
self.arrays[id as usize].rand = true;
return Ok(id);
}
let vt = sym.value_type().ok_or_else(|| BuildError::Missing {
what: format!("value type for `{name}`"),
})?;
let info = self.array_info(uid.clone(), name, vt, true)?;
let id = self.arrays.len() as u32;
self.arrays.push(info);
self.array_ix.insert(uid, id);
Ok(id)
}
fn array_info(
&self,
symbol: SymbolId,
name: String,
vt: Type<'_>,
rand: bool,
) -> Result<ArrayInfo, BuildError> {
match array_shape(vt) {
Some(ArrayShape::Collection) => {}
Some(ArrayShape::Assoc) if rand => {}
Some(ArrayShape::Assoc) => return Err(Self::assoc_operand(&name)),
Some(ArrayShape::Fixed) => {
return Err(unsupported(
format!(
"the fixed-size unpacked array `{name}` as a whole-array constraint \
operand (name its elements, or use a reduction method or `foreach`)"
),
"C14",
));
}
None => {
return Err(BuildError::NonIntegral {
what: format!(
"`{name}` of type `{}`, which is not an array",
vt.to_sv_string()
),
});
}
}
let et = vt
.canonical()
.element_type()
.ok_or_else(|| BuildError::Missing {
what: format!("element type of the array `{name}`"),
})?;
let elem_ty = ty_of(Some(et), &format!("an element of the array `{name}`"))?;
let elem_domain = Self::enum_values(et, elem_ty)?;
Ok(ArrayInfo {
symbol,
name,
elem_ty,
rand,
elem_domain,
assoc: array_shape(vt) == Some(ArrayShape::Assoc),
})
}
fn assoc_operand(name: &str) -> BuildError {
bad_array(format!(
"`{name}` is an associative array: its size and indices are not randomizable and \
its elements are not constraint operands (IEEE 1800-2023 18.4)"
))
}
pub(super) fn array_of(&mut self, e: Expression<'_>) -> Result<ArrayId, BuildError> {
let e = strip_conversions(e);
let sym = match e.kind() {
ExpressionKind::NamedValue | ExpressionKind::HierarchicalValue => e.referenced_symbol(),
ExpressionKind::MemberAccess => {
let base = Self::operand(e, "a member access")?;
let is_this = base.kind() == ExpressionKind::NamedValue
&& base.referenced_symbol().is_some_and(|s| s.name() == "this");
if !is_this {
return Err(BuildError::ForeignObjectAccess {
what: format!(
"the array `.{}` of another object",
e.member_symbol()
.map_or(String::new(), |m| m.name().to_string())
),
});
}
e.member_symbol()
}
other => {
return Err(unsupported(
format!("a `{other:?}` expression as an array operand"),
"C14",
));
}
};
let sym = sym.ok_or_else(|| BuildError::Missing {
what: "symbol of an array operand".into(),
})?;
let uid = sym.uid();
if let Some(&id) = self.array_ix.get(&uid) {
if self.arrays[id as usize].assoc {
return Err(Self::assoc_operand(&self.arrays[id as usize].name));
}
return Ok(id);
}
let vt = e.expr_type().ok_or_else(|| BuildError::Missing {
what: format!("type of the array `{}`", sym.name()),
})?;
let info = self.array_info(uid.clone(), sym.name().to_string(), vt, false)?;
let id = self.arrays.len() as u32;
self.arrays.push(info);
self.array_ix.insert(uid, id);
Ok(id)
}
pub(super) fn size_call_array<'d>(e: Expression<'d>) -> Option<Expression<'d>> {
let sub = e.system_subroutine()?;
if !matches!(
sub.known_name_id(),
KnownSystemName::Size | KnownSystemName::ArraySize | KnownSystemName::Num
) {
return None;
}
let arr = *e.operands().first()?;
array_shape(arr.expr_type()?).map(|_| arr)
}
pub(super) fn array_size(&mut self, arr: Expression<'_>) -> Result<Expr, BuildError> {
self.arrays_allowed("an array size")?;
let id = self.array_of(arr)?;
Ok(Expr::Array(ArrayNode::Size(id)))
}
pub(super) fn array_element(
&mut self,
e: Expression<'_>,
base: Expression<'_>,
ty: Ty,
) -> Result<Expr, BuildError> {
let shape = base.expr_type().and_then(array_shape);
let sel = e.selector().ok_or_else(|| BuildError::Missing {
what: "index of an element select".into(),
})?;
match shape {
Some(ArrayShape::Collection) => {
self.arrays_allowed("an array element")?;
let id = self.array_of(base)?;
let index = self.expr(sel)?;
let mut has_var = false;
index.for_each_var(&mut |_, _| has_var = true);
if has_var {
return Err(bad_array(format!(
"a random variable in the index of the array `{}`: an index is a \
constant, a state variable, a loop variable or the size (IEEE \
1800-2023 18.5.7.1)",
self.arrays[id as usize].name
)));
}
let elem_ty = self.arrays[id as usize].elem_ty;
let el = Expr::Array(ArrayNode::Elem {
array: id,
index: Box::new(index),
ty: elem_ty,
});
Ok(if elem_ty == ty {
el
} else {
Expr::Resize(Box::new(el), ty)
})
}
Some(ArrayShape::Assoc) => Err(bad_array(
"an element of an associative array: its indices are not randomizable and it \
has no constraint form (IEEE 1800-2023 18.4)",
)),
Some(ArrayShape::Fixed) => self.fixed_element(e),
None => Err(BuildError::Missing {
what: "array type of an element select base".into(),
}),
}
}
fn fixed_element(&mut self, e: Expression<'_>) -> Result<Expr, BuildError> {
let what = e
.select_value()
.map(strip_conversions)
.and_then(|b| b.referenced_symbol().or_else(|| b.member_symbol()))
.map_or_else(String::new, |s| s.name().to_string());
Err(unsupported(
format!(
"an element of the unpacked array `{what}`, which is not a property (or a \
member of a struct property) of this class"
),
"C14",
))
}
fn foreach_vars(&self, c: Constraint<'_>) -> Result<Vec<String>, BuildError> {
for tree in self.trees {
if let Some(node) = c.syntax(tree) {
let l = LoopConstraintSyntax::cast(node).ok_or_else(|| BuildError::Missing {
what: "`foreach` syntax for a Foreach constraint".into(),
})?;
return Ok(l
.loop_list()
.loop_variables()
.iter()
.map(|v| v.syntax().text().trim().to_string())
.collect());
}
}
Err(BuildError::Missing {
what: "syntax node for a constraint (is its syntax tree passed to the builder?)".into(),
})
}
fn fresh_loop(&mut self) -> LoopId {
let id = self.next_loop;
self.next_loop += 1;
id
}
fn in_scope(
&mut self,
scope: LoopScope,
body: Constraint<'_>,
) -> Result<Vec<Expr>, BuildError> {
self.loops.push(scope);
let r = self.constraint(body);
self.loops.pop();
r
}
pub(super) fn foreach(&mut self, c: Constraint<'_>) -> Result<Vec<Expr>, BuildError> {
let names = self.foreach_vars(c)?;
let kids = c.children();
let arr =
kids.iter()
.find_map(|n| n.as_expression())
.ok_or_else(|| BuildError::Missing {
what: "array expression of a `foreach` constraint".into(),
})?;
let body =
kids.iter()
.find_map(|n| n.as_constraint())
.ok_or_else(|| BuildError::Missing {
what: "body of a `foreach` constraint".into(),
})?;
let at = arr.expr_type().ok_or_else(|| BuildError::Missing {
what: "type of the `foreach` array".into(),
})?;
let shape = array_shape(at);
let nested_fixed = shape == Some(ArrayShape::Fixed)
&& at
.canonical()
.element_type()
.is_some_and(|elem| array_shape(elem) == Some(ArrayShape::Fixed));
if names.len() > 1 || nested_fixed {
let dims = self.static_dims(at, &names)?;
return self.unroll_nd(&dims, body);
}
let [name] = names.as_slice() else {
return Err(BuildError::Missing {
what: "a loop variable of a `foreach` constraint".into(),
});
};
if shape.is_some()
&& at
.canonical()
.element_type()
.is_some_and(|elem| array_shape(elem).is_some())
{
return Err(unsupported("a `foreach` over an array of arrays", "C11"));
}
let int = Ty::signed(32);
match shape {
Some(ArrayShape::Collection) => {
self.arrays_allowed("a `foreach`")?;
let array = self.array_of(arr)?;
let var = self.fresh_loop();
let (r0, b0, rb0) = (
self.real_conds.len() + self.real_dists.len(),
self.before.len(),
self.real_before.len(),
);
let parts = self.in_scope(
LoopScope {
name: name.clone(),
value: LoopBind::Runtime(var, int),
index: None,
},
body,
)?;
if self.real_conds.len() + self.real_dists.len() != r0
|| self.before.len() != b0
|| self.real_before.len() != rb0
{
return Err(unsupported(
"a real `dist` or condition, or a `solve ... before` constraint, inside a \
`foreach` over a dynamic array or queue (it would have to hold once per \
element)",
"C11",
));
}
Ok(vec![Expr::Array(ArrayNode::Foreach {
array,
var,
body: Box::new(and_all(parts)),
})])
}
Some(ArrayShape::Assoc) => Err(bad_array(
"a `foreach` over an associative array: its indices are not randomizable and \
it has no constraint form (IEEE 1800-2023 18.4)",
)),
Some(ArrayShape::Fixed) => {
let r = at.canonical().fixed_range();
self.unroll(name, i64::from(r.left), i64::from(r.right), body)
}
None => {
let width = u32::try_from(at.bit_width()).map_err(|_| BuildError::Missing {
what: "representable width for a `foreach` array".into(),
})?;
let g = Geom::of(at, width, "the `foreach` array")?;
self.unroll(name, g.left, g.right, body)
}
}
}
fn static_dims(
&self,
ty: sv_lang::Type<'_>,
names: &[String],
) -> Result<Vec<(String, i64, i64)>, BuildError> {
let mut dims = Vec::new();
let mut cur = ty;
for (k, name) in names.iter().enumerate() {
let c = cur.canonical();
let (left, right) = match array_shape(cur) {
Some(ArrayShape::Fixed) => {
let r = c.fixed_range();
(i64::from(r.left), i64::from(r.right))
}
None if c.is_integral() => {
let width = u32::try_from(c.bit_width()).map_err(|_| BuildError::Missing {
what: "representable width for a `foreach` array".into(),
})?;
let g = Geom::of(cur, width, "the `foreach` array")?;
(g.left, g.right)
}
_ => {
return Err(unsupported(
format!(
"a multi-dimensional `foreach`: dimension {} is a dynamic array, queue or associative array (only fixed-size dimensions unroll)",
k + 1
),
"C11",
));
}
};
dims.push((name.clone(), left, right));
if k + 1 < names.len() {
cur = c.element_type().ok_or_else(|| {
unsupported(
format!(
"a `foreach` with {} loop variables over an array with fewer \
dimensions",
names.len()
),
"C11",
)
})?;
}
}
Ok(dims)
}
fn unroll_nd(
&mut self,
dims: &[(String, i64, i64)],
body: Constraint<'_>,
) -> Result<Vec<Expr>, BuildError> {
let Some((name, left, right)) = dims.first() else {
return self.constraint(body);
};
if name.is_empty() {
return self.unroll_nd(&dims[1..], body);
}
let idxs: Vec<i64> = if left >= right {
(*right..=*left).rev().collect()
} else {
(*left..=*right).collect()
};
let mut out = Vec::new();
for i in idxs {
self.loops.push(LoopScope {
name: name.clone(),
value: LoopBind::Const(i),
index: None,
});
let r = self.unroll_nd(&dims[1..], body);
self.loops.pop();
out.extend(r?);
}
Ok(out)
}
fn unroll(
&mut self,
name: &str,
left: i64,
right: i64,
body: Constraint<'_>,
) -> Result<Vec<Expr>, BuildError> {
let idxs: Vec<i64> = if left >= right {
(right..=left).rev().collect()
} else {
(left..=right).collect()
};
let mut out = Vec::new();
for i in idxs {
out.extend(self.in_scope(
LoopScope {
name: name.to_string(),
value: LoopBind::Const(i),
index: None,
},
body,
)?);
}
Ok(out)
}
pub(super) fn in_runtime_loop(&self) -> bool {
self.loops
.iter()
.any(|s| matches!(s.value, LoopBind::Runtime(..)))
}
fn scope_of(&self, name: &str) -> Option<&LoopScope> {
self.loops.iter().rev().find(|s| s.name == name)
}
fn bind_expr(&self, b: LoopBind, ty: Ty) -> Result<Expr, BuildError> {
match b {
LoopBind::Runtime(id, lt) => {
let l = Expr::Array(ArrayNode::Loop(id, lt));
Ok(if lt == ty {
l
} else {
Expr::Resize(Box::new(l), ty)
})
}
LoopBind::Const(i) => Ok(iconst(ty, i)),
LoopBind::Elem(x) => Ok(if x.ty() == ty {
x
} else {
Expr::Resize(Box::new(x), ty)
}),
}
}
pub(super) fn loop_ref(&mut self, sym: Symbol<'_>, ty: Ty) -> Result<Expr, BuildError> {
debug_assert_eq!(sym.kind(), SymbolKind::Iterator);
let name = sym.name();
let b = self
.scope_of(name)
.ok_or_else(|| BuildError::Missing {
what: format!("an enclosing `foreach` or array method binding `{name}`"),
})?
.value
.clone();
if matches!(b, LoopBind::Runtime(..)) {
self.arrays_allowed("a loop variable")?;
}
self.bind_expr(b, ty)
}
pub(super) fn iterator_index(&mut self, e: Expression<'_>, ty: Ty) -> Result<Expr, BuildError> {
let this = *e.operands().first().ok_or_else(|| BuildError::Missing {
what: "iterator of `index`".into(),
})?;
let name = this
.referenced_symbol()
.map(|s| s.name().to_string())
.ok_or_else(|| BuildError::Missing {
what: "symbol of the iterator of `index`".into(),
})?;
let b = self
.scope_of(&name)
.and_then(|s| s.index.clone())
.ok_or_else(|| BuildError::Missing {
what: format!("an array method iterator `{name}` for `.index`"),
})?;
self.arrays_allowed("an iterator index")?;
self.bind_expr(b, ty)
}
pub(super) fn array_reduction(
&mut self,
e: Expression<'_>,
op: ReduceOp,
ty: Ty,
) -> Result<Expr, BuildError> {
let arr = e
.operands()
.into_iter()
.find(|o| o.expr_type().is_some_and(|t| array_shape(t).is_some()))
.ok_or_else(|| BuildError::Missing {
what: "array operand of a reduction method".into(),
})?;
if arr.expr_type().and_then(array_shape) == Some(ArrayShape::Fixed) {
return self.fixed_reduction(e, arr, op, ty);
}
self.arrays_allowed("an array reduction")?;
let array = self.array_of(arr)?;
let elem_ty = self.arrays[array as usize].elem_ty;
let var = self.fresh_loop();
let index_var = self.fresh_loop();
let body = match e.iterator_expr() {
Some(w) => {
let iter = e.iterator_var().ok_or_else(|| BuildError::Missing {
what: "iterator variable of a reduction's `with` clause".into(),
})?;
self.loops.push(LoopScope {
name: iter.name().to_string(),
value: LoopBind::Runtime(var, elem_ty),
index: Some(LoopBind::Runtime(index_var, Ty::signed(32))),
});
let b = self.expr(w);
self.loops.pop();
b?
}
None => Expr::Array(ArrayNode::Loop(var, elem_ty)),
};
let body = if body.ty() == ty {
body
} else {
coerce(body, ty)
};
Ok(Expr::Array(ArrayNode::Reduce {
op,
array,
var,
index_var,
body: Box::new(body),
}))
}
fn fixed_reduction(
&mut self,
e: Expression<'_>,
arr: Expression<'_>,
op: ReduceOp,
ty: Ty,
) -> Result<Expr, BuildError> {
let at = arr
.expr_type()
.ok_or_else(|| BuildError::Missing {
what: "type of the reduced array".into(),
})?
.canonical();
let r = at.fixed_unpacked_array_range();
let (lo, hi) = (
i64::from(r.left).min(i64::from(r.right)),
i64::from(r.left).max(i64::from(r.right)),
);
let et = at.element_type().ok_or_else(|| BuildError::Missing {
what: "element type of the reduced array".into(),
})?;
let elem_ty = ty_of(Some(et), "an element of the reduced array")?;
let mut acc: Option<Expr> = None;
for i in lo..=hi {
let item = self.aggregate_element(arr, i, elem_ty)?;
let body = match e.iterator_expr() {
Some(w) => {
let iter = e.iterator_var().ok_or_else(|| BuildError::Missing {
what: "iterator variable of a reduction's `with` clause".into(),
})?;
self.loops.push(LoopScope {
name: iter.name().to_string(),
value: LoopBind::Elem(item),
index: Some(LoopBind::Const(i)),
});
let b = self.expr(w);
self.loops.pop();
b?
}
None => item,
};
let body = if body.ty() == ty {
body
} else {
coerce(body, ty)
};
acc = Some(match acc {
None => body,
Some(a) => {
let bop = match op {
ReduceOp::Sum => BinOp::Add,
ReduceOp::Product => BinOp::Mul,
ReduceOp::And => BinOp::And,
ReduceOp::Or => BinOp::Or,
ReduceOp::Xor => BinOp::Xor,
};
bin(bop, a, body, ty)
}
});
}
acc.ok_or_else(|| BuildError::Missing {
what: "elements of the reduced array".into(),
})
}
pub(super) fn inside_array(
&mut self,
left: &Expr,
arr: Expression<'_>,
) -> Result<Expr, BuildError> {
let eq = |left: &Expr, x: Expr| {
let t = common_ty(left.ty(), x.ty());
bin(BinOp::Eq, coerce(left.clone(), t), coerce(x, t), Ty::BIT)
};
let at = arr.expr_type().ok_or_else(|| BuildError::Missing {
what: "type of an `inside` array".into(),
})?;
match array_shape(at) {
Some(ArrayShape::Fixed) => {
let c = at.canonical();
let r = c.fixed_unpacked_array_range();
let (lo, hi) = (
i64::from(r.left).min(i64::from(r.right)),
i64::from(r.left).max(i64::from(r.right)),
);
let et = c.element_type().ok_or_else(|| BuildError::Missing {
what: "element type of an `inside` array".into(),
})?;
let elem_ty = ty_of(Some(et), "an element of an `inside` array")?;
let mut acc: Option<Expr> = None;
for i in lo..=hi {
let item = self.aggregate_element(arr, i, elem_ty)?;
let term = eq(left, item);
acc = Some(match acc {
None => term,
Some(prev) => bin(BinOp::LogOr, prev, term, Ty::BIT),
});
}
acc.ok_or_else(|| BuildError::Missing {
what: "elements of an `inside` array".into(),
})
}
Some(ArrayShape::Collection) => {
self.arrays_allowed("an `inside` over an array")?;
let array = self.array_of(arr)?;
let elem_ty = self.arrays[array as usize].elem_ty;
let var = self.fresh_loop();
let index_var = self.fresh_loop();
let body = eq(left, Expr::Array(ArrayNode::Loop(var, elem_ty)));
Ok(Expr::Array(ArrayNode::Reduce {
op: ReduceOp::Or,
array,
var,
index_var,
body: Box::new(body),
}))
}
Some(ArrayShape::Assoc) | None => Err(bad_array(
"an associative array in an `inside` set: its elements are not constraint \
operands (IEEE 1800-2023 18.4)",
)),
}
}
pub(super) fn array_call(
&mut self,
e: Expression<'_>,
ty: Ty,
) -> Option<Result<Expr, BuildError>> {
let sub = e.system_subroutine()?;
let op = match sub.known_name_id() {
KnownSystemName::Size | KnownSystemName::ArraySize | KnownSystemName::Num => {
let arr = Self::size_call_array(e)?;
return Some(self.array_size(arr));
}
KnownSystemName::Index => return Some(self.iterator_index(e, ty)),
KnownSystemName::Sum => ReduceOp::Sum,
KnownSystemName::Product => ReduceOp::Product,
KnownSystemName::And => ReduceOp::And,
KnownSystemName::Or => ReduceOp::Or,
KnownSystemName::XOr => ReduceOp::Xor,
_ => return None,
};
Some(self.array_reduction(e, op, ty))
}
}