1
0
mirror of https://github.com/myhdl/myhdl.git synced 2024-12-14 07:44:38 +08:00
myhdl/doc/manual/modeling.tex

1198 lines
32 KiB
TeX
Raw Normal View History

2003-05-21 08:32:28 +00:00
\chapter{Modeling techniques \label{model}}
2003-05-20 20:29:52 +00:00
2003-08-03 20:32:24 +00:00
\section{Structural modeling \label{model-structure}}
2003-08-27 14:17:06 +00:00
\index{modeling!structural}
2003-08-03 20:32:24 +00:00
2003-08-08 09:12:55 +00:00
Hardware descriptions need to support the concepts of module
instantiation and hierarchy. In \myhdl{}, an instance is recursively
defined as being either a sequence of instances, or a generator.
Hierarchy is modeled by defining instances in a higher-level
function, and returning them. The following is a schematic example
of the basic case.
\begin{verbatim}
def top(...):
...
instance_1 = module_1(...)
instance_2 = module_2(...)
...
instance_n = module_n(...)
...
return instance_1, instance_2, ... , instance_n
\end{verbatim}
Note that \myhdl\ uses conventional procedural techniques
for modeling structure. This makes it straightforward
to model more complex cases.
2003-08-08 13:41:03 +00:00
\subsection{Conditional instantiation \label{model-conf}}
2003-08-27 14:17:06 +00:00
\index{conditional instantiation}
2003-08-08 09:12:55 +00:00
2003-08-08 13:41:03 +00:00
To model conditional instantiation, we can
2003-08-08 09:12:55 +00:00
select the returned instance under parameter control.
For example:
\begin{verbatim}
SLOW, MEDIUM, FAST = range(3)
def top(..., speed=SLOW):
...
def slowAndSmall():
...
...
def fastAndLarge():
...
if speed == SLOW:
return slowAndSmall()
elif speed == FAST:
return fastAndLarge()
else:
raise NotImplementedError
\end{verbatim}
2003-08-03 20:32:24 +00:00
\subsection{Arrays of instances \label{model-instarray}}
2003-08-27 14:17:06 +00:00
\index{arrays of instances}
2003-08-03 20:32:24 +00:00
2003-08-08 09:12:55 +00:00
Python lists are easy to create. We can use them
to model arrays of instances.
Suppose we have a top module that instantiates a
single \code{channel} submodule, as follows:
\begin{verbatim}
def top(...):
din = Signal(0)
dout = Signal(0)
clk = Signal(bool(0))
reset = Signal(bool(0))
channel_inst = channel(dout, din, clk, reset)
return channel_inst
\end{verbatim}
If we wanted to support an arbitrary number of channels,
we can use lists of signals and a list of instances,
as follows:
\begin{verbatim}
def top(..., n=8):
din = [Signal(0) for i in range(n)]
dout = [Signal(0) for in range(n)]
clk = Signal(bool(0))
reset = Signal(bool(0))
channel_inst = [None for i in range(n)]
for i in range(n):
channel_inst[i] = channel(dout[i], din[i], clk, reset)
return channel_inst
\end{verbatim}
2003-08-27 10:37:26 +00:00
\subsection{Inferring the list of instances \label{model-infer-instlist}}
2003-08-03 20:32:24 +00:00
2003-08-08 09:12:55 +00:00
In \myhdl{}, instances have to be returned explicitly by
2005-12-10 23:06:55 +00:00
a top level function. It may be convenient to assemble
2003-08-08 09:12:55 +00:00
the list of instances automatically. For this purpose,
2005-12-10 23:06:55 +00:00
\myhdl provides the function \function{instances()}.
2003-08-08 09:12:55 +00:00
Using the first example in this section, it is used as follows:
\begin{verbatim}
from myhdl import instances
def top(...):
...
instance_1 = module_1(...)
instance_2 = module_2(...)
...
instance_n = module_n(...)
...
return instances()
\end{verbatim}
Function \function{instances()} uses introspection to inspect the type
of the local variables defined by the calling function. All variables
that comply with the definition of an instance are assembled in
a list, and that list is returned.
2003-05-21 08:32:28 +00:00
\section{RTL modeling \label{model-rtl}}
2003-08-27 14:17:06 +00:00
\index{modeling!RTL style}
2003-05-20 20:29:52 +00:00
The present section describes how \myhdl\ supports RTL style modeling
2005-12-27 12:41:51 +00:00
as is typically used for synthesizable models.
2003-05-20 20:29:52 +00:00
2003-08-29 12:35:58 +00:00
\subsection{Combinatorial logic \label{model-comb}}
2003-08-28 19:43:06 +00:00
\index{combinatorial logic}
2003-05-20 20:29:52 +00:00
2003-05-21 08:32:28 +00:00
\subsubsection{Template \label{model-comb-templ}}
2003-08-04 21:21:19 +00:00
2005-12-10 23:06:55 +00:00
Combinatorial logic is described with a code pattern as
2003-05-20 20:29:52 +00:00
follows:
\begin{verbatim}
2005-12-29 21:20:02 +00:00
def top(<parameters>):
2005-12-10 23:06:55 +00:00
...
@always_comb
def combLogic():
2003-05-20 20:29:52 +00:00
<functional code>
2005-12-10 23:06:55 +00:00
...
return combLogic, ...
2003-05-20 20:29:52 +00:00
\end{verbatim}
2005-12-27 12:41:51 +00:00
The \function{always_comb} decorator describes
2005-12-10 23:06:55 +00:00
combinatorial logic.
\footnote{The name \function{always_comb} refers to a construct with
similar semantics in SystemVerilog.}.
The decorated function is a local function that specifies what
happens when one of the input signals of the logic changes. The
2005-12-27 12:41:51 +00:00
\function{always_comb} decorator infers the input signals
2005-12-10 23:06:55 +00:00
automatically. It returns a generator that is sensitive to all inputs,
2005-12-27 12:41:51 +00:00
and that executes the function whenever an input changes.
2003-08-03 20:32:24 +00:00
2003-05-20 20:29:52 +00:00
2003-05-21 08:32:28 +00:00
\subsubsection{Example \label{model-comb-ex}}
2003-05-20 20:29:52 +00:00
The following is an example of a combinatorial multiplexer:
\begin{verbatim}
2005-12-10 23:06:55 +00:00
from myhdl import Signal, Simulation, delay, always_comb
def Mux(z, a, b, sel):
2003-05-20 20:29:52 +00:00
""" Multiplexer.
z -- mux output
a, b -- data inputs
sel -- control input: select a if asserted, otherwise b
"""
2003-08-04 17:28:10 +00:00
2005-12-10 23:06:55 +00:00
@always_comb
def muxLogic():
2003-08-04 17:28:10 +00:00
if sel == 1:
z.next = a
else:
z.next = b
2005-12-10 23:06:55 +00:00
return muxLogic
2003-08-04 17:28:10 +00:00
\end{verbatim}
2005-12-10 23:06:55 +00:00
To verify it, we will simulate the logic with some random patterns. The
2003-05-20 20:29:52 +00:00
\code{random} module in Python's standard library comes in handy for
such purposes. The function \code{randrange(\var{n})} returns a random
natural integer smaller than \var{n}. It is used in the test bench
code to produce random input values:
\begin{verbatim}
from random import randrange
2005-12-10 23:06:55 +00:00
z, a, b, sel = [Signal(0) for i in range(4)]
2003-05-20 20:29:52 +00:00
2005-12-10 23:06:55 +00:00
mux_1 = Mux(z, a, b, sel)
2003-05-20 20:29:52 +00:00
def test():
print "z a b sel"
for i in range(8):
a.next, b.next, sel.next = randrange(8), randrange(8), randrange(2)
yield delay(10)
print "%s %s %s %s" % (z, a, b, sel)
2005-12-10 23:06:55 +00:00
test_1 = test()
sim = Simulation(mux_1, test_1)
sim.run()
2003-05-20 20:29:52 +00:00
\end{verbatim}
Because of the randomness, the simulation output varies between runs
\footnote{It also possible to have a reproducible random output, by
explicitly providing a seed value. See the documentation of the
\code{random} module.}. One particular run produced the following
output:
\begin{verbatim}
% python mux.py
z a b sel
6 6 1 1
7 7 1 1
7 3 7 0
1 2 1 0
7 7 5 1
4 7 4 0
4 0 4 0
3 3 5 1
StopSimulation: No more events
\end{verbatim}
2003-08-29 12:35:58 +00:00
\subsection{Sequential logic \label{model-seq}}
2003-08-28 19:43:06 +00:00
\index{sequential logic}
2003-05-20 20:29:52 +00:00
2003-05-21 08:32:28 +00:00
\subsubsection{Template \label{model-seq-templ}}
2003-05-20 20:29:52 +00:00
Sequential RTL models are sensitive to a clock edge. In addition, they
may be sensitive to a reset signal. We will describe one of the most
common patterns: a template with a rising clock edge and an
asynchronous reset signal. Other templates are similar.
\begin{verbatim}
2005-12-29 21:20:02 +00:00
def top(<parameters>, clock, ..., reset, ...):
2005-12-10 23:06:55 +00:00
...
@always(clock.posedge, reset.negedge)
def seqLogic():
2003-05-20 20:29:52 +00:00
if reset == <active level>:
<reset code>
else:
<functional code>
2005-12-10 23:06:55 +00:00
...
return seqLogic, ...
2003-05-20 20:29:52 +00:00
\end{verbatim}
2003-05-21 08:32:28 +00:00
\subsubsection{Example \label{model-seq-ex}}
2003-05-20 20:29:52 +00:00
The following code is a description of an incrementer with enable, and
2005-12-27 12:41:51 +00:00
an asynchronous reset.
2003-05-20 20:29:52 +00:00
\begin{verbatim}
2005-12-10 23:06:55 +00:00
from random import randrange
from myhdl import *
2003-05-20 20:29:52 +00:00
ACTIVE_LOW, INACTIVE_HIGH = 0, 1
def Inc(count, enable, clock, reset, n):
2005-12-10 23:06:55 +00:00
2003-05-20 20:29:52 +00:00
""" Incrementer with enable.
count -- output
enable -- control input, increment when 1
clock -- clock input
reset -- asynchronous reset input
n -- counter max value
"""
2005-12-10 23:06:55 +00:00
@always(clock.posedge, reset.negedge)
def incLogic():
2003-05-20 20:29:52 +00:00
if reset == ACTIVE_LOW:
count.next = 0
else:
if enable:
count.next = (count + 1) % n
2005-12-10 23:06:55 +00:00
return incLogic
2003-05-20 20:29:52 +00:00
\end{verbatim}
For the test bench, we will use an independent clock generator, stimulus
generator, and monitor. After applying enough stimulus patterns, we
2005-12-27 12:41:51 +00:00
can raise the \code{StopSimulation} exception to stop the
2003-05-20 20:29:52 +00:00
simulation run. The test bench for a small incrementer and a small
number of patterns is a follows:
\begin{verbatim}
2005-12-10 23:06:55 +00:00
def testbench():
count, enable, clock, reset = [Signal(intbv(0)) for i in range(4)]
2003-05-20 20:29:52 +00:00
2005-12-10 23:06:55 +00:00
inc_1 = Inc(count, enable, clock, reset, n=4)
2003-05-20 20:29:52 +00:00
2005-12-10 23:06:55 +00:00
HALF_PERIOD = delay(10)
@always(HALF_PERIOD)
def clockGen():
2003-05-20 20:29:52 +00:00
clock.next = not clock
2005-12-10 23:06:55 +00:00
@instance
def stimulus():
reset.next = ACTIVE_LOW
2005-12-27 12:41:51 +00:00
yield clock.negedge
2005-12-10 23:06:55 +00:00
reset.next = INACTIVE_HIGH
for i in range(12):
enable.next = min(1, randrange(3))
2005-12-27 12:41:51 +00:00
yield clock.negedge
2005-12-10 23:06:55 +00:00
raise StopSimulation
@instance
def monitor():
print "enable count"
2005-12-27 12:41:51 +00:00
yield reset.posedge
2005-12-10 23:06:55 +00:00
while 1:
2005-12-27 12:41:51 +00:00
yield clock.posedge
2005-12-10 23:06:55 +00:00
yield delay(1)
print " %s %s" % (enable, count)
return clockGen, stimulus, inc_1, monitor
2005-12-27 12:41:51 +00:00
2005-12-10 23:06:55 +00:00
tb = testbench()
def main():
Simulation(tb).run()
2003-05-20 20:29:52 +00:00
\end{verbatim}
The simulation produces the following output:
\begin{verbatim}
% python inc.py
enable count
0 0
1 1
0 1
1 2
1 3
1 0
0 0
1 1
0 1
0 1
0 1
1 2
StopSimulation
\end{verbatim}
2003-08-04 21:21:19 +00:00
\subsection{Finite State Machine modeling \label{model-fsm}}
2003-08-27 14:17:06 +00:00
\index{modeling!Finite State Machine}
2003-08-04 21:21:19 +00:00
Finite State Machine (FSM) modeling is very common in RTL
design and therefore deserves special attention.
For code clarity, the state values are typically represented by a set
of identifiers. A standard Python idiom for this purpose is to assign
a range of integers to a tuple of identifiers, like so:
\begin{verbatim}
>>> SEARCH, CONFIRM, SYNC = range(3)
>>> CONFIRM
1
\end{verbatim}
However, this technique has some drawbacks. Though it is clearly
the intention that the identifiers belong together, this information
is lost as soon as they are defined. Also, the identifiers evaluate to
integers, whereas a string representation of the identifiers
would be preferable. To solve these issues, we need an
\emph{enumeration type}.
\myhdl\ supports enumeration types by providing a function
\function{enum()}. The arguments to \function{enum()} are the string
representations of the identifiers, and its return value is an
enumeration type. The identifiers are available as attributes of the
type. For example:
\begin{verbatim}
>>> from myhdl import enum
>>> t_State = enum('SEARCH', 'CONFIRM', 'SYNC')
>>> t_State
<Enum: SEARCH, CONFIRM, SYNC>
>>> t_State.CONFIRM
CONFIRM
\end{verbatim}
We can use this type to construct a state signal as follows:
\begin{verbatim}
state = Signal(t_State.SEARCH)
\end{verbatim}
As an example, we will use a framing controller FSM. It is an
imaginary example, but similar control structures are often found in
2003-08-28 19:43:06 +00:00
telecommunication applications. Suppose that we need to find the
Start Of Frame (SOF) position of an incoming frame of bytes.
2003-08-04 21:21:19 +00:00
A sync pattern detector continuously looks for a framing
pattern and indicates it to the FSM with a \code{syncFlag} signal. When
found, the FSM moves from the initial \code{SEARCH} state to the
\code{CONFIRM} state. When the \code{syncFlag}
2003-08-08 13:41:03 +00:00
is confirmed on the expected position, the FSM declares \code{SYNC},
2003-08-04 21:21:19 +00:00
otherwise it falls back to the \code{SEARCH} state. This FSM can be
coded as follows:
\begin{verbatim}
2005-12-14 10:46:02 +00:00
from myhdl import *
2003-08-04 21:21:19 +00:00
ACTIVE_LOW = 0
FRAME_SIZE = 8
t_State = enum('SEARCH', 'CONFIRM', 'SYNC')
def FramerCtrl(SOF, state, syncFlag, clk, reset_n):
""" Framing control FSM.
SOF -- start-of-frame output bit
2005-12-14 10:46:02 +00:00
state -- FramerState output
2003-08-04 21:21:19 +00:00
syncFlag -- sync pattern found indication input
clk -- clock input
reset_n -- active low reset
"""
2005-12-14 10:46:02 +00:00
2003-08-04 21:21:19 +00:00
index = Signal(0) # position in frame
2005-12-14 10:46:02 +00:00
@always(clk.posedge, reset_n.negedge)
2003-08-04 21:21:19 +00:00
def FSM():
2005-12-14 10:46:02 +00:00
if reset_n == ACTIVE_LOW:
SOF.next = 0
index.next = 0
state.next = t_State.SEARCH
2003-08-04 22:16:33 +00:00
2005-12-14 10:46:02 +00:00
else:
index.next = (index + 1) % FRAME_SIZE
SOF.next = 0
2003-08-04 22:16:33 +00:00
2005-12-14 10:46:02 +00:00
if state == t_State.SEARCH:
index.next = 1
if syncFlag:
state.next = t_State.CONFIRM
2003-08-04 22:16:33 +00:00
2005-12-14 10:46:02 +00:00
elif state == t_State.CONFIRM:
if index == 0:
if syncFlag:
state.next = t_State.SYNC
else:
state.next = t_State.SEARCH
2003-08-04 22:16:33 +00:00
2005-12-14 10:46:02 +00:00
elif state == t_State.SYNC:
if index == 0:
if not syncFlag:
state.next = t_State.SEARCH
SOF.next = (index == FRAME_SIZE-1)
2003-08-04 22:16:33 +00:00
2005-12-14 10:46:02 +00:00
else:
raise ValueError("Undefined state")
2003-08-04 21:21:19 +00:00
2005-12-14 10:46:02 +00:00
return FSM
2003-08-04 21:21:19 +00:00
\end{verbatim}
At this point, we will use the example to demonstrate
2003-08-27 14:17:06 +00:00
the \myhdl\ support for
\index{waveform viewing}%
waveform viewing.
2003-08-04 21:21:19 +00:00
During simulation, signal
changes can be written to a VCD output file. The VCD file can then be
loaded and viewed in a waveform viewer tool such as \program{gtkwave}.
The user interface of this feature consists of a single function,
\function{traceSignals()}. To explain how it works, recall that in
2003-08-29 13:55:31 +00:00
\myhdl{}, an instance is created by assigning
the result of a function call to an instance name.
For example:
2003-08-04 21:21:19 +00:00
\begin{verbatim}
tb_fsm = testbench()
\end{verbatim}
To enable VCD tracing, the instance should be created as follows
instead:
\begin{verbatim}
tb_fsm = traceSignals(testbench)
\end{verbatim}
2005-12-29 21:20:02 +00:00
Note that the first argument of
2003-08-04 21:21:19 +00:00
\function{traceSignals()} consists of the uncalled function. By
calling the function under its control, \function{traceSignals()}
gathers information about the hierarchy and the signals to be traced.
In addition to a function argument, \function{traceSignals()} accepts
an arbitrary number of non-keyword and keyword arguments that will be
passed to the function call.
A small test bench for our framing controller example,
with signal tracing enabled, is shown below:
\begin{verbatim}
def testbench():
SOF = Signal(bool(0))
syncFlag = Signal(bool(0))
clk = Signal(bool(0))
reset_n = Signal(bool(1))
state = Signal(t_State.SEARCH)
framectrl = FramerCtrl(SOF, state, syncFlag, clk, reset_n)
2005-12-14 10:46:02 +00:00
@always(delay(10))
2003-08-04 21:21:19 +00:00
def clkgen():
2005-12-14 10:46:02 +00:00
clk.next = not clk
2003-08-04 21:21:19 +00:00
2005-12-14 10:46:02 +00:00
@instance
2003-08-04 21:21:19 +00:00
def stimulus():
for i in range(3):
2005-12-27 12:41:51 +00:00
yield clk.posedge
2003-08-04 21:21:19 +00:00
for n in (12, 8, 8, 4):
syncFlag.next = 1
2005-12-27 12:41:51 +00:00
yield clk.posedge
2003-08-04 21:21:19 +00:00
syncFlag.next = 0
for i in range(n-1):
2005-12-27 12:41:51 +00:00
yield clk.posedge
2003-08-04 21:21:19 +00:00
raise StopSimulation
2005-12-14 10:46:02 +00:00
return framectrl, clkgen, stimulus
2003-08-04 21:21:19 +00:00
2005-12-14 10:46:02 +00:00
tb_fsm = traceSignals(testbench)
2003-08-04 21:21:19 +00:00
sim = Simulation(tb_fsm)
sim.run()
\end{verbatim}
When we run the test bench, it generates a VCD file
2005-12-14 10:46:02 +00:00
called \file{testbench.vcd}. When we load this file into
2003-08-29 13:55:31 +00:00
\program{gtkwave}, we can view the waveforms:
2003-08-04 21:21:19 +00:00
\ifpdf
\includegraphics{tbfsm.png}
\fi
Signals are dumped in a suitable format. This format is inferred at
the \class{Signal} construction time, from the type of the initial
value. In particular, \class{bool} signals are dumped as single
2003-08-29 13:55:31 +00:00
bits. (This only works starting with Python~2.3, when \class{bool} has
2003-08-04 21:21:19 +00:00
become a separate type). Likewise, \class{intbv} signals with a
defined bit width are dumped as bit vectors. To support the general
case, other types of signals are dumped as a string representation, as
returned by the standard \function{str()} function.
\begin{notice}[warning]
Support for literal string representations is not part of the VCD
standard. It is specific to \program{gtkwave}. To generate a
standard VCD file, you need to use signals with a defined bit width
only.
\end{notice}
2003-05-21 08:32:28 +00:00
\section{High level modeling \label{model-hl}}
2003-08-27 14:17:06 +00:00
\index{modeling!high level}
2003-05-20 20:29:52 +00:00
2005-12-19 11:25:14 +00:00
\subsection{Modeling with bus-functional procedures \label{model-bfm}}
2005-12-09 15:16:09 +00:00
\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
2005-12-27 12:41:51 +00:00
yield rx.negedge, delay(timeout)
2005-12-09 15:16:09 +00:00
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}
2003-05-21 08:32:28 +00:00
\subsection{Modeling memories with built-in types \label{model-mem}}
2003-08-27 14:17:06 +00:00
\index{modeling!memories}
2003-05-20 20:29:52 +00:00
Python has powerful built-in data types that can be useful to model
hardware memories. This can be merely a matter of putting an interface
around some data type operations.
For example, a \dfn{dictionary} comes in handy to model sparse memory
structures. (In other languages, this data type is called
\dfn{associative array}, or \dfn{hash table}.) A sparse memory is one in
which only a small part of the addresses is used in a particular
application or simulation. Instead of statically allocating the full
address space, which can be large, it is better to dynamically
allocate the needed storage space. This is exactly what a dictionary
provides. The following is an example of a sparse memory model:
\begin{verbatim}
def sparseMemory(dout, din, addr, we, en, clk):
2005-12-14 10:46:02 +00:00
2003-05-20 20:29:52 +00:00
""" Sparse memory model based on a dictionary.
Ports:
dout -- data out
din -- data in
addr -- address bus
we -- write enable: write if 1, read otherwise
en -- interface enable: enabled if 1
clk -- clock input
"""
2005-12-14 10:46:02 +00:00
2003-05-20 20:29:52 +00:00
memory = {}
2005-12-14 10:46:02 +00:00
@always(clk.posedge)
def access():
if en:
if we:
2005-12-14 11:28:19 +00:00
memory[addr.val] = din.val
2005-12-14 10:46:02 +00:00
else:
2005-12-14 11:28:19 +00:00
dout.next = memory[addr.val]
2005-12-14 10:46:02 +00:00
return access
2003-05-20 20:29:52 +00:00
\end{verbatim}
Note how we use the \code{val} attribute of the \code{din} signal, as
we don't want to store the signal object itself, but its current
2005-12-27 12:41:51 +00:00
value. Similarly, we use the \code{val} attribute of the \code{addr}
2006-04-14 13:11:39 +00:00
signal as the dictionary key.
In many cases, \myhdl\ code uses a signal's current value
automatically when there is no ambiguity: for example, when a signal
is used in an expression. However, in other cases such as in this
example you have to refer to the value explicitly: for example, when
the Signal is used as an index, or when it is not used in an
expression. One option is to use the \code{val} attribute, as in this
example. Another possibility is to use the \code{int()} or
\code{bool()} functions to typecast the Signal to an integer or a
2006-04-14 13:21:36 +00:00
boolean value. These functions are also useful with \class{intbv}
objects.
2003-05-20 20:29:52 +00:00
As a second example, we will demonstrate how to use a list to model a
synchronous fifo:
\begin{verbatim}
def fifo(dout, din, re, we, empty, full, clk, maxFilling=sys.maxint):
2005-12-14 10:46:02 +00:00
2003-05-20 20:29:52 +00:00
""" Synchronous fifo model based on a list.
2005-12-14 10:46:02 +00:00
2003-05-20 20:29:52 +00:00
Ports:
dout -- data out
din -- data in
re -- read enable
we -- write enable
empty -- empty indication flag
full -- full indication flag
clk -- clock input
2005-12-14 10:46:02 +00:00
2003-05-20 20:29:52 +00:00
Optional parameter:
maxFilling -- maximum fifo filling, "infinite" by default
"""
2005-12-14 10:46:02 +00:00
2003-05-20 20:29:52 +00:00
memory = []
2005-12-14 10:46:02 +00:00
@always(clk.posedge)
def access():
2003-05-20 20:29:52 +00:00
if we:
memory.insert(0, din.val)
if re:
dout.next = memory.pop()
filling = len(memory)
empty.next = (filling == 0)
full.next = (filling == maxFilling)
2005-12-14 10:46:02 +00:00
return access
2003-05-20 20:29:52 +00:00
\end{verbatim}
Again, the model is merely a \myhdl\ interface around some operations
on a list: \function{insert()} to insert entries, \function{pop()} to
retrieve them, and \function{len()} to get the size of a Python
object.
2003-05-21 08:32:28 +00:00
\subsection{Modeling errors using exceptions \label{model-err}}
2003-05-20 20:29:52 +00:00
In the previous section, we used Python data types for modeling. If
such a type is used inappropriately, Python's run time error system
will come into play. For example, if we access an address in the
\function{sparseMemory} model that was not initialized before, we will
get a traceback similar to the following (some lines omitted for
clarity):
\begin{verbatim}
Traceback (most recent call last):
...
2005-12-14 10:46:02 +00:00
File "sparseMemory.py", line 31, in access
2005-12-14 11:28:19 +00:00
dout.next = memory[addr.val]
2005-12-14 10:46:02 +00:00
KeyError: Signal(51)
2003-05-20 20:29:52 +00:00
\end{verbatim}
Similarly, if the \code{fifo} is empty, and we attempt to read from
it, we get:
\begin{verbatim}
Traceback (most recent call last):
...
File "fifo.py", line 34, in fifo
dout.next = memory.pop()
IndexError: pop from empty list
\end{verbatim}
Instead of these low level errors, it may be preferable to define
errors at the functional level. In Python, this is typically done by
defining a custom \code{Error} exception, by subclassing the standard
\code{Exception} class. This exception is then raised explicitly when
an error condition occurs.
For example, we can change the \function{sparseMemory} function as
follows (with the doc string is omitted for brevity):
\begin{verbatim}
class Error(Exception):
pass
2005-12-14 10:46:02 +00:00
def sparseMemory2(dout, din, addr, we, en, clk):
2003-05-20 20:29:52 +00:00
memory = {}
2005-12-14 10:46:02 +00:00
@always(clk.posedge)
def access():
if en:
if we:
2005-12-14 11:28:19 +00:00
memory[addr.val] = din.val
2005-12-14 10:46:02 +00:00
else:
try:
2005-12-14 11:28:19 +00:00
dout.next = memory[addr.val]
2005-12-14 10:46:02 +00:00
except KeyError:
raise Error, "Uninitialized address %s" % hex(addr)
return access
2003-05-20 20:29:52 +00:00
\end{verbatim}
This works by catching the low level data type exception, and raising
the custom exception with an appropriate error message instead. If
the \function{sparseMemory} function is defined in a module with the
same name, an access error is now reported as follows:
\begin{verbatim}
Traceback (most recent call last):
...
2005-12-14 10:46:02 +00:00
File "sparseMemory.py", line 61, in access
2003-05-20 20:29:52 +00:00
raise Error, "Uninitialized address %s" % hex(addr)
2005-12-14 10:46:02 +00:00
Error: Uninitialized address 0x33
2003-05-20 20:29:52 +00:00
\end{verbatim}
Likewise, the \function{fifo} function can be adapted as follows, to
report underflow and overflow errors:
\begin{verbatim}
class Error(Exception):
pass
2005-12-14 10:46:02 +00:00
def fifo2(dout, din, re, we, empty, full, clk, maxFilling=sys.maxint):
2003-05-20 20:29:52 +00:00
memory = []
2005-12-14 10:46:02 +00:00
@always(clk.posedge)
def access():
2003-05-20 20:29:52 +00:00
if we:
memory.insert(0, din.val)
if re:
try:
dout.next = memory.pop()
except IndexError:
raise Error, "Underflow -- Read from empty fifo"
filling = len(memory)
empty.next = (filling == 0)
full.next = (filling == maxFilling)
if filling > maxFilling:
raise Error, "Overflow -- Max filling %s exceeded" % maxFilling
2005-12-14 10:46:02 +00:00
return access
2003-05-20 20:29:52 +00:00
\end{verbatim}
In this case, the underflow error is detected as before, by catching a
low level exception on the list data type. On the other hand, the
overflow error is detected by a regular check on the length of the
list.
2003-08-27 14:17:06 +00:00
\subsection{Object oriented modeling \label{model-obj}}
\index{modeling!object oriented}
2003-08-08 16:39:46 +00:00
The models in the previous sections used high-level built-in data
types internally. However, they had a conventional RTL-style
interface. Communication with such a module is done through signals
that are attached to it during instantiation.
A more advanced approach is to model hardware blocks as
objects. Communication with objects is done through method calls.
A method encapsulates all details of a certain task performed
by the object. As an object has a method interface instead
of an RTL-style hardware interface, this is a much
higher level approach.
As an example, we will design a synchronized queue object.
Such an object can be filled by producer, and independently
read by a consumer. When the queue is empty, the consumer
2003-08-08 21:32:30 +00:00
should wait until an item is available. The queue can be modeled
2003-08-08 16:39:46 +00:00
as an object with a \method{put(item)} and a \method{get()}
method, as follows:
\begin{verbatim}
from myhdl import *
def trigger(event):
event.next = not event
class queue:
def __init__(self):
self.l = []
self.sync = Signal(0)
self.item = None
def put(self,item):
# non time-consuming method
self.l.append(item)
trigger(self.sync)
def get(self):
# time-consuming method
if not self.l:
yield self.sync
self.item = self.l.pop(0)
\end{verbatim}
The \class{queue} object constructor initializes an internal list to
hold items, and a \var{sync} signal to synchronize the operation
between the methods. Whenever \method{put()} puts an item in the
queue, the signal is triggered. When the \method{get()} method sees
that the list is empty, it waits on the trigger first.
\method{get()} is a generator method because
it may consume time. As the \code{yield} statement is used in \myhdl\
for timing control, the method cannot ``yield'' the item. Instead, it
makes it available in the \var{item} instance variable.
To test the queue operation, we will model a producer and a consumer
in the test bench. As a waiting consumer should not block a whole
system, it should run in a concurrent ``thread''. As always in
\myhdl{}, concurrency is modeled by Python generators. Producer
and consumer will thus run independently, and we will monitor
their operation through some print statements:
\begin{verbatim}
q = queue()
def Producer(q):
yield delay(120)
for i in range(5):
print "%s: PUT item %s" % (now(), i)
q.put(i)
2003-08-08 21:32:30 +00:00
yield delay(max(5, 45 - 10*i))
2003-08-08 16:39:46 +00:00
def Consumer(q):
yield delay(100)
while 1:
print "%s: TRY to get item" % now()
yield q.get()
print "%s: GOT item %s" % (now(), q.item)
yield delay(30)
def main():
P = Producer(q)
C = Consumer(q)
return P, C
sim = Simulation(main())
sim.run()
\end{verbatim}
2003-08-08 21:32:30 +00:00
Note that the generator method \method{get()} is called in a
\code{yield} statement in the \function{Consumer} function. The new
generator will take over from \function{Consumer}, until it is done.
Running this test bench produces the following output:
2003-08-08 16:39:46 +00:00
\begin{verbatim}
% python queue.py
100: TRY to get item
120: PUT item 0
120: GOT item 0
150: TRY to get item
165: PUT item 1
165: GOT item 1
195: TRY to get item
200: PUT item 2
200: GOT item 2
225: PUT item 3
230: TRY to get item
230: GOT item 3
240: PUT item 4
260: TRY to get item
260: GOT item 4
290: TRY to get item
StopSimulation: No more events
\end{verbatim}