☰
Xilinx FFT IP 实战:1024 点 IFFT 配置、AXI-Stream 接口与 MATLAB 对拍
2026/10/11 3:05:07 网站建设 项目流程

1. 这篇文章解决什么问题

很多 FPGA 新人第一次用 Xilinx FFT IP 时,容易卡在几个地方:

  1. s_axis_data_tdata里面实部、虚部怎么放;

  2. FWD_INV到底是 FFT 还是 IFFT;

  3. SCALE_SCH为什么会影响 MATLAB 对拍;

  4. tvalid / tready / tlast怎么配合;

  5. FPGA 输出为什么和 MATLAB 有 1LSB 左右误差。

本文不讲复杂 FFT 公式,重点讲工程里怎么把 Vivado 的 FFT IP 跑起来,并用 MATLAB 结果对拍。

Xilinx FFT IP 支持正向 FFT、反向 IFFT、定点/浮点接口、多种结构、自然顺序或位反转顺序输出等配置。PG109 中也明确说明,该 IP 使用 AXI4-Stream 接口,支持 forward/inverse complex FFT,并支持定点缩放配置。(赛灵思)


2. 本次 IP 配置

本次仿真使用的是 1024 点 IFFT,输入为 16bit 有符号复数数据。

配置项本文配置
Transform Length1024
Run Time Config LengthDisabled
ArchitecturePipelined Streaming I/O
Data FormatFixed-Point
Input Data Width16
Phase Factor Width24
Scaling OptionsScaled
Rounding ModeConvergent Rounding
Output OrderingNatural Order
Cyclic PrefixDisabled
FWD_INV0,表示 IFFT
SCALE_SCH16'h1AA,本工程测试配置

这里重点说两个参数。

2.1 FWD_INV

FWD_INV = 1表示正向 FFT,FWD_INV = 0表示反向 IFFT。这个不是猜的,PG109 中明确写了这个定义。(赛灵思)

所以本文做的是 IFFT:

FWD_INV = 1'b0; // inverse FFT,也就是 IFFT

2.2 SCALE_SCH

固定点 FFT IP 最容易出问题的地方就是缩放。

PG109 里明确说明,Xilinx FFT IP 的 IFFT 本身不自动实现 MATLABifft()里面的1/N缩放,缩放要靠 scaling schedule 等方式处理。(赛灵思)

所以不能简单认为:

Xilinx IFFT 输出 = MATLAB ifft 输出

更准确地说,应该是:

Xilinx IFFT 输出 = Xilinx IFFT 内部计算结果 + SCALE_SCH 缩放 + 定点量化 + 舍入误差

如果希望和 MATLABifft()对齐,就要特别关注总缩放量。


3. AXI-Stream 接口先看懂这几个信号

FFT IP 主要有三组 AXI-Stream 接口:

// 配置通道 s_axis_config_tdata s_axis_config_tvalid s_axis_config_tready // 输入数据通道 s_axis_data_tdata s_axis_data_tvalid s_axis_data_tready s_axis_data_tlast // 输出数据通道 m_axis_data_tdata m_axis_data_tvalid m_axis_data_tready m_axis_data_tlast m_axis_data_tuser

PG109 中说明,s_axis_config_tdata用来传CP_LEN / FWD_INV / NFFT / SCALE_SCH等配置,s_axis_data_tdata用来传输入复数数据XN_RE / XN_IM,m_axis_data_tdata用来输出XK_RE / XK_IM。(赛灵思)

对新人来说,先记住一句话:

只有 tvalid = 1 且 tready = 1 的那个时钟沿,数据才真正传输成功。

输入 1024 点数据时,前 1023 个点tlast = 0,最后一个点tlast = 1。

简单时序可以这么理解:

clk ↑ ↑ ↑ ↑ ↑ ↑ ↑ s_axis_data_tvalid 1 1 1 1 1 1 1 s_axis_data_tready 1 1 1 1 1 1 1 s_axis_data_tdata D0 D1 D2 D3 ... D1023 s_axis_data_tlast 0 0 0 0 ... 1

注意:tlast不是用来告诉 IP “我要做多少点 FFT” 的。FFT 点数来自 IP 配置,比如本文固定为 1024 点。tlast主要用于帧边界检查,如果数量不对,会触发event_tlast_missing或event_tlast_unexpected这类事件。PG109 里也说明,输入TLAST主要用于生成这些事件。(赛灵思)


4. tdata 里面实部和虚部怎么放

本文是单通道、16bit 实部、16bit 虚部。

PG109 规定,输入数据从 LSB 开始依次打包:先放XN_RE,再放XN_IM。输出也是一样:先放XK_RE,再放XK_IM。(赛灵思)

所以 32bit 的tdata应该这样理解:

// 输入 s_axis_data_tdata[15:0] = XN_RE; s_axis_data_tdata[31:16] = XN_IM; // 输出 m_axis_data_tdata[15:0] = XK_RE; m_axis_data_tdata[31:16] = XK_IM;

写成 Verilog:

wire signed [15:0] ifft_out_real; wire signed [15:0] ifft_out_imag; assign ifft_out_real = $signed(m_axis_data_tdata[15:0]); assign ifft_out_imag = $signed(m_axis_data_tdata[31:16]);

这个地方非常容易写反。

比如输出数据是:

m_axis_data_tdata = 32'hff5c_00a2

那么:

real = 16'h00a2 = 162 imag = 16'hff5c = -164

这和本文仿真波形里的第二个输出点是对应的。


5. 本文测试输入

本次输入数据是 1024 个复数点:

x[0] = 0 + 0i x[1] = 1 + 1i x[2] = 2 + 2i x[3] = 3 + 3i ... x[1023] = 1023 + 1023i

MATLAB 参考代码如下:

N = 1024; x_real = 0:N-1; x_imag = 0:N-1; x = x_real + 1i * x_imag; y = ifft(x); disp(y(1:10));

MATLAB 前几个结果大致为:

511.5000 + 511.5000i 162.4742 - 163.4742i 80.9863 - 81.9863i 53.8234 - 54.8234i 40.2416 - 41.2416i ...

仿真中 FPGA 输出前几个点为:

512 + 512i 162 - 164i 80 - 82i 54 - 54i 40 - 42i ...

可以看到,符号、趋势、数量级都能对上。整数上存在 1LSB 左右差异是正常的,因为 FPGA IP 用的是 fixed-point,内部有缩放、相位因子量化和舍入;MATLAB 默认是 double 浮点计算。

所以对拍时不要要求每个点都和 MATLAB double 结果完全一样,应该结合定点位宽、缩放策略和允许误差判断。


6. 输入数据发送代码示例

下面给一个简化版 testbench 发送逻辑,只保留关键部分。

localparam integer N = 1024; reg aclk; reg aresetn; reg [31:0] s_axis_data_tdata; reg s_axis_data_tvalid; wire s_axis_data_tready; reg s_axis_data_tlast; reg [15:0] s_axis_config_tdata; reg s_axis_config_tvalid; wire s_axis_config_tready; wire [31:0] m_axis_data_tdata; wire m_axis_data_tvalid; reg m_axis_data_tready; wire m_axis_data_tlast; wire [23:0] m_axis_data_tuser; // 100MHz clock initial begin aclk = 1'b0; forever #5 aclk = ~aclk; end // reset initial begin aresetn = 1'b0; s_axis_data_tdata = 32'd0; s_axis_data_tvalid = 1'b0; s_axis_data_tlast = 1'b0; s_axis_config_tdata = 16'd0; s_axis_config_tvalid = 1'b0; m_axis_data_tready = 1'b1; repeat(10) @(posedge aclk); aresetn = 1'b1; end

发送配置:

task send_fft_config; begin @(posedge aclk); // 注意: // 这里的 16'h01AA 是本文工程中的配置示例。 // 实际工程中 s_axis_config_tdata 的位宽和字段位置,要以 Vivado IP 配置界面为准。 s_axis_config_tdata <= 16'h01AA; s_axis_config_tvalid <= 1'b1; while (!s_axis_config_tready) begin @(posedge aclk); end @(posedge aclk); s_axis_config_tvalid <= 1'b0; end endtask

发送 1024 点输入数据:

integer i; task send_one_frame; begin for (i = 0; i < N; i = i + 1) begin @(posedge aclk); s_axis_data_tvalid <= 1'b1; // 输入:x[i] = i + j*i // tdata[15:0] = real // tdata[31:16] = imag s_axis_data_tdata <= {i[15:0], i[15:0]}; if (i == N-1) s_axis_data_tlast <= 1'b1; else s_axis_data_tlast <= 1'b0; while (!s_axis_data_tready) begin @(posedge aclk); end end @(posedge aclk); s_axis_data_tvalid <= 1'b0; s_axis_data_tlast <= 1'b0; s_axis_data_tdata <= 32'd0; end endtask

主流程:

initial begin wait(aresetn == 1'b1); // 建议先配置,再发数据。 send_fft_config(); repeat(5) @(posedge aclk); send_one_frame(); end

PG109 也建议,在希望配置作用于当前帧时,配置通道的数据要在输入数据之前完成;如果配置写入太晚,当前帧可能使用旧配置。(赛灵思)


7. 输出数据抓取

输出端不要按固定延迟去抓数据,应该看m_axis_data_tvalid && m_axis_data_tready。

wire signed [15:0] f0_real; wire signed [15:0] f0_imag; assign f0_real = $signed(m_axis_data_tdata[15:0]); assign f0_imag = $signed(m_axis_data_tdata[31:16]); always @(posedge aclk) begin if (!aresetn) begin // reset end else if (m_axis_data_tvalid && m_axis_data_tready) begin $display("index = %0d, real = %0d, imag = %0d, tdata = %h", m_axis_data_tuser, f0_real, f0_imag, m_axis_data_tdata); end end

如果开启了XK_INDEX,m_axis_data_tuser可以帮助你确认当前输出点编号。本文配置为 Natural Order,所以输出顺序就是:

0, 1, 2, 3, ...

这也是为什么可以直接和 MATLABifft()输出数组逐点对比。

如果你配置成 bit reversed / digit reversed 输出,就不能直接按顺序对拍,需要先重排。


8. 仿真结果对拍

本文测试中,MATLAB 参考结果如下:

y[0] = 511.5000 + 511.5000i y[1] = 162.4742 - 163.4742i y[2] = 80.9863 - 81.9863i

FPGA 仿真波形中可以看到:

f0_real = 512, 162, 80, ... f0_imag = 512, -164, -82, ...

对应关系如下:

点编号MATLAB realMATLAB imagFPGA realFPGA imag
0511.5000511.5000512512
1162.4742-163.4742162-164
280.9863-81.986380-82

这个结果说明:

  1. 实部、虚部没有接反;

  2. IFFT 方向配置正确;

  3. 输出顺序是 Natural Order;

  4. 缩放配置和 MATLAB 参考结果基本一致;

  5. 固定点误差在可接受范围内。


9. 新人最容易踩的几个坑

9.1 把 real 和 imag 放反

单通道 16bit 定点时:

tdata[15:0] 是 real tdata[31:16] 是 imag

不要写反。


9.2 没有用 signed 看数据

如果直接看 16bit 十六进制,负数容易看错。

例如:

16'hff5c

如果按无符号看,是 65372。

但它是 16bit 有符号数,实际是:

-164

Verilog 里建议这样写:

wire signed [15:0] imag; assign imag = $signed(m_axis_data_tdata[31:16]);

9.3 FWD_INV 方向弄反

记住:

FWD_INV = 1 -> FFT FWD_INV = 0 -> IFFT

9.4 忽略 SCALE_SCH

固定点 FFT 不可能无限增长位宽,所以缩放策略非常重要。

如果缩放太少,容易 overflow;如果缩放太多,输出幅度会变小,精度也会损失。

PG109 说明,对于 Pipelined Streaming I/O,SCALE_SCH以 2bit 为一组,每组表示相邻两个 Radix-2 stage 的右移位数;00/01/10/11分别表示右移 0/1/2/3 bit。(赛灵思)

所以SCALE_SCH不是随便填一个数,它会直接影响最终输出幅度。


9.5 对拍时忘记 MATLAB 和 FPGA 的缩放差异

MATLAB 的ifft()默认带1/N。

Xilinx FFT IP 的 IFFT 本身不自动做这个1/N,所以需要通过 scaling schedule 或后级逻辑处理。否则 MATLAB 和 FPGA 的结果可能差一个固定比例。(赛灵思)


10. 总结

用 Xilinx FFT IP,不要一上来就纠结 FFT 数学公式。工程调试时先抓住下面几件事:

  1. 明确 FFT 还是 IFFT:FWD_INV = 0是 IFFT;

  2. 明确输入输出打包:低 16bit 是 real,高 16bit 是 imag;

  3. 明确帧长度:本文是 1024 点,最后一个点拉高tlast;

  4. 明确输出顺序:Natural Order 才能直接和 MATLAB 顺序对比;

  5. 明确缩放关系:固定点 IP 的输出会受SCALE_SCH影响;

  6. 对拍时允许 1LSB 左右误差,不要拿 FPGA fixed-point 和 MATLAB double 做绝对相等比较。

本文这个用例中,输入0+0i, 1+1i, 2+2i ...,MATLABifft()输出和 FPGA 仿真结果能够对应上,说明 IP 配置、AXI-Stream 输入、复数数据打包、输出顺序和基本缩放关系都是正确的。


参考资料:
Xilinx / AMD PG109 Fast Fourier Transform LogiCORE IP Product Guide。你上传的 PG109 文件索引:;本文关键参数校对参考官方在线 PG109。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询