异步FIFO设计

文章详细解释了异步FIFO的设计原理,包括数据控制、格雷码转换以同步不同时钟域的指针、full和empty信号的生成机制,以及如何利用双口RAM存储数据。重点介绍了格雷码在解决时钟域同步问题中的关键作用。
摘要由CSDN通过智能技术生成

参考csdn

(1)格雷码与二进制码之间的相互转换_格雷码与二进制码的互转-CSDN博客

参考其他:

(1)【数字IC】异步FIFO设计详解(含源码) - 知乎 (zhihu.com)

        异步FIFO读写采用不同的时钟,它主要有两个作用,一个是实现数据在不同时钟域进行传递,另一个作用就是实现不同数据宽度的数据接口互联。对于我本人来说,学习异步fifo主要就是为了跨时钟域数据传输

        异步FIFO主要由六部分组成:

        1)写、读数据控制模块:生成写地址指针、生成写数据使能;

        2)数据控制模块:生成写地址指针、生成写数据使能;

        3)格雷码转换模块:将二进制码转换为格雷码;

        4)格雷码同步模块:将格雷码同步到目标时钟域;

        5)空满信号生成模块:读写指针经格雷码转换、同步后进行比较,生成full & empty信号;

        6)数据存储模块:利用双口ram实现数据存储。

        为了理解异步FIFO的工作原理,因此根据参考文章学习重写了一遍代码,下图是模块的端口定义,本文主要就异步FIFO的设计代码进行展开,包含一些我在学习过程中的理解。

       

1、读写指针控制

        这部分涉及到四个信号,读指针、写指针、full信号、empty信号。读写指针是每一次读使能或写使能时候进行+1,使得能够准确获取当前两个时钟域的数据所在的位置。

        但是如果只使用到两个指针,当fifo被写满的时候再来写信号,fifo就会溢出;当fifo被读完时候再来读信号,fifo就会空读。

        为了避免以上两种情况出现,加入了full和empty信号。full信号控制写指针,当高电平时表示当前fifo已经写满,不能再被写入;empty信号控制读指针,当高电平时表示当前fifo已经读空,不能再被读出。

        代码实现如下:

2、格雷码转换

        异步fifo和同步fifo相比,会多出来一个进行格雷码转换的部分,这部分就是为了跨时钟域而设计的。由于读写指针处于不同的时钟域中,为了能够将其进行比较,必须将它们同步到对方的时钟域中。而指针出现跳变的时候有可能出现错误值,甚至多位同时跳变,出现错误的、不可控的中间值,从而产生错误的空满信号。

        格雷码能够保证每次从一个值变化到相邻的一个值时,有且仅有一位发生变化,因此通过将二进制转换成格雷码,我们就可以将多bit指针同步问题转化为单bit指针同步问题。

        二进制码转换成格雷码:(1)保留二进制码的最高位作为格雷码的最高位。(2)格雷码的其余位为二进制码对应位与其上一位相异或,如下图所示。

        与之相反的是格雷码转换成二进制码:(1)保留格雷码的最高位作为二进制码的最高位。(2)二进制码的其余位为格雷码对应位与二进制码上一位相异或,如下图所示。本次模块设计不涉及这部分转换,仅进行学习记录。

        在verilog中实现将读写指针二进制转换格雷码只需要如下:

3、格雷码跨时钟域同步

        读写指针无法直接进行比较,所以利用两级触发器,将转换为格雷码后的指针同步到目标时钟域。full信号位于写时钟域,需要将读指针同步到写时钟域后进行比较;而empty信号位于读时钟域,需要将写指针同步到读时钟域后进行比较。

        采用的是打两拍消除亚稳态的形式,代码如下:

        这部分我自己第一次写的时候在敏感事件列表将两个复位信号的位置写反了,其实正确的应该是写时钟和读复位,以及读时钟和写复位放在一起,复位信号需要采取的是原本时钟域的信号,而不是目标时钟域。这点很好理解,只不过写的时候很顺手就会写错,特地记录一下。

4、full和empty信号控制

        这段是原文关于这部分的内容以及示意图:

        直接对同步过后的读写指针进行比较,将指针在满足双口ram深度的基础上多设计一位,读写指针各位全部相同时,代表数据被读空了,empty信号拉高,读写指针最高位、次高位(因为是格雷码)不同,其余位相同时,代表双口ram被写满,full信号拉高。

        我对于这部分代码的理解是:1、在原有深度上扩充一位,最高位用于表示写指针超过读指针之后又写完了一次fifo的深度,此时按照格雷码的来说,会出现最高位次高位不同,其他位完全相同的情况。2、这样格雷码设计的时候就必须要满足fifo深度是2的幂次方的形式(实际上异步fifo本来就是有这个深度要求的),使得最高位为0和最高位为1的情况能够中心对称。

        然后原文评论有一段话我觉得说的很有道理,也把它粘贴过来:“例如,本来是0-15,现在我取1-14,或者2-13,你会发现都是可以用格雷码的,而且这样深度不一定是2的幂数,但是有个条件就是,想0-15,1-14,2-13这些都是关于0-15中点对称的,你如果随便去起止点就不可以,例如1-5,这样每次每次改变就不一定是变1bit了。”

        所以关于格雷码这部分的重点我的理解是:中心对称。代码实现如下:

5、数据储存模块

        一个双口ram,用来实现写读过程中的数据储存,这部分没什么要特别说明的,很常规的写法,代码实现如下:

6、总结和全部代码

        以上就是关于异步fifo进行跨时钟域数据处理的全部内容,可以看到这部分有三个需要理解的重点。1、二进制源码转换成格雷码的原因。2、格雷码跨时钟域打两拍防止亚稳态。3、full信号和empty信号的定义。

        最后附上异步FIFO整体代码以及测试所用tb文件,这部分也可以在原文中找到。

        模块代码:

module Async_FIFO 
#(
  parameter FIFO_DEPTH = 8,
  parameter DATA_WIDTH = 4,
  parameter PTR_WIDTH  = 3
)
(
  input clk_r,
  input clk_w,
  input rst_r_n,
  input rst_w_n,
  input w_en,
  input r_en,
  input [DATA_WIDTH-1:0] w_data,

  output reg [DATA_WIDTH-1:0] r_data,
  output reg full,
  output reg empty
);

  reg [PTR_WIDTH:0] w_ptr;
  reg [PTR_WIDTH:0] r_ptr;
  wire [PTR_WIDTH:0] w_ptr_gray;
  wire [PTR_WIDTH:0] r_ptr_gray;
  reg [PTR_WIDTH:0] w_ptr_gray_d1,w_ptr_gray_d2;
  reg [PTR_WIDTH:0] r_ptr_gray_d1,r_ptr_gray_d2;
  reg [DATA_WIDTH-1:0] data_mem[0:FIFO_DEPTH-1];
  reg [PTR_WIDTH:0] i;  
  

// write ptr control

  always @(posedge clk_w or negedge rst_w_n)
  begin
    if(!rst_w_n)
    begin
      w_ptr <= 0;
    end
    else if(w_en && !full)
    begin
      w_ptr <= w_ptr + 1;
    end
  end

// read ptr control

  always @(posedge clk_r or negedge rst_r_n)
  begin
    if(!rst_r_n)
    begin
      r_ptr <= 0;
    end
    else if(r_en && !empty)
    begin
      r_ptr <= r_ptr + 1;
    end
  end


//bin 2 gray

  assign w_ptr_gray = w_ptr ^ (w_ptr >> 1);
  assign r_ptr_gray = r_ptr ^ (r_ptr >> 1);


// gray sync 

  always @(posedge clk_r or negedge rst_w_n)
  begin
    if(!rst_w_n)
    begin
      w_ptr_gray_d1 <= 0;
      w_ptr_gray_d2 <= 0;
    end
    else
    begin
      w_ptr_gray_d1 <= w_ptr_gray;
      w_ptr_gray_d2 <= w_ptr_gray_d1;
    end
  end

  always @(posedge clk_w or negedge rst_r_n)
  begin
    if(!rst_r_n)
    begin
      r_ptr_gray_d1 <= 0;
      r_ptr_gray_d2 <= 0;
    end
    else
    begin
      r_ptr_gray_d1 <= r_ptr_gray;
      r_ptr_gray_d2 <= r_ptr_gray_d1;
    end
  end


//full and emtpy control

  assign empty = r_ptr_gray == w_ptr_gray_d2;
  assign full = w_ptr_gray == {~r_ptr_gray_d2[PTR_WIDTH:PTR_WIDTH-1], r_ptr_gray_d2[PTR_WIDTH-2:0]};


//data buf(ram)

  always @(posedge clk_w or negedge rst_w_n)
  begin
    if(!rst_w_n)
    begin
      for(i=0; i<FIFO_DEPTH; i=i+1)
      begin
          data_mem[i] <= 0;
      end
    end
    else if(w_en && !full)
    begin
      data_mem[w_ptr[PTR_WIDTH-1:0]] <= w_data;
    end
  end

  always @(posedge clk_r or negedge rst_r_n)
  begin
    if(!rst_r_n)
    begin
        r_data <= 0;
    end
    else if(r_en && !empty)
    begin
      r_data <= data_mem[r_ptr[PTR_WIDTH-1:0]];
    end
  end
  
endmodule

        tb代码:

`timescale 1ns/1ps

module tb_FIFO;
  reg          w_clk_i, r_clk_i;
  reg          w_rst_n_i, r_rst_n_i;

  reg          wr_en_i  ;
  reg  [3:0]   wr_data_i;
  
  reg          rd_en_i  ;
  wire  [3:0]  rd_data_o;

  wire          full_o   ;
  wire          empty_o  ;

  parameter  w_clk_period = 1000;   //1GHz时钟: T = 1000ns
  parameter  r_clk_period = 500;    //2GHz时钟: T = 500ns


  Async_FIFO u_Async_FIFO
  (
    .clk_w    (w_clk_i  ),
    .clk_r    (r_clk_i  ),
    .rst_w_n  (w_rst_n_i),
    .rst_r_n  (r_rst_n_i),
    .w_en     (wr_en_i  ),
    .w_data   (wr_data_i),
    .r_en     (rd_en_i  ),
    .r_data   (rd_data_o),
    .full     (full_o   ),
    .empty    (empty_o  )
  );


  // 生成写端clk:
  initial
  begin
    w_clk_i = 1'b1;
    forever
    begin
      #(w_clk_period/2)  w_clk_i = ~w_clk_i;
      
    end
  end

  //生成读端clk
  initial
  begin
    r_clk_i = 1'b1;
    forever
    begin
      #(r_clk_period/2)  r_clk_i = ~r_clk_i;
      
    end
  end

  //生成写复位、写使能、写数据
  initial 
  begin
    w_rst_n_i   = 1  ;
    

    wr_en_i   = 0  ;
    wr_data_i = 4'b0;

    #(w_clk_period)     w_rst_n_i = 0;
    #(w_clk_period*2)   w_rst_n_i = 1;

    @(posedge w_clk_i)
    begin
      wr_en_i = 1;

    end

    @(posedge w_clk_i)
    begin
      wr_en_i = 0;

    end

    @(posedge w_clk_i)
    begin
      wr_en_i = 1;

    end

    @(posedge w_clk_i)
    begin
      wr_en_i = 0;

    end

    #(w_clk_period)
    repeat(50)
    begin
        @(posedge w_clk_i)
        begin
          wr_en_i     = {$random}%2;
          wr_data_i   = {$random}%5'h10;
        end
    end

    #(w_clk_period)

    @(posedge w_clk_i)
    begin
      wr_en_i = 0;

    end

  end

  //生成读复位、读使能
  initial 
  begin
    r_rst_n_i   = 1  ;
    
    rd_en_i   = 0  ;

    #(r_clk_period)     r_rst_n_i = 0;
    #(r_clk_period*2)   r_rst_n_i = 1;

    @(posedge r_clk_i)
    begin
      rd_en_i = 0;
    end

    #(r_clk_period*30)
    repeat(60)
    begin
        @(posedge r_clk_i)
        begin
          rd_en_i = {$random}%2;
        end
    end
    
    #(r_clk_period*30)

    @(posedge r_clk_i)
    begin
      rd_en_i = 1;
    end

  end

  initial 
  begin
    #(w_clk_period*125)
    $stop;
  end


endmodule

  • 23
    点赞
  • 29
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值