#[derive(Debug, Clone)]
pub enum SvaExpr {
Signal(String),
Const(u64, u32),
Rose(Box<SvaExpr>),
Fell(Box<SvaExpr>),
Past(Box<SvaExpr>, u32),
And(Box<SvaExpr>, Box<SvaExpr>),
Or(Box<SvaExpr>, Box<SvaExpr>),
Not(Box<SvaExpr>),
Eq(Box<SvaExpr>, Box<SvaExpr>),
Implication {
antecedent: Box<SvaExpr>,
consequent: Box<SvaExpr>,
overlapping: bool,
},
Delay {
body: Box<SvaExpr>,
min: u32,
max: Option<u32>,
},
Repetition {
body: Box<SvaExpr>,
min: u32,
max: Option<u32>, },
SEventually(Box<SvaExpr>),
SAlways(Box<SvaExpr>),
Stable(Box<SvaExpr>),
Changed(Box<SvaExpr>),
DisableIff {
condition: Box<SvaExpr>,
body: Box<SvaExpr>,
},
Nexttime(Box<SvaExpr>, u32),
IfElse {
condition: Box<SvaExpr>,
then_expr: Box<SvaExpr>,
else_expr: Box<SvaExpr>,
},
NotEq(Box<SvaExpr>, Box<SvaExpr>),
LessThan(Box<SvaExpr>, Box<SvaExpr>),
GreaterThan(Box<SvaExpr>, Box<SvaExpr>),
LessEqual(Box<SvaExpr>, Box<SvaExpr>),
GreaterEqual(Box<SvaExpr>, Box<SvaExpr>),
Ternary {
condition: Box<SvaExpr>,
then_expr: Box<SvaExpr>,
else_expr: Box<SvaExpr>,
},
Throughout {
signal: Box<SvaExpr>,
sequence: Box<SvaExpr>,
},
Within {
inner: Box<SvaExpr>,
outer: Box<SvaExpr>,
},
FirstMatch(Box<SvaExpr>),
Intersect {
left: Box<SvaExpr>,
right: Box<SvaExpr>,
},
OneHot0(Box<SvaExpr>),
OneHot(Box<SvaExpr>),
CountOnes(Box<SvaExpr>),
IsUnknown(Box<SvaExpr>),
Sampled(Box<SvaExpr>),
Bits(Box<SvaExpr>),
Clog2(Box<SvaExpr>),
CountBits(Box<SvaExpr>, Vec<char>),
IsUnbounded(Box<SvaExpr>),
GotoRepetition {
body: Box<SvaExpr>,
count: u32,
},
NonConsecRepetition {
body: Box<SvaExpr>,
min: u32,
max: Option<u32>,
},
AcceptOn {
condition: Box<SvaExpr>,
body: Box<SvaExpr>,
},
RejectOn {
condition: Box<SvaExpr>,
body: Box<SvaExpr>,
},
PropertyNot(Box<SvaExpr>),
PropertyImplies(Box<SvaExpr>, Box<SvaExpr>),
PropertyIff(Box<SvaExpr>, Box<SvaExpr>),
Always(Box<SvaExpr>),
AlwaysBounded { body: Box<SvaExpr>, min: u32, max: Option<u32> },
SAlwaysBounded { body: Box<SvaExpr>, min: u32, max: u32 },
EventuallyBounded { body: Box<SvaExpr>, min: u32, max: u32 },
SEventuallyBounded { body: Box<SvaExpr>, min: u32, max: Option<u32> },
Until { lhs: Box<SvaExpr>, rhs: Box<SvaExpr>, strong: bool, inclusive: bool },
Strong(Box<SvaExpr>),
Weak(Box<SvaExpr>),
SNexttime(Box<SvaExpr>, u32),
FollowedBy { antecedent: Box<SvaExpr>, consequent: Box<SvaExpr>, overlapping: bool },
PropertyCase { expression: Box<SvaExpr>, items: Vec<(Vec<SvaExpr>, Box<SvaExpr>)>, default: Option<Box<SvaExpr>> },
SyncAcceptOn { condition: Box<SvaExpr>, body: Box<SvaExpr> },
SyncRejectOn { condition: Box<SvaExpr>, body: Box<SvaExpr> },
SequenceAnd(Box<SvaExpr>, Box<SvaExpr>),
SequenceOr(Box<SvaExpr>, Box<SvaExpr>),
ImmediateAssert {
expression: Box<SvaExpr>,
deferred: Option<ImmediateDeferred>,
},
FieldAccess { signal: Box<SvaExpr>, field: String },
EnumLiteral { type_name: Option<String>, value: String },
Triggered(String),
Matched(String),
BitAnd(Box<SvaExpr>, Box<SvaExpr>),
BitOr(Box<SvaExpr>, Box<SvaExpr>),
BitXor(Box<SvaExpr>, Box<SvaExpr>),
BitNot(Box<SvaExpr>),
ReductionAnd(Box<SvaExpr>),
ReductionOr(Box<SvaExpr>),
ReductionXor(Box<SvaExpr>),
BitSelect { signal: Box<SvaExpr>, index: Box<SvaExpr> },
PartSelect { signal: Box<SvaExpr>, high: u32, low: u32 },
Concat(Vec<SvaExpr>),
SequenceAction {
expression: Box<SvaExpr>,
assignments: Vec<(String, Box<SvaExpr>)>,
},
LocalVar(String),
ConstCast(Box<SvaExpr>),
Clocked { clock: String, edge: ClockEdge, body: Box<SvaExpr> },
ArrayMap { array: Box<SvaExpr>, iterator: String, with_expr: Box<SvaExpr> },
TypeThis,
RealConst(f64),
}
#[derive(Debug, Clone, PartialEq)]
pub enum ClockEdge {
Posedge,
Negedge,
Edge, }
#[derive(Debug, Clone, PartialEq)]
pub enum ImmediateDeferred {
Observed,
Final,
}
#[derive(Debug, Clone, PartialEq)]
pub enum SvaDirectiveKind {
Assert,
Assume,
Cover,
CoverSequence,
Restrict,
}
#[derive(Debug, Clone)]
pub struct SvaDirective {
pub kind: SvaDirectiveKind,
pub property: SvaExpr,
pub label: Option<String>,
pub clock: Option<String>,
pub disable_iff: Option<SvaExpr>,
pub action_pass: Option<String>,
pub action_fail: Option<String>,
}
#[derive(Debug, Clone, PartialEq)]
pub enum SvaPortType {
Untyped,
Bit,
Sequence,
Property,
}
#[derive(Debug, Clone)]
pub struct SvaPort {
pub name: String,
pub port_type: SvaPortType,
pub default: Option<SvaExpr>,
}
#[derive(Debug, Clone)]
pub struct SequenceDecl {
pub name: String,
pub ports: Vec<SvaPort>,
pub body: SvaExpr,
}
#[derive(Debug, Clone)]
pub struct PropertyDecl {
pub name: String,
pub ports: Vec<SvaPort>,
pub body: SvaExpr,
}
#[derive(Debug, Clone)]
pub struct LetDecl {
pub name: String,
pub ports: Vec<SvaPort>,
pub body: SvaExpr,
}
#[derive(Debug, Clone, PartialEq)]
pub enum DistKind {
PerValue,
PerRange,
}
#[derive(Debug, Clone)]
pub struct DistItem {
pub min: u64,
pub max: Option<u64>,
pub weight: u64,
pub kind: DistKind,
}
#[derive(Debug, Clone)]
pub struct CheckerDecl {
pub name: String,
pub ports: Vec<SvaPort>,
pub rand_vars: Vec<RandVar>,
pub assertions: Vec<SvaDirective>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum RandVarType {
BitVec(u32),
Real,
}
#[derive(Debug, Clone)]
pub struct RandVar {
pub name: String,
pub var_type: RandVarType,
pub is_const: bool,
}
pub fn resolve_sequence_instance(
decls: &[SequenceDecl],
name: &str,
args: &[SvaExpr],
) -> Result<SvaExpr, SvaParseError> {
let decl = decls.iter().find(|d| d.name == name).ok_or_else(|| SvaParseError {
message: format!("undeclared sequence: '{}'", name),
})?;
let expected = decl.ports.len();
let provided = args.len();
let min_args = decl.ports.iter().filter(|p| p.default.is_none()).count();
if provided < min_args || provided > expected {
return Err(SvaParseError {
message: format!(
"sequence '{}' expects {}-{} arguments, got {}",
name, min_args, expected, provided
),
});
}
let mut result = decl.body.clone();
for (i, port) in decl.ports.iter().enumerate() {
let actual = if i < args.len() {
args[i].clone()
} else if let Some(ref default) = port.default {
default.clone()
} else {
return Err(SvaParseError {
message: format!("missing argument for port '{}' in sequence '{}'", port.name, name),
});
};
result = substitute_signal(&result, &port.name, &actual);
}
Ok(result)
}
fn substitute_signal(expr: &SvaExpr, name: &str, replacement: &SvaExpr) -> SvaExpr {
let sub = |e: &SvaExpr| Box::new(substitute_signal(e, name, replacement));
let sub_vec = |v: &[SvaExpr]| v.iter().map(|e| substitute_signal(e, name, replacement)).collect::<Vec<_>>();
match expr {
SvaExpr::Signal(s) if s == name => replacement.clone(),
SvaExpr::Signal(_) | SvaExpr::Const(_, _) => expr.clone(),
SvaExpr::LocalVar(s) if s == name => replacement.clone(),
SvaExpr::LocalVar(_) => expr.clone(),
SvaExpr::Triggered(s) => SvaExpr::Triggered(s.clone()),
SvaExpr::Matched(s) => SvaExpr::Matched(s.clone()),
SvaExpr::EnumLiteral { type_name, value } => SvaExpr::EnumLiteral {
type_name: type_name.clone(), value: value.clone(),
},
SvaExpr::Rose(inner) => SvaExpr::Rose(sub(inner)),
SvaExpr::Fell(inner) => SvaExpr::Fell(sub(inner)),
SvaExpr::Not(inner) => SvaExpr::Not(sub(inner)),
SvaExpr::Stable(inner) => SvaExpr::Stable(sub(inner)),
SvaExpr::Changed(inner) => SvaExpr::Changed(sub(inner)),
SvaExpr::SEventually(inner) => SvaExpr::SEventually(sub(inner)),
SvaExpr::SAlways(inner) => SvaExpr::SAlways(sub(inner)),
SvaExpr::Always(inner) => SvaExpr::Always(sub(inner)),
SvaExpr::FirstMatch(inner) => SvaExpr::FirstMatch(sub(inner)),
SvaExpr::Strong(inner) => SvaExpr::Strong(sub(inner)),
SvaExpr::Weak(inner) => SvaExpr::Weak(sub(inner)),
SvaExpr::PropertyNot(inner) => SvaExpr::PropertyNot(sub(inner)),
SvaExpr::OneHot0(inner) => SvaExpr::OneHot0(sub(inner)),
SvaExpr::OneHot(inner) => SvaExpr::OneHot(sub(inner)),
SvaExpr::CountOnes(inner) => SvaExpr::CountOnes(sub(inner)),
SvaExpr::IsUnknown(inner) => SvaExpr::IsUnknown(sub(inner)),
SvaExpr::Sampled(inner) => SvaExpr::Sampled(sub(inner)),
SvaExpr::Bits(inner) => SvaExpr::Bits(sub(inner)),
SvaExpr::Clog2(inner) => SvaExpr::Clog2(sub(inner)),
SvaExpr::IsUnbounded(inner) => SvaExpr::IsUnbounded(sub(inner)),
SvaExpr::BitNot(inner) => SvaExpr::BitNot(sub(inner)),
SvaExpr::ReductionAnd(inner) => SvaExpr::ReductionAnd(sub(inner)),
SvaExpr::ReductionOr(inner) => SvaExpr::ReductionOr(sub(inner)),
SvaExpr::ReductionXor(inner) => SvaExpr::ReductionXor(sub(inner)),
SvaExpr::ConstCast(inner) => SvaExpr::ConstCast(sub(inner)),
SvaExpr::Past(inner, n) => SvaExpr::Past(sub(inner), *n),
SvaExpr::Nexttime(inner, n) => SvaExpr::Nexttime(sub(inner), *n),
SvaExpr::SNexttime(inner, n) => SvaExpr::SNexttime(sub(inner), *n),
SvaExpr::CountBits(inner, chars) => SvaExpr::CountBits(sub(inner), chars.clone()),
SvaExpr::GotoRepetition { body, count } => SvaExpr::GotoRepetition {
body: sub(body), count: *count,
},
SvaExpr::And(l, r) => SvaExpr::And(sub(l), sub(r)),
SvaExpr::Or(l, r) => SvaExpr::Or(sub(l), sub(r)),
SvaExpr::Eq(l, r) => SvaExpr::Eq(sub(l), sub(r)),
SvaExpr::NotEq(l, r) => SvaExpr::NotEq(sub(l), sub(r)),
SvaExpr::LessThan(l, r) => SvaExpr::LessThan(sub(l), sub(r)),
SvaExpr::GreaterThan(l, r) => SvaExpr::GreaterThan(sub(l), sub(r)),
SvaExpr::LessEqual(l, r) => SvaExpr::LessEqual(sub(l), sub(r)),
SvaExpr::GreaterEqual(l, r) => SvaExpr::GreaterEqual(sub(l), sub(r)),
SvaExpr::PropertyImplies(l, r) => SvaExpr::PropertyImplies(sub(l), sub(r)),
SvaExpr::PropertyIff(l, r) => SvaExpr::PropertyIff(sub(l), sub(r)),
SvaExpr::SequenceAnd(l, r) => SvaExpr::SequenceAnd(sub(l), sub(r)),
SvaExpr::SequenceOr(l, r) => SvaExpr::SequenceOr(sub(l), sub(r)),
SvaExpr::BitAnd(l, r) => SvaExpr::BitAnd(sub(l), sub(r)),
SvaExpr::BitOr(l, r) => SvaExpr::BitOr(sub(l), sub(r)),
SvaExpr::BitXor(l, r) => SvaExpr::BitXor(sub(l), sub(r)),
SvaExpr::Implication { antecedent, consequent, overlapping } => SvaExpr::Implication {
antecedent: sub(antecedent), consequent: sub(consequent), overlapping: *overlapping,
},
SvaExpr::Delay { body, min, max } => SvaExpr::Delay {
body: sub(body), min: *min, max: *max,
},
SvaExpr::Repetition { body, min, max } => SvaExpr::Repetition {
body: sub(body), min: *min, max: *max,
},
SvaExpr::NonConsecRepetition { body, min, max } => SvaExpr::NonConsecRepetition {
body: sub(body), min: *min, max: *max,
},
SvaExpr::DisableIff { condition, body } => SvaExpr::DisableIff {
condition: sub(condition), body: sub(body),
},
SvaExpr::IfElse { condition, then_expr, else_expr } => SvaExpr::IfElse {
condition: sub(condition), then_expr: sub(then_expr), else_expr: sub(else_expr),
},
SvaExpr::Ternary { condition, then_expr, else_expr } => SvaExpr::Ternary {
condition: sub(condition), then_expr: sub(then_expr), else_expr: sub(else_expr),
},
SvaExpr::Throughout { signal, sequence } => SvaExpr::Throughout {
signal: sub(signal), sequence: sub(sequence),
},
SvaExpr::Within { inner, outer } => SvaExpr::Within {
inner: sub(inner), outer: sub(outer),
},
SvaExpr::Intersect { left, right } => SvaExpr::Intersect {
left: sub(left), right: sub(right),
},
SvaExpr::AcceptOn { condition, body } => SvaExpr::AcceptOn {
condition: sub(condition), body: sub(body),
},
SvaExpr::RejectOn { condition, body } => SvaExpr::RejectOn {
condition: sub(condition), body: sub(body),
},
SvaExpr::SyncAcceptOn { condition, body } => SvaExpr::SyncAcceptOn {
condition: sub(condition), body: sub(body),
},
SvaExpr::SyncRejectOn { condition, body } => SvaExpr::SyncRejectOn {
condition: sub(condition), body: sub(body),
},
SvaExpr::FollowedBy { antecedent, consequent, overlapping } => SvaExpr::FollowedBy {
antecedent: sub(antecedent), consequent: sub(consequent), overlapping: *overlapping,
},
SvaExpr::Until { lhs, rhs, strong, inclusive } => SvaExpr::Until {
lhs: sub(lhs), rhs: sub(rhs), strong: *strong, inclusive: *inclusive,
},
SvaExpr::AlwaysBounded { body, min, max } => SvaExpr::AlwaysBounded {
body: sub(body), min: *min, max: *max,
},
SvaExpr::SAlwaysBounded { body, min, max } => SvaExpr::SAlwaysBounded {
body: sub(body), min: *min, max: *max,
},
SvaExpr::EventuallyBounded { body, min, max } => SvaExpr::EventuallyBounded {
body: sub(body), min: *min, max: *max,
},
SvaExpr::SEventuallyBounded { body, min, max } => SvaExpr::SEventuallyBounded {
body: sub(body), min: *min, max: *max,
},
SvaExpr::FieldAccess { signal, field } => SvaExpr::FieldAccess {
signal: sub(signal), field: field.clone(),
},
SvaExpr::BitSelect { signal, index } => SvaExpr::BitSelect {
signal: sub(signal), index: sub(index),
},
SvaExpr::PartSelect { signal, high, low } => SvaExpr::PartSelect {
signal: sub(signal), high: *high, low: *low,
},
SvaExpr::ImmediateAssert { expression, deferred } => SvaExpr::ImmediateAssert {
expression: sub(expression), deferred: deferred.clone(),
},
SvaExpr::Concat(items) => SvaExpr::Concat(sub_vec(items)),
SvaExpr::PropertyCase { expression, items, default } => SvaExpr::PropertyCase {
expression: sub(expression),
items: items.iter().map(|(vals, prop)| {
(sub_vec(vals), sub(prop))
}).collect(),
default: default.as_ref().map(|d| sub(d)),
},
SvaExpr::SequenceAction { expression, assignments } => SvaExpr::SequenceAction {
expression: sub(expression),
assignments: assignments.iter().map(|(var_name, rhs)| {
(var_name.clone(), sub(rhs))
}).collect(),
},
SvaExpr::Clocked { clock, edge, body } => SvaExpr::Clocked {
clock: clock.clone(),
edge: edge.clone(),
body: sub(body),
},
SvaExpr::ArrayMap { array, iterator, with_expr } => SvaExpr::ArrayMap {
array: sub(array),
iterator: iterator.clone(),
with_expr: sub(with_expr),
},
SvaExpr::TypeThis => SvaExpr::TypeThis,
SvaExpr::RealConst(v) => SvaExpr::RealConst(*v),
}
}
#[derive(Debug)]
pub struct SvaParseError {
pub message: String,
}
impl std::fmt::Display for SvaParseError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "SVA parse error: {}", self.message)
}
}
pub fn parse_sva_directive(input: &str) -> Result<SvaDirective, SvaParseError> {
let input = input.trim().trim_end_matches(';');
let input = input.trim();
let (label, rest) = if let Some(colon_pos) = input.find(':') {
let potential_label = input[..colon_pos].trim();
if !potential_label.is_empty()
&& potential_label.chars().all(|c| c.is_alphanumeric() || c == '_')
&& !potential_label.starts_with("assert")
&& !potential_label.starts_with("assume")
&& !potential_label.starts_with("cover")
&& !potential_label.starts_with("restrict")
{
(Some(potential_label.to_string()), input[colon_pos + 1..].trim())
} else {
(None, input)
}
} else {
(None, input)
};
let (kind, after_kind) = if rest.starts_with("assert property") {
(SvaDirectiveKind::Assert, rest["assert property".len()..].trim())
} else if rest.starts_with("assume property") {
(SvaDirectiveKind::Assume, rest["assume property".len()..].trim())
} else if rest.starts_with("cover sequence") {
(SvaDirectiveKind::CoverSequence, rest["cover sequence".len()..].trim())
} else if rest.starts_with("cover property") {
(SvaDirectiveKind::Cover, rest["cover property".len()..].trim())
} else if rest.starts_with("restrict property") {
(SvaDirectiveKind::Restrict, rest["restrict property".len()..].trim())
} else {
return Err(SvaParseError {
message: format!("expected assertion directive, got: '{}'", rest),
});
};
if !after_kind.starts_with('(') {
return Err(SvaParseError {
message: format!("expected '(' after directive keyword, got: '{}'", after_kind),
});
}
let close = find_balanced_close(after_kind, 0).ok_or_else(|| SvaParseError {
message: "unbalanced parentheses in directive".to_string(),
})?;
let prop_str = &after_kind[1..close];
let after_prop = after_kind[close + 1..].trim();
let (clock, prop_rest) = if prop_str.trim().starts_with("@(") {
if let Some(clock_close) = prop_str.find(')') {
let clock_str = prop_str[2..clock_close].trim().to_string();
(Some(clock_str), prop_str[clock_close + 1..].trim())
} else {
(None, prop_str.trim())
}
} else {
(None, prop_str.trim())
};
let (disable_iff, prop_body) = if prop_rest.starts_with("disable iff") {
let after_disable = prop_rest["disable iff".len()..].trim();
if after_disable.starts_with('(') {
if let Some(di_close) = find_balanced_close(after_disable, 0) {
let di_str = &after_disable[1..di_close];
let body = after_disable[di_close + 1..].trim();
(Some(parse_sva(di_str)?), body)
} else {
(None, prop_rest)
}
} else {
(None, prop_rest)
}
} else {
(None, prop_rest)
};
let property = parse_sva(prop_body)?;
let mut action_pass = None;
let mut action_fail = None;
let remaining = after_prop;
if !remaining.is_empty() {
if let Some(else_pos) = find_else_outside_strings(remaining) {
let pass_part = remaining[..else_pos].trim();
let fail_part = remaining[else_pos + 4..].trim();
if !pass_part.is_empty() {
action_pass = Some(pass_part.trim_end_matches(';').trim().to_string());
}
if !fail_part.is_empty() {
action_fail = Some(fail_part.trim_end_matches(';').trim().to_string());
}
} else if remaining.starts_with("$") || remaining.starts_with("else") {
let part = remaining.trim_end_matches(';').trim();
if remaining.starts_with("else") {
action_fail = Some(part["else".len()..].trim().to_string());
} else {
action_pass = Some(part.to_string());
}
}
}
if kind == SvaDirectiveKind::Restrict && (action_pass.is_some() || action_fail.is_some()) {
return Err(SvaParseError {
message: "restrict property cannot have action blocks".to_string(),
});
}
Ok(SvaDirective {
kind,
property,
label,
clock,
disable_iff,
action_pass,
action_fail,
})
}
pub fn parse_sva(input: &str) -> Result<SvaExpr, SvaParseError> {
let input = input.trim();
if input.starts_with("@(") {
if let Some(pos) = input.find(')') {
let clock_spec = input[2..pos].trim();
let rest = input[pos + 1..].trim();
let (edge, clock_name) = if clock_spec.starts_with("posedge ") {
(ClockEdge::Posedge, clock_spec[8..].trim().to_string())
} else if clock_spec.starts_with("negedge ") {
(ClockEdge::Negedge, clock_spec[8..].trim().to_string())
} else if clock_spec.starts_with("edge ") {
(ClockEdge::Edge, clock_spec[5..].trim().to_string())
} else {
(ClockEdge::Posedge, clock_spec.to_string())
};
let body = parse_toplevel(rest)?;
return Ok(SvaExpr::Clocked {
clock: clock_name,
edge,
body: Box::new(body),
});
}
}
parse_toplevel(input)
}
fn parse_toplevel(input: &str) -> Result<SvaExpr, SvaParseError> {
let input = input.trim();
if input.starts_with("assert ") || input.starts_with("assert(") || input.starts_with("assert#") {
let rest = input["assert".len()..].trim();
if rest.starts_with("#0") {
let body = rest[2..].trim();
if body.starts_with('(') {
if let Some(close) = find_balanced_close(body, 0) {
let inner = &body[1..close];
return Ok(SvaExpr::ImmediateAssert {
expression: Box::new(parse_implication(inner)?),
deferred: Some(ImmediateDeferred::Observed),
});
}
}
} else if rest.starts_with("final") {
let body = rest[5..].trim();
if body.starts_with('(') {
if let Some(close) = find_balanced_close(body, 0) {
let inner = &body[1..close];
return Ok(SvaExpr::ImmediateAssert {
expression: Box::new(parse_implication(inner)?),
deferred: Some(ImmediateDeferred::Final),
});
}
}
} else if rest.starts_with('(') {
if let Some(close) = find_balanced_close(rest, 0) {
let inner = &rest[1..close];
return Ok(SvaExpr::ImmediateAssert {
expression: Box::new(parse_implication(inner)?),
deferred: None,
});
}
}
}
if input.starts_with("sync_accept_on") {
let rest = input["sync_accept_on".len()..].trim();
if rest.starts_with('(') {
if let Some(close) = find_balanced_close(rest, 0) {
let cond = &rest[1..close];
let body = rest[close + 1..].trim();
return Ok(SvaExpr::SyncAcceptOn {
condition: Box::new(parse_implication(cond)?),
body: Box::new(parse_toplevel(body)?),
});
}
}
}
if input.starts_with("sync_reject_on") {
let rest = input["sync_reject_on".len()..].trim();
if rest.starts_with('(') {
if let Some(close) = find_balanced_close(rest, 0) {
let cond = &rest[1..close];
let body = rest[close + 1..].trim();
return Ok(SvaExpr::SyncRejectOn {
condition: Box::new(parse_implication(cond)?),
body: Box::new(parse_toplevel(body)?),
});
}
}
}
if input.starts_with("accept_on") {
let rest = input["accept_on".len()..].trim();
if rest.starts_with('(') {
if let Some(close) = find_balanced_close(rest, 0) {
let cond = &rest[1..close];
let body = rest[close + 1..].trim();
return Ok(SvaExpr::AcceptOn {
condition: Box::new(parse_implication(cond)?),
body: Box::new(parse_toplevel(body)?),
});
}
}
}
if input.starts_with("reject_on") {
let rest = input["reject_on".len()..].trim();
if rest.starts_with('(') {
if let Some(close) = find_balanced_close(rest, 0) {
let cond = &rest[1..close];
let body = rest[close + 1..].trim();
return Ok(SvaExpr::RejectOn {
condition: Box::new(parse_implication(cond)?),
body: Box::new(parse_toplevel(body)?),
});
}
}
}
if input.starts_with("disable iff") {
let rest = input["disable iff".len()..].trim();
if rest.starts_with('(') {
if let Some(close) = find_balanced_close(rest, 0) {
let cond = &rest[1..close];
let body = rest[close + 1..].trim();
return Ok(SvaExpr::DisableIff {
condition: Box::new(parse_implication(cond)?),
body: Box::new(parse_implication(body)?),
});
}
}
}
if input.starts_with("if ") || input.starts_with("if(") {
let rest = input[2..].trim();
if rest.starts_with('(') {
if let Some(close) = find_balanced_close(rest, 0) {
let cond = &rest[1..close];
let after_cond = rest[close + 1..].trim();
if let Some(else_pos) = find_else_keyword(after_cond) {
let then_part = after_cond[..else_pos].trim();
let else_part = after_cond[else_pos + 4..].trim();
return Ok(SvaExpr::IfElse {
condition: Box::new(parse_implication(cond)?),
then_expr: Box::new(parse_implication(then_part)?),
else_expr: Box::new(parse_implication(else_part)?),
});
} else {
return Ok(SvaExpr::IfElse {
condition: Box::new(parse_implication(cond)?),
then_expr: Box::new(parse_implication(after_cond)?),
else_expr: Box::new(SvaExpr::Signal("1".to_string())),
});
}
}
}
}
parse_implication(input)
}
fn parse_implication(input: &str) -> Result<SvaExpr, SvaParseError> {
let mut depth = 0i32;
let chars: Vec<char> = input.chars().collect();
for i in 0..chars.len().saturating_sub(2) {
match chars[i] {
'(' => depth += 1,
')' => depth -= 1,
'|' if depth == 0 => {
if i + 2 < chars.len() && chars[i + 1] == '-' && chars[i + 2] == '>' {
let lhs = input[..i].trim();
let rhs = input[i + 3..].trim();
return Ok(SvaExpr::Implication {
antecedent: Box::new(parse_property_implies(lhs)?),
consequent: Box::new(parse_property_implies(rhs)?),
overlapping: true,
});
}
if i + 2 < chars.len() && chars[i + 1] == '=' && chars[i + 2] == '>' {
let lhs = input[..i].trim();
let rhs = input[i + 3..].trim();
return Ok(SvaExpr::Implication {
antecedent: Box::new(parse_property_implies(lhs)?),
consequent: Box::new(parse_property_implies(rhs)?),
overlapping: false,
});
}
}
'#' if depth == 0 && i + 2 < chars.len() => {
if chars[i + 1] == '-' && chars[i + 2] == '#' {
let lhs = input[..i].trim();
let rhs = input[i + 3..].trim();
if !lhs.is_empty() {
return Ok(SvaExpr::FollowedBy {
antecedent: Box::new(parse_property_implies(lhs)?),
consequent: Box::new(parse_property_implies(rhs)?),
overlapping: true,
});
}
}
if chars[i + 1] == '=' && chars[i + 2] == '#' {
let lhs = input[..i].trim();
let rhs = input[i + 3..].trim();
if !lhs.is_empty() {
return Ok(SvaExpr::FollowedBy {
antecedent: Box::new(parse_property_implies(lhs)?),
consequent: Box::new(parse_property_implies(rhs)?),
overlapping: false,
});
}
}
}
_ => {}
}
}
parse_property_implies(input)
}
fn parse_property_implies(input: &str) -> Result<SvaExpr, SvaParseError> {
let input = input.trim();
if let Some(pos) = find_keyword_at_depth_0(input, "implies") {
let lhs = input[..pos].trim();
let rhs = input[pos + 7..].trim();
return Ok(SvaExpr::PropertyImplies(
Box::new(parse_property_iff(lhs)?),
Box::new(parse_property_implies(rhs)?), ));
}
parse_property_iff(input)
}
fn parse_property_iff(input: &str) -> Result<SvaExpr, SvaParseError> {
let input = input.trim();
if let Some(pos) = find_keyword_at_depth_0(input, "iff") {
let lhs = input[..pos].trim();
let rhs = input[pos + 3..].trim();
return Ok(SvaExpr::PropertyIff(
Box::new(parse_until(lhs)?),
Box::new(parse_property_iff(rhs)?), ));
}
parse_until(input)
}
fn parse_until(input: &str) -> Result<SvaExpr, SvaParseError> {
let input = input.trim();
for (keyword, strong, inclusive) in &[
("s_until_with", true, true),
("until_with", false, true),
("s_until", true, false),
("until", false, false),
] {
if let Some(pos) = find_keyword_at_depth_0(input, keyword) {
let lhs = input[..pos].trim();
let rhs = input[pos + keyword.len()..].trim();
return Ok(SvaExpr::Until {
lhs: Box::new(parse_or(lhs)?),
rhs: Box::new(parse_until(rhs)?), strong: *strong,
inclusive: *inclusive,
});
}
}
parse_or(input)
}
fn parse_or(input: &str) -> Result<SvaExpr, SvaParseError> {
let mut depth = 0i32;
let chars: Vec<char> = input.chars().collect();
for i in 0..chars.len().saturating_sub(1) {
match chars[i] {
'(' => depth += 1,
')' => depth -= 1,
'|' if depth == 0 && i + 1 < chars.len() && chars[i + 1] == '|' => {
let lhs = input[..i].trim();
let rhs = input[i + 2..].trim();
return Ok(SvaExpr::Or(
Box::new(parse_seq_ops(lhs)?),
Box::new(parse_or(rhs)?),
));
}
_ => {}
}
}
parse_seq_ops(input)
}
fn parse_seq_ops(input: &str) -> Result<SvaExpr, SvaParseError> {
let input_trimmed = input.trim();
for keyword in &["throughout", "within", "intersect"] {
if let Some(pos) = find_keyword_at_depth_0(input_trimmed, keyword) {
let lhs = input_trimmed[..pos].trim();
let rhs = input_trimmed[pos + keyword.len()..].trim();
return match *keyword {
"throughout" => Ok(SvaExpr::Throughout {
signal: Box::new(parse_and(lhs)?),
sequence: Box::new(parse_and(rhs)?),
}),
"within" => Ok(SvaExpr::Within {
inner: Box::new(parse_and(lhs)?),
outer: Box::new(parse_and(rhs)?),
}),
"intersect" => Ok(SvaExpr::Intersect {
left: Box::new(parse_and(lhs)?),
right: Box::new(parse_and(rhs)?),
}),
_ => unreachable!(),
};
}
}
if let Some(pos) = find_keyword_at_depth_0(input_trimmed, "or") {
let lhs = input_trimmed[..pos].trim();
let rhs = input_trimmed[pos + 2..].trim();
return Ok(SvaExpr::SequenceOr(
Box::new(parse_seq_and(lhs)?),
Box::new(parse_seq_ops(rhs)?), ));
}
parse_seq_and(input)
}
fn parse_seq_and(input: &str) -> Result<SvaExpr, SvaParseError> {
let input_trimmed = input.trim();
if let Some(pos) = find_keyword_at_depth_0(input_trimmed, "and") {
let lhs = input_trimmed[..pos].trim();
let rhs = input_trimmed[pos + 3..].trim();
return Ok(SvaExpr::SequenceAnd(
Box::new(parse_and(lhs)?),
Box::new(parse_seq_and(rhs)?), ));
}
parse_and(input)
}
fn find_keyword_at_depth_0(input: &str, keyword: &str) -> Option<usize> {
let mut depth = 0i32;
let bytes = input.as_bytes();
let klen = keyword.len();
for i in 0..input.len() {
match bytes[i] {
b'(' => depth += 1,
b')' => depth -= 1,
_ if depth == 0 && i + klen <= input.len() => {
if &input[i..i + klen] == keyword {
let before_ok = i == 0 || !bytes[i - 1].is_ascii_alphanumeric();
let after_ok = i + klen >= input.len() || !bytes[i + klen].is_ascii_alphanumeric();
if before_ok && after_ok {
return Some(i);
}
}
}
_ => {}
}
}
None
}
fn parse_and(input: &str) -> Result<SvaExpr, SvaParseError> {
let mut depth = 0i32;
let chars: Vec<char> = input.chars().collect();
for i in 0..chars.len().saturating_sub(1) {
match chars[i] {
'(' => depth += 1,
')' => depth -= 1,
'&' if depth == 0 && i + 1 < chars.len() && chars[i + 1] == '&' => {
let lhs = input[..i].trim();
let rhs = input[i + 2..].trim();
return Ok(SvaExpr::And(
Box::new(parse_sequence(lhs)?),
Box::new(parse_and(rhs)?),
));
}
_ => {}
}
}
parse_sequence(input)
}
fn parse_sequence(input: &str) -> Result<SvaExpr, SvaParseError> {
let input = input.trim();
let bytes = input.as_bytes();
let mut depth = 0i32;
for i in 0..input.len().saturating_sub(1) {
match bytes[i] {
b'(' => { depth += 1; continue; }
b')' => { depth -= 1; continue; }
b'#' if depth == 0 && i > 0 && i + 1 < input.len() && bytes[i + 1] == b'#' => {
let lhs = input[..i].trim();
if lhs.is_empty() { continue; }
let delay_and_rhs = &input[i..]; let rest = &delay_and_rhs[2..];
if rest.starts_with('[') {
if let Some(bracket_end) = rest.find(']') {
let range_str = &rest[1..bracket_end];
let rhs = rest[bracket_end + 1..].trim();
let parts: Vec<&str> = range_str.split(':').collect();
if parts.len() == 2 {
let min = parts[0].trim().parse::<u32>().unwrap_or(0);
let max_str = parts[1].trim();
let max = if max_str == "$" {
None } else {
Some(max_str.parse::<u32>().unwrap_or(0))
};
return Ok(SvaExpr::Implication {
antecedent: Box::new(parse_eq(lhs)?),
consequent: Box::new(SvaExpr::Delay {
body: Box::new(parse_sequence(rhs)?),
min,
max,
}),
overlapping: true,
});
}
}
} else {
let mut num_end = 0;
for c in rest.chars() {
if c.is_ascii_digit() { num_end += 1; } else { break; }
}
if num_end > 0 {
let n = rest[..num_end].parse::<u32>().unwrap_or(0);
let rhs = rest[num_end..].trim();
return Ok(SvaExpr::Implication {
antecedent: Box::new(parse_eq(lhs)?),
consequent: Box::new(SvaExpr::Delay {
body: Box::new(parse_sequence(rhs)?),
min: n,
max: Some(n), }),
overlapping: true,
});
}
}
}
_ => {}
}
}
parse_eq(input)
}
fn parse_eq(input: &str) -> Result<SvaExpr, SvaParseError> {
let mut depth = 0i32;
let chars: Vec<char> = input.chars().collect();
let len = chars.len();
for i in 0..len {
match chars[i] {
'(' => depth += 1,
')' => depth -= 1,
'?' if depth == 0 => {
let cond = input[..i].trim();
let rest = &input[i + 1..];
let mut d2 = 0i32;
for j in 0..rest.len() {
match rest.as_bytes()[j] {
b'(' => d2 += 1,
b')' => d2 -= 1,
b':' if d2 == 0 => {
let then_part = rest[..j].trim();
let else_part = rest[j + 1..].trim();
return Ok(SvaExpr::Ternary {
condition: Box::new(parse_eq(cond)?),
then_expr: Box::new(parse_eq(then_part)?),
else_expr: Box::new(parse_eq(else_part)?),
});
}
_ => {}
}
}
}
_ => {}
}
}
depth = 0;
for i in 0..len {
match chars[i] {
'(' | '[' => depth += 1,
')' | ']' => depth -= 1,
_ if depth != 0 => {}
'!' if i + 1 < len && chars[i + 1] == '=' => {
let lhs = input[..i].trim();
let rhs = input[i + 2..].trim();
return Ok(SvaExpr::NotEq(
Box::new(parse_unary(lhs)?),
Box::new(parse_unary(rhs)?),
));
}
'=' if i + 1 < len && chars[i + 1] == '=' => {
let lhs = input[..i].trim();
let rhs = input[i + 2..].trim();
return Ok(SvaExpr::Eq(
Box::new(parse_unary(lhs)?),
Box::new(parse_unary(rhs)?),
));
}
'<' if i + 1 < len && chars[i + 1] == '=' => {
let lhs = input[..i].trim();
let rhs = input[i + 2..].trim();
return Ok(SvaExpr::LessEqual(
Box::new(parse_unary(lhs)?),
Box::new(parse_unary(rhs)?),
));
}
'>' if i + 1 < len && chars[i + 1] == '=' => {
let lhs = input[..i].trim();
let rhs = input[i + 2..].trim();
return Ok(SvaExpr::GreaterEqual(
Box::new(parse_unary(lhs)?),
Box::new(parse_unary(rhs)?),
));
}
'<' if depth == 0 => {
let lhs = input[..i].trim();
let rhs = input[i + 1..].trim();
return Ok(SvaExpr::LessThan(
Box::new(parse_unary(lhs)?),
Box::new(parse_unary(rhs)?),
));
}
'>' if depth == 0 => {
let lhs = input[..i].trim();
let rhs = input[i + 1..].trim();
return Ok(SvaExpr::GreaterThan(
Box::new(parse_unary(lhs)?),
Box::new(parse_unary(rhs)?),
));
}
_ => {}
}
}
parse_unary(input)
}
fn parse_unary(input: &str) -> Result<SvaExpr, SvaParseError> {
let input = input.trim();
if let Some(result) = try_parse_function_call(input, "strong", |inner| {
Ok(SvaExpr::Strong(Box::new(parse_implication(inner)?)))
})? { return Ok(result); }
if let Some(result) = try_parse_function_call(input, "weak", |inner| {
Ok(SvaExpr::Weak(Box::new(parse_implication(inner)?)))
})? { return Ok(result); }
if input.starts_with("s_nexttime[") {
if let Some(bracket_end) = input.find(']') {
let n_str = &input[11..bracket_end];
if let Ok(n) = n_str.parse::<u32>() {
let rest = input[bracket_end + 1..].trim();
if rest.starts_with('(') {
if let Some(close) = find_balanced_close(rest, 0) {
let inner = &rest[1..close];
return Ok(SvaExpr::SNexttime(
Box::new(parse_implication(inner.trim())?),
n,
));
}
}
}
}
}
if let Some(result) = try_parse_function_call(input, "s_nexttime", |inner| {
Ok(SvaExpr::SNexttime(Box::new(parse_implication(inner)?), 1))
})? { return Ok(result); }
if input.starts_with("not ") || input.starts_with("not(") {
let rest = input[3..].trim();
return Ok(SvaExpr::PropertyNot(Box::new(parse_unary(rest)?)));
}
if input.starts_with("always [") || input.starts_with("always[") {
let rest = input["always".len()..].trim();
if rest.starts_with('[') {
if let Some(bracket_end) = rest.find(']') {
let range_str = &rest[1..bracket_end];
let body_str = rest[bracket_end + 1..].trim();
let parts: Vec<&str> = range_str.split(':').collect();
if parts.len() == 2 {
let min = parts[0].trim().parse::<u32>().map_err(|_| SvaParseError {
message: format!("invalid always min: '{}'", parts[0]),
})?;
let max_str = parts[1].trim();
let max = if max_str == "$" {
None } else {
Some(max_str.parse::<u32>().map_err(|_| SvaParseError {
message: format!("invalid always max: '{}'", max_str),
})?)
};
return Ok(SvaExpr::AlwaysBounded {
body: Box::new(parse_unary(body_str)?),
min,
max,
});
}
}
}
}
if input.starts_with("always ") || input.starts_with("always(") {
let rest = input["always".len()..].trim();
return Ok(SvaExpr::Always(Box::new(parse_unary(rest)?)));
}
if input.starts_with("s_always [") || input.starts_with("s_always[") {
let rest = input["s_always".len()..].trim();
if rest.starts_with('[') {
if let Some(bracket_end) = rest.find(']') {
let range_str = &rest[1..bracket_end];
let body_str = rest[bracket_end + 1..].trim();
let parts: Vec<&str> = range_str.split(':').collect();
if parts.len() == 2 {
let min = parts[0].trim().parse::<u32>().map_err(|_| SvaParseError {
message: format!("invalid s_always min: '{}'", parts[0]),
})?;
let max_str = parts[1].trim();
if max_str == "$" {
return Err(SvaParseError {
message: "s_always range must be bounded ($ not allowed)".to_string(),
});
}
let max = max_str.parse::<u32>().map_err(|_| SvaParseError {
message: format!("invalid s_always max: '{}'", max_str),
})?;
return Ok(SvaExpr::SAlwaysBounded {
body: Box::new(parse_unary(body_str)?),
min,
max,
});
}
}
}
}
if input.starts_with("eventually [") || input.starts_with("eventually[") {
let rest = input["eventually".len()..].trim();
if rest.starts_with('[') {
if let Some(bracket_end) = rest.find(']') {
let range_str = &rest[1..bracket_end];
let body_str = rest[bracket_end + 1..].trim();
let parts: Vec<&str> = range_str.split(':').collect();
if parts.len() == 2 {
let min = parts[0].trim().parse::<u32>().map_err(|_| SvaParseError {
message: format!("invalid eventually min: '{}'", parts[0]),
})?;
let max_str = parts[1].trim();
if max_str == "$" {
return Err(SvaParseError {
message: "weak eventually range must be bounded ($ not allowed)".to_string(),
});
}
let max = max_str.parse::<u32>().map_err(|_| SvaParseError {
message: format!("invalid eventually max: '{}'", max_str),
})?;
return Ok(SvaExpr::EventuallyBounded {
body: Box::new(parse_unary(body_str)?),
min,
max,
});
}
}
}
}
if input.starts_with("s_eventually [") || input.starts_with("s_eventually[") {
let rest = input["s_eventually".len()..].trim();
if rest.starts_with('[') {
if let Some(bracket_end) = rest.find(']') {
let range_str = &rest[1..bracket_end];
let body_str = rest[bracket_end + 1..].trim();
let parts: Vec<&str> = range_str.split(':').collect();
if parts.len() == 2 {
let min = parts[0].trim().parse::<u32>().map_err(|_| SvaParseError {
message: format!("invalid s_eventually min: '{}'", parts[0]),
})?;
let max_str = parts[1].trim();
let max = if max_str == "$" {
None } else {
Some(max_str.parse::<u32>().map_err(|_| SvaParseError {
message: format!("invalid s_eventually max: '{}'", max_str),
})?)
};
return Ok(SvaExpr::SEventuallyBounded {
body: Box::new(parse_unary(body_str)?),
min,
max,
});
}
}
}
}
if input.starts_with("##") {
let rest = &input[2..];
if rest.starts_with('[') {
if rest.starts_with("[*]") {
let body_str = rest[3..].trim();
return Ok(SvaExpr::Delay {
body: Box::new(parse_unary(body_str)?),
min: 0,
max: None, });
}
if rest.starts_with("[+]") {
let body_str = rest[3..].trim();
return Ok(SvaExpr::Delay {
body: Box::new(parse_unary(body_str)?),
min: 1,
max: None, });
}
if let Some(bracket_end) = rest.find(']') {
let range_str = &rest[1..bracket_end];
let body_str = rest[bracket_end + 1..].trim();
let parts: Vec<&str> = range_str.split(':').collect();
if parts.len() == 2 {
let min = parts[0].trim().parse::<u32>().map_err(|_| SvaParseError {
message: format!("invalid delay min: '{}'", parts[0]),
})?;
let max_str = parts[1].trim();
let max = if max_str == "$" {
None } else {
Some(max_str.parse::<u32>().map_err(|_| SvaParseError {
message: format!("invalid delay max: '{}'", max_str),
})?)
};
return Ok(SvaExpr::Delay {
body: Box::new(parse_unary(body_str)?),
min,
max,
});
}
}
} else {
let mut num_end = 0;
for c in rest.chars() {
if c.is_ascii_digit() {
num_end += 1;
} else {
break;
}
}
if num_end > 0 {
let n = rest[..num_end].parse::<u32>().map_err(|_| SvaParseError {
message: format!("invalid delay number: '{}'", &rest[..num_end]),
})?;
let body_str = rest[num_end..].trim();
return Ok(SvaExpr::Delay {
body: Box::new(parse_unary(body_str)?),
min: n,
max: Some(n), });
}
}
}
if input.starts_with('!') {
let inner = input[1..].trim();
let inner = strip_parens(inner);
return Ok(SvaExpr::Not(Box::new(parse_implication(inner)?)));
}
if let Some(result) = try_parse_function_call(input, "$onehot0", |inner| {
Ok(SvaExpr::OneHot0(Box::new(parse_implication(inner)?)))
})? { return Ok(result); }
if let Some(result) = try_parse_function_call(input, "$onehot", |inner| {
Ok(SvaExpr::OneHot(Box::new(parse_implication(inner)?)))
})? { return Ok(result); }
if let Some(result) = try_parse_function_call(input, "$countones", |inner| {
Ok(SvaExpr::CountOnes(Box::new(parse_implication(inner)?)))
})? { return Ok(result); }
if let Some(result) = try_parse_function_call(input, "$isunknown", |inner| {
Ok(SvaExpr::IsUnknown(Box::new(parse_implication(inner)?)))
})? { return Ok(result); }
if let Some(result) = try_parse_function_call(input, "$sampled", |inner| {
Ok(SvaExpr::Sampled(Box::new(parse_implication(inner)?)))
})? { return Ok(result); }
if let Some(result) = try_parse_function_call(input, "$bits", |inner| {
Ok(SvaExpr::Bits(Box::new(parse_implication(inner)?)))
})? { return Ok(result); }
if let Some(result) = try_parse_function_call(input, "$clog2", |inner| {
Ok(SvaExpr::Clog2(Box::new(parse_implication(inner)?)))
})? { return Ok(result); }
if input.starts_with("$countbits(") {
if let Some(close) = find_balanced_close(input, "$countbits".len()) {
let inner = &input["$countbits".len() + 1..close];
let parts: Vec<&str> = inner.split(',').collect();
if !parts.is_empty() {
let sig = parse_implication(parts[0].trim())?;
let mut control_chars = Vec::new();
for part in &parts[1..] {
let trimmed = part.trim().trim_matches('\'');
if let Some(c) = trimmed.chars().next() {
control_chars.push(c);
}
}
return Ok(SvaExpr::CountBits(Box::new(sig), control_chars));
}
}
}
if let Some(result) = try_parse_function_call(input, "$isunbounded", |inner| {
Ok(SvaExpr::IsUnbounded(Box::new(parse_implication(inner)?)))
})? { return Ok(result); }
if let Some(result) = try_parse_function_call(input, "$rose", |inner| {
Ok(SvaExpr::Rose(Box::new(parse_implication(inner)?)))
})? { return Ok(result); }
if let Some(result) = try_parse_function_call(input, "$fell", |inner| {
Ok(SvaExpr::Fell(Box::new(parse_implication(inner)?)))
})? { return Ok(result); }
if let Some(result) = try_parse_function_call(input, "$stable", |inner| {
Ok(SvaExpr::Stable(Box::new(parse_implication(inner)?)))
})? { return Ok(result); }
if let Some(result) = try_parse_function_call(input, "$changed", |inner| {
Ok(SvaExpr::Changed(Box::new(parse_implication(inner)?)))
})? { return Ok(result); }
if let Some(result) = try_parse_function_call(input, "s_eventually", |inner| {
Ok(SvaExpr::SEventually(Box::new(parse_implication(inner)?)))
})? { return Ok(result); }
if let Some(result) = try_parse_function_call(input, "s_always", |inner| {
Ok(SvaExpr::SAlways(Box::new(parse_implication(inner)?)))
})? { return Ok(result); }
if input.starts_with("nexttime[") {
if let Some(bracket_end) = input.find(']') {
let n_str = &input[9..bracket_end];
if let Ok(n) = n_str.parse::<u32>() {
let rest = input[bracket_end + 1..].trim();
if rest.starts_with('(') {
if let Some(close) = find_balanced_close(rest, 0) {
let inner = &rest[1..close];
return Ok(SvaExpr::Nexttime(
Box::new(parse_implication(inner.trim())?),
n,
));
}
}
}
}
}
if let Some(result) = try_parse_function_call(input, "nexttime", |inner| {
Ok(SvaExpr::Nexttime(Box::new(parse_implication(inner)?), 1))
})? { return Ok(result); }
if let Some(result) = try_parse_function_call(input, "$nexttime", |inner| {
Ok(SvaExpr::Nexttime(Box::new(parse_implication(inner)?), 1))
})? { return Ok(result); }
if let Some(result) = try_parse_function_call(input, "first_match", |inner| {
Ok(SvaExpr::FirstMatch(Box::new(parse_implication(inner)?)))
})? { return Ok(result); }
if let Some(result) = try_parse_function_call(input, "$past", |inner| {
if let Some(comma) = inner.find(',') {
let sig = inner[..comma].trim();
let n_str = inner[comma + 1..].trim();
let n = n_str.parse::<u32>().unwrap_or(1);
Ok(SvaExpr::Past(Box::new(parse_atom(sig)?), n))
} else {
Ok(SvaExpr::Past(Box::new(parse_atom(inner)?), 1))
}
})? { return Ok(result); }
if input.starts_with('(') && input.ends_with(')') {
return parse_implication(&input[1..input.len() - 1]);
}
parse_atom(input)
}
fn find_else_outside_strings(input: &str) -> Option<usize> {
let bytes = input.as_bytes();
let len = bytes.len();
let mut i = 0;
while i < len {
if i + 2 < len && bytes[i] == b'"' && bytes[i + 1] == b'"' && bytes[i + 2] == b'"' {
i += 3; while i < len {
if bytes[i] == b'\\' && i + 1 < len {
i += 2; } else if i + 2 < len && bytes[i] == b'"' && bytes[i + 1] == b'"' && bytes[i + 2] == b'"' {
i += 3; break;
} else {
i += 1;
}
}
continue;
}
if bytes[i] == b'"' {
i += 1; while i < len {
if bytes[i] == b'\\' && i + 1 < len {
i += 2; } else if bytes[i] == b'"' {
i += 1; break;
} else {
i += 1;
}
}
continue;
}
if i + 4 <= len && &input[i..i + 4] == "else" {
let before_ok = i == 0 || !bytes[i - 1].is_ascii_alphanumeric();
let after_ok = i + 4 >= len || !bytes[i + 4].is_ascii_alphanumeric();
if before_ok && after_ok {
return Some(i);
}
}
i += 1;
}
None
}
fn find_balanced_close(input: &str, start: usize) -> Option<usize> {
let chars: Vec<char> = input.chars().collect();
let mut depth = 0i32;
for i in start..chars.len() {
match chars[i] {
'(' => depth += 1,
')' => {
depth -= 1;
if depth == 0 {
return Some(i);
}
}
_ => {}
}
}
None
}
fn try_parse_function_call<F>(
input: &str,
prefix: &str,
parse_inner: F,
) -> Result<Option<SvaExpr>, SvaParseError>
where
F: FnOnce(&str) -> Result<SvaExpr, SvaParseError>,
{
let full_prefix = format!("{}(", prefix);
if !input.starts_with(&full_prefix) {
return Ok(None);
}
let paren_start = full_prefix.len() - 1; if let Some(close) = find_balanced_close(input, paren_start) {
let inner = &input[full_prefix.len()..close];
let remaining = input[close + 1..].trim();
if remaining.is_empty() {
return Ok(Some(parse_inner(inner.trim())?));
}
return Ok(None);
}
Err(SvaParseError {
message: format!("unbalanced parens in {}", prefix),
})
}
fn find_else_keyword(input: &str) -> Option<usize> {
let mut depth = 0i32;
let bytes = input.as_bytes();
for i in 0..input.len().saturating_sub(3) {
match bytes[i] {
b'(' => depth += 1,
b')' => depth -= 1,
b'e' if depth == 0 => {
if input[i..].starts_with("else") {
let before_ok = i == 0 || !bytes[i - 1].is_ascii_alphanumeric();
let after_ok = i + 4 >= input.len() || !bytes[i + 4].is_ascii_alphanumeric();
if before_ok && after_ok {
return Some(i);
}
}
}
_ => {}
}
}
None
}
fn parse_atom(input: &str) -> Result<SvaExpr, SvaParseError> {
let input = input.trim();
if input.is_empty() {
return Err(SvaParseError {
message: "empty expression".to_string(),
});
}
if let Some(bracket_pos) = input.find("[+]") {
let signal_part = input[..bracket_pos].trim();
let body = parse_atom(signal_part)?;
return Ok(SvaExpr::Repetition {
body: Box::new(body),
min: 1,
max: None,
});
}
if let Some(bracket_pos) = input.find("[*") {
let signal_part = input[..bracket_pos].trim();
let rep_part = &input[bracket_pos + 2..];
if let Some(close_bracket) = rep_part.find(']') {
let range_str = &rep_part[..close_bracket].trim();
if range_str.is_empty() {
let body = parse_atom(signal_part)?;
return Ok(SvaExpr::Repetition {
body: Box::new(body),
min: 0,
max: None,
});
}
let body = parse_atom(signal_part)?;
if let Some(colon) = range_str.find(':') {
let min_str = range_str[..colon].trim();
let max_str = range_str[colon + 1..].trim();
let min = min_str.parse::<u32>().map_err(|_| SvaParseError {
message: format!("invalid repetition min: '{}'", min_str),
})?;
let max = if max_str == "$" {
None
} else {
Some(max_str.parse::<u32>().map_err(|_| SvaParseError {
message: format!("invalid repetition max: '{}'", max_str),
})?)
};
return Ok(SvaExpr::Repetition {
body: Box::new(body),
min,
max,
});
} else {
let n = range_str.trim().parse::<u32>().map_err(|_| SvaParseError {
message: format!("invalid repetition count: '{}'", range_str),
})?;
return Ok(SvaExpr::Repetition {
body: Box::new(body),
min: n,
max: Some(n),
});
}
}
}
if let Some(bracket_pos) = input.find("[->") {
let signal_part = input[..bracket_pos].trim();
let rep_part = &input[bracket_pos + 3..];
if let Some(close_bracket) = rep_part.find(']') {
let count_str = rep_part[..close_bracket].trim();
let count = count_str.parse::<u32>().map_err(|_| SvaParseError {
message: format!("invalid goto repetition count: '{}'", count_str),
})?;
return Ok(SvaExpr::GotoRepetition {
body: Box::new(parse_atom(signal_part)?),
count,
});
}
}
if let Some(bracket_pos) = input.find("[=") {
let signal_part = input[..bracket_pos].trim();
let rep_part = &input[bracket_pos + 2..];
if let Some(close_bracket) = rep_part.find(']') {
let range_str = &rep_part[..close_bracket];
let body = parse_atom(signal_part)?;
if let Some(colon) = range_str.find(':') {
let min = range_str[..colon].trim().parse::<u32>().map_err(|_| SvaParseError {
message: format!("invalid non-consec repetition min: '{}'", &range_str[..colon]),
})?;
let max_str = range_str[colon + 1..].trim();
let max = if max_str == "$" {
None
} else {
Some(max_str.parse::<u32>().map_err(|_| SvaParseError {
message: format!("invalid non-consec repetition max: '{}'", max_str),
})?)
};
return Ok(SvaExpr::NonConsecRepetition {
body: Box::new(body),
min,
max,
});
} else {
let n = range_str.trim().parse::<u32>().map_err(|_| SvaParseError {
message: format!("invalid non-consec repetition count: '{}'", range_str),
})?;
return Ok(SvaExpr::NonConsecRepetition {
body: Box::new(body),
min: n,
max: Some(n),
});
}
}
}
if (input.contains('.') || input.contains('e') || input.contains('E'))
&& input.chars().next().map_or(false, |c| c.is_ascii_digit())
{
if let Ok(v) = input.parse::<f64>() {
return Ok(SvaExpr::RealConst(v));
}
}
if let Ok(n) = input.parse::<u64>() {
return Ok(SvaExpr::Const(n, 32));
}
if let Some(tick_pos) = input.find('\'') {
let width_str = &input[..tick_pos];
let rest = &input[tick_pos + 1..];
if let Ok(width) = width_str.parse::<u32>() {
let (radix, value_str) = if rest.starts_with('d') || rest.starts_with('D') {
(10, &rest[1..])
} else if rest.starts_with('h') || rest.starts_with('H') {
(16, &rest[1..])
} else if rest.starts_with('b') || rest.starts_with('B') {
(2, &rest[1..])
} else if rest.starts_with('o') || rest.starts_with('O') {
(8, &rest[1..])
} else {
(10, rest)
};
if let Ok(value) = u64::from_str_radix(value_str, radix) {
return Ok(SvaExpr::Const(value, width));
}
}
}
if input == "type(this)" {
return Ok(SvaExpr::TypeThis);
}
if let Some(dot_map_pos) = input.find(".map(") {
let array_part = &input[..dot_map_pos].trim();
let after_map = &input[dot_map_pos + 5..]; if let Some(iter_close) = after_map.find(')') {
let iter_part = after_map[..iter_close].trim();
let iterator = if iter_part.is_empty() {
"item".to_string() } else {
iter_part.split(',').next().unwrap_or("item").trim().to_string()
};
let after_iter_close = after_map[iter_close + 1..].trim();
if after_iter_close.starts_with("with") {
let with_body = after_iter_close["with".len()..].trim();
let with_expr_str = strip_parens(with_body);
let array_expr = parse_atom(array_part)?;
let with_expr = parse_sva(with_expr_str)?;
return Ok(SvaExpr::ArrayMap {
array: Box::new(array_expr),
iterator,
with_expr: Box::new(with_expr),
});
}
}
}
if input
.chars()
.all(|c| c.is_alphanumeric() || c == '_')
{
return Ok(SvaExpr::Signal(input.to_string()));
}
Err(SvaParseError {
message: format!("unexpected token: '{}'", input),
})
}
pub fn sva_expr_to_string(expr: &SvaExpr) -> String {
match expr {
SvaExpr::Signal(name) => name.clone(),
SvaExpr::Const(value, width) => format!("{}'d{}", width, value),
SvaExpr::Rose(inner) => format!("$rose({})", sva_expr_to_string(inner)),
SvaExpr::Fell(inner) => format!("$fell({})", sva_expr_to_string(inner)),
SvaExpr::Past(inner, n) => format!("$past({}, {})", sva_expr_to_string(inner), n),
SvaExpr::And(left, right) => {
format!("({} && {})", sva_expr_to_string(left), sva_expr_to_string(right))
}
SvaExpr::Or(left, right) => {
format!("({} || {})", sva_expr_to_string(left), sva_expr_to_string(right))
}
SvaExpr::Not(inner) => format!("!({})", sva_expr_to_string(inner)),
SvaExpr::Eq(left, right) => {
format!("({} == {})", sva_expr_to_string(left), sva_expr_to_string(right))
}
SvaExpr::Implication {
antecedent,
consequent,
overlapping,
} => {
let op = if *overlapping { "|->" } else { "|=>" };
format!(
"{} {} {}",
sva_expr_to_string(antecedent),
op,
sva_expr_to_string(consequent)
)
}
SvaExpr::Delay { body, min, max } => match (min, max) {
(0, None) => format!("##[*] {}", sva_expr_to_string(body)),
(1, None) => format!("##[+] {}", sva_expr_to_string(body)),
(_, None) => format!("##[{}:$] {}", min, sva_expr_to_string(body)),
(_, Some(max_val)) if min == max_val => format!("##{} {}", min, sva_expr_to_string(body)),
(_, Some(max_val)) => format!("##[{}:{}] {}", min, max_val, sva_expr_to_string(body)),
},
SvaExpr::Repetition { body, min, max } => {
let body_str = sva_expr_to_string(body);
match (min, max) {
(0, None) => format!("{}[*]", body_str),
(1, None) => format!("{}[+]", body_str),
(_, Some(m)) if *m == *min => format!("{}[*{}]", body_str, min),
(_, Some(m)) => format!("{}[*{}:{}]", body_str, min, m),
(_, None) => format!("{}[*{}:$]", body_str, min),
}
}
SvaExpr::SEventually(inner) => format!("s_eventually({})", sva_expr_to_string(inner)),
SvaExpr::SAlways(inner) => format!("s_always({})", sva_expr_to_string(inner)),
SvaExpr::Stable(inner) => format!("$stable({})", sva_expr_to_string(inner)),
SvaExpr::Changed(inner) => format!("$changed({})", sva_expr_to_string(inner)),
SvaExpr::Nexttime(inner, n) => {
if *n == 1 {
format!("nexttime({})", sva_expr_to_string(inner))
} else {
format!("nexttime[{}]({})", n, sva_expr_to_string(inner))
}
}
SvaExpr::DisableIff { condition, body } => {
format!("disable iff ({}) {}", sva_expr_to_string(condition), sva_expr_to_string(body))
}
SvaExpr::IfElse { condition, then_expr, else_expr } => {
format!(
"if ({}) {} else {}",
sva_expr_to_string(condition),
sva_expr_to_string(then_expr),
sva_expr_to_string(else_expr),
)
}
SvaExpr::NotEq(l, r) => format!("({} != {})", sva_expr_to_string(l), sva_expr_to_string(r)),
SvaExpr::LessThan(l, r) => format!("({} < {})", sva_expr_to_string(l), sva_expr_to_string(r)),
SvaExpr::GreaterThan(l, r) => format!("({} > {})", sva_expr_to_string(l), sva_expr_to_string(r)),
SvaExpr::LessEqual(l, r) => format!("({} <= {})", sva_expr_to_string(l), sva_expr_to_string(r)),
SvaExpr::GreaterEqual(l, r) => format!("({} >= {})", sva_expr_to_string(l), sva_expr_to_string(r)),
SvaExpr::Ternary { condition, then_expr, else_expr } => {
format!("{} ? {} : {}",
sva_expr_to_string(condition),
sva_expr_to_string(then_expr),
sva_expr_to_string(else_expr),
)
}
SvaExpr::Throughout { signal, sequence } => {
format!("{} throughout ({})",
sva_expr_to_string(signal),
sva_expr_to_string(sequence),
)
}
SvaExpr::Within { inner, outer } => {
format!("({}) within ({})",
sva_expr_to_string(inner),
sva_expr_to_string(outer),
)
}
SvaExpr::FirstMatch(inner) => format!("first_match({})", sva_expr_to_string(inner)),
SvaExpr::Intersect { left, right } => {
format!("({}) intersect ({})",
sva_expr_to_string(left),
sva_expr_to_string(right),
)
}
SvaExpr::OneHot0(inner) => format!("$onehot0({})", sva_expr_to_string(inner)),
SvaExpr::OneHot(inner) => format!("$onehot({})", sva_expr_to_string(inner)),
SvaExpr::CountOnes(inner) => format!("$countones({})", sva_expr_to_string(inner)),
SvaExpr::IsUnknown(inner) => format!("$isunknown({})", sva_expr_to_string(inner)),
SvaExpr::Sampled(inner) => format!("$sampled({})", sva_expr_to_string(inner)),
SvaExpr::Bits(inner) => format!("$bits({})", sva_expr_to_string(inner)),
SvaExpr::Clog2(inner) => format!("$clog2({})", sva_expr_to_string(inner)),
SvaExpr::CountBits(inner, chars) => {
let char_args: Vec<String> = chars.iter().map(|c| format!("'{}'", c)).collect();
format!("$countbits({}, {})", sva_expr_to_string(inner), char_args.join(", "))
}
SvaExpr::IsUnbounded(inner) => format!("$isunbounded({})", sva_expr_to_string(inner)),
SvaExpr::GotoRepetition { body, count } => {
format!("{}[->{}]", sva_expr_to_string(body), count)
}
SvaExpr::NonConsecRepetition { body, min, max } => {
let body_str = sva_expr_to_string(body);
match max {
Some(m) if *m == *min => format!("{}[={}]", body_str, min),
Some(m) => format!("{}[={}:{}]", body_str, min, m),
None => format!("{}[={}:$]", body_str, min),
}
}
SvaExpr::AcceptOn { condition, body } => {
format!("accept_on({}) {}", sva_expr_to_string(condition), sva_expr_to_string(body))
}
SvaExpr::RejectOn { condition, body } => {
format!("reject_on({}) {}", sva_expr_to_string(condition), sva_expr_to_string(body))
}
SvaExpr::PropertyNot(inner) => format!("not {}", sva_expr_to_string(inner)),
SvaExpr::PropertyImplies(l, r) => {
format!("{} implies {}", sva_expr_to_string(l), sva_expr_to_string(r))
}
SvaExpr::PropertyIff(l, r) => {
format!("{} iff {}", sva_expr_to_string(l), sva_expr_to_string(r))
}
SvaExpr::Always(inner) => format!("always({})", sva_expr_to_string(inner)),
SvaExpr::AlwaysBounded { body, min, max } => match max {
Some(m) => format!("always [{}:{}] {}", min, m, sva_expr_to_string(body)),
None => format!("always [{}:$] {}", min, sva_expr_to_string(body)),
},
SvaExpr::SAlwaysBounded { body, min, max } => {
format!("s_always [{}:{}] {}", min, max, sva_expr_to_string(body))
}
SvaExpr::EventuallyBounded { body, min, max } => {
format!("eventually [{}:{}] {}", min, max, sva_expr_to_string(body))
}
SvaExpr::SEventuallyBounded { body, min, max } => match max {
Some(m) => format!("s_eventually [{}:{}] {}", min, m, sva_expr_to_string(body)),
None => format!("s_eventually [{}:$] {}", min, sva_expr_to_string(body)),
},
SvaExpr::Until { lhs, rhs, strong, inclusive } => {
let op = match (strong, inclusive) {
(false, false) => "until",
(true, false) => "s_until",
(false, true) => "until_with",
(true, true) => "s_until_with",
};
format!("{} {} {}", sva_expr_to_string(lhs), op, sva_expr_to_string(rhs))
}
SvaExpr::Strong(inner) => format!("strong({})", sva_expr_to_string(inner)),
SvaExpr::Weak(inner) => format!("weak({})", sva_expr_to_string(inner)),
SvaExpr::SNexttime(inner, n) => {
if *n == 1 {
format!("s_nexttime({})", sva_expr_to_string(inner))
} else {
format!("s_nexttime[{}]({})", n, sva_expr_to_string(inner))
}
}
SvaExpr::FollowedBy { antecedent, consequent, overlapping } => {
let op = if *overlapping { "#-#" } else { "#=#" };
format!("{} {} {}", sva_expr_to_string(antecedent), op, sva_expr_to_string(consequent))
}
SvaExpr::PropertyCase { expression, items, default } => {
let mut s = format!("case({})", sva_expr_to_string(expression));
for (vals, prop) in items {
let vs: Vec<String> = vals.iter().map(sva_expr_to_string).collect();
s.push_str(&format!(" {}: {};", vs.join(", "), sva_expr_to_string(prop)));
}
if let Some(d) = default {
s.push_str(&format!(" default: {};", sva_expr_to_string(d)));
}
s.push_str(" endcase");
s
}
SvaExpr::SyncAcceptOn { condition, body } => {
format!("sync_accept_on({}) {}", sva_expr_to_string(condition), sva_expr_to_string(body))
}
SvaExpr::SyncRejectOn { condition, body } => {
format!("sync_reject_on({}) {}", sva_expr_to_string(condition), sva_expr_to_string(body))
}
SvaExpr::SequenceAnd(l, r) => {
format!("({}) and ({})", sva_expr_to_string(l), sva_expr_to_string(r))
}
SvaExpr::SequenceOr(l, r) => {
format!("({}) or ({})", sva_expr_to_string(l), sva_expr_to_string(r))
}
SvaExpr::ImmediateAssert { expression, deferred } => {
match deferred {
None => format!("assert({})", sva_expr_to_string(expression)),
Some(ImmediateDeferred::Observed) => format!("assert #0({})", sva_expr_to_string(expression)),
Some(ImmediateDeferred::Final) => format!("assert final({})", sva_expr_to_string(expression)),
}
}
SvaExpr::FieldAccess { signal, field } => format!("{}.{}", sva_expr_to_string(signal), field),
SvaExpr::EnumLiteral { type_name: Some(t), value } => format!("{}::{}", t, value),
SvaExpr::EnumLiteral { type_name: None, value } => value.clone(),
SvaExpr::Triggered(name) => format!("{}.triggered", name),
SvaExpr::Matched(name) => format!("{}.matched", name),
SvaExpr::BitAnd(l, r) => format!("({} & {})", sva_expr_to_string(l), sva_expr_to_string(r)),
SvaExpr::BitOr(l, r) => format!("({} | {})", sva_expr_to_string(l), sva_expr_to_string(r)),
SvaExpr::BitXor(l, r) => format!("({} ^ {})", sva_expr_to_string(l), sva_expr_to_string(r)),
SvaExpr::BitNot(inner) => format!("~{}", sva_expr_to_string(inner)),
SvaExpr::ReductionAnd(inner) => format!("&{}", sva_expr_to_string(inner)),
SvaExpr::ReductionOr(inner) => format!("|{}", sva_expr_to_string(inner)),
SvaExpr::ReductionXor(inner) => format!("^{}", sva_expr_to_string(inner)),
SvaExpr::BitSelect { signal, index } => format!("{}[{}]", sva_expr_to_string(signal), sva_expr_to_string(index)),
SvaExpr::PartSelect { signal, high, low } => format!("{}[{}:{}]", sva_expr_to_string(signal), high, low),
SvaExpr::Concat(items) => {
let parts: Vec<String> = items.iter().map(sva_expr_to_string).collect();
format!("{{{}}}", parts.join(", "))
}
SvaExpr::SequenceAction { expression, assignments } => {
let assigns: Vec<String> = assignments.iter()
.map(|(name, rhs)| format!("{} = {}", name, sva_expr_to_string(rhs)))
.collect();
format!("({}, {})", sva_expr_to_string(expression), assigns.join(", "))
}
SvaExpr::LocalVar(name) => name.clone(),
SvaExpr::ConstCast(inner) => format!("const'({})", sva_expr_to_string(inner)),
SvaExpr::Clocked { clock, edge, body } => {
let edge_str = match edge {
ClockEdge::Posedge => "posedge",
ClockEdge::Negedge => "negedge",
ClockEdge::Edge => "edge",
};
format!("@({} {}) {}", edge_str, clock, sva_expr_to_string(body))
}
SvaExpr::ArrayMap { array, iterator, with_expr } => {
format!("{}.map({}) with ({})", sva_expr_to_string(array), iterator, sva_expr_to_string(with_expr))
}
SvaExpr::TypeThis => "type(this)".to_string(),
SvaExpr::RealConst(v) => format!("{}", v),
}
}
fn strip_parens(input: &str) -> &str {
let input = input.trim();
if input.starts_with('(') && input.ends_with(')') {
&input[1..input.len() - 1]
} else {
input
}
}
pub fn sva_exprs_structurally_equivalent(a: &SvaExpr, b: &SvaExpr) -> bool {
match (a, b) {
(SvaExpr::Signal(sa), SvaExpr::Signal(sb)) => sa == sb,
(SvaExpr::Const(va, wa), SvaExpr::Const(vb, wb)) => va == vb && wa == wb,
(SvaExpr::Rose(ia), SvaExpr::Rose(ib)) => sva_exprs_structurally_equivalent(ia, ib),
(SvaExpr::Fell(ia), SvaExpr::Fell(ib)) => sva_exprs_structurally_equivalent(ia, ib),
(SvaExpr::Past(ia, na), SvaExpr::Past(ib, nb)) => {
na == nb && sva_exprs_structurally_equivalent(ia, ib)
}
(SvaExpr::And(la, ra), SvaExpr::And(lb, rb)) => {
sva_exprs_structurally_equivalent(la, lb)
&& sva_exprs_structurally_equivalent(ra, rb)
}
(SvaExpr::Or(la, ra), SvaExpr::Or(lb, rb)) => {
sva_exprs_structurally_equivalent(la, lb)
&& sva_exprs_structurally_equivalent(ra, rb)
}
(SvaExpr::Not(ia), SvaExpr::Not(ib)) => sva_exprs_structurally_equivalent(ia, ib),
(SvaExpr::Eq(la, ra), SvaExpr::Eq(lb, rb)) => {
sva_exprs_structurally_equivalent(la, lb)
&& sva_exprs_structurally_equivalent(ra, rb)
}
(
SvaExpr::Implication {
antecedent: aa,
consequent: ca,
overlapping: oa,
},
SvaExpr::Implication {
antecedent: ab,
consequent: cb,
overlapping: ob,
},
) => {
oa == ob
&& sva_exprs_structurally_equivalent(aa, ab)
&& sva_exprs_structurally_equivalent(ca, cb)
}
(
SvaExpr::Delay {
body: ba,
min: mna,
max: mxa,
},
SvaExpr::Delay {
body: bb,
min: mnb,
max: mxb,
},
) => mna == mnb && mxa == mxb && sva_exprs_structurally_equivalent(ba, bb),
(
SvaExpr::Repetition { body: ba, min: mna, max: mxa },
SvaExpr::Repetition { body: bb, min: mnb, max: mxb },
) => mna == mnb && mxa == mxb && sva_exprs_structurally_equivalent(ba, bb),
(SvaExpr::SEventually(ia), SvaExpr::SEventually(ib)) => {
sva_exprs_structurally_equivalent(ia, ib)
}
(SvaExpr::SAlways(ia), SvaExpr::SAlways(ib)) => {
sva_exprs_structurally_equivalent(ia, ib)
}
(SvaExpr::Stable(ia), SvaExpr::Stable(ib)) => sva_exprs_structurally_equivalent(ia, ib),
(SvaExpr::Changed(ia), SvaExpr::Changed(ib)) => sva_exprs_structurally_equivalent(ia, ib),
(SvaExpr::Nexttime(ia, na), SvaExpr::Nexttime(ib, nb)) => {
na == nb && sva_exprs_structurally_equivalent(ia, ib)
}
(
SvaExpr::DisableIff { condition: ca, body: ba },
SvaExpr::DisableIff { condition: cb, body: bb },
) => {
sva_exprs_structurally_equivalent(ca, cb)
&& sva_exprs_structurally_equivalent(ba, bb)
}
(
SvaExpr::IfElse { condition: ca, then_expr: ta, else_expr: ea },
SvaExpr::IfElse { condition: cb, then_expr: tb, else_expr: eb },
) => {
sva_exprs_structurally_equivalent(ca, cb)
&& sva_exprs_structurally_equivalent(ta, tb)
&& sva_exprs_structurally_equivalent(ea, eb)
}
(SvaExpr::NotEq(la, ra), SvaExpr::NotEq(lb, rb)) => {
sva_exprs_structurally_equivalent(la, lb) && sva_exprs_structurally_equivalent(ra, rb)
}
(SvaExpr::LessThan(la, ra), SvaExpr::LessThan(lb, rb)) => {
sva_exprs_structurally_equivalent(la, lb) && sva_exprs_structurally_equivalent(ra, rb)
}
(SvaExpr::GreaterThan(la, ra), SvaExpr::GreaterThan(lb, rb)) => {
sva_exprs_structurally_equivalent(la, lb) && sva_exprs_structurally_equivalent(ra, rb)
}
(SvaExpr::LessEqual(la, ra), SvaExpr::LessEqual(lb, rb)) => {
sva_exprs_structurally_equivalent(la, lb) && sva_exprs_structurally_equivalent(ra, rb)
}
(SvaExpr::GreaterEqual(la, ra), SvaExpr::GreaterEqual(lb, rb)) => {
sva_exprs_structurally_equivalent(la, lb) && sva_exprs_structurally_equivalent(ra, rb)
}
(
SvaExpr::Ternary { condition: ca, then_expr: ta, else_expr: ea },
SvaExpr::Ternary { condition: cb, then_expr: tb, else_expr: eb },
) => {
sva_exprs_structurally_equivalent(ca, cb)
&& sva_exprs_structurally_equivalent(ta, tb)
&& sva_exprs_structurally_equivalent(ea, eb)
}
(
SvaExpr::Throughout { signal: sa, sequence: qa },
SvaExpr::Throughout { signal: sb, sequence: qb },
) => {
sva_exprs_structurally_equivalent(sa, sb) && sva_exprs_structurally_equivalent(qa, qb)
}
(
SvaExpr::Within { inner: ia, outer: oa },
SvaExpr::Within { inner: ib, outer: ob },
) => {
sva_exprs_structurally_equivalent(ia, ib) && sva_exprs_structurally_equivalent(oa, ob)
}
(SvaExpr::FirstMatch(ia), SvaExpr::FirstMatch(ib)) => {
sva_exprs_structurally_equivalent(ia, ib)
}
(
SvaExpr::Intersect { left: la, right: ra },
SvaExpr::Intersect { left: lb, right: rb },
) => {
sva_exprs_structurally_equivalent(la, lb) && sva_exprs_structurally_equivalent(ra, rb)
}
(SvaExpr::OneHot0(ia), SvaExpr::OneHot0(ib)) => sva_exprs_structurally_equivalent(ia, ib),
(SvaExpr::OneHot(ia), SvaExpr::OneHot(ib)) => sva_exprs_structurally_equivalent(ia, ib),
(SvaExpr::CountOnes(ia), SvaExpr::CountOnes(ib)) => sva_exprs_structurally_equivalent(ia, ib),
(SvaExpr::IsUnknown(ia), SvaExpr::IsUnknown(ib)) => sva_exprs_structurally_equivalent(ia, ib),
(SvaExpr::Sampled(ia), SvaExpr::Sampled(ib)) => sva_exprs_structurally_equivalent(ia, ib),
(SvaExpr::Bits(ia), SvaExpr::Bits(ib)) => sva_exprs_structurally_equivalent(ia, ib),
(SvaExpr::Clog2(ia), SvaExpr::Clog2(ib)) => sva_exprs_structurally_equivalent(ia, ib),
(SvaExpr::CountBits(ia, ca), SvaExpr::CountBits(ib, cb)) =>
ca == cb && sva_exprs_structurally_equivalent(ia, ib),
(SvaExpr::IsUnbounded(ia), SvaExpr::IsUnbounded(ib)) => sva_exprs_structurally_equivalent(ia, ib),
(
SvaExpr::GotoRepetition { body: ba, count: ca },
SvaExpr::GotoRepetition { body: bb, count: cb },
) => ca == cb && sva_exprs_structurally_equivalent(ba, bb),
(
SvaExpr::NonConsecRepetition { body: ba, min: mna, max: mxa },
SvaExpr::NonConsecRepetition { body: bb, min: mnb, max: mxb },
) => mna == mnb && mxa == mxb && sva_exprs_structurally_equivalent(ba, bb),
(
SvaExpr::AcceptOn { condition: ca, body: ba },
SvaExpr::AcceptOn { condition: cb, body: bb },
) => {
sva_exprs_structurally_equivalent(ca, cb)
&& sva_exprs_structurally_equivalent(ba, bb)
}
(
SvaExpr::RejectOn { condition: ca, body: ba },
SvaExpr::RejectOn { condition: cb, body: bb },
) => {
sva_exprs_structurally_equivalent(ca, cb)
&& sva_exprs_structurally_equivalent(ba, bb)
}
(SvaExpr::PropertyNot(ia), SvaExpr::PropertyNot(ib)) => {
sva_exprs_structurally_equivalent(ia, ib)
}
(SvaExpr::PropertyImplies(la, ra), SvaExpr::PropertyImplies(lb, rb)) => {
sva_exprs_structurally_equivalent(la, lb)
&& sva_exprs_structurally_equivalent(ra, rb)
}
(SvaExpr::PropertyIff(la, ra), SvaExpr::PropertyIff(lb, rb)) => {
sva_exprs_structurally_equivalent(la, lb)
&& sva_exprs_structurally_equivalent(ra, rb)
}
(SvaExpr::Always(ia), SvaExpr::Always(ib)) => sva_exprs_structurally_equivalent(ia, ib),
(
SvaExpr::AlwaysBounded { body: ba, min: mna, max: mxa },
SvaExpr::AlwaysBounded { body: bb, min: mnb, max: mxb },
) => mna == mnb && mxa == mxb && sva_exprs_structurally_equivalent(ba, bb),
(
SvaExpr::SAlwaysBounded { body: ba, min: mna, max: mxa },
SvaExpr::SAlwaysBounded { body: bb, min: mnb, max: mxb },
) => mna == mnb && mxa == mxb && sva_exprs_structurally_equivalent(ba, bb),
(
SvaExpr::EventuallyBounded { body: ba, min: mna, max: mxa },
SvaExpr::EventuallyBounded { body: bb, min: mnb, max: mxb },
) => mna == mnb && mxa == mxb && sva_exprs_structurally_equivalent(ba, bb),
(
SvaExpr::SEventuallyBounded { body: ba, min: mna, max: mxa },
SvaExpr::SEventuallyBounded { body: bb, min: mnb, max: mxb },
) => mna == mnb && mxa == mxb && sva_exprs_structurally_equivalent(ba, bb),
(
SvaExpr::Until { lhs: la, rhs: ra, strong: sa, inclusive: ia },
SvaExpr::Until { lhs: lb, rhs: rb, strong: sb, inclusive: ib },
) => {
sa == sb && ia == ib
&& sva_exprs_structurally_equivalent(la, lb)
&& sva_exprs_structurally_equivalent(ra, rb)
}
(SvaExpr::Strong(ia), SvaExpr::Strong(ib)) => sva_exprs_structurally_equivalent(ia, ib),
(SvaExpr::Weak(ia), SvaExpr::Weak(ib)) => sva_exprs_structurally_equivalent(ia, ib),
(SvaExpr::SNexttime(ia, na), SvaExpr::SNexttime(ib, nb)) => {
na == nb && sva_exprs_structurally_equivalent(ia, ib)
}
(
SvaExpr::FollowedBy { antecedent: aa, consequent: ca, overlapping: oa },
SvaExpr::FollowedBy { antecedent: ab, consequent: cb, overlapping: ob },
) => {
oa == ob
&& sva_exprs_structurally_equivalent(aa, ab)
&& sva_exprs_structurally_equivalent(ca, cb)
}
(
SvaExpr::PropertyCase { expression: ea, items: ia, default: da },
SvaExpr::PropertyCase { expression: eb, items: ib, default: db },
) => {
sva_exprs_structurally_equivalent(ea, eb)
&& ia.len() == ib.len()
&& ia.iter().zip(ib.iter()).all(|((va, pa), (vb, pb))| {
va.len() == vb.len()
&& va.iter().zip(vb.iter()).all(|(a, b)| sva_exprs_structurally_equivalent(a, b))
&& sva_exprs_structurally_equivalent(pa, pb)
})
&& match (da, db) {
(Some(a), Some(b)) => sva_exprs_structurally_equivalent(a, b),
(None, None) => true,
_ => false,
}
}
(
SvaExpr::SyncAcceptOn { condition: ca, body: ba },
SvaExpr::SyncAcceptOn { condition: cb, body: bb },
) => {
sva_exprs_structurally_equivalent(ca, cb)
&& sva_exprs_structurally_equivalent(ba, bb)
}
(
SvaExpr::SyncRejectOn { condition: ca, body: ba },
SvaExpr::SyncRejectOn { condition: cb, body: bb },
) => {
sva_exprs_structurally_equivalent(ca, cb)
&& sva_exprs_structurally_equivalent(ba, bb)
}
(SvaExpr::SequenceAnd(la, ra), SvaExpr::SequenceAnd(lb, rb)) => {
sva_exprs_structurally_equivalent(la, lb)
&& sva_exprs_structurally_equivalent(ra, rb)
}
(SvaExpr::SequenceOr(la, ra), SvaExpr::SequenceOr(lb, rb)) => {
sva_exprs_structurally_equivalent(la, lb)
&& sva_exprs_structurally_equivalent(ra, rb)
}
(
SvaExpr::ImmediateAssert { expression: ea, deferred: da },
SvaExpr::ImmediateAssert { expression: eb, deferred: db },
) => da == db && sva_exprs_structurally_equivalent(ea, eb),
(SvaExpr::FieldAccess { signal: sa, field: fa }, SvaExpr::FieldAccess { signal: sb, field: fb }) => {
fa == fb && sva_exprs_structurally_equivalent(sa, sb)
}
(SvaExpr::EnumLiteral { type_name: ta, value: va }, SvaExpr::EnumLiteral { type_name: tb, value: vb }) => {
ta == tb && va == vb
}
(SvaExpr::Triggered(a), SvaExpr::Triggered(b)) => a == b,
(SvaExpr::Matched(a), SvaExpr::Matched(b)) => a == b,
(SvaExpr::BitAnd(la, ra), SvaExpr::BitAnd(lb, rb)) => {
sva_exprs_structurally_equivalent(la, lb) && sva_exprs_structurally_equivalent(ra, rb)
}
(SvaExpr::BitOr(la, ra), SvaExpr::BitOr(lb, rb)) => {
sva_exprs_structurally_equivalent(la, lb) && sva_exprs_structurally_equivalent(ra, rb)
}
(SvaExpr::BitXor(la, ra), SvaExpr::BitXor(lb, rb)) => {
sva_exprs_structurally_equivalent(la, lb) && sva_exprs_structurally_equivalent(ra, rb)
}
(SvaExpr::BitNot(ia), SvaExpr::BitNot(ib)) => sva_exprs_structurally_equivalent(ia, ib),
(SvaExpr::ReductionAnd(ia), SvaExpr::ReductionAnd(ib)) => sva_exprs_structurally_equivalent(ia, ib),
(SvaExpr::ReductionOr(ia), SvaExpr::ReductionOr(ib)) => sva_exprs_structurally_equivalent(ia, ib),
(SvaExpr::ReductionXor(ia), SvaExpr::ReductionXor(ib)) => sva_exprs_structurally_equivalent(ia, ib),
(SvaExpr::BitSelect { signal: sa, index: ia }, SvaExpr::BitSelect { signal: sb, index: ib }) => {
sva_exprs_structurally_equivalent(sa, sb) && sva_exprs_structurally_equivalent(ia, ib)
}
(SvaExpr::PartSelect { signal: sa, high: ha, low: la }, SvaExpr::PartSelect { signal: sb, high: hb, low: lb }) => {
ha == hb && la == lb && sva_exprs_structurally_equivalent(sa, sb)
}
(SvaExpr::Concat(ia), SvaExpr::Concat(ib)) => {
ia.len() == ib.len() && ia.iter().zip(ib.iter()).all(|(a, b)| sva_exprs_structurally_equivalent(a, b))
}
(SvaExpr::SequenceAction { expression: ea, assignments: aa },
SvaExpr::SequenceAction { expression: eb, assignments: ab }) => {
sva_exprs_structurally_equivalent(ea, eb)
&& aa.len() == ab.len()
&& aa.iter().zip(ab.iter()).all(|((na, ra), (nb, rb))|
na == nb && sva_exprs_structurally_equivalent(ra, rb))
}
(SvaExpr::LocalVar(a), SvaExpr::LocalVar(b)) => a == b,
(SvaExpr::ConstCast(ia), SvaExpr::ConstCast(ib)) => sva_exprs_structurally_equivalent(ia, ib),
(SvaExpr::Clocked { clock: ca, edge: ea, body: ba },
SvaExpr::Clocked { clock: cb, edge: eb, body: bb }) => {
ca == cb && ea == eb && sva_exprs_structurally_equivalent(ba, bb)
}
(SvaExpr::ArrayMap { array: aa, iterator: ia, with_expr: wa },
SvaExpr::ArrayMap { array: ab, iterator: ib, with_expr: wb }) => {
ia == ib && sva_exprs_structurally_equivalent(aa, ab) && sva_exprs_structurally_equivalent(wa, wb)
}
(SvaExpr::TypeThis, SvaExpr::TypeThis) => true,
(SvaExpr::RealConst(a), SvaExpr::RealConst(b)) => a.to_bits() == b.to_bits(),
_ => false,
}
}
#[derive(Debug, Clone, Default)]
pub struct ElaborationContext {
pub default_clocking: Option<String>,
pub default_disable_iff: Option<SvaExpr>,
}
pub fn elaborate_directives(
directives: &[SvaDirective],
ctx: &ElaborationContext,
) -> Vec<SvaDirective> {
directives.iter().map(|d| {
let mut elaborated = d.clone();
if elaborated.clock.is_none() {
if let Some(ref default_clk) = ctx.default_clocking {
elaborated.clock = Some(default_clk.clone());
}
}
if elaborated.disable_iff.is_none() {
if let Some(ref default_dis) = ctx.default_disable_iff {
elaborated.disable_iff = Some(default_dis.clone());
}
}
elaborated
}).collect()
}
pub fn resolve_let_instance(
decls: &[LetDecl],
name: &str,
args: &[SvaExpr],
) -> Result<SvaExpr, SvaParseError> {
let decl = decls.iter().find(|d| d.name == name).ok_or_else(|| SvaParseError {
message: format!("undeclared let: '{}'", name),
})?;
let required_count = decl.ports.iter().filter(|p| p.default.is_none()).count();
if args.len() < required_count || args.len() > decl.ports.len() {
return Err(SvaParseError {
message: format!(
"let '{}' expects {} to {} arguments, got {}",
name, required_count, decl.ports.len(), args.len()
),
});
}
let mut result = decl.body.clone();
for (i, port) in decl.ports.iter().enumerate() {
let actual = if i < args.len() {
args[i].clone()
} else {
port.default.clone().ok_or_else(|| SvaParseError {
message: format!("missing required argument '{}' for let '{}'", port.name, name),
})?
};
result = substitute_signal(&result, &port.name, &actual);
}
Ok(result)
}
pub fn translate_dist_to_ranges(items: &[DistItem]) -> Vec<(u64, u64)> {
items.iter().map(|item| {
let max = item.max.unwrap_or(item.min);
(item.min, max)
}).collect()
}
pub fn validate_dist(items: &[DistItem]) -> Result<(), SvaParseError> {
if items.is_empty() {
return Err(SvaParseError { message: "empty dist list".to_string() });
}
Ok(())
}
pub fn resolve_checker(
checker: &CheckerDecl,
port_bindings: &[(String, SvaExpr)],
) -> Result<Vec<SvaDirective>, SvaParseError> {
checker.assertions.iter().map(|directive| {
let mut resolved_prop = directive.property.clone();
for (port_name, actual) in port_bindings {
resolved_prop = substitute_signal(&resolved_prop, port_name, actual);
}
Ok(SvaDirective {
kind: directive.kind.clone(),
property: resolved_prop,
label: directive.label.clone(),
clock: directive.clock.clone(),
disable_iff: directive.disable_iff.clone(),
action_pass: directive.action_pass.clone(),
action_fail: directive.action_fail.clone(),
})
}).collect()
}
pub fn checker_quantifier_structure(checker: &CheckerDecl) -> (Vec<&RandVar>, Vec<&RandVar>) {
let const_vars: Vec<&RandVar> = checker.rand_vars.iter().filter(|v| v.is_const).collect();
let nonconst_vars: Vec<&RandVar> = checker.rand_vars.iter().filter(|v| !v.is_const).collect();
(const_vars, nonconst_vars)
}