From c9f850508ca1eea5e46c784989825db3221f817c Mon Sep 17 00:00:00 2001 From: Jan Decaluwe Date: Sun, 10 Mar 2013 16:35:24 +0100 Subject: [PATCH] Factored out hardware-oriented types in separate chapter --HG-- branch : 0.8-dev --- doc/source/manual/hwtypes.rst | 380 +++++++++++++++++++++++++++++++++ doc/source/manual/index.rst | 1 + doc/source/manual/intro.rst | 391 +--------------------------------- 3 files changed, 390 insertions(+), 382 deletions(-) create mode 100644 doc/source/manual/hwtypes.rst diff --git a/doc/source/manual/hwtypes.rst b/doc/source/manual/hwtypes.rst new file mode 100644 index 00000000..d86f70e4 --- /dev/null +++ b/doc/source/manual/hwtypes.rst @@ -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() + + diff --git a/doc/source/manual/index.rst b/doc/source/manual/index.rst index 9b2cced8..ed3a2a63 100644 --- a/doc/source/manual/index.rst +++ b/doc/source/manual/index.rst @@ -12,6 +12,7 @@ Contents: preface background intro + hwtypes structure rtl highlevel diff --git a/doc/source/manual/intro.rst b/doc/source/manual/intro.rst index b76bd86b..ebf1c645 100644 --- a/doc/source/manual/intro.rst +++ b/doc/source/manual/intro.rst @@ -3,10 +3,9 @@ .. _intro: -************************************************ -Introduction to Modeling & Simulation with MyHDL -************************************************ - +********************* +Introduction to MyHDL +********************* .. _intro-basic: @@ -287,383 +286,10 @@ The simulation produces the following output:: Normally, the meaning should be clear from the context. Occasionally, I may qualify terms with the words "hardware" or "MyHDL" to avoid ambiguity. - -.. _intro-bit: - -Bit oriented operations -======================= - -.. _intro-intbv: - -The :class:`intbv` class ------------------------- - - -.. index:: single: intbv; basic usage - -Hardware design involves dealing with bits and bit-oriented operations. The -standard Python type :class:`int` has most of the desired features, but lacks -support for indexing and slicing. For this reason, MyHDL provides the -:class:`intbv` class. The name was chosen to suggest an integer with bit vector -flavor. - -:class:`intbv` works transparently with other integer-like types. Like -class :class:`int`, it provides access to the underlying two's complement -representation for bitwise operations. However, unlike :class:`int`, it is -a mutable type. This means that its value can be changed after object -creation, through methods and operators such as slice assignment. - -:class:`intbv` supports the same operators as :class:`int` for arithmetic. -In addition, it provides a number of features to make it -suitable for hardware design. First, the range of allowed values can -be constrained. This makes it possible to check the value at run time -during simulation. Moreover, back end tools can determine the smallest -possible bit width for representing the object. -Secondly, it supports bit level operations by providing an indexing -and slicing interface. - -:class:`intbv` objects are constructed in general as follows:: - - intbv([val=None] [, min=None] [, max=None]) - -*val* is the initial value. *min* and *max* can be used to constrain -the value. Following the Python conventions, *min* is inclusive, and -*max* is exclusive. Therefore, the allowed value range is *min* .. *max*-1. - -Let's us look at some examples. First, an unconstrained :class:`intbv` -object is created as follows: - - >>> a = intbv(24) - -.. index:: - single: intbv; min - single: intbv; max - single: intbv; bit width - -After object creation, *min* and *max* are available as attributes for -inspection. Also, the standard Python function :func:`len` can be used -to determine the bit width. If we inspect the previously created -object, we get:: - - >>> print a.min - None - >>> print a.max - None - >>> print len(a) - 0 - -As the instantiation was unconstrained, the *min* and *max* attributes -are undefined. Likewise, the bit width is undefined, which is indicated -by a return value ``0``. - -A constrained :class:`intbv` object is created as follows: - - >>> a = intbv(24, min=0, max=25) - - -Inspecting the object now gives:: - - >>> a.min - 0 - >>> a.max - 25 - >>> len(a) - 5 - -We see that the allowed value range is 0 .. 24, and that 5 bits are -required to represent the object. - -Sometimes hardware engineers prefer to constrain an object by defining -its bit width directly, instead of the range of allowed values. -The following example shows how to do that:: - - >>> a = intbv(24)[5:] - -What actually happens here is that first an unconstrained :class:`intbv` -is created, which is then sliced. Slicing an :class:`intbv` returns a new -:class:`intbv` with the constraints set up appropriately. -Inspecting the object now shows:: - - >>> a.min - 0 - >>> a.max - 32 - >>> len(a) - 5 - - -Note that the *max* attribute is 32, as with 5 bits it is -possible to represent the range 0 .. 31. -Creating an -:class:`intbv` this way has the disadvantage that only positive value -ranges can be specified. Slicing is described in more detail -in :ref:`intro-slicing`. - -To summarize, there are two ways to constrain an :class:`intbv` object: by -defining its bit width, or by defining its value range. The bit -width method is more traditional in hardware design. However, there -are two reasons to use the range method instead: to represent -negative values as observed above, and for fine-grained control over the -value range. - -Fine-grained control over the value range permits better error -checking, as there is no need for the *min* and *max* bounds -to be symmetric or powers of 2. In all cases, the bit width -is set appropriately to represent all values in -the range. For example:: - - - >>> a = intbv(6, min=0, max=7) - >>> len(a) - 3 - >>> a = intbv(6, min=-3, max=7) - >>> len(a) - 4 - >>> a = intbv(6, min=-13, max=7) - >>> len(a) - 5 - - -.. _intro-indexing: - -Bit indexing ------------- - -.. index:: single: bit indexing - -As an example, we will consider the design of a Gray encoder. The following code -is a Gray encoder modeled in MyHDL:: - - from myhdl import Signal, delay, Simulation, always_comb, instance, intbv, bin - - def bin2gray(B, G, width): - """ Gray encoder. - - B -- input intbv signal, binary encoded - G -- output intbv signal, gray encoded - width -- bit width - """ - - @always_comb - def logic(): - for i in range(width): - G.next[i] = B[i+1] ^ B[i] - - return logic - -This code introduces a few new concepts. The string in triple quotes at the -start of the function is a :dfn:`doc string`. This is standard Python practice -for structured documentation of code. - -.. index:: - single: decorator; always_comb - single: wait; for a signal value change - single: combinatorial logic - -Furthermore, we introduce a third decorator: :func:`always_comb`. It is used -with a classic function and specifies that the resulting generator should wait -for a value change on any input signal. This is typically used to describe -combinatorial logic. The :func:`always_comb` decorator automatically infers -which signals are used as inputs. - -Finally, the code contains bit indexing operations and an exclusive-or operator -as required for a Gray encoder. By convention, the lsb of an :class:`intbv` -object has index ``0``. - -To verify the Gray encoder, we write a test bench that prints input and output -for all possible input values:: - - def testBench(width): - - B = Signal(intbv(0)) - G = Signal(intbv(0)) - - dut = bin2gray(B, G, width) - - @instance - def stimulus(): - for i in range(2**width): - B.next = intbv(i) - yield delay(10) - print "B: " + bin(B, width) + "| G: " + bin(G, width) - - return dut, stimulus - -We use the conversion function :func:`bin` to get a binary string representation of -the signal values. This function is exported by the :mod:`myhdl` package and -supplements the standard Python :func:`hex` and :func:`oct` conversion functions. - -As a demonstration, we set up a simulation for a small width:: - - sim = Simulation(testBench(width=3)) - sim.run() - -The simulation produces the following output:: - - % python bin2gray.py - B: 000 | G: 000 - B: 001 | G: 001 - B: 010 | G: 011 - B: 011 | G: 010 - B: 100 | G: 110 - B: 101 | G: 111 - B: 110 | G: 101 - B: 111 | G: 100 - StopSimulation: No more events - - -.. _intro-slicing: - -Bit slicing ------------ - -.. index:: - single: bit slicing - single: concat(); example usage - -For a change, we will use a traditional function as an example to illustrate -slicing. The following function calculates the HEC byte of an ATM header. :: - - from myhdl import intbv, concat - - COSET = 0x55 - - def calculateHec(header): - """ Return hec for an ATM header, represented as an intbv. - - The hec polynomial is 1 + x + x**2 + x**8. - """ - hec = intbv(0) - for bit in header[32:]: - hec[8:] = concat(hec[7:2], - bit ^ hec[1] ^ hec[7], - bit ^ hec[0] ^ hec[7], - bit ^ hec[7] - ) - return hec ^ COSET - -The code shows how slicing access and assignment is supported on the -:class:`intbv` data type. In accordance with the most common hardware -convention, and unlike standard Python, slicing ranges are downward. The code -also demonstrates concatenation of :class:`intbv` objects. - -As in standard Python, the slicing range is half-open: the highest index bit is -not included. Unlike standard Python however, this index corresponds to the -*leftmost* item. Both indices can be omitted from the slice. If the leftmost -index is omitted, the meaning is to access "all" higher order bits. If the -rightmost index is omitted, it is ``0`` by default. - -The half-openness of a slice may seem awkward at first, but it helps to avoid -one-off count issues in practice. For example, the slice ``hex[8:]`` has exactly -``8`` bits. Likewise, the slice ``hex[7:2]`` has ``7-2=5`` bits. You can think -about it as follows: for a slice ``[i:j]``, only bits below index ``i`` are -included, and the bit with index ``j`` is the last bit included. - -When an :class:`intbv` object is sliced, a new :class:`intbv` object is returned. -This new :class:`intbv` object is always positive, and the value bounds are -set up in accordance with the bit width specified by the slice. For example:: - - >>> a = intbv(6, min=-3, max=7) - >>> len(a) - 4 - >>> b = a[4:] - >>> b - intbv(6L) - >>> len(b) - 4 - >>> b.min - 0 - >>> b.max - 16 - -In the example, the original object is sliced with a slice equal to its bit width. -The returned object has the same value and bit width, but its value -range consists of all positive values that can be represented by -the bit width. - -The object returned by a slice is positive, even when the -original object is negative:: - - >>> a = intbv(-3) - >>> bin(a, width=5) - '11101' - >>> b = a[5:] - >>> b - intbv(29L) - >>> bin(b) - '11101' - -The bit pattern of the two objects is identical within the bit width, -but their values have opposite sign. - -.. _intro-signed: - -Unsigned and signed representation ----------------------------------- - -.. index:: - single: intbv; intbv.signed - -:class:`intbv` is designed to be as high level as possible. The underlying -value of an :class:`intbv` object is a Python :class:`int`, which is -represented as a two's complement number with "indefinite" bit -width. The range bounds are only used for error checking, and to -calculate the minimum required bit width for representation. As a -result, arithmetic can be performed like with normal integers. - -In contrast, HDLs such as Verilog and VHDL typically require designers -to deal with representational issues, especially for synthesizable code. -They provide low-level types like ``signed`` and ``unsigned`` for -arithmetic. The rules for arithmetic with such types are much more -complicated than with plain integers. - -In some cases it can be useful to interpret :class:`intbv` objects -in terms of "signed" and "unsigned". Basically, it depends on attribute *min*. -if *min* < 0, then the object is "signed", otherwise it is "unsigned". -In particular, the bit width of a "signed" object will account for -a sign bit, but that of an "unsigned" will not, because that would -be redundant. From earlier sections, we have learned that the -return value from a slicing operation is always "unsigned". - -In some applications, it is desirable to convert an "unsigned" -:class:`intbv` to a "signed", in other words, to interpret the msb bit -as a sign bit. The msb bit is the highest order bit within the object's -bit width. For this purpose, :class:`intbv` provides the -:meth:`intbv.signed` method. For example:: - - >>> a = intbv(12, min=0, max=16) - >>> bin(a) - '1100' - >>> b = a.signed() - >>> b - -4 - >>> bin(b, width=4) - '1100' - -:meth:`intbv.signed` extends the msb bit into the higher-order bits of the -underlying object value, and returns the result as an integer. -Naturally, for a "signed" the return value will always be identical -to the original value, as it has the sign bit already. - -As an example let's take a 8 bit wide data bus that would be modeled as -follows:: - - data_bus = intbv(0)[8:] - -Now consider that a complex number is 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: -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 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:`intbv` objects are used to describe bit-oriented operations. - * A :class:`Simulation` object is used to simulate MyHDL models. -These concepts are sufficient to start describing and simulating MyHDL models. +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 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 described in Chapter :ref:`model-hl`.