異步FIFO的FPGA實現

本文大部分內容來自Clifford E. Cummings的《Simulation and Synthesis Techniques for Asynchronous FIFO Design》,同時加上一些自己的一些理解,有興趣的朋友可以閱讀原文。

一、FIFO簡介

  FIFO是英文First In First Out 的縮寫,是一種先進先出的數據緩存器,它與普通存儲器的區別是沒有外部讀寫地址線,這樣使用起來非常簡單,但缺點就是隻能順序寫入數據,順序的讀出數據,其數據地址由內部讀寫指針自動加1完成,不能像普通存儲器那樣可以由地址線決定讀取或寫入某個指定的地址。

用途1:

  異步FIFO讀寫分別採用相互異步的不同時鐘。在現代集成電路芯片中,隨着設計規模的不斷擴大,一個系統中往往含有數個時鐘,多時鐘域帶來的一個問題就是,如何設計異步時鐘之間的接口電路。異步FIFO是這個問題的一種簡便、快捷的解決方案,使用異步FIFO可以在兩個不同時鐘系統之間快速而方便地傳輸實時數據。

用途2:

  對於不同寬度的數據接口也可以用FIFO,例如單片機位8位數據輸出,而DSP可能是16位數據輸入,在單片機與DSP連接時就可以使用FIFO來達到數據匹配的目的。

二、分類

  同步FIFO是指讀時鐘和寫時鐘爲同一個時鐘,在時鐘沿來臨時同時發生讀寫操作;

  異步FIFO是指讀寫時鐘不一致,讀寫時鐘是互相獨立的。

三、FIFO的常見參數

  • FIFO的寬度:即FIFO一次讀寫操作的數據位;
  • FIFO的深度:指的是FIFO可以存儲多少個N位的數據(如果寬度爲N)。
  • 滿標誌:FIFO已滿或將要滿時由FIFO的狀態電路送出的一個信號,以阻止FIFO的寫操作繼續向FIFO中寫數據而造成溢出(overflow)。
  • 空標誌:FIFO已空或將要空時由FIFO的狀態電路送出的一個信號,以阻止FIFO的讀操作繼續從FIFO中讀出數據而造成無效數據的讀出(underflow)。
  • 讀時鐘:讀操作所遵循的時鐘,在每個時鐘沿來臨時讀數據。
  • 寫時鐘:寫操作所遵循的時鐘,在每個時鐘沿來臨時寫數據。

===============================分   隔    符 ==============================

  1. 讀寫指針的工作原理

  讀指針:總是指向下一個將要被寫入的單元,復位時,指向第1個單元(編號爲0)

  寫指針:總是指向當前要被讀出的數據,復位時,指向第1個單元(編號爲0)

 

  1. FIFO的“空”/“滿”檢測

  FIFO設計的關鍵:產生可靠的FIFO讀寫指針和生成FIFO“空”/“滿”狀態標誌。

  當讀寫指針相等時,表明FIFO爲空,這種情況發生在復位操作時,或者當讀指針讀出FIFO中最後一個字後,追趕上了寫指針時,如下圖所示:

          

 

  當讀寫指針再次相等時,表明FIFO爲滿,這種情況發生在,當寫指針轉了一圈,折回來(wrapped around)又追上了讀指針,如下圖:

        

 

    爲了區分到底是滿狀態還是空狀態,可以採用以下方法:

    方法1:在指針中添加一個額外的位(extra bit),當寫指針增加並越過最後一個FIFO地址時,就將寫指針這個未用的MSB1,其它位回零。對讀指針也進行同樣的操作。此時,對於深度爲2nFIFO,需要的讀/寫指針位寬爲(n+1)位,如對於深度爲8FIFO,需要採用4bit的計數器,0000100010011111MSB作爲折回標誌位,而低3位作爲地址指針。

    • 如果兩個指針的MSB不同,說明寫指針比讀指針多折回了一次;如r_addr=0000,w_addr = 1000,爲滿。
    • 如果兩個指針的MSB相同,則說明兩個指針折回的次數相等。其餘位相等,說明FIFO爲空;

3.二進制FIFO指針的考慮

  將一個二進制的計數值從一個時鐘域同步到另一個時鐘域的時候很容易出現問題,因爲採用二進制計數器時所有位都可能同時變化,在同一個時鐘沿同步多個信號的變化會產生亞穩態問題。而使用格雷碼只有一位變化,因此在兩個時鐘域間同步多個位不會產生問題。所以需要一個二進制到gray碼的轉換電路,將地址值轉換爲相應的gray碼,然後將該gray碼同步到另一個時鐘域進行對比,作爲空滿狀態的檢測。

    

 

4.

  • 使用gray碼進行對比,如何判斷“空”與“滿”

   使用gray碼解決了一個問題,但同時也帶來另一個問題,即在格雷碼域如何判斷空與滿。

  對於“空”的判斷依然依據二者完全相等(包括MSB)

  而對於“滿”的判斷,如下圖,由於gray碼除了MSB外,具有鏡像對稱的特點,當讀指針指向7,寫指針指向8時,除了MSB,其餘位皆相同,不能說它爲滿。因此不能單純的只檢測最高位了,在gray碼上判斷爲滿必須同時滿足以下3條:

  • wptr和同步過來的rptrMSB不相等,因爲wptr必須比rptr多折回一次。
  • wptrrptr的次高位不相等,如上圖位置7和位置15,轉化爲二進制對應的是01111111MSB不同說明多折回一次,111相同代表同一位置。
  • 剩下的其餘位完全相等。

      

5.總體實現

    系統的總體框圖如下:

        

1)頂層模塊 

複製代碼
  module AsyncFIFO
 #(parameter ASIZE = 4,    //地址位寬
   parameter DSIZE = 8)    //數據位寬
 (
    input  [DSIZE-1:0] wdata,
    input              winc, wclk, wrst_n,  //寫請求信號,寫時鐘,寫復位
    input              rinc, rclk, rrst_n,  //讀請求信號,讀時鐘,讀復位
    output [DSIZE-1:0] rdata,
    output             wfull,
    output             rempty
 );
wire [ASIZE-1:0] waddr, raddr;
wire [ASIZE:0]   wptr, rptr, wq2_rptr, rq2_wptr;        /************************************************************
* In order to perform FIFO full and FIFO empty tests using 
* this FIFO style, the read and write pointers must be
* passed to the opposite clock domain for pointer comparison
*************************************************************/
/*在檢測“滿”或“空”狀態之前,需要將指針同步到其它時鐘域時,使用格雷碼,可以降低同步過程中亞穩態出現的概率*/
sync_r2w I1_sync_r2w(
    .wq2_rptr(wq2_rptr), 
    .rptr(rptr),
    .wclk(wclk), 
    .wrst_n(wrst_n));
sync_w2r I2_sync_w2r (
    .rq2_wptr(rq2_wptr), 
    .wptr(wptr),
    .rclk(rclk), 
    .rrst_n(rrst_n));
/*
*  DualRAM 
*/
DualRAM #(DSIZE, ASIZE) I3_DualRAM(
    .rdata(rdata), 
    .wdata(wdata),
    .waddr(waddr), 
    .raddr(raddr),
    .wclken(winc), 
    .wclk(wclk));
    
/*
*  空、滿比較邏輯
*/
rptr_empty #(ASIZE) I4_rptr_empty(
    .rempty(rempty),
    .raddr(raddr),
    .rptr(rptr), 
    .rq2_wptr(rq2_wptr),
    .rinc(rinc), 
    .rclk(rclk),
    .rrst_n(rrst_n));
wptr_full #(ASIZE) I5_wptr_full(
    .wfull(wfull), 
    .waddr(waddr),
    .wptr(wptr), 
    .wq2_rptr(wq2_rptr),
    .winc(winc), 
    .wclk(wclk),
    .wrst_n(wrst_n));
endmodule
複製代碼

 2)DualRAM模塊

複製代碼
module DualRAM
#(
    parameter DATA_SIZE = 8,   // 數據位寬
    parameter ADDR_SIZE = 4   // 地址位寬
)
(
    input                       wclken,wclk,
    input      [ADDR_SIZE-1:0]  raddr,     //RAM read address
    input      [ADDR_SIZE-1:0]  waddr,     //RAM write address
    input      [DATA_SIZE-1:0]  wdata,    //data input
    output     [DATA_SIZE-1:0]  rdata      //data output
);    
localparam RAM_DEPTH = 1 << ADDR_SIZE;   //RAM深度 = 2^ADDR_WIDTH
        reg [DATA_SIZE-1:0] Mem[RAM_DEPTH-1:0];
        always@(posedge wclk)
begin
     if(wclken)
         Mem[waddr] <= wdata;
end
assign rdata =  Mem[raddr];
endmodule
複製代碼

3)同步模塊

複製代碼
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) 
        {wq2_rptr,wq1_rptr} <= 0;
    else 
        {wq2_rptr,wq1_rptr} <= {wq1_rptr,rptr};
endmodule
複製代碼

4)同步模塊2

複製代碼
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)
        {rq2_wptr,rq1_wptr} <= 0;
    else 
        {rq2_wptr,rq1_wptr} <= {rq1_wptr,wptr};
endmodule
複製代碼

5)空判斷邏輯

複製代碼
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;
wire  rempty_val;
//-------------------
// GRAYSTYLE2 pointer: gray碼讀地址指針
//-------------------
always @(posedge rclk or negedge rrst_n)
    if (!rrst_n) 
        begin 
            rbin <= 0;
            rptr <= 0;
        end
    else
        begin
            rbin <= rbinnext ; 
            rptr <= rgraynext;
        end
// gray碼計數邏輯
assign rbinnext = !rempty ? (rbin + rinc) : rbin;
assign rgraynext = (rbinnext>>1) ^ rbinnext;      //二進制到gray碼的轉換
        assign raddr = rbin[ADDRSIZE-1:0];
//---------------------------------------------------------------
// FIFO empty when the next rptr == synchronized wptr or on reset
//---------------------------------------------------------------
/*
*   讀指針是一個n位的gray碼計數器,比FIFO尋址所需的位寬大一位
*   當讀指針和同步過來的寫指針完全相等時(包括MSB),說明二者折回次數一致,FIFO爲空
*     
*/
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)滿判斷邏輯

複製代碼
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;
wire wfull_val;
// GRAYSTYLE2 pointer
always @(posedge wclk or negedge wrst_n)
    if (!wrst_n) 
    begin
        wbin <= 0;
        wptr <= 0;
    end
    else 
    begin
        wbin <= wbinnext;
         wptr <= wgraynext;
    end
//gray 碼計數邏輯    
assign wbinnext  = !wfull ? wbin + winc : wbin;
assign wgraynext = (wbinnext>>1) ^ wbinnext;
        assign waddr = wbin[ADDRSIZE-1:0];
        /*由於滿標誌在寫時鐘域產生,因此比較安全的做法是將讀指針同步到寫時鐘域*/
/**/
//------------------------------------------------------------------
// Simplified version of the three necessary full-tests:
// assign wfull_val=((wgnext[ADDRSIZE] !=wq2_rptr[ADDRSIZE] ) &&
// (wgnext[ADDRSIZE-1] !=wq2_rptr[ADDRSIZE-1]) &&
// (wgnext[ADDRSIZE-2:0]==wq2_rptr[ADDRSIZE-2:0]));
//------------------------------------------------------------------
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
複製代碼

 

P.S : 在quartus中有異步FIFO IP核,爲安全起見推薦使用IP覈定制FIFO,本文的目的只是作爲思路參考。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章