异步fifo的工作原理(netty异步方法)

FPGA(一):异步FIFO实现(包含源码和仿真文件)一、异步FIFO的重要参数及其作用1、FIFO:FirstInputFirstOutput,即先入先出队列,本质是RAM。FIFO有几个最重要的参数:2、wr_clk:写时钟,所有与写有关的操作都是基于写时钟;3、rd_clk:读时钟,所有与读有关的操作都是基于读时钟;4、FIFO_WIDTH:FIFO的位宽,即FIFO中每个地址对应的数据的位宽;5、FIFO_DEPTH:FIFO的深度,即FIFO中能存入多少个(位宽为FIFO_

大家好,又见面了,我是你们的朋友全栈君。


本次设计主要介绍异步FIFO中读写指针和格雷码的原理及其实现,最后会有代码和仿真文件

一、异步FIFO的重要参数及其作用

FIFO有几个最重要的参数:
1、FIFO:First Input First Output,即先入先出队列,本质是RAM。
2、wr_clk:写时钟,所有与写有关的操作都是基于写时钟;
3、rd_clk:读时钟,所有与读有关的操作都是基于读时钟;
4、FIFO_WIDTH: FIFO的位宽,即FIFO中每个地址对应的数据的位宽;
5、FIFO_DEPTH: FIFO的深度,即FIFO中能存入多少个(位宽为FIFO_WIDTH的)数据;
6、full:FIFO发出的满信号,当FIFO满了之后,将full拉高;
7、empty:FIFO发出的空信号,当FIFO空了之后,将empty拉高;
8、wr_en:主机发送给FIFO的写使能,一般受制于FIFO发出的full信号,若full信号为高,一般主机会拉低写使能信号,防止新数据覆盖原来的数据;
9、rd_en:主机发送给FIFO的读使能,一般受制于FIFO发出的empty信号,若empty信号为高,一般主机会拉低读使能信号,防止从FIFO中读出不确定的数据。

异步FIFO主要用作跨时钟域的数据缓存。

二、设计要点

异步FIFO设计中,最重要的就是空满判断,格雷码是现在使用最多用于判断空满的一种码制,虽然都知道用格雷码,那为什么要用格雷码?

先说一说空满判断:
:读快于写时,读指针追上写指针时,写入的数据被读完,读指针和读指针相同,FIFO为空。
:当写快于读时,当写指针追上读指针,写入的数据比读出的数据多FIFO_DEPTH个,即写指针比读指针大一个FIFO_DEPTH时,此时FIFO为满。

还有就是为什么读写指针要比FIFO_DEPTH多一位,以及格雷码在这之中起到的重要作用,这个在说完格雷码之后会有解释。

格雷码:因1953年公开的弗兰克·格雷专利 “Pulse Code Communication”而得名。
格雷码是一种安全码,因为相邻的格雷码只有一位不同,和二进制不同,二进制一般相邻的都有多位不同。格雷码在传输中,因为相邻只有一位不同,所以其误码率比二进制低得多。
在同步时,出现亚稳态的概率也比二进制低。

二进制转换为格雷码
在这里插入图片描述
Verilog代码描述:gray = binary ^ (binary >> 1)

在这里插入图片描述
注:以上两幅关于格雷码的图片均不是原创,不知道出处,若有侵权,请联系删除

1、首先是读写指针为什么可以多一位,对读写地址有没有影响

答案是,没有影响
如上图,假如FIFO_DEPTH为8,那么指针就有16个值
普通的三位的地址从000写到111,然后再写入的话,地址又为000,一直写到111,重复上述操作。
因为我们取指针的低三位作为读写地址,如图,可以看出,即使是四位的指针,因为取的低三位,所以也是在000-111中往复循环,不会出现地址溢出的情况。

2、其次是为什么要用格雷码

由于以上原因,指针多一位对读写地址没有影响,而多一位又可以很好地利用格雷码的特性进行空满判断。
如上图,3和4, 5和6, 7和8之间有什么关系呢
可以看出,3的高两位与4的高两位相反低两位相同;5和6,7和8也有这种关系。
其实格雷码还是一种对称码,比如说3位的格雷码,可以先写出一位的格雷码,0 和 1;然后将这两位对称一下,写出 0 1 1 0,然后在前两个前面填上0,后两个前面添上1,得到两位格雷码 00 01 11 10,然后再对称得到 00 01 11 10 10 11 01 00,再在前四个前添上0,后四个 前面添上1,得到三位的格雷码,000 001 011 010 110 111 101 100.

而3和4之间还有什么关系呢,那就是他们的数值之间相差8,即一个FIFO_DEPTH,所以可以用这个来判断满。
空的判断很简单,格雷码一样就是空。

假设FIFO_DEPTH == 8
空的判断:empty =(wr_gray == rd_gray)?1:0;
满的判断:full = ({~wr_gray[3:2],wr_gray[1:0]} == rd_gray)?1:0;

3、空满信号的同步
因为空信号是对读操作有影响,所以,将空信号在rd_clk下同步(多采用两级寄存器)
因为满信号是对写操作有影响,所以,将满信号在wr_clk下同步(多采用两级寄存器)

三、源代码及仿真

1、源代码


`timescale 1ns / 1ns
module asynchronous_fifo #(	
	parameter 						FIFO_WIDTH = 8,
	parameter 						FIFO_DEPTH = 8
)(
	input							wr_clk,				//写时钟
	input							rd_clk,				//读时钟
	input							wr_en,				//写使能
	input							rd_en,				//读使能
	input							wr_rst_n,			//写复位
	input							rd_rst_n,			//读复位
	
	input		[FIFO_WIDTH-1:0]	wr_data,			//写入的数据		
	
	output	reg	[FIFO_WIDTH-1:0]	rd_data,			//输出的数据
	output	reg						wr_full,
    output	reg						rd_empty,
    output									wr_fifo_en,
	output									rd_fifo_en,
	output	reg		[$clog2(FIFO_DEPTH):0]	wr_gray_g2,		// 写指针
    output	reg		[$clog2(FIFO_DEPTH):0]	rd_gray_g2
    );
    
    
    // reg define
    reg		[$clog2(FIFO_DEPTH):0]		wr_pointer = 0;		// 写指针
	reg		[$clog2(FIFO_DEPTH):0]		rd_pointer = 0;		// 读指针
	reg		[$clog2(FIFO_DEPTH):0]		wr_gray_g1;  		// 写格雷码,用格雷码来判断空满
	reg		[$clog2(FIFO_DEPTH):0]		rd_gray_g1; 
	

    wire	[$clog2(FIFO_DEPTH):0]		wr_gray;  		// 写格雷码,用格雷码来判断空满
	wire	[$clog2(FIFO_DEPTH):0]		rd_gray;  		// 读格雷码,用格雷码来判断空满 
	wire	[$clog2(FIFO_DEPTH)-1:0]  	wr_addr;
	wire	[$clog2(FIFO_DEPTH)-1:0]  	rd_addr;
	wire								full_1;
	wire								empty_1;
    
    
     //ram define
   //(*ram_style = "distributed"*) reg [FIFO_WIDTH - 1 : 0] 	fifo_buffer	[FIFO_DEPTH - 1:0];			//寄存器组当FIFO
    reg [FIFO_WIDTH - 1 : 0] 	fifo_buffer	[FIFO_DEPTH - 1:0];			//寄存器组当FIFO
    
   	assign	wr_fifo_en = wr_en && !full_1;
    assign	rd_fifo_en = rd_en && !empty_1;
    
    assign	wr_gray = wr_pointer^(wr_pointer>>1);
    assign	rd_gray = rd_pointer^(rd_pointer>>1);
    
    assign	wr_addr = wr_pointer[$clog2(FIFO_DEPTH)-1:0]; 
    assign	rd_addr = rd_pointer[$clog2(FIFO_DEPTH)-1:0];
    
    assign	full_1 = ({~rd_gray_g2[$clog2(FIFO_DEPTH):$clog2(FIFO_DEPTH)-1], rd_gray_g2[$clog2(FIFO_DEPTH)-2:0]} == wr_gray)?1:0;
    assign	empty_1 = (wr_gray_g2 == rd_gray)?1:0;

   
    //将rd_gray在写时钟域与wr_gray比较,如果wr_gray与rd_gray高两位相反,低位相等,则写满
    always@(posedge wr_clk or negedge wr_rst_n) begin
    	if(!wr_rst_n) begin
    		rd_gray_g1 <= 0;
    		rd_gray_g2 <= 0;
    	end
    	else begin
    		rd_gray_g1 <= rd_gray;
    		rd_gray_g2 <= rd_gray_g1;
    	end
    end
    
     always@(posedge wr_clk or negedge wr_rst_n) begin
    	if(!wr_rst_n)
    		wr_full <= 1'b0;
    	else if(wr_full && wr_en)
    		wr_full <= 1'b1;
    	else 
    		wr_full <= full_1;
    end
    
    //将wr_gray在读时钟域与rd_gray比较,相等为FIFO空
     always@(posedge rd_clk or negedge rd_rst_n) begin
    	if(!rd_rst_n) begin
    		wr_gray_g1 <= 0;
    		wr_gray_g2 <= 0;
    	end
    	else begin
    		wr_gray_g1 <= wr_gray;
    		wr_gray_g2 <= wr_gray_g1;
    	end
    end
    
    always@(posedge rd_clk or negedge rd_rst_n) begin
    	if(!rd_rst_n)
    		rd_empty <= 1'b0;
    	else if(rd_empty && rd_en)
    		rd_empty <= 1'b1;
    	else 
    		rd_empty <= empty_1;
    end
    
    
    // 写数据指针计数
   	always@(posedge wr_clk or negedge wr_rst_n) begin
    	if(!wr_rst_n) begin
    		fifo_buffer[wr_addr] <= 'bz;
    		wr_pointer <= 0;
    	end
    	else if(wr_fifo_en) begin
    		fifo_buffer[wr_addr] <= wr_data;
    		wr_pointer <= wr_pointer + 1'b1;
    	end
    	else begin
    		fifo_buffer[wr_addr] <= fifo_buffer[wr_addr];
    		wr_pointer <= wr_pointer;
    	end
    end
    
    // 读数据指针计数
   	always@(posedge rd_clk or negedge rd_rst_n) begin
    	if(!rd_rst_n) begin
    		rd_data <= 'bz;
    		rd_pointer <= 0;
    	end
    	else if(rd_fifo_en) begin
    		rd_data <= fifo_buffer[rd_addr];
    		rd_pointer <= rd_pointer + 1'b1;
    	end
    	else begin
    		rd_data <= fifo_buffer[rd_addr];
    		rd_pointer <= rd_pointer;
    	end
    end

    
endmodule

2、仿真文件

改变clk_period_wr和clk_period_rd就可以实现读写快慢切换


`timescale 1ns / 1ns
`define	 clk_period_wr 50
`define	 clk_period_rd 20


module asynchronous_fifo_tb();


parameter		FIFO_WIDTH = 8;
parameter		FIFO_DEPTH = 16;


reg							wr_clk;				
reg							rd_clk;				
reg							wr_en;			
reg							rd_en;				
reg							wr_rst_n;
reg							rd_rst_n;
reg		[FIFO_WIDTH-1:0]	wr_data;

wire	[FIFO_WIDTH-1:0]	rd_data;			
wire						wr_full;    
wire						rd_empty;
wire						wr_fifo_en;
wire						rd_fifo_en;
wire	[$clog2(FIFO_DEPTH):0]		wr_gray_g2;
wire	[$clog2(FIFO_DEPTH):0]		rd_gray_g2;

assign wr_fifo_en = wr_en && !wr_full;
assign rd_fifo_en = rd_en && !rd_empty;

initial begin
	wr_clk = 0;
	forever begin
		#(`clk_period_wr/2) wr_clk = ~wr_clk;
	end
end
initial begin
	rd_clk = 0;
	forever begin
		#(`clk_period_rd/2) rd_clk = ~rd_clk;
	end
end

initial begin
	wr_rst_n = 1;
	rd_rst_n = 1;
	wr_en = 0;
	rd_en = 0;
	#5;
	wr_rst_n = 0;
	rd_rst_n = 0;
	#5;
	wr_rst_n = 1;
	rd_rst_n = 1;
end


initial begin
	//第一段仿真
	@(negedge wr_clk) wr_en = 1;wr_data = $random;
	repeat(7) begin   		
		@(negedge wr_clk)
		wr_data = $random;
	end
	@(negedge wr_clk) wr_en = 0;
	@(negedge rd_clk) rd_en = 1;
	
	repeat(8) begin
		@(negedge rd_clk); 
	end 
	@(negedge rd_clk) rd_en = 0;
	# 150
	
	//第二段仿真
	@(negedge wr_clk)
	wr_en = 1;
	wr_data = $random;
	repeat(18) begin
	@(negedge wr_clk)
		wr_data = $random;
	end
	@(negedge wr_clk) wr_en = 0;	
	@(negedge rd_clk) rd_en = 1;
	repeat(18) begin
		@(negedge rd_clk);
	end
	rd_en = 0;
	
	// 第三段仿真
	repeat(9) begin
	@(negedge wr_clk)
		wr_data = $random;wr_en = 1;
	end
	rd_en = 1;
	repeat(19) begin
	@(negedge wr_clk)
		wr_data = $random;
	end
	@(negedge wr_clk) wr_en = 0;	
	@(negedge rd_clk) rd_en = 0;


	#20 $finish;	


end
	
asynchronous_fifo #(
		.FIFO_WIDTH	(FIFO_WIDTH),
		.FIFO_DEPTH	(FIFO_DEPTH)
		)
	asynchronous_fifo (	
		.wr_clk			(wr_clk),
		.rd_clk			(rd_clk),
		.wr_rst_n		(wr_rst_n),
		.rd_rst_n		(rd_rst_n),
		.wr_en			(wr_fifo_en),
		.rd_en			(rd_fifo_en),
		.wr_data		(wr_data),
		
		.wr_full		(wr_full),
		.rd_empty		(rd_empty),
		.rd_data		(rd_data)
	);

endmodule

3、仿真截图

读比写快
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

写比读快
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

这个代码也还有些问题,希望大家多多包容并且指出错误或者可以改善的地方,一起进步。

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

发布者:全栈程序员-用户IM,转载请注明出处:https://javaforall.cn/129007.html原文链接:https://javaforall.cn

【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛

【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...

(0)


相关推荐

  • 支持向量机的原理

    一、什么是支持向量机  支持向量机(supportvectormachine,简称SVM)是一种基于统计学习理论的新型学习机,是由前苏联教授Vapnik最早提出的。与传统的学习方法不同,支持向量机是结构风险最小化方法的近似实现。这个归纳原理是基于这样的事实,学习机器在测试数据上的误差率(即泛化误差率)以训练误差率和一个依赖于Vc维数(Vapnik-Chervonenkisdimensio…

  • 海康sdk协议接口_海康sadp搜索不到设备

    海康sdk协议接口_海康sadp搜索不到设备海康威视RTSP转RTMP协议,使ChromeSafari上可以直接播放。

  • jar包如何防止反编译_jar包可以反编译成源码吗

    jar包如何防止反编译_jar包可以反编译成源码吗方法就是,向Jar注入无效代码(不合法的,或者根本不是代码的字符串)。那么无效的代码又怎么能正确运行呢?答案就是,你要保证你的代码永远不会执行到那一步。我作一个简单的例子说明:我们建立一个项目:packagecom.TestJar;publicclassMain{ publicstaticvoidmain(String[]args){ System.out.println(Info.g…

    2022年10月31日
  • linux搭建sendmail邮件服务器,Linux系统Sendmail架设Mail服务器[通俗易懂]

    一、安装Sendmail完全安装RedHatLinux9.0时,Sendmail就会自动内置,版本号为8.12.8-4.如果你不确定Linux是否已经安装有sendmail,可以输入以下命令查看:[root@ahpengroot]rpm–qa grepsendmail如果确定没有安装,请在图形界面下依次选择单击“主菜单-系统设置-添加删除应用程序”,然后在打开的“软件包管理”窗口里选…

  • J2EE是什么?_servlet是什么

    J2EE是什么?_servlet是什么J2EE是Sun公司提出的多层(multi-diered),分布式(distributed),基于组件(component-base)的企业级应用模型(enterpriese application model).在这样的一个应用系统中,可按照功能划分为不同的组件,这些组件又可在不同计算机上,并且处于相应的层次(tier)中。所属层次包括客户层(clietn tier)组件,web层和组件,Bus

    2022年10月11日
  • Photoshop CC 2019选区的基本操作(快捷键)

    Photoshop CC 2019选区的基本操作(快捷键)1.全选与反选ctrl+A2.取消选择和重新选择取消:ctrl+D重新选择:ctrl+shift+D3.运算选区shift+拖动鼠标,添加到选区alt+拖动鼠标,从选区减去alt+shift+拖动,与选区交叉4.移动选区鼠标到选区边缘即可移动…

发表回复

您的电子邮箱地址不会被公开。

关注全栈程序员社区公众号