FloatToFixed constructor

FloatToFixed(
  1. FloatingPoint float, {
  2. String name = 'FloatToFixed',
  3. int? integerWidth,
  4. int? fractionWidth,
  5. bool checkOverflow = false,
  6. FloatingPointRoundingMode roundingMode = FloatingPointRoundingMode.truncate,
  7. bool reserveName = false,
  8. bool reserveDefinitionName = false,
  9. String? definitionName,
})

Build a FloatingPoint to FixedPoint converter.

  • if integerWidth and fractionWidth are supplied, an m.n fixed-point output will be produced. Otherwise, the converter will compute a lossless size for integerWidth and fractionWidth for outputing the floating-point value into a fixed-point value.
  • checkOverflow set to true will cause overflow detection to happen in case that loss can occur and an optional output overflow will be produced that returns true when overflow occurs.
  • roundingMode selects how a fraction narrower than the incoming mantissa is rounded. Defaults to FloatingPointRoundingMode.truncate, matching prior (rounding-free) behavior.

Implementation

FloatToFixed(FloatingPoint float,
    {super.name = 'FloatToFixed',
    int? integerWidth,
    int? fractionWidth,
    this.checkOverflow = false,
    FloatingPointRoundingMode roundingMode =
        FloatingPointRoundingMode.truncate,
    super.reserveName,
    super.reserveDefinitionName,
    String? definitionName})
    : super(
          definitionName: definitionName ??
              'FloatE${float.exponent.width}'
                  'M${float.mantissa.width}'
                  'J${float.explicitJBit ? 1 : 0}ToFixed_'
                  'I${integerWidth ?? _defaultIntegerWidth(float)}_'
                  'F${fractionWidth ?? _defaultFractionWidth(float)}_'
                  'R${roundingMode.name}_'
                  'O${checkOverflow ? 1 : 0}') {
  float = addTypedInput('float', float);

  // [float.mantissa] includes the explicit j-bit as its top bit when
  // [FloatingPoint.explicitJBit] is set; separate it out here so the rest
  // of this component can work uniformly with just the fraction bits and
  // its own independently-computed [jBit], matching the implicit-j-bit
  // convention used throughout.
  final fractionBitsWidth = _fractionBitsWidth(float);
  final mantissaFractionBits = float.explicitJBit
      ? float.mantissa.slice(fractionBitsWidth - 1, 0)
      : float.mantissa;

  // E4M3 expands the max exponent by 1.
  final noLossM = _defaultIntegerWidth(float);
  final noLossN = _defaultFractionWidth(float);

  this.integerWidth = integerWidth ?? noLossM;
  this.fractionWidth = fractionWidth ?? noLossN;
  final outputWidth = this.integerWidth + this.fractionWidth + 1;

  final jBit = Logic(name: 'jBit')..gets(float.isNormal);
  final fullMantissa =
      [jBit, mantissaFractionBits].swizzle().named('fullMantissa');

  final eWidth = max(log2Ceil(this.fractionWidth + this.integerWidth),
          float.exponent.width) +
      2;
  final shift = Logic(name: 'shift', width: eWidth);
  final exp = (float.exponent - 1).zeroExtend(eWidth).named('expMinus1');

  if (this.fractionWidth > noLossN) {
    shift <=
        mux(jBit, exp, Const(0, width: eWidth)) +
            Const(this.fractionWidth - noLossN, width: eWidth)
                .named('deltaN');
  } else if (this.fractionWidth == noLossN) {
    shift <= mux(jBit, exp, Const(0, width: eWidth));
  } else {
    shift <=
        mux(jBit, exp, Const(0, width: eWidth)) -
            Const(noLossN - this.fractionWidth, width: eWidth)
                .named('deltaN');
  }
  // [preNumber] is pre-truncated to output width, so subtract that implicit
  // right shift from the remaining shift amount. A [SignedShifter] would
  // double-count it.
  final shiftRight = ((fullMantissa.width > outputWidth)
          ? (~shift + 1) - (fullMantissa.width - outputWidth)
          : (~shift + 1))
      .named('shiftRight');

  final preNumber = ((outputWidth >= fullMantissa.width)
          ? fullMantissa.zeroExtend(outputWidth)
          : fullMantissa.slice(-1, fullMantissa.width - outputWidth))
      .named('newMantissaPreShift');

  final unroundedNumber =
      mux(shift[-1], preNumber >>> shiftRight, preNumber << shift)
          .named('unroundedNumber');

  // Buffer a guard bit below the mantissa; all pre-truncated and shifted-out
  // bits contribute to sticky.
  final bufferedFullMantissa =
      [fullMantissa, Const(0)].swizzle().named('bufferedFullMantissa');
  final bufferedOutputWidth = outputWidth + 1;
  final bufferedPreNumber =
      ((bufferedOutputWidth >= bufferedFullMantissa.width)
              ? bufferedFullMantissa.zeroExtend(bufferedOutputWidth)
              : bufferedFullMantissa.slice(
                  -1, bufferedFullMantissa.width - bufferedOutputWidth))
          .named('bufferedPreNumber');
  final guard =
      (bufferedPreNumber >>> shiftRight)[0].named('rightShiftGuard');
  final safeShiftRight = mux(shiftRight.gte(bufferedPreNumber.width),
          Const(bufferedPreNumber.width, width: shiftRight.width), shiftRight)
      .named('safeShiftRight');
  final preTruncationSticky = bufferedFullMantissa.width > bufferedOutputWidth
      ? bufferedFullMantissa
          .getRange(0, bufferedFullMantissa.width - bufferedOutputWidth)
          .or()
      : Const(0);
  final sticky = ((bufferedPreNumber <<
                  (Const(bufferedPreNumber.width,
                          width: safeShiftRight.width) -
                      safeShiftRight))
              .or() |
          preTruncationSticky)
      .named('rightShiftSticky');
  final rounder = FloatingPointRounder.fromGRS(
      retainedLsb: unroundedNumber[0],
      guard: guard,
      sticky: sticky,
      roundingMode: roundingMode,
      sign: float.sign);

  final roundingIncrement = (shift[-1] & rounder.doRound)
      .zeroExtend(unroundedNumber.width + 1)
      .named('roundingIncrement');
  final roundedNumber =
      (unroundedNumber.zeroExtend(unroundedNumber.width + 1) +
              roundingIncrement)
          .named('roundedNumber');

  if (checkOverflow &
      ((this.integerWidth < noLossM) | (this.fractionWidth < noLossN))) {
    final overflow = Logic(name: 'overflow');
    final leadDetect = RecursiveModulePriorityEncoder(fullMantissa.reversed,
        name: 'leadone_detector');

    final sWidth = max(eWidth, leadDetect.out.width);
    final fShift = shift.zeroExtend(sWidth).named('wideShift');
    final leadOne = leadDetect.out.zeroExtend(sWidth).named('leadOne');

    // At the threshold, an exact negative power of two still fits in the
    // asymmetric two's-complement range.
    final threshold = outputWidth - fractionBitsWidth - 1;
    final discardedBits = (-threshold).clamp(0, fractionBitsWidth);
    final atThresholdIsExactPowerOfTwo = discardedBits < fractionBitsWidth
        ? ~mantissaFractionBits
            .getRange(discardedBits, fractionBitsWidth)
            .or()
        : Const(1);

    final unroundedOverflow = Logic(name: 'unroundedOverflow');
    Combinational([
      If(jBit, then: [
        unroundedOverflow <
            (_signedGtConst(shift, threshold) |
                (shift.eq(Const(threshold, width: shift.width)) &
                    ~(float.sign & atThresholdIsExactPowerOfTwo))),
      ], orElse: [
        If(fShift.gt(leadOne), then: [
          unroundedOverflow < _signedGteConst(fShift - leadOne, threshold),
        ], orElse: [
          unroundedOverflow < Const(0),
        ]),
      ]),
    ]);

    // Rounding can carry a largest representable magnitude into the next
    // integer. Include that carry in the signed destination range check.
    final maxPositive = Const((BigInt.one << (outputWidth - 1)) - BigInt.one,
        width: outputWidth + 1);
    final maxNegativeMagnitude =
        Const(BigInt.one << (outputWidth - 1), width: outputWidth + 1);
    final roundingOverflow = shift[-1] &
        rounder.doRound &
        ((~float.sign &
                unroundedNumber.zeroExtend(outputWidth + 1).eq(maxPositive)) |
            (float.sign &
                unroundedNumber
                    .zeroExtend(outputWidth + 1)
                    .eq(maxNegativeMagnitude)));

    overflow <= unroundedOverflow | roundingOverflow;
    addOutput('overflow') <= overflow;
  }

  final number =
      roundedNumber.slice(unroundedNumber.width - 1, 0).named('number');

  _fixed <= mux(float.sign, ~number + 1, number).named('signedNumber');
  final typedFixedOut = addTypedOutput('fixed', _fixed.clone);
  typedFixedOut <= _fixed;
  fixed = typedFixedOut;
}