本篇源代码:
https://github.com/holdenQWER/CDC_example
CDC解决方案
EDA厂商提供golden的CDC处理单元,Synopsys的Building Block IP提供如下解决方案:
归类如下:
CDC处理中,根据跨时钟域信号宽度,分为Signle-bit
和Mulit-bit
。VC Spyglass CDC将Single-bit
归为control path
,Mulit-bit
归为data path
Single-bit
Single-bit
分为level 类型
和event 类型
。
Level Signal
level signal
跨时钟域时,会保持较长时间不变,从快时钟域到慢时钟域,还是慢时钟域到快时钟域,都不会漏采样,使用2级同步器即可。
Event Signal
event signal
常看做一个单周期的pulse signal
。无论是快到慢,还是慢到快,都有漏采样的风险。
destination domain
要确保成功采样到event single,需要event single至少维持destination domain
采样时钟3个沿(无论是上升沿还是下降沿,连续的3个沿)。
所以从慢时钟域到快时钟域,如果快时钟域的频率大于慢时钟域的1.5倍,就满足了three edge
的要求。
Fast to Slow
从快时钟域到慢时钟域,需要对快时钟域的信号做展宽处理,以满足慢时钟域的three edge
要求。
可以通过移位寄存器对evnet_signal
做打拍处理,然后自身各bit相或,展宽event signal
。移位寄存器的位宽和clock_ratio相关。这种展宽的方式只适合clock_ratio固定的CDC处理,相邻pulse的间隔也有要求。
Slow to Fast
从慢时钟域到快时钟域,如果频率相差较大,慢时钟域会过采样。可以使用边沿检测的方式,防止过采样。
event_d_dly1
的Q
端和D
端取反后相与,检测上升边沿。evnet_s
需间隔至少一个clk_s
的周期,避免信号重叠漏采。
Pulse Synchronizer
上述两种CDC处理方法都有明显的限制,只能从快到慢或者从慢到快。可以将信号展宽和边沿检测相结合,设计一种通用的pulse synchronizer
。
module pulse_sync(input clk_s,
input rstn_s,
input event_s,
input clk_d,
output event_d);
reg event_s_dly;
reg event_d_dly;
wire event_s_expand;
wire event_d_clr;
wire event_d_sync;
assign event_s_expand = (event_s | event_s_dly) & (~event_d_clr);
always @(posedge clk_s or negedge rstn_s)
if(~rstn_s)
event_s_dly <= 1'b0;
else
event_s_dly <= event_s_expand;
rstn_sync u_rstn_sync(.clk(clk_d),
.rstn_in(rstn_s),
.rstn_out(rstn_d));
sync_cell u_sync_to_dst(.clk(clk_d),
.rst_n(rstn_d),
.in(event_s_dly),
.out(event_d_sync));
sync_cell u_sync_to_src(.clk(clk_s),
.rst_n(rstn_s),
.in(event_d_sync),
.out(event_d_clr));
always @(posedge clk_d or negedge rstn_d)
if(~rstn_d) begin
event_d_dly <= 1'b0;
end
else begin
event_d_dly <= event_d_sync;
end
assign event_d = event_d_sync & ~event_d_dly;
endmodule
src domain
利用或门将边沿信号转化成电平信号;destination domain
采样到信号后回复ack清0。ack信号也需要2级同步处理。(上述电路图缺少了destination domain
边沿检测的部分。)
上述设计的latency比较大,需要2次2级同步处理。event_s
信号要和ack做好handshake。
A Better Design for Pulse Synchronizer
下面介绍一种Design Ware
库中的脉冲同步器:DW_pulse_sync
电路逻辑如下:
将source domain
的两级同步+电平转脉冲的电路结构用简易的图标表示如下:
CODE:
module pulse_sync(input clk_s,
input rstn_s,
input event_s,
input clk_d,
input rstn_d,
output event_d);
reg event_s_toggle;
reg event_d_dly;
wire event_d_sync;
always @(posedge clk_s or negedge rstn_s)
if(~rstn_s)
event_s_toggle <= 1'b0;
else
event_s_toggle <= event_s_toggle ^ event_s;
sync_cell u_2dff_sync(.clk(clk_d),
.rst_n(rstn_d),
.in(event_s_toggle),
.out(event_d_sync));
always @(posedge clk_d or negedge rstn_d)
if(~rstn_d)
event_d_dly <= 1'b0;
else
event_d_dly <= event_d_sync;
assign event_d = event_d_dly ^ event_d_sync;
endmodule
source domain
通过异或门,实现pulse的边沿转电平的操作。这种结构实现不归零编码Non-Return-to-Zero (NRZ)
,相当于一个toggle register。
destimation domain
通过异或门将level转化成一个周期的pulse。
这种不需要ack的脉冲同步器,相比上一种设计,缩减了latency,结构也更简单。
使用时需注意一下两点:
第一:脉冲的频率小于目的域时钟频率的1/2
上述限制出于两点考虑,
1:event_s_toggle
需要满足three edge
,才能被目的域时钟有效采样到,频率相差1.5倍即可。
2:event_d_sync
的level至少需要持续2拍,否则无法恢复出脉冲,也就是说event_s_toggle
至少要被目的域时钟采样两次,频率相差2倍即可。
第二:source domain
复位后,destinantion domain
也需要复位
在奇数脉冲时,电平被toggle为1,只复位source domain
,再次传播脉冲时,第一个脉冲会被漏采。
偶数脉冲时,可以单独复位source domain
。
DW库中也提供了带反馈信号的脉冲同步器Dw_pulseack_sync ,结构如下:
FSM判断ready
busy
:
module pulse_sync_ack(input clk_s,
input rstn_s,
input event_s,
input clk_d,
input rstn_d,
output event_d,
output ack_s);
reg event_s_toggle;
reg event_d_dly;
wire event_d_sync;
wire event_ack;
reg event_ack_dly;
wire ack_pulse;
reg state_cur;
reg state_nxt;
reg ack_r;
parameter READY = 1'b0;
parameter BUSY = 1'b1;
always @(posedge clk_s or negedge rstn_s)
if(~rstn_s)
state_cur <= READY;
else
state_cur <= state_nxt;
always @(*) begin
state_nxt = state_cur;
case (state_cur)
READY:begin
ack_r = 1;
if(event_s)
state_nxt = BUSY;
end
BUSY: begin
ack_r = 0;
if(ack_pulse)
state_nxt = READY;
end
default: begin
state_nxt = READY;
end
endcase
end
assign ack_s = ack_r;
always @(posedge clk_s or negedge rstn_s)
if(~rstn_s)
event_s_toggle <= 1'b0;
else
event_s_toggle <= event_s_toggle ^ (event_s & ack_s);
sync_cell u_2dff_sync_s(.clk(clk_d),
.rst_n(rstn_d),
.in(event_s_toggle),
.out(event_d_sync));
always @(posedge clk_d or negedge rstn_d)
if(~rstn_d)
event_d_dly <= 1'b0;
else
event_d_dly <= event_d_sync;
assign event_d = event_d_dly ^ event_d_sync;
sync_cell u_2dff_sync_d(.clk(clk_s),
.rst_n(rstn_s),
.in(event_d_dly),
.out(event_ack));
always @(posedge clk_s or negedge rstn_s)
if(~rstn_s)
event_ack_dly <= 1'b0;
else
event_ack_dly <= event_ack;
assign ack_pulse = event_ack_dly ^ event_ack;
endmodule
只有在ack_s
为高时,source domain
才可以发送event_s
脉冲,对异步时钟的频率没有要求,latency较大。
Mulit-bit
对于Mulit-bit
的signal,总结如下几类方法:
- 对
Muilt-bit
的各bit分别2级同步器处理。这种方式适用于可以通过格雷编码的设计,如Binary counters
;下级电路对data coherency
不敏感,如被软件处理的interrupt信号;使用更高频率过采样等等。 - 采用
Multi-Cycle Path (MCP )
结构的设计。 - 使用异步FIFO。
上述的方法根据实际业务选择最佳方案,而不是相互取代的。
Multi-Cycle Path (MCP)formulation
MCP解释如下:
DW_data_sync_na
就是一个MCP
结构的设计:
分为control path
, data path
;
control path
需要同步处理,采用的是DW-pulse_sync
;
data path
不需要同步处理,节约寄存器使用;
源和目的端各一个data MUX,由同步的脉冲control信号控制。control使能时,data需保持不变,维持数个Multi-Cycle目的域时钟,直到被采样,避免了data path
上的亚稳态。
DW_data_sync_na
不带ack反馈,若要支持back-to-back
的传输,需要destination domain
频率比source domain
频率快三倍。
如下图,频率2倍关系,dataf2
和data49
back -to-back
发送,dataf2
miss captured。DW_pulse_sync
并不支持back-to-back
的同步,需要额外逻辑设计,所以至少要3倍频率。
DW_data_sync
则是带ack反馈的data同步器,结构类似如下:
是将上述DW_pulseack_sync
的输出脉冲作为data MUX
的sel
。
上述电路默认destination domain
一直可以接收source domain
的数据。
下面介绍一种destination domain
可以通过load
信号控制是否准备接受source doamin
数据的流控结构:
从电路图可以看出,source domain
destination domain
结构对称,destination domain
的FSM划分为 idle
wait
ready
的三个状态。
ack_s
拉高时,src
端拉高data_vld_s
发送有效数据,期间data_s
保持不变。随后ack_s
拉低。- 数拍后
data_vld_d
有效,并持续为高。直到dst
端拉高load_d
对data_vld_d
下的data_d
采样。 - 完成采样后,
ack_s
拉高,src
端可以进行下一次操作。
CODE:
module data_sync(input clk_s,
input rstn_s,
input data_vld_s,
input [7:0] data_s,
input clk_d,
input rstn_d,
input load_d,
output data_vld_d,
output reg [7:0] data_d,
output ack_s);
reg state_curs;
reg state_nxts;
reg [1:0]state_curd;
reg [1:0]state_nxtd;
reg ack_r;
parameter READYS = 1'b0;
parameter BUSYS = 1'b1;
parameter IDLED = 2'b00;
parameter WAITD = 2'b01;
parameter READYD = 2'b10;
reg [7:0] data_r;
reg data_vld;
wire data_sel_s;
wire data_sel_d;
wire ack_sync;
wire data_vld_sync;
// source domain data MUX
always @(posedge clk_s or negedge rstn_s)
if(~rstn_s)
data_r <= 8'h0;
else if (data_sel_s)
data_r <= data_s;
// destination domain data MUX
always @(posedge clk_d or negedge rstn_d)
if(~rstn_d)
data_d <= 8'h0;
else if (data_sel_d)
data_d <= data_r;
// source domain FSM
always @(posedge clk_s or negedge rstn_s)
if(~rstn_s)
state_curs <= READYS;
else
state_curs <= state_nxts;
always @(*) begin
state_nxts = state_curs;
case (state_curs)
READYS:begin
ack_r = 1;
if(data_vld_s)
state_nxts = BUSYS;
end
BUSYS: begin
ack_r = 0;
if(ack_sync)
state_nxts = READYS;
end
default: begin
state_nxts = READYS;
end
endcase
end
assign ack_s = ack_r;
assign data_sel_s = data_vld_s & ack_s;
// source to destination pulse sync
pulse_sync u_pulse_sync_s(.clk_s(clk_s),
.rstn_s(rstn_s),
.event_s(data_sel_s),
.clk_d(clk_d),
.rstn_d(rstn_d),
.event_d(data_vld_sync));
// destination domain FSM
always @(posedge clk_d or negedge rstn_d)
if(~rstn_d)
state_curd <= IDLED;
else
state_curd <= state_nxtd;
always @(*) begin
state_nxtd = state_curd;
case (state_curd)
IDLED:begin
data_vld = 0;
if(data_vld_sync)
state_nxtd = WAITD;
end
WAITD:begin
data_vld = 1;
if(load_d)
state_nxtd = READYD;
end
READYD:begin
data_vld = 0;
if(!load_d)
state_nxtd = IDLED;
end
default:begin
state_nxtd = IDLED;
data_vld = 0;
end
endcase
end
assign data_sel_d = load_d & data_vld;
assign data_vld_d = data_vld;
// destination to source domain sync
pulse_sync u_pulse_sync_d(.clk_s(clk_d),
.rstn_s(rstn_d),
.event_s(data_sel_d),
.clk_d(clk_s),
.rstn_d(rstn_s),
.event_d(ack_sync));
endmodule
AHB to APB asynchronous bridge
设计采用的也是MCP
结构,只需要同步req/ack
握手信号。
异步FIFO
异步FIFO适用对缓冲有要求的异步数据流的设计。DW提供了常见的两种fifo,DW_fifo_s2_sf
和DW_fifo_2c_df
。
异步FIFO的设计难点主要在于异步读写指针的比较和空满状态的产生。具体参考:🔗 Simulation and Synthesis Techniques for Asynchronous FIFO Design with Asynchronous Pointer Comparisons – Cliff Cummings
在使用DW_fifo_s2_sf
需要注意,clk_push
和clk_pop
时钟域由一个rst_n
控制,VC Spyglass CDC检测时会报error。可以将fifo设置为blackbox或者将rst_n设置为static。
为了避免复位时的亚稳态,需要rst_n
拉低至少三个慢时钟的周期,并在一个clk_push
周期后才可以push数据。建议rst_n
使用clk_push
的复位信号。
Qasi Synchronous
时钟同源,由同一个分频器产生,频率为整数倍关系,相位差固定,可以看成qasi synchronous
准同步电路,可以当作同步电路处理,ctrl path
不需要同步处理。
DW_data_qsync_lh
为快时钟域到慢时钟域的传输。
DW_data_qsync_hl
为慢时钟域到快时钟域的传输。
使用时配置clk_ratio
参数。当clk_ratio
= 2时,负责采样的第一个触发器使用下降沿采样,利于时序管理;其余情况所有触发器都是上升沿采样。
clk_ratio=2
时DW_data_qsync_lh
结构如下:
clk_ratio=2
时DW_data_qsync_hl
结构如下: