-
Notifications
You must be signed in to change notification settings - Fork 33
Implement fixed-point Fast Fourier Transform #207
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,225 @@ | ||
| // Copyright (C) 2021-2024 Intel Corporation | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Try to keep the headers consistent and accurate:
See other files or CONTRIBUTING.md for examples |
||
| // SPDX-License-Identifier: BSD-3-Clause | ||
|
|
||
| import 'dart:math'; | ||
|
|
||
| import 'package:rohd/rohd.dart'; | ||
| import 'package:rohd_hcl/src/arithmetic/signals/signals.dart'; | ||
| import 'package:rohd_hcl/src/arithmetic/values/complex_fixed_point_value.dart'; | ||
| import 'package:rohd_hcl/src/exceptions.dart'; | ||
|
|
||
| class ComplexFixedPoint extends Logic { | ||
| final bool signed; | ||
|
|
||
| final int integerBits; | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. for consistency (including with #208), better might be to call |
||
|
|
||
| final int fractionalBits; | ||
|
|
||
| static int _fixedPointWidth( | ||
| bool signed, | ||
| int integerBits, | ||
| int fractionalBits, | ||
| ) => | ||
| signed ? 1 + integerBits + fractionalBits : integerBits + fractionalBits; | ||
|
|
||
| static int _complexFixedPointWidth( | ||
| bool signed, | ||
| int integerBits, | ||
| int fractionalBits, | ||
| ) => | ||
| 2 * _fixedPointWidth(signed, integerBits, fractionalBits); | ||
|
|
||
| ComplexFixedPoint({ | ||
| required this.signed, | ||
| required this.integerBits, | ||
| required this.fractionalBits, | ||
| super.name, | ||
| super.naming, | ||
| }) : assert(integerBits > 0), | ||
| assert(fractionalBits > 0), | ||
| assert(max(integerBits, fractionalBits) > 0), | ||
| super( | ||
| width: _complexFixedPointWidth(signed, integerBits, fractionalBits), | ||
| ) {} | ||
|
|
||
| ComplexFixedPoint.of(Logic signal, | ||
| {required this.signed, | ||
| required this.integerBits, | ||
| required this.fractionalBits}) | ||
| : super( | ||
| width: | ||
| _complexFixedPointWidth(signed, integerBits, fractionalBits)) { | ||
| this <= signal; | ||
| } | ||
|
|
||
| static ComplexFixedPoint fromPartsUnsafe(Logic realPart, Logic imaginaryPart, | ||
| bool signed, int integerBits, int fractionalBits) { | ||
| final result = ComplexFixedPoint( | ||
| signed: signed, | ||
| integerBits: integerBits, | ||
| fractionalBits: fractionalBits); | ||
|
|
||
| result._realPart() <= realPart; | ||
| result._imaginaryPart() <= imaginaryPart; | ||
|
|
||
| return result; | ||
| } | ||
|
|
||
| static ComplexFixedPoint fromParts( | ||
| FixedPoint realPart, FixedPoint imaginaryPart) { | ||
| assert(realPart.signed == imaginaryPart.signed); | ||
| assert(realPart.m == imaginaryPart.m); | ||
| assert(realPart.n == imaginaryPart.n); | ||
|
|
||
| final result = ComplexFixedPoint( | ||
| signed: realPart.signed, | ||
| integerBits: realPart.m, | ||
| fractionalBits: realPart.n); | ||
|
|
||
| result._realPart() <= realPart; | ||
| result._imaginaryPart() <= imaginaryPart; | ||
|
|
||
| return result; | ||
| } | ||
|
|
||
| void _verifyCompatible(ComplexFixedPoint other) { | ||
| if ((signed != other.signed) | | ||
| (integerBits != other.integerBits) | | ||
| (fractionalBits != other.fractionalBits)) { | ||
| throw RohdHclException('Inputs are not comparable.'); | ||
| } | ||
| } | ||
|
|
||
| Logic _realPart() => | ||
| getRange(0, _fixedPointWidth(signed, integerBits, fractionalBits)); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Conceptually, the I think a more convenient way to structure a grouping of two |
||
|
|
||
| FixedPoint realPart() => FixedPoint.of(_realPart(), | ||
| signed: signed, m: integerBits, n: fractionalBits); | ||
|
|
||
| Logic _imaginaryPart() => | ||
| getRange(_fixedPointWidth(signed, integerBits, fractionalBits), width); | ||
|
|
||
| FixedPoint imaginaryPart() => FixedPoint.of(_imaginaryPart(), | ||
| signed: signed, m: integerBits, n: fractionalBits); | ||
|
|
||
| @override | ||
| void put(dynamic val, {bool fill = false}) { | ||
| if (val is ComplexFixedPointValue) { | ||
| if ((signed != val.realPart.signed) | | ||
| (integerBits != val.realPart.m) | | ||
| (fractionalBits != val.realPart.n)) { | ||
| throw RohdHclException('Value is not compatible with signal.'); | ||
| } | ||
|
|
||
| realPart().put(val.realPart); | ||
| imaginaryPart().put(val.imaginaryPart); | ||
| } else { | ||
| throw RohdHclException('Only ComplexFixedPointValue is allowed'); | ||
| } | ||
| } | ||
|
|
||
| @override | ||
| Logic lt(dynamic other) { | ||
| if (other is! ComplexFixedPoint) { | ||
| throw RohdHclException('Input must be complex fixed point signal.'); | ||
| } | ||
| _verifyCompatible(other); | ||
| return realPart().lt(other.realPart()) & | ||
| imaginaryPart().lt(other.imaginaryPart()); | ||
| } | ||
|
|
||
| @override | ||
| Logic lte(dynamic other) { | ||
| if (other is! ComplexFixedPoint) { | ||
| throw RohdHclException('Input must be complex fixed point signal.'); | ||
| } | ||
| _verifyCompatible(other); | ||
| return realPart().lte(other.realPart()) & | ||
| imaginaryPart().lte(other.imaginaryPart()); | ||
| } | ||
|
|
||
| @override | ||
| Logic gt(dynamic other) { | ||
| if (other is! ComplexFixedPoint) { | ||
| throw RohdHclException('Input must be complex fixed point signal.'); | ||
| } | ||
| _verifyCompatible(other); | ||
| return realPart().gt(other.realPart()) & | ||
| imaginaryPart().gt(other.imaginaryPart()); | ||
| } | ||
|
|
||
| @override | ||
| Logic gte(dynamic other) { | ||
| if (other is! ComplexFixedPoint) { | ||
| throw RohdHclException('Input must be complex fixed point signal.'); | ||
| } | ||
| _verifyCompatible(other); | ||
| return realPart().gte(other.realPart()) & | ||
| imaginaryPart().gte(other.imaginaryPart()); | ||
| } | ||
|
|
||
| Logic _add(dynamic other) { | ||
| if (other is! ComplexFixedPoint) { | ||
| throw RohdHclException('Input must be complex fixed point signal.'); | ||
| } | ||
| _verifyCompatible(other); | ||
| return fromPartsUnsafe( | ||
| realPart() + other.realPart(), | ||
| imaginaryPart() + other.imaginaryPart(), | ||
| signed, | ||
| integerBits + 1, | ||
| fractionalBits); | ||
| } | ||
|
|
||
| Logic _multiply(dynamic other) { | ||
| if (other is! ComplexFixedPoint) { | ||
| throw RohdHclException('Input must be complex fixed point signal.'); | ||
| } | ||
| _verifyCompatible(other); | ||
|
|
||
| // assert((realPart() + imaginaryPart()).width == | ||
| // (other.realPart() + other.imaginaryPart()).width); | ||
|
|
||
| // use only 3 multipliers: https://mathworld.wolfram.com/ComplexMultiplication.html | ||
| // final abcd = (realPart() + imaginaryPart()) * | ||
| // (other.realPart() + other.imaginaryPart()); | ||
| final ri = realPart() + imaginaryPart(); | ||
| final ri2 = other.realPart() + other.imaginaryPart(); | ||
|
|
||
| final abcd = ri * ri2; | ||
| final ac = (realPart() * other.realPart()).signExtend(abcd.width); | ||
| final bd = (imaginaryPart() * other.imaginaryPart()).signExtend(abcd.width); | ||
| return fromPartsUnsafe((ac - bd).signExtend(abcd.width + 2), abcd - ac - bd, | ||
| signed, 2 * (integerBits + 1) + 3, fractionalBits * 2); | ||
| } | ||
|
|
||
| @override | ||
| Logic operator >(dynamic other) => gt(other); | ||
|
|
||
| @override | ||
| Logic operator >=(dynamic other) => gte(other); | ||
|
|
||
| @override | ||
| Logic operator +(dynamic other) => _add(other); | ||
|
|
||
| @override | ||
| Logic operator *(dynamic other) => _multiply(other); | ||
|
|
||
| @override | ||
| Logic eq(dynamic other) { | ||
| if (other is! ComplexFixedPoint) { | ||
| throw RohdHclException('Input must be complex fixed point signal.'); | ||
| } | ||
| _verifyCompatible(other); | ||
| return super.eq(other); | ||
| } | ||
|
|
||
| @override | ||
| Logic neq(dynamic other) { | ||
| if (other is! ComplexFixedPoint) { | ||
| throw RohdHclException('Input must be complex fixed point signal.'); | ||
| } | ||
| _verifyCompatible(other); | ||
| return super.neq(other); | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -123,6 +123,25 @@ class FixedPoint extends Logic { | |
| } | ||
| } | ||
|
|
||
| Logic _add(dynamic other) { | ||
| _verifyCompatible(other); | ||
| if (signed) { | ||
| final sum = Add(getRange(0, width - 1), other.getRange(0, width - 1)).sum; | ||
| final difference = | ||
| Subtract(getRange(0, width - 1), other.getRange(0, width - 1)).sum; | ||
|
|
||
| condition[0] <= this[-1]; | ||
| condition[1] <= other[-1]; | ||
|
|
||
| // mux(this[-1], mux(other[-1], ), mux()) | ||
|
|
||
| FixedPoint.of(sum, signed: signed, m: m + 1, n: n); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. looks incomplete? |
||
| } else { | ||
| final sum = Add(this, other).sum; | ||
| return FixedPoint.of(sum, signed: signed, m: m + 1, n: n); | ||
| } | ||
| } | ||
|
|
||
| /// Multiply | ||
| Logic _multiply(dynamic other) { | ||
| _verifyCompatible(other); | ||
|
|
@@ -138,6 +157,9 @@ class FixedPoint extends Logic { | |
| @override | ||
| Logic operator >=(dynamic other) => gte(other); | ||
|
|
||
| @override | ||
| Logic operator +(dynamic other) => _add(other); | ||
|
|
||
| /// multiply | ||
| @override | ||
| Logic operator *(dynamic other) => _multiply(other); | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,20 @@ | ||
| // Copyright (C) 2024-2025 Intel Corporation | ||
| // SPDX-License-Identifier: BSD-3-Clause | ||
|
|
||
| import 'dart:math'; | ||
| import 'package:meta/meta.dart'; | ||
| import 'package:rohd/rohd.dart'; | ||
| import 'package:rohd_hcl/rohd_hcl.dart'; | ||
|
|
||
| @immutable | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. i think you'll want to implement |
||
| class ComplexFixedPointValue { | ||
| final FixedPointValue realPart; | ||
| final FixedPointValue imaginaryPart; | ||
|
|
||
| ComplexFixedPointValue({ | ||
| required this.realPart, | ||
| required this.imaginaryPart, | ||
| }) : assert(realPart.signed == imaginaryPart.signed), | ||
| assert(realPart.m == imaginaryPart.m), | ||
| assert(realPart.n == imaginaryPart.n); | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,44 @@ | ||
| // Copyright (C) 2021-2024 Intel Corporation | ||
| // SPDX-License-Identifier: BSD-3-Clause | ||
|
|
||
| import 'package:rohd/rohd.dart'; | ||
| import 'package:rohd_hcl/rohd_hcl.dart'; | ||
|
|
||
| int bitReverse(int value, int bits) { | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. you could make this private ( |
||
| var reversed = 0; | ||
| for (var i = 0; i < bits; i++) { | ||
| reversed <<= 1; | ||
| reversed |= value & 1; | ||
| value >>= 1; | ||
| } | ||
| return reversed; | ||
| } | ||
|
|
||
| class BitReversal extends Module { | ||
| LogicArray get out => output('out') as LogicArray; | ||
|
|
||
| BitReversal(LogicArray input, {super.name = 'bit_reversal'}) | ||
| : assert(input.dimensions.length == 1, 'Can only bit reverse 1D arrays') { | ||
| input = addInputArray( | ||
| 'input_array', | ||
| input, | ||
| dimensions: input.dimensions, // it seems like these are needed | ||
| elementWidth: input.elementWidth, | ||
| numUnpackedDimensions: input.numUnpackedDimensions, | ||
| ); | ||
|
|
||
| final out = addOutputArray( | ||
| 'out', | ||
| dimensions: input.dimensions, | ||
| elementWidth: input.elementWidth, | ||
| numUnpackedDimensions: input.numUnpackedDimensions, | ||
| ); | ||
|
|
||
| final length = input.dimensions[0]; | ||
| final bits = log2Ceil(length); | ||
|
|
||
| for (var i = 0; i < length; i++) { | ||
| out.elements[bitReverse(i, bits)] <= input.elements[i]; | ||
| } | ||
| } | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When you add new components/files that should be publicly visible, make sure to
exportthem (e.g. seelib/src/arithmetic/signals/signals.dart, or the appropriate one per-file)