ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

FPGA实现基带与中频信号处理:从数字下变频到同步环路

FPGA实现基带与中频信号处理:从数字下变频到同步环路 1. 基带和中频的分工很多人一开始就没搞清带过不少刚入行的师弟师妹发现一个很普遍的误解觉得基带就是低速数字信号中频就是模拟电路里的事。这个认知放到十年前的模拟中频方案里还勉强说得过去但在现在的FPGA实现体系里早就不是这么回事了。基带和中频处理很大一部分已经搬到了同一个芯片里用同一个时钟域甚至跨时钟域的流水线串在一起。搞清楚这两个概念在数字域里的边界是做任何通信物理层开发的第一步。1.1 一条完整的数字接收链路里FPGA到底站在哪先把整条链路拉通了看。一个典型的通信接收机从天线下来经过低噪声放大、模拟下混频把射频信号搬到中频然后经过抗混叠滤波和AGC自动增益控制之后送到ADC采样。到这里为止是模拟前端的事。而从ADC出来那一刻开始信号就变成了数字序列后面的事情基本都可以在FPGA里完成。FPGA要干的活包括数字下变频DDC把中频信号搬到基带、抽取滤波降低数据率、自动增益控制后面跟着的信号均衡、载波同步、符号同步、解映射、信道解码甚至连帧同步和协议状态机都可以一起做掉。当中频数据率太高、后端CPU来不及处理的时候FPGA还要承担数据降速和缓存的工作让DSP或者ARM处理器拿到的已经是低速的基带数据流而不是原始的ADC采样值。我经常用一句话跟新人解释FPGA在通信里的角色它是模拟前端和通用处理器之间的加工厂。模拟前端负责把物理世界的连续波变成离散数字通用处理器负责跑复杂的协议栈和应用层而FPGA负责中间那段实时性要求极高、数据吞吐量极大、算法结构又相对固定的信号处理。基带和中频的算法算法之所以适合放在FPGA里核心原因有三个一是高吞吐ADC采样率动辄几十兆到几百兆每个采样点都要在纳秒级别内处理完二是低延迟很多算法是流式的一个采样点进来几个时钟周期后就要出结果CPU那种取指令、解码、执行的模式根本跟不上三是并行性信道均衡、滤波、FFT这些运算天然就是大量乘加并行执行的FPGA的硬件逻辑正好一一对应。1.2 为什么选在中频数字化而不是直接射频采样现在高速ADC越来越猛也有人提射频直采的方案就是ADC直接采样射频信号省掉模拟中频那一堆混频器和滤波器。但落到工程实现上中频数字化仍然是当前的主流选择原因很现实。第一是ADC的采样率和有效位数之间的平衡。射频直采意味着ADC要工作在GHz级别而在这个采样率下能做到的ENOB有效位数往往只有10位左右动态范围受限。中频数字化则可以把采样率控制在几十到几百兆ADC可以做到14位甚至16位动态范围大了很多。对于宽带信号接收多几位的动态范围在弱信号检测和强干扰抑制上的差距是压倒性的。第二是模拟前端的实现难度。做射频直采模拟链路上需要极陡峭的抗混叠滤波器来抑制带外干扰这个滤波器在GHz频率上用模拟器件做出来难如登天。而中频数字化方案里抗混叠滤波器只要做到几十到几百兆的带宽范围用常见的SAW滤波器甚至LC滤波器就能搞定成本低、一致性好、调试也方便。第三是FPGA处理速度的现实约束。射频直采的数据进来之后DDC和滤波依然要FPGA来做这时FPGA内部逻辑要在GHz时钟下运转目前多数FPGA也就是在高端器件上能局部做到功耗和成本都相当可观。中频数字化之后FPGA运行时钟通常在100MHz到300MHz之间设计裕量充裕时序收敛的难度小一个量级开发风险和迭代速度都友好得多。所以在我实际做过的项目里除非是面向极宽带信号且对体积有苛刻要求的场景一般都会选择中频数字化方案。这个思路跟选FPGA型号其实是同一个逻辑用合理的技术水平解决当前的问题而不是为了指标上的好看把一个本来可以稳健落地的设计逼到极限状态。2. 实现基带与中频算法的四个核心模块聊完系统架构进入正题。FPGA里的通信信号处理算法说多不多说少不少但高频复用的其实是那么几块数字下变频、抽取滤波、同步环路、检波与解调。这四块就像搭积木不同通信制式往上叠加不同的组合就能拼出各种接收机。2.1 数字下变频NCO、混频器与滤波抽取的配合数字下变频是把ADC采样的数字中频信号搬到基带走的第一步也是整个FPGA信号处理链路里的第一个重活。它要做的事在数学上极其简单把信号乘以一个本地产生的正弦波把频谱从载频位置搬到零频附近然后低通滤波去掉高频分量。关键在于本地正弦波怎么产生。在FPGA里最常用的做法是DDS直接数字频率合成加查找表。DDS的核心是一个相位累加器每个时钟沿给累加器加一个频率控制字累加器输出的高位作为地址去查一张正弦ROM表查出来的值就是本地本振的实时样点。频率控制字每增加1输出频率就增加 fclk / 2^NN是累加器位宽这样就能实现很高的频率分辨率。// NCO相位累加器示例32位累加器输出正余弦查找表地址 reg [31:0] phase_acc; always (posedge clk or negedge rst_n) begin if (!rst_n) phase_acc 32b0; else phase_acc phase_acc freq_word; end wire [11:0] sin_addr phase_acc[31:20]; // 取高12位作为查表地址这段代码是最基础的NCO实现。实际工程里要注意几个细节相位累加器位宽决定了频率分辨率但查表只用了高12位或者10位低位相当于相位抖动这会在频谱上产生一定的杂散噪声叫相位截断杂散。想要压低这个噪声一是增加查表位宽二是加抖动dithering在截断之前给累加器加一点伪随机扰动把杂散打成底噪。这个技巧在通信系统设计里非常常用信号频谱干净不干净很多时候就差这么一点。混频器在FPGA里就是乘法器。IQ两条支路分别用余弦和正弦本振去乘输入信号得到I路和Q路。注意这里有一个数据位宽扩展的问题两个12位信号相乘得到24位后续如果直接接滤波器数据位宽会一路膨胀。所以在实际设计里每级运放之后都要做合适的截位或者舍入把位宽控制在一个合理范围。截位方式也很讲究简单截断会有直流偏置最好用舍入加抖动的方式或者用无偏舍入否则后面信号频谱底部会翘起来一块。2.2 抽取滤波器组CIC加等纹波FIR的常规打法DDC之后信号带宽还是宽采样率还是高直接拿去做基带处理浪费资源。这时候就需要抽取把采样率降下来。但抽取有个大坑根据奈奎斯特定理抽取因子为M就必须在做抽取之前先把信号带宽限制在fs/2M以内否则高频分量会折叠到低频这就是混叠。所以抽取必须配滤波器。滤波器怎么设计是新手最容易踩坑的地方。如果上来就用FIR滤波器做全部滤波和抽取比如从100MHz采样的中频信号抽取10倍到10MHz一个FIR滤波器要做10倍抽取前后的抗混叠滤波阶数可能得几百上千阶。每个输出采样点要计算这么多滤波系数FPGA的DSP48资源很快被打满。常规做法是级联结构CIC级联积分梳状滤波器打头阵做整数倍抽取然后是半带滤波器再降一级最后用等纹波FIR做精细的频响整形。CIC滤波器结构极其简洁不用乘法器只用加法器对于大抽取比、高带宽信号的场景非常高效。代价是幅频响应在通带内有倾斜需要用后面的FIR补偿。级联设计的时候有个经验我提了很多次CIC的抽取因子不要一次拉满除非你能容忍通带倾斜加阻带泄漏。通常CIC抽2到4倍半带抽2倍剩下的交给FIR这样整体滤波器组的通带平坦度和资源占用能达到一个很好的平衡。每一级滤波器工作在不同采样率下对应的输出位宽也不同要分别估算动态范围别指望一套位宽走到黑。2.3 载波同步与符号同步从锁相环到Gardner算法信号搬回基带、抽到合理采样率之后还有两个必须解决的问题发射机的载波频率和相位跟接收机本地振荡器不完全一致符号时钟跟采样时钟也不是天然同步的。这就是载波同步和符号同步。载波同步在FPGA里最常见的结构是Costas环专门用于抑制载波的BPSK/QPSK等调制方式。它的做法是I路乘以Q路得到一个跟相位误差成正比的误差信号这个误差信号通过环路滤波器通常是一个比例加积分结构去调整NCO的频率控制字形成一个负反馈。环路滤波器里的比例系数控制响应速度积分系数控制稳态误差工程里就是反复调这两个系数让环路的快慢和稳定性达到平衡。符号同步常用的定时误差检测算法是Gardner算法它有很好的特性每符号只需要两个采样点而且对载波相位不敏感可以先做符号同步再做载波同步。Gardner算法的误差公式是 (y[n] - y[n-1]) * y[n-1/2]其中y[n]是当前符号采样y[n-1]是上一个符号采样y[n-1/2]是它们中间的那个采样。这个误差值经过环路滤波之后用来控制插值器从异步的采样点流中恢复出最佳符号时刻的采样值。这里我想多提一句新人在写同步环路的时候往往纠结于算法本身而忽略了环路里的数据通路延迟。一个环路从误差计算到NCO更新中间过了多少拍会对环路的稳定性产生直接影响。如果不把环路延迟纳入设计考虑环路增益一调大就振荡调小了又锁得慢有个时候直接锁不住。工程上的做法是先把仿真模型搭出来把延迟参数替身进去做闭环仿真稳定之后再转Verilog可以省掉大量的上板调参时间。2.4 中频检波不同调制方式下的算法选择中频检波这个词听着有点老派但它指代的内容在现在的FPGA实现里依然很常见就是从调幅或者带通信号中提取包络信息。相关的热搜词里也提到“中频检波有几种方法(如同步检波)”这里把几种方法放在通信系统和FPGA实现背景下统一梳理一遍。检波的本质是提取信号的幅度或相位信息。最古老的做法是包络检波也就是非相干解调适合调幅信号。在数字域里包络检波就是求信号的瞬时幅度可以用CORDIC算法计算I、Q两路的平方根也可以用绝对值加低通的近似做法。工程上常用平方律检波I^2加Q^2之后低通滤波既避免了开方运算的复杂度又天然实现了包络的提取。同步检波则是相干解调需要本地产生一个与载波同频同相的参考信号混频后滤除高频分量得到基带的原始调制信号。同步检波在FPGA里实现时最关键的就是本地载波的同步通常也是用一个环路来实现这里是Costas环的又一个应用场景。同步检波的优点是抗噪声性能比非相干检波好在低信噪比条件下优势明显适合需要更高灵敏度的接收机场景。两种方法在FPGA实现的资源消耗上也有很大不同。包络检波占用的乘法器少逻辑简单适合对性能要求不高的AM解调或者AGC检测同步检波要做乘法、环路滤波、NCO占用的DSP和逻辑资源明显多而且环路的收敛时间和捕获范围都是设计重点。实际选型没有绝对的好坏完全看调制方式和信噪比要求。我自己的习惯是能用非相干解决的绝不上相干解调因为同步环路是所有算法里最难调试的部分之一多一个环路就多一个深坑。3. 从Matlab模型到Verilog代码中间差着定点化和时序通信算法工程师的日常工作通常先在Matlab或者Python里用浮点建一个模型把功能验证通了再转成FPGA实现。这一步转换过程往往是项目进度真正的瓶颈所在。浮点模型跑得通不代表硬件就能跑得通差异主要在两个地方定点化和时序收敛。3.1 定点数格式Q格式的选择和量化误差控制浮点转定点是一个取舍过程。FPGA里做浮点运算是可能的Vivado和Quartus都有浮点IP核但代价是资源消耗显著增加功耗上升时序困难。基带和中频处理链路里的绝大部分算法其实都可以用定点数在16位或者更少的位宽下实现性能损失控制在零点几个dB以内。这就是定点化的价值所在。定点数的基本表示法是Q格式Qm.n表示m位整数位、n位小数位总位宽W等于m加n加符号位。选格式时最怕的是溢出其次是噪声太大。溢出会导致信号瞬间爆炸产生完全不合理的输出噪声太大则会让信噪比下降通信性能不达标。我的经验是先把每一级信号的动态范围预估一遍。比如ADC输出是14位幅度范围在正负8192之间混频之后乘上一个接近1的本振动态范围不变但后面滤波器可能有增益比如CIC滤波器的增益等于抽取因子的N次方N是CIC级数这个值可能高达十几甚至几十直接在无符号整形运算里毫无问题但在定点小数里就要特别小心。每一级滤波器之后要重新评估位宽不要怕位宽多DSP48的输入位宽通常支持25乘18多出来位宽不是大问题真正的问题在截位。截位策略上推荐使用舍入而不是截断。简单截断相当于向下取整会产生最大一个LSB的直流偏置这个偏置在窄带通信系统里是会降低接收灵敏度的。舍入方式在FPGA里实现也不复杂加半个LSB再截断就行。如果要更好的性能可以用无偏舍入。这些细节看起来不起眼但对信号质量的累积影响非常明显尤其是多级处理之后。3.2 DSP48与查找表资源分配里的取舍逻辑FPGA里做乘法有两种主流方式一种是用芯片里集成的DSP48硬核一个DSP48可以在一个时钟周期内完成18位乘18位再加一个累加另一种是用查找表LUT搭建乘法器消耗大量逻辑资源速度还慢。只要目标器件不是太低级滤波器、混频器、环路里的乘法都应该用DSP48实现。但是DSP48的资源是有限的一个中等规模的Xilinx Kintex-7器件DSP48的数量大概在几百到一千个左右。一个复数乘法需要4个实数乘法一个1024点FFT的蝶形运算要消耗不少乘法器。所以做资源预算的时候要精确计算每个算法模块占用多少DSP48做到心中有数。一个很常见的优化手段是时分复用。比如抽取滤波器组里的FIR滤波器如果输入数据率是10MHz而FPGA的时钟能跑到200MHz那一个DSP48可以在20个时钟周期内完成20个乘法操作也就是说一个物理乘法器可以虚拟成20个逻辑乘法器。用这种方式一个大阶数的FIR滤波器可能只需要几个DSP48就能支撑很高的数据吞吐率。这个思路在通信信号处理里很通用因为通信信号本身是窄带的数据率往往远低于FPGA的运行时钟这给资源共享留出了巨大空间。3.3 流水线划分与时序收敛把组合逻辑拆开FPGA的时序收敛和算法微架构设计是紧密相关的。一个纯粹的组合逻辑深度如果超过了几十级加法器和乘法器在100MHz以上的时钟里就很难收敛。解决方式就是插流水线寄存器把一个周期内超长的组合逻辑路径拆成两拍甚至数拍来完成。我的习惯是先把算法流程画成数据流图标出每一条关键路径的延迟然后决定在哪里插入流水线寄存器。插入流水线会引入延迟这对纯粹的流式处理没有影响但会影响到反馈环路的结构。比如数字锁相环里的环路滤波器输出要反馈到NCO的频率控制字如果这里多加了流水线延迟会改变环路的相位裕度必须把这一拍延迟在环路增益里提前补偿掉。这里也要注意跨时钟域的设计。中频处理工作在高速时钟域基带处理可能在降低了采样率之后切换到低速时钟域两个时钟域之间的数据传递要用异步FIFO或者握手协议不能直接拿一根信号线跨过去。否则上板之后数据偶尔错位几天查不出原因最后发现是跨时钟域没处理好那真是最冤的一种bug。4. 仿真全通过、上板就翻车——我的调试经验记录如果你的项目组已经有完善的仿真验证流程那恭喜你能省掉很多精力。但即使仿真和验证做得再完备FPGA上板调试依然是一个绕不开的环节。我写过的代码里没有一个版本是一上板跑一次就全对的。这里记录几个典型的坑和排查思路希望能帮你缩短调试时间。4.1 没有加约束就上板的后果第一次做中频处理板卡的时候我犯过一个极其经典的错误写完了代码Vivado综合、实现都过了看到时序报告是绿的就高高兴兴烧到板子上。结果出来的信号频谱完全不对带上一个诡异的周期毛刺怎么调参数都没用。折腾了两天最后想起来打开约束文件看一眼发现里面没有任何时钟约束。没有时钟约束意味着Vivado把时钟的频率当作默认值来处理工具并不会知道你的主时钟其实跑在125MHz。这样综合器和布局布线器就没有一个明确的目标去优化关键路径时钟树和逻辑布局都是按照默认策略生成的结果当然是时序不可控。加上了正确的时钟约束之后重新实现一遍问题立刻消除了。从那以后我养成一个习惯写代码之前先写约束哪怕只是最基本的一条create_clock也比事后补要好。布局布线之后的时序报告必须在时序约束完整的前提下看才有意义没有约束的绿色报告都是假绿。4.2 一处异步复位问题导致的中频数据乱跳另一个让我印象深刻的问题是复位电路引起的。当时的算法模块做得很完整可是在复位信号上偷了个懒直接用了一个异步复位。理论上只要复位时间足够宽异步复位也是可用的问题出在复位释放的那一刻当复位信号撤掉时如果它相对于时钟的撤销时刻刚好落在触发器建立保持时间的窗口里不同的寄存器会看到不同的复位状态一部分寄存器先结束复位开始工作另一部分还在复位状态系统状态就乱了。这个问题的表现非常隐蔽上电之后偶尔正常偶尔不正常有时候跑了几十分钟才出一次错误而且错误出现没有规律用示波器抓也抓不住。最后是在代码里把所有异步复位全部改成同步释放的异步复位——也就是复位信号先经过两级触发器同步再统一释放。这个经典结构保证了恢复时间从此再没出现过复位导致的随机故障。// 异步复位、同步释放的典型写法 reg [1:0] rst_sync; always (posedge clk or negedge rst_n) begin if (!rst_n) rst_sync 2b00; else rst_sync {rst_sync[0], 1b1}; end wire rst_sys_n rst_sync[1];这件事给我的教训是复位电路是整个芯片里影响面最大的信号之一却在设计时最容易被忽视。越是细节的地方越要按正规做法来。上板调试的时候如果出现这种间歇性故障优先检查复位和时钟相关的问题比去翻算法要高效得多。4.3 用ILA在线调试定位毛刺的思路碰到信号有问题第一反应当然是想办法观察到内部节点。ILA集成逻辑分析仪是Xilinx FPGA里最常用的调试手段Quartus里对应的是SignalTap。把需要观察的信号引到ILA核里设置好触发条件就能在板子上抓取真实的波形。ILA调试里我踩得最多的坑是触发条件设得太死或者太松。设得太死一个时钟都没抓到设得太松抓了几百万采样点有效信号早淹没在波形里。我的办法是分两步走第一步先做自由运行抓取不设触发条件抓一段数据观察标准波形是什么样的顺便确认信号位宽和数据格式有没有接错第二步再设触发条件比如抓同步信号下降沿、或者数据特征值精确捕获问题窗口。还有一个经验抓信号时不要把ILA的信号太多一只ILA核抓二三十个信号已经很多了多了会把内部的布线资源挤占导致本来就紧的时序更难收敛新闻的故障反而更多。宁可多放几个ILA核分布在不同的处理阶段分段观察这是工程上更实用的做法。5. 给刚入门FPGA通信算法工程师的几个建议做了这么多年FPGA通信算法开发带过团队也面试过不少人看到太多新人在同样的地方打转。最后聊聊几个我觉得对新人最有价值的建议不涉及具体技术但对职业发展影响很大。5.1 先把数字信号处理基础补齐我见过有新人写了一个星期的Verilog代码风格已经很老练了但问他混频之后为什么要滤波答不上来。这其实是方向性的大问题。FPGA开发工具链和语言本身是可以通过短时间训练掌握的但数字信号处理和通信原理属于需要长期积累的基础知识决定一个人能走多远。具体来说至少要掌握离散傅里叶变换和FFT的基本性质、FIR和IIR滤波器的设计和实现、多速率信号处理和抽取插值的混叠关系、常见调制方式的星座图和误码率性能、锁相环的基本原理和环路稳定性判定。这些知识不需要达到理论专业的深度但必须能用来指导实践。比如你设计的滤波器通带纹波大了你很知道这会给误码率带来多少恶化需要怎么调整指标。这些能力不是单纯写代码能培养出来的。5.2 从最小系统开始搭平台无论学习还是做项目我强烈建议从最小系统开始。所谓最小系统就是一块FPGA开发板、一个ADC板卡、一根天线或者一台信号发生器加一个最简单的算法比如把ADC的数据用ILA抓到电脑上看波形。先跑通数据通路再逐步往链路上加DDC、加滤波器、加同步环路。每加一个模块都在板子上观察一次数据变化确认无误再加下一个。很多新人是相反的做法一口气写好整个接收机然后上板调试结果数据出不来一大堆错误同时出现完全不知道从哪里开始排查。这种调试方式的困难程度比模块化逐步验证要高出好几倍。而且从最小系统开始还有个好处你对硬件平台的熟悉程度会高很多什么信号在哪个管脚、ADC的配置寄存器初始值是什么、时钟芯片怎么配置这些细节到了后面排查问题时全是关键信息。5.3 写代码之前先写Matlab参考模型我在团队里立了一条规矩Verilog代码动工之前必须先把定点数的Matlab模型跑通并且留下定点化前后的性能对比数据。这条规矩一开始被认为多此一举后来大家都尝到了甜头。原因很简单在FPGA里排查一个算法bug的时间成本通常是Matlab里排查同样bug的十倍以上。算法逻辑有没有问题、滤波器系数选得合不合理、环路增益该怎么调这些在Matlab里一天能迭代很多次在FPGA里一次上板调试可能就是一整天。定点数模型还可以直接作为Verilog仿真的testbench参考。Vivado或者ModelSim里做RTL仿真时把同样的输入激励给Matlab模型和Verilog模型对比两者输出差多少就能快速判断RTL的实现和预期算法模型是否一致。这个流程看起来多一点前期工作实际上能把整个开发周期缩短三分之一以上越复杂的系统越明显。这几年接触的项目越多越发觉得基带与中频的FPGA实现是一个特别能体现综合功底的领域。它需要通信理论、数字信号处理、硬件结构设计、工具链熟练度、调试经验等多方面能力叠加在一起也正因为这样这个方向的工作才始终充满挑战和成就感。希望这篇内容能帮你少走几步弯路。
返回列表