异步FIFO—Verilog实现

异步FIFO—Verilog实现本文大部分内容来自 CliffordE Cummings 的 Simulationan amp nbsp FIFODesign 经过自己的一些改变 理论部分为转载 代码自己完成 一 FIFO 简介 FIFO 是英文 FirstInFirst 的缩写 是一种先进先出的数据缓存器 它与普通存储器的区别是没有外部

  本文大部分内容来自Clifford E. Cummings的《Simulation and Synthesis Techniques for Asynchronous FIFO Design》,经过自己的一些改变,理论部分为转载,代码自己完成。

一、FIFO简介

  FIFO是英文First In First Out 的缩写,是一种先进先出的数据缓存器,它与普通存储器的区别是没有外部读写地址线,这样使用起来非常简单,但缺点就是只能顺序写入数据,顺序的读出数据,其数据地址由内部读写指针自动加1完成,不能像普通存储器那样可以由地址线决定读取或写入某个指定的地址。

用途1:

  异步FIFO读写分别采用相互异步的不同时钟。在现代集成电路芯片中,随着设计规模的不断扩大,一个系统中往往含有数个时钟,多时钟域带来的一个问题就是,如何设计异步时钟之间的接口电路。异步FIFO是这个问题的一种简便、快捷的解决方案,使用异步FIFO可以在两个不同时钟系统之间快速而方便地传输实时数据。

用途2:

  对于不同宽度的数据接口也可以用FIFO,例如单片机位8位数据输出,而DSP可能是16位数据输入,在单片机与DSP连接时就可以使用FIFO来达到数据匹配的目的。

二、分类

  同步FIFO是指读时钟和写时钟为同一个时钟,在时钟沿来临时同时发生读写操作;

  异步FIFO是指读写时钟不一致,读写时钟是互相独立的。

三、FIFO的常见参数

  • FIFO的宽度:即FIFO一次读写操作的数据位;
  • FIFO的深度:指的是FIFO可以存储多少个N位的数据(如果宽度为N)。
  • 满标志:FIFO已满或将要满时由FIFO的状态电路送出的一个信号,以阻止FIFO的写操作继续向FIFO中写数据而造成溢出(overflow)。
  • 空标志:FIFO已空或将要空时由FIFO的状态电路送出的一个信号,以阻止FIFO的读操作继续从FIFO中读出数据而造成无效数据的读出(underflow)。
  • 读时钟:读操作所遵循的时钟,在每个时钟沿来临时读数据。
  • 写时钟:写操作所遵循的时钟,在每个时钟沿来临时写数据。

===============================分   隔    符 ==============================

  1. 读写指针的工作原理

  读指针:总是指向下一个将要被写入的单元,复位时,指向第1个单元(编号为0)

  写指针:总是指向当前要被读出的数据,复位时,指向第1个单元(编号为0)

 

  1. FIFO的“空”/“满”检测

  FIFO设计的关键:产生可靠的FIFO读写指针和生成FIFO“空”/“满”状态标志。

  当读写指针相等时,表明FIFO为空,这种情况发生在复位操作时,或者当读指针读出FIFO中最后一个字后,追赶上了写指针时,如下图所示:

          异步FIFO---Verilog实现

 

  当读写指针再次相等时,表明FIFO为满,这种情况发生在,当写指针转了一圈,折回来(wrapped around)又追上了读指针,如下图:

        异步FIFO---Verilog实现

 

    为了区分到底是满状态还是空状态,可以采用以下方法:

    方法1:在指针中添加一个额外的位(extra bit),当写指针增加并越过最后一个FIFO地址时,就将写指针这个未用的MSB1,其它位回零。对读指针也进行同样的操作。此时,对于深度为2nFIFO,需要的读/写指针位宽为(n+1)位,如对于深度为8FIFO,需要采用4bit的计数器,0000100010011111MSB作为折回标志位,而低3位作为地址指针。

    • 如果两个指针的MSB不同,说明写指针比读指针多折回了一次;如r_addr=0000,w_addr = 1000,为满。
    • 如果两个指针的MSB相同,则说明两个指针折回的次数相等。其余位相等,说明FIFO为空;

3.二进制FIFO指针的考虑

  将一个二进制的计数值从一个时钟域同步到另一个时钟域的时候很容易出现问题,因为采用二进制计数器时所有位都可能同时变化,在同一个时钟沿同步多个信号的变化会产生亚稳态问题。而使用格雷码只有一位变化,因此在两个时钟域间同步多个位不会产生问题。所以需要一个二进制到gray码的转换电路,将地址值转换为相应的gray码,然后将该gray码同步到另一个时钟域进行对比,作为空满状态的检测。

    异步FIFO---Verilog实现

 

4.

  • 使用gray码进行对比,如何判断“空”与“满”

   使用gray码解决了一个问题,但同时也带来另一个问题,即在格雷码域如何判断空与满。

  对于“空”的判断依然依据二者完全相等(包括MSB)

  而对于“满”的判断,如下图,由于gray码除了MSB外,具有镜像对称的特点,当读指针指向7,写指针指向8时,除了MSB,其余位皆相同,不能说它为满。因此不能单纯的只检测最高位了,在gray码上判断为满必须同时满足以下3条:

  • wptr和同步过来的rptrMSB不相等,因为wptr必须比rptr多折回一次。
  • wptrrptr的次高位不相等,如上图位置7和位置15,转化为二进制对应的是01111111MSB不同说明多折回一次,111相同代表同一位置。
  • 剩下的其余位完全相等。

      异步FIFO---Verilog实现

5.总体实现

    系统的总体框图如下:

        异步FIFO---Verilog实现

 四、同步化分析

由于是异步FIFO的设计,读写时钟不一样,在产生读空信号和写满信号时,会涉及到跨时钟域的问题,如何解决?
  跨时钟域的问题:由于读指针是属于读时钟域的,写指针是属于写时钟域的,而异步FIFO的读写时钟域不同,是异步的,要是将读时钟域的读指针与写时钟域的写指针不做任何处理直接比较肯定是错误的,因此我们需要进行同步处理以后仔进行比较
  解决方法
加两级寄存器同步 + 格雷码(目的都是消除亚稳态)
 
1.使用异步信号进行使用的时候,好的设计都会对异步信号进行同步处理,同步一般采用多级D触发器级联处理,如下图。这种模型大部分资料都说的是第一级寄存器产生亚稳态后,第二级寄存器稳定输出概率为90%,第三极寄存器稳定输出的概率为99%,如果亚稳态跟随电路一直传递下去,那就会另自我修护能力较弱的系统直接崩溃。
异步FIFO---Verilog实现

 

2.将一个二进制的计数值从一个时钟域同步到另一个时钟域的时候很容易出现问题,因为采用二进制计数器时所有位都可能同时变化,在同一个时钟沿同步多个信号的变化会产生亚稳态问题。而使用格雷码只有一位变化,因此在两个时钟域间同步多个位不会产生问题。所以需要一个二进制到gray码的转换电路,将地址值转换为相应的gray码,然后将该gray码同步到另一个时钟域进行对比,作为空满状态的检测。

那么,多位二进制码如何转化为格雷码?

异步FIFO---Verilog实现

 

换一种描述方法:

异步FIFO---Verilog实现

verilog代码实现就一句:assign  gray_code = (bin_code>>1)  ^  bin_code;

 使用gray码解决了一个问题,但同时也带来另一个问题,即在格雷码域如何判断空与满。 

这里直接给出结论:

  判断读空时:需要读时钟域的格雷码rgray_next和被同步到读时钟域的写指针rd2_wp每一位完全相同;

  判断写满时:需要写时钟域的格雷码wgray_next和被同步到写时钟域的读指针wr2_rp高两位不相同,其余各位完全相同;

assign full = (wr_addr_gray == {~(rd_addr_gray_d2[addr_width-:2]),rd_addr_gray_d2[addr_width-2:0]}) ;//高两位不同 assign empty = ( rd_addr_gray == wr_addr_gray_d2 );

五、Verilog实现

module fifo_async#( parameter data_width = 16, parameter data_depth = 256, parameter addr_width = 8 ) ( input rst, input wr_clk, input wr_en, input [data_width-1:0] din, input rd_clk, input rd_en, output reg valid, output reg [data_width-1:0] dout, output empty, output full ); reg [addr_width:0] wr_addr_ptr;//地址指针,比地址多一位,MSB用于检测在同一圈 reg [addr_width:0] rd_addr_ptr; wire [addr_width-1:0] wr_addr;//RAM 地址 wire [addr_width-1:0] rd_addr; wire [addr_width:0] wr_addr_gray;//地址指针对应的格雷码 reg [addr_width:0] wr_addr_gray_d1; reg [addr_width:0] wr_addr_gray_d2; wire [addr_width:0] rd_addr_gray; reg [addr_width:0] rd_addr_gray_d1; reg [addr_width:0] rd_addr_gray_d2; reg [data_width-1:0] fifo_ram [data_depth-1:0]; //=========================================================write fifo  genvar i; generate for(i = 0; i < data_depth; i = i + 1 ) begin:fifo_init always@(posedge wr_clk or posedge rst) begin if(rst) fifo_ram[i] <= 'h0;//fifo复位后输出总线上是0,并非ram中真的复位。可无 else if(wr_en && (~full)) fifo_ram[wr_addr] <= din; else fifo_ram[wr_addr] <= fifo_ram[wr_addr]; end end endgenerate //========================================================read_fifo always@(posedge rd_clk or posedge rst) begin if(rst) begin dout <= 'h0; valid <= 1'b0; end else if(rd_en && (~empty)) begin dout <= fifo_ram[rd_addr]; valid <= 1'b1; end else begin dout <= 'h0;//fifo复位后输出总线上是0,并非ram中真的复位,只是让总线为0; valid <= 1'b0; end end assign wr_addr = wr_addr_ptr[addr_width-1-:addr_width]; assign rd_addr = rd_addr_ptr[addr_width-1-:addr_width]; //=============================================================格雷码同步化 always@(posedge wr_clk ) begin rd_addr_gray_d1 <= rd_addr_gray; rd_addr_gray_d2 <= rd_addr_gray_d1; end always@(posedge wr_clk or posedge rst) begin if(rst) wr_addr_ptr <= 'h0; else if(wr_en && (~full)) wr_addr_ptr <= wr_addr_ptr + 1; else wr_addr_ptr <= wr_addr_ptr; end //=========================================================rd_clk always@(posedge rd_clk ) begin wr_addr_gray_d1 <= wr_addr_gray; wr_addr_gray_d2 <= wr_addr_gray_d1; end always@(posedge rd_clk or posedge rst) begin if(rst) rd_addr_ptr <= 'h0; else if(rd_en && (~empty)) rd_addr_ptr <= rd_addr_ptr + 1; else rd_addr_ptr <= rd_addr_ptr; end //========================================================== translation gary code assign wr_addr_gray = (wr_addr_ptr >> 1) ^ wr_addr_ptr; assign rd_addr_gray = (rd_addr_ptr >> 1) ^ rd_addr_ptr; assign full = (wr_addr_gray == {~(rd_addr_gray_d2[addr_width-:2]),rd_addr_gray_d2[addr_width-2:0]}) ;//高两位不同 assign empty = ( rd_addr_gray == wr_addr_gray_d2 ); endmodule

仿真

六、重要补充


关于格雷码减小亚稳态,如果读写时钟差距过大,从快时钟域同步到慢时钟域的信号,时钟捕获的相邻两个数据变化并不是只有一个bit位的改变,可能导致格雷码失去原来的意义,嗯,目前的理解是这样。

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/200057.html原文链接:https://javaforall.net

(0)
上一篇 2026年3月20日 上午11:37
下一篇 2026年3月20日 上午11:37


相关推荐

  • Linux chmod文件授权命令

    Linux chmod文件授权命令用户类型 uuser 文件所有者 ggroup 文件所有者所在组 oothers 所有其他用户 aall 所用用户 相当于 ugo 操作符 为指定的用户类型增加权限 去除指定用户类型的权限 设置指定用户权限的设置 即将用户类型的所有权限重新设置 权限 r 4 w 2 x 1eg chmodugo rfile1 txt

    2026年3月18日
    2
  • Java多线程超详解

    Java多线程超详解引言随着计算机的配置越来越高,我们需要将进程进一步优化,细分为线程,充分提高图形化界面的多线程的开发。这就要求对线程的掌握很彻底。那么话不多说,今天本帅将记录自己线程的学习。线程的相关API//获取当前线程的名字Thread.currentThread().getName()1.start():1.启动当前线程2.调用线程中的run方法2.run():通常需要重写Thread类中的此…

    2022年6月6日
    35
  • springboot实战第四章-Spring MVC的测试

    springboot实战第四章-Spring MVC的测试

    2021年5月16日
    112
  • Shiro 框架简单介绍

    Shiro 框架简单介绍Shiro 框架简单介绍

    2022年4月22日
    43
  • spring @Transactional注解用于事务回滚案例

    spring @Transactional注解用于事务回滚案例spring @Transactional注解用于事务回滚案例

    2022年4月23日
    80
  • Python setattr()函数

    Python setattr()函数描述 setattr 函数对应函数 getattr 函数 用于设置属性值 参数是对象 字符串和任意值 字符串可以命名现有属性或新属性 如果对象允许 函数将值赋给属性 例如 setattr x foobar 123 等同于 x foobar 123 语法 setattr object name value 参数介绍 object 对象 name 字符串

    2026年3月19日
    2

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号