本文进行FFT核调用,对输入的正弦波进行频谱分析。
作者想要做一个FPGA音频频谱分析仪,先进行模块测试,测试内容如下:
首先进行FFT核配置:


元件例化
cs
`timescale 1ns / 1ps
module fft_t(
input aclk,
input aresetn,
input [7:0] adc_data,
input s_axis_data_tvalid,
input s_axis_data_tlast,
output s_axis_data_tready,
// AXIS data out
output [15:0] m_axis_data_tdata,
output m_axis_data_tvalid,
output m_axis_data_tlast,
output [15:0] m_axis_data_tuser // 宽度依 IP 设置;这里先用 16 做占位,实际12位
);
// 固定配置
wire [15:0] s_axis_config_tdata = {5'b0, 10'b1010101010, 1'b1};
wire s_axis_config_tvalid = 1'b1;
wire s_axis_config_tready;
// 拼接输入复数数据(Im=0, Re=adc_data)
wire [15:0] s_axis_data_tdata = {8'd0, adc_data};
//配置FFT变换核
xfft_0 fft_inst (
.aclk(aclk),
.aresetn(aresetn),
//配置
.s_axis_config_tdata(s_axis_config_tdata),
.s_axis_config_tvalid(s_axis_config_tvalid),
.s_axis_config_tready(s_axis_config_tready),
//数据输入input
.s_axis_data_tdata(s_axis_data_tdata),
//输入数据有效信号 信号input
.s_axis_data_tvalid(s_axis_data_tvalid),
//可以接受外来信号 信号output
.s_axis_data_tready(s_axis_data_tready),
//输入数据最后一个信号 信号input
.s_axis_data_tlast(s_axis_data_tlast),
//输出数据(0-7Re,8-15Im)
.m_axis_data_tdata(m_axis_data_tdata),
//输出数据有效信号 信号output(0-4096)
.m_axis_data_tuser(m_axis_data_tuser),
//输出数据有效信号 信号output
.m_axis_data_tvalid(m_axis_data_tvalid),
//从机可以接受信号 信号input
.m_axis_data_tready(1'b1),
//输出数据最后一个信号 信号output
.m_axis_data_tlast(m_axis_data_tlast),
//其他事件信号
.event_frame_started(),
.event_tlast_unexpected(),
.event_tlast_missing(),
.event_status_channel_halt(),
.event_data_in_channel_halt(),
.event_data_out_channel_halt()
);
endmodule
FFT核的信号输入输出非常多,上手很容易乱,因此在此处作者对每一个信号都进行详细的说明,可以提供对信号的快速查找。
简单例化了fft的ip核之后,对主要引脚进行一个链接,对输入配置部分可以直接进行定义,避免不必要的麻烦,在此处我们没有在运行中改变FFT配置的要求,因此此处直接写死。
仿真文件
cs
`timescale 1ns / 1ps
module tb_fft_t;
reg aclk = 0;
always #5 aclk = ~aclk; // 100 MHz
reg aresetn;
initial begin
aresetn = 0;
#100;
aresetn = 1;
end
// DUT 端口
reg [7:0] adc_data;
reg s_axis_data_tvalid;
reg s_axis_data_tlast;
wire s_axis_data_tready;
wire [15:0] m_axis_data_tdata;
wire m_axis_data_tvalid;
wire m_axis_data_tlast;
wire [15:0] m_axis_data_tuser;
// 例化 DUT
fft_t dut(
.aclk(aclk),
.aresetn(aresetn),
.adc_data(adc_data),
.s_axis_data_tvalid(s_axis_data_tvalid),
.s_axis_data_tlast(s_axis_data_tlast),
.s_axis_data_tready(s_axis_data_tready),
.m_axis_data_tdata(m_axis_data_tdata),
.m_axis_data_tvalid(m_axis_data_tvalid),
.m_axis_data_tlast(m_axis_data_tlast),
.m_axis_data_tuser(m_axis_data_tuser)
);
// ---------------------------
// 简单激励:15 kHz 正弦波,4096 点
// ---------------------------
integer i;
real theta;
integer samp;
initial begin
// 初始化
s_axis_data_tvalid = 0;
s_axis_data_tlast = 0;
adc_data = 0;
// 等待复位完成
@(posedge aresetn);
@(posedge aclk);
// 生成 4096 点正弦
for (i = 0; i < 4096; i = i + 1) begin
// θ = 2π·f·n/Fs = 2π·15000·i/48000
theta = 2.0 * 3.1415926 * 15000.0 * i / 48000.0;
samp = $rtoi($sin(theta) * 127.0); // -127~+127
adc_data = samp[7:0];
s_axis_data_tvalid = 1;
s_axis_data_tlast = (i == 4095);
@(posedge aclk);
while (!s_axis_data_tready) @(posedge aclk);
end
s_axis_data_tvalid = 0;
s_axis_data_tlast = 0;
// 等输出结束
wait (m_axis_data_tlast);
#1000;
$stop;
end
endmodule
仿真文件简单产生了一个激励信号,模拟了AXI输入的几个引脚的时序,向FFT核进行数据写入,运行一下仿真文件,查看现象。
波形图


波形如图所示:
以s_axis_data_tlast信号为1处为分界线,在这个信号出现之前,s_axis_data_tvalid&&s_axis_data_tready一直有效,即一直在输入数据,FFT核也在一直接收数据。
当记录满4096个点之后,即满足FFT变换点数要求时,IP核进行快速傅里叶变化,中间一段时间出现了空窗,就是IP核在进行数据处理计算。当m_axis_data_tvalid有效时,FFT开始对外输出结果。每一个索引m_axis_data_tuser对应一个m_axis_data_tdata,索引即为频率分块点,用于计算所在处频率数值,tdata即为变换结果,此处分为虚部和实部,可以通过计算(根号下虚部实部平方和)得出频谱图来观察,也可以通过功率谱查看结果(虚部实部平方和)。
这里我们图方便,也可以通过直接观察的方式来查看结果(对于模拟输入无其他干扰,观察结果比较明显):
频谱分布:

如图,放大波形之后,可以看到变换结果在索引为1280处,存在峰值8200。
通过计算,48000Hz的采样频率,4096的变换点数,得到分辨率为48000/4096=11.71875
11.71875*1280=15000
15000正是我们模拟输入的正弦波频率!
通过这个实验,简单验证了一下我们FFT的IP核配置是否正确,能否对输入数据进行响应,模块化的试验、仿真,可以避免在项目中寻找错误时无法下手。