01signal.com

FIFO with EOF for protection against overflow

This page is the fourth is in a series of five pages about FIFOs. This page suggests a method to cope with the possibility that an overflow may occur.

Introduction

In many applications that use a FIFO, there is no possibility to control the flow of arriving data. For example, in a data acquisition application, the logic writes the data that is captured directly into the FIFO. If the FIFO is full, this data is lost. In applications of this sort, the reader from the FIFO is responsible for consuming the data in the FIFO fast enough, so that the FIFO never becomes full.

But if the FIFO becomes full, the contiguity of the data is broken because of the loss of data: The FIFO ignores the attempts to write. As a result, the content of the FIFO is incorrect. This situation is called an overflow.

An overflow is the result of some kind of malfunction and should be avoided. However, if an overflow occurs, it's nevertheless important to detect this event and to mitigate the damage. A strategy for this is suggested below.

A modified FIFO

The main effort should always be to prevent an overflow from occurring. But if that fails, all that is left is to ensure that no erroneous data is consumed from the FIFO. In other words, that all data that is read from the FIFO is contiguous and correct.

The suggested solution is a modified FIFO, which is different in two ways:

The first feature ensures that the data that is read from the modified FIFO is always contiguous and correct: The FIFO refuses to write data after the contiguity is broken. The second feature (EOF) sends the message that something has gone wrong to the logic that uses the FIFO. This gives the logic the opportunity to restart the data flow.

The name EOF comes from End of File: This modified FIFO is useful for data acquisition applications that are based upon the Xillybus IP core. In an application of this sort, the Xillybus IP core transports the data to a computer. A simple computer program uses the standard file I/O API in order to read the data from the FIFO. In other words, the computer program opens a file and reads data from it in the usual way. The FIFO's EOF makes this file behave similar to when the end of a regular file has been reached. This is the natural response to the fact that there is no more data to read.

An implementation in Verilog

This is an example of a Verilog module that implements the modified FIFO suggested above:

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

It's quite apparent that this module consists of an instantiation of a standard FIFO, plus some extra logic. Note that this module's ports are almost exactly the same as the ports of a standard FIFO. There is only one difference: The modified FIFO has a port named @eof.

Also note that @din and @dout have a width of 32 bits. If a FIFO with a different width is desired, the only necessary change is the declaration of these ports at the top of the module.

The modified FIFO has a @full port, which is not necessarily useful. The logic that writes to the FIFO might as well ignore this port: If the FIFO becomes full, there is nothing to do about it anyhow. This way or another, the FIFO will ignore later attempts to write data into it. In most applications, the knowledge that the FIFO has become full doesn't help much: It's best to wait until @eof becomes high and then restart the whole mechanism.

Preventing write operations after overflow

As already mentioned, this module is based upon a standard FIFO. All of this FIFO's ports are directly connected to eof_fifo's ports, except for one port: @wr_en. This port is connected to "wr_en && ok_to_write" instead. So it's quite obvious that @ok_to_write is used to halt write operations after the FIFO has been full. The definition of this wire is:

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

We learn from this expression that there are three situations that prevent write operations:

The two first conditions are trivial. Let's focus on the third condition, which is represented by @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_has_been_full is high when the FIFO has been full. This register changes to high when both @full and @fifo_has_been_nonfull are high.

The first part is not surprising: @full is connected to the FIFO's "full" port. But why is @fifo_has_been_nonfull necessary? The reason is that a FIFO often holds its "full" high as long as the FIFO is kept in the reset state. The purpose of this feature is to tell the application logic that the FIFO is not ready to receive data yet. The purpose of @fifo_has_been_nonfull is to prevent @fifo_has_been_full from mistakenly becoming high on this scenario.

@fifo_has_been_full changes to low when the FIFO is reset. In other words, resetting the FIFO is the only way to resume normal operation with the modified FIFO after an overflow has occurred. Note that @rst is an asynchronous reset. It is therefore necessary to add logic in order to create a reset signal that belongs to the correct clock domain. This signal is @rst_sync, which is a copy of @rst.

Generating EOF

The @eof port is high when both of the two following conditions are met:

The expression in the Verilog code for these conditions is:

   assign eof = empty && has_been_full;

Note that this expression is based upon @has_been_full and not @fifo_has_been_nonfull: Both @eof and @empty belong to @rd_clk's clock domain. But @fifo_has_been_nonfull belongs to @wr_clk's clock domain. @fifo_has_been_nonfull is hence copied into @rd_clk's clock domain by virtue of a clock domain crossing. This copy is @has_been_full.

So @eof means "not only is the FIFO empty, but it won't get filled until you reset the FIFO".

Conclusion

In many applications it's impossible to guarantee that an overflow will not occur. If this event is not tolerable, it's possible to reduce the damage when it happens: The strategy is to let through the data that has been written until the overflow, and then not allow more data to be written. Eventually the FIFO will become empty. When that happens, the FIFO reports that no more data will arrive by virtue of the @eof port.

This method guarantees the contiguity of the data that is read from the FIFO: No data was lost in the middle as a result of the FIFO's being full. This makes this method useful for data acquisition applications.

This page showed how to implement a modified FIFO that is based upon this strategy. This modified FIFO can be used as a drop-in replacement for a standard FIFO. The only important difference is that the logic that uses the FIFO must pay attention to the @eof port: When this port becomes high, the logic must reset the FIFO and restart the data flow.

This wraps up the fourth page in this series about FIFOs. The next page shows how to turn a "standard FIFO" into a FWFT FIFO and vice versa, as well as how to improve timing.

Copyright © 2021-2024. All rights reserved. (b4b9813f)