01signal.com

带 EOF 的 FIFO:防止溢出

本页是关于 FIFO 的五页系列教程中的第四页。本页介绍一种应对可能发生溢出(overflow)情况的方法。

引言

在很多使用 FIFO 的应用中,根本无法控制到达数据的流动。例如,在数据采集(data acquisition)应用中,采集到的数据被逻辑直接写入 FIFO;如果 FIFO 已经是 full(满),这些数据就会丢失。在这类应用中,FIFO 的读取方必须足够快地消费其中的数据,以使 FIFO 永远不会变 full(满)。

但如果 FIFO 变成了 full(满),数据的连续性就会被破坏,因为发生了数据丢失:FIFO 会忽略后续的写入尝试,因此 FIFO 中的内容是不正确的。这种情况称为溢出。

溢出是某种故障的结果,所以应当尽量避免。然而,如果溢出还是发生了,那么及时检测到这一点并尽量减小损失仍然十分重要。下面提出一种应对策略。

改进型 FIFO

主要工作始终应该是防止溢出发生。但如果这仍然失败了,那么剩下的唯一能做的事,就是确保没有错误的数据被从 FIFO 中读出。换句话说,从 FIFO 读出的所有数据都是连续且正确的。

建议的解决方案是一种改进型 FIFO,它有以下两点不同:

第一个特点保证了从改进型 FIFO 读出的数据始终连续且正确:一旦数据的连续性被破坏,FIFO 就拒绝写入。第二个特点(EOF)则向使用 FIFO 的逻辑发出“出了故障”的信号,使该逻辑有机会重新启动数据流。

EOF 这个名字来自 End of File(文件结束):这种改进型 FIFO 非常适合基于 Xillybus IP 核(IP core)的数据采集应用。在这类应用中,Xillybus IP 核把数据传输到计算机;计算机上的一个简单程序利用标准文件 I/O API 从 FIFO 读取数据。换句话说,该程序以通常的方式打开一个文件并从中读取数据。FIFO 的 EOF 会让这个文件表现得就像读到了普通文件的末尾一样,这是对“再也没有数据可读”这一事实的自然响应。

Verilog 实现

下面是一个 Verilog 模块示例,它实现了上面提出的改进型 FIFO:

module eof_fifo
  (
   input 	   rst,
   input 	   wr_clk,
   input 	   rd_clk,
   input [31:0]    din,
   input 	   wr_en,
   input 	   rd_en,
   output [31:0]   dout,
   output 	   full,
   output 	   empty,
   output 	   eof
   );

   reg 		   rst_sync;
   reg 		   rst_cross;
   reg 		   fifo_has_been_full;
   reg 		   fifo_has_been_nonfull;
   reg 		   has_been_full_cross;
   reg 		   has_been_full;

   assign ok_to_write = !rst_sync && !full && !fifo_has_been_full;
   assign eof = empty && has_been_full;

   always @(posedge wr_clk)
     begin
	if (!full)
	  fifo_has_been_nonfull <= 1;
	else if (rst_sync)
	  fifo_has_been_nonfull <= 0;

	if (full && fifo_has_been_nonfull)
	  fifo_has_been_full <= 1;
	else if (rst_sync)
	  fifo_has_been_full <= 0;
     end

   // Clock domain crossing logic: asynchronous -> wr_clk
   always @(posedge wr_clk)
     begin
	rst_cross <= rst;
	rst_sync <= rst_cross;
     end

   // Clock domain crossing logic: wr_clk -> rd_clk
   always @(posedge rd_clk)
     begin
	has_been_full_cross <= fifo_has_been_full;
	has_been_full <= has_been_full_cross;
     end

   fifo fifo_ins
     (
      .rst(rst),
      .wr_clk(wr_clk),
      .rd_clk(rd_clk),
      .din(din),
      .wr_en(wr_en && ok_to_write),
      .rd_en(rd_en),
      .dout(dout),
      .full(full),
      .empty(empty)
      );
endmodule

可以很明显地看出,这个模块由一个标准 FIFO 的实例化(instantiation)加上一些额外的逻辑组成。请注意,这个模块的端口几乎与标准 FIFO 的端口完全一样,唯一的区别是:改进型 FIFO 多了一个名为 @eof 的端口。

还需要注意 @din 和 @dout 的位宽是 32 位。如果你希望 FIFO 使用不同的位宽,唯一需要修改的就是模块开头的这两个端口声明。

改进型 FIFO 带有一个 @full(满)端口,而这个端口不一定是必需的。向 FIFO 写入数据的逻辑完全可以忽略它:如果 FIFO 确实变 full(满)了,反正你也做不了什么。无论怎样,FIFO 都会忽略之后试图写入的数据。在大多数应用中,知道 FIFO 已经变 full(满)并没有太大帮助:最好是等到 @eof 变高,然后重新启动整套机制。

防止溢出后的写入操作

如前所述,这个模块以标准 FIFO 为基础。除一个端口之外,标准 FIFO 的所有端口都直接连接到 eof_fifo 的端口上,这个例外就是 @wr_en。它被接到了 “wr_en && ok_to_write” 上。因此很明显,@ok_to_write 的作用是在 FIFO 曾经变 full(满)之后停止写入操作。这个 wire 的定义如下:

assign ok_to_write = !rst_sync && !full && !fifo_has_been_full;

从这个表达式可以看出,有三种情况会阻止写入操作:

前两个条件不言自明。让我们把注意力放在第三个条件上,它由 @fifo_has_been_full 来表示:

   always @(posedge wr_clk)
     begin
	if (!full)
	  fifo_has_been_nonfull <= 1;
	else if (rst_sync)
	  fifo_has_been_nonfull <= 0;

	if (full && fifo_has_been_nonfull)
	  fifo_has_been_full <= 1;
	else if (rst_sync)
	  fifo_has_been_full <= 0;
     end

当 FIFO 曾经 full(满)时,@fifo_has_been_full 为高。当 @full(满)和 @fifo_has_been_nonfull 同时为高时,这个寄存器会变为高。

第一部分并不奇怪:@full 连接的是 FIFO 的 “full” 端口。但为什么需要 @fifo_has_been_nonfull?原因在于,FIFO 在复位期间往往会一直保持其 “full” 输出为高,这个特性的目的是告诉应用逻辑:FIFO 还没有准备好接收数据。@fifo_has_been_nonfull 的作用,就是防止在这种情况下把 @fifo_has_been_full 错误地置为高。

当 FIFO 被复位时,@fifo_has_been_full 会变回低电平。换句话说,在发生溢出之后,复位 FIFO 是让改进型 FIFO 恢复正常运行的唯一方法。请注意,@rst 是一个异步复位(asynchronous reset)信号,因此有必要添加逻辑,以生成一个属于正确时钟域的复位信号。这个信号就是 @rst_sync,它是 @rst 的一个副本。

产生 EOF

当下面两个条件同时满足时,@eof 端口为高:

上述条件在 Verilog 代码中表示为:

   assign eof = empty && has_been_full;

注意,这个表达式依赖于 @has_been_full 而不是 @fifo_has_been_nonfull:@eof 和 @empty(空)都属于 @rd_clk 的时钟域,而 @fifo_has_been_nonfull 属于 @wr_clk 的时钟域。因此,@fifo_has_been_nonfull 需要通过一次时钟域跨越(clock domain crossing)被复制到 @rd_clk 的时钟域中,这个副本就是 @has_been_full。

所以 @eof 的含义是:“不仅 FIFO 现在是 empty(空),而且在你复位它之前,它也不会再被填满。”

结论

在很多应用中,不可能保证永远不会发生溢出。如果这种事件不可接受,那么可以在它发生时尽量减小损失:策略是让溢出之前已经写入的数据正常通过,在此之后不再允许新的数据写入。最终 FIFO 会变成 empty(空)。当这一刻到来时,FIFO 通过 @eof 端口报告:“不会再有任何数据到来了”。

这种方法保证了从 FIFO 读出的数据是连续的:不会因为 FIFO 已满而丢失中间部分的数据。因此它很适合数据采集应用。

本页展示了如何实现一个基于这种策略的改进型 FIFO。这种改进型 FIFO 可以直接替换标准 FIFO 使用。唯一重要的区别是:使用 FIFO 的逻辑必须关注 @eof 端口——当这个端口变为高电平时,逻辑必须复位 FIFO,并重新启动数据流。

至此,本系列关于 FIFO 的第四页就结束了。下一页将展示如何把“标准 FIFO”改造成 FWFT FIFO(反之亦然),以及如何改善时序。

本页由机器从英文翻译而来。如有疑问,请参阅原文。
Copyright © 2021-2026. All rights reserved. (dcc38493)