Randomization with Systemverilog - Second Part

by Maia Desamo May 2024

From the previous section, elements of the SystemVerilog language that allow implementing desired random behaviors on the testbench sequences or reducing the burden of implementing certain types of constraints were left out. In the following article, we will address the use of the unique constraint and options for randomizing sequences.

1. Constraint - unique

A typical problem that can be difficult to solve is randomizing a set of variables or an array with each element distinct from the rest. For this, SystemVerilog provides the unique constraint. This constraint allows adding the condition that the elements passed as a list take different values, for example:

For an execution of the randomize method, the following values were obtained:

The alternative to using the unique constraint for an array is to implement a double foreach loop with the constraint that all elements be distinct from each other, but this has a significant computational impact. Another alternative is to randomize a variable of type randc multiple times; however, implementing these solutions will be more difficult in all cases. These and other alternatives are discussed in the book Spear, C. Tumbush, G. (2012). SystemVerilog for Verification (3rd ed.). Springer. For more details on this type of constraint, you can refer to Chapter "18.5.5 Uniqueness Constraints" of the IEEE Standard for SystemVerilog—Unified Hardware Design, Specification, and Verification Language 2018.

2. Randcase

SystemVerilog includes a mechanism to randomly choose between different cases. For this purpose, it provides the randcase statement. Below is an example of its usage:

In this case, constants 1, 2, and 3 are the weights with which the different threads will be randomized. It is not mandatory for the weight to be a constant; it can be, for example, a variable of type int:

In this example, the weight of the first thread is the integer over which the for loop iterates. We can also express weights with operations, as in the second case. The randcase can be used within functions or tasks to randomize behaviors of the testbench. For more details on this type of structure, you can refer to Chapter "18.16 Random weighted case—randcase" of the IEEE Standard for SystemVerilog—Unified Hardware Design, Specification, and Verification Language 2018.

Randsequence

"Parser generators, such as yacc (Yet Another Compiler-Compiler), use a BNF (Backus-Naur form) or similar notation to describe the grammar of the language to be parsed. The grammar is thus used to generate a program that is able to check whether a stream of tokens represents a syntactically correct utterance in that language. SystemVerilog’s sequence generator reverses this process. It uses the grammar to randomly create a correct utterance (i.e., a stream of tokens) of the language described by the grammar. The random sequence generator is useful for randomly generating structured sequences of stimulus such as instructions or network traffic patterns." Taken from the chapter "18.17 Random sequence generation—randsequence" of the IEEE Standard for SystemVerilog—Unified Hardware Design, Specification, and Verification Language 2018. Basically, this block allows us to define code sections (terminals) that can be combined following different "rules," thus producing different non-terminals. One of these non-terminals will be chosen as the main one, and it is the one the simulator will use to generate the final sequence. Below is a simple example of randsequence:

In this case, the terminals are:

• done

• add

• dec

• pop

• push

and the non-terminals are:

• main: concatenation of first, second, and done.

• first: can be resolved by randomly executing the add or dec command with equal probability.

• second: can be resolved by pop or push with a 30% chance of pop and a 70% chance of push.

The non-terminal "main" is the principal one and will generate the sequence that will be executed in the simulation. A non-terminal can be generated using if-else, case, and repeat structures as shown in the following example:

In this second example, the resolution of the sequence is as follows:

• main: concatenation of first, second, and done_r

• first: will be resolved by add when i%4 is 1 or dec otherwise

• second: will be pop for i equal to 0, add for i equal to 1 or 2, and push for all other cases.

• done_r: will be a random number of repetitions of the terminal done (between 2 and 6 repetitions)

In the examples above, main is always solved by concatenating the three non-terminals that define it in a particular order. However, the language allows describing a random concatenation through the use of the reserved words rand join. This is shown in the following example:

Example of execution of this last structure:

   i = 0 dec pop done done done done

   i = 1 done done done done done done add add

    i = 2 done done add add

    i = 3 push add done done done

    i = 4 done done done done done push dec

Another interesting feature is that both terminal and non-terminal sections can receive values when called. This is illustrated in the following example for the terminal pop.

It’s worth mentioning that from the terminal, you can invoke a function/task as needed to simplify code readability.

4. Other options for sequence randomization

We have already seen that with randcase and ransequence you can randomize the behavior of a particular sequence, but generally you may want to randomize the behavior of a test by randomly selecting from different sequences. To do this you can create a queue of sequences, use the .shuffle() method to mix them, and then take the first one in the list. Below is an example of this:

From the previous example, the following result is obtained in the console:

# I t e r a t i o n 0

# seq_2

# I t e r a t i o n 1

# seq_1

# I t e r a t i o n 2

# seq_1

# I t e r a t i o n 3

# seq_1

# I t e r a t i o n 4

# seq_2

Note that it is mandatory for all sequences to be included in the queue to be children of the same base sequence. Furthermore, the queue is of the base sequence type. Here, the polymorphism property of SystemVerilog is being

utilized, which allows invoking methods of child classes with pointers of the parent class.

Another way to randomly choose from a set of sequences is to use the "uvm_sequence_library" class provided by UVM. In this case the parameterization of the sequences and the sequence library must be compatible. For this example, we reuse the definitions of the classes "seq_1" y "seq_2"

The output obtained in the console is the following:

# UVM_INFO @ 0. 0 ps :

uvm_test_top . init_sequencer_o@@seq_lib_o [ SEQLIB/START]

S t a r ti n g se quence l i b r a r y s e q_li b i n unknown phase :

4 i t e r a t i o n s i n mode UVM_SEQ_LIB_RAND

# seq_1

# seq_2

# seq_2

# seq_1

Note that it is mandatory to create a sequencer in order to implement this solution, as this class does not support virtual sequences. With the members "min_random_count" and "max_random_count", you can modify the number of calls to the sequences that will be executed (this being a random number between these two values). As the calling of these sequences is randomized, it can be controlled with the "selection_mode" member. In the previous example, the inclusion of sequences in the library was done in the constructor description of the class, but it can also be done on an instance of the class using the ".add_sequence()/.add_sequences()" method. For this and more details, we recommend reading the Universal Verification Methodology (UVM) 1.2 User’s Guide, Chapter 6.4 The Sequence Library.

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