diff --git a/lib/src/arithmetic/addend_compressor.dart b/lib/src/arithmetic/addend_compressor.dart index 2c8d65a8..fa92e7c2 100644 --- a/lib/src/arithmetic/addend_compressor.dart +++ b/lib/src/arithmetic/addend_compressor.dart @@ -170,8 +170,22 @@ class ColumnCompressor { /// The partial product array to be compressed final PartialProductArray pp; + /// The clk for the pipelined version of column compression. + Logic? clk; + + /// Optional reset for configurable pipestage + Logic? reset; + + /// Optional enable for configurable pipestage. + Logic? enable; + /// Initialize a ColumnCompressor for a set of partial products - ColumnCompressor(this.pp) { + /// + /// If [clk] is not null then a set of flops are used to latch the output + /// after compression (see [extractRow]). [reset] and [enable] are optional + /// inputs to control these flops when [clk] is provided. If [clk] is null, + /// the [ColumnCompressor] is built as a combinational tree of compressors. + 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 +211,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 e101ada4..9fd7edd2 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,12 +17,42 @@ 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; - final compressor = ColumnCompressor(pp); - final limitX = pow(2, widthX); final limitY = pow(2, widthY); for (var i = 0; i < limitX; i++) { @@ -32,51 +63,79 @@ void testCompressionExhaustive(PartialProductGenerator pp) { final Y = pp.signed ? BigInt.from(j).toSigned(widthY) : BigInt.from(j).toUnsigned(widthY); - final product = X * Y; - - pp.multiplicand.put(X); - pp.multiplier.put(Y); - final value = pp.evaluate(); - expect(value, equals(product), - reason: 'Fail: $i($X) * $j($Y): $value ' - 'vs expected $product' - '\n$pp'); - final evaluateValue = compressor.evaluate(); - if (evaluateValue.$1 != product) { - stdout - ..write('Fail: $i($X)[$widthX] * $j($Y)[$widthY]: $evaluateValue ' - 'vs expected $product\n') - ..write(pp); - } - compressor.compress(); - final compressedValue = compressor.evaluate().$1; - expect(compressedValue, equals(product), - reason: 'Fail: $i($X)[$widthX] * $j($Y)[$widthY]: $compressedValue ' - 'vs expected $product' - '\n$pp'); - final compressedLogicValue = compressor.evaluate(logic: true).$1; - expect(compressedLogicValue, equals(product), - reason: - 'Fail: $i($X)[$widthX] * $j($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: $i($X)[$widthX] * $j($Y)[$widthY]: ' - '$adderValue vs expected $product' - '\n$pp'); + + 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() { - test('exhaustive compression evaluate: square radix-4, just CompactRect', + tearDown(() async { + await Simulator.reset(); + }); + test('ColumnCompressor: random evaluate: square radix-4, just CompactRect', () async { stdout.write('\n'); @@ -95,13 +154,13 @@ void main() { Logic(name: 'Y', width: width), encoder, signed: signed); - testCompressionExhaustive(pp); + testCompressionRandom(pp, 30); } } } } }); - test('single compressor evaluate multiply', () async { + test('Column Compressor: single compressor evaluate', () async { const widthX = 6; const widthY = 9; final a = Logic(name: 'a', width: widthX); @@ -131,65 +190,37 @@ void main() { expect(compressor.evaluate().$1, equals(BigInt.from(av * bv))); } }); - test('single compressor evaluate', () async { + + test('Column 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); - const av = 3; + var av = 3; const bv = 6; - for (final signed in [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 = 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('example multiplier', () async { - const widthX = 10; - const widthY = 10; - final a = Logic(name: 'a', width: widthX); - final b = Logic(name: 'b', width: widthY); - - const av = 37; - const bv = 6; - for (final signed in [true]) { - final bA = signed - ? BigInt.from(av).toSigned(widthX) - : BigInt.from(av).toUnsigned(widthX); - final bB = signed - ? BigInt.from(bv).toSigned(widthY) - : BigInt.from(bv).toUnsigned(widthY); - - // Set these so that printing inside module build will have Logic values - a.put(bA); - b.put(bB); - const radix = 8; - final encoder = RadixEncoder(radix); - - final pp = PartialProductGeneratorCompactRectSignExtension(a, b, encoder, - signed: signed); - expect(pp.evaluate(), equals(BigInt.from(av * bv))); - final compressor = ColumnCompressor(pp)..compress(); - expect(compressor.evaluate().$1, equals(BigInt.from(av * bv))); - } + const radix = 2; + final encoder = RadixEncoder(radix); + final compressorTestMod = CompressorTestMod(a, b, encoder, clk); + await compressorTestMod.build(); + unawaited(Simulator.run()); + var bA = BigInt.from(av).toSigned(widthX); + final bB = BigInt.from(bv).toSigned(widthY); + + // Set these so that printing inside module build will have Logic values + a.put(bA); + b.put(bB); + + await clk.nextNegedge; + expect(compressorTestMod.compressor.evaluate().$1, + equals(BigInt.from(av * bv))); + av = 4; + bA = BigInt.from(av).toSigned(widthX); + a.put(bA); + await clk.nextNegedge; + expect(compressorTestMod.compressor.evaluate().$1, + equals(BigInt.from(av * bv))); + await Simulator.endSimulation(); }); } diff --git a/test/arithmetic/divider_test.dart b/test/arithmetic/divider_test.dart index d920f927..3c06da24 100644 --- a/test/arithmetic/divider_test.dart +++ b/test/arithmetic/divider_test.dart @@ -522,7 +522,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 e965960c..b42365f9 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 }