异步FIFO

异步FIFO简介

结构框图

如上图所示的同步模块synchronize to write clk,其作用是把读时钟域的指针rptr采集到写时钟wr_clk域,然后和写时针wptr进行比较从而产生或撤销写写满标志wfull;类似地,同步模块synchronize to read clk的作用是把写时钟域的写指针wptr采集到读时钟域,然后和读指针rptr进行比较从而产生或撤销读空标志位rempty。

另外还有写指针wptr和写满标志位wfull产生模块读指针和读空标志位rempty产生模块 ,以及双端口存储RAM模块。

写满、读空

当FIFO为满或为空时,写入指针和读取指针都是相等的。但我们需要将“满”与“空”区分,当FIFO工作时,写指针在前,读指针紧跟写指针。当FIFO为满时,写指针往前移动,返回并等于后面紧跟的读指针,这就是所谓的套圈。这个时候我们再增加1bit给读写指针,可以通过这个bit为0还是1来显示“满”或“空”。

二进制计数异步FIFO读写指针需要在数学上的操作和比较才能产生准确的空满标志位,但由于读写指针属于不同的时钟域及读写时钟相位关系的不确定性,同步模块采集另一时钟域的指针时,此指针有可能正处在跳变的过程中。

上图中,rd_ptr2sync3和4以及4和5之间的中间态是由于到各寄存器的时钟rd_clk存在偏差而引起的。二进制的递增操作,在大多数情况下都会有两位或者两位以上的bit位在同一个递增操作内发生变化,但由于实际电路中会存在时钟偏差和不同的路径延时,二进制计数器在自增时会不可避免地产生错误的中间结果。

格雷码计数

格雷码一个最大的特点就是在递增或递减的过程中,每次只变化一位,这是它最大的优点。同时它也有自己的局限性,那么就是循环计数深度必须是2的n次幂(这个例子中n=4),否则就失去了每次只变化一位的特性。通过观察格雷码相邻位每次只有1位发生变化,上下两部分,除最高位相反,其余镜像对称。

7——8,格雷码从0100——1100,只有最高位发生变化其余位相同;

6——9,格雷码从0101——1101,只有最高位发生变化其余位相同;

那么进行空满判断的时候,就不是看最高位了,因为7-8的最高位不同,而其他位相同,在之前的判断中就会被判断为“满”,这就出现误判了。所以,用格雷码来判断时,还要考虑次高位。

当最高位和次高位相同,其余位相同认为是读空。

当最高位和次高位不同,其余位相同认为是写满。

跨时钟域的同步解决

读时针是属于读时钟域的,写指针是属于写时钟域的,而异步FIFO的读写时钟域不同,是异步的。如果将rclk的读指针和wclk的写指针直接比较肯定是错误的,我们需要进行同步处理进行比较。

方案:格雷码+两级寄存器 同步

(1)将写时钟域的写指针同步到读时钟域,将同步后的写指针与读时钟域的读指针进行比较产生读空信号;

(2)将读时钟域的读指针同步到写时钟域,将同步后的读指针与写时钟域的写指针进行比较产生写满信号。

两点注意:

1、打两拍(两级寄存器同步):输入信号来自异步时钟域(比如FPGA芯片外部的输入),必须寄存两拍。第一拍将输入信号同步化,同步化后的输出可能带来建立/保持时间的冲突,产生亚稳态。需要再寄存一拍,减少(注意是减少)亚稳态带来的影响。

2、同步的指针和两者比较的指针都是格雷码指针!

二进制转化格雷码

二进制数 10110

二进制数右移1位,空位补0 01011

异或运算11101

这样就可以实现二进制到格雷码的转换了,总结就是移位并且异或,代码实现如下:

assign wgraynext=(wbinnext>>1)^wbinnext;
assign rgraynext=(rbinnext>>1)^rbinnext;

代码实现

异步FIFO,宽度8,深度16,地址4bit,多加1bit判断空满

顶层模块fifo.v

module fifo
#(parameter DSIZE=8, parameter ASIZE=4)
(
  output [DSIZE-1:0] rdata,
  output wfull,
  output rempty,
  input [DSIZE-1:0] wdata,
  input wclk, wrst, wr_en,
  input rclk, rrst, rd_en
);
wire [ASIZE-1:0] waddr, raddr;
wire [ASIZE:0] rptr, wptr, rq2_wptr, wq2_rptr;

fifomem i1(
 .wdata(wdata),
 .waddr(waddr),
 .raddr(raddr),
 .wr_en(wr_en),
 .wclk(wclk),
 .wfull(wfull),
 .rdata(rdata)
);

sync_r2w i2(
.wclk(wclk),
.wrst(wrst),
.rptr(rptr),
.wq2_rptr(wq2_rptr)
);

sync_w2r i3(
.rclk(rclk),
.rrst(rrst),
.wptr(wptr)
.rq2_wptr(rq2_wptr),
);
wptr_full i4(    //将sync_r2w.v同步后的读指针与wclk时钟域的写指针进行比较生成写满信号
.wclk(wclk),
.wrst(wrst),
.wr_en(wr_en),
.waddr(waddr),
.wfull(wfull),
.wptr(wptr),
.wq2_rptr(wq2_rptr)
);

rptr_empty i5(
.rd_en(rd_en),
.rclk(rclk),
.rrst(rrst),
.rq2_wptr(rq2_wptr),
.rempty(rempty),
.raddr(raddr),
.rptr(rptr)
);

endmodule

fifomem.v

module fifomem
#(parameter DATASIZE=8, parameter ADDRSIZE=4)
(
 input [DATASIZE-1:0] wdata,
input [ADDRSIZE-1:0] waddr, raddr,
input wr_en, wfull, wclk,
output [DATASIZE-1:0] rdata
);
localparam DEPTH=1<<ADDRSIZE;                     //1左移4位
reg [DATASIZE-1:0] ram [DEPTH-1:0];
assign rdata = ram [raddr];
always @(posedge wclk)
  begin
      if(wr_en && !wfull) 
             ram[waddr]<=wdata;
  end
 endmodule

## sync_r2w.v

//将rclk中的格雷码读指针打两拍同步到wclk
module sync_r2w
#(parameter ADDRSIZE=4)
(
  input [ADDRSIZE:0] rptr,            //格雷码形式的读指针
  input wclk, wrst,
  output reg [ADDRSIZE:0] wq2_rptr   //同步到写时钟域的打两拍后的读指针
);
reg [ADDRSIZE:0] wq1_rptr;           //同步到写时钟域的打一拍后的读指针
always @(posedge wclk or negedge wrst) 
begin
   if(!wrst) 
     begin
         wq1_rptr<=0;
         wq2_rptr<=0
         end
       else
 begin
             wq1_rptr<=rptr;
             wq2_rptr<=wq1_rptr;
         end
end
endmodule

sync_w2r.v将写时钟域 (wclk) 中的格雷码写指针打两拍同步到读时钟域(rclk)

module sync_w2r
#(parameter ADDRSIZE=4)
(
  input [ADDRSIZE:0] wptr,                 //格雷码形式的写指针
  input rclk, rrst,
  output reg[ADDRSIZE:0] rq2_wptr         //同步到读时钟域的打两拍的写指针
);
reg [ADDRSIZE:0] rq1_wptr;                //同步到读时钟域的打一拍的写指针
always @(posedge rclk or negedge rrst)
     begin
       if(!rrst)
           begin
              rq1_wptr<=0;
              rq2_wptr<=0;
           end
       else
           begin
              rq1_wptr<=wptr;
              rq2_wptr<=rq1_wptr;
           end
      end
endmodule

wptr_full.v

//产生写满信号
module wptr_full
#(parameter ADDRSIZE=4)
(
  input [ADDRSIZE:0] wq2_rptr,         //同步后的读指针
  input wclk, wrst, wr_en,
  output reg wfull,
  output reg [ADDRSIZE:0]    wptr,    //格雷码形式写指针
  output    [ADDRSIZE-1:0]  waddr   //二进制写指针
);
wire [ADDRSIZE:0]  wbinnext, wgraynext;
reg  [ADDRSIZE:0] wbin;
//将二进制的写指针与格雷码的写指针同步
always @(posedge wclk or negedge wrst)
     begin
         if (!wrst)
            begin
                wbin<=0;
                wptr<=0;
         else
            begin
                wbin<=wbinnext;        //直接作为存储实体的地址
                wptr<=wgraynext;       //输出到sync_w2r.v模块,被同步到rdclk模块
            end
     end
assign  waddr= wbin [ADDRSIZE-1:0];      //生成二进制写地址
assign  wbinnext= wbin+(wr_en & ~wfull);  //wbinnext是由assign关键字指定的,必须是wire类型
assign  wgraynext= (wbinnext>>1)^wbinnext;
assign  wfull_val =(wgraynext=={~wq2_rptr[ADDRSIZE: ADDRSIZE-1], wq2_rptr[ADDRSIZE-2:0]});
always @(posedge wclk or negedge wrst)
      begin
         if (!wrst)
             wfull<=1’b0;
         else
             wfull<=wfull_val;
      end
endmodule

rptr_empty.v

//将sync_w2r.v同步后的写指针与rclk时钟域的读指针进行比较生成读空信号
module rptr_empty
#(parameter ADDRSIZE=4)
(
  input rd_en, rrst, rclk,
  input [ADDRSIZE:0] rq2_wptr,    //同步后的写指针
  output  reg  rempty,
  output  [ADDRSIZE-1:0] raddr,
  output  reg [ADDRSIZE:0] rptr
);
wire [ADDRSIZE:0]  rbinnext, rgraynext;
reg  [ADDRSIZE:0] rbin;

//将二进制的读指针与格雷码进制的读指针同步
always @(posedge rclk or negedge rrst)
      begin
           if (!rrst)
                begin
                   rbin<=0;
                   rptr<=0;
                end
           else
                begin
                   rbin<=rbinnext;
                   rptr<=rgraynext;
                end
      end
assign  raddr=rbin [ADDRSIZE-1:0];           //生成二进制读地址
assign  rbinnext= rbin+(rd_en & ~rempty);     //不空且有读请求的时候读指针加1
assign  rgraynext= (rbinnext>>1)^rbinnext;
assign  rempty_val=(rgraynext==rq2_wptr);
always @(posedge rclk or negedge rrst)
      begin
         if (!rrst)
               	rempty<=1’b1;
         else
                rempty<=rempty_val;
      end
endmodule

参考链接

1.异步FIFO的设计思路及verilog代码

https://blog.csdn.net/weixin_43067657/article/details/87790012

2.异步fifo的设计(FPGA)

https://www.cnblogs.com/aslmer/p/6114216.html

3.跨时钟域信号传输(二)

https://www.cnblogs.com/IClearner/p/6579754.html

4.FPGA基础知识20(FPGA设计异步时钟处理分类及百度文库资料)

https://blog.csdn.net/Times_poem/article/details/51915066

5.《Verilog HDL高级数字设计》中异步FIFO采用脉冲同步器的思路错误

https://blog.csdn.net/u014045393/article/details/100615272?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522158402494119726867857048%2522%252C%2522scm%2522%253A%252220140713.130056874..%2522%257D&request_id=158402494119726867857048&biz_id=0&utm_source=distribute.pc_search_result.none-task(FIFO非教条)

本文来源:https://blog.csdn.net/weixin_43067657/article/details/87790012

本文分享自微信公众号 - 数字ICer(Studying_Times)

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2020-08-24

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • DC其他的时序约束选项(二)

    前面介绍的设计都不算很复杂,都是使用时钟的默认行为作为电路的约束,都存在有路径给你约束,即信号的变化要在一个时钟周期内完成,并达到稳定值,以满足寄存器的建立和保...

    数字芯片社区
  • DC其他的时序约束选项(一)

    之前讲了基本的时序路径约束,现在我们来看一下其他的约束,然后通过实战来讲解一些其他的约束。实战中也没有前面的“理论”中的约束类型,但是可以通过实战来了解其他的约...

    数字芯片社区
  • 脉冲压缩处理

    脉冲压缩指雷达在发射时采用宽脉冲信号,接收和处理回波后输出窄脉冲。脉冲压缩技术是匹配滤波理论和相关接收理论的一个很好的实际应用。很好地解决了这样的一个问题:在发...

    数字芯片社区
  • FPGA时序约束理论篇之两种时序例外

      上面我们讲的是时钟周期约束,默认按照单周期关系来分析数据路径,即数据的发起沿和捕获沿是最邻近的一对时钟沿。如下图所示。

    猫叔Rex
  • 《数字集成电路静态时序分析基础》笔记⑥

    通过set_clock_uncertainty设定悲观条件,收紧约束,例如下图中设定建立时间和保持时间

    空白的贝塔
  • 每个开发者都应该了解的游戏引擎知识

    游戏引擎主要用于快速开发游戏,将游戏中固定不变的处理抽取出来,形成完整的游戏主体,用户只需要对开放的接口进行对象设计和驱动即可。在当前的环境,还...

    open
  • PowerBI 基于移动平均及最小二乘法的动态趋势预测

    如果将任何一个点的值都由此前的7个值平均得到,就是7日移动平均了。考察如下的示意图:

    BI佐罗
  • MediaCodec判断是否可以采用硬解码

    具体的类型对应关系可以查看相关文档,这里在Android源码MediaCodec.createDecoderByType()里面有一些相关的对应支持类型。

    曾大稳
  • 在MySQL里将中文转换成拼音

    兜兜毛毛
  • 操作系统第二篇【进程管理】

    Java3y

扫码关注云+社区

领取腾讯云代金券