01signal.com

FIFO 和 EOF 用于防御溢出(overflow)

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

介绍

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

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

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

改装的 FIFO

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

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

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

EOF 这个名字来自 End of File: 此修改后的 FIFO 对于基于 Xillybus IP core的数据采集应用程序非常有用。在此类应用中, Xillybus IP core 将数据传输到计算机。一个简单的计算机程序使用标准 file I/O 应用程序接口来读取 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(standard FIFO)的例化(instantiation)加上一些额外的逻辑组成。请注意,这个模块的端口与标准 FIFO的端口几乎完全相同。只有一个区别: 修改后的 FIFO 有一个名为 @eof的端口。

另请注意, @din 和 @dout 的宽度为 32 比特。如果需要不同宽度的 FIFO ,唯一需要的更改是在模块顶部声明这些端口。

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

防止溢出之后的写操作

前面已经提到过,这个模块是基于标准 FIFO的。除了一个端口之外,所有 FIFO的端口都直接连接到 eof_fifo的端口: @wr_en。该端口改为连接到“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" 端口。但为什么需要 @fifo_has_been_nonfull ?原因是,只要 FIFO 保持在复位状态, FIFO 就经常将其“full”保持在高电平。该功能的目的是告诉应用逻辑 FIFO 尚未准备好接收数据。 @fifo_has_been_nonfull 的目的是防止 @fifo_has_been_full 在这种情况下错误地变高。

当 FIFO 复位时,@fifo_has_been_full 变为低电平。换句话说,在发生溢出后,重置 FIFO 是修改后的 FIFO 恢复正常操作的唯一方法。请注意, @rst 是异步复位(asynchronous reset)。因此,有必要添加逻辑才能创建属于正确时钟域(clock domain)的复位信号(reset signal)。这个信号是 @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 不仅是空的,而且在重置 FIFO之前它不会被填满”。

结论

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

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

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

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

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