大家好,我是数字小熊饼干,一个练习时长两年半的ic打工人。我在两年前通过自学跨行社招加入了IC行业。现在我打算将这两年的工作经验和当初面试时最常问的一些问题进行总结,并通过汇总成文章的形式进行输出,相信无论你是在职的还是已经还准备入行,看过之后都会有有一些收获,如果看完后喜欢的话就请关注我吧~谢谢~
在前几篇文章里,我们讨论了通过建立时间和保持时间,来进行时序分析,并介绍了同步电路中如果建立时间或保持时间违例,该如何修复。
本篇文章主要讨论异步电路中,该如何保证时序,避免时序违例。
一、同步时钟和异步时钟
首先我们补充一下异步时钟和同步时钟概念。
同步时钟指的是来自于同一个时钟源,他们之间相位存在固定关系的。比如来自于同一个锁相环(pll)的时钟是同步时钟。
而异步时钟,指的是彼此之间没有固定的相位关系的时钟,比如来自于不同pll的时钟肯定是异步时钟。
由于异步时钟没有固定相位关系,因此当一个时钟域下的信号传输到另外一个时钟域时,就不能够保证满足建立时间和保持时间了。
因此,我们需要一些其他的办法来保证时序能够得到满足。
二、两拍同步
首先是最基本的同步方法——两拍同步(2filp-flop synchronizer),即对从源时钟域aclk输入的信号,通过目标时钟域bclk下的两级寄存器进行同步,即:
这种方法也就是俗称的“打两拍”。打两拍”的目的主要是为了降低亚稳态的传递概率,即通过多加一个寄存器,多给adata1个周期的时间让其稳定下来,从而避免亚稳态传递到时钟域b中。可通过平均故障间隔时间(Mean Time Between Failures)来描述寄存器采样失败进入亚稳态的时间间隔:
其中,c1和c2是常数,取决于器件工艺。
tmet是寄存器从亚稳态转换到稳定状态的时间该值由寄存器的时间裕量决定,如果有多个寄存器,则为每个寄存器时间裕量之和。
f为采样时钟频率,α为异步数据的变化频率,因此越高的采样时钟频率以及越快变化的异步信号,会导致MTBF越小,就越容易产生亚稳态。
通常来说,只有一级寄存器情况下的异步信号采样MTBF可能只有几天,即每几天就可能出现一次亚稳态,而两级寄存器的异步信号采样的MTBF非常大,有的工艺下的寄存器甚至大于一亿年,因此通过“打两拍”可以大大降低同步后的信号bdata产生亚稳态的概率。
此外,为了降低亚稳态,从源时钟域aclk下输入的信号adata必须是寄存器输出的,也就是说adata不能由组合逻辑产生,否则由于组合逻辑输出会有毛刺,从而会提升亚稳态产生的概率。
三、慢到快的单比特信号同步
当然,并不是什么情况下都能使用打两拍的方式进行同步的,如果源时钟域aclk下的信号adata是一个有效值为1的脉冲信号,且宽度小于目标时钟域bclk的一个周期。
那么很有可能时钟域bclk的有效沿会错过adata,或者即使遇到了adata,且通过打两拍降低了bdata产生亚稳态的概率,但是两拍寄存器中的第一级寄存器仍然可能产生亚稳态,并且最终稳定下来的值也可能是0,导致时钟域bclk未能采样到aclk下的脉冲信号adata,如下图所示:
这就要求源时钟域aclk下的数据adata相对于目标时钟域bclk保持一段时间的稳定,至少有一个目标时钟沿能够稳定采样。那就有以下两者情况能实现稳定采样:
1. 源时钟域数据adata为持续时间较长的电平信号,能确保被目标时钟域采到。
2. 目标时钟域频率比源时钟域频率高,通常来说,如果时钟域bclk的频率是aclk频率的1.5倍以上,那么通过“打两拍”的方式进行同步,无论源时钟域下的的信号adata,是一个“长电平”的状态信号,或者“短脉冲”的控制信号,都能够实现稳定采样。
此外,由于cdc的不确定性,同步到bclk下的信号bdata,可能会早一个周期,也晚一个周期,在设计电路时需要考虑这一点。
四、快到慢的单比特信号同步
4.1 脉冲同步器
对于那种从源快时钟域到目标慢时钟域的脉冲信号,由于源脉冲信号adata对于目标时钟域变化太快,不可以直接使用“打两拍”的方式进行同步,我们在“打两拍”之前需要对快时钟域的“短”脉冲信号进行展宽处理,让其变为“长”电平信号,电路结构如下图所示:
上图展示的是脉冲同步器的示意图,简单解释一下工作原理:
1. aclk时钟域下产生脉冲信号adata,在输入到第一个寄存器后,使得其输出翻转;
2. 翻转后的电平信号通过bclk时钟域下的“打两拍”同步器,产生adata_sync2;
3. 对adata_sync2再打一拍产生adata_sync2_dff1;
4. 将adata_sync2与其上一拍的结果adata_sync2_dff1输入至异或逻辑,监测adata_sync2的边沿,以产生同步到bclk下的脉冲信号bdata。
上述的电路翻译成verilog代码就是:
module pulse_sync(
input wire aclk ,
input wire rst_a_n,
input wire adata ,
input wire bclk ,
input wire rst_b_n,
output wire bdata
);
reg adata_ext;
reg adata_sync1;
reg adata_sync2;
reg adata_sync_dff1;
always @(posedge clk_a or negedge rst_a_n) begin
if(!rst_a_n) begin
adata_ext <= 1'b0;
end else if (adata) begin
adata_ext <= ~adata_ext;
end
end
always @(posedge clk_b or negedge rst_b_n) begin
if(!rst_b_n) begin
adata_sync1 <= 1'b0;
adata_sync2 <= 1'b0;
end else begin
adata_sync1 <= adata_ext;
adata_sync2 <= adata_sync1;
end
end
always @(posedge clk_b or negedge rst_b_n) begin
if(!rst_b_n) begin
adata_sync2_dff1 <= 1'b0;
end else begin
adata_sync2_dff1 <= adata_sync2;
end
end
assign bdata= adata_sync2 ^ adata_sync_dff1;
endmodule
这种脉冲同步器能将快时钟域下的脉冲信号同步到慢时钟域,但是这还有个前提,那就是快时钟域下的脉冲信号adata,不能来的太快,如果第一个脉冲尚未同步完成,第二个脉冲又来的话,会使电平信号adata_ext再度翻转,进而导致慢时钟域未能成功同步。
为了解决这一问题,我们所能采用的方法就是增加快时钟域第一个和第二个脉冲之间的时间间隔,起码得等第一个脉冲同步完成快时钟域再发送第二个脉冲,因此,有必要让快时钟域知道何时第一个脉冲同步完成,这一方式就是握手(也叫结绳法)!
4.2 握手机制
握手的机制如下图所示:
说明一下时序:
1.当快时钟域aclk脉冲信号adata有效后,产生请求信号req_a以及状态信号busy_a,busy_a用于控制aclk下的相关信号,避免马上产生第二个脉冲信号;
2. req_a同步至慢时钟域bclk,得到req_sync
3. 通过req_sync产生脉冲信号bdata,并且产生应答信号ack_b,以表明adata已经成功同步到时钟域bclk;
4. 应答信号ack_b同步至快时钟域aclk,产生ack_sync,并且拉低请求信号req_a以及状态信号busy_a。本次握手结束。
上述时序翻译成代码就是:
module pulse_handshake(
input wire aclk,
input wire rst_a_n,
input wire adata,
output wire busy_a,
input wire bclk,
input wire rst_b_n,
output wire bdata
);
reg req_a;
reg req_a_sync1, req_a_sync2;
wire ack_b;
reg ack_b_sync1, ack_b_sync2;
reg ack_b_sync2_dff1;
wire ack_b_sync2_neg;
reg req_a_sync2_dff1;
always @(posedge clk_a or negedge rst_a_n) begin
if (!rst_a_n) begin
req_a <= 1'b0;
end else if (adata) begin
req_a <= 1'b1;
end else if(ack_b_sync2) begin
req_a <= 1'b0;
end
end
always @(posedge clk_a or negedge rst_a_n) begin
if (!rst_a_n) begin
busy_a <= 1'b0;
end else if (adata) begin
busy_a <= 1'b1;
end else if(ack_b_sync2_neg) begin
busy_a <= 1'b0;
end
end
always @(posedge clk_a or negedge rst_a_n) begin
if (!rst_a_n) begin
ack_b_sync1 <= 1'b0;
ack_b_sync2 <= 1'b0;
end else begin
ack_b_sync1 <= ack_b;
ack_b_sync2 <= ack_b_sync1;
end
end
always @(posedge clk_a or negedge rst_a_n) begin
if (!rst_a_n) begin
ack_b_sync2_dff1 <= 1'b0;
end else begin
ack_b_sync2_dff1 <= ack_b_sync2;
end
end
assign ack_b_sync2_neg = ack_b_sync2_dff1 && !ack_b_sync2;
always @(posedge clk_b or negedge rst_b_n) begin
if (!rst_b_n) begin
req_a_sync1 <= 1'b0;
req_a_sync2 <= 1'b0;
end else begin
req_a_sync1 <= req_a;
req_a_sync2 <= req_a_sync1;
end
end
assign ack_b = req_a_sync2;
always @(posedge clk_b or negedge rst_b_n) begin
if (!rst_b_n) begin
req_a_sync2_dff1 <= 1'b0;
end else begin
req_a_sync2_dff1 <= req_a_sync2;
end
end
always @(posedge clk_b or negedge rst_b_n) begin
if (!rst_b_n) begin
bdata <= 1'b0;
end else begin
bdata <= req_a_sync2 && !req_a_sync2_dff1 ;
end
end
最后,需要说明的是握手不仅能在单比特脉冲信号的同步中使用,对于多比特的状态信号也能使用握手进行同步,不过握手法是以牺牲效率为前提的,具体如何使用,还得根据具体的设计需求决定。
如果你喜欢这篇文章的话,请关注我的公众号-熊熊的ic车间,还有ic设计和ic验证的学习资料和书籍呢~欢迎您的关注!