傅里叶变换是洞察信号频域特征的“眼睛”,而快速傅里叶变换(FFT)算法则赋予了这双“眼睛”实时观察的能力。本章我们将进行一个标志性的综合实验:FFT/IFFT回环分析。我们将首先生成一个由1MHz和5MHz正弦波叠加的时域混合信号;然后利用FFT将其变换到频域,清晰地分离并观察这两个频率成分;最后,再将频域结果通过逆FFT(IFFT) 变换回时域,目标是完美地重建出原始混合信号。
这个“时域→频域→时域”的完整回环,是验证任何频域算法和处理链路正确性的黄金标准。通过本章,您不仅将理解FFT/IFFT的核心原理及其在信号分析中的不可替代性,还将学习如何使用Xilinx FFT IP核完成正/逆变换、如何通过求模运算获取频谱幅度,以及如何设计寻峰模块来自动识别信号中的主要频率成分。这为您未来实现频谱分析仪、OFDM通信系统等复杂应用奠定了坚实基础。
1 项目目标
用DDS分别产生采样率为50MHz,频率为1MHz以及采样率为50MHz,频率为5MHz的正弦信号,将它们混合,然后将这个混合信号过FFT处理,观察输出的结果是否与MATLAB仿真的结果一致,接着再将这个结果进行IFFT复原这个混合信号,如果成功复原实验成功。
2 FFT 简介
想象一下,你听到了一段复杂的音乐,里面有钢琴、吉他、鼓声和小提琴。你的耳朵能自然地分辨出这些不同的乐器声。傅里叶变换(Fourier Transform)就是一种数学工具,它能像你的耳朵一样,将一段复杂的信号(比如音乐声波)分解成它所包含的各种简单的正弦波(比如不同乐器的音高)。
FFT(Fast Fourier Transform) 并不是一种新的变换,而是一种快速计算离散傅里叶变换(DFT)的算法。
简单来说:
目标 (DFT):将一个信号从“时域”转换到“频域”。
方法 (FFT):一种实现这个转换的、极其高效的计算方法。
1. 核心概念:时域 vs. 频域
理解FFT,首先要理解两个看待信号的角度:
时域 (Time Domain)
这是我们最熟悉的视角。它描述了信号的振幅(大小)如何随时间变化。
比喻:一段音乐的乐谱,它告诉你在哪个时间点弹奏哪个音符。
频域 (Frequency Domain)
它描述了信号由哪些频率的波组成,以及每个频率波的强度(振幅)和相位。
比喻:一个音乐频谱分析仪,它告诉你这段音乐里包含了哪些音高 (频率),以及每个音高有多响亮 (振幅)。它不关心这些音高出现的时间顺序。
从时域到频域的转换,就是傅里叶变换要做的事。FFT就是完成这个转换的“高速公路”。
2. FFT 的核心价值:效率!
为什么FFT如此重要,甚至被誉为“20世纪最重要的算法之一”?答案是效率。
直接计算离散傅里叶变换(DFT)的计算复杂度是 O(N²)。
而FFT算法的计算复杂度是 O(N log N)。
这二者差距有多大?举个例子:
假设我们有 N = 1024 个数据点。
用 DFT 计算:大约需要 1024 × 1024 ≈ 1,000,000 次运算。
用 FFT 计算:大约需要 1024 × log₂(1024) = 1024 × 10 ≈ 10,000 次运算。
FFT比直接计算快了将近 100 倍!当数据点N更大时,这种优势会变得更加惊人。正是这种巨大的效率提升,使得许多需要实时处理信号的应用成为了可能。没有FFT,今天的数字通信、音频处理、图像处理等领域都将无法实现。
3. FFT 的核心思想:分治法 (Divide and Conquer)
FFT是如何做到这么快的呢?它的核心思想是分治法。
最著名的FFT算法是库利-图基算法 (Cooley-Tukey Algorithm),其基本思路是:
分解 (Divide):将一个大规模的DFT计算问题,巧妙地分解成两个规模减半的子问题。例如,将一个1024点的DFT分解成两个512点的DFT。
解决 (Conquer):递归地对子问题进行分解,直到分解为最简单的1点DFT(1点DFT的计算结果就是它本身,非常简单)。
合并 (Combine):将子问题的计算结果巧妙地合并起来,得到最终大规模问题的解。
通过利用数据中的周期性和对称性,FFT避免了大量的重复计算,从而实现了计算速度的飞跃。
注意:FFT算法通常要求数据点的数量N是2的整数次幂(如64, 256, 1024, 4096等),这样分解过程最为高效。
4. FFT 的主要应用领域
FFT的应用无处不在,是数字信号处理的基石。
通信工程:
4G/5G/Wi-Fi (OFDM技术):现代无线通信的核心技术OFDM,就是利用FFT和其逆变换(IFFT)来高效地在多个频率上同时传输数据,极大地提高了频谱利用率和抗干扰能力。
音频处理:
频谱分析:音乐播放器里的频谱可视化效果。
均衡器 (EQ):增强或减弱特定频率(如低音、高音)的声音。
降噪:识别并消除特定频率的噪声(如电流声)。
图像处理:
图像滤波:在频域中进行高通(锐化)或低通(模糊)滤波。
图像压缩:JPEG压缩算法就利用了与FFT相关的离散余弦变换(DCT)。
科学与工程:
振动分析:分析桥梁、建筑或机器的振动频率,以检测结构问题。
医疗成像:核磁共振(MRI)的图像重建过程严重依赖FFT。
天文学:分析来自天体的光或射电信号的频谱。
数据分析:
在股票市场、天气数据等时间序列数据中寻找周期性规律。
3 MATLAB仿真
我们先在MATLAB上生成这个混合信号,代码如下:
clc;
clear;
close all;
fs=50e6;%采样率50M
N=1024;%采样点数1024
t=0:N-1;
t=t/fs; %时间序列
f1=1e6;%频点1 1MHZ
f2=5e6;%频点2 5MHZ
s1=sin(2*pi*f1*t);%信号1
s2=sin(2*pi*f2*t);%信号2
%mixsign=s1.*s2;%混频 sin值相乘
mixsign=s1 + s2;%混频 sin值叠加
fftsign=fft(mixsign);%求fft
fftabs=abs(fftsign);%取模运算
%绘制信号的时域波形
subplot(2,1,1);
plot(mixsign);
xlabel('时间/幅度');
ylabel('幅度/V');
%绘制信号FFT后的频域波形
subplot(2,1,2);
plot(fftabs);
xlabel('FFT的位置');
ylabel('FFT的模');
| 运行后可得一下现象:
4 FPFA 实现
由于FFT纯verilog实现过于复杂,这里使用官方的IP核来进行演示,其中的cordic开方,也可以不用省略用其实数和复数的平方和除以2近似,而寻峰模块也可以省了,因为官方的IP核有位置信号(XK_INDEX),这个代码也已经给出。
对于cordic的配置看参考具体工程,这里不过多介绍,同样,对于寻峰模块也不过多介绍,代码有比较完整的注释,也不过多介绍。
1.XFFT IP 介绍
1.1.在vivado工程界面左栏点击 IP Catalog 搜索 FFT,点击进入IP配置。
1.2第一个配置界面为Configuration Tab,如下图所示:
Configuration Tab
Channels:从1到12选择通道数。多通道操作可用于三种突发I/O架构。当使用多通道时,控制逻辑在数据路径上共享,从而节省资源,尽管额外的扇出和路由可能会降低可实现的时钟速率。对于浮点格式,通道必须为1。
Transform Length:选择所需的点大小。从8到65536的所有2次方都可用。
Target Clock Frequency:目标时钟频率,这个设置越大计算速度越快。
Target Data Throughput:目标数据吞吐量。
Target Data Throughput 为输入数据的采样率,
Target Clock Frequency * SSR = Target Data Throughput,SSR默认1。
Architecture Choice:选择一个实现选项,如架构选项中所述。
流水线流式I/O、Radix-2 Burst I/O和Radix-2 Lite Burst I/O架构支持8到65536的点大小。
Radix-4 Burst I/O架构支持64到65536的点大小。
SSR(Super Sample Rate) 是 AMD(原 Xilinx)在其 FFT IP 核中支持的一种并行处理机制。它表示 FFT 运算中同时并行处理的输入样本数,即 FFT 并行度。当你使用 SSR = 2 或 4 时(不论是原生浮点 native floating-point 还是定点 fixed-point 实现):支持的 FFT 点数范围是 从 8 点到 65536 点;如果你选择的是 除了 SSR = 2, 4 以外的 SSR 值(比如 SSR = 8, 16, 32, 64 等):那么你的 FFT 点数必须 ≥ SSR,即最小点数限制被提升了。
选中自动选择以选择满足指定目标数据吞吐量的最小实现,前提是在FPGA上实现FFT内核时实现了指定的目标时钟频率。
目标时钟频率和目标数据吞吐量仅用于自动选择实现和计算延迟。内核不能保证以指定的目标时钟频率或目标数据吞吐量运行。
只有流水线流I/O支持原生浮点格式。自动选择适用,最终将核心配置为流水线流I/O。
Transform Length Options:选择转换长度是否可在运行时配置。当转换长度不可在运行时配置时,内核使用更少的逻辑资源,并具有更快的最大时钟速度。当选择原生浮点格式或定点SSR>1时,此选项将被禁用。
1.3第二个配置界面为Implementation Tab,如下图所示:
Implementation Tab
Data Format:选择输入和输出数据采样是定点格式,还是IEEE-754单精度(32位)浮点格式。当内核处于多通道配置中时,浮点格式不可用。第三种称为**原生浮点格式(native Floating-point format)**的选项,在 Versal 器件中使用的是 DSPFP32 原语(primitives)。传统浮点选项(legacy Floating-point option)已被迁移为伪浮点格式(pseudo Floating-point)。定点格式(Fixed-point)和原生浮点格式(native Floating-point)均支持超采样率(SSR, Super Sample Rate)。“传统浮点”现在被归类为“伪浮点”(这可能意味着它内部仍使用定点实现+缩放逻辑)。
Scaling Options:对于所有架构,有三种选择:
Scaled 缩放,用户定义的缩放计划决定了如何在FFT阶段之间缩放数据。
unscaled 不做缩放,所有整数位增长都被传送到输出。这可以使用更多的FPGA资源。
Block Floating-Point 块浮点,IP核确定需要多少缩放才能最好地利用可用的动态范围,并将缩放因子报告为块指数。
Rounding Modes:舍入模式,truncated :直接截断;convergent rounding:收敛性舍入,当数字的小数部分正好等于二分之一时,如果数字是奇数,则收敛舍入向上舍入;如果数字是偶数,则向下舍入。
Precision Options:输入数据(Input data)和相位因子(phase factors)可以独立设置为 8 到 34 位的位宽(bit width);**当数据格式为浮点(Floating-Point)**时:输入数据的位宽固定为 32 位(即 IEEE-754 单精度格式);相位因子的位宽可以设置为 24 或 25 位,具体取决于所需的噪声性能和可用的硬件资源;对于“原生浮点”(native Floating-point)格式,相位因子的位宽固定为 32 位;对于定点格式(Fixed-point)并且 SSR > 1 的情况:相位因子的位宽固定为 19 位;输入数据的位宽可以设置为:8 到 18 位,当 FFT 变换长度(transform length)小于 65536;8 到 16 位,当变换长度为 65536。
Super Sample Rate:原生浮点格式和定点格式支持每个时钟周期的多个样本。原生浮点的SSR活动值为2、4、8、16、32和64。对于定点,SSR值为1、2和4。对于伪浮点格式,SSR固定为1。
Control Signals:控制信号配置,时钟使能 ( aclken ) 和同步清除 ( aresetn ) 是可选引脚。
复位信号aresetn要勾选,至少保持两个时钟的低电平,可复位整个IP。如果未选择某个选项,可以节省一些逻辑资源,并且可以获得更高的时钟频率。
Output Ordering:输出数据位序排序,Bit/Digit Reversed Order,位/数字逆序;Natural Order,自然顺序。选择以在选择原生浮点或定点SSR>1时执行逆FFT。在此架构中,所有运行时可配置选项都被禁用,包括INV/FWD选项。
输出数据选择是位/数字反转顺序或自然顺序。基于Radix-2的架构(流水线流I/O、Radix-2 Burst I/O和Radix-2 Lite Burst I/O)提供位反转排序,基于Radix-4的架构(Radix-4 Burst I/O)提供数字反转排序。对于流水线流I/O架构,选择自然顺序输出排序会增加核心使用的内存。对于Burst I/O架构,选择自然顺序输出会增加整体转换时间,因为需要单独的卸载阶段。
如果输出顺序为自然顺序,则可以选择循环前缀插入。循环前缀插入适用于原生浮点SSR流水线I/O架构以外的所有架构,通常用于OFDM无线通信系统。
对于原生浮点SSR和固定点SSR>1流水线流I/O架构,输出排序固定在自然顺序,但这些架构中没有循环前缀插入。
Optional Output Fields :可选输出字段,XK_INDEX是可选字段“数据输出通道”, 是否输出FFT 变换的结果索引,可在m_axis_data_user中有相应的字段。OVFLO是数据输出通道和状态通道中的可选字段,是变换中溢出的指示信号,对应event_fft_overflow.当选择原生浮点或定点SSR>1时,既没有XK_INDEX也没有OVFLO。
Throttle Schemes :节流方案选择性能和数据时序要求之间的权衡。Real Time 实时模式通常提供更小、更快的设计,但对必须提供和使用数据的时间有严格的限制。Not Real Time 非实时模式没有这样的限制,但设计可能更大、更慢。
1.4第三个配置界面为Detailed Implementation,如下图所示:
Detailed Implementation:
Memory Options:内存选项,受数据格式和相位因子影响,可配置选择块 RAM 或分布式 RAM。
Optimize Options:优化选项Complex Multipliers 复数乘法器
Use CLB logic,使用CLB逻辑,适用于性能要求较低的目标应用程序或具有很少 DSP 片的目标设备。
Use 3-multiplier structure (resource optimization),使用3乘法结构(资源优化),所有复数乘法器均使用三个实数乘法、五个加/减结构,其中乘法器使用 DSP Slice。这减少了 DSP 切片数量,但使用了一些切片逻辑。这种结构可以利用DSP Slice预加器来减少或消除对额外Slice逻辑的需要,并提高性能。
Use 4-multiplier structure (performance optimization) 使用4乘法器结构(性能优化),所有复数乘法器均使用四个实数乘法、两个加/减结构,并使用 DSP 片。这种结构可产生最高的时钟性能,但需要更多的专用乘法器。在具有 DSP Slice 的设备中,加/减运算在 DSP Slice 内实现。对于原生浮点数据格式,使用 4 乘法器结构。
Butterfly Arithmetic 蝶形运算
Use CLB logic 使用CLB逻辑
Use XtremeDSP Slices 使用 XtremeDSP 切片,此选项强制使用 DSP 片中的加法器/减法器来实现所有蝶形级。对于原生浮点数据格式和定点SSR>1,蝶形级使用DSP切片实现
2.代码如下
顶层代码如下,具体子模块可参考配套demo:
`timescale 1ns / 1ps
module top (
input I_sysclk_p ,//100Mhz差分时钟
input I_sysclk_n ,//100Mhz差分时钟
input I_rst_n ,
output O_led
);
assign O_led = 1'b0;
/******************************************************************\
变量声明
\******************************************************************/
//clock
wire clk_50m ;
wire locked ;
//DDS
wire dds_5m_data_tvalid ;
wire signed [ 7: 0] dds_5m_data_tdata ;
wire dds_5m_phase_tvalid ;
wire [ 31: 0] dds_5m_phase_tdata ;
wire dds_1m_data_tvalid ;
wire signed [ 7: 0] dds_1m_data_tdata ;
wire dds_1m_phase_tvalid ;
wire [ 31: 0] dds_1m_phase_tdata ;
//叠加
reg signed [ 15: 0] dds_mix ;
reg dds_mix_valid ;
//XFFT
//FFT
//config interface
wire [ 7: 0] s_axis_config_tdata ;
wire s_axis_config_tvalid ;
wire s_axis_config_tready ;
//s_axistream data interface
wire [ 31: 0] s_axis_data_tdata ;
wire s_axis_data_tvalid ;
wire s_axis_data_tready ;
wire s_axis_data_tlast ;
//m_axistream data interface
wire [ 31: 0] m_axis_data_tdata ;
wire [ 23: 0] m_axis_data_tuser ;
wire m_axis_data_tvalid ;
wire m_axis_data_tready ;
wire m_axis_data_tlast ;
//m_axistream status interface
wire [ 7: 0] m_axis_status_tdata ;
wire m_axis_status_tvalid ;
wire m_axis_status_tready ;
//m_axistream event interface
wire event_frame_started ;
wire event_tlast_unexpected ;
wire event_tlast_missing ;
wire event_status_channel_halt ;
wire event_data_in_channel_halt ;
wire event_data_out_channel_halt ;
//其他
wire signed [ 15: 0] x_real ;
wire signed [ 15: 0] x_imag ;
wire [ 9: 0] XK_INDEX ;
wire signed [ 15: 0] ifft_data ;
//IFFT
//config interface
wire [ 7: 0] ifft_config_tdata ;
wire ifft_config_tvalid ;
wire ifft_config_tready ;
//s_axistream data interface
wire s_ifft_axis_data_tready ;
//m_axistream data interface
wire [ 31: 0] m_ifft_axis_data_tdata ;
wire [ 23: 0] m_ifft_axis_data_tuser ;
wire m_ifft_axis_data_tvalid ;
wire m_ifft_axis_data_tready ;
wire m_ifft_axis_data_tlast ;
//m_axistream status interface
wire m_ifft_axis_status_tready ;
//求模
wire cordic_data_valid ;
wire [ 79: 0] cordic_data ;
wire [ 32: 0] cordic_data_real ;
//寻峰
wire [ 32: 0] data_max1 ;
wire [ 32: 0] data_max2 ;
wire [ 32: 0] data_max3 ;
wire [ 32: 0] data_max4 ;
wire [ 10: 0] data_max_addr1 ;
wire [ 10: 0] data_max_addr2 ;
wire [ 10: 0] data_max_addr3 ;
wire [ 10: 0] data_max_addr4 ;
wire data_max_process_work1 ;
wire data_max_process_work2 ;
wire data_max_process_work3 ;
wire data_max_process_work4 ;
/******************************************************************\
时钟管理模块
\******************************************************************/
clk_wiz_0 clock_and_reset
(
.clk_out1(clk_50m),
.locked(locked), // output locked
.clk_in1_p(I_sysclk_p),
.clk_in1_n(I_sysclk_n)
);
/******************************************************************\
DDS数据模块
\******************************************************************/
dds_compiler_0 dds_50_5m (
.aclk(clk_50m), // input wire aclk
.m_axis_data_tvalid (dds_5m_data_tvalid ), // output wire m_axis_data_tvalid
.m_axis_data_tdata (dds_5m_data_tdata ), // output wire [7 : 0] m_axis_data_tdata
.m_axis_phase_tvalid(dds_5m_phase_tvalid ), // output wire m_axis_phase_tvalid
.m_axis_phase_tdata (dds_5m_phase_tdata ) // output wire [31 : 0] m_axis_phase_tdata
);
dds_compiler_1 dds_50_1m (
.aclk(clk_50m), // input wire aclk
.m_axis_data_tvalid (dds_1m_data_tvalid ), // output wire m_axis_data_tvalid
.m_axis_data_tdata (dds_1m_data_tdata ), // output wire [7 : 0] m_axis_data_tdata
.m_axis_phase_tvalid(dds_1m_phase_tvalid ), // output wire m_axis_phase_tvalid
.m_axis_phase_tdata (dds_1m_phase_tdata ) // output wire [31 : 0] m_axis_phase_tdata
);
/******************************************************************\
信号叠加模块
\******************************************************************/
always @(posedge clk_50m ) begin
if(~I_rst_n)begin
dds_mix <= 0;
dds_mix_valid <= 0;
end
else if (dds_1m_data_tvalid & dds_5m_data_tvalid) begin
dds_mix <= dds_1m_data_tdata + dds_5m_data_tdata ;
dds_mix_valid <= dds_1m_data_tvalid & dds_5m_data_tvalid;
end
else begin
dds_mix <= 0 ;
dds_mix_valid <= 0 ;
end
end
/******************************************************************\
FFT模块
\******************************************************************/
//config配置信号
assign s_axis_config_tvalid = 1'b1;//slave用于握手,告诉master已经准备ok,master可以接收数据
assign s_axis_config_tdata = (s_axis_config_tready & s_axis_config_tvalid) ? 8'd1 : s_axis_config_tdata;
//输入数据待处理
assign s_axis_data_tvalid = dds_mix_valid;//slave用于握手,告诉master已经准备ok,master可以接收数据
assign s_axis_data_tdata = {16'd0,dds_mix};
assign s_axis_data_tlast = 1'b0; //tlast 最后一个有效数据,此处连续传输,故数据一直存在
//输出处理完的数据
assign m_axis_data_tready = 1'b1;//slave用于握手,告诉master已经准备ok,slave可以接收数据
assign x_real = m_axis_data_tdata[15:0] ;//输出信号实部
assign x_imag = m_axis_data_tdata[31:16] ;//输出信号虚部
assign XK_INDEX = m_axis_data_tuser[9:0] ;//输出信号坐标
//输入状态信号
assign m_axis_status_tready = 1'b1;//slave用于握手,告诉master已经准备ok,slave可以接收数据
xfft_0 FFT_0 (
.aclk(clk_50m), // input wire aclk
.aresetn(I_rst_n), // input wire aresetn
.s_axis_config_tdata(s_axis_config_tdata), // input wire [7 : 0] s_axis_config_tdata
.s_axis_config_tvalid(s_axis_config_tvalid), // input wire s_axis_config_tvalid
.s_axis_config_tready(s_axis_config_tready), // output wire s_axis_config_tready
.s_axis_data_tdata(s_axis_data_tdata), // input wire [31 : 0] s_axis_data_tdata
.s_axis_data_tvalid(s_axis_data_tvalid), // input wire s_axis_data_tvalid
.s_axis_data_tready(s_axis_data_tready), // output wire s_axis_data_tready
.s_axis_data_tlast(s_axis_data_tlast), // input wire s_axis_data_tlast
.m_axis_data_tdata(m_axis_data_tdata), // output wire [31 : 0] m_axis_data_tdata
.m_axis_data_tuser(m_axis_data_tuser), // output wire [23 : 0] m_axis_data_tuser
.m_axis_data_tvalid(m_axis_data_tvalid), // output wire m_axis_data_tvalid
.m_axis_data_tready(m_axis_data_tready), // input wire m_axis_data_tready
.m_axis_data_tlast(m_axis_data_tlast), // output wire m_axis_data_tlast
.m_axis_status_tdata(m_axis_status_tdata), // output wire [7 : 0] m_axis_status_tdata
.m_axis_status_tvalid(m_axis_status_tvalid), // output wire m_axis_status_tvalid
.m_axis_status_tready(m_axis_status_tready), // input wire m_axis_status_tready
.event_frame_started(event_frame_started), // output wire event_frame_started
.event_tlast_unexpected(event_tlast_unexpected), // output wire event_tlast_unexpected
.event_tlast_missing(event_tlast_missing), // output wire event_tlast_missing
.event_status_channel_halt(event_status_channel_halt), // output wire event_status_channel_halt
.event_data_in_channel_halt(event_data_in_channel_halt), // output wire event_data_in_channel_halt
.event_data_out_channel_halt(event_data_out_channel_halt) // output wire event_data_out_channel_halt
);
/******************************************************************\
IFFT模块
\******************************************************************/
//config配置信号
assign ifft_config_tvalid = 1'b1;//slave用于握手,告诉master已经准备ok,master可以接收数据
assign ifft_config_tdata = (ifft_config_tready & ifft_config_tvalid) ? 8'd0 : ifft_config_tdata;
//输出处理完的数据
assign m_ifft_axis_data_tready = 1'b1;//slave用于握手,告诉master已经准备ok,slave可以接收数据
assign ifft_data = m_ifft_axis_data_tdata[15:0];
//输入状态信号
assign m_ifft_axis_status_tready = 1'b1;//slave用于握手,告诉master已经准备ok,slave可以接收数据
xfft_1 IFFT_0 (
.aclk (clk_50m ),// input wire aclk
.aresetn (I_rst_n ),// input wire aresetn
.s_axis_config_tdata (ifft_config_tdata ),// input wire [7 : 0] s_axis_config_tdata
.s_axis_config_tvalid (ifft_config_tvalid ),// input wire s_axis_config_tvalid
.s_axis_config_tready (ifft_config_tready ),// output wire s_axis_config_tready
.s_axis_data_tdata (m_axis_data_tdata ),// input wire [31 : 0] s_axis_data_tdata
.s_axis_data_tvalid (m_axis_data_tvalid ),// input wire s_axis_data_tvalid
.s_axis_data_tready (s_ifft_axis_data_tready ),// output wire s_axis_data_tready
.s_axis_data_tlast (m_axis_data_tlast ),// input wire s_axis_data_tlast
.m_axis_data_tdata (m_ifft_axis_data_tdata ),// output wire [31 : 0] m_axis_data_tdata
.m_axis_data_tuser (m_ifft_axis_data_tuser ),// output wire [23 : 0] m_axis_data_tuser
.m_axis_data_tvalid (m_ifft_axis_data_tvalid ),// output wire m_axis_data_tvalid
.m_axis_data_tready (m_ifft_axis_data_tready ),// input wire m_axis_data_tready
.m_axis_data_tlast (m_ifft_axis_data_tlast ),// output wire m_axis_data_tlast
.m_axis_status_tdata ( ),// output wire [7 : 0] m_axis_status_tdata
.m_axis_status_tvalid ( ),// output wire m_axis_status_tvalid
.m_axis_status_tready (m_ifft_axis_status_tready ),// input wire m_axis_status_tready
.event_frame_started ( ),// output wire event_frame_started
.event_tlast_unexpected ( ),// output wire event_tlast_unexpected
.event_tlast_missing ( ),// output wire event_tlast_missing
.event_status_channel_halt ( ),// output wire event_status_channel_halt
.event_data_in_channel_halt ( ),// output wire event_data_in_channel_halt
.event_data_out_channel_halt ( ) // output wire event_data_out_channel_halt
);
/******************************************************************\
FFT求模模块
\******************************************************************/
assign cordic_data_real = cordic_data[32:0];
cordic_0 cordic_translate (
.aclk(clk_50m), // input wire aclk
.s_axis_cartesian_tvalid(m_axis_data_tvalid), // input wire s_axis_cartesian_tvalid
.s_axis_cartesian_tdata(m_axis_data_tdata), // input wire [31 : 0] s_axis_cartesian_tdata
.m_axis_dout_tvalid(cordic_data_valid), // output wire m_axis_dout_tvalid
.m_axis_dout_tdata(cordic_data) // output wire [79 : 0] m_axis_dout_tdata
);
/******************************************************************\
寻峰模块
\******************************************************************/
data_max_top # (
.DATA_WIDTH ('d33 ),
.ADDR_WIDTH ('d11 ),
.DATA_ADDR_START_NUM ('d0 ),
.DATA_ADDR_STOP_NUM ('d50 ),
.DATA_ADDR_STEP ('d1 )
)
data_max_top_u1 (
.i_clk (clk_50m ),
.i_rst (~locked ),
.i_fft_sum (cordic_data_real ),
.i_fft_data_tvalid (cordic_data_valid ),
.o_data_max_process_work (data_max_process_work1 ),
.o_data_max (data_max1 ),
.o_data_max_addr (data_max_addr1 )
);
data_max_top # (
.DATA_WIDTH ('d33 ),
.ADDR_WIDTH ('d11 ),
.DATA_ADDR_START_NUM ('d75 ),
.DATA_ADDR_STOP_NUM ('d125 ),
.DATA_ADDR_STEP ('d1 )
)
data_max_top_u2 (
.i_clk (clk_50m ),
.i_rst (~locked ),
.i_fft_sum (cordic_data_real ),
.i_fft_data_tvalid (cordic_data_valid ),
.o_data_max_process_work (data_max_process_work2 ),
.o_data_max (data_max2 ),
.o_data_max_addr (data_max_addr2 )
);
data_max_top # (
.DATA_WIDTH ('d33 ),
.ADDR_WIDTH ('d11 ),
.DATA_ADDR_START_NUM ('d850 ),
.DATA_ADDR_STOP_NUM ('d950 ),
.DATA_ADDR_STEP ('d1 )
)
data_max_top_u3 (
.i_clk (clk_50m ),
.i_rst (~locked ),
.i_fft_sum (cordic_data_real ),
.i_fft_data_tvalid (cordic_data_valid ),
.o_data_max_process_work (data_max_process_work3 ),
.o_data_max (data_max3 ),
.o_data_max_addr (data_max_addr3 )
);
data_max_top # (
.DATA_WIDTH ('d33 ),
.ADDR_WIDTH ('d11 ),
.DATA_ADDR_START_NUM ('d975 ),
.DATA_ADDR_STOP_NUM ('d1023 ),
.DATA_ADDR_STEP ('d1 )
)
data_max_top_u4 (
.i_clk (clk_50m ),
.i_rst (~locked ),
.i_fft_sum (cordic_data_real ),
.i_fft_data_tvalid (cordic_data_valid ),
.o_data_max_process_work (data_max_process_work4 ),
.o_data_max (data_max4 ),
.o_data_max_addr (data_max_addr4 )
);
endmodule
|
5 仿真验证
仿真代码如下:
`timescale 1ns / 1ps
module tb_top();
//变量定义
reg I_sysclk_p;
reg I_rst_n;
wire O_led;
//信号初始化
initial begin
I_sysclk_p = 1;
I_rst_n = 0;
#3000 I_rst_n = 1;
end
//100Mhz时钟
always #5 I_sysclk_p = ! I_sysclk_p ;
top top_inst (
.I_sysclk_p(I_sysclk_p),
.I_sysclk_n(~I_sysclk_p),
.I_rst_n(I_rst_n),
.O_led(O_led)
);
endmodule
| 通过仿真来看,我们的实验是成功的,dds_mix为混合信号,通过FFT处理后,得到四个峰值,这四个峰值可以通过寻峰算法来定位,当然也可以直接观察m_axis_data_tdata(紫色)和XK_INDEX,通过对比MATLAB仿真的结果,四个峰值基本是正确的,说明FFT部分成功;对于IFFT部分,观察ifft_data(橙色)这个信号基本与dds_mix(黄色)一致,故IFFT也是成功的。
|