496 lines
20 KiB
ReStructuredText
496 lines
20 KiB
ReStructuredText
|
|
Simulation Using Icarus Verilog
|
|
===============================
|
|
|
|
Simulation is the process of creating models that mimic the behavior of the
|
|
device you are designing (simulation models) and creating models to exercise
|
|
the device (test benches). The simulation model need not reflect any
|
|
understanding of the underlying technology, and the simulator need not know
|
|
that the design is intended for any specific technology.
|
|
|
|
The Verilog simulator, in fact, is usually a different program than the
|
|
synthesizer. It may even come from a different vendor. The simulator need not
|
|
know of or generate netlists for the target technology, so it is possible to
|
|
write one simulator that can be used to model designs intended for a wide
|
|
variety of technologies. A synthesizer, on the other hand, does need to know a
|
|
great deal about the target technology in order to generate efficient
|
|
netlists. Synthesizers are often technology specific and come from vendors
|
|
with specialized knowledge, whereas simulators are more general purpose.
|
|
|
|
Simulation models and test benches, therefore, can use the full range of
|
|
Verilog features to model the intended design as clearly as possible. This is
|
|
the time to test the algorithms of the design using language that is
|
|
relatively easy for humans to read. The simulator, along with the test bench,
|
|
can test that the clearly written model really does behave as intended, and
|
|
that the intended behavior really does meet expectations.
|
|
|
|
The test benches model the world outside the design, so they are rarely
|
|
destined for real hardware. They are written in Verilog simply as a matter of
|
|
convenience, and sometimes they are not written in Verilog at all. The test
|
|
benches are not throw-away code either, as they are used to retest the device
|
|
under test as it is transformed from a simulation model to a synthesizeable
|
|
description.
|
|
|
|
Compilation and Elaboration
|
|
---------------------------
|
|
|
|
Simulation of a design amounts to compiling and executing a program. The
|
|
Verilog source that represents the simulation model and the test bench is
|
|
compiled into an executable form and executed by a simulation
|
|
engine. Internally, Icarus Verilog divides the compilation of program source
|
|
to an executable form into several steps, and basic understanding of these
|
|
steps helps understand the nature of failures and errors. The first step is
|
|
macro preprocessing, then compilation, elaboration, optional optimizations and
|
|
finally code generation. The boundary between these steps is often blurred,
|
|
but this progression remains a useful model of the compilation process.
|
|
|
|
The macro preprocessing step performs textual substitutions of macros defined
|
|
with "\`define" statements, textual inclusion with "\`include" statements, and
|
|
conditional compilation by "\`ifdef" and "\`ifndef" statements. The
|
|
macropreprocessor for Icarus Verilog is internally a separate program that can
|
|
be accessed independently by using the "-E" flag to the "iverilog" command,
|
|
like so::
|
|
|
|
% iverilog -E -o out.v example.v
|
|
|
|
This command causes the input Verilog file "example.v" to be preprocessed, and
|
|
the output, a Verilog file without preprocessor statements, written into
|
|
"out.v". The "\`include" and "\`ifdef" directives in the input file are interpreted,
|
|
and defined macros substituted, so that the output, a single file, is the same
|
|
Verilog but with the preprocessor directives gone. All the explicitly
|
|
specified source files are also combined by the preprocessor, so that the
|
|
preprocessed result is a single Verilog stream.
|
|
|
|
Normally, however, the "-E" flag is not used and the preprocessed Verilog is
|
|
instead sent directly to the next step, the compiler. The compiler core takes
|
|
as input preprocessed Verilog and generates an internal parsed form. The
|
|
parsed form is an internal representation of the Verilog source, in a format
|
|
convenient for further processing, and is not accessible to the user.
|
|
|
|
The next step, elaboration, takes the parsed form, chooses the root modules,
|
|
and instantiates (makes *instances* of) those roots. The root instances may
|
|
contain instances of other modules, which may in turn contain instances of yet
|
|
other modules. The elaboration process creates a hierarchy of module instances
|
|
that ends with primitive gates and statements.
|
|
|
|
Note that there is a difference between a module and a module instance. A
|
|
module is a type. It is a description of the contents of module instances that
|
|
have its type. When a module is instantiated within another module, the module
|
|
name identifies the type of the instance, and the instance name identifies the
|
|
specific instance of the module. There can be many instances of any given
|
|
module.
|
|
|
|
Root modules are a special case, in that the programmer does not give them
|
|
instance names. Instead, the instance names of root modules are the same as
|
|
the name of the module. This is valid because, due to the nature of the
|
|
Verilog syntax, a module can be a root module only once, so the module name
|
|
itself is a safe instance name.
|
|
|
|
Elaboration creates a hierarchy of scopes. Each module instance creates a new
|
|
scope within its parent module, with each root module starting a
|
|
hierarchy. Every module instance in the elaborated program has a unique scope
|
|
path, a hierarchical name, that starts with its root scope and ends with its
|
|
own instance name. Every named object, including variables, parameters, nets
|
|
and gates, also has a hierarchical name that starts with a root scope and ends
|
|
with its own base name. The compiler uses hierarchical names in error messages
|
|
generated during or after elaboration, so that erroneous items can be
|
|
completely identified. These hierarchical names are also used by waveform
|
|
viewers that display waveform output from simulations.
|
|
|
|
The elaboration process creates from the parsed form the scope hierarchy
|
|
including the primitive objects within each scope. The elaborated design then
|
|
is optimized to reduce it to a more optimal, but equivalent design. The
|
|
optimization step takes the fully elaborated design and transforms it to an
|
|
equivalent design that is smaller or more efficient. These optimizations are,
|
|
for example, forms of constant propagation and dead code elimination. Useless
|
|
logic is eliminated, and constant expressions are pre-calculated. The
|
|
resulting design behaves as if the optimizations were not performed, but is
|
|
smaller and more efficient. The elimination (and spontaneous creation) of
|
|
gates and statements only affects the programmer when writing VPI modules,
|
|
which through the API have limited access to the structures of the design.
|
|
|
|
Finally, the optimized design, which is still in an internal form not
|
|
accessible to users, is passed to a code generator that writes the design into
|
|
an executable form. For simulation, the code generator is selected to generate
|
|
the vvp format--a text format that can be executed by the simulation
|
|
engine. Other code generators may be selected by the Icarus Verilog user, even
|
|
third party code generators, but the vvp code generator is the default for
|
|
simulation purposes.
|
|
|
|
Making and Using Libraries
|
|
--------------------------
|
|
|
|
Although simple programs may be written into a single source file, this gets
|
|
inconvenient as the designs get larger. Also, writing the entire program into
|
|
a single file makes it difficult for different programs to share common
|
|
code. It therefore makes sense to divide large programs into several source
|
|
files, and to put generally useful source code files somewhere accessible to
|
|
multiple designs.
|
|
|
|
Once the program is divided into many files, the compiler needs to be told how
|
|
to find the files of the program. The simplest way to do that is to list the
|
|
source files on the command line or in a command file. This is for example the
|
|
best way to divide up and integrate test bench code with the simulation model
|
|
of the device under test.
|
|
|
|
The Macro Preprocessor
|
|
^^^^^^^^^^^^^^^^^^^^^^
|
|
|
|
Another technique is to use the macro preprocessor to include library files
|
|
into a main file. The `include` directive takes the name of a source file to
|
|
include. The preprocessor inserts the entire contents of the included file in
|
|
place of the `include` directive. The preprocessor normally looks in the
|
|
current working directory (the current working directory of the running
|
|
compiler, and not the directory where the source file is located) for the
|
|
included file, but the "-I" switch to "iverilog" can add directories to the
|
|
search locations list. ::
|
|
|
|
% iverilog -I/directory/to/search example.v
|
|
|
|
It is common to create include directories shared by a set of programs. The
|
|
preprocessor `include` directive can be used by the individual programs to
|
|
include the source files that it needs.
|
|
|
|
The preprocessor method of placing source code into libraries is general
|
|
(arbitrary source code can be placed in the included files) but is static, in
|
|
the sense that the programmer must explicitly include the desired library
|
|
files. The automatic module library is a bit more constrained, but is
|
|
automatic.
|
|
|
|
Automatic Module Libraries
|
|
^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
|
|
A common use for libraries is to store module definitions that may be of use
|
|
to a variety of programs. If modules are divided into a single module per
|
|
file, and the files are named appropriately, and the compiler is told where to
|
|
look, then the compiler can automatically locate library files when it finds
|
|
that a module definition is missing.
|
|
|
|
For this to work properly, the library files must be Verilog source, they
|
|
should contain a single module definition, and the files must be named after
|
|
the module they contain. For example, if the module "AND2" is a module in the
|
|
library, then it belongs in a file called "AND2.v" and that file contains only
|
|
the "AND2" module. A library, then, is a directory that contains properly
|
|
named and formatted source files. ::
|
|
|
|
% iverilog -y/library/to/search example.v
|
|
|
|
The "-y" flag to "iverilog" tells the compiler to look in the specified
|
|
directory for library modules whenever the program instantiates a module that
|
|
is not otherwise defined. The programmer may include several "-y" flags on the
|
|
command line (or in a command file) and the compiler will search each
|
|
directory in order until an appropriate library file is found to resolve the
|
|
module.
|
|
|
|
Once a module is defined, either in the program or by reading a library
|
|
module, the loaded definition is used from then on within the program. If the
|
|
module is defined within a program file or within an included file, then the
|
|
included definition is used instead of any library definition. If a module is
|
|
defined in multiple libraries, then the first definition that the compiler
|
|
finds is used, and later definitions are never read.
|
|
|
|
Icarus Verilog accesses automatic libraries during elaboration, after it has
|
|
already preprocessed and parsed the non-library source files. Modules in
|
|
libraries are not candidates for root modules, and are not even parsed unless
|
|
they are instantiated in other source files. However, a library module may
|
|
reference other library modules, and reading in a library module causes it to
|
|
be parsed and elaborated, and further library references resolved, just like a
|
|
non-library module. The library lookup and resolution process iterates until
|
|
all referenced modules are resolved, or known to be missing from the
|
|
libraries.
|
|
|
|
The automatic module library technique is useful for including vendor or
|
|
technology libraries into a program. Many EDA vendors offer module libraries
|
|
that are formatted appropriately; and with this technique, Icarus Verilog can
|
|
use them for simulation.
|
|
|
|
Advanced Command Files
|
|
----------------------
|
|
|
|
Command files were mentioned in the "Getting Started" chapter, but only
|
|
briefly. In practice, Verilog programs quickly grow far beyond the usefulness
|
|
of simple command line options, and even the macro preprocessor lacks the
|
|
flexibility to combine source and library modules according to the advancing
|
|
development process.
|
|
|
|
The main contents of a command file is a list of Verilog source files. You can
|
|
name in a command file all the source files that make up your design. This is
|
|
a convenient way to collect together all the files that make up your
|
|
design. Compiling the design can then be reduced to a simple command line like
|
|
the following::
|
|
|
|
% iverilog -c example.cf
|
|
|
|
The command file describes a configuration. That is, it lists the specific
|
|
files that make up your design. It is reasonable, during the course of
|
|
development, to have a set of different but similar variations of your
|
|
design. These variations may have different source files but also many common
|
|
source files. A command file can be written for each variation, and each
|
|
command file lists the source file names used by each variation.
|
|
|
|
A configuration may also specify the use of libraries. For example, different
|
|
configurations may be implementations for different technologies so may use
|
|
different parts libraries. To make this work, command files may include "-y"
|
|
statements. These work in command files exactly how they work on "iverilog"
|
|
command line. Each "-y" flag is followed by a directory name, and the
|
|
directories are searched for library modules in the order that they are listed
|
|
in the command file.
|
|
|
|
The include search path can also be specified in configuration files with
|
|
"+incdir+" tokens. These tokens start with the "+incdir+" string, then
|
|
continue with directory paths, separated from each other with "+" characters
|
|
(not spaces) for the length of the line.
|
|
|
|
Other information can be included in the command file. See the section Command
|
|
File Format for complete details on what can go in a command file.
|
|
|
|
Input Data at Runtime
|
|
---------------------
|
|
|
|
Often, it is useful to compile a program into an executable simulation, then
|
|
run the simulation with various inputs. This requires some means to pass data
|
|
and arguments to the compiled program each time it is executed. For example,
|
|
if the design models a micro-controller, one would like to run the compiled
|
|
simulation against a variety of different ROM images.
|
|
|
|
There are a variety of ways for a Verilog program to get data from the outside
|
|
world into the program at run time. Arguments can be entered on the command
|
|
line, and larger amounts of data can be read from files. The simplest method
|
|
is to take arguments from the command line.
|
|
|
|
Consider this running example of a square root calculator
|
|
|
|
.. code-block:: verilog
|
|
|
|
module sqrt32(clk, rdy, reset, x, .y(acc));
|
|
input clk;
|
|
output rdy;
|
|
input reset;
|
|
|
|
input [31:0] x;
|
|
output [15:0] acc;
|
|
|
|
// acc holds the accumulated result, and acc2 is
|
|
// the accumulated square of the accumulated result.
|
|
reg [15:0] acc;
|
|
reg [31:0] acc2;
|
|
|
|
// Keep track of which bit I'm working on.
|
|
reg [4:0] bitl;
|
|
wire [15:0] bit = 1 << bitl;
|
|
wire [31:0] bit2 = 1 << (bitl << 1);
|
|
|
|
// The output is ready when the bitl counter underflows.
|
|
wire rdy = bitl[4];
|
|
|
|
// guess holds the potential next values for acc,
|
|
// and guess2 holds the square of that guess.
|
|
wire [15:0] guess = acc | bit;
|
|
wire [31:0] guess2 = acc2 + bit2 + ((acc << bitl) << 1);
|
|
|
|
task clear;
|
|
begin
|
|
acc = 0;
|
|
acc2 = 0;
|
|
bitl = 15;
|
|
end
|
|
endtask
|
|
|
|
initial clear;
|
|
|
|
always @(reset or posedge clk)
|
|
if (reset)
|
|
clear;
|
|
else begin
|
|
if (guess2 <= x) begin
|
|
acc <= guess;
|
|
acc2 <= guess2;
|
|
end
|
|
bitl <= bitl - 1;
|
|
end
|
|
|
|
endmodule
|
|
|
|
One could write the test bench as a program that passes a representative set
|
|
of input values into the device and checks the output result. However, we can
|
|
also write a program that takes on the command line an integer value to be
|
|
used as input to the device. We can write and compile this program, then pass
|
|
different input values on the run time command line without recompiling the
|
|
simulation.
|
|
|
|
This example demonstrates the use of the "$value$plusargs" to access command
|
|
line arguments of a simulation
|
|
|
|
.. code-block:: verilog
|
|
|
|
module main;
|
|
|
|
reg clk, reset;
|
|
reg [31:0] x;
|
|
wire [15:0] y;
|
|
wire rdy;
|
|
|
|
sqrt32 dut (clk, rdy, reset, x, y);
|
|
|
|
always #10 clk = ~clk;
|
|
|
|
initial begin
|
|
clk = 0;
|
|
reset = 1;
|
|
|
|
if (! $value$plusargs("x=%d", x)) begin
|
|
$display("ERROR: please specify +x=<value> to start.");
|
|
$finish;
|
|
end
|
|
|
|
#35 reset = 0;
|
|
|
|
wait (rdy) $display("y=%d", y);
|
|
$finish;
|
|
end // initial begin
|
|
|
|
endmodule // main
|
|
|
|
The "$value$plusargs" system function takes a string pattern that describes
|
|
the format of the command line argument, and a reference to a variable that
|
|
receives the value. The "sqrt_plusargs" program can be compiled and executed
|
|
like this::
|
|
|
|
% iverilog -osqrt_plusargs.vvp sqrt_plusargs.v sqrt.v
|
|
% vvp sqrt_plusargs.vvp +x=81
|
|
y= 9
|
|
|
|
Notice that the "x=%d" string of the "$value$plusargs" function describes the
|
|
format of the argument. The "%d" matches a decimal value, which in the sample
|
|
run is "81". This gets assigned to "x" by the "$value$plusargs" function,
|
|
which returns TRUE, and the simulation continues from there.
|
|
|
|
If two arguments have to be passed to the testbench then the main module would
|
|
be modified as follows
|
|
|
|
.. code-block:: verilog
|
|
|
|
module main;
|
|
|
|
reg clk, reset;
|
|
reg [31:0] x;
|
|
reg [31:0] z;
|
|
wire [15:0] y1,y2;
|
|
wire rdy1,rdy2;
|
|
|
|
sqrt32 dut1 (clk, rdy1, reset, x, y1);
|
|
sqrt32 dut2 (clk, rdy2, reset, z, y2);
|
|
|
|
always #10 clk = ~clk;
|
|
|
|
initial begin
|
|
clk = 0;
|
|
reset = 1;
|
|
|
|
if (! $value$plusargs("x=%d", x)) begin
|
|
$display("ERROR: please specify +x=<value> to start.");
|
|
$finish;
|
|
end
|
|
|
|
if (! $value$plusargs("z=%d", z)) begin
|
|
$display("ERROR: please specify +z=<value> to start.");
|
|
$finish;
|
|
end
|
|
|
|
|
|
#35 reset = 0;
|
|
|
|
wait (rdy1) $display("y1=%d", y1);
|
|
wait (rdy2) $display("y2=%d", y2);
|
|
$finish;
|
|
end // initial begin
|
|
|
|
endmodule // main
|
|
|
|
and the "sqrt_plusargs" program would be compiled and executed as follows::
|
|
|
|
% iverilog -osqrt_plusargs.vvp sqrt_plusargs.v sqrt.v
|
|
% vvp sqrt_plusargs.vvp +x=81 +z=64
|
|
y1= 9
|
|
y2= 8
|
|
|
|
In general, the "vvp" command that executes the compiled simulation takes a
|
|
few predefined argument flags, then the file name of the simulation. All the
|
|
arguments after the simulation file name are extended arguments to "vvp" and
|
|
are passed to the executed design. Extended arguments that start with a "+"
|
|
character are accessible through the "$test$plusargs" and "$value$plusargs"
|
|
system functions. Extended arguments that do not start with a "+" character
|
|
are only accessible to system tasks and functions written in C using the VPI.
|
|
|
|
In the previous example, the program pulls the argument from the command line,
|
|
assigns it to the variable "x", and runs the sqrt device under test with that
|
|
value. This program can take the integer square root of any single value. Of
|
|
course, if you wish to test with a large number of input values, executing the
|
|
program many times may become tedious.
|
|
|
|
Another technique would be to put a set of input values into a data file, and
|
|
write the test bench to read the file. We can then edit the file to add new
|
|
input values, then rerun the simulation without compiling it again. The
|
|
advantage of this technique is that we can accumulate a large set of test
|
|
input values, and run the lot as a batch.
|
|
|
|
This example
|
|
|
|
.. code-block:: verilog
|
|
|
|
module main;
|
|
|
|
reg clk, reset;
|
|
reg [31:0] data[4:0];
|
|
reg [31:0] x;
|
|
wire [15:0] y;
|
|
wire rdy;
|
|
|
|
sqrt32 dut (clk, rdy, reset, x, y);
|
|
|
|
always #10 clk = ~clk;
|
|
|
|
integer i;
|
|
initial begin
|
|
/* Load the data set from the hex file. */
|
|
$readmemh("sqrt.hex", data);
|
|
for (i = 0 ; i <= 4 ; i = i + 1) begin
|
|
clk = 0;
|
|
reset = 1;
|
|
|
|
x = data[i];
|
|
|
|
#35 reset = 0;
|
|
|
|
wait (rdy) $display("y=%d", y);
|
|
end
|
|
$finish;
|
|
end // initial begin
|
|
|
|
endmodule // main
|
|
|
|
demonstrates the use of "$readmemh" to read data samples from a file into a
|
|
Verilog array. Start by putting into the file "sqrt.hex" the numbers::
|
|
|
|
51
|
|
19
|
|
1a
|
|
18
|
|
1
|
|
|
|
Then run the simulation with the command sequence::
|
|
|
|
% iverilog -osqrt_readmem.vvp sqrt_readmem.vl sqrt.vl
|
|
% vvp sqrt_readmem.vvp
|
|
y= 9
|
|
y= 5
|
|
y= 5
|
|
y= 4
|
|
y= 1
|
|
|
|
It is easy enough to change this program to work with larger data sets, or to
|
|
change the "data.hex" file to contain different data. This technique is also
|
|
common for simulating algorithms that take in larger data sets. One can extend
|
|
this idea slightly by using a "$value$plusargs" statement to select the file
|
|
to read.
|