1.电平同步器
单bit信号跨时钟域最为简单的方法就是通过寄存器打两拍进行同步,也就是所谓的电平同步器。给出电路图:
为了使同步器正常工作,从原时钟传来的信号应该先通过原时钟上的一个触发器,以消除所带的毛刺,而后不经过任何组合逻辑,进行打两拍,这一要求非常重要,因为同步器的第一级触发器对组合逻辑所产生的毛刺非常敏感,一旦毛刺满足条件时序要求时,会给同步时钟送出虚假的信号。本质来说,电平同步器是用来降低跨时钟域时可能产生的亚稳态。
根据上述电路,信号在两个同步时钟周期以后,便可以成为新时钟域下的有效信号。考虑到时钟关系,信号的延时是新时钟域中的一到两个时钟周期。
但这样的电路有一个明显的局限性,可同步的信号需要满足较为苛刻的要求。从本质上说,就是信号必须要被新时钟域所采到,而不能有遗漏,因而原时钟域下的信号必须足够长。即:
- 从慢时钟域传递到快时钟域。信号肯定被采到,故最为适用。但此时输出信号一般为电平信号,如果要求获得与新周期等宽的脉冲信号,则不可用。
- 快时钟域传递到慢时钟域下,传递的信号必须为较宽的电平信号,要求保持高电平或低电平一个同步时钟周期以上。和输入信号关系较大,不可传递原时钟周期的脉冲信号。因而不适用与快时钟传递到慢时钟。
给出设计和测试代码:
//电平同步器
`timescale 1ns/1ps
module level_syc(
input wire clk_1,
input wire clk_2,
input wire din,
input wire rst_n,
output wire dout
);
reg src_state;
reg src_state_d0, src_state_d1;
//原时钟域信号寄存器输出,消除毛刺
always @(posedge clk_1 or negedge rst_n)
begin
if(rst_n == 1'b0)
src_state <= 1'b0;
else
src_state <= din;
end
//同步至新时钟域
always @(posedge clk_2 or negedge rst_n)
begin
if(rst_n == 1'b0)
begin
src_state_d0 <= 1'b0;
src_state_d1 <= 1'b0;
end
else
begin
src_state_d0 <= src_state;
src_state_d1 <= src_state_d0;
end
end
assign dout = src_state_d1;
endmodule
module level_syc_tb();
reg clk_1, clk_2, rst_n;
reg din;
// 慢时钟域到快时钟域
always
begin
#30 clk_1 = ~clk_1;
end
always
begin
#10 clk_2 = ~clk_2;
end
// 快时钟域到慢时钟域
/* always
begin
#10 clk_1 = ~clk_1;
end
always
begin
#30 clk_2 = ~clk_2;
end
*/
initial
fork
clk_1 = 1'b1;
din = 1'b0;
#5 clk_2 = 1'b1;
#10 rst_n = 1'b0;
#50 rst_n = 1'b1;
//慢时钟域到快时钟域
#200 din = 1'b1;
#260 din = 1'b0;
//快时钟域到慢时钟域,高电平持续两个同步时钟周期
/* #320 din = 1'b1;
#380 din = 1'b0;
//快时钟域到慢时钟域,高电平持续小于两个同步时钟周期
#800 din = 1'b1;
#820 din = 1'b0;
*/
join
level_syc u1(.clk_1(clk_1), .clk_2(clk_2), .rst_n(rst_n),
.din(din), .dout(dout));
endmodule
最后给出Modelsim的仿真结果:
慢时钟域到快时钟域:
信号顺利完成跨时钟域。同步延时为80ns。
从波形图中推导可得,电平同步器所引起的延迟区间为[2Dsetup + T2, T1 + 2T2]。
快时钟域到慢时钟域:
第一个信号完成跨时钟域,第二个信号被滤掉。
可得三个结论:
- 通过波形推导,并忽略原时钟下的触发器的延时,信号的同步的延时介于(T2 + Dsetup)和(2T2)之间,或者换另一种说法, 信号在两个新时钟有效沿之后,就成为了新时钟域下的有效信号。
- 从慢时钟域跨域到快时钟域的信号,在输出时,是一个多周期的信号,如果电路要求输出一个与周期等宽的脉冲信号,则电平同步器是不适用的。
- 从快时钟域向慢时钟域传递时钟周期的脉冲信号时,信号很可能会被滤掉。结合波形图可得,信号必须持续至少一个同步时钟周期,才能确保肯定被采到,完成跨时钟域。考虑到跨时钟域下,时钟的相互关系并不确定,因而,采用电平同步器进行块到慢时钟域的跨越是不合理的。
##2.边沿同步(检测)器
在电平同步器的基础上,通过输出端的逻辑组合,可以完成对于信号边沿的提取,识别上升沿,下降沿以及双边沿,并发出相应的脉冲。给出电路图(省略了原时钟域下的触发器输出):
基本思想:信号在新时钟域打两拍完成同步之后,再外接一个触发器,相当于将信号再向后延迟一个周期。之后,通过非门和与门,对标准同步信号以及延迟信号进行逻辑组合,从而完成边沿的提取,最终得到一个与新时钟周期等宽,高电平有效的脉冲信号。
设上述三个触发器从左到右的输出分别为 Q0, Q1, Q2,则有:
提取上边沿 pules = Q1 & (~Q2);
提取下边沿 pulse = (~Q1) & Q2;
提取双边沿 pulse = Q1 ^ Q2;
综上,相比于电平同步器,边沿同步器的作用主要是在跨时钟域的基础上,检测信号的边沿而作脉冲输出,因而适合要求脉冲输出的电路。对于跨时钟的频率要求与电平同步器一致,仅适合从慢时钟域跨到快时钟域的信号。
给出设计以及测试代码:
//边沿同步器
module edge_syc(
input wire clk_1,
input wire clk_2,
input wire din,
input wire rst_n,
output wire dout_r,
output wire dout_f,
output wire dout_e
);
reg src_state;
reg src_state_d0, src_state_d1, src_state_d2;
//原时钟域下脉冲信号转变为电平信号
always @(posedge clk_1 or negedge rst_n)
begin
if(rst_n == 1'b0)
src_state <= 1'b0;
else
src_state <= din;
end
//同步至新时钟域
always @(posedge clk_2 or negedge rst_n)
begin
if(rst_n == 1'b0)
begin
src_state_d0 <= 1'b0;
src_state_d1 <= 1'b0;
src_state_d2 <= 1'b0;
end
else
begin
src_state_d0 <= src_state;
src_state_d1 <= src_state_d0;
src_state_d2 <= src_state_d1;
end
end
//边沿检测产生新的脉冲
assign dout_r = src_state_d1 & ~src_state_d2;
assign dout_f = !src_state_d1 & src_state_d2;
assign dout_e = src_state_d1 ^ src_state_d2;
endmodule
module edge_syc_tb();
reg clk_1, clk_2, rst_n;
reg din;
always
begin
#30 clk_1 = ~clk_1;
end
always
begin
#10 clk_2 = ~clk_2;
end
initial
fork
clk_1 = 1'b1;
din = 1'b0;
#5 clk_2 = 1'b1;
#10 rst_n = 1'b0;
#50 rst_n = 1'b1;
#200 din = 1'b1;
#260 din = 1'b0;
#320 din = 1'b1;
#380 din = 1'b0;
// #400 din = 1'b1;
// #460 din = 1'b0;
join
edge_syc u1(.clk_1(clk_1), .clk_2(clk_2), .rst_n(rst_n),
.din(din), .dout_r(dout_r) ,.dout_f(dout_f),
.dout_e(dout_e));
endmodule
最后给出Modelsim的仿真结果:
从上述波形可以看出,边沿同步器成功的将慢时钟域下的一个信号,转化为了快时钟域下的一个与周期同宽的脉冲信号。
波形给出的周期延时为80ns,忽略原时钟触发器引起的40ns的延时,则边沿同步器给出的延时时间为40ns,恰好为两个新时钟的周期。通过观察和推导,同样可得,这个延迟最高为2个同步时钟周期,40ns,最低为1个同步时钟周期+Dsetup。此外,考虑到输出的组合逻辑,需要再加上组合逻辑所带来的延时,Tlogic,因而边沿同步器的延时区间为[T2 + Dsetup + Tlogic, 2T2 + Tlogic]。
3. 脉冲同步器
之前所考虑的两个同步器,都只适合从慢时钟域到快时钟域,不必考虑新时钟域下采不到信号的问题。从快时钟域传递单bit信号到慢时钟域,则需要用到脉冲同步器。
脉冲同步器的思想在于,先将原时钟域下的脉冲信号,转化为电平信号,再进行同步,同步完成之后再把新时钟域下的电平信号转化为脉冲信号(边沿检测器的功能)。结合之前所了解的两种同步器,实际上在这里只需要考虑完成脉冲到电平的转化便可以了。
脉冲转化电平有两种基本的方法,后续了解到其他的方法再进行补充。
- 通过二选一选择器进行转化,给出脉冲同步器的电路图:
分析:假设初始时Q为0,Q非为1,在DATA为低电平时,Q值保持不变;在DATA脉冲的高电平到来时,Q完成翻转,变为1,此时Q非为0;而后DATA脉冲结束,变为低电平,Q值保持高电平不变,一直为高,直到下一次脉冲到来完成翻转。
- 通过异或门进行转化
分析:假设初始Q为0。在in_pulse为低电平时,异或门输入相同,输出为0,Q值保持为0不变;在in_pulse脉冲的高电平到来时,异或门输入不同,输出为1,Q值变为1;此后in_pulse脉冲的低电平到来,异或门输入仍旧不同,Q值保持为1不变,直到下一次脉冲带来才发生翻转。
此外,脉冲同步器有一个重要的限制,即输入脉冲的需要有两个同步时钟周期的间隔。输入脉冲如果相距过近,则新时钟域中的输出脉冲也会紧密相邻,结果输出的脉冲宽度高于一个同步时钟周期,这是不期望地。
给出两种方法的设计以及测试代码:
//脉冲同步器
module pulse_syc(
input wire clk_1,
input wire clk_2,
input wire din,
input wire rst_n,
output dout
);
reg src_state;
reg src_state_d0, src_state_d1, src_state_d2;
//原时钟域下脉冲信号转变为电平信号
always @(posedge clk_1 or negedge rst_n)
begin
if(rst_n == 1'b0)
src_state <= 1'b0;
// else if(din == 1'b1) //通过2选1MUX完成翻转功能,脉冲到来完成从脉冲到电平的转换
// src_state <= ~src_state;
else
src_state <= din ^ src_state; //通过异或门做处理
end
//同步至新时钟域
always @(posedge clk_2 or negedge rst_n)
begin
if(rst_n == 1'b0)
begin
src_state_d0 <= 1'b0;
src_state_d1 <= 1'b0;
src_state_d2 <= 1'b0;
end
else
begin
src_state_d0 <= src_state;
src_state_d1 <= src_state_d0;
src_state_d2 <= src_state_d1;
end
end
//边沿检测产生新的脉冲
assign dout = src_state_d1 ^ src_state_d2;
endmodule
// 高频到低频 //
module pulse_syc_tb();
reg clk_1, clk_2, rst_n;
reg din;
always
begin
#10 clk_1 = ~clk_1;
end
always
begin
#30 clk_2 = ~clk_2;
end
initial
fork
clk_1 = 1'b1;
din = 1'b0;
#5 clk_2 = 1'b1;
#10 rst_n = 1'b0;
#50 rst_n = 1'b1;
#100 din = 1'b0;
#200 din = 1'b1; //间隔两个同步周期的脉冲信号
#220 din = 1'b0;
#320 din = 1'b1;
#340 din = 1'b0;
#600 din = 1'b1; //间隔一个同步周期的脉冲信号
#620 din = 1'b0;
#680 din = 1'b1;
#700 din = 1'b0;
#900 din = 1'b1; //等于两个原时钟周期的脉冲信号
#1020 din = 1'b0;
join
pulse_syc u1(.clk_1(clk_1), .clk_2(clk_2), .rst_n(rst_n),
.din(din), .dout(dout));
endmodule
// 低频到高频 //
/*module pulse_syc_tb();
reg clk_1, clk_2, rst_n;
reg din;
always
begin
#30 clk_1 = ~clk_1;
end
always
begin
#10 clk_2 = ~clk_2;
end
initial
fork
clk_1 = 1'b1;
din = 1'b0;
#5 clk_2 = 1'b1;
#10 rst_n = 1'b0;
#50 rst_n = 1'b1;
#100 din = 1'b0;
#200 din = 1'b1; //间隔两个同步周期的脉冲信号
#260 din = 1'b0;
#300 din = 1'b1;
#360 din = 1'b0;
#600 din = 1'b1; //间隔一个同步周期的脉冲信号
#660 din = 1'b0;
#680 din = 1'b1;
#740 din = 1'b0;
#900 din = 1'b1; //等于两个原时钟周期的脉冲信号
#1020 din = 1'b0;
join
pulse_syc u1(.clk_1(clk_1), .clk_2(clk_2), .rst_n(rst_n),
.din(din), .dout(dout));
endmodule
*/
给出Modelsim的仿真结果:
观察上述波形可得如下结论:
- 当输入脉冲间隔为120ns(两个同步时钟周期)时,脉冲同步器可正常完成输出;当输入脉冲间隔为80ns(低于两个同步时钟周期)时,脉冲同步器错误输出了一个更宽的脉冲。因而,脉冲同步器对于脉冲的间隔有比较严格的要求:输入脉冲的最小间隔必须等于两个新时钟的时钟周期。
- 脉冲同步器完成同步的延时为100ns,即在一个同步周期和两个同步周期之间。由于不考虑原时钟域下的触发器以及组合逻辑的延迟,因此,脉冲同步器的延时时间与边沿同步器相同:[T2 + Dsetup + Tlogic, 2T2 + Tlogic]。
###参考文章
- Stein, Mike . “跨越鸿沟: 同步世界中的异步信号.” 电子设计技术 11(7)(2004): 76-76.
- 路科验证笔试题目整理
综上三种同步器(Level,Edge,Pulse)可以完成单bit的跨时钟域,包括高频到低频(P),低频到高频(L, E, P),电平到电平(L),电平到脉冲(E),脉冲到脉冲(P)情况下的信号跨越。但即便在最后一种脉冲同步器设计中,仍有要求脉冲需要满足一定的最小间隔,在输入可能无法满足情况下,无法输出错误信号,以指出是否漏掉一定的输入。鉴于此,需要将握手概念加入,通过请求与应答信号,确定同步器的状态,在输入不满足条件下,给出同步失败的信号。这部分内容准备整理中。
参考文章
- Stein, Mike . “跨越鸿沟: 同步世界中的异步信号.” 电子设计技术 11(7)(2004): 76-76.
- 路科验证笔试题目整理
综上三种同步器(Level,Edge,Pulse)可以完成单bit的跨时钟域,包括高频到低频(P),低频到高频(L, E, P),电平到电平(L),电平到脉冲(E),脉冲到脉冲(P)情况下的信号跨越。但即便在最后一种脉冲同步器设计中,仍有要求脉冲需要满足一定的最小间隔,在输入可能无法满足情况下,无法输出错误信号,以指出是否漏掉一定的输入。鉴于此,需要将握手概念加入,通过请求与应答信号,确定同步器的状态,在输入不满足条件下,给出同步失败的信号。这部分内容准备整理中。