Skip to main content

rustdv_gpi/
rustdv_gpi.rs

1//! # rustdv-gpi
2//!
3//! Safe wrapper over the simulator programming interface (design-doc §3.3).
4//! Invariants upheld here so everything above is safe Rust:
5//!
6//! 1. Handles are opaque and non-null; fallible acquisition is `Result`.
7//! 2. Object-handle lifetime = simulation lifetime (freely `Copy`able IDs).
8//!    Callback registrations are modeled by RAII ([`CallbackHandle`]):
9//!    dropping a live handle removes it, and fired one-shots remove themselves
10//!    from inside the trampoline while both supported simulators still accept
11//!    the registration handle.
12//! 3. Strings are copied at the boundary, every call.
13//! 4. No unwinding across FFI: every trampoline wraps the closure in
14//!    `catch_unwind`; panics are routed to the panic sink.
15//! 5. Callback user-data ownership: an `Rc` whose C-side reference is
16//!    reclaimed exactly once (on fire for one-shots, on removal otherwise).
17//!
18//! Thread affinity (§3.4): all types here hold raw pointers and are
19//! therefore `!Send`/`!Sync` — the compiler rejects moving them off the
20//! simulator thread.
21
22use 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// Test executables need vpi_* symbol definitions (the simulator provides
32// them for the real cdylib) — see rustdv-vpi-stubs.
33#[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// ===========================================================================
42// Errors
43// ===========================================================================
44
45#[derive(Debug, Clone, PartialEq, Eq)]
46pub enum HandleError {
47    /// Name did not resolve (cocotb raises AttributeError here; rustdv
48    /// returns this — design-doc §0.6).
49    NotFound {
50        name: String,
51        scope: String,
52    },
53    /// Handle exists but is not the requested kind.
54    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    /// Value contains x/z bits and was asked for as an integer.
84    FourState(String),
85    /// The simulator did not supply the requested value representation.
86    Unavailable,
87    /// VPI strings cannot contain embedded NUL bytes.
88    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// ===========================================================================
110// Handles
111// ===========================================================================
112
113/// Raw non-null simulator object handle. Valid for the whole simulation
114/// (invariant 2), hence `Copy`. `!Send` because it wraps a raw pointer.
115#[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        // Invariant 3: copy immediately.
131        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/// Any simulator object, classified by type (design-doc §3.3 downcasting).
143#[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                // Signal width is immutable for the lifetime of a VPI
174                // object. Cache it at discovery so every value read/write
175                // does not pay for another vpi_get(vpiSize) crossing.
176                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/// A module/scope handle. Port of cocotb's HierarchyObject
240/// (cocotb: handle.py), with `Result`-returning lookup (mapping row 19).
241#[derive(Copy, Clone)]
242pub struct HierarchyHandle {
243    h: ObjHandle,
244}
245
246impl HierarchyHandle {
247    /// A handle to nothing, for unit tests that need a `RustdvCtx` but never
248    /// touch the DUT. Any VPI call through it goes to `rustdv-vpi-stubs`,
249    /// which panics — so a test that *does* touch the DUT fails loudly
250    /// instead of reading garbage.
251    pub fn null_for_test() -> HierarchyHandle {
252        HierarchyHandle {
253            h: ObjHandle(std::ptr::null_mut()),
254        }
255    }
256
257    /// Dynamic child lookup: `dut.child("clk")?` (design-doc OQ-6 lean).
258    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    /// Shorthand: child that must be a signal.
271    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    /// Iterate child objects (modules, nets, regs) — serves the
283    /// `visit_children` debug-print role at the DUT level.
284    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; // scan returning NULL frees the iterator
296                    }
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/// A simulator real variable.
318#[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/// A simulator string variable.
355#[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/// An unpacked struct or union. Read and write through its typed members.
403#[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/// A value-bearing signal handle (net/reg/var). Port of cocotb's
456/// LogicObject surface: explicit `get()`/`set()` (mapping row 20).
457/// Buffered-write semantics live a layer up in rustdv-sim; the methods
458/// here apply values immediately.
459#[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    /// Current value as a slice of VPI vector words. `Err` if the simulator
477    /// did not supply a vector representation (design-doc §0.6: conversion
478    /// failures are Results, not exceptions). Handles `unsafe` and null pointer
479    /// checks internally.
480    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    /// Masks one VPI vector word to the signal width. `Err` if any used bit is
499    /// x/z (design-doc §0.6: conversion failures are Results, not exceptions).
500    ///
501    /// # Arguments
502    ///
503    /// * `word` - Individual VPI vector word from [`Self::get_vpi_words`].
504    /// * `width` - Width of the entire signal in bits.
505    /// * `last` - Whether `word` is the signal's final VPI vector word.
506    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    /// Check that the signal width is at most `want` bits. Returns `Err` if
524    /// the signal is wider than `want` (design-doc §0.6: conversion failures
525    /// are Results, not exceptions).
526    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    /// Current value as bool; `Err` if the signal is wider than one bit or its
537    /// value is x/z (design-doc §0.6: conversion failures are Results, not
538    /// exceptions).
539    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    /// Current value as u8; `Err` if any bit is x/z (design-doc §0.6:
545    /// conversion failures are Results, not exceptions).
546    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    /// Current value as u16; `Err` if any bit is x/z (design-doc §0.6:
552    /// conversion failures are Results, not exceptions).
553    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    /// Current value as u32; `Err` if any bit is x/z (design-doc §0.6:
559    /// conversion failures are Results, not exceptions).
560    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    /// Current value as u64; `Err` if any bit is x/z (design-doc §0.6:
566    /// conversion failures are Results, not exceptions).
567    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    /// Current value as u128; `Err` if any bit is x/z (design-doc §0.6:
579    /// conversion failures are Results, not exceptions).
580    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    /// Current value as an unsigned arbitrary-precision integer; `Err` if
592    /// any bit is x/z.
593    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    /// Current value as a LogicArray (4-state). `Err` if the simulator does
603    /// not supply a VPI vector representation.
604    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    /// Mask unused bits in the final VPI vector word to the signal width.
611    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    /// Resize vector words to the signal width and pass them to
621    /// `vpi_put_value`. Handles unsafe usage internally, i.e. words can be any
622    /// length and the vpi call is still safe.
623    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            // Word length matches, just mask the final word to the signal width
627            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            // Word length is greater, truncate to the signal width and mask the
639            // final word
640            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            // Word length is less, pad with zeros to the signal width
653            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    /// Write a bool with the supplied VPI flag, zero-extended to the signal
668    /// width.
669    pub fn set_bool(&self, v: bool, flags: i32) {
670        self.set_vector_words(&mut v.to_vpi_words(), flags);
671    }
672
673    /// Immediate (NoDelay) write of a bool.
674    pub fn set_bool_now(&self, v: bool) {
675        self.set_bool(v, sys::vpiNoDelay);
676    }
677
678    /// Write a u8 with the supplied VPI flag, zero-extended / truncated to the
679    /// signal width.
680    pub fn set_u8(&self, v: u8, flags: i32) {
681        self.set_vector_words(&mut v.to_vpi_words(), flags);
682    }
683
684    /// Immediate (NoDelay) write of a u8.
685    pub fn set_u8_now(&self, v: u8) {
686        self.set_u8(v, sys::vpiNoDelay);
687    }
688
689    /// Write a u16 with the supplied VPI flag, zero-extended / truncated to
690    /// the signal width.
691    pub fn set_u16(&self, v: u16, flags: i32) {
692        self.set_vector_words(&mut v.to_vpi_words(), flags);
693    }
694
695    /// Immediate (NoDelay) write of a u16.
696    pub fn set_u16_now(&self, v: u16) {
697        self.set_u16(v, sys::vpiNoDelay);
698    }
699
700    /// Write a u32 with the supplied VPI flag, zero-extended / truncated to
701    /// the signal width.
702    pub fn set_u32(&self, v: u32, flags: i32) {
703        self.set_vector_words(&mut v.to_vpi_words(), flags);
704    }
705
706    /// Immediate (NoDelay) write of a u32.
707    pub fn set_u32_now(&self, v: u32) {
708        self.set_u32(v, sys::vpiNoDelay);
709    }
710
711    /// Write a u64 with the supplied VPI flag, zero-extended / truncated to
712    /// the signal width.
713    pub fn set_u64(&self, v: u64, flags: i32) {
714        self.set_vector_words(&mut v.to_vpi_words(), flags);
715    }
716
717    /// Immediate (NoDelay) write of a u64.
718    pub fn set_u64_now(&self, v: u64) {
719        self.set_u64(v, sys::vpiNoDelay);
720    }
721
722    /// Write a u128 with the supplied VPI flag, zero-extended / truncated to
723    /// the signal width.
724    pub fn set_u128(&self, v: u128, flags: i32) {
725        self.set_vector_words(&mut v.to_vpi_words(), flags);
726    }
727
728    /// Immediate (NoDelay) write of a u128.
729    pub fn set_u128_now(&self, v: u128) {
730        self.set_u128(v, sys::vpiNoDelay);
731    }
732
733    /// Write an arbitrary-precision integer with the supplied VPI flag,
734    /// zero-extended / truncated to the signal width.
735    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    /// Immediate (NoDelay) write of an arbitrary-precision integer.
744    pub fn set_bigint_now(&self, v: &BigUint) {
745        self.set_bigint(v, sys::vpiNoDelay);
746    }
747
748    /// Write a 4-state value with the supplied VPI flag.
749    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    /// Immediate (NoDelay) write of a 4-state value.
759    pub fn set_logic_now(&self, v: &LogicArray) {
760        self.set_logic(v, sys::vpiNoDelay);
761    }
762}
763
764/// All top-level modules in the design.
765pub 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
802/// The configured top-level module, or the first simulator root when no top
803/// was configured.
804///
805/// `RUSTDV_TOP` selects an exact root by name. Without it, the first top-level
806/// module returned by VPI is used.
807pub 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
815// ===========================================================================
816// Time
817// ===========================================================================
818
819/// Current simulation time in simulator precision steps.
820pub 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
831/// Simulator time precision as a power of ten (e.g. -9 = 1 ns).
832pub 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
846/// End the simulation (vpi_control(vpiFinish)).
847pub fn finish() {
848    unsafe {
849        sys::vpi_control(sys::vpiFinish, 0i32);
850    }
851}
852
853// ===========================================================================
854// Panic sink (invariant 4)
855// ===========================================================================
856
857type PanicSink = RefCell<Option<Box<dyn Fn(String)>>>;
858
859thread_local! {
860    static PANIC_SINK: PanicSink = const { RefCell::new(None) };
861}
862
863/// Install the handler invoked when a callback closure panics (the runner
864/// routes this to "fail the current test", mirroring cocotb catching
865/// BaseException per task).
866pub 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
887// ===========================================================================
888// Callbacks (invariant 5)
889// ===========================================================================
890
891enum CbKind {
892    OneShot,
893    Recurring,
894}
895
896struct CbShared {
897    kind: CbKind,
898    /// Registration handle returned by vpi_register_cb. One-shots remove it
899    /// from inside the trampoline, while it is valid on both supported
900    /// simulators: Icarus reaps the active callback after it returns and
901    /// Verilator releases its separately-owned handle object immediately.
902    vpi_h: Cell<sys::vpiHandle>,
903    /// True once the C-side Rc reference has been reclaimed (fired one-shot
904    /// or removed callback). Guards against double-free.
905    released: Cell<bool>,
906    once: RefCell<Option<Box<dyn FnOnce()>>>,
907    repeat: RefCell<Option<Box<dyn FnMut()>>>,
908}
909
910/// RAII callback registration. Dropping an unfired/live handle removes the
911/// simulator callback — this is what makes drop-based task cancellation
912/// (design-doc §4.6) clean up trigger registrations for free.
913pub struct CallbackHandle {
914    shared: Rc<CbShared>,
915    raw: *const CbShared,
916    detached: bool,
917}
918
919impl CallbackHandle {
920    /// Detach Rust ownership without leaking it. A detached one-shot keeps its
921    /// C-side reference until it fires and then self-cleans; a detached
922    /// recurring callback lives for the rest of the simulation.
923    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                // Reclaim the C-side reference.
938                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        // Hold our own reference for the duration of the call so that
951        // closures dropping the CallbackHandle can't free us mid-flight.
952        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                    // The returned callback handle has different post-fire
960                    // ownership across simulators. Remove it while the active
961                    // callback is still valid on both: Icarus marks it for
962                    // self-reaping, while Verilator deletes the retained
963                    // VerilatedVpioReasonCb handle.
964                    sys::vpi_remove_cb(shared.vpi_h.get());
965                    // Reclaim the C-side reference before running user code.
966                    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    // C-side reference:
1004    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    // value: NULL — closures read signal values themselves; Icarus rejects
1013    // vpiSuppressVal on value-change callbacks ("format 10 not supported").
1014    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
1042/// One-shot callback after `steps` precision units (cbAfterDelay).
1043pub 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
1054/// Recurring callback on any value change of `sig` (cbValueChange). The
1055/// closure reads the signal itself; edge filtering happens in rustdv-sim.
1056pub 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
1067/// One-shot callback at the next ReadWrite synch point.
1068pub 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
1079/// One-shot callback at the next ReadOnly synch point.
1080pub 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
1091/// One-shot callback at the next simulation time step.
1092pub 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
1103/// One-shot callback at the end of the current simulation time step.
1104pub 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
1115/// One-shot callback at start of simulation (the bootstrap hook).
1116pub 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
1127/// One-shot callback at end of simulation.
1128pub 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}