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":

diagram
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) gendrv           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 path

Two 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.

systemverilog
// 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
endtask

The two stacks, aligned

diagram
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_phase

Interview 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.