FPGA -手写异步FIFO

一,FIFO原理

        FIFO(First In First Out)是一种先进先出的数据缓存器,没有外部读写地址线,使用起来非常简单,只能顺序写入数据顺序的读出数据,其数据地址内部读写指针自动加1完成,不能像普通存储器那样可以由地址线决定读取或写入某个指定的地址。也正是由于这个特性,使得FIFO可以用作跨时钟域数据传输和数据位宽变换

二,双端口RAM

        FIFO中用来存储数据的器件为双口RAM,首先搭建一个Dual Ram(双口RAM)。我们以一个深度为16,数据位宽为8的Dual Ram为例,框图和时序如下。

        

Dual Ram读端和写端采用两个时钟,可以实现读写时钟为异步时钟,也可以实现读写同时进行的功能。代码实现如下:

// -----------------------------------------------------------------------------
// Author : RLG
// File   : Dual_Ram.v
// -----------------------------------------------------------------------------
`timescale 1ns / 1ps
module Dual_Ram#(parameter 			ADDR_WIDTH = 4,parameter 			DATA_WIDTH = 8)(input 								wrclk	,input 								rdclk	,input 								wr_en	,input 								rd_en	,	input 		[ADDR_WIDTH-1:0]		wr_addr ,input 		[ADDR_WIDTH-1:0]		rd_addr ,input  		[DATA_WIDTH-1:0]		wr_data	,output 	reg [DATA_WIDTH-1:0]		rd_data);/*---------------输入数据打一拍-------------*/reg [ADDR_WIDTH-1:0]	wr_addr_d1;reg [ADDR_WIDTH-1:0]	rd_addr_d1;reg [DATA_WIDTH-1:0]	wr_data_d1;reg 					wr_en_d1  ;reg 					rd_en_d1  ;/*----------------数据寄存----------------*/  reg [DATA_WIDTH-1:0] rd_data_out; reg [DATA_WIDTH-1:0] Data_reg [2**ADDR_WIDTH-1:0];/*---------------输入数据打拍-------------*/always @(posedge wrclk ) begin wr_addr_d1 <= wr_addr;rd_addr_d1 <= rd_addr;wr_data_d1 <= wr_data;wr_en_d1   <= wr_en  ;rd_en_d1   <= rd_en  ;end/*-------------------写数据-----------------*/always @(posedge wrclk ) begin if(wr_en_d1)Data_reg[wr_addr_d1] <= wr_data_d1;end/*-------------------读数据-----------------*/always @(posedge rdclk ) begin if(rd_en_d1)rd_data_out <=  Data_reg[rd_addr_d1];end/*-----------------输出打一拍----------------*/always @(posedge rdclk ) begin rd_data <= rd_data_out;endendmodule

二、FIFO地址设计

        我们知道FIFO中是没有地址线的,地址靠自身计数器自加1来控制,那么我们很容易想到把外部输入信号wr_addr和rd_addr换成内部信号并用计数器来控制其自加,计数器加满之后直接清零,从0重新开始写/读,循环往复。由于写端和读端的时钟速率不同,就会有快慢的问题,        

        那么就出现了一个问题,以地址2为例,写入的数据还没有被读出,又被新的数据覆盖了,造成数据丢失;或者写入的数据已经被读出,新的数据还没有写进来,地址2的老数据又被读了一遍,造成数据重复。

        为了解决上述问题,引入 full empty 信号来表示内部RAM中的数据写满或者读空,新的框图如下所示。

        

        如何产生full和empty信号呢,我们可以用 wr_addrrd_addr 来做判断,当 wr_clk 大于 rd_clk 时,会产生写满的情况,如下图中黄色部分代表已经写入数据,还未被读取,白色代表数据已被读取,图1中当 waddr>raddr时,waddr-raddr → 1111 - 0001 = 1110 可以表示两者的差值。

        图2中当 waddr<raddr 时,计算两者的差值为16 – raddr + waddr → 10000 - 1100 +1010 = 1110,此时的 waddr – raddr → 1010-1100 →1010+0011+0001=1110,两者结果相同,所以无论 waddr 大于 raddr 还是小于 raddr,都可以用 waddr-raddr 来表示写比读多几个数据。此时再引入一个full_limit用来设置一个写满的阈值。当waddr – raddr >= full_limit 时,full信号拉高,停止写入。

        同理,读比写快的情况下引入一个empty_limit来作为读空的阈值,当 waddr – raddr <= empty_limit 时。empty信号拉高停止读出。在实际工程中可以根据实际需要和 fifo 的设计区别灵活设置 full_limit 和empty_limit 的数值

三、空满信号判断

        使用读写地址进行判断空满信号。读地址rd_addr是在读时钟域wr_clk内,空信号empty也是在读时钟域内产生的;而写地址wr_addr是在写时钟域内,且满信号full也是在写时钟域内产生的。 那么,要使用读地址rd_addr与写地址wr_addr对比产生空信号empty,可以直接对比吗?        

        答案是不可以。

        因为这两个信号处于不同的时钟域内,要做跨时钟域CDC处理,而多bit信号跨时钟域处理,常用的方法就是使用异步FIFO进行同步可是我们不是在设计异步FIFO吗?

        于是,在这里设计异步FIFO,多bit跨时钟域处理的问题可以转化单bit跨时钟域的处理,把读写地址转换为格雷码后再进行跨时钟域处理,因为无论多少比特的格雷码,每次加1,只改变1位。把读地址rd_addr转换为格雷码,然后同步到写时钟域wr_clk;同样的,把写地址指wr_addr转换为格雷码,然后同步到读时钟域rd_clk

        二进制转格雷码:二进制的最高位作为格雷码的最高位,次高位的格雷码为二进制的高位和次高位相异或得到,其他位与次高位相同。

        代码:

  	assign wr_gray = (wr_addr >> 1) ^ wr_addr;assign rd_gray = (rd_addr >> 1) ^ rd_addr;

        格雷码转二进制:使用格雷码的最高位作为二进制的最高位,二进制次高位产生过程是使用二进制的高位和次高位格雷码相异或得到,其他位的值与次高位产生过程相同。

        代码:

assign wr_bin[ADDR_WIDTH-1] = wr_gray_d2[ADDR_WIDTH-1];genvar i;generatefor ( i = 0; i < ADDR_WIDTH-1; i=i+1) beginassign wr_bin[i] = wr_bin[i+1] ^ wr_gray_d2[i];endendgenerateassign rd_bin[ADDR_WIDTH-1] = rd_gray_d2[ADDR_WIDTH-1];genvar j;generatefor ( j = 0; j < ADDR_WIDTH-1; j=j+1) beginassign rd_bin[j] = rd_bin[j+1] ^ rd_gray_d2[j];endendgenerate

四、跨时钟域同步

        如何避免漏采和重采,首先考虑一个问题,地址同步要在哪个时钟域进行呢,我们所期望的结果是慢时钟地址同步到快时钟域,以免发生快时钟域信号漏采导致的读空或者写满。至于重采的情况,即慢时钟域信号被多采了一次,只会在判断空满状态时更安全,不会导致读空和写满这种不安全现象的出现。不过这样会产生虚假的full和empty信号,即full信号已经拉高,但ram中仍存有可用的地址,或者empty信号已经拉高,但ram中仍存有可被读出的数据。虽然效率和资源上有一点浪费,但不会发生丢失数据或读错数据的不安全行为

        那怎么实现慢时钟域的信号同步到快时钟域呢?因为若同时读写时出现 empty 则一定是读时钟快于写时钟,所以在判断 empty 状态时,读时钟域为快时钟,把较慢的写时钟同步到读时钟域来判断 empty。同理,若同时读写时出现 full 则一定是写时钟快于读时钟,所以在判断 full 状态时,写时钟域为快时钟,把较慢的读时钟同步到写时钟域来判断 full。以判断empty状态为例,过程如下图所示:

        其中B2G模块(二进制转格雷码)G2B模块(格雷码转二进制)empty判断模块均为组合逻辑,所以加一级D触发器以满足时序。圈中的两级D触发器用作消除跨时钟域同步的亚稳态。empty信号在RCLK快于WCLK时产生,中间虽然加入了四级D触发器,导致写地址同步到读时钟域时是之前的老地址,这和之前采重的问题一样,只会让empty的判断更安全,但会造成少许的资源浪费,属于保守但安全的做法。

        至此,一个简易的异步fifo就被设计出来了,总体框图如下:

代码:

// -----------------------------------------------------------------------------
// Author : RLG
// File   : async_fifo.v
// -----------------------------------------------------------------------------
`timescale 1ns / 1ps
module async_fifo#(parameter       ADDR_WIDTH = 4    ,parameter       DATA_WIDTH = 8    ,parameter       EMPTY_LIMIT= 1'b1  ,parameter       FULL_LIMIT = 4'd15)(input                           wrclk   ,input                           rdclk   ,input                           wr_rst_n,input                           rd_rst_n,   input                           wr_en   ,input                           rd_en   , input       [DATA_WIDTH-1:0]    wr_data ,output  reg [DATA_WIDTH-1:0]    rd_data ,output  reg             		    empty   ,output  reg             		    full);/*---------------输入数据打一拍-----------*/reg   [DATA_WIDTH-1:0]  wr_data_d1  ;reg                     wr_en_d1    ;reg                     rd_en_d1    ;/*--  --------------数据寄存----------------*/  reg   [DATA_WIDTH-1:0]  Data_reg [2**ADDR_WIDTH-1:0];/*--  --------------读写地址----------------*/ reg   [ADDR_WIDTH-1:0]  wr_addr    ;reg   [ADDR_WIDTH-1:0]  rd_addr    ;/*--  --------------二进制转格雷码------------*/ wire  [ADDR_WIDTH-1:0]  wr_gray     ;wire  [ADDR_WIDTH-1:0]  rd_gray     ;reg   [ADDR_WIDTH-1:0]  wr_gray_d0  ;reg   [ADDR_WIDTH-1:0]  rd_gray_d0  ;reg   [ADDR_WIDTH-1:0]  wr_gray_d1  ;reg   [ADDR_WIDTH-1:0]  rd_gray_d1  ;reg   [ADDR_WIDTH-1:0]  wr_gray_d2  ;reg   [ADDR_WIDTH-1:0]  rd_gray_d2  ;/*--  --------------格雷码转二进制------------*/ wire  [ADDR_WIDTH-1:0]  wr_bin    ;wire  [ADDR_WIDTH-1:0]  rd_bin    ;reg   [ADDR_WIDTH-1:0]  rd_bin_d0   ;reg   [ADDR_WIDTH-1:0]  wr_bin_d0 ;/*----------------empty 判读---------------*/ wire          empty_logic ;/*----------------full 判读---------------*/ wire          full_logic  ;/*---------------------------------------*\输入数据打拍\*---------------------------------------*/always @(posedge wrclk ) begin wr_data_d1 <= wr_data;wr_en_d1   <= wr_en  ;rd_en_d1   <= rd_en  ;end/*---------------------------------------*\写地址\*---------------------------------------*/always @(posedge wrclk ) begin if(~wr_rst_n)wr_addr<= 0;else if(wr_en_d1 && ~full) beginif(wr_addr == 'd15)wr_addr <= 0;elsewr_addr <= wr_addr + 1'b1;endend/*---------------------------------------*\读地址\*---------------------------------------*/always @(posedge rdclk ) begin if(~rd_rst_n)rd_addr<= 0;else if(rd_en_d1 && ~empty) beginif(rd_addr == 'd15)rd_addr <= 0;elserd_addr <= rd_addr + 1'b1;endend/*---------------------------------------*\写数据\*---------------------------------------*/always @(posedge wrclk ) begin if(wr_en_d1 && ~full)Data_reg[wr_addr] <= wr_data_d1;end/*---------------------------------------*\读数据\*---------------------------------------*/always @(posedge rdclk ) begin if(rd_en_d1 && ~empty)rd_data <=  Data_reg[rd_addr];end/*---------------------------------------*\二进制转格雷码\*---------------------------------------*/assign wr_gray = (wr_addr >> 1) ^ wr_addr;assign rd_gray = (rd_addr >> 1) ^ rd_addr;always @(posedge wrclk ) begin wr_gray_d0 <= wr_gray;endalways @(posedge rdclk ) begin rd_gray_d0 <= rd_gray;end/*---------------------------------------*\格雷码转二进制\*---------------------------------------*/ always @(posedge wrclk ) begin if(!wr_rst_n)beginrd_gray_d1 <= 0;rd_gray_d2 <= 0;endelse beginrd_gray_d1 <= rd_gray_d0;rd_gray_d2 <= rd_gray_d1;endendalways @(posedge rdclk ) begin if (!rd_rst_n) beginwr_gray_d1 <= 0;wr_gray_d2 <= 0;endelse beginwr_gray_d1 <= wr_gray_d0;wr_gray_d2 <= wr_gray_d1;endendassign wr_bin[ADDR_WIDTH-1] = wr_gray_d2[ADDR_WIDTH-1];genvar i;generatefor ( i = 0; i < ADDR_WIDTH-1; i=i+1) beginassign wr_bin[i] = wr_bin[i+1] ^ wr_gray_d2[i];endendgenerateassign rd_bin[ADDR_WIDTH-1] = rd_gray_d2[ADDR_WIDTH-1];genvar j;generatefor ( j = 0; j < ADDR_WIDTH-1; j=j+1) beginassign rd_bin[j] = rd_bin[j+1] ^ rd_gray_d2[j];endendgeneratealways @(posedge wrclk) begin wr_bin_d0 <= wr_bin;endalways @(posedge rdclk) begin rd_bin_d0 <= rd_bin;end/*---------------------------------------*\empty\*---------------------------------------*/ assign empty_logic = ((wr_bin_d0 - rd_addr) <= EMPTY_LIMIT)? 1'b1 : 1'b0;always @(posedge rdclk) begin empty <= empty_logic;end/*---------------------------------------*\full\*---------------------------------------*/assign full_logic = ((wr_addr - rd_bin_d0) >=  FULL_LIMIT)? 1'b1 : 1'b0;always @(posedge wrclk) begin full <= full_logic;endendmodule 	

 仿真代码:

`timescale 1ns / 1ps
module tb_async_fifo;parameter  	ADDR_WIDTH   = 4;parameter  	DATA_WIDTH   = 8;parameter 	EMPTY_LIMIT  = 1'b1;parameter  	FULL_LIMIT   = 4'd15;reg                   wr_rst_n;reg                   rd_rst_n;reg                   wr_en;reg                   rd_en;reg  [DATA_WIDTH-1:0] wr_data;wire [DATA_WIDTH-1:0] rd_data;wire                  empty;wire                  full;reg 					wr_clk;reg 					rd_clk;initial begin wr_clk = 0;rd_clk = 0;wr_rst_n = 0;rd_rst_n = 0;wr_en = 0;rd_en = 0;#20wr_rst_n = 1;rd_rst_n = 1;#20wr_en = 1;#30rd_en = 1;endalways #10 wr_clk = ~wr_clk;always #5  rd_clk = ~rd_clk;always @(posedge wr_clk ) begin if(!wr_rst_n)wr_data <= 0;else if(wr_data == 15)wr_data <= 0;else if(wr_en)wr_data <= wr_data + 1;endasync_fifo #(.ADDR_WIDTH(ADDR_WIDTH),.DATA_WIDTH(DATA_WIDTH),.EMPTY_LIMIT(EMPTY_LIMIT),.FULL_LIMIT(FULL_LIMIT)) inst_async_fifo (.wrclk    (wr_clk),.rdclk    (rd_clk),.wr_rst_n (wr_rst_n),.rd_rst_n (rd_rst_n),.wr_en    (wr_en),.rd_en    (rd_en),.wr_data  (wr_data),.rd_data  (rd_data),.empty    (empty),.full     (full));endmodule

 仿真波形:

五,总结

        在处理跨时钟域时,转换为格雷码处理。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/bicheng/9840.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

win10无法被远程桌面连接,Win10系统无法被远程桌面连接的原因有哪些

win10无法被远程桌面连接&#xff0c;Win10系统无法被远程桌面连接的原因有哪些&#xff1f; 先&#xff0c;我们需要明确Win10系统无法被远程桌面连接的可能原因。其中&#xff0c;最常见的原因包括&#xff1a;远程桌面功能未启用、网络连接问题、防火墙或安全软件设置不当、…

哪个牌子的电视盒子好用?小编分享最新电视盒子排名

最近电视盒子是大家热议的话题&#xff0c;就目前来看它的地位依然无可替代&#xff0c;但许多朋友硬不知道哪个牌子的电视盒子好用&#xff0c;面对众多品牌和产品究竟要如何选择才是最好的呢&#xff1f;本期小编要分享最新发布的电视盒子排名&#xff0c;看看哪些电视盒子最…

C++ 抽象与封装

一 抽象 抽象实例&#xff1a;时钟 数据抽象&#xff1a; 具有表面当前时间的时、分、秒 行为抽象&#xff1a; 具有设置时间和显示时间两个最基本的功能。 抽象实例&#xff1a;人 数据抽象&#xff1a;姓名、年龄、性别等。 行为抽象&#xff1a; 生物属性&#xff1a;吃…

【线性代数】英语版听课笔记

线性代数 - 北京航天航空大学&#xff08;英文版&#xff09;_哔哩哔哩_bilibili 39.concept of vector space in this lecture we will studyvector space&#xff0c; the concept of basis dimension and coordinates 向量空间的维数&#xff1a;向量空间的基底所含向量的…

TCP超时重传机制

一、TCP超时重传机制简介 TCP超时重传机制是指当发送端发送数据后&#xff0c;如果在一定时间内未收到接收端的确认应答&#xff0c;则会认为数据丢失或损坏&#xff0c;从而触发重传机制。发送端会重新发送数据&#xff0c;并等待确认应答。如果在多次重传后仍未收到确认应答&…

车载测试__公司面试题(整理)

案例1&#xff1a; 镁佳 外包岚图汽车 车载测试 区域经理视频面试 1.首先自我介绍一下 2.项目是怎么测的举例说明 3.你是怎么看待加班的 4.你是怎么看待驻场单位 5.是否可以接受外派去做一段时间的技术支持&#xff0c;比如去襄阳&#xff0c;最长一个月。 6.多快能到…

Codigger:优化Vim编辑器的关键是可视化

Vim是一款高度灵活的文本编辑器&#xff0c;以其高效的快捷键和命令行界面而闻名。然而&#xff0c;对于一些初学者来说&#xff0c;Vim的复杂性和强大的功能可能会让他们感到困惑。为了使Vim更易于使用&#xff0c;Codigger引入可视化操作的概念&#xff0c;将原本抽象的、难以…

智慧公厕,运用数据提升公共厕所管理水平!

随着城市人口的增加和生活水平的提高&#xff0c;公共厕所的管理变得越来越重要。传统的厕所管理方式已经无法满足人们对卫生、便利和舒适的需求。而智慧公厕作为新一代公厕管理方式&#xff0c;通过运用数据技术和大数据分析手段&#xff0c;彻底改变了公厕管理的模式&#xf…

C++对象的赋值

同类的对象之间可以互相赋值&#xff0c;即一个对象的值可以赋值给另一个对象。对象之间的赋值通过“”进行。默认就是把一个对象所有非static数据成员的值依次赋值给另一个对象。 对象赋值的一般形式为&#xff1a; 对象名1 对象名2; 注意:对象名1和对象名2必须是属于同一个…

flutter开发实战-GetX响应式状态管理使用

flutter开发实战-GetX响应式状态管理使用 GetX是一个简单的响应式状态管理解决方案。GetX是Flutter的一款超轻、功能强大的解决方案。它将高性能状态管理、智能依赖注入和路由管理快速而实用地结合在一起。这里简单使用一下GetX 一、引入GetX 在工程的pubspec.yaml中引入插件…

信息系统架构模型_1.单机应用模式和客户机/服务器模式

1.单机应用模式&#xff08;Standalone&#xff09; 单机应用系统是最简单的软件结构&#xff0c;是指运行在一台物理机器上的独立应用程序。这些软件系统&#xff0c;从今天的软件架构上来讲&#xff0c;是很简单&#xff0c;是标准的单机系统。当然至今&#xff0c;这种复杂的…

电路板维修【二】

【维修一个65W的氮化镓快充头&#xff0c;摔地上就没输出了&#xff0c;看下怎么回事】&#xff1a;https://www.bilibili.com/video/BV1vG411Q7MDvd_source3cc3c07b09206097d0d8b0aefdf07958 对于某些电器&#xff0c;维修之前需要先测量主滤波电容上面有没有存电&#xff0c…

【AMBA Bus ACE 总线 8 -- ICache maintenance】

请阅读【AMBA Bus ACE 总线与Cache 专栏 】 欢迎学习:【嵌入式开发学习必备专栏】 文章目录 ACE ICache maintenanceACE ICache maintenance 图 1-1 当一个OS run 多个cpu的时候,根据调度算法的不同,OS 可以根据调度算法的不同分别 run 在某个具体的CPU上,因此,它们会有…

分布式模式让业务更高效、更安全、更稳定

​&#x1f308; 个人主页&#xff1a;danci_ &#x1f525; 系列专栏&#xff1a;《设计模式》 &#x1f4aa;&#x1f3fb; 制定明确可量化的目标&#xff0c;坚持默默的做事。 &#x1f680; 转载自热榜文章&#x1f525;&#xff1a;探索设计模式的魅力&#xff1a;分布式模…

centos7下安装配置nginx

1、下载nginx安装包 wget http://nginx.org/download/nginx-1.8.0.tar.gz 2、安装nginx所需依赖 yum -y install pcre pcre-devel yum -y install zlib zlib-devel yum -y install openssl openssl-devel 3、进入到nginx安装包目录下&#xff0c;解压tar.gz包 cd /home/soft t…

pikachu靶场-全套学习

文章目录 配置pikachu靶场浏览器访问过程burpsuite配置代理hackbar安装使用kali安装中国蚁剑暴力破解cookie简化场景解释各部分含义如何工作 基于表单的暴力破解验证码绕过(On server)验证码绕过(on client)token防爆破? XSS&#xff08;Cross-Site Scripting跨站脚本攻击 &am…

使用AudioCraft(MusicGen)生成音乐

AudioCraft 是一个 PyTorch 库,用于音频生成的深度学习研究。AudioCraft 包含 AudioGen 和 MusicGen 两个最先进的人工智能生成模型的推理和训练代码,用于生成高质量的音频。 MusicGen 是一种简单可控的音乐生成模型,它使用Meta 20K 小时的授权音乐来进行训练,能够生成与文…

【栈】Leetcode 比较含退格的字符串

题目讲解 844. 比较含退格的字符串 算法讲解 使用栈模拟&#xff0c;但遇到#字符就让栈顶元素出栈&#xff0c;但是在写的过程中有两点需要注意&#xff1a;当#出现在第一个位置&#xff0c;需要特殊处理一下&#xff1b;当栈为空的时候&#xff0c;还出现#字符需要特殊处理…

【Web】2023浙江大学生省赛初赛 secObj 题解

目录 step 0 step 1 step 2 step 3 题目本身是不难&#xff0c;简单复健一下 step 0 pom依赖就是spring 反序列化入口在./admin/user/readObj 输入流做了黑名单的过滤&#xff0c;TemplatesImpl不能直接打 可以jackson打SignedObject二次反序列化绕过 具体原理看下面这…

全新时代的降临——比亚迪,助力未来出行

近日&#xff0c;世界舞台中央聚焦&#xff0c;比亚迪登上欧洲顶级赛事赞助席位&#xff0c;让全球见证中国新能源汽车传奇崛起&#xff01;作为新能源领袖品牌&#xff0c;比亚迪现已累计销售突破730万辆&#xff0c;全球每售出五辆新能源汽车&#xff0c;便有一辆来自比亚迪。…