1、异步FIFO的Verilog实现

本文详细介绍了异步FIFO在Verilog中的实现,包括定义、关键点解决策略(如读空和写满信号判断)、使用格雷码避免亚稳态以及处理两时钟域同步问题。提供了一个Verilog代码示例来展示具体实现。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

异步FIFO的Verilog实现方法

一、异步FIFO的定义

异步FIFO读、写接口分别采用不同时钟,异步时钟之间的信号接口用异步FIFO来实现,在两个不同时钟系统之间快速、方便地传输实时数据。

二、异步FIFO需要解决的关键点

设计要点:读空信号如何产生?写满信号如何产生?
直接对比读、写指针时钟域不行,ex.读1时刻指针t1(0),2时刻指针t2(1)。

1.读指针同步到写时钟域: 读指针同步到写时钟域,同步后的读指针小于等于原来的读指针;写指针不需要同步。
“写满”的判断: 写指针超过了同步后的读指针一圈,读指针实际大于同步后的读指针,实际上写指针没有大于读指针一圈,“假写满”,写到将满未满报“写满”,不影响实际作用,浪费部分性能。
“读空”的判断: 同步后的读指针追上了写指针。读指针实际大于等于同步后的读指针,“读空”还在读,错误。
2、写指针同步到读时钟域: 写指针同步到读时钟域,写指针实际大于等于同步后的写指针;读指针不需要同步。
“写满”的判断: 同步后的写指针超过了读指针一圈,写指针实际大于等于同步后的写指针,实际上写指针大于读指针一圈,“写满”还在写,错误。
“读空”的判断: 同步后的读指针追上了写指针。写指针实际大于等于同步后的写指针,还有数据通知“读空”,不影响实际作用,浪费部分性能。

总结:

  1. “写满”的判断: 将读指针同步到写时钟域,再与写指针判断。
  2. “读空”的判断: 将写指针同步到读时钟域,再与读指针判断。
    口诀:“写(满)读指针,读(空)看写指针”

三、异步FIFO使用Gray Code 格雷码避免亚稳态

将读、写指针转化为格雷码。格雷码是一种非权重码,每次变化只有一位不同,有效避免跨时钟域亚稳态问题。

  1. 如何用格雷码判断空满?
    首先我们需要将指针向高位拓展一位,这是为了判断写指针是否超过读指针一圈,超过一周最高位肯定不同,其余位相等。二进制方法是对比最高位不同然后通过对比除了最高位的其余位相同,来判断读写指针是否重合。这种方法判断二进制的指针是没有问题的,但是这不适合格雷码形式的指针,因为格雷码是镜像对称的,若只根据最高位是否相同来区分是读空还是写满是有问题的。比如0和15,最高位不一样但是其余位相同,写指针没有超过一周。
    格雷码镜像对称特性:
十进制格雷码
00_000
10_001
20_011
30_010
40_110
50_111
60_101
70_100
中心中心
81_100
91_101
101_111
111_110
121_010
131_011
141_001
151_000

在这里插入图片描述

格雷码判断:

  1. “读空”:最高位和次高位相同,其余位相同认为是读空。
  2. “写满”:最高位和次高位不同,其余位相同认为是写满。

四、两时钟域同步问题

快时钟采慢时钟可以直接打拍,但存在漏采,漏采问题怎么解决?不需要解决。

“读慢写快”:(读时钟慢写时钟快)

1.“写满”的判断: 读指针同步到写时钟域。读慢写快,不会有读指针遗漏,同步后的读指针小于等于实际读指针,未写满,“假写满”,不影响功能。
2.“读空”的判断: 写指针同步到读时钟域。读慢写快,采集遗漏,写指针0,1,2,可能采集到1时,实际到2,“假读空”,不影响功能。

“读快写满”:

1.“写满”的判断: 读指针同步到写时钟域。读快写慢,遗漏部分读指针,读指针0,1,2, 可能采集到1时,实际到2,“假写满”,不影响功能。
2.“读空”的判断: 写指针同步到读时钟域。读快写慢,不会有指针遗漏,同步后的写指针大于等于实际写指针,“假读空”,不影响功能。

代码-Verilog

例子:FIFO深度为16,地址为4位,扩展一位作为指示位,指针5位。

 //异步FIFO
module	async_fifo
#(
	parameter   DATA_WIDTH = 'd8  ,								//FIFO位宽
    parameter   DATA_DEPTH = 'd16 								//FIFO深度
)		
(		
//写数据		
	input							wr_clk		,				//写时钟
	input							wr_rst_n	,       		//低电平有效的写复位信号
	input							wr_en		,       		//写使能信号,高电平有效	
	input	[DATA_WIDTH-1:0]		data_in		,       		//写入的数据
//读数据			
	input							rd_clk		,				//读时钟
	input							rd_rst_n	,       		//低电平有效的读复位信号
	input							rd_en		,				//读使能信号,高电平有效						                                        
	output	reg	[DATA_WIDTH-1:0]	data_out	,				//输出的数据
//状态标志					
	output							empty		,				//空标志,高电平表示当前FIFO已被写满
	output							full		    			//满标志,高电平表示当前FIFO已被读空
);                                                              
 
//reg define
//用二维数组实现RAM
reg [DATA_WIDTH - 1 : 0]			fifo_buffer[DATA_DEPTH - 1 : 0];
	
reg [$clog2(DATA_DEPTH) : 0]		wr_ptr;						//写地址指针,二进制
reg [$clog2(DATA_DEPTH) : 0]		rd_ptr;						//读地址指针,二进制
reg	[$clog2(DATA_DEPTH) : 0]		rd_ptr_g_d1;				//读指针格雷码在写时钟域下同步1拍
reg	[$clog2(DATA_DEPTH) : 0]		rd_ptr_g_d2;				//读指针格雷码在写时钟域下同步2拍
reg	[$clog2(DATA_DEPTH) : 0]		wr_ptr_g_d1;				//写指针格雷码在读时钟域下同步1拍
reg	[$clog2(DATA_DEPTH) : 0]		wr_ptr_g_d2;				//写指针格雷码在读时钟域下同步2拍
	
//wire define
wire [$clog2(DATA_DEPTH) : 0]		wr_ptr_g;					//写地址指针,格雷码
wire [$clog2(DATA_DEPTH) : 0]		rd_ptr_g;					//读地址指针,格雷码
wire [$clog2(DATA_DEPTH) - 1 : 0]	wr_ptr_true;				//真实写地址指针,作为写ram的地址
wire [$clog2(DATA_DEPTH) - 1 : 0]	rd_ptr_true;				//真实读地址指针,作为读ram的地址
 
//地址指针从二进制转换成格雷码
assign 	wr_ptr_g = wr_ptr ^ (wr_ptr >> 1);					
assign 	rd_ptr_g = rd_ptr ^ (rd_ptr >> 1);
//读写RAM地址赋值
assign	wr_ptr_true = wr_ptr [$clog2(DATA_DEPTH) - 1 : 0];		//写RAM地址等于写指针的低DATA_DEPTH位(去除最高位)
assign	rd_ptr_true = rd_ptr [$clog2(DATA_DEPTH) - 1 : 0];		//读RAM地址等于读指针的低DATA_DEPTH位(去除最高位)
 
 
//写操作,更新写地址
always @ (posedge wr_clk or negedge wr_rst_n) begin
	if (!wr_rst_n)
		wr_ptr <= 0;
	else if (!full && wr_en)begin								//写使能有效且非满
		wr_ptr <= wr_ptr + 1'd1;
		fifo_buffer[wr_ptr_true] <= data_in;
	end	
end
//将读指针的格雷码同步到写时钟域,来判断是否写满
always @ (posedge wr_clk or negedge wr_rst_n) begin
	if (!wr_rst_n)begin
		rd_ptr_g_d1 <= 0;										//寄存1拍
		rd_ptr_g_d2 <= 0;										//寄存2拍
	end				
	else begin												
		rd_ptr_g_d1 <= rd_ptr_g;								//寄存1拍
		rd_ptr_g_d2 <= rd_ptr_g_d1;								//寄存2拍
	end	
end
//读操作,更新读地址
always @ (posedge rd_clk or negedge rd_rst_n) begin
	if (!rd_rst_n)
		rd_ptr <= 'd0;
	else if (rd_en && !empty)begin								//读使能有效且非空
		data_out <= fifo_buffer[rd_ptr_true];
		rd_ptr <= rd_ptr + 1'd1;
	end
end
//将写指针的格雷码同步到读时钟域,来判断是否读空
always @ (posedge rd_clk or negedge rd_rst_n) begin
	if (!rd_rst_n)begin
		wr_ptr_g_d1 <= 0;										//寄存1拍
		wr_ptr_g_d2 <= 0;										//寄存2拍
	end				
	else begin												
		wr_ptr_g_d1 <= wr_ptr_g;								//寄存1拍
		wr_ptr_g_d2 <= wr_ptr_g_d1;								//寄存2拍		
	end	
end
//更新指示信号
//当所有位相等时,读指针追到到了写指针,FIFO被读空
assign	empty = ( wr_ptr_g_d2 == rd_ptr_g ) ? 1'b1 : 1'b0;
//当高位相反且其他位相等时,写指针超过读指针一圈,FIFO被写满
//同步后的读指针格雷码高两位取反,再拼接上余下位
assign	full  = ( wr_ptr_g == { ~(rd_ptr_g_d2[$clog2(DATA_DEPTH) : $clog2(DATA_DEPTH) - 1])
				,rd_ptr_g_d2[$clog2(DATA_DEPTH) - 2 : 0]})? 1'b1 : 1'b0;
endmodule
异步FIFO(First-In-First-Out,先进先出)是一种常见的数据缓冲器,用于在数据生产者和消费者之间传递数据。下面是一个基本的异步FIFOVerilog实现示例: ```verilog module AsyncFIFO ( input wire clk, input wire rst, input wire wr_en, input wire rd_en, input wire [DATA_WIDTH-1:0] wr_data, output wire [DATA_WIDTH-1:0] rd_data, output wire full, output wire empty ); parameter DEPTH = 16; // FIFO深度 parameter DATA_WIDTH = 8; // 数据宽度 reg [DATA_WIDTH-1:0] mem [0:DEPTH-1]; reg [DATA_WIDTH-1:0] rd_ptr; reg [DATA_WIDTH-1:0] wr_ptr; reg [DATA_WIDTH-1:0] next_wr_ptr; reg [DATA_WIDTH-1:0] next_rd_ptr; reg full, empty; always @(posedge clk or posedge rst) begin if (rst) begin rd_ptr <= 0; wr_ptr <= 0; full <= 0; empty <= 1; end else begin rd_ptr <= next_rd_ptr; wr_ptr <= next_wr_ptr; full <= (next_wr_ptr == next_rd_ptr) && wr_en; empty <= (next_wr_ptr == next_rd_ptr) && rd_en; end end always @(posedge clk) begin if (rst) begin next_wr_ptr <= 0; next_rd_ptr <= 0; end else begin if (wr_en && !full) next_wr_ptr <= wr_ptr + 1; else next_wr_ptr <= wr_ptr; if (rd_en && !empty) next_rd_ptr <= rd_ptr + 1; else next_rd_ptr <= rd_ptr; end end assign rd_data = mem[rd_ptr]; always @(posedge clk) begin if (rst) begin mem[wr_ptr] <= 0; end else begin if (wr_en && !full) mem[wr_ptr] <= wr_data; end end endmodule ``` 这个异步FIFO模块包含了输入和输出端口,以及一些内部寄存器用于存储数据和指针。其中,`clk`是时钟信号,`rst`是复位信号,`wr_en`是写使能信号,`rd_en`是读使能信号,`wr_data`是写入数据,`rd_data`是输出数据,`full`表示FIFO是否已满,`empty`表示FIFO是否为空。 在时钟上升沿触发的过程中,根据输入的控制信号和当前状态,更新下一个时钟周期的指针和状态。同时,根据输入的写使能信号和当前状态,将写入数据存储到相应的位置。 这个异步FIFO模块可以根据需要进行参数化,包括FIFO的深度(DEPTH)和数据宽度(DATA_WIDTH)。 以上是一个基本的异步FIFOVerilog实现示例,你可以根据实际需求进行修改和扩展。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值