简介面向具备一定硬件调试基础的FPGA与射频工程师这份文档围绕Alinx Zynq UltraScaleRFSoC开发板的ADC/DAC调试展开重点解决DAC输出频率与设置不匹配、ADC采集频谱异常等典型问题。内容按调试现场记录涵盖ILA信号抓取、频谱仪与信号源验证、RF Data Converter配置检查、Vitis代码分析及AXI总线时钟确认等环节并结合5倍抽取、5倍插入、IQ转Real与8 Sample per AXI4-Stream Cycle等参数最终定位到抽取/插入值未计入采样周期因素的配置疏漏给出修正思路。压缩包内为1个docx文档约449KB便于直接查阅与归档。已有904人学习适合希望理解RFSoC调试流程、积累故障排查方法或对照自身项目排查频率配置问题的技术人员参考。1. 从一块 RFSoC 板卡上电说起ADC 和 DAC 回环工程为什么值得先跑一遍拿到这块 RFSoC 板卡我没有急着写自己的采集逻辑而是先把厂商给的回环 demo 跑了一遍——用一根射频线把 DAC 输出直接接回 ADC 输入让信号自己走一圈。原因很实际射频数据转换器这条链路上能自证的地方太多了采样时钟、抽取插值、数字上下变频、AXI4-Stream 位宽只要有一处账没算对波形看上去都像那么回事但频率一定是错的。这个项目做的就是这样一件事在 RFSoC 平台上用 RF Data Converter IP 配出 4.9152 GSPS 的采样时钟ADC 侧 5 倍抽取、DAC 侧 5 倍插值数字变频都放在 1.5 GHz再在 Vitis 里驱动 DDS 出单音、用 ILA 抓数、频谱仪和 MATLAB 交叉验证。适合已经能写 FPGA 时序、但第一次碰射频数据转换器的人尤其是那种配置页每一项都填了、频率就是跟预期差一截的现场。2. 工程骨架与时钟账本4.9152 GSPS 怎样落到 245.76 MHz 的 AXI 时钟2.1 回环链路的物理拓扑与数据通路先把这条链路摆清楚后面所有排查都围着它转。信号从 PL 侧的 DDS 产生四个 16 位 DDS 输出拼成一个 64 位字通过 AXI4-Stream 送进 RF Data Converter 的 DAC 通道DAC 内部做数字上变频NCO 设 1.5 GHz、5 倍插值最后送到模拟输出口。板子外面用一根 SMA 线把 DAC 输出接回 ADC 输入ADC 侧做 5 倍抽取、数字下变频同样 1.5 GHz把 64 位的实数样本通过 AXI4-Stream 吐回 PLILA 挂在这个总线上抓数再把抓到的数据导出给 MATLAB 做频谱。这条链路里有两个容易混淆的地方。第一个是 ADC 和 DAC 的 AXI4-Stream 位宽不一样ADC 是 64 位一个时钟周期 4 个 16 位实数样本DAC 是 128 位一个时钟周期 8 个样本。位宽不同意味着两个端口的时钟频率不是同一个数很多人口口相传的AXI 时钟 245.76 MHz只在 ADC 侧成立。第二个是 NCO 的位置上下变频都在 IP 内部完成PL 侧拿到的已经是基带或近基带的复/实数流所以 DDS 产生的那个几 MHz 的单音落到频谱仪上会跑到 1.5 GHz 附近去这个偏移量才是你要盯的东西。调试时我给自己定了个规矩先看时域再看频域最后才动代码。时域看 ILA 里 DDS 输出的周期对不对频域看频谱仪上单音落在哪两个都对不上再去翻寄存器配置顺序反了就容易在代码里瞎改。2.2 把采样率、抽取因子、样本位宽算成一张表射频调试里最值钱的东西不是示波器是一张算得平的账。这套配置的账本我列在下面后面每一次频率不对的判断都拿它做基准。参数数值推导来源器件采样率 FS4.9152 GSPSADC/DAC 模拟端采样时钟ADC 抽取因子5RF Data Converter 配置页ADC 抽取后数据率982.4 MSPS4.9152 / 5ADC 每 AXI 周期样本数464 位 ÷ 16 位ADC 端口时钟245.76 MHz982.4 / 4DAC 插值因子5RF Data Converter 配置页DAC 每 AXI 周期样本数8128 位 ÷ 16 位DAC 端口时钟122.88 MHz982.4 / 8上下变频 NCO1.5 GHzMixer 配置DDS 相位累加器16 位步进 0x200fout fclk × 512 / 65536这张表里最容易被忽略的是最后一行。DDS 的频率不是随手填的它由相位累加器位宽和步进共同决定。步进填 0x200也就是 512累加器 16 位那么单路 DDS 输出频率就是 245.76 MHz × 512 / 65536 1.92 MHz。四个 DDS 拼成 64 位以后实际波形是四路样本首尾相接等效频率翻四倍到 7.68 MHz周期 130.2 ns正好是 32 个 245.76 MHz 时钟。这个数字和 ILA 里看到的每段 16 位信号 32 个时钟一个周期完全对得上说明 PL 侧的 DDS 是干净的。下面是算这套账的脚本改配置以后重跑一遍就行比拿计算器按靠谱FS 4.9152e9 # 器件采样率单位 Hz DEC 5 # ADC 抽取 / DAC 插值因子 ADC_BITS 64 # ADC AXI4-Stream 位宽 DAC_BITS 128 # DAC AXI4-Stream 位宽 SAMPLE_BITS 16 # 单个样本位宽 NCO 1.5e9 # 数字上下变频频率 DDS_STEP 0x200 # DDS 相位步进 DDS_ACC_BITS 16 # DDS 相位累加器位宽 adc_rate FS / DEC # 抽取后样本率 adc_clk adc_rate / (ADC_BITS / SAMPLE_BITS) dac_clk adc_rate / (DAC_BITS / SAMPLE_BITS) print(fADC 端口时钟 {adc_clk/1e6:.2f} MHz) print(fDAC 端口时钟 {dac_clk/1e6:.2f} MHz) print(f单路 DDS {adc_clk * DDS_STEP / 2**DDS_ACC_BITS/1e6:.2f} MHz) print(f四路拼接 {4 * adc_clk * DDS_STEP / 2**DDS_ACC_BITS/1e6:.2f} MHz) print(f预期频谱仪读数 {(NCO 4*adc_clk*DDS_STEP/2**DDS_ACC_BITS)/1e9:.4f} GHz)脚本里adc_clk和dac_clk之所以要分开算是因为位宽不同DDS_ACC_BITS取 16 是我从 ILA 波形反推出来的如果你的工程里 DDS 是 32 位累加器把这个值改掉频率会差六个数量级别硬套。最后那行打印的是应该看到的频谱仪读数有了这个基准屏幕上的数字一出来就知道对不对不用再凭感觉判断。2.3 RF Data Converter IP 里真正要盯的四项配置Vivado 里这个 IP 的配置页很长但真正决定频率的只有四项剩下的都是跟着走的。第一项是 Sampling Rate填 4.9152 GSPS这一项决定了 IP 内部所有时钟的根。第二项是 Decimation / Interpolation ModeADC 选 5 倍抽取DAC 选 5 倍插值注意这两个下拉框选完之后Summary 页会重新算一遍端口时钟那个数才是最终生效的。第三项是 Samples per AXI4-Stream CycleADC 选 4、DAC 选 8这一项直接和位宽绑定改一个另一个跟着变。第四项是 Mixer 设置Fine Mixer 的频率填 1.5 GHzNCO 的相位增量由工具算你不用管。提示IP 的 Summary 页里显示的 port clock 是唯一可信的时钟数值别拿公式现算的结果去覆盖它。我第一次就是拿245.76 MHz这个数当成了两个端口共用结果在 DAC 侧多绕了半天。这四项配完之后理论上 PL 侧的时钟由 IP 的 clock_out 端口给出一路进 BUFG 分发。注意 clock_out 的频率不等于端口时钟它是一个更高频的参考这套配置下是 153.6 MHz后面在 Vitis 里如果要量总线时钟不能拿它当基准。我当时的做法是把 clock_out 和 Fabric 时钟各分频后引到 ILA 探针上用 AXI-Lite 的 100 MHz 做参考计数才把实际频率量出来。3. 三条证据链ILA、频谱仪、MATLAB 各说各话3.1 ILA 抓 64 位 DDS 输出先看时域周期第一次上板我把 ILA 挂在 AXI4-Stream 的 TData 上采样深度开 8192触发条件用一个简单的 valid 上升沿。抓出来的波形很干净没有毛刺64 位数据拆成四段 16 位以后每段的周期都是 32 个采样时钟。这个结果本身是好消息——说明 PL 侧的数据生成逻辑没问题DDS 在正确地产数AXI4-Stream 的握手也没丢拍。但波形干净和频率正确是两件事。ILA 用的是总线自己的时钟做采样你看到 32 个周期只证明了相对这个时钟是 32 个周期如果这个时钟本身不是你以为的 245.76 MHz那绝对频率就是错的。这一步最容易翻车的地方就是把 ILA 的好看当成了结论后面所有判断都建在错的基准上。我后来的习惯是只要涉及频率ILA 只用来验证逻辑关系和相对周期绝对频率一律交给频谱仪和外部计数器两者对不上就以外部为准。另外 ILA 的采样深度也要留意。抓 8192 个点在 245.76 MHz 下只有 33 µs 的窗口看 7.68 MHz 的信号够用但如果要观察低频包络或者做长时间统计就不够了会误以为信号是稳的。3.2 频谱仪读 DAC 输出1.502 GHz 和 1.508 GHz 的差把 DAC 输出接到频谱仪上中心频率设 1.5 GHz扫宽 100 MHz屏幕上跳出来一根单音读数 1.502 GHz。按 2.2 节那张账本1.5 GHz 的 NCO 加上 7.68 MHz 的基带单音应该落在 1.508 GHz 左右差 6 MHz。这个偏差说大不大说小不小正好卡在是不是测量误差的模糊地带。我当时的判断路径是这样的先排除频谱仪本身把 RBW 调小到 1 kHz读数还是 1.502 GHz说明不是分辨率问题再回去看 ILA 里 DDS 的周期确实是 32 个时钟说明 PL 侧没变那问题只可能在DAC 实际吃样本的速率上。7.68 MHz 变成 1.92 MHz正好是四倍关系而四倍这个数字太眼熟了——AXI4-Stream 一个周期送 4 个样本如果实际时钟慢了四倍样本被拉长输出的等效频率就掉四倍。这个推理后来被证实是对的但当时并不敢下结论因为还有一种可能是 IP 内部的插值配置被改过。所以我没有立刻去改代码而是转头去查 ADC 侧想看看两个通道是不是同时偏。3.3 MATLAB 画 ADC 频谱40 MHz 是怎么冒出来的信号源从外部灌一个 1.510 GHz 的单音进 ADCNCO 是 1.5 GHz差频应该是 10 MHz。我把 ILA 抓到的 64 位数据导出成 CSV写了个脚本做 FFT结果峰值出现在 40 MHz。raw readmatrix(ila_capture.csv); % ILA 导出的 64 位样本每行一个 AXI 时钟 samples reshape(raw., [], 1); % 每个 64 位拆成 4 个 16 位实数样本 samples double(samples); samples samples - mean(samples); % 去直流避免 0 Hz 处的峰盖住有用信号 fs 983.04e6; % 关键参数ADC 实际样本率 N 2^nextpow2(numel(samples)); win hann(numel(samples)); % 加窗抑制泄漏 spec abs(fft(samples .* win, N)); f (0:N-1) * fs / N; % 频率轴 [~, idx] max(spec(2:N/2)); % 忽略直流找最大峰 fprintf(峰值频率 %.2f MHz\n, f(idx1) / 1e6);脚本里fs是最要命的一个参数。这个值必须是 ADC 真正吐样本的速率也就是 4.9152 GSPS 除 5 等于 983.04 MSPS而不是 AXI4-Stream 的端口时钟 245.76 MHz。我第一次填的是 245.76e6结果 10 MHz 的差频被画到 2.5 MHz 附近后来改填 983.04e6 之前中间还试过一版填成四倍的 3.93 GHz那次峰值就跑到了 40 MHz。40 这个数字对应的正是采样率填大了四倍说明数据本身没问题是我把账算错了。reshape(raw., [], 1)这一行的写法要注意MATLAB 按列优先展开所以要先转置再 reshape否则四个样本的顺序会乱频谱上会出现镜像。加 Hann 窗是因为采样点数不是整数个周期不加窗泄漏会很严重峰值位置不好判断。到这一步三条证据链给出的结论是矛盾的ILA 说 PL 侧正常频谱仪说 DAC 输出频率偏低四倍MATLAB 说 ADC 的数据率也不是 983.04 MSPS。三个现象指向同一个方向——实际时钟和配置表里的时钟不是一回事。4. 定位到 Vitis 侧ChangeSampleFreq 少除的那个 44.1 反推总线时钟实测 66.41 MHz 对不上账要确认时钟最直接的办法是量。这块板子没把 AXI 总线时钟引到测试点我用了一个笨办法在工程里找一个 AXI-Lite 的 100 MHz 时钟当参考用它的计数器去数被测时钟的周期数再把两个计数结果一起送进 ILA。被测时钟频率低于 100 MHz 的直接数就行高于 100 MHz 的先在 Fabric 里做二分频再数。这套配置下AXI 总线时钟理论上是 245.76 MHz分频后应该看到 122.88 MHz 左右clock_out 理论上 153.6 MHz分频后 76.8 MHz。实测结果clock_out 是 153.6 MHz对得上AXI 总线时钟只有 66.41 MHz。66.41 这个数既不是 245.76也不是它的二分频 122.88但很接近 61.44 MHz——也就是 245.76 除以 4。到这一步基本可以确定有人在代码里把时钟发生器重新配过而且配成了目标值的四分之一。顺着这个线索回 Vitis 工程里翻果然在初始化函数里找到了那句clkwiz(clk_wiz_addr, sample_rate/4)。4.2 intr_dec FS / sample_rate 漏掉了每周期样本数原始代码的逻辑大致是这样的/* 原始版本IP 配置完以后又被软件改了一遍 */ u32 FS 4915200000U; /* 器件采样率 4.9152 GSPS */ double sample_rate 245.76e6; /* 传给函数的采样率 */ u32 intr_dec (u32)(FS / sample_rate); /* 算出 20写进抽取/插值寄存器 */ XRFdc_SetDecimationFactor(RFdcInstPtr, 0, intr_dec); clkwiz(clk_wiz_addr, sample_rate / 4); /* 时钟发生器被配成 61.44 MHz */这段代码的问题在于sample_rate这个变量的语义是混的。它传进来的值是 245.76e6这是 AXI4-Stream 的端口时钟不是样本率。拿 4.9152 GSPS 去除以端口时钟得到的 20 是把每周期 4 个样本这个因素也算进去了的——但 IP 的抽取寄存器要的是纯抽取因子 5不是 20。同时clkwiz那边又除了一个 4把时钟配成了 61.44 MHz。两处错误的方向还不一样一个把数据流拉长一个把时钟压低叠在一起就是你看到的频率差四倍。我把这段代码逻辑画在纸上对了一遍才想通IP 的配置页里已经写死了 5 倍抽取和每周期 4 个样本Vitis 侧只要保证时钟是 245.76 MHz、不要把抽取因子再覆盖一遍就行。原来那句ChangeSampleFreq是 demo 为了演示运行中改采样率留的接口直接调用等于把配置页的东西全推翻。4.3 改完代码重新上板1.508 GHz 与 10 MHz 差频修正后的代码只改了两行但每一行都要理解清楚u32 FS 4915200000U; double sample_rate 245.76e6; /* AXI4-Stream 端口时钟 */ u32 samples_per_cycle 4; /* ADC 每周期 4 个 16 位样本 */ /* 抽取因子要把每周期样本数除掉才是 IP 寄存器要的值 */ u32 intr_dec (u32)(FS / sample_rate / samples_per_cycle); XRFdc_SetDecimationFactor(RFdcInstPtr, 0, intr_dec); /* 结果 5与 IP 配置一致 */ clkwiz(clk_wiz_addr, sample_rate); /* 时钟给足 245.76 MHz */第一行的除法顺序不能反。先算FS / sample_rate得到 20再除samples_per_cycle得到 5如果写成FS / (sample_rate * samples_per_cycle)结果一样但中间值溢出风险不同用 double 更保险。第二行的clkwiz目标值必须是完整的端口时钟不能再除 4因为四倍的关系已经被samples_per_cycle消化掉了。改完重新编译、下载 bit 和 elf频谱仪上 DAC 的输出跳到 1.508 GHz和账本里的预期完全一致。信号源灌 1.510 GHz 进 ADCILA 抓数、导出、MATLAB 按 983.04 MSPS 的采样率画频谱峰值落在 10 MHz。之前的谐波也一起消失了——那些谐波不是模拟链路的问题是数据流被重复采样导致的周期性杂散。注意RF Data Converter 的配置框里填好参数以后Vitis 侧就不该再动抽取和插值。初始化里只保留复位和使能ChangeSampleFreq这种接口除非你确实要在运行中切采样率否则不要调。5. 避坑与排查五个频率对不上的现场记录5.1 现象是改完 IP 频率依旧不对现象在 Vivado 里把抽取因子从 20 改回 5重新生成 bit 流下载频谱仪读数纹丝不动还是 1.502 GHz。原因Vitis 工程里的 elf 没重新编译上电后ChangeSampleFreq又把寄存器覆盖回去了。RF Data Converter 的抽取/插值寄存器是可以在运行中改的软件写入的优先级高于 IP 的初始配置。解决改完 Vivado 必须重新编译 Vitis 工程并下载新的 elf或者干脆把那段重配代码注释掉。我后来的做法是在初始化函数里加一个宏开关调试阶段默认关闭动态重配只在需要扫频的实验里打开。5.2 现象是 MATLAB 频谱整体偏移现象ADC 采回来的数据做 FFT峰值位置和理论差频对不上偏四倍而且镜像也出来了。原因fs参数填的是 AXI4-Stream 端口时钟 245.76 MHz而不是 ADC 实际吐样本的速率 983.04 MSPS。FFT 的频率轴完全由这个参数决定填错就是整体缩放。解决把fs固定成FS / DEC并且把它和 2.2 节的脚本绑在一起改配置就重算。另外reshape之前记得转置否则样本顺序错乱也会造成假镜像。5.3 现象是总线时钟实测值和手算值差一截现象用 100 MHz 参考时钟去数被测时钟量出来 66.41 MHz手算应该是 245.76 MHz 或 122.88 MHz两个都不是。原因测量方法本身有量化误差计数窗口不够长的时候偏差能到百分之几更主要的原因是时钟发生器确实被clkwiz重配成了 61.44 MHz 附近66.41 是这个值加上测量误差。解决计数窗口拉长到毫秒级误差能压到千分之一以内同时不要只量一个点把 clock_out、Fabric 时钟、AXI 总线时钟三个一起量互相印证。我一般是先在 ILA 里看三个计数的比例关系比例对了再谈绝对值。5.4 现象是频率对了但谐波一大片现象把clkwiz那行改成 245.76 MHz 以后DAC 输出频率跳到了 1.508 GHz但频谱仪上出现一大片等间隔的杂散间隔大约 1.92 MHz。原因只改了时钟没改抽取因子。IP 内部按 5 倍处理软件又写了个 20 进去数据流被重复采样等效于在时域上做了零阶保持频域就是 sinc 包络加一系列谐波。解决抽取因子和时钟必须成对修改改一个不改另一个只会把问题从频率错变成频率对但 spectrum 脏。判断方法是看杂散间隔——如果间隔等于基带单音频率基本可以确定是重采样比例错了。5.5 现象是 ILA 波形漂亮却误导判断现象ILA 抓出来的 64 位数据拆开看四段 16 位信号周期都是 32 个时钟看起来完美但实际频率是错的。原因ILA 用的是被测量的那个时钟做采样时钟时钟本身错了它看到的32 个周期只反映相对关系。这是典型的自己证明自己。解决任何涉及绝对频率的结论都必须有一个独立时钟域的参考。我现在的习惯是在工程里常驻一个自由运行的计数器时钟来自板上晶振任何时候都能拿出来做交叉验证比临时改工程快得多。6. 二次开发把时钟账本做成上电自检脚本这套东西调通以后我做的第一件事不是加功能而是把 2.2 节那张表变成代码里的断言。思路很简单上电初始化时把 IP 的实际配置读回来和账本逐项比对任何一项对不上就直接打印告警并停在那儿而不是让它带着错的配置跑起来。RF Data Converter 的驱动提供了读接口抽取因子、插值因子、NCO 频率、采样率都能读回。具体做法分三步。第一步把配置参数集中到一个头文件里FS、DEC、INT、SAMPLES_PER_CYCLE、NCO_FREQ各一个宏所有计算都从这几个宏推出来禁止在业务代码里出现魔法数字。第二步写一个rfdc_self_check()读回 IP 的实际值和宏算出来的期望值比较误差超过 1% 就报错。第三步把 AXI4-Stream 端口时钟也用计数器量一遍和FS / DEC / SAMPLES_PER_CYCLE的结果对比这一步能抓住IP 配置对但时钟发生器被改的情况。改造的时候有个细节值得说不要试图在运行时动态改采样率。demo 里留的那个接口看着方便实际上每改一次就要重算一遍抽取因子、重配一次时钟、重新校验一次数据流任何一处漏了就是前面那五种坑之一。真要做多速率我一般是在 IP 里例化多个通道每个通道固定一个速率用 AXI4-Stream 的 Switch 去选比动态重配稳得多。另一个可以复用的技巧是把 ILA 的触发条件设成计数器溢出也就是每隔固定时间抓一次把抓到的数据自动导出到主机做一次快速 FFT和频谱仪的读数比对。这套流程跑顺以后换个频点、换个插值因子十分钟就能确认链路是通的不用再靠眼睛盯频谱仪。血泪经验是射频链路上配置看着对和实际跑对之间隔着一整套时钟账本账本不平的时候任何一次改动都可能把问题从一个现象推到另一个现象。从那以后我每次上板不管是新工程还是改了一行代码都强制走一遍自检脚本先把四个数采样率、抽取/插值因子、端口时钟、NCO打出来对一遍再谈波形好不好看。希望这套账本能帮到你少在 1.502 和 1.508 之间来回折腾几个晚上。本文还有配套的精品资源点击获取
阅读完成 · 觉得有帮助?