M2

CM n°2

Avantages des outils HLS

Côté hardware, il est possible de travailler à un niveau d'abstraction plus élevé tout en créant un hardware de haute performance.

Côté software, il est aisé de tester le logiciel sur une nouvelle plateforme et de l'optimiser. Il est ainsi possible de facilement tester plusieurs implémentations en utilisant des directives de pré-processeur. On peut également vérifier l'exactitude plus rapidement qu'avec un langage traditionnel HDL.

Les outils HLS partent du code source C pour produire du code RTL (Register Transfer Level, qui est une abstraction permettant de représenter les circuits) (e.g. Verilog, VHDL, etc.) et automatise la vérification et l'implémentation des logiciels.

Vivado HLS Design Flow

                                           +---------------+
                                       +---| Bibliothèques |---+
                                       |   +---------------+   |
                                       ∨                       ∨
   +------------------------+       +------------+       +---------+    +-----+
   | Design source (C, C++) |------>| Scheduling |------>| Binding |--->| RTL |
   +------------------------+       +------------+       +---------+    +-----+
                                        ^                      ^
                                        |   +--------------+   |
                                        |   |  Directives  |   |
                                        +---| utilisateurs |---+
                                            +--------------+

Les bibliothèques (technology library) et les directives utilisateurs permettent de faire le lien entre la plannification (scheduling) et l'édition des liens / la liaison (binding).

Les directives sont facultatives mais guident le processus de synthèse vers une optimisation spécifique (UNROLL, PIPELINE, etc.).

Plannification (scheduling)

La plannification permet de déterminer quelles opérations à chaque cycle d'horloge en fonction de :

  1. La longueur du cycle d'horloge ou la fréquence d'horloge.
  2. Durée nécessaire à l'opération (ADD, MULTIPLY, ...) telle que définie par la plateforme.
  3. Directives d'optimisation définies par le développeur.

Lorsque la période d'horloge (inverse de la fréquence) est plus longue, plus d'opérations sont réalisées dans un seul cycle et parfois, toute les opérations peuvent se terminer en un seul cycle d'horloge.

Lorsque la période d'horloge est plus courte, l'outil HLS planifie les opérations sur plusieurs cycles d'horloge et certaines opérations peuvent nécessiter une implémentation en tant que fonctionnement à plusieurs cycles.

Liaison (binding)

La liaison permet de déterminer quelle ressource matérielle doit être utilisé pour une opération planifiée.

Par exemple, quand les cycles d'horloges ne permettent de réaliser qu'une seule opération, la phase de liaison peut décider si elle va utiliser un multiplicateur par multiplication ou d'en avoir un seul partagé pour toutes les multiplications planifiées (puisqu'on peut en réaliser qu'une seule à la fois).

A contrario, quand le cycle d'horloge permet de réaliser plusieurs opérations, la phase de liaison alloue un multiplicateur par multiplication présente dans le cycle.

Synthèse / RTL

Les arguments de la fonction de haut niveau (main j'imagine) définissent les ports d'entrées/sorties d'interface matérielle.

Durant cette phase, chaque fonction est transformée en bloc RTL. Une seule fonction peut être utilisée en tant que fonction de haut niveau pour la synthèse (main).

Durant cette phase, les arguments de la fonction de haut niveau sont synthétisés sous la forme de ports RTL. Ces ports se divisent en 3 classes :

  1. Clock and reset ports

    Un outil HLS définie deux ports de ce type. Un pour l'horloge (ap_clk) et un pour le signal de réinitialisation (ap_rst).

  2. Block-level interface protocol

    Quatre états existent pour connaître le statut du bloc.

    • ap_start : contrôle quand le bloc peut commencer à traiter des données.
    • ap_ready : indique si le bloc est prêt à accepter des entrées.
    • ap_idle : indique si le bloc est actif ou non.
    • ap_done : indique que le bloc a finit le traitement.
  3. Port-level interface protocol

    Ce protocol est créé pour chaque argument de la fonction de haut niveau et pour l'argument de retour s'il y en a un. Les interface disponibles sont :

    • ap_memory : interface BRAM avec des ports pour connaître les données traitées par le bloc, son adresse, etc.
    • ap_hs : interface permettant d'établire la liasion avec les signaux valid et acknowledge lorsqu'il doit y avoir communication entre deux blocs.
    • AXI4 : interface gérant les arguments des fonctions / blocs et permettant de les faire transiter entre eux.

      Lorsque la communication est terminée entre deux blocs, un flux AXI4 envoie une valeur TLAST.

L'outils HLS utilisé permet, une fois la phase RTL finie, de choisir les protocoles d'entrées/sorties à utiliser pour convenir aux éxigences du système souhaité.

Directives dans Vivado

Si jamais ça tombe au DS ¯\_(ツ)_/¯

Les directives peuvent être définies de 2 façons dans Vivado :

Facteur de performance

Le design latency représente le nombre de cycles d'horloges qu'il faut pour parvenir au résultat du programme.

On a temps d'exécution = latency × periode d'horloge

Le design throughput est le nombre de cycles entre les nouvelles entrées.

En l'absence de parallélisme, la latence (latency) et le débit (throughput) sont les mêmes. Les fonctions et les boucles peuvent être parallélisées pour augmenter le débit.

Le initiation interval est le nombre de cycles avant que les nouvelles entrées puissent être appliquées. S'il y a des conditions dans le code, cet interval peut varier au cours de l'exécution du programme.

Dans un système FPGA, la fréquence d'horloge est définie comme le temps de parcours du signal entre les différents registres. Il s'agit du chemin critique (critical path). Pour augmenter la fréquence d'horloge, on peut ajouter de nouveaux registres ce qui permet de réduire l'exigence de synchronisation du circuit (aucune idée de pourquoi).

Partitionnement des tableaux

Puisque l'accés aux tableaux est très coûteux, plusieurs techniques doivent être mises au point pour éviter qu'ils ne causent des problèmes de performance.

Une des techniques principales est de partitionner le tableau en mémoire. Il peut être partitionné de 3 façons différentes.

Par exemple, pour le tableau suivant :

+---+---+---+---+---+
| 1 | 2 | 3 | 4 | 5 |
+---+---+---+---+---+

Sous forme de bloc

Les blocs sont des sections du tableau, de même taille.

+---+---+---+
| 1 | 2 | 3 |
+---+---+---+

+---+---+
| 4 | 5 |
+---+---+

Sous forme cyclique

La taille du cycle peut être définie mais des cycles du tableau sont pris pour en former d'autres plus petits. Par exemple, ici, pour un cycle de 2 :

+---+---+
| 1 | 3 |
+---+---+

+---+---+
| 2 | 4 |
+---+---+

+---+
| 5 |
+---+

Sous forme complète

Chaque élément du tableau est contenu dans un registre de la mémoire, le tout étant éparpillé, à la manière d'une liste chaînée.

+---+                         +---+
| 1 |                         | 4 |
+---+                         +---+
           +---+     +---+
           | 3 |     | 2 |
           +---+     +---+
      +---+
      | 5 |
      +---+

Voir la section "Array partitionning" du diapo pour plus de schémas.

Optimisations de boucles

Déroulement

Gagner en performance est toujours une histoire de compromis. En sacrifiant des ressources matérielles, on peut accélérer l'exécution de code. Dans le cas des boucles, on peut accélérer leur exécution en les déroulants (même si la taille du programme s'en voit impacté car plus d'instructions sont présentes).

Les boucles peuvent être complètement déroulées : le corps de la boucle est récopié dans le programme autant de fois qu'il y a d'itération.

Elles peuvent être aussi partiellement déroulées : un facteur définie combien de blocs déroulés doivent être créées. L'avantage et que l'on peut paralléliser les blocs entre eux si jamais le code le permet.

Applatissement

Dans le cas des boucls imbriquées, les boucles parfaites et semi-parfaites peuvent être applaties pour gagner en performance.

Parfaite Semi-parfaite
  • Seulement la boucle la plus imbriquée à un corps
  • Il n'y a pas d'instructions entre les boucles
  • Les bornes de la boucle sont constantes
  • Seulement la boucle la plus imbriquée à un corps
  • Il n'y a pas d'instructions entre les boucles
  • La première boucle a des bornes variables

Fusion de boucles

Pour améliorer les performances, les boucles peuvent parfois être fusionnées entre elles.

Lors de la fusion :

Pipelining

Notes :

Le but est de ramener le iteration interval à 1 entre chaque itération de d'une boucle. Ceci permet d'augmenter le parallélisme et donc le débit.

Même si la latence est la même entre les instructions, la latence de la boucle prend moins de cycle puisque certaines opérations sont exécutées en parallèle.

Les problèmes possibles sont que :