1
0
mirror of https://github.com/myhdl/myhdl.git synced 2025-01-24 21:52:56 +08:00
myhdl/doc/cosimulation.tex
2003-05-14 08:50:19 +00:00

415 lines
15 KiB
TeX

\chapter{MyHDL as a hardware verification language}
\section{Introduction}
One of the most exciting possibilities of \myhdl\
is to use it as a hardware verification language (HVL).
A HVL is a language used to write test benches and
verification environments, and to control simulations.
Nowadays, it is generally acknowledged that HVLs should be equipped
with modern software techniques, such as object orientation. The
reason is that verification it the most complex and time-consuming
task of the design process: consequently every useful technique is
welcome. Moreover, test benches are not required to be
implementable. Therefore, unlike synthesizable code, there
are no constraints on creativity.
Technically, verification of a design implemented in
another language requires cosimulation. \myhdl\ is
enabled for cosimulation with any HDL simulator that
has a procedural language interface (PLI). The \myhdl\
side is designed to be independent of a particular
simulator, On the other hand, for each HDL simulator a specific
PLI module will have to be written in C. Currently,
the \myhdl\ release contains a PLI module to interface
to the Icarus Verilog simulator. This interface will
be used in the examples.
\section{The HDL side}
To introduce cosimulation, we will continue to use the Gray encoder
example from the previous chapters. Suppose that we want to
synthesize it and write it in Verilog for that purpose. Clearly we would
like to reuse our unit test verification environment. This is exactly
what \myhdl\ offers.
To start, let's recall how the Gray encoder in \myhdl{} looks like:
\begin{verbatim}
def bin2gray(B, G, width):
""" Gray encoder.
B -- input intbv signal, binary encoded
G -- output intbv signal, gray encoded
width -- bit width
"""
while 1:
yield B
for i in range(width):
G.next[i] = B[i+1] ^ B[i]
\end{verbatim}
To show the cosimulation flow, we don't need the Verilog
implementation yet, but only the interface. Our Gray encoder in
Verilog would have the following interface:
\begin{verbatim}
module bin2gray(B, G);
parameter width = 8;
input [width-1:0] B;
output [width-1:0] G;
....
\end{verbatim}
To write a test bench, one creates a new module that instantiates the
design under test (DUT). The test bench declares nets and
regs (or signals in VHDL) that are attached to the DUT, and to
stimulus generators and response checkers. In an all-HDL flow, the
generators and checkers are written in the HDL itself, but we will
want to write them in \myhdl{}. To make the connection, we need to
declare which regs \& nets are driven and read by the \myhdl\
simulator. For our example, this is done as follows:
\begin{verbatim}
module dut_bin2gray;
reg [`width-1:0] B;
wire [`width-1:0] G;
initial begin
$from_myhdl(B);
$to_myhdl(G);
end
bin2gray dut (.B(B), .G(G));
defparam dut.width = `width;
endmodule
\end{verbatim}
The \code{\$from_myhdl} task call declares which regs are driven by
\myhdl{}, and the \code{\$to_myhdl} task call which regs \& nets are read
by it. These tasks take an arbitrary number of arguments. They
are defined in a PLI module written in C. They are made available to
the simulation in a simulator-dependent manner. In Icarus Verilog,
the tasks are defined in a \code{myhdl.vpi} module that is compiled
from C source code.
\section{The \myhdl\ side}
\myhdl\ supports cosimulation by a \code{Cosimulation} object.
A \code{Cosimulation} object must know how to run a HDL cosimulation.
Therefore, the first argument to its constructor is a command string
to execute a simulation. The way to generate and run an
simulation executable is simulator dependent.
For example, in Icarus Verilog, a simulation executable for our
example can be obtained obtained by running the \code{iverilog}
compiler as follows:
\begin{verbatim}
% iverilog -o bin2gray -Dwidth=4 bin2gray.v dut_bin2gray.v
\end{verbatim}
This generates a \code{bin2gray} executable for a parameter \code{width}
of 4, by compiling the contributing verilog files.
The simulation itself is run by the \code{vvp} command:
\begin{verbatim}
% vvp -m ./myhdl.vpi bin2gray
\end{verbatim}
This runs the \code{bin2gray} simulation, and specifies to use the
\code{myhdl.vpi} PLI module present in the current directory. (This is
just a command line usage example; actually simulating with the
\code{myhdl.vpi} module is only meaningful from a
\code{Cosimulation} object.)
We can use a \code{Cosimulation} object to provide a HDL cosimulation
version of a design to the \myhdl\ simulator. Instead of a generator
function, we write a function that returns a \code{Cosimulation}
object. For our example and the Icarus Verilog simulator, this is done
as follows:
\begin{verbatim}
import os
from myhdl import Cosimulation
cmd = "iverilog -o bin2gray -Dwidth=%s bin2gray.v dut_bin2gray.v"
def bin2gray(B, G, width):
os.system(cmd % width)
return Cosimulation("vvp -m ./myhdl.vpi bin2gray", B=B, G=G)
\end{verbatim}
After the executable command argument, the \code{Cosimulation}
constructor takes an arbitrary number of keyword arguments. Those
arguments make the link between \myhdl\ Signals and HDL nets, regs, or
signals, by named association. The keyword is the name of the argument
in a \code{\$to_myhdl} or \code{\$from_myhdl} call; the argument is
the \myhdl\ Signal.
With all this in place, we can now use the existing unit test
to verify the Verilog implementation. Note that we kept the
same name and parameters for the the \code{bin2gray} function:
all we need to do is to provide this alternative definition
to the existing unit test.
Let's quickly try it just to be sure:
\begin{verbatim}
module bin2gray(B, G);
parameter width = 8;
input [width-1:0] B;
output [width-1:0] G;
reg [width-1:0] G;
integer i;
always @(B) begin
for (i=0; i < width-1; i=i+1)
G[i] <= B[i+1] ^ B[i];
end
endmodule
\end{verbatim}
If we run our unit test we get:
\begin{verbatim}
% python test_bin2gray.py
Check that only one bit changes in successive codewords ... ERROR
Check that all codewords occur exactly once ... FAIL
Check that the code is an original Gray code ... ERROR
...
\end{verbatim}
Oops! It seems we still have a bug! Oh yes, but of course,
we need to zero-extend the input to get the msb output bit
correctly:
\begin{verbatim}
module bin2gray(B, G);
parameter width = 8;
input [width-1:0] B;
output [width-1:0] G;
reg [width-1:0] G;
integer i;
wire [width:0] extB;
assign extB = {1'b0, B};
always @(extB) begin
for (i=0; i < width; i=i+1)
G[i] <= extB[i+1] ^ extB[i];
end
endmodule
\end{verbatim}
And now:
\begin{verbatim}
% python test_bin2gray.py
Check that only one bit changes in successive codewords ... ok
Check that all codewords occur exactly once ... ok
Check that the code is an original Gray code ... ok
----------------------------------------------------------------------
Ran 3 tests in 2.729s
OK
\end{verbatim}
\section{Restrictions}
In the ideal case, it should be possible to simulate
any HDL description seamlessly with \myhdl{}. Moreover
the communicating signals at each side should act
transparently as a single one, enabling fully racefree
operation.
For various reasons, it may not be possible or desirable
to achieve full generality. As anyone that has developed
applications with the Verilog PLI can testify, the
restrictions in a particular simulator, and the
differences over various simulators, can be quite
frustrating. Moreover, full generality may require
a disproportiate amount of development work compared
to a slightly less general solution that may
be sufficient for the target application.
Consequently, I have tried to achieve a solution
which is simple enough so that one can reasonably
expect that any PLI-enabled simulator can support it,
and that is relatively easy to verify and maintain.
At the same time, the solution is sufficiently general
to cover the target application space.
The result is a compromise that places certain restrictions
on the HDL code. In this section, these restrictions
are presented.
\subsection{Only passive HDL can be cosimulated}
The most important restriction of the \myhdl\ cosimulation solution is
that only ``passive'' HDL can be cosimulated. This means that the HDL
code should not contain any statements with time delays. In other
words, the \myhdl\ simulator should be the master of time; in
particular, any clock signal should be generated at the \myhdl\ side.
At first this may seem like an important restriction, but if one
considers the target application for cosimulation, it probably
isn't.
\myhdl\ support cosimulations so that test benches for HDL
designs can be written in Python.
Let's consider the nature of the targetHDL designs. For high-level,
behavioral models that are not intended for implementation, it should
come as no surprize that I would recommend to write them in \myhdl\
directly; that is exactly the target of the \myhdl\ effort. Likewise,
gate level designs with annotated timing are not the target
application: static timing analysis is a much better verification
method for such designs.
Rather, the targetted HDL designs are naturally models that are
intended for implementation. Most likely, this will be through
synthesis. As time delays are meaningless in synthesizable code, the
restriction is compatible with the target application.
\subsection{Race sensitivity issues}
In a typical RTL code, some events cause other events to occur in the
same timestep. For example, when a clock signal triggers some signals
may change in the same time step. For race-free operation, an HDL
must differentiate between such events within a timestep. This is done
by the concept of ``delta'' cycles. In a fully general, racefree
cosimulation, the cosimulators would communicate at the level of delta
cycles. However, in \myhdl\ cosimulation, this is not entirely the
case.
Delta cycles from the \myhdl\ simulator toward the HDL cosimulator are
preserved. However, in the opposite direction, they are not. The
signals changes are only returned to the \myhdl\ simulator after all delta
cycles have been performed in the HDL cosimulator.
What does this mean? Let's start with the good news. As explained in
the previous section, the logic of the \myhdl\ cosimulation implies
that clocks are generated at the \myhdl\ side. \emph{When using a
\myhdl\ clock and its corresponding HDL signal directly as a clock,
cosimulation operation is racefree.} In other words, the case
that most closely reflects the \myhdl\ cosimulation approach, is racefree.
The situation is different when you want to use a signal driven by the
HDL (and the corresponding MyHDL signal) as a clock.
Communication triggered by such a clock is not racefree. The solution
is to treat such an interface as a chip interface instead of an RTL
interface. For example, when data is triggered at positive clock
edges, it can safely be sampled at negative clock edges.
Alternatively, the \myhdl\ data signals can be declared with a delay
value, so that they are guaranteed to change after the clock
edge.
\section{Implementation notes}
\begin{quote}
\em
This section requires some knowledge of PLI terminology.
\end{quote}
Enabling a simulator for cosimulation requires a PLI module
written in C. In Verilog, the PLI is part of the ``standard''.
However, different simulators implement different versions
and portions of the standard. Worse yet, the behavior of
certain PLI callbacks is not defined on some essential points.
As a result, one should plan to write a specific PLI module
for any simulator.
The present release contains a PLI module for the
open source Icarus simulator. I would like to add
modules for any popular simulator in the future,
either from external contributions, or by getting
access to them myself. The same holds for VHDL
simulators: it would be great to have an interface
to the Modelsim VHDL simulator.
This section documents
the current approach and status of the PLI module
implementation in Icarus, and some reflections
on future implementations in other simulators.
\subsection{Icarus Verilog}
To make cosimulation work, a specific type of PLI callback is
needed. The callback should be run when all pending events have been
processed, while allowing the creation of new events in the current
timestep (e.g. by the \myhdl\ simulator). In some Verilog simulators,
the \code{cbReadWriteSync} callback does exactly that. However,
in others, including Icarus, it does not. The callback's behavior is
not fully standardized; some simulators run the callback before
non-blocking assignment events have been processed.
Consequently, I had to look for a workaround. One half of the solution
is to use the \code{cbReadOnlySync} callback. This callback runs
after all pending events have been processed. However, it does not
permit to create new events in the current timestep. The second half
of the solution is to map \myhdl\ delta cycles onto Verilog timesteps.
Note that there is some freedom here because of the restriction that
only passive HDL code can be cosimulated.
I chose to make the time granularity in the Verilog simulator a 1000
times finer than in the \myhdl{} simulator. For each \myhdl\ timestep,
1000 Verilog timesteps are available for \myhdl\ delta cycles. In practice,
only a few delta cycles per timestep should be needed. More than 1000
almost certainly indicates an error. This limit is checked at
run-time. The factor 1000 also makes it easy to distinguish ``real''
time from delta cycle time when printing out the Verilog time.
\subsection{Other Verilog simulators}
The Icarus module is written with VPI calls, which are provided by the
most recent generation of the Verilog PLI. Some simulators may only
support TF/ACC calls, requiring a complete redesign of the interface
module.
If the simulator supports VPI, the Icarus module should be reusable to
a large extent. However, it may be possible to improve on it. The
workaround described in the previous section may not be necessary. In
some simulators, the \code{cbReadWriteSync} callback occurs after all
events (including non-blocking assignments) have been processed. In
that case, the functionality can be supported without a finer time
granularity in the Verilog simulator.
There are also Verilog standardization efforts underway to resolve the
ambiguity of the \code{cbReadWriteSync} callback. The solution will be
to introduce new, well defined callbacks. From reading some proposals,
I conclude that the \code{cbEndOfSimTime} callback would provide the
required functionality.
\subsection{VHDL}
It would be great to have an interface to the Modelsim VHDL
simulator. This will require a redesign from scratch with the
appropriate PLI. One feature which I would like to keep if possible
is the way to declare the communicating signals. In the Verilog
solution, it is not necessary to define and instantiate any special
entity (module). Rather, the participating signals can be declared
directly in the \code{to_myhdl} and \code{from_myhdl} task calls.