异步电路导致亚稳态的根本原因是跨时钟域导致建立、保持时间不满足,导致采样之后的信号在较长时间处于逻辑0~1的中间态,可能需要很长时间才能恢复到确定值
T
m
e
t
T_{met}
Tmet?。
如果决断时间
T
m
e
t
T_{met}
Tmet?过长,长于sys_clk一个周期,那么后面逻辑电路采样到的值也是亚稳态,进而导致整个电路设计出现问题。
电平不稳定的问题,可以采用电平同步的方法。
但是电平同步还可能导致其他问题,所以下面分成了各种情形。
1. 控制信号
例如wr_en、rd_en这种使能信号,或者是一些flag标志信号等等
● 亚稳态问题——电平同步
电平同步的方法很简单,就是跨时钟域信号 连续经过两级触发器,俗称打两拍。
其原理就是经过两级触发器之后,能大大缩短决断时间,远小于一个sys_clk周期。
注意稳定之后的信号是逻辑0还是逻辑1是随机的。
这里蕴含一个基本数学关系:时钟对信号a打拍得到a_d1,那么在时间上一定满足:
t
r
o
s
e
(
a
)
<
t
r
o
s
e
(
a
d
1
)
<
t
f
e
l
l
(
a
)
<
t
f
e
l
l
(
a
d
1
)
t_{rose(a)}<t_{rose(a_{d1})}<t_{fell(a)}<t_{fell(a_{d1})}
trose(a)?<trose(ad1?)?<tfell(a)?<tfell(ad1?)?
● 多bit标志信号特有的 采样中间态问题——Grey码
这个是多bit信号特有的新问题,最早在异步FIFO中读写指针的跨时钟域时被发现。
就是说即便我可通过电平同步采到稳定电平,但也有可能采到数值变化的第三个值。
例如信号从01变成10,这两bit绝不是完全同时变化的,可能是01→11→10,也可能是01→00→10,无论是哪种情况 都一定会出现既不是旧值01也不是新值10的第三值,这要是被采样到那可就麻烦了。
那怎么搞?用数值变化过程中不会出现第三值的Grey码即可
例如Grey码信号从01变成11,就不会出现中间值。即便是最大值到最小值也不会,例如100到000
1.1. 快采慢
要知道跨时钟域问题不仅仅是时钟没对齐,两个时钟域的时钟频率也不一样。
所以除了亚稳态问题,以及多bit信号特有的中间值问题外,还有个问题需要解决:时钟频率不同导致的采样次数不同问题
例如单bit信号,快时钟同步慢时钟,就有可能慢时钟域下该信号只拉高一个周期,而快时钟域下检测该信号到多次有效,如下图对rd_en进行同步。
对于clk_slw来说,rd_en就有效一个周期。
而对于clk_fst来说,rd_en经过同步之后,持续了3个周期,这么做显然不对。
● 多次采样问题——边沿检测
那么我们也让clk_fst下的信号只有效一个周期,怎么做呢?
思路是创建一个新信号,rd_en_s2f出现上升沿就拉高,否则为低
那么怎么检测rd_en_s2f的上升沿呢?只需寄存rd_en_s2f的信号即可,这样clk_fst的每一拍都可以使用rd_en_s2f和rd_en_s2f的上一拍信号。
说了这么多,其实就是对rd_en_s2f再打一拍,对rd_en_s2f作上升沿检测,这样在电平同步的基础上作边沿检测,称脉冲同步
电平同步得到的是多周期的电平,脉冲同步得到的是只持续一周期的脉冲
波形重画如下图
基于上面这个图,给出verilog代码
module synchronizer_fast(
input clk_fst,
input rst_n,
input rd_en,
output rd_en_d2_rise
);
reg rd_en_d1;
reg rd_en_d2;
reg rd_en_d3;
always@(posedge clk_fst) begin
if(!rst_n) begin
rd_en_d1 <= 1'b0;
rd_en_d2 <= 1'b0;
rd_en_d3 <= 1'b0;
end
else begin
rd_en_d1 <= rd_en;
rd_en_d2 <= rd_en_d1;
rd_en_d3 <= rd_en_d2;
end
end
assign rd_en_d2_rise = (!rd_en_d3) && (rd_en_d2);
endmodule
综合的电路如下:
附一个多bit情形的代码:
module synchronizer_fast(
input clk_fst,
input rst_n,
input [`WIDTH-1:0] rd_en,
output [`WIDTH-1:0] rd_en_d2_rise
);
reg [`WIDTH-1:0] rd_en_d1;
reg [`WIDTH-1:0] rd_en_d2;
reg [`WIDTH-1:0] rd_en_d3;
always@(posedge clk_fst) begin
if(!rst_n) begin
rd_en_d1 <= 'b0;
rd_en_d2 <= 'b0;
rd_en_d3 <= 'b0;
end
else begin
rd_en_d1 <= rd_en;
rd_en_d2 <= rd_en_d1;
rd_en_d3 <= rd_en_d2;
end
end
assign rd_en_d2_rise = (rd_en_d2 != rd_en_d3)?rd_en_d2:'b0;
endmodule
局限性:只提取一clk_fst脉冲
如果慢时钟域下的rd_en拉高多于一拍呢?快时钟域只能检测到拉高,但不知道拉高了多少拍,所以最后还会只产生一clk_fst周期的脉冲。
1.2. 慢采快
如果是慢时钟同步快时钟,则可能慢时钟采样不到有效信号,如下图
● 数据丢失问题——反馈控制
没采到怎么办?我们可以让clk_fst域下的rd_en一直拉高,让clk_slw采到不就行了,然后慢时钟域下执行上述脉冲同步,以得到clk_slw一周期的有效脉冲完事。
那么快时钟域下的rd_en什么时候拉下来呢?可以让慢时钟域给快时钟域返一个标志信号告诉对方我收到啦!这样快时钟域下rd_en就可以拉低了。
别忘了这个返给快时钟域的标志信号也得电平同步去除亚稳态!
即使用握手方法,具体实现如下
时序如下:
● clk_slw第1拍:clk_slw检测到来自clk_fst的跨时钟域信号rd_en,开始执行脉冲同步
● clk_fst第5拍:clk_fst检测到来自clk_slw的跨时钟域信号rd_en_d2,开始执行电平同步
● clk_fst第7拍:clk_fst检测到电平同步之后的信号rd_en_ack_d2为高,说明慢时钟域成功捕获rd_en信号,此时拉低rd_en
根据时序图可以实现verilog代码如下
module synchronizer_slow(
input clk_slw,
input rst_n,
input rd_en,
output rd_en_ack,
output rd_en_d2_rise
);
reg rd_en_d1;
reg rd_en_d2;
reg rd_en_d3;
always@(posedge clk_slw) begin
if(!rst_n) begin
rd_en_d1 <= 1'b0;
rd_en_d2 <= 1'b0;
rd_en_d3 <= 1'b0;
end
else begin
rd_en_d1 <= rd_en;
rd_en_d2 <= rd_en_d1;
rd_en_d3 <= rd_en_d2;
end
end
assign rd_en_d2_rise = (!rd_en_d3) && (rd_en_d2);
assign rd_en_ack = rd_en_d2;
endmodule
module synchronizer_fast(
input clk_fst,
input rst_n,
input rd_en_ack,
input rd,
output rd_en,
);
reg rd_en_ack_d1;
reg rd_en_ack_d2;
reg rd_en_r;
always@(posedge clk_fst) begin
if(!rst_n) begin
rd_en_ack_d1 <= 1'b0;
rd_en_ack_d2 <= 1'b0;
end
else begin
rd_en_ack_d1 <= rd_en_ack;
rd_en_ack_d2 <= rd_en_ack_d1;
end
end
always@(posedge clk_fst) begin
if(!rst_n) begin
rd_en_r <= 1'b0;
else case({rd_en_ack_d2,rd})
2'b00: rd_en_r <= rd_en_r;
2'b01: rd_en_r <= 1'b1;
2'b10: rd_en_r <= 1'b0;
2'b11: rd_en_r <= 1'b0;
default: rd_en_r <= 1'b0;
endcase
end
end
assign rd_en = rd_en_r;
endmodule
根据verilog代码综合出的电路如下
局限性:漏采rd
从verilog代码中的case语句可见,只有在{rd_en_ack_d2,rd} == 2'b01; 时才有rd_en <= rd; ,其他情况下rd_en 的值与rd 无关。
Q:rd_en保持为高期间,如果多个clk_fst周期rd有效,那么慢时钟域最终只会产生一个脉冲。
A:该问题无法解决。因为必须保证检测到慢时钟域反馈的rd_en_ack_d2,才能将rd_en拉低,这个不能改变,所以注定会丢失有效rd。
局限性:最早拉高rd_en的时刻
Q:rd_en拉低后,直到rd_en_ack_d2拉低之后才能使rd_en再次拉高。那么rd_en能否在rd_en_ack_d2拉低之前就拉高呢?
如下图所示,绿色部分为 会被慢时钟域捕捉到的有效rd,红色部分为 不会被慢时钟域捕捉到的有效rd。
A:该问题可以解决。要保证rd_en新的拉高会使慢时钟域的rd_en_d2_rise产生新的脉冲,且快时钟域的rd_en_ack_d2产生新的电平
也就是说rd_en为低的时间存在一个最小值。
产生新的脉冲or电平意思是:产生的脉冲or电平与上一次产生的脉冲or电平不重叠 如下图就会使慢时钟域认为是同一次rd_en,即产生重叠
这个最小值我是没求出来,但是可以证明如果在rd_en_ack_d2下降之后,rd_en再拉高,是一定不会发生重叠的
证明:由于是rd_en_ack_d2下降,所以rd_en_ack_d2和rd_en_ack_d1两个电平不会存在重叠。 同时又因为rd_en_ack_d2又是clk_fst对rd_en_d2多次打拍的结果,那么一定有t_{fell(rd_en_d2)}<t_{fell(rd_en_ack_d2)} 所以rd_en_d2不存在重叠,故rd_en_d3也不会存在重叠,证毕。
但即使这样,依旧会在rd_en为低期间丢失rd
2. 多bit数据信号
|