异步fifo读空、写满分析(附完整异步fifo代码)

一、fifo介绍

  FIFO 是 First-In, First-Out 的缩写,即“先进先出”,是一种常见的数据结构,类似于排队服务。在 FIFO 结构中,最先进入的数据项首先被取出,后进入的数据项则会在队列中等待。
  FIFO 在计算机科学和电子工程中被广泛使用,用于数据存储和传输。以下是 FIFO 的一些特点和应用领域:

  1. 特点

    • 数据按照先进先出的顺序进行存储和检索。
    • 支持两个基本操作:入队(enqueue)将数据放入队列尾部,出队(dequeue)从队列头部取出数据。
    • FIFO 可以用数组或链表等数据结构来实现。
  2. 应用领域

    • 数据缓冲:FIFO 用于临时存储数据,例如在数据传输中平衡发送者和接收者之间的速度差异。
    • 任务调度:在操作系统中,FIFO 可用于调度任务执行顺序,确保按照提交顺序执行任务。
    • 硬件设计:在电子工程中,FIFO 经常用于在不同速度的系统之间传输数据,如存储器和外设之间的数据传输。
  3. 实际应用

    • 计算机网络中的数据包传输;
    • 操作系统中的进程调度;
    • 缓存控制器中的数据暂存等。
        总的来说,FIFO 是一种简单而有效的数据结构,可用于多种应用场景,通过保持数据的顺序性,确保了数据的正确性和完整性。

二、fifo的读空和写满

  在使用 FIFO(First-In, First-Out)队列时,有两种常见的情况需要特别注意,即"读空"和"写满":
  读空(Read Empty):读空表示在尝试从 FIFO 队列中读取数据时,队列已经为空的状态。这意味着没有可用的数据项供读取,如果此时再尝试读取数据,将导致读取操作失败或者返回空值。处理读空状态的方法通常包括等待新数据到达(阻塞式读取)、返回特定值(如错误代码)或者执行其他适当的错误处理逻辑。
  写满(Write Full):写满表示在尝试向 FIFO 队列中写入数据时,队列已经达到了最大容量,无法再写入新数据的状态。如果继续尝试写入数据,可能导致写入操作失败或者丢失数据。处理写满状态的方法通常包括等待队列中的数据被消费(阻塞式写入)、返回特定值(如错误代码)或者执行其他适当的错误处理逻辑。
  理解读空和写满的概念对于正确地处理 FIFO 队列非常重要。在实际应用中,可以通过监视读取和写入操作的返回状态(如返回值或错误码)来判断队列是否处于读空或写满状态,并根据情况采取相应的处理措施。合理处理读空和写满状态有助于确保数据的完整性和系统的稳定性。

同步fifo的读空和写满

  对于同步fifo来说,读和写工作在一个时钟域,可以通过一个计数器来判断空满。
当同步FIFO写入一次数据,计数器加1。
当同步FIFO读出一次数据,计数器减1。
这时就可以通过计数器的值来判断空满,当计数器的值为0,则表示同步FIFO中没有可读的数据,此时同步FIFO为空。
当计数器的值等于同步FIFO的深度时,则表示同步FIFO中已经写满了。

异步fifo的读空和写满

  异步fifo的读和写工作在不同的时钟域,因此无法通过计数器进行读空和写满的判断,需要用读指针和写指针来进行判断,后续会进行详细讲解。

三、异步fifo的读空和写满具体理解

  通过比较写指针和读指针来判断空满,如下图所示,先对异步FIFO写入数据1,写两次,然后对异步FIFO读两次,把之前写进去的数据1都读出来,已经没有数据可读,所以此时异步FIFO处于空状态。注意此时写指针和读指针相等,都是指向地址为2的存储空间。
  然后对异步FIFO写数据0,写6次,此时写指针重新指向地址0所对应的存储空间,此时的状态不是满状态,因为之前写入两次数据1已经被读出,此时地址0和地址1还是可以写入数据的,再对异步FIFO写数据0,写两次,此时异步FIFO中所有的存储空间都写入数据,并且没有一个数据是被读出过的,所以此时是满状态,此时也是写指针和读指针相等。
  所以可以总结当读指针追上写指针时,异步FIFO处于空状态。写指针追上读指针时,异步FIFO处于满状态。但是这种方法处理后,仍然有一个问题就是读写指针相等时,难以区分是空状态还是满状态。
在这里插入图片描述
  因此需要多引入1位来判断是读指针追上写指针还是写指针追上读指针,对于上图中的fifo,深度为8,本来需要3位就可以描述读指针和写指针,这里需要变成4位来描述读指针和写指针,FIFO的深度决定了指针扩展前的宽度,而这扩展的一位与FIFO深度无关,是为了标志指针多转了一圈。
  因此,当读写指针完全相同时,此时FIFO为空状态。
  当读写指针最高位不同,其余位宽相同时,FIFO为满状态。
  用扩展指针的方式分析上面的例子,如下图所示,空状态时,rptr=0010,wptr=0010,读写指针完全相同,为空状态。满状态时,rptr=0010,wptr=1010,读写指针最高位不同,其余位宽相同,为满状态。
在这里插入图片描述
  经过指针扩展,可以进行空满检测。但是异步FIFO读写时钟不同,而读写指针又是分属各自的时钟域,在进行空满检测的时候,是需要将读写指针进行跨时钟域处理的,即读指针跨时钟到写指针,写指针跨时钟到读指针。
  在跨时钟域时,使用二进制的方式很容易出现错误,容易出现亚稳态,相邻二进制地址变化时,不止一位发生变化。比如写指针从0111到1000跳变时,4位地址都可以发生改变,这样读时钟在进行写指针同步后得到的写指针可能是0000~1111中任意一个值,这些都是不可控的,出错的概率非常大,因此需要引入格雷码。
  格雷码对于相邻的数每次只变化一位,这样就大大的降低了亚稳态出现的概率。常用的格雷码和二进制码之间的转换关系如下表所示:
在这里插入图片描述
  可以观察到,对于二进制码来说,当fifo深度为8时,低三位用来表示读和写的指针的位置,最高位(即第3位)用来判断读空和写满,而对于格雷码来说是要用高两位(即第3位和第2位)来判断读空和写满。当读和写指针的所有位都相同时,判断为读空;当读和写指针的高两位不同,低两位相同时,判断为写满。

四、Verilog实现

架构

  异步fifo的内部电路架构如下图所示:
在这里插入图片描述
异步FIFO的内部电路主要分为5个部分,会在下面部分一一详细介绍。在这里主要介绍一下内部的信号。
wptr为写指针,写指针的位数比写地址多一位。
rptr为读指针,读指针的位数比读地址多一位。
waddr为写地址,为具体把数据写入FIFO_Memory的地址。
raddr为读地址,为具体读出FIFO_Memory中数据的地址。
wq2_rptr为将读指针同步到写时钟域的信号。
rq2_wptr为将写指针同步到读时钟域的信号。

写指针、写满信号和写地址产生模块

Verilog代码如下:

 module wptr_wfull #(
                               parameter ADDRSIZE=4)
                       (  
                        input                                        winc,
                        input                                        wclk,
                        input                                        wrst_n,
                        input         [ADDRSIZE:0]                   wq2_rptr,
                        output reg                                   wfull,
                        output reg    [ADDRSIZE:0]                   wptr,
                        output        [ADDRSIZE-1:0]                 waddr
                        );
                        wire              full_value;
                        wire [ADDRSIZE:0] wbinnext , wgraynext;
                        reg  [ADDRSIZE:0] wbin;
              //写地址产生     
              assign wbinnext=wbin+(winc&!wfull);                                              
              always@(posedge wclk or negedge wrst_n)  
              begin
                       if(wrst_n==1’b0) 
                            wbin<=0;           
                       else
                           wbin<=wbinnext;        
              end
              assign waddr=wbin[ADDRSIZE-1:0];        
              
              //写指针产生    
              assign wgraynext=wbinnext^(wbinnext>>1);
              always @(posedge wclk or negedge wrst_n) 
              begin
                       if(wrst_n==1’b0)
                             wptr <=0;
                       else
                             wptr<=wgraynext;
              end
              
              //满信号产生
              assign  full_value=wgraynext == ({~wq2_rptr[ADDRSIZE:ADDRSIZE-1],wq2_rptr[ADDRSIZE-2:0]});
              always@(posedge wclk or negedge wrst_n)
              begin
                   if(wrst_n==1’b0)
                       wfull<=1’b0;
                  else
                      wfull<=full_value; 
             end
             endmodule

读指针、读空信号和读地址产生模块

Verilog代码如下:

     module rptr_rempty #(
                               parameter ADDRSIZE=4)
                     (  
                        input                                        rinc,
                        input                                        rclk,
                        input                                        rrst_n,
                        input        [ADDRSIZE:0]                    rq2_wptr,
                        output reg                                   rempty,
                        output reg   [ADDRSIZE:0]                    rptr,
                        output       [ADDRSIZE-1:0]                  raddr
                        );
                       wire empty_value;
                       wire [ADDRSIZE:0] rbinnext , rgraynext;
                       reg  [ADDRSIZE:0] rbin;

                     //读地址产生     
                     assign rbinnext=rbin+(rinc&!rempty);
                     always@(posedge rclk or negedge rrst_n)                   
                     begin
                             if(rrst_n ==1’b0)                   
                                 rbin <=0;            
                             else
                                 rbin <=rbinnext;        
                     end
                     assign raddr=rbin[ADDRSIZE-1:0];

                   //读指针产生    
                     assign rgraynext=rbinnext^(rbinnext>>1);
                     always@(posedge rclk or negedge rrst_n)
                     begin
                             if(rrst_n ==1’b0)           
                                 rptr <=0; 
                             else
                                 rptr<=rgraynext;
                      end
              
                      //空信号产生
                      assign  empty_value=rgraynext == rq2_wptr;
                      always@(posedge rclk or negedge rrst_n)
                      begin
                            if(rrst_n==1’b0)
                               rempty<=1’b0;
                            else
                               rempty<=empty_value; 
                      end
                 endmodule

读指针同步模块

Verilog代码如下:

           module sync_r2w #(
                               parameter ADDRSIZE=4)
                             (  
                                   input                       wclk,
                                   input                       wrst_n,
                                   input       [ADDRSIZE:0]    rptr,
                                   output reg  [ADDRSIZE:0]    wq2_rptr
                       );
                       
           reg [ADDRSIZE:0] wq1_rptr;
           always@(posedge wclk or negedge wrst_n)
           begin
                if(wrst_n==1’b0)
                                  {wq2_rptr, wq1_rptr}<=2’b0;
                else
                                  {wq2_rptr, wq1_rptr}<= {wq1_rptr, rptr} ;       
            end
            endmodule

写指针同步模块

  module sync_w2r #(
                               parameter ADDRSIZE=4)
                     (  
                        input                                    rclk,
                        input                                    rrst_n,
                        input         [ADDRSIZE:0]               wptr,
                        output reg    [ADDRSIZE:0]               rq2_wptr
                       );
     reg [ADDRSIZE:0] rq1_wptr;
     always@(posedge rclk or negedge rrst_n)
     begin
          if(rrst_n==1’b0)
             {rq2_wptr, rq1_wptr}<=2’b0;
          else
             {rq2_wptr, rq1_wptr}<= {rq1_wptr, wptr};       
     end
     endmodule

数据存储模块

         module fifomem #(
                               parameter ADDRSIZE=4,
                               parameter DATASIZE=8)
                     (
                        input                                   winc,
                        input                                   wclk,
                        input                                   wfull,
                        input   [ADDRSIZE-1:0]   waddr , raddr,
                        input   [DATASIZE-1:0]   wdata ,  
                        output  [DATASIZE-1:0]   rdata
                       );
                       localparam DEPTH=1<<ADDRSIZE;
                       reg [DATASIZE-1:0] mem [0:DEPTH-1];
                       assign rdata=mem[raddr];
                       always@(posedge wclk)
                       begin
                                if(! wfull & winc)
                                                  mem[waddr]<= wdata;
                       end
                       endmodule

顶层模块

         module afifo #(
                        parameter ADDRSIZE=4,
                        parameter DATASIZE=8)
                       (
                          input   wclk,
                          input   rclk,

                          input   wrst_n,
                          input   rrst_n,

                          input   winc,
                          input   rinc,

                         input    [DATASIZE-1:0]  wdata,
                         
                         output   [DATASIZE-1:0]  rdata,
                         
                         output   rempty,
                         output   wfull
                        );
                wire [ ADDRSIZE:0] wq2_rptr;
                wire [ ADDRSIZE:0] wptr;
                wire [ ADDRSIZE-1:0] waddr;
                
                wire [ ADDRSIZE:0] rq2_wptr;
                wire [ ADDRSIZE:0] rptr;
                wire [ ADDRSIZE-1:0] raddr;
                
                 wptr_wfull u1(
                               .winc(winc),
                               .wclk(wclk),
                               .wrst_n(wrst_n),
                               .wq2_rptr(wq2_rptr),
                               .wfull(wfull),
                               .wptr(wptr),
                               .waddr(waddr)
                               );
                rptr_rempty u2(  
                               .rinc(rinc),
                               .rclk(rclk),
                               .rrst_n(rrst_n),
                               .rq2_wptr(rq2_wptr),
                               .rempty(rempty),
                               .rptr(rptr),
                               .raddr(raddr)
                        );
                  sync_r2w  u3(  
                                .wclk(wclk),
                                .wrst_n(wrst_n),
                                .rptr(rptr),
                                .wq2_rptr(wq2_rptr)
                       );
                  sync_w2r u4(
                                .rclk(rclk),
                                .rrst_n(rrst_n),
                                .wptr(wptr),
                                .rq2_wptr(rq2_wptr)
                       );
                  fifomem u5(
                                .winc(winc),
                                .wclk(wclk),
                                .wfull(wfull),
                                .waddr(waddr), 
                                .raddr(raddr),
                                .wdata(wdata),  
                                .rdata(rdata)
                       );
                  
          endmodule

Testbench

  module afifo_tb();
  reg        wclk;
  reg        rclk;
  reg        wrst_n;
  reg        rrst_n;
  reg        winc;
  reg        rinc;
  reg  [7:0] wdata;
  wire [7:0] rdata;
  wire       rempty;
  wire       wfull;
  
              afifo u6(
                       .wclk(wclk),
                       .rclk(rclk), 
                       .wrst_n(wrst_n),
                       .rrst_n(rrst_n),
                       .winc(winc),
                       .rinc(rinc),
                       .wdata(wdata),
                       .rdata(rdata),
                       .rempty(rempty),
                       .wfull(wfull)
                       );
                       
          initial begin                    //设置写时钟,写周期是20ns,50Mhz
                             wclk=0;
                             forever    #10     wclk=~wclk;
                    end
                    
           initial begin                    //设置读时钟,读周期是10ns,100Mhz
                             rclk=0;
                             forever    #5      rclk=~rclk;
                    end
                    
           initial begin
                 
                           wrst_n=1'b0;     //写复位
                           rrst_n=1'b0;     //读复位
                            winc =1'b0;     //写无效
                            rinc =1'b0;     //读无效
                            wdata=0;        //初始写数据为0
                           
                    #28     wrst_n=1'b1;    //松开写复位
                               rrst_n=1'b1;    //松开读复位
                            winc =1'b1;     //写有效
                            wdata=1;        //输入数据为1
                            @(posedge wclk);//写入数据     

                            repeat(7)       //接着写入2,3,4,5,6,7,8这些数据
                                    begin
                                         #18;
                                         wdata=wdata+1'b1;
                                         @(posedge wclk);  
                                     end
                     #18    wdata=wdata+1'b1;  //此时异步FIFO已经写满了,在往同步FIFO中写数据8
                                     //8这个数据不会被写进
                            
                            @(posedge rclk);   
                                
                                #8      rinc=1’b1;      //读使能,写无效 
                                            winc=1'b0;
                                @(posedge rclk);     //第一个读出的数为1
                                repeat(7)        //读取剩余的数
                                   begin
                                       @(posedge rclk);   
                                   end
                                   #2;
                                     rinc=1‘b0;        //结束读操作
                                    
              end    
endmodule

仿真结果如下:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

### 实现 FIFO 缓冲区的预(Almost Full)信号设计 在 FIFO 的设计中,`almost_full` 信号用于通知外部控制器当前 FIFO 已接近容量上限,从而避免发生溢出情况。这种机制通过提前警告来优化数据流控制[^4]。 #### Almost Full 阈值设定 几乎所有的 FIFO 设计都允许用户自定义 `almost_full` 的触发阈值。这一功能通常由寄存器配置完成,具体来说,可以通过硬件接口提供一个可编程参数 `cfg_almost_full` 来指定此临界点的位置。例如,在具有 N 个存储单元的 FIFO 中,可以将 `cfg_almost_full` 设置为某个小于 N 的整数 K,则当 FIFO 当前填充数量达到 K 时,`almost_full` 将被激活。 #### 指针比较逻辑 为了生成 `almost_full` 信号,需要实时监控 FIFO指针 (`w_ptr`)指针 (`r_ptr`) 并计算两者之间的差值。这个差异反映了 FIFO 当前已占用的间大小。一旦该差值等于预先设定好的阈值 K (即 cfg_almost_full),则应驱动 `almost_full` 输出高电平: ```verilog always @(posedge clk or negedge reset_n) begin if (!reset_n) begin almost_full <= 1'b0; end else begin integer diff; // 计算 w_ptr 和 r_ptr 的相对距离 if (w_ptr >= r_ptr) begin diff = w_ptr - r_ptr; end else begin diff = FIFO_DEPTH + w_ptr - r_ptr; // 考虑环形结构特性 end // 如果差距超过预定阈值,则拉高 almost_full if (diff >= cfg_almost_full && !full) begin almost_full <= 1'b1; end else begin almost_full <= 1'b0; end end end ``` 上述代码片段展示了如何基于两个指针位置动态调整 `almost_full` 状态的变化过程[^5]。 #### 同步与异步环境下的注意事项 对于同步 FIFO,由于所有操作共享同一个时钟源,因此可以直接利用组合逻辑快速响应任何输入条件改变并更新相应标志位;然而,在跨时域工作的异步 FIFO 场景下,则需额外引入格雷码转换电路以及其他握手协议措施以消除亚稳态影响,确保最终得到稳定可靠的 `almost_full` 提示信息[^2]。 另外值得注意的是,尽管仿真工具能够在一定程度上验证这些复杂行为模式的有效性,但由于实际物理器件可能存在未建模延迟等因素干扰,某些潜在缺陷仍难以完全暴露出来[^3]。 ---
评论 19
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值