01signal.com

Acquisition de données simple avec Xillybus

Vue d’ensemble

Le cas d’utilisation le plus courant de Xillybus est l’acquisition de données (data acquisition). Cette page montre comment vous y prendre pour transmettre des données du FPGA vers l’hôte.

Pour une vue plus détaillée de l’interaction avec Xillybus dans le FPGA, reportez-vous au Guide du concepteur FPGA de Xillybus.

Si l’application a un débit de données élevé, il est également recommandé de lire les recommandations à ce sujet dans le chapitre 5 du guide de prise en main pour Linux ou de celui pour Microsoft Windows.

Du côté de l’hôte

Pour comprendre comment Xillybus fonctionne, le plus simple est de partir du côté de l’ordinateur. Cette commande permet d’effectuer une acquisition de données vers un fichier sur le disque :

$ cat /dev/xillybus_read_32 > capture-file.dat

« cat » est une commande Linux standard qui lit toutes les données d’un fichier et les écrit sur la sortie standard. Dans cet exemple, l’entrée n’est pas un fichier ordinaire, mais un fichier de périphérique. Cette entrée est un flux de données qui arrive du FPGA. La sortie standard est redirigée, si bien que ces données sont écrites dans un fichier sur le disque.

Ce n’est pas un exemple artificiel : dans certains scénarios d’utilisation, c’est la bonne façon d’utiliser Xillybus en pratique pour l’acquisition de données. Il peut être préférable d’utiliser « dd » pour obtenir une quantité précise de données. Le plus souvent, un programme informatique dédié est utilisé pour lire les données depuis le fichier de périphérique. Chaque application a sa manière préférée de consommer les données.

Peu importe donc le langage de programmation que vous préférez, ou que vous utilisiez Linux ou Windows. Le logiciel qui reçoit les données du FPGA n’a qu’à faire comme « cat » : ouvrir un fichier et le lire. Il existe une page séparée qui présente les techniques de programmation standard pour les entrées-sorties sur fichiers. Des informations encore plus détaillées sur les techniques de programmation avec Xillybus figurent dans les guides de programmation pour Linux et pour Windows.

La logique d’acquisition de données

Voyons maintenant ce qui se passe dans le FPGA. Le cœur IP (IP core) de Xillybus et la logique applicative interagissent par l’intermédiaire d’une FIFO : la logique applicative écrit des données dans la FIFO, et Xillybus veille à ce que ces données parviennent à l’hôte. Si ce concept ne vous est pas familier, une page séparée vous explique comment fonctionnent les FIFO.

Le pack de démonstration (demo bundle) de Xillybus contient un fichier nommé xillydemo.v. C’est le code Verilog qui fait l’interface avec le cœur IP. Le pack de démonstration contient aussi un fichier VHDL : xillydemo.vhd. L’exemple ci-dessous est toutefois en Verilog.

L’instanciation (instantiation) du cœur IP de Xillybus se trouve dans xillydemo.v (ou xillydemo.vhd). Voici les éléments qui concernent l’exemple d’acquisition de données avec la commande « cat » ci-dessus (le reste est omis).

   // Wires related to /dev/xillybus_read_32
   wire        user_r_read_32_rden;
   wire        user_r_read_32_empty;
   wire [31:0] user_r_read_32_data;
   wire        user_r_read_32_eof;
   wire        user_r_read_32_open;

[ ... ]

   xillybus xillybus_ins (
[ ... ]
			  // Ports related to /dev/xillybus_read_32
			  // FPGA to CPU signals:
			  .user_r_read_32_rden(user_r_read_32_rden),
			  .user_r_read_32_empty(user_r_read_32_empty),
			  .user_r_read_32_data(user_r_read_32_data),
			  .user_r_read_32_eof(user_r_read_32_eof),
			  .user_r_read_32_open(user_r_read_32_open),
[ ... ]
			  .bus_clk(bus_clk),
[ ... ]
			  );

La signification des ports du cœur IP est décrite en détail dans le guide de Xillybus sur l’API logique.

Il y a une instanciation d’une FIFO dans xillydemo.v. Cette FIFO est une FIFO à horloge unique, ce qui convient à la boucle de retour (loopback) démontrée dans le code Verilog d’origine. Pour une application d’acquisition de données, une FIFO à deux horloges est plus adaptée, car la logique d’acquisition utilise en général sa propre horloge.

Vous pouvez donc transformer xillydemo.v en un module qui effectue l’acquisition de données de la manière suivante : supprimez l’instanciation de la FIFO à horloge unique (elle s’appelle fifo_32x512). Insérez ceci à la place :

   assign user_r_read_32_eof = 0;

   dualclock_fifo_32 fifo_32
     (
      .rd_clk(bus_clk),

      .rst(!user_r_read_32_open),
      .rd_en(user_r_read_32_rden),
      .dout(user_r_read_32_data),
      .empty(user_r_read_32_empty),

      .wr_clk(capture_clk),

      .wr_en(capture_en),
      .din(capture_data),
      .full(capture_full)
      );

La FIFO à deux horloges

dualclock_fifo_32 est une FIFO standard à deux horloges. Il faut la créer comme un cœur IP avec le logiciel de développement du FPGA. Sa profondeur doit être d’au moins 512 éléments. En raison des différences entre les outils de développement FPGA, les noms des ports peuvent différer de ceux montrés ci-dessus. Il devrait néanmoins être facile d’en déduire comment connecter les ports de la FIFO.

Encore une fois, si vous n’êtes pas déjà familier avec les FIFO, il existe une page qui les présente.

Plusieurs ports de la FIFO sont connectés directement au cœur IP de Xillybus : @rd_clk, @rd_en, @dout et @empty (vide). Notez que ces connexions sont réalisées exactement de la même manière que dans le pack de démonstration. Le cœur IP utilise ces quatre signaux pour extraire les données de la FIFO. C’est toujours la bonne façon de connecter ces ports au cœur IP.

Notez que @rd_clk est connecté à @bus_clk. Ce signal vient du cœur IP de Xillybus. En d’autres termes, le cœur IP impose l’horloge utilisée par l’un des côtés de la FIFO.

Quant à @rst, notez qu’il est connecté à !user_r_read_32_open. @user_r_read_32_open n’est à l’état haut que lorsque le fichier de périphérique correspondant est ouvert sur l’hôte. Par conséquent, la FIFO est réinitialisée lorsque le fichier n’est pas ouvert. La FIFO est donc vide lorsque l’hôte ouvre le fichier de périphérique : s’il restait des données dans la FIFO d’une session précédente, elles ont été effacées à la fermeture du fichier.

Ce comportement est généralement celui que l’on attend d’une source de données. Mais si vous souhaitez que la FIFO conserve ses données lorsque le fichier de périphérique est fermé, connectez autre chose à @rst. Vous pouvez éventuellement maintenir @rst à l’état bas.

Notez que @user_r_read_32_eof est à zéro dans cet exemple, tout comme dans le pack de démonstration. Ce signal peut servir à envoyer une fin de fichier à l’hôte. Vous trouverez plus d’informations à ce sujet dans le guide de l’API.

Interface avec la logique applicative

Dans une application d’acquisition de données, il y a toujours une logique applicative d’une certaine sorte qui produit des données à transmettre à l’hôte. Cette partie varie d’une application à l’autre et n’a donc pas d’importance pour cette discussion. Nous allons nous concentrer sur l’envoi de ces données à l’hôte.

Cette partie est étonnamment simple : la logique applicative écrit les données dans la FIFO. Les données écrites dans la FIFO parviennent au programme de l’hôte sous la forme d’un flux continu.

La logique applicative suit donc la convention standard pour écrire dans une FIFO. Dans l’exemple ci-dessus, cela est illustré avec @capture_clk, @capture_en, @capture_data et @capture_full. Cette logique n’a qu’à placer les données sur @capture_data et piloter @capture_en pour écrire correctement dans la FIFO. Notez que la logique applicative utilise sa propre horloge pour écrire dans la FIFO.

Le flux de données

Voici un schéma fonctionnel simplifié qui illustre le flux de données depuis la logique applicative jusqu’au programme applicatif sur l’hôte.

Simplified data flow diagram for data acquisition with Xillybus

Deux détails techniques ont été volontairement omis de ce schéma : le bloc PCIe et le pilote du noyau, car ils n’ont aucun effet sur la perception qu’a l’utilisateur du flux de données. La bonne façon d’utiliser Xillybus est d’oublier ces détails et de se concentrer sur la logique applicative et le logiciel applicatif.

Il n’est pas nécessaire d’organiser les données en paquets : le canal de communication entre la logique applicative et l’ordinateur est un flux continu. Le cœur IP et le pilote veillent à ce que le flux de données se comporte comme les autres protocoles de flux, par exemple les tubes (pipes) entre programmes sous Linux. Un autre protocole au comportement semblable est TCP/IP. En d’autres termes, la quantité de données écrite dans la FIFO n’a pas d’importance : ces données parviendront rapidement au programme applicatif sur l’hôte.

Une erreur courante consiste à organiser les données en paquets et à adapter les tampons DMA du cœur IP à la taille de ces paquets. Cela ne présente aucun avantage. Même si les données sont émises en paquets de taille constante, il n’est pas nécessaire d’adapter le cœur IP à cette taille.

Mais que se passe-t-il si la FIFO devient pleine ?

L’une des règles de base concernant une FIFO est la suivante : si la broche @full (plein) est à l’état haut, @wr_en doit être à l’état bas. Autrement dit : n’écrivez pas dans une FIFO pleine. Alors, que faire si cela arrive ? Gérer une telle situation compliquerait considérablement la logique applicative.

La réponse courte est que la FIFO ne devrait jamais devenir pleine : dans des conditions normales de fonctionnement, cela est activement évité. Le cœur IP lit les données de la FIFO et les copie dans la RAM de l’hôte. Cela se produit assez rapidement pour empêcher la FIFO de se remplir. En général, la FIFO n’a pas besoin d’être plus profonde que 512 éléments.

La FIFO peut toutefois se remplir si la logique applicative écrit trop rapidement. Autrement dit, si le débit moyen des écritures dépasse la limite du cœur IP (telle qu’annoncée pour chaque type de cœur IP), le cœur IP ne pourra pas vider la FIFO assez vite.

Une autre possibilité est que le logiciel applicatif (par exemple « cat » dans l’exemple ci-dessus) ne lise pas assez vite les données du fichier de périphérique. La mémoire tampon dans la RAM de l’hôte finira alors par se remplir, ce qui empêchera aussi le cœur IP de lire la FIFO (car il n’aura plus où écrire les données). Il en résulte un débordement (overflow). Cela peut arriver parce que le logiciel applicatif est mal écrit. Une autre cause possible est liée au système d’exploitation, comme expliqué plus loin.

La taille de la mémoire tampon dans la RAM de l’hôte dépend du cœur IP Xillybus. Par exemple, elle est de 4 Mo pour xillybus_read_32 et xillybus_write_32 (dans le cœur IP fourni avec le pack de démonstration). L’IP Core Factory permet de créer des cœurs IP personnalisés demandant des tampons nettement plus grands.

En conclusion : éviter un débordement est une question de choix des bons paramètres pour le cœur IP. Avant tout, ce cœur IP doit être capable de supporter le débit de données. De plus, la mémoire tampon dans la RAM de l’hôte doit être suffisamment grande. Cela garantit que le flux de données peut continuer même lorsque le programme applicatif ne lit pas les données du fichier de périphérique.

Si la FIFO se remplit malgré tout, la cause habituelle est une erreur dans la conception du système. Une cause fréquente de débordement est une surestimation de la capacité de l’ordinateur à gérer le débit de données.

En particulier, si les données sont écrites dans un fichier sur le disque (comme avec la commande « cat » ci-dessus), le débit maximal peut être plus faible qu’on ne le pense. En effet, le système d’exploitation dispose souvent d’un grand cache disque (peut-être plusieurs Go). Si l’on mesure le débit du disque avec une quantité de données inférieure à la taille du cache disque, les résultats seront trop optimistes : le système d’exploitation fera semblant d’avoir fini d’écrire les données sur le disque alors que ce n’est pas encore le cas. En réalité, les données n’atteignent que le cache, et l’écriture effective sur le disque a lieu plus tard. Cette erreur ne se révèle que lorsqu’on manipule une plus grande quantité de données.

Privation de CPU

Malheureusement, il existe une possibilité inévitable de débordement : le système d’exploitation (Linux ou Windows) a le droit de priver n’importe quel processus de l’espace utilisateur du CPU pendant une durée illimitée. En d’autres termes, le programme qui lit les données peut soudainement cesser de fonctionner pendant un certain temps, puis reprendre son fonctionnement normal. Aucune limite n’est imposée à cette durée. Tout système d’exploitation non temps réel est en droit de mettre en pause des processus de cette manière.

Et pourtant, l’acquisition de données reste possible avec ces systèmes. C’est surtout parce qu’une longue privation du CPU est généralement considérée comme un mauvais comportement. Ces pauses sont donc en général courtes.

Pendant ces pauses, le cœur IP continue de remplir la mémoire tampon dans la RAM de l’hôte (grâce au DMA, l’intervention du processeur n’est pas nécessaire). Lorsque le programme récupère le CPU, il peut rapidement consommer toutes les données accumulées. Une mémoire tampon dans la RAM qui compense une pause de 10 ms suffit en général. Il est cependant possible de demander une mémoire tampon nettement plus grande lors de la création d’un cœur IP personnalisé (custom IP core) à l’IP Core Factory.

Cela dit, il reste possible que la pause soit trop longue. La mémoire tampon dans la RAM se remplira alors, et la FIFO du FPGA se remplira elle aussi. Le résultat de ce débordement sera une perte de données. Cela ne devrait jamais arriver, et n’arrivera probablement jamais. Mais que faire si cela se produit ?

Détection d’un débordement

La solution suggérée consiste à mettre fin au flux de données si la FIFO se remplit : la logique envoie une fin de fichier (EOF) à l’hôte immédiatement après le dernier élément du flux de données sans interruption. Voyons donc ce qui se passe si l’hôte consomme les données avec une commande « cat », comme suggéré ci-dessus :

$ cat /dev/xillybus_read_32 > capture-file.dat

Normalement, cette commande continue jusqu’à ce qu’on l’arrête avec CTRL-C. Mais si la FIFO se remplit dans le FPGA, cette commande se termine normalement, tout comme elle le ferait après avoir fini de copier un fichier ordinaire. Le fichier de sortie contiendra toutes les données collectées avant que la FIFO ne se remplisse.

Pour résumer cette méthode : toutes les données écrites dans capture-file.dat sont garanties sans erreur et forment un flux continu. Si le système d’acquisition ne parvient pas à maintenir cette continuité à cause d’une privation du CPU, le résultat sera un fichier de sortie plus court. Mais on peut se fier au contenu du fichier.

Pour mettre en œuvre cette solution, remplacez l’instanciation de dualclock_fifo_32 par ceci :

   eof_fifo fifo_32
     (
      .rd_clk(bus_clk),

      .rst(!user_r_read_32_open),
      .rd_en(user_r_read_32_rden),
      .dout(user_r_read_32_data),
      .empty(user_r_read_32_empty),

      .wr_clk(capture_clk),

      .wr_en(capture_en),
      .din(capture_data),
      .full(),
      .eof(user_r_read_32_eof)
      );

La définition de eof_fifo est donnée sur une page séparée.

Notez que @user_r_read_32_eof est connecté à la sortie @eof de cette FIFO. C’est ainsi que la logique envoie la fin de fichier à l’hôte lorsque c’est nécessaire. Notez aussi que rien n’est connecté à la broche @full (pleine) de cette FIFO : il n’est plus nécessaire de surveiller ce signal. Si la FIFO se remplit, il n’y a pas grand-chose à faire. Le mécanisme d’EOF garantit que l’hôte relance le flux de données après avoir consommé toutes les données valides.

Lecture de données

Et dans le sens opposé ? Qu’en est-il d’une commande comme celle-ci ?

$ cat playback-data.dat > /dev/xillybus_write_32

Cela fonctionne selon le même principe : la commande « cat » lit les données depuis un fichier sur le disque et les écrit dans un fichier de périphérique. Dans le FPGA, le cœur IP écrit ces données dans une FIFO. La logique applicative lit les données dans la FIFO. Même idée, mais dans le sens inverse.

Voici un schéma fonctionnel simplifié qui illustre le flux de données depuis le programme applicatif sur l’hôte jusqu’à la logique applicative.

Simplified data flow diagram for data playback with Xillybus

Comme pour l’acquisition de données, il n’y a pas de risque de perte de données : le cœur IP n’écrit pas dans la FIFO lorsqu’elle est pleine. Par conséquent, la mémoire tampon dans la RAM de l’hôte peut aussi se remplir. Lorsque cela se produit, le programme applicatif sur l’hôte attend (en se mettant en sommeil) que la logique applicative ait lu suffisamment de données dans la FIFO.

Autre similitude avec l’acquisition de données : la FIFO ne se videra jamais tant que le programme applicatif continue d’écrire des données suffisamment rapidement. Pour garantir cela, les mêmes considérations s’appliquent : le cœur IP, conformément à ses spécifications, ainsi que le programme applicatif doivent pouvoir supporter le débit de données requis.

Lorsque ces conditions sont réunies, la logique applicative peut consommer les données de la FIFO au moment où elle en a besoin. Un sous-écoulement (underflow) ne devrait jamais se produire.

Flux asynchrones et flux synchrones

Ce sujet n’est pas directement lié, mais il mérite quand même une brève discussion.

Dans une application d’acquisition de données, l’objectif principal est de maintenir un flux continu. Le cœur IP déplace donc les données de la FIFO vers la mémoire tampon dans la RAM de l’hôte dès que possible. Peu importe que le programme applicatif sur l’hôte demande les données à cet instant précis (par un appel à read() ou autre) : le flux continue tant que le fichier de périphérique est ouvert et qu’il y a des données dans la FIFO.

Cela signifie que l’hôte n’a aucun moyen de contrôler le flux de données en provenance du FPGA (si ce n’est en ouvrant ou en fermant le fichier de périphérique, ou en ayant recours à une solution spécifique à l’application). Cependant, dans la plupart des applications réelles d’acquisition de données, il n’est pas nécessaire de contrôler le flux : il convient que le flux démarre à l’ouverture du fichier de périphérique. L’instant précis où chaque donnée est lue dans la FIFO n’a pas d’importance.

Un fichier de périphérique qui se comporte de cette manière est appelé un flux asynchrone (asynchronous stream dans la terminologie de Xillybus).

En revanche, dans d’autres applications, l’instant où les données sont collectées a de l’importance. Par exemple, la logique applicative dans le FPGA peut envoyer le contenu d’un registre d’état plutôt que des données issues d’une FIFO. C’est ce que le pack de démonstration illustre avec le fichier de périphérique xillybus_mem_8. Dans ce cas, il est très important de contrôler le moment où les données sont collectées dans le FPGA : l’hôte lit le fichier de périphérique pour obtenir l’état à cet instant, et non l’état qu’il avait à un moment inconnu dans le passé.

Xillybus propose des flux synchrones (synchronous streams) pour ce type d’applications : le cœur IP collecte toujours le moins de données possible depuis le FPGA. En d’autres termes, il ne collecte des données qu’en réponse à un appel à read() (ou équivalent) sur l’hôte. C’est donc l’hôte qui contrôle le moment où les données sont collectées dans le FPGA.

L’inconvénient des flux synchrones réside dans les pauses du flux de données. Le principal problème de ces pauses est que la FIFO dans le FPGA peut se remplir lorsque le flux est momentanément interrompu. Ces pauses réduisent aussi l’efficacité du flux, de sorte que le débit maximal devient plus faible. Ces deux inconvénients ne concernent toutefois que les applications d’acquisition de données. Une telle application doit de toute façon utiliser un flux asynchrone.

Pour les fichiers de périphérique dans le sens opposé, il existe aussi une différence entre flux asynchrones et flux synchrones. Dans ce sens, la différence réside dans la valeur retournée par l’appel à write(). Avec les flux asynchrones, write() retourne dès que les données ont été écrites dans la mémoire tampon en RAM. Dans la plupart des cas, write() ne se met donc pas du tout en sommeil. Avec les flux synchrones, en revanche, write() attend que les données aient été livrées au FPGA. Cela est important lorsque le canal de communication sert à envoyer des commandes. Mais là encore, c’est mauvais pour une application d’acquisition de données.

Dans le pack de démonstration, seul /dev/xillybus_mem_8 est un flux synchrone. Les quatre autres fichiers de périphérique sont des flux asynchrones.

À l’IP Core Factory, le choix entre flux synchrone et flux asynchrone dépend de l’application sélectionnée (le menu déroulant « Use »). Par exemple, si vous choisissez « Data acquisition / playback », l’outil produit un flux asynchrone. Si vous choisissez « Command and status », vous obtenez un flux synchrone. Il est également possible de faire ce choix manuellement, en désactivant « Autoset internals ».

Pour plus d’informations sur les flux asynchrones et les flux synchrones, reportez-vous à la section 2 du guide de programmation pour Linux (ou du guide de programmation pour Windows). En ce qui concerne l’IP Core Factory, consultez le guide pour définir un cœur IP Xillybus personnalisé.

Résumé

Un système d’acquisition de données simple, et pourtant fonctionnel en pratique, peut être réalisé simplement et rapidement avec Xillybus : le logiciel se résume à l’utilisation d’une commande Linux standard (« cat »). Côté FPGA, l’interaction avec le cœur IP de Xillybus se limite à écrire les données dans une FIFO.

Les données collectées avec Xillybus sont garanties sans erreur et continues. Cependant, en raison de la nature du système d’exploitation, il est impossible de garantir qu’un débordement ne se produira jamais. Comme c’est inévitable, la meilleure approche consiste à garantir la détection d’un débordement s’il se produit. Xillybus offre un mécanisme simple à cette fin, en envoyant une fin de fichier (EOF) à l’hôte.

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)