异步FIFO
FIFO简介
FIFO(First In First Out )是一种先进先出的数据缓存器,它与普通存储器的区别是没有外部读写地址线,这样使用起来非常简单,但缺点就是只能顺序写入数据,顺序的读出数据,其数据地址由内部读写指针自动加1完成,不能像普通存储器那样可以由地址线决定读取或写入某个指定的地址。
同步FIFO是指读时钟和写时钟为同一个时钟;
异步FIFO是指读写时钟不一致,读写时钟是互相独立的。异步FIFO常用于多bit数据的跨时钟域。
异步FIFO空满检测
空满状态是使用格雷码跨时钟域来判断:
(由于格雷码每次只有1位变化,所以可以使用double clock的方式来跨时钟域,但是会因此产生状态判断的延迟)
满标志位在写时钟域判断,因为要反馈给写数据端
空标志位在读时钟域判断,因为要反馈给读数据端
其中格雷码的位宽是ADDR_WIDTH+1的:(是为了判断满状态)
以下面的格雷码为例,假如ADDR_WIDTH为3,读指针为0000当写指针为1100时所有地址对应的数据都被写入了(写入了一圈),则FIFO满,当读指针为其他值时同理。
异步FIFO最小深度
异步FIFO实例
模块中使用到了 RTL设计(2)- 双口RAM。
asyncfifo.v
`timescale 1ns / 1ps
// Company:
// Engineer:
//
// Create Date: 2020/12/12
// Author Name: Sniper
// Module Name: asyncfifo
// Project Name:
// Target Devices:
// Tool Versions:
// Description:
//
// Dependencies:
//
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
//
module asyncfifo
#(
parameter DATA_WIDTH = 8,
parameter DATA_DEPTH = 256
)
(
input rst_n,
//write
input wr_clk,
input wr_en,
input [DATA_WIDTH-1:0] wr_data,
output full,
//read
input rd_clk,
input rd_en,
output reg rd_valid,
output [DATA_WIDTH-1:0] rd_data,
output empty
);
localparam ADDR_WIDTH = $clog2(DATA_DEPTH);
//reg
reg [ADDR_WIDTH:0] wr_addr_ptr;//add one extra MSB for check full or empty
reg [ADDR_WIDTH:0] rd_addr_ptr;
reg [ADDR_WIDTH:0] wr_addr_gray_d1;//use for CDC
reg [ADDR_WIDTH:0] wr_addr_gray_d2;
reg [ADDR_WIDTH:0] rd_addr_gray_d1;
reg [ADDR_WIDTH:0] rd_addr_gray_d2;
reg [DATA_WIDTH-1:0] FIFO_RAM [DATA_DEPTH-1:0];
//wire
wire [ADDR_WIDTH-1:0] wr_addr;//RAM present address
wire [ADDR_WIDTH-1:0] rd_addr;
wire [ADDR_WIDTH:0] wr_addr_gray;//gray code corresponding to the address pointer
wire [ADDR_WIDTH:0] rd_addr_gray;
//assign
assign wr_addr = wr_addr_ptr[ADDR_WIDTH-1-:ADDR_WIDTH];//bits from ADDR_WIDTH-1 counting ADDR_WIDTH number bits
assign rd_addr = rd_addr_ptr[ADDR_WIDTH-1-:ADDR_WIDTH];
assign wr_addr_gray = (wr_addr_ptr >> 1) ^ wr_addr_ptr;//translate gray code in order to reduce Metastable state
assign rd_addr_gray = (rd_addr_ptr >> 1) ^ rd_addr_ptr;
/*full : highest two bits different : wr over number */
/*empty: addr equal **********************************/
//full flag in write clock domain
assign full = (wr_addr_gray == {
~(rd_addr_gray_d2[ADDR_WIDTH-:2]),rd_addr_gray_d2[ADDR_WIDTH-2:0]}) ;
//empty flag in read clock domain
assign empty = ( rd_addr_gray == wr_addr_gray_d2 );
//rd_valid delay 1 cycle
always@(posedge rd_clk or negedge rst_n)
begin
if(!rst_n)
rd_valid <= 1'b0;
else if(rd_en && (~empty))
rd_valid <= 1'b1;
else
rd_valid <= 1'b0;
end
//write addr ptr control
always@(posedge wr_clk or negedge rst_n)
begin
if(!rst_n)
wr_addr_ptr <= 'h0;
else if(wr_en && (~full))
wr_addr_ptr <= wr_addr_ptr + 1;
else
wr_addr_ptr <= wr_addr_ptr;
end
//read addr ptr control
always@(posedge rd_clk or negedge rst_n)
begin
if(!rst_n)
rd_addr_ptr <= 'h0;
else if(rd_en && (~empty))
rd_addr_ptr <= rd_addr_ptr + 1;
else
rd_addr_ptr <= rd_addr_ptr;
end
/*****CLock Domain Crossing*****/
//gray code addr buff
always@(posedge rd_clk )
begin
wr_addr_gray_d1 <= wr_addr_gray;
wr_addr_gray_d2 <= wr_addr_gray_d1;
end
//gray code addr buff
always@(posedge wr_clk )
begin
rd_addr_gray_d1 <= rd_addr_gray;
rd_addr_gray_d2 <= rd_addr_gray_d1;
end
dualram
#(
.WIDTH(DATA_WIDTH),
.DEPTH(DATA_DEPTH)
)
u_dualram
(
.wr_clk(wr_clk),
.wr_addr(wr_addr),
.wr_data(wr_data),
.wr_en(wr_en),
.rd_clk(rd_clk),
.rd_addr(rd_addr),
.rd_data(rd_data),
.rd_en(rd_en)
);
endmodule
tb_asyncfifo.v
`timescale 1ns / 1ps
// Company:
// Engineer:
//
// Create Date: 2020/12/12
// Author Name: Sniper
// Module Name: tb_asyncfifo
// Project Name:
// Target Devices:
// Tool Versions:
// Description:
//
// Dependencies:
//
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
//
module tb_asyncfifo;
//parameter
parameter DATA_WIDTH = 8;
parameter DATA_DEPTH = 256;
reg rst_n;
reg wr_clk;
reg wr_en;
reg [DATA_WIDTH-1:0] wr_data;
wire full;
reg rd_clk;
reg rd_en;
wire rd_valid;
wire [DATA_WIDTH-1:0] rd_data;
wire empty;
initial
begin
rst_n = 0;
wr_clk = 0;
wr_en = 0;
wr_data = 0;
rd_clk = 0;
rd_en = 0;
#100;
rst_n = 1;
fork
forever @(posedge wr_clk)
begin
if(!full)
wr_en <= 1;
else
wr_en <= 0;
if(wr_en == 1)
wr_data <= wr_data + 1;
end
forever @(posedge rd_clk)
begin
if(!empty)
rd_en <= 1;
else
rd_en <= 0;
end
join
end
//clock
always #8 wr_clk = ~wr_clk;
always #5 rd_clk = ~rd_clk;
//DUT
asyncfifo
#(
.DATA_WIDTH(DATA_WIDTH),
.DATA_DEPTH(DATA_DEPTH)
)
DUT
(
.rst_n(rst_n),
.wr_clk(wr_clk),
.wr_en(wr_en),
.wr_data(wr_data),
.full(full),
.rd_clk(rd_clk),
.rd_en(rd_en),
.rd_valid(rd_valid),
.rd_data(rd_data),
.empty(empty)
);
initial
begin
$dumpfile("tb_asyncfifo.vcd");
$dumpvars(0,tb_asyncfifo);
end
initial #100_000 $finish;
endmodule
运行结果
vcs -R dualram.v asyncfifo.v tb_asyncfifo.v
时钟周期:
always #8 wr_clk = ~wr_clk;
always #5 rd_clk = ~rd_clk;
时钟周期:
always #5 wr_clk = ~wr_clk;
always #12 rd_clk = ~rd_clk;