资讯详情

资讯详情

FPGA基带与中频信号处理实战:关键模块与工程经验

1. 项目概述与整体设计思路1.1 通信链路里的基带和中频到底在干什么做通信系统、雷达信号处理或者软件无线电的人迟早要在FPGA里面和基带跟中频这两个词打交道。我第一次在板子上同时把基带和中频算法跑通的时候折腾了大半个月回头看其实很多弯路是因为一开始没把一个核心问题想明白——基带和中频本质上是在不同频率坐标系下做同一件信号处理的事。先解释一下基本概念。基带是指零频附近、未经长距离载波调制的原始信号符号速率高、带宽窄信息密度大中频则是把基带信号搬移到几十兆到几百兆赫兹的载频上方便射频前端处理、变频和滤波也能避开直流偏置、闪烁噪声这些问题。很多项目一开始就纠结是直接做零中频还是两次变频这个选择会直接决定FPGA内部算法架构怎么搭、滤波器怎么设计、NCO数控振荡器频率字怎么配。我自己经历过的一个项目是宽带接收机天线进来经过射频前端到中频ADC然后所有信号处理全部在FPGA里完成。这时候中频部分要干的是DDC数字下变频——把中频信号搬到基带并且抽取降速基带部分要干的是定时同步、载波同步、均衡、解映射这套东西。两者方向相反但结构对称DUC数字上变频就是把基带信号内插滤波再搬回中频。弄清楚这套镜像关系FPGA里的资源规划就有数了。1.2 为什么这些算法必须用FPGA而不是DSP或CPU有人说这些算法DSP也能跑没必要上FPGA。这个话对了一半。如果只做几百kbps的低速调制解调DSP绰绰有余但一旦进入宽带、多通道、高速率的场景FPGA的并行优势就压倒了所有其他方案。举个实际数字。一个100MHz带宽的信号IQ两路采样率至少要200MSPSDSP的串行指令流跑FFT、FIR这种高度并行且反复迭代的运算每一拍只能执行几条指令时钟跑到1.5GHz也很难实时做完所有的乘加。FPGA不一样逻辑资源透明地摊开一个FIR滤波器可以把几十个乘法器并行排开一个时钟周期出几十个滤波结果。这种吞吐量优先的特性决定了高速ADC出来的数据流必须落到FPGA里。还有一个现实因素接口。现代射频芯片输出LVDS或者JESD204B的高速串行数据ADC采样率动辄几百兆甚至上G这些接口协议在FPGA里有现成的硬核IP可以对接DSP那点EMIF并行接口根本吃不下。我在项目里就被JESD204B坑过后来老老实实把链路层、传输层、物理层的对齐时序在FPGA里一条条捋干净才算稳定跑起来。所以在方案选型上我的做法是前端数据预处理DDC/DUC、滤波抽取、同步一律进FPGA后端协议处理和上层控制比如MAC封装、状态机调度、配置管理可以在Zynq里用ARM核来跑软逻辑。这种软硬配合的架构是目前很主流的做法。2. 基带算法的FPGA实现要点2.1 脉冲成型滤波与匹配滤波基带算法里第一个要处理的往往是脉冲成型。数字通信里的码元是有间隔的如果不做成型信号频谱会拖得很宽对邻道形成干扰而且还展不开眼图。工程上最常用的就是根升余弦滤波器RRC配合接收端的匹配滤波两个根升余弦叠加之后等效成一个完整的升余弦正好满足奈奎斯特无码间串扰条件。RRC滤波器在FPGA里的实现不复杂核心就是一个对称FIR。滚降因子α是设计参数一般在0.2到0.5之间α越大带宽越宽但眼图张得越开α越小频谱效率高但对同步精度要求也高。我搭过的最基本结构是module rrc_filter #( parameter DATA_WIDTH 16, parameter COEF_WIDTH 16, parameter TAPS 33 )( input wire clk, input wire rst_n, input wire signed [DATA_WIDTH-1:0] data_in, input wire data_valid_in, output reg signed [DATA_WIDTH-1:0] data_out, output reg data_valid_out ); reg signed [COEF_WIDTH-1:0] coef_rom [0:TAPS-1]; reg signed [DATA_WIDTH-1:0] shift_reg [0:TAPS-1]; wire signed [DATA_WIDTHCOEF_WIDTH-1:0] mul_out [0:TAPS-1]; reg signed [DATA_WIDTHCOEF_WIDTH4:0] acc; integer i; // 初始化系数实际会从外部ROM加载 initial begin for (i 0; i TAPS; i i 1) coef_rom[i] 16h0000; end // 移位寄存器组 always (posedge clk or negedge rst_n) begin if (!rst_n) begin for (i 0; i TAPS; i i 1) shift_reg[i] d0; end else if (data_valid_in) begin shift_reg[0] data_in; for (i 1; i TAPS; i i 1) shift_reg[i] shift_reg[i-1]; end end // 并行乘法 genvar g; generate for (g 0; g TAPS; g g 1) begin : mul_gen assign mul_out[g] shift_reg[g] * coef_rom[g]; end endgenerate // 累加 always (posedge clk or negedge rst_n) begin if (!rst_n) begin acc d0; data_out d0; data_valid_out 1b0; end else if (data_valid_in) begin acc mul_out[0] mul_out[1] ... mul_out[TAPS-1]; data_out acc[TAPS*... - 1 : DATA_WIDTH-1]; // 截位 data_valid_out 1b1; end else begin data_valid_out 1b0; end end endmodule这里有几个容易出问题的细节。第一个是系数对称性一定要利用起来RRC系数是偶对称的把首尾相加再乘一次系数乘法器数量能减一半FIR面积直接砍半。第二个是截位别乱截累加器位宽要留够不然中间溢出之后符号翻转整条链路瞬间报废这种bug特别隐蔽波形上看不出来只能靠仿真比对。第三个是数据有效信号必须贯穿始终多拍延迟没关系但valid不能丢丢了后面同步模块拿不到数据整个解调链路就会卡死。2.2 定时同步Gardner算法及其FPGA实现成型滤波之后就是定时同步。收端和发端的时钟不可能完全一致采样点往往不落在最佳判决点定时同步就是为了校正这个偏差。工程里用得最多的是Gardner算法因为它对载波相位不敏感可以在载波同步之前先做简化了环路级联的复杂度。Gardner算法有个经典公式e (y(n-1/2)) * (y(n) - y(n-1))其中y(n)是当前符号的插值输出y(n-1/2)是两个符号中间时刻的采样。用环路滤波器把这个误差e平滑成频率控制字再去控制一个小数插值器。FPGA实现里核心模块就是插值滤波器最常用的是Farrow结构的立方插值器。我搭的定时同步模块基本思路如下匹配滤波输出送入Farrow插值器插值的小数间隔μ由环路累加器输出控制每个时钟周期符号计数器归纳出当前是符号中间点还是符号判决点根据误差公式计算出定时误差ee经过二阶环路滤波比例项加积分项输出调整量给Farrow的μ累加器μ累加器每次增加1就说明需要跳过/重复一个采样周期从而完成速率校正。环路参数非常有讲究。比例增益Kp大了环路锁定快但是抖动大积分增益Ki小了跟踪不了频偏。我一般先做浮点Python仿真确认环路带宽再转到定点FPGA里。环路带宽一般取符号速率的1%~3%比较稳太大锁不牢太小锁太慢。有一次我在现场调试一台设备定时环路始终锁不住眼图开得乱七八糟。查了很久发现是插值器输出的有效信号没有和环路使能对齐数据valid飘了几拍导致误差信号计算时用了错位的采样点。后来把所有模块的延迟核对了一遍在数据路径上补拍数环路一下子就锁住了。所以送大家一句话数字环路不可怕可怕的是时序关系你没对齐。2.3 载波同步与均衡载波同步要做的是消除收发两端载波频率和相位偏差。QPSK、QAM这类信号用Costas环最合适误差提取用的是判决前后的差值锁定后相位偏差被压到极小。FPGA实现里这个环路很吃定点精度相位累加器至少要32位不然频率分辨率太粗残余频偏会把星座图转成一圈。Costas环里的NCO我常用CORDIC算法实现因为只用加减和移位就能算正余弦不消耗DSP的硬件乘法器。但要注意CORDIC有收敛范围输入角度先要做象限折叠输出幅度会有约1.6倍的增益缩放需要配合增益补偿。第一次用CORDIC的时候没注意这点环路增益异常导致锁定速度特别慢查了两天才发现是CORDIC输出幅度不对。均衡模块按项目需求来短信道用线性均衡的LMS算法就够长时延多径就得考虑判决反馈均衡DFE。LMS在FPGA里的关键路径是误差反馈回路更新抽头系数时存在递归依赖时钟一高就出现时序收敛不了的问题。解决思路一般有两条一是把步长因子做成2的幂次方用移位代替乘法二是把更新路径加流水虽然系数滞后一拍但收敛性能影响很小时序能轻松跑上去。卡尔曼滤波在均衡、信道估计里也有用但FPGA实现卡尔曼面临的麻烦是矩阵求逆资源开销太大工程上通常做简化比如固定增益的稳态卡尔曼一来避免更新的除法运算二来省下大量乘法器。这个思路对很多自适应算法都通用——能离线优化的分量尽量离线算好FPGA里只留运行时的递归部分。3. 中频算法的FPGA实现3.1 DDC和DUC的整体架构与参数规划中频处理的本质是频率搬移加采样率变换。DDC做的是从带通中频搬到基带低速DUC反过来把基带低速信号搬回中频高速。架构通常都是经典的三兄弟结构混频器、CIC滤波器、半带HB滤波器和整形FIR。以DDC为例信号流是这样的高速ADC把中频信号采样成数字序列采样率fsNCO产生与中频频率对应的正交载波和输入信号相乘完成频谱搬移混频后的信号送入CIC抽取滤波器完成第一级大抽取比如4~8倍接着是半带滤波器再做2倍抽取半带滤波器好处是系数约一半为零运算量天然少一半最后靠一个线性相位的FIR做整形滤波和残余抽取既滤除带外噪声也把通带纹波压到设计指标内。DUC就是倒过来先插值后混频但滤波器的级联顺序和抽取方向必须小心。很多人第一次设计DUC时容易犯一个错误直接在最后一级做上变频导致混频后的镜像信号落在过渡带上抑制不掉。正确做法是混频放在几级插值滤波器之后混频之前的滤波器负责把镜像抑制住混频本身只做频率搬移。3.2 NCO与混频的工程实现NCO数控振荡器是中频处理的心脏。它本质上是一个相位累加器加一个正余弦查找表相位累加器的频率控制字决定输出频率。频率分辨率是fs / 2^NN是累加器位宽。比如500MHz采样、32位相位累加器频率分辨率约0.116Hz对绝大多数通信系统绰绰有余。NCO的SFDR无杂散动态范围直接决定混频后信号的纯度。查找表位宽不够时输出会出现量化杂散底噪被抬起来。我之前遇到过SFDR只有50dBc的情况带内杂散严重干扰了误码率测试。后来把查找表从18位扩到24位并加了抖动ditherSFDR提升了近20dB问题才彻底解决。抖动加入门坎低收益大许多工程师容易忽略。混频器实现很简单——一个复数乘法器。但要注意定点位宽的控制。中频ADC出来的数据位宽一般16位NCO正余弦输出16位复数乘法结果32位过不了两个周期就必须做截位。截位策略我建议用四舍五入加少量随机抖动不要用截断截断会引起直流偏移和低电平杂散特别影响小信号解调。3.3 CIC滤波器抽取插值的第一道关卡CIC滤波器在DDC里承担大抽取率的粗滤波任务。它的最大优点是不需要乘法器只有积分器和梳状器全部是加法和延迟非常适合高速率大抽取的场景。CIC的传递函数是H(z) [(1 - z^(-DM)) / (1 - z^(-1))]^N。实现上分成积分级和梳状级积分级工作在高速时钟梳状级放在抽取后低速时钟这样节省大量寄存器。级数N一般取3到5太少阻带衰减不够太多通带跌落严重还要引入补偿滤波器。CIC有个经典问题是通带内有跌落passband droop尤其抽取率大的时候信号在通带边缘可能被压低好几个dB。所以CIC后面通常要配一个CIC补偿FIR在中频处理链路里做反向增益补偿。我习惯把CIC补偿和内插整形合到同一个FIR系数里省一级滤波器资源。当时做这个合并的时候用MATLAB的fdesign工具直接生成补偿系数然后手工转成定点系数存到ROM里省了不少事。定点仿真在CIC设计里尤其重要。CIC的增益很大等于(RM)^N如果设计时不做好增益预算中间结果很容易溢出。我一般会把仿真顶层的结果抓出来对比浮点模型的输出用误差均方根来评估截位是否合理。实测下来每个积分器留3-4个比特的余量然后逐级截位是资源与性能最平衡的做法。4. FPGA工程落地的关键细节4.1 定点量化和数据位宽的平衡FPGA里没有浮点所有算法都要注意定点。定点最核心的问题就是位宽和性能的博弈。位宽越多动态范围越大、量化噪声越小但乘法器、存储器和布线资源都会显著增加时钟频率也可能降下来。具体到设计中我有一套实用做法信号类型位宽选择说明ADC前端输入与ADC位数一致或高2位保留余量防止滤波器增益溢出滤波器系数16位大多数场景下足够太高乘法器资源翻倍滤波器累加器输入位宽 系数位宽 log2(TAPS) 2防止累加溢出环路累加器32位以上频率分辨率需要插值分数间隔16~20位太小导致插值抖动增大我在实际项目里用过一个通用经验每一步滤波后数据位宽保留到原始位宽滤波器增益所需位宽1这个1是为了后续处理留的安全裕度。仿真无误后再逐步收紧看看误码率有没有恶化。这样能最快定位到到底是哪一级的截位造成了性能损失。很多工程师总觉得反正FPGA资源多位宽给宽点就完事。但尤其是在Zynq这类SoC里DSP硬核个数是有限的一个乘法器在两个DSP上跑两个24x24乘法时性能会折损。你做完一个18位系数的FIR发现DSP不够用再回头改成16位系数等于白做一版。所以前期估算资源时就要把位宽选型考虑进去。4.2 串行与并行架构的选择FPGA的优势是并行但不代表所有算法都必须并行写。常见的一个提升资源效率的思路是时分复用TDM。多通道信号处理时每一个通道的数据速率可能不高比如基带部分只有几十MSPS而FPGA时钟跑到200MHz以上那么完全可以一套滤波器用时分复用同时处理4个通道。这样乘法器数量急剧下降但逻辑里要增加通道切换控制逻辑。我之前做一个四通道DDC的时候最初每个通道独立设计CICHBFIRDSP乘法器用掉了一百多个。后来改成时钟三层分频复用同一套滤波组DSP用量降低到二十个左右功耗也跟着降了不少。代价是控制逻辑复杂了许多但和资源节省相比那点复杂度完全值得。还有一个选择是流水线和并行处理的偏好。流水线适合数据流方向单一、级联较多的场景比如DDC链路里各级滤波器级联并行处理适合数据需要被多路复制的场景比如多路同步检测。这两个思路还可以混用核心原则是让每一级处理单元的吞吐率和数据率匹配不要出现某一段特别忙、某一段闲置的失衡局面。4.3 接口与数据流的衔接FPGA做基带中频处理绕不开各种接口。ADC侧LVDS、JESD204B基带侧可能有AXI-Stream跨时钟域更是家常便饭。LVDS接ADC相对简单但要注意源同步时钟的时序约束。ADC送出来的DDR数据要用ISERDES原语在中间位置采样。我习惯先抓一组特定的训练序列验证bit对齐和word对齐没问题后再让数据正式流入DDC。这个过程看似多余但能帮你百分之百确认硬件链路可靠省去后边所有玄学问题。JESD204B就比较折磨人了。它涉及确定性延迟、SYSREF对齐、多片同步、链路层参数协商。我踩过最大的坑是SYSREF的布线时序没约束好导致每次上电后链路对齐状态不稳定有时通有时不通。最后用set_input_delay和set_output_delay把所有时钟路径约束清楚再加一个上电自动重对齐状态机才算彻底解决。做高速接口时序约束一定不能偷懒靠运气吃饭迟早会翻车。AXI-Stream在Zynq方案里是基带数据和ARM交互的标准通道。要注意的是TDATA位宽和数据协议字段TKEEP、TLAST的一致性很多工程师只管TDATA不管TKEEP传了非对齐数据包时会把系统状态带乱。基带数据从FPGA搬运到DDR、再由Linux应用读取这中间DMA描述符的环形缓冲管理也很容易出错。我建议先用小包测试链路稳定再用大包压吞吐别一上来就跑满带宽去调。4.4 时序收敛的实践经验把算法写对只是第一步让它在FPGA里跑到需要的时钟频率是第二步。时序收敛是很多工程师头疼的地方尤其是基带和中频混合的链路组合逻辑路径动不动就几百级。我最常用的三板斧插入流水寄存器、拆分组合逻辑、缩小扇出。FIR滤波器的乘累加路径是最容易成为关键路径的。解决办法是在乘法器和累加器之间插入寄存器让乘法和加法各占一个时钟周期。代价是输出延迟多了一拍但流水线结构下吞吐量不受影响。我见过有些工程师为了省寄存器把累加写在一个always块里不分段结果时钟只能跑到80MHz加了流水之后直接跳到180MHz。扇出太大也会拖慢布线。一个控制信号带几百个触发的使能端时布线资源会爆炸。解决方法是复制几份相同的控制寄存器分别驱动不同的区域或者用BUFG全局时钟树来分配。这个技巧在给大量滤波器模块同步复位的时候特别实用。5. 实测问题与排查经验5.1 常见问题的快速定位思路做基带中频的FPGA系统出了问题常常不像软件那样好定位。硬件链路上每一级都可能出错排查起来非常痛苦。我总结了一套自己的问题排查顺序分享出来给大家参考第一先验证数据通道的连通性。用伪随机码注入从ADC进来一直抓到最后一级输出看到的数据前后是否一致。这一步能排除大量的接口错位、位宽截断、valid丢拍等问题。第二看频谱和星座图。频谱能告诉你频率搬移有没有做对星座图能告诉你同步环路状态怎么样。如果频谱上出现不该有的杂散问题多半在NCO或混频器如果星座图是旋转的问题多半在载波同步如果是散开的云状问题多半在定时同步或均衡。第三逐级对比FPGA输出和MATLAB浮点模型。我一般在项目一开始就会搭建一套MATLAB的浮点模型FPGA每完成一级模块就把同样激励送进去对比输出误差。误差在量化噪声范围内说明没问题差距大了就一级一级二分排查。这个习惯帮我节省了无数调试时间。5.2 几个印象深刻的现场问题第一个问题是DDC输出频谱底噪异常抬高。排查发现ADC数据在接入DDC之前经过了多级寄存器中间某个信号的位宽截位设置有误导致有效位数降低了近6位。重新按定点方案调整位宽后底噪立刻恢复了正常。这种隐蔽问题只能靠定点仿真和实测频谱相结合来发现。第二个问题是定时同步环路偶尔失锁表现为误码率周期性跳变。最终定位是NCO频率控制字更新时机和插值使能信号没有对齐导致环路误差信号混入了一拍旧数据。修复方法是把所有相关控制信号用同一个打拍对齐模块处理保证所有模块的时序基准一致。第三个问题是上电后JESD204B链路有时正常有时异常。通过抓取SYSREF和时钟沿的相对时序发现问题出在SYSREF的建立时间不足。通过对PCB布线调整和对时序约束的重新梳理问题彻底消失。这也说明很多时候FPGA问题根源在板级而不在逻辑里回看原理图和PCB非常重要。5.3 提升调试效率的几个工具和习惯用ILA集成逻辑分析仪抓关键节点时一定要把有效信号、数据信号、状态机状态一起抓只看数据不看valid一准踩坑。有条件尽量多抓几拍前后文不然复现问题成本很高。仿真用带界面的工具但不要完全依赖它。早起验证快速跑一遍就够了重点放在$display打印关键状态转换和定点误差数据方便批量比对。MATLAB脚本生成测试激励和期望结果Vivado仿真的testbench直接读取这个数据文件能极大提高验证一致性。从浮点模型转定点模型时每一步都记录下缩放因子和数据范围方便后续截位选择。设计文档里这页最容易被跳过但后期排查问题全靠它。6. 写在项目之后的一些体会回头看这个项目最大的感受是基带和中频算法在FPGA上的实现本质上不是算法和工具的矛盾而是性能和资源的艺术。很多算法书上就一页纸的原理落到FPGA上却要面对位宽、时序、接口、环路稳定性一系列实际问题。我个人在实际操作中的体会是方案设计阶段一定要把数据位宽预算、资源估算、时序目标、接口协议这四件事同时想清楚缺一不可。哪怕前期多花几天做仿真验证和方案评审比后边焊板上调逻辑加班熬夜要划算得多。最后再分享一个小技巧定点和浮点模型之间的对比测试建立一套自动化的脚本任何一次代码改动之后都能一键回归保证性能没有回退。这套机制帮我拦截过好几次看似无关但其实会影响整条链路指标的微小改动。基带和中频的FPGA实现是一个合格通信工程师绕不开的主战场。希望这篇记录能帮你少走一些弯路尤其是那些我踩过的坑要是能让你省下哪怕一个通宵的调试时间这篇文字就算值得了。
觉得有用,分享给同行:

为您的企业打造数字门面

稳重轻奢商务风格,端正雅致视觉,长效耐看不易过时。

立即咨询 →