【RTL中的FIFO与Buffer的区别】

FIFO的RTL实现

FIFO(先进先出队列)在RTL中通常使用环形缓冲区或双端口存储器来实现。FIFO有两个主要指针:写指针(write pointer)和读指针(read pointer)。写指针指示下一个写入数据的位置,读指针指示下一个读取数据的位置。

使用Verilog实现同步FIFO

简单的同步FIFO实现:

module fifo #(parameter WIDTH = 8, DEPTH = 16)(
    input clk,
    input rst,
    input wr_en,
    input rd_en,
    input [WIDTH-1:0] data_in,
    output reg [WIDTH-1:0] data_out,
    output reg full,
    output reg empty
);
    reg [WIDTH-1:0] fifo_mem [0:DEPTH-1];
    reg [3:0] wr_ptr;
    reg [3:0] rd_ptr;
    reg [4:0] fifo_count;

    always @(posedge clk or posedge rst) begin
        if (rst) begin
            wr_ptr <= 0;
            rd_ptr <= 0;
            fifo_count <= 0;
            full <= 0;
            empty <= 1;
        end else begin
            if (wr_en && !full) begin
                fifo_mem[wr_ptr] <= data_in;
                wr_ptr <= wr_ptr + 1;
                fifo_count <= fifo_count + 1;
            end
            if (rd_en && !empty) begin
                data_out <= fifo_mem[rd_ptr];
                rd_ptr <= rd_ptr + 1;
                fifo_count <= fifo_count - 1;
            end
            full <= (fifo_count == DEPTH);
            empty <= (fifo_count == 0);
        end
    end
endmodule

优点

  • 顺序性:FIFO保证数据以先进先出的顺序处理,适合流式数据处理。
  • 自适应性:可以很容易地适应不同的数据速率,适合异步数据传输。
  • 流量控制:天然支持流量控制机制(反压)。
    缺点:
  • 复杂性:实现较复杂,需要管理读写指针和状态信号(如full和empty)。
  • 资源消耗:对于较大深度的FIFO,存储器资源消耗较大。

使用Verilog实现异步FIFO

异步FIFO(Asynchronous FIFO)需要确保读写指针在不同时钟域之间正确同步。使用格雷码(Gray Code)编码方式可以减少在指针同步过程中出现的亚稳态问题。

module async_fifo #(parameter DATA_WIDTH = 8, ADDR_WIDTH = 4)(
    input wire wr_clk,
    input wire wr_rst,
    input wire wr_en,
    input wire [DATA_WIDTH-1:0] wr_data,
    input wire rd_clk,
    input wire rd_rst,
    input wire rd_en,
    output reg [DATA_WIDTH-1:0] rd_data,
    output wire full,
    output wire empty
);

    // FIFO Memory
    reg [DATA_WIDTH-1:0] fifo_mem [0:(1<<ADDR_WIDTH)-1];

    // Write Pointer and Gray Code
    reg [ADDR_WIDTH:0] wr_ptr_bin, wr_ptr_gray, wr_ptr_gray_sync1, wr_ptr_gray_sync2;
    reg [ADDR_WIDTH:0] rd_ptr_bin, rd_ptr_gray, rd_ptr_gray_sync1, rd_ptr_gray_sync2;

    // Write Pointer Binary to Gray Code Conversion
    always @(posedge wr_clk or posedge wr_rst) begin
        if (wr_rst) begin
            wr_ptr_bin <= 0;
            wr_ptr_gray <= 0;
        end else if (wr_en && !full) begin
            wr_ptr_bin <= wr_ptr_bin + 1;
            wr_ptr_gray <= (wr_ptr_bin + 1) ^ ((wr_ptr_bin + 1) >> 1);
            fifo_mem[wr_ptr_bin[ADDR_WIDTH-1:0]] <= wr_data;
        end
    end

    // Read Pointer Binary to Gray Code Conversion
    always @(posedge rd_clk or posedge rd_rst) begin
        if (rd_rst) begin
            rd_ptr_bin <= 0;
            rd_ptr_gray <= 0;
        end else if (rd_en && !empty) begin
            rd_ptr_bin <= rd_ptr_bin + 1;
            rd_ptr_gray <= (rd_ptr_bin + 1) ^ ((rd_ptr_bin + 1) >> 1);
            rd_data <= fifo_mem[rd_ptr_bin[ADDR_WIDTH-1:0]];
        end
    end

    // Synchronize Write Pointer to Read Clock Domain
    always @(posedge rd_clk or posedge rd_rst) begin
        if (rd_rst) begin
            wr_ptr_gray_sync1 <= 0;
            wr_ptr_gray_sync2 <= 0;
        end else begin
            wr_ptr_gray_sync1 <= wr_ptr_gray;
            wr_ptr_gray_sync2 <= wr_ptr_gray_sync1;
        end
    end

    // Synchronize Read Pointer to Write Clock Domain
    always @(posedge wr_clk or posedge wr_rst) begin
        if (wr_rst) begin
            rd_ptr_gray_sync1 <= 0;
            rd_ptr_gray_sync2 <= 0;
        end else begin
            rd_ptr_gray_sync1 <= rd_ptr_gray;
            rd_ptr_gray_sync2 <= rd_ptr_gray_sync1;
        end
    end

    // Gray Code to Binary Conversion for Write Pointer in Read Clock Domain
    wire [ADDR_WIDTH:0] wr_ptr_bin_sync = gray_to_bin(wr_ptr_gray_sync2);

    // Gray Code to Binary Conversion for Read Pointer in Write Clock Domain
    wire [ADDR_WIDTH:0] rd_ptr_bin_sync = gray_to_bin(rd_ptr_gray_sync2);

    // Full and Empty Flag Generation
    assign full = (wr_ptr_gray == {~rd_ptr_bin_sync[ADDR_WIDTH:ADDR_WIDTH-1], rd_ptr_bin_sync[ADDR_WIDTH-2:0]});
    assign empty = (wr_ptr_gray_sync2 == rd_ptr_gray);

    // Gray Code to Binary Conversion Function
    function [ADDR_WIDTH:0] gray_to_bin;
        input [ADDR_WIDTH:0] gray;
        integer i;
        begin
            gray_to_bin[ADDR_WIDTH] = gray[ADDR_WIDTH];
            for (i = ADDR_WIDTH-1; i >= 0; i = i - 1) begin
                gray_to_bin[i] = gray_to_bin[i+1] ^ gray[i];
            end
        end
    endfunction
endmodule

说明

  1. FIFO Memory
    使用fifo_mem定义FIFO的存储器阵列。
  2. 写指针和格雷码转换:
  • wr_ptr_bin为写指针的二进制表示。
  • wr_ptr_gray为写指针的格雷码表示。
  • 写入时,写指针按顺序递增,并将数据写入fifo_mem。
  1. 读指针和格雷码转换:
  • rd_ptr_bin为读指针的二进制表示。
  • rd_ptr_gray为读指针的格雷码表示。
  • 读取时,读指针按顺序递增,并从fifo_mem读取数据。
  1. 指针同步:
  • wr_ptr_gray_sync1和wr_ptr_gray_sync2用于将写指针同步到读时钟域。
  • rd_ptr_gray_sync1和rd_ptr_gray_sync2用于将读指针同步到写时钟域。
  1. 灰码到二进制转换:
  • 使用gray_to_bin函数将同步后的格雷码指针转换为二进制表示。
  1. 满和空标志:
  • full标志指示FIFO是否已满。
  • empty标志指示FIFO是否为空。

格雷码与二进制码之间的转换

  • 格雷码到二进制码转换
    格雷码到二进制码的转换基于以下公式:
    B [ n ] = G [ n ] B [ n − 1 ] = B [ n ] ⊕ G [ n − 1 ] … B [ 0 ] = B [ 1 ] ⊕ G [ 0 ] \begin{aligned} B[n] &= G[n] \\ B[n-1] &= B[n] \oplus G[n-1] \\ &\dots \\ B[0] &= B[1] \oplus G[0] \end{aligned} B[n]B[n1]B[0]=G[n]=B[n]G[n1]=B[1]G[0]
    其中:
  • 𝐵是二进制码
  • 𝐺是格雷码
  • ⊕表示异或操作

具体来说,从最高位开始,格雷码的最高位直接作为二进制码的最高位。随后,格雷码的每一位与之前计算得到的二进制码的对应位异或,得到当前位的二进制码。

格雷码到二进制码

Verilog代码实现

function [ADDR_WIDTH:0] gray_to_bin;
    input [ADDR_WIDTH:0] gray;
    integer i;
    begin
        gray_to_bin[ADDR_WIDTH] = gray[ADDR_WIDTH]; // Highest bit is the same
        for (i = ADDR_WIDTH-1; i >= 0; i = i - 1) begin
            gray_to_bin[i] = gray_to_bin[i+1] ^ gray[i]; // XOR each bit with the next higher bit
        end
    end
endfunction

  • 二进制码到格雷码转换

二进制码到格雷码的转换基于以下公式:
G [ n ] = B [ n ] G [ n − 1 ] = B [ n ] ⊕ B [ n − 1 ] … G [ 0 ] = B [ 1 ] ⊕ B [ 0 ] \begin{aligned} G[n] &= B[n] \\ G[n-1] &= B[n] \oplus B[n-1] \\ &\dots \\ G[0] &= B[1] \oplus B[0] \end{aligned} G[n]G[n1]G[0]=B[n]=B[n]B[n1]=B[1]B[0]
具体来说,从最高位开始,二进制码的最高位直接作为格雷码的最高位。随后,二进制码的每一位与它前一位进行异或,得到当前位的格雷码。
二进制码到格雷码
代码实现
在代码中,写指针和读指针的二进制码转换成格雷码如下:

always @(posedge wr_clk or posedge wr_rst) begin
    if (wr_rst) begin
        wr_ptr_bin <= 0;
        wr_ptr_gray <= 0;
    end else if (wr_en && !full) begin
        wr_ptr_bin <= wr_ptr_bin + 1;
        wr_ptr_gray <= (wr_ptr_bin + 1) ^ ((wr_ptr_bin + 1) >> 1);
        //fifo_mem[wr_ptr_bin[ADDR_WIDTH-1:0]] <= wr_data;
    end
end

always @(posedge rd_clk or posedge rd_rst) begin
    if (rd_rst) begin
        rd_ptr_bin <= 0;
        rd_ptr_gray <= 0;
    end else if (rd_en && !empty) begin
        rd_ptr_bin <= rd_ptr_bin + 1;
        rd_ptr_gray <= (rd_ptr_bin + 1) ^ ((rd_ptr_bin + 1) >> 1);
        //rd_data <= fifo_mem[rd_ptr_bin[ADDR_WIDTH-1:0]];
    end
end

wr_ptr_bin + 1rd_ptr_bin + 1 表示指针递增之后的新位置,然后我们将这个新位置转换为格雷码,确保指针同步过程中的稳定性。
这个操作确保了在每次写入操作后,指针正确递增并转换为格雷码,用于同步操作。而不直接用当前指针进行格雷码转换,因为我们在更新指针时需要指向下一个位置。

  • 保证指针的递增顺序。
  • 避免同步过程中亚稳态的问题。
  • 符合异步FIFO的设计逻辑,使得读写操作在不同时钟域下能正确运行。

FIFO空满判断逻辑的原理

FIFO的空满状态由读写指针的位置决定。在异步FIFO中,通过将读指针同步到写时钟域、写指针同步到读时钟域,来进行空满状态的判断。

FIFO满判断逻辑

FIFO满的条件是,【写满】在写时钟域,当写指针即将追上读指针,且指针的高位(用于区分周数)不同。具体条件是:
full = ( wr_ptr_gray = = { ∼ rd_ptr_bin_sync [ A D D R _ W I D T H : A D D R _ W I D T H − 1 ] , rd_ptr_bin_sync [ A D D R _ W I D T H − 2 : 0 ] } ) \begin{aligned} \text{full} &= (\text{wr\_ptr\_gray} == \{ \sim \text{rd\_ptr\_bin\_sync}[ADDR\_WIDTH:ADDR\_WIDTH-1], \text{rd\_ptr\_bin\_sync}[ADDR\_WIDTH-2:0] \}) \\ \end{aligned} full=(wr_ptr_gray=={rd_ptr_bin_sync[ADDR_WIDTH:ADDR_WIDTH1],rd_ptr_bin_sync[ADDR_WIDTH2:0]})

FIFO空判断逻辑

FIFO空的条件是,【读空】在读时钟域,当写指针与读指针相等。具体条件是:
empty = ( wr_ptr_gray_sync2 = = rd_ptr_gray ) \begin{aligned} \text{empty} &= (\text{wr\_ptr\_gray\_sync2} == \text{rd\_ptr\_gray}) \\ \end{aligned} empty=(wr_ptr_gray_sync2==rd_ptr_gray)

格雷码到二进制码转换:通过将格雷码的最高位直接作为二进制码的最高位,随后逐位进行异或操作完成转换。
二进制码到格雷码转换:通过将二进制码的最高位直接作为格雷码的最高位,随后逐位与前一位进行异或操作完成转换。
FIFO满判断:通过比较写指针的格雷码和读指针的格雷码(同步到写时钟域),判断FIFO是否满。
FIFO空判断:通过比较写指针的格雷码(同步到读时钟域)和读指针的格雷码,判断FIFO是否空。

Buffer的RTL实现

Buffer通常是一个简单的寄存器组或单端口存储器。数据可以按顺序存储,通常没有FIFO那样复杂的控制逻辑。Buffer通常在数据传输中用于临时存储。

使用Verilog实现Buffer

简单的同步Buffer实现:

module buffer #(parameter WIDTH = 8, SIZE = 16)(
    input clk,
    input rst,
    input wr_en,
    input [WIDTH-1:0] data_in,
    output reg [WIDTH-1:0] data_out
);
    reg [WIDTH-1:0] buffer_mem [0:SIZE-1];
    reg [3:0] ptr;

    always @(posedge clk or posedge rst) begin
        if (rst) begin
            ptr <= 0;
        end else if (wr_en) begin
            buffer_mem[ptr] <= data_in;
            ptr <= ptr + 1;
            data_out <= buffer_mem[ptr];
        end
    end
endmodule

优点

  • 简单性:实现简单,控制逻辑相对简单。
  • 低资源消耗:由于控制逻辑简单,资源消耗较少。

缺点

  • 顺序性:没有内建的顺序处理机制,通常需要额外逻辑来管理数据顺序。
  • 流量控制:不支持流量控制,适合用于固定速率的数据流。

FIFO 吸收反压的理论计算

反压时间:
T = D W − R T=\frac{D}{W-R} T=WRD

  • T T T是达到FIFO满的时间
  • D D D是FIFO深度
  • W W W是每秒写入的数据量
  • R R R是每秒读取的数据量
    FIFO适合需要顺序处理和流量控制的场景,而Buffer则适合简单的临时存储和固定速率的数据传输。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值