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. What an Agent Owns, and What It Shows the World

Part 6/Agents & Protocol IPIntermediate

What an Agent Owns, and What It Shows the World

The agent block diagram, its three public surfaces (sequencer, analysis port, configuration), and why the env never wires to the monitor directly.

On this page

    A VIP team ships a new release of their APB agent. The only change in the notes: the monitor was split into a bus monitor and a protocol checker, and renamed. The next morning forty block environments fail to compile. Every one of them had wired its scoreboard straight to env.apb.mon.ap, a handle that no longer exists. The VIP did nothing wrong by its own rules. The environments had reached through the box instead of using its sockets.

    So what is the box, and which parts of it is the rest of the testbench allowed to touch?

    The idea: an appliance with three sockets

    An agent is a uvm_agent that packages everything one interface needs: a sequencer that decides which transaction goes next, a driver that turns transactions into pin activity, and a monitor that turns pin activity back into transactions. Think of it as a kitchen appliance. You plug things into its sockets and turn its dials; you never open the case and wire into the heating element, because the manufacturer may move it in the next model.

    The agent has exactly three sockets. Stimulus goes in through the sequencer: a test or virtual sequence calls seq.start(env.apb.sqr). Observations come out of one analysis port on the agent, ap, which any number of subscribers can connect to. Configuration arrives before the agent is built, through uvm_config_db: whether it is active or passive, which virtual interface to use, and any policy knobs. Everything else (the driver, the monitor, their names and their internal threads) is private.

    The picture: one agent, all its parts

    Block diagram of apb_agent: a sequence calls start_item and finish_item on the sequencer; the driver pulls with get_next_item and returns item_done; the driver drives the apb_if virtual interface; the monitor samples it through mon_cb and publishes through ap, which fans out to the scoreboard and to coverage and a predictor. Configuration from config_db decides whether the sequencer and driver are built.
    The DMA bench's apb_agent in active mode. Stimulus enters through the sequencer and leaves as pin activity; the monitor reads the same pins and publishes through the agent's one analysis port, which fans out to every subscriber. A passive agent keeps only the monitor and the port.

    Follow the two loops. The stimulus loop runs left to right: sequence, sequencer, driver, pins, with item_done closing the handshake (the read data travels back inside the same request object). The observation path runs from the pins through the monitor to the analysis port. The two never touch inside the agent: the monitor does not ask the driver what it meant to send. That separation is what lets the same agent go passive at chip level and keep checking.

    Build it: the bench's APB agent

    Here is the whole agent from the DMA bench. It is short because the work lives in its children; the agent's job is to build them, wire them and expose the sockets.

    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 apb_agent extends uvm_agent;
      `uvm_component_utils(apb_agent)
      apb_sequencer sqr;
      apb_driver    drv;
      apb_monitor   mon;
      uvm_analysis_port #(apb_item) ap;   // the agent's public output
    
      function new(string name, uvm_component parent);
        super.new(name, parent);
      endfunction
    
      function void build_phase(uvm_phase phase);
        super.build_phase(phase);         // reads is_active from config_db
        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);
        ap = mon.ap;
        if (get_is_active() == UVM_ACTIVE)
          drv.seq_item_port.connect(sqr.seq_item_export);
      endfunction
    endclass

    build_phase: the shape depends on the mode

    super.build_phase(phase) is not ceremony: uvm_agent's build_phase is where the is_active setting is read from config_db. The monitor is created unconditionally, because every use of the agent needs to observe. The sequencer and driver are created only when get_is_active() returns UVM_ACTIVE. The full story of that decision, and how to extend it, is in Mode-gated build.

    connect_phase: `ap = mon.ap` is a handle copy, not a connection

    The agent's public port is not a new port at all. ap = mon.ap copies the monitor's port handle into the agent's member, so env.apb.ap and env.apb.mon.ap are the same object. The env connects to apb.ap and never names the monitor. If a later release replaces the monitor, only this one line inside the agent changes.

    This works because of phase order. connect_phase runs bottom-up: the agent's connect_phase finishes before the env's starts, so by the time dma_env calls apb.ap.connect(sb.apb_export), the handle is set. The alternative style creates a real port on the agent in build_phase (ap = new("ap", this);) and connects mon.ap.connect(ap) in connect_phase: a child-port-to-parent-port connection. Both are correct. The port-to-port version shows up as its own port in the topology and gives the agent a port that exists from build onward; the handle copy is shorter and adds no extra hop. Pick one per project.

    The env side is the consumer of the three sockets, and it uses only those:

    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 dma_env extends uvm_env;
      `uvm_component_utils(dma_env)
      apb_agent      apb;
      axis_agent     in_agt, out_agt;
      dma_scoreboard sb;
    
      function new(string name, uvm_component parent);
        super.new(name, parent);
      endfunction
    
      function void build_phase(uvm_phase phase);
        super.build_phase(phase);
        // The output side is observed only: the DUT drives it.
        uvm_config_db#(uvm_active_passive_enum)::set(this, "out_agt", "is_active", UVM_PASSIVE);
        apb     = apb_agent::type_id::create("apb", this);
        in_agt  = axis_agent::type_id::create("in_agt", this);
        out_agt = axis_agent::type_id::create("out_agt", this);
        sb      = dma_scoreboard::type_id::create("sb", this);
      endfunction
    
      function void connect_phase(uvm_phase phase);
        apb.ap.connect(sb.apb_export);
        in_agt.ap.connect(sb.in_export);
        out_agt.ap.connect(sb.out_export);
      endfunction
    endclass

    Notice what the env does not do. It never touches apb.mon, apb.drv or any interface. It sets one knob (is_active for out_agt), creates three agents and a scoreboard, and connects three agent ports. The same axis_agent class appears twice, once active and once passive, decided entirely by that knob.

    Who owns what

    Part

    Owns

    Must never

    Sequencer

    Arbitration between sequences, handing items to the driver, routing responses back

    Touch pins or know protocol timing

    Driver

    Pin timing for stimulus: when each signal changes, waiting for handshakes

    Check data, publish to the scoreboard, decide what to send next

    Monitor

    Recognising completed transfers on the pins and publishing them

    Drive any signal, raise objections, change an item after publishing it

    Agent (wrapper)

    Building and wiring the three, exposing sqr, ap and its configuration

    Contain test-specific logic or checking

    Watch it run

    Run dma_smoke_test. The base test prints the topology at end of elaboration, and you can read the agent shapes straight from it: env.apb and env.in_agt each hold sqr, drv and mon; env.out_agt holds only mon. The proof that every analysis port reached its subscriber is the scoreboard's closing line:

    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
    • 8 words matched needs all three agent ports: KEY from apb.ap, input words from in_agt.ap, output words from out_agt.ap. Break any one connection and the count drops or check_phase complains.

    • The output agent observed 8 words without a driver. A passive agent is a full participant in checking; it simply has nothing to drive.

    What breaks: the env that reached inside

    Back to the forty broken environments. The symptom was a compile error, which is the lucky case. The unlucky case is the same reach-in done one level softer: an env that connects in build_phase, right after creating the agent, to apb.ap. It compiles. At time 0 the simulator stops with a null-object access, because apb.ap is assigned in the agent's connect_phase, which has not run yet. The root cause in both cases is the same: the env depended on the agent's insides (a child's name, or the moment a handle is set) instead of its contract.

    The fix is the discipline in this lesson. Connect only to agent.ap, and only in the env's connect_phase. Start stimulus only on agent.sqr. Configure only through config_db before the agent is built. An agent that follows the rule can be refactored freely; an env that follows it never notices.

    "What goes inside a UVM agent, and why bother with one?"

    A weak answer lists the parts: "a sequencer, a driver and a monitor." True, and it says nothing about why they are grouped.

    A strong answer names the contract: "An agent is the reuse unit for one interface. It exposes three surfaces: the sequencer for stimulus, one analysis port for observations, and config_db settings such as is_active and the virtual interface. The monitor is always built; the sequencer and driver only when active, so the same class works as a driver at block level and an observer at chip level. In our bench the agent's ap is just the monitor's port handle, copied in connect_phase, which works because connect runs bottom-up."

    Expect the follow-ups: Why not connect the scoreboard to the monitor directly? (it couples the env to an internal name and breaks on refactor) and Does a passive agent need a sequencer? (no; it has nothing to send, so consumers must check for a null sequencer).

    Check yourself A colleague moves ap = mon.ap; from the agent's connect_phase into its build_phase, right after mon is created. Does it still work?

    Yes, as long as it comes after the monitor is created: the monitor creates its port in its constructor, so mon.ap exists as soon as mon does. It is also earlier than any consumer needs it. What would break is putting it before mon = apb_monitor::type_id::create(...), which copies a null handle that never gets updated.

    Check yourself The env connects out_agt.ap to both the scoreboard and a coverage collector. Which subscriber's write() runs first, and should either care?

    The standard does not specify the order; the reference library happens to call subscribers in alphabetical order of their full names. Neither subscriber may depend on it. Each must treat its write() as independent and must not modify the item it receives, because the other subscriber holds the same object.

    Check yourself Why does the monitor sample the pins itself instead of asking the driver what it just sent?

    Because the driver knows only what it meant to drive. If the DUT ignored a transfer, or another master drove the bus, or the agent is passive at chip level with no driver at all, the driver's view is wrong or absent. The monitor reports what really happened on the pins, which is the only thing a scoreboard should trust.

    Next: the agent got its mode and its virtual interface from config_db. In Agent configuration you will see who puts them there, when a config object beats loose knobs, and the shared handle that turned two agents passive at once.

    Related topics

    • Agent configuration

    • The agent as the reuse unit

    • Analysis connectivity patterns

    • Traversal order rules

    Previous

    94 / 280

    Next: Agent Configuration: Knobs, Config Objects and Who Owns Them

    Up next

    Up nextAgent Configuration: Knobs, Config Objects and Who Owns Them

    Sign in to save progress

    Practice this lessonReport an error

    See also

    • 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