From 81431655c10fc915cb24a46f839e4a7f9878dc39 Mon Sep 17 00:00:00 2001 From: Max Korbel Date: Mon, 26 Aug 2024 14:31:32 -0700 Subject: [PATCH 01/43] initial implementation of counter, not fully tested yet --- lib/rohd_hcl.dart | 1 + lib/src/counter.dart | 248 +++++++++++++++++++++++++++++++++++++++++ test/counter_test.dart | 25 +++++ 3 files changed, 274 insertions(+) create mode 100644 lib/src/counter.dart create mode 100644 test/counter_test.dart diff --git a/lib/rohd_hcl.dart b/lib/rohd_hcl.dart index 40df82852..b9b39a482 100644 --- a/lib/rohd_hcl.dart +++ b/lib/rohd_hcl.dart @@ -7,6 +7,7 @@ export 'src/binary_gray.dart'; export 'src/carry_save_mutiplier.dart'; export 'src/component_config/component_config.dart'; export 'src/count.dart'; +export 'src/counter.dart'; export 'src/edge_detector.dart'; export 'src/encodings/encodings.dart'; export 'src/error_checking/error_checking.dart'; diff --git a/lib/src/counter.dart b/lib/src/counter.dart new file mode 100644 index 000000000..73ac3f13b --- /dev/null +++ b/lib/src/counter.dart @@ -0,0 +1,248 @@ +// Copyright (C) 2024 Intel Corporation +// SPDX-License-Identifier: BSD-3-Clause +// +// counter.dart +// A flexible counter implementation. +// +// 2024 August 26 +// Author: Max Korbel + +import 'package:collection/collection.dart'; +import 'package:rohd/rohd.dart'; +import 'package:rohd_hcl/rohd_hcl.dart'; +import 'package:rohd_hcl/src/exceptions.dart'; +import 'package:rohd_hcl/src/parallel_prefix_operations.dart'; + +class CounterInterface extends PairInterface { + final bool hasEnable; + + /// The [amount] to increment/decrement by, depending on [increments]. + Logic get amount => port('amount'); + + /// Controls whether the associated [Counter] should increment or decrement + /// (based on [increments]) this cycle (active high). + /// + /// Present if [hasEnable] is `true`. + Logic? get enable => tryPort('enable'); + + final int width; + + /// If `true`, the counter will increment. If `false`, the counter will + /// decrement. + final bool increments; + + final dynamic fixedAmount; + + /// TODO + /// + /// If [width] is `null`, it can be inferred from [fixedAmount] if provided + /// with a type that contains width information (e.g. a [LogicValue]). There + /// must be enough information provided to determine the [width]. + /// + /// If a [fixedAmount] is provided, then [amount] will be tied to a [Const]. A + /// provided [fixedAmount] must be parseable by [LogicValue.of]. Note that the + /// [fixedAmount] will always be interpreted as a positive value truncated to + /// [width]. If no [fixedAmount] is provided, then [amount] will be a normal + /// [port] with [width] bits. + /// + /// If [hasEnable] is `true`, then an [enable] port will be added to the + /// interface. + CounterInterface( + {this.fixedAmount, + this.increments = true, + int? width = 1, + this.hasEnable = false}) + : width = width ?? LogicValue.ofInferWidth(fixedAmount).width { + setPorts([ + Port('amount', this.width), + if (hasEnable) Port('enable'), + ], [ + PairDirection.fromProvider + ]); + + if (fixedAmount != null) { + amount <= Const(fixedAmount, width: this.width); + } + } + + CounterInterface.clone(CounterInterface other) + : this( + fixedAmount: other.fixedAmount, + increments: other.increments, + width: other.width, + hasEnable: other.hasEnable, + ); + + List _combAdjustments(Logic Function(Logic) s, Logic nextVal) { + final conds = [ + if (increments) + nextVal.incr(s: s, val: amount.zeroExtend(nextVal.width)) + else + nextVal.decr(s: s, val: amount.zeroExtend(nextVal.width)), + ]; + + if (hasEnable) { + return [If(enable!, then: conds)]; + } else { + return conds; + } + } +} + +class Counter extends Module { + final int width; + + /// If `true`, the counter will saturate at the `maxValue` and `minValue`. If + /// `false`, the counter will wrap around (overflow/underflow) at the + /// `maxValue` and `minValue`. + final bool saturates; + + Logic get value => output('value'); + + /// TODO + /// + /// The [width] can be either explicitly provided or inferred from other + /// values such as a [maxValue], [minValue], or [resetValue] that contain + /// width information (e.g. a [LogicValue]), or by making it large enough to + /// fit [maxValue], or by inspecting widths of [interfaces]. There must be + /// enough information provided to determine the [width]. + /// + /// If no [maxValue] is provided, one will be inferred by the maximum that can + /// fit inside of the [width]. + Counter( + List interfaces, { + required Logic clk, + required Logic reset, + dynamic resetValue = 0, + dynamic maxValue, + dynamic minValue = 0, + int? width, + this.saturates = true, + }) : width = + _inferWidth([resetValue, maxValue, minValue], width, interfaces) { + //TODO: handle reset, max, min as Logic, not just static values + + clk = addInput('clk', clk); + reset = addInput('reset', reset); + + interfaces = interfaces + .map((e) => CounterInterface.clone(e)..connectIO(this, e)) + .toList(); + + addOutput('value', width: this.width); + + final resetValueLogic = _dynamicInputToLogic('resetValue', resetValue); + final minValueLogic = _dynamicInputToLogic('minValue', minValue); + final maxValueLogic = _dynamicInputToLogic( + 'maxValue', + maxValue ?? (1 << this.width), + ); + + // assume minValue is 0, maxValue is 2^width, for width safety calcs + final biggestNum = (1 << this.width) + + interfaces.where((e) => e.increments).map((e) => 1 << e.width).sum; + final mostNegNum = + interfaces.where((e) => !e.increments).map((e) => 1 << e.width).sum; + + // calculate the largest number that we could have in intermediate + final internalWidth = log2Ceil(biggestNum + mostNegNum); + + final zeroPoint = Logic(name: 'zeroPoint', width: internalWidth) + ..gets(Const(mostNegNum, width: internalWidth)); + + final nextVal = Logic(name: 'nextVal', width: internalWidth); + final currVal = Logic(name: 'currVal', width: internalWidth); + + final upperSaturation = maxValueLogic.zeroExtend(internalWidth) + zeroPoint; + final lowerSaturation = minValueLogic.zeroExtend(internalWidth) + zeroPoint; + + currVal <= + flop( + clk, + nextVal, + reset: reset, + resetValue: resetValueLogic.zeroExtend(internalWidth) + zeroPoint, + ); + + value <= (currVal - zeroPoint).getRange(0, this.width); + + Combinational.ssa((s) => [ + // initialize + s(nextVal) < currVal, + + // perform increments and decrements + ...interfaces.map((e) => e._combAdjustments(s, nextVal)).flattened, + + // handle saturation or over/underflow + if (saturates) + // saturation + If.block([ + Iff.s( + s(nextVal).gt(upperSaturation), + s(nextVal) < upperSaturation, + ), + ElseIf.s( + s(nextVal).lt(lowerSaturation), + s(nextVal) < lowerSaturation, + ) + ]) + else + // under/overflow + If.block([ + Iff.s( + s(nextVal).gt(upperSaturation), + s(nextVal) < s(nextVal) - upperSaturation + lowerSaturation, + ), + ElseIf.s( + s(nextVal).lt(lowerSaturation), + s(nextVal) < upperSaturation - (lowerSaturation - s(nextVal)), + ) + ]), + ]); + } + + //TODO doc + Logic _dynamicInputToLogic(String name, dynamic value) { + if (value is Logic) { + return addInput(name, value.zeroExtend(width), width: width); + } else { + return Const(value, width: width); + } + } + + //TODO doc + static int _inferWidth( + List values, int? width, List interfaces) { + if (width != null) { + return width; + } + + int? maxWidthFound; + + for (final value in values) { + int? inferredValWidth; + if (value is Logic) { + inferredValWidth = value.width; + } else if (value != null) { + inferredValWidth = LogicValue.ofInferWidth(value).width; + } + + if (inferredValWidth != null && + (maxWidthFound == null || inferredValWidth > maxWidthFound)) { + maxWidthFound = inferredValWidth; + } + } + + for (final interface in interfaces) { + if (interface.width > maxWidthFound!) { + maxWidthFound = interface.width; + } + } + + if (maxWidthFound == null) { + throw RohdHclException('Unabled to infer width.'); + } + + return maxWidthFound; + } +} diff --git a/test/counter_test.dart b/test/counter_test.dart new file mode 100644 index 000000000..10a74a169 --- /dev/null +++ b/test/counter_test.dart @@ -0,0 +1,25 @@ +// Copyright (C) 2024 Intel Corporation +// SPDX-License-Identifier: BSD-3-Clause +// +// counter_test.dart +// Tests for the counter. +// +// 2024 August 26 +// Author: Max Korbel + +import 'package:rohd/rohd.dart'; +import 'package:rohd_hcl/rohd_hcl.dart'; +import 'package:test/test.dart'; + +void main() { + test('basic counter', () async { + final clk = SimpleClockGenerator(10).clk; + final reset = Logic(); + final intf = CounterInterface(); + final counter = Counter([intf], clk: clk, reset: reset); + + await counter.build(); + + print(counter.generateSynth()); + }); +} From 91cd9d8fecac66336e37b1f5e82dc5674310b47d Mon Sep 17 00:00:00 2001 From: Max Korbel Date: Mon, 26 Aug 2024 15:32:47 -0700 Subject: [PATCH 02/43] testing and bug fixes --- lib/src/counter.dart | 62 ++++++++++++++++++++++++++++-------------- test/counter_test.dart | 42 ++++++++++++++++++++++++++-- 2 files changed, 82 insertions(+), 22 deletions(-) diff --git a/lib/src/counter.dart b/lib/src/counter.dart index 73ac3f13b..2960a128a 100644 --- a/lib/src/counter.dart +++ b/lib/src/counter.dart @@ -50,7 +50,7 @@ class CounterInterface extends PairInterface { CounterInterface( {this.fixedAmount, this.increments = true, - int? width = 1, + int? width, this.hasEnable = false}) : width = width ?? LogicValue.ofInferWidth(fixedAmount).width { setPorts([ @@ -117,7 +117,8 @@ class Counter extends Module { dynamic maxValue, dynamic minValue = 0, int? width, - this.saturates = true, + this.saturates = false, + super.name = 'counter', }) : width = _inferWidth([resetValue, maxValue, minValue], width, interfaces) { //TODO: handle reset, max, min as Logic, not just static values @@ -131,37 +132,53 @@ class Counter extends Module { addOutput('value', width: this.width); - final resetValueLogic = _dynamicInputToLogic('resetValue', resetValue); - final minValueLogic = _dynamicInputToLogic('minValue', minValue); - final maxValueLogic = _dynamicInputToLogic( - 'maxValue', - maxValue ?? (1 << this.width), - ); - // assume minValue is 0, maxValue is 2^width, for width safety calcs - final biggestNum = (1 << this.width) + - interfaces.where((e) => e.increments).map((e) => 1 << e.width).sum; - final mostNegNum = - interfaces.where((e) => !e.increments).map((e) => 1 << e.width).sum; + final maxPosMagnitude = _biggestVal(this.width) + + interfaces + .where((e) => e.increments) + .map((e) => _biggestVal(e.width)) + .sum; + final maxNegMagnitude = interfaces + .where((e) => !e.increments) + .map((e) => _biggestVal(e.width)) + .sum; // calculate the largest number that we could have in intermediate - final internalWidth = log2Ceil(biggestNum + mostNegNum); + final internalWidth = log2Ceil(maxPosMagnitude + maxNegMagnitude + 1); + + final resetValueLogic = _dynamicInputToLogic( + 'resetValue', + resetValue, + ).zeroExtend(internalWidth); + final minValueLogic = _dynamicInputToLogic( + 'minValue', + minValue, + ).zeroExtend(internalWidth); + final maxValueLogic = _dynamicInputToLogic( + 'maxValue', + maxValue ?? _biggestVal(this.width), + ).zeroExtend(internalWidth); + + final range = Logic(name: 'range', width: internalWidth) + ..gets(maxValueLogic - minValueLogic); final zeroPoint = Logic(name: 'zeroPoint', width: internalWidth) - ..gets(Const(mostNegNum, width: internalWidth)); + ..gets(Const(maxNegMagnitude, width: internalWidth)); final nextVal = Logic(name: 'nextVal', width: internalWidth); final currVal = Logic(name: 'currVal', width: internalWidth); - final upperSaturation = maxValueLogic.zeroExtend(internalWidth) + zeroPoint; - final lowerSaturation = minValueLogic.zeroExtend(internalWidth) + zeroPoint; + final upperSaturation = Logic(name: 'upperSaturation', width: internalWidth) + ..gets(maxValueLogic + zeroPoint); + final lowerSaturation = Logic(name: 'lowerSaturation', width: internalWidth) + ..gets(minValueLogic + zeroPoint); currVal <= flop( clk, nextVal, reset: reset, - resetValue: resetValueLogic.zeroExtend(internalWidth) + zeroPoint, + resetValue: resetValueLogic + zeroPoint, ); value <= (currVal - zeroPoint).getRange(0, this.width); @@ -191,11 +208,14 @@ class Counter extends Module { If.block([ Iff.s( s(nextVal).gt(upperSaturation), - s(nextVal) < s(nextVal) - upperSaturation + lowerSaturation, + // s(nextVal) < (s(nextVal) - upperSaturation + lowerSaturation), + s(nextVal) < + ((s(nextVal) - zeroPoint) % range + lowerSaturation), ), ElseIf.s( s(nextVal).lt(lowerSaturation), - s(nextVal) < upperSaturation - (lowerSaturation - s(nextVal)), + s(nextVal) < + (upperSaturation - ((zeroPoint - s(nextVal)) % range)), ) ]), ]); @@ -210,6 +230,8 @@ class Counter extends Module { } } + static int _biggestVal(int width) => (1 << width) - 1; + //TODO doc static int _inferWidth( List values, int? width, List interfaces) { diff --git a/test/counter_test.dart b/test/counter_test.dart index 10a74a169..403efafed 100644 --- a/test/counter_test.dart +++ b/test/counter_test.dart @@ -7,19 +7,57 @@ // 2024 August 26 // Author: Max Korbel +import 'dart:async'; + import 'package:rohd/rohd.dart'; import 'package:rohd_hcl/rohd_hcl.dart'; import 'package:test/test.dart'; void main() { - test('basic counter', () async { + test('basic 1-bit counter', () async { final clk = SimpleClockGenerator(10).clk; final reset = Logic(); - final intf = CounterInterface(); + final intf = CounterInterface(fixedAmount: 1); final counter = Counter([intf], clk: clk, reset: reset); await counter.build(); + WaveDumper(counter); print(counter.generateSynth()); + + Simulator.setMaxSimTime(1000); + unawaited(Simulator.run()); + + // little reset routine + reset.inject(0); + await clk.nextNegedge; + reset.inject(1); + await clk.nextNegedge; + await clk.nextNegedge; + reset.inject(0); + + // check initial value + expect(counter.value.value.toInt(), 0); + + // wait a cycle, see 1 + await clk.nextNegedge; + expect(counter.value.value.toInt(), 1); + + // wait a cycle, should overflow (1-bit counter), back to 0 + await clk.nextNegedge; + expect(counter.value.value.toInt(), 0); + + // wait a cycle, see 1 + await clk.nextNegedge; + expect(counter.value.value.toInt(), 1); + + await clk.nextNegedge; + await clk.nextNegedge; + await clk.nextNegedge; + await clk.nextNegedge; + + await Simulator.endSimulation(); }); + + //TODO: test modulo requirement -- if sum is >2x greater than saturation } From 1eecfd47e739d9aea2e9fc6ad36770f12d0f9ab7 Mon Sep 17 00:00:00 2001 From: Max Korbel Date: Mon, 26 Aug 2024 17:05:53 -0700 Subject: [PATCH 03/43] extract aggregator --- lib/src/aggregator.dart | 245 ++++++++++++++++++++++++++++++++++++++++ lib/src/counter.dart | 233 +++++--------------------------------- test/counter_test.dart | 15 ++- 3 files changed, 286 insertions(+), 207 deletions(-) create mode 100644 lib/src/aggregator.dart diff --git a/lib/src/aggregator.dart b/lib/src/aggregator.dart new file mode 100644 index 000000000..09485d870 --- /dev/null +++ b/lib/src/aggregator.dart @@ -0,0 +1,245 @@ +// Copyright (C) 2024 Intel Corporation +// SPDX-License-Identifier: BSD-3-Clause +// +// aggregator.dart +// A flexible aggregator implementation. +// +// 2024 August 26 +// Author: Max Korbel + +import 'package:collection/collection.dart'; +import 'package:rohd/rohd.dart'; +import 'package:rohd_hcl/rohd_hcl.dart'; +import 'package:rohd_hcl/src/exceptions.dart'; +import 'package:rohd_hcl/src/parallel_prefix_operations.dart'; + +class AggregatorInterface extends PairInterface { + final bool hasEnable; + + /// The [amount] to increment/decrement by, depending on [increments]. + Logic get amount => port('amount'); + + /// Controls whether it should increment or decrement (based on [increments]) + /// (active high). + /// + /// Present if [hasEnable] is `true`. + Logic? get enable => tryPort('enable'); + + final int width; + + /// If `true`, will increment. If `false`, will decrement. + final bool increments; + + final dynamic fixedAmount; + + /// TODO + /// + /// If [width] is `null`, it can be inferred from [fixedAmount] if provided + /// with a type that contains width information (e.g. a [LogicValue]). There + /// must be enough information provided to determine the [width]. + /// + /// If a [fixedAmount] is provided, then [amount] will be tied to a [Const]. A + /// provided [fixedAmount] must be parseable by [LogicValue.of]. Note that the + /// [fixedAmount] will always be interpreted as a positive value truncated to + /// [width]. If no [fixedAmount] is provided, then [amount] will be a normal + /// [port] with [width] bits. + /// + /// If [hasEnable] is `true`, then an [enable] port will be added to the + /// interface. + AggregatorInterface( + {this.fixedAmount, + this.increments = true, + int? width, + this.hasEnable = false}) + : width = width ?? LogicValue.ofInferWidth(fixedAmount).width { + setPorts([ + Port('amount', this.width), + if (hasEnable) Port('enable'), + ], [ + PairDirection.fromProvider + ]); + + if (fixedAmount != null) { + amount <= Const(fixedAmount, width: this.width); + } + } + + AggregatorInterface.clone(AggregatorInterface other) + : this( + fixedAmount: other.fixedAmount, + increments: other.increments, + width: other.width, + hasEnable: other.hasEnable, + ); + + List _combAdjustments(Logic Function(Logic) s, Logic nextVal) { + final conds = [ + if (increments) + nextVal.incr(s: s, val: amount.zeroExtend(nextVal.width)) + else + nextVal.decr(s: s, val: amount.zeroExtend(nextVal.width)), + ]; + + if (hasEnable) { + return [If(enable!, then: conds)]; + } else { + return conds; + } + } +} + +class Aggregator extends Module with DynamicInputToLogic { + final int width; + + /// If `true`, will saturate at the `maxValue` and `minValue`. If `false`, + /// will wrap around (overflow/underflow) at the `maxValue` and `minValue`. + final bool saturates; + + Logic get value => output('value'); + + /// TODO + /// + /// The [width] can be either explicitly provided or inferred from other + /// values such as a [maxValue], [minValue], or [initialValue] that contain + /// width information (e.g. a [LogicValue]), or by making it large enough to + /// fit [maxValue], or by inspecting widths of [interfaces]. There must be + /// enough information provided to determine the [width]. + /// + /// If no [maxValue] is provided, one will be inferred by the maximum that can + /// fit inside of the [width]. + Aggregator( + List interfaces, { + dynamic initialValue = 0, + dynamic maxValue, + dynamic minValue = 0, + int? width, + this.saturates = false, + super.name = 'aggregator', + }) : width = + inferWidth([initialValue, maxValue, minValue], width, interfaces) { + interfaces = interfaces + .map((e) => AggregatorInterface.clone(e)..connectIO(this, e)) + .toList(); + + addOutput('value', width: this.width); + + // assume minValue is 0, maxValue is 2^width, for width safety calcs + final maxPosMagnitude = _biggestVal(this.width) + + interfaces + .where((e) => e.increments) + .map((e) => _biggestVal(e.width)) + .sum; + final maxNegMagnitude = interfaces + .where((e) => !e.increments) + .map((e) => _biggestVal(e.width)) + .sum; + + // calculate the largest number that we could have in intermediate + final internalWidth = log2Ceil(maxPosMagnitude + maxNegMagnitude + 1); + + final initialValueLogic = dynamicInputToLogic( + 'initialValue', + initialValue, + ).zeroExtend(internalWidth); + final minValueLogic = dynamicInputToLogic( + 'minValue', + minValue, + ).zeroExtend(internalWidth); + final maxValueLogic = dynamicInputToLogic( + 'maxValue', + maxValue ?? _biggestVal(this.width), + ).zeroExtend(internalWidth); + + final range = Logic(name: 'range', width: internalWidth) + ..gets(maxValueLogic - minValueLogic); + + final zeroPoint = Logic(name: 'zeroPoint', width: internalWidth) + ..gets(Const(maxNegMagnitude, width: internalWidth)); + + final upperSaturation = Logic(name: 'upperSaturation', width: internalWidth) + ..gets(maxValueLogic + zeroPoint); + final lowerSaturation = Logic(name: 'lowerSaturation', width: internalWidth) + ..gets(minValueLogic + zeroPoint); + + final internalValue = Logic(name: 'internalValue', width: internalWidth); + value <= internalValue.getRange(0, this.width); + + Combinational.ssa((s) => [ + // initialize + s(internalValue) < initialValueLogic, + + // perform increments and decrements + ...interfaces + .map((e) => e._combAdjustments(s, internalValue)) + .flattened, + + // handle saturation or over/underflow + if (saturates) + // saturation + If.block([ + Iff.s( + s(internalValue).gt(upperSaturation), + s(internalValue) < upperSaturation, + ), + ElseIf.s( + s(internalValue).lt(lowerSaturation), + s(internalValue) < lowerSaturation, + ) + ]) + else + // under/overflow + If.block([ + Iff.s( + s(internalValue).gt(upperSaturation), + s(internalValue) < + ((s(internalValue) - zeroPoint) % range + lowerSaturation), + ), + ElseIf.s( + s(internalValue).lt(lowerSaturation), + s(internalValue) < + (upperSaturation - + ((zeroPoint - s(internalValue)) % range)), + ) + ]), + ]); + } + + static int _biggestVal(int width) => (1 << width) - 1; +} + +//TODO doc +//TODO: hide this somehow +int inferWidth( + List values, int? width, List interfaces) { + if (width != null) { + return width; + } + + int? maxWidthFound; + + for (final value in values) { + int? inferredValWidth; + if (value is Logic) { + inferredValWidth = value.width; + } else if (value != null) { + inferredValWidth = LogicValue.ofInferWidth(value).width; + } + + if (inferredValWidth != null && + (maxWidthFound == null || inferredValWidth > maxWidthFound)) { + maxWidthFound = inferredValWidth; + } + } + + for (final interface in interfaces) { + if (interface.width > maxWidthFound!) { + maxWidthFound = interface.width; + } + } + + if (maxWidthFound == null) { + throw RohdHclException('Unabled to infer width.'); + } + + return maxWidthFound; +} diff --git a/lib/src/counter.dart b/lib/src/counter.dart index 2960a128a..5eeffdb8a 100644 --- a/lib/src/counter.dart +++ b/lib/src/counter.dart @@ -8,88 +8,14 @@ // Author: Max Korbel import 'package:collection/collection.dart'; +import 'package:meta/meta.dart'; import 'package:rohd/rohd.dart'; import 'package:rohd_hcl/rohd_hcl.dart'; +import 'package:rohd_hcl/src/aggregator.dart'; import 'package:rohd_hcl/src/exceptions.dart'; import 'package:rohd_hcl/src/parallel_prefix_operations.dart'; -class CounterInterface extends PairInterface { - final bool hasEnable; - - /// The [amount] to increment/decrement by, depending on [increments]. - Logic get amount => port('amount'); - - /// Controls whether the associated [Counter] should increment or decrement - /// (based on [increments]) this cycle (active high). - /// - /// Present if [hasEnable] is `true`. - Logic? get enable => tryPort('enable'); - - final int width; - - /// If `true`, the counter will increment. If `false`, the counter will - /// decrement. - final bool increments; - - final dynamic fixedAmount; - - /// TODO - /// - /// If [width] is `null`, it can be inferred from [fixedAmount] if provided - /// with a type that contains width information (e.g. a [LogicValue]). There - /// must be enough information provided to determine the [width]. - /// - /// If a [fixedAmount] is provided, then [amount] will be tied to a [Const]. A - /// provided [fixedAmount] must be parseable by [LogicValue.of]. Note that the - /// [fixedAmount] will always be interpreted as a positive value truncated to - /// [width]. If no [fixedAmount] is provided, then [amount] will be a normal - /// [port] with [width] bits. - /// - /// If [hasEnable] is `true`, then an [enable] port will be added to the - /// interface. - CounterInterface( - {this.fixedAmount, - this.increments = true, - int? width, - this.hasEnable = false}) - : width = width ?? LogicValue.ofInferWidth(fixedAmount).width { - setPorts([ - Port('amount', this.width), - if (hasEnable) Port('enable'), - ], [ - PairDirection.fromProvider - ]); - - if (fixedAmount != null) { - amount <= Const(fixedAmount, width: this.width); - } - } - - CounterInterface.clone(CounterInterface other) - : this( - fixedAmount: other.fixedAmount, - increments: other.increments, - width: other.width, - hasEnable: other.hasEnable, - ); - - List _combAdjustments(Logic Function(Logic) s, Logic nextVal) { - final conds = [ - if (increments) - nextVal.incr(s: s, val: amount.zeroExtend(nextVal.width)) - else - nextVal.decr(s: s, val: amount.zeroExtend(nextVal.width)), - ]; - - if (hasEnable) { - return [If(enable!, then: conds)]; - } else { - return conds; - } - } -} - -class Counter extends Module { +class Counter extends Module with DynamicInputToLogic { final int width; /// If `true`, the counter will saturate at the `maxValue` and `minValue`. If @@ -110,7 +36,7 @@ class Counter extends Module { /// If no [maxValue] is provided, one will be inferred by the maximum that can /// fit inside of the [width]. Counter( - List interfaces, { + List interfaces, { required Logic clk, required Logic reset, dynamic resetValue = 0, @@ -119,152 +45,51 @@ class Counter extends Module { int? width, this.saturates = false, super.name = 'counter', - }) : width = - _inferWidth([resetValue, maxValue, minValue], width, interfaces) { - //TODO: handle reset, max, min as Logic, not just static values - + }) : width = inferWidth([resetValue, maxValue, minValue], width, interfaces) { clk = addInput('clk', clk); reset = addInput('reset', reset); - interfaces = interfaces - .map((e) => CounterInterface.clone(e)..connectIO(this, e)) - .toList(); - addOutput('value', width: this.width); - // assume minValue is 0, maxValue is 2^width, for width safety calcs - final maxPosMagnitude = _biggestVal(this.width) + - interfaces - .where((e) => e.increments) - .map((e) => _biggestVal(e.width)) - .sum; - final maxNegMagnitude = interfaces - .where((e) => !e.increments) - .map((e) => _biggestVal(e.width)) - .sum; + interfaces = interfaces + .map((e) => AggregatorInterface.clone(e)..connectIO(this, e)) + .toList(); - // calculate the largest number that we could have in intermediate - final internalWidth = log2Ceil(maxPosMagnitude + maxNegMagnitude + 1); + final agg = Aggregator( + interfaces, + initialValue: value, + maxValue: maxValue, + minValue: minValue, + width: this.width, + saturates: saturates, + ); - final resetValueLogic = _dynamicInputToLogic( + final resetValueLogic = dynamicInputToLogic( 'resetValue', resetValue, - ).zeroExtend(internalWidth); - final minValueLogic = _dynamicInputToLogic( - 'minValue', - minValue, - ).zeroExtend(internalWidth); - final maxValueLogic = _dynamicInputToLogic( - 'maxValue', - maxValue ?? _biggestVal(this.width), - ).zeroExtend(internalWidth); - - final range = Logic(name: 'range', width: internalWidth) - ..gets(maxValueLogic - minValueLogic); - - final zeroPoint = Logic(name: 'zeroPoint', width: internalWidth) - ..gets(Const(maxNegMagnitude, width: internalWidth)); - - final nextVal = Logic(name: 'nextVal', width: internalWidth); - final currVal = Logic(name: 'currVal', width: internalWidth); - - final upperSaturation = Logic(name: 'upperSaturation', width: internalWidth) - ..gets(maxValueLogic + zeroPoint); - final lowerSaturation = Logic(name: 'lowerSaturation', width: internalWidth) - ..gets(minValueLogic + zeroPoint); + ); - currVal <= + value <= flop( clk, - nextVal, + agg.value, reset: reset, - resetValue: resetValueLogic + zeroPoint, + resetValue: resetValueLogic, ); - - value <= (currVal - zeroPoint).getRange(0, this.width); - - Combinational.ssa((s) => [ - // initialize - s(nextVal) < currVal, - - // perform increments and decrements - ...interfaces.map((e) => e._combAdjustments(s, nextVal)).flattened, - - // handle saturation or over/underflow - if (saturates) - // saturation - If.block([ - Iff.s( - s(nextVal).gt(upperSaturation), - s(nextVal) < upperSaturation, - ), - ElseIf.s( - s(nextVal).lt(lowerSaturation), - s(nextVal) < lowerSaturation, - ) - ]) - else - // under/overflow - If.block([ - Iff.s( - s(nextVal).gt(upperSaturation), - // s(nextVal) < (s(nextVal) - upperSaturation + lowerSaturation), - s(nextVal) < - ((s(nextVal) - zeroPoint) % range + lowerSaturation), - ), - ElseIf.s( - s(nextVal).lt(lowerSaturation), - s(nextVal) < - (upperSaturation - ((zeroPoint - s(nextVal)) % range)), - ) - ]), - ]); } +} + +//TODO doc +//TODO: is this ok? move it somewhere else? +mixin DynamicInputToLogic on Module { + int get width; - //TODO doc - Logic _dynamicInputToLogic(String name, dynamic value) { + @protected + Logic dynamicInputToLogic(String name, dynamic value) { if (value is Logic) { return addInput(name, value.zeroExtend(width), width: width); } else { return Const(value, width: width); } } - - static int _biggestVal(int width) => (1 << width) - 1; - - //TODO doc - static int _inferWidth( - List values, int? width, List interfaces) { - if (width != null) { - return width; - } - - int? maxWidthFound; - - for (final value in values) { - int? inferredValWidth; - if (value is Logic) { - inferredValWidth = value.width; - } else if (value != null) { - inferredValWidth = LogicValue.ofInferWidth(value).width; - } - - if (inferredValWidth != null && - (maxWidthFound == null || inferredValWidth > maxWidthFound)) { - maxWidthFound = inferredValWidth; - } - } - - for (final interface in interfaces) { - if (interface.width > maxWidthFound!) { - maxWidthFound = interface.width; - } - } - - if (maxWidthFound == null) { - throw RohdHclException('Unabled to infer width.'); - } - - return maxWidthFound; - } } diff --git a/test/counter_test.dart b/test/counter_test.dart index 403efafed..b13cd0661 100644 --- a/test/counter_test.dart +++ b/test/counter_test.dart @@ -14,15 +14,14 @@ import 'package:rohd_hcl/rohd_hcl.dart'; import 'package:test/test.dart'; void main() { - test('basic 1-bit counter', () async { + test('basic 1-bit rolling counter', () async { final clk = SimpleClockGenerator(10).clk; final reset = Logic(); - final intf = CounterInterface(fixedAmount: 1); + final intf = AggregatorInterface(fixedAmount: 1); final counter = Counter([intf], clk: clk, reset: reset); await counter.build(); - WaveDumper(counter); print(counter.generateSynth()); Simulator.setMaxSimTime(1000); @@ -59,5 +58,15 @@ void main() { await Simulator.endSimulation(); }); + // TODO: test plan: + // - 4 bit counter overflow roll + // - 4 bit down-counter underflow roll + // - 4 bit counter with upper saturation + // - 4 bit down-counter with lower saturation + // - for each of them + // - with/out variable amount + // - with/out enable + // - weird reset value + //TODO: test modulo requirement -- if sum is >2x greater than saturation } From 502a6baf682222d1cc72c7db0e3b041e693e1179 Mon Sep 17 00:00:00 2001 From: Max Korbel Date: Tue, 27 Aug 2024 08:55:09 -0700 Subject: [PATCH 04/43] minor updates, add restart --- lib/rohd_hcl.dart | 1 + lib/src/aggregator.dart | 2 ++ lib/src/counter.dart | 22 ++++++++++++++++------ 3 files changed, 19 insertions(+), 6 deletions(-) diff --git a/lib/rohd_hcl.dart b/lib/rohd_hcl.dart index b9b39a482..33572a826 100644 --- a/lib/rohd_hcl.dart +++ b/lib/rohd_hcl.dart @@ -2,6 +2,7 @@ // SPDX-License-Identifier: BSD-3-Clause export 'src/adder.dart'; +export 'src/aggregator.dart'; export 'src/arbiters/arbiters.dart'; export 'src/binary_gray.dart'; export 'src/carry_save_mutiplier.dart'; diff --git a/lib/src/aggregator.dart b/lib/src/aggregator.dart index 09485d870..9c6e0d436 100644 --- a/lib/src/aggregator.dart +++ b/lib/src/aggregator.dart @@ -95,6 +95,8 @@ class Aggregator extends Module with DynamicInputToLogic { /// will wrap around (overflow/underflow) at the `maxValue` and `minValue`. final bool saturates; + //TODO: add some sort if "saturated" or "minimum" outputs? + Logic get value => output('value'); /// TODO diff --git a/lib/src/counter.dart b/lib/src/counter.dart index 5eeffdb8a..640add58c 100644 --- a/lib/src/counter.dart +++ b/lib/src/counter.dart @@ -35,6 +35,10 @@ class Counter extends Module with DynamicInputToLogic { /// /// If no [maxValue] is provided, one will be inferred by the maximum that can /// fit inside of the [width]. + /// + /// The [restart] input can be used to restart the counter to a new value, but + /// also continue to increment in that same cycle. This is distinct from + /// [reset] which will reset the counter, holding the [value] at [resetValue]. Counter( List interfaces, { required Logic clk, @@ -44,31 +48,37 @@ class Counter extends Module with DynamicInputToLogic { dynamic minValue = 0, int? width, this.saturates = false, + Logic? restart, super.name = 'counter', }) : width = inferWidth([resetValue, maxValue, minValue], width, interfaces) { clk = addInput('clk', clk); reset = addInput('reset', reset); + if (restart != null) { + restart = addInput('reInit', restart); + } + addOutput('value', width: this.width); interfaces = interfaces .map((e) => AggregatorInterface.clone(e)..connectIO(this, e)) .toList(); + final resetValueLogic = dynamicInputToLogic( + 'resetValue', + resetValue, + ); + final agg = Aggregator( interfaces, - initialValue: value, + initialValue: + restart != null ? mux(restart, resetValueLogic, value) : value, maxValue: maxValue, minValue: minValue, width: this.width, saturates: saturates, ); - final resetValueLogic = dynamicInputToLogic( - 'resetValue', - resetValue, - ); - value <= flop( clk, From c10fe2028b93379b868e1b703bd50fd3e76bae17 Mon Sep 17 00:00:00 2001 From: Max Korbel Date: Tue, 27 Aug 2024 10:43:22 -0700 Subject: [PATCH 05/43] rename some stuff --- lib/src/aggregator.dart | 21 ++++++++++++--------- lib/src/counter.dart | 9 +++++---- test/counter_test.dart | 2 +- 3 files changed, 18 insertions(+), 14 deletions(-) diff --git a/lib/src/aggregator.dart b/lib/src/aggregator.dart index 9c6e0d436..086acc2f9 100644 --- a/lib/src/aggregator.dart +++ b/lib/src/aggregator.dart @@ -13,7 +13,7 @@ import 'package:rohd_hcl/rohd_hcl.dart'; import 'package:rohd_hcl/src/exceptions.dart'; import 'package:rohd_hcl/src/parallel_prefix_operations.dart'; -class AggregatorInterface extends PairInterface { +class SumInterface extends PairInterface { final bool hasEnable; /// The [amount] to increment/decrement by, depending on [increments]. @@ -46,7 +46,7 @@ class AggregatorInterface extends PairInterface { /// /// If [hasEnable] is `true`, then an [enable] port will be added to the /// interface. - AggregatorInterface( + SumInterface( {this.fixedAmount, this.increments = true, int? width, @@ -64,7 +64,7 @@ class AggregatorInterface extends PairInterface { } } - AggregatorInterface.clone(AggregatorInterface other) + SumInterface.clone(SumInterface other) : this( fixedAmount: other.fixedAmount, increments: other.increments, @@ -88,7 +88,7 @@ class AggregatorInterface extends PairInterface { } } -class Aggregator extends Module with DynamicInputToLogic { +class Sum extends Module with DynamicInputToLogic { final int width; /// If `true`, will saturate at the `maxValue` and `minValue`. If `false`, @@ -99,6 +99,8 @@ class Aggregator extends Module with DynamicInputToLogic { Logic get value => output('value'); + // late final Logic isMax = addOutput('isMax')..gets(value.eq()); + /// TODO /// /// The [width] can be either explicitly provided or inferred from other @@ -109,8 +111,8 @@ class Aggregator extends Module with DynamicInputToLogic { /// /// If no [maxValue] is provided, one will be inferred by the maximum that can /// fit inside of the [width]. - Aggregator( - List interfaces, { + Sum( + List interfaces, { dynamic initialValue = 0, dynamic maxValue, dynamic minValue = 0, @@ -120,7 +122,8 @@ class Aggregator extends Module with DynamicInputToLogic { }) : width = inferWidth([initialValue, maxValue, minValue], width, interfaces) { interfaces = interfaces - .map((e) => AggregatorInterface.clone(e)..connectIO(this, e)) + .map((e) => + SumInterface.clone(e)..pairConnectIO(this, e, PairRole.consumer)) .toList(); addOutput('value', width: this.width); @@ -161,7 +164,7 @@ class Aggregator extends Module with DynamicInputToLogic { final upperSaturation = Logic(name: 'upperSaturation', width: internalWidth) ..gets(maxValueLogic + zeroPoint); final lowerSaturation = Logic(name: 'lowerSaturation', width: internalWidth) - ..gets(minValueLogic + zeroPoint); + ..gets(minValueLogic + zeroPoint); //TODO: is this right? final internalValue = Logic(name: 'internalValue', width: internalWidth); value <= internalValue.getRange(0, this.width); @@ -212,7 +215,7 @@ class Aggregator extends Module with DynamicInputToLogic { //TODO doc //TODO: hide this somehow int inferWidth( - List values, int? width, List interfaces) { + List values, int? width, List interfaces) { if (width != null) { return width; } diff --git a/lib/src/counter.dart b/lib/src/counter.dart index 640add58c..0b1bb288c 100644 --- a/lib/src/counter.dart +++ b/lib/src/counter.dart @@ -40,7 +40,7 @@ class Counter extends Module with DynamicInputToLogic { /// also continue to increment in that same cycle. This is distinct from /// [reset] which will reset the counter, holding the [value] at [resetValue]. Counter( - List interfaces, { + List interfaces, { required Logic clk, required Logic reset, dynamic resetValue = 0, @@ -61,7 +61,8 @@ class Counter extends Module with DynamicInputToLogic { addOutput('value', width: this.width); interfaces = interfaces - .map((e) => AggregatorInterface.clone(e)..connectIO(this, e)) + .map((e) => + SumInterface.clone(e)..pairConnectIO(this, e, PairRole.consumer)) .toList(); final resetValueLogic = dynamicInputToLogic( @@ -69,7 +70,7 @@ class Counter extends Module with DynamicInputToLogic { resetValue, ); - final agg = Aggregator( + final sum = Sum( interfaces, initialValue: restart != null ? mux(restart, resetValueLogic, value) : value, @@ -82,7 +83,7 @@ class Counter extends Module with DynamicInputToLogic { value <= flop( clk, - agg.value, + sum.value, reset: reset, resetValue: resetValueLogic, ); diff --git a/test/counter_test.dart b/test/counter_test.dart index b13cd0661..225db7a1f 100644 --- a/test/counter_test.dart +++ b/test/counter_test.dart @@ -17,7 +17,7 @@ void main() { test('basic 1-bit rolling counter', () async { final clk = SimpleClockGenerator(10).clk; final reset = Logic(); - final intf = AggregatorInterface(fixedAmount: 1); + final intf = SumInterface(fixedAmount: 1); final counter = Counter([intf], clk: clk, reset: reset); await counter.build(); From 3b668aa4710c4aafdf129887b51008eaae02b035 Mon Sep 17 00:00:00 2001 From: Max Korbel Date: Tue, 27 Aug 2024 11:45:32 -0700 Subject: [PATCH 06/43] refactor to sum, add reached ports --- lib/src/counter.dart | 17 +++++++ lib/src/{aggregator.dart => sum.dart} | 67 +++++++++++++++------------ 2 files changed, 55 insertions(+), 29 deletions(-) rename lib/src/{aggregator.dart => sum.dart} (81%) diff --git a/lib/src/counter.dart b/lib/src/counter.dart index 0b1bb288c..5d4ec82c6 100644 --- a/lib/src/counter.dart +++ b/lib/src/counter.dart @@ -25,6 +25,20 @@ class Counter extends Module with DynamicInputToLogic { Logic get value => output('value'); + /// Indicates whether the sum has reached the maximum value. + /// + /// If it [saturates], then [value] will be equal to the maximum value. + /// Otherwise, the value may have overflowed to any value, but the net sum + /// before overflow will have been greater than the maximum value. + Logic get reachedMax => output('reachedMax'); + + /// Indicates whether the sum has reached the minimum value. + /// + /// If it [saturates], then [value] will be equal to the minimum value. + /// Otherwise, the value may have underflowed to any value, but the net sum + /// before underflow will have been less than the minimum value. + Logic get reachedMin => output('reachedMin'); + /// TODO /// /// The [width] can be either explicitly provided or inferred from other @@ -87,6 +101,9 @@ class Counter extends Module with DynamicInputToLogic { reset: reset, resetValue: resetValueLogic, ); + + addOutput('reachedMax') <= sum.reachedMax; + addOutput('reachedMin') <= sum.reachedMin; } } diff --git a/lib/src/aggregator.dart b/lib/src/sum.dart similarity index 81% rename from lib/src/aggregator.dart rename to lib/src/sum.dart index 086acc2f9..d9ef4d8a2 100644 --- a/lib/src/aggregator.dart +++ b/lib/src/sum.dart @@ -99,7 +99,19 @@ class Sum extends Module with DynamicInputToLogic { Logic get value => output('value'); - // late final Logic isMax = addOutput('isMax')..gets(value.eq()); + /// Indicates whether the sum has reached the maximum value. + /// + /// If it [saturates], then [value] will be equal to the maximum value. + /// Otherwise, the value may have overflowed to any value, but the net sum + /// before overflow will have been greater than the maximum value. + Logic get reachedMax => output('reachedMax'); + + /// Indicates whether the sum has reached the minimum value. + /// + /// If it [saturates], then [value] will be equal to the minimum value. + /// Otherwise, the value may have underflowed to any value, but the net sum + /// before underflow will have been less than the minimum value. + Logic get reachedMin => output('reachedMin'); /// TODO /// @@ -118,7 +130,7 @@ class Sum extends Module with DynamicInputToLogic { dynamic minValue = 0, int? width, this.saturates = false, - super.name = 'aggregator', + super.name = 'sum', }) : width = inferWidth([initialValue, maxValue, minValue], width, interfaces) { interfaces = interfaces @@ -127,6 +139,8 @@ class Sum extends Module with DynamicInputToLogic { .toList(); addOutput('value', width: this.width); + addOutput('reachedMax'); + addOutput('reachedMin'); // assume minValue is 0, maxValue is 2^width, for width safety calcs final maxPosMagnitude = _biggestVal(this.width) + @@ -178,34 +192,29 @@ class Sum extends Module with DynamicInputToLogic { .map((e) => e._combAdjustments(s, internalValue)) .flattened, + // identify if we're at a max/min case + reachedMax < s(internalValue).gte(upperSaturation), + reachedMin < s(internalValue).lte(lowerSaturation), + // handle saturation or over/underflow - if (saturates) - // saturation - If.block([ - Iff.s( - s(internalValue).gt(upperSaturation), - s(internalValue) < upperSaturation, - ), - ElseIf.s( - s(internalValue).lt(lowerSaturation), - s(internalValue) < lowerSaturation, - ) - ]) - else - // under/overflow - If.block([ - Iff.s( - s(internalValue).gt(upperSaturation), - s(internalValue) < - ((s(internalValue) - zeroPoint) % range + lowerSaturation), - ), - ElseIf.s( - s(internalValue).lt(lowerSaturation), - s(internalValue) < - (upperSaturation - - ((zeroPoint - s(internalValue)) % range)), - ) - ]), + If.block([ + Iff.s( + reachedMax, + s(internalValue) < + (saturates + ? upperSaturation + : ((s(internalValue) - zeroPoint) % range + + lowerSaturation)), + ), + ElseIf.s( + reachedMin, + s(internalValue) < + (saturates + ? lowerSaturation + : (upperSaturation - + ((zeroPoint - s(internalValue)) % range))), + ) + ]), ]); } From b15409733ef8e806dfe7ec0c0670f73865b69a39 Mon Sep 17 00:00:00 2001 From: Max Korbel Date: Tue, 27 Aug 2024 11:46:27 -0700 Subject: [PATCH 07/43] add forgotten files --- lib/rohd_hcl.dart | 2 +- lib/src/counter.dart | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/rohd_hcl.dart b/lib/rohd_hcl.dart index 33572a826..c45e7f325 100644 --- a/lib/rohd_hcl.dart +++ b/lib/rohd_hcl.dart @@ -2,7 +2,7 @@ // SPDX-License-Identifier: BSD-3-Clause export 'src/adder.dart'; -export 'src/aggregator.dart'; +export 'src/sum.dart'; export 'src/arbiters/arbiters.dart'; export 'src/binary_gray.dart'; export 'src/carry_save_mutiplier.dart'; diff --git a/lib/src/counter.dart b/lib/src/counter.dart index 5d4ec82c6..67c109fc1 100644 --- a/lib/src/counter.dart +++ b/lib/src/counter.dart @@ -11,7 +11,7 @@ import 'package:collection/collection.dart'; import 'package:meta/meta.dart'; import 'package:rohd/rohd.dart'; import 'package:rohd_hcl/rohd_hcl.dart'; -import 'package:rohd_hcl/src/aggregator.dart'; +import 'package:rohd_hcl/src/sum.dart'; import 'package:rohd_hcl/src/exceptions.dart'; import 'package:rohd_hcl/src/parallel_prefix_operations.dart'; From 558d99a6e9cfa7df059789f17ce3634b87e0ec56 Mon Sep 17 00:00:00 2001 From: Max Korbel Date: Tue, 27 Aug 2024 12:07:34 -0700 Subject: [PATCH 08/43] add ofLogics --- lib/src/counter.dart | 27 +++++++++++++++++++++++++++ lib/src/sum.dart | 20 ++++++++++++++++++++ 2 files changed, 47 insertions(+) diff --git a/lib/src/counter.dart b/lib/src/counter.dart index 67c109fc1..be00d545d 100644 --- a/lib/src/counter.dart +++ b/lib/src/counter.dart @@ -39,6 +39,33 @@ class Counter extends Module with DynamicInputToLogic { /// before underflow will have been less than the minimum value. Logic get reachedMin => output('reachedMin'); + factory Counter.ofLogics( + List logics, { + required Logic clk, + required Logic reset, + dynamic resetValue = 0, + dynamic maxValue, + dynamic minValue = 0, + int? width, + bool saturates = false, + Logic? restart, + String name = 'counter', + }) => + Counter( + logics + .map((e) => SumInterface(width: e.width)..amount.gets(e)) + .toList(), + clk: clk, + reset: reset, + resetValue: resetValue, + maxValue: maxValue, + minValue: minValue, + width: width, + saturates: saturates, + restart: restart, + name: name, + ); + /// TODO /// /// The [width] can be either explicitly provided or inferred from other diff --git a/lib/src/sum.dart b/lib/src/sum.dart index d9ef4d8a2..66fa6d218 100644 --- a/lib/src/sum.dart +++ b/lib/src/sum.dart @@ -113,6 +113,26 @@ class Sum extends Module with DynamicInputToLogic { /// before underflow will have been less than the minimum value. Logic get reachedMin => output('reachedMin'); + factory Sum.ofLogics( + List logics, { + dynamic initialValue = 0, + dynamic maxValue, + dynamic minValue = 0, + int? width, + bool saturates = false, + String name = 'sum', + }) => + Sum( + logics + .map((e) => SumInterface(width: e.width)..amount.gets(e)) + .toList(), + initialValue: initialValue, + maxValue: maxValue, + minValue: minValue, + width: width, + saturates: saturates, + name: name); + /// TODO /// /// The [width] can be either explicitly provided or inferred from other From e0d895de92a5e9b819b1114763130076fa65f233 Mon Sep 17 00:00:00 2001 From: Max Korbel Date: Tue, 27 Aug 2024 12:33:43 -0700 Subject: [PATCH 09/43] fix overflow bug, start sum testing --- lib/src/sum.dart | 26 +++++++++++----- test/sum_test.dart | 78 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 97 insertions(+), 7 deletions(-) create mode 100644 test/sum_test.dart diff --git a/lib/src/sum.dart b/lib/src/sum.dart index 66fa6d218..63819780f 100644 --- a/lib/src/sum.dart +++ b/lib/src/sum.dart @@ -1,8 +1,8 @@ // Copyright (C) 2024 Intel Corporation // SPDX-License-Identifier: BSD-3-Clause // -// aggregator.dart -// A flexible aggregator implementation. +// sum.dart +// A flexible sum implementation. // // 2024 August 26 // Author: Max Korbel @@ -113,6 +113,9 @@ class Sum extends Module with DynamicInputToLogic { /// before underflow will have been less than the minimum value. Logic get reachedMin => output('reachedMin'); + /// TODO + /// + /// All [logics]s are always enabled and incrementing. factory Sum.ofLogics( List logics, { dynamic initialValue = 0, @@ -143,6 +146,9 @@ class Sum extends Module with DynamicInputToLogic { /// /// If no [maxValue] is provided, one will be inferred by the maximum that can /// fit inside of the [width]. + /// + /// It is expected that [maxValue] is at least [minValue], or else results may + /// be unpredictable. Sum( List interfaces, { dynamic initialValue = 0, @@ -189,7 +195,8 @@ class Sum extends Module with DynamicInputToLogic { maxValue ?? _biggestVal(this.width), ).zeroExtend(internalWidth); - final range = Logic(name: 'range', width: internalWidth) + // lazy range so that it's not generated if not necessary + late final range = Logic(name: 'range', width: internalWidth) ..gets(maxValueLogic - minValueLogic); final zeroPoint = Logic(name: 'zeroPoint', width: internalWidth) @@ -203,6 +210,9 @@ class Sum extends Module with DynamicInputToLogic { final internalValue = Logic(name: 'internalValue', width: internalWidth); value <= internalValue.getRange(0, this.width); + final passedMax = Logic(name: 'passedMax'); + final passedMin = Logic(name: 'passedMin'); + Combinational.ssa((s) => [ // initialize s(internalValue) < initialValueLogic, @@ -213,13 +223,15 @@ class Sum extends Module with DynamicInputToLogic { .flattened, // identify if we're at a max/min case - reachedMax < s(internalValue).gte(upperSaturation), - reachedMin < s(internalValue).lte(lowerSaturation), + passedMax < s(internalValue).gt(upperSaturation), + passedMin < s(internalValue).lt(lowerSaturation), + reachedMax < passedMax | s(internalValue).eq(upperSaturation), + reachedMin < passedMin | s(internalValue).eq(lowerSaturation), // handle saturation or over/underflow If.block([ Iff.s( - reachedMax, + passedMax, s(internalValue) < (saturates ? upperSaturation @@ -227,7 +239,7 @@ class Sum extends Module with DynamicInputToLogic { lowerSaturation)), ), ElseIf.s( - reachedMin, + passedMin, s(internalValue) < (saturates ? lowerSaturation diff --git a/test/sum_test.dart b/test/sum_test.dart new file mode 100644 index 000000000..2f634d525 --- /dev/null +++ b/test/sum_test.dart @@ -0,0 +1,78 @@ +// Copyright (C) 2024 Intel Corporation +// SPDX-License-Identifier: BSD-3-Clause +// +// sum_test.dart +// Tests for sum. +// +// 2024 August 27 +// Author: Max Korbel + +import 'package:rohd/rohd.dart'; +import 'package:rohd_hcl/rohd_hcl.dart'; +import 'package:test/test.dart'; + +int goldenSumOfLogics( + List logics, { + required int width, + bool saturates = false, + int? maxVal, + int minVal = 0, +}) => + goldenSum( + logics.map((e) => SumInterface(width: e.width)..amount.gets(e)).toList(), + width: width, + saturates: saturates, + minVal: minVal, + maxVal: maxVal, + ); + +int goldenSum( + List interfaces, { + required int width, + bool saturates = false, + int? maxVal, + int minVal = 0, +}) { + var sum = 0; + maxVal ??= 1 << width - 1; + for (final intf in interfaces) { + if (!intf.hasEnable || intf.enable!.value.toBool()) { + final amount = intf.amount.value.toInt(); + if (intf.increments) { + sum += amount; + } else { + sum -= amount; + } + } + } + + if (saturates) { + if (sum > maxVal) { + sum = maxVal; + } else if (sum < minVal) { + sum = minVal; + } + } else { + final range = maxVal - minVal; + if (sum > maxVal) { + sum = sum % range + minVal; + } else if (sum < minVal) { + sum = maxVal - sum % range; + } + } + + return sum; +} + +void main() { + test('simple sum of 1', () async { + final logics = [Const(1)]; + final dut = Sum.ofLogics(logics); + await dut.build(); + expect(dut.value.value.toInt(), 1); + expect(dut.width, 1); + expect(goldenSumOfLogics(logics, width: dut.width), 1); + }); + + test('sweep', () {}); +} From bb13be8e6959936689208fc360bc15355545f1d3 Mon Sep 17 00:00:00 2001 From: Max Korbel Date: Tue, 27 Aug 2024 12:51:43 -0700 Subject: [PATCH 10/43] fix intf bug --- lib/src/sum.dart | 9 +++------ test/sum_test.dart | 23 ++++++++++++++++++++++- 2 files changed, 25 insertions(+), 7 deletions(-) diff --git a/lib/src/sum.dart b/lib/src/sum.dart index 63819780f..a7790e030 100644 --- a/lib/src/sum.dart +++ b/lib/src/sum.dart @@ -17,7 +17,8 @@ class SumInterface extends PairInterface { final bool hasEnable; /// The [amount] to increment/decrement by, depending on [increments]. - Logic get amount => port('amount'); + late final Logic amount = + fixedAmount != null ? Const(fixedAmount, width: width) : port('amount'); /// Controls whether it should increment or decrement (based on [increments]) /// (active high). @@ -53,15 +54,11 @@ class SumInterface extends PairInterface { this.hasEnable = false}) : width = width ?? LogicValue.ofInferWidth(fixedAmount).width { setPorts([ - Port('amount', this.width), + if (fixedAmount != null) Port('amount', this.width), if (hasEnable) Port('enable'), ], [ PairDirection.fromProvider ]); - - if (fixedAmount != null) { - amount <= Const(fixedAmount, width: this.width); - } } SumInterface.clone(SumInterface other) diff --git a/test/sum_test.dart b/test/sum_test.dart index 2f634d525..e69dd84ff 100644 --- a/test/sum_test.dart +++ b/test/sum_test.dart @@ -7,6 +7,8 @@ // 2024 August 27 // Author: Max Korbel +import 'dart:math'; + import 'package:rohd/rohd.dart'; import 'package:rohd_hcl/rohd_hcl.dart'; import 'package:test/test.dart'; @@ -74,5 +76,24 @@ void main() { expect(goldenSumOfLogics(logics, width: dut.width), 1); }); - test('sweep', () {}); + group('random', () { + final rand = Random(123); + + SumInterface genRandomInterface() { + final isFixed = rand.nextBool(); + return SumInterface( + fixedAmount: isFixed ? rand.nextInt(100) : null, + width: isFixed ? null : rand.nextInt(8), + increments: rand.nextBool(), + hasEnable: rand.nextBool(), + ); + } + + List genRandomInterfaces() { + final numInterfaces = rand.nextInt(8); + return List.generate(numInterfaces, (_) => genRandomInterface()); + } + + void testSum({required int numIncr, required int numDecr}) {} + }); } From 870d4f5dbf25acbf6c65a4375dac813c1e66b82b Mon Sep 17 00:00:00 2001 From: Max Korbel Date: Tue, 27 Aug 2024 14:33:58 -0700 Subject: [PATCH 11/43] fix bug in overflow wrap-around --- lib/src/counter.dart | 9 +++--- lib/src/sum.dart | 11 ++++--- test/sum_test.dart | 73 +++++++++++++++++++++++++++++++++++++++++--- 3 files changed, 80 insertions(+), 13 deletions(-) diff --git a/lib/src/counter.dart b/lib/src/counter.dart index be00d545d..58cda4741 100644 --- a/lib/src/counter.dart +++ b/lib/src/counter.dart @@ -96,14 +96,15 @@ class Counter extends Module with DynamicInputToLogic { reset = addInput('reset', reset); if (restart != null) { - restart = addInput('reInit', restart); + restart = addInput('restart', restart); } addOutput('value', width: this.width); interfaces = interfaces - .map((e) => - SumInterface.clone(e)..pairConnectIO(this, e, PairRole.consumer)) + .mapIndexed((i, e) => SumInterface.clone(e) + ..pairConnectIO(this, e, PairRole.consumer, + uniquify: (original) => '${original}_$i')) .toList(); final resetValueLogic = dynamicInputToLogic( @@ -144,7 +145,7 @@ mixin DynamicInputToLogic on Module { if (value is Logic) { return addInput(name, value.zeroExtend(width), width: width); } else { - return Const(value, width: width); + return Logic(name: name, width: width)..gets(Const(value, width: width)); } } } diff --git a/lib/src/sum.dart b/lib/src/sum.dart index a7790e030..b784d4908 100644 --- a/lib/src/sum.dart +++ b/lib/src/sum.dart @@ -54,7 +54,7 @@ class SumInterface extends PairInterface { this.hasEnable = false}) : width = width ?? LogicValue.ofInferWidth(fixedAmount).width { setPorts([ - if (fixedAmount != null) Port('amount', this.width), + if (fixedAmount == null) Port('amount', this.width), if (hasEnable) Port('enable'), ], [ PairDirection.fromProvider @@ -157,8 +157,9 @@ class Sum extends Module with DynamicInputToLogic { }) : width = inferWidth([initialValue, maxValue, minValue], width, interfaces) { interfaces = interfaces - .map((e) => - SumInterface.clone(e)..pairConnectIO(this, e, PairRole.consumer)) + .mapIndexed((i, e) => SumInterface.clone(e) + ..pairConnectIO(this, e, PairRole.consumer, + uniquify: (original) => '${original}_$i')) .toList(); addOutput('value', width: this.width); @@ -194,7 +195,7 @@ class Sum extends Module with DynamicInputToLogic { // lazy range so that it's not generated if not necessary late final range = Logic(name: 'range', width: internalWidth) - ..gets(maxValueLogic - minValueLogic); + ..gets(maxValueLogic - minValueLogic + 1); final zeroPoint = Logic(name: 'zeroPoint', width: internalWidth) ..gets(Const(maxNegMagnitude, width: internalWidth)); @@ -202,7 +203,7 @@ class Sum extends Module with DynamicInputToLogic { final upperSaturation = Logic(name: 'upperSaturation', width: internalWidth) ..gets(maxValueLogic + zeroPoint); final lowerSaturation = Logic(name: 'lowerSaturation', width: internalWidth) - ..gets(minValueLogic + zeroPoint); //TODO: is this right? + ..gets(minValueLogic + zeroPoint); final internalValue = Logic(name: 'internalValue', width: internalWidth); value <= internalValue.getRange(0, this.width); diff --git a/test/sum_test.dart b/test/sum_test.dart index e69dd84ff..221f683e2 100644 --- a/test/sum_test.dart +++ b/test/sum_test.dart @@ -33,10 +33,11 @@ int goldenSum( required int width, bool saturates = false, int? maxVal, - int minVal = 0, + int? minVal, }) { var sum = 0; maxVal ??= 1 << width - 1; + minVal ??= 0; for (final intf in interfaces) { if (!intf.hasEnable || intf.enable!.value.toBool()) { final amount = intf.amount.value.toInt(); @@ -67,7 +68,7 @@ int goldenSum( } void main() { - test('simple sum of 1', () async { + test('simple sum of 1 ofLogics', () async { final logics = [Const(1)]; final dut = Sum.ofLogics(logics); await dut.build(); @@ -76,7 +77,36 @@ void main() { expect(goldenSumOfLogics(logics, width: dut.width), 1); }); - group('random', () { + test('simple sum of two numbers', () async { + final a = Logic(width: 4); + final b = Logic(width: 4); + final dut = Sum.ofLogics([a, b]); + await dut.build(); + expect(dut.width, 4); + + // fits + a.put(3); + b.put(5); + expect(dut.value.value.toInt(), 8); + + a.put(7); + b.put(1); + expect(dut.value.value.toInt(), 8); + + // barely overflow + a.put(7); + b.put(9); + expect(dut.value.value.toInt(), 0); + + // overflow + a.put(8); + b.put(10); + expect(dut.value.value.toInt(), 2); + }); + + // TODO: testing with overridden width + + test('random', () { final rand = Random(123); SumInterface genRandomInterface() { @@ -94,6 +124,41 @@ void main() { return List.generate(numInterfaces, (_) => genRandomInterface()); } - void testSum({required int numIncr, required int numDecr}) {} + //TODO: set max number of rand iterations + for (var i = 0; i < 1; i++) { + final interfaces = genRandomInterfaces(); + + final saturates = rand.nextBool(); + final minVal = rand.nextBool() ? rand.nextInt(30) : null; + final maxVal = rand.nextBool() ? rand.nextInt(70) + (minVal ?? 0) : null; + final initialValue = rand.nextInt(maxVal ?? 100); + + for (final intf in interfaces) { + if (intf.hasEnable) { + intf.enable!.put(rand.nextBool()); + } + + if (intf.fixedAmount != null) { + intf.amount.put(rand.nextInt(1 << intf.width)); + } + } + + final dut = Sum(interfaces, + saturates: saturates, + maxValue: maxVal, + minValue: minVal, + initialValue: initialValue); + + expect( + dut.value.value.toInt(), + goldenSum( + interfaces, + width: dut.width, + saturates: saturates, + maxVal: maxVal, + minVal: minVal, + ), + ); + } }); } From 09048bc76ab93e71c96c33001b932a5987b7427d Mon Sep 17 00:00:00 2001 From: Max Korbel Date: Tue, 27 Aug 2024 14:44:34 -0700 Subject: [PATCH 12/43] refactor sum test --- test/sum_test.dart | 67 +++++++++++++++++++++++++++------------------- 1 file changed, 40 insertions(+), 27 deletions(-) diff --git a/test/sum_test.dart b/test/sum_test.dart index 221f683e2..495fa7379 100644 --- a/test/sum_test.dart +++ b/test/sum_test.dart @@ -36,7 +36,7 @@ int goldenSum( int? minVal, }) { var sum = 0; - maxVal ??= 1 << width - 1; + maxVal ??= (1 << width) - 1; minVal ??= 0; for (final intf in interfaces) { if (!intf.hasEnable || intf.enable!.value.toBool()) { @@ -56,7 +56,7 @@ int goldenSum( sum = minVal; } } else { - final range = maxVal - minVal; + final range = maxVal - minVal + 1; if (sum > maxVal) { sum = sum % range + minVal; } else if (sum < minVal) { @@ -77,33 +77,45 @@ void main() { expect(goldenSumOfLogics(logics, width: dut.width), 1); }); - test('simple sum of two numbers', () async { - final a = Logic(width: 4); - final b = Logic(width: 4); - final dut = Sum.ofLogics([a, b]); - await dut.build(); - expect(dut.width, 4); - - // fits - a.put(3); - b.put(5); - expect(dut.value.value.toInt(), 8); - - a.put(7); - b.put(1); - expect(dut.value.value.toInt(), 8); - - // barely overflow - a.put(7); - b.put(9); - expect(dut.value.value.toInt(), 0); - - // overflow - a.put(8); - b.put(10); - expect(dut.value.value.toInt(), 2); + group('simple sum of two numbers', () { + // ignore: avoid_positional_boolean_parameters + Future simpleSumOfTwo(bool saturates) async { + final a = Logic(width: 4); + final b = Logic(width: 4); + final dut = Sum.ofLogics([a, b], saturates: saturates); + await dut.build(); + expect(dut.width, 4); + + for (final pair in [ + // fits + (3, 5), + (7, 1), + // barely fits + (10, 5), + // barely overflows + (7, 9), + // overflows + (8, 10), + ]) { + a.put(pair.$1); + b.put(pair.$2); + final expected = + goldenSumOfLogics([a, b], width: dut.width, saturates: saturates); + expect(dut.value.value.toInt(), expected); + } + } + + test('overflow', () async { + await simpleSumOfTwo(false); + }); + + test('saturate', () async { + await simpleSumOfTwo(true); + }); }); + test('simple decrement of 2 numbers underflow', () {}); + // TODO: testing with overridden width test('random', () { @@ -147,6 +159,7 @@ void main() { saturates: saturates, maxValue: maxVal, minValue: minVal, + width: rand.nextBool() ? null : rand.nextInt(8), initialValue: initialValue); expect( From 29f0c1a29dd79f20a0e21f04bf92a8a9da58d0dc Mon Sep 17 00:00:00 2001 From: Max Korbel Date: Tue, 27 Aug 2024 15:45:47 -0700 Subject: [PATCH 13/43] fixing up tests --- test/sum_test.dart | 85 ++++++++++++++++++++++++++-------------------- 1 file changed, 49 insertions(+), 36 deletions(-) diff --git a/test/sum_test.dart b/test/sum_test.dart index 495fa7379..f88079d72 100644 --- a/test/sum_test.dart +++ b/test/sum_test.dart @@ -34,8 +34,9 @@ int goldenSum( bool saturates = false, int? maxVal, int? minVal, + int initialValue = 0, }) { - var sum = 0; + var sum = initialValue; maxVal ??= (1 << width) - 1; minVal ??= 0; for (final intf in interfaces) { @@ -77,45 +78,56 @@ void main() { expect(goldenSumOfLogics(logics, width: dut.width), 1); }); - group('simple sum of two numbers', () { - // ignore: avoid_positional_boolean_parameters - Future simpleSumOfTwo(bool saturates) async { - final a = Logic(width: 4); - final b = Logic(width: 4); - final dut = Sum.ofLogics([a, b], saturates: saturates); - await dut.build(); - expect(dut.width, 4); - - for (final pair in [ - // fits - (3, 5), - (7, 1), - // barely fits - (10, 5), - // barely overflows - (7, 9), - // overflows - (8, 10), - ]) { - a.put(pair.$1); - b.put(pair.$2); - final expected = - goldenSumOfLogics([a, b], width: dut.width, saturates: saturates); - expect(dut.value.value.toInt(), expected); + group('simple 2 numbers', () { + final pairs = [ + // fits + (3, 5), + (7, 1), + + // barely fits + (10, 5), + + // barely overflows + (7, 9), + + // overflows + (8, 10), + ]; + + for (final increments in [true, false]) { + final initialValue = increments ? 0 : 15; + for (final saturates in [true, false]) { + test('increments=$increments, saturate=$saturates', () async { + final a = Logic(width: 4); + final b = Logic(width: 4); + final intfs = [a, b] + .map((e) => SumInterface( + width: e.width, + increments: increments, + )..amount.gets(e)) + .toList(); + final dut = + Sum(intfs, saturates: saturates, initialValue: initialValue); + + await dut.build(); + expect(dut.width, 4); + + for (final pair in pairs) { + a.put(pair.$1); + b.put(pair.$2); + final expected = goldenSum( + intfs, + width: dut.width, + saturates: saturates, + initialValue: initialValue, + ); + expect(dut.value.value.toInt(), expected); + } + }); } } - - test('overflow', () async { - await simpleSumOfTwo(false); - }); - - test('saturate', () async { - await simpleSumOfTwo(true); - }); }); - test('simple decrement of 2 numbers underflow', () {}); - // TODO: testing with overridden width test('random', () { @@ -170,6 +182,7 @@ void main() { saturates: saturates, maxVal: maxVal, minVal: minVal, + initialValue: initialValue, ), ); } From 6cd33b656cfceb99337114423972017b4f46609a Mon Sep 17 00:00:00 2001 From: Max Korbel Date: Tue, 27 Aug 2024 18:05:49 -0700 Subject: [PATCH 14/43] fix init value bug --- lib/src/sum.dart | 4 ++-- test/sum_test.dart | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/src/sum.dart b/lib/src/sum.dart index b784d4908..cabdf3001 100644 --- a/lib/src/sum.dart +++ b/lib/src/sum.dart @@ -206,14 +206,14 @@ class Sum extends Module with DynamicInputToLogic { ..gets(minValueLogic + zeroPoint); final internalValue = Logic(name: 'internalValue', width: internalWidth); - value <= internalValue.getRange(0, this.width); + value <= (internalValue - zeroPoint).getRange(0, this.width); final passedMax = Logic(name: 'passedMax'); final passedMin = Logic(name: 'passedMin'); Combinational.ssa((s) => [ // initialize - s(internalValue) < initialValueLogic, + s(internalValue) < initialValueLogic + zeroPoint, // perform increments and decrements ...interfaces diff --git a/test/sum_test.dart b/test/sum_test.dart index f88079d72..8adb4ef99 100644 --- a/test/sum_test.dart +++ b/test/sum_test.dart @@ -110,6 +110,7 @@ void main() { Sum(intfs, saturates: saturates, initialValue: initialValue); await dut.build(); + expect(dut.width, 4); for (final pair in pairs) { From ae13aacefbff3f2fd97c5ee61d9c5cc75e97bbd8 Mon Sep 17 00:00:00 2001 From: Max Korbel Date: Tue, 27 Aug 2024 18:21:24 -0700 Subject: [PATCH 15/43] fix off by one on underflow --- lib/src/sum.dart | 3 ++- test/sum_test.dart | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/src/sum.dart b/lib/src/sum.dart index cabdf3001..710746296 100644 --- a/lib/src/sum.dart +++ b/lib/src/sum.dart @@ -241,7 +241,8 @@ class Sum extends Module with DynamicInputToLogic { s(internalValue) < (saturates ? lowerSaturation - : (upperSaturation - + : (upperSaturation + + 1 - // TODO: why +1? ((zeroPoint - s(internalValue)) % range))), ) ]), diff --git a/test/sum_test.dart b/test/sum_test.dart index 8adb4ef99..79f0f9e7a 100644 --- a/test/sum_test.dart +++ b/test/sum_test.dart @@ -61,7 +61,8 @@ int goldenSum( if (sum > maxVal) { sum = sum % range + minVal; } else if (sum < minVal) { - sum = maxVal - sum % range; + // same thing for pos or neg since we can do negative modulo + sum = sum % range + minVal; } } From 3533a9c223ce194f7fa4844c125ffe53cbc894e8 Mon Sep 17 00:00:00 2001 From: Max Korbel Date: Tue, 27 Aug 2024 18:23:43 -0700 Subject: [PATCH 16/43] one rand test passing --- test/sum_test.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/sum_test.dart b/test/sum_test.dart index 79f0f9e7a..4de43a015 100644 --- a/test/sum_test.dart +++ b/test/sum_test.dart @@ -155,7 +155,7 @@ void main() { final interfaces = genRandomInterfaces(); final saturates = rand.nextBool(); - final minVal = rand.nextBool() ? rand.nextInt(30) : null; + final minVal = rand.nextBool() ? rand.nextInt(30) : 0; final maxVal = rand.nextBool() ? rand.nextInt(70) + (minVal ?? 0) : null; final initialValue = rand.nextInt(maxVal ?? 100); @@ -164,7 +164,7 @@ void main() { intf.enable!.put(rand.nextBool()); } - if (intf.fixedAmount != null) { + if (intf.fixedAmount == null) { intf.amount.put(rand.nextInt(1 << intf.width)); } } From 17fb84ed54c8b76586db5160b361b476f8df6354 Mon Sep 17 00:00:00 2001 From: Max Korbel Date: Tue, 27 Aug 2024 19:02:48 -0700 Subject: [PATCH 17/43] more testing --- lib/src/sum.dart | 2 ++ test/sum_test.dart | 43 ++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 44 insertions(+), 1 deletion(-) diff --git a/lib/src/sum.dart b/lib/src/sum.dart index 710746296..7507779af 100644 --- a/lib/src/sum.dart +++ b/lib/src/sum.dart @@ -184,10 +184,12 @@ class Sum extends Module with DynamicInputToLogic { 'initialValue', initialValue, ).zeroExtend(internalWidth); + final minValueLogic = dynamicInputToLogic( 'minValue', minValue, ).zeroExtend(internalWidth); + final maxValueLogic = dynamicInputToLogic( 'maxValue', maxValue ?? _biggestVal(this.width), diff --git a/test/sum_test.dart b/test/sum_test.dart index 4de43a015..18da58030 100644 --- a/test/sum_test.dart +++ b/test/sum_test.dart @@ -38,7 +38,12 @@ int goldenSum( }) { var sum = initialValue; maxVal ??= (1 << width) - 1; + if (maxVal > (1 << width) - 1) { + // ignore: parameter_assignments + maxVal = (1 << width) - 1; + } minVal ??= 0; + for (final intf in interfaces) { if (!intf.hasEnable || intf.enable!.value.toBool()) { final amount = intf.amount.value.toInt(); @@ -130,6 +135,42 @@ void main() { } }); + test('small width, big increment', () { + final a = Logic(width: 4); + final b = Logic(width: 4); + final intfs = [a, b] + .map((e) => SumInterface( + width: e.width, + )..amount.gets(e)) + .toList(); + final dut = Sum( + intfs, + width: 2, + maxValue: 5, + ); + + expect(dut.width, 2); + + a.put(3); + b.put(2); + expect(dut.value.value.toInt(), 1); + expect(goldenSum(intfs, width: dut.width, maxVal: 5), 1); + }); + + test('one up, one down', () { + final intfs = [ + SumInterface(fixedAmount: 3), + SumInterface(fixedAmount: 2, increments: false), + ]; + final dut = Sum(intfs, saturates: true, initialValue: 5, width: 7); + + expect(dut.width, 7); + + expect(dut.value.value.toInt(), 6); + expect(dut.value.value.toInt(), + goldenSum(intfs, width: dut.width, saturates: true, initialValue: 5)); + }); + // TODO: testing with overridden width test('random', () { @@ -173,7 +214,7 @@ void main() { saturates: saturates, maxValue: maxVal, minValue: minVal, - width: rand.nextBool() ? null : rand.nextInt(8), + width: rand.nextBool() ? null : rand.nextInt(10), initialValue: initialValue); expect( From 5d49822a9d978e497948379ad61700e612c2b4b8 Mon Sep 17 00:00:00 2001 From: Max Korbel Date: Wed, 28 Aug 2024 09:37:51 -0700 Subject: [PATCH 18/43] fixing bugs in golden model and design for sum --- lib/src/sum.dart | 22 ++++++-- test/sum_test.dart | 129 ++++++++++++++++++++++++++++++++++++--------- 2 files changed, 122 insertions(+), 29 deletions(-) diff --git a/lib/src/sum.dart b/lib/src/sum.dart index 7507779af..87c637b06 100644 --- a/lib/src/sum.dart +++ b/lib/src/sum.dart @@ -7,6 +7,8 @@ // 2024 August 26 // Author: Max Korbel +import 'dart:math'; + import 'package:collection/collection.dart'; import 'package:rohd/rohd.dart'; import 'package:rohd_hcl/rohd_hcl.dart'; @@ -156,6 +158,10 @@ class Sum extends Module with DynamicInputToLogic { super.name = 'sum', }) : width = inferWidth([initialValue, maxValue, minValue], width, interfaces) { + if (interfaces.isEmpty) { + throw RohdHclException('At least one interface must be provided.'); + } + interfaces = interfaces .mapIndexed((i, e) => SumInterface.clone(e) ..pairConnectIO(this, e, PairRole.consumer, @@ -173,9 +179,13 @@ class Sum extends Module with DynamicInputToLogic { .map((e) => _biggestVal(e.width)) .sum; final maxNegMagnitude = interfaces - .where((e) => !e.increments) - .map((e) => _biggestVal(e.width)) - .sum; + .where((e) => !e.increments) + .map((e) => _biggestVal(e.width)) + .sum + + // also consider that initialValue may be less than min + (initialValue is Logic + ? _biggestVal(initialValue.width) + : LogicValue.ofInferWidth(initialValue).toInt()); // calculate the largest number that we could have in intermediate final internalWidth = log2Ceil(maxPosMagnitude + maxNegMagnitude + 1); @@ -259,6 +269,10 @@ class Sum extends Module with DynamicInputToLogic { int inferWidth( List values, int? width, List interfaces) { if (width != null) { + if (width <= 0) { + throw RohdHclException('Width must be greater than 0.'); + } + return width; } @@ -288,5 +302,5 @@ int inferWidth( throw RohdHclException('Unabled to infer width.'); } - return maxWidthFound; + return max(1, maxWidthFound); } diff --git a/test/sum_test.dart b/test/sum_test.dart index 18da58030..4f29dce0d 100644 --- a/test/sum_test.dart +++ b/test/sum_test.dart @@ -35,8 +35,21 @@ int goldenSum( int? maxVal, int? minVal, int initialValue = 0, + bool debug = false, }) { + void log(String message) { + if (debug) { + // ignore: avoid_print + print(message); + } + } + + log('width: $width'); + var sum = initialValue; + + log('min $minVal -> max $maxVal'); + maxVal ??= (1 << width) - 1; if (maxVal > (1 << width) - 1) { // ignore: parameter_assignments @@ -44,9 +57,22 @@ int goldenSum( } minVal ??= 0; + log('min $minVal -> max $maxVal [adjusted]'); + + if (minVal > maxVal) { + throw Exception('minVal must be less than or equal to maxVal'); + } + + log('init: $initialValue'); + for (final intf in interfaces) { - if (!intf.hasEnable || intf.enable!.value.toBool()) { - final amount = intf.amount.value.toInt(); + final amount = intf.amount.value.toInt(); + final enabled = !intf.hasEnable || intf.enable!.value.toBool(); + + log('${intf.increments ? '+' : '-'}' + '$amount${enabled ? '' : ' [disabled]'}'); + + if (enabled) { if (intf.increments) { sum += amount; } else { @@ -55,20 +81,23 @@ int goldenSum( } } + log('=$sum'); + if (saturates) { if (sum > maxVal) { sum = maxVal; } else if (sum < minVal) { sum = minVal; } + log('saturates to $sum'); } else { final range = maxVal - minVal + 1; if (sum > maxVal) { sum = sum % range + minVal; } else if (sum < minVal) { - // same thing for pos or neg since we can do negative modulo - sum = sum % range + minVal; + sum = maxVal - (minVal - sum) % range + 1; } + log('rolls-over to $sum'); } return sum; @@ -171,7 +200,41 @@ void main() { goldenSum(intfs, width: dut.width, saturates: true, initialValue: 5)); }); - // TODO: testing with overridden width + test('init less than min', () { + final intfs = [ + SumInterface(fixedAmount: 2), + ]; + final dut = Sum(intfs, initialValue: 13, minValue: 16, maxValue: 31); + + final actual = dut.value.value.toInt(); + final expected = goldenSum( + intfs, + width: dut.width, + minVal: 16, + maxVal: 31, + initialValue: 13, + ); + expect(actual, 31); + expect(actual, expected); + }); + + test('init more than max', () { + final intfs = [ + SumInterface(fixedAmount: 2, increments: false), + ]; + final dut = Sum(intfs, initialValue: 34, minValue: 16, maxValue: 31); + + final actual = dut.value.value.toInt(); + final expected = goldenSum( + intfs, + width: dut.width, + minVal: 16, + maxVal: 31, + initialValue: 34, + ); + expect(actual, 16); + expect(actual, expected); + }); test('random', () { final rand = Random(123); @@ -187,18 +250,28 @@ void main() { } List genRandomInterfaces() { - final numInterfaces = rand.nextInt(8); + final numInterfaces = rand.nextInt(8) + 1; return List.generate(numInterfaces, (_) => genRandomInterface()); } //TODO: set max number of rand iterations - for (var i = 0; i < 1; i++) { + for (var i = 0; i < 100; i++) { final interfaces = genRandomInterfaces(); final saturates = rand.nextBool(); - final minVal = rand.nextBool() ? rand.nextInt(30) : 0; - final maxVal = rand.nextBool() ? rand.nextInt(70) + (minVal ?? 0) : null; - final initialValue = rand.nextInt(maxVal ?? 100); + var minVal = rand.nextBool() ? rand.nextInt(30) : 0; + final maxVal = rand.nextBool() ? rand.nextInt(70) + minVal : null; + final initialValue = rand.nextBool() ? rand.nextInt(maxVal ?? 100) : 0; + final width = rand.nextBool() ? null : rand.nextInt(10) + 1; + + if (maxVal == null || minVal >= maxVal) { + if (maxVal == null && width == null) { + minVal = 0; + } else { + minVal = + rand.nextInt(maxVal ?? (width == null ? 0 : (1 << width) - 1)); + } + } for (final intf in interfaces) { if (intf.hasEnable) { @@ -212,22 +285,28 @@ void main() { final dut = Sum(interfaces, saturates: saturates, - maxValue: maxVal, - minValue: minVal, - width: rand.nextBool() ? null : rand.nextInt(10), - initialValue: initialValue); - - expect( - dut.value.value.toInt(), - goldenSum( - interfaces, - width: dut.width, - saturates: saturates, - maxVal: maxVal, - minVal: minVal, - initialValue: initialValue, - ), + maxValue: maxVal != null && rand.nextBool() + ? Const(LogicValue.ofInferWidth(maxVal)) + : maxVal, + minValue: + rand.nextBool() ? Const(LogicValue.ofInferWidth(minVal)) : minVal, + width: width, + initialValue: rand.nextBool() + ? Const(LogicValue.ofInferWidth(initialValue)) + : initialValue); + + final actual = dut.value.value.toInt(); + final expected = goldenSum( + interfaces, + width: dut.width, + saturates: saturates, + maxVal: maxVal, + minVal: minVal, + initialValue: initialValue, + debug: true, ); + + expect(actual, expected); } }); } From de74777b58537d3695f15d6152b448afc37ca00f Mon Sep 17 00:00:00 2001 From: Max Korbel Date: Wed, 28 Aug 2024 11:06:46 -0700 Subject: [PATCH 19/43] fix some overflow/underflow bugs --- lib/src/counter.dart | 5 +++++ lib/src/sum.dart | 17 ++++++++++++----- test/sum_test.dart | 15 ++++++++++----- 3 files changed, 27 insertions(+), 10 deletions(-) diff --git a/lib/src/counter.dart b/lib/src/counter.dart index 58cda4741..4f61ed913 100644 --- a/lib/src/counter.dart +++ b/lib/src/counter.dart @@ -145,6 +145,11 @@ mixin DynamicInputToLogic on Module { if (value is Logic) { return addInput(name, value.zeroExtend(width), width: width); } else { + if (LogicValue.ofInferWidth(value).width > width) { + throw RohdHclException( + 'Value $value for $name is too large for width $width'); + } + return Logic(name: name, width: width)..gets(Const(value, width: width)); } } diff --git a/lib/src/sum.dart b/lib/src/sum.dart index 87c637b06..9f65e1d47 100644 --- a/lib/src/sum.dart +++ b/lib/src/sum.dart @@ -223,6 +223,9 @@ class Sum extends Module with DynamicInputToLogic { final passedMax = Logic(name: 'passedMax'); final passedMin = Logic(name: 'passedMin'); + final preAdjustmentValue = + Logic(name: 'preAdjustmentValue', width: internalWidth); + Combinational.ssa((s) => [ // initialize s(internalValue) < initialValueLogic + zeroPoint, @@ -238,6 +241,9 @@ class Sum extends Module with DynamicInputToLogic { reachedMax < passedMax | s(internalValue).eq(upperSaturation), reachedMin < passedMin | s(internalValue).eq(lowerSaturation), + // useful as an internal node for debug/visibility + preAdjustmentValue < s(internalValue), + // handle saturation or over/underflow If.block([ Iff.s( @@ -245,17 +251,18 @@ class Sum extends Module with DynamicInputToLogic { s(internalValue) < (saturates ? upperSaturation - : ((s(internalValue) - zeroPoint) % range + - lowerSaturation)), + : ((s(internalValue) - upperSaturation) % range + + lowerSaturation - + 1)), ), ElseIf.s( passedMin, s(internalValue) < (saturates ? lowerSaturation - : (upperSaturation + - 1 - // TODO: why +1? - ((zeroPoint - s(internalValue)) % range))), + : (upperSaturation - + ((lowerSaturation - s(internalValue)) % range) + + 1)), ) ]), ]); diff --git a/test/sum_test.dart b/test/sum_test.dart index 4f29dce0d..056f3f838 100644 --- a/test/sum_test.dart +++ b/test/sum_test.dart @@ -93,7 +93,7 @@ int goldenSum( } else { final range = maxVal - minVal + 1; if (sum > maxVal) { - sum = sum % range + minVal; + sum = (sum - maxVal) % range + minVal - 1; } else if (sum < minVal) { sum = maxVal - (minVal - sum) % range + 1; } @@ -164,7 +164,7 @@ void main() { } }); - test('small width, big increment', () { + test('small width, big increment', () async { final a = Logic(width: 4); final b = Logic(width: 4); final intfs = [a, b] @@ -175,15 +175,15 @@ void main() { final dut = Sum( intfs, width: 2, - maxValue: 5, ); + await dut.build(); expect(dut.width, 2); a.put(3); b.put(2); expect(dut.value.value.toInt(), 1); - expect(goldenSum(intfs, width: dut.width, maxVal: 5), 1); + expect(goldenSum(intfs, width: dut.width, maxVal: 5, debug: true), 1); }); test('one up, one down', () { @@ -260,10 +260,15 @@ void main() { final saturates = rand.nextBool(); var minVal = rand.nextBool() ? rand.nextInt(30) : 0; - final maxVal = rand.nextBool() ? rand.nextInt(70) + minVal : null; + var maxVal = rand.nextBool() ? rand.nextInt(70) + minVal : null; final initialValue = rand.nextBool() ? rand.nextInt(maxVal ?? 100) : 0; final width = rand.nextBool() ? null : rand.nextInt(10) + 1; + if (maxVal != null && width != null) { + // truncate to width + maxVal = LogicValue.ofInt(maxVal, width).toInt(); + } + if (maxVal == null || minVal >= maxVal) { if (maxVal == null && width == null) { minVal = 0; From 503708f6931241e857d50639fa011663ed0e86f4 Mon Sep 17 00:00:00 2001 From: Max Korbel Date: Wed, 28 Aug 2024 11:28:15 -0700 Subject: [PATCH 20/43] got random test passing 100 times --- lib/src/sum.dart | 13 ++++++++----- test/sum_test.dart | 42 +++++++++++++++++++++++++++++++----------- 2 files changed, 39 insertions(+), 16 deletions(-) diff --git a/lib/src/sum.dart b/lib/src/sum.dart index 9f65e1d47..7eb16c123 100644 --- a/lib/src/sum.dart +++ b/lib/src/sum.dart @@ -251,9 +251,8 @@ class Sum extends Module with DynamicInputToLogic { s(internalValue) < (saturates ? upperSaturation - : ((s(internalValue) - upperSaturation) % range + - lowerSaturation - - 1)), + : ((s(internalValue) - upperSaturation - 1) % range + + lowerSaturation)), ), ElseIf.s( passedMin, @@ -261,8 +260,7 @@ class Sum extends Module with DynamicInputToLogic { (saturates ? lowerSaturation : (upperSaturation - - ((lowerSaturation - s(internalValue)) % range) + - 1)), + ((lowerSaturation - s(internalValue) - 1) % range))), ) ]), ]); @@ -280,6 +278,11 @@ int inferWidth( throw RohdHclException('Width must be greater than 0.'); } + if (values.any((v) => v is Logic && v.width > width)) { + throw RohdHclException( + 'Width must be at least as large as the largest value.'); + } + return width; } diff --git a/test/sum_test.dart b/test/sum_test.dart index 056f3f838..f878f9f0c 100644 --- a/test/sum_test.dart +++ b/test/sum_test.dart @@ -93,9 +93,9 @@ int goldenSum( } else { final range = maxVal - minVal + 1; if (sum > maxVal) { - sum = (sum - maxVal) % range + minVal - 1; + sum = (sum - maxVal - 1) % range + minVal; } else if (sum < minVal) { - sum = maxVal - (minVal - sum) % range + 1; + sum = maxVal - (minVal - sum - 1) % range; } log('rolls-over to $sum'); } @@ -236,6 +236,8 @@ void main() { expect(actual, expected); }); + //TODO: test max == min + test('random', () { final rand = Random(123); @@ -258,15 +260,23 @@ void main() { for (var i = 0; i < 100; i++) { final interfaces = genRandomInterfaces(); + final width = rand.nextBool() ? null : rand.nextInt(10) + 1; + final saturates = rand.nextBool(); var minVal = rand.nextBool() ? rand.nextInt(30) : 0; - var maxVal = rand.nextBool() ? rand.nextInt(70) + minVal : null; - final initialValue = rand.nextBool() ? rand.nextInt(maxVal ?? 100) : 0; - final width = rand.nextBool() ? null : rand.nextInt(10) + 1; + var maxVal = rand.nextBool() + ? rand.nextInt(width == null ? 70 : ((1 << width) - 1)) + minVal + 1 + : null; + var initialValue = rand.nextBool() ? rand.nextInt(maxVal ?? 100) : 0; if (maxVal != null && width != null) { // truncate to width - maxVal = LogicValue.ofInt(maxVal, width).toInt(); + maxVal = max(1, LogicValue.ofInt(maxVal, width).toInt()); + } + + if (width != null) { + // truncate to width + initialValue = LogicValue.ofInt(initialValue, width).toInt(); } if (maxVal == null || minVal >= maxVal) { @@ -288,16 +298,27 @@ void main() { } } + int safeWidthFor(int val) { + final lv = LogicValue.ofInferWidth(val); + final inferredWidth = lv.width; + + return min(max(inferredWidth, 1), width ?? inferredWidth); + } + final dut = Sum(interfaces, saturates: saturates, maxValue: maxVal != null && rand.nextBool() - ? Const(LogicValue.ofInferWidth(maxVal)) + ? Const(LogicValue.ofInferWidth(maxVal), + width: safeWidthFor(maxVal)) : maxVal, - minValue: - rand.nextBool() ? Const(LogicValue.ofInferWidth(minVal)) : minVal, + minValue: rand.nextBool() + ? Const(LogicValue.ofInferWidth(minVal), + width: safeWidthFor(minVal)) + : minVal, width: width, initialValue: rand.nextBool() - ? Const(LogicValue.ofInferWidth(initialValue)) + ? Const(LogicValue.ofInferWidth(initialValue), + width: safeWidthFor(initialValue)) : initialValue); final actual = dut.value.value.toInt(); @@ -308,7 +329,6 @@ void main() { maxVal: maxVal, minVal: minVal, initialValue: initialValue, - debug: true, ); expect(actual, expected); From bf1e89aa2c9cd77667017a29548a078713cefdaa Mon Sep 17 00:00:00 2001 From: Max Korbel Date: Wed, 28 Aug 2024 11:32:08 -0700 Subject: [PATCH 21/43] add eq max/min test --- test/sum_test.dart | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/test/sum_test.dart b/test/sum_test.dart index f878f9f0c..f3e4315b9 100644 --- a/test/sum_test.dart +++ b/test/sum_test.dart @@ -236,7 +236,23 @@ void main() { expect(actual, expected); }); - //TODO: test max == min + test('min == max', () { + final intfs = [ + SumInterface(fixedAmount: 2, increments: false), + ]; + final dut = Sum(intfs, initialValue: 4, minValue: 12, maxValue: 12); + + final actual = dut.value.value.toInt(); + final expected = goldenSum( + intfs, + width: dut.width, + minVal: 12, + maxVal: 12, + initialValue: 4, + ); + expect(actual, 12); + expect(actual, expected); + }); test('random', () { final rand = Random(123); @@ -256,8 +272,7 @@ void main() { return List.generate(numInterfaces, (_) => genRandomInterface()); } - //TODO: set max number of rand iterations - for (var i = 0; i < 100; i++) { + for (var i = 0; i < 1000; i++) { final interfaces = genRandomInterfaces(); final width = rand.nextBool() ? null : rand.nextInt(10) + 1; From cf82fe80a8cb94ee6593b263b22ef970bb825a0b Mon Sep 17 00:00:00 2001 From: Max Korbel Date: Wed, 28 Aug 2024 11:58:03 -0700 Subject: [PATCH 22/43] bug fixes on counter, more tests --- lib/src/counter.dart | 6 ++- lib/src/sum.dart | 2 - test/counter_test.dart | 97 ++++++++++++++++++++++++++++++++++++------ test/sum_test.dart | 34 +++++++++++++++ 4 files changed, 122 insertions(+), 17 deletions(-) diff --git a/lib/src/counter.dart b/lib/src/counter.dart index 4f61ed913..c0f6a7979 100644 --- a/lib/src/counter.dart +++ b/lib/src/counter.dart @@ -23,6 +23,7 @@ class Counter extends Module with DynamicInputToLogic { /// `maxValue` and `minValue`. final bool saturates; + /// The output value of the counter. Logic get value => output('value'); /// Indicates whether the sum has reached the maximum value. @@ -130,8 +131,9 @@ class Counter extends Module with DynamicInputToLogic { resetValue: resetValueLogic, ); - addOutput('reachedMax') <= sum.reachedMax; - addOutput('reachedMin') <= sum.reachedMin; + // need to flop these since value is flopped + addOutput('reachedMax') <= flop(clk, sum.reachedMax, reset: reset); + addOutput('reachedMin') <= flop(clk, sum.reachedMin, reset: reset); } } diff --git a/lib/src/sum.dart b/lib/src/sum.dart index 7eb16c123..78c19beb1 100644 --- a/lib/src/sum.dart +++ b/lib/src/sum.dart @@ -94,8 +94,6 @@ class Sum extends Module with DynamicInputToLogic { /// will wrap around (overflow/underflow) at the `maxValue` and `minValue`. final bool saturates; - //TODO: add some sort if "saturated" or "minimum" outputs? - Logic get value => output('value'); /// Indicates whether the sum has reached the maximum value. diff --git a/test/counter_test.dart b/test/counter_test.dart index 225db7a1f..9f10b32e8 100644 --- a/test/counter_test.dart +++ b/test/counter_test.dart @@ -17,12 +17,10 @@ void main() { test('basic 1-bit rolling counter', () async { final clk = SimpleClockGenerator(10).clk; final reset = Logic(); - final intf = SumInterface(fixedAmount: 1); - final counter = Counter([intf], clk: clk, reset: reset); - await counter.build(); + final counter = Counter.ofLogics([Const(1)], clk: clk, reset: reset); - print(counter.generateSynth()); + await counter.build(); Simulator.setMaxSimTime(1000); unawaited(Simulator.run()); @@ -58,15 +56,88 @@ void main() { await Simulator.endSimulation(); }); - // TODO: test plan: - // - 4 bit counter overflow roll - // - 4 bit down-counter underflow roll - // - 4 bit counter with upper saturation - // - 4 bit down-counter with lower saturation - // - for each of them - // - with/out variable amount - // - with/out enable - // - weird reset value + test('reset and restart counter', () async { + final clk = SimpleClockGenerator(10).clk; + final reset = Logic(); + final restart = Logic(); + + final counter = Counter( + [ + SumInterface(fixedAmount: 4), + SumInterface(fixedAmount: 2, increments: false), + ], + clk: clk, + reset: reset, + restart: restart, + resetValue: 10, + maxValue: 15, + saturates: true, + width: 8, + ); + + await counter.build(); + WaveDumper(counter); + + Simulator.setMaxSimTime(1000); + unawaited(Simulator.run()); + + // little reset routine + reset.inject(0); + restart.inject(0); + await clk.nextNegedge; + reset.inject(1); + await clk.nextNegedge; + await clk.nextNegedge; + reset.inject(0); + + // check initial value after reset drops + expect(counter.value.value.toInt(), 10); + + // increment each cycle + await clk.nextNegedge; + expect(counter.value.value.toInt(), 12); + await clk.nextNegedge; + expect(counter.value.value.toInt(), 14); + expect(counter.reachedMax.value.toBool(), false); + + // saturate + await clk.nextNegedge; + expect(counter.value.value.toInt(), 15); + expect(counter.reachedMax.value.toBool(), true); + await clk.nextNegedge; + expect(counter.value.value.toInt(), 15); + expect(counter.reachedMax.value.toBool(), true); + + // restart (not reset!) + restart.inject(1); + + // now we should catch the next +2 still, not miss it + await clk.nextNegedge; + expect(counter.value.value.toInt(), 12); + + // and hold there + await clk.nextNegedge; + expect(counter.value.value.toInt(), 12); + + // drop it and should continue + restart.inject(0); + await clk.nextNegedge; + expect(counter.value.value.toInt(), 14); + + // now back to reset + reset.inject(1); + await clk.nextNegedge; + expect(counter.value.value.toInt(), 10); + await clk.nextNegedge; + expect(counter.value.value.toInt(), 10); + + await clk.nextNegedge; + await clk.nextNegedge; + await clk.nextNegedge; + await clk.nextNegedge; + + await Simulator.endSimulation(); + }); //TODO: test modulo requirement -- if sum is >2x greater than saturation } diff --git a/test/sum_test.dart b/test/sum_test.dart index f3e4315b9..d0eb45e3e 100644 --- a/test/sum_test.dart +++ b/test/sum_test.dart @@ -254,6 +254,40 @@ void main() { expect(actual, expected); }); + group('reached', () { + test('has reachedMax', () { + final dut = Sum.ofLogics([Const(10, width: 8)], + width: 8, maxValue: 5, saturates: true); + expect(dut.reachedMax.value.toBool(), true); + expect(dut.value.value.toInt(), 5); + }); + + test('not reachedMax', () { + final dut = Sum.ofLogics([Const(3, width: 8)], + width: 8, maxValue: 5, saturates: true); + expect(dut.reachedMax.value.toBool(), false); + expect(dut.value.value.toInt(), 3); + }); + + test('has reachedMin', () { + final dut = Sum([ + SumInterface(fixedAmount: 10, increments: false), + ], width: 8, minValue: 15, initialValue: 20, saturates: true); + expect(dut.reachedMin.value.toBool(), true); + expect(dut.value.value.toInt(), 15); + }); + + test('not reachedMin', () { + final dut = Sum([ + SumInterface(fixedAmount: 3, increments: false), + ], width: 8, minValue: 15, initialValue: 20, saturates: true); + expect(dut.reachedMin.value.toBool(), false); + expect(dut.value.value.toInt(), 17); + }); + }); + + // TODO: test enable + test('random', () { final rand = Random(123); From 838e45925c3e458cce61ce3cd93c71f050807f3a Mon Sep 17 00:00:00 2001 From: Max Korbel Date: Wed, 28 Aug 2024 15:03:27 -0700 Subject: [PATCH 23/43] reorganizing --- lib/rohd_hcl.dart | 3 +- lib/src/{ => summation}/counter.dart | 68 +++++++------- lib/src/{ => summation}/sum.dart | 120 ++----------------------- lib/src/summation/sum_interface.dart | 67 ++++++++++++++ lib/src/summation/summation.dart | 6 ++ lib/src/summation/summation_utils.dart | 78 ++++++++++++++++ test/{ => summation}/counter_test.dart | 32 ++++--- test/{ => summation}/sum_test.dart | 26 +++--- 8 files changed, 228 insertions(+), 172 deletions(-) rename lib/src/{ => summation}/counter.dart (76%) rename lib/src/{ => summation}/sum.dart (66%) create mode 100644 lib/src/summation/sum_interface.dart create mode 100644 lib/src/summation/summation.dart create mode 100644 lib/src/summation/summation_utils.dart rename test/{ => summation}/counter_test.dart (80%) rename test/{ => summation}/sum_test.dart (94%) diff --git a/lib/rohd_hcl.dart b/lib/rohd_hcl.dart index c45e7f325..c1d7c298c 100644 --- a/lib/rohd_hcl.dart +++ b/lib/rohd_hcl.dart @@ -2,13 +2,11 @@ // SPDX-License-Identifier: BSD-3-Clause export 'src/adder.dart'; -export 'src/sum.dart'; export 'src/arbiters/arbiters.dart'; export 'src/binary_gray.dart'; export 'src/carry_save_mutiplier.dart'; export 'src/component_config/component_config.dart'; export 'src/count.dart'; -export 'src/counter.dart'; export 'src/edge_detector.dart'; export 'src/encodings/encodings.dart'; export 'src/error_checking/error_checking.dart'; @@ -24,4 +22,5 @@ export 'src/ripple_carry_adder.dart'; export 'src/rotate.dart'; export 'src/shift_register.dart'; export 'src/sort.dart'; +export 'src/summation/summation.dart'; export 'src/utils.dart'; diff --git a/lib/src/counter.dart b/lib/src/summation/counter.dart similarity index 76% rename from lib/src/counter.dart rename to lib/src/summation/counter.dart index c0f6a7979..d4c845c49 100644 --- a/lib/src/counter.dart +++ b/lib/src/summation/counter.dart @@ -8,14 +8,11 @@ // Author: Max Korbel import 'package:collection/collection.dart'; -import 'package:meta/meta.dart'; import 'package:rohd/rohd.dart'; import 'package:rohd_hcl/rohd_hcl.dart'; -import 'package:rohd_hcl/src/sum.dart'; -import 'package:rohd_hcl/src/exceptions.dart'; -import 'package:rohd_hcl/src/parallel_prefix_operations.dart'; +import 'package:rohd_hcl/src/summation/summation_utils.dart'; -class Counter extends Module with DynamicInputToLogic { +class Counter extends Module with DynamicInputToLogicForSummation { final int width; /// If `true`, the counter will saturate at the `maxValue` and `minValue`. If @@ -24,32 +21,58 @@ class Counter extends Module with DynamicInputToLogic { final bool saturates; /// The output value of the counter. - Logic get value => output('value'); + Logic get count => output('count'); /// Indicates whether the sum has reached the maximum value. /// - /// If it [saturates], then [value] will be equal to the maximum value. + /// If it [saturates], then [count] will be equal to the maximum value. /// Otherwise, the value may have overflowed to any value, but the net sum /// before overflow will have been greater than the maximum value. Logic get reachedMax => output('reachedMax'); /// Indicates whether the sum has reached the minimum value. /// - /// If it [saturates], then [value] will be equal to the minimum value. + /// If it [saturates], then [count] will be equal to the minimum value. /// Otherwise, the value may have underflowed to any value, but the net sum /// before underflow will have been less than the minimum value. Logic get reachedMin => output('reachedMin'); + Counter.simple({ + required Logic clk, + required Logic reset, + int by = 1, + Logic? enable, + int minValue = 0, + int? maxValue, + int? width, + Logic? restart, + bool saturates = false, + String name = 'counter', + }) : this([ + SumInterface(width: width, hasEnable: enable != null) + ..amount.gets(Const(by, width: width)) + ..enable?.gets(enable!), + ], + clk: clk, + reset: reset, + resetValue: 0, + restart: restart, + maxValue: maxValue, + minValue: minValue, + width: width, + saturates: saturates, + name: name); + factory Counter.ofLogics( List logics, { required Logic clk, required Logic reset, + Logic? restart, dynamic resetValue = 0, dynamic maxValue, dynamic minValue = 0, int? width, bool saturates = false, - Logic? restart, String name = 'counter', }) => Counter( @@ -80,7 +103,7 @@ class Counter extends Module with DynamicInputToLogic { /// /// The [restart] input can be used to restart the counter to a new value, but /// also continue to increment in that same cycle. This is distinct from - /// [reset] which will reset the counter, holding the [value] at [resetValue]. + /// [reset] which will reset the counter, holding the [count] at [resetValue]. Counter( List interfaces, { required Logic clk, @@ -100,7 +123,7 @@ class Counter extends Module with DynamicInputToLogic { restart = addInput('restart', restart); } - addOutput('value', width: this.width); + addOutput('count', width: this.width); interfaces = interfaces .mapIndexed((i, e) => SumInterface.clone(e) @@ -116,17 +139,17 @@ class Counter extends Module with DynamicInputToLogic { final sum = Sum( interfaces, initialValue: - restart != null ? mux(restart, resetValueLogic, value) : value, + restart != null ? mux(restart, resetValueLogic, count) : count, maxValue: maxValue, minValue: minValue, width: this.width, saturates: saturates, ); - value <= + count <= flop( clk, - sum.value, + sum.sum, reset: reset, resetValue: resetValueLogic, ); @@ -139,20 +162,3 @@ class Counter extends Module with DynamicInputToLogic { //TODO doc //TODO: is this ok? move it somewhere else? -mixin DynamicInputToLogic on Module { - int get width; - - @protected - Logic dynamicInputToLogic(String name, dynamic value) { - if (value is Logic) { - return addInput(name, value.zeroExtend(width), width: width); - } else { - if (LogicValue.ofInferWidth(value).width > width) { - throw RohdHclException( - 'Value $value for $name is too large for width $width'); - } - - return Logic(name: name, width: width)..gets(Const(value, width: width)); - } - } -} diff --git a/lib/src/sum.dart b/lib/src/summation/sum.dart similarity index 66% rename from lib/src/sum.dart rename to lib/src/summation/sum.dart index 78c19beb1..6f2801192 100644 --- a/lib/src/sum.dart +++ b/lib/src/summation/sum.dart @@ -7,70 +7,12 @@ // 2024 August 26 // Author: Max Korbel -import 'dart:math'; - import 'package:collection/collection.dart'; import 'package:rohd/rohd.dart'; import 'package:rohd_hcl/rohd_hcl.dart'; -import 'package:rohd_hcl/src/exceptions.dart'; -import 'package:rohd_hcl/src/parallel_prefix_operations.dart'; - -class SumInterface extends PairInterface { - final bool hasEnable; - - /// The [amount] to increment/decrement by, depending on [increments]. - late final Logic amount = - fixedAmount != null ? Const(fixedAmount, width: width) : port('amount'); - - /// Controls whether it should increment or decrement (based on [increments]) - /// (active high). - /// - /// Present if [hasEnable] is `true`. - Logic? get enable => tryPort('enable'); - - final int width; - - /// If `true`, will increment. If `false`, will decrement. - final bool increments; - - final dynamic fixedAmount; - - /// TODO - /// - /// If [width] is `null`, it can be inferred from [fixedAmount] if provided - /// with a type that contains width information (e.g. a [LogicValue]). There - /// must be enough information provided to determine the [width]. - /// - /// If a [fixedAmount] is provided, then [amount] will be tied to a [Const]. A - /// provided [fixedAmount] must be parseable by [LogicValue.of]. Note that the - /// [fixedAmount] will always be interpreted as a positive value truncated to - /// [width]. If no [fixedAmount] is provided, then [amount] will be a normal - /// [port] with [width] bits. - /// - /// If [hasEnable] is `true`, then an [enable] port will be added to the - /// interface. - SumInterface( - {this.fixedAmount, - this.increments = true, - int? width, - this.hasEnable = false}) - : width = width ?? LogicValue.ofInferWidth(fixedAmount).width { - setPorts([ - if (fixedAmount == null) Port('amount', this.width), - if (hasEnable) Port('enable'), - ], [ - PairDirection.fromProvider - ]); - } - - SumInterface.clone(SumInterface other) - : this( - fixedAmount: other.fixedAmount, - increments: other.increments, - width: other.width, - hasEnable: other.hasEnable, - ); +import 'package:rohd_hcl/src/summation/summation_utils.dart'; +extension on SumInterface { List _combAdjustments(Logic Function(Logic) s, Logic nextVal) { final conds = [ if (increments) @@ -87,25 +29,25 @@ class SumInterface extends PairInterface { } } -class Sum extends Module with DynamicInputToLogic { +class Sum extends Module with DynamicInputToLogicForSummation { final int width; /// If `true`, will saturate at the `maxValue` and `minValue`. If `false`, /// will wrap around (overflow/underflow) at the `maxValue` and `minValue`. final bool saturates; - Logic get value => output('value'); + Logic get sum => output('sum'); /// Indicates whether the sum has reached the maximum value. /// - /// If it [saturates], then [value] will be equal to the maximum value. + /// If it [saturates], then [sum] will be equal to the maximum value. /// Otherwise, the value may have overflowed to any value, but the net sum /// before overflow will have been greater than the maximum value. Logic get reachedMax => output('reachedMax'); /// Indicates whether the sum has reached the minimum value. /// - /// If it [saturates], then [value] will be equal to the minimum value. + /// If it [saturates], then [sum] will be equal to the minimum value. /// Otherwise, the value may have underflowed to any value, but the net sum /// before underflow will have been less than the minimum value. Logic get reachedMin => output('reachedMin'); @@ -166,7 +108,7 @@ class Sum extends Module with DynamicInputToLogic { uniquify: (original) => '${original}_$i')) .toList(); - addOutput('value', width: this.width); + addOutput('sum', width: this.width); addOutput('reachedMax'); addOutput('reachedMin'); @@ -216,7 +158,7 @@ class Sum extends Module with DynamicInputToLogic { ..gets(minValueLogic + zeroPoint); final internalValue = Logic(name: 'internalValue', width: internalWidth); - value <= (internalValue - zeroPoint).getRange(0, this.width); + sum <= (internalValue - zeroPoint).getRange(0, this.width); final passedMax = Logic(name: 'passedMax'); final passedMin = Logic(name: 'passedMin'); @@ -266,49 +208,3 @@ class Sum extends Module with DynamicInputToLogic { static int _biggestVal(int width) => (1 << width) - 1; } - -//TODO doc -//TODO: hide this somehow -int inferWidth( - List values, int? width, List interfaces) { - if (width != null) { - if (width <= 0) { - throw RohdHclException('Width must be greater than 0.'); - } - - if (values.any((v) => v is Logic && v.width > width)) { - throw RohdHclException( - 'Width must be at least as large as the largest value.'); - } - - return width; - } - - int? maxWidthFound; - - for (final value in values) { - int? inferredValWidth; - if (value is Logic) { - inferredValWidth = value.width; - } else if (value != null) { - inferredValWidth = LogicValue.ofInferWidth(value).width; - } - - if (inferredValWidth != null && - (maxWidthFound == null || inferredValWidth > maxWidthFound)) { - maxWidthFound = inferredValWidth; - } - } - - for (final interface in interfaces) { - if (interface.width > maxWidthFound!) { - maxWidthFound = interface.width; - } - } - - if (maxWidthFound == null) { - throw RohdHclException('Unabled to infer width.'); - } - - return max(1, maxWidthFound); -} diff --git a/lib/src/summation/sum_interface.dart b/lib/src/summation/sum_interface.dart new file mode 100644 index 000000000..f9d4fc8e5 --- /dev/null +++ b/lib/src/summation/sum_interface.dart @@ -0,0 +1,67 @@ +// Copyright (C) 2024 Intel Corporation +// SPDX-License-Identifier: BSD-3-Clause +// +// sum_interface.dart +// Interface for summation and counting. +// +// 2024 August 26 +// Author: Max Korbel + +import 'package:rohd/rohd.dart'; + +class SumInterface extends PairInterface { + final bool hasEnable; + + /// The [amount] to increment/decrement by, depending on [increments]. + late final Logic amount = + fixedAmount != null ? Const(fixedAmount, width: width) : port('amount'); + + /// Controls whether it should increment or decrement (based on [increments]) + /// (active high). + /// + /// Present if [hasEnable] is `true`. + Logic? get enable => tryPort('enable'); + + final int width; + + /// If `true`, will increment. If `false`, will decrement. + final bool increments; + + final dynamic fixedAmount; + + /// TODO + /// + /// If [width] is `null`, it can be inferred from [fixedAmount] if provided + /// with a type that contains width information (e.g. a [LogicValue]). There + /// must be enough information provided to determine the [width]. + /// + /// If a [fixedAmount] is provided, then [amount] will be tied to a [Const]. A + /// provided [fixedAmount] must be parseable by [LogicValue.of]. Note that the + /// [fixedAmount] will always be interpreted as a positive value truncated to + /// [width]. If no [fixedAmount] is provided, then [amount] will be a normal + /// [port] with [width] bits. + /// + /// If [hasEnable] is `true`, then an [enable] port will be added to the + /// interface. + SumInterface( + {this.fixedAmount, + this.increments = true, + int? width, + this.hasEnable = false}) + : width = width ?? LogicValue.ofInferWidth(fixedAmount).width { + setPorts([ + if (fixedAmount == null) Port('amount', this.width), + if (hasEnable) Port('enable'), + ], [ + PairDirection.fromProvider + ]); + } + + SumInterface.clone(SumInterface other) + : this( + fixedAmount: other.fixedAmount, + increments: other.increments, + width: other.width, + hasEnable: other.hasEnable, + ); +} diff --git a/lib/src/summation/summation.dart b/lib/src/summation/summation.dart new file mode 100644 index 000000000..d1c994b68 --- /dev/null +++ b/lib/src/summation/summation.dart @@ -0,0 +1,6 @@ +// Copyright (C) 2024 Intel Corporation +// SPDX-License-Identifier: BSD-3-Clause + +export 'counter.dart'; +export 'sum.dart'; +export 'sum_interface.dart'; diff --git a/lib/src/summation/summation_utils.dart b/lib/src/summation/summation_utils.dart new file mode 100644 index 000000000..92f1531e5 --- /dev/null +++ b/lib/src/summation/summation_utils.dart @@ -0,0 +1,78 @@ +// Copyright (C) 2024 Intel Corporation +// SPDX-License-Identifier: BSD-3-Clause +// +// summation_utils.dart +// Internal utilities for the summation components. +// +// 2024 August 26 +// Author: Max Korbel + +import 'dart:math'; + +import 'package:meta/meta.dart'; +import 'package:rohd/rohd.dart'; +import 'package:rohd_hcl/rohd_hcl.dart'; + +mixin DynamicInputToLogicForSummation on Module { + int get width; + + @protected + Logic dynamicInputToLogic(String name, dynamic value) { + if (value is Logic) { + return addInput(name, value.zeroExtend(width), width: width); + } else { + if (LogicValue.ofInferWidth(value).width > width) { + throw RohdHclException( + 'Value $value for $name is too large for width $width'); + } + + return Logic(name: name, width: width)..gets(Const(value, width: width)); + } + } +} + +//TODO doc +//TODO: hide this somehow +int inferWidth( + List values, int? width, List interfaces) { + if (width != null) { + if (width <= 0) { + throw RohdHclException('Width must be greater than 0.'); + } + + if (values.any((v) => v is Logic && v.width > width)) { + throw RohdHclException( + 'Width must be at least as large as the largest value.'); + } + + return width; + } + + int? maxWidthFound; + + for (final value in values) { + int? inferredValWidth; + if (value is Logic) { + inferredValWidth = value.width; + } else if (value != null) { + inferredValWidth = LogicValue.ofInferWidth(value).width; + } + + if (inferredValWidth != null && + (maxWidthFound == null || inferredValWidth > maxWidthFound)) { + maxWidthFound = inferredValWidth; + } + } + + for (final interface in interfaces) { + if (interface.width > maxWidthFound!) { + maxWidthFound = interface.width; + } + } + + if (maxWidthFound == null) { + throw RohdHclException('Unabled to infer width.'); + } + + return max(1, maxWidthFound); +} diff --git a/test/counter_test.dart b/test/summation/counter_test.dart similarity index 80% rename from test/counter_test.dart rename to test/summation/counter_test.dart index 9f10b32e8..01140ac31 100644 --- a/test/counter_test.dart +++ b/test/summation/counter_test.dart @@ -14,6 +14,10 @@ import 'package:rohd_hcl/rohd_hcl.dart'; import 'package:test/test.dart'; void main() { + tearDown(() async { + await Simulator.reset(); + }); + test('basic 1-bit rolling counter', () async { final clk = SimpleClockGenerator(10).clk; final reset = Logic(); @@ -34,19 +38,19 @@ void main() { reset.inject(0); // check initial value - expect(counter.value.value.toInt(), 0); + expect(counter.count.value.toInt(), 0); // wait a cycle, see 1 await clk.nextNegedge; - expect(counter.value.value.toInt(), 1); + expect(counter.count.value.toInt(), 1); // wait a cycle, should overflow (1-bit counter), back to 0 await clk.nextNegedge; - expect(counter.value.value.toInt(), 0); + expect(counter.count.value.toInt(), 0); // wait a cycle, see 1 await clk.nextNegedge; - expect(counter.value.value.toInt(), 1); + expect(counter.count.value.toInt(), 1); await clk.nextNegedge; await clk.nextNegedge; @@ -91,21 +95,21 @@ void main() { reset.inject(0); // check initial value after reset drops - expect(counter.value.value.toInt(), 10); + expect(counter.count.value.toInt(), 10); // increment each cycle await clk.nextNegedge; - expect(counter.value.value.toInt(), 12); + expect(counter.count.value.toInt(), 12); await clk.nextNegedge; - expect(counter.value.value.toInt(), 14); + expect(counter.count.value.toInt(), 14); expect(counter.reachedMax.value.toBool(), false); // saturate await clk.nextNegedge; - expect(counter.value.value.toInt(), 15); + expect(counter.count.value.toInt(), 15); expect(counter.reachedMax.value.toBool(), true); await clk.nextNegedge; - expect(counter.value.value.toInt(), 15); + expect(counter.count.value.toInt(), 15); expect(counter.reachedMax.value.toBool(), true); // restart (not reset!) @@ -113,23 +117,23 @@ void main() { // now we should catch the next +2 still, not miss it await clk.nextNegedge; - expect(counter.value.value.toInt(), 12); + expect(counter.count.value.toInt(), 12); // and hold there await clk.nextNegedge; - expect(counter.value.value.toInt(), 12); + expect(counter.count.value.toInt(), 12); // drop it and should continue restart.inject(0); await clk.nextNegedge; - expect(counter.value.value.toInt(), 14); + expect(counter.count.value.toInt(), 14); // now back to reset reset.inject(1); await clk.nextNegedge; - expect(counter.value.value.toInt(), 10); + expect(counter.count.value.toInt(), 10); await clk.nextNegedge; - expect(counter.value.value.toInt(), 10); + expect(counter.count.value.toInt(), 10); await clk.nextNegedge; await clk.nextNegedge; diff --git a/test/sum_test.dart b/test/summation/sum_test.dart similarity index 94% rename from test/sum_test.dart rename to test/summation/sum_test.dart index d0eb45e3e..60922cb47 100644 --- a/test/sum_test.dart +++ b/test/summation/sum_test.dart @@ -108,7 +108,7 @@ void main() { final logics = [Const(1)]; final dut = Sum.ofLogics(logics); await dut.build(); - expect(dut.value.value.toInt(), 1); + expect(dut.sum.value.toInt(), 1); expect(dut.width, 1); expect(goldenSumOfLogics(logics, width: dut.width), 1); }); @@ -157,7 +157,7 @@ void main() { saturates: saturates, initialValue: initialValue, ); - expect(dut.value.value.toInt(), expected); + expect(dut.sum.value.toInt(), expected); } }); } @@ -182,7 +182,7 @@ void main() { a.put(3); b.put(2); - expect(dut.value.value.toInt(), 1); + expect(dut.sum.value.toInt(), 1); expect(goldenSum(intfs, width: dut.width, maxVal: 5, debug: true), 1); }); @@ -195,8 +195,8 @@ void main() { expect(dut.width, 7); - expect(dut.value.value.toInt(), 6); - expect(dut.value.value.toInt(), + expect(dut.sum.value.toInt(), 6); + expect(dut.sum.value.toInt(), goldenSum(intfs, width: dut.width, saturates: true, initialValue: 5)); }); @@ -206,7 +206,7 @@ void main() { ]; final dut = Sum(intfs, initialValue: 13, minValue: 16, maxValue: 31); - final actual = dut.value.value.toInt(); + final actual = dut.sum.value.toInt(); final expected = goldenSum( intfs, width: dut.width, @@ -224,7 +224,7 @@ void main() { ]; final dut = Sum(intfs, initialValue: 34, minValue: 16, maxValue: 31); - final actual = dut.value.value.toInt(); + final actual = dut.sum.value.toInt(); final expected = goldenSum( intfs, width: dut.width, @@ -242,7 +242,7 @@ void main() { ]; final dut = Sum(intfs, initialValue: 4, minValue: 12, maxValue: 12); - final actual = dut.value.value.toInt(); + final actual = dut.sum.value.toInt(); final expected = goldenSum( intfs, width: dut.width, @@ -259,14 +259,14 @@ void main() { final dut = Sum.ofLogics([Const(10, width: 8)], width: 8, maxValue: 5, saturates: true); expect(dut.reachedMax.value.toBool(), true); - expect(dut.value.value.toInt(), 5); + expect(dut.sum.value.toInt(), 5); }); test('not reachedMax', () { final dut = Sum.ofLogics([Const(3, width: 8)], width: 8, maxValue: 5, saturates: true); expect(dut.reachedMax.value.toBool(), false); - expect(dut.value.value.toInt(), 3); + expect(dut.sum.value.toInt(), 3); }); test('has reachedMin', () { @@ -274,7 +274,7 @@ void main() { SumInterface(fixedAmount: 10, increments: false), ], width: 8, minValue: 15, initialValue: 20, saturates: true); expect(dut.reachedMin.value.toBool(), true); - expect(dut.value.value.toInt(), 15); + expect(dut.sum.value.toInt(), 15); }); test('not reachedMin', () { @@ -282,7 +282,7 @@ void main() { SumInterface(fixedAmount: 3, increments: false), ], width: 8, minValue: 15, initialValue: 20, saturates: true); expect(dut.reachedMin.value.toBool(), false); - expect(dut.value.value.toInt(), 17); + expect(dut.sum.value.toInt(), 17); }); }); @@ -370,7 +370,7 @@ void main() { width: safeWidthFor(initialValue)) : initialValue); - final actual = dut.value.value.toInt(); + final actual = dut.sum.value.toInt(); final expected = goldenSum( interfaces, width: dut.width, From 57b9291679407dbfc7cfc8f2af6d86feb5ddfd94 Mon Sep 17 00:00:00 2001 From: Max Korbel Date: Wed, 28 Aug 2024 16:26:28 -0700 Subject: [PATCH 24/43] refactor counter stuff --- lib/src/summation/counter.dart | 77 +++++++---------------- lib/src/summation/sum.dart | 85 ++++++-------------------- lib/src/summation/summation_base.dart | 68 +++++++++++++++++++++ lib/src/summation/summation_utils.dart | 2 + test/summation/counter_test.dart | 48 ++++++++++++++- test/summation/sum_test.dart | 2 + 6 files changed, 160 insertions(+), 122 deletions(-) create mode 100644 lib/src/summation/summation_base.dart diff --git a/lib/src/summation/counter.dart b/lib/src/summation/counter.dart index d4c845c49..8d1ab4214 100644 --- a/lib/src/summation/counter.dart +++ b/lib/src/summation/counter.dart @@ -7,36 +7,14 @@ // 2024 August 26 // Author: Max Korbel -import 'package:collection/collection.dart'; import 'package:rohd/rohd.dart'; import 'package:rohd_hcl/rohd_hcl.dart'; -import 'package:rohd_hcl/src/summation/summation_utils.dart'; - -class Counter extends Module with DynamicInputToLogicForSummation { - final int width; - - /// If `true`, the counter will saturate at the `maxValue` and `minValue`. If - /// `false`, the counter will wrap around (overflow/underflow) at the - /// `maxValue` and `minValue`. - final bool saturates; +import 'package:rohd_hcl/src/summation/summation_base.dart'; +class Counter extends SummationBase { /// The output value of the counter. Logic get count => output('count'); - /// Indicates whether the sum has reached the maximum value. - /// - /// If it [saturates], then [count] will be equal to the maximum value. - /// Otherwise, the value may have overflowed to any value, but the net sum - /// before overflow will have been greater than the maximum value. - Logic get reachedMax => output('reachedMax'); - - /// Indicates whether the sum has reached the minimum value. - /// - /// If it [saturates], then [count] will be equal to the minimum value. - /// Otherwise, the value may have underflowed to any value, but the net sum - /// before underflow will have been less than the minimum value. - Logic get reachedMin => output('reachedMin'); - Counter.simple({ required Logic clk, required Logic reset, @@ -49,8 +27,7 @@ class Counter extends Module with DynamicInputToLogicForSummation { bool saturates = false, String name = 'counter', }) : this([ - SumInterface(width: width, hasEnable: enable != null) - ..amount.gets(Const(by, width: width)) + SumInterface(width: width, fixedAmount: by, hasEnable: enable != null) ..enable?.gets(enable!), ], clk: clk, @@ -105,17 +82,17 @@ class Counter extends Module with DynamicInputToLogicForSummation { /// also continue to increment in that same cycle. This is distinct from /// [reset] which will reset the counter, holding the [count] at [resetValue]. Counter( - List interfaces, { + super.interfaces, { required Logic clk, required Logic reset, - dynamic resetValue = 0, - dynamic maxValue, - dynamic minValue = 0, - int? width, - this.saturates = false, Logic? restart, + dynamic resetValue = 0, + super.maxValue, + super.minValue = 0, + super.width, + super.saturates, super.name = 'counter', - }) : width = inferWidth([resetValue, maxValue, minValue], width, interfaces) { + }) : super(initialValue: resetValue) { clk = addInput('clk', clk); reset = addInput('reset', reset); @@ -123,26 +100,15 @@ class Counter extends Module with DynamicInputToLogicForSummation { restart = addInput('restart', restart); } - addOutput('count', width: this.width); - - interfaces = interfaces - .mapIndexed((i, e) => SumInterface.clone(e) - ..pairConnectIO(this, e, PairRole.consumer, - uniquify: (original) => '${original}_$i')) - .toList(); - - final resetValueLogic = dynamicInputToLogic( - 'resetValue', - resetValue, - ); + addOutput('count', width: width); final sum = Sum( interfaces, initialValue: - restart != null ? mux(restart, resetValueLogic, count) : count, - maxValue: maxValue, - minValue: minValue, - width: this.width, + restart != null ? mux(restart, initialValueLogic, count) : count, + maxValue: maxValueLogic, + minValue: minValueLogic, + width: width, saturates: saturates, ); @@ -151,14 +117,15 @@ class Counter extends Module with DynamicInputToLogicForSummation { clk, sum.sum, reset: reset, - resetValue: resetValueLogic, + resetValue: initialValueLogic, ); // need to flop these since value is flopped - addOutput('reachedMax') <= flop(clk, sum.reachedMax, reset: reset); - addOutput('reachedMin') <= flop(clk, sum.reachedMin, reset: reset); + reachedMax <= + flop(clk, sum.reachedMax, + reset: reset, resetValue: initialValueLogic.gte(maxValueLogic)); + reachedMin <= + flop(clk, sum.reachedMin, + reset: reset, resetValue: initialValueLogic.lte(minValueLogic)); } } - -//TODO doc -//TODO: is this ok? move it somewhere else? diff --git a/lib/src/summation/sum.dart b/lib/src/summation/sum.dart index 6f2801192..66242db42 100644 --- a/lib/src/summation/sum.dart +++ b/lib/src/summation/sum.dart @@ -10,6 +10,7 @@ import 'package:collection/collection.dart'; import 'package:rohd/rohd.dart'; import 'package:rohd_hcl/rohd_hcl.dart'; +import 'package:rohd_hcl/src/summation/summation_base.dart'; import 'package:rohd_hcl/src/summation/summation_utils.dart'; extension on SumInterface { @@ -29,29 +30,9 @@ extension on SumInterface { } } -class Sum extends Module with DynamicInputToLogicForSummation { - final int width; - - /// If `true`, will saturate at the `maxValue` and `minValue`. If `false`, - /// will wrap around (overflow/underflow) at the `maxValue` and `minValue`. - final bool saturates; - +class Sum extends SummationBase { Logic get sum => output('sum'); - /// Indicates whether the sum has reached the maximum value. - /// - /// If it [saturates], then [sum] will be equal to the maximum value. - /// Otherwise, the value may have overflowed to any value, but the net sum - /// before overflow will have been greater than the maximum value. - Logic get reachedMax => output('reachedMax'); - - /// Indicates whether the sum has reached the minimum value. - /// - /// If it [saturates], then [sum] will be equal to the minimum value. - /// Otherwise, the value may have underflowed to any value, but the net sum - /// before underflow will have been less than the minimum value. - Logic get reachedMin => output('reachedMin'); - /// TODO /// /// All [logics]s are always enabled and incrementing. @@ -89,73 +70,49 @@ class Sum extends Module with DynamicInputToLogicForSummation { /// It is expected that [maxValue] is at least [minValue], or else results may /// be unpredictable. Sum( - List interfaces, { + super.interfaces, { dynamic initialValue = 0, - dynamic maxValue, - dynamic minValue = 0, - int? width, - this.saturates = false, + super.maxValue, + super.minValue, + super.width, + super.saturates, super.name = 'sum', - }) : width = - inferWidth([initialValue, maxValue, minValue], width, interfaces) { - if (interfaces.isEmpty) { - throw RohdHclException('At least one interface must be provided.'); - } - - interfaces = interfaces - .mapIndexed((i, e) => SumInterface.clone(e) - ..pairConnectIO(this, e, PairRole.consumer, - uniquify: (original) => '${original}_$i')) - .toList(); - - addOutput('sum', width: this.width); - addOutput('reachedMax'); - addOutput('reachedMin'); + }) : super(initialValue: initialValue) { + addOutput('sum', width: width); // assume minValue is 0, maxValue is 2^width, for width safety calcs - final maxPosMagnitude = _biggestVal(this.width) + + final maxPosMagnitude = biggestVal(this.width) + interfaces .where((e) => e.increments) - .map((e) => _biggestVal(e.width)) + .map((e) => biggestVal(e.width)) .sum; final maxNegMagnitude = interfaces .where((e) => !e.increments) - .map((e) => _biggestVal(e.width)) + .map((e) => biggestVal(e.width)) .sum + // also consider that initialValue may be less than min (initialValue is Logic - ? _biggestVal(initialValue.width) + ? biggestVal(initialValue.width) : LogicValue.ofInferWidth(initialValue).toInt()); // calculate the largest number that we could have in intermediate final internalWidth = log2Ceil(maxPosMagnitude + maxNegMagnitude + 1); - final initialValueLogic = dynamicInputToLogic( - 'initialValue', - initialValue, - ).zeroExtend(internalWidth); - - final minValueLogic = dynamicInputToLogic( - 'minValue', - minValue, - ).zeroExtend(internalWidth); - - final maxValueLogic = dynamicInputToLogic( - 'maxValue', - maxValue ?? _biggestVal(this.width), - ).zeroExtend(internalWidth); + final initialValueLogicExt = initialValueLogic.zeroExtend(internalWidth); + final minValueLogicExt = minValueLogic.zeroExtend(internalWidth); + final maxValueLogicExt = maxValueLogic.zeroExtend(internalWidth); // lazy range so that it's not generated if not necessary late final range = Logic(name: 'range', width: internalWidth) - ..gets(maxValueLogic - minValueLogic + 1); + ..gets(maxValueLogicExt - minValueLogicExt + 1); final zeroPoint = Logic(name: 'zeroPoint', width: internalWidth) ..gets(Const(maxNegMagnitude, width: internalWidth)); final upperSaturation = Logic(name: 'upperSaturation', width: internalWidth) - ..gets(maxValueLogic + zeroPoint); + ..gets(maxValueLogicExt + zeroPoint); final lowerSaturation = Logic(name: 'lowerSaturation', width: internalWidth) - ..gets(minValueLogic + zeroPoint); + ..gets(minValueLogicExt + zeroPoint); final internalValue = Logic(name: 'internalValue', width: internalWidth); sum <= (internalValue - zeroPoint).getRange(0, this.width); @@ -168,7 +125,7 @@ class Sum extends Module with DynamicInputToLogicForSummation { Combinational.ssa((s) => [ // initialize - s(internalValue) < initialValueLogic + zeroPoint, + s(internalValue) < initialValueLogicExt + zeroPoint, // perform increments and decrements ...interfaces @@ -205,6 +162,4 @@ class Sum extends Module with DynamicInputToLogicForSummation { ]), ]); } - - static int _biggestVal(int width) => (1 << width) - 1; } diff --git a/lib/src/summation/summation_base.dart b/lib/src/summation/summation_base.dart new file mode 100644 index 000000000..5262fee34 --- /dev/null +++ b/lib/src/summation/summation_base.dart @@ -0,0 +1,68 @@ +import 'package:collection/collection.dart'; +import 'package:rohd/rohd.dart'; +import 'package:rohd_hcl/rohd_hcl.dart'; +import 'package:rohd_hcl/src/summation/summation_utils.dart'; +import 'package:meta/meta.dart'; + +class SummationBase extends Module with DynamicInputToLogicForSummation { + final int width; + + @protected + late final Logic initialValueLogic; + + @protected + late final Logic minValueLogic; + + @protected + late final Logic maxValueLogic; + + @protected + late final List interfaces; + + /// If `true`, will saturate at the `maxValue` and `minValue`. If `false`, + /// will wrap around (overflow/underflow) at the `maxValue` and `minValue`. + final bool saturates; + + /// Indicates whether the sum has reached the maximum value. + /// + /// If it [saturates], then the result will be equal to the maximum value. + /// Otherwise, the value may have overflowed to any value, but the net sum + /// before overflow will have been greater than the maximum value. + Logic get reachedMax => output('reachedMax'); + + /// Indicates whether the sum has reached the minimum value. + /// + /// If it [saturates], then the result will be equal to the minimum value. + /// Otherwise, the value may have underflowed to any value, but the net sum + /// before underflow will have been less than the minimum value. + Logic get reachedMin => output('reachedMin'); + + SummationBase( + List interfaces, { + dynamic initialValue = 0, + dynamic maxValue, + dynamic minValue = 0, + this.saturates = false, + int? width, + super.name, + }) : width = + inferWidth([initialValue, maxValue, minValue], width, interfaces) { + if (interfaces.isEmpty) { + throw RohdHclException('At least one interface must be provided.'); + } + + this.interfaces = interfaces + .mapIndexed((i, e) => SumInterface.clone(e) + ..pairConnectIO(this, e, PairRole.consumer, + uniquify: (original) => '${original}_$i')) + .toList(); + + initialValueLogic = dynamicInputToLogic('initialValue', initialValue); + minValueLogic = dynamicInputToLogic('minValue', minValue); + maxValueLogic = + dynamicInputToLogic('maxValue', maxValue ?? biggestVal(this.width)); + + addOutput('reachedMax'); + addOutput('reachedMin'); + } +} diff --git a/lib/src/summation/summation_utils.dart b/lib/src/summation/summation_utils.dart index 92f1531e5..c01eb68b6 100644 --- a/lib/src/summation/summation_utils.dart +++ b/lib/src/summation/summation_utils.dart @@ -76,3 +76,5 @@ int inferWidth( return max(1, maxWidthFound); } + +int biggestVal(int width) => (1 << width) - 1; diff --git a/test/summation/counter_test.dart b/test/summation/counter_test.dart index 01140ac31..8dea95be7 100644 --- a/test/summation/counter_test.dart +++ b/test/summation/counter_test.dart @@ -60,6 +60,52 @@ void main() { await Simulator.endSimulation(); }); + test('simple counter', () async { + final clk = SimpleClockGenerator(10).clk; + final reset = Logic(); + final counter = Counter.simple(clk: clk, reset: reset, maxValue: 5); + + await counter.build(); + + Simulator.setMaxSimTime(1000); + unawaited(Simulator.run()); + + // little reset routine + reset.inject(0); + await clk.nextNegedge; + reset.inject(1); + await clk.nextNegedge; + await clk.nextNegedge; + reset.inject(0); + + expect(counter.reachedMin.value.toBool(), true); + + for (var i = 0; i < 20; i++) { + expect(counter.count.value.toInt(), i % 6); + + if (i > 0) { + expect(counter.reachedMin.value.toBool(), false); + } + + if (i % 6 == 5) { + expect(counter.reachedMax.value.toBool(), true); + } else if (i % 6 == 0 && i > 0) { + expect(counter.reachedMax.value.toBool(), true); + } else { + expect(counter.reachedMax.value.toBool(), false); + } + + await clk.nextNegedge; + } + + await clk.nextNegedge; + await clk.nextNegedge; + await clk.nextNegedge; + await clk.nextNegedge; + + await Simulator.endSimulation(); + }); + test('reset and restart counter', () async { final clk = SimpleClockGenerator(10).clk; final reset = Logic(); @@ -142,6 +188,4 @@ void main() { await Simulator.endSimulation(); }); - - //TODO: test modulo requirement -- if sum is >2x greater than saturation } diff --git a/test/summation/sum_test.dart b/test/summation/sum_test.dart index 60922cb47..1a88ddfb2 100644 --- a/test/summation/sum_test.dart +++ b/test/summation/sum_test.dart @@ -186,6 +186,8 @@ void main() { expect(goldenSum(intfs, width: dut.width, maxVal: 5, debug: true), 1); }); + //TODO: test modulo requirement -- if sum is >2x greater than saturation + test('one up, one down', () { final intfs = [ SumInterface(fixedAmount: 3), From c308aaeefd0d22787d30d611b493035e91765ce7 Mon Sep 17 00:00:00 2001 From: Max Korbel Date: Wed, 28 Aug 2024 16:37:41 -0700 Subject: [PATCH 25/43] fix bugs, adjust over/under flow --- lib/src/summation/counter.dart | 11 ++++---- lib/src/summation/sum.dart | 16 ++++++------ lib/src/summation/summation_base.dart | 16 +++++++++--- test/summation/counter_test.dart | 36 +++++++++++++++++++-------- test/summation/sum_test.dart | 18 +++++++------- 5 files changed, 58 insertions(+), 39 deletions(-) diff --git a/lib/src/summation/counter.dart b/lib/src/summation/counter.dart index 8d1ab4214..0e96d6689 100644 --- a/lib/src/summation/counter.dart +++ b/lib/src/summation/counter.dart @@ -121,11 +121,10 @@ class Counter extends SummationBase { ); // need to flop these since value is flopped - reachedMax <= - flop(clk, sum.reachedMax, - reset: reset, resetValue: initialValueLogic.gte(maxValueLogic)); - reachedMin <= - flop(clk, sum.reachedMin, - reset: reset, resetValue: initialValueLogic.lte(minValueLogic)); + overflowed <= flop(clk, sum.overflowed, reset: reset); + underflowed <= flop(clk, sum.underflowed, reset: reset); + + equalsMax <= count.eq(maxValueLogic); + equalsMin <= count.eq(minValueLogic); } } diff --git a/lib/src/summation/sum.dart b/lib/src/summation/sum.dart index 66242db42..efb7dcf5f 100644 --- a/lib/src/summation/sum.dart +++ b/lib/src/summation/sum.dart @@ -117,9 +117,6 @@ class Sum extends SummationBase { final internalValue = Logic(name: 'internalValue', width: internalWidth); sum <= (internalValue - zeroPoint).getRange(0, this.width); - final passedMax = Logic(name: 'passedMax'); - final passedMin = Logic(name: 'passedMin'); - final preAdjustmentValue = Logic(name: 'preAdjustmentValue', width: internalWidth); @@ -133,10 +130,8 @@ class Sum extends SummationBase { .flattened, // identify if we're at a max/min case - passedMax < s(internalValue).gt(upperSaturation), - passedMin < s(internalValue).lt(lowerSaturation), - reachedMax < passedMax | s(internalValue).eq(upperSaturation), - reachedMin < passedMin | s(internalValue).eq(lowerSaturation), + overflowed < s(internalValue).gt(upperSaturation), + underflowed < s(internalValue).lt(lowerSaturation), // useful as an internal node for debug/visibility preAdjustmentValue < s(internalValue), @@ -144,7 +139,7 @@ class Sum extends SummationBase { // handle saturation or over/underflow If.block([ Iff.s( - passedMax, + overflowed, s(internalValue) < (saturates ? upperSaturation @@ -152,7 +147,7 @@ class Sum extends SummationBase { lowerSaturation)), ), ElseIf.s( - passedMin, + underflowed, s(internalValue) < (saturates ? lowerSaturation @@ -161,5 +156,8 @@ class Sum extends SummationBase { ) ]), ]); + + equalsMax <= internalValue.eq(upperSaturation); + equalsMin <= internalValue.eq(lowerSaturation); } } diff --git a/lib/src/summation/summation_base.dart b/lib/src/summation/summation_base.dart index 5262fee34..00c611a72 100644 --- a/lib/src/summation/summation_base.dart +++ b/lib/src/summation/summation_base.dart @@ -23,19 +23,25 @@ class SummationBase extends Module with DynamicInputToLogicForSummation { /// will wrap around (overflow/underflow) at the `maxValue` and `minValue`. final bool saturates; + //TODO: review doc comments! /// Indicates whether the sum has reached the maximum value. /// /// If it [saturates], then the result will be equal to the maximum value. /// Otherwise, the value may have overflowed to any value, but the net sum /// before overflow will have been greater than the maximum value. - Logic get reachedMax => output('reachedMax'); + // Logic get reachedMax => output('reachedMax'); /// Indicates whether the sum has reached the minimum value. /// /// If it [saturates], then the result will be equal to the minimum value. /// Otherwise, the value may have underflowed to any value, but the net sum /// before underflow will have been less than the minimum value. - Logic get reachedMin => output('reachedMin'); + // Logic get reachedMin => output('reachedMin'); + + Logic get overflowed => output('overflowed'); + Logic get underflowed => output('underflowed'); + Logic get equalsMax => output('equalsMax'); + Logic get equalsMin => output('equalsMin'); SummationBase( List interfaces, { @@ -62,7 +68,9 @@ class SummationBase extends Module with DynamicInputToLogicForSummation { maxValueLogic = dynamicInputToLogic('maxValue', maxValue ?? biggestVal(this.width)); - addOutput('reachedMax'); - addOutput('reachedMin'); + addOutput('overflowed'); + addOutput('underflowed'); + addOutput('equalsMax'); + addOutput('equalsMin'); } } diff --git a/test/summation/counter_test.dart b/test/summation/counter_test.dart index 8dea95be7..ffac8ca35 100644 --- a/test/summation/counter_test.dart +++ b/test/summation/counter_test.dart @@ -78,23 +78,34 @@ void main() { await clk.nextNegedge; reset.inject(0); - expect(counter.reachedMin.value.toBool(), true); + expect(counter.overflowed.value.toBool(), false); + expect(counter.underflowed.value.toBool(), false); + expect(counter.equalsMax.value.toBool(), false); + expect(counter.equalsMin.value.toBool(), true); for (var i = 0; i < 20; i++) { expect(counter.count.value.toInt(), i % 6); - if (i > 0) { - expect(counter.reachedMin.value.toBool(), false); - } - if (i % 6 == 5) { - expect(counter.reachedMax.value.toBool(), true); + expect(counter.overflowed.value.toBool(), false); + expect(counter.equalsMax.value.toBool(), true); + expect(counter.equalsMin.value.toBool(), false); } else if (i % 6 == 0 && i > 0) { - expect(counter.reachedMax.value.toBool(), true); + expect(counter.overflowed.value.toBool(), true); + expect(counter.equalsMax.value.toBool(), false); + expect(counter.equalsMin.value.toBool(), true); } else { - expect(counter.reachedMax.value.toBool(), false); + expect(counter.overflowed.value.toBool(), false); + expect(counter.equalsMax.value.toBool(), false); + if (i % 6 != 0) { + expect(counter.equalsMin.value.toBool(), false); + } else { + expect(counter.equalsMin.value.toBool(), true); + } } + expect(counter.underflowed.value.toBool(), false); + await clk.nextNegedge; } @@ -106,6 +117,9 @@ void main() { await Simulator.endSimulation(); }); + //TODO: simple down counter + //TODO: test equalsMin/MAx with sum + test('reset and restart counter', () async { final clk = SimpleClockGenerator(10).clk; final reset = Logic(); @@ -148,15 +162,15 @@ void main() { expect(counter.count.value.toInt(), 12); await clk.nextNegedge; expect(counter.count.value.toInt(), 14); - expect(counter.reachedMax.value.toBool(), false); + expect(counter.overflowed.value.toBool(), false); // saturate await clk.nextNegedge; expect(counter.count.value.toInt(), 15); - expect(counter.reachedMax.value.toBool(), true); + expect(counter.overflowed.value.toBool(), true); await clk.nextNegedge; expect(counter.count.value.toInt(), 15); - expect(counter.reachedMax.value.toBool(), true); + expect(counter.overflowed.value.toBool(), true); // restart (not reset!) restart.inject(1); diff --git a/test/summation/sum_test.dart b/test/summation/sum_test.dart index 1a88ddfb2..e80c977f2 100644 --- a/test/summation/sum_test.dart +++ b/test/summation/sum_test.dart @@ -183,7 +183,7 @@ void main() { a.put(3); b.put(2); expect(dut.sum.value.toInt(), 1); - expect(goldenSum(intfs, width: dut.width, maxVal: 5, debug: true), 1); + expect(goldenSum(intfs, width: dut.width, maxVal: 5), 1); }); //TODO: test modulo requirement -- if sum is >2x greater than saturation @@ -257,33 +257,33 @@ void main() { }); group('reached', () { - test('has reachedMax', () { + test('has overflowed', () { final dut = Sum.ofLogics([Const(10, width: 8)], width: 8, maxValue: 5, saturates: true); - expect(dut.reachedMax.value.toBool(), true); + expect(dut.overflowed.value.toBool(), true); expect(dut.sum.value.toInt(), 5); }); - test('not reachedMax', () { + test('not overflowed', () { final dut = Sum.ofLogics([Const(3, width: 8)], width: 8, maxValue: 5, saturates: true); - expect(dut.reachedMax.value.toBool(), false); + expect(dut.overflowed.value.toBool(), false); expect(dut.sum.value.toInt(), 3); }); - test('has reachedMin', () { + test('has underflowed', () { final dut = Sum([ SumInterface(fixedAmount: 10, increments: false), ], width: 8, minValue: 15, initialValue: 20, saturates: true); - expect(dut.reachedMin.value.toBool(), true); + expect(dut.underflowed.value.toBool(), true); expect(dut.sum.value.toInt(), 15); }); - test('not reachedMin', () { + test('not underflowed', () { final dut = Sum([ SumInterface(fixedAmount: 3, increments: false), ], width: 8, minValue: 15, initialValue: 20, saturates: true); - expect(dut.reachedMin.value.toBool(), false); + expect(dut.underflowed.value.toBool(), false); expect(dut.sum.value.toInt(), 17); }); }); From 882965abb1e72587907234e19ab9315972ae8806 Mon Sep 17 00:00:00 2001 From: Max Korbel Date: Thu, 5 Sep 2024 14:54:52 -0700 Subject: [PATCH 26/43] more tests --- lib/src/summation/counter.dart | 12 +++-- lib/src/summation/summation_base.dart | 3 ++ test/summation/counter_test.dart | 65 +++++++++++++++++++++++++-- test/summation/sum_test.dart | 28 ++++++++++++ 4 files changed, 102 insertions(+), 6 deletions(-) diff --git a/lib/src/summation/counter.dart b/lib/src/summation/counter.dart index 0e96d6689..fca621066 100644 --- a/lib/src/summation/counter.dart +++ b/lib/src/summation/counter.dart @@ -25,14 +25,20 @@ class Counter extends SummationBase { int? width, Logic? restart, bool saturates = false, + bool incremenents = true, + int resetValue = 0, String name = 'counter', }) : this([ - SumInterface(width: width, fixedAmount: by, hasEnable: enable != null) + SumInterface( + width: width, + fixedAmount: by, + hasEnable: enable != null, + increments: incremenents) ..enable?.gets(enable!), ], clk: clk, reset: reset, - resetValue: 0, + resetValue: resetValue, restart: restart, maxValue: maxValue, minValue: minValue, @@ -106,7 +112,7 @@ class Counter extends SummationBase { interfaces, initialValue: restart != null ? mux(restart, initialValueLogic, count) : count, - maxValue: maxValueLogic, + maxValue: maxValueLogic, //TODO: this is a problem, constant for %2 minValue: minValueLogic, width: width, saturates: saturates, diff --git a/lib/src/summation/summation_base.dart b/lib/src/summation/summation_base.dart index 00c611a72..6984c81e1 100644 --- a/lib/src/summation/summation_base.dart +++ b/lib/src/summation/summation_base.dart @@ -37,6 +37,9 @@ class SummationBase extends Module with DynamicInputToLogicForSummation { /// Otherwise, the value may have underflowed to any value, but the net sum /// before underflow will have been less than the minimum value. // Logic get reachedMin => output('reachedMin'); +/** + * +*/ Logic get overflowed => output('overflowed'); Logic get underflowed => output('underflowed'); diff --git a/test/summation/counter_test.dart b/test/summation/counter_test.dart index ffac8ca35..8533219a8 100644 --- a/test/summation/counter_test.dart +++ b/test/summation/counter_test.dart @@ -60,7 +60,7 @@ void main() { await Simulator.endSimulation(); }); - test('simple counter', () async { + test('simple up counter', () async { final clk = SimpleClockGenerator(10).clk; final reset = Logic(); final counter = Counter.simple(clk: clk, reset: reset, maxValue: 5); @@ -117,8 +117,67 @@ void main() { await Simulator.endSimulation(); }); - //TODO: simple down counter - //TODO: test equalsMin/MAx with sum + test('simple down counter', () async { + final clk = SimpleClockGenerator(10).clk; + final reset = Logic(); + final counter = Counter.simple( + clk: clk, + reset: reset, + maxValue: 5, + resetValue: 5, + incremenents: false); + + await counter.build(); + + Simulator.setMaxSimTime(1000); + unawaited(Simulator.run()); + + // little reset routine + reset.inject(0); + await clk.nextNegedge; + reset.inject(1); + await clk.nextNegedge; + await clk.nextNegedge; + reset.inject(0); + + expect(counter.overflowed.value.toBool(), false); + expect(counter.underflowed.value.toBool(), false); + expect(counter.equalsMax.value.toBool(), true); + expect(counter.equalsMin.value.toBool(), false); + + for (var i = 0; i < 20; i++) { + expect(counter.count.value.toInt(), 5 - (i % 6)); + + if (i % 6 == 5) { + expect(counter.underflowed.value.toBool(), false); + expect(counter.equalsMax.value.toBool(), false); + expect(counter.equalsMin.value.toBool(), true); + } else if (i % 6 == 0 && i > 0) { + expect(counter.underflowed.value.toBool(), true); + expect(counter.equalsMax.value.toBool(), true); + expect(counter.equalsMin.value.toBool(), false); + } else { + expect(counter.underflowed.value.toBool(), false); + expect(counter.equalsMin.value.toBool(), false); + if (i % 6 != 0) { + expect(counter.equalsMax.value.toBool(), false); + } else { + expect(counter.equalsMax.value.toBool(), true); + } + } + + expect(counter.overflowed.value.toBool(), false); + + await clk.nextNegedge; + } + + await clk.nextNegedge; + await clk.nextNegedge; + await clk.nextNegedge; + await clk.nextNegedge; + + await Simulator.endSimulation(); + }); test('reset and restart counter', () async { final clk = SimpleClockGenerator(10).clk; diff --git a/test/summation/sum_test.dart b/test/summation/sum_test.dart index e80c977f2..23f10c4c3 100644 --- a/test/summation/sum_test.dart +++ b/test/summation/sum_test.dart @@ -113,6 +113,34 @@ void main() { expect(goldenSumOfLogics(logics, width: dut.width), 1); }); + group('sum indications', () { + test('equalsMax', () { + expect( + Sum.ofLogics([Const(5, width: 8)], maxValue: 5) + .equalsMax + .value + .toBool(), + isTrue); + }); + + test('equalsMin', () { + final dut = + Sum.ofLogics([Const(0, width: 8)], minValue: 3, saturates: true); + expect(dut.equalsMin.value.toBool(), isTrue); + }); + + test('underflowed', () { + final dut = Sum.ofLogics([Const(5, width: 8)], minValue: 6); + expect(dut.underflowed.value.toBool(), isTrue); + }); + + test('overflowed', () { + final dut = + Sum.ofLogics([Const(5, width: 8)], maxValue: 4, saturates: true); + expect(dut.overflowed.value.toBool(), isTrue); + }); + }); + group('simple 2 numbers', () { final pairs = [ // fits From a8a55476ef38b303dfb0e2e1e7a18325239f3736 Mon Sep 17 00:00:00 2001 From: Max Korbel Date: Thu, 5 Sep 2024 15:15:46 -0700 Subject: [PATCH 27/43] refactor utilities to base class --- lib/src/summation/sum.dart | 9 ++- lib/src/summation/summation_base.dart | 75 ++++++++++++++++++++++-- lib/src/summation/summation_utils.dart | 80 -------------------------- 3 files changed, 73 insertions(+), 91 deletions(-) delete mode 100644 lib/src/summation/summation_utils.dart diff --git a/lib/src/summation/sum.dart b/lib/src/summation/sum.dart index efb7dcf5f..0c40d0ac9 100644 --- a/lib/src/summation/sum.dart +++ b/lib/src/summation/sum.dart @@ -11,7 +11,6 @@ import 'package:collection/collection.dart'; import 'package:rohd/rohd.dart'; import 'package:rohd_hcl/rohd_hcl.dart'; import 'package:rohd_hcl/src/summation/summation_base.dart'; -import 'package:rohd_hcl/src/summation/summation_utils.dart'; extension on SumInterface { List _combAdjustments(Logic Function(Logic) s, Logic nextVal) { @@ -81,18 +80,18 @@ class Sum extends SummationBase { addOutput('sum', width: width); // assume minValue is 0, maxValue is 2^width, for width safety calcs - final maxPosMagnitude = biggestVal(this.width) + + final maxPosMagnitude = SummationBase.biggestVal(width) + interfaces .where((e) => e.increments) - .map((e) => biggestVal(e.width)) + .map((e) => SummationBase.biggestVal(e.width)) .sum; final maxNegMagnitude = interfaces .where((e) => !e.increments) - .map((e) => biggestVal(e.width)) + .map((e) => SummationBase.biggestVal(e.width)) .sum + // also consider that initialValue may be less than min (initialValue is Logic - ? biggestVal(initialValue.width) + ? SummationBase.biggestVal(initialValue.width) : LogicValue.ofInferWidth(initialValue).toInt()); // calculate the largest number that we could have in intermediate diff --git a/lib/src/summation/summation_base.dart b/lib/src/summation/summation_base.dart index 6984c81e1..77dd12115 100644 --- a/lib/src/summation/summation_base.dart +++ b/lib/src/summation/summation_base.dart @@ -1,10 +1,11 @@ +import 'dart:math'; + import 'package:collection/collection.dart'; import 'package:rohd/rohd.dart'; import 'package:rohd_hcl/rohd_hcl.dart'; -import 'package:rohd_hcl/src/summation/summation_utils.dart'; import 'package:meta/meta.dart'; -class SummationBase extends Module with DynamicInputToLogicForSummation { +abstract class SummationBase extends Module { final int width; @protected @@ -55,7 +56,7 @@ class SummationBase extends Module with DynamicInputToLogicForSummation { int? width, super.name, }) : width = - inferWidth([initialValue, maxValue, minValue], width, interfaces) { + _inferWidth([initialValue, maxValue, minValue], width, interfaces) { if (interfaces.isEmpty) { throw RohdHclException('At least one interface must be provided.'); } @@ -66,14 +67,76 @@ class SummationBase extends Module with DynamicInputToLogicForSummation { uniquify: (original) => '${original}_$i')) .toList(); - initialValueLogic = dynamicInputToLogic('initialValue', initialValue); - minValueLogic = dynamicInputToLogic('minValue', minValue); + initialValueLogic = _dynamicInputToLogic('initialValue', initialValue); + minValueLogic = _dynamicInputToLogic('minValue', minValue); maxValueLogic = - dynamicInputToLogic('maxValue', maxValue ?? biggestVal(this.width)); + _dynamicInputToLogic('maxValue', maxValue ?? biggestVal(this.width)); addOutput('overflowed'); addOutput('underflowed'); addOutput('equalsMax'); addOutput('equalsMin'); } + + /// TODO doc + Logic _dynamicInputToLogic(String name, dynamic value) { + if (value is Logic) { + return addInput(name, value.zeroExtend(width), width: width); + } else { + if (LogicValue.ofInferWidth(value).width > width) { + throw RohdHclException( + 'Value $value for $name is too large for width $width'); + } + + return Logic(name: name, width: width)..gets(Const(value, width: width)); + } + } + + @protected + static int biggestVal(int width) => (1 << width) - 1; + + //TODO doc + static int _inferWidth( + List values, int? width, List interfaces) { + if (width != null) { + if (width <= 0) { + throw RohdHclException('Width must be greater than 0.'); + } + + if (values.any((v) => v is Logic && v.width > width)) { + throw RohdHclException( + 'Width must be at least as large as the largest value.'); + } + + return width; + } + + int? maxWidthFound; + + for (final value in values) { + int? inferredValWidth; + if (value is Logic) { + inferredValWidth = value.width; + } else if (value != null) { + inferredValWidth = LogicValue.ofInferWidth(value).width; + } + + if (inferredValWidth != null && + (maxWidthFound == null || inferredValWidth > maxWidthFound)) { + maxWidthFound = inferredValWidth; + } + } + + for (final interface in interfaces) { + if (interface.width > maxWidthFound!) { + maxWidthFound = interface.width; + } + } + + if (maxWidthFound == null) { + throw RohdHclException('Unabled to infer width.'); + } + + return max(1, maxWidthFound); + } } diff --git a/lib/src/summation/summation_utils.dart b/lib/src/summation/summation_utils.dart deleted file mode 100644 index c01eb68b6..000000000 --- a/lib/src/summation/summation_utils.dart +++ /dev/null @@ -1,80 +0,0 @@ -// Copyright (C) 2024 Intel Corporation -// SPDX-License-Identifier: BSD-3-Clause -// -// summation_utils.dart -// Internal utilities for the summation components. -// -// 2024 August 26 -// Author: Max Korbel - -import 'dart:math'; - -import 'package:meta/meta.dart'; -import 'package:rohd/rohd.dart'; -import 'package:rohd_hcl/rohd_hcl.dart'; - -mixin DynamicInputToLogicForSummation on Module { - int get width; - - @protected - Logic dynamicInputToLogic(String name, dynamic value) { - if (value is Logic) { - return addInput(name, value.zeroExtend(width), width: width); - } else { - if (LogicValue.ofInferWidth(value).width > width) { - throw RohdHclException( - 'Value $value for $name is too large for width $width'); - } - - return Logic(name: name, width: width)..gets(Const(value, width: width)); - } - } -} - -//TODO doc -//TODO: hide this somehow -int inferWidth( - List values, int? width, List interfaces) { - if (width != null) { - if (width <= 0) { - throw RohdHclException('Width must be greater than 0.'); - } - - if (values.any((v) => v is Logic && v.width > width)) { - throw RohdHclException( - 'Width must be at least as large as the largest value.'); - } - - return width; - } - - int? maxWidthFound; - - for (final value in values) { - int? inferredValWidth; - if (value is Logic) { - inferredValWidth = value.width; - } else if (value != null) { - inferredValWidth = LogicValue.ofInferWidth(value).width; - } - - if (inferredValWidth != null && - (maxWidthFound == null || inferredValWidth > maxWidthFound)) { - maxWidthFound = inferredValWidth; - } - } - - for (final interface in interfaces) { - if (interface.width > maxWidthFound!) { - maxWidthFound = interface.width; - } - } - - if (maxWidthFound == null) { - throw RohdHclException('Unabled to infer width.'); - } - - return max(1, maxWidthFound); -} - -int biggestVal(int width) => (1 << width) - 1; From 3cefdb744d233668c3cb8b4f22d192351d9b1d42 Mon Sep 17 00:00:00 2001 From: Max Korbel Date: Thu, 5 Sep 2024 16:39:26 -0700 Subject: [PATCH 28/43] improve width calculations in sum, improve docs --- lib/src/summation/sum.dart | 43 +++++++++++------ lib/src/summation/summation_base.dart | 67 ++++++++++++++++++--------- test/summation/sum_test.dart | 2 + 3 files changed, 75 insertions(+), 37 deletions(-) diff --git a/lib/src/summation/sum.dart b/lib/src/summation/sum.dart index 0c40d0ac9..dc94c88e8 100644 --- a/lib/src/summation/sum.dart +++ b/lib/src/summation/sum.dart @@ -7,12 +7,18 @@ // 2024 August 26 // Author: Max Korbel +import 'dart:math'; + import 'package:collection/collection.dart'; import 'package:rohd/rohd.dart'; import 'package:rohd_hcl/rohd_hcl.dart'; import 'package:rohd_hcl/src/summation/summation_base.dart'; +/// An extension on [SumInterface] to provide additional functionality for +/// computing in [Sum]. extension on SumInterface { + /// Adjusts the [nextVal] by the amount specified in this interface, to be + /// used within a [Combinational.ssa] block. List _combAdjustments(Logic Function(Logic) s, Logic nextVal) { final conds = [ if (increments) @@ -79,23 +85,30 @@ class Sum extends SummationBase { }) : super(initialValue: initialValue) { addOutput('sum', width: width); - // assume minValue is 0, maxValue is 2^width, for width safety calcs - final maxPosMagnitude = SummationBase.biggestVal(width) + - interfaces - .where((e) => e.increments) - .map((e) => SummationBase.biggestVal(e.width)) - .sum; - final maxNegMagnitude = interfaces - .where((e) => !e.increments) - .map((e) => SummationBase.biggestVal(e.width)) - .sum + - // also consider that initialValue may be less than min - (initialValue is Logic - ? SummationBase.biggestVal(initialValue.width) - : LogicValue.ofInferWidth(initialValue).toInt()); + var maxPosMagnitude = SummationBase.biggestVal(width); + var maxNegMagnitude = BigInt.zero; + for (final intf in interfaces) { + final maxMagnitude = intf.fixedAmount != null + ? intf.amount.value.toBigInt() + : SummationBase.biggestVal(intf.width); + + if (intf.increments) { + maxPosMagnitude += maxMagnitude; + } else { + maxNegMagnitude += maxMagnitude; + } + } + + // also consider that initialValue may be less than min or more than max + final maxInitialValueMagnitude = initialValue is Logic + ? SummationBase.biggestVal(initialValue.width) + : LogicValue.ofInferWidth(initialValue).toBigInt(); + maxPosMagnitude += maxInitialValueMagnitude; + maxNegMagnitude += maxInitialValueMagnitude; // calculate the largest number that we could have in intermediate - final internalWidth = log2Ceil(maxPosMagnitude + maxNegMagnitude + 1); + final internalWidth = max( + (maxPosMagnitude + maxNegMagnitude + BigInt.one).bitLength, width + 1); final initialValueLogicExt = initialValueLogic.zeroExtend(internalWidth); final minValueLogicExt = minValueLogic.zeroExtend(internalWidth); diff --git a/lib/src/summation/summation_base.dart b/lib/src/summation/summation_base.dart index 77dd12115..d0c0e1344 100644 --- a/lib/src/summation/summation_base.dart +++ b/lib/src/summation/summation_base.dart @@ -1,22 +1,38 @@ +// Copyright (C) 2024 Intel Corporation +// SPDX-License-Identifier: BSD-3-Clause +// +// sum.dart +// A flexible sum implementation. +// +// 2024 August 26 +// Author: Max Korbel + import 'dart:math'; import 'package:collection/collection.dart'; +import 'package:meta/meta.dart'; import 'package:rohd/rohd.dart'; import 'package:rohd_hcl/rohd_hcl.dart'; -import 'package:meta/meta.dart'; +/// A base class for modules doing summation operation such as [Counter] and +/// [Sum]. abstract class SummationBase extends Module { + /// The width of the resulting sum. final int width; + /// An internal [Logic] version of the provided initial value. @protected late final Logic initialValueLogic; + /// An internal [Logic] version of the provided minimum value. @protected late final Logic minValueLogic; + /// An internal [Logic] version of the provided maximum value. @protected late final Logic maxValueLogic; + /// The "internal" versions of the [SumInterface]s for this computation. @protected late final List interfaces; @@ -24,29 +40,27 @@ abstract class SummationBase extends Module { /// will wrap around (overflow/underflow) at the `maxValue` and `minValue`. final bool saturates; - //TODO: review doc comments! - /// Indicates whether the sum has reached the maximum value. - /// - /// If it [saturates], then the result will be equal to the maximum value. - /// Otherwise, the value may have overflowed to any value, but the net sum - /// before overflow will have been greater than the maximum value. - // Logic get reachedMax => output('reachedMax'); - - /// Indicates whether the sum has reached the minimum value. - /// - /// If it [saturates], then the result will be equal to the minimum value. - /// Otherwise, the value may have underflowed to any value, but the net sum - /// before underflow will have been less than the minimum value. - // Logic get reachedMin => output('reachedMin'); -/** - * -*/ - + /// Indicates whether the sum is greater than the maximum value. The actual + /// resulting value depends on the provided [saturates] behavior (staturation + /// or overflow). Logic get overflowed => output('overflowed'); + + /// Indicates whether the sum is less than the minimum value. The actual + /// resulting value depends on the provided [saturates] behavior (saturation + /// or underflow). Logic get underflowed => output('underflowed'); + + /// Indicates whether the sum (including potential saturation) is currently + /// equal to the maximum. Logic get equalsMax => output('equalsMax'); + + /// Indicates whether the sum (including potential saturation) is currently + /// equal to the minimum. Logic get equalsMin => output('equalsMin'); + /// Sums the values across the provided [interfaces] within the bounds of the + /// [saturates] behavior, [initialValue], [maxValue], and [minValue], with the + /// specified [width], if provided. SummationBase( List interfaces, { dynamic initialValue = 0, @@ -78,11 +92,18 @@ abstract class SummationBase extends Module { addOutput('equalsMin'); } - /// TODO doc + /// Takes a given `dynamic` [value] and converts it into a [Logic], + /// potentially as an input port, if necessary. Logic _dynamicInputToLogic(String name, dynamic value) { if (value is Logic) { return addInput(name, value.zeroExtend(width), width: width); } else { + // if it's a LogicValue, then don't assume the width is necessary + if (value is LogicValue) { + // ignore: parameter_assignments + value = value.toBigInt(); + } + if (LogicValue.ofInferWidth(value).width > width) { throw RohdHclException( 'Value $value for $name is too large for width $width'); @@ -92,10 +113,12 @@ abstract class SummationBase extends Module { } } + /// Returns the largest value that can fit within [width]. @protected - static int biggestVal(int width) => (1 << width) - 1; + static BigInt biggestVal(int width) => BigInt.two.pow(width) - BigInt.one; - //TODO doc + /// Infers the width of the sum based on the provided values, interfaces, and + /// optionally the provided [width]. static int _inferWidth( List values, int? width, List interfaces) { if (width != null) { diff --git a/test/summation/sum_test.dart b/test/summation/sum_test.dart index 23f10c4c3..5e18b694b 100644 --- a/test/summation/sum_test.dart +++ b/test/summation/sum_test.dart @@ -318,6 +318,8 @@ void main() { // TODO: test enable + //TODO: test very large possible numbers (>64 bit adds) + test('random', () { final rand = Random(123); From 9cc4dce15143b4bf02abdfc1ff1e5adbfb90f8a2 Mon Sep 17 00:00:00 2001 From: Max Korbel Date: Thu, 5 Sep 2024 19:03:08 -0700 Subject: [PATCH 29/43] more documentation, more enable --- lib/src/summation/counter.dart | 131 +++++++++++++++++-------------- lib/src/summation/sum.dart | 56 +++++++------ test/summation/counter_test.dart | 8 +- test/summation/sum_test.dart | 2 + 4 files changed, 109 insertions(+), 88 deletions(-) diff --git a/lib/src/summation/counter.dart b/lib/src/summation/counter.dart index fca621066..ba188a131 100644 --- a/lib/src/summation/counter.dart +++ b/lib/src/summation/counter.dart @@ -11,69 +11,13 @@ import 'package:rohd/rohd.dart'; import 'package:rohd_hcl/rohd_hcl.dart'; import 'package:rohd_hcl/src/summation/summation_base.dart'; +/// Keeps a count of the running sum of any number of sources with optional +/// configuration for widths, saturation behavior, and restarting. class Counter extends SummationBase { /// The output value of the counter. Logic get count => output('count'); - Counter.simple({ - required Logic clk, - required Logic reset, - int by = 1, - Logic? enable, - int minValue = 0, - int? maxValue, - int? width, - Logic? restart, - bool saturates = false, - bool incremenents = true, - int resetValue = 0, - String name = 'counter', - }) : this([ - SumInterface( - width: width, - fixedAmount: by, - hasEnable: enable != null, - increments: incremenents) - ..enable?.gets(enable!), - ], - clk: clk, - reset: reset, - resetValue: resetValue, - restart: restart, - maxValue: maxValue, - minValue: minValue, - width: width, - saturates: saturates, - name: name); - - factory Counter.ofLogics( - List logics, { - required Logic clk, - required Logic reset, - Logic? restart, - dynamic resetValue = 0, - dynamic maxValue, - dynamic minValue = 0, - int? width, - bool saturates = false, - String name = 'counter', - }) => - Counter( - logics - .map((e) => SumInterface(width: e.width)..amount.gets(e)) - .toList(), - clk: clk, - reset: reset, - resetValue: resetValue, - maxValue: maxValue, - minValue: minValue, - width: width, - saturates: saturates, - restart: restart, - name: name, - ); - - /// TODO + /// Creates a counter that increments according to the provided [interfaces]. /// /// The [width] can be either explicitly provided or inferred from other /// values such as a [maxValue], [minValue], or [resetValue] that contain @@ -133,4 +77,73 @@ class Counter extends SummationBase { equalsMax <= count.eq(maxValueLogic); equalsMin <= count.eq(minValueLogic); } + + /// A simplified constructor for [Counter] that accepts a single amount to + /// count [by] (up or down based on [increments]) along with much of the other + /// available configuration in the default constructor. + Counter.simple({ + required Logic clk, + required Logic reset, + int by = 1, + Logic? enable, + int minValue = 0, + int? maxValue, + int? width, + Logic? restart, + bool saturates = false, + bool increments = true, + int resetValue = 0, + String name = 'counter', + }) : this([ + SumInterface( + width: width, + fixedAmount: by, + hasEnable: enable != null, + increments: increments) + ..enable?.gets(enable!), + ], + clk: clk, + reset: reset, + resetValue: resetValue, + restart: restart, + maxValue: maxValue, + minValue: minValue, + width: width, + saturates: saturates, + name: name); + + /// Creates a [Counter] that counts up by all of the provided [logics], including + /// much of the other available configuration in the default constructor. + /// + /// All [logics] are always incrementing and controled optionally by a single + /// [enable]. + factory Counter.ofLogics( + List logics, { + required Logic clk, + required Logic reset, + Logic? restart, + dynamic resetValue = 0, + dynamic maxValue, + dynamic minValue = 0, + Logic? enable, + int? width, + bool saturates = false, + String name = 'counter', + }) => + Counter( + logics + .map((e) => SumInterface(width: e.width, hasEnable: enable != null) + ..amount.gets(e) + ..enable?.gets(enable!)) + .toList(), + clk: clk, + reset: reset, + resetValue: resetValue, + maxValue: maxValue, + minValue: minValue, + width: width, + saturates: saturates, + restart: restart, + name: name, + ); } diff --git a/lib/src/summation/sum.dart b/lib/src/summation/sum.dart index dc94c88e8..9d4f23a20 100644 --- a/lib/src/summation/sum.dart +++ b/lib/src/summation/sum.dart @@ -35,33 +35,13 @@ extension on SumInterface { } } +/// Computes a sum of any number of sources with optional configuration for +/// widths and saturation behavior. class Sum extends SummationBase { + /// The resulting [sum]. Logic get sum => output('sum'); - /// TODO - /// - /// All [logics]s are always enabled and incrementing. - factory Sum.ofLogics( - List logics, { - dynamic initialValue = 0, - dynamic maxValue, - dynamic minValue = 0, - int? width, - bool saturates = false, - String name = 'sum', - }) => - Sum( - logics - .map((e) => SumInterface(width: e.width)..amount.gets(e)) - .toList(), - initialValue: initialValue, - maxValue: maxValue, - minValue: minValue, - width: width, - saturates: saturates, - name: name); - - /// TODO + /// Computes a [sum] across the provided [interfaces]. /// /// The [width] can be either explicitly provided or inferred from other /// values such as a [maxValue], [minValue], or [initialValue] that contain @@ -172,4 +152,32 @@ class Sum extends SummationBase { equalsMax <= internalValue.eq(upperSaturation); equalsMin <= internalValue.eq(lowerSaturation); } + + /// Computes a [sum] across the provided [logics]. + /// + /// All [logics] are always incrementing and controled optionally by a single + /// [enable]. + factory Sum.ofLogics( + List logics, { + dynamic initialValue = 0, + dynamic maxValue, + dynamic minValue = 0, + Logic? enable, + int? width, + bool saturates = false, + String name = 'sum', + }) => + Sum( + logics + .map( + (e) => SumInterface(width: e.width, hasEnable: enable != null) + ..amount.gets(e) + ..enable?.gets(enable!)) + .toList(), + initialValue: initialValue, + maxValue: maxValue, + minValue: minValue, + width: width, + saturates: saturates, + name: name); } diff --git a/test/summation/counter_test.dart b/test/summation/counter_test.dart index 8533219a8..acdc99730 100644 --- a/test/summation/counter_test.dart +++ b/test/summation/counter_test.dart @@ -60,6 +60,8 @@ void main() { await Simulator.endSimulation(); }); + //TODO: test counter ofLogics with enable + test('simple up counter', () async { final clk = SimpleClockGenerator(10).clk; final reset = Logic(); @@ -121,11 +123,7 @@ void main() { final clk = SimpleClockGenerator(10).clk; final reset = Logic(); final counter = Counter.simple( - clk: clk, - reset: reset, - maxValue: 5, - resetValue: 5, - incremenents: false); + clk: clk, reset: reset, maxValue: 5, resetValue: 5, increments: false); await counter.build(); diff --git a/test/summation/sum_test.dart b/test/summation/sum_test.dart index 5e18b694b..0a94ba8d1 100644 --- a/test/summation/sum_test.dart +++ b/test/summation/sum_test.dart @@ -316,6 +316,8 @@ void main() { }); }); + //TODO: test sum ofLogics with enable + // TODO: test enable //TODO: test very large possible numbers (>64 bit adds) From a702d7766c145c7f79a2c073249998de47c63227 Mon Sep 17 00:00:00 2001 From: Max Korbel Date: Thu, 5 Sep 2024 19:31:37 -0700 Subject: [PATCH 30/43] more documentation improvements --- lib/src/summation/counter.dart | 7 ++++--- lib/src/summation/sum.dart | 2 +- lib/src/summation/sum_interface.dart | 10 +++++++++- 3 files changed, 14 insertions(+), 5 deletions(-) diff --git a/lib/src/summation/counter.dart b/lib/src/summation/counter.dart index ba188a131..896d8e11a 100644 --- a/lib/src/summation/counter.dart +++ b/lib/src/summation/counter.dart @@ -56,7 +56,7 @@ class Counter extends SummationBase { interfaces, initialValue: restart != null ? mux(restart, initialValueLogic, count) : count, - maxValue: maxValueLogic, //TODO: this is a problem, constant for %2 + maxValue: maxValueLogic, minValue: minValueLogic, width: width, saturates: saturates, @@ -112,8 +112,9 @@ class Counter extends SummationBase { saturates: saturates, name: name); - /// Creates a [Counter] that counts up by all of the provided [logics], including - /// much of the other available configuration in the default constructor. + /// Creates a [Counter] that counts up by all of the provided [logics], + /// including much of the other available configuration in the default + /// constructor. /// /// All [logics] are always incrementing and controled optionally by a single /// [enable]. diff --git a/lib/src/summation/sum.dart b/lib/src/summation/sum.dart index 9d4f23a20..bb9005eed 100644 --- a/lib/src/summation/sum.dart +++ b/lib/src/summation/sum.dart @@ -107,7 +107,7 @@ class Sum extends SummationBase { ..gets(minValueLogicExt + zeroPoint); final internalValue = Logic(name: 'internalValue', width: internalWidth); - sum <= (internalValue - zeroPoint).getRange(0, this.width); + sum <= (internalValue - zeroPoint).getRange(0, width); final preAdjustmentValue = Logic(name: 'preAdjustmentValue', width: internalWidth); diff --git a/lib/src/summation/sum_interface.dart b/lib/src/summation/sum_interface.dart index f9d4fc8e5..c582e2567 100644 --- a/lib/src/summation/sum_interface.dart +++ b/lib/src/summation/sum_interface.dart @@ -9,7 +9,10 @@ import 'package:rohd/rohd.dart'; +/// A [PairInterface] representing an amount and behavior for inclusion in a +/// sum or count. class SumInterface extends PairInterface { + /// Whether an [enable] signal is present on this interface. final bool hasEnable; /// The [amount] to increment/decrement by, depending on [increments]. @@ -22,14 +25,18 @@ class SumInterface extends PairInterface { /// Present if [hasEnable] is `true`. Logic? get enable => tryPort('enable'); + /// The [width] of the [amount]. final int width; /// If `true`, will increment. If `false`, will decrement. final bool increments; + /// If non-`null`, the constant value of [amount]. final dynamic fixedAmount; - /// TODO + /// Creates a new [SumInterface] with a fixed or variable [amount], optionally + /// with an [enable], in either positive or negative direction based on + /// [increments]. /// /// If [width] is `null`, it can be inferred from [fixedAmount] if provided /// with a type that contains width information (e.g. a [LogicValue]). There @@ -57,6 +64,7 @@ class SumInterface extends PairInterface { ]); } + /// Creates a clone of this [SumInterface] for things like [pairConnectIO]. SumInterface.clone(SumInterface other) : this( fixedAmount: other.fixedAmount, From db7b90f3a69ed5dcf88fe604b05ec6c5ca2f73d1 Mon Sep 17 00:00:00 2001 From: Max Korbel Date: Fri, 6 Sep 2024 09:12:58 -0700 Subject: [PATCH 31/43] more tests --- test/summation/counter_test.dart | 15 +++++++-- test/summation/sum_test.dart | 55 +++++++++++++++++++++++++++++--- 2 files changed, 63 insertions(+), 7 deletions(-) diff --git a/test/summation/counter_test.dart b/test/summation/counter_test.dart index acdc99730..5a8565ef3 100644 --- a/test/summation/counter_test.dart +++ b/test/summation/counter_test.dart @@ -21,8 +21,10 @@ void main() { test('basic 1-bit rolling counter', () async { final clk = SimpleClockGenerator(10).clk; final reset = Logic(); + final enable = Logic()..inject(1); - final counter = Counter.ofLogics([Const(1)], clk: clk, reset: reset); + final counter = + Counter.ofLogics([Const(1)], clk: clk, reset: reset, enable: enable); await counter.build(); @@ -52,6 +54,15 @@ void main() { await clk.nextNegedge; expect(counter.count.value.toInt(), 1); + enable.inject(0); + // wait a cycle, no change + await clk.nextNegedge; + expect(counter.count.value.toInt(), 1); + await clk.nextNegedge; + expect(counter.count.value.toInt(), 1); + + enable.inject(1); + await clk.nextNegedge; await clk.nextNegedge; await clk.nextNegedge; @@ -60,8 +71,6 @@ void main() { await Simulator.endSimulation(); }); - //TODO: test counter ofLogics with enable - test('simple up counter', () async { final clk = SimpleClockGenerator(10).clk; final reset = Logic(); diff --git a/test/summation/sum_test.dart b/test/summation/sum_test.dart index 0a94ba8d1..536c0d471 100644 --- a/test/summation/sum_test.dart +++ b/test/summation/sum_test.dart @@ -214,7 +214,21 @@ void main() { expect(goldenSum(intfs, width: dut.width, maxVal: 5), 1); }); - //TODO: test modulo requirement -- if sum is >2x greater than saturation + test('large increment on small width needs modulo', () async { + final a = Logic(width: 8); + final b = Logic(width: 8); + final dut = Sum.ofLogics( + [a, b], + width: 2, + ); + await dut.build(); + + expect(dut.width, 2); + + a.put(10); + b.put(11); + expect(dut.sum.value.toInt(), 1); + }); test('one up, one down', () { final intfs = [ @@ -316,11 +330,44 @@ void main() { }); }); - //TODO: test sum ofLogics with enable + test('sum ofLogics enable', () async { + final a = Logic(width: 3); + final b = Logic(width: 3); + final en = Logic(); + final dut = Sum.ofLogics( + [a, b], + width: 2, + enable: en, + ); + await dut.build(); + + expect(dut.width, 2); + + a.put(2); + b.put(3); + en.put(false); + expect(dut.sum.value.toInt(), 0); + en.put(true); + expect(dut.sum.value.toInt(), 1); + }); - // TODO: test enable + test('very large widths', () async { + final initVal = BigInt.two.pow(66) + BigInt.one; + final a = BigInt.two.pow(80) + BigInt.one; + final b = BigInt.from(1234); + final dut = Sum( + [ + SumInterface(fixedAmount: a), + SumInterface(width: 75)..amount.gets(Const(b, width: 75)), + ], + initialValue: initVal, + maxValue: BigInt.two.pow(82), + ); + await dut.build(); - //TODO: test very large possible numbers (>64 bit adds) + expect(dut.sum.value.toBigInt(), initVal + a + b); + expect(dut.width, greaterThan(80)); + }); test('random', () { final rand = Random(123); From 392e5fb80120f0f95dc3358b7ebc8d2a6413e452 Mon Sep 17 00:00:00 2001 From: Max Korbel Date: Mon, 9 Sep 2024 16:10:03 -0700 Subject: [PATCH 32/43] add docs about summatino --- doc/components/summation.md | 40 +++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100644 doc/components/summation.md diff --git a/doc/components/summation.md b/doc/components/summation.md new file mode 100644 index 000000000..bf13556d1 --- /dev/null +++ b/doc/components/summation.md @@ -0,0 +1,40 @@ +# Summation + +ROHD-HCL comes with combinational and sequential components for summing any number of input values, including support for increment/decrement and saturation/roll-over behavior. + +## SumInterface + +The `SumInterface` is shared between [`Sum`](#sum) and [`Counter`](#counter) components and represents a single element to be summed. Each instance of a `SumInterface` has an associated `amount`, which could either be a fixed constant value (`fixedAmount`) or a dynamic `Logic`. Fixed amounts will do some automatic width inference, unless a `width` is specified. The interface can also optionally include an enable signal. It is implemented as a `PairInterface` where all ports are `fromProvider`. Each interface may be either incrementing or decrementing. + +```dart +// An interface with a dynamic 4-bit amount to increment by +SumInterface(width: 4); +``` + +## Sum + +The `Sum` component takes a list of `SumInterface`s and adds them all up. The `saturates` configuration enables saturation behavior, otherwise there will be roll-over at overflow/underflow of the counter at `minValue` and `maxValue`. The sum can also be given an `initialValue` to start at. + +Internally, the `Sum` component builds a wider bus which is large enough to hold the biggest possible intermediate value during summation before consideration of overflow and saturation. + +Note that the implementation's size and complexity when synthesized depend significantly on the configuration. For example, if everything is a nice power-of-2 number, then the logic is much simpler than otherwise where hardware modulos may be required to properly handle roll-over/under scenarios. + +A simpler `Sum.ofLogics` constructor has less configurability, but can be passed a simple `List` without needing to construct per-element interfaces. + +```dart +// An 8-bit sum of a list of `SumInterface`s +Sum(intfs, width: 8); +``` + +## Counter + +The `Counter` component is a similarly configurable version of the `Sum` which maintains a sequential element to store the previous value. + +One additional nice feature of the `Counter` is that it supports a `restart` in addition to the normal `reset`. While `reset` will reset the internal flops, `restart` will re-initialize the internal `Sum` back to the reset value, but still perform the computation on inputs in the current cycle. This is especially useful in case you want to restart a counter while events worth counting may still be occuring. + +The `Counter` also has a `Counter.simple` constructor which is intended for very basic scenarios like "count up by 1 each cycle". + +```dart +// A counter which increments by 1 each cycle up to 5, then rolls over. +Counter.simple(clk: clk, reset: reset, maxValue: 5); +``` From ae5c0c3e702f3ded1f736c28b823db0a026561ae Mon Sep 17 00:00:00 2001 From: Max Korbel Date: Mon, 9 Sep 2024 16:13:13 -0700 Subject: [PATCH 33/43] add links to docs --- doc/README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/doc/README.md b/doc/README.md index 1ea46548c..d2d3472cf 100644 --- a/doc/README.md +++ b/doc/README.md @@ -56,7 +56,8 @@ Some in-development items will have opened issues, as well. Feel free to create - Binary-Coded Decimal (BCD) - [Rotate](./components/rotate.md) - Counters - - Binary counter + - [Summation](./components/summation.md#sum) + - [Binary counter](./components/summation.md#counter) - Gray counter - Pseudorandom - LFSR From aad96e0a0f13a0db3f49cbb80a41e5f1469967cd Mon Sep 17 00:00:00 2001 From: Max Korbel Date: Mon, 9 Sep 2024 16:30:51 -0700 Subject: [PATCH 34/43] starting on configurator for counter and sum --- .../lib/hcl/view/screen/content_widget.dart | 8 ++- .../components/config_counter.dart | 29 +++++++++++ .../config_knobs/config_knobs.dart | 2 + .../config_knobs/int_config_knob.dart | 6 ++- .../int_optional_config_knob.dart | 50 +++++++++++++++++++ .../config_knobs/string_config_knob.dart | 2 +- .../config_knobs/text_config_knob.dart | 22 ++++++++ 7 files changed, 112 insertions(+), 7 deletions(-) create mode 100644 lib/src/component_config/components/config_counter.dart create mode 100644 lib/src/component_config/config_knobs/int_optional_config_knob.dart create mode 100644 lib/src/component_config/config_knobs/text_config_knob.dart diff --git a/confapp/lib/hcl/view/screen/content_widget.dart b/confapp/lib/hcl/view/screen/content_widget.dart index 4e807935b..e28e43166 100644 --- a/confapp/lib/hcl/view/screen/content_widget.dart +++ b/confapp/lib/hcl/view/screen/content_widget.dart @@ -69,15 +69,13 @@ class _SVGeneratorState extends State { ); final key = Key(label); - if (knob is IntConfigKnob || knob is StringConfigKnob) { + if (knob is TextInputConfigKnob) { selector = TextFormField( key: key, - initialValue: (knob is IntConfigKnob && knob.value > 255) - ? '0x${knob.value.toRadixString(16)}' - : knob.value.toString(), + initialValue: knob.valueString, decoration: decoration, validator: (value) { - if (value!.isEmpty) { + if (value.isEmpty && !value.allowEmpty) { return 'Please enter value'; } return null; diff --git a/lib/src/component_config/components/config_counter.dart b/lib/src/component_config/components/config_counter.dart new file mode 100644 index 000000000..326f96817 --- /dev/null +++ b/lib/src/component_config/components/config_counter.dart @@ -0,0 +1,29 @@ +// Copyright (C) 2024 Intel Corporation +// SPDX-License-Identifier: BSD-3-Clause +// +// config_counter.dart +// Configurator for a Counter. +// +// 2024 September 6 +// Author: Max Korbel + +import 'package:rohd_hcl/rohd_hcl.dart'; + +class SumInterfaceKnob extends GroupOfKnobs { + ToggleConfigKnob hasEnableKnob = ToggleConfigKnob(value: false); + + ToggleConfigKnob isFixedValueKnob = ToggleConfigKnob(value: false); + + IntOptionalConfigKnob widthKnob = IntOptionalConfigKnob(value: 8); + + SumInterfaceKnob() : super({}); + + @override + Map> get subKnobs => { + 'Has Enable': hasEnableKnob, + 'Is Fixed Value': isFixedValueKnob, + 'Width': widthKnob, + }; +} + +// class CounterConfigurator extends Configurator {} diff --git a/lib/src/component_config/config_knobs/config_knobs.dart b/lib/src/component_config/config_knobs/config_knobs.dart index 6f33c5ac3..bf271a366 100644 --- a/lib/src/component_config/config_knobs/config_knobs.dart +++ b/lib/src/component_config/config_knobs/config_knobs.dart @@ -5,6 +5,8 @@ export 'choice_config_knob.dart'; export 'config_knob.dart'; export 'group_of_knobs_knob.dart'; export 'int_config_knob.dart'; +export 'int_optional_config_knob.dart'; export 'list_of_knobs_knob.dart'; export 'string_config_knob.dart'; +export 'text_config_knob.dart'; export 'toggle_config_knob.dart'; diff --git a/lib/src/component_config/config_knobs/int_config_knob.dart b/lib/src/component_config/config_knobs/int_config_knob.dart index e5a707941..2405271fc 100644 --- a/lib/src/component_config/config_knobs/int_config_knob.dart +++ b/lib/src/component_config/config_knobs/int_config_knob.dart @@ -9,7 +9,7 @@ import 'package:rohd_hcl/rohd_hcl.dart'; /// A knob to store an [int]. -class IntConfigKnob extends ConfigKnob { +class IntConfigKnob extends TextConfigKnob { /// Creates a new config knob with the specified initial [value]. IntConfigKnob({required super.value}); @@ -17,6 +17,10 @@ class IntConfigKnob extends ConfigKnob { Map toJson() => {'value': value > 255 ? '0x${value.toRadixString(16)}' : value}; + @override + String get valueString => + value > 255 ? '0x${value.toRadixString(16)}' : value.toString(); + @override void loadJson(Map decodedJson) { final val = decodedJson['value']; diff --git a/lib/src/component_config/config_knobs/int_optional_config_knob.dart b/lib/src/component_config/config_knobs/int_optional_config_knob.dart new file mode 100644 index 000000000..bcc134724 --- /dev/null +++ b/lib/src/component_config/config_knobs/int_optional_config_knob.dart @@ -0,0 +1,50 @@ +// Copyright (C) 2024 Intel Corporation +// SPDX-License-Identifier: BSD-3-Clause +// +// int_optional_config_knob.dart +// A knob for holding a number or null. +// +// 2024 September 9 + +import 'package:rohd_hcl/rohd_hcl.dart'; + +/// A knob to store an [int]. +class IntOptionalConfigKnob extends TextConfigKnob { + /// Creates a new config knob with the specified initial [value]. + IntOptionalConfigKnob({required super.value}); + + @override + Map toJson() => { + 'value': value == null + ? null + : value! > 255 + ? '0x${value!.toRadixString(16)}' + : value + }; + + @override + String get valueString => value == null + ? '' + : value! > 255 + ? '0x${value!.toRadixString(16)}' + : value.toString(); + + @override + bool get allowEmpty => true; + + @override + void loadJson(Map decodedJson) { + final val = decodedJson['value']; + if (val == null) { + value = null; + } else if (val is String) { + if (val.isEmpty) { + value = null; + } else { + value = int.parse(val); + } + } else { + value = val as int; + } + } +} diff --git a/lib/src/component_config/config_knobs/string_config_knob.dart b/lib/src/component_config/config_knobs/string_config_knob.dart index fc90d6689..4664bb692 100644 --- a/lib/src/component_config/config_knobs/string_config_knob.dart +++ b/lib/src/component_config/config_knobs/string_config_knob.dart @@ -9,7 +9,7 @@ import 'package:rohd_hcl/rohd_hcl.dart'; /// A knob for holding a [String]. -class StringConfigKnob extends ConfigKnob { +class StringConfigKnob extends TextConfigKnob { /// Creates a new knob with the specified initial [value]. StringConfigKnob({required super.value}); } diff --git a/lib/src/component_config/config_knobs/text_config_knob.dart b/lib/src/component_config/config_knobs/text_config_knob.dart new file mode 100644 index 000000000..7f3bead7d --- /dev/null +++ b/lib/src/component_config/config_knobs/text_config_knob.dart @@ -0,0 +1,22 @@ +// Copyright (C) 2024 Intel Corporation +// SPDX-License-Identifier: BSD-3-Clause +// +// text_config_knob.dart +// Definition of a configuration knob which can be configured by parsing text. +// +// 2023 December 5 + +import 'package:rohd_hcl/rohd_hcl.dart'; + +/// A configuration knob for use in [Configurator]s which can be configured by +/// text. +abstract class TextConfigKnob extends ConfigKnob { + /// Creates a new knob with an initial [value]. + TextConfigKnob({required super.value}); + + /// A [String] representation of the [value]. + String get valueString => value.toString(); + + /// Whether the knob allows an empty string as input. + bool get allowEmpty => false; +} From 1e24518073ed8887552fbf105b38ce20c2e41b9c Mon Sep 17 00:00:00 2001 From: Max Korbel Date: Mon, 9 Sep 2024 17:42:46 -0700 Subject: [PATCH 35/43] add configurator for sum and counter --- .github/workflows/general.yml | 4 +- .../lib/hcl/view/screen/content_widget.dart | 11 +- confapp/web/flutter_bootstrap.js | 15 +++ confapp/web/index.html | 20 +-- .../components/component_registry.dart | 2 + .../components/components.dart | 1 + .../components/config_counter.dart | 29 ----- .../components/config_summation.dart | 123 ++++++++++++++++++ .../config_knobs/int_config_knob.dart | 6 +- .../int_optional_config_knob.dart | 9 ++ .../config_knobs/string_config_knob.dart | 3 + .../config_knobs/text_config_knob.dart | 3 + lib/src/summation/sum_interface.dart | 6 +- test/configurator_test.dart | 20 +++ 14 files changed, 192 insertions(+), 60 deletions(-) create mode 100644 confapp/web/flutter_bootstrap.js delete mode 100644 lib/src/component_config/components/config_counter.dart create mode 100644 lib/src/component_config/components/config_summation.dart diff --git a/.github/workflows/general.yml b/.github/workflows/general.yml index 50ca44b85..d1aea52f2 100644 --- a/.github/workflows/general.yml +++ b/.github/workflows/general.yml @@ -84,7 +84,7 @@ jobs: uses: flutter-actions/setup-flutter@v2 with: channel: stable - version: 3.16.3 + version: 3.24.2 - name: Analyze flutter source run: tool/gh_actions/analyze_flutter_source.sh @@ -151,7 +151,7 @@ jobs: uses: flutter-actions/setup-flutter@v2 with: channel: stable - version: 3.16.3 + version: 3.24.2 - name: Build static site run: tool/gh_actions/hcl_site_generation_build.sh diff --git a/confapp/lib/hcl/view/screen/content_widget.dart b/confapp/lib/hcl/view/screen/content_widget.dart index e28e43166..93dac8076 100644 --- a/confapp/lib/hcl/view/screen/content_widget.dart +++ b/confapp/lib/hcl/view/screen/content_widget.dart @@ -69,13 +69,13 @@ class _SVGeneratorState extends State { ); final key = Key(label); - if (knob is TextInputConfigKnob) { + if (knob is TextConfigKnob) { selector = TextFormField( key: key, initialValue: knob.valueString, decoration: decoration, validator: (value) { - if (value.isEmpty && !value.allowEmpty) { + if ((value == null || value.isEmpty) && !knob.allowEmpty) { return 'Please enter value'; } return null; @@ -89,12 +89,7 @@ class _SVGeneratorState extends State { return; } - if (knob is IntConfigKnob) { - final newValue = int.tryParse(value.toString()); - knob.value = newValue ?? knob.value; - } else { - knob.value = value; - } + knob.setValueFromString(value); }); }, ); diff --git a/confapp/web/flutter_bootstrap.js b/confapp/web/flutter_bootstrap.js new file mode 100644 index 000000000..ca3d38642 --- /dev/null +++ b/confapp/web/flutter_bootstrap.js @@ -0,0 +1,15 @@ +{{flutter_js}} +{{flutter_build_config}} +_flutter.loader.load({ + serviceWorkerSettings: { + serviceWorkerVersion: {{flutter_service_worker_version}}, + }, + onEntrypointLoaded: function(engineInitializer) { + engineInitializer.initializeEngine().then(function(appRunner) { + appRunner.runApp(); + }); + }, + config: { + canvasKitBaseUrl: 'canvaskit/', + } +}); \ No newline at end of file diff --git a/confapp/web/index.html b/confapp/web/index.html index 164aee141..79ef299f6 100644 --- a/confapp/web/index.html +++ b/confapp/web/index.html @@ -32,28 +32,10 @@ confapp - - + diff --git a/lib/src/component_config/components/component_registry.dart b/lib/src/component_config/components/component_registry.dart index 6d3665e1d..5d452b111 100644 --- a/lib/src/component_config/components/component_registry.dart +++ b/lib/src/component_config/components/component_registry.dart @@ -15,6 +15,8 @@ List get componentRegistry => [ FifoConfigurator(), EccConfigurator(), RoundRobinArbiterConfigurator(), + CounterConfigurator(), + SumConfigurator(), PriorityArbiterConfigurator(), RippleCarryAdderConfigurator(), CarrySaveMultiplierConfigurator(), diff --git a/lib/src/component_config/components/components.dart b/lib/src/component_config/components/components.dart index 32c33fda9..0d7e59a64 100644 --- a/lib/src/component_config/components/components.dart +++ b/lib/src/component_config/components/components.dart @@ -15,3 +15,4 @@ export 'config_ripple_carry_adder.dart'; export 'config_rotate.dart'; export 'config_round_robin_arbiter.dart'; export 'config_sort.dart'; +export 'config_summation.dart'; diff --git a/lib/src/component_config/components/config_counter.dart b/lib/src/component_config/components/config_counter.dart deleted file mode 100644 index 326f96817..000000000 --- a/lib/src/component_config/components/config_counter.dart +++ /dev/null @@ -1,29 +0,0 @@ -// Copyright (C) 2024 Intel Corporation -// SPDX-License-Identifier: BSD-3-Clause -// -// config_counter.dart -// Configurator for a Counter. -// -// 2024 September 6 -// Author: Max Korbel - -import 'package:rohd_hcl/rohd_hcl.dart'; - -class SumInterfaceKnob extends GroupOfKnobs { - ToggleConfigKnob hasEnableKnob = ToggleConfigKnob(value: false); - - ToggleConfigKnob isFixedValueKnob = ToggleConfigKnob(value: false); - - IntOptionalConfigKnob widthKnob = IntOptionalConfigKnob(value: 8); - - SumInterfaceKnob() : super({}); - - @override - Map> get subKnobs => { - 'Has Enable': hasEnableKnob, - 'Is Fixed Value': isFixedValueKnob, - 'Width': widthKnob, - }; -} - -// class CounterConfigurator extends Configurator {} diff --git a/lib/src/component_config/components/config_summation.dart b/lib/src/component_config/components/config_summation.dart new file mode 100644 index 000000000..48843b7c4 --- /dev/null +++ b/lib/src/component_config/components/config_summation.dart @@ -0,0 +1,123 @@ +// Copyright (C) 2024 Intel Corporation +// SPDX-License-Identifier: BSD-3-Clause +// +// config_counter.dart +// Configurator for a Counter. +// +// 2024 September 6 +// Author: Max Korbel + +import 'package:rohd/rohd.dart'; +import 'package:rohd/src/module.dart'; +import 'package:rohd_hcl/rohd_hcl.dart'; +import 'package:rohd_hcl/src/component_config/component_config.dart'; + +class SumInterfaceKnob extends GroupOfKnobs { + ToggleConfigKnob hasEnableKnob = ToggleConfigKnob(value: false); + + ToggleConfigKnob isFixedValueKnob = ToggleConfigKnob(value: false); + IntConfigKnob fixedValueKnob = IntConfigKnob(value: 1); + + IntOptionalConfigKnob widthKnob = IntOptionalConfigKnob(value: 8); + + ToggleConfigKnob incrementsKnob = ToggleConfigKnob(value: true); + + SumInterfaceKnob() : super({}, name: 'Sum Interface'); + + @override + Map> get subKnobs => { + 'Has Enable': hasEnableKnob, + 'Is Fixed Value': isFixedValueKnob, + if (isFixedValueKnob.value) 'Fixed Value': fixedValueKnob, + 'Width': widthKnob, + 'Increments': incrementsKnob, + }; +} + +abstract class SummationConfigurator extends Configurator { + final ListOfKnobsKnob sumInterfaceKnobs = ListOfKnobsKnob( + count: 1, + generateKnob: (i) => SumInterfaceKnob(), + name: 'Sum Interfaces', + ); + + final IntOptionalConfigKnob widthKnob = IntOptionalConfigKnob(value: null); + final IntOptionalConfigKnob minValueKnob = IntOptionalConfigKnob(value: 0); + final IntOptionalConfigKnob maxValueKnob = IntOptionalConfigKnob(value: null); + final ToggleConfigKnob saturatesKnob = ToggleConfigKnob(value: false); + + @override + Map> get knobs => { + 'Sum Interfaces': sumInterfaceKnobs, + 'Width': widthKnob, + 'Minimum Value': minValueKnob, + 'Maximum Value': maxValueKnob, + 'Saturates': saturatesKnob, + }; +} + +class SumConfigurator extends SummationConfigurator { + final IntConfigKnob initialValueKnob = IntConfigKnob(value: 0); + + @override + Map> get knobs => { + ...super.knobs, + 'Initial Value': initialValueKnob, + }; + + @override + Module createModule() => Sum( + sumInterfaceKnobs.knobs + .map((e) => e as SumInterfaceKnob) + .map((e) => SumInterface( + hasEnable: e.hasEnableKnob.value, + fixedAmount: + e.isFixedValueKnob.value ? e.fixedValueKnob.value : null, + width: e.widthKnob.value, + increments: e.incrementsKnob.value, + )) + .toList(), + initialValue: initialValueKnob.value, + width: widthKnob.value, + minValue: minValueKnob.value, + maxValue: maxValueKnob.value, + saturates: saturatesKnob.value, + ); + + @override + String get name => 'Sum'; +} + +class CounterConfigurator extends SummationConfigurator { + final IntConfigKnob resetValueKnob = IntConfigKnob(value: 0); + + @override + Map> get knobs => { + ...super.knobs, + 'Reset Value': resetValueKnob, + }; + + @override + Module createModule() => Counter( + sumInterfaceKnobs.knobs + .map((e) => e as SumInterfaceKnob) + .map((e) => SumInterface( + hasEnable: e.hasEnableKnob.value, + fixedAmount: + e.isFixedValueKnob.value ? e.fixedValueKnob.value : null, + width: e.widthKnob.value, + increments: e.incrementsKnob.value, + )) + .toList(), + resetValue: resetValueKnob.value, + width: widthKnob.value, + minValue: minValueKnob.value, + maxValue: maxValueKnob.value, + saturates: saturatesKnob.value, + clk: Logic(), + reset: Logic(), + ); + + @override + String get name => 'Counter'; +} diff --git a/lib/src/component_config/config_knobs/int_config_knob.dart b/lib/src/component_config/config_knobs/int_config_knob.dart index 2405271fc..e85ab2375 100644 --- a/lib/src/component_config/config_knobs/int_config_knob.dart +++ b/lib/src/component_config/config_knobs/int_config_knob.dart @@ -25,9 +25,13 @@ class IntConfigKnob extends TextConfigKnob { void loadJson(Map decodedJson) { final val = decodedJson['value']; if (val is String) { - value = int.parse(val); + setValueFromString(val); } else { value = val as int; } } + + @override + void setValueFromString(String valueString) => + value = int.tryParse(valueString) ?? value; } diff --git a/lib/src/component_config/config_knobs/int_optional_config_knob.dart b/lib/src/component_config/config_knobs/int_optional_config_knob.dart index bcc134724..99e6b1233 100644 --- a/lib/src/component_config/config_knobs/int_optional_config_knob.dart +++ b/lib/src/component_config/config_knobs/int_optional_config_knob.dart @@ -47,4 +47,13 @@ class IntOptionalConfigKnob extends TextConfigKnob { value = val as int; } } + + @override + void setValueFromString(String valueString) { + if (valueString.isEmpty) { + value = null; + } else { + value = int.parse(valueString); + } + } } diff --git a/lib/src/component_config/config_knobs/string_config_knob.dart b/lib/src/component_config/config_knobs/string_config_knob.dart index 4664bb692..a8a590a9f 100644 --- a/lib/src/component_config/config_knobs/string_config_knob.dart +++ b/lib/src/component_config/config_knobs/string_config_knob.dart @@ -12,4 +12,7 @@ import 'package:rohd_hcl/rohd_hcl.dart'; class StringConfigKnob extends TextConfigKnob { /// Creates a new knob with the specified initial [value]. StringConfigKnob({required super.value}); + + @override + void setValueFromString(String valueString) => value = valueString; } diff --git a/lib/src/component_config/config_knobs/text_config_knob.dart b/lib/src/component_config/config_knobs/text_config_knob.dart index 7f3bead7d..8ed33342b 100644 --- a/lib/src/component_config/config_knobs/text_config_knob.dart +++ b/lib/src/component_config/config_knobs/text_config_knob.dart @@ -17,6 +17,9 @@ abstract class TextConfigKnob extends ConfigKnob { /// A [String] representation of the [value]. String get valueString => value.toString(); + /// Sets the [value] from a [String]. + void setValueFromString(String valueString); + /// Whether the knob allows an empty string as input. bool get allowEmpty => false; } diff --git a/lib/src/summation/sum_interface.dart b/lib/src/summation/sum_interface.dart index c582e2567..5e5ff7b0f 100644 --- a/lib/src/summation/sum_interface.dart +++ b/lib/src/summation/sum_interface.dart @@ -8,6 +8,7 @@ // Author: Max Korbel import 'package:rohd/rohd.dart'; +import 'package:rohd_hcl/rohd_hcl.dart'; /// A [PairInterface] representing an amount and behavior for inclusion in a /// sum or count. @@ -55,7 +56,10 @@ class SumInterface extends PairInterface { this.increments = true, int? width, this.hasEnable = false}) - : width = width ?? LogicValue.ofInferWidth(fixedAmount).width { + : width = (width == null && fixedAmount == null) + ? throw RohdHclException( + 'Must provide either a fixedAmount or width.') + : width ?? LogicValue.ofInferWidth(fixedAmount).width { setPorts([ if (fixedAmount == null) Port('amount', this.width), if (hasEnable) Port('enable'), diff --git a/test/configurator_test.dart b/test/configurator_test.dart index 01316bf46..ba0ed391e 100644 --- a/test/configurator_test.dart +++ b/test/configurator_test.dart @@ -285,6 +285,26 @@ void main() { expect(sv, contains('swizzle')); }); + test('sum configurator', () { + final cfg = SumConfigurator(); + cfg.initialValueKnob.value = 6; + cfg.widthKnob.value = 10; + cfg.minValueKnob.value = 5; + cfg.maxValueKnob.value = 25; + cfg.saturatesKnob.value = true; + + final mod = cfg.createModule() as Sum; + + // ignore: invalid_use_of_protected_member + expect(mod.initialValueLogic.value.toInt(), 6); + expect(mod.width, 10); + // ignore: invalid_use_of_protected_member + expect(mod.minValueLogic.value.toInt(), 5); + // ignore: invalid_use_of_protected_member + expect(mod.maxValueLogic.value.toInt(), 25); + expect(mod.saturates, true); + }); + group('configurator builds', () { for (final componentConfigurator in componentRegistry) { test(componentConfigurator.name, () async { From ff5936d49585a68fe3e764470aa103519c358f1f Mon Sep 17 00:00:00 2001 From: Max Korbel Date: Mon, 9 Sep 2024 18:15:12 -0700 Subject: [PATCH 36/43] add doc comments to configurators --- .../components/config_summation.dart | 25 +++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/lib/src/component_config/components/config_summation.dart b/lib/src/component_config/components/config_summation.dart index 48843b7c4..01a864096 100644 --- a/lib/src/component_config/components/config_summation.dart +++ b/lib/src/component_config/components/config_summation.dart @@ -8,20 +8,28 @@ // Author: Max Korbel import 'package:rohd/rohd.dart'; -import 'package:rohd/src/module.dart'; import 'package:rohd_hcl/rohd_hcl.dart'; -import 'package:rohd_hcl/src/component_config/component_config.dart'; +import 'package:rohd_hcl/src/summation/summation_base.dart'; +/// A knob for a single sum interface. class SumInterfaceKnob extends GroupOfKnobs { + /// Whether the sum interface has an enable signal. ToggleConfigKnob hasEnableKnob = ToggleConfigKnob(value: false); + /// Whether the sum interface has a fixed value. ToggleConfigKnob isFixedValueKnob = ToggleConfigKnob(value: false); + + /// The fixed value of the sum interface, only present when [isFixedValueKnob] + /// is true. IntConfigKnob fixedValueKnob = IntConfigKnob(value: 1); + /// The width of the sum interface. IntOptionalConfigKnob widthKnob = IntOptionalConfigKnob(value: 8); + /// Whether the sum interface increments (vs. decrements). ToggleConfigKnob incrementsKnob = ToggleConfigKnob(value: true); + /// Creates a new sum interface knob. SumInterfaceKnob() : super({}, name: 'Sum Interface'); @override @@ -34,16 +42,25 @@ class SumInterfaceKnob extends GroupOfKnobs { }; } +/// A configurator for a module like [SummationBase]. abstract class SummationConfigurator extends Configurator { + /// The interface knobs. final ListOfKnobsKnob sumInterfaceKnobs = ListOfKnobsKnob( count: 1, generateKnob: (i) => SumInterfaceKnob(), name: 'Sum Interfaces', ); + /// The width. final IntOptionalConfigKnob widthKnob = IntOptionalConfigKnob(value: null); + + /// The minimum value. final IntOptionalConfigKnob minValueKnob = IntOptionalConfigKnob(value: 0); + + /// The maximum value. final IntOptionalConfigKnob maxValueKnob = IntOptionalConfigKnob(value: null); + + /// Whether the output saturates (vs. rolling over/under). final ToggleConfigKnob saturatesKnob = ToggleConfigKnob(value: false); @override @@ -56,7 +73,9 @@ abstract class SummationConfigurator extends Configurator { }; } +/// A configurator for [Sum]. class SumConfigurator extends SummationConfigurator { + /// The initial value. final IntConfigKnob initialValueKnob = IntConfigKnob(value: 0); @override @@ -88,7 +107,9 @@ class SumConfigurator extends SummationConfigurator { String get name => 'Sum'; } +/// A configurator for [Counter]. class CounterConfigurator extends SummationConfigurator { + /// The reset value. final IntConfigKnob resetValueKnob = IntConfigKnob(value: 0); @override From 0229ff45c3a95e8d1e545ab69b53c0dfa7b62dd5 Mon Sep 17 00:00:00 2001 From: Max Korbel Date: Tue, 10 Sep 2024 12:21:15 -0700 Subject: [PATCH 37/43] fix yosys generated json for schematic viewing --- confapp/lib/hcl/view/screen/schematic.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/confapp/lib/hcl/view/screen/schematic.dart b/confapp/lib/hcl/view/screen/schematic.dart index b756e756b..32c160379 100644 --- a/confapp/lib/hcl/view/screen/schematic.dart +++ b/confapp/lib/hcl/view/screen/schematic.dart @@ -75,7 +75,7 @@ const _suffix = r""" svg.call(zoom) .on("dblclick.zoom", null) - graph = JSON.parse(exmpl); + graph = JSON.parse(exmpl.replaceAll("\\", "__")); if ("creator" in graph) { graph = d3.HwSchematic.fromYosys(graph); } From 56dd78640fd19f0b3b8fd40c1748dacbfe5394d5 Mon Sep 17 00:00:00 2001 From: Max Korbel Date: Wed, 11 Sep 2024 14:00:27 -0700 Subject: [PATCH 38/43] some minor cleanup from review --- lib/src/component_config/components/config_summation.dart | 4 ++-- lib/src/summation/counter.dart | 2 +- lib/src/summation/sum.dart | 7 ++++++- lib/src/summation/summation_base.dart | 2 +- test/configurator_test.dart | 4 +++- 5 files changed, 13 insertions(+), 6 deletions(-) diff --git a/lib/src/component_config/components/config_summation.dart b/lib/src/component_config/components/config_summation.dart index 01a864096..0e2824e0a 100644 --- a/lib/src/component_config/components/config_summation.dart +++ b/lib/src/component_config/components/config_summation.dart @@ -1,8 +1,8 @@ // Copyright (C) 2024 Intel Corporation // SPDX-License-Identifier: BSD-3-Clause // -// config_counter.dart -// Configurator for a Counter. +// config_summation.dart +// Configurators for summation. // // 2024 September 6 // Author: Max Korbel diff --git a/lib/src/summation/counter.dart b/lib/src/summation/counter.dart index 896d8e11a..8b3a486f9 100644 --- a/lib/src/summation/counter.dart +++ b/lib/src/summation/counter.dart @@ -116,7 +116,7 @@ class Counter extends SummationBase { /// including much of the other available configuration in the default /// constructor. /// - /// All [logics] are always incrementing and controled optionally by a single + /// All [logics] are always incrementing and controlled optionally by a single /// [enable]. factory Counter.ofLogics( List logics, { diff --git a/lib/src/summation/sum.dart b/lib/src/summation/sum.dart index bb9005eed..da319c591 100644 --- a/lib/src/summation/sum.dart +++ b/lib/src/summation/sum.dart @@ -121,6 +121,8 @@ class Sum extends SummationBase { .map((e) => e._combAdjustments(s, internalValue)) .flattened, + //TODO: control whether these are exported as outputs + // identify if we're at a max/min case overflowed < s(internalValue).gt(upperSaturation), underflowed < s(internalValue).lt(lowerSaturation), @@ -128,6 +130,8 @@ class Sum extends SummationBase { // useful as an internal node for debug/visibility preAdjustmentValue < s(internalValue), + //TODO: only generated underflow/overflow stuff if it's possible + // handle saturation or over/underflow If.block([ Iff.s( @@ -149,13 +153,14 @@ class Sum extends SummationBase { ]), ]); + //TODO: control whether these actually get generated equalsMax <= internalValue.eq(upperSaturation); equalsMin <= internalValue.eq(lowerSaturation); } /// Computes a [sum] across the provided [logics]. /// - /// All [logics] are always incrementing and controled optionally by a single + /// All [logics] are always incrementing and controlled optionally by a single /// [enable]. factory Sum.ofLogics( List logics, { diff --git a/lib/src/summation/summation_base.dart b/lib/src/summation/summation_base.dart index d0c0e1344..53e7e11b6 100644 --- a/lib/src/summation/summation_base.dart +++ b/lib/src/summation/summation_base.dart @@ -1,7 +1,7 @@ // Copyright (C) 2024 Intel Corporation // SPDX-License-Identifier: BSD-3-Clause // -// sum.dart +// summation_base.dart // A flexible sum implementation. // // 2024 August 26 diff --git a/test/configurator_test.dart b/test/configurator_test.dart index ba0ed391e..9042d3a3d 100644 --- a/test/configurator_test.dart +++ b/test/configurator_test.dart @@ -6,6 +6,8 @@ // // 2023 December 6 +import 'dart:io'; + import 'package:rohd/rohd.dart'; import 'package:rohd_hcl/rohd_hcl.dart'; import 'package:rohd_hcl/src/component_config/components/component_registry.dart'; @@ -285,7 +287,7 @@ void main() { expect(sv, contains('swizzle')); }); - test('sum configurator', () { + test('sum configurator', () async { final cfg = SumConfigurator(); cfg.initialValueKnob.value = 6; cfg.widthKnob.value = 10; From d53d2360d5ee60d462a04ff40146b6045223646c Mon Sep 17 00:00:00 2001 From: Max Korbel Date: Wed, 11 Sep 2024 14:05:49 -0700 Subject: [PATCH 39/43] update comments --- lib/src/summation/counter.dart | 7 +++++++ lib/src/summation/sum.dart | 17 +++++++++++++++-- 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/lib/src/summation/counter.dart b/lib/src/summation/counter.dart index 8b3a486f9..ed30009d3 100644 --- a/lib/src/summation/counter.dart +++ b/lib/src/summation/counter.dart @@ -31,6 +31,13 @@ class Counter extends SummationBase { /// The [restart] input can be used to restart the counter to a new value, but /// also continue to increment in that same cycle. This is distinct from /// [reset] which will reset the counter, holding the [count] at [resetValue]. + /// + /// If [saturates] is `true`, then it will saturate at the [maxValue] and + /// [minValue]. If `false`, will wrap around (overflow/underflow) at the + /// [maxValue] and [minValue]. The [equalsMax], [equalsMin], [overflowed], + /// and [underflowed] outputs can be used to determine if the sum is at the + /// maximum, minimum, (would have) overflowed, or (would have) underflowed, + /// respectively. Counter( super.interfaces, { required Logic clk, diff --git a/lib/src/summation/sum.dart b/lib/src/summation/sum.dart index da319c591..e6ba90c9f 100644 --- a/lib/src/summation/sum.dart +++ b/lib/src/summation/sum.dart @@ -54,6 +54,13 @@ class Sum extends SummationBase { /// /// It is expected that [maxValue] is at least [minValue], or else results may /// be unpredictable. + /// + /// If [saturates] is `true`, then it will saturate at the [maxValue] and + /// [minValue]. If `false`, will wrap around (overflow/underflow) at the + /// [maxValue] and [minValue]. The [equalsMax], [equalsMin], [overflowed], + /// and [underflowed] outputs can be used to determine if the sum is at the + /// maximum, minimum, (would have) overflowed, or (would have) underflowed, + /// respectively. Sum( super.interfaces, { dynamic initialValue = 0, @@ -112,11 +119,17 @@ class Sum extends SummationBase { final preAdjustmentValue = Logic(name: 'preAdjustmentValue', width: internalWidth); + // here we use an `ssa` block to iteratively update the value of + // `internalValue` based on the adjustments from the interfaces and + // saturation/roll-over behavior + // + // For more details, see: + // https://intel.github.io/rohd-website/blog/combinational-ssa/ Combinational.ssa((s) => [ // initialize s(internalValue) < initialValueLogicExt + zeroPoint, - // perform increments and decrements + // perform increments and decrements per-interface ...interfaces .map((e) => e._combAdjustments(s, internalValue)) .flattened, @@ -158,7 +171,7 @@ class Sum extends SummationBase { equalsMin <= internalValue.eq(lowerSaturation); } - /// Computes a [sum] across the provided [logics]. + /// Computes a [Sum] across the provided [logics]. /// /// All [logics] are always incrementing and controlled optionally by a single /// [enable]. From e57f346e0c4e00df7f6531aca7236083e55b52a5 Mon Sep 17 00:00:00 2001 From: Max Korbel Date: Wed, 11 Sep 2024 14:23:42 -0700 Subject: [PATCH 40/43] clean up TODOs, move to an issue --- lib/src/summation/sum.dart | 5 ----- 1 file changed, 5 deletions(-) diff --git a/lib/src/summation/sum.dart b/lib/src/summation/sum.dart index e6ba90c9f..87fd42f44 100644 --- a/lib/src/summation/sum.dart +++ b/lib/src/summation/sum.dart @@ -134,8 +134,6 @@ class Sum extends SummationBase { .map((e) => e._combAdjustments(s, internalValue)) .flattened, - //TODO: control whether these are exported as outputs - // identify if we're at a max/min case overflowed < s(internalValue).gt(upperSaturation), underflowed < s(internalValue).lt(lowerSaturation), @@ -143,8 +141,6 @@ class Sum extends SummationBase { // useful as an internal node for debug/visibility preAdjustmentValue < s(internalValue), - //TODO: only generated underflow/overflow stuff if it's possible - // handle saturation or over/underflow If.block([ Iff.s( @@ -166,7 +162,6 @@ class Sum extends SummationBase { ]), ]); - //TODO: control whether these actually get generated equalsMax <= internalValue.eq(upperSaturation); equalsMin <= internalValue.eq(lowerSaturation); } From 2cca44e233c0210ba4f59f383b47097a0a2445e3 Mon Sep 17 00:00:00 2001 From: Max Korbel Date: Wed, 11 Sep 2024 14:27:14 -0700 Subject: [PATCH 41/43] fix lint --- test/configurator_test.dart | 2 -- 1 file changed, 2 deletions(-) diff --git a/test/configurator_test.dart b/test/configurator_test.dart index 9042d3a3d..3e2561956 100644 --- a/test/configurator_test.dart +++ b/test/configurator_test.dart @@ -6,8 +6,6 @@ // // 2023 December 6 -import 'dart:io'; - import 'package:rohd/rohd.dart'; import 'package:rohd_hcl/rohd_hcl.dart'; import 'package:rohd_hcl/src/component_config/components/component_registry.dart'; From 6e9e79f3858681e25d63ad3fbed7ba636d8bf751 Mon Sep 17 00:00:00 2001 From: Max Korbel Date: Mon, 16 Sep 2024 12:45:08 -0700 Subject: [PATCH 42/43] minor comment updates --- lib/src/component_config/config_knobs/text_config_knob.dart | 1 + lib/src/summation/counter.dart | 6 +++--- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/lib/src/component_config/config_knobs/text_config_knob.dart b/lib/src/component_config/config_knobs/text_config_knob.dart index 8ed33342b..503a0cf16 100644 --- a/lib/src/component_config/config_knobs/text_config_knob.dart +++ b/lib/src/component_config/config_knobs/text_config_knob.dart @@ -5,6 +5,7 @@ // Definition of a configuration knob which can be configured by parsing text. // // 2023 December 5 +// Author: Max Korbel import 'package:rohd_hcl/rohd_hcl.dart'; diff --git a/lib/src/summation/counter.dart b/lib/src/summation/counter.dart index ed30009d3..6094f2fc4 100644 --- a/lib/src/summation/counter.dart +++ b/lib/src/summation/counter.dart @@ -85,9 +85,9 @@ class Counter extends SummationBase { equalsMin <= count.eq(minValueLogic); } - /// A simplified constructor for [Counter] that accepts a single amount to - /// count [by] (up or down based on [increments]) along with much of the other - /// available configuration in the default constructor. + /// A simplified constructor for [Counter] that accepts a single fixed amount + /// to count [by] (up or down based on [increments]) along with much of the + /// other available configuration in the default [Counter] constructor. Counter.simple({ required Logic clk, required Logic reset, From 06fd2df3b82ddd3255b8c74eca462cc29c686c72 Mon Sep 17 00:00:00 2001 From: Max Korbel Date: Mon, 16 Sep 2024 13:41:46 -0700 Subject: [PATCH 43/43] remove prints from divider test --- test/arithmetic/divider_test.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/arithmetic/divider_test.dart b/test/arithmetic/divider_test.dart index 80134f4d7..6a0154616 100644 --- a/test/arithmetic/divider_test.dart +++ b/test/arithmetic/divider_test.dart @@ -471,7 +471,7 @@ void main() { group('divider tests', () { test('VF tests', () async { // Set the logger level - Logger.root.level = Level.ALL; + Logger.root.level = Level.OFF; // Create the testbench final intf = MultiCycleDividerInterface();