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