use crate::{value::decode_vpi_value, Handle, Time, Value, ValueType};
use num_traits::FromPrimitive;
#[cfg(any(not(feature = "cb_info"), feature = "sv"))]
use std::{
collections::HashMap,
sync::{Mutex, OnceLock},
};
#[repr(u32)]
#[derive(Debug, Copy, Clone, PartialEq, Eq, num_derive::FromPrimitive)]
pub enum CbReason {
ValueChange = vpi_sys::cbValueChange,
Stmt = vpi_sys::cbStmt,
Force = vpi_sys::cbForce,
Release = vpi_sys::cbRelease,
AtStartOfSimTime = vpi_sys::cbAtStartOfSimTime,
ReadWriteSynch = vpi_sys::cbReadWriteSynch,
ReadOnlySynch = vpi_sys::cbReadOnlySynch,
NextSimTime = vpi_sys::cbNextSimTime,
AfterDelay = vpi_sys::cbAfterDelay,
EndOfCompile = vpi_sys::cbEndOfCompile,
StartOfSimulation = vpi_sys::cbStartOfSimulation,
EndOfSimulation = vpi_sys::cbEndOfSimulation,
Error = vpi_sys::cbError,
TchkViolation = vpi_sys::cbTchkViolation,
StartOfSave = vpi_sys::cbStartOfSave,
EndOfSave = vpi_sys::cbEndOfSave,
StartOfRestart = vpi_sys::cbStartOfRestart,
EndOfRestart = vpi_sys::cbEndOfRestart,
StartOfReset = vpi_sys::cbStartOfReset,
EndOfReset = vpi_sys::cbEndOfReset,
EnterInteractive = vpi_sys::cbEnterInteractive,
ExitInteractive = vpi_sys::cbExitInteractive,
InteractiveScopeChange = vpi_sys::cbInteractiveScopeChange,
UnresolvedSystf = vpi_sys::cbUnresolvedSystf,
PLIError = vpi_sys::cbPLIError,
Assign = vpi_sys::cbAssign,
Deassign = vpi_sys::cbDeassign,
Disable = vpi_sys::cbDisable,
Signal = vpi_sys::cbSignal,
NBASynch = vpi_sys::cbNBASynch,
AtEndOfSimTime = vpi_sys::cbAtEndOfSimTime,
#[cfg(feature = "sv")]
StartOfThread = vpi_sys::cbStartOfThread,
#[cfg(feature = "sv")]
EndOfThread = vpi_sys::cbEndOfThread,
#[cfg(feature = "sv")]
EnterThread = vpi_sys::cbEnterThread,
#[cfg(feature = "sv")]
StartOfFrame = vpi_sys::cbStartOfFrame,
#[cfg(feature = "sv")]
EndOfFrame = vpi_sys::cbEndOfFrame,
#[cfg(feature = "sv")]
SizeChange = vpi_sys::cbSizeChange,
#[cfg(feature = "sv")]
AssertionStart = vpi_sys::cbAssertionStart,
#[cfg(feature = "sv")]
AssertionSuccess = vpi_sys::cbAssertionSuccess,
#[cfg(feature = "sv")]
AssertionFailure = vpi_sys::cbAssertionFailure,
#[cfg(feature = "sv")]
AssertionStepSuccess = vpi_sys::cbAssertionStepSuccess,
#[cfg(feature = "sv")]
AssertionStepFailure = vpi_sys::cbAssertionStepFailure,
#[cfg(feature = "sv")]
AssertionDisable = vpi_sys::cbAssertionDisable,
#[cfg(feature = "sv")]
AssertionEnable = vpi_sys::cbAssertionEnable,
#[cfg(feature = "sv")]
AssertionReset = vpi_sys::cbAssertionReset,
#[cfg(feature = "sv")]
AssertionKill = vpi_sys::cbAssertionKill,
#[cfg(feature = "sv")]
AssertionSysInitialized = vpi_sys::cbAssertionSysInitialized,
#[cfg(feature = "sv")]
AssertionSysOn = vpi_sys::cbAssertionSysOn,
#[cfg(feature = "sv")]
AssertionSysOff = vpi_sys::cbAssertionSysOff,
#[cfg(feature = "sv")]
AssertionSysKill = vpi_sys::cbAssertionSysKill,
#[cfg(feature = "sv")]
AssertionSysEnd = vpi_sys::cbAssertionSysEnd,
#[cfg(feature = "sv")]
AssertionSysReset = vpi_sys::cbAssertionSysReset,
#[cfg(feature = "sv")]
AssertionVacuousSuccess = vpi_sys::cbAssertionVacuousSuccess,
#[cfg(feature = "sv")]
AssertionDisabledEvaluation = vpi_sys::cbAssertionDisabledEvaluation,
#[cfg(feature = "sv")]
AssertionSysLock = vpi_sys::cbAssertionSysLock,
#[cfg(feature = "sv")]
AssertionSysUnlock = vpi_sys::cbAssertionSysUnlock,
#[cfg(feature = "sv")]
AssertionLock = vpi_sys::cbAssertionLock,
#[cfg(feature = "sv")]
AssertionUnlock = vpi_sys::cbAssertionUnlock,
#[cfg(feature = "sv")]
AssertionEnablePassAction = vpi_sys::cbAssertionEnablePassAction,
#[cfg(feature = "sv")]
AssertionEnableFailAction = vpi_sys::cbAssertionEnableFailAction,
#[cfg(feature = "sv")]
AssertionDisablePassAction = vpi_sys::cbAssertionDisablePassAction,
#[cfg(feature = "sv")]
AssertionDisableFailAction = vpi_sys::cbAssertionDisableFailAction,
#[cfg(feature = "sv")]
AssertionEnableNonvacuousAction = vpi_sys::cbAssertionEnableNonvacuousAction,
#[cfg(feature = "sv")]
AssertionDisableVacuousAction = vpi_sys::cbAssertionDisableVacuousAction,
#[cfg(feature = "sv")]
AssertionSysEnablePassAction = vpi_sys::cbAssertionSysEnablePassAction,
#[cfg(feature = "sv")]
AssertionSysEnableFailAction = vpi_sys::cbAssertionSysEnableFailAction,
#[cfg(feature = "sv")]
AssertionSysDisablePassAction = vpi_sys::cbAssertionSysDisablePassAction,
#[cfg(feature = "sv")]
AssertionSysDisableFailAction = vpi_sys::cbAssertionSysDisableFailAction,
#[cfg(feature = "sv")]
AssertionSysEnableNonvacuousAction = vpi_sys::cbAssertionSysEnableNonvacuousAction,
#[cfg(feature = "sv")]
AssertionSysDisableVacuousAction = vpi_sys::cbAssertionSysDisableVacuousAction,
#[cfg(feature = "sv")]
CreateObj = vpi_sys::cbCreateObj,
#[cfg(feature = "sv")]
ReclaimObj = vpi_sys::cbReclaimObj,
#[cfg(feature = "sv")]
EndOfObject = vpi_sys::cbEndOfObject,
}
#[derive(Debug)]
pub struct CbData {
pub reason: CbReason,
pub obj: Handle,
pub time: Option<Time>,
pub value: Option<Value>,
pub value_type: Option<ValueType>,
pub index: i32,
}
fn time_from_cb_data(raw_time: vpi_sys::s_vpi_time) -> Option<Time> {
match raw_time.type_ as u32 {
vpi_sys::vpiSimTime => Some(Time::Sim(
u64::from(raw_time.high) << 32 | u64::from(raw_time.low),
)),
vpi_sys::vpiScaledRealTime => Some(Time::ScaledReal(raw_time.real)),
vpi_sys::vpiSuppressTime => Some(Time::Suppress),
_ => None,
}
}
struct CallbackState {
callback: Box<dyn Fn(&CbData)>,
time: Option<Box<vpi_sys::t_vpi_time>>,
value: Option<Box<vpi_sys::t_vpi_value>>,
}
#[cfg(not(feature = "cb_info"))]
fn callback_state_registry() -> &'static Mutex<HashMap<usize, usize>> {
static REGISTRY: OnceLock<Mutex<HashMap<usize, usize>>> = OnceLock::new();
REGISTRY.get_or_init(|| Mutex::new(HashMap::new()))
}
#[cfg(not(feature = "cb_info"))]
fn register_callback_state(handle: vpi_sys::vpiHandle, state_ptr: *mut CallbackState) {
if !handle.is_null() {
callback_state_registry()
.lock()
.expect("callback state registry poisoned")
.insert(handle as usize, state_ptr as usize);
}
}
#[cfg(not(feature = "cb_info"))]
fn take_callback_state(handle: vpi_sys::vpiHandle) -> Option<*mut CallbackState> {
callback_state_registry()
.lock()
.expect("callback state registry poisoned")
.remove(&(handle as usize))
.map(|state_ptr| state_ptr as *mut CallbackState)
}
fn default_cb_time() -> vpi_sys::t_vpi_time {
vpi_sys::t_vpi_time {
type_: vpi_sys::vpiSimTime as i32,
high: 0,
low: 0,
real: 0.0,
}
}
fn default_cb_value() -> vpi_sys::t_vpi_value {
vpi_sys::t_vpi_value {
format: vpi_sys::vpiObjTypeVal as i32,
value: vpi_sys::t_vpi_value__bindgen_ty_1 { integer: 0 },
}
}
fn cb_value_with_format(value_type: ValueType) -> vpi_sys::t_vpi_value {
vpi_sys::t_vpi_value {
format: value_type as i32,
value: vpi_sys::t_vpi_value__bindgen_ty_1 { integer: 0 },
}
}
#[cfg(feature = "sv")]
fn assertion_callback_state_registry() -> &'static Mutex<HashMap<usize, usize>> {
static REGISTRY: OnceLock<Mutex<HashMap<usize, usize>>> = OnceLock::new();
REGISTRY.get_or_init(|| Mutex::new(HashMap::new()))
}
#[cfg(feature = "sv")]
fn register_assertion_callback_state(
handle: vpi_sys::vpiHandle,
state_ptr: *mut AssertionCallbackState,
) {
if !handle.is_null() {
assertion_callback_state_registry()
.lock()
.expect("assertion callback state registry poisoned")
.insert(handle as usize, state_ptr as usize);
}
}
#[cfg(feature = "sv")]
fn take_assertion_callback_state(
handle: vpi_sys::vpiHandle,
) -> Option<*mut AssertionCallbackState> {
assertion_callback_state_registry()
.lock()
.expect("assertion callback state registry poisoned")
.remove(&(handle as usize))
.map(|state_ptr| state_ptr as *mut AssertionCallbackState)
}
#[cfg(feature = "sv")]
struct AssertionCallbackState {
callback: Box<dyn Fn(&AssertionCbData)>,
}
#[cfg(feature = "sv")]
#[derive(Debug)]
pub struct AssertionCbData {
pub reason: CbReason,
pub assertion: Handle,
pub time: Option<Time>,
pub attempt_info: Option<AssertionAttemptInfo>,
}
#[cfg(feature = "sv")]
#[derive(Debug)]
pub struct AssertionStepInfo {
pub matched_exprs: Vec<Handle>,
pub state_from: i32,
pub state_to: i32,
}
#[cfg(feature = "sv")]
#[derive(Debug)]
pub enum AssertionAttemptDetail {
FailExpr(Handle),
Step(AssertionStepInfo),
}
#[cfg(feature = "sv")]
#[derive(Debug)]
pub struct AssertionAttemptInfo {
pub detail: AssertionAttemptDetail,
pub attempt_start_time: Option<Time>,
}
#[cfg(feature = "sv")]
fn decode_assertion_attempt_info(
reason: CbReason,
info: vpi_sys::p_vpi_attempt_info,
) -> Option<AssertionAttemptInfo> {
if info.is_null() {
return None;
}
let info_ref = unsafe { &*info };
let attempt_start_time = time_from_cb_data(info_ref.attemptStartTime);
let detail = match reason {
CbReason::AssertionStepSuccess | CbReason::AssertionStepFailure => {
let step_ptr = unsafe { info_ref.detail.step };
if step_ptr.is_null() {
return None;
}
let step_ref = unsafe { &*step_ptr };
let count = if step_ref.matched_expression_count <= 0 {
0
} else {
usize::try_from(step_ref.matched_expression_count).ok()?
};
let matched_exprs = if count == 0 || step_ref.matched_exprs.is_null() {
Vec::new()
} else {
let slice = unsafe { std::slice::from_raw_parts(step_ref.matched_exprs, count) };
slice.iter().copied().map(Handle::from_raw).collect()
};
AssertionAttemptDetail::Step(AssertionStepInfo {
matched_exprs,
state_from: step_ref.stateFrom,
state_to: step_ref.stateTo,
})
}
_ => {
let fail_expr = unsafe { info_ref.detail.failExpr };
AssertionAttemptDetail::FailExpr(Handle::from_raw(fail_expr))
}
};
Some(AssertionAttemptInfo {
detail,
attempt_start_time,
})
}
#[cfg(feature = "sv")]
unsafe extern "C" fn assertion_trampoline(
reason: vpi_sys::PLI_INT32,
cb_time: vpi_sys::p_vpi_time,
assertion: vpi_sys::vpiHandle,
info: vpi_sys::p_vpi_attempt_info,
user_data: *mut vpi_sys::PLI_BYTE8,
) -> vpi_sys::PLI_INT32 {
if user_data.is_null() {
return 0;
}
let state_ptr = user_data.cast::<AssertionCallbackState>();
if state_ptr.is_null() {
return 0;
}
let Some(reason) = CbReason::from_u32(reason as u32) else {
return 0;
};
let mut data = AssertionCbData {
reason,
assertion: Handle::from_raw(assertion),
time: if cb_time.is_null() {
None
} else {
time_from_cb_data(unsafe { *cb_time })
},
attempt_info: decode_assertion_attempt_info(reason, info),
};
let state = unsafe { &*state_ptr };
(state.callback)(&data);
data.assertion.clear();
0
}
fn register_with_state(
reason: CbReason,
obj: vpi_sys::vpiHandle,
state: Box<CallbackState>,
) -> Handle {
let state_ptr = Box::into_raw(state);
let state_ref = unsafe { &mut *state_ptr };
let handle = unsafe {
let mut cb_data = vpi_sys::s_cb_data {
reason: reason as i32,
cb_rtn: Some(trampoline),
obj,
time: std::ptr::from_mut(
state_ref
.time
.as_mut()
.expect("register_with_state requires time storage")
.as_mut(),
),
value: std::ptr::from_mut(
state_ref
.value
.as_mut()
.expect("register_with_state requires value storage")
.as_mut(),
),
index: 0,
user_data: state_ptr.cast::<vpi_sys::PLI_BYTE8>(),
};
vpi_sys::vpi_register_cb(&raw mut cb_data)
};
if handle.is_null() {
unsafe {
let _ = Box::from_raw(state_ptr);
}
} else {
#[cfg(not(feature = "cb_info"))]
register_callback_state(handle, state_ptr);
}
Handle::from_raw(handle)
}
impl Handle {
pub fn register_cb<F>(&self, reason: CbReason, callback: F) -> Handle
where
F: Fn(&CbData) + 'static,
{
let user_data = Box::into_raw(Box::new(CallbackState {
callback: Box::new(callback),
time: None,
value: None,
}));
let handle = unsafe {
let mut cb_data = vpi_sys::s_cb_data {
reason: reason as i32,
cb_rtn: Some(trampoline),
obj: self.as_raw(),
time: std::ptr::null_mut(),
value: std::ptr::null_mut(),
index: 0,
user_data: user_data.cast::<vpi_sys::PLI_BYTE8>(),
};
vpi_sys::vpi_register_cb(&raw mut cb_data)
};
if handle.is_null() {
unsafe {
let _ = Box::from_raw(user_data);
}
} else {
#[cfg(not(feature = "cb_info"))]
register_callback_state(handle, user_data.cast::<CallbackState>());
}
Handle::from_raw(handle)
}
pub fn register_full_cb<F>(&self, reason: CbReason, callback: F) -> Handle
where
F: Fn(&CbData) + 'static,
{
let state = Box::new(CallbackState {
callback: Box::new(callback),
time: Some(Box::new(default_cb_time())),
value: Some(Box::new(default_cb_value())),
});
register_with_state(reason, self.as_raw(), state)
}
pub fn register_value_change_cb<F>(&self, value_type: ValueType, callback: F) -> Handle
where
F: Fn(&CbData) + 'static,
{
let state = Box::new(CallbackState {
callback: Box::new(callback),
time: Some(Box::new(default_cb_time())),
value: Some(Box::new(cb_value_with_format(value_type))),
});
register_with_state(CbReason::ValueChange, self.as_raw(), state)
}
}
unsafe extern "C" fn trampoline(cb_data: *mut vpi_sys::t_cb_data) -> i32 {
if cb_data.is_null() {
return 0; }
let user_data = unsafe { (*cb_data).user_data.cast::<CallbackState>() };
if user_data.is_null() {
return 0; }
let cb_data_ref = unsafe { &*cb_data };
let value = if cb_data_ref.value.is_null() {
None
} else {
Some(unsafe { *cb_data_ref.value })
};
let mut data = CbData {
reason: CbReason::from_u32(cb_data_ref.reason as u32)
.expect("received unknown callback reason from simulator"),
obj: Handle::from_raw(cb_data_ref.obj),
time: if cb_data_ref.time.is_null() {
None
} else {
time_from_cb_data(unsafe { *cb_data_ref.time })
},
value: value.and_then(|raw| decode_vpi_value(raw, cb_data_ref.obj)),
value_type: value.and_then(|raw| ValueType::from_u32(raw.format as u32)),
index: cb_data_ref.index,
};
let state = unsafe { &*user_data };
(state.callback)(&data);
data.obj.clear(); 0 }
pub fn register_cb<F>(reason: CbReason, callback: F) -> Handle
where
F: Fn(&CbData) + 'static,
{
let user_data = Box::into_raw(Box::new(CallbackState {
callback: Box::new(callback),
time: None,
value: None,
}));
let handle = unsafe {
let mut cb_data = vpi_sys::s_cb_data {
reason: reason as i32,
cb_rtn: Some(trampoline),
obj: std::ptr::null_mut(),
time: std::ptr::null_mut(),
value: std::ptr::null_mut(),
index: 0,
user_data: user_data.cast::<vpi_sys::PLI_BYTE8>(),
};
vpi_sys::vpi_register_cb(&raw mut cb_data)
};
if handle.is_null() {
unsafe {
let _ = Box::from_raw(user_data);
}
} else {
#[cfg(not(feature = "cb_info"))]
register_callback_state(handle, user_data.cast::<CallbackState>());
}
Handle::from_raw(handle)
}
pub fn register_full_cb<F>(reason: CbReason, callback: F) -> Handle
where
F: Fn(&CbData) + 'static,
{
let state = Box::new(CallbackState {
callback: Box::new(callback),
time: Some(Box::new(default_cb_time())),
value: Some(Box::new(default_cb_value())),
});
register_with_state(reason, std::ptr::null_mut(), state)
}
pub fn register_cb_with_time<F>(reason: CbReason, time: Time, callback: F) -> Handle
where
F: Fn(&CbData) + 'static,
{
let state = Box::new(CallbackState {
callback: Box::new(callback),
time: Some(Box::new(time.into())),
value: Some(Box::new(default_cb_value())),
});
register_with_state(reason, std::ptr::null_mut(), state)
}
#[cfg(feature = "sv")]
pub fn register_assertion_cb<F>(assertion: &Handle, reason: CbReason, callback: F) -> Handle
where
F: Fn(&AssertionCbData) + 'static,
{
let state_ptr = Box::into_raw(Box::new(AssertionCallbackState {
callback: Box::new(callback),
}));
let handle = unsafe {
vpi_sys::vpi_register_assertion_cb(
assertion.as_raw(),
reason as i32,
Some(assertion_trampoline),
state_ptr.cast::<vpi_sys::PLI_BYTE8>(),
)
};
if handle.is_null() {
unsafe {
let _ = Box::from_raw(state_ptr);
}
} else {
register_assertion_callback_state(handle, state_ptr);
}
Handle::from_raw(handle)
}
#[cfg(feature = "sv")]
pub fn remove_assertion_cb(handle: &Handle) {
if handle.is_null() {
return;
}
let state_ptr = take_assertion_callback_state(handle.as_raw());
unsafe {
vpi_sys::vpi_remove_cb(handle.as_raw());
if let Some(state_ptr) = state_ptr {
let _ = Box::from_raw(state_ptr);
}
}
}
#[cfg(feature = "cb_info")]
pub fn remove_cb(handle: &Handle) {
if !handle.is_null() {
unsafe {
let mut cb_data = vpi_sys::s_cb_data {
reason: 0,
cb_rtn: None,
obj: std::ptr::null_mut(),
time: std::ptr::null_mut(),
value: std::ptr::null_mut(),
index: 0,
user_data: std::ptr::null_mut(),
};
vpi_sys::vpi_get_cb_info(handle.as_raw(), &raw mut cb_data);
vpi_sys::vpi_remove_cb(handle.as_raw());
let trampoline_ptr = trampoline as unsafe extern "C" fn(*mut vpi_sys::t_cb_data) -> i32;
let is_internal = cb_data
.cb_rtn
.is_some_and(|cb| (cb as usize) == (trampoline_ptr as usize));
if is_internal && !cb_data.user_data.is_null() {
let _ = Box::from_raw(cb_data.user_data.cast::<CallbackState>());
}
}
}
}
#[cfg(not(feature = "cb_info"))]
pub fn remove_cb(handle: &Handle) {
if handle.is_null() {
return;
}
let state_ptr = take_callback_state(handle.as_raw());
unsafe {
vpi_sys::vpi_remove_cb(handle.as_raw());
if let Some(state_ptr) = state_ptr {
let _ = Box::from_raw(state_ptr);
}
}
}