This article introduces the building blocks of the Randomization chapter: what randomization is and why it matters, the two ways to trigger it depending on where your variable lives, why checking its return value matters, and how a basic constraint lets you target stimulus toward specific scenarios.
6 min read | Intermediate Level
What You'll Learn
- Why constrained-random verification exists and what problem it solves
- The two ways to trigger randomization:
rand/randcclass properties andstd::randomize()for plain variables - Why checking the return value of
randomize()matters - How a basic constraint targets random stimulus toward specific scenarios
What Is Randomization and Why Does It Help?
Directed tests take a long time to develop because you have to think through every scenario you want to check, and there's a high chance you'll miss some corner case. Randomization flips this around: rather than hand-picking every value, you let SystemVerilog's constraint solver pick legal values that fall within a valid range and apply them to the signals you're interested in, run after run.
A single randomized run only exercises one legal combination out of many. Run the same randomized test dozens or hundreds of times, though, and the solver's picks spread across the entire legal space — including combinations you would never have thought to write a directed test for.
Where Is Constrained-Random Verification Used?
CRV is standard practice for verifying protocol interfaces, memory controllers, and bus arbiters, where the number of legal input combinations is far too large to enumerate by hand. Randomized address, data, and control fields combined with functional coverage are how modern testbenches gain confidence that a design has been exercised thoroughly.
Two Ways to Randomize
SystemVerilog gives you two ways to trigger randomization, depending on where the variable you want to randomize lives: mark a class property with rand or randc and call the object's built-in randomize() method, or call std::randomize() directly on a variable that isn't part of a class at all.
Randomizing Class Properties
Randomization is enabled per-variable by adding the rand or randc qualifier to a class member. SystemVerilog Random covers the difference between the two in depth, along with how they apply to arrays, queues, and dynamic arrays — for now, just know that both keywords make a property randomizable when you call randomize().
rand <type> <variable_name>; // random, any legal value each call
randc <type> <variable_name>; // random-cyclic, no repeats until the full set is exhausted
Parameters:
rand— marks the variable for standard randomization (required keyword)randc— marks the variable for cyclic randomization (required keyword)<type>— any legal data type such asbit,int, or an enum
Here's the simplest possible use of rand — no constraint block at all yet, just a property and a call to randomize():
class myPacket;
rand bit [3:0] mode; // no constraint - any value 0 to 15 is equally likely
endclass
module tb_top;
myPacket pkt;
initial begin
pkt = new();
pkt.randomize();
$display("mode = %0d", pkt.mode);
end
endmodule
Because there's no constraint block, mode can land anywhere from 0 to 15 with equal probability on every call to randomize(). Once you add a constraint block (covered later in this article), you narrow that range down to only the values you actually want.
Randomizing Plain Variables with std::randomize()
Not everything you want to randomize lives inside a class. std::randomize() is a built-in function that randomizes one or more plain variables directly — local variables in a task or function, module-level variables, or anything else that isn't a class property — without requiring the rand/randc qualifier or a surrounding class at all.
module tb_top;
initial begin
int addr, data;
// Randomize plain (non-rand) local variables directly, with an inline constraint
if (std::randomize(addr, data) with { addr inside {[0:255]}; data < 100; }) begin
$display("addr = %0d data = %0d", addr, data);
end else begin
$display("randomization failed");
end
end
endmodule
The with { ... } clause works exactly like an inline constraint on a class object — it applies only to that one call. The std:: prefix explicitly calls the global randomize function rather than a class's own randomize() method, which matters once you're calling it from inside a class (see Mistake #3 below).
Where Is std::randomize() Used?
std::randomize() shows up whenever you need one or two random values without the overhead of writing a whole class — randomizing a delay before driving a signal, picking a random burst length for a loop, or generating a quick randomized value directly inside a testbench.
Always Check the Return Value
Either way you randomize, randomize() and std::randomize() both return 1 on success and 0 if the constraints can't be satisfied — and on failure, the variables simply keep their previous values.
Always check the return value of randomize() (or std::randomize()), typically with assert(). Ignoring a failed call means your test silently continues with stale, non-random data instead of flagging a broken constraint.
"Checking the return value" means wrapping the call in an if (or assert) and reacting when it's 0, instead of calling randomize() as a bare statement. The example below randomizes the same object twice: once normally, and once with an inline constraint that deliberately conflicts with the class-level constraint, so you can see what a caught failure looks like.
class myPacket;
rand bit [1:0] mode;
constraint c_mode { mode < 3; } // mode can only be 0, 1, or 2
endclass
module tb_top;
myPacket pkt;
initial begin
pkt = new();
// Effective pattern: branch on the return value instead of trusting it blindly
if (pkt.randomize()) begin
$display("Randomization OK -> mode = %0d", pkt.mode);
end else begin
$display("Randomization FAIL -> mode unchanged, still = %0d", pkt.mode);
end
// mode == 5 can never satisfy c_mode (mode < 3), so this call is
// guaranteed to fail - a deliberate example of a caught failure
if (pkt.randomize() with { mode == 5; }) begin
$display("Randomization OK -> mode = %0d", pkt.mode);
end else begin
$display("Randomization FAIL -> mode unchanged, still = %0d", pkt.mode);
end
end
endmodule
The first call has no conflict, so it succeeds and pkt.mode gets a fresh random value from {0, 1, 2}. The second call adds an inline constraint (mode == 5) that can never be true alongside c_mode (mode < 3), so randomize() returns 0, the else branch runs, and pkt.mode is left exactly as it was — it is not set to 5. That's the practical meaning of "checking the return value": it's the difference between silently running with unintended data and actively catching the exact constraint that broke.
Quick Check
Q: A class-level constraint says addr < 100. You call randomize() with an inline constraint with { addr > 200; }. What happens?
Show Answer
randomize() returns 0. Inline constraints are combined with all existing constraint blocks using logical AND, and addr < 100 together with addr > 200 has no solution.
What Are Constraints?
A rand or randc property with no constraint block, like the first mode example above, can take on any legal value in its declared range. Most of the time, though, you don't want the entire range — you want to target a specific subset of scenarios: legal opcodes, valid address windows, or corner cases you're specifically trying to hit. A constraint block narrows the random search space down to exactly the values that matter, using one or more relational expressions. The simplest form is a plain comparison:
class myPacket;
rand bit [7:0] mode;
constraint c_mode { mode > 2; } // mode can only be 3 through 255
endclass
Adding c_mode here means every call to randomize() is guaranteed to produce a mode value greater than 2 — the solver simply won't pick anything outside that window. This is the mechanism behind targeted stimulus: instead of hoping a wide random range eventually lands on the scenario you care about, a constraint steers every single randomization attempt toward it.
Beyond simple comparisons like this, SystemVerilog also supports range checks with inside, weighted random distributions with dist, and constraints that reference other variables. Those styles deserve their own space rather than a quick summary here — see SystemVerilog Constraints and the worked Constraint Examples article for the full picture.
rand, randc, and constraint are testbench (verification) constructs, not synthesizable RTL. You'll find them inside classes used to build stimulus and check responses in a testbench, not inside a synthesizable design module.
Common Beginner Mistakes
Mistake #1: Not checking the return value of randomize()
If a constraint conflict makes randomization fail, randomize() returns 0 and the variables simply keep their old values — no error, no random data. Why? Ignoring the return value means the test keeps running with stale data instead of flagging the real problem, which can hide bugs in your constraints for a long time.
Mistake #2: Writing constraints that quietly contradict each other
If one constraint block says key == 3 and another says key < 2, no legal value exists and randomize() fails at run time. Why? All active constraint blocks on a class are combined with logical AND, so conflicting ranges across separate blocks are just as fatal as conflicting statements in the same block.
Mistake #3: Forgetting the std:: prefix inside a class method
Inside a class method, a bare call to randomize() resolves to that class's own built-in randomize() method, which randomizes the class's own rand/randc properties — not the global function. Why? If you need to randomize local or non-rand variables from inside a class method, prefix the call with std:: so SystemVerilog knows you mean the global randomize(), not the object's own method.
Continuing the Randomization Chapter
From here, the rest of the Randomization chapter builds on these basics: SystemVerilog Random covers rand and randc in depth, pre_randomize and post_randomize let you set up state before and after a call, disable randomization covers turning off rand/randc on individual variables, and randcase applies the same ideas to randomizing control flow itself.
Practice Exercise
Challenge: Randomize a Delay with std::randomize()
Without writing a class, use std::randomize() to randomize a local variable delay (an int) so it's constrained between 5 and 20, inclusive. Check the return value and print an error if it fails.
Hint
You don't need a class or the rand keyword at all — declare delay as a plain int and pass it directly to std::randomize() with an inline with constraint.
Solution
module tb_top;
initial begin
int delay;
if (std::randomize(delay) with { delay inside {[5:20]}; }) begin
$display("delay = %0d", delay);
end else begin
$display("ERROR: randomization of delay failed");
end
end
endmodule
delay never needed a rand qualifier or a surrounding class — std::randomize() randomizes the plain local variable directly, and the with clause supplies the range as an inline constraint.
Summary
In this tutorial, you learned:
- Constrained Random Verification generates legal stimulus automatically instead of relying on hand-written directed tests
- Class properties become randomizable with the
randorrandcqualifier — the difference between them is covered in the next article std::randomize()randomizes plain variables directly, without needing a class or therand/randcqualifiers- Always check the return value of
randomize()to catch conflicting constraints - A
constraintblock narrows the random search space to legal, targeted values — ranges,inside, anddistare covered in the Constraints chapter rand/randc/constraintare verification-only constructs, not synthesizable RTL