01signal.com

Mise à jour à distance (Remote Update) avec la reconfiguration partielle sous Vivado

Ceci est le dernier article d’une série de quatre consacrée à la reconfiguration partielle (Partial Reconfiguration), ou Dynamic Function eXchange (DFX), avec Vivado de Xilinx. Il s’adresse principalement à ceux qui souhaitent utiliser la reconfiguration partielle dans un scénario de mise à jour à distance (Remote Update). Cet article est rédigé en supposant que vous avez déjà lu les trois précédents.

Introduction

Après avoir abordé la reconfiguration partielle en général, puis la procédure habituelle de Vivado à cet effet, cet article jette les bases de l’utilisation de cette technique pour la mise à jour à distance de la logique du FPGA.

Le principal problème de ce scénario d’utilisation est que le flux binaire partiel (bitstream) doit être compatible avec le flux binaire initial qui a été généré peut-être des années auparavant. Il faut donc que l’implémentation parente d’origine soit disponible lors de l’implémentation de la logique reconfigurable, ou qu’elle soit régénérée pour produire exactement le même résultat — c’est-à-dire avec exactement le même placement et le même routage (place and route).

Il est possible de conserver intact tout le projet Vivado afin d’éviter de relancer l’implémentation parente. Il est aussi possible d’obtenir exactement le même résultat si cette implémentation est répétée. Il est toutefois difficile de garantir la possibilité de publier des flux binaires partiels à l’avenir en s’appuyant sur l’une ou l’autre de ces méthodes.

Il existe une solution fiable à ce problème, mais pour comprendre comment elle fonctionne, il faut d’abord être familier avec les DCP et les OOC. Une brève introduction à ces deux sujets suit.

Le point de contrôle de conception (DCP)

Rappelons que Vivado réalise l’implémentation d’un design FPGA au moyen de plusieurs runs de conception (design runs), généralement nommés synth_1, impl_1, ainsi que d’autres runs classés comme runs de module hors contexte (Out-of-Context, OOC).

Chacun de ces runs consiste à exécuter un script Tcl, qui crée un « projet en mémoire » temporaire, charge les fichiers de design, définit des propriétés et des attributs, puis appelle des fonctions Tcl pour réaliser la synthèse, le placement, le routage, la génération de flux binaires et d’autres tâches.

Ce projet en mémoire ne crée aucun fichier sur le disque et n’a rien à voir avec le projet Vivado visible dans l’interface graphique. C’est un objet en mémoire qui permet d’enchaîner de nombreuses opérations avec des commandes Tcl.

Au fur et à mesure de l’implémentation, des fichiers de point de contrôle de conception (Design Checkpoints, DCP) sont écrits sur le disque (par la commande Tcl write_checkpoint). Le contenu d’un fichier DCP est un instantané du projet en mémoire. Autrement dit, c’est une base de données qui reflète les fichiers de design chargés et les opérations effectuées sur le projet depuis leur chargement.

Par exemple, le run de synthèse (généralement nommé synth_1) peut charger tous les fichiers HDL, les fichiers de contraintes et les fichiers d’IP, puis utiliser une commande Tcl appelée synth_design pour effectuer la synthèse des fichiers HDL. Le résultat est une grande netlist unique issue de toutes ces sources (y compris les IP). Notons toutefois que la netlist est stockée en mémoire, dans le cadre du projet en mémoire. Le script Tcl du run de synthèse se termine donc par un appel à write_checkpoint afin de créer un fichier DCP, qui est le produit de la synthèse. Cela conclut le run synth_1.

En réalité, le DCP généré par synth_1 est appelé une netlist DCP, même s’il contient généralement aussi des contraintes provenant des fichiers XDC.

Le run d’implémentation qui suit, généralement appelé impl_1, crée un nouveau projet en mémoire et lit cette netlist DCP (entre autres) comme point de départ des opérations ultérieures. Ce run écrit plusieurs fichiers DCP, chacun étant un instantané du projet après une étape de traitement (par exemple optimization, placement, optimisation physique, routage, etc.).

Tous les runs, y compris synth_1, peuvent charger des DCP dans leur projet en mémoire. C’est d’ailleurs ce qu’ils font habituellement.

Les runs de module hors contexte (OOC)

Dans Vivado, les IP sont généralement configurées à l’aide d’un outil graphique. Un script génère alors des fichiers sources (principalement des fichiers HDL et des contraintes) et effectue une synthèse de ceux-ci. Le résultat est une netlist DCP, qui est ensuite chargée dans le run de synthèse et les runs d’implémentation du projet principal. Cela réduit le temps nécessaire à l’implémentation complète du projet, en évitant de devoir régénérer les sources des IP et refaire leur synthèse en permanence.

Le run de Vivado qui prend les produits bruts (les informations de configuration d’une IP, des fichiers HDL, ou ce qu’il y a) et les transforme en netlist DCP est appelé run hors contexte (Out-Of-Context) dans la terminologie de Vivado. Ce terme n’est utilisé qu’en relation avec Vivado et n’a pas, à ma connaissance, d’autre usage. C’est d’ailleurs un nom assez mal choisi.

Cette netlist DCP est chargée par le run de synthèse et les runs d’implémentation, généralement par l’intermédiaire d’une commande Tcl, read_ip, qui revient en pratique à charger le DCP généré par le run OOC.

Vivado permet aussi de sélectionner un module HDL du projet principal et de demander que sa synthèse soit réalisée en tant qu’OOC (clic droit sur la source dans l’arborescence du Project Manager, puis « Set as Out-of-Context for Synthesis… »).

L’inconvénient des OOC est que le synthétiseur (synthesizer) peut effectuer certaines optimisations à travers les frontières des modules lorsqu’il reçoit tout le design comme un seul projet. Une synthèse séparée par OOC peut donc réduire les performances et gaspiller des ressources.

OOC, DCP et reconfiguration partielle

Lorsqu’un fichier source a été sélectionné comme niveau supérieur d’un module reconfigurable, Vivado crée un run OOC pour la synthèse de ce module et de ses sous-modules. Ce run produit une netlist DCP destinée à l’implémentation concernée. Cela vaut pour l’implémentation parente comme pour les implémentations enfant : le module reconfigurable est toujours représenté par un DCP distinct.

Cette netlist DCP est toutefois utilisée différemment entre l’implémentation parente et les implémentations enfant : la netlist DCP assignée à l’implémentation parente est chargée par synth_1 ainsi que par impl_1, exactement comme le ferait une IP dans une implémentation ordinaire. La reconfiguration partielle n’influence ce processus que par les contraintes de placement imposées par le floorplanning.

L’implémentation enfant, en revanche, n’a pas de phase de synthèse dédiée. Elle mélange plutôt la netlist DCP du module reconfigurable avec le DCP final de l’implémentation parente (c’est-à-dire le DCP après placement et routage). Plus précisément, l’implémentation enfant prend le DCP final de l’implémentation parente, retire la partie de logique reconfigurable, et insère à la place la netlist DCP de son propre module reconfigurable. Un peu comme lorsque l’on retire la pulpe d’un légume pour le farcir, si l’on veut une image.

Et c’est le moment de traduire tout cela en commandes Tcl.

Les rouages des implémentations parent-enfant

L’endroit où regarder pour comprendre comment l’implémentation fonctionne sous le capot est le fichier Tcl portant le nom du projet (avec le suffixe .tcl). Ce fichier se trouve dans le même répertoire que celui où sont créés les fichiers de l’implémentation.

En particulier, le script d’implémentation de l’implémentation enfant est intéressant. Ce n’est pas un hasard si le chapitre 3 (« Vivado Software Flow ») du guide utilisateur correspondant, UG909, montre et explique ce script, même s’il ne le dit pas directement.

Comme on vient de le mentionner, il n’y a rien de très particulier dans l’implémentation du parent, si ce n’est qu’elle repose sur un DCP pour la netlist de son module reconfigurable et que des contraintes de floorplanning sont appliquées. C’est plus ou moins comme n’importe quelle conception hiérarchique.

Mais l’implémentation parente écrit ensuite deux flux binaires plutôt qu’un seul, par des commandes Tcl comme celles-ci :

write_bitstream -force -no_partial_bitfile theproject.bit
write_bitstream -force -cell pr_block_ins pr_block_ins_lpf_partial.bit

puis elle crée le DCP qui sera utilisé plus tard par l’implémentation enfant :

update_design -cell pr_block_ins -black_box
lock_design -level routing
write_checkpoint -force theproject_postroute_physopt_bb.dcp

Rappelons que pendant cette partie, il existe un projet en mémoire, qui a commencé par charger des netlist DCP, puis est passé par le placement, le routage et toutes les autres optimisations. Ces trois lignes Tcl sont exécutées après l’écriture du flux binaire, de sorte que le projet en mémoire est vraiment à son stade final.

C’est le bon moment pour faire un trou et jouer les courgettes farcies : la commande update_design transforme le module reconfigurable en boîte noire (black box). Autrement dit, toute sa logique est retirée, laissant la place à une autre logique.

Ensuite, le placement et le routage du design sont verrouillés à l’aide de la commande lock_design. Après quoi, l’instantané du projet est écrit dans theproject_postroute_physopt_bb.dcp. « bb » signifie évidemment « Black Box ».

La partie pertinente du script de l’implémentation enfant est la suivante :

create_project -in_memory -part xc7k325tffg900-2
set_property design_mode GateLvl [current_fileset]
add_files -quiet .../impl_1/theproject_postroute_physopt_bb.dcp
add_files -quiet .../two_synth_1/pr_block.dcp
set_property SCOPED_TO_CELLS pr_block_ins [get_files .../two_synth_1/pr_block.dcp]
link_design -top theproject -part xc7k325tffg900-2 -reconfig_partitions pr_block_ins
opt_design
write_checkpoint -force theproject_opt.dcp
[ ... ]

et, à partir de ce point, le script se poursuit avec le placement, le routage, etc.

Notez que le script d’implémentation ci-dessus n’utilise que deux sources, et que ce sont deux DCP :

Lorsque l’implémentation se poursuit, le fait que le premier DCP soit verrouillé garantit qu’aucun élément de la logique statique ne bouge. Le placement et le routage du module reconfigurable sont néanmoins effectués normalement, à partir de la netlist DCP.

En passant, s’il y a des IP XCI appartenant au module reconfigurable, une ligne comme la suivante est ajoutée pour chaque IP, entre les deux commandes add_files ci-dessus :

read_ip -quiet .../theproject.srcs/sources_1/ip/blkmem/blkmem.xci

Cela n’a toutefois rien de spécifique à la reconfiguration partielle — c’est ainsi que cela se fait dans toute implémentation.

Juste avant d’écrire les deux flux binaires, l’implémentation enfant vérifie que le design routé qu’elle a obtenu est compatible avec la logique statique de l’implémentation parente, en particulier en ce qui concerne le placement et le routage.

La commande Tcl correspondante ressemble à ceci :

pr_verify -full_check -initial /path/to/impl_1/theproject_postroute_physopt.dcp -additional /path/to/child_1_impl_1/theproject_routed.dcp -file child_1_impl_1_pr_verify.log

Notez que cette commande compare deux fichiers DCP, indépendamment du projet en mémoire. Le DCP routé de l’implémentation parente est comparé au DCP final de l’implémentation enfant. Le résultat de cette comparaison est écrit dans un fichier nommé *_pr_verify.log.

Cette comparaison garantit que le flux binaire partiel est compatible, au sens où il peut être chargé lorsque le flux binaire du parent est déjà chargé dans le FPGA. Elle passe en revue à la fois les éléments logiques de la partition statique et le routage.

pr_verify renvoie un statut d’échec s’il y a une incompatibilité. Si tel est le cas, la création des flux binaires dans le script de Vivado est empêchée. Il est important de garder cela à l’esprit lorsqu’on écrit des scripts d’implémentation personnalisés.

Il n’y a aucune raison que cette vérification échoue, mais si elle échoue, la génération du flux binaire échoue avec un grand nombre d’erreurs du genre « ERROR: [Constraints 18-891] HDPRVerify-08: design check point .../impl_1/theproject_postroute_physopt.dcp places instance ... at site SLICE_X118Y125, yet design check point .../impl_2/theproject_routed.dcp does not. Both check point must have the same static placement result ».

Ces messages d’erreur atteindront probablement la limite de 100, puis seront réduits au silence.

Solution pour le scénario de mise à jour à distance (Remote Update)

Rappelons, comme dit plus haut, que le défi est que le résultat de l’implémentation parente d’origine doit être disponible au moment où l’implémentation de la logique reconfigurable est réalisée comme implémentation enfant.

La solution de force brute consiste à faire une copie de tout le répertoire du projet Vivado, ainsi que de tous les fichiers dont il pourrait dépendre. Lorsqu’un nouveau flux binaire partiel doit être généré, on restaure tous les fichiers, on force l’implémentation parente comme étant à jour, et on crée un flux binaire pour la seule implémentation enfant. Cette méthode est techniquement correcte, mais risque fort de devenir pénible à la longue. Si vous choisissez cette voie, assurez-vous d’exécuter manuellement pr_verify contre le fichier DCP de l’implémentation parente d’origine, car cette méthode ne détectera pas un changement involontaire dans l’implémentation parente.

Il existe deux autres alternatives, fondées sur la connaissance de la manière dont l’implémentation parente et les implémentations enfant interagissent, à savoir par deux fichiers DCP, comme expliqué plus haut. L’avantage évident de ces deux alternatives est que vous savez ce que vous faites.

Le principe derrière les deux alternatives est de garantir que le flux binaire partiel repose sur les deux fichiers DCP qui ont été générés avec le flux binaire initial, et que j’appellerai les DCP de référence (Golden DCPs).

La première alternative consiste à réaliser l’implémentation du flux binaire partiel par un script Tcl en flux sans projet (non-project flow). Concrètement, il s’agit d’exécuter le script que Vivado crée pour l’implémentation enfant, mais en le modifiant pour utiliser les DCP de référence. Plus précisément, il faut modifier les arguments des commandes link_design et pr_verify afin qu’elles s’appuient sur les DCP de référence.

Le principal inconvénient de cette alternative est que l’exécution d’un script Tcl en flux sans projet s’intègre mal à l’interface graphique de Vivado. Il devient donc nettement plus difficile de traiter les messages, d’ouvrir le design implémenté pour l’examiner, etc.

L’astuce des DCP de référence

Idéalement, on pourrait modifier automatiquement le script généré par Vivado pour le run d’implémentation enfant, afin qu’il utilise les DCP de référence. Malheureusement, il ne semble pas y avoir de moyen fiable d’y parvenir.

Vivado permet toutefois de définir des scripts Tcl à exécuter avant et après certaines étapes de l’implémentation. Ces scripts sont exécutés depuis le script du run d’implémentation, de sorte qu’ils ne peuvent pas servir à modifier le script du run lui-même.

Cela ouvre néanmoins la porte à une méthode un peu rustre, qui est la deuxième alternative : l’idée est d’écraser les DCP de l’implémentation parente avec les DCP de référence. Ainsi, l’implémentation enfant se déroule normalement, mais elle repose sur les DCP de référence, indépendamment de ce que l’implémentation parente a pu générer.

L’avantage de cette méthode est que les habitudes de travail habituelles avec Vivado restent inchangées : on modifie la logique reconfigurable, Vivado relance l’OOC pour la synthèse du module reconfigurable, puis l’implémentation enfant pour générer le flux binaire partiel. Comme aucune modification n’est apportée à la logique statique, Vivado n’a aucune raison de lancer les runs associés.

Le script Tcl qui met en œuvre cette méthode de copie des DCP de référence est :

if { [catch {
    set parentimpldir "[ file normalize "../impl_1"]"
    set goldendir "[ file normalize "/path/to/golden"]"

    file copy -force "[file normalize "$goldendir/theproject_postroute_physopt_bb.dcp"]" "$parentimpldir/"
    file copy -force "[file normalize "$goldendir/theproject_postroute_physopt.dcp"]" "$parentimpldir/"
} errmsg ] } {
    send_msg_id golden-reconfig-1 error "Failed to copy golden parent reconfiguration file(s): $errmsg"
    return -code error
}

Ce script suppose que l’implémentation parente se trouve dans le répertoire « impl_1 », adjacent au répertoire de l’implémentation enfant (ce qui est très probable), et que les deux DCP de référence sont stockés dans un répertoire défini à la ligne 3 de ce script.

Faites un clic droit sur le run enfant (par exemple child_0_impl_1) dans l’onglet Design Runs, choisissez « Change Run Settings… » et, dans la boîte de dialogue qui s’ouvre, définissez tcl.pre pour Design Initialization (init_design) avec ce script. Ou, en Tcl, si le script a été enregistré sous le nom golden_pr.tcl :

add_files -fileset utils_1 -norecurse /path/to/golden_pr.tcl
set_property STEPS.INIT_DESIGN.TCL.PRE [ get_files /path/to/golden_pr.tcl -of [get_fileset utils_1] ] [get_runs child_0_impl_1]

L’important, lorsqu’on utilise ce script, est d’ignorer toutes les informations présentées par Vivado au sujet de l’implémentation parente, ainsi que tous les fichiers produits de son côté. Vivado peut ouvrir son interface de design implémenté et afficher ses rapports, mais tout cela peut très bien n’avoir aucun rapport. Cela laisse donc la place à une certaine confusion.

Une seconde source d’agacement possible est que Vivado relance l’implémentation parente de temps en temps en réponse à un changement dans les paramètres du projet, ou même dans le fichier de contraintes. On peut contourner cela en faisant un clic droit sur la ligne correspondante dans l’onglet Design Runs et en choisissant « Force Up-to-Date ». Ce menu n’apparaît que lorsque le run est à l’état Out-of-Date, c’est-à-dire terminé, mais jugé à rafraîchir par Vivado. La commande Tcl correspondante est par exemple :

set_property needs_refresh false [get_runs synth_1]

Quels fichiers conserver avec les DCP de référence

Il va de soi que les DCP de référence doivent être conservés en lieu sûr pour pouvoir générer des flux binaires compatibles à l’avenir. En plus de cela, c’est une bonne idée de compresser tout le projet Vivado dans un fichier .tar.gz / .zip pour une reprise rapide. On peut également, ou en complément, générer une archive du projet avec File > Project > Archive… ou une commande du genre :

archive_project /path/to/theproject.xpr.zip -force -include_local_ip_cache -include_config_settings

Notez toutefois que le fichier projet ainsi que d’autres fichiers d’un projet Vivado peuvent contenir des chemins absolus. Déployer le projet dans un autre répertoire, ou sur un autre ordinateur, peut donc ne pas fonctionner comme prévu. Cela vaut aussi pour l’archive.

La version de Vivado utilisée est inscrite dans tous les fichiers de rapport possibles et dans le fichier projet .xpr, mais une petite note écrite ne fait pas de mal. Il est peut-être prudent de conserver une copie exécutable de cette version de Vivado, même s’il n’y a aucune raison apparente qu’une mise à niveau du logiciel pose problème. Mélanger les DCP de référence d’une version de Vivado avec la netlist DCP de logique reconfigurable d’une autre version fonctionnera probablement sans problème. Mais ce n’est pas quelque chose pour lequel Vivado a été conçu.

Pour résumer, l’ensemble minimal de fichiers est :

Notez que l’utilisation du flux d’effacement sur les FPGA Ultrascale est liée à la logique déjà présente dans le FPGA. C’est pourquoi le flux d’effacement associé au flux binaire initial doit être conservé. Notez aussi que si le flux binaire initial est le résultat d’une implémentation enfant, c’est le flux d’effacement de cette implémentation qu’il faut conserver.

En ce qui concerne la conservation des sources de l’implémentation parente, c’est important parce que ces sources déterminent les connexions entre la logique statique et la logique reconfigurable. Par exemple, si les sources sont modifiées et qu’un port est ajouté à la logique reconfigurable, et que ce port apparaît dans l’instanciation (instantiation), l’ensemble des broches de partition change. La netlist de la logique reconfigurable aura donc des broches externes qui n’apparaissent pas dans le design statique d’origine.

Lorsqu’une telle inadéquation se produit, l’implémentation enfant échoue avec un message d’erreur du genre : « ERROR: [Netlist 29-77] Could not replace (cell 'pr_block_bb', library 'work_pr_block_ins_pr_block_ins_4', file 'NOFILE') with (cell 'pr_block', library 'work', file 'pr_block.edf') because of a port interface mismatch; in strict mode, no extra ports are allowed. 8 ports are missing on the original cell. 5 of the missing ports are: 'thingy[7]' 'thingy[6]' 'thingy[5]' 'thingy[1]' 'thingy[0]' ».

Ce qui échoue en réalité dans ce cas, c’est la commande link_design (voir plus haut), qui assemble la logique statique et la logique combinatoire (combinational logic).

La façon simple et évidente d’éviter ce problème est de ne pas modifier la partie statique du design, ni la liste des ports de la logique reconfigurable.

Il est toutefois tout à fait acceptable de modifier la partie statique du projet : c’est seulement l’instanciation du module reconfigurable qui doit rester la même. Tant que l’implémentation se déroule sans accroc, y compris la vérification finale, il n’y a aucun problème.

Résumé

Même s’il n’existe pas de solution parfaitement lisse pour l’utilisation de la reconfiguration partielle dans un scénario de mise à jour à distance (Remote Update), il existe malgré tout quelques stratégies pour atteindre cet objectif.

Le point important est que, en principe, les deux DCP de référence suffisent pour construire et vérifier un flux binaire partiel pour un projet existant.

Et quelle que soit la non-conventionalité des stratégies présentées ici, gardez à l’esprit que la vérification effectuée par pr_verify est un contrôle complet de la compatibilité entre le flux binaire partiel et la logique statique déjà en place. Tant que le bon DCP de référence est utilisé pour cette vérification et que le test passe, il n’y a rien d’autre à craindre. Sans oublier, bien sûr, que l’implémentation enfant doit satisfaire les contraintes de timing (timing constraints), mais c’est vrai pour toute implémentation d’un design.

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)