Skip to main content

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() with soft 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 makes ext_txn's default_length win over base_txn's for an ext_txn object — not a merge of 4 and 8, a full replacement.
  • Constraint blocks are inherited, and can be overridden by name: ext_txn declaring its own constraint default_length { ... } doesn't add a second, competing rule — it replaces the base class's default_length outright for any ext_txn object, the same way overriding an ordinary method does. A derived class that declares no default_length block at all simply inherits the base class's version unchanged.
  • super.pre_randomize()/super.post_randomize(): if a derived class overrides pre_randomize() (or post_randomize()) without calling super.pre_randomize() inside it, the base class's version is not automatically also run — overriding fully replaces it, exactly like any other overridden method. Calling super.pre_randomize() explicitly, as ext_txn does 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 declare extern constraint name; inside the class body and provide the actual constraint className::name { ... } definition later, outside the class — the same declaration/definition split extern already 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.