网上有很多的跨时钟域信号处理的相关文章,主要分为三种:
单比特信号–打两拍或打更多拍(使用触发器);
多比特信号–异步双口块RAM或者异步FIFO;
格雷码转换。
这次就主要说第1种情况,适用于单比特信号。
1.应用场景
从时钟域1的单比特信号DATA需要传到时钟域2下,在2下就可以使用寄存器打拍的方式将DATA信号同步到自己的时钟域下。
为什么要打拍?因为要解决亚稳态的问题。
2.亚稳态
触发器的建立时间和保持时间在时钟上升沿左右定义了一个时间窗口,如果触发器的数据输入端口上数据在这个时间窗口内发生变化(或者数据更新),那么就会产生时序违规。存在这个时序违规是因为建立时间要求和保持时间要求被违反了,此时触发器内部的一个节点(或者要输出到外部的节点)可能会在一个电压范围内浮动,无法稳定在逻辑0或者逻辑1状态。换句话说,如果数据在上述窗口中被采集,触发器中的晶体管不能可靠地设置为逻辑0或者逻辑1对应的电平上。所以此时的晶体管并未处于饱和区对应的高或者低电平,而是在稳定到一个确定电平之前,徘徊在一个中间电平状态(这个中间电平或许是一个正确值,也许不是)。如图所示,这就是所谓的亚稳态。
3、Verilog代码设计
一般而言单bit信号就是我们所用到的脉冲信号或者电平信号。假设A和B是两个时钟域,各自的频率是clk_a和clk_b,clk_a的频率高于clk_b(同频相位差稳定的,不在讨论范围内),那么单bit信号传输分为两种情况。
3.1信号从clk_b到clk_a:从慢到快
在时钟域B下的脉冲pulse_b在时钟域A看来,是一个很宽的“电平”信号,会保持多个clk_a时钟周期,所以一定能被clk_a采到。经验设计采集过程必须寄存两拍。第一拍将输入信号同步化,同步化后的输出可能带来建立/保持时间的冲突,产生亚稳态。需要再寄存一拍,减少亚稳态带来的影响。一般来说两级是最基本要求,如果是高频率设计,则需要增加寄存级数来大幅降低系统的不稳定性。也就是说采用多级触发器来采样来自异步时钟域的信号,级数越多,同步过来的信号越稳定。
特别需要强调的是,此时pulse_b必须是clk_b下的寄存器信号,如果pulse_b是clk_b下的组合逻辑信号,一定要先在clk_b先用D触发器(DFF)抓一拍,再使用两级DFF向clk_a传递。这是因为clk_b下的组合逻辑信号会有毛刺,在clk_b下使用时会由setup/hold时间保证毛刺不会被clk_b采到,但由于异步相位不确定,组合逻辑的毛刺却极有可能被clk_a采到。一般代码设计如下:
always @ (posedge clk_a or negedge rst_n)
begin
if (rst_n == 1’b0)
begin
pules_a_r1 <= 1’b0;
pules_a_r2 <= 1’b0;
pules_a_r3 <= 1’b0;
end
else
begin //打3拍
pules_a_r1 <= pulse_b;
pules_a_r2 <= pules_a_r1;
pules_a_r3 <= pules_a_r2;
end
end
assign pulse_a_pos = pules_a_r2 & (~pules_a_r3); //上升沿检测
assign pulse_a_neg = pules_a_r3 & (~pules_a_r2); //下降沿检测
assign pulse_a = pules_a_r2;
(对于上升沿检测是指同步过来的pules_a_r2的上升沿时,pules_a_pos会持续一个clk_a高电平,读者也可以自己画一下时序图就能分析出来了)
3.2信号从clk_a到clk_b:从快到慢
如果单bit信号从时钟域A到时钟域B,那么存在两种不同的情况,传输脉冲信号pulse_a或传输电平信号level_a。实际上,电平信号level_a的宽度足够宽才能被clk_b采集到才可以保证系统正常工作。那么对于脉冲信号pulse_a采取怎样的处理方法呢?可以用一个展宽信号来替代pulse_a实现垮时钟域的握手。
主要原理就是先把脉冲信号在clk_a下展宽,变成足够宽的电平信号signal_a,再向clk_b传递,当确认clk_b已经“看见”信号同步过去之后,再清掉signal_a。代码通用框架如下:
module Sync_Pulse (
clk_a,
clk_b,
rst_n,
pulse_a_in,
pulse_b_out,
b_out
);
/****************************************************/
input clk_a;
input clk_b;
input rst_n;
input pulse_a;
output pulse_b_out;
output b_out;
/****************************************************/
reg signal_a;
reg signal_b;
reg signal_b_r1;
reg signal_b_r2;
reg signal_b_a1;
reg signal_b_a2;
/****************************************************/
//在时钟域clk_a下,生成展宽信号signal_a
always @ (posedge clk_a or negedge rst_n)
begin
if (rst_n == 1’b0)
signal_a <= 1’b0;
else if (pulse_a_in) //检测到到输入信号pulse_a_in被拉高,则拉高signal_a
signal_a <= 1’b1;
else if (signal_b_a2) //检测到signal_b1_a2被拉高,则拉低signal_a
signal_a <= 1’b0;
else;
end
//在时钟域clk_b下,采集signal_a,生成signal_b
always @ (posedge clk_b or negedge rst_n)
begin
if (rst_n == 1'b0)
signal_b <= 1'b0;
else
signal_b <= signal_a;
end
//多级触发器处理
always @ (posedge clk_b or negedge rst_n)
begin
if (rst_n == 1'b0)
begin
signal_b_r1 <= 1'b0;
signal_b_r2 <= 1'b0;
end
else
begin
signal_b_r1 <= signal_b; //对signal_b打两拍
signal_b_r2 <= signal_b_r1;
end
end
//在时钟域clk_a下,采集signal_b_r1,用于反馈来拉低展宽信号signal_a
always @ (posedge clk_a or negedge rst_n)
begin
if (rst_n == 1'b0)
begin
signal_b_a1 <= 1'b0;
signal_b_a2 <= 1'b0;
end
else
begin
signal_b_a1 <= signal_b_r1; //对signal_b_r1打两拍,因为同样涉及到跨时钟域
signal_b_a2 <= signal_b_a1;
end
end
assign pulse_b_out = signal_b_r1 & (~signal_b_r2);
assign b_out = signal_b_r1;
endmodule