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() and post_randomize() run relative to randomize()
  • How overriding these two callbacks changes an object's randomization behavior
  • What happens to post_randomize() when randomize() 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

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

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

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

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 returns 1 on success and 0 on failure
  • pre_randomize() runs immediately before the solver assigns random values
  • post_randomize() runs immediately after, but only if randomize() 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 virtual keyword, 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.

Did you find what you were looking for?

Quiz

Test Your Knowledge

1 of 5 Score: 0 / 5