最近看秋招面试题,这玩意儿考了很多次,所以单独拿出来写一下:
题目的含义很简单就是一个时钟切换电路,但是在时钟切换时容易出现以下情况:
切换前的时钟为高电平,切换后为低电平,切换后过一段时间切换后电平拉高,就造成了时钟输出电路上一个凸起的毛刺;
如下图:
这看起来很简单但是在时钟切换时存在的毛刺很可能会引起在采样时差生某些差错,而这些差错很有可能向后传播,导致之后的时钟采样和处理采样不到正确的数值,引起X的传播;
在实际设计时上面的切换电路会有一个select信号,当select信号为高电平时,选择一个时钟输出,当select信号为低时选择另一个信号输出;
组合逻辑这样写就可以:
assign clk_out = sel & clk_0|~sel & clk_1;
切换时要遵从这样的原则:当sel选择为在第一个时钟下输出的时候,如果要切换,要先等到当前时钟的下降沿,确定当前时钟为无效,输出时钟非此时钟,此后一段时间将输出时钟为低,一直等到第二个时钟的下降沿将第二个时钟沿的有效拉高,确定输出为第二个时钟这样就完成了切换,保证切换的过程中一直是低电平,没有任何毛刺,这样的输出才是有效的。无论是从时钟1切换到时钟2还是从时钟2切换到时钟1都遵从这样的定理,那么时钟的切换始终是没有毛刺的,始终是有效的! 通俗点讲就是切换后时钟的时钟有效为高时一定要确保前一个时钟的时钟有限为低,而确定两个时钟有效无效的都通过下降沿采样!
就是遵从先确定无效再确定有效这样的顺序去切换!
时钟源切换分为相关时钟源切换和非相关时钟源切换,相关时钟源就是相位相同频率成整倍数关系,也就是上升沿时钟是对齐的。
非相关时钟源就是指相位不同,频率也不是成整倍数的两个时钟,但切换的思路都遵从上面的原则进行变换;
相关时钟源的切换电路代码如下:
module(
input clk0,
input clk1,
input sel,
input rstn
output clk_out
);
reg sel_clk1;
reg sel_clk0;
always@(negedge clk0 or negedge rstn)
begin
if(~rstn)
sel_clk0 <= 1'b0;
else
sel_clk0 <= ~sel_clk1 & ~sel;
end
always@(negedge clk1 or negedge rstn)
begin
if(~rstn)
sel_clk1 <= 1'b0;
else
sel_clk1 <= ~sel_clk0 & sel;
end
assign clk_out <= (clk0 & sel_clk0) | (clk1 & sel_clk1);
endmodule
其实代码很简单,目的就是描述这样一个事情,切换时先要保证上一个时钟的有效已经变成了无效,才可以在本时钟的下降沿将有效锁存,完成切换;还是上述的原则,就是切换时保证当前时钟在下降沿变为无效,然后在下个选择时钟的下降沿完成切换,从而保证一直都是没有毛刺的。
那考虑这样一个问题,sel_clk0的变换使用了sel_clk1,而sel_clk1是在clk1时钟域下变换的,同理sel_clk1也是如此,如果两个时钟是完全的异步时钟域也就是非同源时钟关系,那么必然就会引起亚稳态问题,也就涉及到了单bit的跨时钟域问题,所以非相关时钟域下面的信号切换也要解决单Bit的跨时钟域问题,也就是打两拍完成时钟同步!
module(
input clk0,
input clk1,
input sel,
input rstn
output clk_out
);
reg sel_clk1_r1;
reg sel_clk1_r2;
reg sel_clk0_r1;
reg sel_clk0_r2;
always@(posedge clk0 or negedge rstn) //跨时钟域进来先做同步
begin
if(rstn)
sel_clk0_r1 <= 1'b0;
else
sel_clk0_r1 <= ~sel_clk1_r2;
end
always@(negedge clk0 or negedge rstn) //做同步之后再进行操作
begin
if(~rstn)
sel_clk0_r2 <= 1'b0;
else
sel_clk0_r2 <= sel_clk0_r1 & ~sel;
end
always@(posedge clk1 or negedge rstn)
begin
if(rstn)
sel_clk1_r1 <= 1'b0;
else
sel_clk1_r1 <= ~sel_clk0_r2;
end
always@(negedge clk1 or negedge rstn)
begin
if(~rstn)
sel_clk1_r1 <= 1'b0;
else
sel_clk1_r2 <= sel_clk1_r1 & sel;
end
assign clk_out <= (clk0 & sel_clk0_r2 ) | (clk1 & sel_clk1_r2 );
endmodule
其实理解透了原理还是很简单的,一定要想清前面提到的那个原理