1. 为什么简单的时钟切换会“翻车”?从毛刺说起
很多FPGA新手在第一次做时钟切换时,都会掉进同一个“坑”里:不就是用一个选择信号(比如 sel)在两个时钟之间选一个吗?代码简单到一行搞定:
assign o_clk_sel = sel ? i_clk1 : i_clk0;
我刚开始做项目时也这么干过,结果系统时不时出现一些莫名其妙的错误,数据偶尔会错位,抓波形排查到头秃。后来用示波器一量,才发现输出的时钟上出现了非常窄的“毛刺”(Glitch)。这些毛刺虽然短暂,但足以被后续的寄存器错误地当作一个有效的时钟边沿来采样,导致数据错乱,系统行为完全不可预测。
毛刺是怎么产生的? 核心原因在于 sel 信号的跳变时刻是随机的,它和 i_clk0、i_clk1 的边沿没有任何时序关系。想象一下,sel 信号可能在 i_clk0 处于高电平的某个瞬间从0跳变到1。这时,输出时钟 o_clk_sel 会立刻从 i_clk0 切换到 i_clk1。如果此刻 i_clk1 恰好是低电平,那么输出就会产生一个从高到低的跳变,形成一个本不该存在的“下降沿”。反之亦然,可能产生一个虚假的“上升沿”。这个短暂的、非预期的脉冲,就是毛刺。
这种毛刺的危害是致命的,尤其是在驱动计数器、状态机或数据路径时。一个虚假的时钟边沿会导致寄存器多采样一次或少采样一次,彻底破坏系统的同步性。所以,一个可靠的时钟切换电路,其首要设计目标就是实现 “无毛刺切换”。这意味着在切换过程中,输出时钟必须始终保持一个干净、连续的波形,不能产生任何短于正常时钟周期的脉冲。
那么,如何实现无毛刺切换呢?业界早在1997年,Xilinx的元老Peter Alfke就发表了一篇奠基性的文章,其核心思想被后续几乎所有无毛刺时钟切换电路所沿用。简单来说,其原理是:在关闭当前时钟之前,先确保目标时钟已经稳定开启;在开启目标时钟之后,再关闭当前时钟。 通过这种“先开后关”的握手机制,确保输出时钟线上永远不会出现两者同时为低(产生下降沿毛刺)或同时关断再开启的“断档”情况。接下来,我们就从最直接的硬件原语方案开始,看看如何实现这一目标。
2. 硬件“快车道”:Xilinx BUFGMUX原语详解与实战
对于Xilinx FPGA用户来说,实现时钟切换最省心、最可靠的方法,就是使用官方提供的专用硬件原语——BUFGMUX(全局时钟多路复用缓冲器)。你可以把它理解成芯片内部时钟网络中的一个“高质量、无毛刺的硬件开关”。
2.1 BUFGMUX是什么?怎么用?
BUFGMUX是一个全局时钟缓冲器,它内置了无毛刺切换逻辑。它的端口定义非常直观:
I0,I1: 两路时钟输入。S: 时钟选择信号。S=0时选择I0,S=1时选择I1。O: 时钟输出。
在代码中例化它非常简单,以下是一个标准的例化模板:
BUFGMUX #(
.CLK_SEL_TYPE("SYNC") // 属性:选择同步切换模式
) BUFGMUX_inst (
.O(O), // 输出时钟
.I0(I0), // 输入时钟0 (S=0时选择)
.I1(I1), // 输入时钟1 (S=1时选择)
.S(S) // 时钟选择信号
);
我强烈建议你为 BUFGMUX 显式设置 CLK_SEL_TYPE 属性。这个属性决定了选择信号 S 的同步方式:
“SYNC”(默认):选择信号S会先被一个内部寄存器同步,再用于控制切换。这能防止S信号上的亚稳态传播到时钟路径,是更安全、更推荐的做法。切换会有几个时钟周期的延迟,但绝对稳健。“ASYNC”:选择


168

被折叠的 条评论
为什么被折叠?



