diff --git a/doc/components/adder.md b/doc/components/adder.md index 49af3cf2..7c1e9af1 100644 --- a/doc/components/adder.md +++ b/doc/components/adder.md @@ -6,6 +6,7 @@ ROHD-HCL provides a set of adder modules to get the sum from a pair of Logic. As - [Parallel Prefix Adder](#parallel-prefix-adder) - [One's Complement Adder Subtractor](#ones-complement-adder-subtractor) - [Sign Magnitude Adder](#sign-magnitude-adder) +- [Compound Adder](#compound-adder) ## Ripple Carry Adder @@ -102,3 +103,30 @@ Here is an example of instantiating a `SignMagnitudeAdder`: print('${sum.value.toBigInt()}'); ``` + +## Compound Adder + +A compound carry adder is a digital circuit used for binary addition. It produces sum and sum+1 outputs. +A trivial compound adder component `TrivialCompoundAdder` doesnt use any RTL code optimization. +Carry-select adder-based compound adder `CarrySelectCompoundAdder` uses carry-select adder as a basis. Like a carry-select adder it consists of a multiple blocks of two ripple-carry adders . But the first block has two ripple-carry adders and two separate carry-propagate chains are used to select sum and sum+1 output bits. Sum selecting chain starts from carry input 0 driven block and sum+1 selecting chain starts from carry input 1 driven block. +The delay of the adder is defined by combination ripple-carry adder and accumulated carry-select chain delay. + +The `CarrySelectCompoundAdder` module in ROHD-HCL accept input `Logic`s a and b as the input pin and the name of the module `name`. Note that the width of the inputs must be the same or a `RohdHclException` will be thrown. +Compound adder generator provides two alogithms for splitting adder into ripple-carry blocks. `CarrySelectCompoundAdder.splitSelectAdderAlgorithm4Bit` algoritm splits adder into blocks of 4-bit ripple-carry adders with the first one width adjusted down. `CarrySelectCompoundAdder.splitSelectAdderAlgorithmSingleBlock` algorithm generates only one block of full bitwidth of the adder. Input List\ Function(int adderFullWidth) `widthGen` should be used to specify custom adder splitting algorithm that returns a list of sub-adders width. The default one is `CarrySelectCompoundAdder.splitSelectAdderAlgorithmSingleBlock`. + +An example is shown below to add two inputs of signals that have 8-bits of width. + +```dart +final a = Logic(name: 'a', width: 8); +final b = Logic(name: 'b', width: 8); + +a.put(5); +b.put(5); + +final rippleCarryAdder = CarrySelectCompoundAdder(a, b); +final sum = rippleCarryAdder.sum; +final sum1 = rippleCarryAdder.sum1; + +final rippleCarryAdder4BitBlock = CarrySelectCompoundAdder(a, b, + widthGen: CarrySelectCompoundAdder.splitSelectAdderAlgorithm4Bit); +``` diff --git a/lib/src/arithmetic/arithmetic.dart b/lib/src/arithmetic/arithmetic.dart index bf52d17b..9b0d9e0d 100644 --- a/lib/src/arithmetic/arithmetic.dart +++ b/lib/src/arithmetic/arithmetic.dart @@ -3,6 +3,7 @@ export 'adder.dart'; export 'carry_save_mutiplier.dart'; +export 'compound_adder.dart'; export 'divider.dart'; export 'multiplier.dart'; export 'multiplier_lib.dart'; diff --git a/lib/src/arithmetic/compound_adder.dart b/lib/src/arithmetic/compound_adder.dart new file mode 100644 index 00000000..cf88c71b --- /dev/null +++ b/lib/src/arithmetic/compound_adder.dart @@ -0,0 +1,139 @@ +// Copyright (C) 2023-2024 Intel Corporation +// SPDX-License-Identifier: BSD-3-Clause +// +// compound_adder.dart +// Implementation of Compund Integer Adder Module +// (Output Sum and Sum1 which is Sum + 1). +// +// 2024 September +// Author: Anton Sorokin + +import 'package:rohd/rohd.dart'; +import 'package:rohd_hcl/rohd_hcl.dart'; + +/// An abstract class for all compound adder module implementations. +abstract class CompoundAdder extends Adder { + /// The addition result [sum] + 1 in 2s complement form as [sum1] + Logic get sum1 => output('sum1'); + + /// Takes in input [a] and input [b] and return the [sum] of the addition + /// result and [sum1] sum + 1. + /// The width of input [a] and [b] must be the same. + CompoundAdder(super.a, super.b, {super.name}) { + if (a.width != b.width) { + throw RohdHclException('inputs of a and b should have same width.'); + } + addOutput('sum1', width: a.width + 1); + } +} + +/// A trivial compound adder. +class TrivialCompoundAdder extends CompoundAdder { + /// Constructs a [CompoundAdder]. + TrivialCompoundAdder(super.a, super.b, + {super.name = 'trivial_compound_adder'}) { + sum <= a.zeroExtend(a.width + 1) + b.zeroExtend(b.width + 1); + sum1 <= sum + 1; + } +} + +/// Carry-select compound adder. +class CarrySelectCompoundAdder extends CompoundAdder { + /// Adder ripple-carry block size computation algorithm. + /// Generates only one carry-select block + /// Return list of carry-ripple block sizes starting from + /// the LSB connected one. + /// [adderWidth] is a whole width of adder. + static List splitSelectAdderAlgorithmSingleBlock(int adderWidth) { + final splitData = [adderWidth]; + return splitData; + } + + /// Adder ripple-carry block size computation algorithm. + /// Generates 4 bit carry-select blocks with 1st entry width adjusted down. + /// Return list of carry-ripple block sizes starting from + /// the LSB connected one. + /// [adderWidth] is a whole width of adder. + static List splitSelectAdderAlgorithm4Bit(int adderWidth) { + final blockNb = (adderWidth / 4.0).ceil(); + final firstBlockSize = adderWidth - (blockNb - 1) * 4; + final splitData = [firstBlockSize]; + for (var i = 1; i < blockNb; ++i) { + splitData.add(4); + } + return splitData; + } + + /// Constructs a [CarrySelectCompoundAdder]. + CarrySelectCompoundAdder(super.a, super.b, + {super.name = 'cs_compound_adder', + List Function(int) widthGen = + splitSelectAdderAlgorithmSingleBlock}) { + // output bits lists + final sumList0 = []; + final sumList1 = []; + // carryout of previous ripple-carry adder block + // for sum and sum+1 + Logic? carry0; + Logic? carry1; + // Get size of each ripple-carry adder block + final adderSplit = widthGen(a.width); + // 1st output bit index of each block + var blockStartIdx = 0; + for (var i = 0; i < adderSplit.length; ++i) { + // input width of current ripple-carry adder block + final blockWidth = adderSplit[i]; + if (blockWidth <= 0) { + throw RohdHclException('non-positive ripple-carry adder block size.'); + } + if (blockWidth + blockStartIdx > a.width) { + throw RohdHclException('oversized ripple-carry adders sequence.'); + } + final blockA = Logic(name: 'block_${i}_a', width: blockWidth); + final blockB = Logic(name: 'block_${i}_b', width: blockWidth); + blockA <= a.getRange(blockStartIdx, blockStartIdx + blockWidth); + blockB <= b.getRange(blockStartIdx, blockStartIdx + blockWidth); + // Build ripple-carry adders for 0 and 1 carryin values + final fullAdder0 = RippleCarryAdder(blockA, blockB, + carryIn: Const(0), name: 'block0_$i'); + final fullAdder1 = RippleCarryAdder(blockA, blockB, + carryIn: Const(1), name: 'block1_$i'); + for (var bitIdx = 0; bitIdx < blockWidth; ++bitIdx) { + if (i == 0) { + // connect directly to respective sum output bit + sumList0.add(fullAdder0.sum[bitIdx]); + sumList1.add(fullAdder1.sum[bitIdx]); + } else { + final bitOut0 = Logic(name: 'bit0_${blockStartIdx + bitIdx}'); + final bitOut1 = Logic(name: 'bit1_${blockStartIdx + bitIdx}'); + // select adder output from adder matching carryin value + bitOut0 <= + mux(carry0!, fullAdder1.sum[bitIdx], fullAdder0.sum[bitIdx]); + bitOut1 <= + mux(carry1!, fullAdder1.sum[bitIdx], fullAdder0.sum[bitIdx]); + sumList0.add(bitOut0); + sumList1.add(bitOut1); + } + } + if (i == 0) { + // select carryout as a last bit of the adder + carry0 = fullAdder0.sum[blockWidth]; + carry1 = fullAdder1.sum[blockWidth]; + } else { + // select carryout depending on carryin (carryout of the previous block) + carry0 = mux( + carry0!, fullAdder1.sum[blockWidth], fullAdder0.sum[blockWidth]); + carry1 = mux( + carry1!, fullAdder1.sum[blockWidth], fullAdder0.sum[blockWidth]); + } + blockStartIdx += blockWidth; + } + + // append carryout bit + sumList0.add(carry0!); + sumList1.add(carry1!); + + sum <= sumList0.rswizzle(); + sum1 <= sumList1.rswizzle(); + } +} diff --git a/lib/src/arithmetic/ripple_carry_adder.dart b/lib/src/arithmetic/ripple_carry_adder.dart index a5fe3d38..bb95b517 100644 --- a/lib/src/arithmetic/ripple_carry_adder.dart +++ b/lib/src/arithmetic/ripple_carry_adder.dart @@ -18,11 +18,16 @@ import 'package:rohd_hcl/rohd_hcl.dart'; /// adder sequentially adds corresponding bits of two binary numbers. class RippleCarryAdder extends Adder { /// Constructs an n-bit adder based on inputs List of inputs. - RippleCarryAdder(super.a, super.b, {super.name = 'ripple_carry_adder'}) { + RippleCarryAdder(super.a, super.b, + {Logic? carryIn, super.name = 'ripple_carry_adder_carry_in'}) { + if (carryIn != null) { + carryIn = addInput('carry_in', carryIn, width: carryIn.width); + } Logic? carry; final sumList = []; for (var i = 0; i < a.width; i++) { - final fullAdder = FullAdder(a: a[i], b: b[i], carryIn: carry ?? Const(0)); + final fullAdder = + FullAdder(a: a[i], b: b[i], carryIn: carry ?? (carryIn ?? Const(0))); carry = fullAdder.carryOut; sumList.add(fullAdder.sum); diff --git a/lib/src/component_config/components/component_registry.dart b/lib/src/component_config/components/component_registry.dart index 63842009..0f624073 100644 --- a/lib/src/component_config/components/component_registry.dart +++ b/lib/src/component_config/components/component_registry.dart @@ -28,4 +28,5 @@ List get componentRegistry => [ ParallelPrefixAdderConfigurator(), CompressionTreeMultiplierConfigurator(), ExtremaConfigurator(), + CompoundAdderConfigurator(), ]; diff --git a/lib/src/component_config/components/components.dart b/lib/src/component_config/components/components.dart index 7d17f149..8c465296 100644 --- a/lib/src/component_config/components/components.dart +++ b/lib/src/component_config/components/components.dart @@ -2,6 +2,7 @@ // SPDX-License-Identifier: BSD-3-Clause export 'config_carry_save_multiplier.dart'; +export 'config_compound_adder.dart'; export 'config_compression_tree_multiplier.dart'; export 'config_ecc.dart'; export 'config_edge_detector.dart'; diff --git a/lib/src/component_config/components/config_compound_adder.dart b/lib/src/component_config/components/config_compound_adder.dart new file mode 100644 index 00000000..b4eb734f --- /dev/null +++ b/lib/src/component_config/components/config_compound_adder.dart @@ -0,0 +1,40 @@ +// Copyright (C) 2023-2024 Intel Corporation +// SPDX-License-Identifier: BSD-3-Clause +// +// compound_adder.dart +// Configurator for a CompoundAdder. +// +// 2024 Cotober 1 + +import 'dart:collection'; + +import 'package:rohd/rohd.dart'; +import 'package:rohd_hcl/rohd_hcl.dart'; + +/// A [Configurator] for [CompoundAdder]. +class CompoundAdderConfigurator extends Configurator { + /// Map from Type to Adder generator + static Map generatorMap = { + TrivialCompoundAdder: TrivialCompoundAdder.new, + CarrySelectCompoundAdder: CarrySelectCompoundAdder.new + }; + + /// A knob controlling the width of the inputs to the adder. + final IntConfigKnob logicWidthKnob = IntConfigKnob(value: 16); + + /// Controls the type of [CompoundAdder]. + final moduleTypeKnob = ChoiceConfigKnob(generatorMap.keys.toList(), + value: CarrySelectCompoundAdder); + @override + final String name = 'Compound Adder'; + + @override + late final Map> knobs = UnmodifiableMapView( + {'Width': logicWidthKnob, 'Adder Type': moduleTypeKnob}); + + @override + Module createModule() => generatorMap[moduleTypeKnob.value]!( + Logic(width: logicWidthKnob.value), + Logic(width: logicWidthKnob.value), + ); +} diff --git a/test/arithmetic/compound_adder_test.dart b/test/arithmetic/compound_adder_test.dart new file mode 100644 index 00000000..461d69b6 --- /dev/null +++ b/test/arithmetic/compound_adder_test.dart @@ -0,0 +1,90 @@ +// Copyright (C) 2023-2024 Intel Corporation +// SPDX-License-Identifier: BSD-3-Clause +// +// compound_adder_test.dart +// Tests for the Compound Adder interface. +// +// 2024 September 23 +// Author: Anton Sorokin + +import 'dart:math'; + +import 'package:rohd/rohd.dart'; +import 'package:rohd_hcl/rohd_hcl.dart'; +import 'package:test/test.dart'; + +void checkCompoundAdder(CompoundAdder adder, LogicValue av, LogicValue bv) { + final aB = av.toBigInt(); + final bB = bv.toBigInt(); + // ignore: invalid_use_of_protected_member + adder.a.put(av); + // ignore: invalid_use_of_protected_member + adder.b.put(bv); + + expect(adder.sum.value.toBigInt(), equals(aB + bB)); + expect(adder.sum1.value.toBigInt(), equals(aB + bB + BigInt.one)); +} + +void testExhaustive(int n, CompoundAdder Function(Logic a, Logic b) fn) { + final a = Logic(name: 'a', width: n); + final b = Logic(name: 'b', width: n); + + final mod = fn(a, b); + test( + 'exhaustive: ${mod.name}_W$n' + '_G${fn.call(a, b).name}', () async { + await mod.build(); + + for (var aa = 0; aa < (1 << n); ++aa) { + for (var bb = 0; bb < (1 << n); ++bb) { + final av = LogicValue.of(BigInt.from(aa), width: n); + final bv = LogicValue.of(BigInt.from(bb), width: n); + checkCompoundAdder(mod, av, bv); + } + } + }); +} + +void main() { + tearDown(() async { + await Simulator.reset(); + }); + group('exhaustive', () { + testExhaustive(4, TrivialCompoundAdder.new); + testExhaustive(4, CarrySelectCompoundAdder.new); + }); + test('trivial compound adder test', () async { + const width = 6; + final a = Logic(name: 'a', width: width); + final b = Logic(name: 'b', width: width); + + a.put(18); + b.put(24); + + final adder = CarrySelectCompoundAdder(a, b, + widthGen: CarrySelectCompoundAdder.splitSelectAdderAlgorithm4Bit); + + final sum = adder.sum; + final sum1 = adder.sum1; + expect(sum.value.toBigInt(), equals(BigInt.from(18 + 24))); + expect(sum1.value.toBigInt(), equals(BigInt.from(18 + 24 + 1))); + }); + test('should return correct value when random numbers are given.', () async { + final a = Logic(name: 'a', width: 10); + final b = Logic(name: 'b', width: 10); + + final adder = CarrySelectCompoundAdder(a, b, + widthGen: CarrySelectCompoundAdder.splitSelectAdderAlgorithm4Bit); + await adder.build(); + + final rand = Random(5); + for (var i = 0; i < 100; i++) { + final randA = rand.nextInt(1 << a.width); + final randB = rand.nextInt(1 << a.width); + a.put(randA); + b.put(randB); + expect(adder.sum.value.toInt(), equals(randA + randB)); + expect(adder.sum1.value.toInt(), equals(randA + randB + 1)); + } + }); +} diff --git a/test/arithmetic/ripple_carry_adder_test.dart b/test/arithmetic/ripple_carry_adder_test.dart index 0c26c4fe..d366ad5c 100644 --- a/test/arithmetic/ripple_carry_adder_test.dart +++ b/test/arithmetic/ripple_carry_adder_test.dart @@ -109,4 +109,108 @@ void main() { expect(rippleCarryAdder.sum.value[widthLength], equals(LogicValue.one)); }); }); + group('ripple carry adder with carryIn', () { + test('should throw exception if toSum Logics have diferent width.', () { + final a = Logic(name: 'a', width: 8); + final b = Logic(name: 'b', width: 16); + final ci = Logic(name: 'carry_in'); + + expect(() => RippleCarryAdder(a, b, carryIn: ci), + throwsA(const TypeMatcher())); + }); + + test('should return correct value for ripple carry adder.', () async { + final a = Logic(name: 'a', width: 8); + final b = Logic(name: 'b', width: 8); + final ci = Logic(name: 'carry_in'); + + final lvA = Random(5).nextInt(128); + final lvB = Random(10).nextInt(128); + final lvC = Random(1).nextInt(2); + + a.put(lvA); + b.put(lvB); + ci.put(lvC); + + final rippleCarryAdder = RippleCarryAdder(a, b, carryIn: ci); + await rippleCarryAdder.build(); + + expect(rippleCarryAdder.sum.value.toInt(), equals(lvA + lvB + lvC)); + }); + + test('should return 0 when a , b and carryIn are all 0.', () async { + final a = Logic(name: 'a', width: 10)..put(0); + final b = Logic(name: 'b', width: 10)..put(0); + final carryIn = Logic(name: 'carry_in')..put(0); + + final rippleCarryAdder = RippleCarryAdder(a, b, carryIn: carryIn); + await rippleCarryAdder.build(); + + expect(rippleCarryAdder.sum.value.toInt(), equals(0)); + }); + + test('should return one of the value when one of the input is 0.', + () async { + const valA = 10; + final a = Logic(name: 'a', width: 10)..put(valA); + final b = Logic(name: 'b', width: 10)..put(0); + final carryIn = Logic(name: 'carry_in')..put(0); + + final rippleCarryAdder = RippleCarryAdder(a, b, carryIn: carryIn); + await rippleCarryAdder.build(); + + expect(rippleCarryAdder.sum.value.toInt(), equals(valA)); + }); + + test('should return correct value when random numbers is given.', () async { + final a = Logic(name: 'a', width: 10); + final b = Logic(name: 'b', width: 10); + final carryIn = Logic(name: 'carry_in'); + + final rippleCarryAdder = RippleCarryAdder(a, b, carryIn: carryIn); + await rippleCarryAdder.build(); + + final rand = Random(5); + for (var i = 0; i < 100; i++) { + final randA = rand.nextInt(1 << a.width); + final randB = rand.nextInt(1 << a.width); + final randC = rand.nextInt(2); + a.put(randA); + b.put(randB); + carryIn.put(randC); + expect( + rippleCarryAdder.sum.value.toInt(), equals(randA + randB + randC)); + } + }); + + test('should return correct value when carry bit is non-zero.', () async { + const widthLength = 4; + final a = Logic(name: 'a', width: widthLength)..put(1 << widthLength - 1); + final b = Logic(name: 'b', width: widthLength)..put(1 << widthLength - 1); + final carryIn = Logic(name: 'carry_in')..put(1); + + final rippleCarryAdder = RippleCarryAdder(a, b, carryIn: carryIn); + await rippleCarryAdder.build(); + + expect(rippleCarryAdder.sum.value.toInt(), (1 << a.width) + 1); + expect(rippleCarryAdder.sum.value.width, a.width + 1); + expect(rippleCarryAdder.sum.value[a.width], equals(LogicValue.one)); + }); + + test('should return correct value when overflow from int to Big Int.', + () async { + const widthLength = 64; + final a = Logic(name: 'a', width: widthLength)..put(1 << widthLength - 1); + final b = Logic(name: 'b', width: widthLength)..put(1 << widthLength - 1); + final carryIn = Logic(name: 'carry_in')..put(1); + + final rippleCarryAdder = RippleCarryAdder(a, b, carryIn: carryIn); + await rippleCarryAdder.build(); + + expect(rippleCarryAdder.sum.value.toBigInt(), + (BigInt.one << a.width) + BigInt.one); + expect(rippleCarryAdder.sum.value.width, a.width + 1); + expect(rippleCarryAdder.sum.value[widthLength], equals(LogicValue.one)); + }); + }); }