FloatToFixed constructor
- FloatingPoint float, {
- String name = 'FloatToFixed',
- int? integerWidth,
- int? fractionWidth,
- bool checkOverflow = false,
- FloatingPointRoundingMode roundingMode = FloatingPointRoundingMode.truncate,
- bool reserveName = false,
- bool reserveDefinitionName = false,
- String? definitionName,
Build a FloatingPoint to FixedPoint converter.
- if
integerWidthandfractionWidthare supplied, an m.n fixed-point output will be produced. Otherwise, the converter will compute a lossless size forintegerWidthandfractionWidthfor outputing the floating-point value into a fixed-point value. checkOverflowset totruewill cause overflow detection to happen in case that loss can occur and an optional output overflow will be produced that returnstruewhen overflow occurs.roundingModeselects 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;
}