SystemVerilog Series · SV-12

SystemVerilog Series — SV-12: Random Constraints — VLSI Trainers
SystemVerilog Series · SV-12

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.

3
3
1
0
2
3
0
1

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.

3
2
0
1
|
1
3
2
0

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
Dynamic array elements declared rand: if the array size is not constrained, all existing elements are randomised. If size is constrained, the array is resized first, then all elements randomised. Object handle arrays declared rand require all handles to be non-null before calling 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
Constraint restrictions: no ++/-- (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)
}
dist restrictions: cannot be applied to 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
solve…before changes the distribution but not the solution space. It cannot make an unsatisfiable constraint solvable. Circular orderings (solve a before b AND solve b before a) are illegal. 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
Scope resolution order in randomize() with: innermost first — the class being randomised (Foo), then automatic/local variables, then task/function arguments, then class variables of the enclosing class (Bar), then the outer scope.

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
Typical use pattern: disable a base-class constraint, randomise with a different assumption, then re-enable. This is cleaner than permanently removing a constraint via inheritance.

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)
Always call 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
randomize(null) as a constraint checker: passing 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

FunctionReturnsNotes
$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_range values 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) in new() to reproducibly seed an object; call process::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

FormSyntaxNotes
Simplex > 0; x < 100;Boolean expression — must be true
Set membershipx inside {1, 2, [10:20]};Equal probability for each member
Distributionx dist {100:=1, 200:=2};Weighted probability; no randc
Implicationa == 1 -> b > 0;Bidirectional — solver uses both directions
if-elseif(a) b > 0; else b < 0;Equivalent to implication
Iterativeforeach(A[i]) A[i] > 0;Applied to every array element
Orderingsolve s before d;Bias distribution; no circular deps
Function calllen == count_ones(v);Unidirectional; function args solved first

Control methods

MethodSyntaxEffect
Disable all rand varsobj.rand_mode(0)All vars treated as state
Enable one rand varobj.var.rand_mode(1)That var back to random
Query rand stateobj.var.rand_mode()Returns 1=ON, 0=OFF
Disable constraintobj.cname.constraint_mode(0)Constraint ignored by solver
Enable constraintobj.cname.constraint_mode(1)Constraint back in effect
Query constraintobj.cname.constraint_mode()Returns 1=active, 0=inactive
Inline constraintsobj.randomize() with {x < y;}Extra constraints for this call only
Partial randomiseobj.randomize(x, y)Only x, y random; rest state vars
Check modeobj.randomize(null)No randomisation; just check constraints
Coming next: SV-13 covers Section 13 — Interfaces: interface declarations, modports, clocking blocks, interface methods, and how interfaces simplify module connectivity.

Leave a Comment

Your email address will not be published. Required fields are marked *

Scroll to Top