1、如何将单bit信号完成从快时钟域到慢时钟域的传输?
这个题目是比较常见的,我知道的一般处理方法有两种:一是:脉冲扩展(一般扩展到慢时钟域的1.5倍脉冲宽度左右),另一种是:进行握手信号处理。
进行握手信号处理的时候由简到繁又分为以下几种情况:
(1)只考虑单比特信号的跨时钟域
module handshake_pulse_sync(
input src_clk,
input src_rst_n,
input src_pulse,
input dst_clk,
input dst_rst_n,
output dst_pulse
);
reg src_sync_req;
reg src_sync_ack;
always @ (posedge src_clk or negedge src_rst_n) begin
if(!src_rst_n) begin
src_sync_req <= 1'b0;
end
else if(src_pulse) begin
src_sync_req <= 1'b1;
end
else if(src_sync_ack) begin
src_sync_req <= 1'b0;
end
end
reg req_src_dst1;
reg req_src_dst2;
reg req_src_dst2;
always @ (posedge dst_clk or negedge dst_rst_n) begin
if(!dst_rst_n) begin
req_src_dst1 <= 1'b0;
req_src_dst2 <= 1'b0;
req_src_dst3 <= 1'b0;
end
else begin
req_src_dst1 <= src_sync_req;
req_src_dst2 <= req_src_dst1;
req_src_dst3 <= req_src_dst1;
end
end
wire dst_pulse;
assign dst_pulse = (~req_src_dst3) & req_src_dst2;
reg dst_src_ack1;
always @ (posedge src_clk or negedge src_rst_n) begin
if(!src_rst_n) begin
dst_src_ack1 <= 1'b0;
src_sync_ack <= 1'b0
end
else begin
dst_src_ack1 <= req_src_dst2;
src_sync_ack <= dst_src_ack1
end
end
endmodule
(2)如果两个脉冲信号离的比较近,那么刚进来的脉冲就会同步失败,但是不会得到反馈,致使以为同步成功了。下面这个代码就提出来了同步失败信号。
//若是在同步一个脉冲的过程中,输入端又接收到一个输入进来的脉冲,那么此时刚输入进来的脉冲就会同步失败。
//没有同步失败反馈,导致使用者误以为正确同步了信号。当同步失败后输出src_sync_fail信号指示同步失败
module handshack_pulse_sync(
input src_clk,
input src_rst_n,
input src_pulse,
input dst_clk,
input dst_rst_n,
output reg src_sync_fail,
output dst_pulse
);
wire src_sync_idle;
reg src_sync_req;
reg src_sync_ack;
reg ack_state_dly1;
reg ack_state_dly2;
reg req_state_dly1;
reg req_state_dly2;
reg dst_req_state;
reg dst_sync_ack;
assign src_sync_idle = ~(src_sync_req | src_sync_ack);
always @ (posedge src_clk or negedge src_rst_n) begin
if(!src_rst_n) begin
src_sync_fail <= 1'b0;
end
else if(src_pulse & (~src_sync_idle)) begin
src_sync_fail <= 1'b1;
end
else begin
src_sync_fail <= 1'b0;
end
end
always @ (posedge src_clk or negedge src_rst_n) begin
if(!src_rst_n) begin
src_sync_req <= 1'b0;
end
else if(src_pulse & src_sync_idle) begin
src_sync_req <= 1'b1;
end
else if(src_sync_ack) begin
src_sync_req <= 1'b0;
end
end
always @ (posedge dst_clk or negedge dst_rst_n) begin
if(!dst_rst_n) begin
req_state_dly1 <= 1'b0;
req_state_dly2 <= 1'b0;
dst_req_state <= 1'b0;
end
else begin
req_state_dly1 <= src_sync_req;
req_state_dly2 <= req_state_dly1;
dst_req_state <= req_state_dly2;
end
end
assign dst_pulse = req_state_dly2 & (~dst_req_state);
always @ (posedge dst_clk or negedge dst_rst_n) begin
if(!dst_rst_n) begin
dst_sync_ack <= 1'b0;
end
else if(req_state_dly2) begin
dst_sync_ack <= 1'b1;
end
else begin
dst_sync_ack <= 1'b0;
end
end
always @ (posedge src_clk or negedge src_rst_n) begin
if(!src_rst_n) begin
ack_state_dly1 <= 1'b0;
ack_state_dly2 <= 1'b0;
src_sync_ack <= 1'b0;
end
else begin
ack_state_dly1 <= dst_sync_ack;
ack_state_dly2 <= ack_state_dly1;
src_sync_ack <= ack_state_dly2;
end
end
endmodule
针对这个情况,在网上还看到过有人设计了这样的方法,让两次脉冲都会同步成功。
在上图中in_pulse为输入的待同步的脉冲信号,receive_enable为向上游电路输出的backpressure信号,只有当该信号有效时候,电路才可以接收输入脉冲。
在握手机制下上下游电路可以源源不断地向其输入脉冲信号,而由握手电路决定要同步哪些电路,并将同步失败的信号向上游提供反馈。而backpressure则在电路处理不了下一个要到来的信号时及时告知上游,使上游停止输入。
module sync_pulse__backpressure(
input wire src_clk,
input wire dst_clk,
input wire rst,
input wire in_pulse,
output wire out_pulse,
output wire receive_en
);
reg src_sync,dst_sync;
reg sync_dly1,sync_dly2;
reg sync_back_dly1,sync_back_dly2;
always @(posedge src_clk or posedge rst ) begin
if(rst)
src_sync <= 0;
else
src_sync <= src_sync ^ in_pulse;
end
always @(posedge dst_clk or posedge rst) begin
if(rst)
{sync_dly1,sync_dly2,dst_sync} <= 3'b000;
else
{sync_dly1,sync_dly2,dst_sync} <= {src_sync,sync_dly1,sync_dly2};
end
assign out_pulse = sync_dly2 ^ dst_sync ;
always @(posedge src_clk or posedge rst ) begin
if (rst)
{sync_back_dly1,sync_back_dly2} <= 2'b00;
else
{sync_back_dly1,sync_back_dly2} <= {sync_dly2,sync_back_dly1};
end
assign receive_en = ~(sync_back_dly2 ^ src_sync) ;
endmodule
将上述电路改为握手电路需要修改两个关键部分:
(1)将receive_enable信号改为同步失败信号,在脉冲信号到来时,若sync_fail信号同步输出1,则证明同步失败。可以看出只需将receive_enable信号月in_pulse进行逻辑操作,即可得到sync_fail。
(2)在上一个信号同步过程中的忙状态,若此时又一个脉冲同步信号输入,则握手电路会组止握手回路的信号受输入的影响,而在上述backpressure电路中,一旦有in_pulse,内部信号会在下一个时钟上升沿进行翻转。
如果两个脉冲之间的间隔比较小还是会出现同步失败的问题,但是间隔稍微大点,就可以避免这个问题
module sync_pulse_fast_to_slow_handshake(
input src_clk,
input dst_clk,
input rst_n,
input in_pulse,
output out_pulse,
output sync_fail
);
reg src_sync;
reg dst_sync;
reg sync_dly1;
reg sync_dly2;
reg sync_back_dly1;
reg sync_back_dly2;
wire sync_idle;
reg sync_idle_dly; //用于产生sync_fail信号
always @ (posedge src_clk or negedge rst_n) begin
if(!rst_n) begin
src_sync <= 1'b0;
end
else begin
src_sync <= sync_idle ? (src_sync ^ in_pulse): src_sync;
end
end
always @ (posedge dst_clk or negedge rst_n) begin
if(!rst_n) begin
{sync_dly1,sync_dly2,dst_sync} <= 3'b000;
end
else begin
{sync_dly1,sync_dly2,dst_sync} <= {src_sync,sync_dly1,sync_dly2};
end
end
assign out_pulse = sync_dly2 ^ dst_sync;
always @ (posedge src_clk or negedge rst_n) begin
if(!rst_n) begin
{sync_back_dly1,sync_back_dly2} <= 2'b00;
end
else begin
{sync_back_dly1,sync_back_dly2} <= {sync_dly2,sync_back_dly1};
end
end
assign sync_idle = ~(sync_back_dly2 ^ src_sync);
always @(posedge src_clk or posedge rst_n) begin
if(!rst_n)
sync_idle_dly <= 0;
else
sync_idle_dly <= sync_idle;
end
assign sync_fail = in_pulse & ( ~(sync_idle | sync_idle_dly) );
endmodule
细品一下确实感觉这也就那样(膨胀了)。