FIFO相关信号及空满状态的原理说明

存储技术

600人已加入

描述

FIFO(First In First Out)是异步数据传输时经常使用的存储器。该存储器的特点是数据先进先出(后进后出)。其实,多位宽数据的异步传输问题,无论是从快时钟到慢时钟域,还是从慢时钟到快时钟域,都可以使用 FIFO 处理。

FIFO 原理

◆工作流程

(1) 复位之后,在写时钟和非满状态信号的控制下,数据可以写入 FIFO 中。RAM 的写地址从 0 开始,每写一次数据写地址指针加一,指向下一个存储单元。当 FIFO 写满后,数据将不能再写入,否则数据会因覆盖而丢失。

(2) FIFO 数据为非空、或满状态时,在读时钟和非空状态信号的控制下,数据可以从 FIFO 中读出。RAM 的读地址从 0 开始,每读一次数据读地址指针加一,即指向下一个存储单元。当 FIFO 读空后,就不能再读数据,否则读出的数据将是错误的。

(3) FIFO 的存储结构为双口 RAM,允许读写同时进行。FIFO 的读写指针是循环计数的,即 读写指针对应的 RAM 地址超过 FIFO 深度时,会溢出归零,重新计数。

典型异步 FIFO 结构图如下所示。相关信号及空满状态的原理将在下面一一说明。

状态机

◆读写时刻

(1) 关于写时刻,只要 FIFO 中数据为非满状态,就可以进行写操作;如果 FIFO 为满状态,则禁止再写数据。

(2) 关于读时刻,只要 FIFO 中数据为非空状态,就可以进行读操作;如果 FIFO 为空状态,则禁止再读数据。

(3) 总之,如果一段时间内不间断的对 FIFO 同时进行读写操作,则要求写 FIFO 速率不能大于读 FIFO 速率。

◆读空状态

(1) 复位时,FIFO 中没有数据,空状态信号拉高。当 FIFO 被写入数据后,空状态信号拉低,表示非空状态。当读数据地址追赶上写地址,即读写地址都相等时,FIFO 为空状态。

(2) 因为 FIFO 是异步的,所以读写地址进行比较时,需要同步打拍逻辑,就需要耗费一定的时间。因此,空状态的指示信号不是实时的,会有一定的延时。如果在这段延迟时间内又有新的数据写入 FIFO,就会出现空状态指示信号有效,但实际上 FIFO 中存在数据的现象。

(3) 严格来讲该空状态指示是错误的。但是产生空状态的意义在于防止读操作对空状态的 FIFO 进行数据读取。产生空状态信号时,实际 FIFO 中有数据,相当于提前判断了空状态信号,此时不再进行读 FIFO 操作也是安全的。所以,该设计从应用上来说是没有问题的。

(4) 牢记,读空状态信号,是在读时钟域产生的。

◆写满状态

(1) 复位时,FIFO 中没有数据,满信号是拉低的,表示 FIFO 中的数据没有写满 (其实 FIFO 是空的 )。当 FIFO 开始写数据且读操作不进行或读速率相对较慢时,只要写数据地址超过读数据地址的 FIFO 深度时,便会产生满状态信号。此时写地址和读地址也是相等的,但是意义是不一样的。

状态机

(2) 此时经常使用多余的 1bit 分别当做读写地址的拓展位,来区分读写地址相同的时候,FIFO 的状态是空还是满状态。当读写地址与拓展位均相同的时候,表明读写数据的数量是一致的,则此时 FIFO 是空状态。如果读写地址相同,拓展位相反,表明写数据的数量已经超过读数据数量的一个 FIFO 深度了,此时 FIFO 是满状态。当然,此条件成立的前提是空状态禁止读操作、满状态禁止写操作。

(3) 同理,由于异步延迟逻辑的存在,满状态信号也不是实时的。但是也相当于提前判断了满状态信号,此时不再进行写 FIFO 操作也不会影响应用的正确性。

(4) 牢记,写满状态信号,是在写时钟域产生的。

◆格雷码

(1) 当读写时钟都是同一个时钟时,此时 FIFO 是同步的,直接对读写指针进行比对,产生空、满信号即可。

(2) 当读写时钟是异步的时候,因为读时钟域产生读空信号,写时钟域产生写满信号,所以产生空逻辑信号时,需要将写指针同步到读时钟域,再与读指针进行比较;产生满逻辑信号时,需要将读指针同步到写时钟域,再与写指针进行比较。

(3) 因为读写指针的信号宽度一般都是大于 1bit 的,所以同步处理时不能直接对多位宽的读写指针进行延迟打拍,需要借助格雷码对读写指针进行转换,保证每一个周期内地址指针只有 1bit 变化,然后再进行延迟打拍的同步处理。

(4) 4bit 的二进制码与格雷码之间的变化关系如下所示,其中 ⊕ 表示异或操作符。由图可知,二进制码对应的十六进制码递增时,二进制码对应的相邻的两个格雷码之间只有 1bit 数据有变化。当多位宽信号每次只有 1bit 数据变化时,可以使用延迟打拍的方法对其进行同步处理。

状态机

(5) 下面对空逻辑的产生进行举例说明:

5.1) 首先需要对写指针 waddr 进行组合逻辑的格雷码变换 waddr_gray。

5.2) 为了保证 waddr_gary 在读时钟域每次被采集时只有 1bit 数据变化,则 waddr_gray 需要在其源时钟域即写时钟域进行一拍缓存 waddr_gray_d。因为 waddr 到 waddr_gray 的组合逻辑变换时,每次两者之间不只是有 1bit 变化的。

5.3) 在读时钟域对 waddr_gray_d 进行打拍同步,得到读时钟域同步后的写指针为 waddr_gray_rclk。

5.4) 根据格雷码变换规则,空信号有效时二进制码相等的读写指针,变为格雷码之后仍然相等。所以直接使用 waddr_gray_rclk 与读指针进行组合逻辑变换后的格雷码进行相等比较,即可产生读空信号逻辑。

5.5) 需要说明的是,满信号有效时,带有拓展位的读写指针高 1bit 相反、低位相同。所以变为格雷码之后,写满信号产生的条件,则是读写指针高 2bit 相反、低位相同 (请读者思考一下为什么?)。

FIFO 设计

◆设计要求

为设计应用于各种场景的 FIFO,这里对设计提出如下要求:

(1) FIFO 是异步的,即读写控制信号来自不同的时钟域。

(2) FIFO 深度、宽度参数化,输出空、满状态信号,并输出一个可配置的满状态信号。当 FIFO 内部数据达到设置的参数数量时,该信号拉高,此时需要对格雷码进行反解码。

(3) 输入数据和输出数据位宽可以不一致,但要保证写数据、写地址位宽与读数据、读地址位宽的一致性。例如写数据位宽 8bit,写地址位宽为 6bit(64 个数据)。如果输出数据位宽要求 32bit,则输出地址位宽应该为 4bit(16 个数据)。

◆双口 RAM 设计

RAM 地址位宽、数据位宽等端口参数可配置,读写位宽一致。实际中 RAMDP(Dual Port) 是需要使用 Memory IP 的,这里创建的 RAM 并没有考虑到异步问题。

Verilog 描述如下。

module  ramdp
  #(  parameter       AW     = 5 ,
      parameter       DW     = 16
   )
   (
    input                   CLK_WR , //写时钟
    input [DW-1:0]          D ,      //写数据
    input                   WR_EN ,  //写使能
    input [AW-1:0]          ADDR_WR ,//写地址
    input                   CLK_RD , //读时钟
    input                   RD_EN ,  //读使能
    input [AW-1:0]          ADDR_RD ,//读地址
    output reg [DW-1:0]     Q        //读数据
    );

   reg [DW-1:0]                 mem [(1<

◆计数器设计

计数器用于产生读写地址信息,位宽可配置,不需要设置结束值,让其溢出后自动重新计数即可。同时该计数器还具有格雷码转换与缓存的功能。

Verilog 描述如下。

module  ccnt_gray
  #(parameter W = 32'd8
    )
   (
    input              rstn ,
    input              clk ,
    input              en ,
    output [W-1:0]     cnt ,
    output [W-1:0]     cnt_gray ,
    output [W-1:0]     cnt_gray_d
    );


   reg [W-1:0]          cnt_r ;
   always @(posedge clk or negedge rstn) begin
      if (!rstn) begin
         cnt_r        <= 'b0 ;
      end
      else if (en) begin
         cnt_r        <= cnt_r + 1'b1 ;
      end
   end
   assign cnt = cnt_r ;
   assign cnt_gray      = cnt ^ (cnt>>1);


   reg [W-1:0]          cnt_gray_buf ;
   always @(posedge clk or negedge rstn) begin
      if (!rstn) begin
         cnt_gray_buf   <= 'b0 ;
      end
      else begin
         cnt_gray_buf   <= cnt_gray ;
      end
   end
   assign cnt_gray_d = cnt_gray_buf ;


endmodule

◆多位宽数据同步设计

读写指针进行格雷码变换并缓存后,每一个计数周期内地址指针只有 1bit 变化,所以可以直接使用延迟打拍的方法进行同步。数据宽度、同步级数均可配置。

Verilog 描述如下。

module  data1c_sync
  #(parameter DW    = 8,
    parameter STAGE = 3
    )
   (
    input              rstn ,
    input              clk ,
    input [DW-1:0]     data_in ,
    output [DW-1:0]    data_out
    );


   reg [DW-1: 0]       data_r [STAGE-1: 0];
   integer             i ;
   always @(posedge clk or negedge rstn) begin
      if (!rstn) begin
         for (i=0; i

◆格雷码反解码

因为该 FIFO 还存在一个可配置的满状态信号输出,所以需要对格雷码同步后的读指针进行反解码,然后在写时钟域与写指针进行比较,以判读当前 FIFO 中数据的具体个数。

module  gray_decode
  #(parameter W = 32'd8
    )
   (
    input [W-1:0]      gray ,
    output [W-1:0]     gray_decode
    );


   integer             i ;
   reg [W-1:0] gray_decode_r ;
   always @(*) begin
      gray_decode_r[W-1]        = gray[W-1];
      for (i=W-2; i>=0; i=i-1) begin
         gray_decode_r[i]     = gray_decode_r[i+1] ^ gray[i];
      end
   end


   assign gray_decode = gray_decode_r ;
endmodule

◆FIFO 设计

该模块为 FIFO 的主体部分,产生读写控制逻辑,包括读写指针、读写有效时刻以及空、满、可编程满状态的逻辑。

实际上此模块已经是典型的 FIFO 设计,有需要的读者可以直接使用该层次的 FIFO 代码进行测试,甚至应用到自己的设计之中。

module  fifo
    #(  parameter       DW        = 16 ,
        parameter       DEPTH     = 32 ,
        parameter       PROG_DEPTH = 16) //可设置深度
    (
        input                   rstn,  //读写使用一个复位
        input                   wclk,  //写时钟
        input                   wren,  //写使能
        input [DW-1: 0]         wdata, //写数据
        output                  wfull,    //写满标志
        output                  prog_full, //可编程满标志
        input                   rclk,  //读时钟
        input                   rden,  //读使能
        output [DW-1 : 0]       rdata, //读数据
        output                  rempty   //读空标志
     );


   localparam AW = log2b(DEPTH);
   //==================== push/wr counter ===============
   //wptr/waddr using one more bit to indict new-loop
   wire [AW:0]          waddr ;
   wire [AW:0]          waddr_gray ;
   wire [AW:0]          waddr_gray_d ;
   ccnt_gray        #(.W(AW+1))
   u_push_cnt(
      .rstn             (rstn),
      .clk              (wclk),
      .en               (wren && !wfull), //full 时禁止写
      .cnt              (waddr),
      .cnt_gray         (waddr_gray),
      .cnt_gray_d       (waddr_gray_d)
      );


   // sync: wptr from wclk to rclk
   wire [AW:0]          waddr_gray_rclk ;
   data1c_sync   #(.DW(AW+1), .STAGE(3))
   u_waddr_to_rclk
   (
      .rstn        (rstn),
      .clk         (rclk),
      .data_in     (waddr_gray_d),
      .data_out    (waddr_gray_rclk)
    );
   //============== pop/rd counter ===================
   wire [AW:0]          raddr ;
   wire [AW:0]          raddr_gray ;
   wire [AW:0]          raddr_gray_d ;
   ccnt_gray        #(.W(AW+1))
   u_pop_cnt(
      .rstn             (rstn),
      .clk              (rclk),
      .en               (rden && !rempty), //full 时禁止写
      .cnt              (raddr),
      .cnt_gray         (raddr_gray),
      .cnt_gray_d       (raddr_gray_d)
      );
   // sync: rdtr from rclk to wclk
   wire [AW:0]          raddr_gray_wclk ;
   data1c_sync   #(.DW(AW+1), .STAGE(3) )
   u_raddr_to_wclk
   (
       .rstn        (rstn),
       .clk         (wclk),
       .data_in     (raddr_gray_d),
       .data_out    (raddr_gray_wclk)
    );
   //============== full/empty logic ===================
   //(1) empty logic
   assign rempty = (raddr_gray == waddr_gray_rclk);




   //(2) full logic
   assign wfull  = (waddr_gray[AW:AW-1] == ~raddr_gray_wclk[AW:AW-1]) &&
                   (waddr_gray[AW-2:0] == raddr_gray_wclk[AW-2:0]) ;


   //(3) porgrammable full
   //waddr gray decode
   wire [AW:0]          raddr_degray_wclk ;
   gray_decode  #(.W(AW+1))
   u_waddr_degray_rclk (
       .gray            (raddr_gray_wclk),
       .gray_decode     (raddr_degray_wclk)
     );
   //prog full
   wire [AW:0]  waddr_delta = waddr >= raddr_degray_wclk ?
                              (waddr - raddr_degray_wclk) :
                              ((1<<(AW+1)) + waddr - raddr_degray_wclk) ;
   assign       prog_full   = waddr_delta >= PROG_DEPTH ;


   //双口 ram 例化
   ramdp     #(.AW(AW), .DW (DW))
   u_ramdp
     (
      .CLK_WR          (wclk),
      .WR_EN           (wren & !wfull), //写满时禁止写
      .ADDR_WR         (waddr[AW-1:0]),
      .D               (wdata[DW-1:0]),
      .CLK_RD          (rclk),
      .RD_EN           (rden & !rempty), //读空时禁止读
      .ADDR_RD         (raddr[AW-1:0]),
      .Q               (rdata[DW-1:0])
      );


   function  integer log2b ;
      input     integer depth ;
      for (log2b=0; (1<

◆FIFO 调用

下面可以调用设计的 FIFO,完成多位宽数据传输的异步处理。

写数据位宽为 4bit,写深度为 32。

读数据位宽为 16bit,读深度为 8,可配置 full 深度为 16。

该模块只是 FIFO 的一个具体应用,用于数据的异步传输、缓存与整合。

//ensure write rate < read rate
module  fifo_buf
  #(  parameter       DWI        = 4 , //width 4
      parameter       AWI        = 5 , //depth 32
      parameter       DWO        = 16 ,
      parameter       AWO        = 3 ,
      parameter       PROG_DEPTH = 16
   )
   (
      input                     rstn,  //读写使用一个复位
      //data in
      input                     din_clk,  //写时钟
      input                     din_en,  //写使能
      input [DWI-1: 0]          din, //写数据
      //data out
      input                     dout_clk,  //读时钟
      output                    dout_valid,  //读使能
      output [DWO-1 : 0]        dout //读数据
    );


   wire                         wfull ;    //写满标志
   wire                         prog_full ; //可编程满标志
   wire                         rempty ;   //读空标志
   wire [DWI-1:0]               rdata_fifo ;
   wire                         rden_fifo ;
   fifo  #(.DW(DWI), .DEPTH(1<

◆testbench

testbench 描述如下,用于测试空、满逻辑信号,以及读写操作。测试中只列举了输入数据位宽小于输出数据位宽的情景。

`timescale 1ns/1ns
`define SMALL2BIG


module test ;
`ifdef SMALL2BIG
   reg          rstn ;
   reg          clk_slow, clk_fast ;
   reg [3:0]    din ;
   reg          din_en ;
   wire [15:0]  dout ;
   wire         dout_valid ;


   //reset
   initial begin
      clk_slow  = 0 ;
      clk_fast  = 0 ;
      rstn      = 0 ;
      #50 rstn  = 1 ;
   end


   //读时钟 clock_slow 较快于写时钟 clk_fast 的 1/4
   //保证读数据稍快于写数据
   parameter CYCLE_WR = 40 ;
   always #(CYCLE_WR/2/4) clk_fast = ~clk_fast ;
   always #(CYCLE_WR/2-1) clk_slow = ~clk_slow ;


   //data generate
   initial begin
      din       = 16'h4321 ;
      din_en    = 0 ;
      wait (rstn) ;
      //(1) 测试 full、prog_full、empyt 信号
      force test.u_data_buf.u_fifo.rden = 1'b0 ;
      repeat(32) begin
         @(negedge clk_fast) ;
         din_en = 1'b1 ;
         din    = {$random()} % 16;
      end
      @(negedge clk_fast) din_en = 1'b0 ;


      //(2) 测试数据读写
      #500 ;
      rstn = 0 ;
      #10 rstn = 1 ;
      release test.u_data_buf.u_fifo.rden ;
      repeat(60) begin
         @(negedge clk_fast) ;
         if (!test.u_data_buf.u_fifo.wfull) begin
            din_en = 1'b1 ;
            din    = {$random()} % 16;
         end
         else begin
            din_en = 1'b0 ;
         end
      end


      //(3) 停止读取再一次测试 empyt、full、prog_full 信号
      #800 ;
      force test.u_data_buf.u_fifo.rden = 1'b0 ;
      repeat(18) begin
         @(negedge clk_fast) ;
         din_en = 1'b1 ;
         din    = {$random()} % 16;
      end
   end


   fifo_buf #(.DWI(4), .AWI(5), .DWO(16), .AWO(3), .PROG_DEPTH(16))
     u_data_buf(
        .rstn           (rstn),
        .din_clk        (clk_fast),
        .din            (din),
        .din_en         (din_en),
        .dout_clk       (clk_slow),
        .dout           (dout),
        .dout_valid     (dout_valid));


`else // !`ifdef SMALL2BIG
`endif


   //stop sim
   initial begin
      forever begin
         #100;
         if ($time >= 5000)  $finish ;
      end
   end


endmodule

◆仿真分析

根据 testbench 中的 3 步测试激励,分析如下:

测试 (1) : FIFO 端口及一些内部信号时序结果如下。

由图可知,FIFO 内部开始写数据,空状态信号 rempty 拉低(红色 M1 ) 之前有一段时间延迟,这是写地址同步延时导致的。

由于此时没有进行读 FIFO 操作,所以 prog_full 与 full 拉高 (黄色 M2 与绿色 M3) 几乎没有延迟。

状态机

测试 (2) : FIFO 同时进行读写时,fifo 与 fifo_buf 模块的端口信号如下所示。

由图可知,数据开始传输时,fifo 模块的等位宽数据输出、fifo_buf 整合之后的数据输出,均与输入数据对应,没有传输错误。

状态机

测试 (3) :整个 FIFO 读写行为及读停止的时序仿真图如下所示。

由图可知,读写操作同时进行时,wfull 信号会不断翻转 (M4 时刻之前),这是因为此时 fifo 的使用方法是非满即写,非空即读。

M4 时刻禁止写操作后(din_en 恒为 0),由于读一致存在,所以 full 信号会拉低并保持,表示 fifo 中数据一直未满。而 prog_full 也会相继拉低 (M5 时刻),表示 FIFO 中的数据已经低于配置的数目。

当恢复写操作后 (din_en 恒为 1,对应 M6 时刻),prog_full 与 full 信号相继拉高。

状态机

打开APP阅读更多精彩内容
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉

全部0条评论

快来发表一下你的评论吧 !

×
20
完善资料,
赚取积分