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}