.. _AFix:
AFix
====
Description
^^^^^^^^^^^
Auto-ranging Fixed-Point, ``AFix``, is a fixed-point class which tracks the representable range of values while preforming fixed-point operations.
**Warning: Much of this code is still under development. API and function calls may change.**
User feedback is appreciated!
Declaration
^^^^^^^^^^^
AFix can be created using bit sizes or exponents:
.. code-block:: scala
AFix.U(12 bits) // U12.0
AFix.UQ(8 bits, 4 bits) // U8.4
AFix.U(8 exp, 12 bits) // U8.4
AFix.U(8 exp, -4 exp) // U8.4
AFix.U(8 exp, 4 exp) // U8.-4
AFix.S(12 bits) // S11 + sign
AFix.SQ(8 bits, 4 bits) // S8.4 + sign
AFix.S(8 exp, 12 bits) // S8.3 + sign
AFix.S(8 exp, -4 exp) // S8.4 + sign
These will have representable ranges for all bits.
For example:
``AFix.U(12 bits)`` will have a range of 0 to 4095.
``AFix.SQ(8 bits, 4 bits)`` will have a range of -4096 (-256) to 4095 (255.9375)
``AFix.U(8 exp, 4 exp)`` will have a range of 0 to 256
Custom range ``AFix`` values can be created be directly instantiating the class.
.. code-block:: scala
class AFix(val maxValue: BigInt, val minValue: BigInt, val exp: ExpNumber)
new AFix(4096, 0, 0 exp) // [0 to 4096, 2^0]
new AFix(256, -256, -2 exp) // [-256 to 256, 2^-2]
new AFix(16, 8, 2 exp) // [8 to 16, 2^2]
The ``maxValue`` and ``minValue`` stores what backing integer values are representable.
These values represent the true fixed-point value after multiplying by ``2^exp``.
``AFix.U(2 exp, -1 exp)`` can represent:
``0, 0.5, 1.0, 1.5, 2, 2.5, 3, 3.5``
``AFix.S(2 exp, -2 exp)`` can represent:
``-2.0, -1.75, -1.5, -1.25, -1, -0.75, -0.5, -0.25, 0, 0.25, 0.5, 0.75, 1, 1.25, 1.5, 1.75``
Exponent values greater 0 are allowed and represent values which are larger than 1.
``AFix.S(2 exp, 1 exp)`` can represent:
``-4, 2, 0, 2``
``AFix(8, 16, 2 exp)`` can represent:
``32, 36, 40, 44, 48, 52, 56, 60, 64``
Note: ``AFix`` will use 5 bits to save this type as that can store ``16``, its ``maxValue``.
Mathematical Operations
^^^^^^^^^^^^^^^^^^^^^^^
``AFix`` supports Addition (``+``), Subtraction (``-``), and Multiplication (``*``) at the hardware level.
Division (``\``) and Modulo (``%``) operators are provided but are not recommended for hardware elaboration.
Operations are preformed as if the ``AFix`` value is a regular ``Int`` number.
Signed and unsigned numbers are interoperable. There are no type differences between signed or unsigned values.
.. code-block:: scala
// Integer and fractional expansion
val a = AFix.U(4 bits) // [ 0 ( 0.) to 15 (15. )] 4 bits, 2^0
val b = AFix.UQ(2 bits, 2 bits) // [ 0 ( 0.) to 15 ( 3.75)] 4 bits, 2^-2
val c = a + b // [ 0 ( 0.) to 77 (19.25)] 7 bits, 2^-2
val d = new AFix(-4, 8, -2 exp) // [- 4 (- 1.25) to 8 ( 2.00)] 5 bits, 2^-2
val e = c * d // [-308 (-19.3125) to 616 (38.50)] 11 bits, 2^-4
// Integer without expansion
val aa = new AFix(8, 16, -4 exp) // [8 to 16] 5 bits, 2^-4
val bb = new AFix(1, 15, -4 exp) // [1 to 15] 4 bits, 2^-4
val cc = aa + bb // [9 to 31] 5 bits, 2^-4
``AFix`` supports operations without without range expansion.
It does this by selecting the aligned maximum and minimum ranges from each of the inputs.
``+|`` Add without expansion.
``-|`` Subtract without expansion.
Inequality Operations
^^^^^^^^^^^^^^^^^^^^^
``AFix`` supports standard inequality operations.
.. code-block:: scala
A === B
A =\= B
A < B
A <= B
A > B
A >= B
Warning: Operations which are out of range at compile time will be optimized out!
Bitshifting
^^^^^^^^^^^
``AFix`` supports decimal and bit shifting
``<<`` Shifts the decimal to the left. Adds to the exponent.
``>>`` Shifts the decimal to the right. Subtracts from the exponent.
``<<|`` Shifts the bits to the left. Adds fractional zeros.
``>>|`` Shifts the bits to the right. Removes fractional bits.
Saturation and Rounding
^^^^^^^^^^^^^^^^^^^^^^^
``AFix`` implements saturation and all common rounding methods.
Saturation works by saturating the backing value range of an ``AFix`` value. There are multiple helper functions which
consider the exponent.
.. code-block:: scala
val a = new AFix(63, 0, -2 exp) // [0 to 63, 2^-2]
a.sat(63, 0) // [0 to 63, 2^-2]
a.sat(63, 0, -3 exp) // [0 to 31, 2^-2]
a.sat(new AFix(31, 0, -1 exp)) // [0 to 31, 2^-2]
``AFix`` rounding modes:
.. code-block:: scala
// The following require exp < 0
.floor() or .truncate()
.ceil()
.floorToZero()
.ceilToInf()
// The following require exp < -1
.roundHalfUp()
.roundHalfDown()
.roundHalfToZero()
.roundHalfToInf()
.roundHalfToEven()
.roundHalfToOdd()
An mathematical example of these rounding modes is better explained here: `Rounding - Wikipedia `_
All of these modes will result in an ``AFix`` value with 0 exponent. If rounding to a different exponent is required
consider shifting or use an assignment with the ``truncated`` tag.
Assignment
^^^^^^^^^^
``AFix`` will automatically check and expand range and precision during assignment. By default, it is an error to assign
an ``AFix`` value to another ``AFix`` value with smaller range or precision.
The ``.truncated`` function is used to control how assignments to smaller types.
.. code-block:: scala
def truncated(saturation: Boolean = false,
overflow : Boolean = true,
rounding : RoundType = RoundType.FLOOR)
def saturated(): AFix = this.truncated(saturation = true, overflow = false)
``RoundType``:
.. code-block:: scala
RoundType.FLOOR
RoundType.CEIL
RoundType.FLOORTOZERO
RoundType.CEILTOINF
RoundType.ROUNDUP
RoundType.ROUNDDOWN
RoundType.ROUNDTOZERO
RoundType.ROUNDTOINF
RoundType.ROUNDTOEVEN
RoundType.ROUNDTOODD
The ``saturation`` flag will add logic to saturate to the assigned datatype range.
The ``overflow`` flag will allow assignment directly after rounding without range checking.
Rounding is always required when assigning a value with more precision to one with lower precision.