01signal.com

FIFO avec EOF pour se protéger des débordements

Cette page est la quatrième d'une série de cinq pages consacrées aux FIFO. Elle propose une méthode pour faire face à la possibilité qu'un débordement se produise.

Introduction

Dans de nombreuses applications utilisant un FIFO, il est impossible de contrôler le flux des données qui arrivent. Par exemple, dans une application d'acquisition de données (data acquisition), la logique écrit directement les données capturées dans le FIFO. Si le FIFO est full (plein), ces données sont perdues. Dans ce type d'application, c'est la logique qui lit depuis le FIFO qui est chargée de consommer les données assez rapidement pour que le FIFO ne devienne jamais full (plein).

Mais si le FIFO devient full (plein), la contiguïté des données est rompue à cause de la perte de données : le FIFO ignore les tentatives d'écriture. Par conséquent, le contenu du FIFO est incorrect. Cette situation est appelée un débordement (overflow).

Un débordement est le résultat d'une sorte de dysfonctionnement et doit être évité. Cela dit, s'il se produit, il est important de détecter l'événement et d'en limiter les dégâts. Une stratégie pour cela est proposée ci-dessous.

Un FIFO modifié

L'effort principal doit toujours être d'empêcher qu'un débordement ne se produise. Mais si cela échoue, il ne reste plus qu'à garantir qu'aucune donnée erronée ne soit consommée depuis le FIFO. Autrement dit, que toutes les données lues depuis le FIFO soient contiguës et correctes.

La solution proposée est un FIFO modifié, qui se distingue de deux façons :

La première particularité garantit que les données lues depuis le FIFO modifié sont toujours contiguës et correctes : le FIFO refuse d'écrire des données dès que la contiguïté est rompue. La seconde particularité (l'EOF) transmet à la logique qui utilise le FIFO le message que quelque chose a mal tourné. Cela donne à cette logique l'occasion de relancer le flux de données.

Le nom EOF vient de l'anglais End of File (fin de fichier). Ce FIFO modifié est utile pour les applications d'acquisition de données (data acquisition) basées sur le cœur IP (IP core) Xillybus. Dans une telle application, le cœur IP (IP core) Xillybus achemine les données vers un ordinateur. Un simple programme informatique utilise l'API standard d'entrées-sorties sur fichiers pour lire les données du FIFO. Autrement dit, le programme ouvre un fichier et en lit les données de la manière habituelle. L'EOF du FIFO fait que ce fichier se comporte comme lorsque l'on atteint la fin d'un fichier ordinaire. C'est la réaction naturelle au fait qu'il n'y a plus de données à lire.

Une implémentation en Verilog

Voici un exemple de module Verilog qui implémente le FIFO modifié proposé ci-dessus :

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

On voit clairement que ce module est constitué d'une instanciation (instantiation) d'un FIFO « standard », plus d'un peu de logique supplémentaire. Notez que les ports de ce module sont presque exactement les mêmes que ceux d'un FIFO « standard ». Il n'y a qu'une seule différence : le FIFO modifié possède un port nommé @eof.

Notez aussi que @din et @dout ont une largeur de 32 bits. Si l'on souhaite un FIFO d'une largeur différente, la seule modification nécessaire est la déclaration de ces ports en haut du module.

Le FIFO modifié possède un port @full (plein), qui n'est pas forcément utile. La logique qui écrit dans le FIFO pourrait tout aussi bien ignorer ce port : si le FIFO devient full (plein), il n'y a de toute façon rien à faire. D'une manière ou d'une autre, le FIFO ignorera les tentatives d'écriture ultérieures. Dans la plupart des applications, savoir que le FIFO est devenu full (plein) n'aide pas beaucoup : le mieux est d'attendre que @eof passe à l'état haut, puis de relancer tout le mécanisme.

Empêcher les écritures après un débordement

Comme déjà mentionné, ce module est basé sur un FIFO « standard ». Tous les ports de ce FIFO sont directement connectés aux ports du module eof_fifo, sauf un : @wr_en. Ce port est connecté à « wr_en && ok_to_write » à la place. Il est donc évident que @ok_to_write sert à interrompre les opérations d'écriture après que le FIFO a été full (plein). La définition de ce fil est :

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

Cette expression nous apprend qu'il y a trois situations qui empêchent les opérations d'écriture :

   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 est à l'état haut lorsque le FIFO a été full (plein). Ce registre passe à l'état haut lorsque @full (plein) et @fifo_has_been_nonfull sont tous deux à l'état haut.

La première partie n'a rien de surprenant : @full (plein) est connecté au port « full » du FIFO. Mais pourquoi @fifo_has_been_nonfull est-il nécessaire ? La raison est qu'un FIFO maintient souvent sa sortie « full » à l'état haut tant qu'il est maintenu en état de reset. Le but de cette particularité est d'indiquer à la logique de l'application que le FIFO n'est pas encore prêt à recevoir des données. Le rôle de @fifo_has_been_nonfull est d'empêcher que @fifo_has_been_full ne passe par erreur à l'état haut dans ce scénario.

@fifo_has_been_full repasse à l'état bas lorsque le FIFO est réinitialisé. Autrement dit, réinitialiser le FIFO est le seul moyen de reprendre un fonctionnement normal avec le FIFO modifié après qu'un débordement s'est produit. Notez que @rst est un reset asynchrone (asynchronous reset). Il est donc nécessaire d'ajouter de la logique pour créer un signal de reset qui appartienne au bon domaine d'horloge (clock domain). Ce signal est @rst_sync, qui est une copie de @rst.

Génération de l'EOF

Le port @eof est à l'état haut lorsque les deux conditions suivantes sont réunies :

   assign eof = empty && has_been_full;

Notez que cette expression repose sur @has_been_full et non sur @fifo_has_been_nonfull : @eof et @empty (vide) appartiennent tous deux au domaine d'horloge (clock domain) de @rd_clk. En revanche, @fifo_has_been_nonfull appartient au domaine d'horloge de @wr_clk. @fifo_has_been_nonfull est donc recopié dans le domaine d'horloge de @rd_clk grâce à un changement de domaine d'horloge (clock domain crossing). Cette copie est @has_been_full.

@eof signifie donc : non seulement le FIFO est empty (vide), mais il ne sera pas rempli tant que vous n'aurez pas réinitialisé le FIFO.

Conclusion

Dans de nombreuses applications, il est impossible de garantir qu'un débordement ne se produira pas. Si cet événement n'est pas tolérable, il est possible d'en réduire les dégâts lorsqu'il survient : la stratégie consiste à laisser passer les données écrites jusqu'au débordement, puis à ne plus autoriser d'écritures supplémentaires. Le FIFO finira par devenir empty (vide). Lorsque cela arrive, le FIFO signale, par l'intermédiaire du port @eof, qu'aucune autre donnée n'arrivera.

Cette méthode garantit la contiguïté des données lues depuis le FIFO : aucune donnée n'a été perdue en cours de route parce que le FIFO était full (plein). Cela rend cette méthode utile pour les applications d'acquisition de données (data acquisition).

Cette page a montré comment implémenter un FIFO modifié basé sur cette stratégie. Ce FIFO modifié peut être utilisé en remplacement direct d'un FIFO « standard ». La seule différence importante est que la logique qui utilise le FIFO doit prêter attention au port @eof : lorsque ce port passe à l'état haut, la logique doit réinitialiser le FIFO et relancer le flux de données.

Ceci clôt la quatrième page d'cette série consacrée aux FIFO. La page suivante montre comment transformer un FIFO « standard » en FIFO FWFT et vice versa, ainsi que la manière d'améliorer le timing.

Cette page a été traduite de l’anglais par une machine. En cas de doute, veuillez vous reporter au texte original
Copyright © 2021-2026. All rights reserved. (dcc38493)