0% found this document useful (0 votes)
33 views

Verilog Tutorial

The document provides an introduction and tutorial on using SystemVerilog for circuit design. It covers basic concepts like modules, gates, hierarchy, boolean equations, multi-bit signals, delays, constants, parameterized and RTL design. It also discusses test benches, printing values, and some advanced features.
Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
33 views

Verilog Tutorial

The document provides an introduction and tutorial on using SystemVerilog for circuit design. It covers basic concepts like modules, gates, hierarchy, boolean equations, multi-bit signals, delays, constants, parameterized and RTL design. It also discusses test benches, printing values, and some advanced features.
Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 16

369 SystemVerilog Tutorial

Scott Hauck, Justin Hsia, Max Arnold, Matthew Cinnamon (last updated Oct. 2020)

Introduction
The following tutorial is intended to get you going quickly in circuit design in SystemVerilog. It is not a
comprehensive guide but should contain everything you need to design circuits in this class. For a more
thorough reference, Prof. Hauck recommends Vahid and Lysecky’s Verilog for Digital Design.

Table of Contents
Modules ..................................................................................................................................................................... 2
Basic Gates................................................................................................................................................................. 3
Hierarchy ................................................................................................................................................................... 3
Boolean Equations and “Assign” ............................................................................................................................... 5
Multi-bit Signals ......................................................................................................................................................... 5
Delays ........................................................................................................................................................................ 7
Defining Named Constants ........................................................................................................................................ 8
Parameterized Design ............................................................................................................................................... 8
Enumerations ............................................................................................................................................................ 8
Register Transfer Level (RTL) Code – Behavioral Verilog .......................................................................................... 9
Test Benches ............................................................................................................................................................ 12
Printing Values to the Console ................................................................................................................................ 14
Advanced Features – Multi-Dimensional Buses ...................................................................................................... 15
Advanced Features – Assert Statements ................................................................................................................. 16
Advanced Features – Generate Statements............................................................................................................ 16

1
Modules
The basic building block of Verilog is a module. This is similar to a function or procedure in C/C++/Java in that it
takes input values, performs a computation, and generates outputs. However, modules compile into collections
of logic gates and each time you “call” a module you are creating separate instances of hardware.

Simple Module Example


Shown below is an example of a SystemVerilog module (left) and its corresponding hardware instantiation
(right):

Line-by-Line Analysis
Lines 1-2 are single-line comments, designated by the ‘ ’ (green syntax highlighting in Quartus) and ignored
during compilation. Comments can be placed at the end of lines of code or on separate lines by themselves.
Lines 3-5 define the module name and port list, which is the list of inputs and outputs signals. Line 3 gives the
port names while lines 4-5 define the port types and directions. Here, all 4 port signals are of type ,
and are outputs, and and are inputs. The name ( ) is user-defined but must start with
a letter and can only consist of letters, numbers, and underscores. Avoid using keywords ( syntax
highlighting in Quartus) as names.
Line 7-8 each instantiate a gate following the standard module instantiation syntax of:

Line 7 creates an AND gate called and Line 8 creates an OR gate called . The port lists
are explained below in the section
Basic Gates.
The keyword on line 9 closes the module definition started with the keyword on line 3.

ANSI-style Module Headers


SystemVerilog allows for “ANSI-style” module headers, which allows you to define the port types and directions
within the port list. This can save a lot of space when working with large module headers because the port

2
names are not repeated. The following example is equivalent to the previous module:

Basic Gates
Verilog comes with a number of predefined modules for basic gates that follow the standard module
instantiation syntax of:

The port lists for these gates are defined such that the first connection is always the output. The following
examples show a one-input gate and a multi-input gate:

The other 1-input gate is and the other multi-input gates are , , , , and .
If you want to have more than two inputs to a multi-input gate, simply add more connections to the port list. For
example, the following is a five-input AND gate:

Hierarchy
Just like we build up a complex software program by having procedures call subprocedures, Verilog builds up
complex circuits from modules that instantiate submodules. For example, we can take our previous
module and use it to build a module:

3
Notice that we now instantiate the module just like the standard Verilog gates. We also happen to have
multiple NOT gates in this module. You can instantiate the same type of module (basic or user-defined) more
than once as long as the instance names are different (here, and for the gates).

Local Signals
Line 7 creates local signals and , which are essentially local variables in the module. In
this case, these are wires that carry the signals from the output of the gate to the inverters.

Structural Verilog and Code Ordering


The creation/instantiation of signals and modules as seen so far is considered structural Verilog: the code only
describes the connections between different pieces of hardware. None of this code has any notion of
sequencing or timing—all pieces of hardware will execute in parallel—so the statement order does not matter.
Thus, we could freely swap the ordering of lines 9-11.

Port Connections
Like C/C++/Java arguments and parameters, Verilog will, by default, connect the ports in order of the port list of
the module definition when you instantiate a module. However, we can also explicitly name the ports in Verilog.
That is, when we use in the port list, we are connecting the wire in the
module to the input port of the module instance. This explicit connection tends to avoid
mistakes, especially when someone adds or deletes ports in a module definition.
Note that every signal name in a module must be distinct. However, the same name can be used in different
modules independently. You can connect a module signal to a submodule port of the same name using an
implicit connection. For example, if we had renamed the and input ports as and , then:

could be equivalently written as:

While the and ports are still explicitly connected, the and ports will be implicitly connected
to the and signals of the module (which just happen to also be input ports).

4
There is another style of implicit connection which will implicitly connect as many ports as it can ( ). This saves
a lot of space when connecting many signals. The following is again equivalent to the two examples above.

Boolean Equations and “Assign”


You can also write out Boolean equations in Verilog within a continuous assignment statement ( ), which
sets the value of a variable to the result of a Boolean expression. The Boolean expressions can be constructed
using the bitwise operators NOT ( ), OR ( ), AND ( ), and XOR ( ). An example:

Note that the value of will automatically update (though it may not change) after any of its inputs ( , , , )
change. should not be assigned anywhere else in its module or there will be a conflict.

True and False


Sometimes you want to force a single-bit value to true/high or false/low. We can do that with the constants
= false, and = true. For example, if we wanted to compute the of false and some signal , we
could do the following:

Multi-bit Signals
So far, we have created/declared single-bit signals (i.e., they can only have the value or ) using
statements. However, Verilog also supports multi-bit signals. The following multi-bit declaration example
creates a 3-bit variable named :

This syntax, called a packed array, creates a set of individual signals that can also be treated as a group/bus. The
numbers inside the brackets ( ) define the signal indices with being the most-significant bit ( in this
case) and being the least significant bit ( ). By convention, for an 𝑛-bit signal we use 𝑛-1 as and as .
The individual signals can be used just like any other single-bit signal in Verilog. For example:

This computes AND the right-most bit of and ties the result to the left-most bit of . Multi-bit signals can also
be passed as a group to a module through multi-bit ports:

Note that the bus widths of multi-bit ports and connected signals must match.

5
Multi-bit Signal Declaration Issue
Be aware that all signals declared in the same statement take on the same properties. A common error with
signal declarations looks something like:

What this line does is declare all three signals as 32-bit variables, whereas and are generally one-bit
signals. You may be tipped off to this error if you see a compiler warning about signal or port size mismatches.
What you actually want is:

Array Slices
Sometimes you want to work with a subsection of a multi-bit value, called a slice. We can create a slice by
specifying the start and end indices of the slice separated by a colon. For example, the following code sets
and to and to , leaving all other bits unchanged:

A common use case for slices is breaking apart large packed arrays to pass to submodules. For example, a single
byte can be represented as two hex digits. If we have a module named which converts a 4-bit
value to the segments of a 7-segment display, the following code displays the hex code for a byte:

Multi-bit Literals
You can assign a value to a multi-bit signal using a multi-bit literal. The general format contains 5 parts in the
following order, most of which can be omitted and will assume a default value:
1) the width of the literal in bits (default: 32)
2) a single quote ( )
3) an ‘ ’ character if the number is signed (default: unsigned)
4) the radix indicator (‘ ’ for binary, ‘ ’ for octal, ‘ ’ for decimal, or ‘ ’ for hex; default: decimal)
5) the value expressed in the specified radix
For example, the following statements produce equivalent behavior for a 16-bit signal :

On line 1, the value will be automatically truncated to 16 bits by the compiler (you will get a warning for this).
6
On line 3, the literal is specified as a signed constant, but has the same binary representation as its unsigned
equivalent.
Unspecified bits default to (i.e., can be treated as leading zeros). So, the following is equivalent to Line 3:

Concatenations
You can concatenate multiple signals together into a new grouping by separating them with commas inside curly
braces ( ). For example, if we have two 8-bit signals called and , then creates a 16-bit signal.
Note that the ordering of bits of the new grouping is determined by the ordering of the signals within the
braces.
All types of signals can be used in a concatenation – constants, subsets, buses, single wires, expressions, etc. For
example, the following is a concatenation of a constant with a multi-bit signal:

Bit Replication
Sometimes you would like to replicate a bit or set of bits multiple times. The syntax uses two sets of nested curly
braces with the value to be replicated on the inside and a constant for the number of replications between the
opening braces. The following example replicates the 4-bit signal three times:

Like all other signal expressions, the replication operator can be used inside of a concatenation and vice versa.

Delays
Normally, Verilog statements are assumed to execute instantaneously. However, Verilog does support some
notion of execution delay for basic gates via the # operator (simulation only!!!). For example:

This says that the AND gate takes 5 arbitrary time units to compute, while the OR gate takes 10 units. Note that
the units of time can be whatever you want as long as you are consistent relative to other delays. This can be
handy when testing and simulating designs to mimic combination delays through actual gates in the real world.
7
Defining Named Constants
Sometimes you want to have named constants – values you associate with a meaningful name that you can
reuse throughout a piece of code. For example, you could set the same delay for all units in a module:

This sets the delay of both gates to the value of , which in this case is 5 time units. If we wanted to speed
up both gates, we would only have to reduce the constant in the parameter line.
You may also see parameters used to define names for states in a state machine. However, this particular use
case can be accomplished via an , which is detailed later.

Parameterized Design
Parameters can also be inputs to designs, allowing the caller of the module to set the size of features of that
specific instance of the module. So, if we have a module such as:

This defines a parameter with a default value of 5. Any instantiation of the adder module that does not
specify a width will have all of the internal variable widths set to 5. However, we can also instantiate other
widths as well:

Note that this allows us to instantiate modules of varying size from a single Verilog definition, similar to
templates in C++. You cannot instantiate “on the fly” so parameter values must be known at compile time.

Enumerations
For FSMs and the like, we want to have variables that can take on one of multiple named values – while we
could just use numbers, names are generally clearer. Sometimes we may use statements to set up
names for variables, but for FSM state variables enumerations work better. For example, the following code
defines the allowable states for a hypothetical FSM:
8
This defines two variables, and , and restricts their values to be either , , or . We can test
the value of variables and assign new values using those names:

The compiler will automatically assign values to each of these variables or you can also specify one or more
specific values:

Make sure to have one of the values be equal to , but the other values can be whatever you want. One last tip:
if you want to print the value of an enum variable , you can call to return the string for that value
(e.g., if the current value of is , will return “ ”).

Register Transfer Level (RTL) Code – Behavioral Verilog


In the earlier sections, we showed ways to create structural designs, where we tell the system exactly how to
arrange the design. In RTL (i.e., behavioral) code, we instead state only the behavior we want and allow the
Verilog compiler to determine the actual circuit layout.

In behavioral code, it is easy to forget how the hardware actually works and pretend that it is just C or
Java (i.e., software). This is a great way to design AWFUL hardware. Because of this, we will give you
stylized ways of using the constructs which will guide you towards better designs – think about the
hardware that your system actually requires!

RTL code is divided into two types, combinational logic and sequential logic. In each of these cases, RTL code is
written in block variants, which allow for the use of powerful constructs like - - and
statements. This helps greatly when creating large and complex designs.

Begin-end
and statements denote a block of code, much like the “ ” braces in C and Java, and group multiple
statements together in determining computation “flow.” These can be used with procedural blocks (e.g.,
, ) and conditional statements (e.g., - - , ). Like C, you do not have to use
them for single statements, but they generally help to create more readable and maintainable code.

Combinational Logic
The output of combinational logic is determined purely based on the current value of the inputs. Simple
computations can be written as statements, but complex designs generally go in blocks,
which will execute/trigger whenever any of the input signals change.

9
Sequential Logic
Sequential logic only updates its output when a specific event, typically a clock trigger, occurs. This allows the
design to hold/store state information and behave differently based on previous inputs. The event (i.e.,
expression that evaluates to true) is specified after the keyword and usually triggers from low to
high, , or high to low, . For example, the following block triggers on a rising edge of :

Blocking (=) and Non-blocking (<=) Assignment


Verilog includes two subtly different types of ways to assign values to variables, blocking ( ) and non-blocking
( ):
• A blocking assignment takes effect before subsequent statements in the same block, which will see
the new value. So, will set both and to the value of .
• A non-blocking assignment occurs simultaneously (i.e., in parallel) with all others. So,
will swap the values of and . Similarly, will set to the value of ,
and to the original/old value of .
The two kinds of assignment can be confusing and mixing them in one block is a recipe for disaster. The
following rules are an effective “rules of thumb” to avoid any issues:
1. Use for everything inside blocks (except the iteration variable in a loop).
2. Use for everything inside statements and blocks.
3. Avoid complex logic in blocks. Instead, compute complex logic in blocks,
assign the results to internal signals, and then use simple statements like “ ” in the
blocks.

If-else if-else
The - - constructs look similar to software, but with the important distinction that each signal
must be defined in all cases. This is because the statement is equivalent to defining a logic function. is true
only when = , or when = and = . This is equivalent to the function = + . Similarly, = .

10
Failing to define a signal in all cases will result in one of two outcomes:
1) If the conditional block is inside an block, it will fail with the message:
always_comb construct does not infer purely combinational logic.
2) If not in an block, the code may compile with the warning:
inferring latch(es) for variable "var", which holds its previous value in one or more paths through the
always construct.
Although it may compile successfully, the design will likely exhibit unexpected behavior that is not intended to
be used in this class and therefore should be avoided.

Case
As we move to multi-bit signals that can take on values more than just and , the statement becomes
quite useful. The variable to be considered is placed in the parenthesis of the statement, and then
different values are listed with the associated action. For example in the code below, is set to when the
variable is or and is set to if is , , or . There must also always be a case to catch
values that don’t match any other case. Any variable set in any part of the statement should be set in all
cases. That is, dropping the assignment from any of the cases would be incorrect. Here, we also use the
value to indicate that we do not care if the value is or , allowing the compiler to optimize the design.

Repeat
A loop is a simple statement that repeats a block of code a specified number of times. If you don’t need
to use the value of the loop variable, this is a much simpler syntax than writing out a -loop. For example, the
following code adds to ten times with an 8 arbitrary time unit delay between each addition.

For-Loops
A -loop in SystemVerilog is a much more limited structure compared to other programming languages. It
should only be used as a way of simplifying repetitive statements, not as a way of performing any form of logic.
A good rule to follow is:
11
If you cannot manually expand a -loop in your code, do not use it!

The main scenario where -loops are useful in SystemVerilog is working with multi-bit signals. Sometimes
array slices are insufficient, for example if you want to assign bits in reverse order:

-loops can also be useful in test benches, as they often involve repetitive inputs which can be simplified.

Test Benches
Once a circuit is designed, you need some way to test it. For example, we would like to see how the
circuit we designed earlier behaves. To do this, we create a test bench. A test bench is a module that calls your
original module, often called the Device Under Test or DUT, with the desired input patterns and collects the
results. For example, consider the following setup:

12
The new section of code in this example is the module . It instantiates one copy of the
module, called , and connects input signals that we control and output signals that we can read.
In order to provide test data to the device under test, we create a stimulus block (lines 25-30). The code inside
the statement is executed once at the beginning of the simulation. It sets and to immediately
and then waits 10 arbitrary time units, keeping and at the assigned values. It then set to , keeping
unchanged, and again waits 10 time units. If we consider the entire block, the inputs go through the bit
patterns → → → , which tests all possible input combinations for this circuit. Other
orderings are possible and valid as long as they contain all desired input combinations. For example:

A -loop can be used to simplify sweeping through inputs. The following code is identical in function to the
previous example.

We use the fact that integers are encoded in binary and therefore can be assigned to other variables. This code
easily scales to an arbitrary number of signals of total width – the only changes required are to replace the
upper limit (i.e., ) with 2 (written as in Verilog) and to change the concatenation to include all the input
signals being tested.

13
Testbench Clock Simulator
A sequential circuit will need a clock. We can make a test bench simulate this with the following code:

Many other code variants exist that can produce a valid simulated clock signal. The simulated clock would be put
into the testbench for your system and all sequential modules would take as an input.

Waiting for a Signal


So far, testbench examples have used to delay instructions, but it is also possible to wait for a signal
to change instead of waiting a specific amount of time. This can be useful for debugging modules which take
multiple cycles to finish working. The syntax to wait for a signal to change is or
, similar to the sensitivity list for an block. For example, the following stimulus
block waits for the signal to go high before changing the simulated inputs.

Printing Values to the Console


Most development will use simulated waveforms to examine the state of signals over time. However,
sometimes in debugging it is useful to print messages as well (e.g., any time an error condition is found, print a
message about the error and the values of relevant variables). The command is one way to do this:

takes an arbitrary number of arguments which are concatenated and printed to the console.
is a special function which returns the simulated time when it is called. The empty value between the call
and the string simply adds extra space to make the output more readable.
prints once at the time specified. If you would prefer constant monitoring, use :

14
prints once at the time specified and then any time afterward when any of the signals being
monitored changes.

Advanced Features – Multi-Dimensional Buses


Sometimes it can be useful to have signals with more than one dimension – for example, we might want to hold
sixteen 8-bit values. Verilog allows you to define multiple sets of indices (i.e., multiple packed dimensions):

To access a multi-dimensional packed variable, successive indices refer to the packed dimensions from left-to-
right. For example, the following code sets all the bits of a 3-dimensional bus to :

Unpacked Arrays
It is also possible to declare an array with the size after the variable name. This is called an unpacked array,
because successive elements of an unpacked dimension cannot be treated as one unit. A packed array of 8 bits
can be used as a byte, but an unpacked array of 8 bits can only be accessed one bit at a time:

A mismatched array type and will give an error message about the unpacked array type, often something like:
“unpacked array type cannot be assigned to integer vector type - types do not match”
It is possible to have a variable with both packed and unpacked dimensions. In this case, the unpacked
dimensions are access first from left-to-right, followed by the packed dimensions from left-to-right.

15
Advanced Features – Assert Statements
As you design larger systems, you will often have assumptions you would like to verify are true. For example,
you may have a parameterized module with a limited number of legal parameter values. Or, you may have a
module that assumes the inputs obey certain requirements. You could check this via simulation, but as the
design gets larger you are more and more likely to miss things.
The solution to this is the statement. During simulation, it will raise an error whenever the value inside
the assertion is false. So, if we have a parameter with only a few legal values, we can test it with an assertion
inside the module:

If we require that at least one input to a unit must always be true, we can test it with an always-running
assertion:

Advanced Features – Generate Statements


Earlier in this tutorial the and constructs were introduced in the context of RTL code, which dealt with
changing the values of existing signals. In order to put submodule calls and other logic within -loops and -
statements, the statement must be used. It begins with and ends with . All
structures to be generated, including modules, blocks, blocks, and
statements, should be placed inside all and statements.
Any -loops or -statements must have a block followed by a colon and a name, called the label.
Each construct generated will be identifiable by this label. These names can help when inspecting error
messages and will also appear when examining signals in ModelSim. The following example module creates a
number of submodules based on the parameter .

16

You might also like