侧边栏壁纸
博主头像
Yaohaolin

你好,有缘人

  • 累计撰写 12 篇文章
  • 累计创建 22 个标签
  • 累计收到 1 条评论

目 录CONTENT

文章目录

UCAS高等数字集成电路大作业开源

YaoHaolin
2025-03-06 / 0 评论 / 0 点赞 / 52 阅读 / 0 字 / 正在检测是否收录...
tips:info 本文内容为中国科学院大学(UCAS)高等数字集成电路分析与设计课程寒假大作业的开源。由于是作业需要,因此对FFT的理解很浅显,实现的也很简陋。

更多本课程开源代码可进入我的开源项目中“Verilog学习”查看:开源项目

1 设计要求

本次任务要求设计一个FFT处理器(时序逻辑电路),计算256点FFT和IFFT(N = 256)。设计的FFT处理器整体采用流水线结构,能连续处理数据。
要求的顶层模块名位fft_256,输入输出端口如下表所示:

名称 方向 位宽 描述
clk I 1 系统时钟
rst_n I 1 系统复位,低有效
inv I 1 模式控制。0为FFT,1为IFFT
stb I 1 输入数据有效指示,低有效
sop_in I 1 每组输入数据中第一个有效数据指示,高有效
x_re I 16 输入数据实部,二进制补码定点格式
x_im I 16 输入数据虚部,二进制补码定点格式
valid_out O 1 输出数据有效指示,低有效
sop_out O 1 每组输出数据中第一个有效数据指示,高有效
y_re O 16 输出数据实部,二进制补码定点格式
y_im O 16 输出数据虚部,二进制补码定点格式

同时,需要给出Matlab模型,且设计的VerilogHDL代码可综合,并对结果进行误差分析。

2 FFT算法原理

2.1 基本原理

对于一个长度为N的输入序列,DFT的公式为:

X[k]=\sum^{N-1}_{n=0}x[n]e^{-j\frac{2\pi k}{N}n}=\sum^{N-1}_{n=0}x[n]W_{N}^{nk},0\leq k\leq N-1

可以将上式拆解为奇偶项,则前N/2个点可以表示为:

\begin{aligned} X[k]&=\sum_{r=0}^{\frac{N}{2}-1}X[2r]W_{\frac{N}{2}}^{rk}+W_{N}^{k}\sum_{r=0}^{\frac{N}{2}-1}X[2r+1]W_{\frac{N}{2}}^{rk}\\ &=A[k]+W_{N}^{k}B[k],k=0,1,...,\frac{N}{2}-1 \end{aligned}

同理,后N/2个点可以表示为:

X[k+\frac{N}{2}]=A[k]-W_{N}^{k}B[k],k=0,1,...,\frac{N}{2}-1

也就是说,在DFT变换中,可以通过计算前N/2个点时的中间过程得到的中间值来确定后N/2个点的值。
对A[k]和B[k]进行多次奇偶分解,直至变成两点DFT,就可以实现快速傅里叶变换(FFT)。

2.2 特征与结构

2.2.1 阶与输入数据顺序

如果对FFT的运算进行分割,会得到多阶的FFT结构,每次分割都会产生一阶,阶数m与FFT的点数有关,二者的关系可以通过表达式m=\log_2(N)来表示。其中N是FFT的点数。

因此,如下图所示,对于一个8点FFT(N = 8),可以分为3阶。
输入输出顺序1

从上图可以看出,在按照基本原理进行划分后,FFT数据的输入输出顺序发生了变化,通过分析可知,可以将输入数据的顺序进行调整,使得输出的数据变为正常顺序。
调整后的8点FFT的结构如下图所示。

输入输出顺序2

不难看出,调整后的输入数据的位置与原始的索引有很强的关联:调整后输入数据的索引的二进制值与未调整前原始索引的二进制值呈现回文关系。(如:001->100,011->110)
因此,在设计FFT处理器时,应提前利用这一特性来对输入数据进行预处理。

2.2.2 蝶形单元

FFT多次拆解后变为两点DFT后,可以明显看到算法结构图中存在如下图所示的基本单元,该基本单元称之为蝶形单元。

很明显,蝶形单元的两个输出数据(下一阶,m+1阶)与当前阶(m阶)的两个输入数据和旋转因子W_N^r有关。

由于W_N^r=e^{-j\frac{2\pi r}{N}},根据欧拉公式,可知W_N^r=\cos{\frac{2\pi r}{N}}-j\sin{\frac{2\pi r}{N}},计算时可以将旋转因子以三角函数的形式表示,这样有利于乘法的计算。

通过2.2.1所示的算法结构可知,对于一个N点的FFT,如上图所示的蝶形单元在第m阶中两个输入的间距为2^m,也就是说q=p+2^m

2.2.3 结构

通过上述分析可知,对于一个N点的FFT,在其由蝶形单元组成的算法结构中,可以按分解次数分为m阶,每阶可以按照蝶形单元分为i组,每组中有k个蝶形单元。

一个N点的FFT可以被划分为\log_2(N)阶,即256点FFT可以划分为8阶。

每一阶的组数不一样,对于256点FFT,第m阶有2^{7-m}组。对于第m阶,相邻两个组的第一个数据的索引之间的间隔为2^{m+1}也就是说,第m阶中第i组的第一个数据的索引为i2^{m+1}(不包括最后一阶,因为最后一阶只有一组)。

每一组内的单元数不一样,第m阶中,各个组内有2^{m}个单元。每个单元中成对的两个数据之间的跨度为2^{m}(如第2阶,0和4是一对)。每组内的第k个单元关联的旋转因子Wnr的值为W_{2^{m+1}}^{k}

通过上述对FFT特性和结构的分析,可以利用MATLAB写出256点FFT的等效模型,并利用VerilogHDL以RTL代码的形式在硬件级上描述出来。

2.2.4 FFT与IFFT

IFFT是FFT的逆变换,FFT将时域信号转换为频域表示,而IFFT执行相反的操作,将频域信号转换回时域。

IFFT的公式可表示为:

x[n]=\frac{1}{N}\sum_{k=0}^{N-1}X[k]e^{j\frac{2\pi nk}{N}}

通过其公式可以看出,IFFT可通过复用已有的FFT处理器来实现。具体步骤为:

  1. 输入复共轭;
  2. 使用FFT结构执行IFFT;
  3. 输出复共轭;
  4. 归一化。

其中,复共轭指的是将数据的虚部进行反号(正变负,负变正);归一化指的是对于N点FFT的逆运算N点IFFT,输出结果需要除以N。

3 利用MATLAB实现FFT

3.1 蝶形单元的实现

设计的FFT的处理器的基本结构是蝶形单元,因此,利用MATLAB实现FFT时,应先实现最基本的蝶形单元。

通过2.2.2节所示的蝶形单元结构,可以用如下的MATLAB代码来描述蝶形单元。

function [yp, yq] = butterflyUnit(xp, xq, Wnr_Zoom, ZoomPar)
    %蝶形单元
    yp = round((xp * 2 ^ (ZoomPar) + xq * Wnr_Zoom) * (2 ^ (-ZoomPar)));
    yq = round((xp * 2 ^ (ZoomPar) - xq * Wnr_Zoom) * (2 ^ (-ZoomPar)));
end

代码中,蝶形单元的输入包含两个输入的数据xp和xq、缩放一定倍数后的旋转因子Wnr_Zoom、缩放倍数ZoomPar。输出包括两个与xp和xq位置相对应的数据yp和yq。

由于在计算旋转因子Wnr的时候为了提高结果的精度,将其放大了一定的倍数,因此,蝶形单元在计算时,要将xp同样扩大到一定的倍数,与xq*Wnr_Zoom做加减运算后,还需除以该倍数。

3.2 旋转因子Wnr计算

如2.2.2节所述,旋转因子Wnr的计算利用了欧拉公式,并如3.1节所述,计算过程中应将Wnr乘以一定的倍数。

N点FFT需要计算的Wnr个数为N/2。用MATLAB实现的Wnr计算代码如下。

Wnr = zeros(1, 128);
for r = 0:127
	Wnr_factor = cos(2 * pi * r / 256) + 1i * sin(-2 * pi * r / 256);
	Wnr_integer = round(Wnr_factor * 2 ^ (ZoomPar));
	% 处理实部(补码修正)
	if real(Wnr_integer) < 0
		Wnr_real = typecast(int16(real(Wnr_integer)), 'int16');
	else
		Wnr_real = real(Wnr_integer);
	end
	% 处理虚部(同上)
	if imag(Wnr_integer) < 0
		Wnr_imag = typecast(int16(imag(Wnr_integer)), 'int16');
	else
		Wnr_imag = imag(Wnr_integer);
	end
	Wnr(r + 1) = double(Wnr_real) + 1i * double(Wnr_imag);
end

由于设计的目标是256点FFT,因此根据2.2.2节所述公式该代码计算了统共128个旋转因子。

在计算的过程中,当Wnr的实部或者虚部出现负值时,需要进行二进制补码修正,使结果正确。

因为设计目的已经明确为256点FFT,所以在Verilog的实现中,可将所有的旋转因子利用软件或编程语言提前计算并输入进Verilog代码文件中,在RTL中以查找表的形式体现,这种方式可以有效提高Verilog代码综合后的运行速度。

3.3 整体结构实现

最终要实现的256点FFT处理器的结构应该类似于2.2.1节图中的8点FFT处理器。相关结构已在2.2.3节中详细说明,因此可用Matlab写出如下算法来描述要设计的256点FFT。

xMat = zeros(9, 256);
xMat(1, :) = x(bitrevorder(0:255) + 1);
for m = 0:7
	for i = 0:(2 ^ (7 - m) - 1)
		for k = 0:(2 ^ m - 1)
			[xMat(m + 2, bitshift(i, m + 1) + k + 1), ...
				 xMat(m + 2, bitshift(i, m + 1) + k + bitshift(1, m) + 1)] ...
				= butterflyUnit( ...
				xMat(m + 1, bitshift(i, m + 1) + k + 1), ...
				xMat(m + 1, bitshift(i, m + 1) + k + bitshift(1, m) + 1), ...
				Wnr(bitshift(k, 7 - m) + 1), ...
				ZoomPar);
		end
	end
end
y = xMat(9, :);

上述代码的输入为x,输出为y。

在该代码中,利用了Matlab自带的bitrevorder函数实现了2.2.1节所述的比特位反转(输入输出数据索引的比特回文关系)。同时,在利用自定义的蝶形单元实现算法的过程中,使用了Matlab自带的bitshift函数进行移位运算,模仿Verilog中的移位操作。

代码中xMat是一个9×256的矩阵,第18层对应FFT处理器的第07阶(共8阶),第9层作为输出(缓存)预留以便在Verilog中实现如2.2.4节所述的FFT/IFFT的控制(取复共轭以及归一化)。

4 利用VerilogHDL实现

利用VerilogHDL实现256点FFT大体上基于第3章的Matlab代码构建的模型。

4.1 整体结构

VerilogHDL实现的256点FFT的整体结构大体可分为三部分,分别是输入缓存/控制、输出缓存/控制和主要的FFT处理器。其中,输入输出缓存/控制用来实现流水线操作。

4.2 主要模块

通过第2章的概述,可以将256点FFT分为多个模块并通过VerilogHDL语言描述。本节所述的主要模块均基于第3章的Matlab模型。

4.2.1 蝶形单元

回顾2.2.2和3.1节,蝶形单元的总体结构比较简单,但是在用Verilog实现时,需要考虑如何将xp扩大与旋转因子扩大倍数相同的倍数,同时要考虑将蝶形单元的结果除以该倍数以恢复原始数量级。
蝶形单元的VerilogHDL代码如下:

module butterfly (
        input clk,
        input rst_n,
        input en,
        input signed [15:0] xp_real,      //Xm(p)
        input signed [15:0] xp_imag,
        input signed [15:0] xq_real,      //Xm(q)
        input signed [15:0] xq_imag,
        input signed [15:0] Wnr_real,  //Wnr
        input signed [15:0] Wnr_imag,
        output valid,
        output signed [15:0] yp_real,     //Xm+1(p)
        output signed [15:0] yp_imag,
        output signed [15:0] yq_real,     //Xm+1(q)
        output signed [15:0] yq_imag
    );
    //控制寄存器
    reg [2:0] en_r;
    always @(posedge clk or negedge rst_n) begin
        if(!rst_n) begin
            en_r <= 1'b0;
        end
        else begin
            en_r <= {en_r[1:0],en};
        end
    end
    //计算Xm(q)与Wnr的乘积并使得Xm(p)同样放大8192倍
    reg signed [31:0] xq_wnr_real0;
    reg signed [31:0] xq_wnr_real1;
    reg signed [31:0] xq_wnr_imag0;
    reg signed [31:0] xq_wnr_imag1;
    reg signed [31:0] xp_real_d;
    reg signed [31:0] xp_imag_d;
    always @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
            xp_real_d <= 1'b0;
            xp_imag_d <= 1'b0;
            xq_wnr_real0 <= 1'b0;
            xq_wnr_real1 <= 1'b0;
            xq_wnr_imag0 <= 1'b0;
            xq_wnr_imag1 <= 1'b0;
        end
        else if (en) begin
            xq_wnr_real0 <= $signed(xq_real) * $signed(Wnr_real);
            xq_wnr_real1 <= $signed(xq_imag) * $signed(Wnr_imag);
            xq_wnr_imag0 <= $signed(xq_real) * $signed(Wnr_imag);
            xq_wnr_imag1 <= $signed(xq_imag) * $signed(Wnr_real);
            //进行倍数放大
            xp_real_d <= $signed(xp_real) << 13;
            xp_imag_d <= $signed(xp_imag) << 13;
        end
    end
    //得到完整的Xm(q)
    reg signed [31:0] xp_real_d1;
    reg signed [31:0] xp_imag_d1;
    reg signed [31:0] xq_wnr_real;
    reg signed [31:0] xq_wnr_imag;
    always @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
            xq_wnr_real <= 1'b0;
            xq_wnr_imag <= 1'b0;
            xp_real_d1 <= 1'b0;
            xp_imag_d1 <= 1'b0;
        end
        else if (en_r[0]) begin
            xp_real_d1 <= xp_real_d;
            xp_imag_d1 <= xp_imag_d;
            //复数乘法
            xq_wnr_real <= xq_wnr_real0 - xq_wnr_real1;
            xq_wnr_imag <= xq_wnr_imag0 + xq_wnr_imag1;
        end
    end
    //结果
    reg signed [31:0] yp_real_r;
    reg signed [31:0] yp_imag_r;
    reg signed [31:0] yq_real_r;
    reg signed [31:0] yq_imag_r;
    always @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
            yp_real_r <= 1'b0;
            yp_imag_r <= 1'b0;
            yq_real_r <= 1'b0;
            yq_imag_r <= 1'b0;
        end
        else if (en_r[1]) begin
            yp_real_r <= xp_real_d1 + xq_wnr_real;
            yp_imag_r <= xp_imag_d1 + xq_wnr_imag;
            yq_real_r <= xp_real_d1 - xq_wnr_real;
            yq_imag_r <= xp_imag_d1 - xq_wnr_imag;
        end
    end
    //除以缩放因子
    signed_divider_8192 u_signed_divider_8192_1(
                            .a          ( yp_real_r),
                            .quotient   ( yp_real  )
                        );
    signed_divider_8192 u_signed_divider_8192_2(
                            .a          ( yp_imag_r),
                            .quotient   ( yp_imag  )
                        );
    signed_divider_8192 u_signed_divider_8192_3(
                            .a          ( yq_real_r),
                            .quotient   ( yq_real  )
                        );
    signed_divider_8192 u_signed_divider_8192_4(
                            .a          ( yq_imag_r),
                            .quotient   ( yq_imag  )
                        );
    assign valid = en_r[2];
endmodule

在该蝶形单元的实现中,利用控制寄存器来判断是否进行运算以及该时钟周期需要进行怎样的计算。在计算的过程中,将复数的乘法进行了分解,让其在不同的时钟周期实现不同的计算。

对于xp和旋转因子的放大倍数,通过调研以及Matlab模拟,选择了2的13次方倍。在计算中,使用$signed()语句确保乘法和移位操作是有符号数的正确操作。

对于旋转因子,通过提前利用Python代码进行计算并输出特定的格式,将放大后的旋转因子以查找表的方式实现(Python代码见附件,输出结果见代码全文)。

最后的结果需要除以2的13次方倍,以恢复原始数量级,这一操作使用了自定义的模块,详见下节。

4.2.2 除以缩放因子

在设计中,除以2的13次方的操作实际上是移位操作。对于正数,可以直接进行移位,对于负数,需要先加偏移量再右移以正确处理负数的补码表示。

该模块的Verilog代码如下。

module signed_divider_8192 (
        input wire [31:0] a,
        output reg [15:0] quotient
    );
    reg [31:0] temp;
    always @(*) begin
        if (a[31]) begin
            //负数
            temp = a + 4095;
            temp = temp >> 13;
            quotient = temp[15:0];
        end
        else begin
            //正数
            temp = a >> 13;
            quotient = temp[15:0];
        end
    end
endmodule

4.2.3 状态机实现流水线

在设计的FFT中,利用状态机实现了状态的转换,通过转换不同的状态可以实现流水线操作。

在设计中,使用计数的办法判断是否到下一状态,Verilog代码如下所示。

module counter (
        input clk,
        input rst_n,
        input [8:0] thresh,
        input start,
        input valid,
        output reg not_zero,
        output reg full
    );
    //利用三段式状态机完成状态转换和输出
    reg [8:0] count;
    reg currentState;//低为开始,高为停止
    reg nextState;//低为开始,高为停止
    reg ifCount;//是否计数
    //描述当前状态寄存器的复位和变化
    always @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
            currentState <= 1'b1;
            ifCount <= 1'b0;
        end
        else begin
            currentState <= nextState;
        end
    end
    //描述下一状态的转移
    always @(currentState or start or full) begin
        case (currentState)
            1'b1 : begin
                if(start == 1'b1) begin
                    nextState = 1'b0;
                    ifCount = 1'b1;
                end
                else begin
                    nextState = 1'b1;
                end
            end
            1'b0 : begin
                if(full == 1'b1) begin
                    nextState = 1'b1;
                    ifCount = 1'b0;
                end
                else begin
                    nextState = 1'b0;
                end
            end
            default : begin
                //默认为停止
                nextState = 1'b1;
            end
        endcase
    end
    //状态输出
    always @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
            //复位
            count <= 1'b0;
            full <= 1'b0;
            not_zero <= 1'b0;
        end
        else if (count == thresh) begin
            //到头了
            count <= 1'b0;
            full <= 1'b1;
            not_zero <= 1'b0;
        end
        else if (valid == 1'b1 && ifCount == 1'b1) begin
            //没到头并且要继续计数
            count <= count + 1'b1;
            full <= 1'b0;
            not_zero <= 1'b1;
        end
        else begin
            //没到头但是不要继续计数
            count <= count;
            full <= 1'b0;
            not_zero <= 1'b0;
        end
    end
endmodule

4.2.4 FFT/IFFT控制

设计的FFT处理器通过外部输入实现对FFT和IFFT的控制,在Verilog代码编写中,采用了如2.2.4所述的基本原理和复用FFT实现IFFT的步骤。

这一部分的Verilog代码如下。

//输入处理
always @(x_re_buf[0] or x_im_buf[0]) begin
	if(!inv) begin
		//FFT
		x_re_buf[0] = x_re;
		x_im_buf[0] = x_im;
	end
	else begin
		//IFFT
		x_re_buf[0] = x_re;
		x_im_buf[0] = $signed(x_im)*-1;
	end
end
//输出处理
always@(y_re_buf[0] or y_im_buf[0]) begin
	if (!inv) begin
		//FFT
		y_re_out = y_re_buf[0];
		y_im_out = y_im_buf[0];
	end
	else begin
		//IFFT
		y_re_out = (y_re_buf[0] >>> 8);
		y_im_out = ($signed(y_im_buf[0])*-1 >>> 8);
	end
end

4.2.5 核心结构

设计的256点FFT的核心结构即将蝶形单元组合成8阶的FFT处理器,具体原理已在前文说明,Verilog代码可直接根据3.3节所示的Matlab代码进行改写得到。

这一部分的Veilog代码如下。

genvar m,i,j;
generate
	for (m = 0; m <= 7; m = m + 1) begin:stage       
		for (i = 0; i <= (1<<(7-m))-1; i = i + 1) begin:group
			for (j = 0; j <= (1<<m)-1; j = j + 1) begin:unit
				butterfly butterfly_u(
							  .clk(clk),
							  .rst_n(rst_n),
							  .en(en_ctrl[m]),
							  .xp_real(x_re_mat[m][(i<<(m+1))+j]),
							  .xp_imag(x_im_mat[m][(i<<(m+1))+j]),
							  .xq_real(x_re_mat[m][(i<<(m+1))+j+(1<<m)]),
							  .xq_imag(x_im_mat[m][(i<<(m+1))+j+(1<<m)]),
							  .Wnr_real(Wnr_real[(j<<(7-m))]),
							  .Wnr_imag(Wnr_imag[(j<<(7-m))]),
							  .valid(en_ctrl[m+1]),
							  .yp_real(x_re_mat[m+1][(i<<(m+1))+j]),
							  .yp_imag(x_im_mat[m+1][(i<<(m+1))+j]),
							  .yq_real(x_re_mat[m+1][(i<<(m+1))+j+(1<<m)]),
							  .yq_imag(x_im_mat[m+1][(i<<(m+1))+j+(1<<m)])
						  );
			end
		end
	end
endgenerate

输出输入的利用寄存器实现缓存,并依次实现了流水线的效果,这一部分较为简单,详见代码文件。

5 仿真验证

设计的256点FFT主要通过验证Matlab模型、Verilog仿真和FPGA综合进行仿真验证。

5.1 Matlab模型验证

本节基于第3章所述的利用Matlab实现FFT的代码,将其与Matlab自带的fft函数输出结果进行了比较。总共设置了三种测试,分别是:输入的实部和虚部均从1逐点增加至256;输入是频率为50Hz的单个正弦信号;输入是50Hz和120Hz两个正弦信号混频并引入噪声。

验证用的Matlab代码如下所示。

% % 测试一:1-256
% xr = 1:256;
% xi = 1i*(1:256);
% x = xr + xi;

% 采样等参数
Fs = 1000;                    % 采样频率 (Hz)
T = 1/Fs;                     % 采样周期 (秒)
L = 256;                      % 信号长度 (点数)
t = (0:L-1)*T;                % 时间向量

% % 测试二:单个正弦信号
% f = 50;                       % 频率 (Hz)
% A = 1;                        % 幅度
% % 生成正弦信号
% x = A*sin(2*pi*f*t);

% % 测试三:两个正弦信号混频,含噪声
% 50 Hz + 120 Hz 正弦波
S = 0.7*sin(2*pi*50*t) + sin(2*pi*120*t);
% 添加一些随机噪声到信号中
x = S + 1*randn(size(t));

% 绘制原始信号
figure;
plot(t, x);
title('原始输入信号');
xlabel('时间 (秒)');
ylabel('幅度');

% 绘制结果对比图
fftMatlab = fft(x,256);
fftCus = cusFFTModel(x,13);
figure;
plot(1:256, abs(fftMatlab), 'k:h', 'DisplayName', 'MatlabFFT');
hold on;
plot(1:256, abs(fftCus), 'm-..', 'DisplayName', 'CusFFTModel');
xlabel('频率索引');
ylabel('幅值');
legend show;
title('幅值频谱(自定义FFT与内置fft比较)');
grid on;

% 计算双边频谱P2/P4和单边频谱P1/P3
% 自带的fft
P2 = abs(fftMatlab/L);
P1 = P2(1:L/2+1);
P1(2:end-1) = 2*P1(2:end-1);
% 自创的FFT
P4 = abs(fftCus/L);
P3 = P4(1:L/2+1);
P3(2:end-1) = 2*P3(2:end-1);
% 定义频率域f
f_axis = Fs*(0:(L/2))/L;

% 绘制单边幅值频谱
figure;
plot(f_axis, P1, 'k:h', 'DisplayName', 'MatlabFFT');
hold on;
plot(f_axis, P3 , 'm-..', 'DisplayName', 'CusFFTModel');
title('单边幅值频谱(自定义FFT与内置fft比较)');
xlabel('频率 (Hz)');
ylabel('|P(f)|');
legend show;
grid on;
  
%绘制误差
figure;
plot(f_axis, abs(abs(P1)-abs(P3)));
title('单边幅值频谱误差(自定义FFT与内置fft比较)');
xlabel('频率 (Hz)');
ylabel('|ΔP(f)|');
mean(abs(abs(P1)-abs(P3)))

测试结果如下5.1.1至5.1.3所述。

5.1.1 输入数据的实部和虚部均从1逐点增加至256

测试1原始输入 测试1幅值频谱 测试1单边幅值频谱误差

5.1.2 输入频率50Hz的单个正弦信号

测试2原始输入信号 测试2单边幅值频谱 测试2单边幅值频谱误差

5.1.3 输入50/120Hz正弦信号混频且引入噪声

测试3原始输入信号 测试3单边幅值频谱 测试3单边幅值频谱误差

5.2 VerilogHDL仿真

本节在第4节利用VerilogHDL实现256点FFT的基础上进行了仿真,仿真的工具为iVerilog。

编写的testbench代码如下。

`timescale 1ps/1ps
module fft_256_tb();
    //inports reg
    reg clk;
    reg rst_n;
    reg inv;
    reg stb;
    reg sop_in;
    reg [15:0] x_re;
    reg [15:0] x_im;
    // outports wire
    wire            valid_out;
    wire            sop_out;
    wire [15:0]     y_re;
    wire [15:0]     y_im;
    //fft_256
    fft_256 #(
                .NUMBER_OF_TIMES_IN     ( 9'b011111110  ),
                .NUMBER_OF_TIMES_OUT    ( 9'b100000000  ))
            u_fft_256(
                .clk        ( clk        ),
                .rst_n      ( rst_n      ),
                .inv        ( inv        ),
                .stb        ( stb        ),
                .sop_in     ( sop_in     ),
                .x_re       ( x_re       ),
                .x_im       ( x_im       ),
                .valid_out  ( valid_out  ),
                .sop_out    ( sop_out    ),
                .y_re       ( y_re       ),
                .y_im       ( y_im       )
            );
    initial begin
        $dumpfile("wave.vcd");
        $dumpvars(0, fft_256_tb);
        #40000 $finish;
    end
    initial begin
        clk <= 1'b0;
        rst_n <= 1'b0;
        inv <= 1'b0;
        stb <= 1'b1;
        sop_in <= 1'b0;
        x_re <= 0;
        x_im <= 0;
        #10
        rst_n <= 1'b1;
        stb <= 1'b0;
    end
    always #10 clk <= ~clk;
    always @(posedge clk or negedge rst_n) begin
        if(!rst_n) begin
  
        end
        else begin
            if(x_re < 256 && x_re != 0) begin
                x_re = x_re + 1;
                x_im = x_im + 1;
                sop_in = 0;
            end
            else begin
                x_re = 1;
                x_im = 1;
                sop_in = 1;
            end
        end
    end
endmodule

6 参考

1 中国科学院大学数字集成电路作业开源——64位FFT/IFFT数字逻辑运算电路的Verilog实现 - sasasatori - 博客园
2 Verilog FFT 设计 | 菜鸟教程

0

评论区