新聞中心

        EEPW首頁 > 電源與新能源 > 設計應用 > 異步fifo的設計(FPGA)

        異步fifo的設計(FPGA)

        作者: 時間:2018-07-24 來源:網絡 收藏

        本文首先對異步 FIFO 設計的重點難點進行分析,最后給出詳細代碼。

        本文引用地址:http://www.104case.com/article/201807/383738.htm

        一、FIFO簡單講解

        FIFO的本質是RAM, 先進先出

        重要參數:fifo深度(簡單來說就是需要存多少個數據)

        fifo位寬(每個數據的位寬)

        FIFO有同步和異步兩種,同步即讀寫時鐘相同,異步即讀寫時鐘不相同

        同步FIFO用的少,可以作為數據緩存

        異步FIFO可以解決跨時鐘域的問題,在應用時需根據實際情況考慮好fifo深度即可

        本次要設計一個異步FIFO,深度為8,位寬也是8.

        代碼是學習Simulation and Synthesis Techniques for Asynchronous FIFO Design Clifford E. Cummings, Sunburst Design, Inc.這篇文章的,百度搜搜很容易找到,雖然是英文的但是寫的確實值得研究。

        下面我會對設計的要點進行分析,也是對自己學習過程的一個總結,希望能和大家交流共同進步。

        二、設計要點解析

        1、讀空信號如何產生?寫滿信號如何產生?

        讀空信號:復位的時候,讀指針和寫指針相等,讀空信號有效(這里所說的指針其實就是讀地址、寫地址)

        當讀指針趕上寫指針的時候,寫指針等于讀指針意味著最后一個數據被讀完,此時讀空信號有效

        寫滿信號:當寫指針比讀指針多一圈時,寫指針等于讀指針意味著寫滿了,此時寫滿信號有效

        我們會發現 讀空的條件是寫指針等于讀指針,寫滿的條件也是寫指針等于讀指針,到底如何區分呢?

        解決方法:將指針的位寬多定義一位

        舉個例子說明:假設要設計深度為 8 的異步FIFO,此時定義讀寫指針只需要 3 位(2^3=8)就夠用了,

        但是我們在設計時將指針的位寬設計成 4 位,最高位的作用就是區分是讀空還是寫滿,具體理論 1 如下

        當最高位相同,其余位相同認為是讀空

        當最高位不同,其余位相同認為是寫滿

        注意:理論1試用的是二進制數之間的空滿比較判斷。

        但是這篇文章中確不是這樣比較的,而是用的理論2,這里我解釋一下

        由于文章在設計中判斷是讀指針是否等于寫指針的時候,用的是讀寫指針的格雷碼形式(為什么用格雷碼后面解釋),此時若用上面的理論1就會出問題,因為格雷碼是鏡像對稱的,若只根據最高位是否相同來區分是讀空還是寫滿是有問題的,詳情我會慢慢說,請看圖 1

        綠色框起來的是0--15的格雷碼,用紅線將格雷碼分為上下兩部分

        通過觀察格雷碼相鄰位每次只有1位發生變化,且上下兩部分,除了最高位相反,其余位全都關于紅線鏡像對稱,

        7 --> 8 ,格雷碼從 0100 --> 1100 ,只有最高位發生變化其余位相同

        6 --> 9 , 格雷碼從 0101 --> 1101 , 只有最高位發生變化其余位相同

        以此類推,為什么要說鏡像對稱呢?

        試想如果讀指針指向 8,寫指針指向 7 ,我們可以知道此時此刻并不是讀空狀態也不是寫滿狀態

        但是如果在此刻套用理論 1 來判斷,看會出現什么情況,我們來套一下

        7的格雷碼與8的格雷碼的最高位不同,其余位相同,所以判斷出為寫滿。這就出現誤判了,同樣套用在 6 和 9,5 和 10等也會出現誤判。

        因此用格雷碼判斷是否為讀空或寫滿時應使用理論 2,看最高位和次高位是否相等,具體如下:

        當最高位和次高位相同,其余位相同認為是讀空

        當最高位和次高位不同,其余位相同認為是寫滿

        補:理論2這個判斷方法適用于用格雷碼判斷比較空滿

        在實際設計中如果不想用格雷碼比較,就可以利用格雷碼將讀寫地址同步到一個時鐘域后再將格雷碼再次轉化成二進制數再用理論1進行比較就好了。。

        圖 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滿之后不能繼續寫入??偨Y來說異步邏輯轉到同步邏輯不可避免需要額外的時鐘開銷,這會導致滿空趨于保守,但是保守并不等于錯誤,這么寫會稍微有性能損失,但是不會出錯。

        舉個例子:大多數情形下,異步FIFO兩端的時鐘不是同頻的,或者讀快寫慢,或者讀慢寫快,慢的時鐘域同步到快的時鐘域不會出現漏掉指針的情況,但是將指針從快的時鐘域同步到慢的時鐘域時可能會有指針遺漏,舉個例子以讀慢寫快為例,進行滿標志判斷的時候需要將讀指針同步到寫時鐘域,因為讀慢寫快,所以不會有讀指針遺漏,同步消耗時鐘周期,所以同步后的讀指針滯后(小于等于)當前讀地址,所以可能滿標志會提前產生,滿并非真滿。進行空標志判斷的時候需要將寫指針同步到讀指針 ,因為讀慢寫快,所以當讀時鐘同步寫指針 的時候,必然會漏掉一部分寫指針,我們不用關心那到底會漏掉哪些寫指針,我們在乎的是漏掉的指針會對FIFO的空標志產生影響嗎?比如寫指針從0寫到10,期間讀時鐘域只同步捕捉到了3、5、8這三個寫指針而漏掉了其他指針。當同步到8這個寫指針時,真實的寫指針可能已經寫到10 ,相當于在讀時鐘域還沒來得及覺察的情況下,寫時鐘域可能偷偷寫了數據到FIFO去,這樣在判斷它是不是空的時候會出現不是真正空的情況,漏掉的指針也沒有對FIFO的邏輯操作產生影響。

        4、多位二進制碼如何轉化為格雷碼

        二進制碼轉換成二進制格雷碼,其法則是保留二進制碼的最高位作為格雷碼的最高位,而次高位格雷碼為二進制碼的高位與次高位相異或,而格雷碼其余各位與次高位的求法相類似。

        我再換種更簡單的描述

        二進制數 1 0 1 1 0

        二進制數右移1位,空位補0 0 1 0 1 1

        異或運算 1 1 1 0 1

        這樣就可以實現二進制到格雷碼的轉換了,總結就是移位并且異或,verilog代碼實現就一句:assign wgraynext = (wbinnext>>1) ^ wbinnext;

        是不是非常簡單。

        三、代碼解析

        異步FIFO的信號接口:

        wclk wrst_n winc wdata //寫時鐘、寫復位、寫請求、寫數據 這幾個與寫有關的全部與wclk同步

        rclk rrst_n rinc rdata //讀時鐘、讀 復位、讀 請求、讀 數據 這幾個與讀有關的全部與rclk同步

        wfull //寫滿 與wclk同步

        rempty // 讀空 與rclk同步

        本次代碼共分為6個module

        1、fifo.v 是頂層模塊,作用是將各個小模塊例化聯系起來

        module fifo

        #(

        parameter DSIZE = 8,

        parameter ASIZE = 4

        )

        (

        output [DSIZE-1:0] rdata,

        output wfull,

        output rempty,

        input [DSIZE-1:0] wdata,

        input winc, wclk, wrst_n,

        input rinc, rclk, rrst_n

        );

        wire [ASIZE-1:0] waddr, raddr;

        wire [ASIZE:0] wptr, rptr, wq2_rptr, rq2_wptr;

        // synchronize the read pointer into the write-clock domain

        sync_r2w sync_r2w

        (

        .wq2_rptr (wq2_rptr),

        .rptr (rptr ),

        .wclk (wclk ),

        .wrst_n (wrst_n )

        );

        // synchronize the write pointer into the read-clock domain

        sync_w2r sync_w2r

        (

        .rq2_wptr(rq2_wptr),

        .wptr(wptr),

        .rclk(rclk),

        .rrst_n(rrst_n)

        );

        //this is the FIFO memory buffer that is accessed by both the write and read clock domains.

        //This buffer is most likely an instantiated, synchronous dual-port RAM.

        //Other memory styles can be adapted to function as the FIFO buffer.

        fifomem

        #(DSIZE, ASIZE)

        fifomem

        (

        .rdata(rdata),

        .wdata(wdata),

        .waddr(waddr),

        .raddr(raddr),

        .wclken(winc),

        .wfull(wfull),

        .wclk(wclk)

        );

        //this module is completely synchronous to the read-clock domain and contains the FIFO read pointer and empty-flag logic.

        rptr_empty

        #(ASIZE)

        rptr_empty

        (

        .rempty(rempty),

        .raddr(raddr),

        .rptr(rptr),

        .rq2_wptr(rq2_wptr),

        .rinc(rinc),

        .rclk(rclk),

        .rrst_n(rrst_n)

        );

        //this module is completely synchronous to the write-clock domain and contains the FIFO write pointer and full-flag logic

        wptr_full

        #(ASIZE)

        wptr_full

        (

        .wfull(wfull),

        .waddr(waddr),

        .wptr(wptr),

        .wq2_rptr(wq2_rptr),

        .winc(winc),

        .wclk(wclk),

        .wrst_n(wrst_n)

        );

        endmodule

        2、fifomem.v 生成存儲實體,FIFO 的本質是RAM,因此在設計存儲實體的時候有兩種方法:用數組存儲數據或者調用RAM的IP核

        module fifomem

        #(

        parameter DATASIZE = 8, // Memory data word width

        parameter ADDRSIZE = 4 // 深度為8即地址為3位即可,這里多定義一位的原因是用來判斷是空還是滿,詳細在后文講到

        ) // Number of mem address bits

        (

        output [DATASIZE-1:0] rdata,

        input [DATASIZE-1:0] wdata,

        input [ADDRSIZE-1:0] waddr, raddr,

        input wclken, wfull, wclk

        );

        `ifdef RAM //可以調用一個RAM IP核

        // instantiation of a vendor's dual-port RAM

        my_ram mem

        (

        .dout(rdata),

        .din(wdata),

        .waddr(waddr),

        .raddr(raddr),

        .wclken(wclken),

        .wclken_n(wfull),

        .clk(wclk)

        );

        `else //用數組生成存儲體

        // RTL Verilog memory model

        localparam DEPTH = 1 reg [DATASIZE-1:0] mem [0:DEPTH-1]; //生成2^4個位寬位8的數組

        assign rdata = mem[raddr];

        always @(posedge wclk) //當寫使能有效且還未寫滿的時候將數據寫入存儲實體中,注意這里是與wclk同步的

        if (wclken !wfull)

        mem[waddr] = wdata;

        `endif

        endmodule

        3、sync_r2w.v 將 rclk 時鐘域的格雷碼形式的讀指針同步到 wclk 時鐘域,簡單來講就是用兩級寄存器同步,即打兩拍

        module sync_r2w

        #(

        parameter ADDRSIZE = 4

        )

        (

        output reg [ADDRSIZE:0] wq2_rptr, //讀指針同步到寫時鐘域

        input [ADDRSIZE:0] rptr, // 格雷碼形式的讀指針,格雷碼的好處后面會細說

        input wclk, wrst_n

        );

        reg [ADDRSIZE:0] wq1_rptr;

        always @(posedge wclk or negedge wrst_n)

        if (!wrst_n) begin

        wq1_rptr = 0;

        wq2_rptr = 0;

        end

        else begin

        wq1_rptr= rptr;

        wq2_rptr=wq1_rptr;

        end

        endmodule

        4、sync_w2r.v 將 wclk 時鐘域的格雷碼形式的寫指針同步到 rclk 時鐘域

        module sync_w2r

        #(parameter ADDRSIZE = 4)

        (

        output reg [ADDRSIZE:0] rq2_wptr, //寫指針同步到讀時鐘域

        input [ADDRSIZE:0] wptr, //格雷碼形式的寫指針

        input rclk, rrst_n

        );

        reg [ADDRSIZE:0] rq1_wptr;

        always @(posedge rclk or negedge rrst_n)

        if (!rrst_n)begin

        rq1_wptr = 0;

        rq2_wptr = 0;

        end

        else begin

        rq1_wpt = wptr;

        rq2_wptr = rq1_wptr;

        end

        endmodule

        5、rptr_empty.v 將 sync_w2r.v 同步后的寫指針與 rclk 時鐘域的讀指針進行比較生成都空信號

        module rptr_empty

        #(

        parameter ADDRSIZE = 4

        )

        (

        output reg rempty,

        output [ADDRSIZE-1:0] raddr, //二進制形式的讀指針

        output reg [ADDRSIZE :0] rptr, //格雷碼形式的讀指針

        input [ADDRSIZE :0] rq2_wptr, //同步后的寫指針

        input rinc, rclk, rrst_n

        );

        reg [ADDRSIZE:0] rbin;

        wire [ADDRSIZE:0] rgraynext, rbinnext;

        // GRAYSTYLE2 pointer

        //將二進制的讀指針與格雷碼進制的讀指針同步

        always @(posedge rclk or negedge rrst_n)

        if (!rrst_n) begin

        rbin = 0;

        rptr = 0;

        end

        else begin

        rbin=rbinnext; //直接作為存儲實體的地址

        rptr=rgraynext;//輸出到 sync_r2w.v模塊,被同步到 wrclk 時鐘域

        end

        // Memory read-address pointer (okay to use binary to address memory)

        assign raddr = rbin[ADDRSIZE-1:0]; //直接作為存儲實體的地址,比如連接到RAM存儲實體的讀地址端。

        assign rbinnext = rbin + (rinc ~rempty); //不空且有讀請求的時候讀指針加1

        assign rgraynext = (rbinnext>>1) ^ rbinnext; //將二進制的讀指針轉為格雷碼

        // FIFO empty when the next rptr == synchronized wptr or on reset

        assign rempty_val = (rgraynext == rq2_wptr); //當讀指針等于同步后的寫指針,則為空。

        always @(posedge rclk or negedge rrst_n)

        if (!rrst_n)

        rempty = 1'b1;

        else

        rempty = rempty_val;

        endmodule

        6、wptr_full.v 將 sync_r2w.v 同步后的讀指針與wclk 時鐘域的寫指針進行比較生成寫滿信號

        module wptr_full

        #(

        parameter ADDRSIZE = 4

        )

        (

        output reg wfull,

        output [ADDRSIZE-1:0] waddr,

        output reg [ADDRSIZE :0] wptr,

        input [ADDRSIZE :0] wq2_rptr,

        input winc, wclk, wrst_n

        );

        reg [ADDRSIZE:0] wbin;

        wire [ADDRSIZE:0] wgraynext, wbinnext;

        // GRAYSTYLE2 pointer

        always @(posedge wclk or negedge wrst_n)

        if (!wrst_n)

        {wbin, wptr} = 0;

        else

        {wbin, wptr} = {wbinnext, wgraynext};

        // Memory write-address pointer (okay to use binary to address memory)

        assign waddr = wbin[ADDRSIZE-1:0];

        assign wbinnext = wbin + (winc ~wfull);

        assign wgraynext = (wbinnext>>1) ^ wbinnext; //二進制轉為格雷碼

        //-----------------------------------------------------------------

        assign wfull_val = (wgraynext=={~wq2_rptr[ADDRSIZE:ADDRSIZE-1],wq2_rptr[ADDRSIZE-2:0]}); //當最高位和次高位不同其余位相同時則寫指針超前于讀指針一圈,即寫滿。后面會詳細解釋。

        always @(posedge wclk or negedge wrst_n)

        if (!wrst_n)

        wfull = 1'b0;

        else

        wfull = wfull_val;

        endmodule

        7、測試文件

        `timescale 1ns /1ns

        module test();

        reg [7:0] wdata;

        reg winc, wclk, wrst_n;

        reg rinc, rclk, rrst_n;

        wire [7:0] rdata;

        wire wfull;

        wire rempty;

        fifo

        u_fifo (

        .rdata(rdata),

        .wfull(wfull),

        .rempty(rempty),

        .wdata (wdata),

        .winc (winc),

        .wclk (wclk),

        .wrst_n(wrst_n),

        .rinc(rinc),

        .rclk(rclk),

        .rrst_n(rrst_n)

        );

        localparam CYCLE = 20;

        localparam CYCLE1 = 40;

        //時鐘周期,單位為ns,可在此修改時鐘周期。

        //生成本地時鐘50M

        initial begin

        wclk = 0;

        forever

        #(CYCLE/2)

        wclk=~wclk;

        end

        initial begin

        rclk = 0;

        forever

        #(CYCLE1/2)

        rclk=~rclk;

        end

        //產生復位信號

        initial begin

        wrst_n = 1;

        #2;

        wrst_n = 0;

        #(CYCLE*3);

        wrst_n = 1;

        end

        initial begin

        rrst_n = 1;

        #2;

        rrst_n = 0;

        #(CYCLE*3);

        rrst_n = 1;

        end

        always @(posedge wclk or negedge wrst_n)begin

        if(wrst_n==1'b0)begin

        winc = 0;

        rinc = 0;

        end

        else begin

        winc = $random;

        rinc = $random;

        end

        end

        always @(posedge rclk or negedge rrst_n)begin

        if(rrst_n==1'b0)begin

        rinc = 0;

        end

        else begin

        rinc = $random;

        end

        end

        always@(*)begin

        if(winc == 1)

        wdata= $random ;

        else

        wdata = 0;

        end

        endmodule

        8、仿真結果

        由于截圖篇幅的限制請自己驗證仿真。



        關鍵詞:

        評論


        相關推薦

        技術專區

        關閉
        主站蜘蛛池模板: 新泰市| 忻城县| 武平县| 堆龙德庆县| 祁连县| 紫金县| 太康县| 兴和县| 贡山| 长武县| 荥阳市| 潼南县| 嘉义县| 澳门| 晋中市| 托里县| 淮阳县| 曲水县| 南充市| 新兴县| 淮滨县| 黎城县| 石泉县| 湖北省| 胶南市| 万全县| 织金县| 敦化市| 花莲县| 开平市| 木兰县| 大新县| 育儿| 荔波县| 达尔| 卓资县| 平顺县| 平乡县| 龙南县| 万宁市| 浦北县|