SystemVerilog Basics

Chapter 12 of 16

Randomization and Constraints: rand, randc, and constraint Blocks

Learn to declare randomizable properties with rand/randc, describe rules with constraint blocks, call randomize() and check its result, use $urandom/$urandom_range and std::randomize() for randomization outside a class, and control what gets randomized at runtime with rand_mode and constraint_mode — the core mechanism verification code uses to generate stimulus.

Chapter 1 said Verilog has almost no language features built for verification, including no built-in randomized stimulus generation; chapters 10–11 built the class and object foundation on top of that. This chapter covers constrained randomization — the core mechanism SystemVerilog verification code uses to generate stimulus: instead of hand-writing a pile of specific test data, you declare "what rules the data must satisfy," and let the tool automatically generate large volumes of data that satisfies those rules while still varying every time.

rand and randc: declaring randomizable properties

class packet;
  rand  bit [7:0] addr;
  randc bit [1:0] priority_level;
  bit   [7:0]     data;   // no rand — never touched by randomization
 
  function void print();
    $display("addr=%0h priority=%0d data=%0h", addr, priority_level, data);
  endfunction
endclass
 
initial begin
  packet p = new();
  repeat (3) begin
    void'(p.randomize());
    p.print();
  end
end
  • rand: declares that this property participates in randomization — every call to randomize() may give it a (possibly) different value.
  • randc (random-cyclic): also randomizes, but guarantees no value repeats until every value in its range has come up once — a good fit for small ranges where you want to guarantee coverage of every possible value (like the 2-bit priority_level above, which only has 4 possible values). randc is really only practical for small bit widths — the "set of values already used" that needs tracking quickly becomes unwieldy as the width grows.
  • A property with neither rand nor randc (like data) is never touched by randomize().

Every class comes with a built-in randomize() method (just like it comes with new()) — calling it re-randomizes every rand/randc property. It returns a value: 1 on success, or 0 if the constraints contradict each other and can't all be satisfied at once. The example above discards that return value explicitly with void'(...) (covered back in chapter 9), but real verification code should generally check it and flag an error on failure, rather than silently continuing with stale values:

packet p = new();
if (!p.randomize()) begin
  $error("packet randomization failed - check constraint conflicts");
end

constraint blocks: adding rules to randomization

Without any constraints, a rand property randomizes uniformly across its type's entire range. Real scenarios usually need to narrow that range or exclude invalid values — that's what constraint blocks are for:

class packet;
  rand bit [7:0] addr;
  rand bit [7:0] data;
 
  constraint addr_range_c {
    addr inside {[8'h10 : 8'h1F]};  // addr must land between 0x10 and 0x1F
  }
 
  constraint data_nonzero_c {
    data != 8'h00;  // data can't be 0
  }
endclass

One thing worth a mental shift here, since it's unlike ordinary procedural code: the statements inside a constraint block aren't "code that runs in order" — they're declarative rules describing what the final result must satisfy, handed to a constraint solver that figures out a set of values satisfying all of them at once. The two constraint blocks (addr_range_c, data_nonzero_c) have no ordering relationship between them; the solver satisfies both simultaneously, not one after the other.

Richer constraint styles

Constraint expressions can describe more elaborate rules. A weighted distribution uses dist to make certain values more likely (for example, testing boundary values more often):

constraint data_dist_c {
  data dist { 8'h00 := 1, [8'h01:8'hFE] :/ 8, 8'hFF := 1 };
}

Roughly speaking, this says "0x00 and 0xFF each get 1 share of weight, and the remaining 254 values share 8 total shares between them" — so the boundary values 0x00/0xFF come up far more often than any single one of the other 254 values. That's useful in verification because boundary conditions are where bugs tend to hide.

A conditional constraint uses the implication operator -> to express "if A holds, then B must also hold":

typedef enum { READ, WRITE } op_e;
 
class packet;
  rand op_e         op;
  rand bit [7:0]    addr;
 
  constraint addr_valid_c {
    (op == READ) -> (addr < 8'h80);   // only restrict addr when op is READ
  }
endclass

This constraint only restricts addr when op == READ; if op randomizes to WRITE, this constraint has no effect and addr can take any value across its full width.

One-off extra constraints: randomize() with {...}

Sometimes you don't want to modify a class's declared constraints just for a one-off need. You can layer extra constraints onto a single randomize() call using with — they only apply to that call:

packet p = new();
void'(p.randomize() with {
  addr == 8'h20;  // only for this call, force addr to equal 0x20
});

The constraints inside with {...} are solved together with the class's declared constraints (not as a replacement for them) — if the extra constraint conflicts with an existing one, randomize() fails and returns 0.

$urandom and $urandom_range: randomizing without a class

Everything so far needed a class with rand/randc properties and a randomize() call. That's the right tool when you're generating a structured object (a packet, a transaction), but it's overkill for a quick standalone random number — say, a random loop count or a random array index inside an initial block. For that, SystemVerilog provides two system functions that work directly on plain variables, with no class involved at all:

bit [31:0] raw;
int        delay;
int        idx;
 
raw   = $urandom;                // uniformly random across the full 32-bit range
delay = $urandom_range(20, 5);   // uniformly random integer in [5, 20]
idx   = $urandom_range(7);       // uniformly random integer in [0, 7] (minval defaults to 0)
  • $urandom returns a uniformly random 32-bit value (treat it as signed or unsigned depending on what you assign it to).
  • $urandom_range(maxval, minval = 0) returns a value constrained to [minval, maxval] — the first argument is the upper bound, the second (optional, defaults to 0) is the lower bound.

Neither of these goes through a constraint solver — there's no constraint block, no dist, no inside — just a uniform (or bounded-uniform) random value. That's exactly the tradeoff: $urandom/$urandom_range are cheap and simple for a one-off value, while rand/randc + constraint blocks are for when the value needs to obey real rules or belongs to a larger randomized object.

std::randomize(): randomizing plain variables outside a class

obj.randomize() only touches an object's own declared rand/randc properties — it has nothing to do with an ordinary local variable that isn't part of a class. For that case, SystemVerilog provides a global version of randomize that can operate directly on plain variables, including inline constraints with the same with {...} syntax used earlier:

int unsigned x, y;
 
if (!std::randomize(x, y) with { x inside {[0:9]}; y > x; }) begin
  $error("randomization failed");
end

This randomizes x and y in place, satisfying x inside {[0:9]} and y > x at once — the same declarative, solver-driven behavior as a class's constraint blocks, just applied to variables that don't live inside any class.

The std:: prefix matters: written inside a class method, a bare randomize(...) call always means "call this object's own randomize() method." std::randomize(...) explicitly says "use the global system-level version instead," which is what lets you randomize local variables (or a class's non-rand members) from inside a method without it being mistaken for a call to the object's own randomize().

Turning randomization on/off: rand_mode and constraint_mode

Sometimes a test needs to temporarily stop randomizing a property, or turn off one specific rule, without editing the class itself. Two built-in methods control this, both available on any object with rand/randc properties or named constraint blocks:

packet p = new();
 
// freeze just one property; it keeps its current value across randomize()
p.addr.rand_mode(0);
void'(p.randomize());   // addr is untouched; data still randomizes normally
p.addr.rand_mode(1);    // re-enable addr
 
// freeze the whole object
p.rand_mode(0);
void'(p.randomize());   // no-op: neither addr nor data changes
p.rand_mode(1);          // re-enable everything
 
// disable one named constraint block, leaving the others active
p.addr_range_c.constraint_mode(0);
void'(p.randomize());   // addr can now take any value across its full 8-bit range;
                         // data_nonzero_c is still enforced
p.addr_range_c.constraint_mode(1);   // re-enable it
  • rand_mode(0/1), called on a whole object (p.rand_mode(0)) or on a single property (p.addr.rand_mode(0)), turns randomization off/on. A property with rand_mode off keeps whatever value it last had.
  • constraint_mode(0/1), called on a named constraint block (p.addr_range_c.constraint_mode(0)), disables/enables just that one rule without touching the class's other constraints.

Both default to enabled (1) and are commonly used together — for example, disabling one constraint block so a test can deliberately generate an out-of-range value while every other rule keeps holding.

pre_randomize() and post_randomize(): hooking into the call

Every class implicitly has two callback methods, pre_randomize() and post_randomize(), both empty by default — randomize() automatically calls pre_randomize() right before it solves the constraints, and post_randomize() right after. Overriding post_randomize() is the common case, typically to derive a field that depends on values that were just randomized:

class packet;
  rand bit [7:0] addr;
  rand bit [7:0] data;
  bit   [7:0]    checksum;   // not rand -- derived, not randomized directly
 
  constraint addr_range_c   { addr inside {[8'h10 : 8'h1F]}; }
  constraint data_nonzero_c { data != 8'h00; }
 
  function void post_randomize();
    checksum = addr ^ data;  // compute a dependent field right after randomization
    $display("randomized: addr=%0h data=%0h checksum=%0h", addr, data, checksum);
  endfunction
endclass

checksum isn't rand — it can't be, since its value depends on addr and data after they're randomized, not on its own random draw. post_randomize() is the natural place to compute it: by the time it runs, randomize() has already finished assigning addr and data. pre_randomize() is the mirror hook, called before the solver runs, occasionally used to reset some piece of state ahead of a new randomization.

Summary

  • rand declares a randomizable property; randc additionally guarantees "no repeats until every value has cycled through once," suited to small ranges.
  • Every class comes with a randomize() method that re-randomizes every rand/randc property; it returns 1 on success and 0 if the constraints can't all be satisfied — real code should check this.
  • constraint blocks are declarative rules handed to a solver, not procedural code that runs in order.
  • dist assigns different weights to different values; -> (implication) expresses conditional constraints.
  • randomize() with {...} layers extra constraints onto a single call without touching the class's existing constraint definitions.
  • $urandom/$urandom_range(maxval, minval = 0) generate a plain, uniformly random value with no class or constraint solver involved — a lightweight option for one-off values.
  • std::randomize(x, y) randomizes ordinary variables outside any class, with the same with {...} constraint syntax; the std:: prefix disambiguates it from an object's own randomize() method.
  • rand_mode(0/1) (on a whole object or a single property) and constraint_mode(0/1) (on a named constraint block) turn randomization or a specific rule off/on at runtime, without editing the class.
  • pre_randomize()/post_randomize() are callback methods automatically invoked right before/after randomize() runs — commonly overridden to derive a dependent field or log debug output after randomization.

What's the main difference in randomization behavior between a rand and a randc property?

A class declares two constraint blocks, one restricting addr and one restricting data. Which statement about how they're solved is correct?

Which method, called on an object, generates new random values for its declared rand/randc properties according to its constraint rules? (lowercase, no parentheses)

What does $urandom_range(20, 5) return?

Inside a class method, why write std::randomize(x, y) instead of just randomize(x, y) to randomize two local variables?

After calling p.addr.rand_mode(0) and then p.randomize(), what happens to p.addr?

Which callback method does randomize() automatically call right after it finishes assigning new values, commonly overridden to compute a dependent field or print debug output? (lowercase, no parentheses)