use std::cell::{Cell, RefCell};
use std::ffi::{CStr, CString};
use std::fmt;
use std::panic::{AssertUnwindSafe, catch_unwind};
use std::rc::Rc;
pub use num_bigint::BigUint;
use rustdv_gpi_sys::{self as sys, t_vpi_vecval};
#[cfg(test)]
use rustdv_vpi_stubs as _;
pub mod value;
pub use value::{Logic, LogicArray};
pub mod util;
pub use util::ToVpiWords;
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum HandleError {
NotFound {
name: String,
scope: String,
},
WrongKind {
name: String,
expected: &'static str,
actual: String,
},
NoTopModule,
}
impl fmt::Display for HandleError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
HandleError::NotFound { name, scope } => {
write!(f, "no object named '{name}' in scope '{scope}'")
}
HandleError::WrongKind {
name,
expected,
actual,
} => {
write!(f, "'{name}' is a {actual}, expected {expected}")
}
HandleError::NoTopModule => write!(f, "no top-level module found"),
}
}
}
impl std::error::Error for HandleError {}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ValueError {
FourState(String),
Unavailable,
InteriorNul,
Width {
want: u32,
have: usize,
},
}
impl fmt::Display for ValueError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
ValueError::FourState(s) => write!(f, "value '{s}' has x/z bits"),
ValueError::Unavailable => write!(f, "simulator did not return the requested value"),
ValueError::InteriorNul => write!(f, "VPI string contains an embedded NUL byte"),
ValueError::Width { want, have } => {
write!(f, "width mismatch: want {want}, have {have}")
}
}
}
}
impl std::error::Error for ValueError {}
#[derive(Copy, Clone, PartialEq, Eq)]
pub struct ObjHandle(sys::vpiHandle);
impl ObjHandle {
fn new(h: sys::vpiHandle) -> Option<Self> {
if h.is_null() {
None
} else {
Some(ObjHandle(h))
}
}
fn get(self, prop: i32) -> i32 {
unsafe { sys::vpi_get(prop, self.0) }
}
fn get_str(self, prop: i32) -> String {
unsafe {
let p = sys::vpi_get_str(prop, self.0);
if p.is_null() {
String::new()
} else {
CStr::from_ptr(p).to_string_lossy().into_owned()
}
}
}
}
#[derive(Copy, Clone)]
pub enum AnyHandle {
Hierarchy(HierarchyHandle),
Logic(LogicHandle),
Real(RealHandle),
String(StringHandle),
Struct(AggregateHandle),
Union(AggregateHandle),
Other(ObjHandle),
}
impl AnyHandle {
pub fn classify(h: ObjHandle) -> AnyHandle {
match h.get(sys::vpiType) {
sys::vpiModule => AnyHandle::Hierarchy(HierarchyHandle { h }),
sys::vpiRealVar => AnyHandle::Real(RealHandle { h }),
sys::vpiStringVar => AnyHandle::String(StringHandle { h }),
sys::vpiStructVar => AnyHandle::Struct(AggregateHandle { h }),
sys::vpiUnionVar => AnyHandle::Union(AggregateHandle { h }),
sys::vpiNet
| sys::vpiReg
| sys::vpiIntegerVar
| sys::vpiPort
| sys::vpiMemory
| sys::vpiLongIntVar
| sys::vpiShortIntVar
| sys::vpiIntVar
| sys::vpiByteVar
| sys::vpiEnumVar
| sys::vpiBitVar => {
let width = h.get(sys::vpiSize).max(0) as u32;
AnyHandle::Logic(LogicHandle { h, width })
}
_ => AnyHandle::Other(h),
}
}
fn object(self) -> ObjHandle {
match self {
Self::Hierarchy(h) => h.h,
Self::Logic(h) => h.h,
Self::Real(h) => h.h,
Self::String(h) => h.h,
Self::Struct(h) | Self::Union(h) => h.h,
Self::Other(h) => h,
}
}
fn wrong_kind(self, expected: &'static str) -> HandleError {
let h = self.object();
HandleError::WrongKind {
name: h.get_str(sys::vpiFullName),
expected,
actual: format!("vpiType {}", h.get(sys::vpiType)),
}
}
pub fn as_logic(self) -> Result<LogicHandle, HandleError> {
match self {
Self::Logic(h) => Ok(h),
other => Err(other.wrong_kind("signal")),
}
}
pub fn as_hierarchy(self) -> Result<HierarchyHandle, HandleError> {
match self {
Self::Hierarchy(h) => Ok(h),
other => Err(other.wrong_kind("module")),
}
}
pub fn as_real(self) -> Result<RealHandle, HandleError> {
match self {
Self::Real(h) => Ok(h),
other => Err(other.wrong_kind("real")),
}
}
pub fn as_string(self) -> Result<StringHandle, HandleError> {
match self {
Self::String(h) => Ok(h),
other => Err(other.wrong_kind("string")),
}
}
pub fn as_aggregate(self) -> Result<AggregateHandle, HandleError> {
match self {
Self::Struct(h) | Self::Union(h) => Ok(h),
other => Err(other.wrong_kind("struct or union")),
}
}
}
#[derive(Copy, Clone)]
pub struct HierarchyHandle {
h: ObjHandle,
}
impl HierarchyHandle {
pub fn null_for_test() -> HierarchyHandle {
HierarchyHandle {
h: ObjHandle(std::ptr::null_mut()),
}
}
pub fn child(&self, name: &str) -> Result<AnyHandle, HandleError> {
let cname = CString::new(name).expect("NUL in signal name");
let h = unsafe { sys::vpi_handle_by_name(cname.as_ptr(), self.h.0) };
match ObjHandle::new(h) {
Some(h) => Ok(AnyHandle::classify(h)),
None => Err(HandleError::NotFound {
name: name.into(),
scope: self.full_name(),
}),
}
}
pub fn signal(&self, name: &str) -> Result<LogicHandle, HandleError> {
self.child(name)?.as_logic()
}
pub fn name(&self) -> String {
self.h.get_str(sys::vpiName)
}
pub fn full_name(&self) -> String {
self.h.get_str(sys::vpiFullName)
}
pub fn children(&self) -> Vec<AnyHandle> {
let mut out = Vec::new();
for t in [sys::vpiModule, sys::vpiNet, sys::vpiReg] {
unsafe {
let it = sys::vpi_iterate(t, self.h.0);
if it.is_null() {
continue;
}
loop {
let c = sys::vpi_scan(it);
if c.is_null() {
break; }
if let Some(h) = ObjHandle::new(c) {
out.push(AnyHandle::classify(h));
}
}
}
}
out
}
pub fn real(&self, name: &str) -> Result<RealHandle, HandleError> {
self.child(name)?.as_real()
}
pub fn string(&self, name: &str) -> Result<StringHandle, HandleError> {
self.child(name)?.as_string()
}
pub fn aggregate(&self, name: &str) -> Result<AggregateHandle, HandleError> {
self.child(name)?.as_aggregate()
}
}
#[derive(Copy, Clone, PartialEq, Eq)]
pub struct RealHandle {
h: ObjHandle,
}
impl RealHandle {
pub fn name(&self) -> String {
self.h.get_str(sys::vpiName)
}
pub fn full_name(&self) -> String {
self.h.get_str(sys::vpiFullName)
}
pub fn get(&self) -> Result<f64, ValueError> {
let mut value = sys::t_vpi_value {
format: sys::vpiRealVal,
value: sys::u_vpi_value_union { real: 0.0 },
};
unsafe {
sys::vpi_get_value(self.h.0, &mut value);
if value.format != sys::vpiRealVal {
return Err(ValueError::Unavailable);
}
Ok(value.value.real)
}
}
pub fn set_now(&self, value: f64) {
let mut value = sys::t_vpi_value {
format: sys::vpiRealVal,
value: sys::u_vpi_value_union { real: value },
};
unsafe {
sys::vpi_put_value(self.h.0, &mut value, std::ptr::null_mut(), sys::vpiNoDelay);
}
}
}
#[derive(Copy, Clone, PartialEq, Eq)]
pub struct StringHandle {
h: ObjHandle,
}
impl StringHandle {
pub fn name(&self) -> String {
self.h.get_str(sys::vpiName)
}
pub fn full_name(&self) -> String {
self.h.get_str(sys::vpiFullName)
}
pub fn get(&self) -> Result<String, ValueError> {
let mut value = sys::t_vpi_value {
format: sys::vpiStringVal,
value: sys::u_vpi_value_union {
str_: std::ptr::null_mut(),
},
};
unsafe {
sys::vpi_get_value(self.h.0, &mut value);
if value.format != sys::vpiStringVal {
return Err(ValueError::Unavailable);
}
if value.value.str_.is_null() {
return Err(ValueError::Unavailable);
}
Ok(CStr::from_ptr(value.value.str_)
.to_string_lossy()
.into_owned())
}
}
pub fn set_now(&self, value: &str) -> Result<(), ValueError> {
let text = CString::new(value).map_err(|_| ValueError::InteriorNul)?;
let mut value = sys::t_vpi_value {
format: sys::vpiStringVal,
value: sys::u_vpi_value_union {
str_: text.as_ptr().cast_mut(),
},
};
unsafe {
sys::vpi_put_value(self.h.0, &mut value, std::ptr::null_mut(), sys::vpiNoDelay);
}
Ok(())
}
}
#[derive(Copy, Clone)]
pub struct AggregateHandle {
h: ObjHandle,
}
impl AggregateHandle {
pub fn name(&self) -> String {
self.h.get_str(sys::vpiName)
}
pub fn full_name(&self) -> String {
self.h.get_str(sys::vpiFullName)
}
pub fn child(&self, name: &str) -> Result<AnyHandle, HandleError> {
let path =
CString::new(format!("{}.{}", self.full_name(), name)).expect("NUL in member name");
let raw = unsafe { sys::vpi_handle_by_name(path.as_ptr(), std::ptr::null_mut()) };
ObjHandle::new(raw)
.map(AnyHandle::classify)
.ok_or_else(|| HandleError::NotFound {
name: name.into(),
scope: self.full_name(),
})
}
pub fn children(&self) -> Vec<AnyHandle> {
let mut out = Vec::new();
unsafe {
let it = sys::vpi_iterate(sys::vpiMember, self.h.0);
if !it.is_null() {
while let Some(h) = ObjHandle::new(sys::vpi_scan(it)) {
out.push(AnyHandle::classify(h));
}
}
}
out
}
pub fn signal(&self, name: &str) -> Result<LogicHandle, HandleError> {
self.child(name)?.as_logic()
}
}
impl AggregateHandle {
pub fn real(&self, name: &str) -> Result<RealHandle, HandleError> {
self.child(name)?.as_real()
}
pub fn string(&self, name: &str) -> Result<StringHandle, HandleError> {
self.child(name)?.as_string()
}
pub fn aggregate(&self, name: &str) -> Result<AggregateHandle, HandleError> {
self.child(name)?.as_aggregate()
}
}
#[derive(Copy, Clone, PartialEq, Eq)]
pub struct LogicHandle {
h: ObjHandle,
width: u32,
}
impl LogicHandle {
pub fn name(&self) -> String {
self.h.get_str(sys::vpiName)
}
pub fn full_name(&self) -> String {
self.h.get_str(sys::vpiFullName)
}
pub fn size(&self) -> u32 {
self.width
}
fn get_vpi_words(&self) -> Result<&[sys::t_vpi_vecval], ValueError> {
let mut val = sys::t_vpi_value {
format: sys::vpiVectorVal,
value: sys::u_vpi_value_union {
vector: std::ptr::null_mut(),
},
};
unsafe {
sys::vpi_get_value(self.h.0, &mut val);
let words = val.value.vector;
if words.is_null() {
return Err(ValueError::Unavailable);
}
let word_count = self.size().div_ceil(32) as usize;
Ok(std::slice::from_raw_parts(words, word_count))
}
}
fn get_vpi_word(&self, word: &t_vpi_vecval, width: u32, last: bool) -> Result<u32, ValueError> {
let used_bits = if last && !width.is_multiple_of(32) {
width % 32
} else {
32
};
let mask = if used_bits == 32 {
u32::MAX
} else {
(1u32 << used_bits) - 1
};
if word.bval & mask != 0 {
return Err(ValueError::FourState(self.get_logic()?.to_binstr()));
}
Ok(word.aval & mask)
}
fn check_size(&self, want: u32) -> Result<(), ValueError> {
if self.size() > want {
return Err(ValueError::Width {
want,
have: self.size() as usize,
});
}
Ok(())
}
pub fn get_bool(&self) -> Result<bool, ValueError> {
self.check_size(1)?;
Ok(self.get_vpi_word(&self.get_vpi_words()?[0], self.size(), true)? != 0)
}
pub fn get_u8(&self) -> Result<u8, ValueError> {
self.check_size(u8::BITS)?;
Ok(self.get_vpi_word(&self.get_vpi_words()?[0], self.size(), true)? as u8)
}
pub fn get_u16(&self) -> Result<u16, ValueError> {
self.check_size(u16::BITS)?;
Ok(self.get_vpi_word(&self.get_vpi_words()?[0], self.size(), true)? as u16)
}
pub fn get_u32(&self) -> Result<u32, ValueError> {
self.check_size(u32::BITS)?;
self.get_vpi_word(&self.get_vpi_words()?[0], self.size(), true)
}
pub fn get_u64(&self) -> Result<u64, ValueError> {
self.check_size(u64::BITS)?;
let words = self.get_vpi_words()?;
let mut result = 0;
for (index, word) in words.iter().enumerate() {
let word = self.get_vpi_word(word, self.size(), index + 1 == words.len())?;
result |= (word as u64) << (index * 32);
}
Ok(result)
}
pub fn get_u128(&self) -> Result<u128, ValueError> {
self.check_size(u128::BITS)?;
let words = self.get_vpi_words()?;
let mut result = 0;
for (index, word) in words.iter().enumerate() {
let word = self.get_vpi_word(word, self.size(), index + 1 == words.len())?;
result |= (word as u128) << (index * 32);
}
Ok(result)
}
pub fn get_bigint(&self) -> Result<BigUint, ValueError> {
let words = self.get_vpi_words()?;
let mut digits = Vec::with_capacity(words.len());
for (index, word) in words.iter().enumerate() {
digits.push(self.get_vpi_word(word, self.size(), index + 1 == words.len())?);
}
Ok(BigUint::from_slice(&digits))
}
pub fn get_logic(&self) -> Result<LogicArray, ValueError> {
let words = self.get_vpi_words()?;
let words: Vec<(u32, u32)> = words.iter().map(|word| (word.aval, word.bval)).collect();
Ok(LogicArray::from_vpi_words(&words, self.size() as usize))
}
fn mask_vector_words(&self, words: &mut [sys::t_vpi_vecval]) {
if !self.size().is_multiple_of(32) {
let mask = (1u32 << (self.size() % 32)) - 1;
let last = words.last_mut().expect("partial width has a vector word");
last.aval &= mask;
last.bval &= mask;
}
}
fn set_vector_words(&self, words: &mut [sys::t_vpi_vecval], flags: i32) {
let word_count = self.size().div_ceil(32) as usize;
if words.len() == word_count {
self.mask_vector_words(words);
unsafe {
let mut vpi_value = sys::t_vpi_value {
format: sys::vpiVectorVal,
value: sys::u_vpi_value_union {
vector: words.as_mut_ptr(),
},
};
sys::vpi_put_value(self.h.0, &mut vpi_value, std::ptr::null_mut(), flags);
}
} else if words.len() > word_count {
let words = &mut words[..word_count];
self.mask_vector_words(words);
unsafe {
let mut vpi_value = sys::t_vpi_value {
format: sys::vpiVectorVal,
value: sys::u_vpi_value_union {
vector: words.as_mut_ptr(),
},
};
sys::vpi_put_value(self.h.0, &mut vpi_value, std::ptr::null_mut(), flags);
}
} else {
let mut words = words.to_vec();
words.resize(word_count, sys::t_vpi_vecval::default());
unsafe {
let mut vpi_value = sys::t_vpi_value {
format: sys::vpiVectorVal,
value: sys::u_vpi_value_union {
vector: words.as_mut_ptr(),
},
};
sys::vpi_put_value(self.h.0, &mut vpi_value, std::ptr::null_mut(), flags);
}
}
}
pub fn set_bool(&self, v: bool, flags: i32) {
self.set_vector_words(&mut v.to_vpi_words(), flags);
}
pub fn set_bool_now(&self, v: bool) {
self.set_bool(v, sys::vpiNoDelay);
}
pub fn set_u8(&self, v: u8, flags: i32) {
self.set_vector_words(&mut v.to_vpi_words(), flags);
}
pub fn set_u8_now(&self, v: u8) {
self.set_u8(v, sys::vpiNoDelay);
}
pub fn set_u16(&self, v: u16, flags: i32) {
self.set_vector_words(&mut v.to_vpi_words(), flags);
}
pub fn set_u16_now(&self, v: u16) {
self.set_u16(v, sys::vpiNoDelay);
}
pub fn set_u32(&self, v: u32, flags: i32) {
self.set_vector_words(&mut v.to_vpi_words(), flags);
}
pub fn set_u32_now(&self, v: u32) {
self.set_u32(v, sys::vpiNoDelay);
}
pub fn set_u64(&self, v: u64, flags: i32) {
self.set_vector_words(&mut v.to_vpi_words(), flags);
}
pub fn set_u64_now(&self, v: u64) {
self.set_u64(v, sys::vpiNoDelay);
}
pub fn set_u128(&self, v: u128, flags: i32) {
self.set_vector_words(&mut v.to_vpi_words(), flags);
}
pub fn set_u128_now(&self, v: u128) {
self.set_u128(v, sys::vpiNoDelay);
}
pub fn set_bigint(&self, v: &BigUint, flags: i32) {
let mut words: Vec<sys::t_vpi_vecval> = v
.iter_u32_digits()
.map(|aval| sys::t_vpi_vecval { aval, bval: 0 })
.collect();
self.set_vector_words(&mut words, flags);
}
pub fn set_bigint_now(&self, v: &BigUint) {
self.set_bigint(v, sys::vpiNoDelay);
}
pub fn set_logic(&self, v: &LogicArray, flags: i32) {
let mut words: Vec<sys::t_vpi_vecval> = v
.to_vpi_words()
.into_iter()
.map(|(aval, bval)| sys::t_vpi_vecval { aval, bval })
.collect();
self.set_vector_words(&mut words, flags);
}
pub fn set_logic_now(&self, v: &LogicArray) {
self.set_logic(v, sys::vpiNoDelay);
}
}
pub fn top_modules() -> Vec<HierarchyHandle> {
let mut out = Vec::new();
unsafe {
let it = sys::vpi_iterate(sys::vpiModule, std::ptr::null_mut());
if it.is_null() {
return out;
}
loop {
let m = sys::vpi_scan(it);
if m.is_null() {
break;
}
if let Some(h) = ObjHandle::new(m) {
out.push(HierarchyHandle { h });
}
}
}
out
}
fn select_top_module(explicit_name: Option<&str>) -> Result<HierarchyHandle, HandleError> {
if let Some(name) = explicit_name {
let cname = CString::new(name).expect("NUL in RUSTDV_TOP");
let raw = unsafe { sys::vpi_handle_by_name(cname.as_ptr(), std::ptr::null_mut()) };
let h = ObjHandle::new(raw).ok_or_else(|| HandleError::NotFound {
name: name.to_owned(),
scope: "<top-level>".to_owned(),
})?;
return AnyHandle::classify(h).as_hierarchy();
}
top_modules()
.into_iter()
.next()
.ok_or(HandleError::NoTopModule)
}
pub fn top_module() -> Result<HierarchyHandle, HandleError> {
let explicit_name = std::env::var("RUSTDV_TOP")
.ok()
.map(|name| name.trim().to_owned())
.filter(|name| !name.is_empty());
select_top_module(explicit_name.as_deref())
}
pub fn sim_time_steps() -> u64 {
let mut t = sys::t_vpi_time {
type_: sys::vpiSimTime,
high: 0,
low: 0,
real: 0.0,
};
unsafe { sys::vpi_get_time(std::ptr::null_mut(), &mut t) };
((t.high as u64) << 32) | (t.low as u64)
}
pub fn time_precision() -> i32 {
thread_local! {
static PREC: Cell<Option<i32>> = const { Cell::new(None) };
}
PREC.with(|p| match p.get() {
Some(v) => v,
None => {
let v = unsafe { sys::vpi_get(sys::vpiTimePrecision, std::ptr::null_mut()) };
p.set(Some(v));
v
}
})
}
pub fn finish() {
unsafe {
sys::vpi_control(sys::vpiFinish, 0i32);
}
}
type PanicSink = RefCell<Option<Box<dyn Fn(String)>>>;
thread_local! {
static PANIC_SINK: PanicSink = const { RefCell::new(None) };
}
pub fn set_panic_sink(f: Box<dyn Fn(String)>) {
PANIC_SINK.with(|s| *s.borrow_mut() = Some(f));
}
fn report_panic(payload: Box<dyn std::any::Any + Send>) {
let msg = if let Some(s) = payload.downcast_ref::<&str>() {
s.to_string()
} else if let Some(s) = payload.downcast_ref::<String>() {
s.clone()
} else {
"panic (non-string payload)".to_string()
};
PANIC_SINK.with(|s| {
if let Some(f) = s.borrow().as_ref() {
f(msg.clone());
} else {
eprintln!("rustdv: panic in simulator callback: {msg}");
}
});
}
enum CbKind {
OneShot,
Recurring,
}
struct CbShared {
kind: CbKind,
vpi_h: Cell<sys::vpiHandle>,
released: Cell<bool>,
once: RefCell<Option<Box<dyn FnOnce()>>>,
repeat: RefCell<Option<Box<dyn FnMut()>>>,
}
pub struct CallbackHandle {
shared: Rc<CbShared>,
raw: *const CbShared,
detached: bool,
}
impl CallbackHandle {
pub fn forget(mut self) {
self.detached = true;
}
}
impl Drop for CallbackHandle {
fn drop(&mut self) {
if self.detached {
return;
}
if !self.shared.released.get() {
self.shared.released.set(true);
unsafe {
sys::vpi_remove_cb(self.shared.vpi_h.get());
drop(Rc::from_raw(self.raw));
}
}
}
}
extern "C" fn trampoline(cb: *mut sys::t_cb_data) -> i32 {
unsafe {
let ud = (*cb).user_data as *const CbShared;
if ud.is_null() {
return 0;
}
Rc::increment_strong_count(ud);
let shared: Rc<CbShared> = Rc::from_raw(ud);
match shared.kind {
CbKind::OneShot => {
if !shared.released.get() {
shared.released.set(true);
let f = shared.once.borrow_mut().take();
sys::vpi_remove_cb(shared.vpi_h.get());
drop(Rc::from_raw(ud));
if let Some(f) = f
&& let Err(p) = catch_unwind(AssertUnwindSafe(f))
{
report_panic(p);
}
}
}
CbKind::Recurring => {
let mut guard = shared.repeat.borrow_mut();
if let Some(f) = guard.as_mut()
&& let Err(p) = catch_unwind(AssertUnwindSafe(f))
{
report_panic(p);
}
}
}
drop(shared);
}
0
}
fn register(
kind: CbKind,
once: Option<Box<dyn FnOnce()>>,
repeat: Option<Box<dyn FnMut()>>,
reason: i32,
obj: sys::vpiHandle,
time: Option<sys::t_vpi_time>,
) -> CallbackHandle {
let shared = Rc::new(CbShared {
kind,
vpi_h: Cell::new(std::ptr::null_mut()),
released: Cell::new(false),
once: RefCell::new(once),
repeat: RefCell::new(repeat),
});
let raw = Rc::into_raw(shared.clone());
let mut t = time.unwrap_or(sys::t_vpi_time {
type_: sys::vpiSuppressTime,
high: 0,
low: 0,
real: 0.0,
});
let mut cb = sys::t_cb_data {
reason,
cb_rtn: Some(trampoline),
obj,
time: &mut t,
value: std::ptr::null_mut(),
index: 0,
user_data: raw as *mut _,
};
let vpi_h = unsafe { sys::vpi_register_cb(&mut cb) };
assert!(!vpi_h.is_null(), "vpi_register_cb failed (reason {reason})");
shared.vpi_h.set(vpi_h);
CallbackHandle {
shared,
raw,
detached: false,
}
}
fn simtime(steps: u64) -> sys::t_vpi_time {
sys::t_vpi_time {
type_: sys::vpiSimTime,
high: (steps >> 32) as u32,
low: (steps & 0xFFFF_FFFF) as u32,
real: 0.0,
}
}
pub fn register_timer(steps: u64, f: Box<dyn FnOnce()>) -> CallbackHandle {
register(
CbKind::OneShot,
Some(f),
None,
sys::cbAfterDelay,
std::ptr::null_mut(),
Some(simtime(steps)),
)
}
pub fn register_value_change(sig: LogicHandle, f: Box<dyn FnMut()>) -> CallbackHandle {
register(
CbKind::Recurring,
None,
Some(f),
sys::cbValueChange,
sig.h.0,
Some(simtime(0)),
)
}
pub fn register_read_write(f: Box<dyn FnOnce()>) -> CallbackHandle {
register(
CbKind::OneShot,
Some(f),
None,
sys::cbReadWriteSynch,
std::ptr::null_mut(),
Some(simtime(0)),
)
}
pub fn register_read_only(f: Box<dyn FnOnce()>) -> CallbackHandle {
register(
CbKind::OneShot,
Some(f),
None,
sys::cbReadOnlySynch,
std::ptr::null_mut(),
Some(simtime(0)),
)
}
pub fn register_next_sim_time(f: Box<dyn FnOnce()>) -> CallbackHandle {
register(
CbKind::OneShot,
Some(f),
None,
sys::cbNextSimTime,
std::ptr::null_mut(),
None,
)
}
pub fn register_at_end_of_sim_time(f: Box<dyn FnOnce()>) -> CallbackHandle {
register(
CbKind::OneShot,
Some(f),
None,
sys::cbAtEndOfSimTime,
std::ptr::null_mut(),
Some(simtime(sim_time_steps())),
)
}
pub fn register_start_of_simulation(f: Box<dyn FnOnce()>) -> CallbackHandle {
register(
CbKind::OneShot,
Some(f),
None,
sys::cbStartOfSimulation,
std::ptr::null_mut(),
None,
)
}
pub fn register_end_of_simulation(f: Box<dyn FnOnce()>) -> CallbackHandle {
register(
CbKind::OneShot,
Some(f),
None,
sys::cbEndOfSimulation,
std::ptr::null_mut(),
None,
)
}
#[cfg(test)]
mod vpi_word_tests {
use super::*;
fn assert_single_word<T: ToVpiWords<1>>(value: T, expected: u32) {
let words = value.to_vpi_words();
assert_eq!(words[0].aval, expected);
assert_eq!(words[0].bval, 0);
}
#[test]
fn scalar_values_convert_to_one_vpi_word() {
assert_single_word(false, 0);
assert_single_word(true, 1);
assert_single_word(0xa5u8, 0xa5);
assert_single_word(0xa5b6u16, 0xa5b6);
assert_single_word(0xa5b6_c7d8u32, 0xa5b6_c7d8);
}
#[test]
fn wide_values_convert_in_least_significant_word_first_order() {
let words = 0x0123_4567_89ab_cdefu64.to_vpi_words();
assert_eq!(words.map(|word| word.aval), [0x89ab_cdef, 0x0123_4567]);
assert!(words.iter().all(|word| word.bval == 0));
let words = 0x0123_4567_89ab_cdef_fedc_ba98_7654_3210u128.to_vpi_words();
assert_eq!(
words.map(|word| word.aval),
[0x7654_3210, 0xfedc_ba98, 0x89ab_cdef, 0x0123_4567]
);
assert!(words.iter().all(|word| word.bval == 0));
}
}
#[cfg(test)]
mod callback_tests {
use super::*;
use std::cell::Cell;
use std::rc::Rc;
#[test]
fn fired_one_shot_releases_its_vpi_handle() {
rustdv_vpi_stubs::reset_callbacks();
let fired = Rc::new(Cell::new(false));
let fired_in_callback = fired.clone();
let handle = register_timer(1, Box::new(move || fired_in_callback.set(true)));
assert_eq!(rustdv_vpi_stubs::live_callback_handles(), 1);
rustdv_vpi_stubs::fire_next_callback();
assert!(fired.get());
drop(handle);
assert_eq!(rustdv_vpi_stubs::live_callback_handles(), 0);
rustdv_vpi_stubs::reset_callbacks();
}
#[test]
fn detached_one_shot_releases_shared_state_after_firing() {
rustdv_vpi_stubs::reset_callbacks();
let handle = register_timer(1, Box::new(|| {}));
let shared = Rc::downgrade(&handle.shared);
handle.forget();
assert!(shared.upgrade().is_some());
rustdv_vpi_stubs::fire_next_callback();
assert_eq!(rustdv_vpi_stubs::live_callback_handles(), 0);
assert!(shared.upgrade().is_none());
rustdv_vpi_stubs::reset_callbacks();
}
#[test]
fn callback_stub_refuses_to_reset_a_live_handle() {
rustdv_vpi_stubs::reset_callbacks();
let handle = register_timer(1, Box::new(|| {}));
let reset = catch_unwind(AssertUnwindSafe(rustdv_vpi_stubs::reset_callbacks));
assert!(reset.is_err());
drop(handle);
rustdv_vpi_stubs::reset_callbacks();
}
}
#[cfg(test)]
mod handle_tests {
use super::*;
#[test]
fn sv_integral_variables_support_vector_access() {
for object_type in [
sys::vpiLongIntVar,
sys::vpiShortIntVar,
sys::vpiIntVar,
sys::vpiByteVar,
sys::vpiEnumVar,
sys::vpiBitVar,
] {
rustdv_vpi_stubs::configure_signal(
64,
&[
sys::t_vpi_vecval {
aval: 0x89ab_cdef,
bval: 0,
},
sys::t_vpi_vecval {
aval: 0x1234_5678,
bval: 0,
},
],
"0",
);
rustdv_vpi_stubs::configure_signal_type(object_type);
let signal = AnyHandle::classify(ObjHandle(1usize as sys::vpiHandle))
.as_logic()
.unwrap();
assert_eq!(signal.size(), 64);
assert_eq!(signal.get_u64(), Ok(0x1234_5678_89ab_cdef));
signal.set_u64_now(0xfedc_ba98_7654_3210);
let words = rustdv_vpi_stubs::last_put_vector();
assert_eq!(words[0].aval, 0x7654_3210);
assert_eq!(words[1].aval, 0xfedc_ba98);
}
rustdv_vpi_stubs::configure_signal_type(sys::vpiNet);
}
#[test]
fn non_logic_variables_have_typed_handles() {
for object_type in [
sys::vpiRealVar,
sys::vpiStringVar,
sys::vpiStructVar,
sys::vpiUnionVar,
] {
rustdv_vpi_stubs::configure_signal_type(object_type);
assert!(matches!(
AnyHandle::classify(ObjHandle(1usize as sys::vpiHandle)),
AnyHandle::Real(_)
| AnyHandle::String(_)
| AnyHandle::Struct(_)
| AnyHandle::Union(_)
));
}
rustdv_vpi_stubs::configure_signal_type(sys::vpiNet);
}
#[test]
fn top_selection_prefers_explicit_name_and_otherwise_uses_first_root() {
rustdv_vpi_stubs::configure_top_modules(&["first", "dut"]);
assert_eq!(select_top_module(Some("dut")).unwrap().name(), "dut");
assert_eq!(select_top_module(None).unwrap().name(), "first");
assert!(matches!(
select_top_module(Some("missing")),
Err(HandleError::NotFound { .. })
));
rustdv_vpi_stubs::configure_top_modules(&[]);
assert!(matches!(
select_top_module(None),
Err(HandleError::NoTopModule)
));
}
fn make_signal(width: i32, words: &[sys::t_vpi_vecval], binstr: &str) -> LogicHandle {
rustdv_vpi_stubs::configure_signal(width, words, binstr);
let raw = 1usize as sys::vpiHandle;
let AnyHandle::Logic(signal) = AnyHandle::classify(ObjHandle(raw)) else {
panic!("stub object was not classified as a signal");
};
signal
}
#[test]
fn signal_width_is_cached_when_the_handle_is_classified() {
rustdv_vpi_stubs::reset_property_gets();
let signal = make_signal(37, &[sys::t_vpi_vecval::default(); 2], &"0".repeat(37));
assert_eq!(signal.size(), 37);
assert_eq!(signal.size(), 37);
assert_eq!(rustdv_vpi_stubs::property_get_count(sys::vpiType), 1);
assert_eq!(rustdv_vpi_stubs::property_get_count(sys::vpiSize), 1);
rustdv_vpi_stubs::reset_property_gets();
}
#[test]
fn vector_read_uses_vpi_word_order_and_masks_unused_bits() {
let signal = make_signal(
37,
&[
sys::t_vpi_vecval {
aval: 0x89ab_cdef,
bval: 0,
},
sys::t_vpi_vecval {
aval: 0xffff_fff5,
bval: 0xffff_ffe0,
},
],
"101011001101010111100110111101111",
);
assert_eq!(signal.get_u64(), Ok(0x0000_0015_89ab_cdef));
}
#[test]
fn vector_read_reports_xz_in_used_bits() {
let signal = make_signal(
8,
&[sys::t_vpi_vecval {
aval: 0x5e,
bval: 0x04,
}],
"01011x10",
);
assert_eq!(
signal.get_u64(),
Err(ValueError::FourState("01011x10".to_owned()))
);
}
#[test]
fn logic_array_read_uses_vpi_vector_words() {
let signal = make_signal(
4,
&[sys::t_vpi_vecval {
aval: 0b0110,
bval: 0b0011,
}],
"0000",
);
assert_eq!(signal.get_logic().unwrap().to_binstr(), "01xz");
}
#[test]
fn vector_read_returns_numeric_zero_from_a_vector_word() {
let signal = make_signal(8, &[sys::t_vpi_vecval { aval: 0, bval: 0 }], "00000000");
assert_eq!(signal.get_u64(), Ok(0));
}
#[test]
fn bool_read_converts_single_bit_values() {
let low = make_signal(1, &[sys::t_vpi_vecval { aval: 0, bval: 0 }], "0");
assert_eq!(low.get_bool(), Ok(false));
let high = make_signal(1, &[sys::t_vpi_vecval { aval: 1, bval: 0 }], "1");
assert_eq!(high.get_bool(), Ok(true));
}
#[test]
fn bool_read_rejects_wide_and_four_state_values() {
let wide = make_signal(2, &[sys::t_vpi_vecval { aval: 1, bval: 0 }], "01");
assert_eq!(wide.get_bool(), Err(ValueError::Width { want: 1, have: 2 }));
let unknown = make_signal(1, &[sys::t_vpi_vecval { aval: 1, bval: 1 }], "x");
assert_eq!(
unknown.get_bool(),
Err(ValueError::FourState("x".to_owned()))
);
}
#[test]
fn vector_read_reports_an_unavailable_vpi_result() {
let signal = make_signal(8, &[sys::t_vpi_vecval { aval: 0, bval: 0 }], "00000000");
rustdv_vpi_stubs::make_vector_value_unavailable();
assert_eq!(signal.get_u64(), Err(ValueError::Unavailable));
assert_eq!(signal.get_bigint(), Err(ValueError::Unavailable));
assert_eq!(signal.get_logic(), Err(ValueError::Unavailable));
}
#[test]
fn bigint_read_supports_values_wider_than_u64() {
let signal = make_signal(
70,
&[
sys::t_vpi_vecval {
aval: 0x89ab_cdef,
bval: 0,
},
sys::t_vpi_vecval {
aval: 0x0123_4567,
bval: 0,
},
sys::t_vpi_vecval {
aval: 0xffff_ffea,
bval: 0xffff_ffc0,
},
],
"1010100000000100100011010001010110011110001001101010111100110111101111",
);
assert_eq!(
signal.get_bigint().map(|value| value.to_str_radix(16)),
Ok("2a0123456789abcdef".to_owned())
);
}
#[test]
fn bigint_read_reports_xz_in_used_bits() {
let signal = make_signal(
65,
&[
sys::t_vpi_vecval::default(),
sys::t_vpi_vecval::default(),
sys::t_vpi_vecval { aval: 1, bval: 1 },
],
&format!("x{}", "0".repeat(64)),
);
assert_eq!(
signal.get_bigint(),
Err(ValueError::FourState(format!("x{}", "0".repeat(64))))
);
}
#[test]
fn vector_write_truncates_and_zero_extends_to_signal_width() {
let narrow = make_signal(8, &[sys::t_vpi_vecval::default()], "00000000");
narrow.set_u64_now(0xffff_ffff_ffff_ffa5);
let words = rustdv_vpi_stubs::last_put_vector();
assert_eq!(words.len(), 1);
assert_eq!(words[0].aval, 0xa5);
assert_eq!(words[0].bval, 0);
let signal = make_signal(37, &[sys::t_vpi_vecval::default(); 2], &"0".repeat(37));
signal.set_u64_now(0xffff_fff5_89ab_cdef);
let words = rustdv_vpi_stubs::last_put_vector();
assert_eq!(words.len(), 2);
assert_eq!(words[0].aval, 0x89ab_cdef);
assert_eq!(words[1].aval, 0x15);
assert_eq!(words[0].bval, 0);
assert_eq!(words[1].bval, 0);
let wide = make_signal(70, &[sys::t_vpi_vecval::default(); 3], &"0".repeat(70));
wide.set_u64_now(0x0123_4567_89ab_cdef);
let words = rustdv_vpi_stubs::last_put_vector();
assert_eq!(words.len(), 3);
assert_eq!(words[0].aval, 0x89ab_cdef);
assert_eq!(words[1].aval, 0x0123_4567);
assert_eq!(words[2].aval, 0);
}
#[test]
fn typed_integer_setters_write_vpi_words() {
let signal = make_signal(1, &[sys::t_vpi_vecval::default()], "0");
signal.set_bool_now(true);
assert_eq!(rustdv_vpi_stubs::last_put_vector()[0].aval, 1);
let signal = make_signal(8, &[sys::t_vpi_vecval::default()], "00000000");
signal.set_u8(0xa5, sys::vpiNoDelay);
assert_eq!(rustdv_vpi_stubs::last_put_vector()[0].aval, 0xa5);
let signal = make_signal(16, &[sys::t_vpi_vecval::default()], &"0".repeat(16));
signal.set_u16_now(0xa5b6);
assert_eq!(rustdv_vpi_stubs::last_put_vector()[0].aval, 0xa5b6);
let signal = make_signal(32, &[sys::t_vpi_vecval::default()], &"0".repeat(32));
signal.set_u32_now(0xa5b6_c7d8);
assert_eq!(rustdv_vpi_stubs::last_put_vector()[0].aval, 0xa5b6_c7d8);
let signal = make_signal(128, &[sys::t_vpi_vecval::default(); 4], &"0".repeat(128));
signal.set_u128_now(0x0123_4567_89ab_cdef_fedc_ba98_7654_3210);
let words = rustdv_vpi_stubs::last_put_vector();
assert_eq!(
words.iter().map(|word| word.aval).collect::<Vec<_>>(),
[0x7654_3210, 0xfedc_ba98, 0x89ab_cdef, 0x0123_4567]
);
assert!(words.iter().all(|word| word.bval == 0));
}
#[test]
fn bigint_setter_writes_and_zero_extends_vpi_words() {
let signal = make_signal(192, &[sys::t_vpi_vecval::default(); 6], &"0".repeat(192));
let value = BigUint::from_slice(&[
0x7654_3210,
0xfedc_ba98,
0x89ab_cdef,
0x0123_4567,
0xa5a5_5a5a,
]);
signal.set_bigint_now(&value);
let words = rustdv_vpi_stubs::last_put_vector();
assert_eq!(
words.iter().map(|word| word.aval).collect::<Vec<_>>(),
[
0x7654_3210,
0xfedc_ba98,
0x89ab_cdef,
0x0123_4567,
0xa5a5_5a5a,
0,
]
);
assert!(words.iter().all(|word| word.bval == 0));
}
#[test]
fn vector_word_mask_clears_unused_aval_and_bval_bits() {
let signal = make_signal(37, &[sys::t_vpi_vecval::default(); 2], &"0".repeat(37));
let mut words = [
sys::t_vpi_vecval {
aval: u32::MAX,
bval: u32::MAX,
},
sys::t_vpi_vecval {
aval: u32::MAX,
bval: u32::MAX,
},
];
signal.mask_vector_words(&mut words);
assert_eq!(words[0].aval, u32::MAX);
assert_eq!(words[0].bval, u32::MAX);
assert_eq!(words[1].aval, 0x1f);
assert_eq!(words[1].bval, 0x1f);
}
#[test]
fn logic_array_write_uses_vpi_vector_words() {
let signal = make_signal(
4,
&[sys::t_vpi_vecval::default()],
"binary-string path must not be used",
);
signal.set_logic_now(&LogicArray::from_binstr("01xz"));
let words = rustdv_vpi_stubs::last_put_vector();
assert_eq!(words.len(), 1);
assert_eq!(words[0].aval, 0b0110);
assert_eq!(words[0].bval, 0b0011);
}
}