名称:AD7606 FFT 频谱分析 FPGA 设计 Verilog Vivado VHDL
软件:Vivado
语言:VHDL
功能介绍
本设计面向 AD7606 采样数据的 FPGA 频谱分析应用,将外部 ADC 输入的 16bit 采样数据送入缓存,再按固定点数组织为一帧 FFT 输入数据,通过 Xilinx FFT IP 完成频域转换,最终根据 FFT 输出结果计算频谱幅值并提取最大幅值对应的频率信息。
核心控制模块为 FFT_ctrl,接口包含 50MHz 时钟、复位、AD 采样数据有效标志、16bit 采样数据输入以及最大频率输出。设计中通过 point 参数设置 FFT 点数,通过 resolve 参数设置频率分辨率相关换算系数,便于根据实际采样率和 FFT 点数调整频率计算方式。
数据处理流程比较清晰:ADC 数据先写入 FIFO,当 FIFO 满足一帧数据条件后再读出送入 FFT IP;FFT 输出的实部和虚部分别取出后进行平方和计算,用于幅值比较;在 FFT 输出有效期间统计频点序号,并记录最大幅值对应的位置,从而得到主要频率成分。该设计适合用于 AD7606 数据采集后的频谱观察、主频检测、信号分析等 FPGA 实验或工程参考。
运行环境
开发软件:Vivado。
设计语言:Verilog。
主要 IP:Xilinx FFT IP、FIFO Generator IP。
工程文件包含 Vivado 工程、约束文件、仿真相关文件以及 IP 静态库文件,适合在 Vivado 环境中综合、实现和仿真参考。
设计思路
设计采用“采样缓存 + 分帧读取 + FFT 运算 + 幅值比较”的结构。AD7606 输出的 16bit 采样数据并不是直接连续送入 FFT,而是先进入 FIFO 进行缓存;当 FIFO 写满后,控制逻辑启动读使能,将一帧数据按时钟节拍送入 FFT IP。这样可以保证 FFT 输入数据帧完整,便于与 IP 核的 AXI-Stream 时序配合。
FFT 输入端使用 s_axis_data_tvalid 指示当前数据有效,并通过 last 信号标记一帧输入数据的最后一个点。FFT 配置通道保持有效,数据通道将采样数据作为实部输入,虚部补零,适用于实数采样信号的频域分析。FFT 输出端通过 m_axis_data_tvalid 判断频域数据是否有效,输出数据被拆分为实部和虚部,后续逻辑对二者进行平方和计算,得到可用于比较的幅值能量值。
最大频点提取部分在 FFT 输出有效期间进行计数,计数值代表当前频点位置;同时比较当前幅值平方和与历史最大值,记录最大幅值对应的频点编号,再结合频率分辨率参数换算输出频率。由于参数 point 与 resolve 在源码中给出,使用时可以根据实际采样时钟和 FFT 点数进行调整,从而适配不同采样条件。
模块结构
主要模块包括:
AD_FFT_top:顶层相关模块,用于连接 AD7606 采样接口与 FFT 处理控制逻辑。
ad7606_if:AD7606 接口相关逻辑,用于配合 ADC 采样数据输入。
FFT_ctrl:FFT 控制核心模块,负责 FIFO 写入/读取控制、FFT IP 数据输入、FFT 输出拆分、幅值平方和计算以及最大频点输出。
testbench:仿真测试模块,用于 Vivado 仿真环境下验证逻辑连接和时序行为。
fifo_65536:FIFO Generator IP,用于缓存采样数据并按帧向 FFT 模块供数。
xfft_point / xfft_65536:Xilinx FFT IP 相关模块,用于完成频域转换。
开发板验证
工程带有 AD7606 相关管脚约束文件 ad7606_pins_test.xdc,并在 Vivado 工程约束目录中配置了对应约束。约束文件可作为开发板实际连线和管脚分配参考,适合在匹配硬件连接条件下进行板级综合、实现和下载验证。
由于约束中已经体现 AD7606 接口的管脚分配,使用时需要结合实际 FPGA 开发板原理图核对时钟、复位、ADC 数据线及控制信号连接,确认电平标准和引脚位置一致后再进行板级测试。
仿真图/仿真说明/设计文档图片
设计配套 Vivado 行为仿真工程目录,并提供 testbench 模块,可用于观察 FIFO 控制、FFT 输入有效信号、FFT 输出有效信号及频点计数等关键时序。仿真时可重点关注 wr_data_vld、wr_en、rd_en、valid、last、fft_dout_tvalid、fft_dout_re、fft_dout_im、fre_cnt 和 max_data 等信号。
部分代码
以下展示顶层模块 FFT_ctrl 的部分代码,完整源码请下载压缩包查看。
module FFT_ctrl(
input clk,//时钟50M
input rst_p,//复位
input wr_data_vld,//数据有效标志
input [15:0] wr_data,//AD采样的16bit数据
output [36:0] max_data//最大值对应的频率
);
parameter point=16384;//根据实际点数修改
parameter resolve=12207;//200_000*1000/point=频率分辨率*1000,采样时钟除以点数,根据实际点数修改
//fifo 空满信号
wire empty;
wire almost_empty;
wire full;
reg wr_en=0;//fifo 写使能
reg rd_en=0;//fifo 读使能
wire [15:0] rd_data;//fifo 读数据
wire last;//FFT输入的最后一个数
reg valid=0;//FFT输入数据有效指示
wire [31:0] fft_dout;//FFT输出
wire fft_dout_tvalid;//FFT输出数据有效指示
(* MARK_DEBUG="true" *)wire signed [15:0] fft_dout_re;//FFT输出数据实部
wire signed [15:0] fft_dout_im;//FFT输出数据虚部
always@(posedge clk or posedge rst_p)
if(rst_p)//复位
wr_en<=0;
else
if(empty==1)//FIFO空时开始写
wr_en<=1;
else if(full==1)//FIFO满时停止写
wr_en<=0;
always@(posedge clk or posedge rst_p)
if(rst_p)//复位
rd_en<=0;
else
if(full==1 && fft_dout_tvalid==0)//FIFO存满,且FFT IP核处于空闲状态
rd_en<=1;//读使能
else if(almost_empty==1)//将空时
rd_en<=0;//关闭读使能
always@(posedge clk or posedge rst_p)
if(rst_p)//复位
valid<=0;
else
if(rd_en==1)//读使能
valid<=1;//FFT输入数据有效指示
else
valid<=0;
assign last=valid & empty;//FFT输入的最后一个数
wire [13 : 0] data_count;
//调用point点FIFO IP核
fifo_65536 i_fifo_point (
.clk(clk), // input wire clk
.srst(rst_p), // input wire srst
.din(wr_data), // input wire [15 : 0] din
.wr_en(wr_en & wr_data_vld), // input wire wr_en
.rd_en(rd_en), // input wire rd_en
.dout(rd_data), // output wire [15 : 0] dout
.full(full), // output wire full
.empty(empty), // output wire empty
.almost_empty(almost_empty), // output wire almost_empty
.data_count(data_count) // output wire [13 : 0] data_count
);
assign fft_dout_re=fft_dout[15:0];//FFT输出数据实部
assign fft_dout_im=fft_dout[31:16];//FFT输出数据虚部
//调用65536点FFT IP核
xfft_point i_xfft_point (
.aclk(clk), // input wire aclk
.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({16\'d0,rd_data}), // input wire [31 : 0] s_axis_data_tdata
.s_axis_data_tvalid(valid), // input wire s_axis_data_tvalid
.s_axis_data_tready(), // output wire s_axis_data_tready
.s_axis_data_tlast(last), // input wire s_axis_data_tlast
.m_axis_data_tdata(fft_dout), // output wire [31 : 0] m_axis_data_tdata
.m_axis_data_tuser(), // output wire [7 : 0] m_axis_data_tuser
.m_axis_data_tvalid(fft_dout_tvalid), // output wire m_axis_data_tvalid
.m_axis_data_tready(1\'b1), // input wire m_axis_data_tready
.m_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(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
);
wire signed [31:0] Sum;
assign Sum=fft_dout_re*fft_dout_re+fft_dout_im*fft_dout_im;//实部虚部平方和
reg signed [31:0] Sum_Max;
(* MARK_DEBUG="true" *)reg [13:0] fre_cnt=14\'d0;
reg [15:0] MAX_cnt=16\'d0;
always@(posedge clk or posedge rst_p)
begin
if(rst_p)//复位
fre_cnt<=14\'d0;
else
if(fft_dout_tvalid==1)//FFT输出数据有效指示
fre_cnt<=fre_cnt+14\'d1;//FFT输出个数计数
else
fre_cnt<=14\'d0;
end
//always@(posedge clk or posedge rst_p)
// ... 以下代码略,完整源码请下载压缩包查看
324