Xilinx FFT IP 实战:1024 点 IFFT 配置、AXI-Stream 接口与 MATLAB 对拍 1. 这篇文章解决什么问题很多 FPGA 新人第一次用 Xilinx FFT IP 时容易卡在几个地方s_axis_data_tdata里面实部、虚部怎么放FWD_INV到底是 FFT 还是 IFFTSCALE_SCH为什么会影响 MATLAB 对拍tvalid / tready / tlast怎么配合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 Length1024Run Time Config LengthDisabledArchitecturePipelined Streaming I/OData FormatFixed-PointInput Data Width16Phase Factor Width24Scaling OptionsScaledRounding ModeConvergent RoundingOutput OrderingNatural OrderCyclic PrefixDisabledFWD_INV0表示 IFFTSCALE_SCH16h1AA本工程测试配置这里重点说两个参数。2.1 FWD_INVFWD_INV 1表示正向 FFTFWD_INV 0表示反向 IFFT。这个不是猜的PG109 中明确写了这个定义。(赛灵思)所以本文做的是 IFFTFWD_INV 1b0; // inverse FFT也就是 IFFT2.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_tuserPG109 中说明s_axis_config_tdata用来传CP_LEN / FWD_INV / NFFT / SCALE_SCH等配置s_axis_data_tdata用来传输入复数数据XN_RE / XN_IMm_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;写成 Verilogwire 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 32hff5c_00a2那么real 16h00a2 162 imag 16hff5c -164这和本文仿真波形里的第二个输出点是对应的。5. 本文测试输入本次输入数据是 1024 个复数点x[0] 0 0i x[1] 1 1i x[2] 2 2i x[3] 3 3i ... x[1023] 1023 1023iMATLAB 参考代码如下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 1b0; forever #5 aclk ~aclk; end // reset initial begin aresetn 1b0; s_axis_data_tdata 32d0; s_axis_data_tvalid 1b0; s_axis_data_tlast 1b0; s_axis_config_tdata 16d0; s_axis_config_tvalid 1b0; m_axis_data_tready 1b1; repeat(10) (posedge aclk); aresetn 1b1; end发送配置task send_fft_config; begin (posedge aclk); // 注意 // 这里的 16h01AA 是本文工程中的配置示例。 // 实际工程中 s_axis_config_tdata 的位宽和字段位置要以 Vivado IP 配置界面为准。 s_axis_config_tdata 16h01AA; s_axis_config_tvalid 1b1; while (!s_axis_config_tready) begin (posedge aclk); end (posedge aclk); s_axis_config_tvalid 1b0; 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 1b1; // 输入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 1b1; else s_axis_data_tlast 1b0; while (!s_axis_data_tready) begin (posedge aclk); end end (posedge aclk); s_axis_data_tvalid 1b0; s_axis_data_tlast 1b0; s_axis_data_tdata 32d0; end endtask主流程initial begin wait(aresetn 1b1); // 建议先配置再发数据。 send_fft_config(); repeat(5) (posedge aclk); send_one_frame(); endPG109 也建议在希望配置作用于当前帧时配置通道的数据要在输入数据之前完成如果配置写入太晚当前帧可能使用旧配置。(赛灵思)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_INDEXm_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.9863iFPGA 仿真波形中可以看到f0_real 512, 162, 80, ... f0_imag 512, -164, -82, ...对应关系如下点编号MATLAB realMATLAB imagFPGA realFPGA imag0511.5000511.50005125121162.4742-163.4742162-164280.9863-81.986380-82这个结果说明实部、虚部没有接反IFFT 方向配置正确输出顺序是 Natural Order缩放配置和 MATLAB 参考结果基本一致固定点误差在可接受范围内。9. 新人最容易踩的几个坑9.1 把 real 和 imag 放反单通道 16bit 定点时tdata[15:0] 是 real tdata[31:16] 是 imag不要写反。9.2 没有用 signed 看数据如果直接看 16bit 十六进制负数容易看错。例如16hff5c如果按无符号看是 65372。但它是 16bit 有符号数实际是-164Verilog 里建议这样写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 - IFFT9.4 忽略 SCALE_SCH固定点 FFT 不可能无限增长位宽所以缩放策略非常重要。如果缩放太少容易 overflow如果缩放太多输出幅度会变小精度也会损失。PG109 说明对于 Pipelined Streaming I/OSCALE_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 数学公式。工程调试时先抓住下面几件事明确 FFT 还是 IFFTFWD_INV 0是 IFFT明确输入输出打包低 16bit 是 real高 16bit 是 imag明确帧长度本文是 1024 点最后一个点拉高tlast明确输出顺序Natural Order 才能直接和 MATLAB 顺序对比明确缩放关系固定点 IP 的输出会受SCALE_SCH影响对拍时允许 1LSB 左右误差不要拿 FPGA fixed-point 和 MATLAB double 做绝对相等比较。本文这个用例中输入00i, 11i, 22i ...MATLABifft()输出和 FPGA 仿真结果能够对应上说明 IP 配置、AXI-Stream 输入、复数数据打包、输出顺序和基本缩放关系都是正确的。参考资料Xilinx / AMD PG109 Fast Fourier Transform LogiCORE IP Product Guide。你上传的 PG109 文件索引本文关键参数校对参考官方在线 PG109。