拓冰建站拓冰建站
首页 / 资讯中心 / 正文

FPGA基带与中频信号处理算法实现全解析:从DDC到同步均衡

从“基带与中频的FPGA算法实现”这个题目聊起这个方向一直是我觉得FPGA应用里最有嚼头的领域之一。现在很多通信设备、仪器仪表、雷达和软件无线电项目核心链路基本都绕着中频采样、数字下变频、基带解调这几个环节转。做FPGA的同学一旦把这块吃透不仅在求职市场上很吃香而且做出来的东西在延迟、确定性、带宽上比传统处理器方案有碾压性优势。这篇文章我把从工具链选型、滤波器设计到DDC/DUC链路搭建、基带同步均衡的完整思路写出来很多点都是项目里踩坑换来的经验希望能帮正在搞或者准备搞这方向的人少走点弯路。收到网友留言大家问得比较多的几个问题比如基带verilog怎么上手、中频检波有几种方法、卡尔曼滤波能不能在FPGA里跑、STM32H743和FPGA实现FMC通信怎么做我会结合具体算法实现场景逐一展开。还有朋友问到FPGA和DSP在算法实现上的分工差异这个我觉得也很值得聊干脆放在文章里一起说清楚。1. 基带与中频的算法全貌你到底在做一件什么事在开始写代码之前先把概念砸实。基带信号是原始信息所在的频段中频信号则是在射频和基带之间搭的一座桥。为什么要架这座桥因为直接在高频上做ADC采样对器件难度和成本都是灾难。射频信号进来之后先通过模拟混频搬到一个合适的中频频率再用中等采样率的ADC数字化之后进入FPGA做数字信号处理。这么一搞FPGA在中间承担了什么角色它实际上是三件事第一把中频信号搬回基带这叫数字下变频第二把基带信号做各种编解码、滤波、同步和判决这叫基带处理第三如果系统要发送信号还负责把基带信号搬上中频这叫数字上变频。整个链路上的滤波器、混频器、锁相环、均衡器、定时恢复模块全是算法而FPGA就是把这些算法跑成硬电路的载体。为什么不是用DSP或者ARM来做答案很简单并行性和时序确定性。一个FPGA里可以同时跑几十个乘累加器每时钟周期都能出结果而处理器再怎么快也是串行的。通信系统对实时性要求又非常苛刻误码率指标和时延指标摆在那儿FPGA靠着可定制的数据通路和流水线结构能精确到纳秒级地完成任务。举个例子一个128阶FIR滤波器在500MHz时钟下面FPGA用几个DSP Slice就能实现实时处理换成CPU跑就得掐着时间算能不能赶上实时要求。从开发角度讲这个领域还算是一个相对综合的工程既得懂信号处理理论又得懂FPGA架构特性还得会Verilog或VHDL。很多从纯软件转过来的人最大的坎往往不是语法而是思维模式的转变——软件是“一条线执行所有分支”FPGA是“所有分支同时都在跑”这种并行思维转不过来后面写代码就会别扭。这个领域到底适合谁学三种人最适合。一是通信专业的学生想从理论走向工程实践二是已经会一点FPGA、想在算法方向纵深发展的工程师三是做软件无线电或仪器仪表项目的系统工程师需要了解底层算法如何落地。这篇文章的目的就是把这三类人的断层缝合起来让你看完之后能搭起自己的信号处理链路。2. 工具链与核心DSP模块先把地基打好很多人一上来就急着跑仿真我建议先花半天时间把环境搭好、把IP核吃透。FPGA开发的工具链Xilinx这边是Vivado VitisIntel那边是Quartus Platform Designer。如果你做的是纯逻辑算法Vivado足够用如果涉及到和ARM核联动比如Zynq平台那Vitis也会进入工作流程。工具链的版本选择有个小技巧别追最新求稳定。我见过太多人用最新版Vivado之后IP核版本不兼容、仿真库编译报错折腾两天发现是工具版本太新。选定一个成熟版本至少用一年以上这是很多FPGA老工程师心照不宣的原则。2.1 乘法器和DSP SliceFPGA算力的真正来源FPGA里做数字信号处理乘累加是灵魂操作。以Xilinx 7系列为例每个DSP48E1 Slice包含一个25x18的乘法器、一个加法器和一个累加器能够在一个时钟周期内完成乘加运算。设计时有个原则能用DSP Slice就直接用别用LUT拼乘法器那会占用大量逻辑资源还会导致时序收敛困难。在做算法映射时要格外关注“位宽和量化”。FPGA里的数不是无限精度的每个模块输入输出都要定义好位宽。比如你设计一个18位宽的输入信号经过混频乘法之后位宽会扩展如果直接截断信噪比会下降如果保留太多冗余位宽又会浪费资源。一般经验是每经过一次乘法位宽扩展为两者位宽之和每经过一次加法位宽加1。实际使用时通过验证确定截位位置保证足够的有效位。2.2 FIR滤波器实现从系数设计到硬件架构FIR滤波器是基带和中频处理里最常用的模块没有之一。它干的事情本质上是加权求和输出等于过去N个输入样本和N个系数的对应乘积之和。FIR的好处是线性相位、稳定、易于实现。FPGA实现FIR有两种路径一是直接用Vivado的FIR Compiler IP核填好系数文件就能生成二是手写Verilog实现串行或并行架构。关于FIR系数的设计我推荐用MATLAB的Filter Designer工具或者Python的scipy.signal库。设计时重点考虑三个指标截止频率、通带纹波、阻带抑制。这几个指标决定了滤波器阶数通常阶数越高滤波效果越好但资源消耗和延迟也随之增大。比如要做一个通带1MHz、阻带2MHz、阻带抑制60dB的低通滤波器用等纹波法设计阶数大概在60到80左右。这个阶数在FPGA里很轻松。硬件实现时要考虑数据的吞吐率。并行架构下每个时钟周期输出一个结果需要N个乘法器并行工作半并行架构下用4到8个乘法器分时复用牺牲吞吐率换取资源节省。大多数场景建议直接用IP核因为它会根据目标时钟频率自动做乘法器复用、流水线插入和时序优化。自己写FIR不是不行但要处理延迟平衡、多通道复用等问题工作量不小。2.3 NCO与混频器频率搬移的核心手段数字下变频的第一级就是一个混频器和一个数控振荡器NCO。NCO的功能是产生正弦和余弦两路本振信号用它和输入中频信号相乘实现频谱搬移。NCO的实现通常基于相位累加器加查找表或者用CORDIC算法实时计算正弦余弦值。这里有个关键参数叫频率控制字FCW它决定了NCO的输出频率。计算公式是FCW (目标频率 × 2^N) / 采样率其中N是相位累加器的位宽一般取32位。举个例子采样率100MHz目标本振频率20MHzN32位那么FCW (20e6 × 2^32) / 100e6算出来约等于858993459。用这个值做相位累加步进每时钟周期累加一次查表输出就是20MHz的正余弦信号。这个计算过程虽然简单但几乎每个项目都要做建议形成自己的脚本或者计算表格。混频之后信号频谱被搬移到了基带附近但同时也保留了一个在二倍中频频率上的镜像分量。这个分量需要低通滤波滤掉否则会影响后续处理。实际实现中混频和FIR滤波常常串联在一起先乘后滤干净利落。3. 从天线到比特完整的中频接收链路是怎么搭起来的有了前面这些基础模块就可以搭建一条完整的中频接收链路了。我以一个典型场景为例中频信号中心频率70MHz带宽2MHzADC采样率100MHz目标是解调出基带的QPSK符号。这个配置在卫星通信和微波通信里都很常见拿来做实验也比较容易复现。3.1 数字下变频DDC链路的关键参数设计整个链路的第一级是ADC采集采样率100MHz所以奈奎斯特带宽就是50MHz。70MHz的中频信号落在第二奈奎斯特区这没问题ADC能正常采样只要前端加好抗混叠滤波器就行。采样之后数据进入FPGA第一件事就是做数字下变频。NCO设成20MHz因为70 - 50 20MHz吗不对仔细想ADC采样后70MHz的信号在数字域会混叠。常规做法是选NCO频率等于中频信号在数字域的映射频率。在这个场景下70MHz的数字频率折算之后实际上是70 - 50 20MHz。混频之后信号频谱搬到了0Hz附近也就是基带。但此时基带信号还是双路I路和Q路分别对应余弦和正弦相乘的输出。然后接一个低通滤波器滤除二倍频分量和高频噪声。滤波器参数上我比较推荐通带设为2.5MHz略大于信号带宽阻带截止到5MHz这样既能让有用信号无损通过又能把带外噪声滤干净。滤波器阶数根据阻带抑制要求来定一般60dB以上大约64到96阶。这个滤波器的数据率还是100MHz但信号的符号率只有2Msps所以在滤波之后还需要做抽取——每50个点取一个或者用CIC滤波器配合抽取。抽取倍数这里选50把数据率从100MHz降到2MHz正好是符号率的两倍为后级定时恢复提供合适的过采样率。3.2 AGC自动增益控制与检波方法信号到了基带幅度可能忽大忽小动态范围可能会超过几十dB。AGC就是解决这个问题的模块。简单来说AGC环路先做幅度估计再用估计值和目标值的误差去调整增益系数。幅度估计常用两种方法I^2Q^2的平方和再开方或者用绝对值近似。开方在FPGA里可以用CORDIC但更省资源的方法是直接比较I和Q的绝对值取大值加上小值的1/4倍来近似精度够用且省逻辑。中频检波的方法也是大家问得比较多的地方我单独说下。同步检波需要恢复出一个和载波同频同相的本振信号做相乘灵敏度高、输出信噪比好但实现复杂。非同步检波比如包络检波直接取信号的幅度包络然后过低通滤波器适合调幅信号电路简单但灵敏度较低。剩下的还有乘积检波、平均检波等变体选哪种完全看调制方式。在FPGA里做数字AGC我一般选择同步检波的思路因为本振信号NCO本来就有顺路完成相干解调一举两得。AGC环路要注意环路带宽的问题。环路带宽太宽增益会跟着信号包络快速波动导致调制信号失真带宽太窄又跟不上信号衰落的变化。经验值是带宽设为符号率的1/100到1/10之间具体看信号类型。比如2Msps信号环路带宽设在20kHz到200kHz之间比较稳妥。工程上常用一阶环路参数好算稳定性也容易控制。3.3 载波同步和定时恢复的实现基带信号拿下来不代表就能直接判决了。因为发射机和接收机之间的晶振不完全一致载波频率有偏差符号时钟也有偏差。载波同步就是估计并消除这个频差定时恢复则是找到每个符号的最佳采样点。载波同步常用的算法是Costas环它利用I/Q两路乘积的误差信号来调整NCO的频率。环路的鉴相器输出经过环路滤波器后叠加到NCO的频率控制字上形成闭环。Costas环在FPGA里的实现核心就是一个PI控制器。PI系数怎么定这涉及到环路带宽和阻尼系数。工程上可以先在MATLAB里用连续域设计出系数再离散化到FPGA的定点运算。要注意的是FPGA里的PI控制器要防止积分饱和不然信号重新变大之后环路半天拉不回来。定时恢复的经典方案是Gardner算法它不需要知道载波相位只需要两个采样点一个在符号中间一个在符号边界。通过两者差值计算定时误差再用反馈环路控制NCO或插值滤波器的分数间隔。Gardner算法的好处是每个符号只需要2个样本点数和前面抽取到2倍过采样正好能接上。插值滤波器一般用4阶的Farrow结构精度足够且便于FPGA实现。4. 发送链路与高级算法让数据真正发得出去、抗得住干扰接收链路处理了一堆棘手问题发送链路同样不能马虎。它要做的就是把比特流变成符合频谱模板的中频信号在调制阶段就把发射端的困难提前解决掉。4.1 数字上变频DUC与脉冲成形滤波发送链路的第一步是符号映射把比特串映射成星座点。比如QPSK每两个比特映射为一个复数符号映射表根据格雷码排列使相邻符号只有一个比特差异减小误码率。映射完后需要做脉冲成形滤波也就是根升余弦滤波。它的作用一方面是限带使信号频谱满足指标另一方面是降低码间串扰。滚降因子alpha一般取0.2到0.5之间alpha越小带宽效率越高但滤波器阶数和实现难度也越大。成型滤波器输出之后2倍插值把符号率提高到采样率的整数倍再做频谱搬移把基带信号调制到中频上。DUC里的NCO和混频器结构和DDC类似只是方向相反。还要注意的是DUC的输出需要做峰值因子削减CFR吗这取决于系统链路里有没有功放。如果接功放信号峰均比太高会严重影响效率。DPD数字预失真则进一步补偿功放的非线性这是射频工程师常做的事但在做FPGA算法的人这里也经常要接上一手。4.2 卡尔曼滤波在FPGA上的实现技巧卡尔曼滤波这几年在组合导航、目标跟踪、信号估计里用得很多。它在FPGA里的实现难点不在公式本身而在于矩阵求逆和迭代更新。对于一个状态维数不高2到4维的系统直接把卡尔曼增益公式按标量展开一块一块硬算反倒是最高效的做法。具体操作上矩阵求逆可以用高斯消元法或者Cholesky分解但在FPGA里这些都很费资源。我的建议是能用常数替代的增益就别迭代计算。比如系统是线性时不变的过程噪声和测量噪声都是常数那么卡尔曼增益会快速收敛到一个稳态值直接把这个稳态值算好写死在寄存器里在线只做状态更新计算量能下降一个数量级。这种方法在项目里叫稳态卡尔曼滤波精度损失很小但资源占用大幅减少。另外FPGA里做浮点运算成本很高卡尔曼滤波的定点化是整个实现的难点。定点化要做的是先跑一遍浮点模型记录各中间变量的动态范围然后为每个变量选择合适的定点Q格式。这个过程需要迭代几次才能定下来不要指望一蹴而就。4.3 自适应算法和均衡从最小二乘到LMS信道不理想时符号间干扰变得严重均衡器是解决这个问题的常用手段。LMS算法因为结构简单、计算量小在FPGA里用得最广。核心更新公式是抽头系数更新量 步长因子 × 误差信号 × 输入信号。这里面步长因子是关键太大收敛快但稳态误差大太小收敛慢且实时性差。可以考虑用变步长LMS先大步长快速收敛再逐步减小步长降低稳态误差。用FPGA实现时可以把步长因子设成2的负幂次方这样乘法就变成了算术右移资源节省非常明显。均衡器抽头数量的确定也有门道。抽头太少均衡效果有限抽头太多又会放大噪声。通常的工程经验是最小抽头数是信道最长时延的2到3倍。比如信道最大时延是5个符号周期抽头数取12到16个比较合适。均衡器的输入信号速率如果很高抽头太多会导致时序收敛困难这时候可以采用分段均衡结构把长均衡器拆成两段中间插寄存器打一拍时延多了一点但时序好收敛很多。5. 工程实操从算法仿真到板上调试的完整闭环算法写得再漂亮最终要落到板子上跑起来。这个从MATLAB仿真到FPGA实现的过程中间隔的不只是语言而是从浮点到定点、从理想时钟到真实时钟、从连续时间到离散时钟域的全面转变。这一节我把完整流程和常见问题整理出来方便对照实操。5.1 算法仿真到FPGA的移植流程第一步永远是浮点模型验证。在MATLAB或者Python里把算法按浮点精度实现一遍用理想信道构造测试信号验证算法在理想条件下的性能。这个阶段重点确认逻辑和参数比如滤波器通带和阻带设多少、环路系数取多少、均衡器抽头多少合适。第二步是定点模型评估。将所有变量改为定点表示设定好每个节点的位宽和小数位数。这一步特别重要建议用MATLAB的Fixed-Point Designer工具辅助它能自动比较定点和浮点的差异。一般要求定点实现的SNR损失不超过0.5dB或者误码率损失不超过0.2dB超过就得重新调整位宽分配。第三步是Verilog编码。每个模块独立编码先做模块级仿真验证。仿真时要特别关注模块边界的数据有效性标志位也就是valid信号。数据流在FPGA里不是随时都有效很多算法失效都是因为valid信号没有对齐数据这类错误通常肉眼很难看出但一上板就崩溃。第四步是集成测试和上板调试。在板子上跑之前先在Vivado里做行为仿真和时序仿真把可能出现的信号完整性问题和时序问题降到最低。上板调试时我是用ILA集成逻辑分析仪抓内部信号配合上位机脚本分析定位问题会快很多。5.2 常见问题排查与经验速查表实际做项目中遇到的问题很多不是纯算法问题而是工程细节问题。常见现象可能原因排查建议输出信号全是噪声NCO频率控制字算错用ILA抓NCO输出核对频率是不是预期值信号有周期性毛刺时钟域跨越没做同步检查跨时钟域信号是否做了两级同步或异步FIFO处理环路锁不住、输出漂移环路滤波器系数过大/过小把系数按数量级逐次调整确认系统稳定性I/Q两路幅度不匹配混频后滤波器阶数或截位不同检查I/Q通路各处理节点的位宽和截位是否一致时序收敛失败组合逻辑过长、流水线不够在乘法器之间插寄存器调节流水线级数资源占用过高滤波器并联使用过多用半并行滤波器结构或把多个通道分时复用除了表格里的内容还有几个点想特意强调。第一跨时钟域问题在FPGA里一定要当成头等大事。基带数据可能是100MHz时钟域的而某个外部接口是50MHz时钟域的信号直接跨过去就是亚稳态。亚稳态会导致寄存器输出处于不确定状态且这种问题偶发性强很难复现。最简单的解决方法是打两拍做同步深入一点则要用异步FIFO。第二截位处理别怕麻烦。很多人图省事直接截掉低位结果信号噪声抬升了好几个dB。正确的做法是先用仿真观察数据的动态范围然后决定哪里保留、哪里舍弃必要时加一点抖动dither来消除截位产生的杂散。这个方法在测试信号源里尤其常用能有效改善无杂散动态范围指标。第三关于STM32H743和FPGA实现FMC通信这个问题。我的建议是先把FMC总线的信号分类搞清楚数据线、地址线、控制线、时钟线。FPGA作为从设备挂在FMC总线上关键是处理好异步跨时钟域和总线时序。草率点说FPGA侧写一个简单的AXI从接口封装一下然后接一个异步FIFO做数据缓冲基本就能跑起来。如果是大量数据搬运建议在FPGA里做AXI-DMA通道避免CPU一个数据一个数据去读写。FMC通信的关键点在于地址映射和突发传输长度要和STM32那边的配置严格对齐否则数据搬过来搬过去就是乱码。第四Zynq平台动态加载FPGA的问题也问得很多。方案是把比特流文件先存到SD卡或Flash里Linux系统起来之后用FPGA Manager框架加载。好处是可以按需加载不同的逻辑功能但要注意一次只能加载一个比特流加载过程中对外接口会有一段不可用时间。设计时要在硬件上明确划分好可分区的逻辑区分哪些功能常驻、哪些动态加载。Zynq的PCAP接口会负责把比特流从处理器系统侧搬进可编程逻辑侧这条路径上做数据完整性校验尤其重要。6. 日常开发中的优化思路让算法又快又省最后一个部分也是我最想聊的。做FPGA算法不是功能对了就完事资源和性能总是要博弈的。同一个算法不同实现方式之间资源占用能差出5到10倍延迟差出几十个周期。掌握这些优化思路能让你从“会做”变成“做得巧”。6.1 用CORDIC替代查表法做三角函数NCO的正余弦产生查表法最直接但表越大相位分辨率和幅度精度越高资源占用越大。当需要高精度输出且不想消耗太多BRAM的时候CORDIC是更好的选择。CORDIC的迭代公式只用移位和加减法不需要乘法器适合对DSP资源有严格限制的设计。以16位精度输出为例做16次迭代就能收敛每次迭代消耗一个加法器。用流水线结构展开之后它能做到每个时钟周期输出一个结果吞吐率不输查表法。CORDIC的缺点是迭代次数多输出延迟比查表法大。如果设计对延迟敏感可以把查找表和CORDIC混合使用前面12位用查表粗调后面用CORDIC精调这样在精度、资源和延迟间取得平衡。6.2 多通道分时复用一套逻辑干多份活很多应用里比如相控阵、多通道数据采集同一套算法要处理多达16路甚至32路信号。如果每个通道都复制一套处理逻辑资源爆炸式增长。分时复用是一个常用方法把每一路信号按不同时隙送入同一套滤波器或解调器处理完再路由回各自通道。注意分时复用要求算法的处理时延小于通道切换周期不然数据就撞车了。以CIC抽取滤波器为例CIC本身是积分器和梳状器的级联数据是一路一路送进来的。优化方案是把积分器和梳状器分别改造成多通道共享结构内部缓存用BRAM而不是寄存器这样16路CIC滤波器所占用的LUT资源基本等同于单路的只是BRAM消耗多一些。这种优化开销不大效果却极为显著。6.3 在FPGA上高效跑IIR与迭代算法IIR滤波器在FPGA里不如FIR好做因为递归结构存在反馈环无法大规模流水线化。但这不代表不能用。实现IIR的关键是把直接I型转换成直接II型减少延迟元数量同时采用不完全流水线化策略乘积加完先在寄存器锁存再进入反馈环路。对于双二阶节级联的IIR每级可以做到单周期闭环整体延迟通常能控制在两三个周期以内。还有些迭代算法比如粒子群和贪心算法在FPGA里做要特别谨慎。粒子群算法本质上是一个迭代搜索过程涉及随机数产生、适应度评估、速度和位置更新。随机数用LFSR产生就好适应度评估如果简单可以用组合逻辑实时算更新过程是整型运算资源不多。但粒子群算法通常需要几十个粒子迭代几百次这会消耗大量BRAM存储中间状态所以建议先用上位机离线算好部分系数FPGA只负责在线调整和补偿这样可以降低约60%到70%的资源消耗。贪心算法在FPGA里实现就是状态机加排序网络边界条件是动态的状态量可能比较多做好状态编码是主要工作量。6.4 和处理器灵活分工新架构下的演进趋势FPGA虽然强但不是万能药。现在的趋势是异构计算FPGA做大吞吐量的信号处理ARM或DSP做协议栈和复杂决策。在Zynq平台里PL侧完成DDR读写管理、多通道数据调度和预处理算法PS侧通过AXI总线配置寄存器并运行网络协议。基带算法里那些固定流水线、高吞吐量的模块放PL而那些需要根据业务动态调整策略、需要复杂分支判断的模块放PS两边通过DMA搬运数据效率高得多。从纯FPGA到Zynq SoC还有一个好处是调试方便。以前纯FPGA调试调试要抓信号、写逻辑分析仪现在可以在Linux里写驱动、跑应用把FPGA当成一个高性能外设来管理和监控。好比以前你直接拿螺丝刀去修发动机现在有了仪表盘你能看到转速和油温再决策虽然底层还是那台机器但管理方式完全不同了。这也是为什么现在Zynq平台在基带处理项目里越来越普及的原因。7. 写在最后的一点个人建议从我个人的实践经验来看基带与中频的FPGA算法实现这条路入门门槛确实高但一旦跨过去会有很强的复利效应。你每吃透一个模块后续做新项目时复用率都很高因为通信系统万变不离其宗NCO、滤波器、混频器、同步环路这些核心模块永远都在。如果你现在正要开始我建议不要贪多嚼不烂先搭一个最简的DDC链路用信号发生器给一个正弦波在Vivado的仿真里看到I/Q输出是直流偏置再用MATLAB对比结果这就是一次完整的闭环体验。在这个基础上再逐步加上成形滤波、调制映射、定时同步、均衡一路走下去你会发现原来那些看着像天书的论文实际上是可拆解、可实现的工程问题。还有个小技巧分享给大家。调试基带FPGA项目时我习惯把中间每一个处理节点的数据都抓下来存到DDR里之后统一导出到MATLAB画图对比。这种“全链路实测对比”法能帮你迅速定位到哪一个模块性能没达标比拿着一根ILA探头逐级排查痛快得多。希望这篇文章能在你搭链路、调算法的过程中带来一点帮助。
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门