异步FIFO_Verilog实现「建议收藏」

异步FIFO_Verilog实现「建议收藏」异步FIFO_Verilog实现概述:FIFO本质上还是RAM,是一种先进先出的数据缓存器(先存入的数据先取出)。它与普通存储器的区别:没有外部读写地址线,只能顺序写入数据,顺序的读出数据,其数据地址由内部读写指针自动加1,不像其他存储器可以由地址线决定读取或写入某个指定的地址,异步FIFO读写时钟不同,读写是相互独立的。用途:(1)跨时钟域多bit传输:读写可以由不同的时钟控制,使用异步FIFO可以在两个不同时钟系统之间快速方便的传输数据。(2)数据匹配:对于不同宽度的数据接口可以使用FIFO,

大家好,又见面了,我是你们的朋友全栈君。如果您正在找激活码,请点击查看最新教程,关注关注公众号 “全栈程序员社区” 获取激活教程,可能之前旧版本教程已经失效.最新Idea2022.1教程亲测有效,一键激活。

Jetbrains全系列IDE使用 1年只要46元 售后保障 童叟无欺

异步FIFO_Verilog实现

概述: FIFO本质上还是RAM,是一种先进先出的数据缓存器(先存入的数据先取出)。它与普通存储器的区别:没有外部读写地址线,只能顺序写入数据,顺序的读出数据,其数据地址由内部读写指针自动加1,不像其他存储器可以由地址线决定读取或写入某个指定的地址,异步FIFO读写时钟不同,读写是相互独立的。
用途:
(1)跨时钟域多bit传输:读写可以由不同的时钟控制,使用异步FIFO可以在两个不同时钟系统之间快速方便的传输数据。
(2)数据匹配:对于不同宽度的数据接口可以使用FIFO,比如写入数据宽度为8bit,读取数据宽度为16bit,通过FIFO数据缓存器就可以达到数据匹配。


1、FIFO的原理简介

  FIFO简单的说就是数据先进先出的存储器,存储器端口图示如下:

在这里插入图片描述
端口分别为:
复位信号: rst
读写端信号:

写时钟 wr_clk 读时钟 rd_clk
写使能信号 wr_en (in) 读使能信号 rd_en(in)
写数据 din (in) 读数据 dout (out)
写满信号 full (out) 读空信号 empty (out)

数据进出示意图:

  先写入的数据先被读出,写入数据的顺序为D0 D1 D2 D3 则在输出端口读出的数据顺序也为 D0 D1 D2 D3 。
在这里插入图片描述

2、FIFO空、满信号的检测

  FIFO的主要是通过空(empty)信号、满信号(full)来控制数据的读写的,如果FIFO为空还读取数据势必会出现数据的错误,FIFO已满再写数据就会导致FIFO溢出同样会导致数据出错。那FIFO的空、满信号是怎么产生的呢。首先我们要明白读写FIFO的时候我们不用操作读写地址,但是我们在写入数据或者读出数据时FIFO内部会通过写入或读出数据的操作在内部进行地址的自动增加。当FIFO满或者空时我们通过内部读写地址的特征就会判断出FIFO的状态。

   通过深度为8的FIFO来寻找FIFO空满信号和内部读写地址的关系:
深度为8的FIFO:
深度为8的FIFO即最大可以存放8个数据,地址所需位数为3,在设计FIFO时我们将地址为扩展为4位根据我们扩展的位可以判断FIFO的空满状态。

8深度的FIFO经过以下4个操作:(下面方框代表地址)
1、 当FIFO为空时,读指针为0_000B,写指针为0_000B,此时FIFO里面没有数据,FIFO的状态为
在这里插入图片描述

2、当FIFO写入8个数据时,写指针指向1_000B,读指针还为0_000B,此时FIFO里有8个数据,FIFO状态应该为
在这里插入图片描述

3、当FIFO读出8个数据时,写指针还指向1_000B,读指针为1_000B,此时FIFO里面没有数据,FIFO的状态为
在这里插入图片描述
4、向FIFO再写入8个数据,写指针指向0_000B,读指针还为1_000B,此时FIFO里有8个数据,FIFO状态应该为
在这里插入图片描述
通过上述观察发现地址除了最高位余下的3位为地址0-7的循环。

总结如下表:

FIFO操作 写地址指向(B) 读地址指向(B) FIFO状态
空FIFO 0_000 0_000
写入8个数据 1_000 0_000
读出8个数据 1_000 1_000
写入8个数据 0_000 1_000
读出8个数据 0_000 0_000

引入格雷码(原因往下看):

FIFO操作 写地址指向(B) Gray 读地址指向(B) Gray FIFO状态
空FIFO 0_000 0000 0_000 0000
写入8个数据 1_000 1100 0_000 0000
读出8个数据 1_000 1100 1_000 1100
写入8个数据 0_000 0000 1_000 1100
读出8个数据 0_000 0000 0_000 0000

观察上图FIFO满时读写地址的高2位不同其余位均相同;FIFO空时读写地址完全相同。
所以:

判断异步FIFO空的条件:读写地址(格雷码)完全相同。
判断异步FIFO满的条件:读写地址(格雷码)的高2位不同,其余位均相同。

3、二进制至格雷码的转换

   格雷码的特征是相邻的数之间只有1位二进制数不同。因为FIFO为异步设计,读写都有不同的时钟,读写是相互独立的,因为空满信号的判断是借助于读写地址进行的,所以涉及到了不同的时钟域,当二进制读地址从0111向1000变化时,地址所有位都要变化,如果写时钟恰好在读地址的变化时刻采样,写所得到的读地址值有可能是从0000到1111中的任何一个(即亚稳态的发生),可以采用格雷码形式。由于格雷码每次只变化一位,采用格雷码可以降低亚稳态的发生概率。
码表:

十进制数 二进制码 格雷码
0 0000 0000
1 0001 0001
2 0010 0011
3 0011 0010
4 0100 0110
5 0101 0111
6 0110 0101
7 0111 0100
8 1000 1100
9 1001 1101
10 1010 1111
11 1011 1110
12 1100 1010
13 1101 1011
14 1110 1001
15 1111 1000

二进制至格雷码的转换:
在这里插入图片描述
二进制转格雷码代码:

module top(

		input	sys_clk,
		input	rst_n,

		input	[4:0]bin_code,
		output	[4:0]gray_code

    );
		
assign	gray_code = (bin_code>>1)^bin_code;

endmodule

测试代码:

module test_bench;

reg			sys_clk;
reg			rst_n;

reg		[4:0]bin_code;
wire	[4:0]gray_code;

top	u1
(
	.sys_clk	(sys_clk),
	.rst_n		(rst_n),
	.bin_code	(bin_code),
	.gray_code	(gray_code)
);

initial
begin
	rst_n 	= 0;	
	sys_clk = 0;
	bin_code= 5'b0_0000; #20; rst_n = 1; bin_code= 5'b1_0110;

end

always #10 sys_clk = ~sys_clk;

endmodule

波形:
在这里插入图片描述

4、异步FIFO Verilog代码实现

代码注意: 读指针同步到写时钟域、写指针同步到读时钟域时要通过两级D触发器来进行同步处理。
方法:两级寄存器同步 + 格雷码
读指针同步到写时钟域、写指针同步到读时钟域时要通过两级D触发器来进行同步处理。

由于设计的时候读写指针用了至少两级寄存器同步,同步会消耗至少两个时钟周期,势必会使得判断空或满有所延迟,这会不会导致设计出错呢?
  异步FIFO通过比较读写指针进行满空判断,但是读写指针属于不同的时钟域,所以在比较之前需要先将读写指针进行同步处理,
将写指针同步到读时钟域再和读指针比较进行FIFO空状态判断,因为在同步写指针时需要时间,而在这个同步的时间内有可能还会写入新的数据,因此同步后的写指针一定是小于或者等于当前实际的写指针,所以此时判断FIFO为空不一定是真空,这样更加保守,一共不会出现空读的情况,虽然会影响FIFO的性能,但是并不会出错。
将读指针同步到写时钟域再和写指针比较进行FIFO满状态判断,同步后的读指针一定是小于或者等于当前的读指针,所以此时判断FIFO为满不一定是真满。

这样更保守,这样可以保证FIFO的特性:FIFO空之后不能继续读取,FIFO满之后不能继续写入。 总结来说异步逻辑转到同步逻辑不可避免需要额外的时钟开销,这会导致满空趋于保守,但是保守并不等于错误,这么写会稍微有性能损失,但是不会出错。

异步FIFO Verilog实现代码:

module top
#
(
	parameter	FIFO_DEPTH = 8,
	parameter	FIFO_WIDTH = 16

)
(
		input						rst,
		
		input						wr_clk,
		input						wr_en,
		input	[FIFO_WIDTH-1:0]	din,
		output	reg					full,
			
		input						rd_clk,
		input						rd_en,
		output	[FIFO_WIDTH-1:0]	dout,
		output	reg					empty
);
	
//定义
reg		[FIFO_WIDTH-1:0]mem[FIFO_DEPTH-1:0];

reg		[3:0]	wr_add;
wire	[3:0]	wr_add_next;
wire	[3:0]	wr_add_gray_next;
reg		[3:0]	wp,wr1_rp,wr2_rp;

reg		[3:0]	rd_add;
wire	[3:0]	rd_add_next;
wire	[3:0]	rd_add_gray_next;
reg		[3:0]	rp,rd1_wp,rd2_wp;

wire			full_r;
wire			empty_r;

//输入数据
always@(posedge wr_clk)
begin
	if(wr_en && !full)
		mem[wr_add] <= din;
	else
		mem[wr_add] <= 16'h0000;
end

//输出数据
assign	dout = mem[rd_add];

//写时钟域
assign	wr_add_next 	 = wr_add + (wr_en & ~full);
assign	wr_add_gray_next = (wr_add_next>>1)^wr_add_next;
always@(posedge wr_clk or posedge rst)
begin
	if(rst)
		{ 
   wr_add,wp} <= { 
   4'b0000,4'b0000};
	else
		{ 
   wr_add,wp} <= { 
   wr_add_next,wr_add_gray_next};

end

//读时钟域
assign	rd_add_next 	 = rd_add + (rd_en & ~empty);
assign	rd_add_gray_next = (rd_add_next>>1)^rd_add_next;
always@(posedge rd_clk or posedge rst)
begin
	if(rst)
		{ 
   rd_add,rp} <= { 
   4'b0000,4'b0000};
	else
		{ 
   rd_add,rp} <= { 
   rd_add_next,rd_add_gray_next};
end

//读指针同步到写时钟域
always@(posedge wr_clk or posedge rst)
begin
	if(rst)
		{ 
   wr2_rp,wr1_rp} <= { 
   4'b0000,4'b0000};
	else
		{ 
   wr2_rp,wr1_rp} <= { 
   wr1_rp,rp};

end

//写时钟同步到读时钟域
always@(posedge rd_clk or posedge rst)
begin
	if(rst)
		{ 
   rd2_wp,rd1_wp} <= { 
   4'b0000,4'b0000};
	else
		{ 
   rd2_wp,rd1_wp} <= { 
   rd1_wp,wp};
end

//空信号判断
assign	empty_r = (rd2_wp == rd_add_gray_next)?1:0;
always@(posedge rd_clk or posedge rst)
begin
	if(rst)
		empty <= 1'b1;
	else
		empty <= empty_r;
end

//满信号判断
assign	full_r = (~wr2_rp[3:2] == wr_add_gray_next[3:2])?1:0;
always@(posedge wr_clk or posedge rst)
begin
	if(rst)
		full <= 1'b0;
	else
		full <= full_r;
end
	
endmodule

测试代码:

module test_bench;

reg						rst;
		
reg						wr_clk;
wire					wr_en;
reg	[15:0]				din;
wire					full;
	
reg						rd_clk;
wire					rd_en;
wire [15:0]				dout;
wire					empty;

wire[15:0]				data_out;

top	u1
(
	.rst	(rst),
	
	.wr_clk	(wr_clk),
	.wr_en	(wr_en),
	.din	(din),
	.full	(full),
	
	.rd_clk	(rd_clk),
	.rd_en  (rd_en ),
	.dout   (dout  ),
	.empty  (empty )
);

initial
begin
	rst 	= 1;	
	wr_clk	= 0;
	rd_clk	= 0;


	#50;
	rst 	= 0;	

end
//FIFO Write
assign	wr_en 		=(rst==1)?1'b0: !full;

always@(posedge wr_clk or posedge rst)
begin
	if(rst)
	begin
		din			<= 16'h0000;
	end
	else if(wr_en == 1'b1)
	begin
		din 		<= (din == 16'd8)?16'h0000:din + 16'd1;
	end
	else
	begin	
		din			<= 16'h0000;
	end
end
//FIFO Read
assign	rd_en 	 = !empty;

assign	data_out = (rd_en==1'b1)?dout:16'h0000;


//时钟产生
always #10 wr_clk = ~wr_clk;
always #15 rd_clk = ~rd_clk;

endmodule

FIFO读写 仿真波形:
在这里插入图片描述

★★★如有错误,欢迎指导!!!

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

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/170196.html原文链接:https://javaforall.net

(0)
全栈程序员-站长的头像全栈程序员-站长


相关推荐

  • 《JavaScript 模式》读书笔记(8)— DOM和浏览器模式2

    四、长期运行脚本可能会注意到有时候浏览器会提示某个脚本已经运行了很长时间,是否应该停止该脚本。实际上无论要处理多么复杂的任务,都不希望应用程序发生上述事情。而且,如果该脚本的工作十分繁重,那么浏览器

    2022年3月25日
    34
  • Latex公式换行与对齐

    Latex公式换行与对齐Latex公式换行与对齐1、应用amsmath包:\usepackage{amsmath}2、公式中用aligned:\begin{equation}\begin{aligned}……\end{aligned}\end{equation}或者直接用align:\begin{align}……\end{align}3、换行时用“\”换行符\begin{align}a=1+2+3+4+5\\=6+9\end{align}效果:4、左对齐用“&amp

    2022年6月12日
    55
  • Android应用开发揭秘

    Android应用开发揭秘《Android应用开发揭秘》                                              ///////http://www.iteye.com/wiki/hzbook/2249-Android  书名:Android应用开发揭秘作者:杨丰盛出版社:机械工业出版社ISBN:9787111291954出版日期:2010年3月(1版2次)…

    2022年4月28日
    40
  • Java编程思想重点笔记(Java开发必看)「建议收藏」

    Java编程思想重点笔记(Java开发必看)「建议收藏」Java编程思想,Java学习必读经典,不管是初学者还是大牛都值得一读,这里总结书中的重点知识,这些知识不仅经常出现在各大知名公司的笔试面试过程中,而且在大型项目开发中也是常用的知识,既有简单的概念理

    2022年7月4日
    22
  • java后端开发职责_工作职责和岗位职责有什么区别

    java后端开发职责_工作职责和岗位职责有什么区别java后台开发岗位职责:1.参与项目后端的设计、开发工作,承担核心功能模块的代码编写,确保项目进度和质量;2.参与开发人员codereview工作,并能提供性能优化、安全性建议;3.参与系统架构设计、接口规范制定、技术文档编写等。4.参与现有系统的优化改进。岗位要求:1.本科及以上学历,计算机相关专业优先,【扎实的数据结构/算法与编码能力】;2.JAVA基础扎实,1年及以上JAV…

    2025年5月27日
    0
  • Nginx简单配置转发

    问题分析一台服务器运行多个项目的时候会遇到这样的问题:如果使用同一个tomcat来启动不同项目的话,项目之间会相互影响;如果用多个tomcat运行项目,那么在访问项目的时候又不能都使用80端口,还要加上端口号,显得很麻烦又不美观。考虑用Nginx实现转发,目标是通过访问不同的域名实现对不同tomcat上运行的项目的跳转,例如访问www.a.com跳转到本地的8088端口的项目,访问www…

    2022年4月5日
    115

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

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