Skip to content
DenseChip
Learn
PracticeCode labInterview

DenseChip

Navigate

Menu

LearnPracticeCode labInterview

Tools

All toolsSV editorCode repoResume
DenseChip

Practice, courses, and chip-design tools in one focused workspace.

LearnPracticeCode labInterviewToolsLearning paths

© 2026 DenseChip

AboutContactPrivacyTerms
Skip to lesson

Course contents

  • Start Here4
  • UVM Foundations14
  • UVM Phases & Simulation Lifecycle26
  • Factory & Configuration15
  • Transaction-Level Modeling (TLM)13
  • UVM Sequences & Stimulus21
  • UVM Agents & Protocol VIP21
    • Agent Architecture
      • What an Agent Owns, and What It Shows the World
      • Agent Configuration: Knobs, Config Objects and Who Owns Them
      • Mode-Gated Build: One Agent Class, Active or Passive
      • The Driver: Owning the Pins, One Transfer at a Time
      • The Monitor: Watch the Pins, Publish a Fresh Transaction
      • The Sequencer: The Agent's Stimulus Socket
      • Monitor Implementation Detail: Sampling, Races and X
      • Interface Abstraction: Virtual Interfaces vs Abstract BFMs
    • Reusable VIP: Active, Passive and Protocol Agents
    • Error and Fault Injection
    • Agent Patterns: Slaves, Reset, Interrupts and Pipelines
  • Environment & Test Classes11
  • Checking & Coverage in UVM21
  • UVM Register Abstraction Layer (RAL)22
  • Advanced UVM in Practice13
  • Debug & Regression10
  • Coding Practice47
  • Verification in Industry Practice10
  • Senior Prep: Architecture, Leadership and Interviews32
Loading lesson

‹Agent Architecture
  1. Topics
  2. UVM Verification
  3. UVM Agents & Protocol VIP
  4. Agent Architecture
  5. Mode-Gated Build: One Agent Class, Active or Passive

Part 6/Agents & Protocol IPIntermediate

Mode-Gated Build: One Agent Class, Active or Passive

Where an agent reads is_active, what it builds in each mode, get_is_active(), guarded connect, and extending an agent without losing its monitor.

On this page

    env.in_agt and env.out_agt in the DMA bench are the same class, axis_agent. One drives TVALID, TDATA and TLAST into the DMA. The other sits on the DMA's output, where those same signals are driven by the DUT. If the second one built a driver too, the bench would put a second driver on every output signal, and the scoreboard would be comparing X.

    Nothing in the class says which copy is which. So how does one agent class decide, at build time, whether it drives?

    The idea: one recipe, two sizes

    A restaurant uses one recipe for a full plate and a half portion. The recipe does not change; a note from the front of house says which size to make. Mode-gated build is the same: the agent's build_phase always creates the monitor, and creates the sequencer and driver only if its mode is UVM_ACTIVE. The mode is a note from the parent, read at build time.

    The mode is the field is_active, inherited from uvm_agent, of type uvm_active_passive_enum and defaulting to UVM_ACTIVE. Two pieces of library code matter. uvm_agent::build_phase looks up an is_active entry in config_db for this agent and copies it into the field; that is why an agent must call super.build_phase(phase). And the virtual function get_is_active() returns the field; a VIP whose mode lives in a config object overrides it. Code that asks the mode should call get_is_active(), never read the field directly.

    The picture: the decision, step by step

    Flowchart: the parent sets is_active and creates the agent; the agent's build_phase calls super.build_phase, which reads is_active; it creates the monitor; if get_is_active returns UVM_ACTIVE it also creates the sequencer and driver; in connect_phase it copies mon.ap to ap and, if active, connects the driver's seq_item_port to the sequencer's seq_item_export.
    Mode-gated build in the bench's agents. The parent's note is read inside super.build_phase; the monitor is unconditional; the same question, asked through get_is_active(), gates both the build and the connection.

    Build it

    Step 1: the parent leaves the note

    Two setters in the bench decide modes. dma_env makes its output agent passive, and dma_subsys_env makes dma1.in_agt passive when its config says dma1's input is fed by dma0:

    design.sv
    1
    2
    3
    4
    5
    // snippet: from dma_env::build_phase and dma_subsys_env::build_phase
    uvm_config_db#(uvm_active_passive_enum)::set(this, "out_agt", "is_active", UVM_PASSIVE);
    
    if (cfg.chain_internal)
      uvm_config_db#(uvm_active_passive_enum)::set(this, "dma1.in_agt", "is_active", UVM_PASSIVE);

    Use the enum type. The reference library's uvm_agent also accepts an int entry for is_active, but the enum documents itself and matches what the agent stores. The set may even come after the create call in the same build_phase: create only constructs the child, and the child's build_phase runs after the parent's returns. A set made in the parent's connect_phase, on the other hand, is too late: the agent has already been built.

    Step 2: the agent builds to match

    design.sv
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    // snippet: from the course DMA bench
    class axis_agent extends uvm_agent;
      `uvm_component_utils(axis_agent)
      axis_sequencer sqr;
      axis_driver    drv;
      axis_monitor   mon;
      uvm_analysis_port #(axis_item) ap;
    
      function new(string name, uvm_component parent);
        super.new(name, parent);
      endfunction
    
      function void build_phase(uvm_phase phase);
        super.build_phase(phase);
        mon = axis_monitor::type_id::create("mon", this);
        if (get_is_active() == UVM_ACTIVE) begin
          sqr = axis_sequencer::type_id::create("sqr", this);
          drv = axis_driver::type_id::create("drv", this);
        end
      endfunction
    
      function void connect_phase(uvm_phase phase);
        ap = mon.ap;
        if (get_is_active() == UVM_ACTIVE)
          drv.seq_item_port.connect(sqr.seq_item_export);
      endfunction
    endclass

    Three lines carry the pattern. super.build_phase(phase) comes first, so the mode is known before anything is created. mon is created outside the if. sqr and drv are created inside it. Nothing else in the agent depends on the mode except the matching guard in connect_phase.

    Step 3: the connection is guarded by the same question

    connect_phase asks get_is_active() again before connecting the driver to the sequencer. In passive mode drv and sqr are null, and an unguarded drv.seq_item_port.connect(...) is a null-object access at time 0. Asking through the same function in both phases means build and connect can never disagree.

    Step 4: when the mode lives in a config object

    If the agent has a config object (see Agent configuration), let the object be the single source of truth by overriding the virtual function:

    design.sv
    1
    2
    3
    4
    // snippet: in an agent that holds apb_agent_cfg cfg, fetched in build_phase
    virtual function uvm_active_passive_enum get_is_active();
      return (cfg != null) ? cfg.is_active : is_active;
    endfunction

    Now build_phase, connect_phase, a virtual sequence and the env's debug print all get the same answer. The cfg != null fallback keeps the function safe if something asks before the object has been fetched.

    Step 5: consumers must respect the shape

    A passive agent has no sequencer, so anything that starts stimulus must check. The bench's dma_vseq refuses to start unless its sequencer handles are set: its body begins with if (apb_sqr == null || axis_sqr == null) and a uvm_fatal that says which handles to set. (It checks the sequencer handles rather than the virtual interface handle because comparing a virtual interface with null miscompiles in Verilator 5.048; the comparison is legal SystemVerilog.)

    Extending an agent: add, never replace

    Derived agents are common: an agent with an extra coverage collector, or the bench's own axis_sink_agent, which adds a slave-side sequencer and driver that answer the DUT with TREADY. The rule is the same in every case. The derived build_phase calls super.build_phase(phase) first, which builds the parent's monitor (and sequencer and driver if active), and then creates only what it adds. Its connect_phase calls super.connect_phase(phase) and then wires the additions. The reactive slave and why it builds its slave side even when passive are covered in the agent patterns topic.

    Watch it run

    In dma_smoke_test the topology printed at end of elaboration shows uvm_test_top.env.out_agt with a single child, mon, while env.apb and env.in_agt each list sqr, drv and mon. The scoreboard line proves the passive monitor still did its work:

    Diagram
    UVM_INFO dma_tb.sv(411) @ 655000: uvm_test_top.env.sb [SB] 8 words matched, 0 mismatched
    UVM_ERROR :    0
    UVM_FATAL :    0

    Read the topology on every new bench, and on every bench after a refactor. It is the cheapest test of mode-gated build there is: one glance tells you which agents can drive.

    What breaks: the coverage agent with no monitor

    An engineer needed AXI-Stream coverage on the output port and wrote axis_cov_agent extends axis_agent, overriding build_phase to create a coverage subscriber. The override created the subscriber and returned. It did not call super.build_phase(phase). The test died at time 0 with a null-object access in the env's connect_phase.

    Trace it. Without the super call, no monitor was created and is_active was never read. The inherited connect_phase then ran ap = mon.ap with mon null, which is itself an error, and the env's out_agt.ap.connect(...) failed on the null port. If the derived class had created its own monitor but still skipped the super call, the failure would have been quieter and worse: the passive setting would never be read, the agent would default to active, and on a DUT-driven port the new driver would fight the DUT. The super.build_phase lesson tells that version of the story.

    The fix is one line, super.build_phase(phase);, at the top of the override, followed by additions only. Code review should flag any agent build_phase without it.

    "How does a passive agent differ from an active one, and where is that decided?"

    A weak answer: "A passive agent only has a monitor; you set is_active to passive." Correct and shallow: it does not say who sets it, where it is read, or what can go wrong.

    A strong answer: "The parent sets is_active in config_db before the agent's build_phase runs. uvm_agent's build_phase reads it, which is why the agent calls super.build_phase first. The agent always builds the monitor and builds the sequencer and driver only when get_is_active() returns UVM_ACTIVE, and guards the driver-sequencer connection with the same call. If the mode lives in a config object, I override get_is_active() so there's one source of truth. Passive agents are for DUT-driven ports and for block agents reused at chip level."

    Expect the follow-ups: What does a derived agent's build_phase look like? (super first, then additions only) and What happens to a virtual sequence started with a passive agent's sequencer? (the handle is null; check it before starting).

    Check yourself The env sets is_active for out_agt in its connect_phase instead of build_phase. What happens?

    The setting is ignored for build purposes. The agent's build_phase ran during the build phase, read no is_active entry, and kept the default, UVM_ACTIVE. It built a driver for a DUT-driven port. The set must happen before the child's build_phase, which in practice means in the parent's build_phase.

    Check yourself Why is the monitor created outside the if (get_is_active() == UVM_ACTIVE) block instead of in an else?

    Because the active agent needs it too. An active agent's monitor watches what its own driver produced on the pins, which is the only trustworthy view for checking and coverage. The monitor is unconditional; only stimulus is optional.

    Check yourself A VIP's agent overrides get_is_active() to return its config object's field, but its build_phase still tests is_active == UVM_ACTIVE directly. The env sets the config object to passive and does not set the is_active knob. What gets built?

    A driver and sequencer. The build_phase reads the inherited field, which still holds its default, UVM_ACTIVE, because nobody set the knob. Meanwhile get_is_active() reports passive, so the connect_phase skips the connection: the driver exists, is unconnected, and may still drive the pins from its run_phase. Always ask through get_is_active().

    Next: in active mode the agent built a driver. In The driver you will follow one APB transfer edge by edge, and see the write that vanished when a slave got faster.

    Related topics

    • The driver

    • What super.build_phase() really does

    • Auto-config and field macros

    • Reusable VIP

    Previous

    96 / 280

    Next: The Driver: Owning the Pins, One Transfer at a Time

    Up next

    Up nextThe Driver: Owning the Pins, One Transfer at a Time

    Sign in to save progress

    Practice this lessonReport an error

    See also

    • What an Agent Owns, and What It Shows the World
    • The Monitor: Watch the Pins, Publish a Fresh Transaction
    • The Sequencer: The Agent's Stimulus Socket