Verilog实现同步、异步FIFO

        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磨剑”系列之一,本人水平有限,有任何问题欢迎大家在评论区留言讨论或私信指正,谢谢~

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值