文章目录

    • 前言
    • 异步FIFO的概念
    • 异步FIFO为什么可以解决CDC问题?
    • 异步FIFO的RTL实现
  • 参考资料

前言

异步FIFO是处理多比特信号跨时钟域的最常用方法,简单来说,异步FIFO是双口RAM的一个封装而已,其存储容器本质上还是一个RAM,只不过对其添加了某些控制,使其能够实现先进先出的功能,由于这个功能十分的实用,因此得以广泛应用。
真双口RAM可以实现在一端存储,另一端读取的功能,两端的时钟可以不同,将数据存入一个容器,再取出来,这个过程在双口RAM的两端完全不存在亚稳态的问题。由于异步FIFO的实现中也存在数据的存取问题,和双口RAM类似,再加上空满信号的控制,存在跨时钟域的问题,因此只要处理好,空满信号的判断中的跨时钟域问题,就可以使用FIFO解决多比特信号的跨时钟域问题。
下面从多个方面来了解一下,异步FIFO的内容,最后会给出异步FIFO的一种普遍的实现方式及其仿真,让我们一起进入今天的内容吧。

注:本文首发自易百纳技术社区,原文地址:https://www.ebaina.com/articles/140000005375

另外,请近期路过的朋友投个csdn年度博客之星的票,博主需要你的鼓励。https://bss.csdn.net/m/topic/blog_star2020/detail?username=reborn_lee

异步FIFO的概念

异步FIFO的实现方式有很多种,这里说的实现方式可以理解为实现异步FIFO的技术方式,也可以指使用异步FIFO的选择方式,因为都有很多种!拿实现异步FIFO的技术方式来说,其重难点在于其空满信号的判断,它涉及到内部跨时钟域细节的实现、格雷码的转换以及空满信号的比较等。这里面的实现技术就各有不同了,但殊途同归,只能能巧妙地判断空满就可以凑合着用。下面给出一张异步FIFO的实现架构框图仅供参考:

另外,因为异步FIFO的应用已经太成熟了,手动设计起来破费一番功夫,所以FPGA各大厂家大都提供了专业的IP核供使用,功能齐全且性能良好,鲁棒性强,不用自己设计,在工程应用中,也基本都用IP核,除非想不开或者其他原因?

不妨打开Xilinx的FIFO定制页面:FIFO Generator来看:

可供定制的页面确实及其丰富,各种类型的空满信号、实现的资源选择应有尽有,手动实现这些可是要费大功夫的,没有资本的推动,恐怕很难有人去做这件事吧。

异步FIFO为什么可以解决CDC问题?

异步FIFO的接口如下:

再看其资源使用情况:

以上这两张图片显示的是Xilinx的异步FIFO IP的一种定制情况,可以看到FIFO就是在RAM的基础上的一个产物,通过处理RAM的读写端口来做成先进先出的存储器,实现FIFO的功能。

对于FIFO的读写有独立的时钟,说明读写可以是不同的时钟,因此可以实现不同时钟域数据的传输。

这好像只是说明了一个结论,异步FIFO可以作为处理跨时钟域处理的方法或载体,但是异步FIFO为什么可以用来处理跨时钟域传输问题呢?
这和异步FIFO的具体实现有关!下面一起来看异步FIFO的实现!

异步FIFO的RTL实现

通过RTL实现异步FIFO之前,需要明白异步FIFO的几个重要的参数,也是我们设计的重点:

  • FIFO的深度:通俗地说,就是异步FIFO可以存多少个数据的意思!
  • FIFO的宽度:上面说异步FIFO的深度是表示能存放多少数据的概念,那宽度便是每个数据有多少位,也就是我们通常所说的数据有多宽!
  • FIFO空:表示FIFO里面数据被读完了;
  • FIFO满:表示FIFO里面填满了数据;
  • FIFO写指针:总是指向下一个将要被写入的单元,复位时,指向第1个单元(编号为0);
  • FIFO读指针:总是指向当前要被读出的数据,复位时,指向第1个单元(编号为0);
  • FIFO读时钟:表示读取数据使用的时钟,一般设计时钟的上升沿为有效沿,有效沿读取数据;
  • FIFO写时钟:表示写入数据时使用的时钟,一般上升沿为有效沿,当然也可以设计下降沿为有效沿。

明白了如上的FIFO参数的概念,我们也该具体聊聊FIFO的关键细节了,例如最重要的空满判断条件:
上面也说了,FIFO空的意思是FIFO中没有了数据,可以思考,什么情况下FIFO中数据空了,其实FIFO类似一个容器,就水桶吧,倒进去的水,又全部倒了出来,水桶就是空的。FIFO也是如此,写进去的数据,又全部读了出来,表示FIFO空了。如下图:

写进FIFO4个数据,又读出了4个数据,读写指针指向了同一个地方,也就是读写指针相等了,FIFO就空了。这是最简单的情况,我们其往下看:
还是用上面的图片,如果继续往FIFO内写数据,写指针不断增加,写到尽头了,指针就会从0继续增加,直到写指针回到了最初的位置,再次与读指针处于同一个位置,这时候读写指针再次相等,但是你能说FIFO还是空的吗?如下图:

恰恰相反,此时FIFO是满的!
这就带来了我们今天异步FIFO设计的第一个问题,就是读写指针与空满条件的判断之间的关系问题?

也不是没有解决办法,最直观的便是多增加一位表示读写指针,例如FIFO的深度为8,我们原来用3位表示读写指针即可,但是我们增加到4位,这样只要读写指针的最高位不相等,即便二者剩下的其他位相等也不能表示指针相等,也就是不能说FIFO为空,相反FIFO为满。

有人可能会有这样的疑问?
还是上面的一幅图,如果FIFO满了之后,继续写数据,再来一圈,FIFO的读写指针不就又完全相等了吗?
其实,这很好办,我们在设计FIFO的时候,判断FIFO为空的话就规定不能再读了,如果FIFO为满的话,就不能再继续写了,这就解决了这个疑问!

接着还需要讨论一个问题,如FIFO实现的框图:

如上图,判断FIFO的空满,需要读写指针跨时钟域传输,之后对比读写指针的大小,这就存在一个问题,那就是我们所说的跨时钟域问题,在这里具体来说便是读写指针的跨时钟域问题?
怎么处理呢?由于读写指针有多位,对于多比特数据的CDC问题,我们一般不会直接两级同步过去,两级同步适用于单比特变化的数据!
但是如上图的实现方式,好像还就是两级同步,这是什么原因呢?

如果非要使用两级寄存器同步的方式,我们就要控制每次只有1比特数据发生变化,如何实现呢?
很容易,使用格雷码对读写指针计数值进行编码即可。

如下图:

可见,格雷码的每一次叠加只会发生1比特数据的变化。
在异步FIFO的实现中,读写指针的变化,我们仍然使用二进制加,之后将变化后的二进制通过组合逻辑转换为格雷码即可。
二进制转换为格雷码以及格雷码转换为二进制的方法,我们可以参考我以前的博文:二进制与格雷码之间的转换的Verilog实现(更多一点的讨论),这篇文章还顺便提到了generate for以及for语句的区别,推荐阅读。

这里为了后面的RTL编码铺垫,给出二进制编码与格雷码之间的转换示意图,避免翻阅的麻烦:

  • 二进制转换为格雷码的方法:

伪代码描述为:

assign gray_value = binary_value ^ (binary_value>>1);
  • 格雷码转换为二进制码的方法:

如上图,可以看出,可以从高位入手,格雷码的最高位即是二进制码的最高位,之后的二进制码的实现便是它本身的高1位与该位的格雷码进行异或,如下伪代码描述:

 assign bin[N-1] = gray[N-1];genvar i;generatefor(i = N-2; i >= 0; i = i - 1) begin: gray_2_binassign bin[i] = bin[i + 1] ^ gray[i];endendgenerate

OK,解决了二进制码向格雷码的转换问题,我们继续分析:
二进制码转换成了格雷码并跨时钟域到了另一个时钟域,那接下来就是读写指针的格雷码形式的对比了,二进制的对比很简单,就是如果二者所有的位全部相等, 则表示空;如果二者最高位不同,但其他位相同,则表示满。
上述判断方法为二进制判断空满的方法,那么格雷码呢?
我们分情况讨论:

  • 对于空的判断:这就很简单,二进制完全相同,难道格雷码不完全相等吗?对的,同样完全相同表示空。
  • 对于满的判断:从上面的格雷码与二进制码之间的转换可以确定一点,就是二者的最高位一致,因此当二进制码的最高位不等的时候,格雷码也一定不等,这一点毋庸置疑;同样由二者转换的原理图看出,由于二进制码时,二者的最高位不同,其余为相同,如下图:

二进制转换为格雷码的时候,次高位的格雷码和最高位相关,因此,二者的次高位一定不同,由于二进制码的次高位相同,因此次次高位相同,以此类推,剩余的更低位在二进制编码以及格雷码中完全相同。
这就得出了结论,在格雷码编码中确定满的条件是最高位,次高位都不同,但是其余位相同。

如下图:

0和8,1和9等,由于其二进制码的最高位不同,其余位相同,但是格雷码确是最高位和次高位都不同, 其余位相同。
这个问题到此就当做解决了。

难题都已经解决,下面就是异步FIFO的RTL实现工作了。

如果你读懂了上述的分析过程,那么本设计就十分清晰明了了,下面给出RTL设计:

module asyn_fifo#(parameter DATA_WIDTH = 8,parameter DATA_DEPTH = 32)(//write ports    input wr_clk,input wr_rst,input wr_en,input [DATA_WIDTH - 1 : 0] wr_data,output reg full,//read portsinput rd_clk,input rd_rst,input rd_en,output reg [DATA_WIDTH - 1 : 0] rd_data,output reg empty);// define FIFO buffer reg [DATA_WIDTH - 1 : 0] fifo_buffer[0 : DATA_DEPTH - 1];//define the write and read pointer and //pay attention to the size of pointer which should be greater one to normalreg [$clog2(DATA_DEPTH) : 0] wr_pointer = 0, rd_pointer = 0; //write data to fifo buffer and wr_pointer controlalways@(posedge wr_clk) beginif(wr_rst) beginwr_pointer <= 0;endelse if(wr_en) beginwr_pointer <= wr_pointer + 1;fifo_buffer[wr_pointer] <= wr_data;endend//read data from fifo buffer and rd_pointer controlalways@(posedge rd_clk) beginif(rd_rst) beginrd_pointer <= 0;endelse if(rd_en) beginrd_pointer <= rd_pointer + 1;rd_data <= fifo_buffer[rd_pointer];endend//wr_pointer and rd_pointer translate into gray codewire [$clog2(DATA_DEPTH) : 0] wr_ptr_g, rd_ptr_g; assign wr_ptr_g = wr_pointer ^ (wr_pointer >>> 1);assign rd_ptr_g = rd_pointer ^ (rd_pointer >>> 1);//wr_pointer after gray coding synchronize into read clock regionreg [$clog2(DATA_DEPTH) : 0] wr_ptr_gr, wr_ptr_grr, rd_ptr_gr, rd_ptr_grr; always@(rd_clk) beginif(rd_rst) beginwr_ptr_gr <= 0;wr_ptr_grr <= 0;endelse beginwr_ptr_gr <= wr_ptr_g;wr_ptr_grr <= wr_ptr_gr;endend//rd_pointer after gray coding synchronize into  write clock regionalways@(wr_clk) beginif(wr_rst) beginrd_ptr_gr <= 0;rd_ptr_grr <= 0;endelse beginrd_ptr_gr <= rd_ptr_g;rd_ptr_grr <= rd_ptr_gr;endend// judge full or emptyalways@(posedge rd_clk) beginif(rd_rst) empty <= 0;else if(wr_ptr_grr == rd_ptr_g) beginempty <= 1;endelse empty <= 0;endalways@(posedge wr_clk) beginif(wr_rst) full <= 0;else if( (rd_ptr_grr[$clog2(DATA_DEPTH) - 2 : 0] == wr_ptr_g[$clog2(DATA_DEPTH) - 2 : 0])&& ( rd_ptr_grr[$clog2(DATA_DEPTH)] != wr_ptr_g[$clog2(DATA_DEPTH)] ) && ( rd_ptr_grr[$clog2(DATA_DEPTH) - 1] != wr_ptr_g[$clog2(DATA_DEPTH) - 1] ) ) beginfull <= 1;endelse full <= 0;end//对写满的限制
always@(posedge wr_clk or posedge wr_rst) beginif(wr_rst) beginwr_pointer <= 0;endelse if(wr_en) beginif(!((rd_ptr_grr[$clog2(DATA_DEPTH) - 2 : 0] == wr_ptr_g[$clog2(DATA_DEPTH) - 2 : 0])&& ( rd_ptr_grr[$clog2(DATA_DEPTH)] != wr_ptr_g[$clog2(DATA_DEPTH)] ) && ( rd_ptr_grr[$clog2(DATA_DEPTH) - 1] != wr_ptr_g[$clog2(DATA_DEPTH) - 1] ))) beginwr_pointer <= wr_pointer + 1;endelse beginwr_pointer <= wr_pointer;endendelse  beginwr_pointer <= wr_pointer;endend//对读空的限制
always@(posedge rd_clk or posedge rd_rst) beginif(rd_rst) beginrd_pointer <= 0;endelse if(rd_en) beginif(wr_ptr_grr != rd_ptr_g) beginrd_pointer <= rd_pointer + 1;endelse beginrd_pointer <= rd_pointer;endendelse  beginrd_pointer <= rd_pointer;endendendmodule

这个程序唯一需要注意的是,最后对空满以后,读写指针的处理,例如:

//对读空的限制
always@(posedge rd_clk or posedge rd_rst) beginif(rd_rst) beginrd_pointer <= 0;endelse if(rd_en) beginif(wr_ptr_grr != rd_ptr_g) beginrd_pointer <= rd_pointer + 1;endelse beginrd_pointer <= rd_pointer;endendelse  beginrd_pointer <= rd_pointer;endend

读空了以后,读指针就不会再增加了。

//对写满的限制
always@(posedge wr_clk or posedge wr_rst) beginif(wr_rst) beginwr_pointer <= 0;endelse if(wr_en) beginif(!((rd_ptr_grr[$clog2(DATA_DEPTH) - 2 : 0] == wr_ptr_g[$clog2(DATA_DEPTH) - 2 : 0])&& ( rd_ptr_grr[$clog2(DATA_DEPTH)] != wr_ptr_g[$clog2(DATA_DEPTH)] ) && ( rd_ptr_grr[$clog2(DATA_DEPTH) - 1] != wr_ptr_g[$clog2(DATA_DEPTH) - 1] ))) beginwr_pointer <= wr_pointer + 1;endelse beginwr_pointer <= wr_pointer;endendelse  beginwr_pointer <= wr_pointer;endend

写满了以后,写指针就不会增加了。

其实这个不加上也可以,我们在使用的时候,通过判断空满条件来决定要不要让读写使能继续有效,同样可以达到控制数据读写的不溢出的问题。

下面简单搭建一个仿真平台,仿真出空满信号的发生:

module asyn_fifo_tb();parameter DATA_WIDTH = 8;parameter DATA_DEPTH = 16;//write ports    reg wr_clk;reg wr_rst;reg wr_en;reg [DATA_WIDTH - 1 : 0] wr_data;wire full;//read portsreg rd_clk;reg rd_rst;reg rd_en;wire [DATA_WIDTH - 1 : 0] rd_data;wire empty;initial beginwr_clk = 0;forever begin#2 wr_clk = ~wr_clk;endendinitial beginrd_clk = 0;forever begin#5 rd_clk = ~rd_clk;endendinitial beginwr_rst = 1'b1;rd_rst = 1'b1;wr_en = 1'b0;rd_en = 1'b0;#10wr_rst = 0;rd_rst = 0;#10wr_en = #(0.2) 1'b1;wr_data = #(0.2) $random; repeat(5) begin@(posedge wr_clk);wr_data = #(0.2) $random;  end@(posedge wr_clk); wr_en = #(0.2) 1'b0;wr_data = #(0.2) $random;#10rd_en = #(0.2) 1'b1;repeat(5) begin@(posedge rd_clk);  end@(posedge rd_clk);rd_en = #(0.2) 1'b0;#10wr_en = #(0.2) 1'b1;wr_data = #(0.2) $random; repeat(16) begin@(posedge wr_clk);  wr_data = #(0.2) $random;end@(posedge wr_clk); wr_en = #(0.2) 1'b0;wr_data = #(0.2) $random;                endasyn_fifo#(.DATA_WIDTH ( DATA_WIDTH ),.DATA_DEPTH ( DATA_DEPTH )
)u_asyn_fifo(.wr_clk     ( wr_clk     ),.wr_rst     ( wr_rst     ),.wr_en      ( wr_en      ),.wr_data    ( wr_data    ),.full       ( full       ),.rd_clk     ( rd_clk     ),.rd_rst     ( rd_rst     ),.rd_en      ( rd_en      ),.rd_data    ( rd_data    ),.empty      ( empty      )
);endmodule

下面是仿真结果分析:

下面给出空满时刻的注释:

最后想说的是,手动设计的异步FIFO只是为了让你了解异步FIFO的原理,但对于FPGA工程师来说,我们掌握了不代表平时一定要用自己写的异步FIFO,一般或者往往我们会使用fpga厂家自带的FIFO IP核,别人写好的模块,经过了严格的验证,让设计更加的稳定,且功能很多,如果偏要使用自己设计的FIFO,那么,后果自负吧。

参考资料

  • Gray Code Basics

  • 二进制与格雷码之间的转换的Verilog实现(更多一点的讨论)

  • Verilog code for FIFO memory

  • FPGA基础知识极简教程(4)从FIFO设计讲起之异步FIFO篇

  • 异步FIFO的FPGA实现

  • Getting the basic FIFO right

  • 調試成功的簡單異步FIFO–verilog實現+testbench

FPGA逻辑设计回顾(6)多比特信号的CDC处理方式之异步FIFO相关推荐

  1. FPGA逻辑设计回顾(8)单比特信号的CDC处理方式之Toggle同步器

    文章目录 前言 脉冲反馈展宽同步器技术补充说明 RTL代码 行为仿真 低电平脉冲的展宽处理 切换同步器的原理与实现 RTL实现 前言 本文首发自:FPGA逻辑设计回顾(8)单比特信号的CDC处理方式之 ...

  2. FPGA逻辑设计回顾(3)多比特信号上升沿检测的设计方式与陷阱?

    前言 注:本文首发自FPGA逻辑设计回顾(3)多比特信号上升沿检测的设计方式与陷阱? 在总结本文最后的多比特上升沿检测之前,我们先把备用知识讲清楚,摊开来,以免造成模糊不清的默许! 逻辑运算符与位元运 ...

  3. FPGA逻辑设计回顾(1)新手易犯的逻辑综合错误之always块

    前言 注:本文首发自FPGA逻辑设计回顾(1)新手易犯的逻辑综合错误之always块 本文中用到了如下的小标题: "心中有路"与综合推断 "心中无路"与无从推断 ...

  4. FPGA逻辑设计回顾(12)RAM以及ROM的RTL设计及其验证

    前言 本文首发:FPGA逻辑设计回顾(12)RAM以及ROM的RTL设计及其验证 RAM以及ROM在FPGA中的实现大体有两种方式,一种是使用IP核定制,一种是RTL设计. 也许有人会反驳,那原语呢? ...

  5. FPGA逻辑设计回顾(9)DDR的前世今生以及演变过程中的技术差异

    文章目录 前言 DDR的前世SDRAM DDR的今生以及演变版本:DDR/DDR2/DDR3 DDR/DDR2/DDR3/DDR4之间简单对比 速度对比 电压对比 延迟对比 预取差异 电阻端接对比 物 ...

  6. FPGA逻辑设计回顾(10)DDR/DDR2/DDR3中的时序参数的含义

    前言 本文首发自:FPGA逻辑设计回顾(10)DDR/DDR2/DDR3中的时序参数的含义 上篇文章:FPGA逻辑设计回顾(9)DDR的前世今生以及演变过程中的技术差异有提到,制造商会以一系列由破折号 ...

  7. FPGA逻辑设计回顾(13)RAM以及ROM的IP核定制以及关键参数

    文章目录 前言 RAM IP的定制 Xilinx的IP定制位置 Block RAM的定制过程 第一页 第二页 第三页 第四页 第五页 Block RAM的延迟讨论 ROM IP核的定制 总结 前言 本 ...

  8. FPGA逻辑设计回顾(11)FPGA以及PC中的RAM与ROM

    文章目录 前言 RAM以及ROM在计算机中的应用 什么是存储器? 什么是硬盘驱动器? 其他类型的存储器 什么是RAM? RAM的类型 SRAM DRAM 什么是ROM? ROM的类型 掩膜ROM PR ...

  9. AD7760数据采集系统设计 [FPGA逻辑设计]

    1 AD7760简介 AD7760是一款高性能.24位Σ-Δ型模数转换器(ADC),融合了宽输入带宽.高速特性与Σ-Δ转换技术的优势,2.5 MSPS时信噪比可达100 dB,因此非常适合高速数据采集 ...

最新文章

  1. Tensorflow— name/variable_scope
  2. Palindrome Partitioning
  3. JZOJ 5184. 【NOIP2017提高组模拟6.29】Gift
  4. Android—APK、JVM-Dalvik-ART
  5. Camel 2.11 –没有Spring的Camel Web应用程序
  6. 手把手教你用java读写excel表格文件(POI,EasyExcel)
  7. JS中数组和字符串具有的方法,以及substring,substr和slice的用法与区别
  8. python处理json数据 乱码报错_python json.loads json.dumps(ensure_ascii = False) 汉字乱码问题解决...
  9. OPC DA通讯 KEP6.4 DCOM 配置脚本
  10. python下载教程-Python 如何入门?附Python教程下载
  11. micropython和python区别-什么是 MicroPython ?它有什么优势?
  12. centos jupyter 安装_centos6.4安装 jupyter-notebook
  13. 苹果越狱后怎么还原_iOS 软件证书失效怎么办?越狱后你可以这么做
  14. Opencv中rect的功能应用
  15. 用Python把B站视频弹幕爬下来,绘制词云图看看大家最关心什么!
  16. 2020年汽车驾驶员(初级)考试平台及汽车驾驶员(初级)模拟考试软件
  17. 从代理模式再出发!Proxy.newProxyInstance的秘密
  18. python实现自动登录qq邮箱,写邮件并发送
  19. 李群、李代数在SLAM中的应用
  20. 服务器运维系统哪个好用,可以说宝塔是史上最好用的服务器运维控制面板

热门文章

  1. SQL SERVER 2005 请求失败或服务未及时响应
  2. MongoDB的NUMA CPU架构问题
  3. 一步一步学Silverlight 2系列(35):升级Silverlight 2 Beta 1应用程序到Beta 2
  4. android 打开wifi并链接到制定ip,当设备连接到Android中的WiFi时,如何获取蜂窝网络的IP地址...
  5. c语言是函数式原型的编程,编程范式|程序世界里的编程范式,探索编程本质
  6. 国防科大 linux教程,国防科大《嵌入式系统》肖侬视频教程
  7. php合并播放mp4文件_如何将百度的流畅版视频m3u8合并为正确的mp4文件?
  8. java 规范异常的处理_规范-异常处理
  9. HTTP Status 400
  10. h5打开麦克风权限录音_MAC录屏没有声音?如何在苹果电脑MACBOOK上录音录屏