FIFO是一种先入先出的数据缓存器,他与普通存储器的区别是没有外部读写地址线,这样使用起来非常方便,但缺点是只能顺序的写入数据、读出数据,其内部地址是由内部读写指针自动加1完成,不能像普通存储器那样由地址线读取或者写入某个地址。
FIFO一般用于不同时钟域之间的数据传输,比如FIFO的一端是AD数据采集,另一端是计算机的PCI总线,假设AD采集的速率是16位100K sps,那么每秒的数据量为100K x 16bit=1.6Mbps,而PCI总线的速度为33MHZ,总线宽度是32bit,其最大传输速率为1056Mbps,在两个不同的时钟域间采用FIFO作为数据缓冲。另外对于不同宽度的数据接口也可以用FIFO,例如单片机的8位数据输出。
FIFO的分类根据FIFO工作的时钟域,分为同步FIFO和异步FIFO。同步FIFO是指读写时钟为同一时钟。在时钟沿来临时同时发生读写操作。异步FIFO是指读写时钟不一致,读写时钟相互独立。
FIFO设计要点:
1.读空信号如何产生?写满信号如何产生?(读空有两种情况,写满只有一种情况)
读空信号:复位的时候,读指针和写指针相等,读空信号有效。当读指针赶上写指针的时候,读指针等于写指针意味着最后一个数据被读完,此时读空信号有效。
写满信号:当写指针比读指针多一圈时,写指针等于读指针意味着写满了,此时写满信号有效。
2.如何区分读空和写满两种状态呢?
将指针位宽多定义一位。假设需要设计深度为8的FIFO,此时读写指针的位宽只需要3位就够用了,在设计时将指针位宽设计为4位,最高位的作用就是区分读空或者写满。
当最高位相同,其余位也相同的时候为读空状态
当最高位不同,其余位相同的情况为写满状态
采用格雷码的情况下:
在全部位相同的情况下,为读空状态
在前两位不同,其余位都相同的情况下为写满状态(原因与二进制和格雷码的转换关系有关)
3.异步FIFO设计时,读写时钟不一致,产生读空信号和写满信号时,会涉及到跨时钟域的问题,如何解决?
直接将不同时钟域里的读写指针不做任何处理直接比较肯定是错误的,因此我们需要进行同步以后进行比较。
解决办法:两级寄存器同步+格雷码
同步的过程有两个:
将写时钟域的写指针同步到读时钟域中,将同步后的写指针与读时钟域中的读指针进行比较产生读空信号
将读时钟域中的读指针同步到写时钟域中,将同步后的读指针与写时钟域中的写指针进行比较产生写满信号
同步的思想就是两级寄存器同步,简单的来说就是打两拍。(两级寄存器同步后不能确保信号的正确,只是降低了亚稳态传播的概率)
4.由于设计的时候读写指标的两级寄存器同步,同步会消耗两个时钟周期,势必会是判断空满会有所延迟,这会不会导致设计出错?
异步FIFO,同步后的写指针一定是小于或者等于当前实际值的,所以此时判断FIFO为空不一定为真空,这样更加保守,虽然会影响FIFO的性能,但并不会出错。
同步FIFO的实现代码:
1 module fifo( //信号定义---读、写、data_in,data_out,clk,复位,空,满
2 input [7:0] datain,
3 input rd,//读
4 input wr,//写
5 input rst,
6 input clk,
7 output [7:0] dataout,
8 output full,
9 output empty,
10 );
11
12 wire [7:0] dataout;//输出信号定义为wire类型 ,输入默认为wire
13 reg full_in,empty_in; (空满寄存器)
14 reg [7:0] mem [15:0];//RAM的深度和宽度
15
16 reg [3:0] rp,wp; //读写指针的寄存
17
18 //空满输出
19 assign full = full_in;
20 assign empty = empty_in;
21
22 //read
always@(posedge clk) begin
if(rd && ~empty_in) dataout <= men[rp];
end
23 //assign dataout = mem[rp];
24
25 //write
26 always@(posedge clk) begin
27 if(wr && ~full_in) mem[wp] <=datain;
28 end
29
30 // 读指针读完自增
31 always@(posedge clk or negedge rst) begin
32 if(!rst) rp<=0;
33 else begin
34 if(rd && ~empty_in) rp<=rp+1'b1;
35 end
36 //写指针自增
37 always@(posedge clk or negedge rst) begin
38 if (!rst) wp<=0;
39 else begin
40 if(wd && ~full_in) wp<=wp+1'b1;
41 end
42
43 //满状态生成
44 always@(posedge clk or negedge rst) begin
45 if(~rst) full_in<=0; //复位
46 else begin
47 if((~rd && wr)&& ((wp=rp-1)||(rp==4'0&&wp==4'hf))) //读指针在写指针之后,且下一个来临的是一个写信号,则写满
48 full_in<=1'b1;
49 else if (full_in && rd) full_in <=1'b0;//当满状态时来了读信号,则满状态拉低。
50 end
51 //空状态生成
52 always@(posedge clk or negedge rst)begin
53 if(~rst) empty_in<=1'b0;
54 else begin
55 if((rd && ~wr)&&(rp = wr -1 || (rp==4'hf&&wp==4'h0)))
56 empty_in<=0;
57 else if(empty_in && wr ) empty_in<=1'b0;
58 end
59 end
60 endmodule
异步FIFO的实现:
不同点在于增加了读写控制信号的跨时钟域的同步,此外判断空满也不同。
module fifo1(rdata, wfull, rempty, wdata, winc, wclk, wrst_n,rinc, rclk, rrst_n);
parameter DSIZE = 8; parameter ASIZE = 4;
output [DSIZE-1:0] rdata;
output wfull;
output rempty;
input [DSIZE-1:0] wdata;
input winc, wclk, wrst_n;
input rinc, rclk, rrst_n
reg wfull,rempty;
reg [ASIZE:0] wptr, rptr, wq2_rptr, rq2_wptr, wq1_rptr,rq1_wptr;
reg [ASIZE:0] rbin, wbin;
reg [DSIZE-1:0] mem[0:(1<<ASIZE)-1];
wire [ASIZE-1:0] waddr, raddr;
wire [ASIZE:0] rgraynext, rbinnext,wgraynext,wbinnext;
wire rempty_val,wfull_val;
//-----------------双口RAM存储器--------------------
assign rdata=mem[raddr];
always@(posedge wclk)
if (winc && !wfull) mem[waddr] <= wdata;
//-------------同步rptr 指针-------------------------
always @(posedge wclk or negedge wrst_n)
if (!wrst_n) {wq2_rptr,wq1_rptr} <= 0;
else {wq2_rptr,wq1_rptr} <= {wq1_rptr,rptr};
//-------------同步wptr指针---------------------------
always @(posedge rclk or negedge rrst_n)
if (!rrst_n) {rq2_wptr,rq1_wptr} <= 0;
else {rq2_wptr,rq1_wptr} <= {rq1_wptr,wptr};
//-------------rempty产生与raddr产生-------------------
always @(posedge rclk or negedge rrst_n) // GRAYSTYLE2 pointer
begin
if (!rrst_n) {rbin, rptr} <= 0;
else {rbin, rptr} <= {rbinnext, rgraynext};
end
// Memory read-address pointer (okay to use binary to address memory)
assign raddr = rbin[ASIZE-1:0];
assign rbinnext = rbin + (rinc & ~rempty);//下一个地址
assign rgraynext = (rbinnext>>1) ^ rbinnext;//求格雷码
// FIFO empty when the next rptr == synchronized wptr or on reset
assign rempty_val = (rgraynext == rq2_wptr);
always @(posedge rclk or negedge rrst_n)
begin
if (!rrst_n) rempty <= 1'b1;
else rempty <= rempty_val;
end
//---------------wfull产生与waddr产生------------------------------
always @(posedge wclk or negedge wrst_n) // GRAYSTYLE2 pointer
if (!wrst_n) {wbin, wptr} <= 0;
else {wbin, wptr} <= {wbinnext, wgraynext};
// Memory write-address pointer (okay to use binary to address memory)
assign waddr = wbin[ASIZE-1:0];
assign wbinnext = wbin + (winc & ~wfull);//地址自增
assign wgraynext = (wbinnext>>1) ^ wbinnext;
assign wfull_val = (wgraynext=={~wq2_rptr[ASIZE:ASIZE-1], wq2_rptr[ASIZE-2:0]}); //:ASIZE-1]
always @(posedge wclk or negedge wrst_n)
if (!wrst_n) wfull <= 1'b0;
else wfull <= wfull_val;
endmodule
二进制计数器存在的问题:
异步FIFO读写指针需要在数学上的操作和比较才能产生准确的空满标志位,但由于读写指针属于不同的时钟域及读写指针以及读写时钟相位不定的原因,同一模块采集另一时钟域的指针时,此指针有可能正处在跳变过程中,那么采集到的值很可能是不期望的值。当然,不期望的错误结果也会随着产生。
格雷码计数器
表现形式:最大的特点是在递增和递减过程中,每次只变化以为,这是它最大的优点。同时它也有自己的局限性,那就是循环计数深度必须是2的n次幂,否则就失去了每次只变化一位的特性。
二进制到格雷码的转换: gray = (b>>1)^b
实现