Part 3 · Constraint Randomization · Intermediate
Constraint Policy Classes
Composable policy objects carrying constraints that reference the transaction, mixing policies per test, and comparison with inheritance.
The composition problem inheritance cannot solve
Subclassing handles one axis of variation cleanly. But test intent is usually multi-axis: address region x burst character x error mix. With inheritance you get class explosion — low_addr_long_burst_no_err_txn and seven siblings — because SystemVerilog has single inheritance and constraints compose only down the chain. The policy pattern solves this with a different mechanism: a policy is a separate object that carries constraints on another object's fields . The language permits this because constraint expressions may reference any visible field through a handle. Hold a handle to the transaction inside the policy, constrain through it, and randomize policy and transaction together.
class bus_txn;
rand bit [31:0] addr;
rand bit [3:0] len;
rand bit write;
constraint valid_c { len inside {[1:15]}; addr[1:0] == 0; }
endclass
// Base policy: holds the target handle, no constraints itself
virtual class txn_policy;
bus_txn t; // handle to the object under constraint
function void bind_txn(bus_txn t_h); t = t_h; endfunction
endclass
// Each policy = one axis of intent, constraining THROUGH the handle
class low_addr_policy extends txn_policy;
constraint c { t.addr inside {[32'h0:32'hFFFF]}; }
endclass
class long_burst_policy extends txn_policy;
constraint c { t.len inside {[12:15]}; }
endclass
class write_heavy_policy extends txn_policy;
constraint c { t.write dist { 1 := 9, 0 := 1 }; }
endclassThe key semantic fact: constraints in a class may reference fields of other objects via handles, and when you randomize an object, constraints in that object referencing another object's rand fields treat... careful — the direction matters. Randomizing the policy solves the policy's rand fields; t's fields are only solved if t is also rand or randomized in the same call. The composition container below gets this right by making the policies' constraints participate in one randomize call.
Composing policies in one solve
class policed_txn;
rand bus_txn txn; // rand handle: solved together
rand txn_policy pol[$]; // rand queue of policy objects
function new();
txn = new();
endfunction
function void add(txn_policy p);
p.bind_txn(txn);
pol.push_back(p);
endfunction
endclass
// Because txn and every policy are rand sub-objects of policed_txn,
// ONE call - assert(pt.randomize()) - gathers valid_c (from txn)
// AND every bound policy constraint into a single solver problem.
module demo;
initial begin
policed_txn pt = new();
low_addr_policy p1 = new();
long_burst_policy p2 = new();
pt.add(p1);
pt.add(p2);
repeat (10) begin
assert(pt.randomize());
$display("addr=%h len=%0d", pt.txn.addr, pt.txn.len);
// addr in [0:FFFF] AND len in [12:15] AND valid_c - composed.
end
end
endmoduleThis is the constraint-on-external-object pattern in its standard form: the container makes both the transaction and the policy objects rand members, so a single randomize() conjoins all their constraint blocks. Policies are now mixed per test at runtime — push two policies for one test, three different ones for the next — with no new classes and no edits to bus_txn. Policies can also be disabled individually (p1.c.constraint_mode(0)) or popped from the queue between phases.
Policy vs inheritance vs inline: choosing
COMPOSITION MECHANISM COMPARISON
subclass inline with policy object
------------------------------------------------------------------
composable one chain only per call site N policies mixed
(multi-axis) (class (verbose to freely at runtime
explosion) repeat)
reusable across yes (named no (copy- yes (named class,
tests class) paste) instantiated)
runtime add/ no (type fixed per-call only yes (push/pop,
remove at new()) constraint_mode)
can override yes (same-name no (AND only) no (AND only)
parent block block)
factory-friendly yes (type n/a yes (policies are
(UVM) override) objects too)
typical use recurring one-off multi-axis test
single-axis tweaks matrices, VIP
intent stimulus libraries
Rule of thumb:
1 axis, recurring -> subclass
1 call site, one-off -> inline with
multiple orthogonal axes -> policies
need to REPLACE a block -> subclass with same-name override
(the one thing policies cannot do)The last row of the rule-of-thumb matters: policies only ever AND constraints onto the problem. If a test must relax something — replace the typicality block, widen a range — that requires named-block override (inheritance) or soft constraints in the base. Mature VIP therefore uses both: soft typicality in the base class so policies' hard constraints win automatically, and a policy library for the multi-axis intent matrix.
A production wrinkle: policy queues and null safety
// Defensive add() for long-lived benches:
function void policed_txn::add(txn_policy p);
if (p == null) $fatal(1, "null policy added");
p.bind_txn(txn);
pol.push_back(p);
endfunction
// Remember the silent-failure rule: a null element in a rand
// object queue is silently skipped by randomize() - an unbound
// or null policy simply vanishes from the solve with no error.
// Guard at insertion, not at randomize time.Interview angle
The probe is usually “your tests need address-region x burst-length x error-mix combinations — how do you avoid 27 subclasses?” Answer with the pattern's two pillars: constraints may reference another object's fields through a handle, and randomizing a container whose rand members include both the transaction and the policy objects merges all constraint blocks into one solve. Then place it honestly against inheritance: policies compose N orthogonal axes at runtime but can only AND — replacing or widening still needs named-block override or soft bases. Citing real-world precedent (VIP stimulus libraries and the policy/knob layering in mature UVM environments use exactly this shape) signals you have seen it outside a textbook.
Key takeaways
A policy is an object carrying constraints on another object's fields via a stored handle.
Make txn and policies rand members of one container — a single randomize() conjoins everything.
Policies compose multi-axis intent at runtime without class explosion or base-class edits.
Policies can only AND — replacing/widening a block still requires inheritance override or soft bases.
Choose: subclass (one recurring axis), inline (one-off), policies (orthogonal axis matrix).
Common pitfalls
Randomizing the policy alone and expecting the transaction to be solved — direction matters; use the container.
Forgetting bind_txn before randomize — null handle means the policy is silently skipped, not an error.
Trying to widen a base constraint with a policy — AND semantics make it narrower or UNSAT.
Policy classes accumulating state across tests when reused — pop/reset queues at phase boundaries.
27-subclass explosion because policies were never introduced — the smell is class names with three adjectives.