Part 6 · Agents & Protocol IP · Intermediate

VIP Parameterization: Compile-Time Widths vs Runtime Knobs

How to choose between type parameters and cfg fields to maximize reuse while preserving type safety and maintainable integration APIs.

Parameterization goals

A reusable VIP should expose only meaningful variation points. Over-parameterization creates complexity and verification burden, while under-parameterization forces forks.

Use a simple rule: structural protocol shape belongs to compile-time parameters; scenario behavior belongs to runtime cfg fields.

diagram
[VIP] decision matrix

compile-time parameter:
  affects transaction type width/packing
  affects interface signal dimensions
  affects class type signatures

runtime cfg field:
  affects timing policy
  affects checker enable/disable
  affects mode and logging behavior
diagram
[AGT] practical examples

parameter candidates:
  ADDR_W, DATA_W, ID_W, USER_W

cfg candidates:
  is_active, max_outstanding, timeout_cycles
  checks_enable, coverage_enable, protocol_strictness
  • Expose structural dimensions as parameters for type-safe integration.

  • Keep behavioral switches in cfg to avoid class explosion.

  • Minimize public knobs to what consumers can reason about clearly.


Type-safe generic agent pattern

Parameterize sequence items, interfaces, and agent classes consistently so downstream code remains type-compatible and compile-time checked.

systemverilog
class bus_item #(int ADDR_W = 32, int DATA_W = 64) extends uvm_sequence_item;
  `uvm_object_param_utils(bus_item#(ADDR_W, DATA_W))
  rand bit [ADDR_W-1:0] addr;
  rand bit [DATA_W-1:0] data;
  rand bit              write;

  function new(string name = "bus_item");
    super.new(name);
  endfunction
endclass
systemverilog
class bus_agent_cfg extends uvm_object;
  `uvm_object_utils(bus_agent_cfg)
  uvm_active_passive_enum is_active = UVM_ACTIVE;
  int unsigned timeout_cycles = 256;
  bit checks_enable = 1;
  bit coverage_enable = 1;

  function new(string name = "bus_agent_cfg");
    super.new(name);
  endfunction
endclass
systemverilog
class bus_agent #(int ADDR_W = 32, int DATA_W = 64) extends uvm_agent;
  `uvm_component_param_utils(bus_agent#(ADDR_W, DATA_W))
  bus_agent_cfg cfg;
  bus_monitor#(ADDR_W, DATA_W) mon;
  bus_driver#(ADDR_W, DATA_W)  drv;
  bus_sequencer#(ADDR_W, DATA_W) sqr;

  function void build_phase(uvm_phase phase);
    super.build_phase(phase);
    if (!uvm_config_db#(bus_agent_cfg)::get(this, "", "cfg", cfg))
      `uvm_fatal("CFG", "bus_agent_cfg missing")
    mon = bus_monitor#(ADDR_W, DATA_W)::type_id::create("mon", this);
    if (cfg.is_active == UVM_ACTIVE) begin
      drv = bus_driver#(ADDR_W, DATA_W)::type_id::create("drv", this);
      sqr = bus_sequencer#(ADDR_W, DATA_W)::type_id::create("sqr", this);
    end
  endfunction
endclass
  • Tie all dependent classes to shared width parameters.

  • Avoid runtime fields that alter transaction type dimensions.

  • Use param utils macros for UVM factory compatibility.


Controlling configuration surface area

A public cfg schema is an API. Every exposed field becomes a long-term support commitment, so categorize knobs into stable, advanced, and internal tiers.

diagram
[VIP][CFG] schema tiers

Stable (documented for all users):
  is_active, timeout_cycles, checks_enable, coverage_enable

Advanced (documented with caution):
  protocol_relaxation_mode, random_gap_enable

Internal (not public contract):
  temporary debug hooks, experimental algorithm toggles
diagram
[AGT] API discipline rules

1) every public field has default, valid range, and behavior note
2) every field change assessed for backward compatibility
3) deprecated fields retained with warning period
4) internal fields not consumed directly by external tests
systemverilog
function void validate_cfg(bus_agent_cfg c);
  if (c.timeout_cycles == 0)
    `uvm_fatal("CFG", "timeout_cycles must be > 0");
  if (c.is_active != UVM_ACTIVE && c.is_active != UVM_PASSIVE)
    `uvm_fatal("CFG", "invalid is_active enum value");
endfunction
  • Treat cfg schema as semantically versioned contract.

  • Validate cfg values early to avoid latent runtime failures.

  • Separate consumer-facing knobs from internal experiments.


Parameterization anti-patterns and migration

When legacy VIP already has poor parameter/cfg boundaries, migrate incrementally: freeze old fields, introduce canonical replacements, and provide compatibility wrappers.

diagram
[VIP] common anti-patterns

anti-pattern A:
  runtime integer changes transaction width
  consequence: broken type assumptions

anti-pattern B:
  separate cfg knobs conflict semantically
  consequence: undefined behavior combinations

anti-pattern C:
  parameter every protocol feature
  consequence: class permutation explosion
diagram
[UVM] migration plan

step 1: document canonical structural parameters
step 2: mark conflicting runtime width knobs deprecated
step 3: add translation layer for old tests
step 4: emit warnings with upgrade hints
step 5: remove deprecated knobs in next major version
diagram
[VIP][AGT] consumer guidance

if you need different bus width:
  instantiate parameterized agent type

if you need different timeout/checking:
  edit cfg object values

if both needed:
  choose type at env construction, then apply cfg policy

Key takeaways

  • Compile-time parameters model protocol structure; cfg fields model behavior policy.

  • A small, validated cfg schema improves usability and long-term support.

  • Type-consistent parameterization prevents integration mismatches.

  • Migration from legacy knobs should be staged and semantically versioned.

Common pitfalls

  • Using runtime fields to alter compile-time transaction shape.

  • Growing cfg schema without defaults, ranges, or documentation.

  • Breaking old tests silently during parameterization cleanup.

  • Publishing experimental internal knobs as stable API fields.