异步FIFO的FPGA实现

在数字设计中,FIFO(First-In-First-Out) 是一种常见的缓冲器,广泛应用于数据流的缓存、同步等操作。异步FIFO 是一种FIFO队列,它支持在不同的时钟域之间传输数据。由于其不依赖于单一时钟,异步FIFO在FPGA设计中用于处理不同频率的时钟域之间的数据传输。它主要解决了两个时钟域之间的同步问题。

异步FIFO的基本原理

  1. FIFO结构:FIFO的基本结构包括写指针(write pointer)和读指针(read pointer),它们分别控制数据的写入和读取。
  2. 写入和读取:当数据写入时,写指针递增;当数据读取时,读指针递增。FIFO的操作是基于先进先出的原则,数据总是按照进入FIFO的顺序读取出来。
  3. 异步问题:在异步FIFO中,写时钟(write clock)和读时钟(read clock)是独立的,因此,写指针和读指针可能会处于不同的时钟域。为了解决这个问题,需要使用指针同步(pointer synchronization)技术。
  4. 空和满的检测:FIFO通常需要检测的状态,确保在满的情况下不会继续写入,在空的情况下不会继续读取。

异步FIFO的结构

异步FIFO通常包括以下几个部分:

  • 写入端:包含写数据缓冲区、写指针、写时钟。
  • 读取端:包含读数据缓冲区、读指针、读时钟。
  • 指针同步模块:在两个时钟域之间传递读写指针,并进行同步。

FPGA实现异步FIFO

下面是一个简单的异步FIFO的FPGA实现,采用Verilog语言进行描述。

1. FIFO定义

module async_fifo #(
    parameter DATA_WIDTH = 8,      // 数据宽度
    parameter FIFO_DEPTH = 16      // FIFO深度(可以存储多少数据)
)(
    input wire wr_clk,             // 写时钟
    input wire rd_clk,             // 读时钟
    input wire reset,              // 异步复位
    input wire wr_en,              // 写使能
    input wire rd_en,              // 读使能
    input wire [DATA_WIDTH-1:0] wr_data, // 写入数据
    output wire [DATA_WIDTH-1:0] rd_data, // 读取数据
    output wire full,              // FIFO满标志
    output wire empty              // FIFO空标志
);

2. FIFO存储数组

创建一个 FIFO 存储数组,用于存储数据。

    reg [DATA_WIDTH-1:0] fifo_mem [FIFO_DEPTH-1:0]; // FIFO存储器
    reg [FIFO_DEPTH-1:0] wr_ptr, rd_ptr;             // 写指针和读指针
    reg [FIFO_DEPTH:0] fifo_count;                   // FIFO中的数据数量

3. 指针同步

由于写时钟和读时钟是异步的,所以我们需要同步读指针和写指针。

    reg [FIFO_DEPTH-1:0] rd_ptr_sync1, rd_ptr_sync2; // 两级同步读取指针
    reg [FIFO_DEPTH-1:0] wr_ptr_sync1, wr_ptr_sync2; // 两级同步写入指针

    // 写指针同步
    always @(posedge rd_clk or posedge reset) begin
        if (reset) begin
            wr_ptr_sync1 <= 0;
            wr_ptr_sync2 <= 0;
        end else begin
            wr_ptr_sync1 <= wr_ptr;
            wr_ptr_sync2 <= wr_ptr_sync1;
        end
    end

    // 读指针同步
    always @(posedge wr_clk or posedge reset) begin
        if (reset) begin
            rd_ptr_sync1 <= 0;
            rd_ptr_sync2 <= 0;
        end else begin
            rd_ptr_sync1 <= rd_ptr;
            rd_ptr_sync2 <= rd_ptr_sync1;
        end
    end

4. FIFO操作

  • 写操作:在写时钟下,写指针递增,数据被写入FIFO。
  • 读操作:在读时钟下,读指针递增,数据从FIFO中被读取。
    // 写操作
    always @(posedge wr_clk or posedge reset) begin
        if (reset) begin
            wr_ptr <= 0;
            fifo_count <= 0;
        end else if (wr_en && !full) begin
            fifo_mem[wr_ptr] <= wr_data;
            wr_ptr <= wr_ptr + 1;
            fifo_count <= fifo_count + 1;
        end
    end

    // 读操作
    always @(posedge rd_clk or posedge reset) begin
        if (reset) begin
            rd_ptr <= 0;
        end else if (rd_en && !empty) begin
            rd_data <= fifo_mem[rd_ptr];
            rd_ptr <= rd_ptr + 1;
            fifo_count <= fifo_count - 1;
        end
    end

5. FIFO状态控制(满和空)

    assign full = (fifo_count == FIFO_DEPTH);  // FIFO满
    assign empty = (fifo_count == 0);           // FIFO空

6. 模块结束

endmodule

工作原理

  1. 指针同步:由于写时钟和读时钟不同步,我们需要用两级同步器来同步跨时钟域的指针。这可以有效避免不同步导致的数据错误。
  2. 写入和读取:在写时钟下,通过wr_ptr控制数据写入,fifo_count记录FIFO中的数据数量。在读时钟下,通过rd_ptr来读取数据。
  3. 满/空判断:FIFO满的条件是fifo_count == FIFO_DEPTH,而FIFO空的条件是fifo_count == 0。根据这些条件来生成fullempty信号。

优化与注意事项

  1. 指针同步:为了保证数据的正确性,指针同步部分需要设计得非常小心,防止在高速时钟条件下出现竞态条件。
  2. FIFO深度:FIFO的深度直接影响存储器的大小,过大的FIFO会增加硬件资源的消耗。
  3. 时钟频率差异:在异步FIFO中,写时钟和读时钟的频率差异需要考虑,如果频率差异较大,可能会影响数据传输的稳定性。

总结

异步FIFO在FPGA中非常重要,尤其是在处理不同频率的时钟域之间的通信时。通过Verilog实现异步FIFO时,必须关注指针同步、FIFO满/空判断以及数据写入/读取的正确性。以上示例提供了一个基础的实现,可以根据实际需求进一步优化。

以下是完整的 异步FIFO 的 Verilog 代码,已经加入了详细注释和代码说明:

异步FIFO Verilog代码

module async_fifo #(
    parameter DATA_WIDTH = 8,      // 数据宽度
    parameter FIFO_DEPTH = 16      // FIFO深度(可以存储多少数据)
)(
    input wire wr_clk,             // 写时钟
    input wire rd_clk,             // 读时钟
    input wire reset,              // 异步复位
    input wire wr_en,              // 写使能
    input wire rd_en,              // 读使能
    input wire [DATA_WIDTH-1:0] wr_data, // 写入数据
    output reg [DATA_WIDTH-1:0] rd_data, // 读取数据
    output wire full,              // FIFO满标志
    output wire empty              // FIFO空标志
);

    // FIFO存储器
    reg [DATA_WIDTH-1:0] fifo_mem [FIFO_DEPTH-1:0]; // FIFO存储器
    reg [FIFO_DEPTH-1:0] wr_ptr, rd_ptr;             // 写指针和读指针
    reg [FIFO_DEPTH:0] fifo_count;                   // FIFO中的数据数量

    // 用于指针同步的信号
    reg [FIFO_DEPTH-1:0] rd_ptr_sync1, rd_ptr_sync2; // 两级同步读取指针
    reg [FIFO_DEPTH-1:0] wr_ptr_sync1, wr_ptr_sync2; // 两级同步写入指针

    // 写指针同步
    always @(posedge rd_clk or posedge reset) begin
        if (reset) begin
            wr_ptr_sync1 <= 0;
            wr_ptr_sync2 <= 0;
        end else begin
            wr_ptr_sync1 <= wr_ptr;
            wr_ptr_sync2 <= wr_ptr_sync1;
        end
    end

    // 读指针同步
    always @(posedge wr_clk or posedge reset) begin
        if (reset) begin
            rd_ptr_sync1 <= 0;
            rd_ptr_sync2 <= 0;
        end else begin
            rd_ptr_sync1 <= rd_ptr;
            rd_ptr_sync2 <= rd_ptr_sync1;
        end
    end

    // 写操作
    always @(posedge wr_clk or posedge reset) begin
        if (reset) begin
            wr_ptr <= 0;
            fifo_count <= 0;
        end else if (wr_en && !full) begin
            fifo_mem[wr_ptr] <= wr_data;
            wr_ptr <= wr_ptr + 1;
            fifo_count <= fifo_count + 1;
        end
    end

    // 读操作
    always @(posedge rd_clk or posedge reset) begin
        if (reset) begin
            rd_ptr <= 0;
        end else if (rd_en && !empty) begin
            rd_data <= fifo_mem[rd_ptr];
            rd_ptr <= rd_ptr + 1;
            fifo_count <= fifo_count - 1;
        end
    end

    // FIFO状态控制(满和空的判断)
    assign full = (fifo_count == FIFO_DEPTH);  // FIFO满
    assign empty = (fifo_count == 0);           // FIFO空

endmodule

代码解释

1. 模块定义

module async_fifo #(
    parameter DATA_WIDTH = 8,      // 数据宽度
    parameter FIFO_DEPTH = 16      // FIFO深度(可以存储多少数据)
)(
    input wire wr_clk,             // 写时钟
    input wire rd_clk,             // 读时钟
    input wire reset,              // 异步复位
    input wire wr_en,              // 写使能
    input wire rd_en,              // 读使能
    input wire [DATA_WIDTH-1:0] wr_data, // 写入数据
    output reg [DATA_WIDTH-1:0] rd_data, // 读取数据
    output wire full,              // FIFO满标志
    output wire empty              // FIFO空标志
);
  • DATA_WIDTH:数据宽度,表示每个数据的位数。
  • FIFO_DEPTH:FIFO的深度,即FIFO能够存储的最大数据量。
  • wr_clk:写时钟。
  • rd_clk:读时钟。
  • reset:异步复位信号,复位时FIFO状态清零。
  • wr_en:写使能信号,当为1时,可以向FIFO写入数据。
  • rd_en:读使能信号,当为1时,可以从FIFO读取数据。
  • wr_data:写入的数据。
  • rd_data:读取的数据。
  • full:FIFO满的标志。
  • empty:FIFO空的标志。

2. FIFO存储和指针

    reg [DATA_WIDTH-1:0] fifo_mem [FIFO_DEPTH-1:0]; // FIFO存储器
    reg [FIFO_DEPTH-1:0] wr_ptr, rd_ptr;             // 写指针和读指针
    reg [FIFO_DEPTH:0] fifo_count;                   // FIFO中的数据数量
  • fifo_mem:存储FIFO中的数据。
  • wr_ptr:写指针,指示下一个写入位置。
  • rd_ptr:读指针,指示下一个读取位置。
  • fifo_count:FIFO中存储的数据数量。

3. 指针同步

由于写时钟和读时钟是异步的,因此需要将读写指针进行同步:

    reg [FIFO_DEPTH-1:0] rd_ptr_sync1, rd_ptr_sync2; // 两级同步读取指针
    reg [FIFO_DEPTH-1:0] wr_ptr_sync1, wr_ptr_sync2; // 两级同步写入指针

    always @(posedge rd_clk or posedge reset) begin
        if (reset) begin
            wr_ptr_sync1 <= 0;
            wr_ptr_sync2 <= 0;
        end else begin
            wr_ptr_sync1 <= wr_ptr;
            wr_ptr_sync2 <= wr_ptr_sync1;
        end
    end

    always @(posedge wr_clk or posedge reset) begin
        if (reset) begin
            rd_ptr_sync1 <= 0;
            rd_ptr_sync2 <= 0;
        end else begin
            rd_ptr_sync1 <= rd_ptr;
            rd_ptr_sync2 <= rd_ptr_sync1;
        end
    end
  • 通过 两级同步,我们保证了写指针和读指针在跨时钟域时不会产生竞争条件。

4. 写操作

    always @(posedge wr_clk or posedge reset) begin
        if (reset) begin
            wr_ptr <= 0;
            fifo_count <= 0;
        end else if (wr_en && !full) begin
            fifo_mem[wr_ptr] <= wr_data;
            wr_ptr <= wr_ptr + 1;
            fifo_count <= fifo_count + 1;
        end
    end
  • 在写时钟的上升沿,如果 写使能(wr_en 为高且 FIFO不满,则写入数据到FIFO,并更新写指针和FIFO计数。

5. 读操作

    always @(posedge rd_clk or posedge reset) begin
        if (reset) begin
            rd_ptr <= 0;
        end else if (rd_en && !empty) begin
            rd_data <= fifo_mem[rd_ptr];
            rd_ptr <= rd_ptr + 1;
            fifo_count <= fifo_count - 1;
        end
    end
  • 在读时钟的上升沿,如果 读使能(rd_en 为高且 FIFO不为空,则从FIFO读取数据,并更新读指针和FIFO计数。

6. FIFO状态控制(满和空的判断)

    assign full = (fifo_count == FIFO_DEPTH);  // FIFO满
    assign empty = (fifo_count == 0);           // FIFO空
  • full:如果 FIFO中的数据数量 等于 FIFO深度,则FIFO为满。
  • empty:如果 FIFO中的数据数量 为 0,则FIFO为空。

总结

这段代码实现了一个简单的异步FIFO,支持不同频率的写时钟和读时钟。通过两级同步指针来避免不同步带来的问题,并且实现了FIFO的满、空判断。该设计适合FPGA中的跨时钟域数据传输。

你可以根据自己的需求调整 DATA_WIDTH 和 FIFO_DEPTH 参数来适应不同的数据宽度和FIFO容量需求。在实际应用中,你还可以对代码进行优化,比如增加更复杂的状态控制和更高效的指针同步方法。