use crate::types::TypeWidth;
use std::collections::HashMap;
use std::thread::ThreadId;
use sv_sim_sim::symid::SymbolUid;
use inkwell::context::Context;
use rayon::prelude::*;
use sv_lang::kinds::SymbolKind;
use sv_lang::{Compilation, Design, Session, Symbol, Walk};
use sv_sim_sim::symid::SymbolId;
use sv_sim_sim::process::cfg::{
ProcessLoopKind, build_cfg, contains_unschedulable_fork_join, lower_to_segments,
};
use sv_sim_sim::process::liveness::compute_liveness;
use crate::accessor::declare_accessor_fns;
use crate::error::LoweringError;
use crate::module::{
CombSensitivityIndex, SliceProcKind, build_comb_sensitivity_index, discover_processes,
};
use crate::process_lower::{
SignalSlot, SignalSlotClass, build_lowering_plan, lower_kernel_fn_with_repr,
};
use crate::value::ValueRepr;
static DRIVER_OPTIONS: std::sync::Mutex<Vec<String>> = std::sync::Mutex::new(Vec::new());
pub fn set_driver_options(options: Vec<String>) {
if let Ok(mut o) = DRIVER_OPTIONS.lock() {
*o = options;
}
}
#[derive(Clone, Copy, Debug)]
pub enum SystfReturn {
Typed(sv_lang::SystemFunctionType),
Sized {
width: u32,
signed: bool,
},
}
pub type SystfDeclaration = (String, SystfReturn, usize);
fn declare_systfs(
comp: &mut Compilation,
systfs: &[SystfDeclaration],
) -> Result<(), sv_lang::Error> {
for (name, ret, nargs) in systfs {
let args = vec![sv_lang::SystemFunctionType::Integer; *nargs];
match *ret {
SystfReturn::Typed(t) => comp.add_nonconstant_system_function_typed(name, t, &args)?,
SystfReturn::Sized { width, signed } => {
comp.add_nonconstant_system_function_sized(name, width, signed, &args)?
}
}
}
Ok(())
}
static SYSTEM_FUNCTIONS: std::sync::Mutex<Vec<SystfDeclaration>> =
std::sync::Mutex::new(Vec::new());
pub fn set_system_functions(decls: Vec<SystfDeclaration>) {
if let Ok(mut d) = SYSTEM_FUNCTIONS.lock() {
*d = decls;
}
}
pub fn session() -> Session {
Session::new().with_language_version(sv_lang::LanguageVersion::V1800_2023)
}
pub fn compilation(session: &Session) -> Result<Compilation, sv_lang::Error> {
static NEXT: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
let extra = DRIVER_OPTIONS.lock().map(|o| o.clone()).unwrap_or_default();
let empty = std::env::temp_dir().join(format!(
"sv-sim-std-{}-{}.sv",
std::process::id(),
NEXT.fetch_add(1, std::sync::atomic::Ordering::Relaxed)
));
std::fs::write(&empty, "")
.map_err(|e| sv_lang::Error::InvalidArgument(format!("{}: {e}", empty.display())))?;
let bag = (|| {
let mut driver = sv_lang::Driver::new()?;
let mut args = vec![
"sv-sim".to_string(),
"--std".to_string(),
"1800-2023".to_string(),
];
args.extend(extra);
args.push(empty.to_string_lossy().into_owned());
driver.parse_args(args)?;
driver.process_options()?;
driver.create_option_bag()
})();
let _ = std::fs::remove_file(&empty);
Compilation::from_option_bag(session, &bag?)
}
fn compile_design(comp: Compilation) -> Result<Design, sv_lang::Error> {
let mut design = comp.compile()?;
sv_sim_sim::netlist::itf::force_extern_impl_bodies(&mut design);
check_parameter_overrides(&design)?;
Ok(design)
}
fn check_parameter_overrides(design: &Design) -> Result<(), sv_lang::Error> {
let opts = DRIVER_OPTIONS.lock().map(|o| o.clone()).unwrap_or_default();
let mut it = opts.iter();
while let Some(o) = it.next() {
if o != "-G" {
continue;
}
let Some(name) = it.next().and_then(|v| v.split('=').next()) else {
continue;
};
if name.contains('.') {
continue;
}
let declared = design.top_instances().any(|inst| {
inst.instance_body().is_some_and(|b| {
b.members()
.any(|m| m.kind() == SymbolKind::Parameter && m.name() == name)
})
});
if !declared {
return Err(sv_lang::Error::InvalidArgument(format!(
"-G {name}: no top-level module declares a parameter `{name}`"
)));
}
}
Ok(())
}
pub fn compile_source(src: &str) -> Result<Design, sv_lang::Error> {
let session = session();
let mut comp = compilation(&session)?;
comp.add_source(src)?;
compile_design(comp)
}
pub fn compile_source_named(src: &str, path: &str) -> Result<Design, sv_lang::Error> {
compile_source_named_with_systfs(src, path, &[])
}
pub fn compile_source_named_with_systfs(
src: &str,
path: &str,
systfs: &[SystfDeclaration],
) -> Result<Design, sv_lang::Error> {
let session = session();
let tree = session.parse_named(src, path, path)?;
let mut comp = compilation(&session)?;
declare_systfs(&mut comp, systfs)?;
declare_iverilog_extensions(&mut comp, std::iter::once(src), systfs)?;
comp.add(&tree)?;
compile_design(comp)
}
fn declare_iverilog_extensions<'a>(
comp: &mut Compilation,
sources: impl Iterator<Item = &'a str>,
registered: &[SystfDeclaration],
) -> Result<(), sv_lang::Error> {
use sv_lang::SystemFunctionType::{Bit, Int, Integer, Time};
let sources: Vec<&str> = sources.collect();
for (name, ret, nargs) in [
(SIMTIME_NAME, Time, 0),
(IS_SIGNED_NAME, Bit, 1),
(SIZEOF_NAME, Int, 1),
] {
if registered.iter().any(|r| r.0 == name) || !sources.iter().any(|s| s.contains(name)) {
continue;
}
comp.add_nonconstant_system_function_typed(name, ret, &vec![Integer; nargs])?;
}
Ok(())
}
pub const SIMTIME_NAME: &str = "$simtime";
pub const IS_SIGNED_NAME: &str = "$is_signed";
pub const SIZEOF_NAME: &str = "$sizeof";
#[derive(Debug, Clone, Default)]
pub struct UnitOptions {
pub include_dirs: Vec<String>,
pub top_modules: Vec<String>,
pub library_maps: Vec<String>,
pub liblist: Vec<String>,
}
pub fn compile_units(
units: &[(String, String, String)],
options: &UnitOptions,
) -> Result<Design, sv_lang::Error> {
let (driver, bag) = units_driver(options)?;
let session = driver
.session()
.clone()
.with_language_version(sv_lang::LanguageVersion::V1800_2023);
let mut comp = Compilation::from_option_bag(&session, &bag)?;
let systfs = SYSTEM_FUNCTIONS
.lock()
.map(|d| d.clone())
.unwrap_or_default();
declare_systfs(&mut comp, &systfs)?;
declare_iverilog_extensions(&mut comp, units.iter().map(|u| u.2.as_str()), &systfs)?;
for (name, path, text) in units {
let tree = session.parse_named(text, name, path)?;
comp.add(&tree)?;
}
compile_design(comp)
}
pub fn parse_units(
units: &[(String, String, String)],
options: &UnitOptions,
) -> Result<(), sv_lang::Error> {
let (driver, _bag) = units_driver(options)?;
let mut errors = String::new();
let session = driver
.session()
.clone()
.with_language_version(sv_lang::LanguageVersion::V1800_2023);
for (name, path, text) in units {
let diags = session.parse_named(text, name, path)?.diagnostics();
if diags.has_errors() {
errors.push_str(&diags.to_string());
}
}
if errors.is_empty() {
Ok(())
} else {
Err(sv_lang::Error::InvalidArgument(errors))
}
}
fn units_driver(
options: &UnitOptions,
) -> Result<(sv_lang::Driver, sv_lang::OptionBag), sv_lang::Error> {
static NEXT: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
let empty = std::env::temp_dir().join(format!(
"sv-sim-units-{}-{}.sv",
std::process::id(),
NEXT.fetch_add(1, std::sync::atomic::Ordering::Relaxed)
));
std::fs::write(&empty, "")
.map_err(|e| sv_lang::Error::InvalidArgument(format!("{}: {e}", empty.display())))?;
let setup = (|| {
let mut driver = sv_lang::Driver::new()?;
let mut args = vec![
"sv-sim".to_string(),
"--std".to_string(),
"1800-2023".to_string(),
];
for dir in &options.include_dirs {
args.extend(["-I".to_string(), dir.clone()]);
}
for top in &options.top_modules {
args.extend(["--top".to_string(), top.clone()]);
}
args.extend(DRIVER_OPTIONS.lock().map(|o| o.clone()).unwrap_or_default());
args.push(empty.to_string_lossy().into_owned());
driver.parse_args(args)?;
driver.process_options()?;
let bag = driver.create_option_bag()?;
Ok::<_, sv_lang::Error>((driver, bag))
})();
let _ = std::fs::remove_file(&empty);
setup
}
pub fn compile_library_units(
files: &[String],
options: &UnitOptions,
) -> Result<Design, sv_lang::Error> {
let mut driver = sv_lang::Driver::new()?;
let mut args = vec![
"sv-sim".to_string(),
"--std".to_string(),
"1800-2023".to_string(),
];
for dir in &options.include_dirs {
args.extend(["-I".to_string(), dir.clone()]);
}
for map in &options.library_maps {
args.extend(["--libmap".to_string(), map.clone()]);
}
for lib in &options.liblist {
args.extend(["-L".to_string(), lib.clone()]);
}
for top in &options.top_modules {
args.extend(["--top".to_string(), top.clone()]);
}
args.extend(DRIVER_OPTIONS.lock().map(|o| o.clone()).unwrap_or_default());
args.extend(files.iter().cloned());
driver.parse_args(args)?;
driver.process_options()?;
let ok = driver.parse_sources()?;
if !ok {
return Err(sv_lang::Error::InvalidArgument(
"the library map or a source file failed to load".to_string(),
));
}
let trees = driver.trees();
let bag = driver.create_option_bag()?;
let session = driver
.session()
.clone()
.with_language_version(sv_lang::LanguageVersion::V1800_2023);
let mut comp = Compilation::from_option_bag(&session, &bag)?;
for tree in &trees {
comp.add(tree)?;
}
let design = compile_design(comp)?;
if design.top_instances().next().is_none() && options.top_modules.is_empty() {
return Err(sv_lang::Error::InvalidArgument(
"no top-level module in the default library `work`; name the design with \
--top LIB.CELL or --top CONFIG when the cells are mapped to other libraries \
(LRM 1800-2023 33.4.1.1)"
.to_string(),
));
}
Ok(design)
}
pub fn find_function<'d>(design: &'d Design, name: &str) -> Option<Symbol<'d>> {
for inst in design.top_instances() {
if let Some(body) = inst.instance_body()
&& let Some(sym) = body.find(name)
&& sym.kind() == SymbolKind::Subroutine
{
return Some(sym);
}
}
None
}
pub struct CodegenSession<'d> {
design: &'d Design,
mode: ValueRepr,
}
impl<'d> CodegenSession<'d> {
pub fn new(design: &'d Design, mode: ValueRepr) -> Self {
Self { design, mode }
}
}
#[derive(Debug, Clone)]
pub struct LoweredUnit {
pub unit_name: String,
pub kernel_names: Vec<String>,
pub ir: String,
pub thread_id: ThreadId,
}
fn sim_lowering_to_codegen_error(
e: sv_sim_sim::process::cfg::LoweringError,
sym: &Symbol,
) -> LoweringError {
LoweringError::unsupported_at(&e.to_string(), sym)
}
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,
}
}
fn signal_slots_of(body: &Symbol<'_>) -> HashMap<SymbolId, SignalSlot> {
let mut map = HashMap::new();
let mut slot_index: u32 = 0;
for member in sv_sim_sim::netlist::module_items(body) {
let kind = member.kind();
if !matches!(
kind,
SymbolKind::Variable | SymbolKind::Net | SymbolKind::Port
) {
continue;
}
if kind == SymbolKind::Port
&& (member.port_internal_symbol().is_some() || member.port_is_null_port())
{
continue;
}
let ty = if kind == SymbolKind::Port {
member.port_type()
} else {
member.value_type()
};
let Some(ty) = ty else {
continue;
};
map.insert(
member.uid(),
SignalSlot {
slot: slot_index,
width: ty.value_width(),
signed: ty.is_signed(),
class: SignalSlotClass::Inline,
},
);
slot_index += 1;
}
map
}
pub fn lower_one_instance_body<'d>(
body: Symbol<'d>,
comb_sensitivity: &CombSensitivityIndex,
mode: ValueRepr,
) -> Result<LoweredUnit, LoweringError> {
let ctx = Context::create();
let module = ctx.create_module(&format!("sv_sim_unit_{}", body.name()));
let fns = declare_accessor_fns(&ctx, &module);
let processes = discover_processes(&body, comb_sensitivity)?;
let signals = signal_slots_of(&body);
let mut kernel_names = Vec::with_capacity(processes.len());
for (i, proc) in processes.iter().enumerate() {
let root = sv_sim_sim::process::cfg::proc_body(&proc.sym).ok_or_else(|| {
LoweringError::unsupported_at("procedural block has no body statement", &proc.sym)
})?;
let cfg = build_cfg(&root, loop_kind_of(proc.kind))
.map_err(|e| sim_lowering_to_codegen_error(e, &proc.sym))?;
if contains_unschedulable_fork_join(&cfg) {
return Err(LoweringError::unsupported_at(
"fork/join has no scheduling semantics yet -- construction-only this wave \
(PR-069/docs/design/process.md §7)",
&proc.sym,
));
}
let frame =
compute_liveness(&cfg).map_err(|e| sim_lowering_to_codegen_error(e, &proc.sym))?;
let segments = lower_to_segments(&cfg, &frame);
let plan = build_lowering_plan(
format!("sv_sim_proc_{}_{}", body.name(), i),
proc.sym,
segments,
frame,
signals.clone(),
);
let fv = lower_kernel_fn_with_repr(&ctx, &module, &fns, &plan, mode)?;
kernel_names.push(fv.get_name().to_str().unwrap().to_string());
}
Ok(LoweredUnit {
unit_name: body.name().to_string(),
kernel_names,
ir: module.print_to_string().to_string(),
thread_id: std::thread::current().id(),
})
}
pub fn lower_all_instance_bodies<'d>(
sess: &CodegenSession<'d>,
) -> Result<Vec<LoweredUnit>, LoweringError> {
let comb_sensitivity = build_comb_sensitivity_index(sess.design)?;
let mut claimed: Vec<Symbol<'d>> = Vec::new();
sess.design.root().visit(|sym| {
if sv_sim_sim::netlist::is_uninstantiated_scope(&sym) {
return Walk::Skip;
}
if sym.kind() == SymbolKind::Instance
&& let Some(body) = sym.instance_body()
&& !claimed.iter().any(|u| body.instance_body_has_same_type(u))
{
claimed.push(body);
}
Walk::Continue
});
claimed
.par_iter()
.map(|body| lower_one_instance_body(*body, &comb_sensitivity, sess.mode))
.collect()
}