FIFO介绍
FIFO即First In First out,顾名思义就是先进先出,他是用来存储数据的,先存入的数据会被先读出,数据读出的顺序和数据写入的顺序相同,所以一般用作数据缓冲。例如FIFO前级模块的数据处理速率为100Mbps,后级模块的处理速率为50Mbps,这时候显然不能直接将前级数据直连到后级模块,此时就可以利用FIFO将数据暂存起来,后级模块处理时便从中读出并且不影响数据写入,也不用担心后级模块处理数据过程中数据终端的情况,因为写入速率是快于读出的速率。
而FIFO又分为同步FIFO和异步FIFO。同步FIFO指的是读写使用同一个人时钟,异步FIFO指的就是读写时钟不同。显然同步FIFO先对于异步FIFO实现起来更加简单一些,不需要考虑跨时钟域的亚稳态问题。
同步FIFO设计思路
FIFO其实就是一个存储器,那么如何实现先进先出呢?首先我们需要明确读写是独立的, 而数据的存储一般需要进行寻址,因此我们可以将地址分为读地址和写地址,每次写入数据时写地址自增,读地址不变;每次读数据时读地址自增,写地址不变,以此来完成对读写数据当前顺序的确定。
既然是存储器那必然有一个存储容量,达到存储容量后就不能写入数据了,都则会造成数据丢失。那如何确定何时达到了存储容量呢?假设FIFO的深度为100,那当写地址为99时(地址从0开始增加)就已经达到存储容量吗?显然不是,因为在这个过程中可能数据也会被读出,就好比泳池注水和排水的过程。如果写数据的过程中有数据读出,那么当写地址增加到99后,下一个写地址又会变为0(因为读数据顺序和写地址顺序一样,先从哪写的就先从哪出,既然数据被读出那一定是地址0的数据先被读出,然后是地址1的数据以此类推),之后又开始自增。那何时才是真的写满FIFO了呢,,可以将读写比作两个人围着操场跑步,两人从同一起点出发,他们之间差的距离就是目前已经存储的数据个数,每次跑完一圈回到起点的过程就是上面读写地址从99变为0,只有当前面的那个人快看到慢的那个人的背影,即将超过跑的慢的人时,这时候就已经比他快了一圈,而操场的一圈就代表FIFO的深度(存储容量),这时候就已经存满了。
如何标志FIFO满的状态
根据上面的描述,当写地址要追上读地址的时候就是写满FIFO了,那可以用已经写的地址数量(注意不是写地址,而是已经写的数量,写地址只有0-99)减去已经读的地址数量就好了。这样比较简单,但是有一个问题,因为读写数据可能一直在进行,我们得给多大的位宽才行呢?这个问题是没有办法的,如果读写速率相同并且一直在进行,那么已经写的地址数量/读的地址数量的位宽是没有边界的。但是我们需要明确,写地址只会比读地址最多快一圈,因为FIFO写满后我们就不能让数据继续写入了。因此只需要对地址位宽扩展一位,就能够得到FIFO中现存数据的数量了。下面举个例子说明:
比如读写地址数量从0-31,即FIFO的深度为32,那么读写地址的范围为5’b0-5’b11111(十进制为0-31),那么对其进行扩展一位,地址就变为6’b0-6’b011111,当写地址写满一圈后继续增加时就变为6’b100000,而读地址还处在第一圈,假设其地址为6’b0xxxxx,其实扩展的这一位就代表目前读写地址处在哪一圈,当写地址继续增加到6’b111111时,再增加就变为6’b000000,假设读地址也读完了第一圈,那读地址为6’b1xxxxx,这时候最高位又不一样,就说明最高位代表了读写地址有没有在同一轮次(也就是跑步的两人是不是都在跑第x圈),因此这时候就可以通过扩展的这一位和写地址、读地址来得到FIFO中现存数据的个数。如果最高位不同,那写地址比读地址快一圈,原本的5位写地址+FIFO深度减去原本的5位读地址,就是现存数据的数量,当现存数据等于FIFO深度时,就拉高写满信号,停止数据写入。(也可以直接利用扩展后的写地址减去读地址得到FIFO现存数据量,即使写地址小于读地址,差值是负数,但是用补码表示依然可以得到正确的数量如写地址为6’b0 00011,读地址为6’b1 00011,写地址减去读地址得到6’b100000,此时读写指针处于同一位置,也就是说写入的数量为FIFO的深度,此时FIFO写满了)
代码实现
FIFO设计代码如下:
module FIFO_sync
#(
parameter DEPTH = 32,
parameter DEPTH_WIDTH = 5,
parameter WIDTH = 32
)
(
input clk,
input rst_n,
input wr_en,
input [WIDTH-1:0]wr_data,
input rd_en,
output reg [WIDTH-1:0]rd_data,
output reg rd_data_valid,
output full,
output almost_full,
output empty,
output almost_empty,
output [DEPTH_WIDTH:0]data_count
);
wire [DEPTH_WIDTH-1:0] wr_addr;
reg [DEPTH_WIDTH:0] wr_addr_bit;
wire [DEPTH_WIDTH-1:0] rd_addr;
reg [DEPTH_WIDTH:0] rd_addr_bit;
reg [DEPTH-1:0]memory[WIDTH-1:0];
integer i;
//写数据
//写地址
always @(posedge clk or negedge rst_n)
begin
if(~rst_n)
begin
wr_addr_bit <= {(DEPTH_WIDTH+1){1'b0}};
end
else if(wr_en)
begin
wr_addr_bit <= wr_addr_bit+1'b1;
end
else
begin
wr_addr_bit <= wr_addr_bit;
end
end
assign wr_addr=wr_addr_bit[DEPTH_WIDTH-1:0];
//将数据写入存储单元
always @(posedge clk or negedge rst_n)
begin
if(!rst_n)
begin
for(i=0;i<DEPTH;i=i+1)
memory[i]<= 0;
end
else if(wr_en)
begin
for(i=0;i<DEPTH;i=i+1)
begin
if(i==wr_addr)
memory[i]<= wr_data;
else
memory[i]<=memory[i];
end
end
else
for(i=0;i<DEPTH;i=i+1)
memory[i]<= memory[i];
end
//读数据
//读地址
always @(posedge clk or negedge rst_n)
begin
if(~rst_n)
begin
rd_addr_bit <= {(DEPTH_WIDTH+1){1'b0}};
end
else if(rd_en)
begin
rd_addr_bit <= rd_addr_bit+1'b1;
end
else
begin
rd_addr_bit <= rd_addr_bit;
end
end
assign rd_addr=rd_addr_bit[DEPTH_WIDTH-1:0];
always @(posedge clk or negedge rst_n)
begin
if(!rst_n)
begin
rd_data<={WIDTH{1'b0}};
rd_data_valid<=1'b0;
end
else if(rd_en)
begin
for(i=0;i<DEPTH;i=i+1)
begin
if(i==rd_addr)
rd_data<= memory[i];
end
rd_data_valid<=1'b1;
end
else
begin
rd_data <={WIDTH{1'b0}};
rd_data_valid<=1'b0;
end
end
//FIFO中已存数据量
assign data_count=wr_addr_bit-rd_addr_bit;
//assign data_count=(wr_addr_bit[DEPTH_WIDTH]==rd_addr_bit[DEPTH_WIDTH])?(wr_addr_bit-rd_addr_bit):(wr_addr_bit+DEPTH-rd_addr_bit);
//满标志/将满标志判断
assign full= (data_count==DEPTH)|(data_count==DEPTH-1 && wr_en && (!rd_en));
assign almost_full= (data_count==DEPTH-1)|(data_count==DEPTH-2 && wr_en && (!rd_en));
//空标志/将空标志判断
assign empty=(data_count==0)|(data_count==1 && (!wr_en) && rd_en);
assign almost_empty=(data_count==1)|(data_count==2 && (!wr_en) && rd_en);
endmodule
仿真激励如下:
module tb_fifo_sync();
reg clk;
reg rst_n;
reg wr_en;
reg [31:0] wr_data;
reg rd_en;
wire [31:0] rd_data;
wire rd_data_valid;
wire full;
wire almost_full;
wire empty;
wire almost_empty;
wire [5:0]data_count;
integer seed = 100;
always #5 clk=!clk;
initial
begin
clk=0;
rst_n=0;
wr_en=0;
wr_data=32'b0;
rd_en=0;
#100
rst_n=1;
#55
repeat(40)
@(posedge clk)
begin
if(!full)
begin
wr_en<=1;
wr_data<=$random(seed);
end
else
begin
wr_en<=0;
wr_data<=0;
end
end
#105
repeat(40)
@(posedge clk)
begin
if(!empty)
begin
rd_en<=1;
end
else
begin
rd_en<=0;
end
end
#1000
$stop;
end
FIFO_sync #(
.DEPTH(32),
.DEPTH_WIDTH(5),
.WIDTH(32)
) inst_FIFO_sync (
.clk (clk),
.rst_n (rst_n),
.wr_en (wr_en),
.wr_data (wr_data),
.rd_en (rd_en),
.rd_data (rd_data),
.rd_data_valid (rd_data_valid),
.full (full),
.almost_full (almost_full),
.empty (empty),
.almost_empty (almost_empty),
.data_count (data_count)
);
endmodule
仿真结果
1.数据写入仿真
2.数据读出仿真