Constraint Techniques
Randomization and Constraints covered ordinary (hard) constraints — rules randomize() is never allowed to violate. Real testbenches need a few more tools on top: a default that a specific test can override without editing the class, a way to turn a constraint off entirely for one test, and hooks to run setup/cleanup code automatically around every randomize() call.
Soft constraints: a default, not a hard rule
class transaction;
rand bit [3:0] length;
constraint default_length {
soft length == 4; // default: length is 4, unless overridden
}
endclass
transaction tr = new();
tr.randomize(); // length = 4 (the soft default applies)
tr.randomize() with { length == 10; }; // length = 10 (an inline hard constraint overrides the soft one)
A soft constraint is a suggestion, not a requirement — it applies only when nothing else conflicts with it. Any hard constraint (including an inline with constraint, as shown) overrides a conflicting soft one without any error. This is the right tool for "most tests want this typical value, but a specific test occasionally needs something else" — a hard constraint would make that override impossible (two conflicting hard constraints simply make randomize() fail); a soft one yields gracefully.
constraint_mode(): turning a constraint off entirely
tr.valid_length.constraint_mode(0); // disable this constraint
tr.randomize(); // length can now be anything, ignoring valid_length entirely
tr.valid_length.constraint_mode(1); // re-enable it
Every constraint block has an implicit constraint_mode() method — passing 0 disables it for subsequent randomize() calls, 1 re-enables it. This is useful for a specific negative test that deliberately needs to generate an illegal value (to check the DUT correctly rejects it) — temporarily disabling the constraint that would normally prevent that value is cleaner than duplicating the whole class just to remove one rule.
rand_mode(): excluding a property from randomization entirely
tr.data.rand_mode(0); // data is no longer randomized — keeps whatever value it currently has
tr.randomize(); // length is still randomized; data is left untouched
Similarly, rand_mode(0) on a specific rand/randc property excludes just that property from randomization, leaving its current value untouched while everything else on the object still randomizes normally — useful when one test wants to fix a specific field to a known value while letting the rest of the object randomize freely, without constraining that field to one value (which would still count as "randomizing" it, just to a fixed outcome) or removing rand from the class declaration (which would affect every test using the class, not just this one).
pre_randomize() and post_randomize(): hooks around every call
class transaction;
rand bit [7:0] data;
function void pre_randomize();
$display("about to randomize...");
endfunction
function void post_randomize();
$display("randomized: data=%0h", data);
endfunction
endclass
SystemVerilog automatically calls pre_randomize() immediately before, and post_randomize() immediately after, every randomize() call on an object — both are ordinary methods a class can override (they default to doing nothing), and both run without the caller needing to invoke them explicitly. post_randomize() is the natural place to compute a derived field from the randomized ones (e.g., a checksum computed from data after it's been randomized) or to log what was just generated, without needing every call site to remember to do that logging manually.
solve...before: controlling constraint solve order
class transaction;
rand bit [1:0] mode;
rand bit [7:0] value;
constraint mode_dependent {
solve mode before value;
if (mode == 0) value inside {[0:15]};
else value inside {[16:255]};
}
endclass
By default, the constraint solver is free to solve for every random variable in whatever order it finds most efficient, which can occasionally bias the distribution of results in a constraint that has one variable's legal range depend on another's value (as value's legal range depends on mode above). solve mode before value forces the solver to pick mode first, then solve for value given that specific mode — improving how uniformly value's two possible ranges each get sampled, without changing what values are legal (that's still governed entirely by the if/else inside the constraint) — only how fairly the solver distributes across the legal values across many randomization calls.
Soft constraint priority, and inheriting constraints across a class hierarchy
class base_txn;
rand bit [3:0] length;
constraint default_length {
soft length == 4;
}
endclass
class ext_txn extends base_txn;
constraint default_length {
soft length == 8; // replaces the base class's default_length entirely
}
function void pre_randomize();
super.pre_randomize(); // still run the base class's setup too
$display("ext_txn about to randomize");
endfunction
endclass
Two more rules round out how soft and the randomize hooks behave once a class hierarchy is involved:
- Soft constraint priority: when more than one active soft constraint conflicts over the same variable, the solver doesn't average or pick arbitrarily — an inline
randomize() withsoft constraint outranks any soft constraint declared inside the class, and (relevant here) a derived class's soft constraint outranks a same-named one it inherited from its base class. This is what makesext_txn'sdefault_lengthwin overbase_txn's for anext_txnobject — not a merge of4and8, a full replacement. - Constraint blocks are inherited, and can be overridden by name:
ext_txndeclaring its ownconstraint default_length { ... }doesn't add a second, competing rule — it replaces the base class'sdefault_lengthoutright for anyext_txnobject, the same way overriding an ordinary method does. A derived class that declares nodefault_lengthblock at all simply inherits the base class's version unchanged. super.pre_randomize()/super.post_randomize(): if a derived class overridespre_randomize()(orpost_randomize()) without callingsuper.pre_randomize()inside it, the base class's version is not automatically also run — overriding fully replaces it, exactly like any other overridden method. Callingsuper.pre_randomize()explicitly, asext_txndoes above, is what chains the base class's setup back in alongside the derived class's own.extern constraint: for a very large constraint body (or one generated by a script), a class can declareextern constraint name;inside the class body and provide the actualconstraint className::name { ... }definition later, outside the class — the same declaration/definition splitexternalready allows for methods, applied to constraints.
What's next
Randomization generates one object's worth of data at a time. The next page turns to concurrency — fork/join and its variants — for running multiple pieces of testbench code, like a driver and a monitor, genuinely in parallel within the same simulation.