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

写完一个周期

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

热门文章

暂无图片
编程学习 ·

蓝鲸平台mongodb集群异常处理

问题回顾 蓝鲸平台中的配置平台(cmdb)数据存放在了mongodb集群中(三台mongodb服务器组成的集群),偶然间发现集群中的一个节点日志有报错。 报错信息如下: 2020-06-30T19:27:50.622+0800 I REPL [replication-0] We are too stale to use 10.10.10.2:27017 as a sync …
暂无图片
编程学习 ·

最新99道前端面试题

前言:7月份的第一天,毕业马上两年了,居安思危,为后边儿做个准备吧“即便不跳,也始终保持跳的能力”1.vue优点?答:轻量级框架:只关注视图层,是一个构建数据的视图集合,大小只有几十kb;简单易学:国人开发,中文文档,不存在语言障碍 ,易于理解和学习;双向数据绑定:…
暂无图片
编程学习 ·

RabbitMQ 教程

RabbitMQ 教程 文章目录RabbitMQ 教程消息中间件安装及管理windows安装:RabbitMQLinux安装Mac安装基本概念主要概念Exchange的类型RabbitMQ的工作模式及代码示例简单模式 Simple2.工作模式 work (资源竞争消费)3.发布订阅 publish/subscribe (广播)4.路由 routing5.主题订阅…
暂无图片
编程学习 ·

Scanner对象

Scanner对象 作为输入使用,主要有两种接收键盘输入字符的方法,next()方法和nextLine()方法,下面介绍一下 import java.util.Scanner;public class demo01 {public static void main(String[] args) {Scanner scanner = new Scanner(System.in);System.out.println("请输…
暂无图片
编程学习 ·

源代码编译安装

源代码编译 使用源代码安装软件的优点获得最新的软件版本,计时修复bug 根据用户需要,灵活定制软件功能应用场景举例安装较新版本的应用时 当前安装的程序无法满足需要时 需要为应用程序添加新的功能时Tarball封包.tar.gz和.tar.bz2格式居多 软件素材参考:http://sourceforg…
暂无图片
编程学习 ·

自定义控件三部曲之动画篇(四)——ValueAnimator基本使用

一、概述前面,我写过几篇有关Animation的文章,讲解了传统的alpha、scale、translate、rotate的用法及代码生成方法。其实这三篇文章讲的所有动画效果叫做Tween Animation(补间动画)在Android动画中,总共有两种类型的动画View Animation(视图动画)和Property Animator(属性…
暂无图片
编程学习 ·

面试常问的22个Linux命令

1.查找文件find / -name filename.txt根据名称查找/目录下的filename.txt文件。2.查看一个程序是否运行ps –ef|grep tomcat查看所有有关tomcat的进程3.终止线程kill -9 19979终止线程号位19979的线程4.查看文件,包含隐藏文件ls -al5.当前工作目录pwd6.复制文件包括其子文件到…
暂无图片
编程学习 ·

二叉搜索树与双向链表

题目描述 输入一棵二叉搜索树,将该二叉搜索树转换成一个排序的双向链表。要求不能创建任何新的结点,只能调整树中结点指针的指向。 package hk;import java.util.ArrayList;public class Solution {public TreeNode Convert(TreeNode pRootOfTree) {if (pRootOfTree==null){r…
暂无图片
编程学习 ·

靶机实战(DC-4)

DC-4实验环实验过程信息收集主机发现端口扫描服务发现网站指纹漏洞发现漏洞利用提权总结 实验环 靶机:DC-4 测试机:kali(IP:192.168.186.128),win10 实验过程 信息收集 主机发现 netdiscover -I eth0 -r 192.168.186.0/24 nmap -sn 192.168.186.0/24 arp-scan -l ,arp-sc…
暂无图片
编程学习 ·

Tensorflow实现卷积神经网络

Tensorflow实现卷积神经网络Tensorflow实现卷积神经网络卷积层池化层归一化层实现简单的卷积神经网络 Tensorflow实现卷积神经网络 卷积层 卷积核,步福,填充,多通道卷积,激活函数,卷积函数。 主要函数使用: 1.conv2d函数 tf.nn.conv2d(input, filter, strides, padding, …
暂无图片
编程学习 ·

人工智能常用数据预处理

人工智能常用数据预处理一级目录正态化、标准化、归一化、正则化区别和作用 一级目录 1.读数据 2.合并训练和测试 2.填充空白数据 4.改变非数字为数字 5.去除无关数据 6.降为(合并相关数据) 7.正态化数据(碗圆) 正态化、标准化、归一化、正则化区别和作用 1.正态化归一化是…
暂无图片
编程学习 ·

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.’…
暂无图片
编程学习 ·

一文聊透This

Thisthis指向当前属性所在对象。 var A = {name: 张三,describe: function () {return 姓名:+ this.name;} };var name = 李四; var f = A.describe; f() // "姓名:李四"JavaScript 语言之中,一切皆对象,运行环境也是对象,所以函数都是在某个对象之中运行,this…
暂无图片
编程学习 ·

手把手撸一个轮播图

轮播图HTML和CSSJS HTML和CSS <!DOCTYPE html> <html lang="en"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>轮播图</titl…
暂无图片
编程学习 ·

ubuntu20.04微信无法输入中文解决

打开输入法首选项, 勾选show suggestion去微信聊天框输入你好,会提醒下一个字,将提醒的字按空格输入到聊天框,就可以输入中文了 缺点就是每次都要这么操作
暂无图片
编程学习 ·

文件夹内图片批量重命名代码

文件夹内图片批量重命名代码import os import re import sys import cv2 import torchvision.transforms as transforms path = r"data/masks/" def renameall(path):fileList = os.listdir(path) # 待修改文件夹currentpath = os.getcwd() # 得到进程当前工作目录…