xilinix 的IP核 FFT功能强大
IP核设置
Vivado的FFT IP核支持多通道输入(Number of Channels)和实时更改FFT的点数(Run Time Configurable Transform Length)。Configuration标签下设置FFT的点数(Transform Length)和工作时钟(Target Clock Frequency),选择一种FFT结构,包括流水线Streaming、基4 Burst、基2 Burst和轻量级基2 Burst,计算速度和消耗的资源依次减少。我们一般选用基4 Burst,单次固定输入128个点,输入完成后,开始进行FFT运算,如果数据输入个数错误,或者没有输入完成,FFT是不会输出结果。另外,输入的数据类型为有符号数,仿真数据可以由matlab生成,matlab代码参考本人的另一篇文章 https://blog.csdn.net/baidu_25816669/article/details/119820293
Implementation标签卡下设置FFT的数据格式为定点Fixed Point或浮点Float Point;设置输入数据的位宽和相位因子位宽(相当于旋转因子)。还有一些可选的附加信号。“Output Ordering”设置FFT计算结果以自然顺序(Nature Order)或位/数值反序(Bit/Digit Reversed Order)输出。
Detailed Implementation这个Tab中设置优化方式、存储的类型、是否使用DSP单元等与综合、实现有关的信息,比如可以选择复数乘法器和蝶形运算单元的实现结构。
在IP核右侧,如下图,可以看到设置后,占用的资源,各个接口的功能,以及延时。
这里需要注意S_AXIS_CONFIG_TDATDA,
S_AXIS_CONFIG_TDATDA包含下图数据内容,根据IP核设置的选项,S_AXIS_CONFIG_TDATDA,的位宽核内容有所不同,下图为完整的数据内容,实线框里的内容是始终有,其他会根据IP核设置增加或减少。
如果在configurationaa界面设置没有勾选下Run Time ........,则S_AXIS_CONFIG_TDATDA位宽为八,内D_INV,只需要设置运算类型为FFT还是IFFT。
如果勾选了 Run Time ........,则S_AXIS_CONFIG_TDATDA位宽为十六,在implement detial中可以看到下图的设置位宽对应内容
下图为NFFT设置内容,和运算点数有关。
最麻烦的设置则是和是否截尾、压缩有关的设置,在implement界面选择unscaled,则不需要设置结尾,但资源占用较多,输出数据位宽大,实际工程选择scaled,此时S_AXIS_CONFIG_TDATDA在implement detial中可以看到下图,
对于截尾压缩的设置,参考下图内容,有点复杂。
关于FFT具体配置,可以参考https://blog.csdn.net/gslscyx/article/details/107073799,介绍的很详细。
完整的仿真工程代码如下。
module FFT_TB(
);
reg clk;
reg rst_n;
reg signed [15:0] Time_data_I [127:0];
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 [31: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 [15:0] fft_i_out;
// reg signed [15:0] fft_q_out;
reg signed [23:0] fft_i_out;
reg signed [23:0] fft_q_out;
reg signed [47:0] fft_abs;
integer fid1;
integer i;
integer s;
initial begin
clk = 1'b1;
rst_n = 1'b1;
#5 rst_n = 1'b0;
// #5 rst_n = 1'b1;
fft_m_data_tready = 1'b1;
// $readmemh("C:/Users/HP/Desktop/sin_data1.txt",Time_data_I);//无符号数导入
fid1 = $fopen("C:/Users/HP/Desktop/sin_data.txt","r");//这里的斜杠与计算机里面的斜杠不一样
//有符号数导入
for(i=0;i<=127;i=i+1)
begin
s= $fscanf(fid1,"%d",Time_data_I[i]);
end
$fclose(fid1);
#50 rst_n = 1'b1;;
end
always #5 clk = ~clk;
reg [3:0] state;
//发送时域数据到FFT IP核,主->从
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;
count <= 8'd0;
state<=0;
end
else
case(state)
4'b0:begin
if (fft_s_data_tready) begin//FFT IP核(从设备)已经准备好接收数据,主设备开始发送有效数据
if(count == 8'd127)begin
fft_s_data_tvalid <= 1'b1;
fft_s_data_tlast<=1'b1;//tlast置1
fft_s_data_tdata <= {16'd0,Time_data_I[count]};
count <= 8'd0;
state<=4'b1;
end
else begin
fft_s_data_tvalid <= 1'b1;
fft_s_data_tlast<=1'b0;//tlast置0
fft_s_data_tdata <= {16'd0,Time_data_I[count]};
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
4'b1:begin
fft_s_data_tvalid <= 1'b0;
fft_s_data_tlast <= 1'b0;
if(fft_m_data_tlast)
state<=4'b0;
end
endcase
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];
// fft_i_out<=fft_m_data_tdata[15:0];
// fft_q_out<=fft_m_data_tdata[31:16];
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(16'b1_0000_0111), // 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
);
仿真图形如下,注意有符号和无符号显示的模拟波形不同,应选用有符号显示。下图的频率是镜像的显示。
另外,如果给虚部的最高位写入1,则显示的频谱出现直流分量。