Verilog基礎知識(異步FIFO)

本文主要內容來自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)。
  • 讀時鐘:讀操作所遵循的時鐘,在每個時鐘沿來臨時讀數據。
  • 寫時鐘:寫操作所遵循的時鐘,在每個時鐘沿來臨時寫數據。

四、FIFO設計的關鍵

1. 讀寫指針的工作原理
  • 寫指針:總是指向下一個將要被寫入的單元,復位時,指向第1個單元(編號爲0)。
  • 讀指針:總是指向當前要被讀出的數據,復位時,指向第1個單元(編號爲0)
  • 循環數組:類似於C語言中用數組編寫隊列是front = (front+1)%length,在verilog中定義一個位寬爲ADDRSIZE 的地址變量會自動溢出,天然就是一個循環數組
2. 產生可靠的empty/full控制信號

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

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

那麼讀指針和寫指針相等的時候究竟是空還是滿呢?
爲了區分到底是滿狀態還是空狀態,軟件中常用的辦法如下:

  • 規定front=rear 爲隊列空
  • 規定front=rear+1 爲隊列滿

但是這樣相當於浪費了一個RAM地址,設計硬件本着減少面積的宗旨,則是採用給指針增加一位MSB,當寫指針增加並越過最後一個FIFO地址時,就將寫指針這個未用的MSB加1,其它位回零。對讀指針也進行同樣的操作。此時,對於深度爲2^n的FIFO,需要的讀/寫指針位寬爲(n+1)位,如對於深度爲8的FIFO,需要採用4bit的計數器,0000~1000、1001~1111,MSB作爲折回標誌位,而低3位作爲地址指針。

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

異步FIFO通過比較讀寫地址進行滿空判斷,但是讀寫地址屬於不同的時鐘域,所以在比較之前需要先將讀寫地址進行同步處理,將寫地址同步到讀時鐘域再和讀地址比較進行FIFO空狀態判斷(同步後的寫地址一定是小於或者等於當前的寫地址,所以此時判斷FIFO爲空不一定是真空,這樣更保守),將讀地址同步到寫時鐘域再和寫地址比較進行FIFO滿狀態判斷(同步後的讀地址一定是小於或者等於當前的讀地址,所以此時判斷FIFO爲滿不一定是真空,這樣更保守),這樣可以保證FIFO的特性:FIFO空之後不能繼續讀取,FIFO滿之後不能繼續寫入。

大多數情形下,異步FIFO兩端的時鐘不是同頻的,或者讀快寫慢,或者讀慢寫快,這時候進行地址同步的時候,可能會有地址遺漏,以讀慢寫快爲例,進行滿標誌判斷的時候需要將讀地址同步到寫時鐘域,因爲讀慢寫快,所以不會有讀地址遺漏,同步後的讀地址滯後當前讀地址,所以可能滿標誌會提前產生。進行空標誌判斷的時候需要將寫地址同步到讀地址,因爲讀慢寫快,所以當讀時鐘同步寫地址的時候,必然會漏掉一部分寫地址(寫時鐘快,寫地址隨寫時鐘翻轉,直到滿標誌出現爲止),那到底讀時鐘會同步到哪個寫地址?不必在意是哪一個,我們關注的是漏掉的地址會不會對FIFO的空標誌產生影響。比如寫地址從0寫到10,期間讀時鐘域只同步到了2,5,7這三個寫地址,漏掉了其他地址。同步到7地址時,真實的寫地址可能已經寫到10地址,相當於“在讀時鐘域還沒來得及覺察的情況下,寫時鐘域可能偷偷寫了數據到FIFO去”,這樣在比較讀寫地址的時候不會產生FIFO“空”讀操作。漏掉的地址也沒有對FIFO的邏輯操作產生影響。

binary編碼的地址總線在跳變時極易產生毛刺,因爲binary編碼是多位跳變,在實現電路時不可能做到所有的地址總線等長,address bus skew必然存在,而且寫地址和讀地址分屬不同時鐘域,讀寫時鐘完全異步,這樣地址總線在進行同步過程中出錯不可避免,比如寫地址在從0111到1000轉換時4條地址線同時跳變,這樣讀時鐘在進行寫地址同步後得到的寫地址可能是0000-1111的某個值,這個完全不能確定,所以用這個同步後的寫地址進行FIFO空判斷的時候難免出錯。

這個時候gray碼體現了價值,一次只有一位數據發生變化,這樣在進行地址同步的時候,只有兩種情況:1.地址同步正確;2.地址同步出錯,但是隻有1位出錯;第一種正確的情況不需要分析,我們關注第二種,假設寫地址從000->001,讀時鐘域同步出錯,寫地址爲000->000,也就是地址沒有跳變,但是用這個錯誤的寫地址去做空判斷不會出錯,最多是讓空標誌在FIFO不是真正空的時候產生,而不會出現空讀的情形。所以gray碼保證的是同步後的讀寫地址即使在出錯的情形下依然能夠保證FIFO功能的正確性,當然同步後的讀寫地址出錯總是存在的(因爲時鐘異步,採樣點不確定)。這裏需要注意gray碼只是在相鄰兩次跳變之間纔會出現只有1位數據不一致的情形,超過兩個週期則不一定,所有地址總線bus skew一定不能超過一個週期,否則可能出現gray碼多位數據跳變的情況,這個時候gray碼就失去了作用,因爲這時候同步後的地址已經不能保證只有1位跳變了。

4. gray碼編址情況下的empty/full如何產生

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

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

  • wptr和同步過來的rptr的MSB不相等,因爲wptr必須比rptr多折回一次。

  • wptr與rptr的次高位不相等,如上圖位置7和位置15,轉化爲二進制對應的是0111和1111,MSB不同說明多折回一次,111相同代表同一位置。
  • 剩下的其餘位完全相等。


五、實現

top

module fifo1 #(
    parameter DSIZE = 8,
    parameter ASIZE = 4
    )(
    output [DSIZE-1:0] rdata,
    output             wfull,
    output             rempty,
    input  [DSIZE-1:0] wdata,
    input              winc, wclk, wrst,
    input              rinc, rclk, rrst
    );

wire [ASIZE-1:0] waddr, raddr;
wire [ASIZE:0] wptr, rptr, wrptr2, rwptr2;

sync_r2w #(ASIZE) U1(
    .wrptr2(wrptr2),
    .rptr(rptr),
    .wclk(wclk),
    .wrst(wrst)
    );

sync_w2r #(ASIZE) U2(
    .rwptr2(rwptr2),
    .wptr(wptr),
    .rclk(rclk),
    .rrst(rrst)
    );

fifomem #(DSIZE, ASIZE) U3(
    .rdata(rdata),
    .wdata(wdata),
    .waddr(waddr),
    .raddr(raddr),
    .wclken(winc),
    .wclk(wclk)
    );

rptr_empty #(ASIZE) U4(
    .rempty(rempty),
    .raddr(raddr),
    .rptr(rptr),
    .rwptr2(rwptr2),
    .rinc(rinc),
    .rclk(rclk),
    .rrst(rrst)
    );

wptr_full #(ASIZE) U5(
    .wfull(wfull),
    .waddr(waddr),
    .wptr(wptr),
    .wrptr2(wrptr2),
    .winc(winc),
    .wclk(wclk),
    .wrst(wrst)
    );

endmodule
empty/full generation

module rptr_empty #(
    parameter ADDRSIZE = 4
    )(
    output     [ADDRSIZE-1:0] raddr,
    output reg [ADDRSIZE:0]   rptr,
    output reg                rempty,
    input      [ADDRSIZE:0]   rwptr2,
    input                     rinc, rclk, rrst
    );

reg [ADDRSIZE:0] rbin, rgnext, rbnext;

// Grey code pointer
always @(posedge rclk or posedge rrst) begin
    if (rrst) begin
        rptr <= 0;
        rbin <= 0;
    end
    else begin
        rptr <= rgnext;
        rbin <= rbnext;
    end
end

always @(*) begin
    rbnext = (!rempty)? (rbin + rinc) : rbin;
    rgnext = (rbnext>>1)^rbnext; // binary to gray
end

// Memory read-address pointer
assign raddr = rbin[ADDRSIZE-1:0];

// FIFO empty on reset or when the next rptr == synchronized wptr
always @(posedge rclk or posedge rrst) begin
    if (rrst) rempty <= 1'b1;
    else rempty <= (rgnext == rwptr2);
end

endmodule
module wptr_full #(
    parameter ADDRSIZE = 4
    )(
    output     [ADDRSIZE-1:0] waddr,
    output reg [ADDRSIZE:0]   wptr,
    output reg                wfull,
    input      [ADDRSIZE:0]   wrptr2,
    input                     winc, wclk, wrst
    );

reg [ADDRSIZE:0] wbin, wgnext, wbnext;

// Grey code pointer
always @(posedge wclk or posedge wrst) begin
    if (wrst) begin
        wptr <= 0;
        wbin <= 0;
    end
    else begin
        wptr <= wgnext;
        wbin <= wbnext;
    end
end

always @(*) begin
    wbnext = (!wfull)? (wbin + winc) : wbin;
    wgnext = (wbnext>>1)^wbnext;
end

// Memory read-address pointer
assign waddr = wbin[ADDRSIZE-1:0];

// FIFO full generation
always @(posedge wclk or posedge wrst) begin
    if (wrst) wfull <= 1'b0;
    else wfull <= ((wgnext[ADDRSIZE]     != wrptr2[ADDRSIZE]  ) &&
                   (wgnext[ADDRSIZE-1]   != wrptr2[ADDRSIZE-1]) &&
                   (wgnext[ADDRSIZE-2:0] == wrptr2[ADDRSIZE-2:0]));
end

endmodule
sync
module sync_r2w #(
    parameter ADDRSIZE = 4
    )(
    output reg [ADDRSIZE:0] wrptr2,
    input      [ADDRSIZE:0] rptr,
    input                   wclk, wrst
    );

reg [ADDRSIZE:0] wrptr1;

always @(posedge wclk or posedge wrst) begin
    if (wrst) begin
        {wrptr2, wrptr1} <= 0;
    end
    else begin
        {wrptr2, wrptr1} <= {wrptr1, rptr};
    end
end

endmodule
module sync_w2r #(
    parameter ADDRSIZE = 4
    )(
    output reg [ADDRSIZE:0] rwptr2,
    input      [ADDRSIZE:0] wptr,
    input                   rclk, rrst
    );

reg [ADDRSIZE:0] rwptr1;

always @(posedge rclk or posedge rrst) begin
    if (rrst) begin
        {rwptr2, rwptr1} <= 0;
    end
    else begin
        {rwptr2, rwptr1} <= {rwptr1, wptr};
    end
end

endmodule
testbench

首先是一個行爲級的FIFO,二進制編碼

module beh_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,
    input              rinc, rclk, rrst
    );

localparam MEMDEPTH = 1<<ASIZE;
reg [DSIZE-1:0] ex_mem [0:MEMDEPTH-1];

reg [ASIZE:0] wptr, wrptr1, wrptr2, wrptr3;
reg [ASIZE:0] rptr, rwptr1, rwptr2, rwptr3;

always @(posedge wclk or posedge wrst) begin
    if (wrst) begin
        wptr <= 0;
    end
    else if (winc && !wfull) begin
        ex_mem[wptr[ASIZE-1:0]] <= wdata;
        wptr <= wptr+1;
    end
end

always @(posedge wclk or posedge wrst) begin
    if (wrst) begin
        {wrptr3, wrptr2, wrptr1} <= 0;
    end
    else begin
        {wrptr3, wrptr2, wrptr1} <= {wrptr2, wrptr1, rptr};
    end
end

always @(posedge rclk or posedge rrst) begin
    if (rrst) begin
        rptr <= 0;
    end
    else if (rinc && !rempty) begin
        rptr <= rptr+1;
    end
end

always @(posedge rclk or posedge rrst) begin
    if (rrst) begin
        {rwptr3, rwptr2, rwptr1} <= 0;
    end
    else begin
        {rwptr3, rwptr2, rwptr1} <= {rwptr2, rwptr1, wptr};
    end
end

assign rdata = ex_mem[rptr[ASIZE-1:0]];
assign rempty = (rptr == rwptr3);
assign wfull = ((wptr[ASIZE-1:0] == wrptr3[ASIZE-1:0]) &&
                (wptr[ASIZE]     != wrptr3[ASIZE]));

endmodule
`timescale 1ns/100ps

module fifo_tb;

parameter DSIZE = 8;
parameter ASIZE = 4;

wire [DSIZE-1:0] rdata, beh_rdata;
wire             wfull, beh_wfull;
wire             rempty, beh_rempty;
reg  [DSIZE-1:0] wdata;
reg              winc, wclk, wrst;
reg              rinc, rclk, rrst;

fifo1 #(DSIZE, ASIZE) U1(
    .rdata(rdata),
    .wfull(wfull),
    .rempty(rempty),
    .wdata(wdata),
    .winc(winc),
    .wclk(wclk),
    .wrst(wrst),
    .rinc(rinc),
    .rclk(rclk),
    .rrst(rrst)
    );

beh_fifo #(DSIZE, ASIZE) U2(
    .rdata(beh_rdata),
    .wfull(beh_wfull),
    .rempty(beh_rempty),
    .wdata(wdata),
    .winc(winc),
    .wclk(wclk),
    .wrst(wrst),
    .rinc(rinc),
    .rclk(rclk),
    .rrst(rrst)
    );

always #30 wclk = ~wclk;
always #20 rclk = ~rclk;
always #30 wdata = {$random}%256;

initial begin
    wrst = 0;
    rrst = 0;
    rclk = 0;
    wclk = 0;
    winc = 0;
    rinc = 0;
    #50 wrst = 1;
        rrst = 1;
    #50 wrst = 0;
        rrst = 0;
    #10 rinc = 1;
    #100 rinc = 0;
    #100 winc = 1;
    #1000 winc = 0;
    #100 rinc = 1;
    #2000 $finish;
end

always @((rdata  != beh_rdata ) && 
         (wfull  != beh_wfull ) &&
         (rempty != beh_rempty)) begin
    $display($time, "rdata is %h, beh_rdata is %h", rdata, beh_rdata);
end

endmodule

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