同步异步FIFO详解(介绍、异同、为什么要用格雷码、深度)+ 各自verilog代码+testbench

同步 FIFO / 异步 FIFO

FIFO 简介

FIFO 的参数

  • 宽度:一次读写操作的数据位
  • 深度:可以存储的 N 位数据的数目 (宽度为 N)
  • 满标志:FIFO 已满或将要满时,由 FIFO 的状态电路送出的信号,阻止 FIFO 写操作
  • 空标志:FIFO 已空或将要空时,由 FIFO 的状态电路送出的信号,阻止 FIFO 读操作
  • 读时钟:读操作所遵循的时钟
  • 写时钟:写操作所遵循的时钟

设计空满标志位电路

  • 正确产生空满信号是任何 FIFO 设计的关键。其设计原则是:能写满而不溢出,能读空而不多读。
  • 在写时钟域下,判断读写指针的关系,生成满标志
  • 在读时钟域下,判断读写指针的关系,生成空标志

对于空满判断,可看下图

image.pngl200

在地址中额外添加一个位,当写指针增加并越过最后一个地址位时,就将这个额外的位 (MSB) 加一,其他位回 0。读指针也进行同样的操作。额外的位作为折回标志位。如果两个指针的 MSB 不同,其余位相等,说明读指针比写指针少折回了一次,这种情况下,FIFO 为满。而读写指针所有位都相等,说明折回次数相等,此时 FIFO 为空。

同步 FIFO 和异步 FIFO 的异同

  • 同步 FIFO 的写时钟和读时钟为同一个时钟,FIFO 内部所有逻辑都是同步逻辑,常常用于交互数据缓冲。
  • 异步 FIFO 的写时钟和读时钟为异步时钟,FIFO 内部的写逻辑和读逻辑的交互需要异步处理,异步 FIFO 常用于跨时钟域交互。

数据流的传输与指示信号不同在于

  • 数据流大多具有连续性,及背靠背传输
  • 数据流要求信号具有较快的传输速度。主要的方案是利用 FIFO 进行传输。

image.pngl200

构成

  • FIFO_MEMORY
    • 双口 ram 存储数据
  • Sync_r2w
    • 同步读数据指针到写时钟域
  • Sync_w2r
    • 同步写数据指针到读时钟域
  • Wptr_full
    • 处理写指针和满信号的逻辑
  • Rptr_empty
    • 处理读指针和空信号的逻辑

二进制的读写指针通常位宽超过了 1bit,而多比特信号是不可以直接使用两级同步器的。这时,考虑到 FIFO 地址是连续变化的,我们使用格雷码来进行传输。

读指针和写指针的标志位也需要加到格雷码里面,由此判断条件是

  • MSB 不相等
  • 次高位不相等
  • 其余位相等

image.pngl200

格雷码转换公式

assign gray=(binary>>1)^(binary);

[!note]
为什么要设置一个 fifo 上限,是因为可能传过来时,异步 fifo 一侧 wr 侧的过快,rd 侧没有反应导致溢出

为什么需要用格雷码传输?

不使用格雷码传递

假设现在 wr_ptr = 0011,rd_ptr = 0011,此时为空的情况

image.pngl200|225

如果 wr 时域发生写操作,则 wr_ptr = 0100,此时 fifo 里有效存储为 1,可供 rd 端读取一个值。但 wr_ptr 在传送到写时域除了 0100 还可能出现 0001 和 0111 等多种状态。
我只举例两种情况,wr_ptr 和 rd_ptr 都在读时域下

  1. 如果是 wr_ptr = 0001 时,wr_ptr < rd_ptr,出现 error

image.pngl200|225

  1. 如果是 wr_ptr = 0111 时,wr_ptr > rd_ptr,此时读时域会以为 num=4,可能发生空读情况

image.pngl200|225

使用格雷码传输

同样假设现在 wr_ptr = 0011,rd_ptr = 0011,此时为空的情况

image.pngl200|225

如果 wr 端发生写操作,则 wr_ptr = 0010,此时 fifo 里有效存储为 1,可供 rd 端读取一个值。但 wr_ptr 在读时域上只可能出现 0010 和 0111 两种状态。

如果依然保持 0010,则指示 FIFO 为空,不发生读取

image.pngl200|225

如果变为 0111,则指示 FIFO 容量为 1,正常读取

image.pngl200|225

注意

Gray 码只是在相邻两次跳变之间才会出现只有 1 位数据不一致的情形,超过两个周期则不一定

所以,地址总线 bus skew 一定不能超过一个周期,否则可能出现 gray 码多位数据跳变的情况,这个时候 gray 码就失去了作用,因为这时候同步后的地址已经不能保证只有 1 位跳变了。

设计合理的 FIFO 深度

参考链接1
参考链接2

一般来说是写采用的是 burst,单次大量数据

所以本质上需要
$$

FIFO 被填满的时间 > 数据包传送的时间
转换到读写速率里是 转换到读写速率里是 转换到读写速率里是
(FIFO 深度 / 「写速率 - 读速率) > 写入数据量/写入速率
$$

写时钟的频率为 Fa,读时钟的频率为 Fb

场景一 Fa > Fb 读和写之间没有空闲 cycle

假设:

  • 写数据时钟频率 fa=80MHz
  • 读数据时钟频率 fb=50MHz
  • 突发长度= number of data to be transferred = 120
  • 在突发传输过程中,数据都是连续读写的

解答

  • 写一个数据所需时间 = 1/80MHz = 12.5ns
  • 读一个数据所需时间 = 1/50MHz = 20ns
  • 一次 burst 所需时间 12.5 * 120 = 1500 ns
  • 1500 ns 可以读走 1500 / 20 = 75 个数据
  • 所以 FIFO 深度为 120 - 75 = 45 个
场景二 Fa > Fb 读会在写之后空两拍

背景:异步 FIFO 中采用格雷码和两级同步寄存器降低亚稳态的概率,所以会慢两拍

此时 wr_ptr 同步到读时域时,可能会出现显示为空,但实际非空的情况,并且 FIFO 可能一直还在写入。所以读端的写时钟一定小于等于当前的实际 wr_ptr。

此时 rd_ptr 同步到写时域时,可能会出现显示为满,但实际非满的情况,并且 FIFO 可能一直还在读出。所以读端的写时钟一定小于等于当前的实际 wr_ptr。

我最开始会有疑问?

[!question]
显示满,实际非满,那么会停止写入,FIFO 实际使用的深度没有设计的高,那不需要设计那么高

FIFO 的作用是存储一次 burst,为了避免一次 burst 写不完,应该使得 FIFO 深度更大一些

由此场景二 FIFO 的深度需要略大于场景一。

场景四 Fa > Fb 读和写都会有空白拍

假设

  • 写数据时钟频率 fa=80MHz
  • 读数据时钟频率 fb=50MHz
  • 突发长度= number of data to be transferred = 120
  • 每隔 1 个 cycle 写一次
  • 每隔 3 个 cycle 读一次

解答

  • 写一个数据所需时间 = 1/80MHz = 12.5ns
  • 读一个数据所需时间 = 1/50MHz = 20ns
  • 一次 burst 所需时间 (12.5) * 2 * 120 = 3000 ns
  • 3000 ns 可以读走 3000 / ( 20 * 4) = 37.5 个数据
  • 所以 FIFO 深度为 120 - 37.5 = 82.5 个
场景五 Fa < Fb 读和写之间没有空闲 cycle

假设

  • 写数据时钟频率 fa=40MHz
  • 读数据时钟频率 fb=50MHz
  • 突发长度= number of data to be transferred = 120
  • 在突发传输过程中,数据都是连续读写的

FIFO 深度设置为 1 就好了

已知 Fa 和 Fb,读和写随机

假设

  • 写数据时钟频率 fa=80MHz
  • 读数据时钟频率 fb=50MHz
  • 在写时钟周期内,每 100 个周期就有 40 个数据写入 FIFO
  • 在读时钟周期内,每 10 个周期可以有 8 个数据读出 FIFO

最坏的情况是连续两次背靠背的情况,需要连续写入 80 个数据

image.pngl200

首先检验是否有解?在最复杂的情况下,读取所花费的时间 < 写入所花的时间

  • 200 个周期写入 80 个数据所需时间:1 / 80MHz * ( 80 * 100 / 40) = 2500ns
  • 读出 80 个数据中所需时间:1 / 50MHz * (80 * 10 / 8) = 2000ns
  • 2500ns > 2000ns 有解
  • 连续写入 80 个数据所需要的时间: 1/ 80MHz * 80 = 1000ns
  • 读出一个数据需要的时间:1/50MHz * 10 / 8 = 25 ns
  • 1000ns 可以读出的数据个数:1000 / 25 = 40 个
  • 所以 FIFO 深度 80 - 40 = 40 个

总结:

  • 写时钟频率为 fwr
  • 读时钟频率为 frd
  • 在写时钟周期内,每 m 个周期内就有 n 个数据写入 FIFO
  • 在读时钟周期内,每 x 个周期内可以有 y 个数据读出 FIFO

那么:

  • 首先必须满足 (1/fwr) * (m/n) ≥ (1/frd) * (x/y)
  • ”背靠背“的情形下是 FIFO 读写的最坏情形,burst 长度 B = 2 * n
  • 写完 burst 长度数据最快所需时间 T = (1/fwr) * B
  • 从 FIFO 中读出一个数据至少需要时间 t= (1/frd) * (x/y)
  • 在 T 时间内能够从都走的数据个数 = T/t = B * (frd/fwr) * (y/x)
  • 在 T 时间内还没有读走的数据个数 = B - B * (frd/fwr) * (y/x)
  • 因此 FIFO 的最小深度为 B - B * (frd/fwr) * (y/x)
  • 注意保留一些余量

同步 FIFO Verilog 代码

如果有问题,欢迎留言

代码
module synchronous_fifo#(
    parameter   DATA_WIDTH = 8,
    parameter   ADDR_WIDTH = 4,
    parameter   DEPTH = 1 << ADDR_WIDTH
)(
    input                      clk,
    input                      rst_n,
    input                      wr_en,
    input                      rd_en,
    input  [DATA_WIDTH-1:0]    data_in,

    output                     full,    
    output                     empty,    
    output [DATA_WIDTH-1:0]    data_out

);
    integer                 i;   
    reg [DATA_WIDTH-1:0]    data_out_r;
    reg [DATA_WIDTH-1:0]    data_out;
    reg [ADDR_WIDTH-1:0]    wr_ptr;
    reg [ADDR_WIDTH-1:0]    rd_ptr;
    reg [DATA_WIDTH-1:0]    ram[DEPTH-1:0];

    always @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
            wr_ptr <= 0;
        end else if (wr_en)
            wr_ptr <= wr_ptr + 1 ;
    end

    always @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
            rd_ptr <= 0;
        end else if (rd_en)
            rd_ptr <= rd_ptr + 1 ;
    end

    always @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
            for(i=0;i < DEPTH;i++)
                ram[i] <= {DATA_WIDTH{1'b0}};
        end else if (wr_en)
            ram[wr_ptr] <= data_in;
    end

    always @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
            data_out_r <= 0;
        end else if (rd_en)
            data_out_r <= ram[rd_ptr] ;
    end


    assign data_out = data_out_r;
    assign empty    = wr_ptr == rd_ptr;
    assign full     = (wr_ptr[ADDR_WIDTH-2:0] == rd_ptr[ADDR_WIDTH-2:0]) && (wr_ptr[ADDR_WIDTH-1]!=rd_ptr[ADDR_WIDTH-1]);


endmodule
Testbench
module synchronous_fifo_tb;
    // Parameters definition
    parameter DATA_WIDTH  = 8;
    parameter ADDR_WIDTH = 4;
    parameter DEPTH      = 1 << ADDR_WIDTH;
    parameter CLK_PERIOD = 10;  // 100MHz clock

    // Signal declaration
    reg                    clk;
    reg                    rst_n;
    reg                    wr_en;
    reg                    rd_en;
    reg  [DATA_WIDTH-1:0]  data_in;
    wire                   full;
    wire                   empty;
    wire [DATA_WIDTH-1:0]  data_out;

    // Verification counters
    integer               write_cnt;
    integer               read_cnt;
    integer               error_cnt;

    // DUT instantiation
    synchronous_fifo #(
        .DATA_WIDTH  ( DATA_WIDTH ),
        .ADDR_WIDTH ( ADDR_WIDTH ),
        .DEPTH      ( DEPTH      )
    ) fifo_inst (
        .clk      ( clk      ),
        .rst_n    ( rst_n    ),
        .wr_en    ( wr_en    ),
        .rd_en    ( rd_en    ),
        .data_in  ( data_in  ),
        .full     ( full     ),
        .empty    ( empty    ),
        .data_out ( data_out )
    );

    // Clock generation
    always #(CLK_PERIOD/2) begin
        clk = ~clk;
    end

    // Tasks definition
    task initialize;
        begin
            clk       = 1'b0;
            rst_n     = 1'b1;
            wr_en     = 1'b0;
            rd_en     = 1'b0;
            data_in   = 'd0;
            write_cnt = 0;
            read_cnt  = 0;
            error_cnt = 0;
        end
    endtask

    task reset;
        begin
            rst_n = 1'b0;
            #(CLK_PERIOD*2);
            rst_n = 1'b1;
            #(CLK_PERIOD);
        end
    endtask

    task write_data;
        input [DATA_WIDTH-1:0] data;
        begin
            @(posedge clk);
            if (full) begin
                $display("Error: Attempting to write when FIFO is full at time %0t", $time);
                error_cnt = error_cnt + 1;
            end 
            else begin
                wr_en     = 1'b1;
                data_in   = data;
                write_cnt = write_cnt + 1;
                @(posedge clk);
                wr_en     = 1'b0;
            end
        end
    endtask

    task read_data;
        begin
            @(posedge clk);
            if (empty) begin
                $display("Error: Attempting to read when FIFO is empty at time %0t", $time);
                error_cnt = error_cnt + 1;
            end 
            else begin
                rd_en     = 1'b1;
                read_cnt  = read_cnt + 1;
                @(posedge clk);
                rd_en     = 1'b0;
            end
        end
    endtask

    // Main test procedure
    initial begin
        // Initialize and reset
        initialize;
        reset;
        
        // Test 1: Write until full
        $display("\nTest 1: Writing until FIFO is full");
        repeat(DEPTH/2) begin
            write_data($random);
            #(CLK_PERIOD);
        end
        
        if (full) begin
            $display("PASS: FIFO is full after %0d writes", DEPTH);
        end 
        else begin
            $display("FAIL: FIFO should be full after %0d writes", DEPTH);
            error_cnt = error_cnt + 1;
        end

        // Test 2: Read until empty
        $display("\nTest 2: Reading until FIFO is empty");
        repeat(DEPTH/2) begin
            read_data;
            #(CLK_PERIOD);
        end
        
        if (empty) begin
            $display("PASS: FIFO is empty after %0d reads", DEPTH);
        end 
        else begin
            $display("FAIL: FIFO should be empty after %0d reads", DEPTH);
            error_cnt = error_cnt + 1;
        end

        // Test 3: Alternating read/write
        $display("\nTest 3: Alternating read/write operations");
        repeat(5) begin
            write_data($random);
            #(CLK_PERIOD*2);
            read_data;
            #(CLK_PERIOD*2);
        end

        // Test 4: Write when full
        $display("\nTest 4: Testing write when full condition");
        repeat(DEPTH/2+1) begin
            write_data($random);
            #(CLK_PERIOD);
        end

        // Test 5: Read when empty
        $display("\nTest 5: Testing read when empty condition");
        repeat(DEPTH/2+1) begin
            read_data;
            #(CLK_PERIOD);
        end

        // Test results summary
        $display("\n=== Test Complete ===");
        $display("Total Writes: %0d", write_cnt);
        $display("Total Reads:  %0d", read_cnt);
        $display("Total Errors: %0d", error_cnt);
        
        if (error_cnt == 0) begin
            $display("TEST PASSED!");
        end 
        else begin
            $display("TEST FAILED!");
        end

        #(CLK_PERIOD*5);
        $finish;
    end

    // Watchdog timer
    initial begin
        #10000;
        $display("Timeout Error: Simulation took too long!");
        $finish;
    end
    
	initial begin
    $fsdbDumpfile("tb.fsdb");  // 波形文件名
    $fsdbDumpvars(0);  // 0表示dump所有层级
    $fsdbDumpMDA();  // 专门用于dump内存数组
    $fsdbDumpMemNow;  // 立即dump当前内存内容
    end


endmodule
波形图

image.pngl200

异步 FIFO Verilog 代码

参考博文
如果有问题,欢迎留言

设计思路图

image.pngl200

设计需求
  • 判断 empty 和 full ,wr_ptr 和 rd_ptr 需要过两拍对比
  • 写入和读取 fifo 时使用的地址是 wr_ptr_binary 和 rd_ptr_binary,每次 +1
  • 用来比较 empty 和 full,使用的是 wr_ptr_grey 和 rd_ptr_grey
  • 所有的 ptr 都会多出一位,用来指示 flag
代码
module asynchronous_fifo#(
    parameter   DATA_WIDTH = 8,
    parameter   ADDR_WIDTH = 4,
    parameter   DEPTH = 1 << ADDR_WIDTH
)(
    input                       wclk,
    input                       wen,
    input                       wrst_n,

    input                       rclk,
    input                       ren,
    input                       rrst_n,

    input   [DATA_WIDTH-1:0]    data_in,


    output  reg                       full,
    output  reg                       empty,
    output  reg  [DATA_WIDTH-1:0]     data_out
);
//ptr of binary
    reg    [ADDR_WIDTH:0]     wr_ptr_binary;
    reg    [ADDR_WIDTH:0]     rd_ptr_binary;

//ptr of grey
    wire    [ADDR_WIDTH:0]     wr_ptr_grey;
    wire    [ADDR_WIDTH:0]     rd_ptr_grey;

    reg    [ADDR_WIDTH:0]     wr_ptr_next;
    reg    [ADDR_WIDTH:0]     rd_ptr_next;

    reg    [ADDR_WIDTH:0]     wr_ptr_next2;
    reg    [ADDR_WIDTH:0]     rd_ptr_next2;

    reg    [DATA_WIDTH-1:0]     ram[DEPTH-1:0];    

    always @(posedge wclk or negedge wrst_n)begin
        if(!wrst_n)
            wr_ptr_binary <= 0;
        else if (!full && wen)
            wr_ptr_binary <= wr_ptr_binary + 1;
    end

    assign wr_ptr_grey = (wr_ptr_binary >> 1) ^ wr_ptr_binary;

    always @(posedge rclk or negedge rrst_n)begin
        if(!rrst_n)
            rd_ptr_binary <= 0;
        else if (!empty && ren)
            rd_ptr_binary <= rd_ptr_binary + 1;
    end

    assign rd_ptr_grey = (rd_ptr_binary >> 1) ^ rd_ptr_binary;


    always @(posedge wclk or negedge wrst_n)begin
        if(!rrst_n) begin
            rd_ptr_next <= 0;
            rd_ptr_next2 <= 0;
        end
        else 
            {rd_ptr_next2,rd_ptr_next} <= {rd_ptr_next,rd_ptr_grey} ;
    end

    always @(posedge rclk or negedge rrst_n)begin
        if(!rrst_n) begin
            wr_ptr_next <= 0;
            wr_ptr_next2 <= 0;
        end
        else 
            {wr_ptr_next2,wr_ptr_next} <= {wr_ptr_next,wr_ptr_grey} ;
    end


    assign empty = wr_ptr_next2[ADDR_WIDTH:0] == rd_ptr_grey[ADDR_WIDTH:0];
    assign full  = (rd_ptr_next2[ADDR_WIDTH:ADDR_WIDTH-1] ^ wr_ptr_grey[ADDR_WIDTH:ADDR_WIDTH-1]) && (rd_ptr_grey[ADDR_WIDTH-2:0] == wr_ptr_next2[ADDR_WIDTH-2:0]);
    
    always @(posedge wclk )begin
        if(wen && !full)
            ram[wr_ptr_binary[ADDR_WIDTH-1:0]] <= data_in;
    end

    always @(posedge rclk )begin
        if(ren && !empty)
            data_out <= ram[rd_ptr_binary[ADDR_WIDTH-1:0]];
    end


endmodule
Testbech
module async_fifo_tb;

// Parameters
parameter DATA_WIDTH = 8;
parameter ADDR_WIDTH = 4;
parameter DEPTH = 1 << ADDR_WIDTH;
parameter WR_CLK_PERIOD = 10;  // 100MHz
parameter RD_CLK_PERIOD = 8;   // 125MHz

// Signals
reg                     wclk;
reg                     wen;
reg                     wrst_n;
reg                     rclk;
reg                     ren;
reg                     rrst_n;
reg  [DATA_WIDTH-1:0]   data_in;
wire                    full;
wire                    empty;
wire [DATA_WIDTH-1:0]   data_out;

// Instantiate DUT
asynchronous_fifo #(
    .DATA_WIDTH(DATA_WIDTH),
    .ADDR_WIDTH(ADDR_WIDTH),
    .DEPTH(DEPTH)
) dut (
    .wclk(wclk),
    .wen(wen),
    .wrst_n(wrst_n),
    .rclk(rclk),
    .ren(ren),
    .rrst_n(rrst_n),
    .data_in(data_in),
    .full(full),
    .empty(empty),
    .data_out(data_out)
);

// Clock generation
initial begin
    wclk = 0;
    forever #(WR_CLK_PERIOD/2) wclk = ~wclk;
end

initial begin
    rclk = 0;
    forever #(RD_CLK_PERIOD/2) rclk = ~rclk;
end

// Test stimulus
initial begin
    // Initialize signals
    wen = 0;
    wrst_n = 1;
    ren = 0;
    rrst_n = 1;
    data_in = 0;
    
    // Reset sequence
    #100;
    wrst_n = 0;
    rrst_n = 0;
    #100;
    wrst_n = 1;
    rrst_n = 1;
    #100;

    // Test case 1: Write operation
    $display("Test case 1: Writing data to FIFO");
    repeat(8) @(posedge wclk) begin
        wen = 1;
        data_in = $random;
        $display("Writing data: %h", data_in);
    end
    wen = 0;

    // Wait for synchronization
    #200;

    // Test case 2: Read operation
    $display("Test case 2: Reading data from FIFO");
    repeat(8) @(posedge rclk) begin
        ren = 1;
        //@(posedge rclk);
        $display("Read data: %h", data_out);
    end
    ren = 0;

    // Test case 3: Full condition test
    $display("Test case 3: Testing full condition");
    wen = 1;
    repeat(DEPTH+2) @(posedge wclk) begin
        data_in = $random;
        if (full)
            $display("FIFO is full at count %d", $time);
    end
    wen = 0;

    // Test case 4: Empty condition test
    $display("Test case 4: Testing empty condition");
    ren = 1;
    repeat(DEPTH+2) @(posedge rclk) begin
        if (empty)
            $display("FIFO is empty at count %d", $time);
    end
    ren = 0;

    // Test case 5: Simultaneous read/write
    $display("Test case 5: Simultaneous read/write operations");
    fork
        begin
            repeat(5) @(posedge wclk) begin
                wen = 1;
                data_in = $random;
                $display("Writing data: %h", data_in);
            end
            wen = 0;
        end
        begin
            repeat(5) @(posedge rclk) begin
                ren = 1;
                @(posedge rclk);
                $display("Read data: %h", data_out);
            end
            ren = 0;
        end
    join

    // End simulation
    #1000;
    $display("Simulation completed successfully");
    $finish;
end

// Monitor for full/empty conditions
always @(posedge full)
    $display("FIFO full detected at time %d", $time);

always @(posedge empty)
    $display("FIFO empty detected at time %d", $time);

initial begin
    $fsdbDumpfile("tb.fsdb");  // 波形文件名
    $fsdbDumpvars(0);  // 0表示dump所有层级
    $fsdbDumpMDA();  // 专门用于dump内存数组
    $fsdbDumpMemNow;  // 立即dump当前内存内容
    end


endmodule

波形图

image.pngl200

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值