#![allow(dead_code)]
use sv_lang::kinds::AssertionExprKind;
use sv_lang::nodes::{
CasePropertyExprSyntax, ParenthesizedPropertyExprSyntax, PropertyCaseItemSyntax,
PropertySpecSyntax,
};
use sv_lang::{AssertionExpr, AstNode, Node, SemNode, Symbol};
use super::trees::Trees;
use crate::error::LoweringError;
pub(crate) fn peel_property_parens(mut node: Node<'_>) -> Node<'_> {
loop {
if let Some(p) = ParenthesizedPropertyExprSyntax::cast(node) {
node = p.expr().syntax();
} else if let Some(s) = PropertySpecSyntax::cast(node)
&& s.clocking().is_none()
&& s.disable().is_none()
{
node = s.expr().syntax();
} else {
return node;
}
}
}
#[repr(i32)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum AssertionUnaryOp {
Not = 0,
NextTime = 1,
SNextTime = 2,
Always = 3,
SAlways = 4,
Eventually = 5,
SEventually = 6,
}
impl AssertionUnaryOp {
pub const COUNT: i32 = 7;
pub const ALL: [AssertionUnaryOp; Self::COUNT as usize] = [
AssertionUnaryOp::Not,
AssertionUnaryOp::NextTime,
AssertionUnaryOp::SNextTime,
AssertionUnaryOp::Always,
AssertionUnaryOp::SAlways,
AssertionUnaryOp::Eventually,
AssertionUnaryOp::SEventually,
];
}
impl TryFrom<i32> for AssertionUnaryOp {
type Error = LoweringError;
fn try_from(raw: i32) -> Result<Self, Self::Error> {
match raw {
0 => Ok(AssertionUnaryOp::Not),
1 => Ok(AssertionUnaryOp::NextTime),
2 => Ok(AssertionUnaryOp::SNextTime),
3 => Ok(AssertionUnaryOp::Always),
4 => Ok(AssertionUnaryOp::SAlways),
5 => Ok(AssertionUnaryOp::Eventually),
6 => Ok(AssertionUnaryOp::SEventually),
other => Err(LoweringError::unsupported(
&format!(
"UnaryAssertionOperator raw value {other} is out of the pinned 0..=6 range \
(Not..SEventually) -- either a malformed raw discriminant or a slang \
upgrade that added a variant this mirror does not yet know about"
),
"sva::op::AssertionUnaryOp::try_from",
)),
}
}
}
#[repr(i32)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum AssertionBinaryOp {
And = 0,
Or = 1,
Intersect = 2,
Throughout = 3,
Within = 4,
Iff = 5,
Until = 6,
SUntil = 7,
UntilWith = 8,
SUntilWith = 9,
Implies = 10,
OverlappedImplication = 11,
NonOverlappedImplication = 12,
OverlappedFollowedBy = 13,
NonOverlappedFollowedBy = 14,
}
impl AssertionBinaryOp {
pub const COUNT: i32 = 15;
pub const ALL: [AssertionBinaryOp; Self::COUNT as usize] = [
AssertionBinaryOp::And,
AssertionBinaryOp::Or,
AssertionBinaryOp::Intersect,
AssertionBinaryOp::Throughout,
AssertionBinaryOp::Within,
AssertionBinaryOp::Iff,
AssertionBinaryOp::Until,
AssertionBinaryOp::SUntil,
AssertionBinaryOp::UntilWith,
AssertionBinaryOp::SUntilWith,
AssertionBinaryOp::Implies,
AssertionBinaryOp::OverlappedImplication,
AssertionBinaryOp::NonOverlappedImplication,
AssertionBinaryOp::OverlappedFollowedBy,
AssertionBinaryOp::NonOverlappedFollowedBy,
];
}
impl TryFrom<i32> for AssertionBinaryOp {
type Error = LoweringError;
fn try_from(raw: i32) -> Result<Self, Self::Error> {
match raw {
0 => Ok(AssertionBinaryOp::And),
1 => Ok(AssertionBinaryOp::Or),
2 => Ok(AssertionBinaryOp::Intersect),
3 => Ok(AssertionBinaryOp::Throughout),
4 => Ok(AssertionBinaryOp::Within),
5 => Ok(AssertionBinaryOp::Iff),
6 => Ok(AssertionBinaryOp::Until),
7 => Ok(AssertionBinaryOp::SUntil),
8 => Ok(AssertionBinaryOp::UntilWith),
9 => Ok(AssertionBinaryOp::SUntilWith),
10 => Ok(AssertionBinaryOp::Implies),
11 => Ok(AssertionBinaryOp::OverlappedImplication),
12 => Ok(AssertionBinaryOp::NonOverlappedImplication),
13 => Ok(AssertionBinaryOp::OverlappedFollowedBy),
14 => Ok(AssertionBinaryOp::NonOverlappedFollowedBy),
other => Err(LoweringError::unsupported(
&format!(
"BinaryAssertionOperator raw value {other} is out of the pinned 0..=14 \
range (And..NonOverlappedFollowedBy) -- either a malformed raw \
discriminant or a slang upgrade that added a variant this mirror does not \
yet know about"
),
"sva::op::AssertionBinaryOp::try_from",
)),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Op {
Unary(AssertionUnaryOp),
Binary(AssertionBinaryOp),
}
impl Op {
pub fn of(expr: AssertionExpr, scope: &Symbol) -> Result<Op, LoweringError> {
match expr.kind() {
AssertionExprKind::Unary => {
let raw = expr.op().ok_or_else(|| {
LoweringError::unsupported_at(
"AssertionExprKind::Unary node has no operator (`AssertionExpr::op()` \
returned None) -- this is a slang invariant violation, not a legal \
shape",
scope,
)
})?;
AssertionUnaryOp::try_from(raw).map(Op::Unary)
}
AssertionExprKind::Binary => {
let raw = expr.op().ok_or_else(|| {
LoweringError::unsupported_at(
"AssertionExprKind::Binary node has no operator (`AssertionExpr::op()` \
returned None) -- this is a slang invariant violation, not a legal \
shape",
scope,
)
})?;
AssertionBinaryOp::try_from(raw).map(Op::Binary)
}
other => Err(LoweringError::unsupported_at(
&format!(
"AssertionExprKind::{other:?} never carries an operator -- \
`AssertionExpr::op()` is only valid for Unary/Binary (see its own doc \
comment)"
),
scope,
)),
}
}
}
#[derive(Debug, Clone, Copy)]
pub struct AbortChildren<'d> {
pub condition: SemNode<'d>,
pub body: AssertionExpr<'d>,
}
pub fn abort_children<'d>(
expr: AssertionExpr<'d>,
scope: &Symbol,
) -> Result<AbortChildren<'d>, LoweringError> {
if expr.kind() != AssertionExprKind::Abort {
return Err(LoweringError::unsupported_at(
&format!(
"abort_children: expected AssertionExprKind::Abort, found {:?}",
expr.kind()
),
scope,
));
}
let kids = expr.children();
let [condition, body] = kids.as_slice() else {
return Err(LoweringError::unsupported_at(
&format!(
"abort_children: expected exactly 2 children [condition, body], found {}",
kids.len()
),
scope,
));
};
let body = body.as_assertion_expr().ok_or_else(|| {
LoweringError::unsupported_at(
"abort_children: second child (body) is not an AssertionExpr",
scope,
)
})?;
Ok(AbortChildren {
condition: *condition,
body,
})
}
pub fn disable_iff_children<'d>(
expr: AssertionExpr<'d>,
scope: &Symbol,
) -> Result<AbortChildren<'d>, LoweringError> {
if expr.kind() != AssertionExprKind::DisableIff {
return Err(LoweringError::unsupported_at(
&format!(
"disable_iff_children: expected AssertionExprKind::DisableIff, found {:?}",
expr.kind()
),
scope,
));
}
let kids = expr.children();
let [condition, body] = kids.as_slice() else {
return Err(LoweringError::unsupported_at(
&format!(
"disable_iff_children: expected exactly 2 children [condition, body], found {}",
kids.len()
),
scope,
));
};
let body = body.as_assertion_expr().ok_or_else(|| {
LoweringError::unsupported_at(
"disable_iff_children: second child (body) is not an AssertionExpr",
scope,
)
})?;
Ok(AbortChildren {
condition: *condition,
body,
})
}
#[derive(Debug, Clone, Copy)]
pub struct ClockingChildren<'d> {
pub event: SemNode<'d>,
pub body: AssertionExpr<'d>,
}
pub fn clocking_children<'d>(
expr: AssertionExpr<'d>,
scope: &Symbol,
) -> Result<ClockingChildren<'d>, LoweringError> {
if expr.kind() != AssertionExprKind::Clocking {
return Err(LoweringError::unsupported_at(
&format!(
"clocking_children: expected AssertionExprKind::Clocking, found {:?}",
expr.kind()
),
scope,
));
}
let kids = expr.children();
let [event, body] = kids.as_slice() else {
return Err(LoweringError::unsupported_at(
&format!(
"clocking_children: expected exactly 2 children [event, body], found {}",
kids.len()
),
scope,
));
};
let body = body.as_assertion_expr().ok_or_else(|| {
LoweringError::unsupported_at(
"clocking_children: second child (body) is not an AssertionExpr",
scope,
)
})?;
Ok(ClockingChildren {
event: *event,
body,
})
}
#[derive(Debug, Clone)]
pub struct CaseItemChildren<'d> {
pub labels: Vec<SemNode<'d>>,
pub body: AssertionExpr<'d>,
}
#[derive(Debug, Clone)]
pub struct CaseChildren<'d> {
pub selector: SemNode<'d>,
pub items: Vec<CaseItemChildren<'d>>,
pub default: Option<AssertionExpr<'d>>,
}
fn next_child<'d>(
kids: &[SemNode<'d>],
idx: &mut usize,
scope: &Symbol,
) -> Result<SemNode<'d>, LoweringError> {
let node = kids.get(*idx).copied().ok_or_else(|| {
LoweringError::unsupported_at(
&format!(
"case_children: ran out of semantic children at index {idx} (only {} present)",
kids.len()
),
scope,
)
})?;
*idx += 1;
Ok(node)
}
pub fn case_children<'d>(
expr: AssertionExpr<'d>,
tree: Trees<'_>,
scope: &Symbol,
) -> Result<CaseChildren<'d>, LoweringError> {
if expr.kind() != AssertionExprKind::Case {
return Err(LoweringError::unsupported_at(
&format!(
"case_children: expected AssertionExprKind::Case, found {:?}",
expr.kind()
),
scope,
));
}
let syn = tree.syntax(&expr).ok_or_else(|| {
LoweringError::unsupported_at(
"case_children: Case assertion expression has no syntax node in the given tree",
scope,
)
})?;
let syn = peel_property_parens(syn);
let case_syn = CasePropertyExprSyntax::cast(syn).ok_or_else(|| {
LoweringError::unsupported_at(
&format!(
"case_children: expected CasePropertyExprSyntax syntax, found {:?}",
syn.kind()
),
scope,
)
})?;
let mut label_counts = Vec::new();
let mut has_default = false;
for item in case_syn.items() {
match item {
PropertyCaseItemSyntax::StandardPropertyCaseItem(s) => {
label_counts.push(s.expressions().iter().count());
}
PropertyCaseItemSyntax::DefaultPropertyCaseItem(_) => {
has_default = true;
}
}
}
let kids = expr.children();
let mut idx = 0usize;
let selector = next_child(&kids, &mut idx, scope)?;
let mut items = Vec::with_capacity(label_counts.len());
for count in &label_counts {
let mut labels = Vec::with_capacity(*count);
for _ in 0..*count {
labels.push(next_child(&kids, &mut idx, scope)?);
}
let body_node = next_child(&kids, &mut idx, scope)?;
let body = body_node.as_assertion_expr().ok_or_else(|| {
LoweringError::unsupported_at(
"case_children: an item body child is not an AssertionExpr",
scope,
)
})?;
items.push(CaseItemChildren { labels, body });
}
let default = if has_default {
let body_node = next_child(&kids, &mut idx, scope)?;
let body = body_node.as_assertion_expr().ok_or_else(|| {
LoweringError::unsupported_at(
"case_children: the default item's body child is not an AssertionExpr",
scope,
)
})?;
Some(body)
} else {
None
};
if idx != kids.len() {
return Err(LoweringError::unsupported_at(
&format!(
"case_children: {} semantic children left over after decoding selector + {} \
item(s) + default ({}); expected exactly {idx}, found {}",
kids.len() - idx,
label_counts.len(),
has_default,
kids.len()
),
scope,
));
}
Ok(CaseChildren {
selector,
items,
default,
})
}
#[cfg(test)]
mod tests {
use super::*;
use sv_lang::{AnalysisFlags, Compilation, Session, SyntaxTree};
fn compiled(extra_decls: &str, property_body: &str) -> (sv_lang::Design, String) {
let src = format!(
"module m;\n\
{extra_decls}\n\
logic clk, a, b, c, rst;\n\
always_ff @(posedge clk) begin\n\
assert property (@(posedge clk) {property_body});\n\
end\n\
endmodule\n"
);
let session = Session::new();
let mut comp = Compilation::new(&session).unwrap();
comp.add_source(&src).unwrap();
let design = comp.compile().unwrap();
assert!(
!design.has_fatal_errors(),
"fixture failed to compile: {src}"
);
(design, src)
}
fn with_clocking_root<R>(
extra_decls: &str,
property_body: &str,
f: impl FnOnce(AssertionExpr<'_>, Symbol<'_>) -> R,
) -> R {
let (design, _src) = compiled(extra_decls, property_body);
let body = design
.top_instances()
.next()
.unwrap()
.instance_body()
.unwrap();
let analysis = design.analyze(AnalysisFlags::NONE, 1).unwrap();
let proc = analysis.procedures(body).next().unwrap();
let assertion = analysis.assertions(proc.symbol()).next().unwrap();
let root = assertion.root().as_assertion_expr().unwrap();
assert_eq!(root.kind(), AssertionExprKind::Clocking);
f(root, body)
}
fn with_property_root<R>(
extra_decls: &str,
property_body: &str,
f: impl FnOnce(AssertionExpr<'_>, Symbol<'_>) -> R,
) -> R {
with_clocking_root(extra_decls, property_body, |clocking, body| {
let root = clocking.children()[1].as_assertion_expr().unwrap();
f(root, body)
})
}
fn with_property_root_and_tree<R>(
extra_decls: &str,
property_body: &str,
f: impl FnOnce(AssertionExpr<'_>, &SyntaxTree, Symbol<'_>) -> R,
) -> R {
let (design, _src) = compiled(extra_decls, property_body);
let body = design
.top_instances()
.next()
.unwrap()
.instance_body()
.unwrap();
let analysis = design.analyze(AnalysisFlags::NONE, 1).unwrap();
let proc = analysis.procedures(body).next().unwrap();
let assertion = analysis.assertions(proc.symbol()).next().unwrap();
let clocking = assertion.root().as_assertion_expr().unwrap();
assert_eq!(clocking.kind(), AssertionExprKind::Clocking);
let root = clocking.children()[1].as_assertion_expr().unwrap();
let tree = design.syntax_trees().into_iter().next().unwrap();
f(root, &tree, body)
}
#[test]
fn op_try_from_matches_every_pinned_cpp_value() {
let expected_unary = [
(0, AssertionUnaryOp::Not),
(1, AssertionUnaryOp::NextTime),
(2, AssertionUnaryOp::SNextTime),
(3, AssertionUnaryOp::Always),
(4, AssertionUnaryOp::SAlways),
(5, AssertionUnaryOp::Eventually),
(6, AssertionUnaryOp::SEventually),
];
assert_eq!(expected_unary.len(), AssertionUnaryOp::COUNT as usize);
for (raw, want) in expected_unary {
assert_eq!(AssertionUnaryOp::try_from(raw).unwrap(), want);
}
assert_eq!(
AssertionUnaryOp::ALL.to_vec(),
expected_unary.iter().map(|(_, v)| *v).collect::<Vec<_>>()
);
let expected_binary = [
(0, AssertionBinaryOp::And),
(1, AssertionBinaryOp::Or),
(2, AssertionBinaryOp::Intersect),
(3, AssertionBinaryOp::Throughout),
(4, AssertionBinaryOp::Within),
(5, AssertionBinaryOp::Iff),
(6, AssertionBinaryOp::Until),
(7, AssertionBinaryOp::SUntil),
(8, AssertionBinaryOp::UntilWith),
(9, AssertionBinaryOp::SUntilWith),
(10, AssertionBinaryOp::Implies),
(11, AssertionBinaryOp::OverlappedImplication),
(12, AssertionBinaryOp::NonOverlappedImplication),
(13, AssertionBinaryOp::OverlappedFollowedBy),
(14, AssertionBinaryOp::NonOverlappedFollowedBy),
];
assert_eq!(expected_binary.len(), AssertionBinaryOp::COUNT as usize);
for (raw, want) in expected_binary {
assert_eq!(AssertionBinaryOp::try_from(raw).unwrap(), want);
}
assert_eq!(
AssertionBinaryOp::ALL.to_vec(),
expected_binary.iter().map(|(_, v)| *v).collect::<Vec<_>>()
);
assert_eq!(
AssertionUnaryOp::COUNT + AssertionBinaryOp::COUNT,
22,
"plan §1.1 pins this at 22 variants total"
);
}
#[test]
fn op_try_from_rejects_out_of_range() {
for raw in [-1, i32::MIN, 7, 100] {
assert!(
AssertionUnaryOp::try_from(raw).is_err(),
"raw {raw} must be rejected, not panic or silently coerce"
);
}
for raw in [-1, i32::MIN, 15, 100] {
assert!(
AssertionBinaryOp::try_from(raw).is_err(),
"raw {raw} must be rejected, not panic or silently coerce"
);
}
}
#[test]
fn op_of_resolves_unary_and_binary_and_rejects_other_kinds() {
with_property_root("", "not a", |root, body| {
assert_eq!(root.kind(), AssertionExprKind::Unary);
assert_eq!(
Op::of(root, &body).unwrap(),
Op::Unary(AssertionUnaryOp::Not)
);
});
with_property_root("", "a and b", |root, body| {
assert_eq!(root.kind(), AssertionExprKind::Binary);
assert_eq!(
Op::of(root, &body).unwrap(),
Op::Binary(AssertionBinaryOp::And)
);
});
with_property_root("", "a", |root, body| {
assert_eq!(root.kind(), AssertionExprKind::Simple);
let err = Op::of(root, &body).expect_err("Simple has no operator");
assert!(err.message.contains("Simple"), "error was: {err}");
});
}
#[test]
fn abort_children_are_condition_then_body() {
with_property_root("", "accept_on (rst) a", |root, body| {
assert_eq!(root.kind(), AssertionExprKind::Abort);
let decoded = abort_children(root, &body).unwrap();
assert_eq!(decoded.body.kind(), AssertionExprKind::Simple);
});
}
#[test]
fn abort_children_rejects_non_abort_kind() {
with_property_root("", "a", |root, body| {
let err = abort_children(root, &body).expect_err("Simple is not Abort");
assert!(err.message.contains("Abort"), "error was: {err}");
});
}
#[test]
fn clocking_children_are_event_then_body() {
with_clocking_root("", "a", |clocking, body| {
let decoded = clocking_children(clocking, &body).unwrap();
assert_eq!(decoded.body.kind(), AssertionExprKind::Simple);
});
}
#[test]
fn clocking_children_rejects_non_clocking_kind() {
with_property_root("", "a", |root, body| {
let err = clocking_children(root, &body).expect_err("Simple is not Clocking");
assert!(err.message.contains("Clocking"), "error was: {err}");
});
}
#[test]
fn case_children_decode_selector_items_default() {
with_property_root_and_tree(
"parameter int sel = 0;",
"case (sel) 0, 1 : a; 2 : b; default : c; endcase",
|root, tree, body| {
assert_eq!(root.kind(), AssertionExprKind::Case);
let decoded = case_children(root, tree.into(), &body).unwrap();
assert_eq!(decoded.items.len(), 2, "two non-default items");
assert_eq!(decoded.items[0].labels.len(), 2, "first item has 2 labels");
assert_eq!(decoded.items[1].labels.len(), 1, "second item has 1 label");
assert_eq!(decoded.items[0].body.kind(), AssertionExprKind::Simple);
assert_eq!(decoded.items[1].body.kind(), AssertionExprKind::Simple);
assert!(decoded.default.is_some(), "default item recovered");
assert_eq!(decoded.default.unwrap().kind(), AssertionExprKind::Simple);
},
);
}
#[test]
fn case_children_decode_with_no_default() {
with_property_root_and_tree(
"parameter int sel = 0;",
"case (sel) 0 : a; 1 : b; endcase",
|root, tree, body| {
let decoded = case_children(root, tree.into(), &body).unwrap();
assert_eq!(decoded.items.len(), 2);
assert!(decoded.default.is_none(), "no default item in this fixture");
},
);
}
#[test]
fn case_children_rejects_non_case_kind() {
with_property_root_and_tree("", "a", |root, tree, body| {
let err = case_children(root, tree.into(), &body).expect_err("Simple is not Case");
assert!(err.message.contains("Case"), "error was: {err}");
});
}
}