pub(crate) mod context;
pub(crate) mod declaration;
mod event;
mod expression;
pub(crate) mod inst_layout;
mod module;
pub(crate) mod opt;
pub(crate) mod site_table;
mod statement;
pub(crate) mod variable;
pub(crate) mod write_log;
pub use context::{Context, Conv};
pub use declaration::ProtoDeclaration;
pub use event::Event;
pub use expression::{Expression, ExpressionContext, ProtoDynamicBitSelect, ProtoExpression};
pub use module::{Module, ProtoModule};
pub use statement::{
CompiledBatchStmt, CompiledBlockStatement, CompiledStmt, ProtoAssignDynamicStatement,
ProtoAssignStatement, ProtoForBound, ProtoForRange, ProtoForStatement, ProtoIfStatement,
ProtoStatement, ProtoStatementBlock, ProtoStatements, ProtoSystemFunctionCall, RuntimeForBound,
RuntimeForRange, Statement, SystemFunctionCall, TbMethodKind, format_assert_message,
parse_hex_content, patch_stmt_log_buf,
};
pub use variable::{
ModuleVariableMeta, ModuleVariables, VarOffset, Variable, VariableElement, VariableMeta,
create_variable_meta, native_bytes, read_native_value, read_payload, value_size,
write_native_value, write_payload,
};
pub use veryl_analyzer::ir::{Op, Type, VarId, VarPath};
pub use veryl_analyzer::value::Value;
use crate::HashMap;
use crate::backend::{self, BackendRegistry, CompiledWhole, DispatchOutcome};
use crate::simulator::SimProfile;
use crate::simulator_error::SimulatorError;
use std::sync::Arc;
use std::sync::OnceLock;
use veryl_analyzer::ir as air;
use veryl_analyzer::value::MaskCache;
use veryl_parser::resource_table::StrId;
use veryl_parser::token_range::TokenRange;
pub struct Ir {
pub name: StrId,
pub token: TokenRange,
pub ports: HashMap<VarPath, VarId>,
pub ff_values: Box<[u8]>,
pub comb_values: Box<[u8]>,
pub use_4state: bool,
pub module_variables: ModuleVariables,
pub event_statements: HashMap<Event, Vec<Statement>>,
pub comb_statements: Vec<Statement>,
pub required_comb_passes: usize,
pub site_table: site_table::SiteTable,
pub inst_layout: inst_layout::InstLayout,
pub write_log_buffer: Box<write_log::WriteLogBuffer>,
pub disable_ff_opt: bool,
pub nontrivial_comb_scc: usize,
pub whole_comb: Option<Arc<dyn CompiledWhole>>,
pub aot_c_validate: bool,
pub whole_events: HashMap<Event, Arc<dyn CompiledWhole>>,
}
impl Ir {
pub fn from_module(module: Module, config: &Config, token: TokenRange) -> Ir {
let mut ir = Ir {
name: module.name,
token,
ports: module.ports,
ff_values: module.ff_values,
comb_values: module.comb_values,
use_4state: config.use_4state,
module_variables: module.module_variables,
event_statements: module.event_statements,
comb_statements: module.comb_statements,
required_comb_passes: module.required_comb_passes,
write_log_buffer: {
let (narrow_cap, wide_cap) = write_log_capacity(&module.site_table);
Box::new(write_log::WriteLogBuffer::with_capacity(
narrow_cap, wide_cap,
))
},
site_table: module.site_table,
inst_layout: module.inst_layout,
disable_ff_opt: config.disable_ff_opt,
nontrivial_comb_scc: module.nontrivial_comb_scc,
whole_comb: module.whole_comb,
aot_c_validate: config.aot_c_validate,
whole_events: module.whole_events,
};
ir.install_write_log_ptr();
ir
}
fn install_write_log_ptr(&mut self) {
let log_buf =
(&*self.write_log_buffer) as *const _ as *mut write_log::WriteLogBuffer as *mut u8;
for stmts in self.event_statements.values_mut() {
for s in stmts {
patch_stmt_log_buf(s, log_buf);
}
}
for s in &mut self.comb_statements {
patch_stmt_log_buf(s, log_buf);
}
}
pub fn settle_comb(&self, mask_cache: &mut MaskCache, profile: &mut SimProfile) {
#[cfg(feature = "profile")]
{
profile.settle_comb_count += 1;
}
let _ = profile;
if let Some(whole) = self.whole_comb.as_ref() {
static AOT_C_PASSES_OVERRIDE: OnceLock<Option<usize>> = OnceLock::new();
let validate = self.aot_c_validate;
let env_passes = *AOT_C_PASSES_OVERRIDE.get_or_init(|| {
std::env::var("VERYL_AOT_C_PASSES")
.ok()
.and_then(|s| s.parse::<usize>().ok())
});
let ff_ptr = self.ff_values.as_ptr();
let comb_ptr = self.comb_values.as_ptr() as *mut u8;
let log_ptr = (&*self.write_log_buffer as *const _ as *const u8) as *mut u8;
let passes = env_passes.unwrap_or(self.required_comb_passes).max(1);
if !validate {
for _ in 0..passes {
match whole.try_dispatch(ff_ptr, comb_ptr, log_ptr) {
DispatchOutcome::Done => {}
DispatchOutcome::NotReady => {
self.run_chunked_settle(mask_cache, profile);
return;
}
}
}
return;
}
backend::validate::settle_comb(self, whole.as_ref(), passes, mask_cache, profile);
return;
}
self.run_chunked_settle(mask_cache, profile);
}
pub(crate) fn run_chunked_settle(&self, mask_cache: &mut MaskCache, profile: &mut SimProfile) {
let _ = profile;
static MIN_PASSES_OVERRIDE: OnceLock<Option<usize>> = OnceLock::new();
let min_override = *MIN_PASSES_OVERRIDE.get_or_init(|| {
std::env::var("VERYL_MIN_PASSES_OVERRIDE")
.ok()
.and_then(|s| s.parse().ok())
});
let passes = min_override.unwrap_or(self.required_comb_passes);
for _ in 0..passes {
self.eval_comb_full(mask_cache, profile);
#[cfg(feature = "profile")]
{
profile.comb_eval_count += 1;
}
}
}
pub fn eval_comb_full(&self, mask_cache: &mut MaskCache, profile: &mut SimProfile) {
let _ = profile;
#[cfg(feature = "profile")]
let start = std::time::Instant::now();
for x in &self.comb_statements {
dispatch_stmt_fast(x, mask_cache);
}
#[cfg(feature = "profile")]
{
profile.eval_comb_full_ns += start.elapsed().as_nanos() as u64;
}
}
pub fn comb_stmt_count(&self) -> (usize, usize, usize) {
let mut binary = 0;
let mut interp = 0;
let mut total = 0;
for s in &self.comb_statements {
total += 1;
if s.is_compiled() {
binary += 1;
} else {
interp += 1;
}
}
(total, binary, interp)
}
pub fn dump_variables(&self) -> String {
format!("{}", self.module_variables)
}
pub fn jit_stats(&self) -> (usize, usize) {
let mut jit = 0;
let mut total = 0;
for stmts in self.event_statements.values() {
for s in stmts {
total += 1;
if s.is_compiled() {
jit += 1;
}
}
}
for s in &self.comb_statements {
total += 1;
if s.is_compiled() {
jit += 1;
}
}
(jit, total)
}
pub fn detailed_stats(&self) -> (usize, usize, usize, usize) {
let mut comb_jit = 0;
let mut comb_interp = 0;
let mut event_jit = 0;
let mut event_interp = 0;
for s in &self.comb_statements {
if s.is_compiled() {
comb_jit += 1;
} else {
comb_interp += 1;
}
}
for stmts in self.event_statements.values() {
for s in stmts {
if s.is_compiled() {
event_jit += 1;
} else {
event_interp += 1;
}
}
}
(comb_jit, comb_interp, event_jit, event_interp)
}
}
#[inline(always)]
pub fn dispatch_stmt_fast(s: &Statement, mask_cache: &mut MaskCache) {
match s {
Statement::Compiled(c) => unsafe {
(c.artifact.func)(c.ff, c.comb, c.log_buf);
},
Statement::CompiledBatch(c) => unsafe {
let f = c.artifact.func;
for &(ff, comb) in &c.args {
f(ff, comb, c.log_buf);
}
},
_ => {
s.eval_step(mask_cache);
}
}
}
unsafe impl Send for Ir {}
fn write_log_capacity(site_table: &site_table::SiteTable) -> (usize, usize) {
let mut narrow: usize = 0;
let mut wide: usize = 0;
let mut any_wide = false;
for s in &site_table.sites {
let nb = s.native_bytes as usize;
if nb <= 8 {
narrow += 2 * 2;
} else {
any_wide = true;
let chunks = nb.div_ceil(write_log::WRITE_LOG_WIDE_ENTRY_PAYLOAD_BYTES);
wide += 2 * chunks * 2;
}
}
let narrow_cap = narrow.max(4096);
let wide_cap = if any_wide { wide.max(64) } else { 0 };
(narrow_cap, wide_cap)
}
pub fn build_ir(ir: &air::Ir, top: StrId, config: &Config) -> Result<Ir, SimulatorError> {
for x in &ir.components {
if let air::Component::Module(x) = x
&& top == x.name
{
let token = x.token;
let mut context = context::Context {
config: config.clone(),
backends: BackendRegistry::for_config(config),
..Default::default()
};
let proto: ProtoModule = Conv::conv(&mut context, x)?;
let module = proto.instantiate();
return Ok(Ir::from_module(module, config, token));
}
}
Err(SimulatorError::TopModuleNotFound {
module_name: top.to_string(),
})
}
struct CacheEntry {
proto: ProtoModule,
token: TokenRange,
}
#[derive(Default)]
pub struct ProtoModuleCache {
entries: HashMap<StrId, CacheEntry>,
}
pub fn build_ir_cached(
ir: &air::Ir,
top: StrId,
config: &Config,
cache: &mut ProtoModuleCache,
) -> Result<Ir, SimulatorError> {
if let Some(entry) = cache.entries.get(&top) {
let module = entry.proto.instantiate();
return Ok(Ir::from_module(module, config, entry.token));
}
for x in &ir.components {
if let air::Component::Module(x) = x
&& top == x.name
{
let token = x.token;
let mut context = context::Context {
config: config.clone(),
backends: BackendRegistry::for_config(config),
..Default::default()
};
let proto: ProtoModule = Conv::conv(&mut context, x)?;
let module = proto.instantiate();
let result = Ir::from_module(module, config, token);
cache.entries.insert(top, CacheEntry { proto, token });
return Ok(result);
}
}
Err(SimulatorError::TopModuleNotFound {
module_name: top.to_string(),
})
}
#[derive(Clone, Debug, Default)]
pub struct Config {
pub use_4state: bool,
pub use_jit: bool,
pub dump_cranelift: bool,
pub dump_asm: bool,
pub disable_ff_opt: bool,
pub aot_c: bool,
pub aot_c_event: bool,
pub aot_c_async: bool,
pub aot_c_validate: bool,
pub aot_c_min_stmts: usize,
}
impl Config {
pub fn apply_env(&mut self) {
if std::env::var("VERYL_DUMP_ASM").ok().as_deref() == Some("1") {
self.dump_asm = true;
}
if std::env::var("VERYL_DUMP_CRANELIFT").ok().as_deref() == Some("1") {
self.dump_cranelift = true;
}
let env_bool = |k: &str| match std::env::var(k).ok().as_deref() {
Some("1") => Some(true),
Some("0") => Some(false),
_ => None,
};
if let Some(v) = env_bool("VERYL_AOT_C") {
self.aot_c = v;
}
if let Some(v) = env_bool("VERYL_AOT_C_EVENT") {
self.aot_c_event = v;
}
if let Some(v) = env_bool("VERYL_AOT_C_ASYNC") {
self.aot_c_async = v;
}
if let Some(v) = env_bool("VERYL_AOT_C_VALIDATE") {
self.aot_c_validate = v;
}
if let Ok(n) = std::env::var("VERYL_AOT_C_MIN_STMTS")
&& let Ok(n) = n.parse::<usize>()
{
self.aot_c_min_stmts = n;
}
}
}
impl Config {
pub fn all() -> Vec<Config> {
let mut ret = vec![];
let jit_options: &[bool] = if cfg!(target_family = "wasm") {
&[false]
} else {
&[false, true]
};
for use_4state in [false, true] {
for &use_jit in jit_options {
for disable_ff_opt in [false, true] {
ret.push(Config {
use_4state,
use_jit,
disable_ff_opt,
..Default::default()
});
}
}
}
#[cfg(not(target_family = "wasm"))]
if backend::aot_c::cc_available() {
for disable_ff_opt in [false, true] {
ret.push(Config {
use_4state: false,
use_jit: true,
disable_ff_opt,
aot_c: true,
aot_c_event: true,
aot_c_async: false,
..Default::default()
});
}
}
ret
}
}