use super::arrays::{ArrayShape, array_shape, const_i64};
use super::real::{real_kind, state_ty, strip_conversions};
use super::*;
use crate::constraints::ir::{ArrayNode, Assignment, eval};
use crate::constraints::real_domain::{FloatKind as Fk, RealExpr};
use crate::state::storage::{field_slice, storage_width};
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum Step {
Index(i64),
Field(u32),
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Declared {
Int(u32),
Real(u32),
}
#[derive(Clone, Copy)]
pub(super) enum RawStep<'d> {
Index(Expression<'d>),
Const(i64),
Field(u32),
}
#[derive(Clone)]
pub(super) struct Chain<'d> {
pub root: Symbol<'d>,
pub steps: Vec<RawStep<'d>>,
}
pub(super) fn aggregate_chain<'d>(
e: Expression<'d>,
forced: &HashSet<SymbolId>,
) -> Option<Chain<'d>> {
let e = strip_conversions(e);
let kind = e.kind();
if kind == ExpressionKind::NamedValue {
let sym = e.referenced_symbol()?;
return (sym.kind() == SymbolKind::ClassProperty || forced.contains(&sym.uid())).then(
|| Chain {
root: sym,
steps: Vec::new(),
},
);
}
if kind == ExpressionKind::MemberAccess {
let base = e.left()?;
let member = e.member_symbol()?;
let is_this = strip_conversions(base).kind() == ExpressionKind::NamedValue
&& strip_conversions(base)
.referenced_symbol()
.is_some_and(|s| s.name() == "this");
if is_this {
return (member.kind() == SymbolKind::ClassProperty).then(|| Chain {
root: member,
steps: Vec::new(),
});
}
let bt = base.expr_type()?.canonical();
if !bt.is_unpacked_struct() {
return None;
}
let index = bt.fields().position(|f| f.name() == member.name())?;
let mut chain = aggregate_chain(base, forced)?;
chain.steps.push(RawStep::Field(u32::try_from(index).ok()?));
return Some(chain);
}
if kind == ExpressionKind::ElementSelect {
let base = e.select_value()?;
if array_shape(base.expr_type()?) != Some(ArrayShape::Fixed) {
return None;
}
let mut chain = aggregate_chain(base, forced)?;
chain.steps.push(RawStep::Index(e.selector()?));
return Some(chain);
}
None
}
pub fn is_aggregate_type(t: Type<'_>) -> bool {
let c = t.canonical();
c.is_unpacked_struct() || array_shape(c) == Some(ArrayShape::Fixed)
}
pub fn property_is_random(sym: Symbol<'_>) -> bool {
if sym.rand_mode() != RandMode::None {
return true;
}
sym.value_type().is_some_and(has_random_member)
}
fn has_random_member(t: Type<'_>) -> bool {
let c = t.canonical();
if c.is_unpacked_struct() {
return c.fields().any(|f| {
f.field_rand_mode() != RandMode::None || f.value_type().is_some_and(has_random_member)
});
}
array_shape(c) == Some(ArrayShape::Fixed) && c.element_type().is_some_and(has_random_member)
}
#[derive(Clone, Copy, Debug)]
pub(super) enum LeafKind {
Int(Ty),
Real(Fk),
Vif,
Skipped,
Other,
}
#[derive(Clone)]
pub(super) struct LeafRef<'d> {
pub root: SymbolId,
pub name: String,
pub path: Vec<Step>,
pub window: Window,
pub mode: RandMode,
pub kind: LeafKind,
pub vt: Type<'d>,
}
impl LeafRef<'_> {
pub(super) fn randc(&self) -> bool {
self.mode == RandMode::RandC
}
}
pub(super) enum Sel<'d> {
Leaf(LeafRef<'d>),
Pick {
index: Expr,
low: i64,
elems: Vec<Sel<'d>>,
},
}
#[derive(Clone)]
struct Cursor<'d> {
root: SymbolId,
whole: u64,
ty: Type<'d>,
mode: RandMode,
path: Vec<Step>,
name: String,
bit: u64,
}
fn missing(what: impl Into<String>) -> BuildError {
BuildError::Missing { what: what.into() }
}
impl<'d> Cursor<'d> {
fn of(root: Symbol<'d>) -> Result<Cursor<'d>, BuildError> {
let ty = root
.value_type()
.ok_or_else(|| missing(format!("value type for `{}`", root.name())))?;
let whole = storage_width(&ty).ok_or_else(|| {
missing(format!(
"stored width of `{}` of type `{}`",
root.name(),
ty.to_sv_string()
))
})?;
Ok(Cursor {
root: root.uid(),
whole,
ty,
mode: root.rand_mode(),
path: Vec::new(),
name: root.name().to_string(),
bit: 0,
})
}
fn field(&self, i: u32) -> Result<Cursor<'d>, BuildError> {
let t = self.ty.canonical();
if !t.is_unpacked_struct() {
return Err(missing(format!(
"`{}` is not an unpacked struct",
self.name
)));
}
let f = t
.fields()
.nth(i as usize)
.ok_or_else(|| missing(format!("member {i} of `{}`", self.name)))?;
let (lsb, _) = field_slice(&t, i)
.ok_or_else(|| missing(format!("stored position of member {i} of `{}`", self.name)))?;
let ty = f
.value_type()
.ok_or_else(|| missing(format!("type of `{}.{}`", self.name, f.name())))?;
let mut path = self.path.clone();
path.push(Step::Field(i));
Ok(Cursor {
root: self.root.clone(),
whole: self.whole,
ty,
mode: match f.field_rand_mode() {
RandMode::None => self.mode,
own => own,
},
path,
name: format!("{}.{}", self.name, f.name()),
bit: self.bit + lsb,
})
}
fn dim(&self) -> Result<(i64, i64, Type<'d>), BuildError> {
let t = self.ty.canonical();
match array_shape(t) {
Some(ArrayShape::Fixed) => {}
Some(ArrayShape::Collection | ArrayShape::Assoc) => {
return Err(unsupported(
format!(
"`{}`, whose size is only known at run time (a dynamic array, queue or \
associative array), as an element of an aggregate",
self.name
),
"C14",
));
}
None => {
return Err(BuildError::OutOfRangeSelect {
what: format!("`{}` is not an array, so it has no elements", self.name),
});
}
}
let r = t.fixed_unpacked_array_range();
let elem = t
.element_type()
.ok_or_else(|| missing(format!("element type of the array `{}`", self.name)))?;
Ok((
i64::from(r.left).min(i64::from(r.right)),
i64::from(r.left).max(i64::from(r.right)),
elem,
))
}
fn index(&self, i: i64) -> Result<Cursor<'d>, BuildError> {
let (lo, hi, elem) = self.dim()?;
if !(lo..=hi).contains(&i) {
let r = self.ty.canonical().fixed_unpacked_array_range();
return Err(BuildError::OutOfRangeSelect {
what: format!("`{}` index {i} outside [{}:{}]", self.name, r.left, r.right),
});
}
let ew = storage_width(&elem)
.ok_or_else(|| missing(format!("stored width of an element of `{}`", self.name)))?;
let mut path = self.path.clone();
path.push(Step::Index(i));
Ok(Cursor {
root: self.root.clone(),
whole: self.whole,
ty: elem,
mode: self.mode,
path,
name: format!("{}[{i}]", self.name),
bit: self.bit + (i - lo) as u64 * ew,
})
}
fn leaf(self) -> Result<LeafRef<'d>, BuildError> {
let width = storage_width(&self.ty)
.ok_or_else(|| missing(format!("stored width of `{}`", self.name)))?;
let narrow = |v: u64| {
u32::try_from(v).map_err(|_| {
missing(format!(
"a window of `{}` within 32-bit offsets (the property is {} bits)",
self.name, self.whole
))
})
};
let window = Window {
bit: narrow(self.bit)?,
width: narrow(width)?,
field_width: narrow(self.whole)?,
};
let c = self.ty.canonical();
let kind = if let Some(k) = real_kind(Some(self.ty)) {
LeafKind::Real(k)
} else if c.is_virtual_interface() {
LeafKind::Vif
} else if c.is_integral() {
LeafKind::Int(ty_of(Some(self.ty), &format!("`{}`", self.name))?)
} else if c.is_string() || c.is_chandle() {
LeafKind::Skipped
} else {
LeafKind::Other
};
Ok(LeafRef {
root: self.root,
name: self.name,
path: self.path,
window,
mode: self.mode,
kind,
vt: self.ty,
})
}
fn random_leaves(self, out: &mut Vec<LeafRef<'d>>) -> Result<(), BuildError> {
let t = self.ty.canonical();
if t.is_unpacked_struct() {
for i in 0..t.fields().count() {
self.field(i as u32)?.random_leaves(out)?;
}
return Ok(());
}
if array_shape(t) == Some(ArrayShape::Fixed) {
let (lo, hi, _) = self.dim()?;
for i in lo..=hi {
self.index(i)?.random_leaves(out)?;
}
return Ok(());
}
if self.mode == RandMode::None || t.is_unpacked_union() {
return Ok(());
}
let leaf = self.leaf()?;
match leaf.kind {
LeafKind::Int(_) | LeafKind::Real(_) => out.push(leaf),
LeafKind::Skipped => {}
LeafKind::Vif => return Err(BuildError::VirtualInterfaceRand { name: leaf.name }),
LeafKind::Other => {
return Err(unsupported(
format!(
"the random member `{}` of type `{}` inside an unpacked struct or array \
(a class handle, queue, dynamic array or associative array nested in a \
flat aggregate cannot be randomized)",
leaf.name,
leaf.vt.to_sv_string()
),
"C14",
));
}
}
Ok(())
}
}
impl Builder<'_> {
fn cursor_of<'d>(&self, sym: Symbol<'d>) -> Result<Cursor<'d>, BuildError> {
let mut cur = Cursor::of(sym)?;
if self.forced_roots.contains(&cur.root) {
cur.mode = RandMode::Rand;
}
Ok(cur)
}
pub fn declare_aggregate_forced(
&mut self,
sym: Symbol<'_>,
) -> Result<Vec<Declared>, BuildError> {
self.forced_roots.insert(sym.uid());
self.declare_rand_aggregate(sym)
}
pub fn declare_rand_aggregate(&mut self, sym: Symbol<'_>) -> Result<Vec<Declared>, BuildError> {
let cur = self.cursor_of(sym)?;
if !is_aggregate_type(cur.ty) {
return Err(missing(format!(
"an unpacked struct or fixed-size array type for `{}` (use `declare_rand`)",
sym.name()
)));
}
let mut leaves = Vec::new();
cur.random_leaves(&mut leaves)?;
leaves.iter().map(|l| self.declare_leaf(l)).collect()
}
pub fn declare_rand_element(
&mut self,
array: Symbol<'_>,
idx: &[i64],
) -> Result<u32, BuildError> {
let mut cur = Cursor::of(array)?;
for &i in idx {
cur = cur.index(i)?;
}
if is_aggregate_type(cur.ty) {
return Err(BuildError::OutOfRangeSelect {
what: format!(
"`{}` is still an aggregate: name one index per unpacked dimension",
cur.name
),
});
}
if cur.mode == RandMode::None {
return Err(BuildError::NotRand { name: cur.name });
}
if idx.is_empty() {
return Err(BuildError::OutOfRangeSelect {
what: format!(
"`{}` is not an array element (use `declare_rand`)",
cur.name
),
});
}
let leaf = cur.leaf()?;
match self.declare_leaf(&leaf)? {
Declared::Int(id) => Ok(id),
Declared::Real(_) => Err(BuildError::RealConstraint {
what: format!(
"`{}` is real: it is a real random variable, not an integral element",
leaf.name
),
}),
}
}
fn declare_leaf(&mut self, leaf: &LeafRef<'_>) -> Result<Declared, BuildError> {
let key = (leaf.root.clone(), leaf.path.clone());
match leaf.kind {
LeafKind::Int(_) => {
if let Some(&id) = self.elem_ix.get(&key) {
return Ok(Declared::Int(id));
}
let id = self.new_rand_var(
leaf.root.clone(),
leaf.name.clone(),
leaf.vt,
leaf.randc(),
leaf.path.clone(),
Some(leaf.window),
)?;
self.elem_ix.insert(key, id);
Ok(Declared::Int(id))
}
LeafKind::Real(kind) => {
if let Some(&id) = self.real_leaf_ix.get(&key) {
return Ok(Declared::Real(id));
}
if leaf.randc() {
return Err(BuildError::RealConstraint {
what: format!(
"`randc` variable `{}` is real; `randc` is for integral types only \
(IEEE 1800-2023 18.4.2)",
leaf.name
),
});
}
let id = self.real_vars.len() as u32;
self.real_vars.push(RealVarInfo {
symbol: leaf.root.clone(),
name: leaf.name.clone(),
kind,
window: Some(leaf.window),
});
self.real_leaf_ix.insert(key, id);
Ok(Declared::Real(id))
}
LeafKind::Vif | LeafKind::Skipped | LeafKind::Other => Err(missing(format!(
"a randomizable value for `{}` of type `{}`",
leaf.name,
leaf.vt.to_sv_string()
))),
}
}
fn resolve_chain<'d>(&mut self, chain: &Chain<'d>) -> Result<Sel<'d>, BuildError> {
let cur = self.cursor_of(chain.root)?;
self.walk(cur, &chain.steps)
}
fn walk<'d>(&mut self, cur: Cursor<'d>, steps: &[RawStep<'d>]) -> Result<Sel<'d>, BuildError> {
let Some((first, rest)) = steps.split_first() else {
return Ok(Sel::Leaf(cur.leaf()?));
};
match *first {
RawStep::Field(i) => self.walk(cur.field(i)?, rest),
RawStep::Const(i) => self.walk(cur.index(i)?, rest),
RawStep::Index(e) => {
let ix = self.expr(e)?;
if let Some(i) = Self::constant_index(&ix) {
let (lo, hi, _) = cur.dim()?;
if (lo..=hi).contains(&i) {
return self.walk(cur.index(i)?, rest);
}
}
self.not_in_function(
"an element of an unpacked array property at a computed index",
)?;
self.arrays_allowed("an element of an unpacked array property")?;
let mut has_var = false;
ix.for_each_var(&mut |_, _| has_var = true);
if has_var {
return Err(BuildError::BadArray {
what: 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)",
cur.name
),
});
}
let (lo, hi, _) = cur.dim()?;
let mut elems = Vec::new();
for i in lo..=hi {
elems.push(self.walk(cur.index(i)?, rest)?);
}
Ok(Sel::Pick {
index: ix,
low: lo,
elems,
})
}
}
}
pub(super) fn constant_index(ix: &Expr) -> Option<i64> {
if ix.any_node(&mut |n| matches!(n, Expr::Var(..) | Expr::State(..) | Expr::Array(_))) {
return None;
}
let v = eval(ix, &Assignment::default()).ok()?;
const_i64(ix.ty(), &v)
}
pub(super) fn declared_leaf<'d>(
&mut self,
chain: Chain<'d>,
what: &str,
) -> Result<(LeafRef<'d>, Option<Declared>), BuildError> {
let Sel::Leaf(leaf) = self.resolve_chain(&chain)? else {
return Err(unsupported(
format!("{what} that selects an element at a computed index"),
"C14",
));
};
let key = (leaf.root.clone(), leaf.path.clone());
let declared = match leaf.kind {
LeafKind::Int(_) => self.elem_ix.get(&key).map(|&id| Declared::Int(id)),
LeafKind::Real(_) => self.real_leaf_ix.get(&key).map(|&id| Declared::Real(id)),
LeafKind::Vif | LeafKind::Skipped | LeafKind::Other => {
return Err(BuildError::NonIntegral {
what: format!("`{}` of type `{}`", leaf.name, leaf.vt.to_sv_string()),
});
}
};
Ok((leaf, declared))
}
fn undeclared(leaf: &LeafRef<'_>) -> BuildError {
unsupported(
format!(
"the random variable `{}`, a leaf of an aggregate property that was not declared \
random (`declare_rand_aggregate`)",
leaf.name
),
"C14",
)
}
fn leaf_state(&mut self, leaf: &LeafRef<'_>, ty: Ty) -> Expr {
let key = (leaf.root.clone(), leaf.path.clone());
if let Some(&id) = self.leaf_state_ix.get(&key) {
return Expr::State(id, self.states[id as usize].ty);
}
let id = self.states.len() as u32;
self.states.push(StateInfo {
root_bit: 0,
symbol: leaf.root.clone(),
name: leaf.name.clone(),
ty,
via: Vec::new(),
window: Some(leaf.window),
class_of: false,
});
self.leaf_state_ix.insert(key, id);
Expr::State(id, ty)
}
fn leaf_expr(&mut self, leaf: &LeafRef<'_>) -> Result<Expr, BuildError> {
let LeafKind::Int(ty) = leaf.kind else {
return Err(BuildError::NonIntegral {
what: format!("`{}` of type `{}`", leaf.name, leaf.vt.to_sv_string()),
});
};
if leaf.mode == RandMode::None {
return Ok(self.leaf_state(leaf, ty));
}
let key = (leaf.root.clone(), leaf.path.clone());
let &id = self
.elem_ix
.get(&key)
.ok_or_else(|| Self::undeclared(leaf))?;
if self.frame.is_some() {
return Err(self.rand_read_in_function(&leaf.name));
}
Ok(Expr::Var(id, self.vars[id as usize].ty))
}
fn sel_expr(&mut self, sel: Sel<'_>, ty: Ty) -> Result<Expr, BuildError> {
match sel {
Sel::Leaf(leaf) => {
let x = self.leaf_expr(&leaf)?;
Ok(if x.ty() == ty {
x
} else {
Expr::Resize(Box::new(x), ty)
})
}
Sel::Pick { index, low, elems } => {
let elems = elems
.into_iter()
.map(|s| self.sel_expr(s, ty))
.collect::<Result<Vec<_>, _>>()?;
Ok(Expr::Array(ArrayNode::Pick {
index: Box::new(index),
low,
elems,
ty,
}))
}
}
}
pub(super) fn aggregate_value(&mut self, chain: Chain<'_>, ty: Ty) -> Result<Expr, BuildError> {
let sel = self.resolve_chain(&chain)?;
self.sel_expr(sel, ty)
}
pub(super) fn aggregate_bitstream(&mut self, chain: Chain<'_>) -> Result<Expr, BuildError> {
let mut parts = Vec::new();
let mut cur = self.cursor_of(chain.root)?;
let mut rest: &[RawStep<'_>] = &chain.steps;
while let Some((first, tail)) = rest.split_first() {
cur = match *first {
RawStep::Field(i) => cur.field(i)?,
RawStep::Const(i) => cur.index(i)?,
RawStep::Index(e) => {
let ix = self.expr(e)?;
let i = Self::constant_index(&ix).ok_or_else(|| {
unsupported("a bit-stream cast of an element at a computed index", "C2")
})?;
cur.index(i)?
}
};
rest = tail;
}
self.stream_leaves(cur, &mut parts)?;
let w = parts.iter().map(|p| p.ty().width).sum();
Ok(Expr::Concat(parts, Ty::unsigned(w)))
}
fn stream_leaves(&mut self, cur: Cursor<'_>, out: &mut Vec<Expr>) -> Result<(), BuildError> {
let t = cur.ty.canonical();
if t.is_unpacked_struct() {
for i in 0..t.fields().count() {
self.stream_leaves(cur.field(i as u32)?, out)?;
}
} else if array_shape(t) == Some(ArrayShape::Fixed) {
let r = t.fixed_unpacked_array_range();
let (l, h) = (i64::from(r.left), i64::from(r.right));
let idx: Vec<i64> = if l <= h {
(l..=h).collect()
} else {
(h..=l).rev().collect()
};
for i in idx {
self.stream_leaves(cur.index(i)?, out)?;
}
} else {
let leaf = cur.leaf()?;
let LeafKind::Int(ty) = leaf.kind else {
return Err(BuildError::NonIntegral {
what: format!(
"`{}` of type `{}` in a bit-stream cast",
leaf.name,
leaf.vt.to_sv_string()
),
});
};
let x = self.leaf_expr(&leaf)?;
out.push(Expr::Resize(Box::new(x), Ty::unsigned(ty.width)));
}
Ok(())
}
pub(super) fn aggregate_element(
&mut self,
arr: Expression<'_>,
i: i64,
ty: Ty,
) -> Result<Expr, BuildError> {
let mut chain = aggregate_chain(arr, &self.forced_roots).ok_or_else(|| {
unsupported(
"a method over an unpacked array that is not a property (or a member of a struct \
property) of this class",
"C14",
)
})?;
chain.steps.push(RawStep::Const(i));
self.aggregate_value(chain, ty)
}
pub(super) fn real_aggregate(
&mut self,
chain: Chain<'_>,
kind: Fk,
) -> Result<RealExpr, BuildError> {
let leaf = match self.resolve_chain(&chain)? {
Sel::Leaf(leaf) => leaf,
Sel::Pick { .. } => {
return Err(BuildError::RealConstraint {
what: "a real element of an array at a computed index (only an integral \
element can be chosen by a state or loop variable)"
.into(),
});
}
};
if !matches!(leaf.kind, LeafKind::Real(_)) {
return Err(missing(format!("a real value for `{}`", leaf.name)));
}
if leaf.mode == RandMode::None {
let st = self.leaf_state(&leaf, state_ty(kind));
return self.real_state(st, kind);
}
let key = (leaf.root.clone(), leaf.path.clone());
let &id = self
.real_leaf_ix
.get(&key)
.ok_or_else(|| Self::undeclared(&leaf))?;
Ok(RealExpr::Var(id))
}
}