Randomization and Constraints
A plain Verilog testbench generates stimulus by hand — a directed sequence of specific values the test writer chose. That finds exactly the bugs the writer thought to test for, and nothing else. Constrained-random verification flips this: generate stimulus randomly, but constrained to stay legal (a packet length that's actually a valid packet length, an address that's actually in range) — covering combinations of inputs the test writer never thought to write by hand, while still never generating a value the DUT wasn't supposed to receive.
rand and randc
class transaction;
rand bit [7:0] data;
randc bit [1:0] opcode;
endclass
rand marks a property as randomizable with an ordinary uniform random distribution — the same value can repeat across successive randomizations, exactly like rolling a die repeatedly. randc ("random-cyclic") instead guarantees every possible value is produced exactly once before any value repeats — for opcode above, with 4 possible 2-bit values, eight calls to randomize() (two full cycles) produce every one of the 4 values exactly twice, in some random order each cycle, never repeating a value early. randc is useful specifically when coverage of every possible value matters more than uniform probability — but is more expensive to compute and is practically restricted to small ranges (randc over a full 32-bit field is not realistic), so rand is the default choice unless exhaustive cycling of a small value set is specifically the goal.
randomize()
transaction tr = new();
if (!tr.randomize())
$display("ERROR: randomization failed");
$display("data=%0h opcode=%0d", tr.data, tr.opcode);
Calling .randomize() on an object assigns new random values to every rand/randc property, honoring any constraints declared on the class (below). It returns 1 on success and 0 if no legal solution exists for the current constraints — checking that return value (as above) matters, since a testbench that ignores a randomization failure silently proceeds with stale, unrandomized data instead of catching the constraint conflict that caused the failure.
Constraint blocks
class transaction;
rand bit [7:0] data;
rand bit [3:0] length;
constraint valid_length {
length inside {[1:15]}; // length is never 0 or above 15
}
constraint even_data {
data % 2 == 0; // only even data values
}
endclass
A constraint block restricts which random values are legal for the class's rand/randc properties — randomize() only ever produces values satisfying every active constraint simultaneously. inside {[1:15]} restricts length to a range; ordinary relational/arithmetic expressions (data % 2 == 0) work directly inside a constraint the same as anywhere else in the language. A class can declare any number of constraint blocks, and all of them apply together — valid_length and even_data above both constrain the same randomize() call.
Inline constraints: randomize() with
tr.randomize() with {
length == 8;
data > 100;
};
randomize() with { ... } adds temporary, call-site-specific constraints on top of the class's own declared constraints, without changing the class itself — useful for a specific test that needs to force one particular randomization run into a narrower range (exactly length 8, in this example) while every other call to tr.randomize() elsewhere in the testbench still uses the class's general constraints unmodified.
More constraint operators
Constraint blocks aren't limited to plain relational expressions — a handful of dedicated operators show up constantly in real testbenches:
class packet;
rand bit [3:0] len;
rand bit is_broadcast;
rand bit [7:0] addr[4];
constraint len_dist {
len dist { 1 := 5, [2:14] := 60, 15 := 5 }; // weighted, not uniform
}
constraint broadcast_addr {
is_broadcast -> addr[0] == 8'hFF; // implication
}
constraint addr_range {
foreach (addr[i])
addr[i] inside {[0:200]}; // constrain every array element
}
constraint order_hint {
solve is_broadcast before addr; // pick is_broadcast first
}
endclass
distassigns weighted, non-uniform probability instead ofrand's default uniform spread.1 := 5means the value 1 gets weight 5;[2:14] := 60means that whole range shares weight 60 (not 60 each) — use:/instead of:=when a range's weight should be divided evenly across its values rather than shared as one lump.distonly applies torandproperties — it cannot be used to weight arandcproperty, sincerandc's cyclic guarantee (every value exactly once per cycle) is already a fixed, non-probabilistic scheme that a weighted distribution would directly conflict with.->(implication) encodes if-then logic inside a constraint:is_broadcast -> addr[0] == 8'hFFonly forcesaddr[0]whenis_broadcastis 1; whenis_broadcastis 0, that half of the constraint drops out entirely andaddr[0]is free.if (...) ... else ...blocks work inside a constraint the same way, for cases with more than two branches.foreachapplies a constraint expression to every element of an array property in one line, instead of writing one constraint per index —addr[i] inside {[0:200]}above applies to all four elements ofaddr.solve ... beforedoesn't change which values are legal, only the order the solver picks them in — biasing which corner cases show up more often when multiple valid solutions exist for the same constraint set. It's a probability-distribution hint, not a correctness requirement: a solver ignoring it entirely would still only ever produce legal values.rand_mode()/constraint_mode()toggle randomization and constraint blocks on/off at runtime, per-property or per-constraint, without editing the class:tr.addr.rand_mode(0)stopsaddrfrom being randomized on the nextrandomize()call (it keeps whatever value it already had), andtr.addr_range.constraint_mode(0)disables just that one constraint block while leaving every other constraint on the class active.
Randomizing only some fields, and forcing distinct values
Two more tools come up constantly once a class has several rand properties: randomizing only a subset of them, and forcing a group of them to never collide.
class packet;
rand bit [3:0] len;
rand bit [7:0] addr[4];
rand bit [7:0] header_id, footer_id;
constraint distinct_ids {
unique { header_id, footer_id };
}
endclass
packet pkt = new();
pkt.len = 8; // held fixed — set directly, not randomized
void'(pkt.randomize(addr, header_id, footer_id)); // only these three are randomized
randomize(field1, field2, ...)— passing a list of property names intorandomize()restricts randomization to only those properties; every otherrand/randcproperty on the object keeps whatever value it already had, completely untouched. This is a different mechanism fromrand_mode(0):rand_mode(0)is a persistent per-property toggle that affects every futurerandomize()call until re-enabled, while naming fields directly in onerandomize(...)call only affects that single call.unique { a, b, c }— a constraint guaranteeing every variable (or array element) listed never equals any other listed in the sameuniqueblock. Above,unique { header_id, footer_id }guarantees the solver never assigns them the same value — useful for anything that must never accidentally collide (two IDs meant to be distinguishable, a set of addresses that must each be unique).std::randomize()— for randomizing a plain variable that isn't a class property at all (a local variable inside a task, for example), the class-based.randomize()method call doesn't apply; the free functionstd::randomize(var) with { ... }fills the same role outside of any class.
Why this replaces hand-written directed tests, not just supplements them
| Directed testing | Constrained-random testing |
|---|---|
| Test writer manually enumerates specific input values | randomize() (plus constraints) generates legal values automatically |
| Coverage is exactly what the writer thought to write | Coverage extends to combinations the writer never explicitly considered |
| Adding a new legal input case means writing new test code | Often free — the existing randomized test already explores the new case eventually |
| Finding a corner-case bug requires guessing it exists | A large enough randomized run can stumble onto it without anyone guessing first |
The tradeoff (and it's real, not free) is that constrained-random testing needs a way to know, automatically, whether the DUT's response to an unpredictable input was actually correct — a hand-written directed test's expected value is trivial to compute by hand, but a scoreboard checking thousands of randomly-generated transactions needs its own automated reference model. Testbench covers building that scoreboard/checker infrastructure; this topic's job stops at generating the randomized stimulus itself.
What's next
The next page goes deeper on constraint techniques that come up constantly in real testbenches: soft constraints (a default that can be overridden rather than a hard rule), enabling/disabling constraints at runtime, and hooks that run automatically before and after every randomize() call.