The short answer: A UVM agent bundles everything for one interface: a sequencer and driver to create stimulus (in active mode) and a monitor to observe the interface (always). The driver pulls items from the sequencer and drives them through a virtual interface; the monitor samples the pins, rebuilds transactions and broadcasts them on an analysis port. A configuration object sets the agent active or passive, so the same agent can drive a block’s inputs at block level and just observe at chip level.
Agents are the reusable unit of UVM. A well-written agent for AXI or a UART can be dropped into any project. This post shows the three classes inside an agent and the agent itself, with the rules that make them reusable.
- What’s inside an agent
- The driver
- The monitor
- The agent
- Rules for reusable agents
Table of Contents
Part 1: What’s inside an agent

Part 2: The driver
class bus_driver extends uvm_driver #(bus_item);
`uvm_component_utils(bus_driver)
virtual bus_if vif;
function new(string name, uvm_component parent); super.new(name, parent); endfunction
task run_phase(uvm_phase phase);
vif.cb.valid <= 0;
forever begin
seq_item_port.get_next_item(req);
repeat (req.delay) @(vif.cb);
vif.cb.valid <= 1;
vif.cb.addr <= req.addr;
vif.cb.write <= (req.kind == bus_item::WRITE);
vif.cb.wdata <= req.data;
do @(vif.cb); while (!vif.cb.ready);
if (req.kind == bus_item::READ) req.data = vif.cb.rdata;
vif.cb.valid <= 0;
seq_item_port.item_done();
end
endtask
endclass
Part 3: The monitor
class bus_monitor extends uvm_monitor;
`uvm_component_utils(bus_monitor)
virtual bus_if vif;
uvm_analysis_port #(bus_item) ap;
function new(string name, uvm_component parent);
super.new(name, parent);
ap = new("ap", this);
endfunction
task run_phase(uvm_phase phase);
forever begin
@(vif.cb);
if (vif.cb.valid && vif.cb.ready) begin
bus_item t = bus_item::type_id::create("t");
t.kind = vif.cb.write ? bus_item::WRITE : bus_item::READ;
t.addr = vif.cb.addr;
t.data = vif.cb.write ? vif.cb.wdata : vif.cb.rdata;
ap.write(t);
end
end
endtask
endclass
A new object for every transaction matters: subscribers may keep the handle, so reusing one object would overwrite their data.
Part 4: The agent
class bus_agent extends uvm_agent;
`uvm_component_utils(bus_agent)
bus_agent_cfg cfg;
uvm_sequencer #(bus_item) sqr;
bus_driver drv;
bus_monitor mon;
function new(string name, uvm_component parent); super.new(name, parent); endfunction
function void build_phase(uvm_phase phase);
if (!uvm_config_db#(bus_agent_cfg)::get(this, "", "cfg", cfg))
`uvm_fatal("NOCFG", "agent config not set")
mon = bus_monitor::type_id::create("mon", this);
if (cfg.is_active == UVM_ACTIVE) begin
sqr = uvm_sequencer#(bus_item)::type_id::create("sqr", this);
drv = bus_driver::type_id::create("drv", this);
end
endfunction
function void connect_phase(uvm_phase phase);
mon.vif = cfg.vif;
if (cfg.is_active == UVM_ACTIVE) begin
drv.vif = cfg.vif;
drv.seq_item_port.connect(sqr.seq_item_export);
end
endfunction
endclass
Here the virtual interface travels inside the agent’s configuration object, which is a common and tidy pattern.
Part 5: Rules for reusable agents
- Monitors never drive anything and never depend on the driver; they must work in passive mode.
- No DUT-specific checking inside the agent; put it in the environment’s scoreboard.
- Protocol checks (assertions) belong in the interface, so they run in both modes.
- Everything configurable (active/passive, timeouts, coverage on/off) lives in the agent’s config object.
- Ship the agent with its item, basic sequences and a package, so it’s easy to import.
Practise this on VLSI Forge
I built VLSI Forge so you can write RTL in your browser, run it on a real simulator and check every signal in the waveform. Free, nothing to install.
Design Verification problems · Daily challenge
Previous in my UVM series: Sequences and sequencers. Next: Phases and objections.
FAQ
What is a UVM agent?
A component that groups a sequencer, driver and monitor for one interface, configurable as active (drives) or passive (observes only).
What is the difference between an active and a passive agent?
An active agent has a sequencer and driver and generates stimulus; a passive agent has only a monitor and observes.
Why should the monitor create a new object for every transaction?
Subscribers like scoreboards keep the handle. Reusing one object would change data they have already stored.
Where should protocol checks go?
Usually as assertions in the SystemVerilog interface, so they work whether the agent is active or passive.
