fifo的rdata_FPGA学习之FIFO

  • Post author:
  • Post category:其他


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;



版权声明:本文为weixin_31438219原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。