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.rhkb.cn/news/323846.html

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

相关文章

案例研究|硬之城借助DataEase以数据驱动供应链精细化管理

深圳硬之城信息技术有限公司&#xff08;以下简称为“硬之城”&#xff09;成立于2015年&#xff0c;专注电子元件供应链领域&#xff0c;定位于电子产业供应链与智造平台。硬之城通过名为“Allchips”的集成式服务平台&#xff0c;为客户提供一站式的电子元件采购和供应链管理…

ROS八股总结

1. 概述 ROS系统是为了提高机器人研发中的软件复用率&#xff0c;每个模块可以被单独设计与编译&#xff0c;运行时以松耦合的方式结合在一起。提供硬件抽象、底层驱动、消息传递、程序管理、应用原型等功能和机制。且集成了大量工具、库、协议 2.特点 点对点 节点单元分布式…

动态规划——路径问题:LCR 166.珠宝的最高价值

文章目录 题目描述算法原理1.状态表示&#xff08;题目经验&#xff09;2.状态转移方程3.初始化4.填表顺序5.返回值 代码实现CJava 题目描述 题目链接&#xff1a;LCR 166.珠宝的最高价值 算法原理 1.状态表示&#xff08;题目经验&#xff09; 对于这种路径类的问题&…

【全开源】Java外卖霸王餐免费吃外卖小程序+APP+公众号+H5多端霸王餐源码

一、特色功能 霸王餐活动管理&#xff1a;允许商家发布和管理霸王餐活动&#xff0c;包括设置活动时间、具体优惠、活动规则等。用户参与与浏览&#xff1a;用户可以在小程序中浏览霸王餐活动列表&#xff0c;查看活动的详情信息&#xff0c;如商品或服务的免费赠送、活动规则…

zookeeper启动 FAILED TO START

注意&#xff1a;启动zookeeper时&#xff0c;需要使用zkServer.sh start命令将所有主机启动后&#xff0c;再查看状态 如果&#xff0c;启动一台主机&#xff0c;查看当前主机状态&#xff0c;则会报错 如果出错&#xff0c;进入到$ZOOKEEPER_HOME/logs&#xff0c;查看日志 …

【C++】深入剖析C++11 initializer_list 新的类功能 可变模板参数

目录 一、std::initializer_list 1、std::initializer_list是什么类型 2、std::initializer_list 的应用场景 ①给自定义容器赋值 ② 传递同类型的数据集合 二、新的类功能 1、默认成员函数 2、关键字default 3、关键字delete 三、可变参数模板 一、std::initialize…

信创基础软件之中间件

信创基础软件之中间件 中间件概述 中间件是一种应用于分布式系统的基础软件&#xff0c;位于应用与操作系统、数据库之间&#xff0c;主要用于解决分布式环境下数据传输、数据访问、应用调度、系统构建和系统集成、流程管理等问题&#xff0c;是分布式环境下支撑应用开发、运…

读取打包到JAR中的文件:常见问题与解决方案(文件在但是报错not find)

读取打包到JAR中的文件&#xff1a;常见问题与解决方案 喝淡酒的时候&#xff0c;宜读李清照&#xff1b;喝甜酒时&#xff0c;宜读柳永&#xff1b;喝烈酒则大歌东坡词。其他如辛弃疾&#xff0c;应饮高梁小口&#xff1b;读放翁&#xff0c;应大口喝大曲&#xff1b;读李后主…

android init进程启动流程

一,Android系统完整的启动流程 二,android 系统架构图 三,init进程的启动流程 四,init进程启动服务的顺序 五,android系统启动架构图 六,Android系统运行时架构图 bool Service::Start() {// Starting a service removes it from the disabled or reset state and// imme…

【redis】redis持久化分析

目录 持久化Redis持久化redis持久化的方式持久化策略的设置1. RDB&#xff08;快照&#xff09;fork(多进程)RDB配置触发RDB备份自动备份手动执行命令备份&#xff08;save | bgsave&#xff09;flushall命令主从同步触发动态停止RDB RDB 文件恢复验证 RDB 文件是否被加载 RDB …

c++:(map和set的底层简单版本,红黑树和AVL树的基础) 二叉搜索树(BST)底层和模拟实现

文章目录 二叉搜索树的概念二叉搜索树的操作二叉搜索树的查找find 二叉搜索树的模拟实现构造节点insertfinderase(细节巨多,面试可能会考)a.叶子节点b.有一个孩子左孩子右孩子 c.有两个孩子注意: erase代码 中序遍历 二叉搜索树的应用k模型k模型模拟实现的总代码 k-value模型k-…

代码随想录算法训练营第六十天| LeetCode647. 回文子串 、516.最长回文子序列

一、LeetCode647. 回文子串 题目链接/文章讲解/视频讲解&#xff1a;https://programmercarl.com/0647.%E5%9B%9E%E6%96%87%E5%AD%90%E4%B8%B2.html 状态&#xff1a;已解决 1.思路 这道题我只想出来了暴力解法&#xff0c;动规解法并没有想出来。根据视频讲解才把它想出来。…

聚观早报 | 苹果新款iPad Pro发布;国产特斯拉4月交付量

聚观早报每日整理最值得关注的行业重点事件&#xff0c;帮助大家及时了解最新行业动态&#xff0c;每日读报&#xff0c;就读聚观365资讯简报。 整理丨Cutie 5月9日消息 苹果新款iPad Pro发布 国产特斯拉4月交付量 iOS 18新功能爆料 真我GT Neo6续航细节 三星Galaxy Z F…

智慧公厕,小民生里的“大智慧”!

公共厕所是城市社会生活的基础设施&#xff0c;而智慧公厕则以其独特的管理模式为城市居民提供更优质的服务。通过智能化的监测和控制系统&#xff0c;智慧公厕实现了厕位智能引导、环境监测、资源消耗监测、安全防范管理、卫生消杀设备、多媒体信息交互、自动化控制、自动化清…

uniapp——弹出键盘遮挡住输入框 textarea,处理方法

案例 在写输入框的时候会遇见 键盘遮挡住部分textarea框的一部分&#xff0c;使用cursor-spacing处理即可 修改后&#xff1a; 其他问题&#xff1a; 调起键盘输入时&#xff0c;不希望上方的内容被顶上去 代码 <view class"commentBox" :style"botto…

什么是HTTP?

什么是HTTP&#xff1f; HTTP基本概念HTTP 是什么&#xff1f;HTTP 常见的状态码有哪些&#xff1f;HTTP 常见字段有哪些&#xff1f; HTTP特性HTTP/1.1 的优点有哪些&#xff1f;HTTP/1.1 的缺点有哪些&#xff1f; HTTP基本概念 HTTP 是什么&#xff1f; HTTP 是超文本传输…

基于Springboot的果蔬作物疾病防治系统(有报告)。Javaee项目,springboot项目。

演示视频&#xff1a; 基于Springboot的果蔬作物疾病防治系统&#xff08;有报告&#xff09;。Javaee项目&#xff0c;springboot项目。 项目介绍&#xff1a; 采用M&#xff08;model&#xff09;V&#xff08;view&#xff09;C&#xff08;controller&#xff09;三层体系…

4.【Orangepi Zero2】Linux定时器(signal、setitimer),软件PWM驱动舵机(SG90)

Linux定时器&#xff08;signal、setitimer&#xff09;&#xff0c;软件PWM驱动舵机&#xff08;SG90&#xff09; signalsetitimer示例 软件PWM驱动舵机&#xff08;SG90&#xff09; signal 详情请看Linux 3.进程间通信&#xff08;shmget shmat shmdt shmctl 共享内存、si…

内容安全(DPI和DFI解析)

内容安全前言&#xff1a; 防火墙的本质其实就是包过滤&#xff0c;我们通常所说的安全设备&#xff08;如&#xff1a;IPS、IDS、AV、WAF&#xff09;的检测重心是应用层。下一代防火墙基于传统防火墙的拓展能力&#xff0c;就是可以将以上的安全设备模块集成在一起&#xff0…

HackMyVM-Animetronic

目录 信息收集 arp nmap nikto whatweb WEB web信息收集 feroxbuster steghide exiftool hydra ssh连接 提权 系统信息收集 socat提权 信息收集 arp ┌──(root㉿0x00)-[~/HackMyVM] └─# arp-scan -l Interface: eth0, type: EN10MB, MAC: 08:00:27:9d:6d:7…