Variables declared rand or randc inside a class aren't randomized automatically — you always call the built-in randomize() method to do it, and it returns 1 on success or 0 on failure. Two callback functions, pre_randomize() and post_randomize(), let you hook into that process to run your own code immediately before and after the solver assigns new random values.
6 min read | Intermediate Level
What You'll Learn
- How to call the built-in
randomize()method and check its return value - When
pre_randomize()andpost_randomize()run relative torandomize() - How overriding these two callbacks changes an object's randomization behavior
- What happens to
post_randomize()whenrandomize()fails
Calling randomize()
Class objects are not randomized automatically — you always have to call the built-in randomize() method yourself. It can fail for a variety of reasons, such as conflicting constraints or a solver that simply can't find a value satisfying every constraint at once, so the method returns a status you can check.
virtual function int randomize ();
Here's a simple example showing how randomize() is called:
class Beverage;
rand bit [7:0] beer_id;
constraint c_beer_id { beer_id >= 10;
beer_id <= 50; };
endclass
module tb;
Beverage b;
initial begin
b = new ();
$display ("Initial beerId = %0d", b.beer_id);
if (b.randomize ())
$display ("Randomization successful !");
$display ("After randomization beerId = %0d", b.beer_id);
end
endmodule
Output
run -all; # KERNEL: Initial beerId = 0 # KERNEL: Randomization successful ! # KERNEL: After randomization beerId = 25 # KERNEL: Simulation has finished. There are no more test vectors to simulate.
beer_id starts out at its default value of 0, then randomize() assigns it a fresh value between 10 and 50 as required by c_beer_id, and the if check confirms the call succeeded before printing the result. There are also two callback functions that randomize() automatically calls for you — one right before it computes random values, and one right after.
pre_randomize(): Running Code Before Randomization
This function is defined within the same class whose object will be randomized, and is called before the solver assigns random values.
function void pre_randomize();
We'll add a pre_randomize() function to the example discussed earlier, using the same testbench:
class Beverage;
rand bit [7:0] beer_id;
constraint c_beer_id { beer_id >= 10;
beer_id <= 50; };
function void pre_randomize ();
$display ("This will be called just before randomization");
endfunction
endclass
Output
run -all; # KERNEL: Initial beerId = 0 # KERNEL: This will be called just before randomization # KERNEL: Randomization successful ! # KERNEL: After randomization beerId = 25 # KERNEL: Simulation has finished. There are no more test vectors to simulate.
post_randomize(): Running Code After Randomization
This function is also defined within the same class, and is called after the solver has finished assigning random values.
function void post_randomize();
We'll add a post_randomize() function to the same example:
class Beverage;
rand bit [7:0] beer_id;
constraint c_beer_id { beer_id >= 10;
beer_id <= 50; };
function void pre_randomize ();
$display ("This will be called just before randomization");
endfunction
function void post_randomize ();
$display ("This will be called just after randomization");
endfunction
endclass
Output
run -all; # KERNEL: Initial beerId = 0 # KERNEL: This will be called just before randomization # KERNEL: This will be called just after randomization # KERNEL: Randomization successful ! # KERNEL: After randomization beerId = 25 # KERNEL: Simulation has finished. There are no more test vectors to simulate.
The order in the output tells the whole story: pre_randomize() runs first, then the solver assigns beer_id, then post_randomize() runs, and only after all of that does b.randomize() actually return — which is why "Randomization successful !" prints last, even though it's the first line of code inside the if.
Quick Check
Q: During a single call to randomize(), in what order do pre_randomize(), the constraint solver, and post_randomize() run?
Show Answer
pre_randomize() runs first, then the solver assigns random values to the object's rand/randc properties, and post_randomize() runs last — but only if the solve succeeded.
Overriding the Callbacks
What we did above is override the existing, empty pre_randomize() and post_randomize() methods with our own definitions — a clean way to change an object's randomization behavior without touching randomize() itself. If a class is derived from a base class and doesn't define its own version of either method, both automatically call their super version instead.
pre_randomize() and post_randomize() are not declared virtual, but they behave as if they were. If you try to make them virtual yourself, you'll hit a compiler error:
class Beverage;
rand bit [7:0] beer_id;
virtual function void pre_randomize ();
$display ("This will be called just before randomization");
endfunction
endclass
Output
virtual function void pre_randomize ();
|
ncvlog: *E,CLSMNV (testbench.sv,7|36): The pre_randomize() method cannot be declared virtual.
- If
randomize()fails,post_randomize()is not called - The
randomize()method itself is built-in and cannot be overridden - If randomization fails, the variables retain their original values and are not modified
Where Are pre_randomize() and post_randomize() Used?
pre_randomize() is handy for setting up values that a constraint depends on right before the solve runs, such as adjusting a range based on the current test phase. post_randomize() is more commonly used to fix up fields that are easier to compute after the randomized values exist — recalculating a checksum or parity bit over an already-randomized packet, or logging the final randomized values for debug.
Common Beginner Mistakes
Mistake #1: Expecting post_randomize() to run even when randomize() fails
post_randomize() only runs after a successful solve. Why? If the constraints couldn't be satisfied, there's no new random data to act on, so any cleanup logic you put in post_randomize() (like a checksum update) would be operating on unchanged, possibly stale values — SystemVerilog skips the call entirely instead.
Mistake #2: Manually declaring pre_randomize() or post_randomize() as virtual
Both callbacks already behave like virtual methods without the keyword. Why? The virtual keyword is reserved for methods you declare it on yourself; since these two are built-in overridable hooks, adding virtual explicitly is a compile-time error, not just a style issue.
Mistake #3: Assuming every class needs its own pre_randomize()/post_randomize()
Both methods have empty built-in default implementations, so you only define them when you actually need custom behavior. Why? A derived class that skips its own override doesn't lose functionality — it automatically falls back to calling the base class's version through super.
Practice Exercise
Challenge: Compute a Checksum in post_randomize()
Extend the Beverage class with a second field, bit [7:0] checksum, that is not declared rand. In post_randomize(), set checksum to the bitwise complement of beer_id (~beer_id), then print both fields.
Hint
checksum should be a plain class property, not rand — you're computing it yourself in post_randomize() after beer_id already has its final randomized value.
Solution
class Beverage;
rand bit [7:0] beer_id;
bit [7:0] checksum;
constraint c_beer_id { beer_id >= 10;
beer_id <= 50; };
function void post_randomize ();
checksum = ~beer_id;
$display ("beer_id = %0d checksum = %0d", beer_id, checksum);
endfunction
endclass
Because post_randomize() runs after the solver has already assigned beer_id, it's the right place to derive fields like checksum that depend on the final randomized value — computing it in pre_randomize() would use the old, pre-randomization value instead.
Summary
In this tutorial, you learned:
- Class objects are never randomized automatically — you always call the built-in
randomize()method, which returns1on success and0on failure pre_randomize()runs immediately before the solver assigns random valuespost_randomize()runs immediately after, but only ifrandomize()succeeded- Overriding either callback is a clean way to customize an object's randomization behavior without touching
randomize()itself - Neither callback needs (or accepts) the
virtualkeyword, even though both behave virtually
Continuing the Randomization Chapter
Continue with disable randomization to turn off rand/randc on individual variables, and randcase to apply the same ideas to randomizing control flow itself.