需求说明:Verilog设计
内容 :异步FIFO设计
来自 :时间的诗
原文:http://www.cnblogs.com/BitArt/archive/2013/04/10/3010073.html
本文大部分内容来自Clifford E. Cummings的《Simulation and Synthesis Techniques for Asynchronous FIFO Design》,同时加上一些自己的一些理解,有兴趣的朋友可以阅读原文。
一、FIFO简介
FIFO是英文First In First Out 的缩写,是一种先进先出的数据缓存器,它与普通存储器的区别是没有外部读写地址线,这样使用起来非常简单,但缺点就是只能顺序写入数据,顺序的读出数据,其数据地址由内部读写指针自动加1完成,不能像普通存储器那样可以由地址线决定读取或写入某个指定的地址。
用途1:
异步FIFO读写分别采用相互异步的不同时钟。在现代集成电路芯片中,随着设计规模的不断扩大,一个系统中往往含有数个时钟,多时钟域带来的一个问题就是,如何设计异步时钟之间的接口电路。异步FIFO是这个问题的一种简便、快捷的解决方案,使用异步FIFO可以在两个不同时钟系统之间快速而方便地传输实时数据。
用途2:
对于不同宽度的数据接口也可以用FIFO,例如单片机位8位数据输出,而DSP可能是16位数据输入,在单片机与DSP连接时就可以使用FIFO来达到数据匹配的目的。
二、分类
同步FIFO是指读时钟和写时钟为同一个时钟,在时钟沿来临时同时发生读写操作;
异步FIFO是指读写时钟不一致,读写时钟是互相独立的。
三、FIFO的常见参数
- FIFO的深度:指的是FIFO可以存储多少个N位的数据(如果宽度为N)。
- 满标志:FIFO已满或将要满时由FIFO的状态电路送出的一个信号,以阻止FIFO的写操作继续向FIFO中写数据而造成溢出(overflow)。
- 空标志:FIFO已空或将要空时由FIFO的状态电路送出的一个信号,以阻止FIFO的读操作继续从FIFO中读出数据而造成无效数据的读出(underflow)。
- 读时钟:读操作所遵循的时钟,在每个时钟沿来临时读数据。
- 写时钟:写操作所遵循的时钟,在每个时钟沿来临时写数据。
===============================分 隔 符 ==============================
- 读写指针的工作原理
写指针:总是指向下一个将要被写入的单元,复位时,指向第1个单元(编号为0)。
读指针:总是指向当前要被读出的数据,复位时,指向第1个单元(编号为0)
- FIFO的“空”/“满”检测
FIFO设计的关键:产生可靠的FIFO读写指针和生成FIFO“空”/“满”状态标志。
当读写指针相等时,表明FIFO为空,这种情况发生在复位操作时,或者当读指针读出FIFO中最后一个字后,追赶上了写指针时,如下图所示:
![](http://images.cnitblog.com/blog/470909/201304/09122539-f66a4eee9b0341c9ab4e18f4ad6e782c.jpg)
当读写指针再次相等时,表明FIFO为满,这种情况发生在,当写指针转了一圈,折回来(wrapped around)又追上了读指针,如下图:
![](http://images.cnitblog.com/blog/470909/201304/09122758-65cac4b3445f476b9b25338b88fe0595.jpg)
为了区分到底是满状态还是空状态,可以采用以下方法:
方法1:在指针中添加一个额外的位(extra bit),当写指针增加并越过最后一个FIFO地址时,就将写指针这个未用的MSB加1,其它位回零。对读指针也进行同样的操作。此时,对于深度为2n的FIFO,需要的读/写指针位宽为(n+1)位,如对于深度为8的FIFO,需要采用4bit的计数器,0000~1000、1001~1111,MSB作为折回标志位,而低3位作为地址指针。
-
- 如果两个指针的MSB不同,说明写指针比读指针多折回了一次;如r_addr=0000,而w_addr = 1000,为满。
- 如果两个指针的MSB相同,则说明两个指针折回的次数相等。其余位相等,说明FIFO为空;
3.二进制FIFO指针的考虑
将一个二进制的计数值从一个时钟域同步到另一个时钟域的时候很容易出现问题,因为采用二进制计数器时所有位都可能同时变化,在同一个时钟沿同步多个信号的变化会产生亚稳态问题。而使用格雷码只有一位变化,因此在两个时钟域间同步多个位不会产生问题。所以需要一个二进制到gray码的转换电路,将地址值转换为相应的gray码,然后将该gray码同步到另一个时钟域进行对比,作为空满状态的检测。
![](http://images.cnitblog.com/blog/470909/201304/09142332-2dd055f668444f998685034a733f72cc.jpg)
4.使用gray码进行对比,如何判断“空”与“满”
使用gray码解决了一个问题,但同时也带来另一个问题,即在格雷码域如何判断空与满。
对于“空”的判断依然依据二者完全相等(包括MSB);
而对于“满”的判断,如下图,由于gray码除了MSB外,具有镜像对称的特点,当读指针指向7,写指针指向8时,除了MSB,其余位皆相同,不能说它为满。因此不能单纯的只检测最高位了,在gray码上判断为满必须同时满足以下3条:
- wptr和同步过来的rptr的MSB不相等,因为wptr必须比rptr多折回一次。
- wptr与rptr的次高位不相等,如上图位置7和位置15,转化为二进制对应的是0111和1111,MSB不同说明多折回一次,111相同代表同一位置。
- 剩下的其余位完全相等。
![](http://images.cnitblog.com/blog/470909/201304/09142452-504d231d43c942b5bf3e311138264c04.jpg)
总体实现
系统的总体框图如下:
![](http://images.cnitblog.com/blog/470909/201304/09142554-b1cc1fcef63e460e9c0186beebbc53ef.jpg)
1)顶层模块
- module AsyncFIFO
- #(parameter ASIZE = 4, //地址位宽
- parameter DSIZE = 8) //数据位宽
- (
- input [DSIZE-1:0] wdata,
- input winc, wclk, wrst_n, //写请求信号,写时钟,写复位
- input rinc, rclk, rrst_n, //读请求信号,读时钟,读复位
- output [DSIZE-1:0] rdata,
- output wfull,
- output rempty
- );
-
- wire [ASIZE-1:0] waddr, raddr;
- wire [ASIZE:0] wptr, rptr, wq2_rptr, rq2_wptr;
-
- /************************************************************
- * In order to perform FIFO full and FIFO empty tests using
- * this FIFO style, the read and write pointers must be
- * passed to the opposite clock domain for pointer comparison
- *************************************************************/
-
- /*在检测“满”或“空”状态之前,需要将指针同步到其它时钟域时,使用格雷码,可以降低同步过程中亚稳态出现的概率*/
-
- sync_r2w I1_sync_r2w(
- .wq2_rptr(wq2_rptr),
- .rptr(rptr),
- .wclk(wclk),
- .wrst_n(wrst_n)
- );
-
- sync_w2r I2_sync_w2r (
- .rq2_wptr(rq2_wptr),
- .wptr(wptr),
- .rclk(rclk),
- .rrst_n(rrst_n)
- );
-
- /* DualRAM */
-
- DualRAM #(DSIZE, ASIZE) I3_DualRAM(
- .rdata(rdata),
- .wdata(wdata),
- .waddr(waddr),
- .raddr(raddr),
- .wclken(winc),
- .wclk(wclk)
- );
-
- /* 空、满比较逻辑 */
-
- rptr_empty #(ASIZE) I4_rptr_empty(
- .rempty(rempty),
- .raddr(raddr),
- .rptr(rptr),
- .rq2_wptr(rq2_wptr),
- .rinc(rinc),
- .rclk(rclk),
- .rrst_n(rrst_n)
- );
-
- wptr_full #(ASIZE) I5_wptr_full(
- .wfull(wfull),
- .waddr(waddr),
- .wptr(wptr),
- .wq2_rptr(wq2_rptr),
- .winc(winc),
- .wclk(wclk),
- .wrst_n(wrst_n)
- );
-
- endmodule
2)DualRAM模块
- module DualRAM
- #(
- parameter DATA_SIZE = 8, // 数据位宽
- parameter ADDR_SIZE = 4 // 地址位宽
- )
- (
- input wclken,wclk,
- input [ADDR_SIZE-1:0] raddr, //RAM read address
- input [ADDR_SIZE-1:0] waddr, //RAM write address
- input [DATA_SIZE-1:0] wdata, //data input
- output [DATA_SIZE-1:0] rdata //data output
- );
-
- localparam RAM_DEPTH = 1 << ADDR_SIZE; //RAM深度 = 2^ADDR_WIDTH
-
- reg [DATA_SIZE-1:0] Mem[RAM_DEPTH-1:0];
-
- always@(posedge wclk)
- begin
- if(wclken)
- Mem[waddr] <= wdata;
- end
-
- assign rdata = Mem[raddr];
-
- endmodule
3)同步模块
- module sync_r2w
- #(parameter ADDRSIZE = 4)
- (
- output reg [ADDRSIZE:0] wq2_rptr,
- input [ADDRSIZE:0] rptr,
- input wclk,
- input wrst_n
- );
-
- reg [ADDRSIZE:0] wq1_rptr;
-
- always @(posedge wclk or negedge wrst_n)
- if(!wrst_n) begin
- {wq2_rptr,wq1_rptr} <= 0;
- end
- else begin
- {wq2_rptr,wq1_rptr} <= {wq1_rptr,rptr};
- end
-
- endmodule
4)同步模块2
- module sync_w2r
- #(parameter ADDRSIZE = 4)
- (
- output reg [ADDRSIZE:0] rq2_wptr,
- input [ADDRSIZE:0] wptr,
- input rclk, rrst_n
- );
- reg [ADDRSIZE:0] rq1_wptr;
-
- always @(posedge rclk or negedge rrst_n)
- if (!rrst_n) begin
- {rq2_wptr,rq1_wptr} <= 0;
- end
- else begin
- {rq2_wptr,rq1_wptr} <= {rq1_wptr,wptr};
- end
-
- endmodule
5)空判断逻辑
- module rptr_empty
- #(parameter ADDRSIZE = 4)
- (
- output reg rempty,
- output [ADDRSIZE-1:0] raddr,
- output reg [ADDRSIZE :0] rptr,
- input [ADDRSIZE :0] rq2_wptr,
- input rinc,
- input rclk,
- input rrst_n
-
- );
-
-
- reg [ADDRSIZE:0] rbin;
- wire [ADDRSIZE:0] rgraynext, rbinnext;
- wire rempty_val;
-
- //-------------------
- // GRAYSTYLE2 pointer: gray码读地址指针
- //-------------------
-
- always @(posedge rclk or negedge rrst_n)
- if(!rrst_n) begin
- rbin <= 0;
- rptr <= 0;
- end
- else begin
- rbin <= rbinnext ;
- rptr <= rgraynext;
- end
-
- // gray码计数逻辑
-
- assign rbinnext = !rempty ? (rbin + rinc) : rbin;
- assign rgraynext = (rbinnext>>1) ^ rbinnext; //二进制到gray码的转换
- assign raddr = rbin[ADDRSIZE-1:0];
-
- //---------------------------------------------------------------
- // FIFO empty when the next rptr == synchronized wptr or on reset
- //---------------------------------------------------------------
-
- /*
- * 读指针是一个n位的gray码计数器,比FIFO寻址所需的位宽大一位
- * 当读指针和同步过来的写指针完全相等时(包括MSB),说明二者折回次数一致,FIFO为空
- */
-
- assign rempty_val = (rgraynext == rq2_wptr);
-
- always @(posedge rclk or negedge rrst_n)
- if(!rrst_n) begin
- rempty <= 1'b1;
- end
- else begin
- rempty <= rempty_val;
- end
-
- endmodule
6)满判断逻辑
- module wptr_full
- #(
- parameter ADDRSIZE = 4
- )
- (
- output reg wfull,
- output [ADDRSIZE-1:0] waddr,
- output reg [ADDRSIZE :0] wptr,
- input [ADDRSIZE :0] wq2_rptr,
- input winc,
- input wclk,
- input wrst_n
- );
-
- reg [ADDRSIZE:0] wbin;
- wire [ADDRSIZE:0] wgraynext, wbinnext;
- wire wfull_val;
-
- // GRAYSTYLE2 pointer
-
- always @(posedge wclk or negedge wrst_n)
- if(!wrst_n) begin
- wbin <= 0;
- wptr <= 0;
- end
- else begin
- wbin <= wbinnext;
- wptr <= wgraynext;
- end
-
- //gray 码计数逻辑
-
- assign wbinnext = !wfull ? wbin + winc : wbin;
- assign wgraynext = (wbinnext>>1) ^ wbinnext;
- assign waddr = wbin[ADDRSIZE-1:0];
-
- /*由于满标志在写时钟域产生,因此比较安全的做法是将读指针同步到写时钟域*/
-
- //------------------------------------------------------------------
- // Simplified version of the three necessary full-tests:
- // assign wfull_val=((wgnext[ADDRSIZE] !=wq2_rptr[ADDRSIZE] ) &&
- // (wgnext[ADDRSIZE-1] !=wq2_rptr[ADDRSIZE-1]) &&
- // (wgnext[ADDRSIZE-2:0]==wq2_rptr[ADDRSIZE-2:0]));
- //------------------------------------------------------------------
-
- assign wfull_val = (wgraynext=={~wq2_rptr[ADDRSIZE:ADDRSIZE-1],
- wq2_rptr[ADDRSIZE-2:0]});
-
- always @(posedge wclk or negedge wrst_n)
- if(!wrst_n) begin
- wfull <= 1'b0;
- end
- else begin
- wfull <= wfull_val;
- end
-
- endmodule
P.S : 在quartus中有异步FIFO IP核,为安全起见推荐使用IP核定制FIFO,本文的目的只是作为思路参考。
网友反馈
写得很好,有一个小错误,指出来:
读指针:总是指向下一个将要被写入的单元,复位时,指向第1个单元(编号为0)。
写指针:总是指向当前要被读出的数据,复位时,指向第1个单元(编号为0)
这里的“读指针”和“写指针”反了,上面那个应该是写指针,下面的是读指针