use sv_sim_sim::sva::property::Disposition;
use super::*;
use crate::sva::disable::{DisableSource, resolve_disable};
use sv_lang::SyntaxTree;
fn leaf_text(leaves: &LeafTable<'_>, leaf: LeafId, tree: &SyntaxTree) -> String {
let e = leaves.get(leaf);
if let Some(sym) = e.referenced_symbol() {
return sym.name().to_string();
}
e.syntax(tree)
.expect("a leaf has a syntax node")
.text()
.trim()
.to_string()
}
fn disable_of(decls: &str, prop: &str) -> Option<String> {
with_property(decls, &ap(prop), false, |r, tree| {
let (ir, leaves) = r.unwrap_or_else(|e| panic!("`{prop}` failed to build: {e}"));
ir.disable.map(|d| leaf_text(&leaves, d, tree))
})
}
#[test]
fn explicit_clause_is_recorded_and_the_body_is_built_alone() {
assert_eq!(
disable_of("", "disable iff (reset) a |=> b").as_deref(),
Some("reset")
);
assert_eq!(disable_of("", "a |=> b"), None);
for body in [
"a |=> b",
"a ##1 b",
"not (a ##1 b)",
"always (a |-> ##1 b)",
] {
let with = all_verdicts(
"",
&format!("disable iff (reset) {body}"),
&["a", "b", "reset"],
3,
);
let without = all_verdicts("", body, &["a", "b", "reset"], 3);
assert_eq!(with, without, "`disable iff (reset) {body}`");
}
}
#[test]
fn clause_in_an_instantiated_property_is_the_specs_clause() {
let decls = "property p; disable iff (reset) a |=> b; endproperty
property q(rst); disable iff (rst) a |=> b; endproperty
property w(rst); q(rst); endproperty";
assert_eq!(disable_of(decls, "p").as_deref(), Some("reset"));
assert_eq!(disable_of(decls, "q(reset)").as_deref(), Some("reset"));
assert_eq!(disable_of(decls, "q(x)").as_deref(), Some("x"));
assert_eq!(disable_of(decls, "w(y)").as_deref(), Some("y"));
}
#[test]
fn nested_disable_iff_is_rejected() {
let decls = "logic y; property inner; disable iff (y) a |=> b; endproperty";
for (prop, what) in [
("disable iff (b) inner", "under a clause"),
("not inner", "operand of not"),
("inner and a", "operand of and"),
("a |-> inner", "consequent"),
("always inner", "operand of always"),
("if (a) inner", "branch of if"),
("reject_on (b) inner", "body of an abort"),
(
"disable iff (a) disable iff (b) a |=> b",
"clause under a clause",
),
("(disable iff (b) a) or b", "clause in an operand"),
] {
let msg = slang_errors(decls, prop);
assert!(
msg.contains("error"),
"{what} (`{prop}`) was not refused by slang: {msg}"
);
}
builds(
"property inner; disable iff (y) a |=> b; endproperty",
"inner",
);
}
#[test]
fn condition_may_not_read_a_local_variable_or_matched() {
let msg = slang_errors(
"property pl; int v; disable iff (v == 1) (a, v = 1) ##1 b; endproperty",
"pl",
);
assert!(
msg.contains("local variables cannot be used in disable iff conditions"),
"{msg}"
);
let msg = slang_errors(
"sequence s; a ##1 b; endsequence",
"disable iff (s.matched) a |=> b",
);
assert!(
msg.contains("'matched' method cannot be used in disable iff conditions"),
"{msg}"
);
}
fn attempt(
prop: &str,
signals: &[(&str, &str)],
between_reset: Option<&str>,
start: u32,
strong: bool,
) -> Disposition {
let table = rows(signals);
let len = table.values().next().expect("a signal").len() as u32;
with_property(
"",
&ap(&format!("disable iff (reset) {prop}")),
false,
|r, _| {
let (ir, leaves) = r.unwrap_or_else(|e| panic!("`{prop}` failed to build: {e}"));
let mut env = Sigs::new(&leaves, &table);
if let Some(b) = between_reset {
env.between = rows(&[("reset", b)]);
}
Evaluator::new(&ir, &env, strong)
.attempt(len, start)
.unwrap_or_else(|e| panic!("`{prop}` failed to evaluate: {e}"))
},
)
}
#[test]
fn condition_true_during_the_attempt_disables_it() {
let p = "strong(a ##1 b)";
let sigs = |reset| [("a", "10"), ("b", "00"), ("reset", reset)];
assert_eq!(attempt(p, &sigs("00"), None, 0, false), Disposition::Fails);
assert_eq!(
attempt(p, &sigs("01"), None, 0, false),
Disposition::Disabled
);
assert_eq!(
attempt(p, &sigs("10"), None, 0, false),
Disposition::Disabled
);
assert_eq!(attempt(p, &sigs("10"), None, 1, false), Disposition::Fails);
let p = "a |-> strong(1 ##1 b)";
assert_eq!(
attempt(p, &sigs("01"), None, 0, false),
Disposition::Disabled
);
}
#[test]
fn a_prefix_that_is_vacuous_on_the_tail_passes_under_annex_f() {
let sigs = [("a", "10"), ("b", "00"), ("reset", "01")];
assert_eq!(
attempt("a |=> b", &sigs, None, 0, false),
Disposition::Passes
);
assert_eq!(
attempt("a |-> strong(1 ##1 b)", &sigs, None, 0, false),
Disposition::Disabled
);
}
#[test]
fn condition_pulse_between_ticks_disables_it() {
let p = "strong(a ##1 b)";
let sigs = [("a", "10"), ("b", "00"), ("reset", "00")];
assert_eq!(
attempt(p, &sigs, Some("10"), 0, false),
Disposition::Disabled
);
assert_eq!(attempt(p, &sigs, Some("01"), 0, false), Disposition::Fails);
}
#[test]
fn decided_attempts_are_unaffected_by_a_later_disabling() {
assert_eq!(
attempt(
"a |-> b",
&[("a", "11"), ("b", "11"), ("reset", "01")],
None,
0,
false
),
Disposition::Passes
);
assert_eq!(
attempt(
"strong(a ##1 b)",
&[("a", "100"), ("b", "000"), ("reset", "001")],
None,
0,
false
),
Disposition::Fails
);
assert_eq!(
attempt(
"strong(a ##1 b)",
&[("a", "10"), ("b", "00"), ("reset", "01")],
None,
0,
false
),
Disposition::Disabled
);
assert_eq!(
attempt(
"strong(a ##3 b)",
&[("a", "10"), ("b", "00"), ("reset", "00")],
None,
0,
false
),
Disposition::Pending
);
}
#[test]
fn attempt_is_the_verdict_on_the_letters_before_the_first_disabling() {
for prop in [
"a |=> b",
"a ##1 b",
"not (a ##1 b)",
"a |-> s_eventually b",
"always (a |-> ##1 b)",
"(a ##[1:2] b) or c",
] {
let spec = format!("disable iff (reset) {prop}");
with_property("", &ap(&spec), false, |r, _| {
let (ir, leaves) = r.unwrap_or_else(|e| panic!("`{prop}` failed to build: {e}"));
for len in 1..=4usize {
for table in all_traces(&["a", "b", "c", "reset"], len) {
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 {
let got = ev.attempt(len as u32, start).unwrap();
let first = (start as usize..len).find(|&t| table["reset"][t] != 0);
let want = match first {
None => ev.verdict(len as u32, start).unwrap().into(),
Some(t) if t == start as usize => Disposition::Disabled,
Some(t) => {
let cut: Rows = table
.iter()
.map(|(n, v)| (n.clone(), v[..t].to_vec()))
.collect();
let env = Sigs::new(&leaves, &cut);
match Evaluator::new(&ir, &env, strong)
.verdict(t as u32, start)
.unwrap()
{
Verdict::Holds => Disposition::Disabled,
decided => decided.into(),
}
}
};
assert_eq!(
got, want,
"`{spec}` len {len} start {start} strong {strong}: {table:?}"
);
}
}
}
}
});
}
}
fn resolved(src: &str) -> BTreeMap<String, String> {
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();
assert!(
!diags.has_errors(),
"fixture has compile errors:\n{}\n{src}",
diags.rendered()
);
let analysis = design.analyze(AnalysisFlags::NONE, 1).unwrap();
let tree = design.syntax_trees().into_iter().next().unwrap();
let mut bodies = vec![
design
.top_instances()
.next()
.unwrap()
.instance_body()
.unwrap(),
];
let mut out = BTreeMap::new();
while let Some(body) = bodies.pop() {
bodies.extend(
body.members()
.filter(|m| m.kind() == sv_lang::kinds::SymbolKind::Instance)
.filter_map(|m| m.instance_body()),
);
for proc in analysis.procedures(body) {
for assertion in analysis.assertions(proc.symbol()) {
let label = proc
.symbol()
.syntax(&tree)
.and_then(|n| n.first_token())
.map(|t| t.text().to_string())
.expect("an assertion statement starts with its label");
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)
};
let (ir, leaves) = build_property(root, root_clock, (&tree).into(), &body)
.unwrap_or_else(|e| panic!("{label}: {e}"));
let how = match resolve_disable(ir.disable, &body, &tree)
.unwrap_or_else(|e| panic!("{label}: {e}"))
{
DisableSource::Explicit(l) => {
format!("explicit {}", leaf_text(&leaves, l, &tree))
}
DisableSource::Default { scope, decl } => format!(
"default {} (declared in {})",
decl.expr().syntax().text().trim(),
scope.name()
),
DisableSource::None => "none".to_string(),
};
assert!(
out.insert(label.clone(), how).is_none(),
"two assertions labelled `{label}`"
);
}
}
}
out
}
const EXAMPLES_WITH_DEFAULT: &str = "
module examples_with_default (input logic a, b, clk, rst, rst1);
default disable iff rst;
property p1;
disable iff (rst1) a |=> b;
endproperty
// Disable condition is rst1 - explicitly specified within a1
a1 : assert property (@(posedge clk) disable iff (rst1) a |=> b);
// Disable condition is rst1 - explicitly specified within p1
a2 : assert property (@(posedge clk) p1);
// Disable condition is rst - no explicit specification, inferred from
// default disable iff declaration
a3 : assert property (@(posedge clk) a |=> b);
// Disable condition is 1'b0. This is the only way to
// cancel the effect of default disable.
a4 : assert property (@(posedge clk) disable iff (1'b0) a |=> b);
endmodule
";
const EXAMPLES_WITHOUT_DEFAULT: &str = "
module examples_without_default (input logic a, b, clk, rst);
property p2;
disable iff (rst) a |=> b;
endproperty
// Disable condition is rst - explicitly specified within a5
a5 : assert property (@(posedge clk) disable iff (rst) a |=> b);
// Disable condition is rst - explicitly specified within p2
a6 : assert property (@ (posedge clk) p2);
// No disable condition
a7 : assert property (@ (posedge clk) a |=> b);
endmodule
";
#[test]
fn examples_with_default() {
let r = resolved(EXAMPLES_WITH_DEFAULT);
assert_eq!(r.len(), 4, "{r:?}");
assert_eq!(r["a1"], "explicit rst1");
assert_eq!(r["a2"], "explicit rst1");
assert_eq!(r["a3"], "default rst (declared in examples_with_default)");
assert_eq!(r["a4"], "explicit 1'b0");
}
#[test]
fn examples_without_default() {
let r = resolved(EXAMPLES_WITHOUT_DEFAULT);
assert_eq!(r.len(), 3, "{r:?}");
assert_eq!(r["a5"], "explicit rst");
assert_eq!(r["a6"], "explicit rst");
assert_eq!(r["a7"], "none");
}
#[test]
fn nested_module_inherits_the_default() {
let r = resolved(
"module m1;
bit clk, rst1, a, b;
default disable iff rst1;
a1: assert property (@(posedge clk) a |=> b);
m2 u2();
module m2;
bit rst2;
a2: assert property (@(posedge clk) a |=> b);
endmodule
endmodule",
);
assert_eq!(r["a1"], "default rst1 (declared in m1)");
assert_eq!(r["a2"], "default rst1 (declared in m1)");
}
#[test]
fn nested_module_default_overrides() {
let r = resolved(
"module m1;
bit clk, rst1, a, b;
default disable iff rst1;
a1: assert property (@(posedge clk) a |=> b);
m2 u2();
module m2;
bit rst2;
default disable iff rst2;
a2: assert property (@(posedge clk) a |=> b);
endmodule
endmodule",
);
assert_eq!(r["a1"], "default rst1 (declared in m1)");
assert_eq!(r["a2"], "default rst2 (declared in m2)");
}
#[test]
fn default_does_not_extend_into_instances() {
let r = resolved(
"module leaf (input logic a, b, clk);
a2: assert property (@(posedge clk) a |=> b);
endmodule
module top;
bit clk, rst1, a, b;
default disable iff rst1;
a1: assert property (@(posedge clk) a |=> b);
leaf u (a, b, clk);
endmodule",
);
assert_eq!(r["a1"], "default rst1 (declared in top)");
assert_eq!(r["a2"], "none");
}
#[test]
fn default_is_position_independent() {
let r = resolved(
"module m;
bit clk, rst, a, b;
a1: assert property (@(posedge clk) a |=> b);
default disable iff rst;
endmodule",
);
assert_eq!(r["a1"], "default rst (declared in m)");
}
#[test]
fn duplicate_defaults_are_an_error_even_when_every_assertion_has_a_clause() {
let src = "module m;
bit clk, rst1, rst2, a, b;
default disable iff rst1;
default disable iff rst2;
a1: assert property (@(posedge clk) disable iff (rst1) a |=> b);
endmodule";
let session = Session::new();
let mut comp = Compilation::new(&session).unwrap();
comp.add_source(src).unwrap();
let design = comp.compile().unwrap();
let analysis = design.analyze(AnalysisFlags::NONE, 1).unwrap();
let tree = design.syntax_trees().into_iter().next().unwrap();
let body = design
.top_instances()
.next()
.unwrap()
.instance_body()
.unwrap();
let err = resolve_disable(None, &body, &tree).expect_err("two defaults in one scope");
assert!(err.to_string().contains("16.15"), "{err}");
drop(analysis);
}