【小白入门】Verilog实现异步FIFO

5 篇文章 0 订阅
3 篇文章 1 订阅

 

 之前也在CSDN上面写过两个FIFO相关的文章,不过代码看起来比较复杂,且注释也比较少,不利于新手入门。很多时候都没有耐心继续看下去。

http://t.csdn.cn/0dPX6

http://t.csdn.cn/lYvoY 

因为自己本身是一个初学者,就从初学者的视角来看待并学习FIFO。

为什么选择学习FIFO?

在学完双端口RAM之后看待FIFO,会觉得为什么要用FIFO呢?双端口的RAM也可以实现数据的存储与读取,读写的时钟也可以不一样,为什么不用RAM而要基于RAM来设计一个FIFO呢?

FIFO与RAM的区别是先进先出,不需要读地址和写地址。 写时钟下,将数据写入FIFO中,在读时钟下将先写入的数据先读出来,不需要向FIFO输入写地址和读地址即可完成数据的存储以及不同时钟下的读写操作,这样一听是非常方便的。在SDRAM的学习过程中,我们知道有突发长度这个东西,当突发长度为1的时候,即一个写地址对应一个写数据,这样是非常麻烦的,所以很多SDRAM如DDR3这种,都会将突发长度设置为8,即给一个首地址,后面连续读取8个数据。

 

再贴一张异步FIFO的图

 

在写代码之前,需要了解几个概念。

 先思考,FIFO的存储空间和RAM是一样的,就像一个n行x1列的表格,每个表格里面存放一个数据,并且对应一个地址,在读写的过程中肯定会存在表格写满的情况和读的时候里面没有数据的情况,那应该怎么判断呢?

读写同时进行时

①首先是在读的视角,如果如果读一行数据的时候,刚好也在往这一行数据里面写数据,那这个时候即可判断读空了,如果再继续向下读的话,里面就没有写进的数据,读出的数据也不是我们写进去的,就是无效的。

所以读空的判断条件是:在读时钟的视角下,写时钟同步过来的地址等于我目前正在读的地址。

关于跨时钟域的问题,大家可以去搜索一下跨时钟域以及亚稳态。也可以看我的这篇文章。

http://t.csdn.cn/hvJTa

②在写的视角下, 那什么时候写满呢?因为地址是有限的嘛,当读完一个数据的时候,读对应哪个地址的数据就已经不需要了,因为我们以及读了,即读完的那个“位置”空了。所以当写完一圈,并且追上下一轮的读的时候,就代表写满了。

所以写满判断的条件是:在写的时钟下,写完一圈对应的地址,等于同步过来的读地址。

 

其次在写代码的时候,还需要了解格雷码,地址是按照0000-0001-0010-xxxx这种增长的,但是在地址变化的过程中,地址中的位数会存在”跳变“,如从0001-0010这两个相邻码的时候,有两位发生了变化,这样是不好的。 具体可以参考这篇文章

http://t.csdn.cn/OiesB

以下是代码Verilog的代码

`timescale 1ns / 1ps

module asyn_fifo1
(
		input		             rst_n		,
		
		input			         wr_clk		,
		input	                 wr_en		,
		input		 [7:0]       data_in	,
		input			         rd_clk		,
		input			         rd_en		,
		
		output		             full	    ,
		output			         empty		,
		output reg	[7:0]	     data_out	
);

		reg		[7:0]    ram_mem[255:0]    	;    //定义一个位宽为8bit深度为256的双端口RAM
		
		wire	[7:0]        rd_addr	    ;
		wire	[7:0]        wr_addr		;
		reg		[8:0]    rd_addr_ptr	    ;    //格雷码需要移位运算,且判断写满信号也需要多一位
		reg		[8:0]    wr_addr_ptr	    ;    
		
		wire  	[8:0]    rd_addr_gray	    ;
		reg		[8:0]    rd_addr_gray1	    ;
		reg		[8:0]    rd_addr_gray2	    ;
		wire	[8:0]    wr_addr_gray	    ;
		reg		[8:0]    wr_addr_gray1	    ;
		reg		[8:0]    wr_addr_gray2	    ;
		
		assign	rd_addr[7:0] = rd_addr_ptr[7:0];
		assign	wr_addr[7:0] = wr_addr_ptr[7:0];
		assign	rd_addr_gray = (rd_addr_ptr>>1) ^ rd_addr_ptr;		//bin to gray
		assign	wr_addr_gray = (wr_addr_ptr>>1) ^ wr_addr_ptr;
		
		//dual port ram
		integer	i;
		always @(posedge wr_clk or negedge rst_n)        //写时钟下初始化RAM
		begin
			if(rst_n == 1'b0)
				for(i=0;i<256;i=i+1)
					ram_mem[i] <= 1'b0;
			else if(wr_en && ~full)                      //写使能且没有写满
				ram_mem[wr_addr] = data_in;
			else
				ram_mem[wr_addr] = ram_mem[wr_addr];
		end
		
		//rd_addr_ptr++ and wr_addr_ptr++
		always @(posedge rd_clk or negedge rst_n)        //读时钟下,对读地址进行操作
		begin
			if(rst_n == 1'b0)
				rd_addr_ptr <= 1'b0;
			else if(rd_en && ~empty)
				rd_addr_ptr <= rd_addr_ptr + 1'b1;
			else
				rd_addr_ptr <= rd_addr_ptr;
		end
		always @(posedge wr_clk or negedge rst_n)        //写时钟下,对写地址进行操作
		begin
			if(rst_n == 1'b0)
				wr_addr_ptr <= 1'b0;
			else if(wr_en && ~full)
				wr_addr_ptr <= wr_addr_ptr + 1'b1;
			else
				wr_addr_ptr <= wr_addr_ptr;
		end
		
		//gray and two regsiter
		always @(posedge wr_clk or negedge rst_n)       //写时钟视角下,把读时钟同步到自己的时钟下
		begin
			if(rst_n == 1'b0)begin
				rd_addr_gray1 <= 1'b0;
				rd_addr_gray2 <= 1'b0;
			end
			else begin
				rd_addr_gray1 <= rd_addr_gray;
				rd_addr_gray2 <= rd_addr_gray1;
			end
		end
		always @(posedge rd_clk or negedge rst_n)        //读时钟视角下,把写时钟同步到自己的时钟下
		begin
			if(rst_n == 1'b0)begin
				wr_addr_gray1 <= 1'b0;
				wr_addr_gray2 <= 1'b0;
			end
			else begin
				wr_addr_gray1 <= wr_addr_gray;
				wr_addr_gray2 <= wr_addr_gray1;
			end
		end
		
		//data_out
		always @(posedge rd_clk or negedge rst_n)
		begin
			if(rst_n == 1'b0)
				data_out <= 1'b0;
			else if(rd_en && ~empty)
				data_out <= ram_mem[rd_addr];
			else
				data_out <= 1'b0;
		end
		
		assign empty = (rd_addr_gray == wr_addr_gray2)?1'b1:1'b0;    //判断读没读空
		assign full = (wr_addr_gray[8:7] != rd_addr_gray2[8:7]) && (wr_addr_gray[6:0] == rd_addr_gray2[6:0]);        //判断是否写满
endmodule

 

以下是tb仿真文件代码

`timescale 1ns / 1ps


module asyn_fifo1_tb();

        reg	                       	rst_n		;
		reg			                wr_clk		;
		reg	          	            wr_en		;
		reg		 [7:0]              data_in	    ;
		reg			                rd_clk		;
		reg			                rd_en		;

		wire		                full	    ;
		wire			            empty		;
		wire     [7:0]	            data_out	;
		
asyn_fifo1    asyn_fifo1_inst
(
		.rst_n	       (rst_n)	     ,	
		.wr_clk	       (wr_clk)      ,
		.wr_en	       (wr_en) 	     ,
		.data_in       (data_in)	 ,
		.rd_clk        (rd_clk)	     ,
		.rd_en	       (rd_en) 	     ,
		.full	       (full)        ,
		.empty	       (empty)       ,
		.data_out	   (data_out)
);
		initial wr_clk = 0;
		always #10 wr_clk = ~wr_clk;        //写时钟为50MHz
		
		initial rd_clk = 0;
		always #30 rd_clk = ~rd_clk;        //读时钟频率为写时钟的1/3
		
		always @(posedge wr_clk or negedge rst_n)    //不停的向FIFO中写0-255的数据
		begin
			if(rst_n == 1'b0)
			     data_in <= 0;
			else if (wr_en)
			     data_in <= data_in+1'b1;
			 else
			      data_in <= data_in;
		  end
			  initial  begin        
			     rst_n = 0;
			     wr_en = 0;
			     rd_en = 0;
			     #200;                //时间为200ns时,允许写入
			     rst_n = 1;
			     wr_en = 1;
			     #20000;               //时间再过20000ns时,不允许写,开始读
			     wr_en = 0;
			     rd_en = 1;
			     #20000;                //时间再过20000ns时,读停止
			     rd_en=0;
			     $stop;
			  end
endmodule

波形分析

200ns时,数据开始写入FIFO中,如下图

 写满时,full信号拉高,后面继续不停写入,但满了,都是无效的写入。

 

20200ns时,开始读数据

 读完后,empty信号拉高,表示读空。

 

由于写时钟为读时钟的三倍,从整体的波形图中也可以看出,写满数据的时间是读完数据时间的1/3.

 

 

 

 

 

 

 

  • 4
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
异步FIFO是一种用于在两个异步时钟域之间传输数据的解决方案。在Verilog实现异步FIFO可以采用以下步骤: 1. 定义FIFO的输入和输出接口:包括读写时钟、读写使能信号、读写数据和读写指针等。 2. 使用两个时钟域的寄存器同步输入信号:由于读写时钟不同,需要使用两级寄存器级联来同步输入信号,以消除亚稳态。 3. 实现FIFO的读写逻辑:根据读写使能信号和读写指针,确定读写操作的时机和数据。 4. 实现FIFO的存储器:可以使用RAM或者其他存储结构来存储数据。 5. 实现FIFO的读写指针逻辑:根据读写操作的完成情况,更新读写指针的值。 6. 添加互斥逻辑:为了避免读写冲突,可以使用互斥逻辑来控制读写操作的互斥性。 需要注意的是,在实现异步FIFO时,需要考虑跨时钟域的问题。可以使用两级寄存器同步和格雷码等方法来解决跨时钟域的问题,确保读写指针的比较正确。 总之,通过定义接口、同步输入信号、实现读写逻辑和存储器、更新读写指针以及添加互斥逻辑等步骤,可以在Verilog实现异步FIFO。 #### 引用[.reference_title] - *1* *3* [异步FIFO---Verilog实现](https://blog.csdn.net/alangaixiaoxiao/article/details/81432144)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control,239^v3^insert_chatgpt"}} ] [.reference_item] - *2* [基于Verilog实现异步FIFO](https://blog.csdn.net/ZHOUJIAN1997/article/details/121597269)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值