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.
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.
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:
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).
| Property | Module-based | UVM class-based |
|---|---|---|
| Object lifetime | Static — whole simulation | Dynamic — created in build_phase, GC’d when done |
| Hierarchy construction | Fixed at elaboration | Runtime, top-down in build_phase |
| Component substitution | Requires recompile | Factory override at runtime or in test |
| Configuration | Parameters / defines (compile-time) | config_db (runtime), plusargs |
| Reuse across projects | Difficult — tight DUT coupling | Designed for reuse — UVC pattern |
| Signal access | Direct hierarchical path | Virtual interface through config_db |
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.
The single most important design decision in UVM is which base class to extend. Every class you write derives from one of these two.
env.agent.driver)`uvm_component_utilsnew(string name, uvm_component parent)Use for: driver, monitor, sequencer, agent, env, test, scoreboard, coverage collector
`uvm_object_utilsnew(string name = "classname")Use for: sequence items, sequences, configuration objects, register models
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
UVM defines a standard topology for a block-level testbench. Every component has a specific role and a specific place in the hierarchy.
| Component | Base class | Role |
|---|---|---|
my_test | uvm_test | Configure env, apply factory overrides, start root sequence |
my_env | uvm_env | Top-level container — creates and connects agents + analysis components |
my_agent | uvm_agent | Bundles sequencer + driver + monitor for one interface |
my_sequencer | uvm_sequencer | Arbitrates sequence items to the driver — active agents only |
my_driver | uvm_driver | Drives DUT signals via virtual interface — active agents only |
my_monitor | uvm_monitor | Observes DUT, assembles transactions, publishes to analysis port |
my_scoreboard | uvm_scoreboard | Receives transactions, compares against reference model |
Two separate data flows run concurrently during the run_phase. Understanding these flows is essential before writing any code.
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.
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 ①②③:
`uvm_component_utils registers the class so the factory can create it by name. Without this, run_test("my_test") fails silently.super.new(name, parent). The parent argument wires this component into the hierarchy so it has a path name and participates in phases.raise_objection, the run_phase ends immediately. The simulation exits before your code runs.build_phase top-down — test builds env, env builds agents, agents build driver/monitor/sequencerconnect_phase bottom-up — TLM ports are connected, virtual interfaces assignedrun_phase concurrently in all components that implement it$finish| Item | Key fact |
|---|---|
| UVM standard | IEEE 1800.2 — pure SystemVerilog class library |
| Core benefits | Reusability, scalability, configurability |
| uvm_component | Persistent — has phases, parent, path. Use for: driver, monitor, agent, env, test, scoreboard |
| uvm_object | Transient — no phases, no parent. Use for: sequence items, sequences, config objects |
| `uvm_component_utils | Macro that registers a component class with the factory — mandatory |
| `uvm_object_utils | Macro that registers an object class with the factory — mandatory |
| Component constructor | function new(string name, uvm_component parent) |
| Object constructor | function new(string name = "classname") — no parent |
| Simulation entry point | run_test("test_class_name") in initial block of SV top module |
| Objections | phase.raise_objection(this) / phase.drop_objection(this) control when run_phase ends |
| Import / include | import uvm_pkg::* and `include "uvm_macros.svh" needed in every file |
| Stimulus flow | Sequence → Sequencer → Driver → Virtual Interface → DUT |
| Observation flow | DUT → Virtual Interface → Monitor → Analysis Port → Scoreboard / Coverage |
| Component path example | uvm_test_top.env.agent.driver — used in config_db lookups |