01signal.com

FIFO 和 EOF 用于防御 overflow

本页是有关 FIFO 的五页系列中的第四页。本页提出了一种应对可能发生 overflow 的方法。

介绍

在许多使用 FIFO的应用程序中,无法控制到达的数据流。例如,在 data acquisition 应用中, logic 将捕获的数据直接写入 FIFO。如果 FIFO 已满,该数据就会丢失。在此类应用中, FIFO 的读取器负责足够快地消耗 FIFO 中的数据,以便 FIFO 永远不会变满。

但是如果 FIFO 变满了,就会因为数据丢失而破坏数据的连续性: FIFO 忽略写入尝试。结果 FIFO 的内容不正确。这种情况称为 overflow。

overflow 是某种故障的结果,应该避免。然而,如果发生 overflow ,检测此事件并减轻损害仍然很重要。下面建议了一个策略。

改装的 FIFO

主要努力应该始终是防止 overflow 的发生。但如果失败,剩下的就是确保 FIFO不会消耗错误的数据。换句话说,从 FIFO 读取的所有数据都是连续且正确的。

建议的解决方案是修改后的 FIFO,它有两点不同:

第一个功能确保从修改后的 FIFO 读取的数据始终是连续且正确的: 连续性破坏后 FIFO 拒绝写入数据。第二个功能 (EOF) 向使用 FIFO的 logic 发送出现问题的消息。这使 logic 有机会重新启动数据流。

EOF 这个名字来自 End of File: 此修改后的 FIFO 对于基于 Xillybus IP core的data acquisition 应用程序非常有用。在此类应用中, Xillybus IP core 将数据传输到计算机。一个简单的计算机程序使用标准 file I/O API 来读取 FIFO中的数据。换句话说,计算机程序以通常的方式打开文件并从中读取数据。 FIFO的 EOF 使该文件的行为与到达常规文件末尾时类似。这是对没有更多数据可供读取这一事实的自然反应。

Verilog中的实现

这是一个 Verilog module 的示例,它实现了上面建议的修改后的 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

很明显,这个 module 由标准 FIFO的 instantiation 以及一些额外的 logic组成。请注意,这款 module的 ports 与标准 FIFO的 ports 几乎完全相同。只有一处不同: 修改后的 FIFO 有一个名为 @eof的 port 。

另请注意, @din 和 @dout 的宽度为 32 bits。如果需要不同宽度的 FIFO ,唯一需要的更改是在 module顶部声明这些 ports 。

改装后的 FIFO 有个 @full port,不一定有用。写入 FIFO 的 logic 可能会忽略这个 port: 如果 FIFO 满了,无论如何也无能为力。不管怎样, FIFO 将忽略以后向其中写入数据的尝试。在大多数应用中,知道 FIFO 已满并没有多大帮助: 最好等到 @eof 变高后再重新启动整个机制。

防止 overflow之后的写操作

正如已经提到的,这款 module 基于标准 FIFO。所有这台 FIFO的 ports 都直接连接到 eof_fifo的 ports,除了一台 port: @wr_en。该 port 改为连接到“wr_en && ok_to_write”。所以很明显, @ok_to_write 是用来在 FIFO 写满后停止写操作的。这个 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 已满时,@fifo_has_been_full 为高电平。当 @full 和 @fifo_has_been_nonfull 都为高电平时, register 变为高电平。

第一部分并不奇怪: @full 连接到 FIFO的 "full" port。但为什么需要 @fifo_has_been_nonfull ?原因是,只要 FIFO 保持在 reset 状态, FIFO 就经常将其“full”保持在高电平。该功能的目的是告诉 application logic FIFO 尚未准备好接收数据。 @fifo_has_been_nonfull 的目的是防止 @fifo_has_been_full 在这种情况下错误地变高。

当 FIFO 复位时,@fifo_has_been_full 变为低电平。换句话说,在发生 overflow 后,重置 FIFO 是修改后的 FIFO 恢复正常操作的唯一方法。请注意, @rst 是 asynchronous reset。因此,有必要添加 logic 才能创建属于正确 clock domain的 reset signal 。这个信号是 @rst_sync,它是 @rst的副本。

生成 EOF

当同时满足以下两个条件时, @eof port 为高电平:

这些条件的 Verilog 代码中的表达式为:

assign eof = empty && has_been_full;

请注意,此表达式基于 @has_been_full 而不是 @fifo_has_been_nonfull: @eof 和 @empty 都属于 @rd_clk的 clock domain。但 @fifo_has_been_nonfull 属于 @wr_clk的 clock domain。因此, @fifo_has_been_nonfull 凭借clock domain crossing被复制到 @rd_clk的 clock domain 中。这个副本是 @has_been_full。

所以 @eof 的意思是“ FIFO 不仅是空的,而且在重置 FIFO之前它不会被填满”。

结论

在许多应用中,不可能保证 overflow 不会发生。如果此事件无法容忍,则可以在发生时减少损害: 策略是让已经写入的数据通过,直到 overflow,然后不再允许写入更多数据。最终 FIFO 将变空。发生这种情况时, FIFO 会报告不再有数据通过 @eof port到达。

该方法保证了从 FIFO读取的数据的连续性: 中间没有因为 FIFO满而导致数据丢失。这使得该方法对于 data acquisition 应用程序非常有用。

本页展示了如何实现基于此策略的修改版 FIFO 。此修改后的 FIFO 可用作标准 FIFO的 drop-in replacement 。唯一重要的区别是使用 FIFO 的 logic 必须注意 @eof port: 当 port 变高时, logic 必须重置 FIFO 并重新启动数据流。

关于 FIFOs的系列文章的第四页到此结束。下一页将展示如何将“standard FIFO”变成 FWFT FIFO ,反之亦然,以及如何改进 timing。

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