"工程可落地"的无毛刺时钟切换(glitch-free clock switching)讲解与可用代码,分别覆盖ASIC通用做法 与FPGA厂商原语做法 ,并配上时序/CDC约束与验证要点。
一、问题本质与设计目标
把 clk_out
从 clk0
切到 clk1
(或反向)时,如果简单用 assign clk_out = sel ? clk1 : clk0;
,一旦 sel
在任意时刻翻转、恰逢两路时钟处于不同电平/相位,就会产生:
- runt pulse(短脉冲) :违反下游触发器的最小高/低脉宽约束;
- 毛刺与双沿:造成"多计数/少计数"、状态机跳变等隐患;
- CDC风险 :
sel
通常与两路时钟都异步,若直接驱动组合 MUX,会让毛刺概率放大。
设计目标 :切换时实现"break-before-make "(先关后开),只在低电平窗口 改变门控,使 clk_out
要么连续、要么短暂停(保持低电平)再平滑接入新时钟,但绝不产生短脉冲。
二、ASIC/通用RTL方案(And-Or 门控 + 低电平更新 + 互斥握手)
核心思路:
- 将两路时钟分别门控 成
gclk0 = clk0 & en0
、gclk1 = clk1 & en1
; - 输出
clk_out = gclk0 | gclk1
; en0
/en1
互斥 ,并且只在相应时钟的低电平边界更新 (常用negedge clk
或 ICG 低电平锁存),从而保证开/关都发生在"安全窗口"。
典型可综合参考实现(ASIC/标准单元库/不走FPGA LUT 时钟网络的前提):
verilog
// ===============================================================
// Glitch-free 2:1 Clock Switch (ASIC/通用RTL参考)
// - 先关后开(break-before-make)
// - 在各自时钟的 negedge 更新 en,保证改变发生在低电平窗口
// - 对 sel 进行双级同步(各自域)
// ===============================================================
module clk_switch_gf (
input wire rst_n, // 异步复位,低有效
input wire clk0, // 时钟0(可与clk1异步/不同频)
input wire clk1, // 时钟1
input wire sel_async, // 1->选择clk1,0->选择clk0(异步控制)
output wire clk_out
);
// 1) 将 sel 分别同步到两个时钟域,降低亚稳态风险
reg sel0_d1, sel0_d2;
reg sel1_d1, sel1_d2;
always @(posedge clk0 or negedge rst_n) begin
if (!rst_n) {sel0_d1, sel0_d2} <= 2'b00;
else {sel0_d1, sel0_d2} <= {sel_async, sel0_d1};
end
always @(posedge clk1 or negedge rst_n) begin
if (!rst_n) {sel1_d1, sel1_d2} <= 2'b00;
else {sel1_d1, sel1_d2} <= {sel_async, sel1_d1};
end
wire sel0 = sel0_d2; // 在clk0域观察到的选择(0=>clk0)
wire sel1 = sel1_d2; // 在clk1域观察到的选择(1=>clk1)
// 2) 互斥门控en,在各自时钟的negedge更新(低电平窗口)
// break-before-make:新使能只有在另一边彻底关闭后才能拉起
reg en0, en1;
// 默认上电选择clk0(可按需调整)
always @(negedge clk0 or negedge rst_n) begin
if (!rst_n) en0 <= 1'b1; // 上电默认clk0
else en0 <= (~sel0) & (~en1); // 只有当选择clk0 且 en1已关 才能开
end
always @(negedge clk1 or negedge rst_n) begin
if (!rst_n) en1 <= 1'b0; // 上电默认clk1关闭
else en1 <= ( sel1) & (~en0); // 只有当选择clk1 且 en0已关 才能开
end
// 3) 时钟门控与汇合(ASIC请用ICG/CLKGATE原语替换 & 与 |)
wire gclk0 = clk0 & en0;
wire gclk1 = clk1 & en1;
assign clk_out = gclk0 | gclk1;
endmodule
关键点说明
- 互斥保证 :
en0 <= ~sel0 & ~en1
与en1 <= sel1 & ~en0
形成"你关我才开"的互锁。理论上可用形式验证/断言证明!(en0 & en1)
永真。 - 低电平更新 :在
negedge clk
改变enX
,意味着enX
的翻转发生在clkX
低电平期间,不会截断高电平造成短脉冲。 - 允许短暂停顿 :切换窗口内可能出现
en0=en1=0
(输出保持低电平若干 ns/周期),这不是毛刺 ,是安全空档。 - ICG/库原语 :ASIC务必以**集成时钟门控单元(ICG)**替代
&
、|
,从而满足架构/版图上的时钟树规则与占空比/脉宽保障。 - 边界场景 :若被关闭的时钟卡死在高电平 导致
negedge
不来,上述算法会一直保持输出低电平 (安全降级)。若要求"输入卡死也要强制切走",需在ICG/库里使用带"停摆忽略 "能力的专用门控/切换单元,或引入看门狗辅助。
三、FPGA实现的工程做法(强烈建议优先用厂商原语)
在FPGA中禁止 用 LUT/组合逻辑直接门控时钟或在Fabric里"与/或",应使用专用全局时钟资源 。各家芯片都提供了可无毛刺切换的全局时钟 MUX/门控原语:
1) Xilinx(7-Series/UltraScale/UltraScale+)
推荐 :BUFGCTRL
或 BUFGMUX_CTRL
/ BUFGCE
最稳妥的做法是用 CE 互斥 实现"先关后开",把选择握手逻辑产生的 en0/en1
直接接到 BUFG 的使能脚:
verilog
// 同步/互斥逻辑与你在ASIC方案一致(建议仍做双级同步+negedge更新)
// 这里展示用 BUFGCTRL 做无毛刺切换的骨架:
wire en0, en1; // 互斥门控(同上文逻辑生成)
BUFGCTRL #(
.INIT_OUT(1'b0) // 上电输出默认值
) u_bufgctrl (
.I0 (clk0),
.I1 (clk1),
.S0 (1'b1), // 置1:忽略S,完全由CE控制
.S1 (1'b1),
.CE0 (en0), // 只使能一路
.CE1 (en1),
.IGNORE0(1'b0),
.IGNORE1(1'b0),
.O (clk_out)
);
要点:
S0=S1=1
,让CE0/CE1
完全决定输出;做到"break-before-make "即可无毛刺切换,不要求两路同频同相。- 若输入时钟可能卡死 ,UltraScale 家族还可利用
BUFGCTRL
的更高级用法(IGNORE
等)实现"卡死时强制切走",具体参照器件文档。 - 如果只是在一条时钟上启停 ,用
BUFGCE
就够;两路切换仍建议BUFGCTRL
。
2) Intel(Altera)
使用 Clock Control Block (老名 ALTCLKCTRL
),选择 glitch-free switchover 选项。原则同上:
- 让工具把你的门控/切换逻辑映射到全局时钟控制块;
- 互斥 CE + 先关后开;
- 具体端口名/参数在不同代器件略有差异(Cyclone/Arria/Agilex),用 IP 向导生成后把CE互斥逻辑接上即可。
3) Lattice(ECP5、CrossLink-NX、MachXO等)
使用器件提供的 全局时钟控制/多路选择原语 (例如 CLKMUX2
、ECLK
相关原语、DCCA
/CLKDIVF
等)。同样遵循:
- 不要 用 LUT 对时钟
&
或|
; - 使用官方时钟MUX/门控资源,并用互斥 CE 驱动;
- 具体原语名与接法以器件 TRM 为准。
四、时序与CDC约束(必须做)
-
Clock Groups :
clk0
与clk1
大多无关/异步,在 STA 里:set_clock_groups -asynchronous -group {clk0} -group {clk1}
或在 Vivado 用set_clock_groups -exclusive
(取决于你的语义:确实不会同时到下游)。
-
派生/生成时钟 :把
clk_out
定义为生成时钟,并指明它与激活源之间的关系(静态检查要知道这个树)。 -
选择信号路径 :
sel_async -> sel_sync
的路径设为CDC/false_path,例如set_false_path -from [get_ports sel_async] -to [get_pins sel0_d1/D sel1_d1/D]
或用工具自带 CDC 例外标注。
-
最小脉宽约束 (ASIC尤甚):对
clk_out
设置set_min_pulse_width -high/-low
,匹配目标工艺/库要求。 -
保持互斥 :为
en0 & en1
添加综合/形式约束或 SVA 断言,禁止同时为1;并考虑dont_touch
保护关键互锁逻辑,避免综合优化破坏。
五、功能验证建议(强烈推荐)
-
随机切换 :在仿真里对
sel_async
做随机抖动与突发翻转(远高于两路时钟频率),观察clk_out
波形:- 不出现小于阈值的高/低脉宽(可写断言主动检查)。
-
频比/相位覆盖:覆盖常见组合:同频同相/同频反相/不等频/一快一慢。
-
停摆场景 :将被关闭的时钟"卡高/卡低",确认
clk_out
行为(安全降级或利用原语强制切走)。 -
SVA断言样例(思路示意):
assert never (en0 && en1);
assert clk_out
的任一高脉宽 ≥ T_min_high,低脉宽 ≥ T_min_low(基于周期测量模块或专用checker)。
六、常见坑与规避
- 在Fabric里用门电路直接改时钟:在FPGA是大忌,务必用全局时钟原语。
- 只做组合MUX :
assign clk_out = sel?clk1:clk0;
必出毛刺。 - 同步缺失 :
sel
不双级同步,容易把亚稳态直接"扩散"到门控。 - 非互斥CE:两路 CE 可能瞬间同时为1(或先开后关),会叠加出"怪脉冲"。
- negedge更新被禁止 :若公司规范不允许
negedge
触发器,可改为低电平透明锁存的 ICG 方案,或者在FPGA用带"glitchless"承诺的原语,仍保证"低电平窗口更新"。
七、一个更"厂味"的Xilinx落地骨架(推荐在FPGA中用)
把上文"互斥CE + 双域同步"的思想直接喂给 BUFGCTRL
,实现最标准的无毛刺切换:
verilog
module xilinx_bufgctrl_switch (
input wire rst_n,
input wire clk0,
input wire clk1,
input wire sel_async, // 1->clk1, 0->clk0
output wire clk_out
);
// 双域同步(同上)
reg sel0_d1, sel0_d2, sel1_d1, sel1_d2;
always @(posedge clk0 or negedge rst_n) begin
if (!rst_n) {sel0_d1, sel0_d2} <= 2'b00;
else {sel0_d1, sel0_d2} <= {sel_async, sel0_d1};
end
always @(posedge clk1 or negedge rst_n) begin
if (!rst_n) {sel1_d1, sel1_d2} <= 2'b00;
else {sel1_d1, sel1_d2} <= {sel_async, sel1_d1};
end
wire sel0 = sel0_d2;
wire sel1 = sel1_d2;
// 在各自域低电平窗口更新互斥CE(与ASIC方案一致)
reg en0, en1;
always @(negedge clk0 or negedge rst_n) begin
if (!rst_n) en0 <= 1'b1;
else en0 <= (~sel0) & (~en1);
end
always @(negedge clk1 or negedge rst_n) begin
if (!rst_n) en1 <= 1'b0;
else en1 <= ( sel1) & (~en0);
end
// 用BUFGCTRL做最终无毛刺切换(CE互斥,S固定=1)
BUFGCTRL #(.INIT_OUT(1'b0)) u_bufgctrl (
.I0(clk0), .I1(clk1),
.S0(1'b1), .S1(1'b1),
.CE0(en0), .CE1(en1),
.IGNORE0(1'b0), .IGNORE1(1'b0),
.O(clk_out)
);
endmodule
这套在 7-Series/UltraScale(+) 都是"顺着工具"的推荐用法;Intel/Lattice 用各自的 Clock Control Block 同理迁移。
八、结语(选型建议)
- FPGA项目 :优先 用官方时钟控制原语 (BUFGCTRL/ALTCLKCTRL/CLKMUX2...),在其外围用互斥CE + 低相位更新 + 双域同步的模板即可。
- ASIC项目 :用ICG + 低电平锁存 的门控结构替代
&/|
,维持同样的状态机互斥与更新时机;在STA里做好生成时钟 、最小脉宽 与时钟组隔离约束。 - 若有"输入卡死也要切 "的强需求,选用带忽略/强制切换 能力的专用单元(不同工艺/FPGA原语的具体脚位名称不同),或增加看门狗配合强制降级策略。