pub use crate::timing_options::{SdfMtm, TimingOptions};
use std::collections::{HashMap, HashSet};
use std::fmt;
use std::io::Write;
use std::path::Path;
use vj_sim::symid::SymbolUid;
use sv_lang::kinds::{ExpressionKind, SymbolKind};
use sv_lang::{ArgumentDirection, Expression, SemNode, Statement, Symbol};
use vj_sim::symid::SymbolId;
use vj_codegen::error::LoweringError;
use vj_codegen::module::{
DiscoveredProcess, SliceProcKind, build_comb_sensitivity_index, discover_called_subroutines,
discover_called_subroutines_inlined, discover_cont_assigns, discover_processes_dispatching,
discover_variable_initializers,
};
use vj_codegen::pipeline::{
ContAssignLoweringSpec, RealBindings, SubroutineLoweringSpec, build_pipeline,
};
use vj_codegen::process_lower::{
SignalSlot, SignalSlotClass, build_lowering_plan, to_state_edge_kind,
};
use vj_codegen::select::{RefActualError, ref_actual_window};
use vj_runtime::display_sink::{real_sv_rt_display, real_sv_rt_write, take_output};
use vj_runtime::file_io_sink::{
real_sv_rt_fclose, real_sv_rt_fdisplay, real_sv_rt_feof, real_sv_rt_fflush,
real_sv_rt_fmonitor, real_sv_rt_fopen, real_sv_rt_fopen_mcd, real_sv_rt_fseek,
real_sv_rt_fstrobe, real_sv_rt_fwrite, real_sv_rt_system, reset_mcd_table_for_new_run,
};
use vj_runtime::plusargs_sink::{
real_sv_rt_plusargs_test, real_sv_rt_plusargs_value, set_plusargs_for_run,
};
use vj_runtime::random::{sv_rt_dist, sv_rt_random_reseed, sv_rt_urandom_reseed};
use vj_runtime::random_sink::{
real_sv_rt_random, real_sv_rt_urandom, real_sv_rt_urandom_range, reset_seed_for_new_run,
};
use crate::primitives::loc_of;
pub use crate::sources::SourceSet;
use vj_abi::{SimHandle, SlotId};
use vj_sim::netlist::{self, ElabError, InstanceId, NetlistView, TemplateKey};
use vj_sim::process::cfg::{ProcessLoopKind, lower_to_segments};
use vj_sim::process::liveness::{FrameLayout, compute_liveness};
use vj_sim::sched::{
CompiledKernelProcess, DeferredIoCallback, Engine, IoSink, ProcessKind, SchedTime,
SchedulerControl, StaticSensitivity, real_nba_enqueue, real_sv_rt_disable_fork,
real_sv_rt_finish, real_sv_rt_time, real_sv_set_mem_word, real_sv_set_scalar, real_sv_set_wide,
};
use vj_sim::state::{
AliasWindow, EdgeKind as StateEdgeKind, FourStateValue, StateCellId, StateEngine,
TemplateRegistry, build_template_layout, cell_for_slot, set_foreign_slots_windowed,
};
#[derive(Debug)]
pub enum RunError {
Compile(sv_lang::Error),
Diagnostics(String),
Lowering(LoweringError),
Jit(String),
Io(std::io::Error),
Codegen(vj_codegen::error::CodegenError),
SdfAnnotate(String),
Vpi(String),
Livelock(String),
AotRefused {
feature: String,
reason: String,
at: String,
},
}
impl fmt::Display for RunError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
RunError::Compile(e) => write!(f, "compile error: {e}"),
RunError::Diagnostics(text) => write!(f, "the design has errors:\n{text}"),
RunError::Lowering(e) => write!(f, "lowering error: {e}"),
RunError::Jit(msg) => write!(f, "JIT error: {msg}"),
RunError::Io(e) => write!(f, "I/O error: {e}"),
RunError::Codegen(e) => write!(f, "codegen error: {e}"),
RunError::SdfAnnotate(msg) => write!(f, "$sdf_annotate failed: {msg}"),
RunError::Vpi(msg) => write!(f, "VPI error: {msg}"),
RunError::Livelock(msg) => write!(f, "simulation livelock: {msg}"),
RunError::AotRefused {
feature,
reason,
at,
} => write!(f, "AOT-REFUSED({feature}): {reason} at {at}"),
}
}
}
impl std::error::Error for RunError {}
struct NullIoSink;
impl IoSink for NullIoSink {
fn enqueue(&self, _at: SchedTime, _region: vj_abi::Region, _seq: u64, _cb: DeferredIoCallback) {
}
}
pub(crate) fn sim_lowering_to_run_error(
e: vj_sim::process::cfg::LoweringError,
path: &Path,
) -> RunError {
RunError::Lowering(LoweringError::unsupported(
&e.to_string(),
path.display().to_string().as_str(),
))
}
fn scheduler_process_kind(kind: SliceProcKind) -> ProcessKind {
match kind {
SliceProcKind::Initial => ProcessKind::Initial,
SliceProcKind::Always => ProcessKind::Always,
SliceProcKind::AlwaysComb => ProcessKind::AlwaysComb,
SliceProcKind::AlwaysLatch => ProcessKind::AlwaysLatch,
SliceProcKind::AlwaysFF => ProcessKind::AlwaysFF,
SliceProcKind::Final => ProcessKind::Final,
SliceProcKind::StaticAssertion => ProcessKind::Initial,
}
}
pub(crate) fn loop_kind_of(kind: SliceProcKind) -> ProcessLoopKind {
match kind {
SliceProcKind::Initial
| SliceProcKind::AlwaysComb
| SliceProcKind::AlwaysLatch
| SliceProcKind::Final
| SliceProcKind::StaticAssertion => ProcessLoopKind::OneShot,
SliceProcKind::Always | SliceProcKind::AlwaysFF => ProcessLoopKind::Looping,
}
}
#[derive(Debug, Clone, Default)]
struct DisableScope {
process: usize,
block: Option<vj_sim::process::cfg::PeerBlock>,
nested: Vec<vj_sim::process::cfg::PeerBlock>,
paths: Vec<String>,
keys: Vec<usize>,
labels: Vec<(String, usize)>,
}
struct KernelSensitivitySpec {
read_set: Vec<SymbolId>,
windows: HashMap<SymbolId, Vec<(u32, u32)>>,
reset: Option<(SymbolId, StateEdgeKind)>,
}
type ReadWindows = Vec<(StateCellId, Vec<(u32, u32)>)>;
fn build_static_sensitivity(
spec: &KernelSensitivitySpec,
signals: &HashMap<SymbolId, SignalSlot>,
sim_handle: SimHandle,
path: &Path,
) -> Result<Option<(StaticSensitivity, ReadWindows)>, RunError> {
let resolve = |id: &SymbolId| -> Result<StateCellId, RunError> {
let slot = *signals.get(id).ok_or_else(|| {
RunError::Lowering(LoweringError::unsupported(
&format!(
"static-sensitivity entry `{id}` has no resolved signal slot in its own \
module"
),
path.display().to_string().as_str(),
))
})?;
Ok(cell_for_slot(sim_handle, SlotId(slot.slot)))
};
let mut edges = Vec::with_capacity(spec.read_set.len() + usize::from(spec.reset.is_some()));
for id in &spec.read_set {
edges.push((resolve(id)?, None));
}
if let Some((id, edge)) = &spec.reset {
edges.push((resolve(id)?, Some(*edge)));
}
let narrowed = spec
.read_set
.iter()
.filter_map(|id| Some((id, spec.windows.get(id)?)))
.map(|(id, w)| Ok((resolve(id)?, w.clone())))
.collect::<Result<Vec<_>, RunError>>()?;
if edges.is_empty() {
Ok(None)
} else {
Ok(Some((StaticSensitivity { edges }, narrowed)))
}
}
fn total_frame_words(frame: &FrameLayout) -> u32 {
frame.total_words()
}
pub(crate) fn signal_slots_of<'d>(
body: &Symbol<'d>,
) -> Result<HashMap<SymbolId, SignalSlot>, RunError> {
vj_codegen::module::signal_slots_of(body).map_err(RunError::Lowering)
}
fn lrm_errors_slang_only_warns(design: &sv_lang::Design) -> Option<String> {
const CODES: [&str; 9] = [
"Redefinition",
"MultipleContAssigns",
"MixedVarAssigns",
"DuplicateDefinition",
"ParameterDoesNotExist",
"UnsizedInConcat",
"DigitsLeadingUnderscore",
"RedefinitionDifferentType",
"CannotIndexScalar",
];
let render = |items: &[sv_lang::Diagnostic], analysis: bool| -> Vec<String> {
items
.iter()
.filter(|d| {
CODES.contains(&d.code_name())
|| (analysis && d.is_error() && d.message.contains("'uwire'"))
})
.filter(|d| d.source.as_deref().is_none_or(|s| !s.contains("checker")))
.filter(|d| !is_block_label_redefinition(d) || generate_label_conflict(design, d))
.filter(|d| !is_constraint_redefinition(d))
.map(|d| format!("{}:{}:{}: error: {}", d.file, d.line, d.column, d.message))
.collect()
};
let mut found = render(design.diagnostics().items(), false);
if found.is_empty()
&& let Ok(analysis) = design.analyze(sv_lang::AnalysisFlags::NONE, 1)
{
found = render(analysis.diagnostics().items(), true);
}
(!found.is_empty()).then(|| found.join("\n") + "\n")
}
fn generate_label_conflict(design: &sv_lang::Design, d: &sv_lang::Diagnostic) -> bool {
use sv_lang::kinds::SyntaxKind;
let (Some(src), Some(name)) = (d.source.as_deref(), d.message.split('\'').nth(1)) else {
return false;
};
let label_of = |block: &sv_lang::Node<'_>| -> Option<String> {
block
.children()
.find(|c| c.kind() == SyntaxKind::NamedBlockClause)
.map(|c| c.text().trim().trim_start_matches(':').trim().to_owned())
};
design.syntax_trees().iter().any(|tree| {
let text = tree.text();
if !(src.starts_with(text.as_str()) || text.starts_with(&*src)) {
return false;
}
let nodes: Vec<_> = tree.root().descendants().collect();
let holder = nodes
.iter()
.filter(|n| {
matches!(
n.kind(),
SyntaxKind::ModuleDeclaration
| SyntaxKind::InterfaceDeclaration
| SyntaxKind::ProgramDeclaration
) && n.byte_range().contains(&d.byte_offset)
})
.min_by_key(|n| n.byte_range().len());
let Some(holder) = holder else {
return false;
};
let range = holder.byte_range();
nodes.iter().any(|n| {
n.kind() == SyntaxKind::GenerateBlock
&& range.contains(&n.byte_range().start)
&& label_of(n).as_deref() == Some(name)
})
})
}
fn is_constraint_redefinition(d: &sv_lang::Diagnostic) -> bool {
d.code_name() == "Redefinition"
&& d.source.as_deref().is_some_and(|s| {
s.get(..d.byte_offset).is_some_and(|before| {
before
.rsplit('\n')
.next()
.is_some_and(|line| line.contains("constraint"))
})
})
}
fn is_block_label_redefinition(d: &sv_lang::Diagnostic) -> bool {
d.code_name() == "Redefinition"
&& d.source.as_deref().is_some_and(|s| {
s.get(..d.byte_offset)
.is_some_and(|before| before.trim_end().ends_with(':'))
})
}
fn unknown_system_names(design: &sv_lang::Design) -> Vec<String> {
let mut names: Vec<String> = Vec::new();
for d in design.diagnostics().items() {
if d.code_name() != "UnknownSystemName" {
continue;
}
let name = d
.message
.split('\'')
.nth(1)
.map_or_else(|| d.message.clone(), str::to_owned);
if !names.contains(&name) {
names.push(name);
}
}
names
}
pub(crate) fn elab_to_run_error(e: ElabError, path: &Path) -> RunError {
RunError::Lowering(LoweringError::unsupported(
&e.to_string(),
path.display().to_string().as_str(),
))
}
pub(crate) fn instance_body_of<'d>(
nl: &netlist::Netlist<'d>,
id: InstanceId,
path: &Path,
) -> Result<Symbol<'d>, RunError> {
nl.instance_symbol(id).instance_body().ok_or_else(|| {
RunError::Lowering(LoweringError::unsupported(
"instance has no instance_body()",
path.display().to_string().as_str(),
))
})
}
fn value_target<'d>(sym: Symbol<'d>) -> Option<Symbol<'d>> {
let sym = if sym.kind() == SymbolKind::ModportPort {
sym.modport_port_internal_symbol()?
} else {
sym
};
(matches!(
sym.kind(),
SymbolKind::Net | SymbolKind::Variable | SymbolKind::ClockVar | SymbolKind::ClockingBlock
) || (sym.kind() == SymbolKind::FormalArgument
&& vj_sim::netlist::is_static_procedural_var(&sym)))
.then_some(sym)
}
fn edge_skew_clock<'d>(cv: &Symbol<'d>) -> Option<Symbol<'d>> {
let cb = cv.parent()?;
let mut skew = cv.clock_var_output_skew();
if !skew.has_value() {
skew = cb.clocking_block_default_output_skew();
}
if matches!(skew.edge, sv_lang::EdgeKind::None) {
return None;
}
let ev = cb.clocking_block_event()?.as_timing_control()?;
vj_sim::sva::sampled::clock_event_of(ev)
.ok()?
.signal
.referenced_symbol()
}
pub(crate) fn referenced_values<'d>(roots: Vec<SemNode<'d>>) -> Vec<Symbol<'d>> {
let mut out = Vec::new();
let mut seen = HashSet::new();
let mut called = HashSet::new();
let mut stack = roots;
while let Some(n) = stack.pop() {
if let Some(e) = n.as_expression() {
if vj_sim::symid::is_value_name(e.kind())
&& let Some(sym) = e.referenced_symbol()
&& seen.insert(sym.uid())
{
if sym.kind() == SymbolKind::ClockVar {
let driven = sym.initializer().and_then(|i| i.referenced_symbol());
let clock = edge_skew_clock(&sym);
let block = sym
.parent()
.filter(|p| p.kind() == SymbolKind::ClockingBlock);
for s in driven.into_iter().chain(clock).chain(block) {
if seen.insert(s.uid()) {
out.push(s);
}
}
}
if sym.kind() == SymbolKind::ModportPort
&& let Some(x) = sym.modport_port_explicit_connection()
{
stack.push(x.as_sem_node());
}
out.push(sym);
}
if e.kind() == ExpressionKind::ArbitrarySymbol
&& let Some(sym) = e.referenced_symbol()
&& matches!(sym.kind(), SymbolKind::ClockingBlock | SymbolKind::ClockVar)
&& seen.insert(sym.uid())
{
out.push(sym);
}
for sym in vj_sim::netlist::vif::member_targets(&e)
.into_iter()
.flatten()
.flatten()
{
let driven = (sym.kind() == SymbolKind::ClockVar)
.then(|| sym.initializer().and_then(|i| i.referenced_symbol()))
.flatten();
let clock = (sym.kind() == SymbolKind::ClockVar)
.then(|| edge_skew_clock(&sym))
.flatten();
let block = (sym.kind() == SymbolKind::ClockVar)
.then(|| {
sym.parent()
.filter(|p| p.kind() == SymbolKind::ClockingBlock)
})
.flatten();
for s in std::iter::once(sym).chain(driven).chain(clock).chain(block) {
if seen.insert(s.uid()) {
out.push(s);
}
}
}
if e.kind() == ExpressionKind::Call && e.is_system_call() != Some(true) {
if let Some(callee) = e.call_subroutine()
&& seen.insert(callee.uid())
{
out.push(callee);
}
let callees: Vec<Symbol<'d>> = match vj_sim::netlist::vif::call_targets(&e) {
Some((_, targets)) => targets.into_iter().flatten().collect(),
None => vj_sim::netlist::itf::call_callees(&e),
};
for callee in callees {
if called.insert(callee.uid())
&& let Some(body) = vj_sim::process::cfg::proc_body(&callee)
{
stack.push(body.as_sem_node());
for m in callee.members() {
if matches!(m.kind(), SymbolKind::FormalArgument | SymbolKind::Variable)
&& seen.insert(m.uid())
{
stack.extend(m.initializer().map(|i| i.as_sem_node()));
out.push(m);
}
}
}
}
}
}
stack.extend(vj_sim::process::cfg::live_children(&n));
}
out
}
fn reaches_class_code(roots: Vec<SemNode<'_>>) -> bool {
let mut called = HashSet::new();
let mut stack = roots;
while let Some(n) = stack.pop() {
if let Some(e) = n.as_expression() {
if e.kind() == ExpressionKind::NewClass {
return true;
}
if e.kind() == ExpressionKind::Call
&& e.is_system_call() != Some(true)
&& let Some(callee) = e.call_subroutine()
{
if callee
.parent()
.is_some_and(|p| p.kind() == SymbolKind::ClassType)
{
return true;
}
if called.insert(callee.uid())
&& let Some(body) = vj_sim::process::cfg::proc_body(&callee)
{
stack.push(body.as_sem_node());
}
}
}
stack.extend(vj_sim::process::cfg::live_children(&n));
}
false
}
fn package_of_static<'d>(var: &Symbol<'d>) -> Option<Symbol<'d>> {
if !vj_sim::netlist::is_static_procedural_var(var) {
return None;
}
let mut cur = var.parent();
while let Some(p) = cur {
if matches!(p.kind(), SymbolKind::Package | SymbolKind::CompilationUnit) {
return Some(p);
}
cur = p.parent();
}
None
}
type ForeignRef<'d> = (
Symbol<'d>,
Symbol<'d>,
InstanceId,
SignalSlot,
Option<AliasWindow>,
);
fn foreign_references<'d>(
body: &Symbol<'d>,
own: InstanceId,
instance_signals: &HashMap<InstanceId, HashMap<SymbolId, SignalSlot>>,
body_id_to_instance: &HashMap<SymbolId, InstanceId>,
ref_aliases: &RefAliases,
trees: &[sv_lang::SyntaxTree],
) -> (Vec<ForeignRef<'d>>, bool) {
let mut roots = Vec::new();
roots.extend(crate::clocking::enclosing_global_clock_for_assertions(
body, trees,
));
for m in vj_sim::netlist::module_items(body) {
let kind = m.kind();
if kind == SymbolKind::ProceduralBlock {
roots.extend(vj_sim::process::cfg::proc_body(&m).map(|b| b.as_sem_node()));
roots.extend(crate::clocking::enclosing_global_clock_event(&m, trees));
} else if kind == SymbolKind::ContinuousAssign {
roots.extend(m.continuous_assign_assignment().map(|e| e.as_sem_node()));
} else if matches!(kind, SymbolKind::Net | SymbolKind::Variable) {
roots.extend(m.initializer().map(|e| e.as_sem_node()));
}
}
let class_code = reaches_class_code(roots.clone());
let mut syms = referenced_values(roots);
if let Some(tree) = trees.iter().find(|t| body.syntax(t).is_some()) {
syms.extend(vj_codegen::assertion::default_disable_signals(body, tree));
}
let refs = foreign_references_of_symbols(
syms,
own,
instance_signals,
body_id_to_instance,
ref_aliases,
);
(refs, class_code)
}
fn foreign_references_of_roots<'d>(
roots: Vec<SemNode<'d>>,
own: InstanceId,
instance_signals: &HashMap<InstanceId, HashMap<SymbolId, SignalSlot>>,
body_id_to_instance: &HashMap<SymbolId, InstanceId>,
ref_aliases: &RefAliases,
) -> Vec<ForeignRef<'d>> {
foreign_references_of_symbols(
referenced_values(roots),
own,
instance_signals,
body_id_to_instance,
ref_aliases,
)
}
fn foreign_references_of_symbols<'d>(
syms: Vec<Symbol<'d>>,
own: InstanceId,
instance_signals: &HashMap<InstanceId, HashMap<SymbolId, SignalSlot>>,
body_id_to_instance: &HashMap<SymbolId, InstanceId>,
ref_aliases: &RefAliases,
) -> Vec<ForeignRef<'d>> {
let mut out = Vec::new();
for sym in syms {
if sym.kind() == SymbolKind::Subroutine {
let scope = vj_sim::netlist::enclosing_instance_body(&sym).or_else(|| {
vj_codegen::func::subroutine_forces(&sym)
.then(|| vj_sim::netlist::enclosing_package(&sym))
.flatten()
});
if let Some(owner) = scope.and_then(|b| body_id_to_instance.get(&b.uid()).copied())
&& owner != own
&& vj_codegen::func::subroutine_needs_handle(&sym)
&& let Some(&slot) = instance_signals[&owner]
.values()
.filter(|s| !matches!(s.class, SignalSlotClass::Memory { .. }))
.min_by_key(|s| s.slot)
{
let slot = SignalSlot {
class: SignalSlotClass::Memory {
num_rows: 1,
lower_index: 0,
},
..slot
};
out.push((sym, sym, owner, slot, None));
}
continue;
}
let Some(target) = value_target(sym) else {
continue;
};
let Some(owner) = target
.parent()
.and_then(|scope| {
if matches!(
scope.kind(),
SymbolKind::Package | SymbolKind::CompilationUnit
) {
Some(scope)
} else {
scope
.containing_instance()
.or_else(|| package_of_static(&target))
}
})
.and_then(|b| body_id_to_instance.get(&b.uid()).copied())
else {
continue;
};
if let Some(&(storage_owner, slot, window)) = ref_aliases.get(&(owner, target.uid())) {
out.push((sym, target, storage_owner, slot, window));
continue;
}
if owner == own {
if sym.kind() == SymbolKind::ModportPort
&& let Some(&slot) = instance_signals[&owner].get(&target.uid())
{
out.push((sym, target, owner, slot, None));
}
continue;
}
if let Some(&slot) = instance_signals[&owner].get(&target.uid()) {
out.push((sym, target, owner, slot, None));
}
}
out
}
type RefAliases = HashMap<(InstanceId, SymbolId), (InstanceId, SignalSlot, Option<AliasWindow>)>;
fn ref_port_aliases<'d>(
elaborated: &netlist::Netlist<'d>,
registration_order: &[InstanceId],
instance_signals: &HashMap<InstanceId, HashMap<SymbolId, SignalSlot>>,
body_id_to_instance: &HashMap<SymbolId, InstanceId>,
) -> Result<RefAliases, RunError> {
type Pending<'d> =
HashMap<(InstanceId, SymbolId), (InstanceId, SymbolId, Symbol<'d>, Option<AliasWindow>)>;
let mut pending: Pending<'d> = HashMap::new();
for &id in registration_order {
let child = elaborated.instance_symbol(id);
for conn in child.instance_port_connections() {
let port = conn.port();
if port.kind() == SymbolKind::InterfacePort
|| port.port_direction() != ArgumentDirection::Ref
{
continue;
}
let member = port.port_internal_symbol().unwrap_or(port);
let unsupported = |why: &str| {
RunError::Lowering(LoweringError::unsupported_at(
&format!("ref port `{}`: {why}", port.hierarchical_path()),
&port,
))
};
let expr = conn
.expression()
.ok_or_else(|| unsupported("a ref port is connected to no variable (23.3.3.2)"))?;
let (root_expr, window) = ref_actual_window(expr).map_err(|e| match e {
RefActualError::NonConstant => unsupported(
"the actual indexes an unpacked array with a non-constant expression; \
a ref port is bound once, at elaboration, so a select actual must \
name one fixed element. IEEE 1800-2023 23.3.3.2 says only that \
references to a ref port are hierarchical references to the connected \
variable, and 13.5.2 (pass by reference, which fixes the element when \
the call starts) covers subroutine arguments, not ports; the LRM \
does not say when the index is evaluated for a port (once at \
elaboration, or at every use as a moving alias), so no clause makes \
it unambiguous",
),
RefActualError::Packed => unsupported(
"the actual is a select of a packed value; a ref actual must be a \
variable or a member/element of an unpacked struct or array \
(IEEE 1800-2023 13.5.2, 23.3.3.2)",
),
RefActualError::Unsupported(why) => unsupported(&format!(
"the actual is {why}; not implemented for a ref port (IEEE 1800-2023 \
23.3.3.2)"
)),
})?;
let window = window
.map(|w| {
let (Ok(lo), Ok(width)) = (u32::try_from(w.lo), u32::try_from(w.width)) else {
return Err(unsupported("the selected window is too wide"));
};
Ok(AliasWindow {
row: w.row,
lo,
width,
})
})
.transpose()?;
let actual = root_expr
.referenced_symbol()
.and_then(value_target)
.ok_or_else(|| unsupported("the actual is not a variable"))?;
let owner = actual
.parent()
.and_then(|scope| {
if matches!(
scope.kind(),
SymbolKind::Package | SymbolKind::CompilationUnit
) {
Some(scope)
} else {
scope.containing_instance()
}
})
.and_then(|b| body_id_to_instance.get(&b.uid()).copied())
.ok_or_else(|| unsupported("the actual's storage is not in an instance"))?;
pending.insert((id, member.uid()), (owner, actual.uid(), port, window));
}
}
let mut aliases = RefAliases::new();
for (key, (first_owner, first_actual, port, first_window)) in &pending {
let (mut owner, mut actual) = (*first_owner, first_actual.clone());
let mut window = *first_window;
let mut hops = 0usize;
while let Some((next_owner, next_actual, _, base)) = pending.get(&(owner, actual.clone())) {
(owner, actual) = (*next_owner, next_actual.clone());
window = match (*base, window) {
(None, w) => w,
(b, None) => b,
(Some(b), Some(w)) if w.row.is_none() => Some(AliasWindow {
row: b.row,
lo: b.lo + w.lo,
width: w.width,
}),
(Some(_), Some(_)) => {
return Err(RunError::Lowering(LoweringError::unsupported_at(
&format!(
"ref port `{}`: an element of an array reached through another \
windowed ref port (IEEE 1800-2023 23.3.3.2)",
port.hierarchical_path()
),
port,
)));
}
};
hops += 1;
if hops > pending.len() {
return Err(RunError::Lowering(LoweringError::unsupported_at(
&format!(
"ref port `{}`: a cycle of ref port connections",
port.hierarchical_path()
),
port,
)));
}
}
let slot = instance_signals[&owner]
.get(&actual)
.copied()
.ok_or_else(|| {
RunError::Lowering(LoweringError::unsupported_at(
&format!(
"ref port `{}`: the actual has no storage slot",
port.hierarchical_path()
),
port,
))
})?;
let slot = match window {
None => slot,
Some(w) => {
if !matches!(slot.class, SignalSlotClass::Memory { .. })
&& vj_codegen::huge::is_huge_width(slot.width)
{
return Err(RunError::Lowering(LoweringError::unsupported_at(
&format!(
"ref port `{}`: a select of a variable wider than the wide-value \
threshold (IEEE 1800-2023 6.9.1)",
port.hierarchical_path()
),
port,
)));
}
let shape = instance_signals[&key.0]
.get(&key.1)
.copied()
.unwrap_or(slot);
SignalSlot {
slot: slot.slot,
width: w.width,
signed: shape.signed,
class: if w.width > 64 {
SignalSlotClass::Wide
} else {
SignalSlotClass::Inline
},
}
}
};
aliases.insert(key.clone(), (owner, slot, window));
}
Ok(aliases)
}
static X_INITIAL_ZERO: std::sync::atomic::AtomicBool = std::sync::atomic::AtomicBool::new(false);
pub fn set_x_initial_zero(on: bool) {
X_INITIAL_ZERO.store(on, std::sync::atomic::Ordering::Relaxed);
}
static SEED: std::sync::Mutex<Option<u64>> = std::sync::Mutex::new(None);
pub fn set_seed(seed: Option<u64>) {
*SEED.lock().unwrap_or_else(|e| e.into_inner()) = seed;
}
pub fn seed() -> Option<u64> {
*SEED.lock().unwrap_or_else(|e| e.into_inner())
}
fn native_port_connection<'d>(expr: Expression<'d>, _port: &Symbol<'d>) -> bool {
let width = |x: Expression<'d>| {
x.expr_type()
.filter(|t| t.is_integral())
.map(|t| vj_codegen::types::TypeWidth::value_width(&t))
};
let e = if expr.kind() == ExpressionKind::Assignment {
let (Some(l), Some(r)) = (expr.left(), expr.right()) else {
return false;
};
let mut inner = r;
while inner.kind() == ExpressionKind::Conversion {
match inner.operand() {
Some(o) => inner = o,
None => return false,
}
}
if width(l) != width(inner) {
return false;
}
l
} else {
expr
};
let names_signal = |x: Expression<'d>| {
x.kind() == ExpressionKind::NamedValue
&& x.referenced_symbol().is_some_and(|s| {
matches!(s.kind(), SymbolKind::Variable | SymbolKind::Net)
&& !s.parent().is_some_and(|p| {
matches!(p.kind(), SymbolKind::Package | SymbolKind::CompilationUnit)
})
})
};
match e.kind() {
ExpressionKind::NamedValue => names_signal(e),
ExpressionKind::ElementSelect | ExpressionKind::RangeSelect => {
let Some(base) = select_base(e).filter(|b| names_signal(*b)) else {
return false;
};
let Some(base_ty) = base.expr_type().filter(|t| !t.is_unpacked_array()) else {
return false;
};
let base_width = vj_codegen::types::TypeWidth::value_width(&base_ty);
vj_codegen::select::constant_window(e, base_width)
.is_ok_and(|(_, w)| Some(w) == width(e))
}
_ => false,
}
}
fn clock_port_sources(
elaborated: &netlist::Netlist<'_>,
order: &[InstanceId],
instance_signals: &HashMap<InstanceId, HashMap<SymbolId, SignalSlot>>,
resolution: &crate::nets::Resolution,
) -> crate::clocking::PortSources {
let mut out = crate::clocking::PortSources::new();
for &id in order {
let Some(parent) = elaborated.instance(id).parent else {
continue;
};
let (Some(child_signals), Some(parent_signals)) =
(instance_signals.get(&id), instance_signals.get(&parent))
else {
continue;
};
for conn in elaborated.instance_symbol(id).instance_port_connections() {
let port = conn.port();
if port.kind() == SymbolKind::InterfacePort
|| port.port_direction() != ArgumentDirection::In
|| resolution.inout_ports.contains(&(id, port.uid()))
{
continue;
}
let Some(expr) = conn.expression() else {
continue;
};
if expr.kind() != ExpressionKind::NamedValue || !native_port_connection(expr, &port) {
continue;
}
let Some(src) = expr.referenced_symbol().filter(|s| {
s.kind() == SymbolKind::Variable || out.contains_key(&(parent, s.uid()))
}) else {
continue;
};
let member = port.port_internal_symbol().unwrap_or(port);
let (Some(&cs), Some(&ps)) = (
child_signals.get(&member.uid()),
parent_signals.get(&src.uid()),
) else {
continue;
};
if cs.width != ps.width
|| matches!(cs.class, SignalSlotClass::Memory { .. })
|| matches!(ps.class, SignalSlotClass::Memory { .. })
{
continue;
}
out.insert((id, member.uid()), (parent, src.uid(), ps.slot));
}
}
out
}
fn select_base(mut e: Expression<'_>) -> Option<Expression<'_>> {
while matches!(
e.kind(),
ExpressionKind::ElementSelect | ExpressionKind::RangeSelect
) {
e = e.select_value()?;
}
Some(e)
}
fn compiled_port_connections<'d>(inst: &Symbol<'d>) -> Vec<(Symbol<'d>, Expression<'d>)> {
inst.instance_port_connections()
.filter_map(|conn| {
let port = conn.port();
if port.kind() == SymbolKind::InterfacePort
|| !matches!(
port.port_direction(),
ArgumentDirection::In | ArgumentDirection::Out
)
{
return None;
}
let expr = conn.expression()?;
if crate::nets::connects_interconnect(&port, expr) {
return None;
}
(!native_port_connection(expr, &port)).then_some((port, expr))
})
.collect()
}
pub(crate) fn place_class(class: SignalSlotClass) -> vj_sim::image::PlaceClass {
use vj_sim::image::PlaceClass;
match class {
SignalSlotClass::Inline => PlaceClass::Inline,
SignalSlotClass::Wide => PlaceClass::Wide,
SignalSlotClass::Memory { num_rows, .. } => PlaceClass::Memory { rows: num_rows },
}
}
fn place_of(instance: InstanceId, slot: SignalSlot) -> vj_sim::image::PlaceImage {
vj_sim::image::PlaceImage {
instance,
slot: slot.slot,
width: slot.width,
class: place_class(slot.class),
}
}
pub fn run_design(path: &Path, stdout: &mut dyn Write) -> Result<i32, RunError> {
run_design_with_plusargs(path, &[], stdout)
}
pub fn run_design_with_plusargs(
path: &Path,
plusargs: &[String],
stdout: &mut dyn Write,
) -> Result<i32, RunError> {
run_design_with_options(path, plusargs, TimingOptions::default(), stdout)
}
pub fn run_design_with_options(
path: &Path,
plusargs: &[String],
timing: TimingOptions,
stdout: &mut dyn Write,
) -> Result<i32, RunError> {
run_sources_with_options(&SourceSet::single(path), plusargs, timing, stdout)
}
pub fn run_sources_with_options(
sources: &SourceSet,
plusargs: &[String],
timing: TimingOptions,
stdout: &mut dyn Write,
) -> Result<i32, RunError> {
run_sources_core(sources, plusargs, timing, stdout, None)
}
struct AotRequest<'a> {
out_obj: &'a Path,
with_main: bool,
image: &'a mut Option<vj_sim::image::DesignImage>,
}
pub fn build_aot_object(
sources: &SourceSet,
timing: TimingOptions,
out_obj: &Path,
) -> Result<vj_sim::image::DesignImage, RunError> {
build_aot_object_with(sources, timing, false, out_obj)
}
pub fn build_aot_object_with(
sources: &SourceSet,
timing: TimingOptions,
with_main: bool,
out_obj: &Path,
) -> Result<vj_sim::image::DesignImage, RunError> {
let mut image = None;
run_sources_core(
sources,
&[],
timing,
&mut std::io::sink(),
Some(AotRequest {
out_obj,
with_main,
image: &mut image,
}),
)?;
image.ok_or_else(|| {
RunError::Jit("build-aot: the planner finished without producing a design image".into())
})
}
fn run_sources_core(
sources: &SourceSet,
plusargs: &[String],
timing: TimingOptions,
stdout: &mut dyn Write,
aot: Option<AotRequest<'_>>,
) -> Result<i32, RunError> {
let path = sources.label();
set_plusargs_for_run(plusargs.to_vec());
reset_seed_for_new_run();
if let Some(n) = seed() {
vj_sim::sched::process_rng::set_base_seed(n);
}
reset_mcd_table_for_new_run();
crate::dpi::reset_for_new_run();
vj_sim::sched::reset_cells_for_new_run();
vj_runtime::severity::reset_error_count();
crate::vpi::api::begin_run(plusargs);
let vpi_libraries = crate::vpi::loader::take_pending_libraries();
if !vpi_libraries.is_empty() {
crate::nets::track_strengths();
}
crate::vpi::loader::load(&vpi_libraries).map_err(|e| RunError::Vpi(e.to_string()))?;
vj_codegen::vpi_dyn::set_enabled(!vpi_libraries.is_empty());
vj_codegen::stmtcov::set_enabled(vj_runtime::codecov::enabled());
vj_sim::sched::set_thread_hook(
(!vpi_libraries.is_empty()).then_some(crate::vpi::dynobj::thread_hook as _),
);
vj_exec::verif::set_vpi_hooks(
(!vpi_libraries.is_empty()).then(|| Box::new(crate::vpi::sim::Hooks) as _),
);
let (mut design, systf_codegen) = {
let mut compiled = crate::sources::compile(sources)?;
let unknown = unknown_system_names(&compiled.0);
if !unknown.is_empty() && crate::vpi::callbacks::resolve_unresolved_systfs(&unknown) {
compiled = crate::sources::compile(sources)?;
}
let unknown = unknown_system_names(&compiled.0);
if !unknown.is_empty() {
return Err(RunError::Diagnostics(format!(
"the design calls system tasks or functions that are neither built in nor \
registered by a VPI library: {}\n{}",
unknown.join(", "),
compiled.0.diagnostics().rendered()
)));
}
compiled
};
vj_sim::process::cfg::set_source_session(Some(design.session().clone()));
if design.diagnostics().has_errors() {
return Err(RunError::Diagnostics(design.diagnostics().to_string()));
}
if let Some(text) = lrm_errors_slang_only_warns(&design) {
return Err(RunError::Diagnostics(text));
}
for d in design.diagnostics().items() {
let (tag, task) = if d.message.starts_with("$warning encountered") {
("WARNING", "$warning encountered")
} else if d.message.starts_with("$info encountered") {
("INFO", "$info encountered")
} else {
continue;
};
let text = d.message[task.len()..].trim_start_matches(':').trim_start();
let file = std::fs::canonicalize(&d.file)
.map_or_else(|_| d.file.clone(), |p| p.display().to_string());
let _ = writeln!(stdout, "{tag}: {file}:{}: {text}", d.line);
}
if let Some((path, _)) = vj_sim::netlist::itf::invalid_impl_bodies(&design)
.into_iter()
.next()
{
return Err(RunError::Diagnostics(format!(
"error: the body of the exported interface method `{path}` does not elaborate: \
it names something its modport does not expose, or otherwise misuses the \
interface (IEEE 1800-2023 25.7, 25.5)\n"
)));
}
crate::constraint_decls::check_single_external_blocks(&design.syntax_trees())
.map_err(RunError::Diagnostics)?;
crate::decl_checks::check_udp_tables(&design.syntax_trees()).map_err(RunError::Diagnostics)?;
crate::decl_checks::check_nonansi_port_ranges(&design.syntax_trees())
.map_err(RunError::Diagnostics)?;
for w in crate::decl_checks::check_dpi_spec_strings(&design.syntax_trees())
.map_err(RunError::Diagnostics)?
{
eprint!("{w}");
}
vj_codegen::let_order::check_let_definition_order(&design.syntax_trees())
.map_err(RunError::Diagnostics)?;
let export_names: Vec<String> = design.dpi_exports().map(|e| e.c_identifier()).collect();
let dpi_libraries = crate::dpi::take_pending_libraries();
if aot.is_none() {
crate::dpi::load_export_trampolines(&export_names).map_err(RunError::Jit)?;
if !dpi_libraries.is_empty() {
crate::dpi::load_svdpi_support().map_err(RunError::Jit)?;
}
crate::dpi::load_libraries(&dpi_libraries).map_err(RunError::Jit)?;
}
let comb_sensitivity = build_comb_sensitivity_index(&design).map_err(RunError::Lowering)?;
let vpi_model = crate::vpi::build::build_model(&mut design, &path.to_string_lossy())
.map_err(|e| RunError::Vpi(e.to_string()))?;
crate::vpi::state::install_model(vpi_model);
let eval = design.eval_session();
let elaborated = netlist::elaborate(&eval).map_err(|e| elab_to_run_error(e, path))?;
if elaborated.top.is_empty() && aot.is_none() {
return Ok(0);
}
if elaborated.top.is_empty() {
return Err(RunError::AotRefused {
feature: "no-top-module".to_owned(),
reason: "design has no top-level module instance".to_owned(),
at: format!("{}:1", path.display()),
});
}
let registration_order = elaborated.registration_order();
let (virtual_tasks, class_uids) = {
let mut tys = netlist::collect_new_class_types(&eval);
let ancestors: Vec<sv_lang::Type<'_>> =
tys.iter().flat_map(|t| netlist::base_chain(*t)).collect();
tys.extend(ancestors);
let ids = netlist::assign_class_template_ids(&tys);
let mut seen = HashSet::new();
let classes: Vec<(u32, sv_lang::Type<'_>)> = tys
.iter()
.zip(ids.iter())
.filter(|(_, id)| seen.insert(id.0))
.map(|(ty, id)| (id.0, *ty))
.collect();
let class_uids: Vec<(u32, String)> = {
use vj_sim::symid::SymbolUid as _;
classes
.iter()
.map(|(id, ty)| (*id, format!("{:?}", ty.as_symbol().uid())))
.collect()
};
(
std::rc::Rc::new(vj_sim::process::cfg::VirtualTasks::new(classes)),
class_uids,
)
};
vj_sim::process::cfg::set_suspending_virtuals(&virtual_tasks);
vj_sim::process::cfg::set_explicit_super_detector(Some(
vj_codegen::classes::call_is_explicit_super,
));
let _super_syntax = vj_codegen::classes::DesignSyntaxScope::enter(eval.design());
let templates = TemplateRegistry::new();
let mut registered_templates: HashSet<TemplateKey> = HashSet::new();
for &id in ®istration_order {
let key = *elaborated.dedup_key(elaborated.instance(id).template);
if registered_templates.insert(key) {
let body = instance_body_of(&elaborated, id, path)?;
let layout = build_template_layout(&body).map_err(|e| {
RunError::Lowering(LoweringError::unsupported(
&format!("state layout error: {e:?}"),
path.display().to_string().as_str(),
))
})?;
unsafe {
templates.register(key, layout);
}
}
}
let mut packages: Vec<(InstanceId, TemplateKey, Symbol<'_>)> = Vec::new();
let compilation = eval
.design()
.packages()
.chain(eval.design().compilation_units());
for pkg in compilation {
if !vj_sim::netlist::state_items(&pkg)
.iter()
.any(|m| m.kind() == SymbolKind::Variable)
{
continue;
}
let n = u32::MAX - packages.len() as u32;
let key = TemplateKey {
definition: n,
params_hash: 0,
};
let layout = build_template_layout(&pkg).map_err(|e| {
RunError::Lowering(LoweringError::unsupported_at(
&format!("package state layout error: {e:?}"),
&pkg,
))
})?;
unsafe {
templates.register(key, layout);
}
packages.push((InstanceId(n), key, pkg));
}
let instance_ids_by_path: HashMap<String, u32> = registration_order
.iter()
.map(|&o| (elaborated.instance_symbol(o).hierarchical_path(), o.0))
.collect();
let ext_aliases: std::sync::Arc<HashMap<String, Vec<String>>> = {
let mut m: HashMap<String, Vec<String>> = HashMap::new();
let labels = vj_sim::process::cfg::dotted_disable_labels();
for &r in ®istration_order {
let sym = elaborated.instance_symbol(r);
for label in &labels {
let path: Vec<String> = label.split('.').map(str::to_string).collect();
if let Some(tag) = vj_sim::netlist::itf::extern_disable_tag(&sym, &path) {
m.entry(tag).or_default().push(format!("{}/{label}", r.0));
}
}
}
std::sync::Arc::new(m)
};
let mut instance_path_disables: Vec<(u32, String)> = Vec::new();
let mut inert_blocks: HashSet<String> = HashSet::new();
let mut plans = Vec::new();
let mut kernel_meta: Vec<(u32, ProcessKind, InstanceId, KernelSensitivitySpec, bool)> =
Vec::new();
let mut branch_scope_labels: HashSet<String> = HashSet::new();
let mut kernel_disable_scopes: Vec<std::sync::Arc<[(u32, String, u32)]>> = Vec::new();
let mut kernel_dyn: Vec<(SymbolId, SymbolId)> = Vec::new();
let mut reactive_kernels: std::collections::HashSet<usize> = std::collections::HashSet::new();
let mut program_initial_kernels: std::collections::HashSet<usize> =
std::collections::HashSet::new();
let mut reactive_cont_assigns: std::collections::HashSet<usize> =
std::collections::HashSet::new();
let mut cont_assign_specs: Vec<ContAssignLoweringSpec> = Vec::new();
let mut cont_assign_meta: Vec<(InstanceId, Vec<SymbolId>)> = Vec::new();
let mut subroutine_specs: Vec<SubroutineLoweringSpec> = Vec::new();
let mut call_roots: Vec<SemNode> = Vec::new();
let mut instance_signals: HashMap<InstanceId, HashMap<SymbolId, SignalSlot>> = HashMap::new();
let mut body_id_to_instance: HashMap<SymbolId, InstanceId> = HashMap::new();
for &id in ®istration_order {
let body = instance_body_of(&elaborated, id, path)?;
body_id_to_instance.insert(body.uid(), id);
instance_signals.insert(id, signal_slots_of(&body)?);
}
for (id, _, pkg) in &packages {
body_id_to_instance.insert(pkg.uid(), *id);
instance_signals.insert(*id, signal_slots_of(pkg)?);
}
let syntax_trees = eval.design().syntax_trees();
let mut infos = Vec::new();
for &id in ®istration_order {
infos.push((
id,
instance_body_of(&elaborated, id, path)?,
elaborated.instance_symbol(id),
elaborated.instance(id).parent,
));
}
let user_nets =
crate::usernets::user_net_types(&eval, &infos, &instance_signals, &syntax_trees)
.map_err(|m| RunError::Lowering(LoweringError::unsupported(&m, "nettype")))?;
let bodies: Vec<(InstanceId, Symbol<'_>)> =
infos.iter().map(|(id, body, _, _)| (*id, *body)).collect();
let mut timing_model = crate::specify::discover_with(&eval, &bodies, path, &timing)?;
let iface_terminals: HashMap<InstanceId, HashMap<SymbolId, (InstanceId, SignalSlot)>> = bodies
.iter()
.map(|(id, body)| {
(
*id,
crate::specify::modport_terminals(body, &instance_signals, &body_id_to_instance),
)
})
.collect();
let timing_scope = crate::timingcheck::Scope {
own: &instance_signals,
iface: &iface_terminals,
};
let mut delay_table =
crate::timingcheck::negative_timing(&mut timing_model, &timing_scope, &timing)
.map_err(|m| RunError::Lowering(LoweringError::unsupported(&m, "timing check")))?;
let specify_dests: std::collections::HashSet<(InstanceId, vj_sim::symid::SymbolId)> =
timing_model
.paths
.iter()
.map(|p| (p.instance, p.outputs[0].0.clone()))
.collect();
let mut resolution = crate::nets::analyze(
&infos,
&instance_signals,
&syntax_trees,
&user_nets,
&specify_dests,
)
.map_err(|m| RunError::Lowering(LoweringError::unsupported(&m, "net")))?;
let bank_id = InstanceId(u32::MAX - (1 << 20));
let port_sources = clock_port_sources(
&elaborated,
®istration_order,
&instance_signals,
&resolution,
);
let mut prim_plans = crate::primitives::plan(
&eval,
&bodies,
&instance_signals,
&syntax_trees,
&mut resolution,
bank_id,
&specify_dests,
)
.map_err(|m| RunError::Lowering(LoweringError::unsupported(&m, "primitive")))?;
let trireg_decays = crate::primitives::trireg_decays(&eval, &bodies)
.map_err(|m| RunError::Lowering(LoweringError::unsupported(&m, "trireg")))?;
let trireg_charges = crate::primitives::trireg_charges(&eval, &bodies);
let mut specify_plans = crate::specify::plan(
&bodies,
&instance_signals,
&mut resolution,
bank_id,
&timing_model,
&prim_plans.natural,
&body_id_to_instance,
&prim_plans.specify_staging,
&if timing_model.paths.is_empty() {
Default::default()
} else {
crate::nets::driven_windows(&infos, &instance_signals, &syntax_trees)
},
)
.map_err(|m| RunError::Lowering(LoweringError::unsupported(&m, "specify path")))?;
let timing_check_plans = crate::timingcheck::plan(
&timing_model,
&timing_scope,
&timing,
&mut delay_table,
&mut resolution,
bank_id,
)
.map_err(|m| RunError::Lowering(LoweringError::unsupported(&m, "timing check")))?;
for warning in &delay_table.warnings {
eprintln!("{warning}");
}
let floors: Vec<u64> = timing_model
.paths
.iter()
.map(|p| {
p.inputs
.first()
.map_or(0, |i| delay_table.delay_of(p.instance, &i.0))
.max(0) as u64
})
.collect();
specify_plans.apply_floors(&floors);
let sdf_wires =
crate::primitives::calls_sdf_annotate(&syntax_trees) || !vpi_libraries.is_empty();
let sdf_kick = sdf_wires.then(|| {
let slot = resolution.bank_widths.len() as u32;
resolution.bank_widths.push(32);
crate::primitives::bank_slot(slot, 32)
});
let interconnect_table = crate::interconnect::new_table();
let bank_key = TemplateKey {
definition: u32::MAX - (1 << 20),
params_hash: 1,
};
let needs_bank = !resolution.bank_widths.is_empty()
|| !resolution.groups.is_empty()
|| !resolution.networks.is_empty();
if needs_bank {
let n = resolution.bank_widths.len();
let mut mem_rows = vec![0u32; n];
let mut fills: Vec<Vec<sv_lang::Bit>> = vec![Vec::new(); n];
for (&slot, t) in &resolution.bank_mem {
if let SignalSlotClass::Memory { num_rows, .. } = t.class {
mem_rows[slot as usize] = num_rows;
}
}
for g in &resolution.groups {
let Some(t) = g.user else { continue };
for (&(slot, _), row) in g.drivers.iter().zip(&g.driver_rows) {
if row.is_some() {
fills[slot as usize] = user_nets.types[t].init.clone();
}
}
}
for r in &specify_plans.retimers {
if r.staging.inst == bank_id.0 {
fills[r.staging.slot.slot as usize] =
vec![sv_lang::Bit::X; r.staging.slot.width as usize];
}
}
unsafe {
templates.register(
bank_key,
vj_sim::state::synthetic_z_layout_rows(&resolution.bank_widths, &mem_rows, &fills),
);
}
}
let mut kernel_slots = resolution.contrib.clone();
kernel_slots.extend(prim_plans.staged.iter().map(|(k, s)| (k.clone(), *s)));
kernel_slots.extend(
prim_plans
.hidden
.iter()
.enumerate()
.map(|(n, h)| (crate::nets::DriverKey::Hidden(h.inst, n as u32), h.cell)),
);
for key in specify_plans.staged.keys() {
if prim_plans.staged.contains_key(key) {
return Err(RunError::Lowering(LoweringError::unsupported(
&format!(
"internal: a continuous assignment's own staging slot ({key:?}) was \
claimed by BOTH `primitives::plan` and `specify::plan` -- `primitives::\
plan`'s own `specify_dests` deferral (LRM 30.6, Task 9) should make this \
unreachable"
),
"specify path",
)));
}
}
kernel_slots.extend(specify_plans.staged.iter().map(|(k, s)| (k.clone(), *s)));
let mut contrib_by_inst: HashMap<InstanceId, Vec<(crate::nets::DriverKey, u32)>> =
HashMap::new();
for (key, &slot) in &kernel_slots {
let owner = match key {
crate::nets::DriverKey::Assign(inst, _, _)
| crate::nets::DriverKey::Hidden(inst, _) => Some(*inst),
crate::nets::DriverKey::Port(child, port, _) => {
let inst = elaborated.instance_symbol(*child);
let compiled = compiled_port_connections(&inst)
.iter()
.any(|(p, _)| p.uid() == *port);
if compiled {
elaborated.instance(*child).parent
} else {
None
}
}
crate::nets::DriverKey::Prim(..) | crate::nets::DriverKey::PrimElem(..) => None,
crate::nets::DriverKey::SpecifyPath(..) => None,
crate::nets::DriverKey::ProcVar(inst, _) => Some(*inst),
};
if let Some(owner) = owner {
contrib_by_inst
.entry(owner)
.or_default()
.push((key.clone(), slot));
}
}
let bank_slot_of = |slot: u32| match resolution.bank_mem.get(&slot) {
Some(net_slot) => SignalSlot { slot, ..*net_slot },
None => crate::primitives::bank_slot(slot, resolution.bank_widths[slot as usize]),
};
let mut driver_slot: HashMap<crate::nets::DriverKey, SignalSlot> = HashMap::new();
let mut instance_refs: HashMap<InstanceId, Vec<ForeignRef<'_>>> = HashMap::new();
let ref_aliases = ref_port_aliases(
&elaborated,
®istration_order,
&instance_signals,
&body_id_to_instance,
)?;
let mut class_refs: Option<Vec<Symbol<'_>>> = None;
for &id in ®istration_order {
let body = instance_body_of(&elaborated, id, path)?;
let (mut refs, class_code) = foreign_references(
&body,
id,
&instance_signals,
&body_id_to_instance,
&ref_aliases,
&syntax_trees,
);
if class_code {
let class_syms = class_refs.get_or_insert_with(|| {
let bodies = netlist::class_method_symbols(&eval)
.into_iter()
.filter_map(|m| vj_sim::process::cfg::proc_body(&m))
.map(|b| b.as_sem_node())
.collect();
referenced_values(bodies)
});
refs.extend(foreign_references_of_symbols(
class_syms.clone(),
id,
&instance_signals,
&body_id_to_instance,
&ref_aliases,
));
for (pid, _, pkg) in &packages {
for v in vj_sim::netlist::state_items(pkg) {
if v.kind() == SymbolKind::Variable
&& let Some(&slot) = instance_signals[pid].get(&v.uid())
{
refs.push((v, v, *pid, slot, None));
}
}
}
}
instance_refs.entry(id).or_default().extend(refs);
for body_port in vj_sim::netlist::module_items(&body) {
if let Some(&(owner, slot, window)) = ref_aliases.get(&(id, body_port.uid())) {
instance_refs
.entry(id)
.or_default()
.push((body_port, body_port, owner, slot, window));
}
}
if let Some(parent) = elaborated.instance(id).parent {
for (port, expr) in compiled_port_connections(&elaborated.instance_symbol(id)) {
let refs = foreign_references_of_roots(
vec![expr.as_sem_node()],
parent,
&instance_signals,
&body_id_to_instance,
&ref_aliases,
);
instance_refs.entry(parent).or_default().extend(refs);
let member = port.port_internal_symbol().unwrap_or(port);
if let Some(&slot) = instance_signals[&id].get(&member.uid()) {
instance_refs
.entry(parent)
.or_default()
.push((member, member, id, slot, None));
}
}
}
}
for (pid, _, pkg) in &packages {
let roots = discover_variable_initializers(pkg)
.into_iter()
.map(|v| v.assign_expr.as_sem_node())
.collect();
let refs = foreign_references_of_roots(
roots,
*pid,
&instance_signals,
&body_id_to_instance,
&ref_aliases,
);
instance_refs.entry(*pid).or_default().extend(refs);
}
for cls in netlist::classes_with_dynamic_static_init(&eval) {
let Some(&first) = registration_order.first() else {
break;
};
let owner = std::iter::successors(cls.parent(), |s| s.parent())
.find_map(|anc| packages.iter().find(|(_, _, p)| p.uid() == anc.uid()))
.map_or(first, |(pid, _, _)| *pid);
let roots = netlist::classify_members(cls)
.map(|m| m.statics)
.unwrap_or_default()
.iter()
.filter_map(|s| s.initializer())
.map(|e| e.as_sem_node())
.collect();
let mut refs = foreign_references_of_roots(
roots,
owner,
&instance_signals,
&body_id_to_instance,
&ref_aliases,
);
for (pid, _, pkg) in &packages {
if *pid == owner {
continue;
}
for v in vj_sim::netlist::state_items(pkg) {
if v.kind() == SymbolKind::Variable
&& let Some(&slot) = instance_signals[pid].get(&v.uid())
{
refs.push((v, v, *pid, slot, None));
}
}
}
instance_refs.entry(owner).or_default().extend(refs);
}
type ForeignTarget = (InstanceId, u32, Option<AliasWindow>);
let mut instance_foreign: HashMap<InstanceId, (u32, Vec<ForeignTarget>)> = HashMap::new();
let foreign_owners: Vec<(InstanceId, TemplateKey)> = registration_order
.iter()
.map(|&id| (id, *elaborated.dedup_key(elaborated.instance(id).template)))
.chain(packages.iter().map(|(id, key, _)| (*id, *key)))
.collect();
let event_peers: Vec<(InstanceId, SignalSlot)> = if vj_sim::state::event_plan::group_mode() {
registration_order
.iter()
.filter_map(|&id| {
instance_signals
.get(&id)?
.get(&vj_sim::state::event_pool::pool_key())
.map(|&p| (id, p))
})
.collect()
} else {
Vec::new()
};
for (id, key) in foreign_owners {
let own = templates
.layout_of(&key)
.expect("every instance's template layout was registered above")
.slots
.len() as u32;
let refs = instance_refs.remove(&id).unwrap_or_default();
let mut contribs = contrib_by_inst.remove(&id).unwrap_or_default();
let pooled_peer = event_peers.iter().any(|&(p, _)| p == id);
if refs.is_empty() && contribs.is_empty() && !pooled_peer {
continue;
}
let mut targets: Vec<ForeignTarget> = Vec::new();
contribs.sort_by_key(|(_, slot)| *slot);
for (key, slot) in contribs {
targets.push((bank_id, slot, None));
driver_slot.insert(
key,
SignalSlot {
slot: own + targets.len() as u32 - 1,
..bank_slot_of(slot)
},
);
}
let mut added = Vec::new();
for (sym, target, owner, slot, window) in refs {
if owner == id && window.is_none() {
added.push((sym.uid(), slot));
added.push((target.uid(), slot));
continue;
}
let k = match targets
.iter()
.position(|&t| t == (owner, slot.slot, window))
{
Some(k) => k,
None => {
targets.push((owner, slot.slot, window));
targets.len() - 1
}
};
let foreign = SignalSlot {
slot: own + k as u32,
..slot
};
added.push((sym.uid(), foreign));
added.push((target.uid(), foreign));
if owner != id {
for key in [
vj_sim::state::event_pool::static_key(&target.uid()),
vj_sim::state::event_pool::pool_of_key(&target.uid()),
] {
let Some(&pool_slot) = instance_signals[&owner].get(&key) else {
continue;
};
let k = match targets
.iter()
.position(|&t| t == (owner, pool_slot.slot, None))
{
Some(k) => k,
None => {
targets.push((owner, pool_slot.slot, None));
targets.len() - 1
}
};
added.push((
key,
SignalSlot {
slot: own + k as u32,
..pool_slot
},
));
}
}
}
if pooled_peer {
for (j, &(owner, pool_slot)) in event_peers.iter().enumerate() {
let key = vj_sim::state::event_pool::peer_key(j as u32);
if owner == id {
added.push((key, pool_slot));
continue;
}
let k = match targets
.iter()
.position(|&t| t == (owner, pool_slot.slot, None))
{
Some(k) => k,
None => {
targets.push((owner, pool_slot.slot, None));
targets.len() - 1
}
};
added.push((
key,
SignalSlot {
slot: own + k as u32,
..pool_slot
},
));
}
}
instance_signals
.get_mut(&id)
.expect("filled above")
.extend(added);
instance_foreign.insert(id, (own, targets));
}
let mut specify_dest_redirect: HashMap<InstanceId, HashMap<SymbolId, SignalSlot>> =
HashMap::new();
for (key, slot) in &driver_slot {
if let crate::nets::DriverKey::ProcVar(inst, sym) = key {
specify_dest_redirect
.entry(*inst)
.or_default()
.insert(sym.clone(), *slot);
}
}
let mut next_kernel_idx: usize = 0;
let mut next_cont_assign_idx: usize = 0;
let mut cov_roots: HashMap<InstanceId, Vec<SemNode<'_>>> = HashMap::new();
for &id in ®istration_order {
let body = instance_body_of(&elaborated, id, path)?;
let processes: Vec<DiscoveredProcess<'_>> =
discover_processes_dispatching(&body, &comb_sensitivity, Some(&virtual_tasks))
.map_err(RunError::Lowering)?;
let signals = instance_signals[&id].clone();
let procedural_checkers = vj_sim::netlist::procedural_checkers(&body);
let default_clocking = crate::clocking::default_clocking(&body, &syntax_trees);
let nested_assertions: HashMap<String, usize> = processes
.iter()
.enumerate()
.filter_map(|(j, p)| {
let h = p.assertion.filter(|h| h.procedural)?;
Some((h.queue_key(&p.sym), next_kernel_idx + j))
})
.collect();
let scopes: Vec<Option<DisableScope>> = processes
.iter()
.enumerate()
.map(|(j, p)| {
if p.assertion.is_some() {
return None;
}
let root = vj_sim::process::cfg::proc_body(&p.sym)?;
let queued: Vec<(usize, Statement<'_>)> = processes
.iter()
.enumerate()
.filter_map(|(k, h)| {
let h = h.assertion.filter(|h| h.procedural)?;
(processes[k].sym == p.sym).then_some((next_kernel_idx + k, h.stmt))
})
.collect();
let labels = vj_sim::process::cfg::assertion_labels(&root)
.into_iter()
.filter_map(|(label, stmt)| {
let at = stmt.source_range().start;
let (key, _) = queued.iter().find(|(_, h)| h.source_range().start == at)?;
Some((label, *key))
})
.collect();
Some(DisableScope {
process: j,
block: vj_sim::process::cfg::peer_block(&root),
nested: vj_sim::process::cfg::nested_peer_blocks(&root),
paths: vj_sim::process::cfg::named_block_paths(&root),
keys: queued
.iter()
.map(|(k, _)| *k)
.chain(std::iter::once(
(next_kernel_idx + j)
| vj_sim::sched::proc_assert::PROCESS_KEY_FLAG as usize,
))
.collect(),
labels,
})
})
.collect();
{
let inst_path = elaborated.instance_symbol(id).hierarchical_path();
for s in scopes.iter().flatten() {
for b in s.block.iter().chain(&s.nested).filter(|b| !b.can_suspend) {
inert_blocks.insert(format!("{inst_path}.{}", b.name));
}
}
}
for (j, proc) in processes.iter().enumerate() {
let root = match proc.assertion.filter(|h| h.procedural) {
Some(h) => h.stmt,
None => vj_sim::process::cfg::proc_body(&proc.sym).ok_or_else(|| {
RunError::Lowering(LoweringError::unsupported_at(
"procedural block has no body statement",
&proc.sym,
))
})?,
};
call_roots.push(root.as_sem_node());
cov_roots.entry(id).or_default().push(root.as_sem_node());
if proc.assertion.is_none() {
for pc in procedural_checkers.iter().filter(|c| c.block == proc.sym) {
for block in vj_sim::netlist::module_items(&pc.instance) {
if let Some(d) = vj_codegen::module::static_deferred_assertion_of(&block) {
call_roots.push(d.as_sem_node());
}
}
}
}
if vj_codegen::force::contains_force(root.as_sem_node()) {
vj_codegen::force::note_design_forces();
}
for (site, assign) in vj_codegen::force::force_drivers(root.as_sem_node()) {
let read_set = assign
.right()
.map(vj_codegen::module::cont_assign_read_set)
.unwrap_or_default();
cont_assign_specs.push(ContAssignLoweringSpec {
fn_name: format!("vj_cont_assign_{next_cont_assign_idx}"),
fn_sym: proc.sym,
assign_expr: assign,
net_target: None,
signals: signals.clone(),
read_signals: HashMap::new(),
force_site: Some(site),
port_source: None,
var_init: false,
hidden_target: None,
});
cont_assign_meta.push((id, read_set));
next_cont_assign_idx += 1;
}
let hosts_expect = proc
.assertion
.is_some_and(|h| h.stmt.assertion_kind() == Some(sv_lang::AssertionKind::Expect));
let mut name_uses: HashMap<&str, usize> = HashMap::new();
let mut non_branch: HashSet<&str> = HashSet::new();
for s in scopes.iter().flatten() {
for b in s.block.iter().chain(&s.nested) {
*name_uses.entry(b.name.as_str()).or_default() += 1;
if !b.fork_branch {
non_branch.insert(b.name.as_str());
}
}
}
let peers: Vec<vj_sim::process::cfg::PeerBlock> = scopes
.iter()
.flatten()
.filter(|s| s.process != j)
.flat_map(|s| s.block.iter().chain(&s.nested))
.filter(|b| {
name_uses[b.name.as_str()] == 1 || !non_branch.contains(b.name.as_str())
})
.cloned()
.collect();
let checker_clock = crate::clocking::checker_inferred_clock(&proc.sym, &syntax_trees)
.map_err(|msg| {
RunError::Lowering(LoweringError::unsupported_at(&msg, &proc.sym))
})?;
let cfg = if hosts_expect {
vj_sim::process::cfg::build_cfg_hosted_assertion(&root, default_clocking)
} else {
vj_sim::process::cfg::build_cfg_scoped_with_virtuals(
&root,
loop_kind_of(proc.kind),
default_clocking,
&peers,
checker_clock,
crate::clocking::global_clock_event(&proc.sym, &syntax_trees).map_err(
|msg| RunError::Lowering(LoweringError::unsupported_at(&msg, &proc.sym)),
)?,
Some(virtual_tasks.clone()),
)
}
.map_err(|e| sim_lowering_to_run_error(e, path))?;
let frame = compute_liveness(&cfg).map_err(|e| sim_lowering_to_run_error(e, path))?;
let frame_words = total_frame_words(&frame);
let segments = lower_to_segments(&cfg, &frame);
let mut proc_signals = signals.clone();
for (formal, actual) in &cfg.ref_bindings {
let slot = signals.get(&actual.uid()).cloned().ok_or_else(|| {
RunError::Lowering(LoweringError::unsupported_at(
&format!(
"`ref` argument `{}` is bound to `{}`, which is not storage of this \
instance",
formal.name(),
actual.name()
),
&proc.sym,
))
})?;
proc_signals.insert(formal.uid(), slot);
}
let mut plan = build_lowering_plan(
format!("vj_proc_{next_kernel_idx}"),
proc.sym,
segments,
frame,
proc_signals,
);
plan.owning_instance = id;
plan.disable_broadcast = cfg.disable_broadcast.clone();
for (label, _) in cfg
.disable_broadcast
.iter()
.cloned()
.chain(cfg.branch_disable_broadcast())
{
if label.contains('.') {
instance_path_disables.push((id.0, label.clone()));
}
}
let own_path = elaborated.instance_symbol(id).hierarchical_path();
let ancestors: Vec<(u32, String)> = {
let segs: Vec<&str> = own_path.split('.').collect();
(1..segs.len())
.filter_map(|k| {
let anc = segs[..k].join(".");
let aid = instance_ids_by_path.get(&anc)?;
Some((*aid, segs[k..].join(".")))
})
.collect()
};
let scope_keys = std::sync::Arc::new(vj_codegen::process_lower::DisableScopeKeys {
instance: id.0,
own_path: own_path.clone(),
ancestors,
ext_aliases: ext_aliases.clone(),
});
let mut scope_rows: Vec<(u32, String, u32)> = cfg
.disable_scopes
.iter()
.flat_map(|(resume, label, exit)| scope_keys.expand(resume.0, label, exit.0))
.collect();
let proc_path = proc.sym.hierarchical_path();
if let Some(gen_path) = proc_path
.strip_prefix(own_path.as_str())
.and_then(|r| r.strip_prefix('.'))
.filter(|g| !g.is_empty())
{
let rows: Vec<(u32, String, u32)> = cfg
.disable_scopes
.iter()
.map(|(r, l, e)| (r.0, l.clone(), e.0))
.collect();
for (resume, label, exit) in rows {
scope_rows.extend(scope_keys.expand(
resume,
&format!("{gen_path}.{label}"),
exit,
));
}
}
if let Some(own) = &scopes[j] {
let mut enclosing: Vec<String> = own
.block
.iter()
.chain(&own.nested)
.map(|b| b.name.clone())
.collect();
if let Some(o) = &own.block {
enclosing.extend(own.nested.iter().map(|n| format!("{}.{}", o.name, n.name)));
}
let labels = cfg.branch_disable_labels();
for l in &labels {
for e in &enclosing {
scope_rows.extend(scope_keys.expand(u32::MAX, &format!("{e}.{l}"), 0));
}
}
for p in &own.paths {
if p.rsplit('.')
.next()
.is_some_and(|leaf| labels.iter().any(|l| l == leaf))
{
scope_rows.extend(scope_keys.expand(u32::MAX, p, 0));
}
}
}
kernel_disable_scopes.push(scope_rows.into());
branch_scope_labels.extend(cfg.branch_disable_labels().into_iter().flat_map(|l| {
scope_keys
.expand(0, &l, 0)
.into_iter()
.map(|(_, k, _)| k)
.collect::<Vec<_>>()
}));
plan.disable_scope_keys = Some(scope_keys);
plan.hosted_assertion = proc.assertion;
let mut read_set = proc.read_set.clone();
if proc.assertion.is_none() {
plan.inferred_disable =
vj_codegen::module::procedure_inferred_disable(&proc.sym, &syntax_trees)
.map_err(RunError::Lowering)?
.map(|state| state.map(|(sym, negate)| (sym.uid(), negate)));
if let Some(Some((id, _))) = &plan.inferred_disable
&& matches!(
proc.kind,
SliceProcKind::AlwaysComb | SliceProcKind::AlwaysLatch
)
&& !read_set.contains(id)
{
read_set.push(id.clone());
}
}
if proc.assertion.is_none() {
plan.nested_assertions = nested_assertions.clone();
}
if let Some(own) = &scopes[j] {
for other in scopes.iter().flatten().filter(|s| s.process != j) {
if let Some(block) = &other.block {
plan.disable_keys
.insert(block.name.clone(), other.keys.clone());
}
for block in &other.nested {
plan.disable_keys.entry(block.name.clone()).or_default();
}
}
if let Some(block) = &own.block {
plan.disable_keys
.insert(block.name.clone(), own.keys.clone());
}
for (label, key) in &own.labels {
plan.disable_keys.insert(label.clone(), vec![*key]);
}
}
plan.specify_dest_redirect =
specify_dest_redirect.get(&id).cloned().unwrap_or_default();
let reads_inferred_disable = plan.inferred_disable.is_some();
plans.push(plan);
let reset = proc
.reset
.map(|(sym, edge)| -> Result<_, RunError> {
let converted = to_state_edge_kind(edge, &sym).map_err(RunError::Lowering)?;
Ok((sym.uid(), converted))
})
.transpose()?;
let windows = if matches!(
proc.kind,
SliceProcKind::AlwaysComb | SliceProcKind::AlwaysLatch
) && proc.assertion.is_none()
&& nested_assertions.is_empty()
&& !reads_inferred_disable
{
vj_codegen::process_lower::comb_read_windows(&root, &signals)
} else {
HashMap::new()
};
let sensitivity_spec = KernelSensitivitySpec {
read_set,
windows,
reset,
};
kernel_meta.push((
frame_words,
scheduler_process_kind(proc.kind),
id,
sensitivity_spec,
vj_codegen::module::has_implicit_event_head(&proc.sym),
));
kernel_dyn.push((body.uid(), proc.sym.uid()));
if vj_sim::netlist::is_in_checker(&proc.sym)
|| vj_sim::netlist::is_in_program(&proc.sym)
{
reactive_kernels.insert(next_kernel_idx);
if vj_sim::netlist::is_in_program(&proc.sym)
&& matches!(proc.kind, SliceProcKind::Initial)
{
program_initial_kernels.insert(next_kernel_idx);
}
}
next_kernel_idx += 1;
}
let cont_assigns = discover_cont_assigns(&body).map_err(RunError::Lowering)?;
let var_inits = discover_variable_initializers(&body);
for (ca, var_init) in var_inits
.iter()
.map(|v| (v, true))
.chain(cont_assigns.iter().map(|c| (c, false)))
{
let mut signals = signals.clone();
let roots: Vec<Symbol<'_>> = match ca.net_target {
Some(net) => vec![net],
None => ca
.assign_expr
.left()
.map(crate::nets::lvalue_roots)
.unwrap_or_default(),
};
let mut read_signals: HashMap<SymbolId, SignalSlot> = HashMap::new();
for root in roots {
let key = crate::nets::DriverKey::Assign(id, ca.sym.uid(), root.uid());
if let Some(&slot) = driver_slot.get(&key) {
if let Some(&own) = signals.get(&root.uid()) {
read_signals.insert(root.uid(), own);
}
signals.insert(root.uid(), slot);
}
}
call_roots.push(ca.assign_expr.as_sem_node());
cont_assign_specs.push(ContAssignLoweringSpec {
fn_name: format!("vj_cont_assign_{next_cont_assign_idx}"),
fn_sym: ca.sym,
assign_expr: ca.assign_expr,
net_target: ca.net_target,
signals: signals.clone(),
read_signals,
force_site: None,
port_source: None,
var_init,
hidden_target: None,
});
cont_assign_meta.push((id, ca.read_set.clone()));
if vj_sim::netlist::is_in_checker(&ca.sym) || vj_sim::netlist::is_in_program(&ca.sym) {
reactive_cont_assigns.insert(next_cont_assign_idx);
}
next_cont_assign_idx += 1;
}
for (n, h) in prim_plans.hidden.iter().enumerate() {
if h.inst != id {
continue;
}
let slot = driver_slot[&crate::nets::DriverKey::Hidden(id, n as u32)];
call_roots.push(h.expr.as_sem_node());
cont_assign_specs.push(ContAssignLoweringSpec {
fn_name: format!("vj_cont_assign_{next_cont_assign_idx}"),
fn_sym: h.prim,
assign_expr: h.expr,
net_target: None,
signals: signals.clone(),
read_signals: HashMap::new(),
force_site: None,
port_source: None,
var_init: false,
hidden_target: Some(SignalSlot {
signed: false,
..slot
}),
});
cont_assign_meta.push((id, vj_codegen::module::cont_assign_read_set(h.expr)));
next_cont_assign_idx += 1;
}
for checker in vj_sim::netlist::checker_instances(&body) {
for out in vj_sim::netlist::checker_outputs(&checker) {
let Some(assign) = out.assign else {
continue;
};
call_roots.push(assign.as_sem_node());
let mut out_signals = signals.clone();
for root in assign
.left()
.map(crate::nets::lvalue_roots)
.unwrap_or_default()
{
let key = crate::nets::DriverKey::Assign(id, out.formal.uid(), root.uid());
if let Some(&slot) = driver_slot.get(&key) {
out_signals.insert(root.uid(), slot);
}
}
cont_assign_specs.push(ContAssignLoweringSpec {
fn_name: format!("vj_cont_assign_{next_cont_assign_idx}"),
fn_sym: out.formal,
assign_expr: assign,
net_target: None,
signals: out_signals,
read_signals: HashMap::new(),
force_site: None,
port_source: Some(out.formal),
var_init: false,
hidden_target: None,
});
cont_assign_meta.push((id, vec![out.formal.uid()]));
reactive_cont_assigns.insert(next_cont_assign_idx);
next_cont_assign_idx += 1;
}
}
if let Some(parent) = elaborated.instance(id).parent {
let inst = elaborated.instance_symbol(id);
for (port, expr) in compiled_port_connections(&inst) {
call_roots.push(expr.as_sem_node());
let member = port.port_internal_symbol().unwrap_or(port);
let input = port.port_direction() == ArgumentDirection::In;
let mut port_signals = instance_signals[&parent].clone();
let targets: Vec<Symbol<'_>> = if input {
vec![member]
} else {
expr.left()
.map(crate::nets::lvalue_roots)
.unwrap_or_default()
};
for t in targets {
let key = crate::nets::DriverKey::Port(id, port.uid(), t.uid());
if let Some(&slot) = driver_slot.get(&key) {
port_signals.insert(t.uid(), slot);
}
}
let read_set = if input {
vj_codegen::module::cont_assign_read_set(expr)
} else {
vec![member.uid()]
};
cont_assign_specs.push(ContAssignLoweringSpec {
fn_name: format!("vj_cont_assign_{next_cont_assign_idx}"),
fn_sym: port,
assign_expr: expr,
net_target: input.then_some(member),
signals: port_signals,
read_signals: HashMap::new(),
force_site: None,
port_source: (!input).then_some(member),
var_init: false,
hidden_target: None,
});
cont_assign_meta.push((parent, read_set));
next_cont_assign_idx += 1;
}
}
}
for (pid, _, pkg) in &packages {
for v in discover_variable_initializers(pkg) {
call_roots.push(v.assign_expr.as_sem_node());
cont_assign_specs.push(ContAssignLoweringSpec {
fn_name: format!("vj_cont_assign_{next_cont_assign_idx}"),
fn_sym: v.sym,
assign_expr: v.assign_expr,
net_target: v.net_target,
signals: instance_signals[pid].clone(),
read_signals: HashMap::new(),
force_site: None,
port_source: None,
var_init: true,
hidden_target: None,
});
cont_assign_meta.push((*pid, Vec::new()));
next_cont_assign_idx += 1;
}
}
for cls in netlist::classes_with_dynamic_static_init(&eval) {
let Some(&first) = registration_order.first() else {
break;
};
let owner = std::iter::successors(cls.parent(), |s| s.parent())
.find_map(|anc| packages.iter().find(|(_, _, p)| p.uid() == anc.uid()))
.map_or(first, |(pid, _, _)| *pid);
let statics = netlist::classify_members(cls)
.map(|m| m.statics)
.unwrap_or_default();
for init in statics.iter().filter_map(|s| s.initializer()) {
call_roots.push(init.as_sem_node());
}
let Some((prop, init)) = statics.iter().find_map(|s| {
s.initializer()
.filter(|e| e.constant_value().is_none())
.map(|e| (*s, e))
}) else {
continue;
};
cont_assign_specs.push(ContAssignLoweringSpec {
fn_name: format!("vj_cont_assign_{next_cont_assign_idx}"),
fn_sym: prop,
assign_expr: init,
net_target: Some(prop),
signals: instance_signals[&owner].clone(),
read_signals: HashMap::new(),
force_site: None,
port_source: None,
var_init: true,
hidden_target: None,
});
cont_assign_meta.push((owner, Vec::new()));
next_cont_assign_idx += 1;
}
let cov_packages: Vec<_> = eval.design().packages().collect();
let cov_units: Vec<_> = eval.design().compilation_units().collect();
let mut cov_plan = crate::covergroups::plan(
&bodies,
&cov_packages,
&cov_units,
&instance_signals,
&cov_roots,
&syntax_trees,
)
.map_err(RunError::Lowering)?;
cov_plan.specs.extend(
crate::clocking::gate_specs(&bodies, &instance_signals, &syntax_trees)
.map_err(RunError::Lowering)?,
);
for spec in &cov_plan.specs {
call_roots.extend(spec.kernels.iter().map(|k| k.expr.as_sem_node()));
call_roots.extend(spec.gates.iter().map(|g| g.as_sem_node()));
}
let (mut called, inlined) =
discover_called_subroutines_inlined(call_roots.clone(), &mut HashSet::new());
let mut force_added = false;
for sub in called.iter().map(|c| c.sym).chain(inlined.iter().copied()) {
let Some(body) = vj_sim::process::cfg::proc_body(&sub) else {
continue;
};
if vj_codegen::force::contains_force(body.as_sem_node()) {
vj_codegen::force::note_design_forces();
}
let Some(&id) = vj_sim::netlist::enclosing_instance_body(&sub)
.or_else(|| vj_sim::netlist::enclosing_package(&sub))
.and_then(|b| body_id_to_instance.get(&b.uid()))
else {
continue;
};
for (site, assign) in vj_codegen::force::force_drivers(body.as_sem_node()) {
let read_set = assign
.right()
.map(vj_codegen::module::cont_assign_read_set)
.unwrap_or_default();
if let Some(r) = assign.right() {
call_roots.push(r.as_sem_node());
force_added = true;
}
cont_assign_specs.push(ContAssignLoweringSpec {
fn_name: format!("vj_cont_assign_{next_cont_assign_idx}"),
fn_sym: sub,
assign_expr: assign,
net_target: None,
signals: instance_signals[&id].clone(),
read_signals: HashMap::new(),
force_site: Some(site),
port_source: None,
var_init: false,
hidden_target: None,
});
cont_assign_meta.push((id, read_set));
next_cont_assign_idx += 1;
}
}
if force_added {
called = discover_called_subroutines(call_roots);
}
for sub in called {
subroutine_specs.push(SubroutineLoweringSpec {
sym: sub.sym,
nettype_resolver: false,
});
}
for t in &user_nets.types {
if let Some(f) = t.func {
subroutine_specs.push(SubroutineLoweringSpec {
sym: f,
nettype_resolver: true,
});
}
}
let bindings = RealBindings {
get_scalar: Some(vj_sim::state::real_sv_get_scalar as *const () as usize),
set_scalar: Some(real_sv_set_scalar as *const () as usize),
get_wide: Some(vj_sim::state::real_sv_get_wide as *const () as usize),
set_wide: Some(real_sv_set_wide as *const () as usize),
get_mem_word: Some(vj_sim::state::real_sv_get_mem_word as *const () as usize),
set_mem_word: Some(real_sv_set_mem_word as *const () as usize),
nba_enqueue: Some(real_nba_enqueue as *const () as usize),
display: Some(real_sv_rt_display as *const () as usize),
write: Some(real_sv_rt_write as *const () as usize),
finish: Some(real_sv_rt_finish as *const () as usize),
disable_fork: Some(real_sv_rt_disable_fork as *const () as usize),
disable_fork_block: Some(
vj_sim::sched::real_sv_rt_disable_fork_block as *const () as usize,
),
disable_named: Some(vj_sim::sched::real_sv_rt_disable_named as *const () as usize),
nba_masked: Some(vj_sim::sched::real_sv_rt_nba_masked as *const () as usize),
nba_pulse: Some(vj_sim::sched::real_sv_rt_nba_pulse as *const () as usize),
reactive_drive: Some(vj_sim::sched::real_sv_rt_reactive_drive as *const () as usize),
nba_coll: Some(vj_sim::sched::real_sv_rt_nba_coll as *const () as usize),
set_delay: Some(vj_sim::sched::real_sv_rt_set_delay as *const () as usize),
fired_term: Some(vj_sim::sched::real_sv_rt_fired_term as *const () as usize),
fired_mask: Some(vj_sim::sched::real_sv_rt_fired_mask as *const () as usize),
proc_assert: Some(vj_sim::sched::proc_assert::real_sv_rt_proc_assert as *const () as usize),
assert_input: Some(
vj_sim::sched::proc_assert::real_sv_rt_assert_input as *const () as usize,
),
proc_assert_disable: Some(
vj_sim::sched::proc_assert::real_sv_rt_proc_assert_disable as *const () as usize,
),
slot_owner: Some(vj_sim::state::real_sv_rt_slot_owner as *const () as usize),
inline_ptrs: Some(vj_sim::state::real_sv_rt_inline_ptrs as *const () as usize),
aborted: Some(vj_sim::sched::real_sv_rt_aborted as *const () as usize),
cell_alloc: Some(vj_sim::sched::real_sv_rt_cell_alloc as *const () as usize),
cell_store: Some(vj_sim::sched::real_sv_rt_cell_store as *const () as usize),
edge_snapshot: Some(vj_sim::sched::real_sv_rt_edge_snapshot as *const () as usize),
edge_check: Some(vj_sim::sched::real_sv_rt_edge_check as *const () as usize),
edge_load: Some(vj_sim::sched::real_sv_rt_edge_load as *const () as usize),
set_fired_term: Some(vj_sim::sched::real_sv_rt_set_fired_term as *const () as usize),
deferred_begin: Some(
vj_runtime::display_sink::real_sv_rt_deferred_begin as *const () as usize,
),
deferred_end: Some(vj_runtime::display_sink::real_sv_rt_deferred_end as *const () as usize),
deferred_call: Some(
vj_runtime::display_sink::real_sv_rt_deferred_call as *const () as usize,
),
assert_ctl: Some(
vj_sim::sched::assert_control::real_sv_rt_assert_ctl as *const () as usize,
),
fscan: Some(vj_runtime::file_io_sink::real_sv_rt_fscan as *const () as usize),
sscan: Some(vj_runtime::file_io_sink::real_sv_rt_sscan as *const () as usize),
fgets: Some(vj_runtime::file_io_sink::real_sv_rt_fgets as *const () as usize),
fgetc: Some(vj_runtime::file_io_sink::real_sv_rt_fgetc as *const () as usize),
ungetc: Some(vj_runtime::file_io_sink::real_sv_rt_ungetc as *const () as usize),
ftell: Some(vj_runtime::file_io_sink::real_sv_rt_ftell as *const () as usize),
ferror: Some(vj_runtime::file_io_sink::real_sv_rt_ferror as *const () as usize),
force: Some(vj_sim::sched::real_sv_rt_force as *const () as usize),
release: Some(vj_sim::sched::real_sv_rt_release as *const () as usize),
force_active: Some(vj_sim::sched::real_sv_rt_force_active as *const () as usize),
force_row: Some(vj_sim::sched::real_sv_rt_force_row as *const () as usize),
readmem_rows: Some(vj_runtime::readmem::sv_rt_readmem_rows as *const () as usize),
strobe_postponed: Some(vj_sim::sched::real_sv_rt_strobe as *const () as usize),
monitor_start: Some(vj_sim::sched::real_sv_rt_monitor as *const () as usize),
monitor_enable: Some(vj_sim::sched::real_sv_rt_monitor_enable as *const () as usize),
time: Some(real_sv_rt_time as *const () as usize),
dumpfile: Some(crate::dump::sv_rt_dump_file as *const () as usize),
dumpflush: Some(crate::dump::sv_rt_dump_flush as *const () as usize),
dump_vars: Some(crate::dump::sv_rt_dump_vars as *const () as usize),
dump_ctl: Some(crate::dump::sv_rt_dump_ctl as *const () as usize),
dump_ports: Some(crate::dump::sv_rt_dump_ports as *const () as usize),
timeformat: Some(vj_runtime::timefmt::sv_rt_timeformat as *const () as usize),
real_math: Some(vj_runtime::real_math::sv_rt_real_math as *const () as usize),
dpi_set_scope: Some(crate::dpi::sv_rt_dpi_set_scope as *const () as usize),
dpi_sync: Some(crate::dpi::sv_rt_dpi_sync as *const () as usize),
dpi_export_handle: Some(crate::dpi::sv_rt_dpi_export_handle as *const () as usize),
dpi_export_variant: Some(crate::dpi::sv_rt_dpi_export_variant as *const () as usize),
str_intern: Some(vj_runtime::strings_rt::sv_rt_str_intern as *const () as usize),
str_op: Some(vj_runtime::strings_rt::sv_rt_str_op as *const () as usize),
coll: Some(vj_runtime::collections_rt::sv_rt_coll as *const () as usize),
obj: Some(vj_runtime::objects_rt::sv_rt_obj as *const () as usize),
process_class: Some(vj_runtime::process_class::sv_rt_process as *const () as usize),
weakref: Some(vj_runtime::weakref::sv_rt_weakref as *const () as usize),
semaphore: Some(vj_runtime::semaphore_rt::sv_rt_semaphore as *const () as usize),
stoch_q: Some(vj_runtime::stoch_queue::sv_rt_stoch_q as *const () as usize),
net_drivers: Some(vj_sim::sched::real_sv_rt_net_drivers as *const () as usize),
mailbox: Some(vj_runtime::mailbox_rt::sv_rt_mailbox as *const () as usize),
wide: Some(vj_runtime::wide_rt::sv_rt_wide as *const () as usize),
huge: Some(vj_sim::sched::real_sv_rt_huge as *const () as usize),
str_cstr: Some(vj_runtime::strings_rt::sv_rt_str_cstr as *const () as usize),
str_to_real: Some(vj_runtime::strings_rt::sv_rt_str_to_real as *const () as usize),
str_from_real: Some(vj_runtime::strings_rt::sv_rt_str_from_real as *const () as usize),
str_sformatf: Some(vj_runtime::strings_rt::sv_rt_str_sformatf as *const () as usize),
random: Some(real_sv_rt_random as *const () as usize),
urandom: Some(real_sv_rt_urandom as *const () as usize),
urandom_range: Some(real_sv_rt_urandom_range as *const () as usize),
random_reseed: Some(sv_rt_random_reseed as *const () as usize),
urandom_reseed: Some(sv_rt_urandom_reseed as *const () as usize),
dist: Some(sv_rt_dist as *const () as usize),
fopen: Some(real_sv_rt_fopen as *const () as usize),
fopen_mcd: Some(real_sv_rt_fopen_mcd as *const () as usize),
system: Some(real_sv_rt_system as *const () as usize),
fclose: Some(real_sv_rt_fclose as *const () as usize),
fflush: Some(real_sv_rt_fflush as *const () as usize),
fwrite: Some(real_sv_rt_fwrite as *const () as usize),
fdisplay: Some(real_sv_rt_fdisplay as *const () as usize),
fmonitor: Some(real_sv_rt_fmonitor as *const () as usize),
fstrobe: Some(real_sv_rt_fstrobe as *const () as usize),
fseek: Some(real_sv_rt_fseek as *const () as usize),
feof: Some(real_sv_rt_feof as *const () as usize),
plusargs_test: Some(real_sv_rt_plusargs_test as *const () as usize),
plusargs_value: Some(real_sv_rt_plusargs_value as *const () as usize),
sdf_annotate: Some(vj_runtime::sdf_annotate::sv_rt_sdf_annotate as *const () as usize),
cov: Some(vj_runtime::covergroup_rt::sv_rt_cov as *const () as usize),
vpi_systf: Some(crate::vpi::systf::sv_rt_vpi_systf as *const () as usize),
vpi_dyn: Some(crate::vpi::dynobj::sv_rt_vpi_dyn as *const () as usize),
specparam_value: Some(vj_runtime::specparam::sv_rt_specparam_value as *const () as usize),
};
let mut class_types = netlist::collect_new_class_types(&eval);
let ancestor_types: Vec<sv_lang::Type<'_>> = class_types
.iter()
.flat_map(|ty| netlist::base_chain(*ty))
.collect();
class_types.extend(ancestor_types);
let class_ids = netlist::assign_class_template_ids(&class_types);
let mut classes_by_id: std::collections::HashMap<u32, sv_lang::Symbol<'_>> =
std::collections::HashMap::new();
for (ty, id) in class_types.iter().zip(class_ids.iter()) {
classes_by_id.entry(id.0).or_insert_with(|| ty.as_symbol());
}
let mut classes: Vec<(vj_sim::netlist::ClassTemplateId, sv_lang::Symbol<'_>)> = classes_by_id
.into_iter()
.map(|(raw, sym)| (vj_sim::netlist::ClassTemplateId(raw), sym))
.collect();
classes.sort_by_key(|(id, _)| id.0);
static CLASS_DESIGN_RUN: std::sync::Mutex<()> = std::sync::Mutex::new(());
let _class_design_run =
(!classes.is_empty()).then(|| CLASS_DESIGN_RUN.lock().unwrap_or_else(|e| e.into_inner()));
let mut planned_wires: Vec<(
vj_sim::image::WireImage,
Option<crate::interconnect::WireInfo>,
)> = Vec::new();
for &id in ®istration_order {
let Some(parent_id) = elaborated.instance(id).parent else {
continue;
};
let child_sym = elaborated.instance_symbol(id);
let child_signals = &instance_signals[&id];
let parent_signals = &instance_signals[&parent_id];
for conn in child_sym.instance_port_connections() {
let port_sym = conn.port();
if port_sym.kind() == SymbolKind::InterfacePort {
continue;
}
if port_sym.port_is_null_port() {
continue;
}
if port_sym.port_direction() == ArgumentDirection::Ref {
continue;
}
let member_sym = port_sym.port_internal_symbol().unwrap_or(port_sym);
let child_slot = *child_signals.get(&member_sym.uid()).ok_or_else(|| {
RunError::Lowering(LoweringError::unsupported_at(
"instance port has no resolved signal slot in its own module",
&port_sym,
))
})?;
if resolution.inout_ports.contains(&(id, port_sym.uid())) {
continue;
}
let Some(expr) = conn.expression() else {
continue;
};
if !native_port_connection(expr, &port_sym)
&& matches!(
port_sym.port_direction(),
ArgumentDirection::In | ArgumentDirection::Out
)
{
continue;
}
let ident_expr = if expr.kind() == ExpressionKind::Assignment {
expr.left().ok_or_else(|| {
RunError::Lowering(LoweringError::unsupported_at(
"output port's implicit connection assignment has no lvalue",
&port_sym,
))
})?
} else {
expr
};
let ident_expr = if ident_expr.kind() == ExpressionKind::Conversion {
ident_expr.operand().unwrap_or(ident_expr)
} else {
ident_expr
};
let parent_base_expr = match select_base(ident_expr) {
Some(b) if b.kind() == ExpressionKind::NamedValue => b,
_ => {
return Err(RunError::Lowering(LoweringError::unsupported_at(
&format!(
"instance port connected to a non-identifier expression \
({:?}) -- this slice only realizes a plain identifier \
connection, or a constant bit-select/part-select connection \
(e.g. a genvar-indexed `a[g*4 +: 4]`), as a synthetic \
continuous assignment",
ident_expr.kind()
),
&port_sym,
)));
}
};
let parent_ref_sym = parent_base_expr.referenced_symbol().ok_or_else(|| {
RunError::Lowering(LoweringError::unsupported_at(
"unresolved name in instance port connection expression",
&port_sym,
))
})?;
let parent_slot = *parent_signals.get(&parent_ref_sym.uid()).ok_or_else(|| {
RunError::Lowering(LoweringError::unsupported_at(
"instance port connection expression names a non-local signal in the \
parent scope",
&port_sym,
))
})?;
let bit_range: Option<(u32, u32)> = match ident_expr.kind() {
ExpressionKind::ElementSelect | ExpressionKind::RangeSelect => {
if matches!(parent_slot.class, SignalSlotClass::Memory { .. })
|| matches!(child_slot.class, SignalSlotClass::Memory { .. })
{
return Err(RunError::Lowering(LoweringError::unsupported_at(
"bit-select/part-select instance port connection to an \
unpacked-array (Memory-class) signal is out of this slice's scope",
&port_sym,
)));
}
Some(
vj_codegen::select::constant_window(ident_expr, parent_slot.width)
.map_err(|why| {
RunError::Lowering(LoweringError::unsupported_at(
&format!("instance port connection select: {why}"),
&port_sym,
))
})?,
)
}
_ => None,
};
let bank_cell = |key: crate::nets::DriverKey| {
resolution
.slot(&key)
.map(|slot| (bank_id, bank_slot_of(slot)))
};
let (child_logical, parent_logical) = (child_slot, parent_slot);
let child_bank = (port_sym.port_direction() == ArgumentDirection::In)
.then(|| {
bank_cell(crate::nets::DriverKey::Port(
id,
port_sym.uid(),
member_sym.uid(),
))
})
.flatten();
let parent_bank = (port_sym.port_direction() == ArgumentDirection::Out)
.then(|| {
bank_cell(crate::nets::DriverKey::Port(
id,
port_sym.uid(),
parent_ref_sym.uid(),
))
})
.flatten();
let (child_inst, child_slot) = child_bank.unwrap_or((id, child_slot));
let (parent_inst, parent_slot) = parent_bank.unwrap_or((parent_id, parent_slot));
let wire_info = (sdf_wires
&& !matches!(child_slot.class, SignalSlotClass::Memory { .. })
&& !matches!(parent_slot.class, SignalSlotClass::Memory { .. }))
.then(|| {
let into_port = port_sym.port_direction() == ArgumentDirection::In;
let (lo, _) = bit_range.unwrap_or((0, 0));
let child_node = (id, child_logical.slot);
let parent_node = (parent_id, parent_logical.slot);
let child_actual = loc_of(child_inst, child_slot);
let parent_actual = loc_of(parent_inst, parent_slot);
if into_port {
crate::interconnect::WireInfo {
meta: crate::interconnect::Meta {
src_node: parent_node,
dst_node: child_node,
port_node: child_node,
into_port,
src_lo: lo,
dst_lo: 0,
width: child_logical.width,
dst_banked: child_bank.is_some(),
},
src: parent_actual,
dst: child_actual,
}
} else {
crate::interconnect::WireInfo {
meta: crate::interconnect::Meta {
src_node: child_node,
dst_node: parent_node,
port_node: child_node,
into_port,
src_lo: 0,
dst_lo: lo,
width: child_logical.width,
dst_banked: parent_bank.is_some(),
},
src: child_actual,
dst: parent_actual,
}
}
});
let child_place = place_of(child_inst, child_slot);
let parent_place = place_of(parent_inst, parent_slot);
let into_port = match port_sym.port_direction() {
ArgumentDirection::In => true,
ArgumentDirection::Out => false,
ArgumentDirection::InOut | ArgumentDirection::Ref => {
return Err(RunError::Lowering(LoweringError::unsupported_at(
"inout/ref-direction instance port (not implemented in this slice)",
&port_sym,
)));
}
};
let dir = if into_port { "input" } else { "output" };
let child_window: Option<(u32, u32)> = match port_sym
.port_internal_expr()
.filter(|e| {
matches!(
e.kind(),
ExpressionKind::ElementSelect | ExpressionKind::RangeSelect
)
})
.filter(|_| !matches!(child_slot.class, SignalSlotClass::Memory { .. }))
{
Some(e) => Some(
vj_codegen::select::constant_window(e, child_slot.width).map_err(|why| {
RunError::Lowering(LoweringError::unsupported_at(
&format!("instance port internal select: {why}"),
&port_sym,
))
})?,
),
None => None,
};
let parent_w = bit_range.map_or(parent_slot.width, |(_, w)| w);
let child_w = child_window.map_or(child_slot.width, |(_, w)| w);
if parent_w != child_w {
return Err(RunError::Lowering(LoweringError::unsupported_at(
&format!(
"{dir} port connection width mismatch (this slice does not \
implement implicit extension/truncation)"
),
&port_sym,
)));
}
let (parent_range, child_range) = (bit_range, child_window);
let ranged = parent_range.is_some() || child_range.is_some();
let wire = if into_port {
vj_sim::image::WireImage {
src: parent_place,
src_range: parent_range,
dst: child_place,
dst_range: child_range,
ranged,
}
} else {
vj_sim::image::WireImage {
src: child_place,
src_range: child_range,
dst: parent_place,
dst_range: parent_range,
ranged,
}
};
planned_wires.push((wire, wire_info));
}
}
let group_is_native =
|g: &crate::nets::Group| g.user.is_some() || !g.rows.is_empty() || !g.cells.is_empty();
let native_groups: Vec<bool> = resolution.groups.iter().map(group_is_native).collect();
let network_is_native =
|net: &crate::nets::Network| net.groups.iter().any(|&gi| native_groups[gi]);
let mut group_remap: Vec<Option<u32>> = Vec::with_capacity(resolution.groups.len());
let mut next_group = 0u32;
for &native in &native_groups {
if native {
group_remap.push(None);
} else {
group_remap.push(Some(next_group));
next_group += 1;
}
}
let shows_strength = crate::nets::shows_strength(&syntax_trees);
let port_views = if crate::nets::uses_dumpports(&syntax_trees) {
dump_port_views(
&elaborated,
®istration_order,
&resolution,
&native_groups,
)
} else {
PortViews::default()
};
let planned_groups: Vec<vj_sim::image::GroupImage> = resolution
.groups
.iter()
.enumerate()
.filter(|(_, g)| !group_is_native(g))
.map(|(gi, g)| vj_sim::image::GroupImage {
kind: g.kind,
width: g.width,
nets: g
.nets
.iter()
.map(|(inst, sym)| (*inst, instance_signals[inst][sym].slot))
.collect(),
drivers: g
.drivers
.iter()
.map(|&(slot, strength)| vj_sim::image::DriverImage {
slot,
strength,
ambiguous: resolution.ambiguous.get(&slot).copied(),
src: resolution.strength_src.get(&slot).copied(),
})
.collect(),
strength_tick: resolution.strength_ticks.get(&gi).copied(),
sides: port_views.sides.get(&gi).cloned().unwrap_or_default(),
charge: g
.nets
.iter()
.find_map(|n| trireg_charges.get(n).copied())
.unwrap_or(4),
switched: g.switched,
decay: (g.kind == vj_sim::state::NetKind::TriReg)
.then(|| g.nets.iter().find_map(|n| trireg_decays.get(n).copied()))
.flatten(),
strength_keys: if shows_strength || crate::nets::tracks_strengths() {
g.nets
.iter()
.map(|(_, sym)| vj_runtime::fmt::strength_key(&sym.key_string()))
.collect()
} else {
Vec::new()
},
})
.collect();
let planned_networks: Vec<vj_sim::image::NetworkImage> = resolution
.networks
.iter()
.filter(|net| !network_is_native(net))
.map(|net| {
let local = |gi: usize| {
net.groups
.iter()
.position(|&x| x == gi)
.expect("in network") as u32
};
vj_sim::image::NetworkImage {
groups: net
.groups
.iter()
.map(|&g| {
group_remap[g].expect(
"a non-native network (checked above) only names non-native groups",
)
})
.collect(),
switches: net
.switches
.iter()
.map(|s| vj_sim::image::SwitchImage {
a: (local(s.a.0), s.a.1),
b: (local(s.b.0), s.b.1),
conducts: match s.conducts {
crate::nets::Conducts::Always => vj_sim::image::ConductsImage::Always,
crate::nets::Conducts::When(c, on) => {
vj_sim::image::ConductsImage::When(
match c {
crate::nets::Control::Bit(inst, slot, bit) => {
vj_sim::image::ControlImage::Bit {
instance: inst,
slot: slot.slot,
width: slot.width,
bit,
}
}
crate::nets::Control::Const(v) => {
vj_sim::image::ControlImage::Const(v)
}
crate::nets::Control::Bank(cell) => {
vj_sim::image::ControlImage::Bit {
instance: bank_id,
slot: cell,
width: 1,
bit: 0,
}
}
},
on,
)
}
},
resistive: s.resistive,
})
.collect(),
}
})
.collect();
let native_nets = {
use vj_exec::usernets as un;
let mut types: Vec<un::UserType> = Vec::new();
let mut type_of: HashMap<usize, u32> = HashMap::new();
let mut type_index = |t: usize| -> Result<u32, RunError> {
if let Some(&i) = type_of.get(&t) {
return Ok(i);
}
let ty = &user_nets.types[t];
let func = match &ty.func {
Some(f) => {
Some(vj_codegen::nettype::resolver_symbol(f).map_err(RunError::Lowering)?)
}
None => None,
};
types.push(un::UserType {
func,
default: vj_sim::state::packed_words(&ty.init),
});
let i = (types.len() - 1) as u32;
type_of.insert(t, i);
Ok(i)
};
let targets_of = |g: &crate::nets::Group| -> Vec<un::Target> {
let mut v: Vec<un::Target> = g
.nets
.iter()
.map(|(inst, sym)| un::Target::Cell {
instance: inst.0,
slot: instance_signals[inst][sym].slot,
})
.collect();
v.extend(g.rows.iter().map(|(net, row)| un::Target::Row {
instance: net.0.0,
slot: instance_signals[&net.0][&net.1].slot,
row: *row,
}));
v.extend(g.cells.iter().map(|&s| un::Target::Cell {
instance: bank_id.0,
slot: s,
}));
v
};
let contribs_of = |g: &crate::nets::Group| -> Vec<un::Contrib> {
g.drivers
.iter()
.zip(&g.driver_rows)
.map(|(&(slot, _), row)| match row {
Some(r) => un::Contrib::Row(slot, *r),
None => un::Contrib::Cell(slot),
})
.collect()
};
let native_group = |g: &crate::nets::Group, user: Option<u32>| -> un::NativeGroup {
un::NativeGroup {
user,
kind: g.kind,
width: g.width,
drivers: contribs_of(g)
.into_iter()
.zip(&g.drivers)
.map(|(contrib, &(slot, strength))| un::NativeDriver {
contrib,
strength,
ambiguous: if user.is_some() {
None
} else {
resolution.ambiguous.get(&slot).copied()
},
})
.collect(),
targets: targets_of(g),
strength_keys: if shows_strength || crate::nets::tracks_strengths() {
g.nets
.iter()
.map(|(_, sym)| sym.key_string())
.chain(
g.rows
.iter()
.map(|(net, row)| format!("{}[{row}]", net.1.key_string())),
)
.map(|k| vj_runtime::fmt::strength_key(&k))
.collect()
} else {
Vec::new()
},
}
};
let mut plain_networks = Vec::new();
let mut networks = Vec::new();
for net in resolution
.networks
.iter()
.filter(|net| network_is_native(net))
{
let local = |gi: usize| {
net.groups
.iter()
.position(|&x| x == gi)
.expect("in network") as u32
};
let conducts_of = |c: crate::nets::Conducts| match c {
crate::nets::Conducts::Always => un::NativeConducts::Always,
crate::nets::Conducts::When(crate::nets::Control::Bit(inst, slot, bit), on) => {
un::NativeConducts::Bit {
instance: inst.0,
slot: slot.slot,
width: slot.width,
bit,
on,
}
}
crate::nets::Conducts::When(crate::nets::Control::Const(value), on) => {
un::NativeConducts::Const { value, on }
}
crate::nets::Conducts::When(crate::nets::Control::Bank(cell), on) => {
un::NativeConducts::Bit {
instance: bank_id.0,
slot: cell,
width: 1,
bit: 0,
on,
}
}
};
let Some(t) = resolution.groups[net.groups[0]].user else {
if net
.groups
.iter()
.any(|&gi| resolution.groups[gi].kind == vj_sim::state::NetKind::TriReg)
{
return Err(RunError::Lowering(LoweringError::unsupported(
"a switch network joining a `trireg` net and an element of an \
unpacked-array net (LRM 28.16.2)",
"net",
)));
}
plain_networks.push(un::NativePlainNetwork {
groups: net
.groups
.iter()
.map(|&gi| native_group(&resolution.groups[gi], None))
.collect(),
switches: net
.switches
.iter()
.map(|s| un::NativePlainSwitch {
a: local(s.a.0),
a_bit: s.a.1,
b: local(s.b.0),
b_bit: s.b.1,
conducts: conducts_of(s.conducts),
resistive: s.resistive,
})
.collect(),
});
continue;
};
networks.push(un::NativeNetwork {
user: type_index(t)?,
width: resolution.groups[net.groups[0]].width,
groups: net
.groups
.iter()
.map(|&gi| {
let g = &resolution.groups[gi];
(contribs_of(g), targets_of(g))
})
.collect(),
switches: net
.switches
.iter()
.map(|s| un::NativeSwitch {
a: local(s.a.0),
b: local(s.b.0),
conducts: match s.conducts {
crate::nets::Conducts::Always => un::NativeConducts::Always,
crate::nets::Conducts::When(
crate::nets::Control::Bit(inst, slot, bit),
on,
) => un::NativeConducts::Bit {
instance: inst.0,
slot: slot.slot,
width: slot.width,
bit,
on,
},
crate::nets::Conducts::When(crate::nets::Control::Const(value), on) => {
un::NativeConducts::Const { value, on }
}
crate::nets::Conducts::When(crate::nets::Control::Bank(cell), on) => {
un::NativeConducts::Bit {
instance: bank_id.0,
slot: cell,
width: 1,
bit: 0,
on,
}
}
},
})
.collect(),
});
}
let mut groups = Vec::new();
for g in resolution
.groups
.iter()
.filter(|g| !g.switched && group_is_native(g))
{
let user = match g.user {
Some(t) => Some(type_index(t)?),
None => {
if g.kind == vj_sim::state::NetKind::TriReg {
return Err(RunError::Lowering(LoweringError::unsupported(
"a `trireg` group resolved per element -- this should have been \
rejected when the design's nets were analyzed",
"net",
)));
}
None
}
};
groups.push(native_group(g, user));
}
un::NativeNets {
types,
networks,
plain_networks,
groups,
}
};
let dump_signals = dump_signal_images(
&elaborated,
®istration_order,
&instance_signals,
&eval.design().syntax_trees(),
&port_views,
);
let fsms = if aot.is_some() || vj_runtime::codecov::enabled() {
fsm_images(sources, &elaborated, ®istration_order, &instance_signals)
} else {
Vec::new()
};
if let Some(request) = aot {
use crate::aot_image::{Locator, codegen_refusal, not_supported, refused, runtime_refusal};
use vj_sim::image::{
DesignImage, ForeignImage, InstanceImage, InstanceKind, KernelImage, KernelRole,
TemplateImage, XWindow,
};
let locate = Locator::new(&syntax_trees, sources);
let first_body = instance_body_of(&elaborated, registration_order[0], path)?;
let design_at = locate.symbol(&first_body);
let vpi_registered = systf_codegen.clone();
vj_codegen::vpi_systf::set_registered(systf_codegen);
let mut base = DesignImage::empty(vj_abi::VJ_ABI_VERSION);
base.x_initial_zero = X_INITIAL_ZERO.load(std::sync::atomic::Ordering::Relaxed);
base.tick_exp = vj_codegen::timescale::design_tick_exp(
&elaborated.instance_symbol(registration_order[0]),
);
base.timing.options = timing.to_image();
let layout_of = |key: &TemplateKey| {
templates
.layout_of(key)
.expect("every template was registered above")
.clone()
};
let mut seen_templates: HashSet<TemplateKey> = HashSet::new();
for &id in ®istration_order {
let key = *elaborated.dedup_key(elaborated.instance(id).template);
if seen_templates.insert(key) {
base.templates.push(TemplateImage {
key,
layout: layout_of(&key),
});
}
base.instances.push(InstanceImage {
id,
template: key,
kind: InstanceKind::Elaborated,
path: elaborated.instance_symbol(id).hierarchical_path(),
});
}
for (id, key, _) in &packages {
base.templates.push(TemplateImage {
key: *key,
layout: layout_of(key),
});
base.instances.push(InstanceImage {
id: *id,
template: *key,
kind: InstanceKind::Package,
path: String::new(),
});
}
if needs_bank {
base.templates.push(TemplateImage {
key: bank_key,
layout: layout_of(&bank_key),
});
base.instances.push(InstanceImage {
id: bank_id,
template: bank_key,
kind: InstanceKind::Bank,
path: String::new(),
});
}
base.wires = planned_wires.iter().map(|(w, _)| *w).collect();
base.groups = planned_groups.clone();
let timing_plan = vj_exec::timing::TimingPlan {
prims: prim_plans.prims.clone(),
lines: prim_plans.lines.clone(),
device_init: prim_plans.device_init.clone(),
specify: specify_plans.spec(),
checks: timing_check_plans.spec(),
sdf: Default::default(),
interconnect: vj_exec::timing::interconnect::InterconnectPlan {
kick: sdf_kick.map(|slot| loc_of(bank_id, slot)),
wires: planned_wires.iter().map(|(_, info)| *info).collect(),
style: timing.default_pulse_style(),
showcancelled: timing.default_showcancelled(),
},
};
if !timing_plan.is_empty() {
base.timing.plan = vj_exec::timing::encode(&timing_plan);
}
base.networks = planned_networks.clone();
for id in registration_order
.iter()
.copied()
.chain(packages.iter().map(|(id, _, _)| *id))
{
if let Some((own, targets)) = instance_foreign.get(&id) {
base.foreign.push(ForeignImage {
instance: id,
own_slots: *own,
targets: targets.clone(),
});
}
}
let mut x_windows = crate::nets::driven_windows(&infos, &instance_signals, &syntax_trees);
for (net, &t) in &user_nets.by_net {
x_windows.remove(net);
let slot = &instance_signals[&net.0][&net.1];
if matches!(slot.class, SignalSlotClass::Memory { .. }) {
continue;
}
base.x_windows.push(XWindow {
instance: net.0,
slot: slot.slot,
value: vj_sim::state::bits_to_four_state(&user_nets.types[t].init),
});
}
for g in resolution.groups.iter().filter(|g| g.user.is_some()) {
let value =
vj_sim::state::bits_to_four_state(&user_nets.types[g.user.unwrap_or(0)].init);
for (&(slot, _), row) in g.drivers.iter().zip(&g.driver_rows) {
if row.is_some() {
continue;
}
base.x_windows.push(XWindow {
instance: bank_id,
slot,
value: value.clone(),
});
}
}
for &id in ®istration_order {
let body = instance_body_of(&elaborated, id, path)?;
for uid in crate::nets::trireg_nets(&body, &syntax_trees) {
x_windows.entry((id, uid)).or_default().push(None);
}
}
for ((id, uid), windows) in &x_windows {
let Some(slot) = instance_signals.get(id).and_then(|s| s.get(uid)) else {
continue;
};
let value = match slot.class {
SignalSlotClass::Memory { num_rows, .. } => x_memory_value(slot, num_rows, windows),
_ => x_window_value(slot, windows),
};
base.x_windows.push(XWindow {
instance: *id,
slot: slot.slot,
value,
});
}
base.x_windows.sort_by_key(|w| (w.instance.0, w.slot));
base.x_windows.dedup_by_key(|w| (w.instance.0, w.slot));
let stash: std::cell::RefCell<Option<RunError>> = std::cell::RefCell::new(None);
let slot_of = |id: InstanceId, sym: &SymbolId| -> Result<u32, LoweringError> {
instance_signals
.get(&id)
.and_then(|s| s.get(sym))
.map(|s| s.slot)
.ok_or_else(|| {
LoweringError::unsupported(
&format!(
"static-sensitivity entry `{sym}` has no resolved signal slot in its \
own module"
),
"build-aot",
)
})
};
vj_codegen::systask::clear_sdf_sites();
let result = vj_codegen::pipeline::compile_design_to_object(
&plans,
&cont_assign_specs,
&subroutine_specs,
&classes,
&cov_plan.specs,
eval.design(),
base,
|image, parts| {
let refusal = parts
.declared_runtime
.iter()
.find_map(|s| runtime_refusal(s));
if let Some((feature, what)) = refusal {
*stash.borrow_mut() =
Some(refused(feature, ¬_supported(what), design_at.clone()));
return Err(LoweringError::unsupported(what, "build-aot"));
}
let engine_owned: HashSet<usize> = parts
.assertions
.iter()
.filter(|a| a.end_cell.is_none())
.map(|a| a.plan_index)
.collect();
for (i, (frame_words, kind, id, spec, level)) in kernel_meta.iter().enumerate() {
let symbol = image.intern(&parts.names[i]);
image.kernels.push(KernelImage {
symbol,
frame_words: *frame_words,
kind: *kind,
instance: *id,
role: KernelRole::Process,
level_head: *level,
reactive: reactive_kernels.contains(&i),
program_initial: program_initial_kernels.contains(&i),
registered: !engine_owned.contains(&i),
read_set: spec
.read_set
.iter()
.map(|s| slot_of(*id, s))
.collect::<Result<_, _>>()?,
read_windows: {
let mut out = Vec::new();
for s in &spec.read_set {
for (lsb, width) in spec.windows.get(s).into_iter().flatten() {
out.push((slot_of(*id, s)?, *lsb, *width));
}
}
out
},
reset: spec
.reset
.as_ref()
.map(|(s, edge)| slot_of(*id, s).map(|slot| (slot, *edge)))
.transpose()?,
disable_scopes: kernel_disable_scopes[i].to_vec(),
});
}
for (i, (id, read_set)) in cont_assign_meta.iter().enumerate() {
let symbol = image.intern(&parts.cont_assign_names[i]);
image.kernels.push(KernelImage {
symbol,
frame_words: 0,
kind: ProcessKind::AlwaysComb,
instance: *id,
role: if cont_assign_specs[i].var_init {
KernelRole::VarInit
} else {
KernelRole::ContAssign
},
level_head: false,
reactive: reactive_cont_assigns.contains(&i),
program_initial: false,
registered: true,
read_set: read_set
.iter()
.map(|s| slot_of(*id, s))
.collect::<Result<_, _>>()?,
read_windows: Vec::new(),
reset: None,
disable_scopes: Vec::new(),
});
}
let body_infos: Vec<_> = bodies
.iter()
.map(|(id, body)| crate::verif_image::BodyInfo {
instance: *id,
body,
signals: &instance_signals[id],
port_sources: &port_sources,
})
.collect();
let kernel = |plan_index: usize| {
kernel_meta
.get(plan_index)
.map(
|(frame_words, _, id, _, _)| crate::verif_image::KernelInfo {
name: parts.names[plan_index].as_str(),
frame_words: *frame_words,
instance: *id,
},
)
};
let verif = crate::verif_image::build(
&parts.assertions,
kernel,
&body_infos,
&syntax_trees,
&cov_plan,
|n| image.intern(n),
)?;
image.verif = verif;
image.strings = vj_sim::strings::snapshot();
let class_ids: Vec<_> = classes.iter().map(|(id, _)| *id).collect();
image.classes = vj_sim::objects::template_snapshot(&class_ids);
for t in &native_nets.types {
if let Some(f) = &t.func {
image.intern(f);
}
}
image.native_nets = vj_exec::usernets::encode(&native_nets);
image.randomize = vj_sim::constraints::registry::snapshot(&class_ids);
image.dump_signals = dump_signals.clone();
image.fsms = fsms.clone();
let mut imports = parts.dpi_c_symbols.clone();
imports.sort();
imports.dedup();
let mut exports: Vec<(String, u32)> = parts
.dpi_exports
.iter()
.map(|(c_name, thunk)| (c_name.clone(), image.intern(thunk)))
.collect();
exports.sort();
let mut variants = parts.dpi_export_variants.clone();
variants.sort();
image.dpi = vj_sim::image::DpiImage {
imports,
exports,
variants,
task_fibers: !parts.export_tasks.is_empty(),
};
match vj_sim::collections::frozen_snapshot() {
Ok(collections) => image.collections = collections,
Err(why) => {
*stash.borrow_mut() = Some(refused(
"collection",
¬_supported(&why),
design_at.clone(),
));
return Err(LoweringError::unsupported(&why, "build-aot"));
}
}
let sites = vj_codegen::systask::take_sdf_sites();
if !sites.is_empty() {
let mut plan = timing_plan.clone();
let live = vj_exec::timing::TimingLive::new(&plan);
let wire_graph = std::sync::Arc::new(std::sync::Mutex::new(
vj_exec::timing::interconnect::Table::recording(
plan.interconnect
.wires
.iter()
.flatten()
.map(|w| w.meta)
.collect(),
),
));
let annotator = crate::sdf_apply::Annotator::new(
timing_model.paths.clone(),
crate::sdf_apply::instance_table(&infos, &instance_signals),
crate::sdf_apply::condition_specparams(&timing_model),
std::sync::Arc::clone(&live.paths),
crate::primitives::device_cells(&prim_plans.device, &live),
image.tick_exp,
)
.with_primitives(crate::primitives::prim_cells(&prim_plans.by_path, &live))
.with_checks(
timing_model.timing_checks.clone(),
std::sync::Arc::clone(&live.checks),
)
.with_options(&timing)
.with_labels(crate::sdf_labels::collect(&eval, &bodies, &timing_model))
.with_interconnect(std::sync::Arc::clone(&wire_graph))
.capturing_logs();
match crate::aot_sdf::resolve(
&sites,
&live,
&wire_graph,
timing_model.timing_checks.len(),
annotator,
) {
Ok(sdf) => {
plan.sdf = sdf;
image.timing.plan = vj_exec::timing::encode(&plan);
}
Err((feature, why)) => {
*stash.borrow_mut() =
Some(refused(feature, ¬_supported(&why), design_at.clone()));
return Err(LoweringError::unsupported(&why, "build-aot"));
}
}
}
image.stmt_cov =
vj_codegen::stmtcov::enabled().then(vj_codegen::stmtcov::take_sites);
if !vpi_libraries.is_empty() {
let mut boot = crate::vpi::boot::VpiBoot {
registered: vpi_registered.clone(),
sites: vj_codegen::vpi_systf::take_sites(),
fork_origins: vj_codegen::vpi_dyn::take_fork_origins()
.into_iter()
.collect(),
stmt_sites: vj_codegen::vpi_dyn::take_stmt_sites(),
disable_sites: vj_codegen::vpi_dyn::take_disable_sites(),
frame_sites: vj_codegen::vpi_dyn::take_frame_sites(),
class_uids: class_uids.clone(),
static_keys: vj_sim::objects::static_singleton_key_pairs(),
kernel_dyn: kernel_dyn.clone(),
..Default::default()
};
fill_vpi_planner_facts(
&mut boot,
&timing_model,
&body_id_to_instance,
&instance_signals,
image.tick_exp,
);
let model = crate::vpi::state::model().ok_or_else(|| {
LoweringError::unsupported("the VPI model was not built", "build-aot")
})?;
image.vpi = crate::vpi::boot::encode_image(&model, &boot);
}
Ok(())
},
request.with_main,
request.out_obj,
);
return match result {
Ok(image) => {
*request.image = Some(image);
Ok(0)
}
Err(e) => {
if let Some(r) = stash.take() {
return Err(r);
}
if let vj_codegen::error::CodegenError::Lowering(le) = &e
&& let Some((feature, what)) = codegen_refusal(&le.message)
{
return Err(refused(feature, ¬_supported(what), design_at));
}
Err(RunError::Codegen(e))
}
};
}
let vpi_registered = systf_codegen.clone();
vj_codegen::vpi_systf::set_registered(systf_codegen);
let (pipeline, kernel_names, cont_assign_kernel_names) = build_pipeline(
&plans,
&cont_assign_specs,
&subroutine_specs,
&classes,
&cov_plan.specs,
eval.design(),
bindings,
)
.map_err(RunError::Lowering)?;
let mut vpi_boot = crate::vpi::boot::VpiBoot {
registered: vpi_registered,
sites: vj_codegen::vpi_systf::take_sites(),
fork_origins: vj_codegen::vpi_dyn::take_fork_origins()
.into_iter()
.collect(),
stmt_sites: vj_codegen::vpi_dyn::take_stmt_sites(),
disable_sites: vj_codegen::vpi_dyn::take_disable_sites(),
frame_sites: vj_codegen::vpi_dyn::take_frame_sites(),
class_uids,
static_keys: vj_sim::objects::static_singleton_key_pairs(),
kernel_dyn: std::mem::take(&mut kernel_dyn),
..Default::default()
};
vpi_boot.install_static();
crate::dpi::set_export_targets(&pipeline.dpi_export_thunks().map_err(RunError::Lowering)?)
.map_err(RunError::Jit)?;
for (c_name, scope, variant) in pipeline.dpi_export_variants() {
crate::dpi::register_export_variant(c_name, scope, *variant);
}
#[allow(clippy::type_complexity)]
let kernel_names: Vec<(
String,
u32,
ProcessKind,
InstanceId,
KernelSensitivitySpec,
bool,
)> = kernel_names
.into_iter()
.zip(kernel_meta)
.map(|(name, (frame_words, kind, id, spec, level))| {
(name, frame_words, kind, id, spec, level)
})
.collect();
let cont_assign_kernel_names: Vec<(String, InstanceId, Vec<SymbolId>, bool)> =
cont_assign_kernel_names
.into_iter()
.zip(cont_assign_meta)
.zip(cont_assign_specs.iter().map(|s| s.var_init))
.map(|((name, (id, read_set)), var_init)| (name, id, read_set, var_init))
.collect();
if !instance_path_disables.is_empty() {
let known: HashSet<&str> = kernel_disable_scopes
.iter()
.flat_map(|s| s.iter().map(|(_, l, _)| l.as_str()))
.chain(branch_scope_labels.iter().map(String::as_str))
.collect();
for (requester, label) in &instance_path_disables {
let requester_path = instance_ids_by_path
.iter()
.find_map(|(p, i)| (i == requester).then_some(p));
let inert = inert_blocks.contains(label)
|| requester_path.is_some_and(|rp| inert_blocks.contains(&format!("{rp}.{label}")));
let inert = inert
|| label.rsplit_once('.').is_some_and(|(prefix, leaf)| {
vj_sim::process::cfg::is_task_name(leaf)
&& (instance_ids_by_path.contains_key(prefix)
|| requester_path.is_some_and(|rp| {
instance_ids_by_path.contains_key(&format!("{rp}.{prefix}"))
}))
});
if !known.contains(format!("{requester}/{label}").as_str())
&& !known.contains(format!("/{label}").as_str())
&& !inert
{
return Err(RunError::Lowering(LoweringError::unsupported(
&format!(
"disable of `{label}` (an instance-path target is supported only for a \
named block of a process or fork branch that can suspend, or a task; IEEE 1800-2023 9.6.2)"
),
path.display().to_string().as_str(),
)));
}
}
}
let mut state_engine = StateEngine::new(templates);
state_engine.set_change_log(false);
let mut instance_sim_handle: HashMap<InstanceId, SimHandle> = HashMap::new();
for &id in ®istration_order {
let key = *elaborated.dedup_key(elaborated.instance(id).template);
let instance_handle = state_engine.allocate_instance(key).expect(
"every distinct dedup TemplateKey reachable via registration_order was \
registered into `templates` by the loop above before any allocate_instance call",
);
let handle = instance_handle.into_sim_handle();
crate::dpi::register_scope(
&elaborated.instance_symbol(id).hierarchical_path(),
handle.0 as usize,
);
instance_sim_handle.insert(id, handle);
}
for (id, key, _) in &packages {
let handle = state_engine
.allocate_instance(*key)
.expect("every package template was registered above")
.into_sim_handle();
instance_sim_handle.insert(*id, handle);
}
if needs_bank {
let handle = state_engine
.allocate_instance(bank_key)
.expect("the driver bank template was registered above")
.into_sim_handle();
instance_sim_handle.insert(bank_id, handle);
}
for (&id, (own, targets)) in &instance_foreign {
let targets: Vec<(SimHandle, SlotId, Option<AliasWindow>)> = targets
.iter()
.map(|&(owner, slot, window)| (instance_sim_handle[&owner], SlotId(slot), window))
.collect();
let registered_own =
set_foreign_slots_windowed(&mut state_engine, instance_sim_handle[&id], &targets);
assert_eq!(
registered_own, *own,
"an instance's own slot count is its template layout's"
);
}
state_engine.reset_to_initial_with(X_INITIAL_ZERO.load(std::sync::atomic::Ordering::Relaxed));
let mut x_windows = crate::nets::driven_windows(&infos, &instance_signals, &syntax_trees);
for (net, &t) in &user_nets.by_net {
x_windows.remove(net);
let slot = &instance_signals[&net.0][&net.1];
if matches!(slot.class, SignalSlotClass::Memory { .. }) {
continue;
}
let value = vj_sim::state::bits_to_four_state(&user_nets.types[t].init);
let cell =
vj_sim::state::cell_for_slot(instance_sim_handle[&net.0], vj_abi::SlotId(slot.slot));
state_engine.commit_write(cell, value, &mut ());
}
for g in resolution.groups.iter().filter(|g| g.user.is_some()) {
let value = vj_sim::state::bits_to_four_state(&user_nets.types[g.user.unwrap_or(0)].init);
for (&(slot, _), row) in g.drivers.iter().zip(&g.driver_rows) {
if row.is_some() {
continue;
}
let cell =
vj_sim::state::cell_for_slot(instance_sim_handle[&bank_id], vj_abi::SlotId(slot));
state_engine.commit_write(cell, value.clone(), &mut ());
}
}
for &id in ®istration_order {
let body = instance_body_of(&elaborated, id, path)?;
for uid in crate::nets::trireg_nets(&body, &syntax_trees) {
x_windows.entry((id, uid)).or_default().push(None);
}
}
for ((id, uid), windows) in &x_windows {
let Some(slot) = instance_signals.get(id).and_then(|s| s.get(uid)) else {
continue;
};
if let SignalSlotClass::Memory { num_rows, .. } = slot.class {
let value = x_memory_value(slot, num_rows, windows);
let cell =
vj_sim::state::cell_for_slot(instance_sim_handle[id], vj_abi::SlotId(slot.slot));
state_engine.commit_write(cell, value, &mut ());
continue;
}
let value = x_window_value(slot, windows);
let cell = vj_sim::state::cell_for_slot(instance_sim_handle[id], vj_abi::SlotId(slot.slot));
state_engine.commit_write(cell, value, &mut ());
}
let mut engine = Engine::new(Box::new(state_engine), Box::new(NullIoSink));
let tick_exp = registration_order
.first()
.map(|&id| vj_codegen::timescale::design_tick_exp(&elaborated.instance_symbol(id)))
.unwrap_or(0);
vj_runtime::timefmt::set_design_tick_exp(tick_exp);
let timing_plan = vj_exec::timing::TimingPlan {
prims: std::mem::take(&mut prim_plans.prims),
lines: std::mem::take(&mut prim_plans.lines),
device_init: std::mem::take(&mut prim_plans.device_init),
specify: specify_plans.spec(),
checks: timing_check_plans.spec(),
sdf: Default::default(),
interconnect: Default::default(),
};
let timing_live = vj_exec::timing::TimingLive::new(&timing_plan);
let sdf_live = std::sync::Arc::clone(&timing_live.paths);
let sdf_check_live = std::sync::Arc::clone(&timing_live.checks);
fill_vpi_planner_facts(
&mut vpi_boot,
&timing_model,
&body_id_to_instance,
&instance_signals,
tick_exp,
);
vpi_boot.attach_runtime(
|id| instance_sim_handle.get(&id).copied(),
&sdf_live,
&sdf_check_live,
&interconnect_table,
);
if !vpi_libraries.is_empty() {
engine.set_host(Box::new(crate::vpi::sim::VpiHost));
vj_sim::sched::record_force_events(true);
}
let _sdf_applier = crate::sdf_apply::Installed::new(
crate::sdf_apply::Annotator::new(
timing_model.paths.clone(),
crate::sdf_apply::instance_table(&infos, &instance_signals),
crate::sdf_apply::condition_specparams(&timing_model),
sdf_live,
crate::primitives::device_cells(&prim_plans.device, &timing_live),
tick_exp,
)
.with_primitives(crate::primitives::prim_cells(
&prim_plans.by_path,
&timing_live,
))
.with_checks(timing_model.timing_checks.clone(), sdf_check_live)
.with_options(&timing)
.with_labels(crate::sdf_labels::collect(&eval, &bodies, &timing_model))
.with_interconnect(std::sync::Arc::clone(&interconnect_table)),
);
let mut scratch = crate::verif_image::ScratchSymbols::default();
let verif_image = {
let body_infos: Vec<_> = bodies
.iter()
.map(|(id, body)| crate::verif_image::BodyInfo {
instance: *id,
body,
signals: &instance_signals[id],
port_sources: &port_sources,
})
.collect();
crate::verif_image::build(
pipeline.assertions(),
|plan_index| {
kernel_names
.get(plan_index)
.map(
|(name, frame_words, _, id, _, _)| crate::verif_image::KernelInfo {
name,
frame_words: *frame_words,
instance: *id,
},
)
},
&body_infos,
&syntax_trees,
&cov_plan,
|name| scratch.intern(name),
)
.map_err(RunError::Lowering)?
};
let assertion_plans: HashSet<usize> = pipeline
.assertions()
.iter()
.filter(|a| a.end_cell.is_none())
.map(|a| a.plan_index)
.collect();
let verif = vj_exec::verif::Verif::build(
&verif_image,
&scratch.names,
&crate::verif_image::PipelineResolver(&pipeline),
&instance_sim_handle,
&pipeline.fork_table(),
&pipeline.wait_table(),
)
.map_err(|e| RunError::Jit(e.to_string()))?;
verif.set_fork_kernels(&pipeline.fork_kernels().map_err(RunError::Lowering)?);
verif.start(&engine);
{
let table = crate::dump::table_from_image(&dump_signals, &instance_sim_handle);
let (text, exp10) = crate::dump::timescale(tick_exp);
crate::dump::install(table, text, exp10);
}
let code_cov = vj_runtime::codecov::enabled();
if code_cov {
vj_runtime::codecov::install(&dump_signals, &fsms, &instance_sim_handle);
}
let fork_table = pipeline.fork_table();
let fork_kernels = pipeline.fork_kernels().map_err(RunError::Lowering)?;
let wait_table = pipeline.wait_table();
let register_procedural = |engine: &mut Engine, level_heads: bool| -> Result<(), RunError> {
for (plan_index, (name, frame_words, kind, id, spec, level)) in
kernel_names.iter().enumerate()
{
if assertion_plans.contains(&plan_index) || *level != level_heads {
continue;
}
let sim_handle = instance_sim_handle[id];
let kernel = pipeline.get_process_fn(name).ok_or_else(|| {
RunError::Jit(format!(
"get_process_fn: JIT did not produce a callable pointer for `{name}`"
))
})?;
let proc = CompiledKernelProcess::new(kernel, *frame_words, sim_handle)
.with_fork_table(fork_table.clone())
.with_fork_kernels(fork_kernels.clone())
.with_wait_table(wait_table.clone())
.with_instance_slot_mapping()
.with_disable_scopes(id.0, kernel_disable_scopes[plan_index].clone());
let proc = if pipeline.export_tasks().is_empty() {
proc
} else {
proc.with_task_fibers()
};
let (sensitivity, narrowed) =
match build_static_sensitivity(spec, &instance_signals[id], sim_handle, path)? {
Some((s, n)) => (Some(s), n),
None => (None, Vec::new()),
};
engine.set_registration_scope(Some(id.0));
let pid = if reactive_kernels.contains(&plan_index) {
let pid = engine.register_reactive_process(Box::new(proc), *kind, sensitivity, 0);
if program_initial_kernels.contains(&plan_index) {
engine.mark_program_initial(pid);
}
pid
} else {
engine.register_procedure(Box::new(proc), *kind, sensitivity, 0)
};
for (cell, windows) in &narrowed {
engine.narrow_static_sensitivity(pid, *cell, windows);
}
vj_sim::sched::proc_assert::register_process(plan_index as i64, pid);
if !vpi_libraries.is_empty() {
let (body, sym) = vpi_boot.kernel_dyn[plan_index].clone();
crate::vpi::dynobj::note_root(pid, body, sym);
}
engine.set_registration_scope(None);
}
Ok(())
};
register_procedural(&mut engine, true)?;
vj_exec::timing::register_all(
&timing_plan,
&timing_live,
&instance_sim_handle,
&mut engine,
);
for (ca_index, (name, id, read_set, var_init)) in cont_assign_kernel_names.iter().enumerate() {
let sim_handle = instance_sim_handle[id];
let kernel = pipeline.get_process_fn(name).ok_or_else(|| {
RunError::Jit(format!(
"get_process_fn: JIT did not produce a callable pointer for `{name}`"
))
})?;
let proc = CompiledKernelProcess::new(kernel, 0, sim_handle).with_instance_slot_mapping();
let spec = KernelSensitivitySpec {
read_set: read_set.clone(),
windows: HashMap::new(),
reset: None,
};
let sensitivity =
build_static_sensitivity(&spec, &instance_signals[id], sim_handle, path)?.map(|s| s.0);
if *var_init {
engine.set_registration_scope(Some(id.0));
engine.set_registration_static_initializer(true);
}
if reactive_cont_assigns.contains(&ca_index) {
engine.register_reactive_process(
Box::new(proc),
ProcessKind::AlwaysComb,
sensitivity,
0,
);
} else {
engine.register_process(Box::new(proc), ProcessKind::AlwaysComb, sensitivity, 0);
}
if *var_init {
engine.set_registration_scope(None);
engine.set_registration_static_initializer(false);
}
}
let mut wire_specs: Vec<crate::interconnect::WireSpec> = Vec::new();
for (wire, info) in planned_wires {
let (plain, src_cell) = vj_exec::wires::build_wire(&instance_sim_handle, &wire)
.map_err(|e| RunError::Lowering(LoweringError::unsupported(&e.0, "port wire")))?;
wire_specs.push(crate::interconnect::WireSpec {
plain,
src_cell,
info,
});
}
crate::interconnect::register(
wire_specs,
sdf_kick.map(|slot| loc_of(bank_id, slot)),
&instance_sim_handle,
&mut engine,
&interconnect_table,
);
interconnect_table
.lock()
.unwrap()
.set_pulse(timing.default_pulse_style(), timing.default_showcancelled());
verif
.register_samplers(&mut engine)
.map_err(|e| RunError::Jit(e.to_string()))?;
engine.set_timestep_end_hook(Box::new(move |now| {
verif.end_of_step();
if code_cov {
vj_runtime::codecov::sample();
}
crate::dump::sample(now.ticks)
}));
vj_exec::nets::register_group_resolvers(
&planned_groups,
&planned_networks,
needs_bank.then_some(bank_id),
&instance_sim_handle,
&mut engine,
)
.map_err(|e| RunError::Lowering(LoweringError::unsupported(&e.0, "net resolver")))?;
{
let symbols: Vec<String> = native_nets
.types
.iter()
.filter_map(|t| t.func.clone())
.collect();
vj_exec::usernets::register(
&native_nets,
&symbols,
&crate::verif_image::PipelineResolver(&pipeline),
needs_bank.then_some(bank_id),
&instance_sim_handle,
&mut engine,
)
.map_err(|e| RunError::Lowering(LoweringError::unsupported(&e.0, "net resolver")))?;
}
register_procedural(&mut engine, false)?;
if code_cov {
vj_runtime::codecov::set_stmt_sites(
vj_codegen::stmtcov::enabled().then(vj_codegen::stmtcov::take_sites),
);
}
vj_sim::sched::set_deferred_writer(vj_runtime::display_sink::write_deferred);
vj_sim::sched::set_violation_writer(vj_runtime::display_sink::write_deferred);
let dpi_out = crate::dpi::OutputGuard::set(stdout);
crate::vpi::callbacks::deliver_lifecycle(crate::vpi::consts::CB_END_OF_COMPILE, 0);
crate::vpi::state::set_phase(crate::vpi::state::Phase::Run);
crate::vpi::callbacks::deliver_lifecycle(crate::vpi::consts::CB_START_OF_SIMULATION, 0);
if crate::vpi::sim::active() {
crate::vpi::sim::enable_control_events(true);
crate::vpi::sim::deliver_assertion_sys_callbacks(true);
}
let code = if let Some(code) = crate::vpi::state::take_finish_request() {
code
} else {
loop {
let next = SchedTime {
ticks: engine.now().ticks + 1,
};
let result = engine.run_until(Some(next));
if _class_design_run.is_some() {
vj_sim::objects::maybe_collect_cycles();
}
let pending = take_output();
if !pending.is_empty() {
dpi_out.write(&pending).map_err(RunError::Io)?;
}
let requested = crate::vpi::state::take_finish_request();
match result {
vj_sim::sched::RunResult::Finished { code } => break code,
vj_sim::sched::RunResult::Completed => break requested.unwrap_or(0),
vj_sim::sched::RunResult::Deadline => {
if let Some(code) = requested {
break code;
}
}
vj_sim::sched::RunResult::Livelock(msg) => {
return Err(RunError::Livelock(msg));
}
}
}
};
engine.run_finals();
crate::vpi::callbacks::deliver_lifecycle(
crate::vpi::consts::CB_END_OF_SIMULATION,
engine.now().ticks,
);
crate::dump::finish(engine.now().ticks);
if vj_runtime::codecov::enabled() {
vj_runtime::codecov::uninstall();
}
if let Some(report) = vj_exec::covergroups::finish(plusargs) {
dpi_out.write(&take_output()).map_err(RunError::Io)?;
dpi_out.write(report.as_bytes()).map_err(RunError::Io)?;
}
dpi_out.write(&take_output()).map_err(RunError::Io)?;
drop(dpi_out);
if std::env::var("VJ_OBJECT_STATS").is_ok_and(|v| v == "1") {
eprintln!(
"vj: objects live={} collected={}",
vj_sim::objects::live_count(),
vj_sim::objects::collected_count()
);
eprintln!("vj: collections live={}", vj_sim::collections::live_count());
}
if let Some(msg) = vj_runtime::sdf_annotate::take_failure() {
return Err(RunError::SdfAnnotate(msg));
}
Ok(vj_runtime::severity::error_exit_code(
crate::vpi::state::strict_exit_code(code),
))
}
fn x_memory_value(
slot: &SignalSlot,
num_rows: u32,
windows: &[Option<(u32, u32)>],
) -> FourStateValue {
let (mut aval, mut bval) = (Vec::new(), Vec::new());
for r in 0..num_rows {
let base = r * slot.width;
let row: Vec<Option<(u32, u32)>> = windows
.iter()
.filter_map(|w| match w {
None => Some(None),
Some((lo, width)) => {
let (s, e) = (*lo.max(&base), (lo + width).min(base + slot.width));
(s < e).then(|| Some((s - base, e - s)))
}
})
.collect();
let v = x_window_value(
&SignalSlot {
class: SignalSlotClass::Wide,
..*slot
},
&row,
);
if let FourStateValue::Wide { aval: a, bval: b } = v {
aval.extend(a);
bval.extend(b);
}
}
FourStateValue::Wide { aval, bval }
}
fn fill_vpi_planner_facts(
boot: &mut crate::vpi::boot::VpiBoot,
timing_model: &vj_sim::netlist::specify::TimingModel,
body_id_to_instance: &HashMap<SymbolId, InstanceId>,
instance_signals: &HashMap<InstanceId, HashMap<SymbolId, SignalSlot>>,
tick_exp: i32,
) {
boot.path_keys = timing_model
.paths
.iter()
.map(|e| (e.instance, e.path_uid.clone()))
.collect();
boot.check_keys = timing_model
.timing_checks
.iter()
.map(|e| (e.instance, e.check_uid.clone()))
.collect();
boot.bodies = body_id_to_instance
.iter()
.map(|(b, i)| (b.clone(), *i))
.collect();
boot.signals = {
let used = crate::vpi::state::model()
.map(|m| crate::vpi::boot::used_signals(&m))
.unwrap_or_default();
used.iter()
.filter_map(|(body, sym)| {
let id = *body_id_to_instance.get(body)?;
let slot = *instance_signals.get(&id)?.get(sym)?;
Some((id, sym.clone(), slot))
})
.collect()
};
boot.tick_exp = tick_exp;
boot.canonicalize();
}
fn x_window_value(slot: &SignalSlot, windows: &[Option<(u32, u32)>]) -> FourStateValue {
let words = slot.width.div_ceil(64).max(1) as usize;
let mut driven = vec![0u64; words];
for w in windows {
let (lo, width) = w.unwrap_or((0, slot.width));
for b in lo..(lo + width).min(slot.width) {
driven[(b / 64) as usize] |= 1 << (b % 64);
}
}
let mut all = vec![u64::MAX; words];
if !slot.width.is_multiple_of(64) {
all[words - 1] = (1u64 << (slot.width % 64)) - 1;
}
match slot.class {
SignalSlotClass::Inline => {
FourStateValue::Narrow(u128::from(all[0] & !driven[0]) | (u128::from(all[0]) << 64))
}
_ => FourStateValue::Wide {
aval: driven.iter().zip(&all).map(|(d, a)| d & a).collect(),
bval: all.clone(),
},
}
}
fn fsm_images(
sources: &SourceSet,
elaborated: &netlist::Netlist<'_>,
registration_order: &[InstanceId],
instance_signals: &HashMap<InstanceId, HashMap<SymbolId, SignalSlot>>,
) -> Vec<vj_sim::image::FsmImage> {
use vj_sim::image::{FsmImage, FsmPartImage};
let mut defs: HashMap<String, Vec<crate::fsmpragma::FsmDef>> = HashMap::new();
for path in &sources.files {
if let Ok(text) = std::fs::read_to_string(path) {
for (m, v) in
crate::fsmpragma::extract(&text, &path.to_string_lossy(), &sources.include_dirs)
{
defs.entry(m).or_default().extend(v);
}
}
}
let mut out = Vec::new();
if defs.is_empty() {
return out;
}
for &id in registration_order {
let inst = elaborated.instance_symbol(id);
let Some(body) = inst.instance_body() else {
continue;
};
let Some(def) = inst.instance_definition().map(|d| d.name().to_string()) else {
continue;
};
let Some(fsms) = defs.get(&def) else { continue };
let signals = &instance_signals[&id];
let items = vj_sim::netlist::state_items(&body);
let params: HashMap<String, Option<u64>> = vj_sim::netlist::module_items(&body)
.iter()
.filter(|m| m.kind() == SymbolKind::Parameter)
.map(|m| {
(
m.name().to_string(),
m.constant_value()
.and_then(|v| v.as_integer().and_then(|i| i.as_u64())),
)
})
.collect();
'fsm: for f in fsms {
let mut parts = Vec::new();
let mut total = 0u32;
for (sig, range) in &f.parts {
let found = items.iter().find(|m| {
m.name() == sig.as_str()
&& matches!(m.kind(), SymbolKind::Variable | SymbolKind::Net)
});
let slot = found.and_then(|m| signals.get(&m.uid()));
let Some(slot) = slot.filter(|s| s.class == SignalSlotClass::Inline) else {
eprintln!(
"warning: FSM `{}` in `{def}`: state signal `{sig}` is not a scalar \
variable or net of the module (FSM coverage skipped)",
f.name
);
continue 'fsm;
};
let (msb, lsb) = range.unwrap_or((slot.width - 1, 0));
if msb < lsb || msb >= slot.width {
eprintln!(
"warning: FSM `{}` in `{def}`: part-select [{msb}:{lsb}] of `{sig}` is \
out of range (FSM coverage skipped)",
f.name
);
continue 'fsm;
}
total += msb - lsb + 1;
parts.push(FsmPartImage {
slot: slot.slot,
msb,
lsb,
});
}
if total > 64 {
eprintln!(
"warning: FSM `{}` in `{def}`: a state vector wider than 64 bits is not \
supported (FSM coverage skipped)",
f.name
);
continue;
}
let mut states = Vec::new();
for st in &f.states {
match params.get(st) {
Some(Some(v)) => states.push((st.clone(), *v)),
_ => {
eprintln!(
"warning: FSM `{}` in `{def}`: state `{st}` is not a parameter with \
a known value (FSM coverage skipped)",
f.name
);
continue 'fsm;
}
}
}
out.push(FsmImage {
instance: id,
name: f.name.clone(),
parts,
states,
});
}
}
out
}
fn input_parent_nets(inst: &sv_lang::Symbol<'_>) -> HashMap<SymbolId, SymbolId> {
let mut out = HashMap::new();
for conn in inst.instance_port_connections() {
let port = conn.port();
if port.kind() != SymbolKind::Port || port.port_direction() != ArgumentDirection::In {
continue;
}
let Some(expr) = conn.expression() else {
continue;
};
let target = crate::nets::connection_target(expr);
let member = port.port_internal_symbol().unwrap_or(port);
if vj_sim::symid::is_value_name(target.kind())
&& let Some(parent_net) = target.referenced_symbol()
&& parent_net.kind() == SymbolKind::Net
&& parent_net.value_type().map(|t| t.bit_width())
== member.value_type().map(|t| t.bit_width())
{
out.insert(member.uid(), parent_net.uid());
}
}
out
}
#[derive(Default)]
struct PortViews {
sides: HashMap<usize, Vec<vj_sim::image::SideSpec>>,
side_keys: HashMap<(InstanceId, SymbolId), u64>,
}
fn dump_port_views(
elaborated: &netlist::Netlist<'_>,
registration_order: &[InstanceId],
resolution: &crate::nets::Resolution,
native_groups: &[bool],
) -> PortViews {
let mut group_of: HashMap<(InstanceId, SymbolId), usize> = HashMap::new();
for (gi, g) in resolution.groups.iter().enumerate() {
if native_groups[gi] || g.switched {
continue;
}
for n in &g.nets {
group_of.insert(n.clone(), gi);
}
}
let key_of_slot: HashMap<u32, &crate::nets::DriverKey> =
resolution.contrib.iter().map(|(k, &s)| (s, k)).collect();
let in_subtree = |mut x: InstanceId, root: InstanceId| loop {
if x == root {
break true;
}
match elaborated.instance(x).parent {
Some(p) => x = p,
None => break false,
}
};
let mut views = PortViews::default();
for &id in registration_order {
let inst = elaborated.instance_symbol(id);
let Some(body) = inst.instance_body() else {
continue;
};
let parent_nets = input_parent_nets(&inst);
let ports: Vec<_> = body
.instance_body_ports()
.filter(|p| p.kind() == SymbolKind::Port)
.collect();
let in_ports: std::collections::HashSet<SymbolId> = ports
.iter()
.filter(|p| p.port_direction() == ArgumentDirection::In)
.map(|p| p.uid())
.collect();
for p in &ports {
let member = p.port_internal_symbol().unwrap_or(*p).uid();
let net = match (parent_nets.get(&member), elaborated.instance(id).parent) {
(Some(pn), Some(parent)) => (parent, pn.clone()),
_ => (id, member.clone()),
};
let Some(&gi) = group_of.get(&net) else {
continue;
};
let inside: Vec<bool> = resolution.groups[gi]
.drivers
.iter()
.map(|&(slot, _)| {
use crate::nets::DriverKey as K;
match key_of_slot.get(&slot) {
Some(K::Port(child, port, _)) if *child == id => !in_ports.contains(port),
Some(
K::Port(i, ..)
| K::Assign(i, ..)
| K::Prim(i, ..)
| K::PrimElem(i, ..)
| K::Hidden(i, _)
| K::SpecifyPath(i, _)
| K::ProcVar(i, _),
) => in_subtree(*i, id),
None => false,
}
})
.collect();
let key = vj_runtime::fmt::strength_key(&format!(
"dumpports:{}:{}",
id.0,
member.key_string()
));
views.side_keys.insert((id, member), key);
views
.sides
.entry(gi)
.or_default()
.push(vj_sim::image::SideSpec { key, inside });
}
}
views
}
fn dump_signal_images(
elaborated: &netlist::Netlist<'_>,
registration_order: &[InstanceId],
instance_signals: &HashMap<InstanceId, HashMap<SymbolId, SignalSlot>>,
trees: &[sv_lang::SyntaxTree],
views: &PortViews,
) -> Vec<vj_sim::image::DumpSignalImage> {
use vj_sim::image::{DumpSignalImage, PortDir, ScopeKind};
let mut table = Vec::new();
for &id in registration_order {
let inst = elaborated.instance_symbol(id);
let Some(body) = inst.instance_body() else {
continue;
};
let port_dirs: HashMap<SymbolId, PortDir> = body
.instance_body_ports()
.filter(|p| p.kind() == SymbolKind::Port)
.map(|p| {
let dir = match p.port_direction() {
ArgumentDirection::In => PortDir::Input,
ArgumentDirection::Out => PortDir::Output,
ArgumentDirection::InOut | ArgumentDirection::Ref => PortDir::Inout,
};
(p.port_internal_symbol().unwrap_or(p).uid(), dir)
})
.collect();
let key_of =
|sym: &sv_lang::Symbol<'_>| vj_runtime::fmt::strength_key(&sym.uid().key_string());
let input_parent_key: HashMap<SymbolId, u64> = input_parent_nets(&inst)
.into_iter()
.map(|(member, net)| (member, vj_runtime::fmt::strength_key(&net.key_string())))
.collect();
let inst_path = inst.hierarchical_path();
let inst_scope: Vec<String> = inst_path.split('.').map(str::to_owned).collect();
let def_name = inst
.instance_definition()
.map(|d| d.name().to_string())
.unwrap_or_default();
let signals = &instance_signals[&id];
for member in vj_sim::netlist::state_items(&body) {
let kind = member.kind();
if !matches!(
kind,
SymbolKind::Variable | SymbolKind::Net | SymbolKind::FormalArgument
) {
continue;
}
let Some(info) = signals.get(&member.uid()) else {
continue;
};
let Some(inner_kinds) = dump_scope_kinds(&member) else {
continue;
};
let is_event = member.value_type().is_some_and(|t| t.is_event());
if matches!(info.class, SignalSlotClass::Memory { .. })
|| member
.value_type()
.is_some_and(|t| vj_sim::state::storage::is_collection(&t))
|| (is_event && vj_sim::state::event_pool::event_pool(&body).is_some())
{
continue;
}
let mut scope = inst_scope.clone();
let path = member.hierarchical_path();
if let Some(rest) = path
.strip_prefix(inst_path.as_str())
.and_then(|r| r.strip_prefix('.'))
&& let Some((blocks, _)) = rest.rsplit_once('.')
{
scope.extend(blocks.split('.').map(str::to_owned));
}
let mut scope_kinds = vec![ScopeKind::Module; inst_scope.len()];
scope_kinds.extend(inner_kinds);
scope_kinds.resize(scope.len(), ScopeKind::Begin);
table.push(DumpSignalImage {
instance: id,
def_name: def_name.clone(),
scope,
scope_kinds,
name: member.name().to_string(),
width: info.width,
var_type: dump_var_type(&member, kind, is_event, trees),
slot: info.slot,
wide: info.class == SignalSlotClass::Wide,
real: member.value_type().is_some_and(|t| t.is_floating()),
class_handle: member.value_type().is_some_and(|t| t.is_class()),
port: port_dirs.get(&member.uid()).copied(),
side_key: views.side_keys.get(&(id, member.uid())).copied(),
strength_key: port_dirs.contains_key(&member.uid()).then(|| {
input_parent_key
.get(&member.uid())
.copied()
.unwrap_or_else(|| key_of(&member))
}),
});
}
}
table
}
fn dump_var_type(
member: &sv_lang::Symbol<'_>,
kind: SymbolKind,
is_event: bool,
trees: &[sv_lang::SyntaxTree],
) -> vj_sim::image::VcdVarType {
use vj_sim::image::VcdVarType as V;
if is_event {
return V::Event;
}
if kind == SymbolKind::Net {
return match crate::nets::net_kind_of(member, trees) {
vj_sim::state::NetKind::WAnd => V::WAnd,
vj_sim::state::NetKind::WOr => V::WOr,
vj_sim::state::NetKind::Tri => V::Tri,
vj_sim::state::NetKind::TriAnd => V::TriAnd,
vj_sim::state::NetKind::TriOr => V::TriOr,
vj_sim::state::NetKind::Tri0 => V::Tri0,
vj_sim::state::NetKind::Tri1 => V::Tri1,
vj_sim::state::NetKind::TriReg => V::TriReg,
vj_sim::state::NetKind::Supply0 => V::Supply0,
vj_sim::state::NetKind::Supply1 => V::Supply1,
_ => V::Wire,
};
}
let Some(ty) = member.value_type() else {
return V::Reg;
};
if ty.is_floating() {
return if ty.floating_kind() == sv_lang::FloatKind::RealTime {
V::RealTime
} else {
V::Real
};
}
let ty = if ty.is_enum() {
ty.enum_base().unwrap_or(ty)
} else {
ty
};
if ty.is_predefined_integer() {
match ty.predefined_integer_kind() {
sv_lang::PredefinedIntegerKind::Int | sv_lang::PredefinedIntegerKind::Integer => {
return V::Integer;
}
sv_lang::PredefinedIntegerKind::Time => return V::Time,
_ => {}
}
}
V::Reg
}
fn dump_scope_kinds(member: &sv_lang::Symbol<'_>) -> Option<Vec<vj_sim::image::ScopeKind>> {
use vj_sim::image::ScopeKind;
let mut kinds = Vec::new();
let mut cur = member.parent();
while let Some(p) = cur {
match p.kind() {
SymbolKind::InstanceBody => break,
SymbolKind::GenerateBlock => kinds.push(ScopeKind::Begin),
SymbolKind::StatementBlock => {
if p.name().is_empty() {
return None;
}
kinds.push(match p.statement_block_kind() {
Some(sv_lang::StatementBlockKind::Sequential) | None => ScopeKind::Begin,
Some(_) => ScopeKind::Fork,
});
}
SymbolKind::Subroutine => kinds.push(
if p.subroutine_kind() == Some(sv_lang::SubroutineKind::Task) {
ScopeKind::Task
} else {
ScopeKind::Function
},
),
_ => {}
}
cur = p.parent();
}
kinds.reverse();
Some(kinds)
}