转自http://ninghechuan.com

异步FIFO有两个异步时钟,一个端口写入数据,一个端口读出数据。通常被用于数据的跨时钟域的传输。

同步FIFO的设计。一个时钟控制一个计数器,计数器增加(只写不读),计数器减少(只读不写),计数器保持(不写不读)。计数器为0时,FIFO空,计数器为你定义的最大值,FIFO为满。貌似较容易设计。

很遗憾的是,异步FIFO并不能用这样的思想,因为异步FIFO有两个时钟,并没有办法控制一个计数器读写操作。只能分开读写计数器,通过比较读写指针的值来判断空满状态。

异步FIFO的读写指针

复位时,读写指针都为0。

写指针总是指向下一个要被写入的单元。复位时,写指针为0,所以指定的就是要被写入的0单元,当进行一次写操作,指针会自动加1,指向下一个要被写入的单元。

同样的,读指针总是指向当前要读的单元。复位时,读指针为0。当复位释放,这个时候如果有读使能读数据,是无效的,因为FIFO是空的。只有当写入数据后,FIFO空信号(empty)拉低时,才能读出有效的数据。

FIFO空满标志判断

当读指针和写指针相等的时候,可以认为FIFO空,认为是读指针追上了写指针,FIFO被读空了。当写指针追上读指针时,写数据比读数据快,并且写了一圈又写满了读空的一段,读写指针又相等。这样怎么能判断到底是FIFO满还是FIFO空呢?

通过读写指针位宽都多1bit的方法解决这个问题。读写指针最高位不会表示FIFO的地址深度,仅用来判断FIFO空满标志。即用N位标志读写指针的位宽,剩下的N-1 bit 表示FIFO读写深度。可以得出:

空标志:读指针等于写指针。

满标志:读指针和写指针的最高位不同,其余位相等。

if(waddr == raddr) -> empty
if({~waddr[N-1], waddr[N-2:0]} == raddr) -> full

FIFO的二进制读写指针考虑

异步FIFO的读写指针是在两个异步时钟下分别进行计数的,要判断空满标志就需要将两个指针进行比较,肯定需要将两个信号同步到一个时钟域下进行比较。这个过程是容易出现问题的,因为通常情况下的二进制计数器,可能是多个位同时变化的,例如从7-8(0111-1000)。同一个时钟沿同步多个信号可能会产生亚稳态。

采用Gray码(格雷码)可以解决这个问题,因为Gray码计数值增加,每次只变化一位。所以将一个地址指针转化为Gray后,再同步到另一个时钟域,进行比较得出空满信号。

二进制转换Gray码

assign  graydata = (bindata >> 1) ^ bindata;

FIFO设计框图

从上图可以看到最中间是这个FIFO的缓存部分用一个双口RAM可以实现。

左边的模块来产生FIFO的写指针和FIFO满标志,右边的模块来产生FIFO的读指针和FIFO空标志。

本设计中的FIFO 满标志在写时钟域下产生,这样在FIFO写满后能立即检测到并产生full标志。所以需要将读指针同步到写时钟域下进行比较。

FIFO空标志在读时钟域下产生,这样FIFO写空后能立即检测到并产生empty标志,所以需要将写指针同步到读时钟域下进行比较。

框图最下面有两个同步模块,便是实现读写指针的同步操作。

空满标志判断问题

前面说过判断空满标志的方法

空标志:读指针等于写指针。

assign	empty_val = (rd_cnt == wr_cnt_r);

always @(posedge rclk or negedge rst_n)begin
    if(!rst_n)
        fifo_empty <= 0;
    else
        fifo_empty <= empty_val;
end

满标志:读指针和写指针的最高位不同,其余位相等。

这种方法在二进制计数器下是完全没有问题的,但在格雷码表示下判断满标志就有问题了。如上图,四位格雷码,7(0_100)和8(1_100)的低三位是完全一样的,如果读地址为0_100,读地址为1_100,这个时候按照上文的说法,应该是FIFO写满了,但是真的是这样吗?这就是问题所在。

解决方法就是在判断FIFO满标志时,不仅判断最高位不同,次高位也应该不同,其余为相等,这个时候FIFO处于满状态。

根据四位格雷码可以推论出如上结论。

//assign full_val = ((wr_cnt[ADDR_SIZE] != rd_cnt_r[ADDR_SIZE]) && // (wr_cnt[ADDR_SIZE-1] != rd_cnt_r[ADDR_SIZE-1]) && // (wr_cnt[ADDR_SIZE-2:0] == rd_cnt_r[ADDR_SIZE-2:0])); //上一行代码可简化为 assign 	full_val = (wr_cnt == {~rd_cnt_r[ADDR_SIZE: ADDR_SIZE-1], rd_cnt_r[ADDR_SIZE-2:0]});

always @(posedge wclk or negedge rst_n)begin
    if(!rst_n)
        fifo_full <= 0;
    else
        fifo_full <= full_val;
end

解决了空满标志的这个棘手的问题,FIFO其余部分设计就可以随即设计,当然本设计仅为初学者学习,了解FIFO的基本原理。本文学习自Reference的这个paper,欢迎讨论交流。

代码博主自己尝试着写了一个,仿真了下修改了很多问题,但肯定有bug还没解决。

我放在Github上了,处于学习的学生朋友可以一起讨论。

https://github.com/NingHeChuan/Silicon_Peasant

Reference

Simulation and Synthesis Techniques for Asynchronous FIFO Design——Clifford E. Cummings, Sunburst Design, Inc.

Verilog设计异步FIFO的更多相关文章

  1. 异步fifo的设计

    本文首先对异步 FIFO 设计的重点难点进行分析 最后给出详细代码 一.FIFO简单讲解 FIFO的本质是RAM, 先进先出 重要参数:fifo深度(简单来说就是需要存多少个数据)           ...

  2. 异步fifo的Verilog实现

     一.分析 由于是异步FIFO的设计,读写时钟不一样,在产生读空信号和写满信号时,会涉及到跨时钟域的问题,如何解决? 跨时钟域的问题:由于读指针是属于读时钟域的,写指针是属于写时钟域的,而异步FIFO ...

  3. 基于FPGA的异步FIFO设计

    今天要介绍的异步FIFO,可以有不同的读写时钟,即不同的时钟域.由于异步FIFO没有外部地址端口,因此内部采用读写指针并顺序读写,即先写进FIFO的数据先读取(简称先进先出).这里的读写指针是异步的, ...

  4. 异步fifo的设计(FPGA)

    本文首先对异步 FIFO 设计的重点难点进行分析 最后给出详细代码 一.FIFO简单讲解 FIFO的本质是RAM, 先进先出 重要参数:fifo深度(简单来说就是需要存多少个数据)           ...

  5. 异步FIFO总结+Verilog实现

    异步FIFO简介 异步FIFO(First In First Out)可以很好解决多比特数据跨时钟域的数据传输与同步问题.异步FIFO的作用就像一个蓄水池,用于调节上下游水量. FIFO FIFO是一 ...

  6. 怎么用Verilog语言描述同步FIFO和异步FIFO

    感谢 知乎龚大佬 打杂大佬 网上几个nice的博客(忘了是哪个了....) 前言 虽然FIFO都有IP可以使用,但理解原理还是自己写一个来得透彻. 什么是FIFO? Fist in first out ...

  7. 异步FIFO空满设计延迟问题

    由于设计的时候读写指针用了至少两级寄存器同步,同步会消耗至少两个时钟周期,势必会使得判断空或满有所延迟,这会不会导致设计出错呢? 异步FIFO通过比较读写指针进行满空判断,但是读写指针属于不同的时钟域 ...

  8. 异步FIFO及verilog原码

    这几天看了Clifford E. Cummings的两篇大作<Simulation and Synthesis Techniques for Asynchronous FIFO Design&g ...

  9. 异步FIFO的verilog实现与简单验证(调试成功)

    最近在写一个异步FIFO的时候,从网上找了许多资料,文章都写的相当不错,只是附在后面的代码都多多少少有些小错误. 于是自己写了一个调试成功的代码,放上来供大家参考. 非原创 原理参考下面: 原文 ht ...

随机推荐

  1. 安卓开发之Room实体定义数据

    使用Room实体定义数据 在Room库中,entities(实体)代表着相关字段集.每一个entity(实体)代表着相关联数据库中的一个表.entity 类必须通过Database 类中的entiti ...

  2. python:异常处理、自定义异常、断言

    什么是异常: 当程序遭遇某些非正常问题的时候就会抛出异常:比如int()只能处理能转化成int的对象,如果传入一个不能转化的对象就会报错并抛出异常 常用的异常有: ValueError :传入无效的错 ...

  3. 第五章 绘图基础(SINEWAVE)

    //SINEWAVE.C -- Sine Wave Using Polyline (c) Charles Petzold, 1998 #include <Windows.h> #inclu ...

  4. c#中//注释和///注释的区别

    c#中//注释和///注释的区别 ///会被编译,//不会所以使用///会减慢编译的速度(但不会影响执行速度)///会在其它的人调用你的代码时提供智能感知 也是一种注释,但是这种注释主要有两种作用:1 ...

  5. cmd 命令

    cmd 在桌面或任意磁盘新建一个TXT--输入CMD并保存--修改扩展名为.BAT md 文件夹名 新建文件夹cd 文件夹名 进入到该目录cd.. 返回上一层目录cd\ 返回根目录cd.>文件名 ...

  6. Nginx实现页面缓存

    页面缓存 1.缓存指令 Nginx的缓存配置比较直观简单,具体有下面几个指令需要知道: A.proxy_cache_path 格式:proxy_cache_path path [levels=numb ...

  7. 初识SpringCloud微服务

    微服务是一种架构方式,最终肯定需要技术架构去实施. 微服务的实现方式很多,但是最火的莫过于Spring Cloud了.为什么? 后台硬:作为Spring家族的一员,有整个Spring全家桶靠山,背景十 ...

  8. Linux 下安装Node.js

    安装 node.js 安装包 http://nodejs.org 通过 rz 上传到 CentOS 进行解压 tar -xvf node-v8.0.0-linux-x64.tar.xz 进入到 bin ...

  9. 关于hover和after、before合用

    通常hover后面跟的选择器,都是在myClass结构之下 的dom元素. 如果想在myClass下添加after等,就需要两个::号. 注:after/before是属于myclass的下级元素,并 ...

  10. 【Java多线程】线程状态、线程池状态

    线程状态: 线程共包括以下5种状态.1. 新建状态(New) 线程对象被创建后,就进入了新建状态.例如,Thread thread = new Thread().2. 就绪状态(Runnable) 也 ...