Part 6 · Testbench Architecture · Intermediate
Concept Mapping: TB to UVM
Side-by-side mapping: env to uvm_env, generator to sequences, mailboxes to TLM, objections, and test selection.
Every piece has a twin
Nothing in a UVM environment is conceptually new if you have hand-built a class TB. The table below is the whole secret — read each row as "the thing I wrote by hand" versus "the standardized, automated version":
HAND-BUILT UVM EQUIVALENT
────────────────────────────── ─────────────────────────────────────
env class with new() wiring uvm_env, build_phase/connect_phase
generator class (randomize uvm_sequence (stimulus code) running
loop pushing into mailbox) on a uvm_sequencer (arbitration)
mailbox#(txn) gen→drv seq_item_port: driver pulls with
handoff (put/get) get_next_item / item_done
monitor publishing into N uvm_analysis_port: monitor.ap.write(t)
mailboxes (one per consumer) broadcasts to all subscribers
done-counter / event to end uvm_objection: raise at start,
the test drop when done; run_phase ends
+TESTNAME plusarg + case +UVM_TESTNAME + factory creates
statement choosing the test the test class by name
logger class with levels uvm_report: `uvm_info/`uvm_error
and scopes with verbosity and id filtering
cfg object passed in new() uvm_config_db set/get by
arguments down the tree hierarchical pathTwo mappings deserve a closer look because the shape changes, not just the name: the generator-to-sequence split, and mailbox-to-TLM.
The two mappings that change shape
Generator → sequence + sequencer
A hand-built generator is one class doing two jobs: deciding what stimulus to create and delivering it to the driver. UVM splits these: the uvm_sequence holds the stimulus-generation code (your randomize loop), while the uvm_sequencer is the delivery channel with arbitration — several sequences can run on one sequencer and interleave.
Mailbox → seq_item_port handshake
Your mailbox handoff is producer-push: the generator put()s and moves on. The UVM handshake is consumer-pull with completion: the driver calls get_next_item(), drives the transaction, then calls item_done() — and the sequence's finish_item() unblocks only then. The sequence therefore knows when each item finished, which is what makes reactive stimulus (read a response, decide the next item) natural in UVM and clumsy with a plain mailbox.
// HAND-BUILT: push into a mailbox, fire and forget
task generator::run();
repeat (cfg.num_txns) begin
txn t = new();
void'(t.randomize());
out_mb.put(t); // returns as soon as mailbox accepts
end
endtask
// UVM: pull handshake with completion feedback
task my_seq::body();
repeat (n) begin
req = txn::type_id::create("req");
start_item(req); // wait for driver to be ready
void'(req.randomize());
finish_item(req); // blocks until driver calls item_done()
// here we KNOW the driver completed it — reactive stimulus possible
end
endtaskThe two stacks, aligned
HAND-BUILT STACK UVM STACK
──────────────── ─────────
test_lib.sv my_test (uvm_test)
│ case(+TESTNAME) │ +UVM_TESTNAME → factory
▼ ▼
env ─────────────────────────── uvm_env
├─ generator ├─ sequence ─► sequencer
│ │ mailbox.put │ │ seq_item_port
├─ driver ◄──┘ ├─ driver ◄──────┘ (pull + done)
│ │ virtual if │ │ virtual if (config db)
├─ monitor ├─ monitor
│ │ mailbox per consumer │ │ analysis_port.write
├─ scoreboard ◄┘ ├─ scoreboard (analysis_imp)
└─ done event / counter └─ objections raised/dropped
│ │
▼ ▼
verdict banner + $finish run_phase ends → report_phaseInterview angle
"You have not used UVM — can you ramp?" — walk this table; every UVM concept has a twin you built by hand.
"What is a sequencer, really?" — the delivery half of your generator, with arbitration between competing sequences added.
"What do analysis ports buy over mailboxes?" — one-to-many broadcast without the producer knowing its consumers.
Key takeaways
Map components first: env→uvm_env, generator→sequence+sequencer, mailbox→seq_item_port, fanout→analysis ports.
The sequence/sequencer split separates stimulus content from delivery and arbitration.
The pull handshake gives completion feedback that a fire-and-forget mailbox cannot.
Objections are your done-counter, generalized to many components voting on end-of-test.
Common pitfalls
Treating UVM as alien — it is your architecture with standardized names; learn by mapping, not memorizing.
Equating the sequencer with the generator — stimulus content lives in the sequence, not the sequencer.
Assuming analysis ports block like mailboxes — write() is a non-blocking broadcast function call.
Forgetting the handshake is pull-based — the driver paces stimulus in UVM, not the sequence.