[Edit of Image1]
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!
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:
Let's start off with the new data types.
The state value that any variable can hold is one of the following:
0 : Logic 01 : Logic 1x or X : Unknown Value or Don't Carez or Z : High Impedance ValueMost of the new types only store 2 of those states (0 and 1) and are thus more memory-efficient and faster to simulate.
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 bitsshortint : which stores 16 bitsint : which stores 32 bitslongint : which stores 64 bitsshortreal : which is the equivalent to C's floatLet'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.
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.
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;
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.
SystemVerilog offers several types of arrays, which allow for a lot of flexibility when building data structures:
Static arrays can be split into two categories:
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]
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] = ...;
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().
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 indexdelete(index) : Deletes an item from the specified indexpop_front() : Removes first element and returns itpop_back() : Removes last element and returns itpush_front(item) : Inserts an item at the front of the queuepush_back(item) : Inserts an item at the end of the queueStructures 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.
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!
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.
In addition to the simple = operator, SystemVerilog adds the following assignment operators:
which are basically a shortened representation of left side OP right side.
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.
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.
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.
Block diagrams and other visualizations were made using draw.io
And this is actually it for today's post!
Next time we will continue covering more on SystemVerilog...
See Ya!
Keep on drifting!