mirror of
https://github.com/myhdl/myhdl.git
synced 2024-12-14 07:44:38 +08:00
1022 lines
32 KiB
TeX
1022 lines
32 KiB
TeX
\section{Introduction\label{conv-intro}}
|
|
|
|
\myhdl\ supports the automatic conversion of implementation-oriented
|
|
\myhdl\ code to Verilog code. This feature provides a
|
|
direct path from Python to an FPGA or ASIC implementation.
|
|
|
|
\section{Solution description\label{conv-solution}}
|
|
|
|
The solution works as follows. The hardware description should
|
|
satisfy certain constraints that are typical for
|
|
implementation-oriented hardware modeling. Subsequently, such a
|
|
design is converted to an equivalent model in the Verilog language,
|
|
using the function \function{toVerilog} from the \myhdl\
|
|
library. Finally, a third-party \emph{synthesis tool} is used to
|
|
convert the Verilog design to a gate implementation for an ASIC or
|
|
FPGA. There are a number of Verilog synthesis tools available, varying
|
|
in price, capabilities, and target implementation technology.
|
|
|
|
The conversion does not start from source files, but from an
|
|
instantiated design that has been \emph{elaborated} by the Python
|
|
interpreter. The converter uses the Python profiler to track the
|
|
interpreter's operation and to infer the design structure and name
|
|
spaces. It then selectively compiles pieces of source code for
|
|
additional analysis and for conversion. This is done using the Python
|
|
compiler package.
|
|
|
|
\section{Features\label{conv-features}}
|
|
|
|
\subsection{The design is converted after elaboration\label{conv-features-elab}}
|
|
\emph{Elaboration} refers to the initial processing of a hardware
|
|
description to achieve a representation of a design instance that is
|
|
ready for simulation or synthesis. In particular, structural
|
|
parameters and constructs are processed in this step. In \myhdl{}, the
|
|
Python interpreter itself is used for elaboration. A
|
|
\class{Simulation} object is constructed with elaborated design
|
|
instances as arguments. Likewise, the Verilog conversion works on an
|
|
elaborated design instance. The Python interpreter is thus used as
|
|
much as possible.
|
|
|
|
\subsection{The structural description can be arbitrarily complex and hierarchical\label{conv-features-struc}}
|
|
As the conversion works on an elaborated design instance, any modeling
|
|
constraints only apply to the leaf elements of the design structure,
|
|
that is, the co-operating generators. In other words, there are no
|
|
restrictions on the description of the design structure: Python's full
|
|
power can be used for that purpose. Also, the design hierarchy can be
|
|
arbitrarily deep.
|
|
|
|
\subsection{Generators are mapped to Verilog always or initial blocks\label{conv-features-gen}}
|
|
The converter analyzes the code of each generator and maps it
|
|
to a Verilog \code{always} blocks if possible, and to
|
|
an \code{initial} block otherwise.
|
|
The converted Verilog design will be a flat
|
|
"net list of blocks".
|
|
|
|
\subsection{The Verilog module interface is inferred from signal usage\label{conv-features-intf}}
|
|
In \myhdl{}, the input or output direction of interface signals
|
|
is not explicitly declared. The converter investigates signal usage
|
|
in the design hierarchy to infer whether a signal is used as
|
|
input, output, or as an internal signal. Internal signals are
|
|
given a hierarchical name in the Verilog output.
|
|
|
|
\subsection{Function calls are mapped to a unique Verilog function or task call\label{conv-features-func}}
|
|
The converter analyzes function calls and function code to see if they
|
|
should be mapped to Verilog functions or to tasks. Python functions
|
|
are much more powerful than Verilog subprograms; for example, they are
|
|
inherently generic, and they can be called with named association. To
|
|
support this power in Verilog, a unique Verilog function or task is
|
|
generated per Python function call.
|
|
|
|
\subsection{If-then-else structures may be mapped to Verilog case statements\label{conv-features-if}}
|
|
Python does not provide a case statement. However,
|
|
the converter recognizes if-then-else structures in which a variable is
|
|
sequentially compared to items of an enumeration type, and maps
|
|
such a structure to a Verilog case statement with the appropriate
|
|
synthesis attributes.
|
|
|
|
\subsection{Choice of encoding schemes for enumeration types\label{conv-features-enum}}
|
|
The \function{enum} function in \myhdl\ returns an enumeration type. This
|
|
function takes an additional parameter \var{encoding} that specifies the
|
|
desired encoding in the implementation: binary, one hot, or one cold.
|
|
The Verilog converter generates the appropriate code.
|
|
|
|
\subsection{Support for RAM inference \label{conf-features-ram}}
|
|
Certain synthesis tools can map Verilog memories to RAM
|
|
structures. To support this interesting feature, the Verilog converter
|
|
maps lists of signals to Verilog memories.
|
|
|
|
\subsection{Support for ROM memory \label{conf-features-rom}}
|
|
Some synthesis tools can infer a ROM
|
|
from a case statement. The Verilog converter does the expansion into
|
|
a case statement automatically, based on a higher level
|
|
description. The ROM access is described in a single line, by
|
|
indexing into a tuple of integers.
|
|
|
|
\subsection{Support for signed arithmetic \label{conf-features-signed}}
|
|
In MyHDL, working with negative numbers is trivial: one just uses
|
|
\code{intbv} objects with negative values.
|
|
By contrast, negative numbers are tricky in Verilog. The language
|
|
makes a difference between an unsigned and a signed representation,
|
|
and the user has to declare signed variables explicitly. When the two
|
|
representations are mixed in an expression, all operands are
|
|
interpreted as unsigned, which typically leads to unexpected results.
|
|
|
|
The Verilog converter handles negative \code{intbv} objects by using
|
|
a signed Verilog representation. Also, it automatically performs sign
|
|
extension and casting to a signed representation when unsigned numbers
|
|
are used in a mixed expression. In this way, it automates a task which
|
|
is notoriously hard to get right in Verilog directly.
|
|
|
|
\subsection{Support for user-defined Verilog code \label{conf-features-udfv}}
|
|
If desired, the user can bypass the regular Verilog conversion
|
|
and describe user-defined code to be inserted instead.
|
|
|
|
\section{The convertible subset\label{conv-subset}}
|
|
|
|
\subsection{Introduction\label{conv-subset-intro}}
|
|
|
|
Unsurprisingly, not all \myhdl\ code can be converted to Verilog. In
|
|
fact, there are very important restrictions. As the goal of the
|
|
conversion functionality is implementation, this should not be a big
|
|
issue: anyone familiar with synthesis is used to similar restrictions
|
|
in the \emph{synthesizable subset} of Verilog and VHDL. The converter
|
|
attempts to issue clear error messages when it encounters a construct
|
|
that cannot be converted.
|
|
|
|
In practice, the synthesizable subset usually refers to RTL synthesis,
|
|
which is by far the most popular type of synthesis today. There are
|
|
industry standards that define the RTL synthesis subset. However,
|
|
those were not used as a model for the restrictions of the MyHDL
|
|
converter, but as a minimal starting point. On that basis, whenever
|
|
it was judged easy or useful to support an additional feature, this
|
|
was done. For example, it is actually easier to convert
|
|
\keyword{while} loops than \keyword{for} loops even though they are
|
|
not RTL-synthesizable. As another example, \keyword{print} is
|
|
supported because it's so useful for debugging, even though it's not
|
|
synthesizable. In summary, the convertible subset is a superset of
|
|
the standard RTL synthesis subset, and supports synthesis tools with
|
|
more advanced capabilities, such as behavioral synthesis.
|
|
|
|
Recall that any restrictions only apply to the design post
|
|
elaboration. In practice, this means that they apply only to the code
|
|
of the generators, that are the leaf functional blocks in a MyHDL
|
|
design.
|
|
|
|
\subsection{Coding style\label{conv-subset-style}}
|
|
|
|
A natural restriction on convertible code is that it should be
|
|
written in MyHDL style: cooperating generators, communicating through
|
|
signals, and with sensitivity lists specifying wait points and resume
|
|
conditions. Supported resume conditions are a signal edge, a signal
|
|
change, or a tuple of such conditions.
|
|
|
|
\subsection{Supported types\label{conv-subset-types}}
|
|
|
|
The most important restriction regards object types. Verilog is an
|
|
almost typeless language, while Python is strongly (albeit
|
|
dynamically) typed. The converter has to infer the types of names
|
|
used in the code, and map those names to Verilog variables.
|
|
|
|
Only a limited amount of types can be converted.
|
|
Python \class{int} and \class{long} objects are mapped to Verilog
|
|
integers. All other supported types are mapped to Verilog regs (or
|
|
wires), and therefore need to have a defined bit width. The supported
|
|
types are the Python \class{bool} type, the MyHDL \class{intbv} type,
|
|
and MyHDL enumeration types returned by function \function{enum}. The
|
|
latter objects can also be used as the base object of a
|
|
\class{Signal}.
|
|
|
|
\class{intbv} objects must be constructed so that a bit
|
|
width can be inferred. This can be done by specifying minimum
|
|
and maximum values, e.g. as follows:
|
|
|
|
\begin{verbatim}
|
|
index = intbv(0, min=MIN, max=MAX)
|
|
\end{verbatim}
|
|
|
|
The Verilog converter supports \class{intbv} objects that
|
|
can take negative values.
|
|
|
|
Alternatively, a slice can be taken from an \class{intbv} object
|
|
as follows:
|
|
|
|
\begin{verbatim}
|
|
index = intbv(0)[N:]
|
|
\end{verbatim}
|
|
|
|
Such as slice returns a new \class{intbv} object, with minimum
|
|
value \code{0} , and maximum value \code{2**N}.
|
|
|
|
|
|
\subsection{Supported statements\label{conv-subset-statements}}
|
|
|
|
The following is a list of the statements that are supported by the
|
|
Verilog converter, possibly qualified with restrictions
|
|
or usage notes.
|
|
|
|
\begin{description}
|
|
|
|
\item[The \keyword{break} statement.]
|
|
|
|
\item[The \keyword{continue} statement.]
|
|
|
|
\item[The \keyword{def} statement.]
|
|
|
|
\item[The \keyword{for} statement.]
|
|
The only supported iteration scheme is iterating through sequences of
|
|
integers returned by built-in function \function{range} or \myhdl\
|
|
function \function{downrange}. The optional \keyword{else} clause is
|
|
not supported.
|
|
|
|
\item[The \keyword{if} statement.]
|
|
\keyword{if}, \keyword{elif}, and \keyword{else} clauses
|
|
are fully supported.
|
|
|
|
\item[The \keyword{pass} statement.]
|
|
|
|
\item[The \keyword{print} statement.]
|
|
When printing an interpolated string, the format specifiers are copied
|
|
verbatim to the Verilog output. Printing to a file (with syntax
|
|
\code{'>>'}) is not supported.
|
|
|
|
\item[The \keyword{raise} statement.]
|
|
This statement is mapped to Verilog statements
|
|
that end the simulation with an error message.
|
|
|
|
\item[The \keyword{return} statement.]
|
|
|
|
\item[The \keyword{yield} statement.]
|
|
The yielded expression can be a signal, a signal edge
|
|
as specified by \myhdl\ functions \function{posedge}
|
|
or \function{negedge}, or a tuple of signals and
|
|
edge specifications.
|
|
|
|
\item[The \keyword{while} statement.]
|
|
The optional \keyword{else}
|
|
clause is not supported.
|
|
|
|
\end{description}
|
|
|
|
\section{Methodology notes\label{conv-meth}}
|
|
|
|
\subsection{Simulation\label{conv-meth-sim}}
|
|
|
|
In the Python philosophy, the run-time rules. The Python compiler
|
|
doesn't attempt to detect a lot of errors beyond syntax errors, which
|
|
given Python's ultra-dynamic nature would be an almost impossible task
|
|
anyway. To verify a Python program, one should run it, preferably
|
|
using unit testing to verify each feature.
|
|
|
|
The same philosophy should be used when converting a MyHDL description
|
|
to Verilog: make sure the simulation runs fine first. Although the
|
|
converter checks many things and attempts to issue clear error
|
|
messages, there is no guarantee that it does a meaningful job unless
|
|
the simulation runs fine.
|
|
|
|
\subsection{Conversion output verification\label{conv-meth-conv}}
|
|
It is always prudent to verify the converted Verilog output.
|
|
To make this task easier, the converter also generates a
|
|
test bench that makes it possible to simulate the Verilog
|
|
design using the Verilog co-simulation interface. This
|
|
permits to verify the Verilog code with the same test
|
|
bench used for the \myhdl\ code. This is also how
|
|
the Verilog converter development is being verified.
|
|
|
|
\subsection{Assignment issues\label{conv-meth-assign}}
|
|
|
|
\subsubsection{Name assignment in Python\label{conv-meth-assign-python}}
|
|
|
|
Name assignment in Python is a different concept than in
|
|
many other languages. This point is very important for
|
|
effective modeling in Python, and even more so
|
|
for synthesizable \myhdl\ code. Therefore, the issues are
|
|
discussed here explicitly.
|
|
|
|
Consider the following name assignments:
|
|
|
|
\begin{verbatim}
|
|
a = 4
|
|
a = ``a string''
|
|
a = False
|
|
\end{verbatim}
|
|
|
|
In many languages, the meaning would be that an
|
|
existing variable \var{a} gets a number of different values.
|
|
In Python, such a concept of a variable doesn't exist. Instead,
|
|
assignment merely creates a new binding of a name to a
|
|
certain object, that replaces any previous binding.
|
|
So in the example, the name \var{a} is bound a
|
|
number of different objects in sequence.
|
|
|
|
The Verilog converter has to investigate name
|
|
assignment and usage in \myhdl\ code, and to map
|
|
names to Verilog variables. To achieve that,
|
|
it tries to infer the type and possibly the
|
|
bit width of each expression that is assigned
|
|
to a name.
|
|
|
|
Multiple assignments to the same name can be supported if it can be
|
|
determined that a consistent type and bit width is being used in the
|
|
assignments. This can be done for boolean expressions, numeric
|
|
expressions, and enumeration type literals. In Verilog, the
|
|
corresponding name is mapped to a single bit \code{reg}, an
|
|
\code{integer}, or a \code{reg} with the appropriate width, respectively.
|
|
|
|
In other cases, a single assignment should be used when an object is
|
|
created. Subsequent value changes are then achieved by modification of
|
|
an existing object. This technique should be used for \class{Signal}
|
|
and \class{intbv} objects.
|
|
|
|
\subsubsection{Signal assignment\label{conv-meth-assign-signal}}
|
|
|
|
Signal assignment in \myhdl\ is implemented using attribute assignment
|
|
to attribute \code{next}. Value changes are thus modeled by
|
|
modification of the existing object. The converter investigates the
|
|
\class{Signal} object to infer the type and bit width of the
|
|
corresponding Verilog variable.
|
|
|
|
\subsubsection{\class{intbv} objects\label{conv-meth-assign-intbv}}
|
|
|
|
Type \class{intbv} is likely to be the workhorse for synthesizable
|
|
modeling in \myhdl{}. An \class{intbv} instance behaves like a
|
|
(mutable) integer whose individual bits can be accessed and
|
|
modified. Also, it is possible to constrain its set of values. In
|
|
addition to error checking, this makes it possible to infer a bit
|
|
width, which is required for implementation.
|
|
|
|
In Verilog, an \class{intbv} instance will be mapped to a \code{reg}
|
|
with an appropriate width. As noted before, it is not possible
|
|
to modify its value using name assignment. In the following, we
|
|
will show how it can be done instead. Consider:
|
|
|
|
\begin{verbatim}
|
|
a = intbv(0)[8:]
|
|
\end{verbatim}
|
|
|
|
This is an \class{intbv} object with initial value \code{0} and
|
|
bit width 8. The change its value to \code{5}, we can use
|
|
slice assignment:
|
|
|
|
\begin{verbatim}
|
|
a[8:] = 5
|
|
\end{verbatim}
|
|
|
|
The same can be achieved by leaving the bit width unspecified,
|
|
which has the meaning to change ``all'' bits:
|
|
|
|
\begin{verbatim}
|
|
a[:] = 5
|
|
\end{verbatim}
|
|
|
|
Often the new value will depend on the old one. For example,
|
|
to increment an \class{intbv} with the technique above:
|
|
|
|
\begin{verbatim}
|
|
a[:] = a + 1
|
|
\end{verbatim}
|
|
|
|
Python also provides \emph{augmented} assignment operators,
|
|
which can be used to implement in-place operations. These are supported
|
|
on \class{intbv} objects and by the converter, so that the increment
|
|
can also be done as follows:
|
|
|
|
\begin{verbatim}
|
|
a += 1
|
|
\end{verbatim}
|
|
|
|
\section{Converter usage\label{conv-usage}}
|
|
|
|
We will demonstrate the conversion process by showing some examples.
|
|
|
|
\subsection{A small sequential design\label{conv-usage-seq}}
|
|
|
|
Consider the following MyHDL code for an incrementer module:
|
|
|
|
\begin{verbatim}
|
|
ACTIVE_LOW, INACTIVE_HIGH = 0, 1
|
|
|
|
def inc(count, enable, clock, reset, n):
|
|
|
|
""" Incrementer with enable.
|
|
|
|
count -- output
|
|
enable -- control input, increment when 1
|
|
clock -- clock input
|
|
reset -- asynchronous reset input
|
|
n -- counter max value
|
|
|
|
"""
|
|
|
|
@always(clock.posedge, reset.negedge)
|
|
def incProcess():
|
|
if reset == ACTIVE_LOW:
|
|
count.next = 0
|
|
else:
|
|
if enable:
|
|
count.next = (count + 1) % n
|
|
|
|
return incProcess
|
|
\end{verbatim}
|
|
|
|
In Verilog terminology, function \function{inc} corresponds to a
|
|
module, while the decorated function \function{incProcess}
|
|
roughly corresponds to an always block.
|
|
|
|
Normally, to simulate the design, we would "elaborate" an instance
|
|
as follows:
|
|
|
|
\begin{verbatim}
|
|
m = 8
|
|
n = 2 ** m
|
|
|
|
count = Signal(intbv(0)[m:])
|
|
enable = Signal(bool(0))
|
|
clock, reset = [Signal(bool()) for i in range(2)]
|
|
|
|
inc_inst = inc(count, enable, clock, reset, n=n)
|
|
\end{verbatim}
|
|
|
|
\code{inc_inst} is an elaborated design instance that can be simulated. To
|
|
convert it to Verilog, we change the last line as follows:
|
|
|
|
\begin{verbatim}
|
|
inc_inst = toVerilog(inc, count, enable, clock, reset, n=n)
|
|
\end{verbatim}
|
|
|
|
Again, this creates an instance that can be simulated, but as a side
|
|
effect, it also generates an equivalent Verilog module in file \file{inc.v}.
|
|
The Verilog code looks as follows:
|
|
|
|
\begin{verbatim}
|
|
module inc_inst (
|
|
count,
|
|
enable,
|
|
clock,
|
|
reset
|
|
);
|
|
|
|
output [7:0] count;
|
|
reg [7:0] count;
|
|
input enable;
|
|
input clock;
|
|
input reset;
|
|
|
|
|
|
always @(posedge clock or negedge reset) begin: _MYHDL1_BLOCK
|
|
if ((reset == 0)) begin
|
|
count <= 0;
|
|
end
|
|
else begin
|
|
if (enable) begin
|
|
count <= ((count + 1) % 256);
|
|
end
|
|
end
|
|
end
|
|
|
|
endmodule
|
|
\end{verbatim}
|
|
|
|
You can see the module interface and the always block, as expected
|
|
from the MyHDL design.
|
|
|
|
\subsection{A small combinatorial design\label{conv-usage-comb}}
|
|
|
|
The second example is a small combinatorial design, more
|
|
specifically the binary to Gray code converter from previous chapters:
|
|
|
|
\begin{verbatim}
|
|
def bin2gray(B, G, width):
|
|
|
|
""" Gray encoder.
|
|
|
|
B -- input intbv signal, binary encoded
|
|
G -- output intbv signal, gray encoded
|
|
width -- bit width
|
|
|
|
"""
|
|
|
|
@always_comb
|
|
def logic():
|
|
Bext = intbv(0)[width+1:]
|
|
Bext[:] = B
|
|
for i in range(width):
|
|
G.next[i] = Bext[i+1] ^ Bext[i]
|
|
|
|
return logic
|
|
\end{verbatim}
|
|
|
|
As before, you can create an instance and convert to
|
|
Verilog as follows:
|
|
|
|
\begin{verbatim}
|
|
width = 8
|
|
|
|
B = Signal(intbv(0)[width:])
|
|
G = Signal(intbv(0)[width:])
|
|
|
|
bin2gray_inst = toVerilog(bin2gray, B, G, width)
|
|
\end{verbatim}
|
|
|
|
The generated Verilog code looks as follows:
|
|
|
|
\begin{verbatim}
|
|
module bin2gray (
|
|
B,
|
|
G
|
|
);
|
|
|
|
input [7:0] B;
|
|
output [7:0] G;
|
|
reg [7:0] G;
|
|
|
|
always @(B) begin: _bin2gray_logic
|
|
integer i;
|
|
reg [9-1:0] Bext;
|
|
Bext = 9'h0;
|
|
Bext = B;
|
|
for (i=0; i<8; i=i+1) begin
|
|
G[i] <= (Bext[(i + 1)] ^ Bext[i]);
|
|
end
|
|
end
|
|
|
|
endmodule
|
|
\end{verbatim}
|
|
|
|
\subsection{A hierarchical design\label{conv-usage-hier}}
|
|
The Verilog converter can handle designs with an
|
|
arbitrarily deep hierarchy.
|
|
|
|
For example, suppose we want to design an
|
|
incrementer with Gray code output. Using the
|
|
designs from previous sections, we can proceed
|
|
as follows:
|
|
|
|
\begin{verbatim}
|
|
ACTIVE_LOW, INACTIVE_HIGH = 0, 1
|
|
|
|
def GrayInc(graycnt, enable, clock, reset, width):
|
|
|
|
bincnt = Signal(intbv(0)[width:])
|
|
|
|
inc_1 = inc(bincnt, enable, clock, reset, n=2**width)
|
|
bin2gray_1 = bin2gray(B=bincnt, G=graycnt, width=width)
|
|
|
|
return inc_1, bin2gray_1
|
|
\end{verbatim}
|
|
|
|
According to Gray code properties, only a single bit
|
|
will change in consecutive values. However, as the
|
|
\code{bin2gray} module is combinatorial, the output bits
|
|
may have transient glitches, which may not be desirable.
|
|
To solve this, let's create an additional level of
|
|
hierarchy and add an output register to the design.
|
|
(This will create an additional latency of a clock
|
|
cycle, which may not be acceptable, but we will
|
|
ignore that here.)
|
|
|
|
\begin{verbatim}
|
|
|
|
|
|
def GrayIncReg(graycnt, enable, clock, reset, width):
|
|
|
|
graycnt_comb = Signal(intbv(0)[width:])
|
|
|
|
gray_inc_1 = GrayInc(graycnt_comb, enable, clock, reset, width)
|
|
|
|
@always(clock.posedge)
|
|
def reg_1():
|
|
graycnt.next = graycnt_comb
|
|
|
|
return gray_inc_1, reg_1
|
|
\end{verbatim}
|
|
|
|
We can convert this hierarchical design as before:
|
|
|
|
\begin{verbatim}
|
|
width = 8
|
|
graycnt = Signal(intbv()[width:])
|
|
enable, clock, reset = [Signal(bool()) for i in range(3)]
|
|
|
|
gray_inc_reg_1 = toVerilog(GrayIncReg, graycnt, enable, clock, reset, width)
|
|
\end{verbatim}
|
|
|
|
The Verilog output code looks as follows:
|
|
|
|
\begin{verbatim}
|
|
module GrayIncReg (
|
|
graycnt,
|
|
enable,
|
|
clock,
|
|
reset
|
|
);
|
|
|
|
output [7:0] graycnt;
|
|
reg [7:0] graycnt;
|
|
input enable;
|
|
input clock;
|
|
input reset;
|
|
|
|
reg [7:0] graycnt_comb;
|
|
reg [7:0] _gray_inc_1_bincnt;
|
|
|
|
|
|
always @(posedge clock or negedge reset) begin: _GrayIncReg_gray_inc_1_inc_1_incProcess
|
|
if ((reset == 0)) begin
|
|
_gray_inc_1_bincnt <= 0;
|
|
end
|
|
else begin
|
|
if (enable) begin
|
|
_gray_inc_1_bincnt <= ((_gray_inc_1_bincnt + 1) % 256);
|
|
end
|
|
end
|
|
end
|
|
|
|
always @(_gray_inc_1_bincnt) begin: _GrayIncReg_gray_inc_1_bin2gray_1_logic
|
|
integer i;
|
|
reg [9-1:0] Bext;
|
|
Bext = 9'h0;
|
|
Bext = _gray_inc_1_bincnt;
|
|
for (i=0; i<8; i=i+1) begin
|
|
graycnt_comb[i] <= (Bext[(i + 1)] ^ Bext[i]);
|
|
end
|
|
end
|
|
|
|
always @(posedge clock) begin: _GrayIncReg_reg_1
|
|
graycnt <= graycnt_comb;
|
|
end
|
|
|
|
endmodule
|
|
\end{verbatim}
|
|
|
|
Note that the output is a flat ``net list of blocks'', and
|
|
that hierarchical signal names are generated as necessary.
|
|
|
|
\subsection{Optimizations for finite state machines\label{conv-usage-fsm}}
|
|
As often in hardware design, finite state machines deserve special attention.
|
|
|
|
In Verilog and VHDL, finite state machines are typically described
|
|
using case statements. Python doesn't have a case statement, but the
|
|
converter recognizes particular if-then-else structures and maps them
|
|
to case statements. This optimization occurs when a variable whose
|
|
type is an enumerated type is sequentially tested against enumeration
|
|
items in an if-then-else structure. Also, the appropriate synthesis
|
|
pragmas for efficient synthesis are generated in the Verilog code.
|
|
|
|
As a further optimization, function \function{enum} was enhanced to support
|
|
alternative encoding schemes elegantly, using an additional parameter
|
|
\var{encoding}. For example:
|
|
|
|
\begin{verbatim}
|
|
t_State = enum('SEARCH', 'CONFIRM', 'SYNC', encoding='one_hot')
|
|
\end{verbatim}
|
|
|
|
The default encoding is \code{'binary'}; the other possibilities are
|
|
\code{'one_hot'} and \code{'one_cold'}. This parameter only affects
|
|
the conversion output, not the behavior of the type. The generated
|
|
Verilog code for case statements is optimized for an efficient
|
|
implementation according to the encoding. Note that in contrast, a
|
|
Verilog designer has to make nontrivial code changes to implement a
|
|
different encoding scheme.
|
|
|
|
As an example, consider the following finite state machine, whose
|
|
state variable uses the enumeration type defined above:
|
|
|
|
\begin{verbatim}
|
|
ACTIVE_LOW = 0
|
|
FRAME_SIZE = 8
|
|
|
|
def FramerCtrl(SOF, state, syncFlag, clk, reset_n, t_State):
|
|
|
|
""" Framing control FSM.
|
|
|
|
SOF -- start-of-frame output bit
|
|
state -- FramerState output
|
|
syncFlag -- sync pattern found indication input
|
|
clk -- clock input
|
|
reset_n -- active low reset
|
|
|
|
"""
|
|
|
|
index = Signal(intbv(0)[8:]) # position in frame
|
|
|
|
@always(clk.posedge, reset_n.negedge)
|
|
def FSM():
|
|
if reset_n == ACTIVE_LOW:
|
|
SOF.next = 0
|
|
index.next = 0
|
|
state.next = t_State.SEARCH
|
|
else:
|
|
index.next = (index + 1) % FRAME_SIZE
|
|
SOF.next = 0
|
|
if state == t_State.SEARCH:
|
|
index.next = 1
|
|
if syncFlag:
|
|
state.next = t_State.CONFIRM
|
|
elif state == t_State.CONFIRM:
|
|
if index == 0:
|
|
if syncFlag:
|
|
state.next = t_State.SYNC
|
|
else:
|
|
state.next = t_State.SEARCH
|
|
elif state == t_State.SYNC:
|
|
if index == 0:
|
|
if not syncFlag:
|
|
state.next = t_State.SEARCH
|
|
SOF.next = (index == FRAME_SIZE-1)
|
|
else:
|
|
raise ValueError("Undefined state")
|
|
|
|
return FSM
|
|
\end{verbatim}
|
|
|
|
The conversion is done as before:
|
|
|
|
\begin{verbatim}
|
|
SOF = Signal(bool(0))
|
|
syncFlag = Signal(bool(0))
|
|
clk = Signal(bool(0))
|
|
reset_n = Signal(bool(1))
|
|
state = Signal(t_State.SEARCH)
|
|
framerctrl_inst = toVerilog(FramerCtrl, SOF, state, syncFlag, clk, reset_n)
|
|
\end{verbatim}
|
|
|
|
The Verilog output looks as follows:
|
|
|
|
\begin{verbatim}
|
|
module FramerCtrl (
|
|
SOF,
|
|
state,
|
|
syncFlag,
|
|
clk,
|
|
reset_n
|
|
);
|
|
|
|
output SOF;
|
|
reg SOF;
|
|
output [2:0] state;
|
|
reg [2:0] state;
|
|
input syncFlag;
|
|
input clk;
|
|
input reset_n;
|
|
|
|
reg [7:0] index;
|
|
|
|
|
|
always @(posedge clk or negedge reset_n) begin: _FramerCtrl_FSM
|
|
if ((reset_n == 0)) begin
|
|
SOF <= 0;
|
|
index <= 0;
|
|
state <= 3'b001;
|
|
end
|
|
else begin
|
|
index <= ((index + 1) % 8);
|
|
SOF <= 0;
|
|
// synthesis parallel_case full_case
|
|
casez (state)
|
|
3'b??1: begin
|
|
index <= 1;
|
|
if (syncFlag) begin
|
|
state <= 3'b010;
|
|
end
|
|
end
|
|
3'b?1?: begin
|
|
if ((index == 0)) begin
|
|
if (syncFlag) begin
|
|
state <= 3'b100;
|
|
end
|
|
else begin
|
|
state <= 3'b001;
|
|
end
|
|
end
|
|
end
|
|
3'b1??: begin
|
|
if ((index == 0)) begin
|
|
if ((!syncFlag)) begin
|
|
state <= 3'b001;
|
|
end
|
|
end
|
|
SOF <= (index == (8 - 1));
|
|
end
|
|
default: begin
|
|
$display("ValueError(Undefined state)");
|
|
$finish;
|
|
end
|
|
endcase
|
|
end
|
|
end
|
|
|
|
endmodule
|
|
\end{verbatim}
|
|
|
|
\subsection{RAM inference \label{conf-usage-RAM}}
|
|
|
|
Certain synthesis tools can map Verilog memories to RAM
|
|
structures. To support this interesting feature, the Verilog converter
|
|
maps lists of signals in MyHDL to Verilog memories.
|
|
|
|
The following MyHDL example is a ram model that uses a list of signals
|
|
to model the internal memory.
|
|
|
|
\begin{verbatim}
|
|
def RAM(dout, din, addr, we, clk, depth=128):
|
|
""" Ram model """
|
|
|
|
mem = [Signal(intbv(0)[8:]) for i in range(depth)]
|
|
|
|
@always(clk.posedge)
|
|
def write():
|
|
if we:
|
|
mem[int(addr)].next = din
|
|
|
|
@always_comb
|
|
def read():
|
|
dout.next = mem[int(addr)]
|
|
|
|
return write, read
|
|
\end{verbatim}
|
|
|
|
With the appropriate signal definitions for the interface ports, it is
|
|
converted to the following Verilog code. Note how the
|
|
list of signals \code{mem} is mapped to a Verilog memory.
|
|
|
|
\begin{verbatim}
|
|
module RAM (
|
|
dout,
|
|
din,
|
|
addr,
|
|
we,
|
|
clk
|
|
);
|
|
|
|
output [7:0] dout;
|
|
wire [7:0] dout;
|
|
input [7:0] din;
|
|
input [6:0] addr;
|
|
input we;
|
|
input clk;
|
|
|
|
reg [7:0] mem [0:128-1];
|
|
|
|
always @(posedge clk) begin: _RAM_write
|
|
if (we) begin
|
|
mem[addr] <= din;
|
|
end
|
|
end
|
|
|
|
assign dout = mem[addr];
|
|
|
|
endmodule
|
|
\end{verbatim}
|
|
|
|
|
|
\subsection{ROM inference \label{conf-usage-ROM}}
|
|
Some synthesis tools can infer a ROM memory from a case statement. The
|
|
Verilog converter can perform the expansion into a case statement
|
|
automatically, based on a higher level description. The ROM access is
|
|
described in a single line, by indexing into a tuple of integers. The
|
|
tuple can be described manually, but also by programmatical
|
|
means. Note that a tuple is used instead of a list to stress the
|
|
read-only character of the memory.
|
|
|
|
The following example illustrates this functionality. ROM access
|
|
is described as follows:
|
|
|
|
\begin{verbatim}
|
|
def rom(dout, addr, CONTENT):
|
|
|
|
@always_comb
|
|
def read():
|
|
dout.next = CONTENT[int(addr)]
|
|
|
|
return read
|
|
\end{verbatim}
|
|
|
|
The ROM content is described as a tuple of integers. When the
|
|
ROM content is defined, the conversion can be performed:
|
|
|
|
\begin{verbatim}
|
|
CONTENT = (17, 134, 52, 9)
|
|
dout = Signal(intbv(0)[8:])
|
|
addr = Signal(intbv(0)[4:])
|
|
|
|
toVerilog(rom, dout, addr, CONTENT)
|
|
\end{verbatim}
|
|
|
|
The Verilog output code is as follows:
|
|
|
|
\begin{verbatim}
|
|
module rom (
|
|
dout,
|
|
addr
|
|
);
|
|
|
|
output [7:0] dout;
|
|
reg [7:0] dout;
|
|
input [3:0] addr;
|
|
|
|
always @(addr) begin: _rom_read
|
|
// synthesis parallel_case full_case
|
|
case (addr)
|
|
0: dout <= 17;
|
|
1: dout <= 134;
|
|
2: dout <= 52;
|
|
default: dout <= 9;
|
|
endcase
|
|
end
|
|
|
|
endmodule
|
|
\end{verbatim}
|
|
|
|
\subsection{User-defined Verilog code \label{conf-usage-custom}}
|
|
|
|
MyHDL provides a way to include user-defined Verilog
|
|
code during the conversion process.
|
|
|
|
MyHDL defines a hook that is understood by the converter but ignored by
|
|
the simulator. The hook is called \code{__verilog__}. It operates
|
|
like a special return value. When a MyHDL function defines
|
|
\code{__verilog__}, the Verilog converter will use its value instead of the
|
|
regular return value.
|
|
|
|
The value of \code{__verilog__} should be a format string that uses keys in
|
|
its format specifiers. The keys refer to the variable names in the
|
|
context of the string.
|
|
|
|
Example:
|
|
|
|
\begin{verbatim}
|
|
def inc_comb(nextCount, count, n):
|
|
|
|
@always_comb
|
|
def logic():
|
|
# note: '-' instead of '+'
|
|
nextCount.next = (count - 1) % n
|
|
|
|
nextCount.driven = "wire"
|
|
|
|
__verilog__ =\
|
|
"""
|
|
assign %(nextCount)s = (%(count)s + 1) %% %(n)s;
|
|
"""
|
|
|
|
return logic
|
|
\end{verbatim}
|
|
|
|
The converted code looks as follows:
|
|
|
|
\begin{verbatim}
|
|
module inc_comb (
|
|
nextCount,
|
|
count
|
|
);
|
|
|
|
output [7:0] nextCount;
|
|
wire [7:0] nextCount;
|
|
input [7:0] count;
|
|
|
|
assign nextCount = (count + 1) % 128;
|
|
|
|
endmodule
|
|
\end{verbatim}
|
|
|
|
In this example, conversion of the \function{inc_comb} function is bypassed and
|
|
the user-defined Verilog code is inserted instead. Note that the
|
|
user-defined code refers to signals and parameters in the MyHDL
|
|
context by using format specifiers. During conversion, the appropriate
|
|
hierarchical names and parameter values will be filled in. Note also
|
|
that the format specifier indicator \% needs to be escaped (by doubling
|
|
it) if it is required in the user-defined code.
|
|
|
|
There is one more issue that needs user attention. Normally, the
|
|
Verilog converter infers inputs, internal signals, and outputs. It
|
|
also detects undriven and multiple driven signals. To do this, it
|
|
assumes that signals are not driven by default. It then processes the
|
|
code to find out which signals are driven from where. However, it
|
|
cannot do this for user-defined code. Without additional help, this
|
|
will result in warnings or errors during the inference process, or in
|
|
compilation errors from invalid Verilog code. The user should solve
|
|
this by setting the \code{driven} attribute for signals that are driven from
|
|
the user-defined code. In the example code above, note the following
|
|
assignment:
|
|
|
|
\begin{verbatim}
|
|
nextCount.driven = "wire"
|
|
\end{verbatim}
|
|
|
|
This specifies that the nextCount signal is driven as a Verilog wire
|
|
from this module. The allowed values of the driven attribute are
|
|
\code{'wire'} and \code{'reg'}. The value specifies how the
|
|
user-defined Verilog code drives the signal in Verilog. To decide
|
|
which value to use, consider how the signal should be declared in
|
|
Verilog after the user-defined code is inserted.
|
|
|
|
|
|
\section{Known issues\label{conv-issues}}
|
|
\begin{description}
|
|
\item[Verilog integers are 32 bit wide]
|
|
Usually, Verilog integers are 32 bit wide. In contrast, Python is
|
|
moving toward integers with undefined width. Python \class{int}
|
|
and \class{long} variables are mapped to Verilog integers; so for values
|
|
wider than 32 bit this mapping is incorrect.
|
|
|
|
\item[Synthesis pragmas are specified as Verilog comments.] The recommended
|
|
way to specify synthesis pragmas in Verilog is through attribute
|
|
lists. However, the Icarus simulator doesn't support them
|
|
for \code{case} statements (to specify \code{parallel_case} and
|
|
\code{full_case} pragmas). Therefore, the old
|
|
but deprecated method of synthesis pragmas in Verilog comments
|
|
is still used.
|
|
|
|
\item[Inconsistent place of the sensitivity list inferred from \code{always_comb}.]
|
|
The semantics of \code{always_comb}, both in Verilog and \myhdl{}, is to
|
|
have an implicit sensitivity list at the end of the code. However, this
|
|
may not be synthesizable. Therefore, the inferred sensitivity list is
|
|
put at the top of the corresponding \code{always} block.
|
|
This may cause inconsistent behavior at the start of the
|
|
simulation. The workaround is to create events at time 0.
|
|
|
|
\item[Non-blocking assignments to task arguments don't work.]
|
|
Non-blocking (signal) assignments to task arguments don't work
|
|
for an as yet unknown reason.
|
|
\end{description}
|