目录
跨时钟传输中,亚稳态是如何形成的?
多比特的跨时钟传输——数据准确性以及关联性问题
如何解决多比特跨时钟传输中的数据准确性问题?
方法一:多比特信号合成单比特信号
方法二:MUX/DMUX同步器
方法三:格雷码编码方式
方法四:异步FIFO
方法五:握手机制
方法六:多周期路径同步法,MCP
需要用到跨时钟的场景:
1.单比特:单比特慢到快的跨时钟传输(电平同步器、边沿检测同步电路)、单比特快到慢传输引起的数据丢失问题(脉冲同步/脉冲检测)。
2.多比特:数据准确性和关联性问题。
Din在从CLOCK1时钟域变到CLOCK2时钟域时,如下图所示,假如Din刚好在Clock2的采样窗口变化,且不满足建立保持时间,就会产生亚稳态,也就是Dout会在一个电压范围内浮动,如果在Dout浮动期间采样Dout信号,那么采样到的值可能是正确值,也可能不是。
首先明确什么样的多比特信号才是多比特信号?
多比特信号指的是一个变量的表示是由多个比特位构成,这些比特位之间是相互关联的,比如位宽为32位的ADDR。
而像几个独立的单比特信号组合到一起,在同步之后也是各自起作用的,这样的"多比特信号"就是伪多比特信号,依然可以采用每个比特打两拍的方式去同步。
多比特信号为什么不可以采用打两拍的方式同步?
如下图所示,2比特信号data从aclk时钟跨到bclk时钟,data信号的两个比特位同时发生变化,刚好发生在目标时钟bclk的采样沿,由于双触发同步具有随机性,假设data的第一比特信号被正确采样并同步,而第二比特信号的同步触发器没有采样到,就会延迟一个周期,就会造成同步过去的数据发生错误,如上图中,由于第二比特出现了延迟,导致生成了错误的2'b01的中间态信号。
因此对于多比特信号按照打两拍的方式进行跨时钟传输,就会由于各个比特穿越时间不一致,而造成目标时钟域出现一些中间态无意义数据,也就是数据的准确性和关联性出现了问题
解决多比特的CDC问题有六种方法:
①多比特信号合成单比特信号
②MUX同步器
③格雷码编码
④异步FIFO
⑤握手机制
⑥多周期路径同步MCP
假如多比特信号之间存在逻辑关联性,就可以将源时钟信号合成一个单一的控制信号,然后进行两级寄存器同步。如下图所示,多比特信号经过logic电路合成单比特信号后要经过寄存器寄存一下,因为组合逻辑电路易产生毛刺。
如果多比特信号无法融合成一个信号,那么方法一就不可用。那么就可以采用方法二:MUX同步器(MUX synchronizer)
使用MUX同步器要求被同步的数据跟随一个使能信号,当使能信号有效时,数据才会被同步。
如下图所示:(以慢到快为例)将使能信号从源时钟过渡到目的时钟后,作为MUX的选择信号,当enable有效时再进行同步,保证了所有bit在同一个使能信号有效时刻同步,避免了多比特信号传输中的非关联问题。
可以看出,MUX同步器主要应用于带有数据有效标志位的多比特CDC问题。
MUX同步器应用在慢到快的CDC时,数据长度应该至少为m+1个目标时钟周期,m指的是同步器数量,此处为m=2。
将上述电路使用verilog语言描述:
module mux_syn(
input clka,
input clkb,
input rst_n,
input [3:0] data_in,
input data_en,
output reg data_out
);
reg data_en_ar;
reg data_en_br;
reg data_en_brr;
reg [3:0] data_r;
//先处理源时钟下的信号
always@(posedge clka or negedge rst_n)begin
if(!rst_n)begin
data_r <= 4'b0;
data_en_ar <= 1'b0;
end
else begin
data_r <= data_in;
data_en_ar <= data_en;
end
end
//处理目的时钟下的信号
always@(posedge clkb or negedge rst_n)begin
if(!rst_n)begin
data_en_br <= 1'b0;
data_en_brr <= 1'b0;
end
else begin
data_en_br <= data_en_ar;
data_en_brr <= dada_en_br;
end
end
//使用MUX输出
always@(posedge clkb or negedge rst_n)begin
if(!rst_n)begin
data_out <= 4'b0;
end
else if(data_en_brr)begin
data_out <= data_r;
end
end
endmoudle
由于格雷码相邻两位只有一位发生变化,相当于将多比特的CDC转换成了单比特的CDC。在跨时钟传输时可以大大降低发生亚稳态发生的概率。
但这种方法有使用限制:
1.由于格雷码是相邻两位只有一位变化,因此格雷码又能用在数据连续变化的情况下,一般用于计数器,比如异步fifo中的读写指针。
2.格雷码的变化周期是2^N。因此,如果数据的取值范围不是2^N,就无法使用格雷码。
异步fifo相当于将源时钟数据缓存起来,然后目标时钟从缓存中取数据,常用于数据流的跨时钟传输,相对于握手机制,异步FIFO的传输速度较快。
异步FIFO的设计参考:https://blog.csdn.net/carrotbanana/article/details/126338269
握手机制是一种闭环的数据同步方法,握手机制最基本的方法是数据锁存和握手信号。在源时钟域对数据进行锁存,发出请求信号,用于指示目标时钟域何时对总想数据进行采样。在目的时钟域产生应答信号用于指示源时钟域何时更新寄存器中的数据。
握手机制经典题目:分别编写一个发送模块和一个接收模块,模块的时钟信号分别为clka和clkb,两个时钟频率不相同。发送模块循环发送0-7,每个数据传输网还曾后,间隔5个时钟,发送下一个数据。在两个模块之间添加握手信号,保证数据传输不丢失。接口信号图如下:req为请求信号,ack为应答信号。
可以参考单比特中的握手机制(跨时钟传输——单比特_carrotbanana的博客-CSDN博客),只不过这里将整个模块拆分成了发送模块和接收模块。
发送模块:
首先从数据发送开始,总线上放置data数据,然后req信号置1,在req为1期间,总线数据保持不变,以便能被接收模块接收。
当接收模块接收到数据后,会返回ack应答信号,该应答信号属于接收时钟域,因此需要将该应答信号同步到发送时钟域,该信号是一个单比特信号,采用两级同步器即可。
当接收时钟域的两级触发器ack信号为01时,下一个采样延,采集到的ack有效信号将变为11,表明握手传输完成,此时会撤销req请求信号,并复位计数器,开始准备计数五个时钟周期。
由于req请求信号已经撤销,因此总线数据可以进行更新(本题中是数据加一操作),注意数据更新操作在ack两级同步器为01的时刻开始更新,如果在ack为11的时候,更新会一直更新,因为11会持续五个时钟周期。那么数据就会更新五次。
当计数器记满五个时钟周期,req信号重新变得有效,开始重新请求发送数据然后继续循环往复。。。
module data_driver(
input clka,
input rst_n,
input data_ack,
output data_req,
output [3:0] data
);
reg data_ack_r;
reg data_ack_rr;
reg [2:0] cnt;
//应答信号同步,采用两级同步器。
always@(posedge clka or negedge rst_n)begin
if(!rst_n)begin
data_ack_r<=4'b0;
data_ack_rr<=4'b0;
end
else begin
data_ack_r<=data_ack;
data_ack_rr<=data_ack_rr;
end
end
//计数器计数:每发送一个数据,就要计数五个周期。
always@(posedge clka or negedge rst_n)begin
if(!rst_n)begin
cnt<=3'b0;
end
else if(data_req)begin //在data_req有效期间,数据不能变化,计数停止。
cnt<=cnt;
end
else if(data_ack_r && !data_ack_rr)begin //ack信号在clka时钟域的两个寄存器的值为01,的时候,说明下一个时刻,ack_rr信号将为1,也就是说下一个采样时刻,完成握手传输,计数复位,准备计数。
cnt<=3'b0;
end
else begin
cnt<=cnt+1'b1;
end
end
//发送数据请求,当计数器记满五个周期时,请求发送信号置1,此外,握手机制的应答信号ACK有效时,请求信号会撤销
always@(posedge clka or negedge rst_n)begin
if(!rst_n)begin
data_req<=1'b0;
end
else if(data_ack_r && !data_ack_rr) //反馈信号ACK有效时,撤销req
data_req<=1'b0;
else if(cnt==3'd4)begin
data_req<=1'b1;
end
//数据发送:当req为1时,数据保持不变,当反馈信号回来时,说明上一次的传输已经完成,可以开始更新数据
always@(posedge clka or negedge rst_n)begin
if(!rst_n)begin
data<=4'b0;
end if(data_req)begin
data<=data;
end
else if(data_ack_r && !data_ack_rr)begin
data <= data+1'b1;
end
end
endmodule
接收模块:当接收模块接收到req有效信号后,将信号同步到接收时钟域后,接收模块开始采样数据,并返回应答信号ack。
当接收模块接收到的req信号变为0时,接收模块撤回应答信号。
module data_receiver(
input clkb,
input rst_n,
input data_req,
input [3:0] data,
output data_ack
);
reg data_req_r;
reg data_req_rr;
//同步REQ信号
always@(posedge clkb or negedge rst_n)begin
if(!rst_n)begin
data_req_r<=1'b0;
data_req_rr<=1'b0;
end
else begin
data_req_r<=data_req;
data_req_rr<=data_req_r;
end
end
//发送ACK信号,实际上ack信号就是req信号在clkb时钟域上的一个寄存信号
always@(posedge clkb or negedge rst_n)begin
if(!rst_n)begin
data_ack<=1'b0;
end
else if(data_req_rr)begin
data_ack<=1'b1;
end
else
data_ack<=1'b0;
end
//接受数据data
reg [3:0] data_in_reg;
always@(posedge clkb or negedge rst_n)begin
if(!rst_n)begin
data_in_reg<=4'b0000;
end
else if(data_req_r && !data_req_rr)begin
data_in_reg<=data;
end
end
endmodule
将发送模块与接收模块连接起来
module top(
input clka,
input clkb,
input rst_n
);
wire ack;
wire req;
wire [3:0]data;
data_drivrer data_driver(
.clk(clka)
.rst_n(rst_n)
.data_ack(ack)
.data_req(req)
.data(data)
);
data_receiver data_receiver(
.clk(clkb)
.rst_n(rst_n)
.data_ack(ack)
.data_req(req)
.data(data)
);
endmodule
多周期路径同步法将MUX同步与握手机制结合起来,被同步的数据同样需要一个数据使能信号,数据信号与使能信号被同时同步到目标时钟域,并将使能信号作为反馈信号,返回给源时钟域,完成握手。
参考:
FPGA学习笔记——跨时钟域(CDC)设计之多bit信号同步-pudn.com
FPGA逻辑设计回顾(5)多比特信号的CDC处理方式之MUX同步器 - 知乎
跨时钟域的方法--多周期路径 - 知乎
FPGA学习记录——牛客网刷题(二)-pudn.com