Sum constructor

Sum(
  1. List<SumInterface> interfaces, {
  2. dynamic initialValue = 0,
  3. dynamic maxValue,
  4. dynamic minValue = 0,
  5. int? width,
  6. bool saturates = false,
  7. String name = 'sum',
})

Computes a sum across the provided interfaces.

The width can be either explicitly provided or inferred from other values such as a maxValue, minValue, or initialValue that contain width information (e.g. a LogicValue), or by making it large enough to fit maxValue, or by inspecting widths of interfaces. There must be enough information provided to determine the width.

If no maxValue is provided, one will be inferred by the maximum that can fit inside of the width.

It is expected that maxValue is at least minValue, or else results may be unpredictable.

If saturates is true, then it will saturate at the maxValue and minValue. If false, will wrap around (overflow/underflow) at the maxValue and minValue. The equalsMax, equalsMin, overflowed, and underflowed outputs can be used to determine if the sum is at the maximum, minimum, (would have) overflowed, or (would have) underflowed, respectively.

Implementation

Sum(
  super.interfaces, {
  dynamic initialValue = 0,
  super.maxValue,
  super.minValue,
  super.width,
  super.saturates,
  super.name = 'sum',
}) : super(initialValue: initialValue) {
  addOutput('sum', width: width);

  var maxPosMagnitude = SummationBase.biggestVal(width);
  var maxNegMagnitude = BigInt.zero;
  for (final intf in interfaces) {
    final maxMagnitude = intf.fixedAmount != null
        ? intf.amount.value.toBigInt()
        : SummationBase.biggestVal(intf.width);

    if (intf.increments) {
      maxPosMagnitude += maxMagnitude;
    } else {
      maxNegMagnitude += maxMagnitude;
    }
  }

  // also consider that initialValue may be less than min or more than max
  final maxInitialValueMagnitude = initialValue is Logic
      ? SummationBase.biggestVal(initialValue.width)
      : LogicValue.ofInferWidth(initialValue).toBigInt();
  maxPosMagnitude += maxInitialValueMagnitude;
  maxNegMagnitude += maxInitialValueMagnitude;

  // calculate the largest number that we could have in intermediate
  final internalWidth = max(
      (maxPosMagnitude + maxNegMagnitude + BigInt.one).bitLength, width + 1);

  final initialValueLogicExt = initialValueLogic.zeroExtend(internalWidth);
  final minValueLogicExt = minValueLogic.zeroExtend(internalWidth);
  final maxValueLogicExt = maxValueLogic.zeroExtend(internalWidth);

  // lazy range so that it's not generated if not necessary
  late final range = Logic(name: 'range', width: internalWidth)
    ..gets(maxValueLogicExt - minValueLogicExt + 1);

  final zeroPoint = Logic(name: 'zeroPoint', width: internalWidth)
    ..gets(Const(maxNegMagnitude, width: internalWidth));

  final upperSaturation = Logic(name: 'upperSaturation', width: internalWidth)
    ..gets(maxValueLogicExt + zeroPoint);
  final lowerSaturation = Logic(name: 'lowerSaturation', width: internalWidth)
    ..gets(minValueLogicExt + zeroPoint);

  final internalValue = Logic(name: 'internalValue', width: internalWidth);
  sum <= (internalValue - zeroPoint).getRange(0, width);

  final preAdjustmentValue =
      Logic(name: 'preAdjustmentValue', width: internalWidth);

  // here we use an `ssa` block to iteratively update the value of
  // `internalValue` based on the adjustments from the interfaces and
  // saturation/roll-over behavior
  //
  // For more details, see:
  // https://intel.github.io/rohd-website/blog/combinational-ssa/
  Combinational.ssa((s) => [
        // initialize
        s(internalValue) < initialValueLogicExt + zeroPoint,

        // perform increments and decrements per-interface
        ...interfaces
            .map((e) => e._combAdjustments(s, internalValue))
            .flattened,

        // identify if we're at a max/min case
        overflowed < s(internalValue).gt(upperSaturation),
        underflowed < s(internalValue).lt(lowerSaturation),

        // useful as an internal node for debug/visibility
        preAdjustmentValue < s(internalValue),

        // handle saturation or over/underflow
        If.block([
          Iff.s(
            overflowed,
            s(internalValue) <
                (saturates
                    ? upperSaturation
                    : ((s(internalValue) - upperSaturation - 1) % range +
                        lowerSaturation)),
          ),
          ElseIf.s(
            underflowed,
            s(internalValue) <
                (saturates
                    ? lowerSaturation
                    : (upperSaturation -
                        ((lowerSaturation - s(internalValue) - 1) % range))),
          )
        ]),
      ]);

  equalsMax <= internalValue.eq(upperSaturation);
  equalsMin <= internalValue.eq(lowerSaturation);
}