1. FIFO介绍
FIFO(First In First Out),是一种先进先出的数据缓存器,它与普通存储器的区别是没有外部读写地址线,但缺点就是只能顺序写入和顺序读出数据,其数据地址由内部读写指针自动加1完成,无法同普通存储器那样可以由地址线决定读取或写入某个指定的地址。
1.1 FIFO参数
- 宽度:FIFO每个地址写进数据的位宽(N);
- 深度:FIFO可以存储多少个N位的数据;
- 满标志:FIFO已满或将要满(伪full)时,由FIFO内部输出的一个信号,以阻止FIFO的写操作继续 向FIFO中写数据而造成溢出。
- 空标志:FIFO已空或将要空(伪empty)时,由FIFO内部输出的一个信号,以阻止FIFO的读操作继续从FIFO中读出数据而造成无效数据的读出。
- 读时钟:读操作所遵循的时钟,在每个时钟沿来临时读数据。
- 写时钟:写操作所遵循的时钟,在每个时钟沿来临时写数据。
1.2 full/empty 检测
为了理解FIFO设计,首先需要了解FIFO指针的工作原理。FIFO中的指针分为写指针和读指针,写指针总是指向要写入的下一个字。在复位时,两个指针都设置为零,这也恰好是要写入的下一个FIFO字位置。在FIFO写操作中,写指针所指向的存储单元被写入,然后写指针递增并指向要写入的下一个位置。
类似地,读指针总是指向要读取的当前FIFO字。在复位时,两个指针都复位为零,FIFO为空,读指针指向无效数据(因为FIFO为空,空标志置位)。一旦第一个数据写入FIFO,写指针递增,空标志就会清零,但是读指针仍指向第一个FIFO存储字内容,与此同时将第一个有效数据驱动到FIFO的数据输出端口,由接收器逻辑读取。读指针始终指向要读取的下一个FIFO数据意味着接收器逻辑不必使用两个时钟周期来读取数据。
empty信号检测:如下图所示,当读指针和写指针都相等时,FIFO为empty。这种情形出现在复位操作期读写指针复位为零时,或者当读指针赶上写指针时,此时读取FIFO中的最后一个字。
full信号检测:如下图所示,当指针再次相等时,即当写指针已回环一圈(指到达地址边界后,从起始位开始递增)并追赶到读指针时,FIFO处于full状态。
以上俩种情况都是读写指示信号相同时为full或empty,唯一不同的是,当FIFO为full状态时,写指示信号的循环次数与读指示信号循怀次数不同。因此,可以考虑在读写指示信号前加一个MSB指示信号,用于表示循怀次数是否一致。如:(用二进制表示addr)
wr:000 → 001 → 010 → 011 → 100 → 101 → 110 → 111 → 000 → 001→ 010 (未加入MSB)
wr:00+00 → 0001 → 0010 → 0011 → 0100 → 0101 → 0110 → 0111 → 1000 → 1001 → 1010 (加入MSB)
从而可以直接通过MSB是否相同知道读写指示信号的循环次数是否相同,判断full、empty状态。
1.3 同步FIFO和异步FIFO
FIFO依据读写时钟,将其分为同步FIFO和异步FIFO。同步FIFO是指读时钟和写时钟为同一个时钟,在时钟沿同时发生读写操作;异步FIFO是指读写时钟不一致,读写时钟是互相独立的,但也可以同时发生读写操作。
2. FIFO设计
2.1 二进制和格雷码
将一个二进制的值从一个时钟域同步到另一个时钟域容易出现问题,因为采用二进制计数器时所有位都可能同时变化,在同一个时钟沿同步多个信号的变化会产生亚稳态问题。而使用格雷码无论如何都只有一位变化,因此在两个时钟域间同步多个bit数值不会产生问题。所以需要将二进制转换到gray码,即将地址值转换为相应的gray码,然后将该gray码同步到另一个时钟域进行对比,作为空满状态的检测。对二进制转格雷码以4bit为例做分析,有
从上图可以给出结论:
判断读空时:需要读时钟域的格雷码和被同步到读时钟域的写指针每一位完全相同;
判断写满时:需要写时钟域的格雷码和被同步到写时钟域的读指针高两位不相同,其余各位完全相同;
二进制转格雷码的verilog代码如下(示例):
assign a_gry = (bin >> 1) ^ bin ;
2.2 同步FIFO
同步FIFO由于没有跨时钟的操作,所以只需要使用二进制即可,不用格雷码操作。根据上面的分析,有两种方法进行表示full/empty状态:
代码如下:
//1、generate full/empty signal by addr
assign full = (waddr_ptr == {~raddr_ptr[ADDR_WIDTH-1],radde_ptr[ADDR_WIDTH-2:0]});
assign empty = (waddr_ptr == raddr_ptr);
//2、generate full/empty signal by conuter
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
data_cnt <= {ADDR_WIDTH{1'b0}};
end
else if(wen && ren && !full && !empty)begin
data_cnt <= data_cnt;
end
else if(wen && !full)begin
data_cnt <= data_cnt + 1'b1;
end
else if(ren && !empty)begin
data_cnt <= data_cnt - 1'b1;
end
end
assign full = (data_cnt == FIFO_DEPTH);
assign empty = (data_cnt == 0);
2.3 异步FIFO
由于存在读写时钟不同步的问题,采用的解决方法是:加两级寄存器同步 + 格雷码(目的都是消除亚稳态)
代码如下(示例):
module asyn_fifo(clk_a, clk_b, rst_n, din, wen, ren, dout);
input clk_a;
input clk_b;
input rst_n;
input [FIFO_WIDTH-1:0] din;
input wen;
input ren;
output [FIFO_WIDTH-1:0] dout;
reg [ADDR_WIDTH-1:0] raddr;
reg [ADDR_WIDTH-1:0] raddr_gry_a1;
reg [ADDR_WIDTH-1:0] raddr_gry_a2;
reg [ADDR_WIDTH-1:0] waddr;
reg [ADDR_WIDTH-1:0] waddr_gry_b1;
reg [ADDR_WIDTH-1:0] waddr_gry_b2;
reg [FIFO_WIDTH-1:0] fifo_ram[FIFO_DEPTH-1:0];
wire fifo_empty;
wire fifo_full;
wire [ADDR_WIDTH-1:0] raddr_gry;
wire [ADDR_WIDTH-1:0] waddr_gry;
wire [FIFO_WIDTH-1:0] dout;
parameter FIFO_DEPTH = 4'h8;
parameter FIFO_WIDTH = 4'h8;
parameter ADDR_WIDTH = 2'h3;
always @(posedge clk_a or negedge rst_n)begin
if(!rst_n)begin
waddr <= #`RD {ADDR_WIDTH{1'b0}};
end
else if(wen && (~fifo_full))begin
waddr <= #`RD waddr + 1'b1;
end
end
always @(posedge clk_b or negedge rst_n)begin
if(!rst_n)begin
raddr <= #`RD {ADDR_WIDTH{1'b0}};
end
else if(ren && (~fifo_empty))begin
raddr <= #`RD raddr + 1'b1;
end
end
assign waddr_gry[ADDR_WIDTH-1:0] = (waddr >> 1) ^ waddr;
assign raddr_gry[ADDR_WIDTH-1:0] = (raddr >> 1) ^ raddr;
always @(posedge clk_b or negedge rst_n)begin
if(!rst_n)begin
waddr_gry_b1 <= #`RD {ADDR_WIDTH{1'b0}};
waddr_gry_b2 <= #`RD {ADDR_WIDTH{1'b0}};
end
else begin
waddr_gry_b1 <= #`RD waddr_gry;
waddr_gry_b2 <= #`RD waddr_gry_b1;
end
end
always @(posedge clk_a or negedge rst_n)begin
if(!rst_n)begin
raddr_gry_a1 <= #`RD {ADDR_WIDTH{1'b0}};
raddr_gry_a2 <= #`RD {ADDR_WIDTH{1'b0}};
end
else begin
raddr_gry_a1 <= #`RD raddr_gry;
raddr_gry_a2 <= #`RD raddr_gry_a1;
end
end
assign fifo_full = (waddr_gry == {~raddr_gry_a2[ADDR_WIDTH-1:ADDR_WIDTH-2],raddr_gry_a2[ADDR_WIDTH-3:0]}) ;
assign fifo_empty = (waddr_gry_b2[ADDR_WIDTH-1:0] == raddr_gry[ADDR_WIDTH-1:0]);
always @(posedge clk_a or negedge rst_n)begin
if(!rst_n)begin
fifo_ram[waddr] <= #`RD {FIFO_WIDTH{1'b0}};
end
else if(wen && !fifo_full)begin
fifo_ram[waddr] <= #`RD din;
end
end
assign dout[FIFO_WIDTH-1:0] <= #`RD fifo_ram[raddr];
endmodule
注:1、在跨时钟域时,由于读写地址打了2拍,有可能会出现新的读写地址,需要对此现象进行分析是否会有影响;
2、由于读写速率不一致,但为了保证数据不丢失,需要依据读写速率对FIFO深度进行计算。