use crate::symid::SymbolUid;
use std::collections::HashMap;
use crate::netlist::{ElabError, SymbolKey, TemplateId, TemplateKey};
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub enum ValueDomain {
Narrow { bits: u32 },
Wide { bits: u32 },
Memory,
}
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub enum SignalKind {
Net(sv_lang::NetKind),
Variable,
Genvar,
}
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub struct DriverEntry {
pub driver_kind: sv_lang::DriverKind,
pub containing: SymbolKey,
}
#[derive(Clone, Debug, PartialEq)]
pub struct SignalDecl {
pub key: SymbolKey,
pub name: String,
pub kind: SignalKind,
pub value_domain: ValueDomain,
pub driver_set: Vec<DriverEntry>,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct MemoryDecl {
pub key: SymbolKey,
pub name: String,
pub element_bits: u32,
pub depth: u32,
}
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub enum StaticSensitivity {
Implicit { reads: Vec<SymbolKey> },
Explicit {
edges: Vec<(SymbolKey, crate::state::EdgeKind)>,
},
#[default]
None,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct ProcessTemplate {
pub key: SymbolKey,
pub kind: sv_lang::ProceduralBlockKind,
pub sensitivity: StaticSensitivity,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct ContAssignTemplate {
pub target: SymbolKey,
pub read_set: Vec<SymbolKey>,
pub is_output_port_connection: bool,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct PortDecl {
pub key: SymbolKey,
pub name: String,
pub direction: PortDirection,
pub bits: u32,
}
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub enum PortDirection {
Input,
Output,
Inout,
Ref,
}
#[derive(Debug, Clone)]
pub struct Template {
pub key: TemplateKey,
pub definition_name: String,
pub signals: Vec<SignalDecl>,
pub memories: Vec<MemoryDecl>,
pub processes: Vec<ProcessTemplate>,
pub continuous_assigns: Vec<ContAssignTemplate>,
pub sub_instances: Vec<SubInstanceSlot>,
pub ports: Vec<PortDecl>,
}
#[derive(Debug, Clone)]
pub struct SubInstanceSlot {
pub port_bindings: Vec<(SymbolKey, Vec<SymbolKey>)>,
pub child_template: TemplateId,
}
pub(crate) type ScopeMembers = (Vec<SignalDecl>, Vec<MemoryDecl>, Vec<PortDecl>);
pub(crate) fn build_members<'d>(scope: sv_lang::Symbol<'d>) -> Result<ScopeMembers, ElabError> {
use sv_lang_kinds::SymbolKind;
let mut signals = Vec::new();
let mut memories = Vec::new();
let mut ports = Vec::new();
for (index, member) in crate::netlist::state_items(&scope).into_iter().enumerate() {
let key = SymbolKey(index as u32);
match member.kind() {
SymbolKind::Port => {
ports.push(build_port_decl(member, key)?);
}
SymbolKind::Net | SymbolKind::Variable | SymbolKind::FormalArgument => {
let ty = member.value_type().ok_or_else(|| {
ElabError::Frontend(format!(
"signal `{}` (kind {:?}) has no resolved value_type()",
member.hierarchical_path(),
member.kind(),
))
})?;
if ty.is_unpacked_array() {
memories.push(build_memory_decl(member, key, ty)?);
} else {
let kind = if member.kind() == SymbolKind::Net {
SignalKind::Net(net_kind_of_member(scope, &ty))
} else {
SignalKind::Variable
};
signals.push(SignalDecl {
key,
name: member.name().to_string(),
kind,
value_domain: value_domain_of(ty.bit_width(), &member.hierarchical_path())?,
driver_set: Vec::new(),
});
}
}
SymbolKind::Genvar => {
signals.push(SignalDecl {
key,
name: member.name().to_string(),
value_domain: ValueDomain::Narrow { bits: 32 },
kind: SignalKind::Genvar,
driver_set: Vec::new(),
});
}
SymbolKind::MultiPort
| SymbolKind::InterfacePort
| SymbolKind::Modport
| SymbolKind::LetDecl
| SymbolKind::MethodPrototype
| SymbolKind::PrimitiveInstance
| SymbolKind::NetAlias
| SymbolKind::ClockingBlock
| SymbolKind::ContinuousAssign
| SymbolKind::Parameter
| SymbolKind::TypeParameter
| SymbolKind::ProceduralBlock
| SymbolKind::StatementBlock
| SymbolKind::Subroutine
| SymbolKind::Sequence
| SymbolKind::Property
| SymbolKind::Checker
| 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::Instance
| SymbolKind::InstanceArray
| SymbolKind::GenerateBlockArray
| SymbolKind::GenerateBlock => {}
other => {
return Err(ElabError::UnsupportedConstruct {
hier_path: member.hierarchical_path(),
detail: format!(
"{other:?} member not yet lowered into Template.signals/memories/ports"
),
});
}
}
}
Ok((signals, memories, ports))
}
pub(crate) fn build_processes(
scope: sv_lang::Symbol<'_>,
scope_keys: &HashMap<crate::symid::SymbolId, SymbolKey>,
) -> Result<Vec<ProcessTemplate>, ElabError> {
use sv_lang_kinds::SymbolKind;
let mut processes = Vec::new();
for (index, member) in crate::netlist::module_items(&scope).into_iter().enumerate() {
if member.kind() != SymbolKind::ProceduralBlock {
continue;
}
let key = SymbolKey(index as u32);
let kind = member.procedural_block_procedure_kind();
let sensitivity = match kind {
sv_lang::ProceduralBlockKind::Initial | sv_lang::ProceduralBlockKind::Final => {
StaticSensitivity::None
}
sv_lang::ProceduralBlockKind::AlwaysFF => {
let stmt = crate::process::cfg::proc_body(&member).ok_or_else(|| {
ElabError::Frontend(format!(
"always_ff `{}` has no body()",
member.hierarchical_path()
))
})?;
let tc = stmt
.timing_control()
.ok_or_else(|| ElabError::UnsupportedConstruct {
hier_path: member.hierarchical_path(),
detail: "always_ff block with no root timing control (dynamic/nested \
sensitivity is not yet lowered by this elaboration pass)"
.to_string(),
})?;
if is_inferred_clock_event(tc)
|| crate::sva::sampled::mentions_global_clock_event(tc)
{
StaticSensitivity::None
} else {
let edges =
explicit_sensitivity_edges(tc, scope_keys, &member.hierarchical_path())?;
if edges.is_empty() {
StaticSensitivity::None
} else {
StaticSensitivity::Explicit { edges }
}
}
}
sv_lang::ProceduralBlockKind::Always
| sv_lang::ProceduralBlockKind::AlwaysComb
| sv_lang::ProceduralBlockKind::AlwaysLatch => StaticSensitivity::None,
};
processes.push(ProcessTemplate {
key,
kind,
sensitivity,
});
}
Ok(processes)
}
fn is_inferred_clock_event(tc: sv_lang::TimingControl<'_>) -> bool {
if tc.kind() == sv_lang_kinds::TimingControlKind::EventList {
return tc
.children()
.into_iter()
.filter_map(|c| c.as_timing_control())
.any(is_inferred_clock_event);
}
tc.kind() == sv_lang_kinds::TimingControlKind::SignalEvent
&& tc
.children()
.into_iter()
.find_map(|c| c.as_expression())
.is_some_and(|e| crate::process::cfg::is_inferred_clock_call(&e))
}
fn explicit_sensitivity_edges(
tc: sv_lang::TimingControl<'_>,
scope_keys: &HashMap<crate::symid::SymbolId, SymbolKey>,
context_hier_path: &str,
) -> Result<Vec<(SymbolKey, crate::state::EdgeKind)>, ElabError> {
use sv_lang_kinds::TimingControlKind;
match tc.kind() {
TimingControlKind::SignalEvent => {
debug_assert!(!crate::sva::sampled::is_global_clock_event(tc));
let edge = edge_kind_of(tc.edge());
let expr = tc
.children()
.into_iter()
.find_map(|c| c.as_expression())
.ok_or_else(|| {
ElabError::Frontend(format!(
"SignalEvent timing control in `{context_hier_path}` has no expression \
child naming the watched signal"
))
})?;
let Some(sym) = expr.referenced_symbol() else {
return Ok(Vec::new());
};
if !scope_keys.contains_key(&sym.uid())
&& !sym
.hierarchical_path()
.starts_with(&format!("{context_hier_path}."))
{
return Ok(Vec::new());
}
let key = *scope_keys.get(&sym.uid()).ok_or_else(|| {
ElabError::Frontend(format!(
"SignalEvent timing control in `{context_hier_path}` names `{}`, not a \
member of its own scope's SymbolKey table",
sym.hierarchical_path()
))
})?;
Ok(vec![(key, edge)])
}
TimingControlKind::EventList => {
let mut out = Vec::new();
for child in tc.children() {
let child_tc = child.as_timing_control().ok_or_else(|| {
ElabError::Frontend(format!(
"EventList timing control in `{context_hier_path}` has a non-timing-\
control child"
))
})?;
out.extend(explicit_sensitivity_edges(
child_tc,
scope_keys,
context_hier_path,
)?);
}
Ok(out)
}
other => Err(ElabError::UnsupportedConstruct {
hier_path: context_hier_path.to_string(),
detail: format!(
"{other:?} timing control kind in an explicit sensitivity list is not yet \
lowered by this elaboration pass"
),
}),
}
}
fn edge_kind_of(edge: sv_lang::EdgeKind) -> crate::state::EdgeKind {
match edge {
sv_lang::EdgeKind::None => crate::state::EdgeKind::None,
sv_lang::EdgeKind::PosEdge => crate::state::EdgeKind::Posedge,
sv_lang::EdgeKind::NegEdge => crate::state::EdgeKind::Negedge,
sv_lang::EdgeKind::BothEdges => crate::state::EdgeKind::BothEdges,
}
}
fn build_port_decl(port: sv_lang::Symbol<'_>, key: SymbolKey) -> Result<PortDecl, ElabError> {
let direction = port_direction_of(port.port_direction());
let ty = port.port_type().ok_or_else(|| {
ElabError::Frontend(format!(
"port `{}` has no resolved port_type()",
port.hierarchical_path(),
))
})?;
let bits = u32::try_from(ty.bit_width()).map_err(|_| {
ElabError::Frontend(format!(
"port `{}` has an out-of-range bit width {}",
port.hierarchical_path(),
ty.bit_width(),
))
})?;
Ok(PortDecl {
key,
name: port.name().to_string(),
direction,
bits,
})
}
fn build_memory_decl(
member: sv_lang::Symbol<'_>,
key: SymbolKey,
ty: sv_lang::Type<'_>,
) -> Result<MemoryDecl, ElabError> {
let element = ty.element_type().ok_or_else(|| {
ElabError::Frontend(format!(
"memory `{}` is an unpacked array with no element_type()",
member.hierarchical_path(),
))
})?;
let element_bits = u32::try_from(element.bit_width()).map_err(|_| {
ElabError::Frontend(format!(
"memory `{}` has an out-of-range element bit width {}",
member.hierarchical_path(),
element.bit_width(),
))
})?;
let range = ty.fixed_range();
let depth = range
.left
.checked_sub(range.right)
.map(|span| span.unsigned_abs())
.and_then(|span| span.checked_add(1))
.ok_or_else(|| {
ElabError::Frontend(format!(
"memory `{}` has an out-of-range depth (range {:?})",
member.hierarchical_path(),
range,
))
})?;
Ok(MemoryDecl {
key,
name: member.name().to_string(),
element_bits,
depth,
})
}
fn net_kind_of(scope: sv_lang::Symbol<'_>) -> sv_lang::NetKind {
scope
.default_net_type()
.map(|net_type| net_type.net_kind())
.unwrap_or(sv_lang::NetKind::Unknown)
}
fn net_kind_of_member(scope: sv_lang::Symbol<'_>, ty: &sv_lang::Type<'_>) -> sv_lang::NetKind {
if ty.bit_width() == 0 {
return sv_lang::NetKind::Interconnect;
}
net_kind_of(scope)
}
fn port_direction_of(direction: sv_lang::ArgumentDirection) -> PortDirection {
match 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,
}
}
fn value_domain_of(bits: u64, hier_path: &str) -> Result<ValueDomain, ElabError> {
let bits32 = u32::try_from(bits).map_err(|_| {
ElabError::Frontend(format!(
"signal `{hier_path}` has an out-of-range bit width {bits}"
))
})?;
Ok(if bits32 <= 64 {
ValueDomain::Narrow { bits: bits32 }
} else {
ValueDomain::Wide { bits: bits32 }
})
}