01signal.com

FIFO con EOF para protección contra desbordamientos

Esta página es la cuarta de una serie de cinco páginas dedicadas a las FIFO. En esta página se propone un método para hacer frente a la posibilidad de que se produzca un desbordamiento (overflow).

Introducción

En muchas aplicaciones que utilizan una FIFO, no hay posibilidad de controlar el flujo de datos entrantes. Por ejemplo, en una aplicación de adquisición de datos (data acquisition), la lógica escribe directamente en la FIFO los datos capturados. Si la FIFO está llena, esos datos se pierden. En aplicaciones de este tipo, quien lee de la FIFO es responsable de consumir los datos con la suficiente rapidez para que la FIFO nunca llegue a llenarse.

Pero si la FIFO llega a llenarse, la continuidad de los datos se rompe debido a la pérdida de datos: la FIFO ignora los intentos de escritura. Como resultado, el contenido de la FIFO es incorrecto. Esta situación se denomina desbordamiento.

Un desbordamiento es el resultado de algún tipo de mal funcionamiento y debería evitarse. Sin embargo, si se produce, es importante detectarlo y mitigar los daños. A continuación se propone una estrategia para ello.

Una FIFO modificada

El esfuerzo principal debe ser siempre evitar que se produzca un desbordamiento. Pero si eso falla, lo único que queda es asegurarse de que no se consuma ningún dato erróneo de la FIFO. Es decir, que todos los datos que se lean de la FIFO sean continuos y correctos.

La solución propuesta es una FIFO modificada, que se diferencia en dos aspectos:

La primera característica garantiza que los datos que se leen de la FIFO modificada sean siempre continuos y correctos: la FIFO se niega a escribir datos una vez que se ha roto la continuidad. La segunda característica (EOF) transmite el mensaje de que algo ha ido mal a la lógica que utiliza la FIFO. Esto le da a la lógica la oportunidad de reiniciar el flujo de datos.

El nombre EOF viene de End of File (fin de archivo). Esta FIFO modificada resulta útil para aplicaciones de adquisición de datos basadas en el núcleo de IP (IP core) de Xillybus. En una aplicación de este tipo, el núcleo de IP de Xillybus transporta los datos a un ordenador. Un programa informático sencillo utiliza la API estándar de E/S de archivos para leer los datos de la FIFO. En otras palabras, el programa abre un archivo y lee datos de él de la forma habitual. El EOF de la FIFO hace que este archivo se comporte de forma parecida a cuando se alcanza el final de un archivo normal. Es la respuesta natural ante el hecho de que no hay más datos que leer.

Una implementación en Verilog

Este es un ejemplo de módulo Verilog que implementa la FIFO modificada propuesta arriba:

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

Resulta evidente que este módulo consiste en una instanciación (instantiation) de una FIFO estándar, más algo de lógica adicional. Observa que los puertos de este módulo son casi exactamente iguales que los de una FIFO estándar. Solo hay una diferencia: la FIFO modificada tiene un puerto llamado @eof.

Observa también que @din y @dout tienen un ancho de 32 bits. Si se desea una FIFO con un ancho distinto, el único cambio necesario es la declaración de estos puertos al principio del módulo.

La FIFO modificada tiene un puerto @full (llena) que no es necesariamente útil. La lógica que escribe en la FIFO bien podría ignorar este puerto: si la FIFO se llena, no hay nada que hacer de todos modos. De una forma u otra, la FIFO ignorará los intentos posteriores de escribir datos en ella. En la mayoría de las aplicaciones, saber que la FIFO se ha llenado no ayuda demasiado: lo mejor es esperar a que @eof se ponga en alto y reiniciar entonces todo el mecanismo.

Impedir operaciones de escritura después de un desbordamiento

Como ya se ha mencionado, este módulo se basa en una FIFO estándar. Todos los puertos de esa FIFO están conectados directamente a los puertos de eof_fifo, excepto uno: @wr_en. Este puerto está conectado a «wr_en && ok_to_write» en su lugar. Así que es bastante evidente que @ok_to_write se utiliza para detener las operaciones de escritura después de que la FIFO haya estado llena. La definición de esta señal (wire) es:

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

De esta expresión se deduce que hay tres situaciones que impiden las operaciones de escritura:

Las dos primeras condiciones son triviales. Centrémonos en la tercera, representada por @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

La señal @fifo_has_been_full está en alto cuando la FIFO ha estado llena. Este registro pasa a alto cuando tanto @full (llena) como @fifo_has_been_nonfull están en alto.

La primera parte no sorprende: @full está conectado al puerto «full» de la FIFO. Pero ¿por qué es necesario @fifo_has_been_nonfull? La razón es que una FIFO suele mantener su salida «full» (llena) en alto mientras está en estado de reset. El propósito de esta característica es indicar a la lógica de la aplicación que la FIFO todavía no está lista para recibir datos. El propósito de @fifo_has_been_nonfull es evitar que @fifo_has_been_full pase a alto por error en este escenario.

La señal @fifo_has_been_full pasa a bajo cuando se resetea la FIFO. En otras palabras, resetear la FIFO es la única manera de reanudar el funcionamiento normal con la FIFO modificada después de que se haya producido un desbordamiento. Ten en cuenta que @rst es un reset asíncrono (asynchronous reset). Por tanto, es necesario añadir lógica para crear una señal de reset que pertenezca al dominio de reloj (clock domain) correcto. Esa señal es @rst_sync, que es una copia de @rst.

Generación del EOF

El puerto @eof está en alto cuando se cumplen las dos condiciones siguientes:

La expresión en el código Verilog para estas condiciones es:

   assign eof = empty && has_been_full;

Observa que esta expresión se basa en @has_been_full y no en @fifo_has_been_nonfull: tanto @eof como @empty (vacía) pertenecen al dominio de reloj (clock domain) de @rd_clk. Pero @fifo_has_been_nonfull pertenece al dominio de reloj de @wr_clk. Por ello, @fifo_has_been_nonfull se copia al dominio de reloj de @rd_clk mediante un cruce de dominios de reloj (clock domain crossing). Esa copia es @has_been_full.

Así pues, @eof significa «no solo es que la FIFO esté vacía, sino que no se llenará hasta que la resetees».

Conclusión

En muchas aplicaciones es imposible garantizar que no se produzca un desbordamiento. Si este evento no es tolerable, es posible reducir los daños cuando ocurra: la estrategia consiste en dejar pasar los datos que se han escrito hasta el desbordamiento y, a continuación, no permitir que se escriban más datos. Con el tiempo la FIFO se vaciará. Cuando eso ocurra, la FIFO indicará mediante el puerto @eof que no llegarán más datos.

Este método garantiza la continuidad de los datos que se leen de la FIFO: no se pierde ningún dato en medio como consecuencia de que la FIFO se llenara. Esto hace que este método sea útil para aplicaciones de adquisición de datos.

Esta página ha mostrado cómo implementar una FIFO modificada basada en esta estrategia. Esta FIFO modificada puede utilizarse como sustituto directo de una FIFO estándar. La única diferencia importante es que la lógica que utiliza la FIFO debe prestar atención al puerto @eof: cuando este puerto se ponga en alto, la lógica debe resetear la FIFO y reiniciar el flujo de datos.

Con esto concluye la cuarta página de esta serie sobre las FIFO. La siguiente página muestra cómo convertir una «FIFO estándar» en una FIFO FWFT y viceversa, así como la manera de mejorar la temporización (timing).

Esta página se ha traducido del inglés mediante traducción automática. En caso de duda, consulta el texto original.
Copyright © 2021-2026. All rights reserved. (dcc38493)