异步FIFO的原理以及可综合的Verilog代码
一、FIFO的定义
_ First In First Out 【FIFO】先进先出的缓存器,它与普通存储器的区别是没有外部读写地址线,这样使用起来非常简单,但缺点就是只能顺序写入数据,并顺序读出先写入的数据,其数据地址由内部读写指针自动加1完成,不能像普通存储器可以根据地址对读取或写入某个指定的地址位上的数据信息。
二、FIFO的应用场景
— 数据缓存
比如当数据发送侧写入数据后,接收侧不能实时的对数据进行读取就可以对数据暂存到FIFO内,当接收侧准备完毕后,就可以顺序读取FIFO内相应的数据。
— 位宽转换
比如单片机位8位数据输出,而DSP可能是16位数据输入,在单片机与DSP连接时就可以使用FIFO来达到数据匹配的目的。
— 速率匹配\异步处理
当数据的读写时钟相互异步,不一致时,而这种情况在集成芯片内对这种跨时钟域处理数据的情况非常多,而异步FIFO是一个可以很好解决这个问题的方案。
三、FIFO的分类
根据FIFO工作的时钟域,可以将FIFO分为同步FIFO和异步FIFO。
- 同步FIFO是指读时钟和写时钟为同一个时钟。在时钟沿来临时同时发生读写操作。
- 异步FIFO是指读写时钟不一致,读写时钟是互相独立的。
FIFO的参数
- FIFO的位宽:表示一次性读取数据位的宽度(N),与通常所表示的32位、64位单片机含义是一样的。
- 读时针:读数据时所采用的时钟,在时钟上升沿进行从FIFO内读取读指针所对应地址的数据。
- 写时钟:写数据时所采用的时钟,在时钟上升沿时将数据写入写指针所对应地址的FIFO内存内。
- FIFO的深度(Deep):表示的FIFO可以存取多少个N位数据。如果FIFO的位宽为N=8bit,FIFO的深度为Deep=16,就表示此FIFO可以存储16个8位的数据。也就是说此FIFO的内存大小为8*16bit。
- 读指针:指向下一个要读的数据地址,读完后地址指针自动加一。
- 写指针:指向下一个要写的数据地址,写完后地址指针自动加一。
- 空标致(empty):如果将读、写指针类似成两个人在圆形操场内跑圈,如果读写指针跑的圈数一样(写指针没有领先读指针一圈的情况下),当读指针追上了写指针,表示FIFO内没有数据了,已经被读指针全部读出并送出数据, FIFO就会发出空标致信号,禁止再进行读数据操作。(可以想像成写指针一直领先读指针,因为只有先在FIFO内写入数据,才能进行相应的读数据操作)
- 满标致:表示FIFO已经将最后一个8位内存写入数据,也就是写指针已经超过读指针一圈,并追上了读指针,此时FIFO会发出满标致信号,禁止再进行写操作。
FIFO的设计难点
- 空满信号的判断:对于一个FIFO的设计其难点在于对其空、满状态的准确判断。当进行读操作时,根据FIFO发出的空信号,不至于产生在FIFO已空状态下,进行读操作,造成读空情况的发生; 当进行写操作时,根据FIFO发出的满信号,不至于产生在FIFO已满的状态下,进行写入数据的操作,造成数据溢出的情况发生。所以对FIFO进行正确的空、满状态判断是进行FIFO设计的重点及难点。
Binary | Gray |
---|---|
0_000 | 0_000 |
0_001 | 0_001 |
0_010 | 0_011 |
0_011 | 0_010 |
0_100 | 0_110 |
0_101 | 0_111 |
0_110 | 0_101 |
0_111 | 0_100 |
1_000 | 1_100 |
1_001 | 1_101 |
1_010 | 1_111 |
1_011 | 1_110 |
1_100 | 1_010 |
1_101 | 1_011 |
1_110 | 1_001 |
1_111 | 1_000 |
- 在同步FIFO中,读写时钟为同一时钟,在实际硬件电路设计中,用的到比较少,而且空满判断比较简单,这里不做介绍。而异步FIFO芯片以及FPGA硬件电路设计中是一种常用的数据缓存方式。异步在产生满信号时,需要将读时钟域内的读指针同步到写时钟域内。如果采用Binary(二进制)做为地址指针,那么相邻位码元之间变化会有多个Bit位发生变化(例如:1到2,二进制0_001变为0_010有两bit位发生变化),如上表所示:(为二进制与格雷码变化对应表),每一时钟地址指针发生多bit位变化,会大大增加亚稳态发生的概率,且增大功耗。而格雷码是相邻两个码元之间只有一Bit位发生变化,而在芯片IC设计及FPGA设计异步FIFO中通常采用格雷码做为指针地址,这样大大降低亚稳态情况的发生。
在实际设计中通常采用N+1位格雷码做为地址指针,在读写时钟进行指针同步,以产生空满信号。用N位二进制地址进行对FIFO内数据进行读写。如果感兴趣的可以深入看一下Clifford E的文章,看完之后会对异步FIFO有深入的理解。相应文章链接如下。
超经典的异步 FIFO文章讲解,如果你读懂了异步你肯定懂了
对于Verilog的讲解一定要看程序 ,不然不会明白 了,大家有什么想知道的,不明白的可以评论说明,大家一直学习。 - 采用Gray2的形式的代码如下:
`timescale 1ns / 1ps
//
// Company:
// Engineer:
//
// Create Date: 22:30:20 08/04/2020
// Design Name:
// Module Name: async_fifo
// Project Name:
// Target Devices:
// Tool versions:
// Description:
//
// Dependencies:
//
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
//
//
module async_fifo #(parameter DATASIZE = 8,
parameter ADDRSIZE = 4
//parameter DEEP = 32
)
(
input wire wclk,
input wire rclk,
input wire wrst_n,
input wire rrst_n,
input wire din_vld,
input wire [DATASIZE-1:0] din_data,
output reg rempty,
output reg ral_empty,
output wire dout_vld,
output reg wfull,
output reg wal_full,
output wire [DATASIZE-1:0] dout_data
);
wire [ADDRSIZE-1:0] waddr ;
wire [ADDRSIZE-1:0] raddr ;
reg [ADDRSIZE :0] waddr_bin ;
reg [ADDRSIZE :0] raddr_bin ;
wire wfull_val ;
wire rempty_val ;
reg [ADDRSIZE :0] rptr ;
reg [ADDRSIZE :0] wq1_rptr ;
reg [ADDRSIZE :0] wq2_rptr ;
reg [ADDRSIZE :0] wptr ;
reg [ADDRSIZE :0] rq1_wptr ;
reg [ADDRSIZE :0] rq2_wptr ;
wire [ADDRSIZE :0] rgray_next ;
wire [ADDRSIZE :0] wgray_next ;
wire [ADDRSIZE :0] wbin_next ;
wire [ADDRSIZE :0] rbin_next ;
localparam DEPTH = 1 << ADDRSIZE;
reg [DATASIZE-1 :0] ram [0: DEPTH - 1];
//write and read data from ram
assign dout_data = ram[raddr];
always @(posedge wclk)
if(din_vld && !wfull) ram[waddr] <= din_data;
//radr_gary syco to write clk
always @(posedge wclk or negedge wrst_n)
begin
if(!wrst_n)
begin
wq1_rptr <= 0;
wq2_rptr <= 0;
end
else
begin
wq1_rptr <= rptr;
wq2_rptr <= wq1_rptr;
end
end
//wadr_gray syco to read clk
always @(posedge rclk or negedge rrst_n)
begin
if(!rrst_n)
begin
rq1_wptr <= 0;
rq2_wptr <= 0;
end
else
begin
rq1_wptr <= wptr;
rq2_wptr <= rq1_wptr;
end
end
//generating empty
assign rempty_val = (rgray_next == rq2_wptr);
always @(posedge rclk or negedge rrst_n)
if(!rrst_n) rempty <= 1'b1;
else rempty <= rempty_val;
//generating full
//assign wfull_val = ((wgnext[DATASIZE : DATASIZE-1] != wq2_rptr[DATASIZE : DATASIZE-1]) &&
// (wgnext[DATASIZE-2 : 0] == wq2_rptr[DATASIZE-2 : 0]));
assign wfull_val = (wgray_next == {~wq2_rptr[ADDRSIZE : ADDRSIZE-1],
wq2_rptr[ADDRSIZE-2 : 0]});
always @(posedge wclk or negedge wrst_n)
if(!wrst_n) wfull <= 1'b0;
else wfull <= wfull_val;
//read point
always @(posedge rclk or negedge rrst_n)
begin
if(!rrst_n) begin
raddr_bin <= 0;
rptr <= 0;
end
else begin
raddr_bin <= rbin_next;
rptr <= rgray_next;
end
end
assign raddr = raddr_bin[ADDRSIZE-1 : 0];
assign rbin_next = raddr_bin + (din_vld && ~rempty);
assign rgray_next = (rbin_next>>1) ^ rbin_next;
//write point
always @(posedge wclk or negedge wrst_n)
begin
if(!wrst_n) begin
waddr_bin <= 0;
wptr <= 0;
end
else begin
waddr_bin <= wbin_next;
wptr <= wgray_next;
end
end
assign waddr = waddr_bin[ADDRSIZE-1 : 0] ;
assign wbin_next = waddr_bin + (din_vld && ~wfull) ;
//bin to gray
assign wgray_next = (wbin_next >>1 ) ^ wbin_next ;
endmodule