rustdv-methodology 0.1.0

The verification methodology layer: component lifecycle and phases, ConfigDb, factory, objections, channels, analysis broadcast, TLM FIFOs, and the sequencer handshake (design-doc §5).
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
//! TLM ports, exports, and how they connect (D83–D85).
//!
//! # The problem, and why the obvious answers fail
//!
//! A parent wires its children together in `connect`. In SystemVerilog that
//! is `producer.put_port.connect(fifo.put_export)` — the parent reaches into
//! the child by name. rustdv's children are erased (`RustdvComp` holds a
//! `Box<dyn ComponentNode>`), so `self.producer.put_port` does not compile,
//! and Rust has no `$cast`-to-base to recover the concrete type: a `dyn`
//! pointer carries one vtable, not a class hierarchy, and `Any::downcast`
//! needs the exact type the parent is trying not to know.
//!
//! An earlier design answered this with a thread-local registry keyed by
//! hierarchical path. It worked for children and then fell over on the case
//! the UVM handles without blinking: a component connecting **its own** port.
//! A path-keyed registry can only be addressed by something that knows its
//! path, and a component does not know its own (D7).
//!
//! # The answer: a trait method is the cast
//!
//! `uvm_test` *is* a `uvm_component`, and that uniformity is the whole point
//! (D3): every participant is reachable the same way. rustdv gets the same
//! uniformity from the trait it already has. [`ComponentNode`] gains
//!
//! ```ignore
//! fn port_slot(&self, name: &str) -> Option<Rc<dyn Any>>;
//! ```
//!
//! generated by `#[derive(Component)]` from the `#[port(..)]` fields. It is
//! available *through* `dyn ComponentNode`, so an erased child answers it just
//! as a concrete `self` does. No downcast to the child's type, no path, no
//! registry, no walk-time stamping:
//!
//! ```ignore
//! fn connect(&mut self, _ctx: &mut RustdvCtx) {
//!     self.fifo.put_export().connect(&self.producer, Producer::PUT_PORT);
//!     self.fifo.get_export().connect(&self.consumer, Consumer::GET_PORT);
//!     self.fifo.put_export().connect(self, MathTest::X_OUT);  // my own port
//! }
//! ```
//!
//! The export always initiates the connection, as in the UVM. The first
//! argument is whoever owns the port — a child slot, or `self`. The second is
//! a derive-generated [`PortName`], so a misspelling is a compile error and
//! aiming a `put` export at a `get` port is a compile error too: the name
//! carries the interface, not just a string.
//!
//! [`ComponentNode`]: crate::ComponentNode

use std::any::Any;
use std::cell::RefCell;
use std::fmt;
use std::future::Future;
use std::marker::PhantomData;
use std::pin::Pin;
use std::rc::Rc;

// ===========================================================================
// The three TLM interfaces (TLM 1.0: put, get, peek)
// ===========================================================================

/// What a `put` export offers. Implemented by the FIFO's put side; a
/// [`PutPort`] holds one of these once connected.
///
/// `put` returns a boxed future rather than being an `async fn` because it is
/// called through `dyn`: a trait object cannot have an `async fn` whose future
/// type varies by implementor.
pub trait PutIf<T: 'static>: 'static {
    /// Block until the FIFO has room, then hand the item over.
    fn put(&self, item: T) -> Pin<Box<dyn Future<Output = ()> + '_>>;
    /// Take the item only if it fits right now; hand it back if it does not.
    fn try_put(&self, item: T) -> Result<(), T>;
    /// Is there room this instant? (Zero time; the answer can go stale.)
    fn can_put(&self) -> bool;
}

/// What a `get` export offers: take the item *out*.
pub trait GetIf<T: 'static>: 'static {
    fn get(&self) -> Pin<Box<dyn Future<Output = T> + '_>>;
    fn try_get(&self) -> Option<T>;
    fn can_get(&self) -> bool;
}

/// What a `peek` export offers: copy the item and **leave it there**, so
/// whoever gets it next still finds it. That is why peek needs `T: Clone`
/// where get does not.
pub trait PeekIf<T: 'static>: 'static {
    fn peek(&self) -> Pin<Box<dyn Future<Output = T> + '_>>;
    fn try_peek(&self) -> Option<T>;
    fn can_peek(&self) -> bool;
}

// ===========================================================================
// The analysis interfaces (D86–D88): broadcast, not a queue
// ===========================================================================

/// What a component does with an item it was handed by an analysis broadcast
/// — the port of `uvm_subscriber`'s `write`.
///
/// It takes `&mut self` and returns nothing: delivery is synchronous and in
/// zero time, so there is no `async`, no back-pressure, and nothing to await.
/// Implement it on the **state** a subscriber keeps, not on the component (a
/// sibling cannot be reached from a `run` phase); see
/// [`RustdvShared`](crate::RustdvShared). The UVM makes the subscriber a
/// component; here it is the plain struct the component hosts.
pub trait Subscriber<T>: 'static {
    fn write(&mut self, item: &T);
}

/// The erased handle a subscribe port hands to the hub.
///
/// `&mut` lives *inside* the handle rather than in the call, which is what
/// makes zero-time delivery possible: the hub holds no `&mut` to anything, so
/// it can call every subscriber in a plain loop.
pub trait SinkHandle<T: 'static>: 'static {
    fn deliver(&self, item: &T);
}

impl<T: 'static, S: Subscriber<T>> SinkHandle<T> for crate::shared::RustdvShared<S> {
    fn deliver(&self, item: &T) {
        self.get_mut().write(item);
    }
}

/// What a publish export offers: hand an item to everyone, immediately.
pub trait PublishIf<T: 'static>: 'static {
    fn write(&self, item: &T);
}

// ===========================================================================
// PortName — a typed, derive-generated constant
// ===========================================================================

/// The name a port is declared under, carrying the interface it demands.
///
/// `#[port(put)] cmd_port: PutPort<Command>` generates `Tester::CMD_PORT`, a
/// `PortName<dyn PutIf<Command>>`. The constant and the lookup key both come
/// from the one field, so they cannot drift; a misspelled constant does not
/// compile; and connecting a `get` export to it does not compile either,
/// because the interface is part of the type.
pub struct PortName<I: ?Sized> {
    name: &'static str,
    _marker: PhantomData<fn(&I)>,
}

impl<I: ?Sized> PortName<I> {
    /// Called by the derive. Hand-writing one is allowed and harmless — it
    /// still has to match a real field name to connect.
    pub const fn new(name: &'static str) -> PortName<I> {
        PortName { name, _marker: PhantomData }
    }

    pub const fn as_str(&self) -> &'static str {
        self.name
    }
}

impl<I: ?Sized> Clone for PortName<I> {
    fn clone(&self) -> Self {
        *self
    }
}
impl<I: ?Sized> Copy for PortName<I> {}

impl<I: ?Sized> fmt::Debug for PortName<I> {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "PortName({})", self.name)
    }
}

// ===========================================================================
// Port — what a component declares
// ===========================================================================

/// A port: a component's request for an interface it does not own.
///
/// One generic struct serves all three kinds, since the only difference is
/// which interface it holds — [`PutPort`], [`GetPort`] and [`PeekPort`] are
/// aliases, not separate types.
///
/// The binding lives behind `Rc<RefCell<..>>` because connection happens
/// through erasure: `connect` reaches the port as an `Rc<dyn Any>` and must
/// write to it, so the mutability has to be interior. The component's own
/// field and the handle `connect` obtains are two `Rc`s to the same cell.
pub struct Port<I: ?Sized + 'static> {
    slot: Rc<RefCell<Option<Rc<I>>>>,
}

/// A port that puts items into something it does not own.
pub type PutPort<T> = Port<dyn PutIf<T>>;
/// A port that takes items out of something it does not own.
pub type GetPort<T> = Port<dyn GetIf<T>>;
/// A port that copies items without removing them.
pub type PeekPort<T> = Port<dyn PeekIf<T>>;
/// A port that broadcasts items to whoever is listening — or to nobody.
pub type PublishPort<T> = Port<dyn PublishIf<T>>;
/// A port that receives broadcast items. It is the odd one out: instead of
/// the export writing an interface *into* it, the component fills it with its
/// subscriber ([`subscribe`](SubscribePort::subscribe)) and the hub reads it
/// out at connect time. Broadcast runs the other way, so the wiring does too.
pub type SubscribePort<T> = Port<dyn SinkHandle<T>>;

impl<I: ?Sized + 'static> Clone for Port<I> {
    /// Another handle to the *same* binding, not a second port.
    fn clone(&self) -> Self {
        Port { slot: self.slot.clone() }
    }
}

impl<I: ?Sized + 'static> Default for Port<I> {
    fn default() -> Self {
        Port { slot: Rc::new(RefCell::new(None)) }
    }
}

impl<I: ?Sized + 'static> Port<I> {
    /// A fresh, unconnected port. `Default` does the same; components get
    /// theirs from `#[derive(Default)]`.
    pub fn new() -> Port<I> {
        Port::default()
    }

    /// Has this port been connected?
    pub fn is_bound(&self) -> bool {
        self.slot.borrow().is_some()
    }

    /// The interface behind the port, or a panic naming the failure.
    ///
    /// Elaboration reports every unconnected port before the run phase starts,
    /// so this panic is the belt to that suspenders.
    fn iface(&self) -> Rc<I> {
        match self.slot.borrow().as_ref() {
            Some(i) => i.clone(),
            None => panic!(
                "a TLM port was used before it was connected — connect it in \
                 the parent's connect phase with \
                 `fifo.<kind>_export().connect(owner, Owner::PORT_NAME)`"
            ),
        }
    }
}

impl<T: 'static> PutPort<T> {
    /// Block until the far end has room, then hand the item over.
    pub async fn put(&self, item: T) {
        let iface = self.iface();
        iface.put(item).await
    }
    /// Hand the item over only if it fits right now.
    pub fn try_put(&self, item: T) -> Result<(), T> {
        self.iface().try_put(item)
    }
    pub fn can_put(&self) -> bool {
        self.iface().can_put()
    }
}

impl<T: 'static> GetPort<T> {
    /// Block until there is an item, then take it out.
    pub async fn get(&self) -> T {
        let iface = self.iface();
        iface.get().await
    }
    pub fn try_get(&self) -> Option<T> {
        self.iface().try_get()
    }
    pub fn can_get(&self) -> bool {
        self.iface().can_get()
    }
}

impl<T: 'static> PeekPort<T> {
    /// Block until there is an item, then copy it, leaving it in place.
    pub async fn peek(&self) -> T {
        let iface = self.iface();
        iface.peek().await
    }
    pub fn try_peek(&self) -> Option<T> {
        self.iface().try_peek()
    }
    pub fn can_peek(&self) -> bool {
        self.iface().can_peek()
    }
}

impl<T: 'static> PublishPort<T> {
    /// Broadcast an item. Returns immediately, however many subscribers are
    /// listening — **including none**, which is why this does not panic on an
    /// unconnected port the way `put` does. A source that nobody watches is a
    /// legitimate testbench (D85).
    pub fn write(&self, item: &T) {
        let iface = self.slot.borrow().as_ref().cloned();
        if let Some(iface) = iface {
            iface.write(item);
        }
    }

    /// Is anyone listening?
    pub fn has_subscribers(&self) -> bool {
        self.is_bound()
    }
}

impl<T: 'static> SubscribePort<T> {
    /// Supply the receiver: hand the port a handle to the subscriber whose
    /// `write` should run for every item.
    ///
    /// Called in the subscriber's `build`, before any connect phase runs, so
    /// it is already in place when the hub comes looking for it. `connect`
    /// chooses the stream; `subscribe` supplies the receiver.
    pub fn subscribe<S: Subscriber<T>>(&self, subscriber: crate::shared::RustdvShared<S>) {
        *self.slot.borrow_mut() = Some(Rc::new(subscriber));
    }

    /// The subscriber this port was given, if `subscribe` was called.
    pub fn subscriber(&self) -> Option<Rc<dyn SinkHandle<T>>> {
        self.slot.borrow().clone()
    }
}

/// Read a subscriber's sink out of its port, through erasure.
///
/// The mirror image of [`bind`]: for put/get/peek the export writes an
/// interface *into* the port, but a subscribe port already holds what the hub
/// needs, so the hub takes it out.
pub(crate) fn sink_of<T: 'static>(
    owner: &dyn PortOwner,
    name: PortName<dyn SinkHandle<T>>,
) -> Result<Rc<dyn SinkHandle<T>>, ConnectError> {
    let label = owner.owner_label();
    let slot = owner
        .owner_port_slot(name.as_str())
        .ok_or(ConnectError::NoSuchPort { owner: label, name: name.as_str() })?;
    let slot = slot
        .downcast::<RefCell<Option<Rc<dyn SinkHandle<T>>>>>()
        .map_err(|_| ConnectError::WrongInterface { owner: label, name: name.as_str() })?;
    let sink = slot.borrow().clone();
    sink.ok_or(ConnectError::NoSubscriber { owner: label, name: name.as_str() })
}

impl<REQ: 'static, RSP: 'static> Port<dyn crate::sequence::SeqItemIf<REQ, RSP>> {
    /// Block until a sequence has an item ready for this driver.
    pub async fn get_next_item(&self) -> crate::sequence::SeqItem<REQ> {
        let iface = self.iface();
        iface.get_next_item().await
    }
    /// Take an item **only if one is waiting** (the UVM's `try_next_item`).
    /// A driver that must also do something else this clock edge cannot
    /// afford the blocking form.
    pub fn try_next_item(&self) -> Option<crate::sequence::SeqItem<REQ>> {
        self.iface().try_next_item()
    }
    /// Release the sequence, optionally with its answer.
    pub fn item_done(&self, rsp: Option<RSP>) {
        self.iface().item_done(rsp)
    }
    /// Answer a request that was released earlier — the pipelined case.
    pub fn put_response(&self, id: crate::sequence::TxnId, rsp: RSP) {
        self.iface().put_response(id, rsp)
    }
    /// Bind this port directly, for a component that is handed its export at
    /// construction rather than wired in a `connect` phase.
    pub fn bind_iface(&self, iface: Rc<dyn crate::sequence::SeqItemIf<REQ, RSP>>) {
        *self.slot.borrow_mut() = Some(iface);
    }
}

// ===========================================================================
// PortField — how the derive reaches a port without knowing its kind
// ===========================================================================

/// The bridge between a port field and the generated `ComponentNode` methods.
///
/// The derive sees the field's *type text* (`PutPort<u32>`), not its meaning,
/// so it names the interface as `<PutPort<u32> as PortField>::Iface` rather
/// than parsing generics out of tokens.
pub trait PortField {
    /// The interface this port demands: `dyn PutIf<T>`, `dyn GetIf<T>`, …
    type Iface: ?Sized + 'static;
    /// The binding cell, erased, so `connect` can reach it through `dyn`.
    fn slot_any(&self) -> Rc<dyn Any>;
    /// Has it been connected?
    fn bound(&self) -> bool;
}

impl<I: ?Sized + 'static> PortField for Port<I> {
    type Iface = I;
    fn slot_any(&self) -> Rc<dyn Any> {
        self.slot.clone()
    }
    fn bound(&self) -> bool {
        self.is_bound()
    }
}

// ===========================================================================
// PortOwner — anything that can be asked for one of its ports
// ===========================================================================

/// One declared port, for the elaboration report.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct PortInfo {
    /// The field name it was declared under.
    pub name: &'static str,
    /// `"put"`, `"get"`, `"peek"`, `"publish"`, `"subscribe"`.
    pub kind: &'static str,
    /// Must it be connected? Analysis ports need not be (D85).
    pub required: bool,
    /// Is it connected?
    pub connected: bool,
}

/// Whatever `connect` is pointed at: a child slot, or a component itself.
///
/// This is the uniformity the UVM gets from `uvm_test` being a
/// `uvm_component`. `&self.producer` (an erased `RustdvComp`) and `self` (a
/// concrete component) are both `&dyn PortOwner`, so one `connect` serves both
/// and there is no special case for "my own port".
pub trait PortOwner {
    /// The binding cell of the named port, erased.
    fn owner_port_slot(&self, name: &str) -> Option<Rc<dyn Any>>;
    /// The component's type name, for connection errors.
    fn owner_label(&self) -> &'static str;
}

/// Errors from wiring. Every one is a testbench bug found during elaboration,
/// so the exports panic on them — but the message says exactly which port, on
/// which component, and why.
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ConnectError {
    /// No `#[port(..)]` field of that name on that component.
    NoSuchPort { owner: &'static str, name: &'static str },
    /// The field exists but demands a different interface — a `get` export
    /// aimed at a `put` port, or a different transaction type.
    WrongInterface { owner: &'static str, name: &'static str },
    /// A subscribe port was connected but its component never said what to do
    /// with the items — no `subscribe` in its build phase.
    NoSubscriber { owner: &'static str, name: &'static str },
}

impl fmt::Display for ConnectError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            ConnectError::NoSuchPort { owner, name } => write!(
                f,
                "connect: {owner} has no port named '{name}' \
                 (is the field marked #[port(..)]? is the child slot built?)"
            ),
            ConnectError::WrongInterface { owner, name } => write!(
                f,
                "connect: {owner}'s port '{name}' wants a different interface \
                 (put/get/peek mismatch, or a different transaction type)"
            ),
            ConnectError::NoSubscriber { owner, name } => write!(
                f,
                "connect: {owner}'s subscribe port '{name}' has no subscriber — call \
                 `self.{name}.subscribe(handle)` in {owner}'s build phase"
            ),
        }
    }
}

/// Bind `iface` into `owner`'s port called `name`. The kind and transaction
/// type are checked by the compiler through `PortName<I>`; only the *presence*
/// of the field can fail here.
pub fn bind<I: ?Sized + 'static>(
    owner: &dyn PortOwner,
    name: PortName<I>,
    iface: Rc<I>,
) -> Result<(), ConnectError> {
    let label = owner.owner_label();
    let slot = owner
        .owner_port_slot(name.as_str())
        .ok_or(ConnectError::NoSuchPort { owner: label, name: name.as_str() })?;
    let slot = slot
        .downcast::<RefCell<Option<Rc<I>>>>()
        .map_err(|_| ConnectError::WrongInterface { owner: label, name: name.as_str() })?;
    *slot.borrow_mut() = Some(iface);
    Ok(())
}

/// The panicking form the exports call: a wiring mistake is a bug in the
/// testbench, and elaboration is where bugs should stop the run.
pub(crate) fn bind_or_panic<I: ?Sized + 'static>(
    owner: &dyn PortOwner,
    name: PortName<I>,
    iface: Rc<I>,
) {
    if let Err(e) = bind(owner, name, iface) {
        panic!("{e}");
    }
}