FIFO即First In First Out,是一种先进先出数据存储、缓冲器,我们知道一般的存储器是用外部的读写地址来进行读写,而FIFO这种存储器的结构并不需要外部的读写地址而是通过自动的加一操作来控制读写,这也就决定了FIFO只能顺序的读写数据。下面我们就介绍一下同步FIFO和异步FIFO。
1、FIFO分类
同步FIFO,读和写应用同一个时钟。它的作用一般是做交互数据的一个缓冲,也就是说它的主要作用就是一个buffer。
异步FIFO,读写应用不同的时钟,它有两个主要的作用,一个是实现数据在不同时钟域进行传递,另一个作用就是实现不同数据宽度的数据接口。2、FIFO的主要参数
同步FIFO和异步FIFO略有不同,下面的参数适用于两者。
- 宽度,用参数FIFO_data_size表示,也就是FIFO存储的数据宽度;
- 深度,用参数FIFO_addr_size表示,也就是地址的大小,也就是说能存储多少个数据;
- 满标志,full,当FIFO中的数据满了以后将不再能进行数据的写入;
- 空标志,empty,当FIFO为空的时候将不能进行数据的读出;
- 写地址,w_addr,由自动加一生成,将数据写入该地址;
- 读地址,r_addr,由自动加一生成,将该地址上的数据读出;
同步FIFO和异步FIFO的最主要的不同就体现在空满标志产生的方式上,由此引出两者一些不同的参数。
同步FIFO- 时钟,clk,rst,读写应用同一个时钟;
- 计数器,count,用计数器来进行空满标志的判断;
异步FIFO
- 时钟,clk_w,rst_w,clk_r,rst_r,读写应用不同的时钟;
- 指针,w_pointer_gray,r_pointer_gray,用指针来判断空满标识;
同步指针,w_pointer_gray_sync,r_pointer_gray_sync,指针的同步操作,用来做对比产生空满标志符;
3、同步FIFO
FIFO主要的设计难点在于如何产生空满标志,在同步FIFO中,我们定义一个计数器,当计数器的值为0时,产生空标志,当计数器的值为FIFO的深度时,产生满标志。基于以上的思想,可以将同步FIFO划分为以下几个模块:write、read、count、RAM。
3.1 模块划分
同步FIFO主要划分为四个模块,RAM模块是用来读取和写入数据;write模块是用来产生写地址;read模块是用来产生读地址;count模块是用来产生空满标志符,每写入一位数,count加一,每读出一位数,count减一。
下面是各个模块的连接框图:3.2 同步FIFO代码
源文件
module FIFO_sync( clk, rst, w_en, r_en, data_in, data_out, count, full, empty ); parameter FIFO_data_size=3, FIFO_addr_size=2; input clk,rst; input w_en,r_en; input[FIFO_data_size-1:0] data_in; output[FIFO_data_size-1:0] data_out; output full,empty; output[FIFO_addr_size:0]count; reg [FIFO_data_size-1:0] data_out; reg [FIFO_addr_size:0]count; reg [FIFO_addr_size-1:0]w_addr,r_addr; reg [FIFO_data_size-1:0]mem[{FIFO_addr_size{1'b1}}:0]; integer i; //memory的初始化以及写操作 always@(posedge clk or negedge rst) begin if(!rst) begin w_addr<=0; for(i=0;i<={FIFO_addr_size{1'b1}};i=i+1) mem[i]<={FIFO_data_size{1'b0}}; end else if(w_en&(~full)) begin mem[w_addr]<=data_in; w_addr<=w_addr+1; end end //读操作 always@(posedge clk or negedge rst) begin if(!rst) begin data_out<={(FIFO_data_size-1){1'b0}}; r_addr<=0; end else if(r_en&(~empty)) begin data_out<=mem[r_addr]; r_addr<=r_addr+1; end end //count产生空满标志符 always@(posedge clk or negedge rst) begin if(!rst) count<=0; else if(((w_en)&(~full))&(~((r_en)&(~empty)))) count<=count+1; else if(((r_en)&(~empty))&(~((w_en)&(~full)))) count<=count-1; end assign empty=(count==0); assign full=(count=={FIFO_addr_size{1'b1}}+1); endmodule
测试代码
`timescale 1ns/1ns module FIFO_sync_top; reg clk,rst,w_en,r_en; reg[2:0]data_in; wire[2:0]count; wire[2:0]dtat_out; reg[2:0]i; initial begin clk=0; rst=1; data_in=3'b000; w_en=0; r_en=0; #25 rst=0; #50 rst=1; #25 w_en=1; #100 r_en=1; #100 w_en=0; r_en=0; #100 w_en=1; #400 r_en=1; end initial begin for(i=0;i<=50;i=i+1) #100 data_in=i; end always #50 clk=~clk; FIFO_sync #(.FIFO_data_size(3),.FIFO_addr_size(2)) ut( .clk(clk), .rst(rst), .data_in(data_in), .data_out(data_out), .w_en(w_en), .r_en(r_en), .count(count), .full(full), .empty(empty) ); endmodule
4、异步FIFO
异步FIFO的设计难点在于空满标志符的产生,由于异步FIFO的读写是用不同的时钟来控制的,所以不能采用计数器的方法来产生空满标志符,就好像同一个变量不能再两个always块里赋值一样,所以我们必须寻求新的方法来产生空满标志符。
4.1 空满标志
我们知道FIFO的状态是满还是空,他们的相同的判断条件都是w_addr=r_addr,但到底是空还是满我们还不能确定。在这里介绍一种方法来判断空满状态。我们设定一个指针r_pointer_bin,w_pointer_bin,宽度为[FIFO_addr_size:0],也就是说比传统的地址多一位,我们就用这多出来的一位做空满判断。
- 如果是满状态的话,也就是说w_pointer_bin比r_pointer_bin多走了一圈,反应在数值上就是w_pointer_bin和r_pointer_bin的最高位不相同。
- 如果是空状态的话,也就是说w_pointer_bin和r_pointer_bin的路径相同,反应在数值上就是w_pointer_bin和r_pointer_bin的每一位相等。
如下例子所示:
FIFO_addr_size=2;FIFO_data_size=3;
4.2 格雷码
将一个时钟域上的指针r_pointer_bin/w_pointer_bin同步到另一个时钟域,如果数据用二进制的方式进行同步的话就会出现多位数据同时跳变的问题,比如3'b011到3'b100即3到4跳变会引起多位数据的改变,这样会大大增加出错的概率。Gray 码就很好的解决了上述问题,gray码相邻数据只有一位跳变,这样就大大降低了数据出错的概率。下面 以一个例子介绍一下二进制码向格雷码的转化的算法。
在不同时钟域进行数据交换的时候我们一般采用格雷码的数据形式进行数据传递,这样能很大程度上降低出错的概率。 引入格雷码同时也引入一个问题,就是数据空满标志的判断不再是二进制时候的判断标准。- 如果是空状态的话,无可厚非,仍然是要满足r_pointer_gray和w_pointer_gray每一位都相等。
- 如果是满状态的话,我们以二进制为例,应该满足r_pointer_bin=3'b111,w_pointer_bin=3'b011,相对应的格雷码应该满足r_pointer_gray=3'b100,w_pointer_gray=3'b010,通俗来讲,满状态要满足r_pointer_gray和w_pointer_gray的高位和次高位相反,其余各位相等。
同时由于格雷码的引入,使得FIFO的深度只能是2的幂次方。
4.3 数据同步
我们知道满状态以后数据就不能进行写入,空状态以后数据就不能进行读出。由此,我们在write模块进行满状态的判断,在read模块进行空状态的判断。
- 在满状态的判断时,我们要用到r_pointer_gray,为了避免亚稳态,选择两级D触发器相串联构成的同步模块来传送r_pointer_gray,最后用r_pointer_gray_sync和w_pointer_gray相比较产生full信号。
- 在空状态的判断时,同理我们要用w_pointer_gray_sync和r_pointer_gray相比较产生empty信号。
两拍延时的数据同步对空满标志产生的影响
由此信号r_pointer_gray经过两级D触发器,就会有两拍的延时形成r_pointer_gray_sync信号,所以在进行比较的时候就不是实时的r_pointer_gray与w_pointer_gray进行比较,而是两拍之前的r_pointer_gray即r_pointer_gray_sync与此刻的w_pointer_gray进行比较。那么问题就来了这与我们的本意其实是不相符的,其实是这样的,这是一种最坏情况的考虑,将r_pointer_gray_sync与w_pointer_gray相比较是为了产生full信号,在用于数据同步的这两拍里面有可能再进行读操作,所以用于比较时的读地址一定小于或等于当前的读地址,就算此刻产生full信号,其实FIFO有可能还没有满。这也就为设计留了一些设计的余量。同理,就算有empty信号的产生,FIFO有可能还有数据。这种留余量的设计在实际的工程项目中是很常见的。4.4 模块的划分
异步FIFO将模块划分为4个部分,RAM、write_full、read_empty、synchronization。RAM根据读写地址进行数据的写入和读出,write_full根据clk_w产生写地址和full信号,read_empty根据clk_r产生读地址和empty信号,synchronization用于同步w_pointer_gray到读时钟域或者同步r_pointer_gray到写时钟域。
下面是各个模块的连接图:4.5 异步FIFO代码
源文件
顶层文件
module FIFO_async( clk_w, rst_w, w_en, clk_r, rst_r, r_en, data_in, data_out, empty, full ); parameter FIFO_data_size=6; parameter FIFO_addr_size=5; input clk_w,rst_w,w_en; input clk_r,rst_r,r_en; input[FIFO_data_size-1:0]data_in; output[FIFO_data_size-1:0]data_out; output empty,full; wire[FIFO_addr_size:0]r_pointer_gray_sync,w_pointer_gray_sync; wire[FIFO_addr_size:0]r_pointer_gray,w_pointer_gray; wire[FIFO_addr_size-1:0]w_addr,r_addr; RAM #(FIFO_data_size,FIFO_addr_size) I0(.clk_w(clk_w), .rst_w(rst_w), .clk_r(clk_r), .rst_r(rst_r), .full(full), .empty(empty), .w_en(w_en), .r_en(r_en), .w_addr(w_addr), .r_addr(r_addr), .data_in(data_in), .data_out(data_out)); synchronization #(FIFO_addr_size) I1(.clk(clk_r), .rst(rst_r), .din(w_pointer_gray), .dout(w_pointer_gray_sync)); synchronization #(FIFO_addr_size) I2(.clk(clk_w), .rst(rst_w), .din(r_pointer_gray), .dout(r_pointer_gray_sync)); write_full #(FIFO_addr_size) I3(.clk_w(clk_w), .rst_w(rst_w), .w_en(w_en), .r_pointer_gray_sync(r_pointer_gray_sync), .w_pointer_gray(w_pointer_gray), .w_addr(w_addr), .full(full)); read_empty #(FIFO_addr_size) I4(.clk_r(clk_r), .rst_r(rst_r), .r_en(r_en), .w_pointer_gray_sync(w_pointer_gray_sync), .r_pointer_gray(r_pointer_gray), .r_addr(r_addr), .empty(empty)); endmodule
RAM
module RAM( clk_w, rst_w, clk_r, rst_r, full, empty, w_en, r_en, r_addr, w_addr, data_in, data_out );parameter FIFO_data_size=3, FIFO_addr_size=2; input clk_w,rst_w;input clk_r,rst_r;input w_en,r_en;input full,empty;input [FIFO_addr_size-1:0]w_addr,r_addr;input [FIFO_data_size-1:0]data_in;output[FIFO_data_size-1:0]data_out;reg[FIFO_data_size-1:0]data_out;reg[FIFO_data_size-1:0]mem[{FIFO_addr_size{1'b1}}:0];integer i;always@(posedge clk_w or negedge rst_w)begin if(!rst_w)for(i=1;i<=FIFO_data_size;i=i+1)mem[i]<={FIFO_data_size{1'b0}}; else if((w_en==1)&&(full==0))mem[w_addr]<=data_in; endalways@(posedge clk_r or negedge rst_r) begin if(!rst_r) data_out<={(FIFO_data_size-1){1'b0}}; else if((r_en==1)&&(empty==0)) data_out<=mem[r_addr]; end endmodule
write_full
module write_full( clk_w, rst_w, w_en, r_pointer_gray_sync, //w_pointer_bin, w_pointer_gray, w_addr, full ); parameter FIFO_addr_size=2; input clk_w,rst_w,w_en; input [FIFO_addr_size:0]r_pointer_gray_sync; output full; output [FIFO_addr_size-1:0]w_addr; output [FIFO_addr_size:0]w_pointer_gray; reg [FIFO_addr_size:0]w_pointer_bin; wire [FIFO_addr_size:0]w_pointer_gray; wire [FIFO_addr_size-1:0]w_addr; always@(posedge clk_w or negedge rst_w) begin if(!rst_w) w_pointer_bin<={(FIFO_addr_size){1'b0}}; else if((w_en==1)&&(full==0)) w_pointer_bin<=w_pointer_bin+1; end assign w_pointer_gray=(w_pointer_bin>>1)^w_pointer_bin; assign w_addr=w_pointer_bin[FIFO_addr_size-1:0];assign full=w_pointer_gray=={~r_pointer_gray_sync[FIFO_addr_size:FIFO_addr_size-1],r_pointer_gray_sync[FIFO_addr_size-2:0]}? 1:0;endmodule
read_empty
module read_empty( clk_r, rst_r, r_en, w_pointer_gray_sync, // r_pointer_bin, r_pointer_gray, r_addr, empty ); parameter FIFO_addr_size=2; input clk_r,rst_r,r_en; input [FIFO_addr_size:0]w_pointer_gray_sync; output empty; output [FIFO_addr_size-1:0]r_addr; output [FIFO_addr_size:0]r_pointer_gray; reg [FIFO_addr_size:0]r_pointer_bin; wire [FIFO_addr_size:0]r_pointer_gray; wire [FIFO_addr_size-1:0]r_addr; always@(posedge clk_r or negedge rst_r) begin if(!rst_r) r_pointer_bin<={(FIFO_addr_size){1'b0}}; else if((r_en==1)&&(empty==0)) r_pointer_bin<=r_pointer_bin+1;end assign r_pointer_gray=(r_pointer_bin>>1)^r_pointer_bin; assign r_addr=r_pointer_bin[FIFO_addr_size-1:0];assign empty=r_pointer_gray==w_pointer_gray_sync?1:0;endmodule
synchroization
module synchronization( clk, rst, din, dout ); parameter FIFO_addr_size=2; input clk,rst; input[FIFO_addr_size:0] din; output[FIFO_addr_size:0] dout; reg[FIFO_addr_size:0] dout; reg [FIFO_addr_size:0] dout1; always@(posedge clk or negedge rst) beginif(!rst) begin dout<={(FIFO_addr_size+1){1'b0}}; dout1<={(FIFO_addr_size+1){1'b0}};end elsebegin dout1<=din;dout<=dout1; endend endmodule
测试文件
`timescale 1ns/1ns module FIFO_async_top; parameter FIFO_data_size=3, FIFO_addr_size=2; reg clk_r,rst_r,w_en,r_en,clk_w,rst_w; reg[FIFO_data_size-1:0]data_in; wire[FIFO_addr_size-1:0]data_out; wire empty,full; reg[FIFO_data_size-1:0]i; initial begin clk_w=0; rst_w=1; data_in={FIFO_data_size{1'b0}}; #15 rst_w=0; #20 rst_w=1; end initial begin clk_r=0; rst_r=1; r_en=0; #25 rst_r=0; #50 rst_r=1; end initial begin w_en=0; #450 w_en=1; #400 w_en=0; #750 w_en=1; end initial begin r_en=0; #900 r_en=1; #400 r_en=0; #300 r_en=1; end initialbegin for(i=0;i<=50;i=i+1) #100 data_in=i;end always#25 clk_w=~clk_w;always #50 clk_r=~clk_r; FIFO_async #(.FIFO_data_size(FIFO_data_size),.FIFO_addr_size(FIFO_addr_size)) u1(.clk_w(clk_w), .rst_w(rst_w), .w_en(w_en), .clk_r(clk_r), .rst_r(rst_r), .r_en(r_en), .data_in(data_in), .data_out(data_out), .empty(empty), .full(full) ); endmodule
5、FIFO的深度计算
其实FIFO的深度可大可小,并没有一个具体的公式能够精确计算出FIFO深度的大小。在FIFO实际工作中,其数据的满/空标志可以控制数据的继续写入或读出。在一个具体的应用中也不可能由一些参数算数精确的所需FIFO深度为多少,这在写速度大于读速度的理想状态下是可行的,但在实际中用到的FIFO深度往往要大于计算值。一般来说根据电路的具体情况,在兼顾系统性能和FIFO成本的情况下估算一个大概的宽度和深度就可以了。而对于写速度慢于读速度的应用,FIFO的深度要根据读出的数据结构和读出数据的由那些具体的要求来确定。下面我们以一道简单的题目来估算一下FIFO的深度。
一个8bit宽的异步FIFO,输入时钟为100MHz,输出时钟为95MHz,设一个package为4Kbit,且两个package之间的发送间距足够大。问异步FIFO的深度。解答:8bit位宽的异步FIFO,一个package的大小为4※1024/8=512Word,100MHz的输入时钟,传送一个Word需要的时间为1/100MHz,则发送一个package需要的时间T=512/100MHz,95MHz的输出时钟,接受一个Word需要的时间为1/95MHz,发送一个package的时间所能接受的数据量为(512※95)/100word=486.4word,所以FIFO的深度至少为512-486.4=25.6=26。