“少就是多”系列:数字IC设计知识与手撕代码(二)跨时钟域处理
一、所谓同步电路
电路设计中的所有寄存器,它的时钟都来自于同一个时钟源,满足时序时,同一时钟沿会触发所有寄存器同时作出采样动作,则称这些寄存器是同步的。
推论一
对概念进行推广,假设设计中的有些寄存器被同一个时钟所驱动,而另一些则被该时钟的分频时钟所驱动,这样的电路仍然属于同步电路。
推论二
进一步对概念进行推广,凡是时钟相位有关系的电路,都可以看作同步电路。
例如,如图所示的数字电路设计中从模拟电路中引入了两个时钟,分别是clk1、clk2。两个时钟在模拟电路中有着相同的源头,相位关系也是固定的,因而可以视为是同步的。
二、所谓异步时钟
如果数字设计中的若干个时钟之间,不存在任何的相位关系和频率关系,则称TA们两两之间为异步时钟。如图所示的clk1和clk3为异步时钟,分管不同的时钟域。
而处在不同时钟域下的信号成为异步信号。
跨时钟域问题的产生
两个处在异步时钟域下的信号不会完全保持隔离,有时也需要相互交流。这样就衍生出了跨时钟域的问题。
三、所谓跨时钟域处理
如上文所述,异步信号之间也存在相互交流的需求,但这样的信号之间不能直接用金属线相连,因为异步时钟之间的关系是不确定的。
当寄存器要采集异步信号后,其建立时间和保持时间无法保证符合要求,很可能在寄存器输出端产生亚稳态。
至于什么是亚稳态,这也是值得细细探讨的课题,为了秉持本系列文章“少就是多”的原则,笔者不作深入说明,可以简单的认为信号在此时处于一种不确定的状态,不是0也不是1,需要经过一段时间才能恢复到稳定的0/1状态。
为了避免上述的亚稳态问题,需要对异步信号跨时钟域做特殊的处理,即:跨时钟域处理。
四、所谓最重要的手撕代码环节
1、单比特,慢时钟域到快时钟域,打两拍
如图为著名的背靠背跨时钟域处理方法,也就是我们常说的打两拍、双锁存器法。请编写Verilog代码实现这一电路。
always@(posedge clk1, negedge rst1_n) begin
if (rst1_n == 1'b0) begin
b <= 1'b0;
end else begin
b <= a;
end
always@(posedge clk2, negedge rst2_n) begin
if (rst2_n == 1'b0) begin
c <= 1'b0;
d <= 1'b0;
end else begin
c <= b;
d <= c;
end
end
预期的波形图如图所示。
2、单比特,慢到快,快到慢,通用
跨时钟域处理问题中,针对快时钟域到慢时钟域的情况,一般有脉冲同步器、脉冲展宽法等。根据这些同步器的思想,设计1个单比特通用的跨时钟域结构,使用Verilog实现。
(提示:信号锁存)。
always@(posedge clk1, negedge rst1_n) begin
if (rst1_n == 1'b0) begin
a_latch <= 1'b0;
end else if (a == 1'b1) begin
a_latch <= 1'b1;
end else if (c_latch_r == 1'b1) begin
a_latch <= 1'b0;
end
end
always@(posedge clk2, negedge rst2_n) begin
if (rst_n == 1'b0) begin
b_latch <= 1'b0;
b_latch_r <= 1'b0;
b_latch_2r <= 1'b0;
end else begin
b_latch <= a_latch;
b_latch_r <= b_latch;
b_latch_2r <= b_latch_2r;
end
end
assign b = (b_latch_r == 1'b1) && (b_latch_2r == 1'b0);
always@(posedge clk2, negedge rst2_n) begin
if (rst_n == 1'b0) begin
b_feedback_latch <= 1'b0;
end else if (b == 1'b1) begin
b_feedback_latch <= 1'b1;
end else if (b_latch_r == 1'b0) begin
b_feedback_latch <= 1'b0;
end
end
always@(posedge clk1, negedge rst1_n) begin
if (rst1_n == 1'b0) begin
c_latch <= 1'b0;
c_latch_r <= 1'b0;
end else begin
c_latch <= b_feedback_latch;
c_latch_r <= c_latch;
end
end
assign busy = a_latch | c_latch_r;
预期波形图如下:
3、多比特,握手,handshake
多比特跨时钟域处理的方法有很多,比如:格雷码同步法、DMUX、握手、FIFO。其中,握手同步法的通用性更强且不相FIFO那样占用较大的存储空间,是比较常用的跨时钟域方法。请用Verilog实现。
always@(posedge clk1, negedge rst1_n) begin
if (rst1_n == 1'b0) begin
vld1_latch <= 1'b0;
end else if (a == 1'b1) begin
vld1_latch <= 1'b1;
end else if (c_latch_r == 1'b1) begin
vld1_latch <= 1'b0;
end
end
always@(posedge clk2, negedge rst2_n) begin
if (rst_n == 1'b0) begin
vld2_latch <= 1'b0;
vld2_latch_r <= 1'b0;
vld2_latch_2r <= 1'b0;
end else begin
vld2_latch <= vld1_latch;
vld2_latch_r <= vld1_latch;
vld2_latch_2r <= vld1_latch_2r;
end
end
assign vld2 = (vld2_latch_r == 1'b1) && (vld2_latch_2r == 1'b0);
always@(posedge clk2, negedge rst2_n) begin
if (rst_n == 1'b0) begin
vld2_feedback_latch <= 1'b0;
end else if (vld2 == 1'b1) begin
vld2_feedback_latch <= 1'b1;
end else if (vld2_latch_r == 1'b0) begin
vld2_feedback_latch <= 1'b0;
end
end
always@(posedge clk1, negedge rst1_n) begin
if (rst1_n == 1'b0) begin
c_latch <= 1'b0;
c_latch_r <= 1'b0;
end else begin
c_latch <= vld2_feedback_latch;
c_latch_r <= c_latch;
end
end
assign busy = vld1_latch | c_latch_r;
always@(posedge clk2, negedge rst2_n) begin
if (rst2_n == 1'b0) begin
dat2 <= 'd0;
end else if (vld2 == 1'b1) begin
dat2 <= dat1;
end
end
五、参考文献
《数字IC设计入门》。