Ceci est le premier article d'une série de quatre consacrée à la reconfiguration partielle (Partial Reconfiguration), ou Dynamic Function eXchange (DFX), avec Vivado de Xilinx. L’objectif de cet article est d’expliquer les concepts principaux de ce sujet. Il pose les bases nécessaires pour l’article suivant, qui décrit les étapes pratiques pour configurer un projet FPGA avec la reconfiguration partielle.
Introduction
La reconfiguration partielle est une technique qui permet de remplacer la logique de certaines parties du FPGA pendant que les autres parties continuent de fonctionner normalement. Concrètement, on charge dans le FPGA un flux binaire (bitstream), exactement comme le flux binaire initial qui programme son fonctionnement à la mise sous tension. Cependant, le flux binaire de reconfiguration partielle ne provoque pas l’arrêt du FPGA. Il agit sur des éléments logiques précis et met à jour les cellules mémoire qui commandent leur comportement. C’est un remplacement à chaud de blocs logiques spécifiques.
Les FPGA de Xilinx prennent en charge cette fonctionnalité depuis les Virtex-4 (et les FPGA d’Intel à partir des Series-V).
Cet article passe en revue les concepts qui se cachent derrière la reconfiguration partielle, sans entrer dans les détails pratiques, en préparation à l’article suivant, qui fait précisément ce travail. Comme tout est lié à tout dans ce domaine, il est important de comprendre le cadre d’ensemble avant de le découper en actions individuelles.
Je réexplique autrement ?
Commençons par voir comment on change le fonctionnement d’un FPGA sans reconfiguration partielle. Imaginons qu’un module soit instancié quelque part dans la hiérarchie du design. Par exemple, en Verilog :
moduleA reconfig_ins
(
.clk(clk),
.this(this_w),
.that(that_w),
[ ... ]
);
ou en VHDL :
reconfig_ins : moduleA
port map(
clk => clk,
this => this_w,
that => that_w,
[ ... ]
);
Évidemment, un module appelé moduleA.v ou moduleA.vhd se trouve quelque part dans le projet, ou alors il s’agit d’une IP appelée moduleA, qui peuple l’instanciation (avec ses sous-modules). On réalise donc une implémentation du projet, on obtient un fichier de flux binaire, et on charge le FPGA avec. Jusqu’ici, c’est la procédure habituelle.
Mais imaginons maintenant que l’on écrive moduleB au lieu de moduleA dans le code ci-dessus, et que l’on effectue une implémentation de la logique pour obtenir un fichier de flux binaire. Pour que cela fonctionne, il faut qu’un moduleB.v ou moduleB.vhd, ou une IP appelée moduleB, existe dans le design.
Nous avons donc deux fichiers de flux binaire, qui diffèrent par la logique située dans l’instance nommée reconfig_ins. Pour passer d’un flux à l’autre, il faut charger l’intégralité du FPGA avec le flux souhaité. Cela implique une interruption du fonctionnement du FPGA.
La reconfiguration partielle est une technique qui permet de passer d’une version à l’autre sans cette interruption : le FPGA continue de fonctionner normalement, pendant que la logique dans reconfig_ins passe de moduleA à moduleB, et inversement. Inutile de préciser que cela n’est pas possible en implémentant simplement les deux designs séparément.
moduleA et moduleB sont appelés modules reconfigurables (RM), ce qui signifie que leur logique peut être injectée dans le FPGA grâce à la reconfiguration partielle.
Motivations
Il y a plusieurs raisons d’utiliser la reconfiguration partielle, notamment :
- Permettre la mise à jour à distance (Remote Update) de la logique du FPGA sans écriture en mémoire flash. Par exemple, si le FPGA est connecté à un ordinateur par PCIe, il est souvent souhaitable de mettre à niveau la logique du FPGA en même temps que le logiciel de l’hôte. Pour garantir que les versions des deux côtés restent synchronisées, il est naturel de stocker le flux binaire du FPGA sur l’ordinateur et de charger le FPGA via l’interface PCIe (voir cette page pour un moyen simple d’y parvenir).
- Pour les grands FPGA, le temps de chargement du flux binaire peut être trop long s’il faut inclure toute la logique. On peut résoudre cela en utilisant des flux binaires compressés et en implémentant dans le flux initial le strict minimum de logique nécessaire. Ainsi, le FPGA commence à travailler rapidement, pour assurer ce qui est absolument indispensable. Le reste de la logique est chargé dans un second temps, par reconfiguration partielle. C’est ce qu’on appelle souvent la configuration en tandem (Tandem Configuration).
- Réduire le coût du FPGA en réutilisant des ressources logiques pour différentes tâches qui n’ont pas besoin de fonctionner simultanément. Par exemple, si le FPGA implémente l’un de plusieurs filtres d’image, seul le filtre actuellement utilisé occupe de la logique FPGA. Quand on a besoin d’un autre filtre, la région du FPGA qui lui est allouée est rechargée, tandis que le reste du FPGA continue de fonctionner normalement.
- Mettre à jour certains éléments logiques par JTAG, par exemple les blocs RAM qui contiennent le code exécutable d’un microprocesseur implémenté sur le FPGA. Cela permet des cycles de développement rapides pour le logiciel.
- Insérer une sonde de données et/ou un outil de débogage dans un design qui fonctionne sur le FPGA. La reconfiguration partielle est particulièrement utile pour les problèmes qui apparaissent et disparaissent d’une implémentation du design à l’autre (ce qui indique un défaut fondamental du design en matière d’horloges, de timing, etc., mais c’est une autre histoire).
Le flux binaire partiel
Si vous avez une certaine expérience de la conception FPGA, vous êtes probablement habitué à une routine simple : faire quelques modifications dans le code source du design (et des IP), lancer les outils d’implémentation, puis vérifier que tout s’est bien passé. Ensuite, on charge le flux binaire dans le FPGA par JTAG. Ou alors, on charge un composant flash avec une image du flux binaire.
Parce que nous en avons tous l’habitude, il est facile de croire que le flux binaire n’est qu’un gros paquet de données qui remplit tout le FPGA d’informations mystiques sur le comportement attendu de chaque élément logique. En réalité, un flux binaire est une suite de commandes exécutées séquentiellement par le FPGA pendant son chargement. Le flux binaire ordinaire charge bien tout le FPGA, mais il le fait à l’aide de plusieurs commandes qui contrôlent le déroulement de l’opération. Un aspect plus important de ces commandes est qu’elles déterminent quels éléments logiques reçoivent chaque donnée.
Puisque le flux binaire indique lui-même les éléments logiques concernés, il est possible de créer un flux qui ne modifie que certains éléments et laisse les autres intacts. C’est la pierre angulaire de la reconfiguration partielle.
Cela dit, le flux partiel doit être compatible avec la logique déjà chargée dans le FPGA. Il ne s’agit pas seulement d’éviter d’écraser les mauvais éléments : le flux initial est étroitement lié au flux partiel, notamment parce que le flux initial utilise des ressources logiques et de routage situées dans la zone modifiée par le flux partiel. Lorsque le flux partiel est correctement établi par rapport au flux initial, cette délicate chorégraphie passe inaperçue. Dans le cas contraire, le FPGA risque fortement de devenir instable, y compris dans la partie qui aurait dû rester intacte.
Chargement du flux binaire partiel
La délivrance d’un flux de reconfiguration partielle au FPGA peut se faire par n’importe quelle interface capable de charger des flux binaires, à condition que l’opération puisse se dérouler pendant que le FPGA fonctionne. Cela inclut l’interface JTAG : un fichier .bit de reconfiguration partielle peut donc être chargé avec le Hardware Manager comme d’habitude. Mais plus intéressant encore, cela peut être fait depuis la logique interne du FPGA, en utilisant le port dédié Internal Configuration Access Port (ICAP). Ce port ne peut être utilisé que pour la reconfiguration partielle, car la partie du FPGA qui charge le flux doit rester intacte pendant tout le processus.
L’ICAP n’est qu’une interface vers le sous-système de chargement des flux du FPGA, et il n’impose rien quant à la source du flux. En conséquence, il n’y a aucune limitation sur la manière dont les données arrivent au FPGA, ni sur l’endroit ou la façon de les stocker. Il faut simplement qu’elles soient disponibles d’une manière ou d’une autre pour la partie de la logique FPGA qui alimente l’ICAP.
Par exemple, Xillybus offre un moyen simple d’envoyer un fichier de flux binaire à l’ICAP depuis un ordinateur via une interface PCIe ou USB 3.x, si la carte en possède une.
La logique statique
Pour bien faire de la reconfiguration partielle, il faut respecter la partie complémentaire : la logique statique. C’est un terme général qui désigne les parties du design FPGA qui doivent rester intactes et qui sont donc présentes depuis le chargement du flux initial.
Cette logique est statique à deux égards. L’aspect fonctionnel, d’abord : la logique comprend les parties du design (HDL et IP) qui doivent fonctionner sans interruption depuis le démarrage initial du FPGA. Le second aspect, tout aussi important, est que le placement de cette logique est limité à des emplacements du tissu logique alloués comme statiques. Sur ces emplacements, aucune modification ultérieure n’est autorisée.
Dans un design réel, il ne suffit pas que la logique statique reste inchangée ; il faut aussi qu’elle continue de fonctionner correctement pendant que d’autres parties du FPGA sont modifiées. Comme il existe presque à coup sûr des connexions entre la logique statique et la logique qui change, c’est au concepteur FPGA de s’assurer que tout se passe bien. Le troisième article de cette série en discute.
Séparation de la logique statique et de la logique reconfigurable
Pour que la reconfiguration partielle soit seulement possible, il faut une séparation stricte entre la logique statique et la logique reconfigurable. En particulier, les éléments logiques physiques du FPGA doivent être séparés, de sorte qu’aucun des emplacements contenant de la logique statique ne soit affecté pendant le chargement du flux binaire.
Pour comprendre ce que cela implique, regardons d’abord ce à quoi nous sommes tous habitués.
Rappelons que le processus d’implémentation FPGA habituel commence par une synthèse du design HDL. Notez que l’instanciation (instantiation) de modules en HDL n’implique aucune séparation entre eux. C’est même l’inverse : le synthétiseur (synthesizer) traite les instanciations comme une description de la façon dont la logique doit fonctionner. En conséquence, il est libre de considérer tout le design comme une seule grande pièce de logique à plat. Les optimisations qui traversent les frontières des modules sont non seulement autorisées, mais souhaitées, et elles sont fréquentes. Par exemple, si un registre du module X se trouve être équivalent à un registre sans aucun rapport dans le module Y, l’un des deux registres est supprimé et le registre restant sert aux deux modules (sauf si on interdit explicitement au synthétiseur de le faire).
Une fois la synthèse du HDL terminée, la netlist synthétisée est mélangée avec celles des IP du design, s’il y en a.
Ensuite, ce gros ensemble d’éléments logiques est placé dans tout le tissu logique du FPGA, et les fils sont routés de manière à satisfaire les contraintes de timing (timing constraints) et les autres objectifs. La logique appartenant à différentes parties du design peut être regroupée dans la même slice, ou répartie dans des zones opposées du FPGA. Même le plus petit changement dans le design peut conduire à un placement radicalement différent. C’est chaotique, mais sans danger, car chaque implémentation est indépendante : on se moque de savoir comment la logique est dispersée dans le tissu logique du FPGA.
Revenons à la reconfiguration partielle : comme on vient de le mentionner, pour que cette fonctionnalité soit seulement possible, il faut une distinction claire entre logique statique et logique reconfigurable. Pour garantir cela, on utilise une technique appelée conception hiérarchique (Hierarchical Design). L’idée est de considérer tout le design comme un ensemble de composants, à l’instar de composants physiques sur un circuit imprimé. D’un côté, chaque composant (c’est-à-dire chaque module instancié) se voit attribuer une certaine zone du tissu logique. Et comme chaque composant doit être séparé, il est logique d’effectuer sa synthèse séparément — tout comme on fabriquerait le composant séparément.
Relions maintenant ce concept à la reconfiguration partielle, ce qui revient à deux grandes différences dans le travail sur le design :
- Planification des zones (floorplanning) : le concepteur FPGA doit allouer explicitement des régions physiques du tissu du FPGA pour la logique reconfigurable. C’est ce qu’on appelle la partition reconfigurable. Le reste est occupé par de la logique statique.
- Synthèse : la synthèse de la logique reconfigurable (et éventuellement des IP associées) est effectuée séparément de celle de la logique statique. Il en résulte des netlists indépendantes pour la logique reconfigurable et pour la logique statique.
Les Pblocks
Dans la terminologie de Vivado, une unité de planification des zones est un Pblock, qui n’est rien de plus qu’un conteneur d’informations dans Vivado. Il existe des fonctions Tcl pour créer un Pblock, y ajouter des cellules logiques, puis y ajouter des groupes (ensembles) de sites logiques du FPGA. Vivado interprète cela comme une contrainte de placement : les cellules logiques ajoutées au Pblock ne peuvent être placées que sur les sites qui lui ont été affectés. Au final, les Pblocks ne sont donc pas différents des autres contraintes du fichier XDC.
Les Pblocks sont souvent définis avec l’interface graphique de Vivado en ouvrant le design synthétisé ou implémenté et en dessinant des régions rectangulaires sur la représentation graphique du FPGA. Cela crée un Pblock qui inclut tous les éléments logiques situés dans le rectangle tracé. Plus précisément, ce ne sont pas tous les types d’éléments logiques qui sont inclus, mais seulement ceux qui se prêtent à la planification des zones (pour cette famille de FPGA). Vivado traduit donc le rectangle en plages d’éléments logiques.
Il est tout à fait possible de définir ces plages manuellement en modifiant le fichier XDC. Il est aussi permis de créer une région constituée de plusieurs rectangles, de sorte que la forme peut être plus complexe qu’un simple rectangle. Cependant, la documentation de Xilinx (UG909) suggère de garder des formes simples pour éviter les difficultés de routage.
Voici un exemple de fichier XDC pour un Kintex-7 :
create_pblock pblock_pr_block_ins
add_cells_to_pblock [get_pblocks pblock_pr_block_ins] [get_cells -quiet [list pr_block_ins]]
resize_pblock [get_pblocks pblock_pr_block_ins] -add {SLICE_X118Y0:SLICE_X153Y99 SLICE_X118Y250:SLICE_X145Y349 SLICE_X0Y0:SLICE_X117Y349}
resize_pblock [get_pblocks pblock_pr_block_ins] -add {DSP48_X5Y100:DSP48_X5Y139 DSP48_X5Y0:DSP48_X5Y39 DSP48_X0Y0:DSP48_X4Y139}
resize_pblock [get_pblocks pblock_pr_block_ins] -add {RAMB18_X4Y0:RAMB18_X6Y39 RAMB18_X4Y100:RAMB18_X5Y139 RAMB18_X0Y0:RAMB18_X3Y139}
resize_pblock [get_pblocks pblock_pr_block_ins] -add {RAMB36_X4Y0:RAMB36_X6Y19 RAMB36_X4Y50:RAMB36_X5Y69 RAMB36_X0Y0:RAMB36_X3Y69}
L’image ci-dessous montre ce que cela donne dans le design implémenté. La partition reconfigurable (nommée pblock_pr_block_ins, qui ne contient presque aucune logique dans cet exemple) est dessinée en violet. Sa forme est créée comme une union de trois rectangles (les trois plages listées dans chaque commande resize_pblock ci-dessus).
Sur ce dessin, toute la logique placée est en cyan. La grande majorité de la logique appartient à la région statique, et son confinement dans une petite zone est évident.
Notez que seules les slices, les RAM et les DSP48 sont limités par ces contraintes. Ce sont les seuls types de logique que la reconfiguration partielle peut contrôler sur les FPGA de la série 7. Tout le reste, c’est-à-dire en pratique tout ce qui n’est pas de la « logique pure », doit appartenir au design statique.
Avec les FPGA Ultrascale et plus récents, pratiquement n’importe quelle logique peut être rechargée par reconfiguration partielle.
Il existe des restrictions supplémentaires sur les Pblocks, mais il n’y a aucun intérêt à répéter ici les chapitres 6 à 8 d’UG909.
Quoi qu’il en soit, c’est une bonne idée d’ouvrir un design implémenté, de zoomer et dézoomer sur la vue du FPGA, et d’observer comment les éléments logiques sont organisés dans le FPGA. En particulier, on remarque des colonnes de logique du même type. Parfois, quelques éléments au milieu des colonnes cassent l’uniformité (en particulier les éléments logiques spéciaux, comme le bloc ICAP, les blocs PCIe, etc.).
Il vaut la peine de mentionner que le sujet des Pblocks n’est pas spécifique à la reconfiguration partielle. Par exemple, tout ce qui est dit dans cette section s’applique aussi à l’utilisation de Pblocks pour la conception hiérarchique.
Encore un peu de floorplanning
Et voici quelques faits contre-intuitifs : même si la représentation graphique du floorplanning consiste en formes dessinées sur la carte du FPGA, elle ne s’applique qu’aux types de logique contrôlés par les contraintes de placement. Ainsi, même si presque toutes les slices du FPGA sont allouées à la reconfiguration partielle, des îlots d’autres éléments logiques complètement entourés par ces slices peuvent très bien appartenir à la logique statique. Par exemple, il n’y a aucun problème si le bloc ICAP lui-même se trouve au milieu d’un rectangle affecté à la reconfiguration partielle.
Ce n’est pas si étrange, étant donné que le flux de reconfiguration partielle est destiné à certains éléments logiques et laisse les autres intacts. Mais qu’en est-il du routage ? Si un bloc ICAP est coincé au milieu de la logique reconfigurable, comment les fils sont-ils tirés jusqu’aux slices de logique statique ?
Ceci nous amène au deuxième fait contre-intuitif : le routage du design statique utilise des ressources situées à l’intérieur de la région reconfigurable. Ce routage reste stable pendant toute la reconfiguration partielle, sinon ce ne serait pas de la logique statique. Ainsi, à l’intérieur de la région reconfigurable, le routage de la logique reconfigurable change, mais le routage de la logique statique reste inchangé. S’il y a quelque chose qui ressemble à de la magie dans tout ce sujet, c’est bien ce petit fait. C’est aussi la raison pour laquelle l’utilisation d’un flux partiel incompatible avec la logique statique risque de perturber complètement le FPGA.
La réciproque n’est évidemment pas vraie : la logique reconfigurable n’utilise aucune ressource en dehors de celles qui sont explicitement listées dans l’exemple XDC ci-dessus. Quant aux ressources de routage, rien dans la région statique ne changera pendant le chargement d’un flux de reconfiguration partielle ; en ce sens, la logique reconfigurable n’influence jamais la région statique. Enfin, presque : si la forme de la région reconfigurable n’est pas un simple rectangle, Vivado peut laisser le routage sortir de la région reconfigurable. Cela n’arrive que sur les FPGA Ultrascale, dans le but d’améliorer le routage.
Ce qui devrait être clair à ce stade, c’est que les règles du floorplanning ne sont pas simples. La bonne nouvelle, c’est que Vivado produit des avertissements critiques (Critical Warnings) assez informatifs lorsqu’une de ces règles est violée. Trouver le bon floorplan par essais et erreurs est donc une façon raisonnable de travailler.
Implémentations parente et enfant
En ce qui concerne l’implémentation de la logique reconfigurable, il est important de garder à l’esprit que chaque chemin (path) dans le FPGA doit satisfaire les contraintes de timing, et que cela doit être vrai avant et après le chargement de la logique reconfigurable. Une implémentation de la logique reconfigurable séparée de la logique statique est donc impossible. L’implémentation est toujours réalisée sur l’ensemble du FPGA, pour chaque module reconfigurable. Les contraintes de timing (et les autres contraintes) sont appliquées à chaque implémentation.
Pour clarifier ce point, revenons à l’exemple avec moduleA et moduleB ci-dessus. Dans cet exemple, Vivado réalise l’implémentation du design complet avec moduleA, puis refait la même chose avec moduleB. En sous-produit, on obtient des fichiers de flux binaire complets pour chacune de ces deux options.
Il vaut la peine d’insister : toutes les implémentations produisent un flux binaire initial complet et aussi un flux partiel. Cela est vrai pour toutes les implémentations, qu’elles soient parentes ou enfants. Ainsi, à la mise sous tension du FPGA, on peut le charger avec n’importe lequel de ces flux initiaux.
Pour qu’il soit possible de passer de moduleA à moduleB par reconfiguration partielle, tout dans la partition statique doit être exactement le même. Cela inclut la logique elle-même, le placement et le routage. Pour y parvenir, Vivado réalise l’implémentation pour un scénario (par exemple avec moduleA) comme implémentation parente (Parent Implementation). Il réalise ensuite des implémentations enfant (Child Implementation) pour tous les autres scénarios (par exemple avec moduleB). La manière exacte de procéder est détaillée dans le dernier article de cette série, mais pour faire court :
Vivado commence par exécuter l’implémentation parente pour moduleA comme pour une conception hiérarchique ordinaire. Cela signifie que la synthèse de la logique statique et celle de la logique reconfigurable sont faites séparément, et que les contraintes de floorplanning imposent des placements dans des sites distincts du FPGA. Hormis ces deux différences, une implémentation classique est réalisée. En particulier, le placement et le routage sont effectués pour obtenir des résultats optimaux sur ce scénario précis (même si les contraintes de floorplanning et la synthèse séparée peuvent donner des performances sous-optimales).
L’étape suivante consiste à réaliser l’implémentation enfant pour moduleB. Il n’y a pas besoin de synthétiser le design statique, puisque cela a déjà été fait pour l’implémentation parente. Seule la logique reconfigurable est donc synthétisée.
L’implémentation est ensuite réalisée de la même manière que l’implémentation parente, avec une différence cruciale : le placement et le routage (place and route) de toute la logique statique sont forcés d’être identiques au résultat de l’implémentation parente. Compte tenu de cette limitation, le placement et le routage de la logique reconfigurable sont effectués pour des résultats optimaux.
La clé des relations entre parent et enfant est donc qu’une implémentation enfant commence là où l’implémentation parente s’est arrêtée, mais remplace la logique reconfigurable par sa propre logique. L’implémentation enfant se poursuit ensuite normalement, sans toucher à quoi que ce soit dans la zone de la logique statique.
Comme toutes les implémentations enfant doivent s’adapter aux placements et aux routages de la logique statique, il peut être plus difficile d’atteindre les contraintes de timing que dans une implémentation ordinaire du design. Il y a en réalité deux obstacles :
- La séparation hiérarchique du design entre logique statique et logique reconfigurable empêche les optimisations à travers leurs frontières.
- Le placement et le routage de la logique statique ne sont pas nécessairement optimaux pour la logique reconfigurable.
Il faut garder cela à l’esprit lorsqu’on choisit lequel des modules reconfigurables doit servir pour l’implémentation parente. Par exemple, ce peut être le module pour lequel il est le plus difficile d’atteindre les contraintes de timing. Ou le module qui, par ses connexions avec la logique statique, représente le mieux les autres modules. Ou encore, à l’inverse : un module reconfigurable qui ne contient en pratique aucune logique (une « boîte grise »), afin d’obtenir une implémentation neutre de la logique statique.
Quant à l’utilisation des flux binaires, le paradigme de Vivado pour l’implémentation d’un projet avec reconfiguration partielle est que l’implémentation se termine lorsque tous les flux sont à jour et mutuellement compatibles. Autrement dit, n’importe lequel des flux initiaux des implémentations peut être utilisé pour charger le FPGA au départ. Ensuite, n’importe lequel des flux partiels des implémentations peut être chargé.
Ainsi, la première commande « Generate Bitstream » lance l’implémentation parente et toutes les implémentations enfant. Dans les compilations suivantes, Vivado n’exécute que les runs qui nécessitent une mise à jour, comme d’habitude.
L’assistant Dynamic Function eXchange
Le but de cet assistant (Wizard), que l’on peut lancer depuis le menu Tools, est de définir l’implémentation parente et les implémentations enfant, et en particulier quelle implémentation contient quel module reconfigurable.
Il est plus facile d’expliquer cet assistant en regardant les commandes Tcl qu’il génère lorsqu’on ajoute une implémentation enfant :
create_reconfig_module -name bpf -partition_def [get_partition_defs pr ]
add_files -norecurse /path/to/pr_block1.v -of_objects [get_reconfig_modules bpf]
create_pr_configuration -name config_2 -partitions [list pr_block_ins:bpf ]
create_run child_0_impl_1 -parent_run impl_1 -pr_config config_2 -flow {Vivado Implementation 2020}
Je vais parcourir cette séquence Tcl à l’envers, de la dernière ligne à la première :
Dans la dernière ligne, un run d’implémentation enfant est créé. Ce nouveau run est nommé « child_0_impl_1 » et son run parent est choisi comme étant « impl_1 ». Non moins important, la configuration de ce nouveau run est définie sur « config_2 ».
« config_2 » est définie à la troisième ligne, qui indique que « bpf » est le module reconfigurable destiné à la partition reconfigurable nommée « pr_block_ins ». « pr_block_ins » a été mentionné plus haut, mais qu’est-ce que « bpf » ?
À la première ligne, un module reconfigurable (reconfig_module) est créé et nommé « bpf » — c’est simplement un nom pratique pour désigner ce que fait la logique. La deuxième ligne indique qu’un certain fichier Verilog est ajouté à ce module reconfigurable.
Au total, ces quatre lignes créent une nouvelle implémentation enfant et indiquent que la synthèse d’un certain fichier Verilog est nécessaire pour créer un module reconfigurable. Deux objets de l’environnement Tcl sont également créés : « bpf » et « config_2 ».
Revenons maintenant à l’assistant Dynamic Function eXchange : c’est un outil graphique qui représente les relations entre les sources du design, les modules reconfigurables, les configurations et les runs d’implémentation. C’est simplement un moyen pratique de fournir les informations nécessaires à la génération de commandes Tcl comme celles montrées ci-dessus.
Cet outil peut sembler inutilement compliqué, mais c’est parce que l’exemple est simple. Dans un design réaliste, un reconfig_module a probablement plusieurs fichiers sources, et éventuellement des IP associées. L’interface graphique simplifie donc les choses.
Mais pourquoi la configuration (« config_2 ») est-elle nécessaire ? Pourquoi la connexion entre « bpf » et « pr_block_ins » n’est-elle pas faite dans la commande create_run ? Encore une fois, c’est une question légitime, car cet article se limite à une seule partition reconfigurable. S’il y a plusieurs partitions, une configuration définit quelle partition reçoit quel module reconfigurable ; il est donc logique de donner un nom à chaque combinaison, comme config_*.
S’il y a plusieurs partitions, faut-il vraiment réaliser une implémentation pour chaque combinaison possible de modules reconfigurables ? Cette question n’est pas pertinente pour cette série d’articles, alors n’hésitez pas à passer directement à la section suivante.
Rappelons que l’implémentation de Vivado porte sur tout le design, c’est-à-dire la logique statique et la logique reconfigurable ensemble, et garantit que l’ensemble respecte les contraintes de timing. Par conséquent, s’il y a plusieurs partitions, la façon sûre d’utiliser la reconfiguration partielle est de charger toutes les partitions avec leurs flux partiels issus du même run d’implémentation. Autrement dit, tous les flux partiels ont été créés avec la même configuration (par exemple « config_2 »), et la combinaison de ces flux partiels est donc le résultat d’une implémentation validée par les outils. En particulier, on sait que cette implémentation atteint les contraintes de timing.
Et pourtant, si les modules reconfigurables n’ont aucune interaction mutuelle (c’est-à-dire si tous les ports de niveau supérieur des modules reconfigurables sont connectés à la logique statique, et non entre eux), je ne vois pas ce qui pourrait mal se passer en traitant chaque partition séparément. En effet, Vivado n’a pas explicitement validé le timing du FPGA dans son ensemble si on mélange des flux partiels provenant de runs différents. Mais puisque tous les chemins avec la logique statique satisfont les contraintes de timing, et que la logique statique est exactement la même dans tous les runs, n’est-ce pas suffisant ? La documentation officielle ne semble pas donner d’informations sur ce point.
Routage et broches de partition
Il manque encore une pièce au puzzle : le routage qui connecte la logique statique à la logique reconfigurable. Rappelons que l’implémentation parente réalise le placement et le routage du design de façon optimale pour la logique reconfigurable incluse dans la configuration correspondante. Mais le module reconfigurable de l’enfant doit ensuite s’insérer dans la même partition reconfigurable et se connecter au design statique. Au moins une partie du routage appartient à la logique statique et ne peut donc pas changer.
C’est ici qu’interviennent les broches de partition (partition pins). Conceptuellement, on peut voir la logique reconfigurable comme un composant physique, et les broches de partition comme les broches métalliques qui le connectent au circuit imprimé.
En réalité, les broches de partition sont simplement des positions dans le système de coordonnées des ressources de routage du FPGA. Ce sont les endroits où le routage de la logique statique se termine et où celui de la logique reconfigurable continue. Leur seule importance vient du fait que l’implémentation parente et les implémentations enfant s’accordent sur leur emplacement.
Aucune ressource physique comme des LUT ou des bascules n’est nécessaire pour établir ces points d’ancrage, et ils n’ajoutent pas de délai de routage. Le segment de routage qui mène aux broches de partition et en repart crée évidemment un délai, mais les broches elles-mêmes n’ajoutent aucun délai.
Les positions des broches de partition sont choisies automatiquement par les outils pendant l’implémentation parente, et les implémentations enfant sont obligées de s’y adapter. Autrement dit, le routage entre la logique statique et la logique reconfigurable commence là où l’implémentation parente l’a décidé, et l’implémentation enfant ne peut que faire de son mieux à l’intérieur de la partition reconfigurable. Il peut arriver que certaines broches de partition soient placées à des endroits défavorables pour la logique reconfigurable de l’enfant, ce qui peut rendre difficile l’atteinte des contraintes de timing.
Les broches de partition sont souvent regroupées quelque part près du périmètre de la partition reconfigurable. Vivado semble conçu pour choisir des sites qui ne sont pas trop spécialisés pour un design particulier.
Cependant, des broches de partition peuvent se trouver n’importe où dans la partition reconfigurable, si cela était nécessaire pour satisfaire les contraintes de timing pendant l’implémentation parente. Rappelons que le design statique est autorisé à utiliser des ressources de routage situées dans la partition reconfigurable. Il n’y a donc aucun problème à ce qu’une partie du routage statique pénètre dans la partition reconfigurable.
Pour éviter les problèmes de contraintes de timing et de broches de partition, il est bénéfique que les ports de sortie du module reconfigurable soient des registres, et que les entrées soient également échantillonnées (sampling) par des registres. De même, la logique statique a intérêt à utiliser des registres de la même façon. En fait, c’est toujours une bonne idée de suivre cette règle, chaque fois que c’est possible et que cela ne complique pas le design.
La Greybox
Encore une chose à propos de l’assistant DFX : la greybox. Dans la fenêtre Edit Configuration, il est possible d’affecter une greybox comme module reconfigurable, au lieu de l’un des modules reconfigurables habituels. Une greybox est un module factice généré par Vivado. Ses ports correspondent à ceux du vrai module reconfigurable, mais au lieu d’une vraie logique, il y a une LUT pour chaque broche de port. Les LUT générées pour les entrées ne sont connectées à rien à l’autre bout, et les LUT pour les sorties produisent une valeur nulle. Pour les ports vectoriels, une LUT est créée pour chaque bit du vecteur.
Il n’est peut-être pas judicieux d’utiliser une greybox dans l’implémentation parente, car elle facilite trop le travail de placement et de routage. Même si les modules reconfigurables sont très différents les uns des autres, il est probablement préférable d’écrire un module simple qui mette les outils un peu à l’épreuve.
Mais pour créer un fichier de flux initial avec un minimum de logique, une implémentation enfant qui ne contient que des modules greybox peut être utile. Rappelons que toutes les implémentations produisent des flux complets, qui peuvent tous être utilisés comme flux initial, puisqu’ils contiennent exactement la même logique statique.
Flux d’effacement (Ultrascale uniquement)
Ceci ne concerne que les FPGA Ultrascale (pas les Ultrascale+).
Comme mentionné plus haut, toutes les implémentations créent deux flux : un flux pour tout le design, qui peut servir au chargement initial du FPGA, avec le module reconfigurable correspondant inclus. Le second flux est destiné à la reconfiguration partielle avec le même module reconfigurable.
Avec les circuits Ultrascale, il existe un troisième flux, le « flux d’effacement » (clearing bitstream), créé lors de chaque implémentation. Ce flux doit être envoyé au FPGA avant le flux partiel. Notez que le flux d’effacement envoyé au FPGA doit correspondre à la logique actuellement présente dans le FPGA, et non au flux qui va être chargé. Il faut donc suivre l’état courant du FPGA, ce qui n’est pas nécessaire avec les autres familles de FPGA.
Le chargement du flux d’effacement arrête le module reconfigurable, même s’il ne modifie pas réellement la logique. Les ports de sortie de ce module peuvent présenter des valeurs quelconques jusqu’à ce qu’un nouveau flux partiel ait été chargé et démarré.
Selon UG909, charger le flux d’effacement du mauvais module reconfigurable (c’est-à-dire un flux qui ne correspond pas à la logique déjà présente dans le FPGA) peut perturber également la logique statique et provoquer un dysfonctionnement du mécanisme de reconfiguration.
La documentation de Xilinx semble vague sur ce qui se passe si un flux partiel est chargé sans charger d’abord le flux d’effacement. Dans le chapitre 9 d’UG909, on lit d’abord : « Avant de charger un flux partiel pour un nouveau module reconfigurable, le module reconfigurable existant doit être effacé ». On en conclurait donc que le flux d’effacement est obligatoire.
Mais quelques lignes plus bas, le même guide dit : « Si un fichier de clearing n’est pas chargé, les routines d’initialisation (GSR) n’ont aucun effet ». Cela implique qu’il est acceptable de ne pas utiliser du tout de flux d’effacement, s’il est acceptable que tous les éléments synchrones (RAM et bascules, en principe) se réveillent dans un état inconnu. Dans mes propres expériences anecdotiques où j’ai sauté le flux d’effacement, je n’ai vu aucun problème, mais cela ne prouve rien.
Avec les FPGA Ultrascale, il faut donc absolument suivre ce qui est chargé dans le FPGA. On peut par exemple ajouter au module reconfigurable un port de sortie qui présente une valeur constante (un code d’identification) différente pour chaque module reconfigurable. Cela permet à la logique statique d’identifier quel module reconfigurable est actuellement chargé. Un tel code d’identification peut de toute façon être une bonne idée.
Cas d’usage : plugin ou Remote Update
Cette méthode parent-enfant, adoptée par Vivado, est apparemment conçue pour un cas d’usage particulier, que j’appellerai l’usage plugin : réduire le coût du FPGA en chargeant le module reconfigurable dont on a besoin à un instant donné, plutôt que d’avoir en permanence toutes les fonctionnalités possibles dans le FPGA. Par exemple, si le FPGA sert à implémenter plusieurs filtres d’image, la reconfiguration partielle permet d’implémenter chaque filtre comme module reconfigurable et de recharger le FPGA uniquement avec le filtre nécessaire.
Xilinx utilise le terme « Dynamic Function eXchange » (DFX) pour désigner la reconfiguration partielle, ce qui semble refléter l’usage principal prévu pour cette technique.
La méthode parent-enfant fonctionne bien lorsque c’est le but de la reconfiguration partielle. Un kit complet de fichiers de flux est généré. N’importe lequel des flux initiaux peut être utilisé pour initialiser le FPGA, et tous les flux reconfigurables peuvent être utilisés ensuite pour la reconfiguration partielle. Lorsqu’une nouvelle version du projet est publiée, le kit complet, qui comprend tous les fichiers .bit, est remplacé.
Mais il existe un autre schéma d’utilisation, que j’appellerai Remote Update. C’est lorsque la reconfiguration partielle sert de moyen pour des montées de version, peut-être dans un avenir lointain. Dans ce scénario, le flux initial est publié à un moment donné et ne peut plus être modifié par la suite. Plus tard, des flux partiels sont publiés, et ils doivent être compatibles avec le flux initial. Ces publications ultérieures peuvent s’étaler sur plusieurs années.
Pour Remote Update, la méthode parent-enfant peut être difficile à utiliser telle quelle. Même s’il est possible de lancer une implémentation enfant pour obtenir un nouveau flux partiel sans refaire l’implémentation du parent, cela peut devenir difficile à tenir sur la durée. Par exemple, un changement accidentel dans le code source de la logique statique invalide le design du parent, ce qui oblige à refaire l’implémentation parente. Il en résulte que la nouvelle logique statique est incompatible avec l’ancienne, et que les flux partiels qui en découlent ne peuvent pas être utilisés avec le flux initial d’origine.
Donc, si la reconfiguration partielle est envisagée comme une méthode pour mettre à jour progressivement le design FPGA au fil du temps, la procédure d’implémentation demande quelques manipulations. Ce sujet est abordé dans le dernier article de cette série.
Compression des flux binaires
Ce n’est pas directement lié à la reconfiguration partielle, mais on souhaite parfois disposer d’un petit fichier de flux initial, en particulier pour garantir un démarrage rapide du FPGA. Dans ce contexte, la reconfiguration partielle devient un moyen de terminer le processus de mise en route après un démarrage rapide. Cela peut être fait depuis la même source de données (par exemple la flash SPI) ou depuis une source complètement différente (par exemple une interface PCIe).
La compression du flux est autorisée pour le flux initial comme pour les flux partiels.
Voici la ligne à ajouter au fichier XDC pour demander un flux compressé :
set_property bitstream.general.compress true [current_design]
Ceci conclut la partie théorique. L’article suivant montre les étapes pratiques pour configurer un projet afin d’utiliser la reconfiguration partielle.
