異步FIFO總結+Verilog實現


異步FIFO簡介

異步FIFO(First In First Out)可以很好解決多比特數據跨時鍾域的數據傳輸與同步問題。異步FIFO的作用就像一個蓄水池,用於調節上下游水量。

FIFO

FIFO是一種先進先出的存儲結構,其與普通存儲器的區別是,FIFO沒有讀寫地址總線,讀寫簡單,但相應缺點是無法控制讀寫的位置,只能由內部的讀寫指針自動加,順序讀寫數據。FIFO示意圖如下:

圖1

如圖1所示,輸入信號有讀寫時鍾、讀寫復位信號、讀寫使能信號、寫數據;輸出信號有空滿信號、讀數據。

異步時序電路

異步時序邏輯指電路時序邏輯沒有接在統一的時鍾脈沖上,或者電路中無時鍾脈沖,如SR鎖存器構成的時序電路,電路中各存儲單元的狀態不是同時發生的。

圖2

如上圖所示,常見的異步時序電路有不同的時鍾。
相應地,同步時序電路指電路中所有受時鍾控制的單元都接在統一的全局時鍾源上,存儲電路狀態的轉換在同一時鍾源的同一脈沖邊沿下同步進行。
異步時序電路觸發器狀態刷新不同步,信號延遲可能會累積導致輸出結果異常,應當避免。
目前ASIC與FPGA的設計中,通常是全局異步,局部同步的設計方法,但需注意異步信號與同步電路的交互問題。
常用的異步時序邏輯同步的方法有:

  • 單比特信號同步:結繩法
  • 多比特信號同步:SRAM、異步FIFO

注意,時鍾域是否相同針對的是時鍾源點,如果不同時鍾都是從同一個PLL生成,則這些時鍾相位和倍數都可控,認為是同步時鍾;若不同時鍾是由不同PLL生成,則即使這些時鍾為相同頻率,也認為是異步時鍾,因為這些時鍾間的相位關系無法確定。

亞穩態

亞穩態指觸發器無法在某個規定的時間內到達一個可確定的狀態。當一個觸發器進入亞穩態時,無法確定該單元的輸出電平,也無法確定其何時能穩定在正確的電平。在此期間,觸發器輸出的一些不確定電平,可能沿着信號通道上的各個觸發器傳遞下去。

設計詳解

思路

如圖1,

  • 異步FIFO是一個存儲結構,並且可讀可寫,因此需要一個雙口RAM;
  • 其次異步FIFO讀寫時鍾域分別控制讀寫地址,因此需要讀寫地址生成模塊;
  • 異步FIFO需要判斷是否已寫滿,或已讀空,且讀寫時鍾為異步時鍾,因此需要同步邏輯與判斷空滿邏輯

圖3

如圖3所示,DualRAM為雙口RAM模塊,sync_w2r模塊用於控制寫地址,判斷寫滿信號,sync_r2w模塊用於控制讀地址,判斷讀空信號。

細節

雙口RAM

雙口RAM設計較為簡單,主要為生成一塊memory,將寫數據寫入寫地址對應內存空間,從讀地址對應內存空間讀取讀數據,原理圖如下:

圖4

小細節,已知地址寬度ASIZE,求FIFO深度DEPTHDEPTH = 2 ^ ASIZE,可用移位實現:

DEPTH = 1 << ASIZE;

判斷空滿

上文提到,FIFO只能通過內部地址指針自動加,因此需要有空滿判斷邏輯,以免寫數據溢出,讀數據已空。本設計中判斷空滿采用的方法是比較讀地址與寫地址。讀寫時鍾為異步時鍾,在判斷空滿時需要用格雷碼比較讀寫讀寫地址,因此需要對讀寫地址進行同步。

同步

同步使用打一拍的方法,即將待同步信號延時一個時鍾周期,原理圖如下:

圖5

在寫時鍾域同步讀地址,在讀時鍾域同步寫地址。

格雷碼

上述提到的打一拍的同步方法適合於單比特信號,但顯然讀寫地址都大概率不為單比特信號。我們知道格雷碼的特征為相鄰格雷碼間只有一位不同。將讀寫地址轉換為格雷碼即可應用打一拍的同步方法。

Binary Gray
000 000
001 001
010 011
011 010
100 110
101 111
110 101
111 100
圖6

上圖為三位二進制碼與格雷碼的轉換。可以看出,二進制向格雷碼轉換時,格雷碼最高位為二進制碼最高位,格雷碼次高位為二進制碼最高位與次高位的異或,其余各位規律一致。

assign graynext = (binnext >> 1) ^ binnext;                                      `

本設計為地址位寬為4位,利用格雷碼判斷空滿時需擴展1位.

判斷空

判斷FIFO是否為空,在讀時鍾域同步轉換為格雷碼的寫地址,與轉換為格雷碼的寫地址比較,如果讀寫地址的格雷碼完全相等,則說明FIFO已空。

//判斷空信號
assign empty = (graynext == rq2_wptr);
always @(posedge rclk or negedge rrst_n) begin
    if (!rrst_n) begin
       rempty <= 1'b1;
    end
    else begin
       rempty <= empty;
    end
end
判斷滿

判斷FIFO是否已滿也是比較讀寫地址。在二進制地址中,FIFO已滿時,讀寫地址相等,與已空時一樣,無法判斷。上述說到判斷空滿時地址需擴展一位,實際上如果已寫滿,說明寫比都快,那寫地址比讀地址多走一輪,此時擴展的最高位不相同,如4'b00004'b1000,最高位擴展位,4'b1000說明比4'b0000多走一輪。轉換為格雷碼分別為4'b00004'b1100。可知FIFO已滿時,讀寫地址格雷碼最高位與次高位相反,其余位相同。

//判斷滿信號
assign full = (graynext == {~wq2_rptr[ASIZE: ASIZE - 1],
                wq2_rptr[ASIZE - 2: 0]});
always @(posedge wclk or negedge wrst_n) begin
    if (!wrst_n) begin
        wfull <= 1'b0;
    end
    else begin
        wfull <= full;
    end
end

控制讀寫地址

控制讀寫地址時,若讀寫使能為0,則不讀寫,讀寫地址不變;若讀寫使能為1,則讀寫地址加1。
小技巧,可將讀寫地址加讀寫使能信號,則可巧妙實現上述功能。

assign binnext = !wfull? (wbin + write_en): wbin;
assign binnext = !rempty? (rbin + read_en): rbin;

仿真

VCS仿真結果如下

圖7

完整代碼

top.v

點擊查看代碼
`timescale 1ns / 1ns
module Top #(
    parameter WIDTH = 8,
    parameter ASIZE = 4 //地址位寬
) (
    input wire [WIDTH - 1: 0] data_w,
    input wire wclk,
    input wire rclk,
    input wire wrst_n,
    input wire rrst_n,
    input wire write_en,
    input wire read_en,
    output wire wfull,
    output wire rempty,
    output wire [WIDTH - 1: 0] data_r
);

    wire [ASIZE - 1: 0] waddr, raddr;
    wire [ASIZE: 0] wq2_rptr, rq2_wptr;
    DualRAM #(WIDTH, ASIZE) u1(
                .wclk(wclk),
                .data_w(data_w),
                .write_en(write_en),
                .addr_w(waddr),
                .addr_r(raddr),
                .data_r(data_r)
                );

    sync_r2w #(WIDTH, ASIZE) u2(
                .rclk(rclk),
                .wclk(wclk),
                .wrst_n(wrst_n),
                .rrst_n(rrst_n),
                .read_en(read_en),
                .wq2_rptr(wq2_rptr),
                .raddr(raddr),
                .rq2_wptr(rq2_wptr),
                .rempty(rempty)
                );

    sync_w2r #(WIDTH, ASIZE) u3(
                .rclk(rclk),
                .wclk(wclk),
                .wrst_n(wrst_n),
                .rrst_n(rrst_n),
                .write_en(write_en),
                .wq2_rptr(wq2_rptr),
                .waddr(waddr),
                .rq2_wptr(rq2_wptr),
                .wfull(wfull)
                );
endmodule

sync.v

點擊查看代碼
`timescale 1ns / 1ns

module sync #(
    parameter WIDTH = 8,
    parameter ASIZE = 4
) (
    input wire clk,
    input wire rst_n,
    input wire [ASIZE: 0] ptr,
    output reg [ASIZE: 0] q2ptr
);

    reg [ASIZE: 0] q1ptr;
    always @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
            {q2ptr, q1ptr} <= 0;
        end
        else begin
            {q2ptr, q1ptr} <= {q1ptr, ptr};
        end
    end

endmodule

sync_w2r.v

點擊查看代碼
`timescale 1ns / 1ns

module sync_w2r #(
    parameter WIDTH = 8,
    parameter ASIZE = 4 //地址位寬
) (
    input wire rclk,
    input wire wclk,
    input wire wrst_n,
    input wire rrst_n,
    input wire write_en,
    input wire [ASIZE: 0] wq2_rptr,
    output wire [ASIZE: 0] rq2_wptr,
    output wire [ASIZE - 1: 0] waddr,
    output reg wfull
);

    wire [ASIZE: 0] graynext, binnext;
    reg [ASIZE: 0] wbin, wptr;
    // 寫地址控制
    always @(posedge wclk or negedge wrst_n) begin
        if (!wrst_n) begin
            wbin <= 'd0;
            wptr <= 'd0;
        end
        else begin
            wbin <= binnext;
            wptr <= graynext;
        end
    end

    assign binnext = !wfull? (wbin + write_en): wbin;
    assign graynext = (binnext >> 1) ^ binnext;
    assign waddr = wbin[ASIZE - 1: 0];

    //同步寫地址
    sync #(WIDTH, ASIZE) u_sync(
                                .clk(rclk),
                                .rst_n(rrst_n),
                                .ptr(wptr),
                                .q2ptr(rq2_wptr)
                                );

    //判斷滿信號
    assign full = (graynext == {~wq2_rptr[ASIZE: ASIZE - 1], wq2_rptr[ASIZE - 2: 0]});
    always @(posedge wclk or negedge wrst_n) begin
        if (!wrst_n) begin
            wfull <= 1'b0;
        end
        else begin
            wfull <= full;
        end
    end
endmodule

sync_r2w.v

點擊查看代碼
`timescale 1ns / 1ns

module sync_r2w #(
    parameter WIDTH = 8,
    parameter ASIZE = 4 //地址位寬
) (
    input wire rclk,
    input wire wclk,
    input wire wrst_n,
    input wire rrst_n,
    input wire read_en,
    input wire [ASIZE: 0] rq2_wptr,
    output wire [ASIZE: 0] wq2_rptr,
    output wire [ASIZE - 1: 0] raddr,
    output reg rempty
);

    reg [ASIZE: 0] rbin, rptr;
    wire [ASIZE: 0] graynext, binnext;

    // 讀地址邏輯
    always @(posedge rclk or negedge rrst_n) begin
        if (!rrst_n) begin
            rbin <= 'd0;
            rptr <= 'd0;
        end 
        else begin
            rbin <= binnext;
            rptr <= graynext;
        end
    end

    assign binnext = !rempty?  (rbin + read_en): rbin;
    assign graynext = (binnext >> 1) ^ binnext;
    assign raddr = rbin[ASIZE - 1: 0];

    // 同步讀地址
    sync #(WIDTH, ASIZE) u_sync(
                                .clk(wclk),
                                .rst_n(wrst_n),
                                .ptr(rptr),
                                .q2ptr(wq2_rptr)
                                );

    // 判斷空信號
    assign empty = (graynext == rq2_wptr);
    always @(posedge rclk or negedge rrst_n) begin
        if (!rrst_n) begin
            rempty <= 1'b1;
        end
        else begin
            rempty <= empty;
        end
    end

endmodule

DualRAM.v

點擊查看代碼
`timescale 1ns / 1ns

module DualRAM #(
    parameter WIDTH = 8,
    parameter ASIZE = 4 //地址位寬
) (
    input wire [WIDTH - 1: 0] data_w,
    input wire wclk,
    input wire write_en,
    input wire [ASIZE - 1: 0] addr_w,
    input wire [ASIZE - 1: 0] addr_r,
    output wire [WIDTH - 1: 0] data_r
);

    parameter DEPTH = 1 << ASIZE;

    reg [WIDTH - 1: 0] mem [DEPTH - 1: 0];
    reg [ASIZE - 1: 0] raddr_r;

    always @(posedge wclk) begin
        if (write_en) begin
            mem[addr_w] <= data_w;
        end
    end
    assign data_r = mem[addr_r];
endmodule

top_tb.sv

點擊查看代碼
`timescale 1ns / 1ns

module top_tb #(
    parameter WIDTH = 8,
    parameter ASIZE = 4 //地址位寬
) (
);

    reg [WIDTH - 1: 0] data_w;
    reg wclk;
    reg rclk;
    reg wrst_n;
    reg rrst_n;
    reg write_en;
    reg read_en;
    wire wfull;
    wire rempty;
    wire [WIDTH - 1: 0] data_r;

    Top u1(
            .data_w(data_w),
            .wclk(wclk),
            .rclk(rclk),
            .wrst_n(wrst_n),
            .rrst_n(rrst_n),
            .write_en(write_en),
            .read_en(read_en),
            .wfull(wfull),
            .rempty(rempty),
            .data_r(data_r)
            );

    initial begin
        data_w = 1'b0;
        wclk = 1'b0;
        rclk = 1'b0;
        wrst_n = 1'b1;
        rrst_n = 1'b1;
        write_en = 1'b1;
        read_en = 1'b1;
        #10 wrst_n = 1'b0;
            rrst_n = 1'b0;
        #10 wrst_n = 1'b1;
            rrst_n = 1'b1;
        #400 read_en = 1'b0;
    end

    initial begin
        forever begin
            #10 wclk = ~wclk;
        end        
    end

    initial begin
        forever begin
            #7 rclk = ~rclk;
        end        
    end


    reg [7: 0] i;
    reg [7: 0] mem [15: 0];
    initial begin
        for (i = 8'd0; i < 8'd16; i = i + 8'd1) begin
            mem[i] = i;
        end
    end

    reg [7: 0] j = 8'd0;

    always @(posedge wclk or negedge wrst_n) begin
        if (!wrst_n) begin
            j = 8'd0;
        end
        if (j > 8'd15) begin
            j = 8'd0;
        end
        else begin
            data_w = mem[j];
            j = j + 8'd1;
        end

    end
endmodule

參考鏈接
Verilog描述——異步時序電路與同步時序電路淺析_我要變強Wow-CSDN博客_同步時序電路和異步時序電路差異
同步時序電路和異步時序電路_ltfysa的博客-CSDN博客_同步時序電路和異步時序電路差異
芯動力——硬件加速設計方法_中國大學MOOC(慕課) (icourse163.org)
異步FIFO設計 - 知乎 (zhihu.com)
異步FIFO總結 - 喬_木 - 博客園 (cnblogs.com)


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM