use sv_lang::kinds::{StatementKind, SymbolKind};
use sv_lang::{
ArgumentDirection, AssertionKind, ConstantRange, DefinitionKind, Design, FloatKind, NetKind,
PredefinedIntegerKind, Statement, Symbol, SyntaxTree, Type, UnconnectedDrive, VariableLifetime,
};
use vj_sim::symid::{SymbolId, SymbolUid};
use super::consts;
use super::errors::VpiError;
use super::model::{
ExprId, ExprNode, Obj, ObjId, ObjKind, OpMeta, Operand, ParamValue, Props, SigKey, TfInfo,
VpiModel,
};
use super::value::{B4, Logic};
const MAX_ELEMENTS: usize = 1 << 16;
struct Locator {
trees: Vec<(SyntaxTree, Vec<usize>)>,
}
impl Locator {
fn new(design: &Design) -> Self {
let trees = design
.syntax_trees()
.into_iter()
.map(|tree| {
let text = tree.text();
let starts = std::iter::once(0)
.chain(text.match_indices('\n').map(|(i, _)| i + 1))
.collect();
(tree, starts)
})
.collect();
Locator { trees }
}
fn syntax_trees(&self) -> Vec<SyntaxTree> {
self.trees.iter().map(|(t, _)| t.clone()).collect()
}
fn is_begin_block(&self, st: &Statement<'_>) -> bool {
use sv_lang::AstNode;
self.trees
.iter()
.find_map(|(tree, _)| st.syntax(tree))
.is_none_or(|n| sv_lang::nodes::BlockStatementSyntax::cast(n).is_some())
}
fn line(&self, sym: &Symbol<'_>) -> i32 {
for (tree, starts) in &self.trees {
if let Some(node) = sym.syntax(tree) {
let offset = node.byte_range().start;
return starts.partition_point(|&s| s <= offset) as i32;
}
}
0
}
fn stmt_line(&self, stmt: &Statement<'_>) -> i32 {
for (tree, starts) in &self.trees {
if let Some(node) = stmt.syntax(tree) {
let offset = node.byte_range().start;
return starts.partition_point(|&s| s <= offset) as i32;
}
}
let range = stmt.source_range();
if range.end.offset <= range.start.offset {
return 0;
}
let offset = range.start.offset as usize;
for (tree, starts) in &self.trees {
let text = tree.text();
let Some(rest) = text.get(offset..) else {
continue;
};
if ["if", "unique", "priority", "case"]
.iter()
.any(|k| rest.starts_with(k))
{
return starts.partition_point(|&s| s <= offset) as i32;
}
}
0
}
}
fn assertion_kind(stmt: &Statement<'_>) -> Option<(ObjKind, bool, bool)> {
match stmt.kind() {
StatementKind::ConcurrentAssertion => Some((
match stmt.assertion_kind()? {
AssertionKind::Assert => ObjKind::Assert,
AssertionKind::Assume => ObjKind::Assume,
AssertionKind::CoverProperty | AssertionKind::CoverSequence => ObjKind::Cover,
AssertionKind::Restrict => ObjKind::Restrict,
AssertionKind::Expect => return None,
},
false,
false,
)),
StatementKind::ImmediateAssertion => Some((
match stmt.immediate_assertion_kind()? {
AssertionKind::Assert => ObjKind::ImmediateAssert,
AssertionKind::Assume => ObjKind::ImmediateAssume,
_ => ObjKind::ImmediateCover,
},
stmt.is_deferred_assertion().unwrap_or(false),
stmt.is_final_assertion().unwrap_or(false),
)),
_ => None,
}
}
fn clocking_keyword(block: &Symbol<'_>, trees: &[SyntaxTree]) -> Option<sv_lang::kinds::TokenKind> {
use sv_lang::kinds::SyntaxKind;
for tree in trees {
let Some(mut node) = block.syntax(tree) else {
continue;
};
loop {
if node.kind() == SyntaxKind::ClockingDeclaration {
return <sv_lang::nodes::ClockingDeclarationSyntax as sv_lang::AstNode>::cast(node)
.and_then(|d| d.global_or_default())
.map(|t| t.kind());
}
match node.parent() {
Some(p) => node = p,
None => break,
}
}
}
None
}
fn edge_const(edge: sv_lang::EdgeKind) -> i32 {
let name = match edge {
sv_lang::EdgeKind::PosEdge => "vpiPosedge",
sv_lang::EdgeKind::NegEdge => "vpiNegedge",
sv_lang::EdgeKind::BothEdges => "vpiAnyEdge",
sv_lang::EdgeKind::None => "vpiNoEdge",
};
consts::value_of(name).expect("a vpi_user.h edge") as i32
}
#[derive(Clone)]
struct LetSite {
name: String,
text: String,
}
fn split_top_level(text: &str) -> Vec<String> {
let (mut out, mut cur, mut depth, mut quoted) = (Vec::new(), String::new(), 0i32, false);
for c in text.chars() {
match c {
'"' => quoted = !quoted,
'(' | '[' | '{' if !quoted => depth += 1,
')' | ']' | '}' if !quoted => depth -= 1,
',' if !quoted && depth == 0 => {
out.push(std::mem::take(&mut cur));
continue;
}
_ => {}
}
cur.push(c);
}
if !cur.trim().is_empty() || !out.is_empty() {
out.push(cur);
}
out
}
fn formal_default_node(text: &str) -> ExprNode {
let cv = |name: &str| consts::value_of(name).expect("a vpi_user.h constant type") as i32;
let konst = |value: Logic, signed: bool, ty: &str| ExprNode::Const {
value,
signed,
const_type: cv(ty),
};
let bare: String = text.chars().filter(|c| *c != '_').collect();
if let Some(inner) = bare.strip_prefix('"').and_then(|t| t.strip_suffix('"')) {
return konst(
Logic::from_string_val(inner.as_bytes(), 8 * inner.len().max(1) as u32),
false,
"vpiStringConst",
);
}
if !bare.is_empty()
&& bare.chars().all(|c| c.is_ascii_digit())
&& let Some(l) = Logic::from_dec(&bare, 32)
{
return konst(l, true, "vpiDecConst");
}
if let Ok(r) = bare.parse::<f64>()
&& bare.contains(['.', 'e', 'E'])
{
return konst(Logic::from_u64(r.to_bits(), 64), false, "vpiRealConst");
}
if let Some((size, rest)) = bare.split_once('\'') {
let size = if size.is_empty() {
None
} else if let Ok(n) = size.parse::<u32>() {
Some(n)
} else {
return formal_default_unsupported(text);
};
let (signed, rest) = match rest.strip_prefix(['s', 'S']) {
Some(r) => (true, r),
None => (false, rest),
};
let lower = rest.to_ascii_lowercase();
let mut it = lower.chars();
let base = it.next();
let digits: String = it.collect();
if digits.is_empty()
&& size.is_none()
&& !signed
&& let Some(b) = base.filter(|b| matches!(b, '0' | '1' | 'x' | 'z'))
&& let Some(l) = Logic::from_radix(&b.to_string(), 1, 1)
{
return konst(l, false, "vpiBinaryConst");
}
let (bits, ty) = match base {
Some('b') => (1, "vpiBinaryConst"),
Some('o') => (3, "vpiOctConst"),
Some('h') => (4, "vpiHexConst"),
Some('d') => (0, "vpiDecConst"),
_ => (-1i32, ""),
};
if bits >= 0 && !digits.is_empty() {
let width = size.unwrap_or(32);
let l = if bits == 0 {
Logic::from_dec(&digits, width)
} else {
Logic::from_radix(&digits, bits as u32, width)
};
if let Some(l) = l {
return konst(l, signed, ty);
}
}
}
formal_default_unsupported(text)
}
fn formal_default_unsupported(text: &str) -> ExprNode {
ExprNode::Unsupported(format!(
"the default value `{text}` of a formal argument (only literals are modelled)"
))
}
fn unwrap_invalid_expr<'d>(mut e: sv_lang::Expression<'d>) -> sv_lang::Expression<'d> {
while e.kind() == sv_lang::kinds::ExpressionKind::Invalid
&& let Some(inner) = e.invalid_child()
{
e = inner;
}
e
}
fn const_node(c: &sv_lang::ConstantValue, kind: sv_lang::kinds::ExpressionKind) -> ExprNode {
use sv_lang::kinds::ExpressionKind as K;
let cv = |name: &str| consts::value_of(name).expect("a vpi_user.h constant type") as i32;
if let Some(i) = c.as_integer() {
return ExprNode::Const {
value: logic_of(i),
signed: i.is_signed(),
const_type: if kind == K::UnbasedUnsizedIntegerLiteral {
cv("vpiBinaryConst")
} else if kind == K::StringLiteral {
cv("vpiStringConst")
} else {
cv("vpiDecConst")
},
};
}
if let Some(r) = c.as_f64() {
return ExprNode::Const {
value: Logic::from_u64(r.to_bits(), 64),
signed: false,
const_type: cv("vpiRealConst"),
};
}
if let Some(t) = c.as_str() {
return ExprNode::Const {
value: Logic::from_string_val(t.as_bytes(), 8 * t.len().max(1) as u32),
signed: false,
const_type: cv("vpiStringConst"),
};
}
ExprNode::Unsupported("a constant of an unsupported type".into())
}
fn logic_of(i: &sv_lang::SVInt) -> Logic {
let width = i.bit_width();
let mut l = Logic::zero(width);
for k in 0..width {
let b = i.bit(k as usize).map_or(B4::Zero, |b| {
if b.is_unknown() {
if b.logic_t_value() == sv_lang::Bit::z().logic_t_value() {
B4::Z
} else {
B4::X
}
} else if b.logic_t_value() == 1 {
B4::One
} else {
B4::Zero
}
});
l.set(k, b);
}
l
}
fn has_empty_concat(e: &sv_lang::Expression<'_>) -> bool {
use sv_lang::kinds::ExpressionKind as K;
let own = match e.kind() {
K::Concatenation => e.operands().is_empty(),
K::Replication => e
.replication_concat()
.is_none_or(|c| c.operands().is_empty()),
_ => false,
};
own || e
.children_with_invalid()
.iter()
.filter_map(|c| c.as_expression())
.any(|c| has_empty_concat(&unwrap_invalid_expr(c)))
}
fn assign_rhs<'a>(e: &sv_lang::Expression<'a>) -> Option<sv_lang::Expression<'a>> {
use sv_lang::kinds::ExpressionKind as K;
fn bare<'b>(mut x: sv_lang::Expression<'b>) -> sv_lang::Expression<'b> {
while x.kind() == K::Conversion && x.conversion_is_implicit() != Some(false) {
let Some(inner) = x.conversion_operand() else {
break;
};
x = inner;
}
x
}
let r = e.right()?;
if e.is_compound_assignment() == Some(true) {
let ops = bare(r).operands();
if let [l, b] = ops.as_slice()
&& bare(*l).kind() == K::LValueReference
{
return Some(*b);
}
}
Some(r)
}
fn compound_assign_op(e: &sv_lang::Expression<'_>) -> Option<i32> {
if e.is_compound_assignment() == Some(true) {
e.assignment_op().and_then(|b| {
consts::value_of(match b {
sv_lang::BinaryOp::Add => "vpiAddOp",
sv_lang::BinaryOp::Subtract => "vpiSubOp",
sv_lang::BinaryOp::Multiply => "vpiMultOp",
sv_lang::BinaryOp::Divide => "vpiDivOp",
sv_lang::BinaryOp::Mod => "vpiModOp",
sv_lang::BinaryOp::BinaryAnd => "vpiBitAndOp",
sv_lang::BinaryOp::BinaryOr => "vpiBitOrOp",
sv_lang::BinaryOp::BinaryXor => "vpiBitXorOp",
sv_lang::BinaryOp::LogicalShiftLeft => "vpiLShiftOp",
sv_lang::BinaryOp::LogicalShiftRight => "vpiRShiftOp",
sv_lang::BinaryOp::ArithmeticShiftLeft => "vpiArithLShiftOp",
sv_lang::BinaryOp::ArithmeticShiftRight => "vpiArithRShiftOp",
_ => "",
})
.map(|v| v as i32)
})
} else {
None
}
}
fn op_type_of(e: &sv_lang::Expression<'_>) -> Option<i32> {
use sv_lang::kinds::ExpressionKind as K;
use sv_lang::{BinaryOp as B, UnaryOp as U};
let name = match e.kind() {
K::ConditionalOp => "vpiConditionOp",
K::MinTypMax => "vpiMinTypMaxOp",
K::Concatenation => "vpiConcatOp",
K::Replication => "vpiMultiConcatOp",
K::Streaming => {
if e.streaming_slice_size()? == 0 {
"vpiStreamLROp"
} else {
"vpiStreamRLOp"
}
}
K::UnaryOp => match e.unary_op()? {
U::Plus => "vpiPlusOp",
U::Minus => "vpiMinusOp",
U::BitwiseNot => "vpiBitNegOp",
U::BitwiseAnd => "vpiUnaryAndOp",
U::BitwiseOr => "vpiUnaryOrOp",
U::BitwiseXor => "vpiUnaryXorOp",
U::BitwiseNand => "vpiUnaryNandOp",
U::BitwiseNor => "vpiUnaryNorOp",
U::BitwiseXnor => "vpiUnaryXNorOp",
U::LogicalNot => "vpiNotOp",
U::Preincrement => "vpiPreIncOp",
U::Predecrement => "vpiPreDecOp",
U::Postincrement => "vpiPostIncOp",
U::Postdecrement => "vpiPostDecOp",
},
K::BinaryOp => match e.binary_op()? {
B::Add => "vpiAddOp",
B::Subtract => "vpiSubOp",
B::Multiply => "vpiMultOp",
B::Divide => "vpiDivOp",
B::Mod => "vpiModOp",
B::BinaryAnd => "vpiBitAndOp",
B::BinaryOr => "vpiBitOrOp",
B::BinaryXor => "vpiBitXorOp",
B::BinaryXnor => "vpiBitXnorOp",
B::Equality => "vpiEqOp",
B::Inequality => "vpiNeqOp",
B::CaseEquality => "vpiCaseEqOp",
B::CaseInequality => "vpiCaseNeqOp",
B::GreaterThanEqual => "vpiGeOp",
B::GreaterThan => "vpiGtOp",
B::LessThanEqual => "vpiLeOp",
B::LessThan => "vpiLtOp",
B::WildcardEquality => "vpiWildEqOp",
B::WildcardInequality => "vpiWildNeqOp",
B::LogicalAnd => "vpiLogAndOp",
B::LogicalOr => "vpiLogOrOp",
B::LogicalImplication => "vpiImpliesOp",
B::LogicalShiftLeft => "vpiLShiftOp",
B::LogicalShiftRight => "vpiRShiftOp",
B::ArithmeticShiftLeft => "vpiArithLShiftOp",
B::ArithmeticShiftRight => "vpiArithRShiftOp",
B::Power => "vpiPowerOp",
_ => return None,
},
_ => return None,
};
consts::value_of(name).map(|v| v as i32)
}
fn is_primitive_array(array: &Symbol<'_>) -> bool {
array.instance_array_elements().next().is_some_and(|e| {
e.kind() == SymbolKind::PrimitiveInstance
|| (e.kind() == SymbolKind::InstanceArray && is_primitive_array(&e))
})
}
fn prim_class(def: &str, n: usize, inst: &Symbol<'_>) -> (ObjKind, i32, usize, usize) {
let c = |name: &str| consts::value_of(name).expect("a vpi_user.h primitive type") as i32;
let gate = |t: &str, outs: usize| (ObjKind::Gate, c(t), outs, 0);
let switch = |t: &str, outs: usize, inouts: usize| (ObjKind::Switch, c(t), outs, inouts);
match def {
"and" => gate("vpiAndPrim", 1),
"nand" => gate("vpiNandPrim", 1),
"or" => gate("vpiOrPrim", 1),
"nor" => gate("vpiNorPrim", 1),
"xor" => gate("vpiXorPrim", 1),
"xnor" => gate("vpiXnorPrim", 1),
"buf" => gate("vpiBufPrim", n.saturating_sub(1)),
"not" => gate("vpiNotPrim", n.saturating_sub(1)),
"bufif0" => gate("vpiBufif0Prim", 1),
"bufif1" => gate("vpiBufif1Prim", 1),
"notif0" => gate("vpiNotif0Prim", 1),
"notif1" => gate("vpiNotif1Prim", 1),
"pullup" => gate("vpiPullupPrim", n),
"pulldown" => gate("vpiPulldownPrim", n),
"nmos" => switch("vpiNmosPrim", 1, 0),
"pmos" => switch("vpiPmosPrim", 1, 0),
"cmos" => switch("vpiCmosPrim", 1, 0),
"rnmos" => switch("vpiRnmosPrim", 1, 0),
"rpmos" => switch("vpiRpmosPrim", 1, 0),
"rcmos" => switch("vpiRcmosPrim", 1, 0),
"tran" => switch("vpiTranPrim", 0, 2),
"rtran" => switch("vpiRtranPrim", 0, 2),
"tranif0" => switch("vpiTranif0Prim", 0, 2),
"tranif1" => switch("vpiTranif1Prim", 0, 2),
"rtranif0" => switch("vpiRtranif0Prim", 0, 2),
"rtranif1" => switch("vpiRtranif1Prim", 0, 2),
_ => {
let sequential = udp_definition(inst, def).is_some_and(|d| d.primitive_is_sequential());
(
ObjKind::Udp,
c(if sequential {
"vpiSeqPrim"
} else {
"vpiCombPrim"
}),
1,
0,
)
}
}
}
fn udp_definition<'d>(inst: &Symbol<'d>, name: &str) -> Option<Symbol<'d>> {
let mut root = *inst;
while let Some(p) = root.parent() {
root = p;
}
root.members()
.flat_map(|unit| unit.members().collect::<Vec<_>>())
.find(|m| m.kind() == SymbolKind::Primitive && m.name() == name)
}
fn strength_const(s: Option<sv_lang::DriveStrength>, default_pull: bool) -> i32 {
let name = match s {
Some(sv_lang::DriveStrength::Supply) => "vpiSupplyDrive",
Some(sv_lang::DriveStrength::Strong) => "vpiStrongDrive",
Some(sv_lang::DriveStrength::Pull) => "vpiPullDrive",
Some(sv_lang::DriveStrength::Weak) => "vpiWeakDrive",
Some(sv_lang::DriveStrength::HighZ) => "vpiHighZ",
None if default_pull => "vpiPullDrive",
None => "vpiStrongDrive",
};
consts::value_of(name).expect("a vpi_user.h strength") as i32
}
fn vif_shape(ty: &Type<'_>) -> Option<VifShape> {
let text = ty.to_sv_string();
let mut rest = text.trim();
for prefix in ["virtual ", "interface "] {
rest = rest.strip_prefix(prefix).unwrap_or(rest).trim_start();
}
let end = rest.find(['#', '.', ' ']).unwrap_or(rest.len());
let def = rest[..end].to_owned();
let mut tail = &rest[end..];
let mut params = Vec::new();
if let Some(list) = tail.strip_prefix('#') {
let mut depth = 0usize;
let mut close = None;
let mut cuts = vec![1usize];
for (i, c) in list.char_indices() {
match c {
'(' => depth += 1,
')' => {
depth = depth.checked_sub(1)?;
if depth == 0 {
close = Some(i);
break;
}
}
',' if depth == 1 => cuts.push(i + 1),
_ => {}
}
}
let close = close?;
cuts.push(close + 1);
for w in cuts.windows(2) {
let item = list[w[0]..w[1] - 1].trim();
if let Some((n, v)) = item.split_once('=') {
params.push((n.trim().to_owned(), v.trim().to_owned()));
}
}
tail = &list[close + 1..];
}
let modport = tail.strip_prefix('.').map(|m| m.trim().to_owned());
(!def.is_empty()).then_some(VifShape {
def,
modport,
params,
})
}
struct VifShape {
def: String,
modport: Option<String>,
params: Vec<(String, String)>,
}
fn receiver_listed(e: &sv_lang::Expression<'_>) -> bool {
e.call_subroutine()
.is_some_and(|s| e.operands().len() == s.subroutine_arguments().count() + 1)
}
fn net_type_const(kind: vj_sim::state::NetKind) -> i32 {
use vj_sim::state::NetKind as K;
let name = match kind {
K::WAnd => "vpiWand",
K::WOr => "vpiWor",
K::Tri => "vpiTri",
K::Tri0 => "vpiTri0",
K::Tri1 => "vpiTri1",
K::TriReg => "vpiTriReg",
K::TriAnd => "vpiTriAnd",
K::TriOr => "vpiTriOr",
K::Supply0 => "vpiSupply0",
K::Supply1 => "vpiSupply1",
K::UWire => "vpiUwire",
K::Wire | K::UserDefined => "vpiWire",
};
consts::value_of(name).expect("a vpi_user.h net type") as i32
}
enum UseCtx<'t> {
Modelled,
Range(sv_lang::Node<'t>),
Gen(Vec<sv_lang::Node<'t>>),
Refused,
}
enum GenPos<'t> {
Cond(Vec<sv_lang::Node<'t>>),
Body,
Unknown,
}
struct Builder<'d> {
model: VpiModel,
cur_assertion: Option<ObjId>,
decl_parent: Option<ObjId>,
locator: Locator,
file: String,
body: Option<SymbolId>,
def_name: Option<String>,
links: Vec<(ObjId, u8, SymbolId, SymbolId)>,
expr_links: Vec<(ExprId, i32, Option<SymbolId>, SymbolId)>,
tf_ref_by_uid: std::collections::HashMap<SymbolId, ObjId>,
param_links: Vec<(ExprId, SymbolId)>,
qualified_idents: Option<std::collections::HashSet<String>>,
pending_plain: Vec<ExprId>,
pending_local: std::collections::HashSet<ExprId>,
range_exprs: std::collections::HashSet<(SymbolId, usize, usize, usize)>,
qual_fail: std::collections::HashSet<String>,
param_objs: Vec<(String, ObjId)>,
inline_constraints: u32,
let_decls: std::collections::HashMap<(usize, usize), ObjId>,
let_depth: u32,
let_sites: Vec<LetSite>,
let_hint: Option<Vec<LetSite>>,
stmt_sites: Vec<LetSite>,
op_depth: u32,
path_links: Vec<(ObjId, i32, String)>,
itf_tf_links: Vec<(ObjId, ObjId, String)>,
select_conns: Vec<(String, String)>,
defparam_links: Vec<(ObjId, Vec<String>)>,
disable_links: Vec<(ObjId, String, String)>,
tf_by_uid: std::collections::HashMap<SymbolId, ObjId>,
prop_by_uid: std::collections::HashMap<SymbolId, ObjId>,
formal_by_uid: std::collections::HashMap<SymbolId, ObjId>,
formal_names: Vec<(String, ObjId)>,
formals_by_text: bool,
loop_vars: Vec<(String, ObjId)>,
tf_rel_links: Vec<(ObjId, i32, SymbolId)>,
tf_call_links: Vec<(ExprId, SymbolId)>,
ctx_clock: Option<Operand>,
pending_members: Vec<ExprId>,
member_types: std::collections::HashMap<ExprId, (ObjKind, i32)>,
member_chain: std::collections::HashMap<ExprId, Vec<(ObjKind, i32)>>,
assertion_ids: std::collections::HashMap<u64, ObjId>,
typespecs: std::collections::HashMap<String, ObjId>,
instances: std::collections::HashMap<String, ObjId>,
ref_links: Vec<(ObjId, String, Option<String>)>,
eval: &'d sv_lang::EvalSession<'d>,
}
fn header(name: &str) -> i32 {
consts::value_of(name).expect("a header constant") as i32
}
pub fn build_model(design: &mut Design, file: &str) -> Result<VpiModel, VpiError> {
let eval = design.eval_session();
let design = eval.design();
let mut b = Builder {
model: VpiModel::new(),
cur_assertion: None,
decl_parent: None,
locator: Locator::new(design),
file: file.to_owned(),
body: None,
def_name: None,
links: Vec::new(),
expr_links: Vec::new(),
tf_ref_by_uid: std::collections::HashMap::new(),
param_links: Vec::new(),
qualified_idents: None,
pending_plain: Vec::new(),
pending_local: std::collections::HashSet::new(),
range_exprs: std::collections::HashSet::new(),
qual_fail: std::collections::HashSet::new(),
param_objs: Vec::new(),
inline_constraints: 0,
let_decls: std::collections::HashMap::new(),
let_depth: 0,
let_sites: Vec::new(),
let_hint: None,
stmt_sites: Vec::new(),
op_depth: 0,
path_links: Vec::new(),
itf_tf_links: Vec::new(),
select_conns: Vec::new(),
defparam_links: Vec::new(),
disable_links: Vec::new(),
tf_by_uid: std::collections::HashMap::new(),
prop_by_uid: std::collections::HashMap::new(),
formal_by_uid: std::collections::HashMap::new(),
formal_names: Vec::new(),
formals_by_text: false,
loop_vars: Vec::new(),
tf_rel_links: Vec::new(),
tf_call_links: Vec::new(),
ctx_clock: None,
pending_members: Vec::new(),
member_types: std::collections::HashMap::new(),
member_chain: std::collections::HashMap::new(),
assertion_ids: std::collections::HashMap::new(),
typespecs: std::collections::HashMap::new(),
instances: std::collections::HashMap::new(),
ref_links: Vec::new(),
eval: &eval,
};
for top in design.top_instances() {
let name = top.name().to_owned();
b.instance(&top, None, &name, &name, None, true)?;
}
for package in design.packages() {
if package.name() != "std" {
b.package(&package);
}
}
b.dpi_exports(design);
for (e, pos, body, sym) in std::mem::take(&mut b.expr_links) {
let target = body
.as_ref()
.and_then(|body| b.model.find_signal(body, &sym))
.or_else(|| b.prop_by_uid.get(&sym).copied())
.or_else(|| b.tf_ref_by_uid.get(&sym).copied());
let Some(node) = b.model.expr_mut(e) else {
continue;
};
match (node, target) {
(ExprNode::Op { operands, .. }, Some(t)) if pos >= 0 => {
operands[pos as usize] = Operand::Obj(t);
}
(
ExprNode::BitSelect { base, .. }
| ExprNode::PartSelect { base, .. }
| ExprNode::IndexSelect { base, .. }
| ExprNode::IndexedPartSelect { base, .. }
| ExprNode::Member { base, .. },
Some(t),
) => *base = t,
(node, None) => *node = ExprNode::Unsupported("a reference outside the model".into()),
_ => {}
}
}
for (sel, sym) in std::mem::take(&mut b.param_links) {
if let Some(p) = b.prop_by_uid.get(&sym).copied() {
b.model.add_param_use(p, sel);
}
}
let pending = std::mem::take(&mut b.pending_plain);
let mut plain: std::collections::HashMap<ExprId, Operand> = pending
.iter()
.filter_map(|&h| match b.model.expr(h) {
Some(ExprNode::Op { operands, .. }) => match operands.first() {
Some(Operand::Obj(o)) => Some((h, Operand::Obj(*o))),
_ => None,
},
_ => None,
})
.collect();
for &h in &pending {
if !plain.contains_key(&h)
&& let Some(n) = b.model.expr_mut(h)
{
*n = ExprNode::Unsupported(if b.pending_local.contains(&h) {
super::model::AUTOMATIC_LOCAL_REF.into()
} else {
"a reference to a name the VPI model has no object for (a member of \
an unmodelled scope)"
.into()
});
}
}
for h in std::mem::take(&mut b.pending_members) {
let target = match b.model.expr(h) {
Some(ExprNode::Member { base, path }) if *base != u32::MAX => b
.model
.get(*base)
.and_then(|o| b.model.find(&format!("{}.{path}", o.full_name))),
_ => None,
};
match target {
Some(t) => {
plain.insert(h, Operand::Obj(t));
}
None => {
let mut prefixed = None;
if let (Some(ExprNode::Member { base, path }), Some(&(kind, size))) =
(b.model.expr(h).cloned(), b.member_types.get(&h))
&& base != u32::MAX
{
let bkind = b.model.get(base).map(|o| o.kind);
if bkind == Some(ObjKind::RefObj)
&& b.model
.get(base)
.and_then(|o| b.model.get(o.parent?))
.is_some_and(|p| p.kind == ObjKind::TaskFunc)
{
prefixed = Some(ExprNode::PrefixedMember {
prefix: Operand::Obj(base),
name: path,
kind,
size,
});
} else if (!path.contains('.') && bkind == Some(ObjKind::ClassVar))
|| bkind == Some(ObjKind::VirtualInterfaceVar)
{
prefixed = Some(ExprNode::PrefixedMember {
prefix: Operand::Obj(base),
name: path,
kind,
size,
});
} else if bkind == Some(ObjKind::ClassVar) {
let names: Vec<&str> = path.split('.').collect();
let mids = b.member_chain.get(&h).cloned().unwrap_or_default();
if mids.len() + 1 == names.len()
&& mids.iter().all(|(k, _)| *k == ObjKind::ClassVar)
{
let mut prefix = Operand::Obj(base);
for (n, &(k, sz)) in names.iter().zip(&mids) {
prefix =
Operand::Expr(b.model.add_expr(ExprNode::PrefixedMember {
prefix,
name: (*n).to_owned(),
kind: k,
size: sz,
}));
}
prefixed = Some(ExprNode::PrefixedMember {
prefix,
name: names[names.len() - 1].to_owned(),
kind,
size,
});
}
}
}
if let Some(n) = b.model.expr_mut(h) {
*n = prefixed.unwrap_or_else(|| {
ExprNode::Unsupported("a member select outside the model".into())
});
}
}
}
}
let fix = |o: &mut Operand| {
if let Operand::Expr(e) = o
&& let Some(r) = plain.get(e)
{
*o = *r;
}
};
for i in 0..b.model.expr_count() as ExprId {
match b.model.expr_mut(i) {
Some(ExprNode::Op { operands, .. })
| Some(ExprNode::AssertInst { args: operands, .. }) => {
operands.iter_mut().for_each(&fix);
}
Some(ExprNode::PrefixedElement { prefix, index, .. }) => {
fix(prefix);
fix(index);
}
Some(ExprNode::Call { args, prefix, .. }) => {
args.iter_mut().for_each(&fix);
if let Some(p) = prefix {
fix(p);
}
}
Some(ExprNode::CaseProperty { cond, items }) => {
fix(cond);
items.iter_mut().for_each(&fix);
}
Some(ExprNode::CasePropItem { exprs, prop }) => {
exprs.iter_mut().for_each(&fix);
fix(prop);
}
Some(ExprNode::MulticlockSeq { seqs }) => seqs.iter_mut().for_each(&fix),
Some(ExprNode::LetInst { args, body, .. }) => {
args.iter_mut().for_each(&fix);
fix(body);
}
Some(ExprNode::ValueRange { left, right }) => {
fix(left);
fix(right);
}
Some(ExprNode::ConstrOrdering { before, after }) => {
before.iter_mut().chain(after.iter_mut()).for_each(&fix);
}
Some(ExprNode::Implication { cond, body }) => {
fix(cond);
body.iter_mut().for_each(&fix);
}
Some(ExprNode::ConstrIf { cond, body, els }) => {
fix(cond);
body.iter_mut().for_each(&fix);
if let Some(e) = els {
e.iter_mut().for_each(&fix);
}
}
Some(ExprNode::ConstrForeach { array, vars, body }) => {
fix(array);
vars.iter_mut().chain(body.iter_mut()).for_each(&fix);
}
Some(ExprNode::SoftDisable { expr }) => fix(expr),
Some(ExprNode::Distribution { expr, items, .. }) => {
fix(expr);
items.iter_mut().for_each(&fix);
}
Some(ExprNode::DistItem { value, weight, .. }) => {
fix(value);
if let Some(w) = weight {
fix(w);
}
}
Some(ExprNode::Clocked { clock, body, .. }) => {
fix(clock);
fix(body);
}
Some(ExprNode::IndexSelect { index: o, .. })
| Some(ExprNode::IndexedPartSelect { start: o, .. }) => fix(o),
Some(ExprNode::ExprSelect { base, sel, .. }) => {
fix(base);
match sel {
super::model::Sel::Bit(o) | super::model::Sel::Indexed(o, _) => fix(o),
super::model::Sel::Part(..) => {}
}
}
_ => {}
}
}
for i in 0..b.model.len() as ObjId {
if let Some(p) = b.model.props_mut(i) {
if let Some(o) = p.conn_op.as_mut() {
fix(o);
}
if let Some(o) = p.rhs_op.as_mut() {
fix(o);
}
p.rels.iter_mut().for_each(|(_, o)| fix(o));
p.expr_srcs.iter_mut().for_each(|(_, o)| fix(o));
}
}
for (id, rel, uid) in std::mem::take(&mut b.tf_rel_links) {
if let Some(&t) = b.tf_by_uid.get(&uid)
&& let Some(p) = b.model.props_mut(id)
{
p.rels.push((rel, Operand::Obj(t)));
}
}
for (e, uid) in std::mem::take(&mut b.tf_call_links) {
if let Some(&t) = b.tf_by_uid.get(&uid)
&& let Some(ExprNode::Call { func, .. }) = b.model.expr_mut(e)
{
*func = Some(t);
}
}
for (id, scope, label) in std::mem::take(&mut b.disable_links) {
let mut base = scope.as_str();
let target = loop {
let cand = if base.is_empty() {
label.clone()
} else {
format!("{base}.{label}")
};
if let Some(t) = b.model.find(&cand) {
break Some(t);
}
match base.rfind('.') {
Some(i) => base = &base[..i],
None if !base.is_empty() => base = "",
None => break None,
}
};
if let Some(t) = target {
b.rel(id, consts::VPI_EXPR, Operand::Obj(t));
}
}
for (id, candidates) in std::mem::take(&mut b.defparam_links) {
let Some(t) = candidates.iter().find_map(|c| {
b.model
.find(c)
.filter(|&t| b.model.get(t).is_some_and(|o| o.kind == ObjKind::Parameter))
}) else {
continue;
};
let rhs = b
.model
.get(t)
.and_then(|o| o.props.param.clone())
.map(|v| match v {
ParamValue::Logic(l, signed) => ExprNode::Const {
value: l,
signed,
const_type: header("vpiDecConst"),
},
ParamValue::Real(r) => ExprNode::Const {
value: Logic::from_u64(r.to_bits(), 64),
signed: false,
const_type: consts::VPI_REAL_CONST,
},
ParamValue::Str(text) => ExprNode::Const {
value: Logic::from_string_val(text.as_bytes(), 8 * text.len().max(1) as u32),
signed: false,
const_type: header("vpiStringConst"),
},
});
let rhs = rhs.map(|n| b.model.add_expr(n));
if let Some(p) = b.model.props_mut(id) {
p.rels.push((consts::VPI_LHS, Operand::Obj(t)));
if let Some(r) = rhs {
p.rels.push((consts::VPI_RHS, Operand::Expr(r)));
}
}
}
for (id, rel, path) in std::mem::take(&mut b.path_links) {
if let Some(t) = b.model.find(&path)
&& let Some(p) = b.model.props_mut(id)
{
p.rels.push((rel, Operand::Obj(t)));
}
}
for (mp, io, path) in std::mem::take(&mut b.itf_tf_links) {
let Some(t) = b.model.find(&path).filter(|t| {
b.model
.get(*t)
.is_some_and(|o| o.kind == ObjKind::InterfaceTfDecl)
}) else {
continue;
};
for (obj, rel) in [(io, consts::VPI_EXPR), (mp, header("vpiInterfaceTfDecl"))] {
if let Some(p) = b.model.props_mut(obj) {
p.rels.push((rel, Operand::Obj(t)));
}
}
}
b.interconnect_typespecs();
for (id, inst, modport) in std::mem::take(&mut b.ref_links) {
let Some(&target) = b.instances.get(&inst) else {
continue;
};
let actual = match modport {
Some(m) => b.model.children(target).iter().copied().find(|&c| {
b.model
.get(c)
.is_some_and(|o| o.kind == ObjKind::Modport && o.name == m)
}),
None => Some(target),
};
if let (Some(a), Some(p)) = (actual, b.model.props_mut(id)) {
p.rels.push((header("vpiActual"), Operand::Obj(a)));
}
}
let mut widths = Vec::new();
for (id, side, body, sym) in std::mem::take(&mut b.links) {
if side == 0
&& let Some(t) = b.model.find_signal(&body, &sym)
&& b.model
.get(id)
.is_some_and(|o| o.kind == ObjKind::ContAssign)
{
widths.push((id, b.model.get(t).map_or(0, |o| o.props.width)));
}
b.links.push((id, side, body, sym));
}
for (id, side, body, sym) in std::mem::take(&mut b.links) {
let Some(target) = b.model.find_signal(&body, &sym) else {
continue;
};
if let Some(p) = b.model.props_mut(id) {
match side {
0 => p.conn = Some(target),
1 => p.rhs = Some(target),
_ => {
if !p.reads.contains(&target) {
p.reads.push(target);
}
}
}
}
}
for (id, w) in widths {
if let Some(p) = b.model.props_mut(id)
&& p.width == 0
{
p.width = w;
}
}
let decl: Vec<(ObjId, i32)> = b
.model
.objects()
.filter(|(_, o)| o.kind == ObjKind::ContAssign && o.props.width == 0)
.filter_map(|(i, o)| {
o.props
.conn
.and_then(|c| b.model.get(c))
.map(|t| (i, t.props.width))
})
.collect();
for (id, w) in decl {
if let Some(p) = b.model.props_mut(id) {
p.width = w;
}
}
Ok(b.model)
}
fn def_net_type(kind: NetKind) -> Result<i32, VpiError> {
Ok(match kind {
NetKind::Wire => consts::VPI_WIRE,
NetKind::WAnd => consts::VPI_WAND,
NetKind::WOr => consts::VPI_WOR,
NetKind::Tri => consts::VPI_TRI,
NetKind::TriAnd => consts::VPI_TRI_AND,
NetKind::TriOr => consts::VPI_TRI_OR,
NetKind::Tri0 => consts::VPI_TRI0,
NetKind::Tri1 => consts::VPI_TRI1,
NetKind::TriReg => consts::VPI_TRI_REG,
NetKind::Supply0 => consts::VPI_SUPPLY0,
NetKind::Supply1 => consts::VPI_SUPPLY1,
NetKind::UWire => consts::VPI_UWIRE,
NetKind::Interconnect => consts::VPI_INTERCONNECT,
NetKind::Unknown => consts::VPI_NONE,
other => {
return Err(VpiError::unsupported(
&format!("a default net type of {other:?}"),
"a vpiDefNetType value for it",
));
}
})
}
fn element_index(range: ConstantRange, i: usize) -> i32 {
if range.left >= range.right {
range.left - i as i32
} else {
range.left + i as i32
}
}
impl Builder<'_> {
fn add(&mut self, obj: Obj) -> ObjId {
self.model.add(obj)
}
fn library_of(def: &Symbol<'_>) -> String {
def.source_library()
.map_or_else(|| "work".to_owned(), |l| l.name())
}
fn instance_kind(def: &Symbol<'_>) -> ObjKind {
match def.definition_kind() {
Some(DefinitionKind::Interface) => ObjKind::Interface,
Some(DefinitionKind::Program) => ObjKind::Program,
Some(DefinitionKind::Module) | None => ObjKind::Module,
}
}
fn instance(
&mut self,
inst: &Symbol<'_>,
parent: Option<ObjId>,
name: &str,
full_name: &str,
index: Option<i32>,
top: bool,
) -> Result<ObjId, VpiError> {
let (Some(body), Some(def)) = (inst.instance_body(), inst.instance_definition()) else {
return Err(VpiError::bad_argument(format!(
"instance {full_name} has no body or definition in the elaborated design"
)));
};
let (time_unit, time_precision) = vj_codegen::timescale::unit_and_precision(&body);
let def_net_type = match body.default_net_type() {
Some(t) => def_net_type(t.net_kind())?,
None => consts::VPI_WIRE,
};
let line = self.locator.line(inst);
let def_line = self.locator.line(&def);
let mut obj = Obj::new(Self::instance_kind(&def), parent, name, full_name);
obj.props = Props {
def_name: Some(def.name().to_owned()),
library: Some(Self::library_of(&def)),
cell_name: Some(def.name().to_owned()),
top,
cell: def.definition_cell_define(),
automatic: def.definition_default_lifetime() == Some(VariableLifetime::Automatic),
index,
size: None,
time_unit,
time_precision,
def_net_type,
unconn_drive: match def.definition_unconnected_drive() {
Some(UnconnectedDrive::Pull0) => consts::VPI_PULL0,
Some(UnconnectedDrive::Pull1) => consts::VPI_PULL1,
Some(UnconnectedDrive::None) | None => consts::VPI_HIGH_Z,
},
def_delay_mode: consts::VPI_DELAY_MODE_NONE,
file: Some(self.file.clone()),
line: if line == 0 { def_line } else { line },
def_file: Some(self.file.clone()),
def_line,
dyn_key: Some((body.uid(), body.uid())),
..Props::default()
};
let id = self.add(obj);
self.instances.insert(format!("{:?}", inst.uid()), id);
self.attributes(inst.attributes(), id, full_name, false);
self.attributes(def.attributes(), id, full_name, true);
let mut high: std::collections::HashMap<String, Operand> = std::collections::HashMap::new();
for c in inst.instance_port_connections() {
let e = c.expression().map(|e| {
if e.kind() == sv_lang::kinds::ExpressionKind::Assignment {
e.left().unwrap_or(e)
} else {
e
}
});
if let Some(e) = e
&& let Some(op) = self.operand(&e)
{
high.insert(c.port().name().to_owned(), op);
if let Some(path) = Self::const_select_path(&e) {
self.select_conns
.push((format!("{full_name}.{}", c.port().name()), path));
}
}
}
let ports_by_name = self.instantiation_syntax(inst);
let conn_texts = self.connection_texts(inst);
let outer = self.body.replace(body.uid());
let outer_def = self.def_name.replace(def.name().to_owned());
self.members(&body, id, full_name)?;
self.ports(&body, id, full_name, &high, ports_by_name, &conn_texts);
self.param_assigns(inst, id, full_name);
self.qualified_uses(&body);
self.body = outer;
self.def_name = outer_def;
Ok(id)
}
fn ports(
&mut self,
body: &Symbol<'_>,
parent: ObjId,
parent_name: &str,
high: &std::collections::HashMap<String, Operand>,
by_name: bool,
conn_texts: &[(Option<String>, String)],
) {
for (i, port) in body.instance_body_ports().enumerate() {
if port.kind() == SymbolKind::InterfacePort {
let ordered = conn_texts.iter().filter(|(n, _)| n.is_none()).nth(i);
let text = conn_texts
.iter()
.find(|(n, _)| n.as_deref() == Some(port.name()))
.or(ordered)
.map(|(_, t)| t.clone());
let high_modport = text.is_some_and(|t| t.contains('.'));
self.interface_port(&port, i, parent, parent_name, by_name, high_modport);
continue;
}
if port.kind() != SymbolKind::Port {
continue;
}
let name = port.name().to_owned();
let full = format!("{parent_name}.{name}");
let mut obj = Obj::new(ObjKind::Port, Some(parent), &name, &full);
obj.props.port_type = header("vpiPort");
obj.props.explicit_name = self.port_is_explicit(&port);
obj.props.conn_by_name = by_name;
if let Some(h) = high.get(&name) {
obj.props.rels.push((consts::VPI_HIGH_CONN, *h));
}
obj.props.direction = match port.port_direction() {
ArgumentDirection::In => consts::VPI_INPUT,
ArgumentDirection::Out => consts::VPI_OUTPUT,
ArgumentDirection::InOut => consts::VPI_INOUT,
ArgumentDirection::Ref => consts::VPI_REF,
};
obj.props.index = Some(i as i32);
obj.props.file = Some(self.file.clone());
obj.props.line = self.locator.line(&port);
let internal = port.port_internal_symbol();
if let Some(ty) = internal.and_then(|s| s.value_type()) {
obj.props.width = ty.bit_width() as i32;
obj.props.signed = ty.is_signed();
obj.props.vector = ty.is_packed_array() || ty.bit_width() > 1;
let c = ty.canonical();
if c.is_packed_array() {
let r = c.fixed_range();
obj.props.range = Some((r.left, r.right));
} else if c.is_integral() && c.bit_width() > 1 {
obj.props.range = Some((c.bit_width() as i32 - 1, 0));
}
}
let internal_name = internal.map_or(name.clone(), |s| s.name().to_owned());
let target = self.model.children(parent).iter().copied().find(|&c| {
self.model
.get(c)
.is_some_and(|o| o.kind.is_data() && o.name == internal_name)
});
let id = self.add(obj);
self.attributes(port.attributes(), id, &full, false);
self.typespec_rel(id, port.port_type());
if port.port_direction() == ArgumentDirection::Ref {
let mut r = Obj::new(ObjKind::RefObj, Some(id), &name, &full);
r.props.file = Some(self.file.clone());
r.props.line = self.locator.line(&port);
if let Some(a) = high.get(&name) {
r.props.rels.push((header("vpiActual"), *a));
}
let rid = self.add(r);
self.typespec_rel(rid, port.port_type());
self.model.set_low_conn(id, rid);
} else if let Some(target) = target {
self.model.set_low_conn(id, target);
}
}
}
fn interface_port(
&mut self,
port: &Symbol<'_>,
i: usize,
parent: ObjId,
parent_name: &str,
by_name: bool,
high_modport: bool,
) {
let name = port.name().to_owned();
let full = format!("{parent_name}.{name}");
let modport = port.interface_port_modport().to_owned();
let mut obj = Obj::new(ObjKind::Port, Some(parent), &name, &full);
obj.props.port_type = header(if modport.is_empty() {
"vpiInterfacePort"
} else {
"vpiModportPort"
});
obj.props.direction = consts::VPI_INOUT;
obj.props.index = Some(i as i32);
obj.props.explicit_name = self.port_is_explicit(port);
obj.props.conn_by_name = by_name;
obj.props.file = Some(self.file.clone());
obj.props.line = self.locator.line(port);
let id = self.add(obj);
self.attributes(port.attributes(), id, &full, false);
let (inst, mp) = port.interface_port_connection();
let def = port
.interface_port_interface_def()
.map(|d| d.name().to_owned())
.unwrap_or_default();
let link = inst.map(|s| format!("{:?}", s.uid()));
let mp_name = mp.map(|m| m.name().to_owned());
let def_name = mp_name.clone().unwrap_or(def);
let mut low = Obj::new(ObjKind::RefObj, Some(id), &name, &full);
low.props.def_name = Some(def_name.clone());
low.props.generic = Some(port.interface_port_is_generic());
low.props.file = Some(self.file.clone());
low.props.line = self.locator.line(port);
let lid = self.add(low);
self.model.set_low_conn(id, lid);
let high_mp = mp_name.clone().filter(|_| high_modport);
let hname = match (&inst, &high_mp) {
(Some(s), Some(m)) => format!("{}.{m}", s.name()),
(Some(s), None) => s.name().to_owned(),
_ => String::new(),
};
let mut hi = Obj::new(ObjKind::RefObj, Some(id), &hname, &hname);
hi.props.def_name = Some(high_mp.clone().unwrap_or_else(|| {
inst.and_then(|s| s.instance_definition())
.map(|d| d.name().to_owned())
.unwrap_or(def_name)
}));
hi.props.generic = Some(port.interface_port_is_generic());
hi.props.file = Some(self.file.clone());
let hid = self.add(hi);
if let Some(p) = self.model.props_mut(id) {
p.rels.push((consts::VPI_HIGH_CONN, Operand::Obj(hid)));
}
if let Some(link) = link {
self.ref_links.push((lid, link.clone(), mp_name));
self.ref_links.push((hid, link, high_mp));
}
}
fn connection_texts(&self, inst: &Symbol<'_>) -> Vec<(Option<String>, String)> {
use sv_lang::kinds::SyntaxKind;
for t in self.locator.syntax_trees() {
let Some(node) = inst.syntax(&t) else {
continue;
};
let mut out = Vec::new();
for d in node.descendants() {
match d.kind() {
SyntaxKind::NamedPortConnection => {
if let Some(n) =
<sv_lang::nodes::NamedPortConnectionSyntax as sv_lang::AstNode>::cast(d)
{
let name = n.name().map(|t| t.value_text().to_owned());
let text = n
.expr()
.map(|e| sv_lang::AstNode::syntax(&e).text())
.unwrap_or_default();
out.push((name, text.trim().to_owned()));
}
}
SyntaxKind::OrderedPortConnection => {
out.push((None, d.text().trim().to_owned()));
}
_ => {}
}
}
return out;
}
Vec::new()
}
fn port_is_explicit(&self, port: &Symbol<'_>) -> bool {
use sv_lang::kinds::SyntaxKind;
self.locator.syntax_trees().iter().any(|t| {
let mut node = port.syntax(t);
while let Some(n) = node {
match n.kind() {
SyntaxKind::ExplicitAnsiPort | SyntaxKind::ExplicitNonAnsiPort => return true,
SyntaxKind::ImplicitAnsiPort
| SyntaxKind::ImplicitNonAnsiPort
| SyntaxKind::AnsiPortList
| SyntaxKind::NonAnsiPortList => return false,
_ => node = n.parent(),
}
}
false
})
}
fn instantiation_syntax(&self, inst: &Symbol<'_>) -> bool {
use sv_lang::kinds::SyntaxKind;
for t in self.locator.syntax_trees() {
let Some(node) = inst.syntax(&t) else {
continue;
};
let kinds: Vec<SyntaxKind> = node.descendants().map(|d| d.kind()).collect();
let ordered = kinds.contains(&SyntaxKind::OrderedPortConnection);
return !ordered
&& (kinds.contains(&SyntaxKind::NamedPortConnection)
|| kinds.contains(&SyntaxKind::WildcardPortConnection));
}
false
}
fn param_assigns(&mut self, inst: &Symbol<'_>, parent: ObjId, parent_name: &str) {
for o in inst.instance_parameter_overrides() {
let param = o.parameter;
let by_name = o.assignment == sv_lang::ParameterAssignment::Named;
let pname = param.name().to_owned();
let Some(lhs) = self.model.find(&format!("{parent_name}.{pname}")) else {
continue;
};
let mut a = Obj::new(ObjKind::ParamAssign, Some(parent), "", "");
a.props.conn_by_name = by_name;
a.props.file = Some(self.file.clone());
a.props.line = self.locator.line(¶m);
a.props.rels.push((consts::VPI_LHS, Operand::Obj(lhs)));
let rhs = if param.kind() == SymbolKind::TypeParameter {
param
.declared_type()
.and_then(|t| self.typespec(&t))
.map(Operand::Obj)
} else if let Some(op) = self.override_expression(&o) {
Some(op)
} else {
self.model
.get(lhs)
.and_then(|o| match &o.props.param {
Some(ParamValue::Logic(l, signed)) => Some(ExprNode::Const {
value: l.clone(),
signed: *signed,
const_type: consts::VPI_BINARY_CONST,
}),
_ => None,
})
.map(|n| Operand::Expr(self.model.add_expr(n)))
};
if let Some(r) = rhs {
a.props.rels.push((consts::VPI_RHS, r));
}
if let Some(e) = o.value_expr() {
let e = unwrap_invalid_expr(e);
let mut named = Vec::new();
Self::named_parameters(&e, &mut named);
let modelled = a.props.rels.iter().any(|(k, r)| {
*k == consts::VPI_RHS
&& match r {
Operand::Expr(x) => {
matches!(self.model.expr(*x), Some(ExprNode::Op { .. }))
}
Operand::Obj(p) => self
.model
.get(*p)
.is_some_and(|o| o.kind == ObjKind::Parameter),
}
});
if !modelled {
for p in named {
if let Some(id) = self.prop_by_uid.get(&p).copied() {
self.model.mark_param_folded(id);
}
}
}
}
self.add(a);
}
}
fn override_expression(&mut self, o: &sv_lang::ParameterOverride<'_>) -> Option<Operand> {
let e = unwrap_invalid_expr(o.value_expr()?);
if e.is_bad() {
return None;
}
let mut named = Vec::new();
Self::named_parameters(&e, &mut named);
if named.is_empty() {
return None;
}
self.operand(&e)
}
fn interface_param_assign(&mut self, ts: ObjId, name: &str, value: &str) {
let mut p = Obj::new(ObjKind::Parameter, Some(ts), name, "");
let rhs = match value.parse::<i64>() {
Ok(v) => {
let l = Logic::from_u64(v as u64, 32);
p.props.param = Some(ParamValue::Logic(l.clone(), true));
ExprNode::Const {
value: l,
signed: true,
const_type: consts::VPI_BINARY_CONST,
}
}
Err(_) => ExprNode::Unsupported(format!(
"the value `{value}` of interface parameter {name} (only integers are decoded)"
)),
};
let lhs = self.add(p);
let mut a = Obj::new(ObjKind::ParamAssign, Some(ts), "", "");
a.props.rels.push((consts::VPI_LHS, Operand::Obj(lhs)));
let e = self.model.add_expr(rhs);
a.props.rels.push((consts::VPI_RHS, Operand::Expr(e)));
self.add(a);
}
fn data_kind(ty: &Type<'_>, net: bool) -> ObjKind {
let var = Self::var_kind(ty);
if !net {
return var;
}
match var {
ObjKind::StructVar => ObjKind::StructNet,
ObjKind::UnionVar => ObjKind::UnionNet,
ObjKind::EnumVar => ObjKind::EnumNet,
ObjKind::IntegerVar => ObjKind::IntegerNet,
ObjKind::TimeVar => ObjKind::TimeNet,
ObjKind::BitVar => ObjKind::BitNet,
ObjKind::ByteVar => ObjKind::ByteNet,
ObjKind::ShortIntVar => ObjKind::ShortIntNet,
ObjKind::IntVar => ObjKind::IntNet,
ObjKind::LongIntVar => ObjKind::LongIntNet,
ObjKind::RealVar => ObjKind::RealNet,
ObjKind::ShortRealVar => ObjKind::ShortRealNet,
ObjKind::PackedArrayVar => ObjKind::PackedArrayNet,
_ => ObjKind::Net,
}
}
fn packed_array_of_aggregates(ty: &Type<'_>) -> bool {
let ty = ty.canonical();
if !ty.is_packed_array() {
return false;
}
let Some(elem) = ty.element_type().map(|e| e.canonical()) else {
return false;
};
elem.is_struct()
|| elem.is_packed_union()
|| elem.is_enum()
|| Self::packed_array_of_aggregates(&elem)
}
fn packed_array_objects(ty: &Type<'_>) -> bool {
let ty = ty.canonical();
ty.is_packed_array()
&& ty
.element_type()
.is_some_and(|e| e.canonical().is_packed_array())
|| Self::packed_array_of_aggregates(&ty)
}
fn var_kind(ty: &Type<'_>) -> ObjKind {
let ty = ty.canonical();
if ty.is_event() {
return ObjKind::NamedEvent;
}
if ty.is_virtual_interface() {
return ObjKind::VirtualInterfaceVar;
}
if ty.is_unpacked_array() || vj_sim::state::storage::is_collection(&ty) {
return ObjKind::RegArray;
}
if ty.is_class() {
return ObjKind::ClassVar;
}
if Self::packed_array_objects(&ty) {
return ObjKind::PackedArrayVar;
}
if ty.is_chandle() {
return ObjKind::ChandleVar;
}
if ty.is_floating() {
return match ty.floating_kind() {
FloatKind::ShortReal => ObjKind::ShortRealVar,
FloatKind::Real | FloatKind::RealTime => ObjKind::RealVar,
};
}
if ty.is_string() {
return ObjKind::StringVar;
}
if ty.is_enum() {
return ObjKind::EnumVar;
}
if ty.is_packed_union() || ty.is_unpacked_union() {
return ObjKind::UnionVar;
}
if ty.is_struct() {
return ObjKind::StructVar;
}
if ty.is_predefined_integer() {
return match ty.predefined_integer_kind() {
PredefinedIntegerKind::ShortInt => ObjKind::ShortIntVar,
PredefinedIntegerKind::Int => ObjKind::IntVar,
PredefinedIntegerKind::LongInt => ObjKind::LongIntVar,
PredefinedIntegerKind::Byte => ObjKind::ByteVar,
PredefinedIntegerKind::Integer => ObjKind::IntegerVar,
PredefinedIntegerKind::Time => ObjKind::TimeVar,
};
}
if ty.is_four_state() {
ObjKind::Reg
} else {
ObjKind::BitVar
}
}
fn data(&mut self, sym: &Symbol<'_>, parent: ObjId, parent_name: &str, net: bool) {
let first = self.model.len() as ObjId;
self.data_objects(sym, parent, parent_name, net);
let last = self.model.len() as ObjId;
if net && first != last {
let nt = net_type_const(crate::nets::net_kind_of(sym, &self.locator.syntax_trees()));
for id in first..last {
let unset = self
.model
.get(id)
.is_some_and(|o| o.kind.is_net() && o.props.net_type == 0);
if unset && let Some(p) = self.model.props_mut(id) {
p.net_type = nt;
}
}
}
if sym.value_type().is_some_and(|t| {
let mut e = t.canonical();
while e.is_unpacked_array()
&& let Some(x) = e.element_type()
{
e = x.canonical();
}
e.is_virtual_interface()
}) {
self.vif_details(sym, first, last);
}
if !net || first == last {
return;
}
let interconnect = sym
.value_type()
.is_some_and(|t| vj_sim::state::storage::interconnect_dims(&t).is_some());
let user = !interconnect && self.user_nettype_of(sym).is_some();
if !interconnect && !user {
return;
}
let elements: Vec<ObjId> = self.model.children(first).to_vec();
for id in first..last {
let Some(kind) = self.model.get(id).map(|o| o.kind) else {
continue;
};
if !kind.is_net() {
continue;
}
let whole = id == first
|| (elements.contains(&id)
&& self
.model
.get(first)
.is_some_and(|o| o.kind.is_data_array()));
if interconnect {
self.model.set_kind(
id,
if kind.is_data_array() {
ObjKind::InterconnectArray
} else {
ObjKind::InterconnectNet
},
);
if let Some(p) = self.model.props_mut(id) {
p.net_type = consts::VPI_INTERCONNECT;
p.rels.retain(|(r, _)| *r != header("vpiTypespec"));
}
} else if let Some(p) = self.model.props_mut(id) {
p.net_type = header(if whole {
"vpiNettypeNet"
} else {
"vpiNettypeNetSelect"
});
}
}
}
fn const_select_path(e: &sv_lang::Expression<'_>) -> Option<String> {
use sv_lang::kinds::ExpressionKind as K;
match e.kind() {
K::NamedValue => Some(e.referenced_symbol()?.hierarchical_path()),
K::ElementSelect => {
let base = Self::const_select_path(&e.select_value()?)?;
let i = e.selector()?.constant_value()?.as_i64()?;
Some(format!("{base}[{i}]"))
}
_ => None,
}
}
fn interconnect_typespecs(&mut self) {
let typespec = header("vpiTypespec");
let mut leaf: std::collections::HashMap<ObjId, ObjId> = std::collections::HashMap::new();
for (_, o) in self.model.objects() {
if o.kind != ObjKind::Port {
continue;
}
let ts = o.props.rels.iter().find_map(|(r, t)| match t {
Operand::Obj(t) if *r == typespec => Some(*t),
_ => None,
});
let high = o.props.rels.iter().find_map(|(r, t)| {
if *r != consts::VPI_HIGH_CONN {
return None;
}
match t {
Operand::Obj(t) => Some(*t),
Operand::Expr(_) => None,
}
});
let high = high.or_else(|| {
let (_, el) = self.select_conns.iter().find(|(p, _)| *p == o.full_name)?;
self.model.find(el)
});
if let (Some(ts), Some(h)) = (ts, high)
&& self
.model
.get(h)
.is_some_and(|n| n.kind == ObjKind::InterconnectNet)
{
leaf.entry(h).or_insert(ts);
}
}
let mut leaves: Vec<(ObjId, ObjId)> = leaf.into_iter().collect();
leaves.sort_unstable();
for (net, ts) in leaves {
self.rel(net, typespec, Operand::Obj(ts));
self.interconnect_members(net, ts);
}
let arrays: Vec<ObjId> = self
.model
.objects()
.filter(|(_, o)| o.kind == ObjKind::InterconnectArray)
.map(|(i, _)| i)
.collect();
for arr in arrays.into_iter().rev() {
let elem = self.model.children(arr).iter().find_map(|&c| {
self.model.get(c).and_then(|o| {
o.props.rels.iter().find_map(|(r, t)| match t {
Operand::Obj(t) if *r == typespec => Some(*t),
_ => None,
})
})
});
let Some(elem) = elem else { continue };
let Some(range) = self.model.get(arr).and_then(|o| o.props.range) else {
continue;
};
let mut ts = Obj::new(ObjKind::Typespec, None, "", "");
ts.props.stmt_type = header("vpiArrayTypespec");
ts.props.array_type = header("vpiStaticArray");
ts.props.file = Some(self.file.clone());
ts.props.dims = vec![range];
ts.props.range = Some(range);
let id = self.add(ts);
self.rel(id, header("vpiElemTypespec"), Operand::Obj(elem));
self.rel(arr, typespec, Operand::Obj(id));
}
}
fn interconnect_members(&mut self, net: ObjId, ts: ObjId) {
let struct_ts = [header("vpiStructTypespec"), header("vpiUnionTypespec")];
let is_struct = self
.model
.get(ts)
.is_some_and(|t| struct_ts.contains(&t.props.stmt_type));
if !is_struct {
return;
}
let Some(parent) = self.model.get(net).map(|n| n.full_name.clone()) else {
return;
};
for m in self.model.children_of_kind(ts, ObjKind::TypespecMember) {
let Some(mo) = self.model.get(m) else {
continue;
};
let name = mo.name.clone();
let mts = mo.props.rels.iter().find_map(|(r, t)| match t {
Operand::Obj(t) if *r == header("vpiTypespec") => Some(*t),
_ => None,
});
let line = mo.props.line;
let mut obj = Obj::new(
ObjKind::InterconnectNet,
Some(net),
&name,
&format!("{parent}.{name}"),
);
obj.props.net_type = consts::VPI_INTERCONNECT;
obj.props.file = Some(self.file.clone());
obj.props.line = line;
let id = self.add(obj);
if let Some(mts) = mts {
self.rel(id, header("vpiTypespec"), Operand::Obj(mts));
self.interconnect_members(id, mts);
}
}
}
fn vif_details(&mut self, sym: &Symbol<'_>, first: ObjId, last: ObjId) {
let Some(ty) = sym.value_type() else { return };
let mut elem = ty.canonical();
while elem.is_unpacked_array()
&& let Some(e) = elem.element_type()
{
elem = e.canonical();
}
let Some(VifShape { def, modport, .. }) = vif_shape(&elem) else {
return;
};
let targets: Vec<String> = vj_sim::netlist::vif::interface_instances(sym, &def)
.iter()
.map(|i| match &modport {
Some(m) => format!("{}.{m}", i.hierarchical_path()),
None => i.hierarchical_path(),
})
.collect();
let init = sym
.initializer()
.and_then(|e| {
e.referenced_symbol().or_else(|| {
e.as_sem_node()
.children()
.into_iter()
.find_map(|n| n.as_expression())
.and_then(|x| x.referenced_symbol())
})
})
.map(|s| s.hierarchical_path());
for id in first..last {
if self.model.get(id).map(|o| o.kind) != Some(ObjKind::VirtualInterfaceVar) {
continue;
}
if let Some(p) = self.model.props_mut(id) {
p.vif_targets = Some(targets.clone());
p.is_modport = modport.is_some();
}
if let Some(path) = &init {
let path = match &modport {
Some(m) if !path.ends_with(&format!(".{m}")) => format!("{path}.{m}"),
_ => path.clone(),
};
self.path_links.push((id, consts::VPI_EXPR, path));
}
}
}
fn user_nettype_of<'d>(&self, sym: &Symbol<'d>) -> Option<Symbol<'d>> {
let (name, _) = crate::usernets::declared_type_name(sym, &self.locator.syntax_trees())?;
let name = name.rsplit("::").next().unwrap_or(&name).to_owned();
Self::nettype_named(sym.parent()?, &name)
}
fn nettype_named<'d>(scope: Symbol<'d>, name: &str) -> Option<Symbol<'d>> {
let mut at = Some(scope);
while let Some(sc) = at {
if let Some(m) = sc.members().find(|m| {
m.kind() == SymbolKind::NetType && !m.net_type_is_built_in() && m.name() == name
}) {
return Some(m);
}
at = sc.parent();
}
None
}
fn nettype_decl(&mut self, sym: &Symbol<'_>, parent: ObjId, parent_name: &str) {
let name = sym.name().to_owned();
let full = format!("{parent_name}.{name}");
let mut obj = Obj::new(ObjKind::NettypeDecl, Some(parent), &name, &full);
obj.props.file = Some(self.file.clone());
obj.props.line = self.locator.line(sym);
let id = self.add(obj);
let target = crate::usernets::alias_target(sym, &self.locator.syntax_trees());
let alias = target
.as_deref()
.and_then(|t| Self::nettype_named(sym.parent()?, t.rsplit("::").next().unwrap_or(t)))
.filter(|t| t.uid() != sym.uid());
if let Some(a) = alias {
self.path_links
.push((id, header("vpiNetTypedefAlias"), a.hierarchical_path()));
}
self.typespec_rel(id, sym.declared_type());
let mut func = sym.net_type_resolution_function();
let mut next = alias;
for _ in 0..32 {
let Some(n) = next.filter(|_| func.is_none()) else {
break;
};
func = n.net_type_resolution_function();
next = crate::usernets::alias_target(&n, &self.locator.syntax_trees())
.and_then(|t| Self::nettype_named(n.parent()?, t.rsplit("::").next().unwrap_or(&t)))
.filter(|t| t.uid() != n.uid());
}
if let Some(f) = func {
self.path_links
.push((id, header("vpiWith"), f.hierarchical_path()));
}
}
#[allow(clippy::too_many_arguments)]
fn array_words(
&mut self,
arr_ty: &Type<'_>,
arr: ObjId,
name: &str,
full: &str,
row: i64,
lower: i64,
net: bool,
line: i32,
key: &dyn Fn(Option<i64>) -> SigKey,
) {
let Some(elem) = arr_ty.canonical().element_type() else {
return;
};
let range = arr_ty.canonical().fixed_range();
let count = range.left.abs_diff(range.right) as usize + 1;
let nested = elem.canonical().is_unpacked_array();
let stride: i64 = if nested {
Self::dims_of(&elem)
.iter()
.map(|&(l, r)| i64::from(l.abs_diff(r)) + 1)
.product()
} else {
1
};
let Some((kind, width, signed, vector)) = self
.model
.get(arr)
.map(|o| (o.kind, o.props.width, o.props.signed, o.props.vector))
else {
return;
};
for i in 0..count {
let idx = element_index(range, i);
let ename = format!("{name}[{idx}]");
let efull = format!("{full}[{idx}]");
let step = i64::from(idx - range.left.min(range.right));
let erow = row + step * stride;
if nested {
let ec = elem.canonical();
let inner = ec.fixed_range();
let mut e = Obj::new(kind, Some(arr), &ename, &efull);
e.props.size = Some((inner.left.abs_diff(inner.right) + 1) as i32);
e.props.range = Some((inner.left, inner.right));
e.props.dims = Self::dims_of(&ec);
e.props.array_key = Some(key(None));
e.props.index = Some(idx);
e.props.width = width;
e.props.vector = vector;
e.props.signed = signed;
e.props.file = Some(self.file.clone());
e.props.line = line;
let eid = self.add(e);
self.typespec_rel(eid, Some(elem));
self.array_words(&ec, eid, &ename, &efull, erow, lower, net, line, key);
continue;
}
let mut e = Obj::new(Self::data_kind(&elem, net), Some(arr), &ename, &efull);
Self::fill_scalar_props(&mut e.props, &elem);
e.props.index = Some(idx);
e.props.sig = Some(key(Some(lower + erow)));
e.props.file = Some(self.file.clone());
e.props.line = line;
let eid = self.add(e);
self.typespec_rel(eid, Some(elem));
self.parts(&elem, eid, &ename, &efull, 0, net, line);
}
}
fn struct_members(&mut self, ty: &Type<'_>) -> Vec<super::dynarr::MemberPart> {
use vj_codegen::types::TypeWidth;
use vj_sim::state::storage;
let ty = ty.canonical();
if !(ty.is_unpacked_struct() || ty.is_unpacked_union()) {
return Vec::new();
}
if storage::storage_width(&ty) != Some(u64::from(ty.value_width())) {
return Vec::new();
}
let mut out = Vec::new();
if self.push_members(&ty, 0, None, &mut out) {
out
} else {
Vec::new()
}
}
fn push_members(
&mut self,
ty: &Type<'_>,
bit0: u32,
parent: Option<u32>,
out: &mut Vec<super::dynarr::MemberPart>,
) -> bool {
use vj_codegen::types::TypeWidth;
use vj_sim::state::storage;
let scalar = |t: &Type<'_>| (t.is_integral() || t.is_floating()) && !t.is_unpacked_array();
for f in ty.fields() {
let (Some(fty), Some((lo, w))) =
(f.value_type(), storage::field_slice(ty, f.field_index()))
else {
return false;
};
let fty = fty.canonical();
if fty.is_void() {
continue;
}
let lo = bit0 + lo as u32;
let nested = fty.is_unpacked_struct() || fty.is_unpacked_union();
let tag = match storage::tagged_tag_slice(ty) {
Some((tlo, tw)) if tw > 0 => Some(super::dynarr::TagCond {
base: bit0,
payload: tlo as u32,
tag_lo: bit0 + tlo as u32,
tag_w: tw as u32,
value: f.field_index(),
}),
_ => None,
};
let elem = if fty.is_unpacked_array() && !fty.is_dynamically_sized_array() {
fty.element_type().filter(|e| scalar(&e.canonical()))
} else {
None
};
if !nested && elem.is_none() && !scalar(&fty) {
return false;
}
out.push(super::dynarr::MemberPart {
name: f.name().to_owned(),
kind: Self::data_kind(&fty, false),
width: w as u32,
signed: fty.is_signed(),
lo,
parent,
index: None,
typespec: self.typespec(&fty),
tag,
});
let me = (out.len() - 1) as u32;
if nested {
if !self.push_members(&fty, lo, Some(me), out) {
return false;
}
} else if let Some(e) = elem {
let range = fty.fixed_range();
let ew = e.value_width().max(1);
let count = range.left.abs_diff(range.right) + 1;
if u64::from(ew) * u64::from(count) != w {
return false;
}
let low = range.left.min(range.right);
let step = if range.left <= range.right { 1 } else { -1 };
for k in 0..count {
let idx = i64::from(range.left) + i64::from(step) * i64::from(k);
let r = (idx - i64::from(low)) as u32;
out.push(super::dynarr::MemberPart {
name: format!("[{idx}]"),
kind: Self::data_kind(&e, false),
width: ew,
signed: e.is_signed(),
lo: lo + r * ew,
parent: Some(me),
index: Some(idx),
typespec: self.typespec(&e),
tag: None,
});
}
}
}
true
}
fn dyn_shape(&mut self, canon: &Type<'_>) -> Option<super::dynarr::DynShape> {
use vj_codegen::types::TypeWidth;
let elem = canon.element_type()?;
let obj_elems = elem.canonical().is_class();
let width = if obj_elems {
64
} else {
elem.value_width().max(1)
};
let key = if canon.is_associative_array() {
match canon.associative_index_type() {
Some(k) if k.is_string() => vj_sim::collections::KeyKind::Str,
Some(k) => vj_sim::collections::KeyKind::Int {
width: k.value_width().max(1),
signed: k.is_signed(),
},
None => vj_sim::collections::KeyKind::Int {
width: 64,
signed: false,
},
}
} else {
vj_sim::collections::KeyKind::Int {
width: 32,
signed: true,
}
};
let elem_typespec = self.typespec(&elem);
let inner = if vj_sim::state::storage::is_collection(&elem.canonical()) {
self.dyn_shape(&elem.canonical()).map(Box::new)
} else {
None
};
let ec = elem.canonical();
let fixed = if ec.is_unpacked_array() && !ec.is_dynamically_sized_array() {
ec.element_type()
.filter(|e| e.canonical().is_integral())
.map(|e| {
let range = ec.fixed_range();
super::dynarr::FixedElems {
left: range.left,
right: range.right,
elem_kind: Self::data_kind(&e, false),
width: e.value_width().max(1),
signed: e.is_signed(),
elem_typespec: self.typespec(&e),
}
})
} else {
None
};
let members = self.struct_members(&ec);
Some(super::dynarr::DynShape {
members,
fixed,
elem_kind: Self::data_kind(&elem, false),
width,
signed: elem.is_signed(),
assoc: canon.is_associative_array(),
queue: canon.is_queue(),
key,
obj: obj_elems,
elem_typespec,
inner,
})
}
fn data_objects(&mut self, sym: &Symbol<'_>, parent: ObjId, parent_name: &str, net: bool) {
let Some(ty) = sym.value_type() else { return };
let Some(body) = self.body.clone() else {
return;
};
let name = sym.name().to_owned();
if name.is_empty() {
return;
}
let full = format!("{parent_name}.{name}");
let key = |index: Option<i64>| SigKey {
body: body.clone(),
sym: sym.uid(),
index,
};
let key_of = key;
let line = self.locator.line(sym);
let canon = ty.canonical();
if vj_sim::state::storage::is_collection(&canon) && !net {
let Some(shape) = self.dyn_shape(&canon) else {
return;
};
let width = shape.width;
let elem = canon.element_type().expect("a collection");
let mut obj = Obj::new(ObjKind::RegArray, Some(parent), &name, &full);
obj.props.array_type = header(if canon.is_associative_array() {
"vpiAssocArray"
} else if canon.is_queue() {
"vpiQueueArray"
} else {
"vpiDynamicArray"
});
obj.props.dyn_elem = Some(shape);
obj.props.width = width as i32;
obj.props.signed = elem.is_signed();
obj.props.sig = Some(key_of(None));
obj.props.file = Some(self.file.clone());
obj.props.line = line;
let id = self.add(obj);
self.attributes(sym.attributes(), id, &full, false);
self.typespec_rel(id, Some(ty));
return;
}
if canon.is_unpacked_array() && !canon.is_dynamically_sized_array() {
let Some(elem) = canon.element_type() else {
return;
};
let range = canon.fixed_range();
let count = range.left.abs_diff(range.right) as usize + 1;
let mut obj = Obj::new(
if net {
ObjKind::NetArray
} else if elem.canonical().is_event() {
ObjKind::NamedEventArray
} else {
ObjKind::RegArray
},
Some(parent),
&name,
&full,
);
obj.props.size = Some(count as i32);
obj.props.range = Some((range.left, range.right));
obj.props.dims = Self::dims_of(&canon);
obj.props.array_key = Some(key(None));
obj.props.width = elem.bit_width() as i32;
obj.props.vector = Self::elem_is_vector(&elem);
obj.props.signed = elem.is_signed();
obj.props.file = Some(self.file.clone());
obj.props.line = line;
let id = self.add(obj);
if !net
&& let Some(init) = sym.initializer()
&& let Some(op) = self.operand(&init)
{
self.rel(id, consts::VPI_EXPR, op);
}
self.attributes(sym.attributes(), id, &full, false);
self.typespec_rel(id, Some(ty));
let total = Self::dims_of(&canon)
.iter()
.map(|&(l, r)| l.abs_diff(r) as usize + 1)
.product::<usize>();
if count > MAX_ELEMENTS || total > MAX_ELEMENTS {
return;
}
let lower = i64::from(range.left.min(range.right));
self.array_words(&canon, id, &name, &full, 0, lower, net, line, &key);
return;
}
let mut obj = Obj::new(Self::data_kind(&ty, net), Some(parent), &name, &full);
Self::fill_scalar_props(&mut obj.props, &ty);
if net {
obj.props.net_type =
net_type_const(crate::nets::net_kind_of(sym, &self.locator.syntax_trees()));
}
if net {
obj.props.expansion_hint =
match crate::nets::expansion_hint_of(sym, &self.locator.syntax_trees()) {
Some(true) => 1,
Some(false) => 2,
None => 0,
};
obj.props.implicit = sym.net_is_implicit();
}
obj.props.sig = Some(key(None));
obj.props.const_var = sym.variable_flags().contains(sv_lang::VariableFlags::CONST);
obj.props.cont_driven = vj_codegen::force::continuously_driven(sym);
obj.props.file = Some(self.file.clone());
obj.props.line = if obj.props.implicit { 0 } else { line };
let id = self.add(obj);
if !net
&& self.model.get(id).map(|o| o.kind) != Some(ObjKind::VirtualInterfaceVar)
&& let Some(init) = sym.initializer()
&& let Some(op) = self.operand(&init)
{
self.rel(id, consts::VPI_EXPR, op);
}
self.attributes(sym.attributes(), id, &full, false);
self.typespec_rel(id, Some(ty));
if canon.is_packed_array() {
self.range_exprs(sym, id);
}
self.parts(&ty, id, &name, &full, 0, net, line);
}
#[allow(clippy::too_many_arguments)]
fn parts(
&mut self,
ty: &Type<'_>,
owner: ObjId,
owner_name: &str,
owner_full: &str,
base: u32,
net: bool,
line: i32,
) {
let canon = ty.canonical();
if Self::packed_array_objects(&canon) {
let Some(elem) = canon.element_type() else {
return;
};
let range = canon.fixed_range();
let count = range.left.abs_diff(range.right) as usize + 1;
if count > MAX_ELEMENTS {
return;
}
let ew = elem.bit_width() as u32;
for i in 0..count {
let idx = element_index(range, i);
let lsb = base + (idx - range.right).unsigned_abs() * ew;
let name = format!("{owner_name}[{idx}]");
let full = format!("{owner_full}[{idx}]");
let mut e = Obj::new(Self::data_kind(&elem, net), Some(owner), &name, &full);
Self::fill_scalar_props(&mut e.props, &elem);
e.props.index = Some(idx);
e.props.field = Some((lsb, ew));
e.props.packed_member = true;
e.props.file = Some(self.file.clone());
e.props.line = line;
let id = self.add(e);
self.typespec_rel(id, Some(elem));
self.parts(&elem, id, &name, &full, lsb, net, line);
}
return;
}
if !(canon.is_struct() || canon.is_packed_union() || canon.is_unpacked_union()) {
return;
}
let unpacked = vj_sim::state::storage::is_unpacked_aggregate(&canon);
for f in canon.fields() {
let Some(fty) = f.value_type() else { continue };
let slice = if unpacked {
vj_sim::state::storage::field_slice(&canon, f.field_index())
} else {
Some((f.field_bit_offset(), fty.bit_width()))
};
let Some((lo, w)) = slice else { continue };
let lsb = base + lo as u32;
let name = f.name().to_owned();
let full = format!("{owner_full}.{name}");
let fc = fty.canonical();
if fc.is_unpacked_array() && !fc.is_dynamically_sized_array() {
let Some(elem) = fc.element_type() else {
continue;
};
let Some(ew) = vj_sim::state::storage::storage_width(&elem) else {
continue;
};
let range = fc.fixed_range();
let count = range.left.abs_diff(range.right) as usize + 1;
let mut a = Obj::new(
if net {
ObjKind::NetArray
} else {
ObjKind::RegArray
},
Some(owner),
&name,
&full,
);
a.props.size = Some(count as i32);
a.props.range = Some((range.left, range.right));
a.props.width = elem.bit_width() as i32;
a.props.vector = Self::elem_is_vector(&elem);
a.props.signed = elem.is_signed();
a.props.member = true;
a.props.dims = Self::dims_of(&fc);
a.props.file = Some(self.file.clone());
a.props.line = line;
let aid = self.add(a);
self.typespec_rel(aid, Some(fty));
if count > MAX_ELEMENTS || elem.is_unpacked_array() {
continue;
}
let low = range.left.min(range.right);
for i in 0..count {
let idx = element_index(range, i);
let elsb = lsb + (idx - low) as u32 * ew as u32;
let ename = format!("{name}[{idx}]");
let efull = format!("{full}[{idx}]");
let mut e = Obj::new(Self::data_kind(&elem, net), Some(aid), &ename, &efull);
Self::fill_scalar_props(&mut e.props, &elem);
e.props.index = Some(idx);
e.props.field = Some((elsb, ew as u32));
e.props.file = Some(self.file.clone());
e.props.line = line;
let eid = self.add(e);
self.typespec_rel(eid, Some(elem));
self.parts(&elem, eid, &ename, &efull, elsb, net, line);
}
continue;
}
let mut m = Obj::new(Self::data_kind(&fty, net), Some(owner), &name, &full);
Self::fill_scalar_props(&mut m.props, &fty);
m.props.field = Some((lsb, w as u32));
m.props.member = true;
m.props.file = Some(self.file.clone());
m.props.line = line;
let id = self.add(m);
self.typespec_rel(id, Some(fty));
self.parts(&fty, id, &name, &full, lsb, net, line);
}
}
fn elem_is_vector(elem: &Type<'_>) -> bool {
let canon = elem.canonical();
canon.is_packed_array() || (canon.is_integral() && canon.bit_width() > 1)
}
fn fill_scalar_props(p: &mut Props, ty: &Type<'_>) {
let canon = ty.canonical();
p.packed = (canon.is_struct() && !canon.is_unpacked_struct())
|| canon.is_packed_union()
|| canon.is_enum()
|| Self::packed_array_objects(&canon);
p.tagged = (canon.is_packed_union() && canon.packed_union_is_tagged())
|| (canon.is_unpacked_union() && canon.unpacked_union_is_tagged());
p.soft = canon.is_packed_union() && canon.packed_union_is_soft();
if vj_sim::state::storage::is_unpacked_aggregate(&canon) {
p.members = Some(canon.fields().count() as i32);
}
p.width = canon.bit_width() as i32;
p.signed = canon.is_signed();
p.vector = canon.is_packed_array() || (canon.is_integral() && canon.bit_width() > 1);
if canon.is_packed_array() {
let r = canon.fixed_range();
p.range = Some((r.left, r.right));
p.dims = Self::dims_of(&canon);
} else if canon.is_integral() && canon.bit_width() > 1 {
p.range = Some((canon.bit_width() as i32 - 1, 0));
}
}
fn attributes<'a>(
&mut self,
attrs: impl Iterator<Item = Symbol<'a>>,
owner: ObjId,
owner_full: &str,
def: bool,
) {
for a in attrs {
let name = a.name().to_owned();
let mut obj = Obj::new(
ObjKind::Attribute,
Some(owner),
&name,
&format!("{owner_full}.{name}"),
);
Self::fill_value(&mut obj.props, &a.attribute_value());
obj.props.def_attribute = def;
obj.props.file = Some(self.file.clone());
obj.props.line = self.locator.line(&a);
obj.props.def_file = Some(self.file.clone());
obj.props.def_line = obj.props.line;
self.add(obj);
}
}
fn process(&mut self, block: &Symbol<'_>, parent: ObjId, parent_name: &str) {
use sv_lang::ProceduralBlockKind as P;
let c = |n: &str| consts::value_of(n).expect("a header constant") as i32;
let mut obj = Obj::new(ObjKind::Process, Some(parent), "", "");
let (ty, always) = match block.procedural_block_procedure_kind() {
P::Initial => (c("vpiInitial"), 0),
P::Final => (c("vpiFinal"), 0),
P::Always => (c("vpiAlways"), c("vpiAlways")),
P::AlwaysComb => (c("vpiAlways"), c("vpiAlwaysComb")),
P::AlwaysLatch => (c("vpiAlways"), c("vpiAlwaysLatch")),
P::AlwaysFF => (c("vpiAlways"), c("vpiAlwaysFF")),
};
obj.props.stmt_type = ty;
obj.props.always_type = always;
obj.props.dyn_key = self.body.clone().map(|b| (b, block.uid()));
obj.props.file = Some(self.file.clone());
obj.props.line = self.locator.line(block);
let id = self.add(obj);
if let Some(body) = vj_sim::process::cfg::proc_body(block)
&& let Some(s) = self.stmt(&body, id, parent_name)
{
self.rel(id, consts::VPI_STMT, Operand::Obj(s));
}
}
fn local_var(&mut self, sym: &Symbol<'_>, parent: ObjId, scope: &str) {
let first = self.model.len() as ObjId;
self.data(sym, parent, scope, false);
let last = self.model.len() as ObjId;
if sym.variable_lifetime() == VariableLifetime::Automatic {
for id in first..last {
if let Some(p) = self.model.props_mut(id) {
p.automatic = true;
}
}
}
}
fn scope_locals(
&mut self,
scope_sym: &Symbol<'_>,
parent: ObjId,
scope: &str,
skip: Option<SymbolId>,
) {
for m in scope_sym.members() {
if m.kind() == SymbolKind::Variable && Some(m.uid()) != skip {
self.local_var(&m, parent, scope);
}
}
}
fn interface_tf_decl(&mut self, proto: &Symbol<'_>, parent: ObjId, parent_name: &str) {
let Some(t) = vj_sim::netlist::itf::extern_targets(proto) else {
return;
};
let name = proto.name().to_owned();
let mut obj = Obj::new(
ObjKind::InterfaceTfDecl,
Some(parent),
&name,
&format!("{parent_name}.{name}"),
);
obj.props.access_type = header(if t.fork_join {
"vpiForkJoinAcc"
} else {
"vpiExternAcc"
});
obj.props.file = Some(self.file.clone());
obj.props.line = self.locator.line(proto);
let id = self.add(obj);
for imp in t.impls {
let rel = match imp.subroutine_kind() {
Some(sv_lang::SubroutineKind::Task) => header("vpiTask"),
_ => header("vpiFunction"),
};
self.tf_rel_links.push((id, rel, imp.uid()));
}
}
fn task_func(&mut self, sub: &Symbol<'_>, parent: ObjId, parent_name: &str, sep: &str) {
let c = |n: &str| consts::value_of(n).expect("a header constant") as i32;
let name = sub.name().to_owned();
let full = format!("{parent_name}{sep}{name}");
let mut obj = Obj::new(ObjKind::TaskFunc, Some(parent), &name, &full);
let uid = sub.uid();
obj.props.stmt_type = match sub.subroutine_kind() {
Some(sv_lang::SubroutineKind::Task) => c("vpiTask"),
_ => c("vpiFunction"),
};
obj.props.automatic =
sub.subroutine_default_lifetime() == Some(VariableLifetime::Automatic);
obj.props.virtual_ = sub.subroutine_is_virtual();
let quals = self
.locator
.trees
.iter()
.find_map(|(t, _)| sub.syntax(t))
.map(|mut n| {
if let Some(p) = n.parent()
&& p.kind() == sv_lang::kinds::SyntaxKind::ClassMethodDeclaration
{
n = p;
}
n.text().to_string()
})
.unwrap_or_default();
let quals = quals
.split(|ch: char| !(ch.is_alphanumeric() || ch == '_'))
.take_while(|w| !matches!(*w, "function" | "task"))
.collect::<Vec<_>>();
obj.props.visibility = if quals.contains(&"local") {
c("vpiLocalVis")
} else if quals.contains(&"protected") {
c("vpiProtectedVis")
} else {
0
};
obj.props.tf = Some(self.tf_info(sub, parent));
let tf_obj_id = self.model.len() as ObjId;
self.tf_by_uid.insert(uid, tf_obj_id);
obj.props.dyn_key = Some((self.body.clone().unwrap_or_else(|| sub.uid()), sub.uid()));
obj.props.file = Some(self.file.clone());
obj.props.line = self.locator.line(sub);
let id = self.add(obj);
self.attributes(sub.attributes(), id, &full, false);
for arg in sub.subroutine_arguments() {
let aname = arg.name().to_owned();
let mut io = Obj::new(
ObjKind::IODecl,
Some(id),
&aname,
&format!("{full}.{aname}"),
);
io.props.direction = match arg.formal_argument_direction() {
ArgumentDirection::In => consts::VPI_INPUT,
ArgumentDirection::Out => consts::VPI_OUTPUT,
ArgumentDirection::InOut => consts::VPI_INOUT,
ArgumentDirection::Ref => consts::VPI_REF,
};
if let Some(ty) = arg.value_type() {
io.props.width = ty.bit_width() as i32;
io.props.signed = ty.is_signed();
}
io.props.file = Some(self.file.clone());
io.props.line = self.locator.line(&arg);
let io = self.add(io);
self.typespec_rel(io, arg.value_type());
if arg.formal_argument_direction() == ArgumentDirection::Ref {
let mut r = Obj::new(
ObjKind::RefObj,
Some(id),
&aname,
&format!("{full}.{aname}"),
);
if let Some(ty) = arg.value_type() {
r.props.width = ty.bit_width() as i32;
r.props.signed = ty.is_signed();
r.props.vector = ty.is_packed_array() || ty.bit_width() > 1;
let c = ty.canonical();
if c.is_packed_array() {
let rg = c.fixed_range();
r.props.range = Some((rg.left, rg.right));
} else if c.is_integral() && c.bit_width() > 1 {
r.props.range = Some((c.bit_width() as i32 - 1, 0));
}
}
r.props.file = Some(self.file.clone());
r.props.line = self.locator.line(&arg);
let rid = self.add(r);
self.typespec_rel(rid, arg.value_type());
self.tf_ref_by_uid.insert(arg.uid(), rid);
}
}
if let Some(rv) = sub.subroutine_return_val_var()
&& let Some(ty) = rv.value_type()
&& !ty.canonical().is_void()
{
let mut v = Obj::new(
Self::data_kind(&ty, false),
Some(id),
&name,
&format!("{full}.{name}"),
);
Self::fill_scalar_props(&mut v.props, &ty);
v.props.automatic = true;
v.props.file = Some(self.file.clone());
v.props.line = self.locator.line(sub);
let v = self.add(v);
self.typespec_rel(v, Some(ty));
self.rel(id, header("vpiReturn"), Operand::Obj(v));
}
let result = sub.subroutine_return_val_var().map(|r| r.uid());
self.scope_locals(sub, id, &full, result);
self.assertions(sub, id, &full);
if let Some(body) = vj_sim::process::cfg::proc_body(sub) {
let mut items = Vec::new();
Self::flat(&body, &mut items);
for item in items {
if let Some(s) = self.stmt(&item, id, &full) {
self.rel(id, consts::VPI_STMT, Operand::Obj(s));
}
}
}
}
fn tf_info(&self, sub: &Symbol<'_>, parent: ObjId) -> TfInfo {
let c = |n: &str| consts::value_of(n).expect("a header constant") as i32;
let mut tf = TfInfo {
method: self
.model
.get(parent)
.is_some_and(|o| o.kind == ObjKind::ClassDefn),
..TfInfo::default()
};
if let Some(ty) = sub.subroutine_return_type() {
let canon = ty.canonical();
tf.size = if canon.is_void() {
0
} else {
ty.bit_width() as i32
};
tf.signed = !canon.is_void() && ty.is_signed();
tf.func_type = if canon.is_floating() {
c("vpiRealFunc")
} else if canon.is_predefined_integer()
&& canon.predefined_integer_kind() == PredefinedIntegerKind::Time
{
c("vpiTimeFunc")
} else if canon.is_predefined_integer()
&& matches!(
canon.predefined_integer_kind(),
PredefinedIntegerKind::Int | PredefinedIntegerKind::Integer
)
{
c("vpiIntFunc")
} else if tf.signed {
c("vpiSizedSignedFunc")
} else {
c("vpiSizedFunc")
};
}
if sub
.subroutine_flags()
.contains(sv_lang::MethodFlags::DPI_IMPORT)
{
tf.access = c("vpiDPIImportAcc");
let text = self
.locator
.trees
.iter()
.find_map(|(t, _)| sub.syntax(t))
.map(|n| n.text().to_string())
.unwrap_or_default();
let d = parse_dpi_import(&text, sub.name());
tf.dpi_pure = d.pure;
tf.dpi_context = d.context;
tf.dpi_cstr = c(if d.spec == "DPI" { "vpiDPI" } else { "vpiDPIC" });
tf.dpi_ident = Some(d.ident);
}
tf
}
fn dpi_exports(&mut self, design: &Design) {
let c = |n: &str| consts::value_of(n).expect("a header constant") as i32;
for e in design.dpi_exports() {
let Some(&id) = self.tf_by_uid.get(&e.subroutine().uid()) else {
continue;
};
let spec = self
.locator
.trees
.iter()
.find_map(|(t, _)| e.syntax(t))
.map(|n| n.text().to_string())
.and_then(|t| t.split('"').nth(1).map(str::to_owned))
.unwrap_or_else(|| "DPI-C".to_owned());
if let Some(tf) = self.model.props_mut(id).and_then(|p| p.tf.as_mut()) {
tf.access = c("vpiDPIExportAcc");
tf.dpi_cstr = c(if spec == "DPI" { "vpiDPI" } else { "vpiDPIC" });
tf.dpi_ident = Some(e.c_identifier());
}
}
}
fn rel(&mut self, id: ObjId, kind: i32, target: Operand) {
if let Some(p) = self.model.props_mut(id) {
p.rels.push((kind, target));
}
}
fn pattern(&mut self, p: &sv_lang::Pattern<'_>, parent: ObjId) -> Option<Operand> {
use sv_lang::kinds::PatternKind as P;
let c = |n: &str| consts::value_of(n).expect("a header constant") as i32;
let (ty, name) = match p.kind() {
P::Constant => return p.value_expr().and_then(|e| self.operand(&e)),
P::Wildcard => (c("vpiAnyPattern"), String::new()),
P::Variable => (
c("vpiAnyPattern"),
p.variable()
.map(|v| v.name().to_owned())
.unwrap_or_default(),
),
P::Tagged => (
c("vpiTaggedPattern"),
self.pattern_shape(p)
.and_then(|(t, _)| t)
.unwrap_or_default(),
),
P::Structure => (c("vpiStructPattern"), String::new()),
other => {
return Some(Operand::Expr(
self.model
.add_expr(ExprNode::Unsupported(format!("a {other:?} pattern"))),
));
}
};
let mut obj = Obj::new(ObjKind::Pattern, Some(parent), &name, "");
obj.props.stmt_type = ty;
obj.props.file = Some(self.file.clone());
let id = self.add(obj);
let kids: Vec<sv_lang::Pattern<'_>> = p
.children()
.into_iter()
.filter_map(|n| n.as_pattern())
.collect();
let names = self
.pattern_shape(p)
.and_then(|(_, s)| s)
.unwrap_or_default();
for (k, kid) in kids.iter().enumerate() {
if let Some(op) = self.pattern(kid, id) {
if let (Operand::Obj(o), Some(Some(n))) = (op, names.get(k)) {
self.model.set_name(o, n);
}
self.rel(id, header("vpiPattern"), op);
}
}
Some(Operand::Obj(id))
}
#[allow(clippy::type_complexity)]
fn pattern_shape(
&self,
p: &sv_lang::Pattern<'_>,
) -> Option<(Option<String>, Option<Vec<Option<String>>>)> {
use sv_lang::AstNode;
use sv_lang::nodes::{
ParenthesizedPatternSyntax, StructurePatternMemberSyntax, StructurePatternSyntax,
TaggedPatternSyntax,
};
for tree in self.locator.syntax_trees() {
let Some(mut node) = p.syntax(&tree) else {
continue;
};
while let Some(paren) = ParenthesizedPatternSyntax::cast(node) {
node = paren.pattern().syntax();
}
if let Some(t) = TaggedPatternSyntax::cast(node) {
return Some((t.member_name().map(|m| m.text().to_string()), None));
}
if let Some(s) = StructurePatternSyntax::cast(node) {
let names = s
.members()
.iter()
.map(|m| match m {
StructurePatternMemberSyntax::NamedStructurePatternMember(n) => {
n.name().map(|t| t.text().to_string())
}
StructurePatternMemberSyntax::OrderedStructurePatternMember(_) => None,
})
.collect();
return Some((None, Some(names)));
}
return None;
}
None
}
fn flat<'d>(s: &sv_lang::Statement<'d>, out: &mut Vec<sv_lang::Statement<'d>>) {
if s.kind() == sv_lang::kinds::StatementKind::List {
for c in vj_sim::process::cfg::live_statements(s) {
Self::flat(&c, out);
}
} else {
out.push(*s);
}
}
fn timing_control_obj(
&mut self,
tc: &sv_lang::TimingControl<'_>,
parent: ObjId,
line: i32,
) -> Option<ObjId> {
use sv_lang::kinds::TimingControlKind as T;
let c = |n: &str| consts::value_of(n).expect("a header constant") as i32;
let (ty, rel) = match tc.kind() {
T::Delay | T::Delay3 | T::OneStepDelay | T::CycleDelay => {
(c("vpiDelayControl"), consts::VPI_DELAY)
}
T::RepeatedEvent => (c("vpiRepeatControl"), consts::VPI_CONDITION),
_ => (c("vpiEventControl"), consts::VPI_CONDITION),
};
let scope = self.model.get(parent).map(|o| o.full_name.clone())?;
let mut obj = Obj::new(ObjKind::Stmt, Some(parent), "", "");
obj.props.stmt_type = ty;
obj.props.file = Some(self.file.clone());
obj.props.line = line;
let id = self.add(obj);
let e = tc.children().into_iter().find_map(|n| n.as_expression());
let edge = (tc.kind() == T::SignalEvent)
.then(|| match tc.edge() {
sv_lang::EdgeKind::PosEdge => Some(c("vpiPosedgeOp")),
sv_lang::EdgeKind::NegEdge => Some(c("vpiNegedgeOp")),
_ => None,
})
.flatten();
let _ = scope;
if let Some(sig) = e
&& let Some(operand) = self.operand(&sig)
{
let op = match edge {
Some(op_type) => Operand::Expr(self.model.add_expr(ExprNode::Op {
op_type,
width: 1,
operands: vec![operand],
meta: OpMeta::default(),
})),
None => operand,
};
self.rel(id, rel, op);
}
Some(id)
}
fn var_init_stmt(
&mut self,
var: ObjId,
init: &sv_lang::Expression<'_>,
parent: ObjId,
) -> Option<ObjId> {
let (file, line) = self
.model
.get(parent)
.map(|o| (o.props.file.clone(), o.props.line))?;
let mut obj = Obj::new(ObjKind::Stmt, Some(parent), "", "");
obj.props.stmt_type = consts::value_of("vpiAssignment").expect("a header constant") as i32;
obj.props.blocking = true;
obj.props.op_type = consts::value_of("vpiAssignmentOp").expect("a header constant") as i32;
obj.props.file = file;
obj.props.line = line;
let id = self.add(obj);
self.rel(id, consts::VPI_LHS, Operand::Obj(var));
if let Some(op) = self.operand(init) {
self.rel(id, consts::VPI_RHS, op);
}
Some(id)
}
fn for_clause_stmt(&mut self, e: &sv_lang::Expression<'_>, parent: ObjId) -> Option<ObjId> {
use sv_lang::kinds::ExpressionKind as K;
if !matches!(e.kind(), K::Assignment | K::UnaryOp) {
return None;
}
let (file, line) = self
.model
.get(parent)
.map(|o| (o.props.file.clone(), o.props.line))?;
let mut obj = Obj::new(ObjKind::Stmt, Some(parent), "", "");
obj.props.stmt_type = consts::value_of("vpiAssignment").expect("a header constant") as i32;
obj.props.file = file;
obj.props.line = line;
let id = self.add(obj);
if e.kind() == K::Assignment {
let op = compound_assign_op(e);
if let Some(p) = self.model.props_mut(id) {
p.blocking = e.is_nonblocking() != Some(true);
p.op_type = op.unwrap_or(
consts::value_of("vpiAssignmentOp").expect("a header constant") as i32,
);
}
for (rel, side) in [
(consts::VPI_LHS, e.left()),
(consts::VPI_RHS, assign_rhs(e)),
] {
if let Some(op) = side.and_then(|s| self.operand(&s)) {
self.rel(id, rel, op);
}
}
} else {
if let Some(p) = self.model.props_mut(id) {
p.blocking = true;
p.op_type = op_type_of(e).unwrap_or(0);
}
if let Some(op) = e.operands().first().and_then(|s| self.operand(s)) {
self.rel(id, consts::VPI_LHS, op);
}
}
Some(id)
}
fn stmt(&mut self, st: &sv_lang::Statement<'_>, parent: ObjId, scope: &str) -> Option<ObjId> {
use sv_lang::kinds::StatementKind as S;
use sv_lang::kinds::TimingControlKind as T;
let c = |n: &str| consts::value_of(n).expect("a header constant") as i32;
let st = &vj_sim::process::cfg::unwrap_invalid(*st);
if st.kind() == S::List {
let mut items = Vec::new();
Self::flat(st, &mut items);
return match items.as_slice() {
[one] => self.stmt(one, parent, scope),
_ => None,
};
}
if st.kind() == S::Block && !self.locator.is_begin_block(st) {
let mut items = Vec::new();
for s in vj_sim::process::cfg::live_statements(st) {
Self::flat(&s, &mut items);
}
if let Some((last, decls)) = items.split_last()
&& last.kind() == S::ForLoop
&& !last.for_loop_vars().is_empty()
&& decls.iter().all(|d| d.kind() == S::VariableDeclaration)
{
return self.stmt(last, parent, scope);
}
}
if st.kind() == S::Block && !self.locator.is_begin_block(st) {
let mut items = Vec::new();
for s in vj_sim::process::cfg::live_statements(st) {
Self::flat(&s, &mut items);
}
if let [one] = items.as_slice() {
let label = st
.block_symbol()
.map(|b| b.name().to_owned())
.filter(|n| !n.is_empty());
let id = self.stmt(one, parent, scope)?;
if let Some(l) = label {
self.model.set_name(id, &l);
}
return Some(id);
}
}
if matches!(st.kind(), S::ImmediateAssertion | S::ConcurrentAssertion) {
return self
.assertion_ids
.get(&st.source_range().start.offset)
.copied();
}
let mut name = String::new();
let mut full = String::new();
let ty = match st.kind() {
S::Empty => c("vpiNullStmt"),
S::Block => {
let named = st
.block_symbol()
.map(|b| b.name().to_owned())
.filter(|n| !n.is_empty());
let fork = st.block_kind() != Some(sv_lang::StatementBlockKind::Sequential);
if let Some(n) = named {
full = format!("{scope}.{n}");
name = n;
}
match (fork, name.is_empty()) {
(false, true) => c("vpiBegin"),
(false, false) => c("vpiNamedBegin"),
(true, true) => c("vpiFork"),
(true, false) => c("vpiNamedFork"),
}
}
S::ExpressionStatement => {
let e = st.expr()?;
match e.kind() {
sv_lang::kinds::ExpressionKind::Assignment => c("vpiAssignment"),
sv_lang::kinds::ExpressionKind::Call => {
let builtin = e.system_subroutine().filter(|m| !m.name().starts_with('$'));
if let Some(m) = builtin {
c(if m.is_task() {
"vpiMethodTaskCall"
} else {
"vpiMethodFuncCall"
})
} else if e.call_this_class().is_some() {
let task = e.call_subroutine().and_then(|s| s.subroutine_kind())
== Some(sv_lang::SubroutineKind::Task);
c(if task {
"vpiMethodTaskCall"
} else {
"vpiMethodFuncCall"
})
} else if e.is_system_call() == Some(true) {
c("vpiSysTaskCall")
} else {
c("vpiTaskCall")
}
}
sv_lang::kinds::ExpressionKind::UnaryOp => c("vpiAssignment"),
_ => return None,
}
}
S::Conditional if st.else_branch().is_some() => c("vpiIfElse"),
S::Conditional => c("vpiIf"),
S::Case | S::PatternCase => c("vpiCase"),
S::ForLoop => c("vpiFor"),
S::RepeatLoop => c("vpiRepeat"),
S::ForeachLoop => c("vpiForeachStmt"),
S::WhileLoop => c("vpiWhile"),
S::DoWhileLoop => c("vpiDoWhile"),
S::ForeverLoop => c("vpiForever"),
S::Timed => match st.timing_control().map(|t| t.kind()) {
Some(T::Delay | T::Delay3 | T::OneStepDelay | T::CycleDelay) => {
c("vpiDelayControl")
}
Some(T::RepeatedEvent) => c("vpiRepeatControl"),
_ => c("vpiEventControl"),
},
S::Return => c("vpiReturnStmt"),
S::Break => c("vpiBreak"),
S::Continue => c("vpiContinue"),
S::Disable => c("vpiDisable"),
S::DisableFork => c("vpiDisableFork"),
S::Wait => c("vpiWait"),
S::WaitFork => c("vpiWaitFork"),
S::WaitOrder => c("vpiOrderedWait"),
S::EventTrigger => c("vpiEventStmt"),
S::ProceduralAssign if st.procedural_assign_is_force() => c("vpiForce"),
S::ProceduralAssign => c("vpiAssignStmt"),
S::ProceduralDeassign if st.procedural_deassign_is_release() => c("vpiRelease"),
S::ProceduralDeassign => c("vpiDeassign"),
_ => return None,
};
let mut obj = Obj::new(ObjKind::Stmt, Some(parent), &name, &full);
obj.props.stmt_type = ty;
obj.props.join_type = match st.block_kind() {
Some(sv_lang::StatementBlockKind::JoinNone) => c("vpiJoinNone"),
Some(sv_lang::StatementBlockKind::JoinAny) => c("vpiJoinAny"),
_ => c("vpiJoin"),
};
obj.props.local_var_decls = st.kind() == S::ForLoop && !st.for_loop_vars().is_empty();
if matches!(st.kind(), S::Conditional | S::Case | S::PatternCase) {
use sv_lang::UniquePriorityCheck as U;
let check = if st.kind() == S::PatternCase {
st.pattern_case_check()
} else {
st.check()
};
let inside = i32::from(
st.kind() == S::Case
&& st.case_condition() == Some(sv_lang::CaseStatementCondition::Inside),
) * c("vpiInsideQualifier");
obj.props.qualifier = match check {
None | Some(U::None) => Some(c("vpiNoQualifier") | inside),
Some(U::Unique) => Some(c("vpiUniqueQualifier") | inside),
Some(U::Priority) => Some(c("vpiPriorityQualifier") | inside),
Some(U::Unique0) => None,
};
}
if st.kind() == S::EventTrigger {
obj.props.blocking = st.is_nonblocking_trigger() == Some(false);
}
obj.props.stmt_key = Some(vj_codegen::vpi_dyn::key_of(st));
obj.props.file = Some(self.file.clone());
obj.props.line = self.locator.stmt_line(st);
let obj_line = obj.props.line;
let id = self.add(obj);
self.attributes(
st.attributes(),
id,
if full.is_empty() { scope } else { &full },
false,
);
let inner = if full.is_empty() {
scope.to_owned()
} else {
full.clone()
};
let sub = |b: &mut Self, rel: i32, s: Option<sv_lang::Statement<'_>>| {
if let Some(s) = s
&& let Some(o) = b.stmt(&s, id, &inner)
{
b.rel(id, rel, Operand::Obj(o));
}
};
let stmt_sites = if matches!(st.kind(), S::Block | S::List) || self.let_decls.is_empty() {
Vec::new()
} else {
let trees = self.locator.syntax_trees();
trees
.iter()
.find_map(|t| st.syntax(t))
.map(|n| self.collect_let_sites(n))
.unwrap_or_default()
};
let expr = |b: &mut Self, rel: i32, e: Option<sv_lang::Expression<'_>>| {
if let Some(e) = e
&& {
b.stmt_sites = stmt_sites.clone();
true
}
&& let Some(op) = b.operand(&e)
{
b.rel(id, rel, op);
}
};
match st.kind() {
S::Block => {
if let Some(bs) = st.block_symbol() {
self.scope_locals(&bs, id, &inner, None);
}
let mut items = Vec::new();
for s in vj_sim::process::cfg::live_statements(st) {
Self::flat(&s, &mut items);
}
for s in items {
sub(self, consts::VPI_STMT, Some(s));
}
}
S::ExpressionStatement => {
let e = st.expr()?;
match e.kind() {
sv_lang::kinds::ExpressionKind::Assignment => {
let nb = e.is_nonblocking() == Some(true);
let op = compound_assign_op(&e);
if let Some(p) = self.model.props_mut(id) {
p.blocking = !nb;
p.op_type = op.unwrap_or(c("vpiAssignmentOp"));
}
expr(self, consts::VPI_LHS, e.left());
self.let_hint = Some(self.let_sites_of_expr(&e));
expr(self, consts::VPI_RHS, assign_rhs(&e));
self.let_hint = None;
if let Some(tc) = e.assignment_timing().and_then(|n| n.as_timing_control())
&& let Some(ctl) = self.timing_control_obj(&tc, id, obj_line)
{
let rel = self.model.get(ctl).map_or(0, |o| o.props.stmt_type);
self.rel(id, rel, Operand::Obj(ctl));
}
}
sv_lang::kinds::ExpressionKind::Call => {
self.model
.set_name(id, &e.call_subroutine_name().unwrap_or_default());
let builtin = e
.system_subroutine()
.is_some_and(|m| !m.name().starts_with('$'))
|| (e.call_this_class().is_some() && receiver_listed(&e));
let operands = e.operands();
if let Some(t) = e.call_this_class() {
expr(self, header("vpiPrefix"), Some(t));
} else if builtin {
expr(self, header("vpiPrefix"), operands.first().copied());
}
for a in operands.iter().skip(usize::from(builtin)) {
expr(self, consts::VPI_ARGUMENT, Some(*a));
}
if let Some(w) = self.call_with(&e) {
self.rel(id, header("vpiWith"), w);
}
if let Some(sub) = e.call_subroutine() {
let rel =
if sub.subroutine_kind() == Some(sv_lang::SubroutineKind::Task) {
"vpiTask"
} else {
"vpiFunction"
};
self.tf_rel_links.push((id, header(rel), sub.uid()));
}
}
_ => {
let op = op_type_of(&e).unwrap_or(0);
if let Some(p) = self.model.props_mut(id) {
p.blocking = true;
p.op_type = op;
}
expr(self, consts::VPI_LHS, e.operands().first().copied());
}
}
}
S::Conditional => {
expr(
self,
consts::VPI_CONDITION,
st.conditions().first().map(|c| c.expr()),
);
sub(self, consts::VPI_STMT, st.then_branch());
sub(self, consts::VPI_ELSE_STMT, st.else_branch());
}
S::PatternCase => {
let ct = match st.pattern_case_condition() {
Some(sv_lang::CaseStatementCondition::WildcardXOrZ) => c("vpiCaseX"),
Some(sv_lang::CaseStatementCondition::WildcardJustZ) => c("vpiCaseZ"),
_ => c("vpiCaseExact"),
};
if let Some(p) = self.model.props_mut(id) {
p.case_type = ct;
}
expr(
self,
consts::VPI_CONDITION,
vj_sim::patterns::pattern_case_expr(*st),
);
let items: Vec<_> = st
.pattern_case_items()
.into_iter()
.map(|i| (Some((i.pattern(), i.filter())), Some(i.stmt())))
.chain(st.pattern_case_default().map(|d| (None, Some(d))))
.collect();
for (pat, body) in items {
let mut it = Obj::new(ObjKind::CaseItem, Some(id), "", "");
it.props.file = Some(self.file.clone());
it.props.line = body.map_or(0, |b| self.locator.stmt_line(&b));
let item = self.add(it);
if let Some((p, filter)) = pat {
if let Some(op) = self.pattern(&p, item) {
self.rel(item, consts::VPI_EXPR, op);
}
if let Some(f) = filter
&& let Some(op) = self.operand(&f)
{
self.rel(item, consts::VPI_EXPR, op);
}
}
if let Some(b) = body
&& let Some(o) = self.stmt(&b, item, &inner)
{
self.rel(item, consts::VPI_STMT, Operand::Obj(o));
}
}
}
S::Case => {
let ct = match st.case_condition() {
Some(sv_lang::CaseStatementCondition::WildcardXOrZ) => c("vpiCaseX"),
Some(sv_lang::CaseStatementCondition::WildcardJustZ) => c("vpiCaseZ"),
_ => c("vpiCaseExact"),
};
if let Some(p) = self.model.props_mut(id) {
p.case_type = ct;
}
expr(self, consts::VPI_CONDITION, st.expr());
let items: Vec<(Vec<sv_lang::Expression<'_>>, Option<sv_lang::Statement<'_>>)> = st
.case_items()
.into_iter()
.map(|i| (i.expressions(), Some(i.stmt())))
.chain(st.default_case().map(|d| (Vec::new(), Some(d))))
.collect();
for (exprs, body) in items {
let mut it = Obj::new(ObjKind::CaseItem, Some(id), "", "");
it.props.file = Some(self.file.clone());
it.props.line = body.map_or(0, |b| self.locator.stmt_line(&b));
let item = self.add(it);
for e in exprs {
if let Some(op) = self.operand(&e) {
self.rel(item, consts::VPI_EXPR, op);
}
}
if let Some(b) = body
&& let Some(o) = self.stmt(&b, item, &inner)
{
self.rel(item, consts::VPI_STMT, Operand::Obj(o));
}
}
}
S::ForLoop => {
let mut loop_vars = Vec::new();
for v in st.for_loop_vars() {
let at = self.model.len() as ObjId;
self.local_var(&v, id, &inner);
if self.model.len() as ObjId > at {
loop_vars.push((v, at));
}
}
for (v, vid) in loop_vars {
if let Some(init) = v.initializer()
&& let Some(s) = self.var_init_stmt(vid, &init, id)
{
self.rel(id, consts::VPI_FOR_INIT_STMT, Operand::Obj(s));
}
}
for e in st.for_loop_initializers() {
match self.for_clause_stmt(&e, id) {
Some(s) => self.rel(id, consts::VPI_FOR_INIT_STMT, Operand::Obj(s)),
None => expr(self, consts::VPI_FOR_INIT_STMT, Some(e)),
}
}
expr(self, consts::VPI_CONDITION, st.condition());
for e in st.for_loop_steps() {
match self.for_clause_stmt(&e, id) {
Some(s) => self.rel(id, consts::VPI_FOR_INC_STMT, Operand::Obj(s)),
None => expr(self, consts::VPI_FOR_INC_STMT, Some(e)),
}
}
sub(self, consts::VPI_STMT, st.body());
}
S::RepeatLoop | S::WhileLoop | S::DoWhileLoop => {
expr(self, consts::VPI_CONDITION, st.condition().or(st.expr()));
sub(self, consts::VPI_STMT, st.body());
}
S::ForeverLoop => sub(self, consts::VPI_STMT, st.body()),
S::ForeachLoop => {
expr(
self,
header("vpiVariables"),
st.expressions().first().copied(),
);
let mut vars = Vec::new();
for d in st.foreach_loop_dims() {
let Some(sym) = d.loop_var() else {
let n = self.model.add_expr(ExprNode::Op {
op_type: header("vpiNullOp"),
width: 0,
operands: Vec::new(),
meta: OpMeta::default(),
});
vars.push(Operand::Expr(n));
continue;
};
let n = sym.name().to_owned();
let mut v = Obj::new(ObjKind::IntVar, Some(id), &n, &format!("{scope}.{n}"));
v.props.width = 32;
v.props.signed = true;
v.props.automatic = true;
v.props.file = Some(self.file.clone());
v.props.line = obj_line;
let vid = self.add(v);
vars.push(Operand::Obj(vid));
}
let mark = self.loop_vars.len();
for v in &vars {
if let Operand::Obj(vid) = v
&& let Some(o) = self.model.get(*vid)
{
self.loop_vars.push((o.name.clone(), *vid));
}
}
for v in vars {
self.rel(id, header("vpiLoopVars"), v);
}
sub(self, consts::VPI_STMT, st.body());
self.loop_vars.truncate(mark);
}
S::Timed => {
let tc = st.timing_control();
let e = tc.and_then(|t| t.children().into_iter().find_map(|n| n.as_expression()));
let rel = if ty == c("vpiDelayControl") {
consts::VPI_DELAY
} else {
consts::VPI_CONDITION
};
let edge = tc
.filter(|t| t.kind() == T::SignalEvent)
.and_then(|t| match t.edge() {
sv_lang::EdgeKind::PosEdge => Some(c("vpiPosedgeOp")),
sv_lang::EdgeKind::NegEdge => Some(c("vpiNegedgeOp")),
_ => None,
});
match (edge, e) {
(Some(op_type), Some(sig)) => {
if let Some(operand) = self.operand(&sig) {
let width = 1;
let node = self.model.add_expr(ExprNode::Op {
op_type,
width,
operands: vec![operand],
meta: OpMeta::default(),
});
self.rel(id, rel, Operand::Expr(node));
}
}
_ => expr(self, rel, e),
}
sub(self, consts::VPI_STMT, st.body());
}
S::Return => expr(self, consts::VPI_CONDITION, st.expr()),
S::Wait => {
expr(self, consts::VPI_CONDITION, st.condition().or(st.expr()));
sub(self, consts::VPI_STMT, st.body());
}
S::Disable => {
let label = vj_sim::process::cfg::source_text_range(st.source_range())
.and_then(|t| {
t.trim()
.strip_prefix("disable")
.map(|r| r.trim().trim_end_matches(';').trim().to_owned())
})
.filter(|l| !l.is_empty());
if let Some(l) = label {
self.disable_links.push((id, scope.to_owned(), l));
}
}
S::EventTrigger => {
let target = st.expr().or_else(|| {
st.as_sem_node()
.children()
.into_iter()
.find_map(|n| n.as_expression())
});
if let Some(op) = target.and_then(|t| self.operand(&t)) {
self.rel(id, consts::VPI_EXPR, op);
self.rel(id, header("vpiNamedEvent"), op);
}
}
S::ProceduralAssign => {
let e = st.expr().or_else(|| {
st.as_sem_node()
.children()
.into_iter()
.find_map(|n| n.as_expression())
});
if let Some(a) = e {
expr(self, consts::VPI_LHS, a.left());
self.let_hint = Some(self.let_sites_of_expr(&a));
expr(self, consts::VPI_RHS, a.right());
self.let_hint = None;
}
}
S::ProceduralDeassign => {
let target = st.expr().or_else(|| {
st.as_sem_node()
.children()
.into_iter()
.find_map(|n| n.as_expression())
});
expr(self, consts::VPI_LHS, target);
}
_ => {}
}
Some(id)
}
fn let_decl(&mut self, sym: &Symbol<'_>, parent: ObjId, parent_name: &str) {
let name = sym.name().to_owned();
let full = format!("{parent_name}.{name}");
let mut obj = Obj::new(ObjKind::LetDecl, Some(parent), &name, &full);
obj.props.file = Some(self.file.clone());
obj.props.line = self.locator.line(sym);
let id = self.add(obj);
self.attributes(sym.attributes(), id, &full, false);
self.formal_names.clear();
for port in sym.let_decl_ports() {
let pname = port.name().to_owned();
let mut f = Obj::new(
ObjKind::SeqFormalDecl,
Some(id),
&pname,
&format!("{full}.{pname}"),
);
f.props.file = Some(self.file.clone());
f.props.line = self.locator.line(&port);
f.props.direction = match port.assertion_port_direction() {
Some(ArgumentDirection::Out) => consts::VPI_OUTPUT,
Some(ArgumentDirection::InOut) => consts::VPI_INOUT,
_ => consts::VPI_INPUT,
};
let fid = self.add(f);
self.formal_by_uid.insert(port.uid(), fid);
self.formal_names.push((pname, fid));
self.formal_default(&port, fid, None, &sym.parent().unwrap_or(*sym));
}
let bound = sym.bound_body().map(unwrap_invalid_expr);
let bound = bound.and_then(|b| self.let_body_concat(sym, &b).or_else(|| self.operand(&b)));
self.formal_names.clear();
if let Some(op) = bound {
self.rel(id, consts::VPI_EXPR, op);
}
use sv_lang::AstNode;
let mut body = None;
for (ti, tree) in self.locator.syntax_trees().iter().enumerate() {
if let Some(n) = sym
.syntax(tree)
.and_then(sv_lang::nodes::LetDeclarationSyntax::cast)
{
body = Some(n.expr().syntax().text().trim().to_owned());
self.let_decls
.insert((ti, n.syntax().byte_range().start), id);
break;
}
}
if bound.is_none()
&& let Some(body) = body
{
let e = self.model.add_expr(ExprNode::Unsupported(format!(
"the body `{body}` of a let declaration (slang binds it only at an instance)"
)));
self.rel(id, consts::VPI_EXPR, Operand::Expr(e));
}
}
#[allow(clippy::too_many_arguments)]
fn let_instance(
&mut self,
rhs_text: &str,
expanded_text: Option<&str>,
scope: ObjId,
body: Operand,
width: i32,
known: Option<ObjId>,
uses: &[(String, sv_lang::Expression<'_>)],
) -> Operand {
let rhs = rhs_text.trim();
if expanded_text.is_some_and(|t| t.trim() == rhs) {
return body;
}
let ident_end = rhs
.find(|c: char| !(c.is_ascii_alphanumeric() || c == '_' || c == '$'))
.unwrap_or(rhs.len());
let (name, rest) = rhs.split_at(ident_end);
if name.is_empty() || name.starts_with(|c: char| c.is_ascii_digit()) {
return body;
}
let rest = rest.trim();
let actuals: Vec<String> = if rest.is_empty() {
Vec::new()
} else if let Some(inner) = rest.strip_prefix('(').and_then(|r| r.strip_suffix(')')) {
split_top_level(inner)
} else {
return body;
};
let mut decl = known;
let mut at = known.is_none().then_some(scope);
while let Some(sc) = at {
let Some(o) = self.model.get(sc) else { break };
if let Some(d) = self.model.find(&format!("{}.{name}", o.full_name))
&& self
.model
.get(d)
.is_some_and(|o| o.kind == ObjKind::LetDecl)
{
decl = Some(d);
break;
}
at = o.parent;
}
let Some(decl) = decl else { return body };
let formals = self.model.children_of_kind(decl, ObjKind::SeqFormalDecl);
let mut given: Vec<Option<String>> = vec![None; formals.len()];
for (i, a) in actuals.iter().enumerate() {
let a = a.trim();
if let Some(named) = a.strip_prefix('.') {
let (fname, val) = named.split_once('(').unwrap_or((named, ""));
let val = val.trim_end().trim_end_matches(')').trim();
if let Some(k) = formals
.iter()
.position(|&f| self.model.get(f).is_some_and(|o| o.name == fname.trim()))
{
given[k] = Some(val.to_owned());
}
} else if i < given.len() && !a.is_empty() {
given[i] = Some(a.to_owned());
}
}
let mut args = Vec::new();
for (k, &f) in formals.iter().enumerate() {
let op = match &given[k] {
Some(text) if !text.is_empty() => {
let fname = self
.model
.get(f)
.map(|o| o.name.clone())
.unwrap_or_default();
Some(self.let_actual(text, scope, &fname, uses))
}
_ => self
.model
.get(f)
.and_then(|o| o.props.rels.iter().find(|(r, _)| *r == consts::VPI_EXPR))
.map(|(_, t)| *t),
};
args.push(op.unwrap_or_else(|| {
Operand::Expr(self.model.add_expr(ExprNode::Unsupported(format!(
"the omitted argument {k} of let instance {name} (it has no default)"
))))
}));
}
Operand::Expr(self.model.add_expr(ExprNode::LetInst {
decl,
name: name.to_owned(),
args,
body,
width,
}))
}
fn let_actual(
&mut self,
text: &str,
scope: ObjId,
fname: &str,
uses: &[(String, sv_lang::Expression<'_>)],
) -> Operand {
if !text.is_empty() && text.chars().all(|c| c.is_ascii_alphanumeric() || c == '_') {
let mut at = Some(scope);
while let Some(sc) = at {
let Some(o) = self.model.get(sc) else { break };
if let Some(t) = self.model.find(&format!("{}.{text}", o.full_name)) {
return Operand::Obj(t);
}
at = o.parent;
}
}
if let Some((_, x)) = uses.iter().find(|(f, _)| f == fname)
&& let Some(op) = self.operand(x)
{
return op;
}
Operand::Expr(self.model.add_expr(formal_default_node(text)))
}
fn let_decl_of(&self, trees: &[SyntaxTree], x: &sv_lang::Expression<'_>) -> Option<ObjId> {
use sv_lang::AstNode;
for (ti, tree) in trees.iter().enumerate() {
let Some(mut n) = x.syntax(tree) else {
continue;
};
loop {
if let Some(d) = sv_lang::nodes::LetDeclarationSyntax::cast(n) {
return self
.let_decls
.get(&(ti, d.syntax().byte_range().start))
.copied();
}
n = n.parent()?;
}
}
None
}
fn collect_let_sites(&self, node: sv_lang::Node<'_>) -> Vec<LetSite> {
use sv_lang::AstNode;
let names: Vec<&str> = self
.let_decls
.values()
.filter_map(|&d| self.model.get(d).map(|o| o.name.as_str()))
.collect();
let mut out = Vec::new();
for n in node.descendants() {
if let Some(inv) = sv_lang::nodes::InvocationExpressionSyntax::cast(n) {
let l = inv.left().syntax().text();
let callee = l.trim().rsplit(['.', ':']).next().unwrap_or("");
if names.contains(&callee) {
out.push(LetSite {
name: callee.to_owned(),
text: n.text(),
});
}
} else if let Some(id) = sv_lang::nodes::IdentifierNameSyntax::cast(n)
&& let Some(callee) = Some(n.text().trim().to_owned())
&& names.contains(&callee.as_str())
&& !n.parent().is_some_and(|p| {
sv_lang::nodes::InvocationExpressionSyntax::cast(p)
.is_some_and(|i| i.left().syntax().byte_range() == id.syntax().byte_range())
})
&& !Self::name_shadowed(n, &callee)
{
out.push(LetSite {
name: callee.clone(),
text: callee,
});
}
}
out
}
fn name_shadowed(id: sv_lang::Node<'_>, name: &str) -> bool {
use sv_lang::AstNode;
use sv_lang::nodes::{
BlockStatementSyntax, DeclaratorSyntax, ForLoopStatementSyntax,
ForeachLoopStatementSyntax, FunctionDeclarationSyntax,
};
let is_scope = |n: sv_lang::Node<'_>| {
BlockStatementSyntax::cast(n).is_some()
|| ForLoopStatementSyntax::cast(n).is_some()
|| ForeachLoopStatementSyntax::cast(n).is_some()
|| FunctionDeclarationSyntax::cast(n).is_some()
};
let mut at = id.parent();
while let Some(a) = at {
if is_scope(a) {
for d in a.descendants() {
let Some(decl) = DeclaratorSyntax::cast(d) else {
continue;
};
if !decl.name().is_some_and(|t| t.text() == name) {
continue;
}
let mut up = d.parent();
while let Some(u) = up {
if is_scope(u) {
break;
}
up = u.parent();
}
if up.is_some_and(|u| u.byte_range() == a.byte_range()) {
return true;
}
}
}
at = a.parent();
}
false
}
fn let_sites_of_expr(&self, x: &sv_lang::Expression<'_>) -> Vec<LetSite> {
if self.let_decls.is_empty() {
return Vec::new();
}
let trees = self.locator.syntax_trees();
match trees.iter().find_map(|t| x.syntax(t)) {
Some(n) => self.collect_let_sites(n),
None => Vec::new(),
}
}
fn let_sites_of_symbol(&self, sym: &Symbol<'_>) -> Vec<LetSite> {
if self.let_decls.is_empty() {
return Vec::new();
}
let trees = self.locator.syntax_trees();
match trees.iter().find_map(|t| sym.syntax(t)) {
Some(n) => self.collect_let_sites(n),
None => Vec::new(),
}
}
fn let_root(&mut self, e: &sv_lang::Expression<'_>) -> Option<Operand> {
if self.let_sites.is_empty() || self.let_depth > 0 {
return None;
}
let trees = self.locator.syntax_trees();
let decl = self.let_decl_of(&trees, e)?;
let name = self.model.get(decl)?.name.clone();
let idx = self.let_sites.iter().position(|s| s.name == name)?;
let formals: Vec<String> = self
.model
.children_of_kind(decl, ObjKind::SeqFormalDecl)
.iter()
.filter_map(|&f| self.model.get(f).map(|o| o.name.clone()))
.collect();
let mut uses: Vec<(String, sv_lang::Expression<'_>)> = Vec::new();
let mut stack = vec![e.as_sem_node()];
while let Some(n) = stack.pop() {
if let Some(x) = n.as_expression()
&& let Some(node) = trees.iter().find_map(|t| x.syntax(t))
&& self.let_decl_of(&trees, &x) == Some(decl)
{
let t = node.text();
let t = t.trim();
if formals.iter().any(|f| f == t) && !uses.iter().any(|(f, _)| f == t) {
uses.push((t.to_owned(), x));
}
}
stack.extend(n.children());
}
let text = self.let_sites[idx].text.clone();
let inner = text
.find('(')
.map(|i| text[i + 1..].trim_end().trim_end_matches(')').to_owned())
.unwrap_or_default();
for (i, a) in split_top_level(&inner).iter().enumerate() {
let a = a.trim();
let (fname, val) = match a.strip_prefix('.') {
Some(named) => {
let (f, v) = named.split_once('(').unwrap_or((named, ""));
(
Some(f.trim().to_owned()),
v.trim_end().trim_end_matches(')').trim(),
)
}
None => (formals.get(i).cloned(), a),
};
let plain =
!val.is_empty() && val.chars().all(|c| c.is_ascii_alphanumeric() || c == '_');
if plain
&& let Some(f) = fname
&& let Some((_, x)) = uses.iter().find(|(u, _)| *u == f)
&& x.kind() == sv_lang::kinds::ExpressionKind::NamedValue
&& x.referenced_symbol().is_some_and(|s| s.name() != val)
{
return None;
}
}
self.let_sites.remove(idx);
let width = e.expr_type().map_or(0, |t| t.bit_width() as i32);
self.let_depth += 1;
let body = self.operand_inner(e);
self.let_depth -= 1;
let scope = self.model.get(decl).and_then(|o| o.parent).unwrap_or(decl);
Some(self.let_instance(&text, None, scope, body?, width, Some(decl), &uses))
}
fn formal_default(
&mut self,
port: &Symbol<'_>,
fid: ObjId,
bound: Option<Operand>,
scope: &Symbol<'_>,
) {
use sv_lang::AstNode;
use sv_lang::nodes::AssertionItemPortSyntax;
let mut text = None;
let mut bound_here = None;
for tree in self.locator.syntax_trees() {
if let Some(n) = port
.syntax(&tree)
.and_then(AssertionItemPortSyntax::cast)
.and_then(|n| n.default_value())
{
text = Some(n.expr().syntax().text().trim().to_owned());
if bound.is_none()
&& let Some(syn) = port.assertion_port_default_syntax(&tree)
&& let Some(e) = self.eval.bind_expression(
sv_lang::nodes::SimpleSequenceExprSyntax::cast(syn)
.or_else(|| syn.find_first::<sv_lang::nodes::SimpleSequenceExprSyntax>())
.map_or(syn, |q| q.expr().syntax()),
*scope,
)
{
let e = unwrap_invalid_expr(e);
if !e.is_bad() {
bound_here = self.operand(&e);
}
}
break;
}
}
let bound = bound.or(bound_here);
let Some(text) = text else { return };
let node = formal_default_node(&text);
if matches!(node, ExprNode::Unsupported(_))
&& let Some(op) = bound
{
self.rel(fid, consts::VPI_EXPR, op);
return;
}
let e = self.model.add_expr(node);
self.rel(fid, consts::VPI_EXPR, Operand::Expr(e));
}
fn named_parameters(e: &sv_lang::Expression<'_>, out: &mut Vec<SymbolId>) {
use sv_lang::kinds::ExpressionKind as K;
if e.kind() == K::NamedValue
&& let Some(sym) = e.referenced_symbol()
&& matches!(sym.kind(), SymbolKind::Parameter | SymbolKind::Specparam)
{
out.push(sym.uid());
}
for o in e.operands() {
Self::named_parameters(&o, out);
}
if let Some(inner) = e.conversion_operand() {
Self::named_parameters(&inner, out);
}
}
fn range_expr_root<'t>(n: sv_lang::Node<'t>) -> Option<sv_lang::Node<'t>> {
use sv_lang::kinds::SyntaxKind as SK;
let mut cur = n;
loop {
let p = cur.parent()?;
if matches!(
p.kind(),
SK::SimpleRangeSelect
| SK::BitSelect
| SK::AscendingRangeSelect
| SK::DescendingRangeSelect
| SK::RangeDimensionSpecifier
| SK::VariableDimension
| SK::ColonExpressionClause
) {
let k = format!("{:?}", cur.kind());
return (matches!(k.as_str(), "IdentifierName" | "ScopedName")
|| k.ends_with("Expression"))
.then_some(cur);
}
cur = p;
}
}
fn range_use_expr(
&mut self,
sym: &Symbol<'_>,
tree_ix: usize,
root: sv_lang::Node<'_>,
) -> bool {
let Some(scope) = sym.parent() else {
return false;
};
self.bind_range_expr(&scope, tree_ix, root, Some(sym.uid()))
}
fn bind_range_expr(
&mut self,
scope: &Symbol<'_>,
tree_ix: usize,
root: sv_lang::Node<'_>,
only: Option<SymbolId>,
) -> bool {
let r = root.byte_range();
let key = (scope.uid(), tree_ix, r.start, r.end);
if self.range_exprs.contains(&key) {
return true;
}
let Some(e) = self.eval.bind_expression(root, *scope) else {
return false;
};
let e = unwrap_invalid_expr(e);
if e.is_bad() {
return false;
}
let mut named = Vec::new();
Self::named_parameters(&e, &mut named);
if only.is_some_and(|u| !named.contains(&u)) {
return true;
}
let Some(Operand::Expr(x)) = self.operand(&e) else {
return false;
};
self.range_exprs.insert(key);
for p in named {
self.param_links.push((x, p));
}
true
}
fn genvar_names(def: sv_lang::Node<'_>) -> Vec<String> {
use sv_lang::kinds::SyntaxKind as SK;
let mut out = Vec::new();
def.visit(|n| {
if n.kind() == SK::LoopGenerate {
let t = n.text();
let t = t.split_once('(').map_or("", |x| x.1).trim_start();
let t = t.strip_prefix("genvar").map_or(t, str::trim_start);
let id: String = t
.chars()
.take_while(|c| c.is_alphanumeric() || *c == '_' || *c == '$')
.collect();
if !id.is_empty() {
out.push(id);
}
}
sv_lang::Walk::Continue
});
out
}
fn gen_position<'t>(a: &sv_lang::Node<'t>, chain: &[sv_lang::Node<'t>]) -> GenPos<'t> {
use sv_lang::kinds::SyntaxKind as SK;
let Some(last) = chain.last() else {
return GenPos::Unknown;
};
let kids: Vec<_> = a.children().collect();
let (Some(first), Some(end)) = (kids.first(), kids.last()) else {
return GenPos::Unknown;
};
let is_last = last.byte_range() == end.byte_range();
let is_first = last.byte_range() == first.byte_range();
let cond = match a.kind() {
SK::IfGenerate | SK::CaseGenerate => is_first,
SK::LoopGenerate | SK::StandardCaseItem => !is_last,
_ => return GenPos::Unknown,
};
if !cond {
return GenPos::Body;
}
let exprs: Vec<_> = chain
.iter()
.copied()
.filter(|c| {
let k = format!("{:?}", c.kind());
matches!(k.as_str(), "IdentifierName" | "ScopedName") || k.ends_with("Expression")
})
.collect();
if exprs.is_empty() {
GenPos::Unknown
} else {
GenPos::Cond(exprs)
}
}
fn gen_use_expr(
&mut self,
sym: &Symbol<'_>,
tree_ix: usize,
cands: &[sv_lang::Node<'_>],
genvars: &[String],
) -> bool {
let Some(scope) = sym.parent() else {
return false;
};
self.bind_use_expr_in(&scope, tree_ix, cands, genvars, Some(sym.uid()))
}
fn bind_use_expr(
&mut self,
scope: &Symbol<'_>,
tree_ix: usize,
cands: &[sv_lang::Node<'_>],
genvars: &[String],
) -> bool {
self.bind_use_expr_in(scope, tree_ix, cands, genvars, None)
}
fn bind_use_expr_in(
&mut self,
scope: &Symbol<'_>,
tree_ix: usize,
cands: &[sv_lang::Node<'_>],
genvars: &[String],
only: Option<SymbolId>,
) -> bool {
use sv_lang::kinds::SyntaxKind as SK;
for c in cands.iter().rev() {
let mut hit = false;
c.visit(|n| {
if n.kind() == SK::IdentifierName && genvars.iter().any(|g| *g == n.text().trim()) {
hit = true;
}
sv_lang::Walk::Continue
});
if !hit && self.bind_range_expr(scope, tree_ix, *c, only.clone()) {
return true;
}
}
false
}
fn classify_use<'t>(n: sv_lang::Node<'t>) -> UseCtx<'t> {
use sv_lang::kinds::SyntaxKind as SK;
let mut cur = n.parent();
let mut in_init = false;
let mut chain = vec![n];
let mut gen_cands = None;
while let Some(a) = cur {
if !matches!(
a.kind(),
SK::IfGenerate | SK::CaseGenerate | SK::LoopGenerate | SK::StandardCaseItem
) {
chain.push(a);
}
match a.kind() {
SK::EqualsValueClause => in_init = true,
SK::ParameterDeclaration if in_init => {}
SK::ParameterValueAssignment
if a.parent()
.is_some_and(|p| p.kind() == SK::HierarchyInstantiation) => {}
SK::RangeDimensionSpecifier
if a.parent().is_some_and(|d| Self::modelled_decl_dim(&d)) =>
{
return UseCtx::Modelled;
}
SK::VariableDimension if Self::modelled_decl_dim(&a) => return UseCtx::Modelled,
SK::RangeDimensionSpecifier | SK::VariableDimension => {
return Self::range_expr_root(n).map_or(UseCtx::Refused, UseCtx::Range);
}
SK::ElementSelect
if a.parent()
.and_then(|p| p.parent())
.is_some_and(|t| t.kind() == SK::NamedType) =>
{
return Self::range_expr_root(n).map_or(UseCtx::Refused, UseCtx::Range);
}
SK::IfGenerate | SK::CaseGenerate | SK::LoopGenerate | SK::StandardCaseItem => {
let ch = std::mem::replace(&mut chain, vec![a]);
match Self::gen_position(&a, &ch) {
GenPos::Cond(c) => {
gen_cands = Some(c);
break;
}
GenPos::Body => {}
GenPos::Unknown => return UseCtx::Refused,
}
cur = a.parent();
continue;
}
SK::ParameterValueAssignment => {
return Self::expr_candidates(&chain[..chain.len() - 1]);
}
SK::SpecifyBlock | SK::SpecparamDeclaration => return UseCtx::Modelled,
SK::ParameterDeclaration => return UseCtx::Refused,
_ => {}
}
cur = a.parent();
}
gen_cands.map_or(UseCtx::Modelled, UseCtx::Gen)
}
fn expr_candidates<'t>(chain: &[sv_lang::Node<'t>]) -> UseCtx<'t> {
let c: Vec<_> = chain
.iter()
.copied()
.filter(|c| {
let k = format!("{:?}", c.kind());
matches!(k.as_str(), "IdentifierName" | "ScopedName") || k.ends_with("Expression")
})
.collect();
if c.is_empty() {
UseCtx::Refused
} else {
UseCtx::Gen(c)
}
}
fn enclosing_instance_def<'t>(n: sv_lang::Node<'t>) -> Option<sv_lang::Node<'t>> {
use sv_lang::kinds::SyntaxKind as SK;
let mut cur = n.parent();
while let Some(d) = cur {
if matches!(
d.kind(),
SK::ModuleDeclaration | SK::InterfaceDeclaration | SK::ProgramDeclaration
) {
return Some(d);
}
cur = d.parent();
}
None
}
fn qualified_uses(&mut self, body: &Symbol<'_>) {
use sv_lang::kinds::SyntaxKind as SK;
let trees = self.locator.syntax_trees();
for (tree_ix, tree) in trees.iter().enumerate() {
let Some(decl) = body.syntax(tree) else {
continue;
};
let Some(def) = std::iter::successors(Some(decl), |d| d.parent()).find(|d| {
matches!(
d.kind(),
SK::ModuleDeclaration | SK::InterfaceDeclaration | SK::ProgramDeclaration
)
}) else {
continue;
};
let genvars = Self::genvar_names(def);
let mut found: Vec<(Vec<sv_lang::Node<'_>>, String)> = Vec::new();
def.visit(|n| {
if n.kind() == SK::IdentifierName
&& n.parent().is_some_and(|p| p.kind() == SK::ScopedName)
{
let last = n.text().trim().to_owned();
match Self::classify_use(n) {
UseCtx::Range(r) => found.push((vec![r], last)),
UseCtx::Gen(c) => found.push((c, last)),
UseCtx::Refused => {
self.qual_fail.insert(last);
}
UseCtx::Modelled => {}
}
}
sv_lang::Walk::Continue
});
for (cands, last) in found {
if !self.bind_use_expr(body, tree_ix, &cands, &genvars) {
self.qual_fail.insert(last);
}
}
}
let fails: Vec<_> = self.qual_fail.iter().cloned().collect();
for (name, id) in self.param_objs.clone() {
if fails.contains(&name) {
self.model.mark_param_folded(id);
}
}
}
fn modelled_decl_dim(d: &sv_lang::Node<'_>) -> bool {
use sv_lang::kinds::SyntaxKind as SK;
if d.kind() != SK::VariableDimension {
return false;
}
let Some(ty) = d.parent() else { return false };
if !matches!(
ty.kind(),
SK::LogicType | SK::RegType | SK::BitType | SK::ImplicitType
) {
return false;
}
let first = ty.children().find(|c| c.kind() == SK::VariableDimension);
if first.is_none_or(|f| f.byte_range().start != d.byte_range().start) {
return false;
}
let Some(decl) = ty.parent() else {
return false;
};
matches!(decl.kind(), SK::DataDeclaration | SK::NetDeclaration)
&& decl.parent().is_some_and(|m| {
matches!(
m.kind(),
SK::ModuleDeclaration | SK::InterfaceDeclaration | SK::ProgramDeclaration
)
})
}
fn range_exprs(&mut self, sym: &Symbol<'_>, id: ObjId) {
use sv_lang::kinds::SyntaxKind as SK;
let Some(scope) = sym.parent() else { return };
for tree in self.locator.syntax_trees() {
let Some(decl) = sym.syntax(&tree) else {
continue;
};
let mut dim = None;
let mut up = decl.parent();
while let Some(a) = up {
if matches!(a.kind(), SK::DataDeclaration | SK::NetDeclaration) {
a.visit(|n| {
if dim.is_none() && Self::modelled_decl_dim(&n) {
dim = Some(n);
}
sv_lang::Walk::Continue
});
break;
}
up = a.parent();
}
let Some(dim) = dim else { return };
let mut range = None;
dim.visit(|n| {
if range.is_none() && n.kind() == SK::SimpleRangeSelect {
range = Some(n);
}
sv_lang::Walk::Continue
});
let Some(range) = range else { return };
let (Some(l), Some(r)) = (range.children().next(), range.children().last()) else {
return;
};
let mut ops = Vec::new();
let mut any = false;
let mut ok = true;
for (n, key) in [(l, consts::VPI_LEFT_RANGE), (r, consts::VPI_RIGHT_RANGE)] {
let e = self
.eval
.bind_expression(n, scope)
.map(unwrap_invalid_expr)
.filter(|e| !e.is_bad());
let Some(e) = e else {
ok = false;
continue;
};
let mut named = Vec::new();
Self::named_parameters(&e, &mut named);
any |= !named.is_empty();
let op = self.operand(&e);
let good = op.is_some_and(|o| match o {
Operand::Expr(x) => {
!matches!(self.model.expr(x), Some(ExprNode::Unsupported(_)))
}
Operand::Obj(_) => true,
});
match op {
Some(o) if good => ops.push((key, o)),
_ => {
ok = false;
for p in named {
if let Some(pid) = self.prop_by_uid.get(&p).copied() {
self.model.mark_param_folded(pid);
}
}
}
}
}
if !ok {
let mut idents = Vec::new();
range.visit(|n| {
if n.kind() == SK::IdentifierName {
idents.push(n.text().trim().to_owned());
}
sv_lang::Walk::Continue
});
for name in idents {
if let Some(p) = scope.find(&name)
&& let Some(pid) = self.prop_by_uid.get(&p.uid()).copied()
{
self.model.mark_param_folded(pid);
}
}
return;
}
if any {
for (key, o) in ops {
self.rel(id, key, o);
}
}
return;
}
}
fn param_has_folded_use(&mut self, sym: &Symbol<'_>) -> bool {
use sv_lang::kinds::SyntaxKind as SK;
let name = sym.name().to_owned();
let trees = self.locator.syntax_trees();
let qualified = self.qualified_idents.get_or_insert_with(|| {
let mut set = std::collections::HashSet::new();
for tree in &trees {
tree.root().visit(|n| {
if n.kind() == SK::IdentifierName
&& n.parent().is_some_and(|p| p.kind() == SK::ScopedName)
&& !matches!(Self::classify_use(n), UseCtx::Modelled)
&& Self::enclosing_instance_def(n).is_none()
{
set.insert(n.text().trim().to_owned());
}
sv_lang::Walk::Continue
});
}
set
});
if qualified.contains(&name) || self.qual_fail.contains(&name) {
return true;
}
for (tree_ix, tree) in trees.iter().enumerate() {
let Some(decl) = sym.syntax(tree) else {
continue;
};
let mut def = decl.parent();
while let Some(d) = def {
if matches!(
d.kind(),
SK::ModuleDeclaration
| SK::InterfaceDeclaration
| SK::ProgramDeclaration
| SK::PackageDeclaration
) {
break;
}
def = d.parent();
}
let Some(def) = def else {
return true;
};
let mut folded = false;
let mut ranges = Vec::new();
let mut gens = Vec::new();
let genvars = Self::genvar_names(def);
def.visit(|n| {
if n.kind() == SK::IdentifierName && n.text().trim() == name {
match Self::classify_use(n) {
UseCtx::Range(r) => ranges.push(r),
UseCtx::Gen(c) => gens.push(c),
UseCtx::Refused => folded = true,
UseCtx::Modelled => {}
}
}
sv_lang::Walk::Continue
});
for r in ranges {
if !self.range_use_expr(sym, tree_ix, r) {
folded = true;
}
}
for cands in gens {
if !self.gen_use_expr(sym, tree_ix, &cands, &genvars) {
folded = true;
}
}
return folded;
}
true
}
fn parameter(&mut self, sym: &Symbol<'_>, parent: ObjId, parent_name: &str) {
self.parameter_sep(sym, parent, parent_name, ".");
}
fn parameter_sep(&mut self, sym: &Symbol<'_>, parent: ObjId, parent_name: &str, sep: &str) {
let name = sym.name().to_owned();
let full = format!("{parent_name}{sep}{name}");
let mut obj = Obj::new(ObjKind::Parameter, Some(parent), &name, &full);
obj.props.local_param = sym.parameter_is_local_param();
obj.props.file = Some(self.file.clone());
obj.props.line = self.locator.line(sym);
Self::fill_value(&mut obj.props, &sym.constant_value());
if let Some(t) = sym.declared_type()
&& !t.is_alias()
&& self.locator.syntax_trees().iter().any(|tr| {
sym.declared_type_syntax(tr)
.is_some_and(|n| n.text().contains('['))
})
{
let canon = t.canonical();
if canon.is_packed_array() {
let r = canon.fixed_range();
obj.props.range = Some((r.left, r.right));
}
}
let id = self.add(obj);
self.prop_by_uid.insert(sym.uid(), id);
self.param_objs.push((name.clone(), id));
if self.param_has_folded_use(sym) {
self.model.mark_param_folded(id);
}
self.attributes(sym.attributes(), id, &full, false);
self.typespec_rel(id, sym.declared_type());
self.parameter_default(sym, id);
}
fn parameter_default(&mut self, sym: &Symbol<'_>, id: ObjId) {
let mut text = None;
let mut bound = None;
for tree in self.locator.syntax_trees() {
if let Some(n) = sym.parameter_default_syntax(&tree) {
text = Some(n.text().trim().to_owned());
if let Some(scope) = sym.parent()
&& let Some(e) = self.eval.bind_expression(n, scope)
{
let e = unwrap_invalid_expr(e);
if !e.is_bad() {
bound = self.operand(&e);
}
}
break;
}
}
let Some(text) = text else { return };
let node = formal_default_node(&text);
if matches!(node, ExprNode::Unsupported(_))
&& let Some(op) = bound
{
self.rel(id, consts::VPI_EXPR, op);
return;
}
let e = self.model.add_expr(node);
self.rel(id, consts::VPI_EXPR, Operand::Expr(e));
}
fn type_parameter_default(&mut self, sym: &Symbol<'_>, id: ObjId) {
if sym.type_parameter_is_overridden() {
let e = self.model.add_expr(ExprNode::Unsupported(format!(
"the default typespec of the overridden type parameter {}",
sym.name()
)));
self.rel(id, consts::VPI_EXPR, Operand::Expr(e));
} else if let Some(ts) = sym.declared_type().and_then(|t| self.typespec(&t)) {
self.rel(id, consts::VPI_EXPR, Operand::Obj(ts));
}
}
fn tchk(&mut self, sym: &Symbol<'_>, parent: ObjId) {
use sv_lang::SystemTimingCheckKind as K;
let kind = sym.system_timing_check_kind();
let (ty, refi, datai, notifier) = match kind {
K::Setup => ("vpiSetup", Some(1), Some(0), 3),
K::Hold => ("vpiHold", Some(0), Some(1), 3),
K::SetupHold => ("vpiSetupHold", Some(0), Some(1), 4),
K::Recovery => ("vpiRecovery", Some(0), Some(1), 3),
K::Removal => ("vpiRemoval", Some(0), Some(1), 3),
K::RecRem => ("vpiRecrem", Some(0), Some(1), 4),
K::Skew => ("vpiSkew", Some(0), Some(1), 3),
K::TimeSkew => ("vpiTimeskew", Some(0), Some(1), 3),
K::FullSkew => ("vpiFullskew", Some(0), Some(1), 4),
K::Period => ("vpiPeriod", Some(0), None, 2),
K::Width => ("vpiWidth", Some(0), None, 3),
K::NoChange => ("vpiNoChange", Some(0), Some(1), 4),
K::Unknown => return,
};
let mut obj = Obj::new(ObjKind::Tchk, Some(parent), "", "");
obj.props.prim_type = header(ty);
obj.props.path_key = self.body.clone().map(|b| (b, sym.uid()));
obj.props.file = Some(self.file.clone());
obj.props.line = self.locator.line(sym);
let id = self.add(obj);
let args: Vec<sv_lang::SystemTimingCheckArg<'_>> =
sym.system_timing_check_arguments().collect();
for (i, a) in args.iter().enumerate() {
let is_event = Some(i) == refi || Some(i) == datai;
let op = if is_event {
let mut t = Obj::new(ObjKind::TchkTerm, Some(id), "", "");
t.props.prim_type = crate::timingcheck::vpi_edge(a);
t.props.file = Some(self.file.clone());
t.props.line = self.locator.line(sym);
let tid = self.add(t);
if let Some(x) = a.expr {
let e = self.operand(&x);
self.fold_if_unmodelled(&x, e);
if let Some(e) = e {
self.rel(tid, consts::VPI_EXPR, e);
}
}
if let Some(x) = a.condition {
let c = self.operand(&x);
self.fold_if_unmodelled(&x, c);
if let Some(c) = c {
self.rel(tid, consts::VPI_CONDITION, c);
}
}
let rel = if Some(i) == refi {
"vpiTchkRefTerm"
} else {
"vpiTchkDataTerm"
};
self.rel(id, header(rel), Operand::Obj(tid));
Some(Operand::Obj(tid))
} else {
let x = a.expr.map(|e| {
if e.kind() == sv_lang::kinds::ExpressionKind::Assignment {
e.left().unwrap_or(e)
} else {
e
}
});
let o = x.and_then(|e| self.operand(&e));
if let Some(x) = x {
self.fold_if_unmodelled(&x, o);
}
o
};
if i == notifier
&& let Some(n) = op
{
self.rel(id, header("vpiTchkNotifier"), n);
}
if let Some(op) = op {
self.rel(id, consts::VPI_EXPR, op);
}
}
}
fn class_typespec(&mut self, ty: &Type<'_>, key: &str) -> Option<ObjId> {
let sym = ty.as_symbol();
let name = sym.name().to_owned();
let mut obj = Obj::new(ObjKind::Typespec, None, &name, "");
obj.props.stmt_type = header("vpiClassTypespec");
let builtin = sym
.parent()
.is_some_and(|p| p.kind() == SymbolKind::Package && p.name() == "std");
obj.props.class_type = header(match (builtin, name.as_str()) {
(true, "mailbox") => "vpiMailboxClass",
(true, "semaphore") => "vpiSemaphoreClass",
(true, "process") => "vpiProcessClass",
_ => "vpiUserDefinedClass",
});
obj.props.file = Some(self.file.clone());
obj.props.line = self.locator.line(&sym);
let id = self.add(obj);
self.typespecs.insert(key.to_owned(), id);
self.model
.class_by_uid
.insert(format!("{:?}", sym.uid()), id);
if !builtin {
let defn_sym = ty.class_generic_class().unwrap_or(sym);
if let Some(defn) = self.class_defn(&defn_sym, Some(&sym), None) {
self.rel(id, header("vpiClassDefn"), Operand::Obj(defn));
self.rel(defn, header("vpiClassTypespec"), Operand::Obj(id));
}
}
if let Some(base) = ty.class_base()
&& let Some(b) = self.typespec(&base)
{
self.rel(id, header("vpiExtends"), Operand::Obj(b));
}
Some(id)
}
fn constraint_ordinals(c: &Symbol<'_>) -> std::collections::HashMap<String, u32> {
let mut levels = vec![*c];
let mut cur = c.as_type();
while let Some(base) = cur.and_then(|t| t.class_base()) {
levels.push(base.as_symbol());
cur = Some(base);
}
levels.reverse();
let mut names: Vec<String> = Vec::new();
for level in levels {
for m in level.members() {
if m.kind() == SymbolKind::ConstraintBlock
&& !m
.constraint_block_flags()
.contains(sv_lang::ConstraintBlockFlags::PURE)
&& !names.iter().any(|n| n == m.name())
{
names.push(m.name().to_owned());
}
}
}
names
.into_iter()
.enumerate()
.map(|(i, n)| (n, i as u32))
.collect()
}
fn constraint_block(
&mut self,
m: &Symbol<'_>,
parent: ObjId,
full: &str,
ordinals: &std::collections::HashMap<String, u32>,
class_key: Option<&str>,
) {
let flags = m.constraint_block_flags();
let name = m.name().to_owned();
let cfull = format!("{full}::{name}");
let mut obj = Obj::new(ObjKind::Constraint, Some(parent), &name, &cfull);
obj.props.virtual_ = flags.contains(sv_lang::ConstraintBlockFlags::PURE);
obj.props.constraint_static = flags.contains(sv_lang::ConstraintBlockFlags::STATIC);
obj.props.extern_ = flags.contains(sv_lang::ConstraintBlockFlags::EXTERN)
|| flags.contains(sv_lang::ConstraintBlockFlags::EXPLICIT_EXTERN);
obj.props.constraint_ordinal = ordinals.get(&name).copied();
if obj.props.constraint_static {
obj.props.constraint_class_key = class_key.map(str::to_owned);
}
obj.props.file = Some(self.file.clone());
obj.props.line = self.locator.line(m);
let id = self.add(obj);
if let Some(root) = m
.constraint_block_constraints()
.and_then(|n| n.as_constraint())
{
let trees = self.locator.syntax_trees();
for item in self.constraint_items(root, &trees, id, &cfull) {
self.rel(id, header("vpiConstraintItem"), item);
}
}
}
fn call_with(&mut self, e: &sv_lang::Expression<'_>) -> Option<Operand> {
if let Some(w) = e.iterator_expr() {
return self.operand(&w);
}
let root = e
.randomize_inline_constraints()
.and_then(|n| n.as_constraint())?;
let n = self.inline_constraints;
self.inline_constraints += 1;
let full = format!("$with{n}");
let mut obj = Obj::new(ObjKind::Constraint, None, "", &full);
obj.props.file = Some(self.file.clone());
let id = self.add(obj);
let trees = self.locator.syntax_trees();
for item in self.constraint_items(root, &trees, id, &full) {
self.rel(id, header("vpiConstraintItem"), item);
}
Some(Operand::Obj(id))
}
fn constraint_syntax<'t>(
c: &sv_lang::Constraint<'_>,
trees: &'t [SyntaxTree],
) -> Option<sv_lang::Node<'t>> {
trees.iter().find_map(|t| c.syntax(t))
}
fn constraint_items(
&mut self,
c: sv_lang::Constraint<'_>,
trees: &[SyntaxTree],
owner: ObjId,
owner_full: &str,
) -> Vec<Operand> {
use sv_lang::AstNode;
use sv_lang::kinds::ConstraintKind as C;
let kids = c.children();
let sub_constraints: Vec<sv_lang::Constraint<'_>> =
kids.iter().filter_map(|n| n.as_constraint()).collect();
let exprs: Vec<sv_lang::Expression<'_>> =
kids.iter().filter_map(|n| n.as_expression()).collect();
let items_of = |s: &mut Self, k: Option<&sv_lang::Constraint<'_>>| -> Vec<Operand> {
k.map_or_else(Vec::new, |k| {
s.constraint_items(*k, trees, owner, owner_full)
})
};
let node = |s: &mut Self, n: ExprNode| Operand::Expr(s.model.add_expr(n));
let unsupported = |s: &mut Self, what: String| {
Operand::Expr(s.model.add_expr(ExprNode::Unsupported(what)))
};
match c.kind() {
C::List => sub_constraints
.iter()
.flat_map(|k| self.constraint_items(*k, trees, owner, owner_full))
.collect(),
C::Expression => {
let Some(e) = c.expr() else { return Vec::new() };
let soft = Self::constraint_syntax(&c, trees)
.and_then(sv_lang::nodes::ExpressionConstraintSyntax::cast)
.is_some_and(|s| s.soft().is_some());
if e.kind() == sv_lang::kinds::ExpressionKind::Dist {
let Some(left) = e.dist_left().and_then(|l| self.operand(&l)) else {
return Vec::new();
};
let mut items = Vec::new();
for it in e.dist_items() {
let Some(value) = self.operand(&it.value()) else {
continue;
};
let w = it.weight();
let weight = w.as_ref().and_then(|w| self.operand(&w.expr));
let div = w.is_some_and(|w| w.kind == sv_lang::DistWeightKind::PerRange);
items.push(node(self, ExprNode::DistItem { value, weight, div }));
}
return vec![node(
self,
ExprNode::Distribution {
expr: left,
items,
soft,
},
)];
}
let Some(op) = self.operand(&e) else {
return Vec::new();
};
if soft
&& let Operand::Expr(x) = op
&& let Some(ExprNode::Op { meta, .. }) = self.model.expr_mut(x)
{
meta.soft = true;
}
vec![op]
}
C::Implication => {
let Some(cond) = c.predicate().and_then(|p| self.operand(&p)) else {
return Vec::new();
};
let body = items_of(self, sub_constraints.first());
vec![node(self, ExprNode::Implication { cond, body })]
}
C::Conditional => {
let Some(cond) = c.predicate().and_then(|p| self.operand(&p)) else {
return Vec::new();
};
let body = items_of(self, sub_constraints.first());
let els = sub_constraints.get(1).map(|k| items_of(self, Some(k)));
vec![node(self, ExprNode::ConstrIf { cond, body, els })]
}
C::Foreach => {
let Some(array) = exprs.first().and_then(|a| self.operand(a)) else {
return Vec::new();
};
let names: Vec<String> = Self::constraint_syntax(&c, trees)
.and_then(sv_lang::nodes::LoopConstraintSyntax::cast)
.map(|l| {
l.loop_list()
.loop_variables()
.iter()
.map(|v| v.syntax().text().trim().to_string())
.collect()
})
.unwrap_or_default();
let mut vars = Vec::new();
for n in names {
if n.is_empty() {
vars.push(node(
self,
ExprNode::Op {
op_type: header("vpiNullOp"),
width: 0,
operands: Vec::new(),
meta: OpMeta::default(),
},
));
continue;
}
let mut v = Obj::new(
ObjKind::IntVar,
Some(owner),
&n,
&format!("{owner_full}.{n}"),
);
v.props.width = 32;
v.props.signed = true;
v.props.automatic = true;
v.props.file = Some(self.file.clone());
let vid = self.add(v);
vars.push(Operand::Obj(vid));
}
let mark = self.loop_vars.len();
for v in &vars {
if let Operand::Obj(id) = v
&& let Some(o) = self.model.get(*id)
{
self.loop_vars.push((o.name.clone(), *id));
}
}
let body = items_of(self, sub_constraints.first());
self.loop_vars.truncate(mark);
vec![node(self, ExprNode::ConstrForeach { array, vars, body })]
}
C::SolveBefore => {
let n_before = Self::constraint_syntax(&c, trees)
.and_then(sv_lang::nodes::SolveBeforeConstraintSyntax::cast)
.map_or(exprs.len(), |s| s.before_expr().iter().count());
let ops: Vec<Operand> = exprs.iter().filter_map(|e| self.operand(e)).collect();
let split = n_before.min(ops.len());
let (before, after) = ops.split_at(split);
vec![node(
self,
ExprNode::ConstrOrdering {
before: before.to_vec(),
after: after.to_vec(),
},
)]
}
C::DisableSoft => match exprs.first().and_then(|e| self.operand(e)) {
Some(expr) => vec![node(self, ExprNode::SoftDisable { expr })],
None => Vec::new(),
},
other => vec![unsupported(
self,
format!("a {other:?} constraint (37.38 has no object for it)"),
)],
}
}
fn class_defn(
&mut self,
defn: &Symbol<'_>,
content: Option<&Symbol<'_>>,
scope: Option<(ObjId, &str)>,
) -> Option<ObjId> {
let key = format!("classdefn:{:?}", defn.uid());
let id = match self.typespecs.get(&key) {
Some(&id) => id,
None => {
let name = defn.name().to_owned();
let full = scope.map_or(name.clone(), |(_, n)| format!("{n}.{name}"));
let mut obj = Obj::new(ObjKind::ClassDefn, scope.map(|(p, _)| p), &name, &full);
obj.props.file = Some(self.file.clone());
obj.props.line = self.locator.line(defn);
let id = self.add(obj);
self.typespecs.insert(key.clone(), id);
id
}
};
let built = format!("{key}:built");
if let Some(c) = content
&& !self.typespecs.contains_key(&built)
{
self.typespecs.insert(built, id);
self.class_contents(c, id);
}
Some(id)
}
fn class_contents(&mut self, c: &Symbol<'_>, id: ObjId) {
let full = self
.model
.get(id)
.map(|o| o.full_name.clone())
.unwrap_or_default();
if let Some(t) = c.as_type()
&& let Some(p) = self.model.props_mut(id)
{
p.virtual_ = t.class_is_abstract();
}
let layout = vj_codegen::classes::ObjectLayout::for_template(*c).ok();
for m in c.members() {
match m.kind() {
SymbolKind::ClassProperty => {
let Some(ty) = m.value_type() else { continue };
let name = m.name().to_owned();
let mut v = Obj::new(
Self::data_kind(&ty, false),
Some(id),
&name,
&format!("{full}::{name}"),
);
Self::fill_scalar_props(&mut v.props, &ty);
v.props.automatic = m.variable_lifetime() == VariableLifetime::Automatic;
v.props.visibility = match m.class_property_visibility() {
sv_lang::Visibility::Protected => header("vpiProtectedVis"),
sv_lang::Visibility::Local => header("vpiLocalVis"),
sv_lang::Visibility::Public => 0,
};
v.props.field_slot = layout
.as_ref()
.and_then(|l| l.slot_of(&m))
.map(|s| (s.offset, s.words));
v.props.file = Some(self.file.clone());
v.props.line = self.locator.line(&m);
let vid = self.add(v);
self.prop_by_uid.insert(m.uid(), vid);
self.typespec_rel(vid, Some(ty));
}
SymbolKind::Subroutine
if !m
.subroutine_flags()
.contains(sv_lang::MethodFlags::BUILT_IN) =>
{
self.task_func(&m, id, &full, "::");
}
SymbolKind::Parameter => self.parameter(&m, id, &full),
SymbolKind::TypeAlias => {
if let Some(ts) = m.as_type().and_then(|t| self.typespec(&t)) {
self.rel(id, header("vpiTypedef"), Operand::Obj(ts));
}
}
_ => {}
}
}
if let Some(t) = c.as_type()
&& let Some(base) = t.class_base()
{
let mut e = Obj::new(ObjKind::Extends, Some(id), "", "");
e.props.file = Some(self.file.clone());
let eid = self.add(e);
if let Some(b) = self.typespec(&base) {
self.rel(eid, header("vpiClassTypespec"), Operand::Obj(b));
let base_defn = self.model.get(b).and_then(|o| {
o.props
.rels
.iter()
.find(|(k, _)| *k == header("vpiClassDefn"))
.map(|(_, t)| *t)
});
if let Some(Operand::Obj(bd)) = base_defn {
self.rel(bd, header("vpiDerivedClasses"), Operand::Obj(id));
}
}
if let Some(call) = t.class_base_constructor_call() {
for a in call.operands() {
if let Some(op) = self.operand(&a) {
self.rel(eid, consts::VPI_ARGUMENT, op);
}
}
}
self.rel(id, header("vpiExtends"), Operand::Obj(eid));
}
let ordinals = Self::constraint_ordinals(c);
let class_key = c.as_type().map(vj_sim::netlist::class_specialization_key);
for m in c.members() {
if m.kind() == SymbolKind::ConstraintBlock {
self.constraint_block(&m, id, &full, &ordinals, class_key.as_deref());
}
}
}
fn assertion_decl(&mut self, sym: &Symbol<'_>, parent: ObjId, parent_name: &str) {
let property = sym.kind() == SymbolKind::Property;
let name = sym.name().to_owned();
let full = format!("{parent_name}.{name}");
let mut obj = Obj::new(
if property {
ObjKind::PropertyDecl
} else {
ObjKind::SequenceDecl
},
Some(parent),
&name,
&full,
);
obj.props.file = Some(self.file.clone());
obj.props.line = self.locator.line(sym);
let id = self.add(obj);
self.formal_names.clear();
let mut formals: Vec<(Symbol<'_>, ObjId)> = Vec::new();
let ports: Vec<Symbol<'_>> = if property {
sym.property_ports().collect()
} else {
sym.sequence_ports().collect()
};
for p in ports {
let pname = p.name().to_owned();
let mut f = Obj::new(
if property {
ObjKind::PropFormalDecl
} else {
ObjKind::SeqFormalDecl
},
Some(id),
&pname,
&format!("{full}.{pname}"),
);
f.props.direction = if p.assertion_port_is_local_var() {
match p.assertion_port_direction() {
Some(ArgumentDirection::Out) => consts::VPI_OUTPUT,
Some(ArgumentDirection::InOut) => consts::VPI_INOUT,
_ => consts::VPI_INPUT,
}
} else {
header("vpiNoDirection")
};
f.props.file = Some(self.file.clone());
f.props.line = self.locator.line(&p);
let fid = self.add(f);
self.formal_by_uid.insert(p.uid(), fid);
self.formal_names.push((pname, fid));
formals.push((p, fid));
if let Some(t) = p.declared_type().filter(|t| !t.is_untyped_type()) {
self.typespec_rel(fid, Some(t));
}
}
let scope_sym = sym.parent().unwrap_or(*sym);
let trees = self.locator.syntax_trees();
let bound = sym.bound_body().map(unwrap_invalid_expr);
let body = bound.and_then(|b| {
b.as_sem_node()
.children_with_invalid()
.into_iter()
.find_map(|n| n.as_assertion_expr())
});
self.decl_locals(sym, id, &full, body, property);
let mut defaults = std::collections::HashMap::new();
if let Some(body) = body {
for (p, _) in &formals {
let Some(range) = trees
.iter()
.find_map(|t| p.assertion_port_default_syntax(t).map(|n| n.byte_range()))
else {
continue;
};
let Some(e) = Self::expr_at_range(body.as_sem_node(), &range) else {
continue;
};
if let Some(op) = self.leaf_operand(&e, &trees, &scope_sym) {
defaults.insert(p.uid(), op);
}
}
}
for (p, fid) in &formals {
self.formal_default(p, *fid, defaults.get(&p.uid()).copied(), &scope_sym);
}
self.formals_by_text = true;
self.decl_parent = Some(id);
let real = body.is_some_and(|body| self.assertion_decl_real_body(sym, id, property, body));
self.decl_parent = None;
self.formal_names.clear();
self.formals_by_text = false;
if !real {
self.assertion_decl_body(sym, id, property);
}
}
fn decl_locals(
&mut self,
sym: &Symbol<'_>,
id: ObjId,
full: &str,
body: Option<sv_lang::AssertionExpr<'_>>,
property: bool,
) {
use sv_lang::AstNode;
let mut declared: Vec<String> = Vec::new();
for tree in self.locator.syntax_trees() {
let Some(node) = sym.syntax(&tree) else {
continue;
};
let lists = if property {
sv_lang::nodes::PropertyDeclarationSyntax::cast(node).map(|d| d.variables())
} else {
sv_lang::nodes::SequenceDeclarationSyntax::cast(node).map(|d| d.variables())
};
let Some(lists) = lists else { continue };
for decl in lists.into_iter() {
for d in decl.declarators().into_iter() {
if let Some(n) = d.name() {
declared.push(n.text().to_owned());
}
}
}
break;
}
if declared.is_empty() {
return;
}
fn find<'d>(n: sv_lang::SemNode<'d>, out: &mut Vec<Symbol<'d>>) {
if let Some(e) = n.as_expression()
&& let Some(s) = e.referenced_symbol()
&& s.kind() == SymbolKind::LocalAssertionVar
&& !out.iter().any(|o| o.uid() == s.uid())
{
out.push(s);
}
for k in n.children_with_invalid() {
find(k, out);
}
}
let mut seen = Vec::new();
if let Some(b) = body {
find(b.as_sem_node(), &mut seen);
}
let mut unbound = Vec::new();
for name in declared {
let Some(v) = seen.iter().find(|s| s.name() == name) else {
unbound.push(name);
continue;
};
let first = self.model.len() as ObjId;
self.data(v, id, full, false);
let last = self.model.len() as ObjId;
for o in first..last {
if let Some(p) = self.model.props_mut(o) {
p.automatic = true;
}
}
if first < last {
self.prop_by_uid.insert(v.uid(), first);
}
}
if !unbound.is_empty()
&& let Some(p) = self.model.props_mut(id)
{
p.unbound_locals = unbound;
}
}
fn leaves_within(n: &sv_lang::SemNode<'_>, range: &std::ops::Range<usize>) -> Option<bool> {
let kids: Vec<_> = n.children_with_invalid();
if kids.iter().all(|k| k.as_expression().is_none())
&& let Some(e) = n.as_expression()
{
let r = e.source_range();
let (s, t) = (r.start.offset as usize, r.end.offset as usize);
return Some(t > s && s >= range.start && t <= range.end);
}
let mut seen = None;
for k in &kids {
match Self::leaves_within(k, range) {
Some(false) => return Some(false),
Some(true) => seen = Some(true),
None => {}
}
}
seen
}
fn expr_at_range<'d>(
n: sv_lang::SemNode<'d>,
range: &std::ops::Range<usize>,
) -> Option<sv_lang::Expression<'d>> {
for k in n.children_with_invalid() {
if let Some(e) = k.as_expression()
&& e.kind() != sv_lang::kinds::ExpressionKind::Invalid
&& Self::leaves_within(&k, range) == Some(true)
{
return Some(e);
}
if let Some(e) = Self::expr_at_range(k, range) {
return Some(e);
}
}
None
}
fn assertion_decl_real_body(
&mut self,
sym: &Symbol<'_>,
id: ObjId,
property: bool,
body: sv_lang::AssertionExpr<'_>,
) -> bool {
use sv_lang::kinds::AssertionExprKind as A;
let scope_sym = sym.parent().unwrap_or(*sym);
let trees = self.locator.syntax_trees();
let mut body = body;
let mut clock = None;
let mut disable = None;
if property {
loop {
match body.kind() {
A::Clocking => {
let Ok(c) = vj_codegen::sva_shape::op::clocking_children(body, &scope_sym)
else {
break;
};
clock = c
.event
.as_timing_control()
.and_then(|t| self.event_operand(&t));
body = c.body;
}
A::DisableIff => {
let Ok(d) =
vj_codegen::sva_shape::op::disable_iff_children(body, &scope_sym)
else {
break;
};
disable = d.condition.as_expression().and_then(|e| self.operand(&e));
body = d.body;
}
_ => break,
}
}
}
let saved = std::mem::replace(&mut self.ctx_clock, clock);
let expr = self.prop_expr(body, &trees, &scope_sym);
self.ctx_clock = saved;
let Some(expr) = expr else { return false };
if property {
let mut ps = Obj::new(ObjKind::PropertySpec, Some(id), "", "");
ps.props.file = Some(self.file.clone());
ps.props.line = self.locator.line(sym);
let ps_id = self.add(ps);
if let Some(c) = clock {
self.rel(ps_id, header("vpiClockingEvent"), c);
}
if let Some(d) = disable {
self.rel(ps_id, header("vpiDisableCondition"), d);
}
self.rel(ps_id, header("vpiPropertyExpr"), expr);
self.rel(id, header("vpiProperty"), Operand::Obj(ps_id));
} else {
self.rel(id, consts::VPI_EXPR, expr);
}
true
}
fn assertion_decl_body(&mut self, sym: &Symbol<'_>, id: ObjId, property: bool) {
use sv_lang::AstNode;
let mut text = None;
for tree in self.locator.syntax_trees() {
let Some(node) = sym.syntax(&tree) else {
continue;
};
let full = if property {
sv_lang::nodes::PropertyDeclarationSyntax::cast(node)
.map(|n| n.syntax().text().to_owned())
} else {
sv_lang::nodes::SequenceDeclarationSyntax::cast(node)
.map(|n| n.syntax().text().to_owned())
};
if let Some(full) = full {
text = Some(full);
break;
}
}
let Some(full) = text else { return };
let mut depth = 0i32;
let mut start = None;
for (i, c) in full.char_indices() {
match c {
'(' => depth += 1,
')' => depth -= 1,
';' if depth == 0 => {
start = Some(i + 1);
break;
}
_ => {}
}
}
let end_kw = if property {
"endproperty"
} else {
"endsequence"
};
let (Some(start), Some(end)) = (start, full.rfind(end_kw)) else {
return;
};
let body = full[start..end.max(start)]
.trim()
.trim_end_matches(';')
.trim()
.to_owned();
let kind = if property { "property" } else { "sequence" };
let e = self.model.add_expr(ExprNode::Unsupported(format!(
"the body `{body}` of a {kind} declaration (slang binds it only at an instance)"
)));
if property {
let mut ps = Obj::new(ObjKind::PropertySpec, Some(id), "", "");
ps.props.file = Some(self.file.clone());
ps.props.line = self.locator.line(sym);
let ps_id = self.add(ps);
self.rel(ps_id, header("vpiPropertyExpr"), Operand::Expr(e));
self.rel(id, header("vpiProperty"), Operand::Obj(ps_id));
} else {
self.rel(id, consts::VPI_EXPR, Operand::Expr(e));
}
}
fn span_of(&self, stmt: &Statement<'_>) -> Option<(i32, i32, i32, i32)> {
for (tree, starts) in &self.locator.trees {
if let Some(node) = stmt.syntax(tree) {
let r = node.byte_range();
let at = |o: usize| {
let line = starts.partition_point(|&s| s <= o);
(line as i32, (o - starts[line - 1]) as i32 + 1)
};
let (sl, sc) = at(r.start);
let (el, ec) = at(r.end.saturating_sub(1).max(r.start));
return Some((sl, sc, el, ec));
}
}
None
}
fn assertion_structure(
&mut self,
stmt: &Statement<'_>,
id: ObjId,
scope: &str,
block: &Symbol<'_>,
) {
let span = self.span_of(stmt);
if let Some(p) = self.model.props_mut(id) {
p.span = span;
}
let concurrent = stmt.kind() == StatementKind::ConcurrentAssertion;
let (then, els) = if concurrent {
(stmt.assertion_if_true(), stmt.assertion_if_false())
} else {
(
stmt.immediate_assertion_if_true(),
stmt.immediate_assertion_if_false(),
)
};
let no_action = concurrent && stmt.assertion_kind() == Some(AssertionKind::Restrict);
if !no_action
&& let Some(t) = then
&& let Some(s) = self.stmt(&t, id, scope)
{
self.rel(id, consts::VPI_STMT, Operand::Obj(s));
}
if let Some(e) = els
&& let Some(s) = self.stmt(&e, id, scope)
{
self.rel(id, consts::VPI_ELSE_STMT, Operand::Obj(s));
}
if !concurrent {
if let Some(c) = stmt.children().into_iter().find_map(|n| n.as_expression())
&& let Some(op) = self.operand(&c)
{
self.rel(id, consts::VPI_EXPR, op);
}
return;
}
let cover_seq = stmt.assertion_kind() == Some(AssertionKind::CoverSequence);
let Some(spec) = stmt
.children()
.into_iter()
.find_map(|n| n.as_assertion_expr())
else {
return;
};
let outer = self.cur_assertion.replace(id);
let scope_sym = block.parent().unwrap_or(*block);
let trees = self.locator.syntax_trees();
let mut body = spec;
let mut clock = None;
let mut disable = None;
loop {
match body.kind() {
sv_lang::kinds::AssertionExprKind::Clocking => {
let Ok(c) = vj_codegen::sva_shape::op::clocking_children(body, &scope_sym)
else {
break;
};
clock = c
.event
.as_timing_control()
.and_then(|t| self.event_operand(&t));
body = c.body;
}
sv_lang::kinds::AssertionExprKind::DisableIff => {
let Ok(d) = vj_codegen::sva_shape::op::disable_iff_children(body, &scope_sym)
else {
break;
};
disable = d.condition.as_expression().and_then(|e| self.operand(&e));
body = d.body;
}
_ => break,
}
}
let saved = std::mem::replace(&mut self.ctx_clock, clock);
let expr = self.prop_expr(body, &trees, &scope_sym);
self.ctx_clock = saved;
let mut ps = Obj::new(ObjKind::PropertySpec, Some(id), "", "");
ps.props.file = Some(self.file.clone());
ps.props.line = span.map_or(0, |s| s.0);
let ps_id = self.add(ps);
if let Some(c) = clock {
self.rel(ps_id, header("vpiClockingEvent"), c);
}
let actual = match clock {
Some(c) => Some(c),
None => {
let leading = Self::leading_instance_clock(body, &scope_sym);
let procedural = || {
(block.kind() == SymbolKind::ProceduralBlock)
.then(|| {
vj_sim::analysis::infer_structural_clock(block)
.ok()
.flatten()
})
.flatten()
};
match leading.or_else(procedural) {
Some(t) => self.event_operand(&t),
None => {
let default = trees.iter().find_map(|t| {
vj_sim::state::default_clocking_of(block, t).ok().flatten()
});
default
.and_then(|cb| cb.clocking_block_event())
.and_then(|n| n.as_timing_control())
.and_then(|t| self.event_operand(&t))
}
}
}
};
if let Some(c) = actual {
self.rel(id, header("vpiClockingEvent"), c);
}
if let Some(d) = disable {
self.rel(ps_id, header("vpiDisableCondition"), d);
self.rel(id, header("vpiDisableCondition"), d);
}
if let Some(e) = expr {
self.rel(ps_id, header("vpiPropertyExpr"), e);
}
self.rel(id, header("vpiProperty"), Operand::Obj(ps_id));
if let Some(p) = self.model.props_mut(id) {
p.clock_inferred = clock.is_none();
p.cover_sequence = cover_seq;
}
self.cur_assertion = outer;
}
fn leading_instance_clock<'d>(
mut e: sv_lang::AssertionExpr<'d>,
scope: &Symbol<'d>,
) -> Option<sv_lang::TimingControl<'d>> {
use sv_lang::kinds::AssertionExprKind as A;
loop {
match e.kind() {
A::Clocking => {
let c = vj_codegen::sva_shape::op::clocking_children(e, scope).ok()?;
return c.event.as_timing_control();
}
A::Simple => {
let x = e.children().into_iter().find_map(|n| n.as_expression())?;
if x.kind() != sv_lang::kinds::ExpressionKind::AssertionInstance {
return None;
}
e = x
.as_sem_node()
.children()
.into_iter()
.find_map(|n| n.as_assertion_expr())?;
}
_ => return None,
}
}
}
fn event_operand(&mut self, t: &sv_lang::TimingControl<'_>) -> Option<Operand> {
use sv_lang::kinds::TimingControlKind as T;
match t.kind() {
T::SignalEvent => {
let e = t.children().into_iter().find_map(|n| n.as_expression())?;
let op = self.operand(&e)?;
let edge = match t.edge() {
sv_lang::EdgeKind::PosEdge => Some("vpiPosedgeOp"),
sv_lang::EdgeKind::NegEdge => Some("vpiNegedgeOp"),
sv_lang::EdgeKind::BothEdges => Some("vpiAnyEdge"),
_ => None,
};
Some(match edge {
Some(name) if consts::value_of(name).is_some() && name != "vpiAnyEdge" => {
Operand::Expr(self.model.add_expr(ExprNode::Op {
op_type: header(name),
width: 1,
operands: vec![op],
meta: OpMeta::default(),
}))
}
_ => op,
})
}
T::EventList => {
let ops: Vec<Operand> = t
.children()
.into_iter()
.filter_map(|n| n.as_timing_control())
.filter_map(|c| self.event_operand(&c))
.collect();
Some(Operand::Expr(self.model.add_expr(ExprNode::Op {
op_type: header("vpiListOp"),
width: 1,
operands: ops,
meta: OpMeta::default(),
})))
}
_ => None,
}
}
fn bound(&mut self, v: Option<u32>) -> Operand {
Operand::Expr(self.model.add_expr(match v {
Some(v) => ExprNode::Const {
value: Logic::from_u64(u64::from(v), 32),
signed: true,
const_type: header("vpiDecConst"),
},
None => ExprNode::Const {
value: Logic::zero(1),
signed: false,
const_type: header("vpiUnboundedConst"),
},
}))
}
fn op_node(&mut self, name: &str, operands: Vec<Operand>, strong: bool) -> Operand {
Operand::Expr(self.model.add_expr(ExprNode::Op {
op_type: header(name),
width: 1,
operands,
meta: OpMeta {
strong,
..OpMeta::default()
},
}))
}
fn sequence_with_match(
&mut self,
e: sv_lang::AssertionExpr<'_>,
kids: Vec<sv_lang::SemNode<'_>>,
trees: &[SyntaxTree],
scope: &Symbol<'_>,
) -> Option<Operand> {
use sv_lang::AstNode;
use vj_codegen::sva_shape::{ranges, trees::Trees};
let unsupported = |s: &mut Self, what: &str| {
Some(Operand::Expr(
s.model.add_expr(ExprNode::Unsupported(what.into())),
))
};
let Some(syn) = Trees::new(trees).syntax(&e) else {
return unsupported(self, "a parenthesized sequence without a syntax node");
};
let Some(paren) = sv_lang::nodes::ParenthesizedSequenceExprSyntax::cast(
ranges::peel_sequence_parens(syn),
) else {
return unsupported(
self,
"a sequence-with-match that is not a parenthesized sequence",
);
};
let sub = kids.iter().find_map(|k| k.as_assertion_expr())?;
let mut inner = self.prop_expr(sub, trees, scope)?;
let parent = self.cur_assertion.or(self.decl_parent);
let mut items = Vec::new();
for k in kids.iter().filter(|k| k.as_assertion_expr().is_none()) {
let Some(x) = k.as_expression() else {
return unsupported(self, "a sequence match item that is not an expression");
};
let item = parent
.and_then(|p| self.for_clause_stmt(&x, p))
.map(Operand::Obj)
.or_else(|| self.operand(&x));
items.push(item?);
}
if !items.is_empty() {
let w = self.op_node("vpiListOp", vec![inner], false);
if let Operand::Expr(id) = w
&& let Some(ExprNode::Op { meta, .. }) = self.model.expr_mut(id)
{
meta.match_items = items;
}
inner = w;
}
let Some(rep) = paren.repetition() else {
return Some(inner);
};
let r = match ranges::repetition_range(rep, scope) {
Ok(r) => r,
Err(_) => {
return unsupported(self, "a sequence repetition whose bounds are not constants");
}
};
let name = match rep.op().map(|t| t.text()) {
Some("->") => "vpiGotoRepeatOp",
Some("=") => "vpiRepeatOp",
_ => "vpiConsecutiveRepeatOp",
};
let mut ops = vec![inner, self.bound(Some(r.min))];
if r.max != Some(r.min) {
ops.push(self.bound(r.max));
}
Some(self.op_node(name, ops, false))
}
fn assertion_text(trees: &[SyntaxTree], e: &sv_lang::AssertionExpr<'_>) -> String {
trees
.iter()
.find_map(|t| e.syntax(t).map(|n| n.text()))
.unwrap_or_default()
}
fn keyword_range(text: &str, kw: &str) -> Option<(u32, Option<u32>, bool)> {
let at = text.find(kw)? + kw.len();
let rest = text[at..].trim_start();
let inner = rest.strip_prefix('[')?;
let inner = &inner[..inner.find(']')?];
let mut parts = inner.split(':').map(str::trim);
let lo: u32 = parts.next()?.parse().ok()?;
match parts.next() {
None => Some((lo, Some(lo), false)),
Some("$") => Some((lo, None, true)),
Some(h) => Some((lo, Some(h.parse().ok()?), true)),
}
}
fn prop_expr(
&mut self,
e: sv_lang::AssertionExpr<'_>,
trees: &[SyntaxTree],
scope: &Symbol<'_>,
) -> Option<Operand> {
use sv_lang::kinds::AssertionExprKind as A;
use vj_codegen::sva_shape::op::{AssertionBinaryOp as B, AssertionUnaryOp as U, Op};
let kids = e.as_sem_node().children_with_invalid();
let sub = |s: &mut Self, n: &sv_lang::SemNode<'_>| -> Option<Operand> {
if let Some(a) = n.as_assertion_expr() {
s.prop_expr(a, trees, scope)
} else {
n.as_expression()
.and_then(|x| s.leaf_operand(&x, trees, scope))
}
};
let text = Self::assertion_text(trees, &e);
let range_ops = |s: &mut Self, lo: u32, hi: Option<u32>| -> Vec<Operand> {
let mut v = vec![s.bound(Some(lo))];
if hi != Some(lo) {
v.push(s.bound(hi));
}
v
};
let tr = vj_codegen::sva_shape::trees::Trees::new(trees);
match e.kind() {
A::Simple => {
let leaf = sub(self, kids.first()?)?;
let ranges = vj_codegen::sva_shape::ranges::sequence_range_of(e, tr, scope)
.unwrap_or_default();
match ranges.first() {
None => Some(leaf),
Some(r) => {
let name = if text.contains("[->") {
"vpiGotoRepeatOp"
} else if text.contains("[=") {
"vpiRepeatOp"
} else {
"vpiConsecutiveRepeatOp"
};
let mut ops = vec![leaf];
ops.extend(range_ops(self, r.min, r.max));
Some(self.op_node(name, ops, false))
}
}
}
A::SequenceConcat
if kids.iter().any(|k| {
k.as_assertion_expr()
.is_some_and(|a| a.kind() == A::Clocking)
}) =>
{
let mut seqs = Vec::new();
for k in &kids {
let clocked = k
.as_assertion_expr()
.is_some_and(|a| a.kind() == A::Clocking);
let s = sub(self, k)?;
seqs.push(match self.ctx_clock {
Some(clock) if !clocked => {
Operand::Expr(self.model.add_expr(ExprNode::Clocked {
clock,
body: s,
seq: true,
}))
}
_ => s,
});
}
Some(Operand::Expr(
self.model.add_expr(ExprNode::MulticlockSeq { seqs }),
))
}
A::SequenceConcat => {
use vj_codegen::sva_shape::ranges::{SequenceRangeValue, sequence_range_of};
let seqs: Vec<Operand> = kids.iter().filter_map(|k| sub(self, k)).collect();
let ranges = sequence_range_of(e, tr, scope).ok()?;
let leading = ranges.len() == seqs.len();
if !leading && ranges.len() + 1 != seqs.len() {
return Some(Operand::Expr(self.model.add_expr(ExprNode::Unsupported(
"a sequence concatenation whose delays do not match its operands".into(),
))));
}
let mut ranges = ranges.into_iter();
let mut seqs = seqs.into_iter();
let first = seqs.next()?;
let mut acc = if leading {
let SequenceRangeValue { min, max } = ranges.next()?;
let mut ops = vec![first];
ops.extend(range_ops(self, min, max));
self.op_node("vpiUnaryCycleDelayOp", ops, false)
} else {
first
};
for (s, SequenceRangeValue { min, max }) in seqs.zip(ranges) {
let mut ops = vec![acc, s];
ops.extend(range_ops(self, min, max));
acc = self.op_node("vpiCycleDelayOp", ops, false);
}
Some(acc)
}
A::SequenceWithMatch => self.sequence_with_match(e, kids, trees, scope),
A::FirstMatch => {
let s = kids
.iter()
.find_map(|k| k.as_assertion_expr())
.and_then(|a| self.prop_expr(a, trees, scope))?;
Some(self.op_node("vpiFirstMatchOp", vec![s], false))
}
A::Unary | A::Binary => {
let op = Op::of(e, scope).ok()?;
let operands: Vec<Operand> = kids.iter().filter_map(|k| sub(self, k)).collect();
let (name, strong, kw) = match op {
Op::Unary(U::Not) => ("vpiNotOp", false, None),
Op::Unary(U::NextTime) => ("vpiNexttimeOp", false, Some("nexttime")),
Op::Unary(U::SNextTime) => ("vpiNexttimeOp", true, Some("s_nexttime")),
Op::Unary(U::Always) => ("vpiAlwaysOp", false, Some("always")),
Op::Unary(U::SAlways) => ("vpiAlwaysOp", true, Some("s_always")),
Op::Unary(U::Eventually) => ("vpiEventuallyOp", false, Some("eventually")),
Op::Unary(U::SEventually) => ("vpiEventuallyOp", true, Some("s_eventually")),
Op::Binary(B::And) => ("vpiCompAndOp", false, None),
Op::Binary(B::Or) => ("vpiCompOrOp", false, None),
Op::Binary(B::Intersect) => ("vpiIntersectOp", false, None),
Op::Binary(B::Throughout) => ("vpiThroughoutOp", false, None),
Op::Binary(B::Within) => ("vpiWithinOp", false, None),
Op::Binary(B::Iff) => ("vpiIffOp", false, None),
Op::Binary(B::Until) => ("vpiUntilOp", false, None),
Op::Binary(B::SUntil) => ("vpiUntilOp", true, None),
Op::Binary(B::UntilWith) => ("vpiUntilWithOp", false, None),
Op::Binary(B::SUntilWith) => ("vpiUntilWithOp", true, None),
Op::Binary(B::Implies) => ("vpiImpliesOp", false, None),
Op::Binary(B::OverlappedImplication) => ("vpiOverlapImplyOp", false, None),
Op::Binary(B::NonOverlappedImplication) => {
("vpiNonOverlapImplyOp", false, None)
}
Op::Binary(B::OverlappedFollowedBy) => ("vpiOverlapFollowedByOp", false, None),
Op::Binary(B::NonOverlappedFollowedBy) => {
("vpiNonOverlapFollowedByOp", false, None)
}
};
let mut operands = operands;
if let Some(kw) = kw
&& let Some((lo, hi, ranged)) = Self::keyword_range(&text, kw)
{
if name == "vpiNexttimeOp" {
if lo != 1 {
operands.push(self.bound(Some(lo)));
}
} else if ranged {
operands.push(self.bound(Some(lo)));
operands.push(self.bound(hi));
}
}
Some(self.op_node(name, operands, strong))
}
A::Clocking => {
let c = vj_codegen::sva_shape::op::clocking_children(e, scope).ok()?;
let clock = c
.event
.as_timing_control()
.and_then(|t| self.event_operand(&t))?;
let seq = !matches!(
c.body.kind(),
A::Unary | A::Binary | A::Abort | A::Conditional | A::Case | A::DisableIff
);
let saved = self.ctx_clock.replace(clock);
let body = self.prop_expr(c.body, trees, scope);
self.ctx_clock = saved;
let body = body?;
Some(Operand::Expr(self.model.add_expr(ExprNode::Clocked {
clock,
body,
seq,
})))
}
A::StrongWeak => {
let inner = kids
.iter()
.find_map(|k| k.as_assertion_expr())
.and_then(|a| self.prop_expr(a, trees, scope))?;
if text.trim_start().starts_with("strong")
&& let Operand::Expr(x) = inner
&& let Some(ExprNode::Op { meta, .. }) = self.model.expr_mut(x)
{
meta.strong = true;
}
Some(inner)
}
A::Abort => {
let a = vj_codegen::sva_shape::op::abort_children(e, scope).ok()?;
let t = text.trim_start();
let name = if t.starts_with("sync_accept_on") {
"vpiSyncAcceptOnOp"
} else if t.starts_with("sync_reject_on") {
"vpiSyncRejectOnOp"
} else if t.starts_with("accept_on") {
"vpiAcceptOnOp"
} else {
"vpiRejectOnOp"
};
let cond = a.condition.as_expression().and_then(|x| self.operand(&x))?;
let body = self.prop_expr(a.body, trees, scope)?;
Some(self.op_node(name, vec![cond, body], false))
}
A::Conditional => {
let ops: Vec<Operand> = kids.iter().filter_map(|k| sub(self, k)).collect();
let name = if ops.len() > 2 {
"vpiIfElseOp"
} else {
"vpiIfOp"
};
Some(self.op_node(name, ops, false))
}
A::DisableIff => {
let d = vj_codegen::sva_shape::op::disable_iff_children(e, scope).ok()?;
self.prop_expr(d.body, trees, scope)
}
A::Case => {
let c = vj_codegen::sva_shape::op::case_children(e, tr, scope).ok()?;
let cond = c.selector.as_expression().and_then(|x| self.operand(&x))?;
let mut items = Vec::new();
for item in c.items {
let exprs: Vec<Operand> = item
.labels
.iter()
.filter_map(|l| l.as_expression().and_then(|x| self.operand(&x)))
.collect();
let prop = self.prop_expr(item.body, trees, scope)?;
items.push(Operand::Expr(
self.model.add_expr(ExprNode::CasePropItem { exprs, prop }),
));
}
if let Some(d) = c.default {
let prop = self.prop_expr(d, trees, scope)?;
items.push(Operand::Expr(self.model.add_expr(ExprNode::CasePropItem {
exprs: Vec::new(),
prop,
})));
}
Some(Operand::Expr(
self.model.add_expr(ExprNode::CaseProperty { cond, items }),
))
}
other => Some(Operand::Expr(self.model.add_expr(ExprNode::Unsupported(
format!("a {other:?} property expression"),
)))),
}
}
fn leaf_operand(
&mut self,
x: &sv_lang::Expression<'_>,
trees: &[SyntaxTree],
scope: &Symbol<'_>,
) -> Option<Operand> {
if x.kind() != sv_lang::kinds::ExpressionKind::AssertionInstance {
let op = self.operand(x)?;
if let Some(a) = self.cur_assertion {
let r = x.source_range();
if let Some(p) = self.model.props_mut(a) {
p.expr_srcs
.push(((r.start.buffer, r.start.offset, r.end.offset), op));
}
}
return Some(op);
}
let bound = x.assertion_arguments();
let text = trees
.iter()
.find_map(|t| x.syntax(t).map(|n| n.text()))
.unwrap_or_default();
let name = text
.split(|c: char| c == '(' || c.is_whitespace())
.find(|w| !w.is_empty())
.unwrap_or("")
.rsplit(['.', ':'])
.next()
.unwrap_or("")
.to_owned();
let decl_sym = bound.first().and_then(|a| a.port.parent()).or_else(|| {
let mut at = Some(*scope);
while let Some(sc) = at {
if let Some(m) = sc.members().find(|m| {
matches!(m.kind(), SymbolKind::Property | SymbolKind::Sequence)
&& m.name() == name
}) {
return Some(m);
}
at = sc.parent();
}
None
});
let property = decl_sym.is_some_and(|s| s.kind() == SymbolKind::Property);
let decl = decl_sym.and_then(|s| self.model.find(&s.hierarchical_path()));
let mut args = Vec::new();
for a in bound {
let op = if let Some(p) = a.actual.as_assertion_expr() {
self.prop_expr(p, trees, scope)
} else if let Some(e) = a.actual.as_expression() {
self.leaf_operand(&e, trees, scope)
} else if let Some(t) = a.actual.as_timing_control() {
self.event_operand(&t)
} else {
None
};
args.push(op.unwrap_or_else(|| {
Operand::Expr(self.model.add_expr(ExprNode::Unsupported(format!(
"a {} argument of a sequence/property instance",
a.actual.kind_name()
))))
}));
}
Some(Operand::Expr(self.model.add_expr(ExprNode::AssertInst {
decl,
name,
args,
property,
})))
}
fn fold_if_unmodelled(&mut self, e: &sv_lang::Expression<'_>, op: Option<Operand>) {
let ok = match op {
None => false,
Some(Operand::Expr(x)) => !matches!(self.model.expr(x), Some(ExprNode::Unsupported(_))),
Some(Operand::Obj(_)) => true,
};
if ok {
return;
}
let mut named = Vec::new();
Self::named_parameters(e, &mut named);
for p in named {
if let Some(id) = self.prop_by_uid.get(&p).copied() {
self.model.mark_param_folded(id);
}
}
}
fn spec_param(&mut self, sym: &Symbol<'_>, parent: ObjId, parent_name: &str) {
let name = sym.name().to_owned();
let full = format!("{parent_name}.{name}");
let mut obj = Obj::new(ObjKind::SpecParam, Some(parent), &name, &full);
obj.props.file = Some(self.file.clone());
obj.props.line = self.locator.line(sym);
Self::fill_value(&mut obj.props, &sym.constant_value());
let id = self.add(obj);
self.prop_by_uid.insert(sym.uid(), id);
if self.param_has_folded_use(sym) {
self.model.mark_param_folded(id);
}
self.typespec_rel(id, sym.declared_type());
let value = self.model.get(id).and_then(|o| match &o.props.param {
Some(ParamValue::Logic(l, signed)) => Some(ExprNode::Const {
value: l.clone(),
signed: *signed,
const_type: consts::VPI_BINARY_CONST,
}),
_ => None,
});
if let Some(op) = self.spec_param_initialiser(sym) {
self.rel(id, consts::VPI_EXPR, op);
} else if let Some(n) = value {
let e = self.model.add_expr(n);
self.rel(id, consts::VPI_EXPR, Operand::Expr(e));
}
}
fn spec_param_initialiser(&mut self, sym: &Symbol<'_>) -> Option<Operand> {
let scope = sym.parent()?;
for tree in self.locator.syntax_trees() {
let Some(decl) = sym.syntax(&tree) else {
continue;
};
let init = decl.children().last();
let expr = init?;
let e = self
.eval
.bind_expression(expr, scope)
.map(unwrap_invalid_expr);
let mut named = Vec::new();
if let Some(e) = e.as_ref().filter(|e| !e.is_bad()) {
Self::named_parameters(e, &mut named);
} else {
let mut idents = Vec::new();
expr.visit(|n| {
if n.kind() == sv_lang::kinds::SyntaxKind::IdentifierName {
idents.push(n.text().trim().to_owned());
}
sv_lang::Walk::Continue
});
for name in idents {
if let Some(p) = scope.find(&name)
&& let Some(id) = self.prop_by_uid.get(&p.uid()).copied()
{
self.model.mark_param_folded(id);
}
}
return None;
}
if named.is_empty() {
return None;
}
let op = e.as_ref().and_then(|e| self.operand(e));
let modelled = op.is_some_and(|o| match o {
Operand::Expr(x) => matches!(self.model.expr(x), Some(ExprNode::Op { .. })),
Operand::Obj(p) => self
.model
.get(p)
.is_some_and(|o| matches!(o.kind, ObjKind::Parameter | ObjKind::SpecParam)),
});
if modelled {
return op;
}
for p in named {
if let Some(id) = self.prop_by_uid.get(&p).copied() {
self.model.mark_param_folded(id);
}
}
return None;
}
None
}
fn typespec_rel(&mut self, id: ObjId, ty: Option<Type<'_>>) {
if let Some(ts) = ty.and_then(|t| self.typespec(&t)) {
self.rel(id, header("vpiTypespec"), Operand::Obj(ts));
}
}
fn typespec_type(ty: &Type<'_>) -> i32 {
let name = if ty.is_sequence_type() {
"vpiSequenceTypespec"
} else if ty.is_property_type() {
"vpiPropertyTypespec"
} else if ty.is_unpacked_array() || ty.is_dynamically_sized_array() {
"vpiArrayTypespec"
} else if ty.is_floating() {
match ty.floating_kind() {
FloatKind::ShortReal => "vpiShortRealTypespec",
FloatKind::Real | FloatKind::RealTime => "vpiRealTypespec",
}
} else if ty.is_string() {
"vpiStringTypespec"
} else if ty.is_chandle() {
"vpiChandleTypespec"
} else if ty.is_event() {
"vpiEventTypespec"
} else if ty.is_void() {
"vpiVoidTypespec"
} else if ty.is_enum() {
"vpiEnumTypespec"
} else if ty.is_struct() {
"vpiStructTypespec"
} else if ty.is_union() {
"vpiUnionTypespec"
} else if ty.is_predefined_integer() {
match ty.predefined_integer_kind() {
PredefinedIntegerKind::ShortInt => "vpiShortIntTypespec",
PredefinedIntegerKind::Int => "vpiIntTypespec",
PredefinedIntegerKind::LongInt => "vpiLongIntTypespec",
PredefinedIntegerKind::Byte => "vpiByteTypespec",
PredefinedIntegerKind::Integer => "vpiIntegerTypespec",
PredefinedIntegerKind::Time => "vpiTimeTypespec",
}
} else if Self::packed_array_of_aggregates(ty) {
"vpiPackedArrayTypespec"
} else if ty.is_four_state() {
"vpiLogicTypespec"
} else {
"vpiBitTypespec"
};
header(name)
}
fn is_builtin(ty: &Type<'_>) -> bool {
let c = ty.canonical();
!(c.is_enum()
|| c.is_struct()
|| c.is_union()
|| c.is_unpacked_array()
|| c.is_dynamically_sized_array()
|| c.is_packed_array())
}
fn dims_of(ty: &Type<'_>) -> Vec<(i32, i32)> {
let mut out = Vec::new();
let mut t = ty.canonical();
if t.is_unpacked_array() {
while t.is_unpacked_array() && !t.is_dynamically_sized_array() {
let r = t.fixed_range();
out.push((r.left, r.right));
match t.element_type() {
Some(e) => t = e.canonical(),
None => break,
}
}
return out;
}
while t.is_packed_array() {
let r = t.fixed_range();
out.push((r.left, r.right));
match t.element_type() {
Some(e) => t = e.canonical(),
None => break,
}
}
out
}
fn typespec(&mut self, ty: &Type<'_>) -> Option<ObjId> {
let alias = ty.is_alias();
let key = if alias {
format!("alias:{:?}", ty.as_symbol().uid())
} else {
format!("type:{}", ty.to_sv_string())
};
if let Some(&id) = self.typespecs.get(&key) {
return Some(id);
}
let canon = ty.canonical();
if canon.is_class() {
return self.class_typespec(&canon, &key);
}
if canon.is_virtual_interface() {
let shape = vif_shape(&canon)?;
let name = if alias {
ty.as_symbol().name().to_owned()
} else {
String::new()
};
let iface_key = format!(
"iface:{}#{}",
shape.def,
shape
.params
.iter()
.map(|(n, v)| format!("{n}={v}"))
.collect::<Vec<_>>()
.join(",")
);
let iface = match self.typespecs.get(&iface_key) {
Some(&i) => i,
None => {
let mut o = Obj::new(ObjKind::Typespec, None, "", "");
o.props.stmt_type = header("vpiInterfaceTypespec");
o.props.def_name = Some(shape.def.clone());
let i = self.add(o);
for (pn, pv) in &shape.params {
self.interface_param_assign(i, pn, pv);
}
self.typespecs.insert(iface_key, i);
i
}
};
let id = if let Some(m) = &shape.modport {
let mut obj = Obj::new(ObjKind::Typespec, Some(iface), &name, "");
obj.props.stmt_type = header("vpiInterfaceTypespec");
obj.props.def_name = Some(m.clone());
obj.props.is_modport = true;
self.add(obj)
} else if alias {
let mut obj = Obj::new(ObjKind::Typespec, None, &name, "");
obj.props.stmt_type = header("vpiInterfaceTypespec");
obj.props.def_name = Some(shape.def.clone());
let i = self.add(obj);
for (pn, pv) in &shape.params {
self.interface_param_assign(i, pn, pv);
}
i
} else {
iface
};
self.typespecs.insert(key, id);
return Some(id);
}
if canon.is_error() {
return None;
}
let name = if alias {
ty.as_symbol().name().to_owned()
} else {
String::new()
};
let mut shape = *ty;
let mut target = None;
while shape.is_alias() {
let Some(next) = shape.as_symbol().declared_type() else {
break;
};
if target.is_none() {
target = Some(next);
}
shape = next;
}
let mut obj = Obj::new(ObjKind::Typespec, None, &name, "");
obj.props.stmt_type = Self::typespec_type(&canon);
obj.props.signed = canon.is_signed();
obj.props.vector =
canon.is_packed_array() || (canon.is_integral() && canon.bit_width() > 1);
obj.props.packed = (canon.is_struct() && !canon.is_unpacked_struct())
|| canon.is_packed_union()
|| Self::packed_array_of_aggregates(&canon);
obj.props.tagged = (canon.is_packed_union() && canon.packed_union_is_tagged())
|| (canon.is_unpacked_union() && canon.unpacked_union_is_tagged());
obj.props.soft = canon.is_packed_union() && canon.packed_union_is_soft();
obj.props.dims = Self::dims_of(&canon);
obj.props.range = obj.props.dims.first().copied();
if canon.is_unpacked_array() || canon.is_dynamically_sized_array() {
obj.props.array_type = header(if canon.is_associative_array() {
"vpiAssocArray"
} else if canon.is_queue() {
"vpiQueueArray"
} else if canon.is_dynamically_sized_array() {
"vpiDynamicArray"
} else {
"vpiStaticArray"
});
}
obj.props.file = Some(self.file.clone());
if alias {
obj.props.line = self.locator.line(&ty.as_symbol());
}
let id = self.add(obj);
self.typespecs.insert(key, id);
if let Some(t) = target
&& (t.is_alias() || Self::is_builtin(&t))
&& let Some(ts) = self.typespec(&t)
{
self.rel(id, header("vpiTypedefAlias"), Operand::Obj(ts));
}
let shape_c = shape.canonical();
let unwinds = shape_c.is_unpacked_array()
|| shape_c.is_dynamically_sized_array()
|| shape_c.is_packed_array();
if unwinds
&& let Some(e) = shape.element_type()
&& let Some(ts) = self.typespec(&e)
{
self.rel(id, header("vpiElemTypespec"), Operand::Obj(ts));
}
if canon.is_associative_array()
&& let Some(k) = canon.associative_index_type()
&& let Some(ts) = self.typespec(&k)
{
self.rel(id, header("vpiIndexTypespec"), Operand::Obj(ts));
}
if canon.is_enum() {
if let Some(b) = canon.enum_base()
&& let Some(ts) = self.typespec(&b)
{
self.rel(id, header("vpiBaseTypespec"), Operand::Obj(ts));
}
for m in canon.enum_members() {
let mname = m.name().to_owned();
let mut c = Obj::new(ObjKind::EnumConst, Some(id), &mname, "");
Self::fill_value(&mut c.props, &m.constant_value());
c.props.file = Some(self.file.clone());
c.props.line = self.locator.line(&m);
self.add(c);
}
}
if canon.is_struct() || canon.is_union() {
for f in canon.fields() {
let fname = f.name().to_owned();
let mut m = Obj::new(ObjKind::TypespecMember, Some(id), &fname, "");
m.props.file = Some(self.file.clone());
m.props.line = self.locator.line(&f);
m.props.rand_type = match f.field_rand_mode() {
sv_lang::RandMode::None => 0,
sv_lang::RandMode::Rand => header("vpiRand"),
sv_lang::RandMode::RandC => header("vpiRandC"),
};
let mid = self.add(m);
if let Some(init) = f.initializer()
&& let Some(op) = self.operand(&init)
{
self.rel(mid, consts::VPI_EXPR, op);
}
self.typespec_rel(mid, f.value_type());
}
}
Some(id)
}
fn fill_value(props: &mut Props, cv: &Option<sv_lang::ConstantValue>) {
let obj = props;
obj.param = match cv {
Some(c) if c.as_integer().is_some() => {
let i = c.as_integer().expect("checked");
let width = i.bit_width();
let mut l = Logic::zero(width);
for k in 0..width {
let b = i.bit(k as usize).map_or(B4::Zero, |b| {
if b.is_unknown() {
if b.logic_t_value() == sv_lang::Bit::z().logic_t_value() {
B4::Z
} else {
B4::X
}
} else if b.logic_t_value() == 1 {
B4::One
} else {
B4::Zero
}
});
l.set(k, b);
}
obj.width = width as i32;
obj.signed = i.is_signed();
obj.vector = width > 1;
Some(ParamValue::Logic(l, i.is_signed()))
}
Some(c) if c.as_f64().is_some() => {
obj.width = 64;
Some(ParamValue::Real(c.as_f64().expect("checked")))
}
Some(c) if c.as_str().is_some() => {
let s = c.as_str().expect("checked").to_owned();
obj.width = 8 * s.len() as i32;
Some(ParamValue::Str(s))
}
_ => None,
};
}
fn members(
&mut self,
scope: &Symbol<'_>,
parent: ObjId,
parent_name: &str,
) -> Result<(), VpiError> {
for m in scope.members() {
match m.kind() {
SymbolKind::Instance => {
let name = m.name().to_owned();
let full = format!("{parent_name}.{name}");
self.instance(&m, Some(parent), &name, &full, None, false)?;
}
SymbolKind::InstanceArray if is_primitive_array(&m) => {
let name = m.name().to_owned();
self.primitive_array(&m, parent, parent_name, &name);
}
SymbolKind::InstanceArray => {
let name = m.name().to_owned();
self.instance_array(&m, parent, parent_name, &name, None)?;
}
SymbolKind::GenerateBlock => {
if !m.generate_block_is_uninstantiated() {
let name = m.generate_block_external_name().unwrap_or_default();
self.gen_scope(&m, parent, parent_name, &name, None)?;
}
}
SymbolKind::GenerateBlockArray => {
self.gen_scope_array(&m, parent, parent_name)?;
}
SymbolKind::Modport => {
let name = m.name().to_owned();
let full = format!("{parent_name}.{name}");
let mut obj = Obj::new(ObjKind::Modport, Some(parent), &name, &full);
obj.props.file = Some(self.file.clone());
obj.props.line = self.locator.line(&m);
let mp_id = self.add(obj);
for port in m.members() {
if port.kind() == SymbolKind::MethodPrototype {
let pname = port.name().to_owned();
let pfull = format!("{full}.{pname}");
let mut io = Obj::new(ObjKind::IODecl, Some(mp_id), &pname, &pfull);
io.props.direction = consts::VPI_UNDEFINED;
io.props.file = Some(self.file.clone());
io.props.line = self.locator.line(&port);
let io_id = self.add(io);
self.itf_tf_links.push((
mp_id,
io_id,
format!("{parent_name}.{pname}"),
));
continue;
}
if port.kind() != SymbolKind::ModportPort {
continue;
}
let pname = port.name().to_owned();
let pfull = format!("{full}.{pname}");
let mut io = Obj::new(ObjKind::IODecl, Some(mp_id), &pname, &pfull);
io.props.direction = match port.modport_port_direction() {
ArgumentDirection::In => consts::VPI_INPUT,
ArgumentDirection::Out => consts::VPI_OUTPUT,
ArgumentDirection::InOut => consts::VPI_INOUT,
ArgumentDirection::Ref => consts::VPI_REF,
};
let internal = port.modport_port_internal_symbol();
if let Some(ty) = internal.as_ref().and_then(|s| s.value_type()) {
io.props.width = ty.bit_width() as i32;
io.props.signed = ty.is_signed();
}
io.props.file = Some(self.file.clone());
io.props.line = self.locator.line(&port);
let io_id = self.add(io);
if internal.is_some() {
self.path_links.push((
io_id,
consts::VPI_EXPR,
format!("{parent_name}.{pname}"),
));
}
}
}
SymbolKind::Variable => self.data(&m, parent, parent_name, false),
SymbolKind::Net => {
self.data(&m, parent, parent_name, true);
if let Some(init) = m.initializer() {
self.net_decl_assign(&m, &init, parent, parent_name);
}
}
SymbolKind::Parameter => self.parameter(&m, parent, parent_name),
SymbolKind::ClassType => {
self.class_defn(&m, Some(&m), Some((parent, parent_name)));
}
SymbolKind::GenericClassDef => {
self.class_defn(&m, None, Some((parent, parent_name)));
}
SymbolKind::TypeAlias => {
if let Some(ts) = m.as_type().and_then(|t| self.typespec(&t)) {
self.rel(parent, header("vpiTypedef"), Operand::Obj(ts));
}
}
SymbolKind::ProceduralBlock => {
self.assertions(&m, parent, parent_name);
if vj_codegen::module::static_concurrent_assertion_of(&m).is_none()
&& vj_codegen::module::static_deferred_assertion_of(&m).is_none()
{
self.process(&m, parent, parent_name);
}
}
SymbolKind::Subroutine => self.task_func(&m, parent, parent_name, "."),
SymbolKind::MethodPrototype
if vj_sim::netlist::itf::extern_prototype(&m).is_some() =>
{
self.interface_tf_decl(&m, parent, parent_name);
}
SymbolKind::ClockingBlock => self.clocking(&m, parent, parent_name),
SymbolKind::PrimitiveInstance => {
let name = m.name().to_owned();
let full = format!("{parent_name}.{name}");
self.primitive(&m, parent, &name, &full, None, &m, &m);
}
SymbolKind::ContinuousAssign => self.cont_assign(&m, parent),
SymbolKind::LetDecl => self.let_decl(&m, parent, parent_name),
SymbolKind::SpecifyBlock => {
let ifnone_ends: Vec<String> = m
.members()
.filter(|p| p.kind() == SymbolKind::TimingPath && self.is_ifnone(p))
.map(|p| self.path_ends(&p))
.collect();
for p in m.members() {
match p.kind() {
SymbolKind::TimingPath => {
let has =
self.is_ifnone(&p) || ifnone_ends.contains(&self.path_ends(&p));
self.mod_path(&p, parent, has);
}
SymbolKind::SystemTimingCheck => self.tchk(&p, parent),
SymbolKind::Specparam => self.spec_param(&p, parent, parent_name),
_ => {}
}
}
}
SymbolKind::Specparam => self.spec_param(&m, parent, parent_name),
SymbolKind::Property | SymbolKind::Sequence => {
self.assertion_decl(&m, parent, parent_name);
}
SymbolKind::DefParam => {
let mut o = Obj::new(ObjKind::DefParam, Some(parent), "", "");
o.props.file = Some(self.file.clone());
o.props.line = self.locator.line(&m);
let id = self.add(o);
if let Some(t) = m.defparam_target() {
self.defparam_links.push((id, vec![t.hierarchical_path()]));
}
}
SymbolKind::NetType if !m.net_type_is_built_in() => {
self.nettype_decl(&m, parent, parent_name);
}
SymbolKind::NetAlias => {
let nets: Vec<sv_lang::Expression<'_>> = m.net_alias_net_references().collect();
if let Some((rhs, lhss)) = nets.split_last() {
for lhs in lhss {
let mut o = Obj::new(ObjKind::AliasStmt, Some(parent), "", "");
o.props.file = Some(self.file.clone());
o.props.line = self.locator.line(&m);
let id = self.add(o);
if let Some(op) = self.operand(lhs) {
self.rel(id, consts::VPI_LHS, op);
}
if let Some(op) = self.operand(rhs) {
self.rel(id, consts::VPI_RHS, op);
}
}
}
}
SymbolKind::TypeParameter => {
let name = m.name().to_owned();
let full = format!("{parent_name}.{name}");
let mut obj = Obj::new(ObjKind::TypeParameter, Some(parent), &name, &full);
obj.props.local_param = m.parameter_is_local_param();
obj.props.file = Some(self.file.clone());
obj.props.line = self.locator.line(&m);
let id = self.add(obj);
self.typespec_rel(id, m.declared_type());
self.type_parameter_default(&m, id);
}
SymbolKind::Genvar => {
let name = m.name().to_owned();
let full = format!("{parent_name}.{name}");
let mut obj = Obj::new(ObjKind::GenVar, Some(parent), &name, &full);
obj.props.file = Some(self.file.clone());
obj.props.line = self.locator.line(&m);
self.add(obj);
}
_ => {}
}
}
for imp in vj_sim::netlist::itf::extern_impls_in(scope) {
self.task_func(&imp, parent, parent_name, ".");
}
Ok(())
}
fn instance_array(
&mut self,
array: &Symbol<'_>,
parent: ObjId,
parent_name: &str,
name: &str,
index: Option<i32>,
) -> Result<(), VpiError> {
let full = format!("{parent_name}.{name}");
self.instance_array_at(array, parent, &full, name, index)
}
fn instance_array_at(
&mut self,
array: &Symbol<'_>,
parent: ObjId,
full: &str,
name: &str,
index: Option<i32>,
) -> Result<(), VpiError> {
let range = array.instance_array_range();
let elements: Vec<Symbol<'_>> = array.instance_array_elements().collect();
let kind = match Self::array_kind(array) {
Some(k) => k,
None => ObjKind::ModuleArray,
};
let mut obj = Obj::new(kind, Some(parent), name, full);
obj.props.index = index;
obj.props.size = Some(elements.len() as i32);
obj.props.range = Some((range.left, range.right));
obj.props.file = Some(self.file.clone());
obj.props.line = self.locator.line(array);
let id = self.add(obj);
for (i, element) in elements.iter().enumerate() {
let idx = element_index(range, i);
let element_name = format!("{name}[{idx}]");
let element_full = format!("{full}[{idx}]");
match element.kind() {
SymbolKind::InstanceArray => {
self.instance_array_at(element, id, &element_full, &element_name, Some(idx))?;
}
SymbolKind::Instance => {
self.instance(
element,
Some(id),
&element_name,
&element_full,
Some(idx),
false,
)?;
}
_ => {}
}
}
self.instance_array_expr(id);
Ok(())
}
fn instance_array_expr(&mut self, array: ObjId) {
let mut at = array;
let first = loop {
let Some(&c) = self
.model
.children_where(at, |k| k.is_instance() || k.is_instance_array())
.first()
else {
return;
};
if self
.model
.get(c)
.is_some_and(|o| o.kind.is_instance_array())
{
at = c;
} else {
break c;
}
};
let ops: Vec<Operand> = self
.model
.children_of_kind(first, ObjKind::Port)
.into_iter()
.filter_map(|p| {
self.model
.get(p)?
.props
.rels
.iter()
.find(|(k, _)| *k == consts::VPI_HIGH_CONN)
.map(|(_, o)| *o)
})
.collect();
if ops.is_empty() {
return;
}
let op = self.op_node("vpiListOp", ops, false);
if let Some(p) = self.model.props_mut(array) {
p.rels.push((consts::VPI_EXPR, op));
}
}
fn array_kind(array: &Symbol<'_>) -> Option<ObjKind> {
for element in array.instance_array_elements() {
match element.kind() {
SymbolKind::Instance => {
return element
.instance_definition()
.map(|d| match Self::instance_kind(&d) {
ObjKind::Interface => ObjKind::InterfaceArray,
ObjKind::Program => ObjKind::ProgramArray,
_ => ObjKind::ModuleArray,
});
}
SymbolKind::InstanceArray => return Self::array_kind(&element),
_ => {}
}
}
None
}
fn gen_scope(
&mut self,
block: &Symbol<'_>,
parent: ObjId,
parent_name: &str,
name: &str,
index: Option<i32>,
) -> Result<(), VpiError> {
let full = format!("{parent_name}.{name}");
let mut obj = Obj::new(ObjKind::GenScope, Some(parent), name, &full);
obj.props.index = index;
obj.props.implicit = index.is_none() && !self.has_block_name(block);
obj.props.file = Some(self.file.clone());
obj.props.line = self.locator.line(block);
let id = self.add(obj);
self.members(block, id, &full)
}
fn has_block_name(&self, block: &Symbol<'_>) -> bool {
use sv_lang::kinds::SyntaxKind;
self.locator.syntax_trees().iter().any(|t| {
block.syntax(t).is_some_and(|n| {
n.kind() == SyntaxKind::GenerateBlock
&& n.children()
.any(|c| c.kind() == SyntaxKind::NamedBlockClause)
})
})
}
fn gen_scope_array(
&mut self,
array: &Symbol<'_>,
parent: ObjId,
parent_name: &str,
) -> Result<(), VpiError> {
let name = array.generate_block_external_name().unwrap_or_default();
let full = format!("{parent_name}.{name}");
let entries: Vec<Symbol<'_>> = if array.generate_block_array_valid() {
array.generate_block_array_entries().collect()
} else {
Vec::new()
};
let mut obj = Obj::new(ObjKind::GenScopeArray, Some(parent), &name, &full);
obj.props.size = Some(entries.len() as i32);
obj.props.file = Some(self.file.clone());
obj.props.line = self.locator.line(array);
let id = self.add(obj);
for (i, entry) in entries.iter().enumerate() {
let idx = entry
.generate_block_array_index()
.and_then(|v| v.as_i64())
.map_or(i as i32, |v| v as i32);
let entry_name = format!("{name}[{idx}]");
self.gen_scope(entry, id, &full_of(parent_name), &entry_name, Some(idx))?;
}
Ok(())
}
fn assertions(&mut self, block: &Symbol<'_>, parent: ObjId, parent_name: &str) {
let Some(body) = vj_sim::process::cfg::proc_body(block) else {
return;
};
let mut stack = vec![(body.as_sem_node(), parent_name.to_owned(), None::<String>)];
while let Some((node, prefix, label)) = stack.pop() {
let Some(stmt) = node.as_statement() else {
for c in vj_sim::process::cfg::live_children(&node).into_iter().rev() {
stack.push((c, prefix.clone(), None));
}
continue;
};
if stmt.kind() == StatementKind::ConcurrentAssertion
&& stmt.assertion_kind() == Some(AssertionKind::Expect)
{
let mut obj = Obj::new(ObjKind::Stmt, Some(parent), "", "");
obj.props.stmt_type = header("vpiExpectStmt");
obj.props.file = Some(self.file.clone());
obj.props.line = self.locator.stmt_line(&stmt);
let id = self.add(obj);
self.assertion_ids
.insert(stmt.source_range().start.offset, id);
self.attributes(stmt.attributes(), id, "", false);
self.assertion_structure(&stmt, id, &prefix, block);
continue;
}
if let Some((kind, deferred, final_)) = assertion_kind(&stmt) {
let (name, full) = match label {
Some(n) => (n.clone(), format!("{prefix}.{n}")),
None => (String::new(), String::new()),
};
let mut obj = Obj::new(kind, Some(parent), &name, &full);
obj.props.def_name = self.def_name.clone();
obj.props.deferred = deferred;
obj.props.final_ = final_;
obj.props.file = Some(self.file.clone());
obj.props.line = self.locator.stmt_line(&stmt);
let id = self.add(obj);
self.assertion_ids
.insert(stmt.source_range().start.offset, id);
if let Some(p) = self.model.props_mut(id) {
p.src_offset = Some(stmt.source_range().start.offset);
}
self.attributes(stmt.attributes(), id, &full, false);
self.assertion_structure(&stmt, id, &prefix, block);
continue;
}
let children = node.children();
let named = (stmt.kind() == StatementKind::Block)
.then(|| stmt.block_symbol())
.flatten()
.map(|s| s.name().to_owned())
.filter(|n| !n.is_empty());
match named {
Some(n) => {
let only_assertion = children.len() == 1
&& children[0]
.as_statement()
.is_some_and(|c| assertion_kind(&c).is_some());
if only_assertion {
stack.push((children[0], prefix, Some(n)));
} else {
let inner = format!("{prefix}.{n}");
for c in children.into_iter().rev() {
stack.push((c, inner.clone(), None));
}
}
}
None => {
for c in children.into_iter().rev() {
stack.push((c, prefix.clone(), None));
}
}
}
}
}
fn clocking(&mut self, block: &Symbol<'_>, parent: ObjId, parent_name: &str) {
let name = block.name().to_owned();
let full = format!("{parent_name}.{name}");
let mut obj = Obj::new(ObjKind::ClockingBlock, Some(parent), &name, &full);
let keyword = clocking_keyword(block, &self.locator.syntax_trees());
obj.props.default_clocking = keyword == Some(sv_lang::kinds::TokenKind::DefaultKeyword);
obj.props.global_clocking = keyword == Some(sv_lang::kinds::TokenKind::GlobalKeyword);
let (din, dout) = (
block.clocking_block_default_input_skew(),
block.clocking_block_default_output_skew(),
);
obj.props.input_edge = edge_const(din.edge);
obj.props.output_edge = edge_const(dout.edge);
obj.props.file = Some(self.file.clone());
obj.props.line = self.locator.line(block);
let line = obj.props.line;
let id = self.add(obj);
self.attributes(block.attributes(), id, &full, false);
self.skew_rel(id, "vpiInputSkew", &din, line);
self.skew_rel(id, "vpiOutputSkew", &dout, line);
if let Some(tc) = block
.clocking_block_event()
.and_then(|n| n.as_timing_control())
&& let Some(ev) = self.timing_control_obj(&tc, id, line)
{
self.rel(id, header("vpiClockingEvent"), Operand::Obj(ev));
}
for v in block.members() {
if v.kind() != SymbolKind::ClockVar {
continue;
}
let vname = v.name().to_owned();
let mut io = Obj::new(
ObjKind::ClockingIODecl,
Some(id),
&vname,
&format!("{full}.{vname}"),
);
io.props.direction = match v.clock_var_direction() {
ArgumentDirection::In => consts::VPI_INPUT,
ArgumentDirection::Out => consts::VPI_OUTPUT,
ArgumentDirection::InOut => consts::VPI_INOUT,
ArgumentDirection::Ref => consts::VPI_REF,
};
let (vin, vout) = (v.clock_var_input_skew(), v.clock_var_output_skew());
io.props.input_edge = edge_const(if vin.has_value() { vin.edge } else { din.edge });
io.props.output_edge = edge_const(if vout.has_value() {
vout.edge
} else {
dout.edge
});
io.props.file = Some(self.file.clone());
let line = self.locator.line(&v);
io.props.line = line;
let io_id = self.add(io);
self.skew_rel(io_id, "vpiInputSkew", &vin, line);
self.skew_rel(io_id, "vpiOutputSkew", &vout, line);
if let Some(init) = v.initializer()
&& let Some(operand) = self.operand(&init)
{
self.rel(io_id, consts::VPI_EXPR, operand);
}
}
}
fn skew_rel(&mut self, id: ObjId, rel: &str, skew: &sv_lang::ClockingSkew<'_>, line: i32) {
if let Some(tc) = skew.delay.and_then(|d| d.as_timing_control())
&& let Some(obj) = self.timing_control_obj(&tc, id, line)
{
self.rel(id, header(rel), Operand::Obj(obj));
}
}
fn link(&mut self, id: ObjId, side: u8, e: &sv_lang::Expression<'_>) {
let plain = e.kind() == sv_lang::kinds::ExpressionKind::NamedValue;
if let (true, Some(sym), Some(body)) = (plain, e.referenced_symbol(), self.body.clone()) {
self.links.push((id, side, body, sym.uid()));
}
}
fn primitive_delays(&self, inst: &Symbol<'_>) -> Option<[u64; 3]> {
let Some(d) = inst.primitive_instance_delay() else {
return Some([0; 3]);
};
let tc = d.as_timing_control()?;
let list = crate::primitives::delay_values_of(
self.eval,
tc.children().into_iter().filter_map(|n| n.as_expression()),
inst,
)
.ok()?;
match list[..] {
[d] => Some([d, d, d]),
[r, f] => Some([r, f, r.min(f)]),
[r, f, z] => Some([r, f, z]),
_ => None,
}
}
fn primitive_delay_operand(&mut self, inst: &Symbol<'_>) -> Option<Operand> {
let tc = inst.primitive_instance_delay()?.as_timing_control()?;
let exprs: Vec<sv_lang::Expression<'_>> = tc
.children()
.into_iter()
.filter_map(|n| n.as_expression())
.collect();
let ops: Vec<Operand> = exprs.iter().filter_map(|e| self.operand(e)).collect();
match ops[..] {
[] => None,
[one] => Some(one),
_ => Some(self.op_node("vpiListOp", ops, false)),
}
}
fn assign_delay(&mut self, delay: Option<sv_lang::SemNode<'_>>, scope: &Symbol<'_>, id: ObjId) {
let Some(tc) = delay.and_then(|d| d.as_timing_control()) else {
if let Some(p) = self.model.props_mut(id) {
p.prim_delays = Some([0; 3]);
}
return;
};
let exprs: Vec<sv_lang::Expression<'_>> = tc
.children()
.into_iter()
.filter_map(|n| n.as_expression())
.collect();
let list = crate::primitives::delay_values_of(self.eval, exprs.iter().copied(), scope).ok();
let ticks = match list.as_deref() {
Some([d]) => Some([*d, *d, *d]),
Some([r, f]) => Some([*r, *f, (*r).min(*f)]),
Some([r, f, z]) => Some([*r, *f, *z]),
_ => None,
};
let ops: Vec<Operand> = exprs.iter().filter_map(|e| self.operand(e)).collect();
let op = match ops[..] {
[] => None,
[one] => Some(one),
_ => Some(self.op_node("vpiListOp", ops, false)),
};
if let Some(p) = self.model.props_mut(id) {
p.prim_delays = ticks;
p.delay_op = op;
}
}
#[allow(clippy::too_many_arguments)]
fn primitive(
&mut self,
inst: &Symbol<'_>,
parent: ObjId,
name: &str,
full: &str,
index: Option<i32>,
at: &Symbol<'_>,
conn_src: &Symbol<'_>,
) {
let trees = self.locator.syntax_trees();
let def = crate::primitives::type_name(inst, &trees).unwrap_or_default();
let conns: Vec<sv_lang::Expression<'_>> =
conn_src.primitive_instance_port_connections().collect();
let (kind, prim_type, outs, inouts) = prim_class(&def, conns.len(), inst);
let mut obj = Obj::new(kind, Some(parent), name, full);
obj.props.def_name = Some(def.clone());
obj.props.prim_type = prim_type;
obj.props.size = Some(conns.len() as i32);
obj.props.index = index;
let given = inst.primitive_instance_drive_strength();
let weak_default = def.starts_with('r') || def == "pullup" || def == "pulldown";
obj.props.strength0 = strength_const(given.strength0, weak_default);
obj.props.strength1 = strength_const(given.strength1, weak_default);
obj.props.prim_delays = self.primitive_delays(inst);
obj.props.delay_op = self.primitive_delay_operand(inst);
obj.props.file = Some(self.file.clone());
obj.props.line = self.locator.line(at);
let id = self.add(obj);
self.attributes(inst.attributes(), id, full, false);
if kind == ObjKind::Udp
&& let Some(d) = udp_definition(inst, &def)
&& let Some(defn) = self.udp_defn(&d)
{
self.rel(id, header("vpiUdpDefn"), Operand::Obj(defn));
}
for (i, c) in conns.iter().enumerate() {
let e = if c.kind() == sv_lang::kinds::ExpressionKind::Assignment {
c.left().unwrap_or(*c)
} else {
*c
};
let op = self.operand(&e);
let mut t = Obj::new(ObjKind::PrimTerm, Some(id), "", "");
t.props.conn_op = op;
t.props.term_index = i as i32;
t.props.direction = if i < inouts {
consts::VPI_INOUT
} else if i < inouts + outs {
consts::VPI_OUTPUT
} else {
consts::VPI_INPUT
};
t.props.file = Some(self.file.clone());
t.props.line = self.locator.line(at);
let tid = self.add(t);
let e = if c.kind() == sv_lang::kinds::ExpressionKind::Assignment {
c.left().unwrap_or(*c)
} else {
*c
};
self.link(tid, 0, &e);
}
}
fn udp_defn(&mut self, d: &Symbol<'_>) -> Option<ObjId> {
let key = format!("udp:{:?}", d.uid());
if let Some(&id) = self.typespecs.get(&key) {
return Some(id);
}
let name = d.name().to_owned();
let ports: Vec<Symbol<'_>> = d.primitive_ports().collect();
let seq = d.primitive_is_sequential();
let mut obj = Obj::new(ObjKind::UdpDefn, None, &name, "");
obj.props.def_name = Some(name.clone());
obj.props.prim_type = header(if seq { "vpiSeqPrim" } else { "vpiCombPrim" });
obj.props.size = Some(ports.len().saturating_sub(1) as i32);
obj.props.file = Some(self.file.clone());
obj.props.line = self.locator.line(d);
let id = self.add(obj);
self.typespecs.insert(key, id);
let mut out_decl = None;
for p in &ports {
let pname = p.name().to_owned();
let mut io = Obj::new(ObjKind::IODecl, Some(id), &pname, "");
io.props.direction = match p.primitive_port_direction() {
sv_lang::PrimitivePortDirection::In => consts::VPI_INPUT,
_ => consts::VPI_OUTPUT,
};
io.props.width = 1;
io.props.file = Some(self.file.clone());
io.props.line = self.locator.line(p);
let io_id = self.add(io);
if out_decl.is_none() {
out_decl = Some(io_id);
}
}
for (inputs, output, state) in d.primitive_table() {
let text = if state.is_empty() {
format!("{inputs}:{output}")
} else {
format!("{inputs}:{state}:{output}")
};
let mut symbols = 0;
let mut in_edge = false;
for c in inputs.chars() {
match c {
'(' => in_edge = true,
')' => {
in_edge = false;
symbols += 1;
}
' ' | '\t' => {}
_ if in_edge => {}
_ => symbols += 1,
}
}
symbols += 1 + usize::from(!state.is_empty());
let mut e = Obj::new(ObjKind::TableEntry, Some(id), "", "");
e.props.size = Some(symbols as i32);
e.props.param = Some(ParamValue::Str(text));
e.props.file = Some(self.file.clone());
self.add(e);
}
if let Some(v) = d.primitive_init_val()
&& let Some(out) = out_decl
&& let ExprNode::Const {
value,
signed,
const_type,
} = const_node(&v, sv_lang::kinds::ExpressionKind::IntegerLiteral)
{
let rhs = self.model.add_expr(ExprNode::Const {
value,
signed,
const_type,
});
let mut st = Obj::new(ObjKind::Stmt, None, "", "");
st.props.stmt_type = header("vpiAssignment");
st.props.blocking = true;
st.props.rels.push((consts::VPI_LHS, Operand::Obj(out)));
st.props.rels.push((consts::VPI_RHS, Operand::Expr(rhs)));
let st_id = self.add(st);
let mut init = Obj::new(ObjKind::Process, None, "", "");
init.props.stmt_type = header("vpiInitial");
init.props
.rels
.push((consts::VPI_STMT, Operand::Obj(st_id)));
let init_id = self.add(init);
self.rel(id, header("vpiInitial"), Operand::Obj(init_id));
}
Some(id)
}
fn operand(&mut self, e: &sv_lang::Expression<'_>) -> Option<Operand> {
self.operand_with_convs(e).0
}
fn conv_of(e: &sv_lang::Expression<'_>) -> Option<crate::vpi::eval::Conv> {
let t = e.expr_type()?.canonical();
Some(crate::vpi::eval::Conv {
width: t.bit_width() as u32,
signed: t.is_signed(),
real: t.is_floating(),
shortreal: t.is_floating() && t.floating_kind() == FloatKind::ShortReal,
})
}
fn operand_with_convs(
&mut self,
e: &sv_lang::Expression<'_>,
) -> (Option<Operand>, Vec<crate::vpi::eval::Conv>) {
let outer = self.op_depth == 0 && !self.let_decls.is_empty();
if outer {
self.let_sites = match self.let_hint.take() {
Some(h) => h,
None => {
let own = self.let_sites_of_expr(e);
if own.is_empty() {
self.stmt_sites.clone()
} else {
own
}
}
};
}
self.op_depth += 1;
let r = self.operand_with_convs_inner(e);
self.op_depth -= 1;
if outer {
self.let_sites.clear();
}
r
}
fn operand_with_convs_inner(
&mut self,
e: &sv_lang::Expression<'_>,
) -> (Option<Operand>, Vec<crate::vpi::eval::Conv>) {
use sv_lang::kinds::ExpressionKind as K;
let mut e = unwrap_invalid_expr(*e);
let mut convs = Vec::new();
while e.kind() == K::Conversion && e.conversion_is_implicit() != Some(false) {
if let Some(c) = Self::conv_of(&e) {
convs.push(c);
}
match e.conversion_operand() {
Some(inner) => e = inner,
None => return (None, Vec::new()),
}
}
convs.reverse();
if e.kind() == K::Invalid
&& let Some(f) = self.placeholder_formal(&e)
{
return (Some(Operand::Obj(f)), convs);
}
(self.operand_inner(&e), convs)
}
fn placeholder_formal(&self, e: &sv_lang::Expression<'_>) -> Option<ObjId> {
let text = self
.locator
.syntax_trees()
.iter()
.find_map(|t| e.syntax(t).map(|n| n.text().trim().to_owned()))?;
self.formal_names
.iter()
.find(|(n, _)| *n == text)
.map(|(_, f)| *f)
}
fn operand_inner(&mut self, e: &sv_lang::Expression<'_>) -> Option<Operand> {
use sv_lang::kinds::ExpressionKind as K;
let e = *e;
if let Some(op) = self.let_root(&e) {
return Some(op);
}
if self.formals_by_text
&& let Some(f) = self.placeholder_formal(&e)
{
return Some(Operand::Obj(f));
}
if e.kind() == K::NamedValue
&& let Some(&f) = e
.referenced_symbol()
.and_then(|s| self.formal_by_uid.get(&s.uid()))
{
return Some(Operand::Obj(f));
}
if e.kind() == K::NamedValue
&& let Some(sym) = e.referenced_symbol()
&& matches!(
sym.kind(),
SymbolKind::LocalAssertionVar | SymbolKind::AssertionPort
)
&& let Some((_, f)) = self.formal_names.iter().find(|(n, _)| n == sym.name())
{
return Some(Operand::Obj(*f));
}
if e.kind() == K::NamedValue
&& let Some(sym) = e.referenced_symbol()
&& sym.kind() == SymbolKind::Iterator
&& let Some(&(_, v)) = self.loop_vars.iter().rev().find(|(n, _)| n == sym.name())
{
return Some(Operand::Obj(v));
}
if e.kind() == K::NamedValue
|| (e.kind() == K::HierarchicalValue
&& e.referenced_symbol()
.is_some_and(|s| s.kind() == SymbolKind::Parameter))
{
let holder = self.model.add_expr(ExprNode::Op {
op_type: 0,
width: 0,
operands: vec![Operand::Expr(u32::MAX)],
meta: OpMeta::default(),
});
if let (Some(sym), Some(body)) =
(e.referenced_symbol(), self.link_body(e.referenced_symbol()))
{
self.expr_links.push((holder, 0, body, sym.uid()));
}
self.pending_plain.push(holder);
if e.referenced_symbol().is_some_and(|s| {
s.kind() == SymbolKind::FormalArgument
|| (s.kind() == SymbolKind::Variable
&& s.variable_lifetime() == VariableLifetime::Automatic)
}) {
self.pending_local.insert(holder);
}
return Some(Operand::Expr(holder));
}
Some(Operand::Expr(self.expr(&e)))
}
fn link_body(&self, sym: Option<Symbol<'_>>) -> Option<Option<SymbolId>> {
match self.body.clone() {
Some(b) => Some(Some(b)),
None => sym
.filter(|s| self.tf_ref_by_uid.contains_key(&s.uid()))
.map(|_| None),
}
}
fn expr(&mut self, e: &sv_lang::Expression<'_>) -> ExprId {
use sv_lang::kinds::ExpressionKind as K;
let width = e.expr_type().map_or(0, |t| t.bit_width() as i32);
let constant = |x: Option<sv_lang::Expression<'_>>| {
x.and_then(|x| x.constant_value())
.and_then(|c| c.as_i64())
.map(|v| v as i32)
};
let node = match e.kind() {
K::IntegerLiteral
| K::UnbasedUnsizedIntegerLiteral
| K::RealLiteral
| K::StringLiteral => match e.constant_value().or_else(|| match e.kind() {
K::IntegerLiteral => e.integer_literal_value(),
K::UnbasedUnsizedIntegerLiteral => e.unbased_unsized_literal_value(),
_ => None,
}) {
Some(c) => const_node(&c, e.kind()),
None => ExprNode::Unsupported("a literal with no value".into()),
},
K::ElementSelect
if e.select_value().is_some_and(|mut b| {
while b.kind() == K::ElementSelect {
match b.select_value() {
Some(inner) => b = inner,
None => return false,
}
}
b.kind() == K::MemberAccess
&& b.expr_type()
.is_some_and(|t| t.canonical().is_unpacked_array())
}) =>
{
return self.member_element_select(e);
}
K::ElementSelect | K::RangeSelect
if e.select_value().is_some_and(|b| b.kind() != K::NamedValue) =>
{
return self.expr_select(e, width);
}
K::ElementSelect | K::RangeSelect
if e.select_value()
.and_then(|b| b.expr_type())
.is_some_and(|t| t.canonical().is_unpacked_array())
|| (e.kind() == K::ElementSelect
&& e.selector().and_then(|s| s.constant_value()).is_none())
|| (e.kind() == K::RangeSelect && e.range_selection_kind() != Some(0)) =>
{
return self.dynamic_select(e, width);
}
K::MemberAccess => {
let mut path = vec![
e.member_symbol()
.map(|m| m.name().to_owned())
.unwrap_or_default(),
];
let mut base = e.operand();
let mut chain = Vec::new();
while let Some(b) = base.filter(|b| b.kind() == K::MemberAccess) {
chain.push(
b.expr_type()
.map(|t| (Self::var_kind(&t), t.bit_width() as i32)),
);
path.push(
b.member_symbol()
.map(|m| m.name().to_owned())
.unwrap_or_default(),
);
base = b.operand();
}
path.reverse();
if path.len() == 1
&& let Some(b) = base.filter(|b| b.kind() == K::NamedValue)
&& let Some(def) = b
.expr_type()
.filter(|t| t.canonical().is_virtual_interface())
.and_then(|t| vif_shape(&t.canonical()))
.map(|v| v.def)
&& let Some(full) = e.member_symbol().map(|m| m.hierarchical_path())
{
let comps: Vec<&str> = full.split('.').collect();
if let Some(at) = comps.iter().position(|c| *c == def)
&& at + 2 < comps.len()
{
path = vec![comps[at + 1..].join(".")];
}
}
match base.filter(|b| b.kind() == K::NamedValue) {
Some(b) => {
let id = self.model.add_expr(ExprNode::Member {
base: u32::MAX,
path: path.join("."),
});
if let (Some(sym), Some(body)) =
(b.referenced_symbol(), self.link_body(b.referenced_symbol()))
{
self.expr_links.push((id, -1, body, sym.uid()));
}
self.pending_members.push(id);
chain.reverse();
if let Some(c) = chain.into_iter().collect::<Option<Vec<_>>>() {
self.member_chain.insert(id, c);
}
if let Some(t) = e.expr_type() {
self.member_types.insert(id, (Self::var_kind(&t), width));
}
return id;
}
None => ExprNode::Unsupported("a member select of a non-signal".into()),
}
}
K::Conversion => {
let (inner, mut convs) = match e.conversion_operand() {
Some(i) => self.operand_with_convs(&i),
None => (None, Vec::new()),
};
convs.extend(Self::conv_of(e));
let ty = e.expr_type();
let meta = OpMeta {
signed: ty.is_some_and(|t| t.is_signed()),
real: ty.is_some_and(|t| t.is_floating()),
convs: vec![convs],
typespec: ty.and_then(|t| self.typespec(&t)),
strong: false,
soft: false,
slice: 0,
..OpMeta::default()
};
ExprNode::Op {
op_type: header("vpiCastOp"),
width,
operands: inner.into_iter().collect(),
meta,
}
}
K::ValueRange => {
let bounds: Vec<sv_lang::Expression<'_>> = e
.as_sem_node()
.children()
.into_iter()
.filter_map(|n| n.as_expression())
.collect();
match (
bounds.first().and_then(|l| self.operand(l)),
bounds.get(1).and_then(|r| self.operand(r)),
) {
(Some(left), Some(right)) => ExprNode::ValueRange { left, right },
_ => ExprNode::Unsupported("a value range without both bounds".into()),
}
}
K::Inside => {
let mut ops: Vec<Operand> = e
.inside_left()
.and_then(|l| self.operand(&l))
.into_iter()
.collect();
ops.extend(e.inside_range_list().iter().filter_map(|x| self.operand(x)));
ExprNode::Op {
op_type: header("vpiInsideOp"),
width: 1,
operands: ops,
meta: OpMeta::default(),
}
}
K::NewClass => {
if let Some(c) = e.new_class_constructor_call() {
let id = self.expr(&c);
if let Some(ExprNode::Call {
method,
name,
prefix,
..
}) = self.model.expr_mut(id)
{
*method = true;
*prefix = None;
"new".clone_into(name);
}
return id;
}
ExprNode::Call {
func: None,
name: "new".to_owned(),
sys: false,
args: Vec::new(),
width,
prefix: None,
method: true,
with: None,
folded: None,
}
}
K::NewCovergroup => {
let args: Vec<Operand> = e
.new_covergroup_arguments()
.iter()
.filter_map(|a| self.operand(a))
.collect();
ExprNode::Call {
func: None,
name: "new".to_owned(),
sys: false,
args,
width,
prefix: None,
method: true,
with: None,
folded: None,
}
}
K::Call => {
let sys = e.system_subroutine();
let name = match &sys {
Some(m) => m.name().to_owned(),
None => e
.call_subroutine()
.map(|s| s.name().to_owned())
.unwrap_or_default(),
};
let callee = e.call_subroutine();
let func = callee.and_then(|s| self.model.find(&s.hierarchical_path()));
let this = e.call_this_class();
let operands = e.operands();
let builtin = sys.as_ref().is_some_and(|m| !m.name().starts_with('$'))
|| (this.is_some() && receiver_listed(e));
let prefix = match this {
Some(t) => self.operand(&t),
None if builtin => operands.first().and_then(|a| self.operand(a)),
None => None,
};
let args: Vec<Operand> = operands
.iter()
.skip(usize::from(builtin))
.filter_map(|a| self.operand(a))
.collect();
let folded = (sys.is_none() && !builtin && this.is_none() && callee.is_some())
.then(|| self.eval.eval_constant(*e))
.flatten()
.map(|c| const_node(&c, K::Call))
.filter(|n| !matches!(n, ExprNode::Unsupported(_)))
.map(|n| Operand::Expr(self.model.add_expr(n)));
let with = self.call_with(e);
let node = ExprNode::Call {
func,
name,
sys: sys.is_some() && !builtin,
args,
width,
prefix,
method: builtin || this.is_some(),
with,
folded,
};
if func.is_none()
&& let Some(s) = callee
{
let id = self.model.add_expr(node);
self.tf_call_links.push((id, s.uid()));
return id;
}
node
}
K::SimpleAssignmentPattern
| K::StructuredAssignmentPattern
| K::ReplicatedAssignmentPattern => {
let mut ops = Vec::new();
let mut convs = Vec::new();
let multi = e.kind() == K::ReplicatedAssignmentPattern;
let mut elems: Vec<_> = e.replicated_pattern_count().into_iter().collect();
elems.extend(e.pattern_elements());
for p in elems {
let (o, c) = self.operand_with_convs(&p);
if let Some(o) = o {
ops.push(o);
convs.push(c);
}
}
let ty = e.expr_type();
ExprNode::Op {
op_type: header(if multi {
"vpiMultiAssignmentPatternOp"
} else {
"vpiAssignmentPatternOp"
}),
width,
operands: ops,
meta: OpMeta {
signed: false,
real: false,
convs,
typespec: ty.and_then(|t| self.typespec(&t)),
strong: false,
soft: false,
slice: 0,
..OpMeta::default()
},
}
}
K::ElementSelect | K::RangeSelect => {
let base = e.select_value();
let named = base.filter(|b| b.kind() == K::NamedValue);
let bounds = if e.kind() == K::ElementSelect {
constant(e.selector()).map(|i| (i, i))
} else if e.range_selection_kind() == Some(0) {
constant(e.range_left()).zip(constant(e.range_right()))
} else {
None
};
match (named, bounds) {
(Some(b), Some((l, r))) => {
let node = if e.kind() == K::ElementSelect {
ExprNode::BitSelect {
base: u32::MAX,
index: l,
}
} else {
ExprNode::PartSelect {
base: u32::MAX,
left: l,
right: r,
}
};
let id = self.model.add_expr(node);
let mut named = Vec::new();
for part in [e.selector(), e.range_left(), e.range_right()]
.into_iter()
.flatten()
{
Self::named_parameters(&part, &mut named);
}
for p in named {
self.param_links.push((id, p));
}
if let (Some(sym), Some(body)) =
(b.referenced_symbol(), self.link_body(b.referenced_symbol()))
{
self.expr_links.push((id, -1, body, sym.uid()));
} else if let Some(n) = self.model.expr_mut(id) {
*n = ExprNode::Unsupported("a select of a non-signal".into());
}
return id;
}
_ => ExprNode::Unsupported(
"a select with a non-constant index (vpiVarSelect)".into(),
),
}
}
K::UnaryOp
| K::BinaryOp
| K::ConditionalOp
| K::MinTypMax
| K::Concatenation
| K::Replication
| K::Streaming => {
let op_type = op_type_of(e);
let parts: Vec<sv_lang::Expression<'_>> = match e.kind() {
K::ConditionalOp => {
let (t, f) = (e.true_value(), e.false_value());
let mut v: Vec<_> = e.conditions().iter().map(|c| c.expr()).collect();
v.extend(t);
v.extend(f);
v
}
K::Replication => {
let mut v: Vec<_> = e.replication_count().into_iter().collect();
if let Some(c) = e.replication_concat() {
v.extend(c.operands());
}
v
}
K::Concatenation => {
let v: Vec<_> = e.operands().into_iter().collect();
if v.is_empty() {
e.children_with_invalid()
.iter()
.filter_map(|c| c.as_expression())
.collect()
} else {
v
}
}
K::Streaming => {
let streams = e.streams();
if streams.iter().any(|s| s.with_expr().is_some()) {
return self.model.add_expr(ExprNode::Unsupported(
"a streaming concatenation with a `with` clause".into(),
));
}
streams.iter().map(|s| s.operand()).collect()
}
_ => e
.children_with_invalid()
.iter()
.filter_map(|c| c.as_expression())
.collect(),
};
if parts.is_empty() && matches!(e.kind(), K::Concatenation | K::Replication) {
return self.model.add_expr(ExprNode::Unsupported(
"a concatenation with an untyped formal operand (slang drops its operands)"
.into(),
));
}
match op_type {
Some(op_type) => {
let ty = e.expr_type();
let id = self.model.add_expr(ExprNode::Op {
op_type,
width,
operands: Vec::new(),
meta: OpMeta::default(),
});
let mut ops = Vec::new();
let mut all_convs = Vec::new();
for p in &parts {
let (o, c) = self.operand_with_convs(p);
if let Some(o) = o {
ops.push(o);
all_convs.push(c);
}
}
if let Some(ExprNode::Op { operands, meta, .. }) = self.model.expr_mut(id) {
*operands = ops;
*meta = OpMeta {
signed: ty.is_some_and(|t| t.is_signed()),
real: ty.is_some_and(|t| t.is_floating()),
convs: all_convs,
typespec: None,
strong: false,
soft: false,
slice: e
.streaming_slice_size()
.map_or(0, |n| u32::try_from(n).unwrap_or(u32::MAX)),
..OpMeta::default()
};
}
return id;
}
None => ExprNode::Unsupported(format!(
"an operator without a vpiOpType ({:?})",
e.kind()
)),
}
}
sv_lang::kinds::ExpressionKind::EmptyArgument => ExprNode::Op {
op_type: header("vpiNullOp"),
width: 0,
operands: Vec::new(),
meta: OpMeta::default(),
},
other => ExprNode::Unsupported(format!("an expression of kind {other:?}")),
};
self.model.add_expr(node)
}
fn let_body_concat(
&mut self,
sym: &Symbol<'_>,
body: &sv_lang::Expression<'_>,
) -> Option<Operand> {
use sv_lang::AstNode;
use sv_lang::kinds::ExpressionKind as K;
let empty = match body.kind() {
K::Concatenation => body.operands().is_empty(),
K::Replication => body
.replication_concat()
.is_none_or(|c| c.operands().is_empty()),
_ => false,
};
if !empty && !has_empty_concat(body) {
return None;
}
let width = body.expr_type().map_or(0, |t| t.bit_width() as i32);
let trees = self.locator.syntax_trees();
let node = trees.iter().find_map(|t| {
sym.syntax(t)
.and_then(sv_lang::nodes::LetDeclarationSyntax::cast)
.map(|n| n.expr().syntax())
})?;
self.syntax_operand(node, *sym, width)
}
fn syntax_operand(
&mut self,
node: sv_lang::Node<'_>,
scope: Symbol<'_>,
width: i32,
) -> Option<Operand> {
use sv_lang::AstNode;
use sv_lang::nodes::{
BinaryExpressionSyntax, ConcatenationExpressionSyntax,
MultipleConcatenationExpressionSyntax, ParenthesizedExpressionSyntax,
PrefixUnaryExpressionSyntax,
};
if let Some(p) = ParenthesizedExpressionSyntax::cast(node) {
return self.syntax_operand(p.expression().syntax(), scope, width);
}
let (op_type, items): (&str, Vec<sv_lang::Node<'_>>) = if let Some(c) =
ConcatenationExpressionSyntax::cast(node)
{
(
"vpiConcatOp",
c.expressions().iter().map(|x| x.syntax()).collect(),
)
} else if let Some(r) = MultipleConcatenationExpressionSyntax::cast(node) {
let mut v = vec![r.expression().syntax()];
v.extend(r.concatenation().expressions().iter().map(|x| x.syntax()));
("vpiMultiConcatOp", v)
} else {
let text = node.text().trim().to_owned();
if let Some((_, f)) = self.formal_names.iter().find(|(n, _)| *n == text) {
return Some(Operand::Obj(*f));
}
let eval = self.eval;
let bound = unwrap_invalid_expr(eval.bind_expression(node, scope)?);
if !bound.is_bad() {
return self.operand(&bound);
}
let op = op_type_of(&bound)?;
let w = bound.expr_type().map_or(0, |t| t.bit_width() as i32);
let kids: Vec<sv_lang::Node<'_>> = if let Some(b) = BinaryExpressionSyntax::cast(node) {
vec![b.left().syntax(), b.right().syntax()]
} else {
let u = PrefixUnaryExpressionSyntax::cast(node)?;
vec![u.operand().syntax()]
};
let mut ops = Vec::new();
for k in kids {
ops.push(self.syntax_operand(k, scope, 0)?);
}
let convs = vec![Vec::new(); ops.len()];
let id = self.model.add_expr(ExprNode::Op {
op_type: op,
width: w,
operands: ops,
meta: OpMeta {
convs,
signed: bound.expr_type().is_some_and(|t| t.is_signed()),
..OpMeta::default()
},
});
return Some(Operand::Expr(id));
};
let mut ops = Vec::new();
for item in items {
ops.push(self.syntax_operand(item, scope, 0)?);
}
let convs = vec![Vec::new(); ops.len()];
let id = self.model.add_expr(ExprNode::Op {
op_type: header(op_type),
width,
operands: ops,
meta: OpMeta {
convs,
..OpMeta::default()
},
});
Some(Operand::Expr(id))
}
fn member_element_select(&mut self, e: &sv_lang::Expression<'_>) -> ExprId {
let unsupported =
|m: &mut Self, why: &str| m.model.add_expr(ExprNode::Unsupported(why.into()));
let Some(b) = e.select_value() else {
return unsupported(self, "a select with no base");
};
let Some(bty) = b.expr_type().map(|t| t.canonical()) else {
return unsupported(self, "a select of an untyped value");
};
let range = bty.fixed_unpacked_array_range();
let el = bty.element_type().map(|t| t.canonical());
let Some(el) = el.filter(|t| {
vj_sim::state::storage::storage_width(t).is_some()
&& !vj_sim::state::storage::is_collection(t)
}) else {
return unsupported(
self,
"a select of an array member whose element has no fixed stored form",
);
};
let sub = el.is_unpacked_array();
let elem_kind = if sub {
ObjKind::RegArray
} else if el.is_integral() {
ObjKind::Reg
} else {
Self::var_kind(&el)
};
let elem_bits = vj_sim::state::storage::storage_width(&el).unwrap_or(0) as i32;
let elem_width = if sub {
el.fixed_unpacked_array_range().width() as i32
} else if el.is_integral() {
el.bit_width() as i32
} else {
elem_bits
};
let Some(prefix) = self.operand(&b) else {
return unsupported(self, "a select of an unresolved array member");
};
let Some(index) = e.selector().and_then(|s| self.operand(&s)) else {
return unsupported(self, "a select with no index");
};
let text = e
.selector()
.map(|s| self.expr_source_text(&s))
.unwrap_or_default();
self.model.add_expr(ExprNode::PrefixedElement {
prefix,
index,
text,
lo: range.left.min(range.right),
count: (range.left - range.right).abs() + 1,
elem_width,
four_state: el.is_four_state(),
signed: el.is_signed(),
elem_kind,
elem_bits,
})
}
fn expr_select(&mut self, e: &sv_lang::Expression<'_>, width: i32) -> ExprId {
use super::model::Sel;
use sv_lang::kinds::ExpressionKind as K;
let Some(b) = e.select_value() else {
return self
.model
.add_expr(ExprNode::Unsupported("a select with no base".into()));
};
let Some(bty) = b.expr_type().map(|t| t.canonical()) else {
return self
.model
.add_expr(ExprNode::Unsupported("a select of an untyped value".into()));
};
if bty.is_unpacked_array() {
return self.model.add_expr(ExprNode::Unsupported(
"a select from an unpacked subarray".into(),
));
}
let range = if !bty.is_integral() {
(width.max(1) - 1, 0)
} else if bty.is_packed_array() {
let r = bty.fixed_range();
(r.left, r.right)
} else {
(bty.bit_width() as i32 - 1, 0)
};
let constant = |x: Option<sv_lang::Expression<'_>>| {
x.and_then(|x| x.constant_value())
.and_then(|c| c.as_i64())
.map(|v| v as i32)
};
let sel = if e.kind() == K::ElementSelect {
e.selector().and_then(|i| self.operand(&i)).map(Sel::Bit)
} else if e.range_selection_kind() == Some(0) {
constant(e.range_left())
.zip(constant(e.range_right()))
.map(|(l, r)| Sel::Part(l, r))
} else {
e.range_left()
.and_then(|s| self.operand(&s))
.map(|s| Sel::Indexed(s, e.range_selection_kind() == Some(1)))
};
let (Some(base), Some(sel)) = (self.operand(&b), sel) else {
return self
.model
.add_expr(ExprNode::Unsupported("an incomplete select".into()));
};
let text = if e.kind() == K::ElementSelect {
e.selector()
} else {
e.range_left()
}
.map(|x| self.expr_source_text(&x))
.unwrap_or_default();
self.model.add_expr(ExprNode::ExprSelect {
base,
sel,
text,
range,
width,
count: constant(e.range_right()).unwrap_or(width),
four_state: !bty.is_integral() || bty.is_four_state(),
})
}
fn expr_source_text(&self, e: &sv_lang::Expression<'_>) -> String {
use sv_lang::kinds::ExpressionKind as K;
let mut e = *e;
while e.kind() == K::Conversion && e.conversion_is_implicit() != Some(false) {
match e.conversion_operand() {
Some(inner) => e = inner,
None => break,
}
}
self.locator
.syntax_trees()
.iter()
.find_map(|t| e.syntax(t))
.map(|n| n.text().split_whitespace().collect::<String>())
.unwrap_or_default()
}
fn dynamic_select(&mut self, e: &sv_lang::Expression<'_>, width: i32) -> ExprId {
use sv_lang::kinds::ExpressionKind as K;
let base = e.select_value().filter(|b| b.kind() == K::NamedValue);
let Some(b) = base else {
return self
.model
.add_expr(ExprNode::Unsupported("a select of a non-signal".into()));
};
let bty = b.expr_type().map(|t| t.canonical());
let array = bty.is_some_and(|t| t.is_unpacked_array());
let four_state = bty
.map(|t| {
let mut t = t;
while t.is_unpacked_array() {
match t.element_type() {
Some(el) => t = el.canonical(),
None => break,
}
}
t.is_four_state()
})
.unwrap_or(true);
let node = if e.kind() == K::ElementSelect {
let index = e.selector().and_then(|s| self.operand(&s));
let text = e
.selector()
.map(|s| self.expr_source_text(&s))
.unwrap_or_default();
match index {
Some(index) => ExprNode::IndexSelect {
base: u32::MAX,
index,
text,
width,
array,
four_state,
},
None => ExprNode::Unsupported("a select with no index".into()),
}
} else if e.range_selection_kind() == Some(0) {
ExprNode::Unsupported("a slice of an unpacked array".into())
} else {
let text = e
.range_left()
.map(|s| self.expr_source_text(&s))
.unwrap_or_default();
match e.range_left().and_then(|s| self.operand(&s)) {
Some(start) => ExprNode::IndexedPartSelect {
base: u32::MAX,
start,
text,
width,
count: e
.range_right()
.and_then(|x| x.constant_value())
.and_then(|c| c.as_i64())
.map_or(width, |v| v as i32),
up: e.range_selection_kind() == Some(1),
four_state,
},
None => ExprNode::Unsupported("an indexed part select with no base".into()),
}
};
let id = self.model.add_expr(node);
if let (Some(sym), Some(body)) =
(b.referenced_symbol(), self.link_body(b.referenced_symbol()))
{
self.expr_links.push((id, -1, body, sym.uid()));
}
id
}
fn is_ifnone(&self, path: &Symbol<'_>) -> bool {
path.timing_path_is_state_dependent() && path.timing_path_condition_expr().is_none()
}
fn path_ends(&self, path: &Symbol<'_>) -> String {
let text = |e: sv_lang::Expression<'_>| self.expr_source_text(&e);
let ins: Vec<String> = path.timing_path_inputs().map(text).collect();
let outs: Vec<String> = path.timing_path_outputs().map(text).collect();
format!("{}>{}", ins.join(","), outs.join(","))
}
fn mod_path(&mut self, path: &Symbol<'_>, parent: ObjId, has_ifnone: bool) {
let mut obj = Obj::new(ObjKind::ModPath, Some(parent), "", "");
obj.props.path_key = self.body.clone().map(|b| (b, path.uid()));
obj.props.path_type = match path.timing_path_connection_kind() {
sv_lang::TimingPathConnectionKind::Full => 1,
sv_lang::TimingPathConnectionKind::Parallel => 2,
};
obj.props.polarity = match path.timing_path_polarity() {
sv_lang::TimingPathPolarity::Positive => 1,
sv_lang::TimingPathPolarity::Negative => 2,
sv_lang::TimingPathPolarity::Unknown => 3,
};
obj.props.data_polarity = match path.timing_path_edge_polarity() {
sv_lang::TimingPathPolarity::Positive => 1,
sv_lang::TimingPathPolarity::Negative => 2,
sv_lang::TimingPathPolarity::Unknown => 3,
};
obj.props.has_ifnone = has_ifnone;
obj.props.file = Some(self.file.clone());
obj.props.line = self.locator.line(path);
let id = self.add(obj);
if let Some(ce) = path.timing_path_condition_expr() {
let c = self.operand(&ce);
self.fold_if_unmodelled(&ce, c);
if let Some(c) = c {
self.rel(id, consts::VPI_CONDITION, c);
}
}
let delays: Vec<sv_lang::Expression<'_>> = path.timing_path_delays().collect();
for d in delays {
let op = self.operand(&d);
self.fold_if_unmodelled(&d, op);
if let Some(d) = op {
self.rel(id, consts::VPI_DELAY, d);
}
}
let path_full = self
.model
.get(id)
.map(|o| o.full_name.clone())
.unwrap_or_default();
self.attributes(path.attributes(), id, &path_full, false);
let header = |n: &str| crate::vpi::consts::value_of(n).expect("a header constant") as i32;
let edge = match path.timing_path_edge_identifier() {
sv_lang::EdgeKind::PosEdge => header("vpiPosedge"),
sv_lang::EdgeKind::NegEdge => header("vpiNegedge"),
sv_lang::EdgeKind::BothEdges => header("vpiAnyEdge"),
sv_lang::EdgeKind::None => header("vpiNoEdge"),
};
let mut terms: Vec<(i32, bool, i32, sv_lang::Expression<'_>)> = path
.timing_path_inputs()
.map(|e| (consts::VPI_INPUT, false, edge, e))
.chain(
path.timing_path_outputs()
.map(|e| (consts::VPI_OUTPUT, false, 0, e)),
)
.collect();
if let Some(e) = path.timing_path_edge_source_expr() {
terms.push((consts::VPI_INPUT, true, 0, e));
}
for (dir, data_in, edge, e) in terms {
let op = self.operand(&e);
self.fold_if_unmodelled(&e, op);
let mut t = Obj::new(ObjKind::PathTerm, Some(id), "", "");
t.props.direction = dir;
t.props.data_in = data_in;
t.props.prim_type = edge;
t.props.conn_op = op;
t.props.file = Some(self.file.clone());
t.props.line = self.locator.line(path);
let tid = self.add(t);
self.link(tid, 0, &e);
}
}
fn primitive_array(
&mut self,
array: &Symbol<'_>,
parent: ObjId,
parent_name: &str,
name: &str,
) {
let full = format!("{parent_name}.{name}");
let range = array.instance_array_range();
let elements: Vec<Symbol<'_>> = array.instance_array_elements().collect();
let mut obj = Obj::new(ObjKind::PrimitiveArray, Some(parent), name, &full);
obj.props.size = Some(elements.len() as i32);
obj.props.range = Some((range.left, range.right));
obj.props.file = Some(self.file.clone());
obj.props.line = self.locator.line(array);
let id = self.add(obj);
let mut array_type = consts::VPI_GATE_ARRAY;
for (i, e) in elements.iter().enumerate() {
if e.kind() != SymbolKind::PrimitiveInstance {
continue;
}
let idx = element_index(range, i);
let src = elements.get(elements.len() - 1 - i).unwrap_or(e);
self.primitive(
e,
id,
&format!("{name}[{idx}]"),
&format!("{full}[{idx}]"),
Some(idx),
array,
src,
);
if let Some(k) = self
.model
.children(id)
.last()
.and_then(|&c| self.model.get(c))
{
array_type = match k.kind {
ObjKind::Switch => consts::VPI_SWITCH_ARRAY,
ObjKind::Udp => consts::VPI_UDP_ARRAY,
_ => consts::VPI_GATE_ARRAY,
};
}
}
if let Some(p) = self.model.props_mut(id) {
p.array_type = array_type;
}
}
fn cont_assign(&mut self, assign: &Symbol<'_>, parent: ObjId) {
let mut obj = Obj::new(ObjKind::ContAssign, Some(parent), "", "");
let given = assign.continuous_assign_drive_strength();
obj.props.strength0 = strength_const(given.strength0, false);
obj.props.strength1 = strength_const(given.strength1, false);
obj.props.file = Some(self.file.clone());
obj.props.line = self.locator.line(assign);
let id = self.add(obj);
self.assign_delay(assign.continuous_assign_delay(), assign, id);
let assign_full = self
.model
.get(id)
.map(|o| o.full_name.clone())
.unwrap_or_default();
self.attributes(assign.attributes(), id, &assign_full, false);
let Some(a) = assign.continuous_assign_assignment() else {
return;
};
if let Some(l) = a.left() {
self.link(id, 0, &l);
let w = l.expr_type().map_or(0, |t| t.bit_width() as i32);
if let Some(p) = self.model.props_mut(id) {
p.width = w;
}
let op = self.operand(&l);
if let Some(p) = self.model.props_mut(id) {
p.conn_op = op;
}
}
if let Some(r) = a.right() {
self.link(id, 1, &r);
self.let_hint = Some(self.let_sites_of_expr(&a));
let mut op = self.operand(&r);
self.let_hint = None;
if let Some(body) = op
&& !matches!(
body,
Operand::Expr(x) if matches!(self.model.expr(x), Some(ExprNode::LetInst { .. }))
)
{
let use_site = self.locator.syntax_trees().iter().find_map(|t| {
use sv_lang::AstNode;
let n = sv_lang::nodes::BinaryExpressionSyntax::cast(a.syntax(t)?)?;
Some(n.right().syntax().text().to_owned())
});
let expanded = self
.locator
.syntax_trees()
.iter()
.find_map(|t| r.syntax(t).map(|n| n.text().to_owned()));
if let Some(use_site) = use_site {
let width = r.expr_type().map_or(0, |t| t.bit_width() as i32);
op = Some(self.let_instance(
&use_site,
expanded.as_deref(),
id,
body,
width,
None,
&[],
));
}
}
if let Some(p) = self.model.props_mut(id) {
p.rhs_op = op;
}
let mut stack = vec![r.as_sem_node()];
while let Some(n) = stack.pop() {
if let Some(e) = n.as_expression()
&& e.kind() == sv_lang::kinds::ExpressionKind::NamedValue
{
self.link(id, 2, &e);
}
stack.extend(n.children());
}
}
}
fn net_decl_assign(
&mut self,
net: &Symbol<'_>,
init: &sv_lang::Expression<'_>,
parent: ObjId,
parent_name: &str,
) {
let Some(target) = self.model.find(&format!("{parent_name}.{}", net.name())) else {
return;
};
let mut obj = Obj::new(ObjKind::ContAssign, Some(parent), "", "");
let given = net.net_drive_strength();
obj.props.strength0 = strength_const(given.strength0, false);
obj.props.strength1 = strength_const(given.strength1, false);
obj.props.net_decl = true;
obj.props.conn = Some(target);
obj.props.conn_op = Some(Operand::Obj(target));
obj.props.file = Some(self.file.clone());
obj.props.line = self.locator.line(net);
let id = self.add(obj);
self.assign_delay(net.net_delay(), net, id);
if let Some(p) = self.model.props_mut(target) {
p.net_decl = true;
}
self.link(id, 1, init);
self.let_hint = Some(self.let_sites_of_symbol(net));
let op = self.operand(init);
self.let_hint = None;
if let Some(p) = self.model.props_mut(id) {
p.rhs_op = op;
}
let mut stack = vec![init.as_sem_node()];
while let Some(n) = stack.pop() {
if let Some(e) = n.as_expression()
&& e.kind() == sv_lang::kinds::ExpressionKind::NamedValue
{
self.link(id, 2, &e);
}
stack.extend(n.children());
}
}
fn package(&mut self, package: &Symbol<'_>) {
let name = package.name().to_owned();
let mut obj = Obj::new(ObjKind::Package, None, &name, &format!("{name}::"));
obj.props.def_name = Some(name);
obj.props.automatic =
package.package_default_lifetime() == Some(VariableLifetime::Automatic);
obj.props.file = Some(self.file.clone());
obj.props.line = self.locator.line(package);
let id = self.add(obj);
self.attributes(
package.attributes(),
id,
&format!("{}::", package.name()),
false,
);
for m in package.members() {
if m.kind() == SymbolKind::Subroutine {
self.task_func(&m, id, package.name(), "::");
}
if m.kind() == SymbolKind::Parameter {
self.parameter_sep(&m, id, package.name(), "::");
}
}
}
}
struct DpiImport {
spec: String,
pure: bool,
context: bool,
ident: String,
}
fn parse_dpi_import(text: &str, fallback: &str) -> DpiImport {
let head = text.split('(').next().unwrap_or(text);
let spaced = head.replace('=', " = ");
let spec = spaced.split('"').nth(1).unwrap_or("DPI-C").to_owned();
let words: Vec<&str> = spaced
.rsplit('"')
.next()
.unwrap_or("")
.split_whitespace()
.collect();
let ident = words
.iter()
.position(|w| *w == "=")
.and_then(|i| i.checked_sub(1))
.map_or_else(|| fallback.to_owned(), |i| words[i].to_owned());
DpiImport {
spec,
pure: words.contains(&"pure"),
context: words.contains(&"context"),
ident,
}
}
fn full_of(parent_name: &str) -> String {
parent_name.to_owned()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn elements_run_from_the_left_bound_to_the_right_one() {
let down = ConstantRange { left: 2, right: 0 };
assert_eq!(
(0..3).map(|i| element_index(down, i)).collect::<Vec<_>>(),
[2, 1, 0]
);
let up = ConstantRange { left: 1, right: 3 };
assert_eq!(
(0..3).map(|i| element_index(up, i)).collect::<Vec<_>>(),
[1, 2, 3]
);
let single = ConstantRange { left: 5, right: 5 };
assert_eq!(element_index(single, 0), 5);
}
#[test]
fn default_net_types_map_to_the_header_constants() {
assert_eq!(def_net_type(NetKind::Wire), Ok(consts::VPI_WIRE));
assert_eq!(def_net_type(NetKind::WAnd), Ok(consts::VPI_WAND));
assert_eq!(def_net_type(NetKind::Unknown), Ok(consts::VPI_NONE));
assert!(def_net_type(NetKind::UserDefined).is_err());
}
#[test]
fn assertions_are_named_by_label_scoped_by_block_and_typed() {
let session = sv_lang::Session::new();
let mut comp = sv_lang::Compilation::new(&session).expect("compilation created");
comp.add_source(
"module sub(input logic clk, input logic a);\n\
pa: assert property (@(posedge clk) a);\n\
cover property (@(posedge clk) a);\n\
endmodule\n\
module top;\n\
logic clk, a;\n\
sub u1(clk, a);\n\
as1: assume property (@(posedge clk) a);\n\
rs: restrict property (@(posedge clk) a);\n\
cs: cover sequence (@(posedge clk) a ##1 a);\n\
initial begin\n\
ia: assert (a);\n\
assert #0 (a);\n\
ic: cover final (a);\n\
end\n\
always @(posedge clk) begin : blk\n\
r1: assert (a) else $error(\"a low\");\n\
assume (a);\n\
end\n\
endmodule\n",
)
.expect("source added");
let mut design = comp.compile().expect("design compiled");
let m = build_model(&mut design, "t.sv").expect("model built");
let found: Vec<(String, &str, i32, bool, bool)> = m
.objects()
.filter(|(_, o)| o.kind.is_assertion())
.map(|(_, o)| {
(
o.full_name.clone(),
o.kind.type_name(),
o.props.line,
o.props.deferred,
o.props.final_,
)
})
.collect();
let s = |n: &str| n.to_owned();
assert_eq!(
found,
[
(s("top.u1.pa"), "vpiAssert", 2, false, false),
(s(""), "vpiCover", 3, false, false),
(s("top.as1"), "vpiAssume", 8, false, false),
(s("top.rs"), "vpiRestrict", 9, false, false),
(s("top.cs"), "vpiCover", 10, false, false),
(s("top.ia"), "vpiImmediateAssert", 12, false, false),
(s(""), "vpiImmediateAssert", 13, true, false),
(s("top.ic"), "vpiImmediateCover", 14, true, true),
(s("top.blk.r1"), "vpiImmediateAssert", 17, false, false),
(s(""), "vpiImmediateAssume", 18, false, false),
]
);
assert!(m.find("top.blk.r1").is_some());
assert!(m.find("").is_none());
}
#[test]
fn model_round_trips_through_its_image_section() {
let session = sv_lang::Session::new();
let mut comp = sv_lang::Compilation::new(&session).expect("compilation created");
comp.add_source(
"module sub #(parameter int W = 4, parameter string S = \"hi\", parameter real R = 1.5)\n\
(input logic clk, input logic [W-1:0] a, output logic [W-1:0] q);\n\
logic [7:0] mem [0:3];\n\
int unsigned cnt;\n\
always @(posedge clk) begin q <= a + 1; mem[a[1:0]] <= a[3:0] ^ 8'h5a; end\n\
assign cnt = mem[1] + (a > 2 ? 3 : 4);\n\
function automatic int f(int x); return x * 2 + 1; endfunction\n\
pa: assert property (@(posedge clk) a != 0);\n\
endmodule\n\
module top;\n\
logic clk; logic [3:0] a, q;\n\
sub #(.W(4)) u1(clk, a, q);\n\
sub #(.W(4), .S(\"yo\")) u2(clk, a, q);\n\
initial begin $display(\"%0d\", u1.f(3)); end\n\
endmodule\n",
)
.expect("source added");
let mut design = comp.compile().expect("design compiled");
let m = build_model(&mut design, "t.sv").expect("model built");
assert!(m.len() > 20 && m.expr_count() > 3, "a non-trivial model");
let bytes = m.encode();
assert_eq!(bytes, m.encode(), "encoding is deterministic");
let back = VpiModel::decode(&bytes).expect("section decodes");
assert_eq!(back.len(), m.len());
assert_eq!(back.expr_count(), m.expr_count());
for (id, o) in m.objects() {
let b = back.get(id).expect("object present");
assert_eq!(format!("{o:?}"), format!("{b:?}"), "object {id}");
assert_eq!(m.children(id), back.children(id), "children of {id}");
assert_eq!(
back.find(&o.full_name).is_some(),
m.find(&o.full_name).is_some()
);
}
for i in 0..m.expr_count() as ExprId {
assert_eq!(
format!("{:?}", m.expr(i)),
format!("{:?}", back.expr(i)),
"expr {i}"
);
}
assert_eq!(m.tops(), back.tops());
assert_eq!(m.packages(), back.packages());
assert_eq!(m.find("top.u2"), back.find("top.u2"));
assert_eq!(
back.encode(),
bytes,
"re-encoding the decoded model is stable"
);
assert!(VpiModel::decode(&bytes[..bytes.len() / 2]).is_err());
}
#[test]
fn formal_default_requires_a_whole_literal() {
assert!(matches!(
formal_default_node("a + 4'd1"),
ExprNode::Unsupported(_)
));
assert!(matches!(
formal_default_node("4'd1"),
ExprNode::Const { .. }
));
assert!(matches!(formal_default_node("'1"), ExprNode::Const { .. }));
}
}