使时钟开关无毛刺的技术
随着越来越多的多频时钟被应用在当今的芯片中,特别是在通信领域,当芯片运行时经常需要切换时钟线的源。通常是通过在硬件上复用两个不同频率的时钟源,并通过内部逻辑控制多路复用器选择线来实现的。
这两个时钟频率可能彼此完全无关,也可能是彼此的倍数。但是无论哪种情况,切换时都有可能在时钟线上产生故障。时钟线上的小故障对整个系统都是有害的,因为它可能被一些寄存器解释为捕获时钟边缘,而被其他寄存器忽略。
在本文中,提出了两种避免开关输出时钟线故障的不同方法。当时钟是彼此的倍数时,使用第一种方法,而第二种方法处理彼此完全无关的时钟。
动态时钟切换的问题
图1显示了使用“与或”型多路复用器逻辑的时钟开关的简单实现。
多路复用器有一个名为SELECT的控制信号,当设置为0时,该信号将CLK0传播到输出,当设置为1时,该信号将CLK1传播输出。当SELECT值改变时,由于立即将输出从当前时钟源切换到下一时钟源,可能会导致故障。当前时钟是当前选择的时钟源,而下一时钟是对应于新SELECT值的时钟源。
图1中的时序图显示了当SELECT控制信号改变时,如何在输出OUT CLOCK处产生毛刺。这种开关的问题在于,开关控制信号可以相对于源时钟在任何时间改变,从而产生斩波输出时钟或在输出处产生毛刺的可能性。
选择控制信号很可能是由两个源时钟中的任何一个驱动的寄存器产生的,这意味着如果两个时钟都是彼此的倍数,则它与两个时钟具有已知的时序关系,或者如果源时钟在任何方面都不相关,则它可能与至少一个时钟异步。
需要避免在任一时钟的高状态期间进行切换,而不知道这些时钟的频率或相位关系。固定延迟可用于诱导两个源时钟的开始和停止时间之间的间隙,但前提是两个时钟源之间存在固定关系。它不能用于输入频率未知或时钟不相关的情况。
相关时钟源的故障保护
图2给出了一种防止时钟开关输出端毛刺的解决方案,其中源时钟是彼此的倍数。负边沿触发D触发器被插入每个时钟源的选择路径中。在时钟的负边沿被选择控制,以及仅在其他时钟首先被取消选择后才启用选择,提供了针对输出故障的出色保护。将选择信号在时钟的负边缘捕获可确保在任一时钟处于高电平时输出不会发生变化,从而防止输出时钟被斩波。从一个时钟选择到另一个时钟的反馈使时钟开关能够在开始传播下一个时钟之前等待当前时钟的取消选择,从而避免任何故障。
图2的时序图显示了SELECT信号从0到1的转变如何首先在CLK0的下降沿处停止CLK0向输出的传播,然后在CLK1的负沿处开始CLK1向输出的传输。
该电路中有三条定时路径需要特别考虑:SELECT控制信号到两个负边缘触发触发器之一,DFF0的输出到DFF1的输入,以及DFF1的输出到DFF0的输入。如果这三条路径中的任何一条路径上的信号与目标触发器时钟的捕获边缘同时发生变化,则该寄存器的输出很可能会变为元稳定,这意味着它可能会进入理想“1”和理想“0”之间的状态。(亚稳态状态)。时钟多路复用器和另一个触发器的使能反馈可以引起亚稳态。因此,需要将触发器的捕获边沿和SELECT信号的启动边沿彼此分开,以避免任何异步接口。这可以通过使用适当的多周期保持约束或最小延迟约束来容易地实现,因为两个时钟之间的时序关系是已知的。
容错性
在芯片启动时,两个触发器DFF0和DFF1都应重置为“零”状态,以使任何一个时钟最初都不会传播。通过在“零”状态下启动两个触发器,时钟开关内置了容错功能。
假设其中一个时钟在启动时由于故障而未切换。如果与故障时钟相关的触发器以“1”状态启动,它将阻止选择其他时钟作为下一时钟,并且由于缺少运行时钟,其自身的状态不可更改。通过在“零”状态下启动两个触发器,即使其中一个源时钟未运行,仍有能力将另一个好时钟传播到开关的输出。
不相关时钟源的故障保护
以前避免时钟开关输出毛刺的方法要求两个CLOCK源彼此的倍数,这样用户就可以避免信号与其中一个时钟域异步。但这种方法没有处理异步信号的机制。
这导致了用同步器电路实现时钟开关的第二种方法,以避免由异步信号引起的潜在亚稳态状态。当两个时钟源彼此完全无关时,异步行为的来源可能是SELECT信号,也可能是从一个时钟域到另一个时钟域的反馈。
如图3所示,通过为每个时钟源增加一个额外的正边沿触发触发器级,提供了对稳定性的保护。每个选择路径中的正边缘触发触发器与现有的负边缘触发触发器一起,防止可能由异步SELECT信号或从一个时钟域到另一个时钟域的异步反馈引起的潜在的亚稳态。
同步器只是两级触发器,其中第一级通过锁存数据并随后将其传递到下一级以由电路的其他部分解释来帮助稳定数据。
总结
通过使用本文中介绍的设计技术,可以以很少的开销避免在时钟源之间切换时在时钟线上产生毛刺的危险。这些技术是完全可扩展的,可以扩展到两个以上时钟的时钟开关。对于多个时钟源,每个时钟源的选择信号将由所有其他源的反馈启用。
##示例代码
module clock_sel
(
input clk1,
input clk2,
input resetn,
input sel,
output oclk
);
reg restn1;
reg restn2;
reg clk1_en_q1, clk1_en_q2;
reg clk2_en_q1, clk2_en_q2;
always @(posedge clk1 or negedge resetn)
begin : b_resetn1
reg resetn_1;
if (!resetn) begin
resetn_1 <= 1'b0;
resetn <= 1'b0;
end else begin
resetn_1 <= 1'b1;
resetn1 <= resetn_1;
end
end
always @(posedge clk2 or negedge resetn)
begin : b_resetn2
reg resetn_2;
if (!resetn) begin
resetn_2 <= 1'b0;
resetn2 <= 1'b0;
end else begin
resetn_2 <= 1'b1;
resetn2 <= 1'b1;
end
end
always @(posedge clk1 or negedge resetn1)
begin : b_clk1_en_q1
if (!resetn1) begin
clk1_en_q1 <= 1'b0;
end else begin
clk1_en_q1 <= ~(sel & clk2_en_q2);
end
end
always @(negedge clk1 or negedge resetn1)
begin : b_clk1_en_q2
if (!resetn1) begin
clk1_en_q2 <= 1'b0;
end else begin
clk1_en_q2 <= clk1_en_q1;
end
end
always @(posedge clk2 or negedge resetn2)
begin : b_clk2_en_q1
if (!resetn) begin
clk2_en_q1 <= 1'b0;
end else begin
clk2_en_q1 <= sel & ~clk1_en_q2;
end
end
always @(posedge clk2 or negedge resetn2)
begin : b_clk2_en_q2
if (!resetn2) begin
clk2_en_q2 <= 1'b0;
end else begin
clk2_en_q2 <= clk2_en_q1;
end
assign oclk = (clk1 & clk1_en_q2) | (clk2 & clk2_en_q2);
endmodule