1
0
mirror of https://github.com/myhdl/myhdl.git synced 2025-01-24 21:52:56 +08:00
myhdl/doc/manual/informal.tex
2003-08-29 12:35:58 +00:00

748 lines
22 KiB
TeX

\chapter{Introduction to \myhdl\ \label{intro}}
\section{A basic \myhdl\ simulation \label{intro-basic}}
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{The exception is the \samp{from module import *} syntax,
that imports all the symbols from a module. Although this is generally
considered bad practice, it can be tolerated for large modules that
export a lot of symbols. One may argue that
\code{myhdl} falls into that category.}.
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).
When called, a generator function returns a \dfn{generator},
which is the basic simulation object in \myhdl{}.
The \keyword{yield} statement in \myhdl{} has a similar meaning as
the \keyword{wait} statement in VHDL: the statement suspends execution
of a generator, and its clauses specify the conditions on which the
generator should wait before resuming. In this case, it should
\index{wait!for a delay}%
wait for a delay.
To make sure that a 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
\index{Verilog!always block}%
Verilog \keyword{always} block
\index{VHDL!process}%
and a VHDL \keyword{process}.
In the example, variable
\code{gen} refers to a generator. To simulate it, 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 \label{intro-conc}}
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.
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
\index{VHDL!signal assignment}%
the VHDL signal assignment and the
\index{Verilog!non-blocking assignment}%
Verilog non-blocking assignment.
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 the clock edge is achieved with a second form of the
\keyword{yield} statement: \samp{yield posedge(\var{signal})}.
At that point, the generator will
\index{wait!for a rising edge}%
wait for 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, instances and hierarchy \label{intro-hier}}
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 instantiation in hardware description languages and we will use
the same terminology in \myhdl{}. A \myhdl\ \dfn{instance} is
\index{instance!defined}%
recursively defined as being either a sequence of instances, or a
generator. Hierarchy can be modeled by defining the instances in a
higher-level function, and returning them.
A \class{Simulation} constructor takes an arbitrary number of
instances as its arguments.
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
sim = Simulation(greetings())
sim.run(50)
\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.
The simulation 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}
\begin{notice}[warning]
Some commonly used terminology has different meanings
in Python versus hardware design. Rather than artificially
changing terminology, I think it's best to keep it
and explicitly describing the differences.
A \dfn{module} in Python refers to all source code
in a particular file. A module can be reused by
other modules by importing. In hardware design,
\index{module!in Python versus hardware design}%
a module is a reusable block of hardware with
a well defined interface. It can be reused in
another module by \dfn{instantiating} it.
An \dfn{instance} in Python (and other object-oriented
languages) refers to the object created by a
\index{instance!in Python versus hardware design}%
class constructor. In hardware design, an instance
is a particular incarnation of a hardware module.
Normally, the meaning should be clear from
the context. Occasionally, I may qualify terms
with the words 'hardware' or '\myhdl{}' to
avoid ambiguity.
\end{notice}
\section{Bit oriented operations \label{intro-bit}}
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{Bit indexing \label{intro-indexing}}
\index{bit indexing}
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
"""xc
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
\index{wait!for a signal value change}%
wait for a signal value change. This is typically used to
describe
\index{combinatorial logic}%
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{Bit slicing \label{intro-slicing}}
\index{bit slicing}
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. Both indices can be omitted
from the slice. If the leftmost index is omitted, the meaning is to
access ``all'' higher order bits. If the rightmost index is omitted,
it is \code{0} by default.
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 \label{intro-bfm}}
\index{bus-functional procedure}%
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\ uses generator functions to support
bus-functional procedures. 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: at that point,
the original generator should
\index{wait!for the completion of a generator}%
wait for the completion of a generator.
In this case, the original generator resumes when the
\code{rs232_tx(tx, txData)} 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 generator
resumes as soon as the wait condition specified by one
of the clauses is satisfied. This corresponds to the functionality of
\index{sensitivity list}%
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 original
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 original 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 \label{intro-python}}
To conclude this introductory chapter, it is useful to stress that
\myhdl\ is not a language in itself. The underlying 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}