尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

FPGA数字锁相环设计实战:从原理到Vivado仿真与调试

FPGA数字锁相环设计实战:从原理到Vivado仿真与调试 做FPGA这行时钟就是命脉。你可能遇到过这种情况两个板卡之间要传数据结果主时钟分频后相位对不齐或者ADC采样率和上游数据率之间有偏差怎么调都差那么几个ns再或者电机控制的PWM相位总在抖动。这些场景靠手动调PLL参数或者加长走线去“磨”相位往往磨到怀疑人生。我个人的习惯是遇到这种相位和频率同步的问题直接上一个数字锁相环DPLL用FPGA内部逻辑把同步这件事变成可编程、可复现的工程操作。这篇内容我给的是一个可以直接拿来跑的DPLL工程方案配套Vivado仿真代码和完整的参数计算过程。无论你是刚入门FPGA、在“Vivado仿真如何提高速度”“fpga定点数处理”上卡过壳还是已经在做fpga图像处理、仪器同步、马达控制这类需要时钟对齐的项目这套思路都适用。我会从为什么用DPLL、每个模块怎么设计、参数怎么算到仿真怎么搭、排查怎么下手全部讲清楚。1. 项目整体设计与思路拆解1.1 为什么在FPGA里做数字锁相环而不是直接用IP核先说说方案选择的理由。Xilinx/Vivado里确实有现成的PLL和MMCM硬核它们可以完成时钟倍频、分频和基本的相位调整。但这些模拟锁相环的相位调整精度是固定的比如TCLK_OUT_JITTER、CLKOUT1_PHASE_SHIFT这些参数最小步进受限于VCO压控振荡器的分辨率而且在系统运行过程中你不能动态改变相位关系。我这里说的数字锁相环是完全用可编程逻辑搭建的环路它的核心结构基本固定鉴相器Phase DetectorPD检测参考信号和本地输出信号之间的相位差。环路滤波器Loop FilterLF对相位差信号做滤波生成控制字。这一步决定了环路的带宽、稳定性和动态响应。数控振荡器Numerically Controlled OscillatorNCO用控制字调整相位累加器的步长产生本地输出信号。对比模拟PLLDPLL在FPGA里的优势是“越用越香”的环路滤波器系数是寄存器里的数字随时改不需要换电阻电容环路带宽可以做到很低比如几十赫兹可以在强噪声背景下提纯信号还有就是温度漂移几乎为零因为它本质是算法不是器件特性。1.2 系统架构与模块划分整个DPLL我习惯划分为三个模块加一个顶层顶层模块 dpll_top ├── phase_detector 鉴相器 │ └── 乘法器型鉴相 低通滤波 ├── loop_filter 环路滤波器 │ └── 二阶环路比例积分 └── nco 数控振荡器 └── 相位累加器 正弦查找表鉴相器我选的乘法器型而不是异或门型或边沿型。原因是乘法器型输出的相位差信号与相位差成余弦关系配合环路滤波器可以做到很高的精度而且它对输入信号的占空比不敏感尤其适合后面要做图像同步、数据时钟恢复这类场景。异或门型实现简单但谐波分量大边沿型适合方波但精度受时钟采样率限制。NCO部分实际上就是一个直接数字频率合成器DDS的相位累加器结构。它由相位累加器和波形存储表组成控制字K的物理含义是每一个系统时钟周期相位累加器增加多少度数。1.3 语言选择与开发环境代码我用Verilog工程基于Vivado。这里解释下为什么不做成浮点运算FPGA里浮点DSP资源虽然越来越多但锁相环的环路迭代需要小步长、高速率、低延迟的反馈控制浮点延迟太大定时闭环很难收敛。所以关键路径上的相位差、环路滤波系数、频率控制字全部使用自定义Q格式定点数位宽设计按需定制这是FPGA工程师必须过的一道坎。仿真平台我做了两件比较讲究的事第一参考信号用$sin函数发生器在testbench里生成这样我可以精确控制频率偏移和信噪比第二顶层留了频率锁定标志信号lock_flag仿真时直接观察拉高时间判断环路锁定情况不用肉眼看正弦波形。2. 核心模块设计与参数计算2.1 鉴相器设计乘法器型的原理与实现细节乘法器型鉴相的原理一句话把本地NCO输出正弦信号和输入参考信号相乘积化和差后得到两路分量一路是两信号的相位差直流分量另一路是两倍频分量后面滤波器滤掉高频即可。本地NCO输出的信号假设是sin(ω0·t θ_out)输入参考是sin(ω0·t θ_in)相乘后sin(A)·sin(B) 0.5·cos(A-B) - 0.5·cos(AB)这里的A-B就是相位误差。当环路锁定后A-B趋近于0鉴相器输出直流分量为0.5·cos(0) 0.5考虑量化会有量化噪声。Verilog实现时注意两个点乘法结果的位宽处理、直流分量的提取。我在工程里用的是36位乘法器18bit × 18bit乘法后先做符号扩展再截位把输出规整到16bit定点格式。高频分量cos(AB)会用同步积分CIC抽取滤波器的方式在下一级处理掉没有单独插一个FIR核。为什么这么做因为后面环路滤波器本身也有平均作用省掉一个IP核综合面积和时序压力都小很多仿真速度也快一截。2.2 环路滤波器设计数字PI参数计算这一步决定成败环路滤波是整个DPLL的灵魂它决定了环路带宽、捕获范围和噪声抑制能力。我用的是比例积分PI结构的二阶环路。数字PI的差分方程是y[n] y[n-1] Kp·(x[n] - x[n-1]) Ki·x[n]其中x[n]是鉴相器输出的相位差y[n]是滤波后的控制字。参数设计思路我是这样走的先定模拟域指标再双线性变换到数字域。假设我们需要环路自然角频率ω_n 2π·100 628 rad/s阻尼系数ζ 0.707临界阻尼锁定快且过冲小。模拟PI滤波器的传递函数F(s) (s·τ2 1) / (s·τ1)和标准二阶系统对比可得τ1 Knco / (ω_n²)τ2 2ζ/ω_n - 1/(Knco·Kpd)。Kpd是鉴相器增益Knco是NCO增益。为了简化实际工程中我常用一个更暴力的整定方式固定Ki为很小的一个值决定稳态精度Kp为Ki的20到50倍决定跟踪速度然后用仿真观察锁定时间通过调整Kp、Ki来压缩环路建立时间至目标范围。这里给出一个我测试可用的起点// 环路滤波器参数配置 localparam Kp 16d800; // 比例系数 localparam Ki 16d20; // 积分系数注意这个参数和环路采样率强相关。系统时钟100MHz环路滤波器运行在1MHz速率下可以先按这个数量级跑后续按需微调。参数整定后务必把环路数字增益归一化Kp、Ki都带符号左移/右移由定点小数位决定我用的是Q1.15格式存储系数乘法器后第16位开始右移截位防止增益过大导致NCO频率一字跳变太大。2.3 数控振荡器NCO与定点数处理NCO的实现基于相位累加器。相位累加器的位宽我取了32位输出频率由频率控制字fword和系统时钟f_sys决定f_out fword / 2^32 · f_sys反推fword f_out · 2^32 / f_sys。比如要产生1MHz的信号系统时钟100MHzfword 1_000_000 × 4_294_967_296 / 100_000_000 ≈ 42_949_673这个值直接写死在初始值中并在环路滤波器的输出上做累加即最终频率控制字 初始字 环路输出。这样环路既能快速拉到初始频率附近又能精细修正剩余频差。为什么位宽选32而不是更小相位累加器位宽越大频率分辨率越高。100MHz系统时钟下32位累加器的频率分辨率约为0.023Hz足够满足大多数应用。定点数处理的重头戏在相位到幅度的转换我用了一个16bit寻址、16bit输出的正弦查找表正弦表存了1/4周期通过象限映射得到完整正弦波既节省BRAM资源又保证SFDR足够好。3. 实战过程从RTL设计到Vivado仿真验证3.1 顶层模块与接口定义工程顶层代码如下接口上把相位误差和锁定标志都引出来方便外部监控和联合仿真module dpll_top #( parameter DATA_W 16, parameter PHASE_W 32 )( input wire clk, // 系统时钟 input wire rst_n, // 异步复位 input wire signed [DATA_W-1:0] ref_signal, // 输入参考信号 output wire signed [DATA_W-1:0] nco_out, // NCO输出信号 output wire signed [PHASE_W-1:0] phase_err, // 相位误差观测 output reg lock_flag // 锁定标志 );lock_flag在设计中很重要它通过检测相位误差的绝对值在连续TIME_THRESHOLD个周期内都低于某个阈值例如16d100延迟一段时间后拉高防止假锁造成误触发。我测试下来锁定时间随频率差增大而增大但一般小于2ms。3.2 Testbench设计与激励生成testbench的结构决定了验证能不能一次性通过。我的做法分三步第一步先写一个62.5MHz的基准时钟发生器16ns周期用initial块里forever #8 clk ~clk;实现。第二步生成输入参考信号。这里我刻意把待同步信号的频率设置成和NCO初始频率错开一点比如NCO初始字对应1.001MHz输入信号则设置为1.003MHz模拟实际系统中外部源频率漂移的场景// 生成1.003MHz参考信号 wire signed [15:0] ref_signal; reg [31:0] phase_ref 32d0; always (posedge clk) begin // fword_ref 1_003_000 * 2^32 / 100_000_000 ≈ 43_078_754 phase_ref phase_ref 32d43078754; end assign ref_signal sin_lut[phase_ref[31:16]]; // 复用同一个查找表第三步记录phase_err和nco_out在锁定后打印$display(LOCKED)并用$fwrite把波形数据写入文本文件方便之后用Python画图分析相位差收敛曲线。这一条是很多教程忽略的但实际调环路时能省大量时间。3.3 仿真波形分析与锁定过程观察跑完仿真后第一个看的是nco_out和ref_signal的波形。数字锁相环锁定后两者应保持同频相位差恒定理想情况下趋近0。实际操作中nco_out会有少量相位抖动这取决于环路带宽和输入噪声水平一般锁定时相位误差在±0.005rad以内就说明设计合格。第二个观测点是phase_err的阶跃响应曲线。理想情况是一条衰减振荡曲线超调约10%振荡1到2个周期后进入稳态误差带。如果相位误差一直无法收敛、缓慢发散或出现极限环说明环路增益过大或NCO字长导致非线性。第三个是看锁定时间。测试中频差约2kHz时锁定一般在0.5ms左右完成约50000个系统时钟周期这个值对于一般同步场景完全够用。如果嫌慢可以把Kp调大但注意过冲会同步增大环路的相位裕度也会下降得自己权衡。3.4 复位与跨时钟域处理在给工程加复位信号时要特别注意亚稳态处理。FPGA的复位信号如果不做同步释放极易在释放瞬间产生亚稳态导致NCO相位累加器出现毛刺表现为输出频率跳变。我的代码里强制对rst_n做了两级同步释放reg [1:0] rst_sync; always (posedge clk, negedge rst_n) begin if (!rst_n) begin rst_sync 2b00; end else begin rst_sync {rst_sync[0], 1b1}; end end wire rst_n_sync rst_sync[1];核对环路中所有模块都使用同步后的rst_n_sync。按规范讲话异步复位同步释放这种设计即使不完全消除亚稳态也能把发生概率压到极低我在系统的好几个项目中都验证过了。4. 常见问题与排查技巧实录4.1 环路无法锁定怎么办这是初学者最容易卡住的地方。我排查的顺序是固定的每步都能精确定位问题确认鉴相器极性。如果环路负反馈接反了环路永远不会锁定相位误差会持续积分发散。检查方法很简单断开环路输出手动给固定频率控制字观察相位差变化方向确认负反馈关系是否存在。确认增益是否合理。Kp、Ki太小会让拉入时间过长10ms仿真看不到锁定就误以为失败Kp、Ki太大则会引发振荡相位误差呈正弦形状不能收敛。建议做扫参仿真把Kp做成参数跑10个候选值观察锁定时间和超调量选出最合适的。检查NCO初始频率和参考频率偏差是否超出捕获带。PI控制器的捕获带有限频差超过一定范围就需要靠缩小初始频率差来解决。实际调试中我会先用$display打印初始频差然后把NCO初始字改为接近参考频点再跑用“两步锁定”法处理电调谐覆盖范围不足的系统。看定点数溢出。环路积分项如果累加溢出NCO会瞬间跳变一大步表现为锁定完全失效。给环路滤波器的正向通路加饱和限制Saturation而不是普通截断可以避免这个问题。这也是“fpga定点数”实际工程中最容易埋雷的点之一。4.2 仿真跑得慢怎么提速这个问题几乎每个人都问过。Vivado自带的xsim在环路仿真中确实不快尤其是当输入信号是高载波频率时步长被载波周期限制导致仿真时间步数巨大。我的技巧有三个合理设置系统时钟测试DPLL时不必一上来就跑真实载波频率可以先设一个低速基准频率验证环路行为再逐步提速到真实工作频率仿真速度差距能有十倍。关闭部分波形信息xsim加载所有内部信号会拖慢仿真。我通常只保存phase_err、nco_out、lock_flag这几个关键信号必要时把数据用$fwrite落盘。使用-onfinish quit输出日志批量跑参数扫描时效率高得多。如果确实需要大数据量仿真可以换用VCS或ModelSim但这些工具和主流EDA环境集成度更高一般大规模验证才会考虑。就DPLL这个量级xsim把波形裁剪好其实完全够用。4.3 综合实现与上板调试的坑仿真过了不代表上板就能跑Vivado在综合实现阶段也有经典坑位。最典型的是“Vivado生成比特流失败”大多是时序约束没写对。DPLL是一个反馈环路NCO的相位累加器到环路滤波器的路径不能有过度松弛的约束否则很容易出现组合逻辑毛刺。我的惯例是给整个DPLL模块设置create_clock约束并且对环路相关的关键信号设置set_false_path或set_max_delay确保静默路径不会被错误约束。具体到Vivado工程我在约束文件里加了这样一段create_clock -name sys_clk -period 10.000 [get_ports clk] set_multicycle_path -from [get_cells -hier -filter {NAME ~ *loop_filter*/Kp_reg*}] -to [get_cells -hier -filter {NAME ~ *nco*/phase_acc_reg*}] 2另外提醒一点上板调试时务必用ILA集成逻辑分析仪抓phase_err和nco_out而不要只靠示波器看波形。因为示波器看到的原始信号往往掺杂噪声和谐波而phase_err能直观反映环路收敛情况。用ILA设个触发条件lock_flag拉低时抓数据基本能一次定位问题。4.4 锁相环在实际项目中的应用扩展数字锁相环远不止是给正弦波做同步。我在这里给几条可以顺延的思路直流电机速度环把编码器脉冲作为参考源NCO输出作为速度指令环路锁定后电机速度精确跟随给定值。图像行同步图像采集系统中像素时钟与上游信号存在相位偏差时用DPLL恢复对齐后的像素时钟配合FPGA图像处理流水线做去马赛克、插值等操作。电力系统并网锁相三相电网电压相位跟踪用同步旋转坐标系的锁相环SRF-PLL实现基本原理和这里的DPLL一致只是鉴相器变成Clark/Park变换后的q轴分量。频率合成器NCO输出加一个分频器和DAC就可以做出一个高分辨率的参考频率源替代模拟PLL的VCO这在测试仪器中非常常见。做扩展时注意一点不同应用对环路响应的要求差异极大。电网锁相要求抗谐波能力强环路带宽就要做窄电机速度环要求动态响应快带宽自然要宽。没有一套参数走天下的说法这就是FPGA工程师要反复扫参、仿真验证的核心原因。最后分享一点调试心得数字锁相环这个东西仿真跑通不算完真正上板调试才会遇到它的脾气。我个人的经验是永远把“观测点”留足。工程里我只留了phase_err一个观测信号后来升级为把phase_err的均值、方差和瞬时值同时引出配合Vivado的ILA触发调试效率提升了不止一个档次。另一个心得是改参数时一次只改一个。环路参数是强耦合的Kp和Ki同时乱调出了问题根本不知道是谁的锅。这个习惯在我后续做很多算法工程时都受益算是做FPGA算法的一种通用修养了。如果你按这个流程搭的DPLL能在一小时内锁定那说明你已经掌握了一套可以复用的数字同步设计方法论。
返回列表