1use std::cell::{Cell, RefCell};
23use std::ffi::{CStr, CString};
24use std::fmt;
25use std::panic::{AssertUnwindSafe, catch_unwind};
26use std::rc::Rc;
27
28pub use num_bigint::BigUint;
29use rustdv_gpi_sys::{self as sys, t_vpi_vecval};
30
31#[cfg(test)]
34use rustdv_vpi_stubs as _;
35
36pub mod value;
37pub use value::{Logic, LogicArray};
38pub mod util;
39pub use util::ToVpiWords;
40
41#[derive(Debug, Clone, PartialEq, Eq)]
46pub enum HandleError {
47 NotFound {
50 name: String,
51 scope: String,
52 },
53 WrongKind {
55 name: String,
56 expected: &'static str,
57 actual: String,
58 },
59 NoTopModule,
60}
61
62impl fmt::Display for HandleError {
63 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
64 match self {
65 HandleError::NotFound { name, scope } => {
66 write!(f, "no object named '{name}' in scope '{scope}'")
67 }
68 HandleError::WrongKind {
69 name,
70 expected,
71 actual,
72 } => {
73 write!(f, "'{name}' is a {actual}, expected {expected}")
74 }
75 HandleError::NoTopModule => write!(f, "no top-level module found"),
76 }
77 }
78}
79impl std::error::Error for HandleError {}
80
81#[derive(Debug, Clone, PartialEq, Eq)]
82pub enum ValueError {
83 FourState(String),
85 Unavailable,
87 InteriorNul,
89 Width {
90 want: u32,
91 have: usize,
92 },
93}
94
95impl fmt::Display for ValueError {
96 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
97 match self {
98 ValueError::FourState(s) => write!(f, "value '{s}' has x/z bits"),
99 ValueError::Unavailable => write!(f, "simulator did not return the requested value"),
100 ValueError::InteriorNul => write!(f, "VPI string contains an embedded NUL byte"),
101 ValueError::Width { want, have } => {
102 write!(f, "width mismatch: want {want}, have {have}")
103 }
104 }
105 }
106}
107impl std::error::Error for ValueError {}
108
109#[derive(Copy, Clone, PartialEq, Eq)]
116pub struct ObjHandle(sys::vpiHandle);
117
118impl ObjHandle {
119 fn new(h: sys::vpiHandle) -> Option<Self> {
120 if h.is_null() {
121 None
122 } else {
123 Some(ObjHandle(h))
124 }
125 }
126 fn get(self, prop: i32) -> i32 {
127 unsafe { sys::vpi_get(prop, self.0) }
128 }
129 fn get_str(self, prop: i32) -> String {
130 unsafe {
132 let p = sys::vpi_get_str(prop, self.0);
133 if p.is_null() {
134 String::new()
135 } else {
136 CStr::from_ptr(p).to_string_lossy().into_owned()
137 }
138 }
139 }
140}
141
142#[derive(Copy, Clone)]
144pub enum AnyHandle {
145 Hierarchy(HierarchyHandle),
146 Logic(LogicHandle),
147 Real(RealHandle),
148 String(StringHandle),
149 Struct(AggregateHandle),
150 Union(AggregateHandle),
151 Other(ObjHandle),
152}
153
154impl AnyHandle {
155 pub fn classify(h: ObjHandle) -> AnyHandle {
156 match h.get(sys::vpiType) {
157 sys::vpiModule => AnyHandle::Hierarchy(HierarchyHandle { h }),
158 sys::vpiRealVar => AnyHandle::Real(RealHandle { h }),
159 sys::vpiStringVar => AnyHandle::String(StringHandle { h }),
160 sys::vpiStructVar => AnyHandle::Struct(AggregateHandle { h }),
161 sys::vpiUnionVar => AnyHandle::Union(AggregateHandle { h }),
162 sys::vpiNet
163 | sys::vpiReg
164 | sys::vpiIntegerVar
165 | sys::vpiPort
166 | sys::vpiMemory
167 | sys::vpiLongIntVar
168 | sys::vpiShortIntVar
169 | sys::vpiIntVar
170 | sys::vpiByteVar
171 | sys::vpiEnumVar
172 | sys::vpiBitVar => {
173 let width = h.get(sys::vpiSize).max(0) as u32;
177 AnyHandle::Logic(LogicHandle { h, width })
178 }
179 _ => AnyHandle::Other(h),
180 }
181 }
182
183 fn object(self) -> ObjHandle {
184 match self {
185 Self::Hierarchy(h) => h.h,
186 Self::Logic(h) => h.h,
187 Self::Real(h) => h.h,
188 Self::String(h) => h.h,
189 Self::Struct(h) | Self::Union(h) => h.h,
190 Self::Other(h) => h,
191 }
192 }
193
194 fn wrong_kind(self, expected: &'static str) -> HandleError {
195 let h = self.object();
196 HandleError::WrongKind {
197 name: h.get_str(sys::vpiFullName),
198 expected,
199 actual: format!("vpiType {}", h.get(sys::vpiType)),
200 }
201 }
202
203 pub fn as_logic(self) -> Result<LogicHandle, HandleError> {
204 match self {
205 Self::Logic(h) => Ok(h),
206 other => Err(other.wrong_kind("signal")),
207 }
208 }
209
210 pub fn as_hierarchy(self) -> Result<HierarchyHandle, HandleError> {
211 match self {
212 Self::Hierarchy(h) => Ok(h),
213 other => Err(other.wrong_kind("module")),
214 }
215 }
216
217 pub fn as_real(self) -> Result<RealHandle, HandleError> {
218 match self {
219 Self::Real(h) => Ok(h),
220 other => Err(other.wrong_kind("real")),
221 }
222 }
223
224 pub fn as_string(self) -> Result<StringHandle, HandleError> {
225 match self {
226 Self::String(h) => Ok(h),
227 other => Err(other.wrong_kind("string")),
228 }
229 }
230
231 pub fn as_aggregate(self) -> Result<AggregateHandle, HandleError> {
232 match self {
233 Self::Struct(h) | Self::Union(h) => Ok(h),
234 other => Err(other.wrong_kind("struct or union")),
235 }
236 }
237}
238
239#[derive(Copy, Clone)]
242pub struct HierarchyHandle {
243 h: ObjHandle,
244}
245
246impl HierarchyHandle {
247 pub fn null_for_test() -> HierarchyHandle {
252 HierarchyHandle {
253 h: ObjHandle(std::ptr::null_mut()),
254 }
255 }
256
257 pub fn child(&self, name: &str) -> Result<AnyHandle, HandleError> {
259 let cname = CString::new(name).expect("NUL in signal name");
260 let h = unsafe { sys::vpi_handle_by_name(cname.as_ptr(), self.h.0) };
261 match ObjHandle::new(h) {
262 Some(h) => Ok(AnyHandle::classify(h)),
263 None => Err(HandleError::NotFound {
264 name: name.into(),
265 scope: self.full_name(),
266 }),
267 }
268 }
269
270 pub fn signal(&self, name: &str) -> Result<LogicHandle, HandleError> {
272 self.child(name)?.as_logic()
273 }
274
275 pub fn name(&self) -> String {
276 self.h.get_str(sys::vpiName)
277 }
278 pub fn full_name(&self) -> String {
279 self.h.get_str(sys::vpiFullName)
280 }
281
282 pub fn children(&self) -> Vec<AnyHandle> {
285 let mut out = Vec::new();
286 for t in [sys::vpiModule, sys::vpiNet, sys::vpiReg] {
287 unsafe {
288 let it = sys::vpi_iterate(t, self.h.0);
289 if it.is_null() {
290 continue;
291 }
292 loop {
293 let c = sys::vpi_scan(it);
294 if c.is_null() {
295 break; }
297 if let Some(h) = ObjHandle::new(c) {
298 out.push(AnyHandle::classify(h));
299 }
300 }
301 }
302 }
303 out
304 }
305
306 pub fn real(&self, name: &str) -> Result<RealHandle, HandleError> {
307 self.child(name)?.as_real()
308 }
309 pub fn string(&self, name: &str) -> Result<StringHandle, HandleError> {
310 self.child(name)?.as_string()
311 }
312 pub fn aggregate(&self, name: &str) -> Result<AggregateHandle, HandleError> {
313 self.child(name)?.as_aggregate()
314 }
315}
316
317#[derive(Copy, Clone, PartialEq, Eq)]
319pub struct RealHandle {
320 h: ObjHandle,
321}
322
323impl RealHandle {
324 pub fn name(&self) -> String {
325 self.h.get_str(sys::vpiName)
326 }
327 pub fn full_name(&self) -> String {
328 self.h.get_str(sys::vpiFullName)
329 }
330 pub fn get(&self) -> Result<f64, ValueError> {
331 let mut value = sys::t_vpi_value {
332 format: sys::vpiRealVal,
333 value: sys::u_vpi_value_union { real: 0.0 },
334 };
335 unsafe {
336 sys::vpi_get_value(self.h.0, &mut value);
337 if value.format != sys::vpiRealVal {
338 return Err(ValueError::Unavailable);
339 }
340 Ok(value.value.real)
341 }
342 }
343 pub fn set_now(&self, value: f64) {
344 let mut value = sys::t_vpi_value {
345 format: sys::vpiRealVal,
346 value: sys::u_vpi_value_union { real: value },
347 };
348 unsafe {
349 sys::vpi_put_value(self.h.0, &mut value, std::ptr::null_mut(), sys::vpiNoDelay);
350 }
351 }
352}
353
354#[derive(Copy, Clone, PartialEq, Eq)]
356pub struct StringHandle {
357 h: ObjHandle,
358}
359
360impl StringHandle {
361 pub fn name(&self) -> String {
362 self.h.get_str(sys::vpiName)
363 }
364 pub fn full_name(&self) -> String {
365 self.h.get_str(sys::vpiFullName)
366 }
367 pub fn get(&self) -> Result<String, ValueError> {
368 let mut value = sys::t_vpi_value {
369 format: sys::vpiStringVal,
370 value: sys::u_vpi_value_union {
371 str_: std::ptr::null_mut(),
372 },
373 };
374 unsafe {
375 sys::vpi_get_value(self.h.0, &mut value);
376 if value.format != sys::vpiStringVal {
377 return Err(ValueError::Unavailable);
378 }
379 if value.value.str_.is_null() {
380 return Err(ValueError::Unavailable);
381 }
382 Ok(CStr::from_ptr(value.value.str_)
383 .to_string_lossy()
384 .into_owned())
385 }
386 }
387 pub fn set_now(&self, value: &str) -> Result<(), ValueError> {
388 let text = CString::new(value).map_err(|_| ValueError::InteriorNul)?;
389 let mut value = sys::t_vpi_value {
390 format: sys::vpiStringVal,
391 value: sys::u_vpi_value_union {
392 str_: text.as_ptr().cast_mut(),
393 },
394 };
395 unsafe {
396 sys::vpi_put_value(self.h.0, &mut value, std::ptr::null_mut(), sys::vpiNoDelay);
397 }
398 Ok(())
399 }
400}
401
402#[derive(Copy, Clone)]
404pub struct AggregateHandle {
405 h: ObjHandle,
406}
407
408impl AggregateHandle {
409 pub fn name(&self) -> String {
410 self.h.get_str(sys::vpiName)
411 }
412 pub fn full_name(&self) -> String {
413 self.h.get_str(sys::vpiFullName)
414 }
415 pub fn child(&self, name: &str) -> Result<AnyHandle, HandleError> {
416 let path =
417 CString::new(format!("{}.{}", self.full_name(), name)).expect("NUL in member name");
418 let raw = unsafe { sys::vpi_handle_by_name(path.as_ptr(), std::ptr::null_mut()) };
419 ObjHandle::new(raw)
420 .map(AnyHandle::classify)
421 .ok_or_else(|| HandleError::NotFound {
422 name: name.into(),
423 scope: self.full_name(),
424 })
425 }
426 pub fn children(&self) -> Vec<AnyHandle> {
427 let mut out = Vec::new();
428 unsafe {
429 let it = sys::vpi_iterate(sys::vpiMember, self.h.0);
430 if !it.is_null() {
431 while let Some(h) = ObjHandle::new(sys::vpi_scan(it)) {
432 out.push(AnyHandle::classify(h));
433 }
434 }
435 }
436 out
437 }
438 pub fn signal(&self, name: &str) -> Result<LogicHandle, HandleError> {
439 self.child(name)?.as_logic()
440 }
441}
442
443impl AggregateHandle {
444 pub fn real(&self, name: &str) -> Result<RealHandle, HandleError> {
445 self.child(name)?.as_real()
446 }
447 pub fn string(&self, name: &str) -> Result<StringHandle, HandleError> {
448 self.child(name)?.as_string()
449 }
450 pub fn aggregate(&self, name: &str) -> Result<AggregateHandle, HandleError> {
451 self.child(name)?.as_aggregate()
452 }
453}
454
455#[derive(Copy, Clone, PartialEq, Eq)]
460pub struct LogicHandle {
461 h: ObjHandle,
462 width: u32,
463}
464
465impl LogicHandle {
466 pub fn name(&self) -> String {
467 self.h.get_str(sys::vpiName)
468 }
469 pub fn full_name(&self) -> String {
470 self.h.get_str(sys::vpiFullName)
471 }
472 pub fn size(&self) -> u32 {
473 self.width
474 }
475
476 fn get_vpi_words(&self) -> Result<&[sys::t_vpi_vecval], ValueError> {
481 let mut val = sys::t_vpi_value {
482 format: sys::vpiVectorVal,
483 value: sys::u_vpi_value_union {
484 vector: std::ptr::null_mut(),
485 },
486 };
487 unsafe {
488 sys::vpi_get_value(self.h.0, &mut val);
489 let words = val.value.vector;
490 if words.is_null() {
491 return Err(ValueError::Unavailable);
492 }
493 let word_count = self.size().div_ceil(32) as usize;
494 Ok(std::slice::from_raw_parts(words, word_count))
495 }
496 }
497
498 fn get_vpi_word(&self, word: &t_vpi_vecval, width: u32, last: bool) -> Result<u32, ValueError> {
507 let used_bits = if last && !width.is_multiple_of(32) {
508 width % 32
509 } else {
510 32
511 };
512 let mask = if used_bits == 32 {
513 u32::MAX
514 } else {
515 (1u32 << used_bits) - 1
516 };
517 if word.bval & mask != 0 {
518 return Err(ValueError::FourState(self.get_logic()?.to_binstr()));
519 }
520 Ok(word.aval & mask)
521 }
522
523 fn check_size(&self, want: u32) -> Result<(), ValueError> {
527 if self.size() > want {
528 return Err(ValueError::Width {
529 want,
530 have: self.size() as usize,
531 });
532 }
533 Ok(())
534 }
535
536 pub fn get_bool(&self) -> Result<bool, ValueError> {
540 self.check_size(1)?;
541 Ok(self.get_vpi_word(&self.get_vpi_words()?[0], self.size(), true)? != 0)
542 }
543
544 pub fn get_u8(&self) -> Result<u8, ValueError> {
547 self.check_size(u8::BITS)?;
548 Ok(self.get_vpi_word(&self.get_vpi_words()?[0], self.size(), true)? as u8)
549 }
550
551 pub fn get_u16(&self) -> Result<u16, ValueError> {
554 self.check_size(u16::BITS)?;
555 Ok(self.get_vpi_word(&self.get_vpi_words()?[0], self.size(), true)? as u16)
556 }
557
558 pub fn get_u32(&self) -> Result<u32, ValueError> {
561 self.check_size(u32::BITS)?;
562 self.get_vpi_word(&self.get_vpi_words()?[0], self.size(), true)
563 }
564
565 pub fn get_u64(&self) -> Result<u64, ValueError> {
568 self.check_size(u64::BITS)?;
569 let words = self.get_vpi_words()?;
570 let mut result = 0;
571 for (index, word) in words.iter().enumerate() {
572 let word = self.get_vpi_word(word, self.size(), index + 1 == words.len())?;
573 result |= (word as u64) << (index * 32);
574 }
575 Ok(result)
576 }
577
578 pub fn get_u128(&self) -> Result<u128, ValueError> {
581 self.check_size(u128::BITS)?;
582 let words = self.get_vpi_words()?;
583 let mut result = 0;
584 for (index, word) in words.iter().enumerate() {
585 let word = self.get_vpi_word(word, self.size(), index + 1 == words.len())?;
586 result |= (word as u128) << (index * 32);
587 }
588 Ok(result)
589 }
590
591 pub fn get_bigint(&self) -> Result<BigUint, ValueError> {
594 let words = self.get_vpi_words()?;
595 let mut digits = Vec::with_capacity(words.len());
596 for (index, word) in words.iter().enumerate() {
597 digits.push(self.get_vpi_word(word, self.size(), index + 1 == words.len())?);
598 }
599 Ok(BigUint::from_slice(&digits))
600 }
601
602 pub fn get_logic(&self) -> Result<LogicArray, ValueError> {
605 let words = self.get_vpi_words()?;
606 let words: Vec<(u32, u32)> = words.iter().map(|word| (word.aval, word.bval)).collect();
607 Ok(LogicArray::from_vpi_words(&words, self.size() as usize))
608 }
609
610 fn mask_vector_words(&self, words: &mut [sys::t_vpi_vecval]) {
612 if !self.size().is_multiple_of(32) {
613 let mask = (1u32 << (self.size() % 32)) - 1;
614 let last = words.last_mut().expect("partial width has a vector word");
615 last.aval &= mask;
616 last.bval &= mask;
617 }
618 }
619
620 fn set_vector_words(&self, words: &mut [sys::t_vpi_vecval], flags: i32) {
624 let word_count = self.size().div_ceil(32) as usize;
625 if words.len() == word_count {
626 self.mask_vector_words(words);
628 unsafe {
629 let mut vpi_value = sys::t_vpi_value {
630 format: sys::vpiVectorVal,
631 value: sys::u_vpi_value_union {
632 vector: words.as_mut_ptr(),
633 },
634 };
635 sys::vpi_put_value(self.h.0, &mut vpi_value, std::ptr::null_mut(), flags);
636 }
637 } else if words.len() > word_count {
638 let words = &mut words[..word_count];
641 self.mask_vector_words(words);
642 unsafe {
643 let mut vpi_value = sys::t_vpi_value {
644 format: sys::vpiVectorVal,
645 value: sys::u_vpi_value_union {
646 vector: words.as_mut_ptr(),
647 },
648 };
649 sys::vpi_put_value(self.h.0, &mut vpi_value, std::ptr::null_mut(), flags);
650 }
651 } else {
652 let mut words = words.to_vec();
654 words.resize(word_count, sys::t_vpi_vecval::default());
655 unsafe {
656 let mut vpi_value = sys::t_vpi_value {
657 format: sys::vpiVectorVal,
658 value: sys::u_vpi_value_union {
659 vector: words.as_mut_ptr(),
660 },
661 };
662 sys::vpi_put_value(self.h.0, &mut vpi_value, std::ptr::null_mut(), flags);
663 }
664 }
665 }
666
667 pub fn set_bool(&self, v: bool, flags: i32) {
670 self.set_vector_words(&mut v.to_vpi_words(), flags);
671 }
672
673 pub fn set_bool_now(&self, v: bool) {
675 self.set_bool(v, sys::vpiNoDelay);
676 }
677
678 pub fn set_u8(&self, v: u8, flags: i32) {
681 self.set_vector_words(&mut v.to_vpi_words(), flags);
682 }
683
684 pub fn set_u8_now(&self, v: u8) {
686 self.set_u8(v, sys::vpiNoDelay);
687 }
688
689 pub fn set_u16(&self, v: u16, flags: i32) {
692 self.set_vector_words(&mut v.to_vpi_words(), flags);
693 }
694
695 pub fn set_u16_now(&self, v: u16) {
697 self.set_u16(v, sys::vpiNoDelay);
698 }
699
700 pub fn set_u32(&self, v: u32, flags: i32) {
703 self.set_vector_words(&mut v.to_vpi_words(), flags);
704 }
705
706 pub fn set_u32_now(&self, v: u32) {
708 self.set_u32(v, sys::vpiNoDelay);
709 }
710
711 pub fn set_u64(&self, v: u64, flags: i32) {
714 self.set_vector_words(&mut v.to_vpi_words(), flags);
715 }
716
717 pub fn set_u64_now(&self, v: u64) {
719 self.set_u64(v, sys::vpiNoDelay);
720 }
721
722 pub fn set_u128(&self, v: u128, flags: i32) {
725 self.set_vector_words(&mut v.to_vpi_words(), flags);
726 }
727
728 pub fn set_u128_now(&self, v: u128) {
730 self.set_u128(v, sys::vpiNoDelay);
731 }
732
733 pub fn set_bigint(&self, v: &BigUint, flags: i32) {
736 let mut words: Vec<sys::t_vpi_vecval> = v
737 .iter_u32_digits()
738 .map(|aval| sys::t_vpi_vecval { aval, bval: 0 })
739 .collect();
740 self.set_vector_words(&mut words, flags);
741 }
742
743 pub fn set_bigint_now(&self, v: &BigUint) {
745 self.set_bigint(v, sys::vpiNoDelay);
746 }
747
748 pub fn set_logic(&self, v: &LogicArray, flags: i32) {
750 let mut words: Vec<sys::t_vpi_vecval> = v
751 .to_vpi_words()
752 .into_iter()
753 .map(|(aval, bval)| sys::t_vpi_vecval { aval, bval })
754 .collect();
755 self.set_vector_words(&mut words, flags);
756 }
757
758 pub fn set_logic_now(&self, v: &LogicArray) {
760 self.set_logic(v, sys::vpiNoDelay);
761 }
762}
763
764pub fn top_modules() -> Vec<HierarchyHandle> {
766 let mut out = Vec::new();
767 unsafe {
768 let it = sys::vpi_iterate(sys::vpiModule, std::ptr::null_mut());
769 if it.is_null() {
770 return out;
771 }
772 loop {
773 let m = sys::vpi_scan(it);
774 if m.is_null() {
775 break;
776 }
777 if let Some(h) = ObjHandle::new(m) {
778 out.push(HierarchyHandle { h });
779 }
780 }
781 }
782 out
783}
784
785fn select_top_module(explicit_name: Option<&str>) -> Result<HierarchyHandle, HandleError> {
786 if let Some(name) = explicit_name {
787 let cname = CString::new(name).expect("NUL in RUSTDV_TOP");
788 let raw = unsafe { sys::vpi_handle_by_name(cname.as_ptr(), std::ptr::null_mut()) };
789 let h = ObjHandle::new(raw).ok_or_else(|| HandleError::NotFound {
790 name: name.to_owned(),
791 scope: "<top-level>".to_owned(),
792 })?;
793 return AnyHandle::classify(h).as_hierarchy();
794 }
795
796 top_modules()
797 .into_iter()
798 .next()
799 .ok_or(HandleError::NoTopModule)
800}
801
802pub fn top_module() -> Result<HierarchyHandle, HandleError> {
808 let explicit_name = std::env::var("RUSTDV_TOP")
809 .ok()
810 .map(|name| name.trim().to_owned())
811 .filter(|name| !name.is_empty());
812 select_top_module(explicit_name.as_deref())
813}
814
815pub fn sim_time_steps() -> u64 {
821 let mut t = sys::t_vpi_time {
822 type_: sys::vpiSimTime,
823 high: 0,
824 low: 0,
825 real: 0.0,
826 };
827 unsafe { sys::vpi_get_time(std::ptr::null_mut(), &mut t) };
828 ((t.high as u64) << 32) | (t.low as u64)
829}
830
831pub fn time_precision() -> i32 {
833 thread_local! {
834 static PREC: Cell<Option<i32>> = const { Cell::new(None) };
835 }
836 PREC.with(|p| match p.get() {
837 Some(v) => v,
838 None => {
839 let v = unsafe { sys::vpi_get(sys::vpiTimePrecision, std::ptr::null_mut()) };
840 p.set(Some(v));
841 v
842 }
843 })
844}
845
846pub fn finish() {
848 unsafe {
849 sys::vpi_control(sys::vpiFinish, 0i32);
850 }
851}
852
853type PanicSink = RefCell<Option<Box<dyn Fn(String)>>>;
858
859thread_local! {
860 static PANIC_SINK: PanicSink = const { RefCell::new(None) };
861}
862
863pub fn set_panic_sink(f: Box<dyn Fn(String)>) {
867 PANIC_SINK.with(|s| *s.borrow_mut() = Some(f));
868}
869
870fn report_panic(payload: Box<dyn std::any::Any + Send>) {
871 let msg = if let Some(s) = payload.downcast_ref::<&str>() {
872 s.to_string()
873 } else if let Some(s) = payload.downcast_ref::<String>() {
874 s.clone()
875 } else {
876 "panic (non-string payload)".to_string()
877 };
878 PANIC_SINK.with(|s| {
879 if let Some(f) = s.borrow().as_ref() {
880 f(msg.clone());
881 } else {
882 eprintln!("rustdv: panic in simulator callback: {msg}");
883 }
884 });
885}
886
887enum CbKind {
892 OneShot,
893 Recurring,
894}
895
896struct CbShared {
897 kind: CbKind,
898 vpi_h: Cell<sys::vpiHandle>,
903 released: Cell<bool>,
906 once: RefCell<Option<Box<dyn FnOnce()>>>,
907 repeat: RefCell<Option<Box<dyn FnMut()>>>,
908}
909
910pub struct CallbackHandle {
914 shared: Rc<CbShared>,
915 raw: *const CbShared,
916 detached: bool,
917}
918
919impl CallbackHandle {
920 pub fn forget(mut self) {
924 self.detached = true;
925 }
926}
927
928impl Drop for CallbackHandle {
929 fn drop(&mut self) {
930 if self.detached {
931 return;
932 }
933 if !self.shared.released.get() {
934 self.shared.released.set(true);
935 unsafe {
936 sys::vpi_remove_cb(self.shared.vpi_h.get());
937 drop(Rc::from_raw(self.raw));
939 }
940 }
941 }
942}
943
944extern "C" fn trampoline(cb: *mut sys::t_cb_data) -> i32 {
945 unsafe {
946 let ud = (*cb).user_data as *const CbShared;
947 if ud.is_null() {
948 return 0;
949 }
950 Rc::increment_strong_count(ud);
953 let shared: Rc<CbShared> = Rc::from_raw(ud);
954 match shared.kind {
955 CbKind::OneShot => {
956 if !shared.released.get() {
957 shared.released.set(true);
958 let f = shared.once.borrow_mut().take();
959 sys::vpi_remove_cb(shared.vpi_h.get());
965 drop(Rc::from_raw(ud));
967 if let Some(f) = f
968 && let Err(p) = catch_unwind(AssertUnwindSafe(f))
969 {
970 report_panic(p);
971 }
972 }
973 }
974 CbKind::Recurring => {
975 let mut guard = shared.repeat.borrow_mut();
976 if let Some(f) = guard.as_mut()
977 && let Err(p) = catch_unwind(AssertUnwindSafe(f))
978 {
979 report_panic(p);
980 }
981 }
982 }
983 drop(shared);
984 }
985 0
986}
987
988fn register(
989 kind: CbKind,
990 once: Option<Box<dyn FnOnce()>>,
991 repeat: Option<Box<dyn FnMut()>>,
992 reason: i32,
993 obj: sys::vpiHandle,
994 time: Option<sys::t_vpi_time>,
995) -> CallbackHandle {
996 let shared = Rc::new(CbShared {
997 kind,
998 vpi_h: Cell::new(std::ptr::null_mut()),
999 released: Cell::new(false),
1000 once: RefCell::new(once),
1001 repeat: RefCell::new(repeat),
1002 });
1003 let raw = Rc::into_raw(shared.clone());
1005
1006 let mut t = time.unwrap_or(sys::t_vpi_time {
1007 type_: sys::vpiSuppressTime,
1008 high: 0,
1009 low: 0,
1010 real: 0.0,
1011 });
1012 let mut cb = sys::t_cb_data {
1015 reason,
1016 cb_rtn: Some(trampoline),
1017 obj,
1018 time: &mut t,
1019 value: std::ptr::null_mut(),
1020 index: 0,
1021 user_data: raw as *mut _,
1022 };
1023 let vpi_h = unsafe { sys::vpi_register_cb(&mut cb) };
1024 assert!(!vpi_h.is_null(), "vpi_register_cb failed (reason {reason})");
1025 shared.vpi_h.set(vpi_h);
1026 CallbackHandle {
1027 shared,
1028 raw,
1029 detached: false,
1030 }
1031}
1032
1033fn simtime(steps: u64) -> sys::t_vpi_time {
1034 sys::t_vpi_time {
1035 type_: sys::vpiSimTime,
1036 high: (steps >> 32) as u32,
1037 low: (steps & 0xFFFF_FFFF) as u32,
1038 real: 0.0,
1039 }
1040}
1041
1042pub fn register_timer(steps: u64, f: Box<dyn FnOnce()>) -> CallbackHandle {
1044 register(
1045 CbKind::OneShot,
1046 Some(f),
1047 None,
1048 sys::cbAfterDelay,
1049 std::ptr::null_mut(),
1050 Some(simtime(steps)),
1051 )
1052}
1053
1054pub fn register_value_change(sig: LogicHandle, f: Box<dyn FnMut()>) -> CallbackHandle {
1057 register(
1058 CbKind::Recurring,
1059 None,
1060 Some(f),
1061 sys::cbValueChange,
1062 sig.h.0,
1063 Some(simtime(0)),
1064 )
1065}
1066
1067pub fn register_read_write(f: Box<dyn FnOnce()>) -> CallbackHandle {
1069 register(
1070 CbKind::OneShot,
1071 Some(f),
1072 None,
1073 sys::cbReadWriteSynch,
1074 std::ptr::null_mut(),
1075 Some(simtime(0)),
1076 )
1077}
1078
1079pub fn register_read_only(f: Box<dyn FnOnce()>) -> CallbackHandle {
1081 register(
1082 CbKind::OneShot,
1083 Some(f),
1084 None,
1085 sys::cbReadOnlySynch,
1086 std::ptr::null_mut(),
1087 Some(simtime(0)),
1088 )
1089}
1090
1091pub fn register_next_sim_time(f: Box<dyn FnOnce()>) -> CallbackHandle {
1093 register(
1094 CbKind::OneShot,
1095 Some(f),
1096 None,
1097 sys::cbNextSimTime,
1098 std::ptr::null_mut(),
1099 None,
1100 )
1101}
1102
1103pub fn register_at_end_of_sim_time(f: Box<dyn FnOnce()>) -> CallbackHandle {
1105 register(
1106 CbKind::OneShot,
1107 Some(f),
1108 None,
1109 sys::cbAtEndOfSimTime,
1110 std::ptr::null_mut(),
1111 Some(simtime(sim_time_steps())),
1112 )
1113}
1114
1115pub fn register_start_of_simulation(f: Box<dyn FnOnce()>) -> CallbackHandle {
1117 register(
1118 CbKind::OneShot,
1119 Some(f),
1120 None,
1121 sys::cbStartOfSimulation,
1122 std::ptr::null_mut(),
1123 None,
1124 )
1125}
1126
1127pub fn register_end_of_simulation(f: Box<dyn FnOnce()>) -> CallbackHandle {
1129 register(
1130 CbKind::OneShot,
1131 Some(f),
1132 None,
1133 sys::cbEndOfSimulation,
1134 std::ptr::null_mut(),
1135 None,
1136 )
1137}
1138
1139#[cfg(test)]
1140mod vpi_word_tests {
1141 use super::*;
1142
1143 fn assert_single_word<T: ToVpiWords<1>>(value: T, expected: u32) {
1144 let words = value.to_vpi_words();
1145 assert_eq!(words[0].aval, expected);
1146 assert_eq!(words[0].bval, 0);
1147 }
1148
1149 #[test]
1150 fn scalar_values_convert_to_one_vpi_word() {
1151 assert_single_word(false, 0);
1152 assert_single_word(true, 1);
1153 assert_single_word(0xa5u8, 0xa5);
1154 assert_single_word(0xa5b6u16, 0xa5b6);
1155 assert_single_word(0xa5b6_c7d8u32, 0xa5b6_c7d8);
1156 }
1157
1158 #[test]
1159 fn wide_values_convert_in_least_significant_word_first_order() {
1160 let words = 0x0123_4567_89ab_cdefu64.to_vpi_words();
1161 assert_eq!(words.map(|word| word.aval), [0x89ab_cdef, 0x0123_4567]);
1162 assert!(words.iter().all(|word| word.bval == 0));
1163
1164 let words = 0x0123_4567_89ab_cdef_fedc_ba98_7654_3210u128.to_vpi_words();
1165 assert_eq!(
1166 words.map(|word| word.aval),
1167 [0x7654_3210, 0xfedc_ba98, 0x89ab_cdef, 0x0123_4567]
1168 );
1169 assert!(words.iter().all(|word| word.bval == 0));
1170 }
1171}
1172
1173#[cfg(test)]
1174mod callback_tests {
1175 use super::*;
1176 use std::cell::Cell;
1177 use std::rc::Rc;
1178
1179 #[test]
1180 fn fired_one_shot_releases_its_vpi_handle() {
1181 rustdv_vpi_stubs::reset_callbacks();
1182 let fired = Rc::new(Cell::new(false));
1183 let fired_in_callback = fired.clone();
1184 let handle = register_timer(1, Box::new(move || fired_in_callback.set(true)));
1185
1186 assert_eq!(rustdv_vpi_stubs::live_callback_handles(), 1);
1187 rustdv_vpi_stubs::fire_next_callback();
1188 assert!(fired.get());
1189 drop(handle);
1190
1191 assert_eq!(rustdv_vpi_stubs::live_callback_handles(), 0);
1192 rustdv_vpi_stubs::reset_callbacks();
1193 }
1194
1195 #[test]
1196 fn detached_one_shot_releases_shared_state_after_firing() {
1197 rustdv_vpi_stubs::reset_callbacks();
1198 let handle = register_timer(1, Box::new(|| {}));
1199 let shared = Rc::downgrade(&handle.shared);
1200
1201 handle.forget();
1202 assert!(shared.upgrade().is_some());
1203 rustdv_vpi_stubs::fire_next_callback();
1204
1205 assert_eq!(rustdv_vpi_stubs::live_callback_handles(), 0);
1206 assert!(shared.upgrade().is_none());
1207 rustdv_vpi_stubs::reset_callbacks();
1208 }
1209
1210 #[test]
1211 fn callback_stub_refuses_to_reset_a_live_handle() {
1212 rustdv_vpi_stubs::reset_callbacks();
1213 let handle = register_timer(1, Box::new(|| {}));
1214
1215 let reset = catch_unwind(AssertUnwindSafe(rustdv_vpi_stubs::reset_callbacks));
1216 assert!(reset.is_err());
1217
1218 drop(handle);
1219 rustdv_vpi_stubs::reset_callbacks();
1220 }
1221}
1222
1223#[cfg(test)]
1224mod handle_tests {
1225 use super::*;
1226
1227 #[test]
1228 fn sv_integral_variables_support_vector_access() {
1229 for object_type in [
1230 sys::vpiLongIntVar,
1231 sys::vpiShortIntVar,
1232 sys::vpiIntVar,
1233 sys::vpiByteVar,
1234 sys::vpiEnumVar,
1235 sys::vpiBitVar,
1236 ] {
1237 rustdv_vpi_stubs::configure_signal(
1238 64,
1239 &[
1240 sys::t_vpi_vecval {
1241 aval: 0x89ab_cdef,
1242 bval: 0,
1243 },
1244 sys::t_vpi_vecval {
1245 aval: 0x1234_5678,
1246 bval: 0,
1247 },
1248 ],
1249 "0",
1250 );
1251 rustdv_vpi_stubs::configure_signal_type(object_type);
1252 let signal = AnyHandle::classify(ObjHandle(1usize as sys::vpiHandle))
1253 .as_logic()
1254 .unwrap();
1255 assert_eq!(signal.size(), 64);
1256 assert_eq!(signal.get_u64(), Ok(0x1234_5678_89ab_cdef));
1257 signal.set_u64_now(0xfedc_ba98_7654_3210);
1258 let words = rustdv_vpi_stubs::last_put_vector();
1259 assert_eq!(words[0].aval, 0x7654_3210);
1260 assert_eq!(words[1].aval, 0xfedc_ba98);
1261 }
1262 rustdv_vpi_stubs::configure_signal_type(sys::vpiNet);
1263 }
1264
1265 #[test]
1266 fn non_logic_variables_have_typed_handles() {
1267 for object_type in [
1268 sys::vpiRealVar,
1269 sys::vpiStringVar,
1270 sys::vpiStructVar,
1271 sys::vpiUnionVar,
1272 ] {
1273 rustdv_vpi_stubs::configure_signal_type(object_type);
1274 assert!(matches!(
1275 AnyHandle::classify(ObjHandle(1usize as sys::vpiHandle)),
1276 AnyHandle::Real(_)
1277 | AnyHandle::String(_)
1278 | AnyHandle::Struct(_)
1279 | AnyHandle::Union(_)
1280 ));
1281 }
1282 rustdv_vpi_stubs::configure_signal_type(sys::vpiNet);
1283 }
1284
1285 #[test]
1286 fn top_selection_prefers_explicit_name_and_otherwise_uses_first_root() {
1287 rustdv_vpi_stubs::configure_top_modules(&["first", "dut"]);
1288 assert_eq!(select_top_module(Some("dut")).unwrap().name(), "dut");
1289 assert_eq!(select_top_module(None).unwrap().name(), "first");
1290 assert!(matches!(
1291 select_top_module(Some("missing")),
1292 Err(HandleError::NotFound { .. })
1293 ));
1294 rustdv_vpi_stubs::configure_top_modules(&[]);
1295 assert!(matches!(
1296 select_top_module(None),
1297 Err(HandleError::NoTopModule)
1298 ));
1299 }
1300
1301 fn make_signal(width: i32, words: &[sys::t_vpi_vecval], binstr: &str) -> LogicHandle {
1302 rustdv_vpi_stubs::configure_signal(width, words, binstr);
1303 let raw = 1usize as sys::vpiHandle;
1304 let AnyHandle::Logic(signal) = AnyHandle::classify(ObjHandle(raw)) else {
1305 panic!("stub object was not classified as a signal");
1306 };
1307 signal
1308 }
1309
1310 #[test]
1311 fn signal_width_is_cached_when_the_handle_is_classified() {
1312 rustdv_vpi_stubs::reset_property_gets();
1313 let signal = make_signal(37, &[sys::t_vpi_vecval::default(); 2], &"0".repeat(37));
1314
1315 assert_eq!(signal.size(), 37);
1316 assert_eq!(signal.size(), 37);
1317 assert_eq!(rustdv_vpi_stubs::property_get_count(sys::vpiType), 1);
1318 assert_eq!(rustdv_vpi_stubs::property_get_count(sys::vpiSize), 1);
1319 rustdv_vpi_stubs::reset_property_gets();
1320 }
1321
1322 #[test]
1323 fn vector_read_uses_vpi_word_order_and_masks_unused_bits() {
1324 let signal = make_signal(
1325 37,
1326 &[
1327 sys::t_vpi_vecval {
1328 aval: 0x89ab_cdef,
1329 bval: 0,
1330 },
1331 sys::t_vpi_vecval {
1332 aval: 0xffff_fff5,
1333 bval: 0xffff_ffe0,
1334 },
1335 ],
1336 "101011001101010111100110111101111",
1337 );
1338
1339 assert_eq!(signal.get_u64(), Ok(0x0000_0015_89ab_cdef));
1340 }
1341
1342 #[test]
1343 fn vector_read_reports_xz_in_used_bits() {
1344 let signal = make_signal(
1345 8,
1346 &[sys::t_vpi_vecval {
1347 aval: 0x5e,
1348 bval: 0x04,
1349 }],
1350 "01011x10",
1351 );
1352
1353 assert_eq!(
1354 signal.get_u64(),
1355 Err(ValueError::FourState("01011x10".to_owned()))
1356 );
1357 }
1358
1359 #[test]
1360 fn logic_array_read_uses_vpi_vector_words() {
1361 let signal = make_signal(
1362 4,
1363 &[sys::t_vpi_vecval {
1364 aval: 0b0110,
1365 bval: 0b0011,
1366 }],
1367 "0000",
1368 );
1369
1370 assert_eq!(signal.get_logic().unwrap().to_binstr(), "01xz");
1371 }
1372
1373 #[test]
1374 fn vector_read_returns_numeric_zero_from_a_vector_word() {
1375 let signal = make_signal(8, &[sys::t_vpi_vecval { aval: 0, bval: 0 }], "00000000");
1376
1377 assert_eq!(signal.get_u64(), Ok(0));
1378 }
1379
1380 #[test]
1381 fn bool_read_converts_single_bit_values() {
1382 let low = make_signal(1, &[sys::t_vpi_vecval { aval: 0, bval: 0 }], "0");
1383 assert_eq!(low.get_bool(), Ok(false));
1384
1385 let high = make_signal(1, &[sys::t_vpi_vecval { aval: 1, bval: 0 }], "1");
1386 assert_eq!(high.get_bool(), Ok(true));
1387 }
1388
1389 #[test]
1390 fn bool_read_rejects_wide_and_four_state_values() {
1391 let wide = make_signal(2, &[sys::t_vpi_vecval { aval: 1, bval: 0 }], "01");
1392 assert_eq!(wide.get_bool(), Err(ValueError::Width { want: 1, have: 2 }));
1393
1394 let unknown = make_signal(1, &[sys::t_vpi_vecval { aval: 1, bval: 1 }], "x");
1395 assert_eq!(
1396 unknown.get_bool(),
1397 Err(ValueError::FourState("x".to_owned()))
1398 );
1399 }
1400
1401 #[test]
1402 fn vector_read_reports_an_unavailable_vpi_result() {
1403 let signal = make_signal(8, &[sys::t_vpi_vecval { aval: 0, bval: 0 }], "00000000");
1404 rustdv_vpi_stubs::make_vector_value_unavailable();
1405
1406 assert_eq!(signal.get_u64(), Err(ValueError::Unavailable));
1407 assert_eq!(signal.get_bigint(), Err(ValueError::Unavailable));
1408 assert_eq!(signal.get_logic(), Err(ValueError::Unavailable));
1409 }
1410
1411 #[test]
1412 fn bigint_read_supports_values_wider_than_u64() {
1413 let signal = make_signal(
1414 70,
1415 &[
1416 sys::t_vpi_vecval {
1417 aval: 0x89ab_cdef,
1418 bval: 0,
1419 },
1420 sys::t_vpi_vecval {
1421 aval: 0x0123_4567,
1422 bval: 0,
1423 },
1424 sys::t_vpi_vecval {
1425 aval: 0xffff_ffea,
1426 bval: 0xffff_ffc0,
1427 },
1428 ],
1429 "1010100000000100100011010001010110011110001001101010111100110111101111",
1430 );
1431
1432 assert_eq!(
1433 signal.get_bigint().map(|value| value.to_str_radix(16)),
1434 Ok("2a0123456789abcdef".to_owned())
1435 );
1436 }
1437
1438 #[test]
1439 fn bigint_read_reports_xz_in_used_bits() {
1440 let signal = make_signal(
1441 65,
1442 &[
1443 sys::t_vpi_vecval::default(),
1444 sys::t_vpi_vecval::default(),
1445 sys::t_vpi_vecval { aval: 1, bval: 1 },
1446 ],
1447 &format!("x{}", "0".repeat(64)),
1448 );
1449
1450 assert_eq!(
1451 signal.get_bigint(),
1452 Err(ValueError::FourState(format!("x{}", "0".repeat(64))))
1453 );
1454 }
1455
1456 #[test]
1457 fn vector_write_truncates_and_zero_extends_to_signal_width() {
1458 let narrow = make_signal(8, &[sys::t_vpi_vecval::default()], "00000000");
1459 narrow.set_u64_now(0xffff_ffff_ffff_ffa5);
1460 let words = rustdv_vpi_stubs::last_put_vector();
1461 assert_eq!(words.len(), 1);
1462 assert_eq!(words[0].aval, 0xa5);
1463 assert_eq!(words[0].bval, 0);
1464
1465 let signal = make_signal(37, &[sys::t_vpi_vecval::default(); 2], &"0".repeat(37));
1466 signal.set_u64_now(0xffff_fff5_89ab_cdef);
1467 let words = rustdv_vpi_stubs::last_put_vector();
1468
1469 assert_eq!(words.len(), 2);
1470 assert_eq!(words[0].aval, 0x89ab_cdef);
1471 assert_eq!(words[1].aval, 0x15);
1472 assert_eq!(words[0].bval, 0);
1473 assert_eq!(words[1].bval, 0);
1474
1475 let wide = make_signal(70, &[sys::t_vpi_vecval::default(); 3], &"0".repeat(70));
1476 wide.set_u64_now(0x0123_4567_89ab_cdef);
1477 let words = rustdv_vpi_stubs::last_put_vector();
1478 assert_eq!(words.len(), 3);
1479 assert_eq!(words[0].aval, 0x89ab_cdef);
1480 assert_eq!(words[1].aval, 0x0123_4567);
1481 assert_eq!(words[2].aval, 0);
1482 }
1483
1484 #[test]
1485 fn typed_integer_setters_write_vpi_words() {
1486 let signal = make_signal(1, &[sys::t_vpi_vecval::default()], "0");
1487 signal.set_bool_now(true);
1488 assert_eq!(rustdv_vpi_stubs::last_put_vector()[0].aval, 1);
1489
1490 let signal = make_signal(8, &[sys::t_vpi_vecval::default()], "00000000");
1491 signal.set_u8(0xa5, sys::vpiNoDelay);
1492 assert_eq!(rustdv_vpi_stubs::last_put_vector()[0].aval, 0xa5);
1493
1494 let signal = make_signal(16, &[sys::t_vpi_vecval::default()], &"0".repeat(16));
1495 signal.set_u16_now(0xa5b6);
1496 assert_eq!(rustdv_vpi_stubs::last_put_vector()[0].aval, 0xa5b6);
1497
1498 let signal = make_signal(32, &[sys::t_vpi_vecval::default()], &"0".repeat(32));
1499 signal.set_u32_now(0xa5b6_c7d8);
1500 assert_eq!(rustdv_vpi_stubs::last_put_vector()[0].aval, 0xa5b6_c7d8);
1501
1502 let signal = make_signal(128, &[sys::t_vpi_vecval::default(); 4], &"0".repeat(128));
1503 signal.set_u128_now(0x0123_4567_89ab_cdef_fedc_ba98_7654_3210);
1504 let words = rustdv_vpi_stubs::last_put_vector();
1505 assert_eq!(
1506 words.iter().map(|word| word.aval).collect::<Vec<_>>(),
1507 [0x7654_3210, 0xfedc_ba98, 0x89ab_cdef, 0x0123_4567]
1508 );
1509 assert!(words.iter().all(|word| word.bval == 0));
1510 }
1511
1512 #[test]
1513 fn bigint_setter_writes_and_zero_extends_vpi_words() {
1514 let signal = make_signal(192, &[sys::t_vpi_vecval::default(); 6], &"0".repeat(192));
1515 let value = BigUint::from_slice(&[
1516 0x7654_3210,
1517 0xfedc_ba98,
1518 0x89ab_cdef,
1519 0x0123_4567,
1520 0xa5a5_5a5a,
1521 ]);
1522
1523 signal.set_bigint_now(&value);
1524
1525 let words = rustdv_vpi_stubs::last_put_vector();
1526 assert_eq!(
1527 words.iter().map(|word| word.aval).collect::<Vec<_>>(),
1528 [
1529 0x7654_3210,
1530 0xfedc_ba98,
1531 0x89ab_cdef,
1532 0x0123_4567,
1533 0xa5a5_5a5a,
1534 0,
1535 ]
1536 );
1537 assert!(words.iter().all(|word| word.bval == 0));
1538 }
1539
1540 #[test]
1541 fn vector_word_mask_clears_unused_aval_and_bval_bits() {
1542 let signal = make_signal(37, &[sys::t_vpi_vecval::default(); 2], &"0".repeat(37));
1543 let mut words = [
1544 sys::t_vpi_vecval {
1545 aval: u32::MAX,
1546 bval: u32::MAX,
1547 },
1548 sys::t_vpi_vecval {
1549 aval: u32::MAX,
1550 bval: u32::MAX,
1551 },
1552 ];
1553
1554 signal.mask_vector_words(&mut words);
1555
1556 assert_eq!(words[0].aval, u32::MAX);
1557 assert_eq!(words[0].bval, u32::MAX);
1558 assert_eq!(words[1].aval, 0x1f);
1559 assert_eq!(words[1].bval, 0x1f);
1560 }
1561
1562 #[test]
1563 fn logic_array_write_uses_vpi_vector_words() {
1564 let signal = make_signal(
1565 4,
1566 &[sys::t_vpi_vecval::default()],
1567 "binary-string path must not be used",
1568 );
1569 signal.set_logic_now(&LogicArray::from_binstr("01xz"));
1570
1571 let words = rustdv_vpi_stubs::last_put_vector();
1572 assert_eq!(words.len(), 1);
1573 assert_eq!(words[0].aval, 0b0110);
1574 assert_eq!(words[0].bval, 0b0011);
1575 }
1576}