use sv_lang::{AnalyzedAssertion, Diagnostics, SemNode, TimingControl};
use super::super::analysis::AnalysisError;
pub fn clock_of<'d>(
assertion: &AnalyzedAssertion<'d>,
node: SemNode<'d>,
) -> Result<Option<TimingControl<'d>>, AnalysisError> {
as_timing_control(assertion.clock(node))
}
pub fn semantic_leading_clock<'d>(
assertion: &AnalyzedAssertion<'d>,
) -> Result<Option<TimingControl<'d>>, AnalysisError> {
as_timing_control(assertion.semantic_leading_clock())
}
fn as_timing_control<'d>(
node: Option<SemNode<'d>>,
) -> Result<Option<TimingControl<'d>>, AnalysisError> {
match node {
None => Ok(None),
Some(n) => n.as_timing_control().map(Some).ok_or_else(|| {
AnalysisError::Unsupported(format!(
"AnalyzedAssertion clock query returned a SemNode of kind `{}` that is not a \
TimingControl -- sv-lang API shape assumption violated",
n.kind_name(),
))
}),
}
}
pub fn has_non_unique_clock_error(diags: &Diagnostics) -> bool {
diags
.items()
.iter()
.any(|d| d.is_error() && d.code_name() == "NoUniqueClock")
}
pub fn has_no_clock_error(diags: &Diagnostics) -> bool {
diags
.items()
.iter()
.any(|d| d.is_error() && d.code_name() == "AssertionNoClock")
}
#[cfg(test)]
mod tests {
use super::*;
use sv_lang::{Analysis, AnalysisFlags, Compilation, EdgeKind, Session, Symbol};
fn compile(src: &str) -> sv_lang::Design {
let session = Session::new();
let mut comp = Compilation::new(&session).expect("session");
comp.add_source(src).expect("add_source");
comp.compile().expect("compiles")
}
fn top_body<'d>(design: &'d sv_lang::Design) -> Symbol<'d> {
design
.top_instances()
.next()
.expect("one top instance")
.instance_body()
.expect("instance body")
}
fn assertions_by_offset<'d>(
analysis: &'d Analysis<'d>,
body: Symbol<'d>,
) -> std::collections::BTreeMap<usize, AnalyzedAssertion<'d>> {
let mut out = std::collections::BTreeMap::new();
for proc in analysis.procedures(body) {
for a in analysis.assertions(proc.symbol()) {
let off = a
.ast_node()
.as_statement()
.expect("a concurrent assertion statement")
.source_range()
.start
.offset as usize;
out.insert(off, a);
}
}
out
}
fn edge_and_signal(tc: TimingControl<'_>) -> (EdgeKind, Option<String>) {
let sig = tc
.children()
.first()
.and_then(|c| c.as_expression())
.and_then(|e| e.referenced_symbol())
.map(|s| s.name().to_string());
(tc.edge(), sig)
}
const EXAMPLES_WITH_DEFAULT: &str = "
module examples_with_default (input logic a, b, c, clk);
property q1;
$rose(a) |-> ##[1:5] b;
endproperty
property q2;
@(posedge clk) q1;
endproperty
default clocking posedge_clk @(posedge clk);
property q3;
$fell(c) |=> q1;
// legal: q1 has no clocking event
endproperty
property q4;
$fell(c) |=> q2;
// legal: q2 has clocking event identical to that of the clocking block
endproperty
endclocking
property q5;
@(negedge clk) b[*3] |=> !b;
endproperty
always @(negedge clk)
begin
a1: assert property ($fell(c) |=> q1);
// legal: contextually inferred leading clocking event, @(negedge clk)
a2: assert property (posedge_clk.q4);
// legal: will be queued (pending) on negedge clk, then (if matured)
// checked at next posedge clk (see 16.14.6)
a3: assert property ($fell(c) |=> q2);
// illegal: multiclocked property with contextually inferred leading
// clocking event
a4: assert property (q5);
// legal: contextually inferred leading clocking event, @(negedge clk)
end
property q6;
q1 and q5;
endproperty
a5: assert property (q6);
// illegal: default leading clocking event, @(posedge clk), but
// semantic leading clock is not unique
a6: assert property ($fell(c) |=> q6);
// legal: default leading clocking event, @(posedge clk), is the
// unique semantic leading clock
sequence s2;
$rose(a) ##[1:5] b;
endsequence
c1: cover property (s2);
// legal: default leading clocking event, @(posedge clk)
c2: cover property (@(negedge clk) s2);
// legal: explicit leading clocking event, @(negedge clk)
endmodule
";
const EXAMPLES_WITHOUT_DEFAULT: &str = "
module examples_without_default (input logic a, b, c, clk);
property q1;
$rose(a) |-> ##[1:5] b;
endproperty
property q5;
@(negedge clk) b[*3] |=> !b;
endproperty
property q6;
q1 and q5;
endproperty
a5: assert property (q6);
// illegal: no leading clocking event
a6: assert property ($fell(c) |=> q6);
// illegal: no leading clocking event
sequence s2;
$rose(a) ##[1:5] b;
endsequence
c1: cover property (s2);
// illegal: no leading clocking event
c2: cover property (@(negedge clk) s2);
// legal: explicit leading clocking event, @(negedge clk)
sequence s3;
@(negedge clk) s2;
endsequence
c3: cover property (s3);
// legal: leading clocking event, @(negedge clk), determined from
// declaration of s3
c4: cover property (s3 ##1 b);
// illegal: no default, inferred, or explicit leading clocking event
// and maximal property is not an instance
endmodule
";
#[test]
fn legal_contextually_inferred_cases_resolve_expected_clock() {
let design = compile(EXAMPLES_WITH_DEFAULT);
let body = top_body(&design);
let analysis = design.analyze(AnalysisFlags::NONE, 1).expect("analysis");
let by_offset = assertions_by_offset(&analysis, body);
let a1 = by_offset[&EXAMPLES_WITH_DEFAULT.find("a1:").unwrap()];
let tc = semantic_leading_clock(&a1)
.expect("no API-shape error")
.expect("a1's clock is resolved");
assert_eq!(edge_and_signal(tc), (EdgeKind::NegEdge, Some("clk".into())));
let a2 = by_offset[&EXAMPLES_WITH_DEFAULT.find("a2:").unwrap()];
let tc = semantic_leading_clock(&a2)
.expect("no API-shape error")
.expect("a2's clock is resolved");
assert_eq!(edge_and_signal(tc), (EdgeKind::PosEdge, Some("clk".into())));
let a4 = by_offset[&EXAMPLES_WITH_DEFAULT.find("a4:").unwrap()];
let tc = semantic_leading_clock(&a4)
.expect("no API-shape error")
.expect("a4's clock is resolved");
assert_eq!(edge_and_signal(tc), (EdgeKind::NegEdge, Some("clk".into())));
}
#[test]
fn legal_default_clocking_cases_resolve_default_event() {
let design = compile(EXAMPLES_WITH_DEFAULT);
let body = top_body(&design);
let analysis = design.analyze(AnalysisFlags::NONE, 1).expect("analysis");
let by_offset = assertions_by_offset(&analysis, body);
for tag in ["a6:", "c1:"] {
let a = by_offset[&EXAMPLES_WITH_DEFAULT.find(tag).unwrap()];
let tc = semantic_leading_clock(&a)
.expect("no API-shape error")
.unwrap_or_else(|| panic!("{tag}'s clock is resolved"));
assert_eq!(
edge_and_signal(tc),
(EdgeKind::PosEdge, Some("clk".into())),
"{tag} resolves to the default clocking event"
);
}
}
#[test]
fn legal_explicit_clock_overrides_default() {
let design = compile(EXAMPLES_WITH_DEFAULT);
let body = top_body(&design);
let analysis = design.analyze(AnalysisFlags::NONE, 1).expect("analysis");
let by_offset = assertions_by_offset(&analysis, body);
let c2 = by_offset[&EXAMPLES_WITH_DEFAULT.find("c2:").unwrap()];
let tc = semantic_leading_clock(&c2)
.expect("no API-shape error")
.expect("c2's clock is resolved");
assert_eq!(edge_and_signal(tc), (EdgeKind::NegEdge, Some("clk".into())));
}
#[test]
fn a5_non_unique_clock_is_dropped_and_diagnosed() {
let design = compile(EXAMPLES_WITH_DEFAULT);
let body = top_body(&design);
let analysis = design.analyze(AnalysisFlags::NONE, 1).expect("analysis");
let by_offset = assertions_by_offset(&analysis, body);
let a5_offset = EXAMPLES_WITH_DEFAULT.find("a5:").unwrap();
assert!(
!by_offset.contains_key(&a5_offset),
"a5 is illegal (non-unique semantic leading clock): slang drops it from \
Analysis::assertions() entirely rather than returning an AnalyzedAssertion \
a caller could ask semantic_leading_clock() of"
);
assert!(
has_non_unique_clock_error(&analysis.diagnostics()),
"a5's illegality must be visible as a NoUniqueClock analysis diagnostic"
);
assert!(
!has_no_clock_error(&analysis.diagnostics()),
"a5's violation is non-uniqueness, not absence of any clock at all"
);
}
#[test]
fn a3_examples_with_default_is_accepted_not_rejected_known_gap() {
let design = compile(EXAMPLES_WITH_DEFAULT);
let body = top_body(&design);
let analysis = design.analyze(AnalysisFlags::NONE, 1).expect("analysis");
let by_offset = assertions_by_offset(&analysis, body);
let a3_offset = EXAMPLES_WITH_DEFAULT.find("a3:").unwrap();
let a3 = *by_offset
.get(&a3_offset)
.expect("known gap: slang does not drop a3 the way it drops a5");
let tc = semantic_leading_clock(&a3)
.expect("no API-shape error")
.expect("known gap: slang resolves a3's clock instead of rejecting it");
assert_eq!(
edge_and_signal(tc),
(EdgeKind::NegEdge, Some("clk".into())),
"known gap: a3 resolves to the contextually inferred clock, not an error"
);
let non_unique_clock_errors = analysis
.diagnostics()
.items()
.iter()
.filter(|d| d.is_error() && d.code_name() == "NoUniqueClock")
.count();
assert_eq!(
non_unique_clock_errors, 1,
"known gap: slang raises no diagnostic for a3 at all -- the one NoUniqueClock \
error present is a5's, not a3's (LRM says a3 should also be illegal via rule \
(c)+(e); see the module doc's gap section for the confirmed root cause)"
);
}
#[test]
fn examples_without_default_legal_cases_resolve_expected_clock() {
let design = compile(EXAMPLES_WITHOUT_DEFAULT);
let body = top_body(&design);
let analysis = design.analyze(AnalysisFlags::NONE, 1).expect("analysis");
let by_offset = assertions_by_offset(&analysis, body);
for tag in ["c2:", "c3:"] {
let a = by_offset[&EXAMPLES_WITHOUT_DEFAULT.find(tag).unwrap()];
let tc = semantic_leading_clock(&a)
.expect("no API-shape error")
.unwrap_or_else(|| panic!("{tag}'s clock is resolved"));
assert_eq!(
edge_and_signal(tc),
(EdgeKind::NegEdge, Some("clk".into())),
"{tag} resolves to the explicit/declared @(negedge clk)"
);
}
}
#[test]
fn examples_without_default_illegal_cases_are_dropped_and_diagnosed() {
let design = compile(EXAMPLES_WITHOUT_DEFAULT);
let body = top_body(&design);
let analysis = design.analyze(AnalysisFlags::NONE, 1).expect("analysis");
let by_offset = assertions_by_offset(&analysis, body);
let diags = analysis.diagnostics();
for tag in ["a5:", "a6:", "c1:", "c4:"] {
let offset = EXAMPLES_WITHOUT_DEFAULT.find(tag).unwrap();
assert!(
!by_offset.contains_key(&offset),
"{tag} is illegal (no leading clocking event): must be dropped from \
Analysis::assertions()"
);
}
assert!(
has_no_clock_error(&diags),
"at least one AssertionNoClock diagnostic must be present"
);
assert!(
!has_non_unique_clock_error(&diags),
"examples_without_default's violations are all rule (f) (no clock at all), never \
rule (e) (non-unique)"
);
let no_clock_errors = diags
.items()
.iter()
.filter(|d| d.is_error() && d.code_name() == "AssertionNoClock")
.count();
assert_eq!(
no_clock_errors, 4,
"a5, a6, c1, and c4 each raise their own AssertionNoClock diagnostic"
);
}
#[test]
fn semantic_leading_clock_agrees_with_clock_of_root() {
let design = compile(EXAMPLES_WITHOUT_DEFAULT);
let body = top_body(&design);
let analysis = design.analyze(AnalysisFlags::NONE, 1).expect("analysis");
let by_offset = assertions_by_offset(&analysis, body);
let c2 = by_offset[&EXAMPLES_WITHOUT_DEFAULT.find("c2:").unwrap()];
let via_slc = semantic_leading_clock(&c2)
.expect("no API-shape error")
.expect("c2 has a resolved clock");
let via_clock_of = clock_of(&c2, c2.root())
.expect("no API-shape error")
.expect("c2 has a resolved clock");
assert_eq!(edge_and_signal(via_slc), edge_and_signal(via_clock_of));
}
}