-
Notifications
You must be signed in to change notification settings - Fork 34
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 all 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,35 @@ | ||
| // 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 { | ||
| late final ComplexFloatingPoint outA; | ||
| late final ComplexFloatingPoint outB; | ||
|
|
||
| Butterfly({ | ||
| required ComplexFloatingPoint inA, | ||
| required ComplexFloatingPoint inB, | ||
| required ComplexFloatingPoint twiddleFactor, | ||
| super.name = 'butterfly', | ||
| }) { | ||
| final _inA = inA.clone()..gets(addInput('inA', inA, width: inA.width)); | ||
| final _inB = inA.clone()..gets(addInput('inB', inB, width: inA.width)); | ||
| final _twiddleFactor = inA.clone() | ||
| ..gets( | ||
| addInput('twiddleFactor', twiddleFactor, width: twiddleFactor.width), | ||
| ); | ||
|
|
||
| final outALogic = addOutput('outA', width: inA.width); | ||
| final outBLogic = addOutput('outB', width: inA.width); | ||
|
|
||
| final temp = _twiddleFactor.multiplier(_inB); | ||
|
|
||
| outALogic <= _inA.adder(temp.negated); | ||
| outBLogic <= _inA.adder(temp); | ||
|
|
||
| outA = inA.clone()..gets(outALogic); | ||
| outB = inA.clone()..gets(outBLogic); | ||
| } | ||
| } | ||
|
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. remember to export components that are intended to be part of the public API for ROHD-HCL
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. Please add user guide descriptions on how to use new components in the doc/ directory (including links to the pages) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,181 @@ | ||
| // Copyright (C) 2025 Intel Corporation | ||
| // SPDX-License-Identifier: BSD-3-Clause | ||
|
|
||
| import 'dart:math'; | ||
|
|
||
| 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'; | ||
|
|
||
| class BadFFTStage extends Module { | ||
|
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 "Bad"? :)
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. It's not fully pipelined
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. maybe "multi-cycle" is a better name or something (if that's what it is)? |
||
| final int logStage; | ||
| final int exponentWidth; | ||
| final int mantissaWidth; | ||
| Logic clk; | ||
| Logic reset; | ||
| Logic go; | ||
| DataPortInterface inputSamplesA; | ||
| DataPortInterface inputSamplesB; | ||
| DataPortInterface twiddleFactorROM; | ||
|
|
||
| late final Logic done; | ||
|
|
||
| BadFFTStage({ | ||
|
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. please do write doc comments |
||
| required this.logStage, | ||
| required this.exponentWidth, | ||
| required this.mantissaWidth, | ||
| required this.clk, | ||
| required this.reset, | ||
| required this.go, | ||
| required this.inputSamplesA, | ||
| required this.inputSamplesB, | ||
| required this.twiddleFactorROM, | ||
| required DataPortInterface outputSamplesA, | ||
| required DataPortInterface outputSamplesB, | ||
| super.name = 'badfftstage', | ||
| }) : assert(go.width == 1), | ||
| assert( | ||
| inputSamplesA.dataWidth == 2 * (1 + exponentWidth + mantissaWidth), | ||
| ), | ||
| assert( | ||
| inputSamplesB.dataWidth == 2 * (1 + exponentWidth + mantissaWidth), | ||
| ) { | ||
| clk = addInput('clk', clk); | ||
| reset = addInput('reset', reset); | ||
| go = addInput('go', go); | ||
| final doneInner = Logic(name: '_done'); | ||
| done = addOutput('done')..gets(doneInner); | ||
| final en = (go & ~done).named('enable'); | ||
|
|
||
| inputSamplesA = addInterfacePorts( | ||
| inputSamplesA, | ||
| inputTags: [DataPortGroup.data], | ||
| outputTags: [DataPortGroup.control], | ||
| uniquify: (name) => 'inputSamplesA$name', | ||
| ); | ||
| inputSamplesB = addInterfacePorts( | ||
| inputSamplesB, | ||
| inputTags: [DataPortGroup.data], | ||
| outputTags: [DataPortGroup.control], | ||
| uniquify: (name) => 'inputSamplesB$name', | ||
| ); | ||
| twiddleFactorROM = addInterfacePorts( | ||
| twiddleFactorROM, | ||
| inputTags: [DataPortGroup.data], | ||
| outputTags: [DataPortGroup.control], | ||
| uniquify: (name) => 'twiddleFactorROM$name', | ||
| ); | ||
|
|
||
| outputSamplesA = addInterfacePorts( | ||
| outputSamplesA, | ||
| inputTags: [DataPortGroup.control], | ||
| outputTags: [DataPortGroup.data], | ||
| uniquify: (name) => 'outputSamplesA$name', | ||
| ); | ||
| outputSamplesB = addInterfacePorts( | ||
| outputSamplesB, | ||
| inputTags: [DataPortGroup.control], | ||
| outputTags: [DataPortGroup.data], | ||
| uniquify: (name) => 'outputSamplesB$name', | ||
| ); | ||
|
|
||
| final outputSamplesWritePortA = DataPortInterface( | ||
| inputSamplesA.dataWidth, | ||
| inputSamplesA.addrWidth, | ||
| ); | ||
| final outputSamplesWritePortB = DataPortInterface( | ||
| inputSamplesA.dataWidth, | ||
| inputSamplesA.addrWidth, | ||
| ); | ||
| final outputSamplesReadPortA = DataPortInterface( | ||
| inputSamplesA.dataWidth, | ||
| inputSamplesA.addrWidth, | ||
| ); | ||
| final outputSamplesReadPortB = DataPortInterface( | ||
| inputSamplesA.dataWidth, | ||
| inputSamplesA.addrWidth, | ||
| ); | ||
|
|
||
| final n = 1 << inputSamplesA.addrWidth; | ||
| RegisterFile( | ||
| clk, | ||
| reset, | ||
| [outputSamplesWritePortA, outputSamplesWritePortB], | ||
| [outputSamplesReadPortA, outputSamplesReadPortB], | ||
| numEntries: n, | ||
| name: 'outputSamplesBuffer', | ||
| ); | ||
| outputSamplesA.data <= outputSamplesReadPortA.data; | ||
| outputSamplesReadPortA.en <= outputSamplesA.en; | ||
| outputSamplesReadPortA.addr <= outputSamplesA.addr; | ||
| outputSamplesB.data <= outputSamplesReadPortB.data; | ||
| outputSamplesReadPortB.en <= outputSamplesB.en; | ||
| outputSamplesReadPortB.addr <= outputSamplesB.addr; | ||
|
|
||
| final log2Length = inputSamplesA.addrWidth; | ||
| final m = 1 << logStage; | ||
| final mShift = log2Ceil(m); | ||
|
|
||
| final i = Counter.ofLogics( | ||
| [flop(clk, en)], | ||
| clk: clk, | ||
| reset: reset | (go & doneInner), | ||
| width: max(log2Length - 1, 1), | ||
| maxValue: n ~/ 2, | ||
| name: 'i', | ||
| ); | ||
| doneInner <= i.equalsMax; | ||
|
|
||
| final k = ((i.count >> (mShift - 1)) << mShift).named('k'); | ||
| final j = (i.count & Const((m >> 1) - 1, width: i.width)).named('j'); | ||
|
|
||
| // 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 | ||
| final addressA = (k + j).named('addressA'); | ||
| final addressB = (addressA + m ~/ 2).named('addressB'); | ||
| inputSamplesA.addr <= addressA; | ||
| inputSamplesA.en <= en; | ||
| inputSamplesB.addr <= addressB; | ||
| inputSamplesB.en <= en; | ||
| twiddleFactorROM.addr <= j; | ||
| twiddleFactorROM.en <= en; | ||
|
|
||
| final butterfly = Butterfly( | ||
| inA: ComplexFloatingPoint.of( | ||
| inputSamplesA.data, | ||
| exponentWidth: exponentWidth, | ||
| mantissaWidth: mantissaWidth, | ||
| ), | ||
| inB: ComplexFloatingPoint.of( | ||
| inputSamplesB.data, | ||
| exponentWidth: exponentWidth, | ||
| mantissaWidth: mantissaWidth, | ||
| ), | ||
| twiddleFactor: ComplexFloatingPoint.of( | ||
| twiddleFactorROM.data, | ||
| exponentWidth: exponentWidth, | ||
| mantissaWidth: mantissaWidth, | ||
| ), | ||
| ); | ||
|
|
||
| outputSamplesWritePortA.addr <= addressA; | ||
| outputSamplesWritePortA.en <= en; | ||
| outputSamplesWritePortB.addr <= addressB; | ||
| outputSamplesWritePortB.en <= en; | ||
|
|
||
| Sequential( | ||
| clk, | ||
| [ | ||
| outputSamplesWritePortA.data < butterfly.outA.named('butterflyOutA'), | ||
| outputSamplesWritePortB.data < butterfly.outB.named('butterflyOutB'), | ||
| ], | ||
| reset: reset); | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,102 @@ | ||
| // 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.of( | ||
| Logic input, { | ||
| required int exponentWidth, | ||
| required int mantissaWidth, | ||
| String? name, | ||
| }) : this._internal( | ||
| realPart: FloatingPoint( | ||
| exponentWidth: exponentWidth, | ||
| mantissaWidth: mantissaWidth, | ||
| name: _nameJoin(name, 're'), | ||
| )..gets(input.getRange(0, 1 + exponentWidth + mantissaWidth)), | ||
| imaginaryPart: FloatingPoint( | ||
| exponentWidth: exponentWidth, | ||
| mantissaWidth: mantissaWidth, | ||
| name: _nameJoin(name, 'im'), | ||
| )..gets( | ||
| input.getRange(1 + exponentWidth + mantissaWidth, input.width)), | ||
| 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')); | ||
| } | ||
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.