一、FIFO概念
FIFO是先进先出的首字母缩写,它描述了如何相对于时间或优先级管理数据。在这种情况下,到达的第一个数据也将是从一组数据中离开的第一个数据。 FIFO缓冲区是一种读/写存储阵列,可自动跟踪数据进入模块的顺序并以相同顺序读出数据。在硬件中,FIFO缓冲区用于同步目的。它通常实现为循环队列,并具有两个指针:
- 读指针/读地址寄存器
- 写指针/写地址寄存器
FIFO 从读写时钟上来分有两类结构:同步 FIFO 和 异步FIFO。同步 FIFO 具有一个时钟(读写共用一个时钟)输入,因此所有输入信号的读取都是在这个时钟的上升沿进行的,所有输出信号的变化也是在这个时钟信号的上升沿的控制下进行的,即单时钟 FIFO 的所有输入输出信号都是同步这个时钟信号的。而在异步FIFO结构中,写端口和读端口分别有独立的时钟,所有与写相关的信号都是同步于写时钟 wr_clk,所有与读相关的信号都是同步于读时钟 rd_clk 。
二、同步FIFO
- 写指针WP总是指向下一个时钟要写的地址;
- 读指针RP总是指向下一个时钟要读的地址;
- 读指针等于写指针的时候有可能为空,有可能为满。
FIFO 的常见参数:
- FIFO 的宽度:即 FIFO 一次读写操作的数据位;
- FIFO 的深度:指的是 FIFO 可以存储多少个 N 位的数据(如果宽度为 N)。
- 满标志:FIFO 已满或将要满时由 FIFO 的状态电路送出的一个信号,以阻止 FIFO 的写操作继续向FIFO 中写数据而造成溢出(overflow)。
- 空标志:FIFO 已空或将要空时由 FIFO 的状态电路送出的一个信号,以阻止 FIFO 的读操作继续从FIFO 中读出数据而造成无效数据的读出(underflow)。
- 读时钟:读操作所遵循的时钟,在每个时钟沿来临时读数据。
- 写时钟:写操作所遵循的时钟,在每个时钟沿来临时写数据。
同步FIFO设计:
FIFO 本质上是一个存储器,存储介质可以是寄存器,SRAM 。 DDR等。其左边接上位机,及写FIFO设备,右边接下位机,及读FIFO设备。
上位机会对FIFO发起写操作,wr是写使能脉冲,wr_dat 是写入的数据,这两个信号是对齐的。上位机在发起写操作前,会读FIFO的满状态指示信号full或almost_full,因为FIFO的存储空间可能被写满,此时再写入数据必然会发生数据丢失现象。full状态信号表示FIFO的存储空间已经被全部占满,而almost_full状态信号表示存储空间并未被全部占满,但已经达到了用户设置的临界点,用户可以用cfg_almost_full配置线设置该临界点。
下位机对FIFO发起读操作,rd是读使能脉冲rd_dat 是读出的数据,根据存储介质类型、存储空间大小的不同,读出数据的延迟也不同,有的是rd发出后一拍输出数据,有的是两拍输出数据,要根据实际情况来定,不能一概而论。下位机在发起读之前,先要采样FIFO的状态信号empty或almost_empty,empty 表示FIFO内存全空,因此不支持读取数据,而almost_empty 表示虽然FIFO内存中尚存在一些数据,但数量较少,处于用户配置的cfg_almost_empty以下,以此发出警告。fifo_num用于实时反映FIFO内存中的数据数量。原本,FIFO只需 full和 empty 两种状态信号,但是,在以Burst方式通信时,上位机灌水不是每输入一个数据先看一下full状态,而是只看一下full状态,就往FIFO中连续灌很多数据,此时,almost_full就很有必要了,它会告诉上位机停止以Burst方式灌水。同理,下位机在使用Burst方式读取时,也需要almost_empty 提醒它停止Burst 读取。两条水线一般设置为稍大于Burst的长度。
2.1 高位扩展法
在深度为8的FIFO中,需要3bit的读写指针来分别指示读写地址3'b000-3'b111这8个地址。若将地址指针扩展1bit,则变成4bit的地址,而地址表示区间则变成了4'b0000-4'b1111。假设不看最高位的话,后面3位的表示区间仍然是3'b000-3'b111,也就意味着最高位可以拿来作为指示位。
当最高位不同,且其他位相同,则写指针多跑了一圈,意味着FIFO被写满了
当最高位相同,且其他位相同,则表示读指针追到了写指针,意味着FIFO被读空了
`timescale 1ns / 1ps
module synch_fifo #(
parameter P_DEEP = 8 , // FIFO深度(读写数据量)
parameter P_DEEPWID = 3 , // FIFO地址的位宽
parameter P_BITWID = 5 // FIFO宽度(读写数据位)
)(
input i_clk ,
input i_rst_n ,
input wr_en ,
input [P_BITWID - 1 : 0] wr_data ,
input [P_DEEPWID - 1 : 0] cfg_almost_full ,
output almost_full ,
output full ,
input rd_en ,
input [P_DEEPWID - 1 : 0] cfg_almost_empty,
output[P_BITWID - 1 : 0] rd_data ,
output almost_empty ,
output empty ,
output[P_DEEPWID : 0] fifo_num
);
wire[P_DEEPWID - 1 : 0] r_wr_ptr ;
wire[P_DEEPWID - 1 : 0] r_rd_ptr ;
reg[P_DEEPWID : 0] r_wr_ptr_exp;
reg[P_DEEPWID : 0] r_rd_ptr_exp;
reg[P_BITWID - 1 : 0] r_memory[P_DEEP - 1 : 0];
reg[P_BITWID - 1 : 0] r_rd_data ;
integer i ;
assign rd_data = r_rd_data ;
assign r_wr_ptr = r_wr_ptr_exp[P_DEEPWID - 1 : 0];
assign r_rd_ptr = r_rd_ptr_exp[P_DEEPWID - 1 : 0];
always@(posedge i_clk,negedge i_rst_n)begin
if(!i_rst_n)
r_wr_ptr_exp <= 'd0;
else if(wr_en && !full)
r_wr_ptr_exp <= r_wr_ptr_exp + 1;
else
r_wr_ptr_exp <= r_wr_ptr_exp;
end
always@(posedge i_clk,negedge i_rst_n)begin
if(!i_rst_n)
r_rd_ptr_exp <= 'd0;
else if(rd_en && !empty)
r_rd_ptr_exp <= r_rd_ptr_exp + 1;
else
r_rd_ptr_exp <= r_rd_ptr_exp;
end
always@(posedge i_clk,negedge i_rst_n)begin
if(!i_rst_n)
for (i = 0 ; i < P_DEEP ; i = i + 1) begin
r_memory [i] <= {(P_BITWID){1'b0}};
end
else if(wr_en && !full)
r_memory[r_wr_ptr] <= wr_data;
end
always@(posedge i_clk,negedge i_rst_n)begin
if(!i_rst_n)
r_rd_data <= 'd0;
else if (rd_en && !empty)
r_rd_data <= r_memory[r_rd_ptr];
end
assign fifo_num = r_wr_ptr_exp - r_rd_ptr_exp;
assign full = (fifo_num == P_DEEP); // | ((fifo_num == P_DEEP - 1) & wr_en & ~rd_en);
assign almost_full = (fifo_num >= cfg_almost_full) ;//| ((fifo_num == cfg_almost_full) & wr_en & ~rd_en);
assign empty = (fifo_num == 0) ;//| ((fifo_num == 1) & ~wr_en & rd_en);
assign almost_empty = (fifo_num <= cfg_almost_empty) ;//| ((fifo_num == cfg_almost_empty) & ~wr_en & rd_en);
endmodule
tb文件
`timescale 1ns / 1ps
module synch_fifo_tb();
parameter P_DEEP = 8 ;// FIFO深度(读写数据量)
parameter P_DEEPWID = 3 ;// FIFO地址的位宽
parameter P_BITWID = 8 ;// FIFO宽度(读写数据位)
reg i_clk ;
reg i_rst_n ;
reg wr_en ;
reg [P_BITWID - 1 : 0] wr_data ;
reg [P_DEEPWID - 1 : 0] cfg_almost_full ;
wire almost_full ;
wire full ;
reg rd_en ;
reg [P_DEEPWID - 1 : 0] cfg_almost_empty;
wire[P_BITWID - 1 : 0] rd_data ;
wire almost_empty ;
wire empty ;
wire[P_DEEPWID : 0] fifo_num ;
synch_fifo #(
.P_DEEP (P_DEEP ) , // FIFO深度(读写数据量)
.P_DEEPWID (P_DEEPWID) , // FIFO地址的位宽
.P_BITWID (P_BITWID ) // FIFO宽度(读写数据位)
)
synch_fifo_u0(
.i_clk (i_clk ),
.i_rst_n (i_rst_n),
.wr_en (wr_en ),
.wr_data (wr_data ),
.cfg_almost_full (6),
.almost_full (almost_full ),
.full (full ),
.rd_en (rd_en ),
.cfg_almost_empty(2),
.rd_data (rd_data ),
.almost_empty (almost_empty ),
.empty (empty ),
.fifo_num (fifo_num)
);
always #10 i_clk = ~i_clk;
initial begin
i_clk = 1'b0; //初始时钟为0
i_rst_n <= 1'b0; //初始复位
wr_data <= 'd0;
wr_en <= 1'b0;
rd_en <= 1'b0;
#50;
i_rst_n <= 1'b1;
#20;
//重复8次写操作,让FIFO写满
repeat(8) begin
@(negedge i_clk)begin
wr_en <= 1'b1;
rd_en <= 1'b0;
wr_data <= $random; //生成8位随机数
end
end
//重复8次读操作,让FIFO读空
repeat(8) begin
@(negedge i_clk)begin
wr_en <= 1'b0;
rd_en <= 1'd1;
end
end
//重复4次写操作,写入4个随机数据
repeat(4) begin
@(negedge i_clk)begin
wr_en <= 1'b1;
wr_data <= $random; //生成8位随机数
rd_en <= 1'b0;
end
end
//持续同时对FIFO读写,写入数据为随机数据
forever begin
@(negedge i_clk)begin
wr_en <= 1'b1;
wr_data <= $random; //生成8位随机数
rd_en <= 1'b1;
end
end
#100;
$finish;
end
endmodule
PS: 借鉴帖子 https://blog.csdn.net/wuzhikaidetb/article/details/12113604
白老师的《数字IC设计入门》