Skip to content

SystemVerilog Randomization & Random Number Generation

Introduction

SystemVerilog has a number of methods to generate pseudo-random numbers - $random, $urandom, $urandom_range, object.randomize, std::randomize and many more. We look at how these methods are different and when to use each of them.

All code presented here can be downloaded from GitHub

Pseudo Random Number Generation

The random number generation methods provided by SystemVerilog can be broadly classified into 3 categories

  1. Constrained Pseudo Random Number Generators
  2. Non-Constrained System Functions
  3. Probabilistically distributed Random Number Generators

There are 2 important facts regarding the above 3 categories

  • Categories 1 & 2 are implementation dependent (i.e., they can be implemented differently in Synopsys VCS versus in Mentor Graphics Questa). Whereas category 3 is part of the SystemVerilog IEEE specification. Annex N of the SystemVerilog LRM specification has the actual code to generate these random numbers, so for a given seed the Synopsys VCS simulator and the Mentor Graphics Questa simulator will generate the same stream of random numbers.
  • Categories 1 & 2 offer something called Random Stability. This is discussed further down in this article.

PRNG

SystemVerilog Pseudo Random Number Generators

1. Constrained PRNG - obj.randomize & std::randomize

  • obj.randomize(), also called Class-Randomize Function, is a function built into all SystemVerilog classes. It is used to randomize the member variables of the class. Examine example 1.1, see how class member variable pkt_size is randomized.
  • std::randomize(), also called Scope-Randomize Function, is a utility provided by the SystemVerilog standard library (that's where the std:: comes from). It gives you the ability to randomize variables that are not members of a Class. In example 1.1 scope_var is randomized using std::randomize() because it is a local variable of function get_num.
  • Also, multiple variables can be randomized at the same time with either of these methods - std::randomize(var_a, var_b, var_c)

Example 1.1

/* Example 1.1 */
program automatic test;
    class pkt;
        logic [15:0] pkt_size;

        function new();
            pkt_size = 100;
        endfunction: new

        function logic [7:0] get_num();
            logic [7:0] scope_var;

            // When this function is called, randomize class
            // member var 'pkt_size' using the class's
            // in-built randomize method
            randomize(pkt_size);
            // Using SV std lib's scope randomize this  
            // function's local'scope_var'
            std::randomize(scope_var);

            $display("pkt.get_num: pkt_size %0d scope_var %0d",
                pkt_size, scope_var);
        endfunction: get_num
    endclass: pkt

    initial begin
        pkt p;

        p = new();
        p.get_num();
    end
endprogram
OUTPUT:
pkt.get_num: pkt_size 826 scope_var 62

  • You can use std lib's std::randomize() to randomize a class member variable, but if you try to use Class's randomize() call to randomize a function or task scope variable, as shown in example 1.2, you'll get the following type of compilation error.

Example 1.2

...
        function logic [7:0] get_num();
            logic [7:0] scope_var;

            /* Interchanging the class & scope randomize usage */
            // Using the class's in-built randomize
            std::randomize(pkt_size);
            // Using Class-Randomize function
            randomize(scope_var);

            $display("pkt.get_num: pkt_size %0d scope_var %0d",
                pkt_size, scope_var);
        endfunction: get_num 
... 
OUTPUT (From Synopsys VCS):
Error-[MFNFIOR] Constraint: member field
prng1.sv, 59
test, "scope_var"   
Member field scope_var not found in object this in randomize call.
Only direct class members or member array elements can be referenced as
arguments to object randomize.
1 error

OUTPUT (From Mentor Questa)
** Error: prng1.sv(59): (vlog-2934) Argument for randomize()
function must be a field of 'this'.

  • These are classified as Constrained PRNG because you can specify conditions or "constraints" around how you want the number randomized. Here is a simple example of how this is used in the Class & Scope-Randomize calls.

Example 1.3

...
            /* Example using Constraints */
            // Using the class's in-built randomize
            randomize(pkt_size) with { 
                pkt_size inside {[10:50]};
            };
            // Using SV std lib's scope randomize
            std::randomize(scope_var) with {
                scope_var inside {10, 20, 30};
            };
... 
  • In the above examples you saw randomize(pkt_size) being used to randomize the class member pkt_size from within the function get_num(). When randomize() is called on an object of the class, instead of from within it, its behavior is a little different. object.randomize() randomizes only the member variables that are defined as rand.
  • The output of example 1.4 is as follows. You'll see that the non-rand class member, pkt_size, doesn't get randomized. Its value remains at its initial value of 100, which is set when the object is new-ed

Example 1.4

program automatic test;
    class pkt;
        rand logic [7:0] saddr, daddr;
        rand logic [3:0] etype;
        logic [15:0] pkt_size;

        function new();
            pkt_size = 100;
        endfunction: new
    endclass: pkt

    initial begin
        pkt p;

        p = new();
        for (int i=0; i<5; i++) begin
            /* Calling randomize() on an object does  
            * not randomize non-rand variables
            */
            p.randomize();
            $display("p[%0d] -> saddr %d, daddr %d, etype %d, pkt_size %d",
                i ,p.saddr, p.daddr, p.etype, p.pkt_size);
        end
    end
endprogram
OUTPUT:

p[0] -> saddr  64, daddr   2, etype 14, pkt_size   100
p[1] -> saddr 107, daddr 193, etype  0, pkt_size   100
p[2] -> saddr  86, daddr 157, etype  9, pkt_size   100
p[3] -> saddr  69, daddr 171, etype 12, pkt_size   100
p[4] -> saddr 122, daddr 135, etype 11, pkt_size   100

Seeding

There are 2 ways to set the random seed of an object -

  • Direct: Along with randomize() every SystemVerilog class has an in-built function called srandom(). Calling srandom() on an object overrides its RNG seed. As shown in example 1.5A & 1.5B you can either call this.srandom(seed) from within a class function/task or call it on an object of the class, p.srandom(seed). In both cases the output is the same
pkt.seeding: pkt_size 56668 scope_var 62
  • Indirect: Using the simulator's command-line. In case of Synopsys VCS this is +ntb_random_seed=10 and in case of Mentor Graphics Questa it is -sv_seed=10. With this method, you are re-setting the root seed (i.e, the seed of the program block) and by virtue of that you are changing the seed of the object as well.
# Synopsys-VCS
% ./simv +ntb_random_seed=10
# Mentor-Questa
% vsim -c test -sv_seed 10 -do "run -all"

Example 1.5A & 1.5B

* Example 1.5A & 1.5B */
program automatic test;
    class pkt;
        logic [15:0] pkt_size;

        function void seeding(int seed);
            logic [7:0] scope_var;
            $display("seeding: seed is %0d", seed);
            // Call srandom only if the seed arg is non-zero
            if (seed != 0)
                this.srandom(seed);
            randomize(pkt_size);
            std::randomize(scope_var);

            $display("pkt.seeding: pkt_size %0d scope_var %0d", pkt_size, scope_var);
        endfunction: seeding
    endclass: pkt

    initial begin
        pkt p;

        p = new();
        if ($value$plusargs("SEED=%d", seed)) begin
            $display("SEED entered %0d", seed);
        end else begin
            seed = 0;
        end
        // Example 1.5A
        // Call srandom from within the class function
        `ifdef EX_1_5A
        p.seeding(seed);
        `endif

        // Example 1.5B
        // Call obj.srandom() to set the seed
        `ifdef EX_1_5B
        p.srandom(seed);
        // Pass seed function arg as 0 so that the seeding function
        // does not call srandom.
        p.seeding(0);
        `endif
    end
endprogram

If the above 2 points were a little confusing, or if you would like to know how seeds affect random number generators - check out this article on Random Stability

Download example code of the above section


Subscribe

Get Notified when a new article is published!


2. Non-Constrained System Functions - $urandom & $urandom_range

  • $urandom() and $urandom_range(max, min=0) fall under this category. The former returns a 32-bit unsigned integer while the latter returns a number in the specified range. If the 'min' value is not provided in $urandom_range, it returns a number in the range [0, max]. This range is inclusive, i.e, any number in the range 'min' to 'max', including the values 'min' and 'max' could be returned.
  • Since $urandom() returns a 32-bit number, if you have to randomize a variable or a bus that is larger than 32 bits it is easier to use std::randomize() instead. With $urandom() you'll have to concatenate multiple $urandom() calls to get a random number larger than 32 bits. This is shown in Example 2.1.
  • With these 2 system calls constraints cannot be specified, hence we classify them as Non-Constrained PRNGs

Example 2.1

program automatic test;
    initial begin
        int unsigned var_a;
        logic [63:0] var_b;
        logic [7:0] var_c;

        var_a = $urandom();
        var_c = $urandom_range(10, 20);
        $display("var_a 0x%x var_c 0x%x", var_a, var_c);

        var_b = $urandom();
        $display("Using $urandom() to randomize 64bit var, var_b = 0x%x", var_b);

        //var_b = $urandom() << 32 | $urandom(); - another option
        var_b = {$urandom(), $urandom()};
        $display("Using concat $urandom(), var_b = 0x%x", var_b);

        std::randomize(var_b);
        $display("std::randomize(var_b) = 0x%x", var_b);
    end
endprogram
OUTPUT:

var_a 0x0c62b465 var_c 0x0c
Using $urandom() to randomize 64bit var, var_b = 0x0000000084ddb13d
Using concat $urandom(), var_b = 0x4a768d1860367d0f
std::randomize(var_b) = 0x7a4a73cb0e12f31f

Seeding

Here again you have 2 ways to set the random seed

  • Direct: Pass the seed into the first $urandom(seed) call. Seeding $urandom() also affects subsequent calls to $urandom_range().
  • Indirect: Using the simulator's command-line. In case of Synopsys VCS this is +ntb_random_seed=10 and in case of Mentor Graphics Questa it is -sv_seed=10.

Example 2.2

program automatic test;
initial begin
    int unsigned var_a, var_d, seed;
    logic [63:0] var_b;

    if ($value$plusargs("SEED=%d", seed)) begin
        $display("SEED entered %0d", seed);
    end else begin
        seed = 20;
    end
    var_a = $urandom(seed);    
    var_b = $urandom();
    var_d = $urandom_range(10, 2000);
    $display("var_a 0x%x var_b 0x%x var_d 0x%x", var_a, var_b, var_d);
end

Once again, for more on this checkout Random Stability.

3. Probabilistically Distributed PRNGs - $random, $dist_normal, $dist_poisson ...

This excerpt from the SystemVerilog LRM 1800-2012 best explains what this category includes -

Quote

In addition to the constrained random value generation, SystemVerilog provides a set of RNGs that return integer values distributed according to standard probabilistic functions. These are: $random, $dist_uniform, $dist_normal, $dist_exponential, $dist_poisson, $dist_chi_square, $dist_t, and $dist_erlang.The value generation algorithm for these system functions is part of this standard, ensuring repeatable random value sets across different implementations. The C source code for this algorithm is included in Annex N.

Example 3.1

program automatic test;
    initial begin
        int var_a, var_b, seed;

        seed = 10;
        for (int i=0; i<3; i++) begin
            var_a = $random;
            var_b = $dist_poisson(seed, 100);
            $display("%0d -> var_a %d var_b %d", i, var_a, var_b);
        end
    end
endprogram
  • $random returns a signed 32-bit integer. From Annex N, you'll see $random is nothing but $dist_uniform, with some pre-defined arguments. dist_uniform (seed, LONG_MIN, LONG_MAX)
  • So, $random returns numbers such that their probabilities are uniformly distributed in the range LONG_MIN to LONG_MAX (LONG here is the C definition).
CONSOLE OUTPUT:
0 -> var_a   303379748 var_b         100
1 -> var_a -1064739199 var_b         103
2 -> var_a -2071669239 var_b         110
  • But, there's one quirky feature that $random provides. If you use a modulo operator to generate a number within a range, then adding the concatenation operator { } around $random, generates a positive number (See example 3.2). I call this quirky because just doing var_a = {$random} does not return a positive integer value, it is only when you use {} in conjunction to % operator, do you get this.

Example 3.2

program automatic test;
    initial begin
        int var_a, var_b, var_c, seed, seed1, seed2, seed3;

        // Using 3 different seed vars for a specific reason.
        // Read the seeding section next to find out why.
        seed1 = 10; seed2 = 10; seed3 = 10;
        for (int i=0; i<5; i++) begin
            var_a = {$random(seed1)};
            var_b = $random(seed2)%50;
            var_c = {$random(seed3)}%50;
            $display("$random %0d -> var_a %d var_b %d var_c %d", i, var_a, var_b, var_c);
        end
    end
endprogram
CONSOLE OUTPUT:
# $random 0 -> var_a -2146792448 var_b -48 var_c  48
# $random 1 -> var_a -1686787018 var_b -18 var_c  28
# $random 2 -> var_a   576097604 var_b   4 var_c   4
# $random 3 -> var_a  1855681501 var_b   1 var_c   1
# $random 4 -> var_a  -390931759 var_b  -9 var_c  37

Seeding

  • The first argument for all the system functions in this section is the seed. It has to be an integer variable and cannot be a constant. This is because the "seed" argument is of type INOUT, i.e., every time one of these system fuctions is called a value is passed in and different value is returned. This returned value becomes the seed for the next iteration of the call. Lets make sure you've understood this using an example.

Example 3.3

program automatic test;
    initial begin
        int var_a, seed;
        seed = 10;
        for (int i=0; i<5; i++) begin
            var_a = $random(seed);
            $display("Loop #%0d. var_a %d next-seed %0d", i, var_a, seed);
        end
    end
endprogram
CONSOLE OUTPUT:
Loop #0. var_a -2146792448 next-seed 690691
Loop #1. var_a -1686787018 next-seed 460696424
Loop #2. var_a   576097604 next-seed -1571386807
Loop #3. var_a  1855681501 next-seed -291802762
Loop #4. var_a  -390931759 next-seed 1756551551
  • In example 3.3 we call $random 5 times in a loop. The starting seed is 10 so we assign var seed=10 and call $random(seed). Invoking $random not only returns a 32-bit signed int (which is assigned to var_a) but also changes the value of the seed variable (remember, seed argument is of type INOUT). So the seed for the 'next' $random call is set by the 'current' $random call. This is how these system functions guarantee that for a given starting seed, the same sequence of pseudo-random numbers are always generated. .. smart isn't it?
  • Note that you should not pass a constant for the seed argument. A call such as this $dist_poisson(10, 550) will result in the following type of warning.
Warning-[STASKW_ISATDP] Illegal seed argument
prng3.sv, 31
  Expecting seed, an integer variable as the first argument to 
  '$dist_poisson', but an argument of invalid type is passed.
  Please pass an integer variable as the first argument to '$dist_poisson'.
  • Since the seed for the $random call is carried in the "seed" variable, you can do things like example 3.4. Notice how we have created 2 separate, independent PRNG sequences by using 2 separate seed variables. Even though var_a and var_b are randomized alternatively, each of their $random function call does not affect the other variable, since their "random states" is being carried through different variables. Since seed1=seed2=20, both var_a and var_b will generate the same sequence of random numbers ... Once again, I think this is pretty cool!

Example 3.4

program automatic test;
    initial begin
        int var_a, var_b, seed1, seed2;
        seed1 = 20;
        seed2 = 20
        for (int i=0; i5; i++) begin
            var_a = $random(seed1); var_b = $random(seed2);
            $display("$random #%0d -> var_a %d var_b %d", i, var_a, var_b);
        end
    end
endprogram
OUTPUT:
$random 0 -> var_a -2146101760 var_b -2146101760
$random 1 -> var_a -1226159507 var_b -1226159507
$random 2 -> var_a -1470918064 var_b -1470918064
$random 3 -> var_a -1713525709 var_b -1713525709
$random 4 -> var_a   592620358 var_b   592620358

In a Nutshell

So, when do you use what?

  • $urandom, $urandom_range - Use this if the variable you are assigning this to is of type int
  • obj.randomize - This is an easy one. You can only use this to randomize objects.
  • std::randomize - If you aren't randomizing an object, then this is the call you should be using. Use this especially if the variable you are trying to randomize is wider than 32-bits.
  • $random, $dist_normal, $dist_exponential, etc - Use these if you need your random numbers to follow a specific distribution. Remember, $random is essetianlly $dist_uniform. For general purposes use the above 3 methods instead of using $random. The main reason you should use $urandom, $urandom_range, obj.randomize() or std::randomize() is because they offer an important property called Random Stability.

Subscribe

Every month or so I send out a newsletter with lessons from my experience, notable technical papers, and notifications about new articles.

If you found this content useful then please consider supporting this site! 🫶

Buy Me A Coffee