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.
[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[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_strictnessExpose 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.
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
endclassclass 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
endclassclass 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
endclassTie 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.
[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[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 testsfunction 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");
endfunctionTreat 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.
[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[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[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 policyKey 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.