#![allow(dead_code)]
use sv_lang::kinds::{ExpressionKind, SymbolKind};
use sv_lang::nodes::DefaultDisableDeclarationSyntax;
use sv_lang::{AstNode, Expression, Symbol, SyntaxTree};
use sv_sim_sim::sva::ir::LeafId;
use sv_sim_sim::state::default_disable_of;
use crate::error::LoweringError;
#[derive(Debug, Clone, Copy)]
pub enum DisableSource<'d, 't> {
Explicit(LeafId),
Default {
scope: Symbol<'d>,
decl: DefaultDisableDeclarationSyntax<'t>,
},
None,
}
pub fn resolve_disable<'d, 't>(
explicit: Option<LeafId>,
scope: &Symbol<'d>,
tree: &'t SyntaxTree,
) -> Result<DisableSource<'d, 't>, LoweringError> {
let default = default_disable_of(scope, tree).map_err(|e| {
LoweringError::unsupported_at(&format!("default disable iff (LRM 16.15): {e:?}"), scope)
})?;
Ok(match (explicit, default) {
(Some(leaf), _) => DisableSource::Explicit(leaf),
(None, Some((scope, decl))) => DisableSource::Default { scope, decl },
(None, None) => DisableSource::None,
})
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum TermPred {
Whole,
Bit(u32),
Eq(u64),
}
#[derive(Debug, Clone, Copy)]
pub struct DefaultDisable<'d> {
pub signal: Symbol<'d>,
pub negate: bool,
pub second: Option<(Symbol<'d>, bool)>,
pub and: bool,
pub pred: TermPred,
pub second_pred: TermPred,
}
impl<'d> DefaultDisable<'d> {
pub fn single(self, scope: &Symbol<'d>) -> Result<(Symbol<'d>, bool), LoweringError> {
if self.second.is_some() || self.pred != TermPred::Whole {
return Err(LoweringError::unsupported_at(
"a `default disable iff` joining two signals, or testing a bit or a comparison, as `$inferred_disable` of a \
checker (LRM 17.4) is not executable yet",
scope,
));
}
Ok((self.signal, self.negate))
}
}
fn checker_formal_actual<'d>(formal: &Symbol<'d>) -> Option<Expression<'d>> {
if formal.kind() != SymbolKind::AssertionPort {
return None;
}
let mut cur = formal.parent();
let inst = loop {
let s = cur?;
if s.kind() == SymbolKind::CheckerInstance {
break s;
}
if s.kind() == SymbolKind::CheckerInstanceBody {
break s.checker_instance_body_parent_instance()?;
}
cur = s.parent();
};
let formals = inst.members().filter(|m| {
matches!(
m.kind(),
SymbolKind::AssertionPort | SymbolKind::FormalArgument
)
});
formals
.zip(inst.checker_instance_connections())
.find(|(f, _)| f.name() == formal.name())
.and_then(|(_, conn)| conn.actual())
.and_then(|a| a.as_expression())
}
pub fn resolve_checker_default_disable<'d>(
scope: &Symbol<'d>,
context: &Symbol<'d>,
tree: &SyntaxTree,
) -> Result<Option<DefaultDisable<'d>>, LoweringError> {
let Some(d) = resolve_default_disable(scope, tree)? else {
return Ok(None);
};
let Some(actual) = checker_formal_actual(&d.signal) else {
return Ok(Some(d));
};
d.single(scope)?;
let mut negate = d.negate;
let mut e = actual;
loop {
if e.call_subroutine_name().as_deref() == Some("$inferred_disable") {
return resolve_default_disable(context, tree)?
.map(|c| {
let (signal, cneg) = c.single(context)?;
Ok(DefaultDisable {
signal,
negate: cneg != negate,
second: None,
and: false,
pred: TermPred::Whole,
second_pred: TermPred::Whole,
})
})
.transpose();
}
if e.kind() == ExpressionKind::NamedValue
&& let Some(signal) = e.referenced_symbol()
{
return Ok(Some(DefaultDisable {
signal,
negate,
second: None,
and: false,
pred: TermPred::Whole,
second_pred: TermPred::Whole,
}));
}
if e.kind() == ExpressionKind::UnaryOp
&& e.unary_op() == Some(sv_lang::UnaryOp::LogicalNot)
&& let Some(inner) = e.operand()
{
negate = !negate;
e = inner;
continue;
}
if e.kind() == ExpressionKind::Conversion
&& let Some(inner) = e.operand()
{
e = inner;
continue;
}
return Err(LoweringError::unsupported_at(
&format!(
"`default disable iff ({})` of a checker (LRM 17.2): its formal is bound to \
an expression other than a signal name, `$inferred_disable` or the `!` of \
one, which is not executable yet",
d.signal.name()
),
scope,
));
}
}
pub fn resolve_default_disable<'d>(
scope: &Symbol<'d>,
tree: &SyntaxTree,
) -> Result<Option<DefaultDisable<'d>>, LoweringError> {
let Some((decl_scope, decl)) = default_disable_of(scope, tree).map_err(|e| {
LoweringError::unsupported_at(&format!("default disable iff (LRM 16.15): {e:?}"), scope)
})?
else {
return Ok(None);
};
let text = decl.expr().syntax().text();
let whole = strip_outer_parens(text.trim());
let (first, join, second) = match split_top_level(whole) {
Some((a, and, b)) => (a, and, Some(b)),
None => (whole, false, None),
};
let term = |t: &str| resolve_term(t, &text, scope, &decl_scope);
let (signal, negate, pred) = term(first)?;
let second = second.map(term).transpose()?;
Ok(Some(DefaultDisable {
signal,
negate,
second: second.map(|(s, n, _)| (s, n)),
and: join,
pred,
second_pred: second.map_or(TermPred::Whole, |(_, _, p)| p),
}))
}
fn strip_outer_parens(mut s: &str) -> &str {
while s.starts_with('(') && s.ends_with(')') {
let mut depth = 0i32;
let mut closes_at_end = true;
for (i, c) in s.char_indices() {
match c {
'(' => depth += 1,
')' => {
depth -= 1;
if depth == 0 && i + 1 != s.len() {
closes_at_end = false;
break;
}
}
_ => {}
}
}
if !closes_at_end {
break;
}
s = s[1..s.len() - 1].trim();
}
s
}
fn split_top_level(s: &str) -> Option<(&str, bool, &str)> {
let bytes = s.as_bytes();
let mut depth = 0i32;
let mut i = 0;
while i + 1 < bytes.len() {
match bytes[i] {
b'(' => depth += 1,
b')' => depth -= 1,
b'|' | b'&' if depth == 0 && bytes[i + 1] == bytes[i] => {
return Some((s[..i].trim(), bytes[i] == b'&', s[i + 2..].trim()));
}
_ => {}
}
i += 1;
}
None
}
fn resolve_term<'d>(
term: &str,
text: &str,
scope: &Symbol<'d>,
decl_scope: &Symbol<'d>,
) -> Result<(Symbol<'d>, bool, TermPred), LoweringError> {
let mut rest = term.trim();
let mut negate = false;
let mut bitwise = false;
let mut after_prefix = false;
loop {
if let Some(r) = rest.strip_prefix('!') {
negate = !negate;
rest = r.trim_start();
after_prefix = true;
} else if let Some(r) = rest.strip_prefix('~') {
negate = !negate;
bitwise = true;
rest = r.trim_start();
after_prefix = true;
} else if rest.starts_with('(') && rest.ends_with(')') {
rest = strip_outer_parens(rest);
after_prefix = false;
if rest.starts_with('(') && rest.ends_with(')') {
break;
}
} else {
break;
}
}
let refuse = |why: &str| {
LoweringError::unsupported_at(
&format!(
"`default disable iff ({text})` (LRM 16.15): {why}; only signal names, a \
constant bit select or a comparison with a constant, each optionally negated, \
joined by one `||` or `&&`, are executable yet"
),
scope,
)
};
let is_name = |s: &str| {
s.chars()
.next()
.is_some_and(|c| c.is_ascii_alphabetic() || c == '_')
&& s.chars()
.all(|c| c.is_ascii_alphanumeric() || c == '_' || c == '$')
};
let mut cmp: Option<(&str, bool, &str)> = None;
if let Some(i) = rest.find("==").or_else(|| rest.find("!=")) {
let eq = rest.as_bytes()[i] == b'=';
let after = &rest[i + 2..];
if after.starts_with(['=', '?']) || (!eq && i > 0 && rest.as_bytes()[i - 1] == b'=') {
return Err(refuse("a case or wildcard equality"));
}
cmp = Some((rest[..i].trim(), eq, after.trim()));
}
let mut select: Option<u32> = None;
let name = match cmp {
Some((n, _, _)) => n,
None => rest,
};
let mut name = name;
if let Some(open) = name.find('[')
&& name.ends_with(']')
{
let idx = name[open + 1..name.len() - 1].trim();
let n: u32 = idx
.replace('_', "")
.parse()
.map_err(|_| refuse("a bit select that is not a decimal constant"))?;
select = Some(n);
name = name[..open].trim();
}
if !is_name(name) {
return Err(refuse("an operand that is not a signal name"));
}
if cmp.is_some() && after_prefix {
return Err(refuse(
"a comparison under a `!`/`~` that is not parenthesised",
));
}
if cmp.is_some() && select.is_some() {
return Err(refuse("a comparison of a bit select"));
}
let mut cur = Some(*decl_scope);
while let Some(s) = cur {
if let Some(signal) = s.find(name) {
let ty = signal.value_type();
let width = ty.as_ref().map_or(0, |t| t.bit_width());
let mut pred = TermPred::Whole;
if let Some(n) = select {
let t = ty.ok_or_else(|| refuse(&format!("`{name}` has no value type")))?;
if !t.canonical().is_packed_array() || width <= 1 {
return Err(refuse(&format!("`{name}` is not a packed vector")));
}
let range = t.canonical().fixed_range();
let (lo, hi) = (
i64::from(range.left.min(range.right)),
i64::from(range.left.max(range.right)),
);
if !(lo..=hi).contains(&i64::from(n)) {
return Err(refuse(&format!("bit {n} is outside `{name}`")));
}
pred = TermPred::Bit(i64::from(n).abs_diff(i64::from(range.right)) as u32);
} else if let Some((_, eq, lit)) = cmp {
let (value, sized) = parse_literal(lit).ok_or_else(|| {
refuse("a comparison constant that is not a decimal or sized binary/octal/hex literal")
})?;
if signal.value_type().is_some_and(|t| t.is_signed()) && !sized && width < 32 {
return Err(refuse(&format!(
"an unsized constant against the signed `{name}` (sign extension)"
)));
}
if width < 64 && value >> width != 0 {
return Err(refuse(&format!(
"the constant {value} does not fit `{name}`"
)));
}
pred = TermPred::Eq(value);
if !eq {
negate = !negate;
}
} else if bitwise && width != 1 {
return Err(refuse(&format!("`~` of the multi-bit `{name}`")));
}
return Ok((signal, negate, pred));
}
cur = s.parent();
}
Err(refuse(&format!("`{name}` is not declared")))
}
fn parse_literal(lit: &str) -> Option<(u64, bool)> {
let lit = lit.trim().replace('_', "");
if let Some((size, rest)) = lit.split_once('\'') {
let size: u32 = size.trim().parse().ok().filter(|&s| s > 0)?;
let mut chars = rest.chars();
let radix = match chars.next()?.to_ascii_lowercase() {
'd' => 10,
'b' => 2,
'o' => 8,
'h' => 16,
_ => return None,
};
let value = u64::from_str_radix(chars.as_str().trim(), radix).ok()?;
let value = if size < 64 {
value & ((1u64 << size) - 1)
} else {
value
};
return Some((value, true));
}
lit.parse::<u64>().ok().map(|v| (v, false))
}