1
0
mirror of https://github.com/myhdl/myhdl.git synced 2025-01-24 21:52:56 +08:00

merged with myhdl.org repo

--HG--
branch : 0.8-dev
This commit is contained in:
cfelton 2012-05-10 04:10:36 -05:00
commit 61a4bad598
12 changed files with 705 additions and 647 deletions

View File

@ -21,6 +21,8 @@ transcript
*.0
*.bak
*.wlf
*.rej
*.out
doc/build
build/
dist/

View File

@ -34,15 +34,15 @@ master_doc = 'index'
# General substitutions.
project = 'MyHDL'
copyright = '2008-2010, Jan Decaluwe'
copyright = '2008-2012, Jan Decaluwe'
# The default replacements for |version| and |release|, also used in various
# other places throughout the built documents.
#
# The short X.Y version.
version = '0.7'
version = '0.8'
# The full version, including alpha/beta/rc tags.
release = '0.7'
release = '0.8-dev'
# There are two options for replacing |today|: either, you set today to some
# non-false value, then it is used:

View File

@ -35,9 +35,9 @@ or VHDL, using the function :func:`toVerilog` or :func:`toVHDL` from the MyHDL
library.
When the design is intended for implementation
a third-party :dfn:`synthesis tool` is used to convert the Verilog or VHDL
design to a gate implementation for an ASIC or FPGA. With this step, there is
a path from a hardware description in Python to an FPGA or ASIC implementation.
a third-party :dfn:`synthesis tool` is used to compile the Verilog or VHDL
model into an implementation for an ASIC or FPGA. With this step, there is
an automated path from a hardware description in Python to an FPGA or ASIC implementation.
The conversion does not start from source files, but from an instantiated design
that has been *elaborated* by the Python interpreter. The converter uses the

View File

@ -1,639 +1,40 @@
.. currentmodule:: myhdl
.. _model:
*******************
Modeling techniques
*******************
.. _model-structure:
Structural modeling
===================
.. index:: single: modeling; structural
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. ::
def top(...):
...
instance_1 = module_1(...)
instance_2 = module_2(...)
...
instance_n = module_n(...)
...
return instance_1, instance_2, ... , instance_n
Note that MyHDL uses conventional procedural techniques for modeling structure.
This makes it straightforward to model more complex cases.
.. _model-conf:
Conditional instantiation
-------------------------
.. index:: single: conditional instantiation
To model conditional instantiation, we can select the returned instance under
parameter control. For example::
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
.. _model-instarray:
Lists of instances and signals
------------------------------
.. index:: single: lists of instances and signals
Python lists are easy to create. We can use them to model lists of instances.
Suppose we have a top module that instantiates a single ``channel`` submodule,
as follows::
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
If we wanted to support an arbitrary number of channels, we can use lists of
signals and a list of instances, as follows::
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
.. _model-shadow-signals:
Converting between lists of signals and bit vectors
---------------------------------------------------
Compared to HDLs such as VHDL and Verilog, MyHDL signals are less
flexible for structural modeling. For example, slicing a signal
returns a slice of the current value. For behavioral code, this is
just fine. However, it implies that you cannot use such as slice in
structural descriptions. In other words, a signal slice cannot be used
as a signal.
In MyHDL, you can address such cases by a concept called
shadow signals. A shadow signal is constructed out of
other signals and follows their value changes automatically.
For example, a :class:`_SliceSignal` follows the value of
an index or a slice from another signal. Likewise,
A :class:`ConcatSignal` follows the
values of a number of signals as a concatenation.
As an example, suppose we have a system with N requesters that
need arbitration. Each requester has a ``request`` output
and a ``grant`` input. To connect them in the system, we can
use list of signals. For example, a list of request signals
can be constructed as follows::
request_list = [Signal(bool()) for i in range(M)]
Suppose that an arbiter module is available that is
instantiated as follows::
arb = arbiter(grant_vector, request_vector, clock, reset)
The ``request_vector`` input is a bit vector that can have
any of its bits asserted. The ``grant_vector`` is an output
bit vector with just a single bit asserted, or none.
Such a module is typically based on bit vectors because
they are easy to process in RTL code. In MyHDL, a bit vector
is modeled using the :class:`intbv` type.
We need a way to "connect" the list of signals to the
bit vector and vice versa. Of course, we can do this with explicit
code, but shadow signals can do this automatically. For
example, we can construct a ``request_vector`` as a
:class:`ConcatSignal` object::
request_vector = ConcatSignal(*reversed(request_list)
Note that we reverse the list first. This is done because the index range
of lists is the inverse of the range of :class:`intbv` bit vectors.
By reversing, the indices correspond to the same bit.
The inverse problem exist for the ``grant_vector``. It would be defined as follows::
grant_vector = Signal(intbv(0)[M:])
To construct a list of signals that are connected automatically to the
bit vector, we can use the :class:`Signal` call interface to construct
:class:`_SliceSignal` objects::
grant_list = [grant_vector(i) for i in range(M)]
Note the round brackets used for this type of slicing. Also, it may not be
necessary to construct this list explicitly. You can simply use
``grant_vector(i)`` in an instantiation.
To decide when to use normal or shadow signals, consider the data
flow. Use normal signals to connect to *outputs*. Use shadow signals to
transform these signals so that they can be used as *inputs*.
.. _model-infer-instlist:
Inferring the list of instances
-------------------------------
In MyHDL, instances have to be returned explicitly by a top level function. It
may be convenient to assemble the list of instances automatically. For this
purpose, MyHDL provides the function :func:`instances`. Using the first example
in this section, it is used as follows::
from myhdl import instances
def top(...):
...
instance_1 = module_1(...)
instance_2 = module_2(...)
...
instance_n = module_n(...)
...
return instances()
Function :func:`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.
.. _model-rtl:
RTL modeling
============
.. index:: single: modeling; RTL style
The present section describes how MyHDL supports RTL style modeling as is
typically used for synthesizable models.
.. _model-comb:
Combinatorial logic
-------------------
.. index:: single: combinatorial logic
.. _model-comb-templ:
Template
^^^^^^^^
Combinatorial logic is described with a code pattern as follows::
def top(<parameters>):
...
@always_comb
def combLogic():
<functional code>
...
return combLogic, ...
The :func:`always_comb` decorator describes combinatorial logic. [#]_. The
decorated function is a local function that specifies what happens when one of
the input signals of the logic changes. The :func:`always_comb` decorator
infers the input signals automatically. It returns a generator that is sensitive
to all inputs, and that executes the function whenever an input changes.
.. _model-comb-ex:
Example
^^^^^^^
The following is an example of a combinatorial multiplexer::
from myhdl import Signal, Simulation, delay, always_comb
def Mux(z, a, b, sel):
""" Multiplexer.
z -- mux output
a, b -- data inputs
sel -- control input: select a if asserted, otherwise b
"""
@always_comb
def muxLogic():
if sel == 1:
z.next = a
else:
z.next = b
return muxLogic
To verify it, we will simulate the logic with some random patterns. The
``random`` module in Python's standard library comes in handy for such purposes.
The function ``randrange(n)`` returns a random natural integer smaller than *n*.
It is used in the test bench code to produce random input values::
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)
test_1 = test()
sim = Simulation(mux_1, test_1)
sim.run()
Because of the randomness, the simulation output varies between runs [#]_. One
particular run produced the following output::
% 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
.. _model-seq:
Sequential logic
----------------
.. index:: single: sequential logic
.. _model-seq-templ:
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. ::
def top(<parameters>, clock, ..., reset, ...):
...
@always(clock.posedge, reset.negedge)
def seqLogic():
if reset == <active level>:
<reset code>
else:
<functional code>
...
return seqLogic, ...
.. _model-seq-ex:
Example
^^^^^^^
The following code is a description of an incrementer with enable, and an
asynchronous reset. ::
from random import randrange
from myhdl import *
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
"""
@always(clock.posedge, reset.negedge)
def incLogic():
if reset == ACTIVE_LOW:
count.next = 0
else:
if enable:
count.next = (count + 1) % n
return incLogic
For the test bench, we will use an independent clock generator, stimulus
generator, and monitor. After applying enough stimulus patterns, we can raise
the ``StopSimulation`` exception to stop the simulation run. The test bench for
a small incrementer and a small number of patterns is a follows::
def testbench():
count, enable, clock, reset = [Signal(intbv(0)) for i in range(4)]
inc_1 = Inc(count, enable, clock, reset, n=4)
HALF_PERIOD = delay(10)
@always(HALF_PERIOD)
def clockGen():
clock.next = not clock
@instance
def stimulus():
reset.next = ACTIVE_LOW
yield clock.negedge
reset.next = INACTIVE_HIGH
for i in range(12):
enable.next = min(1, randrange(3))
yield clock.negedge
raise StopSimulation
@instance
def monitor():
print "enable count"
yield reset.posedge
while 1:
yield clock.posedge
yield delay(1)
print " %s %s" % (enable, count)
return clockGen, stimulus, inc_1, monitor
tb = testbench()
def main():
Simulation(tb).run()
The simulation produces the following output::
% 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
.. _model-fsm:
Finite State Machine modeling
-----------------------------
.. index:: single: modeling; Finite State Machine
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::
>>> SEARCH, CONFIRM, SYNC = range(3)
>>> CONFIRM
1
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 *enumeration type*.
.. index:: single: enum(); example usage
MyHDL supports enumeration types by providing a function :func:`enum`. The
arguments to :func:`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::
>>> from myhdl import enum
>>> t_State = enum('SEARCH', 'CONFIRM', 'SYNC')
>>> t_State
<Enum: SEARCH, CONFIRM, SYNC>
>>> t_State.CONFIRM
CONFIRM
We can use this type to construct a state signal as follows::
state = Signal(t_State.SEARCH)
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 find the Start Of Frame (SOF) position of
an incoming frame of bytes. A sync pattern detector continuously looks for a
framing pattern and indicates it to the FSM with a ``syncFlag`` signal. When
found, the FSM moves from the initial ``SEARCH`` state to the ``CONFIRM`` state.
When the ``syncFlag`` is confirmed on the expected position, the FSM declares
``SYNC``, otherwise it falls back to the ``SEARCH`` state. This FSM can be
coded as follows::
from myhdl import *
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 -- FramerState output
syncFlag -- sync pattern found indication input
clk -- clock input
reset_n -- active low reset
"""
index = Signal(0) # position in frame
@always(clk.posedge, reset_n.negedge)
def FSM():
if reset_n == ACTIVE_LOW:
SOF.next = 0
index.next = 0
state.next = t_State.SEARCH
else:
index.next = (index + 1) % FRAME_SIZE
SOF.next = 0
if state == t_State.SEARCH:
index.next = 1
if syncFlag:
state.next = t_State.CONFIRM
elif state == t_State.CONFIRM:
if index == 0:
if syncFlag:
state.next = t_State.SYNC
else:
state.next = t_State.SEARCH
elif state == t_State.SYNC:
if index == 0:
if not syncFlag:
state.next = t_State.SEARCH
SOF.next = (index == FRAME_SIZE-1)
else:
raise ValueError("Undefined state")
return FSM
.. index:: single: waveform viewing
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,
:func:`traceSignals`. To explain how it works, recall that in MyHDL, an
instance is created by assigning the result of a function call to an instance
name. For example::
tb_fsm = testbench()
To enable VCD tracing, the instance should be created as follows instead::
tb_fsm = traceSignals(testbench)
Note that the first argument of :func:`traceSignals` consists of the uncalled
function. By calling the function under its control, :func:`traceSignals`
gathers information about the hierarchy and the signals to be traced. In
addition to a function argument, :func:`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::
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)
@always(delay(10))
def clkgen():
clk.next = not clk
@instance
def stimulus():
for i in range(3):
yield clk.posedge
for n in (12, 8, 8, 4):
syncFlag.next = 1
yield clk.posedge
syncFlag.next = 0
for i in range(n-1):
yield clk.posedge
raise StopSimulation
return framectrl, clkgen, stimulus
tb_fsm = traceSignals(testbench)
sim = Simulation(tb_fsm)
sim.run()
When we run the test bench, it generates a VCD file called
:file:`testbench.vcd`. When we load this file into :program:`gtkwave`, we can
view the waveforms:
.. image:: tbfsm.png
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 Python 2.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 :func:`str` function.
.. 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.
.. _model-hl:
*******************
High level modeling
===================
*******************
Introduction
============
.. index:: single: modeling; high level
To write synthesizable models in MyHDL, you should stick to
the RTL templates shown in :ref:`model-rtl`. However,
modeling in MyHDL is much more powerful than that.
Conceptually, MyHDL is a library for general event-driven
modeling and simulation of hardware systems.
There are many reasons why it can be useful to model at a
higher abstraction level than RTL. For example, you can
use MyHDL to verify architectural features, such as system
throughput, latency and buffer sizes. You can also write
high level models for specialized technology-dependent cores
that are not going through synthesis. Last but not least,
you can use MyHDL to write test benches that verify a system
model or a synthesizable description.
This chapter explores some of the options for high level
modeling with MyHDL.
.. _model-bfm:
Modeling with bus-functional procedures
---------------------------------------
=======================================
.. index:: single: bus-functional procedure
@ -916,7 +317,7 @@ Now, transmission of a new byte only starts when the previous one is received::
.. _model-mem:
Modeling memories with built-in types
-------------------------------------
=====================================
.. index:: single: modeling; memories
@ -1014,7 +415,7 @@ to get the size of a Python object.
.. _model-err:
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
@ -1113,7 +514,7 @@ detected by a regular check on the length of the list.
.. _model-obj:
Object oriented modeling
------------------------
========================
.. index:: single: modeling; object oriented

View File

@ -12,7 +12,9 @@ Contents:
preface
background
intro
modeling
structure
rtl
highlevel
unittest
cosimulation
conversion

View File

@ -47,9 +47,9 @@ 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.
Then, we define a function called :func:`HelloWorld`. In MyHDL, hardware
modules can be modeled using classic functions. In particular, the parameter list
is then used to define the interface. In this first example, the interface is empty.
.. index:: single: decorator; always

View File

@ -80,13 +80,18 @@ Waveform tracing
will be moved to a backup file by attaching a timestamp to it, before creating
the new file.
The ``traceSignals`` callable has the following attribute:
The ``traceSignals`` callable has the following attribute:
.. attribute:: traceSignals.name
.. attribute:: name
This attribute is used to overwrite the default top-level instance name and the
basename of the VCD output filename.
This attribute is used to overwrite the default top-level instance name and the
basename of the VCD output filename.
.. attribute:: timescale
This attribute is used to set the timescale corresponding to unit steps,
according to the VCD format. The default is "1ns".
.. _ref-model:
@ -641,9 +646,13 @@ Conversion
.. attribute:: name
This attribute is used to overwrite the default top-level instance name and the
basename of the Verilog output filename.
This attribute is used to overwrite the default top-level instance name and the
basename of the Verilog output filename.
.. attribute:: timescale
This attribute is used to set the timescale in Verilog format. Default is
"1ns/10ps".
.. function:: toVHDL(func[, *args][, **kwargs])

444
doc/source/manual/rtl.rst Normal file
View File

@ -0,0 +1,444 @@
.. currentmodule:: myhdl
.. _model-rtl:
************
RTL modeling
************
Introduction
============
.. index:: single: modeling; RTL style
RTL (Register Transfer Level) is a modeling abstraction level that
is typically used to write synthesizable models.
:dfn:`Synthesis` refers to the process by which an HDL description
is automatically compiled into an implementation for an ASIC or FPGA.
This chapter describes how MyHDL supports it.
.. _model-comb:
Combinatorial logic
===================
.. index:: single: combinatorial logic
.. _model-comb-templ:
Template
--------
Combinatorial logic is described with a code pattern as follows::
def top(<parameters>):
...
@always_comb
def combLogic():
<functional code>
...
return combLogic, ...
The :func:`always_comb` decorator describes combinatorial logic. [#]_. The
decorated function is a local function that specifies what happens when one of
the input signals of the logic changes. The :func:`always_comb` decorator
infers the input signals automatically. It returns a generator that is sensitive
to all inputs, and that executes the function whenever an input changes.
.. _model-comb-ex:
Example
-------
The following is an example of a combinatorial multiplexer::
from myhdl import Signal, Simulation, delay, always_comb
def Mux(z, a, b, sel):
""" Multiplexer.
z -- mux output
a, b -- data inputs
sel -- control input: select a if asserted, otherwise b
"""
@always_comb
def muxLogic():
if sel == 1:
z.next = a
else:
z.next = b
return muxLogic
To verify it, we will simulate the logic with some random patterns. The
``random`` module in Python's standard library comes in handy for such purposes.
The function ``randrange(n)`` returns a random natural integer smaller than *n*.
It is used in the test bench code to produce random input values::
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)
test_1 = test()
sim = Simulation(mux_1, test_1)
sim.run()
Because of the randomness, the simulation output varies between runs [#]_. One
particular run produced the following output::
% 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
.. _model-seq:
Sequential logic
================
.. index:: single: sequential logic
.. _model-seq-templ:
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. ::
def top(<parameters>, clock, ..., reset, ...):
...
@always(clock.posedge, reset.negedge)
def seqLogic():
if reset == <active level>:
<reset code>
else:
<functional code>
...
return seqLogic, ...
.. _model-seq-ex:
Example
-------
The following code is a description of an incrementer with enable, and an
asynchronous reset. ::
from random import randrange
from myhdl import *
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
"""
@always(clock.posedge, reset.negedge)
def incLogic():
if reset == ACTIVE_LOW:
count.next = 0
else:
if enable:
count.next = (count + 1) % n
return incLogic
For the test bench, we will use an independent clock generator, stimulus
generator, and monitor. After applying enough stimulus patterns, we can raise
the ``StopSimulation`` exception to stop the simulation run. The test bench for
a small incrementer and a small number of patterns is a follows::
def testbench():
count, enable, clock, reset = [Signal(intbv(0)) for i in range(4)]
inc_1 = Inc(count, enable, clock, reset, n=4)
HALF_PERIOD = delay(10)
@always(HALF_PERIOD)
def clockGen():
clock.next = not clock
@instance
def stimulus():
reset.next = ACTIVE_LOW
yield clock.negedge
reset.next = INACTIVE_HIGH
for i in range(12):
enable.next = min(1, randrange(3))
yield clock.negedge
raise StopSimulation
@instance
def monitor():
print "enable count"
yield reset.posedge
while 1:
yield clock.posedge
yield delay(1)
print " %s %s" % (enable, count)
return clockGen, stimulus, inc_1, monitor
tb = testbench()
def main():
Simulation(tb).run()
The simulation produces the following output::
% 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
.. _model-fsm:
Finite State Machine modeling
=============================
.. index:: single: modeling; Finite State Machine
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::
>>> SEARCH, CONFIRM, SYNC = range(3)
>>> CONFIRM
1
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 *enumeration type*.
.. index:: single: enum(); example usage
MyHDL supports enumeration types by providing a function :func:`enum`. The
arguments to :func:`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::
>>> from myhdl import enum
>>> t_State = enum('SEARCH', 'CONFIRM', 'SYNC')
>>> t_State
<Enum: SEARCH, CONFIRM, SYNC>
>>> t_State.CONFIRM
CONFIRM
We can use this type to construct a state signal as follows::
state = Signal(t_State.SEARCH)
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 find the Start Of Frame (SOF) position of
an incoming frame of bytes. A sync pattern detector continuously looks for a
framing pattern and indicates it to the FSM with a ``syncFlag`` signal. When
found, the FSM moves from the initial ``SEARCH`` state to the ``CONFIRM`` state.
When the ``syncFlag`` is confirmed on the expected position, the FSM declares
``SYNC``, otherwise it falls back to the ``SEARCH`` state. This FSM can be
coded as follows::
from myhdl import *
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 -- FramerState output
syncFlag -- sync pattern found indication input
clk -- clock input
reset_n -- active low reset
"""
index = Signal(0) # position in frame
@always(clk.posedge, reset_n.negedge)
def FSM():
if reset_n == ACTIVE_LOW:
SOF.next = 0
index.next = 0
state.next = t_State.SEARCH
else:
index.next = (index + 1) % FRAME_SIZE
SOF.next = 0
if state == t_State.SEARCH:
index.next = 1
if syncFlag:
state.next = t_State.CONFIRM
elif state == t_State.CONFIRM:
if index == 0:
if syncFlag:
state.next = t_State.SYNC
else:
state.next = t_State.SEARCH
elif state == t_State.SYNC:
if index == 0:
if not syncFlag:
state.next = t_State.SEARCH
SOF.next = (index == FRAME_SIZE-1)
else:
raise ValueError("Undefined state")
return FSM
.. index:: single: waveform viewing
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,
:func:`traceSignals`. To explain how it works, recall that in MyHDL, an
instance is created by assigning the result of a function call to an instance
name. For example::
tb_fsm = testbench()
To enable VCD tracing, the instance should be created as follows instead::
tb_fsm = traceSignals(testbench)
Note that the first argument of :func:`traceSignals` consists of the uncalled
function. By calling the function under its control, :func:`traceSignals`
gathers information about the hierarchy and the signals to be traced. In
addition to a function argument, :func:`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::
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)
@always(delay(10))
def clkgen():
clk.next = not clk
@instance
def stimulus():
for i in range(3):
yield clk.posedge
for n in (12, 8, 8, 4):
syncFlag.next = 1
yield clk.posedge
syncFlag.next = 0
for i in range(n-1):
yield clk.posedge
raise StopSimulation
return framectrl, clkgen, stimulus
tb_fsm = traceSignals(testbench)
sim = Simulation(tb_fsm)
sim.run()
When we run the test bench, it generates a VCD file called
:file:`testbench.vcd`. When we load this file into :program:`gtkwave`, we can
view the waveforms:
.. image:: tbfsm.png
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 Python 2.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 :func:`str` function.
.. 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.
.. rubric:: Footnotes
.. [#] The name :func:`always_comb` refers to a construct with similar semantics in
SystemVerilog.
.. [#] It also possible to have a reproducible random output, by explicitly providing a
seed value. See the documentation of the ``random`` module.

View File

@ -0,0 +1,196 @@
.. currentmodule:: myhdl
.. _model-structure:
*******************
Structural modeling
*******************
.. index:: single: modeling; structural
Introduction
============
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. ::
def top(...):
...
instance_1 = module_1(...)
instance_2 = module_2(...)
...
instance_n = module_n(...)
...
return instance_1, instance_2, ... , instance_n
Note that MyHDL uses conventional procedural techniques for modeling structure.
This makes it straightforward to model more complex cases.
.. _model-conf:
Conditional instantiation
=========================
.. index:: single: conditional instantiation
To model conditional instantiation, we can select the returned instance under
parameter control. For example::
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
.. _model-instarray:
Lists of instances and signals
------------------------------
.. index:: single: lists of instances and signals
Python lists are easy to create. We can use them to model lists of instances.
Suppose we have a top module that instantiates a single ``channel`` submodule,
as follows::
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
If we wanted to support an arbitrary number of channels, we can use lists of
signals and a list of instances, as follows::
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
.. _model-shadow-signals:
Converting between lists of signals and bit vectors
===================================================
Compared to HDLs such as VHDL and Verilog, MyHDL signals are less
flexible for structural modeling. For example, slicing a signal
returns a slice of the current value. For behavioral code, this is
just fine. However, it implies that you cannot use such as slice in
structural descriptions. In other words, a signal slice cannot be used
as a signal.
In MyHDL, you can address such cases by a concept called
shadow signals. A shadow signal is constructed out of
other signals and follows their value changes automatically.
For example, a :class:`_SliceSignal` follows the value of
an index or a slice from another signal. Likewise,
A :class:`ConcatSignal` follows the
values of a number of signals as a concatenation.
As an example, suppose we have a system with N requesters that
need arbitration. Each requester has a ``request`` output
and a ``grant`` input. To connect them in the system, we can
use list of signals. For example, a list of request signals
can be constructed as follows::
request_list = [Signal(bool()) for i in range(M)]
Suppose that an arbiter module is available that is
instantiated as follows::
arb = arbiter(grant_vector, request_vector, clock, reset)
The ``request_vector`` input is a bit vector that can have
any of its bits asserted. The ``grant_vector`` is an output
bit vector with just a single bit asserted, or none.
Such a module is typically based on bit vectors because
they are easy to process in RTL code. In MyHDL, a bit vector
is modeled using the :class:`intbv` type.
We need a way to "connect" the list of signals to the
bit vector and vice versa. Of course, we can do this with explicit
code, but shadow signals can do this automatically. For
example, we can construct a ``request_vector`` as a
:class:`ConcatSignal` object::
request_vector = ConcatSignal(*reversed(request_list)
Note that we reverse the list first. This is done because the index range
of lists is the inverse of the range of :class:`intbv` bit vectors.
By reversing, the indices correspond to the same bit.
The inverse problem exist for the ``grant_vector``. It would be defined as follows::
grant_vector = Signal(intbv(0)[M:])
To construct a list of signals that are connected automatically to the
bit vector, we can use the :class:`Signal` call interface to construct
:class:`_SliceSignal` objects::
grant_list = [grant_vector(i) for i in range(M)]
Note the round brackets used for this type of slicing. Also, it may not be
necessary to construct this list explicitly. You can simply use
``grant_vector(i)`` in an instantiation.
To decide when to use normal or shadow signals, consider the data
flow. Use normal signals to connect to *outputs*. Use shadow signals to
transform these signals so that they can be used as *inputs*.
.. _model-infer-instlist:
Inferring the list of instances
===============================
In MyHDL, instances have to be returned explicitly by a top level function. It
may be convenient to assemble the list of instances automatically. For this
purpose, MyHDL provides the function :func:`instances`. Using the first example
in this section, it is used as follows::
from myhdl import instances
def top(...):
...
instance_1 = module_1(...)
instance_2 = module_2(...)
...
instance_n = module_n(...)
...
return instances()
Function :func:`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.

View File

@ -400,7 +400,7 @@ Thanks to Francesco Balau for packaging MyHDL for Ubuntu.
I would also like to thank `Easics`_ for
the opportunity to use MyHDL in industrial projects.
.. _`Easics`: http:www.easics.com
.. _`Easics`: http://www.easics.com

View File

@ -29,6 +29,7 @@ def test_dff():
return dff_inst, clkgen, stimulus
def simulate(timesteps):
traceSignals.timescale = "1ps"
tb = traceSignals(test_dff)
sim = Simulation(tb)
sim.run(timesteps)

View File

@ -45,10 +45,13 @@ _error.MultipleTraces = "Cannot trace multiple instances simultaneously"
class _TraceSignalsClass(object):
__slot__ = ("name", )
__slot__ = ("name",
"timescale",
)
def __init__(self):
self.name = None
self.timescale = "1ns"
def __call__(self, dut, *args, **kwargs):
global _tracing
@ -82,7 +85,7 @@ class _TraceSignalsClass(object):
vcdfile = open(vcdpath, 'w')
_simulator._tracing = 1
_simulator._tf = vcdfile
_writeVcdHeader(vcdfile)
_writeVcdHeader(vcdfile, self.timescale)
_writeVcdSigs(vcdfile, h.hierarchy)
finally:
_tracing = 0
@ -111,7 +114,7 @@ def _namecode(n):
code = _codechars[r] + code
return code
def _writeVcdHeader(f):
def _writeVcdHeader(f, timescale):
print >> f, "$date"
print >> f, " %s" % time.asctime()
print >> f, "$end"
@ -119,7 +122,7 @@ def _writeVcdHeader(f):
print >> f, " MyHDL %s" % __version__
print >> f, "$end"
print >> f, "$timescale"
print >> f, " 1ns"
print >> f, " %s" % timescale
print >> f, "$end"
print >> f