From 9a2cf5b86f136dfcf5d1907439cf2a6a3cc95694 Mon Sep 17 00:00:00 2001 From: "Desmond A. Kirkpatrick" Date: Wed, 9 Oct 2024 17:39:23 -0700 Subject: [PATCH 1/6] add utils to exports --- lib/src/arithmetic/arithmetic.dart | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/src/arithmetic/arithmetic.dart b/lib/src/arithmetic/arithmetic.dart index 549d843ec..3b458bb1a 100644 --- a/lib/src/arithmetic/arithmetic.dart +++ b/lib/src/arithmetic/arithmetic.dart @@ -2,6 +2,7 @@ // SPDX-License-Identifier: BSD-3-Clause export 'adder.dart'; +export 'arithmetic_utils.dart'; export 'carry_save_mutiplier.dart'; export 'divider.dart'; export 'floating_point/floating_point.dart'; From 13d4de4c1e8096304e34dd235d4971bb4c904126 Mon Sep 17 00:00:00 2001 From: "Desmond A. Kirkpatrick" Date: Tue, 29 Oct 2024 18:19:27 -0700 Subject: [PATCH 2/6] compressor with flopped output --- lib/src/arithmetic/addend_compressor.dart | 15 +++- test/arithmetic/addend_compressor_test.dart | 80 +++++++++++++++++++ test/arithmetic/divider_test.dart | 2 +- .../parallel_prefix_operations_test.dart | 8 ++ 4 files changed, 102 insertions(+), 3 deletions(-) diff --git a/lib/src/arithmetic/addend_compressor.dart b/lib/src/arithmetic/addend_compressor.dart index 2c8d65a84..d6700c943 100644 --- a/lib/src/arithmetic/addend_compressor.dart +++ b/lib/src/arithmetic/addend_compressor.dart @@ -170,8 +170,17 @@ class ColumnCompressor { /// The partial product array to be compressed final PartialProductArray pp; + /// The clk + Logic? clk; + + /// Optional reset + Logic? reset; + + /// Optional enable + Logic? enable; + /// Initialize a ColumnCompressor for a set of partial products - ColumnCompressor(this.pp) { + ColumnCompressor(this.pp, {this.clk, this.reset, this.enable}) { columns = List.generate(pp.maxWidth(), (i) => ColumnQueue()); for (var row = 0; row < pp.rows; row++) { @@ -197,7 +206,9 @@ class ColumnCompressor { final colList = columns[col].toList(); if (row < colList.length) { final value = colList[row].logic; - rowBits.add(value); + + rowBits.add( + clk != null ? flop(clk!, value, reset: reset, en: enable) : value); } } rowBits.addAll(List.filled(pp.rowShift[row], Const(0))); diff --git a/test/arithmetic/addend_compressor_test.dart b/test/arithmetic/addend_compressor_test.dart index e101ada46..cb8d574d7 100644 --- a/test/arithmetic/addend_compressor_test.dart +++ b/test/arithmetic/addend_compressor_test.dart @@ -7,6 +7,7 @@ // 2024 June 04 // Author: Desmond Kirkpatrick +import 'dart:async'; import 'dart:io'; import 'dart:math'; import 'package:rohd/rohd.dart'; @@ -16,6 +17,38 @@ import 'package:rohd_hcl/src/arithmetic/evaluate_partial_product.dart'; import 'package:rohd_hcl/src/arithmetic/partial_product_sign_extend.dart'; import 'package:test/test.dart'; +/// A simple module to test partial product generation and compression +class CompressorTestMod extends Module { + late final PartialProductGenerator pp; + + late final ColumnCompressor compressor; + + Logic get r0 => output('r0'); + + Logic get r1 => output('r1'); + + CompressorTestMod(Logic ia, Logic ib, RadixEncoder encoder, Logic? iclk, + {bool signed = true}) + : super(name: 'compressor_test_mod') { + final a = addInput('a', ia, width: ia.width); + final b = addInput('b', ib, width: ib.width); + Logic? clk; + if (iclk != null) { + clk = addInput('clk', iclk); + } + + pp = PartialProductGeneratorCompactRectSignExtension(a, b, encoder, + signed: signed); + compressor = ColumnCompressor(pp, clk: clk); + compressor.compress(); + final r0 = addOutput('r0', width: compressor.columns.length); + final r1 = addOutput('r1', width: compressor.columns.length); + + r0 <= compressor.extractRow(0); + r1 <= compressor.extractRow(1); + } +} + void testCompressionExhaustive(PartialProductGenerator pp) { final widthX = pp.selector.multiplicand.width; final widthY = pp.encoder.multiplier.width; @@ -76,6 +109,9 @@ void testCompressionExhaustive(PartialProductGenerator pp) { } void main() { + tearDown(() async { + await Simulator.reset(); + }); test('exhaustive compression evaluate: square radix-4, just CompactRect', () async { stdout.write('\n'); @@ -163,6 +199,50 @@ void main() { } }); + test('single compressor evaluate flopped', () async { + final clk = SimpleClockGenerator(10).clk; + const widthX = 6; + const widthY = 6; + final a = Logic(name: 'a', width: widthX); + final b = Logic(name: 'b', width: widthY); + + var av = 3; + const bv = 6; + for (final signed in [true]) { + var 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 compressorTestMod = CompressorTestMod(a, b, encoder, clk); + await compressorTestMod.build(); + + unawaited(Simulator.run()); + + await clk.nextNegedge; + expect(compressorTestMod.compressor.evaluate().$1, + equals(BigInt.from(av * bv))); + av = 4; + bA = signed + ? BigInt.from(av).toSigned(widthX) + : BigInt.from(bv).toUnsigned(widthX); + a.put(bA); + await clk.nextNegedge; + expect(compressorTestMod.compressor.evaluate().$1, + equals(BigInt.from(av * bv))); + + await Simulator.endSimulation(); + } + }); + test('example multiplier', () async { const widthX = 10; const widthY = 10; diff --git a/test/arithmetic/divider_test.dart b/test/arithmetic/divider_test.dart index 6a0154616..4a313c5ab 100644 --- a/test/arithmetic/divider_test.dart +++ b/test/arithmetic/divider_test.dart @@ -481,7 +481,7 @@ void main() { await tb.divider.build(); // Attach a waveform dumper to the DUT - WaveDumper(tb.divider); + // WaveDumper(tb.divider); // Set a maximum simulation time so it doesn't run forever Simulator.setMaxSimTime(100000); diff --git a/test/arithmetic/parallel_prefix_operations_test.dart b/test/arithmetic/parallel_prefix_operations_test.dart index e965960c7..b42365f90 100644 --- a/test/arithmetic/parallel_prefix_operations_test.dart +++ b/test/arithmetic/parallel_prefix_operations_test.dart @@ -195,6 +195,14 @@ void main() { } } }); + test('simple priority encoder test', () { + final val = Logic(width: 5); + // ignore: cascade_invocations + val.put(3); + expect(ParallelPrefixPriorityEncoder(val).out.value.toInt(), equals(0)); + expect(ParallelPrefixPriorityEncoder(val.reversed).out.value.toInt(), + equals(3)); + }); // Note: all ParallelPrefixAdders are tested in adder_test.dart } From 7d3a08a14fdb15572b9c1087b1f7a74355bdba4d Mon Sep 17 00:00:00 2001 From: "Desmond A. Kirkpatrick" Date: Mon, 4 Nov 2024 10:50:40 -0800 Subject: [PATCH 3/6] refactored to pass selectSigned Logic, but not yet enabled --- lib/src/arithmetic/multiplicand_selector.dart | 22 +- lib/src/arithmetic/multiplier.dart | 9 +- lib/src/arithmetic/multiplier_encoder.dart | 23 +- .../arithmetic/partial_product_generator.dart | 199 +++-------------- .../partial_product_sign_extend.dart | 209 +++++++++++++++--- test/arithmetic/addend_compressor_test.dart | 46 +++- test/arithmetic/multiplier_encoder_test.dart | 8 +- test/arithmetic/multiplier_test.dart | 30 ++- 8 files changed, 323 insertions(+), 223 deletions(-) diff --git a/lib/src/arithmetic/multiplicand_selector.dart b/lib/src/arithmetic/multiplicand_selector.dart index b49d22177..b869af8df 100644 --- a/lib/src/arithmetic/multiplicand_selector.dart +++ b/lib/src/arithmetic/multiplicand_selector.dart @@ -28,17 +28,31 @@ class MultiplicandSelector { late LogicArray multiples; /// Generate required multiples of multiplicand - MultiplicandSelector(this.radix, this.multiplicand, {required bool signed}) + MultiplicandSelector(this.radix, this.multiplicand, + {Logic? selectSigned, bool signed = false}) : shift = log2Ceil(radix) { + if (signed && (selectSigned != null)) { + throw RohdHclException('sign reconfiguration requires signed=false'); + } if (radix > 16) { throw RohdHclException('Radices beyond 16 are not yet supported'); } final width = multiplicand.width + shift; final numMultiples = radix ~/ 2; multiples = LogicArray([numMultiples], width); - final extendedMultiplicand = signed - ? multiplicand.signExtend(width) - : multiplicand.zeroExtend(width); + final Logic extendedMultiplicand; + if (selectSigned == null) { + extendedMultiplicand = signed + ? multiplicand.signExtend(width) + : multiplicand.zeroExtend(width); + } else { + final len = multiplicand.width; + final sign = multiplicand[len - 1]; + final extension = [ + for (var i = len; i < width; i++) mux(selectSigned, sign, Const(0)) + ]; + extendedMultiplicand = (multiplicand.elements + extension).swizzle(); + } for (var pos = 0; pos < numMultiples; pos++) { final ratio = pos + 1; diff --git a/lib/src/arithmetic/multiplier.dart b/lib/src/arithmetic/multiplier.dart index 1fde57422..e425ef5bf 100644 --- a/lib/src/arithmetic/multiplier.dart +++ b/lib/src/arithmetic/multiplier.dart @@ -11,6 +11,7 @@ import 'package:meta/meta.dart'; import 'package:rohd/rohd.dart'; import 'package:rohd_hcl/rohd_hcl.dart'; +import 'package:rohd_hcl/src/arithmetic/partial_product_sign_extend.dart'; /// An abstract class for all multiplier implementations. abstract class Multiplier extends Module { @@ -75,7 +76,8 @@ class CompressionTreeMultiplier extends Multiplier { /// Construct a compression tree integer multipler with /// a given radix and final adder functor CompressionTreeMultiplier(super.a, super.b, int radix, - {ParallelPrefix Function(List, Logic Function(Logic, Logic)) + {Logic? selectSigned, + ParallelPrefix Function(List, Logic Function(Logic, Logic)) ppTree = KoggeStone.new, super.signed = false}) : super( @@ -86,7 +88,7 @@ class CompressionTreeMultiplier extends Multiplier { final product = addOutput('product', width: a.width + b.width); final pp = PartialProductGeneratorCompactRectSignExtension( a, b, RadixEncoder(radix), - signed: signed); + selectSigned: selectSigned, signed: signed); final compressor = ColumnCompressor(pp)..compress(); final adder = ParallelPrefixAdder( @@ -106,6 +108,7 @@ class CompressionTreeMultiplyAccumulate extends MultiplyAccumulate { /// a given radix and final adder functor CompressionTreeMultiplyAccumulate(super.a, super.b, super.c, int radix, {required super.signed, + Logic? selectSigned, ParallelPrefix Function(List, Logic Function(Logic, Logic)) ppTree = KoggeStone.new}) : super( @@ -114,7 +117,7 @@ class CompressionTreeMultiplyAccumulate extends MultiplyAccumulate { final accumulate = addOutput('accumulate', width: a.width + b.width + 1); final pp = PartialProductGeneratorCompactRectSignExtension( a, b, RadixEncoder(radix), - signed: signed); + selectSigned: selectSigned, signed: signed); // TODO(desmonddak): This sign extension method for the additional // addend may only work with CompactRectSignExtension diff --git a/lib/src/arithmetic/multiplier_encoder.dart b/lib/src/arithmetic/multiplier_encoder.dart index 2d5b0753a..e5d036f6c 100644 --- a/lib/src/arithmetic/multiplier_encoder.dart +++ b/lib/src/arithmetic/multiplier_encoder.dart @@ -97,9 +97,12 @@ class MultiplierEncoder { /// Generate an encoding of the input multiplier MultiplierEncoder(this.multiplier, RadixEncoder radixEncoder, - {required bool signed}) + {Logic? selectSigned, bool signed = false}) : _encoder = radixEncoder, _sliceWidth = log2Ceil(radixEncoder.radix) + 1 { + if (signed && (selectSigned != null)) { + throw RohdHclException('sign reconfiguration requires signed=false'); + } // Unsigned encoding wants to overlap past the multipler if (signed) { rows = @@ -109,10 +112,20 @@ class MultiplierEncoder { rows = (((multiplier.width + 1) % (_sliceWidth - 1) == 0) ? 0 : 1) + ((multiplier.width + 1) ~/ log2Ceil(radixEncoder.radix)); } - // slices overlap by 1 and start at -1 - _extendedMultiplier = (signed - ? multiplier.signExtend(rows * (_sliceWidth - 1)) - : multiplier.zeroExtend(rows * (_sliceWidth - 1))); + // slices overlap by 1 and start at -1a + if (selectSigned == null) { + _extendedMultiplier = (signed + ? multiplier.signExtend(rows * (_sliceWidth - 1)) + : multiplier.zeroExtend(rows * (_sliceWidth - 1))); + } else { + final len = multiplier.width; + final sign = multiplier[len - 1]; + final extension = [ + for (var i = len - 1; i < (rows * (_sliceWidth - 1)); i++) + mux(selectSigned, sign, Const(0)) + ]; + _extendedMultiplier = (multiplier.elements + extension).swizzle(); + } } /// Retrieve the Booth encoding for the row diff --git a/lib/src/arithmetic/partial_product_generator.dart b/lib/src/arithmetic/partial_product_generator.dart index 719e397af..efb6fbe51 100644 --- a/lib/src/arithmetic/partial_product_generator.dart +++ b/lib/src/arithmetic/partial_product_generator.dart @@ -205,13 +205,21 @@ abstract class PartialProductGenerator extends PartialProductArray { /// Used to avoid sign extending more than once bool isSignExtended = false; + /// If not null, use this signal to select between signed and unsigned + /// operation. + late final Logic? selectSigned; + /// Construct a [PartialProductGenerator] -- the partial product matrix PartialProductGenerator( Logic multiplicand, Logic multiplier, RadixEncoder radixEncoder, - {required this.signed}) { - encoder = MultiplierEncoder(multiplier, radixEncoder, signed: signed); - selector = - MultiplicandSelector(radixEncoder.radix, multiplicand, signed: signed); + {required this.signed, this.selectSigned}) { + if (signed && (selectSigned != null)) { + throw RohdHclException('sign reconfiguration requires signed=false'); + } + encoder = MultiplierEncoder(multiplier, radixEncoder, + selectSigned: selectSigned, signed: signed); + selector = MultiplicandSelector(radixEncoder.radix, multiplicand, + selectSigned: selectSigned, signed: signed); if (multiplicand.width < selector.shift) { throw RohdHclException('multiplicand width must be greater than ' @@ -240,174 +248,37 @@ abstract class PartialProductGenerator extends PartialProductArray { rowShift.add(row * shift); } } -} - -/// A Partial Product Generator with no sign extension -class PartialProductGeneratorNoSignExtension extends PartialProductGenerator { - /// Construct a basic Partial Product Generator - PartialProductGeneratorNoSignExtension( - super.multiplicand, super.multiplier, super.radixEncoder, - {required super.signed}); - @override - void signExtend() {} -} - -/// A Partial Product Generator using Compact Rectangular Extension -class PartialProductGeneratorCompactRectSignExtension - extends PartialProductGenerator { - /// Construct a compact rect sign extending Partial Product Generator - PartialProductGeneratorCompactRectSignExtension( - super.multiplicand, super.multiplier, super.radixEncoder, - {required super.signed}); - - void _addStopSignFlip(List addend, Logic sign) { - if (signed) { - addend.last = ~addend.last; - } else { + /// Helper function for sign extension routines: + /// For signed operands, set the MSB to [sign], otherwise add this [sign] bit. + void addStopSign(List addend, SignBit sign) { + if (!signed) { addend.add(sign); + } else { + addend.last = sign; } } - void _addStopSign(List addend, Logic sign) { - if (signed) { - addend.last = sign; - } else { + /// Helper function for sign extension routines: + /// For signed operands, flip the MSB, otherwise add this [sign] bit. + void addStopSignFlip(List addend, SignBit sign) { + if (!signed) { addend.add(sign); + } else { + addend.last = SignBit(~addend.last, inverted: true); } } +} - /// Sign extend the PP array using stop bits without adding a row - /// This routine works with different widths of multiplicand/multiplier, - /// an extension of Mohanty, B.K., Choubey designed by - /// Desmond A. Kirkpatrick - @override - void signExtend() { - if (isSignExtended) { - throw RohdHclException('Partial Product array already sign-extended'); - } - isSignExtended = true; - - final lastRow = rows - 1; - final firstAddend = partialProducts[0]; - final lastAddend = partialProducts[lastRow]; - - final firstRowQStart = selector.width - (signed ? 1 : 0); - final lastRowSignPos = shift * lastRow; - - final align = firstRowQStart - lastRowSignPos; - - final signs = [for (var r = 0; r < rows; r++) encoder.getEncoding(r).sign]; - - // Compute propgation info for folding sign bits into main rows - final propagate = - List.generate(rows, (i) => List.filled(0, Logic(), growable: true)); - - for (var row = 0; row < rows; row++) { - propagate[row].add(SignBit(signs[row])); - for (var col = 0; col < 2 * (shift - 1); col++) { - propagate[row].add(partialProducts[row][col]); - } - // Last row has extend sign propagation to Q start - if (row == lastRow) { - var col = 2 * (shift - 1); - while (propagate[lastRow].length <= align) { - propagate[lastRow].add(SignBit(partialProducts[row][col++])); - } - } - // Now compute the propagation logic - for (var col = 1; col < propagate[row].length; col++) { - propagate[row][col] = propagate[row][col] & propagate[row][col - 1]; - } - } - - // Compute 'm', the prefix of each row to carry the sign of the next row - final m = - List.generate(rows, (i) => List.filled(0, Logic(), growable: true)); - for (var row = 0; row < rows; row++) { - for (var c = 0; c < shift - 1; c++) { - m[row].add(partialProducts[row][c] ^ propagate[row][c]); - } - m[row].addAll(List.filled(shift - 1, Logic())); - } - while (m[lastRow].length < align) { - m[lastRow].add(Logic()); - } - for (var i = shift - 1; i < m[lastRow].length; i++) { - m[lastRow][i] = - lastAddend[i] ^ (i < align ? propagate[lastRow][i] : Const(0)); - } - - final remainders = List.filled(rows, Logic()); - for (var row = 0; row < lastRow; row++) { - remainders[row] = propagate[row][shift - 1]; - } - remainders[lastRow] = propagate[lastRow][align > 0 ? align : 0]; - - // Merge 'm' into the LSBs of each addend - for (var row = 0; row < rows; row++) { - final addend = partialProducts[row]; - if (row > 0) { - final mLimit = (row == lastRow) ? align : shift - 1; - for (var i = 0; i < mLimit; i++) { - addend[i] = m[row][i]; - } - // Stop bits - _addStopSignFlip(addend, SignBit(~signs[row], inverted: true)); - addend - ..insert(0, remainders[row - 1]) - ..addAll(List.filled(shift - 1, Const(1))); - rowShift[row] -= 1; - } else { - // First row - for (var i = 0; i < shift - 1; i++) { - firstAddend[i] = m[0][i]; - } - } - } - - // Insert the lastRow sign: Either in firstRow's Q if there is a - // collision or in another row if it lands beyond the Q sign extension - - final firstSign = signed ? SignBit(firstAddend.last) : SignBit(signs[0]); - final lastSign = SignBit(remainders[lastRow]); - // Compute Sign extension MSBs for firstRow - final qLen = shift + 1; - final insertSignPos = (align > 0) ? 0 : -align; - final q = List.filled(min(qLen, insertSignPos), firstSign, growable: true); - if (insertSignPos < qLen) { - // At sign insertion position - q.add(SignBit(firstSign ^ lastSign)); - if (insertSignPos == qLen - 1) { - q[insertSignPos] = SignBit(~q[insertSignPos], inverted: true); - q.add(SignBit(~(firstSign | q[insertSignPos]), inverted: true)); - } else { - q - ..addAll(List.filled( - qLen - insertSignPos - 2, SignBit(firstSign & ~lastSign))) - ..add(SignBit(~(firstSign & ~lastSign), inverted: true)); - } - } +/// A Partial Product Generator with no sign extension +class PartialProductGeneratorNoSignExtension extends PartialProductGenerator { + /// Construct a basic Partial Product Generator + PartialProductGeneratorNoSignExtension( + super.multiplicand, super.multiplier, super.radixEncoder, + {required super.signed, + // ignore: avoid_unused_constructor_parameters + Logic? selectSigned}); - if (-align >= q.length) { - q.last = SignBit(~firstSign, inverted: true); - } - _addStopSign(firstAddend, q[0]); - firstAddend.addAll(q.getRange(1, q.length)); - - if (-align >= q.length) { - final finalCarryRelPos = - lastRowSignPos - selector.width - shift + (signed ? 1 : 0); - final finalCarryRow = (finalCarryRelPos / shift).floor(); - final curRowLength = - partialProducts[finalCarryRow].length + rowShift[finalCarryRow]; - - partialProducts[finalCarryRow] - ..addAll(List.filled(lastRowSignPos - curRowLength, Const(0))) - ..add(remainders[lastRow]); - } - if (shift == 1) { - lastAddend.add(Const(1)); - } - } + @override + void signExtend() {} } diff --git a/lib/src/arithmetic/partial_product_sign_extend.dart b/lib/src/arithmetic/partial_product_sign_extend.dart index 6d4612db8..ecbfb4db1 100644 --- a/lib/src/arithmetic/partial_product_sign_extend.dart +++ b/lib/src/arithmetic/partial_product_sign_extend.dart @@ -7,6 +7,8 @@ // 2024 May 15 // Author: Desmond Kirkpatrick +import 'dart:math'; + import 'package:rohd/rohd.dart'; import 'package:rohd_hcl/rohd_hcl.dart'; @@ -29,27 +31,28 @@ enum SignExtension { } /// Used to test different sign extension methods -typedef PPGFunction = PartialProductGenerator - Function(Logic a, Logic b, RadixEncoder radixEncoder, {bool signed}); +typedef PPGFunction = PartialProductGenerator Function( + Logic a, Logic b, RadixEncoder radixEncoder, Logic? selectSigned, + {bool signed}); /// Used to test different sign extension methods PPGFunction curryPartialProductGenerator(SignExtension signExtension) => - (a, b, encoder, {signed = false}) => switch (signExtension) { + (a, b, encoder, selectSigned, {signed = false}) => switch (signExtension) { SignExtension.none => PartialProductGeneratorNoSignExtension( a, b, encoder, - signed: signed), + selectSigned: selectSigned, signed: signed), SignExtension.brute => PartialProductGeneratorBruteSignExtension( a, b, encoder, - signed: signed), + selectSigned: selectSigned, signed: signed), SignExtension.stop => PartialProductGeneratorStopBitsSignExtension( a, b, encoder, - signed: signed), + selectSigned: selectSigned, signed: signed), SignExtension.compact => PartialProductGeneratorCompactSignExtension( a, b, encoder, - signed: signed), + selectSigned: selectSigned, signed: signed), SignExtension.compactRect => PartialProductGeneratorCompactRectSignExtension(a, b, encoder, - signed: signed), + selectSigned: selectSigned, signed: signed), }; /// These other sign extensions are for asssisting with testing and debugging. @@ -62,7 +65,9 @@ class PartialProductGeneratorBruteSignExtension /// Construct a brute-force sign extending Partial Product Generator PartialProductGeneratorBruteSignExtension( super.multiplicand, super.multiplier, super.radixEncoder, - {required super.signed}); + {required super.signed, + // ignore: avoid_unused_constructor_parameters + Logic? selectSigned}); /// Fully sign extend the PP array: useful for reference only @override @@ -96,7 +101,9 @@ class PartialProductGeneratorCompactSignExtension /// Construct a compact sign extending Partial Product Generator PartialProductGeneratorCompactSignExtension( super.multiplicand, super.multiplier, super.radixEncoder, - {required super.signed}); + {required super.signed, + // ignore: avoid_unused_constructor_parameters + Logic? selectSigned}); /// Sign extend the PP array using stop bits without adding a row. @override @@ -173,12 +180,13 @@ class PartialProductGeneratorCompactSignExtension for (var i = 0; i < mLimit; i++) { addend[i] = m[row][i]; } - // Stop bits - if (signed) { - addend.last = ~addend.last; - } else { - addend.add(~signs[row]); - } + addStopSignFlip(addend, SignBit(~signs[row], inverted: true)); + // // Stop bits + // if (signed) { + // addend.last = ~addend.last; + // } else { + // addend.add(~signs[row]); + // } addend ..insert(0, remainders[row - 1]) ..addAll(List.filled(shift - 1, Const(1))); @@ -207,15 +215,18 @@ class PartialProductGeneratorStopBitsSignExtension /// Construct a stop bits sign extending Partial Product Generator PartialProductGeneratorStopBitsSignExtension( super.multiplicand, super.multiplier, super.radixEncoder, - {required super.signed}); + {required super.signed, super.selectSigned}); - /// Sign extend the PP array using stop bits + /// Sign extend the PP array using stop bits. /// If possible, fold the final carry into another row (only when rectangular /// enough that carry bit lands outside another row). /// This technique can then be combined with a first-row extension technique /// for folding in the final carry. @override void signExtend() { + if (signed && (selectSigned != null)) { + throw RohdHclException('sign reconfiguration requires signed=false'); + } if (isSignExtended) { throw RohdHclException('Partial Product array already sign-extended'); } @@ -234,18 +245,19 @@ class PartialProductGeneratorStopBitsSignExtension final addend = partialProducts[row]; final sign = signed ? addend.last : signs[row]; if (row == 0) { - if (signed) { - addend.addAll(List.filled(shift - 1, SignBit(sign))); // signed only? - } else { + if (!signed) { addend.addAll(List.filled(shift, SignBit(sign))); + } else { + addend.addAll(List.filled(shift - 1, SignBit(sign))); // signed only? } addend.add(SignBit(~sign, inverted: true)); } else { - if (signed) { - addend.last = SignBit(~sign, inverted: true); - } else { - addend.add(SignBit(~sign, inverted: true)); - } + addStopSign(addend, SignBit(~sign, inverted: true)); + // if (signed) { + // addend.last = SignBit(~sign, inverted: true); + // } else { + // addend.add(SignBit(~sign, inverted: true)); + // } addend ..addAll(List.filled(shift - 1, Const(1))) ..insertAll(0, List.filled(shift - 1, Const(0))) @@ -260,7 +272,7 @@ class PartialProductGeneratorStopBitsSignExtension ..addAll(List.filled( finalCarryPos - (extensionRow.length + rowShift[finalCarryRow]), Const(0))) - ..add(signs[rows - 1]); + ..add(SignBit(signs[rows - 1])); } else if (signed) { // Create an extra row to hold the final carry bit partialProducts @@ -275,3 +287,146 @@ class PartialProductGeneratorStopBitsSignExtension } } } + +/// A Partial Product Generator using Compact Rectangular Extension +class PartialProductGeneratorCompactRectSignExtension + extends PartialProductGenerator { + /// Construct a compact rect sign extending Partial Product Generator + PartialProductGeneratorCompactRectSignExtension( + super.multiplicand, super.multiplier, super.radixEncoder, + {required super.signed, super.selectSigned}); + + /// Sign extend the PP array using stop bits without adding a row + /// This routine works with different widths of multiplicand/multiplier, + /// an extension of Mohanty, B.K., Choubey designed by + /// Desmond A. Kirkpatrick + @override + void signExtend() { + if (isSignExtended) { + throw RohdHclException('Partial Product array already sign-extended'); + } + isSignExtended = true; + + final lastRow = rows - 1; + final firstAddend = partialProducts[0]; + final lastAddend = partialProducts[lastRow]; + + final firstRowQStart = selector.width - (signed ? 1 : 0); + final lastRowSignPos = shift * lastRow; + + final align = firstRowQStart - lastRowSignPos; + + final signs = [for (var r = 0; r < rows; r++) encoder.getEncoding(r).sign]; + + // Compute propgation info for folding sign bits into main rows + final propagate = + List.generate(rows, (i) => List.filled(0, Logic(), growable: true)); + + for (var row = 0; row < rows; row++) { + propagate[row].add(SignBit(signs[row])); + for (var col = 0; col < 2 * (shift - 1); col++) { + propagate[row].add(partialProducts[row][col]); + } + // Last row has extend sign propagation to Q start + if (row == lastRow) { + var col = 2 * (shift - 1); + while (propagate[lastRow].length <= align) { + propagate[lastRow].add(SignBit(partialProducts[row][col++])); + } + } + // Now compute the propagation logic + for (var col = 1; col < propagate[row].length; col++) { + propagate[row][col] = propagate[row][col] & propagate[row][col - 1]; + } + } + + // Compute 'm', the prefix of each row to carry the sign of the next row + final m = + List.generate(rows, (i) => List.filled(0, Logic(), growable: true)); + for (var row = 0; row < rows; row++) { + for (var c = 0; c < shift - 1; c++) { + m[row].add(partialProducts[row][c] ^ propagate[row][c]); + } + m[row].addAll(List.filled(shift - 1, Logic())); + } + while (m[lastRow].length < align) { + m[lastRow].add(Logic()); + } + for (var i = shift - 1; i < m[lastRow].length; i++) { + m[lastRow][i] = + lastAddend[i] ^ (i < align ? propagate[lastRow][i] : Const(0)); + } + + final remainders = List.filled(rows, Logic()); + for (var row = 0; row < lastRow; row++) { + remainders[row] = propagate[row][shift - 1]; + } + remainders[lastRow] = propagate[lastRow][align > 0 ? align : 0]; + + // Merge 'm' into the LSBs of each addend + for (var row = 0; row < rows; row++) { + final addend = partialProducts[row]; + if (row > 0) { + final mLimit = (row == lastRow) ? align : shift - 1; + for (var i = 0; i < mLimit; i++) { + addend[i] = m[row][i]; + } + // Stop bits + addStopSignFlip(addend, SignBit(~signs[row], inverted: true)); + addend + ..insert(0, remainders[row - 1]) + ..addAll(List.filled(shift - 1, Const(1))); + rowShift[row] -= 1; + } else { + // First row + for (var i = 0; i < shift - 1; i++) { + firstAddend[i] = m[0][i]; + } + } + } + + // Insert the lastRow sign: Either in firstRow's Q if there is a + // collision or in another row if it lands beyond the Q sign extension + + final firstSign = signed ? SignBit(firstAddend.last) : SignBit(signs[0]); + final lastSign = SignBit(remainders[lastRow]); + // Compute Sign extension MSBs for firstRow + final qLen = shift + 1; + final insertSignPos = (align > 0) ? 0 : -align; + final q = List.filled(min(qLen, insertSignPos), firstSign, growable: true); + if (insertSignPos < qLen) { + // At sign insertion position + q.add(SignBit(firstSign ^ lastSign)); + if (insertSignPos == qLen - 1) { + q[insertSignPos] = SignBit(~q[insertSignPos], inverted: true); + q.add(SignBit(~(firstSign | q[insertSignPos]), inverted: true)); + } else { + q + ..addAll(List.filled( + qLen - insertSignPos - 2, SignBit(firstSign & ~lastSign))) + ..add(SignBit(~(firstSign & ~lastSign), inverted: true)); + } + } + + if (-align >= q.length) { + q.last = SignBit(~firstSign, inverted: true); + } + addStopSign(firstAddend, q[0]); + firstAddend.addAll(q.getRange(1, q.length)); + + if (-align >= q.length) { + final finalCarryRelPos = + lastRowSignPos - selector.width - shift + (signed ? 1 : 0); + final finalCarryRow = (finalCarryRelPos / shift).floor(); + final curRowLength = + partialProducts[finalCarryRow].length + rowShift[finalCarryRow]; + + partialProducts[finalCarryRow] + ..addAll(List.filled(lastRowSignPos - curRowLength, Const(0))) + ..add(remainders[lastRow]); + } + if (shift == 1) { + lastAddend.add(Const(1)); + } + } +} diff --git a/test/arithmetic/addend_compressor_test.dart b/test/arithmetic/addend_compressor_test.dart index e101ada46..ebb68d2c8 100644 --- a/test/arithmetic/addend_compressor_test.dart +++ b/test/arithmetic/addend_compressor_test.dart @@ -79,6 +79,7 @@ void main() { test('exhaustive compression evaluate: square radix-4, just CompactRect', () async { stdout.write('\n'); + const Null selectSign = null; for (final signed in [false, true]) { for (var radix = 4; radix < 8; radix *= 2) { @@ -92,7 +93,7 @@ void main() { } final ppg = curryPartialProductGenerator(signExtension); final pp = ppg(Logic(name: 'X', width: width), - Logic(name: 'Y', width: width), encoder, + Logic(name: 'Y', width: width), encoder, selectSign, signed: signed); testCompressionExhaustive(pp); @@ -153,7 +154,10 @@ void main() { const radix = 2; final encoder = RadixEncoder(radix); - final pp = PartialProductGeneratorCompactRectSignExtension(a, b, encoder, + // final pp = PartialProductGeneratorCompactRectSignExtension(a, b, + // encoder, + // selectSigned: selectSign, signed: signed); + final pp = PartialProductGeneratorStopBitsSignExtension(a, b, encoder, signed: signed); expect(pp.evaluate(), equals(BigInt.from(av * bv))); final compressor = ColumnCompressor(pp); @@ -171,7 +175,7 @@ void main() { const av = 37; const bv = 6; - for (final signed in [true]) { + for (final signed in [false, true]) { final bA = signed ? BigInt.from(av).toSigned(widthX) : BigInt.from(av).toUnsigned(widthX); @@ -187,9 +191,41 @@ void main() { final pp = PartialProductGeneratorCompactRectSignExtension(a, b, encoder, signed: signed); - expect(pp.evaluate(), equals(BigInt.from(av * bv))); + expect(pp.evaluate(), equals(bA * bB)); final compressor = ColumnCompressor(pp)..compress(); - expect(compressor.evaluate().$1, equals(BigInt.from(av * bv))); + expect(compressor.evaluate().$1, equals(bA * bB)); + } + }); + + test('single sign agnostic compressor evaluate', () async { + const widthX = 6; + const widthY = 6; + final a = Logic(name: 'a', width: widthX); + final b = Logic(name: 'b', width: widthY); + + const av = -3; + const bv = 6; + for (final signed in [false, true]) { + final bA = signed + ? BigInt.from(av).toSigned(widthX) + : BigInt.from(av).toUnsigned(widthX); + final bB = signed + ? BigInt.from(bv).toSigned(widthY) + : BigInt.from(bv).toUnsigned(widthY); + + // Set these so that printing inside module build will have Logic values + a.put(bA); + b.put(bB); + const radix = 2; + final encoder = RadixEncoder(radix); + + final pp = PartialProductGeneratorStopBitsSignExtension(a, b, encoder, + signed: signed); + expect(pp.evaluate(), equals(bA * bB)); + final compressor = ColumnCompressor(pp); + expect(compressor.evaluate().$1, equals(bA * bB)); + compressor.compress(); + expect(compressor.evaluate().$1, equals(bA * bB)); } }); } diff --git a/test/arithmetic/multiplier_encoder_test.dart b/test/arithmetic/multiplier_encoder_test.dart index 6aafbbe15..93787a7b7 100644 --- a/test/arithmetic/multiplier_encoder_test.dart +++ b/test/arithmetic/multiplier_encoder_test.dart @@ -126,7 +126,7 @@ void main() { } final ppg = curryPartialProductGenerator(signExtension); final pp = ppg(Logic(name: 'X', width: width), - Logic(name: 'Y', width: width), encoder, + Logic(name: 'Y', width: width), encoder, null, signed: signed); checkEvaluateExhaustive(pp); @@ -152,7 +152,7 @@ void main() { ]) { final ppg = curryPartialProductGenerator(signExtension); final pp = ppg(Logic(name: 'X', width: width), - Logic(name: 'Y', width: width + skew), encoder, + Logic(name: 'Y', width: width + skew), encoder, null, signed: signed); checkEvaluateRandom(pp, 20); } @@ -214,7 +214,7 @@ void main() { } final ppg = curryPartialProductGenerator(signExtension); final pp = ppg(Logic(name: 'X', width: width), - Logic(name: 'Y', width: width + skew), encoder, + Logic(name: 'Y', width: width + skew), encoder, null, signed: signed); checkEvaluateRandom(pp, 100); } @@ -238,7 +238,7 @@ void main() { { final ppg = curryPartialProductGenerator(signExtension); final pp = ppg(Logic(name: 'X', width: width), - Logic(name: 'Y', width: width + skew), encoder, + Logic(name: 'Y', width: width + skew), encoder, null, signed: signed); checkEvaluateExhaustive(pp); } diff --git a/test/arithmetic/multiplier_test.dart b/test/arithmetic/multiplier_test.dart index 07afce8d0..8f62cfc07 100644 --- a/test/arithmetic/multiplier_test.dart +++ b/test/arithmetic/multiplier_test.dart @@ -11,6 +11,7 @@ import 'dart:math'; import 'package:rohd/rohd.dart'; import 'package:rohd_hcl/rohd_hcl.dart'; import 'package:rohd_hcl/src/arithmetic/evaluate_compressor.dart'; +import 'package:rohd_hcl/src/arithmetic/partial_product_sign_extend.dart'; import 'package:test/test.dart'; // Inner test of a multipy accumulate unit @@ -108,26 +109,34 @@ void main() { int radix, ParallelPrefix Function(List, Logic Function(Logic, Logic)) ppTree, - {required bool signed}) => + {required bool signed, + Logic? selectSigned}) => (a, b) => CompressionTreeMultiplier(a, b, radix, - ppTree: ppTree, signed: signed); + selectSigned: selectSigned, ppTree: ppTree, signed: signed); MultiplyAccumulateCallback curryMultiplierAsMultiplyAccumulate( int radix, + Logic? selectSign, ParallelPrefix Function(List, Logic Function(Logic, Logic)) ppTree, {required bool signed}) => - (a, b, c) => MutiplyOnly(a, b, c, - curryCompressionTreeMultiplier(radix, ppTree, signed: signed)); + (a, b, c) => MutiplyOnly( + a, + b, + c, + curryCompressionTreeMultiplier(radix, ppTree, + selectSigned: selectSign, signed: signed)); MultiplyAccumulateCallback curryMultiplyAccumulate( int radix, + Logic? selectSign, ParallelPrefix Function(List, Logic Function(Logic, Logic)) ppTree, {required bool signed}) => (a, b, c) => CompressionTreeMultiplyAccumulate(a, b, c, radix, - ppTree: ppTree, signed: signed); + selectSigned: selectSign, ppTree: ppTree, signed: signed); + // TODO(desmonddak): fix the selectSign null group('Curried Test of Compression Tree Multiplier', () { for (final signed in [false, true]) { for (final radix in [2, 16]) { @@ -136,7 +145,7 @@ void main() { testMultiplyAccumulateRandom( width, 10, - curryMultiplierAsMultiplyAccumulate(radix, ppTree, + curryMultiplierAsMultiplyAccumulate(radix, null, ppTree, signed: signed)); } } @@ -150,7 +159,7 @@ void main() { for (final width in [5, 6]) { for (final ppTree in [KoggeStone.new, BrentKung.new]) { testMultiplyAccumulateRandom(width, 10, - curryMultiplyAccumulate(radix, ppTree, signed: signed)); + curryMultiplyAccumulate(radix, null, ppTree, signed: signed)); } } } @@ -162,7 +171,6 @@ void main() { final a = Logic(name: 'a', width: width); final b = Logic(name: 'b', width: width); final c = Logic(name: 'c', width: 2 * width); - const av = 0; const bv = 0; const cv = -512; @@ -294,9 +302,9 @@ void main() { const expectedEval = ''' 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 - 1 1 0 0 0 0 0 0 1 1 0 0 0 1 1 0 = 49350 (-16186) - 1 1 1 0 0 0 0 0 0 0 1 0 0 = 14344 (14344) - 0 0 1 0 0 0 0 1 1 = 536 (536) + 1 I 0 0 0 0 0 0 1 1 0 0 0 1 1 0 = 49350 (-16186) + 1 I 1 0 0 0 0 0 0 0 1 0 0 = 14344 (14344) + 0 i 1 0 0 0 0 1 1 = 536 (536) i S S 1 1 0 = 960 (960) p 1 1 1 1 1 1 1 0 1 0 1 0 0 1 1 0 = 65190 (-346)'''; expect(ts.toString(), equals(expectedEval)); From fdc3d74f635e9aa6e638c12f087d9e0f182f098c Mon Sep 17 00:00:00 2001 From: "Desmond A. Kirkpatrick" Date: Mon, 4 Nov 2024 19:03:24 -0800 Subject: [PATCH 4/6] selectSigned is working --- .../arithmetic/evaluate_partial_product.dart | 6 +- lib/src/arithmetic/multiplicand_selector.dart | 2 +- lib/src/arithmetic/multiplier_encoder.dart | 2 +- .../arithmetic/partial_product_generator.dart | 4 +- .../partial_product_sign_extend.dart | 70 ++++++++------- test/arithmetic/addend_compressor_test.dart | 85 +++++++++++++------ .../floating_point_adder_simple_test.dart | 1 - test/arithmetic/multiplier_encoder_test.dart | 58 +++++++++---- test/arithmetic/multiplier_test.dart | 2 +- 9 files changed, 148 insertions(+), 82 deletions(-) diff --git a/lib/src/arithmetic/evaluate_partial_product.dart b/lib/src/arithmetic/evaluate_partial_product.dart index 71dcc9cb4..4b8209aab 100644 --- a/lib/src/arithmetic/evaluate_partial_product.dart +++ b/lib/src/arithmetic/evaluate_partial_product.dart @@ -25,7 +25,11 @@ extension EvaluateLivePartialProduct on PartialProductGenerator { } } final sum = LogicValue.ofBigInt(accum, maxW).toBigInt(); - return signed ? sum.toSigned(maxW) : sum; + return signed + ? sum.toSigned(maxW) + : (selectSigned != null && !selectSigned!.value.isZero) + ? sum.toSigned(maxW) + : sum; } /// Print out the partial product matrix diff --git a/lib/src/arithmetic/multiplicand_selector.dart b/lib/src/arithmetic/multiplicand_selector.dart index b869af8df..8b3934db9 100644 --- a/lib/src/arithmetic/multiplicand_selector.dart +++ b/lib/src/arithmetic/multiplicand_selector.dart @@ -51,7 +51,7 @@ class MultiplicandSelector { final extension = [ for (var i = len; i < width; i++) mux(selectSigned, sign, Const(0)) ]; - extendedMultiplicand = (multiplicand.elements + extension).swizzle(); + extendedMultiplicand = (multiplicand.elements + extension).rswizzle(); } for (var pos = 0; pos < numMultiples; pos++) { diff --git a/lib/src/arithmetic/multiplier_encoder.dart b/lib/src/arithmetic/multiplier_encoder.dart index e5d036f6c..335af2167 100644 --- a/lib/src/arithmetic/multiplier_encoder.dart +++ b/lib/src/arithmetic/multiplier_encoder.dart @@ -124,7 +124,7 @@ class MultiplierEncoder { for (var i = len - 1; i < (rows * (_sliceWidth - 1)); i++) mux(selectSigned, sign, Const(0)) ]; - _extendedMultiplier = (multiplier.elements + extension).swizzle(); + _extendedMultiplier = (multiplier.elements + extension).rswizzle(); } } diff --git a/lib/src/arithmetic/partial_product_generator.dart b/lib/src/arithmetic/partial_product_generator.dart index efb6fbe51..5d9e588ed 100644 --- a/lib/src/arithmetic/partial_product_generator.dart +++ b/lib/src/arithmetic/partial_product_generator.dart @@ -200,7 +200,7 @@ abstract class PartialProductGenerator extends PartialProductArray { late final MultiplicandSelector selector; /// Operands are signed - final bool signed; + late final bool signed; /// Used to avoid sign extending more than once bool isSignExtended = false; @@ -212,7 +212,7 @@ abstract class PartialProductGenerator extends PartialProductArray { /// Construct a [PartialProductGenerator] -- the partial product matrix PartialProductGenerator( Logic multiplicand, Logic multiplier, RadixEncoder radixEncoder, - {required this.signed, this.selectSigned}) { + {this.signed = false, this.selectSigned}) { if (signed && (selectSigned != null)) { throw RohdHclException('sign reconfiguration requires signed=false'); } diff --git a/lib/src/arithmetic/partial_product_sign_extend.dart b/lib/src/arithmetic/partial_product_sign_extend.dart index ecbfb4db1..b230bd61a 100644 --- a/lib/src/arithmetic/partial_product_sign_extend.dart +++ b/lib/src/arithmetic/partial_product_sign_extend.dart @@ -32,15 +32,15 @@ enum SignExtension { /// Used to test different sign extension methods typedef PPGFunction = PartialProductGenerator Function( - Logic a, Logic b, RadixEncoder radixEncoder, Logic? selectSigned, - {bool signed}); + Logic a, Logic b, RadixEncoder radixEncoder, + {Logic? selectSigned, bool signed}); /// Used to test different sign extension methods PPGFunction curryPartialProductGenerator(SignExtension signExtension) => - (a, b, encoder, selectSigned, {signed = false}) => switch (signExtension) { + (a, b, encoder, {selectSigned, signed = false}) => switch (signExtension) { SignExtension.none => PartialProductGeneratorNoSignExtension( a, b, encoder, - selectSigned: selectSigned, signed: signed), + signed: signed), SignExtension.brute => PartialProductGeneratorBruteSignExtension( a, b, encoder, selectSigned: selectSigned, signed: signed), @@ -49,10 +49,10 @@ PPGFunction curryPartialProductGenerator(SignExtension signExtension) => selectSigned: selectSigned, signed: signed), SignExtension.compact => PartialProductGeneratorCompactSignExtension( a, b, encoder, - selectSigned: selectSigned, signed: signed), + signed: signed), SignExtension.compactRect => PartialProductGeneratorCompactRectSignExtension(a, b, encoder, - selectSigned: selectSigned, signed: signed), + signed: signed), }; /// These other sign extensions are for asssisting with testing and debugging. @@ -65,13 +65,14 @@ class PartialProductGeneratorBruteSignExtension /// Construct a brute-force sign extending Partial Product Generator PartialProductGeneratorBruteSignExtension( super.multiplicand, super.multiplier, super.radixEncoder, - {required super.signed, - // ignore: avoid_unused_constructor_parameters - Logic? selectSigned}); + {super.signed, super.selectSigned}); /// Fully sign extend the PP array: useful for reference only @override void signExtend() { + if (signed && (selectSigned != null)) { + throw RohdHclException('sign reconfiguration requires signed=false'); + } if (isSignExtended) { throw RohdHclException('Partial Product array already sign-extended'); } @@ -79,8 +80,14 @@ class PartialProductGeneratorBruteSignExtension final signs = [for (var r = 0; r < rows; r++) encoder.getEncoding(r).sign]; for (var row = 0; row < rows; row++) { final addend = partialProducts[row]; - final sign = SignBit(signed ? addend.last : signs[row]); - addend.addAll(List.filled((rows - row) * shift, sign)); + // final sign = SignBit(signed ? addend.last : signs[row]); + final Logic sign; + if (selectSigned != null) { + sign = mux(selectSigned!, addend.last, signs[row]); + } else { + sign = signed ? addend.last : signs[row]; + } + addend.addAll(List.filled((rows - row) * shift, SignBit(sign))); if (row > 0) { addend ..insertAll(0, List.filled(shift - 1, Const(0))) @@ -101,9 +108,7 @@ class PartialProductGeneratorCompactSignExtension /// Construct a compact sign extending Partial Product Generator PartialProductGeneratorCompactSignExtension( super.multiplicand, super.multiplier, super.radixEncoder, - {required super.signed, - // ignore: avoid_unused_constructor_parameters - Logic? selectSigned}); + {super.signed, super.selectSigned}); /// Sign extend the PP array using stop bits without adding a row. @override @@ -112,6 +117,9 @@ class PartialProductGeneratorCompactSignExtension // Mohanty, B.K., Choubey, A. Efficient Design for Radix-8 Booth Multiplier // and Its Application in Lifting 2-D DWT. Circuits Syst Signal Process 36, // 1129–1149 (2017). https://doi.org/10.1007/s00034-016-0349-9 + if (signed && (selectSigned != null)) { + throw RohdHclException('sign reconfiguration requires signed=false'); + } if (isSignExtended) { throw RohdHclException('Partial Product array already sign-extended'); } @@ -166,7 +174,7 @@ class PartialProductGeneratorCompactSignExtension remainders[lastRow] <= propagate[lastRow][alignRow0Sign]; // Compute Sign extension for row==0 - final firstSign = signed ? firstAddend.last : signs[0]; + final firstSign = !signed ? signs[0] : firstAddend.last; final q = [ firstSign ^ remainders[lastRow], ~(firstSign & ~remainders[lastRow]), @@ -181,12 +189,6 @@ class PartialProductGeneratorCompactSignExtension addend[i] = m[row][i]; } addStopSignFlip(addend, SignBit(~signs[row], inverted: true)); - // // Stop bits - // if (signed) { - // addend.last = ~addend.last; - // } else { - // addend.add(~signs[row]); - // } addend ..insert(0, remainders[row - 1]) ..addAll(List.filled(shift - 1, Const(1))); @@ -195,10 +197,10 @@ class PartialProductGeneratorCompactSignExtension for (var i = 0; i < shift - 1; i++) { firstAddend[i] = m[0][i]; } - if (signed) { - firstAddend.last = q[0]; - } else { + if (!signed) { firstAddend.add(q[0]); + } else { + firstAddend.last = q[0]; } firstAddend.addAll(q.getRange(1, q.length)); } @@ -215,7 +217,7 @@ class PartialProductGeneratorStopBitsSignExtension /// Construct a stop bits sign extending Partial Product Generator PartialProductGeneratorStopBitsSignExtension( super.multiplicand, super.multiplier, super.radixEncoder, - {required super.signed, super.selectSigned}); + {super.signed, super.selectSigned}); /// Sign extend the PP array using stop bits. /// If possible, fold the final carry into another row (only when rectangular @@ -241,9 +243,15 @@ class PartialProductGeneratorStopBitsSignExtension : 0; final signs = [for (var r = 0; r < rows; r++) encoder.getEncoding(r).sign]; + for (var row = 0; row < rows; row++) { final addend = partialProducts[row]; - final sign = signed ? addend.last : signs[row]; + final Logic sign; + if (selectSigned != null) { + sign = mux(selectSigned!, addend.last, signs[row]); + } else { + sign = signed ? addend.last : signs[row]; + } if (row == 0) { if (!signed) { addend.addAll(List.filled(shift, SignBit(sign))); @@ -253,11 +261,6 @@ class PartialProductGeneratorStopBitsSignExtension addend.add(SignBit(~sign, inverted: true)); } else { addStopSign(addend, SignBit(~sign, inverted: true)); - // if (signed) { - // addend.last = SignBit(~sign, inverted: true); - // } else { - // addend.add(SignBit(~sign, inverted: true)); - // } addend ..addAll(List.filled(shift - 1, Const(1))) ..insertAll(0, List.filled(shift - 1, Const(0))) @@ -273,7 +276,7 @@ class PartialProductGeneratorStopBitsSignExtension finalCarryPos - (extensionRow.length + rowShift[finalCarryRow]), Const(0))) ..add(SignBit(signs[rows - 1])); - } else if (signed) { + } else if (signed | (selectSigned != null)) { // Create an extra row to hold the final carry bit partialProducts .add(List.filled(selector.width, Const(0), growable: true)); @@ -302,6 +305,9 @@ class PartialProductGeneratorCompactRectSignExtension /// Desmond A. Kirkpatrick @override void signExtend() { + if (signed && (selectSigned != null)) { + throw RohdHclException('sign reconfiguration requires signed=false'); + } if (isSignExtended) { throw RohdHclException('Partial Product array already sign-extended'); } diff --git a/test/arithmetic/addend_compressor_test.dart b/test/arithmetic/addend_compressor_test.dart index ebb68d2c8..b84bb419c 100644 --- a/test/arithmetic/addend_compressor_test.dart +++ b/test/arithmetic/addend_compressor_test.dart @@ -22,14 +22,17 @@ void testCompressionExhaustive(PartialProductGenerator pp) { final compressor = ColumnCompressor(pp); + final signed = + (pp.selectSigned == null) ? pp.signed : !pp.selectSigned!.value.isZero; + final limitX = pow(2, widthX); final limitY = pow(2, widthY); for (var i = 0; i < limitX; i++) { for (var j = 0; j < limitY; j++) { - final X = pp.signed + final X = signed ? BigInt.from(i).toSigned(widthX) : BigInt.from(i).toUnsigned(widthX); - final Y = pp.signed + final Y = signed ? BigInt.from(j).toSigned(widthY) : BigInt.from(j).toUnsigned(widthY); final product = X * Y; @@ -79,8 +82,6 @@ void main() { test('exhaustive compression evaluate: square radix-4, just CompactRect', () async { stdout.write('\n'); - const Null selectSign = null; - for (final signed in [false, true]) { for (var radix = 4; radix < 8; radix *= 2) { final encoder = RadixEncoder(radix); @@ -92,11 +93,22 @@ void main() { continue; } final ppg = curryPartialProductGenerator(signExtension); - final pp = ppg(Logic(name: 'X', width: width), - Logic(name: 'Y', width: width), encoder, selectSign, - signed: signed); - - testCompressionExhaustive(pp); + for (final useSelect in [false, true]) { + final PartialProductGenerator pp; + if (useSelect) { + final selectSigned = Logic(); + // ignore: cascade_invocations + selectSigned.put(signed ? 1 : 0); + pp = ppg(Logic(name: 'X', width: width), + Logic(name: 'Y', width: width), encoder, + selectSigned: selectSigned); + } else { + pp = ppg(Logic(name: 'X', width: width), + Logic(name: 'Y', width: width), encoder, + signed: signed); + } + testCompressionExhaustive(pp); + } } } } @@ -140,7 +152,7 @@ void main() { const av = 3; const bv = 6; - for (final signed in [true]) { + for (final signed in [false, true]) { final bA = signed ? BigInt.from(av).toSigned(widthX) : BigInt.from(av).toUnsigned(widthX); @@ -188,9 +200,15 @@ void main() { 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); - final pp = PartialProductGeneratorCompactRectSignExtension(a, b, encoder, - signed: signed); expect(pp.evaluate(), equals(bA * bB)); final compressor = ColumnCompressor(pp)..compress(); expect(compressor.evaluate().$1, equals(bA * bB)); @@ -198,13 +216,13 @@ void main() { }); test('single sign agnostic compressor evaluate', () async { - const widthX = 6; - const widthY = 6; + const widthX = 3; + const widthY = 3; final a = Logic(name: 'a', width: widthX); final b = Logic(name: 'b', width: widthY); - const av = -3; - const bv = 6; + const av = 1; + const bv = 4; for (final signed in [false, true]) { final bA = signed ? BigInt.from(av).toSigned(widthX) @@ -213,19 +231,32 @@ void main() { ? 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; + 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 pp = PartialProductGeneratorStopBitsSignExtension(a, b, encoder, - signed: signed); - expect(pp.evaluate(), equals(bA * bB)); - final compressor = ColumnCompressor(pp); - expect(compressor.evaluate().$1, equals(bA * bB)); - compressor.compress(); - expect(compressor.evaluate().$1, equals(bA * bB)); + final selectSigned = Logic(); + // ignore: cascade_invocations + selectSigned.put(signed ? 1 : 0); + + final pp = useSelect + ? PartialProductGeneratorBruteSignExtension(a, b, encoder, + selectSigned: selectSigned) + : PartialProductGeneratorBruteSignExtension(a, b, encoder, + signed: signed); + + // print(pp.representation()); + + expect(pp.evaluate(), equals(bA * bB)); + final compressor = ColumnCompressor(pp); + expect(compressor.evaluate().$1, equals(bA * bB)); + compressor.compress(); + expect(compressor.evaluate().$1, equals(bA * bB)); + } } }); } diff --git a/test/arithmetic/floating_point/floating_point_adder_simple_test.dart b/test/arithmetic/floating_point/floating_point_adder_simple_test.dart index 67ce71ef2..c5b0f17ee 100644 --- a/test/arithmetic/floating_point/floating_point_adder_simple_test.dart +++ b/test/arithmetic/floating_point/floating_point_adder_simple_test.dart @@ -238,7 +238,6 @@ void main() { final out = fp2.floatingPointValue + fp1.floatingPointValue; final adder = FloatingPointAdderSimple(fp1, fp2); - // TODO(desmonddak): figure out how to handle -0.0, as this would fail expect(adder.sum.floatingPointValue.abs().compareTo(out), 0); }); diff --git a/test/arithmetic/multiplier_encoder_test.dart b/test/arithmetic/multiplier_encoder_test.dart index 93787a7b7..e00673b25 100644 --- a/test/arithmetic/multiplier_encoder_test.dart +++ b/test/arithmetic/multiplier_encoder_test.dart @@ -19,13 +19,15 @@ import 'package:test/test.dart'; void checkEvaluateExhaustive(PartialProductGenerator pp) { final widthX = pp.selector.multiplicand.width; final widthY = pp.encoder.multiplier.width; + final signed = + (pp.selectSigned == null) ? pp.signed : !pp.selectSigned!.value.isZero; final limitX = pow(2, widthX); final limitY = pow(2, widthY); for (var i = BigInt.zero; i < BigInt.from(limitX); i += BigInt.one) { for (var j = BigInt.zero; j < BigInt.from(limitY); j += BigInt.one) { - final X = pp.signed ? i.toSigned(widthX) : i.toUnsigned(widthX); - final Y = pp.signed ? j.toSigned(widthY) : j.toUnsigned(widthY); + final X = signed ? i.toSigned(widthX) : i.toUnsigned(widthX); + final Y = signed ? j.toSigned(widthY) : j.toUnsigned(widthY); pp.multiplicand.put(X); pp.multiplier.put(Y); final value = pp.evaluate(); @@ -38,12 +40,14 @@ void checkEvaluateExhaustive(PartialProductGenerator pp) { void checkEvaluateRandom(PartialProductGenerator pp, int nSamples) { final widthX = pp.selector.multiplicand.width; final widthY = pp.encoder.multiplier.width; + final signed = + (pp.selectSigned == null) ? pp.signed : !pp.selectSigned!.value.isZero; for (var i = 0; i < nSamples; ++i) { final rX = Random().nextLogicValue(width: widthX).toBigInt(); final rY = Random().nextLogicValue(width: widthY).toBigInt(); - final X = pp.signed ? rX.toSigned(widthX) : rX; - final Y = pp.signed ? rY.toSigned(widthY) : rY; + final X = signed ? rX.toSigned(widthX) : rX; + final Y = signed ? rY.toSigned(widthY) : rY; pp.multiplicand.put(X); pp.multiplier.put(Y); final value = pp.evaluate(); @@ -125,11 +129,22 @@ void main() { continue; } final ppg = curryPartialProductGenerator(signExtension); - final pp = ppg(Logic(name: 'X', width: width), - Logic(name: 'Y', width: width), encoder, null, - signed: signed); - - checkEvaluateExhaustive(pp); + for (final useSelect in [false, true]) { + final PartialProductGenerator pp; + if (useSelect) { + final selectSigned = Logic(); + // ignore: cascade_invocations + selectSigned.put(signed ? 1 : 0); + pp = ppg(Logic(name: 'X', width: width), + Logic(name: 'Y', width: width), encoder, + selectSigned: selectSigned); + } else { + pp = ppg(Logic(name: 'X', width: width), + Logic(name: 'Y', width: width), encoder, + signed: signed); + } + checkEvaluateExhaustive(pp); + } } } } @@ -147,14 +162,25 @@ void main() { // Commented out rectangular extension routines for speedup for (final signExtension in [ SignExtension.brute, - SignExtension.stop, SignExtension.compactRect ]) { final ppg = curryPartialProductGenerator(signExtension); - final pp = ppg(Logic(name: 'X', width: width), - Logic(name: 'Y', width: width + skew), encoder, null, - signed: signed); - checkEvaluateRandom(pp, 20); + for (final useSelect in [false, true]) { + final PartialProductGenerator pp; + if (useSelect) { + final selectSigned = Logic(); + // ignore: cascade_invocations + selectSigned.put(signed ? 1 : 0); + pp = ppg(Logic(name: 'X', width: width), + Logic(name: 'Y', width: width), encoder, + selectSigned: selectSigned); + } else { + pp = ppg(Logic(name: 'X', width: width), + Logic(name: 'Y', width: width), encoder, + signed: signed); + } + checkEvaluateRandom(pp, 20); + } } } } @@ -214,7 +240,7 @@ void main() { } final ppg = curryPartialProductGenerator(signExtension); final pp = ppg(Logic(name: 'X', width: width), - Logic(name: 'Y', width: width + skew), encoder, null, + Logic(name: 'Y', width: width + skew), encoder, signed: signed); checkEvaluateRandom(pp, 100); } @@ -238,7 +264,7 @@ void main() { { final ppg = curryPartialProductGenerator(signExtension); final pp = ppg(Logic(name: 'X', width: width), - Logic(name: 'Y', width: width + skew), encoder, null, + Logic(name: 'Y', width: width + skew), encoder, signed: signed); checkEvaluateExhaustive(pp); } diff --git a/test/arithmetic/multiplier_test.dart b/test/arithmetic/multiplier_test.dart index 8f62cfc07..b71b04139 100644 --- a/test/arithmetic/multiplier_test.dart +++ b/test/arithmetic/multiplier_test.dart @@ -136,7 +136,7 @@ void main() { (a, b, c) => CompressionTreeMultiplyAccumulate(a, b, c, radix, selectSigned: selectSign, ppTree: ppTree, signed: signed); - // TODO(desmonddak): fix the selectSign null + // TODO(desmonddak): fix the selectSign null group('Curried Test of Compression Tree Multiplier', () { for (final signed in [false, true]) { for (final radix in [2, 16]) { From d23e93459305f2bdfda38cce8b1d19535427d3ef Mon Sep 17 00:00:00 2001 From: "Desmond A. Kirkpatrick" Date: Tue, 5 Nov 2024 09:34:30 -0800 Subject: [PATCH 5/6] zero out radix-encode sign when multiple is 0 -- allows for final row deletion during sign-extend --- lib/src/arithmetic/multiplier_encoder.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/src/arithmetic/multiplier_encoder.dart b/lib/src/arithmetic/multiplier_encoder.dart index 335af2167..2b85426c3 100644 --- a/lib/src/arithmetic/multiplier_encoder.dart +++ b/lib/src/arithmetic/multiplier_encoder.dart @@ -76,8 +76,8 @@ class RadixEncoder { ].swizzle().and()); } - return RadixEncode._( - multiples.rswizzle(), multiplierSlice[multiplierSlice.width - 1]); + return RadixEncode._(multiples.rswizzle(), + multiples.rswizzle().or() & multiplierSlice[multiplierSlice.width - 1]); } } From a4a7d010809184cf3b45bc15ffdd52fd9c156490 Mon Sep 17 00:00:00 2001 From: "Desmond A. Kirkpatrick" Date: Tue, 5 Nov 2024 10:18:01 -0800 Subject: [PATCH 6/6] multiplier documentation update --- doc/components/multiplier.md | 14 ++++++++------ lib/src/arithmetic/multiplier.dart | 30 ++++++++++++++++++++++++------ 2 files changed, 32 insertions(+), 12 deletions(-) diff --git a/doc/components/multiplier.md b/doc/components/multiplier.md index 14d2b8fea..97d437d1d 100644 --- a/doc/components/multiplier.md +++ b/doc/components/multiplier.md @@ -82,10 +82,11 @@ digital signal processing. The parameters of the `CompressionTreeMultiplier` are: -- Two input terms `a` and `b` +- Two input terms `a` and `b` which can be different widths - The radix used for Booth encoding (2, 4, 8, and 16 are currently supported) - The type of `ParallelPrefix` tree used in the final `ParallelPrefixAdder` (optional) -- Whether the operands should be treated as signed (2s complement) or unsigned +- `signed` parameter: whether the operands should be treated as signed (2s complement) or unsigned +- An optional `selectSigned` control signal which overrides the `signed` configuration allowing for runtime control of signed or unsigned operation with the same hardware. Here is an example of use of the `CompressionTreeMultiplier`: @@ -116,11 +117,12 @@ tree to allow for accumulation into this third input. The parameters of the `CompressionTreeMultiplyAccumulate` are: -- Two input terms a and b -- The accumulate input term c +- Two input product terms `a` and `b` which can be different widths +- The accumulate input term `c` which must have width as sum of the two operand widths + 1. - The radix used for Booth encoding (2, 4, 8, and 16 are currently supported) -- The type of `ParallelPrefix` tree used in the final `ParallelPrefixAdder` (optional) -- Whether the operands should be treated as signed (2s complement) or unsigned +- The type of `ParallelPrefix` tree used in the final `ParallelPrefixAdder` (default Kogge-Stone). +- `signed` parameter: whether the operands should be treated as signed (2s complement) or unsigned +- An optional `selectSigned` control signal which overrides the `signed` configuration allowing for runtime control of signed or unsigned operation with the same hardware. Here is an example of using the `CompressionTreeMultiplyAccumulate`: diff --git a/lib/src/arithmetic/multiplier.dart b/lib/src/arithmetic/multiplier.dart index e425ef5bf..f79f1ff44 100644 --- a/lib/src/arithmetic/multiplier.dart +++ b/lib/src/arithmetic/multiplier.dart @@ -73,8 +73,17 @@ class CompressionTreeMultiplier extends Multiplier { @override Logic get product => output('product'); - /// Construct a compression tree integer multipler with - /// a given radix and final adder functor + /// Construct a compression tree integer multiplier with a given [radix] + /// and prefix tree functor [ppTree] for the compressor and final adder. + /// + /// [a] and [b] are the product terms and they can be different widths + /// allowing for rectangular multiplication. + /// + /// [signed] parameter configures the multiplier as a signed multiplier + /// (default is unsigned). + /// + /// Optional [selectSigned] allows for runtime configuration of signed + /// or unsigned operation, overriding the [signed] static configuration. CompressionTreeMultiplier(super.a, super.b, int radix, {Logic? selectSigned, ParallelPrefix Function(List, Logic Function(Logic, Logic)) @@ -98,14 +107,23 @@ class CompressionTreeMultiplier extends Multiplier { } } -/// An implementation of an integer multiply accumulate using compression trees +/// An implementation of an integer multiply-accumulate using compression trees class CompressionTreeMultiplyAccumulate extends MultiplyAccumulate { /// The final product of the multiplier module. @override Logic get accumulate => output('accumulate'); - /// Construct a compression tree integer multipler with - /// a given radix and final adder functor + /// Construct a compression tree integer multiply-add with a given [radix] + /// and prefix tree functor [ppTree] for the compressor and final adder. + /// + /// [a] and [b] are the product terms, [c] is the accumulate term which + /// must be the sum of the widths plus 1. + /// + /// [signed] parameter configures the multiplier as a signed multiplier + /// (default is unsigned). + /// + /// Optional [selectSigned] allows for runtime configuration of signed + /// or unsigned operation, overriding the [signed] static configuration. CompressionTreeMultiplyAccumulate(super.a, super.b, super.c, int radix, {required super.signed, Logic? selectSigned, @@ -154,7 +172,7 @@ class MutiplyOnly extends MultiplyAccumulate { Logic get accumulate => output('accumulate'); /// Construct a MultiplyAccumulate that only multiplies to enable - /// using the same tester with zero addend. + /// using the same tester with zero accumulate addend [c]. MutiplyOnly(super.a, super.b, super.c, Multiplier Function(Logic a, Logic b) multiplyGenerator, {super.signed = false}) // Will be overrwridden by multiplyGenerator