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.
The random number generation methods provided by SystemVerilog can be broadly classified into 3 categories
Constrained Pseudo Random Number Generators
Non-Constrained System Functions
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.
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();
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);
endprogram
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.
* 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;
// 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
endprogram
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
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;
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);
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);
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).
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);
endprogram
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);
endprogram
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);
endprogram
$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.