use std::collections::HashMap;
use std::path::Path;
use inkwell::context::Context;
use sv_lang::kinds::SymbolKind;
use sv_lang::{Design, Expression, MethodFlags, Symbol};
use sv_sim_sim::netlist::InstanceId;
use sv_sim_sim::process::ProcessInstanceId;
use sv_sim_sim::process::dedup::{ProcessTemplateKey, assign_process_instance_ids};
use sv_sim_sim::symid::SymbolId;
use crate::accessor::declare_accessor_fns;
use crate::aot::{AotBackend, emit_process_count_fn};
use crate::error::{CodegenError, LoweringError};
use crate::orc::JitBackend;
use crate::process_lower::{
ProcessLoweringPlan, SignalSlot, build_process_side_table, emit_process_side_table,
emit_process_table, lower_cont_assign_kernel_fn, lower_kernel_fn, lower_kernel_fn_with_repr,
};
use crate::value::ValueRepr;
use sv_sim_sim::sched::{ForkKernel, SpawnSpec, WaitTableEntry};
type ForkBranch = (String, u32, Vec<(u32, u32)>, Vec<(u32, String, u32)>);
pub struct ContAssignLoweringSpec<'d> {
pub fn_name: String,
pub fn_sym: Symbol<'d>,
pub assign_expr: Expression<'d>,
pub net_target: Option<Symbol<'d>>,
pub signals: HashMap<SymbolId, SignalSlot>,
pub read_signals: HashMap<SymbolId, SignalSlot>,
pub force_site: Option<u64>,
pub port_source: Option<Symbol<'d>>,
pub var_init: bool,
pub hidden_target: Option<SignalSlot>,
}
pub struct CovergroupLoweringSpec<'d> {
pub prefix: String,
pub cg: Symbol<'d>,
pub type_index: u32,
pub kernels: Vec<crate::covergroup::CovKernel<'d>>,
pub refs: Vec<(Symbol<'d>, Symbol<'d>)>,
pub signals: HashMap<SymbolId, SignalSlot>,
pub gates: Vec<Expression<'d>>,
pub event_values: Vec<(Expression<'d>, bool)>,
}
pub struct SubroutineLoweringSpec<'d> {
pub sym: Symbol<'d>,
pub nettype_resolver: bool,
}
#[derive(Clone, Copy, Debug, Default)]
pub struct RealBindings {
pub get_scalar: Option<usize>,
pub set_scalar: Option<usize>,
pub get_wide: Option<usize>,
pub set_wide: Option<usize>,
pub get_mem_word: Option<usize>,
pub set_mem_word: Option<usize>,
pub nba_enqueue: Option<usize>,
pub display: Option<usize>,
pub write: Option<usize>,
pub finish: Option<usize>,
pub disable_fork: Option<usize>,
pub disable_fork_block: Option<usize>,
pub disable_named: Option<usize>,
pub nba_masked: Option<usize>,
pub nba_pulse: Option<usize>,
pub reactive_drive: Option<usize>,
pub nba_coll: Option<usize>,
pub fscan: Option<usize>,
pub sscan: Option<usize>,
pub fgets: Option<usize>,
pub fgetc: Option<usize>,
pub ungetc: Option<usize>,
pub ftell: Option<usize>,
pub ferror: Option<usize>,
pub force: Option<usize>,
pub release: Option<usize>,
pub force_active: Option<usize>,
pub force_row: Option<usize>,
pub set_delay: Option<usize>,
pub fired_term: Option<usize>,
pub fired_mask: Option<usize>,
pub aborted: Option<usize>,
pub cell_alloc: Option<usize>,
pub cell_store: Option<usize>,
pub edge_snapshot: Option<usize>,
pub edge_check: Option<usize>,
pub edge_load: Option<usize>,
pub set_fired_term: Option<usize>,
pub deferred_begin: Option<usize>,
pub deferred_end: Option<usize>,
pub deferred_call: Option<usize>,
pub assert_ctl: Option<usize>,
pub dump_vars: Option<usize>,
pub dump_ctl: Option<usize>,
pub dump_ports: Option<usize>,
pub timeformat: Option<usize>,
pub dpi_set_scope: Option<usize>,
pub dpi_sync: Option<usize>,
pub dpi_export_handle: Option<usize>,
pub dpi_export_variant: Option<usize>,
pub str_intern: Option<usize>,
pub str_op: Option<usize>,
pub str_cstr: Option<usize>,
pub str_to_real: Option<usize>,
pub str_from_real: Option<usize>,
pub str_sformatf: Option<usize>,
pub coll: Option<usize>,
pub real_math: Option<usize>,
pub readmem_rows: Option<usize>,
pub strobe_postponed: Option<usize>,
pub monitor_start: Option<usize>,
pub monitor_enable: Option<usize>,
pub time: Option<usize>,
pub dumpfile: Option<usize>,
pub dumpflush: Option<usize>,
pub random: Option<usize>,
pub urandom: Option<usize>,
pub urandom_range: Option<usize>,
pub random_reseed: Option<usize>,
pub urandom_reseed: Option<usize>,
pub dist: Option<usize>,
pub fopen: Option<usize>,
pub fopen_mcd: Option<usize>,
pub system: Option<usize>,
pub fclose: Option<usize>,
pub fflush: Option<usize>,
pub fwrite: Option<usize>,
pub fdisplay: Option<usize>,
pub fmonitor: Option<usize>,
pub fstrobe: Option<usize>,
pub fseek: Option<usize>,
pub feof: Option<usize>,
pub plusargs_test: Option<usize>,
pub plusargs_value: Option<usize>,
pub sdf_annotate: Option<usize>,
pub specparam_value: Option<usize>,
pub obj: Option<usize>,
pub cov: Option<usize>,
pub vpi_systf: Option<usize>,
pub vpi_dyn: Option<usize>,
pub process_class: Option<usize>,
pub weakref: Option<usize>,
pub semaphore: Option<usize>,
pub stoch_q: Option<usize>,
pub net_drivers: Option<usize>,
pub mailbox: Option<usize>,
pub wide: Option<usize>,
pub huge: Option<usize>,
pub proc_assert: Option<usize>,
pub assert_input: Option<usize>,
pub proc_assert_disable: Option<usize>,
pub slot_owner: Option<usize>,
pub inline_ptrs: Option<usize>,
}
impl RealBindings {
fn declared_in(&self, module: &inkwell::module::Module) -> Vec<(String, usize)> {
self.table()
.into_iter()
.filter(|(name, _)| module.get_function(name).is_some())
.filter_map(|(name, addr)| Some((name.to_owned(), addr?)))
.collect()
}
pub fn symbol_names() -> Vec<&'static str> {
Self::default()
.table()
.into_iter()
.map(|(n, _)| n)
.collect()
}
fn table(&self) -> [(&'static str, Option<usize>); 109] {
let RealBindings {
get_scalar,
set_scalar,
get_wide,
set_wide,
get_mem_word,
set_mem_word,
nba_enqueue,
display,
write,
finish,
disable_fork,
disable_fork_block,
disable_named,
nba_masked,
nba_pulse,
reactive_drive,
nba_coll,
fscan,
sscan,
fgets,
fgetc,
ungetc,
ftell,
ferror,
force,
release,
force_active,
force_row,
set_delay,
fired_term,
fired_mask,
aborted,
cell_alloc,
cell_store,
edge_snapshot,
edge_check,
edge_load,
set_fired_term,
deferred_begin,
deferred_end,
deferred_call,
assert_ctl,
dump_vars,
dump_ctl,
dump_ports,
timeformat,
real_math,
dpi_set_scope,
dpi_sync,
dpi_export_handle,
dpi_export_variant,
str_intern,
str_op,
str_cstr,
str_to_real,
str_from_real,
str_sformatf,
coll,
readmem_rows,
strobe_postponed,
monitor_start,
monitor_enable,
time,
dumpfile,
dumpflush,
random,
urandom,
urandom_range,
random_reseed,
urandom_reseed,
dist,
fopen,
fopen_mcd,
system,
fclose,
fflush,
fwrite,
fdisplay,
fmonitor,
fstrobe,
fseek,
feof,
plusargs_test,
plusargs_value,
sdf_annotate,
specparam_value,
obj,
cov,
vpi_systf,
vpi_dyn,
process_class,
weakref,
semaphore,
stoch_q,
net_drivers,
mailbox,
wide,
huge,
proc_assert,
assert_input,
proc_assert_disable,
slot_owner,
inline_ptrs,
} = *self;
let table: [(&'static str, Option<usize>); 109] = [
(crate::process_lower::SV_RT_SLOT_OWNER_SYMBOL, slot_owner),
(crate::accessor::SV_RT_INLINE_PTRS_SYMBOL, inline_ptrs),
(
crate::dpi::SV_RT_DPI_EXPORT_TASK_CALL_SYMBOL,
Some(sv_sim_sim::dpi_task::sv_rt_dpi_export_task_call as *const () as usize),
),
(
crate::dpi::SV_RT_DPI_EXPORT_TASK_ERROR_SYMBOL,
Some(sv_sim_sim::dpi_task::sv_rt_dpi_export_task_error as *const () as usize),
),
(
crate::dpi::SV_RT_DPI_TASK_SCOPE_SYMBOL,
Some(sv_sim_sim::dpi_task::sv_rt_dpi_task_scope as *const () as usize),
),
(
crate::dpi::SV_RT_DPI_FN_SCOPE_SYMBOL,
Some(sv_sim_sim::dpi_task::sv_rt_dpi_fn_scope as *const () as usize),
),
(
crate::dpi::SV_RT_DPI_FN_DISABLE_SET_SYMBOL,
Some(sv_sim_sim::dpi_task::sv_rt_dpi_fn_disable_set as *const () as usize),
),
(
crate::dpi::SV_RT_DPI_FN_DISABLE_INDEX_SYMBOL,
Some(sv_sim_sim::dpi_task::sv_rt_dpi_fn_disable_index as *const () as usize),
),
(crate::huge::SV_RT_HUGE_SYMBOL, huge),
(crate::process_lower::SV_RT_ASSERT_CTL_SYMBOL, assert_ctl),
(
crate::process_lower::SV_RT_PROC_ASSERT_DISABLE_SYMBOL,
proc_assert_disable,
),
(crate::process_lower::SV_RT_PROC_ASSERT_SYMBOL, proc_assert),
(
crate::process_lower::SV_RT_ASSERT_INPUT_SYMBOL,
assert_input,
),
(sv_sim_abi::accessor::SV_GET_SCALAR_SYMBOL, get_scalar),
(sv_sim_abi::accessor::SV_SET_SCALAR_SYMBOL, set_scalar),
(sv_sim_abi::accessor::SV_GET_WIDE_SYMBOL, get_wide),
(sv_sim_abi::accessor::SV_SET_WIDE_SYMBOL, set_wide),
(sv_sim_abi::accessor::SV_GET_MEM_WORD_SYMBOL, get_mem_word),
(sv_sim_abi::accessor::SV_SET_MEM_WORD_SYMBOL, set_mem_word),
(sv_sim_abi::process::NBA_ENQUEUE_SYMBOL, nba_enqueue),
(sv_sim_runtime::rtarg::SV_RT_DISPLAY_SYMBOL, display),
(sv_sim_runtime::rtarg::SV_RT_WRITE_SYMBOL, write),
(crate::process_lower::SV_RT_FINISH_SYMBOL, finish),
(
crate::process_lower::SV_RT_DISABLE_FORK_SYMBOL,
disable_fork,
),
(crate::process_lower::SV_RT_NBA_MASKED_SYMBOL, nba_masked),
(crate::process_lower::SV_RT_NBA_PULSE_SYMBOL, nba_pulse),
(
crate::process_lower::SV_RT_REACTIVE_DRIVE_SYMBOL,
reactive_drive,
),
(crate::process_lower::SV_RT_NBA_COLL_SYMBOL, nba_coll),
(
crate::process_lower::SV_RT_DISABLE_FORK_BLOCK_SYMBOL,
disable_fork_block,
),
(
crate::process_lower::SV_RT_DISABLE_NAMED_SYMBOL,
disable_named,
),
(crate::process_lower::SV_RT_SET_DELAY_SYMBOL, set_delay),
(crate::process_lower::SV_RT_FIRED_TERM_SYMBOL, fired_term),
(crate::process_lower::SV_RT_FIRED_MASK_SYMBOL, fired_mask),
(crate::process_lower::SV_RT_ABORTED_SYMBOL, aborted),
(crate::process_lower::SV_RT_CELL_ALLOC_SYMBOL, cell_alloc),
(crate::process_lower::SV_RT_CELL_STORE_SYMBOL, cell_store),
(
crate::process_lower::SV_RT_EDGE_SNAPSHOT_SYMBOL,
edge_snapshot,
),
(crate::process_lower::SV_RT_EDGE_CHECK_SYMBOL, edge_check),
(crate::process_lower::SV_RT_EDGE_LOAD_SYMBOL, edge_load),
(
crate::process_lower::SV_RT_SET_FIRED_TERM_SYMBOL,
set_fired_term,
),
(
crate::process_lower::SV_RT_DEFERRED_BEGIN_SYMBOL,
deferred_begin,
),
(
crate::process_lower::SV_RT_DEFERRED_END_SYMBOL,
deferred_end,
),
(
crate::deferred_call::SV_RT_DEFERRED_CALL_SYMBOL,
deferred_call,
),
(crate::fileio::SV_RT_FSCAN_SYMBOL, fscan),
(crate::fileio::SV_RT_SSCAN_SYMBOL, sscan),
(crate::fileio::SV_RT_FGETS_SYMBOL, fgets),
(crate::fileio::SV_RT_FGETC_SYMBOL, fgetc),
(crate::fileio::SV_RT_UNGETC_SYMBOL, ungetc),
(crate::fileio::SV_RT_FTELL_SYMBOL, ftell),
(crate::fileio::SV_RT_FERROR_SYMBOL, ferror),
(crate::force::SV_RT_FORCE_SYMBOL, force),
(crate::force::SV_RT_RELEASE_SYMBOL, release),
(crate::force::SV_RT_FORCE_ACTIVE_SYMBOL, force_active),
(crate::force::SV_RT_FORCE_ROW_SYMBOL, force_row),
(crate::process_lower::SV_RT_DUMP_VARS_SYMBOL, dump_vars),
(crate::process_lower::SV_RT_DUMP_CTL_SYMBOL, dump_ctl),
(crate::process_lower::SV_RT_DUMP_PORTS_SYMBOL, dump_ports),
(sv_sim_runtime::timefmt::SV_RT_TIMEFORMAT_SYMBOL, timeformat),
(crate::real::SV_RT_REAL_MATH_SYMBOL, real_math),
(crate::dpi::SV_RT_DPI_SET_SCOPE_SYMBOL, dpi_set_scope),
(crate::dpi::SV_RT_DPI_SYNC_SYMBOL, dpi_sync),
(
crate::dpi::SV_RT_DPI_EXPORT_HANDLE_SYMBOL,
dpi_export_handle,
),
(
crate::dpi::SV_RT_DPI_EXPORT_VARIANT_SYMBOL,
dpi_export_variant,
),
(
sv_sim_runtime::strings_rt::SV_RT_STR_INTERN_SYMBOL,
str_intern,
),
(sv_sim_runtime::strings_rt::SV_RT_STR_OP_SYMBOL, str_op),
(sv_sim_runtime::collections_rt::SV_RT_COLL_SYMBOL, coll),
(sv_sim_runtime::strings_rt::SV_RT_STR_CSTR_SYMBOL, str_cstr),
(
sv_sim_runtime::strings_rt::SV_RT_STR_TO_REAL_SYMBOL,
str_to_real,
),
(
sv_sim_runtime::strings_rt::SV_RT_STR_FROM_REAL_SYMBOL,
str_from_real,
),
(
sv_sim_runtime::strings_rt::SV_RT_STR_SFORMATF_SYMBOL,
str_sformatf,
),
(
sv_sim_runtime::readmem::SV_RT_READMEM_ROWS_SYMBOL,
readmem_rows,
),
(
crate::process_lower::SV_RT_STROBE_POSTPONED_SYMBOL,
strobe_postponed,
),
(
crate::process_lower::SV_RT_MONITOR_START_SYMBOL,
monitor_start,
),
(
crate::process_lower::SV_RT_MONITOR_ENABLE_SYMBOL,
monitor_enable,
),
(crate::expr::SV_RT_TIME_SYMBOL, time),
(sv_sim_runtime::fst::SV_RT_DUMPFILE_SYMBOL, dumpfile),
(sv_sim_runtime::fst::SV_RT_DUMPFLUSH_SYMBOL, dumpflush),
(crate::expr::SV_RT_RANDOM_SYMBOL, random),
(crate::expr::SV_RT_URANDOM_SYMBOL, urandom),
(crate::expr::SV_RT_URANDOM_RANGE_SYMBOL, urandom_range),
(crate::expr::SV_RT_RANDOM_RESEED_SYMBOL, random_reseed),
(crate::expr::SV_RT_URANDOM_RESEED_SYMBOL, urandom_reseed),
(crate::expr::SV_RT_DIST_SYMBOL, dist),
(sv_sim_runtime::file_io::SV_RT_FOPEN_SYMBOL, fopen),
(sv_sim_runtime::file_io::SV_RT_FOPEN_MCD_SYMBOL, fopen_mcd),
(sv_sim_runtime::file_io::SV_RT_SYSTEM_SYMBOL, system),
(sv_sim_runtime::file_io::SV_RT_FCLOSE_SYMBOL, fclose),
(sv_sim_runtime::file_io::SV_RT_FFLUSH_SYMBOL, fflush),
(sv_sim_runtime::file_io::SV_RT_FWRITE_SYMBOL, fwrite),
(sv_sim_runtime::file_io::SV_RT_FDISPLAY_SYMBOL, fdisplay),
(sv_sim_runtime::file_io::SV_RT_FMONITOR_SYMBOL, fmonitor),
(sv_sim_runtime::file_io::SV_RT_FSTROBE_SYMBOL, fstrobe),
(sv_sim_runtime::file_io::SV_RT_FSEEK_SYMBOL, fseek),
(sv_sim_runtime::file_io::SV_RT_FEOF_SYMBOL, feof),
(
sv_sim_runtime::plusargs::SV_RT_PLUSARGS_TEST_SYMBOL,
plusargs_test,
),
(
sv_sim_runtime::plusargs::SV_RT_PLUSARGS_VALUE_SYMBOL,
plusargs_value,
),
(
sv_sim_runtime::sdf_annotate::SV_RT_SDF_ANNOTATE_SYMBOL,
sdf_annotate,
),
(
sv_sim_runtime::specparam::SV_RT_SPECPARAM_VALUE_SYMBOL,
specparam_value,
),
(sv_sim_runtime::objects_rt::SV_RT_OBJ_SYMBOL, obj),
(sv_sim_runtime::covergroup_rt::SV_RT_COV_SYMBOL, cov),
(crate::vpi_systf::SV_RT_VPI_SYSTF_SYMBOL, vpi_systf),
(crate::vpi_dyn::SV_RT_VPI_DYN_SYMBOL, vpi_dyn),
(
sv_sim_runtime::process_class::SV_RT_PROCESS_SYMBOL,
process_class,
),
(sv_sim_runtime::weakref::SV_RT_WEAKREF_SYMBOL, weakref),
(
sv_sim_runtime::semaphore_rt::SV_RT_SEMAPHORE_SYMBOL,
semaphore,
),
(sv_sim_runtime::stoch_queue::SV_RT_STOCH_Q_SYMBOL, stoch_q),
("sv_rt_net_drivers", net_drivers),
(sv_sim_runtime::mailbox_rt::SV_RT_MAILBOX_SYMBOL, mailbox),
(sv_sim_runtime::wide_rt::SV_RT_WIDE_SYMBOL, wide),
];
table
}
}
#[derive(Debug, Clone)]
pub struct ExportTaskInfo {
pub c_name: String,
pub scope: String,
pub kernel: crate::process_lower::ExportTaskKernel,
pub fork_row: u32,
}
pub struct CompiledPipeline {
backend: JitBackend,
fork_table: Vec<SpawnSpec>,
fork_branches: Vec<ForkBranch>,
wait_table: Vec<WaitTableEntry>,
dpi_exports: Vec<(String, String)>,
dpi_export_variants: Vec<(String, String, u32)>,
export_tasks: Vec<ExportTaskInfo>,
assertions: Vec<crate::sva::emit::AssertionSpec>,
}
impl CompiledPipeline {
pub fn assertions(&self) -> &[crate::sva::emit::AssertionSpec] {
&self.assertions
}
pub fn assertion_snap(&self, name: &str) -> Option<crate::sva::emit::SnapFn> {
unsafe { self.backend.get_fn(&format!("{name}_snap")) }
}
pub fn assertion_leaf(&self, name: &str, leaf: u32) -> Option<crate::sva::emit::LeafFn> {
unsafe { self.backend.get_fn(&format!("{name}_l{leaf}")) }
}
pub fn assertion_live_leaf(&self, name: &str, leaf: u32) -> Option<crate::sva::emit::LeafFn> {
unsafe { self.backend.get_fn(&format!("{name}_lv{leaf}")) }
}
pub fn assertion_tick(&self, name: &str, clock: u32) -> Option<crate::sva::emit::TickFn> {
unsafe { self.backend.get_fn(&format!("{name}_t{clock}")) }
}
pub fn assertion_case(&self, name: &str, pair: u32) -> Option<crate::sva::emit::CaseFn> {
unsafe { self.backend.get_fn(&format!("{name}_c{pair}")) }
}
pub fn export_tasks(&self) -> &[ExportTaskInfo] {
&self.export_tasks
}
pub fn dpi_export_variants(&self) -> &[(String, String, u32)] {
&self.dpi_export_variants
}
pub fn dpi_export_thunks(&self) -> Result<Vec<(String, usize)>, LoweringError> {
self.dpi_exports
.iter()
.map(|(c_name, thunk)| {
self.get_global_ptr(thunk)
.map(|p| (c_name.clone(), p as usize))
.ok_or_else(|| LoweringError {
message: format!("JIT did not produce DPI export thunk `{thunk}`"),
at: "pipeline::CompiledPipeline::dpi_export_thunks".to_string(),
})
})
.collect()
}
pub fn wait_table(&self) -> std::sync::Arc<[WaitTableEntry]> {
std::sync::Arc::from(self.wait_table.clone())
}
pub fn fork_table(&self) -> std::sync::Arc<[SpawnSpec]> {
std::sync::Arc::from(self.fork_table.clone())
}
pub fn fork_kernels(&self) -> Result<std::sync::Arc<[Option<ForkKernel>]>, LoweringError> {
self.fork_branches
.iter()
.map(|(name, frame_word_count, captures, disable_scopes)| {
let kernel = self.get_process_fn(name).ok_or_else(|| LoweringError {
message: format!("JIT did not produce fork-branch kernel `{name}`"),
at: "pipeline::CompiledPipeline::fork_kernels".to_string(),
})?;
Ok(Some(ForkKernel {
kernel,
frame_word_count: *frame_word_count,
captures: std::sync::Arc::from(captures.as_slice()),
disable_scopes: std::sync::Arc::from(disable_scopes.as_slice()),
}))
})
.collect()
}
pub fn get_process_fn(&self, mangled: &str) -> Option<sv_sim_abi::ProcessFn> {
unsafe { self.backend.get_fn(mangled) }
}
pub fn get_sampled_guard(
&self,
name: &str,
) -> Option<extern "C" fn(sv_sim_abi::SimHandle) -> u8> {
unsafe { self.backend.get_fn(name) }
}
pub fn nettype_resolver(&self, symbol: &str) -> Option<crate::nettype::ResolverFn> {
unsafe { self.backend.get_fn(symbol) }
}
pub fn get_global_ptr(&self, mangled: &str) -> Option<*mut u8> {
unsafe { self.backend.get_fn(mangled) }
}
}
pub fn build_pipeline<'d>(
plans: &[ProcessLoweringPlan<'d>],
cont_assigns: &[ContAssignLoweringSpec<'d>],
subroutines: &[SubroutineLoweringSpec<'d>],
classes: &[(sv_sim_sim::netlist::ClassTemplateId, Symbol<'d>)],
covergroups: &[CovergroupLoweringSpec<'d>],
design: &'d Design,
bindings: RealBindings,
) -> Result<(CompiledPipeline, Vec<String>, Vec<String>), LoweringError> {
let _direct = crate::accessor::DirectInlineGuard::enter(bindings.inline_ptrs.is_some());
lower_design_module(
plans,
cont_assigns,
subroutines,
classes,
covergroups,
design,
|_ctx, module, parts| {
let runtime_symbols = bindings.declared_in(&module);
let dpi_symbols = crate::dpi::resolve_dpi_c_symbols(&parts.dpi_c_symbols)?;
if let Some(path) = std::env::var_os("SV_SIM_DUMP_IR") {
module.print_to_file(&path).map_err(|e| LoweringError {
message: format!("SV_SIM_DUMP_IR: {e}"),
at: "pipeline::build_pipeline".to_string(),
})?;
}
let t_ver = crate::timing::start();
module.verify().map_err(|e| LoweringError {
message: format!("sv_sim_design module failed LLVM verify: {e}"),
at: "pipeline::build_pipeline".to_string(),
})?;
crate::timing::end(t_ver, "module.verify", "");
let t_bc = crate::timing::start();
let bitcode = module.write_bitcode_to_memory().as_slice().to_vec();
crate::timing::end(t_bc, "write bitcode", &format!("{} bytes", bitcode.len()));
drop(module);
crate::orc::release_freed_memory();
let t_jit = crate::timing::start();
let backend = JitBackend::new().map_err(|e| LoweringError {
message: format!("failed to create JitBackend: {e}"),
at: "pipeline::build_pipeline".to_string(),
})?;
for (name, addr) in runtime_symbols.iter().chain(&dpi_symbols) {
backend.define_absolute_symbol(name, *addr)?;
}
backend
.add_module_split(&bitcode)
.map_err(|e| LoweringError::unsupported(&e.to_string(), "<jit>"))?;
crate::timing::end(t_jit, "JIT backend setup + parallel codegen + link", "");
Ok((
CompiledPipeline {
backend,
fork_table: parts.fork_table,
fork_branches: parts.fork_branches,
wait_table: parts.wait_table,
dpi_exports: parts.dpi_exports,
dpi_export_variants: parts.dpi_export_variants,
export_tasks: parts.export_tasks,
assertions: parts.assertions,
},
parts.names,
parts.cont_assign_names,
))
},
)
}
pub struct LoweredParts {
pub names: Vec<String>,
pub cont_assign_names: Vec<String>,
pub fork_table: Vec<SpawnSpec>,
pub fork_branches: Vec<ForkBranch>,
pub wait_table: Vec<WaitTableEntry>,
pub dpi_exports: Vec<(String, String)>,
pub dpi_export_variants: Vec<(String, String, u32)>,
pub export_tasks: Vec<ExportTaskInfo>,
pub assertions: Vec<crate::sva::emit::AssertionSpec>,
pub dpi_c_symbols: Vec<String>,
pub declared_runtime: Vec<&'static str>,
}
#[allow(clippy::too_many_arguments)]
pub fn lower_design_module<'d, R>(
plans: &[ProcessLoweringPlan<'d>],
cont_assigns: &[ContAssignLoweringSpec<'d>],
subroutines: &[SubroutineLoweringSpec<'d>],
classes: &[(sv_sim_sim::netlist::ClassTemplateId, Symbol<'d>)],
covergroups: &[CovergroupLoweringSpec<'d>],
design: &'d Design,
finish: impl for<'c> FnOnce(
&'c Context,
inkwell::module::Module<'c>,
LoweredParts,
) -> Result<R, LoweringError>,
) -> Result<R, LoweringError> {
let t_lower = crate::timing::start();
let _abi_memo = crate::func::AbiMemoGuard::enter();
let _uid_memo = sv_sim_sim::symid::UidMemoGuard::enter();
let ctx = inkwell::context::Context::create();
let module = ctx.create_module("sv_sim_design");
let fns = declare_accessor_fns(&ctx, &module);
let dpi_scope = crate::dpi::DpiDesignScope::enter(design);
let design_syntax_scope = crate::classes::DesignSyntaxScope::enter(design);
let _cov_scope = crate::covergroup::CovTypeScope::enter(
covergroups
.iter()
.map(|c| (crate::covergroup::type_key(&c.cg), c.type_index))
.collect(),
crate::covergroup::block_target_names(covergroups.iter().map(|c| c.cg))?,
);
let mut method_specs: Vec<(Symbol<'d>, Symbol<'d>)> = Vec::new();
let mut inlined_methods: Vec<Symbol<'d>> = Vec::new();
crate::classes::reset_class_registries();
let t_cls = crate::timing::start();
for &(id, class_sym) in classes {
let layout = crate::classes::register_template_in(id, class_sym, classes)?;
let _ = layout; if crate::classes::is_builtin_std_class(&class_sym).is_some() {
continue;
}
let members = sv_sim_sim::netlist::classify_members(class_sym)
.map_err(|e| LoweringError::unsupported_at(&format!("{e}"), &class_sym))?;
if let Some(ctor) = members.ctor {
method_specs.push((class_sym, ctor));
}
for m in members.methods {
let m = if m.kind() == SymbolKind::MethodPrototype {
match m.method_prototype_subroutine() {
Some(real) => real,
None => continue,
}
} else {
m
};
if m.subroutine_this_var().is_none() {
continue;
}
if sv_sim_sim::process::cfg::subroutine_can_suspend(&m) {
inlined_methods.push(m);
continue;
}
method_specs.push((class_sym, m));
}
}
crate::classes::register_ancestor_closures(classes);
crate::covergroup::register_derived_groups(classes);
let _method_registry = crate::classes::register_method_names(&method_specs);
for (_, method) in &method_specs {
let mangled = crate::func::mangled_subroutine_name(method)?;
if module.get_function(&mangled).is_some() {
continue;
}
let (fn_ty, _is_void) = crate::classes::method_fn_type(&ctx, method, ValueRepr::FourState)?;
module.add_function(&mangled, fn_ty, None);
}
crate::classes::register_virtual_impls(classes)?;
crate::timing::end(
t_cls,
"lower: class registration",
&format!("{} classes, {} methods", classes.len(), method_specs.len()),
);
let t_meth = crate::timing::start();
for (class_sym, method) in &method_specs {
let mangled = crate::func::mangled_subroutine_name(method)?;
let fv = module.get_function(&mangled).ok_or_else(|| LoweringError {
message: format!(
"internal error: class method/constructor `{mangled}` was not pre-declared \
by the class sub-pass's own first sub-pass"
),
at: "pipeline::build_pipeline".to_string(),
})?;
if fv.count_basic_blocks() > 0 {
continue;
}
let t_one = crate::timing::start();
crate::classes::lower_method_body(
&ctx,
&module,
*class_sym,
method,
ValueRepr::FourState,
fv,
)?;
crate::timing::record(t_one, &mangled, fv);
}
crate::timing::end(t_meth, "lower: class method bodies", "");
let mut sub_syms: Vec<Symbol<'d>> = subroutines.iter().map(|s| s.sym).collect();
let mut discovered = std::collections::HashSet::new();
for spec in subroutines.iter().filter(|s| s.nettype_resolver) {
if let Some(body) = sv_sim_sim::process::cfg::proc_body(&spec.sym) {
sub_syms.extend(
crate::module::discover_called_subroutines_seen(
[body.as_sem_node()],
&mut discovered,
)
.into_iter()
.map(|d| d.sym),
);
}
}
for method in method_specs
.iter()
.map(|(_, m)| m)
.chain(inlined_methods.iter())
{
if let Some(body) = sv_sim_sim::process::cfg::proc_body(method) {
sub_syms.extend(
crate::module::discover_called_subroutines_seen(
[body.as_sem_node()],
&mut discovered,
)
.into_iter()
.map(|d| d.sym),
);
}
}
for export in design.dpi_exports() {
let sub = export.subroutine();
if !sv_sim_sim::process::cfg::subroutine_can_suspend(&sub) {
sub_syms.push(sub);
}
if let Some(body) = sv_sim_sim::process::cfg::proc_body(&sub) {
sub_syms.extend(
crate::module::discover_called_subroutines_seen(
[body.as_sem_node()],
&mut discovered,
)
.into_iter()
.map(|d| d.sym),
);
}
}
for sym in &sub_syms {
let mangled = crate::func::mangled_subroutine_name(sym)?;
if module.get_function(&mangled).is_some() {
continue;
}
let (fn_ty, _is_void) =
crate::func::subroutine_fn_type(&ctx, sym, ValueRepr::FourState, false)?;
module.add_function(&mangled, fn_ty, None);
}
let t_sub = crate::timing::start();
for sym in &sub_syms {
let mangled = crate::func::mangled_subroutine_name(sym)?;
let fv = module.get_function(&mangled).ok_or_else(|| LoweringError {
message: format!(
"internal error: subroutine `{mangled}` was not pre-declared \
by the pre-pass's own first sub-pass"
),
at: "pipeline::build_pipeline".to_string(),
})?;
if fv.count_basic_blocks() > 0 {
continue;
}
let t_one = crate::timing::start();
crate::func::lower_function_body(&ctx, &module, sym, ValueRepr::FourState, fv, None)?;
crate::timing::record(t_one, &mangled, fv);
}
crate::timing::end(
t_sub,
"lower: subroutine bodies",
&format!("{} subroutines", sub_syms.len()),
);
for spec in subroutines.iter().filter(|s| s.nettype_resolver) {
crate::nettype::emit_resolver(&ctx, &module, &spec.sym)?;
}
for c in covergroups {
crate::covergroup::compile_kernels(
&ctx, &module, &fns, &c.signals, &c.cg, &c.prefix, &c.kernels, &c.refs,
)?;
crate::covergroup::compile_gates(
&ctx, &module, &fns, &c.signals, &c.cg, &c.prefix, &c.gates,
)?;
crate::covergroup::compile_event_values(
&ctx,
&module,
&fns,
&c.signals,
&c.cg,
&c.prefix,
&c.event_values,
)?;
}
let t_kern = crate::timing::start();
let mut names = Vec::with_capacity(plans.len());
let mut kernel_fvs = Vec::with_capacity(plans.len());
let mut branch_fvs = Vec::new();
let mut fork_table = Vec::new();
let mut branch_frames = Vec::new();
let mut wait_table = Vec::new();
let mut assertions = crate::sva::emit::emit_assertions(&ctx, &module, &fns, plans, design)?;
for plan in plans {
let fv = crate::process_lower::lower_kernel_fn_with_fork_branches(
&ctx,
&module,
&fns,
plan,
ValueRepr::FourState,
&mut branch_fvs,
&mut fork_table,
&mut branch_frames,
&mut wait_table,
)?;
names.push(fv.get_name().to_str().unwrap().to_string());
kernel_fvs.push(fv);
}
let mut export_tasks: Vec<ExportTaskInfo> = Vec::new();
for export in design.dpi_exports() {
let sub = export.subroutine();
if !sv_sim_sim::process::cfg::subroutine_can_suspend(&sub) {
continue;
}
let body = sub.parent().ok_or_else(|| {
LoweringError::unsupported_at("an exported subroutine outside any scope", &sub)
})?;
let signals = if body.kind() == SymbolKind::InstanceBody {
crate::module::signal_slots_of(&body)?
} else {
HashMap::new()
};
let k = crate::process_lower::lower_export_task_kernel(
&ctx,
&module,
&fns,
&sub,
signals,
format!("sv_sim_export_task_{}", export_tasks.len()),
ValueRepr::FourState,
&mut branch_fvs,
&mut fork_table,
&mut branch_frames,
&mut wait_table,
)?;
let fork_row = fork_table.len() as u32;
fork_table.push(sv_sim_sim::sched::SpawnSpec {
kernel: k.kernel,
entry_pc: k.entry_pc,
parent: 0,
disable_name: Vec::new(),
});
export_tasks.push(ExportTaskInfo {
c_name: export.c_identifier(),
scope: body.hierarchical_path().trim_end_matches(".").to_owned(),
kernel: k,
fork_row,
});
}
let mut spawn_kernels: Vec<(usize, u32, u32, u32, u32)> = Vec::new();
for (ai, spec) in assertions.iter().enumerate() {
let plan = &plans[spec.plan_index];
let calls = plan.assertion_calls.borrow();
for (k, site) in calls.iter().enumerate().filter(|(_, c)| c.spawns) {
let k_out = crate::process_lower::lower_match_item_task_kernel(
&ctx,
&module,
&fns,
site,
spec.captured.len(),
plan.signals.clone(),
format!("sv_sim_match_item_task_{}_{k}", spec.name),
ValueRepr::FourState,
&mut branch_fvs,
&mut fork_table,
&mut branch_frames,
&mut wait_table,
)?;
spawn_kernels.push((
ai,
k as u32,
k_out.kernel,
k_out.frame_words,
k_out.entry_pc,
));
}
}
crate::sampled::emit_guards(&ctx, &module, &fns, plans, design)?;
let mut fork_branches: Vec<ForkBranch> = branch_fvs
.iter()
.map(|fv| {
(
fv.get_name().to_str().unwrap().to_string(),
0,
Vec::new(),
Vec::new(),
)
})
.collect();
for (idx, words, captures, scopes) in branch_frames {
fork_branches[idx as usize].1 = words;
fork_branches[idx as usize].2 = captures;
fork_branches[idx as usize].3 = scopes;
}
for (ai, call, kernel, frame_words, entry_pc) in spawn_kernels {
assertions[ai]
.spawn_calls
.push(crate::sva::emit::SpawnCallSpec {
call,
kernel: fork_branches[kernel as usize].0.clone(),
frame_words,
entry_pc,
});
}
if !kernel_fvs.is_empty() {
emit_process_table(&ctx, &module, &kernel_fvs);
}
if !plans.is_empty() {
emit_process_side_table(&ctx, &module, &side_tables_for_plans(plans));
}
let mut dpi_exports = Vec::new();
let mut dpi_export_variants = Vec::new();
let mut timed_seen = 0usize;
let mut by_c_name: std::collections::BTreeMap<String, Vec<(String, Vec<String>)>> =
Default::default();
for export in design.dpi_exports() {
let sub = export.subroutine();
let c_name = export.c_identifier();
let timed = sv_sim_sim::process::cfg::subroutine_can_suspend(&sub);
let mangled = if timed {
let k = timed_seen;
timed_seen += 1;
format!("<timed export task {k}>")
} else {
crate::func::mangled_subroutine_name(&sub)?
};
let body = sub.parent().ok_or_else(|| {
LoweringError::unsupported_at("an exported subroutine outside any scope", &sub)
})?;
let variants = by_c_name.entry(c_name.clone()).or_default();
let k = match variants.iter().position(|(m, _)| *m == mangled) {
Some(k) => k,
None => {
let signals = if body.kind() == SymbolKind::InstanceBody {
crate::module::signal_slots_of(&body)?
} else {
HashMap::new()
};
let thunk = format!(
"{}${}",
crate::dpi::dpi_export_thunk_name(&c_name),
variants.len()
);
if timed {
let k = timed_seen - 1;
crate::dpi::lower_dpi_export_task_thunk(
&ctx,
&module,
&export,
export_tasks[k].fork_row,
&export_tasks[k].kernel,
&thunk,
)?;
} else {
crate::dpi::lower_dpi_export_thunk(
&ctx, &module, &fns, &export, &signals, &thunk,
)?;
}
variants.push((mangled, Vec::new()));
variants.len() - 1
}
};
let scope = body.hierarchical_path().trim_end_matches(".").to_owned();
variants[k].1.push(scope.clone());
dpi_export_variants.push((c_name, scope, k as u32));
}
for (c_name, variants) in &by_c_name {
let thunks: Vec<String> = (0..variants.len())
.map(|k| format!("{}${k}", crate::dpi::dpi_export_thunk_name(c_name)))
.collect();
crate::dpi::lower_dpi_export_dispatcher(&ctx, &module, c_name, &thunks)?;
dpi_exports.push((c_name.clone(), crate::dpi::dpi_export_thunk_name(c_name)));
}
let mut cont_assign_names = Vec::with_capacity(cont_assigns.len());
for spec in cont_assigns {
let fv = lower_cont_assign_kernel_fn(&ctx, &module, &fns, spec)?;
cont_assign_names.push(fv.get_name().to_str().unwrap().to_string());
}
crate::timing::end(
t_kern,
"lower: kernels (processes, assertions, DPI, cont-assigns)",
&format!(
"{} processes, {} cont-assigns",
plans.len(),
cont_assigns.len()
),
);
let dpi_c_symbols = dpi_scope.c_symbols();
drop(dpi_scope);
drop(design_syntax_scope);
let declared_runtime: Vec<&'static str> = RealBindings::symbol_names()
.into_iter()
.filter(|name| {
module.get_function(name).is_some_and(|f| {
inkwell::values::BasicValue::get_first_use(&f.as_global_value().as_pointer_value())
.is_some()
})
})
.collect();
crate::timing::end(t_lower, "lower: total (all of lower_design_module)", "");
crate::timing::report_module(&module);
finish(
&ctx,
module,
LoweredParts {
names,
cont_assign_names,
fork_table,
fork_branches,
wait_table,
dpi_exports,
dpi_export_variants,
export_tasks,
assertions,
dpi_c_symbols,
declared_runtime,
},
)
}
pub fn compile_to_object<'d>(
plans: &[ProcessLoweringPlan<'d>],
out_obj: &Path,
) -> Result<(Vec<String>, u32), CodegenError> {
if crate::sampled::plans_use_sampled_values(plans) {
return Err(LoweringError::unsupported(
"sampled value functions ($sampled/$rose/$fell/$stable/$changed/$past and the \
*_gclk family) are not supported in AOT output; run the design with `sv-sim run`",
"pipeline::compile_to_object",
)
.into());
}
crate::sva::emit::reject_static_assertions(plans, "pipeline::compile_to_object")?;
let ctx = Context::create();
let module = ctx.create_module("sv_sim_design");
let fns = declare_accessor_fns(&ctx, &module);
let mut names = Vec::with_capacity(plans.len());
let mut kernel_fvs = Vec::with_capacity(plans.len());
for plan in plans {
let fv = lower_kernel_fn(&ctx, &module, &fns, plan)?;
names.push(fv.get_name().to_str().unwrap().to_string());
kernel_fvs.push(fv);
}
if !kernel_fvs.is_empty() {
emit_process_table(&ctx, &module, &kernel_fvs);
}
if !plans.is_empty() {
emit_process_side_table(&ctx, &module, &side_tables_for_plans(plans));
}
let count = kernel_fvs.len() as u32;
emit_process_count_fn(&ctx, &module, count);
AotBackend::for_host()?.emit_object(&module, out_obj)?;
Ok((names, count))
}
#[allow(clippy::too_many_arguments)]
pub fn compile_design_to_object<'d>(
plans: &[ProcessLoweringPlan<'d>],
cont_assigns: &[ContAssignLoweringSpec<'d>],
subroutines: &[SubroutineLoweringSpec<'d>],
classes: &[(sv_sim_sim::netlist::ClassTemplateId, Symbol<'d>)],
covergroups: &[CovergroupLoweringSpec<'d>],
design: &'d Design,
base: sv_sim_sim::image::DesignImage,
fill: impl FnOnce(&mut sv_sim_sim::image::DesignImage, &LoweredParts) -> Result<(), LoweringError>,
with_main: bool,
out_obj: &Path,
) -> Result<sv_sim_sim::image::DesignImage, CodegenError> {
let result: Result<sv_sim_sim::image::DesignImage, CodegenError> = lower_design_module(
plans,
cont_assigns,
subroutines,
classes,
covergroups,
design,
|ctx, module, parts| {
let mut image = base;
fill(&mut image, &parts)?;
image.fork_table = parts.fork_table.clone();
image.wait_table = parts.wait_table.clone();
for (name, frame_words, captures, disable_scopes) in &parts.fork_branches {
let symbol = image.intern(name);
image
.fork_branches
.push(sv_sim_sim::image::ForkBranchImage {
symbol,
frame_words: *frame_words,
captures: captures.clone(),
disable_scopes: disable_scopes.clone(),
});
}
let module = &module;
emit_process_count_fn(ctx, module, parts.names.len() as u32);
let bytes = image.encode();
crate::aot::emit_image_globals(ctx, module, &bytes, &image.symbols)?;
if with_main {
crate::aot::emit_main_fn(ctx, module);
}
if let Some(path) = std::env::var_os("SV_SIM_DUMP_IR") {
module.print_to_file(&path).map_err(|e| LoweringError {
message: format!("SV_SIM_DUMP_IR: {e}"),
at: "pipeline::compile_design_to_object".to_string(),
})?;
}
module.verify().map_err(|e| LoweringError {
message: format!("sv_sim_design module failed LLVM verify: {e}"),
at: "pipeline::compile_design_to_object".to_string(),
})?;
Ok(AotBackend::for_host()
.and_then(|b| b.emit_object(module, out_obj))
.map(|()| image))
},
)?;
result
}
fn canonical_ids_for(owning_instances: &[InstanceId]) -> Vec<ProcessInstanceId> {
let mut order: Vec<InstanceId> = Vec::new();
let mut seen: std::collections::HashSet<InstanceId> = std::collections::HashSet::new();
for &id in owning_instances {
if seen.insert(id) {
order.push(id);
}
}
let ids = assign_process_instance_ids(&order);
owning_instances.iter().map(|id| ids[id]).collect()
}
fn side_tables_for_plans<'d>(
plans: &[ProcessLoweringPlan<'d>],
) -> Vec<crate::process_lower::ProcessSideTable> {
let owning: Vec<InstanceId> = plans.iter().map(|p| p.owning_instance).collect();
let canonical_ids = canonical_ids_for(&owning);
plans
.iter()
.zip(canonical_ids)
.map(|(plan, canonical_id)| build_process_side_table(plan, canonical_id))
.collect()
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct DpiThunkStub {
pub import_name: String,
}
pub fn dpi_thunk_stubs_for_design(design: &Design) -> Vec<DpiThunkStub> {
let mut seen = std::collections::HashSet::new();
let mut stubs = Vec::new();
for inst in design.top_instances() {
let Some(body) = inst.instance_body() else {
continue;
};
for sym in sv_sim_sim::netlist::module_items(&body) {
if sym.kind() != SymbolKind::Subroutine {
continue;
}
if !sym.subroutine_flags().contains(MethodFlags::DPI_IMPORT) {
continue;
}
let name = sym.name().to_string();
if seen.insert(name.clone()) {
stubs.push(DpiThunkStub { import_name: name });
}
}
}
stubs
}
pub fn compile_design_kernels<'d>(
design: &Design,
keyed_plans: &[(ProcessTemplateKey, ProcessLoweringPlan<'d>)],
value_repr: ValueRepr,
bindings: RealBindings,
) -> Result<(CompiledPipeline, Vec<String>, Vec<DpiThunkStub>), LoweringError> {
for (_, plan) in keyed_plans {
crate::sva::emit::reject_static_assertions(
std::slice::from_ref(plan),
"pipeline::compile_design_kernels",
)?;
}
let ctx = Context::create();
let module = ctx.create_module("sv_sim_design");
let fns = declare_accessor_fns(&ctx, &module);
let dpi_scope = crate::dpi::DpiDesignScope::enter(design);
let mut names: Vec<String> = Vec::with_capacity(keyed_plans.len());
let mut kernel_fvs = Vec::new();
let mut seen: std::collections::HashMap<ProcessTemplateKey, String> =
std::collections::HashMap::new();
for (key, plan) in keyed_plans {
if let Some(existing_name) = seen.get(key) {
names.push(existing_name.clone());
continue;
}
let fv = lower_kernel_fn_with_repr(&ctx, &module, &fns, plan, value_repr)?;
let name = fv.get_name().to_str().unwrap().to_string();
seen.insert(key.clone(), name.clone());
names.push(name);
kernel_fvs.push(fv);
}
if !kernel_fvs.is_empty() {
emit_process_table(&ctx, &module, &kernel_fvs);
}
if !keyed_plans.is_empty() {
let owning: Vec<InstanceId> = keyed_plans
.iter()
.map(|(_, plan)| plan.owning_instance)
.collect();
let canonical_ids = canonical_ids_for(&owning);
let tables: Vec<crate::process_lower::ProcessSideTable> = keyed_plans
.iter()
.zip(canonical_ids)
.map(|((_, plan), canonical_id)| build_process_side_table(plan, canonical_id))
.collect();
emit_process_side_table(&ctx, &module, &tables);
}
let runtime_symbols = bindings.declared_in(&module);
let dpi_symbols = crate::dpi::resolve_dpi_c_symbols(&dpi_scope.c_symbols())?;
drop(dpi_scope);
module.verify().map_err(|e| LoweringError {
message: format!("sv_sim_design module failed LLVM verify: {e}"),
at: "pipeline::compile_design_kernels".to_string(),
})?;
let bitcode = module.write_bitcode_to_memory().as_slice().to_vec();
drop(module);
drop(ctx);
crate::orc::release_freed_memory();
let backend = JitBackend::new().map_err(|e| LoweringError {
message: format!("failed to create JitBackend: {e}"),
at: "pipeline::compile_design_kernels".to_string(),
})?;
for (name, addr) in runtime_symbols.iter().chain(&dpi_symbols) {
backend.define_absolute_symbol(name, *addr)?;
}
backend
.add_module_split(&bitcode)
.map_err(|e| LoweringError::unsupported(&e.to_string(), "<jit>"))?;
let stubs = dpi_thunk_stubs_for_design(design);
Ok((
CompiledPipeline {
backend,
fork_table: Vec::new(),
fork_branches: Vec::new(),
wait_table: Vec::new(),
dpi_exports: Vec::new(),
dpi_export_variants: Vec::new(),
export_tasks: Vec::new(),
assertions: Vec::new(),
},
names,
stubs,
))
}
#[cfg(test)]
mod tests {
use super::*;
use std::collections::HashMap as StdHashMap;
use std::sync::atomic::{AtomicU32, Ordering};
use sv_sim_sim::symid::SymbolUid;
use sv_lang::kinds::SymbolKind;
use sv_sim_sim::netlist::InstanceId;
use sv_sim_sim::process::cfg::{ProcessLoopKind, build_cfg, lower_to_segments};
use sv_sim_sim::process::liveness::compute_liveness;
use sv_sim_sim::sched::{CompiledKernelProcess, CompiledProcess, ResumeArg, Suspend};
use crate::frontend::compile_source;
use crate::module::{SliceProcKind, build_comb_sensitivity_index, discover_processes};
use crate::process_lower::{
SignalSlot, SignalSlotClass, build_lowering_plan, find_named_value_symbol,
};
static LAST_SET_SCALAR_VALUE: AtomicU32 = AtomicU32::new(u32::MAX);
extern "C" fn stub_get_scalar(_h: sv_sim_abi::SimHandle, _slot: sv_sim_abi::SlotId) -> u128 {
0
}
extern "C" fn stub_set_scalar(
_h: sv_sim_abi::SimHandle,
_slot: sv_sim_abi::SlotId,
value: u128,
) {
LAST_SET_SCALAR_VALUE.store(value as u32, Ordering::SeqCst);
}
struct NoOpSchedulerOps;
impl sv_sim_sim::sched::SchedulerOps for NoOpSchedulerOps {
fn now(&self) -> sv_sim_sim::sched::SchedTime {
sv_sim_sim::sched::SchedTime { ticks: 0 }
}
fn current_region(&self) -> sv_sim_abi::Region {
sv_sim_abi::Region::Active
}
fn stage_nba(
&self,
_cell: sv_sim_sim::state::StateCellId,
_value: sv_sim_sim::state::FourStateValue,
_source: u32,
) {
}
fn commit_blocking_write(
&self,
_cell: sv_sim_sim::state::StateCellId,
_value: sv_sim_sim::state::FourStateValue,
) {
}
fn request_finish(&self, _code: i32) {}
fn defer_io(
&self,
_at: sv_sim_sim::sched::SchedTime,
_region: sv_sim_abi::Region,
_cb: sv_sim_sim::sched::DeferredIoCallback,
) {
}
fn register_region_callback(
&self,
_region: sv_sim_abi::Region,
_at: sv_sim_sim::sched::SchedTime,
_cb: Box<dyn sv_sim_sim::sched::RegionCallback>,
) {
}
}
#[test]
fn build_pipeline_produces_a_callable_kernel_via_get_process_fn() {
let design =
compile_source("module m(output logic q); initial q = 1; endmodule\n").unwrap();
let body = design
.top_instances()
.next()
.unwrap()
.instance_body()
.unwrap();
let proc_sym = body
.members()
.find(|s| s.kind() == SymbolKind::ProceduralBlock)
.unwrap();
let root = sv_sim_sim::process::cfg::proc_body(&proc_sym).unwrap();
let cfg = build_cfg(&root, ProcessLoopKind::OneShot).unwrap();
let frame = compute_liveness(&cfg).unwrap();
let segments = lower_to_segments(&cfg, &frame);
let q_sym = find_named_value_symbol(&segments, "q").unwrap();
let mut signals: StdHashMap<sv_sim_sim::symid::SymbolId, SignalSlot> = StdHashMap::new();
signals.insert(
q_sym.uid(),
SignalSlot {
slot: 0,
width: 1,
signed: false,
class: SignalSlotClass::Inline,
},
);
let plan = build_lowering_plan(
"proc_pipeline".to_string(),
proc_sym,
segments,
frame,
signals,
);
let bindings = RealBindings {
get_scalar: Some(stub_get_scalar as *const () as usize),
set_scalar: Some(stub_set_scalar as *const () as usize),
..RealBindings::default()
};
let (pipeline, names, cont_assign_names) =
build_pipeline(&[plan], &[], &[], &[], &[], &design, bindings).unwrap();
assert!(
cont_assign_names.is_empty(),
"no `ContAssignLoweringSpec` was passed, so no continuous-assign kernel name should \
come back"
);
assert_eq!(names.len(), 1);
let kernel = pipeline
.get_process_fn(&names[0])
.expect("the kernel this pipeline just lowered must be retrievable by its own name");
assert!(pipeline.get_process_fn("not_a_real_name").is_none());
let mut proc =
CompiledKernelProcess::new(kernel, 0, sv_sim_abi::SimHandle(std::ptr::null_mut()));
let ops = NoOpSchedulerOps;
let suspend = proc.resume(
sv_sim_abi::SimHandle(std::ptr::null_mut()),
ResumeArg::Start,
&ops,
);
assert!(
matches!(suspend, Suspend::Done),
"the real kernel must run to completion, got {suspend:?}"
);
assert_eq!(
LAST_SET_SCALAR_VALUE.load(Ordering::SeqCst),
1,
"the kernel's `initial q = 1;` must reach the real, bound stub_set_scalar with value 1"
);
}
#[test]
fn build_pipeline_lowers_a_real_dpi_export_thunk_callable_by_its_native_c_abi() {
let design = compile_source(
"module m;\n\
function automatic int add(int a, int b);\n\
return a + b;\n\
endfunction\n\
export \"DPI-C\" c_add = function add;\n\
endmodule\n",
)
.unwrap();
assert!(!design.diagnostics().has_errors());
extern "C" fn export_handle(_name: *const u8) -> *mut u8 {
std::ptr::NonNull::dangling().as_ptr()
}
let bindings = RealBindings {
dpi_export_handle: Some(export_handle as *const () as usize),
..RealBindings::default()
};
let (pipeline, names, _cont_assign_names) =
build_pipeline(&[], &[], &[], &[], &[], &design, bindings).unwrap();
assert!(
names.is_empty(),
"this design has no processes, so no kernel names are expected"
);
assert_eq!(
pipeline.dpi_export_thunks().unwrap().len(),
1,
"one thunk for c_add"
);
let c_add: unsafe extern "C" fn(i32, i32) -> i32 = unsafe {
pipeline
.backend
.get_fn(&crate::dpi::dpi_export_thunk_name("c_add"))
}
.expect("build_pipeline must have lowered and JIT-compiled the c_add DPI export thunk");
let got = unsafe { c_add(3, 4) };
assert_eq!(
got, 7,
"the real, pipeline-lowered DPI export thunk must be callable from outside SV and \
correctly re-enter the kernel (RT-007's own literal acceptance wording)"
);
}
#[test]
fn side_tables_for_plans_shares_one_canonical_id_per_owning_instance() {
let src = std::fs::read_to_string(
std::path::Path::new(env!("CARGO_MANIFEST_DIR"))
.join("../sv-sim-bin/tests/fixtures/counter.sv"),
)
.unwrap();
let design = compile_source(&src).unwrap();
let body = design
.top_instances()
.next()
.unwrap()
.instance_body()
.unwrap();
let comb_sensitivity = build_comb_sensitivity_index(&design).unwrap();
let processes = discover_processes(&body, &comb_sensitivity).unwrap();
assert_eq!(
processes.len(),
2,
"counter.sv is documented as exactly 2 processes"
);
let owning_instance = InstanceId(9);
let plans: Vec<_> = processes
.into_iter()
.enumerate()
.map(|(i, proc)| {
let root = sv_sim_sim::process::cfg::proc_body(&proc.sym).unwrap();
let loop_kind = match proc.kind {
SliceProcKind::Initial
| SliceProcKind::AlwaysComb
| SliceProcKind::AlwaysLatch
| SliceProcKind::Final
| SliceProcKind::StaticAssertion => ProcessLoopKind::OneShot,
SliceProcKind::Always | SliceProcKind::AlwaysFF => ProcessLoopKind::Looping,
};
let cfg = build_cfg(&root, loop_kind).unwrap();
let frame = compute_liveness(&cfg).unwrap();
let segments = lower_to_segments(&cfg, &frame);
let mut plan = build_lowering_plan(
format!("proc_{i}"),
proc.sym,
segments,
frame,
StdHashMap::new(),
);
plan.owning_instance = owning_instance;
plan
})
.collect();
let tables = side_tables_for_plans(&plans);
assert_eq!(tables.len(), 2);
assert_eq!(
tables[0].canonical_process_id, tables[1].canonical_process_id,
"both of counter.sv's processes share ONE owning elaborated instance, so \
side_tables_for_plans must give them the SAME canonical_process_id -- never \
a raw per-plan index"
);
}
#[test]
fn canonical_ids_for_groups_by_first_occurrence_of_each_owning_instance() {
let a = InstanceId(3);
let b = InstanceId(9);
let owning = [a, b, a];
let ids = canonical_ids_for(&owning);
assert_eq!(ids.len(), 3);
assert_eq!(
ids[0], ids[2],
"both entries of instance `a` must share one id"
);
assert_ne!(ids[0], ids[1], "instance `b`'s id must differ from `a`'s");
}
}