1
0
mirror of https://github.com/myhdl/myhdl.git synced 2024-12-14 07:44:38 +08:00
myhdl/doc/informal.tex
2003-05-16 08:05:25 +00:00

729 lines
21 KiB
TeX

\chapter{Introduction to \myhdl\ }
\section{A basic \myhdl\ simulation}
We will introduce \myhdl\ with a classic \code{Hello World} style
example. Here are the contents of a \myhdl\ simulation script called
\file{hello1.py}:
\begin{verbatim}
from myhdl import delay, now, Simulation
def sayHello():
while 1:
yield delay(10)
print "%s Hello World!" % now()
gen = sayHello()
sim = Simulation(gen)
sim.run(30)
\end{verbatim}
All example code can be found in the distribution directory under
\file{example/manual}. When we run this simulation, we get the
following output:
\begin{verbatim}
% python hello1.py
10 Hello World!
20 Hello World!
30 Hello World!
StopSimulation: Simulated for duration 30
\end{verbatim}
The first line of the script imports a
number of objects from the \code{myhdl} package. In good Python style, and
unlike most other languages, we can only use identifiers that are
\emph{literally} defined in the source file \footnote{I don't want to
explain the \samp{import *} syntax}.
Next, we define a generator function called
\code{sayHello}. This is a generator function (as opposed to
a classic Python function) because it contains a \keyword{yield}
statement (instead of \keyword{return} statement). In \myhdl{}, a
\keyword{yield} statement has a similar purpose as a \keyword{wait}
statement in VHDL: the statement suspends execution of the function,
while its clauses specify when the function should resume. In this case,
there is a \code{delay} clause, that specifies the required delay.
To make sure that the generator runs ``forever'', we wrap its behavior
in a \code{while 1} loop. This is a standard Python idiom, and it is
the \myhdl\ equivalent of the implicit looping behavior of a Verilog
\keyword{always} block and a VHDL \keyword{process}.
In \myhdl{}, the basic simulation objects are generators. Generators
are created by calling generator functions. For example, variable
\code{gen} refers to a generator. To simulate this generator, we pass
it as an argument to a \class{Simulation} object constructor. We then
run the simulation for the desired amount of time. In \myhdl{}, time
is modeled as a natural integer.
\section{Concurrent generators and signals}
In the previous section, we simulated a single generator. Of course,
real hardware descriptions are not like that: in fact, they are
typically massively concurrent. \myhdl\ supports this by allowing an
arbitrary number of concurrent generators. More specifically, a
\class{Simulation} constructor can take an arbitrary number of
arguments, each of which can be a generator or a nested sequence of
generators.
With concurrency comes the problem of deterministic
communication. Hardware languages use special objects to
support deterministic communication between concurrent code. \myhdl\
has a \class{Signal} object which is roughly modeled after VHDL
signals.
We will demonstrate these concepts by extending and modifying our
first example. We introduce a clock signal, driven by a second
generator:
\begin{verbatim}
clk = Signal(0)
def clkGen():
while 1:
yield delay(10)
clk.next = 1
yield delay(10)
clk.next = 0
\end{verbatim}
The \code{clk} signal is constructed with an initial value
\code{0}. In the clock generator function \code{clkGen}, it is
continuously assigned a new value after a certain delay. In \myhdl{},
the new value of a signal is specified by assigning to its
\code{next} attribute. This is the \myhdl\ equivalent of VHDL signal
assignments and Verilog's nonblocking assignments.
The \code{sayHello} generator function is modified to wait for a
rising edge of the clock instead of a delay:
\begin{verbatim}
def sayHello():
while 1:
yield posedge(clk)
print "%s Hello World!" % now()
\end{verbatim}
Waiting for a clock edge is achieved with a second form of the
\keyword{yield} statement: \samp{yield posedge(\var{signal})}.
A \class{Simulation} object will suspend the generator as that point,
and resume it when there is a rising edge on the signal.
The \class{Simulation} is now constructed with 2 generator arguments:
\begin{verbatim}
sim = Simulation(clkGen(), sayHello())
sim.run(50)
\end{verbatim}
When we run this simulation, we get:
\begin{verbatim}
% python hello2.py
10 Hello World!
30 Hello World!
50 Hello World!
StopSimulation: Simulated for duration 50
\end{verbatim}
\section{Parameters, instantiations and hierarchy}
So far, the generator function examples had no parameters. For
example, the \code{clk} signal was defined in the enclosing scope of
the generator functions. To make code reusable we will
want to pass arguments through a parameter list. For example, we can
change the clock generator function to make it more general
and reusable, as follows:
\begin{verbatim}
def clkGen(clock, period=20):
lowTime = int(period / 2)
highTime = period - lowTime
while 1:
yield delay(lowTime)
clock.next = 1
yield delay(highTime)
clock.next = 0
\end{verbatim}
The clock signal is now a parameter of the function. Also, the clock
\var{period} is a parameter with a default value of \code{20}.
This makes \var{period} an \dfn{optional} parameter; if it is not
specified in a call, the default value will be used.
Similarly, the \code{sayHello} function can be made more general as follows:
\begin{verbatim}
def sayHello(clock, to="World!"):
while 1:
yield posedge(clock)
print "%s Hello %s" % (now(), to)
\end{verbatim}
We can create any number of generators by calling generator functions
with the appropriate parameters. This is very similar to the concept of
\dfn{instantiation} in hardware description languages and we will use
the same terminology in \myhdl{}. Hierarchy can be modeled by defining
the instances in a higher-level function, and returning them. For
example:
\begin{verbatim}
def greetings():
clk1 = Signal(0)
clk2 = Signal(0)
clkGen1 = clkGen(clk1)
clkGen2 = clkGen(clock=clk2, period=19)
sayHello1 = sayHello(clock=clk1)
sayHello2 = sayHello(to="MyHDL", clock=clk2)
return clkGen1, clkGen2, sayHello1, sayHello2
\end{verbatim}
As in standard Python, positional or named parameter association can
be used in instantiations, or a mix of both\footnote{All positional
parameters have to go before any named parameter.}. All these styles
are demonstrated in the example above. As in hardware description
languages, named association can be very useful if there are a lot of
parameters, as the parameter order in the call does not matter in that
case.
To support hierarchy, \class{Simulation} constructor arguments can be
sequences of generators. More specifically, the return value of a
higher-level instantiating function can be used as an argument of the
constructor. For example:
\begin{verbatim}
sim = Simulation(greetings())
sim.run(50)
\end{verbatim}
This produces the following output:
\begin{verbatim}
% python greetings.py
9 Hello MyHDL
10 Hello World!
28 Hello MyHDL
30 Hello World!
47 Hello MyHDL
50 Hello World!
StopSimulation: Simulated for duration 50
\end{verbatim}
\section{Bit-oriented operations}
Hardware design involves dealing with bits and bit-oriented
operations. The standard Python type \class{int} has most of the
desired features, but lacks support for indexing and slicing. For this
reason, \myhdl\ provides the \class{intbv} class. The name was chosen
to suggest an integer with bit vector flavor.
Class \class{intbv} works transparently as an integer and with other
integer-like types. Like class \class{int}, it provides access to the
underlying two's complement representation for bitwise
operations. In addition, it is a mutable type that provides indexing
and slicing operations, and some additional bit-oriented support such
as concatenation.
\subsection{Indexing operations}
\label{gray}
As an example, we will consider the design of a Gray encoder. The
following code is a Gray encoder modeled in \myhdl{}:
\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}
This code introduces a few new concepts. The string in triple quotes
at the start of the function is a \dfn{doc string}. This is standard
Python practice for structured documentation of code. Moreover, we
use a third form of the \keyword{yield} statement:
\samp{yield \var{signal}}. This specifies that the generator should
resume whenever \var{signal} changes value. This is typically used to
describe combinatorial logic.
Finally, the code contains bit indexing operations and an exclusive-or
operator as required for a Gray encoder. By convention, the lsb of an
\class{intbv} object has index~\code{0}.
To verify the Gray encoder, we write a test bench that prints input
and output for all possible input values:
\begin{verbatim}
def testBench(width):
B = Signal(intbv(0))
G = Signal(intbv(0))
dut = bin2gray(B, G, width)
def stimulus():
for i in range(2**width):
B.next = intbv(i)
yield delay(10)
print "B: " + bin(B, width) + "| G: " + bin(G, width)
return (dut, stimulus())
\end{verbatim}
We use the conversion function \code{bin} to get a binary
string representation of the signal values. This function is exported
by the \code{myhdl} package and complements the standard Python
\code{hex} and \code{oct} conversion functions.
To demonstrate, we set up a simulation for a small width:
\begin{verbatim}
Simulation(testBench(width=3)).run()
\end{verbatim}
The simulation produces the following output:
\begin{verbatim}
% python bin2gray.py
B: 000 | G: 000
B: 001 | G: 001
B: 010 | G: 011
B: 011 | G: 010
B: 100 | G: 110
B: 101 | G: 111
B: 110 | G: 101
B: 111 | G: 100
StopSimulation: No more events
\end{verbatim}
\subsection{Slicing operations}
For a change, we will use a plain function as an example to illustrate
slicing. The following function calculates the HEC byte of an ATM
header.
\begin{verbatim}
from myhdl import intbv
concat = intbv.concat # shorthand alias
COSET = 0x55
def calculateHec(header):
""" Return hec for an ATM header, represented as an intbv.
The hec polynomial is 1 + x + x**2 + x**8.
"""
hec = intbv(0)
for bit in header[32:]:
hec[8:] = concat(hec[7:2],
bit ^ hec[1] ^ hec[7],
bit ^ hec[0] ^ hec[7],
bit ^ hec[7]
)
return hec ^ COSET
\end{verbatim}
The code shows how slicing access and assignment is supported on the
\class{intbv} data type. In accordance with the most common hardware
convention, and unlike standard Python, slicing ranges are
downward. The code also demonstrates concatenation of \class{intbv}
objects.
As in standard Python, the slicing range is half-open: the highest
index bit is not included. Unlike standard Python however, this index
corresponds to the \emph{leftmost} item. The rightmost index is
\code{0} by default, and can be omitted from the slice.
The half-openness of a slice may seem awkward at first, but it helps
to avoid one-off count issues in practice. For example, the slice
\code{hex[8:]} has exactly \code{8} bits. Likewise, the slice
\code{hex[7:2]} has \code{7-2=5} bits. You can think about it as
follows: for a slice \code{[i:j]}, only bits below index \code{i} are
included, and the bit with index \code{j} is the last bit included.
\section{Bus-functional procedures}
A \dfn{bus-functional procedure} is a reusable encapsulation of the
low-level operations needed to implement some abstract transaction on
a physical interface. Bus-functional procedures are typically used in
flexible verification environments.
Once again, \myhdl\ supports bus-functional procedures by generator
functions. In \myhdl\, the difference between instances and
bus-functional procedure calls comes from the way in which a generator
function is used.
As an example, we will design a bus-functional procedure of a
simplified UART transmitter. We assume 8 data bits, no parity bit, and
a single stop bit, and we add print statements to follow the
simulation behavior:
\begin{verbatim}
T_9600 = int(1e9 / 9600)
def rs232_tx(tx, data, duration=T_9600):
""" Simple rs232 transmitter procedure.
tx -- serial output data
data -- input data byte to be transmitted
duration -- transmit bit duration
"""
print "-- Transmitting %s --" % hex(data)
print "TX: start bit"
tx.next = 0
yield delay(duration)
for i in range(8):
print "TX: %s" % data[i]
tx.next = data[i]
yield delay(duration)
print "TX: stop bit"
tx.next = 1
yield delay(duration)
\end{verbatim}
This looks exactly like the generator functions in previous sections. It
becomes a bus functional procedure when we use it differently. Suppose
that in a test bench, we want to generate a number of data bytes to be
transmitted. This can be modeled as follows:
\begin{verbatim}
testvals = (0xc5, 0x3a, 0x4b)
def stimulus():
tx = Signal(1)
for val in testvals:
txData = intbv(val)
yield rs232_tx(tx, txData)
\end{verbatim}
We use the bus functional procedure call as a clause in a
\code{yield} statement. This introduces a fourth form of the
\code{yield} statement: using a generator as a clause. Although this is
a more dynamic usage than in the previous cases, the meaning is
actually very similar. When the calling generator \code{stimulus()}
encounters the \code{yield} statement, it suspends execution, and the
clause specifies when it should resume. In this case, the generator
\code{rs232_tx(tx, txData)} is \dfn{forked}, and the caller resumes
when the forked generator returns.
When simulating this, we get:
\begin{verbatim}
-- Transmitting 0xc5 --
TX: start bit
TX: 1
TX: 0
TX: 1
TX: 0
TX: 0
TX: 0
TX: 1
TX: 1
TX: stop bit
-- Transmitting 0x3a --
TX: start bit
TX: 0
TX: 1
TX: 0
TX: 1
...
\end{verbatim}
We will continue with this example by designing the corresponding UART
receiver bus-functional procedure. This will allow us to introduce
further capabilities of \myhdl\ and its use of the \code{yield}
statement.
Until now, the \code{yield} statements had a single clause. However,
they can have multiple clauses as well. In that case, the calling
generator is triggered as soon as the condition corresponding to one
of the clauses is satisfied. This corresponds to the functionality of
sensitivity lists in Verilog and VHDL.
For example, suppose we want to design an UART receive procedure with
a timeout. We can specify the timeout condition while waiting for the
start bit, as in the following generator function:
\begin{verbatim}
def rs232_rx(rx, data, duration=T_9600, timeout=MAX_TIMEOUT):
""" Simple rs232 receiver procedure.
rx -- serial input data
data -- data received
duration -- receive bit duration
"""
# wait on start bit until timeout
yield negedge(rx), delay(timeout)
if rx == 1:
raise StopSimulation, "RX time out error"
# sample in the middle of the bit duration
yield delay(duration // 2)
print "RX: start bit"
for i in range(8):
yield delay(duration)
print "RX: %s" % rx
data[i] = rx
yield delay(duration)
print "RX: stop bit"
print "-- Received %s --" % hex(data)
\end{verbatim}
If the timeout condition is triggered, the receive bit \code{rx}
will still be \code{1}. In that case, we raise an exception to stop
the simulation. The \code{StopSimulation} exception is predefined in
\myhdl\ for such purposes. In the other case, we proceed by
positioning the sample point in the middle of the bit duration, and
sampling the received data bits.
When a \code{yield} statement has multiple clauses, they can be of any
type that is supported as a single clause, including generators. For
example, we can verify the transmitter and receiver generator against
each other by yielding them together, as follows:
\begin{verbatim}
def test():
tx = Signal(1)
rx = tx
rxData = intbv(0)
for val in testvals:
txData = intbv(val)
yield rs232_rx(rx, rxData), rs232_tx(tx, txData)
\end{verbatim}
Both forked generators will run concurrently, and the calling
generator will resume as soon as one of them finishes (which will be
the transmitter in this case). The simulation output shows how
the UART procedures run in lockstep:
\begin{verbatim}
-- Transmitting 0xc5 --
TX: start bit
RX: start bit
TX: 1
RX: 1
TX: 0
RX: 0
TX: 1
RX: 1
TX: 0
RX: 0
TX: 0
RX: 0
TX: 0
RX: 0
TX: 1
RX: 1
TX: 1
RX: 1
TX: stop bit
RX: stop bit
-- Received 0xc5 --
-- Transmitting 0x3a --
TX: start bit
RX: start bit
TX: 0
RX: 0
...
\end{verbatim}
For completeness, we will verify the timeout behavior with a test
bench that disconnects the \code{rx} from the \code{tx} signal, and we
specify a small timeout for the receive procedure:
\begin{verbatim}
def testTimeout():
tx = Signal(1)
rx = Signal(1)
rxData = intbv(0)
for val in testvals:
txData = intbv(val)
yield rs232_rx(rx, rxData, timeout=4*T_9600-1), rs232_tx(tx, txData)
\end{verbatim}
The simulation now stops with a timeout exception after a few
transmit cycles:
\begin{verbatim}
-- Transmitting 0xc5 --
TX: start bit
TX: 1
TX: 0
TX: 1
StopSimulation: RX time out error
\end{verbatim}
Recall that the calling generator resumes as soon as one of the
forked generators returns. In the previous cases, this is just fine,
as the transmitter and receiver generators run in lockstep. However,
it may be desirable to resume the caller only when \emph{all} of the
forked generators have finished. For example, suppose that we want to
characterize the robustness of the transmitter and receiver design to
bit duration differences. We can adapt our test bench as follows, to
run the transmitter at a faster rate:
\begin{verbatim}
T_10200 = int(1e9 / 10200)
def testNoJoin():
tx = Signal(1)
rx = tx
rxData = intbv(0)
for val in testvals:
txData = intbv(val)
yield rs232_rx(rx, rxData), rs232_tx(tx, txData, duration=T_10200)
\end{verbatim}
Simulating this shows how the transmission of the new byte starts
before the previous one is received, potentially creating additional
transmission errors:
\begin{verbatim}
-- Transmitting 0xc5 --
TX: start bit
RX: start bit
...
TX: 1
RX: 1
TX: 1
TX: stop bit
RX: 1
-- Transmitting 0x3a --
TX: start bit
RX: stop bit
-- Received 0xc5 --
RX: start bit
TX: 0
\end{verbatim}
It is more likely that we want to characterize the design on a byte
by byte basis, and align the two generators before transmitting each
byte. In \myhdl{}, this is done with the \function{join} function. By
joining clauses together in a \code{yield} statement, we create a new
clause that triggers only when all of its clause arguments have
triggered. For example, we can adapt the test bench as follows:
\begin{verbatim}
def testJoin():
tx = Signal(1)
rx = tx
rxData = intbv(0)
for val in testvals:
txData = intbv(val)
yield join(rs232_rx(rx, rxData), rs232_tx(tx, txData, duration=T_10200))
\end{verbatim}
Now, transmission of a new byte only starts when the previous one is received:
\begin{verbatim}
-- Transmitting 0xc5 --
TX: start bit
RX: start bit
...
TX: 1
RX: 1
TX: 1
TX: stop bit
RX: 1
RX: stop bit
-- Received 0xc5 --
-- Transmitting 0x3a --
TX: start bit
RX: start bit
TX: 0
RX: 0
\end{verbatim}
\section{\myhdl\ and Python}
To conclude this introductory chapter, it is useful to stress that
\myhdl\ is not a language in itself. The language is Python, and \myhdl\ is
implemented as a Python package called \code{myhdl}. Moreover, it is
a design goal to keep the \code{myhdl} package as minimalistic as
possible, so that \myhdl\ descriptions are very much ``pure Python''.
To have Python as the underlying language is significant in several
ways:
\begin{itemize}
\item Python is a very powerful high level language. This translates
into high productivity and elegant solutions to complex problems.
\item Python is continuously improved by some very clever
minds, supported by a large and fast growing user base. Python profits
fully from the open source development model.
\item Python comes with an extensive standard library. Some
functionality is likely to be of direct interest to \myhdl\ users:
examples include string handling, regular expressions, random number
generation, unit test support, operating system interfacing and GUI
development. In addition, there are modules for mathematics, database
connections, networking programming, internet data handling, and so
on.
\item Python has a powerful C extension model. All built-in types are
written with the same C API that is available for custom
extensions. To a module user, there is no difference between a
standard Python module and a C extension module --- except
performance. The typical Python development model is to prototype
everything in Python until the application is stable, and (only) then
rewrite performance critical modules in C if necessary.
\end{itemize}