Verification

Active vs Passive Agent in UVM

By CourseTron Team ·  9 min read ·  Updated Oct 2026 💬 𝕏 in f
Table of Contents
    ? Print / Save as PDF

    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.

    Active vs passive agent: what gets built UVM_ACTIVE agent Sequencer Driver Monitor UVM_PASSIVE agent Sequencernot built Drivernot built Monitor DUT interface (pins) Same monitor code and same pins in both modes; only the stimulus path differs. CourseTron · coursetron.com
    Active agents build all three sub-components; passive agents build the monitor only. Both share the same monitor code and the same DUT interface.

    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.

    How is_active gates build and connect build_phase create monitoralways built get_is_active()== UVM_ACTIVE ? yes create seqr + drv connect_phasedrv.seq_item_port to seqr no Passive: skip stimulus pathno drv, no seqr, no connect Unguarded connect on a passive build dereferences a null drv handle. CourseTron · coursetron.com
    The agent build_phase always creates the monitor and creates the sequencer and driver only when get_is_active() returns UVM_ACTIVE; connect_phase repeats the same guard.

    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

    ScenarioModeWhy
    Driving an AXI/APB master interfaceUVM_ACTIVENeeds sequencer + driver to inject reads/writes
    Observing a DUT-internal bus for coverageUVM_PASSIVEOnly sampling; never drives pins
    DUT is itself the slave; RTL respondsUVM_PASSIVERTL drives the bus; agent just checks
    Chip-level reuse of a block-level master agentUVM_PASSIVEAt SoC level the neighbor block drives; you only monitor
    Full stimulus generation at block levelUVM_ACTIVETestbench 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.

    Active agent: transaction path to the pins Sequencer Driver DUT pins item driver converts item to pin activity Passive agent: this whole path is not built; only the monitor remains. CourseTron · coursetron.com
    Animated: in an active agent a transaction travels sequencer → driver → DUT pins. In a passive agent this entire path does not exist — only the reverse monitor path.

    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_phase connects drv.seq_item_port without the UVM_ACTIVE guard, a passive build crashes with a null-handle dereference because drv was never created.
    • Setting is_active too late: the config-db set must execute before the agent's build_phase. Set it in the parent's build phase, not in end_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.

    Advertisement