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. Agent Configuration: Knobs, Config Objects and Who Owns Them

Part 6 · Agents & Protocol IP · Intermediate

Agent Configuration: Knobs, Config Objects and Who Owns Them

How an agent learns its mode, interface and policy: config_db knobs versus config objects, get-or-create defaults, validation, and the shared-handle trap.

On this page

    The DMA team builds a two-DMA subsystem by instantiating the block env twice. The first run fills the log with X on the middle bus. Both dma0's output and dma1's input agent are driving it: dma0 because it is the DUT's output, dma1's agent because nobody told it that, at this level, its input is fed by another DUT. The class was right. Its configuration was missing.

    An agent is built by someone else, at a path it did not choose, for a context it cannot see. How does it find out what it is supposed to be?

    The idea: an envelope left at the agent's address

    Configuration is how a parent tells a child about the world before the child is built. The parent leaves an envelope in uvm_config_db addressed to the child's hierarchical path; the child opens it in its build_phase. There are two ways to fill the envelope. Knobs are individual typed entries: the bench sets is_active for out_agt as one uvm_active_passive_enum entry, and each virtual interface as one virtual apb_if entry. A config object is one uvm_object holding every setting for a component, passed as a single handle.

    Knobs are fine for two or three settings. Past that they scatter: twelve set calls with twelve string names, any of which can be misspelt, and no single place that says what the agent's settings are. A config object turns those into fields with types and defaults, can carry constraints and a validate() method, and travels as one handle. Commercial VIPs almost always use one per agent instance.

    The picture: who configures whom in the subsystem bench

    Configuration flow: the top module sets virtual interfaces for each agent path; the test optionally sets a dma_subsys_cfg; the subsystem env reads it or creates a default, then marks dma1.in_agt passive when chain_internal is set; each block env marks its own out_agt passive.
    Configuration in the two-DMA subsystem. The top module supplies interfaces; the test may supply a subsystem config object; the subsystem env turns that object into a knob for dma1's input agent. The block env inside is unchanged.

    Configuration flows down the tree, one level translating it for the next. The subsystem env reads a high-level fact ("dma1's input is fed internally") from its config object and turns it into the low-level knob the agent understands. The agent never learns why it is passive.

    Build it: three kinds of configuration in the bench

    A knob: is_active as one config_db entry

    In dma_env::build_phase (shown in the previous lesson), one line makes the output agent passive: uvm_config_db#(uvm_active_passive_enum)::set(this, "out_agt", "is_active", UVM_PASSIVE);. The context this plus the relative path "out_agt" addresses exactly env.out_agt. The virtual interfaces are knobs too, set from the top module with a null context and full paths such as "uvm_test_top.env.apb.*"; the driver and monitor each get theirs with get(this, "", "vif", vif) and stop with a fatal if it is missing.

    An env config object with a get-or-create default

    The subsystem env has a small config object of its own. It is not randomized and holds two switches, each with a sensible default:

    design.sv
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    // snippet: from the course DMA bench
    // Two DMAs in a chain (dma0's output feeds dma1's input), verified by reusing the
    // block env twice, unchanged, plus an end-to-end checker.
    class dma_subsys_cfg extends uvm_object;
      bit has_e2e_checker = 1;                   // the end-to-end scoreboard
      bit chain_internal  = 1;                   // dma1's input is driven by dma0, so its agent is passive
      `uvm_object_utils(dma_subsys_cfg)
      function new(string name = "dma_subsys_cfg");
        super.new(name);
      endfunction
    endclass

    The env that consumes it shows the pattern to copy: get first, create a default only if nothing was set. A test that wants a different topology sets its own object; a test that does not care gets the defaults without writing a line.

    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
    28
    29
    30
    31
    // snippet: from the course DMA bench
    class dma_subsys_env extends uvm_env;
      `uvm_component_utils(dma_subsys_env)
      dma_subsys_cfg     cfg;
      dma_env            dma0, dma1;             // the block env, reused as is
      dma_e2e_scoreboard e2e;
    
      function new(string name, uvm_component parent);
        super.new(name, parent);
      endfunction
    
      function void build_phase(uvm_phase phase);
        super.build_phase(phase);
        if (!uvm_config_db#(dma_subsys_cfg)::get(this, "", "cfg", cfg))
          cfg = dma_subsys_cfg::type_id::create("cfg");   // defaults if the test set none
        if (cfg.chain_internal)
          uvm_config_db#(uvm_active_passive_enum)::set(this, "dma1.in_agt", "is_active", UVM_PASSIVE);
        dma0 = dma_env::type_id::create("dma0", this);
        dma1 = dma_env::type_id::create("dma1", this);
        if (cfg.has_e2e_checker) e2e = dma_e2e_scoreboard::type_id::create("e2e", this);
      endfunction
    
      function void connect_phase(uvm_phase phase);
        if (cfg.has_e2e_checker) begin
          dma0.apb.ap.connect(e2e.key0_export);
          dma1.apb.ap.connect(e2e.key1_export);
          dma0.in_agt.ap.connect(e2e.first_export);
          dma1.out_agt.ap.connect(e2e.last_export);
        end
      endfunction
    endclass

    Two details matter. The object is created with the factory (type_id::create), so a test could override the config class itself. And the env sets the agent knob after reading its own config and before creating the child envs, which is the order in which the facts become known.

    A randomizable config object for the scenario

    dma_job_cfg describes one DMA job (key, length, interrupt enable) with constraints. dma_rand_test looks for one in config_db and randomizes a fresh one only if none was set, so a derived test can pin a job without touching the loop. The full treatment, including polymorphic config and validation, is in Config objects; the point here is the shape: the owner that consumes the object is the one that decides between "use what I was given" and "make my own".

    What an agent config object would look like

    The bench keeps its agents on knobs to stay small. A production APB agent would carry one object per instance. This sketch shows what goes in it and how the agent uses it:

    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
    // snippet
    class apb_agent_cfg extends uvm_object;
      uvm_active_passive_enum is_active = UVM_ACTIVE;
      virtual apb_if vif;                      // one home for the interface handle
      bit          has_checks   = 1;           // protocol checks in the monitor
      bit          has_coverage = 0;
      int unsigned max_wait     = 16;          // PREADY timeout, in clocks
    
      `uvm_object_utils(apb_agent_cfg)
      function new(string name = "apb_agent_cfg");
        super.new(name);
      endfunction
    
      function bit validate(output string why);
        if (is_active == UVM_ACTIVE && vif == null) begin why = "active agent with no vif"; return 0; end
        if (max_wait == 0) begin why = "max_wait must be at least 1"; return 0; end
        return 1;
      endfunction
    endclass
    
    // Inside the agent:
    //   build_phase: get "cfg" (fatal if missing), call cfg.validate(), hand cfg.vif to children.
    //   get_is_active() is virtual: override it to return cfg.is_active,
    //   so the mode has one source of truth.

    Pick one home for each setting. If the vif lives in the config object, the driver and monitor get it from there, not from a second config_db entry that might disagree. If the mode lives in the object, override get_is_active() to return cfg.is_active, and every piece of code that asks the agent its mode gets the same answer.

    When two components set the same thing

    During build_phase, a set from higher in the hierarchy wins: the test (depth 1) beats the env (depth 2), whatever paths they use. Between sets at equal precedence, and for every set made after build, the last set wins. That is why the get-or-create pattern above is safe: a test-supplied object always beats anything the env might set. The full rules, with worked cases, are in config_db resolution; turn on +UVM_CONFIG_DB_TRACE when a get returns something you did not expect.

    Watch it run

    Run dma_subsys_test with the subsystem file (scripts/data/uvm-course/bench/dma_subsys_tb.sv). The test sets no dma_subsys_cfg, so the env creates the default: end-to-end checker on, dma1's input passive.

    Diagram
    UVM_INFO dma_tb.sv(411) @ 875000: uvm_test_top.env.dma0.sb [SB] 10 words matched, 0 mismatched
    UVM_INFO dma_tb.sv(411) @ 875000: uvm_test_top.env.dma1.sb [SB] 10 words matched, 0 mismatched
    UVM_INFO dma_tb.sv(1178) @ 875000: uvm_test_top.env.e2e [E2E] 10 words matched end to end, 0 mismatched
    UVM_ERROR :    0
    UVM_FATAL :    0
    • dma1.sb matched 10 words although dma1's input agent drives nothing. Its passive monitor watched dma0's output on the shared middle bus and fed dma1's scoreboard. That is configuration doing its job.

    • The e2e line exists because has_e2e_checker defaulted to 1. A test that sets a cfg with it cleared would get the same two block lines and no third.

    What breaks: one config object, two agents

    A team converted their stream agents to config objects. To save lines, the env created one axis_agent_cfg, set it for both in_agt and out_agt, and then wrote cfg.is_active = UVM_PASSIVE for the output side. Every test hung. The input agent had no driver, so no word ever entered the DMA, no interrupt came, and the run ended on the global timeout. The topology print told the story to anyone who looked: neither agent had a driver.

    The root cause is that a config object is passed by handle. Both agents held the same object, so the line meant for one changed both. The fix is one object per agent instance (create two, or clone() a template and then change the copy). The same trap catches shared objects changed at run time: a test that edits cfg.max_wait mid-run changes it for every agent holding that handle, which is sometimes what you want and must always be deliberate.

    Interview

    "How do you configure an agent? config_db knobs or a config object?"

    A weak answer: "I use config_db to set the values." It does not say which values, who sets them, or what happens on a clash.

    A strong answer: "One config object per agent instance, created by the env or test that owns the agent, set into config_db before the agent is built, and checked by a validate() call in the agent's build_phase. The object holds the mode, the vif and policy knobs, and the agent overrides get_is_active() to read it, so there is one source of truth. The env uses get-or-create, so a test-supplied object wins; during build the higher setter wins anyway. I never share one object between two agents unless I mean them to change together."

    Expect the follow-ups: Why does the test beat the env even if the env sets later? (build-time precedence is 1000 minus the setter's depth) and Should the vif go in the object or in its own entry? (either, but only one; two copies can disagree).

    Check yourself The env always creates a fresh apb_agent_cfg and sets it for apb. A test also sets one, during its own build_phase. Which does the agent get, and is the env's code still wrong?

    The agent gets the test's object: the test is higher in the hierarchy, so its build-time set wins regardless of order. The env's code is still poor style. It does work every run that nobody uses, and it hides intent: a reader cannot tell that a test is allowed to replace the object. Get first, and create a default only when the get fails.

    Check yourself Why does dma_subsys_env set is_active for dma1.in_agt itself, instead of letting the test do it?

    Because "dma1's input is driven by dma0" is a fact about the subsystem's topology, which the subsystem env owns. Tests choose scenarios; they should not need to know the path of an agent two levels down. The env translates its own config switch, chain_internal, into the agent's knob.

    Check yourself An agent's build_phase reads its config object and calls cfg.validate(), which fails. Fatal, error or warning?

    Fatal. An invalid configuration means the bench is not the bench the test thinks it is; continuing produces failures that look like DUT bugs. Stopping at time 0 with a message that names the bad field costs seconds; debugging a misconfigured run costs hours.

    Next: the configuration said passive. In Mode-gated build you will see exactly where the agent reads that, what it builds as a result, and how a derived agent can lose its monitor by skipping one line.

    Related topics

    • Mode-gated build

    • Config objects

    • config_db resolution

    • Configuring sequences

    Previous

    95 / 280

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

    Up next

    Up nextMode-Gated Build: One Agent Class, Active or Passive

    Sign in to save progress

    Practice this lessonReport an error

    See also

    • 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