目录
一、基本原理
1、FIFO基本概念:First In First Out的缩写,是一种先进先出的数据缓存器,没有外部读写地址线,只能顺序写入读出数据。
FIFO存储器主要分为基于移位寄存器类型和基于RAM类型,而RAM又有单口和双口的类型。应用较多的是基于双端口的RAM的FIFO.(双端口的RAM就是带读写地址、使能端的存储器)
2、同步FIFO:读和写都是在一个CLK下工作,对双端口RAM进行操作,产生空满信号,满时不能写入,空时不能读出。
3、空满标注是FIFO设计的关键。通过比较读写信号指针来判断空满,当读指针等于满指针时,FIFO可能是空可能是满。这里我们引入一个扩展位来判断: 首先把读写标志位+地址位全部复位,如果地址循环了奇数次,标志位置1,偶数置0。当读写指针完全相等时,处于“空”;当标志位不同,地址位相等时,处于“满”。
4、时延
在真实情境下,写进去的数不能立即读出来,需要延迟两个clk。
二、基于Verilog的同步FIFO例题
根据题目提供的双口RAM代码和接口描述,实现同步FIFO,要求FIFO位宽和深度参数化可配置。
电路的接口如下图所示。
详解:
FIFO题的难点就在空满标志的判断:
思路一:拓展地址位宽
将raddr和waddr两个地址扩展一位,用首位数字做标志来进行判断,当写操作领先整一个周期(即一个depth)时,为full,此时raddr和waddr除了首位其余相等,用 wfull <= waddr == raddr + DEPTH;来描述。而当读写地址完全相同时,则为empty,意味着读写标志相同。 rempty <= waddr == raddr;
`timescale 1ns/1ns
/**********************************RAM************************************/
module dual_port_RAM #(parameter DEPTH = 16,
parameter WIDTH = 8)(
input wclk
,input wenc
,input [$clog2(DEPTH)-1:0] waddr //深度对2取对数,得到地址的位宽。
,input [WIDTH-1:0] wdata //数据写入
,input rclk
,input renc
,input [$clog2(DEPTH)-1:0] raddr //深度对2取对数,得到地址的位宽。
,output reg [WIDTH-1:0] rdata //数据输出
);
reg [WIDTH-1:0] RAM_MEM [0:DEPTH-1]; //定义一定位宽的存储器
always @(posedge wclk) begin
if(wenc)
RAM_MEM[waddr] <= wdata; //写入数据
end
always @(posedge rclk) begin
if(renc)
rdata <= RAM_MEM[raddr]; //读出数据
end
endmodule
//上述为RAM模块
/**********************************SFIFO************************************/
module sfifo#(
parameter WIDTH = 8,
parameter DEPTH = 16
)(
input clk ,
input rst_n ,
input winc ,
input rinc ,
input [WIDTH-1:0] wdata ,
output reg wfull ,
output reg rempty ,
output wire [WIDTH-1:0] rdata
);
wire wenc,renc;
reg [$clog2(DEPTH) : 0] waddr;
reg [$clog2(DEPTH) : 0] raddr;
//空满标志的判断
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
wfull <= 0;
rempty <= 0;
end
else begin
wfull <= waddr == raddr + DEPTH;
rempty <= waddr == raddr;
end
end
//FIFO使能信号受空满标志的影响
assign wenc = winc && !wfull;
assign renc = rinc && !rempty;
//读写地址的变化
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
waddr <= 0 ;raddr <= 0 ;
end
else begin
waddr <= (wenc)? waddr + 1 : waddr;
raddr <= (renc)? raddr + 1 : raddr;
end
end
//调用RAM模块
dual_port_RAM #(.DEPTH (DEPTH),
.WIDTH (WIDTH))
RAM (
.wclk (clk ),
.wenc (wenc ),
.waddr (waddr),
.wdata (wdata),
.rclk (clk ),
.renc (renc ),
.raddr (raddr),
.rdata (rdata)
);
endmodule
思路二:增加判断标志
空满信号判断可以更直观的加入两个标志来对周期进行计数,向上文中所提到的,首先把读写标志位+地址位全部复位,如果地址循环了奇数次,标志位置1,偶数置0。当读写指针完全相等时,处于“空”;当标志位不同,地址位相等时,处于“满”。详解如下:
//节选了判断空满的一部分,其余和思路一代码相同
reg r,w;
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
w <= 'd0;
r <= 'd0;
end
else begin
w <= (waddr == 4'd16)? ~w:w;
r <= (raddr == 4'd16)? ~r:r;
end
end
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
wfull <= 'd0;
rempty <= 'd0;
end
else begin
wfull <= (w^r && waddr == raddr)? 1:0;
rempty <=(w==r && waddr == raddr)? 1:0;
end
end
思路三:计数器
添加一个计数器来判断也是常用的方法,当写使能有效的时候计数器加一;当读使能有效的时候,计数器减一,将计数器与depth进行比较来判断fifo的空满状态。具体代码就不详细写啦。这种方式写起来比较简单,但需要占用很多额外的资源,而且如果FIFO的深度越大会使得效率很低。
DONE~