Skip to content

UVM

Agent Configuration

The knobs that make one agent serve every use — a dedicated config object aggregating is_active, the virtual interface, and protocol parameters, set per instance by the environment through the config_db before build, retrieved by the agent, and distributed to its sub-components.

Agents · Module 14 · Page 14.6

The Engineering Problem

The module established that one agent class serves every use through configuration (Modules 14.1–14.5): active or passive, this interface or that, this protocol width or that, this instance or another. This closing chapter is the mechanics of that configuration — how, concretely, an agent is configured, and the patterns that make it robust. The problem is real: an agent has many knobs (is_active, the virtual interface, protocol parameters, behavioral options), it has multiple instances (several ports on one DUT), each needing different settings, and the configuration must reach the agent at the right time (before it builds) and the right instance (this agent, not that one). Get the aggregation wrong (knobs scattered loosely) and configuration is unauditable; get the timing wrong (set too late) and the agent builds with defaults; get the per-instance targeting wrong (one config shared across instances) and agents alias each other's settings. The problem this chapter solves is configuring an agent correctly and robustly — one config object, set per instance, before build, distributed down.

Agent configuration is the discipline of driving an agent's behavior through a dedicated config object. A single uvm_object — the agent's configaggregates all its knobs: is_active, the virtual interface handle, protocol parameters (width, ID size, timing mode), and behavioral options (coverage enable, error injection). The environment creates this object, populates it with this instance's choices, and sets it into the config database targeted at the specific agent instancebefore that agent's build_phase. The agent retrieves its config in build_phase, uses it to drive its build (sequencer + driver only if active), and distributes the relevant pieces down to its sub-components (the driver and monitor get the virtual interface and parameters). This chapter is agent configuration: the config object that aggregates the knobs, the config_db set/get that delivers it per instance, the timing that must precede build, the distribution down the hierarchy, and the aliasing bug from sharing one config across instances.

How is an agent configured — through a dedicated config object the environment populates and sets per instance via the config database, before build, that the agent retrieves and distributes to its sub-components — and what patterns make this robust?

Motivation — why a dedicated config object, set per instance

Configuration could be done with scattered, individual config_db entries — but a dedicated config object, set per instance, before build, is far more robust. The reasons:

  • An agent has many related knobs that belong together. is_active, the virtual interface, width, timing mode, coverage enable — these are one coherent set of choices for one agent. Aggregating them in one config object makes them atomic, auditable, and type-safe — versus a scatter of loose config_db entries that are hard to find and easy to mismatch.
  • Each instance needs its own settings. A DUT with three AXI ports has three agent instances — possibly different roles (master/slave), different interfaces, different widths. Configuration must be per instance, targeted at each agent by its path.
  • Configuration must precede build. An agent decides what to build (sequencer + driver, or monitor only) from its config — so the config must be set before the agent's build_phase runs. Top-down phase ordering (parent build sets, child build gets) makes this work; getting it wrong makes the agent build with defaults.
  • The agent distributes config down. The agent receives its config and hands the relevant pieces to its sub-components (the driver and monitor need the virtual interface and parameters). The agent is the distribution point for its interface's configuration.
  • Configuration is owned top-down. The environment (and ultimately the test) owns the configuration — it creates and sets each agent's config; the agent only consumes. This keeps who decides (the environment) separate from who implements (the agent).

The motivation, in one line: an agent has many related knobs, multiple instances, and a build that depends on its config, so configuration is best done with a dedicated config objectaggregating the knobs atomically, set per instance by the environment before build, and distributed down to the sub-components — which is robust, auditable, and per-instance correct in a way scattered entries are not.

Mental Model

Hold agent configuration as filling out the agent's order form before it's assembled:

An agent's config object is its order form: before the agent is assembled, the environment fills out one form with all the choices — which interface, active or passive, what width, which options — hands it over, and the agent builds itself to that order. Each instance gets its own form. Picture ordering a custom-assembled product. The agent is the factory that will assemble itself to your specification. The config object is the order form — a single document listing every choice: which interface it connects to, active or passive, what data width, which options (coverage, error injection). The environment is the customer: it fills out the form with this order's choices and submits itbefore assembly begins. The factory (the agent's build_phase) reads the form first, then assembles itself to match: builds the full mechanism (sequencer + driver) if the form says active, or just the sensor (monitor) if it says passive, wiring in the specified interface and width. Two things make this work. The form must be submitted before assembly — hand it over after the factory has already built the wrong thing, and it's too late (the agent built with defaults). And each order gets its own form — if you're ordering three units, you fill out three separate forms; reusing one form (scribbling new values on the same sheet and resubmitting) means all three units come out matching whatever the form said last (the settings alias). A clean configuration is one form per agent, fully filled, submitted before assembly — so each agent is built exactly to its own order.

So agent configuration is filling out the order form before assembly: the environment (customer) populates a config object (the form) with one instance's choices and submits it (sets it in the config_db) before the agent's build_phase (assembly); the agent (factory) reads it first and builds itself to spec, distributing the relevant details to its sub-components. And the two disciplines are submit-before-assembly (config before build) and one-form-per-unit (a separate config object per instance — never share one). Fill out each agent's own form completely, and submit it before the agent builds.

Visual Explanation — the config object aggregates the knobs

The defining picture is aggregation: a single config object holds all the agent's knobs, set by the environment and read by the agent.

A single config object aggregates is_active, the virtual interface, protocol parameters, and behavioral optionspopulates + setsagent reads in build_phaseagent readsin…Environmentowns configurationAgent config objectis_active · vif · params · optionsAgentbuilds itself to the configis_activeactive / passivevirtual interfacewhich interfaceparams + optionswidth, timing, coverage12
Figure 1 — the config object aggregates all of the agent's knobs in one place. Rather than scattering settings as loose config_db entries, the agent's config object gathers them: is_active (active or passive), the virtual interface handle, protocol parameters (data width, ID size, timing mode), and behavioral options (coverage enable, error injection). The environment populates this one object and sets it for the agent instance; the agent reads it in build_phase. One object, all settings — atomic, auditable, type-safe.

The figure shows the config object as the single aggregation point for the agent's settings. The environment owns the configuration — it populates and sets the agent config object, which aggregates the agent's knobs: is_active (active or passive), the virtual interface (which interface this agent connects to), and parameters + options (data width, ID size, timing mode, coverage enable). The agent reads this one object in build_phase and builds itself to it. The crucial reading is aggregation: all the agent's settings live in one typed object, not scattered as loose, individually-set config_db entries. This buys three things. Atomicity: the agent's whole configuration is one object, set and retrieved as a unit — you can't partially configure it or forget a stray entry. Auditability: one place shows every knob the agent has — open the config class and you see the full configuration surface. Type safety: a typed object (is_active is an enum, vif is the interface type, data_width is an int) catches mismatches at compile time, versus string-keyed config_db entries that fail silently at run time. The warning-colored config object is the contract between the environment (which fills it) and the agent (which reads it) — a single, coherent, typed description of what this agent should be. The diagram is the argument for the dedicated config object: gather the knobs, and configuration becomes a coherent, checkable, auditable act — "here is exactly what this agent is configured to be," in one place.

RTL / Simulation Perspective — set per instance, get before build, distribute down

In code, the environment sets a populated config per agent instance in its own build_phase, and the agent gets it in its build_phase and distributes it down. The code shows the full cycle.

agent configuration: env sets per instance (before build), agent gets and distributes down
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// === ENVIRONMENT: create a SEPARATE config per instance, populate, set, THEN build the agent ===
function void env::build_phase(uvm_phase phase);
  // --- agent 0: its OWN config object ---
  axi_agent_config cfg0 = axi_agent_config::type_id::create("cfg0");
  cfg0.vif = vif0;  cfg0.is_active = UVM_ACTIVE;  cfg0.data_width = 32;
  uvm_config_db#(axi_agent_config)::set(this, "agt0", "cfg", cfg0);   // targeted at agt0, BEFORE build
  agt0 = axi_agent::type_id::create("agt0", this);
 
  // --- agent 1: a DIFFERENT config object (never reuse cfg0!) ---
  axi_agent_config cfg1 = axi_agent_config::type_id::create("cfg1");
  cfg1.vif = vif1;  cfg1.is_active = UVM_PASSIVE;  cfg1.data_width = 64;
  uvm_config_db#(axi_agent_config)::set(this, "agt1", "cfg", cfg1);   // targeted at agt1
  agt1 = axi_agent::type_id::create("agt1", this);
endfunction
 
// === AGENT: GET the config in build_phase, build from it, DISTRIBUTE pieces down ===
function void axi_agent::build_phase(uvm_phase phase);
  if (!uvm_config_db#(axi_agent_config)::get(this, "", "cfg", cfg))   // get BEFORE deciding what to build
    `uvm_fatal("NOCFG", "agent config not set")
  mon = axi_monitor::type_id::create("mon", this);
  if (cfg.is_active == UVM_ACTIVE) begin                              // build decision FROM the config
    seqr = axi_sequencer::type_id::create("seqr", this);
    drv  = axi_driver   ::type_id::create("drv",  this);
  end
  // DISTRIBUTE config down to sub-components (they need the vif + parameters)
  uvm_config_db#(virtual axi_if)::set(this, "mon", "vif", cfg.vif);
  if (cfg.is_active == UVM_ACTIVE)
    uvm_config_db#(virtual axi_if)::set(this, "drv", "vif", cfg.vif);
endfunction

The code shows the full configuration cycle, with the three disciplines visible. The environment, in its build_phase, creates a separate config object per instance (cfg0 for agt0, a different cfg1 for agt1never reusing cfg0), populates each with that instance's choices (agt0 active/32-bit on vif0; agt1 passive/64-bit on vif1), sets each into the config_db targeted at that specific agent (set(this, "agt0", "cfg", cfg0)), and then creates the agent. Timing is the point: the set happens before create — so when the agent's build_phase runs later, the config is already there. The agent, in its build_phase, gets its config (get(this, "", "cfg", cfg)) before deciding what to build, uses it to drive the build decision (mon always; seqr + drv only if is_active == UVM_ACTIVE), and distributes the relevant pieces down — setting the virtual interface into the config_db for its sub-components (mon, and drv if active). The shape to carry: configuration flows down the hierarchy — the environment sets per instance before build, the agent gets and re-distributes to its children — and the three disciplines are set-before-build (the set precedes the agent's build_phase), one-config-per-instance (separate cfg0/cfg1 objects), and distribute-down (the agent hands the vif/params to its sub-components). Each agent ends up built exactly to its own config, and its children receive what they need from it.

Verification Perspective — per-instance configuration

The power of agent configuration is per-instance control: one agent class, instantiated many times, each instance configured differently. Seeing the independence of instances is seeing why configuration is per instance.

One agent class instantiated three times, each instance given its own independent config objectcfg0: active, if0,32bcfg1: passive, if1, 64bcfg1:passive,…cfg2: active slave, if2cfg2:active…Environmentsets each config per instanceAgent instance 0active master · if0 · 32bAgent instance 1passive monitor · if1 · 64bAgent instance 2active slave · if212
Figure 2 — one agent class, many instances, each configured independently. The environment instantiates the same agent class three times — for three interfaces on the DUT — and gives each its own config object: agent 0 active master on interface 0 at 32-bit; agent 1 passive monitor on interface 1 at 64-bit; agent 2 active slave on interface 2. Each config is targeted at its specific instance, so the instances are independent — different roles, interfaces, and parameters, all from one agent class. Per-instance configuration is what lets one class serve every interface a DUT exposes.

The figure shows per-instance configuration — the independence of instances of the same class. The environment instantiates the same agent class three times — for three interfaces on the DUT — and gives each its own config object. Agent instance 0 is an active master on interface 0 at 32-bit; agent instance 1 is a passive monitor on interface 1 at 64-bit; agent instance 2 is an active slave on interface 2. The crucial reading is that each instance is configured independentlydifferent roles (master, monitor, slave), different interfaces, different parametersall from one agent class. Because each config is targeted at its specific instance (by the instance's path), the instances don't interfere: instance 0's is_active is independent of instance 1's, instance 0's vif is independent of instance 2's. The verification insight is that per-instance configuration is what makes one class serve every interface a DUT exposes: a real SoC has dozens of protocol interfaces — masters, slaves, internal monitors, various widths — and one agent class, configured per instance, covers all of them. This is the culmination of the module's theme: the agent is reusable (14.4) and protocol-specific (14.5), and per-instance configuration is the mechanism that deploys it differently at every instance — active here, passive there, this width, that rolewithout duplicating the class. The figure is the payoff of the config object and the per-instance set: a single agent class fanned out across all a DUT's interfaces, each instance precisely configured for its role — one class, many tailored instances. And it only works because each instance gets its own, independently-targeted config — the foundation the DebugLab's aliasing bug violates.

Runtime / Execution Flow — the configuration timeline

Configuration follows a strict timeline: the environment sets each config in its build_phase, before the agent builds; the agent gets and distributes in its build_phase. The flow shows the ordering.

Configuration timeline: env populates and sets each config before creating the agent; agent gets and distributes in its buildenv build: create+populate config per instance → set in config_db (before agent builds) → agent build: get config → build + distribute downenv build: create+populate config per instance → set in config_db (before agent builds) → agent build: get config → build + distribute down1Env: create + populate a config per instancea separate config object for each agent, filled with thatinstance's choices.2Env: set it in config_db, then create the agenttargeted at the specific agent instance — the set precedes theagent's build_phase.3Agent: get the config in build_phaseretrieve it before deciding what to build; fatal if missing.4Agent: build from it and distribute downbuild sub-components per is_active; hand the vif and parameters tothe driver and monitor.
Figure 3 — the configuration timeline, ordered by phase. In the environment's build_phase: create a separate config object per agent, populate it, and set it in the config_db targeted at that agent — then create the agent. Later, in the agent's build_phase: get the config, build the sub-components from it, and distribute the relevant pieces down to them. The ordering is essential — the set must happen before the agent's build_phase runs, or the agent gets defaults. Configuration flows top-down and earlier-to-later in phase order.

The flow shows configuration as a phase-ordered timelinewho does what, when. Env: create + populate (step 1): in the environment's build_phase, create a separate config object per agent instance, filled with that instance's choices. Env: set, then create (step 2): set each config into the config_db targeted at the specific agent instance, then create the agent — so the set precedes the agent's build_phase. Agent: get (step 3): later, in the agent's build_phase, get the config before deciding what to build (fatal if missing). Agent: build + distribute (step 4): build the sub-components per is_active, and distribute the vif and parameters down to the driver and monitor. The runtime insight is that the ordering is not optional — it follows UVM's top-down build_phase execution: a parent's build_phase runs before its children's, so the environment (parent) sets the config before the agent (child) gets it. This is why the environment sets in its own build_phase (not later) and why the agent can rely on the config being there when its build runs. Violate the ordering — set the config after the agent's build (e.g., in connect_phase, or after create has already triggered build in a different flow) — and the agent's get finds nothing, so it builds with defaults or fatals. The flow is the configuration contract in time: environment sets before, agent gets after, distribution flows down — a top-down, earlier-to-later cascade that delivers each agent its config in time to build itself correctly. Respect the phase ordering, and configuration just works; violate it, and the agent is configured too late to matter.

Waveform Perspective — configuration determines run-time behavior

Though configuration happens at build time, its effect is visible at run time: a differently-configured instance of the same agent class behaves differently on its interface. The waveform shows two instances — one configured active, one passive.

Same agent class, two configs: instance 0 (active) drives, instance 1 (passive) only observes

12 cycles
Same agent class, two configs: instance 0 (active) drives, instance 1 (passive) only observesinstance 0 (config: active) — its driver drives the interfaceinstance 0 (config: ac…instance 1 (config: passive) — driven externally; instance only observesinstance 1 (config: pa…a1_mon_capture: passive instance samples, drives nothinga1_mon_capture: passiv…clka0_drv_valida0_data--3C3C----9E9E----------a1_ext_valida1_mon_capturet0t1t2t3t4t5t6t7t8t9t10t11
Figure 4 — configuration selects each instance's run-time behavior. Both instances are the SAME agent class, configured differently at build. Instance 0 was configured active (is_active = UVM_ACTIVE): its driver drives the interface — a0_drv_valid asserts and a0_data carries the driven value. Instance 1 was configured passive (is_active = UVM_PASSIVE): it drives nothing — there is no a1 drive signal — and only observes, a1_mon_capture pulsing as it samples its (externally driven) interface. The build-time config is invisible here, but its effect is plain: active drives, passive observes, from one class.

The waveform shows configuration's run-time effecttwo instances of the same class behaving differently because they were configured differently. Instance 0 was configured active (is_active = UVM_ACTIVE): its driver drives the interface — a0_drv_valid asserts and a0_data carries the driven value (3C, then 9E). Instance 1 was configured passive (is_active = UVM_PASSIVE): it drives nothing — there is no a1 drive signal — and only observes, a1_mon_capture pulsing as it samples its externally-driven interface. The crucial reading is that the build-time configurationinvisible on the waveform (it happened before time zero, in build_phase) — determines the run-time behavior that is visible: active drives, passive observes, from one agent class. This is configuration made observable: you can't see the config object on the timeline, but you can see its consequencewhich agent drives and which only watches is the direct result of each instance's is_active setting. The picture to carry is that configuration is build-time but its effect is run-time: the order form was filled out before assembly, and the finished product's behavior on the interface is exactly what the form specified. Reading two same-class instances' waveforms — does instance 0 drive (active config)? does instance 1 only observe (passive config)?confirms each was configured correctly. The waveform is the proof that per-instance configuration worked: one class, two configs, two distinct correct behaviors — the visible payoff of the invisible, build-time configuration.

DebugLab — the two agents that shared one config

Two agent instances behaving identically because one config object was reused for both

Symptom

An environment configured two AXI agents — agt0 intended for interface 0 (active) and agt1 for interface 1 (passive) — but at run time both agents drove/observed the same interface (interface 1), and both came up with the same mode. agt0 was supposed to be active on vif0; instead it behaved exactly like agt1passive on vif1. The two distinct configurations the environment thought it set had collapsed into one: both agents got whatever was configured last.

Root cause

The environment created one config object and reused it for both agents — mutating its fields between the two set calls. Since the config_db stores a handle (a reference, not a copy), both agents ended up pointing at the same object, which held the last-written values:

why sharing one config object aliased the two agents
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
✗ ONE config object reused for both agents — they ALIAS (both see the last values):
  axi_agent_config cfg = axi_agent_config::type_id::create("cfg");
  cfg.vif = vif0;  cfg.is_active = UVM_ACTIVE;
  uvm_config_db#(...)::set(this, "agt0", "cfg", cfg);    // stores a HANDLE to cfg
  cfg.vif = vif1;  cfg.is_active = UVM_PASSIVE;           // MUTATES the SAME object
  uvm_config_db#(...)::set(this, "agt1", "cfg", cfg);    // stores a HANDLE to the SAME cfg
  // both agents get the SAME object → both see vif1 / PASSIVE (the last writes) → aliased
 
✓ a SEPARATE config object per instance — independent settings:
  axi_agent_config cfg0 = axi_agent_config::type_id::create("cfg0");
  cfg0.vif = vif0;  cfg0.is_active = UVM_ACTIVE;
  uvm_config_db#(...)::set(this, "agt0", "cfg", cfg0);
  axi_agent_config cfg1 = axi_agent_config::type_id::create("cfg1");   // a DIFFERENT object
  cfg1.vif = vif1;  cfg1.is_active = UVM_PASSIVE;
  uvm_config_db#(...)::set(this, "agt1", "cfg", cfg1);
  // each agent gets its OWN object → agt0 = active/vif0, agt1 = passive/vif1 → independent

This is the config-aliasing bug — sharing one config object across instances, which defeats per-instance configuration because the config_db stores a handle, not a copy. The environment created one cfg object, set vif0/active on it, set it for agt0, then mutated the same object to vif1/passive and set it for agt1. The trap is that uvm_config_db::set stores a reference to the object — not a snapshot of its current values. So both set calls stored a handle to the same cfg, and by the time the agents' build_phase ran (later, after both mutations), cfg held its final state — vif1/passive. Both agents get the same object and both read vif1/passive — they alias. agt0's intended config (vif0/active) was overwritten in the shared object before agt0 ever read it. The two logically distinct configurations collapsed into the last-written one. The fix is a separate config object per instance: create cfg0 and cfg1 as different objects, populate each independently, and set each — so each agent gets its own object with its values, independent of the other. The general lesson, and the chapter's thesis: each agent instance needs its own config object — the config_db stores a handle, so reusing and mutating one object across instances makes them alias (all see the last-written values, read at build time, not the values at set time); create a fresh config object per instance and populate each independently, so per-instance configuration actually takes effect. One form per unitnever scribble new values on the same sheet and resubmit.

Diagnosis

The tell is multiple instances sharing settings they were configured to differ on. Diagnose config aliasing:

  1. Check whether one config object is reused across set calls. A single object mutated between set calls for different instances is the aliasing bug.
  2. Confirm instances ended up with the last-written values. If all instances behave like the one configured last, they're sharing a handle to one object.
  3. Look for create-once, set-many. A config object created once and set for multiple agents (with mutations between) aliases them.
  4. Verify each set targets a distinct object. Each instance's set should reference a separately-created config object, not a shared one.
Prevention

Give each instance its own config object:

  1. Create a fresh config object per instance. A separate object for each agent, populated independently, before each set.
  2. Never mutate-and-reset a shared object. The config_db stores a handle, not a copy, so reusing one object across instances aliases them to the last-written values.
  3. Populate fully before setting. Fill each config object completely, then set it for its instance, then move to the next instance's own object.
  4. Verify per-instance behavior. Confirm each instance behaves per its own intended config, not all like the last-configured one.

The one-sentence lesson: each agent instance needs its own config object — the config_db stores a handle, not a copy, so reusing and mutating one object across instances makes them alias (all reading the last-written values at build time); create a fresh config object per instance and populate each independently, so per-instance configuration actually takes effect.

Common Mistakes

  • Reusing one config object across instances. The config_db stores a handle; mutating and re-setting one object aliases the instances — create a separate object per instance.
  • Setting the config after the agent builds. Configuration must be set before the agent's build_phase; set too late and the agent builds with defaults.
  • Scattering knobs as loose config_db entries. Aggregate the agent's settings in one typed config object for atomicity, auditability, and type safety.
  • Targeting the wrong instance. Each set must target the specific agent's path; a mismatched scope means the agent's get fails and it uses defaults.
  • Not distributing config down. The agent must hand the virtual interface and parameters to its sub-components; the driver and monitor need them to build.
  • Letting the agent reach for config instead of receiving it. The environment owns configuration and sets it; the agent only gets and consumes — keep who-decides separate from who-implements.

Senior Design Review Notes

Interview Insights

An agent is configured through a dedicated config object that the environment populates and sets per instance via the config database, before the agent builds. The config object is a single uvm_object that aggregates all the agent's knobs: is_active for active or passive, the virtual interface handle, protocol parameters like data width and ID size and timing mode, and behavioral options like coverage enable. The environment, in its own build_phase, creates this config object, fills it with this instance's choices, and sets it into the config database targeted at the specific agent instance by the agent's path — and it does this before creating the agent. Then, later, in the agent's build_phase, the agent gets its config, uses it to drive its build decisions — building the monitor always but the sequencer and driver only if active — and distributes the relevant pieces down to its sub-components, handing the virtual interface and parameters to the driver and monitor. So configuration flows top-down: the environment owns it and sets it, the agent consumes it and re-distributes it to its children. The key disciplines are aggregation, putting all the knobs in one typed object for atomicity and auditability; per-instance setting, a separate config targeted at each agent; correct timing, the set must precede the agent's build_phase, which works because UVM runs build top-down so the parent sets before the child gets; and distribution, the agent passing config down to its sub-components. The mental model is an order form: the environment fills out one form per agent with all the choices, submits it before assembly, and the agent builds itself to that order. Each instance gets its own form.

Because aggregating the agent's knobs in one typed object gives atomicity, auditability, and type safety that scattered entries don't. An agent has many related knobs — is_active, the virtual interface, width, timing mode, coverage enable — and they're one coherent set of choices for one agent. If you set them as loose, individually-keyed config_db entries, several problems arise. There's no atomicity: you can partially configure the agent or forget a stray entry, and nothing ties the settings together as a unit. There's poor auditability: to see everything the agent can be configured with, you have to hunt through scattered set calls instead of looking in one place. And there's no type safety: string-keyed config_db entries fail silently at run time if a key is misspelled or a type mismatches, whereas a typed config object catches those at compile time — is_active is an enum, vif is the interface type, data_width is an int. With a dedicated config object, the agent's whole configuration is one object, set and retrieved as a unit. You open the config class and see the full configuration surface — every knob the agent has, in one typed declaration. That object is the contract between the environment, which fills it, and the agent, which reads it: a single, coherent, checkable description of what this agent should be. It also travels as part of the VIP, so a consumer gets a clear configuration API. So the dedicated config object turns configuration from a scatter of loose, error-prone entries into a coherent, auditable, type-safe act — here is exactly what this agent is configured to be, in one place. That's why mature UVM code uses per-component config objects rather than ad hoc config_db sets for structured settings.

Because the config database stores a handle to the object, not a copy of its values, so reusing and mutating one config object across instances makes them alias — all reading the last-written values. Suppose you create one config object, set its fields for agent 0, set it in the config_db for agent 0, then mutate the same object's fields for agent 1 and set it for agent 1. The trap is that each set call stored a reference to the same object, not a snapshot of its current state. So both agents have a handle to the one shared object. By the time the agents' build_phase runs — which is later, after both mutations — that object holds its final state, the values written for agent 1. Both agents get the same object and both read agent 1's values. Agent 0's intended configuration was overwritten in the shared object before agent 0 ever read it. The two logically distinct configurations collapse into the last-written one, and the agents alias — they behave identically when they were supposed to differ. The fix is to create a separate config object per instance: a different object for agent 0 and for agent 1, each populated independently and set for its own instance. Then each agent gets its own object with its own values, independent of the other. This is essential precisely because configuration is per-instance — the whole point is that each instance can differ, and that only works if each has its own config object. The rule is one config object per instance, populate each fully before setting it, and never mutate-and-reset a shared object. The order-form analogy is one form per unit — you never scribble new values on the same sheet and resubmit, or every unit comes out matching whatever the form said last.

Exercises

  1. Aggregate the knobs. List the settings an agent's config object should hold, and the three benefits of aggregating them versus scattered config_db entries.
  2. Order the timeline. Describe the phase ordering for setting and getting an agent's config, and what goes wrong if the set is too late.
  3. Fix the aliasing. Given an environment that reuses one config object for two agents, explain the bug and the fix.
  4. Distribute down. Describe how an agent passes the virtual interface to its driver and monitor, and why the agent is the distribution point.

Summary

  • Agent configuration drives an agent's behavior through a dedicated config object — a single typed uvm_object that aggregates all the agent's knobs (is_active, the virtual interface, protocol parameters, behavioral options) — for atomicity, auditability, and type safety.
  • The environment owns configuration: it creates a config object, populates it with this instance's choices, and sets it into the config_db targeted at the specific agent instance, before that agent's build_phase.
  • The agent gets and distributes: it retrieves its config in build_phase, builds its sub-components from it (sequencer + driver only if active), and distributes the virtual interface and parameters down to its driver and monitor.
  • Per-instance configuration is the mechanism that lets one agent class serve every interface a DUT exposes — each instance configured independently (role, interface, width) — provided each instance gets its own config object (the config_db stores a handle, so sharing one aliases them).
  • The durable rule of thumb: configure an agent through one typed config object per instance — aggregate every knob in it, create a fresh object for each agent and populate it independently, set it in the environment's build_phase before the agent builds, and let the agent get it and distribute the interface and parameters down; reusing one config object across instances aliases them to the last-written values, and setting too late leaves the agent with defaults.

Next — Environment Architecture: the module assembled the agent; the next module assembles agents into environments. Module 15 opens with the environment — the container that instantiates and connects multiple agents, the scoreboard, and coverage into a complete, reusable verification environment for a DUT or subsystem.