Vivado调用FFT IP核进行数据频谱分析
本文进行FFT核调用,对输入的正弦波进行频谱分析。
作者想要做一个FPGA音频频谱分析仪,先进行模块测试,测试内容如下:
首先进行FFT核配置:
元件例化
`timescale 1ns / 1psmodule 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 outoutput [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配置的要求,因此此处直接写死。
仿真文件
`timescale 1ns / 1ps
module tb_fft_t;reg aclk = 0;
always #5 aclk = ~aclk; // 100 MHzreg aresetn;
initial beginaresetn = 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/48000theta = 2.0 * 3.1415926 * 15000.0 * i / 48000.0;samp = $rtoi($sin(theta) * 127.0); // -127~+127adc_data = samp[7:0];s_axis_data_tvalid = 1;s_axis_data_tlast = (i == 4095);@(posedge aclk);while (!s_axis_data_tready) @(posedge aclk);ends_axis_data_tvalid = 0;s_axis_data_tlast = 0;// 等输出结束wait (m_axis_data_tlast);#1000;$stop;
endendmodule
仿真文件简单产生了一个激励信号,模拟了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核配置是否正确,能否对输入数据进行响应,模块化的试验、仿真,可以避免在项目中寻找错误时无法下手。