FPGA之fifo设计

                                                           FPGA之手撕fifo(含设计代码和仿真)

本文回答以下几个问题:

1:fifo的读空和写满信号如何给出

2:fifo的写控制模块设计

3:fifo的读控制模块设计

4:双口RAM使用

5:顶层文件

6:仿真文件编写

7:modelsim的RTL仿真

 

1:上一篇文章(FPGA之FIFO IP核详细教程)已经简单说了一下读写指针变换准则:概括一句话就是读地址和写地址不能相同,读地址不能追上写地址,写地址不能追上读地址(多一个周期),你可以把他们想成在一个圆圈上转动。那读写地址相同的情况如何区分呢?到底是读地址追上写地址(读空),还是写地址经过一个存储深度周期后追上读地址(写满)呢?

    这一段回答上一段的两个问题。对于读写地址来说地址的每一位对应其在地址数的权值,如4位地址MSB(高位)到LSB(低位)都有自己的数值分别是8,4,2,1,换句话说就是我有专门的用处了,别让我干其他事情了,心有余而力不足。所以无法用真实对应于RAM上地址的其中一位或者多位来区分读空和写满信号。这时只有在高位增加一位来区分。如8位地址在首位增加一位,看起来是16个数,其实真实地址(下面说的读地址写地址都是4位,真实地址是3位)还是只有8。先说读空这种情况最好理解,就是读地址追上了些地址,也就是读写地址的每一位都一样。图1右侧是写满时候的指针情况,可以看到读地址是0001,写地址是1001,首位不同而后三位相同。

      读写地址要对比,要涉及到跨时钟域了,但是地址仅仅是8421BCD编码在跨时钟域时候有缺陷:相邻地址间存在多bit变化情况如0111——>1000就发生4bit变化,多比特跨时钟域传输很容易产生亚稳态。这时你可能会想把信号在另一个时钟域打两拍就可以大大降低亚稳态发生概率了。没错,确实是可以将信号打两排,但是地址位数较多时候会消耗很多芯片内部的资源。

                                                                    图1 4位8421BCD码

 

       而格雷码相邻地址只有1bit变化,所以讲地址在跨时钟域前前转换成格雷码,然后再打两拍即可。格雷码的产生,8421编码的二进制右移一位再异或初始的二进制数如0010转换成格雷码的过程是0010右移一位变成0001,0001异或0010就是0011。格雷码编码中读空和上面一样只要读写地址一样就是读空。而写满稍有变化,还以地址2为例,这时候看到读地址指向0001,写地址指向1101,对比时候将读或者写的地址高两位取反如果相同则说明已经写满。

 

 

                                                                       图2 4位二进制格雷码编码

在说第二个问题前,先看一下整个fifo的“框架”,(示意的可能不规范)但心里至少有一个框架。

 

3

                                                                           图3 fifo框架

2:写控制模块

module	wr_ctrl(

	input		wire			wrclk,
	input		wire			rst_n,	
	input		wire	[8:0]	gray_rd_addr,
	input		wire			wr_req,//写请求
	output		reg				wr_full,
	output		wire	[8:0]	gray_wr_addr,
	output		wire	[8:0]	wr_addr
	
);
	wire				wr_en;
	reg		[8:0]		gray_rd_addr1;
	reg		[8:0]		gray_rd_addr2;
	reg		[8:0]		wraddr;
	
			
assign	wr_addr = wraddr;
assign	wr_en = (~wr_full) & wr_req;//写使能信号产生
assign	gray_wr_addr = (wr_addr >> 1) ^ wr_addr;

always @(posedge wrclk or negedge rst_n)
		if(rst_n == 1'b0)
		 wraddr <= 9'd0;
		 else if((~wr_full) & wr_en)
		 wraddr <= wraddr +1'b1;
		// else  wraddr <= 9'd0;
		
//把来自读时钟的格雷码地址打两排
always @(posedge wrclk or negedge rst_n)
		if(rst_n == 1'b0)
		 {gray_rd_addr2[8:0],gray_rd_addr1[8:0]} = 18'b0;
		else
		 {gray_rd_addr2[8:0],gray_rd_addr1[8:0]} = {gray_rd_addr1[8:0],gray_rd_addr} ;

//写满信号产生
always @(posedge wrclk or negedge rst_n)
		if(rst_n == 1'b0)
		 wr_full <= 1'b0;
		else if({~gray_rd_addr2[8:7],gray_rd_addr2[6:0]} == (gray_wr_addr[8:0]))
		 wr_full <= 1'b1;
		else
		 wr_full <= 1'b0;
		

endmodule

3:读控制模块

module rd_ctrl(
		input		wire		rdclk,
		input		wire		rst_n,
		input		wire	[8:0]	wr_gray_addr,
		input		wire	        rd_en,
		output		reg		rd_empty,
		output		wire	[8:0]	rd_gray_addr,
		output		wire	[8:0]	rd_addr

);
		reg		[8:0]		w_gaddr1;
		reg		[8:0]		w_gaddr2;
		reg		[8:0]		rdaddr;
assign	rd_gray_addr = (rd_addr >> 1) ^ rd_addr;//读地址格雷码
assign	rd_addr	= rdaddr;


always @(posedge rdclk or negedge rst_n)
		if(rst_n == 1'b0)
		  rdaddr <= 9'd0;
		  else if((~rd_empty) & rd_en)
		  rdaddr <= rdaddr + 1'b1;
		   //else rdaddr <= 9'd0;

//输入的写地址打两排
always @(posedge rdclk or negedge rst_n)
		if(rst_n == 1'b0)
		 {w_gaddr2,w_gaddr1} <= 18'b0;
		else
		 {w_gaddr2,w_gaddr1} <= {w_gaddr1,wr_gray_addr};
		 
//读空信号产生
always @(posedge rdclk or negedge rst_n)
		if(rst_n == 1'b0)
		 rd_empty <= 1'b0;
		else if(rd_gray_addr == w_gaddr2)
		 rd_empty <= 1'b1;
		else rd_empty <= 1'b0; 
		
endmodule

 

4:双口RAM(这里用到RAM IP核)

module	dp_ram(
		input		wire		rdclk,
		input		wire		wrclk,
		input		wire		rst_n,
		input		wire	[7:0]	w_data,
		output		wire		rden,
		output		wire		wren,
		input		wire	[8:0]	wr_addr,
		input		wire	[8:0]	rd_addr,
		output		wire		wr_full,
		output		wire		rd_empty,
		output		wire	[7:0]	rd_data		

);

		wire			ram_wren;
		wire			ram_rden;
assign	ram_wren = (~wr_full) & wren;
assign	ram_rden = (~rd_empty) & rden;
		

dpram_8x256	dpram_8x256_inst (
	.data ( w_data ),
	.rdaddress ( rd_addr[7:0] ),
	.rdclock ( rdclk ),
	.rden ( ram_rden ),
	.wraddress ( wr_addr[7:0] ),
	.wrclock ( wrclk ),
	.wren ( ram_wren ),
	.q ( rd_data )
	);

endmodule

5:顶层模块

module top_fifo(
		input		wire			wrclk,
		input		wire			rdclk,
		input		wire			rst_n,
		input		wire			wrfull,
		input		wire			rden,
		input		wire			wren,
		input		wire	[7:0]	        data_in,
		output		wire	[7:0]	        data_o
);
	//wire			wrfull;
	wire			rdempty;
	wire	[8:0]	rd_addr;
	wire	[8:0]	wr_addr;
	wire	[8:0]	wr_gaddr;
	wire	[8:0]	rd_gaddr;

dp_ram dp_ram_inst(                                 
			.rdclk		(rdclk),		      
			.wrclk		(wrclk),      
			.rst_n		(rst_n),      
			.w_data		(data_in),     
			.rden		(rden),       
			.wren		(wren),       
			.wr_addr	(wr_addr),    
			.rd_addr	(rd_addr),    
			.wr_full	(wrfull),    
			.rd_empty	(rdempty),   
			.rd_data	(data_o)
	
);

wr_ctrl wr_ctrl_inst(                              
                                      
			.wrclk			(wrclk),        
			.rst_n			(rst_n),	      
			.gray_rd_addr	(rd_gaddr), 
			.wr_req			(wren),       
			.wr_full		(wrfull),      
			.gray_wr_addr 	(wr_gaddr),
			.wr_addr		(wr_addr)
                                      
 );   
                                   
rd_ctrl rd_ctrl_inst(                                                   
			.rdclk			(rdclk),       
			.rst_n			(rst_n),       
			.wr_gray_addr	(wr_gaddr),	
			.rd_en			(rden),       
			.rd_empty		(rdempty),    
			.rd_gray_addr	(rd_gaddr),
			.rd_addr		(rd_addr)
                                                 
);                                                                      


endmodule
                                        
                                        

6:仿真文件

`timescale	1ns/1ns
module	tb_fifo;
reg				r_clk;
reg				w_clk;
reg				rst_n;
reg				wren;
reg		[7:0]	        w_data;
reg				rden;
wire			        w_full;
wire	        [7:0]	        r_data;
 


initial	begin
	r_clk <= 0;
	w_clk <= 0;
	rst_n <= 0;
	
	#100
	rst_n <= 1;	
end

initial begin
	w_data <= 0;
	wren <=0;
	
	#200
	wr_data_fun();
end

initial	begin
	rden <= 0;
	//@(posedge	w_full)
	#500
	rd_data_fun();
end

always #7 r_clk =~r_clk;
always #10 w_clk =~w_clk;

 top_fifo top_fifo_inst(
			.wrclk		(w_clk), 	
			.rdclk		(r_clk), 	
			.rst_n		(rst_n), 	
			.wrfull		(w_full),	
			.rden		(rden),  	
			.wren		(wren),  	
			.data_in	(w_data),
			.data_o 	(r_data)
);

task wr_data_fun();
	integer i;
	begin
		for (i=0;i<256;i=i+1)
		begin
			@(posedge w_clk);
			wren=1'b1;
			w_data=i;
		end
		@(posedge w_clk);
		wren = 1'b0;
		w_data =0;
	end
endtask

task rd_data_fun();
	integer i;
	begin
		for (i=0;i<256;i=i+1)
		begin
			@(posedge r_clk);
			rden=1'b1;
		end
		@(posedge r_clk);
		rden = 1'b0;
	end
endtask
endmodule

7:仿真

图4,同步fifo起始信号变化,读空信号在地址

                                                                                    图4

图5异步fifo信号变化

 

                                                                                   图5

图6边读边写,读时钟频率大于写时钟的

                                                                                               图6

写完一个周期

既然看到这里就动手试一试吧。如果发现瑕疵和错误之处还请指出,作者虚心学习,感激不尽。需要整个设计评论里留下邮箱。

热门文章

暂无图片
编程学习 ·

yum本地云搭建

yum Yum(全称为 Yellow dog Updater, Modified)是一个在Fedora和RedHat以及CentOS中的Shell前端软件包管理器。基于RPM包管理,能够从指定的服务器自动下载RPM包并且安装,可以自动处理依赖性关系,并且一次安装所有依赖的软件包,无须繁琐地一次次下载、安装。 yum仓库配置文…
暂无图片
编程学习 ·

SSM整合小案例

SSM整合 数据库部分(Oracle)创建表 CREATE TABLE product( id varchar2(32) default SYS_GUID() PRIMARY KEY, productNum VARCHAR2(50) NOT NULL, productName VARCHAR2(50), cityName VARCHAR2(50), DepartureTime timestamp, productPrice Number, productDesc VARCHAR2(500…
暂无图片
编程学习 ·

一线互联网大厂300多道Java面试题【全面解析】,助你备战“金九银十”、进军BAT、斩获offer必备的核心知识点

前言今年因为疫情原因,很多人在家里宅了很长一段时间,“金三银四”黄金季也随之而然的“泡汤”,所有的跳槽涨薪的黄金季都集中在了“金九银十”季,所以程序员的竞争会对比往年更加激烈,为了备战“金九银十”需要有充足的时间复习筹备,为面试做足准备。我这里这筹备了一份…
暂无图片
编程学习 ·

单调栈解决Next Greater Number一类题

单调栈是什么? 单调栈使得每次新元素入栈后,栈内元素都保持有序(单调递增或者单调递减)。 单调递增栈:栈中数据出栈的序列为单调递增序列。 单调递减栈:栈中数据出栈的序列为单调递减序列。 注意:这里所说的递增递减是出栈的顺序,不是栈中数据的顺序。 单调栈的应用 通…
暂无图片
编程学习 ·

旋翼机自主着陆-主要技术难点

搜索阶段: 远距离: ​ 目标为几个像素,并且淹没在环境里 ​ 完全没有任何目标或目标偶尔出现,如何进行导航 中远距离 ​ 目标部分容易被遮挡,如何进行目标检测 ​ 在光线条件较差的环境下,目标检测出现误判和无法工作的情况 近距离 ​ 目标在视场中占据较大部分,飞机…
暂无图片
编程学习 ·

[46]api接口文档的生成和与项目文档进行集成

导出api文档 登录Eolinker 平台,生成api文档因为我们不是付费用户,所以无法导出markdown文档,所以导出了html文档 集成到docsify 因为docsify默认时处理markdown的,所以如果我们放上一个html文件就会出现解析错误,所以我们需要对该路径启用不处理window.$docsify = {name:…
暂无图片
编程学习 ·

本地项目提交到Github上

1.在个人github主页创建一个空仓库2.填写完相关资料后再项目文件中打开本地git客户端3.进入到刚刚的新建仓库中,如图操作3.依次在git客户端内输入以下命令,这部会用到上面复制到的地址 git initgit add .origin后面的地址是你刚刚自己复制的地址 git remote add origin https:/…
暂无图片
编程学习 ·

客户端渲染与服务端渲染

本人是前端小白菜,最近在苦学前端,做点自己的学习小总结。欢迎各位大佬纠错。 模版引擎原来一开始是后端使用的,后来才慢慢支持前端,听起来很高大上的模版引擎,什么页面渲染,我不喜欢这么专业的难懂的叫法,所以我要自己亲自总结一下。 服务端渲染模版引擎不关心内容,只…
暂无图片
编程学习 ·

通过小项目学习23种设计模式(四)

通过读取文件导入数据库功能学习23种设计模式 第一次重构代码 目前代码写的很随性,导致以后业务增加时拓展起来繁杂,所以我们将已有逻辑进行第一重构: 抽取公共的行为生成接口 package com.xiaoma.fileimport.common;/*** 任务主执行类* 使用工厂模式,首先将任务共同行为抽象出…
暂无图片
编程学习 ·

COMP9101学习笔记 贪心算法的应用

1. 霍夫曼编码 (The Huffman Code) 1.1 怎么定义好的编码? 由于计算机处理二进制位序列,人们需要一种编码模式将文本处理成二进制位的长串,以英文为例,26个字母,空格和5种标点符号共32个符号需要编码,以二进制表示则需要5位编码(25=322^5 = 3225=32),比如00000代表字…
暂无图片
编程学习 ·

LeetCode——remove n-th node from end of list

题目描述: 给定一个链表,删除链表的倒数第n个节点并返回链表的头指针 例如, 给出的链表为:1->2->3->4->5, n= 2.↵↵ 删除了链表的倒数第n个节点之后,链表变为1->2->3->5. 备注: 题目保证n一定是合法的 请尝试只用一步操作完成该功能 解题思路: 删除…
暂无图片
编程学习 ·

Kotlin上手(一)

标准函数with with函数接收两个参数,第一个参数是任意类型的对象,第二个是Lambda表达式。with函数会在Lambda表达式中提供第一个参数的上下文,并使用Lambda表达式中的最后一行代码作为返回值返回。 fun test() {val list = listOf("Apple", "Banana", &…
暂无图片
编程学习 ·

“/“和 “\“以及“//“和“\\“有什么区别?

大家在学习生活中对于斜杠肯定不会陌生,不管在网址或者地址都会接触到这些东西,这几天刚好抽出一段时间来给大家讲解一下,有什么问题遗漏大家多多指正!!"/"正斜杠表示除法,分割;在windows系统中常用来分割“命令行参数”;正斜杠还表示虚拟路径,比如网址。&q…
暂无图片
编程学习 ·

POJ1270 Following Orders(拓扑排序+dfs回溯)

POJ1270 Following Orders(拓扑排序+dfs回溯) Description Order is an important concept in mathematics and in computer science. For example, Zorn’s Lemma states: ``a partially ordered set in which every chain has an upper bound contains a maximal element.’…