UVM-01: Introduction to UVM — VLSI Trainers
VLSI Trainers UVM Series · UVM-01
UVM Series · UVM-01

Introduction to UVM

What UVM is, why it was created, how it differs from module-based testbenches, the uvm_component vs uvm_object distinction, the standard testbench topology, and your first working UVM component.

📋 What is UVM?

UVM (Universal Verification Methodology) is a standardised SystemVerilog class library for building structured, reusable, and scalable verification testbenches. It is defined by the Accellera IEEE 1800.2 standard and is the dominant verification methodology in the semiconductor industry — used across ASIC, FPGA, and IP verification at every level from block to chip.

At its core, UVM is a collection of base classes — around 100 — that you extend to create testbench components. The library provides infrastructure that you would otherwise have to build from scratch on every project: component lifecycle management, a phase execution engine, a factory for component construction, a configuration database, a TLM connection framework, a register abstraction layer, and a reporting system.

UVM does not verify your design for you. It gives you a consistent, proven framework inside which your verification code lives. Every engineer who knows UVM can pick up any UVM testbench and understand its structure immediately — the same way any C++ engineer can navigate any C++ codebase.

UVM is a library, not a language. It is pure SystemVerilog. Any IEEE 1800 compliant simulator — ModelSim, VCS, Xcelium, Riviera-PRO — runs UVM testbenches without modification. You do not need a special compiler option or simulator mode.

📋 Why UVM — The Problem it Solves

Before UVM, every verification team built their own testbench infrastructure from scratch. The result was thousands of incompatible, non-reusable frameworks across the industry. An IP block verified at one company could not have its testbench reused at another company using a different framework.

UVM solves three specific problems:

UVM — Three Core Benefits Reusability Write a UVC once for APB Reuse at block, chip, system level across multiple projects Scalability Block → Integration → Chip Same patterns at every level Add interface → add agent Configurability Factory swaps component types config_db passes settings down No testbench edits per test
Figure 1 — The three core benefits of UVM. Each addresses a specific failure mode of pre-standard testbench development: incompatible one-off frameworks (reusability), single-level monolithic environments (scalability), and hard-coded test configurations (configurability).

📋 Module vs Class Based Testbenches

To understand why UVM structures testbenches the way it does, you need to understand the fundamental difference between a module-based testbench (Verilog style) and a class-based testbench (UVM style).

Module-Based (Verilog) vs Class-Based (UVM) Testbench Module-Based Testbench tb_top (module) — static, loaded at elaboration stimulus (module) checker (module) DUT (module) ✗ Static — all modules exist for entire simulation ✗ No factory — cannot swap components without recompile ✗ Hard to reuse — tightly coupled to this DUT UVM Class-Based Testbench my_test extends uvm_test — constructed dynamically my_env extends uvm_env my_agent my_scoreboard ✓ Dynamic — classes constructed in build_phase at runtime ✓ Factory — swap any component without recompile ✓ Reusable — UVC plug-in pattern across projects
Figure 2 — Module-based vs class-based testbench. In a module-based testbench all components are static — they exist for the entire simulation as fixed modules. In a UVM class-based testbench, components are objects constructed dynamically in the build_phase, enabling the factory to substitute any component type at runtime without recompiling.
PropertyModule-basedUVM class-based
Object lifetimeStatic — whole simulationDynamic — created in build_phase, GC’d when done
Hierarchy constructionFixed at elaborationRuntime, top-down in build_phase
Component substitutionRequires recompileFactory override at runtime or in test
ConfigurationParameters / defines (compile-time)config_db (runtime), plusargs
Reuse across projectsDifficult — tight DUT couplingDesigned for reuse — UVC pattern
Signal accessDirect hierarchical pathVirtual interface through config_db

📋 The UVM Class Library

The UVM library contains around 100 classes organised in a deep inheritance hierarchy. In practice, a complete testbench uses around a dozen base classes. The key structural split is between two branches of the hierarchy: uvm_component and uvm_object.

UVM Class Hierarchy — Classes You Use Most uvm_void uvm_object uvm_component uvm_transaction uvm_sequence_item uvm_sequence uvm_reg uvm_test uvm_env uvm_agent uvm_driver uvm_monitor uvm_sequencer uvm_object — Transient data • No phases, no parent, no path name • Sequence items, sequences, config, register model uvm_component — Persistent infrastructure • Has phases, parent pointer, dot-path name • Driver, monitor, agent, env, scoreboard, test
Figure 3 — UVM class hierarchy. Everything derives from uvm_void, then splits into two branches. uvm_object (green): transient data — sequence items, sequences, config objects, register models. uvm_component (blue): persistent infrastructure — has phases, parent-child relationships, and hierarchical path names. Every class you write will extend one of these two.

📋 uvm_component vs uvm_object

The single most important design decision in UVM is which base class to extend. Every class you write derives from one of these two.

uvm_component — Persistent infrastructure

  • Has phases (build, connect, run, report…)
  • Has a parent in the component hierarchy
  • Has a dot-path name (e.g. env.agent.driver)
  • Registered with `uvm_component_utils
  • Constructor: new(string name, uvm_component parent)

Use for: driver, monitor, sequencer, agent, env, test, scoreboard, coverage collector

uvm_object — Transient data

  • No phases — no build_phase, no run_phase
  • No parent in the hierarchy
  • Has utility methods: do_copy, do_compare, do_print
  • Registered with `uvm_object_utils
  • Constructor: new(string name = "classname")

Use for: sequence items, sequences, configuration objects, register models

The golden rule: if it has phases, extend uvm_component. If it carries data between components, extend uvm_object. Getting this wrong is the most common UVM mistake. A driver must be a component (it has a run_phase that runs forever). A transaction must be an object (it is created hundreds of times and passed between components).
// ── uvm_component — persistent infrastructure ─────────────
class my_driver extends uvm_driver#(my_seq_item);
  `uvm_component_utils(my_driver)   // register with factory

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

  task run_phase(uvm_phase phase);  // components have phases
    // drive DUT signals forever
  endtask
endclass

// ── uvm_object — transient data ───────────────────────────
class my_seq_item extends uvm_sequence_item;
  `uvm_object_utils_begin(my_seq_item)
    `uvm_field_int(addr, UVM_ALL_ON)
    `uvm_field_int(data, UVM_ALL_ON)
  `uvm_object_utils_end

  rand logic [31:0] addr;
  rand logic [31:0] data;

  function new(string name = "my_seq_item");
    super.new(name);               // no parent — it's an object
  endfunction
endclass

📋 Standard Testbench Topology

UVM defines a standard topology for a block-level testbench. Every component has a specific role and a specific place in the hierarchy.

Standard UVM Testbench Topology SV Top Module — DUT + interface instantiation, run_test() my_test extends uvm_test — configures env, starts root sequence my_env extends uvm_env — top-level container my_agent extends uvm_agent Sequencer uvm_sequencer arbitrates items to driver Driver uvm_driver gets items drives DUT Monitor uvm_monitor observes DUT publishes txns Scoreboard uvm_scoreboard receives from monitor compares actual vs expected Coverage uvm_subscriber samples cover- groups from analysis port analysis_port DUT — connected via SystemVerilog interface + virtual interface handle
Figure 4 — Standard UVM testbench topology. The SV top module instantiates the DUT and interface, then calls run_test(). The test creates the env, which creates agents, scoreboards, and coverage collectors. Inside the agent: the sequencer arbitrates sequence items to the driver, which drives the DUT via a virtual interface. The monitor observes the DUT and broadcasts transactions to the scoreboard and coverage collector via analysis ports.
ComponentBase classRole
my_testuvm_testConfigure env, apply factory overrides, start root sequence
my_envuvm_envTop-level container — creates and connects agents + analysis components
my_agentuvm_agentBundles sequencer + driver + monitor for one interface
my_sequenceruvm_sequencerArbitrates sequence items to the driver — active agents only
my_driveruvm_driverDrives DUT signals via virtual interface — active agents only
my_monitoruvm_monitorObserves DUT, assembles transactions, publishes to analysis port
my_scoreboarduvm_scoreboardReceives transactions, compares against reference model

📋 Stimulus and Observation Flow

Two separate data flows run concurrently during the run_phase. Understanding these flows is essential before writing any code.

Two Concurrent Data Flows During run_phase STIMULUS FLOW (downward) Test Sequence Sequencer Driver Virtual Interface DUT items items signals OBSERVATION FLOW (upward) DUT Virtual Interface Monitor Analysis Port SB / Coverage signals→txn broadcast
Figure 5 — Two concurrent flows during run_phase. The stimulus flow carries sequence items down through the sequencer-driver path onto the DUT as signal-level activity. The observation flow captures DUT signal activity in the monitor, assembles transactions, and broadcasts them up to the scoreboard and coverage collector via analysis ports. These two flows are completely independent.

These two flows run concurrently and are completely independent of each other. The stimulus flow is active — it drives the DUT. The observation flow is passive — it only observes. This separation is why a monitor can be reused in a passive agent: remove the sequencer and driver, keep the monitor.

📋 Your First UVM Component

The minimum viable UVM testbench — a test that prints a message and exits cleanly. This introduces all the mandatory boilerplate you will write for every component.

// ── Import and include — required in every UVM file ──────
import uvm_pkg::*;
`include "uvm_macros.svh"

// ── Minimum UVM test ──────────────────────────────────────
class my_test extends uvm_test;
  `uvm_component_utils(my_test)   // ① register with factory

  function new(string name, uvm_component parent);
    super.new(name, parent);       // ② mandatory constructor
  endfunction

  task run_phase(uvm_phase phase);
    phase.raise_objection(this);   // ③ keep sim alive
    `uvm_info("TEST", "Hello from UVM!", UVM_LOW)
    #100ns;
    phase.drop_objection(this);   // ④ allow sim to end
  endtask
endclass

// ── SV top module ─────────────────────────────────────────
module tb_top;
  import uvm_pkg::*;
  `include "uvm_macros.svh"
  `include "my_test.sv"

  initial run_test("my_test");  // construct and run my_test
endmodule

Three mandatory elements in every UVM component are highlighted with ①②③:

  1. Factory registration`uvm_component_utils registers the class so the factory can create it by name. Without this, run_test("my_test") fails silently.
  2. Constructor — must call super.new(name, parent). The parent argument wires this component into the hierarchy so it has a path name and participates in phases.
  3. Objection — without raise_objection, the run_phase ends immediately. The simulation exits before your code runs.

What run_test() does step by step

  1. Asks the factory to create an instance of the named test class
  2. Calls build_phase top-down — test builds env, env builds agents, agents build driver/monitor/sequencer
  3. Calls connect_phase bottom-up — TLM ports are connected, virtual interfaces assigned
  4. Calls run_phase concurrently in all components that implement it
  5. Waits until all objections are dropped, then runs post-run phases and calls $finish

📋 Quick Reference

ItemKey fact
UVM standardIEEE 1800.2 — pure SystemVerilog class library
Core benefitsReusability, scalability, configurability
uvm_componentPersistent — has phases, parent, path. Use for: driver, monitor, agent, env, test, scoreboard
uvm_objectTransient — no phases, no parent. Use for: sequence items, sequences, config objects
`uvm_component_utilsMacro that registers a component class with the factory — mandatory
`uvm_object_utilsMacro that registers an object class with the factory — mandatory
Component constructorfunction new(string name, uvm_component parent)
Object constructorfunction new(string name = "classname") — no parent
Simulation entry pointrun_test("test_class_name") in initial block of SV top module
Objectionsphase.raise_objection(this) / phase.drop_objection(this) control when run_phase ends
Import / includeimport uvm_pkg::* and `include "uvm_macros.svh" needed in every file
Stimulus flowSequence → Sequencer → Driver → Virtual Interface → DUT
Observation flowDUT → Virtual Interface → Monitor → Analysis Port → Scoreboard / Coverage
Component path exampleuvm_test_top.env.agent.driver — used in config_db lookups
Scroll to Top