diff --git a/doc/components/floating_point.md b/doc/components/floating_point.md index 4e8360a6..1de216d7 100644 --- a/doc/components/floating_point.md +++ b/doc/components/floating_point.md @@ -16,7 +16,7 @@ $$minExponent <= exponent <= maxExponent$$ And a mantissa in the range of $[1,2)$. Subnormal numbers are represented with a zero exponent and leading zeros in the mantissa capture the negative exponent value. -The various IEEE constants representing corner cases of the field of floating-point values for a given size of `FloatingPointValue`: infinities, zeros, limits for normal (e.g. mantissa in the range of $[1,2])$ and sub-normal numbers (zero exponent, and mantissa <1). +The various IEEE constants representing corner cases of the field of floating-point values for a given size of `FloatingPointValue`: infinities, zeros, limits for normal (e.g. mantissa in the range of $[1,2)$ and sub-normal numbers (zero exponent, and mantissa <1). Appropriate string representations, comparison operations, and operators are available. The usefulness of `FloatingPointValue` is in the testing of `FloatingPoint` components, where we can leverage the abstraction of a floating-point value type to drive and compare floating-point values operated upon by floating-point components. diff --git a/doc/components/multiplier.md b/doc/components/multiplier.md index 14d2b8fe..97d437d1 100644 --- a/doc/components/multiplier.md +++ b/doc/components/multiplier.md @@ -82,10 +82,11 @@ digital signal processing. The parameters of the `CompressionTreeMultiplier` are: -- Two input terms `a` and `b` +- Two input terms `a` and `b` which can be different widths - The radix used for Booth encoding (2, 4, 8, and 16 are currently supported) - The type of `ParallelPrefix` tree used in the final `ParallelPrefixAdder` (optional) -- Whether the operands should be treated as signed (2s complement) or unsigned +- `signed` parameter: whether the operands should be treated as signed (2s complement) or unsigned +- An optional `selectSigned` control signal which overrides the `signed` configuration allowing for runtime control of signed or unsigned operation with the same hardware. Here is an example of use of the `CompressionTreeMultiplier`: @@ -116,11 +117,12 @@ tree to allow for accumulation into this third input. The parameters of the `CompressionTreeMultiplyAccumulate` are: -- Two input terms a and b -- The accumulate input term c +- Two input product terms `a` and `b` which can be different widths +- The accumulate input term `c` which must have width as sum of the two operand widths + 1. - The radix used for Booth encoding (2, 4, 8, and 16 are currently supported) -- The type of `ParallelPrefix` tree used in the final `ParallelPrefixAdder` (optional) -- Whether the operands should be treated as signed (2s complement) or unsigned +- The type of `ParallelPrefix` tree used in the final `ParallelPrefixAdder` (default Kogge-Stone). +- `signed` parameter: whether the operands should be treated as signed (2s complement) or unsigned +- An optional `selectSigned` control signal which overrides the `signed` configuration allowing for runtime control of signed or unsigned operation with the same hardware. Here is an example of using the `CompressionTreeMultiplyAccumulate`: diff --git a/lib/src/arithmetic/evaluate_partial_product.dart b/lib/src/arithmetic/evaluate_partial_product.dart index 71dcc9cb..4b8209aa 100644 --- a/lib/src/arithmetic/evaluate_partial_product.dart +++ b/lib/src/arithmetic/evaluate_partial_product.dart @@ -25,7 +25,11 @@ extension EvaluateLivePartialProduct on PartialProductGenerator { } } final sum = LogicValue.ofBigInt(accum, maxW).toBigInt(); - return signed ? sum.toSigned(maxW) : sum; + return signed + ? sum.toSigned(maxW) + : (selectSigned != null && !selectSigned!.value.isZero) + ? sum.toSigned(maxW) + : sum; } /// Print out the partial product matrix diff --git a/lib/src/arithmetic/multiplicand_selector.dart b/lib/src/arithmetic/multiplicand_selector.dart index b49d2217..8b3934db 100644 --- a/lib/src/arithmetic/multiplicand_selector.dart +++ b/lib/src/arithmetic/multiplicand_selector.dart @@ -28,17 +28,31 @@ class MultiplicandSelector { late LogicArray multiples; /// Generate required multiples of multiplicand - MultiplicandSelector(this.radix, this.multiplicand, {required bool signed}) + MultiplicandSelector(this.radix, this.multiplicand, + {Logic? selectSigned, bool signed = false}) : shift = log2Ceil(radix) { + if (signed && (selectSigned != null)) { + throw RohdHclException('sign reconfiguration requires signed=false'); + } if (radix > 16) { throw RohdHclException('Radices beyond 16 are not yet supported'); } final width = multiplicand.width + shift; final numMultiples = radix ~/ 2; multiples = LogicArray([numMultiples], width); - final extendedMultiplicand = signed - ? multiplicand.signExtend(width) - : multiplicand.zeroExtend(width); + final Logic extendedMultiplicand; + if (selectSigned == null) { + extendedMultiplicand = signed + ? multiplicand.signExtend(width) + : multiplicand.zeroExtend(width); + } else { + final len = multiplicand.width; + final sign = multiplicand[len - 1]; + final extension = [ + for (var i = len; i < width; i++) mux(selectSigned, sign, Const(0)) + ]; + extendedMultiplicand = (multiplicand.elements + extension).rswizzle(); + } for (var pos = 0; pos < numMultiples; pos++) { final ratio = pos + 1; diff --git a/lib/src/arithmetic/multiplier.dart b/lib/src/arithmetic/multiplier.dart index 1fde5742..f79f1ff4 100644 --- a/lib/src/arithmetic/multiplier.dart +++ b/lib/src/arithmetic/multiplier.dart @@ -11,6 +11,7 @@ import 'package:meta/meta.dart'; import 'package:rohd/rohd.dart'; import 'package:rohd_hcl/rohd_hcl.dart'; +import 'package:rohd_hcl/src/arithmetic/partial_product_sign_extend.dart'; /// An abstract class for all multiplier implementations. abstract class Multiplier extends Module { @@ -72,10 +73,20 @@ class CompressionTreeMultiplier extends Multiplier { @override Logic get product => output('product'); - /// Construct a compression tree integer multipler with - /// a given radix and final adder functor + /// Construct a compression tree integer multiplier with a given [radix] + /// and prefix tree functor [ppTree] for the compressor and final adder. + /// + /// [a] and [b] are the product terms and they can be different widths + /// allowing for rectangular multiplication. + /// + /// [signed] parameter configures the multiplier as a signed multiplier + /// (default is unsigned). + /// + /// Optional [selectSigned] allows for runtime configuration of signed + /// or unsigned operation, overriding the [signed] static configuration. CompressionTreeMultiplier(super.a, super.b, int radix, - {ParallelPrefix Function(List, Logic Function(Logic, Logic)) + {Logic? selectSigned, + ParallelPrefix Function(List, Logic Function(Logic, Logic)) ppTree = KoggeStone.new, super.signed = false}) : super( @@ -86,7 +97,7 @@ class CompressionTreeMultiplier extends Multiplier { final product = addOutput('product', width: a.width + b.width); final pp = PartialProductGeneratorCompactRectSignExtension( a, b, RadixEncoder(radix), - signed: signed); + selectSigned: selectSigned, signed: signed); final compressor = ColumnCompressor(pp)..compress(); final adder = ParallelPrefixAdder( @@ -96,16 +107,26 @@ class CompressionTreeMultiplier extends Multiplier { } } -/// An implementation of an integer multiply accumulate using compression trees +/// An implementation of an integer multiply-accumulate using compression trees class CompressionTreeMultiplyAccumulate extends MultiplyAccumulate { /// The final product of the multiplier module. @override Logic get accumulate => output('accumulate'); - /// Construct a compression tree integer multipler with - /// a given radix and final adder functor + /// Construct a compression tree integer multiply-add with a given [radix] + /// and prefix tree functor [ppTree] for the compressor and final adder. + /// + /// [a] and [b] are the product terms, [c] is the accumulate term which + /// must be the sum of the widths plus 1. + /// + /// [signed] parameter configures the multiplier as a signed multiplier + /// (default is unsigned). + /// + /// Optional [selectSigned] allows for runtime configuration of signed + /// or unsigned operation, overriding the [signed] static configuration. CompressionTreeMultiplyAccumulate(super.a, super.b, super.c, int radix, {required super.signed, + Logic? selectSigned, ParallelPrefix Function(List, Logic Function(Logic, Logic)) ppTree = KoggeStone.new}) : super( @@ -114,7 +135,7 @@ class CompressionTreeMultiplyAccumulate extends MultiplyAccumulate { final accumulate = addOutput('accumulate', width: a.width + b.width + 1); final pp = PartialProductGeneratorCompactRectSignExtension( a, b, RadixEncoder(radix), - signed: signed); + selectSigned: selectSigned, signed: signed); // TODO(desmonddak): This sign extension method for the additional // addend may only work with CompactRectSignExtension @@ -151,7 +172,7 @@ class MutiplyOnly extends MultiplyAccumulate { Logic get accumulate => output('accumulate'); /// Construct a MultiplyAccumulate that only multiplies to enable - /// using the same tester with zero addend. + /// using the same tester with zero accumulate addend [c]. MutiplyOnly(super.a, super.b, super.c, Multiplier Function(Logic a, Logic b) multiplyGenerator, {super.signed = false}) // Will be overrwridden by multiplyGenerator diff --git a/lib/src/arithmetic/multiplier_encoder.dart b/lib/src/arithmetic/multiplier_encoder.dart index 2d5b0753..2b85426c 100644 --- a/lib/src/arithmetic/multiplier_encoder.dart +++ b/lib/src/arithmetic/multiplier_encoder.dart @@ -76,8 +76,8 @@ class RadixEncoder { ].swizzle().and()); } - return RadixEncode._( - multiples.rswizzle(), multiplierSlice[multiplierSlice.width - 1]); + return RadixEncode._(multiples.rswizzle(), + multiples.rswizzle().or() & multiplierSlice[multiplierSlice.width - 1]); } } @@ -97,9 +97,12 @@ class MultiplierEncoder { /// Generate an encoding of the input multiplier MultiplierEncoder(this.multiplier, RadixEncoder radixEncoder, - {required bool signed}) + {Logic? selectSigned, bool signed = false}) : _encoder = radixEncoder, _sliceWidth = log2Ceil(radixEncoder.radix) + 1 { + if (signed && (selectSigned != null)) { + throw RohdHclException('sign reconfiguration requires signed=false'); + } // Unsigned encoding wants to overlap past the multipler if (signed) { rows = @@ -109,10 +112,20 @@ class MultiplierEncoder { rows = (((multiplier.width + 1) % (_sliceWidth - 1) == 0) ? 0 : 1) + ((multiplier.width + 1) ~/ log2Ceil(radixEncoder.radix)); } - // slices overlap by 1 and start at -1 - _extendedMultiplier = (signed - ? multiplier.signExtend(rows * (_sliceWidth - 1)) - : multiplier.zeroExtend(rows * (_sliceWidth - 1))); + // slices overlap by 1 and start at -1a + if (selectSigned == null) { + _extendedMultiplier = (signed + ? multiplier.signExtend(rows * (_sliceWidth - 1)) + : multiplier.zeroExtend(rows * (_sliceWidth - 1))); + } else { + final len = multiplier.width; + final sign = multiplier[len - 1]; + final extension = [ + for (var i = len - 1; i < (rows * (_sliceWidth - 1)); i++) + mux(selectSigned, sign, Const(0)) + ]; + _extendedMultiplier = (multiplier.elements + extension).rswizzle(); + } } /// Retrieve the Booth encoding for the row diff --git a/lib/src/arithmetic/partial_product_generator.dart b/lib/src/arithmetic/partial_product_generator.dart index 719e397a..5d9e588e 100644 --- a/lib/src/arithmetic/partial_product_generator.dart +++ b/lib/src/arithmetic/partial_product_generator.dart @@ -200,18 +200,26 @@ abstract class PartialProductGenerator extends PartialProductArray { late final MultiplicandSelector selector; /// Operands are signed - final bool signed; + late final bool signed; /// Used to avoid sign extending more than once bool isSignExtended = false; + /// If not null, use this signal to select between signed and unsigned + /// operation. + late final Logic? selectSigned; + /// Construct a [PartialProductGenerator] -- the partial product matrix PartialProductGenerator( Logic multiplicand, Logic multiplier, RadixEncoder radixEncoder, - {required this.signed}) { - encoder = MultiplierEncoder(multiplier, radixEncoder, signed: signed); - selector = - MultiplicandSelector(radixEncoder.radix, multiplicand, signed: signed); + {this.signed = false, this.selectSigned}) { + if (signed && (selectSigned != null)) { + throw RohdHclException('sign reconfiguration requires signed=false'); + } + encoder = MultiplierEncoder(multiplier, radixEncoder, + selectSigned: selectSigned, signed: signed); + selector = MultiplicandSelector(radixEncoder.radix, multiplicand, + selectSigned: selectSigned, signed: signed); if (multiplicand.width < selector.shift) { throw RohdHclException('multiplicand width must be greater than ' @@ -240,174 +248,37 @@ abstract class PartialProductGenerator extends PartialProductArray { rowShift.add(row * shift); } } -} - -/// A Partial Product Generator with no sign extension -class PartialProductGeneratorNoSignExtension extends PartialProductGenerator { - /// Construct a basic Partial Product Generator - PartialProductGeneratorNoSignExtension( - super.multiplicand, super.multiplier, super.radixEncoder, - {required super.signed}); - @override - void signExtend() {} -} - -/// A Partial Product Generator using Compact Rectangular Extension -class PartialProductGeneratorCompactRectSignExtension - extends PartialProductGenerator { - /// Construct a compact rect sign extending Partial Product Generator - PartialProductGeneratorCompactRectSignExtension( - super.multiplicand, super.multiplier, super.radixEncoder, - {required super.signed}); - - void _addStopSignFlip(List addend, Logic sign) { - if (signed) { - addend.last = ~addend.last; - } else { + /// Helper function for sign extension routines: + /// For signed operands, set the MSB to [sign], otherwise add this [sign] bit. + void addStopSign(List addend, SignBit sign) { + if (!signed) { addend.add(sign); + } else { + addend.last = sign; } } - void _addStopSign(List addend, Logic sign) { - if (signed) { - addend.last = sign; - } else { + /// Helper function for sign extension routines: + /// For signed operands, flip the MSB, otherwise add this [sign] bit. + void addStopSignFlip(List addend, SignBit sign) { + if (!signed) { addend.add(sign); + } else { + addend.last = SignBit(~addend.last, inverted: true); } } +} - /// Sign extend the PP array using stop bits without adding a row - /// This routine works with different widths of multiplicand/multiplier, - /// an extension of Mohanty, B.K., Choubey designed by - /// Desmond A. Kirkpatrick - @override - void signExtend() { - if (isSignExtended) { - throw RohdHclException('Partial Product array already sign-extended'); - } - isSignExtended = true; - - final lastRow = rows - 1; - final firstAddend = partialProducts[0]; - final lastAddend = partialProducts[lastRow]; - - final firstRowQStart = selector.width - (signed ? 1 : 0); - final lastRowSignPos = shift * lastRow; - - final align = firstRowQStart - lastRowSignPos; - - final signs = [for (var r = 0; r < rows; r++) encoder.getEncoding(r).sign]; - - // Compute propgation info for folding sign bits into main rows - final propagate = - List.generate(rows, (i) => List.filled(0, Logic(), growable: true)); - - for (var row = 0; row < rows; row++) { - propagate[row].add(SignBit(signs[row])); - for (var col = 0; col < 2 * (shift - 1); col++) { - propagate[row].add(partialProducts[row][col]); - } - // Last row has extend sign propagation to Q start - if (row == lastRow) { - var col = 2 * (shift - 1); - while (propagate[lastRow].length <= align) { - propagate[lastRow].add(SignBit(partialProducts[row][col++])); - } - } - // Now compute the propagation logic - for (var col = 1; col < propagate[row].length; col++) { - propagate[row][col] = propagate[row][col] & propagate[row][col - 1]; - } - } - - // Compute 'm', the prefix of each row to carry the sign of the next row - final m = - List.generate(rows, (i) => List.filled(0, Logic(), growable: true)); - for (var row = 0; row < rows; row++) { - for (var c = 0; c < shift - 1; c++) { - m[row].add(partialProducts[row][c] ^ propagate[row][c]); - } - m[row].addAll(List.filled(shift - 1, Logic())); - } - while (m[lastRow].length < align) { - m[lastRow].add(Logic()); - } - for (var i = shift - 1; i < m[lastRow].length; i++) { - m[lastRow][i] = - lastAddend[i] ^ (i < align ? propagate[lastRow][i] : Const(0)); - } - - final remainders = List.filled(rows, Logic()); - for (var row = 0; row < lastRow; row++) { - remainders[row] = propagate[row][shift - 1]; - } - remainders[lastRow] = propagate[lastRow][align > 0 ? align : 0]; - - // Merge 'm' into the LSBs of each addend - for (var row = 0; row < rows; row++) { - final addend = partialProducts[row]; - if (row > 0) { - final mLimit = (row == lastRow) ? align : shift - 1; - for (var i = 0; i < mLimit; i++) { - addend[i] = m[row][i]; - } - // Stop bits - _addStopSignFlip(addend, SignBit(~signs[row], inverted: true)); - addend - ..insert(0, remainders[row - 1]) - ..addAll(List.filled(shift - 1, Const(1))); - rowShift[row] -= 1; - } else { - // First row - for (var i = 0; i < shift - 1; i++) { - firstAddend[i] = m[0][i]; - } - } - } - - // Insert the lastRow sign: Either in firstRow's Q if there is a - // collision or in another row if it lands beyond the Q sign extension - - final firstSign = signed ? SignBit(firstAddend.last) : SignBit(signs[0]); - final lastSign = SignBit(remainders[lastRow]); - // Compute Sign extension MSBs for firstRow - final qLen = shift + 1; - final insertSignPos = (align > 0) ? 0 : -align; - final q = List.filled(min(qLen, insertSignPos), firstSign, growable: true); - if (insertSignPos < qLen) { - // At sign insertion position - q.add(SignBit(firstSign ^ lastSign)); - if (insertSignPos == qLen - 1) { - q[insertSignPos] = SignBit(~q[insertSignPos], inverted: true); - q.add(SignBit(~(firstSign | q[insertSignPos]), inverted: true)); - } else { - q - ..addAll(List.filled( - qLen - insertSignPos - 2, SignBit(firstSign & ~lastSign))) - ..add(SignBit(~(firstSign & ~lastSign), inverted: true)); - } - } +/// A Partial Product Generator with no sign extension +class PartialProductGeneratorNoSignExtension extends PartialProductGenerator { + /// Construct a basic Partial Product Generator + PartialProductGeneratorNoSignExtension( + super.multiplicand, super.multiplier, super.radixEncoder, + {required super.signed, + // ignore: avoid_unused_constructor_parameters + Logic? selectSigned}); - if (-align >= q.length) { - q.last = SignBit(~firstSign, inverted: true); - } - _addStopSign(firstAddend, q[0]); - firstAddend.addAll(q.getRange(1, q.length)); - - if (-align >= q.length) { - final finalCarryRelPos = - lastRowSignPos - selector.width - shift + (signed ? 1 : 0); - final finalCarryRow = (finalCarryRelPos / shift).floor(); - final curRowLength = - partialProducts[finalCarryRow].length + rowShift[finalCarryRow]; - - partialProducts[finalCarryRow] - ..addAll(List.filled(lastRowSignPos - curRowLength, Const(0))) - ..add(remainders[lastRow]); - } - if (shift == 1) { - lastAddend.add(Const(1)); - } - } + @override + void signExtend() {} } diff --git a/lib/src/arithmetic/partial_product_sign_extend.dart b/lib/src/arithmetic/partial_product_sign_extend.dart index 6d4612db..b230bd61 100644 --- a/lib/src/arithmetic/partial_product_sign_extend.dart +++ b/lib/src/arithmetic/partial_product_sign_extend.dart @@ -7,6 +7,8 @@ // 2024 May 15 // Author: Desmond Kirkpatrick +import 'dart:math'; + import 'package:rohd/rohd.dart'; import 'package:rohd_hcl/rohd_hcl.dart'; @@ -29,21 +31,22 @@ enum SignExtension { } /// Used to test different sign extension methods -typedef PPGFunction = PartialProductGenerator - Function(Logic a, Logic b, RadixEncoder radixEncoder, {bool signed}); +typedef PPGFunction = PartialProductGenerator Function( + Logic a, Logic b, RadixEncoder radixEncoder, + {Logic? selectSigned, bool signed}); /// Used to test different sign extension methods PPGFunction curryPartialProductGenerator(SignExtension signExtension) => - (a, b, encoder, {signed = false}) => switch (signExtension) { + (a, b, encoder, {selectSigned, signed = false}) => switch (signExtension) { SignExtension.none => PartialProductGeneratorNoSignExtension( a, b, encoder, signed: signed), SignExtension.brute => PartialProductGeneratorBruteSignExtension( a, b, encoder, - signed: signed), + selectSigned: selectSigned, signed: signed), SignExtension.stop => PartialProductGeneratorStopBitsSignExtension( a, b, encoder, - signed: signed), + selectSigned: selectSigned, signed: signed), SignExtension.compact => PartialProductGeneratorCompactSignExtension( a, b, encoder, signed: signed), @@ -62,11 +65,14 @@ class PartialProductGeneratorBruteSignExtension /// Construct a brute-force sign extending Partial Product Generator PartialProductGeneratorBruteSignExtension( super.multiplicand, super.multiplier, super.radixEncoder, - {required super.signed}); + {super.signed, super.selectSigned}); /// Fully sign extend the PP array: useful for reference only @override void signExtend() { + if (signed && (selectSigned != null)) { + throw RohdHclException('sign reconfiguration requires signed=false'); + } if (isSignExtended) { throw RohdHclException('Partial Product array already sign-extended'); } @@ -74,8 +80,14 @@ class PartialProductGeneratorBruteSignExtension final signs = [for (var r = 0; r < rows; r++) encoder.getEncoding(r).sign]; for (var row = 0; row < rows; row++) { final addend = partialProducts[row]; - final sign = SignBit(signed ? addend.last : signs[row]); - addend.addAll(List.filled((rows - row) * shift, sign)); + // final sign = SignBit(signed ? addend.last : signs[row]); + final Logic sign; + if (selectSigned != null) { + sign = mux(selectSigned!, addend.last, signs[row]); + } else { + sign = signed ? addend.last : signs[row]; + } + addend.addAll(List.filled((rows - row) * shift, SignBit(sign))); if (row > 0) { addend ..insertAll(0, List.filled(shift - 1, Const(0))) @@ -96,7 +108,7 @@ class PartialProductGeneratorCompactSignExtension /// Construct a compact sign extending Partial Product Generator PartialProductGeneratorCompactSignExtension( super.multiplicand, super.multiplier, super.radixEncoder, - {required super.signed}); + {super.signed, super.selectSigned}); /// Sign extend the PP array using stop bits without adding a row. @override @@ -105,6 +117,9 @@ class PartialProductGeneratorCompactSignExtension // Mohanty, B.K., Choubey, A. Efficient Design for Radix-8 Booth Multiplier // and Its Application in Lifting 2-D DWT. Circuits Syst Signal Process 36, // 1129–1149 (2017). https://doi.org/10.1007/s00034-016-0349-9 + if (signed && (selectSigned != null)) { + throw RohdHclException('sign reconfiguration requires signed=false'); + } if (isSignExtended) { throw RohdHclException('Partial Product array already sign-extended'); } @@ -159,7 +174,7 @@ class PartialProductGeneratorCompactSignExtension remainders[lastRow] <= propagate[lastRow][alignRow0Sign]; // Compute Sign extension for row==0 - final firstSign = signed ? firstAddend.last : signs[0]; + final firstSign = !signed ? signs[0] : firstAddend.last; final q = [ firstSign ^ remainders[lastRow], ~(firstSign & ~remainders[lastRow]), @@ -173,12 +188,7 @@ class PartialProductGeneratorCompactSignExtension for (var i = 0; i < mLimit; i++) { addend[i] = m[row][i]; } - // Stop bits - if (signed) { - addend.last = ~addend.last; - } else { - addend.add(~signs[row]); - } + addStopSignFlip(addend, SignBit(~signs[row], inverted: true)); addend ..insert(0, remainders[row - 1]) ..addAll(List.filled(shift - 1, Const(1))); @@ -187,10 +197,10 @@ class PartialProductGeneratorCompactSignExtension for (var i = 0; i < shift - 1; i++) { firstAddend[i] = m[0][i]; } - if (signed) { - firstAddend.last = q[0]; - } else { + if (!signed) { firstAddend.add(q[0]); + } else { + firstAddend.last = q[0]; } firstAddend.addAll(q.getRange(1, q.length)); } @@ -207,15 +217,18 @@ class PartialProductGeneratorStopBitsSignExtension /// Construct a stop bits sign extending Partial Product Generator PartialProductGeneratorStopBitsSignExtension( super.multiplicand, super.multiplier, super.radixEncoder, - {required super.signed}); + {super.signed, super.selectSigned}); - /// Sign extend the PP array using stop bits + /// Sign extend the PP array using stop bits. /// If possible, fold the final carry into another row (only when rectangular /// enough that carry bit lands outside another row). /// This technique can then be combined with a first-row extension technique /// for folding in the final carry. @override void signExtend() { + if (signed && (selectSigned != null)) { + throw RohdHclException('sign reconfiguration requires signed=false'); + } if (isSignExtended) { throw RohdHclException('Partial Product array already sign-extended'); } @@ -230,22 +243,24 @@ class PartialProductGeneratorStopBitsSignExtension : 0; final signs = [for (var r = 0; r < rows; r++) encoder.getEncoding(r).sign]; + for (var row = 0; row < rows; row++) { final addend = partialProducts[row]; - final sign = signed ? addend.last : signs[row]; + final Logic sign; + if (selectSigned != null) { + sign = mux(selectSigned!, addend.last, signs[row]); + } else { + sign = signed ? addend.last : signs[row]; + } if (row == 0) { - if (signed) { - addend.addAll(List.filled(shift - 1, SignBit(sign))); // signed only? - } else { + if (!signed) { addend.addAll(List.filled(shift, SignBit(sign))); + } else { + addend.addAll(List.filled(shift - 1, SignBit(sign))); // signed only? } addend.add(SignBit(~sign, inverted: true)); } else { - if (signed) { - addend.last = SignBit(~sign, inverted: true); - } else { - addend.add(SignBit(~sign, inverted: true)); - } + addStopSign(addend, SignBit(~sign, inverted: true)); addend ..addAll(List.filled(shift - 1, Const(1))) ..insertAll(0, List.filled(shift - 1, Const(0))) @@ -260,8 +275,8 @@ class PartialProductGeneratorStopBitsSignExtension ..addAll(List.filled( finalCarryPos - (extensionRow.length + rowShift[finalCarryRow]), Const(0))) - ..add(signs[rows - 1]); - } else if (signed) { + ..add(SignBit(signs[rows - 1])); + } else if (signed | (selectSigned != null)) { // Create an extra row to hold the final carry bit partialProducts .add(List.filled(selector.width, Const(0), growable: true)); @@ -275,3 +290,149 @@ class PartialProductGeneratorStopBitsSignExtension } } } + +/// A Partial Product Generator using Compact Rectangular Extension +class PartialProductGeneratorCompactRectSignExtension + extends PartialProductGenerator { + /// Construct a compact rect sign extending Partial Product Generator + PartialProductGeneratorCompactRectSignExtension( + super.multiplicand, super.multiplier, super.radixEncoder, + {required super.signed, super.selectSigned}); + + /// Sign extend the PP array using stop bits without adding a row + /// This routine works with different widths of multiplicand/multiplier, + /// an extension of Mohanty, B.K., Choubey designed by + /// Desmond A. Kirkpatrick + @override + void signExtend() { + if (signed && (selectSigned != null)) { + throw RohdHclException('sign reconfiguration requires signed=false'); + } + if (isSignExtended) { + throw RohdHclException('Partial Product array already sign-extended'); + } + isSignExtended = true; + + final lastRow = rows - 1; + final firstAddend = partialProducts[0]; + final lastAddend = partialProducts[lastRow]; + + final firstRowQStart = selector.width - (signed ? 1 : 0); + final lastRowSignPos = shift * lastRow; + + final align = firstRowQStart - lastRowSignPos; + + final signs = [for (var r = 0; r < rows; r++) encoder.getEncoding(r).sign]; + + // Compute propgation info for folding sign bits into main rows + final propagate = + List.generate(rows, (i) => List.filled(0, Logic(), growable: true)); + + for (var row = 0; row < rows; row++) { + propagate[row].add(SignBit(signs[row])); + for (var col = 0; col < 2 * (shift - 1); col++) { + propagate[row].add(partialProducts[row][col]); + } + // Last row has extend sign propagation to Q start + if (row == lastRow) { + var col = 2 * (shift - 1); + while (propagate[lastRow].length <= align) { + propagate[lastRow].add(SignBit(partialProducts[row][col++])); + } + } + // Now compute the propagation logic + for (var col = 1; col < propagate[row].length; col++) { + propagate[row][col] = propagate[row][col] & propagate[row][col - 1]; + } + } + + // Compute 'm', the prefix of each row to carry the sign of the next row + final m = + List.generate(rows, (i) => List.filled(0, Logic(), growable: true)); + for (var row = 0; row < rows; row++) { + for (var c = 0; c < shift - 1; c++) { + m[row].add(partialProducts[row][c] ^ propagate[row][c]); + } + m[row].addAll(List.filled(shift - 1, Logic())); + } + while (m[lastRow].length < align) { + m[lastRow].add(Logic()); + } + for (var i = shift - 1; i < m[lastRow].length; i++) { + m[lastRow][i] = + lastAddend[i] ^ (i < align ? propagate[lastRow][i] : Const(0)); + } + + final remainders = List.filled(rows, Logic()); + for (var row = 0; row < lastRow; row++) { + remainders[row] = propagate[row][shift - 1]; + } + remainders[lastRow] = propagate[lastRow][align > 0 ? align : 0]; + + // Merge 'm' into the LSBs of each addend + for (var row = 0; row < rows; row++) { + final addend = partialProducts[row]; + if (row > 0) { + final mLimit = (row == lastRow) ? align : shift - 1; + for (var i = 0; i < mLimit; i++) { + addend[i] = m[row][i]; + } + // Stop bits + addStopSignFlip(addend, SignBit(~signs[row], inverted: true)); + addend + ..insert(0, remainders[row - 1]) + ..addAll(List.filled(shift - 1, Const(1))); + rowShift[row] -= 1; + } else { + // First row + for (var i = 0; i < shift - 1; i++) { + firstAddend[i] = m[0][i]; + } + } + } + + // Insert the lastRow sign: Either in firstRow's Q if there is a + // collision or in another row if it lands beyond the Q sign extension + + final firstSign = signed ? SignBit(firstAddend.last) : SignBit(signs[0]); + final lastSign = SignBit(remainders[lastRow]); + // Compute Sign extension MSBs for firstRow + final qLen = shift + 1; + final insertSignPos = (align > 0) ? 0 : -align; + final q = List.filled(min(qLen, insertSignPos), firstSign, growable: true); + if (insertSignPos < qLen) { + // At sign insertion position + q.add(SignBit(firstSign ^ lastSign)); + if (insertSignPos == qLen - 1) { + q[insertSignPos] = SignBit(~q[insertSignPos], inverted: true); + q.add(SignBit(~(firstSign | q[insertSignPos]), inverted: true)); + } else { + q + ..addAll(List.filled( + qLen - insertSignPos - 2, SignBit(firstSign & ~lastSign))) + ..add(SignBit(~(firstSign & ~lastSign), inverted: true)); + } + } + + if (-align >= q.length) { + q.last = SignBit(~firstSign, inverted: true); + } + addStopSign(firstAddend, q[0]); + firstAddend.addAll(q.getRange(1, q.length)); + + if (-align >= q.length) { + final finalCarryRelPos = + lastRowSignPos - selector.width - shift + (signed ? 1 : 0); + final finalCarryRow = (finalCarryRelPos / shift).floor(); + final curRowLength = + partialProducts[finalCarryRow].length + rowShift[finalCarryRow]; + + partialProducts[finalCarryRow] + ..addAll(List.filled(lastRowSignPos - curRowLength, Const(0))) + ..add(remainders[lastRow]); + } + if (shift == 1) { + lastAddend.add(Const(1)); + } + } +} diff --git a/test/arithmetic/addend_compressor_test.dart b/test/arithmetic/addend_compressor_test.dart index 9fd7edd2..eafee0eb 100644 --- a/test/arithmetic/addend_compressor_test.dart +++ b/test/arithmetic/addend_compressor_test.dart @@ -17,7 +17,6 @@ import 'package:rohd_hcl/src/arithmetic/evaluate_partial_product.dart'; import 'package:rohd_hcl/src/arithmetic/partial_product_sign_extend.dart'; import 'package:test/test.dart'; -/// A simple module to test partial product generation and compression class CompressorTestMod extends Module { late final PartialProductGenerator pp; @@ -53,14 +52,17 @@ void testCompressionExhaustive(PartialProductGenerator pp) { final widthX = pp.selector.multiplicand.width; final widthY = pp.encoder.multiplier.width; + final signed = + (pp.selectSigned == null) ? pp.signed : !pp.selectSigned!.value.isZero; + final limitX = pow(2, widthX); final limitY = pow(2, widthY); for (var i = 0; i < limitX; i++) { for (var j = 0; j < limitY; j++) { - final X = pp.signed + final X = signed ? BigInt.from(i).toSigned(widthX) : BigInt.from(i).toUnsigned(widthX); - final Y = pp.signed + final Y = signed ? BigInt.from(j).toSigned(widthY) : BigInt.from(j).toUnsigned(widthY); @@ -138,7 +140,6 @@ void main() { test('ColumnCompressor: random evaluate: square radix-4, just CompactRect', () async { stdout.write('\n'); - for (final signed in [false, true]) { for (var radix = 4; radix < 8; radix *= 2) { final encoder = RadixEncoder(radix); @@ -150,11 +151,22 @@ void main() { continue; } final ppg = curryPartialProductGenerator(signExtension); - final pp = ppg(Logic(name: 'X', width: width), - Logic(name: 'Y', width: width), encoder, - signed: signed); - - testCompressionRandom(pp, 30); + for (final useSelect in [false, true]) { + final PartialProductGenerator pp; + if (useSelect) { + final selectSigned = Logic(); + // ignore: cascade_invocations + selectSigned.put(signed ? 1 : 0); + pp = ppg(Logic(name: 'X', width: width), + Logic(name: 'Y', width: width), encoder, + selectSigned: selectSigned); + } else { + pp = ppg(Logic(name: 'X', width: width), + Logic(name: 'Y', width: width), encoder, + signed: signed); + } + testCompressionExhaustive(pp); + } } } } @@ -200,17 +212,19 @@ void main() { var av = 3; const bv = 6; - const radix = 2; - final encoder = RadixEncoder(radix); - final compressorTestMod = CompressorTestMod(a, b, encoder, clk); - await compressorTestMod.build(); - unawaited(Simulator.run()); var bA = BigInt.from(av).toSigned(widthX); final bB = BigInt.from(bv).toSigned(widthY); // Set these so that printing inside module build will have Logic values a.put(bA); b.put(bB); + const radix = 2; + final encoder = RadixEncoder(radix); + + final compressorTestMod = CompressorTestMod(a, b, encoder, clk); + await compressorTestMod.build(); + + unawaited(Simulator.run()); await clk.nextNegedge; expect(compressorTestMod.compressor.evaluate().$1, @@ -223,4 +237,85 @@ void main() { equals(BigInt.from(av * bv))); await Simulator.endSimulation(); }); + + test('example multiplier', () async { + const widthX = 10; + const widthY = 10; + final a = Logic(name: 'a', width: widthX); + final b = Logic(name: 'b', width: widthY); + + const av = 37; + const bv = 6; + for (final signed in [false, true]) { + final bA = signed + ? BigInt.from(av).toSigned(widthX) + : BigInt.from(av).toUnsigned(widthX); + final bB = signed + ? BigInt.from(bv).toSigned(widthY) + : BigInt.from(bv).toUnsigned(widthY); + + // Set these so that printing inside module build will have Logic values + a.put(bA); + b.put(bB); + const radix = 8; + final encoder = RadixEncoder(radix); + final selectSigned = Logic(); + // ignore: cascade_invocations + selectSigned.put(signed ? 1 : 0); + final pp = PartialProductGeneratorStopBitsSignExtension(a, b, encoder, + // final pp = PartialProductGeneratorCompactRectSignExtension(a, b, + // encoder, + // signed: signed); + selectSigned: selectSigned); + + expect(pp.evaluate(), equals(bA * bB)); + final compressor = ColumnCompressor(pp)..compress(); + expect(compressor.evaluate().$1, equals(bA * bB)); + } + }); + + test('single sign agnostic compressor evaluate', () async { + const widthX = 3; + const widthY = 3; + final a = Logic(name: 'a', width: widthX); + final b = Logic(name: 'b', width: widthY); + + const av = 1; + const bv = 4; + for (final signed in [false, true]) { + final bA = signed + ? BigInt.from(av).toSigned(widthX) + : BigInt.from(av).toUnsigned(widthX); + final bB = signed + ? BigInt.from(bv).toSigned(widthY) + : BigInt.from(bv).toUnsigned(widthY); + + const radix = 4; + final encoder = RadixEncoder(radix); + // for (final useSelect in [true]) { + for (final useSelect in [false, true]) { + // Set these so that printing inside module build will have Logic values + a.put(bA); + b.put(bB); + + final selectSigned = Logic(); + // ignore: cascade_invocations + selectSigned.put(signed ? 1 : 0); + + final pp = useSelect + ? PartialProductGeneratorBruteSignExtension(a, b, encoder, + selectSigned: selectSigned) + : PartialProductGeneratorBruteSignExtension(a, b, encoder, + signed: signed); + + // print(pp.representation()); + + expect(pp.evaluate(), equals(bA * bB)); + final compressor = ColumnCompressor(pp); + expect(compressor.evaluate().$1, equals(bA * bB)); + compressor.compress(); + expect(compressor.evaluate().$1, equals(bA * bB)); + } + } + }); } diff --git a/test/arithmetic/floating_point/floating_point_adder_simple_test.dart b/test/arithmetic/floating_point/floating_point_adder_simple_test.dart index 67ce71ef..c5b0f17e 100644 --- a/test/arithmetic/floating_point/floating_point_adder_simple_test.dart +++ b/test/arithmetic/floating_point/floating_point_adder_simple_test.dart @@ -238,7 +238,6 @@ void main() { final out = fp2.floatingPointValue + fp1.floatingPointValue; final adder = FloatingPointAdderSimple(fp1, fp2); - // TODO(desmonddak): figure out how to handle -0.0, as this would fail expect(adder.sum.floatingPointValue.abs().compareTo(out), 0); }); diff --git a/test/arithmetic/multiplier_encoder_test.dart b/test/arithmetic/multiplier_encoder_test.dart index 6aafbbe1..e00673b2 100644 --- a/test/arithmetic/multiplier_encoder_test.dart +++ b/test/arithmetic/multiplier_encoder_test.dart @@ -19,13 +19,15 @@ import 'package:test/test.dart'; void checkEvaluateExhaustive(PartialProductGenerator pp) { final widthX = pp.selector.multiplicand.width; final widthY = pp.encoder.multiplier.width; + final signed = + (pp.selectSigned == null) ? pp.signed : !pp.selectSigned!.value.isZero; final limitX = pow(2, widthX); final limitY = pow(2, widthY); for (var i = BigInt.zero; i < BigInt.from(limitX); i += BigInt.one) { for (var j = BigInt.zero; j < BigInt.from(limitY); j += BigInt.one) { - final X = pp.signed ? i.toSigned(widthX) : i.toUnsigned(widthX); - final Y = pp.signed ? j.toSigned(widthY) : j.toUnsigned(widthY); + final X = signed ? i.toSigned(widthX) : i.toUnsigned(widthX); + final Y = signed ? j.toSigned(widthY) : j.toUnsigned(widthY); pp.multiplicand.put(X); pp.multiplier.put(Y); final value = pp.evaluate(); @@ -38,12 +40,14 @@ void checkEvaluateExhaustive(PartialProductGenerator pp) { void checkEvaluateRandom(PartialProductGenerator pp, int nSamples) { final widthX = pp.selector.multiplicand.width; final widthY = pp.encoder.multiplier.width; + final signed = + (pp.selectSigned == null) ? pp.signed : !pp.selectSigned!.value.isZero; for (var i = 0; i < nSamples; ++i) { final rX = Random().nextLogicValue(width: widthX).toBigInt(); final rY = Random().nextLogicValue(width: widthY).toBigInt(); - final X = pp.signed ? rX.toSigned(widthX) : rX; - final Y = pp.signed ? rY.toSigned(widthY) : rY; + final X = signed ? rX.toSigned(widthX) : rX; + final Y = signed ? rY.toSigned(widthY) : rY; pp.multiplicand.put(X); pp.multiplier.put(Y); final value = pp.evaluate(); @@ -125,11 +129,22 @@ void main() { continue; } final ppg = curryPartialProductGenerator(signExtension); - final pp = ppg(Logic(name: 'X', width: width), - Logic(name: 'Y', width: width), encoder, - signed: signed); - - checkEvaluateExhaustive(pp); + for (final useSelect in [false, true]) { + final PartialProductGenerator pp; + if (useSelect) { + final selectSigned = Logic(); + // ignore: cascade_invocations + selectSigned.put(signed ? 1 : 0); + pp = ppg(Logic(name: 'X', width: width), + Logic(name: 'Y', width: width), encoder, + selectSigned: selectSigned); + } else { + pp = ppg(Logic(name: 'X', width: width), + Logic(name: 'Y', width: width), encoder, + signed: signed); + } + checkEvaluateExhaustive(pp); + } } } } @@ -147,14 +162,25 @@ void main() { // Commented out rectangular extension routines for speedup for (final signExtension in [ SignExtension.brute, - SignExtension.stop, SignExtension.compactRect ]) { final ppg = curryPartialProductGenerator(signExtension); - final pp = ppg(Logic(name: 'X', width: width), - Logic(name: 'Y', width: width + skew), encoder, - signed: signed); - checkEvaluateRandom(pp, 20); + for (final useSelect in [false, true]) { + final PartialProductGenerator pp; + if (useSelect) { + final selectSigned = Logic(); + // ignore: cascade_invocations + selectSigned.put(signed ? 1 : 0); + pp = ppg(Logic(name: 'X', width: width), + Logic(name: 'Y', width: width), encoder, + selectSigned: selectSigned); + } else { + pp = ppg(Logic(name: 'X', width: width), + Logic(name: 'Y', width: width), encoder, + signed: signed); + } + checkEvaluateRandom(pp, 20); + } } } } diff --git a/test/arithmetic/multiplier_test.dart b/test/arithmetic/multiplier_test.dart index 07afce8d..b71b0413 100644 --- a/test/arithmetic/multiplier_test.dart +++ b/test/arithmetic/multiplier_test.dart @@ -11,6 +11,7 @@ import 'dart:math'; import 'package:rohd/rohd.dart'; import 'package:rohd_hcl/rohd_hcl.dart'; import 'package:rohd_hcl/src/arithmetic/evaluate_compressor.dart'; +import 'package:rohd_hcl/src/arithmetic/partial_product_sign_extend.dart'; import 'package:test/test.dart'; // Inner test of a multipy accumulate unit @@ -108,26 +109,34 @@ void main() { int radix, ParallelPrefix Function(List, Logic Function(Logic, Logic)) ppTree, - {required bool signed}) => + {required bool signed, + Logic? selectSigned}) => (a, b) => CompressionTreeMultiplier(a, b, radix, - ppTree: ppTree, signed: signed); + selectSigned: selectSigned, ppTree: ppTree, signed: signed); MultiplyAccumulateCallback curryMultiplierAsMultiplyAccumulate( int radix, + Logic? selectSign, ParallelPrefix Function(List, Logic Function(Logic, Logic)) ppTree, {required bool signed}) => - (a, b, c) => MutiplyOnly(a, b, c, - curryCompressionTreeMultiplier(radix, ppTree, signed: signed)); + (a, b, c) => MutiplyOnly( + a, + b, + c, + curryCompressionTreeMultiplier(radix, ppTree, + selectSigned: selectSign, signed: signed)); MultiplyAccumulateCallback curryMultiplyAccumulate( int radix, + Logic? selectSign, ParallelPrefix Function(List, Logic Function(Logic, Logic)) ppTree, {required bool signed}) => (a, b, c) => CompressionTreeMultiplyAccumulate(a, b, c, radix, - ppTree: ppTree, signed: signed); + selectSigned: selectSign, ppTree: ppTree, signed: signed); + // TODO(desmonddak): fix the selectSign null group('Curried Test of Compression Tree Multiplier', () { for (final signed in [false, true]) { for (final radix in [2, 16]) { @@ -136,7 +145,7 @@ void main() { testMultiplyAccumulateRandom( width, 10, - curryMultiplierAsMultiplyAccumulate(radix, ppTree, + curryMultiplierAsMultiplyAccumulate(radix, null, ppTree, signed: signed)); } } @@ -150,7 +159,7 @@ void main() { for (final width in [5, 6]) { for (final ppTree in [KoggeStone.new, BrentKung.new]) { testMultiplyAccumulateRandom(width, 10, - curryMultiplyAccumulate(radix, ppTree, signed: signed)); + curryMultiplyAccumulate(radix, null, ppTree, signed: signed)); } } } @@ -162,7 +171,6 @@ void main() { final a = Logic(name: 'a', width: width); final b = Logic(name: 'b', width: width); final c = Logic(name: 'c', width: 2 * width); - const av = 0; const bv = 0; const cv = -512; @@ -294,9 +302,9 @@ void main() { const expectedEval = ''' 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 - 1 1 0 0 0 0 0 0 1 1 0 0 0 1 1 0 = 49350 (-16186) - 1 1 1 0 0 0 0 0 0 0 1 0 0 = 14344 (14344) - 0 0 1 0 0 0 0 1 1 = 536 (536) + 1 I 0 0 0 0 0 0 1 1 0 0 0 1 1 0 = 49350 (-16186) + 1 I 1 0 0 0 0 0 0 0 1 0 0 = 14344 (14344) + 0 i 1 0 0 0 0 1 1 = 536 (536) i S S 1 1 0 = 960 (960) p 1 1 1 1 1 1 1 0 1 0 1 0 0 1 1 0 = 65190 (-346)'''; expect(ts.toString(), equals(expectedEval));