news 2026/10/7 15:45:52

FPGA动态重配置实战:MMCM/PLL时钟频率相位占空比在线调整

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
FPGA动态重配置实战:MMCM/PLL时钟频率相位占空比在线调整

FPGA 开发里有个场景特别常见:系统跑起来了,时钟频率想微调一下,或者相位对不齐导致数据采样出错,再或者占空比不合适让外设时序余量不够。常规做法是改 PLL 参数、重新综合、重新布局布线、重新下载,一套流程下来少说十几分钟,调试效率极低。而动态重配置(Dynamic Reconfiguration)就是来解决这个痛点的——它允许你在 FPGA 运行过程中,直接通过配置接口修改 MMCM/PLL 的分频、倍频参数,实时改变输出时钟的频率、相位和占空比,不用重新编译整个工程。

这篇内容面向已经能跑通基本 FPGA 工程、用过 MMCM/PLL 但还没接触过动态重配置的开发者。我会把 Xilinx 7 系列(Artix-7/Kintex-7 等)上 MMCM 的动态重配置完整流程拆开讲清楚,包括 DRP 接口的时序逻辑、参数计算、状态机设计,以及我在实际调试中踩过的几个坑。读完你应该能自己写出一个可用的动态时钟调整模块。

1. 先搞清楚动态重配置到底改的是什么

1.1 MMCM 内部结构与可重配置寄存器

要理解动态重配置,得先知道 MMCM 内部到底有哪些参数决定了输出时钟。一个 MMCM 的核心结构可以简化为:输入分频器(D)、相位频率检测器(PFD)、电荷泵、环路滤波器、压控振荡器(VCO)、输出分频器(O)、以及每个输出通道独立的小数分频器和相位偏移单元。

输出频率的公式是:

Fout = Fin × (M / D) / O

其中 M 是倍频系数,D 是输入分频系数,O 是输出分频系数。VCO 频率 = Fin × M / D,必须落在 MMCM 允许的范围内(7 系列一般是 600MHz 到 1200MHz,不同速度等级略有差异)。

动态重配置能改的就是这些分频/倍频系数,以及每个输出通道的相位偏移量。这些参数在 MMCM 内部对应一组配置寄存器,通过 DRP(Dynamic Reconfiguration Port)接口读写。DRP 本质上是一个同步的读写端口,类似简单的 SRAM 接口:给地址、给数据、拉使能,等 ready 信号返回。

需要特别注意的是,不是所有寄存器都能在运行时随意改。比如 VCO 频率相关的 M 和 D,改动时 MMCM 会短暂失锁,输出时钟会不稳定甚至停振。而输出分频比 O 和相位偏移的修改相对安全,因为不改变 VCO 工作点。这个区别直接决定了你的重配置策略。

1.2 频率、相位、占空比三者的调整边界

频率调整是最直观的需求。比如你有个 100MHz 的输入时钟,想输出 75MHz 到 150MHz 之间可调。通过改 M 和 O 的组合就能实现,但要注意 VCO 频率必须始终在合法范围内。我的经验是先把 VCO 固定在一个中间值(比如 1000MHz),然后通过改 O 来调输出频率,这样每次改频率不会导致 VCO 重新锁定,稳定性好很多。

相位调整分两种:粗调和细调。粗调是通过改变输出分频器的边沿位置实现,步进通常是 VCO 周期的整数倍;细调则是通过 MMCM 内部的相位偏移单元,步进可以做到 VCO 周期的 1/56(7 系列)。实际项目中如果要做源同步接口的采样对齐,细调相位是刚需。

占空比调整这个需求相对少见,但确实存在。比如某些外设要求时钟高电平时间特别短或特别长。MMCM 本身不直接提供占空比寄存器,但可以通过组合两个相位不同的输出来实现,或者用 MMCM 输出再经过逻辑做占空比整形。纯靠 MMCM 寄存器改占空比的能力有限,这一点要有预期。

下面这张表总结了三种调整方式的关键差异:

调整类型涉及寄存器是否影响 VCO输出是否中断典型步进
频率调整M、D、O改 M/D 会影响改 M/D 会中断取决于 O 值
相位调整相位偏移寄存器不影响不中断VCO/56
占空比调整需组合实现不影响不中断受相位步进限制

1.3 为什么不用外部时钟芯片

有人会问,既然要动态调时钟,为什么不直接用 Si5341 这类可编程时钟芯片?确实,专用时钟芯片在抖动性能和灵活性上更好,但它需要额外的硬件成本、I2C/SPI 配置逻辑,而且增加了板级复杂度。如果你的系统里已经有 FPGA,且对时钟抖动要求不是极端苛刻(比如普通数据采集、显示驱动、通信接口),用 MMCM 动态重配置是性价比最高的方案。省掉一颗芯片、省掉一路配置总线,PCB 也更好布。

2. DRP 接口的时序细节与状态机设计

2.1 DRP 读写时序的完整拆解

Xilinx 7 系列的 MMCM 动态重配置接口信号包括:DADDR(7 位地址)、DI(16 位写数据)、DO(16 位读数据)、DEN(使能)、DWE(写使能)、DCLK(DRP 时钟)、DRDY(就绪)。时序逻辑是:在 DCLK 上升沿,当 DEN 为高时,如果是写操作(DWE=1),DI 上的数据写入 DADDR 指定的寄存器;如果是读操作(DWE=0),DO 上会在几个周期后输出对应地址的数据,同时 DRDY 拉高一个周期表示数据有效。

关键点在于 DRDY 的时序。写操作后 DRDY 通常在第 1 到第 2 个 DCLK 周期拉高,读操作的 DRDY 延迟更大,可能到第 3 到第 4 个周期。你不能假设固定延迟,必须用状态机等 DRDY。我见过有人用固定计数器等 3 个周期就去读 DO,结果在某些速度等级下数据还没准备好,读回来全是错的。

还有一个容易忽略的点:DCLK 的频率不能太高。UG472 里建议 DCLK 不超过 100MHz,实际我用 50MHz 比较稳。如果你用系统时钟直接驱动 DCLK,先确认频率是否在范围内。

2.2 一个可复用的 DRP 状态机实现

下面是我在实际项目中反复使用的一个 DRP 控制状态机,用 Verilog 写的,结构清晰,容易移植:

module drp_ctrl ( input wire clk, input wire rst_n, input wire start, input wire rw, // 1=write, 0=read input wire [6:0] addr, input wire [15:0] wdata, output reg [15:0] rdata, output reg done, // DRP 物理接口 output reg [6:0] DADDR, output reg [15:0] DI, input wire [15:0] DO, output reg DEN, output reg DWE, output wire DCLK, input wire DRDY ); assign DCLK = clk; localparam IDLE = 2'd0; localparam ISSUE = 2'd1; localparam WAIT = 2'd2; localparam FINISH= 2'd3; reg [1:0] state; reg [1:0] wait_cnt; always @(posedge clk or negedge rst_n) begin if (!rst_n) begin state <= IDLE; DEN <= 1'b0; DWE <= 1'b0; done <= 1'b0; wait_cnt <= 2'd0; end else begin case (state) IDLE: begin done <= 1'b0; if (start) begin DADDR <= addr; DI <= wdata; DWE <= rw; DEN <= 1'b1; state <= ISSUE; end end ISSUE: begin DEN <= 1'b0; state <= WAIT; end WAIT: begin if (DRDY) begin rdata <= DO; done <= 1'b1; state <= FINISH; end end FINISH: begin done <= 1'b0; state <= IDLE; end endcase end end endmodule

这个状态机的逻辑很直接:IDLE 等 start,ISSUE 发一个周期的 DEN,然后进 WAIT 死等 DRDY,收到后锁存数据、拉高 done,最后回 IDLE。注意 DEN 只拉高一个周期,这是 DRP 协议要求的,不能持续拉高。

2.3 多寄存器写入的顺序问题

改一个输出频率往往需要写多个寄存器。比如你要改输出分频比,可能需要先写 O 寄存器,再写对应的分频使能位,最后写一个"加载"寄存器让配置生效。这些寄存器之间有顺序依赖,不能乱写。

我的做法是把整个重配置流程做成一个查找表:每个配置项对应一组(地址,数据)对,按顺序存在 ROM 里,状态机依次读出并写入。这样改配置只需要改 ROM 内容,不用动状态机逻辑。对于需要频繁切换几种固定频率的场景,这个方案特别省事。

注意:写寄存器之前一定要先读一遍原始值,确认当前状态。有些寄存器是保留位不能乱写,直接覆盖可能导致 MMCM 行为异常。

3. 参数计算:从目标频率反推寄存器值

3.1 频率参数的完整计算链路

假设输入时钟 Fin = 100MHz,目标输出 Fout = 66.67MHz。计算步骤如下:

第一步,确定 VCO 频率。VCO 必须在 600-1200MHz 之间。选一个合适的值,比如 800MHz。

第二步,算 M 和 D。VCO = Fin × M / D,所以 M/D = 8。如果 D=1,M=8;如果 D=2,M=16。D 的取值范围通常是 1-106,M 是 2-64。选 D=1,M=8。

第三步,算 O。Fout = VCO / O,所以 O = 800/66.67 = 12。O 的取值范围是 1-128,12 合法。

所以最终配置是 M=8,D=1,O=12。验证:100 × 8 / 1 / 12 = 66.67MHz,正确。

但这里有个细节:MMCM 的 M 和 D 寄存器不是直接存这个整数值。实际寄存器里存的是 M-1 或类似偏移值,而且有些系列还有小数分频。具体映射关系必须查 UG472 的寄存器表,不能想当然。我一般会先用 Xilinx 的 Clocking Wizard 生成一个目标频率的配置,然后从生成的代码或报告中反查寄存器值,这样最保险。

3.2 相位偏移的量化计算

相位偏移寄存器的值对应的是 VCO 周期的分数。7 系列 MMCM 的相位偏移精度是 VCO 周期的 1/56。假设 VCO = 800MHz,周期 1.25ns,那么一个相位步进 = 1.25/56 ≈ 22.3ps。

如果你需要 90 度相位偏移,对应周期 1/4,即 1.25ns/4 = 312.5ps。需要的步进数 = 312.5/22.3 ≈ 14。所以相位寄存器写 14。

但要注意,相位偏移是相对于该输出通道的基准边沿,而且不同输出通道的相位偏移是独立配置的。做源同步接口时,通常固定一个通道作为发送时钟,另一个通道做相位偏移作为采样时钟,两者配合使用。

3.3 占空比调整的替代方案

前面说过 MMCM 不直接支持占空比寄存器。如果你的应用确实需要非 50% 占空比,有两个可行方案。

方案一:用两个相位不同的输出做逻辑组合。比如输出 A 相位 0 度,输出 B 相位 90 度,两者做异或,得到的就是 50% 占空比但频率翻倍的时钟;做与或组合可以得到不同占空比。这个方案需要额外的逻辑资源,但精度取决于相位步进。

方案二:MMCM 输出后接一个计数器做占空比整形。比如 100MHz 时钟进来,用计数器数 3 个周期高、7 个周期低,输出 10MHz、30% 占空比的时钟。这个方案简单直接,但输出频率必须是输入频率的整数分频,灵活性差一些。

实际选哪个取决于你的具体需求。如果占空比精度要求高且频率固定,方案二更稳;如果需要动态调整占空比,方案一配合动态相位重配置更合适。

4. 实测中遇到的几个典型问题

4.1 重配置后 MMCM 失锁导致系统跑飞

这是最危险的一个坑。当你修改 M 或 D 寄存器时,VCO 频率会跳变,MMCM 会短暂失锁,输出时钟可能停振几十个周期。如果你的系统逻辑直接依赖这个时钟,就会跑飞。

我的解决方案是:重配置期间把系统逻辑切到备用时钟(比如输入时钟的缓冲),等 MMCM 重新锁定后再切回来。MMCM 有个 LOCKED 信号,重配置后要等 LOCKED 重新拉高才能认为时钟稳定。切换时钟用 BUFGMUX 实现,切换时要确保两个时钟都稳定,否则 BUFGMUX 输出会有毛刺。

如果你只是改输出分频比 O 而不动 M/D,VCO 不变,MMCM 不会失锁,输出时钟只是频率变了但一直有。这种情况下可以不用切时钟,但要注意下游逻辑是否能承受频率突变。

4.2 DRP 时钟域与系统时钟域的交叉

DRP 接口的 DCLK 和你的系统时钟往往是同一个,但 DRDY 返回是异步的(相对于你的状态机)。虽然实际上 DRDY 是由 DCLK 同步产生的,但如果你用不同的时钟驱动状态机和 DCLK,就需要做跨时钟域处理。

我的建议是:DRP 状态机和 DCLK 用同一个时钟,这样最简单也最可靠。如果系统时钟频率太高(超过 100MHz),就分频出一个 50MHz 专门给 DRP 用,状态机也跑在这个时钟域,然后通过握手信号和系统时钟域通信。

4.3 寄存器写入后不生效的排查思路

有时候你写了寄存器,读回来也对,但输出频率就是没变。这种情况我遇到过几次,原因各不相同。

第一次是忘了写"加载"寄存器。有些配置需要最后写一个特定的寄存器位才会真正生效,只写参数寄存器是不够的。

第二次是写顺序错了。某个使能位必须在参数写完之后才能置位,我提前置位了,导致参数没被加载。

第三次最隐蔽:MMCM 的某个输出通道被我在约束文件里设成了固定相位模式,动态重配置改不了。这种情况读寄存器值是对的,但硬件行为不跟随。排查方法是查约束文件里有没有对 MMCM 输出做固定约束。

排查这类问题的通用思路是:先读回所有相关寄存器确认值正确,再检查写顺序是否符合 UG472 的要求,最后查约束和综合属性有没有限制动态重配置。

4.4 动态重配置对时序收敛的影响

动态重配置本身不改变逻辑的时序路径,但它改变了时钟频率,而你的时序约束是按某个频率写的。如果重配置后的频率高于约束频率,时序可能不满足。

我的做法是:按最高可能频率写时序约束,确保所有频率下都收敛。如果最高频率下资源紧张,就分两个约束场景,但这样综合实现会复杂很多。实际项目中,如果频率调整范围不大(比如 ±20%),按最高频率约束一次就够了。

5. 一个完整的动态频率调整实例

5.1 系统架构与模块划分

假设我们要做一个系统:输入 50MHz 晶振,输出一个频率可在 40MHz 到 120MHz 之间动态调整的时钟,调整通过 UART 命令控制。系统包含以下模块:

  • MMCM 原语(带 DRP 接口)
  • DRP 控制状态机
  • 参数查找表(ROM,存储几组预设频率对应的寄存器值)
  • UART 接收模块
  • 时钟切换逻辑(BUFGMUX)
  • 顶层控制逻辑

MMCM 的配置先用 Clocking Wizard 生成一个基础配置(比如输出 100MHz),然后在代码里手动例化 MMCM 原语并接出 DRP 接口。Clocking Wizard 生成的代码默认不开放 DRP,需要手动修改或直接用原语例化。

5.2 关键代码片段与注释

MMCM 原语例化时,DRP 相关端口要接出来:

MMCME2_ADV #( .BANDWIDTH ("OPTIMIZED"), .CLKFBOUT_MULT_F (10.0), .CLKIN1_PERIOD (20.0), .DIVCLK_DIVIDE (1), .CLKOUT0_DIVIDE_F (5.0), .STARTUP_WAIT ("FALSE") ) mmcm_inst ( .CLKIN1 (clk_in), .CLKFBIN (clkfb), .CLKFBOUT (clkfb), .CLKOUT0 (clk_out_raw), .LOCKED (locked), // DRP 接口 .DADDR (drp_addr), .DI (drp_di), .DO (drp_do), .DEN (drp_den), .DWE (drp_dwe), .DCLK (drp_clk), .DRDY (drp_drdy), .PSCLK (1'b0), .PSEN (1'b0), .PSINCDEC (1'b0), .PSDONE () );

注意 PSCLK/PSEN/PSINCDEC 是相位偏移的专用接口,如果你用 DRP 改相位寄存器,这几个端口可以不用。但如果你用 PS 接口做动态相位调整,那就是另一套逻辑了。两种方式各有优劣:PS 接口简单但只能调相位,DRP 灵活但逻辑复杂。

5.3 上板验证与调试方法

上板后怎么确认重配置成功了?最直接的方法是用示波器或频率计测输出时钟。如果没有仪器,可以用 FPGA 内部的计数器测频:用一个已知的参考时钟去数待测时钟的周期数,通过 UART 读回来。

我的调试习惯是先在仿真里验证 DRP 状态机的时序,确认读写时序正确,再上板测实际频率。仿真时可以用 MMCM 的行为模型,Xilinx 的 unisim 库里有,能模拟 DRP 行为。

上板后如果频率不对,先读回寄存器值确认写入成功,再检查 MMCM 的 LOCKED 信号是否正常。如果 LOCKED 一直不拉高,说明参数组合非法(VCO 超出范围或分频比越界),需要重新计算参数。

5.4 从 40MHz 切到 120MHz 的实测数据

我用一块 Artix-7 开发板做了实测。输入 50MHz,VCO 固定在 1000MHz。40MHz 对应 O=25,120MHz 对应 O=8.33(实际取整为 8,输出 125MHz,误差 4%)。切换时 MMCM 不失锁,因为 VCO 没变。切换前后用计数器测频,误差在 0.1% 以内,满足大多数应用需求。

切换耗时方面,一次完整的 DRP 写入(3 个寄存器)大约需要 10 个 DCLK 周期,50MHz 下就是 200ns。加上状态机开销,整体切换时间在 1 微秒以内。这个速度对于大多数动态调频场景都够用了。

6. 几个容易被忽略的约束与注意事项

6.1 约束文件里必须放开的设置

如果你的工程用了 Clocking Wizard,它生成的 XDC 里可能包含对 MMCM 的固定约束。要支持动态重配置,需要确认以下几点:

  • MMCM 的 CLKOUT 没有设成固定相位模式
  • 没有对 MMCM 内部寄存器做位置约束
  • 时序约束覆盖了所有可能的输出频率

我一般会在 XDC 里显式声明 MMCM 输出时钟的 create_clock,频率按最高值写,然后对下游逻辑做时序约束。如果频率范围跨度大,可以用 set_clock_groups 把不同频率场景隔开,但这样会放松时序检查,要谨慎使用。

6.2 动态重配置与部分重配置的区别

这两个概念容易混。动态重配置(这里特指 MMCM 的 DRP)是改时钟参数,不涉及逻辑功能变化。部分重配置(Partial Reconfiguration)是改 FPGA 部分区域的逻辑功能,是更重量级的技术。两者可以结合使用,但解决的问题完全不同。本文只讨论 MMCM 的 DRP。

6.3 不同 FPGA 系列的差异

本文以 Xilinx 7 系列为例,但不同系列差异不小。UltraScale 系列的 MMCM 寄存器映射和 7 系列不同,DRP 接口时序也有变化。Intel(Altera)的 FPGA 用的是 ALTPLL,重配置接口叫 reconfig,协议和 Xilinx 完全不同。如果你用的是其他系列,寄存器地址和计算方式必须查对应系列的手册,不能照搬本文的数值。

高云、安路等国产 FPGA 也有类似的动态配置功能,但文档相对少一些,调试时可能需要更多试错。建议先用厂商提供的 IP 生成工具生成基础配置,再研究其寄存器映射。

6.4 实际项目中的取舍建议

最后说点实在的。动态重配置不是所有项目都需要。如果你的系统时钟固定不变,老老实实用 Clocking Wizard 生成一个固定配置就行,简单可靠。只有当确实需要运行时改变时钟参数(比如多协议兼容、动态功耗管理、自适应采样)时,才值得引入 DRP 逻辑。

引入 DRP 的代价是:额外的逻辑资源、更复杂的约束、更长的调试周期。我一般会先评估是否能用固定时钟加时钟切换(多个 MMCM 输出切 BUFGMUX)来替代。如果预设频率种类少(比如 2-3 种),多 MMCM 加时钟切换更简单。只有当频率需要连续可调或种类很多时,DRP 才是最优解。

另外,动态重配置期间系统的行为要仔细设计。如果重配置会导致时钟短暂中断,必须确保系统能安全度过这个窗口。我在一个数据采集项目里就遇到过:重配置时 ADC 采样时钟停了,导致 FIFO 溢出。后来改成先停采集、再重配置、再恢复采集,问题才解决。这种系统级的配合,比 DRP 逻辑本身更考验设计功力。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/7 15:45:23

Eclipse时代JavaEE图书管理系统:Struts2+JDBC课设项目拆解与避坑指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/7 15:44:54

遥感影像多类别分割数据集实操:从检查到训练的完整指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/7 15:44:09

Isaac Sim机器人仿真:摄像头与传感器添加配置实战指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/7 15:44:06

SAP HANA SQLScript 存储过程开发:计算下推与性能优化实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/7 15:43:09

遥感图像语义分割UNet实战:从数据标注到推理优化全流程

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/7 15:43:09

ROS2 + Gazebo阿克曼小车搭载Livox MID-360雷达仿真搭建指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华