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

407 lines
11 KiB
TeX
Raw Normal View History

2003-02-17 10:08:19 +00:00
\chapter{Modeling techniques}
\section{RTL modeling}
The present section describes how \myhdl\ supports RTL style modeling
2003-03-04 09:59:08 +00:00
as is typically used for synthesizable models in Verilog or VHDL. This
section is included mainly for illustrative purposes, as this modeling
style is well known and understood.
2003-02-20 13:20:15 +00:00
2003-02-17 10:08:19 +00:00
\subsection{Combinatorial logic}
\subsubsection{Template}
Combinatorial logic is described with a generator function code template as
follows:
\begin{verbatim}
def combinatorialLogic(<arguments>)
while 1:
yield <input signal arguments>
<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
changes.
\subsubsection{Example}
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
2003-02-17 10:08:19 +00:00
\end{verbatim}
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
2003-02-18 13:04:38 +00:00
explicitly providing a seed value. See the documentation of the
2003-02-17 10:08:19 +00:00
\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}
\subsection{Sequential logic}
\subsubsection{Template}
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}
\subsubsection{Example}
The following code is a description of an incrementer with enable, and
2003-02-18 13:04:38 +00:00
an asynchronous power-up reset.
2003-02-17 10:08:19 +00:00
\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
2003-02-18 13:04:38 +00:00
can raise the \code{myhdl.StopSimulation} exception to stop the
2003-02-17 10:08:19 +00:00
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)
2003-02-19 15:36:45 +00:00
Simulation(clockGen(), stimulus(), monitor(), INC_1).run()
2003-02-17 10:08:19 +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-02-17 12:26:22 +00:00
\section{High level modeling}
2003-03-06 19:16:41 +00:00
\begin{quote}
\em
2003-03-06 19:21:41 +00:00
This section on high level modeling should become an exciting part of
2003-03-06 19:16:41 +00:00
the manual, but it doesn't contain a lot of material just yet. One
reason is that I concentrated on the groundwork first. Moreover,
though I expect Python to offer some very powerful capabilities in
this domain, I'm just starting to experiment and learn myself. For
2003-03-06 19:21:41 +00:00
example, note that so far we haven't used classes (nor meta-classes
2003-03-06 19:16:41 +00:00
:-)) yet, even though Python has a very powerful object-oriented
model.
\end{quote}
\subsection{Modeling memories with built-in types}
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
2003-03-06 20:41:51 +00:00
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.)
2003-03-06 19:16:41 +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):
""" 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()
empty.next = (len(memory) == 0)
full.next = (len(memory) == 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.
\subsection{Modeling errors using exceptions}
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 at the module level, by
subclassing the standard \code{Exception} class. This exception is
then raised explicitly when an error condition occurs.
For example, we can change function \function{sparseMemory} 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, "Initialize address %s" % hex(addr)
sparseMemory.Error: Initializes 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 fifo2(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"
empty.next = (len(memory) == 0)
full.next = (len(memory) == maxFilling)
if len(memory) > 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.
\begin{quote}
\em
Lots of material to be added ...
\end{quote}