1use std::cell::{Cell, RefCell};
23use std::ffi::{CStr, CString};
24use std::fmt;
25use std::panic::{catch_unwind, AssertUnwindSafe};
26use std::rc::Rc;
27
28use rustdv_gpi_sys as sys;
29
30#[cfg(test)]
33use rustdv_vpi_stubs as _;
34
35pub mod value;
36pub use value::{Logic, LogicArray};
37
38#[derive(Debug, Clone, PartialEq, Eq)]
43pub enum HandleError {
44 NotFound { name: String, scope: String },
47 WrongKind { name: String, expected: &'static str, actual: String },
49 NoTopModule,
50}
51
52impl fmt::Display for HandleError {
53 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
54 match self {
55 HandleError::NotFound { name, scope } => {
56 write!(f, "no object named '{name}' in scope '{scope}'")
57 }
58 HandleError::WrongKind { name, expected, actual } => {
59 write!(f, "'{name}' is a {actual}, expected {expected}")
60 }
61 HandleError::NoTopModule => write!(f, "no top-level module found"),
62 }
63 }
64}
65impl std::error::Error for HandleError {}
66
67#[derive(Debug, Clone, PartialEq, Eq)]
68pub enum ValueError {
69 FourState(String),
71 Width { want: u32, have: usize },
72}
73
74impl fmt::Display for ValueError {
75 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
76 match self {
77 ValueError::FourState(s) => write!(f, "value '{s}' has x/z bits"),
78 ValueError::Width { want, have } => write!(f, "width mismatch: want {want}, have {have}"),
79 }
80 }
81}
82impl std::error::Error for ValueError {}
83
84#[derive(Copy, Clone, PartialEq, Eq)]
91pub struct ObjHandle(sys::vpiHandle);
92
93impl ObjHandle {
94 fn new(h: sys::vpiHandle) -> Option<Self> {
95 if h.is_null() { None } else { Some(ObjHandle(h)) }
96 }
97 fn get(self, prop: i32) -> i32 {
98 unsafe { sys::vpi_get(prop, self.0) }
99 }
100 fn get_str(self, prop: i32) -> String {
101 unsafe {
103 let p = sys::vpi_get_str(prop, self.0);
104 if p.is_null() {
105 String::new()
106 } else {
107 CStr::from_ptr(p).to_string_lossy().into_owned()
108 }
109 }
110 }
111}
112
113#[derive(Copy, Clone)]
115pub enum AnyHandle {
116 Hierarchy(HierarchyHandle),
117 Logic(LogicHandle),
118 Other(ObjHandle),
119}
120
121impl AnyHandle {
122 pub fn classify(h: ObjHandle) -> AnyHandle {
123 match h.get(sys::vpiType) {
124 sys::vpiModule => AnyHandle::Hierarchy(HierarchyHandle { h }),
125 sys::vpiNet | sys::vpiReg | sys::vpiIntegerVar | sys::vpiPort | sys::vpiMemory
126 | sys::vpiLongIntVar | sys::vpiShortIntVar | sys::vpiIntVar | sys::vpiByteVar
127 | sys::vpiEnumVar | sys::vpiBitVar => {
128 let width = h.get(sys::vpiSize).max(0) as u32;
132 AnyHandle::Logic(LogicHandle { h, width })
133 }
134 _ => AnyHandle::Other(h),
135 }
136 }
137
138 pub fn as_logic(self) -> Result<LogicHandle, HandleError> {
139 match self {
140 AnyHandle::Logic(l) => Ok(l),
141 AnyHandle::Hierarchy(h) => Err(HandleError::WrongKind {
142 name: h.full_name(),
143 expected: "signal",
144 actual: "module".into(),
145 }),
146 AnyHandle::Other(o) => Err(HandleError::WrongKind {
147 name: o.get_str(sys::vpiFullName),
148 expected: "signal",
149 actual: format!("vpiType {}", o.get(sys::vpiType)),
150 }),
151 }
152 }
153
154 pub fn as_hierarchy(self) -> Result<HierarchyHandle, HandleError> {
155 match self {
156 AnyHandle::Hierarchy(h) => Ok(h),
157 AnyHandle::Logic(l) => Err(HandleError::WrongKind {
158 name: l.full_name(),
159 expected: "module",
160 actual: "signal".into(),
161 }),
162 AnyHandle::Other(o) => Err(HandleError::WrongKind {
163 name: o.get_str(sys::vpiFullName),
164 expected: "module",
165 actual: format!("vpiType {}", o.get(sys::vpiType)),
166 }),
167 }
168 }
169}
170
171#[derive(Copy, Clone)]
174pub struct HierarchyHandle {
175 h: ObjHandle,
176}
177
178impl HierarchyHandle {
179 pub fn null_for_test() -> HierarchyHandle {
184 HierarchyHandle { h: ObjHandle(std::ptr::null_mut()) }
185 }
186
187 pub fn child(&self, name: &str) -> Result<AnyHandle, HandleError> {
189 let cname = CString::new(name).expect("NUL in signal name");
190 let h = unsafe { sys::vpi_handle_by_name(cname.as_ptr(), self.h.0) };
191 match ObjHandle::new(h) {
192 Some(h) => Ok(AnyHandle::classify(h)),
193 None => Err(HandleError::NotFound { name: name.into(), scope: self.full_name() }),
194 }
195 }
196
197 pub fn signal(&self, name: &str) -> Result<LogicHandle, HandleError> {
199 self.child(name)?.as_logic()
200 }
201
202 pub fn name(&self) -> String {
203 self.h.get_str(sys::vpiName)
204 }
205 pub fn full_name(&self) -> String {
206 self.h.get_str(sys::vpiFullName)
207 }
208
209 pub fn children(&self) -> Vec<AnyHandle> {
212 let mut out = Vec::new();
213 for t in [sys::vpiModule, sys::vpiNet, sys::vpiReg] {
214 unsafe {
215 let it = sys::vpi_iterate(t, self.h.0);
216 if it.is_null() {
217 continue;
218 }
219 loop {
220 let c = sys::vpi_scan(it);
221 if c.is_null() {
222 break; }
224 if let Some(h) = ObjHandle::new(c) {
225 out.push(AnyHandle::classify(h));
226 }
227 }
228 }
229 }
230 out
231 }
232}
233
234#[derive(Copy, Clone, PartialEq, Eq)]
239pub struct LogicHandle {
240 h: ObjHandle,
241 width: u32,
242}
243
244impl LogicHandle {
245 pub fn name(&self) -> String {
246 self.h.get_str(sys::vpiName)
247 }
248 pub fn full_name(&self) -> String {
249 self.h.get_str(sys::vpiFullName)
250 }
251 pub fn size(&self) -> u32 {
252 self.width
253 }
254
255 pub fn get_binstr(&self) -> String {
257 let mut val = sys::t_vpi_value {
258 format: sys::vpiBinStrVal,
259 value: sys::u_vpi_value_union { integer: 0 },
260 };
261 unsafe {
262 sys::vpi_get_value(self.h.0, &mut val);
263 let p = val.value.str_;
264 if p.is_null() {
265 String::new()
266 } else {
267 CStr::from_ptr(p).to_string_lossy().into_owned()
268 }
269 }
270 }
271
272 pub fn get(&self) -> LogicArray {
274 LogicArray::from_binstr(&self.get_binstr())
275 }
276
277 pub fn get_u64(&self) -> Result<u64, ValueError> {
280 let width = self.size().max(1);
281 if width > 64 {
282 return Err(ValueError::Width {
283 want: width,
284 have: 64,
285 });
286 }
287 let mut val = sys::t_vpi_value {
288 format: sys::vpiVectorVal,
289 value: sys::u_vpi_value_union {
290 vector: std::ptr::null_mut(),
291 },
292 };
293 unsafe {
294 sys::vpi_get_value(self.h.0, &mut val);
295 let words = val.value.vector;
296 if words.is_null() {
297 return Ok(0);
298 }
299 let word_count = width.div_ceil(32) as usize;
300 let mut result = 0u64;
301 for index in 0..word_count {
302 let word = *words.add(index);
303 let used_bits = if index + 1 == word_count && !width.is_multiple_of(32) {
304 width % 32
305 } else {
306 32
307 };
308 let mask = if used_bits == 32 {
309 u32::MAX
310 } else {
311 (1u32 << used_bits) - 1
312 };
313 if word.bval & mask != 0 {
314 return Err(ValueError::FourState(self.get_binstr()));
315 }
316 result |= ((word.aval & mask) as u64) << (index * 32);
317 }
318 Ok(result)
319 }
320 }
321
322 fn put_binstr_flags(&self, bin: &str, flags: i32) {
323 let c = CString::new(bin).expect("NUL in binstr");
324 let mut val = sys::t_vpi_value {
325 format: sys::vpiBinStrVal,
326 value: sys::u_vpi_value_union { str_: c.as_ptr() as *mut _ },
327 };
328 unsafe {
329 sys::vpi_put_value(self.h.0, &mut val, std::ptr::null_mut(), flags);
330 }
331 }
332
333 fn put_vector_words(&self, words: &mut [sys::t_vpi_vecval]) {
334 let mut val = sys::t_vpi_value {
335 format: sys::vpiVectorVal,
336 value: sys::u_vpi_value_union {
337 vector: words.as_mut_ptr(),
338 },
339 };
340 unsafe {
341 sys::vpi_put_value(self.h.0, &mut val, std::ptr::null_mut(), sys::vpiNoDelay);
342 }
343 }
344
345 pub fn set_u64_now(&self, v: u64) {
349 let width = self.size().max(1);
350 let word_count = width.div_ceil(32) as usize;
351 let mut words = if word_count <= 2 {
352 let words = [
353 sys::t_vpi_vecval {
354 aval: v as u32,
355 bval: 0,
356 },
357 sys::t_vpi_vecval {
358 aval: (v >> 32) as u32,
359 bval: 0,
360 },
361 ];
362 words[..word_count].to_vec()
363 } else {
364 let mut words = vec![sys::t_vpi_vecval::default(); word_count];
365 words[0].aval = v as u32;
366 words[1].aval = (v >> 32) as u32;
367 words
368 };
369 if !width.is_multiple_of(32) {
370 let mask = (1u32 << (width % 32)) - 1;
371 let last = words.last_mut().expect("positive width has a vector word");
372 last.aval &= mask;
373 last.bval &= mask;
374 }
375 self.put_vector_words(&mut words);
376 }
377
378 pub fn set_now(&self, v: &LogicArray) {
380 self.put_binstr_flags(&v.to_binstr(), sys::vpiNoDelay);
381 }
382}
383
384pub fn top_modules() -> Vec<HierarchyHandle> {
386 let mut out = Vec::new();
387 unsafe {
388 let it = sys::vpi_iterate(sys::vpiModule, std::ptr::null_mut());
389 if it.is_null() {
390 return out;
391 }
392 loop {
393 let m = sys::vpi_scan(it);
394 if m.is_null() {
395 break;
396 }
397 if let Some(h) = ObjHandle::new(m) {
398 out.push(HierarchyHandle { h });
399 }
400 }
401 }
402 out
403}
404
405pub fn top_module() -> Result<HierarchyHandle, HandleError> {
407 top_modules().into_iter().next().ok_or(HandleError::NoTopModule)
408}
409
410pub fn sim_time_steps() -> u64 {
416 let mut t = sys::t_vpi_time { type_: sys::vpiSimTime, high: 0, low: 0, real: 0.0 };
417 unsafe { sys::vpi_get_time(std::ptr::null_mut(), &mut t) };
418 ((t.high as u64) << 32) | (t.low as u64)
419}
420
421pub fn time_precision() -> i32 {
423 thread_local! {
424 static PREC: Cell<Option<i32>> = const { Cell::new(None) };
425 }
426 PREC.with(|p| match p.get() {
427 Some(v) => v,
428 None => {
429 let v = unsafe { sys::vpi_get(sys::vpiTimePrecision, std::ptr::null_mut()) };
430 p.set(Some(v));
431 v
432 }
433 })
434}
435
436pub fn finish() {
438 unsafe {
439 sys::vpi_control(sys::vpiFinish, 0i32);
440 }
441}
442
443thread_local! {
448 static PANIC_SINK: RefCell<Option<Box<dyn Fn(String)>>> = const { RefCell::new(None) };
449}
450
451pub fn set_panic_sink(f: Box<dyn Fn(String)>) {
455 PANIC_SINK.with(|s| *s.borrow_mut() = Some(f));
456}
457
458fn report_panic(payload: Box<dyn std::any::Any + Send>) {
459 let msg = if let Some(s) = payload.downcast_ref::<&str>() {
460 s.to_string()
461 } else if let Some(s) = payload.downcast_ref::<String>() {
462 s.clone()
463 } else {
464 "panic (non-string payload)".to_string()
465 };
466 PANIC_SINK.with(|s| {
467 if let Some(f) = s.borrow().as_ref() {
468 f(msg.clone());
469 } else {
470 eprintln!("rustdv: panic in simulator callback: {msg}");
471 }
472 });
473}
474
475enum CbKind {
480 OneShot,
481 Recurring,
482}
483
484struct CbShared {
485 kind: CbKind,
486 vpi_h: Cell<sys::vpiHandle>,
491 released: Cell<bool>,
494 once: RefCell<Option<Box<dyn FnOnce()>>>,
495 repeat: RefCell<Option<Box<dyn FnMut()>>>,
496}
497
498pub struct CallbackHandle {
502 shared: Rc<CbShared>,
503 raw: *const CbShared,
504 detached: bool,
505}
506
507impl CallbackHandle {
508 pub fn forget(mut self) {
512 self.detached = true;
513 }
514}
515
516impl Drop for CallbackHandle {
517 fn drop(&mut self) {
518 if self.detached {
519 return;
520 }
521 if !self.shared.released.get() {
522 self.shared.released.set(true);
523 unsafe {
524 sys::vpi_remove_cb(self.shared.vpi_h.get());
525 drop(Rc::from_raw(self.raw));
527 }
528 }
529 }
530}
531
532extern "C" fn trampoline(cb: *mut sys::t_cb_data) -> i32 {
533 unsafe {
534 let ud = (*cb).user_data as *const CbShared;
535 if ud.is_null() {
536 return 0;
537 }
538 Rc::increment_strong_count(ud);
541 let shared: Rc<CbShared> = Rc::from_raw(ud);
542 match shared.kind {
543 CbKind::OneShot => {
544 if !shared.released.get() {
545 shared.released.set(true);
546 let f = shared.once.borrow_mut().take();
547 sys::vpi_remove_cb(shared.vpi_h.get());
553 drop(Rc::from_raw(ud));
555 if let Some(f) = f {
556 if let Err(p) = catch_unwind(AssertUnwindSafe(f)) {
557 report_panic(p);
558 }
559 }
560 }
561 }
562 CbKind::Recurring => {
563 let mut guard = shared.repeat.borrow_mut();
564 if let Some(f) = guard.as_mut() {
565 if let Err(p) = catch_unwind(AssertUnwindSafe(|| f())) {
566 report_panic(p);
567 }
568 }
569 }
570 }
571 drop(shared);
572 }
573 0
574}
575
576fn register(
577 kind: CbKind,
578 once: Option<Box<dyn FnOnce()>>,
579 repeat: Option<Box<dyn FnMut()>>,
580 reason: i32,
581 obj: sys::vpiHandle,
582 time: Option<sys::t_vpi_time>,
583) -> CallbackHandle {
584 let shared = Rc::new(CbShared {
585 kind,
586 vpi_h: Cell::new(std::ptr::null_mut()),
587 released: Cell::new(false),
588 once: RefCell::new(once),
589 repeat: RefCell::new(repeat),
590 });
591 let raw = Rc::into_raw(shared.clone());
593
594 let mut t = time.unwrap_or(sys::t_vpi_time {
595 type_: sys::vpiSuppressTime,
596 high: 0,
597 low: 0,
598 real: 0.0,
599 });
600 let mut cb = sys::t_cb_data {
603 reason,
604 cb_rtn: Some(trampoline),
605 obj,
606 time: &mut t,
607 value: std::ptr::null_mut(),
608 index: 0,
609 user_data: raw as *mut _,
610 };
611 let vpi_h = unsafe { sys::vpi_register_cb(&mut cb) };
612 assert!(!vpi_h.is_null(), "vpi_register_cb failed (reason {reason})");
613 shared.vpi_h.set(vpi_h);
614 CallbackHandle { shared, raw, detached: false }
615}
616
617fn simtime(steps: u64) -> sys::t_vpi_time {
618 sys::t_vpi_time {
619 type_: sys::vpiSimTime,
620 high: (steps >> 32) as u32,
621 low: (steps & 0xFFFF_FFFF) as u32,
622 real: 0.0,
623 }
624}
625
626pub fn register_timer(steps: u64, f: Box<dyn FnOnce()>) -> CallbackHandle {
628 register(CbKind::OneShot, Some(f), None, sys::cbAfterDelay, std::ptr::null_mut(), Some(simtime(steps)))
629}
630
631pub fn register_value_change(sig: LogicHandle, f: Box<dyn FnMut()>) -> CallbackHandle {
634 register(
635 CbKind::Recurring,
636 None,
637 Some(f),
638 sys::cbValueChange,
639 sig.h.0,
640 Some(simtime(0)),
641 )
642}
643
644pub fn register_read_write(f: Box<dyn FnOnce()>) -> CallbackHandle {
646 register(CbKind::OneShot, Some(f), None, sys::cbReadWriteSynch, std::ptr::null_mut(), Some(simtime(0)))
647}
648
649pub fn register_read_only(f: Box<dyn FnOnce()>) -> CallbackHandle {
651 register(CbKind::OneShot, Some(f), None, sys::cbReadOnlySynch, std::ptr::null_mut(), Some(simtime(0)))
652}
653
654pub fn register_next_sim_time(f: Box<dyn FnOnce()>) -> CallbackHandle {
656 register(CbKind::OneShot, Some(f), None, sys::cbNextSimTime, std::ptr::null_mut(), None)
657}
658
659pub fn register_at_end_of_sim_time(f: Box<dyn FnOnce()>) -> CallbackHandle {
661 register(
662 CbKind::OneShot,
663 Some(f),
664 None,
665 sys::cbAtEndOfSimTime,
666 std::ptr::null_mut(),
667 Some(simtime(sim_time_steps())),
668 )
669}
670
671pub fn register_start_of_simulation(f: Box<dyn FnOnce()>) -> CallbackHandle {
673 register(CbKind::OneShot, Some(f), None, sys::cbStartOfSimulation, std::ptr::null_mut(), None)
674}
675
676pub fn register_end_of_simulation(f: Box<dyn FnOnce()>) -> CallbackHandle {
678 register(CbKind::OneShot, Some(f), None, sys::cbEndOfSimulation, std::ptr::null_mut(), None)
679}
680
681#[cfg(test)]
682mod callback_tests {
683 use super::*;
684 use std::cell::Cell;
685 use std::rc::Rc;
686
687 #[test]
688 fn fired_one_shot_releases_its_vpi_handle() {
689 rustdv_vpi_stubs::reset_callbacks();
690 let fired = Rc::new(Cell::new(false));
691 let fired_in_callback = fired.clone();
692 let handle = register_timer(1, Box::new(move || fired_in_callback.set(true)));
693
694 assert_eq!(rustdv_vpi_stubs::live_callback_handles(), 1);
695 rustdv_vpi_stubs::fire_next_callback();
696 assert!(fired.get());
697 drop(handle);
698
699 assert_eq!(rustdv_vpi_stubs::live_callback_handles(), 0);
700 rustdv_vpi_stubs::reset_callbacks();
701 }
702
703 #[test]
704 fn detached_one_shot_releases_shared_state_after_firing() {
705 rustdv_vpi_stubs::reset_callbacks();
706 let handle = register_timer(1, Box::new(|| {}));
707 let shared = Rc::downgrade(&handle.shared);
708
709 handle.forget();
710 assert!(shared.upgrade().is_some());
711 rustdv_vpi_stubs::fire_next_callback();
712
713 assert_eq!(rustdv_vpi_stubs::live_callback_handles(), 0);
714 assert!(shared.upgrade().is_none());
715 rustdv_vpi_stubs::reset_callbacks();
716 }
717
718 #[test]
719 fn callback_stub_refuses_to_reset_a_live_handle() {
720 rustdv_vpi_stubs::reset_callbacks();
721 let handle = register_timer(1, Box::new(|| {}));
722
723 let reset = catch_unwind(AssertUnwindSafe(rustdv_vpi_stubs::reset_callbacks));
724 assert!(reset.is_err());
725
726 drop(handle);
727 rustdv_vpi_stubs::reset_callbacks();
728 }
729}
730
731#[cfg(test)]
732mod handle_tests {
733 use super::*;
734
735 fn make_signal(width: i32, words: &[sys::t_vpi_vecval], binstr: &str) -> LogicHandle {
736 rustdv_vpi_stubs::configure_signal(width, words, binstr);
737 let raw = 1usize as sys::vpiHandle;
738 let AnyHandle::Logic(signal) = AnyHandle::classify(ObjHandle(raw)) else {
739 panic!("stub object was not classified as a signal");
740 };
741 signal
742 }
743
744 #[test]
745 fn signal_width_is_cached_when_the_handle_is_classified() {
746 rustdv_vpi_stubs::reset_property_gets();
747 let signal = make_signal(37, &[sys::t_vpi_vecval::default(); 2], &"0".repeat(37));
748
749 assert_eq!(signal.size(), 37);
750 assert_eq!(signal.size(), 37);
751 assert_eq!(rustdv_vpi_stubs::property_get_count(sys::vpiType), 1);
752 assert_eq!(rustdv_vpi_stubs::property_get_count(sys::vpiSize), 1);
753 rustdv_vpi_stubs::reset_property_gets();
754 }
755
756 #[test]
757 fn vector_read_uses_vpi_word_order_and_masks_unused_bits() {
758 let signal = make_signal(
759 37,
760 &[
761 sys::t_vpi_vecval {
762 aval: 0x89ab_cdef,
763 bval: 0,
764 },
765 sys::t_vpi_vecval {
766 aval: 0xffff_fff5,
767 bval: 0xffff_ffe0,
768 },
769 ],
770 "101011001101010111100110111101111",
771 );
772
773 assert_eq!(signal.get_u64(), Ok(0x0000_0015_89ab_cdef));
774 }
775
776 #[test]
777 fn vector_read_reports_xz_in_used_bits() {
778 let signal = make_signal(
779 8,
780 &[sys::t_vpi_vecval {
781 aval: 0x5a,
782 bval: 0x04,
783 }],
784 "01011x10",
785 );
786
787 assert_eq!(
788 signal.get_u64(),
789 Err(ValueError::FourState("01011x10".to_owned()))
790 );
791 }
792
793 #[test]
794 fn vector_write_truncates_and_zero_extends_to_signal_width() {
795 let signal = make_signal(37, &[sys::t_vpi_vecval::default(); 2], &"0".repeat(37));
796 signal.set_u64_now(0xffff_fff5_89ab_cdef);
797 let words = rustdv_vpi_stubs::last_put_vector();
798
799 assert_eq!(words.len(), 2);
800 assert_eq!(words[0].aval, 0x89ab_cdef);
801 assert_eq!(words[1].aval, 0x15);
802 assert_eq!(words[0].bval, 0);
803 assert_eq!(words[1].bval, 0);
804
805 let wide = make_signal(70, &[sys::t_vpi_vecval::default(); 3], &"0".repeat(70));
806 wide.set_u64_now(0x0123_4567_89ab_cdef);
807 let words = rustdv_vpi_stubs::last_put_vector();
808 assert_eq!(words.len(), 3);
809 assert_eq!(words[0].aval, 0x89ab_cdef);
810 assert_eq!(words[1].aval, 0x0123_4567);
811 assert_eq!(words[2].aval, 0);
812 }
813}