Part 6 · Testbench Architecture · Intermediate
Q&A: TB Architecture & UVM Readiness
Interview Q&A — explain your TB end to end, reusability, what UVM adds, handshake vs mailbox in depth, and clean test termination.
Q1: Explain your testbench architecture end to end
Structure the answer as a transaction's journey, not a parts list: the test (selected by plusarg) configures knobs and picks a generator variant; the generator randomizes transactions under constraints and puts them in a mailbox; the driver pulls each one and wiggles pins through a virtual interface; the monitor independently reassembles pin activity into transactions and broadcasts to the scoreboard (which compares against a reference model and counts errors) and coverage. The env constructs and wires all of it; end-of-test drains in-flight traffic, the scoreboard reports leftovers, and a single PASS/FAIL banner plus exit code closes the run.
THE 60-SECOND WHITEBOARD
test (+TESTNAME, knobs)
│ configures
▼
env ─ builds & wires everything
├─ generator ──mailbox──► driver ──vif──► DUT
│ │ pins
├─ ref model ◄── monitor ◄─────────────────┘
│ │ └────────► coverage
└─ scoreboard ◄──────┘
│ error_count
▼
drain → final checks → "*** TEST PASSED/FAILED ***" → exit codeQ2 & Q3: Reusability, and what UVM would add
Q2: How would you make it reusable across projects?
Package per protocol agent (txn + gen + drv + mon) with zero references to any DUT specifics — the agent is the reuse unit.
All DUT-specific knowledge confined to the env layer and the test layer; agents see only their own interface.
Configuration objects instead of hard-coded values — widths, timing, agent counts set per project.
Virtual interfaces as the only RTL touchpoint — no hierarchical paths inside classes.
The reference model and checkers as plain classes with no TB dependencies — reusable even outside this TB.
Q3: What does UVM give you over this?
Answer in one line each: standardization (any UVM engineer reads my env in an afternoon; my custom one needs a tour); factory (swap driver/txn types from the test without env edits — my version is a case statement I maintain); config db (hierarchical config without threading constructor arguments); sequences (layered, interleaved, reactive stimulus my single generator loop cannot express); ecosystem (commercial VIP plugs in directly). What it costs: learning curve and debug opacity — and for this DUT's size, the hand-built TB was the right scope call.
Q4: Sequencer-driver handshake vs your mailbox — in depth
This is the depth question that separates name-droppers from engineers. The mailbox is push with no completion : the producer runs ahead (bounded only by mailbox depth), and the generator never learns when — or whether — an item actually hit the pins. The UVM handshake is pull with completion : the driver requests work with get_next_item(), and item_done() releases the sequence's blocking finish_item() — so stimulus is paced by the consumer and the sequence gets per-item completion (and optionally a response object back).
MAILBOX (push) SEQ_ITEM_PORT (pull + done)
gen: put(t1) put(t2) put(t3) seq: start_item(t1) ──► waits
│ (runs ahead, items driver: get_next_item()
▼ queue up blindly) seq: randomize, finish_item(t1)
drv: get(t1)... get(t2)... driver: drives pins, item_done()
seq: unblocked ── knows t1 DONE
no feedback: gen cannot react can read response, then decide
to responses or errors what t2 should even be
consequences
───────────
ordering across 2 generators: arbitration is built into the
needs hand-rolled locking sequencer (grab/lock/priority)
reactive stimulus: natural: finish_item returns,
needs a second mailbox inspect rsp, branch
+ hand-rolled correlationState the equivalence too: a mailbox of depth 1 plus an ack event approximates the handshake — UVM standardizes that pattern and adds arbitration among multiple sequences, which is the part genuinely painful to hand-roll.
Q5: How do you end a test cleanly — in both worlds
Hand-built answer
Generator signals stimulus exhausted (done event or count).
Drain: wait until driver is idle and the scoreboard's expected-queue empties — with a watchdog timeout so a hang fails loudly.
Scoreboard final check: leftover expected or orphan actual transactions are errors.
Print the single PASS/FAIL banner; $finish (pass) or $fatal(1) (fail) for the exit code.
UVM answer
Components and sequences raise objections while they have pending work; the run_phase ends when all are dropped.
drain_time (or a drop-delay) absorbs in-flight tail traffic after the last drop.
check_phase/report_phase run after run_phase — scoreboard leftovers and error counts are evaluated there.
Watchdog still required: a never-dropped objection hangs exactly like a never-fired done event — `uvm_fatal on timeout.
The unifying insight to close with: both are votes-outstanding schemes — my done-counter was a single vote; objections generalize it to every component voting independently. Same concept, standardized.
Key takeaways
Narrate the architecture as a transaction's journey ending in a self-checking verdict.
Reusability = protocol-pure agents, config objects, virtual-interface-only RTL contact, plain-class models.
Mailbox vs handshake: push-no-feedback vs pull-with-completion; arbitration is UVM's real hand-roll-painful add.
Clean ending in both worlds: signal done, drain with a watchdog, final sweep, one verdict.
Common pitfalls
Reciting a component list without data flow or end-of-test story — sounds memorized, not built.
Claiming the mailbox and seq_item_port are "basically the same" — misses completion feedback and arbitration.
No watchdog in either world's ending — a hung test that never fails is the worst regression outcome.
Answering "what does UVM add" with only buzzwords — pair every feature with the hand-built code it replaces.