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 { name, _marker: PhantomData }
151    }
152
153    pub const fn as_str(&self) -> &'static str {
154        self.name
155    }
156}
157
158impl<I: ?Sized> Clone for PortName<I> {
159    fn clone(&self) -> Self {
160        *self
161    }
162}
163impl<I: ?Sized> Copy for PortName<I> {}
164
165impl<I: ?Sized> fmt::Debug for PortName<I> {
166    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
167        write!(f, "PortName({})", self.name)
168    }
169}
170
171// ===========================================================================
172// Port — what a component declares
173// ===========================================================================
174
175/// A port: a component's request for an interface it does not own.
176///
177/// One generic struct serves all three kinds, since the only difference is
178/// which interface it holds — [`PutPort`], [`GetPort`] and [`PeekPort`] are
179/// aliases, not separate types.
180///
181/// The binding lives behind `Rc<RefCell<..>>` because connection happens
182/// through erasure: `connect` reaches the port as an `Rc<dyn Any>` and must
183/// write to it, so the mutability has to be interior. The component's own
184/// field and the handle `connect` obtains are two `Rc`s to the same cell.
185pub struct Port<I: ?Sized + 'static> {
186    slot: Rc<RefCell<Option<Rc<I>>>>,
187}
188
189/// A port that puts items into something it does not own.
190pub type PutPort<T> = Port<dyn PutIf<T>>;
191/// A port that takes items out of something it does not own.
192pub type GetPort<T> = Port<dyn GetIf<T>>;
193/// A port that copies items without removing them.
194pub type PeekPort<T> = Port<dyn PeekIf<T>>;
195/// A port that broadcasts items to whoever is listening — or to nobody.
196pub type PublishPort<T> = Port<dyn PublishIf<T>>;
197/// A port that receives broadcast items. It is the odd one out: instead of
198/// the export writing an interface *into* it, the component fills it with its
199/// subscriber ([`subscribe`](SubscribePort::subscribe)) and the hub reads it
200/// out at connect time. Broadcast runs the other way, so the wiring does too.
201pub type SubscribePort<T> = Port<dyn SinkHandle<T>>;
202
203impl<I: ?Sized + 'static> Clone for Port<I> {
204    /// Another handle to the *same* binding, not a second port.
205    fn clone(&self) -> Self {
206        Port { slot: self.slot.clone() }
207    }
208}
209
210impl<I: ?Sized + 'static> Default for Port<I> {
211    fn default() -> Self {
212        Port { slot: Rc::new(RefCell::new(None)) }
213    }
214}
215
216impl<I: ?Sized + 'static> Port<I> {
217    /// A fresh, unconnected port. `Default` does the same; components get
218    /// theirs from `#[derive(Default)]`.
219    pub fn new() -> Port<I> {
220        Port::default()
221    }
222
223    /// Has this port been connected?
224    pub fn is_bound(&self) -> bool {
225        self.slot.borrow().is_some()
226    }
227
228    /// The interface behind the port, or a panic naming the failure.
229    ///
230    /// Elaboration reports every unconnected port before the run phase starts,
231    /// so this panic is the belt to that suspenders.
232    fn iface(&self) -> Rc<I> {
233        match self.slot.borrow().as_ref() {
234            Some(i) => i.clone(),
235            None => panic!(
236                "a TLM port was used before it was connected — connect it in \
237                 the parent's connect phase with \
238                 `fifo.<kind>_export().connect(owner, Owner::PORT_NAME)`"
239            ),
240        }
241    }
242}
243
244impl<T: 'static> PutPort<T> {
245    /// Block until the far end has room, then hand the item over.
246    pub async fn put(&self, item: T) {
247        let iface = self.iface();
248        iface.put(item).await
249    }
250    /// Hand the item over only if it fits right now.
251    pub fn try_put(&self, item: T) -> Result<(), T> {
252        self.iface().try_put(item)
253    }
254    pub fn can_put(&self) -> bool {
255        self.iface().can_put()
256    }
257}
258
259impl<T: 'static> GetPort<T> {
260    /// Block until there is an item, then take it out.
261    pub async fn get(&self) -> T {
262        let iface = self.iface();
263        iface.get().await
264    }
265    pub fn try_get(&self) -> Option<T> {
266        self.iface().try_get()
267    }
268    pub fn can_get(&self) -> bool {
269        self.iface().can_get()
270    }
271}
272
273impl<T: 'static> PeekPort<T> {
274    /// Block until there is an item, then copy it, leaving it in place.
275    pub async fn peek(&self) -> T {
276        let iface = self.iface();
277        iface.peek().await
278    }
279    pub fn try_peek(&self) -> Option<T> {
280        self.iface().try_peek()
281    }
282    pub fn can_peek(&self) -> bool {
283        self.iface().can_peek()
284    }
285}
286
287impl<T: 'static> PublishPort<T> {
288    /// Broadcast an item. Returns immediately, however many subscribers are
289    /// listening — **including none**, which is why this does not panic on an
290    /// unconnected port the way `put` does. A source that nobody watches is a
291    /// legitimate testbench (D85).
292    pub fn write(&self, item: &T) {
293        let iface = self.slot.borrow().as_ref().cloned();
294        if let Some(iface) = iface {
295            iface.write(item);
296        }
297    }
298
299    /// Is anyone listening?
300    pub fn has_subscribers(&self) -> bool {
301        self.is_bound()
302    }
303}
304
305impl<T: 'static> SubscribePort<T> {
306    /// Supply the receiver: hand the port a handle to the subscriber whose
307    /// `write` should run for every item.
308    ///
309    /// Called in the subscriber's `build`, before any connect phase runs, so
310    /// it is already in place when the hub comes looking for it. `connect`
311    /// chooses the stream; `subscribe` supplies the receiver.
312    pub fn subscribe<S: Subscriber<T>>(&self, subscriber: crate::shared::RustdvShared<S>) {
313        *self.slot.borrow_mut() = Some(Rc::new(subscriber));
314    }
315
316    /// The subscriber this port was given, if `subscribe` was called.
317    pub fn subscriber(&self) -> Option<Rc<dyn SinkHandle<T>>> {
318        self.slot.borrow().clone()
319    }
320}
321
322/// Read a subscriber's sink out of its port, through erasure.
323///
324/// The mirror image of [`bind`]: for put/get/peek the export writes an
325/// interface *into* the port, but a subscribe port already holds what the hub
326/// needs, so the hub takes it out.
327pub(crate) fn sink_of<T: 'static>(
328    owner: &dyn PortOwner,
329    name: PortName<dyn SinkHandle<T>>,
330) -> Result<Rc<dyn SinkHandle<T>>, ConnectError> {
331    let label = owner.owner_label();
332    let slot = owner
333        .owner_port_slot(name.as_str())
334        .ok_or(ConnectError::NoSuchPort { owner: label, name: name.as_str() })?;
335    let slot = slot
336        .downcast::<RefCell<Option<Rc<dyn SinkHandle<T>>>>>()
337        .map_err(|_| ConnectError::WrongInterface { owner: label, name: name.as_str() })?;
338    let sink = slot.borrow().clone();
339    sink.ok_or(ConnectError::NoSubscriber { owner: label, name: name.as_str() })
340}
341
342impl<REQ: 'static, RSP: 'static> Port<dyn crate::sequence::SeqItemIf<REQ, RSP>> {
343    /// Block until a sequence has an item ready for this driver.
344    pub async fn get_next_item(&self) -> crate::sequence::SeqItem<REQ> {
345        let iface = self.iface();
346        iface.get_next_item().await
347    }
348    /// Take an item **only if one is waiting** (the UVM's `try_next_item`).
349    /// A driver that must also do something else this clock edge cannot
350    /// afford the blocking form.
351    pub fn try_next_item(&self) -> Option<crate::sequence::SeqItem<REQ>> {
352        self.iface().try_next_item()
353    }
354    /// Release the sequence, optionally with its answer.
355    pub fn item_done(&self, rsp: Option<RSP>) {
356        self.iface().item_done(rsp)
357    }
358    /// Answer a request that was released earlier — the pipelined case.
359    pub fn put_response(&self, id: crate::sequence::TxnId, rsp: RSP) {
360        self.iface().put_response(id, rsp)
361    }
362    /// Bind this port directly, for a component that is handed its export at
363    /// construction rather than wired in a `connect` phase.
364    pub fn bind_iface(&self, iface: Rc<dyn crate::sequence::SeqItemIf<REQ, RSP>>) {
365        *self.slot.borrow_mut() = Some(iface);
366    }
367}
368
369// ===========================================================================
370// PortField — how the derive reaches a port without knowing its kind
371// ===========================================================================
372
373/// The bridge between a port field and the generated `ComponentNode` methods.
374///
375/// The derive sees the field's *type text* (`PutPort<u32>`), not its meaning,
376/// so it names the interface as `<PutPort<u32> as PortField>::Iface` rather
377/// than parsing generics out of tokens.
378pub trait PortField {
379    /// The interface this port demands: `dyn PutIf<T>`, `dyn GetIf<T>`, …
380    type Iface: ?Sized + 'static;
381    /// The binding cell, erased, so `connect` can reach it through `dyn`.
382    fn slot_any(&self) -> Rc<dyn Any>;
383    /// Has it been connected?
384    fn bound(&self) -> bool;
385}
386
387impl<I: ?Sized + 'static> PortField for Port<I> {
388    type Iface = I;
389    fn slot_any(&self) -> Rc<dyn Any> {
390        self.slot.clone()
391    }
392    fn bound(&self) -> bool {
393        self.is_bound()
394    }
395}
396
397// ===========================================================================
398// PortOwner — anything that can be asked for one of its ports
399// ===========================================================================
400
401/// One declared port, for the elaboration report.
402#[derive(Debug, Clone, PartialEq, Eq)]
403pub struct PortInfo {
404    /// The field name it was declared under.
405    pub name: &'static str,
406    /// `"put"`, `"get"`, `"peek"`, `"publish"`, `"subscribe"`.
407    pub kind: &'static str,
408    /// Must it be connected? Analysis ports need not be (D85).
409    pub required: bool,
410    /// Is it connected?
411    pub connected: bool,
412}
413
414/// Whatever `connect` is pointed at: a child slot, or a component itself.
415///
416/// This is the uniformity the UVM gets from `uvm_test` being a
417/// `uvm_component`. `&self.producer` (an erased `RustdvComp`) and `self` (a
418/// concrete component) are both `&dyn PortOwner`, so one `connect` serves both
419/// and there is no special case for "my own port".
420pub trait PortOwner {
421    /// The binding cell of the named port, erased.
422    fn owner_port_slot(&self, name: &str) -> Option<Rc<dyn Any>>;
423    /// The component's type name, for connection errors.
424    fn owner_label(&self) -> &'static str;
425}
426
427/// Errors from wiring. Every one is a testbench bug found during elaboration,
428/// so the exports panic on them — but the message says exactly which port, on
429/// which component, and why.
430#[derive(Debug, Clone, PartialEq, Eq)]
431pub enum ConnectError {
432    /// No `#[port(..)]` field of that name on that component.
433    NoSuchPort { owner: &'static str, name: &'static str },
434    /// The field exists but demands a different interface — a `get` export
435    /// aimed at a `put` port, or a different transaction type.
436    WrongInterface { owner: &'static str, name: &'static str },
437    /// A subscribe port was connected but its component never said what to do
438    /// with the items — no `subscribe` in its build phase.
439    NoSubscriber { owner: &'static str, name: &'static str },
440}
441
442impl fmt::Display for ConnectError {
443    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
444        match self {
445            ConnectError::NoSuchPort { owner, name } => write!(
446                f,
447                "connect: {owner} has no port named '{name}' \
448                 (is the field marked #[port(..)]? is the child slot built?)"
449            ),
450            ConnectError::WrongInterface { owner, name } => write!(
451                f,
452                "connect: {owner}'s port '{name}' wants a different interface \
453                 (put/get/peek mismatch, or a different transaction type)"
454            ),
455            ConnectError::NoSubscriber { owner, name } => write!(
456                f,
457                "connect: {owner}'s subscribe port '{name}' has no subscriber — call \
458                 `self.{name}.subscribe(handle)` in {owner}'s build phase"
459            ),
460        }
461    }
462}
463
464/// Bind `iface` into `owner`'s port called `name`. The kind and transaction
465/// type are checked by the compiler through `PortName<I>`; only the *presence*
466/// of the field can fail here.
467pub fn bind<I: ?Sized + 'static>(
468    owner: &dyn PortOwner,
469    name: PortName<I>,
470    iface: Rc<I>,
471) -> Result<(), ConnectError> {
472    let label = owner.owner_label();
473    let slot = owner
474        .owner_port_slot(name.as_str())
475        .ok_or(ConnectError::NoSuchPort { owner: label, name: name.as_str() })?;
476    let slot = slot
477        .downcast::<RefCell<Option<Rc<I>>>>()
478        .map_err(|_| ConnectError::WrongInterface { owner: label, name: name.as_str() })?;
479    *slot.borrow_mut() = Some(iface);
480    Ok(())
481}
482
483/// The panicking form the exports call: a wiring mistake is a bug in the
484/// testbench, and elaboration is where bugs should stop the run.
485pub(crate) fn bind_or_panic<I: ?Sized + 'static>(
486    owner: &dyn PortOwner,
487    name: PortName<I>,
488    iface: Rc<I>,
489) {
490    if let Err(e) = bind(owner, name, iface) {
491        panic!("{e}");
492    }
493}