芯片设计,包括FPGA程序设计中,都可能出现时钟选择器。在时钟选择器设计中,非常重要的一点就是避免在时钟切换时产生毛刺。
关于glitch free时钟选择器设计的文章很多,但大多数都是直接给出了几种设计方法,而没有思考过程。本文则记录了自己的这一过程。
假设存在两个时钟,clka和clkb,sel为0时clka输出,为1时clkb输出;当前sel为0,clka正在输出。在上述假设下,如果sel切换为1,那么clkb需要输出。
首先从考虑实现glitch free需要满足什么样的条件。第一,clka的输出需要先被同步关闭;第二,clkb需要在clka同步关闭后,再被同步开启。
为了满足上述条件,在最简的实现下,clka时钟域至少存在一个寄存器,该寄存器输出clka的时钟控制信号,用于实现clka的同步输出/禁止控制,该寄存器记为q2;同理clkb时钟域也存在这样一个寄存器,记为q4。
除了clka同步禁止外,禁止和启动顺序也很重要。clka需要先被禁止,然后clkb同步使能。所以,clkb的控制逻辑需要知道何时clka被禁止。为了实现该过程,那么q2的输出一定会被反馈给clkb的控制逻辑,然后送给q4。q2的输出为clka时钟域的信号,当他被送给q4前,需要经由clkb重新进行同步处理。同理,q4到q2之间也存在同样的反馈回路和同步过程。
基于上述分析,glitch free时钟选择器的最简实现中,至少包含4个寄存器。q2和q4分别产生clka和clkb的同步时钟控制信号。在q2之前还存在q1,在q4之前还存在q3,分别实现对上文提到的反馈信号的同步处理。
在q1和q2之间还存在简单的组合逻辑,该组合逻辑的输入分别是sel和feedback_b,feedback_b与q4输出相关,也就是与clkb输出状态相关;同理,在q3和q4之间也存在这样的组合逻辑。这两部分组合逻辑的作用是为了实现sel信号对时钟使能/禁止的控制。
q1和q2之间,q3和q4之间的控制逻辑应该是相似的。该组合逻辑有两个输入,以clka时钟为例,应该满足以下条件:
当sel为1时,clka输出被禁止,feedback_b的值不重要;
当sel由1切换为0时,组合逻辑输出不会立即发生变化。而是等待feedback_b,当feedback_b也发生变化时,组合逻辑输出才会变化,最终使能clka。
基于上述思考,可以考虑在q1和q2之间使用“与”逻辑。该“与”逻辑的行为是:当sel为1时,组合逻辑输出立即变化,导致clka最先被同步关闭;当sel由1变为0时,只是满足了“与”逻辑输出值变化的一个条件,需要继续等待feedback_b变化后,输出才会翻转,最终使能clka。
这部分选择的自由度应该比较大。我们就选择使用或逻辑来实现时钟输出的使能和禁止,当时钟被禁止时,输出为常1。clka和clkb的“或”逻辑输出,进行“与”操作后作为最终的时钟输出。如下图。
当我们确定基本结构和组合逻辑的基本形式后,需要找到一个切入点来确定最终的实现形式。个人认为最好的切入点是sel变化时,被禁止的时钟。因为禁止行为最先发生,使能行为后发生。
假设sel由0变为1,此时q2的输出需要在下一个clka上升沿后立即变为1,从而实现clka的禁止操作。
代码如下。
- `timescale 1ps / 1ps
-
- module clk_switch (
- input clka,
- input clkb,
- input sel, // 0: clka, 1: clkb
-
- output clk_o
- );
-
- reg ff1, ff2, ff3, ff4;
- wire clka_inner_src, clkb_inner_src;
-
- always_ff @ (posedge clka) begin
- ff2 <= ~sel & ~ff1;
- ff1 <= ff4;
- end
-
- always_ff @ (posedge clkb) begin
- ff3 <= ff2;
- ff4 <= sel & ~ff3;
- end
-
- assign clka_inner_src = ~ff2 | clka;
- assign clkb_inner_src = ~ff4 | clkb;
-
- assign clk_o = clka_inner_src & clkb_inner_src;
-
- endmodule