In short: An active UVM agent builds and runs a sequencer and driver to stimulate the DUT, while a passive agent builds only the monitor to observe a bus, and the is_active field (UVM_ACTIVE or UVM_PASSIVE) decides which set gets created during build_phase.
Why one agent class serves two jobs
Every interface in a testbench needs a reusable verification component. The same AXI, APB or I2C bus is sometimes driven by the testbench and sometimes merely watched. Think of a block-level bench where your env plays the CPU and drives the peripheral, versus an SoC-level bench where a real CPU RTL model already drives that bus and you only want protocol checking and coverage.
Rather than write two components, UVM lets you write one agent and switch its behaviour with a single configuration value. That is the entire idea behind the active and passive split. For the broader architecture of agents, see our guide on active vs passive agents in UVM, and for the foundations, what UVM is.
What is inside the agent
A standard agent contains three children: a sequencer, a driver and a monitor. The monitor is always present because observation is needed in every mode. The sequencer and driver exist only when the agent must generate pin-level activity.
How is_active controls construction
The agent base class uvm_agent carries a field named is_active of type uvm_active_passive_enum. Its default is UVM_ACTIVE. Inside build_phase, you create the monitor unconditionally and gate the other two on get_is_active(). Because building happens top-down in the UVM phases, the value must be set before the agent's build_phase runs, usually from the env or the test through uvm_config_db.
class apb_agent extends uvm_agent;
`uvm_component_utils(apb_agent)
apb_monitor mon;
apb_sequencer sqr;
apb_driver drv;
function void build_phase(uvm_phase phase);
super.build_phase(phase);
mon = apb_monitor::type_id::create("mon", this);
if (get_is_active() == UVM_ACTIVE) begin
sqr = apb_sequencer::type_id::create("sqr", this);
drv = apb_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
// in the env or test
uvm_config_db#(uvm_active_passive_enum)::set(this, "apb_agt", "is_active", UVM_PASSIVE);
Key fact: the connect_phase must use the same guard as build_phase. Connecting drv when it is null is the most common cause of a null-object fatal in the first second of simulation.
The guard must appear twice, not once
Candidates often remember the build guard and forget two other places. First, the connect_phase shown above. Second, any virtual sequence or test code that refers to env.agt.sqr. In a passive configuration that handle is null, so a virtual sequencer should only collect handles for agents that are active. A cleaner pattern is to keep sequencer handles inside the virtual sequencer and assign them under the same condition.
Where passive agents earn their place
- Block to SoC reuse. A UART agent is active on the block bench, then flipped to passive at chip level where the real UART traffic is generated by firmware running on the CPU.
- Output interfaces. The DUT's response port (for example the read-data side of a DMA) is nearly always passive: nobody drives it, but the scoreboard needs its transactions.
- Back-to-back or loopback. When two DUT ports are wired together, only one side gets an active agent; the other is monitored.
- Protocol checkers. Assertion-heavy monitors for AMBA or PCIe can run passively in emulation on Synopsys ZeBu or Cadence Palladium, where the driver would be a testbench-to-hardware bottleneck.
Seeing the data flow
The animation below shows an active agent: a transaction leaves the sequencer, crosses the driver handshake, becomes pin activity on the interface, and is re-observed by the monitor, which publishes it through an analysis port. The pull model between sequencer and driver is detailed in the driver-sequencer handshake article.
Comparison at a glance
| Aspect | Active | Passive |
|---|---|---|
| Components built | Sequencer, driver, monitor | Monitor only |
| Drives interface | Yes (modport or clocking block) | No |
| Typical use | Block level, stimulus | SoC level, output ports, checking |
| Sequences can run on it | Yes | No sequencer, so no |
| Scoreboard feed | Via monitor analysis port | Via monitor analysis port |
Key fact: switching to passive never removes the monitor, so coverage and scoreboarding behave identically in both modes. This is why many teams run the same functional coverage model from block to chip.
Pitfalls seen in reviews and interviews
Treat the following as a checklist. The driver and monitor roles are covered in UVM driver and monitor, and the sequencing side in sequences and sequencer.
- Driving an interface twice. Leaving a block-level active agent on a bus that the RTL CPU also drives creates multiple drivers, so signals go X in a 4-state simulator such as VCS, Xcelium or Questa.
- Setting is_active too late. If the config_db set happens in the test's end_of_elaboration, the agent has already built. Set it in the test's build_phase before creating the env.
- Monitor depending on the driver. A monitor must sample only the interface. If it reads data from the driver's handle, passive mode breaks.
- Per-instance versus per-class configuration. A multi-agent env, for example four AXI masters, needs each instance set separately with its own path string.
- Coverage in the driver. Putting covergroups in the driver means passive mode loses coverage. Put them in the monitor or a subscriber.
Reusing the same agent at three levels
Engineers practising this at scale, including the design-services teams at Avecas, typically wrap each agent's configuration in a small config object holding the virtual interface and the is_active flag, so a single test-level override reconfigures the whole hierarchy. Communication between monitor and scoreboard uses analysis ports, explained in TLM in UVM.
Want to build this yourself on a real bench? Explore the hands-on UVM modules in our CourseTron courses, and for instructor-led placement training see ChipXpert.
FAQ
What is the default value of is_active?
UVM_ACTIVE. If you never set it, the agent builds the sequencer and driver.
Can a passive agent run sequences?
No. It has no sequencer, so there is nothing to start a sequence on. Only the monitor and its analysis port operate.
Can is_active change during simulation?
No. It is consumed in build_phase, and components cannot be created or removed afterwards, so the mode is fixed for the run.
Does a passive agent still need a virtual interface?
Yes. The monitor samples signals through the virtual interface, so it must still be supplied through the config_db.