先进先出 (FIFO) 是一种非常流行且有用的设计块,用于模块之间的同步和握手机制。
FIFO 的深度: FIFO 中的槽数或行数称为 FIFO 的深度。
FIFO 的宽度:每个槽或行中可以存储的位数称为 FIFO 的宽度。
在同步 FIFO 中,数据读取和写入操作使用相同的时钟频率。通常,它们与高时钟频率一起使用以支持高速系统。
wr_en: 写使能
wr_data: 写数据write data
full: FIFO is full 满
empty: FIFO is empty 空
rd_en: 读使能 read enable
rd_data: 读数据read data
w_ptr: 写指针write pointer
r_ptr: 读指针 read pointer
FIFO 可以根据 wr_en 信号在时钟的每个 posege 存储/写入 wr_data,直到满为止。每次将数据写入 FIFO 存储器时,写入指针都会递增。
根据rd_en信号,可以在时钟的每个周期从FIFO中取出或读取数据,直到为空为止。每次从 FIFO 存储器读取数据时,读指针都会递增。
w_ptr == r_ptr 即写指针和读指针具有相同的值。
满的情况意味着 FIFO 中的每个槽都被占用,但是 w_ptr 和 r_ptr 将再次具有相同的值。因此,无法确定是满状态还是空状态。因此,故意将 FIFO 的最后一个槽保持为空,满条件可以写为 (w_ptr+1’b1) == r_ptr)
module synchronous_fifo #(parameter DEPTH=8, DATA_WIDTH=8) (
input clk, rst_n,
input w_en, r_en,
input [DATA_WIDTH-1:0] data_in,
output reg [DATA_WIDTH-1:0] data_out,
output full, empty
);
reg [$clog2(DEPTH)-1:0] w_ptr, r_ptr;
reg [DATA_WIDTH-1:0] fifo[DEPTH];
// Set Default values on reset.
always@(posedge clk) begin
if(!rst_n) begin
w_ptr <= 0; r_ptr <= 0;
data_out <= 0;
end
end
// To write data to FIFO
always@(posedge clk) begin
if(w_en & !full)begin
fifo[w_ptr] <= data_in;
w_ptr <= w_ptr + 1;
end
end
// To read data from FIFO
always@(posedge clk) begin
if(r_en & !empty) begin
data_out <= fifo[r_ptr];
r_ptr <= r_ptr + 1;
end
end
assign full = ((w_ptr+1'b1) == r_ptr);
assign empty = (w_ptr == r_ptr);
endmodule
module sync_fifo_TB;
parameter DATA_WIDTH = 8;
reg clk, rst_n;
reg w_en, r_en;
reg [DATA_WIDTH-1:0] data_in;
wire [DATA_WIDTH-1:0] data_out;
wire full, empty;
// Queue to push data_in
reg [DATA_WIDTH-1:0] wdata_q[$], wdata;
synchronous_fifo s_fifo(clk, rst_n, w_en, r_en, data_in, data_out, full, empty);
always #5ns clk = ~clk;
initial begin
clk = 1'b0; rst_n = 1'b0;
w_en = 1'b0;
data_in = 0;
repeat(10) @(posedge clk);
rst_n = 1'b1;
repeat(2) begin
for (int i=0; i<30; i++) begin
@(posedge clk);
w_en = (i%2 == 0)? 1'b1 : 1'b0;
if (w_en & !full) begin
data_in = $urandom;
wdata_q.push_back(data_in);
end
end
#50;
end
end
initial begin
clk = 1'b0; rst_n = 1'b0;
r_en = 1'b0;
repeat(20) @(posedge clk);
rst_n = 1'b1;
repeat(2) begin
for (int i=0; i<30; i++) begin
@(posedge clk);
r_en = (i%2 == 0)? 1'b1 : 1'b0;
if (r_en & !empty) begin
#1;
wdata = wdata_q.pop_front();
if(data_out !== wdata) $error("Time = %0t: Comparison Failed: expected wr_data = %h, rd_data = %h", $time, wdata, data_out);
else $display("Time = %0t: Comparison Passed: wr_data = %h and rd_data = %h",$time, wdata, data_out);
end
end
#50;
end
$finish;
end
initial begin
$dumpfile("dump.vcd"); $dumpvars;
end
endmodule
count == 0 i.e. FIFO contains nothing.
count == FIFO_DEPTH i.e. counter value has reached till the depth of FIFO
w_ptr == r_ptr i.e. write and read pointers has the same value. MSB of w_ptr and r_ptr also has the same value.
w_ptr == r_ptr i.e. write and read pointers has the same value, but the MSB of w_ptr and r_ptr differs.
在异步 FIFO 中,数据读取和写入操作使用不同的时钟频率。由于写入和读取时钟不同步,因此称为异步 FIFO。通常,这些用于数据需要从一个时钟域传递到另一个时钟域的系统,这通常被称为“时钟域交叉”。因此,异步 FIFO 有助于同步工作在不同时钟上的两个系统之间的数据流。
wr_en: 写使能write enable
wr_data: 写数据write data
full: FIFO is full满
empty: FIFO is empty 空
rd_en: read enable 读使能
rd_data: read data 读数据
b_wptr: binary write pointer 二进制写指针
g_wptr: gray write pointer 格雷码写指针
b_wptr_next: binary write pointer next 指示用于二进制写入新数据的下一个可用地址。
g_wptr_next: gray write pointer next 指示用于格雷码写入新数据的下一个可用地址。
b_rptr: binary read pointer 二进制读指针
g_rptr: gray read pointer 格雷码读指针
b_rptr_next: binary read pointer next 指示用于二进制读新数据的下一个可用地址。
g_rptr_next: gray read pointer next 指示用于格雷码读入新数据的下一个可用地址。
b_rptr_sync: binary read pointer synchronized
b_wptr_sync: binary write pointer synchronized
同步必要性:
在同步 FIFO 的情况下,写入和读取指针在同一时钟上生成。然而,在异步 FIFO 的情况下,写指针与写时钟域对齐,而读指针与读时钟域对齐。因此,需要跨域来计算 FIFO 满和空的情况。这会导致实际设计中的亚稳态。为了解决这种亚稳态问题,可以使用2个触发器或3个触发器同步器来传递写入和读取指针。为了便于解释,我们将使用 2 个触发器同步器。请注意,单个“2 FF 同步器”只能解决一位的亚稳态问题。因此,根据写入和读取指针,需要多个 2FF 同步器。
module synchronizer #(parameter WIDTH=3) (input clk, rst_n, [WIDTH:0] d_in, output reg [WIDTH:0] d_out);
reg [WIDTH:0] q1;
always@(posedge clk) begin
if(!rst_n) begin
q1 <= 0;
d_out <= 0;
end
else begin
q1 <= d_in;
d_out <= q1;
end
end
endmodule
到目前为止,我们讨论了如何在各个时钟域中获取异步写入和读取指针。但是,我们不应该传递二进制格式的写入和读取指针值。由于亚稳态,整体写入或读取指针值可能不同。
示例:当写时钟域的二进制值 wr_ptr = 4’b1101 通过 2FF 同步器传输时,读时钟域的 wr_ptr 值可能会接收为 4’b1111 或任何其他不可接受的值。而格雷码则保证与之前的值相比只有一位变化。因此,写指针和读指针都需要首先转换为其相应域中的等效格雷码,然后将它们传递到相反的域。要检查另一个域中 FIFO 满和空的情况,我们有两种方法。
将接收到的格雷码格式的指针转换为二进制格式,然后检查满和空的情况。
g2b_converter g2b_wr(g_rptr_sync, b_rptr_sync);
wrap_around = b_rptr_sync[PTR_WIDTH] ^ b_wptr[PTR_WIDTH];
wfull = wrap_around & (b_wptr[PTR_WIDTH-1:0] == b_rptr_sync[PTR_WIDTH-1:0]);
g2b_converter g2b_rd(g_wptr_sync, b_wptr_sync);
rempty = (b_wptr_sync == b_rptr_next);
借助接收到的格雷码写入和读取指针直接检查满和空状况。这是高效的,因为它不需要额外的硬件来将格雷码写入和读取指针转换为等效的二进制形式。
wfull = (g_wptr_next == {~g_rptr_sync[PTR_WIDTH:PTR_WIDTH-1], g_rptr_sync[PTR_WIDTH-2:0]});
rempty = (g_wptr_sync == g_rptr_next);
同步器 g_rptr_sync 的输出作为“写指针处理程序”模块的输入,用于生成 FIFO 满状态。如果二进制写指针 (b_wptr) 满足 (w_en & !full) 条件,则它会递增。该 b_wptr 值被馈送到 fifo_mem 模块以将数据写入 FIFO。
module wptr_handler #(parameter PTR_WIDTH=3) (
input wclk, wrst_n, w_en,
input [PTR_WIDTH:0] g_rptr_sync,
output reg [PTR_WIDTH:0] b_wptr, g_wptr,
output reg full
);
reg [PTR_WIDTH:0] b_wptr_next;
reg [PTR_WIDTH:0] g_wptr_next;
reg wrap_around;
wire wfull;
assign b_wptr_next = b_wptr+(w_en & !full);
assign g_wptr_next = (b_wptr_next >>1)^b_wptr_next;
always@(posedge wclk or negedge wrst_n) begin
if(!wrst_n) begin
b_wptr <= 0; // set default value
g_wptr <= 0;
end
else begin
b_wptr <= b_wptr_next; // incr binary write pointer
g_wptr <= g_wptr_next; // incr gray write pointer
end
end
always@(posedge wclk or negedge wrst_n) begin
if(!wrst_n) full <= 0;
else full <= wfull;
end
assign wfull = (g_wptr_next == {~g_rptr_sync[PTR_WIDTH:PTR_WIDTH-1], g_rptr_sync[PTR_WIDTH-2:0]});
endmodule
同步器 g_wptr_sync 的输出作为“读指针处理程序”模块的输入,以生成 FIFO 空条件。如果二进制读指针 (b_rptr) 满足 (r_en & !empty) 条件,则它会递增。该 b_rptr 值被馈送到 fifo_mem 模块以从 FIFO 读取数据。
module rptr_handler #(parameter PTR_WIDTH=3) (
input rclk, rrst_n, r_en,
input [PTR_WIDTH:0] g_wptr_sync,
output reg [PTR_WIDTH:0] b_rptr, g_rptr,
output reg empty
);
reg [PTR_WIDTH:0] b_rptr_next;
reg [PTR_WIDTH:0] g_rptr_next;
assign b_rptr_next = b_rptr+(r_en & !empty);
assign g_rptr_next = (b_rptr_next >>1)^b_rptr_next;
assign rempty = (g_wptr_sync == g_rptr_next);
always@(posedge rclk or negedge rrst_n) begin
if(!rrst_n) begin
b_rptr <= 0;
g_rptr <= 0;
end
else begin
b_rptr <= b_rptr_next;
g_rptr <= g_rptr_next;
end
end
always@(posedge rclk or negedge rrst_n) begin
if(!rrst_n) empty <= 1;
else empty <= rempty;
end
endmodule
基于二进制编码的写和读指针,数据分别写入 FIFO 或从 FIFO 读取。
module fifo_mem #(parameter DEPTH=8, DATA_WIDTH=8, PTR_WIDTH=3) (
input wclk, w_en, rclk, r_en,
input [PTR_WIDTH:0] b_wptr, b_rptr,
input [DATA_WIDTH-1:0] data_in,
input full, empty,
output reg [DATA_WIDTH-1:0] data_out
);
reg [DATA_WIDTH-1:0] fifo[0:DEPTH-1];
always@(posedge wclk) begin
if(w_en & !full) begin
fifo[b_wptr[PTR_WIDTH-1:0]] <= data_in;
end
end
/*
always@(posedge rclk) begin
if(r_en & !empty) begin
data_out <= fifo[b_rptr[PTR_WIDTH-1:0]];
end
end
*/
assign data_out = fifo[b_rptr[PTR_WIDTH-1:0]];
endmodule
`include "synchronizer.v"
`include "wptr_handler.v"
`include "rptr_handler.v"
`include "fifo_mem.v"
module asynchronous_fifo #(parameter DEPTH=8, DATA_WIDTH=8) (
input wclk, wrst_n,
input rclk, rrst_n,
input w_en, r_en,
input [DATA_WIDTH-1:0] data_in,
output reg [DATA_WIDTH-1:0] data_out,
output reg full, empty
);
parameter PTR_WIDTH = $clog2(DEPTH);
reg [PTR_WIDTH:0] g_wptr_sync, g_rptr_sync;
reg [PTR_WIDTH:0] b_wptr, b_rptr;
reg [PTR_WIDTH:0] g_wptr, g_rptr;
wire [PTR_WIDTH-1:0] waddr, raddr;
synchronizer #(PTR_WIDTH) sync_wptr (rclk, rrst_n, g_wptr, g_wptr_sync); //write pointer to read clock domain
synchronizer #(PTR_WIDTH) sync_rptr (wclk, wrst_n, g_rptr, g_rptr_sync); //read pointer to write clock domain
wptr_handler #(PTR_WIDTH) wptr_h(wclk, wrst_n, w_en,g_rptr_sync,b_wptr,g_wptr,full);
rptr_handler #(PTR_WIDTH) rptr_h(rclk, rrst_n, r_en,g_wptr_sync,b_rptr,g_rptr,empty);
fifo_mem fifom(wclk, w_en, rclk, r_en,b_wptr, b_rptr, data_in,full,empty, data_out);
endmodule
module async_fifo_TB;
parameter DATA_WIDTH = 8;
wire [DATA_WIDTH-1:0] data_out;
wire full;
wire empty;
reg [DATA_WIDTH-1:0] data_in;
reg w_en, wclk, wrst_n;
reg r_en, rclk, rrst_n;
// Queue to push data_in
reg [DATA_WIDTH-1:0] wdata_q[$], wdata;
asynchronous_fifo as_fifo (wclk, wrst_n,rclk, rrst_n,w_en,r_en,data_in,data_out,full,empty);
always #10ns wclk = ~wclk;
always #35ns rclk = ~rclk;
initial begin
wclk = 1'b0; wrst_n = 1'b0;
w_en = 1'b0;
data_in = 0;
repeat(10) @(posedge wclk);
wrst_n = 1'b1;
repeat(2) begin
for (int i=0; i<30; i++) begin
@(posedge wclk iff !full);
w_en = (i%2 == 0)? 1'b1 : 1'b0;
if (w_en) begin
data_in = $urandom;
wdata_q.push_back(data_in);
end
end
#50;
end
end
initial begin
rclk = 1'b0; rrst_n = 1'b0;
r_en = 1'b0;
repeat(20) @(posedge rclk);
rrst_n = 1'b1;
repeat(2) begin
for (int i=0; i<30; i++) begin
@(posedge rclk iff !empty);
r_en = (i%2 == 0)? 1'b1 : 1'b0;
if (r_en) begin
wdata = wdata_q.pop_front();
if(data_out !== wdata) $error("Time = %0t: Comparison Failed: expected wr_data = %h, rd_data = %h", $time, wdata, data_out);
else $display("Time = %0t: Comparison Passed: wr_data = %h and rd_data = %h",$time, wdata, data_out);
end
end
#50;
end
$finish;
end
initial begin
$dumpfile("dump.vcd"); $dumpvars;
end
endmodule