MCP公式是指将不同步的数据发送到与同步控制信号配对的接收时钟域。数据和控制信号同时发送,允许数据在目标寄存器的输入端进行设置,同时控制信号在到达目标寄存器的负载输入端之前同步两个接收时钟周期。
优点:
1.发送时钟域不需要计算要在时钟域之间发送的适当脉冲宽度。
2.发送时钟域只需要将enable切换到接收时钟域,以指示数据已经传递并准备加载。使能信号不需要返回到其初始逻辑级别。
这种方法不同步地传输多个CDC信号,同时向接收时钟域传递一个同步的enable信号。在synchronized enable通过同步到达接收寄存器之前,接收时钟域不允许采样多位CDC信号。也被称为多周期路径,由于同步的数据字是直接传递给接收时钟域和举办多个接收时钟周期,允许一个启动信号同步并认识到接收时钟域允许同步的数据字改变之前。
图1
在图1中,d的输入在周期1中被拉高;周期4时,通过三个触发器传输q3被拉高;红虚线框内,在周期3时,q2、q3触发器的输出相反,异或可形成使能脉冲。同理,d输入在周期7拉低,周期9 时异或产生使能脉冲。
图2
图2为等效电路,将产生脉冲电路简化为右边的图。
图3
q-ouput跟随d输入所延迟的三个周期。q-output常作为反馈信号,并通过发送时钟域中的另一个同步使能作为确认信号。
//两级寄存
module sync2FF#(
parameter DATAWIDTH = 8
)
(
input CLK,
input RSTn,
input D,
output reg Q
);
reg Q1;
always @(posedge CLK or negedge RSTn) begin
if (!RSTn) begin
// reset
Q <= 0;
Q1 <= 0;
end
else begin
Q1 <= D;
Q <= Q1;
end
end
endmodule
//source domain
module src_domain#(
parameter DATAWIDTH = 8
)
(
input CLK ,
input RSTn ,
input [ DATAWIDTH - 1 : 0 ] src_data_in ,
output reg [ DATAWIDTH - 1 : 0 ] src2dest_data ,
output reg src2dest_load ,
output reg src_data_valid
);
reg [ DATAWIDTH - 1 : 0 ] src_data_in_reg;
always @(posedge CLK or negedge RSTn) begin
if (!RSTn) begin
// reset
src2dest_data <= 0;
src2dest_load <= 0;
src_data_in_reg <= 0;
end
else begin
src2dest_load <= src2dest_load ^ src_data_valid;
src_data_in_reg <= src_data_in;
end
if (src_data_valid) begin
src2dest_data <= src_data_in;
end
end
/**********src_data_in changed signal**********/
always @(posedge CLK or negedge RSTn) begin
if (!RSTn) begin
// reset
src_data_valid <= 0;
end
else if (src_data_in!=src_data_in_reg)
begin
src_data_valid <= 1;
end
else
src_data_valid <= 0;
end
endmodule
module dest_domain#(
parameter DATAWIDTH = 8
)
(
input CLK ,
input RSTn ,
input [ DATAWIDTH - 1 : 0 ] src2dest_data ,
input src2dest_load ,
output reg dest_data_valid ,
output reg [ DATAWIDTH - 1 : 0 ] dest_data_out
);
wire dest_valid;
wire ldtoggle_sy;
reg ldtoggle_sy_dl;
always @(posedge CLK or negedge RSTn)
begin
if (!RSTn)
begin
dest_data_valid <= 0;
dest_data_out <= 0;
ldtoggle_sy_dl <= 0;
end
else
begin
ldtoggle_sy_dl <= ldtoggle_sy;
dest_data_valid <= dest_valid;
end
end
assign dest_valid = ldtoggle_sy ^ ldtoggle_sy_dl;
always @(posedge CLK or negedge RSTn)
begin
if (dest_valid)
begin
dest_data_out <= src2dest_data;
end
end
sync2FF sync2FF_inst( .CLK (CLK ) ,
.RSTn (RSTn ) ,
.D (src2dest_load ) ,
.Q (ldtoggle_sy )) ;
endmodule
//top module
module src2dest#(
parameter DATAWIDTH = 8
)
(
input src_CLK ,
input dest_CLK ,
input RSTn ,
input [ DATAWIDTH - 1 : 0 ] src_data_in ,
output [ DATAWIDTH - 1 : 0 ] dest_data_out ,
output src_data_valid ,
output dest_data_valid
);
wire [ DATAWIDTH - 1 : 0 ] src2dest_data;
wire [ DATAWIDTH - 1 :0 ] src2dest_load;
src_domain src_domain_inst( .CLK (src_CLK ) ,
.RSTn (RSTn ) ,
.src_data_in (src_data_in ) ,
.src2dest_data (src2dest_data ) ,
.src2dest_load (src2dest_load ) ,
.src_data_valid (src_data_valid )) ;
dest_domain dest_domain_inst(.CLK (dest_CLK ) ,
.RSTn (RSTn ) ,
.src2dest_data (src2dest_data ) ,
.src2dest_load (src2dest_load ) ,
.dest_data_valid (dest_data_valid) ,
.dest_data_out (dest_data_out )) ;
endmodule
//testbench
module src2dest_tb();
parameter DATAWIDTH = 8;
reg src_CLK ;
reg dest_CLK ;
reg RSTn ;
reg [ DATAWIDTH - 1 : 0 ] src_data_in ;
wire [ DATAWIDTH - 1 : 0 ] dest_data_out ;
wire src_data_valid ;
wire dest_data_valid ;
initial
begin
src_CLK = 0;
forever #20 src_CLK <= ~src_CLK;
end
initial
begin
dest_CLK = 0;
forever #30 dest_CLK <= ~dest_CLK;
end
initial
begin
RSTn = 0;
#10
RSTn = 1;
end
initial
begin
src_data_in <= 0;
#200 src_data_in <= 8'd2;
#240 src_data_in <= 8'd20;
#200 src_data_in <= 8'd22;
#280 src_data_in <= 8'd11;
#300 src_data_in <= 8'd3;
#340 src_data_in <= 8'd6;
#300 src_data_in <= 8'd7;
#240 src_data_in <= 8'd8;
#240 src_data_in <= 8'd13;
#300 src_data_in <= 8'd24;
#300 src_data_in <= 8'd35;
#240 src_data_in <= 8'd17;
#400 src_data_in <= 8'd18;
#260 src_data_in <= 8'd21;
#240 src_data_in <= 8'd13;
#300 src_data_in <= 8'd25;
#240 src_data_in <= 8'd36;
#280 src_data_in <= 8'd47;
#400 src_data_in <= 8'd63;
#440 src_data_in <= 8'd32;
#800
$stop;
end
src2dest src2deat_inst( .src_CLK (src_CLK ) ,
.dest_CLK (dest_CLK ) ,
.RSTn (RSTn ) ,
.src_data_in (src_data_in ) ,
.dest_data_out (dest_data_out ) ,
.src_data_valid (src_data_valid ) ,
.dest_data_valid (dest_data_valid)) ;
endmodule
总结:
跨时钟域电路设计使用广泛,一直以来都是一个重要且复杂的问题,传统的做法一般为两级寄存器或是握手,这也成为了很多问题的根源。有几篇不错的文章供大家参考阅读https://github.com/zhangzek/Clock-Domain-Crossing-Design/tree/master/doc&paper,可以花多点时间去阅读CummingsSNUG2008Boston_CDC.pdf(也就是文献[1])
本例使用MCP(Multi-Cycle Path)Formulation,多周期路径去同步,可跨时钟域同步多个位。但是,有一个问题是,源时钟域中的逻辑如何知道何时可以发送另一段数据?就需要带反馈确认来增强(MCP)同步。
以后更新带反馈的MCP
大家新年快乐!
参考文献:
[1]:Clock Domain Crossing (CDC) Design & VerificationTechniques Using SystemVerilog
[2]:Mike Stein, Paradigm Works.Crossing the abyss:asynchronous signals in a synchronous world
[3]:Clifford E. Cummings.Simulation and Synthesis Techniques for Asynchronous FIFO Design
[4]:Ran Ginosar.Metastability and Synchronizers: A Tutorial