Part 6 · Testbench Architecture · Intermediate

Migrating a TB to UVM

A staged migration plan — wrap the transaction, componentize, adopt phases, factory and config db, then sequences — without stopping test runs.

Migrate in stages, never in one jump

The fatal migration mistake is the big-bang rewrite: stop the world, rewrite the TB in UVM, and try to bring everything back up at once. Weeks pass with zero working tests , and every bug could be in the rewrite or the RTL. The alternative is a staged migration where the TB runs its full test list after every stage — each stage is a refactor with a regression gate, not a leap of faith.

diagram
STAGED MIGRATION LADDER          (regression green after EVERY rung)

  stage 0   hand-built class TB, tests passing  ← start: a known-good base
     │
  stage 1   TXN: txn class extends uvm_sequence_item
     │          (field macros or do_copy/do_compare/convert2string)
  stage 2   COMPONENTS: drv/mon/scb extend uvm_component family;
     │          still wired by hand, still driven by your env
  stage 3   PHASES: new()/connect()/run() code moves into
     │          build_phase / connect_phase / run_phase; objections
     │          replace the done-counter
  stage 4   FACTORY + CONFIG DB: type_id::create everywhere,
     │          virtual interfaces via uvm_config_db, +UVM_TESTNAME
  stage 5   SEQUENCES: generator logic becomes sequences on a
     │          sequencer; mailbox handoff becomes seq_item_port
     ▼
  full UVM env — and tests ran at every stage

What to refactor first — and why this order

The order is dependency-driven

  1. Transaction first — every component touches it, and extending uvm_sequence_item changes no behavior; lowest risk, unblocks everything else.

  2. Components second — extending uvm_component buys reporting and hierarchy while your env still controls wiring; behavior unchanged.

  3. Phases third — this is the first behavioral change (who calls what, when); objections replace your end-of-test counter here.

  4. Factory and config db fourth — mechanical substitution of new() and constructor plumbing, enabling +UVM_TESTNAME test selection.

  5. Sequences last — the largest shape change (push mailbox to pull handshake); do it once everything around it is stable.

systemverilog
// Stage 1: the transaction — smallest possible first step
class bus_txn extends uvm_sequence_item;
  `uvm_object_utils(bus_txn)
  rand bit        write;
  rand bit [31:0] addr, data;

  function new(string name = "bus_txn");
    super.new(name);
  endfunction

  // your hand-written compare/print migrate nearly verbatim
  virtual function bit do_compare(uvm_object rhs, uvm_comparer comparer);
    bus_txn t;
    if (!$cast(t, rhs)) return 0;
    return (write == t.write) && (addr == t.addr) && (data == t.data);
  endfunction
endclass

// Stage 3: run() body moves into run_phase, done-counter → objection
task my_driver::run_phase(uvm_phase phase);
  forever begin
    // stage 3-4: still mailbox; stage 5 swaps to seq_item_port
    in_mb.get(t);
    drive_one(t);
  end
endtask

Keeping tests running, and the classic mistakes

The discipline that makes staged migration work: after every stage, run the full existing test list and require the same pass results before starting the next stage. Where a stage is long (sequences, typically), keep the old path alive briefly — a driver can accept a mailbox in stages 2–4 and switch to seq_item_port in stage 5 behind a config flag — so a mid-stage RTL bug can still be chased with a working TB.

Common migration mistakes

  • Big-bang rewrite — weeks of no working tests; every failure ambiguous between TB rewrite and RTL.

  • Migrating stimulus (sequences) first — the hardest change taken while everything else is also moving.

  • Dropping the scoreboard "temporarily" during migration — RTL bugs land unnoticed in exactly that window.

  • Skipping the regression gate between stages — errors compound across stages and unwind is impossible.

  • Rewriting working checker logic "the UVM way" for purity — do_compare can wrap your existing compare verbatim.

Interview angle

  • "How would you move your TB to UVM?" — stage ladder: transaction, components, phases, factory/config db, sequences; regression green after each rung.

  • "What would you migrate first and why?" — the transaction: everything depends on it and the change is behavior-neutral.

  • "Biggest migration risk?" — the window with no working tests; staged refactor with gates exists to keep that window at zero.

Key takeaways

  • Migrate up a stage ladder — transaction, components, phases, factory/config db, sequences.

  • Run the full test list after every stage; never start a stage from a red base.

  • Order is dependency-driven: behavior-neutral changes first, the pull-handshake shape change last.

  • Keep the old data path switchable until the new one is proven.

Common pitfalls

  • Big-bang rewrite — the no-working-tests window swallows the schedule.

  • Sequences first — maximum shape change with no stable base under it.

  • No scoreboard during migration — RTL regressions slip in silently.

  • Treating migration as a purity exercise — wrap proven logic, do not rewrite it.