Quick Reference: SystemVerilog Data Types

Friends, In this post I thought to present a quick reference for SystemVerilog (SV) Data Types which are the key elements in the foundation of any UVM based Verification Environment.

Many of you would be already familiar with all these contents hence it may act as a refresher for you and my friends who are relatively new to SV can found it a point of beginning and take it ahead with the help of some excellent books and material available online & offline.

Before proceeding, Let me mention that – we’ll focus on the data types or structures primarily most useful to the Functional Verification.

1) 2-value and 4-value Data Types:

In SystemVerilog, there are following value data types:

  1. 2-value data type
  2. 4-value data type

As the name indicates, 2-value data type can have values as 0 or 1, yet 4-value data types can have values out of 0, 1, X, Z. 2-value data types are introduced in SystemVerilog and primary purpose is to speed up the simulation. But for the design use, its better to use 4-value data types to serves the purpose of high impedance state and unknown states. Different types of 2-valued and 4-valued data types are shown in the Table 1 & Table 2 below.

Data Types_1

Table 1: 2-value Data Types

Data Types_2

Table 2: 4-value Data Types

Here I would like to talk little more about a data type that is introduced in the SystemVerilog i.e. “logic”. SystemVerilog improves the classic reg data type so that it can be driven by continuous assignments, gates and modules, in addition to being a variable. A logic type can be used anywhere a net is used expect that logic variable does not support being driven by the multiple sources e.g. bi-directional bus. In such cases, the variable to be declared a net type such as wire. Details of different valued data types are shown in Table 3 with their mode, sizes & default sign.

Data Types_3

Table 3: Data Types Detailed Information

The default sign values of a data type variable can be overwritten by explicitly assigning the expected sign value, as shown in the example below:


bit [15:0] bus_A; // Default unsigned

bit signed [15:0] bus_A; // declared as signed


2) String Data Type:

String data type in SystemVerilog is a dynamic collection of characters. Each character of the String variable is of type “byte”.

SystemVerilog provide serveral operators to work with String variables. Table 4 elaborates about these operators:

String_Op

Table 4: String Operators

In addition, SV provides number of methods for String data type variables. Primarily these are – “len”, “putc”, “getc”, “toupper”, “tolower”, “compare”, “icompare”, “substr”. There are few more methods to support the conversion between asci to bin, hex, oct, int and vice versa. Please refer the SV LRM to get the detailed information on these methods.

Lets move to the SystemVerilog supportted operators in a little more detail.

3) SystemVerilog Operators:

SV supports many standard operators most of which are carried forward from Verilog 1995/2001.

  1. Arithmetic Operators
  2. Logical Operators
  3. Bit-wise Operators
  4. Unary Reduction Operators
  5. Equality Operators
  6. Identity Operators
  7. Wild Equality Operators

Please refer the SystemVerilog LRM (Language Reference Manual) or text books to get the detailed information on these operators.

4) Creating new types with typedef:

Using SystemVerilog, we can define new types using typedef statement. The advantage of having user defined types is to create those data types & let them be the part of ‘package’ so that these data types can be repeatedly use across the projects & designs without defining them again & again. Lets see few examples to make things clear:


Example 1:

parameter OPSIZE = 16;

typedef logic [OPSIZE – 1:0] opreg_t;

opreg_t op_A, op_B;

Example 2:

typedef bit [31:0] my_intA; // 32-bit unsigned interger

typedef int unsigned my_intA; // 32-bit unsigned interger, unsigned is used since int is by default signed.

Example 3:

typedef enum {RED, BLUE, GREEN} color_t; // enum data type (we’ll learn about it next)

color_t my_color;


5) Enumerated Data Types:

An Enumerated data type allows us to define strongly typed variables with a group of unique constants which are related to each other. Enumeration types makes your code easier to write and maintain. Enumerated type automatically gives an unique value to each name in the list. An Enumerated type is stored as int unless we specifies otherwise. It’s a good coding practice to have _e with the variable name to clearly indicate Enumerated type.

Enumerated type support several methods to stepping through the names in the list & these are – first(), last(), next(), next(N), prev(), prev(N). Please find details of each of these methods in SV LRM.

Lets see few examples for it:


Example 1:

typedef {INIT, DECODE, IDLE} fsm_e;

fsm_e nstate, pstate;

initial 

    begin

        case(pstate);

           IDLE:  nstate = INIT;

           INIT:   nstate = DECODE;

           default: nstate = IDLE;

        endcase

        $display(“Next state is %s”, nstate.name());

    end

Example 2:

typedef enum {GREEN, ORANGE, BLUE} color_e;

color_e color;

color = color.first;

do

     begin

         $display(“Color is = %s”, color.name());

         color = color.next();

         while (color != color.first)

      end


6) Constant Data Types:

In SV, as per LRM definition – Constants are the named data object that never changes. SystemVerilog provide following 2 types of constants:

  • Elaboration time constants
    • Parameter constants (parameter, localparam, specifyparam)
  • Run time constants
    • const

In Verilog, text macros (`define) were used to define the constants which were global in nature so sometimes its a good feature to use the macro anywhere but at times it turns out to be a challenge in dealing with the local constant.

Verilog ‘parameter’ was loosely typed so its scope was limited to a module. In Verilog 2001, parameter are made typed parameters but scope was still the limitation. In SystemVerilog, parameters can be part of ‘package’ and can be used across multiple modules.

In SystemVerilog, Run-time constant i.e. const can be initialized with-in the declaration but can not be over-written with-in the procedural code.

Few examples below:


parameter logic brate = 1;

localparam byte param1 = “abc”;

specparam delay = 20;

initial begin

      const logic CtrlBit = 1

end


7) Fixed Size Array:

In SystemVerilog, there is no need to have low and high array limits (as it is required in Verilog).

int my_array [8]; // my_array[0]……my_array[7]

We can create multi-dimensional array by declaring dimensions after the variable name.

int my_multi_dimension_array [8][4]; // A multi-dimensional array of dimension [0:7][0:3]

There is a concept of packed and unpacked array in SystemVerilog, lets talk about it and go through some of these examples too. Its very critical to understand that most of the SystemVerilog simulators stores each element of the array on a 32-bit boundary, so a byte, shortint & int are accommodated in a 32-bit word. Yet longint consume 2 WORDs.

Unpacked Array:

bit [7:0]  mem [4]; // Unpacked Array

Unpacked_Array

Packed Array:

bit [3:0] [7:0] mem; // Packed Array

Packed_Array

From the packed and unpacked diagrams, its clear that its all about how we’re going to use the available memory, so its important to use the right set of syntax to fulfill the requirements.

8) Dynamic Array:

The challenge with Fixed Size Arrays (Verilog supported) is that the memory is allocated at the compile time itself and that much memory is blocked even if that the allocated memory is never fully utilized. It turn out to be the wastage of available memory (one of the critical computing resource). For example, if we’re planning for random size transactions simulation, in that case we need to have max fixed size array to take care of maximum size of transaction even though smaller size transactions do not require that much memory to be allocated.

SystemVerilog helps to resolve this challenge by introducing an array called “Dynamic Array“. Dynamic array allocates memory at the run time instead of the compile time. In case of our above example, allocated memory size will be dependent on the size of transaction at the run-time & memory may got released after the simulation is over. So we can utilized the memory in most optimal way.


/// Dyanmic array declaration

int dyna1[], dyna2[];  

     initial begin

/// Allocates memory for 10 elements

         dyna1 = new[10];  

/// Initialize each element of the array

        foreach (dyna1[i])

                     dyna1[i] = i;

/// Copy the array

        dyna2 = dyna1;

 /// Allocates value i.e. 3 to the first element

        dyna2[0] = 3;

/// Display the values i.e. 0 & 3 

        $display(dyna1[0], dyna2[0]);

 /// Adding 30 new element & copy

       dyna1 = new[30] (dyna1);  

 /// Allocating 100 element but previous elements are lost

       dyna1 = new[100];  

 /// Deleting all the elements.

       dyna1.delete();

end


9) Associative Array:

Associative array are used when the size of the array is not known or the data is sparse. In principles, Associative array implements a lookup table with elements of its declared type. The data type to be used as index serves as the lookup key.

Declaring an Associative array:

data_type array_name [index_type];

Following various types of index are supported:

  1. Wildcard Index
    1. int my_assocA [*];
  2. String Index
    1. int my_assocA [string];
  3. Class Index
    1. int my_assocA [Class_type];
  4. Integral Index
    1. int my_assocA [integer];
  5. User Defined Index
    1. typedef int unsigned int_B; int my_assocA [int_B];

Following Associative array methods are supported by SystemVerilog:

  1. num() & size() – Both returns the number of entries in the associative array.
  2. delete() & delete(index) – Deletes all elements & delete indexed element.
  3. exists(index) – Checks for the existence of the indexed element.
  4. first(index) – Assigns the first element to the index being mentioned.
  5. last(index) – Assigns the last element to the index being mentioned.
  6. next(index) – Finds the smallest index whose value is greater than the given index argument.
  7. prev(index) – Finds the largest index whose value is smaller than the given index argument.

9) Queues:

Queue is introduced in SystemVerilog. With Queue we can insert or delete any element without the performance hit as with dynamic array that has to allocate a new array and copy the entire content. Each element in the Queue is identified with a number, where 0 being the first element and $ being the last element. A Queue is analogous to one dimensional array which grows and shrink automatically.

Declaring a Queue:

data_type queue_name [$];

byte my_qA [$];

string my_qA [“name”];

integer my_qA [$] = {1,2,3};

Queue’s methods:

  1. size() – Returns the no. of elements in the queue.
  2. insert(index, element) – Insert an element at the index position.
  3. delete() or delete(index) – Delete all elements or delete indexed item.
  4. pop_front() – Gives out the first element of the queue.
  5. pop_back() – Gives out the last element of the queue.
  6. push_front(item) – Insert the element at the front of the queue.
  7. push_back(item) – Insert the element at the back of the queue.

10. Package:

Package is a SystemVerilog concept where we can define our common parameters, typedef, strings & other methods, in fact everything that is part of language which can be re-used in our project. Once we import a Package, all the properties and methods become part of Testbench. Package provides a declaration space, which can be shared by other building blocks. Package declarations can be imported into other building blocks, including other packages.

If some properties with the same name is present in the module/testbench, in that case the one inside module/testbench overwrite the one inside the Package.

Overall, the advantage of using Package is that we can put together the reusable items and call all of these items to be part of a new project with just one SystemVerilog import statement. Lets see the example to clarify it:


package my_pkg;

    parameter int data_size = 32;

    string message =  “hello”;

    parameter time timeout = 1000ns;

    typedef logic [data_size – 1:0] data_size_t;

endpackage: my_pkg

module test;

    import my_pkg::*;

    data_size_t bus_size;

    string message = “yolla”;

endmodule: test


As shown in the example above, imported string ‘message’ is overwritten by the local ‘message’ variable.

11. Casting:

Data types in SystemVerilog may need to convert between them, one simple example is between signed and unsigned. Different application may have other different conversion requirement. We will discuss primarily 2 type of casting here which are supported by SystemVerilog:

Static Casting:

Static casting converts between two data types without checking of values. Conversion between integer and real is such example.

int my_int;

my_int = int'(2.4 * 5.0);

Dynamic Casting:

Dynamic casting i.e. $cast, allows you to check for out-of-bound values. $cast can be used to assign values to the variables that otherwise would not be possible due to different data types. The example below illustrate this:


program test;

typedef enum {PURPLE, BLUE, GREEN, RED} color_e;

color_e color, c1;
int c;

initial  begin
    color = GREEN;
    c = color;

    c++;
    if (!$cast(color, c))
    $display(“Casting failed for c=%0d”, c);
    $display (” Color is %0d / %s”, color, color.name());

    c++;
    c1 = color_e'(c);
    $display(“Color is %0d / %s”, c1, c1.name());
end

endprogram: test


Now lets see in summary the evolution & different parts of the puzzule i.e SystemVerilog using a diagram below:

SystemVerilogView

With all this information being shared, I would like to conclude this blog here and hope it will work for you as outlined by me in the beginning of this post.

I wish all the success for you, see you again soon, bye!


amazon