[Edit of Image1]
Hey it's a me again @drifter1!
Today we continue with the Logic Design series on SystemVerilog in order to talk about Constraints and Randomization.
So, without further ado, let's get straight into it!
Initially, design testing and verification was done through something known as a verification plan. Such verification is more direct as independent scenarios target particular features of a design. But, as the complexity of the designs increased, many such scenarios lead to the introduction of randomized testing. This allowed for a better identification of corner cases that may "slip" using the traditional direct approach.
In Verilog, the only way of randomizing is the build-in system task $random, which returns a "random" 32-bit integer. Of course, this is not sufficient for object randomization, which is needed in the context of SystemVerilog's OOP. Therefore, SystemVerilog introduces Constraints, which are a compact way of specifying the legal values that will be used by the solver / tester. These values are given to specially-declared Random Variables.
Any normal variable, array, dynamic array or queue can be declared random by adding the keyword rand. This specifies a standard random variable, where the values are uniformly distributed.
For example:
rand integer num;
It's also possible to declare a random variable using the randc keyword. This leads to something known as cyclic randomization, where no value is repeated within a given iteration. Additionally, such variables are limited to a maximum of 16-bits (which means that the maximum range is 0 to 65535) and can only be of integer, reg and enumerated types.
In order to randomize the random variables declared in a class, the randomize() method has to be called on an instance (or object) of that class. Of course, objects are not randomized automatically.
The randomize() method returns 1 when the randomization is successful, otherwise it returns 0. This can be used for a success check as shown below.
className instance;
if (instance.randomize())
// success
else
// failure
By default, constraints are used for the purpose of randomization. So, all constraints are initially active.
Deactivating constraints is a simple as calling the constraint_mode() method on each constraint.
instance.constraintName.constraint_mode(val);
where a value of 1 activates the constraint, and a value of 0 deactivates it.
Additionally, it's also possible to activate and deactivate the "randomness" of the random variables by calling the random_mode() method on them. By default, all random variables are active.
instance.randomVariable.rand_mode(val);
Here a value of 1 specifies that the random variable will be randomized, and 0 that it will not be randomized when calling randomize().
When calling randomize() the built-in pre_randomize() and post_randomize() functions are called automatically, before and after the randomization correspondingly. Similar to randomize() these are also recursively called on each object member of the object that calls randomize().
Of course, these functions are initially empty. So, it's possible to override them in order to add custom code before and after randomization.
Note that the randomize() task cannot be overridden!
Block diagrams and other visualizations were made using draw.io
And this is actually it for today's post!
Next time we will continue on with more on Constraints...
See Ya!

Keep on drifting!