最近做的一个项目中,需要将网口传输的数据同步到系统时钟下进行分析。
这块板子上以太网使用的是 RMII 接口,一次发送 2 位数据。我在网口时钟域下,将数据拓宽成 8 位,分析完网络协议之后再传递给系统时钟。这样,每 4 个时钟周期出现一次使能信号,一次发送 1 字节数据。
我原本想着,网口时钟和系统时钟都是 50 MHz,那么直接打两拍就可以了。刚开始少量的数据并未发现问题,后面大量数据连续发送时,出现了明显的数据错误。抓取使能信号可以看到,有一些地方使能信号连续出现两个时钟周期,并且出现错误的地方都不是孤立的,错误会连续出现。
这是因为打拍只能消除亚稳态,但无法保证数据的正确。上述抓取到的错误情况中,源数据恰巧处于亚稳态,打拍时选取了错误的值,给后续使用。并且可以推测,也有许多使能缺失的错误情况。
对于这个问题,可以延长原使能信号,打拍后捕捉上升沿的方式来产生新的使能信号。代码如下:
always @ ( posedge rmii_clk or negedge sys_rst_n ) begin
if ( !sys_rst_n ) begin
udp_rxdv_d1 <= 1'b0;
udp_rxdv_wide <= 1'b0;
end else begin
udp_rxdv_d1 <= udp_rxdv_i;
udp_rxdv_wide <= udp_rxdv_i || udp_rxdv_d1;
end
end
always @ ( posedge sys_clk or negedge sys_rst_n ) begin
if ( !sys_rst_n ) begin
udp_rxdv_wide_d1 <= 1'b0;
udp_rxdv_wide_d2 <= 1'b0;
end else begin
udp_rxdv_wide_d1 <= udp_rxdv_wide;
udp_rxdv_wide_d2 <= udp_rxdv_wide_d1;
end
end
always @ ( posedge sys_clk or negedge sys_rst_n ) begin
if ( !sys_rst_n ) begin
udp_rxdv <= 1'b0;
end else if ( udp_rxdv_wide_d1 && !udp_rxdv_wide_d2 ) begin
udp_rxdv <= 1'b1;
end else begin
udp_rxdv <= 1'b0;
end
end
这样使能信号处理好了,理想情况下,相对于原使能信号打了 4 拍。这种处理方法也适用于频率不同的时钟,但要确保以下两点:
- 使能信号不是连续的。
- 演延长后的使能信号,其高、低电平的持续时间,在新的时钟域下都要大于一个时钟周期,以确保使能信号发生时能捕捉到上升沿。
对于数据,同样不能做简单的打拍处理,首先是打拍无法保证数据的正确,其次产生的使能信号位置不一定准确。最后还需要加个异步 FIFO 。需要注意的是,异步 FIFO 会有较长的 latency, 新的使能信号得做适当的延迟才能使用。