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

748 lines
22 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-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-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-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-03 20:32:24 +00:00
\subsection{Inferring the list of all instances \label{model-infer-instlist}}
2003-08-08 09:12:55 +00:00
In \myhdl{}, instances have to be returned explicitly by
a top level function. This procedural approach has important
advantages, as shown earlier. However, if we just want to
return all defined instances, it may be convenient to assemble
the list of instances automatically. For this purpose,
2003-08-08 13:41:03 +00:00
\myhdl\ 0.3 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-05-20 20:29:52 +00:00
The present section describes how \myhdl\ supports RTL style modeling
2003-08-03 20:32:24 +00:00
as is typically used for synthesizable models in Verilog or VHDL.
2003-05-20 20:29:52 +00:00
2003-05-21 08:32:28 +00:00
\subsection{Combinatorial logic \label{model-comb}}
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
2003-05-20 20:29:52 +00:00
Combinatorial logic is described with a generator function code template as
follows:
\begin{verbatim}
def combinatorialLogic(<arguments>)
while 1:
2003-08-03 20:32:24 +00:00
yield <input signals>
2003-05-20 20:29:52 +00:00
<functional code>
\end{verbatim}
The overall code is wrapped in a \code{while 1} statement to keep the
generator alive. All input signals are clauses in the \code{yield}
statement, so that the generator resumes whenever one of the inputs
2003-08-03 20:32:24 +00:00
changes.
Note that the input signals are listed explicitly in the
template. Alternatively, \myhdl\ provides the \function{always_comb()}
2003-08-04 17:28:10 +00:00
function to infer the input signals automatically.
\footnote{The name \function{always_comb()} refers to
a construct with similar semantics in SystemVerilog.}.
The argument of
2003-08-03 20:32:24 +00:00
\function{always_comb()} is a local function that specifies
what happens when one of the input signals
changes. It returns a generator that is
sensitive to all inputs, and that will run the function argument
whenever an input changes. Using \function{always_comb}, the template
can be rewritten as follows:
\begin{verbatim}
def combinatorialLogic(<arguments>)
def logicFunction():
<functional code>
return always_comb(logicFunction)
\end{verbatim}
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}
def mux(z, a, b, sel):
""" Multiplexer.
z -- mux output
a, b -- data inputs
sel -- control input: select a if asserted, otherwise b
"""
while 1:
yield a, b, sel
if sel == 1:
z.next = a
else:
z.next = b
\end{verbatim}
2003-08-04 17:28:10 +00:00
Alternatively, it could be written as follows, using
\function{always_comb()}:
\begin{verbatim}
def mux(z, a, b, sel):
def muxlogic():
if sel == 1:
z.next = a
else:
z.next = b
return always_comb(muxlogic)
\end{verbatim}
2003-05-20 20:29:52 +00:00
To verify, let's simulate this logic with some random patterns. The
\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
(z, a, b, sel) = [Signal(0) for i in range(4)]
MUX_1 = mux(z, a, b, sel)
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)
Simulation(MUX_1, test()).run()
\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-05-21 08:32:28 +00:00
\subsection{Sequential logic \label{model-seq}}
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}
def sequentialLogic(<arguments>, clock, ..., reset, ...)
while 1:
yield posedge(clock), negedge(reset)
if reset == <active level>:
<reset code>
else:
<functional code>
\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
an asynchronous power-up reset.
\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
"""
while 1:
yield posedge(clock), negedge(reset)
if reset == ACTIVE_LOW:
count.next = 0
else:
if enable:
count.next = (count + 1) % n
\end{verbatim}
For the test bench, we will use an independent clock generator, stimulus
generator, and monitor. After applying enough stimulus patterns, we
can raise the \code{myhdl.StopSimulation} exception to stop the
simulation run. The test bench for a small incrementer and a small
number of patterns is a follows:
\begin{verbatim}
count, enable, clock, reset = [Signal(intbv(0)) for i in range(4)]
INC_1 = Inc(count, enable, clock, reset, n=4)
def clockGen():
while 1:
yield delay(10)
clock.next = not clock
def stimulus():
reset.next = ACTIVE_LOW
yield negedge(clock)
reset.next = INACTIVE_HIGH
for i in range(12):
enable.next = min(1, randrange(3))
yield negedge(clock)
raise StopSimulation
def monitor():
print "enable count"
yield posedge(reset)
while 1:
yield posedge(clock)
yield delay(1)
print " %s %s" % (enable, count)
Simulation(clockGen(), stimulus(), monitor(), INC_1).run()
\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}}
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
telecommunication applications. Suppose that we need to recover the
Start Of Frame (SOF) position of a incoming frame.
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}
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
state -- t_State output signal
syncFlag -- sync pattern found indication input
clk -- clock input
reset_n -- active low reset
"""
index = Signal(0) # position in frame
def FSM():
while 1:
yield posedge(clk), negedge(reset_n)
if reset_n == ACTIVE_LOW:
SOF.next = 0
index.next = 0
state.next = t_State.SEARCH
2003-08-04 22:16:33 +00:00
2003-08-04 21:21:19 +00:00
else:
index.next = (index + 1) % FRAME_SIZE
SOF.next = 0
2003-08-04 22:16:33 +00:00
2003-08-04 21:21:19 +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
2003-08-04 21:21:19 +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
2003-08-04 21:21:19 +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
2003-08-04 21:21:19 +00:00
else:
raise ValueError("Undefined state")
return FSM()
\end{verbatim}
At this point, we will use the example to demonstrate
the \myhdl\ support for waveform viewing.
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
\myhdl{}, an instance is created by a function call and by assigning
the result to an instance name. For example:
\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}
All signals in the instance hierarchy will be traced in an output VCD
file called \file{tb_fsm.vcd}. Note that first the argument of
\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)
def clkgen():
while 1:
yield delay(10)
clk.next = not clk
def stimulus():
for i in range(3):
yield posedge(clk)
for n in (12, 8, 8, 4):
syncFlag.next = 1
yield posedge(clk)
syncFlag.next = 0
for i in range(n-1):
yield posedge(clk)
raise StopSimulation
return framectrl, clkgen(), stimulus()
tb_fsm = traceSignals(testbench)
sim = Simulation(tb_fsm)
sim.run()
\end{verbatim}
When we run the test bench, it generates a VCD file
called \file{tb_fsm.vcd}. When we load this file into
\program{gtkwave}, we can view we waveforms:
\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
bits. (This only works starting with Python2.3, when \class{bool} has
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-05-20 20:29:52 +00:00
\begin{quote}
2003-08-03 20:32:24 +00:00
\em
2003-08-08 13:41:03 +00:00
High level modeling is the ideal application of \myhdl{} and Python.
The possibilities are extensive and beyond the scope of most other
languages. It can be expected that \myhdl\ users will often have the
``Pythonic experience'' of finding an elegant solution to a complex
modeling problem. The following section should be seen as a starting
point for experiments and exploration. Over time, more techniques that
prove useful will be added.
2003-05-20 20:29:52 +00:00
\end{quote}
2003-05-21 08:32:28 +00:00
\subsection{Modeling memories with built-in types \label{model-mem}}
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):
""" 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
"""
memory = {}
while 1:
yield posedge(clk)
if not en:
continue
if we:
memory[addr] = din.val
else:
dout.next = memory[addr]
\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
value. (In many cases, \myhdl\ can use a signal's value automatically
when there is no ambiguity: for example, this happens whenever a
signal is used in expressions. When in doubt, you can always use the
\code{val} attribute explicitly.)
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):
""" Synchronous fifo model based on a list.
Ports:
dout -- data out
din -- data in
re -- read enable
we -- write enable
empty -- empty indication flag
full -- full indication flag
clk -- clock input
Optional parameter:
maxFilling -- maximum fifo filling, "infinite" by default
"""
memory = []
while 1:
yield posedge(clk)
if we:
memory.insert(0, din.val)
if re:
dout.next = memory.pop()
filling = len(memory)
empty.next = (filling == 0)
full.next = (filling == maxFilling)
\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):
...
File "sparseMemory.py", line 30, in sparseMemory
dout.next = memory[addr]
KeyError: 51
\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
def sparseMemory(dout, din, addr, we, en, clk):
memory = {}
while 1:
yield posedge(clk)
if not en:
continue
if we:
memory[addr] = din.val
else:
try:
dout.next = memory[addr]
except KeyError:
raise Error, "Uninitialized address %s" % hex(addr)
\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):
...
File "sparseMemory.py", line 57, in sparseMemory
raise Error, "Uninitialized address %s" % hex(addr)
__main__.Error: Uninitialized address 0x33
\end{verbatim}
Likewise, the \function{fifo} function can be adapted as follows, to
report underflow and overflow errors:
\begin{verbatim}
class Error(Exception):
pass
def fifo(dout, din, re, we, empty, full, clk, maxFilling=sys.maxint):
memory = []
while 1:
yield posedge(clk)
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
\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-04 22:16:33 +00:00
\subsection{Object-oriented modeling \label{model-obj}}