01signal.com

Verilog中 single clock FIFOs 的实现

范围

本页是有关 FIFOs的系列五页中的第三页,展示了 baseline single clock FIFO在 Verilog 中的实现。这对于编写 portable 代码很有用,但本页的重点是重申 FIFO 的工作原理。因此,我将展示“标准 FIFO(standard FIFO)”和 FWFT FIFO的实现。但首先我们需要一 dual port RAM ,两 FIFOs 都会使用。

dual port RAM

这就是凭借 inference实现 RAM 的 Verilog 模块。很可能任何综合工具(synthesizer)都能做到这一点,但它可能会生成不希望的 RAM (block RAM 与 distributed RAM),因此可能有必要添加综合工具指令(synthesizer directives)。或者,如果效果最好,也可以使用 FPGA 供应商为 dual port RAM提供的 IP 。

所以这里是模块:

module dualport_ram #(parameter depth = 64,
                      log2_depth = 6,
                      width = 8
                     )
   (
    input                    clk,
    input [(log2_depth-1):0] wr_addr,
    input [(log2_depth-1):0] rd_addr,
    output reg [(width-1):0] rd_data,
    input [(width-1):0]      wr_data,
    input                    rd_en,
    input                    wr_en
    );

   reg [(width-1):0] inferred_ram[0:(depth-1)];

   always @(posedge clk)
     begin
        if (wr_en)
          inferred_ram[wr_addr] <= wr_data;

        if (rd_en)
          rd_data <= inferred_ram[rd_addr];
     end
endmodule

“标准 FIFO”

现在我们准备看看实现“标准 FIFO”(即不是 FWFT FIFO)的模块:

module fifo
  #(parameter depth = 64, // Must equal 2^log2_depth exactly
    log2_depth = 6,
    width = 8
    )
   (
    input  clk,
    input  rst,

    input  wr_en,
    input [(width-1):0] din,

    input  rd_en,
    output [(width-1):0] dout,

    output reg full,
    output reg empty
    );

   reg [log2_depth:0]         next_words_in_ram; // Combinatorial
   reg [log2_depth:0]         words_in_ram;
   reg [(log2_depth-1):0]     rd_addr;
   reg [(log2_depth-1):0]     wr_addr;
   wire                       fetch_data, commit_data;

   assign fetch_data = rd_en && !empty;
   assign commit_data = wr_en && !full;

   always @(*)
     if (commit_data && !fetch_data)
       next_words_in_ram <= words_in_ram + 1;
     else if (!commit_data && fetch_data)
       next_words_in_ram <= words_in_ram - 1;
     else
       next_words_in_ram <= words_in_ram;

   always @(posedge clk)
     begin
        words_in_ram <= next_words_in_ram;
        full <= (next_words_in_ram == depth);
        empty <= (next_words_in_ram == 0);

        if (fetch_data)
          rd_addr <= rd_addr + 1;

        if (commit_data)
          wr_addr <= wr_addr + 1;

        if (rst)
          begin
             empty <= 1;
             full <= 1;
             words_in_ram <= 0;
             rd_addr <= 0;
             wr_addr <= 0;
          end
     end

   dualport_ram
     #(.depth(depth), .log2_depth(log2_depth), .width(width)) dp_ins
       (.clk(clk), .wr_addr(wr_addr),
        .rd_addr(rd_addr),
        .wr_en(commit_data),
        .rd_en(fetch_data),
        .wr_data(din),
        .rd_data(dout)
        );
endmodule

关于“depth”和“log2_depth”参数的唯一重要用法说明在顶部的注释中: depth 必须等于 2log2_depth

这个模块的操作非常简单: @fetch_data 类似于 @rd_en,但它考虑了 @empty 。因此, @fetch_data 是 @rd_en的安全版本,即使 @rd_en 和 @empty 同时为高电平(这是非法的),也不会发生任何不好的事情,因为在这种情况下 @rd_en 会被忽略。

@commit_data 是 @wr_en 的安全版本,同样考虑到 @full 。

@words_in_ram的下一个值 @next_words_in_ram根据 @fetch_data 和 @commit_data (注意 always @(*) 语句)计算为组合函数(combinatorial function)。 @next_words_in_ram 用于通过以下代码片段生成多个寄存器(registers)的值:

always @(posedge clk)
     begin
        words_in_ram <= next_words_in_ram;
        full <= (next_words_in_ram == depth);
        empty <= (next_words_in_ram == 0);
[ ... ]

请注意,此处定义了 @full 和 @empty 。

与 dual-clock FIFO相比, FIFO 如此易于实现的原因在于能够像这样定义 @words_in_ram ,并在 FIFO的两侧使用相同的寄存器(register)。

接下来在代码中,我们更新了 @rd_addr 和 @wr_addr,然后是 @rst的子句。请注意,复位(reset)会导致 @empty 和 @full 都变为高电平,但当复位释放时, @full 将返回低电平。

关于编码风格的说明: 当 @rst 为高时, if statement的 begin-end 子句中的赋值会覆盖之前可能进行的赋值,因此 @rst 确实将所有寄存器重置为其初始值。这不是最常见的编码风格,但当不是所有寄存器都被重置时,它具有明显的优势。这个具体案例并没有展示这种优势,但请参阅此页面

最后,还有 dual port RAM的例化(instantiation)。

FWFT FIFO

本页所示,将“标准 FIFO”转换为 FWFT FIFO非常容易。但它的直接实现允许讨论一些有趣的点,所以这里是:

module fwft_fifo
  #(parameter depth = 64, // Must equal 2^log2_depth exactly
    log2_depth = 6,
    width = 8
    )
   (
    input  clk,
    input  rst,

    input  wr_en,
    input [(width-1):0] din,

    input  rd_en,
    output [(width-1):0] dout,

    output reg full,
    output reg empty
    );

   reg [log2_depth:0]         next_words_in_ram; // Combinatorial
   reg [log2_depth:0]         words_in_ram;
   reg [(log2_depth-1):0]     rd_addr;
   reg [(log2_depth-1):0]     wr_addr;
   reg                        has_more_words;
   wire                       fetch_data, commit_data;

   assign fetch_data = (rd_en || empty) && has_more_words;
   assign commit_data = wr_en && !full;

   always @(*)
     if (commit_data && !fetch_data)
       next_words_in_ram <= words_in_ram + 1;
     else if (!commit_data && fetch_data)
       next_words_in_ram <= words_in_ram - 1;
     else
       next_words_in_ram <= words_in_ram;

   always @(posedge clk)
     begin
        words_in_ram <= next_words_in_ram;
        full <= (next_words_in_ram == depth);
        has_more_words <= (next_words_in_ram != 0);

        if (fetch_data)
          rd_addr <= rd_addr + 1;

        if (commit_data)
          wr_addr <= wr_addr + 1;

        if (fetch_data)
          empty <= 0;
        else if (rd_en)
          empty <= 1;

        if (rst)
          begin
             empty <= 1;
             full <= 1;
             words_in_ram <= 0;
             has_more_words <= 0;
             rd_addr <= 0;
             wr_addr <= 0;
          end
     end

   dualport_ram
     #(.depth(depth), .log2_depth(log2_depth), .width(width)) dp_ins
       (.clk(clk), .wr_addr(wr_addr),
        .rd_addr(rd_addr),
        .wr_en(commit_data),
        .rd_en(fetch_data),
        .wr_data(din),
        .rd_data(dout)
        );
endmodule

首先注意我们有一个新的寄存器,也就是 @has_more_words。将其定义与上面的“标准 FIFO”进行比较,并说服自己 @has_more_words 是 @empty的逻辑非(logical NOT)。

接下来,请注意 @fetch_data 的定义已更改。它现在由 @has_more_words 保护(不足为奇),所以它说

assign fetch_data = (rd_en || empty) && has_more_words;

回想一下, @empty FWFT FIFO上意味着“@dout 无效”。所以这个赋值意味着除了 @rd_en之外,如果输出(output)无效,并且内存阵列中有数据可以读取,请继续执行此操作。那就是 first word的 falling through 。

最后,用于分配 @empty 的逻辑已更改为

always @(posedge clk)
     if (fetch_data)
       empty <= 0;
     else if (rd_en)
       empty <= 1;

这简单地说,如果从内存阵列中读取一个字, @empty 会在下一个时钟周期(clock cycle)上变为低电平,因为现在显然有新的有效数据。但是,如果这没有发生,并且 @rd_en 仍然启用,那么应用逻辑刚刚使用了最后一个可用的单词,因此将 @empty 更改为高电平。请注意,如果 @rd_en 为高而 @fetch_data 为低,则 @has_more_words 肯定为低(请参阅上面 @fetch_data 的分配)。这就是为什么这个条件相当于读取 FIFO中的最后一个字。

关于 FIFOs的本系列的第三页到此结束。下一页显示如何调整“标准 FIFO”以在数据采集(data acquisition)应用程序中使用。

此页面由英文自动翻译。 如果有不清楚的地方,请参考原始页面
Copyright © 2021-2024. All rights reserved. (38a9d8fd)