基于FPGA的快速傅里叶变换加速(一) 基本原理介绍及C++代码实现
基本原理介绍及C++代码实现)
1. FFT的基本原理
1.1 离散傅里叶变换
介绍FFT之前需要首先介绍DFT(Discrete Fourier Transform),即离散傅里叶变换,是傅里叶变换在时域和频域上都呈离散的形式,将信号的时域采样变换为其 DTFT 的频域采样。
1.2 快速傅里叶变换
1.2.1 概念
FFT(Fast Fourier Transform),是一种高效实现 DFT 的算法,它对傅里叶变换的理论并没有新的发现,但是对于在计算机系统或者说数字系统中应用离散傅立叶变换,可以说是进了一大步。快速数论变换 (NTT) 是快速傅里叶变换(FFT)在数论基础上的实现。
在 1965 年,Cooley 和 Tukey 发表了快速傅里叶变换算法。事实上 FFT 早在这之前就被发现过了,但是在当时现代计算机并未问世,人们没有意识到 FFT 的重要性。一些调查者认为 FFT 是由 Runge 和 König 在 1924 年发现的。但事实上高斯早在 1805 年就发明了这个算法,但一直没有发表。
1.2.2 基本思想
FFT 算法的基本思想是分治。就 DFT 来说,它分治地来求当 的时候 的值。他的分治思想体现在将多项式分为奇次项和偶次项处理。
举个例子,对于一共8项的多项式
2 两种C++代码实现
2.1 递归实现
代码实现方面,STL 提供了复数的模板,当然也可以手动实现。两者区别在于,使用 STL 的 complex 可以调用 exp 函数求出Wn。但事实上使用欧拉公式得到的虚数来求 Wn也是等价的。
#include <cmath>
#include <complex>
#define M_PI 3.14159265358979323846
// M_PI是C++自带宏,如果报错则加上上面一行即可
typedef std::complex<double> Comp; // STL complex
const Comp I(0, 1); // i
const int MAX_N = 1 << 20;
Comp tmp[MAX_N];
//这里是封装好的DFT函数,需要自行在main函数中调用
void DFT(Comp* f, int n, int rev)
{
// rev=1,DFT; rev=-1,IDFT
if (n == 1) return;
for (int i = 0; i < n; ++i)
tmp[i] = f[i];
for (int i = 0; i < n; ++i)
{ // 偶数放左边,奇数放右边
if (i & 1)
f[n / 2 + i / 2] = tmp[i];
else
f[i / 2] = tmp[i];
}
Comp* g = f, * h = f + n / 2;
DFT(g, n / 2, rev), DFT(h, n / 2, rev); // 递归 DFT
Comp cur(1, 0), step(cos(2 * M_PI / n), sin(2 * M_PI * rev / n));
// Comp step=exp(I*(2*M_PI/n*rev));
// 两个 step 定义是等价的
for (int k = 0; k < n / 2; ++k)
{
tmp[k] = g[k] + cur * h[k];
tmp[k + n / 2] = g[k] - cur * h[k];
cur *= step;
}
for (int i = 0; i < n; ++i)
f[i] = tmp[i];
}
时间复杂度O(n log n)
2.2 分层蝶形运算/位逆序置换(bit-reversal permutation)
这里只贴出可运行的64点FFT变换,在下一篇博客中将会具体解释其原理及matlab验证
#include <stdio.h>
#include<iostream>
#include"math.h"
#include<string.h>
#define pi 3.14159265
double w[32][2];
void Reverse(double x[64][2], double y[64][2])
{
int m;
int cnt[6];
for (int i = 0; i < 64; i++)
{
m = i;
int j = 0;
for (int k = 0; k < 6; k++)
{
cnt[5 - k] = m % 2;
m = m / 2;
}
for (int k = 0; k < 3; k++)
{
int q = 0;
q = cnt[k];
cnt[k] = cnt[5 - k];
cnt[5 - k] = q;
}
for (int k = 0; k < 6; k++)
{
int qq = 1;
for (int q = k; q > 0; q--)
{
qq = 2 * qq;
}
j += cnt[k] * (32 / qq);
}
y[j][0] = x[i][0];
y[j][1] = x[i][1];
//cout << i << " " << j << endl;
}
}
void Wcalculate()
{
//这里是为了满足大作业要求,无法使用三角函数
w[0][0] = 1; w[0][1] = -0;
w[1][0] = 0.995185; w[1][1] = -0.0980171;
w[2][0] = 0.980785; w[2][1] = -0.19509;
w[3][0] = 0.95694; w[3][1] = -0.290285;
w[4][0] = 0.92388; w[4][1] = -0.382683;
w[5][0] = 0.881921; w[5][1] = -0.471397;
w[6][0] = 0.83147; w[6][1] = -0.55557;
w[7][0] = 0.77301; w[7][1] = -0.634393;
w[8][0] = 0.707107; w[8][1] = -0.707107;
w[9][0] = 0.634393; w[9][1] = -0.77301;
w[10][0] = 0.55557; w[10][1] = -0.83147;
w[11][0] = 0.471397; w[11][1] = -0.881921;
w[12][0] = 0.382683; w[12][1] = -0.92388;
w[13][0] = 0.290285; w[13][1] = -0.95694;
w[14][0] = 0.19509; w[14][1] = -0.980785;
w[15][0] = 0.0980171; w[15][1] = -0.995185;
w[16][0] = 0; w[16][1] = -1;
w[17][0] = -0.0980171; w[17][1] = -0.995185;
w[18][0] = -0.19509; w[18][1] = -0.980785;
w[19][0] = -0.290285; w[19][1] = -0.95694;
w[20][0] = -0.382683; w[20][1] = -0.92388;
w[21][0] = -0.471397; w[21][1] = -0.881921;
w[22][0] = -0.55557; w[22][1] = -0.83147;
w[23][0] = -0.634393; w[23][1] = -0.77301;
w[24][0] = -0.707107; w[24][1] = -0.707107;
w[25][0] = -0.77301; w[25][1] = -0.634393;
w[26][0] = -0.83147; w[26][1] = -0.55557;
w[27][0] = -0.881921; w[27][1] = -0.471397;
w[28][0] = -0.92388; w[28][1] = -0.382683;
w[29][0] = -0.95694; w[29][1] = -0.290285;
w[30][0] = -0.980785; w[30][1] = -0.19509;
w[31][0] = -0.995185; w[31][1] = -0.0980171;
//实际可将下代码还原即可
//double w[32][2];
//for (int i = 0; i < 32; i++)
//{
//w[i][0] = (i * pi / 32);
//w[i][1] = -(i * pi / 32);
//cout << w[i][0] << " " << w[i][1] << endl;
//}
}
char* my_itoa(int d, char str[]) //定义整形-字符型转换函数
{
int j, len = 0, sign;
if ((sign = d) < 0) //记录符号
d = -d; //使成为正数
for (int i = 0; d != 0; i++)
{
str[i] = d % 10 + '0'; //取下一个数字
d = d / 10;
if (d == 0)
{
str[i + 1] = '\0';
}
}
for (int i = 0; str[i] != '\0'; i++)
len = i;//
len++;
int i;
for (j = len - 1, i = 0; j > i; j--, i++) //生成的数字是逆序的,所以要交换
{
int t = str[j];
str[j] = str[i];
str[i] = t;
}
return str;
}
int main()
{
printf("Final work test\n");
double input[64][2];
double middle[64][2];
for (int i = 0; i < 64; i++)
{
//cout << "请输入实部" << endl; //可以通过手动输入
//cin >> middle[i][0];
input[i][0] = 1.0; //pow(2, 16)* //或者在此初始化
input[i][1] = 0.0;
//cout << input[i][0] << endl;
//input[i][0] = i;
}
//也可以在这里进行全部数据的初始化
//input[0][0] = 0.0; input[1][0] = 12.0; input[2][0] = 25.0; input[3][0] = 37.0; input[4][0] = 49.0; input[5][0] = 60.0; input[6][0] = 71.0; input[7][0] = 81.0;
//input[8][0] = 90.0; input[9][0] = 99.0; input[10][0] = 106.0; input[11][0] = 113.0; input[12][0] = 118.0; input[13][0] = 122.0; input[14][0] = 125.0; input[15][0] = 126.0;
//input[16][0] = 126.0; input[17][0] = 126.0; input[18][0] = 123.0; input[19][0] = 120.0; input[20][0] = 115.0; input[21][0] = 109.0; input[22][0] = 103.0; input[23][0] = 95.0;
//input[24][0] = 86.0; input[25][0] = 76.0; input[26][0] = 66.0; input[27][0] = 55.0; input[28][0] = 43.0; input[29][0] = 31.0; input[30][0] = 18.0; input[31][0] = 6.0;
//input[32][0] = -6.0; input[33][0] = -18.0; input[34][0] = -31.0; input[35][0] = -43.0; input[36][0] = -55.0; input[37][0] = -66.0; input[38][0] = -76.0; input[39][0] = -86.0;
//input[40][0] = -95.0; input[41][0] = -103.0; input[42][0] = -109.0; input[43][0] = -115.0; input[44][0] = -120.0; input[45][0] = -123.0; input[46][0] = -126.0; input[47][0] = -126.0;
//input[48][0] = -126.0; input[49][0] = -125.0; input[50][0] = -122.0; input[51][0] = -118.0; input[52][0] = -113.0; input[53][0] = -106.0; input[54][0] = -99.0; input[55][0] = -90.0;
//input[56][0] = -81.0; input[57][0] = -71.0; input[58][0] = -60.0; input[59][0] = -49.0; input[60][0] = -37.0; input[61][0] = -25.0; input[62][0] = -12.0; input[63][0] = 0.0;
Reverse(input, middle); //逆序
Wcalculate(); //旋转系数的生成
//蝶形运算
for (int N = 0; N < 6; N++)
{
for (int i = 0; i < 64;)
{
int qq = 1;
for (int q = N; q > 0; q--)
{
qq = qq * 2;
}
int m = qq;
int p = 32 / qq;
for (int n = 0; n < p; n++, i = i + m)
{
for (int j = 0; j < m; j++, i++)
{
double q1[2], q2[2];
q1[0] = middle[i][0]; //切记不可直接变换
q1[1] = middle[i][1];
q2[0] = middle[i + m][0];
q2[1] = middle[i + m][1];
middle[i][0] = q1[0] + q2[0] * w[j * p][0] - q2[1] * w[j * p][1];
middle[i][1] = q1[1] + q2[0] * w[j * p][1] + q2[1] * w[j * p][0];
middle[i + m][0] = q1[0] - (q2[0] * w[j * p][0] - q2[1] * w[j * p][1]);
middle[i + m][1] = q1[1] - (q2[0] * w[j * p][1] + q2[1] * w[j * p][0]);
}
}
}
}
for (int i = 0; i < 64; i++)
{
if (middle[i][1] < 0)
{
char m[80] = "\0", n[80] = "\0";
my_itoa(middle[i][0], m);
my_itoa(middle[i][1], n);
printf(m);
printf(" - ");
printf(n);
printf("j\n");
}
else
{
char m[80] = "\0", n[80] = "\0";
my_itoa(middle[i][0], m);
my_itoa(middle[i][1], n);
printf(m);
printf(" + ");
printf(n);
printf("j\n");
}
}
system("pause");
return 0;
}