FPGA基础 -- 无毛刺时钟切换(glitch-free clock switching)

"工程可落地"的无毛刺时钟切换(glitch-free clock switching)讲解与可用代码,分别覆盖ASIC通用做法FPGA厂商原语做法 ,并配上时序/CDC约束与验证要点

一、问题本质与设计目标

clk_outclk0 切到 clk1(或反向)时,如果简单用 assign clk_out = sel ? clk1 : clk0;,一旦 sel 在任意时刻翻转、恰逢两路时钟处于不同电平/相位,就会产生:

  • runt pulse(短脉冲) :违反下游触发器的最小高/低脉宽约束;
  • 毛刺与双沿:造成"多计数/少计数"、状态机跳变等隐患;
  • CDC风险sel 通常与两路时钟都异步,若直接驱动组合 MUX,会让毛刺概率放大。

设计目标 :切换时实现"break-before-make "(先关后开),只在低电平窗口 改变门控,使 clk_out 要么连续、要么短暂停(保持低电平)再平滑接入新时钟,但绝不产生短脉冲


二、ASIC/通用RTL方案(And-Or 门控 + 低电平更新 + 互斥握手)

核心思路:

  1. 将两路时钟分别门控gclk0 = clk0 & en0gclk1 = clk1 & en1
  2. 输出 clk_out = gclk0 | gclk1
  3. 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 & ~en1en1 <= 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+)

推荐BUFGCTRLBUFGMUX_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互斥逻辑接上即可。

使用器件提供的 全局时钟控制/多路选择原语 (例如 CLKMUX2ECLK 相关原语、DCCA/CLKDIVF 等)。同样遵循:

  • 不要 用 LUT 对时钟 &|
  • 使用官方时钟MUX/门控资源,并用互斥 CE 驱动;
  • 具体原语名与接法以器件 TRM 为准。

四、时序与CDC约束(必须做)

  1. Clock Groupsclk0clk1 大多无关/异步,在 STA 里:

    • set_clock_groups -asynchronous -group {clk0} -group {clk1}
      或在 Vivado 用 set_clock_groups -exclusive(取决于你的语义:确实不会同时到下游)。
  2. 派生/生成时钟 :把 clk_out 定义为生成时钟,并指明它与激活源之间的关系(静态检查要知道这个树)。

  3. 选择信号路径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 例外标注。
  4. 最小脉宽约束 (ASIC尤甚):对 clk_out 设置 set_min_pulse_width -high/-low,匹配目标工艺/库要求。

  5. 保持互斥 :为 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是大忌,务必用全局时钟原语。
  • 只做组合MUXassign 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原语的具体脚位名称不同),或增加看门狗配合强制降级策略。
相关推荐
Blossom.1185 小时前
把AI“绣”进丝绸:生成式刺绣神经网络让古装自带摄像头
人工智能·pytorch·python·深度学习·神经网络·机器学习·fpga开发
电子凉冰5 小时前
FPGA强化-VGA显示设计与验证
fpga开发
XINVRY-FPGA6 小时前
XC7A100T-2FGG484I Xilinx Artix-7 FPGA
arm开发·嵌入式硬件·fpga开发·硬件工程·信息与通信·信号处理·fpga
cmc10286 小时前
129.FPGA绑定管脚时差分管脚只绑_p是不行的,tx与rx只绑一个也不行
fpga开发
望获linux6 小时前
【实时Linux实战系列】FPGA 与实时 Linux 的协同设计
大数据·linux·服务器·网络·数据库·fpga开发·操作系统
cycf6 小时前
系统同步输出延迟分析(七)
fpga开发
国科安芯6 小时前
高辐射环境下AS32S601ZIT2型MCU的抗辐照性能与应用潜力分析
网络·人工智能·单片机·嵌入式硬件·fpga开发
爱吃汽的小橘15 小时前
用串口控制DAC
fpga开发
FPGA_ADDA20 小时前
RFSOC27DR+VU13P 6U VPX板卡
fpga开发·信号处理·adda射频采集·rfsoc27dr·vu13p