FixedToFloat constructor

FixedToFloat(
  1. FixedPoint fixed,
  2. FloatingPoint outFloat, {
  3. bool signed = true,
  4. Logic? leadingDigitPredict,
  5. String name = 'FixedToFloat',
  6. bool reserveName = false,
  7. bool reserveDefinitionName = false,
  8. String? definitionName,
})

Constructor for fixed to floating-point conversion. This component takes a fixed point input number to convert, producing a floating-point output float. The number can be specified as signed (true by default). The leadingDigitPredict pposition can optionally be provided which avoids having to do a full leading-digit scan for conversion. This provided value must be within 1 of the actual final leading one (after absolute value) of the input fixed-point number. A LeadingDigitAnticipate module can be used to provide this value from two inputs to an adder producing the fixed-point value input to this converter.

Implementation

FixedToFloat(FixedPoint fixed, this.outFloat,
    {bool signed = true,
    Logic? leadingDigitPredict,
    super.name = 'FixedToFloat',
    super.reserveName,
    super.reserveDefinitionName,
    String? definitionName})
    : super(
          definitionName: definitionName ??
              'Fixed${fixed.width}ToFloat_E${outFloat.exponent.width}'
                  'M${outFloat.mantissa.width}') {
  fixed = fixed.clone(name: 'fixed')
    ..gets(addInput('fixed', fixed, width: fixed.width));

  final fixedAsLogic = fixed.packed;
  final exponentWidth = outFloat.exponent.width;
  final mantissaWidth = outFloat.mantissa.width;
  _convertedFloat = FloatingPoint(
      exponentWidth: exponentWidth, mantissaWidth: mantissaWidth);
  addOutput('float', width: outFloat.width) <= _convertedFloat;
  outFloat <= output('float');

  leadingDigitPredict = (leadingDigitPredict != null)
      ? addInput('leadingDigitPredict', leadingDigitPredict,
          width: leadingDigitPredict.width)
      : null;

  final bias = float.floatingPointValue.bias;
  final eMax = pow(2, float.exponent.width) - 2;
  final iWidth = (1 +
          max(log2Ceil(fixed.fractionWidth),
              max(log2Ceil(fixed.width), float.exponent.width)))
      .toInt();

  // Special handling needed for E4M3 as it does not support inf
  if ((exponentWidth == 4) && (mantissaWidth == 3)) {
    UnimplementedError('E4M3 is not supported.');
  }

  final Logic absValue;
  if (signed) {
    // Extract sign bit
    _convertedFloat.sign <= (fixed.signed ? fixedAsLogic[-1] : Const(0));

    absValue = Logic(name: 'absValue', width: fixed.width)
      ..gets(mux(_convertedFloat.sign, ~(fixedAsLogic - 1), fixedAsLogic));
  } else {
    _convertedFloat.sign <= Const(0);
    absValue = fixedAsLogic;
  }

  final maxShift = fixed.width - fixed.fractionWidth + bias - 2;

  final jBit = Logic(name: 'jBit', width: iWidth);
  Logic estimatedJBit;
  Logic absValueShifted;
  if (leadingDigitPredict != null) {
    // 3 positions are possible:  The leadingDigitPredict can be one
    // ahead of, matching or one behindthe actual jBit after absolute value.
    final fSign = fixedAsLogic[-1]
        .zeroExtend(leadingDigitPredict.width)
        .named('fixedSign');

    // If the lead is 1 for a negative, start at leadingDigitPredict - 1
    estimatedJBit = mux(
            _convertedFloat.sign.eq(fixedAsLogic[-1]),
            mux(leadingDigitPredict.gte(fSign), leadingDigitPredict - fSign,
                leadingDigitPredict),
            Const(0, width: leadingDigitPredict.width))
        .named('estimatedJBit');
    // Shift by current preJ to inspect leading bit
    if (absValue.width < float.mantissa.width + 2) {
      absValueShifted = ([
                absValue,
                Const(0, width: float.mantissa.width + 2 - absValue.width)
              ].swizzle() <<
              estimatedJBit)
          .named('absValueShifted');
    } else {
      absValueShifted = (absValue << estimatedJBit).named('absValueShifted');
    }
    // Second Shift by one if leading digit is not '1'.
    estimatedJBit = mux(absValueShifted[-1], estimatedJBit, estimatedJBit + 1)
        .named('estimatedJBit2');
    absValueShifted =
        mux(absValueShifted[-1], absValueShifted, absValueShifted << 1)
            .named('absValueShifted');

    // Third and final shift by one if leading digit is not '1'.
    jBit <=
        mux(absValueShifted[-1], estimatedJBit, estimatedJBit + 1)
            .zeroExtend(iWidth);
    absValueShifted =
        mux(absValueShifted[-1], absValueShifted, absValueShifted << 1)
            .named('absValueShifted');
  } else {
    // No prediction given:  go find the leading digit
    final exactJBit = RecursiveModulePriorityEncoder(absValue.reversed)
        .out
        .zeroExtend(iWidth)
        .named('predictedjBit');
    // Limit to minimum exponent
    if (maxShift > 0) {
      jBit <=
          mux(exactJBit.gt(maxShift), Const(maxShift, width: iWidth),
              exactJBit);
    } else {
      jBit <= exactJBit;
    }
    // Align mantissa
    if (absValue.width < float.mantissa.width + 2) {
      absValueShifted = ([
                absValue,
                Const(0, width: float.mantissa.width + 2 - absValue.width)
              ].swizzle() <<
              jBit)
          .named('absValueShifted');
    } else {
      absValueShifted = (absValue << jBit).named('absValueShiftedJ');
    }
  }
  // TODO(desmonddak): refactor to use the roundRNE component.  Also:
  // https://github.com/intel/rohd-hcl/issues/191

  // Extract mantissa
  final mantissa = Logic(name: 'mantissa', width: float.mantissa.width);
  final guard = Logic(name: 'guardBit');
  final sticky = Logic(name: 'stickyBit');
  mantissa <= absValueShifted.getRange(-float.mantissa.width - 1, -1);
  guard <=
      absValueShifted.getRange(
          -float.mantissa.width - 2, -float.mantissa.width - 1);
  sticky <= absValueShifted.getRange(0, -float.mantissa.width - 2).or();

  /// Round to nearest even: mantissa | guard sticky
  final roundUp = (guard & (sticky | mantissa[0])).named('roundUp');
  final mantissaRounded =
      mux(roundUp, mantissa + 1, mantissa).named('roundedMantissa');

  // Calculate biased exponent
  final eRaw = mux(
          absValueShifted[-1],
          Const(bias + fixed.width - fixed.fractionWidth - 1, width: iWidth) -
              jBit,
          Const(0, width: iWidth))
      .named('eRaw');

  // TODO(desmonddak): potential optimization --
  //  we may be able to predict this from absValue instead of after
  //  mantissa increment.
  final eRawRne =
      mux(roundUp & ~mantissaRounded.or(), eRaw + 1, eRaw).named('eRawRNE');

  // Select output handling corner cases
  final expoLessThanOne =
      (eRawRne[-1] | ~eRawRne.or()).named('expLessThanOne');
  final expoMoreThanMax =
      (~eRawRne[-1] & (eRawRne.gt(eMax))).named('expMoreThanMax');
  Combinational([
    If.block([
      Iff(~absValue.or(), [
        // Zero
        _convertedFloat.exponent < Const(0, width: exponentWidth),
        _convertedFloat.mantissa < Const(0, width: float.mantissa.width),
      ]),
      ElseIf(expoMoreThanMax, [
        // Infinity
        _convertedFloat.exponent <
            LogicValue.filled(exponentWidth, LogicValue.one),
        _convertedFloat.mantissa < Const(0, width: float.mantissa.width),
      ]),
      ElseIf(expoLessThanOne, [
        // Subnormal
        _convertedFloat.exponent < Const(0, width: exponentWidth),
        _convertedFloat.mantissa < mantissaRounded
      ]),
      Else([
        // Normal
        _convertedFloat.exponent < eRawRne.slice(exponentWidth - 1, 0),
        _convertedFloat.mantissa < mantissaRounded
      ])
    ])
  ]);
}