Skip to main content

rustdv_sim/
handle.rs

1//! Typed DUT handles with scheduled-write semantics and edge methods —
2//! the user-facing layer over rustdv-gpi (design-doc mapping rows 14,
3//! 19–22). `set()` is buffered and applied at the next ReadWrite phase;
4//! `set_now()` is the immediate variant.
5
6use rustdv_gpi as gpi;
7pub use rustdv_gpi::{HandleError, Logic, LogicArray, ValueError};
8
9use crate::phase;
10use crate::triggers::{Edge, EdgeKind};
11
12/// A module/scope handle: `dut.child("name")?` (OQ-6 dynamic-first lean).
13#[derive(Copy, Clone)]
14pub struct HierarchyHandle {
15    raw: gpi::HierarchyHandle,
16}
17impl HierarchyHandle {
18    /// A handle to nothing, for unit tests that need a `RustdvCtx` but never
19    /// touch the DUT. Any VPI call through it reaches `rustdv-vpi-stubs`,
20    /// which panics — so a test that *does* touch the DUT fails loudly rather
21    /// than reading garbage, and its author learns it belongs in a `sim-*`
22    /// case instead.
23    pub fn null_for_test() -> HierarchyHandle {
24        HierarchyHandle { raw: gpi::HierarchyHandle::null_for_test() }
25    }
26}
27
28
29impl HierarchyHandle {
30    pub fn child(&self, name: &str) -> Result<AnyHandle, HandleError> {
31        Ok(AnyHandle::wrap(self.raw.child(name)?))
32    }
33
34    /// Child that must be a signal.
35    pub fn signal(&self, name: &str) -> Result<LogicHandle, HandleError> {
36        Ok(LogicHandle { raw: self.raw.child(name)?.as_logic()? })
37    }
38
39    pub fn name(&self) -> String {
40        self.raw.name()
41    }
42    pub fn full_name(&self) -> String {
43        self.raw.full_name()
44    }
45
46    pub fn children(&self) -> Vec<AnyHandle> {
47        self.raw.children().into_iter().map(AnyHandle::wrap).collect()
48    }
49}
50
51#[derive(Copy, Clone)]
52pub enum AnyHandle {
53    Hierarchy(HierarchyHandle),
54    Logic(LogicHandle),
55    Other,
56}
57
58impl AnyHandle {
59    fn wrap(h: gpi::AnyHandle) -> AnyHandle {
60        match h {
61            gpi::AnyHandle::Hierarchy(h) => AnyHandle::Hierarchy(HierarchyHandle { raw: h }),
62            gpi::AnyHandle::Logic(l) => AnyHandle::Logic(LogicHandle { raw: l }),
63            gpi::AnyHandle::Other(_) => AnyHandle::Other,
64        }
65    }
66
67    pub fn as_logic(self) -> Option<LogicHandle> {
68        match self {
69            AnyHandle::Logic(l) => Some(l),
70            _ => None,
71        }
72    }
73    pub fn as_hierarchy(self) -> Option<HierarchyHandle> {
74        match self {
75            AnyHandle::Hierarchy(h) => Some(h),
76            _ => None,
77        }
78    }
79}
80
81/// A value-bearing signal. Explicit `get()`/`set()` (mapping row 20 —
82/// cocotb 2.x itself moved off the `.value` property).
83#[derive(Copy, Clone, PartialEq, Eq)]
84pub struct LogicHandle {
85    raw: gpi::LogicHandle,
86}
87
88impl std::fmt::Debug for LogicHandle {
89    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
90        write!(f, "LogicHandle(\"{}\")", self.full_name())
91    }
92}
93
94impl std::fmt::Debug for HierarchyHandle {
95    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
96        write!(f, "HierarchyHandle(\"{}\")", self.full_name())
97    }
98}
99
100impl LogicHandle {
101    pub fn name(&self) -> String {
102        self.raw.name()
103    }
104    pub fn full_name(&self) -> String {
105        self.raw.full_name()
106    }
107    pub fn size(&self) -> u32 {
108        self.raw.size()
109    }
110
111    // ---- read ----
112    pub fn get(&self) -> LogicArray {
113        self.raw.get()
114    }
115    pub fn get_binstr(&self) -> String {
116        self.raw.get_binstr()
117    }
118    pub fn get_u64(&self) -> Result<u64, ValueError> {
119        self.raw.get_u64()
120    }
121    /// Convenience for 1-bit signals: true iff the value is 1.
122    pub fn is_high(&self) -> bool {
123        self.raw.get_binstr() == "1"
124    }
125    pub fn is_low(&self) -> bool {
126        self.raw.get_binstr() == "0"
127    }
128
129    // ---- write: scheduled (applied at next ReadWrite phase, row 22) ----
130    pub fn set_u64(&self, v: u64) {
131        phase::schedule_write_u64(self.raw, v);
132    }
133    pub fn set(&self, v: &LogicArray) {
134        phase::schedule_write_arr(self.raw, v.clone());
135    }
136
137    // ---- write: immediate (setimmediatevalue analog) ----
138    //
139    // Immediate skips the write *scheduler*, not the phase *rule* (D108). The
140    // rule is the simulator's: writing during ReadOnly is illegal however the
141    // write gets there. Left unchecked, these two went straight to
142    // `vpi_put_value` and Icarus swallowed them with a printed diagnostic —
143    // "attempted to put a value to variable 'x' during a read-only synch
144    // callback" — after which the run continued on values that were never
145    // applied. That is the worst kind of wrong: it looks like a warning and it
146    // silently changes results.
147    pub fn set_u64_now(&self, v: u64) {
148        phase::deny_write_in_read_only(self.raw);
149        self.raw.set_u64_now(v);
150    }
151    pub fn set_now(&self, v: &LogicArray) {
152        phase::deny_write_in_read_only(self.raw);
153        self.raw.set_now(v);
154    }
155
156    // ---- edges (methods on typed handles, mapping row 14) ----
157    pub fn rising_edge(&self) -> Edge {
158        Edge::new(self.raw, EdgeKind::Rising)
159    }
160    pub fn falling_edge(&self) -> Edge {
161        Edge::new(self.raw, EdgeKind::Falling)
162    }
163    pub fn value_change(&self) -> Edge {
164        Edge::new(self.raw, EdgeKind::AnyChange)
165    }
166}
167
168/// The first top-level module (the DUT in single-top designs).
169pub fn top_module() -> Result<HierarchyHandle, HandleError> {
170    Ok(HierarchyHandle { raw: gpi::top_module()? })
171}