use std::cell::{Cell, RefCell};
use std::ffi::{CStr, CString};
use std::fmt;
use std::panic::{catch_unwind, AssertUnwindSafe};
use std::rc::Rc;
use rustdv_gpi_sys as sys;
#[cfg(test)]
use rustdv_vpi_stubs as _;
pub mod value;
pub use value::{Logic, LogicArray};
#[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),
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::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),
Other(ObjHandle),
}
impl AnyHandle {
pub fn classify(h: ObjHandle) -> AnyHandle {
match h.get(sys::vpiType) {
sys::vpiModule => AnyHandle::Hierarchy(HierarchyHandle { h }),
sys::vpiNet | sys::vpiReg | sys::vpiIntegerVar | sys::vpiPort | sys::vpiMemory
| sys::vpiLongIntVar | sys::vpiShortIntVar | sys::vpiIntVar | sys::vpiByteVar
| sys::vpiEnumVar | sys::vpiBitVar => {
AnyHandle::Logic(LogicHandle { h })
}
_ => AnyHandle::Other(h),
}
}
pub fn as_logic(self) -> Result<LogicHandle, HandleError> {
match self {
AnyHandle::Logic(l) => Ok(l),
AnyHandle::Hierarchy(h) => Err(HandleError::WrongKind {
name: h.full_name(),
expected: "signal",
actual: "module".into(),
}),
AnyHandle::Other(o) => Err(HandleError::WrongKind {
name: o.get_str(sys::vpiFullName),
expected: "signal",
actual: format!("vpiType {}", o.get(sys::vpiType)),
}),
}
}
pub fn as_hierarchy(self) -> Result<HierarchyHandle, HandleError> {
match self {
AnyHandle::Hierarchy(h) => Ok(h),
AnyHandle::Logic(l) => Err(HandleError::WrongKind {
name: l.full_name(),
expected: "module",
actual: "signal".into(),
}),
AnyHandle::Other(o) => Err(HandleError::WrongKind {
name: o.get_str(sys::vpiFullName),
expected: "module",
actual: format!("vpiType {}", o.get(sys::vpiType)),
}),
}
}
}
#[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
}
}
#[derive(Copy, Clone, PartialEq, Eq)]
pub struct LogicHandle {
h: ObjHandle,
}
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.h.get(sys::vpiSize).max(0) as u32
}
pub fn get_binstr(&self) -> String {
let mut val = sys::t_vpi_value {
format: sys::vpiBinStrVal,
value: sys::u_vpi_value_union { integer: 0 },
};
unsafe {
sys::vpi_get_value(self.h.0, &mut val);
let p = val.value.str_;
if p.is_null() {
String::new()
} else {
CStr::from_ptr(p).to_string_lossy().into_owned()
}
}
}
pub fn get(&self) -> LogicArray {
LogicArray::from_binstr(&self.get_binstr())
}
pub fn get_u64(&self) -> Result<u64, ValueError> {
let s = self.get_binstr();
let mut v: u64 = 0;
for c in s.chars() {
match c {
'0' => v <<= 1,
'1' => v = (v << 1) | 1,
_ => return Err(ValueError::FourState(s)),
}
}
Ok(v)
}
fn put_binstr_flags(&self, bin: &str, flags: i32) {
let c = CString::new(bin).expect("NUL in binstr");
let mut val = sys::t_vpi_value {
format: sys::vpiBinStrVal,
value: sys::u_vpi_value_union { str_: c.as_ptr() as *mut _ },
};
unsafe {
sys::vpi_put_value(self.h.0, &mut val, std::ptr::null_mut(), flags);
}
}
pub fn set_u64_now(&self, v: u64) {
let w = self.size().max(1) as usize;
let mut s = String::with_capacity(w);
for i in (0..w).rev() {
s.push(if (v >> i) & 1 == 1 { '1' } else { '0' });
}
self.put_binstr_flags(&s, sys::vpiNoDelay);
}
pub fn set_now(&self, v: &LogicArray) {
self.put_binstr_flags(&v.to_binstr(), 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
}
pub fn top_module() -> Result<HierarchyHandle, HandleError> {
top_modules().into_iter().next().ok_or(HandleError::NoTopModule)
}
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);
}
}
thread_local! {
static PANIC_SINK: RefCell<Option<Box<dyn Fn(String)>>> = 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,
released: Cell<bool>,
once: RefCell<Option<Box<dyn FnOnce()>>>,
repeat: RefCell<Option<Box<dyn FnMut()>>>,
}
pub struct CallbackHandle {
shared: Rc<CbShared>,
raw: *const CbShared,
vpi_h: sys::vpiHandle,
}
impl CallbackHandle {
pub fn forget(self) {
std::mem::forget(self);
}
}
impl Drop for CallbackHandle {
fn drop(&mut self) {
if !self.shared.released.get() {
self.shared.released.set(true);
unsafe {
sys::vpi_remove_cb(self.vpi_h);
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();
drop(Rc::from_raw(ud));
if let Some(f) = f {
if 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() {
if 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,
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})");
CallbackHandle { shared, raw, vpi_h }
}
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_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)
}