Skip to main content

rustdv_methodology/
fifo.rs

1//! `TlmFifo<T>`: the UVM `uvm_tlm_fifo` — a *component* that **encapsulates**
2//! a queue (pyuvm's Queue/Mailbox) so two components connect to the same FIFO
3//! and neither learns the other exists (D17/D23/D24; pyuvm `uvm_tlm_fifo`,
4//! default depth 1).
5//!
6//! This encapsulation is the FIFO's *point*, not a nice-to-have: it is what
7//! makes the connection late-bound. Hierarchy visibility is a consequence;
8//! decoupling is the reason. Do not treat the FIFO as optional plumbing.
9//!
10//! # Ports and exports
11//!
12//! A component that needs a FIFO it does not own declares a **port**
13//! ([`crate::port::PutPort`], [`crate::port::GetPort`], [`crate::port::PeekPort`]). The component that *owns* the
14//! FIFO hands out **exports** and connects them:
15//!
16//! ```ignore
17//! self.fifo.put_export().connect(&self.producer, Producer::PUT_PORT);
18//! self.fifo.get_export().connect(&self.consumer, Consumer::GET_PORT);
19//! ```
20//!
21//! The export always initiates, as in the UVM. See [`crate::port`] for why the
22//! first argument works for an erased child and for `self` alike.
23
24use std::cell::RefCell;
25use std::future::Future;
26use std::pin::Pin;
27use std::rc::Rc;
28
29use rustdv_sim::queue::Queue;
30
31use crate::component::{Component, ComponentNode};
32use crate::port::{bind_or_panic, sink_of, GetIf, PeekIf, PortName, PortOwner, PutIf, SinkHandle};
33
34// ===========================================================================
35// The shared inside
36// ===========================================================================
37
38/// What every handle to one FIFO points at. The interfaces are implemented
39/// here, not on [`TlmFifo`], so an export can hold an `Rc` to the queue that
40/// outlives any particular handle.
41struct FifoInner<T: 'static> {
42    q: Queue<T>,
43    size: Option<usize>,
44    /// The built-in analysis taps (D23), the port of `uvm_tlm_fifo`'s
45    /// `put_ap`/`get_ap`. Observation running alongside the data path: every
46    /// tap sees every item, none of them consume anything, and nobody waits.
47    put_taps: RefCell<Vec<Rc<dyn SinkHandle<T>>>>,
48    get_taps: RefCell<Vec<Rc<dyn SinkHandle<T>>>>,
49}
50
51impl<T: 'static> FifoInner<T> {
52    fn new(size: Option<usize>) -> FifoInner<T> {
53        FifoInner {
54            q: match size {
55                Some(n) => Queue::new(Some(n)),
56                None => Queue::unbounded(),
57            },
58            size,
59            put_taps: RefCell::new(Vec::new()),
60            get_taps: RefCell::new(Vec::new()),
61        }
62    }
63
64    fn is_full(&self) -> bool {
65        match self.size {
66            None => false,
67            Some(s) => self.q.len() >= s,
68        }
69    }
70
71    /// Fire one set of taps. The list is copied out first so a subscriber's
72    /// handler cannot deadlock against the borrow.
73    fn tap(taps: &RefCell<Vec<Rc<dyn SinkHandle<T>>>>, item: &T) {
74        let subs: Vec<Rc<dyn SinkHandle<T>>> = taps.borrow().clone();
75        for sub in subs {
76            sub.deliver(item);
77        }
78    }
79
80    /// Put, tapping the item at the instant it is accepted.
81    ///
82    /// The wait and the handover are separate steps because `try_put` takes
83    /// ownership: once the item is in the queue there is nothing left to show
84    /// the taps. Nothing awaits between the two lines, so no other task can
85    /// take the space in between.
86    async fn put_tapped(&self, item: T) {
87        self.q.wait_for_space().await;
88        Self::tap(&self.put_taps, &item);
89        let _ = self.q.try_put(item);
90    }
91
92    fn try_put_tapped(&self, item: T) -> Result<(), T> {
93        if !self.q.has_space() {
94            return Err(item);
95        }
96        Self::tap(&self.put_taps, &item);
97        self.q.try_put(item)
98    }
99
100    fn tap_get(&self, item: Option<T>) -> Option<T> {
101        if let Some(v) = &item {
102            Self::tap(&self.get_taps, v);
103        }
104        item
105    }
106}
107
108impl<T: 'static> PutIf<T> for FifoInner<T> {
109    fn put(&self, item: T) -> Pin<Box<dyn Future<Output = ()> + '_>> {
110        Box::pin(self.put_tapped(item))
111    }
112    fn try_put(&self, item: T) -> Result<(), T> {
113        self.try_put_tapped(item)
114    }
115    fn can_put(&self) -> bool {
116        !self.is_full()
117    }
118}
119
120impl<T: 'static> GetIf<T> for FifoInner<T> {
121    fn get(&self) -> Pin<Box<dyn Future<Output = T> + '_>> {
122        Box::pin(async move {
123            let item = self.q.get().await;
124            Self::tap(&self.get_taps, &item);
125            item
126        })
127    }
128    fn try_get(&self) -> Option<T> {
129        let item = self.q.try_get();
130        self.tap_get(item)
131    }
132    fn can_get(&self) -> bool {
133        !self.q.is_empty()
134    }
135}
136
137impl<T: Clone + 'static> PeekIf<T> for FifoInner<T> {
138    fn peek(&self) -> Pin<Box<dyn Future<Output = T> + '_>> {
139        Box::pin(self.q.peek())
140    }
141    fn try_peek(&self) -> Option<T> {
142        self.q.try_peek()
143    }
144    fn can_peek(&self) -> bool {
145        !self.q.is_empty()
146    }
147}
148
149// ===========================================================================
150// The exports
151// ===========================================================================
152
153/// The FIFO's put side, handed to a component that needs to put.
154///
155/// An export is a *value you connect*, not a value you keep: the usual life of
156/// one is a single line in the parent's connect phase.
157pub struct PutExport<T: 'static> {
158    iface: Rc<dyn PutIf<T>>,
159}
160
161impl<T: 'static> PutExport<T> {
162    /// Connect this export to `owner`'s port called `name`.
163    ///
164    /// `owner` is a child slot (`&self.producer`) or the connecting component
165    /// itself (`self`) — both are `PortOwner`. `name` is the derive-generated
166    /// constant, so it cannot be misspelled and cannot name a `get` port.
167    pub fn connect(&self, owner: &dyn PortOwner, name: PortName<dyn PutIf<T>>) {
168        bind_or_panic(owner, name, self.iface.clone());
169    }
170}
171
172/// The FIFO's get side.
173pub struct GetExport<T: 'static> {
174    iface: Rc<dyn GetIf<T>>,
175}
176
177impl<T: 'static> GetExport<T> {
178    pub fn connect(&self, owner: &dyn PortOwner, name: PortName<dyn GetIf<T>>) {
179        bind_or_panic(owner, name, self.iface.clone());
180    }
181}
182
183/// One of a FIFO's analysis taps (D23): connect a subscriber to watch the
184/// traffic without joining the data path.
185pub struct TapExport<T: 'static> {
186    taps: Rc<FifoInner<T>>,
187    on_put: bool,
188}
189
190impl<T: 'static> TapExport<T> {
191    /// Add a subscriber to this tap. Same shape as every other connection —
192    /// the export, the owner, the port name.
193    pub fn connect(&self, owner: &dyn PortOwner, name: PortName<dyn SinkHandle<T>>) {
194        match sink_of(owner, name) {
195            Ok(sink) => {
196                let list = if self.on_put { &self.taps.put_taps } else { &self.taps.get_taps };
197                list.borrow_mut().push(sink);
198            }
199            Err(e) => panic!("{e}"),
200        }
201    }
202}
203
204/// The FIFO's peek side.
205pub struct PeekExport<T: 'static> {
206    iface: Rc<dyn PeekIf<T>>,
207}
208
209impl<T: 'static> PeekExport<T> {
210    pub fn connect(&self, owner: &dyn PortOwner, name: PortName<dyn PeekIf<T>>) {
211        bind_or_panic(owner, name, self.iface.clone());
212    }
213}
214
215// ===========================================================================
216// TlmFifo
217// ===========================================================================
218
219/// A bounded (or unbounded) FIFO that is also a component in the hierarchy.
220///
221/// Declare it as a child with `#[component]` so it appears in the tree,
222/// then hand out its exports in `connect`.
223pub struct TlmFifo<T: 'static> {
224    inner: Rc<FifoInner<T>>,
225}
226
227impl<T: 'static> Default for TlmFifo<T> {
228    /// Depth 1, the UVM default — and the depth that makes a producer and a
229    /// consumer take turns, which is what most testbenches want.
230    fn default() -> Self {
231        TlmFifo::new(1)
232    }
233}
234
235impl<T: 'static> TlmFifo<T> {
236    /// A FIFO `size` items deep. Depth 1 is the UVM default and forces the
237    /// producer to wait for the consumer.
238    pub fn new(size: usize) -> TlmFifo<T> {
239        TlmFifo { inner: Rc::new(FifoInner::new(Some(size))) }
240    }
241
242    /// A FIFO with no depth limit: a put never blocks.
243    pub fn unbounded() -> TlmFifo<T> {
244        TlmFifo { inner: Rc::new(FifoInner::new(None)) }
245    }
246
247    /// The declared depth; `None` for an unbounded FIFO.
248    pub fn size(&self) -> Option<usize> {
249        self.inner.size
250    }
251    /// How many items are in it now.
252    pub fn used(&self) -> usize {
253        self.inner.q.len()
254    }
255    pub fn is_empty(&self) -> bool {
256        self.inner.q.is_empty()
257    }
258    pub fn is_full(&self) -> bool {
259        self.inner.is_full()
260    }
261    /// Throw away everything in the FIFO (UVM `flush`).
262    pub fn flush(&self) {
263        while self.inner.q.try_get().is_some() {}
264    }
265
266    // --- the exports ------------------------------------------------------
267
268    /// The put side, to connect to a component's [`PutPort`](crate::PutPort).
269    pub fn put_export(&self) -> PutExport<T> {
270        PutExport { iface: self.inner.clone() }
271    }
272
273    /// The get side, to connect to a component's [`GetPort`](crate::GetPort).
274    pub fn get_export(&self) -> GetExport<T> {
275        GetExport { iface: self.inner.clone() }
276    }
277
278    /// The tap that fires as each item goes **in** (`uvm_tlm_fifo::put_ap`).
279    pub fn put_ap(&self) -> TapExport<T> {
280        TapExport { taps: self.inner.clone(), on_put: true }
281    }
282
283    /// The tap that fires as each item comes **out** (`uvm_tlm_fifo::get_ap`).
284    pub fn get_ap(&self) -> TapExport<T> {
285        TapExport { taps: self.inner.clone(), on_put: false }
286    }
287
288    // --- direct use, for the component that owns the FIFO -----------------
289    //
290    // The owner does not need a port: it holds the FIFO. These are the same
291    // operations the exports offer, called without the indirection.
292
293    pub async fn put(&self, item: T) {
294        self.inner.put_tapped(item).await
295    }
296    pub fn try_put(&self, item: T) -> Result<(), T> {
297        self.inner.try_put_tapped(item)
298    }
299    pub async fn get(&self) -> T {
300        let item = self.inner.q.get().await;
301        FifoInner::tap(&self.inner.get_taps, &item);
302        item
303    }
304    pub fn try_get(&self) -> Option<T> {
305        let item = self.inner.q.try_get();
306        self.inner.tap_get(item)
307    }
308
309    /// The put interface itself, for tests that exercise `bind` directly
310    /// rather than through an export.
311    #[cfg(test)]
312    pub(crate) fn put_iface_for_test(&self) -> Rc<dyn PutIf<T>> {
313        self.inner.clone()
314    }
315
316    /// A second handle to the *same* FIFO (for wiring at construction).
317    pub fn handle(&self) -> TlmFifo<T> {
318        TlmFifo { inner: self.inner.clone() }
319    }
320}
321
322impl<T: Clone + 'static> TlmFifo<T> {
323    /// The peek side, to connect to a [`PeekPort`](crate::PeekPort). Peek
324    /// copies rather than removes, which is why it needs `T: Clone`.
325    pub fn peek_export(&self) -> PeekExport<T> {
326        PeekExport { iface: self.inner.clone() }
327    }
328
329    pub async fn peek(&self) -> T {
330        self.inner.q.peek().await
331    }
332    pub fn try_peek(&self) -> Option<T> {
333        self.inner.q.try_peek()
334    }
335}
336
337// A FIFO is a component: it appears in the hierarchy, and its phases are
338// no-ops (it has no children and nothing to run).
339impl<T: 'static> Component for TlmFifo<T> {}
340
341impl<T: 'static> ComponentNode for TlmFifo<T> {
342    fn node_name(&self) -> &'static str {
343        "TlmFifo"
344    }
345    fn children_mut(&mut self) -> Vec<(String, &mut (dyn ComponentNode + 'static))> {
346        Vec::new()
347    }
348}
349
350// ===========================================================================
351// Tests — no simulator.
352// ===========================================================================
353
354#[cfg(test)]
355mod tests {
356    use super::*;
357    use crate::port::{
358        GetPort, PeekPort, PortField, PortName, PortOwner, PutPort, SubscribePort, Subscriber,
359    };
360    use crate::shared::RustdvShared;
361    use rustdv_sim::testing::block_on;
362    use std::any::Any;
363
364    /// A hand-written `PortOwner`: the derive is convenience, not requirement
365    /// (OQ-15), and a test should not need it.
366    struct Holder {
367        put: PutPort<u8>,
368        get: GetPort<u8>,
369        peek: PeekPort<u8>,
370        sub: SubscribePort<u8>,
371    }
372
373    impl Holder {
374        fn new() -> Holder {
375            Holder {
376                put: PutPort::default(),
377                get: GetPort::default(),
378                peek: PeekPort::default(),
379                sub: SubscribePort::default(),
380            }
381        }
382        const PUT: PortName<dyn PutIf<u8>> = PortName::new("put");
383        const GET: PortName<dyn GetIf<u8>> = PortName::new("get");
384        const PEEK: PortName<dyn PeekIf<u8>> = PortName::new("peek");
385        const SUB: PortName<dyn SinkHandle<u8>> = PortName::new("sub");
386    }
387
388    impl PortOwner for Holder {
389        fn owner_port_slot(&self, name: &str) -> Option<Rc<dyn Any>> {
390            match name {
391                "put" => Some(self.put.slot_any()),
392                "get" => Some(self.get.slot_any()),
393                "peek" => Some(self.peek.slot_any()),
394                "sub" => Some(self.sub.slot_any()),
395                _ => None,
396            }
397        }
398        fn owner_label(&self) -> &'static str {
399            "Holder"
400        }
401    }
402
403    #[test]
404    fn a_port_is_unbound_until_connected() {
405        let h = Holder::new();
406        assert!(!h.put.bound());
407        let fifo: TlmFifo<u8> = TlmFifo::new(1);
408        fifo.put_export().connect(&h, Holder::PUT);
409        assert!(h.put.bound());
410    }
411
412    #[test]
413    fn put_and_get_through_a_fifo() {
414        block_on(async {
415            let h = Holder::new();
416            let fifo: TlmFifo<u8> = TlmFifo::new(2);
417            fifo.put_export().connect(&h, Holder::PUT);
418            fifo.get_export().connect(&h, Holder::GET);
419
420            h.put.put(1).await;
421            h.put.put(2).await;
422            assert_eq!(h.get.get().await, 1, "FIFO order through the ports");
423            assert_eq!(h.get.get().await, 2);
424        });
425    }
426
427    #[test]
428    fn peek_leaves_the_item_for_get() {
429        block_on(async {
430            let h = Holder::new();
431            let fifo: TlmFifo<u8> = TlmFifo::new(1);
432            fifo.put_export().connect(&h, Holder::PUT);
433            fifo.peek_export().connect(&h, Holder::PEEK);
434            fifo.get_export().connect(&h, Holder::GET);
435
436            h.put.put(9).await;
437            assert_eq!(h.peek.peek().await, 9);
438            assert_eq!(h.get.get().await, 9, "peek did not consume it");
439        });
440    }
441
442    /// D89 through the port, not just the queue.
443    #[test]
444    fn try_put_hands_a_refused_item_back() {
445        block_on(async {
446            let h = Holder::new();
447            let fifo: TlmFifo<u8> = TlmFifo::new(1);
448            fifo.put_export().connect(&h, Holder::PUT);
449            assert!(h.put.try_put(1).is_ok());
450            assert_eq!(h.put.try_put(2), Err(2), "the item comes home");
451        });
452    }
453
454    #[test]
455    fn can_put_and_can_get_track_the_fifo() {
456        block_on(async {
457            let h = Holder::new();
458            let fifo: TlmFifo<u8> = TlmFifo::new(1);
459            fifo.put_export().connect(&h, Holder::PUT);
460            fifo.get_export().connect(&h, Holder::GET);
461            assert!(h.put.can_put());
462            assert!(!h.get.can_get());
463            h.put.put(1).await;
464            assert!(!h.put.can_put());
465            assert!(h.get.can_get());
466        });
467    }
468
469    #[test]
470    fn a_bad_port_name_is_a_named_error() {
471        let h = Holder::new();
472        let nope: PortName<dyn PutIf<u8>> = PortName::new("no_such_port");
473        let fifo: TlmFifo<u8> = TlmFifo::new(1);
474        match crate::port::bind(&h, nope, fifo.put_iface_for_test()) {
475            Err(crate::port::ConnectError::NoSuchPort { owner, name }) => {
476                assert_eq!(owner, "Holder");
477                assert_eq!(name, "no_such_port");
478            }
479            other => panic!("expected NoSuchPort, got {other:?}"),
480        }
481    }
482
483    #[test]
484    fn fifo_size_used_and_flush() {
485        block_on(async {
486            let fifo: TlmFifo<u8> = TlmFifo::new(3);
487            assert_eq!(fifo.size(), Some(3));
488            assert!(fifo.is_empty());
489            fifo.put(1).await;
490            fifo.put(2).await;
491            assert_eq!(fifo.used(), 2);
492            fifo.flush();
493            assert!(fifo.is_empty(), "flush empties it");
494        });
495    }
496
497    #[test]
498    fn unbounded_is_never_full() {
499        block_on(async {
500            let fifo: TlmFifo<u8> = TlmFifo::unbounded();
501            assert_eq!(fifo.size(), None);
502            for n in 0..100 {
503                assert!(fifo.try_put(n).is_ok());
504            }
505            assert!(!fifo.is_full());
506        });
507    }
508
509    /// D23: the taps observe without joining the data path — every item seen,
510    /// none consumed, nobody delayed.
511    #[test]
512    fn the_put_tap_sees_every_item_and_consumes_none() {
513        #[derive(Default)]
514        struct Log {
515            seen: Vec<u8>,
516        }
517        impl Subscriber<u8> for Log {
518            fn write(&mut self, item: &u8) {
519                self.seen.push(*item);
520            }
521        }
522
523        block_on(async {
524            let h = Holder::new();
525            let log: RustdvShared<Log> = RustdvShared::default();
526            h.sub.subscribe(log.clone());
527
528            let fifo: TlmFifo<u8> = TlmFifo::unbounded();
529            fifo.put_ap().connect(&h, Holder::SUB);
530
531            for n in 1..=3u8 {
532                fifo.put(n).await;
533            }
534            assert_eq!(log.get().seen, vec![1, 2, 3], "the tap saw all three");
535            assert_eq!(fifo.used(), 3, "and took none of them");
536        });
537    }
538
539    #[test]
540    fn the_get_tap_fires_as_items_leave() {
541        #[derive(Default)]
542        struct Log {
543            seen: Vec<u8>,
544        }
545        impl Subscriber<u8> for Log {
546            fn write(&mut self, item: &u8) {
547                self.seen.push(*item);
548            }
549        }
550
551        block_on(async {
552            let h = Holder::new();
553            let log: RustdvShared<Log> = RustdvShared::default();
554            h.sub.subscribe(log.clone());
555
556            let fifo: TlmFifo<u8> = TlmFifo::unbounded();
557            fifo.get_ap().connect(&h, Holder::SUB);
558            fifo.put(7).await;
559            assert!(log.get().seen.is_empty(), "nothing has left yet");
560            let _ = fifo.get().await;
561            assert_eq!(log.get().seen, vec![7]);
562        });
563    }
564
565    #[test]
566    fn a_handle_is_the_same_fifo() {
567        block_on(async {
568            let fifo: TlmFifo<u8> = TlmFifo::unbounded();
569            let other = fifo.handle();
570            fifo.put(1).await;
571            assert_eq!(other.used(), 1, "two handles, one FIFO");
572        });
573    }
574
575}