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
说明
- FIFO Memory:
使用fifo_mem定义FIFO的存储器阵列。 - 写指针和格雷码转换:
- wr_ptr_bin为写指针的二进制表示。
- wr_ptr_gray为写指针的格雷码表示。
- 写入时,写指针按顺序递增,并将数据写入fifo_mem。
- 读指针和格雷码转换:
- rd_ptr_bin为读指针的二进制表示。
- rd_ptr_gray为读指针的格雷码表示。
- 读取时,读指针按顺序递增,并从fifo_mem读取数据。
- 指针同步:
- wr_ptr_gray_sync1和wr_ptr_gray_sync2用于将写指针同步到读时钟域。
- rd_ptr_gray_sync1和rd_ptr_gray_sync2用于将读指针同步到写时钟域。
- 灰码到二进制转换:
- 使用gray_to_bin函数将同步后的格雷码指针转换为二进制表示。
- 满和空标志:
- 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[n−1]B[0]=G[n]=B[n]⊕G[n−1]…=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[n−1]G[0]=B[n]=B[n]⊕B[n−1]…=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 + 1
与rd_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_WIDTH−1],rd_ptr_bin_sync[ADDR_WIDTH−2: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=W−RD
- T T T是达到FIFO满的时间
- D D D是FIFO深度
- W W W是每秒写入的数据量
-
R
R
R是每秒读取的数据量
FIFO适合需要顺序处理和流量控制的场景,而Buffer则适合简单的临时存储和固定速率的数据传输。