Cummings異步FIFO——第一篇

0. 參考

Simulation and Synthesis Techniques for Asynchronous FIFO Design --- Clifford E. Cummings, Sunburst Design

1. 異步FIFO

在跨時鐘域傳輸的時候容易發生亞穩態。當在不同時鐘域之間傳遞的多個信號時,需要用到異步FIFO。

異步FIFO的難點在於生成讀寫地址和空滿指示位。

 

2. FIFO指針

2.1 同步fifo指針

對於同步FIFO而言,讀寫時鐘相同,可以用一個計數器來表示FIFO的狀態,如果只寫數據,則計數增加;如果只讀,則計數減少;級讀又寫,則計數不變。當計數到某個值,表示FIFO爲滿;計數爲零,表示FIFO空。此時的讀地址在每次讀有效的時候增加就可以了,當讀到最高爲會回到零;寫地址一樣。

2.2 異步FIFO指針

寫指針:寫指針指向下一個要寫的地址。當FIFO reset之後,寫指針爲0。當寫操作有效的時候,在下一個時鐘沿出向寫地址指向的位置寫數據,然後寫指針累加,指向下一個寫地址。

讀地址:讀指針也要指向下一個要讀的地址,FIFO reset之後,讀指針也爲0,此時空標誌位有效,當寫了一個數據後,空表示爲清零。讀操作有效的時候,在下一個時鐘沿,向讀地址指示的位置讀數據,並使讀地址累加。

滿狀態:如果寫地址趕上讀地址,此時讀寫地址相同,寫地址將讀地址套圈了,那麼FIFO滿了。

空狀態:reset之後是空;當讀地址趕上寫地址,FIFO空。

地址上增加一位空滿標誌位:n位地址,最高位爲標誌位,低n-1位位真正的FIFO地址。除了最高位,讀寫地址相同,則爲滿;如果讀寫地址相同——最高位也相同,那麼空。

2.3 二進制的地址問題

二進制地址累加時,相鄰地址經常存在多位同時跳變比如01到10,有兩位同時跳變。在異步採樣時可能沒有采樣到同時跳變後的值,可能採樣到00,11的情況。

解決:用格雷碼錶示地址

3.格雷碼計數器

特點: 1.相鄰碼只改變1bit 2.只能用來記2的指數倍數,不能記奇數個數

如下圖:

格雷碼與二進制的轉換:

格雷碼計數器的功能:在寫時鐘域內,二進制地址累加,然後將二進制的寫地址轉換成格雷碼地址,格雷碼地址要傳給讀時鐘域;在寫時鐘域中格雷碼轉換器將二進制的讀地址轉換成格雷碼地址,格雷碼地址要傳給寫時鐘域。

3.1 第一種格雷碼計數器

一種格雷碼編碼風格如下,只需要一組地址寄存器來保存地址:

上圖中上半部分表示生成n位的格雷碼;下半部分紅框部分將格雷碼最高位與次高位異或,然後將生成的addrmsb與格雷碼的低n-2位合併成一個N-1位的格雷碼地址,不知有什麼用??

// 上半圖的n位格雷碼計數器
wire clk,rst_n;
wire [n-1:0] bin,bnext,gnext;
wire inc,full; // 寫時鐘域的格雷碼轉換器
reg [n-1:0] ptr; //輸出的n位格雷碼
integer i;
always@(ptr) begin  //格雷碼轉二進制
    bin[n-1] = ptr[n-1];
    for(i=n-2;i>=0;i=i+1)
        bin[i] = bin[i+1] ^ bin[i];
end
assign bnext = bnext+(inc && !full)?1:0;
assign gnext = (bnext>>1) ^ bnext; //二進制轉格雷碼
always @(posedge clk or negedge rst_n) begin
    if(rst_n==1'b0) ptr<=0;
    else begin
        ptr <= bnext;
    end
end

 

3.2 第二種格雷碼計數器

用寄存器保存二進制碼,去除格雷碼到二進制的轉換邏輯。

減小寄存器之間的組合邏輯長度,可以增加頻率,特別是在FPGA中。

如下圖:

 


 

 

wire clk,rst_n;
wire [n-1:0] bnext,gnext;
wire inc,full; // 寫時鐘域的格雷碼轉換器
reg [n-1:0] ptr; //輸出的n位格雷碼
reg [n-1:0] bin;
integer i;
​
assign bnext = bnext+(inc && !full)?1:0;
assign gnext = (bnext>>1) ^ bnext; //二進制轉格雷碼
always @(posedge clk or negedge rst_n) begin
    if(rst_n==1'b0) {ptr,bin}<=0;
    else {ptr,bin} <={gnext,bnext};
end

4. fifo結構圖

5. 空滿標誌位

空標誌位在讀時鐘域產生;滿標誌位在寫時鐘域產生。

5.1 空標誌位

地址比實際地址增加一位。

讀時鐘域的格雷碼讀地址 與 同步過來的格雷碼寫地址相同,說明滿

assign rempty_val = (rgraynext == rq2_wptr); // 讀時鐘域的格雷碼讀地址 與 同步過來的格雷碼寫地址相同,說明滿
always @(posedge rclk or negedge rrst_n) // 讀時鐘
    if (!rrst_n) rempty <= 1'b1;
    else rempty <= rempty_val;

5.2 滿標誌位

地址比實際地址增加一位。

根據格雷碼的特性,寫時鐘域與的格雷碼寫地址與同步過來的格雷碼讀地址相比,最高兩位都不同,其他位相同,說明滿了。

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;

5.3 不同時鐘頻率的考慮

問題一:快時鐘是慢時鐘的兩倍,那麼快域地址變化兩次,慢時鐘域採樣一次,前後採樣值變化了兩次,會產生多位同步的問題嗎?

不會。快時鐘域第一次改變一位,比如從A到B,慢時鐘域沒有采樣,當快時鐘域改變第二次B到C之後,慢時鐘域才採樣,雖然這期間快時鐘域的地址從A到C變了兩次,但是慢時鐘域只看到第二次B到C,只跳變了一位,因此不會產生多位同步問題。

問題二:快時鐘域是否會引起full+1的情況——寫溢出,或者empty+1——讀溢出?

不會。對於滿是在寫時鐘域產生,如果寫時鐘比讀時鐘快,如果waddr追上raddr,那麼full有效,此時不能再寫了,也就不會產生full+1情況。對於空是在寫時鐘域產生,如果讀時鐘比寫時鐘快,如果raddr趕上waddr,那麼就不能再讀,也就不會產生empty+1.

5.4 空滿標誌位取消

空滿的set是立即生效的。比如讀時鐘域中,將採樣到的寫時鐘域的格雷碼寫地址 與當前的讀地址比較,如果相等,則立馬使empty有效;full類似。但是讀寫標誌位clear是有延遲的。

當empty有效的時候,讀時鐘域採樣的寫地址與讀地址相同,如果此時寫入數據,那麼在寫時鐘域裏寫地址是增加的,但是這個增加了的寫地址需要兩個讀時鐘的同步才能讓讀時鐘域裏的地址比較器看到,所以empty的clear有兩個讀時鐘週期的延遲;同樣full的clear有兩個寫時鐘週期的延遲。

但這不會使FIFO發生功能錯誤,可忽略。

5.5 reset時候地址多位跳變

沒影響,因爲reset就表明FIFO裏的數據是無效的,此時不對FIFO進行讀寫。

6. 代碼

這些代碼是Cummings論文中給的。

6.1 頂層

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_n,
input rinc, rclk, rrst_n);
wire [ASIZE-1:0] waddr, raddr;
wire [ASIZE:0] wptr, rptr, wq2_rptr, rq2_wptr;
sync_r2w sync_r2w (.wq2_rptr(wq2_rptr), .rptr(rptr),
.wclk(wclk), .wrst_n(wrst_n));
sync_w2r sync_w2r (.rq2_wptr(rq2_wptr), .wptr(wptr),
.rclk(rclk), .rrst_n(rrst_n));
fifomem #(DSIZE, ASIZE) fifomem
(.rdata(rdata), .wdata(wdata),
.waddr(waddr), .raddr(raddr),
.wclken(winc), .wfull(wfull),
.wclk(wclk));
rptr_empty #(ASIZE) rptr_empty
(.rempty(rempty),
.raddr(raddr),
.rptr(rptr), .rq2_wptr(rq2_wptr),
.rinc(rinc), .rclk(rclk),
.rrst_n(rrst_n));
wptr_full #(ASIZE) wptr_full
(.wfull(wfull), .waddr(waddr),
.wptr(wptr), .wq2_rptr(wq2_rptr),
.winc(winc), .wclk(wclk),
.wrst_n(wrst_n));
endmodule

6.2 內存讀寫模塊

讀數據是直接從mem中讀,不需要時鐘。讀地址A指示的是下一次要讀的地址——此時FIFO不爲空,讀A地址是有效的。當前rdata信號上保存的是下一次要讀的數據。如果外部讀時鐘域打算讀數據,那麼給一個讀有效rinc,在rclk上升沿就可以直接把rdata取走,在rinc上升沿FIFO內部會根據A+1地址判斷是否empty。(可以參考3.2節)。

寫數據winc有效時,下一個寫時鐘沿wclk要寫入數據。滿標誌表示如果在下一個時鐘沿寫數據,就寫到讀地址處(讀寫地址相同)。如果滿,則下一個時鐘沿不能寫。所以在下一個寫時鐘沿到來時要判斷滿標誌,如果滿了,則不能寫。

同樣,讀地址表示下一個讀時鐘沿要讀的數據,空表示下一個時鐘沿是否可以讀。對於讀數據的設備,它需要在讀的時候判斷是否空了,至於FIFO的rdata輸出端則不需要進行empty判斷,FIFO將下一次要讀的數據放在rdata處,如果讀數據的設備要讀,就在rclk時鐘沿讀就行了。

module fifomem #(parameter DATASIZE = 8, // Memory data word width
parameter ADDRSIZE = 4) // 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 VENDORRAM
// instantiation of a vendor's dual-port RAM
    vendor_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; //將地址位數轉化成FIFO深度
reg [DATASIZE-1:0] mem [0:DEPTH-1];
assign rdata = mem[raddr]; //直接讀,empty標誌位影響raddr的累加
always @(posedge wclk)
    if (wclken && !wfull) mem[waddr] <= wdata;//寫需要判斷full,並且在時鐘沿處寫
`endif
endmodule

6.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

6.4 寫地址到讀時鐘域同步

讀時鐘控制,打兩拍

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

6.5 空標誌位產生

產生空標誌位、n-1位當前的二進制讀地址、n位格雷碼讀地址

產生空標誌位的結構框圖如下:

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) {rbin, rptr} <= 0;
else {rbin, rptr} <= {rbinnext, rgraynext};
// Memory read-address pointer (okay to use binary to address memory)
assign raddr = rbin[ADDRSIZE-1:0];//給到memory模塊
assign rbinnext = rbin + (rinc & ~rempty);
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.6 滿標誌產生

產生滿標誌、n-1位當前寫地址、n位格雷碼寫地址

產生滿標誌位的結構框圖如下:

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;
//------------------------------------------------------------------
// 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

 

 

 

 

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