mirror of
https://github.com/myhdl/myhdl.git
synced 2024-12-14 07:44:38 +08:00
414 lines
16 KiB
TeX
414 lines
16 KiB
TeX
\chapter{Co-simulation with Verilog and VHDL}
|
|
|
|
\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 co-simulation. \myhdl\ is
|
|
enabled for co-simulation 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 co-simulation, 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.
|
|
|
|
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 co-simulation 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 and made available 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 co-simulation by a \code{Cosimulation} object.
|
|
A \code{Cosimulation} object must know how to run a HDL simulation.
|
|
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
|
|
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 an argument
|
|
in a \code{\$to_myhdl} or \code{\$from_myhdl} call; the argument is
|
|
a \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 try it on the Verilog design:
|
|
|
|
\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}; // zero-extend input
|
|
|
|
always @(extB) begin
|
|
for (i=0; i < width; i=i+1)
|
|
G[i] <= extB[i+1] ^ extB[i];
|
|
end
|
|
|
|
endmodule
|
|
|
|
\end{verbatim}
|
|
|
|
When we run our unit test, we get:
|
|
|
|
\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 race free
|
|
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 disproportionate 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 co-simulated}
|
|
|
|
The most important restriction of the \myhdl\ co-simulation solution is
|
|
that only ``passive'' HDL can be co-simulated. 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 co-simulation, it probably
|
|
isn't.
|
|
|
|
\myhdl\ supports co-simulation so that test benches for HDL
|
|
designs can be written in Python. Let's consider the nature of the
|
|
target HDL designs. For high-level, behavioral models that are not
|
|
intended for implementation, it should come as no surprise that I
|
|
would recommend to write them in \myhdl\ directly; that is one of the
|
|
goals 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 targeted HDL designs are naturally models that are
|
|
intended for implementation, most likely 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 time step. 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 time step. This is done
|
|
by the concept of ``delta'' cycles. In a fully general, race free
|
|
co-simulation, the co-simulators would communicate at the level of delta
|
|
cycles. However, in \myhdl\ co-simulation, this is not entirely the
|
|
case.
|
|
|
|
Delta cycles from the \myhdl\ simulator toward the HDL co-simulator 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 co-simulator.
|
|
|
|
What does this mean? Let's start with the good news. As explained in
|
|
the previous section, the concept behind \myhdl\ co-simulation implies
|
|
that clocks are generated at the \myhdl\ side. \emph{When using a
|
|
\myhdl\ clock and its corresponding HDL signal directly as a clock,
|
|
co-simulation is race free.} In other words, the case
|
|
that most closely reflects the \myhdl\ co-simulation approach, is race free.
|
|
|
|
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 race free. 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 co-simulation 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 other simulators in the
|
|
future, either from external contributions, or by getting access to
|
|
them myself.
|
|
|
|
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}
|
|
|
|
\subsubsection{Delta cycle implementation}
|
|
\label{icarus-delta-cycles}
|
|
|
|
To make co-simulation 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
|
|
time step (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 time step. The second half
|
|
of the solution is to map \myhdl\ delta cycles onto real Verilog time
|
|
steps. Note that fortunately I had some freedom here because of the
|
|
restriction that only passive HDL code can be co-simulated.
|
|
|
|
I chose to make the time granularity in the Verilog simulator a 1000
|
|
times finer than in the \myhdl{} simulator. For each \myhdl\ time
|
|
step, 1000 Verilog time steps are available for \myhdl\ delta
|
|
cycles. In practice, only a few delta cycles per time step should be
|
|
needed. Exceeding this limit almost certainly indicates a design error;
|
|
the 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.
|
|
|
|
\subsubsection{Passive Verilog check}
|
|
|
|
As explained before, co-simulated Verilog should not contain delay
|
|
statements. Ideally, there should be a run-time check to flag
|
|
non-compliant code. However, there is currently no such check in the
|
|
Icarus module.
|
|
|
|
The check can be written using the \code{cbNextSimTime} VPI callback
|
|
in Verilog. However, Icarus 0.7 doesn't support this callback. In the
|
|
meantime, support for it has been added to the Icarus development
|
|
branch. When Icarus 0.8 is released, a check will be added.
|
|
|
|
In the mean time, just don't do this. It may appear to ``work'' but it
|
|
really won't as events will be missed over the co-simulation
|
|
interface.
|
|
|
|
\subsubsection{Interrupted system calls}
|
|
|
|
The PLI module uses \code{read} and \code{write} system calls to
|
|
communicate between the co-simulators. The implementation assumes that
|
|
these calls are restarted automatically by the operating system when
|
|
interrupted. This is apparently what happens on my Linux box and on
|
|
other operating systems I have used before.
|
|
|
|
I know how to handle non-restarted interrupted system calls, but I
|
|
cannot test the code! Also, I don't know whether this is still a
|
|
relevant issue with modern operating systems. So I left it
|
|
at that for the moment, and added assertions that should trigger
|
|
when this situation occurs.
|
|
|
|
Whenever an assertion fires in the PLI module, let me
|
|
know. The same holds for Python exceptions that you cannot
|
|
easily explain.
|
|
|
|
\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 to support delta cycles described in
|
|
Section~\ref{icarus-delta-cycles} 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 VHDL simulators such as the
|
|
Modelsim VHDL simulator. This will require a PLI module using the
|
|
PLI of the VHDL simulator. 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 a special HDL module (entity). Rather, the participating
|
|
signals can be declared directly in the \code{\$to_myhdl} and
|
|
\code{\$from_myhdl} task calls.
|