Vivado2017.4上实现FFT,使用FFT IP核
在进行FFT实现前,当然需要提前了解一下快速傅里叶变换(FFT)的基本概念和基础知识啦!
一、IP核
IP核就是知识产权核或知识产权模块的意思,在EDA技术开发中具有十分重要的地位。美国著名的Dataquest咨询公司将半导体产业的IP定义为“用于ASIC或FPGA中的预先设计好的电路功能模块”。IP(Intellectual Property)内核模块是一种预先设计好的甚至已经过验证的具有某种确定功能的集成电路、器件或部件。IP主要分为软IP、固IP和硬IP。软IP是用Verilog/VHDL等硬件描述语言描述的功能块,但是并不涉及用什么具体电路元件实现这些功能。固IP是完成了综合的功能块。硬IP提供设计的最终阶段产品——掩膜。(这些都可以百度到)
二、快速傅里叶变换(FFT)
FFT是一种DFT的高效算法,称为快速傅立叶变换(fast Fourier transform)。傅里叶变换是时域一频域变换分析中最基本的方法之一。在数字处理领域应用的离散傅里叶变换(DFT:Discrete Fourier Transform)是许多数字信号处理方法的基础 [1] 。
FFT基本上可分为时间抽取法和频率抽取法,而一般的时间抽取法和频率抽取法只能处理长度N=2^M的情况,另外还有组合数基四FFT来处理一般长度的FFT。
所谓抽选,就是把长序列分为短序列的过程,可在时域也可在频域进行。最常用的时域抽选方法是按奇偶将长序列不断地变为短序列,结果使输入序列为倒序,输出序列为顺序排列,这就是Coolly—Tukey算法 [2] 。这里只是简要的介绍。
三、Vivado2017.4上实现
3.1新建工程
双击打开Vivado2017.4软件,开始创建新工程。
这里的工程名称和工程的保存路径根据自己的需求进行设定。
然后一直点击Next。到达选择元器件这一步时,还是根据自己的板子型号进行一下选择,养成习惯。
最后点击Finish,完成工程的创建。
3.2添加和配置FFT IP核
依次:IP Catalog–>输入FFT搜索–>双击Fast Fourier Transform
我们知道FFT的IP核配置起来比较复杂,左侧为IP核的接口图(IP Symbol)、实现消耗的资源等信息(Implementation Details)、计算FFT所需的时间(Latency);右侧为Configuration、Implementation和Detailed Implementation三个配置选项。
在Configuration中,主要配置如下,Number of Channels:通道数、 Transform Length:FFT的点数、 Target clock Frequency:工作的时钟、 Architecture Choice:FFT的结构选择。
FFT IP核提供了四种可选择的计算结构架构,可以在资源和转换时间之间进行权衡,具体包括:
流水线I/O:允许连续数据处理;
Radix-4突发I/O:使用迭代方法分别加载和处理数据。使用资源大小比流水线解决方案小,但是转换时间较长;
Radix-2突发I/O:使用与基-4相同的迭代方法,但蝶形较小。使用资源比基-4更少,但是转换时间更长;
Radix-2 Lite突发I/O:基于基2体系结构,该变体使用时间复用方法使用更小的内核执行蝶形运算,代价是转换时间更长。
在Implementation中,主要配置如下,FFT的数据格式为定点Fixed Point;设置输入数据的位宽(Input data width)和相位因子位宽( Phase factor width )为8;Output ordering设置FFT计算结果以自然顺序(Nature order)或位/数值反序(bit/digit reversed order)输出。这些设置都是根据自己的需求来设置便可。
在Detailed Implementation配置页中,主要是内部数据块的使用和优化方式、存储类型、是否使用DSP单元等于综合、实现有关等信息。可不用配置。
配置完成后,我们左侧可以查看配置的结果,IP symbol中主要查看各种接口;Implementation detals中有较多的信息,比如结构、长度、数据带宽等,需要注的是CONFIG TDATA这一项,与配置接口的参数有关,在使用中需要正确配置。latency显示出计算FFT所需的时间。点击OK就可以完成配置。IP核的配置方法都一样。
FFT的IP核配置完成后,在IP Sources中找到xfft_0.veo文件,我们用这个模板创建的实例,根据需要对它进行重新配置和封装。即是对配置好的IP核进行例化。
3.3 代码测试
引脚定义:
aclk: 上升沿有效
aclken: 高电平时钟使能(可选)
aresetn: Active-Low同步清除(可选,始终优先于aclken)。需要两个周期的最小有效脉冲。
s_axis_config_tvalid:配置频道的TVALID。 由外部主设备发出信号,表明它能够提供数据。
s_axis_config_tready: TREADY作为配置通道。 核心声明表示已准备好接受数据。
s_axis_config_tdata: TDATA用于配置通道。携带配置信息:CP_LEN,FWD / INV,NFFT和SCALE_SCH。 请参阅运行时间转换配置。
s_axis_data_tvalid: 数据输入通道的TVALID。 由外部主设备用来表示它能够提供数据。
s_axis_data_tready: 数据输入通道的TREADY。核心用它来表示已经准备好接受数据。
s_axis_data_tdata: TDATA为数据输入通道。 载入未处理的采样数据:XN_RE和XN_IM。 请参阅数据输入通道。
s_axis_data_tlast: 数据输入通道的TLAST。 在框架的最后一个样本上由外部主设备声明。 除了生成事件event_tlast_unexpected和event_tlast_missing事件之外,核心不使用这个事件。
m_axis_data_tvalid: 数据输出通道的TVALID。 核心声明表示能够提供样本数据。
m_axis_data_tready: 数据输出通道的TREADY。由外部从设备断言,表示已准备好接受数据。 仅以非实时模式呈现。
m_axis_data_tdata: TDATA为数据输出通道。载入处理后的样本数据XK_RE和XK_IM。
m_axis_data_tuser: 用于数据输出通道的TUSER。携带附加的每个样本信息,例如XK_INDEX,OVFLO和BLK_EXP。
m_axis_data_tlast: 数据输出通道的TLAST。在框架的最后一个样本上由核心声明。
m_axis_status_tvalid: 状态通道的TVALID。核心声明表示它能够提供状态数据。
m_axis_status_tready: TREADY用于状态通道。由外部从设备断言,表示已准备好接受数据。 仅以非实时模式呈现。
m_axis_status_tdata: TDATA的状态通道。携带状态数据:BLK_EXP或OVFLO.See状态通道。
event_frame_started: 当核开始处理一个新的帧时声明。请参阅event_frame_started。
event_tlast_unexpected: 当内核在不是帧中最后一个数据采样的s_axis_data_tlast为高电平时触发。请参阅event_tlast_unexpected。
event_tlast_missing: 在帧的最后一个数据样本上s_axis_data_tlast为低时断言。 请参阅event_tlast_missing。
event_fft_overflow: 在从数据输出通道卸载的数据样本中发现溢出时断言。 只有当溢出是一个有效的选项时才出现。 请参阅event_fft_overflow。
event_data_in_channel_halt: 内核从数据输入通道请求数据并且没有可用时声明。 请参阅event_data_in_channel_halt。
event_data_out_channel_halt: 当内核尝试将数据写入数据输出通道并且无法执行时声明。 仅以非实时模式呈现。 请参阅event_data_out_channel_halt。
event_status_channel_halt: 当内核尝试将数据写入状态通道并且无法执行时断言。 仅以非实时模式呈现。 请参阅event_status_channel_halt。
测试代码如下:
`timescale 1ns / 1ps
module FFT_test2();
reg clk;
reg rst_n;
reg signed [15:0] Time_data_I[127:0];
reg data_finish_flag;
wire fft_s_config_tready;
reg signed [31:0] fft_s_data_tdata;
reg fft_s_data_tvalid;
wire fft_s_data_tready;
reg fft_s_data_tlast;
wire signed [47:0] fft_m_data_tdata;
wire signed [7:0] fft_m_data_tuser;
wire fft_m_data_tvalid;
reg fft_m_data_tready;
wire fft_m_data_tlast;
wire fft_event_frame_started;
wire fft_event_tlast_unexpected;
wire fft_event_tlast_missing;
wire fft_event_status_channel_halt;
wire fft_event_data_in_channel_halt;
wire fft_event_data_out_channel_halt;
reg [7:0] count;
reg signed [23:0] fft_i_out;
reg signed [23:0] fft_q_out;
reg signed [47:0] fft_abs;
initial begin
clk = 1'b1;
rst_n = 1'b0;
fft_m_data_tready = 1'b1;
$readmemb("D:/matlab/MatlabCode/FPGA/data_before_fft.txt",Time_data_I);
end
always #5 clk = ~clk;
always @ (posedge clk or negedge rst_n) begin
if(!rst_n) begin
fft_s_data_tvalid <= 1'b0;
fft_s_data_tdata <= 32'd0;
fft_s_data_tlast <= 1'b0;
data_finish_flag <= 1'b0;
count <= 8'd0;
rst_n = 1'b1;
end
else if (fft_s_data_tready) begin
if(count == 8'd127) begin
fft_s_data_tvalid <= 1'b1;
fft_s_data_tlast <= 1'b1;
fft_s_data_tdata <= {Time_data_I[count],16'd0};
count <= 8'd0;
data_finish_flag <= 1'b1;
end
else begin
fft_s_data_tvalid <= 1'b1;
fft_s_data_tlast <= 1'b0;
fft_s_data_tdata <= {Time_data_I[count],16'd0};
count <= count + 1'b1;
end
end
else begin
fft_s_data_tvalid <= 1'b0;
fft_s_data_tlast <= 1'b0;
fft_s_data_tdata <= fft_s_data_tdata;
end
end
always @ (posedge clk) begin
if(fft_m_data_tvalid) begin
fft_i_out <= fft_m_data_tdata[23:0];
fft_q_out <= fft_m_data_tdata[47:24];
end
end
always @ (posedge clk) begin
fft_abs <= $signed(fft_i_out)* $signed(fft_i_out)+ $signed(fft_q_out)* $signed(fft_q_out);
end
//fft ip核例化
xfft_0 u_fft(
.aclk(clk), // 时钟信号(input)
// .aresetn(rst_n), // 复位信号,低有效(input)
.s_axis_config_tdata(8'd1), // ip核设置参数内容,为1时做FFT运算,为0时做IFFT运算(input)
.s_axis_config_tvalid(1'b1), // ip核配置输入有效,可直接设置为1(input)
.s_axis_config_tready(fft_s_config_tready), // output wire s_axis_config_tready
//作为接收时域数据时是从设备
.s_axis_data_tdata(fft_s_data_tdata), // 把时域信号往FFT IP核传输的数据通道,[31:16]为虚部,[15:0]为实部(input,主->从)
.s_axis_data_tvalid(fft_s_data_tvalid), // 表示主设备正在驱动一个有效的传输(input,主->从)
.s_axis_data_tready(fft_s_data_tready), // 表示从设备已经准备好接收一次数据传输(output,从->主),当tvalid和tready同时为高时,启动数据传输
.s_axis_data_tlast(fft_s_data_tlast), // 主设备向从设备发送传输结束信号(input,主->从,拉高为结束)
//作为发送频谱数据时是主设备
.m_axis_data_tdata(fft_m_data_tdata), // FFT输出的频谱数据,[47:24]对应的是虚部数据,[23:0]对应的是实部数据(output,主->从)。
.m_axis_data_tuser(fft_m_data_tuser), // 输出频谱的索引(output,主->从),该值*fs/N即为对应频点;
.m_axis_data_tvalid(fft_m_data_tvalid), // 表示主设备正在驱动一个有效的传输(output,主->从)
.m_axis_data_tready(fft_m_data_tready), // 表示从设备已经准备好接收一次数据传输(input,从->主),当tvalid和tready同时为高时,启动数据传输
.m_axis_data_tlast(fft_m_data_tlast), // 主设备向从设备发送传输结束信号(output,主->从,拉高为结束)
//其他输出数据
.event_frame_started(fft_event_frame_started), // output wire event_frame_started
.event_tlast_unexpected(fft_event_tlast_unexpected), // output wire event_tlast_unexpected
.event_tlast_missing(fft_event_tlast_missing), // output wire event_tlast_missing
.event_status_channel_halt(fft_event_status_channel_halt), // output wire event_status_channel_halt
.event_data_in_channel_halt(fft_event_data_in_channel_halt), // output wire event_data_in_channel_halt
.event_data_out_channel_halt(fft_event_data_out_channel_halt) // output wire event_data_out_channel_halt
);
endmodule
上诉代码中的“D:/matlab/MatlabCode/FPGA/data_before_fft.txt”
其代码为
Fs=100; %采样率1ns一个点
%t=0:1/Fs:63/Fs; %数据时长:64个采样周期
N = 128;
n = 1:N;
t = n/Fs;
%% 生成测试信号
f1 = 10;
f2 = 30;
s1 = cos(2*pi*f1*t);
s2 = cos(2*pi*f2*t);
signalN = 2 + s1 + s2 ;
data_before_fft = 100*signalN; %系数放大100倍
%% 把数据写到txt里面,让fpga调用
fp = fopen('data_before_fft.txt','w');
for i = 1:N
if(data_before_fft(i)>=0)
temp= dec2bin(data_before_fft(i),16);
else
temp= dec2bin(data_before_fft(i)+2^16+1, 16);
end
for j=1:16
fprintf(fp,'%s',temp(j));
end
fprintf(fp,'\r\n');
end
fclose(fp);
%% 绘制fft信号
y = fft(data_before_fft,N);
y = abs(y);
f = n*Fs/N;
plot(f,y);
运行仿真后就可完成测试。点击Run Behavior Simulation进行测试,测试结果为:
仅供参考!!!
参考文献
[1] 赵成主编,DSP原理及应用技术 基于TMS320F2812的仿真与实例设计,国防工业出版社,2012.01,第230页
[2] 宿富林,冀振元,赵雅琴等主编,数字信号处理,哈尔滨工业大学出版社,2012.12,第111页