单比特信号跨时钟域传输
- 参考链接:https://www.cnblogs.com/rouwawa/p/7501319.html#4527113
1、时钟域
- 单时钟域:电路中所有触发器都是用一个全局网络,比如FPGA的主时钟输入
- 多时钟域:设计中有多个时钟输入
2、亚稳态
- 含义:触发器的输出无法在某个规定时间内达到一个确定的状态
- 说明:在建立时间和保持时间定义的时间窗口上,若触发器的数据输入端口发生变化,就会产生时序违规(不满足建立时间、保持时间),从而使得输出为亚稳态(不能设置为逻辑0或逻辑1对应的电平)
- 原因:
- 不满足 建立时间/保持时间
- 解决:
- 1、相位控制:可在一个时钟频率是另一个时钟的数倍,并且其中一个时钟可以由FPGA内部PLL 或 DLL 控制时使用。
- 2、多级寄存器(打多拍):
- 适用于:单 bit 信号跨越两个异步时钟域传输
- 同步电路的第一拍也许会产生亚稳态,但是一般会在第二级及之后的寄存器中稳定下来。
- 常用:对 单bit 信号 打两拍
- 3、异步FIFO缓存
- 适用于:跨时钟域数据传输。写端和读端分别对应两个时钟域,由空/满信号控制着读写过程,实现数据的跨域传输。
3、多级寄存器处理
- 适用于:单bit信号进行跨时钟域传输
- 假设A,B是两个时钟域,各自的频率是 clk_a 和 clk_b ,clk_a的频率高于 clk_b,则单bit信号传输分为两种情况
3.1 信号从B到A(慢到快)
- 慢时钟域 到 快时钟域:脉冲信号 pulse_b 相对于快时钟 clk_a 来说是很宽的电平信号,可直接采样
- 注意:经验设计采集过程必须寄存两拍,第一拍将输入信号同步化,同步化后的输出可能产生亚稳态,需要再寄存一拍,减少亚稳态带来的影响。
- 一般两级是基本要求,若是高频率设计,则需要增加寄存器级数来大幅降低系统的不稳定性 —— 采用多级触发器来采样来自异步时钟域的信号,级数越多,同步过来的信号越稳定。
- pulse_b 必须是 clk_b 下的寄存器信号,若 pulse_b 是 clk_b 下的组合逻辑信号,一定要先用 D触发器(DFF)抓一拍,再使用两级DFF 向 clk_a 传递
- 原因:clk_b 下的组合逻辑信号会有毛刺,在clk_b 下使用时会由 setup/hold 时间保证毛刺不被 clk_b 采到,但由于异步相位不确定,组合逻辑的毛刺却极有可能被 clk_a采到。
// 这里打了3拍,也就是将pulse_b输入到快时钟 clk_a驱动的触发器组(三个)
always@(posedge clk_a or negedge rst_n)
begin
if(rst_n == 1'b0)
begin
pulse_a_r1<=1'b0;
pulse_a_r2<= 1'b0;
pulse_a_r3<=1'b0;
end
else
begin
pulse_a_r1<=pulse_b;
pulse_a_r2<=pulse_a_r1;
pulse_a_r3<=pulse_a_r2; //打3拍
end
end
assign pulse_a_pos = pulse_a_r2 &(~pulse_a_r3);
assign pulse_a_neg = (~pulse_a_r2) & pulse_a_r3;
assign pulse_a = pulse_a_r2;
3.2 信号从A到B(快到慢)
- 单 bit 信号从快时钟域到慢时钟域
- 传输电平信号 level_a:只有电平信号 level_a 的宽度能被 clk_b 采集到,才可以保证系统正常工作
- 传输脉冲信号 pulse_a:用一个展宽信号 替代 pulse_a ,实现跨时钟域握手
- 原理:先把脉冲信号 pulse_a 在快时钟 clk_a 下展宽,变成电平信号 signal_a ,再向 慢时钟 clk_b 传递,当确认 clk_b 已经“看到” 信号同步过去之后,再清除掉 signal_a。
module Sync_Pulse(
input clk_a, // 快时钟
input clk_b, //慢时钟
input rst_n,
input pulse_ina, //输入信号
output pulse_outb,
output signal_outb);
reg signal_a;
reg signal_b;
reg [1:0] signal_b_r;
reg [1:0] signal_a_r;
// 在 clk_a下,生成展宽信号 signal_a
always @(posedge clk_a or negedge rst_n)
begin
if(rst_n == 1'b0)
begin
signal_a <= 1'b0;
end
else if(pulse_ina == 1'b1)
begin
signal_a <= 1'b1; //定义展宽信号的开始
end
else if(single_a_r[1] == 1'b1)
begin
signal_a <= 1'b0; // 定义展宽信号的结束
end
else
signal_a <= signal_a;
end
// 在慢时钟 clk_b 下同步 signal_a
always@(posedge clk_b or negedge rst_n)
begin
if(rst_n == 1'b0)
begin
signal_b <= 1'b0;
end
else
begin
signal_b <= signal_a; //同步signal_a到慢时钟下的 signal_b
end
end
// 在慢时钟clk_b下生成脉冲信号和输出信号
always@(posedge clk_b or negedge rst_n)
begin
if(rst_n == 1'b0)
begin
signal_b_r <= 2'b00;
end
else
begin
signal_b_r <= {signal_b_r[0],signal_b}; //将同步后的信号signal_b 传入 signal_b_r中
end
end
assign pulse_outb <= ~signal_b_r[1] &signal_b_r[0]; //生成慢时钟clk_b下的脉冲信号
assign signal_outb <= signal_b_r[1]; //输出信号
// 在clk_a 下采集signal_b[1],生成signal_a[1],用于反馈拉低 signal_a
always@(posedge clka or negedge rst_n) //注意这里是在快时钟域 clka 下
begin
if(rst_n == 1'b0)
begin
signal_a_r <= 2'b00;
end
else
begin
signal_a_r <= {signal_a_r[0],signal_b_r[1]}; //生成反馈信号signal_a_r,用于拉低展宽信号 signal_a
end
end
endmodule
- 代码说明
- 在快始终域clk_a下,将脉冲信号 pulse_ina 展宽(上升沿对应 pulse_ina 在clk_a 下的上升沿,下降沿对应 反馈信号signal_a_r的下拉)生成 signal_a 信号 —— 过程1
- 将 signal_a 在 慢时钟 clk_b 下进行采样得到 signal_b —— 过程2
- signal_b 在慢时钟 clk_b 下生成反馈信号 signal_b_r,为了防止亚稳态, signal_b_r 为 2bit —— 过程3
- 然后生成慢时钟下的脉冲信号 pulse_outb ,以及 输出信号 signal_outb —— 过程4,5
- 再将反馈信号 signal_b_r 传输到 快时钟域 clk_a 下,生成signal_a_r ,用来拉低 展宽信号 signal_a , —— 过程7,1,2
- 跨时钟域传输,所以将 signal_a_r 也设置为 2bit ,防止亚稳态
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-RrxZYzaU-1602333581224)(assets/image-20200404092803763.png)]
- 这种通用代码框架适用于
- 慢到快,单脉冲
- 慢到快,长信号传输 (慢到快可以用上面简单的,也可以用这个通用框架)
- 快到慢,单脉冲
- 快到慢,长信号传输
- 设计中牢记五条原则:
- 1、在全局时钟的跳变沿最可靠
- 2、来自异步时钟域的输入需要寄存一次以同步化,再寄存一次以减少亚稳态带来的影响
- 3、不需要用到跳变沿的来自同一时钟域的输入,没有必要对信号进行寄存
- 4、需要用到跳变沿的来自同一时钟域的输入,寄存一次即可
- 5、需要用到跳变沿的来自不同时钟域的输入,需要用到3个触发器,前两个用以同步,第3个触发器的输出和第2个触发器的输出经过逻辑门来判断跳变沿