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
    • Reusable VIP: Active, Passive and Protocol Agents
      • Active and Passive in Delivery: What a VIP Owes Its Integrator
      • Two Passive Monitors, One Bus
      • VIP Parameterization: Widths Up Front, Behavior at Runtime
      • Protocol Agent Patterns: APB, AXI and Streaming
      • Versioning, Delivery and Release Readiness
    • 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

‹Reusable VIP: Active, Passive and Protocol Agents
  1. Topics
  2. UVM Verification
  3. UVM Agents & Protocol VIP
  4. Reusable VIP: Active, Passive and Protocol Agents
  5. VIP Parameterization: Widths Up Front, Behavior at Runtime

Part 6 · Agents & Protocol IP · Intermediate

VIP Parameterization: Widths Up Front, Behavior at Runtime

Which variation points are parameters and which are configuration knobs, the max-width alternative, parameterized classes and the factory, and a validated config object.

On this page

    Your stream agent is a hit, and three projects want it. One has a 64-bit stream with TKEEP. One has 32 bits like the DMA but needs TUSER. The third wants the same agent on eight ports with different timeouts on each. You could fork the agent three times; within a year the forks will have three different bug lists. Or you could make it configurable, and then the question is how: which differences become SystemVerilog parameters, which become fields in a configuration object, and which should not be options at all?

    The idea: shape is compiled, behaviour is chosen

    Some differences change the shape of the code: how many bits a field has, how many wires the interface has, what type a transaction is. SystemVerilog fixes those at elaboration, so they are parameters. Other differences change behaviour: whether the agent drives, how long it waits before calling a timeout, whether coverage is on. Those can change per test or per instance without recompiling, so they are configuration knobs, fields of a config object read in build_phase.

    A car factory is a fair picture. The chassis length and the engine mount are decided when the production line is set up; changing them means retooling (recompiling). The seat position, the radio station and the drive mode are chosen by each driver every morning (runtime config). Putting a seat position in the tooling, or the chassis length in a menu, makes both jobs harder.

    The picture: where does this option belong?

    Decision flowchart: if an option changes a type, width or pin count it becomes a parameter, or a max-width build with a runtime width if one compile must serve all values; otherwise, if anyone needs a different value it becomes a validated cfg field, else it stays a constant.
    Decide each option before adding it. Every option a VIP exposes is something its regression must test.

    Build it

    Parameters: one generic agent, many specializations

    A parameterized agent carries the width through every class that touches data: item, driver, monitor, sequencer, agent. Each is registered with the _param_utils macros, because the plain macros register a type name, and a parameterized class has no single name.

    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
    // snippet: the stream agent parameterized by data width
    class axis_item_p #(int DW = 32) extends uvm_sequence_item;
      `uvm_object_param_utils(axis_item_p #(DW))
      rand bit [DW-1:0]   data;
      rand bit [DW/8-1:0] keep;                  // one TKEEP bit per byte
      rand bit            last;
      constraint c_keep { soft keep == '1; }     // full beats unless a test asks otherwise
      function new(string name = "axis_item_p");
        super.new(name);
      endfunction
    endclass
    
    class axis_agent_p #(int DW = 32) extends uvm_agent;
      `uvm_component_param_utils(axis_agent_p #(DW))
      axis_monitor_p #(DW)            mon;
      axis_driver_p #(DW)             drv;
      uvm_sequencer #(axis_item_p #(DW)) sqr;
      function new(string name, uvm_component parent);
        super.new(name, parent);
      endfunction
      virtual function string get_type_name();   // the param macros do not define it
        return $sformatf("axis_agent_p#(%0d)", DW);
      endfunction
      // build_phase and connect_phase as in axis_agent, with the #(DW) types
    endclass
    
    // In the VIP package: name the specializations projects actually use.
    typedef axis_agent_p #(32) axis32_agent;
    typedef axis_agent_p #(64) axis64_agent;

    What the factory can and cannot do with them

    The _param_utils macros register the class by type only. Three consequences follow, and each one surprises someone on every project:

    Operation

    Plain class

    Parameterized class

    type_id::create() and type-based overrides

    Works

    Works, with the full specialization: axis_agent_p#(64)::type_id::set_type_override(...)

    Override by type name (string), including +uvm_set_type_override on the command line

    Works

    Silently does nothing: no component is ever requested by that name

    Name in print_topology and factory.print()

    The class name

    The nearest base class's name (uvm_agent, uvm_component) unless you override get_type_name()

    Override across specializations

    n/a

    Impossible: axis_agent_p#(32) and #(64) are unrelated types

    The same "every specialization is a different type" rule applies to the virtual interface: a config_db set of virtual axis_if #(64) is invisible to a get of virtual axis_if #(32). That trap, and the abstract-BFM pattern that removes it, are covered in Interface abstraction.

    The alternative: build for the maximum, choose at runtime

    Many commercial VIPs avoid class parameters altogether. Every data field is sized to a compile-time maximum (often a compile-time define such as AXIS_MAX_DW), and the active width is a config field. One compile then serves every port, overrides by name work, and the topology prints real names. The price is that width correctness moves from the compiler to your code: the driver must zero unused bits, the monitor must mask them, and validate() must reject a width above the maximum. Both strategies are legitimate. Pick one per VIP, document it, and do not mix them inside one agent.

    Knobs: a config object with a contract

    Runtime options live in one config object per agent, which the env or test creates, fills and sets before the agent is built. The object is the VIP's public API, so each field gets a default, a documented range and a check. Checking in build_phase makes a bad value fail at time 0 with a message that names the field, instead of failing an hour later as a mysterious hang.

    design.sv
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    // snippet: a stream agent config with validation
    class axis_cfg extends uvm_object;
      `uvm_object_utils(axis_cfg)
      uvm_active_passive_enum is_active      = UVM_ACTIVE;
      int unsigned            data_width     = 32;    // runtime width, max-width strategy only
      int unsigned            ready_timeout  = 1000;  // clocks before a stall is an error
      bit                     coverage_on    = 1;
      bit                     checks_on      = 1;
      function new(string name = "axis_cfg");
        super.new(name);
      endfunction
      virtual function void validate(uvm_component who);
        if (!(data_width inside {8, 16, 32, 64}))
          `uvm_fatal_context("CFG", $sformatf("data_width=%0d: must be 8, 16, 32 or 64", data_width), who)
        if (ready_timeout < 16)
          `uvm_fatal_context("CFG", $sformatf("ready_timeout=%0d: minimum is 16", ready_timeout), who)
      endfunction
    endclass

    Tier the fields. Stable fields (mode, timeouts, check and coverage switches) are promised across releases. Advanced fields carry a warning in the user guide. Internal hooks stay out of the config object entirely, because whatever is in it, someone will set. And whatever range validate() enforces is the range the user guide states, in the same words.

    Watch it run

    A scratch run builds two parameterized components side by side: p_comp#(8) registered with the macro alone, and p_comp_named#(16) with a get_type_name() override like the one in the listing. It was started with +uvm_set_type_override=p_comp,p_comp_named. The topology it printed:

    Diagram
    Name            Type               Size  Value
    ----------------------------------------------
    <unnamed>       uvm_root           -     @75
      uvm_test_top  param_topo_test    -     @189
        pa          uvm_component      -     @259
        pb          p_comp_named#(16)  -     @267
    • pa is listed as uvm_component: with no get_type_name() of its own, the class reports its nearest base class's name. A parameterized agent would show up as uvm_agent, which looks plausible and is wrong, so nobody questions it.

    • pb shows its real specialization, thanks to the override.

    • The command-line override by name changed nothing and printed no message at all: pa was built as p_comp#(8). A by-name override for a class that is only registered by type is silently ignored.

    What breaks: the timeout that was set and never read

    A project wanted a longer ready timeout on one slow port, and its test did uvm_config_db#(int)::set(this, "env.slow_agt*", "ready_timeout", 5000);. The VIP, at that time, read each knob as its own config_db entry with uvm_config_db#(int unsigned)::get(...). The types did not match, so the get found nothing and the agent kept its default of 1000. The test failed with a timeout error on exactly the port it had tried to fix, and two engineers spent an afternoon proving the set was at the right path.

    The fix was structural: the VIP moved every knob into axis_cfg, set as one object. A misspelt or mistyped field in a config object is a compile error; a mistyped config_db key is silence. Separate config_db entries per knob are still common in older VIP, which is exactly why +UVM_CONFIG_DB_TRACE exists and why it is the first switch to try when a setting seems ignored.

    Interview

    "How do you decide what to parameterize in a VIP?"

    A weak answer: "Make everything a parameter so it is flexible." Every parameter multiplies compiles and types, and parameterized classes cannot be overridden by name.

    A strong answer: "Parameters only for what changes types or pin counts: data, address, ID and user widths. Everything else is a field in one config object with a default, a documented range and a validate() call in build. Parameterized classes register by type only, so I typedef the common specializations in the package, override get_type_name so topology and factory prints are readable, and remember there is no by-name or command-line override for them. If one compile must cover many widths, I use the max-width strategy instead, with the active width in cfg and masking in the driver and monitor. Every option I add is one more thing the VIP regression must cover, so I add few."

    Follow-ups to expect: Why can't I override `axis_agent_p#(32)` with `axis_agent_p#(64)`? (unrelated types) and Where does validation run? (in the agent's build_phase, before anything uses the values).

    Check yourself A test tries +uvm_set_type_override=axis_agent_p,my_axis_agent on the command line. What happens?

    Nothing, and silently: a parameterized class registered with uvm_component_param_utils has no name in the factory, and components are requested by type, so no request ever matches the name axis_agent_p. The original type is built with no warning. Use a type-based override in code with the full specialization instead.

    Check yourself A project needs a per-instance inter-beat gap limit. Parameter or config field, and why?

    A config field. It changes behaviour, not types or pin counts, and it differs per instance, which a parameter could only do with a separate specialization per value. As a cfg field it gets a default, a range check in validate(), and one compile serves every instance.

    Next: width and knobs are the same for every protocol. What differs is the protocol itself: Protocol agent patterns compares what APB, AXI and AXI-Stream agents must each get right.

    Related topics

    • Agent configuration

    • Interface abstraction

    • Factory: advanced use

    • Protocol agent patterns

    Previous

    104 / 280

    Next: Protocol Agent Patterns: APB, AXI and Streaming

    Up next

    Up nextProtocol Agent Patterns: APB, AXI and Streaming

    Sign in to save progress

    Practice this lessonReport an error

    See also

    • Active and Passive in Delivery: What a VIP Owes Its Integrator
    • Versioning, Delivery and Release Readiness