基于FPGA的快速傅里叶变换加速(一)

本文详细介绍了快速傅里叶变换(FFT)的基本原理和C++实现,包括递归方法和位逆序置换。通过分治策略,FFT将计算复杂度降低到O(nlogn)。此外,还提供了64点FFT的C++代码实例,展示了如何进行分层蝶形运算。
摘要由CSDN通过智能技术生成


基本原理介绍及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项的多项式
源自OI-Wiki
在这里插入图片描述

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;
}

本文结束,详情可见下篇博客

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

生如昭诩

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值