Constraint blocks inherit by name. A subclass block with the parent's name fully replaces that block for subclass objects, while a new name adds another block that intersects with everything inherited.
// snippet: the classes, run in the bench below
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 the parent block, so relaxation works
constraint len_c { len inside {[1:255]}; }
endclass
class urgent_txn extends base_txn;
// New name: ADDS, so it meets the inherited blocks
constraint urgent_c { prio inside {[6:7]}; }
endclass
// jumbo_txn: len in [1:255] (parent [1:16] is gone), prio < 8
// urgent_txn: len in [1:16], prio in {6, 7} from prio<8 met with [6:7]Replacement is total, so an override must restate any parent expression it still wants. Small single-purpose blocks keep that restatement cheap.
The methodology pattern: the base carries protocol legality only, and each test family adds a thin subclass that reshapes distributions or drops one rule. The subclass is named, reviewable, and reusable.
// snippet: two personalities of one base class
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
// Personality 1: write-heavy small bursts near a hotspot
class hotspot_txn extends axi_txn;
constraint shape_c { // replaces the base shape_c
write dist { 1 :/ 9, 0 :/ 1 };
len inside {[1:4]};
addr inside {[32'h8000:32'h80FF]};
}
endclass
// Personality 2: out-of-spec lengths for error checking
class badlen_txn extends axi_txn;
constraint legal_c { addr[1:0] == 0; } // replaces: the len rule is dropped
constraint bad_c { len inside {[17:64]}; }
endclassOverride scales when a factory builds the objects: sequences keep creating the base type while the test installs a subclass. Every randomize() in every reused sequence then solves the subclass set with zero sequence edits.
class axi_txn;
rand bit [31:0] addr;
rand bit [7:0] len;
constraint legal_c { len inside {[1:16]}; addr[1:0] == 0; }
virtual function axi_txn clone_empty();
axi_txn t = new();
return t;
endfunction
endclass
class hotspot_txn extends axi_txn;
constraint legal_c { len inside {[1:4]}; addr[1:0] == 0; }
virtual function axi_txn clone_empty();
hotspot_txn t = new();
return t; // subclass object behind a base-typed handle
endfunction
endclass
// The same idea without UVM: a virtual create hook
class txn_factory;
static axi_txn proto = null; // the test installs one prototype
static function axi_txn create();
if (proto == null) begin axi_txn t = new(); return t; end
return proto.clone_empty(); // virtual call returns the subclass instance
endfunction
endclass
module tb_factory;
initial begin
hotspot_txn proto; // declarations first, statements after
axi_txn t;
proto = new();
txn_factory::proto = proto; // one line in the test reshapes every sequence
t = txn_factory::create();
void'(t.randomize()); // solves the hotspot_txn block set
if (!(t.len inside {[1:4]}))
$fatal(1, "FAIL: len=%0d, the base legal_c was solved", t.len);
$display("PASS: the factory returned a hotspot_txn, len=%0d", t.len);
$finish;
end
endmodulePASS: the factory returned a hotspot_txn, len=3The test installs the override: build hotspot_txn wherever axi_txn is asked for.
Every reusable sequence still asks the factory for an axi_txn, unchanged.
It receives a hotspot_txn, so each randomize() solves the overridden legal_c.
One line in the test reshapes every transaction in every sequence.
jumbo_txn redeclares len_c as len inside {[1:255]}. urgent_txn adds a new block urgent_c { prio inside {[6:7]}; }. Can a jumbo_txn have len == 200? Can an urgent_txn have prio == 7 and len == 200?Yes for jumbo: the same name replaced the parent's 1 to 16. No for urgent: a new name adds to the inherited blocks, so len is still 1 to 16 (and prio == 7 is fine).
The base class and three subclasses from this lesson, 1,000 solves each.
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;
constraint len_c { len inside {[1:255]}; } // same name: REPLACES the parent block
endclass
class urgent_txn extends base_txn;
constraint urgent_c { prio inside {[6:7]}; } // new name: ADDS to the inherited blocks
endclass
class badlen_txn extends base_txn;
constraint len_c { len inside {[17:64]}; } // replaces: out-of-spec lengths for error tests
endclass
module tb_override;
base_txn b; jumbo_txn j; urgent_txn u; badlen_txn x;
int jumbo_big = 0;
initial begin
b = new(); j = new(); u = new(); x = new();
repeat (300) begin
if (!b.randomize() || !j.randomize() || !u.randomize() || !x.randomize()) $fatal(1, "FAIL: randomize failed");
if (b.len < 1 || b.len > 16 || b.prio >= 8) $fatal(1, "FAIL: base len %0d prio %0d", b.len, b.prio);
if (j.len == 0 || j.prio >= 8) $fatal(1, "FAIL: jumbo len %0d prio %0d", j.len, j.prio);
if (j.len > 16) jumbo_big++;
if (u.len < 1 || u.len > 16 || !(u.prio inside {6, 7})) $fatal(1, "FAIL: urgent len %0d prio %0d", u.len, u.prio);
if (x.len < 17 || x.len > 64) $fatal(1, "FAIL: badlen len %0d", x.len);
end
$display("jumbo lengths above 16: %0d of 300", jumbo_big);
if (jumbo_big == 0) $fatal(1, "FAIL: the jumbo override did not widen len");
$display("PASS: same-name blocks replaced the parent rule, a new block added to it, over 300 solves each");
$finish;
end
endmodulejumbo lengths above 16: 258 of 300
PASS: same-name blocks replaced the parent rule, a new block added to it, over 300 solves eachThey are gone for subclass objects. Replacement is total, so the override must restate anything it still wants, which is why blocks stay small and single-purpose.
When the change is a standing policy for a whole test rather than a temporary mid-sequence toggle. Override is per-type with nothing to restore, while mode is per-instance sticky state.
Choice | Scope | Restore needed |
|---|---|---|
Subclass override | Every instance of the subtype | None: the type carries the policy. |
constraint_mode toggle | One object, until changed | Yes: re-enable what was disabled. |
Factory substitution | Every sequence creating the base | None: the test owns the install. |
In the layer map, inheritance answers what standing personality a test type carries. Use it for per-test policy, never for one-off directed values.
Run with Verilator 5.048 (--binary --timing) on 2026-10-18; every bench printed the PASS line shown. Verilator prints a harmless WIDTHTRUNC warning for if (!obj.randomize()), because randomize() returns an int, and an UNSATCONSTR warning when a solve fails on purpose. Icarus 13 does not support constraints.