打开APP
userphoto
未登录

开通VIP,畅享免费电子书等14项超值服

开通VIP
一天一个设计实例-FIFO先进先出模块程序设计
万字长文实例讲解FIFO。

先进先出(First In first Out, FIFO)是数据通信的一种等待处理的方式,即对先到达的数据先处理。根据 FIFO 原现设计的 FIFO 存储器,是一个带有控制逻辑模块的先入先出存队列。控制逻辑模块能管理读/写指示器,产生状态标志,并提供与用户逻辑相关的接口连接的握手倍号。通常控制逻辑模块的数据存放结构和 RAM 完全一致,但存取方式不同,它解决了速率匹配、数据缓冲和总线匹配等芯片间通信的问题。FIFO 存储器一般可分为同 FIFO 存储器、异步 FIFO 存储器和双向 FIFO 存储器种。

1.1.1FIFO简单讲解

FIFO的本质是RAM,先进先出

重要参数:FIFO深度(简单来说就是需要存多少个数据)

             FIFO位宽(每个数据的位宽)

FIFO有同步和异步两种,同步即读写时钟相同,异步即读写时钟不相同

同步FIFO用的少,可以作为数据缓存

异步FIFO可以解决跨时钟域的问题,在应用时需根据实际情况考虑好FIFO深度即可。

1.1.2同步FIFO的设计

同步 FIFO 存储器只有一个单输入吋钟信号,同步地控制读数据和写数据的操作。

当写数据操作有效时,输入脚(WR_EN)为高电平时,在上升沿到来时输入数据模块引脚(DIN)上的数据单元闪容写入下一个有效的空存储单元。存储器满,标志位输出FULL,表明内部存储模块已经没有空的存储单元了。

当读数据操作有效时,输入脚(RD_EN)为高电平时,在上升沿到来时 FIFO 上的数据将通过数据输出模块引脚(DOUT)被读出。存储器空,标志位输出 EMPTY,表明内部存储模块中已经没有存储数据了。

315 同步FIFO内部结构框图

FIFO 的用意一般都是缓冲数据,另模块独立,让模块回避调用的束缚。同步 FIFO 是 RAM 的亚种,它基于 RAM,再加上先进先出的机制,学习同步 FIFO 就是学习如何建立先进先出的机制。

316 同步 FIFO 电路图(简化常规)。

常规上,同步 FIFO 的电路如图 15.1 所示,左边有写入请求 ReqW,写入数据 DataW,还有写满标示 Full。换之,右边则有读出请求 ReqR,读出数据 DataR,还有读空标示Empty。写入方面, ReqW 必须拉高 DataW 才能写入,一旦 FIFO 写满,那么 Full 就会拉高。至于读出方面,ReqR 必须拉高,数据才能经由 DataR 读出,一旦 FIFO 读空,Empty 就会拉高。

代码39 同步 FIFO 代码结构

1.module fifo_savemod  

2.(  

3.input CLOCK, RESET,  

4.input ReqW,ReqR,  

5.input [3:0]DataW,  

6.ouptut [3:0]DataR,  

7.output Full,Empty  

8.);  

9.  

10.  

11.assign Full  = ...; // Full  

12.assign Empty = ...; // Empty  

13.  

14.endmodule  

同步 FIFO 大致的外皮如代码3‑9所示,第 3~7 行是相关的出入端声明,第 10~11 行则是相关的输出驱动声明。理解这些以后,接下来我们要学习先进先出这个机制。

317 读空状态

假设建立位宽为 4,深度为 4 的 ram,然后又建立位宽为 3 的写指针 WP 与读指针RP。既然 ram 只有 4 个深度,那么指针只要 2 位宽( 22 = 4)即不是可以访问所有深度呢?话虽如此,为了利用指正表示写满与读空状态,指针必须多出一位 ... 因此,指针的最高位常常也被称为方向位(异步FIFO部分会有详细介绍)

3‑17所示,一开始的时候,写指针与读指针同样指向地址 0,而且 ram 里边也是空空如也,为此读空状态“叮咚”亮着红灯。为此,我们可以暂时这样表示读空的逻辑关系:

Empty = (WP == RP)

318 写入中①

当开始写入数据以后,首先数据 4’hA 写入地址 0,然后写指针从原来的 3’b0_00 递增为3’b0_01,并且指向地址 1。此刻, ram 再也不是空空入也,所示读空状态消除红灯,结果如3‑18所示。

319 写入中②

紧接着,数据 4’hB 写入地址 1,然后写指针从原来的 3’b0_01 递增为 3’b0_10,并且指向地址 2,结果如3‑19所示。

320 写入中③

然后,数据 4’hC 写入地址 2,然后写指针从原来的 3’b0_10 递增为 3’b0_11,并且指向地址 3,结果如3‑20所示。

321 写满状态

接着,数据 4’hD 写入地址 3,然后写指针从原来的 3’b0_11 递增为 3’b1_00,并且重新指向地址 0。此刻写指针的最高位为 1,这表示写指针已经绕弯 ram 一圈又回来原点,反之读指针从刚才开始一动也不动,结果最高为 0 ... 所以我们可以说写指针与读指针目前处于不同的方向。在此 ram 已经写满,所以写满状态便“叮咚”亮红灯,结果如3‑21所示。写满状态的逻辑关系则可以这样表示:

FULL = ( WP[2] ^ RP[2] && WP[1:0] == RP[1:0]);

322 读出中①

从现在开始,另一头火车才开始走动 ... 首先数据 4’hA 从地址 0 读出来,读指针也从原本的 3’b0_00 递增为 3’b0_01,并且指向地址 1。此刻 ram 再也不是吃饱饱的状态,所以写满状态被消除红灯(FULL),结果如3‑22所示。

323 读出中②

接下来,数据 4’hB 从地址 1 哪里读出,读指针也从原本的 3’b0_01 递增为 3’b0_10,并且指向地址 2,结果如3‑23所示。

324 读出中③

随之,数据 4’hC 从地址 2 哪里读出,读指针也从原本的 3’b0_10 递增为 3’b0_11,并且指向地址 3,结果如3‑24所示。

325 读空状态

最后,数据 4’hD 从地址 3 哪里读出,读指针也从原本的 3’b0_11 递增为 3’b1_00,并且重新指向地址 0。当读指针绕弯一圈又回到原点的时候,读者的最高位也成为值 1,换句话说 ... 此刻的读指针与写指针也处于同样的位置。同一个时候, ram 也是空空如也,所以读空状态便“叮咚”亮起红灯,结果如3‑25所示。为此,读空状态的逻辑关系可以这样表示:

Empty = (WP == RP);

总结而言,当我们设置 N 位位宽的时候,读写指针的位宽便是 N + 1。此外,读空状态为写指针等价读指针。反之,写满状态是两个指针方向一致(异或状态),然后地址一致。理解先进先出的机制以后,接下来我们便可以填充一下 FIFO 储存模块的内容。

代码310 同步FIFO代码

1.module fifo_savemod  

2.(  

3.input CLOCK, RESET,  

4.input ReqW,ReqR,  

5.input [3:0]DataW,  

6.output [3:0]DataR,  

7.output Full,Empty  

8.);  

9.  

10.reg [3:0] RAM [3:0];  

11.reg [3:0] D1;  

12.reg [2:0] WP,RP; // N+1  

13.  

14.always @ ( posedge CLOCK or negedge RESET )  

15.if( !RESET )  

16.    begin  

17.        WP <= 3'd0;  

18.    end  

19.else if( ReqW )  

20.    begin  

21.        RAM[ WP[1:0] ] <= DataW;//前两位才是深度,最高位是标志位  

22.        WP <= WP + 1'b1;  

23.    end  

24.      

25.always @ ( posedge CLOCK or negedge RESET )  

26.    if( !RESET )  

27.        begin  

28.            D1 <= 4'd0;  

29.            RP <= 3'd0;  

30.    end  

31.    else if( ReqR )  

32.        begin  

33.            D1 <= RAM[ RP[1:0] ];//前两位才是深度,最高位是标志位  

34.            RP <= RP + 1'b1;  

35.    end  

36.      

37.assign DataR = D1;  

38.assign Full = ( WP[2]^RP[2] & WP[1:0] == RP[1:0] ); // Full  

39.assign Empty = ( WP == RP ); // Empty  

40.  

41.  

42.endmodule  

在第 9~11 行创建相关的寄存器, WP, RP含义在上诉分析过程中一样。第 13~22 行是写操作,内容非常单纯,即 ReqW 拉高便将DataW 写入 WP[1:0] 指定的地方,然后 C1 递增。

24~34 行是读操作,内容也是一样单纯, ReqR 拉高便将 RP[1:0] 指定的数据暂存至D1,随后 RP递增,最后由 D1 驱动DataR。第 37~38 行是写满状态与读空状态的逻辑关系。

326 调用 FIFO 储存模块

创建同步 FIFO 基本上没有什么难度,但是调用 FIFO 倒是一件难题。如3‑26 所示,建立一支核心操作尝试调用 FIFO储存模块,至于核心操作的内容如代码3‑11所示:

代码311 调用同步FIFO

1.case( i ) // Core  

2.0:  

3.    if( iTag[1]! ) begin oEn[1] <= 1’ b1; oData <= 4’ hA; i <= i + 1’ b1; end  

4.1:  

5.    if( iTag[1]! ) begin oEn[1] <= 1’ b1; oData <= 4’ hB; i <= i + 1’ b1; end  

6.2:  

7.    if( iTag[1]! ) begin oEn[1] <= 1’ b1; oData <= 4’ hC; i <= i + 1’ b1; end  

8.3:  

9.    if( iTag[1]! ) begin oEn[1] <= 1’ b1; oData <= 4’ hD; i <= i + 1’ b1; end  

10.4:  

11.    begin oEn[1] <= 1’ b0; i <= i + 1’ b1; end  

12.5:  

13.    if( iTag[0]! ) begin oEn[0] <= 1’ b1; i <= i + 1’ b1; end  

14.6:  

15.    if( iTag[0]! ) begin oEn[0] <= 1’ b1; i <= i + 1’ b1; end  

16.7:  

17.    if( iTag[0]! ) begin oEn[0] <= 1’ b1; i <= i + 1’ b1; end  

18.8:  

19.    if( iTag[0]! ) begin oEn[0] <= 1’ b1; i <= i + 1’ b1; end  

20.9:  

21.    begin oEn[0] <= 1’ b0; i <= i + 1’ b1; end  

22.endcase  

其中 En[1] 表示写入使能, En[0]表示读出使能。Data× 改为数据信号 Data, iData 为写入数据,oData 为读出数据。Full 与 Empty则改为状态信号 Tag[1] 与 Tag[0]。C1 取代 WP, C2 取代 RP。

代码3‑11所示,步骤 0~3 用来一边检测 Tag[1] 是否为高,一边向储存模块写入数据4’hA~4‘hD,步骤 4 则用来拉低 oEn[1] 并且歇息一下。步骤 5~8 用来一边检测 Tag[0]是否为高,一边从储存模块哪里读出数据,步骤 9 则用来拉低 oEn[0]并且偷懒一下。

327 读写 FIFO 储存模块的理想时序图

3‑27代码3‑11所生产的理想时序图,同时也是核心操作作为视角的时序,至于C1~C2 是 FIFO 储存模块作为视角的时序。各个视角的时序过程如下:

核心操作视角:

T0,isTag[1]为低(即时值),拉高 oEn[1](未来值),并且发送数据 4’hA(未来值)。

T1,isTag[1]为低(即时值),拉高 oEn[1](未来值),并且发送数据 4’hB(未来值)。

T2,isTag[1]为低(即时值),拉高 oEn[1](未来值),并且发送数据 4’hC(未来值)。

T3,isTag[1]为低(即时值),拉高 oEn[1](未来值),并且发送数据 4’hD(未来值)。

T4, isTag[1]为高(即时值),拉低 oEn[1](未来值)。

T5,isTag[0]为低(即时值),拉高 oEn[1](未来值)。

T6,isTag[0]为低(即时值),拉高 oEn[1](未来值),数据 4’hA 读出(过去值)。  T7,isTag[0]为低(即时值),拉高 oEn[1](未来值),数据 4’hB 读出(过去值)。

T8,isTag[0]为低(即时值),拉高 oEn[1](未来值),数据 4’hC 读出(过去值)。

T9,isTag[0]为高(即时值),拉低 oEn[1](未来值),数据 4’hD 读出(过去值)。

T10,isTag[0]为高(即时值)。

FIFO 储存模块视角:

T0,oEn[1]为低(过去值)。C1 等价 C2 为读空状态, iTag[0]拉高(即时值)。

T1,oEn[1]为高(过去值),读取数据 4’hA(过去值),递增 C1。C1 不等价 C2,iTag[0]拉低(即时值)。

T2,oEn[1]为高(过去值),读取数据 4’hB(过去值),递增 C1。

T3,oEn[1]为高(过去值),读取数据 4’hC(过去值),递增 C1。

T4,oEn[1]为高(过去值),读取数据 4’hA(过去值),递增 C1。C1 等价 C2 为写满状态, iTag[1]拉高(即时值)。

T5,oEn[1]为低(过去值)。

T6,oEn[0]为高(过去值),读出数据 4’hA(未来值),递增 C2。C1 不等价 C2,isTag[1]拉低(即时值)。

T7,oEn[0]为高(过去值),读出数据 4’hB(未来值),递增 C2。

T8,oEn[0]为高(过去值),读出数据 4’hC(未来值),递增 C2。

T9,oEn[0]为高(过去值),读出数据 4’hD(未来值),递增 C2。C1 等价 C2 为读空状态,isTag[0]拉高(即时值)。

T10,oEn[0]为低(过去值)。

什么过去值,又什么未来值,又又什么即时值的 ...没错,同步 FIFO 的设计原理虽然简单,但是时序解读却让人泪流满面。因为同步 FIFO夹杂两种时序表现——时间点事件还有即时事件。如3‑27所示,除了 iTag 信号是触发即时事件以外,所有信号都是触发时间点事件。即时值不仅比过去值优先,而且即时值也会无视时钟。

好奇的同学可能困惑道:“为什么 iTag 不能设计成为时间点事件呢?”。基于移位寄存器的 FIFO,其中 iTag 就是设计成为时间点事件,结果 FIFO 的写满状态或者读空状态都来不及反馈,因此发生调用上的混乱。

328 读写 FIFO 储存模块的即时事件

为了理解重点,首先让我们来焦距写数据的部分。如3‑28所示,关键的地方就是发生在 T4——这只时钟沿。T4 之际, FIFO 储存模块读取 oEn[1]的过去值, C1 也因此递增,即时事件就在这个瞬间发生了。写满状态成立, iTag[1]也随之拉高即时值。从时序上来看, C1 的更新( C1 为 4’b100)是发生在 T4 之后,不过那也无关紧要,因为即时值是更新在小小的时间沿之间,也是即时层 ... 然而,即时层是无法显示在时序之上。

329 迟到的写满状态

假设,iTag[1]不是经由即时事件触发而是事件点事件,那么 iTag 就会反馈迟到的写满状态。如图 15.15 所示, T4 之际 oEn[1] 为高, C1 也因此递增为 3’b100。T5 之际,C1 C2 的过去值均为 3’b100 与 3’b000,然后拉高 iTag[1]。由于时间点事件的关系,所 iTag[1]迟一拍被拉高 ... 读者千万别小看这样慢来一拍,它是搞乱调用的罪魁祸首。如果核心操作在 T5 继续写操作的话,此刻 iTag[1]的过去值为 0,它会认为 FIFO 未满,然后不管三七二十一执行写操作,结果 FIFO 发生错乱随之机能崩溃。从某种程度来看,即时事件的偷时钟能力,是建立同步 FIFO 的关键。

同步FIFO程序设计如下:

代码312 同步FIFO程序代码

1.//****************************************************************************//  

2.//# @Author:  

3.//# @Date:   2019-04-06 23:22:19  

4.//# @Last Modified by:   zlk  

5.//# @WeChat Official Account: OpenFPGA  

6.//# @Last Modified time: 2019-04-07 03:21:08  

7.//# Description:   

8.//# @Modification History: 2019-04-07 03:21:08  

9.//# Date                By             Version             Change Description:   

10.//# ========================================================================= #  

11.//# 2019-04-07 03:21:08  

12.//# ========================================================================= #  

13.//# |                                                                       | #  

14.//# |                                OpenFPGA                               | #  

15.//****************************************************************************//  

16.module Synchronous_FIFO_savemod  

17.(  

18.    input CLOCK, RESET,  

19.    input [1:0]iEn,     //写、读使能  

20.    input [3:0]iData,  

21.    output [3:0]oData,  

22.    output [1:0]oTag    //空满标志位  

23.    );  

24.reg [3:0] RAM [3:0];  

25.reg [3:0]D1;  

26.reg [2:0]C1,C2; // N+1  

27.  

28.always @ ( posedge CLOCK or negedge RESET )  

29.    if( !RESET )  

30.        begin  

31.            C1 <= 3'd0;  

32.        end  

33.    else if( iEn[1] )  

34.        begin  

35.            RAM[ C1[1:0] ] <= iData;  

36.            C1 <= C1 + 1'b1;  

37.    end  

38.      

39.always @ ( posedge CLOCK or negedge RESET )  

40.    if( !RESET )  

41.        begin  

42.            D1 <= 4'd0;  

43.            C2 <= 3'd0;  

44.        end  

45.    else if( iEn[0] )  

46.        begin  

47.            D1 <= RAM[ C2[1:0] ];  

48.            C2 <= C2 + 1'b1;  

49.        end  

50.  

51.assign oData = D1;  

52.assign oTag[1] = ( C1[2]^C2[2] & C1[1:0] == C2[1:0] ); // Full  

53.assign oTag[0] = ( C1 == C2 ); // Empty  

54.  

55.endmodule  

仿真结果:

330 同步FIFO程序仿真结果

结果分析:

结果和上述分析结果一样,不再累述。

1.1.3同步FIFO IP设计分析

利用Vivado和Quartus II调用FIFO模块,以Quartus II为例:

1、新建工程;

2、在IP catalog中搜索 FIFO:

3、输入例化的IP名称和选择语言;

4、选择1:FIFO的宽度;2:FIFO的深度;3:FIFO的类型,上边代表同步FIFO,下边代表异步FIFO

4、FIFO电路控制信号的选择,1代表空、满信号(需要控制时必选);2代表FIFO中数据的深度数值(即当前在操作第几个数据);3、4配合2信号就可以当数据深度为设定值时产生almost_full/almost_empty信号;5代表异步清零/复位信号;6代表同步清零/复位信号;

6、1代表读信号生效时刻选择,可以选择数据在读信号之后输出,或者在读信号“之前”(参考即时事件触发事件点事件)输出;2代表输出存储资源的选择,根据自己项目需要及存储资源的使用情况进行选择。

7、FIFO一些整体控制相关,比如最大或者最快选择等,根据实际情况进行选择。

8、完成。

9、之后就可以在项目中例化使用了,由于和之前实验过程类似,就不再累述。

异步FIFO的设计要点解析

3‑31给出了FIFO的接口信号和内部模块图。 

3‑31可以看出,写地址产生模块根据写时钟和写有效信号产生递增的写地睛,读地址产生模块根据读时钟和读有效信号产生递增的读地址。FIFO的操作如下:在写时钟wr_clk的升沿,当wren有效时,将wr_data写入双口RAM中写地址对应的位置中;始终将读地址对应的双口RAM中的数据输出到读数据总线上。这样就实现了先进先出的功能。 

331 异步FIFO内部模块图和接口信号

写地址产生模块还根据读地址和写地址关系产生FIFO的满标志。当wren有效时,若写地址+2=读地址时,full为1;当wren无效时,若写地址+ 1=读地址时,full为1。读地址产生模块还根据读地址和写地址的差产生FIFO的空标志。当rden有效时,若写地址-1=读地址时,empty为 1;当rden无效时,若写地址=读地址时,empty为1。按照以上方式产生标志信号是为了提前一个时钟周期产生对应的标志信号。 
  由于空标志和满标志控制了FIFO的操作,因此标志错误会引起操作的错误。如上所述,标志的产生是通过对读写地址的比较产生的,当读写时钟完全异步时,对读写地址进行比较时,可能得出错误的结果。例如,在读地址变化过程中,由于读地址的各位变化并不同步,计算读写地址的差值,可能产生错误的差值,导致产生错误的满标志信号。若将未满标志置为满标志时,可能降低了应用的性能,降低写数据速率;而将满置标志置为未满时,执行一次写操作,则可能产生溢出错误,这对于实际应用来说是绝对应该避免的。空标志信号的产生也可能产生类似的错误。

异步FIFO的改进设计

从以上分析中可以看出,异步FIFO之所以会发生错误是国为在地址变化时,由于多位地址各位变化时间不同,异步时钟对其进行采样时数值可能为不同于地址变化丧后数值的其他值,异步产生错误的空标志和满标志,以致于产生FIFO的操作错误。 

格雷码是一种在相邻计数值之间只有一位发生变化的编码方式。可以看出,若读写地址采用格雷码编码方式,就可以解决上面的问题。

为了应用的灵活,还增加了两个标志信号,将满(almosf_full)标志和空(almost_empty)标志分别定义如下:当写地址与读地址的距离小于某个预先定义数值时,almost_full为1;当读地址与写地址的距离小于这个预先定义的数值时,almost_empty为1。

异步FIFO比同步FIFO控制要更复杂一点,Empty和Full信号依然是最主要的信号,下面着重介绍这两个信号的判断依据。

1、读空信号如何产生?写满信号如何产生?

读空信号:复位的时候,读指针和写指针相等,读空信号有效(这里所说的指针其实就是读地址、写地址)当读指针赶上写指针的时候,写指针等于读指针意味着最后一个数据被读完,此时读空信号有效

写满信号:当写指针比读指针多一圈时,写指针等于读指针意味着写满了,此时写满信号有效。我们会发现读空的条件是写指针等于读指针,写满的条件也是写指针等于读指针,到底如何区分呢?

解决方法:将指针的位宽多定义一位

举个例子说明:假设要设计深度为 8 的异步FIFO,此时定义读写指针只需要3 位(2^3=8)就够用了,但是我们在设计时将指针的位宽设计成 4 位,最高位的作用就是区分是读空还是写满,具体理论 1 如下

当最高位相同,其余位相同认为是读空

当最高位不同,其余位相同认为是写满

注意:理论1试用的是二进制数之间的空满比较判断。

但是这篇文章中确不是这样比较的,而是用的理论2,这里我解释一下,由于文章在设计中判断是读指针是否等于写指针的时候,用的是读写指针的格雷码形式(为什么用格雷码后面解释),此时若用上面的理论1就会出问题,因为格雷码是镜像对称的,若只根据最高位是否相同来区分是读空还是写满是有问题的,详情我会慢慢说,请看图 1。绿色框起来的是0--15的格雷码,用红线将格雷码分为上下两部分,通过观察格雷码相邻位每次只有1位发生变化,且上下两部分,除了最高位相反,其余位全都关于红线镜像对称,7 --> 8 ,格雷码从 0100--> 1100 ,只有最高位发生变化其余位相同;6 --> 9, 格雷码从 0101 --> 1101 , 只有最高位发生变化其余位相同。

                     

332 0--15的格雷码

以此类推,为什么要说镜像对称呢?

试想如果读指针指向 8,写指针指向 7 ,我们可以知道此时此刻并不是读空状态也不是写满状态。但是如果在此刻套用理论 1 来判断,看会出现什么情况,我们来套一下7的格雷码与8的格雷码的最高位不同,其余位相同,所以判断出为写满。这就出现误判了,同样套用在 6 和 9,5 和 10等也会出现误判。因此用格雷码判断是否为读空或写满时应使用理论 2,看最高位和次高位是否相等,具体如下:

当最高位和次高位相同,其余位相同认为是读空

当最高位和次高位不同,其余位相同认为是写满

补:理论2这个判断方法适用于用格雷码判断比较空满

在实际设计中如果不想用格雷码比较,就可以利用格雷码将读写地址同步到一个时钟域后再将格雷码再次转化成二进制数再用理论1进行比较就好了。。

2、由于是异步FIFO的设计,读写时钟不一样,在产生读空信号和写满信号时,会涉及到跨时钟域的问题,如何解决?

跨时钟域的问题:上面我们已经提到要通过比较读写指针来判断产生读空和写满信号,但是读指针是属于读时钟域的,写指针是属于写时钟域的,而异步FIFO的读写时钟域不同,是异步的,要是将读时钟域的读指针与写时钟域的写指针不做任何处理直接比较肯定是错误的,因此我们需要进行同步处理以后仔进行比较

解决方法两级寄存器同步 + 格雷码

同步的过程有两个:

(1)将写时钟域的写指针同步到读时钟域,将同步后的写指针与读时钟域的读指针进行比较产生读空信号

(2)将读时钟域的读指针同步到写时钟域,将同步后的读指针与写时钟域的写指针进行比较产生写满信号

同步的思想就是用两级寄存器同步,简单说就是打两拍,相信有点基础的早都烂熟于心,就不再多做解释,不懂的可以看看代码结合理解。

只是这样简单的同步就可以了吗?no no no ,可怕的亚稳态还在等着你。

 我们如果直接用二进制编码的读写指针去完成上述的两种同步是不行的,使用格雷码更合适,为什么呢?

因为二进制编码的指针在跳变的时候有可能是多位数据一起变化,如二进制的7-->8 即 0111--> 1000 ,在跳变的过程中 4 位全部发生了改变,这样很容易产生毛刺,例如异步FIFO的写指针和读指针分属不同时钟域,这样指针在进行同步过程中很容易出错,比如写指针在从0111到1000跳变时4位同时改变,这样读时钟在进行写指针同步后得到的写指针可能是0000-1111的某个值,一共有2^4个可能的情况,而这些都是不可控制的,你并不能确定会出现哪个值,那出错的概率非常大,怎么办呢?到了格雷码发挥作用的时候了,而格雷码的编码特点是相邻位每次只有 1 位发生变化,这样在进行指针同步的时候,只有两种可能出现的情况:1.指针同步正确,正是我们所要的;2.指针同步出错,举例假设格雷码写指针从000->001,将写指针同步到读时钟域同步出错,出错的结果只可能是000->000,因为相邻位的格雷码每次只有一位变化,这个出错结果实际上也就是写指针没有跳变保持不变,我们所关心的就是这个错误会不会导致读空判断出错?答案是不会,最多是让空标志在FIFO不是真正空的时候产生,而不会出现空读的情形。所以gray码保证的是同步后的读写指针即使在出错的情形下依然能够保证FIFO功能的正确性。在同步过程中的亚稳态不可能消除,但是我们只要保证它不会影响我们的正常工作即可。

3、由于设计的时候读写指针用了至少两级寄存器同步,同步会消耗至少两个时钟周期,势必会使得判断空或满有所延迟,这会不会导致设计出错呢?

异步FIFO通过比较读写指针进行满空判断,但是读写指针属于不同的时钟域,所以在比较之前需要先将读写指针进行同步处理,将写指针同步到读时钟域再和读指针比较进行FIFO空状态判断,因为在同步写指针时需要时间,而在这个同步的时间内有可能还会写入新的数据,因此同步后的写指针一定是小于或者等于当前实际的写指针,所以此时判断FIFO为空不一定是真空,这样更加保守,一共不会出现空读的情况,虽然会影响FIFO的性能,但是并不会出错,同理将读指针同步到写时钟域再和写指针比较进行FIFO满状态判断,同步后的读指针一定是小于或者等于当前的读指针,所以此时判断FIFO为满不一定是真满,这样更保守,这样可以保证FIFO的特性:FIFO空之后不能继续读取,FIFO满之后不能继续写入。总结来说异步逻辑转到同步逻辑不可避免需要额外的时钟开销,这会导致满空趋于保守,但是保守并不等于错误,这么写会稍微有性能损失,但是不会出错。

举个例子:大多数情形下,异步FIFO两端的时钟不是同频的,或者读快写慢,或者读慢写快,慢的时钟域同步到快的时钟域不会出现漏掉指针的情况,但是将指针从快的时钟域同步到慢的时钟域时可能会有指针遗漏,举个例子以读慢写快为例,进行满标志判断的时候需要将读指针同步到写时钟域,因为读慢写快,所以不会有读指针遗漏,同步消耗时钟周期,所以同步后的读指针滞后(小于等于)当前读地址,所以可能满标志会提前产生,满并非真满。进行空标志判断的时候需要将写指针同步到读指针,因为读慢写快,所以当读时钟同步写指针的时候,必然会漏掉一部分写指针,我们不用关心那到底会漏掉哪些写指针,我们在乎的是漏掉的指针会对FIFO的空标志产生影响吗?比如写指针从0写到10,期间读时钟域只同步捕捉到了3、5、8这三个写指针而漏掉了其他指针。当同步到8这个写指针时,真实的写指针可能已经写到10 ,相当于在读时钟域还没来得及觉察的情况下,写时钟域可能偷偷写了数据到FIFO去,这样在判断它是不是空的时候会出现不是真正空的情况,漏掉的指针也没有对FIFO的逻辑操作产生影响。

4、多位二进制码如何转化为格雷码

二进制码转换成二进制格雷码,其法则是保留二进制码的最高位作为格雷码的最高位,而次高位格雷码为二进制码的高位与次高位相异或,而格雷码其余各位与次高位的求法相类似。

                                       

333 多位二进制码如何转化为格雷码

我再换种更简单的描述 

二进制数                                     1 0 1 1 0

二进制数右移1位,空位补0              0 1 0 1 1

异或运算                                     1 1 1 0 1  

这样就可以实现二进制到格雷码的转换了,总结就是移位并且异或,verilog代码实现就一句:assign wgraynext = (wbinnext>>1) ^wbinnext;是不是非常简单。

异步FIFO程序设计详见代码附件,这里不再赘述。

1.1.4异步FIFO IP设计分析

利用Vivado和Quartus II调用FIFO模块,方法同同步FIFO IP设计分析相同,在此不再累述。



本站仅提供存储服务,所有内容均由用户发布,如发现有害或侵权内容,请点击举报
打开APP,阅读全文并永久保存 查看更多类似文章
猜你喜欢
类似文章
【热】打开小程序,算一算2024你的财运
异步FIFO设计(非常详细,图文并茂,值得一看!)
高速异步FIFO的实现
【学术论文】一种高可靠性高速可编程异步FIFO的设计
FIFO使用情况
FIFO原理及其应用
AL422B引脚图时序图及pdf中文资料-FIFO芯片
更多类似文章 >>
生活服务
热点新闻
分享 收藏 导长图 关注 下载文章
绑定账号成功
后续可登录账号畅享VIP特权!
如果VIP功能使用有故障,
可点击这里联系客服!

联系客服