mirror of
https://github.com/myhdl/myhdl.git
synced 2025-01-24 21:52:56 +08:00
Factored out hardware-oriented types in separate chapter
--HG-- branch : 0.8-dev
This commit is contained in:
parent
e3879ef878
commit
c9f850508c
380
doc/source/manual/hwtypes.rst
Normal file
380
doc/source/manual/hwtypes.rst
Normal file
@ -0,0 +1,380 @@
|
|||||||
|
.. currentmodule:: myhdl
|
||||||
|
|
||||||
|
.. _hwtypes:
|
||||||
|
|
||||||
|
***********************
|
||||||
|
Hardware-oriented types
|
||||||
|
***********************
|
||||||
|
|
||||||
|
.. _hwtypes-bit:
|
||||||
|
|
||||||
|
Bit oriented operations
|
||||||
|
=======================
|
||||||
|
|
||||||
|
.. _hwtypes-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:`hwtypes-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
|
||||||
|
|
||||||
|
|
||||||
|
.. _hwtypes-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
|
||||||
|
|
||||||
|
|
||||||
|
.. _hwtypes-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.
|
||||||
|
|
||||||
|
.. _hwtypes-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 transferred 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()
|
||||||
|
|
||||||
|
|
@ -12,6 +12,7 @@ Contents:
|
|||||||
preface
|
preface
|
||||||
background
|
background
|
||||||
intro
|
intro
|
||||||
|
hwtypes
|
||||||
structure
|
structure
|
||||||
rtl
|
rtl
|
||||||
highlevel
|
highlevel
|
||||||
|
@ -3,10 +3,9 @@
|
|||||||
|
|
||||||
.. _intro:
|
.. _intro:
|
||||||
|
|
||||||
************************************************
|
*********************
|
||||||
Introduction to Modeling & Simulation with MyHDL
|
Introduction to MyHDL
|
||||||
************************************************
|
*********************
|
||||||
|
|
||||||
|
|
||||||
.. _intro-basic:
|
.. _intro-basic:
|
||||||
|
|
||||||
@ -287,383 +286,10 @@ The simulation produces the following output::
|
|||||||
Normally, the meaning should be clear from the context. Occasionally, I may
|
Normally, the meaning should be clear from the context. Occasionally, I may
|
||||||
qualify terms with the words "hardware" or "MyHDL" to avoid ambiguity.
|
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 transferred 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:
|
.. _intro-python:
|
||||||
|
|
||||||
Some concluding remarks on MyHDL and Python
|
Some remarks on MyHDL and Python
|
||||||
===========================================
|
================================
|
||||||
|
|
||||||
To conclude this introductory chapter, it is useful to stress that MyHDL is not
|
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
|
a language in itself. The underlying language is Python, and MyHDL is
|
||||||
@ -711,15 +337,16 @@ Here is an overview of what we have learned in this chapter:
|
|||||||
|
|
||||||
* :class:`Signal` objects are used to communicate between concurrent generators.
|
* :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.
|
* A :class:`Simulation` object is used to simulate MyHDL models.
|
||||||
|
|
||||||
These concepts are sufficient to start describing and simulating MyHDL models.
|
These concepts are sufficient to start modeling and simulating with MyHDL.
|
||||||
|
|
||||||
However, there is much more to MyHDL. Here is an overview of what can be learned
|
However, there is much more to MyHDL. Here is an overview of what can be learned
|
||||||
from the following chapters:
|
from the following chapters:
|
||||||
|
|
||||||
|
* MyHDL supports hardware-oriented types that make it easier to write
|
||||||
|
typical hardware models. These are described in Chapter :ref:`hwtypes`.
|
||||||
|
|
||||||
* MyHDL supports sophisticated and high level modeling techniques. This is
|
* MyHDL supports sophisticated and high level modeling techniques. This is
|
||||||
described in Chapter :ref:`model-hl`.
|
described in Chapter :ref:`model-hl`.
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user