mirror of
https://github.com/myhdl/myhdl.git
synced 2024-12-14 07:44:38 +08:00
744 lines
25 KiB
ReStructuredText
744 lines
25 KiB
ReStructuredText
.. currentmodule:: myhdl
|
|
|
|
|
|
.. _intro:
|
|
|
|
*********************
|
|
Introduction to MyHDL
|
|
*********************
|
|
|
|
|
|
.. _intro-basic:
|
|
|
|
A basic MyHDL simulation
|
|
========================
|
|
|
|
We will introduce MyHDL with a classic ``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`::
|
|
|
|
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)
|
|
|
|
When we run this simulation, we get the following output::
|
|
|
|
% python hello1.py
|
|
10 Hello World!
|
|
20 Hello World!
|
|
30 Hello World!
|
|
_SuspendSimulation: Simulated 30 timesteps
|
|
|
|
The first line of the script imports a number of objects from the ``myhdl``
|
|
package. In Python we can only use identifiers that are literally defined in the
|
|
source file [#]_.
|
|
|
|
Then, we define a function called :func:`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.
|
|
|
|
.. index:: single: decorator; always
|
|
|
|
Inside the top level function we declare a local function called
|
|
:func:`sayHello` that defines the desired behavior. This function is decorated
|
|
with an :func:`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 :func:`always` decorator creates a Python *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 is the
|
|
simplest case 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 *instance* of a hardware module by calling the
|
|
corresponding function. In the example, variable ``inst`` refers to an instance
|
|
of :func:`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.
|
|
|
|
|
|
.. _intro-conc:
|
|
|
|
Signals, ports, and concurrency
|
|
===============================
|
|
|
|
In the previous section, we simulated a design with a single generator and no
|
|
concurrency. Real hardware descriptions are typically
|
|
massively concurrent. MyHDL supports this by allowing an arbitrary number of
|
|
concurrently running generators.
|
|
|
|
With concurrency comes the problem of deterministic communication. Hardware
|
|
languages use special objects to support deterministic communication between
|
|
concurrent code. In particular, MyHDL has a :class:`Signal` object which is
|
|
roughly modeled after VHDL signals.
|
|
|
|
We will demonstrate signals and 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::
|
|
|
|
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)
|
|
|
|
.. index::
|
|
single: VHDL; signal assignment
|
|
single: Verilog; non-blocking assignment
|
|
|
|
The clock driver function :func:`ClkDriver` has a clock signal as its parameter.
|
|
This is how a *port* is modeled 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 ``next`` attribute. This is the MyHDL
|
|
equivalent of the VHDL signal assignment and the Verilog non-blocking
|
|
assignment.
|
|
|
|
.. index:: single: wait; for a rising edge
|
|
|
|
The :func:`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
|
|
edge of the clock signal. This is specified by the ``posedge`` attribute of a
|
|
signal. The edge specifier is the argument of the ``always`` decorator. As a
|
|
result, the decorated function will be executed on every rising clock edge.
|
|
|
|
The ``clk`` signal is constructed with an initial value ``0``. When creating an
|
|
instance of each hardware module, the same clock signal is passed as the
|
|
argument. The result is that the 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::
|
|
|
|
% python hello2.py
|
|
10 Hello World!
|
|
30 Hello World!
|
|
50 Hello World!
|
|
_SuspendSimulation: Simulated 50 timesteps
|
|
|
|
|
|
.. _intro-hier:
|
|
|
|
Parameters and hierarchy
|
|
========================
|
|
|
|
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 parameterizable, as follows::
|
|
|
|
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
|
|
|
|
In addition to the clock signal, the clock period is a parameter, with a default
|
|
value of ``20``.
|
|
|
|
.. index:: single: decorator; instance
|
|
|
|
As the low time of the clock may differ from the high time in case of an odd
|
|
period, we cannot use the :func:`always` decorator with a single delay value
|
|
anymore. Instead, the :func:`driveClk` function is now a generator function with
|
|
an explicit definition of the desired behavior. It is decorated with the
|
|
:func:`instance` decorator. You can see that :func:`driveClk` is a generator
|
|
function because it contains ``yield`` statements.
|
|
|
|
When a generator function is called, it returns a generator object. This is
|
|
basically what the :func:`instance` decorator does. It is less sophisticated
|
|
than the :func:`always` decorator, but it can be used to create a generator from
|
|
any local generator function.
|
|
|
|
The ``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.
|
|
|
|
Note that to make sure that the generator runs "forever", we wrap its behavior
|
|
in a ``while True`` loop.
|
|
|
|
Similarly, we can define a general :func:`Hello` function as follows::
|
|
|
|
def Hello(clk, to="World!"):
|
|
|
|
@always(clk.posedge)
|
|
def sayHello():
|
|
print "%s Hello %s" % (now(), to)
|
|
|
|
return sayHello
|
|
|
|
.. index:: single: instance; defined
|
|
|
|
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 hierarchical levels. Consequently, the general definition of
|
|
a MyHDL instance is recursive: an instance 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::
|
|
|
|
def greetings():
|
|
|
|
clk1 = Signal(0)
|
|
clk2 = Signal(0)
|
|
|
|
clkdriver_1 = ClkDriver(clk1) # positional and default association
|
|
clkdriver_2 = ClkDriver(clk=clk2, period=19) # named association
|
|
hello_1 = Hello(clk=clk1) # named and default association
|
|
hello_2 = Hello(to="MyHDL", clk=clk2) # named association
|
|
|
|
return clkdriver_1, clkdriver_2, hello_1, hello_2
|
|
|
|
|
|
inst = greetings()
|
|
sim = Simulation(inst)
|
|
sim.run(50)
|
|
|
|
As in standard Python, positional or named parameter association can be used in
|
|
instantiations, or a mix of both [#]_. All these styles are demonstrated in the
|
|
example above. 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::
|
|
|
|
% 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
|
|
|
|
.. 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.
|
|
|
|
.. index:: single: module; in Python versus hardware design
|
|
|
|
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, a module
|
|
is a reusable block of hardware with a well defined interface. It can be reused
|
|
in another module by :dfn:`instantiating` it.
|
|
|
|
.. index:: single: instance; in Python versus hardware design
|
|
|
|
An :dfn:`instance` in Python (and other object-oriented languages) refers to the
|
|
object created by a 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.
|
|
|
|
|
|
.. _intro-bit:
|
|
|
|
Bit oriented operations
|
|
=======================
|
|
|
|
.. _intro-intbv:
|
|
|
|
The :class:`intbv` class
|
|
------------------------
|
|
|
|
|
|
.. index:: single: intbv; basic usage
|
|
|
|
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:`intbv` works transparently with other integer-like types. Like
|
|
class :class:`int`, it provides access to the underlying two's complement
|
|
representation for bitwise operations. However, unlike :class:`int`, it is
|
|
a mutable type. This means that its value can be changed after object
|
|
creation, through methods and operators such as slice assignment.
|
|
|
|
:class:`intbv` supports the same operators as :class:`int` for arithmetic.
|
|
In addition, it provides a number of features to make it
|
|
suitable for hardware design. First, the range of allowed values can
|
|
be constrained. This makes it possible to check the value at run time
|
|
during simulation. Moreover, back end tools can determine the smallest
|
|
possible bit width for representing the object.
|
|
Secondly, it supports bit level operations by providing an indexing
|
|
and slicing interface.
|
|
|
|
:class:`intbv` objects are constructed in general as follows::
|
|
|
|
intbv([val=None] [, min=None] [, max=None])
|
|
|
|
*val* is the initial value. *min* and *max* can be used to constrain
|
|
the value. Following the Python conventions, *min* is inclusive, and
|
|
*max* is exclusive. Therefore, the allowed value range is *min* .. *max*-1.
|
|
|
|
Let's us look at some examples. First, an unconstrained :class:`intbv`
|
|
object is created as follows:
|
|
|
|
>>> a = intbv(24)
|
|
|
|
.. index::
|
|
single: intbv; min
|
|
single: intbv; max
|
|
single: intbv; bit width
|
|
|
|
After object creation, *min* and *max* are available as attributes for
|
|
inspection. Also, the standard Python function :func:`len` can be used
|
|
to determine the bit width. If we inspect the previously created
|
|
object, we get::
|
|
|
|
>>> print a.min
|
|
None
|
|
>>> print a.max
|
|
None
|
|
>>> print len(a)
|
|
0
|
|
|
|
As the instantiation was unconstrained, the *min* and *max* attributes
|
|
are undefined. Likewise, the bit width is undefined, which is indicated
|
|
by a return value ``0``.
|
|
|
|
A constrained :class:`intbv` object is created as follows:
|
|
|
|
>>> a = intbv(24, min=0, max=25)
|
|
|
|
|
|
Inspecting the object now gives::
|
|
|
|
>>> a.min
|
|
0
|
|
>>> a.max
|
|
25
|
|
>>> len(a)
|
|
5
|
|
|
|
We see that the allowed value range is 0 .. 24, and that 5 bits are
|
|
required to represent the object.
|
|
|
|
Sometimes hardware engineers prefer to constrain an object by defining
|
|
its bit width directly, instead of the range of allowed values.
|
|
The following example shows how to do that::
|
|
|
|
>>> a = intbv(24)[5:]
|
|
|
|
What actually happens here is that first an unconstrained :class:`intbv`
|
|
is created, which is then sliced. Slicing an :class:`intbv` returns a new
|
|
:class:`intbv` with the constraints set up appropriately.
|
|
Inspecting the object now shows::
|
|
|
|
>>> a.min
|
|
0
|
|
>>> a.max
|
|
32
|
|
>>> len(a)
|
|
5
|
|
|
|
|
|
Note that the *max* attribute is 32, as with 5 bits it is
|
|
possible to represent the range 0 .. 31.
|
|
Creating an
|
|
:class:`intbv` this way has the disadvantage that only positive value
|
|
ranges can be specified. Slicing is described in more detail
|
|
in :ref:`intro-slicing`.
|
|
|
|
To summarize, there are two ways to constrain an :class:`intbv` object: by
|
|
defining its bit width, or by defining its value range. The bit
|
|
width method is more traditional in hardware design. However, there
|
|
are two reasons to use the range method instead: to represent
|
|
negative values as observed above, and for fine-grained control over the
|
|
value range.
|
|
|
|
Fine-grained control over the value range permits better error
|
|
checking, as there is no need for the *min* and *max* bounds
|
|
to be symmetric or powers of 2. In all cases, the bit width
|
|
is set appropriately to represent all values in
|
|
the range. For example::
|
|
|
|
|
|
>>> a = intbv(6, min=0, max=7)
|
|
>>> len(a)
|
|
3
|
|
>>> a = intbv(6, min=-3, max=7)
|
|
>>> len(a)
|
|
4
|
|
>>> a = intbv(6, min=-13, max=7)
|
|
>>> len(a)
|
|
5
|
|
|
|
|
|
.. _intro-indexing:
|
|
|
|
Bit indexing
|
|
------------
|
|
|
|
.. index:: single: bit indexing
|
|
|
|
As an example, we will consider the design of a Gray encoder. The following code
|
|
is a Gray encoder modeled in MyHDL::
|
|
|
|
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
|
|
|
|
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.
|
|
|
|
.. index::
|
|
single: decorator; always_comb
|
|
single: wait; for a signal value change
|
|
single: combinatorial logic
|
|
|
|
Furthermore, we introduce a third decorator: :func:`always_comb`. It is used
|
|
with a classic function and specifies that the resulting generator should wait
|
|
for a value change on any input signal. This is typically used to describe
|
|
combinatorial logic. The :func:`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 ``0``.
|
|
|
|
To verify the Gray encoder, we write a test bench that prints input and output
|
|
for all possible input values::
|
|
|
|
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
|
|
|
|
We use the conversion function :func:`bin` to get a binary string representation of
|
|
the signal values. This function is exported by the :mod:`myhdl` package and
|
|
supplements the standard Python :func:`hex` and :func:`oct` conversion functions.
|
|
|
|
As a demonstration, we set up a simulation for a small width::
|
|
|
|
sim = Simulation(testBench(width=3))
|
|
sim.run()
|
|
|
|
The simulation produces the following output::
|
|
|
|
% 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
|
|
|
|
|
|
.. _intro-slicing:
|
|
|
|
Bit slicing
|
|
-----------
|
|
|
|
.. index::
|
|
single: bit slicing
|
|
single: concat(); example usage
|
|
|
|
For a change, we will use a traditional function as an example to illustrate
|
|
slicing. The following function calculates the HEC byte of an ATM header. ::
|
|
|
|
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
|
|
|
|
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
|
|
*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 ``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 ``hex[8:]`` has exactly
|
|
``8`` bits. Likewise, the slice ``hex[7:2]`` has ``7-2=5`` bits. You can think
|
|
about it as follows: for a slice ``[i:j]``, only bits below index ``i`` are
|
|
included, and the bit with index ``j`` is the last bit included.
|
|
|
|
When an :class:`intbv` object is sliced, a new :class:`intbv` object is returned.
|
|
This new :class:`intbv` object is always positive, and the value bounds are
|
|
set up in accordance with the bit width specified by the slice. For example::
|
|
|
|
>>> a = intbv(6, min=-3, max=7)
|
|
>>> len(a)
|
|
4
|
|
>>> b = a[4:]
|
|
>>> b
|
|
intbv(6L)
|
|
>>> len(b)
|
|
4
|
|
>>> b.min
|
|
0
|
|
>>> b.max
|
|
16
|
|
|
|
In the example, the original object is sliced with a slice equal to its bit width.
|
|
The returned object has the same value and bit width, but its value
|
|
range consists of all positive values that can be represented by
|
|
the bit width.
|
|
|
|
The object returned by a slice is positive, even when the
|
|
original object is negative::
|
|
|
|
>>> a = intbv(-3)
|
|
>>> bin(a, width=5)
|
|
'11101'
|
|
>>> b = a[5:]
|
|
>>> b
|
|
intbv(29L)
|
|
>>> bin(b)
|
|
'11101'
|
|
|
|
The bit pattern of the two objects is identical within the bit width,
|
|
but their values have opposite sign.
|
|
|
|
.. _intro-signed:
|
|
|
|
Unsigned and signed representation
|
|
----------------------------------
|
|
|
|
.. index::
|
|
single: intbv; intbv.signed
|
|
|
|
:class:`intbv` is designed to be as high level as possible. The underlying
|
|
value of an :class:`intbv` object is a Python :class:`int`, which is
|
|
represented as a two's complement number with "indefinite" bit
|
|
width. The range bounds are only used for error checking, and to
|
|
calculate the minimum required bit width for representation. As a
|
|
result, arithmetic can be performed like with normal integers.
|
|
|
|
In contrast, HDLs such as Verilog and VHDL typically require designers
|
|
to deal with representational issues, especially for synthesizable code.
|
|
They provide low-level types like ``signed`` and ``unsigned`` for
|
|
arithmetic. The rules for arithmetic with such types are much more
|
|
complicated than with plain integers.
|
|
|
|
In some cases it can be useful to interpret :class:`intbv` objects
|
|
in terms of "signed" and "unsigned". Basically, it depends on attribute *min*.
|
|
if *min* < 0, then the object is "signed", otherwise it is "unsigned".
|
|
In particular, the bit width of a "signed" object will account for
|
|
a sign bit, but that of an "unsigned" will not, because that would
|
|
be redundant. From earlier sections, we have learned that the
|
|
return value from a slicing operation is always "unsigned".
|
|
|
|
In some applications, it is desirable to convert an "unsigned"
|
|
:class:`intbv` to a "signed", in other words, to interpret the msb bit
|
|
as a sign bit. The msb bit is the highest order bit within the object's
|
|
bit width. For this purpose, :class:`intbv` provides the
|
|
:meth:`intbv.signed` method. For example::
|
|
|
|
>>> a = intbv(12, min=0, max=16)
|
|
>>> bin(a)
|
|
'1100'
|
|
>>> b = a.signed()
|
|
>>> b
|
|
-4
|
|
>>> bin(b, width=4)
|
|
'1100'
|
|
|
|
:meth:`intbv.signed` extends the msb bit into the higher-order bits of the
|
|
underlying object value, and returns the result as an integer.
|
|
Naturally, for a "signed" the return value will always be identical
|
|
to the original value, as it has the sign bit already.
|
|
|
|
As an example let's take a 8 bit wide data bus that would be modeled as
|
|
follows::
|
|
|
|
data_bus = intbv(0)[8:]
|
|
|
|
Now consider that a complex number is transfered over this data
|
|
bus. The upper 4 bits of the data bus are used for the real value and
|
|
the lower 4 bits for the imaginary value. As real and imaginary values
|
|
have a positive and negative value range, we can slice them off from
|
|
the data bus and convert them as follows::
|
|
|
|
real.next = data_bus[8:4].signed()
|
|
imag.next = data_bus[4:].signed()
|
|
|
|
|
|
.. _intro-python:
|
|
|
|
Some concluding remarks on MyHDL and 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 ``myhdl``. Moreover, it is a design goal
|
|
to keep the ``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:
|
|
|
|
* Python is a very powerful high level language. This translates into high
|
|
productivity and elegant solutions to complex problems.
|
|
|
|
* 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.
|
|
|
|
* 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.
|
|
|
|
* 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.
|
|
|
|
|
|
.. _intro-summary:
|
|
|
|
Summary and perspective
|
|
=======================
|
|
|
|
Here is an overview of what we have learned in this chapter:
|
|
|
|
* Generators are the basic building blocks of MyHDL models. They provide the way
|
|
to model massive concurrency and sensitivity lists.
|
|
|
|
* MyHDL provides decorators that create useful generators from local functions.
|
|
|
|
* Hardware structure and hierarchy is described with classic Python functions.
|
|
|
|
* :class:`Signal` objects are used to communicate between concurrent generators.
|
|
|
|
* :class:`intbv` objects are used to describe bit-oriented operations.
|
|
|
|
* A :class:`Simulation` object is used to simulate MyHDL models.
|
|
|
|
These concepts are sufficient to start describing and simulating MyHDL models.
|
|
|
|
However, there is much more to MyHDL. Here is an overview of what can be learned
|
|
from the following chapters:
|
|
|
|
* MyHDL supports sophisticated and high level modeling techniques. This is
|
|
described in Chapter :ref:`model`.
|
|
|
|
* MyHDL enables the use of modern software verification techniques, such as unit
|
|
testing, on hardware designs. This is the topic of Chapter :ref:`unittest`.
|
|
|
|
* It is possible to co-simulate MyHDL models with other HDL languages such as
|
|
Verilog and VHDL. This is described in Chapter :ref:`cosim`.
|
|
|
|
* Last but not least, MyHDL models can be converted to Verilog, providing a path
|
|
to a silicon implementation. This is the topic of Chapter :ref:`conv`.
|
|
|
|
.. rubric:: Footnotes
|
|
|
|
.. [#] The exception is the ``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 ``myhdl`` falls into that category.
|
|
|
|
.. [#] All positional parameters have to go before any named parameter.
|
|
|