diff --git a/doc/components/multiplier.md b/doc/components/multiplier.md index 75a71c6f5..988aefe7d 100644 --- a/doc/components/multiplier.md +++ b/doc/components/multiplier.md @@ -96,14 +96,15 @@ The parameters of the - 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). -- `seGen` parameter: the type of `PartialProductSignExtension` functor to use which has derived classes for different styles of sign extension. In some cases this adds an extra row to hold a sign bit (default `CompactRectSignExtension` does not). See [Sign Extension Options](./multiplier_components.md#sign-extension-option). -- Signed or unsigned operands: - - `signedMultiplicand` parameter: whether the multiplicand (first arg) should be treated as signed (twos' complement) or unsigned. - - `signedMultiplier` parameter: whether the multiplier (second arg) should be treated as signed (twos' complement) or unsigned. -- Alternatively, it supports runtime control of signage: - - An optional `selectSignedMultiplicand` control signal which allows for runtime control of signed or unsigned operation with the same hardware. `signedMultiplicand` must be false if using this control signal. - - An optional `selectSignedMultiplier` control signal which allows for runtime control of signed or unsigned operation with the same hardware. `signedMultiplier` must be false if using this control signal. -- An optional `clk`, as well as `enable` and `reset` that are used to add a pipestage in the `ColumnCompressor` to allow for pipelined operation, making the multiplier operate in 2 cycles. + +- The type of `ParallelPrefix` tree used in the final `ParallelPrefixAdder` (optional). +- `ppGen` parameter: the type of `PartialProductGenerator` to use which has derived classes for different styles of sign extension. In some cases this adds an extra row to hold a sign bit. +- `signedMultiplicand` parameter: whether the multiplicand (first arg) should be treated as signed (2s complement) or unsigned. +- `signedMultiplier` parameter: whether the multiplier (second arg) should be treated as signed (2s complement) or unsigned. +- An optional `selectSignedMultiplicand` control signal which overrides the `signedMultiplicand` parameter allowing for runtime control of signed or unsigned operation with the same hardware. `signedMultiplicand` must be false if using this control signal. +- An optional `selectSignedMultiplier` control signal which overrides the `signedMultiplier` parameter allowing for runtime control of signed or unsigned operation with the same hardware. `signedMultiplier` must be false if using this control signal. +- An optional `clk`, as well as `enable` and `reset` that are used to add a pipestage in the `ColumnCompressor` to allow for pipelined operation. +- An optional `use42Compressors` boolean enables the `ColumnCompressor` to use 4:2 compressors in addition to 3:2 (Full Adder) and 2:2 (Half Adder) compressors. Here is an example of use of the `CompressionTreeMultiplier` with one signed input: @@ -140,6 +141,7 @@ The additional parameters of the OR - An optional `selectSignedAddend` control signal allows for runtime control of signed or unsigned operation with the same hardware. `signedAddend` must be false if using this control signal. - An optional `clk`, as well as `enable` and `reset` that are used to add a pipestage in the `ColumnCompressor` to allow for pipelined operation. +- An optional `use42Compressors` boolean enables the `ColumnCompressor` to use 4:2 compressors in addition to 3:2 (Full Adder) and 2:2 (Half Adder) compressors. The output width of the `CompressionTreeMultiplier` is the sum of the product term widths plus one to accomodate the additional acccumulate term. diff --git a/lib/src/arithmetic/addend_compressor.dart b/lib/src/arithmetic/addend_compressor.dart index 5cca11a35..06b878fc5 100644 --- a/lib/src/arithmetic/addend_compressor.dart +++ b/lib/src/arithmetic/addend_compressor.dart @@ -12,52 +12,11 @@ import 'package:meta/meta.dart'; import 'package:rohd/rohd.dart'; import 'package:rohd_hcl/rohd_hcl.dart'; -/// Base class for bit-level column compressor function -abstract class BitCompressor extends Module { - /// Input bits to compress - @protected - late final Logic compressBits; - - /// The addition results [sum] including carry bit - Logic get sum => output('sum'); - - /// The carry results [carry]. - Logic get carry => output('carry'); - - /// Construct a column compressor - BitCompressor(Logic compressBits, {super.name = 'bit_compressor'}) { - this.compressBits = addInput( - 'compressBits', - compressBits, - width: compressBits.width, - ); - addOutput('sum'); - addOutput('carry'); - } -} - -/// 2-input column compressor (half-adder) -class Compressor2 extends BitCompressor { - /// Construct a 2-input compressor (half-adder) - Compressor2(super.compressBits, {super.name = 'compressor_2'}) { - sum <= compressBits.xor(); - carry <= compressBits.and(); - } -} - -/// 3-input column compressor (full-adder) -class Compressor3 extends BitCompressor { - /// Construct a 3-input column compressor (full-adder) - Compressor3(super.compressBits, {super.name = 'compressor_3'}) { - sum <= compressBits.xor(); - carry <= - mux(compressBits[0], compressBits.slice(2, 1).or(), - compressBits.slice(2, 1).and()); - } -} - /// Compress terms enum CompressTermType { + /// A cout (horizontal carry) + cout, + /// A carry term carry, @@ -65,7 +24,10 @@ enum CompressTermType { sum, /// A partial product term (from the original matrix) - pp + pp, + + /// a cin (horizontal carry-in) term + cin } /// A compression term @@ -76,6 +38,9 @@ class CompressTerm implements Comparable { /// The inputs that drove this Term late final List inputs; + /// The carry input that drove this Term + late final List? carryInputs; + /// The row of the terminal final int row; @@ -85,26 +50,29 @@ class CompressTerm implements Comparable { /// The Logic wire of the term final Logic logic; - /// Estimated delay of the output of this CompessTerm + /// Estimated delay of the output of this CompressTerm late double delay; - /// Estimated delay of a Sum term - static const sumDelay = 1.0; - - /// Estimated delay of a Carry term - static const carryDelay = 0.75; - /// CompressTerm constructor - CompressTerm(this.type, this.logic, this.inputs, this.row, this.col) { + CompressTerm(BitCompressor? compressor, this.type, this.logic, this.inputs, + this.row, this.col, + {this.carryInputs}) { delay = 0.0; - final deltaDelay = switch (type) { - CompressTermType.carry => carryDelay, - CompressTermType.sum => sumDelay, - CompressTermType.pp => 0.0 - }; - for (final i in inputs) { - if (i.delay + deltaDelay > delay) { - delay = i.delay + deltaDelay; + if (compressor != null) { + final deltaDelay = compressor.evaluateDelay(type, CompressTermType.pp); + for (final i in inputs) { + if (i.delay + deltaDelay > delay) { + delay = i.delay + deltaDelay; + } + } + if (carryInputs != null) { + final deltaDelay2 = + compressor.evaluateDelay(type, CompressTermType.cin); + for (final c in carryInputs!) { + if (c.delay + deltaDelay2 > delay) { + delay = c.delay + deltaDelay2; + } + } } } } @@ -139,6 +107,11 @@ class CompressTerm implements Comparable { final majority = (count > termValues.length ~/ 2 ? LogicValue.one : LogicValue.zero); value = majority; + case CompressTermType.cout: + throw RohdHclException('cout CompressTermType should not be evaluated'); + + case CompressTermType.cin: + throw RohdHclException('cin CompressTermType should not be evaluated'); } return value; } @@ -149,7 +122,9 @@ class CompressTerm implements Comparable { final ts = switch (type) { CompressTermType.pp => 'pp', CompressTermType.carry => 'c', - CompressTermType.sum => 's' + CompressTermType.cout => 'o', + CompressTermType.sum => 's', + CompressTermType.cin => 'i' }; str ..write(ts) @@ -158,15 +133,116 @@ class CompressTerm implements Comparable { } } +/// Base class for bit-level column compressor function +abstract class BitCompressor extends Module { + /// Input bits to compress + @protected + late final Logic compressBits; + + /// Input terms to compress + late final List terms; + + /// The addition results [sum] including carry bit + Logic get sum => output('sum'); + + /// The carry results [carry]. + Logic get carry => output('carry'); + + late final List> _delays; + + /// Construct a column compressor. + BitCompressor(this.terms, {super.name = 'bitcompressor'}) { + compressBits = [ + for (var pos = 0; pos < terms.length; pos++) + addInput('t_$pos', terms[pos].logic) + ].swizzle(); + addOutput('sum'); + addOutput('carry'); + _delays = List.filled(CompressTermType.values.length, + List.filled(CompressTermType.values.length, 0)); + } + + /// Evaluate the delay between input and output + double evaluateDelay(CompressTermType outTerm, CompressTermType inTerm) => + _delays[outTerm.index][inTerm.index]; +} + +/// 2-input column compressor (half-adder) +class Compressor2 extends BitCompressor { + /// Construct a 2-input compressor (half-adder). + Compressor2(super.terms, {super.name = 'bitcompressor2'}) { + sum <= compressBits.xor(); + carry <= compressBits.and(); + _delays[CompressTermType.sum.index][CompressTermType.pp.index] = 1.0; + _delays[CompressTermType.carry.index][CompressTermType.pp.index] = 1.5; + } +} + +/// 3-input column compressor (full-adder) +class Compressor3 extends BitCompressor { + /// Construct a 3-input column compressor (full-adder). + Compressor3(super.terms, {super.name = 'bitcompressor3'}) { + sum <= compressBits.xor(); + carry <= + mux(compressBits[0], compressBits.slice(2, 1).or(), + compressBits.slice(2, 1).and()); + // TODO(desmonddak): wiring different inputs for different delays + // means we may need to index by input not just type + _delays[CompressTermType.sum.index][CompressTermType.pp.index] = 1.0; + _delays[CompressTermType.carry.index][CompressTermType.pp.index] = 1.5; + } +} + +/// 4-input column compressor (4:2 compressor) +class Compressor4 extends BitCompressor { + /// Horizontal carry-out [cout] + Logic get cout => output('cout'); + + /// Construct a 4-input column compressor using two 3-input compressors. + Compressor4(List terms, List cinL, + {super.name = 'bitcompressor4'}) + : super(terms) { + // We need to use internal Logic and regenerate Term lists inside + cinL = [ + for (final (i, c) in cinL.indexed) + CompressTerm( + this, c.type, addInput('cin_$i', c.logic), c.inputs, c.row, c.col) + ]; + final internalTerms = [ + for (var i = 0; i < compressBits.width; i++) + CompressTerm(this, terms[i].type, compressBits.reversed[i], + terms.sublist(0, 4), terms[i].row, terms[i].col) + ]; + addOutput('cout'); + final c3A = Compressor3(internalTerms.sublist(1, 4)); + cout <= c3A.carry; + final t = CompressTerm( + c3A, CompressTermType.sum, c3A.sum, internalTerms.sublist(1, 4), 0, 0); + final c3B = Compressor3([t, internalTerms[0], cinL[0]]); + carry <= c3B.carry; + sum <= c3B.sum; + + // TODO(desmonddak): wiring different inputs for different delays + _delays[CompressTermType.sum.index][CompressTermType.pp.index] = 4.0; + _delays[CompressTermType.sum.index][CompressTermType.cin.index] = 2.0; + _delays[CompressTermType.carry.index][CompressTermType.pp.index] = 3.0; + _delays[CompressTermType.carry.index][CompressTermType.cin.index] = 2.0; + _delays[CompressTermType.cout.index][CompressTermType.pp.index] = 3.0; + _delays[CompressTermType.cout.index][CompressTermType.cin.index] = 0.0; + } +} + /// A column of partial product terms typedef ColumnQueue = PriorityQueue; /// A column compressor class ColumnCompressor { /// Columns of partial product CompressTerms - late final List columns; + /// Columns of partial product CompressTerms for carries (4:2 output) + late final List carryColumns; + /// The partial product array to be compressed final PartialProductArray pp; @@ -179,19 +255,26 @@ class ColumnCompressor { /// Optional enable for configurable pipestage. Logic? enable; + /// Use 4:2 compressors in compression tree + bool use42Compressors; + /// Initialize a ColumnCompressor for a set of partial products /// /// 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}) { + /// + /// [use42Compressors] will combine 4:2, 3:2, and 2:2 compressors in building + /// a compression tree. + ColumnCompressor(this.pp, + {this.use42Compressors = false, this.clk, this.reset, this.enable}) { columns = List.generate(pp.maxWidth(), (i) => ColumnQueue()); - + carryColumns = List.generate(pp.maxWidth(), (i) => ColumnQueue()); for (var row = 0; row < pp.rows; row++) { for (var col = 0; col < pp.partialProducts[row].length; col++) { final trueColumn = pp.rowShift[row] + col; - final term = CompressTerm(CompressTermType.pp, + final term = CompressTerm(null, CompressTermType.pp, pp.partialProducts[row][col], [], row, trueColumn); columns[trueColumn].add(term); } @@ -200,7 +283,8 @@ class ColumnCompressor { /// Return the longest column length int longestColumn() => - columns.reduce((a, b) => a.length > b.length ? a : b).length; + columns.reduce((a, b) => a.length > b.length ? a : b).length + + carryColumns.reduce((a, b) => a.length > b.length ? a : b).length; /// Convert a row to a Logic bitvector Logic extractRow(int row) { @@ -208,15 +292,16 @@ class ColumnCompressor { final rowBits = []; for (var col = columns.length - 1; col >= 0; col--) { - final colList = columns[col].toList(); + final colList = carryColumns[col].toList() + columns[col].toList(); if (row < colList.length) { final value = colList[row].logic; rowBits.add( clk != null ? flop(clk!, value, reset: reset, en: enable) : value); + } else { + rowBits.add(Const(0)); } } - rowBits.addAll(List.filled(pp.rowShift[row], Const(0))); if (width > rowBits.length) { return rowBits.swizzle().zeroExtend(width); } @@ -228,24 +313,42 @@ class ColumnCompressor { final terms = []; for (var col = 0; col < columns.length; col++) { final queue = columns[col]; - final depth = queue.length; + final PriorityQueue carryQueue; + if (use42Compressors) { + carryQueue = carryColumns[col]; + } else { + carryQueue = PriorityQueue(); + } + final depth = queue.length + carryQueue.length; if (depth > iteration) { if (depth > 2) { final first = queue.removeFirst(); final second = queue.removeFirst(); final inputs = [first, second]; BitCompressor compressor; - if (depth > 3) { + if (depth > 4 && use42Compressors) { + final cin = carryQueue.isNotEmpty + ? carryQueue.removeFirst() + : CompressTerm(null, CompressTermType.cin, Const(0), [], 0, 0); + inputs + ..add(queue.removeFirst()) + ..add(queue.removeFirst()); + compressor = Compressor4(inputs, [cin]); + if (col < columns.length - 1) { + final t = CompressTerm(compressor, CompressTermType.carry, + (compressor as Compressor4).cout, inputs, 0, col); + carryColumns[col + 1].add(t); + } + } else if (depth > 3) { inputs.add(queue.removeFirst()); - compressor = Compressor3( - [for (final i in inputs) i.logic].swizzle(), - name: 'cmp3_iter${iteration}_col$col'); + compressor = + Compressor3(inputs, name: 'cmp3_iter${iteration}_col$col'); } else { - compressor = Compressor2( - [for (final i in inputs) i.logic].swizzle(), - name: 'cmp2_iter${iteration}_col$col'); + compressor = + Compressor2(inputs, name: 'cmp2_iter${iteration}_col$col'); } final t = CompressTerm( + compressor, CompressTermType.sum, compressor.sum.named('cmp_sum_iter${iteration}_c$col', naming: Naming.mergeable), @@ -256,12 +359,14 @@ class ColumnCompressor { columns[col].add(t); if (col < columns.length - 1) { final t = CompressTerm( + compressor, CompressTermType.carry, compressor.carry.named('cmp_carry_iter${iteration}_c$col', naming: Naming.mergeable), inputs, 0, col); + columns[col + 1].add(t); terms.add(t); } diff --git a/lib/src/arithmetic/evaluate_compressor.dart b/lib/src/arithmetic/evaluate_compressor.dart index 71685e442..559208122 100644 --- a/lib/src/arithmetic/evaluate_compressor.dart +++ b/lib/src/arithmetic/evaluate_compressor.dart @@ -24,23 +24,30 @@ extension EvaluateLiveColumnCompressor on ColumnCompressor { bool logic = false, bool header = true, int prefix = 1, - int extraSpace = 5}) { + int extraSpace = 0}) { final ts = StringBuffer(); final rows = longestColumn(); final width = pp.maxWidth(); var accum = BigInt.zero; for (var row = 0; row < rows; row++) { + final int shift; + if (row >= pp.rows) { + shift = pp.rowShift[pp.rows - 1]; + } else { + shift = pp.rowShift[row]; + } final rowLogic = []; for (var col = columns.length - 1; col >= 0; col--) { - final colList = columns[col].toList(); + final colList = carryColumns[col].toList() + columns[col].toList(); if (row < colList.length) { rowLogic.insert(0, colList[row].logic); + } else if (col >= shift) { + // rowLogic.insert(0, Const(0)); } } - final rowBits = [for (final c in rowLogic) c.value].reversed.toList(); - // ignore: cascade_invocations - rowBits.addAll(List.filled(pp.rowShift[row], LogicValue.zero)); + final rowBits = [for (final c in rowLogic) c.value].reversed.toList() + ..addAll(List.filled(shift, LogicValue.zero)); final rowBitsExtend = rowBits.length < width ? rowBits.swizzle().zeroExtend(width) : rowBits.swizzle(); @@ -53,7 +60,7 @@ extension EvaluateLiveColumnCompressor on ColumnCompressor { prefix: prefix, extraSpace: extraSpace, intValue: true, - shift: pp.rowShift[row])) + shift: shift)) ..write('\n'); } @@ -67,15 +74,23 @@ extension EvaluateLiveColumnCompressor on ColumnCompressor { } /// Return a string representing the compression tree in its current state - String representation() { + String representation({bool evalLogic = false, bool useTabs = true}) { final ts = StringBuffer(); + + final sep = useTabs ? '\t' : ' '; + for (var row = 0; row < longestColumn(); row++) { + ts.write(useTabs ? '' : ' '); for (var col = columns.length - 1; col >= 0; col--) { - final colList = columns[col].toList(); + final colList = columns[col].toList() + carryColumns[col].toList(); if (row < colList.length) { - ts.write('\t${colList[row]}'); + if (evalLogic) { + ts.write('$sep${colList[row].logic.value.toInt()}'); + } else { + ts.write('$sep${colList[row]}'); + } } else { - ts.write('\t'); + ts.write(useTabs ? sep : '$sep '); } } ts.write('\n'); diff --git a/lib/src/arithmetic/evaluate_partial_product.dart b/lib/src/arithmetic/evaluate_partial_product.dart index bbf82bab4..b155b230d 100644 --- a/lib/src/arithmetic/evaluate_partial_product.dart +++ b/lib/src/arithmetic/evaluate_partial_product.dart @@ -10,6 +10,26 @@ import 'package:rohd/rohd.dart'; import 'package:rohd_hcl/rohd_hcl.dart'; +/// This extension will eventually move to ROHD once it is proven useful +extension LogicFirstOne on LogicValue { + /// Compute the first One find operation on LogicValue, returning its position + int? firstOne() { + if (!isValid) { + return null; + } + var shiftedValue = this; + var result = 0; + while (shiftedValue[0] != LogicValue.one) { + result++; + if (result == width) { + return null; + } + shiftedValue >>>= 1; + } + return result; + } +} + /// The following routines are useful only during testing extension TestPartialProductSignage on PartialProductGeneratorBase { /// Return true if multiplicand is truly signed (fixed or runtime) diff --git a/lib/src/arithmetic/multiplier.dart b/lib/src/arithmetic/multiplier.dart index c572d7060..046cc9aab 100644 --- a/lib/src/arithmetic/multiplier.dart +++ b/lib/src/arithmetic/multiplier.dart @@ -340,6 +340,9 @@ class CompressionTreeMultiplier extends Multiplier { /// after compression. [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. + /// + /// [use42Compressors] will combine 4:2, 3:2, and 2:2 compressors in building + /// a compression tree. CompressionTreeMultiplier(super.a, super.b, int radix, {super.clk, super.reset, @@ -353,7 +356,8 @@ class CompressionTreeMultiplier extends Multiplier { PartialProductSignExtension Function(PartialProductGeneratorBase pp, {String name}) seGen = CompactRectSignExtension.new, - super.name = 'compression_tree_multiplier'}) + bool use42Compressors = false, + super.name = 'compressison_tree_multiplier'}) : super( definitionName: 'CompressionTreeMultiplier_W${a.width}x' '${b.width}_' @@ -373,13 +377,16 @@ class CompressionTreeMultiplier extends Multiplier { selectSignedMultiplier: selectSignedMultiplier, signedMultiplier: signedMultiplier, ); - seGen(pp).signExtend(); - - final compressor = - ColumnCompressor(clk: clk, reset: reset, enable: enable, pp) - ..compress(); + final compressor = ColumnCompressor( + clk: clk, + reset: reset, + enable: enable, + pp, + use42Compressors: use42Compressors) + ..compress(); final adder = adderGen(compressor.extractRow(0), compressor.extractRow(1)); + product <= adder.sum.slice(a.width + b.width - 1, 0); } } @@ -424,6 +431,9 @@ class CompressionTreeMultiplyAccumulate extends MultiplyAccumulate { /// after compression. [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. + /// + /// [use42Compressors] will combine 4:2, 3:2, and 2:2 compressors in building + /// a compression tree. CompressionTreeMultiplyAccumulate(super.a, super.b, super.c, int radix, {Logic? clk, Logic? reset, @@ -439,6 +449,7 @@ class CompressionTreeMultiplyAccumulate extends MultiplyAccumulate { PartialProductSignExtension Function(PartialProductGeneratorBase pp, {String name}) seGen = CompactRectSignExtension.new, + bool use42Compressors = false, super.name = 'compression_tree_mac'}) { final accumulate = addOutput('accumulate', width: a.width + b.width + 1); final pp = PartialProductGenerator( @@ -475,9 +486,13 @@ class CompressionTreeMultiplyAccumulate extends MultiplyAccumulate { pp.partialProducts.insert(0, l); pp.rowShift.insert(0, 0); - final compressor = - ColumnCompressor(clk: clk, reset: reset, enable: enable, pp) - ..compress(); + final compressor = ColumnCompressor( + clk: clk, + reset: reset, + enable: enable, + pp, + use42Compressors: use42Compressors) + ..compress(); final adder = adderGen(compressor.extractRow(0), compressor.extractRow(1)); accumulate <= adder.sum.slice(a.width + b.width - 1 + 1, 0); } diff --git a/lib/src/arithmetic/values/logicvalue_extension.dart b/lib/src/arithmetic/values/logicvalue_extension.dart new file mode 100644 index 000000000..f77517721 --- /dev/null +++ b/lib/src/arithmetic/values/logicvalue_extension.dart @@ -0,0 +1,53 @@ +// Copyright (C) 2025 Intel Corporation +// SPDX-License-Identifier: BSD-3-Clause +// +// logicvalue_extension.dart +// Utilities for LogicValue as an extension. +// +// 2025 January 31 +// Author: Desmond Kirkpatrick + +import 'package:rohd/rohd.dart'; +import 'package:rohd_hcl/rohd_hcl.dart'; + +/// This extension will eventually move to ROHD once it is proven useful +extension LogicValueMajority on LogicValue { + /// Compute the unary majority on LogicValue + bool majority() { + if (!isValid) { + return false; + } + final zero = LogicValue.filled(width, LogicValue.zero); + var shiftedValue = this; + var result = 0; + while (shiftedValue != zero) { + result += (shiftedValue[0] & LogicValue.one == LogicValue.one) ? 1 : 0; + shiftedValue >>>= 1; + } + return result > (width ~/ 2); + } + + /// Compute the first One find operation on LogicValue, returning its position + int? firstOne() { + if (!isValid) { + return null; + } + var shiftedValue = this; + var result = 0; + while (shiftedValue[0] != LogicValue.one) { + result++; + if (result == width) { + return null; + } + shiftedValue >>>= 1; + } + return result; + } + + /// Return the populationCount of 1s in a LogicValue + int popCount() { + final r = RegExp('1'); + final matches = r.allMatches(bitString); + return matches.length; + } +} 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 400a594ce..5925ecab4 100644 --- a/lib/src/component_config/components/config_compression_tree_multiplier.dart +++ b/lib/src/component_config/components/config_compression_tree_multiplier.dart @@ -53,6 +53,9 @@ class CompressionTreeMultiplierConfigurator extends Configurator { /// Controls whether the adder is pipelined final ToggleConfigKnob pipelinedKnob = ToggleConfigKnob(value: false); + /// Controls whether the adder is pipelined + final ToggleConfigKnob use42CompressorsKnob = ToggleConfigKnob(value: false); + @override Module createModule() => CompressionTreeMultiplier( clk: pipelinedKnob.value ? Logic() : null, @@ -65,7 +68,8 @@ class CompressionTreeMultiplierConfigurator extends Configurator { signMultiplicandValueKnob.value == 'selected' ? Logic() : null, selectSignedMultiplier: signMultiplierValueKnob.value == 'selected' ? Logic() : null, - adderGen: adderGeneratorMap[adderTypeKnob.value]!); + adderGen: adderGeneratorMap[adderTypeKnob.value]!, + use42Compressors: use42CompressorsKnob.value); @override late final Map> knobs = UnmodifiableMapView({ @@ -76,6 +80,7 @@ class CompressionTreeMultiplierConfigurator extends Configurator { 'Multiplier width': multiplierWidthKnob, 'Multiplier sign': signMultiplierValueKnob, 'Pipelined': pipelinedKnob, + 'Use 4:2 Compressors': use42CompressorsKnob, }); @override diff --git a/lib/src/utils.dart b/lib/src/utils.dart index b4a52a144..9f31924e3 100644 --- a/lib/src/utils.dart +++ b/lib/src/utils.dart @@ -16,41 +16,6 @@ extension LogicValueBitString on LogicValue { String get bitString => toString(includeWidth: false); } -/// This extension will eventually move to ROHD once it is proven useful -extension LogicValueMajority on LogicValue { - /// Compute the unary majority on LogicValue - bool majority() { - if (!isValid) { - return false; - } - final zero = LogicValue.filled(width, LogicValue.zero); - var shiftedValue = this; - var result = 0; - while (shiftedValue != zero) { - result += (shiftedValue[0] & LogicValue.one == LogicValue.one) ? 1 : 0; - shiftedValue >>>= 1; - } - return result > (width ~/ 2); - } - - /// Compute the first One find operation on LogicValue, returning its position - int? firstOne() { - if (!isValid) { - return null; - } - var shiftedValue = this; - var result = 0; - while (shiftedValue[0] != LogicValue.one) { - result++; - if (result == width) { - return null; - } - shiftedValue >>>= 1; - } - 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 diff --git a/test/arithmetic/addend_compressor_test.dart b/test/arithmetic/addend_compressor_test.dart index 20704cb58..f098fbfa4 100644 --- a/test/arithmetic/addend_compressor_test.dart +++ b/test/arithmetic/addend_compressor_test.dart @@ -8,11 +8,13 @@ // Author: Desmond Kirkpatrick import 'dart:async'; +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/evaluate_partial_product.dart'; import 'package:rohd_hcl/src/arithmetic/partial_product_sign_extend.dart'; +import 'package:rohd_hcl/src/arithmetic/values/logicvalue_extension.dart'; import 'package:test/test.dart'; /// This [CompressorTestMod] module is used to test instantiation, where we can @@ -55,6 +57,37 @@ void main() { await Simulator.reset(); }); + test('4-2 compressor', () { + final bits = Logic(width: 4); + final cin = [Logic()]; + + final inputs = [ + for (var i = 0; i < bits.width; i++) + CompressTerm(null, CompressTermType.pp, bits[i], [], 0, 0) + ]; + final carryInputs = [ + for (var i = 0; i < cin.length; i++) + CompressTerm(null, CompressTermType.pp, cin[i], [], 0, 0) + ]; + + final compressor = Compressor4(inputs, carryInputs); + + for (var cVal = 0; cVal < 2; cVal++) { + cin[0].put(cVal); + + for (var val = 0; val < pow(2, 4) - 1; val++) { + bits.put(val); + final count = compressor.sum.value.toInt() + + 2 * + (compressor.carry.value.toInt() + + compressor.cout.value.toInt()); + final bitCount = + bits.value.popCount() + ((cin[0].value == LogicValue.one) ? 1 : 0); + expect(count, equals(bitCount)); + } + } + }); + test('Column Compressor: evaluate flopped', () async { final clk = SimpleClockGenerator(10).clk; const widthX = 6; @@ -89,24 +122,24 @@ void main() { equals(BigInt.from(av * bv))); await Simulator.endSimulation(); }); - test('Column Compressor: single compressor evaluate', () async { - const widthX = 3; - const widthY = 3; + test('Column Compressor: single compressor 4:2 evaluate', () { + const widthX = 5; + const widthY = 5; final a = Logic(name: 'a', width: widthX); final b = Logic(name: 'b', width: widthY); - const av = -3; - const bv = -3; - for (final signed in [true, false]) { + const av = 12; + const bv = 13; + for (final signed in [true]) { 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 = 4; + const radix = 2; final encoder = RadixEncoder(radix); - for (final useSelect in [false, true]) { + for (final useSelect in [false]) { final selectSignedMultiplicand = useSelect ? Logic() : null; final selectSignedMultiplier = useSelect ? Logic() : null; if (useSelect) { @@ -121,11 +154,17 @@ void main() { CompactRectSignExtension(pp).signExtend(); expect(pp.evaluate(), equals(bA * bB)); - final compressor = ColumnCompressor(pp); - expect(compressor.evaluate().$1, equals(bA * bB)); - compressor.compress(); + final compressor = ColumnCompressor(pp, use42Compressors: true) + ..compress(); expect(compressor.evaluate().$1, equals(bA * bB)); } } }); + 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_encoder_test.dart b/test/arithmetic/multiplier_encoder_test.dart index d7fde0f07..16392bf8b 100644 --- a/test/arithmetic/multiplier_encoder_test.dart +++ b/test/arithmetic/multiplier_encoder_test.dart @@ -530,12 +530,4 @@ void main() { } 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 909e73c45..b669a5f4d 100644 --- a/test/arithmetic/multiplier_test.dart +++ b/test/arithmetic/multiplier_test.dart @@ -191,6 +191,7 @@ void main() { {SignExtensionFunction seGen = CompactRectSignExtension.new, Adder Function(Logic a, Logic b, {Logic? carryIn, String name}) adderGen = NativeAdder.new, + bool use42Compressors = false, bool signedMultiplicand = false, bool signedMultiplier = false, Logic? selectSignedMultiplicand, @@ -209,6 +210,7 @@ void main() { selectSignedMultiplicand: selectSignedMultiplicand, selectSignedMultiplier: selectSignedMultiplier, seGen: seGen, + use42Compressors: use42Compressors, adderGen: adderGen, name: 'Compression Tree Multiplier: ' '${adderName(a, b)}' @@ -219,6 +221,7 @@ void main() { {SignExtensionFunction seGen = CompactRectSignExtension.new, Adder Function(Logic a, Logic b, {Logic? carryIn, String name}) adderGen = NativeAdder.new, + bool use42Compressors = false, bool signedMultiplicand = false, bool signedMultiplier = false, Logic? selectSignedMultiplicand, @@ -233,8 +236,7 @@ void main() { selectSignedMultiplier: selectSignedMultiplier, curryCompressionTreeMultiplier( radix, - adderGen: adderGen, - seGen: seGen, + use42Compressors: use42Compressors, signedMultiplicand: signedMultiplicand, signedMultiplier: signedMultiplier, selectSignedMultiplicand: selectSignedMultiplicand, @@ -246,6 +248,7 @@ void main() { Adder Function(Logic a, Logic b, {Logic? carryIn, String name}) adderGen = NativeAdder.new, SignExtensionFunction seGen = CompactRectSignExtension.new, + bool use42Compressors = false, bool signedMultiplicand = false, bool signedMultiplier = false, bool signedAddend = false, @@ -263,6 +266,7 @@ void main() { return (a, b, c) => CompressionTreeMultiplyAccumulate(a, b, c, radix, adderGen: adderGen, seGen: seGen, + use42Compressors: use42Compressors, signedMultiplicand: signedMultiplicand, signedMultiplier: signedMultiplier, signedAddend: signedAddend, @@ -358,6 +362,23 @@ void main() { } } }); + group('Compression Tree Multiplier: curried 4:2 random radix/ptree/width', + () { + for (final radix in [2, 4]) { + for (final width in [8, 10]) { + for (final ppTree in [KoggeStone.new]) { + Adder adderFn(Logic a, Logic b, {Logic? carryIn, String? name}) => + ParallelPrefixAdder(a, b, carryIn: carryIn, ppGen: ppTree); + testMultiplyAccumulateRandom( + // testMultiplyAccumulateExhaustive( + width, + 10, + curryMultiplierAsMultiplyAccumulate(radix, + adderGen: adderFn, use42Compressors: true)); + } + } + } + }); group('Compression Tree Multiplier: curried random radix/extension/width', () { for (final radix in [2, 4]) { @@ -500,7 +521,7 @@ void main() { }); test('single multiplier', () async { - const width = 8; + const width = 16; final a = Logic(name: 'a', width: width); final b = Logic(name: 'b', width: width); const av = 12; @@ -523,6 +544,7 @@ void main() { b.put(bB); final mod = CompressionTreeMultiplier(a, b, 4, + use42Compressors: true, adderGen: ParallelPrefixAdder.new, seGen: StopBitsSignExtension.new, signedMultiplier: !useSignedLogic && signed, @@ -537,6 +559,43 @@ void main() { } } }); + test('single 4:2 multiplier', () { + const width = 8; + final a = Logic(name: 'a', width: width); + final b = Logic(name: 'b', width: width); + const av = 0; + const bv = 0; + for (final signed in [false, true]) { + for (final useSignedLogic in [false, true]) { + final bA = SignedBigInt.fromSignedInt(av, width, signed: signed); + final bB = SignedBigInt.fromSignedInt(bv, width, signed: signed); + + final Logic? signedSelect; + + if (useSignedLogic) { + signedSelect = Logic()..put(signed ? 1 : 0); + } else { + signedSelect = null; + } + + // Set these so that printing inside module build will have Logic values + a.put(bA); + b.put(bB); + + final mod = CompressionTreeMultiplier(a, b, 4, + signedMultiplicand: !useSignedLogic && signed, + signedMultiplier: !useSignedLogic && signed, + use42Compressors: true, + selectSignedMultiplicand: signedSelect, + selectSignedMultiplier: signedSelect); + final golden = bA * bB; + final result = mod.isSignedResult() + ? mod.product.value.toBigInt().toSigned(mod.product.width) + : mod.product.value.toBigInt().toUnsigned(mod.product.width); + expect(result, equals(golden)); + } + } + }); test('trivial instantiated multiplier', () async { // Using this to find trace errors when instantiating multipliers @@ -637,7 +696,7 @@ void main() { c.put(5); final multiplier = CompressionTreeMultiplyAccumulate(a, b, c, radix, - signedMultiplier: true); + signedMultiplicand: true, signedMultiplier: true, signedAddend: true); final accumulate = multiplier.accumulate; expect(accumulate.value.toBigInt(), equals(BigInt.from(15 * 3 + 5))); }); @@ -719,6 +778,7 @@ void main() { expect(ppG0.getAbsolute(1, 10).value, equals(Const(0).value)); final cc = ColumnCompressor(ppG0); + const expectedRep = ''' pp3,15 pp3,14 pp3,13 pp3,12 pp3,11 pp3,10 pp3,9 pp3,8 pp3,7 pp3,6 pp3,5 pp2,4 pp2,3 pp1,2 pp1,1 pp0,0 pp2,13 pp2,12 pp1,11 pp2,10 pp2,9 pp2,8 pp2,7 pp2,6 pp2,5 pp0,4 pp0,3 pp0,2 pp0,1 @@ -727,7 +787,7 @@ void main() { '''; expect(cc.representation(), equals(expectedRep)); - final (v, ts) = cc.evaluate(printOut: true); + final (v, ts) = cc.evaluate(printOut: true, extraSpace: 5); const expectedEval = ''' 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 diff --git a/test/configurator_test.dart b/test/configurator_test.dart index 925fb2470..1b42dbae3 100644 --- a/test/configurator_test.dart +++ b/test/configurator_test.dart @@ -170,7 +170,7 @@ void main() { expect( multiplier.knobs.values.whereType>().length, 4); - expect(multiplier.knobs.values.whereType().length, 1); + expect(multiplier.knobs.values.whereType().length, 2); }); test('should return rtl code when invoke generate() with default value',