文章目录
一、什么是FFT?
首先来看傅里叶提出的理论:任何连续周期信号可以由一组适当的正弦曲线组合而成。因此傅里叶变换的目的是可将时域上的信号转变为频域的信号,这样可以使我们在时域上不好观察的信号转变一个角度观察,这提供了一个非常好的处理视角,在《【学习笔记】奥本海姆第二版《信号与系统》第五章:离散时间傅里叶变换》我们知道傅里叶变换的公式为:
- 连续时间傅里叶变换:
X
(
j
ω
)
=
∫
−
∞
+
∞
x
(
t
)
e
−
j
ω
t
d
t
X(jω)=∫_{-∞}^{+∞}x(t)e^{-jωt}dt
X(jω)=∫−∞+∞x(t)e−jωtdt
x
(
t
)
=
1
2
π
∫
−
∞
+
∞
X
(
j
ω
)
e
j
ω
t
d
ω
x(t)=\frac{1}{2π}∫_{-∞}^{+∞}X(jω)e^{jωt}dω
x(t)=2π1∫−∞+∞X(jω)ejωtdω
函数
X
(
j
ω
)
X(jω)
X(jω)称为
x
(
t
)
x(t)
x(t)的连续时间傅里叶变换。一个非周期信号的傅里叶变换
X
(
j
ω
)
X(jω)
X(jω)通常称为
x
(
t
)
x(t)
x(t)的频谱。
- 离散时间傅里叶变换:
x
[
n
]
=
1
2
π
∫
2
π
X
(
e
j
ω
)
e
j
ω
n
d
ω
x[n]=\frac{1}{2π}∫_{2π}X(e^{jω})e^{jωn}dω
x[n]=2π1∫2πX(ejω)ejωndω
X
(
e
j
ω
)
=
∑
n
=
−
∞
+
∞
x
[
n
]
e
−
j
ω
n
X(e^{jω})=\sum_{n=-∞}^{+∞}x[n]e^{-jωn}
X(ejω)=n=−∞∑+∞x[n]e−jωn
X ( e j ω ) X(e^{jω}) X(ejω)称为离散时间傅里叶变换,这一对式子就是离散时间傅里叶变换对。与连续时间情况一样,傅里叶变换 X ( e j ω ) X(e^{jω}) X(ejω)往往称为 x [ n ] x[n] x[n]的频谱。
FFT也称快速傅里叶变换,是对傅里叶变换公式的一种优化的算法,在N个采样点的DFT中,傅里叶变换的时间复杂度为 O ( N 2 ) O(N^2) O(N2)因为需要进行 N 2 N^2 N2次乘法和加法运算;为FFT的时间复杂度只需要 O ( N l o g N ) O(NlogN) O(NlogN),显著的提高了计算效率,特别是处理采样点越多的信号。
二、FFT后频谱分析
对于一个长度为N的离散信号的傅里叶变换,变换后会得到N个频率分量;这些频率分量对应于信号在不同频率上的幅度和相位信息。
- 频率分量的数量:进行N点离散傅里叶变换后,输出结果是一个包含N个复数的数组,每个复数表示一个频率分量,从0 — N-1。
- 第K个频率分量X[K]对应的频率 f k f_k fk的计算公式如下: f k = k ∗ F S N ,( K = 0 , 1 , 2..... N − 1 ; F S 为采样频率) f_k=\frac{k*F_S}{N},(K=0,1,2.....N-1;F_S为采样频率) fk=Nk∗FS,(K=0,1,2.....N−1;FS为采样频率)
- 频谱对称性:对于实值信号,频率分量具有对称性。即X[k]=X[N-k]*,这意味着只需考虑前一半的频率分量(0到N/2)就能获得完整的信息。
2.1 频谱分辨率
Δ
f
=
F
S
N
Δf=\frac{F_S}{N}
Δf=NFS
F
S
F_S
FS为采样频率,N为FFT的点数,意思是只要
F
S
F_S
FS和N都确定了,频谱就确定了。根据采样定理,采样频率要>信号频率的2倍。
2.2 频谱幅度
假如原始信号的幅度为A,在FFT计算过后,频谱会出现N个点的峰值对应不同频率。第一个点是直流分量(0HZ)的频谱幅度为A*N,其它频率的幅度为N/2。
三、MATLAB使用FFT观察信号频谱
我们显示一下实信号的FFT变换,观察频谱,MATLAB代码如下:
clc; close all;
Adc=2; %直流分量幅度
A1=3; %频率F1信号的幅度
A2=1.5; %频率F2信号的幅度
F1=50; %信号1频率(Hz)
F2=75; %信号2频率(Hz)
Fs=1024; %采样频率(Hz)
P1=-30; %信号1相位(度)
P2=90; %信号相位(度)
N=1024; %采样点数
t=[0:1/Fs:N/Fs]; %采样时刻
%信号
S=Adc+A1*cos(2*pi*F1*t+pi*P1/180)+A2*cos(2*pi*F2*t+pi*P2/180);
%显示原始信号
plot(S);
title('原始信号');
figure;
Y = fft(S,N); %做FFT变换
Ayy = (abs(Y)); %取模
plot(Ayy(1:N)); %显示原始的FFT模值结果
title('fft后的频谱');
figure;
Ayy=Ayy/(N/2); %换算成实际的幅度
Ayy(1)=Ayy(1)/2;
F=([1:N]-1)*Fs/N; %换算成实际的频率值
plot(F(1:N/2),Ayy(1:N/2)); %显示换算后的FFT模值结果
title('实际频谱');
由代码可看出,原始信号是一个实信号,由一个幅度为2直流分量和两个余弦信号组成,信号的波形如上所示。
对信号进行FFT后,第一个峰值为直流分量的幅度,由图可以看出幅度值为2048= 2* 1024 ,符合预期。因为信号是实信号,因此频谱呈现出共轭对称,因此我们只观察N/2之前的频谱图就可以。
第二个峰值的坐标为(51,1536),对应的频率就为50HZ,幅度1536=3*1024/2 ,也是符合预期。
第三个峰值的坐标为(76,768),对应的频率为75HZ,幅度768=1.5/1024/2,也是符合预期。为了方便观察,我们可以进行移频操作,将对称的频谱移动到直流分量两边,具体代码如下:
clc; close all;
Adc=2; % 直流分量幅度
A1=3; % 频率F1信号的幅度
A2=1.5; % 频率F2信号的幅度
F1=50; % 信号1频率(Hz)
F2=75; % 信号2频率(Hz)
Fs=1024; % 采样频率(Hz)
P1=-30; % 信号1相位(度)
P2=90; % 信号相位(度)
N=1024; % 采样点数
t = (0:N-1)/Fs; % 生成时间向量
% 信号
S = Adc + A1*cos(2*pi*F1*t + pi*P1/180) + A2*cos(2*pi*F2*t + pi*P2/180);
% 显示原始信号
figure;
plot(t, S);
title('原始信号');
% FFT 变换
Y = fft(S, N); % 做 FFT 变换
% 计算频率轴
freq = (0:N-1) * (Fs/N); % 频率向量
X_shifted = fftshift(Y); % 使用 fftshift 调整频率轴和频谱
freq_shifted = freq - Fs/2; % 中心化频率轴
% 取模
Ayy = abs(X_shifted);
Ayy1 = abs(Y);
% 绘制频谱
figure;
plot(freq_shifted, Ayy); % 显示 FFT 模值结果
title('fft后的频谱');
% 绘制实际频谱
figure;
% 取模并归一化
Ayy = Ayy1/(N/2);
Ayy(1) = Ayy(1)/2;
F = ([1:N]-1) * Fs/N; % 换算成实际的频率值
plot(F(1:N/2), Ayy(1:N/2)); % 显示换算后的 FFT 模值结果
title('实际频谱');
在归一化模值后,频谱就显示了原始信号正确的频率和幅度。
我们在给一个复信号来看看和实信号的频谱有啥不一样,matlab代码如下:
clc;
close all;
% 参数设置
Fs = 1000; % 采样频率 (Hz)
T = 1/Fs; % 采样周期 (s)
L = 1000; % 信号长度
t = (0:L-1)*T; % 时间向量
% 生成复信号
f1 = 50; % 第一个频率 (Hz)
f2 = 120; % 第二个频率 (Hz)
A1 = 3; % 第一个频率的幅度
A2 = 5.5; % 第二个频率的幅度
% 复信号
S_real = A1 * sin(2 * pi * f1 * t); % 实部
S_imag = A2 * sin(2 * pi * f2 * t + pi/4); % 虚部(相位偏移)
S = 4 +S_real + 1i * S_imag; % 复信号
% 计算 FFT
Y = fft(S);
% 计算频率轴
f = (0:L-1)*(Fs/L); % 频率向量
P2 = abs(Y/L); % 双边幅度谱
P1 = P2(1:L/2+1); % 单边幅度谱
P1(2:end-1) = 2*P1(2:end-1); % 单边幅度谱修正
f1 = f(1:L/2+1); % 单边频率向量
% 绘制频谱
figure;
plot(f1, P1);
title('复信号的频谱');
xlabel('频率 (Hz)');
ylabel('|P1(f)|');
grid on;
可以看到复信号的频谱没有共轭对称。
四、 Xilinx FFT IP的端口说明
4.1 端口说明
端口名称 | 输入方向 | 端口说明 |
aclk | 输入 | 上升沿有效 |
aclken | 输入 | 时钟使能信号,高电平有效 |
aresetn | 输入 | 低电平有效同步复位信号。 需要至少两个周期的 aresetn 有效脉冲 |
s_axis_config_tvalid | 输入 | 配置数据有效信号 |
s_axis_config_tready | 输出 | 配置数据准备信号 |
s_axis_config_tdata | 输入 | 配置数据 |
s_axis_data_tvalid | 输入 | 输入数据有效信号 |
s_axis_data_tready | 输出 | 输入数据准备信号 |
s_axis_data_tdata | 输入 | 输入数据,带未处理的样本数据:XN_RE 和 XN_IM |
s_axis_data_tlast | 输入 | 输入最后一个数据指示信号 |
端口名称 | 输入方向 | 端口说明 |
m_axis_data_tvalid | 输出 | 输出数据有效信号 |
m_axis_data_tready | 输入 | 输出数据准备信号 |
m_axis_data_tdata | 输出 | 输出数据 |
m_axis_data_tuser | 输出 | 数据输出通道的 TUSER。 携带额外的每个样本信息,例如 XK_INDEX、OVFLO 和 BLK_EXP |
m_axis_data_tlast | 输出 | 输出最后一个数据指示信号 |
m_axis_status_tvalid | 输出 | 输出状态有效信号 |
m_axis_status_tready | 输入 | 输出状态准备信号 |
m_axis_status_tdata | 输出 | 输出状态数据 |
端口名称 | 输入方向 | 端口说明 |
event_frame_started | 输出 | 当核心开始处理新帧时拉高 |
event_tlast_unexpected | 输出 | 当输入的 s_axis_data_tlast不在最后一个数据对应位置时,拉高 |
event_tlast_missing | 输出 | 当 s_axis_data_tlast 在帧的最后一个数据样本上为低时,拉高 |
event_fft_overflow | 输出 | 当从数据输出通道卸载的数据样本中发现有溢出时,会拉高。仅当溢出是有效选项时才会出现 |
event_data_in_channel_halt | 输出 | 当核心从数据输入通道请求数据但没有可用数据时,置位 |
event_data_out_channel_halt | 输出 | 当内核尝试将数据写入数据输出通道但无法执行时,会触发此信号。仅在非实时模式下出现 |
event_status_channel_halt | 输出 | 当内核尝试将数据写入状态通道但无法执行时,会触发此信号。仅在非实时模式下出现 |
4.2 事件指示信号说明
- event_frame_started:当内核开始处理新帧时,此事件信号将持续一个时钟周期。
- event_tlast_missing: 当 s_axis_data_tlast 在帧的最后一个传入数据样本上为低时,此事件信号在一个时钟周期内有效。这表明核心和上游数据源在帧大小方面的配置不匹配,并表明上游数据源的点大小配置大于核心。
- event_tlast_unexpected:当核心在任何传入数据样本(不是帧中的最后一个)上看到 s_axis_data_tlast 高电平时,此事件信号会持续一个时钟周期。这表明核心和上游数据源在帧大小方面的配置不匹配,并表明上游数据源配置为比核心更小的点大小。
- event_fft_overflow:当在 m_axis_data_tdata 上传输的数据样本中看到溢出时,此事件信号在每个时钟周期都会被置位。 只有在使用缩放算法或单精度浮点 I/O 时才有可能获得 FFT 溢出。在所有其他配置中,引脚都会从核心中移除。
- event_data_in_channel_halt:当核心需要来自数据输入通道的数据但没有可用数据时,每个周期都会断言此事件。
- event_data_out_channel_halt:此事件在每个周期中被置位,当核心需要将数据写入数据输出通道但因为通道中的缓冲区已满而无法写入时。发生这种情况时,核心处理将停止,所有活动都将停止,直到通道缓冲区中有可用空间。帧不会损坏。
- event_status_channel_halt:此事件在每个周期中被置位,当核心需要将数据写入状态通道但因为通道上的缓冲区已满而无法写入时。发生这种情况时,核心处理将停止,所有活动都将停止,直到通道缓冲区中有可用空间。帧不会损坏。事件引脚仅在非实时模式下可用。
4.3 s_axis_config_tdata 格式
- (可选) NFFT:宽度5位,变换的点大小;NFFT 可以是最大变换的大小或任何较小的点大小。例如,1024 点 FFT 可以计算点大小 1024、512、256 等。NFFT 的值是 l o g 2 log_2 log2(点大小)
- (可选)CP_LEN :宽度 l o g 2 log_2 log2(最大点数),循环前缀长度:在输出整个变换之前,最初作为循环前缀输出的变换末尾的样本数。
- FWD_INV:宽度1bit,指示执行的是正向 FFT 变换还是反向 FFT 变换。当 FWD_INV = 1 时,计算正向变换。如果 FWD_INV = 0,则计算反向变换。
- (可选)SCALE_SCH
4.4 数据输入通道s_axis_data_tdata
数据通道包括虚部和实部数据。
- XN_RE:实部 (bxn = 8 - 34) 采用二进制补码或单精度浮点格式
- XN_IM:虚部 (bxn = 8 - 34) 采用二进制补码或单精度浮点格式
多通道数据格式:
单通道数据格式:
例如:核心已配置为具有两个 FFT 数据通道,数据为 12 位。通道 0 具有以下样本值:
- RE: 0010 1101 1001
- IM :0011 1110 0110
通道 1 具有以下样本值:
- RE: 0111 0000 0000
- IM: 0000 0000 0000
由于数据必须是8的整数被,因此高四位必须补0:
4.5 数据输出通道
数据输出通道包含变换的实部和虚部结果,这些结果在 TDATA 上传输。此外,TUSER 还传输与 TDATA 上的样本数据相关的每个样本状态信息。数据通道包括虚部和实部数据。
- XK_RE:实部 (bxn = 8 - 34) 采用二进制补码或单精度浮点格式
- XK_IM:虚部 (bxn = 8 - 34) 采用二进制补码或单精度浮点格式
多通道数据格式:
单通道数据格式:
例如:核心已配置为具有两个 FFT 数据通道,数据为 12 位。输出通道 0 具有以下样本值:
- RE: 0010 1101 1001
- IM:1011 1110 0110
输出通道 1 具有以下样本值:
3. RE: 0111 0000 0000
4. IM: 1000 0000 0000
五、FFT IP 的例化
我们先用DDS IP核生成一个10M的正弦波,让信号经过FFT后观察频谱,FFT IP例化步骤如下:
DDS IP 的例化参考《FPGA实现频率、幅度、相位可调的DDS以及DDS Compiler IP核的使用验证》,本文就不再赘述。
六、仿真验证
6.1 验证单音信号的FFT
我们先用DDS生成一个5M和一个10M的正弦波,分别通过FFT后观察频谱,仿真代码如下:
`timescale 1ns / 1ps
module tb_test_fft();
//IP核接口信号
/********时钟复位*********/
reg aclk ;
reg aresetn ;
//dds
wire m_axis_dds_5m_data_tvalid ;
wire signed [15:0] m_axis_dds_5m_data_tdata ;
wire m_axis_dds_10m_data_tvalid ;
wire signed [15:0] m_axis_dds_10m_data_tdata ;
//fft_10M
wire [31:0] s_axis_fft_10M_data_tdata ;
wire s_axis_fft_10M_data_tvalid ;
wire [31 : 0] m_axis_fft_10M_data_tdata ;
wire [15 : 0] m_axis_fft_10M_data_tuser ;
wire m_axis_fft_10M_data_tvalid ;
reg m_axis_fft_10M_data_tready ;
wire m_axis_fft_10M_data_tlast ;
wire event_fft_10M_frame_started ;
wire event_fft_10M_tlast_unexpected ;
wire event_fft_10M_tlast_missing ;
wire event_fft_10M_status_channel_halt ;
wire event_fft_10M_data_in_channel_halt ;
wire event_fft_10M_data_out_channel_halt ;
reg signed [15:0] fft_10M_data_real ;
reg signed [15:0] fft_10M_data_imag ;
wire signed [32:0] fft_10M_power ;
//fft_5M
wire [31:0] s_axis_fft_5M_data_tdata ;
wire s_axis_fft_5M_data_tvalid ;
wire [31 : 0] m_axis_fft_5M_data_tdata ;
wire [15 : 0] m_axis_fft_5M_data_tuser ;
wire m_axis_fft_5M_data_tvalid ;
reg m_axis_fft_5M_data_tready ;
wire m_axis_fft_5M_data_tlast ;
wire event_fft_5M_frame_started ;
wire event_fft_5M_tlast_unexpected ;
wire event_fft_5M_tlast_missing ;
wire event_fft_5M_status_channel_halt ;
wire event_fft_5M_data_in_channel_halt ;
wire event_fft_5M_data_out_channel_halt ;
reg signed [15:0] fft_5M_data_real ;
reg signed [15:0] fft_5M_data_imag ;
wire signed [32:0] fft_5M_power ;
initial begin
aclk=1'b0;
aresetn=1'b0;
m_axis_fft_5M_data_tready=1'b1;
m_axis_fft_10M_data_tready=1'b1;
#15;
aresetn=1'b1;
end
always #5 aclk=~aclk; //产生100M时钟
assign s_axis_fft_10M_data_tdata = {16'd0,m_axis_dds_10m_data_tdata}; //虚部补0
assign s_axis_fft_10M_data_tvalid = m_axis_dds_10m_data_tvalid;
assign s_axis_fft_5M_data_tdata = {16'd0,m_axis_dds_5m_data_tdata}; //虚部补0
assign s_axis_fft_5M_data_tvalid = m_axis_dds_5m_data_tvalid;
//求模
assign fft_10M_power=fft_10M_data_real*fft_10M_data_real+fft_10M_data_imag*fft_10M_data_imag;
assign fft_5M_power=fft_5M_data_real*fft_5M_data_real+fft_5M_data_imag*fft_5M_data_imag;
always@(posedge aclk or negedge aresetn)begin
if(!aresetn)begin
fft_10M_data_real<='b0;
fft_10M_data_imag<='b0;
end
else if(m_axis_fft_10M_data_tvalid)begin
//取输出频谱的实部和虚部
fft_10M_data_real<=m_axis_fft_10M_data_tdata[15:0];
fft_10M_data_imag<=m_axis_fft_10M_data_tdata[31:16];
end
else begin
fft_10M_data_real<=fft_10M_data_real;
fft_10M_data_imag<=fft_10M_data_imag;
end
end
always@(posedge aclk or negedge aresetn)begin
if(!aresetn)begin
fft_5M_data_real<='b0;
fft_5M_data_imag<='b0;
end
else if(m_axis_fft_5M_data_tvalid)begin
//取输出频谱的实部和虚部
fft_5M_data_real<=m_axis_fft_5M_data_tdata[15:0];
fft_5M_data_imag<=m_axis_fft_5M_data_tdata[31:16];
end
else begin
fft_5M_data_real<=fft_5M_data_real;
fft_5M_data_imag<=fft_5M_data_imag;
end
end
//例化IP核
dds_5m u0_dds_5m (
.aclk(aclk), // input wire aclk
.aresetn(aresetn), // input wire aresetn
.m_axis_data_tvalid(m_axis_dds_5m_data_tvalid), // output wire m_axis_data_tvalid
.m_axis_data_tdata(m_axis_dds_5m_data_tdata) // output wire [15 : 0] m_axis_data_tdata
);
dds_10m u0_dds_10m (
.aclk(aclk), // input wire aclk
.aresetn(aresetn), // input wire aresetn
.m_axis_data_tvalid(m_axis_dds_10m_data_tvalid), // output wire m_axis_data_tvalid
.m_axis_data_tdata(m_axis_dds_10m_data_tdata) // output wire [15 : 0] m_axis_data_tdata
);
test_fft u0_test_fft (
.aclk(aclk), // input wire aclk
.aresetn(aresetn), // input wire aresetn
.s_axis_config_tdata(8'b1), // input wire [7 : 0] s_axis_config_tdata
.s_axis_config_tvalid(1'b1), // input wire s_axis_config_tvalid
.s_axis_config_tready(), // output wire s_axis_config_tready
.s_axis_data_tdata(s_axis_fft_10M_data_tdata), // input wire [31 : 0] s_axis_data_tdata
.s_axis_data_tvalid(m_axis_dds_10m_data_tvalid), // input wire s_axis_data_tvalid
.s_axis_data_tready(), // output wire s_axis_data_tready
.s_axis_data_tlast(), // input wire s_axis_data_tlast
.m_axis_data_tdata(m_axis_fft_10M_data_tdata), // output wire [47 : 0] m_axis_data_tdata
.m_axis_data_tuser(m_axis_fft_10M_data_tuser), // output wire [15 : 0] m_axis_data_tuser
.m_axis_data_tvalid(m_axis_fft_10M_data_tvalid), // output wire m_axis_data_tvalid
.m_axis_data_tready(m_axis_fft_10M_data_tready), // input wire m_axis_data_tready
.m_axis_data_tlast(m_axis_fft_10M_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(1'b1), // input wire m_axis_status_tready
.event_frame_started(event_fft_10M_frame_started), // output wire event_frame_started
.event_tlast_unexpected(event_fft_10M_tlast_unexpected), // output wire event_tlast_unexpected
.event_tlast_missing(event_fft_10M_tlast_missing), // output wire event_tlast_missing
.event_status_channel_halt(event_fft_10M_status_channel_halt), // output wire event_status_channel_halt
.event_data_in_channel_halt(event_fft_10M_data_in_channel_halt), // output wire event_data_in_channel_halt
.event_data_out_channel_halt(event_fft_10M_data_out_channel_halt) // output wire event_data_out_channel_halt
);
test_fft u1_test_fft (
.aclk(aclk), // input wire aclk
.aresetn(aresetn), // input wire aresetn
.s_axis_config_tdata(8'b1), // input wire [7 : 0] s_axis_config_tdata
.s_axis_config_tvalid(1'b1), // input wire s_axis_config_tvalid
.s_axis_config_tready(), // output wire s_axis_config_tready
.s_axis_data_tdata(s_axis_fft_5M_data_tdata), // input wire [31 : 0] s_axis_data_tdata
.s_axis_data_tvalid(m_axis_dds_5m_data_tvalid), // input wire s_axis_data_tvalid
.s_axis_data_tready(), // output wire s_axis_data_tready
.s_axis_data_tlast(), // input wire s_axis_data_tlast
.m_axis_data_tdata(m_axis_fft_5M_data_tdata), // output wire [47 : 0] m_axis_data_tdata
.m_axis_data_tuser(m_axis_fft_5M_data_tuser), // output wire [15 : 0] m_axis_data_tuser
.m_axis_data_tvalid(m_axis_fft_5M_data_tvalid), // output wire m_axis_data_tvalid
.m_axis_data_tready(m_axis_fft_5M_data_tready), // input wire m_axis_data_tready
.m_axis_data_tlast(m_axis_fft_5M_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(1'b1), // input wire m_axis_status_tready
.event_frame_started(event_fft_5M_frame_started), // output wire event_frame_started
.event_tlast_unexpected(event_fft_5M_tlast_unexpected), // output wire event_tlast_unexpected
.event_tlast_missing(event_fft_5M_tlast_missing), // output wire event_tlast_missing
.event_status_channel_halt(event_fft_5M_status_channel_halt), // output wire event_status_channel_halt
.event_data_in_channel_halt(event_fft_5M_data_in_channel_halt), // output wire event_data_in_channel_halt
.event_data_out_channel_halt(event_fft_5M_data_out_channel_halt) // output wire event_data_out_channel_halt
);
endmodule
运行仿真:
我们可以看到两个单音信号经过FFT后都产生了一个波峰,我们先放大看10M信号的频谱波峰的坐标:
10M信号的频谱波峰在102的位置,我们来计算一下原始信号的频率 =坐标K * 采样频率 / 采样点数。
f
c
l
k
=
102
∗
100000000
/
1024
=
9960937
≈
10
M
f_clk = 102 *100000000 / 1024 = 9960937 ≈ 10M
fclk=102∗100000000/1024=9960937≈10M
我们再来先放大看5M信号的频谱波峰的坐标:
10M信号的频谱波峰在51的位置,我们来计算一下原始信号的频率 =坐标K * 采样频率 / 采样点数。
f
c
l
k
=
51
∗
100000000
/
1024
=
4980468
≈
5
M
f_clk = 51 *100000000 / 1024 = 4980468 ≈ 5M
fclk=51∗100000000/1024=4980468≈5M
再用matlab生成一个5M正弦波和10M正弦波,采样频率为100M,采样点数为1024,进行FFT来验真频谱是否正确,matlab代码如下:
clc; clear; close all;
% 参数设置
Fs = 100e6; % 采样频率 100 MHz
N = 1024; % 采样点数
F1 = 5e6; % 信号1频率 5 MHz
F2 = 10e6; % 信号2频率 10 MHz
% 生成时间向量
t = (0:N-1)/Fs; % 时间向量
% 生成信号
S1 = cos(2*pi*F1*t); % 5 MHz 正弦波
S2 = cos(2*pi*F2*t); % 10 MHz 正弦波
S = S1 + S2; % 合成信号
% 显示原始信号
figure;
plot(t, S);
title('合成信号');
xlabel('时间 (s)');
ylabel('幅度');
% FFT 变换
Y = fft(S, N); % 做 FFT 变换
% 计算频率轴
Ayy = abs(Y) / N; % 取模并归一化
% 绘制频谱
figure;
plot(1:N/2, Ayy(1:N/2)); % 只显示正频率部分的幅度
title('频谱');
xlabel('点数');
ylabel('幅度');
xlim([1 N/2]); % 限制x轴范围到正频率部分
grid on;
可以看到使用matlab仿真出来的频谱坐标和我们用vivado仿真一致。
6.2 验证IFFT
我们让前面两个正弦波信号经过FFT后再经过IFFT观测能否恢复出原始波形,IFFT也是使用FFT的IP核,只需要给配置数据设置为0即可,仿真代码如下:
`timescale 1ns / 1ps
module tb_test_fft();
//IP核接口信号
/********时钟复位*********/
reg aclk ;
reg aresetn ;
//dds
wire m_axis_dds_5m_data_tvalid ;
wire signed [15:0] m_axis_dds_5m_data_tdata ;
wire m_axis_dds_10m_data_tvalid ;
wire signed [15:0] m_axis_dds_10m_data_tdata ;
//fft_10M
wire [31:0] s_axis_fft_10M_data_tdata ;
wire s_axis_fft_10M_data_tvalid ;
wire [31 : 0] m_axis_fft_10M_data_tdata ;
wire [23 : 0] m_axis_fft_10M_data_tuser ;
wire m_axis_fft_10M_data_tvalid ;
wire m_axis_fft_10M_data_tready ;
wire m_axis_fft_10M_data_tlast ;
wire event_fft_10M_frame_started ;
wire event_fft_10M_tlast_unexpected ;
wire event_fft_10M_tlast_missing ;
wire event_fft_10M_status_channel_halt ;
wire event_fft_10M_data_in_channel_halt ;
wire event_fft_10M_data_out_channel_halt ;
reg signed [15:0] fft_10M_data_real ;
reg signed [15:0] fft_10M_data_imag ;
wire signed [32:0] fft_10M_power ;
//fft_5M
wire [31:0] s_axis_fft_5M_data_tdata ;
wire s_axis_fft_5M_data_tvalid ;
wire [31 : 0] m_axis_fft_5M_data_tdata ;
wire [23 : 0] m_axis_fft_5M_data_tuser ;
wire m_axis_fft_5M_data_tvalid ;
wire m_axis_fft_5M_data_tready ;
wire m_axis_fft_5M_data_tlast ;
wire event_fft_5M_frame_started ;
wire event_fft_5M_tlast_unexpected ;
wire event_fft_5M_tlast_missing ;
wire event_fft_5M_status_channel_halt ;
wire event_fft_5M_data_in_channel_halt ;
wire event_fft_5M_data_out_channel_halt ;
reg signed [15:0] fft_5M_data_real ;
reg signed [15:0] fft_5M_data_imag ;
wire signed [32:0] fft_5M_power ;
// ifft_10M
wire [31 : 0] m_axis_ifft_10M_data_tdata ;
wire m_axis_ifft_10M_data_tvalid ;
wire m_axis_ifft_10M_data_tlast ;
wire [15:0] ifft_10m_wave ;
// ifft_5M
wire [31 : 0] m_axis_ifft_5M_data_tdata ;
wire m_axis_ifft_5M_data_tvalid ;
wire m_axis_ifft_5M_data_tlast ;
wire [15:0] ifft_5m_wave ;
initial begin
aclk=1'b0;
aresetn=1'b0;
fft_10M_data_real = 'd0;
fft_10M_data_imag ='d0;
#201;
aresetn=1'b1;
end
assign s_axis_fft_10M_data_tdata = {16'd0,m_axis_dds_10m_data_tdata}; //虚部补0
assign s_axis_fft_10M_data_tvalid = m_axis_dds_10m_data_tvalid;
assign s_axis_fft_5M_data_tdata = {16'd0,m_axis_dds_5m_data_tdata}; //虚部补0
assign s_axis_fft_5M_data_tvalid = m_axis_dds_5m_data_tvalid;
assign ifft_10m_wave = m_axis_ifft_10M_data_tdata[15:0];
assign ifft_5m_wave = m_axis_ifft_5M_data_tdata[15:0];
//求模
assign fft_10M_power=fft_10M_data_real*fft_10M_data_real+fft_10M_data_imag*fft_10M_data_imag;
assign fft_5M_power=fft_5M_data_real*fft_5M_data_real+fft_5M_data_imag*fft_5M_data_imag;
always #5 aclk=~aclk; //产生100M时钟
always@(posedge aclk)begin
if(aresetn == 1'b0)begin
fft_10M_data_real<='b0;
fft_10M_data_imag<='b0;
end
else if(m_axis_fft_10M_data_tvalid)begin
//取输出频谱的实部和虚部
fft_10M_data_real<=m_axis_fft_10M_data_tdata[15:0];
fft_10M_data_imag<=m_axis_fft_10M_data_tdata[31:16];
end
else begin
fft_10M_data_real<=fft_10M_data_real;
fft_10M_data_imag<=fft_10M_data_imag;
end
end
always@(posedge aclk)begin
if(aresetn == 1'b0)begin
fft_5M_data_real<='b0;
fft_5M_data_imag<='b0;
end
else if(m_axis_fft_5M_data_tvalid)begin
//取输出频谱的实部和虚部
fft_5M_data_real<=m_axis_fft_5M_data_tdata[15:0];
fft_5M_data_imag<=m_axis_fft_5M_data_tdata[31:16];
end
else begin
fft_5M_data_real<=fft_5M_data_real;
fft_5M_data_imag<=fft_5M_data_imag;
end
end
//例化IP核
dds_5m u0_dds_5m (
.aclk(aclk), // input wire aclk
.aresetn(aresetn), // input wire aresetn
.m_axis_data_tvalid(m_axis_dds_5m_data_tvalid), // output wire m_axis_data_tvalid
.m_axis_data_tdata(m_axis_dds_5m_data_tdata) // output wire [15 : 0] m_axis_data_tdata
);
dds_10m u0_dds_10m (
.aclk(aclk), // input wire aclk
.aresetn(aresetn), // input wire aresetn
.m_axis_data_tvalid(m_axis_dds_10m_data_tvalid), // output wire m_axis_data_tvalid
.m_axis_data_tdata(m_axis_dds_10m_data_tdata) // output wire [15 : 0] m_axis_data_tdata
);
test_fft u0_test_fft (
.aclk(aclk), // input wire aclk
.aresetn(aresetn), // input wire aresetn
.s_axis_config_tdata(8'd1), // input wire [7 : 0] s_axis_config_tdata
.s_axis_config_tvalid(1'b1), // input wire s_axis_config_tvalid
.s_axis_config_tready(), // output wire s_axis_config_tready
.s_axis_data_tdata(s_axis_fft_10M_data_tdata), // input wire [31 : 0] s_axis_data_tdata
.s_axis_data_tvalid(m_axis_dds_10m_data_tvalid), // input wire s_axis_data_tvalid
.s_axis_data_tready(m_axis_fft_10M_data_tready), // output wire s_axis_data_tready
.s_axis_data_tlast(m_axis_fft_10M_data_tlast), // input wire s_axis_data_tlast
.m_axis_data_tdata(m_axis_fft_10M_data_tdata), // output wire [47 : 0] m_axis_data_tdata
.m_axis_data_tuser(m_axis_fft_10M_data_tuser), // output wire [15 : 0] m_axis_data_tuser
.m_axis_data_tvalid(m_axis_fft_10M_data_tvalid), // output wire m_axis_data_tvalid
.m_axis_data_tready(m_axis_fft_10M_data_tready), // input wire m_axis_data_tready
.m_axis_data_tlast(m_axis_fft_10M_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(1'b1), // input wire m_axis_status_tready
.event_frame_started(event_fft_10M_frame_started), // output wire event_frame_started
.event_tlast_unexpected(event_fft_10M_tlast_unexpected), // output wire event_tlast_unexpected
.event_tlast_missing(event_fft_10M_tlast_missing), // output wire event_tlast_missing
.event_status_channel_halt(event_fft_10M_status_channel_halt), // output wire event_status_channel_halt
.event_data_in_channel_halt(event_fft_10M_data_in_channel_halt), // output wire event_data_in_channel_halt
.event_data_out_channel_halt(event_fft_10M_data_out_channel_halt) // output wire event_data_out_channel_halt
);
test_fft u1_test_fft (
.aclk(aclk), // input wire aclk
.aresetn(aresetn), // input wire aresetn
.s_axis_config_tdata(8'd1), // input wire [7 : 0] s_axis_config_tdata
.s_axis_config_tvalid(1'b1), // input wire s_axis_config_tvalid
.s_axis_config_tready(), // output wire s_axis_config_tready
.s_axis_data_tdata(s_axis_fft_5M_data_tdata), // input wire [31 : 0] s_axis_data_tdata
.s_axis_data_tvalid(m_axis_dds_5m_data_tvalid), // input wire s_axis_data_tvalid
.s_axis_data_tready(m_axis_fft_5M_data_tready), // output wire s_axis_data_tready
.s_axis_data_tlast(m_axis_fft_5M_data_tlast), // input wire s_axis_data_tlast
.m_axis_data_tdata(m_axis_fft_5M_data_tdata), // output wire [47 : 0] m_axis_data_tdata
.m_axis_data_tuser(m_axis_fft_5M_data_tuser), // output wire [15 : 0] m_axis_data_tuser
.m_axis_data_tvalid(m_axis_fft_5M_data_tvalid), // output wire m_axis_data_tvalid
.m_axis_data_tready(m_axis_fft_5M_data_tready), // input wire m_axis_data_tready
.m_axis_data_tlast(m_axis_fft_5M_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(1'b1), // input wire m_axis_status_tready
.event_frame_started(event_fft_5M_frame_started), // output wire event_frame_started
.event_tlast_unexpected(event_fft_5M_tlast_unexpected), // output wire event_tlast_unexpected
.event_tlast_missing(event_fft_5M_tlast_missing), // output wire event_tlast_missing
.event_status_channel_halt(event_fft_5M_status_channel_halt), // output wire event_status_channel_halt
.event_data_in_channel_halt(event_fft_5M_data_in_channel_halt), // output wire event_data_in_channel_halt
.event_data_out_channel_halt(event_fft_5M_data_out_channel_halt) // output wire event_data_out_channel_halt
);
// IFFT
test_fft u0_test_ifft (
.aclk(aclk), // input wire aclk
.aresetn(aresetn), // input wire aresetn
.s_axis_config_tdata(8'd0), // input wire [7 : 0] s_axis_config_tdata
.s_axis_config_tvalid(1'b1), // input wire s_axis_config_tvalid
.s_axis_config_tready(), // output wire s_axis_config_tready
.s_axis_data_tdata(m_axis_fft_10M_data_tdata), // input wire [31 : 0] s_axis_data_tdata
.s_axis_data_tvalid(m_axis_fft_10M_data_tvalid), // input wire s_axis_data_tvalid
.s_axis_data_tready(m_axis_fft_10M_data_tready), // output wire s_axis_data_tready
.s_axis_data_tlast(m_axis_fft_10M_data_tlast), // input wire s_axis_data_tlast
.m_axis_data_tdata(m_axis_ifft_10M_data_tdata), // output wire [47 : 0] m_axis_data_tdata
.m_axis_data_tuser(), // output wire [15 : 0] m_axis_data_tuser
.m_axis_data_tvalid(m_axis_ifft_10M_data_tvalid), // output wire m_axis_data_tvalid
.m_axis_data_tready(1'b1), // input wire m_axis_data_tready
.m_axis_data_tlast(m_axis_ifft_10M_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(1'b1), // 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
);
test_fft u1_test_ifft (
.aclk(aclk), // input wire aclk
.aresetn(aresetn), // input wire aresetn
.s_axis_config_tdata(8'd0), // input wire [7 : 0] s_axis_config_tdata
.s_axis_config_tvalid(1'b1), // input wire s_axis_config_tvalid
.s_axis_config_tready(), // output wire s_axis_config_tready
.s_axis_data_tdata(m_axis_fft_5M_data_tdata), // input wire [31 : 0] s_axis_data_tdata
.s_axis_data_tvalid(m_axis_fft_5M_data_tvalid), // input wire s_axis_data_tvalid
.s_axis_data_tready(m_axis_fft_5M_data_tready), // output wire s_axis_data_tready
.s_axis_data_tlast(m_axis_fft_5M_data_tlast), // input wire s_axis_data_tlast
.m_axis_data_tdata(m_axis_ifft_5M_data_tdata), // output wire [47 : 0] m_axis_data_tdata
.m_axis_data_tuser(), // output wire [15 : 0] m_axis_data_tuser
.m_axis_data_tvalid(m_axis_ifft_5M_data_tvalid), // output wire m_axis_data_tvalid
.m_axis_data_tready(1'b1), // input wire m_axis_data_tready
.m_axis_data_tlast(m_axis_ifft_5M_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(1'b1), // 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
);
endmodule
我们可以看到两个正弦信号经过FFT后再经过IFFT成功恢复出了波形,我们放大看一下频率是否正确:
10M波形的周期为100ns,5M波形的周期为200ns,波形频率正确,但是不知道为啥IFFT第一次出来的波形幅度比原来的DDS波形小一半,但是后面的波形幅度正常,这个问题后续再研究一下。
6.3 验证多音信号的FFT
我们使用《Xilinx 使用DDS实现本振混频上下变频》里面的混频信号进行FFT以及IFFT频谱观察,matlab代码如下:
clc;
clear;
close all;
% 参数设置
Fs = 100e6; % 采样频率为 100 MHz
N = 1024; % 采样点数
t = (0:N-1) / Fs; % 时间向量
% 生成信号
f1 = 5e6; % 5 MHz 信号
f2 = 10e6; % 10 MHz 信号
signal = sin(2 * pi * f1 * t) .* sin(2 * pi * f2 * t); % 生成5M和10M正弦波的叠加信号
% 进行FFT
Y = fft(signal);
% 计算频率轴
f = (0:N-1) * (Fs / N); % 频率向量
% 计算幅度谱
P2 = abs(Y / N); % 双边幅度谱
P1 = P2(1:N/2+1); % 单边幅度谱
P1(2:end-1) = 2 * P1(2:end-1); % 单边幅度谱调整
% 进行IFFT
signal_reconstructed = ifft(Y);
% 绘制结果
figure;
% 绘制原始信号
subplot(3, 1, 1);
plot(t, signal);
title('Original Signal (5 MHz * 10 MHz)');
xlabel('Time (s)');
ylabel('Amplitude');
grid on;
% 绘制FFT结果
subplot(3, 1, 2);
plot(f(1:N/2+1), P1);
title('FFT Result');
xlabel('Frequency (Hz)');
ylabel('Magnitude');
grid on;
% 绘制IFFT结果
subplot(3, 1, 3);
plot(t, real(signal_reconstructed));
title('Reconstructed Signal from IFFT');
xlabel('Time (s)');
ylabel('Amplitude');
grid on;
% 调整图形
sgtitle('FFT and IFFT Demonstration');
vivado仿真代码如下:
`timescale 1ns / 1ps
module tb_test_fft();
//IP核接口信号
/********时钟复位*********/
reg aclk ;
reg aresetn ;
//dds
wire m_axis_dds_5m_data_tvalid ;
wire signed [15:0] m_axis_dds_5m_data_tdata ;
wire m_axis_dds_10m_data_tvalid ;
wire signed [15:0] m_axis_dds_10m_data_tdata ;
//fft_10M
wire [63:0] s_axis_fft_10M_data_tdata ;
wire s_axis_fft_10M_data_tvalid ;
wire [63 : 0] m_axis_fft_10M_data_tdata ;
wire [23 : 0] m_axis_fft_10M_data_tuser ;
wire m_axis_fft_10M_data_tvalid ;
wire m_axis_fft_10M_data_tready ;
wire m_axis_fft_10M_data_tlast ;
wire event_fft_10M_frame_started ;
wire event_fft_10M_tlast_unexpected ;
wire event_fft_10M_tlast_missing ;
wire event_fft_10M_status_channel_halt ;
wire event_fft_10M_data_in_channel_halt ;
wire event_fft_10M_data_out_channel_halt ;
reg signed [31:0] fft_10M_data_real ;
reg signed [31:0] fft_10M_data_imag ;
wire signed [63:0] fft_10M_power ;
// ifft_10M
wire [31 : 0] m_axis_ifft_10M_data_tdata ;
wire m_axis_ifft_10M_data_tvalid ;
wire m_axis_ifft_10M_data_tlast ;
wire [31:0] ifft_10m_wave ;
// mult
wire [31:0] mix_dds ;
initial begin
aclk=1'b0;
aresetn=1'b0;
fft_10M_data_real = 'd0;
fft_10M_data_imag ='d0;
#201;
aresetn=1'b1;
end
assign s_axis_fft_10M_data_tdata = {32'd0,mix_dds}; //虚部补0
assign ifft_10m_wave = m_axis_ifft_10M_data_tdata[31:0];
//求模
assign fft_10M_power=fft_10M_data_real*fft_10M_data_real+fft_10M_data_imag*fft_10M_data_imag;
always #5 aclk=~aclk; //产生100M时钟
always@(posedge aclk)begin
if(aresetn == 1'b0)begin
fft_10M_data_real<='b0;
fft_10M_data_imag<='b0;
end
else if(m_axis_fft_10M_data_tvalid)begin
//取输出频谱的实部和虚部
fft_10M_data_real<=m_axis_fft_10M_data_tdata[31:0];
fft_10M_data_imag<=m_axis_fft_10M_data_tdata[63:32];
end
else begin
fft_10M_data_real<=fft_10M_data_real;
fft_10M_data_imag<=fft_10M_data_imag;
end
end
//例化IP核
dds_5m u0_dds_5m (
.aclk(aclk), // input wire aclk
.aresetn(aresetn), // input wire aresetn
.m_axis_data_tvalid(m_axis_dds_5m_data_tvalid), // output wire m_axis_data_tvalid
.m_axis_data_tdata(m_axis_dds_5m_data_tdata) // output wire [15 : 0] m_axis_data_tdata
);
dds_10m u0_dds_10m (
.aclk(aclk), // input wire aclk
.aresetn(aresetn), // input wire aresetn
.m_axis_data_tvalid(m_axis_dds_10m_data_tvalid), // output wire m_axis_data_tvalid
.m_axis_data_tdata(m_axis_dds_10m_data_tdata) // output wire [15 : 0] m_axis_data_tdata
);
mult_dds u0_mult_dds (
.CLK(aclk), // input wire CLK
.A(m_axis_dds_5m_data_tdata), // input wire [15 : 0] A
.B(m_axis_dds_10m_data_tdata), // input wire [15 : 0] B
.P(mix_dds) // output wire [31 : 0] P
);
test_fft u0_test_fft (
.aclk(aclk), // input wire aclk
.aresetn(aresetn), // input wire aresetn
.s_axis_config_tdata(8'd1), // input wire [7 : 0] s_axis_config_tdata
.s_axis_config_tvalid(1'b1), // input wire s_axis_config_tvalid
.s_axis_config_tready(), // output wire s_axis_config_tready
.s_axis_data_tdata(s_axis_fft_10M_data_tdata), // input wire [63: 0] s_axis_data_tdata
.s_axis_data_tvalid(1'b1), // input wire s_axis_data_tvalid
.s_axis_data_tready(m_axis_fft_10M_data_tready), // output wire s_axis_data_tready
.s_axis_data_tlast(), // input wire s_axis_data_tlast
.m_axis_data_tdata(m_axis_fft_10M_data_tdata), // output wire [63 : 0] m_axis_data_tdata
.m_axis_data_tuser(m_axis_fft_10M_data_tuser), // output wire [23 : 0] m_axis_data_tuser
.m_axis_data_tvalid(m_axis_fft_10M_data_tvalid), // output wire m_axis_data_tvalid
.m_axis_data_tready(1'b1), // input wire m_axis_data_tready
.m_axis_data_tlast(m_axis_fft_10M_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(1'b1), // input wire m_axis_status_tready
.event_frame_started(event_fft_10M_frame_started), // output wire event_frame_started
.event_tlast_unexpected(event_fft_10M_tlast_unexpected), // output wire event_tlast_unexpected
.event_tlast_missing(event_fft_10M_tlast_missing), // output wire event_tlast_missing
.event_status_channel_halt(event_fft_10M_status_channel_halt), // output wire event_status_channel_halt
.event_data_in_channel_halt(event_fft_10M_data_in_channel_halt), // output wire event_data_in_channel_halt
.event_data_out_channel_halt(event_fft_10M_data_out_channel_halt) // output wire event_data_out_channel_halt
);
// IFFT
test_fft u0_test_ifft (
.aclk(aclk), // input wire aclk
.aresetn(aresetn), // input wire aresetn
.s_axis_config_tdata(8'd0), // input wire [7 : 0] s_axis_config_tdata
.s_axis_config_tvalid(1'b1), // input wire s_axis_config_tvalid
.s_axis_config_tready(), // output wire s_axis_config_tready
.s_axis_data_tdata(m_axis_fft_10M_data_tdata), // input wire [63 : 0] s_axis_data_tdata
.s_axis_data_tvalid(m_axis_fft_10M_data_tvalid), // input wire s_axis_data_tvalid
.s_axis_data_tready(m_axis_fft_10M_data_tready), // output wire s_axis_data_tready
.s_axis_data_tlast(m_axis_fft_10M_data_tlast), // input wire s_axis_data_tlast
.m_axis_data_tdata(m_axis_ifft_10M_data_tdata), // output wire [63 : 0] m_axis_data_tdata
.m_axis_data_tuser(), // output wire [23 : 0] m_axis_data_tuser
.m_axis_data_tvalid(m_axis_ifft_10M_data_tvalid), // output wire m_axis_data_tvalid
.m_axis_data_tready(1'b1), // input wire m_axis_data_tready
.m_axis_data_tlast(m_axis_ifft_10M_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(1'b1), // 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
);
endmodule
我们可以看到IFFT后恢复出来的波形和混频后的波形一致,我们来放大看FFT后的频谱图坐标:因为是实信号,所以频谱是共轭对称的,所以我们只看正频率部分即可。
第一个波峰坐标为51,我们计算一下原始信号的频率 =坐标K * 采样频率 / 采样点数。
f
c
l
k
=
51
∗
100000000
/
1024
=
4980468
≈
5
M
f_clk = 51 *100000000 / 1024 = 4980468 ≈ 5M
fclk=51∗100000000/1024=4980468≈5M对应的为混频后的下变频部分,我们再来看第二个波峰坐标。
第一个波峰坐标为154,我们计算一下原始信号的频率 =坐标K * 采样频率 / 采样点数。
f
c
l
k
=
154
∗
100000000
/
1024
=
15039062
≈
15
M
f_clk = 154 *100000000 / 1024 = 15039062 ≈ 15M
fclk=154∗100000000/1024=15039062≈15M对应的为混频后的上变频部分。至此Xilinx的FFT IP核仿真完成。