use std::collections::{BTreeMap, BTreeSet, BinaryHeap};
use std::ops::Range;
use sv_lang::kinds::{AssertionExprKind, ExpressionKind};
use sv_lang::nodes::{
AcceptOnPropertyExprSyntax, IdentifierNameSyntax, SelectorSyntax, StrongWeakPropertyExprSyntax,
UnaryPropertyExprSyntax, UnarySelectPropertyExprSyntax,
};
use sv_lang::{ArgumentDirection, AssertionExpr, AstNode, Expression, SemNode, Symbol};
use sv_sim_sim::sva::ir::{
self, AbortMode, ClockId, LeafId, LocalVarId, ThreadStateId, Transition,
};
use sv_sim_sim::sva::property::{
CaseArm, DeclId, NvRule, Prop, PropDecl, PropId, PropertyIr, SeqRef, SequenceClass, Strength,
classify_sequence,
};
use super::super::op::{AssertionBinaryOp as B, AssertionUnaryOp as U, Op, peel_property_parens};
use super::super::ranges;
use super::super::trees::{Trees, key};
use super::{
LeafTable, ScopeVar, Span, actual_syntax, add_match_edges, blank_automaton, build_node,
collect_end_points, collect_local_reads, enter_instance, formal_reference_origin,
leave_instance, prune_from,
};
use crate::error::LoweringError;
pub fn build_property<'d>(
root: AssertionExpr<'d>,
root_clock: Option<SemNode<'d>>,
tree: Trees<'_>,
scope: &Symbol<'d>,
) -> Result<(PropertyIr, LeafTable<'d>), LoweringError> {
build_property_as(root, root_clock, tree, scope, false, false, None)
}
pub fn build_property_as<'d>(
root: AssertionExpr<'d>,
root_clock: Option<SemNode<'d>>,
tree: Trees<'_>,
scope: &Symbol<'d>,
cover_sequence: bool,
root_inferred: bool,
checker_frame: Option<ranges::FormalFrame>,
) -> Result<(PropertyIr, LeafTable<'d>), LoweringError> {
let mut leaves = LeafTable::new(root_clock);
leaves.root_inferred = root_inferred;
if let Some(f) = checker_frame {
leaves.env.push(f);
}
leaves.future_gclk = crate::sva::gclk::check(root, scope)?;
let mut pb = Pb {
a: blank_automaton(),
leaves,
props: Vec::new(),
has_call: Vec::new(),
nv_rules: Vec::new(),
decls: Vec::new(),
frames: Vec::new(),
edges: BTreeSet::new(),
sites: BTreeMap::new(),
adv: 0,
top: true,
cover_sequence,
seq_top: false,
disable: None,
tree,
scope: *scope,
};
let root = pb.build_prop(root.as_sem_node())?;
Ok(pb.finish(root))
}
pub fn build_sequence_watcher<'d>(
inst: sv_lang::Expression<'d>,
root_clock: Option<SemNode<'d>>,
tree: Trees<'_>,
scope: &Symbol<'d>,
) -> Result<(PropertyIr, LeafTable<'d>), LoweringError> {
let mut leaves = LeafTable::new(root_clock);
leaves.root_inferred = root_clock.is_some();
let mut pb = Pb {
a: blank_automaton(),
leaves,
props: Vec::new(),
has_call: Vec::new(),
nv_rules: Vec::new(),
decls: Vec::new(),
frames: Vec::new(),
edges: BTreeSet::new(),
sites: BTreeMap::new(),
adv: 0,
top: true,
cover_sequence: true,
seq_top: false,
disable: None,
tree,
scope: *scope,
};
let root = pb.build_prop(inst.as_sem_node())?;
Ok(pb.finish(root))
}
type DeclKey = (usize, usize);
#[derive(Clone, Debug, PartialEq, Eq)]
struct Root {
text: DeclKey,
home: Option<usize>,
}
struct PFrame {
name: String,
key: DeclKey,
formals: Vec<(String, Option<Root>)>,
locals: Vec<(String, LocalVarId, LocalVarId)>,
has_inits: bool,
entry_adv: u32,
decl: Option<DeclId>,
}
fn is_local_formal<'d>(args: &[sv_lang::AssertionInstanceArg<'d>], sym: &Symbol<'d>) -> bool {
args.iter()
.any(|g| g.port.assertion_port_is_local_var() && g.port.name() == sym.name())
}
struct Bound {
root: Option<Root>,
mixed: bool,
}
#[derive(Clone)]
struct ArgSite {
name: String,
local: bool,
text: DeclKey,
bare: Option<String>,
idents: Vec<String>,
}
struct Pb<'d, 't> {
a: ir::Automaton,
leaves: LeafTable<'d>,
props: Vec<Prop>,
has_call: Vec<bool>,
nv_rules: Vec<(PropId, NvRule)>,
decls: Vec<PropDecl>,
frames: Vec<PFrame>,
edges: BTreeSet<(DeclKey, DeclKey)>,
sites: BTreeMap<DeclKey, Vec<ArgSite>>,
adv: u32,
top: bool,
cover_sequence: bool,
seq_top: bool,
disable: Option<LeafId>,
tree: Trees<'t>,
scope: Symbol<'d>,
}
fn operands(p: &Prop) -> Vec<PropId> {
match p {
Prop::Seq { .. } | Prop::Call { .. } => Vec::new(),
Prop::Not(x) => vec![*x],
Prop::And(x, y) | Prop::Or(x, y) => vec![*x, *y],
Prop::If { then, els, .. } => std::iter::once(*then).chain(els.iter().copied()).collect(),
Prop::Case { arms, default, .. } => arms
.iter()
.map(|arm| arm.body)
.chain(default.iter().copied())
.collect(),
Prop::Implication { cons, .. } => vec![*cons],
Prop::Nexttime { p, .. } | Prop::Always { p, .. } | Prop::Eventually { p, .. } => {
vec![*p]
}
Prop::Until { p, q, .. } => vec![*p, *q],
Prop::Abort { p, .. } | Prop::Clocked { p, .. } => vec![*p],
Prop::Let { body, .. } => vec![*body],
}
}
fn operands_mut(p: &mut Prop) -> Vec<&mut PropId> {
match p {
Prop::Seq { .. } | Prop::Call { .. } => Vec::new(),
Prop::Not(x) => vec![x],
Prop::And(x, y) | Prop::Or(x, y) => vec![x, y],
Prop::If { then, els, .. } => std::iter::once(then).chain(els.iter_mut()).collect(),
Prop::Case { arms, default, .. } => arms
.iter_mut()
.map(|arm| &mut arm.body)
.chain(default.iter_mut())
.collect(),
Prop::Implication { cons, .. } => vec![cons],
Prop::Nexttime { p, .. } | Prop::Always { p, .. } | Prop::Eventually { p, .. } => vec![p],
Prop::Until { p, q, .. } => vec![p, q],
Prop::Abort { p, .. } | Prop::Clocked { p, .. } => vec![p],
Prop::Let { body, .. } => vec![body],
}
}
fn seqs_mut(p: &mut Prop) -> Option<&mut SeqRef> {
match p {
Prop::Seq { seq, .. } => Some(seq),
Prop::Implication { ante, .. } => Some(ante),
Prop::Not(_)
| Prop::And(..)
| Prop::Or(..)
| Prop::If { .. }
| Prop::Case { .. }
| Prop::Nexttime { .. }
| Prop::Always { .. }
| Prop::Eventually { .. }
| Prop::Until { .. }
| Prop::Abort { .. }
| Prop::Let { .. }
| Prop::Clocked { .. }
| Prop::Call { .. } => None,
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
enum Bracket {
Absent,
Count(u32),
Range(u32, Option<u32>),
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
enum Position {
Property,
OverlappedAntecedent,
NonOverlappedAntecedent,
}
impl<'d, 't> Pb<'d, 't> {
fn err(&self, msg: &str) -> LoweringError {
LoweringError::unsupported_at(msg, &self.scope)
}
fn push_nv(&mut self, p: Prop, rule: NvRule) -> PropId {
let id = self.push(p);
self.nv_rules.push((id, rule));
id
}
fn push(&mut self, p: Prop) -> PropId {
let calls = matches!(p, Prop::Call { .. })
|| operands(&p).iter().any(|id| self.has_call[id.0 as usize]);
let id = PropId(self.props.len() as u32);
self.props.push(p);
self.has_call.push(calls);
id
}
fn written_event(&self, node: SemNode<'d>) -> Option<SemNode<'d>> {
if let Some(e) = node.as_expression()
&& is_property_instance(e)
{
return node
.children()
.into_iter()
.find(|c| c.as_assertion_expr().is_some())
.and_then(|b| self.written_event(b));
}
let x = node.as_assertion_expr()?;
match (x.kind(), x.children().as_slice()) {
(AssertionExprKind::Simple, [only]) => self.written_event(*only),
(AssertionExprKind::Clocking, [event, _])
if !super::is_inferred_clock_event(*event) =>
{
Some(*event)
}
_ => None,
}
}
fn leads_with_written_clock(&self, node: SemNode<'d>) -> bool {
self.leads_with_clock_in(node, true)
}
fn leads_with_clock_in(&self, node: SemNode<'d>, top: bool) -> bool {
if self.written_event(node).is_some() {
return true;
}
if let Some(e) = node.as_expression() {
return top
&& e.kind() == ExpressionKind::AssertionInstance
&& node
.children()
.into_iter()
.find(|c| c.as_assertion_expr().is_some())
.is_some_and(|b| self.leads_with_written_clock(b));
}
let Some(x) = node.as_assertion_expr() else {
return false;
};
if x.kind() == AssertionExprKind::Simple
&& let [only] = x.children().as_slice()
{
return self.leads_with_clock_in(*only, top);
}
let kind = x.kind();
let inst_ok = !matches!(kind, AssertionExprKind::SequenceConcat);
let first = |s: &Self| {
x.children()
.first()
.is_some_and(|&c| s.leads_with_clock_in(c, inst_ok))
};
if matches!(
kind,
AssertionExprKind::SequenceConcat | AssertionExprKind::Unary
) {
first(self)
} else if matches!(kind, AssertionExprKind::Binary) {
!matches!(Op::of(x, &self.scope), Ok(Op::Binary(B::And | B::Or))) && first(self)
} else {
false
}
}
fn mentions_written_clock(&self, node: SemNode<'d>) -> bool {
if let Some(x) = node.as_assertion_expr()
&& x.kind() == AssertionExprKind::Clocking
&& let [event, _] = x.children().as_slice()
&& !super::is_inferred_clock_event(*event)
{
return true;
}
node.children()
.into_iter()
.any(|c| self.mentions_written_clock(c))
}
fn changes_clock_inside(&self, node: SemNode<'d>, ctx: SemNode<'d>) -> bool {
let Some(x) = node.as_assertion_expr() else {
return false;
};
let kids = x.children();
if let (AssertionExprKind::Clocking, [event, _]) = (x.kind(), kids.as_slice())
&& !super::is_inferred_clock_event(*event)
&& !super::events_equivalent(*event, ctx)
{
return true;
}
kids.iter().any(|&k| self.changes_clock_inside(k, ctx))
}
fn and_or_with_two_leading_clocks(&self, x: AssertionExpr<'d>) -> Result<bool, LoweringError> {
if !matches!(Op::of(x, &self.scope)?, Op::Binary(B::And | B::Or)) {
return Ok(false);
}
if self.leaves.clock_event(self.leaves.cur).is_none() {
let kids = x.children();
if let [l, r] = kids.as_slice()
&& self.written_event(*l).is_some() != self.written_event(*r).is_some()
{
return Ok(true);
}
}
Ok(self.operands_clocked_differently(x))
}
fn operands_clocked_differently(&self, x: AssertionExpr<'d>) -> bool {
let kids = x.children();
let [l, r] = kids.as_slice() else {
return false;
};
let ctx = self.leaves.clock_event(self.leaves.cur);
if let Some(c) = ctx
&& (self.changes_clock_inside(*l, c) || self.changes_clock_inside(*r, c))
{
return true;
}
match (self.written_event(*l), self.written_event(*r)) {
(Some(a), Some(b)) => !super::events_equivalent(a, b),
(Some(a), None) | (None, Some(a)) => {
ctx.is_some_and(|c| !super::events_equivalent(a, c))
}
(None, None) => false,
}
}
fn is_seq(&self, node: SemNode<'d>) -> Result<bool, LoweringError> {
if let Some(e) = node.as_expression() {
return Ok(!is_property_instance(e));
}
let Some(x) = node.as_assertion_expr() else {
return Err(self.err(
"sva property builder: a property operand is neither an Expression nor an \
AssertionExpr (unexpected SemNode kind)",
));
};
match x.kind() {
AssertionExprKind::Simple => match x.children().as_slice() {
[only] => self.is_seq(*only),
_ => Ok(true),
},
AssertionExprKind::SequenceConcat
| AssertionExprKind::SequenceWithMatch
| AssertionExprKind::FirstMatch => Ok(true),
AssertionExprKind::Clocking => match x.children().as_slice() {
[_, body] => self.is_seq(*body),
_ => Err(self.err("a clocking property expression does not have two children")),
},
AssertionExprKind::Binary => match super::super::op::Op::of(x, &self.scope)? {
super::super::op::Op::Binary(op) => {
use super::super::op::AssertionBinaryOp as B;
match op {
B::Intersect | B::Throughout | B::Within => Ok(true),
B::And | B::Or => {
if self.operands_clocked_differently(x) {
return Ok(false);
}
for c in x.children() {
if !self.is_seq(c)? {
return Ok(false);
}
}
Ok(true)
}
B::Iff
| B::Until
| B::SUntil
| B::UntilWith
| B::SUntilWith
| B::Implies
| B::OverlappedImplication
| B::NonOverlappedImplication
| B::OverlappedFollowedBy
| B::NonOverlappedFollowedBy => Ok(false),
}
}
super::super::op::Op::Unary(_) => {
Err(self.err("a Binary assertion expression resolved to a unary operator"))
}
},
AssertionExprKind::Unary
| AssertionExprKind::StrongWeak
| AssertionExprKind::Abort
| AssertionExprKind::Conditional
| AssertionExprKind::Case
| AssertionExprKind::DisableIff => Ok(false),
AssertionExprKind::Invalid => Err(self.err(
"an assertion expression slang could not bind (an Invalid node): the design has \
an error, see `Design::diagnostics()`",
)),
other => Err(self.err(&format!(
"sva property builder: AssertionExprKind::{other:?} is not a kind this builder \
knows (the enum is non-exhaustive)"
))),
}
}
fn seq(&mut self, node: SemNode<'d>) -> Result<(SeqRef, super::Flow), LoweringError> {
let frag = build_node(&mut self.a, &mut self.leaves, node, self.tree, &self.scope)?;
add_match_edges(&mut self.a, frag.exits);
let span = frag.clock.unwrap_or(Span::single(self.leaves.cur));
Ok((
SeqRef {
entry: frag.entry,
admits_empty: frag.empty,
clock: span.start,
end_clock: span.end,
},
frag.flow,
))
}
fn check_nondegenerate(&self, seq: SeqRef, at: Position) -> Result<(), LoweringError> {
let class = classify_sequence(&self.a, seq, &|_| false);
let ok = match (at, class) {
(Position::Property, SequenceClass::NonEmpty { also_empty: false }) => true,
(Position::Property, _) => false,
(Position::OverlappedAntecedent, SequenceClass::NonEmpty { .. }) => true,
(Position::OverlappedAntecedent, _) => false,
(Position::NonOverlappedAntecedent, SequenceClass::Never) => false,
(Position::NonOverlappedAntecedent, _) => true,
};
if ok {
return Ok(());
}
let why = match class {
SequenceClass::Never => "it can never match (it is degenerate)",
SequenceClass::EmptyOnly => "it admits only the empty match (it is degenerate)",
SequenceClass::NonEmpty { also_empty } => {
debug_assert!(also_empty);
"it admits the empty match"
}
};
let rule = match at {
Position::Property => {
"a sequence used as a property must be nondegenerate and admit no empty match \
(LRM 16.12.22 (a), 16.12.2)"
}
Position::OverlappedAntecedent => {
"the antecedent of an overlapped implication `|->`/`#-#` must be nondegenerate \
(LRM 16.12.22 (b))"
}
Position::NonOverlappedAntecedent => {
"the antecedent of a nonoverlapped implication `|=>`/`#=#` must admit at least \
one match (LRM 16.12.22 (c))"
}
};
Err(self.err(&format!("{rule}: {why}")))
}
fn seq_prop(
&mut self,
node: SemNode<'d>,
strength: Option<Strength>,
) -> Result<PropId, LoweringError> {
let root_of_cover = std::mem::take(&mut self.seq_top);
let (seq, _) = self.seq(node)?;
if !root_of_cover {
self.check_nondegenerate(seq, Position::Property)?;
}
Ok(self.push(Prop::Seq { seq, strength }))
}
fn build_prop(&mut self, node: SemNode<'d>) -> Result<PropId, LoweringError> {
let top = std::mem::take(&mut self.top);
self.seq_top = top && self.cover_sequence;
if let Some(e) = node.as_expression() {
return if is_property_instance(e) {
self.top = top;
let built = self.instance(e);
self.top = false;
built
} else {
self.seq_prop(node, None)
};
}
let Some(x) = node.as_assertion_expr() else {
return Err(self.err(
"sva property builder: a property operand is neither an Expression nor an \
AssertionExpr (unexpected SemNode kind)",
));
};
if top
&& self.leaves.clock_event(self.leaves.cur).is_none()
&& matches!(
x.kind(),
AssertionExprKind::SequenceConcat
| AssertionExprKind::Binary
| AssertionExprKind::Unary
| AssertionExprKind::Conditional
| AssertionExprKind::Case
)
&& self.mentions_written_clock(node)
&& !self.leads_with_written_clock(node)
&& !self.and_or_with_two_leading_clocks(x).unwrap_or(false)
{
return Err(self.err(
"the assertion has no leading clocking event (no default clocking, no inferred clock) and its maximal property is not an instance of a sequence or property with a unique leading clock (LRM 16.16 (f))",
));
}
match x.kind() {
AssertionExprKind::Clocking => {
let ch = crate::sva::op::clocking_children(x, &self.scope)?;
if self.is_seq(ch.body.as_sem_node())? {
return self.seq_prop(node, None);
}
self.build_under_clock(ch.event, ch.body.as_sem_node(), top)
}
AssertionExprKind::Simple => {
if let [only] = x.children().as_slice()
&& only.as_expression().is_some_and(is_property_instance)
{
self.top = top;
return self.build_prop(*only);
}
self.seq_prop(node, None)
}
AssertionExprKind::SequenceConcat
| AssertionExprKind::SequenceWithMatch
| AssertionExprKind::FirstMatch => self.seq_prop(node, None),
AssertionExprKind::StrongWeak => self.build_strong_weak(x),
AssertionExprKind::Binary if top && self.and_or_with_two_leading_clocks(x)? => {
Err(self.err(
"the maximal property is an `and`/`or` of differently clocked operands (or of one \
that names a clock and one that inherits none), \
which has no unique semantic leading clock (LRM 16.16.1, 16.13.2: legal \
only as a consequent or below an operator that fixes the leading clock)",
))
}
AssertionExprKind::Binary if self.is_seq(node)? => self.seq_prop(node, None),
AssertionExprKind::Binary => self.build_binary(x),
AssertionExprKind::Unary => self.build_unary(x),
AssertionExprKind::Abort => self.build_abort(x),
AssertionExprKind::Conditional => self.build_if(x),
AssertionExprKind::Case => self.build_case(x),
AssertionExprKind::DisableIff => self.build_disable_iff(x, top),
AssertionExprKind::Invalid => Err(self.err(
"an assertion expression slang could not bind (an Invalid node): the design has \
an error, see `Design::diagnostics()`",
)),
other => Err(self.err(&format!(
"sva property builder: AssertionExprKind::{other:?} is not a kind this builder \
knows (the enum is non-exhaustive)"
))),
}
}
fn build_under_clock(
&mut self,
event: SemNode<'d>,
body: SemNode<'d>,
top: bool,
) -> Result<PropId, LoweringError> {
if top {
self.leaves.rebind_inferred_root(event);
}
let id = self.leaves.resolve_clock(event, &self.scope)?;
if id == self.leaves.cur {
self.top = top;
return self.build_prop(body);
}
let is_seq = self.is_seq(body)?;
let saved = std::mem::replace(&mut self.leaves.cur, id);
self.top = top;
let built = self.build_prop(body);
self.leaves.cur = saved;
let p = built?;
if is_seq {
return Ok(p);
}
Ok(self.push(Prop::Clocked { clock: id, p }))
}
fn leaf(&mut self, node: SemNode<'d>, what: &str) -> Result<LeafId, LoweringError> {
let e = node.as_expression().ok_or_else(|| {
self.err(&format!(
"the {what} of a property operator is not an expression (unexpected SemNode kind)"
))
})?;
self.leaves.push_leaf(&mut self.a, e, None, &self.scope)
}
fn build_unary(&mut self, x: AssertionExpr<'d>) -> Result<PropId, LoweringError> {
let Op::Unary(op) = Op::of(x, &self.scope)? else {
return Err(self.err("a Unary assertion expression resolved to a binary operator"));
};
let kids = x.children();
let [operand] = kids.as_slice() else {
return Err(self.err(&format!(
"a unary property operator {op:?} does not have exactly one operand"
)));
};
match op {
U::Not => {
let p = self.build_prop(*operand)?;
self.negate(p, "`not`")
}
U::NextTime | U::SNextTime => {
let strong = op == U::SNextTime;
let name = if strong { "s_nexttime" } else { "nexttime" };
let n = match self.unary_bracket(x, name)? {
Bracket::Absent => 1,
Bracket::Count(n) => n,
Bracket::Range(..) => {
return Err(self.err(&format!(
"`{name} [..]` takes one constant number of clock ticks, not a \
range (LRM 16.12.10)"
)));
}
};
let p = self.after(n, |s| s.build_prop(*operand))?;
if strong {
self.forbid_call(p, "`s_nexttime`")?;
}
Ok(self.push(Prop::Nexttime { p, n, strong }))
}
U::Always | U::SAlways => {
let strong = op == U::SAlways;
let name = if strong { "s_always" } else { "always" };
let (lo, hi) = match self.unary_bracket(x, name)? {
Bracket::Absent if strong => {
return Err(
self.err("`s_always` needs a bounded range `[m:n]` (LRM 16.12.11)")
);
}
Bracket::Absent => (0, None),
Bracket::Range(lo, hi) => (lo, hi),
Bracket::Count(_) => {
return Err(self.err(&format!(
"`{name} [..]` takes a range `[m:n]`, not a single count \
(LRM 16.12.11)"
)));
}
};
if strong && hi.is_none() {
return Err(self.err(
"`s_always [m:$]` is illegal: the range of a strong always shall be \
bounded (LRM 16.12.11)",
));
}
let p = self.after(lo, |s| s.build_prop(*operand))?;
if strong {
self.forbid_call(p, "`s_always`")?;
}
Ok(self.push(Prop::Always { p, lo, hi, strong }))
}
U::Eventually | U::SEventually => {
let strong = op == U::SEventually;
let name = if strong { "s_eventually" } else { "eventually" };
let (lo, hi) = match self.unary_bracket(x, name)? {
Bracket::Absent if strong => (0, None),
Bracket::Absent => {
return Err(self.err(
"`eventually` needs a bounded range `[m:n]`; the unbracketed form is \
`s_eventually` (LRM 16.12.13)",
));
}
Bracket::Range(lo, hi) => (lo, hi),
Bracket::Count(_) => {
return Err(self.err(&format!(
"`{name} [..]` takes a range `[m:n]`, not a single count \
(LRM 16.12.13)"
)));
}
};
if !strong && hi.is_none() {
return Err(self.err(
"`eventually [m:$]` is illegal: the range of a weak eventually shall be \
bounded (LRM 16.12.13)",
));
}
let p = self.after(lo, |s| s.build_prop(*operand))?;
if strong {
self.forbid_call(p, "`s_eventually`")?;
}
Ok(self.push(Prop::Eventually { p, lo, hi, strong }))
}
}
}
fn unary_bracket(&self, x: AssertionExpr<'d>, name: &str) -> Result<Bracket, LoweringError> {
let syn = self.tree.syntax(&x).ok_or_else(|| {
self.err(&format!(
"`{name}`: the property operator has no syntax node in the given tree"
))
})?;
let syn = peel_property_parens(syn);
let selector = if let Some(s) = UnarySelectPropertyExprSyntax::cast(syn) {
s.selector()
} else if UnaryPropertyExprSyntax::cast(syn).is_some() {
None
} else {
return Err(self.err(&format!(
"`{name}`: expected a unary property expression syntax node, found {:?}",
syn.kind()
)));
};
let Some(selector) = selector else {
return Ok(Bracket::Absent);
};
let one = matches!(selector, SelectorSyntax::BitSelect(_));
let r = ranges::range_of_selector(selector, &self.scope, &self.leaves.env)?;
if one {
return Ok(Bracket::Count(r.min));
}
if let Some(max) = r.max
&& r.min > max
{
return Err(self.err(&format!(
"`{name} [{}:{max}]`: the minimum of the range is greater than its maximum \
(LRM 16.12.11, 16.12.13)",
r.min
)));
}
Ok(Bracket::Range(r.min, r.max))
}
fn build_binary(&mut self, x: AssertionExpr<'d>) -> Result<PropId, LoweringError> {
let Op::Binary(op) = Op::of(x, &self.scope)? else {
return Err(self.err("a Binary assertion expression resolved to a unary operator"));
};
let kids = x.children();
let [lhs, rhs] = kids.as_slice() else {
return Err(self.err(&format!(
"a binary property operator {op:?} does not have exactly two operands"
)));
};
match op {
B::Or => {
let (p, q) = (self.build_prop(*lhs)?, self.build_prop(*rhs)?);
Ok(self.push(Prop::Or(p, q)))
}
B::And => {
let (p, q) = (self.build_prop(*lhs)?, self.build_prop(*rhs)?);
Ok(self.push(Prop::And(p, q)))
}
B::Intersect | B::Throughout | B::Within => Err(self.err(&format!(
"the sequence operator {op:?} reached the property builder with a property \
operand (16.9.6-16.9.10: its operands are sequences)"
))),
B::Implies => {
let (p, q) = (self.build_prop(*lhs)?, self.build_prop(*rhs)?);
self.implies(p, q, true)
}
B::Iff => {
let (p, q) = (self.build_prop(*lhs)?, self.build_prop(*rhs)?);
let (pq, qp) = (self.implies(p, q, false)?, self.implies(q, p, false)?);
Ok(self.push(Prop::And(pq, qp)))
}
B::OverlappedImplication => self.implication(*lhs, *rhs, true, false),
B::NonOverlappedImplication => self.implication(*lhs, *rhs, false, false),
B::OverlappedFollowedBy => self.implication(*lhs, *rhs, true, true),
B::NonOverlappedFollowedBy => self.implication(*lhs, *rhs, false, true),
B::Until | B::SUntil => {
let (p, q) = (self.build_prop(*lhs)?, self.build_prop(*rhs)?);
if op == B::SUntil {
self.forbid_call(p, "`s_until`")?;
self.forbid_call(q, "`s_until`")?;
}
Ok(self.push(Prop::Until {
p,
q,
strong: op == B::SUntil,
}))
}
B::UntilWith | B::SUntilWith => {
let (p, q) = (self.build_prop(*lhs)?, self.build_prop(*rhs)?);
if op == B::SUntilWith {
self.forbid_call(p, "`s_until_with`")?;
self.forbid_call(q, "`s_until_with`")?;
}
let both = self.push(Prop::And(p, q));
Ok(self.push_nv(
Prop::Until {
p,
q: both,
strong: op == B::SUntilWith,
},
NvRule::UntilWith,
))
}
}
}
fn implies(&mut self, p: PropId, q: PropId, written: bool) -> Result<PropId, LoweringError> {
let not_p = self.negate(p, "`implies`/`iff`")?;
if written {
Ok(self.push_nv(Prop::Or(not_p, q), NvRule::Implies))
} else {
Ok(self.push(Prop::Or(not_p, q)))
}
}
fn negate(&mut self, p: PropId, what: &str) -> Result<PropId, LoweringError> {
self.forbid_call(p, what)?;
Ok(self.push(Prop::Not(p)))
}
fn forbid_call(&self, operand: PropId, what: &str) -> Result<(), LoweringError> {
if !self.has_call[operand.0 as usize] {
return Ok(());
}
Err(self.err(&format!(
"{what} is applied to a property expression that instantiates a recursive \
property, or one from which a recursive property is reachable (LRM 16.12.17 \
restriction 1)"
)))
}
fn after<T>(
&mut self,
ticks: u32,
build: impl FnOnce(&mut Self) -> Result<T, LoweringError>,
) -> Result<T, LoweringError> {
let saved = self.adv;
self.adv = saved.saturating_add(ticks);
let built = build(self);
self.adv = saved;
built
}
fn min_match_ticks(&self, seq: SeqRef) -> u32 {
if seq.admits_empty {
return 0;
}
let mut best = vec![u32::MAX; self.a.states.len()];
let mut heap = BinaryHeap::new();
best[seq.entry.0 as usize] = 0;
heap.push(std::cmp::Reverse((0u32, seq.entry.0)));
while let Some(std::cmp::Reverse((d, s))) = heap.pop() {
if d > best[s as usize] {
continue;
}
for e in &self.a.state(ThreadStateId(s)).edges {
let w = match &e.label {
Transition::Match { .. } => return d,
Transition::Delay { min, .. } => *min,
_ => 0,
};
let nd = d.saturating_add(w);
let t = e.target.0 as usize;
if nd < best[t] {
best[t] = nd;
heap.push(std::cmp::Reverse((nd, e.target.0)));
}
}
}
0
}
fn implication(
&mut self,
ante: SemNode<'d>,
cons: SemNode<'d>,
overlapped: bool,
followed_by: bool,
) -> Result<PropId, LoweringError> {
if !self.is_seq(ante)? {
return Err(self.err(
"the antecedent of an implication or a followed-by is a property, not a \
sequence (LRM 16.12.7, 16.12.9)",
));
}
let x0 = self.leaves.flow_in.clone();
let (ante, flow) = self.seq(ante)?;
let position = if overlapped {
Position::OverlappedAntecedent
} else {
Position::NonOverlappedAntecedent
};
self.check_nondegenerate(ante, position)?;
self.leaves.flow_in = flow.flow_out(&x0);
let ticks = self
.min_match_ticks(ante)
.saturating_add(u32::from(!overlapped));
let cons = self.after(ticks, |s| s.build_prop(cons));
self.leaves.flow_in = x0;
let mut cons = cons?;
if followed_by {
cons = self.negate(cons, "`#-#`/`#=#`")?;
}
let imp = self.push(Prop::Implication {
ante,
cons,
overlapped,
});
if followed_by {
Ok(self.push(Prop::Not(imp)))
} else {
Ok(imp)
}
}
fn build_if(&mut self, x: AssertionExpr<'d>) -> Result<PropId, LoweringError> {
let kids = x.children();
let (cond, then, els) = match kids.as_slice() {
[c, t] => (*c, *t, None),
[c, t, e] => (*c, *t, Some(*e)),
other => {
return Err(self.err(&format!(
"an if-else property has {} children, expected [condition, then] or \
[condition, then, else]",
other.len()
)));
}
};
let cond = self.leaf(cond, "condition")?;
let then = self.build_prop(then)?;
let els = els.map(|e| self.build_prop(e)).transpose()?;
Ok(self.push(Prop::If { cond, then, els }))
}
fn build_case(&mut self, x: AssertionExpr<'d>) -> Result<PropId, LoweringError> {
let c = crate::sva::op::case_children(x, self.tree, &self.scope)?;
let selector = self.leaf(c.selector, "case selector")?;
let mut arms = Vec::with_capacity(c.items.len());
for item in &c.items {
let mut labels = Vec::with_capacity(item.labels.len());
for l in &item.labels {
labels.push(self.leaf(*l, "case item label")?);
}
let body = self.build_prop(item.body.as_sem_node())?;
arms.push(CaseArm { labels, body });
}
let default = c
.default
.map(|d| self.build_prop(d.as_sem_node()))
.transpose()?;
Ok(self.push(Prop::Case {
selector,
arms,
default,
}))
}
fn build_abort(&mut self, x: AssertionExpr<'d>) -> Result<PropId, LoweringError> {
let syn = self
.tree
.syntax(&x)
.ok_or_else(|| self.err("an abort property has no syntax node"))?;
let syn = peel_property_parens(syn);
let ab = AcceptOnPropertyExprSyntax::cast(syn).ok_or_else(|| {
self.err(&format!(
"expected an abort property syntax node, found {:?}",
syn.kind()
))
})?;
let keyword = ab.keyword().map(|t| t.text().to_string());
let mode = match keyword.as_deref() {
Some("accept_on") => AbortMode::Accept,
Some("reject_on") => AbortMode::Reject,
Some("sync_accept_on") => AbortMode::SyncAccept,
Some("sync_reject_on") => AbortMode::SyncReject,
other => {
return Err(self.err(&format!(
"an abort property with the keyword {other:?}, expected `accept_on`, \
`reject_on`, `sync_accept_on` or `sync_reject_on`"
)));
}
};
let ch = crate::sva::op::abort_children(x, &self.scope)?;
let cond = self.abort_condition(ch.condition)?;
let p = self.build_prop(ch.body.as_sem_node())?;
Ok(self.push(Prop::Abort { cond, p, mode }))
}
fn abort_condition(&mut self, node: SemNode<'d>) -> Result<LeafId, LoweringError> {
let e = node.as_expression().ok_or_else(|| {
self.err(
"the condition of an abort property is not an expression (unexpected SemNode kind)",
)
})?;
let mut points = Vec::new();
collect_end_points(e, &mut points);
if !points.is_empty() {
return Err(self.err(
"an abort condition shall not contain the sequence methods `.triggered` or \
`.matched` (LRM 16.12.14)",
));
}
let (mut locals, mut lvalue) = (Vec::new(), false);
collect_local_reads(node, &mut locals, &mut lvalue);
if let Some(v) = locals.first() {
return Err(self.err(&format!(
"an abort condition shall not reference the local variable `{}` (LRM 16.12.14)",
v.name()
)));
}
self.leaf(node, "abort condition")
}
fn build_strong_weak(&mut self, x: AssertionExpr<'d>) -> Result<PropId, LoweringError> {
let syn = self
.tree
.syntax(&x)
.ok_or_else(|| self.err("a strong/weak property has no syntax node"))?;
let syn = peel_property_parens(syn);
let sw = StrongWeakPropertyExprSyntax::cast(syn).ok_or_else(|| {
self.err(&format!(
"expected a strong/weak property syntax node, found {:?}",
syn.kind()
))
})?;
let keyword = sw.keyword().map(|t| t.text().to_string());
let strength = match keyword.as_deref() {
Some("strong") => Strength::Strong,
Some("weak") => Strength::Weak,
other => {
return Err(self.err(&format!(
"a strong/weak property with the keyword {other:?}, expected `strong` or \
`weak`"
)));
}
};
let kids = x.children();
let [operand] = kids.as_slice() else {
return Err(self.err("a strong/weak property does not have exactly one operand"));
};
self.seq_prop(*operand, Some(strength))
}
fn instance(&mut self, inst: Expression<'d>) -> Result<PropId, LoweringError> {
let origin =
formal_reference_origin(self.tree.syntax(&inst), &self.leaves.env).map(<[_]>::to_vec);
let Some(origin) = origin else {
return self.expand(inst, false);
};
let saved = std::mem::replace(&mut self.leaves.chain, origin);
let built = self.expand(inst, true);
self.leaves.chain = saved;
built
}
fn expand(&mut self, inst: Expression<'d>, formal_ref: bool) -> Result<PropId, LoweringError> {
let args = inst.assertion_arguments();
for g in &args {
if g.port.assertion_port_is_local_var()
&& !matches!(
g.port.assertion_port_direction(),
None | Some(ArgumentDirection::In)
)
{
return Err(self.err(&format!(
"the `local` formal `{}` of a property must be an `input` (LRM 16.12.19)",
g.port.name()
)));
}
}
let body = inst
.as_sem_node()
.children()
.into_iter()
.find_map(|c| c.as_assertion_expr());
if formal_ref {
let body = body.ok_or_else(|| {
self.err("the reference to a property formal has no body (the actual)")
})?;
return self.expand_body(inst, &args, body, None);
}
let name = self.callee_name(inst, &args)?;
let home = self.home_frame(inst);
let call_site = self
.tree
.syntax(&inst)
.map(|n| {
let r = key(n);
(r.start, r.end)
})
.ok_or_else(|| self.err("a property instance has no syntax node"))?;
let sites = if body.is_some() {
let s = self.site_args(&args)?;
self.sites.insert(call_site, s.clone());
s
} else {
self.sites.get(&call_site).cloned().ok_or_else(|| {
self.err(&format!(
"the instance of `{name}` has no body and no earlier instance of the same \
call site to take its actuals from (LRM 16.12.17)"
))
})?
};
let bound = self.bind_args(&sites, home);
let key = match body {
Some(b) => self.decl_key(b)?,
None => self
.frames
.iter()
.rev()
.find(|f| f.name == name)
.map(|f| f.key)
.ok_or_else(|| {
self.err(&format!(
"the instance of `{name}` has no body: slang stops unfolding a \
recursive property one level after the first, and no expansion of \
`{name}` is in progress to call (LRM 16.12.17)"
))
})?,
};
if let Some(h) = home {
let here = self.frames[h].key;
self.edges.insert((here, key));
if self.reaches(key, here) {
if let Some(arg) = sites
.iter()
.zip(&bound)
.find_map(|(s, b)| b.mixed.then(|| s.name.clone()))
{
return Err(self.err(&format!(
"the actual of the non-local formal `{arg}` in the recursive instance \
of `{name}` is an expression over formals of the property it is \
written in: it must be one of its formals, a `local` formal's actual, \
or mention none (LRM 16.12.17 restriction 4)"
)));
}
let target = (0..=h).rev().find(|&i| {
let f = &self.frames[i];
f.key == key
&& f.formals.len() == bound.len()
&& f.formals.iter().zip(&bound).all(|((_, r), b)| *r == b.root)
});
if let Some(i) = target {
if body.is_none() && sites.iter().any(|s| s.local) {
return Err(self.err(&format!(
"the recursive instance of `{name}` is past the point where slang \
stops unfolding it, so the actuals of its `local` formals are \
unavailable (LRM 16.12.17): the instance recurses only after \
the non-local actuals have been permuted or replaced"
)));
}
if body.is_none() && self.frames[i].has_inits {
return Err(self.err(&format!(
"the recursive instance of `{name}` is past the point where slang \
stops unfolding it, so the initialisers of its declared local \
variables, with this instance's actuals substituted, are \
unavailable (LRM 16.12.17, 16.12.19)"
)));
}
return self.recursive_call(inst, &name, &args, i);
}
}
}
let Some(body) = body else {
return Err(self.err(&format!(
"the instance of `{name}` has no body, and the formal arguments it binds are \
no binding of any expansion of `{name}` in progress (LRM 16.12.17: slang \
unfolds a recursive property only one level past the first)"
)));
};
self.expand_body(inst, &args, body, Some((name, key, bound)))
}
fn expand_body(
&mut self,
inst: Expression<'d>,
args: &[sv_lang::AssertionInstanceArg<'d>],
body: AssertionExpr<'d>,
frame: Option<(String, DeclKey, Vec<Bound>)>,
) -> Result<PropId, LoweringError> {
let block_event = if frame.is_none() || body.kind() == AssertionExprKind::Clocking {
None
} else {
super::enclosing_clocking_event(body, self.tree, &self.scope)?
};
let top = self.top;
let entered = enter_instance(
&mut self.a,
&mut self.leaves,
inst,
args,
false,
self.tree,
&self.scope,
)?;
let pushed = frame.is_some();
if let Some((name, key, bound)) = frame {
let has_inits = match &entered.locals {
Some(l) => l
.syms
.iter()
.any(|s| !is_local_formal(args, s) && s.initializer().is_some()),
None => false,
};
let locals: Vec<(String, LocalVarId)> = match &entered.locals {
Some(l) => l
.syms
.iter()
.zip(&l.slots)
.map(|(s, &slot)| (s.name().to_string(), slot))
.collect(),
None => Vec::new(),
};
let locals = locals
.into_iter()
.map(|(n, slot)| (n, slot, self.leaves.phys_of(&mut self.a, slot)))
.collect();
self.frames.push(PFrame {
name,
key,
formals: args
.iter()
.zip(bound)
.map(|(g, b)| (g.port.name().to_string(), b.root))
.collect(),
locals,
has_inits,
entry_adv: self.adv,
decl: None,
});
}
let built = match block_event {
Some(event) => self.build_under_clock(event, body.as_sem_node(), top),
None => self.build_prop(body.as_sem_node()),
};
let decl = if pushed {
self.frames.pop().and_then(|f| f.decl)
} else {
None
};
let locals = leave_instance(&mut self.leaves, entered);
let body = built?;
let Some(l) = locals else {
if let Some(d) = decl {
self.decls[d.0 as usize].body = body;
}
return Ok(body);
};
self.leaves.scope.truncate(l.scope_len);
if !l.write_back.is_empty() {
return Err(self.err(
"a property cannot have a `local` output or inout formal (LRM 16.12.19: the \
local variable formals of a property are inputs)",
));
}
let writes: Vec<_> = l
.init
.into_iter()
.filter_map(|t| match t {
ir::Transition::LocalVarWrite { var, value } => Some((var, value)),
_ => None,
})
.collect();
let Some(d) = decl else {
if writes.is_empty() {
return Ok(body);
}
return Ok(self.wrap_let(writes, body));
};
self.decls[d.0 as usize].body = body;
if writes.is_empty() {
return Ok(body);
}
Ok(self.wrap_let(writes, body))
}
fn lead_clocks(&self, p: PropId, out: &mut BTreeSet<ClockId>) {
match &self.props[p.0 as usize] {
Prop::Seq { seq, .. } => {
out.insert(seq.clock);
}
Prop::Implication { ante, .. } => {
out.insert(ante.clock);
}
Prop::And(x, y) | Prop::Or(x, y) => {
self.lead_clocks(*x, out);
self.lead_clocks(*y, out);
}
Prop::Not(x) => self.lead_clocks(*x, out),
Prop::Clocked { clock, .. } => {
out.insert(*clock);
}
Prop::Let { body, .. } => self.lead_clocks(*body, out),
Prop::If { .. }
| Prop::Case { .. }
| Prop::Nexttime { .. }
| Prop::Always { .. }
| Prop::Eventually { .. }
| Prop::Until { .. }
| Prop::Abort { .. }
| Prop::Call { .. } => {
out.insert(self.leaves.cur);
}
}
}
fn wrap_let(&mut self, writes: Vec<(LocalVarId, LeafId)>, body: PropId) -> PropId {
let mut leads = BTreeSet::new();
self.lead_clocks(body, &mut leads);
if leads.len() < 2 {
return self.push(Prop::Let { writes, body });
}
self.let_per_clock(&writes, body)
}
fn let_per_clock(&mut self, writes: &[(LocalVarId, LeafId)], p: PropId) -> PropId {
match self.props[p.0 as usize].clone() {
Prop::And(x, y) => {
let (x, y) = (self.let_per_clock(writes, x), self.let_per_clock(writes, y));
self.push(Prop::And(x, y))
}
Prop::Or(x, y) => {
let (x, y) = (self.let_per_clock(writes, x), self.let_per_clock(writes, y));
self.push(Prop::Or(x, y))
}
Prop::Clocked { clock, p } => {
let inner = self.push(Prop::Let {
writes: writes.to_vec(),
body: p,
});
self.push(Prop::Clocked { clock, p: inner })
}
_ => self.push(Prop::Let {
writes: writes.to_vec(),
body: p,
}),
}
}
fn recursive_call(
&mut self,
inst: Expression<'d>,
name: &str,
args: &[sv_lang::AssertionInstanceArg<'d>],
target: usize,
) -> Result<PropId, LoweringError> {
if self.adv == self.frames[target].entry_adv {
return Err(self.err(&format!(
"the recursive instance of `{name}` is not after a positive guaranteed advance \
in time since the instance it recurses into began: a cycle of the dependency \
digraph of `{name}` has total weight 0 (LRM 16.12.17 restriction 3)"
)));
}
let mut call_args = Vec::new();
for g in args {
if !g.port.assertion_port_is_local_var() {
continue;
}
let formal = g.port.name();
let var = self.frames[target]
.locals
.iter()
.find(|(n, _, _)| n == formal)
.map(|&(_, _, v)| v)
.ok_or_else(|| {
self.err(&format!(
"the `local` formal `{formal}` of the recursive instance of `{name}` \
has no slot in the expansion it calls"
))
})?;
let actual = g.actual.as_expression().ok_or_else(|| {
self.err(&format!(
"the actual of the `local` formal `{formal}` is not an expression"
))
})?;
call_args.extend(self.leaves.slot_leaves(
&mut self.a,
var,
actual,
None,
&self.scope,
)?);
}
let mut inits = Vec::new();
if self.frames[target].has_inits {
let scope_len = self.leaves.scope.len();
let built = self.recursive_inits(inst, name, args, target, &mut inits);
self.leaves.scope.truncate(scope_len);
built?;
}
let decl = match self.frames[target].decl {
Some(d) => d,
None => {
let d = DeclId(self.decls.len() as u32);
self.decls.push(PropDecl {
name: name.to_string(),
body: PropId(0),
});
self.frames[target].decl = Some(d);
d
}
};
Ok(self.push(Prop::Call {
decl,
args: call_args,
inits,
}))
}
fn recursive_inits(
&mut self,
inst: Expression<'d>,
name: &str,
args: &[sv_lang::AssertionInstanceArg<'d>],
target: usize,
inits: &mut Vec<(LocalVarId, LeafId)>,
) -> Result<(), LoweringError> {
for sym in inst.assertion_local_vars() {
if is_local_formal(args, &sym) {
continue;
}
let (canon, phys) = self.frames[target]
.locals
.iter()
.find(|(n, _, _)| n == sym.name())
.map(|&(_, c, p)| (c, p))
.ok_or_else(|| {
self.err(&format!(
"the declared local `{}` of the recursive instance of `{name}` has no \
slot in the expansion it calls",
sym.name()
))
})?;
self.leaves.scope.push(ScopeVar { sym, slot: canon });
if let Some(initializer) = sym.initializer() {
inits.extend(self.leaves.slot_leaves(
&mut self.a,
phys,
initializer,
None,
&self.scope,
)?);
}
}
Ok(())
}
fn callee_name(
&self,
inst: Expression<'d>,
args: &[sv_lang::AssertionInstanceArg<'d>],
) -> Result<String, LoweringError> {
if let Some(parent) = args.first().and_then(|g| g.port.parent()) {
return Ok(parent.name().to_string());
}
let syn = self
.tree
.syntax(&inst)
.ok_or_else(|| self.err("a property instance has no syntax node"))?;
let text = syn.text();
let name = text.split('(').next().unwrap_or_default().trim();
if name.is_empty() {
return Err(self.err("a property instance has no name"));
}
Ok(name.to_string())
}
fn decl_key(&self, body: AssertionExpr<'d>) -> Result<DeclKey, LoweringError> {
let r: Range<usize> = key(self.tree.syntax(&body).ok_or_else(|| {
self.err(
"a property declaration has no syntax node in any source file of the \
compilation (a declaration slang made up)",
)
})?);
Ok((r.start, r.end))
}
fn home_frame(&self, inst: Expression<'d>) -> Option<usize> {
let r = key(self.tree.syntax(&inst)?);
self.frames
.iter()
.rposition(|f| f.key.0 <= r.start && r.end <= f.key.1)
}
fn site_args(
&self,
args: &[sv_lang::AssertionInstanceArg<'d>],
) -> Result<Vec<ArgSite>, LoweringError> {
let mut out = Vec::with_capacity(args.len());
for g in args {
let name = g.port.name().to_string();
if g.port.assertion_port_is_local_var() {
out.push(ArgSite {
name,
local: true,
text: (0, 0),
bare: None,
idents: Vec::new(),
});
continue;
}
let syn = actual_syntax(g.actual, self.tree)
.or_else(|| self.tree.syntax(&g.actual.as_assertion_expr()?))
.or_else(|| self.tree.syntax(&g.actual.as_timing_control()?))
.ok_or_else(|| {
self.err(&format!(
"the actual of the formal `{name}` has no syntax node"
))
})?;
let r = key(syn);
let ident = |n| {
IdentifierNameSyntax::cast(n)
.and_then(|i| i.identifier())
.map(|t| t.value_text().to_string())
};
out.push(ArgSite {
name,
local: false,
text: (r.start, r.end),
bare: ident(syn),
idents: syn.descendants().filter_map(ident).collect(),
});
}
Ok(out)
}
fn bind_args(&self, sites: &[ArgSite], home: Option<usize>) -> Vec<Bound> {
let mut out = Vec::with_capacity(sites.len());
for s in sites {
if s.local {
out.push(Bound {
root: None,
mixed: false,
});
continue;
}
let text = s.text;
let Some(h) = home else {
out.push(Bound {
root: Some(Root { text, home: None }),
mixed: false,
});
continue;
};
let formals = &self.frames[h].formals;
let bare = s
.bare
.as_ref()
.and_then(|id| formals.iter().find(|(n, _)| n == id));
let bound = match bare {
Some((_, Some(root))) => Bound {
root: Some(root.clone()),
mixed: false,
},
Some((_, None)) => Bound {
root: Some(Root {
text,
home: Some(h),
}),
mixed: true,
},
None => {
let mentions = s
.idents
.iter()
.any(|id| formals.iter().any(|(f, _)| f == id));
Bound {
root: Some(Root {
text,
home: mentions.then_some(h),
}),
mixed: mentions,
}
}
};
out.push(bound);
}
out
}
fn reaches(&self, from: DeclKey, to: DeclKey) -> bool {
let mut seen = BTreeSet::new();
let mut stack = vec![from];
while let Some(k) = stack.pop() {
if k == to {
return true;
}
if !seen.insert(k) {
continue;
}
stack.extend(
self.edges
.range((k, (0, 0))..)
.take_while(|(f, _)| *f == k)
.map(|&(_, t)| t),
);
}
false
}
fn on_cycle(&self, key: DeclKey) -> bool {
self.edges
.range((key, (0, 0))..)
.take_while(|(f, _)| *f == key)
.any(|&(_, t)| self.reaches(t, key))
}
fn build_disable_iff(
&mut self,
x: AssertionExpr<'d>,
top: bool,
) -> Result<PropId, LoweringError> {
let ch = crate::sva::op::disable_iff_children(x, &self.scope)?;
let in_decl = self.tree.syntax(&x).and_then(|n| {
let r = key(n);
self.frames
.iter()
.rposition(|f| f.key.0 <= r.start && r.end <= f.key.1)
});
let body = self.build_prop(ch.body.as_sem_node())?;
if let Some(h) = in_decl {
let key = self.frames[h].key;
if self.on_cycle(key) {
return Err(self.err(&format!(
"`disable iff` in the declaration of the recursive property `{}` (LRM 16.12.17 \
restriction 2)",
self.frames[h].name
)));
}
}
if !top {
return Err(self.err(
"`disable iff` is legal only as the outermost operator of a property \
specification: it cannot be nested in a property expression, in a property \
whose instance is itself under a `disable iff`, or be the actual of a \
property operand (LRM 16.12, 16.15)",
));
}
let e = ch.condition.as_expression().ok_or_else(|| {
self.err(
"the condition of a `disable iff` is not an expression (unexpected SemNode kind)",
)
})?;
if e.call_subroutine_name().as_deref() == Some("$inferred_disable") {
self.leaves.disable_inferred = true;
return Ok(body);
}
let mut points = Vec::new();
collect_end_points(e, &mut points);
if points.iter().any(|(m, _)| *m == ir::EndMethod::Matched) {
return Err(self.err(
"a `disable iff` condition shall not contain the sequence method `.matched` \
(LRM 16.15)",
));
}
let (mut locals, mut lvalue) = (Vec::new(), false);
collect_local_reads(ch.condition, &mut locals, &mut lvalue);
if let Some(v) = locals.first() {
return Err(self.err(&format!(
"a `disable iff` condition shall not reference the local variable `{}` \
(LRM 16.15)",
v.name()
)));
}
let cond = self.leaf(ch.condition, "disable iff condition")?;
self.disable = Some(cond);
Ok(body)
}
fn finish(mut self, root: PropId) -> (PropertyIr, LeafTable<'d>) {
let mut keep = vec![false; self.props.len()];
let mut stack = vec![root];
stack.extend(self.decls.iter().map(|d| d.body));
while let Some(id) = stack.pop() {
if std::mem::replace(&mut keep[id.0 as usize], true) {
continue;
}
stack.extend(operands(&self.props[id.0 as usize]));
}
let mut new_id = vec![None; self.props.len()];
let mut props = Vec::new();
for (i, p) in self.props.drain(..).enumerate() {
if keep[i] {
new_id[i] = Some(PropId(props.len() as u32));
props.push(p);
}
}
let map = |id: PropId| new_id[id.0 as usize].expect("a kept node's operands are kept");
for p in &mut props {
for id in operands_mut(p) {
*id = map(*id);
}
}
for d in &mut self.decls {
d.body = map(d.body);
}
let root = map(root);
let nv_rules: Vec<(PropId, NvRule)> = self
.nv_rules
.iter()
.filter_map(|(id, rule)| new_id[id.0 as usize].map(|n| (n, *rule)))
.collect();
let roots: Vec<ThreadStateId> = props
.iter_mut()
.filter_map(|p| seqs_mut(p).map(|s| s.entry))
.collect();
let renumber = prune_from(&mut self.a, &roots);
for p in &mut props {
if let Some(s) = seqs_mut(p) {
s.entry = renumber[s.entry.0 as usize].expect("a sequence's entry is a root");
}
}
(
PropertyIr {
automaton: self.a,
props,
decls: self.decls,
root,
disable: self.disable,
nv_rules,
},
self.leaves,
)
}
}
fn is_property_instance(e: Expression<'_>) -> bool {
e.kind() == ExpressionKind::AssertionInstance
&& e.expr_type().is_some_and(|t| t.is_property_type())
}
#[cfg(test)]
mod tests;