FPGA双通道DDC与相位差估计:从参数设计到调板实战 做雷达测向、干涉仪测向或者无源定位的朋友对“双通道中频信号数字下变频及相位差估计”这套流程应该不陌生。把两天线收到的中频信号分别采样、数字下变频到基带再通过FPGA实时算出两路信号的相位差这个相位差就是测向算法里最核心的观测量。这篇文章把我自己做过的一个FPGA工程从思路到落地的全过程整理出来包括参数怎么定、RTL怎么搭、相位差算法怎么实现以及调板阶段那些仿真完全看不出来的坑。适合正在做FPGA信号处理、想搞测向系统或者对数字下变频感兴趣的朋友尤其是那种“原理都懂一上手就各种问题”的状态这篇应该能帮上忙。1. 为什么双通道DDC要跟相位差估计绑在一起一个被低估的硬骨头1.1 相位差测量系统为什么必须走DDC这条路干涉仪测向的基本原理不复杂两天线收到同一个辐射源信号因为路径差导致相位差测得相位差就能反推到达角。但射频前端输出的中频信号通常是几十到上百兆赫兹的载波你不可能直接在70MHz这个频率上去做精确的相位解算。原因有两个一是数据率太高直接对这个频率的信号做FFT或者相关运算FPGA资源会被大量吃掉二是信号带宽外全是噪声和干扰不滤掉的话相位测量结果会像喝醉了一样乱跳。所以业内通用的做法是先做数字下变频DDC把中频信号从载波频率搬到零中频用低通滤波器把带外噪声滤干净再做抽取降低数据率。这时候信号变成了I/Q两路基带数据载波信息被剥离了剩下的是幅度包络和相位信息。相位差估计就在这个窄带基带信号上做数据率低、信噪比高、处理也从容。我选数字下变频而不是模拟下变频核心原因是通道一致性。模拟下变频方案里两个通道如果各用一个混频器和一个本振本振的相位噪声和频率偏差很难做到完全一致通道间会引入不确定的相位漂移。而DDC在数字域完成混频两个通道共享同一个NCO配置、同一个主时钟I/Q正交性只取决于定点运算精度通道一致性完全是可控的。这一点在相位差测量里是生死攸关的。1.2 为什么这个活只能FPGA干有人可能会说相位差估计用DSP或者ARM不一样能做吗我之前也认真对比过结论是单通道慢速测量DSP完全没问题但双通道DDC加实时相位差解算这个组合FPGA的优势是碾压性的。DDC链路里的混频、CIC抽取、FIR滤波全是高数据率的重复性乘加运算。两路信号并行处理每一路都有I/Q两个支路FPGA可以用流水线结构让每个时钟周期都吐出一个结果。DSP是串行取指执行100Msps采样率下要实时完成这些运算主频要求会非常夸张。更重要的是时钟同源问题。双通道相位差测量要求两路信号的处理时钟必须是同一个源头。FPGA里两个通道的DDC模块挂在同一个全局时钟树上NCO的相位累加器在每个时钟沿同步更新天然保证了两通道的处理节拍完全一致。多核DSP虽然也能做并行处理但片内外设的时钟同步机制要复杂得多稍有不慎就引入通道间延迟差。提示如果你在方案阶段就被要求“用DSP实现”建议先问清楚系统对相位差更新率和通道间延迟差的要求。如果更新率低于1kHz且可以接受校准DSP还有讨论空间如果要实时出角度且两路严格同步FPGA几乎是唯一选择。1.3 完整信号链路与关键指标我做的这套系统结构是这样的天线信号经过模拟下混频后输出70MHz中频进入双通道同步采样ADC。ADC采样率100Msps14位分辨率。两路数字中频信号进入FPGA后先各经过一个DDC模块把70MHz中频采样后等效30MHz数字中频搬到基带输出I/Q数据。然后经过4倍CIC抽取、2倍半带滤波、2倍FIR补偿数据率降到6.25Msps。相位差估计模块基于CORDIC实现输出两路信号的瞬时相位差再经过滑动平均和标定补偿通过AXI-Lite接口送给ARM。系统关键指标如下参数设计值说明中频频率70MHz模拟前端输出ADC采样率100Msps双通道同步模式ADC分辨率14bit量化信噪比约 86dB输出数据率6.25Msps总抽取比16信号带宽5MHz滤波器通带设计值相位差精度≤0.2°20dB信噪比条件下相位差更新率6.25MHz可降速输出滑动平均后可配置这个链路里最容易被忽略的其实是ADC采样时钟的质量。相位差的精度由所有环节的噪声共同决定但采样时钟抖动是直接叠加在信号相位上的。我第一版设计直接用了板上PLL生成的100MHz时钟实测相位差抖动高达0.8度后来换成专用时钟芯片给ADC供钟并把同一路时钟引入FPGA做系统时钟抖动立刻降到0.15度以内。时钟抖动这件事做相位测量怎么强调都不过分。2. 参数设计的底层逻辑频率规划、NCO与抽取滤波器的三角关系2.1 采样率和中频频率怎么搭配才不浪费参数设计的起点是70MHz中频、100Msps采样率这个组合。很多人第一反应是采样率不满足奈奎斯特采样定理因为70MHz已经高于50MHz的奈奎斯特频率。这里用到的其实是带通采样欠采样信号落在第二奈奎斯特区50MHz~100MHz采样后频谱会以50MHz为折叠中心发生混叠。具体算一下70MHz中频信号在100Msps采样率下数字域中心频率 |70 - 100| 30MHz。也就是说ADC输出数据流里看到的信号频谱中心在30MHz处。所以DDC里NCO的本地振荡频率设为30MHz而不是70MHz。这个换算如果搞错了NCO频率不对DDC输出就是乱的而且仿真里还不容易发现。选择这个组合的原因也简单100Msps采样率对ADC和FPGA的IO速率要求适中30MHz数字中频留了20MHz的带宽空间方便处理5MHz宽带信号70MHz中频则是目前大多数接收机模拟前端的常用输出芯片选型容易。2.2 NCO频率控制字计算与相位分辨率NCO数控振荡器是DDC里产生本振信号的模块核心是一个相位累加器加一张正弦查找表。每个时钟周期相位累加器累加一个固定的频率控制字FTW累加器高若干位作为地址查表输出正弦和余弦值。FTW的计算公式是FTW f_out × 2^N / f_clk我用的N位累加器位宽是32位f_clk 100MHzf_out 30MHz那么FTW 30e6 × 2^32 / 100e6 1,288,490,188.8取整为1,288,490,189十六进制就是0x4CCCCCCD。这个值写入NCO实际输出频率和理想频率的偏差远小于1Hz对相位测量来说完全可以忽略。相位分辨率由累加器位宽决定2^32个相位步进对应360度每个步进对应的相位约0.0000000838度理论上极高。但实际查找表地址位宽有限我用的是高14位查表所以相位量化误差其实只取决于查找表深度。14位地址对应每周期16384个采样点相位量化误差约0.022度对系统0.2度的精度指标来说足够了。查找表本身我用的是16位量化正弦表。如果你对SFDR无杂散动态范围有更高要求可以考虑用两个小表做泰勒修正或者直接用CORDIC核生成正弦余弦。我项目里对SFDR要求不高查找表方案最省资源。2.3 抽取滤波器为什么非要三级级联抽取的目的有两个降低数据率滤除带外噪声。如果只用一级FIR做大抽取比滤波器阶数会高到不可接受。比如直接从100Msps降到6.25Msps抽取比16单级FIR要用几百个抽头DSP48资源直接爆掉。工程上几乎都用多级级联方案我采用的是CIC 半带 FIR的标准组合级数滤波器类型抽取比作用第一级CIC4级级联4大抽取比粗滤无乘法器第二级半带滤波器HBF2抽取2倍过渡带天然镜像抑制第三级FIR补偿滤波器2补偿CIC通带衰减精确整形CIC滤波器只需要加法器和延迟寄存器不需要乘法器特别适合第一级高数据率的抽取。但CIC通带不够平坦带外衰减也不陡峭所以后面必须跟补偿级。半带滤波器有一半系数为零乘法器数量减半放在CIC后很合适。第三级FIR的阶数可以取得比较小因为前面已经做了8倍抽取数据率已经降下来了。每级搭配都讲究一个原则前级负责简单粗暴地降速后级负责精细整形。如果反过来前面用高精度FIR后面用CIC那前级的高精度在后面的粗滤波里就白费了。2.4 CIC增益补偿最容易算错的一个数CIC滤波器虽然没有乘法器但它的增益很高而且随抽取比和级数指数增长。这一点在做定点设计时简直是灾难。CIC增益计算公式G (R × M)^N其中R是抽取比M是微分延迟通常取1或2N是级数。我这里的配置R4M1N4所以 G (4×1)^4 256也就是2^8次方。输入18位数据经过CIC之后输出动态范围会扩大8位。所以CIC输出必须右移8位才能恢复到和输入差不多的量级。这个细节如果没算对后果分两种不右移下一级滤波器输入饱和输出信号削顶相位差噪声骤增右移太多有效位数丢失信号幅度被量化噪声淹没。我第一版就是把右移位数算成了16结果信号幅度只有正常值的1/256在FPGA内部看不出来拿到板上测相位差完全被量化噪声淹没折腾了两天才定位到问题。建议做CIC级联时拿起计算器老老实实算一遍各级增益别省这一步。3. RTL实现里的关键模块混频链路与通道一致性的工程细节3.1 时钟方案与数据进片时序整个FPGA内部的数字逻辑全部跑在100MHz单时钟域里。ADC的100MHz采样时钟由专用时钟芯片产生同时送入FPGA的全局时钟引脚。这样ADC数据和FPGA系统时钟严格同源数据采样位置稳定不需要跨时钟域FIFO逻辑设计简化很多。但同源不等于自动安全。PCB上ADC数据线到FPGA的走线长度不同数据会在不同时刻到达FPGA引脚。7系列FPGA上有IDELAYE2原语可以以约78ps的步进精细调整每个输入数据的延迟把采样窗口对准眼图中央。我在调板阶段用vivado的debug工具扫描了DATA eye把每根线的delay值单独配置采样裕量从原来的不到50ps提高到300ps以上亚稳态风险基本消除。双通道的ADC数据线在PCB布局上尽量保持等长这个不用我说但实际打板之后总会有几十mil的偏差。如果不做引脚级延迟微调两通道数据进入FPGA的时间差会折算成固定相位偏置虽然标定能校掉一部分但不如在源头就尽量做齐。3.2 混频器位宽设计与截位策略数字混频器就是两个乘法器I路等于输入信号乘以NCO余弦Q路等于输入信号乘以NCO正弦。ADC输出14位数据NCO查找表输出16位乘法器结果是30位。30位直接进后级滤波资源压力太大必须截位。截位的原则只有一个保证后续各级不溢出的前提下尽可能多地保留有效位。我乘完取高18位因为信号本身14位NCO幅度归一化后乘积不会增加太多动态范围18位足够。同时18位输入CIC后加上2.4节算出来的8位增益CIC输出26位右移8位后恢复18位。这里有个容易被忽略的点截位会引入直流偏置。如果直接丢弃低位会产生一个与信号幅度无关的直流分量后续在频谱上表现为0Hz处一个尖峰。虽然不影响相位差测量两路都有相同偏置且会相减但会压缩后续滤波器的动态范围。稳妥的做法是截位前加一个舍入操作而不是直接截断。我当时用的是“加上半个LSB再截断”的简单舍入效果不错硬件成本也低。3.3 FIR补偿滤波器的IP配置与通道复用第三级FIR用Xilinx的Fir Compiler IP核实现系数在MATLAB里设计好导出。我用的是32阶等纹波FIR通带纹波0.01dB带外抑制60dB采样率6.25Msps通带约5MHz。这里值得说的是IP核的“通道数”参数。Fir Compiler支持配置成多通道模式两个通道共用同一组乘法器一个时钟周期交替处理通道0和通道1的数据资源占用几乎减半。配置成2通道模式后输入数据总线上两个通道的数据要拼接在一起按通道号交替送入IP核。这个模式下IP核的输出也会有同样交替关系后级逻辑要按通道号把数据拆开。FIR输出位宽我保留了24位。滤波器系数量化后频响会有轻微偏差但工程上INL和相位纹波都会通过标定补偿不需要追求完美。真正要注意的是输出不能饱和尤其输入信号幅度接近满量程时多级滤波的累加效应可能让瞬时值超过24位范围。我在输出级加了一个饱和保护逻辑一旦溢出就钳到最大正值或最小负值防止wrap-around造成的大幅度毛刺。3.4 相位展开unwrap为什么要单独做一个模块CORDIC核输出的相位范围是-π到π。当信号相位连续变化时输出会在π和-π之间跳变。如果两路信号的CORDIC输出直接做减法在跳变点附近会产生2π的跳变误差这个误差反映到测向角度上就是一条向反方向甩出去的毛刺。相位展开模块的思路检测相邻两次相位差采样值的变化量如果超过π也就是超过半个周期判断发生了wrap然后对后续输出自动加上或减去2π修正。// 相位展开核心逻辑phase_in为-32768~32767对应-π~π always (posedge clk) begin if (rst) begin phase_corr 16d0; phase_prev 16d0; end else begin if ((phase_in - phase_prev) 16d16000) // 跳变超过约π/2 phase_corr phase_corr - 16d32768; // 补偿-π else if ((phase_in - phase_prev) -16d16000) phase_corr phase_corr 16d32768; // 补偿π phase_prev phase_in; end end assign phase_unwrapped phase_in phase_corr;我最早偷懒没加这个模块仿真时因为输入是理想正弦没暴露问题上板后实测测向结果在信号相位跨过0度附近时频繁出现尖峰查了很久才发现是相位wrap导致。加了展开逻辑之后相位差输出是一条平滑的曲线毛刺彻底消失。4. 相位差估计的三种FPGA实现路径与精度实测对比4.1 基于FFT的频域测量稳但贵第一种路径是对两路DDC输出信号分别做FFT然后取信号所在频点的FFT结果相位两路相减得到相位差。FFT本质上是一组并行的窄带滤波器对噪声有天然抑制能力所以单次测量精度不错。FPGA里实现FFT用Xilinx FFT IP核就行我配置了1024点、16位输入输出、流水线架构。在100MHz时钟下处理6.25Msps的数据流实时性毫无压力。FFT路径有个天然的坑频谱泄漏。如果信号频率不落在某个FFT bin的正中心能量会泄漏到相邻bin相位测量结果会有误差。我实测下来不做任何窗函数时信号频率偏离bin中心0.3个bin宽度时相位误差可以达到2度以上。解决办法有两种一是加汉宁窗把泄漏压下去但主瓣会展宽bin分辨率变差二是用Goertzel算法只算信号所在频点的准确值精度高且资源比全FFT小很多。FFT路径的优点是一次能得到两路信号的频谱信息如果系统同时需要测频、测幅和测相用FFT最划算。缺点是延迟大——必须缓存一整帧数据才能做一次变换存在最大的延迟而且IP核资源占用比较高。4.2 基于CORDIC的时域连续测量快且省但别忘求平均第二种路径是对每一路I/Q数据用CORDIC核直接算瞬时相位两路相位相减得到瞬时相位差。CORDIC IP配置为“Translate”模式即向量模式输入I/Q输出arctan(Q/I)值和幅度。CORDIC输出精度由迭代次数决定。Xilinx的CORDIC核配置20次迭代16位输入输出时相位误差在0.2度左右。但这个误差是随机性的可以通过后续滤波平均消除。瞬时相位差的噪声实际上比较大。我实测单点相位差的随机抖动约1到2度输入信噪比30dB情况下按常理一看这个精度根本不够用。但注意相位测量和幅度测量不一样相位噪声是零均值的高斯白噪声可以通过时间平均大幅压低。我做了256点滑动平均之后相位差抖动量降到0.1度以内完全满足系统指标。滑动平均模块我用了移位寄存器加累加器的滑窗结构每来一个新数据就加进去移出一个旧数据资源开销很小延迟也只有256个时钟周期约41微秒比FFT路径小两个数量级。4.3 互相关法思路简单但窄带场景下性价比不高第三种路径是互相关法把两路信号中的一路延迟N个点和另一路做逐点相乘累加得到互相关函数。互相关峰值对应的延迟采样点数和相角就能换算出相位差。这个方法处理宽带信号时优势很明显因为只有两个信号高度相关时互相关峰值才尖锐能同时得到时延和相位信息。我做了个512点互相关的原型验证发现两个问题一是资源开销比CORDIC路径高很多乘法器和寄存器阵列占用了几乎两倍的DSP48二是在窄带信号下相关峰比较平坦相位差分辨率反而不如CORDIC加平均的组合。最终项目没有采用互相关法只留作以后做宽带信号时的备选方案。如果非要用互相关建议在FPGA里用并行乘法器阵列和加法树来实现数据率6.25Msps512点相关可以在约82微秒内完成一次更新率比FFT高但比CORDIC路径低。4.4 三种路径的实测数据对比方法单次精度平均后精度延迟DSP48资源适用场景FFT频域加窗约0.3°0.15°多帧平均毫秒级高FFT核需要频谱分析的场合CORDIC时域约1.5°0.08°256点平均微秒级极低实时连续测相互相关法约0.5°0.3°多次平均百微秒级中高宽带信号、时延估计最终我的工程选了CORDIC路径核心原因是延迟低和资源少。系统对相位差实时性要求高需要以微秒级延迟连续输出测向数据FFT和互相关方案的延迟都不能满足。CORDIC加滑动平均的组合在延迟和精度上找到了最好的平衡点。5. 调板阶段踩过的坑从时序违例到相位漂移的完整排查链路5.1 仿真正常但实测数据偶发跳变根因在采样窗口第一个大坑DDC仿真把所有波形看了个遍全对。上板之后用ILA抓数据发现混频后的信号大多数时间正常但每隔几万次采样就会出现一次明显的错误值看起来像信号瞬间被拉偏。排查过程记录一下第一步先用ILA隔一段时间抓一次NCO输出频率和相位都正常排除了NCO问题。第二步抓混频器输入也就是ADC原始数据。发现错误值出现在ADC数据跳变沿附近数据稳定时完全正常这基本锁定是采样时序问题。第三步用vivado眼图扫描工具看每个ADC数据引脚的建立保持裕量。结果发现其中一根数据线最高位几乎没有裕量数据跳变时刻和时钟采样沿几乎重叠。问题本质是PCB走线长度差异和器件引脚延迟不一致导致的数据到达时间偏差。解决方案是给ADC数据通道加上IDELAYE2逐根线调整延迟步进确保所有数据线的跳变沿都远离采样时钟沿。调整后重新扫描眼图所有信道的采样裕量都超过300ps实测数据错误完全消失。这个坑给我们的教训是FPGA内部逻辑再完美如果输入数据本身没有稳定地在时钟沿附近采样一切都白搭。尤其是高速ADC数据接口上板第一步就应该是调眼图而不是急着看信号处理结果。5.2 相位差输出与理论值系统性偏差两个通道的模拟前端群延迟不一致第二个坑信号链路正常了相位差输出也不跳变了但测出来的相位差和用网络分析仪测到的理论值差了一个随频率变化的量。一开始我以为是标定没做随手在数字域减了一个固定偏置结果发现一个频率点上校准对了换个频率又偏了。这才意识到问题是两通道模拟前端的群延迟不一致而且群延迟是随频率变化的。群延迟差异的来源两个通道的模拟滤波器、混频器、放大器虽然是同一型号但器件容差导致相位响应不完全一致。这个差异折算到相位上等于Δφ 2π × Δτ × f是个随频率线性增长的项。所以固定偏置补偿根本搞不定。解决办法是分频点标定在工作频带内取5个频点每隔2MHz用信号源分别注入记录每一频点上两通道的固定相位差存成查找表。实际工作时根据DDC输出的频点测量结果用线性插值算出当前频点的补偿值再从解算出的相位差中减掉。标定做完后相位差残差从2度左右降到0.2度以内。这一点尤其提醒做测向系统的朋友相位差标定不能只做一个频点必须覆盖整个工作频带否则换频点就是换误差。5.3 时序收敛困难与电源纹波两个“慢性病”的处理顺序第三个坑牵扯两件事先时序后电源。系统初期在Artix-7 200T上实现时序跑不到100MHz。排查下来不是因为逻辑量太大而是布局太散。两个通道的DDC路径上数据流跨越了多个时钟区域的边界布线延迟很大。解决办法给每个通道的DDC和相位估计模块加上Pblock物理区域约束把同一个通道的数据通路限制在相近的Slice区域路径长度大幅缩短同时混频器和FIR之间插入两级流水寄存器打断长组合逻辑链CORDIC IP配置从并行高吞吐改为部分串行模式LUT占用率降下来给布局流出空间。优化后时序稳定跑到135MHz以上有了约35%的余量。电源纹波的问题更隐蔽。系统工作一段时间后相位差输出的抖动会逐渐增大一开始以为温度问题后来用示波器测FPGA和ADC的电源引脚发现纹波将近50mV来自开关电源模块。相位差测量对电源纹波极其敏感因为纹波会通过模拟前端的偏置电路耦合进信号链路变成相位调制。解决办法是把ADC模拟电源和FPGA核心电源换成低噪声LDO实测相位差抖动从0.5度降到0.12度。注意做高精度相位测量系统电源要按模拟电路的思路来设计。开关电源可以给数字部分供电但ADC模拟电源、时钟芯片电源、模拟前端电源都必须用低噪声LDO并在靠近引脚处加足够的去耦电容。这个经验值回票价。5.4 一个容易被忽略的“软故障”滑动平均窗口长度对动态响应的影响最后补充一个不算bug但很容易踩到的点。滑动平均提高精度的原理是压制白噪声但它本质是一个低通滤波器会拖慢相位差输出对输入的响应速度。如果信号源的相位在快速变化平均窗口太长会导致角度输出滞后看起来就像测向结果“迟钝”。我当时刚开始用512点滑动平均测试单音信号没问题但换成跳频信号时相位差输出的响应明显跟不上。把滑动窗口从512降到128配合一个简单的IIR滤波才在响应速度和噪声抑制之间找到平衡。这类参数调试没有统一公式完全取决于系统对延迟和精度的具体需求。建议在做方案时把相位差输出的更新率和允许的最大延迟写明然后反推平均窗口长度。最后再分享一点我自己的体会。双通道DDC加相位差估计这套东西难点不在某个单点技术有多深而在于整条信号链太长了模拟前端、ADC时钟、数字混频、滤波抽取、相位解算、标定补偿每一环的微小误差都会叠加到最后的相位差结果上。做这个项目最大的收获是养成了一种“全链路顺信号”的排查习惯——遇到问题先不急着改代码而是画一条从天线端口到相位差输出的信号流图逐级检查每个节点的信噪比、增益和时序裕量是否合理。这个习惯比会写多少行Verilog都值钱。如果你也准备做类似系统建议从最小系统开始先把单通道DDC调通再接第二通道最后再做相位差解算和标定一步一个脚印比一次性搭完再排查高效得多。