FIFO是一种先进先出的电路,使用在需要产生数据接口的部分,用来存储、缓冲在两个一部时钟之间的数据传输。在一部电路中,由于时钟之间周期和相位完全独立,因为数据丢失概率不为零。使用FIFO可以在两个不同时钟域系统之间快速而方便地传输实时数据。在网络接口、图像处理灯方面,FIFO得到广泛的应用。
FIFO的设计难点:产生可靠的FIFO读写指针和生成FIFO“空”、“满”状态标识。
异步FIFO地址最好使用Gray计数器,这是因为,采用二进制数时又可能变化计数一次所有位都会有所变化,而Gray码计数器就只是计数一次数据变化一次。
1、FIFO“空”/“满”状态
由于FIFO“空”/“满”状态都表明读写指针相等,所以必须要准确区分是空还是满。
解决办法是将FIFO地址空间按最高量为划分成4个象限,每当读写地址相等时,通过对最高两位译码以申城正空的空”/“满”标志。
如果写指针比读指针之后一个象限,则表明FIFO接近满。此时置标志位direction为1,并且锁存其值。相应的等式为:
wire dirset_n = ~(( wptr[N]^rptr[N-1]) & ~(wptr[N-1]^rptr[N]));
如果写指针比读指针超前一个象限,则表明FIFO为接近空状态,如图所示。此时置标志direction为0,并且锁存其值。相应的等式为:
wire dirclr_n = ~((~(wptr[n]^rptr[n-1]) & (wptr[n-1] ^ rptr[n])) | ~wrst_n;
2、FIFO模块结构
(1)顶层模块,对所有FIFO模块进行封装
(2)双口RAM模块,用于实现读写操作
(3)异步比较器,用于实现FIFO读写指针比较,并输出状态信号以生成正确的FIFO空满标志
(4)FIFO写指针与满逻辑控制模块,用于生成FIFO写地址指针并且生成FIFO满标志。
(5)FIFO读指针与空逻辑控制模块,用于生成FIFO读地址指针并且生成FIFO空标志。
代码如下:
//双端口RAM模块
module dp_ram(
rdata,
wdata,
waddr,
raddr,
wclken,
wclk
);
parameter DATA_WIDTH = 8; //双口RAM的数据位宽
parameter ADDR_WIDTH = 4; //双口RAM的地址位宽
parameter DEPTH = 1<
output [DATA_WIDTH-1:0]rdata; //读出的数据
input [DATA_WIDTH-1:0]wdata; //写入的数据
input [ADDR_WIDTH-1:0]waddr, raddr; //读写数据地址
input wclken; //写时钟使能,高电平有效
input wclk; //写时钟,上升沿有效
reg [DATA_WIDTH-1:0]MEN[0:DEPTH-1];
always @(posedge wclk)
if(wclken) MEN[waddr] <= wdata;
assign rdata = MEN[raddr];
endmodule
//异步比较器模块
module async_cmp(
aempty_n,
afull_n,
wptr,
rptr,
wrst_n
);
parameter ADDR_WIDTH = 4;
parameter N = ADDR_WIDTH – 1;
output aempty_n, afull_n;
input [N:0]wptr;
input [N:0]rptr;
input wrst_n;
reg direction;
wire high = 1’b1;
wire dirset_n = ~(( wptr[N]^rptr[N-1]) & ~(wptr[N-1]^rptr[N]));
wire dirclr_n = ~((~(wptr[N]^rptr[N-1]) & (wptr[N-1]^rptr[N])) | ~wrst_n);
always @(posedge high or negedge dirset_n or negedge dirclr_n)
begin
if(!dirclr_n) direction <= 1’b0;
else if(!dirset_n) direction <= 1’b1;
else direction <= high;
end
assign aempty_n = ~((wptr == rptr) && !direction);
assign afull_n = ~((wptr == rptr) && direction);
endmodule
//读指针与“满”逻辑
module rptr_empty( rempty, rptr, aempty_n, rreq, rclk, rrst_n );
parameter ADDR_WIDTH = 4;
output rempty;
output [ADDR_WIDTH-1:0]rptr;
input aempty_n;
input rreq, rclk, rrst_n;
reg [ADDR_WIDTH-1:0]rptr,rbin; reg rempty, rempty2;
wire [ADDR_WIDTH-1:0]rgnext, rbnext;
//寄存器输出Gray码读地址指针
always @(posedge rclk or negedge rrst_n)
if(!rrst_n) begin rbin <= 0; rptr <= 0; end else begin rbin <= rbnext; rptr <= rgnext; end //Gray码计数逻辑 assign rbnext = !rempty ? rbin + rreq : rbin;//二进制递增 assign rgnext = (rbnext>>1) ^ rbnext; //二进制到Gray码的转换 //rempty被aempty异步置位,并且当读指针递加后撤除 always @(posedge rclk or negedge aempty_n) begin if(!aempty_n) {rempty, rempty2} <= 2’b11; else {rempty, rempty2} <= {rempty2, ~aempty_n}; end endmodule //写指针与满逻辑模块 module wptr_full( wfull, wptr, afull_n, wreq, wclk, wrst_n ); parameter ADDR_WIDTH = 4; output wfull; output [ADDR_WIDTH-1:0]wptr; input afull_n; input wreq, wclk, wrst_n; reg [ADDR_WIDTH-1:0]wptr, wbin; reg wfull, wfull2; wire [ADDR_WIDTH-1:0]wgnext, wbnext; //寄存器输出Gray码读地址指针 always @(posedge wclk or negedge wrst_n) begin if(!wrst_n) begin wbin <= 0; wptr <= 0; end else begin wbin <= wbnext; wptr <= wgnext; end end //Gray码计数逻辑 assign wbnext = !wfull ? wbin + wreq : wbin; //二进制递增 assign wgnext = (wbnext>>1) ^ wbnext; //二进制到Gray码的转换 //当FIFO复位时,状态wfull也被复位,此外其也被afull_n异步置位,并且当读指针递加后撤除 always @(posedge wclk or negedge wrst_n or negedge afull_n) begin if(!wrst_n) {wfull, wfull2} <= 2’b00; else if(!afull_n) {wfull, wfull2} <= 2’b11; else {wfull, wfull2} <= {wfull2, ~afull_n}; end endmodule module async_fifo( rdata, wfull, rempty, wdata, wreq, wclk, wrst_n, rreq, rclk, rrst_n ); parameter DATA_WIDTH = 8; parameter ADDR_WIDTH = 4; output [DATA_WIDTH-1:0]rdata; //从fifo读出的数据 output wfull; //fifo数据满了,写不下了 output rempty; //fifo被读空了,别再读了 input [DATA_WIDTH-1:0]wdata; //写进fifo的数据 input wreq; //写允许 input wclk; //写时钟 input wrst_n; //写复位 input rreq; //读允许 input rclk; //读时钟 input rrst_n; //读复位 //一下wire类型只是起到两个模块之间的连线作用 wire [ADDR_WIDTH-1:0]wptr, rptr; wire [ADDR_WIDTH-1:0]waddr, raddr; wire aempty_n, afull_n; dp_ram dp_ram( .rdata(rdata), .wdata(wdata), .waddr(wptr), .raddr(rptr), .wclken(wreq), .wclk(wclk) ); defparam dp_ram.DATA_WIDTH = DATA_WIDTH; defparam dp_ram.ADDR_WIDTH = ADDR_WIDTH; async_cmp async_cmp( .aempty_n(aempty_n), .afull_n(afull_n), .wptr(wptr), .rptr(rptr), .wrst_n(wrst_n) ); defparam async_cmp.ADDR_WIDTH = ADDR_WIDTH; rptr_empty rptr_empty( .rempty(rempty), .rptr(rptr), .aempty_n(aempty_n), .rreq(rreq), .rclk(rclk), .rrst_n(rrst_n) ); defparam rptr_empty.ADDR_WIDTH = ADDR_WIDTH; wptr_full wptr_full( .wfull(wfull), .wptr(wptr), .afull_n(afull_n), .wreq(wreq), .wclk(wclk), .wrst_n(wrst_n) ); defparam wptr_full.ADDR_WIDTH = ADDR_WIDTH; endmodule 简单笔记:
1、defparam 重定义参数
语法:defparam path_name = value ;
低层模块的参数可以通过层次路径名重新定义
2、menory生成办法:
reg [DATA_WIDTH-1:0]MEN[0:DEPTH-1];
3、一个技巧:
//rempty被aempty异步置位,并且当读指针递加后撤除 always @(posedge rclk or negedge aempty_n) begin if(!aempty_n) {rempty, rempty2} <= 2’b11; else {rempty, rempty2} <= {rempty2, ~aempty_n}; end
4、用wire去连接两个模块
//以下wire类型只是起到两个模块之间的连线作用
wire [ADDR_WIDTH-1:0]wptr, rptr;
wire [ADDR_WIDTH-1:0]waddr, raddr;