之前在研究FPGA与CPU通信的SPI协议时,看到Bingo在其中加的边沿检测实现,在文中,Bingo强调对外部信号要进行两次寄存而并非一次寄存。虽然我一直也是这么做的,但只是模仿别人而已,却没有想过为什么要这么做。现在就来好好研究下。
使用一级D触发器比较时,前一时刻的信号已经同步到系统时钟域,而当前时刻直接从外部输入,与FPGA的逻辑不在同一时钟域,属于异步时钟。这点在书中已经讲到。但是,如果坚持使用这个异步信号,会有什么问题呢?
以下是测试的过程。
(1) RTL代码:
module edge_dec(
input clk_50m ,
input reset_l ,
input signal_in ,//异步数据
output sig_posedge1 ,
output sig_posedge2
);
//-----------------------------------
reg signal_d1 ;
reg signal_d2 ;
always @(posedge clk_50m or negedge reset_l)
begin
if(!reset_l)begin
signal_d1 <= 1'b0 ;
end
else begin
signal_d1 <= signal_in ;
signal_d2 <= signal_d1 ;
end
end
//---------------------------------------
//wiresig_posedge1 ;
//wiresig_posedge2 ;
//使用一级同步和异步数据产生的脉冲
assignsig_posedge1 = (~signal_d1) & signal_in;
//使用一级同步和二级同步产生的脉冲
assignsig_posedge2 = (~signal_d2) & signal_d1 ;
endmodule
(2) Testbench
`timescale1ns/100ps
moduleedge_dec_tb();
reg clk_50m ;
reg reset_l ;
reg signal_in;
initial
begin
signal_in = 1'b0 ;
reset_l = 1'b0 ;
clk_50m = 1'b0 ;
#20
reset_l = 1'b1 ;
#112
signal_in = 1'b1 ;
#133
signal_in = 1'b0 ;
#157
signal_in = 1'b1 ;
end
always #10clk_50m <= ~clk_50m ;
edge_decu_edge_dec(
.clk_50m (clk_50m ),
.reset_l (reset_l ),
.signal_in (signal_in)
);
endmodule
在设计中,我们希望sig_posedge信号保持一到两个时钟的高电平,这样才能被时钟采样到。在一级寄存的波形中,很明显可以看到sig_posedge1信号高电平的宽度长短不一,这便是异步信号的影响,由于没有进行同步,信号何时出现以及信号的宽度都不能确定,在进行一系列组合逻辑处理后,得到的输出信号的宽度仍然太窄,导致处理后得到的sig_posedge1信号宽度小于一个时钟周期(甚至出现尖峰脉冲),那么这个信号很可能无法被检测到,从而使系统出现误动作。蓝色线表示的sig_posedge2信号是采用了两级寄存,可以看到,很客观的解决了这个问题(被寄存后的信号宽度都为时钟周期的整数倍)。
说到底,这只是属于异步时钟域数据交互的一个小问题,如果不去思考,我也许发现不了。这让我认识到,对待未知的问题,如果不去独立思考,不去深究,而是浅尝辄止,那了解的终究只是皮毛。一本好书,作者很重要,读者也同样重要,没有深入的思考、实践,知识永远都是别人的,即使作者再牛,也无法将知识强行塞到你脑子,到最后,你只是不断地原地踏步再踏步… …