# UVM Testbench: Architecture, Components and an Example

> What a UVM testbench is, its architecture (test, env, agent, driver, monitor, scoreboard), uvm_object vs uvm_component, phases and a worked FIFO example.

Canonical: https://origindatapartners.com/guides/uvm-testbench-example
By: Origin Data Partners
Status: Published
Published: 2026-11-13
Updated: 2026-11-13
Sources checked: 2026-11-13

## Quick answer

A UVM testbench is a SystemVerilog verification environment built from the Universal Verification Methodology class library (IEEE 1800.2). A test configures an environment; agents drive and monitor each interface through a sequencer, driver and monitor; a scoreboard checks results; coverage collectors measure what was exercised. Reusable components and the factory let the same environment run many tests.

## Key points

- UVM is a standard (IEEE 1800.2) with a free reference library from Accellera.
- Components (uvm_component) form the fixed hierarchy; transactions and sequences (uvm_object) flow through it.
- Phases order the build, connection and run of every component.
- Scoreboards check correctness; coverage tells you whether you checked enough.

## What is a UVM testbench?

UVM is a standard: the "IEEE Standard for Universal Verification Methodology Language Reference Manual" ([IEEE 1800.2](https://standards.ieee.org/ieee/1800.2/7567/)), "Superseding 1800.2-2017". Accellera describes it as "a standard to enable guaranteed development and reuse of verification environments and verification IP (VIP) throughout the electronics industry" ([Accellera](https://www.accellera.org/community/uvm)) and publishes the "Library Code for IEEE 1800.2" ([Accellera downloads](https://www.accellera.org/downloads/standards/uvm)).

It is written in SystemVerilog, whose standard supports "writing test benches using coverage, assertions, object-oriented programming, and constrained random verification" ([IEEE 1800](https://standards.ieee.org/ieee/1800/7743/)). A UVM testbench uses those features through a common class library, so a driver or scoreboard written for one project can be reused on the next.


## What is the testbench architecture in UVM?

A UVM testbench is a tree of components built at the start of simulation. [Download the UVM testbench checklist (CSV)](https://origindatapartners.com/downloads/uvm-testbench-checklist.csv), one row per component with what to check before the first regression. From the top:

| Component | Base class | What it does |
| --- | --- | --- |
| Top module | SystemVerilog module (not a class) | Instantiates the DUT and interfaces, puts virtual interfaces into the configuration database, calls run_test() |
| Test | uvm_test | Picks the configuration and the sequences to run; one class per test scenario |
| Environment | uvm_env | Holds the agents, scoreboard and coverage collectors and connects them |
| Agent | uvm_agent | Groups the sequencer, driver and monitor for one interface; active agents drive, passive agents only watch |
| Sequencer | uvm_sequencer | Passes sequence items from running sequences to the driver |
| Driver | uvm_driver | Turns each sequence item into pin activity on the interface |
| Monitor | uvm_monitor | Watches the interface and rebuilds transactions, sent out through an analysis port |
| Scoreboard | uvm_scoreboard | Compares what the DUT did with what a reference model predicts |
| Coverage collector | uvm_subscriber (common choice) | Samples covergroups from monitored transactions |


## What is the difference between uvm_object and uvm_component?

A uvm_component is part of the testbench structure: it has a parent, a place in the hierarchy and phases, and it exists for the whole simulation. Drivers, monitors, agents and environments are components.

A uvm_object is data or configuration that is created, copied and passed around: sequence items (transactions), sequences and configuration objects. Objects have no phases and no fixed place in the tree. Both are registered with the factory, so a test can swap in a derived class without editing the environment.


## What are the key differences between a SystemVerilog testbench and a UVM testbench?

A plain SystemVerilog testbench can use classes, constrained random stimulus and covergroups, but each team invents its own structure. A UVM testbench uses the standard base classes, phases, configuration database, factory and reporting, so components from different projects and vendors fit together. The cost is more code up front; the gain is reuse and a structure any verification engineer can read.


## UVM testbench example: verifying a FIFO (complete code)

A complete, minimal example: an 8-deep synchronous FIFO, one agent that drives its write and read ports, a scoreboard with a queue as the reference model, a coverage collector and a test. The verification plan asks for correct data order, the full and empty flags, and no write accepted when full or read accepted when empty.

The code follows IEEE 1800.2 conventions: every class is registered with the factory (the uvm_object_utils and uvm_component_utils macros) and created with type_id::create, components get the virtual interface from uvm_config_db in build_phase, connect_phase wires the monitor's analysis ports to the scoreboard and coverage, and the test raises and drops an objection around its sequences. [Download the full testbench (.sv)](https://origindatapartners.com/downloads/uvm-fifo-testbench-example.sv); compile it with a simulator that ships the Accellera UVM library. It is an illustrative example, not verification IP.

| Piece | Class in the example | What it does |
| --- | --- | --- |
| Transaction | fifo_item extends uvm_sequence_item | One cycle of activity: op (write, read or idle) and data, weighted toward writes |
| Sequences | fill_drain_seq, random_rw_seq | Nine writes then nine reads (one past full, one past empty); then a random mix |
| Agent | fifo_agent (uvm_sequencer, fifo_driver, fifo_monitor) | The driver turns items into pin activity; the monitor reports each accepted write and read on two analysis ports |
| Scoreboard | fifo_scoreboard | Pushes each accepted write onto a queue; pops and compares on each read; fails if nothing was compared |
| Coverage | fifo_coverage extends uvm_subscriber | Fill level before each operation (empty, 1, mid, full-1, full) crossed with the operation |
| Test and top | fifo_random_test, tb_top | The test starts both sequences under an objection; the top module sets the virtual interface and calls run_test() |

DUT, interface, transaction and sequences:

```systemverilog
// fifo_uvm_example.sv: a complete, minimal UVM (IEEE 1800.2) testbench for an 8-deep synchronous FIFO.
// Illustrative example from origindatapartners.com/guides/uvm-testbench-example. Compile it with a simulator
// that ships the Accellera UVM library and select the test with +UVM_TESTNAME=fifo_random_test.
`timescale 1ns/1ps

// Design under test: first-word-fall-through FIFO, 8 entries of 8 bits.
module sync_fifo #(parameter int DEPTH = 8, parameter int WIDTH = 8) (
  input  logic             clk, rst_n,
  input  logic             wr_en, rd_en,
  input  logic [WIDTH-1:0] wdata,
  output logic [WIDTH-1:0] rdata,
  output logic             full, empty
);
  logic [WIDTH-1:0]         mem [DEPTH];
  logic [$clog2(DEPTH):0]   count;
  logic [$clog2(DEPTH)-1:0] wptr, rptr;
  assign full  = (count == DEPTH);
  assign empty = (count == 0);
  assign rdata = mem[rptr];
  always_ff @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
      wptr <= '0; rptr <= '0; count <= '0;
    end else begin
      if (wr_en && !full) begin
        mem[wptr] <= wdata;
        wptr      <= wptr + 1'b1;
      end
      if (rd_en && !empty) rptr <= rptr + 1'b1;
      count <= count + (wr_en && !full) - (rd_en && !empty);
    end
  end
endmodule

interface fifo_if (input logic clk);
  logic       rst_n, wr_en, rd_en, full, empty;
  logic [7:0] wdata, rdata;
endinterface

package fifo_pkg;
  `include "uvm_macros.svh"
  import uvm_pkg::*;

  typedef enum bit [1:0] {IDLE, WRITE, READ} fifo_op_e;

  // Transaction: one cycle of FIFO activity. A uvm_object, registered with the factory.
  class fifo_item extends uvm_sequence_item;
    rand fifo_op_e op;
    rand bit [7:0] data;
    constraint c_op { op dist {WRITE := 5, READ := 4, IDLE := 1}; }
    `uvm_object_utils_begin(fifo_item)
      `uvm_field_enum(fifo_op_e, op, UVM_ALL_ON)
      `uvm_field_int(data, UVM_ALL_ON)
    `uvm_object_utils_end
    function new(string name = "fifo_item");
      super.new(name);
    endfunction
  endclass

  // Nine writes (the last one meets a full FIFO), then nine reads (the last one meets an empty FIFO).
  class fill_drain_seq extends uvm_sequence #(fifo_item);
    `uvm_object_utils(fill_drain_seq)
    function new(string name = "fill_drain_seq");
      super.new(name);
    endfunction
    task body();
      repeat (9) send(WRITE);
      repeat (9) send(READ);
    endtask
    task send(fifo_op_e kind);
      fifo_item it = fifo_item::type_id::create("it");
      start_item(it);
      if (!it.randomize() with { op == kind; }) `uvm_error("SEQ", "randomize failed")
      finish_item(it);
    endtask
  endclass

  // Random mix of writes, reads and idle cycles.
  class random_rw_seq extends uvm_sequence #(fifo_item);
    `uvm_object_utils(random_rw_seq)
    rand int unsigned n_items = 200;
    function new(string name = "random_rw_seq");
      super.new(name);
    endfunction
    task body();
      repeat (n_items) begin
        fifo_item it = fifo_item::type_id::create("it");
        start_item(it);
        if (!it.randomize()) `uvm_error("SEQ", "randomize failed")
        finish_item(it);
      end
    endtask
  endclass
```

Driver, monitor and agent:

```systemverilog
  // Driver: turns each item into one clock cycle of pin activity; drives idle when no item is waiting.
  class fifo_driver extends uvm_driver #(fifo_item);
    `uvm_component_utils(fifo_driver)
    virtual fifo_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 fifo_if)::get(this, "", "vif", vif))
        `uvm_fatal("NOVIF", "virtual interface not set for the driver")
    endfunction
    task run_phase(uvm_phase phase);
      vif.wr_en <= 1'b0;
      vif.rd_en <= 1'b0;
      vif.wdata <= '0;
      wait (vif.rst_n === 1'b1);
      forever begin
        @(posedge vif.clk);
        seq_item_port.try_next_item(req);
        if (req == null) begin
          vif.wr_en <= 1'b0;
          vif.rd_en <= 1'b0;
        end else begin
          vif.wr_en <= (req.op == WRITE);
          vif.rd_en <= (req.op == READ);
          vif.wdata <= req.data;
          seq_item_port.item_done();
        end
      end
    endtask
  endclass

  // Monitor: samples the pins at each clock edge and reports accepted writes and reads on two analysis ports.
  class fifo_monitor extends uvm_monitor;
    `uvm_component_utils(fifo_monitor)
    virtual fifo_if vif;
    uvm_analysis_port #(fifo_item) wr_ap;  // each write the FIFO accepted
    uvm_analysis_port #(fifo_item) rd_ap;  // each word the FIFO gave out
    function new(string name, uvm_component parent);
      super.new(name, parent);
      wr_ap = new("wr_ap", this);
      rd_ap = new("rd_ap", this);
    endfunction
    function void build_phase(uvm_phase phase);
      super.build_phase(phase);
      if (!uvm_config_db#(virtual fifo_if)::get(this, "", "vif", vif))
        `uvm_fatal("NOVIF", "virtual interface not set for the monitor")
    endfunction
    task run_phase(uvm_phase phase);
      wait (vif.rst_n === 1'b1);
      forever begin
        @(posedge vif.clk);
        if (vif.wr_en && !vif.full) begin
          fifo_item t = fifo_item::type_id::create("wr");
          t.op   = WRITE;
          t.data = vif.wdata;
          wr_ap.write(t);
        end
        if (vif.rd_en && !vif.empty) begin
          fifo_item t = fifo_item::type_id::create("rd");
          t.op   = READ;
          t.data = vif.rdata;
          rd_ap.write(t);
        end
      end
    endtask
  endclass

  // Agent: sequencer, driver and monitor for the FIFO ports. A passive agent builds only the monitor.
  class fifo_agent extends uvm_agent;
    `uvm_component_utils(fifo_agent)
    uvm_sequencer #(fifo_item) sqr;
    fifo_driver                drv;
    fifo_monitor               mon;
    function new(string name, uvm_component parent);
      super.new(name, parent);
    endfunction
    function void build_phase(uvm_phase phase);
      super.build_phase(phase);
      mon = fifo_monitor::type_id::create("mon", this);
      if (get_is_active() == UVM_ACTIVE) begin
        sqr = uvm_sequencer #(fifo_item)::type_id::create("sqr", this);
        drv = fifo_driver::type_id::create("drv", this);
      end
    endfunction
    function void connect_phase(uvm_phase phase);
      if (get_is_active() == UVM_ACTIVE) drv.seq_item_port.connect(sqr.seq_item_export);
    endfunction
  endclass
```

Scoreboard, coverage, environment, test and top module:

```systemverilog
  `uvm_analysis_imp_decl(_wr)
  `uvm_analysis_imp_decl(_rd)

  // Scoreboard: a queue is the reference model. Push on each accepted write; pop and compare on each read.
  class fifo_scoreboard extends uvm_scoreboard;
    `uvm_component_utils(fifo_scoreboard)
    uvm_analysis_imp_wr #(fifo_item, fifo_scoreboard) wr_imp;
    uvm_analysis_imp_rd #(fifo_item, fifo_scoreboard) rd_imp;
    bit [7:0] model[$];
    int unsigned n_compared;
    function new(string name, uvm_component parent);
      super.new(name, parent);
      wr_imp = new("wr_imp", this);
      rd_imp = new("rd_imp", this);
    endfunction
    function void write_wr(fifo_item t);
      model.push_back(t.data);
    endfunction
    function void write_rd(fifo_item t);
      bit [7:0] exp;
      if (model.size() == 0) begin
        `uvm_error("SB", "Read observed while the model is empty")
        return;
      end
      exp = model.pop_front();
      n_compared++;
      if (t.data !== exp)
        `uvm_error("SB", $sformatf("Mismatch: expected %0h, got %0h", exp, t.data))
    endfunction
    function void check_phase(uvm_phase phase);
      if (n_compared == 0) `uvm_error("SB", "No reads were compared")
    endfunction
    function void report_phase(uvm_phase phase);
      `uvm_info("SB", $sformatf("%0d reads compared, %0d words left in the FIFO", n_compared, model.size()), UVM_LOW)
    endfunction
  endclass

  // Coverage: fill level before each accepted operation, crossed with the operation.
  class fifo_coverage extends uvm_subscriber #(fifo_item);
    `uvm_component_utils(fifo_coverage)
    int       level;
    fifo_op_e op;
    covergroup cg;
      cp_level: coverpoint level {
        bins empty = {0}; bins one = {1}; bins mid = {[2:6]}; bins almost_full = {7}; bins full = {8};
      }
      cp_op: coverpoint op { bins wr = {WRITE}; bins rd = {READ}; }
      level_x_op: cross cp_level, cp_op {
        ignore_bins no_write_when_full = binsof(cp_level.full)  && binsof(cp_op.wr);
        ignore_bins no_read_when_empty = binsof(cp_level.empty) && binsof(cp_op.rd);
      }
    endgroup
    function new(string name, uvm_component parent);
      super.new(name, parent);
      cg = new();
    endfunction
    function void write(fifo_item t);
      op = t.op;
      cg.sample();
      level += (t.op == WRITE) ? 1 : -1;
    endfunction
  endclass

  // Environment: builds the agent, scoreboard and coverage, and connects the monitor to both.
  class fifo_env extends uvm_env;
    `uvm_component_utils(fifo_env)
    fifo_agent      agent;
    fifo_scoreboard sb;
    fifo_coverage   cov;
    function new(string name, uvm_component parent);
      super.new(name, parent);
    endfunction
    function void build_phase(uvm_phase phase);
      super.build_phase(phase);
      agent = fifo_agent::type_id::create("agent", this);
      sb    = fifo_scoreboard::type_id::create("sb", this);
      cov   = fifo_coverage::type_id::create("cov", this);
    endfunction
    function void connect_phase(uvm_phase phase);
      agent.mon.wr_ap.connect(sb.wr_imp);
      agent.mon.rd_ap.connect(sb.rd_imp);
      agent.mon.wr_ap.connect(cov.analysis_export);
      agent.mon.rd_ap.connect(cov.analysis_export);
    endfunction
  endclass

  class fifo_base_test extends uvm_test;
    `uvm_component_utils(fifo_base_test)
    fifo_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 = fifo_env::type_id::create("env", this);
    endfunction
  endclass

  // The test raises an objection so run_phase lasts until both sequences finish, then lets the last cycles settle.
  class fifo_random_test extends fifo_base_test;
    `uvm_component_utils(fifo_random_test)
    function new(string name, uvm_component parent);
      super.new(name, parent);
    endfunction
    task run_phase(uvm_phase phase);
      fill_drain_seq fd = fill_drain_seq::type_id::create("fd");
      random_rw_seq  rw = random_rw_seq::type_id::create("rw");
      phase.raise_objection(this);
      fd.start(env.agent.sqr);
      if (!rw.randomize() with { n_items inside {[100:300]}; }) `uvm_error("TEST", "randomize failed")
      rw.start(env.agent.sqr);
      #100ns;
      phase.drop_objection(this);
    endtask
  endclass
endpackage

// Top: clock, reset, DUT, interface; the virtual interface goes into the configuration database; run_test starts UVM.
module tb_top;
  import uvm_pkg::*;
  import fifo_pkg::*;
  logic clk = 1'b0;
  always #5ns clk = ~clk;
  fifo_if vif (clk);
  sync_fifo dut (
    .clk(clk), .rst_n(vif.rst_n), .wr_en(vif.wr_en), .rd_en(vif.rd_en),
    .wdata(vif.wdata), .rdata(vif.rdata), .full(vif.full), .empty(vif.empty)
  );
  initial begin
    vif.rst_n = 1'b0;
    repeat (2) @(posedge clk);
    vif.rst_n = 1'b1;
  end
  initial begin
    uvm_config_db#(virtual fifo_if)::set(null, "uvm_test_top.*", "vif", vif);
    run_test("fifo_random_test");
  end
endmodule
```

Run order follows the phases: build_phase creates the components top-down, connect_phase links the monitor's analysis ports to the scoreboard and coverage, run_phase runs the sequences (the test raises an objection to keep simulation running and drops it when the sequences finish), and check_phase and report_phase print the scoreboard result. A bug such as the full flag rising one entry late shows up as a scoreboard mismatch at the full-1 coverage bin.


## Common UVM testbench mistakes

Most of these cost days, not minutes, because the testbench appears to pass.

- Forgetting to raise an objection, so run_phase ends at time zero and every test "passes".
- Scoreboards that only count transactions instead of comparing data against a reference model.
- Creating components with new() instead of the factory, which breaks overrides from the test.
- Getting virtual interfaces from the configuration database with a mismatched path or name.
- Covergroups that are never sampled, so coverage reports look empty or misleadingly full.


## What does a testbench's history show?

Kept in version control next to the RTL, a testbench records how verification grew: which sequences were added after which bug, which scoreboard checks were tightened and which coverage holes were closed. Linked to the bug tracker and regression results, it shows the reasoning behind each fix, which is the part new engineers learn from.


## Testbenches and verification IP as a data asset

A firm's own verification IP and testbenches, with their commit history and the bugs they found, are among the most reusable records a verification team keeps.

AI developers building chip design and verification tools ask for this kind of linked history from the firms that own it. Where a license is agreed, customer designs, foundry PDK content and third-party IP are removed first, and the export classification is checked before anything leaves the company.

If your company keeps years of design and verification records, the [guide for chip and electronics design firms](https://origindatapartners.com/guides/chip-design-firm-data) explains what AI developers ask for and who signs. To see what yours could be worth, [describe them in six questions](https://origindatapartners.com/check-fit?sector=chips); no files or system access needed.


## Questions and answers

### What is a UVM testbench used for?

Verifying a digital design in simulation with reusable components: agents drive and watch interfaces, a scoreboard checks results and coverage measures what was tested.

### Which standard defines UVM?

IEEE 1800.2. The 2020 edition supersedes 1800.2-2017, and Accellera publishes the reference library code.

### What are the main components of a UVM testbench?

Test, environment, agents (each with a sequencer, driver and monitor), scoreboard and coverage collectors, plus the top module that holds the DUT and interfaces.

### What is the difference between an active and a passive UVM agent?

An active agent has a sequencer and driver and drives the interface. A passive agent has only a monitor and watches, which is common for internal interfaces or when another block drives the bus.

### What are UVM phases?

Ordered steps every component goes through: build, connect, end of elaboration, start of simulation, run (with its sub-phases), extract, check, report and final.

### Why does my UVM test end at time zero?

Usually because no objection was raised in run_phase, so the phase ends immediately. Raise an objection before starting sequences and drop it when they finish.

### Is UVM only for ASIC verification?

No. It is used for FPGA and ASIC verification alike, wherever a SystemVerilog simulator with UVM support is available.

## Sources

- [IEEE 1800.2-2020: Standard for Universal Verification Methodology](https://standards.ieee.org/ieee/1800.2/7567/)
- [Accellera: UVM downloads and reference implementation](https://www.accellera.org/downloads/standards/uvm)
- [Accellera: UVM community](https://www.accellera.org/community/uvm)
- [IEEE 1800-2023: Standard for SystemVerilog](https://standards.ieee.org/ieee/1800/7743/)

## Related reading

- [Chip Design Data for AI: What Design Firms Can License](https://origindatapartners.com/guides/chip-design-firm-data)
- [Design Verification Plan Template: Coverage and Sign-off](https://origindatapartners.com/guides/design-verification-plan-template)
- [Tapeout: What It Means, Plus a Tapeout Checklist (CSV)](https://origindatapartners.com/guides/tapeout-checklist)
- [License Semiconductor Process and Design Data for AI](https://origindatapartners.com/guides/license-semiconductor-process-data-ai)
- [Gerber Files and PCB Design Data: What Altium and KiCad Hold](https://origindatapartners.com/guides/altium-kicad-pcb-design-data-ai)
- [/glossary/uvm](https://origindatapartners.com/glossary/uvm)
