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

588 lines
20 KiB
ReStructuredText
Raw Normal View History

2015-07-07 08:38:09 +02:00
.. currentmodule:: myhdl
.. _new05:
=======================
What's new in MyHDL 0.5
=======================
:Author: Jan Decaluwe
Modeling
========
Creating generators with decorators
-----------------------------------
Introduction
~~~~~~~~~~~~
Python 2.4 introduced a new feature called *decorators*. A decorator consists
of special syntax in front of a function declaration. It refers to a decorator
function. The decorator function automatically transforms the declared function
into some other callable object.
MyHDL 0.5 defines decorators that can be used to create ready-to-run generators
from local functions. The use of decorators results in clearer, more explicit
code.
The ``@instance`` decorator
~~~~~~~~~~~~~~~~~~~~~~~~~~~
The ``@instance`` decorator is the most general decorator in MyHDL.
In earlier versions of MyHDL, local generator functions are typically used as
follows:
::
def top(...):
...
def gen_func():
<generator body>
...
inst = gen_func()
...
return inst, ...
Note that the generator function :func:`gen_func()` is intended to be called
exactly once, and that its name is not necessary anymore afterwards. In MyHDL
0.5, this can be rewritten as follows, using the ``@instance`` decorator:
::
def top(...):
...
@instance
def inst():
<generator body>
...
return inst, ...
Behind the curtains, the ``@instance`` decorator automatically creates a
generator by calling the generator function, and by reusing its name. Note that
it is placed immediately in front of the corresponding generator function,
resulting in clearer code.
The ``@always`` decorator
~~~~~~~~~~~~~~~~~~~~~~~~~
The ``@always`` decorator is a specialized decorator that targets a very
popular coding pattern. It is used as follows:
::
def top(...):
...
@always(event1, event2, ...)
def inst()
<body>
...
return inst, ...
The meaning of this code is that the decorated function is executed whenever
one of the events occurs. The argument list of the decorator corresponds to the
sensitivity list. Appropriate events are edge specifiers, signals, and delay
objects. The decorated function is a classic function instead of a generator
function.
Behind the curtains, the ``always`` decorator creates an enclosing ``while
True`` loop automatically, and inserts a ``yield`` statement with the
sensitivity list.
The ``@always_comb`` decorator
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
The ``@always_comb`` decorator is used to describe combinatorial logic. It is
nothing else than the :func:`always_comb` function from earlier MyHDL versions
used as a decorator:
::
def top(...):
...
@always_comb
def comb_inst():
<combinatorial body>
...
return comb_inst, ...
The ``@always_comb`` decorator infers the inputs of the combinatorial logic and
the corresponding sensitivity list automatically.
More information
~~~~~~~~~~~~~~~~
For more information about the background and the design decisions regarding
MyHDL decorators, see `mep-100`_.
Recommended style changes
-------------------------
Decorator usage
~~~~~~~~~~~~~~~
The use of decorators has clear advantages in terms of code clarity. Therefore,
it is recommended that all local generators be created using decorators.
Edge specifiers
~~~~~~~~~~~~~~~
Signal edges are typically specified using the :func:`posedge()` and
:func:`negedge()` functions in MyHDL. However, these functions are simply
wrappers around attributes with the same name. The design decision to use
functions have been reviewed and found questionable. In fact, using the
attributes directly instead has significant advantages, listed in order of
increasing significance:
* one character less to type
* more object-oriented style
* less symbols in the ``myhdl`` namespace
* no brackets, which is better for clarity
* no function call overhead
From MyHDL 0.5 on, it is therefore recommended to use the edge specifier
attributes. For example:
::
clk.posedge # instead of posedge(clk)
rst.negedge # instead of negedge(clk)
Deprecated features
-------------------
Edge specifier functions
~~~~~~~~~~~~~~~~~~~~~~~~
Functions :func:`posedge()` and :func:`negedge()` are deprecated. As discussed
before, it is recommended to use the signal attributes with the same name
instead.
In MyHDL 0.5, the functions will be removed from all documentation and
examples. They will be removed from MyHDL in a future version.
processes() function
~~~~~~~~~~~~~~~~~~~~
Function :func:`processes()` is deprecated. It looks up local generator functions and
calls them to create generators. When MyHDL 0.5 decorators are used as
recommended, this functionality becomes superfluous as it is part of the
decorator functionality.
On the other hand, the companion function :func:`instances()` continues to be
relevant and useful. It merely looks up instances in a local namespace. Having
a single lookup function will also improve usability.
In MyHDL 0.5, the :func:`processes()` function will be removed from all documentation
and examples. It will be removed from MyHDL in a future version.
Backwards incompatible changes
------------------------------
Default initial value of an intbv instance
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
It has always been possible to construct an :class:`intbv` instance without
explicit initial value:
::
m = intbv()
Prior to MyHDL 0.4, the default initial value was ``0``. In MyHDL 0.5, this has
been changed to ``None``. This is a first step towards support for ``X`` and
``Z`` functionality as found in other HDLs. This may be occasionally useful :-)
For example, it may be meaningful to initialize memory locations to ``None`` to
make sure that they will not be read before they have been initialized. If
``None`` is supported, it seems also logical to make it the default initial
value, to be interpreted as "No value".
**Warning**: if you have calls like the above in your code, it will probably
fail with MyHDL 0.5, as many integer-like operations are not supported with
`None` values.
**Workaround**: change your existing code by using ``0`` as an explicit initial
value, like so::
m = intbv(0)
Python version
--------------
Because of the usage of new features such as decorators, MyHDL 0.5 requires
upgrading to Python 2.4 or higher.
Verilog conversion
==================
Decorator support
-----------------
The Verilog convertor was enhanced to support the proposed decorators.
Mapping a list of signals to a RAM memory
-----------------------------------------
Certain synthesis tools can map Verilog memories to memory structures. For
example, this is supported by the Xilinx toolset. To support this interesting
feature, the Verilog convertor now maps lists of signals in MyHDL to Verilog
memories.
The following MyHDL example is a ram model that uses a list of signals to model
the internal memory.
::
def RAM(dout, din, addr, we, clk, depth=128):
""" Ram model """
mem = [Signal(intbv(0)[8:]) for i in range(depth)]
@always(clk.posedge)
def write():
if we:
mem[int(addr)].next = din
@always_comb
def read():
dout.next = mem[int(addr)]
return write, read
With the appropriate signal definitions for the interface ports, it is mapped
by :func:`toVerilog` to the following Verilog code. Note how the list of signals
``mem`` is mapped to a Verilog memory.
::
module RAM (
dout,
din,
addr,
we,
clk
);
output [7:0] dout;
wire [7:0] dout;
input [7:0] din;
input [6:0] addr;
input we;
input clk;
reg [7:0] mem [0:128-1];
always @(posedge clk) begin: _RAM_write
if (we) begin
mem[addr] <= din;
end
end
assign dout = mem[addr];
endmodule
Lists of signals can also be used in MyHDL to elegantly describe iterative
hierarchical structures. (See the MyHDL manual.) However, there is an important
difference: such signals will have a name at some level of the hierarchy, while
in the case described above, the individual signals are anonymous. The
:func:`toVerilog` convertor detects which case we are in. In the first case,
the individual signals will still be declared in the Verilog output, using the
highest-level hierarchical name. It is only in the second case that the list of
signals is declared as a Verilog memory.
Mapping combinatorial logic to assign statements
------------------------------------------------
When possible, combinatorial logic is now converted to Verilog assign
statements. There are two conditions for this to happen. First, the logic has
to be explicitly described as a combinatorial function using the
``@always_comb`` decorator. Secondly, the function has to be simple enough so
that a mapping to assign statements is possible: only signal assignments are
permitted. Otherwise, a Verilog always block is used as previously.
See the RAM model of the previous section for an example.
This was done because certain synthesis tools require assign statements to
recognize code templates.
Mapping a tuple of integers to a ROM memory
--------------------------------------------
Some synthesis tools, such as the Xilinx tool, can infer a ROM memory from a
case statement. :func:`toVerilog` has been enhanced to do the expansion into a
case statement automatically, based on a higher level description. The rom
access is described in a single line, by indexing into a tuple of integers. The
tuple can be described manually, but also by programmatical means. Note that a
tuple is used instead of a list to stress the read-only character of the
memory.
The following example illustrates this functionality.
::
def rom(dout, addr, CONTENT):
@always_comb
def read():
dout.next = CONTENT[int(addr)]
return read
dout = Signal(intbv(0)[8:])
addr = Signal(intbv(0)[4:])
CONTENT = (17, 134, 52, 9)
toVerilog(rom, dout, addr, CONTENT)
The output Verilog code is as follows:
::
module rom (
dout,
addr
);
output [7:0] dout;
reg [7:0] dout;
input [3:0] addr;
always @(addr) begin: _rom_read
// synthesis parallel_case full_case
case (addr)
0: dout <= 17;
1: dout <= 134;
2: dout <= 52;
default: dout <= 9;
endcase
end
endmodule
Support for signed arithmetic
-----------------------------
Getting signed representations right in Verilog is tricky. One issue is that a
signed representation is treated as a special case, and unsigned as the rule.
For example, whenever one of the operands in an expression is unsigned, all
others are also treated like unsigned. While this is understandable from a
historical perspective (for backwards compatibility reasons) it is the opposite
from what one expects from a high-level point of view, when working with
negative numbers. The basic problem is that a Verilog user has to deal with
representation explicitly in all cases, even for abstract integer operations.
It would be much better to leave representational issues to a tool.
MyHDL doesn't make the distinction between signed and unsigned. The
:class:`intbv` class can handle any kind of integer, including negative ones.
If required, you can access the 2's complement representation of an
:class:`intbv` object, but for integer operations such a counting, there is no
need to worry about this.
Of course, the Verilog convertor has to deal with the representation carefully.
MyHDL 0.4 avoided the issue by simply prohibiting :class:`intbv` objects with
negative values. MyHDL 0.5 adds support for negative values and uses the signed
Verilog representation to accomplish this.
The problematic cases are those when signed and unsigned representations are
mixed in Verilog expressions. The convertor avoids this by making sure that
signed arithmetic is used whenever one of the operands is signed. Note that
this is exactly the opposite of the Verilog default. More specifically, the
convertor may convert an unsigned operand by adding a sign bit and casting to a
signed interpretation, using the Verilog ``$signed`` function. Operands that
are treated like this are positive :class:`intbv` objects, slices and
subscripts of :class:`intbv` objects, and :class:`bool` objects.
Integer constants are treated as a special case. Unsized integer numbers were
always treated as signed numbers in Verilog. However, as their representation
is minimally 32 bits wide, they usually don't give problems when mixed with
unsigned numbers. Therefore, integer constants don't cause signed casting of
other operands in the same expression: users would actually find it surprizing
if they did.
Support for user-defined Verilog code
-------------------------------------
Introduction
~~~~~~~~~~~~
In order to provide a path to implementation, MyHDL code can be converted to
Verilog. However, in some cases the conversion may fail or the result may not
be acceptable. For example:
* conversion will fail if the MyHDL code doesn't follow the rules of the convertible subset
* a user may want to explicitly instantiate an existing Verilog module, instead of converting the corresponding MyHDL code
* it may be necessary to include technology-dependent modules in the Verilog output
As a conclusion, MyHDL users need a method to include user-defined Verilog code
during the conversion process.
Solution
~~~~~~~~
MyHDL 0.5 defines a hook that is understood by ``toVerilog`` but ignored by the
MyHDL simulator. The hook is called ``__verilog__``. Its operation can be
understood as a special return value. When a MyHDL function defines
``__verilog__``, the Verilog converter will use its value instead of the
regular return value.
The value of ``__verilog__`` should be a format string that uses keys in its
format specifiers. The keys refer to the variable names in the context of the
string.
Example::
def inc_comb(nextCount, count, n):
@always_comb
def logic():
nextCount.next = (count + 1) % n
__verilog__ = \
"""
assign %(nextCount)s = (%(count)s + 1) %% %(n)s;
"""
nextCount.driven = "wire"
return logic
In this example, conversion of the ``inc_comb`` function is bypassed and the
user-defined Verilog code is inserted instead. Note that the user-defined code
refers to signals and parameters in the MyHDL context by using format
specifiers. During conversion, the appropriate hierarchical names and parameter
values will be filled in. Note also that the format specifier indicator `%`
needs to be escaped (by doubling it) if it is required in the user-defined
code.
There is one more issue that needs user attention. Normally, the Verilog
convertor infers inputs, internal signals, and outputs. It also detects
undriven and multiple driven signals. To do this, it assumes that signals are
not driven by default. It then processes the code to find out which signals are
driven from where. However, it cannot do this for user-defined code. Without
additional help, this will result in warnings or errors during the inference
process, or in compilation errors from invalid Verilog code. The user should
solve this by setting the ``driven`` attribute for signals that are driven from
the user-defined code. In the example code above, note the following
assignment::
nextCount.driven = "wire"
This specifies that the ``nextCount`` signal is driven as a Verilog wire from
this module. The allowed values of the ``driven`` attribute are ``"wire"`` and
``"reg"``. The value specifies how the user-defined Verilog code drives the
signal in Verilog. To decide which value to use, consider how the signal should
be declared in Verilog after the user-defined code is inserted.
Limitations
~~~~~~~~~~~
It is not possible to use the ``__verilog__`` hook in a generator function -
it should be in a classic function. This is because in MyHDL those functions
are completely run (elaborated) before the conversion starts, while generator
functions are not.
More info
~~~~~~~~~
For more information about the background and the design decisions regarding
user-defined Verilog code, see `mep-101`_.
Backwards incompatible changes
------------------------------
Verilog conversion output filename
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
A Verilog conversion is performed with a call that looks as follows::
instance_name = toVerilog(func, ...)
In MyHDL 0.4, the Verilog output filename was called ``instance_name.v``. In
MyHDL 0.5, the default output filename is ``func_name.v``, where ``func_name``
is the name of the function, available as the ``func.func_name`` attribute.
This was done for the following reasons. The MyHDL 0.4 was overly clever and
therefore complicated. It involves frame handling and parsing the source file
for the assignment pattern. Besides being too clever, it also had awkward
limitations. For example, it was not possible to construct a dynamic name for
the instance, which is very un-Pythonic behavior.
Both the implementation complexity and the limitations are gone with the new
behavior: the name of the top-level function argument is simply used. In
addition, it is now possible to specify a user-defined name for the instance as
follows::
toVerilog.name = "my_name"
toVerilog(func, ....)
To support this feature, it was necessary to make toVerilog an instance of a
class with a call interface.
**Warning**: When existing converting code is re-run, the Verilog output
filename will be different than in 0.4.
Simulation
==========
Performance optimizations
-------------------------
To improve the simulation performance of MyHDL, we mainly put our trust in
Python development itself. There are good reasons to be optimistic.
What MyHDL itself can do is to minimize the overhead of the simulation loop. In
MyHDL 0.5, a first step was taken in this respect.
MyHDL supports a number of "trigger objects". These are the objects that can
occur in ``yield`` statements, for example :class:`delay`, ``posedge``,
:class:`Signal`, and generator objects. Each of these are handled differently
and so the simulation loop has to account for the object type. Prior to MyHDL
0.5, this type check was explicitly done for each occurence of a ``yield``
statement during simulation. As many generators will loop endlessly, it is
clear that the same things will be checked over and over again, resulting in an
important overhead.
In MyHDL 0.5, all generators are predigested. Certain trigger object patterns
that tend to occur often are given specialized simulation handlers, so that
continuously performing the same type check is avoided. More specifically, they
consist of generators that only contain ``yield`` statements with a specific
argument. Currently, 5 different kinds of generators are recognized and
accelerated, corresponding to the following ``yield`` statement arguments:
* a :class:`delay` object
* a :class:`Signal` object
* a tuple of :class:`Signal` objects
* a ``posedge`` or ``negedge`` object
* a tuple of ``posedge`` and/or ``negedge`` objects
Backwards incompatible changes
------------------------------
Waveform tracing output filename
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Waveform tracing is initiated by a call that looks as follows::
instance_name = traceSignals(func, ...)
In MyHDL 0.4, the output filename was called `instance_name.vcd`. In MyHDL 0.5,
the default output filename is `func_name.vcd`, where `func_name` is the name
of the function, available as the `func.func_name` attribute.
This was done for the same reasons as in the similar case for `toVerilog`, as
described earlier.
A user-defined name for the output file can be specified as follows::
traceSignals.name = "my_name"
inst = traceSignals(func, ....)
**Warning**: When existing converting code is re-run, the vcd output filename
will be different than in 0.4.
.. _mep-100: http://dev.myhdl.org/meps/mep-100.html
.. _mep-101: http://dev.myhdl.org/meps/mep-101.html