Part 3 · Constraint Randomization · Intermediate
Constraint Override via Inheritance
Same-named constraint blocks replace the parent's, test-specific subclasses, factory substitution, and override vs constraint_mode.
Same name replaces; new name adds
Constraint blocks are inherited like methods, and like methods they are virtual by name : a subclass block with the same name completely replaces the parent's block for objects of the subclass — the parent's expressions in that block are gone, not merged. A subclass block with a new name simply adds another block that intersects with everything inherited. This name-based replace/add distinction is the entire mechanism, and the only relaxation tool besides constraint_mode.
class base_txn;
rand bit [7:0] len;
rand bit [3:0] prio;
constraint len_c { len inside {[1:16]}; }
constraint prio_c { prio < 8; }
endclass
class jumbo_txn extends base_txn;
// SAME NAME → REPLACES parent's len_c entirely (relaxation works!)
constraint len_c { len inside {[1:255]}; }
endclass
class urgent_txn extends base_txn;
// NEW NAME → ADDS; intersects with inherited len_c and prio_c
constraint urgent_c { prio inside {[6:7]}; }
endclass
// jumbo_txn: len ∈ [1:255] (parent's [1:16] is GONE), prio < 8
// urgent_txn: len ∈ [1:16], prio ∈ {6,7} (prio<8 ∩ [6:7])Because replacement is total, an overriding block must restate any parent expressions it still wants — a maintenance hazard when the parent block mixes several concerns. This is another argument for small single-purpose blocks: override granularity equals block granularity.
Test-specific transaction subclasses
The standard methodology pattern: the base transaction encodes protocol legality; each test (or test family) defines a thin subclass whose only job is to reshape distributions or relax/replace specific blocks. The subclass is a named, reviewable, reusable artifact — unlike a pile of inline with blocks scattered through sequences.
// Base: protocol legality only
class axi_txn;
rand bit [31:0] addr;
rand bit [7:0] len;
rand bit write;
constraint legal_c { len inside {[1:16]}; addr[1:0] == 0; }
constraint shape_c { soft write dist { 1 :/ 1, 0 :/ 1 }; }
endclass
// Test personality 1: write-heavy small bursts near a hotspot
class hotspot_txn extends axi_txn;
constraint shape_c { // replaces base shape_c
write dist { 1 :/ 9, 0 :/ 1 };
len inside {[1:4]};
addr inside {[32'h8000:32'h80FF]};
}
endclass
// Test personality 2: out-of-spec lengths for error checking
class badlen_txn extends axi_txn;
constraint legal_c { addr[1:0] == 0; } // replaces: len rule DROPPED
constraint bad_c { len inside {[17:64]}; }
endclassFactory substitution: override without touching sequences
Subclass override becomes powerful at scale when combined with a factory (UVM's, or a hand-rolled one): sequences keep creating what they believe is the base type, and the factory returns the test's subclass instead. Every randomize() in every reused sequence now solves the subclass's constraint set — zero sequence edits.
// UVM flavor — in the test's build_phase:
// axi_txn::type_id::set_type_override(hotspot_txn::get_type());
// Every sequence doing axi_txn::type_id::create("t") now gets hotspot_txn,
// and t.randomize() solves hotspot_txn's blocks (legal_c + new shape_c).
// The same idea without UVM — virtual "create" hook:
class txn_factory;
static axi_txn proto = null; // test installs a prototype
static function axi_txn create();
if (proto == null) begin axi_txn t = new(); return t; end
return proto.clone_empty(); // returns subclass instance
endfunction
endclassFACTORY + CONSTRAINT OVERRIDE FLOW
test build_phase:
set_type_override(axi_txn → hotspot_txn)
│
reusable sequence (UNCHANGED):
t = axi_txn::type_id::create("t") ──► actually hotspot_txn
t.randomize() ──► solves:
│ legal_c (inherited)
│ shape_c (OVERRIDDEN ver.)
▼
one line in the test reshapes EVERY transaction in EVERY sequenceOverride vs constraint_mode
Override is per-TYPE and compile-time-defined: every instance of the subclass behaves the new way; nothing to restore at runtime.
constraint_mode is per-INSTANCE and procedural: flexible mid-sequence, but sticky state someone must manage.
Override can REPLACE expressions (change a range); constraint_mode can only remove a whole block (all-or-nothing).
Override + factory scales to a whole environment with one line; constraint_mode requires a handle to every object you want to alter.
Use constraint_mode for temporary in-sequence toggles; use override for a test's standing policy.
Interview angle
What interviewers ask
“What happens when a subclass declares a constraint with the parent's block name?” — full replacement of that block for subclass objects, enabling relaxation. New names add and intersect.
“How do you change transaction constraints for one test without editing sequences?” — subclass with overriding blocks + factory type override; one build_phase line.
“Does the parent's replaced block still apply somehow?” — no; replacement is total, restate anything you still need.
“Override vs constraint_mode?” — type-level standing policy vs instance-level runtime toggle; replace-content vs remove-block.
“Why keep constraint blocks small?” — override granularity equals block granularity; big blocks force overrides to restate unrelated rules.
Key takeaways
Same-named subclass constraint blocks replace the parent's entirely; new names add and intersect.
Override is the clean relaxation mechanism — the parent's expressions in that block cease to exist.
Test-specific subclasses make stimulus personalities named, reviewable, and reusable.
Factory substitution applies an override across every sequence with one line and zero sequence edits.
Keep blocks small and single-purpose — override granularity equals block granularity.
Common pitfalls
Expecting parent + child same-named blocks to merge — replacement is total, not additive.
Overriding a multi-concern block and silently dropping rules you forgot to restate.
Renaming a parent block and breaking every subclass override bound to the old name — the link is purely the string name.
Building per-test personalities as inline with piles instead of subclasses — unreviewable, unreusable.
Forgetting that randomize() is not virtual-dispatch magic: it is the object's TYPE (set at creation/factory) that decides which blocks exist.