Fixed-point inputs and outputs

Fix types are available in MLDesigner as a type of Particle.

The conversion from an int or a double to a Fix takes place using the Fix::Fix(double) constructor which creates a Fix object with the default word length of 24 bits and the number of integer bits as required by the value. For instance, the double 10.3 will be converted to a Fix with precision 5.19, since 5 is the minimum number of bits needed to represent the integer part, 10, including its sign bit.

To use the Fix type in a primitive, the type of the portholes must be declared as fix. Primitives that receive or transmit fixed-point data have parameters that specify the precision of the input and output in bits, as well as the overflow behavior. Here is a simplified version of SDF primitive AddFix, configured for two inputs:

defprimitive
{
  name    { SimpleAddFix }
  domain  { SDF }
  derived { Fix }

  output
  {
    name { Output }
    type { fix }
    desc { "Fixed-point sum." }
  }

  input
  {
    name { Input1 }
    type { fix }
  }

  input
  {
    name { Input2 }
    type { fix }
  }

  defparameter
  {
    name        { OutputPrecision }
    type        { precision }
    default     { "2.14" }
    desc        { "Precision of the output in bits and precision of the accumulation.
When the value of the accumulation extends outside of the precision,
the OverflowHandler will be called." }
  }

Note that the real AddFix primitive supports any number of inputs. By default, the precision used by this primitive during the addition will have 2 bits to the left of the binary point and 14 bits to the right. Not shown here is the parameter OverflowHandler, which is inherited from the SDFFix primitive and defaults to saturate, that is, if the addition overflows, the result saturates, pegging it to either the largest positive or negative number representable. The result value, sum, is initialized by the following code:

     protected
  {
    Fix sum;
  }

  setup
  {
    SDFFix::setup();

    sum = Fix( ((const char *) OutputPrecision) );
    if ( sum.invalid() )
    {
      Error::abortRun( *this, "Invalid OutputPrecision" );
      return;
    }
    sum.set_ovflow( ((const char *) OverflowHandler) );
    if ( sum.invalid() )
    {
      Error::abortRun( *this, "Invalid OverflowHandler" );
      return;
    }
  }

The setup method checks the specified precision and overflow handler for correctness. Then, in the go method, the sum is used to add the input values, thus taking care that the desired precision and overflow handling are enforced. For example,

  go
  {
    // set to zero and clear error bits
    sum.setToZero(); 
    sum += Input1%0;
    checkOverflow(sum);
    sum += Input2%0;
    checkOverflow(sum);
    Output%0 << sum;
  }

The checkOverflow method is inherited from SDFFix primitive. The protected member sum is an uninitialized Fix object until the setup method runs. In the setup method, it is given the precision specified by OutputPrecision. The go method initializes it to zero. If the go method had assigned a value specified by another Fix object instead, it would acquire the precision of that other object, at the point it had been initialized.

In the above version of the go method the input is added to the protected member sum, which has the side-effect of quantizing the input value to the precision of sum. The programmer also could have written the go method as follows:

  go
  {
    // set to zero and clear error bits
    sum.setToZero();
    Fix tIn1 = Input1%0;
    Fix tIn2 = Input2%0;
    sum = tIn1 + tIn2;
    checkOverflow(sum);
    Output2%0 << sum;
  }

The behavior here is significantly different: the inputs are added using their own native precision, and only the result is quantized to the precision of sum. Some primitives allow the programmer to select between these two different behaviors with a parameter called ArrivingPrecision. If set to YES, the input particles are not explicitly cast. They are used as they are. If set to NO, the input particles are cast to an internal precision, which is usually specified by another parameter.

Note that the SDFAddFix primitive and many of the Fix primitives are derived from the primitive SDFFix. SDFFix implements commonly used methods and defines two parameters: OverflowHandler selects one of four overflow handlers to be called each time an overflow occurs, and ReportOverflow, which, if true, causes the number and percentage of overflows that occurred for that primitive during a simulation run to be reported in the wrapup method.