常见的跨时钟有以下相关的问题:
单bit:
单bit 电平信号
单bit 脉冲信号:
慢时钟到快时钟
快时钟到慢时钟
多bit:
多bit 控制数据:
快时钟到慢时钟
慢时钟到快时钟
自带vld 有效信号
不自带vld 有效信号
多bit 连续数据:
慢时钟到快时钟
快时钟到慢时钟
自带vld 有效信号
不自带vld 有效信号
单bit 电平信号
对于电平信号的快时钟,只要遵循3沿原则,就可以实现任意的快慢时钟间的跨时钟。
单bit 脉冲信号
快时钟到慢时钟
快时钟到慢时钟的电平,若2个时钟的频率跨度很大,直接跨时钟,在慢时钟侧有概率会丢掉快时钟传递过来的数据 。

脉冲转电平
为了实现任意跨度的快时钟到慢时钟间脉冲采样。 可以快时钟的plus 转为level 信号。在慢时钟侧,对level 采样并捕获边沿。

脉冲展宽
根据快时钟和慢时钟的频率关系,将快时钟的脉冲信号使用shift reg 后,对脉冲展宽(展宽后的脉冲在慢时钟域满足三个沿的原则)。 在慢时钟域,使用同步器对展宽后的脉冲采样,并捕获器上升沿作为跨时钟后的脉冲信号。


补充说明下,为什么至少有三个沿???
为什么2沿不行,2个沿的情况有以下几种形式:
先下降沿 再上升沿:若慢时钟在上升沿采样,不一定保证t_hd 可以满足
先上升沿 再下降沿:若慢时钟在下降沿采样,不一定保证t_hd 可以满足
而3沿原则可以保证中间的采样沿的t_st和h_hd 时间是充足的。
慢时钟到快时钟
同步器+边沿检测
慢时钟到快时钟,在快时钟侧存在对同一个慢时钟数据的多次采样。 可以使用同步器+边沿检测,实现对快时钟侧的脉冲采样。

NOTE :有时候我们在做设计时,会将脉冲信号直接用2/3 拍DFF 组成的synczr 做同步。这种方式的前提是,设计者做过时钟频率的分析,可以保证。
握手
握手机制在单bit 上,使用的很少。 但是可以精确的保证脉冲可以被同步过去 。握手机制常用与多bit的数据跨时钟。 在单bit的脉冲跨时钟上,可以理解为隐含1bit的 数据跨时钟。
多bit含vld的跨时钟
多bit的数据,在跨时钟时。 若使用同步器跨时钟存在问题: 不能保证所有bit 的稳定时间相同(同步器的一级DFF 同步部分bit,同步器的二级dff 同步部分bit),造成同步后的数据出错。
所以常用的同步方法包括: 握手机制、异步fifo 、gray码转换
burst 数据
对于burst的数据,数据会背靠背的给。 握手机制存在req 和ack 过同步器时间消耗,一次数据同步消耗多个周期,造成数据丢失。
多以对于burst 数据,常采用的异步跨时钟方法时: 异步缓存。
这里也涉及到 异步缓存fifo的最小深度计算:
写快读慢,易造成写满 ;写慢读快,易造成读空。
常见的计算是写或者读是否有背靠背(写玩或者读完一整包后,连续来一整包的写、读)?
例如: 写时钟100Mhz ,读时钟75Mhz ,最大连续包长为1500bytes,fifo 位宽为64bits(8bytes) 。没有背靠背
1500/8 = 188 cycles
写一包需要188 * 10 ns=1880ns。
1880/(1000/75) = 141 cycles
188-141 = 47 。
理论上fifo的最小深度是47 。 但是如果fifo读有延时、fifo 需要吸收前级的pipe(链路上的延时)。 此时fifo深度要加上链路上的时延和读的延时。
如果存在背靠背呢?
也就是写完一包后,继续写一包,可以理解为包长扩展到1500x2=3000 bytes 。 此时理论深度为47x2 = 94。
还有一类题型: 读时钟为75Mhz,但是读侧是3 拍读一次,求fifo深度?
fifo的读频率降为75/3=25Mhz ,没有背靠背下,需要深度为47x3 = 141 。
异步fifo的核心是:
读写ptr的跨时钟生成empty 和full 信号;
gray 和bin的互转;
binary 转gray
gray= bin ^ (bin>>1);
gray 转binary
bin[n] = gray[n]
bin[n-1] = gray[n-1] ^ bin[n]
......
bin[0] = gray[0] ^ bin[1]
读写指针 位宽增加1bit,借助gray 的镜像实现快慢判断:
空 : wr_sync == rd_sync
满: rd_sync[n-:2] == ~wr_ptr[n-:2] && ( rd_sync[n-2:0] == wr_ptr[n-2:0] )
满是高位和次高位取反,其他bit相同
控制数据
控制数据,不是每拍数据都有效。 中间的间隔就给了握手处理预留了时间。

cpp
//using handshak for data sync
// data has valid
module data_vld_sync#(
parameter DATA_WDTH=8
)(
input in_clk,
input rst_n,
input in_vld,
input [DATA_WDTH-1:0]in_data,
input out_clk,
input out_vld,
input [DATA_WDTH-1:0]out_data
);
reg [DATA_WDTH-1:0] in_data_r;
reg req;
reg ack;
reg [2:0] req2dst;
reg [2:0] ack2src;
wire ack2src_pos;
wire req2dst_pos;
wire ack_neg;
assign ack2src_pos = ack2src[1] && (ack2src[2]==1'b0);
assign req2dst_pos = (req2dst[1] && (req2dst[2]==1'b0);
//req
always_ff @(posedge in_clk or negedge rst_n) begin
if(rst_n==1'b0)begin
req<=1'b0;
end
else if(ack2src_pos)begin
req <=1'b0;
end
else if(in_vld)begin
req <=1'b1;
end
end
// req2dst
always_ff @(posedge out_clk or negedge rst_n) begin
if(rst_n==1'b0)begin
req2dst <= 3'b0;
end
else begin
req2dst <= {req2dst[1:0],req};
end
end
//ack
always_ff @(posedge out_clk or negedge rst_n) begin
if(rst_n==1'b0)begin
ack <=1'b0;
end
else if(req==0)begin
ack <=1'b0;
else if( req2dst_pos )begin
ack <=1'b1;
end
end
// ack2src
always_ff @(posedge in_clk or negedge rst_n) begin
if(rst_n==1'b0)begin
ack2src <=3'b0;
end
else begin
ack2src <= {ack2src[1:0],ack};
end
end
// latch in_data
always_ff @(posedge clk or negedge rst_n) begin
if(rst_n==1'b0)begin
in_data_r <={DATA_WDTH{1'b0}};
end
else if(req==1'b0 && in_vld) begin
in_data_r <= in_data;
end
end
assign out_data = in_data_r;
assing out_vld = req2dst_pos;
endmodule
