-
Notifications
You must be signed in to change notification settings - Fork 33
Implement floating-point Fast Fourier Transform #210
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
base: main
Are you sure you want to change the base?
Changes from 3 commits
87ca92b
492150c
b5e4895
cc2059c
e2463a1
468abe4
51e4e7c
f2ee651
7712bf8
0b8c3bf
7481def
823c57b
f88400b
f634b18
972fcb1
2537fcb
7c1ad04
dffb187
ca5422f
bc4f828
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,71 @@ | ||
| // Copyright (C) 2025 Intel Corporation | ||
| // SPDX-License-Identifier: BSD-3-Clause | ||
|
|
||
| import 'package:rohd/rohd.dart'; | ||
|
|
||
| // class FFT extends Module { | ||
| // LogicArray get out => output('out') as LogicArray; | ||
| // | ||
| // FFT(Logic en, Logic clk, Logic reset, LogicArray input, {super.name = 'fft'}) | ||
| // : assert(input.dimensions.length == 1) { | ||
| // final int length = input.dimensions[0]; | ||
| // if ((length & (~(length - 1))) != length) { | ||
| // assert(false); | ||
| // } | ||
| // final int log2Length = log2Ceil(length); | ||
| // | ||
| // input = addInputArray( | ||
| // 'input_array', | ||
| // input, | ||
| // dimensions: input.dimensions, // it seems like these are needed | ||
| // elementWidth: input.elementWidth, | ||
| // numUnpackedDimensions: input.numUnpackedDimensions, | ||
| // ); | ||
| // | ||
| // List<LogicArray> stageArrays = List.generate( | ||
| // log2Length + 1, | ||
| // (stage) => LogicArray( | ||
| // input.dimensions, | ||
| // input.elementWidth, | ||
| // name: 'stage${stage}Array', | ||
| // numUnpackedDimensions: input.numUnpackedDimensions, | ||
| // ), | ||
| // ); | ||
| // | ||
| // LogicArray out = addOutputArray( | ||
| // 'out', | ||
| // dimensions: input.dimensions, | ||
| // elementWidth: input.elementWidth, | ||
| // numUnpackedDimensions: input.numUnpackedDimensions, | ||
| // ); | ||
| // out <= stageArrays[log2Length]; | ||
| // | ||
| // List<List<Conditional> Function(PipelineStageInfo)> fftStages = []; | ||
| // | ||
| // fftStages.add((p) => [stageArrays[0] < BitReverse(input).out]); | ||
| // | ||
| // for (var s = 1; s <= log2Length; s++) { | ||
| // final m = 1 << s; | ||
| // final mShift = log2Ceil(m); | ||
| // | ||
| // Counter i = Counter(en, reset, clk, width: log2Length - 1); | ||
| // | ||
| // Logic k = (i.val >> (mShift - 1)) << mShift; | ||
| // Logic j = (i.val & Const((m >> 1) - 1, width: i.width)); | ||
| // } | ||
| // | ||
| // // ReadyValidPipeline() | ||
| // | ||
| // // for s = 1 to log(n) do | ||
| // // m ← 2s | ||
| // // ωm ← exp(−2πi/m) | ||
| // // for k = 0 to n-1 by m do | ||
| // // ω ← 1 | ||
| // // for j = 0 to m/2 – 1 do | ||
| // // t ← ω A[k + j + m/2] | ||
| // // u ← A[k + j] | ||
| // // A[k + j] ← u + t | ||
| // // A[k + j + m/2] ← u – t | ||
| // // ω ← ω ωm | ||
| // } | ||
| // } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,31 @@ | ||
| // Copyright (C) 2025 Intel Corporation | ||
| // SPDX-License-Identifier: BSD-3-Clause | ||
|
|
||
| import 'package:rohd/rohd.dart'; | ||
| import 'package:rohd_hcl/src/arithmetic/signals/floating_point_logics/complex_floating_point_logic.dart'; | ||
|
|
||
| class Butterfly extends Module { | ||
| final ComplexFloatingPoint inA; | ||
| final ComplexFloatingPoint inB; | ||
| final ComplexFloatingPoint twiddleFactor; | ||
|
|
||
| late final outA = inA.clone()..gets(output('outA')); | ||
| late final outB = inA.clone()..gets(output('outB')); | ||
|
|
||
| Butterfly( | ||
| {required this.inA, | ||
| required this.inB, | ||
| required this.twiddleFactor, | ||
| super.name = 'butterfly'}) { | ||
| addInput('inA', inA, width: inA.width); | ||
|
||
| addInput('inB', inB, width: inA.width); | ||
|
|
||
| final outA = addOutput('outA', width: inA.width); | ||
| final outB = addOutput('outB', width: inA.width); | ||
|
|
||
| final temp = twiddleFactor.multiplier(inB); | ||
|
|
||
| outB <= inA.adder(temp.negated); | ||
| outA <= inA.adder(temp); | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,83 @@ | ||
| // Copyright (C) 2024-2025 Intel Corporation | ||
| // SPDX-License-Identifier: BSD-3-Clause | ||
|
|
||
| import 'package:meta/meta.dart'; | ||
| import 'package:rohd/rohd.dart'; | ||
| import 'package:rohd_hcl/rohd_hcl.dart'; | ||
|
|
||
| class ComplexFloatingPoint extends LogicStructure { | ||
| final FloatingPoint realPart; | ||
|
|
||
| final FloatingPoint imaginaryPart; | ||
|
|
||
| static String _nameJoin(String? structName, String signalName) { | ||
| if (structName == null) { | ||
| return signalName; | ||
| } | ||
| return '${structName}_$signalName'; | ||
| } | ||
|
|
||
| ComplexFloatingPoint({ | ||
| required int exponentWidth, | ||
| required int mantissaWidth, | ||
| String? name, | ||
| }) : this._internal( | ||
| realPart: FloatingPoint( | ||
| exponentWidth: exponentWidth, | ||
| mantissaWidth: mantissaWidth, | ||
| name: _nameJoin(name, 're'), | ||
| ), | ||
| imaginaryPart: FloatingPoint( | ||
| exponentWidth: exponentWidth, | ||
| mantissaWidth: mantissaWidth, | ||
| name: _nameJoin(name, 'im'), | ||
| ), | ||
| name: name, | ||
| ); | ||
|
|
||
| ComplexFloatingPoint._internal( | ||
| {required this.realPart, required this.imaginaryPart, super.name}) | ||
| : assert(realPart.exponent.width == imaginaryPart.exponent.width), | ||
| assert(realPart.mantissa.width == imaginaryPart.mantissa.width), | ||
| super([realPart, imaginaryPart]); | ||
|
|
||
| @mustBeOverridden | ||
| @override | ||
| ComplexFloatingPoint clone({String? name}) => ComplexFloatingPoint( | ||
| exponentWidth: realPart.exponent.width, | ||
| mantissaWidth: realPart.mantissa.width, | ||
| name: name, | ||
| ); | ||
|
|
||
| ComplexFloatingPoint adder(ComplexFloatingPoint other) => | ||
|
Contributor
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. why not override operator
Author
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 don't want to hide the fact that it's expensive
Contributor
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. the names of the functions seem unintuitive to me, maybe if we wanted to stay more consistent with other parts of the library, these could be their own components (classes, modules) e.g. |
||
| ComplexFloatingPoint._internal( | ||
| realPart: FloatingPointAdderSinglePath(realPart, other.realPart).sum, | ||
| imaginaryPart: | ||
| FloatingPointAdderSinglePath(imaginaryPart, other.imaginaryPart) | ||
| .sum, | ||
| name: _nameJoin(name, "adder")); | ||
|
|
||
| ComplexFloatingPoint multiplier(ComplexFloatingPoint other) { | ||
| // use only 3 multipliers: https://mathworld.wolfram.com/ComplexMultiplication.html | ||
| final ac = FloatingPointMultiplierSimple(realPart, other.realPart).product; | ||
| final bd = FloatingPointMultiplierSimple(imaginaryPart, other.imaginaryPart) | ||
| .product; | ||
| final abcd = FloatingPointMultiplierSimple( | ||
| FloatingPointAdderSinglePath(realPart, imaginaryPart).sum, | ||
| FloatingPointAdderSinglePath(other.realPart, other.imaginaryPart) | ||
| .sum) | ||
| .product; | ||
|
|
||
| return ComplexFloatingPoint._internal( | ||
| realPart: FloatingPointAdderSinglePath(ac, bd.negated()).sum, | ||
| imaginaryPart: FloatingPointAdderSinglePath(abcd, | ||
| FloatingPointAdderSinglePath(ac.negated(), bd.negated()).sum) | ||
| .sum, | ||
| name: _nameJoin(name, "multiplier")); | ||
| } | ||
|
|
||
| late final ComplexFloatingPoint negated = ComplexFloatingPoint._internal( | ||
| realPart: realPart.negated(), | ||
| imaginaryPart: imaginaryPart.negated(), | ||
| name: _nameJoin(name, "negated")); | ||
| } | ||
| 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) { | ||
| 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]; | ||
| } | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,46 @@ | ||
| // Copyright (C) 2025 Intel Corporation | ||
| // SPDX-License-Identifier: BSD-3-Clause | ||
|
|
||
| import 'package:rohd/rohd.dart'; | ||
| import 'package:rohd_hcl/rohd_hcl.dart'; | ||
| import 'package:rohd_hcl/src/arithmetic/floating_point/fft/butterfly.dart'; | ||
| import 'package:rohd_hcl/src/arithmetic/signals/floating_point_logics/complex_floating_point_logic.dart'; | ||
| import 'package:test/test.dart'; | ||
|
|
||
| ComplexFloatingPoint newComplex(double real, double imaginary) { | ||
| final realFP = FloatingPoint64(); | ||
| final imaginaryFP = FloatingPoint64(); | ||
|
|
||
| final realFPValue = FloatingPoint64Value.populator().ofDouble(real); | ||
| final imaginaryFPValue = FloatingPoint64Value.populator().ofDouble(imaginary); | ||
|
|
||
| realFP.put(realFPValue); | ||
| imaginaryFP.put(imaginaryFPValue); | ||
|
|
||
| final complex = ComplexFloatingPoint( | ||
| exponentWidth: realFP.exponent.width, | ||
| mantissaWidth: realFP.mantissa.width); | ||
| complex.realPart <= realFP; | ||
| complex.imaginaryPart <= imaginaryFP; | ||
|
|
||
| return complex; | ||
| } | ||
|
|
||
| void main() { | ||
| tearDown(() async { | ||
| await Simulator.reset(); | ||
| }); | ||
|
|
||
| test('butterfly unit test', () { | ||
| final a = newComplex(1.0, 2.0); | ||
| final b = newComplex(-3.0, -4.0); | ||
| final twiddle = newComplex(1.0, 0.0); | ||
|
|
||
| final butterfly = Butterfly(inA: a, inB: b, twiddleFactor: twiddle); | ||
|
|
||
| expect(butterfly.outA.realPart.floatingPointValue.toDouble(), -2.0); | ||
| expect(butterfly.outA.imaginaryPart.floatingPointValue.toDouble(), -2.0); | ||
| expect(butterfly.outB.realPart.floatingPointValue.toDouble(), 4.0); | ||
| expect(butterfly.outB.imaginaryPart.floatingPointValue.toDouble(), 6.0); | ||
| }); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,57 @@ | ||
| // Copyright (C) 2025 Intel Corporation | ||
| // SPDX-License-Identifier: BSD-3-Clause | ||
|
|
||
| import 'package:rohd/rohd.dart'; | ||
| import 'package:rohd_hcl/rohd_hcl.dart'; | ||
| import 'package:rohd_hcl/src/arithmetic/signals/floating_point_logics/complex_floating_point_logic.dart'; | ||
| import 'package:test/test.dart'; | ||
|
|
||
| ComplexFloatingPoint newComplex(double real, double imaginary) { | ||
| final realFP = FloatingPoint64(); | ||
| final imaginaryFP = FloatingPoint64(); | ||
|
|
||
| final realFPValue = FloatingPoint64Value.populator().ofDouble(real); | ||
| final imaginaryFPValue = FloatingPoint64Value.populator().ofDouble(imaginary); | ||
|
|
||
| realFP.put(realFPValue); | ||
| imaginaryFP.put(imaginaryFPValue); | ||
|
|
||
| final complex = ComplexFloatingPoint( | ||
| exponentWidth: realFP.exponent.width, | ||
| mantissaWidth: realFP.mantissa.width); | ||
| complex.realPart <= realFP; | ||
| complex.imaginaryPart <= imaginaryFP; | ||
|
|
||
| return complex; | ||
| } | ||
|
|
||
| void main() { | ||
| tearDown(() async { | ||
| await Simulator.reset(); | ||
| }); | ||
|
|
||
| test('complex constructor', () { | ||
| final complex = newComplex(1.23, 3.45); | ||
|
|
||
| expect(complex.realPart.floatingPointValue.toDouble(), 1.23); | ||
| expect(complex.imaginaryPart.floatingPointValue.toDouble(), 3.45); | ||
| }); | ||
|
|
||
| test('complex addition', () { | ||
| final a = newComplex(1.0, 0.0); | ||
| final b = newComplex(0.0, -1.0); | ||
| final c = a.adder(b); | ||
|
|
||
| expect(c.realPart.floatingPointValue.toDouble(), 1.0); | ||
| expect(b.imaginaryPart.floatingPointValue.toDouble(), -1.0); | ||
| }); | ||
|
|
||
| test('complex multiplication', () { | ||
| final a = newComplex(1.0, 2.0); | ||
| final b = newComplex(-3.0, -4.0); | ||
| final c = a.multiplier(b); | ||
|
|
||
| expect(c.realPart.floatingPointValue.toDouble(), 5.0); | ||
| expect(c.imaginaryPart.floatingPointValue.toDouble(), -10.0); | ||
| }); | ||
| } |
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.
please update headers on the files to match the recommended style, including the name of the file, date authored, purpose of the file, etc.