use crate::symid::SymbolUid;
use std::collections::HashMap;
use crate::netlist::dedup::{DefinitionTable, template_key_of};
use crate::netlist::exprwalk::expression_read_set;
use crate::netlist::instance::array_path_of;
use crate::netlist::lattice::cont_assign_read_set;
use crate::netlist::template::{build_members, build_processes};
use crate::netlist::{
ContAssignTemplate, ElabError, InstanceId, InstanceNode, InstanceTree, InstantiatedSignalId,
NetlistView, PortDirection, SubInstanceSlot, SymbolKey, Template, TemplateId, TemplateKey,
confirm_and_assign_symbol_keys, merge_groups_within_bucket,
};
pub struct Netlist<'d> {
pub templates: Vec<Template>,
pub instances: InstanceTree,
pub top: Vec<InstanceId>,
hier_paths: Vec<String>,
member_symbols: Vec<Vec<sv_lang::Symbol<'d>>>,
instance_syms: Vec<sv_lang::Symbol<'d>>,
pub time_scale: sv_lang::TimeScale,
pub tick_ns_per_unit: u64,
_design: std::marker::PhantomData<&'d sv_lang::Design>,
}
fn fold_struct_member_defaults(eval: &sv_lang::EvalSession<'_>) {
fn sweep(ty: sv_lang::Type<'_>, eval: &sv_lang::EvalSession<'_>, depth: u32) {
if depth > 8 || !ty.is_unpacked_struct() {
return;
}
for f in ty.fields() {
if let Some(init) = f.initializer() {
let _ = eval.eval_constant(init);
}
if let Some(fty) = f.value_type() {
sweep(fty, eval, depth + 1);
}
}
}
eval.design().root().visit(|s| {
if let Some(t) = s.declared_type() {
sweep(t.canonical(), eval, 0);
}
sv_lang::Walk::Continue
});
}
fn check_declared_class_constraints(eval: &sv_lang::EvalSession<'_>) -> Result<(), ElabError> {
let mut result = Ok(());
eval.design().root().visit(|s| {
if s.kind() == sv_lang_kinds::SymbolKind::ClassType
&& let Err(e) = super::classes::check_class_constraints(s)
{
result = Err(e);
return sv_lang::Walk::Break;
}
sv_lang::Walk::Continue
});
result
}
pub fn elaborate<'d>(eval: &'d sv_lang::EvalSession<'d>) -> Result<Netlist<'d>, ElabError> {
let time_scale = resolve_time_scale(eval);
let tick_ns_per_unit = tick_ns_per_unit_of(time_scale.precision)?;
fold_struct_member_defaults(eval);
check_declared_class_constraints(eval)?;
let mut netlist = Netlist {
templates: Vec::new(),
instances: InstanceTree::new(),
top: Vec::new(),
hier_paths: Vec::new(),
member_symbols: Vec::new(),
instance_syms: Vec::new(),
time_scale,
tick_ns_per_unit,
_design: std::marker::PhantomData,
};
let mut staged: Vec<(sv_lang::Symbol<'d>, sv_lang::Symbol<'d>)> = Vec::new();
for top_sym in eval.design().top_instances() {
match top_sym.kind() {
sv_lang_kinds::SymbolKind::Instance => {
let id = visit_instance(&mut netlist, &mut staged, top_sym, None)?;
netlist.top.push(id);
}
other => {
return Err(ElabError::UnsupportedConstruct {
hier_path: top_sym.hierarchical_path(),
detail: format!(
"top-level symbol of kind {other:?} is not a module/program instance"
),
});
}
}
}
dedup_templates(&mut netlist, &staged, eval)?;
Ok(netlist)
}
pub(crate) fn symbol_key_map(
scope: sv_lang::Symbol<'_>,
) -> HashMap<crate::symid::SymbolId, SymbolKey> {
crate::netlist::state_items(&scope)
.into_iter()
.enumerate()
.map(|(i, m)| (m.uid(), SymbolKey(i as u32)))
.collect()
}
fn resolve_time_scale(eval: &sv_lang::EvalSession<'_>) -> sv_lang::TimeScale {
let from_top_scope = eval
.design()
.top_instances()
.next()
.and_then(|top| top.instance_body())
.and_then(|body| body.time_scale());
from_top_scope.unwrap_or(sv_lang::TimeScale {
base: sv_lang::TimeScaleValue {
unit: sv_lang::TimeUnit::Nanoseconds,
magnitude: 1,
},
precision: sv_lang::TimeScaleValue {
unit: sv_lang::TimeUnit::Nanoseconds,
magnitude: 1,
},
})
}
fn tick_ns_per_unit_of(precision: sv_lang::TimeScaleValue) -> Result<u64, ElabError> {
use sv_lang::TimeUnit;
let fs_per_unit: u64 = match precision.unit {
TimeUnit::Seconds => 1_000_000_000_000_000,
TimeUnit::Milliseconds => 1_000_000_000_000,
TimeUnit::Microseconds => 1_000_000_000,
TimeUnit::Nanoseconds => 1_000_000,
TimeUnit::Picoseconds => 1_000,
TimeUnit::Femtoseconds => 1,
other => {
return Err(ElabError::Frontend(format!(
"unrecognized TimeUnit {other:?} in design's time-scale precision"
)));
}
};
const FS_PER_NS: u64 = 1_000_000;
let tick_length_fs = fs_per_unit
.checked_mul(precision.magnitude as u64)
.ok_or_else(|| {
ElabError::Frontend(format!(
"time-scale precision magnitude {} overflows u64 femtoseconds",
precision.magnitude
))
})?;
if tick_length_fs > FS_PER_NS && tick_length_fs.is_multiple_of(FS_PER_NS) {
return Ok(1);
}
if tick_length_fs == 0 || !FS_PER_NS.is_multiple_of(tick_length_fs) {
return Err(ElabError::Frontend(format!(
"design time-scale precision ({:?} x{}) does not evenly divide 1ns into a \
whole number of ticks",
precision.unit, precision.magnitude
)));
}
Ok(FS_PER_NS / tick_length_fs)
}
fn dedup_templates<'d>(
netlist: &mut Netlist<'d>,
staged: &[(sv_lang::Symbol<'d>, sv_lang::Symbol<'d>)],
eval: &'d sv_lang::EvalSession<'d>,
) -> Result<(), ElabError> {
let mut defs = DefinitionTable::default();
let tagged: Vec<(TemplateKey, sv_lang::Symbol<'d>)> = staged
.iter()
.map(|(instance, body)| (template_key_of(&mut defs, *instance, *body), *body))
.collect();
let groups = merge_groups_within_bucket(&tagged);
netlist.member_symbols = vec![Vec::new(); staged.len()];
let mut canonical_instance_of_template: Vec<InstanceId> = Vec::new();
for group in groups {
let canonical_index = group[0];
let (canonical_instance, canonical_body) = staged[canonical_index];
let key = tagged[canonical_index].0;
let definition_name = canonical_instance
.instance_definition()
.map(|def| def.name().to_string())
.unwrap_or_default();
let other_bodies: Vec<sv_lang::Symbol<'d>> = group[1..]
.iter()
.map(|&member_index| staged[member_index].1)
.collect();
let confirmed = confirm_and_assign_symbol_keys(canonical_body, &other_bodies)?;
debug_assert_eq!(
confirmed.per_body_symbols.len(),
group.len(),
"one per_body_symbols entry per group member (canonical + every other)"
);
let (signals, memories, ports) = build_members(canonical_body)?;
let scope_keys = symbol_key_map(canonical_body);
let processes = build_processes(canonical_body, &scope_keys)?;
let continuous_assigns = build_own_cont_assigns(eval, canonical_body, &scope_keys)?;
let template_id = TemplateId(netlist.templates.len() as u32);
netlist.templates.push(Template {
key,
definition_name,
signals,
memories,
processes,
continuous_assigns,
sub_instances: Vec::new(),
ports,
});
canonical_instance_of_template.push(InstanceId(canonical_index as u32));
for (slot, &member_index) in group.iter().enumerate() {
let instance_id = InstanceId(member_index as u32);
netlist.instances.node_mut(instance_id).template = template_id;
netlist.member_symbols[member_index] = confirmed.per_body_symbols[slot].clone();
}
}
for (template_index, &canonical_id) in canonical_instance_of_template.iter().enumerate() {
let child_ids = netlist.instances.node(canonical_id).children.clone();
let canonical_instance_sym = netlist.instance_symbol(canonical_id);
let canonical_body = canonical_instance_sym.instance_body().ok_or_else(|| {
ElabError::Frontend(format!(
"instance `{}` has no instance_body()",
canonical_instance_sym.hierarchical_path()
))
})?;
let parent_scope_keys = symbol_key_map(canonical_body);
let mut sub_instances = Vec::with_capacity(child_ids.len());
let mut synthetic_assigns = Vec::new();
for &child_id in &child_ids {
let child_template_id = netlist.instances.node(child_id).template;
let child_instance_sym = netlist.instance_symbol(child_id);
let mut port_bindings = Vec::new();
for conn in child_instance_sym.instance_port_connections() {
let port_symbol = conn.port();
if port_symbol.kind() == sv_lang_kinds::SymbolKind::InterfacePort {
continue;
}
let plain_internal = port_symbol.kind() == sv_lang_kinds::SymbolKind::Port
&& port_symbol.port_internal_expr().is_none_or(|e| {
matches!(
e.kind(),
sv_lang_kinds::ExpressionKind::NamedValue
| sv_lang_kinds::ExpressionKind::ElementSelect
| sv_lang_kinds::ExpressionKind::RangeSelect
)
});
if !plain_internal {
return Err(ElabError::Frontend(format!(
"unsupported: port expression on `{}` (LRM 23.2.2.1: a port that is a \
part-select or concatenation of internal signals, or a multi-signal \
port, is not supported; declare one port per signal)",
port_symbol.hierarchical_path(),
)));
}
let child_template = &netlist.templates[child_template_id.0 as usize];
let (child_port_key, direction) = match child_template
.ports
.iter()
.find(|p| p.name == port_symbol.name())
{
Some(p) => (p.key, p.direction),
None => {
let direction = match port_symbol.port_direction() {
sv_lang::ArgumentDirection::In => PortDirection::Input,
sv_lang::ArgumentDirection::Out => PortDirection::Output,
sv_lang::ArgumentDirection::InOut => PortDirection::Inout,
sv_lang::ArgumentDirection::Ref => PortDirection::Ref,
};
let signal = child_template
.signals
.iter()
.find(|sig| sig.name == port_symbol.name())
.ok_or_else(|| {
ElabError::Frontend(format!(
"port connection `{}` has no matching PortDecl in its \
child Template (a concatenated port expression such as \
`.p({{a, b}})`, LRM 23.2.2.1, is not supported)",
port_symbol.hierarchical_path(),
))
})?;
(signal.key, direction)
}
};
let read_set = match conn.expression() {
Some(expr) => expression_read_set(expr, &parent_scope_keys),
None => Vec::new(),
};
port_bindings.push((child_port_key, read_set.clone()));
if direction == PortDirection::Ref {
continue;
}
synthetic_assigns.push(ContAssignTemplate {
target: child_port_key,
read_set,
is_output_port_connection: direction == PortDirection::Output,
});
}
sub_instances.push(SubInstanceSlot {
port_bindings,
child_template: child_template_id,
});
}
netlist.templates[template_index].sub_instances = sub_instances;
netlist.templates[template_index]
.continuous_assigns
.extend(synthetic_assigns);
}
Ok(())
}
fn build_own_cont_assigns<'d>(
eval: &sv_lang::EvalSession<'d>,
scope: sv_lang::Symbol<'d>,
scope_keys: &HashMap<crate::symid::SymbolId, SymbolKey>,
) -> Result<Vec<ContAssignTemplate>, ElabError> {
use sv_lang_kinds::SymbolKind;
let mut out = Vec::new();
for member in crate::netlist::module_items(&scope) {
if member.kind() != SymbolKind::ContinuousAssign {
continue;
}
let assignment = member.continuous_assign_assignment().ok_or_else(|| {
ElabError::Frontend(format!(
"continuous assign `{}` has no continuous_assign_assignment()",
member.hierarchical_path()
))
})?;
let lhs = assignment.left().ok_or_else(|| {
ElabError::Frontend(format!(
"continuous assign `{}`'s assignment expression has no left()",
member.hierarchical_path()
))
})?;
let target_symbols = lvalue_roots(lhs);
if target_symbols.is_empty() {
return Err(ElabError::Frontend(format!(
"continuous assign `{}`'s target expression names no variable or net",
member.hierarchical_path()
)));
}
let rhs = assignment.right().ok_or_else(|| {
ElabError::Frontend(format!(
"continuous assign `{}`'s assignment expression has no right()",
member.hierarchical_path()
))
})?;
for target_symbol in target_symbols {
let Some(&target) = scope_keys.get(&target_symbol.uid()) else {
continue;
};
let read_set = if crate::netlist::exprwalk::has_pattern_conditional(rhs) {
let mut keys = crate::netlist::exprwalk::pattern_rhs_read_set(rhs, scope_keys)
.map_err(ElabError::Frontend)?;
keys.sort();
keys.dedup();
keys
} else {
cont_assign_read_set(
eval,
member,
target_symbol.uid(),
rhs.source_range(),
scope_keys,
)?
};
out.push(ContAssignTemplate {
target,
read_set,
is_output_port_connection: false,
});
}
}
Ok(out)
}
fn lvalue_roots<'d>(e: sv_lang::Expression<'d>) -> Vec<sv_lang::Symbol<'d>> {
use sv_lang_kinds::ExpressionKind;
let kind = e.kind();
if crate::symid::is_value_name(kind) {
e.referenced_symbol().into_iter().collect()
} else if matches!(
kind,
ExpressionKind::ElementSelect | ExpressionKind::RangeSelect
) {
e.select_value().map(lvalue_roots).unwrap_or_default()
} else if kind == ExpressionKind::MemberAccess {
e.operand().map(lvalue_roots).unwrap_or_default()
} else if kind == ExpressionKind::Concatenation {
e.operands().into_iter().flat_map(lvalue_roots).collect()
} else {
Vec::new()
}
}
fn visit_instance<'d>(
netlist: &mut Netlist<'d>,
staged: &mut Vec<(sv_lang::Symbol<'d>, sv_lang::Symbol<'d>)>,
instance: sv_lang::Symbol<'d>,
parent: Option<InstanceId>,
) -> Result<InstanceId, ElabError> {
let body = instance.instance_body().ok_or_else(|| {
ElabError::Frontend(format!(
"instance `{}` (kind Instance) has no instance_body()",
instance.hierarchical_path()
))
})?;
let hier_path = instance.hierarchical_path();
let name = instance.name().to_string();
let array_path = array_path_of(instance)?;
let placeholder_template = TemplateId(u32::MAX);
let own_index = netlist.instances.push(InstanceNode {
id: InstanceId(0), template: placeholder_template,
parent,
name,
array_path,
children: Vec::new(),
});
let id = InstanceId(own_index as u32);
netlist.instances.node_mut(id).id = id;
netlist.hier_paths.push(hier_path);
debug_assert_eq!(
staged.len(),
own_index,
"staged must stay index-aligned with InstanceId.0"
);
staged.push((instance, body));
debug_assert_eq!(
netlist.instance_syms.len(),
own_index,
"instance_syms must stay index-aligned with InstanceId.0"
);
netlist.instance_syms.push(instance);
let child_syms = collect_instance_children(body)?;
let mut child_ids = Vec::with_capacity(child_syms.len());
for child_sym in child_syms {
let child_id = visit_instance(netlist, staged, child_sym, Some(id))?;
child_ids.push(child_id);
}
netlist.instances.node_mut(id).children = child_ids;
Ok(id)
}
fn collect_instance_children<'d>(
body: sv_lang::Symbol<'d>,
) -> Result<Vec<sv_lang::Symbol<'d>>, ElabError> {
let mut children = Vec::new();
collect_instance_children_into(body, &mut children)?;
Ok(children)
}
fn collect_instance_children_into<'d>(
scope: sv_lang::Symbol<'d>,
children: &mut Vec<sv_lang::Symbol<'d>>,
) -> Result<(), ElabError> {
use sv_lang_kinds::SymbolKind;
for member in scope.members() {
match member.kind() {
SymbolKind::Instance => children.push(member),
SymbolKind::InstanceArray => collect_instance_array_elements(member, children)?,
SymbolKind::GenerateBlockArray => {
for entry in member.generate_block_array_entries() {
collect_instance_children_into(entry, children)?;
}
}
SymbolKind::GenerateBlock if member.generate_block_is_uninstantiated() => {}
SymbolKind::GenerateBlock => collect_instance_children_into(member, children)?,
SymbolKind::Port
| SymbolKind::MultiPort
| SymbolKind::InterfacePort
| SymbolKind::Modport
| SymbolKind::LetDecl
| SymbolKind::MethodPrototype
| SymbolKind::PrimitiveInstance
| SymbolKind::NetAlias
| SymbolKind::ClockingBlock
| SymbolKind::Net
| SymbolKind::Variable
| SymbolKind::ContinuousAssign
| SymbolKind::Parameter
| SymbolKind::TypeParameter
| SymbolKind::Genvar
| SymbolKind::ProceduralBlock
| SymbolKind::StatementBlock
| SymbolKind::Subroutine
| SymbolKind::Sequence
| SymbolKind::Property
| SymbolKind::ElabSystemTask
| SymbolKind::TypeAlias
| SymbolKind::NetType
| SymbolKind::DefParam
| SymbolKind::Specparam
| SymbolKind::SpecifyBlock
| SymbolKind::CovergroupType
| SymbolKind::ClassType
| SymbolKind::GenericClassDef
| SymbolKind::ForwardingTypedef
| SymbolKind::EmptyMember
| SymbolKind::TransparentMember
| SymbolKind::DeferredMember
| SymbolKind::Attribute
| SymbolKind::WildcardImport
| SymbolKind::ExplicitImport => {}
SymbolKind::Checker => {}
SymbolKind::CheckerInstance => collect_instance_children_into(member, children)?,
SymbolKind::AssertionPort | SymbolKind::FormalArgument => {}
other => {
return Err(ElabError::UnsupportedConstruct {
hier_path: member.hierarchical_path(),
detail: format!("{other:?}"),
});
}
}
}
Ok(())
}
fn collect_instance_array_elements<'d>(
array: sv_lang::Symbol<'d>,
children: &mut Vec<sv_lang::Symbol<'d>>,
) -> Result<(), ElabError> {
use sv_lang_kinds::SymbolKind;
for element in array.instance_array_elements() {
match element.kind() {
SymbolKind::Instance => children.push(element),
SymbolKind::InstanceArray => collect_instance_array_elements(element, children)?,
SymbolKind::PrimitiveInstance => {}
SymbolKind::CheckerInstance => collect_instance_children_into(element, children)?,
other => {
return Err(ElabError::UnsupportedConstruct {
hier_path: element.hierarchical_path(),
detail: format!("instance-array element of kind {other:?}"),
});
}
}
}
Ok(())
}
impl<'d> Netlist<'d> {
pub fn member_symbol(&self, instance: InstanceId, key: SymbolKey) -> sv_lang::Symbol<'d> {
self.member_symbols[instance.0 as usize][key.0 as usize]
}
pub fn instance_symbol(&self, id: InstanceId) -> sv_lang::Symbol<'d> {
self.instance_syms[id.0 as usize]
}
}
impl<'d> NetlistView for Netlist<'d> {
fn registration_order(&self) -> Vec<InstanceId> {
(0..self.instances.len() as u32).map(InstanceId).collect()
}
fn hierarchical_path(&self, id: InstanceId) -> &str {
&self.hier_paths[id.0 as usize]
}
fn resolve_hier_path(&self, path: &str) -> Option<InstantiatedSignalId> {
let segments: Vec<&str> = path.split('.').collect();
let (first, rest) = segments.split_first()?;
let (last, middle) = rest.split_last()?;
let mut current_id = *self
.top
.iter()
.find(|&&id| self.instances.node(id).name == *first)?;
for seg in middle {
current_id = *self
.instances
.node(current_id)
.children
.iter()
.find(|&&child_id| self.instances.node(child_id).name == *seg)?;
}
let template_id = self.instances.node(current_id).template;
let signal = self.templates[template_id.0 as usize]
.signals
.iter()
.find(|signal| signal.name == *last)?;
Some(InstantiatedSignalId {
instance: current_id,
member: signal.key,
})
}
fn dedup_key(&self, tmpl: TemplateId) -> &TemplateKey {
&self.templates[tmpl.0 as usize].key
}
fn tick_ns_per_unit(&self) -> u64 {
self.tick_ns_per_unit
}
fn instance(&self, id: InstanceId) -> &InstanceNode {
self.instances.node(id)
}
fn template(&self, id: TemplateId) -> &Template {
&self.templates[id.0 as usize]
}
}