UVM Tutorial: Structure, Hierarchy and First Steps

If you have written a few SystemVerilog testbenches and found that every new project means re-inventing drivers, checkers and stimulus from scratch, this uvm tutorial is for you. The Universal Verification Methodology (UVM) is the industry-standard way to build verification environments that are reusable, scalable and readable by any engineer who knows the framework. This guide explains what UVM is, why it exists on top of plain SystemVerilog, how its testbench hierarchy fits together, and a practical roadmap to learn it.

A quick, honest note before we start: the UVM examples below are written for a UVM-capable simulator such as industry-standard simulation tools or industry-standard simulation tools (or the free EDA Playground, which hosts them online). They depend on the UVM base-class library, so they will not compile on free tools like Icarus Verilog or Verilator. Treat them as canonical structure you read and adapt, not as code to run locally as-is. The single plain-SystemVerilog example near the end is self-contained and runs anywhere.

What is UVM?

UVM is a class library plus a set of conventions, written in SystemVerilog and standardised as IEEE 1800.2. It gives you ready-made base classes — uvm_component, uvm_object, uvm_driver, uvm_monitor and more — together with rules for how objects are built, connected, run and reported. You do not verify a chip “in UVM” the way you code in C; instead you extend these base classes to model your own design’s stimulus and checking.

Three ideas sit at its core: components (long-lived structural blocks such as drivers and monitors), transactions (short-lived data objects describing one unit of activity, like a single bus read), and sequences (the programs that generate streams of transactions). Everything else is machinery to connect these cleanly.

Why UVM over plain SystemVerilog testbenches?

Plain SystemVerilog is perfectly capable of verifying a small block. The trouble appears as designs and teams grow:

  • Reuse: a hand-rolled testbench for a FIFO rarely transplants to the next project. UVM components are parameterised and factory-created, so an agent written for an SPI master can be reused across several SoCs.
  • Separation of concerns: UVM pushes you to keep stimulus (sequences) apart from testbench structure (components). You can run a hundred different tests against the same environment by swapping sequences, without touching the wiring.
  • Shared vocabulary: every verification engineer recognises a “driver”, “sequencer” and “scoreboard”. A new teammate reads an unfamiliar UVM environment far faster than a bespoke one.
  • Built-in services: a configuration database, severity-based reporting, ordered phasing, and a factory for run-time type overrides all come for free.

The cost is a learning curve and some boilerplate. For a one-off college assignment, plain SystemVerilog may still be the right tool; for anything you expect to maintain or reuse, UVM pays back quickly.

The UVM testbench hierarchy

A UVM environment is a tree of components. From top to bottom the usual chain is test → env → agent → driver / monitor / sequencer. Understanding who owns whom is the single most important thing in this uvm tutorial.

ComponentRole
uvm_testTop-level controller; chooses the configuration and the sequence to run.
uvm_envContainer that groups one or more agents plus the scoreboard.
uvm_agentBundles the components for one interface or protocol.
uvm_sequencerArbitrates and forwards transactions from sequences to the driver.
uvm_driverConverts transactions into pin-level activity on the DUT interface.
uvm_monitorObserves DUT pins and reconstructs transactions for checking.
uvm_scoreboardCompares observed results against expected behaviour.

Data flows like this: a sequence runs on the sequencer and produces transactions; the sequencer hands each one to the driver, which wiggles the DUT pins through a virtual interface; the monitor independently watches those same pins and sends reconstructed transactions to the scoreboard for checking. The driver never talks to the monitor — that independence is exactly what makes the check trustworthy.

The transaction

Start at the bottom: the data object that flows through the environment. It extends uvm_sequence_item and declares the fields one operation needs.

class alu_item extends uvm_sequence_item;
  rand bit [7:0] a, b;
  rand bit [2:0] op;
  bit  [8:0] result;

  `uvm_object_utils_begin(alu_item)
    `uvm_field_int(a,  UVM_DEFAULT)
    `uvm_field_int(b,  UVM_DEFAULT)
    `uvm_field_int(op, UVM_DEFAULT)
  `uvm_object_utils_end

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

The driver

The driver pulls a transaction from the sequencer and converts it into signal activity on a virtual interface. Note how it fetches that interface from the config database during build_phase.

class alu_driver extends uvm_driver #(alu_item);
  `uvm_component_utils(alu_driver)
  virtual alu_if vif;

  function new(string name, uvm_component parent);
    super.new(name, parent);
  endfunction

  function void build_phase(uvm_phase phase);
    super.build_phase(phase);
    if (!uvm_config_db#(virtual alu_if)::get(this, "", "vif", vif))
      `uvm_fatal("NOVIF", "virtual interface not set for driver")
  endfunction

  task run_phase(uvm_phase phase);
    forever begin
      seq_item_port.get_next_item(req);
      vif.a  <= req.a;
      vif.b  <= req.b;
      vif.op <= req.op;
      @(posedge vif.clk);
      seq_item_port.item_done();
    end
  endtask
endclass

Agent, env and test

The agent groups the driver, monitor and sequencer for one interface and connects them. The env holds agents plus the scoreboard, and the test sits on top. Every child is created with type_id::create — the factory call that lets you override types later without editing this code.

class alu_agent extends uvm_agent;
  `uvm_component_utils(alu_agent)
  alu_driver    drv;
  alu_monitor   mon;
  alu_sequencer seqr;

  function new(string name, uvm_component parent);
    super.new(name, parent);
  endfunction

  function void build_phase(uvm_phase phase);
    super.build_phase(phase);
    drv  = alu_driver::type_id::create("drv",  this);
    mon  = alu_monitor::type_id::create("mon",  this);
    seqr = alu_sequencer::type_id::create("seqr", this);
  endfunction

  function void connect_phase(uvm_phase phase);
    drv.seq_item_port.connect(seqr.seq_item_export);
  endfunction
endclass

class alu_env extends uvm_env;
  `uvm_component_utils(alu_env)
  alu_agent      agt;
  alu_scoreboard sb;

  function new(string name, uvm_component parent);
    super.new(name, parent);
  endfunction

  function void build_phase(uvm_phase phase);
    super.build_phase(phase);
    agt = alu_agent::type_id::create("agt", this);
    sb  = alu_scoreboard::type_id::create("sb", this);
  endfunction
endclass

class alu_base_test extends uvm_test;
  `uvm_component_utils(alu_base_test)
  alu_env env;

  function new(string name, uvm_component parent);
    super.new(name, parent);
  endfunction

  function void build_phase(uvm_phase phase);
    super.build_phase(phase);
    env = alu_env::type_id::create("env", this);
  endfunction
endclass

A runnable illustration: UVM phasing

One concept you can feel without the UVM library is phasing. UVM runs every component through the same ordered phases — build, then connect, then run, then report — and each phase completes everywhere before the next begins. The plain-SystemVerilog sketch below imitates that ordering with simple tasks, so it compiles and runs on any simulator and prints the sequence. It uses no UVM classes at all.

module tb;
  // Illustrates UVM's phased execution: phases run in a fixed order,
  // and each one finishes before the next one starts.

  task automatic build_phase;
    $display("[build]   construct components, read configuration");
  endtask

  task automatic connect_phase;
    $display("[connect] wire driver/monitor to sequencer and scoreboard");
  endtask

  task automatic run_phase;
    $display("[run]     drive stimulus and sample the DUT");
  endtask

  task automatic report_phase;
    $display("[report]  print the pass/fail summary");
  endtask

  initial begin
    build_phase;
    connect_phase;
    run_phase;
    report_phase;
    $display("All phases complete.");
    $finish;
  end
endmodule

[build]   construct components, read configuration
[connect] wire driver/monitor to sequencer and scoreboard
[run]     drive stimulus and sample the DUT
[report]  print the pass/fail summary
All phases complete.
/var/folders/l0/gs5hnkm56g54bmxg32r6nnwr0000gn/T/tmpltit571l/tb.sv:27: $finish called at 0 (1s)

Real UVM phasing adds much more — run_phase is time-consuming and uses objections to decide when the test ends — but the ordering principle is exactly what you see above.

A roadmap to learn UVM

  1. Solidify SystemVerilog OOP — classes, inheritance, virtual methods, parameterised classes and interfaces. UVM is only as clear as your grasp of these.
  2. Build one agent by hand — transaction, sequencer, driver and monitor for a simple interface such as an ALU or a counter. Resist copy-paste; type it out.
  3. Add the env and scoreboard — connect the monitor to a scoreboard through analysis ports and get a self-checking test passing.
  4. Learn the factory and config_db — use type_id::create everywhere, pass the virtual interface through uvm_config_db, then practise overriding one component type from the test.
  5. Write layered sequences — directed first, then randomised, then constrained-random with functional coverage. This is where real verification value lives.
  6. Study phasing and objections — understand how run_phase ends via raised and dropped objections, and when to use the other phases.

Work through these on a real DUT, however small, and read other engineers’ environments as you go. UVM rewards hands-on repetition far more than passive reading.

Quick check

Why use UVM instead of a plain SystemVerilog testbench?

For reuse of parameterised, factory-built components; separation of stimulus (sequences) from testbench structure (components); a shared vocabulary every engineer recognises; and built-in services such as the config database, reporting, phasing and the factory.

Which component turns transactions into pin activity, and which reconstructs transactions from pins?

The driver converts transactions into pin-level signals on the DUT interface. The monitor independently observes those pins and reconstructs transactions, sending them to the scoreboard to check.

What is the ordering of the UVM phases shown in the runnable example?

Build, then connect, then run, then report. Each phase completes across all components before the next phase begins.

Why doesn’t the driver communicate with the monitor directly?

Keeping them independent means the monitor observes the DUT without being influenced by what the driver intended, so the scoreboard’s comparison is a genuine, trustworthy check rather than a self-fulfilling one.

Have questions about this topic?
Share your question in comments or talk to our mentor team for batch guidance.
▶ Try it in the ChipXpert Lab

Compile and simulate this in the browser — no install. Edit the code and re-run instantly.

Open in Verification Lab

Learn Verilog with mentors, labs and placement support

This free series is maintained by the ChipXpert training team in Hyderabad & Bengaluru. Go deeper with instructor-led courses on real EDA tools.

Explore VLSI Courses
Next batch starts 18th November 2026
Start your VLSI career with ChipXpert

Live online and classroom batches in Hyderabad & Bengaluru. Fill in your details and a counsellor will call you back.

  • Real EDA tools in your browser: industry-standard EDAReal EDA tools in your browser
  • Recorded sessions on the elearn portalRecorded sessions
  • Placement assistance: resume, mock interviewsPlacement assistance
  • Merit scholarship up to 60% · EMI optionsScholarship up to 60%
Hi! Ask me about courses, fees, batches or discounts. ×
BENGALURU
HYDERABAD