基於FPGA的異步FIFO設計


今天要介紹的異步FIFO,可以有不同的讀寫時鍾,即不同的時鍾域。由於異步FIFO沒有外部地址端口,因此內部采用讀寫指針並順序讀寫,即先寫進FIFO的數據先讀取(簡稱先進先出)。這里的讀寫指針是異步的,處理不同的時鍾域,而異步FIFO的空滿標志位是根據讀寫指針的情況得到的。為了得到正確的空滿標志位,需要對讀寫指針進行同步。一般情況下,如果一個時鍾域的信號直接給另一個時鍾域采集,可能會產生亞穩態,亞穩態的產生對設計而言是致命的。為了減少不同時鍾域間的亞穩態問題,我們先對它進行兩拍寄存同步,如圖1所示。當然,對異步信號的寄存越多,產生亞穩態的概率就越小,但延時越多。不過一般情況下,寄存兩拍就夠了。為了繼續減少亞穩態產生的概率,在對異步信號同步之前,將其轉換為格雷碼,使其每個狀態只有一個位在變化。例如,假設N位二進制變量產生的亞穩態概率為a,那么二進制轉換成格雷碼后其產生的亞穩態概率則為a/N。

對異步信號進行同步.jpg

圖1  對異步信號用兩級寄存器同步

    根據上述原理,設計了異步FIFO的架構,如圖2所示。

異步FIFO設計框架.jpg

圖2  異步FIFO設計架構

    根據異步FIFO的設計架構,歸納以下設計步驟:

    寫時鍾域:

    (1)根據寫使能wr_en和寫滿標志位wr_full產生二進制寫指針

    (2)根據二進制寫指針產生雙端口RAM的寫地址

    (3)由二進制寫指針轉換成格雷碼寫指針

    (4)對格雷碼讀指針在寫時鍾域中進行兩級同步得同步后格雷碼讀指針

    (5)同步后格雷碼讀指針轉化成同步后二進制讀指針

    (6)步驟(3)與步驟(4)比較得寫滿標志位wr_full

    (7)步驟(1)與步驟(5)相減得指示寫FIFO的數據量

    讀時鍾域:

    (8)根據讀使能rd_en和讀空標志位rd_empty產生二進制讀指針

    (9)根據二進制讀指針產生雙端口RAM的讀地址

    (10)由二進制讀指針轉換成格雷碼讀指針

    (11)對格雷碼寫指針在讀時鍾域中進行兩級同步得同步后格雷碼寫指針

    (12)同步后格雷碼寫指針轉化成同步后二進制寫指針

    (13)步驟(10)與步驟(11)比較得讀空標志位rd_empty

    (14)步驟(8)與步驟(12)相減得指示讀FIFO的數據量

    Verilog HDL設計電路,如下所示:

/*******************************版權申明********************************
**                     電子技術應用網站, CrazyBird
**     http://www.chinaaet.com, http://blog.chinaaet.com/crazybird
**
**------------------------------文件信息--------------------------------
** 文件名:          asyn_fifo.v
** 創建者:          CrazyBird
** 創建日期:        2016-1-16
** 版本號:           v1.0
** 功能描述:        異步FIFO,用於處理不同的時鍾域
**                   
***********************************************************************/
// synopsys translate_off
`timescale 1 ns / 1 ps
// synopsys translate_on
module asyn_fifo(
    wr_rst_n,
    wr_clk,
    wr_en,
    wr_data,
    wr_full,
    wr_cnt,
    rd_rst_n,
    rd_clk,
    rd_en,
    rd_data,
    rd_empty,
    rd_cnt
    );
    //******************************************************************
    //  參數定義
    //******************************************************************
    parameter   C_DATA_WIDTH = 8;
    parameter   C_FIFO_DEPTH_WIDTH = 4;
    
    //******************************************************************
    //  端口定義
    //******************************************************************
    input                                   wr_rst_n;
    input                                   wr_clk;
    input                                   wr_en;
    input       [C_DATA_WIDTH-1:0]          wr_data;
    output reg                              wr_full;
    output reg  [C_FIFO_DEPTH_WIDTH:0]      wr_cnt;
    input                                   rd_rst_n;
    input                                   rd_clk;
    input                                   rd_en;
    output      [C_DATA_WIDTH-1:0]          rd_data;
    output reg                              rd_empty;
    output reg  [C_FIFO_DEPTH_WIDTH:0]      rd_cnt;
    
    //******************************************************************
    //  內部變量定義
    //******************************************************************
    reg     [C_DATA_WIDTH-1:0]      mem     [0:(1 << C_FIFO_DEPTH_WIDTH)-1];
    wire    [C_FIFO_DEPTH_WIDTH-1:0]        wr_addr;
    wire    [C_FIFO_DEPTH_WIDTH-1:0]        rd_addr;
    wire    [C_FIFO_DEPTH_WIDTH:0]          next_wr_bin_ptr;
    wire    [C_FIFO_DEPTH_WIDTH:0]          next_rd_bin_ptr;
    reg     [C_FIFO_DEPTH_WIDTH:0]          wr_bin_ptr;
    reg     [C_FIFO_DEPTH_WIDTH:0]          rd_bin_ptr;
    wire    [C_FIFO_DEPTH_WIDTH:0]          next_wr_gray_ptr;
    wire    [C_FIFO_DEPTH_WIDTH:0]          next_rd_gray_ptr;
    wire    [C_FIFO_DEPTH_WIDTH:0]          syn_wr_bin_ptr_rd_clk;
    wire    [C_FIFO_DEPTH_WIDTH:0]          syn_rd_bin_ptr_wr_clk;
    wire    [C_FIFO_DEPTH_WIDTH:0]          syn_wr_gray_ptr_rd_clk;
    wire    [C_FIFO_DEPTH_WIDTH:0]          syn_rd_gray_ptr_wr_clk;
    wire    [C_FIFO_DEPTH_WIDTH:0]          wr_cnt_w;
    wire    [C_FIFO_DEPTH_WIDTH:0]          rd_cnt_w;
    wire                                    wr_full_w;
    wire                                    rd_empty_w;
    
    //******************************************************************
    //  雙端口RAM的讀寫
    //******************************************************************
    //  寫RAM
    always @(posedge wr_clk)
    begin
        if((wr_en & ~wr_full) == 1'b1)
            mem[wr_addr] <= wr_data;
    end
    //  讀RAM
    assign rd_data = mem[rd_addr];
    
    //******************************************************************
    //  二進制寫指針的產生
    //******************************************************************
    assign next_wr_bin_ptr = wr_bin_ptr + (wr_en & ~wr_full);
    
    always @(posedge wr_clk or negedge wr_rst_n)
    begin
        if(wr_rst_n == 1'b0)
            wr_bin_ptr <= {(C_FIFO_DEPTH_WIDTH+1){1'b0}};
        else
            wr_bin_ptr <= next_wr_bin_ptr;
    end
    
    //******************************************************************
    //  RAM寫地址的產生
    //******************************************************************
    assign wr_addr = wr_bin_ptr[C_FIFO_DEPTH_WIDTH-1:0];
    
    //******************************************************************
    //  二進制寫指針轉換成格雷碼寫指針
    //******************************************************************
    bin2gray #(
        .C_DATA_WIDTH(C_FIFO_DEPTH_WIDTH+1)
    )
    u_bin2gray_wr (
        .bin    (   next_wr_bin_ptr     ),
        .gray   (   next_wr_gray_ptr    )
    );
    
    //******************************************************************
    //  對格雷碼讀指針在寫時鍾域中進行兩級同步
    //******************************************************************
    double_syn_ff #(
        .C_DATA_WIDTH(C_FIFO_DEPTH_WIDTH+1)
    )
    u_double_syn_ff_wr (
        .rst_n  (   wr_rst_n                ),
        .clk    (   wr_clk                  ),
        .din    (   next_rd_gray_ptr         ),
        .dout   (   syn_rd_gray_ptr_wr_clk   )
    );
    
    //******************************************************************
    //  同步后的格雷碼讀指針轉換成同步后的二進制讀指針
    //******************************************************************
    gray2bin #(
        .C_DATA_WIDTH(C_FIFO_DEPTH_WIDTH+1)
    )
    u_gray2bin_wr (
        .gray   (   syn_rd_gray_ptr_wr_clk  ),
        .bin    (   syn_rd_bin_ptr_wr_clk   )
    );
    
    //******************************************************************
    //  FIFO寫滿標志位的產生和寫FIFO數據量的計數
    //******************************************************************
    assign wr_full_w = (next_wr_gray_ptr == ({~syn_rd_gray_ptr_wr_clk[C_FIFO_DEPTH_WIDTH:C_FIFO_DEPTH_WIDTH-1],
                        syn_rd_gray_ptr_wr_clk[C_FIFO_DEPTH_WIDTH-2:0]}));
    assign wr_cnt_w  = next_wr_bin_ptr - syn_rd_bin_ptr_wr_clk;
    
    always @(posedge wr_clk or negedge wr_rst_n)
    begin
        if(wr_rst_n == 1'b0)
        begin
            wr_full <= 1'b0;
            wr_cnt  <= {(C_FIFO_DEPTH_WIDTH+1){1'b0}};
        end
        else
        begin
            wr_full <= wr_full_w;
            wr_cnt  <= wr_cnt_w;
        end
    end
    
    //******************************************************************
    //  二進制讀指針的產生
    //******************************************************************
    assign next_rd_bin_ptr = rd_bin_ptr + (rd_en & ~rd_empty);
    
    always @(posedge rd_clk or negedge rd_rst_n)
    begin
        if(rd_rst_n == 1'b0)
            rd_bin_ptr <= {(C_FIFO_DEPTH_WIDTH+1){1'b0}};
        else
            rd_bin_ptr <= next_rd_bin_ptr;
    end
    
    //******************************************************************
    //  RAM讀地址的產生
    //******************************************************************
    assign rd_addr = rd_bin_ptr[C_FIFO_DEPTH_WIDTH-1:0];
    
    //******************************************************************
    //  二進制讀指針轉換成格雷碼讀指針
    //******************************************************************
    bin2gray #(
        .C_DATA_WIDTH(C_FIFO_DEPTH_WIDTH+1)
    )
    u_bin2gray_rd (
        .bin    (   next_rd_bin_ptr     ),
        .gray   (   next_rd_gray_ptr    )
    );
    
    //******************************************************************
    //  對格雷碼寫指針在讀時鍾域中進行兩級同步
    //******************************************************************
    double_syn_ff #(
        .C_DATA_WIDTH(C_FIFO_DEPTH_WIDTH+1)
    )
    u_double_syn_ff_rd (
        .rst_n  (   rd_rst_n                ),
        .clk    (   rd_clk                  ),
        .din    (   next_wr_gray_ptr        ),
        .dout   (   syn_wr_gray_ptr_rd_clk  )
    );
    
    //******************************************************************
    //  同步后的格雷碼寫指針轉換成同步后的二進制寫指針
    //******************************************************************
    gray2bin #(
        .C_DATA_WIDTH(C_FIFO_DEPTH_WIDTH+1)
    )
    u_gray2bin_rd (
        .gray   (   syn_wr_gray_ptr_rd_clk  ),
        .bin    (   syn_wr_bin_ptr_rd_clk   )
    );
    
    //******************************************************************
    //  FIFO讀空標志位的產生和讀FIFO數據量的計數
    //******************************************************************
    assign rd_empty_w = (next_rd_gray_ptr == syn_wr_gray_ptr_rd_clk);
    assign rd_cnt_w   = syn_wr_bin_ptr_rd_clk - next_rd_bin_ptr;
    
    always @(posedge rd_clk or negedge rd_rst_n)
    begin
        if(rd_rst_n == 1'b0)
        begin
            rd_empty <= 1'b0;
            rd_cnt   <= {(C_FIFO_DEPTH_WIDTH+1){1'b0}};
        end
        else
        begin
            rd_empty <= rd_empty_w;
            rd_cnt   <= rd_cnt_w;
        end
    end
    
endmodule

    其中,模塊gray2bin是格雷碼轉二進制碼,模塊bin2gray是二進制碼轉格雷碼,詳情見上一篇博客,地址:http://blog.chinaaet.com/crazybird/p/5100000866 。模塊double_syn_ff是兩級寄存器,用於同步信號,對應的Verilog HDL實現如下所示:

/*******************************版權申明********************************
**                     電子技術應用網站, CrazyBird
**     http://www.chinaaet.com, http://blog.chinaaet.com/crazybird
**
**------------------------------文件信息--------------------------------
** 文件名:          double_syn_ff.v
** 創建者:          CrazyBird
** 創建日期:        2016-1-16
** 版本號:           v1.0
** 功能描述:        對輸入信號進行兩級同步后輸出
**                   
***********************************************************************/
// synopsys translate_off
`timescale 1 ns / 1 ps
// synopsys translate_on
module double_syn_ff(
    rst_n,
    clk,
    din,
    dout
    );
    //******************************************************************
    //  參數定義
    //******************************************************************
    parameter   C_DATA_WIDTH = 8;
    
    //******************************************************************
    //  端口定義
    //******************************************************************
    input                               rst_n;
    input                               clk;
    input       [C_DATA_WIDTH-1:0]      din;
    output reg  [C_DATA_WIDTH-1:0]      dout;
    
    //******************************************************************
    //  內部變量定義
    //******************************************************************
    reg         [C_DATA_WIDTH-1:0]      data_r;
    
    //******************************************************************
    //  對輸入信號進行兩級同步后輸出
    //******************************************************************
    always @(posedge clk or negedge rst_n)
    begin
        if(rst_n == 1'b0)
            {dout,data_r} <= {(2*C_DATA_WIDTH){1'b0}};
        else
            {dout,data_r} <= {data_r,din};
    end
    
endmodule

   由於字數的限制,異步FIFO的功能驗證放在下一篇博文中吧!!!

轉載:http://blog.chinaaet.com/crazybird/p/5100000872


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM