1
0
mirror of https://github.com/myhdl/myhdl.git synced 2025-01-24 21:52:56 +08:00
myhdl/doc/manual/intro.tex
2005-12-14 10:46:02 +00:00

519 lines
17 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. All example code can be found in the distribution directory
under \file{example/manual}. Here are the contents of a \myhdl\
simulation script called \file{hello1.py}:
\begin{verbatim}
from myhdl import Signal, delay, always, now, Simulation
def HelloWorld():
interval = delay(10)
@always(interval)
def sayHello():
print "%s Hello World!" % now()
return sayHello
inst = HelloWorld()
sim = Simulation(inst)
sim.run(30)
\end{verbatim}
When we run this simulation, we get the following output:
\begin{verbatim}
% python hello1.py
10 Hello World!
20 Hello World!
30 Hello World!
_SuspendSimulation: Simulated 30 timesteps
\end{verbatim}
The first line of the script imports a number of objects from the
\code{myhdl} package. In Python we can only use identifiers that are
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.}.
Then, we define a function called \function{HelloWorld}. In MyHDL,
classic functions are used to model hardware modules. In particular,
the parameter list is used to define the interface. In this first
example, the interface is empty.
Inside the top level function we declared a local function called
\function{sayHello} that defines the desired behavior. This function
is decorated with an \function{@always} decorator that has a delay
object as its parameter. The meaning is that the function will be
executed whenever the specified delay interval has expired.
Behind the curtains, the \function{@always} decorator creates a Python
\emph{generator} and reuses the name of the decorated function for
it. Generators are the fundamental objects in MyHDL, and we will say
much more about them further on.
Finally, the top level function returns the local generator. This code
pattern is the simplest incarnation of the basic MyHDL code pattern
to define the contents of a hardware module. We will describe the
general case further on.
In MyHDL, we create an \emph{instance} of a hardware module by calling
the corresponding function. In the example, variable \code{inst} refers
to an instance of \function{HelloWorld}. To simulate the instance, we
pass it as an argument to a \class{Simulation} object constructor. We
then run the simulation for the desired amount of timesteps.
\section{Signals, ports, and concurrency \label{intro-conc}}
In the previous section, we simulated a design that consisted
of a single generator. Of course,
real hardware descriptions are not like that: 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.
For this purpose \myhdl\
has a \class{Signal} object which is roughly modeled after VHDL
signals.
We will demonstrate the use of signals and the concept of concurrency
by extending and modifying our first example. We define two hardware
modules, one that drives a clock signal, and one that is sensitive
to a positive edge on a clock signal:
\begin{verbatim}
from myhdl import Signal, delay, always, now, Simulation
def ClkDriver(clk):
halfPeriod = delay(10)
@always(halfPeriod)
def driveClk():
clk.next = not clk
return driveClk
def HelloWorld(clk):
@always(clk.posedge)
def sayHello():
print "%s Hello World!" % now()
return sayHello
clk = Signal(0)
clkdriver_inst = ClkDriver(clk)
hello_inst = HelloWorld(clk)
sim = Simulation(clkdriver_inst, hello_inst)
sim.run(50)
\end{verbatim}
The clock driver function \function{ClkDriver} has a
clock signal as its parameter. This is how a
\emph{port} is modelled in MyHDL. The function
defines a generator
that continuously toggles a clock signal after a certain delay.
A 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 \function{HelloWorld} function is modified from the
first example. It now also takes a clock signal as parameter.
Its generator is made sensitive to a rising
\index{wait!for a rising edge}%
edge of the clock signal. This is specified by the
\code{posedge} attribute of a signal. The edge
specifier is the argument of the \code{@always}
decorator. As a result, the decorated function
will be executed on every rising clock edge.
The \code{clk} signal is constructed with an initial value
\code{0}. When creating an instance of each to the two
hardware modules, the same clock signal is passed as
the argument. The result is that the two instances
are now connected through the clock signal.
The \class{Simulation} object is constructed with the
two instances.
When we run the simulation, we get:
\begin{verbatim}
% python hello2.py
10 Hello World!
30 Hello World!
50 Hello World!
_SuspendSimulation: Simulated 50 timesteps
\end{verbatim}
\section{Parameters and hierarchy \label{intro-hier}}
We have seen that MyHDL uses functions to model hardware
modules. We have also seen that ports are modeled by using
signals as parameters. To make designs reusable we will also
want to use other objects as parameters. For example, we can
change the clock generator function to make it more general
and reusable, by making the clock period parametrizable, as
follows:
\begin{verbatim}
from myhdl import Signal, delay, instance, always, now, Simulation
def ClkDriver(clk, period=20):
lowTime = int(period/2)
highTime = period - lowTime
@instance
def driveClk():
while True:
yield delay(lowTime)
clk.next = 1
yield delay(highTime)
clk.next = 0
return driveClk
\end{verbatim}
In addition to the clock signal, the clock
\var{period} is a parameter with a default value of \code{20}.
As the low time of the clock may differ from the high time in case of
an odd period, we cannot use the \function{@always} decorator with a
single delay value anymore. Instead, the \function{driveClk} function
is now a generator function with an explicit definition of the desired
behavior. You can see that \function{driveClk} is a generator function (as
opposed to a classic function) because it contains \code{yield}
statements.
When a generator function is called, it returns a generator object. In
fact, this is mainly what the \function{@instance} decorator does. It
is less sophisticated than the \function{@always} decorator,
but it can be used to create a generator from any local generator
function.
The \code{yield} statement is a general Python construct, but MyHDL
uses it in a dedicated way. In MyHDL, it has a similar meaning as the
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, the generator
waits for a certain delay.
Not that to make sure that the generator runs ``forever'', we wrap its
behavior in a \code{while True} loop.
Likewise, we can define a general \function{Hello} function as follows:
\begin{verbatim}
def Hello(clk, to="World!"):
@always(clk.posedge)
def sayHello():
print "%s Hello %s" % (now(), to)
return sayHello
\end{verbatim}
We can create any number of instances by calling the functions with
the appropriate parameters. Hierarchy can be modeled by defining the
instances in a higher-level function, and returning them.
This pattern can be repeated for an arbitrary number of
hierarhical levels. Consequently, the general definition
of a \myhdl\ \dfn{instance} is recursive: an instance
\index{instance!defined}%
is either a sequence of instances, or a generator.
As an example, we will create a higher-level function with
four instances of the lower-level functions, and simulate it:
\begin{verbatim}
def greetings():
clk1 = Signal(0)
clk2 = Signal(0)
clkdriver_1 = ClkDriver(clk1) # positional and default association
clkdriver_2 = ClkDriver(clk=clk2, period=19) # named assocation
hello_1 = Hello(clk=clk1) # named and default association
hello_2 = Hello(to="MyHDL", clk=clk2) # named assocation
return clkdriver_1, clkdriver_2, hello_1, hello_2
inst = greetings()
sim = Simulation(inst)
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 argument 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!
_SuspendSimulation: Simulated 50 timesteps
\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}
from myhdl import Signal, delay, Simulation, always_comb, instance, intbv, bin
def bin2gray(B, G, width):
""" Gray encoder.
B -- input intbv signal, binary encoded
G -- output intbv signal, gray encoded
width -- bit width
"""
@always_comb
def logic():
for i in range(width):
G.next[i] = B[i+1] ^ B[i]
return logic
\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.
Furthermore, we introduce a third decorator: \function{@always_comb}.
It is used with a classic function and specifies that the
resulting generator should
\index{wait!for a signal value change}%
wait for a value change on any input signal. This is typically used to
describe
\index{combinatorial logic}%
combinatorial logic. The \function{@always_comb} decorator
automatically infers which signals are used as inputs.
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)
@instance
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}
sim = Simulation(testBench(width=3))
sim.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
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.
When an intbv object is sliced, a new intbv object is returned. This
new intbv always has a positive value, even when the original object
was negative.
\section{Summary and perspective}
\section{Some remarks on \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}