FPGA实现同步FIFO

  • FIFO

FIFO表示先入先出,是一种存储器结构,被广泛应用于芯片设计当中。FIFO由存储单元阵列或队列组成,第一个被写入的数据也是第一个从数列中读出的数据。在芯片设计中,FIFO可以满足下列需求:

  1. 当输入速率与输出速率不匹配时,作为临时存储单元。例如,CPU可以先将数据写入FIFO,然后继续其他工作,设备可以很方便地从FIFO中读取数据。再如以太网控制器,它将从网络接收来的数据存入FIFO,后端的DMA(直接存储器访问)控制器从FIFO中读取数据,然后写入存储系统。
  2. 用于不同时钟域之间的同步。实际应用中,数据将不得不从一个时钟域进入另一个时钟域,此时FIFO不仅用作临时数据存储单元,也起到数据同步的作用。
  3. 输入数据和输出数据路径之间位宽不匹配时,可以用于数据位宽调整电路。
  • FIFO操作

一开始(电路复位后),FIFO为空,写指针和读指针都指向同一个位置,该位置通常为零。w_data端口进来的数据被依次写入FIFO内的不同存储位置上。当FIFO被读出时,r_data端口将数据送出。如果我们持续以每次一个字的速度将数据写人FIFO,w_ptr将不断增加,当达到地址范围的最大值(例如是111)时,w_ptr又回到0。将数据写人FIFO时,w_en信号有效,同时数据需要被提供给写人端口。时钟周期结束时,写指针调整为下一个值。

类似地,当我们需要从FIFO中读取数据时,将r_en信号置为有效。在下一个时钟期开始时,总线r_data的数据可用,r_ptr指向下一个数值,当其达到最大值时,返回到0。换句话说,写指针和读指针以环形的方式移动,写指针在前,读指针追随。持续写入时,写指针会按照如下方式变化:000->001->010->011->100->101->110->111-000->001。写操作和读操作也被称为push/pop、put/get或fill/drain操作。

我们也可以同时对FIFO进行写入和读取操作,因为两种操作使用各自的指针、使能信号和数据总线。FIFO的这种操作就像一个水箱,它有一个进水口让水进入水箱,还有一个出口让水流出水箱。在任何时刻,都可以让水进入水箱,同时又可以从水箱取水。我们只需要关心在任一时钟周期,写入和读取的位置不能相同,因为只有当FIFO为满或空时,写入和读指针才可能相同。很明显,我们要确保不要发生两种情形,一是给满的FIFO写入数据(所有位置都有有效数据,没有多余位置);二是从空的FIFO中读取数据(FIFO中没有有效数据)它们分别被称为上溢(overun,写人满的FIFO)和下溢(underrun,从空的FIFO中读取频据)。FIFO将产生fifo_full和fifo_empty信号,用于表示FIFO是满的还是空的。当FIFO为满时禁止继续写人数据;当FIFO为空时,禁止继续读取数据。FIFO的读写时序图如下所示:

  • 同步FIFO

在同步FFO中,单一时钟同时用于写入和读取操作。数据流和相关的控制逻辑在同一个时钟域内处理和工作。同步FIFO用于临时存储数据,此时写入和读取操作可以同时发生,也可发生在不同时刻。由于同步FIFO中只使用了一个时钟,其控制逻辑相对于异步FIFO来说简单得多。前面讨论过一些输入和输出端口,现在需要增加一些有用的输出,如fifo_full、fifo_empty、room_available和data_available。从名称可以看出,fifo_full信号表示FIFO为满的状态,

fifo_empty信号表示FIFO为空的状态。这两个信号(在工业上也被广泛地称为标识)是边界条件,用于提醒外部电路不要对满的FIFO写入和对空的FIFO读出。

FIFO还提供其他标识,如almost_full和almost_empty,用于提供关于FIFO再写人多少会满以及再读出多少会空的信息。例如,所设计的FIFO中还剩余2到3个位置时almost_full有效,那么当almost_full有效时,负责写人的外围电路就应该考虑停止写入,因为从决定停止到w_en信号被置为无效可能还需要多个时钟周期。如果写人逻辑等待fifo_full标识有效后才将w_en信号置为无效,就可能太迟了,当前流水线中可能仍旧有一个或两个数据会被写人FIFO从而导致操作不正确。另外,这也取决于具体的外围电路实现方式,总之必须确保fifo_full开始有效时,w_en无效,这样上溢才不会发生。almost_empty信号也采用类似的工作方式用于阻止某个时刻的数据读取,从而避免下溢。相对于almost_full或almost_empty标识,我们有时更愿意使用room_available和data_available信号所提供的准确数据深度信息。写人逻辑使用room_avail信号,读逻辑使用data_avail信号,结合相关外围逻辑,外部电路可以主动决定采取不同操作的时机。

  • 代码实现
    `timescale 1ns / 1ps
    
    module sync_fifo
        #(     
            parameter   WIDTH   =   8,                                  //FIFO宽度
            parameter   DEPTH   =   16                                  //FIFO深度
    
        )
        (
            input                           clk,                        //系统时钟          
            input                           rst_n,                      //系统复位
            input                           r_en,                       //读使能
            input                           w_en,                       //写使能
            input               [7:0]       din,                        //写入数据
    
            output          wire            empty,                      //读空信号
            output          wire            full,                       //写满信号
            output          reg [7:0]       dout
        );
    
        reg             [WIDTH - 1 :0]      fifo_r[DEPTH - 1 : 0];      //二维寄存器实现RAM
        reg             [$clog2(DEPTH):0]   r_ptr;                      //读指针,比地址多一位
        reg             [$clog2(DEPTH):0]   w_ptr;                      //写指针,比地址多一位
       // reg             [DEPTH - 1 :0]      fifo_cnt;                   //计数器,用于指示fifo中的数据个数     
    
        
        wire    [$clog2(DEPTH) - 1 : 0]	    wr_ptr_true;				//真实写地址指针
        wire    [$clog2(DEPTH) - 1 : 0]	    rd_ptr_true;				//真实读地址指针
        wire								wr_ptr_msb;					//写地址指针地址最高位
        wire								rd_ptr_msb;					//读地址指针地址最高位
      
        assign {wr_ptr_msb,wr_ptr_true} = w_ptr;						//将最高位与其他位拼接
        assign {rd_ptr_msb,rd_ptr_true} = r_ptr;						//将最高位与其他位拼接
    
    
        //写操作
        always @(posedge clk or negedge rst_n) begin
            if(!rst_n)
                w_ptr <= 'd0;
            else if(w_en && !full)begin
                    w_ptr <= w_ptr + 1'b1;                              //写入8位数据指针加一
                    fifo_r[wr_ptr_true] <= din; 
                end
            end
    
        //读操作
        always @(posedge clk or negedge rst_n) begin
            if(!rst_n)
                r_ptr <= 'd0;
            else if(r_en && !empty)begin                                                      
                    r_ptr <= r_ptr + 1'b1;                              //读指针加一
                    dout <= fifo_r[rd_ptr_true];
                end
            end
      
    /*
         always @(posedge clk or negedge rst_n) begin
            if(!rst_n)
                fifo_cnt <= 'd0;
            else if(w_en && r_en && !full && !empty)
                fifo_cnt <= fifo_cnt;
            else if(w_en && !full && !r_en)
                fifo_cnt <= fifo_cnt + 1'b1;
            else if(r_en && !empty && !w_en)
                fifo_cnt <= fifo_cnt - 1'b1;
            else
                fifo_cnt <= fifo_cnt;
        end
    */
        //assign full = (fifo_cnt == DEPTH - 1);
        //assign empty = (fifo_cnt == 0);
        assign	empty = ( w_ptr == r_ptr ) ? 1'b1 : 1'b0;
        assign	full  = ( (wr_ptr_msb != rd_ptr_msb ) && ( wr_ptr_true == rd_ptr_true ) )? 1'b1 : 1'b0;
      
    
    endmodule
    
    `timescale  1ns / 1ps
    
    module tb_sync_fifo;
    
    // sync_fifo Parameters
    parameter PERIOD = 10;
    parameter WIDTH  = 8 ;
    parameter DEPTH  = 16;
    
    // sync_fifo Inputs
    reg   clk                                  = 0 ;
    reg   rst_n                                = 0 ;
    reg   r_en                                 = 0 ;
    reg   w_en                                 = 0 ;
    reg   [7:0]  din                           = 0 ;
    
    // sync_fifo Outputs
    wire  empty                                ;
    wire  full                                 ;
    wire  [7:0]  dout                          ;
    
    
    initial
    begin
        forever #(PERIOD/2)  clk=~clk;
    end
    
    initial
    begin
        #(PERIOD*2) rst_n  =  1;
    end
    
    sync_fifo #(
        .WIDTH ( WIDTH ),
        .DEPTH ( DEPTH ))
     u_sync_fifo (
        .clk                     ( clk          ),
        .rst_n                   ( rst_n        ),
        .r_en                    ( r_en         ),
        .w_en                    ( w_en         ),
        .din                     ( din          ),
    
        .empty                   ( empty        ),
        .full                    ( full         ),
        .dout                    ( dout         )
    );
    
    initial
    begin
     clk = 1'b1;
        rst_n = 1'b0;
        din = 8'd0;
        r_en = 1'b0;
        w_en = 1'b0;
        repeat(16)begin
           @(posedge clk)begin
            w_en = 1'b1;
            r_en = 1'b0;
            din = {$random % 255};
           end
        end
        repeat(16)begin
            @(posedge clk)begin
            r_en = 1'b1;
            w_en = 1'b0;
            end
        end
        repeat(11)begin
           @(posedge clk)begin
            w_en = 1'b1;
            r_en = 1'b0;
            din = {$random % 255};
           end
        end
        repeat(8)begin
            @(posedge clk)begin
            r_en = 1'b1;
            w_en = 1'b0;
            end
        end
        forever begin
            @(posedge clk)begin
            r_en = 1'b1;
            w_en = 1'b1;    
            din = {$random % 64};
            end
        end
        $stop;
    end
    
    endmodule
    

    在代码编写过程中,博主想利用计数器来控制full和empty信号的拉高和拉低,结果却出现了有一位写入的数据读不出来的情况,思考了很久还是没有发现问题出在哪里,如果有懂得大佬可以在评论区地点一下,最后又是借鉴了刀哥的代码才实现功能,不得不说刀哥YYDS.本文仅用于记录学习,如有侵权,请联系博主

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值