Skip to content

UVM

Agent Architecture

The agent — UVM's reusable, configurable container that bundles a sequencer, driver, and monitor into one self-contained unit for a single interface, built once and reused everywhere, switchable between active (drives) and passive (observes).

Agents · Module 14 · Page 14.1

The Engineering Problem

The previous modules built the three components that interact with an interface: the sequencer (plans the traffic, Module 11), the driver (drives it onto the pins, Module 12), and the monitor (observes the pins, Module 13). But these three are not independent — they belong together. They share the same virtual interface, the same transaction type, the same protocol knowledge; the driver pulls from the sequencer; the monitor watches what the driver drove. Scattering them loose across the testbench — three separate instances to create, connect, configure, and reuse individually — is fragile and repetitive: every environment that touches this interface must re-wire the same sequencer→driver connection, re-export the monitor's analysis port, and re-decide whether to drive or just observe. The problem this chapter solves is cohesion and reuse: how do you package everything needed to interact with one interface into a single, standard, reusable, configurable unit?

The answer is the agent — UVM's standard container that bundles a sequencer, driver, and monitor into one self-contained component for a single interface protocol. The agent encapsulates the complete capability to drive and observe one interface: it holds the three components, connects them internally (driver↔sequencer, monitor's analysis port exported upward), and exposes a clean boundary to the environment. It is configurable by a single knob — is_active — that selects active (build the sequencer + driver + monitor: drive and observe) or passive (build only the monitor: observe only). And it is the fundamental unit of reuse in UVM: you build a protocol agent once (an AXI agent, an APB agent) and reuse it in every environment, at every level — block, subsystem, SoC — where that interface appears. This chapter is the agent's architecture: what it bundles, why bundling buys cohesion and reuse, where it sits in the hierarchy, and the configurability that makes one class serve both driving and observing.

What is a UVM agent — the reusable, configurable container bundling a sequencer, driver, and monitor for one interface — and why does packaging the three interface components into a single self-contained unit buy cohesion, reuse, and the active/passive flexibility a testbench needs?

Motivation — why bundle the three into one unit

The agent exists because the three interface components form a natural unit that should be built, connected, configured, and reused together. The reasons:

  • The three components are coupled by the interface. The sequencer, driver, and monitor share the same virtual interface, transaction type, and protocol; the driver pulls from the sequencer; the monitor watches the interface. They are a cohesive set — bundling them keeps what belongs together, together.
  • The connection wiring is always the same. Active mode always connects the driver's seq_item_port to the sequencer's seq_item_export, and the monitor's analysis port is always exported upward. Encapsulating this in the agent means writing it once, not in every environment.
  • The interface protocol recurs everywhere. An AXI interface appears in dozens of DUTs; an APB in dozens more. Building the seq+drv+mon once as a protocol agent and reusing it avoids re-implementing the protocol's interaction per project. The agent is the reuse boundary.
  • Driving vs observing is a per-instance decision. Sometimes you drive an interface (you control it); sometimes you only observe it (an internal interface between two DUT blocks you watch but don't control). One agent, configured by is_active, serves both — no separate classes, no duplicated monitor.
  • Standardization makes testbenches legible. Every uvm_agent has the same structure — predictable build (conditional on is_active), predictable connections, predictable analysis-port export. An engineer opening any agent already knows its shape.

The motivation, in one line: the sequencer, driver, and monitor are a cohesive, recurring, configurably-driven set bound to one interface, so UVM packages them into an agent — a self-contained, standard, reusable unit that bundles and connects them once, exposes a clean boundary, and is configured active or passive — turning per-interface interaction into a build-once, reuse-everywhere component.

Mental Model

Hold the agent as the self-contained crew assigned to one interface:

An agent is the complete crew for one interface — a planner, a pair of hands, and a pair of eyes — packaged as a single unit you deploy wherever that interface appears, and switch between full-duty and observe-only. Picture each interface in your design as a station that needs staffing. The agent is the crew you assign to one station, and it has three roles. The planner (the sequencer) decides what traffic to generate. The hands (the driver) carry out the plan — driving it onto the interface. The eyes (the monitor) watch the interface — observing everything that happens, reporting it outward. These three belong together: they all work the same station, speak the same protocol, and the hands carry out the planner's plan while the eyes watch the result. So you package them as one crewhire once, deploy anywhere: the same crew works any station of the same type (any AXI interface, any APB interface), in any building (block, subsystem, SoC). And the crew has a duty switch. In full-duty mode (active), the whole crew works — the planner plans, the hands drive, the eyes watch: the agent drives and observes. In observe-only mode (passive), only the eyes work — no planner, no hands, just the monitor watching: the agent observes without driving (for a station you don't control, only witness). The crew is self-contained (everything the station needs is inside it) and standard (every crew has the same three roles and the same duty switch), so deploying one is plug-and-play, and anyone who's seen one crew understands every crew.

So the agent is the self-contained, standard crew for one interface: planner (sequencer) + hands (driver) + eyes (monitor), bundled so they deploy as a unit, reused across every environment with that interface, and switched between full-duty (active: drive + observe) and observe-only (passive: observe). The one thing to hold is that the agent is the unit — you don't think about the seq/drv/mon individually when you place an interface's verification; you drop in the agent and set its mode.

Visual Explanation — the agent's internal structure

The defining picture is the agent's internals: a sequencer feeding a driver that drives the interface, a monitor observing the interface, and the monitor's analysis port exported up to the environment — all inside the agent boundary.

Inside the agent: sequencer feeds driver which drives the interface, monitor observes it, analysis port exported upseq_item (pull)drivesobservesanalysis port(exported up)Sequencerplans trafficDriverdrives the interfaceMonitorobserves the interfaceInterface (pins)one protocol boundaryAnalysis port ↑exported to the env12
Figure 1 — the agent bundles a sequencer, driver, and monitor for one interface. Inside the agent boundary: the sequencer feeds transactions to the driver (driver.seq_item_port to sequencer.seq_item_export); the driver drives them onto the interface; the monitor observes the same interface; and the monitor's analysis port is exported upward, out of the agent, so the environment can subscribe. The agent is self-contained — everything needed to drive and observe this one interface is inside — and presents a clean boundary: a sequencer to run sequences on, and an analysis port to subscribe to.

The figure shows the agent's self-contained internal structure. Inside the agent boundary sit the three components. The sequencer plans the traffic and feeds transactions to the driver (the driver's seq_item_port pulls from the sequencer's seq_item_export — Module 12.1). The driver drives those transactions onto the interface — the one protocol boundary this agent owns. The monitor observes that same interface — passively, read-only (Module 13.1) — and its analysis port is exported upward, out of the agent, so the environment (and the scoreboard/coverage in it) can subscribe (Module 13.4). The crucial reading is the encapsulation and the clean boundary. Everything needed to drive and observe this one interface is inside the agent: the planning (sequencer), the driving (driver), the observing (monitor), and the internal connection (driver↔sequencer) are all bundled — the environment doesn't wire them. What the agent exposes is a minimal, clean boundary: a sequencer (so a test can run sequences on it) and an analysis port (so the environment can subscribe to what it observed). The warning-colored interface is the agent's one connection to the DUT; the success-colored analysis port is its one connection up to the environment. The diagram is the agent as a sealed unit: internally a complete sequencer-driver-monitor assembly for one interface, externally just "run sequences here, subscribe to observations there." That encapsulationcomplete inside, clean boundary outside — is what makes the agent reusable: you drop it in, run sequences on its sequencer, subscribe to its analysis port, and never re-wire its internals.

RTL / Simulation Perspective — building and connecting the agent

In code, the agent is a uvm_agent whose build_phase conditionally builds the driver and sequencer (only when active) and always builds the monitor, and whose connect_phase wires the driver to the sequencer (when active) and exports the monitor's analysis port. The code shows the structure.

the agent: conditional build on is_active, internal connection, analysis-port export
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
class my_agent extends uvm_agent;
  `uvm_component_utils(my_agent)
  my_sequencer seqr;                              // built only when ACTIVE
  my_driver    drv;                               // built only when ACTIVE
  my_monitor   mon;                               // ALWAYS built
  uvm_analysis_port #(bus_item) ap;               // exported from the monitor, upward
 
  function new(string n, uvm_component p); super.new(n,p); ap = new("ap", this); endfunction
 
  function void build_phase(uvm_phase phase);
    mon = my_monitor::type_id::create("mon", this);       // monitor ALWAYS
    if (get_is_active() == UVM_ACTIVE) begin               // the configurability knob
      seqr = my_sequencer::type_id::create("seqr", this); //   sequencer + driver only when ACTIVE
      drv  = my_driver   ::type_id::create("drv",  this);
    end
  endfunction
 
  function void connect_phase(uvm_phase phase);
    ap = mon.ap;                                            // export the monitor's analysis port up
    if (get_is_active() == UVM_ACTIVE)
      drv.seq_item_port.connect(seqr.seq_item_export);     // wire driver <- sequencer (active only)
  endfunction
endclass

The code shows the agent's standard, configurable structure. The agent declares all three components — seqr, drv, mon — plus an ap analysis port. The build_phase is conditional on the configurability knob: the monitor is always built (an agent always observes), but the sequencer and driver are built only when get_is_active() == UVM_ACTIVE (an agent drives only when active). The connect_phase does the internal wiring: it exports the monitor's analysis port upward (ap = mon.ap) — always, so the environment can subscribe whether the agent is active or passive — and, only when active, connects the driver to the sequencer (drv.seq_item_port.connect(seqr.seq_item_export)) so the driver can pull transactions. The shape to carry: an agent is a uvm_agent that always builds and exports a monitor, conditionally builds a sequencer + driver based on is_active, and wires the driver to the sequencer when active — a standard, predictable structure that encapsulates the build-and-connect logic once, so every environment that uses the agent gets it for free. The get_is_active() check is the single point where one agent class becomes either a driving agent or an observing agent — the configurability that Modules 14.2 (active) and 14.3 (passive) explore in depth.

Verification Perspective — where the agent sits, and how it's configured

The agent is the reuse boundary in the testbench hierarchy, and its active/passive configuration is what lets one agent class serve every role. Both are essential to how a testbench is assembled.

The testbench hierarchy: test contains environment, which contains agents, each bundling sequencer, driver, monitorTest → Environment → Agent → (sequencer, driver, monitor)Test → Environment → Agent → (sequencer, driver, monitor)Test (top)selects and configures the environment, sets each agent active or passive, runs sequences.selects and configures the environment, sets each agent active or passive, runs sequences.Environmentinstantiates one or more agents (one per interface) and wires them to the scoreboard and coverage.instantiates one or more agents (one per interface) and wires them to the scoreboard and coverage.Agent — the reuse boundarybundles the sequencer, driver, and monitor for ONE interface; built once, reused everywhere that interface appears.bundles the sequencer, driver, and monitor for ONE interface; built once, reused everywhere that interface appears.Sequencer · Driver · Monitorthe interface-level components the agent encapsulates and connects internally.the interface-level components the agent encapsulates and connects internally.
Figure 2 — the agent in the testbench hierarchy, as the reuse boundary. The test sits at the top, configuring and running. The environment instantiates one or more agents — one per interface. Each agent bundles the sequencer, driver, and monitor for its interface. The agent is the unit you reuse: the same protocol agent drops into any environment at any level. Below the agent are the interface-level components it encapsulates; above it is the environment-level structure that wires agents to the scoreboard and coverage.

The figure places the agent in the hierarchy and shows why it's the reuse boundary. At the top, the test configures the environment, sets each agent active or passive, and runs sequences. Below it, the environment instantiates one or more agentsone per interface — and wires them (their analysis ports) to the scoreboard and coverage. Each agent — the brand-colored reuse boundarybundles the sequencer, driver, and monitor for one interface. At the bottom, those interface-level components are what the agent encapsulates and connects internally. The verification insight is that the agent is the seam in the hierarchy where reuse happens: below the agent is interface-specific, protocol-detailed machinery (how to drive and observe this protocol); above the agent is environment-specific, DUT-detailed structure (which interfaces exist, how their observations feed checking). The agent cleanly separates these: the same agent (the same protocol machinery) drops into a different environment (a different DUT) unchanged — because the agent's boundary (run sequences on its sequencer, subscribe to its analysis port) is independent of which environment holds it. This is why an AXI agent built for one project reuses in another: the environment around it changes, but the agent — the interface machinerydoesn't. And the active/passive knob, set by the test at the top, is what lets the same agent be a driving agent in one environment and an observing agent in another (or even at different instances in the same environment) — one class, configured per instance. The figure is the agent as the architectural pivot: the boundary that separates reusable interface machinery from environment-specific assembly, configured active or passive from above.

Runtime / Execution Flow — assembling an agent into a testbench

Using an agent is a fixed assembly flow: instantiate it in the environment, configure its mode, let it build its (conditional) internals, and subscribe to its analysis port. The flow shows the steps.

Assembly flow: instantiate and configure the agent, it builds and connects itself, the environment subscribes, the test runs sequencesinstantiate + set mode → agent builds (conditional) → agent connects internals + exports port → env subscribes → test runs sequencesinstantiate + set mode → agent builds (conditional) → agent connects internals + exports port → env subscribes → test runs sequences1Instantiate and set modethe environment creates the agent and configures it active orpassive (is_active).2Agent builds its componentsmonitor always; sequencer and driver only if active — the agentdecides from its mode.3Agent connects internals + exports portwires driver to sequencer when active; exports the monitor'sanalysis port upward.4Environment subscribes, test runs sequencesscoreboard and coverage subscribe to the agent's port; the testruns sequences on its sequencer.
Figure 3 — assembling an agent into a testbench. The environment instantiates the agent and sets its mode (active or passive) via configuration. In build, the agent constructs its components — monitor always, sequencer and driver only if active. In connect, the agent wires its internals (driver to sequencer when active) and exports its monitor's analysis port. The environment subscribes the scoreboard and coverage to that port. The test then runs sequences on the agent's sequencer (active agents). The agent's internals are assembled once, by the agent itself — the environment only places, configures, and subscribes.

The flow is the agent's plug-and-play assembly. Instantiate and set mode (step 1): the environment creates the agent and configures it active or passive (is_active, typically via the config database). Agent builds (step 2): in build_phase, the agent itself constructs its components — the monitor always, the sequencer and driver only if activedeciding from its own mode. Agent connects (step 3): in connect_phase, the agent itself wires its internals (driver↔sequencer when active) and exports the monitor's analysis port upward. Environment subscribes, test runs (step 4): the environment subscribes the scoreboard and coverage to the agent's analysis port, and the test runs sequences on the agent's sequencer (for active agents). The runtime insight is the division of labor: the agent assembles itself (build + connect its internals) — the environment only places it, configures its mode, and subscribes to its output. The environment never reaches inside the agent to wire the driver to the sequencer or to build the monitor — that's encapsulated. This is exactly what makes the agent reusable: all the internal complexity (conditional build, internal connection, port export) is the agent's responsibility, done the same way every time, so using an agent is trivialinstantiate, configure, subscribe, run. The flow shows the agent as a self-assembling unit: hand it a mode, and it builds and wires itself into a complete, connected interface-interaction component, exposing just the sequencer and analysis port the environment needs.

Waveform Perspective — one agent, driving and observing

An active agent's dual nature — it drives and observes the same interface — is visible on a timeline: its driver drives a transaction onto the pins, and its monitor observes that same activity. The waveform shows both paths of one agent.

One active agent: its driver drives the interface, its monitor observes the same activity

12 cycles
One active agent: its driver drives the interface, its monitor observes the same activityagent's driver drives the interface (valid, data=B4) — the drive pathagent's driver drives …agent's monitor observes the same activity — mon_capture — the observe pathagent's monitor observ…agent's monitor emits the observed transaction on its analysis portagent's monitor emits …clkdrv_validdata--B4B4----D9D9----------mon_captureap_emitt0t1t2t3t4t5t6t7t8t9t10t11
Figure 4 — an active agent both drives and observes its interface. The agent's driver (fed by its sequencer) drives a transaction onto the pins — valid asserts, data=B4. The agent's monitor observes that same activity: mon_capture pulses when it samples the pins. So one agent contains both the drive path (sequencer to driver to pins) and the observe path (pins to monitor to analysis port). A passive agent would show only the mon_capture path — observing without the driver's drive. The agent is the unit that owns this interface's complete interaction.

The waveform shows an active agent's two paths on its one interface. The agent's driver (fed by its sequencer) drives a transaction onto the pins — drv_valid asserts, data=B4 — the drive path (sequencer → driver → pins). The agent's monitor observes that same activity — mon_capture pulses when it samples the pins — and emits the observed transaction on its analysis port (ap_emit) — the observe path (pins → monitor → analysis port). The crucial reading is that one agent contains both paths: it drives the interface and observes the same interface, concurrently, on the same pins. This is the active agent's complete interface interaction — stimulus (drive) and checking-feed (observe) bundled in one unit. The contrast sharpens the picture: a passive agent (Module 14.3) would show only the mon_capture/ap_emit path — observing without the driver's drive — because a passive agent builds no driver. So the waveform distinguishes the two modes visually: an active agent appears as both driving and observing; a passive agent appears as observing only. Reading the interface for which agent owns itis something driving (active agent) or only observing (passive agent)? — maps the agent's mode onto the pins. The picture to carry: the agent is the unit that owns one interface's complete interaction — in active mode, both driving and observing; in passive mode, observing only — and the waveform shows the agent's mode as the presence or absence of its drive path.

DebugLab — the duplicated monitor that should have been an agent mode

A passive interface modeled by copy-pasting the monitor instead of reusing the agent passively

Symptom

A testbench needed to observe an internal interface (between two DUT sub-blocks) it didn't drive. The engineer already had a working agent for that protocol (used actively elsewhere), but instead of reusing it, they copy-pasted the monitor out of the agent into a standalone instance, wiring it directly into the environment. It worked — at first. Then the protocol's monitor was updated (a reconstruction fix, Module 13.2) in the agent's monitor — and the copied standalone monitor silently kept the old, buggy logic. The two diverged: the active agent's interface was checked correctly, the copied one wasn't, and a real bug on the observed interface slipped through the stale copy.

Root cause

The engineer bypassed the agent's configurability — they duplicated the monitor instead of reusing the agent in passive mode. The agent already supported "observe only" via is_active = UVM_PASSIVE; copy-pasting the monitor forked the protocol logic into a second, un-maintained copy:

why duplication diverged, and what passive mode would have given
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
✗ COPY-PASTE the monitor into a standalone instance (forks the protocol logic):
  my_monitor standalone_mon;            // a SECOND copy of the protocol's monitor
  standalone_mon = my_monitor::type_id::create("standalone_mon", this);
  // ... manually wire its analysis port ...
  // agent's monitor gets a reconstruction FIX → standalone copy stays STALE → divergence, missed bug
 
✓ REUSE the agent in PASSIVE mode (one source of truth for the protocol):
  uvm_config_db#(uvm_active_passive_enum)::set(this, "obs_agent", "is_active", UVM_PASSIVE);
  obs_agent = my_agent::type_id::create("obs_agent", this);   // SAME agent, observe-only
  // builds ONLY the monitor (no driver/sequencer); same protocol logic, maintained once
  // any monitor fix applies to BOTH the active and passive instances — no divergence

This is the reuse-bypass bug — re-implementing (here, copy-pasting) what the agent's configurability already provides, which forks the protocol logic and guarantees eventual divergence. The engineer needed an observe-only instance of an interface they already had an agent for. The agent was built for exactly this — is_active = UVM_PASSIVE makes it build only the monitor, observing without driving (Module 14.3). But instead of setting that knob, they copy-pasted the monitor out of the agent into a standalone instance. Initially identical, the two copies were now separate code: when the agent's monitor received a reconstruction fix, the copied monitor — a forkkept the old logic. The interfaces diverged: the agent-checked one was correct, the copy-checked one was stale, and a real bug on the observed interface slipped through the un-fixed copy. The fix is to reuse the agent in passive mode: set is_active = UVM_PASSIVE and instantiate the same agent — it builds only the monitor (no driver/sequencer), using the same, single-source protocol logic that the active instances use. Now there is one monitor implementation, maintained once; any fix applies to both active and passive instances; no divergence is possible. The general lesson, and the chapter's thesis: the agent is the unit of reuse, and its active/passive configurability is designed to serve both driving and observing from one class — so when you need to observe an interface you have an agent for, reuse the agent in passive mode, never copy-paste its monitor; duplicating the protocol logic forks it, and forked logic inevitably diverges, leaving a stale copy that misses bugs. One agent, configurednot two implementations, drifting apart.

Diagnosis

The tell is duplicated protocol logic that drifts from its source. Diagnose reuse bypasses:

  1. Look for a standalone monitor alongside an existing agent. A copy of an agent's monitor wired directly into the environment is a fork; the agent's passive mode was the intended path.
  2. Check whether passive mode was considered. If an interface is observed but the protocol has an agent, is_active = UVM_PASSIVE should have been used instead of duplication.
  3. Diff the duplicated logic against the agent's. If a copied monitor lacks a fix the agent's monitor has, they've diverged — the copy is stale.
  4. Trace a known bug through both instances. If the agent-checked interface catches it but the copied one doesn't, the duplication is the cause.
Prevention

Reuse the agent through its configurability; never fork its components:

  1. Use passive mode to observe. For an interface you watch but don't drive, instantiate the agent with is_active = UVM_PASSIVE — it builds only the monitor, reusing the one protocol implementation.
  2. Never copy-paste an agent's components. Duplicating a monitor or driver forks the protocol logic; keep one source of truth inside the agent.
  3. Configure per instance, don't duplicate per role. One agent class serves active and passive via is_active; the role is a configuration, not a new class or a copy.
  4. Centralize protocol fixes in the agent. A monitor or driver fix in the agent must reach every instance; that only holds if every instance is the same agent.

The one-sentence lesson: the agent is the unit of reuse and its active/passive knob is designed to serve both driving and observing from one class — so to observe an interface you have an agent for, reuse the agent in passive mode (is_active = UVM_PASSIVE), never copy-paste its monitor; duplicating the protocol logic forks it, and forked logic inevitably diverges into a stale copy that misses bugs.

Common Mistakes

  • Copy-pasting an agent's monitor to observe an interface. Use the agent in passive mode (is_active = UVM_PASSIVE) instead; duplication forks the protocol logic and drifts.
  • Wiring the sequencer, driver, and monitor loose in the environment. Bundle them in an agent so the connection and export logic is written once, not per environment.
  • Building the driver/sequencer regardless of mode. A passive agent must build only the monitor; gate the sequencer and driver on is_active.
  • Forgetting to export the monitor's analysis port. The agent must expose the monitor's port upward so the environment can subscribe — in both active and passive modes.
  • Making separate classes for active and passive. One agent class with an is_active knob serves both; separate classes duplicate the monitor.
  • Reaching into the agent from the environment. The environment places, configures, and subscribes; it must not wire the agent's internals — that breaks encapsulation and reuse.

Senior Design Review Notes

Interview Insights

A UVM agent is the standard, reusable, configurable container that bundles a sequencer, driver, and monitor into one self-contained component encapsulating everything needed to drive and observe a single interface protocol. The three components it bundles each have a role: the sequencer plans the traffic, deciding what transactions to generate; the driver drives those transactions onto the interface pins; and the monitor observes the interface, reconstructing and broadcasting what actually happened. These three belong together because they all work the same interface — they share the same virtual interface, the same transaction type, and the same protocol knowledge, and the driver pulls from the sequencer while the monitor watches the result. The agent holds all three, connects them internally — wiring the driver to the sequencer and exporting the monitor's analysis port upward — and presents a clean boundary to the environment: a sequencer to run sequences on, and an analysis port to subscribe to. It's also configurable through a single knob, is_active, that selects whether the agent is active, building the sequencer, driver, and monitor so it both drives and observes, or passive, building only the monitor so it observes without driving. And it's the fundamental unit of reuse in UVM — you build a protocol agent once, like an AXI agent or an APB agent, and reuse it in every environment and at every level where that interface appears. The mental model is a self-contained crew for one interface: a planner, a pair of hands, and a pair of eyes, packaged as one unit you deploy wherever that interface shows up and switch between full-duty and observe-only.

Because the three are a cohesive, recurring, configurably-driven set bound to one interface, and bundling them buys cohesion, reuse, and standardization that separate instantiation can't. Cohesion: the sequencer, driver, and monitor are coupled by the interface — they share the virtual interface, transaction type, and protocol, the driver pulls from the sequencer, and the monitor watches what the driver drove. They belong together, so packaging them as a unit keeps what belongs together, together, rather than scattered loose across the environment. Reuse: the same interface protocol recurs everywhere — an AXI interface appears in dozens of DUTs. If you build the sequencer, driver, and monitor once as a protocol agent, you reuse it in every environment without re-implementing the protocol interaction per project. The agent is the reuse boundary. Without bundling, every environment would re-wire the same driver-to-sequencer connection and re-export the monitor's port, duplicating boilerplate and inviting mistakes. Standardization: every uvm_agent has the same structure — conditional build on is_active, the same internal connections, the same analysis-port export — so an engineer opening any agent already knows its shape. Configurability: the driving-versus-observing decision is per instance, and one agent configured by is_active serves both, with no separate classes. The encapsulation is what makes it reusable: all the internal complexity — conditional build, internal connection, port export — is the agent's own responsibility, done the same way every time, so using an agent is just instantiate, configure, subscribe, and run. Separate instantiation throws away all of that, forcing every environment to reassemble the interface machinery by hand.

Exercises

  1. Name the bundle. List the three components an agent contains and the role of each, plus the two things the agent connects.
  2. Trace the knob. Describe what an agent builds and connects in active mode versus passive mode, and the single check that decides.
  3. Place it in the hierarchy. Sketch test → env → agent → components, and explain why the agent is the reuse boundary.
  4. Avoid the fork. You need to observe an interface you have an active agent for. State what you do instead of copying the monitor, and why.

Summary

  • An agent is UVM's standard, reusable, configurable container that bundles a sequencer, driver, and monitor into one self-contained component encapsulating everything needed to drive and observe a single interface protocol.
  • It holds the three components, connects them internally (driver pulls from the sequencer; monitor's analysis port exported upward), and presents a clean boundary: a sequencer to run sequences on and an analysis port to subscribe to.
  • A single knob — is_active — configures it: active builds sequencer + driver + monitor (drives and observes), passive builds only the monitor (observes only) — one class, two modes, decided by a single check in build and connect.
  • The agent is the reuse boundary: it sits test → env → agent → sequencer/driver/monitor, separating reusable interface machinery (below) from environment-specific assembly (above) — so a protocol agent is built once and reused everywhere that interface appears.
  • The durable rule of thumb: package each interface's sequencer, driver, and monitor into one agent, expose only a sequencer and an analysis port, and let the agent build and connect its own internals from is_active — then reuse that one agent everywhere through its configuration (active to drive, passive to observe); never copy-paste its components, because duplicating the protocol logic forks it and the copy drifts into a stale, bug-missing fork.

Next — The Active Agent: the agent's active mode in depth — how an active agent builds and wires its full sequencer-driver-monitor trio to generate stimulus and observe an interface it controls, the connection between the driver and sequencer, and when an interface calls for an active agent.