异步FIFO理解及设计

一、异步FIFO理解

1.1异步FIFO结构

由图可见,异步FIFO的核心部件就是一个 Simple Dual Port RAM ;左右两边的长条矩形是地址控制器,负责控制地址自增、将二进制地址转为格雷码以及解格雷码;下面的两对D触发器 sync_r2w 和 sync_w2r 是同步器,负责将写地址同步至读时钟域、将读地址同步至写时钟域。 

1.2异步FIFO空满的判断 

        在同步FIFO设计中,fifo_num是由写地址减去读地址所得到,利用fifo_num 来作为FIFO空或满的判断依据。而对于异步FIFO而言,数据是由某一个时钟域的控制信号写入FIFO,而由另一个时钟域的控制信号将数据读出FIFO。也就是说,读写指针的变化动作是由不同的时钟产生的,处在不同的时钟域上,所以不能直接相减。因此,对FIFO空或满的判断也是跨时钟域的,我们首先需要解决的就是如何进行异步FIFO空满的判断。

        对于异步FIFO 而言,我们用wr_num和rd_num(对应同步FIFO中的fifo_num)作为读写时钟域各自维护的一套数据计数器。wr_num作为FIFO写满的判断依据,rd_num作为FIFO读空的判断依据。为了得到wr_num,写地址wr_ptr_exp与其处于同一时钟域(写时钟域),然后,读地址rd_ptr_exp则需要从读时钟域同步到写时钟域才能使用,若没有同步,wr_num则会出现亚稳态,从而影响full和almost_full的指示信号,造成数据的丢失。同理,对于rd_num来讲,我们需要将写地址同步到读时钟域来进行rd_num的计算。

  • “写满”的判断:需要将读指针同步到写时钟域,再与写指针判断
  • “读空”的判断:需要将写指针同步到读时钟域,再与读指针判断

1.2.1跨时钟域:虚空与虚满

针对读指针跨到写时钟域以及写指针跨到读时钟域会导致“虚空”和“虚满”现象的出现,所谓的“虚空”和“虚满”现象是有意为之的保守策略,旨在绝对避免数据丢失或读错,其代价是略微降低了FIFO的实际可用吞吐量。

我们针对异步FIFO中“虚空”和“虚满”的现象,从读写两个时钟域的视角进行分情况讨论。

情况一:写时钟域 与 “虚满”(False Full)

站在写控制器(wclk域)的视角:

1. 同步过程:
    真实的读指针 rptr(格雷码)经过两级同步器后,到达写时钟域,成为rptr_sync_wclk( rptr 在2-3个 wclk 周期前的旧值)。
2. “虚满”的产生:
    写控制器的判断逻辑是:(wptr_next == rptr_sync_wclk) (格雷码比较,或转换为二进制后比较最高位和低位)。由于 rptr_sync_wclk 是一个旧值,它小于或等于当前真实的 rptr。这意味着,从写控制器的视角看,读走的数据比实际更少,FIFO的剩余空间比实际更小。 因此,写控制器会在FIFO物理上还未真正满的时候,就提前发出 full 信号。这就是“虚满”。

· 现象:full 信号过早拉高。
· 根源:同步到写时钟域的读指针 rptr_sync_wclk 是旧值,导致写控制器低估了剩余空间。
· 结果:安全地阻止写入,防止溢出(overflow),但可能轻微降低吞吐量。

情况二:读时钟域与“虚空”(False Empty)

站在读控制器(rclk域)的视角

1. 同步过程:
    真实的写指针 wptr(格雷码)经过两级同步器后,到达读时钟域,成为(wptr_sync_rclk )是 wptr 在2-3个 rclk 周期前的旧值。
2. “虚空”的产生:
   读控制器的判断逻辑是:(rptr_next == wptr_sync_rclk) (格雷码比较,或转换为二进制后比较)。由于 wptr_sync_rclk 是一个旧值,它小于或等于当前真实的 wptr。这意味着,从读控制器的视角看,写入的数据比实际更少,FIFO内的有效数据量比实际更少。因此,读控制器会在FIFO物理上还有数据的时候,就提前发出 empty 信号。这就是“虚空”。

· 现象:empty 信号过早拉高。
· 根源:同步到读时钟域的写指针 wptr_sync_rclk 是旧值,导致读控制器低估了有效数据量。
· 结果:安全地阻止读取,防止下溢(underflow),但可能轻微降低吞吐量。

如何缓解“虚空虚满”的影响?

1. 增加FIFO深度:这是最直接的方法。如果你预计到会有“虚”现象,就设计一个更深的FIFO,确保即使在最坏的同步延迟下,其“实际可用深度”也能满足你的带宽需求。
2. 优化时钟频率比:如果 wclk 和 rclk 频率相差过大,同步延迟的相对影响会更大。在设计时钟方案时可以考虑这一点。
3. 理解并接受它:最重要的是意识到这是异步FIFO的正常工作特性,而不是一个需要修复的“Bug”。你的系统设计必须能够容忍这种短暂的停顿。

1.2.2跨时钟域:①读慢写快  、②读快写慢 

写时钟频率(wclk)与读时钟频率(rclk)的比率,是决定“虚满”/“虚空”严重程度的关键因素。 我们同样分两种情况详细讨论:读慢写快/读快写慢 

情况一:写时钟显著快于读时钟(wclk >> rclk)读慢写快
1.1“虚满”不严重,FIFO利用率高

· 同步指针的陈旧程度:同步器延迟是固定的,例如 Δt = 2 * Twclk(Twclk 是写时钟周期)。在这段延迟时间 Δt 内,读侧能进行的操作非常有限。因为 rclk 很慢,可能在这 2个 wclk 周期内,读侧一个数据都没读完(rptr 几乎没动)。因此,从写侧看,rptr_sync_wclk 和真实的 rptr 几乎一模一样,几乎没有任何进展。
· “虚满”的表现:由于 rptr_sync_wclk ≈ 真实 rptr,写控制器的判断非常接近真实情况。“虚满”现象很轻微。FIFO会在非常接近真正满的时候才报告满信号。FIFO的实际可用深度非常接近其物理深度。
· 设计启示:在这种模式下,FIFO的深度利用率很高。你需要多大的缓冲,就设计差不多深度的FIFO即可。

一句话总结(wclk >> rclk):读指针变化慢,同步延迟期间它几乎不动,所以写侧看到的指针很“新鲜”,“虚满”不明显,FIFO深度利用率高

1.2“虚空”现象严重,写侧数据堆积,降低了吞吐量

· 同步指针的陈旧程度:同步器延迟是固定的 Δt = 2 * Trclk。因为 rclk 很慢,这个 Δt 是一个很长的时间段。在这段很长的延迟时间 Δt 内,写侧可以疯狂地工作!因为 wclk 很快,它可以在这段时间内写入大量的数据(wptr 飞速前进)。因此,真实的 wptr 已经往前走了很远,而读侧看到的 wptr_sync_rclk 还是一个非常陈旧的、落后的值。
· “虚空”的表现:读控制器比较 rptr 和那个陈旧的 wptr_sync_rclk,发现它们相等了,于是认为FIFO“空”了,拉起了 empty 信号并停止读取。然而事实上,写侧在这段同步延迟期间已经灌入了海量的新数据!FIFO其实非常满。“虚空”现象极其严重。empty 信号会在FIFO还有大量数据时就被拉高,读侧会错误地停止读取,导致数据堆积在FIFO中无法及时读出,极大降低了吞吐量。
· 设计启示:这是最需要避免的模式。如果系统设计成快写慢读,FIFO的深度必须设计得足够深,以容纳在读侧“发呆”(因虚假空信号而停止工作)的这段时间内,写侧持续写入的数据量。否则,FIFO会被写满,进而引发“虚满”或真满,最终导致整个数据流停滞。

一句话总结(rclk << wclk):写指针变化快,同步延迟期间它跑了很远,所以读侧看到的指针非常“陈旧”,“虚空”非常严重,导致读侧经常误停工。

情况二:写时钟显著慢于读时钟(wclk << rclk)读快写慢 
1.1“虚满”现象严重,需要极其深的FIFO

· 同步指针的陈旧程度:同步器延迟仍然是固定的 Δt = 2 * Twclk。注意,因为 wclk 很慢,这个 Δt 是一个很长的时间段。在这段很长的延迟时间 Δt 内,读侧可以疯狂地工作!因为 rclk 很快,它可以在这段时间内读完大量的数据(rptr 飞速前进)。因此,真实的 rptr 已经往前走了很远,而写侧看到的 rptr_sync_wclk 还是一个非常陈旧的、“远古时代”的值。
· “虚满”的表现:写控制器比较 wptr 和那个陈旧的 rptr_sync_wclk,发现它们快要相等了(快要“满”了),于是惊慌地拉起了 full 信号。然而事实上,读侧在这段同步延迟期间已经腾出了海量的空间!FIFO其实非常空。“虚满”现象极其严重。full 信号会在FIFO还非常空的时候就被拉高,从而极大地浪费了FIFO的深度。
· 设计启示:这是最需要警惕的模式。如果你需要一个吞吐量很高的写接口(即使wclk慢,但突发数据量大),你必须设计一个极其深的FIFO。这个FIFO的深度不仅要能容纳突发数据,还必须能抵消掉因为同步延迟而严重低估的可用空间。所需深度可能远超你的直觉想象。

一句话总结(wclk << rclk):读指针变化快,同步延迟期间它跑了很远,所以写侧看到的指针非常“陈旧”,“虚满”非常严重,需要极深的FIFO来补偿。

1.2“虚空”不明显,数据利用率高

· 同步指针的陈旧程度:同步器延迟是固定的时间,例如 Δt = 2 * Trclk。因为 rclk 很快,Trclk 非常短,所以这个 Δt 是一个极短的时间段。在这段极短的延迟时间 Δt 内,写侧能进行的操作非常有限。因为 wclk 很慢,可能在这 2个 rclk 周期内,写侧一个数据都没写完(wptr 几乎没动)。因此,从读侧看,wptr_sync_rclk 和真实的 wptr 几乎一模一样,写侧几乎没有新的数据写入。
· “虚空”的表现:由于 wptr_sync_rclk ≈ 真实 wptr,读控制器的判断非常接近真实情况。“虚空”现象很轻微。FIFO会在非常接近真正空的时候(最后一个数据被读走后)才报告空信号。FIFO中有效数据的利用率很高,读侧能几乎及时地读到所有已写入的数据。
· 设计启示:在这种模式下,读侧的性能很好。FIFO不需要为了缓解“虚空”而特意加深,主要根据写侧的突发长度来设计深度即可。

一句话总结(rclk >> wclk):写指针变化慢,同步延迟期间它几乎不动,所以读侧看到的指针很“新鲜”,“虚空”不明显,数据利用率高。

情况三:写时钟与读时钟频率相近(wclk ≈ rclk)
“虚满”

· 同步指针的陈旧程度:同步延迟 Δt = 2 * Twclk。在这段延迟内,读侧大约能完成 2 次读操作(因为频率相近),即 rptr 会前进大约 2。
· “虚满”的表现:写控制器看到的 rptr_sync_wclk 比真实 rptr 始终小2左右。这意味着写控制器永远认为FIFO里的数据比实际多了2个。因此,当FIFO物理上还剩2个空位时,写控制器就会报告“满”。这是一种稳定且可预测的“虚满”。FIFO的实际可用深度 = 物理深度 - 2。
· 设计启示:设计时可以直接在代码或计算中考虑这个固定的偏移量。例如,你需要缓冲8个数据,就设计一个深度为10的FIFO。

“虚空”

· 同步指针的陈旧程度:同步延迟 Δt = 2 * Trclk。在这段延迟内,写侧大约能完成 2 次写操作(因为频率相近),即 wptr 会前进大约 2。
· “虚空”的表现:读控制器看到的 wptr_sync_rclk 比真实 wptr 始终小2左右。这意味着读控制器永远认为FIFO里的数据比实际少了2个。因此,当FIFO物理上还剩2个数据时,读控制器就会报告“空”。这是一种稳定且可预测的“虚空”。FIFO中总是有2个数据还没来得及被读侧发现就被“隐藏”了。
· 设计启示:这种固定的偏移是可接受的。系统需要理解,empty 有效时,FIFO中可能还有2个数据正在被同步“解锁”。FIFO的有效缓存能力比物理深度小2。

总结:

读慢写快(rclk << wclk):“虚满”不明显,FIFO深度利用率高;// “虚空”非常严重,导致读侧经常误停工,FIFO的深度必须设计得足够深。

读快写满(wclk << rclk“虚满”非常严重,需要极深的FIFO来补偿。// “虚空”不明显,数据利用率高

最终结论: “虚满”/“虚空”的严重性不是由单个时钟的频率决定的,而是由读写时钟的频率比决定的。频率差异越大,慢时钟域的一方感受到的“虚”现象就越严重。在设计异步FIFO时,必须充分考虑这个比率,才能正确设计FIFO深度,保证功能正确的同时满足性能需求。

一个优秀的系统设计应避免让FIFO工作在最恶劣的时钟模式下:

· 最恶劣的写模式:wclk << rclk(导致严重虚满,浪费写带宽)。
· 最恶劣的读模式:rclk << wclk(导致严重虚空,浪费读带宽)。

现在我们会发现,所谓的空满信号实际上是不准确的,在还没有空、满的时钟就已经输出了空满信号,这样的空满信号一般称为假空、假满。假空、假满信号本质上是一种保守设计, 对FIFO 的正常功能没什么影响。我们就仅采用结构图中所用的两级触发器进行打拍同步。


另外,我们还需要理解并解决另一个问题,除了采取打拍对读写指针(地址)进行跨时钟域处理,还需要什么操作?因为跨时钟域传输一旦没处理好就会引起亚稳态问题,造成指针的值异常,从而引发FIFO的功能错误。

        在同步FIFO的设计中,我们采用改为扩展法,采用二进制编码,扩展出来的最高位来判断究竟是读指针追上写指针(读空了)还是写指针超过读指针一圈(写满了)。而在异步FIFO中,倘若我们仍然采取二进制编码的话,会很容易造成跨时钟域的亚稳态。

        因为采用二进制计数器时所有位都可能同时变化,在同一个时钟沿同步多个信号的变化会产生亚稳态问题。而使用格雷码只有一位变化,因此在两个时钟域间同步多个位不会产生问题。举个例子,二进制的7(0111)跳转到8(1000),4位都会发生变化,所以发生亚稳态的概率就比较大。而格雷码的跳转就只有一位(从0100--1100,仅第四位发生变化)会发生变化,有效地减小亚稳态发生的可能性。所以需要一个二进制到gray码的转换电路,将地址值转换为相应的gray码,然后将该gray码同步到另一个时钟域进行对比,作为空满状态的检测。

1.2.2格雷码 

  • 二进制码与格雷码之间的相互转换

1.二进制转格雷码

gray[3] = 0         ^ bin[3];----gray[3] = bin[3] 异或0等于自身
gray[2] = bin[3]  ^ bin[2];
gray[1] = bin[2]  ^ bin[1];   
gray[0] = bin[1]  ^ bin[0]; 

2.格雷码转回二进制 

 最高位不需要转换,从次高位开始使用二进制的高位和次高位格雷码相异或 

仿真图: 

  •  格雷码如何进行空满判断 

          与二进制一样,首先我们需要将指针向高位拓展一位,这是为了判断写指针是否超过读指针一圈。然后通过对比除了最高位的其余位来判断读写指针是否重合。这种方法判断二进制的指针是没有问题的,但是这不适合格雷码形式的指针,因为格雷码是镜像对称的,若只根据最高位是否相同来区分是读空还是写满是有问题的,如下图:

例如,假设FIFO深度为8,读指针在0_100 (7),写指针位于1_100 (8) ,若按照二进制的指针判断法则去判断,写指针超过读指针一圈,则说明FIFO写满。但此时,实际的写指针指向的地址为0_000(0),读写指针并未重合(写满)。因此,我们采取格雷码进行判断FIFO空满我们应采用另外的方法,我们可以看出,0-8,1-9,2-10,3-11,4-12,5-13,6-14,7-15,(最高和次高为不同,剩余位形同).

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

二、异步FIFO 设计(Verilog代码及仿真) 

2.1.TOP文件

`timescale 1ns / 1ps
module asnch_fifo_top#(
    parameter  DATASIZE_T = 32, // Memory data word width               
    parameter  ADDRSIZE_T = 6   // 地址指针位宽设定为6,地址宽度则为5(定义为6是为进位),fifo深度则为2^5
)(
    input                       w_clk   ,
    input                       w_rst_n ,
    input                       winc    ,
    input [DATASIZE_T - 1:0]    w_data  ,
    output                      w_full  ,
    input                       r_clk   ,
    input                       r_rst_n ,
    input                       rinc    ,
    output[DATASIZE_T - 1:0]    r_data  ,
    output                      r_empty 
    );

wire[ADDRSIZE_T:0]       wptr   , rptr  ; 
wire[ADDRSIZE_T:0]       r_gray_rptr,r_gray_rptr_d2;
wire[ADDRSIZE_T:0]       r_gray_wptr,r_gray_wptr_d2;
wire[ADDRSIZE_T - 1:0]   w_addr , r_addr;
//*******************写时钟域:产生ram写地址,写指针以及其格雷码,写满判断**************\\
wptr_full#(
    .ADDRSIZE           (ADDRSIZE_T)    
)
wptr_full_u0(
    .w_clk              ( w_clk         ),
    .w_rst_n            ( w_rst_n       ),
    .winc               ( winc          ),
    .rq2_rptr           ( r_gray_rptr_d2),
    .wptr               ( wptr          ),  //写指针
    .r_gray_wptr        ( r_gray_wptr   ),  //写指针格雷码
    .w_addr             ( w_addr        ),  //ram写地址
    .w_full             ( w_full        )
);
//*******************格雷码读指针在写时钟域同步**************\\
fifo_r2w#(  
    .ADDRSIZE           (ADDRSIZE_T)
)
fifo_r2w_u0(
    .w_clk               (w_clk      ),
    .w_rst_n             (w_rst_n    ),
    .rptr                (r_gray_rptr),  
    .rq2_rptr            (r_gray_rptr_d2)  //同步用于full判断
);
//*******************读时钟域:产生ram读地址,读指针以及其格雷码,读空判断**************\\
fifo_empty#(
    .ADDRSIZE           (ADDRSIZE_T) 
)
fifo_empty_u0(
    .r_clk              (r_clk  ),
    .r_rst_n            (r_rst_n),
    .rinc               (rinc   ),
    .rq2_wptr           (r_gray_wptr_d2),
    .rptr               (rptr   ),      //读指针(二进制
    .r_gray_rptr        (r_gray_rptr),  //读指针(格雷码
    .r_addr             (r_addr ),      //ram读地址
    .r_empty            (r_empty)
);
//*******************格雷码写指针在读时钟域同步**************\\
fifo_w2r#(
   .ADDRSIZE           (ADDRSIZE_T) 
)fifo_w2r_u0(
    .r_clk              (r_clk  ),
    .r_rst_n            (r_rst_n),
    .wptr               (r_gray_wptr   ),
    .rq2_wptr           (r_gray_wptr_d2) //同步用于empty判断
    );
//*******************ram寄存器**************\\
fifo_mem#(
    .DATASIZE           (DATASIZE_T) ,            
    .ADDRSIZE           (ADDRSIZE_T) 
)
fifo_mem_u0(
    .w_clk              (w_clk   )  ,
    .winc               (winc    )  ,
    .w_full             (w_full  )  ,
    .w_addr             (w_addr  )  ,
    .w_data             (w_data  )  ,
    .r_addr             (r_addr  )  ,
    .r_data             (r_data  )
);
endmodule

 2.2 写文件

`timescale 1ns / 1ps
module wptr_full#(
    parameter   ADDRSIZE   =   6
)(
    input                       w_clk   ,
    input                       w_rst_n ,
    input                       winc    ,
    input [ADDRSIZE : 0]        rq2_rptr,   //格雷码
    output[ADDRSIZE : 0]        wptr    ,   //二进制
    output[ADDRSIZE : 0]        r_gray_wptr,//格雷码
    output[ADDRSIZE - 1 : 0]    w_addr  ,   //二进制
    output                      w_full  
);

reg  [ADDRSIZE : 0]        r_wptr     ;

wire [ADDRSIZE : 0]        w_gray_wptr;

assign  wptr   =  r_wptr;
assign  w_addr =  wptr[ADDRSIZE - 1 : 0];  //产生二进制实际ram地址

assign  r_gray_wptr = w_gray_wptr;


always @(posedge w_clk or negedge w_rst_n) begin
    if(!w_rst_n)
        r_wptr <= 'd0;
    else if(winc && !w_full) 
        r_wptr <= r_wptr + 1;
    else
        r_wptr <= r_wptr;
end

bin2gray#(
    .P_DATA_WIDTH       (ADDRSIZE)
)
bin2gray_u0(
    .i_bin          (r_wptr     ),
    .o_gray         (w_gray_wptr)
);

assign w_full = (w_gray_wptr == {~rq2_rptr[ADDRSIZE:ADDRSIZE-1],rq2_rptr[ADDRSIZE-2:0]});

endmodule

2.3 读文件

`timescale 1ns / 1ps
module fifo_empty#(
    parameter   ADDRSIZE   =   6
)(
    input                       r_clk       ,
    input                       r_rst_n     ,
    input                       rinc        ,
    input [ADDRSIZE : 0]        rq2_wptr    ,
    output[ADDRSIZE : 0]        rptr        ,
    output[ADDRSIZE : 0]        r_gray_rptr ,
    output[ADDRSIZE - 1 : 0]    r_addr      ,
    output                      r_empty 
);

reg [ADDRSIZE : 0]              r_rptr      ;
reg                             r_r_empty   ;
wire[ADDRSIZE : 0]              w_gray_rptr ;
wire                            empty_value ;

assign r_empty       = r_r_empty;
assign rptr          = r_rptr;
assign r_addr        = rptr[ADDRSIZE - 1 : 0];
assign r_gray_rptr   = w_gray_rptr;

always @(posedge r_clk or negedge r_rst_n) begin
    if(!r_rst_n)
        r_rptr <= 'd0;
    else if(rinc && !r_empty)
        r_rptr <= r_rptr + 1;
    else
        r_rptr <= r_rptr;
end

bin2gray#(
    .P_DATA_WIDTH    (ADDRSIZE)
)
bin2gray_u2(
    .i_bin   (r_rptr     ),
    .o_gray  (w_gray_rptr)
);

assign empty_value = (w_gray_rptr == rq2_wptr);
always @(posedge r_clk or negedge r_rst_n) begin
    if(!r_rst_n)
        r_r_empty <= 'd0;
    else
        r_r_empty <= empty_value;
end
endmodule

2.4 读写指针同步文件 

`timescale 1ns / 1ps
module fifo_r2w#(
    parameter   ADDRSIZE    =   6
)(
    input                   w_clk   ,
    input                   w_rst_n ,
    input [ADDRSIZE : 0]    rptr    ,
    output[ADDRSIZE : 0]    rq2_rptr
    );
reg[ADDRSIZE:0] r_rptr_d1;
reg[ADDRSIZE:0] r_rptr_d2;

always @(posedge w_clk or negedge w_rst_n) begin
    if(!w_rst_n)begin
        r_rptr_d1 <= 'd0;
        r_rptr_d2 <= 'd0;
    end
    else begin
        r_rptr_d1 <= rptr;
        r_rptr_d2 <= r_rptr_d1;
    end
end

assign rq2_rptr = r_rptr_d2;
endmodule

`timescale 1ns / 1ps
module fifo_w2r#(
    parameter   ADDRSIZE    =   6
)(
    input                   r_clk   ,
    input                   r_rst_n ,
    input [ADDRSIZE : 0]    wptr    ,
    output[ADDRSIZE : 0]    rq2_wptr
    );

reg[ADDRSIZE:0] r_wptr_d1;
reg[ADDRSIZE:0] r_wptr_d2;
always @(posedge r_clk or negedge r_rst_n) begin
    if(!r_rst_n)begin
        r_wptr_d1 <= 'd0;
        r_wptr_d2 <= 'd0;
    end
    else begin
        r_wptr_d1 <= wptr;
        r_wptr_d2 <= r_wptr_d1;
    end
end
assign rq2_wptr = r_wptr_d2;

endmodule

2.5 TB文件

`timescale 1ns / 1ps
module asnch_fifo_tb();
    reg                       w_clk   ;
    reg                       w_rst_n ;
    reg                       winc    ;
    reg [8 - 1:0]             w_data  ;
    wire                      w_full  ;
    reg                       r_clk   ;
    reg                       r_rst_n ;
    reg                       rinc    ;
    wire[8- 1:0]              r_data  ;
    wire                      r_empty ;
asnch_fifo_top#(
    .DATASIZE_T  (8), // Memory data word width               
    .ADDRSIZE_T  (3)  // 地址指针位宽设定为4,地址宽度则为3(定义为6是为进位),fifo深度则为2^3
)
asnch_fifo_top_u0(
    .w_clk                  (w_clk   ),
    .w_rst_n                (w_rst_n ),
    .winc                   (winc    ),
    .w_data                 (w_data  ),
    .w_full                 (w_full  ),
    .r_clk                  (r_clk   ),
    .r_rst_n                (r_rst_n ),
    .rinc                   (rinc    ),
    .r_data                 (r_data  ),
    .r_empty                (r_empty )
    );

 initial begin  //设置写时钟,写周期是20ns,50Mhz
    w_clk=0;
    forever #10 w_clk=~w_clk;
end

initial begin  //设置读时钟,读周期是10ns,100Mhz
    r_clk=0;
    forever  #5 r_clk=~r_clk;
end
                    
initial begin
    w_rst_n=1'b0;      //写复位
    r_rst_n=1'b0;      //读复位
    winc   =1'b0;      //写无效
    rinc   =1'b0;      //读无效
    w_data=0;          //初始写数据为0  
    #28 w_rst_n=1'b1;  //松开写复位
        r_rst_n=1'b1;  //松开读复位
        winc =1'b1;    //写有效
        w_data=1;      //输入数据为1
    @(posedge w_clk);//写入数据     
    repeat(7)       //接着写入2,3,4,5,6,7,8这些数据
            begin
                 #18;
                 w_data=w_data+1'b1;
                 @(posedge w_clk);  
             end
    #18    w_data = w_data+1'b1;  //此时异步FIFO已经写满了,在往同步FIFO中写数据8
    //8这个数据不会被写进
    @(posedge r_clk);     
    #8  rinc=1'b1;      //读使能,写无效 
        winc=1'b0;
    @(posedge r_clk);     //第一个读出的数为1
    repeat(7)        //读取剩余的数
       begin
           @(posedge r_clk);   
       end
    #2;
      rinc=1'b0;        //结束读操作                                    
end    
endmodule

仿真

 能够成功实现仿真,具体的细致分析与应用需要进一步学习!

PS:小白学习,借鉴颇多。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值