一、时钟域
如果一个设计全局只使用了一个时钟,那么此设计有一个时钟域。如果在一个设计中有两个时钟去控制不同的接口,那么就称这个设计中有两个时钟域。如下图所示
当时钟不匹配时,就要进行同步化,否则就可能出现亚稳态,从而造成整个设计不稳定。
二、亚稳态
亚稳态其实就是数据的转变没有符合时钟采样所需要的setup/hold时间,在时钟的上升沿或下降沿到来时正好采到数据的变化状态。此时,由于数据并没有稳定,所以会导致采到的数据不停变化,而不是逻辑0或逻辑1.此时采到的数据会一直抖动,直至隔一段时间稳定。产生亚稳态的时序图如下:
处理亚稳态的方法:
1、相位控制
2、多级寄存器
3、异步FIFO缓存
多级触发寄存器处理
如果信号来自同一时钟域,则不需要多级触发器处理,如果来自两个时钟域,那么分为两种情况:
1、从快到慢
2、从慢到快
此处的快慢指时钟的频率,首先说从快到慢的处理方法:
其实此处也分信号所来的时间长短与慢的时钟的频率关系。
有以下两种可能
从快到慢
右图中的pulse_a信号是不会被clk_b采集到的,因为pulse_a信号的保持时间小于clk_b的时钟周期,所以要从clk_a的时钟域下将此信号采集过来就必须对其进行展宽,否则极有可能采集不到。
module Sync_Pulse(
input clk_a,
input clk_b,
input rst_n,
input pulse_a_in,
output pulse_b_out,
output b_out
);
/**************************************************************************************/
reg signal_a;
reg signal_b;
reg signal_b_s;
reg signal_b_ss;
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)begin
signal_b <= 0;
end
else begin
signal_b <= signal_a;
end
end
//多级触发器将clk_b抓到的signal_b信号打两拍输出
always@(posedge clk_b or negedge rst_n)begin
if(rst_n == 1'b0)begin
signal_b_s <= 1'b1;
signal_b_ss <= 1'b1;
end
else begin
signal_b_s <= signal_b;
signal_b_ss <= signal_b_s;
end
end
//在时钟域clk_a下,采集signal_b_s,用于反馈来拉低展宽信号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_s打两拍,因此处涉及到跨时钟域
signal_b_a1 <= signal_b_s;
signal_b_a2 <= signal_b_a1;
end
end
assign pulse_b_out = signal_b_s & (~signal_b_ss);
assign b_out = signal_b_s;
endmodule
从慢到快
在clk_a时钟看来,pulse_b信号是一个非常宽的信号,那么在clk_b时钟域下的pulse_b必然能够被clk_a采到。如果pulse_b是clk_b时钟域下的组合逻辑所产生的信号,那么就得先用DFF在clk_b时钟抓一拍,之后再用DFF抓两次或者更高次向clk_a时钟域传递。
module sys_clk(
input clk_a,
input clk_b,
input pules_in,
input rst_n,
output pules_out
);
reg pules_b;
reg pules_a1;
reg pules_a2;
reg pules_a3;
wire pules_a_pos;
wire pules_a_neg;
always@(posedge clk_b or negedge rst_n)begin
if(rst_n == 1'b0)begin
pules_b <= 1'b0;
end
else begin
pules_b <= 1'b1;
end
end
always@(posedge clk_a or negedge rst_n)begin
if(rst_n == 1'b0)begin
pules_a1 <= 1'b0;
pules_a2 <= 1'b0;
pules_a3 <= 1'b0;
end
else begin
pules_a1 <= pules_b;
pules_a2 <= pules_a1;
pules_a3 <= pules_a2;
end
end
assign pules_out = pules_a2;
assign pules_a_pos = pules_a2 & (~pules_a3); //输出信号的上升沿检测
assign pules_a_neg = pules_a3 & (~pules_a2); //下降沿检测
endmodule
需要强调的是,此时的pules_必须为clk_b时钟域下的信号**
在设计中可以简单的牢记以下五条原则:
1. 再全局时钟的跳变沿最可靠。
2. 来自异步时钟域的输入需要寄存一次以同步化,再寄存一次以减少亚稳态带来的影响。
3. 不需要用到跳变沿的来自同一时钟域的输入,没有必要对信号进行寄存。
4. 需要用到跳变沿的来自同一时钟域的输入,寄存一次即可。
5. 需要用到跳变沿的来自不同时钟域的输入,需要用到3个触发器,前两个用以同步,第3个触发器的输出和第2个的输出经过逻辑门来判断跳变沿