An introduction to SystemVerilog Data Types

By John
October 2, 2020

In this post, we talk about the most commonly used data types in SystemVerilog. This includes a discussion of data representation, 2 state vs 4 state types, binary data types and numerical data types.

Although SystemVerilog is considered to be a loosely typed language, we must still declare a data type for every port or signal in our SystemVerilog design.

The type which we specify is used to define the characteristics of our data.

We can use types which interpret data purely as a logical value, for example. We can also use types which interpret our data as if it were a numeric value.

When we assign data to a signal in SystemVerilog, the data is implicitly converted to the correct type in most cases.

As a result, there is often no need necessary to explicitly perform type conversions in verilog.

As SystemVerilog is an extension of verilog, we can use all of the existing verilog data types in our code.

In addition to this, a number of new types were introduced as a part of the SystemVerilog standard.

In this post, we look at both the older verilog data types and the newer SystemVerilog types.

Representing Data in SystemVerilog

When we write SystemVerilog, we often need to represent digital data in our code. We can express this data as either a binary, hexadecimal or octal value.

Unlike in other programming languages, we also need to define the number of bits we have in our data representation.

This is because we are describing hardware circuits when we use SystemVerilog. Therefore, we can create data busses which contain as many bits as we choose.

The code snippet below shows the general syntax we use to represent digital data in SystemVerilog.

// Binary value of 8
4'b1000;
// Hex value of 8
4'h8;
// Octal value of 8
4'o10;
// Decimal value of 8
4'd8

2 State vs 4 State Data Types

In verilog, we can assign four different states to the individual bits in our data. These different states are shown in the table below.

0Binary 0 value
1Binary 1 value
zHigh impedance value
xunknown value

We require the high impedance and unknown states to more accurately represent the underlying hardware we are describing in verilog.

However, a number of 2 state data types were introduced as a part of the SystemVerilog extension.

These types are intended to be simpler, more efficient versions of the 4 state types from verilog.

When we use the 2 state data types in SystemVerilog, we can only assign a logical 1 or 0 to the individual bits. Therefore, we should only use these data types in SystemVerilog testbenches.

As they have half as many states, the SystemVerilog 2 state types use half as much memory as the 4 state verilog types.

As a result of this, the 2 state types have faster execution times than their equivalent 4 state type.

The table below shows all of the different types which we can use in SystemVerilog. It also shows which types use the old verilog 4 state encoding and which use 2 state encoding.

TypeDescriptionStates
WireVerilog type used to model connections4 state
RegVerilog type used in sequential blocks4 state
LogicSystemVerilog replacement for reg and wire4 state
bit2 state equivalent of the logic type2 state
integer32 bit signed type to model whole numbers in Verilog4 state
intSystemVerilog 2 state equivalent to integer2 state
byte8 bit signed type to model whole numbers in SystemVerilog2 state
shortint16 bit signed type to model whole numbers in SystemVerilog2 state
longint64 bit signed type to model whole numbers in SystemVerilog2 state
real64 bit IEEE 754 double precision number2 state
shortreal32 bit IEEE 754 single precision number2 state
time64 bit unsigned number which represents simulation time-

Each of these types is described in more depth in the following sections.

Basic SystemVerilog Data Types

Broadly speaking, we can split the SystemVerilog types into two distinct families.

One of these families consists of types which are used to model basic binary data in our code.

We mainly use these types to model simple logic components, connections and busses.

The second family consists of types which we use to model numerical data.

We use these types to model either whole or decimal numbers in our SystemVerilog code.

Regardless of the exact type of data we are using, we always use the same syntax to declare a variable in SystemVerilog. The code snippet below shows this general syntax.

// General syntax to declare a variable in verilog
<type_name> <size> <variable_name> = <value>;

We use the <type_name> field in the above example to declare the type of variable we have. We simply replace this field with the name of the type.

The <size> field is used to declare how many bits are in our variable. We discuss the format of the <size> field in more detail in the section on SystemVerilog vector types.

Finally, we use the <variable_name> field to give a unique name to our variable.

As an example, the verilog code below declares an integer type variable and assigns it a value of 100.

integer example = 100;

Let's look at all of the basic SystemVerilog types in more detail.

Binary Data Types

In verilog, we can split the binary data types into groups - net types and variables types.

We use these two different groups to model different elements of our digital circuits.

We use the net types to model point to point connections in our digital circuits. They are unable to store values on their own and must be driven with data.

We primarily use the variable types to model registers or flip flops in our design. These types can store data, meaning that their behaviour is similar to variables in other programming languages such as C.

Both of these families contain a number of different types which we can use in our code.

However, the most commonly used of the net types is the wire type whilst the most commonly used variable type is the reg type.

Therefore, we will only consider the reg and wire types in this pos. However, all of the types available in verilog are discussed in more detail in the post on verilog data types.

The subtle differences between the reg and wire types often leads to confusion for new verilog designers.

In fact, this can even be difficult for experienced developers to fully understand.

Therefore, the logic type was introduced in SystemVerilog. This type is intended to replace both the reg and wire types from verilog.

We can still use the verilog reg and wire types to model binary data in our SystemVerilog designs. However, it is much simpler to use the basic logic type.

In the rest of this section, we discuss these three different data types in more detail.

Verilog Wire Type

In verilog we use the wire type to describe physical connections between different components in our design. As a result of this, the wire type can't be used to store data values or drive data.

To better demonstrate when we would use the wire type in a verilog design, consider the circuit diagram shown below.

In this circuit, we would use the wire type to connect the output of the multiplexor to the input of the flip flop.

A circuit diagram showing a two input multiplexor and a d type flip flop. The output of the multiplexor is the input to the flip flop.

We normally use continuous assignment to drive data onto a wire type. To do this we must use the assign keyword, as shown in the code snippet below. We talk about continuous assignment in more detail in a later blog post.

// Declaration of a single wire
wire a;

// Driving a net type to a constant 0 using the assign keyword
assign a = 1'b0;

We can not use the wire type in procedural code such as always blocks. The always block is also discussed in more detail in a later blog post.

Verilog Reg Type

Unlike the wire type, we use the reg type to store data values in our verilog designs. When we assign a value to a reg type, it maintains this until it is assigned a new value.

The reg type is generally more intuitive to understand than the wire type as the behavior is similar to variables in other programming languages such as C.

We most commonly use the reg type to model the behaviour of flip flops in verilog. However, the reg type can also be used to model combinational logic circuits in some cases.

To better demonstrate when we would use the reg type in a verilog design, consider the circuit diagram shown below.

In this circuit, we would use the reg type to model the flip flop output as it effectively stores a single bit of data.

A circuit diagram showing a two input multiplexor and a d type flip flop. The output of the multiplexor is the input to the flip flop.

We must use the reg type within blocks of procedural code such as an always block, as shown in the code snippet below.

// Declaration of our reg types
reg q;

// Code for a basic flip flop
always @(posedge clock)
  q <= d;
end

SystemVerilog logic Type

As the difference between reg and wire types often causes confusion, a new binary data type was introduced in SystemVerilog.

The logic type was introduced as a replacement for both the reg and wire types.

As the logic type combines the characteristics of the reg and wire types, we can use it in pretty much any part of our SystemVerilog code.

This is in contrast to the reg and wire types which can only be used in procedural blocks and continuous assignment respectively.

All of this makes the logic type much more flexible. As a result, it is easier to understand and work with.

As a general rule, we should use the logic type for any binary signal in our SystemVerilog design.

The only exception to this is when we want to drive a signal from more than one source.

As it's illegal to drive a logic type from more than one source, we would have to use a wire type for this.

To demonstrate how we use the logic type, let's consider a basic example.

For this example, we will look at the simple example circuit below. This is the same circuit we previously looked at when discussing the reg type.

A circuit diagram showing a two input multiplexor and a d type flip flop. The output of the multiplexor is the input to the flip flop.

As we saw in the previous example, we could use a reg type to model the flip flop output.

In addition to this, we could also model the multiplexor output with a wire type.

However, when we write code in SystemVerilog it is much easier to use the logic type to model both of these signals.

The code snippet below shows how we would model this circuit using the logic type in SystemVerilog.

// Declaration of our signal
logic d;
logic q;

// Basic code for a mux
assign d = addr ? b : a; 

// Code for a basic flip flop
always @(posedge clock)
  q <= d;
end

SystemVerilog bit Type

In addition to the logic type, the bit type was also introduced as a part of the SystemVerilog language.

We can use the bit type in either procedural blocks or in continuous assignment.

This behavior means that the bit type is almost identical to the logic type which we previously discussed.

However, there is one important difference between these two data types.

Unlike the logic type, the bit type uses 2 states rather than 4.

As a result of this, we can't use this type to model unknown state or high impedance.

However, the bit type uses half the amount of memory that the logic type requires as it has less states. This can speed up the execution time of our simulations.

These characteristics mean that the bit type is less suitable than the logic type for SystemVerilog designs.

However, we should use the bit type rather than the logic type in our SystemVerilog testbenches due to its faster execution time.

Vector Types in SystemVerilog

All of the examples which we have looked at so far have consisted of a single bit of data.

However, we often use data busses to transfer data within a digital circuit.

In SystemVerilog, we can use vector types to create data buses. This allows us to declare a signal which has more than one bit.

The code snippet below shows the general syntax which we use to declare a vector type in SystemVerilog.

// General syntax to declare a vector type
<type> <size> <variable_name>;

When we define the size of the vector we must specify the most significant and least significant bits (MSB and LSB). Therefore, the <size> field takes the form [MSB:LSB].

For example, to declare a 4 bit little endian type vector we would use the construct [3:0].

As we talked about earlier in this post, we can represent data using binary, hex, octal or decimal formats. When we assign data to a vector we can use any of these representations.

The SystemVerilog code below shows how we would declare a 4 bit wide logic type. We also see how we can use the different data representations to assign the value of 1010b to the variable.

// Declare our logic type vector
logic [3:0] a;

// Assign binary data
a = 4'b1010;

// Assign hex data
a = 4'ha;

// Assign decimal data
a = 4'd10;

// Assign octal data
a = 4'o12;

Numeric Data Types

The types which we have considered so far in this post are all used to model digital data.

However, we can also represent data numerically in our SystemVerilog designs.

In SystemVerilog, we can group the numerical data types into two families.

The first family consists of types which are used to represent whole numbers.

The most commonly used types in this family are the integer and int type.

The second family consists of types which are used to model decimal numbers in SystemVerilog.

There are considerably less types in this family but the most commonly used of these types is the real type.

Let's take a closer look at all of the numerical types available to us in SystemVerilog.

Verilog Integer Type

The most commonly used type for numerical data in verilog is the integer type.

We normally use this type for internal signals rather than for module ports. 

By default, the integer is a 32 bit 2s complement number which we can use to represent any whole number in our SystemVerilog design. When we use an integer type, we assign numerical rather than binary values to the variable.

As we can also assign numeric values to the reg and logic types, we typically use integers for constants or loop variables in SystemVerilog.

Our synthesis tools will automatically trim any unused bits in our integer type. For example, if we declare an integer constant with a value of 255 then our synthesis tool will trim this down to 8 bits.

The code snippet below shows how we declare and assign an integer type in SystemVerilog.

// Example of an integer
integer a = 255;

We can also use the unsigned keyword to change the encoding of an integer type signal from signed to unsigned.

The code snippet below shows how we would declare an unsigned integer type in SystemVerilog.

// Example of an unsigned integer
integer unsigned a = 255;

SystemVerilog int Type

By default, the int type is a 32 bit signed number which we can use to model whole numbers in SystemVerilog.

The int type was introduced as a part of the SystemVerilog extension and it is virtually identical to the verilog integer type.

However, the key difference between the integer and int types is that the int type uses only 2 states. Therefore, we can treat the SystemVerilog int type as being exactly equivalent to the C int type.

As the int type only uses 2 states, we can't use this type to model high impedance or unknown states.

This means that the 4 state integer type is generally preferable for FPGA designs as it gives a more realistic model of the underlying hardware.

However, the int type has faster execution times that the integer type because it only requires half the amount of memory.

Therefore, we should generally use the int type instead of the integer type within our SystemVerilog testbenches.

The code snippet below shows how we would declare and use the int type in SystemVerilog.

// Example of an int type
int a = 255;

We can also use the unsigned keyword to change the encoding of an int type signal from signed to unsigned.

The code snippet below shows how we would declare an unsigned int type in SystemVerilog.

// Example of an unsigned int
int unsigned b = 128;

SystemVerilog byte Type

The SystemVerilog byte type is an 8 bit data type which we can use to model whole numbers.

By default, the byte type is is encoded as a signed 2s complement number. As a result of this, it can only accept values from -127 to 127.

However, we can also declare the the byte type to be encoded as an unsigned number. When we do this, we can assign values from 0 to 255 to the byte type.

Like the int type, the byte type only uses 2 states. As a result of this, we should avoid using this type in SystemVerilog based designs.

However, we should use the byte type in our testbenches as it uses less memory and can reduce execution times.

The code snippet below shows how we declare and use the byte type in SystemVerilog.

// byte type example 
byte a = 64;

We can also use the unsigned keyword to change the encoding of a byte type signal from signed to unsigned.

The code snippet below shows how we declare an unsigned byte type in SystemVerilog.

// unsigned byte type example
byte unsigned a = 255;

SystemVerilog shortint type

The SystemVerilog shortint type is a 16 bit data type which we can use to model whole numbers.

By default, the shortint type is encoded as a signed 2s complement number. As a result of this, it can only accept values from -32767 to 32767.

However, we can also declare the the shortint type to be encoded as an unsigned number. When we do this, we can assign values from 0 to 65535 to the shortint type.

Like the int type, the shortint type only has 2 states. As a result of this, we should avoid using this type in SystemVerilog based designs.

However, we should use the shortint type in our testbenches as it uses less memory and can reduce execution times.

The code snippet below shows how we declare and use the shortint type in SystemVerilog.

// shortint example
shortint a = 127;

We can also use the unsigned keyword to change the encoding of a shortint type signal from signed to unsigned.

The code snippet below shows how we declare an unsigned shortint type in SystemVerilog.

// unsigned shortint example
shortint unsigned a = 127;

SystemVerilog longint type

The final 2 state integer type which was introduced as part of the SystemVerilog language is the longint type.

The longint type uses 64 bits and is encoded as a signed 2s complement number.

We can use the longint type to store numbers which are too large to store in either the int or integer types.

As the longint type uses 2 states, it is generally only used within SystemVerilog testbenches.

The code snippet below shows how we declare and use the longint type in SystemVerilog.

// longint type example
longint a = 127;

We can also use the unsigned keyword to change the encoding of a longint type signal from signed to unsigned.

The code snippet below shows how we declare an unsigned longint type in SystemVerilog.

// longint type example
longint unsigned a = 127;

Verilog real Type

In verilog, the second most commonly used numerical type is the real type.

We use this type to store non-integer numbers, i.e. numbers which also have a decimal part.

The real type is implemented as a 64 bit IEEE 754 floating point number in SystemVerilog.

As a result of this, it can't be directly synthesized and we typically only use the real type in our testbench code.

We can use either decimal or engineering type notation to assign values to the real type.

The code snippet below shows how we declare a real type and assign data to it.

// Declaration of a real type
real a;

// Assign of data using decimal notation
a = 2.5;

// Assingment of data using engineering notation
a = 1e-3;

The real type is equivalent to the double type in C.

SystemVerilog shortreal Type

In addition to the real type, the SystemVerilog extension introduced the shortreal type.

As with the real type, we use this type to store non-integer numbers.

The difference between the shortreal and the real type is that the shortreal only uses 32 bits.

The shortreal is implemented as an IEEE 754 single precision floating point number in SystemVerilog.

As a result of this, we can't use the shortreal type in synthesizable code.

We can use either decimal or engineering type notation to assign values to the shortreal type.

The code snippet below shows how we declare a shortreal type and assign data to it.

// Declaration of a shortreal type
shortreal a;

// Assign of data using decimal notation
a = 2.5;

// Assingment of data using engineering notation
a = 1e-3;

We can think of the shortreal type as being equivalent to the float type in C.

Exercises

What is the difference between types which use 4 states and types which use 2 states? When we would use a 2 state type instead of a 4 state type.

The 2 state type can't be used to model high impedance or unknown signals. The 2 state types are more suitable for testbenches as they have faster execution times.

Which types of data can we represent in our SystemVerilog design?

Binary, hexidecimal, octal and whole decimal numbers. We can also represent decimal numbers but this is not synthesizable.

What is the difference between the verilog reg and wire types?

The wire type is used to model connections in our design and can’t store values. The reg type can store data values and behaves like variables in other programming languages.

Write some SystemVerilog code to declare an 8 bit logic type and assign it the value of AAh

logic [7:0] example = 8'haa;

Write the code to declare an unsigned int type and assign the value of 100 to it.

int unsigned example = 100;
Enjoyed this post? Why not share it with others.

Leave a Reply

Your email address will not be published. Required fields are marked *

Subscribe

Join our mailing list and be the first to hear about our latest FPGA tutorials
Sign Up to our Mailing List
© 2024 FPGA Tutorial

Sign up free for exclusive content.

Don't Miss Out

We are about to launch exclusive video content. Sign up to hear about it first.

Close
The fpgatutorial.com site logo

Don't Miss Out

We are about to launch exclusive video content. Sign up to hear about it first.

Close