相关文章
数字IC前端学习笔记:LSFR(线性反馈移位寄存器)
数字IC前端学习笔记:跨时钟域信号同步
数字IC前端学习笔记:信号同步和边沿检测
数字IC前端学习笔记:锁存器Latch的综合
数字IC前端学习笔记:格雷码(含Verilog实现的二进制格雷码转换器)
数字IC前端学习笔记:FIFO的Verilog实现(二)
数字IC前端学习笔记:仲裁轮询(一)
数字IC前端学习笔记:仲裁轮询(二)
数字IC前端学习笔记:仲裁轮询(三)
目录
1.引言
2.FIFO操作
3.同步FIFO原理
4.同步FIFO的Verilog实现
FIFO表示先入先出(First in First out),它是一种存储器结构,被广泛用于芯片设计中。FIFO由存储器单元队列或阵列组成,如名所示,先被写入队列的数据也是先被读出。在芯片设计中,FIFO可以满足下列需求:
(1)当输入数据速率和输出速率不匹配时,作为临时存储单元。例如,CPU可以先将数据写入FIFO,然后继续做其他工作,设备可以很方便地从FIFO中读取数据。再如因特网控制器,它将从网络接收来的数据存入FIFO,后端的DMA(Direct Memory Access,直接存储器访问)控制器(位于PCIe或PCI接口电路中)从FIFO中读取数据,然后写入系统存储器。
(2)用于不同时钟域之间的同步。实际应用中,数据有时会从一个时钟域进入另一个时钟域,此时FIFO不仅做临时数据存储单元,也起到数据同步的作用。
(3)输入数据和输出数据的数据宽度不匹配时,可以用数据宽度调整电路。
我们在后面再详细介绍这些应用,现在先给出FIFO的结构和输入输出端口,如下图所示,宽度为6、深度为8的FIFO。这意味着此FIFO中有8个存储位置,每个位置可以存储6位数据。注意,实际上读写指针对于外部是不可见的,图中画出只是为了后面说明方便。
电路复位后,FIFO为空,写指针和读指针都指向同一个位置,该位置通常是0。数据由write_data端口输入,由控制信号write_en控制,由read_data端口输出,由控制信号read_en控制。write_pointer指针指向下一个可能的空位置(队列尾元素的后一个位置),而read_pointer指向下一个被读取的位置(队列首元素)。我们一般采取循环队列的方式构建FIFO,即如果我们持续写入,当write_pointer指向最大值时(例如111),如果再次写入元素,write_pointer将回到0。读指针也拥有这种循环的性质,换句话说,写指针和读指针以环形的方式移动,写指针在前,读指针在后追随。写操作和读操作也被称为push/pop、put/get或fill/drain操作。
我们也可以同时进行读操作和写操作,因为两种操作使用不同的指针、控制信号和数据总线。FIFO的这种操作就像一个水箱,它有一个进水口让水进入水箱,还有一个出口让水流出水箱。在任何时候,可以一边进水一边放水,我们需要关注的是,除了FIFO空或满,读指针和写指针不能相同。我们要确保两种情况不会发生,一是给满的FIFO写入数据(所有位置都有数据,没有多余位置);二是从空的FIFO中读取数据(FIFO中没有有效数据)。它们分别被称为上溢(overrun)和下溢(underrun)。FIFO会产生fifo_full和fifo_empty信号,用于表示FIFO是否已满或已空。当FIFO为满时,禁止写入数据;当FIFO为空时,禁止继续读取数据。
在详细叙述前,先通过时序图来进一步增强我们的理解,如下图所示。
在同步FIFO中,单一时钟用于写入和读取操作。数据流和相关的控制逻辑在一个时钟域内工作。同步FIFO用于临时存储数据,此时写入和读取操作即可以同时发生,也可以发生在不同时刻。由于同步FIFO只使用了一个时钟,其控制逻辑相对于异步FIFO来说简单得多前面讨论过一些输入和输出端口,现在需要增加一些有用的输出,如fifo_full、fifo_empty、room_available、data_available。从名称中可以看出,fifo_full信号表示FIFO为满,fifo_empty表示FIFO为空。这两个信号作为边界条件,提醒外部电路不要写满的FIFO和读空的FIFO。
FIFO还可能提供其他信号,如almost_full和almost_empty,用于提前提供FIFO的满和空的信息。例如,当做设计的FIFO还剩余2到3个空余位置时,almost_full信号有效,此时负责外围写入的电路就要考虑停止写入了,因为从决定停止到write_en信号被置为无效可能还需要多个时钟周期。如果写入逻辑等待fifo_full标识有效后才将write_en信号置为无效,就可能太迟了,可能会有信号被错误写入或遗漏(取决于是否有保险措施)。almost_empty也采用类似的工作方式用于防止数据下溢。相较于almost_full和almost_empty信号,我们有时更愿意使用room_available和data_available来提供准确的剩余空间和数据深度信息(本文下面的FIFO就是使用了这两个信号)。写逻辑使用room_available,因为它关注还有多少空间,读逻辑使用data_available,因为它关注还有多少数据可读。
module synch_fifo #(parameter FIFO_PTR = 4,
FIFO_WIDTH = 32,
FIFO_DEPTH = 16)
(fifo_clk,rstb,
fifo_wren,
fifo_wrdata,
fifo_rden,
fifo_rddata,
fifo_full,
fifo_empty,
fifo_room_avail,
fifo_data_avail);
//端口声明
//*******************************************************
input fifo_clk;
input rstb;
input fifo_wren;
input [FIFO_WIDTH - 1 : 0] fifo_wrdata;
input fifo_rden;
input [FIFO_WIDTH - 1 : 0] fifo_rddata;
output reg fifo_full;
output reg fifo_empty;
output reg [FIFO_PTR : 0] fifo_room_avail;
output [FIFO_PTR : 0] fifo_data_avail;
//变量定义
//*******************************************************
reg [FIFO_PTR-1 : 0] wr_ptr,wr_ptr_nxt;
reg [FIFO_PTR-1 : 0] rd_ptr,rd_ptr_nxt;
reg [FIFO_PTR : 0] num_entries,num_entries_nxt;
reg [FIFO_PTR : 0] fifo_room_avail;
reg fifo_full,fifo_empty;
wire [FIFO_PTR : 0] fifo_room_avail_nxt;
wire [FIFO_PTR : 0] fifo_data_avail_nxt;
wire fifo_full_nxt,fifo_empty_nxt;
//写指针控制逻辑
//*******************************************************
always@(*) begin
wr_ptr_nxt = wr_ptr;
if(fifo_wren) begin
if(wr_ptr == FIFO_DEPTH - 1)
wr_ptr_nxt = 0;
else
wr_ptr_nxt = wr_ptr + 1;
end
end
//读指针控制逻辑
//*******************************************************
always@(*) begin
rd_ptr_nxt = rd_ptr;
if(fifo_rden) begin
if(rd_ptr == FIFO_DEPTH - 1)
rd_ptr_nxt = 0;
else
rd_ptr_nxt = rd_ptr + 1;
end
end
//计算FIFO内的数据量
//*******************************************************
always@(*) begin
num_entries_nxt = num_entries;
if(fifo_wren && fifo_rden)
num_entries_nxt = num_entries;
else if(fifo_wren)
num_entries_nxt = num_entries + 1;
else if(fifo_rden)
num_entries_nxt = num_entries - 1;
end
//产生提示信号
//*******************************************************
assign fifo_full_nxt = (num_entries_nxt == FIFO_DEPTH);
assign fifo_empty_nxt = (num_entries_nxt == 0);
assign fifo_data_avail = num_entries;
assign fifo_room_avail_nxt = (FIFO_DEPTH - num_entries_nxt);
//寄存器操作
//*******************************************************
always@(posedge fifo_clk or negedge rstb) begin
if(!rstb) begin
wr_ptr <= 0;
rd_ptr <= 0;
num_entries <= 0;
fifo_full <= 0;
fifo_empty <= 0;
fifo_room_avail <=0;
end
else begin
wr_ptr <= wr_ptr_nxt;
rd_ptr <= rd_ptr_nxt;
num_entries <= num_entries_nxt;
fifo_full <= fifo_full_nxt;
fifo_empty <= fifo_empty_nxt;
fifo_room_avail <= fifo_room_avail_nxt;
end
end
//可以自己写一个存储器,也可以直接例化存储器ip(这里选择后者)
//*******************************************************
sram #(.FIFO_PTR (FIFO_PTR),
.FIFO_WIDTH (FIFO_WIDTH)) sram_0
(.wrclk(fifo_clk),
.wren(fifo_wren),
.wrptr(wr_ptr),
.wrdata(fifo_wrdata),
.rdclk(fifo_clk),
.rden(fifo_rden),
.rdptr(rd_ptr),
.rddata(fifo_rddata));
endmodule
以上内容来源于《Verilog高级数字系统设计技术和实例分析》