diff --git a/lib/src/arithmetic/carry_save_mutiplier.dart b/lib/src/arithmetic/carry_save_mutiplier.dart index 3a8363d1..3ec4f410 100644 --- a/lib/src/arithmetic/carry_save_mutiplier.dart +++ b/lib/src/arithmetic/carry_save_mutiplier.dart @@ -42,8 +42,8 @@ class CarrySaveMultiplier extends Multiplier { CarrySaveMultiplier(super.a, super.b, {required Logic clk, required Logic reset, - required super.signed, - super.name = 'carry_save_multiplier'}) { + super.name = 'carry_save_multiplier'}) + : super(signedMultiplicand: false, signedMultiplier: false) { if (a.width != b.width) { throw RohdHclException('inputs of a and b should have same width.'); } diff --git a/lib/src/arithmetic/evaluate_partial_product.dart b/lib/src/arithmetic/evaluate_partial_product.dart index 4b8209aa..d7825922 100644 --- a/lib/src/arithmetic/evaluate_partial_product.dart +++ b/lib/src/arithmetic/evaluate_partial_product.dart @@ -25,11 +25,9 @@ extension EvaluateLivePartialProduct on PartialProductGenerator { } } final sum = LogicValue.ofBigInt(accum, maxW).toBigInt(); - return signed + return isSignedMultiplicand() | isSignedMultiplier() ? sum.toSigned(maxW) - : (selectSigned != null && !selectSigned!.value.isZero) - ? sum.toSigned(maxW) - : sum; + : sum; } /// Print out the partial product matrix diff --git a/lib/src/arithmetic/multiplicand_selector.dart b/lib/src/arithmetic/multiplicand_selector.dart index 21522cd4..75b5c15d 100644 --- a/lib/src/arithmetic/multiplicand_selector.dart +++ b/lib/src/arithmetic/multiplicand_selector.dart @@ -12,7 +12,7 @@ import 'package:rohd_hcl/rohd_hcl.dart'; /// A class accessing the multiples of the multiplicand at a position class MultiplicandSelector { - /// radix of the selector + /// The radix of the selector int radix; /// The bit shift of the selector (typically overlaps 1) @@ -21,17 +21,25 @@ class MultiplicandSelector { /// New width of partial products generated from the multiplicand int get width => multiplicand.width + shift - 1; - /// Access the multiplicand + /// The base multiplicand from which to generate multiples to select. Logic multiplicand = Logic(); - /// Place to store multiples of the multiplicand + /// Place to store [multiples] of the [multiplicand] (e.g. *1, *2, *-1, *-2..) late LogicArray multiples; - /// Generate required multiples of multiplicand + /// Build a [MultiplicandSelector] generationg required [multiples] of + /// [multiplicand] to [select] using a [RadixEncoder] argument. + /// + /// [multiplicand] is base multiplicand multiplied by Booth encodings of + /// the [RadixEncoder] during [select]. + /// + /// [signedMultiplicand] generates a fixed signed selector versus using + /// [selectSignedMultiplicand] which is a runtime sign selection [Logic] + /// in which case [signedMultiplicand] must be false. MultiplicandSelector(this.radix, this.multiplicand, - {Logic? selectSigned, bool signed = false}) + {Logic? selectSignedMultiplicand, bool signedMultiplicand = false}) : shift = log2Ceil(radix) { - if (signed && (selectSigned != null)) { + if (signedMultiplicand && (selectSignedMultiplicand != null)) { throw RohdHclException('sign reconfiguration requires signed=false'); } if (radix > 16) { @@ -41,15 +49,16 @@ class MultiplicandSelector { final numMultiples = radix ~/ 2; multiples = LogicArray([numMultiples], width); final Logic extendedMultiplicand; - if (selectSigned == null) { - extendedMultiplicand = signed + if (selectSignedMultiplicand == null) { + extendedMultiplicand = signedMultiplicand ? 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)) + for (var i = len; i < width; i++) + mux(selectSignedMultiplicand, sign, Const(0)) ]; extendedMultiplicand = (multiplicand.elements + extension).rswizzle(); } diff --git a/lib/src/arithmetic/multiplier.dart b/lib/src/arithmetic/multiplier.dart index 4bb3e711..6d00f096 100644 --- a/lib/src/arithmetic/multiplier.dart +++ b/lib/src/arithmetic/multiplier.dart @@ -15,23 +15,53 @@ import 'package:rohd_hcl/src/arithmetic/partial_product_sign_extend.dart'; /// An abstract class for all multiplier implementations. abstract class Multiplier extends Module { - /// The input to the multiplier pin [a]. + /// The multiplicand input [a]. @protected late final Logic a; - /// The input to the multiplier pin [b]. + /// The multiplier input [b]. @protected late final Logic b; - /// The multiplier treats operands and output as signed - bool signed; + /// The multiplier treats input [a] always as a signed input. + bool signedMultiplicand; + + /// The multiplier treats input [b] always as a signed input. + bool signedMultiplier; + + /// If not null, use this signal to select between signed and unsigned + /// multiplicand [a]. + late Logic? selectSignedMultiplicand; + + /// If not null, use this signal to select between signed and unsigned + /// multiplier [b] + late Logic? selectSignedMultiplier; /// The multiplication results of the multiplier. Logic get product; /// Take input [a] and input [b] and return the /// [product] of the multiplication result. - Multiplier(Logic a, Logic b, {required this.signed, super.name}) { + /// + /// [signedMultiplicand] parameter configures the multiplicand [a] as a signed + /// multiplier (default is unsigned). + /// + /// [signedMultiplier] parameter configures the multiplier [b] as a signed + /// multiplier (default is unsigned). + /// + /// Optional [selectSignedMultiplicand] allows for runtime configuration of + /// signed or unsigned operation, overriding the [signedMultiplicand] static + /// configuration. + /// + /// Optional [selectSignedMultiplier] allows for runtime configuration of + /// signed or unsigned operation, overriding the [signedMultiplier] static + /// configuration. + Multiplier(Logic a, Logic b, + {required this.signedMultiplicand, + required this.signedMultiplier, + this.selectSignedMultiplicand, + this.selectSignedMultiplier, + super.name}) { this.a = addInput('a', a, width: a.width); this.b = addInput('b', b, width: b.width); } @@ -51,16 +81,52 @@ abstract class MultiplyAccumulate extends Module { @protected late final Logic c; - /// The multiplier treats operands and output as signed - bool signed; + /// The MAC treats multiplicand [a] as always signed. + bool signedMultiplicand; + + /// The MAC treats multiplier [b] as always signed. + bool signedMultiplier; + + /// The MAC treats addend [c] as always signed. + bool signedAddend; + + /// If not null, use this signal to select between signed and unsigned + /// multiplicand [a]. + late Logic? selectSignedMultiplicand; + + /// If not null, use this signal to select between signed and unsigned + /// multiplier [b] + late Logic? selectSignedMultiplier; + + /// If not null, use this signal to select between signed and unsigned + /// addend [c] + late Logic? selectSignedAddend; /// The multiplication results of the multiply-accumulate. Logic get accumulate; /// Take input [a] and input [b], compute their /// product, add input [c] to produce the [accumulate] result. + /// + /// Optional [selectSignedMultiplicand] allows for runtime configuration of + /// signed or unsigned operation, overriding the [signedMultiplicand] static + /// configuration. + /// + /// Optional [selectSignedMultiplier] allows for runtime configuration of + /// signed or unsigned operation, overriding the [signedMultiplier] static + /// configuration. + /// + /// Optional [selectSignedAddend] allows for runtime configuration of + /// signed or unsigned operation, overriding the [signedAddend] static + /// configuration. MultiplyAccumulate(Logic a, Logic b, Logic c, - {required this.signed, super.name}) { + {required this.signedMultiplicand, + required this.signedMultiplier, + required this.signedAddend, + this.selectSignedMultiplicand, + this.selectSignedMultiplier, + this.selectSignedAddend, + super.name}) { this.a = addInput('a', a, width: a.width); this.b = addInput('b', b, width: b.width); this.c = addInput('c', c, width: c.width); @@ -88,14 +154,22 @@ class CompressionTreeMultiplier extends Multiplier { /// Sign extension methodology is defined by the partial product generator /// supplied via [ppGen]. /// - /// [a] and [b] are the product terms and they can be different widths - /// allowing for rectangular multiplication. + /// [a] multiplicand and [b] multiplier are the product terms and they can + /// be different widths allowing for rectangular multiplication. + /// + /// [signedMultiplicand] parameter configures the multiplicand [a] as a signed + /// multiplier (default is unsigned). /// - /// [signed] parameter configures the multiplier as a signed multiplier - /// (default is unsigned). + /// [signedMultiplier] parameter configures the multiplier [b] as a signed + /// multiplier (default is unsigned). /// - /// Optional [selectSigned] allows for runtime configuration of signed - /// or unsigned operation, overriding the [signed] static configuration. + /// Optional [selectSignedMultiplicand] allows for runtime configuration of + /// signed or unsigned operation, overriding the [signedMultiplicand] static + /// configuration. + /// + /// Optional [selectSignedMultiplier] allows for runtime configuration of + /// signed or unsigned operation, overriding the [signedMultiplier] static + /// configuration. /// /// If [clk] is not null then a set of flops are used to latch the output /// after compression. [reset] and [enable] are optional @@ -105,26 +179,42 @@ class CompressionTreeMultiplier extends Multiplier { {this.clk, this.reset, this.enable, - Logic? selectSigned, + super.signedMultiplicand = false, + super.signedMultiplier = false, + super.selectSignedMultiplicand, + super.selectSignedMultiplier, ParallelPrefix Function(List, Logic Function(Logic, Logic)) ppTree = KoggeStone.new, PartialProductGenerator Function(Logic, Logic, RadixEncoder, - {required bool signed, Logic? selectSigned}) + {required bool signedMultiplier, + required bool signedMultiplicand, + Logic? selectSignedMultiplier, + Logic? selectSignedMultiplicand}) ppGen = PartialProductGeneratorCompactRectSignExtension.new, - super.signed = false, super.name = 'compression_tree_multiplier'}) { - final internalSelectSigned = - (selectSigned != null) ? addInput('selectSigned', selectSigned) : null; - final iClk = (clk != null) ? addInput('clk', clk!) : null; - final iReset = (reset != null) ? addInput('reset', reset!) : null; - final iEnable = (enable != null) ? addInput('enable', enable!) : null; + selectSignedMultiplicand = (selectSignedMultiplicand != null) + ? addInput('selectSignedMultiplicand', selectSignedMultiplicand!) + : null; + selectSignedMultiplier = (selectSignedMultiplier != null) + ? addInput('selectSignedMultiplier', selectSignedMultiplier!) + : null; + clk = (clk != null) ? addInput('clk', clk!) : null; + reset = (reset != null) ? addInput('reset', reset!) : null; + enable = (enable != null) ? addInput('enable', enable!) : null; final product = addOutput('product', width: a.width + b.width); - final pp = ppGen(a, b, RadixEncoder(radix), - selectSigned: internalSelectSigned, signed: signed); + final pp = ppGen( + a, + b, + RadixEncoder(radix), + selectSignedMultiplicand: selectSignedMultiplicand, + signedMultiplicand: signedMultiplicand, + selectSignedMultiplier: selectSignedMultiplier, + signedMultiplier: signedMultiplier, + ); final compressor = - ColumnCompressor(clk: iClk, reset: iReset, enable: iEnable, pp) + ColumnCompressor(clk: clk, reset: reset, enable: enable, pp) ..compress(); final adder = ParallelPrefixAdder( compressor.extractRow(0), compressor.extractRow(1), @@ -154,14 +244,29 @@ class CompressionTreeMultiplyAccumulate extends MultiplyAccumulate { /// [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). + /// [signedMultiplicand] parameter configures the multiplicand [a] as + /// always signed (default is unsigned). + /// + /// [signedMultiplier] parameter configures the multiplier [b] as + /// always signed (default is unsigned). + /// + /// [signedAddend] parameter configures the addend [c] as + /// always signed (default is unsigned). /// /// Sign extension methodology is defined by the partial product generator /// supplied via [ppGen]. /// - /// Optional [selectSigned] allows for runtime configuration of signed - /// or unsigned operation, overriding the [signed] static configuration. + /// Optional [selectSignedMultiplicand] allows for runtime configuration of + /// signed or unsigned operation, overriding the [signedMultiplicand] static + /// configuration. + /// + /// Optional [selectSignedMultiplier] allows for runtime configuration of + /// signed or unsigned operation, overriding the [signedMultiplier] static + /// configuration. + /// + /// Optional [selectSignedAddend] allows for runtime configuration of + /// signed or unsigned operation, overriding the [signedAddend] static + /// configuration. /// /// If[clk] is not null then a set of flops are used to latch the output /// after compression. [reset] and [enable] are optional @@ -171,31 +276,52 @@ class CompressionTreeMultiplyAccumulate extends MultiplyAccumulate { {this.clk, this.reset, this.enable, - super.signed = false, - Logic? selectSigned, + super.signedMultiplicand = false, + super.signedMultiplier = false, + super.signedAddend = false, + super.selectSignedMultiplicand, + super.selectSignedMultiplier, + super.selectSignedAddend, ParallelPrefix Function(List, Logic Function(Logic, Logic)) ppTree = KoggeStone.new, PartialProductGenerator Function(Logic, Logic, RadixEncoder, - {required bool signed, Logic? selectSigned}) + {required bool signedMultiplier, + required bool signedMultiplicand, + Logic? selectSignedMultiplier, + Logic? selectSignedMultiplicand}) ppGen = PartialProductGeneratorCompactRectSignExtension.new, super.name = 'compression_tree_mac'}) { - final internalSelectSigned = - (selectSigned != null) ? addInput('selectSigned', selectSigned) : null; + selectSignedMultiplicand = (selectSignedMultiplicand != null) + ? addInput('selectSignedMultiplicand', selectSignedMultiplicand!) + : null; + selectSignedMultiplier = (selectSignedMultiplier != null) + ? addInput('selectSignedMultiplier', selectSignedMultiplier!) + : null; + selectSignedAddend = (selectSignedAddend != null) + ? addInput('selectSignedAddend', selectSignedAddend!) + : null; final iClk = (clk != null) ? addInput('clk', clk!) : null; final iReset = (reset != null) ? addInput('reset', reset!) : null; final iEnable = (enable != null) ? addInput('enable', enable!) : null; final accumulate = addOutput('accumulate', width: a.width + b.width + 1); - final pp = ppGen(a, b, RadixEncoder(radix), - selectSigned: internalSelectSigned, signed: signed); + final pp = ppGen( + a, + b, + RadixEncoder(radix), + selectSignedMultiplicand: selectSignedMultiplicand, + signedMultiplicand: signedMultiplicand, + selectSignedMultiplier: selectSignedMultiplier, + signedMultiplier: signedMultiplier, + ); final lastLength = pp.partialProducts[pp.rows - 1].length + pp.rowShift[pp.rows - 1]; final sign = mux( - (internalSelectSigned != null) - ? internalSelectSigned - : (signed ? Const(1) : Const(0)), + (selectSignedAddend != null) + ? selectSignedAddend! + : (signedAddend ? Const(1) : Const(0)), c[c.width - 1], Const(0)); final l = [for (var i = 0; i < c.width; i++) c[i]]; @@ -227,31 +353,64 @@ class MutiplyOnly extends MultiplyAccumulate { @override Logic get accumulate => output('accumulate'); + static String _genName( + Multiplier Function(Logic a, Logic b, + {Logic? selectSignedMultiplicand, + Logic? selectSignedMultiplier}) + fn, + Logic a, + Logic b, + Logic? selectSignedMultiplicand, + Logic? selectSignedMultiplier) => + fn(a, b, + selectSignedMultiplicand: selectSignedMultiplicand, + selectSignedMultiplier: selectSignedMultiplier) + .name; + /// Construct a MultiplyAccumulate that only multiplies to enable /// using the same tester with zero accumulate addend [c]. - MutiplyOnly(super.a, super.b, super.c, - Multiplier Function(Logic a, Logic b, {Logic? selectSigned}) mulGen, - {super.signed = false, - Logic? selectSigned}) // Will be overrwridden by multiplyGenerator - : super( - name: 'Multiply Only: ' - '${mulGen.call(a, b, selectSigned: selectSigned).name}') { - final Logic? internalSelectSigned; - - if (selectSigned != null) { - internalSelectSigned = addInput('selectSigned', selectSigned); - } else { - internalSelectSigned = null; + MutiplyOnly( + super.a, + super.b, + super.c, + Multiplier Function(Logic a, Logic b, + {Logic? selectSignedMultiplicand, Logic? selectSignedMultiplier}) + mulGen, { + super.signedMultiplicand = false, + super.signedMultiplier = false, + super.signedAddend = false, + super.selectSignedMultiplicand, + super.selectSignedMultiplier, + super.selectSignedAddend, + }) // Will be overrwridden by multiplyGenerator + : super( + // ignore: prefer_interpolation_to_compose_strings + name: 'Multiply Only: ' + + _genName(mulGen, a, b, selectSignedMultiplicand, + selectSignedMultiplier)) { + if (selectSignedMultiplicand != null) { + selectSignedMultiplicand = + addInput('selectSignedMultiplicand', selectSignedMultiplicand!); + } + + if (selectSignedMultiplier != null) { + selectSignedMultiplier = + addInput('selectSignedMultiplier', selectSignedMultiplier!); + } + if (selectSignedAddend != null) { + selectSignedAddend = addInput('selectSignedAddend', selectSignedAddend!); } final accumulate = addOutput('accumulate', width: a.width + b.width + 1); - final multiply = mulGen(a, b, selectSigned: internalSelectSigned); - signed = multiply.signed; + final multiply = mulGen(a, b, + selectSignedMultiplicand: selectSignedMultiplicand, + selectSignedMultiplier: selectSignedMultiplier); + final signed = multiply.signedMultiplicand | multiply.signedMultiplier; accumulate <= mux( - (internalSelectSigned != null) - ? internalSelectSigned + (selectSignedMultiplier != null) + ? selectSignedMultiplier! : (signed ? Const(1) : Const(0)), [multiply.product[multiply.product.width - 1], multiply.product] .swizzle(), diff --git a/lib/src/arithmetic/multiplier_encoder.dart b/lib/src/arithmetic/multiplier_encoder.dart index 2b85426c..e96317b4 100644 --- a/lib/src/arithmetic/multiplier_encoder.dart +++ b/lib/src/arithmetic/multiplier_encoder.dart @@ -81,48 +81,55 @@ class RadixEncoder { } } -/// A class that generates the Booth encoding of the multipler +/// A class that generates the Booth encoding of the multipler. class MultiplierEncoder { /// Access the multiplier final Logic multiplier; - /// Number of row radixEncoders + /// Number of rows that are Booth-encoded. late final int rows; /// The multiplier value, sign extended as appropriate to be divisible - /// by the RadixEncoder overlapping bitslices. + /// by the RadixEncoder width using overlapping (by one) bitslices. Logic _extendedMultiplier = Logic(); + + /// Store the [RadixEncoder] used. late final RadixEncoder _encoder; - late final int _sliceWidth; - /// Generate an encoding of the input multiplier + /// Generate the Booth encoding of an input [multiplier] using + /// [radixEncoder]. + /// + /// [signedMultiplier] generates a fixed signed encoder versus using + /// [selectSignedMultiplier] which is a runtime sign selection [Logic] + /// in which case [signedMultiplier] must be false. MultiplierEncoder(this.multiplier, RadixEncoder radixEncoder, - {Logic? selectSigned, bool signed = false}) - : _encoder = radixEncoder, - _sliceWidth = log2Ceil(radixEncoder.radix) + 1 { - if (signed && (selectSigned != null)) { + {Logic? selectSignedMultiplier, bool signedMultiplier = false}) + : _encoder = radixEncoder { + if (signedMultiplier && (selectSignedMultiplier != null)) { throw RohdHclException('sign reconfiguration requires signed=false'); } // Unsigned encoding wants to overlap past the multipler - if (signed) { - rows = - ((multiplier.width + (signed ? 0 : 1)) / log2Ceil(radixEncoder.radix)) - .ceil(); + if (signedMultiplier) { + rows = ((multiplier.width + (signedMultiplier ? 0 : 1)) / + log2Ceil(radixEncoder.radix)) + .ceil(); } else { - rows = (((multiplier.width + 1) % (_sliceWidth - 1) == 0) ? 0 : 1) + + rows = (((multiplier.width + 1) % (log2Ceil(radixEncoder.radix)) == 0) + ? 0 + : 1) + ((multiplier.width + 1) ~/ log2Ceil(radixEncoder.radix)); } // slices overlap by 1 and start at -1a - if (selectSigned == null) { - _extendedMultiplier = (signed - ? multiplier.signExtend(rows * (_sliceWidth - 1)) - : multiplier.zeroExtend(rows * (_sliceWidth - 1))); + if (selectSignedMultiplier == null) { + _extendedMultiplier = (signedMultiplier + ? multiplier.signExtend(rows * (log2Ceil(radixEncoder.radix))) + : multiplier.zeroExtend(rows * (log2Ceil(radixEncoder.radix)))); } 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)) + for (var i = len - 1; i < (rows * (log2Ceil(radixEncoder.radix))); i++) + mux(selectSignedMultiplier, sign, Const(0)) ]; _extendedMultiplier = (multiplier.elements + extension).rswizzle(); } @@ -133,13 +140,15 @@ class MultiplierEncoder { if (row >= rows) { throw RohdHclException('row $row is not < number of encoding rows $rows'); } - final base = row * (_sliceWidth - 1); + final base = row * log2Ceil(_encoder.radix); final multiplierSlice = [ if (row > 0) - _extendedMultiplier.slice(base + _sliceWidth - 2, base - 1) + _extendedMultiplier.slice(base + log2Ceil(_encoder.radix) - 1, base - 1) else - [_extendedMultiplier.slice(base + _sliceWidth - 2, base), Const(0)] - .swizzle() + [ + _extendedMultiplier.slice(base + log2Ceil(_encoder.radix) - 1, base), + Const(0) + ].swizzle() ]; return _encoder.encode(multiplierSlice.first); } diff --git a/lib/src/arithmetic/partial_product_generator.dart b/lib/src/arithmetic/partial_product_generator.dart index 59a7500d..548461a7 100644 --- a/lib/src/arithmetic/partial_product_generator.dart +++ b/lib/src/arithmetic/partial_product_generator.dart @@ -202,35 +202,52 @@ abstract class PartialProductGenerator extends PartialProductArray { /// multiples of the multiplicand and generate partial products late final MultiplicandSelector selector; - /// Operands are signed - late final bool signed; + /// [multiplicand] operand is always signed + late final bool signedMultiplicand; + + /// [multiplier] operand is always signed + late final bool signedMultiplier; /// 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; + /// [multiplicand]. + late final Logic? selectSignedMultiplicand; - /// Construct a [PartialProductGenerator] -- the partial product matrix + /// If not null, use this signal to select between signed and unsigned + /// [multiplier]. + late final Logic? selectSignedMultiplier; + + /// Construct a [PartialProductGenerator] -- the partial product matrix. + /// + /// [signedMultiplier] generates a fixed signed encoder versus using + /// [selectSignedMultiplier] which is a runtime sign selection [Logic] + /// in which case [signedMultiplier] must be false. PartialProductGenerator( Logic multiplicand, Logic multiplier, RadixEncoder radixEncoder, - {this.signed = false, this.selectSigned, super.name = 'ppg'}) { - if (signed && (selectSigned != null)) { + {this.signedMultiplicand = false, + this.selectSignedMultiplicand, + this.signedMultiplier = false, + this.selectSignedMultiplier, + super.name = 'ppg'}) { + if (signedMultiplier && (selectSignedMultiplier != null)) { throw RohdHclException('sign reconfiguration requires signed=false'); } encoder = MultiplierEncoder(multiplier, radixEncoder, - selectSigned: selectSigned, signed: signed); + signedMultiplier: signedMultiplier, + selectSignedMultiplier: selectSignedMultiplier); selector = MultiplicandSelector(radixEncoder.radix, multiplicand, - selectSigned: selectSigned, signed: signed); + signedMultiplicand: signedMultiplicand, + selectSignedMultiplicand: selectSignedMultiplicand); if (multiplicand.width < selector.shift) { throw RohdHclException('multiplicand width must be greater than ' - '${selector.shift}'); + 'or equal to ${selector.shift}'); } - if (multiplier.width < (selector.shift + (signed ? 1 : 0))) { + if (multiplier.width < (selector.shift + (signedMultiplier ? 1 : 0))) { throw RohdHclException('multiplier width must be greater than ' - '${selector.shift + (signed ? 1 : 0)}'); + 'or equal to ${selector.shift + (signedMultiplier ? 1 : 0)}'); } _build(); signExtend(); @@ -252,10 +269,20 @@ abstract class PartialProductGenerator extends PartialProductArray { } } + /// Return true if [multiplicand] is truly signed (fixed or runtime) + bool isSignedMultiplicand() => (selectSignedMultiplicand == null) + ? signedMultiplicand + : !selectSignedMultiplicand!.value.isZero; + + /// Return true if [multiplier] is truly signed (fixed or runtime) + bool isSignedMultiplier() => (selectSignedMultiplier == null) + ? signedMultiplier + : !selectSignedMultiplier!.value.isZero; + /// 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) { + if (!signedMultiplicand) { addend.add(sign); } else { addend.last = sign; @@ -265,12 +292,12 @@ abstract class PartialProductGenerator extends PartialProductArray { /// 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) { - if (selectSigned == null) { + if (!signedMultiplicand) { + if (selectSignedMultiplicand == null) { addend.add(sign); } else { - addend.add(SignBit(mux(selectSigned!, ~addend.last, sign), - inverted: selectSigned != null)); + addend.add(SignBit(mux(selectSignedMultiplicand!, ~addend.last, sign), + inverted: selectSignedMultiplicand != null)); } } else { addend.last = SignBit(~addend.last, inverted: true); @@ -279,13 +306,14 @@ abstract class PartialProductGenerator extends PartialProductArray { } /// A Partial Product Generator with no sign extension -class PartialProductGeneratorNoSignExtension extends PartialProductGenerator { +class PartialProductGeneratorNoneSignExtension extends PartialProductGenerator { /// Construct a basic Partial Product Generator - PartialProductGeneratorNoSignExtension( + PartialProductGeneratorNoneSignExtension( super.multiplicand, super.multiplier, super.radixEncoder, - {required super.signed, - // ignore: avoid_unused_constructor_parameters - Logic? selectSigned}); + {required super.signedMultiplicand, + required super.signedMultiplier, + super.selectSignedMultiplicand, + super.selectSignedMultiplier}); @override void signExtend() {} diff --git a/lib/src/arithmetic/partial_product_sign_extend.dart b/lib/src/arithmetic/partial_product_sign_extend.dart index 7a8e6d64..6e62eab1 100644 --- a/lib/src/arithmetic/partial_product_sign_extend.dart +++ b/lib/src/arithmetic/partial_product_sign_extend.dart @@ -21,7 +21,7 @@ enum SignExtension { brute, /// Extend using stop bits in each row (and an extra row for final sign) - stop, + stopBits, /// Fold in last row sign bit (Mohanty, B.K., Choubey, A.) compact, @@ -33,29 +33,24 @@ enum SignExtension { /// Used to test different sign extension methods typedef PPGFunction = PartialProductGenerator Function( Logic a, Logic b, RadixEncoder radixEncoder, - {Logic? selectSigned, bool signed}); + {bool signedMultiplicand, + Logic? selectSignedMultiplicand, + bool signedMultiplier, + Logic? selectSignedMultiplier}); /// Used to test different sign extension methods PPGFunction curryPartialProductGenerator(SignExtension signExtension) => - (a, b, encoder, {selectSigned, signed = false}) => switch (signExtension) { - SignExtension.none => PartialProductGeneratorNoSignExtension( - a, b, encoder, - signed: signed), - SignExtension.brute => PartialProductGeneratorBruteSignExtension( - a, b, encoder, - selectSigned: selectSigned, signed: signed), - SignExtension.stop => PartialProductGeneratorStopBitsSignExtension( - a, b, encoder, - selectSigned: selectSigned, signed: signed), - SignExtension.compact => PartialProductGeneratorCompactSignExtension( - a, b, encoder, - signed: signed), - SignExtension.compactRect => - PartialProductGeneratorCompactRectSignExtension(a, b, encoder, - signed: signed), - }; - -/// These other sign extensions are for asssisting with testing and debugging. + switch (signExtension) { + SignExtension.none => PartialProductGeneratorNoneSignExtension.new, + SignExtension.brute => PartialProductGeneratorBruteSignExtension.new, + SignExtension.stopBits => + PartialProductGeneratorStopBitsSignExtension.new, + SignExtension.compact => PartialProductGeneratorCompactSignExtension.new, + SignExtension.compactRect => + PartialProductGeneratorCompactRectSignExtension.new, + } as PPGFunction; + +/// These other sign extensions are for assisting with testing and debugging. /// More robust and simpler sign extensions in case /// complex sign extension routines obscure other bugs. @@ -65,13 +60,18 @@ class PartialProductGeneratorBruteSignExtension /// Construct a brute-force sign extending Partial Product Generator PartialProductGeneratorBruteSignExtension( super.multiplicand, super.multiplier, super.radixEncoder, - {super.signed, super.selectSigned, super.name = 'brute'}); + {super.signedMultiplicand, + super.signedMultiplier, + super.selectSignedMultiplicand, + super.selectSignedMultiplier, + super.name = 'brute'}); /// 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 (signedMultiplicand && (selectSignedMultiplicand != null)) { + throw RohdHclException('multiplicand sign reconfiguration requires ' + 'signedMultiplicand=false'); } if (isSignExtended) { throw RohdHclException('Partial Product array already sign-extended'); @@ -80,12 +80,11 @@ 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]); final Logic sign; - if (selectSigned != null) { - sign = mux(selectSigned!, addend.last, signs[row]); + if (selectSignedMultiplicand != null) { + sign = mux(selectSignedMultiplicand!, addend.last, signs[row]); } else { - sign = signed ? addend.last : signs[row]; + sign = signedMultiplicand ? addend.last : signs[row]; } addend.addAll(List.filled((rows - row) * shift, SignBit(sign))); if (row > 0) { @@ -108,7 +107,11 @@ class PartialProductGeneratorCompactSignExtension /// Construct a compact sign extending Partial Product Generator PartialProductGeneratorCompactSignExtension( super.multiplicand, super.multiplier, super.radixEncoder, - {super.signed, super.selectSigned, super.name = 'compact'}); + {super.signedMultiplicand, + super.selectSignedMultiplicand, + super.signedMultiplier, + super.selectSignedMultiplier, + super.name = 'compact'}); /// Sign extend the PP array using stop bits without adding a row. @override @@ -117,8 +120,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 (signedMultiplicand && (selectSignedMultiplicand != null)) { + throw RohdHclException('multiplicand sign reconfiguration requires ' + 'signedMultiplicand=false'); } if (isSignExtended) { throw RohdHclException('Partial Product array already sign-extended'); @@ -129,7 +133,7 @@ class PartialProductGeneratorCompactSignExtension final firstAddend = partialProducts[0]; final lastAddend = partialProducts[lastRow]; - final firstRowQStart = selector.width - (signed ? 1 : 0); + final firstRowQStart = selector.width - (signedMultiplicand ? 1 : 0); final lastRowSignPos = shift * lastRow; final alignRow0Sign = firstRowQStart - lastRowSignPos; @@ -137,11 +141,19 @@ class PartialProductGeneratorCompactSignExtension final propagate = List.generate(rows, (i) => List.filled(0, Logic(), growable: true)); + for (var row = 0; row < rows; row++) { propagate[row].add(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 <= alignRow0Sign) { + propagate[lastRow].add(SignBit(partialProducts[row][col++])); + } + } for (var col = 1; col < propagate[row].length; col++) { propagate[row][col] = propagate[row][col] & propagate[row][col - 1]; } @@ -154,6 +166,9 @@ class PartialProductGeneratorCompactSignExtension } m[row].addAll(List.filled(shift - 1, Logic())); } + while (m[lastRow].length < alignRow0Sign) { + m[lastRow].add(Logic()); + } for (var i = shift - 1; i < m[lastRow].length; i++) { m[lastRow][i] = lastAddend[i] ^ @@ -164,15 +179,16 @@ class PartialProductGeneratorCompactSignExtension for (var row = 0; row < lastRow; row++) { remainders[row] = propagate[row][shift - 1]; } - remainders[lastRow] <= propagate[lastRow][alignRow0Sign]; + remainders[lastRow] <= propagate[lastRow][max(alignRow0Sign, 0)]; // Compute Sign extension for row==0 - // final firstSign = !signed ? signs[0] : firstAddend.last; final Logic firstSign; - if (selectSigned == null) { - firstSign = signed ? SignBit(firstAddend.last) : SignBit(signs[0]); + if (selectSignedMultiplicand == null) { + firstSign = + signedMultiplicand ? SignBit(firstAddend.last) : SignBit(signs[0]); } else { - firstSign = SignBit(mux(selectSigned!, firstAddend.last, signs[0])); + firstSign = + SignBit(mux(selectSignedMultiplicand!, firstAddend.last, signs[0])); } final q = [ firstSign ^ remainders[lastRow], @@ -183,7 +199,7 @@ class PartialProductGeneratorCompactSignExtension for (var row = 0; row < rows; row++) { final addend = partialProducts[row]; if (row > 0) { - final mLimit = (row == lastRow) ? 2 * (shift - 1) : shift - 1; + final mLimit = (row == lastRow) ? alignRow0Sign : shift - 1; for (var i = 0; i < mLimit; i++) { addend[i] = m[row][i]; } @@ -196,7 +212,7 @@ class PartialProductGeneratorCompactSignExtension for (var i = 0; i < shift - 1; i++) { firstAddend[i] = m[0][i]; } - if (!signed) { + if (!signedMultiplicand) { firstAddend.add(q[0]); } else { firstAddend.last = q[0]; @@ -216,17 +232,23 @@ class PartialProductGeneratorStopBitsSignExtension /// Construct a stop bits sign extending Partial Product Generator PartialProductGeneratorStopBitsSignExtension( super.multiplicand, super.multiplier, super.radixEncoder, - {super.signed, super.selectSigned, super.name = 'stop_bits'}); + {super.signedMultiplicand, + super.selectSignedMultiplicand, + super.signedMultiplier, + super.selectSignedMultiplier, + super.name = '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 (signedMultiplicand && (selectSignedMultiplicand != null)) { + throw RohdHclException('multiplicand sign reconfiguration requires ' + 'signedMultiplicand=false'); } if (isSignExtended) { throw RohdHclException('Partial Product array already sign-extended'); @@ -246,15 +268,16 @@ class PartialProductGeneratorStopBitsSignExtension for (var row = 0; row < rows; row++) { final addend = partialProducts[row]; final Logic sign; - if (selectSigned != null) { - sign = mux(selectSigned!, addend.last, signs[row]); + if (selectSignedMultiplicand != null) { + sign = mux(selectSignedMultiplicand!, addend.last, signs[row]); } else { - sign = signed ? addend.last : signs[row]; + sign = signedMultiplicand ? addend.last : signs[row]; } if (row == 0) { - if (!signed) { + if (!signedMultiplicand) { addend.addAll(List.filled(shift, SignBit(sign))); } else { + // either is signed? addend.addAll(List.filled(shift - 1, SignBit(sign))); // signed only? } addend.add(SignBit(~sign, inverted: true)); @@ -275,7 +298,7 @@ class PartialProductGeneratorStopBitsSignExtension finalCarryPos - (extensionRow.length + rowShift[finalCarryRow]), Const(0))) ..add(SignBit(signs[rows - 1])); - } else if (signed | (selectSigned != null)) { + } else if (signedMultiplier | (selectSignedMultiplier != null)) { // Create an extra row to hold the final carry bit partialProducts .add(List.filled(selector.width, Const(0), growable: true)); @@ -284,7 +307,8 @@ class PartialProductGeneratorStopBitsSignExtension // Hack for radix-2 if (shift == 1) { - partialProducts.last.last = ~partialProducts.last.last; + addStopSignFlip( + partialProducts.last, SignBit(Const(1), inverted: true)); } } } @@ -296,7 +320,11 @@ class PartialProductGeneratorCompactRectSignExtension /// Construct a compact rect sign extending Partial Product Generator PartialProductGeneratorCompactRectSignExtension( super.multiplicand, super.multiplier, super.radixEncoder, - {required super.signed, super.selectSigned, super.name = 'compact_rect'}); + {super.signedMultiplicand, + super.signedMultiplier, + super.selectSignedMultiplicand, + super.selectSignedMultiplier, + super.name = 'compact_rect'}); /// Sign extend the PP array using stop bits without adding a row /// This routine works with different widths of multiplicand/multiplier, @@ -304,8 +332,9 @@ class PartialProductGeneratorCompactRectSignExtension /// Desmond A. Kirkpatrick @override void signExtend() { - if (signed && (selectSigned != null)) { - throw RohdHclException('sign reconfiguration requires signed=false'); + if (signedMultiplicand && (selectSignedMultiplicand != null)) { + throw RohdHclException('multiplicand sign reconfiguration requires ' + 'signedMultiplicand=false'); } if (isSignExtended) { throw RohdHclException('Partial Product array already sign-extended'); @@ -316,7 +345,7 @@ class PartialProductGeneratorCompactRectSignExtension final firstAddend = partialProducts[0]; final lastAddend = partialProducts[lastRow]; - final firstRowQStart = selector.width - (signed ? 1 : 0); + final firstRowQStart = selector.width - (signedMultiplicand ? 1 : 0); final lastRowSignPos = shift * lastRow; final align = firstRowQStart - lastRowSignPos; @@ -354,6 +383,7 @@ class PartialProductGeneratorCompactRectSignExtension } m[row].addAll(List.filled(shift - 1, Logic())); } + while (m[lastRow].length < align) { m[lastRow].add(Logic()); } @@ -392,14 +422,13 @@ class PartialProductGeneratorCompactRectSignExtension // 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 Logic firstSign; - if (selectSigned == null) { - firstSign = signed ? SignBit(firstAddend.last) : SignBit(signs[0]); + if (selectSignedMultiplicand == null) { + firstSign = + signedMultiplicand ? SignBit(firstAddend.last) : SignBit(signs[0]); } else { - firstSign = SignBit(mux(selectSigned!, firstAddend.last, signs[0])); + firstSign = + SignBit(mux(selectSignedMultiplicand!, firstAddend.last, signs[0])); } final lastSign = SignBit(remainders[lastRow]); // Compute Sign extension MSBs for firstRow @@ -423,13 +452,14 @@ class PartialProductGeneratorCompactRectSignExtension if (-align >= q.length) { q.last = SignBit(~firstSign, inverted: true); } - addStopSign(firstAddend, SignBit(q[0])); firstAddend.addAll(q.getRange(1, q.length)); if (-align >= q.length) { - final finalCarryRelPos = - lastRowSignPos - selector.width - shift + (signed ? 1 : 0); + final finalCarryRelPos = lastRowSignPos - + selector.width - + shift + + (signedMultiplicand ? 1 : 0); final finalCarryRow = (finalCarryRelPos / shift).floor(); final curRowLength = partialProducts[finalCarryRow].length + rowShift[finalCarryRow]; diff --git a/lib/src/component_config/components/config_carry_save_multiplier.dart b/lib/src/component_config/components/config_carry_save_multiplier.dart index 96374d80..f75c02af 100644 --- a/lib/src/component_config/components/config_carry_save_multiplier.dart +++ b/lib/src/component_config/components/config_carry_save_multiplier.dart @@ -16,20 +16,16 @@ class CarrySaveMultiplierConfigurator extends Configurator { /// A knob controlling the width of the inputs to a [CarrySaveMultiplier]. final IntConfigKnob logicWidthKnob = IntConfigKnob(value: 8); - /// A knob controling the sign of a [CarrySaveMultiplier] - final signChoiceKnob = ChoiceConfigKnob([false, true], value: false); - @override final String name = 'Carry Save Multiplier'; @override CarrySaveMultiplier createModule() => CarrySaveMultiplier( Logic(width: logicWidthKnob.value), Logic(width: logicWidthKnob.value), - clk: Logic(), reset: Logic(), signed: true); + clk: Logic(), reset: Logic()); @override late final Map> knobs = UnmodifiableMapView({ 'Width': logicWidthKnob, - 'Sign': signChoiceKnob, }); } diff --git a/lib/src/component_config/components/config_compression_tree_multiplier.dart b/lib/src/component_config/components/config_compression_tree_multiplier.dart index 3b63e8ad..5b9e7764 100644 --- a/lib/src/component_config/components/config_compression_tree_multiplier.dart +++ b/lib/src/component_config/components/config_compression_tree_multiplier.dart @@ -40,6 +40,14 @@ class CompressionTreeMultiplierConfigurator extends Configurator { /// Controls the width of the multiplier.! final IntConfigKnob multiplierWidthKnob = IntConfigKnob(value: 5); + /// A knob controlling the sign of the multiplicand + final ChoiceConfigKnob signMultiplicandValueKnob = + ChoiceConfigKnob(['unsigned', 'signed', 'selected'], value: 'unsigned'); + + /// A knob controlling the sign of the multiplier + final ChoiceConfigKnob signMultiplierValueKnob = + ChoiceConfigKnob(['unsigned', 'signed', 'selected'], value: 'unsigned'); + /// Controls whether the adder is pipelined final ToggleConfigKnob pipelinedKnob = ToggleConfigKnob(value: false); @@ -49,6 +57,12 @@ class CompressionTreeMultiplierConfigurator extends Configurator { Logic(name: 'a', width: multiplicandWidthKnob.value), Logic(name: 'b', width: multiplierWidthKnob.value), radixKnob.value, + signedMultiplicand: signMultiplicandValueKnob.value == 'signed', + signedMultiplier: signMultiplierValueKnob.value == 'signed', + selectSignedMultiplicand: + signMultiplicandValueKnob.value == 'selected' ? Logic() : null, + selectSignedMultiplier: + signMultiplierValueKnob.value == 'selected' ? Logic() : null, ppTree: generatorMap[prefixTreeKnob.value]!); @override @@ -56,7 +70,9 @@ class CompressionTreeMultiplierConfigurator extends Configurator { 'Tree type': prefixTreeKnob, 'Radix': radixKnob, 'Multiplicand width': multiplicandWidthKnob, + 'Multiplicand sign': signMultiplicandValueKnob, 'Multiplier width': multiplierWidthKnob, + 'Multiplier sign': signMultiplierValueKnob, 'Pipelined': pipelinedKnob, }); diff --git a/lib/src/utils.dart b/lib/src/utils.dart index e27b7f08..102149e6 100644 --- a/lib/src/utils.dart +++ b/lib/src/utils.dart @@ -50,3 +50,16 @@ extension LogicValueMajority on LogicValue { return result; } } + +/// This extension will provide conversion to Signed or Unsigned BigInt +extension SignedBigInt on BigInt { + /// Convert a BigInt to Signed when [signed] is true + BigInt toCondSigned(int width, {bool signed = false}) => + signed ? toSigned(width) : toUnsigned(width); + + /// Construct a Signed BigInt from an int when [signed] is true + static BigInt fromSignedInt(int value, int width, {bool signed = false}) => + signed + ? BigInt.from(value).toSigned(width) + : BigInt.from(value).toUnsigned(width); +} diff --git a/test/arithmetic/addend_compressor_test.dart b/test/arithmetic/addend_compressor_test.dart index 77cf3833..d479e940 100644 --- a/test/arithmetic/addend_compressor_test.dart +++ b/test/arithmetic/addend_compressor_test.dart @@ -8,8 +8,6 @@ // Author: Desmond Kirkpatrick import 'dart:async'; -import 'dart:io'; -import 'dart:math'; import 'package:rohd/rohd.dart'; import 'package:rohd_hcl/rohd_hcl.dart'; import 'package:rohd_hcl/src/arithmetic/evaluate_compressor.dart'; @@ -17,6 +15,8 @@ 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'; +/// This [CompressorTestMod] module is used to test instantiation, where we can +/// catch trace errors (IO not added) not found in a simple test instantiation. class CompressorTestMod extends Module { late final PartialProductGenerator pp; @@ -37,7 +37,7 @@ class CompressorTestMod extends Module { } pp = PartialProductGeneratorCompactRectSignExtension(a, b, encoder, - signed: signed); + signedMultiplicand: signed, signedMultiplier: signed); compressor = ColumnCompressor(pp, clk: clk); compressor.compress(); final r0 = addOutput('r0', width: compressor.columns.length); @@ -48,159 +48,10 @@ class CompressorTestMod extends Module { } } -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 = signed - ? BigInt.from(i).toSigned(widthX) - : BigInt.from(i).toUnsigned(widthX); - final Y = signed - ? BigInt.from(j).toSigned(widthY) - : BigInt.from(j).toUnsigned(widthY); - - checkCompressor(pp, X, Y); - } - } -} - -void testCompressionRandom(PartialProductGenerator pp, int iterations) { - final widthX = pp.selector.multiplicand.width; - final widthY = pp.encoder.multiplier.width; - - final value = Random(47); - for (var i = 0; i < iterations; i++) { - final X = pp.signed - ? value.nextLogicValue(width: widthX).toBigInt().toSigned(widthX) - : value.nextLogicValue(width: widthX).toBigInt().toUnsigned(widthX); - final Y = pp.signed - ? value.nextLogicValue(width: widthY).toBigInt().toSigned(widthY) - : value.nextLogicValue(width: widthY).toBigInt().toUnsigned(widthY); - - checkCompressor(pp, X, Y); - } -} - -void checkCompressor(PartialProductGenerator pp, BigInt X, BigInt Y) { - final widthX = pp.selector.multiplicand.width; - final widthY = pp.encoder.multiplier.width; - final compressor = ColumnCompressor(pp); - - final product = X * Y; - - pp.multiplicand.put(X); - pp.multiplier.put(Y); - final value = pp.evaluate(); - expect(value, equals(product), - reason: 'Fail: $X * $Y: $value ' - 'vs expected $product' - '\n$pp'); - final evaluateValue = compressor.evaluate(); - if (evaluateValue.$1 != product) { - stdout - ..write('Fail: $X)$widthX] * $Y[$widthY]: $evaluateValue ' - 'vs expected $product\n') - ..write(pp); - } - compressor.compress(); - final compressedValue = compressor.evaluate().$1; - expect(compressedValue, equals(product), - reason: 'Fail: $X[$widthX] * $Y[$widthY]: $compressedValue ' - 'vs expected $product' - '\n$pp'); - final compressedLogicValue = compressor.evaluate(logic: true).$1; - expect(compressedLogicValue, equals(product), - reason: 'Fail: $X[$widthX] * $Y[$widthY]: $compressedLogicValue ' - 'vs expected $product' - '\n$pp'); - - final a = compressor.extractRow(0); - final b = compressor.extractRow(1); - - final adder = ParallelPrefixAdder(a, b); - final adderValue = - adder.sum.value.toBigInt().toSigned(compressor.columns.length); - expect(adderValue, equals(product), - reason: 'Fail: $X[$widthX] * $Y[$widthY]: ' - '$adderValue vs expected $product' - '\n$pp'); -} - void main() { tearDown(() async { await Simulator.reset(); }); - test('ColumnCompressor: random evaluate: square radix-4, just CompactRect', - () async { - for (final signed in [false, true]) { - for (var radix = 4; radix < 8; radix *= 2) { - final encoder = RadixEncoder(radix); - // stdout.write('encoding with radix=$radix\n'); - final shift = log2Ceil(encoder.radix); - for (var width = shift + 1; width < 2 * shift + 1; width++) { - for (final signExtension in SignExtension.values) { - if (signExtension != SignExtension.compactRect) { - continue; - } - final ppg = curryPartialProductGenerator(signExtension); - 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); - } - testCompressionRandom(pp, 10); - } - } - } - } - } - }); - test('Column Compressor: single compressor evaluate', () async { - const widthX = 6; - const widthY = 9; - final a = Logic(name: 'a', width: widthX); - final b = Logic(name: 'b', width: widthY); - - const av = 4; - const bv = 14; - for (final signed in [true, false]) { - 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 = 2; - final encoder = RadixEncoder(radix); - final pp = PartialProductGeneratorCompactRectSignExtension(a, b, encoder, - signed: signed); - expect(pp.evaluate(), equals(BigInt.from(av * bv))); - final compressor = ColumnCompressor(pp); - expect(compressor.evaluate().$1, equals(BigInt.from(av * bv))); - compressor.compress(); - expect(compressor.evaluate().$1, equals(BigInt.from(av * bv))); - } - }); test('Column Compressor: evaluate flopped', () async { final clk = SimpleClockGenerator(10).clk; @@ -211,8 +62,8 @@ void main() { var av = 3; const bv = 6; - var bA = BigInt.from(av).toSigned(widthX); - final bB = BigInt.from(bv).toSigned(widthY); + var bA = SignedBigInt.fromSignedInt(av, widthX, signed: true); + final bB = SignedBigInt.fromSignedInt(bv, widthY, signed: true); // Set these so that printing inside module build will have Logic values a.put(bA); @@ -236,79 +87,36 @@ void main() { equals(BigInt.from(av * bv))); await Simulator.endSimulation(); }); - - test('example multiplier', () async { - const widthX = 10; - const widthY = 10; + test('Column Compressor: single 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 = 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); + const av = -3; + const bv = -3; + for (final signed in [true, false]) { + final bA = SignedBigInt.fromSignedInt(av, widthX, signed: signed); + final bB = SignedBigInt.fromSignedInt(bv, widthY, signed: signed); // 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()); - + final selectSignedMultiplicand = useSelect ? Logic() : null; + final selectSignedMultiplier = useSelect ? Logic() : null; + if (useSelect) { + selectSignedMultiplicand!.put(signed ? 1 : 0); + selectSignedMultiplier!.put(signed ? 1 : 0); + } + final pp = PartialProductGeneratorCompactRectSignExtension( + a, b, encoder, + signedMultiplicand: !useSelect & signed, + signedMultiplier: !useSelect & signed, + selectSignedMultiplicand: selectSignedMultiplicand, + selectSignedMultiplier: selectSignedMultiplier); expect(pp.evaluate(), equals(bA * bB)); final compressor = ColumnCompressor(pp); expect(compressor.evaluate().$1, equals(bA * bB)); diff --git a/test/arithmetic/carry_save_multiplier_test.dart b/test/arithmetic/carry_save_multiplier_test.dart index 9cffff90..35778a80 100644 --- a/test/arithmetic/carry_save_multiplier_test.dart +++ b/test/arithmetic/carry_save_multiplier_test.dart @@ -25,8 +25,7 @@ void main() { final clk = SimpleClockGenerator(10).clk; final reset = Logic(name: 'reset'); - expect( - () => CarrySaveMultiplier(clk: clk, reset: reset, signed: true, a, b), + expect(() => CarrySaveMultiplier(clk: clk, reset: reset, a, b), throwsA(const TypeMatcher())); }); @@ -37,7 +36,7 @@ void main() { final reset = Logic(name: 'reset'); final clk = SimpleClockGenerator(10).clk; - final csm = CarrySaveMultiplier(clk: clk, reset: reset, signed: true, a, b); + final csm = CarrySaveMultiplier(clk: clk, reset: reset, a, b); await csm.build(); diff --git a/test/arithmetic/multiplier_encoder_test.dart b/test/arithmetic/multiplier_encoder_test.dart index 485728b1..13570b83 100644 --- a/test/arithmetic/multiplier_encoder_test.dart +++ b/test/arithmetic/multiplier_encoder_test.dart @@ -16,134 +16,226 @@ 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'; -void checkEvaluateExhaustive(PartialProductGenerator pp) { +// Here are the variations needed testing: +// - Sign variants +// - Multiplicand: [unsigned, signed], [selectedUnsigned, selectedSigned] +// - Multiplier: [unsigned, signed], [selectedUnsigned, selectedSigned] +// - Radix Encodings: [2,4,8,16] +// - Widths: +// - Cross the shift intervals for each radix +// - Rectangular: again, need to cross a shift interval +// - Sign Extension: [brute, stop, compact, compactRect] + +void testPartialProductExhaustive(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 = 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(); - expect(value, equals(X * Y), - reason: '$X * $Y = $value should be ${X * Y}'); + + final multiplicandSigns = pp.signedMultiplicand + ? [true] + : (pp.selectSignedMultiplicand != null) + ? [false, true] + : [false]; + final multiplierSigns = pp.signedMultiplier + ? [true] + : (pp.selectSignedMultiplier != null) + ? [false, true] + : [false]; + test( + 'exhaustive: ${pp.name} R${pp.selector.radix} ' + 'WD=${pp.multiplicand.width} WM=${pp.multiplier.width} ' + 'SD=${pp.signedMultiplicand ? 1 : 0} ' + 'SM=${pp.signedMultiplier ? 1 : 0} ' + 'SelD=${pp.selectSignedMultiplicand != null ? 1 : 0} ' + 'SelM=${pp.selectSignedMultiplier != null ? 1 : 0}', () async { + for (var i = 0; i < limitX; i++) { + for (var j = 0; j < limitY; j++) { + for (final multiplicandSign in multiplicandSigns) { + final X = + SignedBigInt.fromSignedInt(i, widthX, signed: multiplicandSign); + if (pp.selectSignedMultiplicand != null) { + pp.selectSignedMultiplicand!.put(multiplicandSign ? 1 : 0); + } + for (final multiplierSign in multiplierSigns) { + final Y = + SignedBigInt.fromSignedInt(j, widthY, signed: multiplierSign); + if (pp.selectSignedMultiplier != null) { + pp.selectSignedMultiplier!.put(multiplierSign ? 1 : 0); + } + checkPartialProduct(pp, X, Y); + } + } + } } - } + }); } -void checkEvaluateRandom(PartialProductGenerator pp, int nSamples) { +void testPartialProductRandom(PartialProductGenerator pp, int iterations) { 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 = signed ? rX.toSigned(widthX) : rX; - final Y = signed ? rY.toSigned(widthY) : rY; - pp.multiplicand.put(X); - pp.multiplier.put(Y); - final value = pp.evaluate(); - expect(value, equals(X * Y), reason: '$X * $Y = $value should be ${X * Y}'); - } + + final multiplicandSigns = pp.signedMultiplicand + ? [true] + : (pp.selectSignedMultiplicand != null) + ? [false, true] + : [false]; + final multiplierSigns = pp.signedMultiplier + ? [true] + : (pp.selectSignedMultiplier != null) + ? [false, true] + : [false]; + + test( + 'random: ${pp.name} R${pp.selector.radix} ' + 'WD=${pp.multiplicand.width} WM=${pp.multiplier.width} ' + 'SD=${pp.signedMultiplicand ? 1 : 0} ' + 'SM=${pp.signedMultiplier ? 1 : 0} ' + 'SelD=${pp.selectSignedMultiplicand != null ? 1 : 0} ' + 'SelM=${pp.selectSignedMultiplier != null ? 1 : 0}', () async { + final value = Random(47); + for (var i = 0; i < iterations; i++) { + for (final multiplicandSign in multiplicandSigns) { + final X = value + .nextLogicValue(width: widthX) + .toBigInt() + .toCondSigned(widthX, signed: multiplicandSign); + if (pp.selectSignedMultiplicand != null) { + pp.selectSignedMultiplicand!.put(multiplicandSign ? 1 : 0); + } + for (final multiplierSign in multiplierSigns) { + final Y = value + .nextLogicValue(width: widthY) + .toBigInt() + .toCondSigned(widthY, signed: multiplierSign); + if (pp.selectSignedMultiplier != null) { + pp.selectSignedMultiplier!.put(multiplierSign ? 1 : 0); + } + checkPartialProduct(pp, X, Y); + } + } + } + }); } -void main() { - test('single MAC partial product test', () async { - final encoder = RadixEncoder(16); - const widthX = 8; - const widthY = 18; +void testPartialProductSingle(PartialProductGenerator pp, BigInt X, BigInt Y) { + test( + 'single: ${pp.name} R${pp.selector.radix} ' + 'WD=${pp.multiplicand.width} WM=${pp.multiplier.width} ' + 'SD=${pp.signedMultiplicand ? 1 : 0} ' + 'SM=${pp.signedMultiplier ? 1 : 0} ' + 'SelD=${pp.selectSignedMultiplicand != null ? 1 : 0} ' + 'SelM=${pp.selectSignedMultiplier != null ? 1 : 0}', () async { + if (pp.selectSignedMultiplicand != null) { + pp.selectSignedMultiplicand!.put(X.isNegative ? 1 : 0); + } + if (pp.selectSignedMultiplier != null) { + pp.selectSignedMultiplier!.put(Y.isNegative ? 1 : 0); + } + checkPartialProduct(pp, X, Y); + }); +} - const i = 1478; - const j = 9; - const k = 0; +void checkPartialProduct(PartialProductGenerator pp, BigInt iX, BigInt iY) { + final widthX = pp.selector.multiplicand.width; + final widthY = pp.encoder.multiplier.width; - final X = BigInt.from(i).toSigned(widthX); - final Y = BigInt.from(j).toSigned(widthY); - final Z = BigInt.from(k).toSigned(widthX + widthY); - // print('X=$X Y=$Y, Z=$Z'); - final product = X * Y + Z; + final X = iX.toCondSigned(widthX, signed: pp.isSignedMultiplicand()); + final Y = iY.toCondSigned(widthY, signed: pp.isSignedMultiplier()); - final logicX = Logic(name: 'X', width: widthX); - final logicY = Logic(name: 'Y', width: widthY); - final logicZ = Logic(name: 'Z', width: widthX + widthY); - logicX.put(X); - logicY.put(Y); - logicZ.put(Z); - final pp = PartialProductGeneratorCompactRectSignExtension( - logicX, logicY, encoder, - signed: true); + final product = X * Y; - final lastLength = - pp.partialProducts[pp.rows - 1].length + pp.rowShift[pp.rows - 1]; + pp.multiplicand.put(X); + pp.multiplier.put(Y); + final value = pp.evaluate(); + expect(value, equals(product), + reason: 'Fail1: $X * $Y: $value ' + 'vs expected $product' + '\n$pp'); +} - final sign = logicZ[logicZ.width - 1]; - // for unsigned versus signed testing - // final sign = signed ? logicZ[logicZ.width - 1] : Const(0); - final l = [for (var i = 0; i < logicZ.width; i++) logicZ[i]]; - while (l.length < lastLength) { - l.add(sign); +void main() { + tearDown(() async { + await Simulator.reset(); + }); + + group('PartialProduct: fixed sign variants', () { + for (final signedMultiplicand in [false, true]) { + for (final signedMultiplier in [false, true]) { + for (final radix in [2, 4]) { + final width = log2Ceil(radix) + (signedMultiplier ? 1 : 0); + for (final signExtension + in SignExtension.values.where((e) => e != SignExtension.none)) { + final ppg = curryPartialProductGenerator(signExtension); + final pp = ppg(Logic(name: 'X', width: width), + Logic(name: 'Y', width: width), RadixEncoder(radix), + signedMultiplicand: signedMultiplicand, + signedMultiplier: signedMultiplier); + testPartialProductExhaustive(pp); + } + } + } } - l - ..add(~sign) - ..add(Const(1)); - // print(pp.representation()); + }); + group('PartialProduct: singleton fixed sign variants', () { + const radix = 16; + final encoder = RadixEncoder(radix); - pp.partialProducts.insert(0, l); - pp.rowShift.insert(0, 0); - // print(pp.representation()); + for (final signedMultiplicand in [false, true]) { + for (final signedMultiplier in [false, true]) { + final width = log2Ceil(radix) + (signedMultiplier ? 1 : 0); + final a = Logic(name: 'X', width: width); + final b = Logic(name: 'Y', width: width); - if (pp.evaluate() != product) { - stdout.write('Fail: $X * $Y: ${pp.evaluate()} vs expected $product\n'); + const i = 0; + const j = 0; + final X = + SignedBigInt.fromSignedInt(i, width, signed: signedMultiplicand); + final Y = + SignedBigInt.fromSignedInt(j, width, signed: signedMultiplier); + a.put(X); + b.put(Y); + final PartialProductGenerator pp; + pp = PartialProductGeneratorStopBitsSignExtension(a, b, encoder, + signedMultiplicand: signedMultiplicand, + signedMultiplier: signedMultiplier); + testPartialProductSingle(pp, X, Y); + } } - expect(pp.evaluate(), equals(product)); }); - test('majority function', () async { - expect(LogicValue.ofBigInt(BigInt.from(7), 5).majority(), true); - expect(LogicValue.ofBigInt(BigInt.from(7) << 1, 5).majority(), true); - expect(LogicValue.ofBigInt(BigInt.from(11) << 1, 5).majority(), true); - expect(LogicValue.ofBigInt(BigInt.from(9) << 1, 5).majority(), false); - expect(LogicValue.ofBigInt(BigInt.from(7) << 3, 7).majority(), false); - }); + group('PartialProduct: fixed/select sign variants', () { + final selectSignMultiplicand = Logic(); + final selectSignMultiplier = Logic(); + for (final radix in [2, 4]) { + final encoder = RadixEncoder(radix); + for (final selectMultiplicand in [false, true]) { + for (final signMultiplicand + in (!selectMultiplicand ? [false, true] : [false])) { + for (final selectMultiplier in [false, true]) { + for (final signMultiplier + in (!selectMultiplier ? [false, true] : [false])) { + selectSignMultiplicand.put(selectMultiplicand ? 1 : 0); + selectSignMultiplier.put(selectMultiplier ? 1 : 0); + for (final signExtension in SignExtension.values + .where((e) => e != SignExtension.none)) { + final width = log2Ceil(radix) + (signMultiplier ? 1 : 0); + final ppg = curryPartialProductGenerator(signExtension); + final PartialProductGenerator pp; + pp = ppg( + Logic(name: 'X', width: width), + Logic(name: 'Y', width: width), + encoder, + signedMultiplicand: signMultiplicand, + signedMultiplier: signMultiplier, + selectSignedMultiplicand: + selectMultiplicand ? selectSignMultiplicand : null, + selectSignedMultiplier: + selectMultiplier ? selectSignMultiplier : null); - // This is a two-minute exhaustive but quick test - test('exhaustive partial product evaluate: square radix-4, all extension', - () async { - for (final signed in [false, true]) { - for (var radix = 4; radix < 8; radix *= 2) { - const radix = 4; - final encoder = RadixEncoder(radix); - final shift = log2Ceil(encoder.radix); - final minWidth = shift + (signed ? 1 : 0); - for (var width = minWidth; width < shift * 2 + 1; width++) { - for (final signExtension in SignExtension.values) { - if (signExtension == SignExtension.none) { - continue; - } - final ppg = curryPartialProductGenerator(signExtension); - 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); + testPartialProductExhaustive(pp); } - checkEvaluateExhaustive(pp); } } } @@ -151,36 +243,121 @@ void main() { } }); - test('Multiplier encoder rectangular test,', () async { - for (final signed in [false, true]) { - for (var radix = 2; radix < 8; radix *= 2) { - final encoder = RadixEncoder(radix); - final shift = log2Ceil(encoder.radix); - for (var width = 3 + shift + 1; width < 3 + shift * 2 + 1; width++) { - for (var skew = -3; skew < shift * 2; skew++) { - // Only some sign extension routines have rectangular support - // Commented out rectangular extension routines for speedup + group('PartialProduct: singleton fixed/select sign variants', () { + const radix = 4; + final encoder = RadixEncoder(radix); + const width = 4; + + final selectSignMultiplicand = Logic(); + final selectSignMultiplier = Logic(); + + for (final selectMultiplicand in [false, true]) { + for (final selectMultiplier in [false, true]) { + selectSignMultiplicand.put(selectMultiplicand ? 1 : 0); + selectSignMultiplier.put(selectMultiplier ? 1 : 0); + final PartialProductGenerator pp; + pp = PartialProductGeneratorStopBitsSignExtension( + Logic(name: 'X', width: width), + Logic(name: 'Y', width: width), + encoder, + signedMultiplicand: !selectMultiplicand, + signedMultiplier: !selectMultiplier, + selectSignedMultiplicand: + selectMultiplicand ? selectSignMultiplicand : null, + selectSignedMultiplier: + selectMultiplier ? selectSignMultiplier : null); + + const i = 6; + const j = -6; + final X = SignedBigInt.fromSignedInt(i, width, + signed: pp.isSignedMultiplicand()); + final Y = SignedBigInt.fromSignedInt(j, width, + signed: pp.isSignedMultiplier()); + + testPartialProductSingle(pp, X, Y); + } + } + }); + + group('PartialProduct: width/radix/extension sweep', () { + for (var radix = 2; radix < 16; radix *= 2) { + final encoder = RadixEncoder(radix); + final shift = log2Ceil(encoder.radix); + for (var width = shift; width < min(5, 2 * shift); width++) { + for (final signExtension + in SignExtension.values.where((e) => e != SignExtension.none)) { + final ppg = curryPartialProductGenerator(signExtension); + final pp = ppg(Logic(name: 'X', width: width), + Logic(name: 'Y', width: width), encoder); + testPartialProductExhaustive(pp); + } + } + } + }); + group('PartialProduct: rectangle/radix/extension sweep', () { + for (var radix = 2; radix < 8; radix *= 2) { + final encoder = RadixEncoder(radix); + final shift = log2Ceil(encoder.radix); + for (final signedMultiplicand in [false, true]) { + final widthX = shift + 2; + for (final signedMultiplier in [false, true]) { + for (var widthY = shift + (signedMultiplier ? 1 : 0); + widthY < shift + 3 + (signedMultiplier ? 1 : 0); + widthY++) { + for (final signExtension in [ + SignExtension.stopBits, + SignExtension.compactRect + ]) { + final ppg = curryPartialProductGenerator(signExtension); + final pp = ppg(Logic(name: 'X', width: widthX), + Logic(name: 'Y', width: widthY), encoder, + signedMultiplicand: signedMultiplicand, + signedMultiplier: signedMultiplier); + testPartialProductExhaustive(pp); + } + } + } + } + } + }); + + group('PartialProduct: minimum width', () { + for (var radix = 2; radix < 32; radix *= 2) { + final encoder = RadixEncoder(radix); + final shift = log2Ceil(encoder.radix); + for (var width = shift; width < min(5, 2 * shift); width++) { + for (final signExtension + in SignExtension.values.where((e) => e != SignExtension.none)) { + final ppg = curryPartialProductGenerator(signExtension); + final pp = ppg(Logic(name: 'X', width: width), + Logic(name: 'Y', width: width), encoder); + testPartialProductExhaustive(pp); + } + } + } + }); + + group('PartialProduct: minimum rectangle', () { + for (var radix = 2; radix < 32; radix *= 2) { + final encoder = RadixEncoder(radix); + final shift = log2Ceil(encoder.radix); + for (final signedMultiplicand in [false, true]) { + final widthX = shift; + for (final signedMultiplier in [false, true]) { + for (var widthY = shift + (signedMultiplier ? 1 : 0); + widthY < shift + 1 + (signedMultiplier ? 1 : 0); + widthY++) { for (final signExtension in [ SignExtension.brute, + SignExtension.stopBits, SignExtension.compactRect ]) { final ppg = curryPartialProductGenerator(signExtension); - 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, 10); - } + final pp = ppg(Logic(name: 'X', width: widthX), + Logic(name: 'Y', width: widthY), encoder, + signedMultiplicand: signedMultiplicand, + signedMultiplier: signedMultiplier); + testPartialProductExhaustive(pp); } } } @@ -188,7 +365,7 @@ void main() { } }); - test('Rectangle Q collision tests,', () async { + group('Rectangle Q collision tests New,', () { // These collide with the normal q extension bits // These are unsigned tests @@ -207,69 +384,99 @@ void main() { final width = align.$2; final skew = align.$3; - final X = BigInt.from(29).toUnsigned(width); - final Y = BigInt.from(2060).toUnsigned(width + skew); - final product = X * Y; final pp = PartialProductGeneratorCompactRectSignExtension( Logic(name: 'X', width: width), Logic(name: 'Y', width: width + skew), - encoder, - signed: false); + encoder); - pp.multiplicand.put(X); - pp.multiplier.put(Y); - expect(pp.evaluate(), equals(product)); - checkEvaluateRandom(pp, 100); + testPartialProductRandom(pp, 10); } } }); - test('minimum width verification,', () async { + test('PartialProduct: flat test', () { + const radix = 4; + final radixEncoder = RadixEncoder(radix); + const widthX = 6; + const widthY = 3; + final limitX = pow(2, widthX); + final limitY = pow(2, widthY); + final multiplicand = Logic(width: widthX); + final multiplier = Logic(width: widthY); for (final signed in [false, true]) { - for (var radix = 2; radix < 32; radix *= 2) { - final encoder = RadixEncoder(radix); - final shift = log2Ceil(encoder.radix); - final width = shift + (signed ? 1 : 0); - const skew = 0; - // Only some sign extension routines have rectangular support - // Commented out rectangular extension routines for speedup - for (final signExtension in SignExtension.values) { - if (signExtension == SignExtension.none) { - continue; - } - final ppg = curryPartialProductGenerator(signExtension); - final pp = ppg(Logic(name: 'X', width: width), - Logic(name: 'Y', width: width + skew), encoder, - signed: signed); - checkEvaluateRandom(pp, 100); + final pp = PartialProductGeneratorCompactSignExtension( + multiplicand, multiplier, radixEncoder, + signedMultiplicand: signed, signedMultiplier: signed); + 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 = signed ? i.toSigned(widthX) : i.toUnsigned(widthX); + final Y = signed ? j.toSigned(widthY) : j.toUnsigned(widthY); + multiplicand.put(X); + multiplier.put(Y); + final value = pp.evaluate(); + expect(value, equals(X * Y), + reason: '$X * $Y = $value should be ${X * Y}'); } } } }); - test('minimum rectangular width verification,', () async { - for (final signed in [false, true]) { - for (var radix = 2; radix < 32; radix *= 2) { - final encoder = RadixEncoder(radix); - final shift = log2Ceil(encoder.radix); - final width = shift; - final skew = (signed ? 1 : 0); - // Only some sign extension routines have rectangular support - // 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); - checkEvaluateExhaustive(pp); - } - } - } + + test('single MAC partial product test', () async { + final encoder = RadixEncoder(16); + const widthX = 8; + const widthY = 18; + + const i = 1478; + const j = 9; + const k = 0; + + final X = BigInt.from(i).toSigned(widthX); + final Y = BigInt.from(j).toSigned(widthY); + final Z = BigInt.from(k).toSigned(widthX + widthY); + // print('X=$X Y=$Y, Z=$Z'); + final product = X * Y + Z; + + final logicX = Logic(name: 'X', width: widthX); + final logicY = Logic(name: 'Y', width: widthY); + final logicZ = Logic(name: 'Z', width: widthX + widthY); + logicX.put(X); + logicY.put(Y); + logicZ.put(Z); + final pp = PartialProductGeneratorCompactRectSignExtension( + logicX, logicY, encoder, + signedMultiplicand: true, signedMultiplier: true); + + final lastLength = + pp.partialProducts[pp.rows - 1].length + pp.rowShift[pp.rows - 1]; + + final sign = logicZ[logicZ.width - 1]; + // for unsigned versus signed testing + // final sign = signed ? logicZ[logicZ.width - 1] : Const(0); + final l = [for (var i = 0; i < logicZ.width; i++) logicZ[i]]; + while (l.length < lastLength) { + l.add(sign); } + l + ..add(~sign) + ..add(Const(1)); + // print(pp.representation()); + + pp.partialProducts.insert(0, l); + pp.rowShift.insert(0, 0); + // print(pp.representation()); + + if (pp.evaluate() != product) { + stdout.write('Fail: $X * $Y: ${pp.evaluate()} vs expected $product\n'); + } + expect(pp.evaluate(), equals(product)); + }); + + test('majority function', () async { + expect(LogicValue.ofBigInt(BigInt.from(7), 5).majority(), true); + expect(LogicValue.ofBigInt(BigInt.from(7) << 1, 5).majority(), true); + expect(LogicValue.ofBigInt(BigInt.from(11) << 1, 5).majority(), true); + expect(LogicValue.ofBigInt(BigInt.from(9) << 1, 5).majority(), false); + expect(LogicValue.ofBigInt(BigInt.from(7) << 3, 7).majority(), false); }); } diff --git a/test/arithmetic/multiplier_test.dart b/test/arithmetic/multiplier_test.dart index 479c3fda..ad62b1e0 100644 --- a/test/arithmetic/multiplier_test.dart +++ b/test/arithmetic/multiplier_test.dart @@ -15,28 +15,69 @@ 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'; +/// The following routines are useful only during testing +extension TestMultiplierSignage on Multiplier { + /// Return true if multiplicand [a] is truly signed (fixed or runtime) + bool isSignedMultiplicand() => (selectSignedMultiplicand == null) + ? signedMultiplicand + : selectSignedMultiplicand!.value.isZero; + + /// Return true if multiplier [b] is truly signed (fixed or runtime) + bool isSignedMultiplier() => (selectSignedMultiplier == null) + ? signedMultiplier + : selectSignedMultiplier!.value.isZero; + + /// Return true if accumulate result is truly signed (fixed or runtime) + bool isSignedResult() => isSignedMultiplicand() | isSignedMultiplier(); +} + +/// The following routines are useful only during testing +extension TestMultiplierAccumulateSignage on MultiplyAccumulate { + /// Return true if multiplicand [a] is truly signed (fixed or runtime) + bool isSignedMultiplicand() => (selectSignedMultiplicand == null) + ? signedMultiplicand + : !selectSignedMultiplicand!.value.isZero; + + /// Return true if multiplier [b] is truly signed (fixed or runtime) + bool isSignedMultiplier() => (selectSignedMultiplier == null) + ? signedMultiplier + : !selectSignedMultiplier!.value.isZero; + + /// Return true if addend [c] is truly signed (fixed or runtime) + bool isSignedAddend() => (selectSignedAddend == null) + ? signedAddend + : !selectSignedAddend!.value.isZero; + + /// Return true if accumulate result is truly signed (fixed or runtime) + bool isSignedResult() => + isSignedAddend() | isSignedMultiplicand() | isSignedMultiplier(); +} + /// Simple multiplier to demonstrate instantiation of CompressionTreeMultiplier class SimpleMultiplier extends Module { /// The output of the simple multiplier late final Logic product; /// Construct a simple multiplier with runtime sign operation - SimpleMultiplier(Logic a, Logic b, Logic multASigned) + SimpleMultiplier( + Logic a, Logic b, Logic selSignedMultiplicand, Logic selSignedMultiplier) : super(name: 'my_test_module') { a = addInput('a', a, width: a.width); b = addInput('b', b, width: b.width); - multASigned = addInput('multASigned', multASigned); + selSignedMultiplicand = addInput('multDSigned', selSignedMultiplicand); + selSignedMultiplier = addInput('multMSigned', selSignedMultiplier); product = addOutput('product', width: a.width + b.width); - final mult = CompressionTreeMultiplier(a, b, 4, selectSigned: multASigned); + final mult = CompressionTreeMultiplier(a, b, 4, + selectSignedMultiplicand: selSignedMultiplicand, + selectSignedMultiplier: selSignedMultiplier); product <= mult.product; } } // Inner test of a multipy accumulate unit void checkMultiplyAccumulate( - MultiplyAccumulate mod, BigInt bA, BigInt bB, BigInt bC, - {bool signedTest = false}) { + MultiplyAccumulate mod, BigInt bA, BigInt bB, BigInt bC) { final golden = bA * bB + bC; // ignore: invalid_use_of_protected_member mod.a.put(bA); @@ -45,16 +86,15 @@ void checkMultiplyAccumulate( // ignore: invalid_use_of_protected_member mod.c.put(bC); - final result = signedTest - ? mod.accumulate.value.toBigInt().toSigned(mod.accumulate.width) - : mod.accumulate.value.toBigInt().toUnsigned(mod.accumulate.width); + final result = mod.accumulate.value + .toBigInt() + .toCondSigned(mod.accumulate.width, signed: mod.isSignedResult()); + expect(result, equals(golden)); } -// Random testing of a mutiplier or multiplier/accumulate unit -void testMultiplyAccumulateRandom(int width, int iterations, - MultiplyAccumulate Function(Logic a, Logic b, Logic c) fn, - {bool signedTest = false}) { +void testMultiplyAccumulateSingle(int width, BigInt ibA, BigInt ibB, BigInt ibC, + MultiplyAccumulate Function(Logic a, Logic b, Logic c) fn) { final a = Logic(name: 'a', width: width); final b = Logic(name: 'b', width: width); final c = Logic(name: 'c', width: width * 2); @@ -62,31 +102,56 @@ void testMultiplyAccumulateRandom(int width, int iterations, b.put(0); c.put(0); final mod = fn(a, b, c); - test('random_${mod.name}_S${mod.signed}_W${width}_I$iterations', () async { + test('single_W${width}_${mod.name}', () async { final multiplyOnly = mod is MutiplyOnly; await mod.build(); + final bA = ibA.toCondSigned(width, signed: mod.isSignedMultiplicand()); + final bB = ibB.toCondSigned(width, signed: mod.isSignedMultiplier()); + final bC = multiplyOnly + ? BigInt.zero + : ibC.toCondSigned(width * 2, signed: mod.isSignedAddend()); + + checkMultiplyAccumulate(mod, bA, bB, bC); + }); +} + +void testMultiplyAccumulateRandom(int width, int iterations, + MultiplyAccumulate Function(Logic a, Logic b, Logic c) fn) { + final a = Logic(name: 'a', width: width); + final b = Logic(name: 'b', width: width); + final c = Logic(name: 'c', width: width * 2); + a.put(0); + b.put(0); + c.put(0); + final mod = fn(a, b, c); + test('random_W${width}_I${iterations}_${mod.name}', () { + final multiplyOnly = mod is MutiplyOnly; + final value = Random(47); for (var i = 0; i < iterations; i++) { - final bA = signedTest - ? value.nextLogicValue(width: width).toBigInt().toSigned(width) - : value.nextLogicValue(width: width).toBigInt().toUnsigned(width); - final bB = signedTest - ? value.nextLogicValue(width: width).toBigInt().toSigned(width) - : value.nextLogicValue(width: width).toBigInt().toUnsigned(width); + final bA = value + .nextLogicValue(width: width) + .toBigInt() + .toCondSigned(width, signed: mod.isSignedMultiplicand()); + final bB = value + .nextLogicValue(width: width) + .toBigInt() + .toCondSigned(width, signed: mod.isSignedMultiplier()); + final bC = multiplyOnly ? BigInt.zero - : signedTest - ? value.nextLogicValue(width: width).toBigInt().toSigned(width) - : value.nextLogicValue(width: width).toBigInt().toUnsigned(width); - checkMultiplyAccumulate(mod, bA, bB, bC, signedTest: signedTest); + : value + .nextLogicValue(width: width) + .toBigInt() + .toCondSigned(width, signed: mod.isSignedAddend()); + + checkMultiplyAccumulate(mod, bA, bB, bC); } }); } -// Exhaustive testing of a mutiplier or multiplier/accumulate unit void testMultiplyAccumulateExhaustive( - int width, MultiplyAccumulate Function(Logic a, Logic b, Logic c) fn, - {bool signedTest = false}) { + int width, MultiplyAccumulate Function(Logic a, Logic b, Logic c) fn) { final a = Logic(name: 'a', width: width); final b = Logic(name: 'b', width: width); final c = Logic(name: 'c', width: 2 * width); @@ -94,27 +159,22 @@ void testMultiplyAccumulateExhaustive( b.put(0); c.put(0); final mod = fn(a, b, c); - test('exhaustive_${mod.name}_S${mod.signed}_W$width', () async { + test('exhaustive_W${width}_${mod.name}', () async { await mod.build(); final multiplyOnly = mod is MutiplyOnly; - final cLimit = multiplyOnly ? 1 : (1 << (2 * width)); - for (var aa = 0; aa < (1 << width); ++aa) { for (var bb = 0; bb < (1 << width); ++bb) { - for (var cc = 0; cc < cLimit; ++cc) { - final bA = signedTest - ? BigInt.from(aa).toSigned(width) - : BigInt.from(aa).toUnsigned(width); - final bB = signedTest - ? BigInt.from(bb).toSigned(width) - : BigInt.from(bb).toUnsigned(width); + for (var cc = 0; cc < (multiplyOnly ? 1 : (1 << (2 * width))); ++cc) { + final bA = SignedBigInt.fromSignedInt(aa, width, + signed: mod.isSignedMultiplicand()); + final bB = SignedBigInt.fromSignedInt(bb, width, + signed: mod.isSignedMultiplier()); final bC = multiplyOnly ? BigInt.zero - : signedTest - ? BigInt.from(cc).toSigned(2 * width) - : BigInt.from(cc).toUnsigned(2 * width); - checkMultiplyAccumulate(mod, bA, bB, bC, signedTest: signedTest); + : SignedBigInt.fromSignedInt(bb, width * 2, + signed: mod.isSignedAddend()); + checkMultiplyAccumulate(mod, bA, bB, bC); } } } @@ -125,69 +185,117 @@ typedef MultiplyAccumulateCallback = MultiplyAccumulate Function( Logic a, Logic b, Logic c); typedef MultiplierCallback = Multiplier Function(Logic a, Logic b, - {Logic? selectSigned}); + {Logic? selectSignedMultiplicand, Logic? selectSignedMultiplier}); void main() { tearDown(() async { await Simulator.reset(); }); - MultiplierCallback curryCompressionTreeMultiplier( - int radix, - ParallelPrefix Function(List, Logic Function(Logic, Logic)) - ppTree, - {PartialProductGenerator Function(Logic, Logic, RadixEncoder, - {required bool signed, Logic? selectSigned}) - ppGen = PartialProductGeneratorCompactRectSignExtension.new, - bool signed = false, - Logic? selectSigned}) => - (a, b, {selectSigned}) => CompressionTreeMultiplier(a, b, radix, - selectSigned: selectSigned, - ppTree: ppTree, - ppGen: ppGen, - signed: signed, - name: 'Compression Tree Multiplier: ${ppTree.call([ - Logic() - ], (a, b, {selectSigned}) => Logic()).name}' - ' Sel=${selectSigned != null}R${radix}_E' - '${ppGen.call(a, b, RadixEncoder(radix), signed: signed).name}'); + MultiplierCallback curryCompressionTreeMultiplier(int radix, + ParallelPrefix Function(List, Logic Function(Logic, Logic)) ppTree, + {PPGFunction ppGen = PartialProductGeneratorCompactRectSignExtension.new, + bool signedMultiplicand = false, + bool signedMultiplier = false, + Logic? selectSignedMultiplicand, + Logic? selectSignedMultiplier}) { + String genName(Logic a, Logic b) => ppGen( + a, + b, + RadixEncoder(radix), + signedMultiplicand: signedMultiplicand, + signedMultiplier: signedMultiplier, + selectSignedMultiplicand: + selectSignedMultiplicand != null ? Logic() : null, + selectSignedMultiplier: + selectSignedMultiplier != null ? Logic() : null, + ).name; + final signage = ' SelD=${(selectSignedMultiplicand != null) ? 1 : 0}' + ' SelM=${(selectSignedMultiplier != null) ? 1 : 0}' + ' SD=${signedMultiplicand ? 1 : 0}' + ' SM=${signedMultiplier ? 1 : 0}'; + return (a, b, {selectSignedMultiplicand, selectSignedMultiplier}) => + CompressionTreeMultiplier(a, b, radix, + ppTree: ppTree, + ppGen: ppGen, + signedMultiplicand: signedMultiplicand, + signedMultiplier: signedMultiplier, + selectSignedMultiplicand: selectSignedMultiplicand, + selectSignedMultiplier: selectSignedMultiplier, + name: 'Compression Tree Multiplier: ' + '${ppTree([Logic()], (a, b) => Logic()).name}' + '$signage R${radix}_E${genName(a, b)}'); + } MultiplyAccumulateCallback curryMultiplierAsMultiplyAccumulate( int radix, ParallelPrefix Function(List, Logic Function(Logic, Logic)) ppTree, - {PartialProductGenerator Function(Logic, Logic, RadixEncoder, - {required bool signed, Logic? selectSigned}) - ppGen = PartialProductGeneratorCompactRectSignExtension.new, - bool signed = false, - Logic? selectSign}) => + {PPGFunction ppGen = + PartialProductGeneratorCompactRectSignExtension.new, + bool signedMultiplicand = false, + bool signedMultiplier = false, + Logic? selectSignedMultiplicand, + Logic? selectSignedMultiplier}) => (a, b, c) => MutiplyOnly( a, b, c, - selectSigned: selectSign, - curryCompressionTreeMultiplier(radix, ppTree, - ppGen: ppGen, selectSigned: selectSign, signed: signed)); + signedMultiplicand: signedMultiplicand, + signedMultiplier: signedMultiplier, + selectSignedMultiplicand: selectSignedMultiplicand, + selectSignedMultiplier: selectSignedMultiplier, + curryCompressionTreeMultiplier( + radix, + ppTree, + ppGen: ppGen, + signedMultiplicand: signedMultiplicand, + signedMultiplier: signedMultiplier, + selectSignedMultiplicand: selectSignedMultiplicand, + selectSignedMultiplier: selectSignedMultiplier, + )); MultiplyAccumulateCallback curryMultiplyAccumulate( int radix, ParallelPrefix Function(List, Logic Function(Logic, Logic)) ppTree, { - PartialProductGenerator Function(Logic, Logic, RadixEncoder, - {required bool signed, Logic? selectSigned}) - ppGen = PartialProductGeneratorCompactRectSignExtension.new, - bool signed = false, - Logic? selectSign, - }) => - (a, b, c) => CompressionTreeMultiplyAccumulate(a, b, c, radix, - selectSigned: selectSign, - ppTree: ppTree, - ppGen: ppGen, - signed: signed, - name: 'Compression Tree MAC: ${ppTree.call([ - Logic() - ], (a, b) => Logic()).name}' - ' Sel=${selectSign != null}R${radix}_E' - '${ppGen.call(a, b, RadixEncoder(radix), signed: signed).name}'); + PPGFunction ppGen = PartialProductGeneratorCompactRectSignExtension.new, + bool signedMultiplicand = false, + bool signedMultiplier = false, + bool signedAddend = false, + Logic? selectSignedMultiplicand, + Logic? selectSignedMultiplier, + Logic? selectSignedAddend, + }) { + String genName(Logic a, Logic b) => ppGen( + a, + b, + RadixEncoder(radix), + signedMultiplicand: signedMultiplicand, + signedMultiplier: signedMultiplier, + selectSignedMultiplicand: + selectSignedMultiplicand != null ? Logic() : null, + selectSignedMultiplier: + selectSignedMultiplier != null ? Logic() : null, + ).name; + final signage = ' SelD=${(selectSignedMultiplicand != null) ? 1 : 0}' + ' SelM=${(selectSignedMultiplier != null) ? 1 : 0}' + ' SD=${signedMultiplicand ? 1 : 0}' + ' SM=${signedMultiplier ? 1 : 0}'; + + return (a, b, c) => CompressionTreeMultiplyAccumulate(a, b, c, radix, + ppTree: ppTree, + ppGen: ppGen, + signedMultiplicand: signedMultiplicand, + signedMultiplier: signedMultiplier, + signedAddend: signedAddend, + selectSignedMultiplicand: selectSignedMultiplicand, + selectSignedMultiplier: selectSignedMultiplier, + selectSignedAddend: selectSignedAddend, + name: 'Compression Tree MAC: ${ppTree.call([ + Logic() + ], (a, b) => Logic()).name}' + ' $signage R$radix E${genName(a, b)}'); + } group('Compression Tree Multiplier: curried random radix/width', () { for (final signedTest in [false, true]) { @@ -198,17 +306,18 @@ void main() { } else { signedSelect = null; } - for (final radix in [2, 16]) { - for (final width in [5, 6]) { + for (final radix in [2, 4]) { + for (final width in [3, 4]) { for (final ppTree in [KoggeStone.new]) { testMultiplyAccumulateRandom( width, 10, curryMultiplierAsMultiplyAccumulate(radix, ppTree, ppGen: PartialProductGeneratorStopBitsSignExtension.new, - signed: !signedOperands && signedTest, - selectSign: signedSelect), - signedTest: signedTest); + signedMultiplicand: !signedOperands && signedTest, + signedMultiplier: !signedOperands && signedTest, + selectSignedMultiplicand: signedSelect, + selectSignedMultiplier: signedSelect)); } } } @@ -226,7 +335,9 @@ void main() { final bA = BigInt.from(-10).toSigned(width); final bB = BigInt.from(-10).toSigned(width); final mod = CompressionTreeMultiplier(a, b, 4, - clk: clk, selectSigned: signedSelect); + clk: clk, + selectSignedMultiplicand: signedSelect, + selectSignedMultiplier: signedSelect); unawaited(Simulator.run()); a.put(bA); b.put(bB); @@ -241,7 +352,7 @@ void main() { await Simulator.endSimulation(); }); - group('Compression Tree Multiplier: curried exhaustive sign/select/extension', + group('Compression Tree Multiplier: curried random sign/select/extension', () { for (final signedTest in [false, true]) { for (final signedOperands in [false, true]) { @@ -251,7 +362,7 @@ void main() { } else { signedSelect = null; } - for (final radix in [4]) { + for (final radix in [2, 4]) { for (final ppTree in [KoggeStone.new]) { for (final ppGen in [ PartialProductGeneratorCompactSignExtension.new, @@ -260,13 +371,15 @@ void main() { PartialProductGeneratorBruteSignExtension.new ]) { for (final width in [1 + log2Ceil(radix)]) { - testMultiplyAccumulateExhaustive( + testMultiplyAccumulateRandom( width, + 10, curryMultiplierAsMultiplyAccumulate(radix, ppTree, ppGen: ppGen, - signed: !signedOperands && signedTest, - selectSign: signedSelect), - signedTest: signedTest); + signedMultiplicand: !signedOperands && signedTest, + signedMultiplier: !signedOperands && signedTest, + selectSignedMultiplicand: signedSelect, + selectSignedMultiplier: signedSelect)); } } } @@ -275,27 +388,7 @@ void main() { } }); - test('Trim Curried Test of Compression Tree Multiplier', () async { - final Logic? signedSelect; - signedSelect = Logic()..put(1); - const width = 5; - final a = Logic(name: 'a', width: width); - final b = Logic(name: 'b', width: width); - final c = Logic(name: 'c', width: width * 2 + 1); - a.put(0); - b.put(0); - c.put(0); - final bA = BigInt.from(-10).toSigned(width); - final bB = BigInt.from(-10).toSigned(width); - a.put(bA); - b.put(bB); - final mod = curryMultiplierAsMultiplyAccumulate(4, KoggeStone.new, - ppGen: PartialProductGeneratorCompactSignExtension.new, - selectSign: signedSelect)(a, b, c); - checkMultiplyAccumulate(mod, bA, bB, BigInt.zero, signedTest: true); - }); - - group('Compression Tree Multiplier Accumulate: random radix/width', () { + group('Compression Tree MAC: random + signed/radix/width', () { for (final signedTest in [false, true]) { for (final signedOperands in [false, true]) { final Logic? signedSelect; @@ -304,51 +397,22 @@ void main() { } else { signedSelect = null; } - for (final radix in [4, 16]) { - for (final width in [5, 6]) { + for (final radix in [2, 4]) { + for (final width in [3, 4]) { for (final ppTree in [KoggeStone.new]) { testMultiplyAccumulateRandom( width, 10, - curryMultiplyAccumulate(radix, ppTree, - signed: !signedOperands && signedTest, - selectSign: signedSelect), - signedTest: signedTest); - } - } - } - } - } - }); - - group( - 'Compression Tree Multiplier Accumulate: ' - 'curried exhaustive sign/select/extension', () { - for (final signedTest in [false, true]) { - for (final signedOperands in [false, true]) { - final Logic? signedSelect; - if (signedOperands) { - signedSelect = Logic()..put(signedTest ? 1 : 0); - } else { - signedSelect = null; - } - for (final radix in [4]) { - for (final ppTree in [KoggeStone.new]) { - for (final ppGen in [ - PartialProductGeneratorCompactSignExtension.new, - PartialProductGeneratorCompactRectSignExtension.new, - PartialProductGeneratorStopBitsSignExtension.new, - PartialProductGeneratorBruteSignExtension.new - ]) { - for (final width in [1 + log2Ceil(radix)]) { - testMultiplyAccumulateExhaustive( - width, - curryMultiplyAccumulate(radix, ppTree, - ppGen: ppGen, - signed: !signedOperands && signedTest, - selectSign: signedSelect), - signedTest: signedTest); - } + curryMultiplyAccumulate( + radix, + ppTree, + signedMultiplicand: !signedOperands && signedTest, + signedMultiplier: !signedOperands && signedTest, + signedAddend: !signedOperands && signedTest, + selectSignedMultiplicand: signedSelect, + selectSignedMultiplier: signedSelect, + selectSignedAddend: signedSelect, + )); } } } @@ -362,17 +426,22 @@ void main() { const width = 5; final a = Logic(name: 'a', width: width); final b = Logic(name: 'b', width: width); - final c = Logic(name: 'c', width: width * 2 + 1); - final bA = BigInt.from(-10).toSigned(width); - final bB = BigInt.from(-10).toSigned(width); - final bC = BigInt.from(0).toSigned(width); + final c = Logic(name: 'c', width: width * 2); + final bA = BigInt.from(0).toSigned(width); + final bB = BigInt.from(0).toSigned(width); + final bC = BigInt.from(-1).toSigned(width * 2); a.put(bA); b.put(bB); c.put(bC); - final mod = curryMultiplyAccumulate(4, KoggeStone.new, - ppGen: PartialProductGeneratorCompactSignExtension.new, - selectSign: signedSelect)(a, b, c); - checkMultiplyAccumulate(mod, bA, bB, bC, signedTest: true); + final mod = curryMultiplyAccumulate( + 4, + KoggeStone.new, + selectSignedMultiplicand: signedSelect, + selectSignedMultiplier: signedSelect, + selectSignedAddend: signedSelect, + )(a, b, c); + + checkMultiplyAccumulate(mod, bA, bB, bC); }); test('Compression Tree MAC: pipelined test', () async { @@ -382,13 +451,19 @@ void main() { const width = 5; final a = Logic(name: 'a', width: width); final b = Logic(name: 'b', width: width); - final c = Logic(name: 'c', width: width * 2 + 1); - final bA = BigInt.from(-10).toSigned(width); - final bB = BigInt.from(-10).toSigned(width); - final bC = BigInt.from(100).toSigned(width); + final c = Logic(name: 'c', width: width * 2); + final bA = BigInt.from(0).toSigned(width); + final bB = BigInt.from(0).toSigned(width); + final bC = BigInt.from(-512).toSigned(width * 2); + a.put(0); + b.put(0); + c.put(0); final mod = CompressionTreeMultiplyAccumulate(a, b, c, 4, - clk: clk, selectSigned: signedSelect); + clk: clk, + selectSignedMultiplicand: signedSelect, + selectSignedMultiplier: signedSelect, + selectSignedAddend: signedSelect); unawaited(Simulator.run()); a.put(bA); b.put(bB); @@ -414,12 +489,8 @@ void main() { const bv = 13; for (final signed in [true, false]) { for (final useSignedLogic in [true, false]) { - final bA = signed - ? BigInt.from(av).toSigned(width) - : BigInt.from(av).toUnsigned(width); - final bB = signed - ? BigInt.from(bv).toSigned(width) - : BigInt.from(bv).toUnsigned(width); + final bA = SignedBigInt.fromSignedInt(av, width, signed: signed); + final bB = SignedBigInt.fromSignedInt(bv, width, signed: signed); final Logic? signedSelect; @@ -434,11 +505,13 @@ void main() { b.put(bB); final mod = CompressionTreeMultiplier(a, b, 4, - signed: !useSignedLogic && signed, selectSigned: signedSelect); + signedMultiplier: !useSignedLogic && signed, + selectSignedMultiplicand: signedSelect, + selectSignedMultiplier: signedSelect); await mod.build(); mod.generateSynth(); final golden = bA * bB; - final result = mod.signed + final result = mod.isSignedResult() ? mod.product.value.toBigInt().toSigned(mod.product.width) : mod.product.value.toBigInt().toUnsigned(mod.product.width); expect(result, equals(golden)); @@ -448,7 +521,6 @@ void main() { test('trivial instantiated multiplier', () async { const dataWidth = 5; - final av = BigInt.from(-16).toSigned(dataWidth); final bv = BigInt.from(-6).toSigned(dataWidth); @@ -462,29 +534,24 @@ void main() { multB.put(bv); final mod = curryMultiplierAsMultiplyAccumulate(4, KoggeStone.new, - selectSign: signedOperands)(multA, multB, Const(0)); + selectSignedMultiplicand: signedOperands, + selectSignedMultiplier: signedOperands)(multA, multB, Const(0)); - checkMultiplyAccumulate(mod, av, bv, BigInt.zero, signedTest: true); + checkMultiplyAccumulate(mod, av, bv, BigInt.zero); }); test('single mac', () async { const width = 8; final a = Logic(name: 'a', width: width); final b = Logic(name: 'b', width: width); - final c = Logic(name: 'c', width: 2 * width + 1); - const av = 0; - const bv = 0; - const cv = -512; - for (final signed in [true, false]) { - final bA = signed - ? BigInt.from(av).toSigned(width) - : BigInt.from(av).toUnsigned(width); - final bB = signed - ? BigInt.from(bv).toSigned(width) - : BigInt.from(bv).toUnsigned(width); - final bC = signed - ? BigInt.from(cv).toSigned(2 * width + 1) - : BigInt.from(cv).toUnsigned(width * 2 + 1); + final c = Logic(name: 'c', width: 2 * width); + const av = 10; + const bv = 6; + const cv = 0; + for (final signed in [false, true]) { + final bA = SignedBigInt.fromSignedInt(av, width, signed: signed); + final bB = SignedBigInt.fromSignedInt(bv, width, signed: signed); + final bC = SignedBigInt.fromSignedInt(cv, width * 2, signed: signed); final signedOperands = Logic(name: 'signedOperands'); // ignore: cascade_invocations @@ -496,39 +563,39 @@ void main() { c.put(bC); final mod = CompressionTreeMultiplyAccumulate(a, b, c, 4, - selectSigned: signedOperands); - checkMultiplyAccumulate(mod, bA, bB, bC, signedTest: signed); + selectSignedMultiplicand: signedOperands, + selectSignedMultiplier: signedOperands, + selectSignedAddend: signedOperands); + checkMultiplyAccumulate(mod, bA, bB, bC); } }); test('single rectangular mac', () async { - const widthA = 6; - const widthB = 9; + const widthA = 8; + const widthB = 8; + const widthC = widthA + widthB; final a = Logic(name: 'a', width: widthA); final b = Logic(name: 'b', width: widthB); - final c = Logic(name: 'c', width: widthA + widthB + 1); + final c = Logic(name: 'c', width: widthC); - const av = 0; + const av = 10; const bv = 0; - const cv = -512; + const cv = 0; for (final signed in [true, false]) { - final bA = signed - ? BigInt.from(av).toSigned(widthA) - : BigInt.from(av).toUnsigned(widthA); - final bB = signed - ? BigInt.from(bv).toSigned(widthB) - : BigInt.from(bv).toUnsigned(widthB); - final bC = signed - ? BigInt.from(cv).toSigned(widthA + widthB) - : BigInt.from(cv).toUnsigned(widthA + widthB); + final bA = SignedBigInt.fromSignedInt(av, widthA, signed: signed); + final bB = SignedBigInt.fromSignedInt(bv, widthB, signed: signed); + final bC = SignedBigInt.fromSignedInt(cv, widthC, signed: signed); // Set these so that printing inside module build will have Logic values a.put(bA); b.put(bB); c.put(bC); - final mod = CompressionTreeMultiplyAccumulate(a, b, c, 4, signed: signed); - checkMultiplyAccumulate(mod, bA, bB, bC, signedTest: signed); + final mod = CompressionTreeMultiplyAccumulate(a, b, c, 4, + signedMultiplicand: signed, + signedMultiplier: signed, + signedAddend: signed); + checkMultiplyAccumulate(mod, bA, bB, bC); } }); test('trivial compression tree multiply-accumulate test', () async { @@ -537,18 +604,48 @@ void main() { const radix = 8; final a = Logic(name: 'a', width: widthA); final b = Logic(name: 'b', width: widthB); - final c = Logic(name: 'c', width: widthA + widthB + 1); + final c = Logic(name: 'c', width: widthA + widthB); a.put(15); b.put(3); c.put(5); - final multiplier = - CompressionTreeMultiplyAccumulate(a, b, c, radix, signed: true); + final multiplier = CompressionTreeMultiplyAccumulate(a, b, c, radix, + signedMultiplier: true); final accumulate = multiplier.accumulate; expect(accumulate.value.toBigInt(), equals(BigInt.from(15 * 3 + 5))); }); + test('trivial compression MAC signed', () async { + const widthA = 6; + const widthB = 6; + const widthC = widthA + widthB; + const radix = 4; + final a = Logic(name: 'a', width: widthA); + final b = Logic(name: 'b', width: widthB); + final c = Logic(name: 'c', width: widthC); + + const av = 10; + const bv = 6; + const cv = 0; + for (final signed in [false, true]) { + final bA = SignedBigInt.fromSignedInt(av, widthA, signed: signed); + final bB = SignedBigInt.fromSignedInt(bv, widthB, signed: signed); + final bC = SignedBigInt.fromSignedInt(cv, widthC, signed: signed); + + final golden = bA * bB + bC; + + a.put(bA); + b.put(bB); + c.put(bC); + + final multiplier = CompressionTreeMultiplyAccumulate(a, b, c, radix, + signedMultiplicand: signed, signedMultiplier: signed); + final accumulate = multiplier.accumulate; + expect(accumulate.value.toBigInt(), equals(golden)); + } + }); + test('setting PPG', () async { const width = 8; final a = Logic(name: 'a', width: width); @@ -561,7 +658,7 @@ void main() { final ppG0 = PartialProductGeneratorCompactRectSignExtension( a, b, RadixEncoder(4), - signed: true); + signedMultiplicand: true, signedMultiplier: true); final bit_0_5 = ppG0.getAbsolute(0, 5); expect(bit_0_5.value, equals(LogicValue.one)); diff --git a/test/configurator_test.dart b/test/configurator_test.dart index 3e256195..0f4fee76 100644 --- a/test/configurator_test.dart +++ b/test/configurator_test.dart @@ -136,8 +136,8 @@ void main() { test('should return both Int knobs to be configured', () { final multiplier = CarrySaveMultiplierConfigurator(); expect(multiplier.knobs.values.whereType().length, 1); - expect(multiplier.knobs.values.whereType>().length, - 1); + expect( + multiplier.knobs.values.whereType>().length, 1); }); test('should return rtl code when invoke generate() with default value',