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
- 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.
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 variablepkt_size
is randomized.std::randomize()
, also called Scope-Randomize Function, is a utility provided by the SystemVerilog standard library (that's where thestd::
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 usingstd::randomize()
because it is a local variable offunction
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'srandomize()
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 memberpkt_size
from within the functionget_num()
. Whenrandomize()
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 asrand
. - 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 isnew
-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 calledsrandom()
. Callingsrandom()
on an object overrides its RNG seed. As shown in example 1.5A & 1.5B you can either callthis.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
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 usestd::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 rangeLONG_MIN
toLONG_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 doingvar_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 varseed=10
and call$random(seed)
. Invoking$random
not only returns a 32-bit signed int (which is assigned tovar_a
) but also changes the value of theseed
variable (remember, seed argument is of typeINOUT
). 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 thoughvar_a
andvar_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. Sinceseed1=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 intobj.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()
orstd::randomize()
is because they offer an important property called Random Stability.