Why Learn Randomization? A modern design can have dozens of interacting signals, modes, and legal-but-unusual combinations. Hand-writing a directed test for every one of those combinations takes weeks, and it is easy to miss the exact corner case that later shows up as a bug. SystemVerilog's built-in randomization lets a constraint solver generate thousands of legal, weighted stimulus combinations automatically, which is why Constrained Random Verification (CRV) is the backbone of most UVM-based testbenches.

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/randc class properties and std::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.

Random state space coverage

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 as bit, 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 rand or randc qualifier — the difference between them is covered in the next article
  • std::randomize() randomizes plain variables directly, without needing a class or the rand/randc qualifiers
  • Always check the return value of randomize() to catch conflicting constraints
  • A constraint block narrows the random search space to legal, targeted values — ranges, inside, and dist are covered in the Constraints chapter
  • rand/randc/constraint are verification-only constructs, not synthesizable RTL
Did you find what you were looking for?

Quiz

Test Your Knowledge

1 of 5 Score: 0 / 5