ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

FPGA OFDM基带设计:从MATLAB模型到可综合RTL的落地路径与避坑指南

FPGA OFDM基带设计:从MATLAB模型到可综合RTL的落地路径与避坑指南

简介:这份基于Xilinx FPGA的OFDM通信系统基带设计代码,源自史治国编著、浙江大学出版社出版的配套光盘,面向通信工程、电子科学与技术专业学生,以及从事FPGA无线通信开发的工程师和研究者,内容围绕OFDM基带收发链路的FPGA实现,覆盖FFT/IFFT、循环前缀、信道估计等关键模块,可作为课程设计、毕业设计或科研预研的参考起点。压缩包共收录227个文件,总大小4.59MB,文件类型以Verilog/VHDL源码(v、vhd)、Xilinx ISE工程文件(ise、xco)、IP核接口模板(veo、vho)及符号定义(asy)为主,另有若干rar压缩包便于分包存储;目录结构按功能模块拆分,配合mif初始化文件和ngc网表,基本能还原随书例程的完整工程环境。该资源已有1322人浏览学习,按模块顺序阅读源码和IP核配置,可快速理解OFDM基带处理中FFT运算、数据缓存RAM及同步设计的具体实现,再结合Xilinx ISE环境自行仿真与验证,对提升FPGA数字通信系统设计能力有直接帮助。

1. 为什么OFDM基带要自己用FPGA动手写:从MATLAB的一行ifft到可综合代码的距离

在 MATLAB 里写 OFDM 发射机最容易,一行ifft就能得到时域波形,加上循环前缀就能画出漂亮的频谱图;但换成基于 Xilinx FPGA 的 OFDM 通信系统基带设计,很多想当然的步骤都会卡住。FPGA 里没有隐式的浮点运算,IFFT 要用固定位宽实现,还要面对 AXI-Stream 握手、流水延迟、定点截位、符号同步这些工程问题。这篇文章想讲的不是 OFDM 的数学公式推导,而是一条从算法模型走到可综合代码的落地路径:发射链路和接收链路怎么拆、位宽怎么定、FFT IP 怎么配、CP 插入的 RTL 怎么写、哪些坑在仿真里根本看不出来。适合已经会用 FPGA 写基础逻辑,正准备交付一版真实基带波形的开发者。

2. OFDM基带整体架构与模块拆解:先画数据流,再谈代码实现

2.1 发射端数据流:从比特到OFDM符号的完整链路

在动手写 RTL 之前,我习惯先把基带数据流画成一张三级结构图。第一级是比特级:原始比特经过加扰、信道编码和交织,变成一串连续比特。第二级是符号级:把比特每 B 个一组映射成 QPSK、16QAM 或 64QAM 复数符号,同时插入导频和空子载波,形成频域向量。第三级是样点级:把频域向量交给 IFFT,得到时域样点,加循环前缀,再按 I/Q 两路输出给射频前端。把数据流分成这三段,最大的好处是可以独立约束每段的位宽和时序,避免一整条链路上到处都是位宽错误。

如果按模块清单看,OFDM 发射端比传统单载波系统多出来的核心模块就是 IFFT 和 CP 插入。IFFT 把频域并行数据变成时域串行数据,CP 则把 OFDM 符号尾部的采样点复制到头部,用来抵抗多径时延扩展。这两个模块在 FPGA 里的资源消耗都不小,IFFT 需要复数乘法器和蝶形运算单元,CP 插入需要一块符号深度的存储,所以架构设计阶段就要确认子载波数量和 CP 长度,否则后级模块的地址都算不对。

数据段数据形式典型位宽主要模块
比特段0/1 串流1 bit加扰、编码、交织
符号段I/Q 并行复数2×(16 bit)QAM映射、导频插入
样点段I/Q 串行复数2×(16 bit)IFFT、加CP、成型滤波

比特级在 FPGA 里通常不是性能瓶颈,用移位寄存器就能完成;符号级要注意映射表的实现,16QAM 的判决边界是 0 和 ±2,映射表可以用 case 语句写死,也可以做成 ROM。导频插入的位置不是随便选的,它会落到子载波索引的固定位置,工程上常用梳状导频,每隔几个子载波放一个已知符号;FPGA 里做导频插入并不复杂,用一个计数器判断当前子载波索引是否等于导频位置,是则从导频 ROM 取数代替数据,同时拉高导频有效标记。

真正容易翻车的是空子载波处理。OFDM 频谱两侧通常要留 DC 和 Guard Band,这些位置不能送数据;如果映射阶段就把空子载波补 0,IFFT 输入位宽不会变,总符号数也不会变,后续 FFT 和解映射必须按同样位置剔除空子载波。所以我在 RTL 里会专门拉一个is_pilot或is_null标记信号,跟着数据一起穿过 IFFT,否则接收端根本不知道哪些子载波才是有效数据,整条链路就只能靠猜。

2.2 接收端数据流:同步、去CP、FFT与信道估计的闭环

接收端的复杂度比发射端高一截。发射端只需要按固定时序把符号发出去;接收端必须回答三个问题:符号从哪里开始、载波偏了多少、信道把每个子载波的幅度相位改成了什么。对应到 RTL 就是三个模块:符号同步、频偏估计与补偿、信道估计与均衡。符号同步常见做法是延迟相关,利用循环前缀与 OFDM 符号尾部重复的特性做自相关,找到峰值位置;在 FPGA 里,自相关计算消耗的乘法器很多,需要拆成滑动窗口和累加器来降低资源。

频偏估计通常在时域完成。如果发送端插入了一段重复训练序列,接收端把两个重复段共轭相乘再求相位,就能得到小数倍频偏;频偏补偿需要在时域乘一个旋转因子。这个旋转因子的生成在 FPGA 里一般用 CORDIC 或者查找表,相位精度至少 16 位,否则补偿后残余频偏会让星座点缓慢旋转,解调门限明显抬高。

FFT 窗口对齐是 OFDM 系统最精密的时序。窗口超前或滞后若干个采样点,会引起旋转因子误差和符号间干扰;RTL 里应该在去 CP 模块中保存一个symbol_start脉冲,这个脉冲和第一个有效样点的 tvalid 必须严格对齐。我见过某开发者在代码里把symbol_start多打了一拍没发现,结果整个 FFT 窗口偏一位,误码率从千分之一跳到百分之十,改回后立刻正常。窗口定位精度直接决定接收端可用性,这是仿真里不能省掉的检查项。

2.3 时钟域与接口选择:AXI-Stream 还是自定义握手信号

OFDM 基带用单一主时钟比较简单。如果收发芯片的采样时钟和 FPGA 逻辑时钟同频,建议全部模块挂在同一个时钟域下,FFT IP、CP 插入、同步模块之间直接 AXI-Stream 直连,避免跨时钟域 FIFO 带来的额外延迟。只有当 ADC/DAC 采样时钟必须与 FPGA 逻辑时钟异步时,才在最前端加异步 FIFO,其他部分仍然保持单时钟。

数据通路上的接口,我强烈建议统一用 AXI4-Stream。原因有两个:第一,Xilinx 的 FFT IP、FIR IP、FIFO IP 都带 AXI-Stream 接口,直接对接省去一层转换器;第二,AXI-Stream 的 tvalid/tready 握手机制能自然处理背压,模块间不会丢数据。自定义的单线握手也可以工作,但一旦需要插入 FIFO、加流水或做多路选择,就要重写接口逻辑,代价很大。

AXI-Stream 的核心是 valid 和 ready。发送方把数据放上总线并拉高 tvalid,接收方准备好时拉高 tready;当 tvalid 和 tready 同时为高时,数据成功传输。如果接收方忙,可以拉低 tready,数据必须保持有效直到被接收。这个规则必须在 OFDM 链路里严格统一:所有下游逻辑都不能在 tready 拉低时丢掉 tvalid。tlast 用于标志一帧的最后一个数据,OFDM 里就是一帧有效子载波的最后一项,它必须和最后一个子载波同拍出现。

3. 定点化和MATLAB参考模型:让FPGA行为和仿真脚本对齐

3.1 为什么必须先做定点仿真:浮点与定点的误差边界

先明确一个判断:直接用浮点模型去验证 RTL,是对不上的。Xilinx FFT IP 的输入输出虽然可以配置成 16、24、32 位,但内部都是定点运算,舍入和截断每级各一次。OFDM 系统对幅度失配很敏感,信道估计的结果要和已知导频做除法,很小的定点误差就会被均衡器放大;如果映射是 64QAM,判决门限更密集,EVM 超过 2% 就会开始冒误码。

所以真正工程流程应该是两步。第一步,在 MATLAB 里先写一版浮点 golden model,作为算法正确性的参照;第二步,把所有模块逐步改成定点模型,IFFT 用与 FPGA 相同位宽、相同缩放策略的定点运算,比较定点输出和浮点输出的 EVM。只要定点 EVM 控制在 1% 以内,就可以把它作为 RTL testbench 的期望值。如果只上板抓星座图,星座发散和逻辑错误都会呈现为乱点,很难定位是量化误差、模块 bug 还是同步错位,这种“黑匣子”式调板效率最低。

我建议项目一开始就约定定点模型和 RTL 逐级对拍,每完成一个模块验证一个模块,而不是等整个链路全写完再一次性调板。很多工程团队到最后阶段才慌张,就是因为没有一个参照物,所有问题都挤在板测阶段爆发,后面改位宽改接口,成本成倍增加。

3.2 位宽选择方法与 MATLAB 代码映射

决定位宽要从 OFDM 符号的峰均比开始。OFDM 时域信号是多个子载波叠加,峰值明显高于平均值,64 个子载波时峰均比通常在 10 dB 左右。如果 IFFT 输入用 16 位,而内部每级都保持满幅,很容易溢出。Xilinx FFT IP 的 Scaling Schedule 就是为了解决这个问题,它会在每个蝶形级右移若干 bit 避免输出溢出,但代价是量化噪声增加。到底是每级右移一位还是两位,不能凭感觉定。

我的经验是先把 MATLAB 定点模型跑起来,用fi对象逐级模拟。先固定 FFT 输入位宽为 16 位,I/Q 各 16 位,然后扫描不同的分数长度和缩放策略,观察 16QAM 下的星座点 EVM 和误码率,选择量化损耗最小的一组参数。工程上 16 位是性价比最高的选择,24 位资源接近翻倍,但性能提升有限。

% 16QAM 符号映射并转成 16 位定点 word_len = 16; frac_len = 10; bits = randi([0 1], 64*4, 1); sym = qammod(bits, 16, 'InputType', 'bit', ... 'UnitAveragePower', true); sym_fi = fi(sym, 1, word_len, frac_len); % 用与 FPGA 一致的 64 点 IFFT ifft_out = ifft(sym_fi, 64); % 量化噪声评估:和浮点 IFFT 比较 EVM ifft_ref = ifft(double(sym), 64); evm = std(abs(ifft_out(:) - ifft_ref(:))) / std(abs(ifft_ref(:))); fprintf('EVM = %.2f%%\n', evm*100);

这个代码把 64 个 16QAM 符号做映射,以 10 bit 分数长度存成定点,再做 64 点 IFFT,最后和浮点 IFFT 比较 EVM。分数长度设为 10 时,I/Q 各能表示大约 -4 到 4 的范围,对 16QAM 足够;如果改用 64QAM,最大幅度接近 7.07,frac_len 可能要降到 8,把整数位放宽到 8。注意这个 EVM 只能在统计意义上用,不能单看一个符号就判断位宽够不够。

3.3 用Co-Simulation对拍RTL与模型:反复核对每个符号

定点模型做好之后,下一步就是让 RTL 与模型对拍。常见做法是,把 MATLAB 定点模型的激励和期望输出写成文本文件;Verilog testbench 在仿真开始前用$readmemh读入激励,跑完逻辑后用脚本比较实际输出和期望输出。这样能定位到第几个符号、第几个样点出错,比只看误码率直观得多。

% 将定点 IFFT 输出导出成 RTL 可读的 HEX 文件 fp = fopen('ifft_expected.hex', 'w'); for n = 1:length(ifft_out) re = real(ifft_out(n)); im = imag(ifft_out(n)); fprintf(fp, '%04x %04x\n', ... typecast(int16(re*2^frac_len), 'uint16'), ... typecast(int16(im*2^frac_len), 'uint16')); end fclose(fp);

这里导出的是两列 HEX,一列实部、一列虚部,testbench 里可以按列读取,和 DUT 的 m_axis 输出逐周期比较。需要提醒的是,FFT IP 输出有流水延迟,比较时要把期望值和实际输出做 shift 对齐,否则会把流水延迟误判成数据错误。我第一次做对拍就吃过这个亏:看到第三级输出全错,其实只是期望文件没加延迟,数据本身完全对;把模型输出向后移动一个固定 latency 后,所有结果立刻匹配。

4. 基于Xilinx IP核的实现步骤:从IFFT到CP插入的可综合代码

4.1 配置Xilinx FFT IP用于IFFT:架构、缩放与溢出处理

Xilinx 的 FFT IP 在 Vivado IP Catalog 里可以直接搜到,配置界面里最重要的几项是 Transform Length、Implementation Options、Data Format、Scaling Options 和 Output Order。做 OFDM 基带时,Transform Length 要等于有效子载波总数,常见是 64、128、256 或 1024;如果子载波数不是 2 的幂,就要先做子载波分配,把有用符号放到对应索引上,其余补零。

Implementation Options 建议选 Pipelined Streaming,它能让复数数据连续流入,正好匹配 OFDM 连续帧;如果只需要突发帧,Radix-2 Burst 可以省不少逻辑资源。Data Format 选 Fixed Point。Scaling Options 我建议先用 Block Floating Point 试跑,后续要精确控制噪声时再改成手动 Scaling Schedule。Block Floating Point 的优点是不用自己逐级计算增益,缺点是输出增益会随输入动态变化,信道估计时需要做归一化;手动缩放则增益固定,接收端更容易反推增益关系。

配置完 IP,生成的例化模板里端口很多,但只关注 s_axis_config、s_axis_data 和 m_axis_data 三组就够了。做 IFFT 时,s_axis_config 里的 FWD_INV 位要设成 1。下面是一段例化示意,端口名以你当前 IP 版本生成模板为准:

xfft_0 u_xfft ( .aclk (clk), .s_axis_config_tdata (8'h01), // 示意:低1位 FWD_INV=1,实际按IP配置位域 .s_axis_config_tvalid (cfg_tvalid), .s_axis_config_tready (cfg_tready), .s_axis_data_tdata (tx_sym_tdata), .s_axis_data_tvalid (tx_sym_tvalid), .s_axis_data_tready (tx_sym_tready), .m_axis_data_tdata (ifft_tdata), .m_axis_data_tvalid (ifft_tvalid), .m_axis_data_tready (ifft_tready) );

cfg_tvalid 只在配置写入期间拉高一个周期,FFT IP 通常要求每个变换开始前重新配置一次。OFDM 系统里每个符号都需要一次 config 握手,所以这段逻辑不能只在复位时初始化一次,而是要跟着帧起始信号一起发,否则后续帧都会按上一次的配置执行,可能导致 FFT/IFFT 方向出错。

4.2 写AXI-Stream时序:tlast、tready与背压处理

FFT IP 的输入输出都要求正确的 AXI-Stream 时序。操作规则很简单:正常时刻把数据放在 tdata 上并拉高 tvalid;当 tvalid 和 tready 同时为高时,数据被采走,计数器加一;当计数到 FFT_LEN-1 时,当前数据必须打上 tlast,下一个周期回到 0。如果 tready 在某周期拉低,计数器和 tlast 都不能推进。FFT IP 输入 tdata 的位宽根据配置会按 2 的幂对齐,你传的 I/Q 数据要拼接到约定位置,比如 16 bit I 在高 16 位、16 bit Q 在低 16 位。

帧尾和帧头之间如果有 idle gap,不能把 tvalid 拉高却不给 tlast,否则 FFT IP 会认为这是一个超长帧,内部状态混乱。因此每个 OFDM 符号的最后一个有效子载波必须和 tlast 对齐,哪怕映射时插入了 null 子载波,也要把 null 当成数据参与计数,tlast 始终落在 idx=FFT_LEN-1 的位置。同样,tready 拉低期间 tdata/tvalid/tlast 都不能变,这是 AXI-Stream 背压规则里最容易忽略的一条。

4.3 发射端模块Verilog骨架:映射后的符号组装成FFT帧

下面这段可综合逻辑是“符号组装 + tlast 生成”的核心,适合放在 QAM 映射器和 FFT IP 之间:

parameter FFT_LEN = 64; reg [5:0] sym_cnt; reg tx_tvalid = 1'b0; reg tx_tlast = 1'b0; reg [15:0] tx_tdata = 16'd0; always @(posedge clk or negedge rst_n) begin if (!rst_n) begin sym_cnt <= 0; tx_tvalid <= 1'b0; tx_tlast <= 1'b0; end else begin // 只有上游数据有效且下游能接收时,帧计数才算前进 if (qam_tvalid && fft_tready) begin tx_tvalid <= 1'b1; tx_tdata <= qam_tdata; // I/Q 拼接符号 if (sym_cnt == FFT_LEN - 1) begin tx_tlast <= 1'b1; sym_cnt <= 0; end else begin tx_tlast <= 1'b0; sym_cnt <= sym_cnt + 1; end end else if (fft_tready) begin // 无新数据时输出帧间 idle tx_tvalid <= 1'b0; tx_tlast <= 1'b0; end end end

这里的qam_tvalid和fft_tready是上游映射器和下游 FFT IP 的握手信号。只要有一方条件不满足,状态就不变,这是背压规则的关键。当sym_cnt到 63 且握手成功时,tlast 拉高并清零计数;下一周期如果 FFT 端还没准备好,tvalid 继续保持为高,直到握手成功为止。tx_tdata 的值必须保持到握手成功,不能提前改变,否则已经上总线的数据会被下一次赋值覆盖。

接下来是 CP 插入。IFFT 输出 64 个时域样点,CP 长度按 OFDM 符号长度的四分之一左右取,常见是 16。CP 插入本质是“先发最后 16 个,再从第 0 个发到第 63 个”。省 RAM 的方法是直接把 FFT 输出写入一个 64 深的环形缓冲区,tlast 到来后停止写入,然后按“先 48 到 63、再 0 到 63”的读地址顺序向外发送。状态机大概这样:

localparam S_RX = 2'd0; localparam S_TX_CP = 2'd1; localparam S_TX_BODY = 2'd2; reg [1:0] cp_state; reg [6:0] rd_addr; reg [5:0] cp_len_cnt; always @(posedge clk or negedge rst_n) begin if (!rst_n) begin cp_state <= S_RX; rd_addr <= 0; cp_len_cnt <= 0; end else begin case (cp_state) S_RX: begin if (ifft_tvalid && ifft_tready) begin buf[wr_addr] <= ifft_tdata; if (ifft_tlast) begin rd_addr <= FFT_LEN - CP_LEN; cp_len_cnt <= 0; cp_state <= S_TX_CP; end end end S_TX_CP: begin // 输出循环前缀,地址从 FFT_LEN-CP_LEN 到 FFT_LEN-1 // 每次握手成功后 cp_len_cnt 加 1 end S_TX_BODY: begin // 输出整个 OFDM 符号,地址从 0 到 FFT_LEN-1 // 结束后回到 S_RX end endcase end end

这里不展开两段完整代码,是避免和你的数据位宽以及 IP 端口绑定,但核心点要抓到:循环前缀的输出不能依赖其他模块,只要 tready 不满足就停在当前地址;CP 部分发送完进入 Body 部分时,读地址要回绕到 0,而不是继续线性读。很多翻车案例就出在地址回绕上,发完 CP 后读地址没有归零,直接发到符号末尾,接收端 FFT 窗口整个错位。

5. OFDM基带设计避坑指南:五个高频问题与排查方法

5.1 现象:星座图旋转或散焦

现象:接收端解映射后的星座点不是清晰的四簇或十六簇,而是围绕理想点旋转,甚至每一帧内旋转角度不一样。原因:FFT 窗口没有对齐会引入子载波间干扰;频偏补偿没生效会引入公共相偏;数字下变频 I/Q 不平衡会引入镜像干扰。最容易被忽视的是 IFFT/FFT 输出顺序问题,比如 IFFT 配置成 Natural Order,接收端 FFT 也配成 Natural Order,看起来对称,但实际 OFDM 子载波索引负半轴和正半轴在不同 IP 里的映射顺序不一致,解映射前没做索引翻转,星座就会整体旋转。

解决:先用单子载波测试信号,不叠加多子载波,只看 FFT 输出。单音通过后,再用梳状导频检查每个导频所在子载波的相位。如果导频位置的相位误差随子载波索引线性变化,就是定时偏;如果是常数,就是公共相位;如果规律不固定,多半是频偏补偿的旋转因子没跟上。

5.2 现象:CP插入后FFT窗口错位,误码率不下降

现象:单独看 CP 插入模块波形正确,发射链路和接收链路单独仿真也正确,但接收端 FFT 窗口总错几位,误码率在一个底线上不去。原因:CP 长度和 FFT 窗口位置之间隔着一个整数符号长度,但有的设计在接收端忽略符号起点标记,而是用 FFT 输出端的 tlast 反推窗口。当发送端 CP 插入后引入了一拍流水延迟,接收端用 tlast 反推就会偏移,延迟越大错得越多。

解决:不要在接收端从 FFT 输出反推窗口,应该用同步模块输出的 symbol_start 脉冲直接控制去 CP 的读指针。调试时把 symbol_start 和 IFFT 输入第一帧的 tvalid 打印到同一个逻辑分析仪窗口,确认两者之间的固定延迟等于发射链路流水延迟,然后把延迟差补进接收端窗口。这个方法看着简单,但能把误码率从百分之几直接压到千分之一以下。

5.3 现象:AXI-Stream tready拉低后数据丢失,仿真里却不丢数据

现象:单独仿真 TX 链路过全对,一旦把模块串起来,实测 OFDM 帧头数据少一两个子载波。原因:AXI-Stream 要求 tvalid 在 tready 拉低时保持数据不变;但很多模块在 tready 拉低时,把 tdata 和 tvalid 也一起拉低,认为这拍没传成功。仿真里下游永远 tready=1,所以看不出来;板上前级处理有抖动,tready 一低就丢数据。

解决:在代码规范里加一条硬规则:握手成功之前,驱动方不允许改变 tdata、tvalid 和 tlast;只有 tvalid 和 tready 同时为高后,模块才能进入下一拍状态。最好在模块里加断言,用 SystemVerilog assertion 检查tvalid && !tready期间 tdata 是否变化,回归测试时任何违反都会立即报错。

5.4 现象:FFT IP缩放配置导致信噪比突变

现象:Block Floating Point 模式下,星座点整体幅度忽大忽小,EVM 值随信号幅度变化波动明显。原因:Block Floating Point 的增益会根据一帧数据的最大幅度自动调整,两帧最大值不同,输出增益就不同;如果接收端固定归一化,帧间幅度差异就变成了调制干扰。解决:改用 Manual Scaling Schedule,为每一级确定右移位数,然后根据总增益在接收端做固定放大或移位补偿。确定 schedule 的方法是先用 MATLAB 统计输入信号各级的最大幅值,保证每级不溢出且不要右移过多;常见策略是前级多移、后级少移,避免把大动态范围信号压缩太多。

5.5 现象:复位和配置顺序导致偶发性IFFT首帧错误

现象:上电后第一次 IFFT 输出总少几个点,第二次以后正常;或者复位时序不稳时,某些板子每隔几帧就错一次。原因:FFT IP 需要先完成配置通道握手,再接收数据;s_axis_config_tvalid 必须在复位释放后、第一个数据帧之前拉高,而且 config tready 握手完成后数据才能进入。有些模块在复位的同时就开始发数据,IP 配置还没生效,首帧自然出错。解决:用一个状态机按“复位释放、写配置、等待 config_tready、等一个 idle 周期、再拉数据 tvalid”的顺序驱动。还要确认 aclken 引脚是否默认打开,有的工程把它接到了某个不复位的使能信号,导致 IP 长期处于时钟禁用状态。

6. 收尾:用ILA和自环测试给基带设计上双保险

6.1 片上逻辑分析仪观察关键信号

RTL 仿真通过只是第一步,板上调试时,我习惯在 ILA 里同时观察五个信号:发送端映射完成的 tvalid、FFT IP 输入 tdata、IFFT 输出 tvalid、接收端 FFT 窗口 symbol_start、解映射后输出的有效数据。把 ILA 触发条件设为接收端误码计数器的上升沿,而不是只抓首帧,这样抓到的数据大概率是故障发生时刻前后的真实链路状态。采样深度至少给 8192,保证抓到一整个 OFDM 符号和 CP 区域,否则只能看到碎片,判断不了整体时序。

6.2 自环测试与误码率估算

自环测试是最快验证基带可用性的方式。在 FPGA 内部把发射端 I/Q 数据直接接到接收端,中间不加任何信道模型,只插一个数据衰减和噪声注入模块,看解调误码率。如果自环误码率不接近 0,说明基带模块之间有确定性 bug,不是射频问题;自环跑通后再接入 DAC/ADC 环回,把射频失真引进来逐级排查。误码率测试不能只看几个包,我通常发至少 6 万帧,按 OFDM 符号数统计误码,算出来的 BER 才有统计意义。拿几百帧就断言“通了”,那只是运气。

6.3 习惯:每改一个参数,重跑一遍定点对拍脚本

写到这里,我的个人习惯已经很明显:每改 FFT 缩放、CP 长度或映射位宽,不是直接上板,而是先在 MATLAB 定点模型里重跑一遍 EVM,再重新导出期望文件,RTL 回归仿真通过后才上板。这个流程看起来慢,实际比上板抓波形省时间得多。OFDM 基带的坑大多数不是逻辑复杂度高,而是级联后时序和位宽容易在某个环节悄悄出错;没有模型对拍作参照,最后只能靠玄学改参数。希望这条从架构到定点、再到 IP 配置和板级调试的路径,能帮你把 OFDM 基带从仿真图变成一套能跑误码的交付代码。

本文还有配套的精品资源,点击获取

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进