Aller au contenu

Fonctionnement d'un ordinateur/Les processeurs VLIW et EPIC

Un livre de Wikilivres.

Dans les chapitres précédents, nous avons parlé des processeurs qui sont capables d’exécuter des instructions dans le désordre, ou plusieurs instructions en parallèle, en même temps. Mais nous nous sommes concentrés sur les processeurs avec un jeu d'instruction normal, où l’exécution des instructions en parallèle est invisible pour le programmeur ou le compilateur. Les processeurs superscalaires sont de ce type, mais ils permettent non seulement d’exécuter plusieurs instructions en même temps, mais aussi d'émettre plusieurs instructions en même temps sur des unités de calcul différentes. Le parallélisme d'instruction maximal avec ce genre d’architectures est donc un processeur pipeliné, superscalaire, à exécution dans le désordre avec renommage de registre.

Mai ces architectures ont un défaut majeur, au-delà de la prédiction des branchements : elles doivent détecter les dépendances et déterminer quelles sont les instructions exécutables en parallèle. Mais il se trouve que ces informations sur les dépendances d'instructions sont connues des compilateurs modernes. Lors de la compilation, le code source est traduit en une représentation intermédiaire où les dépendances de type WAR, WAW et RAR n'existent tout simplement pas. Il faut dire que la représentation intermédiaire a souvent une infinité de registres, voire fonctionne sans registres et avec une infinité de places mémoires. De fait, les processeurs à exécution dans le désordre doivent retrouver des informations qui étaient connues du compilateur mais ont disparu du code source.

De cette observation est venue l'idée de modifier le jeu d’instruction pour que ces informations sur les dépendances soient disponibles directement dans le code machine lui-même. Ainsi, le compilateur fait tout le travail de réorganisation des instructions et d’exécution en parallèle, à la place du processeur. De telles architectures s'appellent des architectures à parallélisme d'instruction explicite. Il existe plusieurs types d'architectures de ce genre, les deux principales étant les architectures VLIW et dataflow. Il faut aussi mentionner les architectures découplées, fort confidentielles et peu nombreuses, que nous verrons à la fin du chapitre.

Les architectures dataflow seront vues dans le chapitre suivant. L'idée derrière ces architectures est d'expliciter les dépendances entre instruction et de les encoder directement dans le code machine. Le processeur peut ainsi déterminer quelle instruction est prête à s’exécuter en fonction de la disponibilité des opérandes. L’exécution dans le désordre est donc réalisée par le compilateur. Dans ce chapitre, nous allons voir des architectures à parallélisme d'instruction qui n'encodent pas les dépendances dans les instructions, ou du moins pas explicitement. Voyons maintenant les architectures VLIW, EPIC et découplées.

Les processeurs VLIW

[modifier | modifier le wikicode]

Les processeurs VLIW et leurs dérivés font de l'émission multiple, à savoir exécuter plusieurs instructions indépendantes en parallèle, mais sans forcément avoir de l’exécution dans le désordre.

Les processeurs VLIW, ou very long instruction word exécutent des regroupements de plusieurs instructions qui s'exécutent en parallèle sur différentes unités de calcul. Les regroupements en question sont appelés des faisceaux d’instructions (aussi appelés bundle). Le faisceau est chargé en une seule fois et est encodé comme une instruction unique. En clair, les processeurs VLIW chargent "plusieurs instructions à la fois" et les exécutent sur des unités de calcul séparées (les guillemets sont là pour vous faire comprendre que c'est en réalité plus compliqué).

Une autre manière de voir les choses est que les faisceaux d'instruction regroupent plusieurs opérations en une seule super-instruction machine. Là où les instructions machines usuelles effectuent une seule opération, les faisceaux d'instruction VLIW exécutent plusieurs opérations indépendantes en même temps, dans des unités de calcul séparées.

Pipeline simplifié d'un processeur VLIW. On voit que le faisceau est chargé en un cycle d'horloge, mais que les instructions sont exécutées en même temps dans des unités de calcul séparées.

L'attribution des instructions/opérations aux ALUs

[modifier | modifier le wikicode]

Sur un processeur VLIW pur, les faisceaux sont de taille fixe. Aussi, le regroupement des instructions est grandement facilité, de même que leur attribution aux unités de calcul. L'attriobution d'une instruction à une unité de calcul utilise l'encodage par position.

Avec cette méthode, un faisceau est découpé en créneaux (slot), chacun étant attribué à une ALU. La position de l'instruction dans le faisceau détermine l'ALU à utiliser. Les opérations/instructions ne peuvent pas être réparties n'importe comment dans un faisceau. Pour le dire autrement, chaque créneau ne peut contenir que quelques instructions bien précises,compatibles avec l'unité de calcul associée. Par exemple, le premier créneau ne peut accepter que des instructions d'addition, le second que des multiplications, le dernier que des instructions transcendantales, etc.

Instruction VLIW à 3 slots
Slot 1 Slot 1 Slot 3
Addition Multiplication Décalage à gauche

En conséquence, il y a de nombreuses contraintes quand au regroupement des opérations/instructions. On ne peut pas regrouper n'importe quelle opération avec n'importe quelle autre, il faut que les unités de calcul permettent le regroupement. Prenons l'exemple d'un processeur VLIW disposant de deux additionneurs et d'un circuit multiplieur : il sera possible de regrouper deux additions avec une multiplication, mais pas deux multiplications ou trois additions.

Il y a aussi des contraintes sur les registres : les instructions d'un faisceau ne peuvent pas écrire dans les mêmes registres, il y a des contraintes qui font que si telle opération utilise tel registre, alors certains autres registres seront interdits pour l'autre opération, etc.

L'exécution lockstep des opérations/instructions d'un faisceau

[modifier | modifier le wikicode]

Sur un processeur VLIW idéal, toutes les instructions d'un faisceau s'exécutent en même temps, durant les mêmes cycles d'horloge. La conséquence est que si une instruction/opération bloque le pipeline, toutes les instructions du faisceau doivent l'attendre. Par exemple, si un accès mémoire est dans un faisceau, les autres instructions s’exécutent en parallèle, mais on ne peut pas passer au faisceau suivant tant que l'accès mémoire n'est pas terminé. Un faisceau est un tout, qui s’exécute en bloc, on ne peut pas passer au faisceau suivant tant que tout le faisceau précédent est terminé. On parle parfois d'exécution lockstep d'un faisceau.

Un défaut important est la gestion des instructions multi-cycle et notamment des accès mémoire. Lors d'un défaut de cache, les autres unités de calcul sont inutilisées une fois qu'elles ont fait les calculs associés dans le faisceau. Ce ne serait pas le cas avec un processeur à lectures non-bloquantes ou à exécution dans le désordre. Les instructions suivant la lecture pourraient parfaitement alimenter les unités de calcul, à condition d'être indépendants de la lecture. Mais avec un processeur VLIW, impossible de démarrer les opérations du faisceau suivant tant que l'accès mémoire n'est pas terminé. Idem avec les instructions multicycle, pour des instructions flottantes ou les multiplications/divisions.

Les dépendances de données sont interdites dans un faisceau

[modifier | modifier le wikicode]

Les instructions/opérations d'un faisceau sont censées être indépendantes, dans le sens où elles n'ont pas de dépendances de données. Aussi, des difficultés arrivent quand des instructions/opérations d'un faisceau manipulent le même registre. Pour les lectures, cela ne pose pas de problème : la donnée lue est envoyée à plusieurs ALU, pas de quoi déclencher des problèmes, il faut juste que la connexion entre registres et unité de calcul le permettent, ce qui est souvent le cas. Les problèmes apparaissent avec des écritures. En théorie, deux instructions/opérations ne sont pas censées écrire dans le même registre. De même, si les instructions sont indépendantes, une instruction ne doit pas lire un registre écrit par une autre instruction. Mais dans les faits, cette situation arrive sur certains processeurs VLIW. Et ils peuvent gérer la situation de trois manières.

La toute première est la plus simple : le processeur interdit à deux instructions d'un même faisceau d'avoir une dépendance de ce type. Si une instruction écrit dans un registre, tout autre instruction du faisceau ne peut pas lire ce registre. Une autre solution autorise ce genre de choses, mais avec une subtilité : la lecture du registre renvoie la valeur avant l'écriture. En clair, les deux instructions n'ont pas de dépendances entre elles, c’est juste que l'instruction lectrice lit une donnée qui est écrasée par la seconde au cycle suivant. L'implémentation est assez simple au niveau du matériel, et assez intuitive.

Il existe cependant une exception, qui permet à plusieurs instructions d'écrire dans un registre. Il s'agit d'une sous-catégories d'instructions à prédicat sur le processeur Itanium. Les instructions en question effectuent une comparaison, puis font un ET/OU/XOR entre le résultat et un registre à prédicat. Or, l'Itanium peut exécuter plusieurs comparaisons de ce type en parallèle, en mettre plusieurs dans un seul faisceau. Si plusieurs comparaisons utilisent le même registre à prédicat et font un ET avec leur résultat, le résultat sera un ET entre tous les résultats des comparaisons. Idem avec un OU ou un XOR sur un même registre. Par contre, impossible de faire à la fois un OU et un ET sur le même registre. Il s'agit donc d'une exception à la règle : pas d'écritures simultanées dans le même registre.

La gestion des exceptions matérielles avec des faisceaux d'instructions

[modifier | modifier le wikicode]

Passons maintenant aux dépendances de contrôle, qui existent encore sur les architectures VLIW. L'exécution en bloc, lockstep, est censée résoudre ces dépendances pour ce qui est des branchements. Mais il ne faut pas oublier les exceptions matérielle. Une instruction/opération peut déclencher une exception, et il faut la traiter avec la granularité d'un faisceau. Un processeur VLIW peut gérer la situation de plusieurs manières différentes, qui dépendent du processeur.

La plus simple invalide toutes les instructions du faisceau, l'exception est traitée au niveau du faisceau. Le faisceau est ré-executé intégralement une fois l'exception traitée par la routine adéquate. Une solution complétement à l'opposé exécute toutes les instructions du faisceau, sauf celle qui a levé l'exception. La première fait qu'on doit ré-exécuter toutes les instructions du faisceau, l'autre solution n'en ré-exécute qu'une seule. En terme de consommation énergétique et de performance, les deux sont complétement opposées : ré-exécution maximale couteuse en performance et énergie d'un côté, ré-exécution minimale et peu couteuse en énergie/performance de l'autre. Mais elles ont un point commun : dans les deux cas précédents, les exceptions deviennent alors imprécises.

Une autre solution, plus compatible avec le modèle familier aux programmeurs, ajoute un support des exceptions précises. L'idée est d'invalider uniquement les instructions qui suivent l'exception dans l'ordre du programme. Pour cela, le compilateur doit ajouter quelques informations sur l'ordre des instructions dans le faisceau. Une solution intermédiaire invalide seulement les instructions qui ont une dépendance avec celle qui a levé l'exception. On a alors des exceptions semi-précises, mais les performances sont meilleures vu qu'on n'a pas à ré-exécuter beaucoup d'instructions.

Le compilateur a un rôle primordial sur les architectures VLIW

[modifier | modifier le wikicode]

Les architectures VLIW ont des avantages certains : hardware très simple, émission multiple supportée nativement, etc. Mais elles ont aussi divers problèmes, comme une faible compatibilité, des performances limitées par les dépendances existantes à la compilation et une densité de code mauvaise. Tous les avantages et inconvénients majeurs ont la même source : c'est le compilateur qui regroupe plusieurs instructions/opérations en un seul faisceau. Le compilateur garantit que les instructions/opérations regroupées sont indépendantes et peuvent s’exécuter en parallèle.

Un avantage à cela est que les processeurs VLIW ont un hardware très simple, avec peu de circuits de contrôle. Le compilateur se charge de vérifier que des opérations indépendantes sont regroupées dans une instruction, pas besoin de hardware pour cela, pas besoin d'une unité d'émission complexe, ni d'exécution dans le désordre. Il y a juste besoin d'un scoreboard pour gérer les instructions multicycle et les accès mémoire. Alors qu'avec un processeur superscalaire, il y a des circuits de détection des dépendances entre instructions assez complexes et couteux en circuits.

L'absence de ces circuits fait que les processeurs VLIW étaient utilisés sur les premières cartes graphiques : autant utiliser les transistors pour placer le plus de circuits de calcul possible au lieu d'en dépenser dans des circuits de contrôle. Mais avec l'évolution de la technologie, il est devenu plus rentable d'ajouter de tels circuits pour gagner en performance, ce qui fait que les cartes graphiques incorporent un scoreboard ou quelque chose de similaire.

Mais pour ce qui est des désavantages, les processeurs VLIW ont une performance très dépendante du compilateur. Le compilateur doit analyser le code source pour détecter les dépendances d'instruction, et faire les regroupements. En soi, analyser les dépendances entre instruction est assez simple pour les compilateurs modernes, qui utilisent une représentation SSA. Mais malgré cela, les compilateurs ne font pas du très bon travail.

Il arrive régulièrement que le compilateur ne puisse pas remplir tout le faisceau avec des instructions indépendantes. Sur les anciens processeurs VLIW, les instructions VLIW (les faisceaux) étaient de taille fixe, ce qui forçait le compilateur à remplir d'éventuels vides avec des NOP, diminuant la densité de code. La majorité des processeurs VLIW récents utilise des faisceaux de longueur variable, supprimant ces NOP. Mais il reste le fait que ces NOP sont une sous-exploitation des unités de calcul.

De plus, certaines dépendances entre instructions ne peuvent être supprimées, ce qu'un processeur à exécution dans le désordre peut faire. Par exemple, le fait que les accès à la mémoire aient des durées variables (suivant que la donnée soit dans le cache ou la RAM, par exemple) joue sur les différentes dépendances. Un compilateur ne peut pas savoir combien de temps va mettre un accès mémoire et il ne peut organiser les instructions d'un programme en conséquence. Par contre, un processeur le peu. Autre exemple : les dépendances d'instructions dues aux branchements, qui ont tendance à limiter fortement les possibilités d'optimisation du compilateur.

Autre défaut : les processeurs VLIW n'ont strictement aucune compatibilité, ou alors celle-ci est très limitée. En effet, le format des faisceaux VLIW est spécifique à un processeur. Celui-ci va dire : telle instruction va sur telle ALU, et pas ailleurs. Mais si on rajoute des unités de calcul dans une nouvelle version du processeur, il faudra recompiler notre programme pour que celui-ci puisse l'utiliser, voire simplement faire fonctionner notre programme. Dans des situations dans lesquelles on se moque de la compatibilité, cela ne pose aucun problème : par exemple, on utilise beaucoup les processeurs VLIW dans l'embarqué. Mais pour un ordinateur de bureau, c'est autre chose...

Un petit historique des processeurs VLIW

[modifier | modifier le wikicode]

Les processeurs VLIW sont une idée assez ancienne, qui a ses sources dans un algorithme conçu à base pour l'implémentation d'un microcode horizontal ! Dans les années 80, Joseph Fisher travaillait sur l'architecture du CDC-6600, un super-ordinateur dont le processeur avait un microcode horizontal. Et le microcode horizontal a justement des ressemblances avec les processeurs VLIW.

Frustré par les difficultés à coder un microcode sur ce genre de machines, il chercha un algorithme qui part d'une séquence de micro-opérations, et qui les regroupe dans des micro-opérations horizontales. L'algorithme qui naquit de ces recherches est appelé l'algorithme de trace scheduling. L'algorithme était capable de regrouper des opérations isolées dans un paquet encodé avec une seule micro-opération.

Quelques architectures similaires au VLIW existaient déjà à l'époque, mais étaient prévues pour être codées en assembleur. L'invention de cet algorithme rendit ces architectures bien plus utiles. Le parallélisme exposé dans les micro-instructions horizontales est similaire à celui exposé par les architectures VLIW. Il était alors possible de prendre un compilateur, d'exécuter un algorithme de trace scheduling sur les instructions, et de se retrouver avec un programme encodant directement les des faisceaux VLIW.

Fisher participa alors à un projet de recherche visant à créer un processeur VLIW, nommé le ELI-512 (Extremely Long Instruction-512), dont les instructions faisaient 512 bits et encodaient jusqu’à 30 instructions machines. Par la suite, il créa l'entreprise Multiflow, qui batit les premiers processeurs VLIW commerciaux, les bien nommés processeurs Multiflow. L'entreprise Cydrome tenta de leur faire concurrence. Mais les deux entreprises firent faillite au bout de quelques années.

Quelques tentatives récentes de faire revenir ces architectures sur le devant de la scène ont été des échecs. Citons le cas de l'architecture Itanium d'Intel, ainsi que les processeurs Crusoe de l'entreprise Transmetta. Les deux visaient à remplacer le jeu d'instruction x86 des PC modernes, objectifs très compliqué et sans doute voué à l'échec du fait des problèmes de compatibilité intrinsèques. Transmetta a pourtant pris le problème à bras le corps, avec des techniques de traduction x86-VLIW très performantes, mais purement logicielles. Mais rien à faire, les processeurs VLIW n'ont percé que dans des domaines où la compatibilité avec le code existant n'est pas nécessaire, l'embarqué étant le meilleur d'entre eux. Les processeurs VLIW ont été autrefois utilisés dans les cartes graphiques AMD et possiblement NVIDIA de l'époque de DirextX 8/9. Mais tout cela est le sujet d'un autre wikilivre...

Les instructions multicyles et le pipeline sur les CPU VLIW

[modifier | modifier le wikicode]

La discussion précédente partait du principe que le processeur VLIW n'avait pas de pipeline. L'implémentation du VLIW sur un pipeline demande cependant quelques modifications. Le cas le plus simple est celui d'un pipeline de longueur fixe, où toutes les opérations s'exécutent en un seul cycle d'horloge.

Sans système de contournement, l'usage du pîpeline fait qu'il y a un délai entre le moment où une opération lit ses opérandes dans les registres et celui où son résultat est enregistré dans les registres. Quelques cycles d'horloges, tout au plus, mais il s'agit d'un délai qui fait que le résultat d'une opération est enregistré en retard. Or, un processeur pipeliné démarre un nouveau faisceau à chaque cycle d'horloge. Ce qui pose des problèmes si deux faisceaux ont des dépendances de données. Si on lance ces deux faisceaux l'un à la suite de l'autre, le second faisceau ne lira pas le résultat calculé par le premier. On retombe sur les problèmes liés à l'émission dans l'ordre/désordre, que les architectures VLIW souhaitaient éviter.

Les mitigations et solutions

[modifier | modifier le wikicode]

Il est possible de mitiger le tout dans le cas d'un pipeline de longueur fixe, où toutes les instructions s'exécutent en un cycle d'horloge. Pour cela, il suffit d'utiliser un système de contournement, pour envoyer les résultats calculés par l'ALU sur son entrée, afin de mieux gérer les dépendances de données de type RAW. Sans cela, deux faisceaux avec une dépendance ne peuvent pas être démarrés l'un après l'autre. Mais la solution ne résout le problème que si toutes les instructions/opérations s'exécutent en cycle d'horloge au niveau de l'ALU. Dans les faits, la solution ne marche pas pour les instructions multicycles, dont les accès mémoire.

Une solution à cela serait de vérifier les dépendances entre faisceaux avec un scoreboard ou un circuit similaire. En cas de dépendances entre les faisceaux, l'exécution lockstep des instructions fait qu'on doit ajouter des bulles de pipeline. Le problème est que l'on ne profite pas du pipeline, sauf à utiliser un système de contournement complexe. De plus, cela demanderait d'améliorer l'unité d'émission et d'ajouter du hardware pour cela, ce qui colle mal à la philosophie du VLIW. Mais c'est l'une des seule solution possible pour gérer les accès mémoire.

Une autre solution est de laisser le travail au compilateur, qui gère de lui-même les latences des instructions liées au pipeline. Le compilateur doit donc produire un code machine spécifique à un processeur, qui prend en compte la longueur du pipeline. La solution marche pas trop mal pour les instructions multicycles exécutées dans une ALU, mais pas trop pour les accès mémoire. Pour les instructions multicycles, le compilateur connait la latence de chaque opération et s’arrange pour que le code compilé n'aie pas de problèmes. Par exemple, si une unité de calcul multicycle est utilisée pendant 5 cycles, elle s'arrange pour que les 5 faisceaux suivants ne l'utilisent pas. Le compilateur doit gérer les latences pour chaque opération d'un faisceau, vérifier que des faisceaux consécutifs soient compatibles, et ainsi de suite.

Les modèles de latence d'instruction

[modifier | modifier le wikicode]

Peu importe que l'on utilise un scoreboard ou le compilateur, la latence des instruction a un grand impact dans la manière dont on exécute/compile le code. Le processeur connait la latence maximale d'une instruction multicycle, sauf éventuellement pour les accès mémoire. Idéalement, le compilateur fonctionne mieux avec des latences fixes, mais il peut avoir à se débrouiller avec des latences variables. Les deux cas donnent des résultats très différents pour le compilateur, et ils sont deux modèles différents, qui doivent être pris en compte à la création du processeur.

Avec le premier modèle, la latence des instructions est fixe, à savoir qu'elles prennent toujours le même nombre de cycles pour s'exécuter. Si une instruction de multiplication prend 5 cycles, elle prendra toujours 5 cycles, pas un de moins. Avec ce modèle, le compilateur a plus de facilités à gérer les registres. Par contre, la gestion des exceptions précises est particulièrement complexe. Le processeur doit être conçu pour. Il se peut que l'instruction finisse avant dans l'unité de calcul, mais l'écriture dans les registres sera alors retardée pour respecter la latence fixée. Le chemin de données du processeur doit être conçu en conséquence et doit mettre en attente les résultats disponibles à l'avance.

Le second modèle permet à une instruction multicycle de finir plus tôt que prévu, leur latence est variable. Par exemple, une instruction de multiplication a une latence maximale de 5 cycles, mais il se peut qu'elle prennent 4/3 cycles si les opérandes sont particulières. Le compilateur a un peu plus de mal avec ce genre de modèle. Mais l'implémentation des exceptions précises est bien plus simple. De plus, cela améliore un petit peu la compatibilité dans certains cas. Si le nouveau modèle du processeur a une latence inférieure pour certaines instructions, le code conçu pour l'ancien processeur' marchera toujours, et ses performances seront améliorées.

Les processeurs VLIW à instruction de longueur variable

[modifier | modifier le wikicode]

Les processeurs VLIW purs ont de nombreux défauts, mais cela ne signifie pas qu'ils n'ont pas de solutions. Cependant, ces solutions sont un peu opposées à la philosophie de base des processeurs VLIW : réduire au maximum le hardware lié au décodage et aux unités d'émission, tout an gardant l'émission multiple. Les solutions en question sont multiples, mais elle partent du même principe : elle encodent des faisceaux de taille variable.

Par taille variable, on veut dire que les faisceaux ont un nombre d'instructions/opérations variables, qui peut varier d'un faisceau à l'autre. Par exemple, prenons un processeur VLIW codant ses faisceaux sur une taille fixe de 512 bits. En passant à des faisceaux de taille variable, les faisceaux peuvent faire entre 64 et 512 bits, par exemple. Pour cela, les NOPs, les instructions qui ne font rien, sont retirées du faisceau ou sont encodées de manière compressée. Et cela implique que le décodage des instructions et leur émission est plus complexe, elle demande plus de circuits. La densité de code est grandement améliorée, de même que la compatibilité.

Les faisceaux ont donc une taille variable, comprise entre une taille minimale et une taille maximale. La taille maximale est obtenue quand aucun NOP n'est présent dans le faisceau. Par contre, le processeur doit pouvoir charger un faisceau complet. Par exemple, pour un processeur dont les faisceaux font entre 64 et 512 bits, le processeur charge 512 bits en une fois, en un accès au cache d'instruction. Il faut donc faire la distinction entre les faisceaux et les paquets de chargement. Un paquet de chargement dans l'exemple précédent fait 512 bits, mais les faisceaux en font entre 64 et 512.

L'avantage principal est uen amélioration de la densité de code : pas besoin d'insérer des NOPs à l'intérieur des faisceaux, même si on peut avoir besoin d'insérer des NOPs de bourrage. Avec un faisceau de taille variable, il n'y a pas besoin d'ajouter des NOPs si le faisceau est partiellement remplit, on a juste à raccourcir le faisceau.

Le défaut est que le décodage et le chargement des instructions est plus complexe. Les faisceaux n'ayant plus une taille fixe, il faut utiliser les techniques de chargement/décodage des instructions variables vues dans le chapitre sur l'unité de chargement. Cela fait du hardware en plus, un cout en performance et en énergie. Tout ce que la philosophie VLIW voulait éviter. En contrepartie, le gain en densité de code est important et le gain de compatibilité binaire est très important.

La délimitation des faisceaux

[modifier | modifier le wikicode]

Un paquet de chargement peut contenir plusieurs faisceaux. Lorsqu'un paquet est chargé, il est découpé en faisceaux au fur et à mesure de l'exécution. Le processeur exécute les faisceaux les uns après les autres. Par exemple, si toutes les instructions d'un paquet doivent s’exécuter en série, il les exécute une après l'autre, et ne charge le paquet suivant qu'une fois que toutes les instructions du paquet sont terminées. Reste que pour cela, il faut trouver un moyen pour découper un paquet de chargement en plusieurs faisceaux. Pour cela, il existe plusieurs techniques, avec d'autres techniques dérivées.

La première technique ajoute des méta-données au début du faisceau, à savoir quelques bits qui fournissent des informations sur le faisceau. Et parmi ces information, on peut intégrer la taille du faisceau, sa longueur en nombre d'octets ou d'instructions. Le processeur a juste à extraire la longueur du faisceau et sait comment découper le paquet de chargement en faisceaux. Les faisceaux sont donc placés à la suite les uns des autres en mémoire, mais sont précédés par un octet de méta-données qui indique la longueur du faisceau, comment sont organisées les instructions/opérations dedans, etc. Un défaut est que la taille d'un faisceau est limitée par le nombre de bits utilisés pour encoder les méta-données.

La méthode peut être étendue pour gérer autre chose que la taille des faisceaux, comme on le verra plus bas. En effet, les méta-données ne se bornent pas à donner la longueur du faisceau, mais peuvent indiquer comment sont organisées les instructions dans le faisceau, ou toute autre information utile. A défaut d'intégrer la longueur du faisceau dans l'instruction, on peut intégrer des informations qui permettent de la calculer. Par exemple, nous verrons plus bas la technique du masque de NOPs qui permet de calculer la longueur de l'instruction sur la base d'informations utilisées pour autre chose.

Les deux techniques suivantes dispersent des méta-données entre chaque instruction, au lieu de les regrouper dans un octet au début du faisceau. Pour ce faire, elles ajoutent des bits à la fin de chaque instruction, qui précisent s'il s'agit de la dernière instruction d'un faisceau. La solution est plus flexible, car les faisceaux peuvent avoir des tailles arbitraires, en théorie.

Avec la première technique, chaque instruction est suivie d'un bit d’arrêt (stop bits) qui indique la fin d'un faisceau.

Bits d’arrêt.

Avec la seconde méthode, chaque instruction/opération est suivie d'un un bit de parallélisme (parallel bit), un bit placé à la fin d'une instruction qui dit si elle peut s'effectuer ou non en parallèle de la suivante.

Bit de parallélisme.

Un exemple est celui des processeurs Texas Instrument de série C6X, notamment le TMS320C62 (C62) et le TMS320C67 (C67). Sur ces processeurs, un paquet de chargement fait 256 bits et les paquets sont eux-même alignés en mémoire sur 256 bits. Les instructions font 32 bits, ce qui fait qu'on peut en mettre 8 par paquet de chargement. Les faisceaux sont indiqués avec des bits de parallélisme.

L'attribution des instructions/opérations aux ALUs

[modifier | modifier le wikicode]

Le passage à des faisceaux de taille variable fait que l'on perd la correspondance unique entre une instruction/opération et une unité de calcul. Le fait que les NOPs sont devenus implicites fait que l'on doit répartir les opérations sur les différentes unités de calcul. Il faut déterminer quelle instruction/opération va sur tel unité de calcul. Les solutions pour cela sont assez variables, allant d'un codage des NOPs compressé avec un encodage explicite des NOPs, à des systèmes avec un encodage implicite.

Une solution est de se débarrasser de l'encodage par position utilisé plus haut, où chaque instruction était attribuée à une ALU en fonction de sa place dans le faisceau. A la palce, on le remplace par l'encodage par nommage, où chaque instruction d'un faisceau précise l'unité de calcul qui doit la prendre en charge. L'instruction contient un numéro qui indique l'unité de calcul à utiliser. Cette technique est déclinée en deux formes : soit on trouve un identifiant d'ALU par instruction, soit on utilise un identifiant pour tout le faisceau, qui permet à lui seul de déterminer l'unité associée à chaque instruction.

L'encodage par position vu précédemment est toujours utilisable, à condition qu'on représente les NOPS sous forme compressée. Les instructions/opérations sont donc toujours à la bonne place, à la bonne position dans le faisceau, mais les NOPs sont compressés et réduits à une taille plus petite. Le décodage doit alors juste identifier les NOPs et les étendre, de manière à retrouver le faisceau d'instruction non-compressé.

Une solution pour cela est d'incorporer, dans le faisceau, un masque qui indique où sont les NOPs dans le faisceau. La technique porte le nom de masque de NOP. Prenons l'exemple d'un faisceau contenant 8 instructions/opérations maximum. Si j'ai par exemple 6 positions remplies avec deux NOPs, le faisceau contiendra 6 instructions seulement, les NOPs ne seront pas placés dans le faisceau. Par contre, le masque indiquera où sont les NOPs dans le faisceau, où il faut les placer. Le masque contient un bit par instruction, par slot, par position dans le faisceau. Le premier bit est pour la première instruction, le second bit pour la seconde instruction, etc. Le bit est mis à 1 si l'instruction est un NOP, 0 sinon. Les NOPs ne sont donc pas représentés.

Instruction décompréssée ADD MUL NOP SUB NOP NOP NOP LOAD
Masque de NOP 0 0 1 0 1 1 1 0
Instruction compressée ADD MUL SUB LOAD 0010 1110

Le masque est généralement placé au tout début du faisceau, pour faciliter le décodage. Il faut noter que cette technique permet de compresser les faisceaux qui contiennent au moins un NOP. Mais les autres sont allongés d'un octet pour stocker le masque. La technique est donc d'autant plus efficace que le code contient de NOPs, ce qui fait qu'elle est surtout utile sur les CPU VLIW avec beaucoup de slots, qui ont beaucoup d’instructions par faisceau, qui ont plus de difficultés à remplir leurs faisceaux.

Il faut préciser qu'avec cette technique, l'encodage de la longueur de l'instruction n'est pas nécessaire. Déterminer la longueur de l'instruction se fait simplement à partir du masque. Il suffit de l'envoyer en opérande d'un encodeur, qui renvoie la longueur du faisceau. La solution a pour avantage de ne pas ajouter d'informations en plus dans le faisceau.

Pour implémenter la technique, l'instruction est décompressée lors du chargement de l'instruction depuis le cache L1. Un circuit prend en entrée un paquet de chargement, et l'analyse, puis décompresse le faisceau en ajoutant les NOPs au bon endroit. L'avantage est que les instructions sont compressées dans le cache, ce qui utilise au mieux sa capacité. Une autre solution déplace le circuit de décompression avant le cache. Les faisceaux sont alors décompressés lors de leur chargement dans le cache d’instruction L1.

L'alignement des faisceaux dans un paquet de chargement

[modifier | modifier le wikicode]

Un point important est que les faisceaux peuvent ou non être à cheval sur deux paquets de chargement. Certains processeurs ne le permettent pas. C'est le cas sur les processeurs Texas Instrument de série C6X, où un faisceau doit rentrer intégralement dans le paquet. Ainsi, le bit de parallélisme est à 0 toutes les 8 instructions, vu qu'un paquet de chargement fait 8 instructions.

Un inconvénient est que cela peut parfois laisser des espaces vides à la fin d'un paquet d'instruction. Le compilateur essaye de faire rentrer le plus de faisceaux possibles dans un paquet de chargement, mais il arrive malgré tout qu'il reste de la place. Par exemple, pour un paquet de 8 instructions, imaginons qu'on ait un faisceau de 4 instructions, un autre de 3, et un autre de deux. Les deux premiers faisceaux rentrent dans le paquet, mais pas le troisième. Il manquera une instruction pour compléter, le vide est comblé par une instruction NOP qui ne fait rien. Le ou les NOP en question, sont appelés des NOP de bourrage.

La compatibilité est très mauvaise avec les bits de bourrage, vu que cela limite la taille du paquet de chargement. De plus, cela réduit les opportunités de compression. On gagne en densité de code si et seulement si on peut mettre plusieurs faisceaux dans un seul paquet de chargement. L'idéal est d'utiliser des paquets de chargement assez grands, pour pouvoir mettre pleins de petits faisceaux dedans.

Mais d'autres processeurs autorisent un système différent, où un faisceau peut être à cheval sur un paquet de chargement. Le chargement se fait paquet de chargement par paquet de chargement, avec un découpage des faisceaux lors du décodage. Les techniques utilisées pour cela sont similaires à celles utilisées pour le chargement des instructions de longueur variable. L'avantage est que cela permet de ne pas avoir à ajouter des NOPs de bourrage. Mais le cout en circuits est conséquent. La technique est notamment utilisée sur l'Itanium, comme on le verra plus bas.

Les techniques pour charger des faisceaux non-alignées demandent d'accumuler les paquets de chargement dans une mémoire temporaire, et de le insérer au bon endroit, à la suite des faisceaux déjà chargés. Le tout implique des circuits de décalage et autres. Et pour qu'ils fonctionnent, ils doivent connaitre la longueur d'un faisceau, afin de décaler du bon nombre de rangs/instructions. Déterminer la longueur d'un faisceau dépend grandement de la méthode employée pour délimiter les faisceaux.

Avec un octet de méta-donnée, la longueur peut être intégrée directement dans les méta-données du faisceau et n'a pas à être déterminée. Avec l'usage de bits d'arrêt ou de parallélisme, on peut la calculer sur la base de ces bits. Il suffit d'envoyer ces bits dans un circuit encodeur qui renvoie la longueur du faisceau, exprimée en nombre d'instructions. Avec un masque pour encoder les NOPs, l'encodage de la longueur se détermine à partir du masque de NOP. Là encore, il suffit de le faire passer dans un circuit qui prend le masque de NOP et détermine la longueur du faisceau. Le circuit n'est ni plus ni moins qu'un circuit de population count.

L'encodage des faisceaux sur l'Itanium

[modifier | modifier le wikicode]

L'Itanium utilise un mélange des deux méthodes. Elle regroupe les instructions dans des paquets de trois instructions appelés des bundles. N'allez pas croire que ces bundles sont des paquets de chargement : les paquets de chargement contiennent 2 bundles, voire plus. Les bundles ne peuvent pas regrouper n'importe quel triplet d'instructions, mais seulement certains regroupements prévus à l'avance. Encore une fois, il y a des contraintes sur les regroupements d'instructions dans un bundle. Il y a en tout 32 possibilités, qui définissent chacune un mix d'instructions mémoire, entières, flottantes et de branchement.

Les trois instructions d'un bundle sont associées à un octet de template, qui encode les dépendances entre instructions et qui dit comment découper le paquet de chargement en faisceaux. Il regroupe notamment les trois bits d'arrêt des trois instructions, et 5 bits annexes qui encodent la nature des instructions présentes dans le paquet. J'ai dit plus haut qu'il y a 32 possibilités pour les regroupements possibles, et bien on peut coder les 32 possibilités sur 5 bits. Le tout est codé sur 128 bits, avec 40 bits pour chaque instruction, plus l'octet de template.

Encodage des stop bits sur l'Itanium

Un faisceau peut être intégralement contenu dans un bundle s'il fait trois instructions ou moins. Dans le cas contraire, il est répartit sur plusieurs bundles et c'est au processeur de reconstituer le faisceau à partir de plusieurs bundles. L'usage de bits d'arrêt permet à un faisceau d'être à cheval sur deux bundles et éventuellement sur deux paquets de chargement.

Les paquets de chargement chargent 2 bundles à la fois sur l'Itanium 1 et 2. De plus, les paquets de chargement sont accumulées dans un tampon de 8 bundles (24 instructions). Le tampon s'assure que deux bundles soient disponibles pour les unités de décodages, vu que le processeur a la capacité d'exécuter 2 bundles à la fois dans ses unités de calcul. L'unité de décodage se charge de reconstituer des faisceaux d'instruction en utilisant les octets de template des bundles chargés, en analysant les bits d'arrêts et les bits qui encodent le regroupement des instructions.

Avec la technique utilisée sur l'Itanium, la compatibilité binaire est très bonne. Il est possible de changer les unités de calcul du processeur sans que la compatibilité binaire soit altérée. D'ailleurs, l'Itanium n'a lui-même pas de correspondance entre un bundle et ses unités de calcul, vu qu'il a 6 unités de calcul/mémoire, pour des bundles de trois instructions. Il pourrait en rajouter ou en enlever sans que ce soit un problème, il faudrait juste adapter le décodeur.

Les processeurs hybrides VLIW/RISC

[modifier | modifier le wikicode]

Il existe des processeurs hybrides entre processeurs RISC et VLIW. L'idée est que ces processeurs gérent à la fois des instructions machines isolées, et des faisceaux d'instructions. Il en existe assez peu, mais il est globalement possible de quand même les classer en deux sous-types. Le premier est celui où il est possible de mélanger instructions RISC et VLIW sans véritables contraintes. L'autre est celui où le processeur a deux modes de fonctionnement :un mode RISC, et un mode VLIW, les deux étant totalement séparés et ne pouvant pas être mixés.

Les CPU RISC/VLIW à instructions mixées

[modifier | modifier le wikicode]

L'encodage des instructions autorise des instructions isolées, parfois complétées par des faisceaux d'instructions à l'encodage spécifique. Le processeur exécute un programme qui mélange instructions normales et faisceaux VLIW librement.

Un exemple est celui des processeurs Xtensa LX2 de Tensilica. Ils appelent cette technique sous le nom de Flexible Length Instruction eXtensions (FLIX). Le nom trahit bien que les instructions VLIW sont une extension d'un jeu d'instruction normal, au même titre que les extensions MMX/SSE/x87 et autres du jeu d'instruction x86 s'ajoutent aux instructions x86 normales. Les instructions normales font entre 16 et 24 bits, alors que les faisceaux font soit 32, soit 64 bits. Ils peuvent donc regrouper 2 à 4 instructions normales.

Les processeurs DSP Infineon Carmel utilise une technique similaire. Ils sont des CPU RISC dont les instructions de base font 48 bits. Ils disposent aussi d'instructions courtes codées sur 24 bits. Et enfin, l'extension Configurable Long Instruction Word (CLIW) permet de gérer des faisceaux VLIW qui regroupent six instructions machines. Vu que le processeur charge 48 bits d'un seul coup, il peut exécuter une à deux instructions courtes d'un seul coup, ce qui en fait une forme basique de VLIW, complémentaire du CLIW.

Les CPU RISC/VLIW à deux modes de fonctionnement

[modifier | modifier le wikicode]

Le processeur Intel i860 utilise une méthode différente. Le processeur peut fonctionner en mode RISC normal, ou en mode VLIW. En mode RISC, il peut exécuter des instructions entières ou flottante, les deux étant codées sur 32 bits. En mode VLIW, il exécute des faisceaux VLIW codés sur 64 bits.

L'implémentation du VLIW profite de la présence d'une unité FPU séparée de l'ALU entière. En mode VLIW, les faisceaux d'instruction sont composés en concaténant une instruction entière et une instruction flottante, le tout faisant 64 bits. Les faisceaux sont alignées sur 64 bits, la première instruction est forcément entière, la seconde est une instruction flottante. L'instruction flottante peut faire une multiplication et une addition à la suite, vu qu'on a un additionneur flottant relié à un multiplieur flottant. Les deux circuits ont une interconnexion partiellement programmable, afin de faciliter l'implémentation de certaines instructions flottantes.

Les processeurs EPIC

[modifier | modifier le wikicode]

En 1997, Intel et HP lancèrent un nouveau processeur, l'Itanium, dont l'architecture corrigeait les défauts des autres processeurs VLIW. Dans un but marketing évident, Intel et HP prétendirent que l'architecture de ce processeur n'était pas du VLIW amélioré, mais une nouvelle architecture appelée EPIC, pour Explicit Parallelism Instruction Computing. Il faut avouer que cette architecture avait de fortes différences avec le VLIW, mais aussi beaucoup de points communs. Bien évidemment, beaucoup ne furent pas dupes, et une gigantesque controverse vit le jour : est-ce que les architectures EPIC sont des VLIW ou non ?

Dans cette section, nous allons voir les optimisations vraiment spécifiques des processeurs Itanium. Il implémente des faisceaux d'instruction de taille variable, mais aussi des techniques de spéculation avancées. Les techniques spécifiques aux processeurs Itanium sont des techniques de spéculation avancées, qui permettent de gérer les exceptions ou les branchements, ou des accès mémoire. Vu qu'il s'agit de techniques spéculatives, on s'attend à ce que ce soit le processeur qui soit en charge de ces spéculations, mais la réalité est qu'elles sont une collaboration entre CPU et compilateur !

Les exceptions différées

[modifier | modifier le wikicode]

L'Itanium implémente ce qu'on appelle les exceptions différées. Avec cette technique, le compilateur crée deux versions d'un même code : une version optimisée qui suppose qu'aucune exception matérielle n'est levée, et une autre version qui prend en compte les exceptions. Le programme exécute d'abord la version optimisée de manière spéculative, mais annule son exécution et repasse sur la version non-optimisée en cas d'exception.

Pour vérifier l’occurrence d'une exception, chaque registre est associé à un bit « rien du tout » (not a thing), mis à 1 s'il contient une valeur invalide. Si une instruction lève une exception, elle écrira un résultat faux dans un registre et le bit « rien du tout » est mis à 1. Les autres instructions propageront ce bit « rien du tout » dans leurs résultats : si un opérande est « rien du tout », le résultat de l'instruction l'est aussi. Une fois le code fini, il suffit d'utiliser une instruction qui teste l'ensemble des bits « rien du tout » et agit en conséquence.

La spéculation sur les lectures

[modifier | modifier le wikicode]

L'Itanium fournit une fonctionnalité similaire pour les lectures, où le code est compilé dans une version optimisée où les lectures sont déplacées sans tenir compte des dépendances, avec un code de secours non-optimisé. Encore une fois, le processeur vérifie si la spéculation s'est bien passée, avant de décider de passer sur le code de secours ou non. La vérification ne se fait pas de la même façon selon que la lecture ait été déplacée avant un branchement ou avant une autre écriture.

  • Si on passe une lecture avant un branchement, la lecture et la vérification sont effectuées par les instructions LD.S et CHK.S. Si une dépendance est violée, le processeur lève une exception différée : le bit « rien du tout » du registre contenant la donnée lue est alors mis à 1. CHK.S ne fait rien d'autre que vérifier ce bit.
  • Si on passe une lecture avant une écriture, la désambiguïsation de la mémoire est gérée par le compilateur. Tout se passe comme avec les branchements, à part que les instructions sont nommées LD.A et CHK.A.
Spéculation sur les lectures.

Pour détecter les violations de dépendance, le processeur maintient une liste des lectures spéculatives qui n'ont pas causé de dépendances mémoire, dans un cache : la table des adresses lues en avance (advanced load address table ou ALAT). Ce cache stocke l'adresse, la longueur de la donnée lue, le registre de destination, etc. Toute écriture vérifie si une lecture à la même adresse est présente dans l'ALAT : si c'est le cas, une dépendance a été violée, et la lecture est retirée de l'ALAT.

Les bancs de registres tournants

[modifier | modifier le wikicode]

Les processeurs EPIC et VLIW utilisent une forme limitée de renommage de registres pour accélérer certaines boucles. Pour l’expliquer, prenons une boucle simple et intéressons-nous au corps de la boucle, à savoir la boucle sans les branchements et instructions de test qui servent à répéter les instructions. La boucle d'exemple se contente d'ajouter 5 à tous les éléments d'un tableau. L'adresse de l’élément du tableau est stockée dans le registre R2. Dans le code qui suivra, les crochets serviront à indiquer l'utilisation du mode d'adressage indirect. Sans optimisations, le corps de la boucle est le suivant :

loop :

   load R5 [R2] / add 4 R2 ;
   add 5 R5 ; 
   store [R2] R5 ;

Les différentes itérations de la boucle peuvent se calculer en parallèle, vu que les éléments du tableau sont manipulés indépendamment. Mais codée comme dessus, ce n'est pas possible car les trois instructions de la boucle utilisent le registre R5 et ont donc des dépendances. Le renommage de registres peut éliminer ces dépendances, mais il n'est pas disponible sur les processeurs VLIW et EPIC. À la place, les concepteurs de processeurs ont inventé les bancs de registres tournants (rotating register files). Avec cette méthode, la correspondance (nom de registre - registre physique) se décale d'un cran à chaque cycle d’horloge. Par exemple, le registre nommé R0 à un instant donné devient le registre R1 au cycle d'après, et idem pour tous les registres. Précisons que sur l'Itanium, cette technique est appliquée non pas à l'ensemble du banc de registre, mais est limitée à un banc de registres spécialisé dans l’exécution des boucles. Évidemment, le code source du programme doit être modifié pour en tenir compte. Ainsi, le code vu précédemment devient celui-ci.

loop :

   load RB5 [R2] / add 4 R2 ;
   add 5 RB6 ; 
   store [R2] RB7 ;

Ainsi, le LOAD d'une itération ne touchera pas le même registre que le LOAD de l'itération suivante, idem pour l'instruction de calcul et le STORE. Le nom de registre sera le même, mais le fait que les noms de registre se décalent à chaque cycle d'horloge fera que ces noms identiques correspondent à des registres différents. Les dépendances sont supprimées, et le pipeline est utilisé à pleine puissance.

Cette technique s'implémente avec un simple compteur, incrémenté à chaque cycle d'horloge, qui mémorise le décalage à appliquer aux noms de registre. À chaque utilisation d'un registre, le contenu de ce compteur est ajouté au nom de registre à accéder.

Les architectures découplées

[modifier | modifier le wikicode]

Les architectures découplées, aussi appelées Decoupled Access/ExecuteComputer Architectures, sont totalement différentes des architectures VLIW. Elles sont bien des architectures à parallélisme d'instruction explicites, mais leur rôle est d'implémenter une forme limitée d’exécution dans le désordre, pas d’exécuter des instructions indépendantes en parallèles. Par contre, elle ont pour point commun avec les architectures VLIW : elles n'encodent pas les dépendances entre instructions de manière explicite dans les instructions, ce que font les architectures dataflow.

Les architectures découplées séparent les accès mémoires des autres instructions dans deux programmes qui s’exécutent en parallèle, ce qui permet une forme limitée d’exécution dans le désordre. Le découpage du programme en plusieurs flux s'effectue à la compilation. Les deux flux sont chacun exécutés sur un processeur séparé, un le processeur access en charge des accès mémoire et un processeur execute pour les calculs et des branchements.

Le transfert des opérandes entre processeurs se fait par l'intermédiaire de mémoires tampons de type FIFOs, manipulées par le programmeur. Les transferts se font dans les deux sens : du processeur access vers le processeur execute pour les lectures, dans l'autre sens pour les écritures. Les branchements demandent cependant une synchronisation entre les deux flux d'instruction, afin de garantir qu'un branchement s’exécute bien au même moment dans les deux flux. Et toute la difficulté est de synchroniser les deux processeurs.

Cette technique n'est pas compatible avec l’exécution dans le désordre telle que vue dans les chapitres précédents, mais elle en reprend certains avantages. L'avantage principal est que la séparation en deux flux permet de profiter d'une forme limitée d’exécution dans le désordre, mais avec beaucoup moins de structures matérielles, moins de registres, moins de circuits. Les deux flux étant séparés, l'un peut continuer à s’exécuter alors que l'autre est en train d'attendre. Par exemple, le flux de calcul peut continuer à faire des calculs pendant que l'autre est coincé dans un accès mémoire de longue durée. Ou alors, le flux d'accès mémoire peut charger des données à l'avance, pendant que le flux de calcul est bloqué dans un calcul long (une division, par exemple). Cette forme de parallélisme est certes plus limitée que celle permise par l’exécution dans le désordre, mais le principe est là.

Dans ce qui suit, le processeur access sera noté processeur A, et le processeur execute sera noté processeur X, pour plus de simplicité.

Pour en savoir, voici quelques liens utiles.

Les échanges de données entre processeurs

[modifier | modifier le wikicode]

Prenons le cas d'une lecture : la lecture est démarrée par le processeur A, puis est transmise au processeur X. La transmission au processeur X se fait par l'intermédiaire d'une mémoire FIFO, la file de lecture de X (X load queue, abréviée XLQ). Quand le processeur X a besoin d'un opérande lu depuis la mémoire, il vérifie si elle est présente dans l'XLQ et se met en attente tant que ce n'est pas le cas.

Pour les écritures, l'adresse de l'écriture est calculée par le processeur A, tandis que la donnée à écrire est calculée par le processeur X, et les deux ne sont pas forcément disponibles en même temps. Pour cela, les adresses et les données sont mises en attente dans deux mémoires tampons qui coopèrent entre elles. La première est intégrée dans le processeur A et met en attente les adresses calculées : c'est la file d’écriture des adresses (store address queue, ou SAQ). La seconde met en attente les données à lire, et relie le processeur X au processeur A : c'est la file d’écriture de X (X store queue, ou XSQ). Quand l'adresse et la donnée sont disponibles en même temps, l'écriture est envoyée à la mémoire ou au cache.

Les échanges entre processeurs peuvent aussi se faire sans passer par des files de lecture/écriture. Cela peut servir pour diverses scénarios. Par exemple, le calcul d'une adresse sur le processeur A peut utiliser des données calculées par le processeur X. Pour cela, les échanges s'effectuent par copies inter-processeurs de registres. Un registre peut être copié du processeur A vers le processeur X, ou réciproquement. Pour utiliser cette unité de copie, chaque processeur dispose d'une instruction COPY.

Les branchements

[modifier | modifier le wikicode]

Les branchements sont présents à des endroits identiques dans les deux flux, ce qui fait que les structures de contrôle présentes dans un programme sont présentes à l'identique dans l'autre, etc. Or, les branchements doivent donner le même résultat dans les deux processeurs. Pour cela, le résultat d'un branchement sur un processeur doit être transmis à l'autre processeur. Pour cela, on trouve deux files d'attente :

  • la file A vers X (access-execute queue, abréviée AEQ), pour les transferts du processeur A vers le processeur X ;
  • la file X vers A (execute-access queue, abréviée EAQ), pour l'autre sens.

Il faut noter que le processeur peut se trouver définitivement bloqué si l'AEQ et l'EAQ sont toutes deux totalement remplies ou totalement vide. Pour éviter cette situation, les compilateurs doivent compiler le code source d'une certaine manière.

Résumé d'une architecture découplée de type access-execute.