异步FIFO_Verilog实现
异步FIFO_Verilog实现
概述: FIFO本质上还是RAM,是一种先进先出的数据缓存器(先存入的数据先取出)。它与普通存储器的区别:没有外部读写地址线,只能顺序写入数据,顺序的读出数据,其数据地址由内部读写指针自动加1,不像其他存储器可以由地址线决定读取或写入某个指定的地址,异步FIFO读写时钟不同,读写是相互独立的。
用途:
(1)跨时钟域多bit传输:读写可以由不同的时钟控制,使用异步FIFO可以在两个不同时钟系统之间快速方便的传输数据。
(2)数据匹配:对于不同宽度的数据接口可以使用FIFO,比如写入数据宽度为8bit,读取数据宽度为16bit,通过FIFO数据缓存器就可以达到数据匹配。
文章目录
- 1、FIFO的原理简介
- 2、FIFO空、满信号的检测
- 3、二进制至格雷码的转换
- 4、异步FIFO Verilog代码实现
1、FIFO的原理简介
FIFO简单的说就是数据先进先出的存储器,存储器端口图示如下:
端口分别为:
复位信号: rst
读写端信号:
写 | 读 |
---|---|
写时钟 wr_clk | 读时钟 rd_clk |
写使能信号 wr_en (in) | 读使能信号 rd_en(in) |
写数据 din (in) | 读数据 dout (out) |
写满信号 full (out) | 读空信号 empty (out) |
数据进出示意图:
先写入的数据先被读出,写入数据的顺序为D0 D1 D2 D3 则在输出端口读出的数据顺序也为 D0 D1 D2 D3 。
2、FIFO空、满信号的检测
FIFO的主要是通过空(empty)信号、满信号(full)来控制数据的读写的,如果FIFO为空还读取数据势必会出现数据的错误,FIFO已满再写数据就会导致FIFO溢出同样会导致数据出错。那FIFO的空、满信号是怎么产生的呢。首先我们要明白读写FIFO的时候我们不用操作读写地址,但是我们在写入数据或者读出数据时FIFO内部会通过写入或读出数据的操作在内部进行地址的自动增加。当FIFO满或者空时我们通过内部读写地址的特征就会判断出FIFO的状态。
通过深度为8的FIFO来寻找FIFO空满信号和内部读写地址的关系:
深度为8的FIFO:
深度为8的FIFO即最大可以存放8个数据,地址所需位数为3,在设计FIFO时我们将地址为扩展为4位根据我们扩展的位可以判断FIFO的空满状态。
8深度的FIFO经过以下4个操作:(下面方框代表地址)
1、 当FIFO为空时,读指针为0_000B,写指针为0_000B,此时FIFO里面没有数据,FIFO的状态为空。
2、当FIFO写入8个数据时,写指针指向1_000B,读指针还为0_000B,此时FIFO里有8个数据,FIFO状态应该为满。
3、当FIFO读出8个数据时,写指针还指向1_000B,读指针为1_000B,此时FIFO里面没有数据,FIFO的状态为空。
4、向FIFO再写入8个数据,写指针指向0_000B,读指针还为1_000B,此时FIFO里有8个数据,FIFO状态应该为满。
通过上述观察发现地址除了最高位余下的3位为地址0-7的循环。
总结如下表:
FIFO操作 | 写地址指向(B) | 读地址指向(B) | FIFO状态 |
---|---|---|---|
空FIFO | 0_000 | 0_000 | 空 |
写入8个数据 | 1_000 | 0_000 | 满 |
读出8个数据 | 1_000 | 1_000 | 空 |
写入8个数据 | 0_000 | 1_000 | 满 |
读出8个数据 | 0_000 | 0_000 | 空 |
引入格雷码(原因往下看):
FIFO操作 | 写地址指向(B) | Gray | 读地址指向(B) | Gray | FIFO状态 |
---|---|---|---|---|---|
空FIFO | 0_000 | 0000 | 0_000 | 0000 | 空 |
写入8个数据 | 1_000 | 1100 | 0_000 | 0000 | 满 |
读出8个数据 | 1_000 | 1100 | 1_000 | 1100 | 空 |
写入8个数据 | 0_000 | 0000 | 1_000 | 1100 | 满 |
读出8个数据 | 0_000 | 0000 | 0_000 | 0000 | 空 |
观察上图FIFO满时读写地址的高2位不同其余位均相同;FIFO空时读写地址完全相同。
所以:
判断异步FIFO空的条件:读写地址(格雷码)完全相同。
判断异步FIFO满的条件:读写地址(格雷码)的高2位不同,其余位均相同。
3、二进制至格雷码的转换
格雷码的特征是相邻的数之间只有1位二进制数不同。因为FIFO为异步设计,读写都有不同的时钟,读写是相互独立的,因为空满信号的判断是借助于读写地址进行的,所以涉及到了不同的时钟域,当二进制读地址从0111向1000变化时,地址所有位都要变化,如果写时钟恰好在读地址的变化时刻采样,写所得到的读地址值有可能是从0000到1111中的任何一个(即亚稳态的发生),可以采用格雷码形式。由于格雷码每次只变化一位,采用格雷码可以降低亚稳态的发生概率。
码表:
十进制数 | 二进制码 | 格雷码 |
---|---|---|
0 | 0000 | 0000 |
1 | 0001 | 0001 |
2 | 0010 | 0011 |
3 | 0011 | 0010 |
4 | 0100 | 0110 |
5 | 0101 | 0111 |
6 | 0110 | 0101 |
7 | 0111 | 0100 |
8 | 1000 | 1100 |
9 | 1001 | 1101 |
10 | 1010 | 1111 |
11 | 1011 | 1110 |
12 | 1100 | 1010 |
13 | 1101 | 1011 |
14 | 1110 | 1001 |
15 | 1111 | 1000 |
二进制至格雷码的转换:
二进制转格雷码代码:
module top(input sys_clk,input rst_n,input [4:0]bin_code,output [4:0]gray_code);assign gray_code = (bin_code>>1)^bin_code;endmodule
测试代码:
module test_bench;reg sys_clk;
reg rst_n;reg [4:0]bin_code;
wire [4:0]gray_code;top u1
(.sys_clk (sys_clk),.rst_n (rst_n),.bin_code (bin_code),.gray_code (gray_code)
);initial
beginrst_n = 0; sys_clk = 0;bin_code= 5'b0_0000;#20;rst_n = 1; bin_code= 5'b1_0110;endalways #10 sys_clk = ~sys_clk;endmodule
波形:
4、异步FIFO Verilog代码实现
代码注意: 读指针同步到写时钟域、写指针同步到读时钟域时要通过两级D触发器来进行同步处理。
方法:两级寄存器同步 + 格雷码
读指针同步到写时钟域、写指针同步到读时钟域时要通过两级D触发器来进行同步处理。
由于设计的时候读写指针用了至少两级寄存器同步,同步会消耗至少两个时钟周期,势必会使得判断空或满有所延迟,这会不会导致设计出错呢?
异步FIFO通过比较读写指针进行满空判断,但是读写指针属于不同的时钟域,所以在比较之前需要先将读写指针进行同步处理,
① 将写指针同步到读时钟域再和读指针比较进行FIFO空状态判断,因为在同步写指针时需要时间,而在这个同步的时间内有可能还会写入新的数据,因此同步后的写指针一定是小于或者等于当前实际的写指针,所以此时判断FIFO为空不一定是真空,这样更加保守,一共不会出现空读的情况,虽然会影响FIFO的性能,但是并不会出错。
②将读指针同步到写时钟域再和写指针比较进行FIFO满状态判断,同步后的读指针一定是小于或者等于当前的读指针,所以此时判断FIFO为满不一定是真满。
这样更保守,这样可以保证FIFO的特性:FIFO空之后不能继续读取,FIFO满之后不能继续写入。 总结来说异步逻辑转到同步逻辑不可避免需要额外的时钟开销,这会导致满空趋于保守,但是保守并不等于错误,这么写会稍微有性能损失,但是不会出错。
异步FIFO Verilog实现代码:
module top
#
(parameter FIFO_DEPTH = 8,parameter FIFO_WIDTH = 16)
(input rst,input wr_clk,input wr_en,input [FIFO_WIDTH-1:0] din,output reg full,input rd_clk,input rd_en,output [FIFO_WIDTH-1:0] dout,output reg empty
);//定义
reg [FIFO_WIDTH-1:0]mem[FIFO_DEPTH-1:0];reg [3:0] wr_add;
wire [3:0] wr_add_next;
wire [3:0] wr_add_gray_next;
reg [3:0] wp,wr1_rp,wr2_rp;reg [3:0] rd_add;
wire [3:0] rd_add_next;
wire [3:0] rd_add_gray_next;
reg [3:0] rp,rd1_wp,rd2_wp;wire full_r;
wire empty_r;//输入数据
always@(posedge wr_clk)
beginif(wr_en && !full)mem[wr_add] <= din;elsemem[wr_add] <= 16'h0000;
end//输出数据
assign dout = mem[rd_add];//写时钟域
assign wr_add_next = wr_add + (wr_en & ~full);
assign wr_add_gray_next = (wr_add_next>>1)^wr_add_next;
always@(posedge wr_clk or posedge rst)
beginif(rst){wr_add,wp} <= {4'b0000,4'b0000};else{wr_add,wp} <= {wr_add_next,wr_add_gray_next};end//读时钟域
assign rd_add_next = rd_add + (rd_en & ~empty);
assign rd_add_gray_next = (rd_add_next>>1)^rd_add_next;
always@(posedge rd_clk or posedge rst)
beginif(rst){rd_add,rp} <= {4'b0000,4'b0000};else{rd_add,rp} <= {rd_add_next,rd_add_gray_next};
end//读指针同步到写时钟域
always@(posedge wr_clk or posedge rst)
beginif(rst){wr2_rp,wr1_rp} <= {4'b0000,4'b0000};else{wr2_rp,wr1_rp} <= {wr1_rp,rp};end//写时钟同步到读时钟域
always@(posedge rd_clk or posedge rst)
beginif(rst){rd2_wp,rd1_wp} <= {4'b0000,4'b0000};else{rd2_wp,rd1_wp} <= {rd1_wp,wp};
end//空信号判断
assign empty_r = (rd2_wp == rd_add_gray_next)?1:0;
always@(posedge rd_clk or posedge rst)
beginif(rst)empty <= 1'b1;elseempty <= empty_r;
end//满信号判断
assign full_r = (~wr2_rp[3:2] == wr_add_gray_next[3:2])?1:0;
always@(posedge wr_clk or posedge rst)
beginif(rst)full <= 1'b0;elsefull <= full_r;
endendmodule
测试代码:
module test_bench;reg rst;reg wr_clk;
wire wr_en;
reg [15:0] din;
wire full;reg rd_clk;
wire rd_en;
wire [15:0] dout;
wire empty;wire[15:0] data_out;top u1
(.rst (rst),.wr_clk (wr_clk),.wr_en (wr_en),.din (din),.full (full),.rd_clk (rd_clk),.rd_en (rd_en ),.dout (dout ),.empty (empty )
);initial
beginrst = 1; wr_clk = 0;rd_clk = 0;#50;rst = 0; end
//FIFO Write
assign wr_en =(rst==1)?1'b0: !full;always@(posedge wr_clk or posedge rst)
beginif(rst)begindin <= 16'h0000;endelse if(wr_en == 1'b1)begindin <= (din == 16'd8)?16'h0000:din + 16'd1;endelsebegin din <= 16'h0000;end
end
//FIFO Read
assign rd_en = !empty;assign data_out = (rd_en==1'b1)?dout:16'h0000;//时钟产生
always #10 wr_clk = ~wr_clk;
always #15 rd_clk = ~rd_clk;endmodule
FIFO读写 仿真波形:
★★★如有错误,欢迎指导!!!
异步FIFO_Verilog实现相关推荐
- js异步提交form表单的解决方案
1.定义异步提交表单的方法 (通用方法) /*** 异步提交form表单* @param options {form:form表单元素,success:执行成功后处理函数}* <span sty ...
- Redis 笔记(12)— 单线程架构(非阻塞 IO、多路复用)和多个异步线程
Redis 使用了单线程架构.非阻塞 I/O .多路复用模型来实现高性能的内存数据库服务.Redis 是单线程的.那么为什么说是单线程呢? Redis 在 Reactor 模型内开发了事件处理器,这个 ...
- STL库(C++11)提供的异步执行方法的方式
在进行并发编程的时候难免会遇到异步执行时候,现代C++标准库提供了几种异步执行的方式,本文收集整理了一下,以备将来翻阅. Thread方式 Thread 是STL提供的一种快捷创建线程的方式,极大方便 ...
- 协程和任务 异步IO 重点
20210815 https://mp.weixin.qq.com/s/XeHaWhKztnCOIXb_2GSitQ https://mp.weixin.qq.com/s/lnox3pbpzJ2kWl ...
- 同步与异步,阻塞与非阻塞的区别
1.概念剖析 相信很多从事linux后台开发工作的都接触过同步&异步.阻塞&非阻塞这样的概念,也相信都曾经产生过误解,比如认为同步就是阻塞.异步就是非阻塞,下面我们先剖析下这几个概念分 ...
- 2021年大数据Flink(四十六):扩展阅读 异步IO
目录 扩展阅读 异步IO 介绍 异步IO操作的需求 使用Aysnc I/O的前提条件 Async I/O API 案例演示 扩展阅读 原理深入 AsyncDataStream 消息的顺序性 扩展阅读 ...
- DCN-2655 同异步端口PPP (chap)认证
DCN-2655 同异步端口PPP --chap 认证 路由器命名/路由器本地用户名和密码配置: 配置路由器AAA认证: 进入同异步端口进行配置: 端口gi0/3配置: 备注: 路由器命名/路由器本地 ...
- DCN-2655同异步端口
时钟率为:64000 当开启时钟率和封装协议时必须将端口shutdown后再no shutdown(路由器端口重启) 路由器正常情况下同异步端口的协议为shutdown而且两端同步口为DTE, 只有在 ...
- Java 异步与同步的区别
同步: 所有操作完成之后,才会通知用户操作完成了. 异步:不用等所有操作完成之后,就会通知用户操作完成了,然后后台会继续操作直到完成结束. 为了方便理解 举例个常见的 android 网络请求使用的异 ...
最新文章
- c++ 构造函数析构函数 数据安全_C++知识点 16:构造函数和析构函数的语法
- python中enumerate在for循环中用法_python中enumerate的用法实例解析
- Mac Vim 如何设置高亮
- JEE6 CDI 扩展实现 MVC (四) 实现多模板引擎支持,并提供扩展接口
- git指定版本openwrt源码_[OpenWrt Wiki] LEDE源代码
- Spring3+ibatis (SQL Server)+pager-taglib.tld查询分页的实现
- java足球经理2010下载_apk是什么文件?apk文件怎么打开?
- OpenCV2.4.5在13-04的配置过程
- 从一个数据流中取出中位数
- 开发环境配置--Ubuntu+Qt4+OpenCV(二)
- cocos2dx 3.10 网狐土豪金版PC+手机端棋牌平台搭建
- priority_queue 用法总结
- 九章算术卷第三 衰分
- matplotlib tricks(一)—— 多类别数据的 scatter(cmap)
- auxiliary variable(辅助变量)的引入
- Vscode中HTML与CSS代码的快速写法
- 系统类配置(六) ubuntu16.04命令行安装Nvidia显卡驱动(操作指令详细注释版)
- Nginx+Tomcat关于Session的管理
- 阿里巴巴Java开发手册(原文地址)
- 六轴机器人直角坐标系建立_六自由度机械手的坐标建立及运动学分析
热门文章
- 泽天夬:当断则断;天风姤:福祸相依
- QQMusic与Android studio 冲突导致Adb not reposponding
- 图片透视校正 java js_iOS 使用OpenCV 实现图片的透视校正
- python打印报错信息_python打印当前文件错误行的简单示例
- 冰火两重天版格斗场(热血格斗场+冷血格斗场)
- 【计算机组成原理】实验5:运算器实验
- mybatis plus argument type mismatch
- 设计模式的艺术 工厂方法模式
- Activity转场动画
- Nginx windows 版本 修改句柄数 解决 maximum number of descriptors supported by select() is 1024 while waiting