
做射频接收链路有一阵子了之前接触的方案都是外挂高速ADC加FPGA布线麻烦不算还要调一堆差分时钟、同步信号。后来换了Zynq UltraScale RFSoC平台第一感觉是“集成度真高”——RF-ADC、RF-DAC、DDC/DUC、混频器、QMC全部塞进一颗SoC里射频前端直采不再是实验室里遥不可及的东西。但真到自己动手配置RF Data Converter IP的时候才发现里面可调的参数、寄存器的时序、校调的顺序每一条都藏着坑。这篇文章就把我从ADC混频器一路调到QMC校调的完整过程写出来包含NCO频率的计算方法、QMC系数的估计算法、以及实际工程里最容易出问题的几个环节。适合手里有ZCU111或者类似RFSoC板卡的工程师参考也适合刚接触射频直采的人建立整体概念。1. 为什么选择Zynq UltraScale RFSoC做射频直采1.1 RF-ADC和RF-DAC到底解决了什么问题Zynq UltraScale RFSoC这个系列核心卖点就是把射频采样前端集成进SoC。我这边使用的是ZCU111评估板主芯片是XCZU28DR片上集成了8路12bit RF-ADC标称采样率4.096 GSPS配套的还有8路RF-DAC。按照Nyquist采样定理4.096 GSPS的采样率理论上可以覆盖到2 GHz以下的信号实际工程里最常用的是第一Nyquist区或者第二Nyquist区采样直接把射频信号采进来省掉了一整条模拟下变频链路。以前做宽带接收机得用分立混频器、本振源、滤波器、两级中频放大一条链路打下来又贵又难调用RFSoC之后模拟前端变得非常简洁信号进来之后就是ADC之后全部是数字处理。但集成度高也意味着你必须接受它给定的数字处理链路。RF数据转换器内部不是裸的ADC采样那么简单在采样数据到你手里的AXI-Stream接口之前它会经过抽取滤波、复数混频、QMC等模块。很多刚上手的人直接在PL端抓ADC原始数据发现抓回来的数据不是自己想要的就是因为没搞清楚这些内部模块的默认配置。所以理解RF Data Converter IP内部到底干了什么是配置的第一步。1.2 RF Data Converter IP在系统中的位置和职责在Vivado的Block Design里RF Data Converter IP是一个软核IP通过AXI4-Lite接口做寄存器配置通过AXI4-Stream接口输出采样数据。它通常按Tile来组织每个Tile对应一组ADC或DAC模拟通道一个IP实例里可以包含多个Tile每个Tile又可以包含多个Channel。IP内部各个模块的职责我用一张表总结一下。模块作用配置关键点RF-ADC模拟前端采样、增益控制、偏置校准采样率、满量程电压、阻抗匹配抽取滤波器Decimation降低数据率缓解后端处理压力抽取倍数、滤波器带宽复数混频器数字下变频将射频信号搬到基带混频模式、NCO频率NCO产生混频所需的本地振荡信号频率字、相位字、更新触发QMC正交调制校正校正I/Q不平衡和本振泄漏使能位、幅度/相位校正系数AXI-Stream接口把处理后的数据送出PL位宽、数据格式、TREADY信号搞清楚这个结构之后你配置IP的时候就不会眉毛胡子一把抓了。我个人的习惯是先看采样率和时钟再看混频器和滤波器最后调QMC。这个顺序对应的是信号链路的先后顺序也符合“先让数据通起来再谈信号质量”的工程思路。2. ADC混频器配置从原理到寄存器操作2.1 混频器的类型和选择RF Data Converter里的混频器和传统模拟混频器原理上是一样的只不过一个处理的是数字序列一个处理的是连续电压。模拟混频器通过乘法器把信号与本振相乘输出和频、差频分量数字混频器也是做乘法只不过输入信号已经是ADC采样后的数字序列本振则是NCO数控振荡器生成的数字正弦/余弦序列。混频前后的波形差异很多人第一次用ILA观测时会很困惑混频前看到的是高频实信号波形混频后看到的是I/Q两路复信号波形频率完全不同。IP里混频器有几种可选模式实际工程中要根据你的后端处理方式来做选择。Real - Real输入实信号输出实信号。这种模式通常只在DAC侧使用因为DAC如果要直接输出实射频信号混频器就配成实数混频输出NCO产生的本振与输入相乘后取实部。Real - Complex这是ADC接收链路最常用的模式。输入是实信号混频输出I/Q两路复信号实现数字下变频到零中频或者低中频。Complex - Complex输入输出都是复信号适用于I/Q信号需要进一步变频的场景。Bypass完全旁路混频器NCO不使能ADC采样数据直接送往滤波器或AXI-Stream接口。这个模式在纯中频采样、后端自己处理频谱翻转时使用。配置的时候还有一个容易忽略的点如果选择了Real-Complex模式ADC通道会占用两个AXI-Stream输出一个I路一个Q路数据率会翻倍。如果你在后端接DMA或者直接进FFT位宽和数据率都要提前算好否则很容易出现总线拥塞。我第一次做的时候没注意数据率翻倍这个问题DMA配置还是按单通道算的结果采集回来的数据隔一段就跳一个样点排查了好半天才发现是AXI-Stream带宽不够。2.2 NCO频率计算与写入流程NCO本质上是一个相位累加器它决定了混频器本振的频率。RF Data Converter里的NCO通常使用48位相位累加器NCO输出频率与采样时钟、频率控制字的关系是固定的。假设ADC采样时钟为FsNCO希望输出的本振频率为F_nco那么频率控制字ΔP的计算公式为ΔP round(F_nco * 2^48 / Fs)这里有几个要点。首先计算出来的ΔP是48位无符号整数写入寄存器时要注意位宽超出48位的部分直接截断。其次这个公式里的Fs一定要用混频器所在数据路径的实际采样率而不是简单地用ADC的模拟采样率因为如果前面已经做了抽取进入混频器的数据率会变低NCO累加器的时钟域也相应变化。我自己踩过的坑就是ADC标称4.096 GSPS前面配了4倍抽取混频器实际工作在1.024 GSPS结果我NCO频率计算时还用的4.096 GSPS算出来的本振频率差了4倍混频后信号位置完全不对。写入NCO频率的流程也不是只写一个寄存器就完事需要按照固定的步骤来。简化后的写流程如下将NCO频率控制字按高32位、低16位拆分分别写入对应的频率控制字寄存器。如果需要设置初始相位同样将相位字拆分写入相位寄存器。写触发更新控制寄存器置更新位让NCO重新装载频率字。回读状态寄存器确认更新完成。很多寄存器是影子寄存器你写进去的值不会立即生效必须有更新触发信号才会进入工作状态。我见过有同事写完频率字寄存器后没有触发更新然后一遍遍怀疑自己的NCO频率算错了其实就是少了最后一步。2.3 混频前后波形的观察方法混频器配完之后如何在工程里验证它真的把信号搬到了正确的位置我通常用两种方式。第一种是在IP内部调试接口上直接观测数据用ILA集成逻辑分析仪抓混频器输出第二种是把捕获到的数据导出在MATLAB或Python里做FFT看频谱。先说ILA观测。如果输入信号的频率是F_signalNCO频率是F_nco那么混频器输出会同时出现F_signal - F_nco和F_signal F_nco两个分量。在数字下变频场景下后面一般跟着低通抽取滤波器所以和频分量会被滤掉留下的是差频分量。在ILA里看I/Q两路波形如果F_nco不等于F_signal你会看到I路和Q路各自都是一个缓变的正弦波频率等于两边的差频如果F_nco恰好等于F_signal差频为零I/Q两路波形会变成接近直流的电平幅值。第二种方式是做FFT。把混频后的I/Q数据导出拼成复数序列I jQ再做FFT。这里有一个特别需要注意的点复数序列的频谱不是双边对称的只有单边谱F_nco的频率搬移会在正频率和负频率上体现出差异所以你可以通过观察镜像频谱的位置来判断混频是否正确。如果混频后频谱出现了很强的镜像分量要么是I/Q两路增益不匹配要么是相位不正交这就和下一节的QMC联系起来了。3. QMC校调消除I/Q失衡的关键一步3.1 什么是I/Q失衡以及为什么会存在I/Q失衡其实是个非常现实的问题。理论上I路和Q路应该是幅度完全一致、相位严格相差90度的正交信号。但实际链路里模拟前端、变压器、放大器、PCB布线都会让I/Q两路出现幅度偏差和相位偏差。幅度偏差一般用增益误差g来描述相位偏差用相位误差φ来描述。我举个直观的例子一个原始的单音信号经过I/Q失衡的链路后在频谱上就会出现镜像分量也就是在负频率或者对应镜像频率位置出现一个不该有的峰值。这个镜像会直接影响接收机的动态范围和信噪比。在发射链路里QMC的意义更大。发射的I/Q信号如果存在直流偏置会直接导致本振泄漏也就是说本振信号会直接串到发射频点上这在频谱仪上一看就是在载波中心位置有一条异常谱线不仅浪费发射功率还干扰同频段的其它设备。所以发射链路QMC除了校正I/Q不平衡外还必须包含直流偏置的校正。RF Data Converter IP中的QMC分接收QMC和发射QMC两者在寄存器空间上分属不同的地址区域。接收QMC主要校正I/Q增益和相位误差发射QMC还要额外校正常数项直流偏置。配置的时候不要只盯着系数寄存器使能位和控制位也要一起检查否则系数写进去也不会生效。3.2 QMC校正系数的计算方法QMC校正系数怎么确定严格的做法是发射时用外部仪器测量接收时注入已知单音信号来评估。工程中最常用的方法是单音测量法基本流程如下。先在接收通道注入一个单音信号频率选择在带内比较干净的位置幅度不要超过满量程的80%然后采集一段I/Q数据估计增益误差和相位误差。增益误差可以用I/Q两路信号的均方根幅度比值来估算g sqrt( sum(Q^2) / sum(I^2) )相位误差可以通过两路信号的相关系数来估算。一个简单的方法是计算I和Q的互相关再除以I路的自相关rho sum(I*Q) / sum(I^2)得到g和rho之后可以用Gram-Schmidt正交化来构造校正矩阵也就是把Q路信号减去与I路相关的部分再归一化I IQ (Q - rho * I) / sqrt(1 - rho^2)这个校正矩阵的目的是让变换后的I和Q正交且幅度一致。实际写入IP寄存器时通常需要把浮点系数转成定点小数格式。具体的小数位宽看IP版本PG269文档里写得很清楚。我用的Vivado版本下QMC系数寄存器是带符号定点数需要先把计算出的浮点系数乘以对应的比例因子再取整否则写入完全没效果甚至可能把信号搞乱。这里还要强调一个工程细节计算QMC系数前一定要先把直流分量去掉。如果I/Q数据带着直流偏置相关系数和幅度比值都会被带偏算出来的系数是错的。这也是为什么上面代码里先对I和Q做了去均值处理。我自己调试时曾经跳过去直流直接算QMC系数结果镜像不仅没消掉反而更差了原因就是直流偏置污染了系数估计。3.3 在IP寄存器中写入QMC系数的流程QMC系数的写流程不同IP版本略有差异但整体套路是一致的。以我使用的RF Data Converter IP为例流程如下。确认通道的混频器和抽取配置已经稳定工作采集到有效的I/Q数据。先做直流估计把I路和Q路的均值算出来如果需要写直流偏置校正寄存器就先把直流项校正掉。注入单音信号接收QMC或者启用测试音发射QMC重新采集I/Q数据。在软件里计算I/Q增益误差g、相位误差rho以及对应的QMC系数。把系数按定点格式写入QMC增益寄存器和QMC相位寄存器。置QMC使能位让校正开始生效。再次采集数据检查镜像抑制比Image Rejection Ratio是否达到要求。如果不够重复步骤3到步骤6。我在实际项目里的做法是写一个Python脚本通过JTAG或者以太网连接板卡自动完成“注音、采集、计算、写寄存器、回读验证”这个闭环。整个过程跑一轮通常只需要几十秒比手工在Vivado里点来点去高效得多。调试初期建议先把使能位关掉验证系数写入通道确认寄存器回读值和写入值一致再开使能否则排查问题时很难分清是算法算错了还是寄存器没写成。4. 完整实操一次从Vivado建工程到频谱验证的流程4.1 工程搭建与IP例化这部分写一下我从零开始搭建一个RFSoC收发链路验证工程的过程。先新建一个Vivado RTL工程器件选择XCZU28DR。在Block Design里添加RF Data Converter IP双击打开配置界面。配置界面里需要做的事情有这几项。第一根据板卡上的时钟源设置ADC和DAC的采样率ZCU111上通常有可编程时钟芯片可以在Vivado里通过驱动管理也可以直接读固定的时钟频率。我习惯于先在时钟芯片驱动里把需要的采样率设置好再回到IP里配置这样两边能对应上。第二使能需要用到的ADC/DAC Tile和Channel配置抽取倍数、混频器模式、NCO频率等参数。第三把IP的AXI4-Lite从接口接到一个AXI主控上在ZCU111这种带有PS端的板卡上我一般直接接在PS的M_AXI_HPM0接口上这样可以在Linux里通过/dev/mem或者自定义驱动去读写寄存器调试起来很方便。连接完AXI接口后记得把AXI-Stream数据接口接到后续处理逻辑。最简单的验证方式是直接用ILA去抓数据不需要先搭DMA先把数据通路打通。4.2 ADC通道配置与数据捕获ADC通道配置的核心是确认采样率、抽取倍数、混频器三者的参数匹配。我举一个实际参数例子假设ADC采样率是4.096 GSPS信号载频在1.8 GHz需要下变频到基带。那么混频器NCO频率设置为1.8 GHz抽取倍数根据后端期望的数据率来选择。如果后端希望数据率是256 MSPS抽取倍数就是4.096 GHz / 256 MHz 16倍。抽取倍数会决定信号带宽务必确保信号本身的带宽小于抽取后有效带宽的一半否则会产生混叠。配置完成后在ILA里抓取混频器输出数据。抓取的时候要注意ILA的采样时钟是PL逻辑时钟数据本身是连续流动的需要设置合适的触发条件比如用数据有效信号作为触发或者直接抓一段固定深度的数据。抓回来的I/Q数据通过Vivado的波形导出功能存成CSV再导入Python进行分析。我一般把I/Q两列分别导出然后在Python里拼成复数数组这样可以直接做FFT。4.3 去直流、归一化和滤波处理ADC采集回来的基带数据第一件事就是去直流。射频直采后经过数字下变频如果混频器或者模拟前端存在直流偏置频谱中心位置会看到明显的直流分量严重影响信号质量。去直流最简单的办法就是求均值然后减去。我在嵌入式处理里经常写一段滑动平均滤波来估计直流分量逻辑很简单开一个缓冲数组存放最近N个采样点的值每次新数据进来把最旧的值移除把新值加入再取平均。下面是C语言实现的一个16点滑动平均滤波函数在低数据率场景下非常好用。#define FILTER_TAPS 16 static int32_t buffer[FILTER_TAPS]; static uint8_t idx 0; static int64_t sum 0; int32_t moving_average_dc_removal(int32_t sample) { sum - buffer[idx]; buffer[idx] sample; sum buffer[idx]; idx (idx 1) % FILTER_TAPS; int32_t mean (int32_t)(sum / FILTER_TAPS); return sample - mean; }在高数据率的RFSoC场景下这种软件滤波只能用于低速监控链路。真正的高速数据通路一般直接在PL里用FIR滤波器IP核实现或者依靠RF Data Converter内部的抽取滤波器自带的高通特性。但不管在哪个环节实现去直流和归一化的思想是一致的。归一化就是把信号幅度对齐到满量程。RF-ADC的输出通常是二进制补码格式比如12bit ADC对应-2048到2047的范围归一化时可以除以2048让幅度范围落在-1到1之间这样后续FFT处理和信噪比计算会更方便。信号滤波方面采集到的I/Q数据可能会混入带外噪声。最简单的处理方式是做一个FIR低通滤波器滤除带外噪声后再做FFT分析。这里给一个参考思路滤波器的通带带宽要大于信号带宽阻带衰减至少60 dB以上。滤波器设计在MATLAB里用fdatool生成系数然后导入到Python或C里使用。如果只是做频谱观察直接对原始I/Q数据做加窗FFT也能看到大致的频谱形态。4.4 信噪比与镜像抑制比验证配置完成后最终要用指标说话。两个最直观的指标就是信噪比和镜像抑制比。信噪比的计算方法是采集一段单音信号做FFT后找到信号峰值所在的频率bin把信号功率算出来然后统计除了信号和直流附近的若干bin之外的所有功率作为噪声功率两者相除就是SNR。下面是一段Python示例代码用于计算信号bin及其周围几个bin的总功率。import numpy as np def compute_snr(complex_signal, fs, signal_freq, exclude_bins10): N len(complex_signal) win np.blackman(N) sig complex_signal * win spectrum np.fft.fftshift(np.fft.fft(sig)) freqs np.fft.fftshift(np.fft.fftfreq(N, 1/fs)) k_signal int(np.argmin(np.abs(freqs - signal_freq))) signal_indices range(k_signal - exclude_bins, k_signal exclude_bins 1) signal_power np.sum(np.abs(spectrum[list(signal_indices)])**2) mask np.ones(N, dtypebool) center N // 2 mask[center - exclude_bins*2 : center exclude_bins*2] False mask[list(signal_indices)] False noise_power np.sum(np.abs(spectrum[mask])**2) snr_db 10 * np.log10(signal_power / noise_power) return snr_db, spectrum, freqs镜像抑制比的验证方法类似注入单音信号后在镜像频率位置寻找峰值功率然后用信号功率减去镜像功率。比如信号在1 MHz处那么镜像应该在-1 MHz处。QMC校准前镜像抑制比可能只有20~30 dB校准之后一般可以提升到50 dB以上。我实测下来在ZCU111上做好QMC校调和直流校后镜像抑制比能做到55 dB左右这个水平对大多数通信接收链路来说已经够用了。5. 常见问题与排查技巧实录5.1 采样时钟与逻辑时钟不同步这类问题非常典型表面的现象是AXI4-Lite寄存器访问超时或者回读数据全部是同一个值。我排查时先看时钟用Vivado的Clock Debug功能确认ADCCLK、DACCLK和PL逻辑时钟是否都稳定。有些问题是板卡上的可编程时钟芯片没有初始化有些问题是IP配置界面里的时钟频率和实际提供的时钟对不上。RF Data Converter IP对采样时钟的要求很严格尤其是不同Tile之间如果需要同步必须保证参考时钟来自同一个源否则通道间的相位关系是乱的。遇到这类问题不要一上来就怀疑代码逻辑先用回读寄存器的方式确认IP是否已经启动。一般IP会有版本寄存器或者状态寄存器回读正常再往下查数据通路。5.2 混频后出现频谱镜像泄漏频谱上出现镜像峰值首先确认NCO频率和信号频率的关系。如果信号和本振没有完全对准镜像位置会出现一个同样强度的信号这个是正常现象不是失衡。排除这个情况后再看I/Q两路数据是否真的正交。如果后端拿到的I/Q数据其中一路是空的或者两路数据错位了频谱镜像会非常严重。最常见的原因是后端代码在拼接I/Q数据时时序没对齐也就是I路和Q路不是同一个采样时刻的数据。检查时需要确认AXI-Stream接口上I/Q数据是否交替输出还是分两个通道输出处理逻辑要匹配IP的配置。如果数据通路没问题那就是模拟I/Q失衡需要做QMC校调。5.3 QMC校准始终不收敛QMC校准不收敛是大家问得最多的问题。我排查时会依次检查下面几件事。输入信号是否是干净的单音如果信号本身有很强的杂散相关系数和幅度估计都会被污染。信号幅度是否在合适的范围内幅度太小量化噪声占比大估计误差大幅度太大ADC削波波形失真估计结果当然不对。直流偏置是否已经先校掉这个问题我前面提过直流偏置不除I/Q增益和相位的估计全是错的。寄存器系数格式是否正确QMC系数是定点小数直接把浮点系数写进寄存器会得到完全错误的结果。更新流程是否正确有些通道的QMC系数需要先写使能位再写系数或者反过来顺序错了一个都生效不了。把这些问题逐一排查完QMC校准基本都能收敛。我自己调试时最常犯的是漏掉直流偏置校准这一步后来固定成“先直流、后I/Q”的流程就很少再卡住了。5.4 常见问题速查表现象可能原因排查与解决建议AXI寄存器访问超时采样时钟未起振或频率不对检查时钟芯片配置、确认IP时钟频率匹配混频器输出频率不对NCO频率计算用了错误的采样率用混频器实际工作时钟重新计算ΔP频谱出现镜像峰值I/Q失衡、数据错位、本振未对准先验证数据对齐再做QMC校调QMC校准后镜像改善不明显直流偏置未消除、系数格式错误按“先直流、后I/Q”流程重新校准数据流中断或跳点AXI-Stream带宽不足、DMA配置错误核对数据率、检查DMA描述符和中断写在最后这套RF Data Converter IP的配置流程我自己前前后后调了好几轮最大的体会是一定要按信号链路的顺序去排查采样时钟没通就不要谈混频混频不对就不要谈QMC。不要跳步。另一个想提醒的是QMC校调里“先直流、后I/Q”这个顺序几乎决定了整个校调的成败。很多人一上来就瞄着镜像抑制比忽略了直流偏置结果系数怎么算都不对。如果这篇文章能让你的RFSoC调试少走几次弯路那就算值了。