#![allow(dead_code)]
use sv_lang::Diagnostics;
use crate::error::LoweringError;
const RECURSIVE_SEQUENCE: &str = "RecursiveSequence";
pub fn check_named_sequence_cycles(diags: &Diagnostics) -> Result<(), LoweringError> {
let Some(first) = diags
.items()
.iter()
.find(|d| d.is_error() && d.code_name() == RECURSIVE_SEQUENCE)
else {
return Ok(());
};
let reported = quoted_name(&first.message)
.unwrap_or("<unknown>")
.to_string();
let chain = expansion_chain(diags.rendered(), &first.message);
let cycle = match chain.as_slice() {
[] => format!("sequence `{reported}` is recursive"),
[head, ..] => {
let mut walk = chain.join(" -> ");
walk.push_str(" -> ");
walk.push_str(head);
walk
}
};
Err(LoweringError {
message: format!(
"cyclic dependency among named sequences (LRM 16.8): {cycle} (slang reports \
sequence `{reported}` recursive; {} such diagnostic(s) in this design)",
diags
.items()
.iter()
.filter(|d| d.is_error() && d.code_name() == RECURSIVE_SEQUENCE)
.count()
),
at: "sva::named::check_named_sequence_cycles".into(),
})
}
const FORMAL_ARGUMENT_RULES: &[(&str, &str)] = &[
(
"UnboundedNotAllowed",
"a `$` actual argument must bind an untyped formal argument whose every reference is \
an upper bound of a delay/repetition range or itself an actual argument of another \
named sequence instance (LRM 16.8.1)",
),
(
"ConstEvalNonConstVariable",
"an actual argument bound to a formal that is used in a cycle delay or a repetition \
bound must be an elaboration-time constant (LRM 16.8.1: delay_example)",
),
(
"EventExprAssertionArg",
"an `event`-typed formal argument is bound to an event expression and referenced only \
where an event expression is allowed (LRM 16.8.1)",
),
(
"SeqInstanceRepetition",
"a goto/nonconsecutive repetition cannot be applied to a named sequence instance \
(LRM 16.9.2)",
),
(
"AssertionDelayFormalType",
"a typed formal argument used in a cycle delay or repetition bound must be `shortint`, \
`int` or `longint` (LRM 16.8.1)",
),
(
"TooFewArguments",
"an instance must supply an actual argument for every formal argument that has no \
default (LRM 16.8)",
),
(
"AssertionArgTypeMismatch",
"an actual argument bound to a typed formal argument must be cast compatible with the \
formal's type (LRM 16.8.1 rule c)",
),
(
"InvalidSignalEventInSeq",
"an event expression may be an actual argument only for an `event`-typed or untyped \
formal that is used where an event expression is allowed, never for a `sequence` \
formal (LRM 16.8.1 rule a)",
),
(
"TooManyArguments",
"an instance may not supply more actual arguments than the declaration has formal \
arguments (LRM 16.8)",
),
(
"ArgDoesNotExist",
"a named actual argument must name a formal argument of the declaration (LRM 16.8)",
),
(
"UnconnectedArg",
"every formal argument without a default needs an actual argument, by position or by \
name (LRM 16.8)",
),
(
"DuplicateArgAssignment",
"a formal argument may be bound by only one actual argument (LRM 16.8)",
),
(
"ArgCannotBeEmpty",
"an empty actual argument is legal only for a formal argument that has a default \
(LRM 16.8)",
),
];
pub fn check_formal_argument_rules(diags: &Diagnostics) -> Result<(), LoweringError> {
let violations: Vec<_> = diags
.items()
.iter()
.filter(|d| d.is_error())
.filter_map(|d| {
FORMAL_ARGUMENT_RULES
.iter()
.find(|(code, _)| *code == d.code_name())
.map(|(code, rule)| (d, *code, *rule))
})
.collect();
let Some((first, code, rule)) = violations.first() else {
return Ok(());
};
Err(LoweringError {
message: format!(
"illegal formal/actual argument of a named sequence: {rule} -- slang: {} ({code}; \
{} such diagnostic(s) in this design)",
first.message,
violations.len()
),
at: "sva::named::check_formal_argument_rules".into(),
})
}
fn quoted_name(message: &str) -> Option<&str> {
let start = message.find('\'')? + 1;
let len = message[start..].find('\'')?;
Some(&message[start..start + len])
}
fn expansion_chain(rendered: &str, message: &str) -> Vec<String> {
let mut lines = rendered.lines();
let header = format!("error: {message}");
if !lines.any(|l| l.contains(&header)) {
return Vec::new();
}
let mut chain = Vec::new();
for line in lines {
if line.contains(": error: ") || line.contains(": warning: ") {
break;
}
if let Some(pos) = line.find("note: while expanding sequence '")
&& let Some(name) = quoted_name(&line[pos..])
{
chain.push(name.to_string());
}
}
chain.reverse();
chain
}
#[cfg(test)]
mod tests {
use super::*;
use sv_lang::{Compilation, Session};
fn cycle_check(decls: &str) -> Result<(), LoweringError> {
let src = format!(
"module m;\n\
logic sysclk, clk, a, b, c, x, y, z;\n\
{decls}\n\
endmodule\n"
);
let session = Session::new();
let mut comp = Compilation::new(&session).unwrap();
comp.add_source(&src).unwrap();
let design = comp.compile().unwrap();
check_named_sequence_cycles(&design.diagnostics())
}
#[test]
fn cyclic_named_sequences_rejected_with_named_cycle() {
let err = cycle_check(
"sequence s1;\n\
@(posedge sysclk) (x ##1 s2);\n\
endsequence\n\
sequence s2;\n\
@(posedge sysclk) (y ##1 s1);\n\
endsequence",
)
.expect_err("a cyclic dependency among named sequences must be rejected");
let msg = err.to_string();
assert!(
msg.contains("cyclic dependency among named sequences"),
"{msg}"
);
assert!(
msg.contains("s1 -> s2 -> s1") || msg.contains("s2 -> s1 -> s2"),
"the error must quote the cycle `s1 -> s2 -> s1`, got: {msg}"
);
}
#[test]
fn cycle_through_an_instance_inside_an_actual_argument_is_rejected() {
let err = cycle_check(
"sequence s(p); p ##1 b; endsequence\n\
assert property (@(posedge sysclk) s(s(a)));",
)
.expect_err("s(s(a)) is a cyclic dependency under 16.8's actual-argument edge");
let msg = err.to_string();
assert!(msg.contains("s -> s"), "{msg}");
}
#[test]
fn actual_argument_cycle_inside_another_sequence_is_not_reported_by_slang_known_gap() {
cycle_check(
"sequence s(p); p ##1 b; endsequence\n\
sequence user; s(s(a)); endsequence\n\
assert property (@(posedge sysclk) user);",
)
.expect("slang 0.1.9 raises no RecursiveSequence for this shape");
}
#[test]
fn cycle_through_a_default_actual_argument_is_rejected() {
let err = cycle_check(
"sequence s(p = t); p ##1 b; endsequence\n\
sequence t; s ##1 c; endsequence",
)
.expect_err("a default-argument instance closing a loop is a cyclic dependency");
let msg = err.to_string();
assert!(
msg.contains("s") && msg.contains("t") && msg.contains("->"),
"{msg}"
);
}
#[test]
fn an_unused_cyclic_pair_is_still_rejected() {
cycle_check(
"sequence s1; @(posedge sysclk) (x ##1 s2); endsequence\n\
sequence s2; @(posedge sysclk) (y ##1 s1); endsequence",
)
.expect_err("no instantiation of s1/s2 anywhere, still a cycle");
}
fn rules_check(decls: &str) -> Result<(), LoweringError> {
let src = format!(
"module m;\n\
logic clk, a, b, x, y;\n\
int zz;\n\
{decls}\n\
endmodule\n"
);
let session = Session::new();
let mut comp = Compilation::new(&session).unwrap();
comp.add_source(&src).unwrap();
let design = comp.compile().unwrap();
check_formal_argument_rules(&design.diagnostics())
}
#[test]
fn formal_argument_rules_are_reported_with_their_clause() {
let cases: &[(&str, &str, &str)] = &[
(
"sequence s(k, p); p ##[k:5] b; endsequence\n\
assert property (@(posedge clk) s($, a));",
"`$`",
"UnboundedNotAllowed",
),
(
"sequence s(k, p); p ##k b; endsequence\n\
assert property (@(posedge clk) s(zz, a));",
"elaboration-time constant",
"ConstEvalNonConstVariable",
),
(
"int n;\n\
sequence s(event ev, p); @(ev) p ##1 b; endsequence\n\
assert property (s(n, a));",
"`event`-typed",
"EventExprAssertionArg",
),
(
"sequence s(sequence q); q[->2]; endsequence\n\
assert property (@(posedge clk) s(a));",
"goto/nonconsecutive",
"SeqInstanceRepetition",
),
(
"sequence s(bit k, p); p ##k b; endsequence\n\
assert property (@(posedge clk) s(1, a));",
"`shortint`",
"AssertionDelayFormalType",
),
(
"sequence s(p, q); p ##1 q; endsequence\n\
assert property (@(posedge clk) s(a));",
"every formal argument",
"TooFewArguments",
),
(
"int arr[2];\n\
sequence s(int k, p); p ##1 (k == 1); endsequence\n\
assert property (@(posedge clk) s(arr, a));",
"rule c",
"AssertionArgTypeMismatch",
),
(
"sequence s(sequence q); q; endsequence\n\
assert property (@(posedge clk) s(posedge clk));",
"rule a",
"InvalidSignalEventInSeq",
),
(
"sequence s(p, q); p ##1 q; endsequence\n\
assert property (@(posedge clk) s(a, b, x));",
"more actual arguments",
"TooManyArguments",
),
(
"sequence s(p, q); p ##1 q; endsequence\n\
assert property (@(posedge clk) s(.p(a), .q(b), .zzz(x)));",
"must name a formal",
"ArgDoesNotExist",
),
(
"sequence s(p, q); p ##1 q; endsequence\n\
assert property (@(posedge clk) s(.p(a)));",
"by position or by name",
"UnconnectedArg",
),
(
"sequence s(p, q); p ##1 q; endsequence\n\
assert property (@(posedge clk) s(.p(a), .q(b), .p(x)));",
"only one actual",
"DuplicateArgAssignment",
),
(
"sequence s(p, q); p ##1 q; endsequence\n\
assert property (@(posedge clk) s(, b));",
"empty actual argument",
"ArgCannotBeEmpty",
),
];
for (decls, clause, code) in cases {
let err = rules_check(decls).expect_err(decls);
let msg = err.to_string();
assert!(msg.contains(clause), "{code}: {msg}");
assert!(msg.contains(code), "{code}: {msg}");
}
}
#[test]
fn legal_formal_arguments_are_accepted() {
rules_check(
"sequence s(max, int d, p); p ##d b ##[1:max] x; endsequence\n\
sequence u(m2, p); s(m2, 2, p); endsequence\n\
sequence t(event ev, untyped p); @(ev) p; endsequence\n\
sequence r(p); p[*2]; endsequence\n\
assert property (@(posedge clk) u($, a));\n\
assert property (@(posedge clk) s(3, 1, a));\n\
assert property (@(posedge clk) r(a)[*2]);\n\
cover property (t(posedge clk, a));",
)
.expect("every rule of 16.8/16.8.1 is obeyed");
}
#[test]
fn acyclic_dependency_graphs_are_accepted() {
cycle_check(
"sequence s; a ##1 b ##1 c; endsequence\n\
sequence rule; @(posedge sysclk) x ##1 y ##1 s ##1 z; endsequence\n\
sequence left; s ##1 a; endsequence\n\
sequence right; s ##2 b; endsequence\n\
sequence top; left ##1 right; endsequence",
)
.expect("a chain and a diamond are acyclic");
}
}