FIFO 包括同步 FIFO 和异步 FIFO 两种,同步 FIFO 有一个时钟信号,读和写逻辑全部使用这一个时钟信号,异步 FIFO 有两个时钟信号,读和写逻辑用的各种的读写时钟,本节说的全部是同步 FIFO。
FIFO 与普通存储器 RAM 的区别是没有外部读写地址线,使用起来非常简单,但缺点就是只能顺序写入数据,顺序的读出数据,其数据地址由内部读写指针自动加 1 完成,不能像普通存储器那样可以由地址线决定读取或写入某个指定的地址。 FIFO 本质上是由 RAM 加读写控制逻辑构成的一种先进先出的数据缓冲器。
FIFO 的常见参数:
FIFO 的宽度:即 FIFO 一次读写操作的数据位;
FIFO 的深度:指的是 FIFO 可以存储多少个 N 位的数据(如果宽度为 N)。
满标志:FIFO 已满或将要满时由 FIFO 的状态电路送出的一个信号,以阻止 FIFO 的写操作继续向FIFO 中写数据而造成溢出(overflow)。
空标志:FIFO 已空或将要空时由 FIFO 的状态电路送出的一个信号,以阻止 FIFO 的读操作继续从FIFO 中读出数据而造成无效数据的读出(underflow)。
读时钟:读操作所遵循的时钟,在每个时钟沿来临时读数据。
写时钟:写操作所遵循的时钟,在每个时钟沿来临时写数据。
当读写指针相等时,表明 FIFO 为空,这种情况发生在复位操作时,或者当读指针读出 FIFO 中最后一个字后,追赶上了写指针时,如下图所示:
当读写指针再次相等时,表明 FIFO 为满,这种情况发生在,当写指针转了一圈,折回来(wrapped around)又追上了读指针,如下图:
为了区分到底是满状态还是空状态,可以采用以下方法:
我们设计一个计数器,该计数器(count)用于指示当前 FIFO 中数据的个数。计数器变化的过程如下:复位的时候,count 初始化为 0;
如果 FIFO 读使能和写使能同时有效的时候,count 不加也不减;表示同时对 FIFO 进行读写操作的时候,FIFO 中的数据个数不变。
如果写使能有效且 full=0,则 count+1;表示写操作且 FIFO 未满时候,FIFO 中的数据个数增加了 1;
如果读使能有效且 empty=0,则 count-1; 表示读操作且 FIFO 未满时候,FIFO 中的数据个数减少了 1;
count=0 的时候,表示 FIFO 空,需要设置 empty=1;如果 count=16 的时候,表示 FIFO 现在已经满,需要设置 full=1。
1) RAM 模块
本设计中的 FIFO 采用采用 16*8 双口(一个写口,一个读口)RAM,以循环读写的方式现;
读地址:根据 RD_addr_gen模块产生的读地址,在读使能(rd_en)为高电平的时候,将 RAM中 rd_addr[3:0]地址中的对应单元的数据在时钟上升沿到来的时候,读出到 data_out[7:0]中。
写地址:根据 WR_addr_gen 产生的写地址,在写使能(wr_en)为高电平的时候,将输入数据(data_in[7:0])在时钟上升沿到来的时候,写入 wr_addr[3:0]地址对应的单元。
2)WR_addr_gen:
该模块用于产生 FIFO 写数据时所用的地址。由于 16 深度的 RAM 单元可以用 4 位地址线寻址。本模块用 4 位计数器(wr_addr[3:0])实现写地址的产生。
在复位信号为 0(复位有效)时,写地址值为 0。
如果 FIFO 未满(full=0,FIFO 满了如果还继续写入,会导致新数据覆盖掉原来的旧数据)且有写使能(wr_en)有效,则 wr_addr[3:0]加 1;否则写地址不变。
3)RD_addr_gen:
该模块用于产生 FIFO 读数据时所用的地址。由于 16 个 RAM 单元可以用 4 位地址线寻址。本模块用 4位计数器(rd_addr[3:0])实现读地址的产生。
在复位信号为 0(复位有效)时,读地址值为 0。
如果 FIFO 未空(empty=0,FIFO 空了如果还继续读,会导致 FIFO 读出的数据不是期望的数据或者不确定的数据)且有读使能(rd_en)有效,则 rd_addr[3:0]加 1;否则读地址不变。
4) flag_gen 模块
flag_gen 模块产生 FIFO 空满标志。本模块设计没有使用读写地址判定 FIFO 是否空满(也可以使用读写地址做判断),而是设计一个计数器,该计数器(count)用于指示当前 FIFO 中数据的个数。由于 FIFO 中最多只有 16 个数据,因此采用 5 位计数器来指示 FIFO 中数据个数。
module sync_fifo
(
input clk ,
input rst ,
input wr_en ,
input rd_en ,
input [7:0] data_in ,
output reg [7:0] data_out ,
output reg empty ,
output reg full
);
reg [3:0] wr_addr ;
reg [3:0] rd_addr ;
reg [4:0] count ;
parameter max_count = 5'b10000 ;
parameter max1_count = 5'b01111 ;
reg [7:0] fifo [0:max_count] ; // 定义一个二维的 RAM,实际设计中根据 RAM 深度
//和宽度可能会采用 Vedor 提供的 RAM
//读操作
always @ (posedge clk or negedge rst) begin
if (rst == 1'b0)
data_out <=0;
else if (rd_en && empty==0)
data_out<=fifo[rd_addr];
end
//写操作
always @ (posedge clk ) begin
if (wr_en==1&&full==0)
fifo[wr_addr]<=data_in;
end
//更新读地址
always @ (posedge clk or negedge rst) begin
if (rst == 1'b0)
rd_addr<=4'b0000;
else if (empty==0&&rd_en==1)
rd_addr<=rd_addr+1;
end
//更新写地址
always @ (posedge clk or negedge rst) begin
if (rst == 1'b0)
wr_addr<=4'b0000;
else if (full==0&&wr_en==1)
wr_addr<=wr_addr+1;
else
wr_addr<=4'b0000;
end
//更新标志位
always @ (posedge clk or negedge rst) begin
if (rst == 1'b0)
count<=0;
else begin
case({wr_en,rd_en})
2'b00:count<=count;
2'b01:
if(count!==5'b00000)
count<=count-1;
2'b10:
if(count!== max1_count)
count<=count+1;
2'b11:count<=count;
endcase
end
end
always @(count) begin
if(count==5'b00000)
empty = 1;
else
empty = 0;
end
always @(count) begin
if (count== max_count)
full = 1;
else
full = 0;
end
endmodule
仿真结果: