by Maia Desamo March 2023

Developing a test where simulation parameters are randomized enables achieving better coverage of the DUT’s state space by simulating different test seeds. This methodology reduces the time required for test creation and maintenance. If a directed test is required, it can be created by adding constraints to the random test.

To see the benefits of Constrained-Random Testing(CRT) technique, I re-commend reading Chapter 1 of Spear, C. Tumbush, G. (2012). SystemVerilog for Verification (3rd ed.). Springer.

SystemVerilog has powerful tools that allow controlling the randomization of simulation variables. The purpose of this section is to review the different tools and use cases. To simplify the language and draw from probability theory to describe the behavior of our testbench based on a CRT design, we will define our input signals and register values as discrete random variables. We will use the frequentist definition of probability, and for the examples, we will assume that:

with a number of samples greater than 500.

The reserved keyword rand/randc allows us to give an element of a class the property of being randomized. This implies that when the .randomize() method of the class is called, the value of that member will change according to the probability distribution associated with it. To modify the distribution of a member of a class, we will have to use constraints or the .pre_randomize() or .post_randomize() methods. For example:

- Randomization of string members is not supported by SystemVerilog.
- If an empty queue is randomized and any element is accessed, it will return 0 without runtime error. And the randomization will not have any effect.
- If the queue has values with allocated memory, they will be randomized.
- If a constraint is added to the size member of the queue during randomization, the queue will have as many elements as assigned to size and they will be random.
- Enumeration variables can be randomized, which facilitates maintaining names associated with the abstraction of the DUT.

Using the .randomize() method on an object of the class allows the elements to take random values. When this method is invoked, the pre_randomize() method is executed first, followed by the post_randomize() method. By using these methods, we can create practically any desired joint input distribution to the DUT. Here are some examples.

With constraints, we can modify the distribution with which the members of the rand type are generated. One very useful thing that it allows us to do is to establish relationships between the members of the class. Let’s take the following class as an example:

Basically, we have a random pair (*x, y*) that, being of type bit, can only take the value 0 or 1. We know that if we do not put any constraint, they will take values from flat and independent distributions. However, with the new constraint, this is modified as the pair (0*, *1) goes from having a probability of 1*/*4 to having a probability of 0. This implies that we no longer have the four possible combinations of (*x, y*) with equal probability. The following figure shows the effect of this constraint on the distribution of all members.

We can see that *P*(*x*) and *P*(*y*) became strongly asymmetric, and *P *(*x,y*) is practically flat among the three possible values it can take. We can interpret that the system that solves the random values of the members tends to give flat joint probabilities, which when the members are independent translates into members of a flat distribution. But as soon as *P *(*y/x*) is modified by adding a constraint, this implies that to maintain a flat *P*(*x,y*), *P*(*x*) and *P *(*y*) must be asymmetric. Perhaps remembering the following probability expressions will be useful at this point:

Regarding this, the Institute of Electrical and Electronics Engineers (Standard for System Verilog, 2018) states: "The solver shall assure that the random values are selected to give a uniform value distribution over legal value combinations (that is, all combinations of legal values have the same probability of being the solution). This important property guarantees that all legal value combinations are equally probable, which allows randomization to better explore the whole design space... "

Remember that the flat distribution is the one that maximizes entropy (that is, the least biased that can be attributed to a statistical system) if only the support of the variable is known (Information Theory). This is also valid for joint distribution. If you want an immersion in topics such as discrete variable entropy, error correction code, or data compression, I recommend Cover,T.M. Thomas, J.A. (2006). Elements of information theory (2nd ed.). John Wiley & Sons, Inc.

Perhaps, as the constraint conditions *y *to *x*, someone might think that *P*(*x*) would remain flat and only *P *(*y*) would be modified. But as we saw, this is not the case. However, we have another element to modify the distribution of the members, and this is the "solve" keyword. This element allows us to choose the order in which the members of the class are randomized, allowing some of them to be independent of the rest despite the constraint. In our example, it would be:

With this change, *x *becomes independent of the value of *y *since it takes its value "before hand". In this case, the distributions are as follows:

Note that in this case, the pair (0,0) becomes much more likely.

If you want to add a constraint for a class randomization, you can add the condition without modifying the class using the reserved word "with"(this is called an in-line constraint, later you will find example 3 of this type of constraint). Note that in constraints, you cannot invoke user-defined functions or all methods provided by SystemVerilog. Therefore, if the relationship that a member has is too complex to be added in a constraint, it can be calculated as a function of the other random variables within the .post_randomize. A typical example of this is the calculation of frame redundancy.

Now, suppose for some reason we want the member to have the distribution:

To achieve this, we can use the dist operator. This operator allows us to assign weights to the occurrence of values or ranges of values. Note that this operator cannot be used with randc variables (for more information, see (18.5.4 of the Standard for SystemVerilog2018)). The example would be as follows:

For this case, the distributions are as follows:

Note that it is not necessary to use solve/before to get *P*(*x*) to have the desired distribution.

The inside operator is similar to the dist operator, except that only the ranges of the member are defined and the distribution is flat over it.

Finally, suppose that by default we want the variable to have the distribution of Eq.5, but with a constraint added during randomization to change this distribution to *P *(*x *== 0) = 0*,*95 *P *(*x *== 1) = 0*,*05. Essentially, we want the class to have two randomization modes. To achieve this, we can add a member that stores the randomization mode, which by default is the ’NORMAL’ mode, and with a constraint, we can ’force’ the ’ERROR’ mode. We will use an enumeration as the type of the member that stores the mode. Additionally, we will use a soft constraint, which allows us to avoid errors in randomization by declaring that if the soft constraint cannot be satisfied, it should simply be ignored. The following is an example:

If when randomizing the following call is used:

the mode member will always take the value ’NORMAL’. Instead if the following call is used:

The member mode will take the value ’ERROR’. The distribution of member *x *will change according to the value of mode based on the constraint dist_x_c. The use of soft constraints is very useful to include randomization modes such as error injection.

Note that constraints such as randomization of members can be turned off. If we do not want the constraint dist_xy_c to be met for a particular test, we can randomize as follows:

If, on the other hand, we want the value of the variable *y *to be randomized only once in the entire test, we can ’turn off’ its randomization in the .post_randomize method,for example:

Suppose we want to stimulate a DUT with input transactions that follow a Poisson occurrence distribution. One way to implement this is by using the fact that the time between events in a Poisson process follows an exponential distribution. By simplifying that the number of clock cycles between transactions will also follow an exponential distribution, we only need to create an integer variable that contains the number of clock cycles to wait between sending sequences.

One might be tempted to use a constraint of the following type:

Suppose that after some laboratory testing, we estimate that the mean of the Poisson process that gives rise to these transactions is different from that used to create the above constraint. We would have to manually change all the occurrence weights, and we are truncating the distribution in an ’abrupt’ manner. A more flexible way to do this is to use a class like the following:

We can see that the *value *member will already have the assigned value at the end of the .pre_randomize method, so if another class has an object of typed_exponential_value as a member, it can define dependency constraints on the value of *value*.

Let’s see what the probability distribution looks like:

One might have imagined that the same value would always be generated since the seed is equal to 1 in the new function. But it turns out that in the methods that implement distributions in SystemVerilog, the seed is of the in out type, returning the value of the seed to be used in the next randomization.

Now let’s imagine that we have two interfaces to send transactions to the DUT. In this case, we can instantiate two objects of the type d_exponential_value to control the time between transactions. Now that we have two random variables, we can graph the joint probability:

From Figure 5, we can see that the only pairs that occur are those where*obj*1.value is equal to obj2.value.This is probably notwhat we want. This result has another consequence: if we change the seed of thetest, we will have the same sequence of values for these members, which is alsonot desired. To solve this, we make the following modification:

Obtaining the following joint distribution:

The following example and solution were taken from the book Spear, C.Tumbush, G.(2012). SystemVerilog for Verification (3rd ed.). Springer. The presented solution was fixed by setting the seed. Suppose that we are testing a DUT with a register of length REG_WITH, and we want it to take values from a distribution that favors the occurrence of values at the ends of the range in a smooth way, something like the bathtub distribution. We can use the following class:

As we can see, we can create objects that have the desired probability distribution, and we can use constraints or classes that use the probability functions implemented by SystemVerilog to construct these distributions.

See you on the next Verification Article

*Maia Desamo*

*Telecommunication and FPGA Verification Engineer at Emtech S.A*

Any Comments or questions, please feel free to contact us: info@emtech.com.ar