UVM Foundations
What UVM is, the factory, phasing, config DB, and your first sequence item and sequence.
The Universal Verification Methodology
UVM is a SystemVerilog class library and set of conventions for building reusable verification components. Before UVM, every team re-invented its own testbench architecture. UVM standardized that architecture so an agent written for one project can drop into another.
UVM is not a language feature. It's a library: you import it, extend its base classes, and rely on its factory and phasing machinery.
The three base classes
Almost everything in UVM inherits from one of three base classes:
uvm_object— data containers (transactions, configs, sequences).uvm_component— long-lived actors in the hierarchy (drivers, monitors, scoreboards, agents, env, tests).uvm_transaction— legacy; in modern UVM useuvm_sequence_item.
Registration and type substitution
UVM's factory is a type registry. You register every class, and the factory constructs instances by type name — which means you can replace any type with a subtype from outside the class, without editing the class.
class axi_driver extends uvm_driver#(axi_txn); `uvm_component_utils(axi_driver) // register with factory function new(string name, uvm_component parent); super.new(name, parent); endfunction endclass class axi_txn extends uvm_sequence_item; `uvm_object_utils(axi_txn) // register data type endclass
Use uvm_component_utils for components (anything with a parent in the test hierarchy). Use uvm_object_utils for data objects (transactions, sequences, configs).
Type overrides
From a test, you can tell the factory to create a subclass wherever the base class was requested:
class error_test extends base_test; function void build_phase(uvm_phase phase); super.build_phase(phase); axi_txn::type_id::set_type_override(bad_axi_txn::get_type()); // now every axi_txn created is actually a bad_axi_txn endfunction endclass
The build/run schedule UVM runs for you
UVM runs every component through a sequence of phases. You override the phase methods you care about; UVM calls them in order.
| Phase | Use for |
|---|---|
build_phase | Create child components. Get config from uvm_config_db. |
connect_phase | Connect TLM ports and exports. |
end_of_elaboration | Final checks before simulation starts. |
run_phase | The simulation itself. Tasks here consume time. |
extract / check / report | Post-sim analysis and pass/fail reporting. |
build_phase top-down — parents build their children first. It calls connect_phase bottom-up — leaves connect first, then parents wire them together.Passing config from top to deep children
The uvm_config_db is a keyed registry for runtime configuration. A test sets a value under a scope path; any component in that scope can get it during build.
// In top-level tb module: initial begin uvm_config_db#(virtual axi_lite_if)::set( null, "uvm_test_top.env.agent*", "vif", dut_if); run_test("base_test"); end // In the agent's build_phase: if (!uvm_config_db#(virtual axi_lite_if)::get( this, "", "vif", vif)) `uvm_fatal("AGENT", "no virtual interface supplied")
The most common use is handing a virtual interface from the top-level module (where the actual interface is instanced) down to the driver and monitor deep inside the env.
The unit of transaction-level stimulus
A uvm_sequence_item is a data class carrying everything the driver needs to perform one transaction. It's a bag of rand fields plus the methods UVM needs (convert2string, do_copy, do_compare).
class axi_txn extends uvm_sequence_item; rand bit[31:0] addr; rand bit[31:0] data; rand bit is_write; axi_resp_e resp; `uvm_object_utils_begin(axi_txn) `uvm_field_int(addr, UVM_ALL_ON) `uvm_field_int(data, UVM_ALL_ON) `uvm_field_int(is_write, UVM_ALL_ON) `uvm_field_enum(axi_resp_e, resp, UVM_ALL_ON) `uvm_object_utils_end function new(string name = "axi_txn"); super.new(name); endfunction endclass
The uvm_field_* macros generate copy, compare, pack, print for free. Heavy macro magic but effective.
A sequence drives items at a sequencer
A sequence is a stream of sequence items. It runs on a sequencer, which hands items to the driver.
class axi_basic_seq extends uvm_sequence#(axi_txn); `uvm_object_utils(axi_basic_seq) function new(string name = "axi_basic_seq"); super.new(name); endfunction task body(); repeat (10) begin `uvm_do_with(req, { req.is_write == 1; req.addr[1:0] == 0; }) end endtask endclass
The `uvm_do_with macro creates, randomizes with the given inline constraint, sends to the driver, and waits for completion — all in one line.