1、定义
FIFO是英文First In First Out 的缩写,是一种先进先出的数据缓存器,他与普通存储器的区别是没有外部读写地址线,这样使用起来非常简单,但缺点就是只能顺序写入数据,顺序的读出数据, 其数据地址由内部读写指针自动加1完成,不能像普通存储器那样可以由地址线决定读取或写入某个指定的地址。
FIFO一般用于不同时钟域之间的数据传输,比如FIFO的一端是AD数据采集, 另一端是计算机的PCI总线,假设其AD采集的速率为16位 100K SPS,那么每秒的数据量为100K×16bit=1.6Mbps,而PCI总线的速度为33MHz,总线宽度32bit,其最大传输速率为 1056Mbps,在两个不同的时钟域间就可以采用FIFO来作为数据缓冲。另外对于不同宽度的数据接口也可以用FIFO,例如单片机位8位数据输出,而 DSP可能是16位数据输入,在单片机与DSP连接时就可以使用FIFO来达到数据匹配的目的。
2、空/满检测
FIFO设计的关键:产生可靠的FIFO读写指针和生成FIFO“空”/“满”状态标志。
当读写指针相等时,表明FIFO为空,这种情况发生在复位操作时,或者当读指针读出FIFO中最后一个字后,追赶上了写指针:
当读写指针再次相等时,表明FIFO为满,这种情况发生在,当写指针转了一圈,折回来(wrapped around)又追上了读指针:
异步fifo空/满区分方法:
在指针中添加一个额外的位(extra bit),当读写指针增加并越过最后一个FIFO地址时,就将读写指针这个未用的MSB加1,其它位回零,MSB作为折回标志位,而低3位作为地址指针。
- 如果两个指针的MSB不同,说明写指针比读指针多折回了一次;如r_addr=0000,而w_addr = 1000,为满。
- 如果两个指针的MSB相同,则说明两个指针折回的次数相等。其余位相等,说明FIFO为空;
gray码判断“空”与“满”
- 判断读空时:需要读时钟域的格雷码和被同步到读时钟域的写指针每一位完全相同;
- 判断写满时:需要写时钟域的格雷码和被同步到写时钟域的读指针高两位不相同,其余各位完全相同;
3、同步FIFO
module fifo_syn_test(
input clk,
input rst_n,
input wr_req,
input rd_req,
input [7:0] data_in,
output reg [7:0] data_out,
output reg full,
output reg empty
);
reg [7:0] mem [15:0];
reg [3:0] wr_addr,rd_addr;
always @(posedge clk)begin
if(wr_req && !full)
mem[wr_addr] <= data_in;
end
always @(posedge clk)begin
if(~rst_n)
data_out <= 8'd0;
if(rd_req && !empty)
data_out <= mem[rd_addr];
end
always @(posedge clk)begin
if(~rst_n)
wr_addr <= 4'd0;
else if(wr_req && !full)
wr_addr <= wr_addr + 4'd1;
end
always @(posedge clk)begin
if(~rst_n)
rd_addr <= 4'd0;
else if(rd_req && !empty)
rd_addr <= rd_addr + 4'd1;
end
always @(posedge clk)begin
if(~rst_n)
full <= 1'd0;
else if(wr_req && rd_addr == wr_addr + 1)
full <= 1'd1;
else
full <= 1'd0;
end
always @(posedge clk)begin
if(~rst_n)
empty <= 1'd0;
else if(rd_req && wr_addr == rd_addr + 1)
empty <= 1'd1;
else
empty <= 1'd0;
end
endmodule
4、异步FIFO
module fifo_asyn_test(
input wclk,
input rclk,
input rst_n,
input [7:0] data_in,
input wr_req,
input rd_req,
output reg [7:0] data_out,
output fifo_empty,
output fifo_full
);
reg [7:0] ram [15:0];
reg [4:0] wr_addr;
wire [4:0] wr_addr_g;
reg [4:0] wr_addr_g_r, wr_addr_g_rr;
reg [4:0] rd_addr;
wire [4:0] rd_addr_g;
reg [4:0] rd_addr_g_r, rd_addr_g_rr;
always @(posedge wclk or negedge rst_n)begin
if(wr_req && !fifo_full)begin
ram[wr_addr] <= data_in;
end
end
always @(posedge wclk or negedge rst_n)begin
if(~rst_n)begin
wr_addr <= 5'd0;
end
else if(wr_req && !fifo_full)
wr_addr <= wr_addr + 5'd1;
end
assign wr_addr_g = wr_addr^(wr_addr>>1);
always @(posedge rclk or negedge rst_n)begin
if(~rst_n)begin
wr_addr_g_r <= 5'd0;
wr_addr_g_rr <= 5'd0;
end
else begin
wr_addr_g_r <= wr_addr_g;
wr_addr_g_rr <= wr_addr_g_r;
end
end
always @(posedge rclk or negedge rst_n)begin
if(~rst_n)begin
rd_addr <= 5'd0;
end
else if(rd_req && !fifo_empty)
rd_addr <= rd_addr + 5'd1;
end
assign rd_addr_g = rd_addr^(rd_addr>>1);
always @(posedge wclk or negedge rst_n)begin
if(~rst_n)begin
rd_addr_g_r <= 5'd0;
rd_addr_g_rr <= 5'd0;
end
else begin
rd_addr_g_r <= rd_addr_g;
rd_addr_g_rr <= rd_addr_g_r;
end
end
always @(posedge rclk or negedge rst_n)begin
if(~rst_n)begin
data_out <= 8'd0;
end
else if(rd_req && !fifo_empty)
data_out <= ram[rd_addr];
else
data_out <= data_out;
end
assign fifo_empty = (rd_addr_g == wr_addr_g_rr);
assign fifo_full = (wr_addr_g == {~rd_addr_g_rr[4:3],rd_addr_g_rr[2:0]});
endmodule
仿真
module tb(
);
reg wclk;
reg rclk;
reg rst_n;
reg [7:0] data_in;
reg wr_req;
reg rd_req;
wire [7:0] data_out;
wire fifo_empty;
wire fifo_full;
initial begin
rst_n = 0;
wclk = 0;
rclk = 0;
wr_req = 0;
rd_req = 0;
#20;
rst_n = 1;
wr_req = 1;
#360;
rd_req = 1;
#80;
wr_req = 0;
end
always @(posedge wclk or negedge rst_n)begin
if(~rst_n)begin
data_in <= 8'd1;
end
else
data_in <= data_in + 1 ;
end
initial begin
forever
#10 wclk = ~wclk;
end
initial begin
forever
#20 rclk = ~rclk;
end
fifo_syn fifo_syn(
.wclk (wclk ),
.rclk (rclk ),
.rst_n (rst_n ),
.data_in (data_in ),
.wr_req (wr_req ),
.rd_req (rd_req ),
.data_out (data_out ) ,
.fifo_empty(fifo_empty) ,
.fifo_full (fifo_full )
);
endmodule
5、自然二进制码转格雷码
gray=(binary>>1)^binary;
6、格雷码转自然二进制码
integer i;
always @(data_gray) begin
data_bin[n-1] = data_gray[n-1];
for(i=n-1; i>0; i=i-1)
data_bin[i-1] = data_gray[i-1]^data_bin[i];
end
补充
1、判断是否写满时,需要将读指针转换成格雷码形式,再同步到写时钟域,与写指针比较,判断是否写满!
当读指针转换成格雷码以及同步到写时钟域的过程中,读写指针可能还都在递增,这样的话,等同步后的读指针与写指针相等时(不包括最高位),实际的读指针可能已经变了,这样的话其实还有几个空间没有写满!这叫保守设计,可以增加FIFO的安全性。
2、当判断是否读空时,需要把写指针同步到读时钟域,具体过程是先将写指针转换为格雷码,再同步到读时钟域,之后和读指针比较,如果二者相等,则空标志置位!
当写指针转换成格雷码以及同步到读时钟域的过程中,写指针和读指针都可能还在递增,这样当二者判断相等的时候,则写指针可能还多写了几个空间,实际上并没有读空。这样也保证了FIFO的安全,防止被读空。