赞
踩
FIFO是英文First In First Out 的缩写,是一种先进先出的数据缓存器,他与普通存储器的区别是没有外部读写地址线,这样使用起来非常简单,但缺点就是只能顺序写入数据,顺序的读出数据, 其数据地址由内部读写指针自动加1完成,不能像普通存储器那样可以由地址线决定读取或写入某个指定的地址。
FIFO的分类:
FIFO的分类根均FIFO工作的时钟域,可以将FIFO分为同步FIFO和异步FIFO。同步FIFO是指读时钟和写时钟为同一个时钟。在时钟沿来临时同时发生读写操作。异步FIFO是指读写时钟不一致,读写时钟是互相独立的。
若输入输出总线为同一时钟域,FIFO只是作为缓存使用,用同步FIFO即可,此时,FIFO在同一时钟下工作,FIFO的写使能、读使能、满信号、空信号、输入输出数据等各种信号都在同一时钟沿打入或输出。
若输入输出为不同时钟域,FIFO作时钟协同作用,需要采用异步FIFO,此时,FIFO在读与写分别在各自时钟下工作,FIFO的写使能、写满信号、输入数据等各种输入信号都在同一输入时钟沿打入或输出。读使能、读空信号、输出数据等各种输出信号都在同一输出时钟沿打入或输出。
1.读写指针的工作原理
写指针:总是指向下一个将要被写入的单元,复位时,指向第1个单元(编号为0)。
读指针:总是指向当前要被读出的数据,复位时,指向第1个单元(编号为0)。
2.FIFO的“空”/“满”检测
FIFO设计的关键:产生可靠的FIFO读写指针和生成FIFO“空”/“满”状态标志。
当读写指针相等时,表明FIFO为空,这种情况发生在复位操作时,或者当读指针读出FIFO中最后一个字后,追赶上了写指针时,如下图所示:
当读写指针再次相等时,表明FIFO为满,这种情况发生在,当写指针转了一圈,折回来(wrapped around)又追上了读指针,如下图:
为了区分到底是满状态还是空状态,可以采用以下方法:
在指针中添加一个额外的位(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为空;
异步设计读写时钟不一样,在产生读空信号和写满信号时,会涉及到跨时钟域的问题,如何解决?
跨时钟域的问题:由于读指针是属于读时钟域的,写指针是属于写时钟域的,而异步FIFO的读写时钟域不同,是异步的,要是将读时钟域的读指针与写时钟域的写指针不做任何处理直接比较肯定是错误的,因此我们需要进行同步处理以后仔进行比较
解决方法:加两级寄存器同步 + 格雷码(目的都是消除亚稳态)
使用异步信号进行使用的时候,好的设计都会对异步信号进行同步处理,同步一般采用多级D触发器级联处理,如下图。这种模型大部分资料都说的是第一级寄存器产生亚稳态后,第二级寄存器稳定输出概率为90%,第三极寄存器稳定输出的概率为99%,如果亚稳态跟随电路一直传递下去,那就会另自我修护能力较弱的系统直接崩溃。
二进制FIFO指针的考虑
将一个二进制的计数值从一个时钟域同步到另一个时钟域的时候很容易出现问题,因为采用二进制计数器时所有位都可能同时变化,在同一个时钟沿同步多个信号的变化会产生亚稳态问题。而使用格雷码只有一位变化,因此在两个时钟域间同步多个位不会产生问题。所以需要一个二进制到gray码的转换电路,将地址值转换为相应的gray码,然后将该gray码同步到另一个时钟域进行对比,作为空满状态的检测。
使用gray码解决了一个问题,但同时也带来另一个问题,即在格雷码域如何判断空与满。
对于“空”的判断依然依据二者完全相等(包括MSB);
而对于“满”的判断,如下图,由于gray码除了MSB外,具有镜像对称的特点,当读指针指向7,写指针指向8时,除了MSB,其余位皆相同,不能说它为满。因此不能单纯的只检测最高位了,在gray码上判断为满必须同时满足以下3条:
wptr和同步过来的rptr的MSB不相等,因为wptr必须比rptr多折回一次。
wptr与rptr的次高位不相等,如上图位置7和位置15,转化为二进制对应的是0111和1111,MSB不同说明多折回一次,111相同代表同一位置。
剩下的其余位完全相等。
上参考了博客上的已经实现的异步fifo代码:
module yfifo #(parameter width=8, parameter depth=5 ) (input wr_clk, input rd_clk, input wrst, input rrst, input wr_en, input rd_en, input[width-1:0]din, output[width-1:0]dout, output reg empty, output reg full ); // reg[width-1:0]mem[0:(1<<depth)-1]; reg[depth:0]wbin,rbin; reg[depth:0]wp,rp; reg[depth:0]wp_r1,wp_r2; reg[depth:0] rp_r1,rp_r2; wire empty_r1,full_r1; //实际的读写地址; wire[depth-1:0] waddr,raddr; wire[depth:0]wbin_next,rbin_next;//bincode wire[depth:0]wgray_next,rgray_next;//graycode //读写数据 always@(posedge wr_clk) begin if(wr_en&&!full) mem[waddr]<=din; end assign dout=(rd_en &&!empty)?mem[raddr] : 0; //generate waddr and raddr; //generate waddr always@(posedge wr_clk or negedge wrst) begin if(!wrst) begin wbin<=0; wp<=0; end else begin wbin<=wbin_next; wp<=wgray_next; end end assign waddr=wbin[depth-1:0]; assign wbin_next=wbin+(wr_en & ~full); assign wgray_next=wbin_next^(wbin_next>>1); //generate raddr always@(posedge rd_clk or negedge rrst) begin if(!rrst) begin rbin<=0; rp<=0; end else begin rbin<=rbin_next; rp<=rgray_next; end end assign rbin_next=rbin+(rd_en & !empty); assign rgray_next=rbin_next^(rbin_next>>1); assign raddr=rbin[depth-1:0]; //delay two clk always@(posedge rd_clk or negedge rrst) begin if(!rrst) begin wp_r1<=0; wp_r2<=0; end else begin wp_r1<=wp; wp_r2<=wp_r1; end end always@(posedge wr_clk or negedge wrst) begin if(!wrst) begin rp_r1<=0; rp_r2<=0; end else begin rp_r1<=rp; rp_r2<=rp_r1; end end //generate full and empty assign empty_r1=(wp_r2==rgray_next); always@(posedge rd_clk or negedge rrst) begin if(!rrst) empty<=1; else empty<=empty_r1; end assign full_r1=({~rp_r2[depth :depth -1],rp_r2[depth-2:0]} == wgray_next); always@(posedge wr_clk or negedge wrst) begin if(!wrst) full<=1; else full<=full_r1; end endmodule
testbench:
module yfifo_tb( ); reg wr_clk; reg rd_clk; reg wrst; reg rrst; reg wr_en; reg rd_en; reg[7:0]din; wire[7:0]dout; wire empty; wire full; yfifo yfifo_u( .wr_clk(wr_clk ), .rd_clk(rd_clk ), .wrst(wrst ), .rrst(rrst ), .wr_en(wr_en ), .rd_en(rd_en), .din(din), .dout(dout), .empty(empty), .full(full) ); initial begin wr_clk=0; rd_clk=0; wrst=0; rrst=0; wr_en=0; rd_en=0; #20 wrst=1; rrst=1; #80 wr_en=1; rd_en=0; #1000 wr_en=0; rd_en=1; #2000 $stop; end always@(posedge wr_clk) din<=($random)%256; always#10 wr_clk=!wr_clk; always#20 rd_clk=!rd_clk; endmodule
测试结果:
写使能拉高,32个数据开始写入:
第32个数据写入,fifo满,
读使能拉高,开始读数据
读完最后一个数据9d,fifo空
fifo写入第一个数据0d好像没读进来,不知道什么问题。
参考博文:https://www.cnblogs.com/ylsm-kb/p/9068449.html
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。