use std::collections::BTreeMap;
use super::*;
use sv_lang::SyntaxTree;
const MC: &str = "logic clk1, clk2, f;\n\
sequence s1; a ##1 b; endsequence\n\
sequence s2; c ##1 d; endsequence\n\
sequence mult_s;\n\
@(posedge clk) a ##1 @(posedge clk1) s1 ##1 @(posedge clk2) s2;\n\
endsequence\n\
property mult_p1;\n\
@(posedge clk) a ##1 @(posedge clk1) s1 ##1 @(posedge clk2) s2;\n\
endproperty\n\
property mult_p2;\n\
mult_s;\n\
endproperty\n\
property mult_p3;\n\
@(posedge clk) a ##1 @(posedge clk1) s1 |=> @(posedge clk2) s2;\n\
endproperty\n\
property mult_p6;\n\
mult_s |=> mult_s;\n\
endproperty\n\
property mult_p7;\n\
@(posedge clk) a ##1 b |-> c ##1 @(posedge clk1) d;\n\
endproperty\n\
property mult_p8;\n\
@(posedge clk) a ##1 b |->\n\
if (c)\n\
(1 |=> @(posedge clk1) d)\n\
else\n\
e ##1 @(posedge clk2) f ;\n\
endproperty\n";
fn clock_key(leaves: &LeafTable<'_>, tree: &SyntaxTree, id: u32) -> String {
let event = leaves
.clock_event(ClockId(id))
.unwrap_or_else(|| panic!("clock {id} has no event"));
let text: String = event
.as_timing_control()
.expect("a clock event is a timing control")
.syntax(tree)
.expect("a clock event has syntax")
.text()
.split_whitespace()
.collect();
let text = text.strip_prefix('@').unwrap_or(&text);
text.strip_prefix('(')
.and_then(|t| t.strip_suffix(')'))
.unwrap_or(text)
.to_string()
}
fn mc(
decls: &str,
stmt: &str,
signals: &[(&str, &str)],
clocks: &[(&str, &str)],
strong: bool,
) -> Verdict {
let table = rows(signals);
let len = table.values().next().expect("a signal").len();
assert!(table.values().all(|r| r.len() == len), "ragged signal rows");
let by_event: BTreeMap<&str, &str> = clocks.iter().copied().collect();
with_property(decls, stmt, false, |r, tree| {
let (ir, leaves) = r.unwrap_or_else(|e| panic!("`{stmt}` failed to build: {e}"));
let mut rows_by_id: Vec<Vec<bool>> = Vec::new();
let mut used = 0;
for id in 0..leaves.clock_count() as u32 {
let key = clock_key(&leaves, tree, id);
let row = by_event
.get(key.as_str())
.unwrap_or_else(|| panic!("`{stmt}`: no tick row for clock `{key}`"));
assert_eq!(row.len(), len, "tick row of `{key}`");
rows_by_id.push(row.chars().map(|c| c == '1').collect());
used += 1;
}
assert_eq!(
used,
by_event.len(),
"a tick row names no clock of `{stmt}`"
);
let refs: Vec<&[bool]> = rows_by_id.iter().map(Vec::as_slice).collect();
let mut env = Sigs::new(&leaves, &table);
env.timeline = Some(Timeline::from_rows(&refs));
Evaluator::new(&ir, &env, strong)
.verdict(len as u32, 0)
.unwrap_or_else(|e| panic!("`{stmt}` failed to evaluate: {e}"))
})
}
fn mca(prop: &str, signals: &[(&str, &str)], clocks: &[(&str, &str)]) -> Verdict {
mc(
MC,
&format!("assert property ({prop});"),
signals,
clocks,
false,
)
}
fn sync_shape(decls: &str, stmt: &str) -> (usize, Vec<(u32, bool)>) {
with_property(decls, stmt, false, |r, _| {
let (ir, leaves) = r.unwrap_or_else(|e| panic!("`{stmt}` failed to build: {e}"));
let syncs = ir
.automaton
.states
.iter()
.flat_map(|st| &st.edges)
.filter_map(|e| match &e.label {
Transition::ClockSync { to, strict } => Some((to.0, *strict)),
_ => None,
})
.collect();
(leaves.clock_count(), syncs)
})
}
fn refused(decls: &str, stmt: &str) -> String {
with_property(decls, stmt, false, |r, _| match r {
Ok(_) => panic!("`{stmt}` was expected to be rejected"),
Err(e) => e.to_string(),
})
}
fn passes(v: Verdict) -> bool {
matches!(v, Verdict::Holds | Verdict::HoldsStrongly)
}
#[test]
fn mult_s_p1_p2_synchronise_each_clock_change_to_the_next_tick() {
let clocks = [
("posedgeclk", "1000000000"),
("posedgeclk1", "0101010000"),
("posedgeclk2", "0000101010"),
];
let good = [
("a", "1100000000"),
("b", "0001000000"),
("c", "0000100000"),
("d", "0000001000"),
];
let bad_b = [
("a", "1100000000"),
("b", "0010000000"), ("c", "0000100000"),
("d", "0000001000"),
];
let bad_c = [
("a", "1100000000"),
("b", "0001000000"),
("c", "0001000000"), ("d", "0000001000"),
];
let bad_d = [
("a", "1100000000"),
("b", "0001000000"),
("c", "0000100000"),
("d", "0000000010"), ];
for prop in ["mult_p1", "mult_p2"] {
assert_eq!(mca(prop, &good, &clocks), Verdict::HoldsStrongly, "{prop}");
for (name, trace) in [("b", &bad_b), ("c", &bad_c), ("d", &bad_d)] {
assert_eq!(
mca(prop, trace, &clocks),
Verdict::Fails,
"{prop} bad {name}"
);
}
}
let on_its_own = mc(MC, "assert property (mult_s);", &good, &clocks, false);
assert_eq!(on_its_own, Verdict::HoldsStrongly);
let cut = [
("a", "11000"),
("b", "00010"),
("c", "00001"),
("d", "00000"),
];
let cut_clocks = [
("posedgeclk", "10000"),
("posedgeclk1", "01010"),
("posedgeclk2", "00001"),
];
for strong in [false, true] {
assert_eq!(
mc(MC, "assert property (mult_s);", &cut, &cut_clocks, strong),
Verdict::Holds,
"strong = {strong}"
);
}
let cut_bad = [
("a", "11000"),
("b", "00010"),
("c", "00000"),
("d", "00000"),
];
assert_eq!(
mc(
MC,
"assert property (mult_s);",
&cut_bad,
&cut_clocks,
false
),
Verdict::Fails
);
}
#[test]
fn mult_s_has_two_strict_clock_syncs() {
for prop in ["mult_p1", "mult_p2", "mult_s"] {
let (clocks, syncs) = sync_shape(MC, &format!("assert property ({prop});"));
assert_eq!(clocks, 3, "{prop}");
assert_eq!(syncs, [(1, true), (2, true)], "{prop}");
}
let (_, syncs) = sync_shape(
MC,
"assert property (@(posedge clk) a ##0 @(posedge clk1) b ##0 @(posedge clk2) c);",
);
assert_eq!(syncs, [(1, false), (2, false)]);
}
#[test]
fn cross_clock_zero_delay_may_overlap_and_one_delay_may_not() {
let clocks = [("posedgeclk", "1100"), ("posedgeclk1", "0110")];
let on_ticks = [("a", "1000"), ("b", "0100")];
let decls = "logic clk1;";
let zero = "assert property (@(posedge clk) a ##0 @(posedge clk1) b);";
let one = "assert property (@(posedge clk) a ##1 @(posedge clk1) b);";
assert_eq!(
mc(decls, zero, &on_ticks, &clocks, false),
Verdict::HoldsStrongly
);
assert_eq!(
mc(decls, one, &on_ticks, &clocks, false),
Verdict::HoldsStrongly
);
let clocks = [("posedgeclk", "1100"), ("posedgeclk1", "1110")];
let at_zero = [("a", "1000"), ("b", "1000")];
assert_eq!(
mc(decls, zero, &at_zero, &clocks, false),
Verdict::HoldsStrongly,
"##0 reads b at the overlapping clk1 tick"
);
assert_eq!(
mc(decls, one, &at_zero, &clocks, false),
Verdict::Fails,
"##1 skips the overlapping tick: b is read at step 1, where it is false"
);
}
#[test]
fn mult_p3_checks_the_consequent_on_the_next_clk2_tick_after_the_antecedent() {
let ante = [("a", "1100000000"), ("b", "0001000000")];
let clocks = [
("posedgeclk", "1000000000"),
("posedgeclk1", "0101010000"),
("posedgeclk2", "0000101010"),
];
let with = |c: &str, d: &str| -> Vec<(&'static str, String)> {
let mut v: Vec<(&'static str, String)> =
ante.iter().map(|(n, r)| (*n, r.to_string())).collect();
v.push(("c", c.to_string()));
v.push(("d", d.to_string()));
v
};
let run = |c: &str, d: &str, clocks: &[(&str, &str)]| {
let owned = with(c, d);
let sig: Vec<(&str, &str)> = owned.iter().map(|(n, r)| (*n, r.as_str())).collect();
mca("mult_p3", &sig, clocks)
};
assert_eq!(
run("0000100000", "0000001000", &clocks),
Verdict::HoldsStrongly
);
assert_eq!(run("0000100000", "0000000010", &clocks), Verdict::Fails);
assert_eq!(run("0000001000", "0000000010", &clocks), Verdict::Fails);
let sig = [
("a", "1100000000"),
("b", "0000000000"),
("c", "0000000000"),
("d", "0000000000"),
];
assert!(passes(mca("mult_p3", &sig, &clocks)));
let clocks = [
("posedgeclk", "1000000000"),
("posedgeclk1", "0101010000"),
("posedgeclk2", "0001001010"),
];
assert_eq!(
run("0000001000", "0000000010", &clocks),
Verdict::HoldsStrongly
);
assert_eq!(run("0001000000", "0000001000", &clocks), Verdict::Fails);
}
#[test]
fn mult_p6_restarts_the_whole_chain_on_the_leading_clock() {
let clocks = [
("posedgeclk", "10000010000010000000"),
("posedgeclk1", "01010000000001010000"),
("posedgeclk2", "00001010000000101010"),
];
let a = "11000000000011000000";
let b = "00010000000000010000";
let c = "00001000000000001000";
let d = "00000010000000000010";
let good = [("a", a), ("b", b), ("c", c), ("d", d)];
assert_eq!(mca("mult_p6", &good, &clocks), Verdict::HoldsStrongly);
let early = [
("a", "11000010000001000000"),
("b", "00010000000000010000"),
("c", "00001000000000001000"),
("d", "00000010000000000010"),
];
assert_eq!(mca("mult_p6", &early, &clocks), Verdict::Fails);
let short_clocks = [
("posedgeclk", "1000001000001"),
("posedgeclk1", "0101000000000"),
("posedgeclk2", "0000101000100"),
];
let short = [
("a", "1100001000001"),
("b", "0001000000000"),
("c", "0000100000000"),
("d", "0000001000000"),
];
assert_eq!(mca("mult_p6", &short, &short_clocks), Verdict::Holds);
let short_bad = [
("a", "1100001000000"),
("b", "0001000000000"),
("c", "0000100000000"),
("d", "0000001000000"),
];
assert_eq!(mca("mult_p6", &short_bad, &short_clocks), Verdict::Fails);
}
#[test]
fn mult_p7_flows_clk_through_the_overlapped_implication() {
let clocks = [("posedgeclk", "101010"), ("posedgeclk1", "010101")];
let base = |d: &'static str| [("a", "100000"), ("b", "001000"), ("c", "001000"), ("d", d)];
assert_eq!(
mca("mult_p7", &base("000100"), &clocks),
Verdict::HoldsStrongly
);
assert_eq!(mca("mult_p7", &base("000001"), &clocks), Verdict::Fails);
assert_eq!(mca("mult_p7", &base("001000"), &clocks), Verdict::Fails);
let no_c = [
("a", "100000"),
("b", "001000"),
("c", "000000"),
("d", "000100"),
];
assert_eq!(mca("mult_p7", &no_c, &clocks), Verdict::Fails);
let no_b = [
("a", "100000"),
("b", "000000"),
("c", "001000"),
("d", "000000"),
];
assert!(passes(mca("mult_p7", &no_b, &clocks)));
let (clocks_n, syncs) = sync_shape(MC, "assert property (mult_p7);");
assert_eq!((clocks_n, syncs), (2, vec![(1, true)]));
}
#[test]
fn mult_p8_selects_between_two_clock_flows() {
let clocks = [
("posedgeclk", "1010000"),
("posedgeclk1", "0001010"),
("posedgeclk2", "0000101"),
];
let run = |c: &'static str, d: &'static str, e: &'static str, f: &'static str| {
mca(
"mult_p8",
&[
("a", "1000000"),
("b", "0010000"),
("c", c),
("d", d),
("e", e),
("f", f),
],
&clocks,
)
};
assert_eq!(
run("0010000", "0001000", "0000000", "0000000"),
Verdict::HoldsStrongly
);
assert_eq!(
run("0010000", "0000010", "0000000", "0000000"),
Verdict::Fails,
"d at clk1's SECOND tick is not read"
);
assert_eq!(
run("0010000", "0001000", "0000000", "0000000"),
Verdict::HoldsStrongly
);
assert_eq!(
run("0000000", "0000000", "0010000", "0000100"),
Verdict::HoldsStrongly
);
assert_eq!(
run("0000000", "0000000", "0010000", "0000001"),
Verdict::Fails,
"f at clk2's second tick is not read"
);
assert_eq!(
run("0000000", "0001000", "0000000", "0000100"),
Verdict::Fails,
"e false at the end of the antecedent"
);
assert_eq!(
run("0000000", "0001000", "0010000", "0000000"),
Verdict::Fails
);
let (n, syncs) = sync_shape(MC, "assert property (mult_p8);");
assert_eq!((n, syncs), (3, vec![(2, true)]));
}
const P_A1: &str = "logic clk1, clk2, f;\n\
property p;\n\
logic v = e;\n\
(@(posedge clk1) (a == v)[*1:$] |-> b)\n\
and\n\
(@(posedge clk2) c[*1:$] |-> d == v)\n\
;\n\
endproperty\n";
const A1: &str = "assert property (@(posedge clk) f |=> p);";
#[test]
fn each_semantic_leading_clock_gets_its_own_copy_of_the_local_variable() {
let clocks = [
("posedgeclk", "10000"),
("posedgeclk1", "01010"),
("posedgeclk2", "00101"),
];
let sig = |a: &'static str, b: &'static str, c: &'static str, d: &'static str| {
[
("f", "10000"),
("e", "01000"),
("a", a),
("b", b),
("c", c),
("d", d),
]
.map(|(n, r)| (n, r))
.to_vec()
};
let run = |s: Vec<(&str, &str)>| mc(P_A1, A1, &s, &clocks, false);
assert_eq!(
run(sig("01000", "01000", "00100", "00000")),
Verdict::HoldsStrongly
);
assert_eq!(run(sig("01000", "01000", "00100", "00100")), Verdict::Fails);
assert_eq!(run(sig("01000", "00000", "00100", "00000")), Verdict::Fails);
assert!(passes(run(sig("00000", "00000", "00100", "00000"))));
assert_eq!(run(sig("00000", "00000", "00100", "00100")), Verdict::Fails);
assert_eq!(run(sig("01000", "01000", "00100", "01100")), Verdict::Fails);
}
#[test]
fn the_declaration_assignment_happens_after_the_antecedent_not_at_it() {
let clocks = [
("posedgeclk", "1000000"),
("posedgeclk1", "1101010"),
("posedgeclk2", "1010101"),
];
let good = [
("f", "1000000"),
("e", "0110000"),
("a", "0100000"), ("b", "0100000"),
("c", "0010000"),
("d", "0010000"), ];
assert_eq!(mc(P_A1, A1, &good, &clocks, false), Verdict::HoldsStrongly);
let bad_b = [
("f", "1000000"),
("e", "0110000"),
("a", "0100000"),
("b", "0000000"),
("c", "0010000"),
("d", "0010000"),
];
assert_eq!(mc(P_A1, A1, &bad_b, &clocks, false), Verdict::Fails);
let bad_d = [
("f", "1000000"),
("e", "0110000"),
("a", "0100000"),
("b", "0100000"),
("c", "0010000"),
("d", "0000000"),
];
assert_eq!(mc(P_A1, A1, &bad_d, &clocks, false), Verdict::Fails);
}
#[test]
fn the_local_variable_is_pushed_below_each_leading_clock() {
let counts = |decls: &str, stmt: &str| {
with_property(decls, stmt, false, |r, _| {
let (ir, _) = r.unwrap_or_else(|e| panic!("`{stmt}` failed to build: {e}"));
let lets = ir
.props
.iter()
.filter(|p| matches!(p, Prop::Let { .. }))
.count();
let under_clock = ir
.props
.iter()
.filter(|p| match p {
Prop::Clocked { p, .. } => matches!(ir.prop(*p), Prop::Let { .. }),
_ => false,
})
.count();
(lets, under_clock)
})
};
assert_eq!(counts(P_A1, A1), (2, 2));
let one = "logic clk1, f;\n\
property q; logic v = e; (@(posedge clk1) (a == v)[*1:$] |-> b); endproperty\n";
assert_eq!(
counts(one, "assert property (@(posedge clk) f |=> q);"),
(1, 0)
);
}
const EQ_CLOCKS: &str = "module m;\n\
wire clk1, clk2;\n\
logic a, b;\n\
assign clk2 = clk1;\n\
a1: assert property (@(clk1) a and @(clk2) b);\n\
a2: assert property (@(clk1) a and @(clk1) b);\n\
always @(posedge clk1) begin\n\
a3: assert property(a and @(posedge clk2) b);\n\
a4: assert property(a and @(posedge clk1) b);\n\
end\n\
endmodule\n";
#[test]
fn the_same_value_clocks_of_16_16_1_a1_to_a4() {
let session = Session::new();
let mut comp = Compilation::new(&session).unwrap();
comp.add_source(EQ_CLOCKS).unwrap();
let design = comp.compile().unwrap();
let m = design
.top_instances()
.next()
.unwrap()
.instance_body()
.unwrap();
let analysis = design.analyze(AnalysisFlags::NONE, 1).unwrap();
let tree = design.syntax_trees().into_iter().next().unwrap();
let diags = analysis.diagnostics();
let no_unique = diags
.items()
.iter()
.filter(|d| d.is_error() && d.code_name() == "NoUniqueClock")
.count();
assert_eq!(
no_unique,
2,
"a1 and a3 are the illegal assertions slang reports:\n{}",
diags.rendered()
);
let mut built_labels = Vec::new();
for proc in analysis.procedures(m) {
for assertion in analysis.assertions(proc.symbol()) {
let stmt_text = assertion
.ast_node()
.as_statement()
.and_then(|s| s.syntax(&tree))
.map(|n| n.text().split_whitespace().collect::<String>())
.unwrap_or_default();
let root = assertion.root().as_assertion_expr().unwrap();
let proc_event = || {
let mut stack: Vec<_> = sv_sim_sim::process::cfg::proc_body(&proc.symbol())
.map(|b| b.as_sem_node())
.into_iter()
.collect();
while let Some(n) = stack.pop() {
if n.as_timing_control().is_some() {
return Some(n);
}
stack.extend(n.children());
}
None
};
let (root, root_clock) = if root.kind() == AssertionExprKind::Clocking {
let kids = root.children();
let clock = if crate::sva::build::is_inferred_clock_event(kids[0]) {
proc_event().unwrap_or(kids[0])
} else {
kids[0]
};
(kids[1].as_assertion_expr().unwrap(), Some(clock))
} else {
(root, proc_event())
};
let (_, leaves) = build_property(root, root_clock, (&tree).into(), &m)
.unwrap_or_else(|e| panic!("`{stmt_text}` failed to build: {e}"));
built_labels.push((stmt_text, leaves.clock_count()));
}
}
let count_of = |label: &str| {
built_labels
.iter()
.find(|(t, _)| t.contains(label))
.unwrap_or_else(|| {
panic!(
"{label} was not analysed: {built_labels:?}\n{}",
diags.rendered()
)
})
.1
};
assert_eq!(count_of("a2:"), 1, "a2: identical clocks are one clock");
assert_eq!(count_of("a4:"), 1, "a4: identical clocks are one clock");
assert!(
built_labels
.iter()
.all(|(t, _)| !t.contains("a1:") && !t.contains("a3:")),
"a1 and a3 are illegal and are not built: {built_labels:?}"
);
}
fn slang_diagnostics(decls: &str, stmt: &str) -> String {
let src = format!(
"module m;\n\
logic clk, a, b, c, d, e;\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 analysis = design.analyze(AnalysisFlags::NONE, 1).unwrap();
format!(
"{}{}",
design.diagnostics().rendered(),
analysis.diagnostics().rendered()
)
}
#[test]
fn repeating_a_multiclocked_sequence_is_refused_by_name() {
let decls = "logic clk1;\n\
sequence u; @(posedge clk) a ##1 b; endsequence\n\
sequence v; @(posedge clk1) c ##1 d; endsequence\n";
for stmt in [
"assert property ((u ##1 v)[*2]);",
"assert property ((u ##1 v)[*1:2]);",
"assert property (first_match(u ##1 v));",
] {
let msg = refused(decls, stmt);
assert!(
msg.contains("multiclocked sequence"),
"`{stmt}` was refused for another reason: {msg}"
);
}
}
#[test]
fn slang_refuses_what_a_clock_change_cannot_take() {
let decls = "logic clk1;\n\
sequence u; @(posedge clk) a ##1 b; endsequence\n\
sequence v; @(posedge clk1) c ##1 d; endsequence\n";
for stmt in [
"assert property (u ##2 v);",
"assert property (u ##[1:2] v);",
"assert property (u and v);",
"assert property (u or v);",
"assert property (u intersect v);",
"assert property (u within v);",
"assert property (a throughout v);",
"assert property (@(posedge clk) a[*0:1] ##1 v);",
"assert property (@(posedge clk) a ##1 v[*0:1]);",
] {
let msg = slang_diagnostics(decls, stmt);
assert!(msg.contains("error"), "slang accepted `{stmt}`");
}
for stmt in [
"assert property (u and u);",
"assert property (u intersect u);",
"assert property (first_match(u ##1 u));",
"assert property (u[*2]);",
] {
let msg = slang_diagnostics(decls, stmt);
assert!(!msg.contains("error"), "`{stmt}`: {msg}");
}
}