In short: A UVM agent's is_active flag decides whether it drives stimulus — when set to UVM_ACTIVE it builds a sequencer and driver to generate and inject transactions, and when set to UVM_PASSIVE it builds only the monitor and observes the bus without driving it.
Ask any verification engineer who has sat through a few interviews and they will tell you this question comes up again and again: "What is the difference between an active and a passive UVM agent, and how does is_active control it?" It sounds trivial, but a sharp follow-up — "What exactly stops the driver from toggling the bus in a passive agent?" — separates people who have genuinely built a System Verilog/UVM testbench from people who have only read about one. This explainer walks through the mechanism, the code, and the real-world reasons you reach for each mode.
What an agent actually is
In UVM, an agent is a container component — a reusable bundle of the three sub-components that handle one physical interface of your DUT: a sequencer (arbitrates and routes sequence items), a driver (converts transactions into pin-level activity across a virtual interface), and a monitor (samples the same pins and reconstructs transactions for checking and coverage). Group them with a configuration object and you have an agent that can be instantiated once for an AXI port, again for an APB port, and so on. This modularity is exactly what makes the methodology scale; see our walkthrough of UVM testbench architecture for how agents nest inside the environment.
The catch: not every interface in a testbench needs to be driven. A master interface needs stimulus. A slave-side or purely observed interface often needs only watching. Rebuilding two separate agents for that would defeat reuse. UVM's answer is a single agent with a mode switch.
The is_active flag and how it gates the build
Every agent extending uvm_agent inherits a field is_active of type uvm_active_passive_enum, whose two legal values are UVM_ACTIVE (the default) and UVM_PASSIVE. The gating happens in the agent's build_phase, where you conditionally construct the stimulus path:
class apb_agent extends uvm_agent;
`uvm_component_utils(apb_agent)
apb_driver drv;
apb_sequencer seqr;
apb_monitor mon;
function void build_phase(uvm_phase phase);
super.build_phase(phase);
mon = apb_monitor::type_id::create("mon", this); // ALWAYS built
if (get_is_active() == UVM_ACTIVE) begin
seqr = apb_sequencer::type_id::create("seqr", 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(seqr.seq_item_export);
endfunction
endclass
The key idea: is_active is not a runtime behavioral switch inside the driver. It is a construction-time decision. In a passive agent the driver object is never created — there is literally no component to call drive() on, nothing connected to the virtual interface's output drivers, so the DUT pins are never written. The monitor, by contrast, only reads the signals, so it is always safe to build. That is the "why" the follow-up question is fishing for.
Key fact: Prefer get_is_active() over reading the is_active member directly. The accessor honors any value pushed in through uvm_config_db#(uvm_active_passive_enum), so a parent environment or the test can flip an agent to passive without touching the agent's code.
Setting the mode from above
Because reuse is the whole point, you almost never hardcode the mode inside the agent. You set it from the environment or test using the configuration database before build_phase runs:
// in env or test build_phase, BEFORE the agent is created
uvm_config_db#(uvm_active_passive_enum)::set(
this, "mst_agent", "is_active", UVM_ACTIVE);
uvm_config_db#(uvm_active_passive_enum)::set(
this, "slv_agent", "is_active", UVM_PASSIVE);
The same packaged agent is now a master driver on one port and a passive observer on another. This is standard practice in VIP (verification IP) from Synopsys VC VIP, Cadence's UVM VIP library, and Siemens EDA's QVIP, where the end user toggles active/passive per instance from the integration layer.
When to use each mode
| Scenario | Mode | Why |
|---|---|---|
| Driving an AXI/APB master interface | UVM_ACTIVE | Needs sequencer + driver to inject reads/writes |
| Observing a DUT-internal bus for coverage | UVM_PASSIVE | Only sampling; never drives pins |
| DUT is itself the slave; RTL responds | UVM_PASSIVE | RTL drives the bus; agent just checks |
| Chip-level reuse of a block-level master agent | UVM_PASSIVE | At SoC level the neighbor block drives; you only monitor |
| Full stimulus generation at block level | UVM_ACTIVE | Testbench must create all traffic |
That fourth row is the most common real-world reuse story: a block-level environment builds an active agent to stimulate a port; when the block is integrated into the SoC, the adjacent RTL block now drives that port, so the same agent is reconfigured passive to avoid two drivers fighting on one wire — which would otherwise produce X contention in simulation.
Key fact: Two active drivers on the same physical interface is a classic bring-up bug. Symptoms are red X values on the bus and unexplained protocol-checker failures. The fix is almost always demoting one agent to UVM_PASSIVE, not debugging the driver logic.
A subtlety: monitors are mode-independent
A frequent misconception is that passive means "do nothing." It does not. The monitor runs in both modes, continuously sampling the interface, publishing transactions on its analysis port to scoreboards and coverage collectors. A passive agent is therefore fully functional for checking and coverage — it simply contributes no stimulus. This is why you can drop a passive instance of a well-written agent anywhere in a hierarchy and immediately get protocol checking "for free." Design teams at Avecas VLSI design services routinely reuse a single verified agent across block and SoC levels using exactly this active/passive split.
Common pitfalls
- Connecting the driver unconditionally: if
connect_phaseconnectsdrv.seq_item_portwithout theUVM_ACTIVEguard, a passive build crashes with a null-handle dereference becausedrvwas never created. - Setting is_active too late: the config-db
setmust execute before the agent'sbuild_phase. Set it in the parent's build phase, not inend_of_elaboration. - Starting a sequence on a passive agent: there is no sequencer to run on, so
seq.start(agent.seqr)dereferences null. Guard your virtual-sequence body or keep active/passive sequencing cleanly separated.
For the bigger picture of how these components phase, connect, and communicate, pair this with our deep dive on UVM phases and objections, and browse the full CourseTron course catalog for guided labs.
Ready to build a real UVM environment end to end — agents, sequences, scoreboards and coverage on an actual DUT? Join the CourseTron Advanced UVM Verification track and write the testbenches interviewers ask about. Prefer instructor-led cohorts? Our training partner ChipXpert runs live VLSI verification batches.
Frequently asked questions
Does a passive agent still need a sequencer?
No. A passive agent does not build a sequencer or a driver, so there is nothing to run sequences on. If a virtual sequence tries to start on a passive agent's sequencer handle, it will dereference a null object and the simulation will error out.
Can I change is_active during the run?
Not meaningfully. Because the flag controls whether the driver and sequencer are constructed in build_phase, it must be set before that phase runs. Changing it mid-simulation has no effect — the components already exist or do not.
What is the default value of is_active?
The default is UVM_ACTIVE. If you want an observer-only agent you must explicitly set it to UVM_PASSIVE, usually through the uvm_config_db from the enclosing environment or test.
Why not just build two separate agent classes?
Reuse. One agent that toggles active/passive is instantiated across block-level and SoC-level testbenches without code changes, which is precisely the modularity UVM and commercial VIP from Synopsys, Cadence, and Siemens EDA are built around.