Skip to main content

rustdv_sim/
triggers.rs

1//! GPI-backed awaitables: `Timer`, edge triggers, `NullTrigger`
2//! (design-doc §4.4). Each future registers its simulator callback lazily
3//! on first poll and removes it via RAII when dropped — cancellation of a
4//! waiting task cleans up its trigger registration for free (§4.6).
5
6use std::cell::{Cell, RefCell};
7use std::future::Future;
8use std::pin::Pin;
9use std::rc::Rc;
10use std::task::{Context, Poll, Waker};
11
12use rustdv_gpi as gpi;
13
14use crate::executor;
15use crate::time::SimDuration;
16
17/// Shared fire/waker cell — the `TriggerWaker` role (design-doc §4.3).
18pub(crate) struct TrigShared {
19    fired: Cell<bool>,
20    waker: RefCell<Option<Waker>>,
21}
22
23impl TrigShared {
24    pub(crate) fn new() -> Rc<TrigShared> {
25        Rc::new(TrigShared { fired: Cell::new(false), waker: RefCell::new(None) })
26    }
27    pub(crate) fn fire(&self) {
28        self.fired.set(true);
29        if let Some(w) = self.waker.borrow_mut().take() {
30            w.wake();
31        }
32    }
33    pub(crate) fn fired(&self) -> bool {
34        self.fired.get()
35    }
36    pub(crate) fn set_waker(&self, w: Waker) {
37        *self.waker.borrow_mut() = Some(w);
38    }
39}
40
41// ---------------------------------------------------------------------------
42// Timer
43// ---------------------------------------------------------------------------
44
45/// One-shot timed trigger (port of cocotb `Timer`, mapping row 13).
46/// Construction rejects zero durations, as cocotb's does.
47#[must_use = "triggers do nothing unless you .await them"]
48pub struct Timer {
49    steps: u64,
50    shared: Option<Rc<TrigShared>>,
51    _cb: Option<gpi::CallbackHandle>,
52}
53
54impl Timer {
55    pub fn new(d: SimDuration) -> Timer {
56        assert!(d.steps > 0, "Timer duration must be positive (cocotb rule)");
57        Timer { steps: d.steps, shared: None, _cb: None }
58    }
59    pub fn steps(steps: u64) -> Timer {
60        Self::new(SimDuration::steps(steps))
61    }
62    pub fn ns(n: u64) -> Timer {
63        Self::new(SimDuration::ns(n))
64    }
65    pub fn us(n: u64) -> Timer {
66        Self::new(SimDuration::us(n))
67    }
68    pub fn ms(n: u64) -> Timer {
69        Self::new(SimDuration::ms(n))
70    }
71}
72
73impl Future for Timer {
74    type Output = ();
75    fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<()> {
76        match &self.shared {
77            None => {
78                let sh = TrigShared::new();
79                sh.set_waker(cx.waker().clone());
80                let sh2 = sh.clone();
81                let cb = gpi::register_timer(
82                    self.steps,
83                    Box::new(move || {
84                        sh2.fire();
85                        executor::current().run_until_idle();
86                    }),
87                );
88                self.shared = Some(sh);
89                self._cb = Some(cb);
90                Poll::Pending
91            }
92            Some(sh) => {
93                if sh.fired() {
94                    Poll::Ready(())
95                } else {
96                    sh.set_waker(cx.waker().clone());
97                    Poll::Pending
98                }
99            }
100        }
101    }
102}
103
104// ---------------------------------------------------------------------------
105// Edges
106// ---------------------------------------------------------------------------
107
108#[derive(Copy, Clone, PartialEq, Eq)]
109pub(crate) enum EdgeKind {
110    Rising,
111    Falling,
112    AnyChange,
113}
114
115/// Edge trigger on a signal (ports of RisingEdge/FallingEdge/ValueChange,
116/// mapping row 14 — exposed as methods on typed handles).
117#[must_use = "triggers do nothing unless you .await them"]
118pub struct Edge {
119    sig: gpi::LogicHandle,
120    kind: EdgeKind,
121    shared: Option<Rc<TrigShared>>,
122    _cb: Option<gpi::CallbackHandle>,
123}
124
125impl Edge {
126    pub(crate) fn new(sig: gpi::LogicHandle, kind: EdgeKind) -> Edge {
127        Edge { sig, kind, shared: None, _cb: None }
128    }
129}
130
131impl Future for Edge {
132    type Output = ();
133    fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<()> {
134        match &self.shared {
135            None => {
136                let sh = TrigShared::new();
137                sh.set_waker(cx.waker().clone());
138                let sh2 = sh.clone();
139                let sig = self.sig;
140                let kind = self.kind;
141                let cb = gpi::register_value_change(
142                    sig,
143                    Box::new(move || {
144                        if sh2.fired() {
145                            return; // already matched; awaiting removal
146                        }
147                        let hit = match kind {
148                            EdgeKind::AnyChange => true,
149                            EdgeKind::Rising => sig.get_binstr() == "1",
150                            EdgeKind::Falling => sig.get_binstr() == "0",
151                        };
152                        if hit {
153                            sh2.fire();
154                            executor::current().run_until_idle();
155                        }
156                    }),
157                );
158                self.shared = Some(sh);
159                self._cb = Some(cb);
160                Poll::Pending
161            }
162            Some(sh) => {
163                if sh.fired() {
164                    Poll::Ready(())
165                } else {
166                    sh.set_waker(cx.waker().clone());
167                    Poll::Pending
168                }
169            }
170        }
171    }
172}
173
174// ---------------------------------------------------------------------------
175// NullTrigger
176// ---------------------------------------------------------------------------
177
178/// Yield once to the scheduler. Kept for parity but documented as a smell —
179/// prefer `Event` (cocotb NullTrigger docstring; book: Coroutines chapter).
180#[must_use = "triggers do nothing unless you .await them"]
181pub struct NullTrigger {
182    yielded: bool,
183}
184
185impl NullTrigger {
186    #[allow(clippy::new_without_default)]
187    pub fn new() -> NullTrigger {
188        NullTrigger { yielded: false }
189    }
190}
191
192impl Future for NullTrigger {
193    type Output = ();
194    fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<()> {
195        if self.yielded {
196            Poll::Ready(())
197        } else {
198            self.yielded = true;
199            cx.waker().wake_by_ref();
200            Poll::Pending
201        }
202    }
203}