Skip to main content

rustdv_sim/
queue.rs

1//! Sim-aware FIFO queue (port of `cocotb.queue.Queue`, mapping row 18 —
2//! the foundation for TLM FIFOs, as in pyuvm).
3
4use std::cell::RefCell;
5use std::collections::VecDeque;
6use std::future::Future;
7use std::pin::Pin;
8use std::rc::Rc;
9use std::task::{Context, Poll, Waker};
10
11struct QInner<T> {
12    buf: RefCell<VecDeque<T>>,
13    cap: Option<usize>, // None = unbounded
14    get_waiters: RefCell<Vec<Waker>>,
15    put_waiters: RefCell<Vec<Waker>>,
16}
17
18impl<T> QInner<T> {
19    fn has_space(&self) -> bool {
20        match self.cap {
21            None => true,
22            Some(c) => self.buf.borrow().len() < c,
23        }
24    }
25    fn wake_getters(&self) {
26        for w in self.get_waiters.borrow_mut().drain(..) {
27            w.wake();
28        }
29    }
30    fn wake_putters(&self) {
31        for w in self.put_waiters.borrow_mut().drain(..) {
32            w.wake();
33        }
34    }
35}
36
37/// FIFO queue with optional bound; cloning shares the queue.
38pub struct Queue<T> {
39    inner: Rc<QInner<T>>,
40}
41
42impl<T> Clone for Queue<T> {
43    fn clone(&self) -> Self {
44        Queue {
45            inner: self.inner.clone(),
46        }
47    }
48}
49
50impl<T> Queue<T> {
51    /// `cap = None` → unbounded; `Some(n)` → bounded at n.
52    pub fn new(cap: Option<usize>) -> Queue<T> {
53        Queue {
54            inner: Rc::new(QInner {
55                buf: RefCell::new(VecDeque::new()),
56                cap,
57                get_waiters: RefCell::new(Vec::new()),
58                put_waiters: RefCell::new(Vec::new()),
59            }),
60        }
61    }
62
63    pub fn unbounded() -> Queue<T> {
64        Self::new(None)
65    }
66
67    pub fn len(&self) -> usize {
68        self.inner.buf.borrow().len()
69    }
70    pub fn is_empty(&self) -> bool {
71        self.inner.buf.borrow().is_empty()
72    }
73
74    /// Is there room right now? (Zero time; the answer can go stale as soon
75    /// as another task runs.)
76    pub fn has_space(&self) -> bool {
77        self.inner.has_space()
78    }
79
80    pub fn try_put(&self, item: T) -> Result<(), T> {
81        if self.inner.has_space() {
82            self.inner.buf.borrow_mut().push_back(item);
83            self.inner.wake_getters();
84            Ok(())
85        } else {
86            Err(item)
87        }
88    }
89
90    pub fn try_get(&self) -> Option<T> {
91        let item = self.inner.buf.borrow_mut().pop_front();
92        if item.is_some() {
93            self.inner.wake_putters();
94        }
95        item
96    }
97
98    pub fn put(&self, item: T) -> Put<T> {
99        Put {
100            inner: self.inner.clone(),
101            item: Some(item),
102        }
103    }
104
105    pub fn get(&self) -> Get<T> {
106        Get {
107            inner: self.inner.clone(),
108        }
109    }
110
111    /// Wait until the queue has room, **without** handing over an item.
112    ///
113    /// For callers that must do something with the item at the instant it is
114    /// accepted — a TLM FIFO's analysis tap, which broadcasts each item as it
115    /// goes in. `put` takes ownership, so by the time it returns there is
116    /// nothing left to show anyone; this splits the wait from the handover:
117    ///
118    /// ```ignore
119    /// q.wait_for_space().await;
120    /// tap(&item);            // no await between these two lines, so no
121    /// let _ = q.try_put(item); // other task can take the space first
122    /// ```
123    pub fn wait_for_space(&self) -> Space<T> {
124        Space {
125            inner: self.inner.clone(),
126        }
127    }
128}
129
130/// Resolves when the queue has room. See [`Queue::wait_for_space`].
131pub struct Space<T> {
132    inner: Rc<QInner<T>>,
133}
134
135impl<T> Future for Space<T> {
136    type Output = ();
137    fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<()> {
138        if self.inner.has_space() {
139            Poll::Ready(())
140        } else {
141            self.inner.put_waiters.borrow_mut().push(cx.waker().clone());
142            Poll::Pending
143        }
144    }
145}
146
147impl<T: Clone> Queue<T> {
148    /// The front item **without removing it** (TLM `peek`).
149    ///
150    /// A copy, so the item stays in the queue for whoever gets it next. That
151    /// is why `peek` needs `T: Clone` and `get` does not: `get` hands over
152    /// ownership, `peek` cannot.
153    pub fn try_peek(&self) -> Option<T> {
154        self.inner.buf.borrow().front().cloned()
155    }
156
157    /// Block until there is something to peek at, then copy it.
158    pub fn peek(&self) -> Peek<T> {
159        Peek {
160            inner: self.inner.clone(),
161        }
162    }
163}
164
165pub struct Peek<T> {
166    inner: Rc<QInner<T>>,
167}
168
169impl<T: Clone> Future for Peek<T> {
170    type Output = T;
171    fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<T> {
172        let item = self.inner.buf.borrow().front().cloned();
173        match item {
174            // No `wake_putters`: nothing left the queue, so no space opened up.
175            Some(v) => Poll::Ready(v),
176            None => {
177                self.inner.get_waiters.borrow_mut().push(cx.waker().clone());
178                Poll::Pending
179            }
180        }
181    }
182}
183
184pub struct Put<T> {
185    inner: Rc<QInner<T>>,
186    item: Option<T>,
187}
188
189impl<T> Future for Put<T> {
190    type Output = ();
191    fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<()> {
192        // T may be !Unpin, but we never project into it — safe to get_mut
193        // via unsafe-free approach: Put is structurally Unpin because we
194        // only move `item` out of an Option.
195        let this = unsafe { self.get_unchecked_mut() };
196        if this.inner.has_space() {
197            let item = this.item.take().expect("Put polled after completion");
198            this.inner.buf.borrow_mut().push_back(item);
199            this.inner.wake_getters();
200            Poll::Ready(())
201        } else {
202            this.inner.put_waiters.borrow_mut().push(cx.waker().clone());
203            Poll::Pending
204        }
205    }
206}
207
208pub struct Get<T> {
209    inner: Rc<QInner<T>>,
210}
211
212impl<T> Future for Get<T> {
213    type Output = T;
214    fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<T> {
215        let item = self.inner.buf.borrow_mut().pop_front();
216        match item {
217            Some(v) => {
218                self.inner.wake_putters();
219                Poll::Ready(v)
220            }
221            None => {
222                self.inner.get_waiters.borrow_mut().push(cx.waker().clone());
223                Poll::Pending
224            }
225        }
226    }
227}
228
229// ===========================================================================
230// Tests — no simulator: a Queue is pure async, with no `gpi::` anywhere.
231// ===========================================================================
232
233#[cfg(test)]
234mod tests {
235    use super::*;
236    use crate::testing::{assert_pending, block_on};
237
238    #[test]
239    fn put_get_round_trips_in_order() {
240        block_on(async {
241            let q: Queue<u8> = Queue::unbounded();
242            for n in 0..5 {
243                q.put(n).await;
244            }
245            for n in 0..5 {
246                assert_eq!(q.get().await, n, "a queue is FIFO");
247            }
248        });
249    }
250
251    /// D89: a refused `try_put` hands the item **back**. A bare "no" would
252    /// swallow a transaction that was never delivered.
253    #[test]
254    fn try_put_on_a_full_queue_returns_the_item() {
255        block_on(async {
256            let q: Queue<String> = Queue::new(Some(1));
257            assert!(q.try_put(String::from("first")).is_ok());
258            match q.try_put(String::from("second")) {
259                Err(back) => assert_eq!(back, "second", "the item comes home"),
260                Ok(()) => panic!("a depth-1 queue accepted a second item"),
261            }
262        });
263    }
264
265    #[test]
266    fn try_get_on_an_empty_queue_is_none() {
267        block_on(async {
268            let q: Queue<u8> = Queue::unbounded();
269            assert!(q.try_get().is_none());
270            q.put(1).await;
271            assert_eq!(q.try_get(), Some(1));
272            assert!(q.try_get().is_none(), "and it was consumed");
273        });
274    }
275
276    #[test]
277    fn a_blocked_get_wakes_on_a_put() {
278        block_on(async {
279            let q: Queue<u8> = Queue::unbounded();
280            let producer = q.clone();
281            crate::executor::spawn(async move {
282                producer.put(42).await;
283            });
284            assert_eq!(q.get().await, 42, "the get was waiting before the put");
285        });
286    }
287
288    #[test]
289    fn a_blocked_put_wakes_when_space_appears() {
290        block_on(async {
291            let q: Queue<u8> = Queue::new(Some(1));
292            q.put(1).await;
293            let consumer = q.clone();
294            crate::executor::spawn(async move {
295                let _ = consumer.get().await;
296            });
297            q.put(2).await; // blocks until the spawned get drains the first
298            assert_eq!(q.len(), 1);
299        });
300    }
301
302    #[test]
303    fn a_get_on_an_empty_queue_waits() {
304        let q: Queue<u8> = Queue::unbounded();
305        assert_pending(async move { q.get().await });
306    }
307
308    /// Peek copies and leaves the item, which is why it needs `T: Clone`
309    /// where `get` does not.
310    #[test]
311    fn peek_does_not_consume() {
312        block_on(async {
313            let q: Queue<u8> = Queue::unbounded();
314            q.put(9).await;
315            assert_eq!(q.peek().await, 9);
316            assert_eq!(q.len(), 1, "peek left it there");
317            assert_eq!(q.get().await, 9, "and get takes the same item");
318        });
319    }
320
321    #[test]
322    fn try_peek_is_none_when_empty() {
323        block_on(async {
324            let q: Queue<u8> = Queue::unbounded();
325            assert!(q.try_peek().is_none());
326            q.put(3).await;
327            assert_eq!(q.try_peek(), Some(3));
328            assert_eq!(q.len(), 1);
329        });
330    }
331
332    #[test]
333    fn has_space_agrees_with_len() {
334        block_on(async {
335            let q: Queue<u8> = Queue::new(Some(2));
336            assert!(q.has_space());
337            q.put(1).await;
338            assert!(q.has_space());
339            q.put(2).await;
340            assert!(!q.has_space(), "full at its declared depth");
341            assert_eq!(q.len(), 2);
342        });
343    }
344
345    #[test]
346    fn wait_for_space_returns_when_drained() {
347        block_on(async {
348            let q: Queue<u8> = Queue::new(Some(1));
349            q.put(1).await;
350            let consumer = q.clone();
351            crate::executor::spawn(async move {
352                let _ = consumer.get().await;
353            });
354            q.wait_for_space().await;
355            assert!(q.has_space());
356        });
357    }
358
359    #[test]
360    fn unbounded_never_blocks_a_put() {
361        block_on(async {
362            let q: Queue<u32> = Queue::unbounded();
363            for n in 0..1000 {
364                assert!(q.try_put(n).is_ok(), "an unbounded queue always has room");
365            }
366            assert_eq!(q.len(), 1000);
367        });
368    }
369
370    #[test]
371    fn is_empty_tracks_contents() {
372        block_on(async {
373            let q: Queue<u8> = Queue::unbounded();
374            assert!(q.is_empty());
375            q.put(1).await;
376            assert!(!q.is_empty());
377            let _ = q.get().await;
378            assert!(q.is_empty());
379        });
380    }
381
382    /// A dropped `get` future must not have consumed an item — otherwise
383    /// losing a race (D82c) would silently eat a transaction.
384    #[test]
385    fn a_dropped_get_consumes_nothing() {
386        block_on(async {
387            let q: Queue<u8> = Queue::unbounded();
388            {
389                let pending = q.get();
390                drop(pending);
391            }
392            q.put(5).await;
393            assert_eq!(q.get().await, 5, "the item survived the dropped get");
394        });
395    }
396}