异步FIFO_跨时钟域数据信号传输

在大规模ASIC或FPGA设计中,多时钟系统往往是不可避免的,这样就产生了不同时钟域数据传输的问题,其中一个比较好的解决方案就是使用异步FIFO来作不同时钟域数据传输的缓冲区,这样既可以使相异时钟域数据传输的时序要求变得宽松,也提高了它们之间的传输效率。

 FIFO在硬件上是一种地址依次自增的Simple Dual Port RAM,按读数据和写数据工作的时钟域是否相同分为同步FIFO和异步FIFO,其中同步FIFO是指读时钟和写时钟为同步时钟,常用于数据缓存和数据位宽转换;异步FIFO通常情况下是指读时钟和写时钟频率有差异,即由两个异步时钟驱动的FIFO,由于读写操作是独立的,故常用于多比特数据跨时钟域处理。

异步FIFO是通过比较读指针和写指针的位置来判断FIFO是否写满或读空,但是不可以直接比较两个指针,因为他们属于不同时钟域,直接相比可能会产生亚稳态从而引起误判,这就需要将两个指针分别进行跨时钟域处理,然后再判断。但是存在一个问题,自然二进制编码的地址在状态翻转的时候是多位变化,这就可能会产生竞争现象并有可能被另一个时钟域的触发器采样到,从而引发误判。最容易的解决方法就是将自然二进制编码的地址转为格雷码编码的地址。

从上图可以看出,格雷码如果每2^n个数一循环,首尾两个格雷码仍然是只有一位变化,如果不是2^n个数,那么首尾数据就不是仅有一位变化,那就不是真正的格雷码,所以这也是异步FIFO的存储深度只能是2^n的原因

自然二进制码转换成二进制格雷码,其法则是保留自然二进制码的最高位作为格雷码的最高位,而次高位格雷码为二进制码的高位与次高位相异或,而格雷码其余各位与次高位的求法相类似。:


module ASFIFO#
	(
		parameter WIDTH = 16,		// FIFO数据总线位宽
		parameter PTR   = 4			// FIFO存储深度(bit数,深度只能是2^n个)
	)
	(
		// write interface
		input					wrclk	,		// 写时钟
		input					wr_rst_n,		// 写指针复位
		input	[WIDTH-1:0]		wr_data	,		// 写数据总线
		input					wr_en	,		// 写使能
		output  reg				wr_full	,		// 写满标志
 
		//read interface
		input					rdclk	,		// 读时钟
		input					rd_rst_n,		// 读指针复位
		input					rd_en	,		// 读使能
		output	[WIDTH-1:0]		rd_data	,		// 读数据输出
		output	reg				rd_empty		// 读空标志
    );
 
 
	// 写时钟域信号定义
	reg [PTR:0] wr_bin		;					// 二进制写地址
	reg [PTR:0] wr_gray		;					// 格雷码写地址
	reg [PTR:0] rd_gray_ff1 ;					// 格雷码读地址同步寄存器1
	reg [PTR:0] rd_gray_ff2 ;					// 格雷码读地址同步寄存器2
	reg [PTR:0] rd_bin_wr	;					// 同步到写时钟域的二进制读地址
 
 
	// 读时钟域信号定义
	reg [PTR:0] rd_bin		;					// 二进制读地址
	reg [PTR:0] rd_gray		;					// 格雷码读地址
	reg [PTR:0] wr_gray_ff1 ;					// 格雷码写地址同步寄存器1
	reg [PTR:0] wr_gray_ff2 ;					// 格雷码写地址同步寄存器2
	reg [PTR:0] wr_bin_rd	;					// 同步到读时钟域的二进制写地址
 
 
	// 解格雷码电路循环变量
	integer i ;
	integer j ;
 
 
	// DPRAM控制信号
	wire  				dpram_wr_en		 ;		// DPRAM写使能
	wire [PTR-1:0]		dpram_wr_addr    ;		// DPRAM写地址
	wire [WIDTH-1:0] 	dpram_wr_data	 ;		// DPRAM写数据
	wire  				dpram_rd_en		 ;		// DPRAM读使能
	wire [PTR-1:0]		dpram_rd_addr    ;		// DPRAM读地址
	wire [WIDTH-1:0] 	dpram_rd_data	 ;		// DPRAM读数据
 
 
 
	// ******************************** 写时钟域 ******************************** //
	// 二进制写地址递增
	always @(posedge wrclk or posedge wr_rst_n) begin
		if (!wr_rst_n) begin
			wr_bin <= 'b0;
		end
		else if ( wr_en == 1'b1 && wr_full == 1'b0 ) begin
			wr_bin <= wr_bin + 1'b1;
		end
		else begin
			wr_bin <= wr_bin;
		end
	end
 
 
	// 写地址:二进制转格雷码
	always @(posedge wrclk or posedge wr_rst_n) begin
		if (!wr_rst_n) begin
			wr_gray <= 'b0;
		end
		else begin
			wr_gray <= { wr_bin[PTR], wr_bin[PTR:1] ^ wr_bin[PTR-1:0] };
		end
	end
 
 
	// 格雷码读地址同步至写时钟域
	always @(posedge wrclk or posedge wr_rst_n) begin
		if(!wr_rst_n) begin
			rd_gray_ff1 <= 'b0;
			rd_gray_ff2 <= 'b0;
		end
		else begin
			rd_gray_ff1 <= rd_gray;
			rd_gray_ff2 <= rd_gray_ff1;
		end
	end
 
 
	// 同步后的读地址解格雷
	always @(*) begin
		rd_bin_wr[PTR] = rd_gray_ff2[PTR];
		for ( i=PTR-1; i>=0; i=i-1 )
			rd_bin_wr[i] = rd_bin_wr[i+1] ^ rd_gray_ff2[i];
	end
 
 
	// 写时钟域产生写满标志
	always @(*) begin
		if( (wr_bin[PTR] != rd_bin_wr[PTR]) && (wr_bin[PTR-1:0] == rd_bin_wr[PTR-1:0]) ) begin
			wr_full = 1'b1;
		end
		else begin
			wr_full = 1'b0;
		end
	end
 
 
	// ******************************** 读时钟域 ******************************** //
	always @(posedge rdclk or posedge rd_rst_n) begin
		if (!rd_rst_n) begin
			rd_bin <= 'b0;
		end
		else if ( rd_en == 1'b1 && rd_empty == 1'b0 ) begin
			rd_bin <= rd_bin + 1'b1;
		end
		else begin
			rd_bin <= rd_bin;
		end
	end
 
 
	// 读地址:二进制转格雷码
	always @(posedge rdclk or posedge rd_rst_n) begin
		if (!rd_rst_n) begin
			rd_gray <= 'b0;
		end
		else begin
			rd_gray <= { rd_bin[PTR], rd_bin[PTR:1] ^ rd_bin[PTR-1:0] };
		end
	end
 
 
	// 格雷码写地址同步至读时钟域
	always @(posedge rdclk or posedge rd_rst_n) begin
		if(!rd_rst_n) begin
			wr_gray_ff1 <= 'b0;
			wr_gray_ff2 <= 'b0;
		end
		else begin
			wr_gray_ff1 <= wr_gray;
			wr_gray_ff2 <= wr_gray_ff1;
		end
	end
 
 
	// 同步后的写地址解格雷
	always @(*) begin
		wr_bin_rd[PTR] = wr_gray_ff2[PTR];
		for ( j=PTR-1; j>=0; j=j-1 )
			wr_bin_rd[j] = wr_bin_rd[j+1] ^ wr_gray_ff2[j];
	end
 
 
	// 读时钟域产生读空标志
	always @(*) begin
		if( wr_bin_rd == rd_bin )
			rd_empty = 1'b1;
		else
			rd_empty = 1'b0;
	end
 
 
	// RTL双口RAM例化
	DPRAM
		# ( .WIDTH(16), .DEPTH(16), .ADDR(4) )
		U_DPRAM
		(
			.wrclk		(wrclk		 	),
			.rdclk		(rdclk			),
			.rd_rst_n   (rd_rst_n		),
			.wr_en 		(dpram_wr_en	),
			.rd_en 		(dpram_rd_en	),
			.wr_data 	(dpram_wr_data	),
			.rd_data 	(dpram_rd_data	),
			.wr_addr 	(dpram_wr_addr	),
			.rd_addr 	(dpram_rd_addr	)
		);
 
 
	// 产生DPRAM读写控制信号
	assign dpram_wr_en   = ( wr_en == 1'b1 && wr_full == 1'b0 )? 1'b1 : 1'b0;
	assign dpram_wr_data = wr_data;
	assign dpram_wr_addr = wr_bin[PTR-1:0];
 
	assign dpram_rd_en   = ( rd_en == 1'b1 && rd_empty == 1'b0 )? 1'b1 : 1'b0;
	assign rd_data = dpram_rd_data;
	assign dpram_rd_addr = rd_bin[PTR-1:0];
 
 
endmodule


 

  • 0
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值