Randomization in System Verilog
System Verilog is a powerful language for designing and verifying complex digital systems. One of the features that makes System Verilog stand out is its support for randomization. In this article, we explore the basics of randomization in System Verilog and see some examples of how to use it. Constraints will be covered in other article, as they are a more advanced topic that deserves a separate discussion.
Introduction
What is randomization?
Randomization is the process of assigning random values to variables or objects in System Verilog. Randomization can be used to generate inputs for the DUT or to create variations in testbench components, like monitor, driver, or checkers.
Randomization also helps achieve better functional and code coverage of DUT, as it can expose corner cases and unexpected behaviours that might be difficult to predict.
Need for randomization!
In a test bench, we need to generate lots of stimulus for the DUT. Writing a directed stimulus for all scenarios are not feasible and there are also chances that we miss some corner case scenarios in directed stimulus. Randomization allows us to create test scenarios that are unpredictable and realistic, without having to write a lot of code.
For example, if we need to verify a 32-bit ALU, and we need to verify the addition feature. Input for alu will be thirty-two bits wide and there can be lot of input. Writing stimulus for each input will not be possible. Using randomization, we can randomly create lot of stimuli and hit some corner case scenarios as well.
How to use randomization?
Apart from the $random and $urandom system function, System Verilog provides new ways to achieve randomization. $random and $urandom can be used to generate randomness but in real-life scenarios, random stimuli also need to be constrained. Providing a constraint with these built-in functions are difficult. Also, these functions do not provide random stability and thus it becomes hard to reproduce a failure.
Random stability ensures that the same sequence of random numbers is generated every time a code is run. This helps ensure that certain behaviour can be reproduced. Random stability is achieved by using a seed value to initialize the random number generator.
In System Verilog we can declare any variable defined inside a class as random. These random variables are randomized whenever we call randomize function on the class object handle.
To declare a variable as random we use rand
or randc
keyword. The rand
keyword means that the variable or object can take any value within its range, while the randc
keyword means that the variable or object can take any value within its range, but not the same value until on the possible random values have been used. Using randc
ensures that we get unique random values.
randomize()
In SV, all classes have an in-built method randomize
which can be called to randomize the variables. If we do not call randomize function, then the variables will take the default value assigned to them. This also ensures that we can use the random variables with some known values without randomizing it.
This method also returns the status of randomization, i.e., whether randomization is successful or not. Randomization can be unsuccessful due to some conflicting constraints or other solver issues. Thus, we can use the returned value to catch randomization errors.
pre_randomize()
This is a callback function which is called automatically when we call randomize
function. This function is not called directly. SV provides this function which we can override in our class to perform some action before randomization of variable happens.
post_randomize()
Like pre_randomize
this is also a callback function which is called automatically. But this is called after randomization has been done. This is also overridden in the class definition and can be used to check the randomized variables or modify it.
Disabling randomization
We can disable randomization for any of the random variables using rand_mode()
method. This ensures that we have finer granularity in randomizing variables and randomization for some variable can be enabled/disabled for specific scenarios.
<var>.rand_mode(0);
will disable the randomization while <var>.rand_mode(1);
will enable randomization for variable <var>
.
Example
In this example, we have two random variables in class, addr
and data
. parity
for data
is calculated after the randomization is done.
class packet;
rand bit [3:0] addr;
rand bit [7:0] data;
randc bit [1:0] pkt_type;
bit parity;
function void pre_randomize();
$display("Value before randomization - addr: %h, data: %0d, pkt_type: %0d, parity: %b", addr, data, pkt_type, parity);
endfunction
function void post_randomize();
parity = ($countones(data) % 2) ? 0 : 1;
$display("Value after randomization - addr: %h, data: %0d, pkt_type: %0d, parity: %b\n", addr, data, pkt_type, parity);
endfunction
endclass //packet
module test;
packet pkt;
initial begin
pkt = new;
repeat(5) begin
pkt.randomize();
end
pkt.data = 45;
pkt.data.rand_mode(0);
// does not randomize data and thus
// data will be 45
pkt.randomize();
pkt.data = 45;
pkt.data.rand_mode(1);
// will randomize data as randomization
// is enabled
pkt.randomize();
end
endmodule
Try this code in EDA PlaygroundOutput
# Value before randomization - addr: 0, data: 0, pkt_type: 0, parity: 0
# Value after randomization - addr: e, data: 80, pkt_type: 3, parity: 1
#
# Value before randomization - addr: e, data: 80, pkt_type: 3, parity: 1
# Value after randomization - addr: 2, data: 39, pkt_type: 0, parity: 1
#
# Value before randomization - addr: 2, data: 39, pkt_type: 0, parity: 1
# Value after randomization - addr: 8, data: 24, pkt_type: 1, parity: 1
#
# Value before randomization - addr: 8, data: 24, pkt_type: 1, parity: 1
# Value after randomization - addr: 0, data: 77, pkt_type: 2, parity: 1
#
# Value before randomization - addr: 0, data: 77, pkt_type: 2, parity: 1
# Value after randomization - addr: b, data: 126, pkt_type: 2, parity: 1
#
# Value before randomization - addr: b, data: 45, pkt_type: 2, parity: 1
# Value after randomization - addr: 7, data: 45, pkt_type: 1, parity: 1
#
# Value before randomization - addr: 7, data: 45, pkt_type: 1, parity: 1
# Value after randomization - addr: 1, data: 41, pkt_type: 3, parity: 0
In output observe that the variable pkt_type
is randomized with unique values until all the possible values are exhausted.
Static randomization function
SV also provides a static randomization function which can be called from any class and can be used to randomize variables. It is different from the normal randomization function as in this we explicitly pass the variables which we want to randomize. Even variables which are declared as random can be used with this static function.
Syntax – std::randomize(<var>);
This is useful when we want to randomize some of the variables without randomizing the entire class or when we want to randomize non-rand variables. This is better than $urandom and $random function as it provides all the good feature of the randomize function.
Another advantage of using static randomize function is that it can be used inside static class or modules as well. Thus, if we need to randomize some variable in module, this function comes very handy.
with keyword
with
keyword is used to provide constraint when we are using randomize function. This works both with normal randomize function and std::randomize function. While using std::randomize
we can pass the constraint only through with
keyword.
Example
module test();
bit [15:0] addr;
initial begin
repeat(5) begin
std::randomize(addr) with { addr < 23; };
$display("addr = %0d", addr);
end
end
endmodule
Output
# addr = 7
# addr = 15
# addr = 5
# addr = 14
# addr = 22
Conclusion
In this article, we have learned the basics of randomization in System Verilog, which is a feature that allows us to generate random values for variables or objects. Randomization can help us to create realistic and unpredictable test scenarios for our DUT and improve our verification quality. We have learned how to declare variables as random using rand or randc keywords, and how to randomize them using randomize or std::randomize methods. We have also learned how to use pre_randomize and post_randomize callbacks to perform some actions before or after randomization. Additionally, we have looked at some examples of how to use randomization for different scenarios, such as ALU operation and packet generator. In next article we explore constraints and how the generated values can be restricted.