1use std::cell::{Cell, RefCell};
21use std::ffi::{CStr, CString};
22use std::fmt;
23use std::panic::{catch_unwind, AssertUnwindSafe};
24use std::rc::Rc;
25
26use rustdv_gpi_sys as sys;
27
28#[cfg(test)]
31use rustdv_vpi_stubs as _;
32
33pub mod value;
34pub use value::{Logic, LogicArray};
35
36#[derive(Debug, Clone, PartialEq, Eq)]
41pub enum HandleError {
42 NotFound { name: String, scope: String },
45 WrongKind { name: String, expected: &'static str, actual: String },
47 NoTopModule,
48}
49
50impl fmt::Display for HandleError {
51 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
52 match self {
53 HandleError::NotFound { name, scope } => {
54 write!(f, "no object named '{name}' in scope '{scope}'")
55 }
56 HandleError::WrongKind { name, expected, actual } => {
57 write!(f, "'{name}' is a {actual}, expected {expected}")
58 }
59 HandleError::NoTopModule => write!(f, "no top-level module found"),
60 }
61 }
62}
63impl std::error::Error for HandleError {}
64
65#[derive(Debug, Clone, PartialEq, Eq)]
66pub enum ValueError {
67 FourState(String),
69 Width { want: u32, have: usize },
70}
71
72impl fmt::Display for ValueError {
73 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
74 match self {
75 ValueError::FourState(s) => write!(f, "value '{s}' has x/z bits"),
76 ValueError::Width { want, have } => write!(f, "width mismatch: want {want}, have {have}"),
77 }
78 }
79}
80impl std::error::Error for ValueError {}
81
82#[derive(Copy, Clone, PartialEq, Eq)]
89pub struct ObjHandle(sys::vpiHandle);
90
91impl ObjHandle {
92 fn new(h: sys::vpiHandle) -> Option<Self> {
93 if h.is_null() { None } else { Some(ObjHandle(h)) }
94 }
95 fn get(self, prop: i32) -> i32 {
96 unsafe { sys::vpi_get(prop, self.0) }
97 }
98 fn get_str(self, prop: i32) -> String {
99 unsafe {
101 let p = sys::vpi_get_str(prop, self.0);
102 if p.is_null() {
103 String::new()
104 } else {
105 CStr::from_ptr(p).to_string_lossy().into_owned()
106 }
107 }
108 }
109}
110
111#[derive(Copy, Clone)]
113pub enum AnyHandle {
114 Hierarchy(HierarchyHandle),
115 Logic(LogicHandle),
116 Other(ObjHandle),
117}
118
119impl AnyHandle {
120 pub fn classify(h: ObjHandle) -> AnyHandle {
121 match h.get(sys::vpiType) {
122 sys::vpiModule => AnyHandle::Hierarchy(HierarchyHandle { h }),
123 sys::vpiNet | sys::vpiReg | sys::vpiIntegerVar | sys::vpiPort | sys::vpiMemory
124 | sys::vpiLongIntVar | sys::vpiShortIntVar | sys::vpiIntVar | sys::vpiByteVar
125 | sys::vpiEnumVar | sys::vpiBitVar => {
126 AnyHandle::Logic(LogicHandle { h })
127 }
128 _ => AnyHandle::Other(h),
129 }
130 }
131
132 pub fn as_logic(self) -> Result<LogicHandle, HandleError> {
133 match self {
134 AnyHandle::Logic(l) => Ok(l),
135 AnyHandle::Hierarchy(h) => Err(HandleError::WrongKind {
136 name: h.full_name(),
137 expected: "signal",
138 actual: "module".into(),
139 }),
140 AnyHandle::Other(o) => Err(HandleError::WrongKind {
141 name: o.get_str(sys::vpiFullName),
142 expected: "signal",
143 actual: format!("vpiType {}", o.get(sys::vpiType)),
144 }),
145 }
146 }
147
148 pub fn as_hierarchy(self) -> Result<HierarchyHandle, HandleError> {
149 match self {
150 AnyHandle::Hierarchy(h) => Ok(h),
151 AnyHandle::Logic(l) => Err(HandleError::WrongKind {
152 name: l.full_name(),
153 expected: "module",
154 actual: "signal".into(),
155 }),
156 AnyHandle::Other(o) => Err(HandleError::WrongKind {
157 name: o.get_str(sys::vpiFullName),
158 expected: "module",
159 actual: format!("vpiType {}", o.get(sys::vpiType)),
160 }),
161 }
162 }
163}
164
165#[derive(Copy, Clone)]
168pub struct HierarchyHandle {
169 h: ObjHandle,
170}
171
172impl HierarchyHandle {
173 pub fn null_for_test() -> HierarchyHandle {
178 HierarchyHandle { h: ObjHandle(std::ptr::null_mut()) }
179 }
180
181 pub fn child(&self, name: &str) -> Result<AnyHandle, HandleError> {
183 let cname = CString::new(name).expect("NUL in signal name");
184 let h = unsafe { sys::vpi_handle_by_name(cname.as_ptr(), self.h.0) };
185 match ObjHandle::new(h) {
186 Some(h) => Ok(AnyHandle::classify(h)),
187 None => Err(HandleError::NotFound { name: name.into(), scope: self.full_name() }),
188 }
189 }
190
191 pub fn signal(&self, name: &str) -> Result<LogicHandle, HandleError> {
193 self.child(name)?.as_logic()
194 }
195
196 pub fn name(&self) -> String {
197 self.h.get_str(sys::vpiName)
198 }
199 pub fn full_name(&self) -> String {
200 self.h.get_str(sys::vpiFullName)
201 }
202
203 pub fn children(&self) -> Vec<AnyHandle> {
206 let mut out = Vec::new();
207 for t in [sys::vpiModule, sys::vpiNet, sys::vpiReg] {
208 unsafe {
209 let it = sys::vpi_iterate(t, self.h.0);
210 if it.is_null() {
211 continue;
212 }
213 loop {
214 let c = sys::vpi_scan(it);
215 if c.is_null() {
216 break; }
218 if let Some(h) = ObjHandle::new(c) {
219 out.push(AnyHandle::classify(h));
220 }
221 }
222 }
223 }
224 out
225 }
226}
227
228#[derive(Copy, Clone, PartialEq, Eq)]
233pub struct LogicHandle {
234 h: ObjHandle,
235}
236
237impl LogicHandle {
238 pub fn name(&self) -> String {
239 self.h.get_str(sys::vpiName)
240 }
241 pub fn full_name(&self) -> String {
242 self.h.get_str(sys::vpiFullName)
243 }
244 pub fn size(&self) -> u32 {
245 self.h.get(sys::vpiSize).max(0) as u32
246 }
247
248 pub fn get_binstr(&self) -> String {
250 let mut val = sys::t_vpi_value {
251 format: sys::vpiBinStrVal,
252 value: sys::u_vpi_value_union { integer: 0 },
253 };
254 unsafe {
255 sys::vpi_get_value(self.h.0, &mut val);
256 let p = val.value.str_;
257 if p.is_null() {
258 String::new()
259 } else {
260 CStr::from_ptr(p).to_string_lossy().into_owned()
261 }
262 }
263 }
264
265 pub fn get(&self) -> LogicArray {
267 LogicArray::from_binstr(&self.get_binstr())
268 }
269
270 pub fn get_u64(&self) -> Result<u64, ValueError> {
273 let s = self.get_binstr();
274 let mut v: u64 = 0;
275 for c in s.chars() {
276 match c {
277 '0' => v <<= 1,
278 '1' => v = (v << 1) | 1,
279 _ => return Err(ValueError::FourState(s)),
280 }
281 }
282 Ok(v)
283 }
284
285 fn put_binstr_flags(&self, bin: &str, flags: i32) {
286 let c = CString::new(bin).expect("NUL in binstr");
287 let mut val = sys::t_vpi_value {
288 format: sys::vpiBinStrVal,
289 value: sys::u_vpi_value_union { str_: c.as_ptr() as *mut _ },
290 };
291 unsafe {
292 sys::vpi_put_value(self.h.0, &mut val, std::ptr::null_mut(), flags);
293 }
294 }
295
296 pub fn set_u64_now(&self, v: u64) {
300 let w = self.size().max(1) as usize;
301 let mut s = String::with_capacity(w);
302 for i in (0..w).rev() {
303 s.push(if (v >> i) & 1 == 1 { '1' } else { '0' });
304 }
305 self.put_binstr_flags(&s, sys::vpiNoDelay);
306 }
307
308 pub fn set_now(&self, v: &LogicArray) {
310 self.put_binstr_flags(&v.to_binstr(), sys::vpiNoDelay);
311 }
312}
313
314pub fn top_modules() -> Vec<HierarchyHandle> {
316 let mut out = Vec::new();
317 unsafe {
318 let it = sys::vpi_iterate(sys::vpiModule, std::ptr::null_mut());
319 if it.is_null() {
320 return out;
321 }
322 loop {
323 let m = sys::vpi_scan(it);
324 if m.is_null() {
325 break;
326 }
327 if let Some(h) = ObjHandle::new(m) {
328 out.push(HierarchyHandle { h });
329 }
330 }
331 }
332 out
333}
334
335pub fn top_module() -> Result<HierarchyHandle, HandleError> {
337 top_modules().into_iter().next().ok_or(HandleError::NoTopModule)
338}
339
340pub fn sim_time_steps() -> u64 {
346 let mut t = sys::t_vpi_time { type_: sys::vpiSimTime, high: 0, low: 0, real: 0.0 };
347 unsafe { sys::vpi_get_time(std::ptr::null_mut(), &mut t) };
348 ((t.high as u64) << 32) | (t.low as u64)
349}
350
351pub fn time_precision() -> i32 {
353 thread_local! {
354 static PREC: Cell<Option<i32>> = const { Cell::new(None) };
355 }
356 PREC.with(|p| match p.get() {
357 Some(v) => v,
358 None => {
359 let v = unsafe { sys::vpi_get(sys::vpiTimePrecision, std::ptr::null_mut()) };
360 p.set(Some(v));
361 v
362 }
363 })
364}
365
366pub fn finish() {
368 unsafe {
369 sys::vpi_control(sys::vpiFinish, 0i32);
370 }
371}
372
373thread_local! {
378 static PANIC_SINK: RefCell<Option<Box<dyn Fn(String)>>> = const { RefCell::new(None) };
379}
380
381pub fn set_panic_sink(f: Box<dyn Fn(String)>) {
385 PANIC_SINK.with(|s| *s.borrow_mut() = Some(f));
386}
387
388fn report_panic(payload: Box<dyn std::any::Any + Send>) {
389 let msg = if let Some(s) = payload.downcast_ref::<&str>() {
390 s.to_string()
391 } else if let Some(s) = payload.downcast_ref::<String>() {
392 s.clone()
393 } else {
394 "panic (non-string payload)".to_string()
395 };
396 PANIC_SINK.with(|s| {
397 if let Some(f) = s.borrow().as_ref() {
398 f(msg.clone());
399 } else {
400 eprintln!("rustdv: panic in simulator callback: {msg}");
401 }
402 });
403}
404
405enum CbKind {
410 OneShot,
411 Recurring,
412}
413
414struct CbShared {
415 kind: CbKind,
416 released: Cell<bool>,
419 once: RefCell<Option<Box<dyn FnOnce()>>>,
420 repeat: RefCell<Option<Box<dyn FnMut()>>>,
421}
422
423pub struct CallbackHandle {
427 shared: Rc<CbShared>,
428 raw: *const CbShared,
429 vpi_h: sys::vpiHandle,
430}
431
432impl CallbackHandle {
433 pub fn forget(self) {
436 std::mem::forget(self);
437 }
438}
439
440impl Drop for CallbackHandle {
441 fn drop(&mut self) {
442 if !self.shared.released.get() {
443 self.shared.released.set(true);
444 unsafe {
445 sys::vpi_remove_cb(self.vpi_h);
446 drop(Rc::from_raw(self.raw));
448 }
449 }
450 }
451}
452
453extern "C" fn trampoline(cb: *mut sys::t_cb_data) -> i32 {
454 unsafe {
455 let ud = (*cb).user_data as *const CbShared;
456 if ud.is_null() {
457 return 0;
458 }
459 Rc::increment_strong_count(ud);
462 let shared: Rc<CbShared> = Rc::from_raw(ud);
463 match shared.kind {
464 CbKind::OneShot => {
465 if !shared.released.get() {
466 shared.released.set(true);
467 let f = shared.once.borrow_mut().take();
468 drop(Rc::from_raw(ud));
470 if let Some(f) = f {
471 if let Err(p) = catch_unwind(AssertUnwindSafe(f)) {
472 report_panic(p);
473 }
474 }
475 }
476 }
477 CbKind::Recurring => {
478 let mut guard = shared.repeat.borrow_mut();
479 if let Some(f) = guard.as_mut() {
480 if let Err(p) = catch_unwind(AssertUnwindSafe(|| f())) {
481 report_panic(p);
482 }
483 }
484 }
485 }
486 drop(shared);
487 }
488 0
489}
490
491fn register(
492 kind: CbKind,
493 once: Option<Box<dyn FnOnce()>>,
494 repeat: Option<Box<dyn FnMut()>>,
495 reason: i32,
496 obj: sys::vpiHandle,
497 time: Option<sys::t_vpi_time>,
498) -> CallbackHandle {
499 let shared = Rc::new(CbShared {
500 kind,
501 released: Cell::new(false),
502 once: RefCell::new(once),
503 repeat: RefCell::new(repeat),
504 });
505 let raw = Rc::into_raw(shared.clone());
507
508 let mut t = time.unwrap_or(sys::t_vpi_time {
509 type_: sys::vpiSuppressTime,
510 high: 0,
511 low: 0,
512 real: 0.0,
513 });
514 let mut cb = sys::t_cb_data {
517 reason,
518 cb_rtn: Some(trampoline),
519 obj,
520 time: &mut t,
521 value: std::ptr::null_mut(),
522 index: 0,
523 user_data: raw as *mut _,
524 };
525 let vpi_h = unsafe { sys::vpi_register_cb(&mut cb) };
526 assert!(!vpi_h.is_null(), "vpi_register_cb failed (reason {reason})");
527 CallbackHandle { shared, raw, vpi_h }
528}
529
530fn simtime(steps: u64) -> sys::t_vpi_time {
531 sys::t_vpi_time {
532 type_: sys::vpiSimTime,
533 high: (steps >> 32) as u32,
534 low: (steps & 0xFFFF_FFFF) as u32,
535 real: 0.0,
536 }
537}
538
539pub fn register_timer(steps: u64, f: Box<dyn FnOnce()>) -> CallbackHandle {
541 register(CbKind::OneShot, Some(f), None, sys::cbAfterDelay, std::ptr::null_mut(), Some(simtime(steps)))
542}
543
544pub fn register_value_change(sig: LogicHandle, f: Box<dyn FnMut()>) -> CallbackHandle {
547 register(
548 CbKind::Recurring,
549 None,
550 Some(f),
551 sys::cbValueChange,
552 sig.h.0,
553 Some(simtime(0)),
554 )
555}
556
557pub fn register_read_write(f: Box<dyn FnOnce()>) -> CallbackHandle {
559 register(CbKind::OneShot, Some(f), None, sys::cbReadWriteSynch, std::ptr::null_mut(), Some(simtime(0)))
560}
561
562pub fn register_read_only(f: Box<dyn FnOnce()>) -> CallbackHandle {
564 register(CbKind::OneShot, Some(f), None, sys::cbReadOnlySynch, std::ptr::null_mut(), Some(simtime(0)))
565}
566
567pub fn register_next_sim_time(f: Box<dyn FnOnce()>) -> CallbackHandle {
569 register(CbKind::OneShot, Some(f), None, sys::cbNextSimTime, std::ptr::null_mut(), None)
570}
571
572pub fn register_start_of_simulation(f: Box<dyn FnOnce()>) -> CallbackHandle {
574 register(CbKind::OneShot, Some(f), None, sys::cbStartOfSimulation, std::ptr::null_mut(), None)
575}
576
577pub fn register_end_of_simulation(f: Box<dyn FnOnce()>) -> CallbackHandle {
579 register(CbKind::OneShot, Some(f), None, sys::cbEndOfSimulation, std::ptr::null_mut(), None)
580}