Logic Design - From Verilog To SystemVerilog

[Edit of Image1]

Introduction

Hey it's a me again @drifter1!

Today we continue with the Logic Design series on Verilog with the purpose of extending our knowledge into SystemVerilog. The differences will be covered one-by-one starting off with the additional Data Types, Operators and Expressions that it provides.

So, without further ado, let's dive straight into it!


Getting into SystemVerilog

The verification and testing features that Verilog (and VHDL as well) provide us with, are far from sufficient. This exact limitation made hardware engineers use different languages for the purpose of design and verification. A Hardware Description Language (HDL) like Verilog or VHDL for the purpose of hardware design, and a Hardware Verification Language (HVL) like OpenVera or e for verification.

And that's exactly where SystemVerilog fits in. It can be thought of as an extension of Verilog that allows for more complicated testing procedures. This allows engineers to use a single language for the purpose of design and verification.

SystemVerilog basically adds Object-Oriented Programming (OOP) features to Verilog. Knowing C and C++ is a big plus when learning it, as many of the new features are quite similar:

  • Lots of C data types are supported by SystemVerilog like int, typedef, struct, union or enum, as well as dynamic data types like structs, classes, queues and arrays.
  • Lots of new operators and built-in methods are added.
  • The control flow is enhanced with the likes of foreach, return, break and continue.
  • Processes and Threads.
  • Events, Semaphores and Mailboxes are added for the purpose of process and thread synchronization and communication.
  • Let's also not forget to mention Assertions and Functional Coverage.

Data Types

Let's start off with the new data types.

State Values

The state value that any variable can hold is one of the following:

  • 0 : Logic 0
  • 1 : Logic 1
  • x or X : Unknown Value or Don't Care
  • z or Z : High Impedance Value

Most of the new types only store 2 of those states (0 and 1) and are thus more memory-efficient and faster to simulate.

Basic Data Types

In addition to Verilog's reg, wire, integer, real (basically C's double), time and realtime data types, SystemVerilog adds the following C data types:

  • logic : which can take any of the 4 state values: 0, 1, x and z.
  • bit : which can only be 0 or 1 (2-state).
  • byte : which stores 8 bits
  • shortint : which stores 16 bits
  • int : which stores 32 bits
  • longint : which stores 64 bits
  • shortreal : which is the equivalent to C's float

Let's note that byte, shortint, int and longint types store signed values. Making them hold unsigned values is as simple as adding the unsigned keyword. For example: shortint unsigned.

Strings

SystemVerilog also adds the data type String, defined using the keyword string, which stores an ordered collection of characters. A string literal is as simple as enclosing the characters within ", like "Hello, world!". Strings are objects and lots of operators and built-in methods are therefore included in order to work with them. For example, the method len() can be called on a string variable str to return the length as follows str.len() using the quite common dot notation.

Enumerations

Enumerated types can also be defined in SystemVerilog using the keyword enum followed by an optional data type, the various names enclosed in { } and the identifier of the enum. By default, the values start from 0 and are incremented by 1 (0, 1, 2, ...). It's also possible to assign specific values.

For example:

enum {RED, GREEN, BLUE} colors_1;
enum {YELLOW = 2, ORANGE} colors_2;

In the second case, ORANGE will have a value of 3, as the value will be taken automatically by incrementing the value from the previous defined name.

Defining a variable that uses the first enumeration looks like this:

enum colors_1 color = GREEN;

Custom Data Types

Using the typedef keyword, the data type definition that follows is specified as a new custom data type. This is commonly used for enums, structs and so fourth in order to shorten the code that's required for defining a variable of that type.

For example:

typedef enum {FALSE, TRUE} e_false_true;

e_false_true answer = FALSE;

Without the custom data type, the complete enum e_false_true would have to be used instead.


Arrays

SystemVerilog offers several types of arrays, which allow for a lot of flexibility when building data structures:

  • Static Arrays : size is known before compilation.
  • Dynamic arrays : size is not known before compilation. They are expanded as needed during runtime.
  • Associative arrays : sparse contsent of unknown size is stored with a certain key (also known as Hash Maps or Dictionaries).
  • Queues : one-dimensional array that follows a First-In First-Out (FIFO) scheme.

Static Arrays

Static arrays can be split into two categories:

  • Packed Arrays : Dimensions are defined before the variable name
  • Unpacked Arrays : Dimensions are defined after the variable name

A one-dimensional packed array is basically a vector. Multi-dimensional packed arrays define a contiguous bit space where the bits are segmented further into smaller groups. An unpacked array can be a static (or fixed-size), as well as the dynamic, associative and queue types that where mentioned earlier. It's also possible to combine the two types in order to specify more complex packed + unpacked arrays.

For example, an 8-bit variable can be defined using the byte data type or as a packed array of bit as follows:

bit [7 : 0] data;

A 2D packed array which occupies a total of 32-bits split into four 8-bit segments looks like this:

bit [1 : 0][7 : 0] data2;

where accessing each individual 8-bit segment is as simple as:

data2[0]
data2[1]
data2[2]
data2[3]

and accessing an individual bit of index i of let's say segment with index 1, is simply:

data2[1][i];

A 2D unpacked array is defined as:

byte data [M][N]; // M rows, N cols

and accessing, let's say, the element of row 2, column 1 is done using:

data[2][1]

Instead of byte, we could also use an 8-bit packed array of bit together with the unpacked array defintion, in order to end up with the combination of the two:

bit [7 : 0] data [M][N]; // M rows, N cols of 8-bit data

The i-th bit of the element at row 2, column 1 is taken using:

data[2][1][i]

Dynamic Arrays

An dynamic array is a kind of unpacked array, where its size can be set or changed at runtime. The dynamic dimension is defined using empty square brackets [ ]. The new() function is called in order to allocate the initial size or extend the size as required. Another useful function is size(), which returns the size of the array.

Adding a new item to the dynamic array is thus done by first allocating a new slot using the new() function followed by the actual insertion, as shown in the example below.

int array[];        // dynamic array
array = new[10];    // allocate 10 slots

/* add 10 items */

array = new(array.size() + 1)(array);
array[10] = ...;

Associative Arrays

An associative array is like a look-up table of elements of its declared type. Therefore, the type used as the key defines a kind of ordering of the individual elements included. Specifying the key is as simple as including the data type of the key within square brackets.

For example:

int arr1 [int];
int arr2 [string];

Entries can be added using key-value pairs within {}, as shown in the example below.

arr2 = `{ "Aqua" : 8,
          "Mai" : 10 };

There are also lots and lots of methods that can be used on them such as size(), delete() and exists().

Queues

A Queue is an 1D unpacked array that grows and shrinks as needed. Specifying a queue is as simple as using $ as the size specifier, as shown below.

string names [$];       // queue of string elements
bit [3 : 0] data [$];   // queue of 4-bit elements

In addition to the various array methods, the following methods can also be used with queues:

  • insert(index, item) : Inserts a given item at the specified index
  • delete(index) : Deletes an item from the specified index
  • pop_front() : Removes first element and returns it
  • pop_back() : Removes last element and returns it
  • push_front(item) : Inserts an item at the front of the queue
  • push_back(item) : Inserts an item at the end of the queue

Structures

Structures can contain elements of different data types, and group those elements together. This is the main difference they have with arrays, which are clearly collections of the same data type.

The Syntax is as follows:

struct {
    // list of variables
} struct_name;

By default, such a structure is unpacked.

It's also common to use typedef when defining such structures in order to skip the struct keyword completely.

Initializing the structure is as simple as giving a list of values to the various fields within curly brackets {}, whilst accessing a given element is done using dot notation.

For example:

typedef struct {
    string name;
    int followers;
    shortreal reputation;
} st_user;

st_user user1 = `{"Drifter", 1024, 69.61};

$display("The name of the user is", user1.name);
$display("The user's reputation is", user1.reputation);

A packed structure is defined using struct packed. The members of such a structure are packed together without memory gaps and with decreasing significance. This allows for implementation-independent packing, which basically matches C compilers.


Classes

A Class is defined within the class and endclass keywords, and is basically a collection of data (struct) together with methods that act upon that data.

The Syntax is as follows:

class Class_name;
    // data
    // methods
endclass : Class_name;

A class instance, an object, is created calling the new() function. Assigning values to the various fields or calling a class method is done using dot notation. More on them, later on in the series!


Operators and Expressions

SystemVerilog's operators are a combination of both Verilog and C/C++ operators. The type and size of the operands is always of fixed type and size.

Assignment Operators

In addition to the simple = operator, SystemVerilog adds the following assignment operators:

  • +=
  • -=
  • *=
  • /=
  • %=
  • &=
  • |=
  • ^=
  • <<=
  • >>=
  • <<<=
  • >>>=

which are basically a shortened representation of left side OP right side.

Mixing 2- and 4-state

When mixing up 2-state and 2-state data type in a given expression, the x and z operand values are mostly converted to 0.

Wild Equality and Inequality

The wild equality and inequality operators are defined as =?= and !?= respectively, and treat any X and Z values in the right hand expresion as a wildcard. For example, data =?= 8'b1xxx_1xx1 would be true if the respective bits (0s and 1s) defined in the wild card are 1, like in 1000_1001 or even 1111_1111.

Structure Expression

A structure expression is an expression where the members are declared in order, within curly brackets and separated by commas (basically what was used previously for the purpose of initializing structures). It's also possible to use the actual name or identifier of each member in order to specify the values in any order (what was used when adding entries to associative arrays).

For example a 2D Point struct can be initialized as a variable using:

typedef struct {
    int x;
    int y;
} Point;

Point A;

A = `{1, 2};
    or
A = `{x : 1, y : 2};

Some more advanced operators and / or expressions will be covered later on in the series.


RESOURCES:

References

  1. https://www.chipverify.com/systemverilog/systemverilog-tutorial
  2. https://www.asic-world.com/systemverilog/tutorial.html

Images

  1. https://www.flickr.com/photos/creative_stock/5227842611

Block diagrams and other visualizations were made using draw.io


Previous articles of the series

Verilog


Final words | Next up

And this is actually it for today's post!

Next time we will continue covering more on SystemVerilog...

See Ya!

Keep on drifting!

H2
H3
H4
3 columns
2 columns
1 column
Join the conversation now
Logo
Center