use std::collections::BTreeMap;
use sv_lang::{AnalysisFlags, Compilation, Session, SyntaxTree};
use sv_sim_sim::sva::ir::LeafId;
use sv_sim_sim::sva::property::{Evaluator, PropEnv, Verdict};
use sv_sim_sim::sva::trace::{self, Timeline, TraceError};
use super::super::LeafLocals;
use super::*;
const X: i64 = i64::MIN;
fn with_property<R>(
decls: &str,
stmt: &str,
allow_errors: bool,
f: impl FnOnce(Result<(PropertyIr, LeafTable<'_>), LoweringError>, &SyntaxTree) -> R,
) -> R {
let src = format!(
"module m;\n\
logic clk, sysclk, a, b, c, d, e, x, y, z, reset;\n\
logic [7:0] bb;\n\
logic [1:0] dly;\n\
int n;\n\
parameter my_delay = 2;\n\
{decls}\n\
always_ff @(posedge clk) begin\n\
{stmt}\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();
let diags = design.diagnostics();
if !allow_errors {
assert!(
!diags.has_errors(),
"fixture has compile errors:\n{}\n{src}",
diags.rendered()
);
}
let m = design
.top_instances()
.next()
.unwrap()
.instance_body()
.unwrap();
let analysis = design.analyze(AnalysisFlags::NONE, 1).unwrap();
let proc = analysis.procedures(m).next().unwrap();
let Some(assertion) = analysis.assertions(proc.symbol()).next() else {
panic!(
"slang dropped the assertion; analysis diagnostics:\n{}",
analysis.diagnostics().rendered()
);
};
let tree = design.syntax_trees().into_iter().next().unwrap();
let root = assertion.root().as_assertion_expr().unwrap();
let (root, root_clock) = if root.kind() == AssertionExprKind::Clocking {
let kids = root.children();
(kids[1].as_assertion_expr().unwrap(), Some(kids[0]))
} else {
(root, None)
};
f(build_property(root, root_clock, (&tree).into(), &m), &tree)
}
fn ap(prop: &str) -> String {
format!("assert property (@(posedge clk) {prop});")
}
fn rejected(decls: &str, prop: &str) -> String {
with_property(decls, &ap(prop), false, |r, _| match r {
Ok(_) => panic!("`{prop}` was expected to be rejected"),
Err(e) => e.to_string(),
})
}
fn rejected_lenient(decls: &str, prop: &str) -> String {
with_property(decls, &ap(prop), true, |r, _| match r {
Ok(_) => panic!("`{prop}` was expected to be rejected"),
Err(e) => e.to_string(),
})
}
fn builds(decls: &str, prop: &str) {
with_property(decls, &ap(prop), false, |r, _| {
r.unwrap_or_else(|e| panic!("`{prop}` failed to build: {e}"));
});
}
type Rows = BTreeMap<String, Vec<i64>>;
fn rows(spec: &[(&str, &str)]) -> Rows {
spec.iter()
.map(|(name, samples)| {
(
name.to_string(),
samples
.chars()
.map(|c| match c {
'x' => X,
d => i64::from(d.to_digit(10).expect("a digit or x")),
})
.collect(),
)
})
.collect()
}
struct Sigs<'a, 'd> {
leaves: &'a LeafTable<'d>,
rows: &'a Rows,
between: BTreeMap<String, Vec<i64>>,
timeline: Option<Timeline>,
}
impl<'a, 'd> Sigs<'a, 'd> {
fn new(leaves: &'a LeafTable<'d>, rows: &'a Rows) -> Self {
Sigs {
leaves,
rows,
between: BTreeMap::new(),
timeline: None,
}
}
fn eval(
&self,
leaf: LeafId,
tick: u32,
frame: &[Option<i64>],
table: &BTreeMap<String, Vec<i64>>,
) -> Result<i64, TraceError> {
let v = self.raw(leaf, tick, frame, table)?;
Ok(if self.leaves.is_negated(leaf) {
i64::from(not4(v) == 1)
} else if v == X {
0
} else {
v
})
}
fn raw(
&self,
leaf: LeafId,
tick: u32,
frame: &[Option<i64>],
table: &BTreeMap<String, Vec<i64>>,
) -> Result<i64, TraceError> {
let sig = |name: &str, t: u32| -> i64 {
table
.get(name)
.unwrap_or_else(|| panic!("the trace has no signal `{name}`"))[t as usize]
};
eval_leaf(
self.leaves.get(leaf),
self.leaves.locals_of(leaf),
frame,
tick,
&sig,
)
}
}
impl PropEnv for Sigs<'_, '_> {
fn value(&self, leaf: LeafId, tick: u32, frame: &[Option<i64>]) -> Result<i64, TraceError> {
self.eval(leaf, tick, frame, self.rows)
}
fn case_match(
&self,
selector: LeafId,
label: LeafId,
tick: u32,
frame: &[Option<i64>],
) -> Result<bool, TraceError> {
let s = self.raw(selector, tick, frame, self.rows)?;
let l = self.raw(label, tick, frame, self.rows)?;
Ok(s != X && s == l)
}
fn between(&self, leaf: LeafId, after: u32) -> Result<bool, TraceError> {
if self.between.is_empty() {
return Ok(false);
}
let frame: Vec<Option<i64>> = Vec::new();
Ok(self.eval(leaf, after, &frame, &self.between)? != 0)
}
fn timeline(&self) -> Option<&Timeline> {
self.timeline.as_ref()
}
}
fn not4(v: i64) -> i64 {
if v == X { X } else { i64::from(v == 0) }
}
fn eval_leaf<'d>(
e: Expression<'d>,
locals: &LeafLocals<'d>,
frame: &[Option<i64>],
tick: u32,
sig: &dyn Fn(&str, u32) -> i64,
) -> Result<i64, TraceError> {
let kids = e.as_sem_node().children();
let sub = |i: usize| -> Result<i64, TraceError> {
let k = kids[i]
.as_expression()
.expect("an operand of a leaf expression is an expression");
eval_leaf(k, locals, frame, tick, sig)
};
match e.kind() {
ExpressionKind::IntegerLiteral => Ok(e
.integer_literal_value()
.and_then(|v| v.as_i64())
.expect("an integer literal fits an i64")),
ExpressionKind::NamedValue => {
let sym = e.referenced_symbol().expect("a named value has a symbol");
match locals.reads.iter().find(|(s, _)| *s == sym) {
Some((_, slot)) => trace::read_local(frame, *slot),
None => Ok(sig(sym.name(), tick)),
}
}
ExpressionKind::LValueReference => trace::read_local(
frame,
locals
.lvalue
.expect("an LValueReference leaf has an lvalue"),
),
ExpressionKind::Conversion => sub(0),
ExpressionKind::UnaryOp => match e.unary_op() {
Some(sv_lang::UnaryOp::LogicalNot) => Ok(not4(sub(0)?)),
other => panic!("test evaluator: unary operator {other:?}"),
},
ExpressionKind::BinaryOp => {
let (l, r) = (sub(0)?, sub(1)?);
use sv_lang::BinaryOp as B;
let ambiguous = l == X || r == X;
let four = |v: i64| if ambiguous { X } else { v };
match e.binary_op() {
Some(B::Add) => Ok(four(l.wrapping_add(r))),
Some(B::Subtract) => Ok(four(l.wrapping_sub(r))),
Some(B::Equality) => Ok(four(i64::from(l == r))),
Some(B::Inequality) => Ok(four(i64::from(l != r))),
Some(B::CaseEquality) => Ok(i64::from(l == r)),
Some(B::CaseInequality) => Ok(i64::from(l != r)),
Some(B::GreaterThan) => Ok(four(i64::from(l > r))),
Some(B::GreaterThanEqual) => Ok(four(i64::from(l >= r))),
Some(B::LessThan) => Ok(four(i64::from(l < r))),
Some(B::LessThanEqual) => Ok(four(i64::from(l <= r))),
Some(B::LogicalAnd) => Ok(if l == 0 || r == 0 { 0 } else { four(1) }),
Some(B::LogicalOr) => Ok(if (l != X && l != 0) || (r != X && r != 0) {
1
} else {
four(0)
}),
Some(B::BinaryAnd) => Ok(if l == 0 || r == 0 { 0 } else { four(l & r) }),
Some(B::BinaryOr) => Ok(four(l | r)),
other => panic!("test evaluator: binary operator {other:?}"),
}
}
other => panic!("test evaluator: expression kind {other:?}"),
}
}
fn verdict(decls: &str, prop: &str, signals: &[(&str, &str)], start: u32, strong: bool) -> Verdict {
verdict_in(decls, prop, signals, start, strong, false)
}
fn verdict_in(
decls: &str,
prop: &str,
signals: &[(&str, &str)],
start: u32,
strong: bool,
allow_errors: bool,
) -> Verdict {
let table = rows(signals);
let len = table.values().next().expect("a signal").len() as u32;
with_property(decls, &ap(prop), allow_errors, |r, _| {
let (ir, leaves) = r.unwrap_or_else(|e| panic!("`{prop}` failed to build: {e}"));
let env = Sigs::new(&leaves, &table);
Evaluator::new(&ir, &env, strong)
.verdict(len, start)
.unwrap_or_else(|e| panic!("`{prop}` failed to evaluate: {e}"))
})
}
fn assert_verdict(decls: &str, prop: &str, signals: &[(&str, &str)]) -> Verdict {
verdict(decls, prop, signals, 0, false)
}
fn all_traces(names: &[&str], len: usize) -> Vec<Rows> {
all_traces_over(names, len, &[0, 1])
}
fn all_traces_over(names: &[&str], len: usize, digits: &[i64]) -> Vec<Rows> {
let base = digits.len() as u32;
let cells = names.len() * len;
(0..base.pow(cells as u32))
.map(|m| {
names
.iter()
.enumerate()
.map(|(i, name)| {
(
name.to_string(),
(0..len)
.map(|t| digits[(m / base.pow((i * len + t) as u32) % base) as usize])
.collect(),
)
})
.collect()
})
.collect()
}
fn all_verdicts(decls: &str, prop: &str, names: &[&str], max_len: usize) -> Vec<Verdict> {
all_verdicts_over(decls, prop, names, max_len, &[0, 1])
}
fn all_verdicts_over(
decls: &str,
prop: &str,
names: &[&str],
max_len: usize,
digits: &[i64],
) -> Vec<Verdict> {
with_property(decls, &ap(prop), false, |r, _| {
let (ir, leaves) = r.unwrap_or_else(|e| panic!("`{prop}` failed to build: {e}"));
let mut out = Vec::new();
for len in 1..=max_len {
for table in all_traces_over(names, len, digits) {
let env = Sigs::new(&leaves, &table);
for strong in [false, true] {
let ev = Evaluator::new(&ir, &env, strong);
for start in 0..len as u32 {
out.push(
ev.verdict(len as u32, start)
.unwrap_or_else(|e| panic!("`{prop}` failed to evaluate: {e}")),
);
}
}
}
}
out
})
}
fn equivalent(decls: &str, lhs: &str, rhs: &str, names: &[&str], max_len: usize) -> Vec<Verdict> {
let l = all_verdicts(decls, lhs, names, max_len);
let r = all_verdicts(decls, rhs, names, max_len);
assert_eq!(l.len(), r.len());
if let Some(i) = (0..l.len()).find(|&i| l[i] != r[i]) {
panic!(
"`{lhs}` and `{rhs}` differ at case #{i}: {:?} vs {:?}",
l[i], r[i]
);
}
assert!(
l.iter().any(|v| *v != l[0]),
"`{lhs}` has the same verdict ({:?}) on every trace: the comparison is vacuous",
l[0]
);
l
}
fn weakly_equivalent(decls: &str, rec: &str, flat: &str, names: &[&str], max_len: usize) {
let r = all_verdicts(decls, rec, names, max_len);
let f = all_verdicts(decls, flat, names, max_len);
assert_eq!(r.len(), f.len());
for i in 0..r.len() {
assert!(
r[i] == f[i] || (r[i] == Verdict::HoldsStrongly && f[i] == Verdict::Holds),
"`{rec}` and `{flat}` differ at case #{i}: {:?} vs {:?}",
r[i],
f[i]
);
}
assert!(
f.contains(&Verdict::Fails) && f.iter().any(|v| *v != Verdict::Fails),
"`{flat}` fails on every trace or on none: the comparison is vacuous"
);
}
fn leaf_texts(leaves: &LeafTable<'_>, tree: &SyntaxTree) -> Vec<String> {
(0..leaves.len() as u32)
.map(|i| {
let e = leaves.get(LeafId(i));
match e.syntax(tree) {
Some(n) => n.text().split_whitespace().collect::<Vec<_>>().join(""),
None => format!("<{:?}>", e.kind()),
}
})
.collect()
}
#[test]
fn t19_bare_sequence_is_weak_under_assert_and_strong_under_cover() {
let never_b = &[("a", "100"), ("b", "000")];
assert_eq!(
verdict("", "a ##[1:$] b", never_b, 0, false),
Verdict::Holds,
"assert: weak, no finite prefix refutes it but nothing matched yet"
);
assert_eq!(
verdict("", "a ##[1:$] b", never_b, 0, true),
Verdict::Holds,
"cover: strong, the match is still owed"
);
let hit = &[("a", "100"), ("b", "001")];
assert_eq!(
verdict("", "a ##[1:$] b", hit, 0, false),
Verdict::HoldsStrongly
);
assert_eq!(
verdict("", "a ##[1:$] b", hit, 0, true),
Verdict::HoldsStrongly
);
let cut = &[("b", "1"), ("c", "0")];
assert_eq!(verdict("", "b ##1 c", cut, 0, false), Verdict::Holds);
assert_eq!(verdict("", "b ##1 c", cut, 0, true), Verdict::Holds);
let bad = &[("b", "10"), ("c", "00")];
assert_eq!(verdict("", "b ##1 c", bad, 0, false), Verdict::Fails);
assert_eq!(verdict("", "b ##1 c", bad, 0, true), Verdict::Fails);
let full = &[("b", "10"), ("c", "01")];
assert_eq!(
verdict("", "b ##1 c", full, 0, false),
Verdict::HoldsStrongly
);
assert_eq!(
verdict("", "b ##1 c", full, 0, true),
Verdict::HoldsStrongly
);
}
#[test]
fn t19_strong_and_weak_ignore_the_directive() {
let sig = &[("a", "100"), ("b", "000")];
for cover in [false, true] {
for text in ["strong(a ##[1:$] b)", "(strong(a ##[1:$] b))"] {
assert_eq!(
verdict("", text, sig, 0, cover),
Verdict::Holds,
"{text} needs its match (cover={cover})"
);
}
for text in ["weak(a ##[1:$] b)", "((weak(a ##[1:$] b)))"] {
assert_eq!(
verdict("", text, sig, 0, cover),
Verdict::Holds,
"{text} is only refutable, not yet matched (cover={cover})"
);
}
}
let hit = &[("a", "100"), ("b", "001")];
assert_eq!(
verdict("", "strong(a ##[1:$] b)", hit, 0, false),
Verdict::HoldsStrongly
);
assert_eq!(
verdict("", "weak(a ##[1:$] b)", hit, 0, false),
Verdict::HoldsStrongly
);
let miss = &[("a", "1000"), ("b", "0001")];
assert_eq!(
verdict("", "strong(a ##[1:2] b)", miss, 0, false),
Verdict::Fails
);
}
#[test]
fn t19_sequence_property_holds_on_any_match() {
let sig = &[("a", "1000"), ("b", "0010")];
assert_eq!(
verdict("", "a ##[1:3] b", sig, 0, false),
Verdict::HoldsStrongly
);
assert_eq!(
verdict("", "a ##[1:2] b", sig, 0, false),
Verdict::HoldsStrongly
);
assert_eq!(verdict("", "a ##1 b", sig, 0, false), Verdict::Fails);
let cut = &[("a", "100"), ("b", "000")];
assert_eq!(verdict("", "a ##[1:5] b", cut, 0, false), Verdict::Holds);
assert_eq!(verdict("", "a ##[1:5] b", cut, 0, true), Verdict::Holds);
assert_eq!(verdict("", "a ##1 b", sig, 1, false), Verdict::Fails);
}
#[test]
fn t19_nondegeneracy_of_a_sequence_property() {
let never = rejected_lenient("", "(1'b1) intersect (1'b1 ##1 1'b1)");
assert!(never.contains("16.12.22"), "{never}");
assert!(never.contains("never match"), "{never}");
let empty_only = rejected_lenient("", "1'b1[*0]");
assert!(empty_only.contains("16.12.22"), "{empty_only}");
assert!(empty_only.contains("only the empty match"), "{empty_only}");
let both = rejected_lenient("", "a[*0:2]");
assert!(both.contains("16.12.22"), "{both}");
assert!(both.contains("empty match"), "{both}");
for text in ["strong(a[*0:2])", "weak(a[*0:2])", "weak(1'b1[*0])"] {
let msg = rejected_lenient("", text);
assert!(msg.contains("16.12.22"), "{text}: {msg}");
}
for text in [
"a[*1:2]",
"a ##1 b",
"1'b0",
"a[*2]",
"a ##0 b",
"a[->1]",
"(a[*0:2]) ##1 b",
] {
builds("", text);
}
}
#[test]
fn t19_constant_false_boolean_is_a_failing_property() {
let sig = &[("a", "1")];
assert_eq!(assert_verdict("", "1'b0", sig), Verdict::Fails);
assert_eq!(assert_verdict("", "1'b1", sig), Verdict::HoldsStrongly);
}
#[test]
fn t19_typed_property_formals() {
let decls = "property p(property q); q; endproperty";
assert_eq!(
assert_verdict(decls, "p(a ##1 b)", &[("a", "10"), ("b", "01")]),
Verdict::HoldsStrongly
);
assert_eq!(
assert_verdict(decls, "p(a ##1 b)", &[("a", "10"), ("b", "00")]),
Verdict::Fails
);
let decls = "property p(sequence s); s; endproperty";
assert_eq!(
assert_verdict(decls, "p(a ##2 b)", &[("a", "100"), ("b", "001")]),
Verdict::HoldsStrongly
);
let decls = "property p(x1); x1 ##1 b; endproperty";
assert_eq!(
assert_verdict(decls, "p(a)", &[("a", "10"), ("b", "01")]),
Verdict::HoldsStrongly
);
let decls = "property p(int k); a ##k b; endproperty";
assert_eq!(
assert_verdict(decls, "p(2)", &[("a", "100"), ("b", "001")]),
Verdict::HoldsStrongly
);
assert_eq!(
assert_verdict(decls, "p(1)", &[("a", "100"), ("b", "001")]),
Verdict::Fails
);
let decls = "property inner; a ##1 b; endproperty\n\
property p(property q); q; endproperty";
assert_eq!(
assert_verdict(decls, "p(inner)", &[("a", "10"), ("b", "01")]),
Verdict::HoldsStrongly
);
assert_eq!(
assert_verdict(decls, "p(inner)", &[("a", "10"), ("b", "00")]),
Verdict::Fails
);
let decls = "property p; a ##1 b; endproperty";
assert_eq!(
assert_verdict(decls, "p", &[("a", "10"), ("b", "01")]),
Verdict::HoldsStrongly
);
}
#[test]
fn t19_local_input_formal_and_declared_local_initialisation() {
let decls = "property p(local input int v); a ##1 (n == v); endproperty";
with_property(decls, &ap("p(3)"), false, |r, tree| {
let (ir, leaves) = r.unwrap();
let Prop::Let { writes, .. } = ir.prop(ir.root) else {
panic!("the instance with a local formal is a Let: {:?}", ir.props);
};
assert_eq!(writes.len(), 1);
assert_eq!(leaf_texts(&leaves, tree)[writes[0].1.0 as usize], "3");
});
assert_eq!(
assert_verdict(decls, "p(3)", &[("a", "10"), ("n", "03")]),
Verdict::HoldsStrongly
);
assert_eq!(
assert_verdict(decls, "p(3)", &[("a", "10"), ("n", "04")]),
Verdict::Fails
);
let decls = "property p(local input int f); int v = f + 1; a ##1 (n == v); endproperty";
assert_eq!(
assert_verdict(decls, "p(7)", &[("a", "10"), ("n", "08")]),
Verdict::HoldsStrongly
);
assert_eq!(
assert_verdict(decls, "p(7)", &[("a", "10"), ("n", "07")]),
Verdict::Fails
);
}
#[test]
fn t19_local_variable_assigned_inside_the_property_sequence() {
let decls = "property p; int v; (a, v = n) ##1 (n == v + 1); endproperty";
assert_eq!(
assert_verdict(decls, "p", &[("a", "10"), ("n", "45")]),
Verdict::HoldsStrongly
);
assert_eq!(
assert_verdict(decls, "p", &[("a", "10"), ("n", "44")]),
Verdict::Fails
);
}
fn slang_errors(decls: &str, prop: &str) -> String {
let src = format!(
"module m;\nlogic clk, a, b;\nint n;\n{decls}\nalways_ff @(posedge clk) begin\n{}\nend\nendmodule\n",
ap(prop)
);
let session = Session::new();
let mut comp = Compilation::new(&session).unwrap();
comp.add_source(&src).unwrap();
let design = comp.compile().unwrap();
design.diagnostics().rendered().to_string()
}
#[test]
fn t19_local_formal_of_a_property_must_be_input() {
let msg = slang_errors("property p(local output int v); a; endproperty", "p(n)");
assert!(
msg.contains("error"),
"slang accepted a local output formal: {msg:?}"
);
}
#[test]
fn t19_disable_iff_inside_a_property_is_the_specs_disable_iff() {
with_property(
"property p; disable iff (reset) a ##1 b; endproperty",
&ap("p"),
false,
|r, _| {
let (ir, _) = r.expect("`p` builds");
assert!(
ir.disable.is_some(),
"the `disable iff` condition was dropped"
);
},
);
}
#[test]
fn t20_not_is_the_opposite() {
assert_eq!(assert_verdict("", "not a", &[("a", "1")]), Verdict::Fails);
assert_eq!(
assert_verdict("", "not a", &[("a", "0")]),
Verdict::HoldsStrongly
);
assert_eq!(
assert_verdict("", "not (if (a) b)", &[("a", "1"), ("b", "0")]),
Verdict::HoldsStrongly
);
assert_eq!(
assert_verdict("", "not (if (a) b)", &[("a", "1"), ("b", "1")]),
Verdict::Fails
);
assert_eq!(
assert_verdict("", "not (if (a) b)", &[("a", "0"), ("b", "1")]),
Verdict::Fails
);
equivalent("", "not not (a ##1 b)", "a ##1 b", &["a", "b"], 3);
equivalent(
"",
"not not (if (a) (b ##1 c))",
"if (a) (b ##1 c)",
&["a", "b", "c"],
3,
);
}
#[test]
fn t20_not_switches_the_strength_of_a_sequence() {
let sig = &[("a", "100"), ("b", "000")];
assert_eq!(
verdict("", "not (a ##[1:$] b)", sig, 0, false),
Verdict::Fails
);
assert_eq!(
verdict("", "not strong(a ##[1:$] b)", sig, 0, false),
Verdict::Holds
);
let weak = all_verdicts("", "not (a ##[1:$] b)", &["a", "b"], 3);
let strong = all_verdicts("", "not strong(a ##[1:$] b)", &["a", "b"], 3);
assert_eq!(weak.len(), strong.len());
assert!(weak.iter().zip(&strong).any(|(w, s)| w != s));
equivalent("", "not (a ##1 b)", "not strong(a ##1 b)", &["a", "b"], 3);
}
#[test]
fn t20_and_or_of_properties() {
for (text, a, b, want) in [
("(not a) or (not b)", "1", "1", Verdict::Fails),
("(not a) or (not b)", "1", "0", Verdict::HoldsStrongly),
("(not a) or (not b)", "0", "1", Verdict::HoldsStrongly),
("(not a) or (not b)", "0", "0", Verdict::HoldsStrongly),
("(not a) and (not b)", "1", "0", Verdict::Fails),
("(not a) and (not b)", "0", "1", Verdict::Fails),
("(not a) and (not b)", "0", "0", Verdict::HoldsStrongly),
] {
assert_eq!(
assert_verdict("", text, &[("a", a), ("b", b)]),
want,
"{text} at a={a} b={b}"
);
}
equivalent("", "(not a) and (not b)", "not (a or b)", &["a", "b"], 3);
equivalent(
"",
"(if (a) b) and (if (c) d)",
"not ((not (if (a) b)) or (not (if (c) d)))",
&["a", "b", "c", "d"],
3,
);
equivalent(
"",
"(a ##1 b) or (not c)",
"(not c) or (a ##1 b)",
&["a", "b", "c"],
3,
);
}
#[test]
fn t20_if_else_property() {
let decls = "";
assert_eq!(
assert_verdict(
decls,
"if (a) (b ##1 c)",
&[("a", "0"), ("b", "00"), ("c", "00")]
),
Verdict::HoldsStrongly
);
assert_eq!(
assert_verdict(
decls,
"if (a) (b ##1 c)",
&[("a", "10"), ("b", "10"), ("c", "01")]
),
Verdict::HoldsStrongly
);
assert_eq!(
assert_verdict(
decls,
"if (a) (b ##1 c)",
&[("a", "10"), ("b", "10"), ("c", "00")]
),
Verdict::Fails
);
assert_eq!(
assert_verdict(
decls,
"if (a) (b ##1 c) else (d ##1 e)",
&[
("a", "00"),
("b", "10"),
("c", "01"),
("d", "10"),
("e", "00")
]
),
Verdict::Fails
);
assert_eq!(
assert_verdict(
decls,
"if (a) (b ##1 c) else (d ##1 e)",
&[
("a", "00"),
("b", "00"),
("c", "00"),
("d", "10"),
("e", "01")
]
),
Verdict::HoldsStrongly
);
assert_eq!(
assert_verdict(
decls,
"if (a) (b ##1 c) else (d ##1 e)",
&[
("a", "01"),
("b", "00"),
("c", "00"),
("d", "10"),
("e", "01")
]
),
Verdict::HoldsStrongly
);
equivalent(
"",
"if (a) (b ##1 c) else (not b)",
"(a and (b ##1 c)) or ((not a) and (not b))",
&["a", "b", "c"],
3,
);
equivalent("", "if (a) (not b)", "(not a) or (not b)", &["a", "b"], 3);
}
#[test]
fn t20_case_p_delay() {
let decls = "property p_delay(logic [1:0] delay);\n\
case (delay)\n\
2'd0 : a && b;\n\
2'd1 : a ##2 b;\n\
2'd2 : a ##4 b;\n\
2'd3 : a ##8 b;\n\
default: 0;\n\
endcase\n\
endproperty";
let run = |dly: char, a: &str, b: &str| {
let n = a.len();
let d: String = std::iter::repeat_n(dly, n).collect();
assert_verdict(decls, "p_delay(dly)", &[("dly", &d), ("a", a), ("b", b)])
};
assert_eq!(run('0', "1", "1"), Verdict::HoldsStrongly);
assert_eq!(run('0', "10", "01"), Verdict::Fails);
assert_eq!(run('1', "100", "001"), Verdict::HoldsStrongly);
assert_eq!(run('1', "100", "010"), Verdict::Fails);
assert_eq!(run('2', "10000", "00001"), Verdict::HoldsStrongly);
assert_eq!(run('2', "10000", "00100"), Verdict::Fails);
assert_eq!(run('3', "100000000", "000000001"), Verdict::HoldsStrongly);
assert_eq!(run('3', "100000000", "000010000"), Verdict::Fails);
assert_eq!(run('x', "1", "1"), Verdict::Fails);
let table = &[("dly", "10"), ("a", "01"), ("b", "01")];
assert_eq!(
verdict(decls, "p_delay(dly)", table, 1, false),
Verdict::HoldsStrongly
);
let table = &[("dly", "10"), ("a", "01"), ("b", "00")];
assert_eq!(
verdict(decls, "p_delay(dly)", table, 1, false),
Verdict::Fails
);
}
#[test]
fn t20_case_without_default_and_first_match() {
let decls = "";
let prop = "case (dly) 2'd0: a; 2'd1: b; endcase";
let sig = |dly: &'static str, a: &'static str, b: &'static str| {
assert_verdict(decls, prop, &[("dly", dly), ("a", a), ("b", b)])
};
assert_eq!(sig("0", "1", "0"), Verdict::HoldsStrongly);
assert_eq!(sig("0", "0", "1"), Verdict::Fails);
assert_eq!(sig("1", "0", "1"), Verdict::HoldsStrongly);
assert_eq!(sig("1", "1", "0"), Verdict::Fails);
assert_eq!(sig("2", "0", "0"), Verdict::HoldsStrongly);
assert_eq!(sig("3", "0", "0"), Verdict::HoldsStrongly);
assert_eq!(sig("x", "0", "0"), Verdict::HoldsStrongly);
let prop = "case (dly) 2'd0, 2'd2: a; 2'd2: b; default: 0; endcase";
let sig = |dly: &'static str, a: &'static str, b: &'static str| {
assert_verdict(decls, prop, &[("dly", dly), ("a", a), ("b", b)])
};
assert_eq!(sig("2", "1", "0"), Verdict::HoldsStrongly, "item 0 wins");
assert_eq!(sig("2", "0", "1"), Verdict::Fails, "item 0 wins");
assert_eq!(sig("3", "1", "1"), Verdict::Fails, "default");
}
#[test]
fn t20_case_equals_the_if_chain() {
equivalent(
"",
"case (a) 1: (b ##1 c); default: (not b); endcase",
"if (a == 1) (b ##1 c) else (not b)",
&["a", "b", "c"],
3,
);
}
#[test]
fn t21_overlapped_implication_truth_table() {
for (a, b, want) in [
("0", "0", Verdict::HoldsStrongly),
("0", "1", Verdict::HoldsStrongly),
("1", "1", Verdict::HoldsStrongly),
("1", "0", Verdict::Fails),
] {
assert_eq!(
assert_verdict("", "a |-> b", &[("a", a), ("b", b)]),
want,
"a |-> b at a={a} b={b}"
);
}
assert_eq!(
assert_verdict("", "a |-> ##1 b", &[("a", "1"), ("b", "0")]),
Verdict::Holds
);
assert_eq!(
assert_verdict("", "a |-> ##1 b", &[("a", "10"), ("b", "01")]),
Verdict::HoldsStrongly
);
assert_eq!(
assert_verdict("", "a |-> ##1 b", &[("a", "10"), ("b", "00")]),
Verdict::Fails
);
}
#[test]
fn t21_data_end_window() {
let decls = "property data_end; a |-> ##[1:5] b; endproperty";
let a = "01000000";
for (b, want) in [
("00000010", Verdict::HoldsStrongly),
("00100000", Verdict::HoldsStrongly),
("00000001", Verdict::Fails),
("01000000", Verdict::Fails),
] {
assert_eq!(
verdict(decls, "data_end", &[("a", a), ("b", b)], 1, false),
want,
"b = {b}"
);
}
assert_eq!(
verdict(decls, "data_end", &[("a", a), ("b", "00000000")], 0, false),
Verdict::HoldsStrongly
);
}
#[test]
fn t21_every_antecedent_match_is_checked() {
let decls = "";
let prop = "(a ##[1:2] b) |-> c";
let sig = |a: &'static str, b: &'static str, c: &'static str| {
assert_verdict(decls, prop, &[("a", a), ("b", b), ("c", c)])
};
assert_eq!(sig("1000", "0110", "0110"), Verdict::HoldsStrongly);
assert_eq!(sig("1000", "0110", "0100"), Verdict::Fails);
assert_eq!(sig("1000", "0110", "0010"), Verdict::Fails);
assert_eq!(sig("1000", "0010", "0010"), Verdict::HoldsStrongly);
assert_eq!(sig("1000", "0001", "0000"), Verdict::HoldsStrongly);
let prop = "(a ##1 b ##1 c) |-> (d ##1 e)";
let sig = |d: &'static str, e: &'static str, b: &'static str| {
assert_verdict(
decls,
prop,
&[("a", "1000"), ("b", b), ("c", "0010"), ("d", d), ("e", e)],
)
};
assert_eq!(sig("0010", "0001", "0100"), Verdict::HoldsStrongly);
assert_eq!(sig("0010", "0000", "0100"), Verdict::Fails);
assert_eq!(sig("0000", "0000", "0000"), Verdict::HoldsStrongly);
}
#[test]
fn t21_nonoverlapped_implication() {
let decls = "";
for (a, b, want) in [
("10", "01", Verdict::HoldsStrongly),
("10", "10", Verdict::Fails),
("10", "00", Verdict::Fails),
("00", "00", Verdict::HoldsStrongly),
] {
assert_eq!(
assert_verdict(decls, "a |=> b", &[("a", a), ("b", b)]),
want,
"a |=> b at a={a} b={b}"
);
}
assert_eq!(
assert_verdict(decls, "a |=> b", &[("a", "1"), ("b", "0")]),
Verdict::HoldsStrongly
);
for ante in ["a", "a ##[1:2] b", "a[*0:1]", "a[*0:1] ##1 b", "a ##0 b"] {
for cons in ["c", "b ##1 c", "not c", "if (b) c"] {
let rewritten = format!("{ante} ##1 1'b1 |-> {cons}");
let direct = format!("{ante} |=> {cons}");
equivalent(decls, &direct, &rewritten, &["a", "b", "c"], 3);
}
}
}
#[test]
fn t21_nested_implication() {
let decls = "";
equivalent(
decls,
"(a & b) ##0 c[*2] |-> ##[1:2] d",
"(a & b) |-> c[*2] |-> ##[1:2] d",
&["a", "b", "c", "d"],
4,
);
equivalent(
decls,
"a |-> b |-> c",
"a |-> (b |-> c)",
&["a", "b", "c"],
3,
);
equivalent(
decls,
"a |-> (b |-> c)",
"(a ##0 b) |-> c",
&["a", "b", "c"],
3,
);
equivalent(
decls,
"a |=> (b |-> c)",
"(a ##1 b) |-> c",
&["a", "b", "c"],
3,
);
}
#[test]
fn t21_followed_by() {
let decls = "";
assert_eq!(
assert_verdict(decls, "a |-> b", &[("a", "0"), ("b", "0")]),
Verdict::HoldsStrongly
);
assert_eq!(
assert_verdict(decls, "a #-# b", &[("a", "0"), ("b", "1")]),
Verdict::Fails
);
assert_eq!(
assert_verdict(decls, "a #-# b", &[("a", "1"), ("b", "1")]),
Verdict::HoldsStrongly
);
assert_eq!(
assert_verdict(decls, "a #-# b", &[("a", "1"), ("b", "0")]),
Verdict::Fails
);
let prop = "(a ##[1:2] b) #-# c";
let sig =
|c: &'static str| assert_verdict(decls, prop, &[("a", "1000"), ("b", "0110"), ("c", c)]);
assert_eq!(sig("0010"), Verdict::HoldsStrongly);
assert_eq!(sig("0100"), Verdict::HoldsStrongly);
assert_eq!(sig("0000"), Verdict::Fails);
assert_eq!(
assert_verdict(
decls,
"(a ##[1:2] b) |-> c",
&[("a", "1000"), ("b", "0110"), ("c", "0010")]
),
Verdict::Fails
);
for (a, b, want) in [
("10", "01", Verdict::HoldsStrongly),
("10", "10", Verdict::Fails),
("00", "01", Verdict::Fails),
] {
assert_eq!(
assert_verdict(decls, "a #=# b", &[("a", a), ("b", b)]),
want,
"a #=# b at a={a} b={b}"
);
}
}
#[test]
fn t21_followed_by_is_the_dual_of_implication() {
let decls = "";
for ante in ["a", "a ##[1:2] b", "a[*0:1] ##1 b"] {
for cons in ["c", "c ##1 d", "if (c) d"] {
equivalent(
decls,
&format!("{ante} #-# {cons}"),
&format!("not ({ante} |-> not ({cons}))"),
&["a", "b", "c", "d"],
3,
);
equivalent(
decls,
&format!("{ante} #=# {cons}"),
&format!("not ({ante} |=> not ({cons}))"),
&["a", "b", "c", "d"],
3,
);
}
}
let l = all_verdicts(decls, "a #-# b", &["a", "b"], 2);
let r = all_verdicts(decls, "a |-> b", &["a", "b"], 2);
assert!(l.iter().zip(&r).any(|(x, y)| x != y));
}
#[test]
fn t21_followed_by_of_a_strong_sequence() {
let decls = "";
let names = ["a", "b", "c", "d"];
let mut pending_corner = 0;
for ante in ["a", "a ##1 b", "a ##[1:2] b", "a[*0:1]"] {
for cons in ["c", "c ##[1:2] d"] {
for (op, gap) in [("#-#", "##0"), ("#=#", "##1")] {
let fb = format!("{ante} {op} strong({cons})");
let st = format!("strong({ante} {gap} {cons})");
let l = all_verdicts(decls, &fb, &names, 3);
let r = all_verdicts(decls, &st, &names, 3);
assert_eq!(l.len(), r.len());
for (i, (x, y)) in l.iter().zip(&r).enumerate() {
if x != y {
assert_eq!(
(*x, *y),
(Verdict::Fails, Verdict::Holds),
"`{fb}` and `{st}` differ at case #{i} other than in the pending corner"
);
pending_corner += 1;
}
}
assert!(
l.iter().any(|v| *v != l[0]),
"`{fb}` has the same verdict on every trace"
);
}
}
}
assert!(pending_corner > 0, "the pending corner is documented above");
let sig = &[("a", "1"), ("b", "0"), ("c", "0")];
assert_eq!(
assert_verdict(decls, "a ##1 b #-# strong(c)", sig),
Verdict::Fails
);
assert_eq!(
assert_verdict(decls, "strong(a ##1 b ##0 c)", sig),
Verdict::Holds
);
let sig = &[("a", "10"), ("b", "01"), ("c", "01")];
for text in ["a ##1 b #-# strong(c)", "strong(a ##1 b ##0 c)"] {
assert_eq!(assert_verdict(decls, text, sig), Verdict::HoldsStrongly);
}
}
#[test]
fn t21_nondegeneracy_of_antecedents() {
let never = "(1'b1) intersect (1'b1 ##1 1'b1)";
let empty_only = "1'b1[*0]";
let both = "a[*0:2]";
for op in ["|->", "#-#"] {
for bad in [never, empty_only] {
let msg = rejected_lenient("", &format!("{bad} {op} b"));
assert!(msg.contains("16.12.22 (b)"), "{bad} {op}: {msg}");
assert!(msg.contains("overlapped implication"), "{bad} {op}: {msg}");
}
builds("", &format!("{both} {op} b"));
builds("", &format!("a {op} b"));
}
for op in ["|=>", "#=#"] {
let msg = rejected_lenient("", &format!("{never} {op} b"));
assert!(msg.contains("16.12.22 (c)"), "{op}: {msg}");
assert!(msg.contains("at least one match"), "{op}: {msg}");
builds("", &format!("{empty_only} {op} b"));
builds("", &format!("{both} {op} b"));
builds("", &format!("a {op} b"));
}
for op in ["|->", "|=>", "#-#", "#=#"] {
let msg = rejected_lenient("", &format!("a {op} a[*0:1]"));
assert!(msg.contains("16.12.22 (a)"), "{op}: {msg}");
}
}
#[test]
fn t21_antecedent_local_variables_flow_into_the_consequent() {
for body in [
"(a, v = n) |-> ##1 (n == v + 1)",
"(a, v = n) |=> (n == v + 1)",
"(a, v = n) #-# ##1 (n == v + 1)",
"(a, v = n) #=# (n == v + 1)",
] {
let decls = format!("property p; int v; {body}; endproperty");
assert_eq!(
assert_verdict(&decls, "p", &[("a", "10"), ("n", "45")]),
Verdict::HoldsStrongly,
"{body}"
);
assert_eq!(
assert_verdict(&decls, "p", &[("a", "10"), ("n", "44")]),
Verdict::Fails,
"{body}"
);
}
let decls = "property p; int v; (a, v = n) |-> (b |-> ##1 (n == v + 1)); endproperty";
let t = |a, b, n| assert_verdict(decls, "p", &[("a", a), ("b", b), ("n", n)]);
assert_eq!(t("10", "10", "45"), Verdict::HoldsStrongly);
assert_eq!(t("10", "10", "44"), Verdict::Fails);
let decls = "property p; int v; (a, v = n) |-> (if (n == v) (b ##1 c)); endproperty";
let t = |b, c| assert_verdict(decls, "p", &[("a", "10"), ("n", "00"), ("b", b), ("c", c)]);
assert_eq!(t("10", "01"), Verdict::HoldsStrongly);
assert_eq!(t("10", "00"), Verdict::Fails);
}
#[test]
fn t21_variables_do_not_flow_across_other_operators() {
for body in [
"((a, v = n) |-> b) and (c |-> (n == v))",
"((a, v = n) |-> b) or (c |-> (n == v))",
"if (c) ((a, v = n) |-> b) else (d |-> (n == v))",
"not ((a, v = n) |-> b) and (c |-> (n == v))",
] {
let msg = rejected_lenient(&format!("property p; int v; {body}; endproperty"), "p");
assert!(msg.contains("does not flow"), "{body}: {msg}");
}
}
#[test]
fn t22_implies_truth_table() {
for (a, b, want) in [
("0", "0", Verdict::HoldsStrongly),
("0", "1", Verdict::HoldsStrongly),
("1", "1", Verdict::HoldsStrongly),
("1", "0", Verdict::Fails),
] {
assert_eq!(
assert_verdict("", "a implies b", &[("a", a), ("b", b)]),
want,
"a implies b at a={a} b={b}"
);
}
let prop = "(a ##1 b) implies (c ##1 d)";
let sig = |c: &'static str, d: &'static str| {
assert_verdict("", prop, &[("a", "10"), ("b", "01"), ("c", c), ("d", d)])
};
assert_eq!(sig("10", "01"), Verdict::HoldsStrongly);
assert_eq!(sig("10", "00"), Verdict::Fails);
assert_eq!(
assert_verdict(
"",
prop,
&[("a", "00"), ("b", "01"), ("c", "00"), ("d", "00")]
),
Verdict::HoldsStrongly
);
}
#[test]
fn t22_implies_is_not_or() {
let names = ["a", "b", "c", "d"];
equivalent("", "a implies b", "(not a) or b", &names, 3);
equivalent(
"",
"(a ##1 b) implies (c ##1 d)",
"(not (a ##1 b)) or (c ##1 d)",
&names,
3,
);
equivalent(
"",
"(a ##[1:$] b) implies strong(c ##1 d)",
"(not (a ##[1:$] b)) or strong(c ##1 d)",
&names,
3,
);
equivalent(
"",
"(if (a) b) implies (a |-> c)",
"(not (if (a) b)) or (a |-> c)",
&names,
3,
);
}
#[test]
fn t22_iff_truth_table() {
for (a, b, want) in [
("0", "0", Verdict::HoldsStrongly),
("0", "1", Verdict::Fails),
("1", "0", Verdict::Fails),
("1", "1", Verdict::HoldsStrongly),
] {
assert_eq!(
assert_verdict("", "a iff b", &[("a", a), ("b", b)]),
want,
"a iff b at a={a} b={b}"
);
}
let names = ["a", "b", "c", "d"];
equivalent("", "a iff b", "(a implies b) and (b implies a)", &names, 3);
equivalent(
"",
"(a ##1 b) iff (c |-> d)",
"((a ##1 b) implies (c |-> d)) and ((c |-> d) implies (a ##1 b))",
&names,
3,
);
equivalent(
"",
"(a ##1 b) iff (c ##1 d)",
"(c ##1 d) iff (a ##1 b)",
&names,
3,
);
equivalent(
"",
"(a ##[1:$] b) iff c",
"((a ##[1:$] b) implies c) and (c implies (a ##[1:$] b))",
&names,
3,
);
}
#[test]
fn t22_implies_and_iff_associativity() {
let names = ["a", "b", "c"];
equivalent(
"",
"a implies b implies c",
"a implies (b implies c)",
&names,
3,
);
equivalent("", "a iff b iff c", "a iff (b iff c)", &names, 3);
let l = all_verdicts("", "a implies b implies c", &names, 2);
let r = all_verdicts("", "(a implies b) implies c", &names, 2);
assert!(l.iter().zip(&r).any(|(x, y)| x != y));
}
#[test]
fn t22_variables_do_not_flow_across_implies_and_iff() {
for body in [
"((a, v = n) |-> b) implies (c |-> (n == v))",
"((a, v = n) |-> b) iff (c |-> (n == v))",
"(c |-> (n == v)) implies ((a, v = n) |-> b)",
] {
let msg = rejected_lenient(&format!("property p; int v; {body}; endproperty"), "p");
assert!(msg.contains("does not flow"), "{body}: {msg}");
}
let decls = "property p; int v; (a, v = n) |-> ((n == v) implies (b iff c)); endproperty";
builds(decls, "p");
}
fn nexttimes(decls: &str, prop: &str) -> Vec<(u32, bool)> {
with_property(decls, &ap(prop), false, |r, _| {
let (ir, _) = r.unwrap_or_else(|e| panic!("`{prop}` failed to build: {e}"));
ir.props
.iter()
.filter_map(|p| match p {
Prop::Nexttime { n, strong, .. } => Some((*n, *strong)),
_ => None,
})
.collect()
})
}
fn always_nodes(decls: &str, prop: &str) -> Vec<(u32, Option<u32>, bool)> {
with_property(decls, &ap(prop), false, |r, _| {
let (ir, _) = r.unwrap_or_else(|e| panic!("`{prop}` failed to build: {e}"));
ir.props
.iter()
.filter_map(|p| match p {
Prop::Always { lo, hi, strong, .. } => Some((*lo, *hi, *strong)),
_ => None,
})
.collect()
})
}
#[test]
fn t23_nexttime_and_s_nexttime() {
for prop in ["nexttime a", "s_nexttime a"] {
for (a, want) in [
("01", Verdict::HoldsStrongly),
("00", Verdict::Fails),
("10", Verdict::Fails),
("011", Verdict::HoldsStrongly),
("1", Verdict::Holds),
("0", Verdict::Holds),
] {
assert_eq!(
assert_verdict("", prop, &[("a", a)]),
want,
"{prop} on a={a}"
);
}
}
assert_eq!(nexttimes("", "nexttime a"), [(1, false)]);
assert_eq!(nexttimes("", "s_nexttime a"), [(1, true)]);
assert_eq!(
verdict("", "nexttime a", &[("a", "0101")], 2, false),
Verdict::HoldsStrongly
);
assert_eq!(
verdict("", "nexttime a", &[("a", "0110")], 2, false),
Verdict::Fails
);
}
#[test]
fn t23_indexed_nexttime() {
for prop in ["nexttime [2] a", "s_nexttime [2] a"] {
for (a, want) in [
("001", Verdict::HoldsStrongly),
("101", Verdict::HoldsStrongly),
("000", Verdict::Fails),
("110", Verdict::Fails),
("00", Verdict::Holds),
("0", Verdict::Holds),
] {
assert_eq!(
assert_verdict("", prop, &[("a", a)]),
want,
"{prop} on a={a}"
);
}
}
assert_eq!(nexttimes("", "nexttime [2] a"), [(2, false)]);
assert_eq!(nexttimes("", "s_nexttime [2] a"), [(2, true)]);
assert_eq!(nexttimes("", "nexttime [my_delay] a"), [(2, false)]);
assert_eq!(nexttimes("", "s_nexttime [my_delay + 1] a"), [(3, true)]);
assert_eq!(
nexttimes("property p(int k); nexttime [k] a; endproperty", "p(3)"),
[(3, false)]
);
}
#[test]
fn t23_nexttime_equivalences() {
let names = ["a", "b"];
equivalent("", "nexttime a", "nexttime [1] a", &names, 5);
equivalent("", "s_nexttime a", "s_nexttime [1] a", &names, 5);
equivalent("", "nexttime [0] a", "a", &names, 5);
equivalent("", "s_nexttime [0] a", "a", &names, 5);
equivalent("", "nexttime [2] a", "nexttime nexttime a", &names, 5);
equivalent(
"",
"s_nexttime [3] a",
"s_nexttime s_nexttime [2] a",
&names,
5,
);
equivalent("", "s_nexttime a", "not nexttime not a", &names, 5);
equivalent("", "nexttime a", "not s_nexttime not a", &names, 5);
equivalent(
"",
"s_nexttime (a ##1 b)",
"not nexttime not (a ##1 b)",
&names,
4,
);
equivalent(
"",
"nexttime [2] (a |-> ##1 b)",
"nexttime nexttime (a |-> ##1 b)",
&names,
5,
);
let weak = all_verdicts("", "nexttime [2] a", &names, 4);
let other = all_verdicts("", "nexttime [1] a", &names, 4);
assert_ne!(weak, other);
}
#[test]
fn t23_nexttime_always() {
for prop in ["nexttime always a", "s_nexttime always a"] {
for (a, want) in [
("0111", Verdict::Holds),
("1", Verdict::Holds),
("0", Verdict::Holds),
("0101", Verdict::Fails),
("1110", Verdict::Fails),
] {
assert_eq!(
assert_verdict("", prop, &[("a", a)]),
want,
"{prop} on a={a}"
);
}
}
assert_eq!(nexttimes("", "nexttime always a"), [(1, false)]);
assert_eq!(nexttimes("", "s_nexttime always a"), [(1, true)]);
assert_eq!(always_nodes("", "nexttime always a"), [(0, None, false)]);
}
#[test]
fn t23_always_truth_table() {
for prop in ["always a", "always [0:$] a"] {
for (a, want) in [
("111", Verdict::Holds),
("1", Verdict::Holds),
("101", Verdict::Fails),
("011", Verdict::Fails),
("110", Verdict::Fails),
] {
assert_eq!(
assert_verdict("", prop, &[("a", a)]),
want,
"{prop} on a={a}"
);
}
}
for prop in ["always [2:5] a", "s_always [2:5] a"] {
for (a, want) in [
("00111100", Verdict::HoldsStrongly),
("11111111", Verdict::HoldsStrongly),
("00111101", Verdict::HoldsStrongly),
("00101100", Verdict::Fails),
("00111000", Verdict::Fails),
("11011111", Verdict::Fails),
("0011", Verdict::Holds),
("0", Verdict::Holds),
("00111", Verdict::Holds),
("001111", Verdict::HoldsStrongly),
] {
assert_eq!(
assert_verdict("", prop, &[("a", a)]),
want,
"{prop} on a={a}"
);
}
}
assert_eq!(always_nodes("", "always [2:5] a"), [(2, Some(5), false)]);
assert_eq!(always_nodes("", "s_always [2:5] a"), [(2, Some(5), true)]);
assert_eq!(always_nodes("", "always a"), [(0, None, false)]);
for (a, want) in [
("0011", Verdict::Holds),
("00", Verdict::Holds),
("0010", Verdict::Fails),
("1111110", Verdict::Fails),
] {
assert_eq!(
assert_verdict("", "always [2:$] a", &[("a", a)]),
want,
"always [2:$] a on a={a}"
);
}
assert_eq!(always_nodes("", "always [2:$] a"), [(2, None, false)]);
assert_eq!(
always_nodes("", "always [my_delay:my_delay + 3] a"),
[(2, Some(5), false)]
);
assert_eq!(
always_nodes(
"property p(int lo, int hi); s_always [lo:hi] a; endproperty",
"p(1, 4)"
),
[(1, Some(4), true)]
);
}
#[test]
fn t23_always_equivalences() {
let names = ["a"];
equivalent("", "always a", "always [0:$] a", &names, 7);
equivalent(
"",
"always [2:5] a",
"nexttime [2] a and nexttime [3] a and nexttime [4] a and nexttime [5] a",
&names,
7,
);
equivalent(
"",
"s_always [2:5] a",
"s_nexttime [2] a and s_nexttime [3] a and s_nexttime [4] a and s_nexttime [5] a",
&names,
7,
);
equivalent("", "always [3:3] a", "nexttime [3] a", &names, 6);
equivalent("", "s_always [3:3] a", "s_nexttime [3] a", &names, 6);
equivalent("", "always [2:$] a", "nexttime [2] always a", &names, 7);
equivalent(
"",
"always a",
"(always [0:3] a) and nexttime [4] always a",
&names,
7,
);
let two = ["a", "b"];
equivalent(
"",
"always [1:2] (a |-> ##1 b)",
"nexttime (a |-> ##1 b) and nexttime [2] (a |-> ##1 b)",
&two,
5,
);
let single = all_verdicts("", "always [2:2] a", &names, 5);
assert_eq!(single, all_verdicts("", "nexttime [2] a", &names, 5));
}
#[test]
fn t23_always_is_every_start_tick() {
let names = ["a", "b"];
let prop = "a |-> ##1 b";
let always = format!("always ({prop})");
for len in 1..=5usize {
for table in all_traces(&names, len) {
let sig: Vec<(&str, String)> = names
.iter()
.map(|n| {
(
*n,
table[*n]
.iter()
.map(|v| char::from_digit(*v as u32, 10).unwrap())
.collect(),
)
})
.collect();
let sig: Vec<(&str, &str)> = sig.iter().map(|(n, s)| (*n, s.as_str())).collect();
let each: Vec<Verdict> = (0..len as u32)
.map(|t| verdict("", prop, &sig, t, false))
.collect();
let want = if each.contains(&Verdict::Fails) {
Verdict::Fails
} else if each.iter().all(|v| *v == Verdict::HoldsStrongly) {
Verdict::HoldsStrongly
} else {
Verdict::Holds
};
assert_eq!(
verdict("", &always, &sig, 0, false),
want,
"{always} on {sig:?} (per start tick: {each:?})"
);
}
}
}
#[test]
fn t23_lrm_always_examples() {
let p1 = "a ##1 b |=> always c";
let sig = |a, b, c| assert_verdict("", p1, &[("a", a), ("b", b), ("c", c)]);
assert_eq!(sig("1000", "0100", "0011"), Verdict::Holds);
assert_eq!(sig("1000", "0100", "0010"), Verdict::Fails);
assert_eq!(sig("1000", "0100", "0001"), Verdict::Fails);
assert_eq!(sig("1000", "0100", "1111"), Verdict::Holds);
assert_eq!(sig("0000", "0100", "0000"), Verdict::HoldsStrongly);
assert_eq!(sig("1000", "0000", "0000"), Verdict::HoldsStrongly);
let a1 = "reset[*5] #=# always !reset";
let sig = |r| assert_verdict("", a1, &[("reset", r)]);
assert_eq!(sig("11111000"), Verdict::Holds);
assert_eq!(sig("111110"), Verdict::Holds);
assert_eq!(sig("11111010"), Verdict::Fails);
assert_eq!(sig("11110000"), Verdict::Fails);
assert_eq!(sig("01111100"), Verdict::Fails);
let overlapped = "##[0:5] d #-# always !reset";
let nonoverlapped = "##[0:5] d #=# always !reset";
let sig = |p, d, r| assert_verdict("", p, &[("d", d), ("reset", r)]);
assert_eq!(sig(overlapped, "0010000", "1100000"), Verdict::Holds);
assert_eq!(sig(nonoverlapped, "0010000", "1100000"), Verdict::Holds);
assert_eq!(sig(overlapped, "0010000", "0010000"), Verdict::Fails);
assert_eq!(sig(nonoverlapped, "0010000", "0010000"), Verdict::Holds);
assert_eq!(sig(overlapped, "0010100", "0010000"), Verdict::Holds);
assert_eq!(sig(overlapped, "0010100", "0000100"), Verdict::Fails);
assert_eq!(sig(overlapped, "0000001", "0000000"), Verdict::Fails);
assert_eq!(sig(nonoverlapped, "0000001", "0000000"), Verdict::Fails);
}
#[test]
fn t23_variables_and_nexttime_always() {
let decls = "property p; int v; (a, v = n) |-> nexttime (n == v); endproperty";
assert_eq!(
assert_verdict(decls, "p", &[("a", "10"), ("n", "55")]),
Verdict::HoldsStrongly
);
assert_eq!(
assert_verdict(decls, "p", &[("a", "10"), ("n", "56")]),
Verdict::Fails
);
let decls = "property p; int v; (a, v = n) |=> always (n == v); endproperty";
assert_eq!(
assert_verdict(decls, "p", &[("a", "100"), ("n", "555")]),
Verdict::Holds
);
assert_eq!(
assert_verdict(decls, "p", &[("a", "100"), ("n", "565")]),
Verdict::Fails
);
let decls = "property p; int v; (a, v = n) |-> nexttime [2] always (n >= v); endproperty";
builds(decls, "p");
for body in [
"(nexttime ((a, v = n) |-> b)) and (c |-> (n == v))",
"(always ((a, v = n) |-> b)) and (c |-> (n == v))",
"(s_always [1:2] ((a, v = n) |-> b)) and (c |-> (n == v))",
"(s_nexttime [2] ((a, v = n) |-> b)) and (c |-> (n == v))",
] {
let msg = rejected_lenient(&format!("property p; int v; {body}; endproperty"), "p");
assert!(msg.contains("does not flow"), "{body}: {msg}");
}
}
#[test]
fn t23_illegal_forms_are_rejected() {
let msg = rejected_lenient("", "s_always [2:$] a");
assert!(
msg.contains("16.12.11") && msg.contains("bounded"),
"s_always [2:$]: {msg}"
);
let msg = rejected_lenient("", "always [3:2] a");
assert!(
msg.contains("minimum") && msg.contains("maximum"),
"always [3:2]: {msg}"
);
let msg = rejected_lenient("", "s_always [5:1] a");
assert!(msg.contains("minimum"), "s_always [5:1]: {msg}");
let msg = rejected_lenient("", "nexttime [1:2] a");
assert!(msg.contains("16.12.10"), "nexttime [1:2]: {msg}");
let msg = rejected_lenient("", "s_nexttime [1:$] a");
assert!(msg.contains("16.12.10"), "s_nexttime [1:$]: {msg}");
let msg = rejected_lenient("", "always [3] a");
assert!(
msg.contains("16.12.11") && msg.contains("range"),
"always [3]: {msg}"
);
let msg = rejected_lenient("", "s_always [3] a");
assert!(msg.contains("16.12.11"), "s_always [3]: {msg}");
}
fn eventually_nodes(decls: &str, prop: &str) -> Vec<(u32, Option<u32>, bool)> {
with_property(decls, &ap(prop), false, |r, _| {
let (ir, _) = r.unwrap_or_else(|e| panic!("`{prop}` failed to build: {e}"));
ir.props
.iter()
.filter_map(|p| match p {
Prop::Eventually { lo, hi, strong, .. } => Some((*lo, *hi, *strong)),
_ => None,
})
.collect()
})
}
fn until_flags(decls: &str, prop: &str) -> Vec<bool> {
with_property(decls, &ap(prop), false, |r, _| {
let (ir, _) = r.unwrap_or_else(|e| panic!("`{prop}` failed to build: {e}"));
ir.props
.iter()
.filter_map(|p| match p {
Prop::Until { strong, .. } => Some(*strong),
_ => None,
})
.collect()
})
}
#[test]
fn t24_until_and_s_until() {
for prop in ["a until b", "a s_until b"] {
for (a, b, want) in [
("111", "000", Verdict::Holds),
("1", "0", Verdict::Holds),
("110", "000", Verdict::Fails),
("011", "000", Verdict::Fails),
("0", "0", Verdict::Fails),
("1101", "0001", Verdict::Fails),
("110", "001", Verdict::HoldsStrongly),
("100", "010", Verdict::HoldsStrongly),
("000", "100", Verdict::HoldsStrongly),
("0", "1", Verdict::HoldsStrongly),
("111", "111", Verdict::HoldsStrongly),
("100", "011", Verdict::HoldsStrongly),
] {
assert_eq!(
assert_verdict("", prop, &[("a", a), ("b", b)]),
want,
"{prop} on a={a} b={b}"
);
}
}
assert_eq!(until_flags("", "a until b"), [false]);
assert_eq!(until_flags("", "a s_until b"), [true]);
assert_eq!(
verdict("", "a until b", &[("a", "0110"), ("b", "0000")], 1, false),
Verdict::Fails
);
assert_eq!(
verdict("", "a until b", &[("a", "0110"), ("b", "0010")], 1, false),
Verdict::HoldsStrongly
);
}
#[test]
fn t24_until_with_and_s_until_with() {
for prop in ["a until_with b", "a s_until_with b"] {
for (a, b, want) in [
("111", "000", Verdict::Holds),
("1", "0", Verdict::Holds),
("110", "000", Verdict::Fails),
("110", "001", Verdict::Fails),
("100", "010", Verdict::Fails),
("0", "1", Verdict::Fails),
("111", "001", Verdict::HoldsStrongly),
("110", "010", Verdict::HoldsStrongly),
("1", "1", Verdict::HoldsStrongly),
("1100", "0100", Verdict::HoldsStrongly),
] {
assert_eq!(
assert_verdict("", prop, &[("a", a), ("b", b)]),
want,
"{prop} on a={a} b={b}"
);
}
}
assert_eq!(until_flags("", "a until_with b"), [false]);
assert_eq!(until_flags("", "a s_until_with b"), [true]);
}
#[test]
fn t24_until_equivalences() {
let ab = ["a", "b"];
equivalent("", "always a", "a until 1'b0", &["a"], 7);
equivalent("", "a s_until b", "(a until b) and s_eventually b", &ab, 5);
equivalent("", "a until_with b", "a until (a and b)", &ab, 5);
equivalent("", "a s_until_with b", "a s_until (a and b)", &ab, 5);
equivalent("", "a s_until_with b", "strong(a[*1:$] ##0 b)", &ab, 5);
equivalent(
"",
"(a |-> ##1 b) until_with c",
"(a |-> ##1 b) until ((a |-> ##1 b) and c)",
&["a", "b", "c"],
4,
);
equivalent(
"",
"(a ##1 b) s_until c",
"((a ##1 b) until c) and s_eventually c",
&["a", "b", "c"],
4,
);
let weak = all_verdicts("", "a until b", &ab, 4);
assert_ne!(weak, all_verdicts("", "a until_with b", &ab, 4));
let abc = ["a", "b", "c"];
let right = equivalent("", "a until b until c", "a until (b until c)", &abc, 4);
assert_ne!(right, all_verdicts("", "(a until b) until c", &abc, 4));
}
#[test]
fn t24_until_over_properties() {
let prop = "(a |-> ##1 b) until c";
let sig = |a, b, c| assert_verdict("", prop, &[("a", a), ("b", b), ("c", c)]);
assert_eq!(sig("0100", "0010", "0000"), Verdict::HoldsStrongly);
assert_eq!(sig("0100", "0000", "0000"), Verdict::Fails);
assert_eq!(sig("0001", "0000", "0100"), Verdict::HoldsStrongly);
assert_eq!(sig("0100", "0000", "0100"), Verdict::HoldsStrongly);
let seq = |a, b, c| assert_verdict("", "(a ##1 b) until c", &[("a", a), ("b", b), ("c", c)]);
assert_eq!(seq("11", "01", "00"), Verdict::Holds);
assert_eq!(seq("10", "01", "00"), Verdict::Fails);
}
#[test]
fn t24_s_eventually() {
for (a, want) in [
("000", Verdict::Holds),
("0", Verdict::Holds),
("010", Verdict::HoldsStrongly),
("1", Verdict::HoldsStrongly),
("0001", Verdict::HoldsStrongly),
] {
assert_eq!(
assert_verdict("", "s_eventually a", &[("a", a)]),
want,
"s_eventually a on a={a}"
);
}
assert_eq!(
verdict("", "s_eventually a", &[("a", "1001")], 1, false),
Verdict::HoldsStrongly
);
assert_eq!(
verdict("", "s_eventually a", &[("a", "1000")], 1, false),
Verdict::Holds
);
assert_eq!(eventually_nodes("", "s_eventually a"), [(0, None, true)]);
for a in ["0111", "1111", "0110", "0"] {
assert_eq!(
assert_verdict("", "s_eventually always a", &[("a", a)]),
Verdict::Holds,
"s_eventually always a on a={a}"
);
}
for a in ["0100", "1110", "0111"] {
assert_eq!(
assert_verdict("", "always s_eventually a", &[("a", a)]),
Verdict::Holds,
"always s_eventually a on a={a}"
);
}
}
#[test]
fn t24_ranged_eventually() {
for prop in ["eventually [2:5] a", "s_eventually [2:5] a"] {
for (a, want) in [
("0010000", Verdict::HoldsStrongly),
("0001000", Verdict::HoldsStrongly),
("0000010", Verdict::HoldsStrongly),
("001", Verdict::HoldsStrongly),
("1100001", Verdict::Fails),
("1100000", Verdict::Fails),
("0100001", Verdict::Fails),
("11000", Verdict::Holds),
("1100", Verdict::Holds),
("0", Verdict::Holds),
("10", Verdict::Holds),
] {
assert_eq!(
assert_verdict("", prop, &[("a", a)]),
want,
"{prop} on a={a}"
);
}
}
assert_eq!(
eventually_nodes("", "eventually [2:5] a"),
[(2, Some(5), false)]
);
assert_eq!(
eventually_nodes("", "s_eventually [2:5] a"),
[(2, Some(5), true)]
);
for (a, want) in [
("0010", Verdict::HoldsStrongly),
("0000001", Verdict::HoldsStrongly),
("1100000", Verdict::Holds),
("11", Verdict::Holds),
] {
assert_eq!(
assert_verdict("", "s_eventually [2:$] a", &[("a", a)]),
want,
"s_eventually [2:$] a on a={a}"
);
}
assert_eq!(
eventually_nodes("", "s_eventually [2:$] a"),
[(2, None, true)]
);
assert_eq!(
eventually_nodes("", "eventually [my_delay:my_delay + 3] a"),
[(2, Some(5), false)]
);
assert_eq!(
eventually_nodes(
"property p(int lo, int hi); s_eventually [lo:hi] a; endproperty",
"p(1, 4)"
),
[(1, Some(4), true)]
);
}
#[test]
fn t24_eventually_equivalences() {
let a = ["a"];
equivalent("", "s_eventually a", "strong(##[0:$] a)", &a, 7);
equivalent("", "s_eventually a", "s_eventually [0:$] a", &a, 7);
equivalent("", "s_eventually a", "not always not a", &a, 7);
equivalent("", "eventually [2:5] a", "weak(##[2:5] a)", &a, 8);
equivalent("", "s_eventually [2:5] a", "strong(##[2:5] a)", &a, 8);
equivalent("", "eventually [3:3] a", "nexttime [3] a", &a, 7);
equivalent("", "s_eventually [3:3] a", "s_nexttime [3] a", &a, 7);
equivalent(
"",
"eventually [2:5] a",
"(eventually [2:4] a) or nexttime [5] a",
&a,
8,
);
equivalent("", "s_eventually [2:5] a", "not always [2:5] not a", &a, 8);
equivalent("", "s_always [2:5] a", "not eventually [2:5] not a", &a, 8);
equivalent(
"",
"s_eventually [2:$] a",
"s_nexttime [2] s_eventually a",
&a,
7,
);
let two = ["a", "b"];
equivalent(
"",
"s_eventually [1:3] (a |-> ##1 b)",
"not always [1:3] not (a |-> ##1 b)",
&two,
5,
);
for prop in ["nexttime s_eventually a", "s_nexttime s_eventually a"] {
for (a, want) in [
("1000", Verdict::Holds),
("1010", Verdict::HoldsStrongly),
("0001", Verdict::HoldsStrongly),
("1", Verdict::Holds),
] {
assert_eq!(
assert_verdict("", prop, &[("a", a)]),
want,
"{prop} on a={a}"
);
}
}
equivalent("", "nexttime s_eventually a", "s_eventually [1:$] a", &a, 6);
assert_eq!(
eventually_nodes("", "eventually [1:3] a"),
[(1, Some(3), false)]
);
assert_eq!(
eventually_nodes("", "s_eventually [1:3] a"),
[(1, Some(3), true)]
);
}
#[test]
fn t24_variables_and_until_eventually() {
let decls = "property p; int v; (a, v = n) |-> (n == v) until b; endproperty";
let sig = |a, n, b| assert_verdict(decls, "p", &[("a", a), ("n", n), ("b", b)]);
assert_eq!(sig("10000", "55555", "00001"), Verdict::HoldsStrongly);
assert_eq!(sig("10000", "55655", "00001"), Verdict::Fails);
assert_eq!(sig("10000", "55555", "00000"), Verdict::Holds);
let decls = "property p; int v; (a, v = n) |=> s_eventually (n == v + 1); endproperty";
let sig = |a, n| assert_verdict(decls, "p", &[("a", a), ("n", n)]);
assert_eq!(sig("1000", "5557"), Verdict::Holds);
assert_eq!(sig("1000", "5566"), Verdict::HoldsStrongly);
let decls = "property p; int v; (a, v = n) |-> eventually [1:3] (n == v); endproperty";
assert_eq!(
assert_verdict(decls, "p", &[("a", "10000"), ("n", "56665")]),
Verdict::Fails
);
assert_eq!(
assert_verdict(decls, "p", &[("a", "10000"), ("n", "56655")]),
Verdict::HoldsStrongly
);
for body in [
"(((a, v = n) |-> b) until c) and (d |-> (n == v))",
"(c until ((a, v = n) |-> b)) and (d |-> (n == v))",
"(((a, v = n) |-> b) s_until_with c) and (d |-> (n == v))",
"(s_eventually ((a, v = n) |-> b)) and (d |-> (n == v))",
"(eventually [1:2] ((a, v = n) |-> b)) and (d |-> (n == v))",
] {
let msg = rejected_lenient(&format!("property p; int v; {body}; endproperty"), "p");
assert!(msg.contains("does not flow"), "{body}: {msg}");
}
}
#[test]
fn t24_illegal_forms_are_rejected() {
let msg = rejected_lenient("", "eventually [2:$] a");
assert!(
msg.contains("16.12.13") && msg.contains("bounded"),
"eventually [2:$]: {msg}"
);
let msg = rejected_lenient("", "eventually a");
assert!(
msg.contains("16.12.13") && msg.contains("range"),
"eventually a: {msg}"
);
let msg = rejected_lenient("", "eventually [3] a");
assert!(
msg.contains("16.12.13") && msg.contains("range"),
"eventually [3]: {msg}"
);
let msg = rejected_lenient("", "s_eventually [3] a");
assert!(msg.contains("16.12.13"), "s_eventually [3]: {msg}");
let msg = rejected_lenient("", "eventually [5:2] a");
assert!(
msg.contains("minimum") && msg.contains("maximum"),
"eventually [5:2]: {msg}"
);
let msg = rejected_lenient("", "s_eventually [4:1] a");
assert!(msg.contains("minimum"), "s_eventually [4:1]: {msg}");
}
fn aborts(decls: &str, prop: &str) -> Vec<(AbortMode, String)> {
with_property(decls, &ap(prop), false, |r, tree| {
let (ir, leaves) = r.unwrap_or_else(|e| panic!("`{prop}` failed to build: {e}"));
let texts = leaf_texts(&leaves, tree);
ir.props
.iter()
.filter_map(|p| match p {
Prop::Abort { cond, mode, .. } => Some((*mode, texts[cond.0 as usize].clone())),
_ => None,
})
.collect()
})
}
fn verdict_between(
prop: &str,
signals: &[(&str, &str)],
between: &[(&str, &str)],
start: u32,
) -> Verdict {
let table = rows(signals);
let len = table.values().next().expect("a signal").len() as u32;
with_property("", &ap(prop), false, |r, _| {
let (ir, leaves) = r.unwrap_or_else(|e| panic!("`{prop}` failed to build: {e}"));
let mut env = Sigs::new(&leaves, &table);
env.between = rows(between);
Evaluator::new(&ir, &env, false)
.verdict(len, start)
.unwrap_or_else(|e| panic!("`{prop}` failed to evaluate: {e}"))
})
}
#[test]
fn t25_each_keyword_has_its_own_mode() {
assert_eq!(
aborts("", "accept_on(a) b"),
[(AbortMode::Accept, "a".to_string())]
);
assert_eq!(
aborts("", "reject_on(a) b"),
[(AbortMode::Reject, "a".to_string())]
);
assert_eq!(
aborts("", "sync_accept_on(a) b"),
[(AbortMode::SyncAccept, "a".to_string())]
);
assert_eq!(
aborts("", "sync_reject_on(a) b"),
[(AbortMode::SyncReject, "a".to_string())]
);
assert_eq!(
aborts("", "sync_reject_on(a && !b || c) d"),
[(AbortMode::SyncReject, "a&&!b||c".to_string())]
);
assert_eq!(
aborts(
"",
"accept_on(a) sync_reject_on(b) reject_on(c) sync_accept_on(d) e"
),
[
(AbortMode::SyncAccept, "d".to_string()),
(AbortMode::Reject, "c".to_string()),
(AbortMode::SyncReject, "b".to_string()),
(AbortMode::Accept, "a".to_string()),
]
);
}
#[test]
fn t25_accept_on_makes_the_evaluation_true() {
for prop in [
"accept_on(a) strong(c ##1 d)",
"sync_accept_on(a) strong(c ##1 d)",
] {
for (a, c, d, want) in [
("000", "100", "010", Verdict::HoldsStrongly),
("000", "100", "000", Verdict::Fails),
("010", "100", "000", Verdict::HoldsStrongly),
("100", "000", "000", Verdict::HoldsStrongly),
("001", "100", "000", Verdict::Fails),
("0", "1", "0", Verdict::Holds),
("1", "1", "0", Verdict::HoldsStrongly),
] {
assert_eq!(
assert_verdict("", prop, &[("a", a), ("c", c), ("d", d)]),
want,
"{prop} on a={a} c={c} d={d}"
);
}
}
assert_eq!(
verdict(
"",
"sync_accept_on(a) strong(c ##1 d)",
&[("a", "1000"), ("c", "0100"), ("d", "0000")],
1,
false
),
Verdict::Fails
);
assert_eq!(
assert_verdict("", "accept_on(1'b1) a", &[("a", "000")]),
Verdict::HoldsStrongly
);
}
#[test]
fn t25_reject_on_makes_the_evaluation_false() {
for prop in [
"reject_on(a) strong(c ##1 d)",
"sync_reject_on(a) strong(c ##1 d)",
] {
for (a, c, d, want) in [
("000", "100", "010", Verdict::HoldsStrongly),
("000", "100", "000", Verdict::Fails),
("010", "100", "010", Verdict::Fails),
("010", "100", "000", Verdict::Fails),
("100", "100", "010", Verdict::Fails),
("001", "100", "010", Verdict::HoldsStrongly),
] {
assert_eq!(
assert_verdict("", prop, &[("a", a), ("c", c), ("d", d)]),
want,
"{prop} on a={a} c={c} d={d}"
);
}
}
assert_eq!(
assert_verdict("", "reject_on(1'b1) a", &[("a", "111")]),
Verdict::Fails
);
assert_eq!(
verdict(
"",
"sync_reject_on(b) a",
&[("a", "11"), ("b", "10")],
1,
false
),
Verdict::HoldsStrongly
);
}
#[test]
fn t25_abort_takes_precedence_at_the_end_of_the_evaluation() {
for (a, b, accept, reject) in [
("1", "1", Verdict::HoldsStrongly, Verdict::Fails),
("0", "1", Verdict::HoldsStrongly, Verdict::Fails),
("1", "0", Verdict::HoldsStrongly, Verdict::HoldsStrongly),
("0", "0", Verdict::Fails, Verdict::Fails),
] {
for (kw, want) in [
("accept_on", accept),
("sync_accept_on", accept),
("reject_on", reject),
("sync_reject_on", reject),
] {
assert_eq!(
assert_verdict("", &format!("{kw}(b) a"), &[("a", a), ("b", b)]),
want,
"{kw}(b) a on a={a} b={b}"
);
}
}
assert_eq!(
assert_verdict("", "sync_accept_on(b) a", &[("a", "00"), ("b", "01")]),
Verdict::Fails
);
assert_eq!(
assert_verdict("", "sync_reject_on(b) a", &[("a", "10"), ("b", "01")]),
Verdict::HoldsStrongly
);
}
#[test]
fn t25_reject_on_is_not_accept_on_not() {
let names = ["a", "b", "c"];
for p in ["strong(b ##1 c)", "b", "(b |-> ##1 c)", "(b until c)"] {
for (rej, acc) in [
("reject_on", "accept_on"),
("sync_reject_on", "sync_accept_on"),
] {
equivalent(
"",
&format!("{rej}(a) {p}"),
&format!("not ({acc}(a) (not {p}))"),
&names,
4,
);
}
}
let rej = all_verdicts("", "sync_reject_on(a) strong(b ##1 c)", &names, 4);
let acc = all_verdicts("", "sync_accept_on(a) strong(b ##1 c)", &names, 4);
let plain = all_verdicts("", "strong(b ##1 c)", &names, 4);
assert_ne!(rej, acc);
assert_ne!(rej, plain);
assert_ne!(acc, plain);
equivalent(
"",
"accept_on(a) strong(b ##1 c)",
"sync_accept_on(a) strong(b ##1 c)",
&names,
4,
);
equivalent(
"",
"reject_on(a) strong(b ##1 c)",
"sync_reject_on(a) strong(b ##1 c)",
&names,
4,
);
}
#[test]
fn t25_asynchronous_aborts_see_the_time_steps_between_ticks() {
let sigs = [("c", "10"), ("d", "00"), ("a", "00")];
let pulse = [("a", "10")];
let quiet = [("a", "00")];
for (prop, with_pulse, without) in [
(
"accept_on(a) strong(c ##1 d)",
Verdict::HoldsStrongly,
Verdict::Fails,
),
(
"sync_accept_on(a) strong(c ##1 d)",
Verdict::Fails,
Verdict::Fails,
),
] {
assert_eq!(
verdict_between(prop, &sigs, &pulse, 0),
with_pulse,
"{prop}"
);
assert_eq!(verdict_between(prop, &sigs, &quiet, 0), without, "{prop}");
}
let sigs = [("c", "10"), ("d", "01"), ("a", "00")];
assert_eq!(
verdict_between("reject_on(a) strong(c ##1 d)", &sigs, &pulse, 0),
Verdict::Fails
);
assert_eq!(
verdict_between("sync_reject_on(a) strong(c ##1 d)", &sigs, &pulse, 0),
Verdict::HoldsStrongly
);
assert_eq!(
verdict_between("reject_on(a) strong(c ##1 d)", &sigs, &quiet, 0),
Verdict::HoldsStrongly
);
let sigs = [("c", "100"), ("d", "010"), ("a", "000")];
assert_eq!(
verdict_between("reject_on(a) strong(c ##1 d)", &sigs, &[("a", "010")], 0),
Verdict::HoldsStrongly
);
let sigs = [("c", "010"), ("d", "000"), ("a", "000")];
assert_eq!(
verdict_between("accept_on(a) strong(c ##1 d)", &sigs, &[("a", "100")], 1),
Verdict::Fails
);
}
#[test]
fn t25_the_stop_example_three_ways() {
let async_ = "x ##1 y[*2] |-> reject_on(z) e[->2]";
let sync = "x ##1 y[*2] |-> sync_reject_on(z) e[->2]";
let throughout = "x ##1 y[*2] |-> !z throughout e[->2]";
equivalent(
"",
"sync_reject_on(z) e[->2]",
"!z throughout e[->2]",
&["z", "e"],
4,
);
equivalent(
"",
"reject_on(z) e[->2]",
"sync_reject_on(z) e[->2]",
&["z", "e"],
4,
);
let sig = |stop| {
assert_verdict(
"",
sync,
&[
("x", "1000000"),
("y", "0110000"),
("z", stop),
("e", "0001010"),
],
)
};
assert_eq!(sig("0000000"), Verdict::HoldsStrongly);
assert_eq!(sig("0000100"), Verdict::Fails);
assert_eq!(sig("0000010"), Verdict::Fails);
assert_eq!(sig("0000001"), Verdict::HoldsStrongly);
let sigs = [
("x", "1000000"),
("y", "0110000"),
("z", "0000000"),
("e", "0001010"),
];
let pulse = [("z", "0000100")];
assert_eq!(verdict_between(async_, &sigs, &pulse, 0), Verdict::Fails);
assert_eq!(
verdict_between(sync, &sigs, &pulse, 0),
Verdict::HoldsStrongly
);
assert_eq!(
verdict_between(throughout, &sigs, &pulse, 0),
Verdict::HoldsStrongly
);
}
#[test]
fn t25_goto_and_nonconsecutive_repetition_over_every_tail() {
for (lhs, rhs) in [
(
"strong(b[->2])",
"strong(!b[*0:$] ##1 b ##1 !b[*0:$] ##1 b)",
),
("b[->2]", "!b[*0:$] ##1 b ##1 !b[*0:$] ##1 b"),
(
"strong(b[=2])",
"strong(!b[*0:$] ##1 b ##1 !b[*0:$] ##1 b ##1 !b[*0:$])",
),
("b[=2]", "!b[*0:$] ##1 b ##1 !b[*0:$] ##1 b ##1 !b[*0:$]"),
("c ##1 b[->2] ##1 d", "c ##1 (!b[*0:$] ##1 b)[*2] ##1 d"),
] {
let names: &[&str] = if lhs.starts_with("c ") {
&["b", "c", "d"]
} else {
&["b"]
};
equivalent("", lhs, rhs, names, if names.len() == 1 { 6 } else { 4 });
}
let sig = |p: &str, b, strong| verdict("", p, &[("b", b)], 0, strong);
assert_eq!(sig("strong(b[->2])", "0101", false), Verdict::HoldsStrongly);
assert_eq!(sig("strong(b[->2])", "0100", false), Verdict::Holds);
assert_eq!(sig("b[->2] ##1 b", "01101", false), Verdict::Fails);
assert_eq!(sig("b[=2] ##1 b", "01101", false), Verdict::HoldsStrongly);
}
#[test]
fn goto_and_nonconsecutive_repetition_agree_with_their_definitions_on_x_samples() {
for (lhs, rhs) in [
("strong(b[->1])", "strong(!b[*0:$] ##1 b)"),
("b[->1]", "!b[*0:$] ##1 b"),
(
"strong(b[->2])",
"strong(!b[*0:$] ##1 b ##1 !b[*0:$] ##1 b)",
),
("b[->1:2]", "(!b[*0:$] ##1 b)[*1:2]"),
("strong(b[=1])", "strong(!b[*0:$] ##1 b ##1 !b[*0:$])"),
("b[=2]", "!b[*0:$] ##1 b ##1 !b[*0:$] ##1 b ##1 !b[*0:$]"),
] {
let l = all_verdicts_over("", lhs, &["b"], 5, &[0, 1, X]);
let r = all_verdicts_over("", rhs, &["b"], 5, &[0, 1, X]);
assert_eq!(l.len(), r.len());
if let Some(i) = (0..l.len()).find(|&i| l[i] != r[i]) {
panic!(
"`{lhs}` and `{rhs}` differ at case #{i}: {:?} vs {:?}",
l[i], r[i]
);
}
assert!(l.iter().any(|v| *v != l[0]), "`{lhs}` is vacuous");
}
let sig = |p: &str, b: &str| verdict("", p, &[("b", b)], 0, false);
assert_eq!(sig("strong(b[->1])", "0x1"), Verdict::Fails);
assert_eq!(sig("strong(b[->1])", "001"), Verdict::HoldsStrongly);
assert_eq!(
sig("strong(b[->1])", "0x1"),
sig("strong(!b[*0:$] ##1 b)", "0x1")
);
}
#[test]
fn t25_aborts_under_and_or_not() {
let and = "(sync_accept_on(a) strong(c ##1 d)) and (sync_reject_on(b) strong(c ##1 e))";
let sig = |a, b, d, e| {
assert_verdict(
"",
and,
&[("a", a), ("b", b), ("c", "100"), ("d", d), ("e", e)],
)
};
assert_eq!(sig("000", "000", "010", "010"), Verdict::HoldsStrongly);
assert_eq!(sig("000", "000", "000", "010"), Verdict::Fails);
assert_eq!(sig("010", "000", "000", "010"), Verdict::HoldsStrongly);
assert_eq!(sig("010", "000", "000", "000"), Verdict::Fails);
assert_eq!(sig("000", "010", "010", "010"), Verdict::Fails);
assert_eq!(sig("010", "010", "000", "010"), Verdict::Fails);
let or = "(sync_accept_on(a) strong(c ##1 d)) or (sync_reject_on(b) strong(c ##1 e))";
let sig = |a, b, d, e| {
assert_verdict(
"",
or,
&[("a", a), ("b", b), ("c", "100"), ("d", d), ("e", e)],
)
};
assert_eq!(sig("010", "000", "000", "000"), Verdict::HoldsStrongly);
assert_eq!(sig("000", "000", "000", "000"), Verdict::Fails);
assert_eq!(sig("000", "010", "010", "010"), Verdict::HoldsStrongly);
assert_eq!(sig("000", "010", "000", "010"), Verdict::Fails);
let not = "not (sync_accept_on(a) strong(c ##1 d))";
let sig = |a, d| assert_verdict("", not, &[("a", a), ("c", "100"), ("d", d)]);
assert_eq!(sig("010", "000"), Verdict::Fails);
assert_eq!(sig("010", "010"), Verdict::Fails);
assert_eq!(sig("000", "000"), Verdict::HoldsStrongly);
assert_eq!(sig("000", "010"), Verdict::Fails);
equivalent(
"",
"not (accept_on(a) strong(b ##1 c))",
"reject_on(a) not strong(b ##1 c)",
&["a", "b", "c"],
4,
);
equivalent(
"",
"accept_on(a) b and c",
"accept_on(a) (b and c)",
&["a", "b", "c"],
4,
);
let outer = all_verdicts("", "(accept_on(a) b) and c", &["a", "b", "c"], 4);
assert_ne!(
outer,
all_verdicts("", "accept_on(a) b and c", &["a", "b", "c"], 4)
);
}
#[test]
fn t25_nested_aborts_the_outermost_wins() {
let sig = |prop: &str, a, b| assert_verdict("", prop, &[("x", "111"), ("a", a), ("b", b)]);
for (outer, inner) in [
("accept_on", "reject_on"),
("sync_accept_on", "sync_reject_on"),
] {
let p = format!("{outer}(a) {inner}(b) strong(x ##5 x)");
assert_eq!(sig(&p, "001", "001"), Verdict::HoldsStrongly, "{p}");
assert_eq!(sig(&p, "001", "010"), Verdict::Fails, "{p}");
assert_eq!(sig(&p, "010", "001"), Verdict::HoldsStrongly, "{p}");
assert_eq!(sig(&p, "000", "000"), Verdict::Holds, "{p}");
let q = format!("{inner}(b) {outer}(a) strong(x ##5 x)");
assert_eq!(sig(&q, "001", "001"), Verdict::Fails, "{q}");
assert_eq!(sig(&q, "010", "001"), Verdict::HoldsStrongly, "{q}");
assert_eq!(sig(&q, "001", "010"), Verdict::Fails, "{q}");
assert_eq!(sig(&q, "000", "000"), Verdict::Holds, "{q}");
}
let sigs = [("x", "111"), ("a", "000"), ("b", "000")];
assert_eq!(
verdict_between(
"accept_on(a) sync_reject_on(b) strong(x ##5 x)",
&sigs,
&[("a", "001")],
0
),
Verdict::HoldsStrongly
);
}
#[test]
fn t25_abort_conditions_are_sampled_expressions() {
builds("", "accept_on($rose(a, @(posedge clk))) b");
builds(
"",
"reject_on($sampled(a) || $past(b, 1, , @(posedge clk))) c",
);
let decls = "property p; int v; (a, v = n) |=> sync_accept_on(v == 1) b; endproperty";
let msg = rejected_lenient(decls, "p");
assert!(
msg.contains("16.12.14") && msg.contains("local variable `v`"),
"{msg}"
);
let decls = "sequence s; a ##1 b; endsequence";
for method in ["triggered", "matched"] {
let msg = rejected_lenient(decls, &format!("accept_on(s.{method}) c"));
assert!(
msg.contains("16.12.14") && msg.contains(method),
"s.{method}: {msg}"
);
}
}
#[test]
fn t25_variables_and_abort() {
let decls = "property p; int v; (a, v = n) |=> sync_accept_on(b) (n == v); endproperty";
let sig = |a, n, b| assert_verdict(decls, "p", &[("a", a), ("n", n), ("b", b)]);
assert_eq!(sig("100", "551", "000"), Verdict::HoldsStrongly);
assert_eq!(sig("100", "561", "000"), Verdict::Fails);
assert_eq!(sig("100", "561", "010"), Verdict::HoldsStrongly);
let decls = "property p; int v; (a, v = n) |=> sync_reject_on(b) (n == v); endproperty";
let sig = |a, n, b| assert_verdict(decls, "p", &[("a", a), ("n", n), ("b", b)]);
assert_eq!(sig("100", "551", "000"), Verdict::HoldsStrongly);
assert_eq!(sig("100", "551", "010"), Verdict::Fails);
for body in [
"(sync_accept_on(b) ((a, v = n) |-> c)) and (d |-> (n == v))",
"(reject_on(b) ((a, v = n) |-> c)) and (d |-> (n == v))",
] {
let msg = rejected_lenient(&format!("property p; int v; {body}; endproperty"), "p");
assert!(msg.contains("does not flow"), "{body}: {msg}");
}
}
const PROP_ALWAYS: &str = "property prop_always(p); p and (1'b1 |=> prop_always(p)); endproperty";
const PROP_WEAK_UNTIL: &str =
"property prop_weak_until(p, q); q or (p and (1'b1 |=> prop_weak_until(p, q))); endproperty";
fn recursion_shape(decls: &str, prop: &str) -> (usize, usize) {
with_property(decls, &ap(prop), false, |r, _| {
let (ir, _) = r.unwrap_or_else(|e| panic!("`{prop}` failed to build: {e}"));
let calls = ir
.props
.iter()
.filter(|p| matches!(p, Prop::Call { .. }))
.count();
assert_eq!(ir.is_recursive(), calls > 0);
(ir.decls.len(), calls)
})
}
fn k_fold(depth: usize, level: impl Fn(usize) -> String) -> String {
(0..=depth).map(level).collect::<Vec<_>>().join("\n")
}
fn agree_with_unfolding(decls: &str, rec: &str, unfolded: &str, traces: &[Vec<(&str, &str)>]) {
let mut seen = Vec::new();
for t in traces {
for (start, strong) in [(0, false), (0, true), (1, false)] {
let r = verdict(decls, rec, t, start, strong);
let u = verdict(decls, unfolded, t, start, strong);
assert_eq!(
r, u,
"`{rec}` vs `{unfolded}` on {t:?} (start {start}, strong {strong})"
);
if !seen.contains(&r) {
seen.push(r);
}
}
}
assert!(
seen.len() > 1,
"every trace gives {seen:?}: the comparison is vacuous"
);
}
#[test]
fn t26_prop_always_is_always() {
weakly_equivalent(PROP_ALWAYS, "prop_always(a)", "always a", &["a"], 7);
let a = [("a", "111")];
assert_eq!(
assert_verdict(PROP_ALWAYS, "prop_always(a)", &a),
Verdict::HoldsStrongly
);
assert_eq!(assert_verdict(PROP_ALWAYS, "always a", &a), Verdict::Holds);
assert_eq!(
assert_verdict(PROP_ALWAYS, "prop_always(a)", &[("a", "101")]),
Verdict::Fails
);
assert_eq!(recursion_shape(PROP_ALWAYS, "prop_always(a)"), (1, 1));
weakly_equivalent(
PROP_ALWAYS,
"prop_always(b |-> c)",
"always (b |-> c)",
&["b", "c"],
5,
);
}
#[test]
fn t26_prop_weak_until_is_until() {
weakly_equivalent(
PROP_WEAK_UNTIL,
"prop_weak_until(a, b)",
"a until b",
&["a", "b"],
5,
);
assert_eq!(
recursion_shape(PROP_WEAK_UNTIL, "prop_weak_until(a, b)"),
(1, 1)
);
}
#[test]
fn t26_properties_built_on_recursive_properties() {
let decls = format!(
"{PROP_ALWAYS} {PROP_WEAK_UNTIL}
property p1(s, p); s |=> prop_always(p); endproperty
property p2(s, p, q); s |=> prop_weak_until(p, q); endproperty"
);
weakly_equivalent(&decls, "p1(a, b)", "a |=> always b", &["a", "b"], 5);
weakly_equivalent(
&decls,
"p2(a, b, c)",
"a |=> (b until c)",
&["a", "b", "c"],
4,
);
assert_eq!(recursion_shape(&decls, "p1(a, b)"), (1, 1));
}
#[test]
fn t26_mutually_recursive_properties() {
let decls = "logic s1, s2, ph1, ph2;
property check_phase1; s1 |-> (ph1 and (1'b1 |=> check_phase2)); endproperty
property check_phase2; s2 |-> (ph2 and (1'b1 |=> check_phase1)); endproperty";
let run = |s1, s2, ph1, ph2| {
assert_verdict(
decls,
"check_phase1",
&[("s1", s1), ("s2", s2), ("ph1", ph1), ("ph2", ph2)],
)
};
assert_eq!(run("1010", "0101", "1010", "0101"), Verdict::HoldsStrongly);
assert_eq!(run("1010", "0101", "1000", "0101"), Verdict::Fails);
assert_eq!(run("1010", "0101", "1010", "0001"), Verdict::Fails);
assert_eq!(run("1010", "0001", "1000", "0000"), Verdict::HoldsStrongly);
assert_eq!(run("0000", "0000", "0000", "0000"), Verdict::HoldsStrongly);
assert_eq!(recursion_shape(decls, "check_phase1"), (1, 1));
}
#[test]
fn t26_aborts_in_recursive_properties() {
let decls = "property p3(p, bit b, abort); (p and (1'b1 |=> p4(p, b, abort))); endproperty
property p4(p, bit b, abort); accept_on(b) reject_on(abort) p3(p, b, abort); endproperty";
let run = |a, c, d| assert_verdict(decls, "p3(a, c, d)", &[("a", a), ("c", c), ("d", d)]);
assert_eq!(run("1111", "0000", "0000"), Verdict::HoldsStrongly);
assert_eq!(run("1101", "0000", "0000"), Verdict::Fails);
assert_eq!(run("1101", "0100", "0000"), Verdict::HoldsStrongly);
assert_eq!(run("1101", "0010", "0000"), Verdict::HoldsStrongly);
assert_eq!(run("1101", "0001", "0000"), Verdict::Fails);
assert_eq!(run("1111", "0000", "0100"), Verdict::Fails);
assert_eq!(run("1111", "0000", "1000"), Verdict::HoldsStrongly);
assert_eq!(run("1111", "0000", "0010"), Verdict::Holds);
assert_eq!(run("1111", "0000", "0001"), Verdict::Holds);
assert_eq!(recursion_shape(decls, "p3(a, c, d)"), (1, 1));
let all = format!(
"{decls}\n{}",
k_fold(8, |k| match k {
0 => "property v0(p, bit b, abort); 1'b1; endproperty".to_string(),
_ => format!(
"property v{k}(p, bit b, abort); p and (1'b1 |=> accept_on(b) \
reject_on(abort) v{}(p, b, abort)); endproperty",
k - 1
),
})
);
let traces: Vec<Vec<(&str, &str)>> = [
("1111", "0000", "0000"),
("1101", "0000", "0000"),
("1101", "0100", "0000"),
("1101", "0010", "0000"),
("1101", "0001", "0000"),
("1111", "0000", "0100"),
("1111", "0000", "0001"),
("1111", "0000", "1000"),
("1011", "0001", "0010"),
]
.iter()
.map(|&(a, c, d)| vec![("a", a), ("c", c), ("d", d)])
.collect();
agree_with_unfolding(&all, "p3(a, c, d)", "v8(a, c, d)", &traces);
}
#[test]
fn t26_fibonacci_with_local_variable_formals() {
let decls = "property fibonacci1 (local input int a, b, n, int fib_sig);
(n > 0) |-> ((fib_sig == a) and (1'b1 |=> fibonacci1(b, a + b, n - 1, fib_sig)));
endproperty";
let run = |count, sig| {
assert_verdict(
decls,
&format!("fibonacci1(0, 1, {count}, n)"),
&[("n", sig)],
)
};
assert_eq!(run(5, "01123"), Verdict::HoldsStrongly);
assert_eq!(run(5, "0112358"), Verdict::HoldsStrongly);
assert_eq!(run(5, "01124"), Verdict::Fails);
assert_eq!(run(5, "11123"), Verdict::Fails);
assert_eq!(run(3, "0119"), Verdict::HoldsStrongly);
assert_eq!(run(3, "0129"), Verdict::Fails);
with_property(decls, &ap("fibonacci1(0, 1, 5, n)"), false, |r, _| {
let (ir, _) = r.unwrap();
let args: Vec<usize> = ir
.props
.iter()
.filter_map(|p| match p {
Prop::Call { args, .. } => Some(args.len()),
_ => None,
})
.collect();
assert_eq!(args, vec![3]);
assert_eq!(ir.decls.len(), 1);
assert_eq!(ir.decls[0].name, "fibonacci1");
});
}
#[test]
fn t26_declared_local_is_reinitialised_by_each_instance() {
let decls = "property q(local input int k);
int w = k + 1;
(n == w) and (1'b1 |=> q(k + 1));
endproperty";
let run = |sig| assert_verdict(decls, "q(0)", &[("n", sig)]);
assert_eq!(run("1234"), Verdict::HoldsStrongly);
assert_eq!(run("1224"), Verdict::Fails);
assert_eq!(run("1233"), Verdict::Fails);
let all = format!(
"{decls}\n{}",
k_fold(8, |k| match k {
0 => "property u0(local input int k); 1'b1; endproperty".to_string(),
_ => format!(
"property u{k}(local input int k); int w = k + 1; \
(n == w) and (1'b1 |=> u{}(k + 1)); endproperty",
k - 1
),
})
);
let traces: Vec<Vec<(&str, &str)>> = ["1234", "1224", "1233", "0234", "12", "1", "1234567"]
.iter()
.map(|n| vec![("n", *n)])
.collect();
agree_with_unfolding(&all, "q(0)", "u8(0)", &traces);
with_property(decls, &ap("q(0)"), false, |r, _| {
let (ir, _) = r.unwrap();
let calls: Vec<(usize, usize)> = ir
.props
.iter()
.filter_map(|p| match p {
Prop::Call { args, inits, .. } => Some((args.len(), inits.len())),
_ => None,
})
.collect();
assert_eq!(calls, vec![(1, 1)]);
});
let decls = "property q2(local input int k);
int w = k + 1, v = w + w;
(n == v) and (1'b1 |=> q2(k + 1));
endproperty";
let run = |sig| assert_verdict(decls, "q2(0)", &[("n", sig)]);
assert_eq!(run("2468"), Verdict::HoldsStrongly);
assert_eq!(run("2478"), Verdict::Fails);
assert_eq!(run("2268"), Verdict::Fails);
let all = format!(
"{decls}\n{}",
k_fold(8, |k| match k {
0 => "property z0(local input int k); 1'b1; endproperty".to_string(),
_ => format!(
"property z{k}(local input int k); int w = k + 1, v = w + w; \
(n == v) and (1'b1 |=> z{}(k + 1)); endproperty",
k - 1
),
})
);
let traces: Vec<Vec<(&str, &str)>> = ["2468", "2478", "2268", "4", "2", "24681"]
.iter()
.map(|n| vec![("n", *n)])
.collect();
agree_with_unfolding(&all, "q2(0)", "z8(0)", &traces);
}
#[test]
fn t26_check_write_data_beat() {
let decls = "logic wreq, wack, dv, rt, last; int model, wtag, dtag, rtag, data;
property check_write;
int expected_data; int tag;
(wreq && wack, expected_data = model, tag = wtag)
|=>
check_write_data_beat(expected_data, tag, 0);
endproperty
property check_write_data_beat(local input int expected_data, tag, i);
((dv && (dtag == tag)) || (rt && (rtag == tag)))[->1]
|->
(
((dv && (dtag == tag)) |-> (data == expected_data + i))
and
(
if (rt && (rtag == tag))
(1'b1 |=> check_write_data_beat(expected_data, tag, 0))
else if (!last)
(1'b1 |=> check_write_data_beat(expected_data, tag, i + 1))
else
(##1 (rt && (rtag == tag)) |=> check_write_data_beat(expected_data, tag, 0))
)
);
endproperty";
let run = |dv, dtag, data, rt, rtag, last| {
assert_verdict(
decls,
"check_write",
&[
("wreq", "1000000"),
("wack", "1000000"),
("model", "5000000"),
("wtag", "2000000"),
("dv", dv),
("dtag", dtag),
("data", data),
("rt", rt),
("rtag", rtag),
("last", last),
],
)
};
assert_eq!(
run(
"0110000", "0220000", "0560000", "0000000", "0000000", "0010000"
),
Verdict::HoldsStrongly
);
assert_eq!(
run(
"0110000", "0220000", "0570000", "0000000", "0000000", "0010000"
),
Verdict::Fails
);
assert_eq!(
run(
"0110000", "0320000", "0950000", "0000000", "0000000", "0000000"
),
Verdict::HoldsStrongly
);
assert_eq!(
run(
"0100100", "0200000", "0500500", "0010000", "0020000", "0000000"
),
Verdict::HoldsStrongly
);
assert_eq!(
run(
"0100100", "0200200", "0500700", "0010000", "0020000", "0000000"
),
Verdict::Fails
);
assert_eq!(
run(
"0100100", "0200200", "0500500", "0010000", "0020000", "0100000"
),
Verdict::HoldsStrongly
);
assert_eq!(
run(
"0100100", "0200200", "0500600", "0010000", "0020000", "0100000"
),
Verdict::Fails
);
assert_eq!(recursion_shape(decls, "check_write"), (1, 3));
}
#[test]
fn t26_recursion_through_permuted_formals() {
let decls = "property alt(x, y); x and (1'b1 |=> alt(y, x)); endproperty";
let run = |a, b| assert_verdict(decls, "alt(a, b)", &[("a", a), ("b", b)]);
assert_eq!(run("1010", "0101"), Verdict::HoldsStrongly);
assert_eq!(run("1001", "0101"), Verdict::Fails);
assert_eq!(run("1010", "0001"), Verdict::Fails);
assert_eq!(recursion_shape(decls, "alt(a, b)"), (1, 1));
with_property(decls, &ap("alt(a, b)"), false, |r, _| {
let (ir, _) = r.unwrap();
assert_eq!(
ir.props
.iter()
.filter(|p| matches!(p, Prop::And(..)))
.count(),
2
);
});
}
#[test]
fn t26_recursive_instance_with_a_formal_free_actual() {
let decls = "property fx(x, y); (x and y) and (1'b1 |=> fx(c, d)); endproperty";
let run =
|a, b, c, d| assert_verdict(decls, "fx(a, b)", &[("a", a), ("b", b), ("c", c), ("d", d)]);
assert_eq!(run("1000", "1000", "0111", "0111"), Verdict::HoldsStrongly);
assert_eq!(run("1000", "1000", "0101", "0111"), Verdict::Fails);
assert_eq!(run("1000", "1000", "0111", "0110"), Verdict::Fails);
assert_eq!(run("0111", "1000", "0111", "0111"), Verdict::Fails);
assert_eq!(run("1111", "1000", "0111", "0111"), Verdict::HoldsStrongly);
assert_eq!(recursion_shape(decls, "fx(a, b)"), (1, 1));
}
#[test]
fn t26_a_binding_chain_longer_than_slang_unfolds_is_a_named_error() {
let msg = rejected(
"property sw(x, y); x and (1'b1 |=> sw(y, c)); endproperty",
"sw(a, b)",
);
assert!(
msg.contains("16.12.17") && msg.contains("no body"),
"sw: {msg}"
);
let msg = rejected(
"property alt2(x, y, local input int k); (x and (n == k)) and (1'b1 |=> alt2(y, x, k + 1)); endproperty",
"alt2(a, b, 0)",
);
assert!(
msg.contains("16.12.17") && msg.contains("local"),
"alt2: {msg}"
);
}
#[test]
fn t26_restriction_1_negation_of_a_recursive_property() {
let wrapper = "property wrapper(p); prop_always(p); endproperty";
for (extra, prop, why) in [
(
"property illegal_recursion_1(p); not prop_always(not p); endproperty",
"illegal_recursion_1(a)",
"the LRM's first example",
),
(
"property illegal_recursion_2(p); p and (1'b1 |=> not illegal_recursion_2(p)); endproperty",
"illegal_recursion_2(a)",
"the LRM's second example",
),
("", "not prop_always(a)", "the negation asserted"),
(
wrapper,
"not wrapper(a)",
"a property that depends on a recursive one (F.7)",
),
(
"",
"prop_always(a) implies b",
"implies negates its left operand",
),
("", "prop_always(a) iff b", "iff is two implies"),
("", "b iff prop_always(a)", "iff is two implies"),
(
"",
"a #-# prop_always(b)",
"a followed-by is a negated implication",
),
("", "s_nexttime prop_always(a)", "strong operator"),
("", "s_always [1:2] prop_always(a)", "strong operator"),
("", "s_eventually prop_always(a)", "strong operator"),
("", "prop_always(a) s_until b", "strong operator"),
("", "b s_until_with prop_always(a)", "strong operator"),
(
"property upto(p, q); q s_until prop_always(p); endproperty",
"upto(a, b)",
"a strong operator inside a wrapper",
),
] {
let msg = rejected_lenient(&format!("{PROP_ALWAYS} {extra}"), prop);
assert!(
msg.contains("16.12.17") && msg.contains("restriction 1"),
"{prop} ({why}): {msg}"
);
}
let ok = format!("{PROP_ALWAYS} {wrapper} property plain(p); p |=> b; endproperty");
for prop in [
"nexttime prop_always(a)",
"always prop_always(a)",
"eventually [1:2] prop_always(a)",
"prop_always(a) until b",
"prop_always(a) or prop_always(b)",
"prop_always(a) and prop_always(b)",
"a implies prop_always(b)",
"a |-> prop_always(b)",
"reject_on(b) prop_always(a)",
"not plain(a)",
"not (a ##1 b)",
"wrapper(a)",
] {
builds(&ok, prop);
}
}
#[test]
fn t26_restriction_2_disable_iff_in_a_recursive_property() {
let decls = format!(
"{PROP_ALWAYS}
property illegal_recursion_3(p); disable iff (b) p and (1'b1 |=> illegal_recursion_3(p)); endproperty
property legal_3(p); disable iff (b) prop_always(p); endproperty
property pa; disable iff (b) pb; endproperty
property pb; a |=> pa; endproperty"
);
for prop in ["illegal_recursion_3(a)", "pa"] {
let msg = rejected_lenient(&decls, prop);
assert!(
msg.contains("16.12.17") && msg.contains("restriction 2"),
"{prop}: {msg}"
);
}
with_property(&decls, &ap("legal_3(a)"), true, |r, _| {
let (ir, _) = r.expect("legal_3 builds");
assert!(ir.disable.is_some(), "legal_3 lost its `disable iff`");
});
}
#[test]
fn t26_restriction_3_positive_advance_in_time() {
for (decl, prop) in [
(
"property illegal_recursion_4(p); p and (1'b1 |-> illegal_recursion_4(p)); endproperty",
"illegal_recursion_4(a)",
),
(
"property ma; a |-> mb; endproperty property mb; b |-> ma; endproperty",
"ma",
),
(
"property same(p); p and nexttime [0] same(p); endproperty",
"same(a)",
),
(
"property wait0(p); p and (c ##[0:2] d |-> wait0(p)); endproperty",
"wait0(a)",
),
(
"property ev(p); p and eventually [0:2] ev(p); endproperty",
"ev(a)",
),
(
"property alw0(p); p and always [0:2] alw0(p); endproperty",
"alw0(a)",
),
(
"property orbranch(p); (1'b1 |-> orbranch(p)) or (1'b1 |=> orbranch(p)); endproperty",
"orbranch(a)",
),
] {
let msg = rejected_lenient(decl, prop);
assert!(
msg.contains("16.12.17") && msg.contains("restriction 3"),
"{prop}: {msg}"
);
}
let decls = "
property mc; a |=> md; endproperty
property md; b |-> mc; endproperty
property nx(p); p and nexttime ev2(p); endproperty
property ev2(p); p and (1'b1 |=> nx(p)); endproperty
property alw(p); p and always [1:2] alw(p); endproperty";
builds(decls, "mc");
builds(decls, "md");
builds(decls, "nx(a)");
builds(decls, "alw(a)");
let wait = "property wait1(p); p and (c ##[1:2] d |-> wait1(p)); endproperty";
let run = |a| {
verdict_in(
wait,
"wait1(a)",
&[("a", a), ("c", "1000"), ("d", "0110")],
0,
false,
true,
)
};
assert_eq!(run("1110"), Verdict::HoldsStrongly);
assert_eq!(run("1101"), Verdict::Fails);
assert_eq!(run("1011"), Verdict::Fails);
assert_eq!(run("0111"), Verdict::Fails);
}
#[test]
fn t26_restriction_4_actuals_of_a_recursive_instance() {
for (decl, prop) in [
(
"property fibonacci2 (int a, b, n, fib_sig);
(n > 0) |-> ((fib_sig == a) and (1'b1 |=> fibonacci2(b, a + b, n - 1, fib_sig)));
endproperty",
"fibonacci2(0, 1, 5, n)",
),
(
"property grow(p, int k); p and (1'b1 |=> grow(p, k + 1)); endproperty",
"grow(a, 1)",
),
] {
let msg = rejected_lenient(decl, prop);
assert!(msg.contains("slang could not bind"), "{prop}: {msg}");
}
let msg = rejected_lenient(
"property mixed2(local input int k, int m); (n == k + m) and (1'b1 |=> mixed2(k, k)); endproperty",
"mixed2(0, 2)",
);
assert!(
msg.contains("16.12.17") && msg.contains("restriction 4") && msg.contains("`m`"),
"mixed2: {msg}"
);
let decls = "property fine(p, int k); p and (1'b1 |=> fine(p, 3)); endproperty
property mixed(local input int k, int m); (n == k + m) and (1'b1 |=> mixed(k + 1, m)); endproperty";
builds(decls, "fine(a, 1)");
builds(decls, "mixed(0, 2)");
}
#[test]
fn t26_several_instances_of_a_recursive_property() {
weakly_equivalent(
PROP_ALWAYS,
"prop_always(a) and prop_always(b)",
"always (a && b)",
&["a", "b"],
4,
);
weakly_equivalent(
PROP_ALWAYS,
"prop_always(a) or prop_always(b)",
"(always a) or (always b)",
&["a", "b"],
4,
);
assert_eq!(
recursion_shape(PROP_ALWAYS, "prop_always(a) and prop_always(b)"),
(2, 2)
);
}
mod disable;
mod multiclock;