本文介绍Vivado中Fast Fourier Transform V9.1的使用方法。
参考资料:pg109
FFT理论
FFT是用于计算样本大小为2的正整数幂的离散傅里叶变换(DFT)的高效计算方法。序列的DFT定义为
X
(
k
)
=
∑
n
=
0
N
−
1
x
(
n
)
e
−
j
n
k
2
π
/
N
k
=
0
,
…
N
−
1
X(k) = \sum_{n=0}^{N-1}x(n)e^{-jnk2\pi /N}\ \ k = 0,\dots N-1
X(k)=n=0∑N−1x(n)e−jnk2π/N k=0,…N−1
逆离散傅里叶变换(IDFT)定义为
x
(
n
)
=
1
N
∑
n
=
0
N
−
1
X
(
k
)
e
j
n
k
2
π
/
N
n
=
0
,
…
,
N
−
1
x(n)= \frac{1}{N} \sum_{n=0}^{N-1}X(k)e^{jnk2\pi /N}\ \ n=0,\dots,N-1
x(n)=N1n=0∑N−1X(k)ejnk2π/N n=0,…,N−1
FFT IP核使用Radix-4核Radix-2分解来计算DFT。对于突发I/O体系结构,使用时间抽取(DIT)方法,而流水线式流式I/O体系结构使用频率抽取(DIF)方法。
IP核参数
Configuration选项卡
Number of Channels:变换的通道数,支持1-12通道。
Transform Length:变换的数据长度,支持8-65536的所有2次方。
Architecture Configuration:
Target Clock Frequency (MHz):目标时钟频率。
Target Data Throughput(MSPS):目标数据吞吐量。
注意:设定的时钟频率和设定的数据吞吐量仅用于自动选择一个实现和计算延迟。不保证该核能以指定的目标时钟频率或目标数据吞吐量运行。
Architecture Choice:
Automatically Select:选择满足指定数据吞吐量的最小实现,前提是在FPGA上实现FFT核时达到指定的时钟频率。
Pipelined Streaming I/O::允许连续的数据处理。
Burst 1/O:使用迭代方法分别加载和处理数据。
四种架构的资源与吞吐量对比如下。
Run Time Configurable Transform Length:勾选可在IP核运行时更改变换数据长度。
Implementation选项卡
Data Format:输入和输出的数据格式,可选Fixed-Point和Floating-Point(IEEE-754单精度(32位)浮点格式)。
Scaling Options:输出数据缩放选项,可选Unscaled、Scaled、Block Floating-Point(由IP核决定缩放多少)。
Rounding Modes:舍入模式,可选Convergent Rounding或Truncated。Convergent Rounding,即当一个数字的小数部分正好等于二分之一时,如果数字是奇数,收敛四舍五入就会向上舍入,如果数字是偶数就会向下舍入。Truncated即直接截断。
Precision Options:
Input Data Width:输入数据位宽。
Phase Factor Width: 相位系数位宽。
Control Signals:
ACLKEN:IP核时钟使能信号,勾选时启用。
ARESETn(active low):IP核复位信号,勾选时为低电平有效。
Output Ordering Options:
Output Ordering:输出数据排序,可选Bit/Digit Reversed Order 或Natural Order。即反转顺序或自然顺序。
Cyclic Prefix Insertion:如果输出排序为自然顺序,可以勾选循环前缀插入。
Optional Output Fields:
可选的输出字段。XK_INDEX是数据输出通道的一个可选字段。
OVFLO在数据输出通道和状态通道中都是一个可选字段。
Throttle Scheme:
节流方案。可选Non Real Time或Real Time。即非实时模式与实时模式。
Detailed Implementation选项卡
Memory Options:
选择片内RAM还是分布式RAM。
Optimize Options:
Complex Multipliers:复数乘法器的实现,可选Use CLB logic、Use 3-multiplier structure (resource optimization)、Use 4-multiplier structure (performance optimization)。即使用可编程逻辑资源或使用DSP资源。
Butterfly Arithmetic:蝶形运算的实现,可选 Use CLB logic或Use XtremeDSP Slices。即使用可编程逻辑资源或使用DSP资源。
接口介绍
简单来说,AXI4-Stream接口主要就是一个Basic Handshake。
TVALID由主机驱动,TVALID表示载荷字段(TDATA、TUSER和TLAST)中的值是有效的。
TREADY由从机驱动,TREADY表示从机已准备好接收数据。
当TVALID和TREADY在一个周期内同时为高电平,就会发生数据传输。
所有的TDATA和TUSER字段都以小端格式打包。所有的TDATA和TUSER向量都是8比特的倍数。当TDATA或TUSER向量中的所有字段都被连接起来时,整个向量被填充到8位边界。
s_axis_config_tdata
s_axis_config_tdata总线包含如下字段:
- (optional) NFFT plus padding
- (optional) CP_LEN plus padding
- FWD/INV
- (optional) SCALE_SCH
其中,NFFT位宽为5,表示变换点的大小,NFFT的值等于 l o g 2 ( point size ) log_2(\text{point size}) log2(point size),这个字段只存在于运行时的变换点大小可变的配置。
CP_LEN,表示循环前缀长度,位宽为 l o g 2 ( maximum point size ) log_2(\text{maximum\ point \ size}) log2(maximum point size)。在整个变换输出之前,从变换末端开始作为循环前缀输出的数据数量。CP_LEN可以是0到(point size-1)的任何数字。这个字段只在配置了循环前缀插入时出现。
FWD_INV,表示执行正向FFT变换或逆向FFT变换,为1时,将计算正向变换,为 0时,则计算逆向变换。该字段包含每个FFT数据通道的1位,位0(LSB)代表通道0,位1代表通道1,…。
SCALE_SCH,表示缩放时刻表,每个通道的该字段没有填充,但整个字段都有填充。该字段只在配置缩放时存在(unscaled、 block floating-point、single precision floating-point时不存在)。
对于Burst I/O架构,Radix-4有 l o g 4 ( point size ) log_4(\text{point size}) log4(point size)个阶段,Radix-2则有 l o g 2 ( point size ) log_2(\text{point size}) log2(point size)个阶段。在每个阶段,数据可以被移位0、1、2或3位,对应的SCALE_SCH值为00、01、10、11。
例如,对于Radix-4,当N=1024时,[01 10 00 11 10]转换为阶段0右移2位,阶段1右移3位,阶段2不移,阶段3右移2位,阶段4右移1位,总共有 l o g 4 ( 1024 ) = 5 log_4(1024)=5 log4(1024)=5个阶段,总共缩放了8位,缩放系数为 1 2 8 = 1 / 256 \frac{1}{2^8}=1/256 281=1/256。
对于N=1024,Radix-4架构下可设置SCALE_SCH = [10 10 10 10 11],缩放系数为1/2048,Radix-2架构下可设置SCALE_SCH =[01 01 01 01 01 01 01 01 01 10],缩放系数为1/2048,则可避免溢出。
s_axis_data_tdata
该总线携带输入数据,其中XN_RE为实部分量,XN_IM为虚部分量,位宽
b
xn
\text b_{\text {xn}}
bxn为8-34,采用二进制补码或单精度浮点格式。
注意,所有带填充的字段如果没有在8位边界上结束,就应该扩展到下一个8位边界,即输入数据时,需要手动拓展至8位边界。
m_axis_data_tdata
该总线携带输出数据,其中XK_RE为输出数据实部分量,XK_IM为虚部分量,采用二进制补码或单精度浮点格式。对于缩放运算和块浮点运算,位宽
b
xk
\text b_{\text {xk}}
bxk等于输入数据位宽
b
xn
\text b_{\text {xn}}
bxn,对无缩放运算,则
b
xk
=
b
xn
+
log
2
(
maximum point size
)
\text b_{\text {xk}} = \text b_{\text {xn}} + \text{log}_2(\text {maximum point size})
bxk=bxn+log2(maximum point size),对于单精度浮点运算,则
b
xk
=
32
\text b_{\text {xk}}=32
bxk=32。
各字段采用符号拓展至8位边界。
m_axis_data_tuser
该总线可携带XK_INDEX、BLK_EXP、OVFLO。
XK_INDEX,为输出数据的索引(无符号二进制补码),位宽
log
2
(
maximum point size
)
\text{log}_2(\text {maximum point size})
log2(maximum point size),采用零填。
BLK_EXP,为运算中的缩放系数值(无符号二进制补码),位宽为8,采用零填充。该字段只在块浮点运算时可选。
OVFLO,为溢出指示位(单比特,高电平有效)。如果数据帧中的任何值溢出,OVFLO在结果卸载期间为高电平。该字段只在缩放运算或单精度浮点运算时可选。
m_axis_status_tdata
该总线可携带BLK_EXP、OVFLO。
详细见m_axis_data_tuser总线介绍。
事件信号Event Signals
event_frame_started
当IP核开始处理一个新的帧时,该信号会被拉高一个时钟周期。
event_tlast_missing
当s_axis_data_tlast在一个帧的最后一个输入数据时刻为低时,该信号会被拉高一个时钟周期。该信号表明了IP核和输入数据的帧大小不匹配,拉高时表明了输入数据帧大于IP核配置的大小。
event_tlast_unexpected
当IP核检测到在任何输入数据上s_axis_data_tlast为高电平,而这个数据不是帧中的最后一个。这表明IP核和输入数据的帧大小不匹配,拉高时表明了输入数据帧小于IP核配置的大小。
event_fft_overflow
当m_axis_data_tdata上传输的数据出现溢出时,该信号在每个时钟周期内都会被拉高。
只有在使用缩放运算或单精度浮点运算时,才有可能出现溢出。在其他配置情况下,该引脚会被移除。
event_data_in_channel_halt
该信号在IP核需要数据输入通道的数据而没有数据的每个时钟周期内都会被拉高。
event_data_out_channel_halt
该信号在IP核需要向数据输出通道写入数据,但由于通道中的缓冲区已满而不能写入时,会被拉高一个时钟周期。当这种情况发生时,IP核所有活动停止,直到通道缓冲区有可用空间。该信号只在非实时模式下可用。
event_status_channel_halt
该信号在IP核需要向状态通道写入数据,但由于通道上的缓冲区已满而不能写入时,会被拉高一个时钟周期。当这种情况发生时,IP核所有活动停止,直到通道缓冲区有可用空间。该信号只在非实时模式下可用。
使用实例
前面已经详细讲述了IP核的各项参数,根据需要自行配置即可。
仿真参数配置如下。
本次仿真使用MATLAB生成输入数据。信号生成代码如下。
clc;clear;close all;
fs = 1e4;
f1 = 5e2;
N = 1024;
t = 0:1/fs:(N-1)/fs;
x = sin(2*pi*f1*t);
x = mapminmax(x).* (2^15-1);
fid = fopen('fft_test_signal.txt', 'wt');
for i = 1:N
if (x(i) >= 0)
fprintf(fid, '%s\n', dec2bin(x(i),16));
else
fprintf(fid, '%s\n', dec2bin(2^16 + x(i), 16));
end
end
fclose(fid);
y = fft(x(1:256)) / 2^9;
y_re = real(y);
y_im = imag(y);
plot(y_re);
figure;
plot(y_im);
仿真代码如下。
module FFT_sim;
reg aclk;
reg aresetn;
reg [23:0] s_axis_config_tdata;
reg s_axis_config_tvalid;
reg [31:0] s_axis_data_tdata;
reg s_axis_data_tvalid;
reg s_axis_data_tlast;
reg m_axis_data_tready;
wire [31:0] m_axis_data_tdata;
wire [7:0] m_axis_data_tuser;
wire m_axis_data_tlast;
wire m_axis_data_tvalid;
wire s_axis_config_tready;
wire s_axis_data_tready;
wire event_frame_started;
wire event_tlast_unexpectedl;
wire event_tlast_unexpected;
wire event_tlast_missing;
wire event_status_channel_halt;
wire event_data_in_channel_halt;
wire event_data_out_channel_halt;
initial begin
aclk = 1'b0;
forever begin
#2.5; aclk = ~aclk;
end
end
initial begin
aresetn = 1'b0;
# 40;
aresetn = 1'b1;
end
initial begin
s_axis_config_tdata = {7'b000_0000, 16'b_01_01_01_01_01_01_01_10, 1'b1};
s_axis_config_tvalid = 1'b1;
end
reg [15:0] data_in[0:1024-1];
integer i;
initial begin
$readmemb("D:/Xilinx2/Vivado/2018.3/user_projects/ip_fft/fft_test_signal.txt", data_in);
i = 0;
s_axis_data_tdata = 32'd0;
s_axis_data_tlast = 1'b0;
m_axis_data_tready = 1'b1;
# 10;
forever begin
@(negedge aclk) begin
if(s_axis_data_tready == 1'b1) begin
s_axis_data_tvalid = 1'b1;
s_axis_data_tdata = {data_in[i], 16'd0};
if(i == 1024-1) begin
i = 0;
end
else begin
i = i+1;
end
end
else begin
s_axis_data_tvalid = 1'b0;
end
end
end
end
wire [15:0] Xk_Re, Xk_Im;
assign Xk_Re = m_axis_data_tdata[31:16];
assign Xk_Im = m_axis_data_tdata[15:0];
FFT your_instance_name (
.aclk(aclk), // input wire aclk
.aresetn(aresetn), // input wire aresetn
.s_axis_config_tdata(s_axis_config_tdata), // input wire [23 : 0] s_axis_config_tdata
.s_axis_config_tvalid(s_axis_config_tvalid), // input wire s_axis_config_tvalid
.s_axis_config_tready(s_axis_config_tready), // output wire s_axis_config_tready
.s_axis_data_tdata(s_axis_data_tdata), // input wire [31 : 0] s_axis_data_tdata
.s_axis_data_tvalid(s_axis_data_tvalid), // input wire s_axis_data_tvalid
.s_axis_data_tready(s_axis_data_tready), // output wire s_axis_data_tready
.s_axis_data_tlast(s_axis_data_tlast), // input wire s_axis_data_tlast
.m_axis_data_tdata(m_axis_data_tdata), // output wire [31 : 0] m_axis_data_tdata
.m_axis_data_tuser(m_axis_data_tuser), // output wire [7 : 0] m_axis_data_tuser
.m_axis_data_tvalid(m_axis_data_tvalid), // output wire m_axis_data_tvalid
.m_axis_data_tready(m_axis_data_tready), // input wire m_axis_data_tready
.m_axis_data_tlast(m_axis_data_tlast), // output wire m_axis_data_tlast
.event_frame_started(event_frame_started), // output wire event_frame_started
.event_tlast_unexpected(event_tlast_unexpected), // output wire event_tlast_unexpected
.event_tlast_missing(event_tlast_missing), // output wire event_tlast_missing
.event_status_channel_halt(event_status_channel_halt), // output wire event_status_channel_halt
.event_data_in_channel_halt(event_data_in_channel_halt), // output wire event_data_in_channel_halt
.event_data_out_channel_halt(event_data_out_channel_halt) // output wire event_data_out_channel_halt
);
endmodule
仿真波形如下。
启用XK_INDEX时,该字段由m_axis_data_tuser携带。
根据上图可知频谱实部分量的第一个波峰出现在XK_INDEX = 13时,XK_RE = 1191。由NFFT = 256,fs = 1e4Hz,可知XK_INDEX / NFFT * fs = 507.8 Hz。
仿真结果与MATLAB计算结果一致。需要注意的是,仿真时FFT IP核的SCALE_SCH = 16’b_01_01_01_01_01_01_01_10,对应缩放系数为1/512。故MATLAB计算的FFT结果也需要除以512。
关于输入数据流控制的补充
以输入数据通道为例,其有s_axis_data_tdata、s_axis_data_tvalid、s_axis_data_tready、s_axis_data_tlast四组接口。
为了方便查看数据,配置FFT IP核的运算数据个数为16个,缩放系数为1/16,运行时钟频率为50MHz(周期20ns)。
输入数据为16个9999,FFT的输出数据应为1个5000和15个0;
x = 9999*ones(1,16);
X = fft(x,16)/2^4;
时序1
此时s_axis_data_tdata和s_axis_data_tvalid的维持时间是320ns,即16个时钟周期,s_axis_data_tlast在第16个数据输入周期时同时也拉高一个时钟周期。事件信号也无异常。
输出数据如下,且与理论相符。
时序2
此时s_axis_data_tdata和s_axis_data_tvalid同样维持16个时钟周期,但是s_axis_data_tlast在第16个数据输入周期时不拉高。则此时事件信号event_tlast_missing会被拉高一个时钟周期。
输出数据同时序1情况,正确。
时序3
此时s_axis_data_tdata和s_axis_data_tvalid同样维持16个时钟周期,但是s_axis_data_tlast在第13个数据输入周期时拉高,即提前拉高。则此时事件信号event_tlast_unexpected和event_tlast_missing均会被拉高一个时钟周期。
输出数据同时序1情况,正确。
时序4
此时s_axis_data_tdata和s_axis_data_tvalid一共维持20个时钟周期,其前16个时钟周期数据为9999,后4个时钟周期的数据为1000。s_axis_data_tlast一直保持为低。
输出数据同时序1情况,正确。
时序5
此时s_axis_data_tdata和s_axis_data_tvalid一共维持20个时钟周期,其前16个时钟周期数据为9999,后4个时钟周期的数据为1000。但是s_axis_data_tlast在第20个数据输入周期时拉高。
输出数据同时序1情况,正确。
小结
根据时序1-5的结果可知,
FFT IP核计算时取的是s_axis_data_tdata输入数据流的前16个数据。
s_axis_data_tlast信号对于IP核的计算结果无影响,其只是起一个指示作用。
时序6
为了进一步验证。
将输入数据流改为10个9999和10个1000。s_axis_data_tlast仍然在第20个数据输入周期时拉高。
数据输出结果:
文章来源:https://www.toymoban.com/news/detail-414456.html
x = [9999*ones(1,10), 1000*ones(1,10)];
X = (fft(x,16)/2^4);
X_Re = round(real(X));
X_Im = round(imag(X));
可以看到此时输出结果与Matlab仿真一致。文章来源地址https://www.toymoban.com/news/detail-414456.html
到了这里,关于Vivado_FFT IP核 使用详解的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!