【【深入浅出跨时钟域异步处理方法】】
两个异步跨时钟域下的信号并不会完全保持隔离,有时候也需要交流。所以怎么处理好两个时钟域下的时序问题就显得尤为关键。
这就需要用到一种名为跨时钟域处理的方法( Clock Domain Crossing CDC ) 处理
信号可以分为电平信号和脉冲信号,也可以分为单比特信号和总线信号。
单比特电平信号的跨时钟域处理方法 : 背靠背跨时钟域处理方法
将一个信号分别打了两拍 :
对于单比特脉冲信号的跨时钟域处理,需要比较源时钟和目的时钟的时钟快慢关系。
一 : 如果源时钟相比目的时钟慢,则源时钟的脉冲信号相比目的时钟来说是一个电平信号。这种跨时钟域的处理处理先按照上述的方法进行背靠背结构将脉冲信号打进来,然后再取得这样一个信号的上升沿或下降沿,就能转变成目的时钟域的脉冲信号 。
assign a_fall = (~a_latch) & a_latch_r ; // 采样下降沿 新为0 旧为1
assign a_rise = (a_latch) & (~a_latch_r) ; // 采样上升沿 新为1 旧为0
二 : 若源时钟要比目的时钟快,则源时钟上的脉冲信号,在目的时钟看来可能会变成一个毛刺,因此为了处理这样的问题,需要进行如下的操作
下面附上这里的代码
```c
```c
```c
module clk1toclk2( // 怎么系统的把一个时钟域下的a 转化成另一个时钟域下的b
input clk1 ,
input clk2 ,
input a ,
input rst1_n ,
input rst2_n ,
output b ,
output busy
);
reg a_latch ;
reg b_latch ;
reg b_latch_r ;
reg b_latch_2r ;
reg b_feedback_latch ;
reg c_latch ;
reg c_latch_r ;
// ----------------------------------
always@(posedge clk1 or negedge rst1_n)
begin
if(rst1_n == 0)
a_latch <= 0 ;
else if(a == 1) // 现在clk1 的时钟域把a变成电平
a_latch <= 1 ;
else if(c_latch_r == 1)
a_latch <= 1'b0 ;
end
always@(posedge clk2 or negedge rst2_n)
begin
if( rst2_n == 0)
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_r ; // 为了转换为脉冲而打拍
end
end
assign b = b_latch_r & (~b_latch_2r) ; // 抓取脉冲信号
always@(posedge clk2 or negedge rst2_n)
begin
if(rst2_n == 0)
b_feedback_latch <= 1'b0 ;
else if(b == 1)
b_feedback_latch <= 1 ;
else if(~b_latch_r)
b_feedback_latch <= 0 ;
end
always@(posedge clk1 or negedge rst1_n)
begin
if(rst1_n == 0)
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 ; // busy 信号是给前级让其不能输出的意思
endmodule
观察发现,先将输入a在时钟源clk1下转换成电平信号 a_latch
然后使用前文介绍的背靠背打拍方法,在clk2上打拍两次,第一次得到b_latch,第二次得到b_latch_r
再使用前文介绍的给电平取脉冲的方法,抓取b_latch_r的脉冲得到信号b,这样就可以把脉冲信号a跨时钟域得到脉冲信号b
此时握手过程尚未结束,因为各个latch还在latch中,需要让它们都回到0
方法是: 先将目的时钟域的b信号也latch住,得到b_feedback_latch(即脉冲变成电平信号)
再通过打拍将其同步到clk1上先得到c_latch 再得到c_latch_r
当a_latch看到c_latch_r为1,说明目的时钟域的脉冲信号b已成功生成,a_latch就恢复为0,再通过打拍传递使得b_latch_r也为0。b_feedback_latch看到b_latch_r为0,则它也变为0,再经过打拍传递,最终c_latch_r也为0 .
三 : 对于总线信号的跨时钟域传输
对于总线信号的跨时钟域处理有两种方式,一种是异步FIFO,另一种是vld握手
异步fifo天生就可以处理这种问题,而在有些需要更为简单的处理方式允许源时钟域在数据到达目的时钟域之前可以等待一段时间,在这段时间内不会再过来新的数据。当vld跨时钟完毕之后,在目的的时钟域上用跨时钟后的vld直接采样总线信号即可。
下面介绍一个从快到慢的总线跨时钟处理代码
其实处理的效果就是和之前的差不多
module clk12clk2_2(
input clk1 ,
input clk2 ,
input rst1_n ,
input rst2_n ,
input vld1 , // 其实相对来说vld其实是一个脉冲信号
input [7 : 0] data1 ,
output vld2 ,
output reg [7 : 0] data2
);
reg vld1_latch ;
reg vld2_latch ;
reg vld2_latch_r ;
reg vld2_latch_2r ;
reg vld2_feedback_latch ;
reg c_latch ;
reg c_latch_r ;
wire busy ;
// 这是一个快到慢的使用形式
always@(posedge clk1 or negedge rst1_n )
begin
if(rst1_n == 0)
vld1_latch <= 1'b0 ;
else if(vld1 == 1)
vld1_latch <= 1'b1 ; // 先把vld1脉冲转化成电平信号
else if(c_latch_r == 1)
vld1_latch <= 1'b0 ;
end
always@(posedge clk2 or negedge rst2_n)
begin
if(rst2_n == 0)
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 <= vld2_latch ;
vld2_latch_2r <= vld2_latch_r ;
end
end
assign vld2 = vld2_latch_r &(~vld2_latch_2r) ; // 提取上升沿获得 vld2
always@(posedge clk2 or negedge rst2_n )
begin
if(rst2_n == 0)
vld2_feedback_latch <= 1'b0 ;
else if(vld2)
vld2_feedback_latch <= 1'b1 ;
else if(~vld2_latch_r)
vld2_feedback_latch <= 1'b0 ;
end
always@(posedge clk1 or negedge rst1_n )
begin
if(!rst1_n )
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 or negedge rst2_n)
begin
if(rst2_n == 0)
data2 <= 8'b0 ;
else if(vld2)
data2 <= data1 ;
end
endmodule