Skip to main content

rustdv_methodology/
port.rs

1//! TLM ports, exports, and how they connect (D83–D85).
2//!
3//! # The problem, and why the obvious answers fail
4//!
5//! A parent wires its children together in `connect`. In SystemVerilog that
6//! is `producer.put_port.connect(fifo.put_export)` — the parent reaches into
7//! the child by name. rustdv's children are erased (`RustdvComp` holds a
8//! `Box<dyn ComponentNode>`), so `self.producer.put_port` does not compile,
9//! and Rust has no `$cast`-to-base to recover the concrete type: a `dyn`
10//! pointer carries one vtable, not a class hierarchy, and `Any::downcast`
11//! needs the exact type the parent is trying not to know.
12//!
13//! An earlier design answered this with a thread-local registry keyed by
14//! hierarchical path. It worked for children and then fell over on the case
15//! the UVM handles without blinking: a component connecting **its own** port.
16//! A path-keyed registry can only be addressed by something that knows its
17//! path, and a component does not know its own (D7).
18//!
19//! # The answer: a trait method is the cast
20//!
21//! `uvm_test` *is* a `uvm_component`, and that uniformity is the whole point
22//! (D3): every participant is reachable the same way. rustdv gets the same
23//! uniformity from the trait it already has. [`ComponentNode`] gains
24//!
25//! ```ignore
26//! fn port_slot(&self, name: &str) -> Option<Rc<dyn Any>>;
27//! ```
28//!
29//! generated by `#[derive(Component)]` from the `#[port(..)]` fields. It is
30//! available *through* `dyn ComponentNode`, so an erased child answers it just
31//! as a concrete `self` does. No downcast to the child's type, no path, no
32//! registry, no walk-time stamping:
33//!
34//! ```ignore
35//! fn connect(&mut self, _ctx: &mut RustdvCtx) {
36//!     self.fifo.put_export().connect(&self.producer, Producer::PUT_PORT);
37//!     self.fifo.get_export().connect(&self.consumer, Consumer::GET_PORT);
38//!     self.fifo.put_export().connect(self, MathTest::X_OUT);  // my own port
39//! }
40//! ```
41//!
42//! The export always initiates the connection, as in the UVM. The first
43//! argument is whoever owns the port — a child slot, or `self`. The second is
44//! a derive-generated [`PortName`], so a misspelling is a compile error and
45//! aiming a `put` export at a `get` port is a compile error too: the name
46//! carries the interface, not just a string.
47//!
48//! [`ComponentNode`]: crate::ComponentNode
49
50use std::any::Any;
51use std::cell::RefCell;
52use std::fmt;
53use std::future::Future;
54use std::marker::PhantomData;
55use std::pin::Pin;
56use std::rc::Rc;
57
58// ===========================================================================
59// The three TLM interfaces (TLM 1.0: put, get, peek)
60// ===========================================================================
61
62/// What a `put` export offers. Implemented by the FIFO's put side; a
63/// [`PutPort`] holds one of these once connected.
64///
65/// `put` returns a boxed future rather than being an `async fn` because it is
66/// called through `dyn`: a trait object cannot have an `async fn` whose future
67/// type varies by implementor.
68pub trait PutIf<T: 'static>: 'static {
69    /// Block until the FIFO has room, then hand the item over.
70    fn put(&self, item: T) -> Pin<Box<dyn Future<Output = ()> + '_>>;
71    /// Take the item only if it fits right now; hand it back if it does not.
72    fn try_put(&self, item: T) -> Result<(), T>;
73    /// Is there room this instant? (Zero time; the answer can go stale.)
74    fn can_put(&self) -> bool;
75}
76
77/// What a `get` export offers: take the item *out*.
78pub trait GetIf<T: 'static>: 'static {
79    fn get(&self) -> Pin<Box<dyn Future<Output = T> + '_>>;
80    fn try_get(&self) -> Option<T>;
81    fn can_get(&self) -> bool;
82}
83
84/// What a `peek` export offers: copy the item and **leave it there**, so
85/// whoever gets it next still finds it. That is why peek needs `T: Clone`
86/// where get does not.
87pub trait PeekIf<T: 'static>: 'static {
88    fn peek(&self) -> Pin<Box<dyn Future<Output = T> + '_>>;
89    fn try_peek(&self) -> Option<T>;
90    fn can_peek(&self) -> bool;
91}
92
93// ===========================================================================
94// The analysis interfaces (D86–D88): broadcast, not a queue
95// ===========================================================================
96
97/// What a component does with an item it was handed by an analysis broadcast
98/// — the port of `uvm_subscriber`'s `write`.
99///
100/// It takes `&mut self` and returns nothing: delivery is synchronous and in
101/// zero time, so there is no `async`, no back-pressure, and nothing to await.
102/// Implement it on the **state** a subscriber keeps, not on the component (a
103/// sibling cannot be reached from a `run` phase); see
104/// [`RustdvShared`](crate::RustdvShared). The UVM makes the subscriber a
105/// component; here it is the plain struct the component hosts.
106pub trait Subscriber<T>: 'static {
107    fn write(&mut self, item: &T);
108}
109
110/// The erased handle a subscribe port hands to the hub.
111///
112/// `&mut` lives *inside* the handle rather than in the call, which is what
113/// makes zero-time delivery possible: the hub holds no `&mut` to anything, so
114/// it can call every subscriber in a plain loop.
115pub trait SinkHandle<T: 'static>: 'static {
116    fn deliver(&self, item: &T);
117}
118
119impl<T: 'static, S: Subscriber<T>> SinkHandle<T> for crate::shared::RustdvShared<S> {
120    fn deliver(&self, item: &T) {
121        self.get_mut().write(item);
122    }
123}
124
125/// What a publish export offers: hand an item to everyone, immediately.
126pub trait PublishIf<T: 'static>: 'static {
127    fn write(&self, item: &T);
128}
129
130// ===========================================================================
131// PortName — a typed, derive-generated constant
132// ===========================================================================
133
134/// The name a port is declared under, carrying the interface it demands.
135///
136/// `#[port(put)] cmd_port: PutPort<Command>` generates `Tester::CMD_PORT`, a
137/// `PortName<dyn PutIf<Command>>`. The constant and the lookup key both come
138/// from the one field, so they cannot drift; a misspelled constant does not
139/// compile; and connecting a `get` export to it does not compile either,
140/// because the interface is part of the type.
141pub struct PortName<I: ?Sized> {
142    name: &'static str,
143    _marker: PhantomData<fn(&I)>,
144}
145
146impl<I: ?Sized> PortName<I> {
147    /// Called by the derive. Hand-writing one is allowed and harmless — it
148    /// still has to match a real field name to connect.
149    pub const fn new(name: &'static str) -> PortName<I> {
150        PortName {
151            name,
152            _marker: PhantomData,
153        }
154    }
155
156    pub const fn as_str(&self) -> &'static str {
157        self.name
158    }
159}
160
161impl<I: ?Sized> Clone for PortName<I> {
162    fn clone(&self) -> Self {
163        *self
164    }
165}
166impl<I: ?Sized> Copy for PortName<I> {}
167
168impl<I: ?Sized> fmt::Debug for PortName<I> {
169    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
170        write!(f, "PortName({})", self.name)
171    }
172}
173
174// ===========================================================================
175// Port — what a component declares
176// ===========================================================================
177
178/// A port: a component's request for an interface it does not own.
179///
180/// One generic struct serves all three kinds, since the only difference is
181/// which interface it holds — [`PutPort`], [`GetPort`] and [`PeekPort`] are
182/// aliases, not separate types.
183///
184/// The binding lives behind `Rc<RefCell<..>>` because connection happens
185/// through erasure: `connect` reaches the port as an `Rc<dyn Any>` and must
186/// write to it, so the mutability has to be interior. The component's own
187/// field and the handle `connect` obtains are two `Rc`s to the same cell.
188pub struct Port<I: ?Sized + 'static> {
189    slot: Rc<RefCell<Option<Rc<I>>>>,
190}
191
192/// A port that puts items into something it does not own.
193pub type PutPort<T> = Port<dyn PutIf<T>>;
194/// A port that takes items out of something it does not own.
195pub type GetPort<T> = Port<dyn GetIf<T>>;
196/// A port that copies items without removing them.
197pub type PeekPort<T> = Port<dyn PeekIf<T>>;
198/// A port that broadcasts items to whoever is listening — or to nobody.
199pub type PublishPort<T> = Port<dyn PublishIf<T>>;
200/// A port that receives broadcast items. It is the odd one out: instead of
201/// the export writing an interface *into* it, the component fills it with its
202/// subscriber ([`subscribe`](SubscribePort::subscribe)) and the hub reads it
203/// out at connect time. Broadcast runs the other way, so the wiring does too.
204pub type SubscribePort<T> = Port<dyn SinkHandle<T>>;
205
206impl<I: ?Sized + 'static> Clone for Port<I> {
207    /// Another handle to the *same* binding, not a second port.
208    fn clone(&self) -> Self {
209        Port {
210            slot: self.slot.clone(),
211        }
212    }
213}
214
215impl<I: ?Sized + 'static> Default for Port<I> {
216    fn default() -> Self {
217        Port {
218            slot: Rc::new(RefCell::new(None)),
219        }
220    }
221}
222
223impl<I: ?Sized + 'static> Port<I> {
224    /// A fresh, unconnected port. `Default` does the same; components get
225    /// theirs from `#[derive(Default)]`.
226    pub fn new() -> Port<I> {
227        Port::default()
228    }
229
230    /// Has this port been connected?
231    pub fn is_bound(&self) -> bool {
232        self.slot.borrow().is_some()
233    }
234
235    /// The interface behind the port, or a panic naming the failure.
236    ///
237    /// Elaboration reports every unconnected port before the run phase starts,
238    /// so this panic is the belt to that suspenders.
239    fn iface(&self) -> Rc<I> {
240        match self.slot.borrow().as_ref() {
241            Some(i) => i.clone(),
242            None => panic!(
243                "a TLM port was used before it was connected — connect it in \
244                 the parent's connect phase with \
245                 `fifo.<kind>_export().connect(owner, Owner::PORT_NAME)`"
246            ),
247        }
248    }
249}
250
251impl<T: 'static> PutPort<T> {
252    /// Block until the far end has room, then hand the item over.
253    pub async fn put(&self, item: T) {
254        let iface = self.iface();
255        iface.put(item).await
256    }
257    /// Hand the item over only if it fits right now.
258    pub fn try_put(&self, item: T) -> Result<(), T> {
259        self.iface().try_put(item)
260    }
261    pub fn can_put(&self) -> bool {
262        self.iface().can_put()
263    }
264}
265
266impl<T: 'static> GetPort<T> {
267    /// Block until there is an item, then take it out.
268    pub async fn get(&self) -> T {
269        let iface = self.iface();
270        iface.get().await
271    }
272    pub fn try_get(&self) -> Option<T> {
273        self.iface().try_get()
274    }
275    pub fn can_get(&self) -> bool {
276        self.iface().can_get()
277    }
278}
279
280impl<T: 'static> PeekPort<T> {
281    /// Block until there is an item, then copy it, leaving it in place.
282    pub async fn peek(&self) -> T {
283        let iface = self.iface();
284        iface.peek().await
285    }
286    pub fn try_peek(&self) -> Option<T> {
287        self.iface().try_peek()
288    }
289    pub fn can_peek(&self) -> bool {
290        self.iface().can_peek()
291    }
292}
293
294impl<T: 'static> PublishPort<T> {
295    /// Broadcast an item. Returns immediately, however many subscribers are
296    /// listening — **including none**, which is why this does not panic on an
297    /// unconnected port the way `put` does. A source that nobody watches is a
298    /// legitimate testbench (D85).
299    pub fn write(&self, item: &T) {
300        let iface = self.slot.borrow().as_ref().cloned();
301        if let Some(iface) = iface {
302            iface.write(item);
303        }
304    }
305
306    /// Is anyone listening?
307    pub fn has_subscribers(&self) -> bool {
308        self.is_bound()
309    }
310}
311
312impl<T: 'static> SubscribePort<T> {
313    /// Supply the receiver: hand the port a handle to the subscriber whose
314    /// `write` should run for every item.
315    ///
316    /// Called in the subscriber's `build`, before any connect phase runs, so
317    /// it is already in place when the hub comes looking for it. `connect`
318    /// chooses the stream; `subscribe` supplies the receiver.
319    pub fn subscribe<S: Subscriber<T>>(&self, subscriber: crate::shared::RustdvShared<S>) {
320        *self.slot.borrow_mut() = Some(Rc::new(subscriber));
321    }
322
323    /// The subscriber this port was given, if `subscribe` was called.
324    pub fn subscriber(&self) -> Option<Rc<dyn SinkHandle<T>>> {
325        self.slot.borrow().clone()
326    }
327}
328
329/// Read a subscriber's sink out of its port, through erasure.
330///
331/// The mirror image of [`bind`]: for put/get/peek the export writes an
332/// interface *into* the port, but a subscribe port already holds what the hub
333/// needs, so the hub takes it out.
334pub(crate) fn sink_of<T: 'static>(
335    owner: &dyn PortOwner,
336    name: PortName<dyn SinkHandle<T>>,
337) -> Result<Rc<dyn SinkHandle<T>>, ConnectError> {
338    let label = owner.owner_label();
339    let slot = owner
340        .owner_port_slot(name.as_str())
341        .ok_or(ConnectError::NoSuchPort {
342            owner: label,
343            name: name.as_str(),
344        })?;
345    let slot = slot
346        .downcast::<RefCell<Option<Rc<dyn SinkHandle<T>>>>>()
347        .map_err(|_| ConnectError::WrongInterface {
348            owner: label,
349            name: name.as_str(),
350        })?;
351    let sink = slot.borrow().clone();
352    sink.ok_or(ConnectError::NoSubscriber {
353        owner: label,
354        name: name.as_str(),
355    })
356}
357
358impl<REQ: 'static, RSP: 'static> Port<dyn crate::sequence::SeqItemIf<REQ, RSP>> {
359    /// Block until a sequence has an item ready for this driver.
360    pub async fn get_next_item(&self) -> crate::sequence::SeqItem<REQ> {
361        let iface = self.iface();
362        iface.get_next_item().await
363    }
364    /// Take an item **only if one is waiting** (the UVM's `try_next_item`).
365    /// A driver that must also do something else this clock edge cannot
366    /// afford the blocking form.
367    pub fn try_next_item(&self) -> Option<crate::sequence::SeqItem<REQ>> {
368        self.iface().try_next_item()
369    }
370    /// Release the sequence, optionally with its answer.
371    pub fn item_done(&self, rsp: Option<RSP>) {
372        self.iface().item_done(rsp)
373    }
374    /// Answer a request that was released earlier — the pipelined case.
375    pub fn put_response(&self, id: crate::sequence::TxnId, rsp: RSP) {
376        self.iface().put_response(id, rsp)
377    }
378    /// Bind this port directly, for a component that is handed its export at
379    /// construction rather than wired in a `connect` phase.
380    pub fn bind_iface(&self, iface: Rc<dyn crate::sequence::SeqItemIf<REQ, RSP>>) {
381        *self.slot.borrow_mut() = Some(iface);
382    }
383}
384
385// ===========================================================================
386// PortField — how the derive reaches a port without knowing its kind
387// ===========================================================================
388
389/// The bridge between a port field and the generated `ComponentNode` methods.
390///
391/// The derive sees the field's *type text* (`PutPort<u32>`), not its meaning,
392/// so it names the interface as `<PutPort<u32> as PortField>::Iface` rather
393/// than parsing generics out of tokens.
394pub trait PortField {
395    /// The interface this port demands: `dyn PutIf<T>`, `dyn GetIf<T>`, …
396    type Iface: ?Sized + 'static;
397    /// The binding cell, erased, so `connect` can reach it through `dyn`.
398    fn slot_any(&self) -> Rc<dyn Any>;
399    /// Has it been connected?
400    fn bound(&self) -> bool;
401}
402
403impl<I: ?Sized + 'static> PortField for Port<I> {
404    type Iface = I;
405    fn slot_any(&self) -> Rc<dyn Any> {
406        self.slot.clone()
407    }
408    fn bound(&self) -> bool {
409        self.is_bound()
410    }
411}
412
413// ===========================================================================
414// PortOwner — anything that can be asked for one of its ports
415// ===========================================================================
416
417/// One declared port, for the elaboration report.
418#[derive(Debug, Clone, PartialEq, Eq)]
419pub struct PortInfo {
420    /// The field name it was declared under.
421    pub name: &'static str,
422    /// `"put"`, `"get"`, `"peek"`, `"publish"`, `"subscribe"`.
423    pub kind: &'static str,
424    /// Must it be connected? Analysis ports need not be (D85).
425    pub required: bool,
426    /// Is it connected?
427    pub connected: bool,
428}
429
430/// Whatever `connect` is pointed at: a child slot, or a component itself.
431///
432/// This is the uniformity the UVM gets from `uvm_test` being a
433/// `uvm_component`. `&self.producer` (an erased `RustdvComp`) and `self` (a
434/// concrete component) are both `&dyn PortOwner`, so one `connect` serves both
435/// and there is no special case for "my own port".
436pub trait PortOwner {
437    /// The binding cell of the named port, erased.
438    fn owner_port_slot(&self, name: &str) -> Option<Rc<dyn Any>>;
439    /// The component's type name, for connection errors.
440    fn owner_label(&self) -> &'static str;
441}
442
443/// Errors from wiring. Every one is a testbench bug found during elaboration,
444/// so the exports panic on them — but the message says exactly which port, on
445/// which component, and why.
446#[derive(Debug, Clone, PartialEq, Eq)]
447pub enum ConnectError {
448    /// No `#[port(..)]` field of that name on that component.
449    NoSuchPort {
450        owner: &'static str,
451        name: &'static str,
452    },
453    /// The field exists but demands a different interface — a `get` export
454    /// aimed at a `put` port, or a different transaction type.
455    WrongInterface {
456        owner: &'static str,
457        name: &'static str,
458    },
459    /// A subscribe port was connected but its component never said what to do
460    /// with the items — no `subscribe` in its build phase.
461    NoSubscriber {
462        owner: &'static str,
463        name: &'static str,
464    },
465}
466
467impl fmt::Display for ConnectError {
468    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
469        match self {
470            ConnectError::NoSuchPort { owner, name } => write!(
471                f,
472                "connect: {owner} has no port named '{name}' \
473                 (is the field marked #[port(..)]? is the child slot built?)"
474            ),
475            ConnectError::WrongInterface { owner, name } => write!(
476                f,
477                "connect: {owner}'s port '{name}' wants a different interface \
478                 (put/get/peek mismatch, or a different transaction type)"
479            ),
480            ConnectError::NoSubscriber { owner, name } => write!(
481                f,
482                "connect: {owner}'s subscribe port '{name}' has no subscriber — call \
483                 `self.{name}.subscribe(handle)` in {owner}'s build phase"
484            ),
485        }
486    }
487}
488
489/// Bind `iface` into `owner`'s port called `name`. The kind and transaction
490/// type are checked by the compiler through `PortName<I>`; only the *presence*
491/// of the field can fail here.
492pub fn bind<I: ?Sized + 'static>(
493    owner: &dyn PortOwner,
494    name: PortName<I>,
495    iface: Rc<I>,
496) -> Result<(), ConnectError> {
497    let label = owner.owner_label();
498    let slot = owner
499        .owner_port_slot(name.as_str())
500        .ok_or(ConnectError::NoSuchPort {
501            owner: label,
502            name: name.as_str(),
503        })?;
504    let slot =
505        slot.downcast::<RefCell<Option<Rc<I>>>>()
506            .map_err(|_| ConnectError::WrongInterface {
507                owner: label,
508                name: name.as_str(),
509            })?;
510    *slot.borrow_mut() = Some(iface);
511    Ok(())
512}
513
514/// The panicking form the exports call: a wiring mistake is a bug in the
515/// testbench, and elaboration is where bugs should stop the run.
516pub(crate) fn bind_or_panic<I: ?Sized + 'static>(
517    owner: &dyn PortOwner,
518    name: PortName<I>,
519    iface: Rc<I>,
520) {
521    if let Err(e) = bind(owner, name, iface) {
522        panic!("{e}");
523    }
524}