FIFO全称First in First out,即先入先出,常用作数据缓存、跨时钟数据传输等场合。FIFO可根据读写时钟是否为同一时钟域可分为同步FIFO和异步FIFO。
具体的FIFO原理可以看下这篇文章,很通俗易懂,在此不再赘述。附上链接:https://baijiahao.baidu.com/s?id=1723287516895080443&wfr=spider&for=pc
一、同步FIFO
同步FIFO很简单,参考代码如下:
module sync_fifo
#(
parameter data_width = 8,
parameter data_depth = 8,
parameter addr_width = 4
)
(
input wire clk ,
input wire rst_n ,
input wire [data_width-1:0] wdata ,
input wire wr_en ,
output wire full ,
output reg [data_width-1:0] rdata ,
input wire rd_en ,
output wire empty
);
reg [data_width-1:0] FIFO [data_depth-1:0];
reg [addr_width-1:0] wr_addr;
reg [addr_width-1:0] rd_addr;
//wdata、wdata:
always@(posedge clk or negedge rst_n)
if(!rst_n)
wr_addr <= 0;
else if(wr_en && !full) begin
FIFO[wr_addr] <= wdata;
wr_addr <= wr_addr + 1'b1;
end
else begin
FIFO[wr_addr] <= FIFO[wr_addr];
wr_addr <= wr_addr;
end
//rd_addr、rdata:
always@(posedge clk or negedge rst_n)
if(!rst_n) begin
rd_addr <= 0;
rdata <= 0;
end
else if(rd_en && !empty) begin
rd_addr <= rd_addr + 1'b1;
rdata <= FIFO[rd_addr];
end
else begin
rd_addr <= rd_addr;
rdata <= rdata;
end
//full:
assign full = ({~wr_addr[addr_width-1],wr_addr[addr_width-1:0]} == rd_addr);
//empty:
assign empty = (wr_addr == rd_addr);
endmodule
二、异步FIFO
异步FIFO需要注意,因为FIFO内部存在跨时钟域(读时钟和写时钟),所以在空满判断前要先将读写地址转格雷码,再在对方时钟域打两拍,然后再进行判断。(这里啰嗦两句,转格雷码的主要作用除了减小亚稳态的影响,更主要的是即使是在亚稳态情况下进行读写,造成的影响也是最小的,详解看这篇:https://blog.csdn.net/qijitao/article/details/50969328)
参考代码如下:
module async_fifo
#(
parameter data_width = 32,
parameter data_depth = 32,
parameter addr_width = 6 //比实际地址位宽多1位,用于地址比较
)
(
input wire rst_n ,
input wire wr_clk ,
input wire wr_en ,
input wire [data_width-1:0] wr_data ,
input wire rd_clk ,
input wire rd_en ,
output reg [data_width-1:0] rd_data ,
output reg full ,
output reg empty
);
reg [data_width-1:0] FIFO [data_depth-1:0];
reg [addr_width-1:0] wr_addr ,
wr_addr_gry1 ,
wr_addr_gry2 ;
wire [addr_width-1:0] wr_addr_gry ;
reg [addr_width-1:0] rd_addr ,
rd_addr_gry1 ,
rd_addr_gry2 ;
wire [addr_width-1:0] rd_addr_gry ;
//wr_clk:
always@(posedge wr_clk or negedge rst_n)
if(!rst_n) begin
wr_addr <= 0;
end
else if(wr_en && !full) begin
wr_addr <= wr_addr + 1'b1;
FIFO[wr_addr] <= wr_data;
end
else begin
wr_addr <= wr_addr;
FIFO[wr_addr] <= FIFO[wr_addr];
end
//rd_clk:
always@(posedge rd_clk or negedge rst_n)
if(!rst_n) begin
rd_addr <= 0;
rd_data <= 0;
end
else if(rd_en && !empty) begin
rd_addr <= rd_addr + 1'b1;
rd_data <= FIFO[rd_addr];
end
else begin
rd_addr <= rd_addr;
rd_data <= rd_data;
end
//gry:
assign wr_addr_gry = (wr_addr >> 1) ^ wr_addr;
assign rd_addr_gry = (rd_addr >> 1) ^ rd_addr;
//wr_addr_gry:
always@(posedge rd_clk or negedge rst_n)
if(!rst_n) begin
wr_addr_gry1 <= 0;
wr_addr_gry2 <= 0;
end
else begin
wr_addr_gry1 <= wr_addr_gry;
wr_addr_gry2 <= wr_addr_gry1;
end
//rd_addr_gry:
always@(posedge wr_clk or negedge rst_n)
if(!rst_n) begin
rd_addr_gry1 <= 0;
rd_addr_gry2 <= 0;
end
else begin
rd_addr_gry1 <= rd_addr_gry;
rd_addr_gry2 <= rd_addr_gry1;
end
//full:格雷码的比较方式,最高2位不同,其余低位相同,证明写指针“超一圈”追上了读指针
always@(*)
if({~wr_addr_gry[addr_width-1:addr_width-2],wr_addr_gry[addr_width-3:0]} == rd_addr_gry2)
full <= 1'b1;
else
full <= 1'b0;
//empty:
always@(posedge rd_clk or negedge rst_n)
if(!rst_n)
empty <= 1'b0;
else if(rd_addr_gry == wr_addr_gry2)
empty <= 1'b1;
else
empty <= 1'b0;
endmodule
参考链接:https://blog.csdn.net/bgskip/article/details/122940327
ps:这是“IC磨剑”系列之一,本人水平有限,有任何问题欢迎大家在评论区留言讨论或私信指正,谢谢~