Random Constraints
rand vs randc, constraint blocks and all their expression forms, the randomize() method, inline randomize() with, rand_mode(), constraint_mode(), pre/post randomisation hooks, scope randomisation, and random stability.
🎲 Overview — The Bus Example
Constraint-driven randomisation lets you describe what values are legal rather than manually enumerating every test case. The solver finds values satisfying all constraints, making it easy to hit corner cases automatically.
class Bus; rand bit[15:0] addr; rand bit[31:0] data; constraint word_align { addr[1:0] == 2'b0; } // must be word-aligned endclass Bus bus = new; repeat(50) begin if(bus.randomize() == 1) $display("addr=%h data=%h", bus.addr, bus.data); else $display("Randomisation failed"); end
The randomise-and-check pattern above is the foundation of constrained-random verification. Constraints can be inherited, extended, and overridden — enabling layered constraint hierarchies.
typedef enum {low, mid, high} AddrType; class MyBus extends Bus; rand AddrType atype; constraint addr_range { (atype == low) -> addr inside {[0 :15 ]}; (atype == mid) -> addr inside {[16 :127]}; (atype == high) -> addr inside {[128:255]}; } endclass // Inline further restriction at the call site bus.randomize() with { atype == low; }; // EXAMPLE 1 bus.randomize() with { 10 <= addr && addr <= 20; }; // EXAMPLE 2 bus.randomize() with { data & (data-1) == 0; }; // EXAMPLE 3: power-of-two
🎲 rand vs randc
rand — uniform random
Values drawn independently with equal probability. Repeats possible on every call.
Example: 2-bit rand — repeats 3,3 possible
randc — random cyclic
Cycles through every value in a random permutation before repeating any. No repeats within a cycle.
Permutation 1: 3,2,0,1 | Permutation 2: 1,3,2,0 (new random order)
rand bit [7:0] y; // 0–255 with equal probability each call randc bit [1:0] z; // cycles 0,1,2,3 in a random order, then repeats // randc restrictions: // — only bit types and enumerated types // — implementations may cap maximum size (at least 8 bits) // — cannot be used in dist or solve...before constraints // — randc variables are always solved BEFORE other rand variables
📌 Random Arrays and Handles
// All array types can be rand/randc — all elements randomised class C; rand byte fixed_arr[8]; // fixed array — 8 random bytes rand integer dyn[]; // dynamic — all elements randomised rand bit aa[int]; // associative — all entries randomised // Constrain the SIZE of a dynamic array rand bit [7:0] len; rand integer data[]; constraint db { data.size == len; } // Solver: first picks len, then randomises data[0..len-1] // Object handle declared rand — all nested random variables solved together rand Bus nested_bus; // Bus must be non-null before randomize() endclass
randomize().
📋 Constraint Blocks
A constraint block is a named class member containing one or more constraint expressions. The solver processes all active constraint blocks together.
class Packet; rand int length; rand int delay; // Named constraint block — can be enabled/disabled by name constraint valid_length { length inside {[64:1500]}; } constraint short_delay { delay inside {[1:10]}; } // Multiple expressions in one block — ALL must be satisfied constraint combined { length >= 64; length <= 1500; delay <= length / 8; // delay proportional to length } endclass // External constraint body — declared outside the class class XYPair; rand integer x, y; constraint c; // prototype only inside class endclass constraint XYPair::c { x < y; } // body defined outside // Constraint inheritance — derived class overrides same-named constraint class A; rand integer x; constraint c { x < 0; } // x negative in A endclass class B extends A; constraint c { x > 0; } // x positive in B — overrides A's constraint c // like virtual: B instance uses B's c, even via A handle endclass
++/-- (side effects); no 4-state operators (===, !==); all values must be 2-state; dist cannot appear inside other expressions; functions allowed with limits (see §Functions in Constraints).
∈ Set Membership in Constraints
rand integer x, y, z; constraint c1 { x inside {3, 5, [9:15], [24:32], [y:2*y], z}; } // x can be 3, 5, any of 9..15, any of 24..32, any of y..2y, or exactly z // All values have equal probability (absent other constraints) rand integer a, b, c; constraint c2 { a inside {b, c}; } // bidirectional: a==b || a==c // Negation: outside the set constraint not_zero { !(a inside {0}); } // a != 0 // Using an array as a set integer fives[0:3] = {5, 10, 15, 20}; rand integer v; constraint c3 { v inside fives; } // v is 5, 10, 15, or 20
🎲 Distribution — dist
The dist operator constrains a variable to a set with specified relative weights — not exact probabilities. Two weight operators: := assigns weight per-value, :/ assigns weight to the whole range.
rand int x; // := : weight per individual value constraint ratio_1_2_5 { x dist {100 := 1, 200 := 2, 300 := 5}; } // x = 100 with prob 1/8, 200 with 2/8, 300 with 5/8 // := on a range: each value in range gets that weight constraint per_value { x dist {[100:102] := 1, 200 := 2, 300 := 5}; } // ratio: 100→1, 101→1, 102→1, 200→2, 300→5 (8 total weight) // :/ on a range: weight is shared across all values in range constraint shared_weight { x dist {[100:102] :/ 1, 200 := 2, 300 := 5}; } // ratio: 100→1/3, 101→1/3, 102→1/3, 200→2, 300→5 // Combined constraint: if x != 200, ratio changes automatically constraint no200 { x != 200; x dist {100 := 1, 200 := 2, 300 := 5}; // 200 is excluded → effective ratio: 100→1, 300→5 (weights re-normalised) }
randc variables. The variable in a dist expression must be a rand variable. dist cannot appear inside other expressions — only as a top-level constraint expression.
→ Implication and if-else Constraints
Constraints are bidirectional — the solver can propagate in either direction. Both -> (implication) and if...else express conditional constraints and are equivalent.
// Implication: (a -> b) means (!a || b) // Constraints are bidirectional — len constrains mode too! constraint mode_len { mode == small -> len < 10; mode == large -> len > 100; } // if...else — equivalent to implication above constraint mode_len2 { if (mode == small) len < 10; else if (mode == large) len > 100; } // Inline predicate on a 4-bit variable — probability example // a == 0 implies b == 1: eliminates 15 of 256 combinations // P(a==0) = 1/241 (very low — solver rarely picks a=0) constraint c { (a == 0) -> (b == 1); } // Dangling else: always associates with the closest previous if constraint nested { if (mode != large) if (mode == small) len < 10; else // applies to inner if (mode == small) len > 100; }
→ Iterative Constraints — foreach
class C; rand byte A[]; // Every element must be in the set {2,4,8,16} constraint C1 { foreach(A[i]) A[i] inside {2,4,8,16}; } // Every element must be greater than twice its index constraint C2 { foreach(A[j]) A[j] > 2 * j; } endclass // Constrain array size + iterative: sorted ascending array class Sorted; rand int A[]; constraint c1 { A.size inside {[1:10]}; } constraint c2 { foreach(A[k]) (k < A.size-1) -> A[k+1] > A[k]; } // c1 solved first (size constraint), then c2 uses it as state variable endclass // Multi-dimensional: i,j cover all dimensions int A[2][3][4]; // foreach( A [ i, j, k ] ) — i:0→1, j:0→2, k:0→3
📋 Global Constraints
When a class contains a rand object handle as a property, the nested object’s constraints are solved together with the outer object’s constraints — they share one solution space.
class PktHeader; rand bit[7:0] dst, src; constraint no_loopback { dst != src; } endclass class Packet; rand PktHeader hdr; // rand object — global constraint rand bit[7:0] len; constraint p1 { hdr.dst != 255; } // cross-object constraint endclass // When Packet.randomize() runs, it solves hdr.dst, hdr.src, len together // hdr.no_loopback and Packet.p1 are both active
→ solve…before — Variable Ordering
By default the solver picks all variables simultaneously to produce a uniform distribution over the solution space. This can make rare corner cases extremely unlikely. solve...before forces a partial evaluation order to bias the distribution.
class B; rand bit s; rand bit[31:0] d; constraint c { s -> d == 0; } // Without ordering: P(s==1) ≈ 1/233 — s almost never set constraint order { solve s before d; } // With ordering: s chosen first (50% chance each). // Then d is solved given s. // P(s==1) = 50%, P(d==0) = 50% endclass
randc variables cannot appear in ordering constraints — they are always solved first regardless.
▶ Functions in Constraints
// Count ones in a 10-bit vector using a function function int count_ones(bit[9:0] w); for(count_ones=0; w!=0; w = w >> 1) count_ones += w & 1'b1; endfunction constraint C1 { length == count_ones(v); } // C1 is unidirectional: function call is evaluated, result used as state variable // v is solved BEFORE length (function argument creates implicit ordering) // Function restrictions in constraints: // — No output or ref arguments (const ref OK) // — Should be automatic / no side effects // — Cannot call rand_mode() or constraint_mode() // — Called BEFORE constraints are solved (return value = state variable) // — Function arguments with random vars create implicit solve ordering // Linked-list constraint with null guard class SList; rand int n; rand SList next; constraint sort { if(next != null) n < next.n; } // Guard: if next is null, constraint is skipped — no access error endclass
📌 Constraint Guards
A constraint guard is a predicate involving only constants, state variables, handle comparisons, loop variables, or array sizes. Guards are evaluated before solving — if false, the constraint is skipped; if error (e.g. null dereference), the solver fails.
// Guard prevents null dereference on linked list constraint sort { if(next != null) n < next.n; } // Complex example: disjunction of guards // constraint c1 { (x < y || a.x > b.x || a.x == 5) -> x+y == 10; } // Case: a non-null, b null, a.x==5 // a.x>b.x = ERROR (b is null), a.x==5 = TRUE // || short-circuits: TRUE → constraint x+y==10 is generated (no error) // 4-state guard truth tables applied recursively: // && : if ANY subexpr is FALSE → guard is FALSE (constraint skipped, no error) // || : if ANY subexpr is TRUE → guard is TRUE (unconditional constraint) // In both: if result is ERROR → randomize() fails
🎲 The randomize() Method
Bus b = new; // Returns 1 on success, 0 on failure (unsatisfiable constraints) if(!b.randomize()) $error("Randomisation failed"); // Always assert — never silently continue with bad data assert(b.randomize()) else $fatal(1, "Bus randomisation failed"); // void' discards the return value (but gives no error on failure!) void'(b.randomize()); // use only when failure is explicitly handled elsewhere // randomize() is virtual — subclass constraints are always used, // even when called via a base-class handle Bus bh = new MyBus; // base handle, holds MyBus object bh.randomize(); // MyBus constraints apply — correct virtual dispatch
📋 Inline Constraints — randomize() with
Additional constraints can be added at the call site using randomize() with {}. They are combined with all active class constraints for that one call. The inline constraint block can reference local variables from the enclosing scope.
class SimpleSum; rand bit[7:0] x, y, z; constraint c { z == x + y; } endclass task demo(SimpleSum p); // Additional constraint: x < y (only for this call) int success = p.randomize() with { x < y; }; endtask // Scope resolution in inline constraints — complex example class Foo; rand integer x; endclass class Bar; integer x; // non-random member of Bar integer y; task doit(Foo f, integer x, integer z); f.randomize() with { x < y + z; }; // x = Foo::x (innermost scope — the rand variable in Foo, hides Bar.x and arg x) // y = Bar.y (outer scope — not in Foo, found in Bar) // z = task argument endtask endclass
⚙ rand_mode() — Enabling/Disabling Variables
class Packet; rand integer source_value, dest_value; endclass Packet pkt = new; // Disable ALL random variables in an object pkt.rand_mode(0); // Re-enable just one variable pkt.source_value.rand_mode(1); // Query active state as a function (returns 1=ON, 0=OFF) int ret = pkt.dest_value.rand_mode(); // 0 (still disabled) // Array element: disable/enable individual elements pkt.some_array[3].rand_mode(0); // only element [3] disabled pkt.some_array.rand_mode(0); // all elements disabled // rand_mode is built-in and cannot be overridden
⚙ constraint_mode() — Enabling/Disabling Constraints
class Packet; rand integer source_value; integer m; constraint filter1 { source_value > 2 * m; } endclass function integer toggle_rand(Packet p); if(p.filter1.constraint_mode()) // query: 1=ON, 0=OFF p.filter1.constraint_mode(0); // was ON → turn OFF else p.filter1.constraint_mode(1); // was OFF → turn ON return p.randomize(); endfunction // Disable ALL constraints in an object at once p.constraint_mode(0); // all constraints OFF for p p.constraint_mode(1); // all back ON // Static constraint: disable/enable ALL instances at once static constraint global_limit { source_value < 1000; } // p.global_limit.constraint_mode(0) → turns off for ALL Packet objects // constraint_mode is built-in and cannot be overridden
⚙ pre_randomize() and post_randomize()
Two built-in hooks are called automatically around every randomize() call. Override them in your class to run setup/teardown code at each randomisation.
class XYPair; rand integer x, y; endclass class MyXYPair extends XYPair; function void pre_randomize(); super.pre_randomize(); // always call super first! $display("Before: x=%0d y=%0d", x, y); endfunction function void post_randomize(); super.post_randomize(); // always call super first! $display("After: x=%0d y=%0d", x, y); // Common use: compute derived fields from rand results crc = compute_crc(x, y); endfunction endclass // Call sequence for p.randomize(): // 1. p.pre_randomize() (including super chain) // 2. Solver runs — assigns random values to rand vars // 3. p.post_randomize() (including super chain)
super.pre_randomize() and super.post_randomize() in overrides — otherwise base-class hooks are silently skipped. The exception is when your class is the root base class with no parent.
📋 Inline Variable Control
randomize() can be called with an explicit list of variables — only those variables are treated as random; all others become state variables for that call only.
class CA; rand byte x, y; byte v, w; constraint c1 { x < v && y > w; } endclass CA a = new; a.randomize(); // random: x, y state: v, w a.randomize(x); // random: x state: y, v, w a.randomize(v, w); // random: v, w state: x, y (non-rand vars made random!) a.randomize(w, x); // random: w, x state: y, v // null argument: all variables are state variables — check mode only int ok = a.randomize(null); // returns 1 if current x,y,v,w satisfy constraints, else 0
null makes all variables state variables. The solver doesn’t assign anything — it just evaluates whether the current values satisfy all active constraints and returns 1 or 0. This is useful for sanity-checking state after procedural assignments.
🎲 std::randomize() — Scope Variables
For lightweight randomisation without defining a class, std::randomize() randomises variables in the current scope directly.
module stim; bit[15:0] addr; bit[31:0] data; function bit gen_stim(); bit rd_wr; // Randomise addr, data (module scope) and rd_wr (local) return randomize(addr, data, rd_wr); // std:: prefix optional here endfunction endmodule // With inline constraints task stimulus(int length); int a, b, c; std::randomize(a, b, c) with { a < b; a + b < length; }; // length is a state variable (not in the argument list) std::randomize(a, b) with { b - a > length; }; // c is not randomised in second call endtask // No-argument form: checker mode — returns 1 if in-scope constraints satisfied std::randomize();
🎲 Random Number Functions
| Function | Returns | Notes |
|---|---|---|
| $urandom(seed) | Unsigned 32-bit int | Unsigned (unlike $random). Thread-stable. Seed is optional. |
| $urandom_range(max, min=0) | Unsigned int in [min, max] | If max < min, arguments auto-reversed. Thread-stable. |
| obj.srandom(seed) | void | Seeds the object’s RNG. Best called in new(). |
| obj.get_randstate() | string | Returns internal RNG state (implementation-defined format). |
| obj.set_randstate(s) | void | Restores RNG state from a previously captured string. |
bit[63:0] addr; $urandom(254); // seed the thread's RNG addr = {$urandom, $urandom}; // 64-bit value from two 32-bit calls int n = $urandom & 15; // 4-bit value int val = $urandom_range(7, 0); // 0 to 7 inclusive int val2 = $urandom_range(0, 7); // same (args auto-reversed)
📌 Random Stability
Each thread and each object has its own independent RNG. This random stability means adding new unrelated code to one part of the testbench does not change random values in other parts.
- Thread stability: each thread gets its own RNG seeded from its parent thread (hierarchical seeding).
$urandom/$urandom_rangevalues from one thread are independent of other threads. - Object stability: each object’s
randomize()uses its own RNG seeded when the object is created. Adding a new object at the end of creation code does not shift existing objects’ RNG sequences. - Manual seeding: call
this.srandom(seed)innew()to reproducibly seed an object; callprocess::self.srandom(seed)to seed the current thread.
fork begin process::self.srandom(100); x = $urandom; end // independent begin y = $urandom; process::self.srandom(200); end // independent begin z = $urandom + $urandom; end // independent join // x, y, z are independent regardless of thread execution order // Self-seed in constructor for reproducible objects class Packet; function new(int seed); this.srandom(seed); // deterministic randomisation from this seed endfunction endclass
📋 Quick Reference
Constraint expression forms
| Form | Syntax | Notes |
|---|---|---|
| Simple | x > 0; x < 100; | Boolean expression — must be true |
| Set membership | x inside {1, 2, [10:20]}; | Equal probability for each member |
| Distribution | x dist {100:=1, 200:=2}; | Weighted probability; no randc |
| Implication | a == 1 -> b > 0; | Bidirectional — solver uses both directions |
| if-else | if(a) b > 0; else b < 0; | Equivalent to implication |
| Iterative | foreach(A[i]) A[i] > 0; | Applied to every array element |
| Ordering | solve s before d; | Bias distribution; no circular deps |
| Function call | len == count_ones(v); | Unidirectional; function args solved first |
Control methods
| Method | Syntax | Effect |
|---|---|---|
| Disable all rand vars | obj.rand_mode(0) | All vars treated as state |
| Enable one rand var | obj.var.rand_mode(1) | That var back to random |
| Query rand state | obj.var.rand_mode() | Returns 1=ON, 0=OFF |
| Disable constraint | obj.cname.constraint_mode(0) | Constraint ignored by solver |
| Enable constraint | obj.cname.constraint_mode(1) | Constraint back in effect |
| Query constraint | obj.cname.constraint_mode() | Returns 1=active, 0=inactive |
| Inline constraints | obj.randomize() with {x < y;} | Extra constraints for this call only |
| Partial randomise | obj.randomize(x, y) | Only x, y random; rest state vars |
| Check mode | obj.randomize(null) | No randomisation; just check constraints |
