FPGA设计过程中常用的FIFO

描述

 

介绍

无论何时,在复杂的 FPGA 设计过程中,都不可避免地需要在模块之间发送数据,实现这一点的常用的是 FIFO。

FIFO

写入:当写入 FIFO 时,需要确保不要写入太多数据以致 FIFO 溢出。为了帮助解决这个问题,FIFO 通常有一个完整的计数标志,有时还可以使用一个watermark端口。

watermark:本质上告诉 FIFO 中的项目何时超过一定数量,这时候应该放慢速度或不放入数据。但如果想发送特定数量的数据,将需要添加额外的步骤在状态机中管理“above watermark”的情况。在状态机上工作时,可能需要添加状态和寄存器来管理边缘情况(空满情况)。

full flag:比较棘手的信号,因为 full flag 可能会在输入数据的同一时钟变高。如果有流水线设计,则需要在检测到“full”状态时缓冲这些数据。

count:计数可以大致了解可以进入 FIFO 的数据量。计数的更新比watermark和full flag慢,并且会给你一个保守的 FIFO 内的空间计数。我很想经常使用它,但我发现我需要在状态机中添加一些状态来管理它。

Reading:从 FIFO 读取通常不会那么糟糕,只要在空标志不置位时不读取即可。

Double Buffer

我师傅让我考虑使用双端口block-ram 作为双缓冲器。就像 FIFO 一样,类似具有如下行为的读取器和写入器:

写入器:将数据写入block-ram,然后使用跨时钟域技术将数据的大小和状态发送给读取器。

读取器:读取写入器放入 RAM 的已知数据量。

这种方法的好处在于,写入器知道它可以写入多少空间,而读取器知道它可以读取多少数据。这非常适合流水线设计。另一个方面是写入器可以在读取器读取数据时开始处理block-ram 的后半部分。不过,这种方法并不是自由操作。以下是现在需要由写入器和读取器管理的一些事情:

写入器

ram中有多少空间(或ram的一半)

开始/结束地址指针

写入了多少数据

读取器

有多少数据可供读取

开始/结束地址指针

我喜欢这种双缓冲区给我的数据量的预知。这允许我编写内核,将已知数量的数据从双缓冲区的输出转储到另一个位置,如音频或视频缓冲区。不幸的是,每个使用双缓冲器的模块都必须设计为能够处理上述所有问题以及更多的跨时钟域标志。

如果不需要担心 FIFO(满/空)的边缘情况,这将是最容易使用的机制。下面将这两种机制结合起来可能是最佳方案。

Ping Pong FIFO

Ping Pong FIFO 本质上是一个上面描述的双缓冲区,包裹起来看起来像一个 FIFO。所有地址指针和跨时钟域通信都包含在一个简单的模块中。模块如下所示:

 

module PPFIFO
#(parameter     DATA_WIDTH    = 8,
                ADDRESS_WIDTH = 4
)(

  //universal input
  input                             reset,

  //write side
  input                             write_clock,
  output reg  [1:0]                 write_ready,
  input       [1:0]                 write_activate,
  output      [23:0]                write_fifo_size,
  input                             write_strobe,
  input       [DATA_WIDTH - 1: 0]   write_data,
  output                            starved,

  //read side
  input                             read_clock,
  input                             read_strobe,
  output reg                        read_ready,
  input                             read_activate,
  output reg  [23:0]                read_count,
  output      [DATA_WIDTH - 1: 0]   read_data,

  output                            inactive
);

 

有单独的写入端和读取端时钟,选通用于写入和读取数据。不过也有一些新的信号:

write_ready:这与双缓冲区有关,需要管理缓冲区的两侧。这2 bit信号告诉双缓冲区的哪一侧已准备好。

0:缓冲区的下半部分准备好

1:上半部分准备好

write_activate:用户告诉 Ping Pong FIFO 它想要拥有缓冲区的一侧

write_fifo_size:表示用户可以写入 Ping Pong FIFO(PPFIFO) 的字数。

注意:不需要在完成之前填充写入端,PPFIFO 将跟踪写入的元素数量并将此信息发送到读取端,作为将递增数字模式写入 PPFIFO 的简单模块示例

 

/* Module: ppfifo_source
 *
 * Description: Populate a Ping Pong FIFO with an incrementing number pattern
 */

module ppfifo_source #(
  parameter                       DATA_WIDTH    = 8
)(
  input                           clk,
  input                           rst,
  input                           i_enable,

  //Ping Pong FIFO Interface
  input       [1:0]               i_wr_rdy,
  output  reg [1:0]               o_wr_act,
  input       [23:0]              i_wr_size,
  output  reg                     o_wr_stb,
  output  reg [DATA_WIDTH - 1:0]  o_wr_data
);

//Local Parameters
//Registers/Wires
reg   [23:0]          r_count;
//Submodules
//Asynchronous Logic
//Synchronous Logic
always @ (posedge clk) begin
  //De-assert Strobes
  o_wr_stb          <= 0;

  if (rst) begin
    o_wr_act        <=  0;
    o_wr_stb        <=  0;
    o_wr_data       <=  0;
    r_count         <=  0;
  end
  else begin
    if (i_enable) begin
      if ((i_wr_rdy > 0) && (o_wr_act == 0))begin
        r_count     <=  0;
        if (i_wr_rdy[0]) begin
          //Channel 0 is open
          o_wr_act[0]  <=  1;
        end
        else begin
          //Channel 1 is open
          o_wr_act[1]  <=  1;
        end
      end
      else if (o_wr_act > 0) begin
        if (r_count < i_wr_size) begin
          //More room left in the buffer
          r_count   <=  r_count + 1;
          o_wr_stb  <=  1;
          //put the count in the data
          o_wr_data <=  r_count;
        end
        else begin
          //Filled up the buffer, release it
          o_wr_act  <=  0;
        end
      end
    end
  end
end

endmodule

 

正如所看到的,通过添加一个额外的寄存器来跟踪添加到 PPFIFO 的数据量,不必担心full flags、water marks 或者counts。

阅读方面更容易。PPFIFO 知道首先写入哪个缓冲区,因此用户只需要观察一个read_ready标志,然后使用read_activate告诉它我们有控制权。以下是从 PPFIFO 读取数据的示例:

这里有更具体的细节:

用户监视“read_ready”位:当“read_ready”信号为 1 时,ppfifo 为用户准备好一个数据块。

用户使用“read_activate”信号激活该块并使用“read_strobe”读取 NEXT 数据

“read_count”是缓冲区中数据元素的总数。

用户必须在将“read_activate”设置为低之前读取所有数据

 

/* Module: ppfifo_sink
 *
 * Description: Whenever data is available within the FIFO activate it and read it all
 */

module ppfifo_sink #(
  parameter                       DATA_WIDTH    = 8
)(
  input                           clk,
  input                           rst,

  //Ping Pong FIFO Interface
  input                           i_rd_rdy,
  output  reg                     o_rd_act,
  input       [23:0]              i_rd_size,
  output  reg                     o_rd_stb,
  input       [DATA_WIDTH - 1:0]  i_rd_data
);

//Local Parameters
//Registers/Wires
reg   [23:0]          r_count;
//Submodules
//Asynchronous Logic
//Synchronous Logic
always @ (posedge clk) begin
  //De-Assert Strobes
  o_rd_stb            <=  0;

  if (rst) begin
    o_rd_act          <=  0;
    r_count           <=  0;
    o_rd_stb          <=  0;
  end
  else begin
    if (i_rd_rdy && !o_rd_act) begin
      r_count         <=  0;
      o_rd_act        <=  1;
    end
    else if (o_rd_act) begin
      if (r_count < i_rd_size) begin
        o_rd_stb      <=  1;
        r_count       <=  r_count + 1;
      end
      else begin
        o_rd_act      <=  0;
      end
    end
  end
end
endmodule

 

下面设计一个简单的测试模块来演示 Ping Pong FIFO。

源代码地址:

https://github.com/CospanDesign/verilog_ppfifo_demo

下面是几个简单模拟的截图:

数据

在读事务开始和下一个写事务开始时放大仿真区域:

数据

在读取事务之间放大

数据

半放大

数据

截图可能不清晰,建议自己仿真。

总结

PPFIFO除了上面用于解决FIFO的“痛处”外,常见的还是处理高速数据流处理,下面是一个10M数据流分成两个5M数据流的例子。  

      审核编辑:彭静

 

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

全部0条评论

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

×
20
完善资料,
赚取积分