異步fifo的設計(FPGA)

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

1、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.這篇文章的,
百度搜搜很容易找到,雖然是英文的可是寫的確實值得研究。
下面我會對設計的要點進行分析,也是對本身學習過程的一個總結,但願能和你們交流共同進步。

2、設計要點解析
一、讀空信號如何產生?寫滿信號如何產生?
讀空信號:復位的時候,讀指針和寫指針相等,讀空信號有效(這裏所說的指針其實就是讀地址、寫地址)
              當讀指針遇上寫指針的時候,寫指針等於讀指針意味着最後一個數據被讀完,此時讀空信號有效
寫滿信號:當寫指針比讀指針多一圈時,寫指針等於讀指針意味着寫滿了,此時寫滿信號有效
咱們會發現 讀空的條件是寫指針等於讀指針,寫滿的條件也是寫指針等於讀指針,到底如何區分呢?
解決方法將指針的位寬多定義一位
舉個例子說明:假設要設計深度爲 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
 
二、因爲是異步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功能的正確性。在同步過程當中的亞穩態不可能消除,可是咱們只要保證它不會影響咱們的正常工做便可。
 
三、因爲設計的時候讀寫指針用了至少兩級寄存器同步,同步會消耗至少兩個時鐘週期,勢必會使得判斷空或滿有所延遲,這會不會致使設計出錯呢?
異步FIFO經過比較讀寫指針進行滿空判斷,可是讀寫指針屬於不一樣的時鐘域,因此在比較以前須要先將讀寫指針進行同步處理,
將寫指針同步到讀時鐘域再和讀指針比較進行FIFO空狀態判斷,由於在同步寫指針時須要時間,而在這個同步的時間內有可能還會寫入新的數據,所以同步後的寫指針必定是小於或者等於當前實際的寫指針,因此此時判斷FIFO爲空不必定是真空,這樣更加保守,一共不會出現空讀的狀況,雖然會影響FIFO的性能,可是並不會出錯,同理將讀指針同步到寫時鐘域再和寫指針比較進行FIFO滿狀態判斷,同步後的讀指針必定是小於或者等於當前的讀指針,因此此時判斷FIFO爲滿不必定是真滿,這樣更保守,這樣能夠保證FIFO的特性:FIFO空以後不能繼續讀取,FIFO滿以後不能繼續寫入。 總結來講異步邏輯轉到同步邏輯不可避免須要額外的時鐘開銷,這會致使滿空趨於保守,可是保守並不等於錯誤,這麼寫會稍微有性能損失,可是不會出錯。
舉個例子:大多數情形下,異步FIFO兩端的時鐘不是同頻的,或者讀快寫慢,或者讀慢寫快, 慢的時鐘域同步到快的時鐘域不會出現漏掉指針的狀況,可是將指針從快的時鐘域同步到慢的時鐘域時可能會有指針遺漏舉個例子以讀慢寫快爲例,進行滿標誌判斷的時候須要將讀指針同步到寫時鐘域,由於讀慢寫快,因此不會有讀指針遺漏,同步消耗時鐘週期,因此同步後的讀指針滯後(小於等於)當前讀地址,因此可能滿標誌會提早產生,滿並不是真滿。進行空標誌判斷的時候須要將寫指針同步到讀指針 ,由於讀慢寫快,因此當讀時鐘同步寫指針 的時候,必然會漏掉一部分寫指針,咱們不用關心那到底會漏掉哪些寫指針, 咱們在意的是漏掉的指針會對FIFO的空標誌產生影響嗎?好比寫指針從0寫到10,期間讀時鐘域只同步捕捉到了三、五、8這三個寫指針而漏掉了其餘指針。當同步到8這個寫指針時,真實的寫指針可能已經寫到10 ,至關於在讀時鐘域還沒來得及覺察的狀況下,寫時鐘域可能偷偷寫了數據到FIFO去,這樣在判斷它是否是空的時候會出現不是真正空的狀況,漏掉的指針也沒有對FIFO的邏輯操做產生影響。
 
四、多位二進制碼如何轉化爲格雷碼
二進制碼轉換成二進制格雷碼,其法則是保留二進制碼的最高位做爲格雷碼的最高位,而次高位格雷碼爲二進制碼的高位與次高位相異或,而格雷碼其他各位與次高位的求法相相似。 
  轉換示意圖
 
我再換種更簡單的描述 
二進制數                                       1 0 1 1 0
二進制數右移1位,空位補0               1 0 1 1
異或運算                                       1 1 1 0 1  
這樣就能夠實現二進制到格雷碼的轉換了,總結就是移位而且異或,verilog代碼實現就一句:assign wgraynext = (wbinnext>>1) ^ wbinnext;
是否是很是簡單。
 

3、代碼解析
異步FIFO的信號接口:
wclk  wrst_n  winc wdata //寫時鐘、寫復位、寫請求、寫數據     這幾個與寫有關的所有與wclk同步
rclk  rrst_n    rinc   rdata //讀時鐘、讀 復位、讀 請求、讀 數據    這幾個與讀有關的所有與rclk同步
wfull                              //寫滿  與wclk同步
rempty                          // 讀空 與rclk同步
本次代碼共分爲6個module
一、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
View Code

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

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<<ADDRSIZE;   // 左移至關於乘法,2^4
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
View Code

三、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
View Code

四、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
View Code

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

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
View Code

六、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
View Code

七、測試文件ide

`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
View Code

 

八、仿真結果性能

因爲截圖篇幅的限制請本身驗證仿真。學習

原文連接:http://www.cnblogs.com/aslmer/p/6114216.html 測試

相關文章
相關標籤/搜索