Wikilivres frwikibooks https://fr.wikibooks.org/wiki/Accueil MediaWiki 1.47.0-wmf.21 first-letter Média Spécial Discussion Utilisateur Discussion utilisateur Wikilivres Discussion Wikilivres Fichier Discussion fichier MediaWiki Discussion MediaWiki Modèle Discussion modèle Aide Discussion aide Catégorie Discussion catégorie Transwiki Discussion Transwiki Wikijunior Discussion Wikijunior TimedText TimedText talk Module Discussion module Event Event talk Fonctionnement d'un ordinateur/Le renommage de registres 0 65844 773235 765344 2026-09-26T13:36:43Z Mewtow 31375 /* Renommer le registre de destination : la liste de disponibilité */ 773235 wikitext text/x-wiki Pour améliorer l'exécution dans le désordre, les concepteurs de processeurs ont étudié des méthodes pour éliminer la grosse majorité des dépendances de données lors de l’exécution. Dans ce que j'ai dit précédemment, j'ai évoqué trois types de dépendances de données. Les dépendances RAW (''Read After Write'') sont des dépendances contre lesquelles on ne peut rien faire : ce sont de « vraies » dépendances de données. Elles imposent un certain ordre d’exécution de nos instructions. Mais les dépendances WAW (''write after write'') et WAR (''write after read'') sont de fausses dépendances, nées du fait qu'un emplacement mémoire est réutilisé, pour stocker des données différentes à des instants différents. Bien évidemment, l’imagination débridée des concepteurs de processeur a trouvé une solution pour supprimer ces dépendances : le '''renommage de registres'''. Avant toute chose, précisons que ce chapitre abordera uniquement les techniques compatibles avec les exceptions précises. En clair, seulement les techniques utilisées sur les processeurs modernes. Il existe deux autres techniques de renommage de registre utilisées sur les processeurs sans exceptions précises : l'algorithme de Tomasulo et le ''scoreboarding''. Contrairement à ce que vous avez pu lire ailleurs, il y a une forme de renommage de registre dans le ''scoreboarding'', mais nous verrons cela dans quelques chapitres. Toujours est-il que ces deux techniques historiques étaient utilisées sur d'anciens ordinateurs, qui n'implémentaient pas la prédiction de branchement. Leurs techniques de renommage de registres étaient particulières, aussi nous les verrons dans un chapitre à part. ==Le renommage de registres : généralités== Les dépendances WAR et WAW sont souvent opposées aux dépendances vraies (true dependency), à savoir les dépendances RAW. Les dépendances WAR et WAW viennent du fait qu'un même registre nommé est réutilisé plusieurs fois pour des données différentes. Un même nom de registre identifie une donnée différente à des instants différents du programme, ce qui force à exécuter les instructions qui utilisent ce registre dans l'ordre. D'où le fait que les dépendances WAR et WAW sont souvent appelées des dépendances de nom (''naming dependency''). Si on change l'ordre de deux instructions ayant une dépendance de données WAW ou WAR, on peut se retrouver dans une situation où les deux instructions veulent stocker des données différentes en même temps dans le même registre, ce qui n'est pas possible. Mais elles n'existeraient pas si on utilisait un registre pour chaque donnée, qui est écrit une fois, lu une ou plusieurs fois, mais jamais écrasé. Un même nom de registre correspond alors à une donnée. Une solution simple vient alors à l'esprit : conserver chaque donnée dans son propre registre et choisir le registre adéquat à l'utilisation. Un registre architectural correspond alors à plusieurs registres, chacun contenant une donnée bien précise. L'idée est qu'au lieu d'avoir un registre qui contient des données différentes à des instants différents, on a autant de registres que de données, ce qui améliore les possibilités de réorganisation des instructions. Il faut ainsi faire la distinction entre : * '''registres architecturaux''', définis par le jeu d'instructions ; * '''registres physiques''', physiquement présents dans l'ordinateur ; * '''registres virtuels''' utilisés pour le renommage de registre mais invisibles pour le programmeur. Les registres physiques regroupent l'ensemble des registres, à savoir les registres architecturaux et virtuels. Pour illustrer l'idée, prenons l'exemple suivant, où un registre architectural nommé R6 est utilisé pour stocker trois données consécutives. Il mémorise d'abord le nombre 255, puis le nombre 2567763, puis zéro. On suppose que ces trois valeurs sont indépendantes, dans le sens où elles sont utilisées par des instructions sans dépendances RAW. En théorie, on pourrait exécuter ces instructions séparément, mais ce n'est pas possible. Le fait est que l'on doit d'abord faire les calculs avec l'opérande 255, puis ceux avec l'opérande 2567763, puis ceux avec l'opérande zéro. La raison est que pour que le calcul démarre, il faut que l'opérande soit disponible. Et l’usage d'un seul registre pour stocker des opérandes différentes à des temps différents limite les possibilités d’exécution en parallèle. Avec le renommage de registres, ces trois données sont enregistrées dans des registres physiques séparés, dès qu'elles sont disponibles. Le processeur garde évidemment trace du fait que ces trois registres physiques correspondent au registre architectural R6. Si les trois valeurs n'ont pas de dépendances entre elles, les instructions liées peuvent être exécutées dans le désordre et s’exécuter en même temps. Ce qui est le cas dans le schéma suivant, où certaines instructions sont exécutées en avance, ce qui réserve les registres physiques en avance. [[File:Renommage de registres - principe.png|centre|vignette|upright=2|Exemple où un registre architectural est utilisé pour contenir trois données successives.]] : Pour les connaisseurs, le renommage de registres réécrit le programme exécuté dans une représentation appelée SSA, utilisée par les compilateurs lors de la compilation. Et cette réécriture se fait un peu de la même manière avec un compilateur. Là où le compilateur change le nom des variables pour passer en forme SSA, le processeur change les noms de registres pour obtenir de l'assembleur SSA interne au processeur. ===La durée de vie des registres virtuels=== La plupart des processeurs disposent d'un banc de registre physique unique, qui regroupe registres physiques et virtuels. Mais il existe quelques processeurs qui font autrement. Ils ont un banc de registre pour les registres architecturaux, et les registres virtuels sont placés ailleurs dans le processeur, typiquement dans le tampon de ré-ordonnancement (ROB) ou la fenêtre d'instruction. Pour simplifier, considérons qu'ils sont dans un banc de registre spécialisé qui regroupe les registres virtuels. C'est un peu faux en soi, mais faisons comme si. Dans le reste de cette section, nous allons partir du principe que les registres virtuels et architecturaux sont séparés pour simplifier les explications, nous verrons les autres formes de renommage de registre plus tard. Les registres virtuels ont une certaine durée de vie. On veut dire par là qu'une donnée écrite dans un registre virtuel y reste durant un certain temps, mais pas indéfiniment. Un registre virtuel est utilisé par une instruction en cours d'exécution dans le pipeline. Il nait lors du renommage de registre et meurt une fois l'instruction terminée. En clair, un registre virtuel dure durant toutes les étapes du pipeline qui suivent le renommage de registres. Une fois que l'instruction quitte le pipeline, qu'elle sort du ROB, le registre virtuel est inutile et doit laisser la place au registre architectural non-renommé. Le résultat de l'instruction doit être transféré dans le registre architectural adéquat, le registre de destination de l'instruction. Si les registres virtuels et architecturaux sont séparés, il faut copier la donnée des registres virtuels vers les registres architecturaux. Utiliser un banc de registre unique résout le problème, mais n'allons pas trop vite, nous verrons cela en détail dans la suite. Toujours est-il qu'un registre virtuel est libéré quand l'instruction associée quitte le ROB. Il est théoriquement possible de libérer un registre virtuel en avance, si on est certain qu'aucune instruction n'ira lire ce registre. Pour cela, le mieux est d'attribuer un compteur de lectures pour chaque registre. À noter que pour maintenir des exceptions précises, on est obligé d'attendre que la dernière instruction qui lit le registre ait validé. Le jeu n'en vaut clairement pas la chandelle. Le seul intérêt d'une telle optimisation est économiser des registres virtuels, d'avoir un banc de registres virtuels plus petits, mais cela se fait au prix de l'ajout de circuits qui consomment tout autant, si ce n'est plus de circuits, pour un résultat en termes de performances limité, voire nul. Après avoir vu la mort des registres virtuels, voyons leur naissance. Le renommage de registre a lieu après le décodage, donc juste avant le passage dans le ''scoreboard'', la fenêtre d’instruction, la file d'instruction ou toute autre structure dédiée à l'émission. En clair : les registres virtuels sont attribués juste avant émission et ne sont utiles qu'après. Les instructions susceptibles d'utiliser un registre virtuel sont donc les instructions émises ou prêtes à l'être, mais pas encore terminées. Les instructions en question sont soit en attente dans la fenêtre d’instruction, soit en cours d'exécution dans le chemin de données, soit en attente dans le ROB. Ces dernières attendent que les instructions précédentes se terminent. Il faut noter que toutes les instructions émises ou en passe de l'être ont été ajoutées dans le ROB juste après le renommage. Même les instructions en attente dans la fenêtre d'instruction sont dans le ROB. Pour résumer, le ROB garde la trace de ces instructions prêtes à être émises ou déjà émises. ===Le nombre idéal de registres virtuels=== Mine de rien, le contenu de la section précédente nous donne une seconde borne maximale sur le nombre de registres virtuels utiles. Supposons que chaque instruction fournit un résultat, stocké dans un registre virtuel rien que pour lui. Le nombre maximal d'instructions chargées dans le pipeline après émission est égal au nombre d'entrée dans le ROB. Il peut y avoir moins d'instructions, ce qui fait que des entrées du ROB sont inutilisées, mais supposons que nous soyons dans le meilleur des cas. Dans ce cas, on doit avoir un registre virtuel par instruction, ce qui fait au maximum un registre virtuel par entrée du ROB. Rien ne sert d'avoir plus de registres virtuels que d'entrées dans le ROB, il n'y aura pas assez d’instructions dans le pipeline pour tous les utiliser. Après avoir vu une borne maximale, voyons une limite minimale. Non pas qu'il faille absolument plus de registres virtuels que cette limite, simplement qu'il est préférable d'en avoir que cette limite. Partons du principe que les instructions pouvant utiliser un registre virtuel sont celles qui sont soit déjà émises, soit en attente de l'être. Si on néglige les instructions en attente dans le ROB, alors les instructions émises sont dans les ALUs. Le nombre d'instruction est donc égal à la somme de : la taille de la fenêtre d'instruction (instructions en attente), du nombre d'unités de calcul (instruction en cours d'exécution), unités d'accès mémoire inclues. Mieux vaut ne pas passer sous la somme taille de la fenêtre d'instruction + nombre d'unités de calcul. Un bon compromis est d'avoir un nombre de registre virtuel compris entre ces deux bornes. Le nombre réel de registre virtuels est très souvent inférieur au nombre d'entrées du ROB, car toutes les instructions ne fournissent pas de résultat à enregistrer dans les registres. C'est le cas pour les instructions de branchement, par exemple, ou les écritures en mémoire RAM. Par contre, il vaut mieux avoir plus de registres que d'entrées dans la fenêtre d'instructions. La majorité es processeurs respectent ces deux limites basse et hautes, avec un nombre de registres réels entre les deux. Les quelques rares exceptions étaient des processeurs qui implémentaient le renommage de registre pour la première fois, et où ce genre de détails n'étaient pas bien compris. Par exemple, on peut citer le PowerPC 604, qui avait 20 registres virtuels, alors que le ROB avait seulement 16 entrées. Les 4 registres virtuels en trop n'étaient pas utilisés. Le processeur qui a immédiatement suivi, le PowerPC 604, n'a pas fait cette erreur et a réduit le nombre de registres virtuels à seulement 16. Un autre exemple est le MIPS R10000, avec son ROB à 32 entrées, sa fenêtre d'instruction à 48 entrées et ses 64 registres virtuels. Le processeur suivant, le R12000, corrigea partiellement le problème en augmentant le ROB à 48 entrées, ce qui était insuffisant, mais bienvenu. ===Le renommage de registre total et partiel=== De nombreux processeurs ont des registres architecturaux séparés pour les nombres flottants et entiers. Dans ce cas, leur renommage est indépendant : les registres flottants ont leur unité de renommage dédiée, séparée de celle pour les registres entiers. A ce propos, quelques rares processeurs ne renomment que que les registres flottants, ou que les registres entiers ! Pour donner un exemple, le processeur Nx586 ne renommait que les registres entiers, pour les instructions entières. Un autre exemple est celui de l'IBM System/360 Model 91, sur lequel les registres flottants étaient renommés, mais pas les registres entiers. Sur les CPU x86, lorsque le renommage de registre a été introduit, il ne concernait que les registres flottants. Ce choix s'explique par l'organisation en pile de registres liée au jeu d'instruction x87. Elle limitait grandement le parallélisme d'instruction, le renommage permettait de fortement limiter la casse. [[File:Zen microarchitecture.svg|centre|vignette|upright=2|Microarchitecture Zen 1 des CPU d'AMD.]] Le renommage de registre peut être total ou partiel. Avec le renommage total, toutes les instructions voient leurs registres renommés. Il s'agit de la méthode la plus utilisée à ce jour, par simplicité de conception. Mais des processeurs assez anciens utilisaient un renommage partiel, qui ne s’appliquaient qu'à certains types d'instruction bien précis. Par exemple, on pourrait citer les processeurs Power1, Power2 et PowerPC 601. Sur le premier, seuls les registres flottants étaient renommés, et seulement pour les instructions de lecture flottante. La raison est que le processeur n'a qu'une seule FPU, ce qui limite l'efficacité de l'exécution dans le désordre sur celle-ci, et fait que renommer les registres ne sert à rien. Le processeur Power 2 dispose lui de plusieurs FPU et le renommage de registres touche aussi les instructions flottantes arithmétiques, c'est un renommage total. Les processeur qui utilisaient le renommage partiel étaient des processeurs assez anciens, qui faisaient avec un budget en transistors limité. De plus, la technique n'était pas très bien maitrisée, et était encore balbutiante. La technique a pourtant été proposée dès 1970 dans divers articles académiques, et raffinée pendant les années 70. Mais le budget en transistors du renommage de registre a fait qu'elle n'a pas été utilisée avant que la loi de Moore fasse son œuvre. ==Les différentes implémentations du renommage de registres== Il existe plusieurs manières différentes d'implémenter le renommage de registre, que nous allons voir dans cette section. Mais avant toute chose, parlons du réseau de contournement. Pour simplifier les explications, appelons ''instruction productrice'' l'instruction qui fournit un résultat, et instruction consommatrice celle qui l'utilise comme opérande. Si une instruction consommatrice est chargée dans le pipeline quelques cycles après l'instruction productrice, les deux sont dans le pipeline en même temps. Dans ce cas, on pourrait penser que c'est le réseau de contournement qui gérerait le transfert du résultat de l'instruction productrice à l’instruction consommatrice. Et effectivement, c'est souvent le cas. Mais les registres virtuels sont pris en compte dans le système de contournement. Si le résultat est écrit dans un registre virtuel, il peut aussi être lu dans ce registre virtuel. Lire un résultat dans les registres virtuels permet d'implémenter une forme de contournement. Tout cela pour dire qu'il y a un lien entre contournement et renommage de registres, les deux doivent être conçu de manière à communiquer entre eux. Typiquement, il y a toujours un réseau de contournement pour connecter la sortie d'une ALU aux entrées des autres. Ce dernier gère le cas où un résultat d’instruction est réutilisé comme opérande au cycle suivant. Mais pour réutiliser un résultat deux cycles après ou plus tard, c'est le système de renommage de registres qui s'en charge. Ceci étant dit, nous allons étudier les implémentations principales du renommage de registre. Pour simplifier, il existe deux manières d'implémenter le renommage de registre. La première utilise deux bancs de registres : un pour les registres architecturaux, un autre pour les registres virtuels. Une autre implémentation regroupe registres virtuels et architecturaux dans un seul banc de registre. Mais d'autres implémentations font totalement autrement, en réutilisant les stations de réservation ou le tampon de ré-ordonnancement. Dans ce qui suit, nous allons nous concentrer sur les principales : le renommage à banc de registre physique, le renommage dans le ROB, le renommage à registres virtuels. Nous allons laisser de côté le renommage dans les stations de réservation pour une raison très simple : elle n'est pas compatible avec les exceptions précises. Nous la verrons dans un chapitre à part, portant sur le ''scoreboarding'' et l'algorithme de Tomasulo. ===Le renommage à banc de registres physiques=== Les processeurs modernes utilisent un seul banc de registres pour les registres architecturaux et virtuels, appelé '''banc de registres physiques''' (''physical register file''). Le tampon de réordonnancement et la fenêtre d'instruction contiennent des numéros de registres virtuels, des pointeurs vers les registres. Il y a toujours un réseau de contournement pour connecter la sortie d'une ALU aux entrées des autres, qui gère le cas où la sortie de l'ALU est utilisé comme opérande au cycle suivant. Mais pour réutiliser un résultat deux cycles après ou plus tard, c'est le banc de registres physiques qui s'en charge. Les opérandes viennent donc soit du banc de registres physique, soit du réseau de contournement. [[File:Renommage à banc de registres physiques.png|centre|vignette|upright=2|Renommage à banc de registres physiques.]] Avec ce système, un registre physique peut être dans quatre états. * Le premier état est celui d'un ''registre disponible'', près à servir de registre de destination. Par disponible, on veut dire que ce registre peut être réservé par l'unité de renommage, mais qu'il n'a pas encore été réservé. Ce n'est ni un registre virtuel, ni un registre architectural, juste un registre inutilisé. * Le second état est l'''état réservé'', celui d'un registre virtuel qui attend qu'une instruction écrive son résultat dedans. L'état réservé réserve un registre en écriture, mais interdit les lectures dedans. * Le troisième état est celui d'un registre virtuel dans lequel une instruction a écrit son résultat, mais l'instruction n'a pas encore quitté le ROB. Le registre est alors appelé un ''registre écrit''. * Enfin, le quatrième est celui d'un ''registre architectural'', qui contient une donnée valide, dans le sens où il n'est associé à aucune instruction dans le ROB. Un registre passe normalement par ces quatre états dans l'ordre disponible, virtuel réservé, virtuel écrit, architectural. Une fois que l'instruction attribuée quitte le ROB, le registre virtuel devient un registre architectural. Cependant, cela suppose que l'instruction quitte le ROB normalement, ce qui n'est pas le cas suite à une mauvaise prédiction de branchement ou autre. Dans ce cas, il repasse immédiatement à l'état disponible. L'unité de renommage des registre mémorise l'état de chaque registre pour savoir dans quel état il est. Avec ce système, les données ne sont pas déplacées, ce qui a un avantage en termes de performance, de consommation énergétique et de simplification des circuits du processeur. Une fois enregistrées dans un registre, les données ne se déplacent plus. Avec un banc de registre virtuel séparé, elles sont recopiées d'un banc de registre virtuel vers un banc de registre architectural. Et les autres techniques que nous allons voir ont le même problème. Un autre avantage est que les opérandes proviennent toutes du même endroit : le banc de registre unique. Avec un banc de registre séparés, les opérandes peuvent provenir soit des registres architecturaux, soit des registres virtuels. Les opérandes peuvent donc provenir du banc de registre architectural, mais aussi du banc de registre renommés. La conséquence est que la gestion des interconnexions est beaucoup plus compliquée, notamment quand on veut implémenter le contournement. Pas de problèmes de ce genre avec un banc de registre unique. ===Le renommage dans le tampon de réordonnancement=== Il est possible de faire le renommage dans le tampon de réordonnancement (ROB, pour ''ReOrder Buffer'')). Cela veut dire que les résultats des instructions sont mémorisés dans le tampon de réordonnancement. Les entrées du ROB se voient ajouter un champ pour mémoriser le résultat d'une instruction, et ce champ sert de registre virtuel. Les entrées du ROB deviennent ainsi adressables, elle ont un numéro, qui sert de numéro/nom de registre virtuel. Avec cette technique, les registres architecturaux sont séparés des registres virtuels inclus dans le ROB. Les registres architecturaux sont regroupés dans un banc de registres spécialisé, appelé le '''''retirement register file'''''. La fenêtre d'instruction mémorise soit un nom de registre architectural, soit l'adresse de l'entrée du ROB qui contient le résultat voulu. Quand une instruction a tous ses opérandes de prêts, ceux-ci sont lus depuis le ROB ou les registres architecturaux. Pour cela, le ROB mémorise l'état de chaque entrée, de chaque registre virtuel : vide, réservée pour un résultat, écrite avec un résultat. [[File:Renommage dans le tampon de réordonnancement.png|centre|vignette|upright=2|Renommage dans le tampon de réordonnancement.]] Un défaut est que cela complexifie l'implémentation des interconnexions par rapport à un banc de registre physique. Il y a toujours un réseau de contournement pour réutiliser un résultat un cycle plus tard. Mais pour réutiliser un résultat deux cycles après ou plus tard, c'est le ROB qui s'en charge. Les opérandes peuvent être lues depuis trois sources : le ROB, le banc de registre architectural, et le réseau de contournement. Soit une source de plus qu'avec la technique précédente, ce qui complexifie les multiplexeurs de contournement. Et cela a un effet sur la latence des opérations, le temps de traversée de ces multiplexeurs n'est pas gratuit, sans compter le temps mis pour les signaux de commande pour les configurer. Le renommage de registre est aussi plus compliqué. Au lieu de simplement remplacer un numéro de registre architectural par un numéro de registre physique, on doit faire la différence entre numéro de registre et adresse dans le ROB, pour savoir où lire les opérandes. Sans quoi ne sait pas configurer les multiplexeurs et choisir la bonne source pour les opérandes. Un autre défaut est que les registres virtuels doivent être copiés dans les registres architecturaux quand une instruction quitte le ROB. La copie se fait ici du ROB vers le banc de registre architectural. Et copier la donnée a un cout en énergie que l'usage d'un banc de registre unique n'a pas. Un dernier défaut est que le nombre de registres virtuel est égal au nombre d'entrées dans le ROB. Or, on a vu plus haut que c'est un peu du gâchis, car beaucoup d'instructions ne produisent aucun résultat. Le hardware ajouté pour les entrées est donc sous-utilisé, il ne sert que si l'instruction fournit un résultat, mais ne sert à rien sinon. ===Le renommage avec un tampon de renommage=== Le dernier défaut évoqué dit que beaucoup d'entrées du ROB sont inutilisées et que les registres virtuels associés sont gâchés. Pour éviter cela, il est possible de sortir les résultats d'instruction du ROB et de les placer dans une mémoire FIFO complémentaire du ROB, appelée le '''tampon de renommage'''. Pour le dire autrement, le ROB amélioré est scindé en deux, avec le ROB proprement dit d'un côté, et un pseudo-ROB pour le renommage de registres et le contournement. Pour information, cette technique de renommage était utilisée dans d'anciens processeurs commerciaux, comme les Pentium Pro, le Pentium II, ou encore le Pentium III, ainsi que dans le Core 2 Duo. La technique est souvent expliquée en décrivant le tampon de renommage comme un banc de registre séparé pour les registres virtuels, appelé le '''banc de registres renommés''' (''rename register file''). Il est séparé du banc de registres architecturaux, avec lequel il communique. La raison à cela est que le tampon de renommage est adressable alors que le ROB ne l'est pas. Il faut dire que le tampon de renommage a tout d'un banc de registre : il contient les registres virtuels et seulement ceux-ci, on peut l'adresser, lire des opérandes dedans, écrire des résultats dedans, etc. Par contre, il y a une différence avec un banc de registre normal : il est traité comme une mémoire FIFO. Nous avons déjà vu comment implémenter une FIFO avec un banc de registre et quelques registres, aussi je ne fais pas de rappels sur ce point. S'il s'agissait d'un véritable banc de registre isolé, sans rien de plus, le ROB et la fenêtre d'instruction contiendraient des numéros de registres virtuels, qui adressent directement le banc de registre renommé. Une telle implémentation est possible, mais aucun processeur commercial n'a utilisé une telle implémentation. À la place, le banc de registre est en réalité une mémoire FIFO, dont les entrées sont adressables, avec de plus un système de cache utilisé pour faire la correspondance entre nom de registre architectural et nom de registre virtuel. Nous détaillerons ce dernier point dans la section qui explique comment fonctionne l'unité de renommage. Une fois que l'instruction attribuée quitte le ROB, le registre virtuel est libéré, à savoir qu'il repasse en état disponible. C'est à ce moment qu'à lieu la copie de la donnée dans le registre architectural adéquat. Il faut noter que le registre virtuel n'est pas effacé, il contient toujours l'ancienne donnée. Mais ce n'est pas un problème. S'il est réservé par une instruction, elle écrira dedans, ce qui écrasera cette donnée. Et de manière générale, il est impossible de lire cette donnée, que ce soit si le registre est disponible ou réservé. [[File:Renommage à banc de registres renommés.png|centre|vignette|upright=2|Renommage à banc de registres renommés.]] La technique partage tous les défauts du renommage dans le ROB. Le réseau de contournement est plus compliqué car les opérandes peuvent venir de trois sources : les deux bancs de registres et le réseau de contournement. De plus, les registres virtuels doivent être copiés dans les registres architecturaux, avec tout ce que cela implique. Déjà, déplacer des données implique un cout en énergie et en consommation d'électricité, qu'il vaut mieux éviter. De plus, il faut ajouter un port de lecture sur le banc de registre renommés, pour copier un registre sans nuire aux performances. Le banc de registres physiques n'a pas à être modifié : le port d'écriture existant suffit pour la copie, vu qu'on n'écrit dedans que pour copier des résultats d'instructions validées/terminées. Par contre, le cout en transistors est plus faible qu'avec le renommage dans le ROB, vu que le banc de registre a moins de registres virtuels que le ROB. La technique n'impose pas d'avoir autant de registres virtuels que d'entrées dans le ROB, ce qui permet d'en réduire le nombre. Par contre, comparé à l'usage d'un banc de registres unique, le cout en transistors est plus élevé. La différence se fait surtout au niveau des ports de lecture/écriture du banc de registres. La raison est que l'on doit ajouter un port de lecture pour copier les résultats dans les registres architecturaux. Du moins, dans le cas le plus simple. C'est le cas sur un processeur qui permet de compléter l'exécution d'une instruction par cycle. Mais certains processeurs peuvent terminer l'exécution de plusieurs instructions par cycle. C'est notamment le cas des processeurs dits superscalaires, qui sont capables de charger, exécuter et terminer plusieurs instructions par cycle. Ils auront droit à leur chapitre dédié, mais passons. Toujours est-il que si plusieurs instructions peuvent se terminer en même temps, il faut ajouter autant de ports de lecture sur le banc de registre pour copier leurs résultats. ===Le renommage physique-virtuel=== Les méthodes mentionnées au-dessus ont un léger problème : beaucoup de registres physiques sont gâchés, car attribués à une instruction dès l'étage de renommage et libérés lorsque on est certain qu'aucune instruction ne lira le registre physique attribué. Et parfois, les registres sont alloués trop tôt, alors qu'ils auraient pu rester libres durant encore quelques cycles. C'est notamment le cas quand l'instruction renommée attend un opérande dans la fenêtre d'instruction, ou quand le résultat est en cours de calcul dans l'unité de calcul. Ces situations ont toutes la même origine : le renommage a lieu tôt dans le pipeline, pour garder les dépendances entre instructions. Mais dans les faits, rien n'oblige à utiliser les registres architecturaux pour conserver les dépendances. On peut tout simplement attribuer un tag à chaque résultat d'instruction, tag qui ne correspond pas à un registre architectural. Et ce tag sera alloué à un registre architectural le plus tard possible, quand l'instruction fournira son résultat. Ce genre de méthodes a été formalisée avec ce qu'on appelle la technique du '''banc de registres physiques-virtuels''' (physical-virtual register file). Cette méthode demande d'ajouter une seconde table de correspondance, qui fait le lien entre le tag et le registre physique. De plus, la table d’alias de registres doit être modifiée : elle ne doit pas seulement faire la correspondance entre le nom de registre et le tag, mais aussi avec le registre physique s'il est déjà attribué. Ainsi, lors du renommage en sortie du décodeur, on peut renommer l'instruction avec les registres physiques si ceux-ci sont connus lors du renommage, et renommer avec des tags dans le cas contraire. Il faut noter que cette méthode a un léger problème : quand une instruction termine et que le résultat doit se voir attribuer un tag, il se peut parfaitement qu'il n'y ait plus de registre physique de libre. Les solutions pour régler ce problème sont assez complexes, aussi je n'en parlerai pas ici. ==L’unité de renommage== Pour commencer, sachez qu'il existe deux méthodes pour implémenter l'unité de renommage. La première utilise une unité à part, séparées, qui contient de nombreuses mémoires caches/FIFO pour stocker des informations nécessaires pour le renommage de registres. La seconde mémorise les informations de renommage dans le ROB ou le tampon de renommage, voir ailleurs. Les deux types d'unités de renommage sont assez différentes. Pour les désigner, je vais parler d''''implémentation par ''tag''''' et d''''implémentation associative'''. De plus, les deux types d'unités ne sont pas utilisées sur les mêmes implémentations. Par exemple, avec un banc de registre physique, il est naturel d'utiliser une implémentation par ''tag'', l'autre implémentation n'est pas pratique. Aussi, tous les processeurs avec un banc de registre physique utilisent une unité de renommage pat ''tag''. Mais pour le renommage dans le ROB, c'est l'inverse : les deux implémentations sont possibles, mais il est préférable d'utiliser une implémentation associative. Pour simplifier les explications, je vais commencer par décrire le premier type, avec un banc de registre physique. {|class="wikitable" |- ! ! Implémentation par ''tag'' ! Implémentation associative |- ! Banc de registre physique | Seule implémentation possible | |- ! Renommage dans le ROB | Possible, certains processeurs utilisent la technique | Possible, certains processeurs utilisent la technique |- ! Renommage dans un tampon de renommage | Possible, tous les processeurs utilisent la technique | Possible, aucun processeur connu |} ===Les unités de renommage par ''tag''=== Les registres architecturaux sont identifiés par des noms de registre, tout comme les registres physiques. Les noms de registre des registres physiques sont appelés des '''tags'''. Les registres physiques sont bien plus nombreux que les registres architecturaux, sans quoi le renommage de registre n'a aucun intérêt. La conséquence est que les numéros de registres physiques sont plus grands que les numéros de registres architecturaux. Le renommage de registres remplace les noms/numéros de registres architecturaux par des numéros de registres physiques, par des ''tags''. D'où le terme de ''renommage'' de registre. Ce remplacement est effectué dans un étage supplémentaire du pipeline, intercalé entre le décodage et l'émission. [[File:Renommage de registres.png|centre|vignette|upright=2|Renommage de registres.]] Le remplacement se fait cependant différemment pour le registre de destination et les registres d'opérandes. Le registre de destination est associé à un registre virtuel disponible. L'allocation du registre de destination prend un registre disponible et le place en état réservé. Une nouvelle correspondance entre registre architectural et virtuel est alors crée. Mais pour les opérandes, c'est autre chose. Les opérandes sont soit dans un registre architectural, soit dans un registre virtuel en état écrit. Les noms de registres architecturaux pour les opérandes doivent être remplacé par le nom de registre adéquat, déjà attribué. Une sacrée différence : on attribue un registre virtuel pour le résultat, les opérandes ont déjà un registre virtuel attribué. Un autre point est que l'unité de renommage ne fait pas que modifier les registres opérande/de destination. Elle gère aussi la disponibilité des opérandes. Plus haut, on a dit qu'un registre virtuel pouvait être dans quatre états. Soit il est disponible, soit il est réservé, soit il est écrit, soit la donnée est dans un registre architectural. L'unité de renommage est au courant de l'état de chaque registre. Les quatre états possibles sont utilisés pour diverses raisons. Et il y a deux manières pour mémoriser la correspondance entre un registre physique et un registre architectural. La première est d'utiliser un circuit à part, qui fait partie de l'unité de renommage, l'autre est d'intégrer les correspondances dans le ROB ou toute autre structure dédiée. Pr exemple, on peut ajouter la correspondance dans le ROB, dans la fenêtre d'instruction, éventuellement dans le banc de registres renommés ! Dans ce qui suit, nous allons parler du cas où on utilise une mémoire à part, partie intégrante de l'unité de renommage de registre. Nous verrons l'intégration dans le ROB après, de même que la relation avec les quatre techniques précédentes. ===Renommer le registre de destination : la liste de disponibilité=== Renommer un registre de destination revient à lui attribuer un registre virtuel inutilisé. Pour cela, certaines techniques de renommage de registres mémorisent la liste des registres vides/disponibles dans une petite mémoire : la '''liste de disponibilités''' (''free list''). L'implémentation de la liste de disponibilité varie grandement d'un processeur à l'autre. Avec certaines méthodes de renommage de registre, qui impliquent un ROB, on peut même s'en passer totalement. Dans sa version la plus simple, elle contient un bit pour chaque registre, qui indique s'il est disponible ou non. Le bit en question est mis à jour quand une instruction entre/quitte le tampon de réordonnancement avec ce registre comme opérande. Lorsqu'un registre est attribué, il quitte la liste de disponibilités. Lorsque son instruction quitte le ROB, le bit de disponibilité est mis à jour. Une version plus évoluée utilise une mémoire FIFO qui contient des noms de registres. Le renommage du registre de destination utilise cette FIFO pour sortir le numéro de registre virtuel adéquat. Dans la suite, nous partirons du principe que c'est cette méthode qui est utilisée. Il faut noter que si le renommage est fait dans le ROB, la liste de disponibilité est inutile. La raison est que le ROB est déjà une mémoire FIFO, comme la liste de disponibilité. De plus, les registres virtuels sont dans le ROB. Attribuer un registre virtuel consiste simplement à mobiliser une entrée vide du ROB, en enfilant l'instruction dedans. Le ROB sait quelle est la prochaine entrée à allouer à une instruction émise, du fait de son caractère FIFO. ===La table de correspondances de registres=== Le renommage du registre de destination attribue un registre virtuel pour un résultat d'instruction. Reste qu'il faut maintenir cette attribution tant que l'instruction ne s'est pas terminée. Pendant toute la durée de vie de l'instruction, il faut se souvenir que tel registre virtuel correspond à tel résultat d'instruction, mais aussi à tel registre architectural. En effet, les instructions suivantes vont lire ce résultat pour l'utiliser comme opérande. Elle encoderont ce résultat/opérande avec le registre architectural censé contenir l'opérande. Le renommage de registre devra alors remplacer les registres opérande par le registre virtuel adéquat, qui contient l'opérande. Le renommage des registres opérande est le rôle de la '''table d’alias de registres''' (''register alias table''), une mémoire qui contient le registre virtuel associé à chaque registre architectural. Elle mémorise précisément une table de correspondance entre registre architectural et registre virtuel/physique. La table d'alias contient une correspondance par registre architectural, qui change au gré du renommage des instruction. De plus, elle contient un bit qui indique si le registre a été renommé ou non. S'il n'a pas été renommé, alors l'opérande est disponible dans les registres architecturaux, pas dans un registre virtuel. C'est très utile sur les processeurs avec un banc de registre séparés pour les registres architecturaux. Cela permet de savoir où lire les opérandes. La correspondance en question est mise à jour quand une instruction est émise, et quand elle termine. Premièrement, elle est mise à jour à chaque fois qu'un registre de destination est renommé. En clair, la table d’alias de registres est mise à jour à chaque lecture dans la table de disponibilité. Le registre virtuel choisi dans la liste de disponibilités est attribué au registre architectural de destination du résultat. Il suffira de réutiliser cette correspondance par la suite. Deuxièmement, elle est mise à jour quand une instruction termine, quand elle quitte le ROB. Le ROB envoie alors le numéro du registre virtuel qui contenait le résultat enregistré dans les registres architecturaux. Le numéro est alors enregistré dans la table de disponibilité. De plus, la correspondance associée dans la table d'alias est effacée. [[File:Renommage de registres - circuits.png|centre|vignette|upright=2|Unité de renommage de registres.]] Il existe deux façons pour implémenter la table d'alias. La plus ancienne consiste à utiliser une mémoire associative dont le tag contient le nom du registre architectural et la donnée le nom du registre physique. Sur les processeurs plus récents, on utilise une mémoire RAM, dont les adresses correspondent aux noms de registres architecturaux, et dont le contenu d'une adresse correspond au nom du registre physique associé. On n'a alors qu'une seule correspondance entre registre physique et registre architectural, mais cela ne pose pas de problème si on renomme les instructions dans l'ordre d'émission (ce qui est toujours fait). Notons que la table l'alias doit avoir deux ports de lecture : un pour chaque opérande. Et cela vaut peu importe son implémentation : le cache comme la RAM doivent avoir deux ports. ==L'interaction entre renommage de registres et autres optimisations== L'unité de renommage de registre ne fait pas que du renommage de registre, mais joue aussi un rôle dans l'exécution dans le désordre, et prend en charge des rôles annexes à son rôle principal. Par exemple, elle est impliquée pour corriger les mauvaises prédiction de branchement, les exceptions matérielle ou toute situation qui demande de vider le pipeline. En dehors de ces situations, un registre passe normalement par ces quatre état dans l'ordre disponible, réservé, écrit, architectural. Mais ce n'est pas le cas lors d'une mauvaise prédiction de branchement ou autre. Dans ce cas, tous les registres passent immédiatement à l'état disponible, sauf les registres architecturaux. ===La récupération après une prédiction invalide=== Quand une exception ou une mauvaise prédiction de branchement a lieu, les registres virtuels contiennent des données potentiellement invalides, contrairement aux registres architecturaux. En cas de mauvaise prédiction de branchement, la table d’alias de registres est partiellement vidée. Par vidée, on veut dire qu'on élimine les correspondances avec les registres virtuels invalidés. De plus, on doit remettre la table d’alias de registres et la table de disponibilités dans l'état antérieur au chargement de l'instruction qui a déclenché la mauvaise prédiction de branchement ou l'exception matérielle. Et cela peut se faire de différentes manières. La première solution s'applique au renommage dans le ROB. Il suffit de stocker dans le tampon de réordonnancement ce qui a été modifié dans l'unité de renommage de registres par l'instruction correspondante. Ainsi, lorsqu'une instruction sera prête à valider, et qu'une exception ou mauvaise prédiction de branchement aura eu lieu, les modifications effectuées dans l'unité de renommage de registres seront annulées les unes après les autres. Une autre solution consiste à garder un historique des changements fait dans la table d'alias. Lorsqu'une mauvais prédiction est détectée, le processeur traverse l'historique et annule les changements faits un par un. Le tout est assez lent, vu que la traversée se fait un renommage à la fois. Une optimisation utilise une copie valide de la table d’alias de registres dans une mémoire à part, pour la restaurer au besoin. Si jamais le processeur détecte un branchement, la table d’alias de registres est sauvegardée dans une mémoire intégrée au processeur. On peut utiliser plusieurs mémoires de sauvegarde de ce type, pour gérer une succession de branchements. ===La gestion des opérandes disponibles : "lecture avant émission" et "lecture après émission"=== Passons maintenant à un détail d'implémentation de l'exécution dans le désordre. La '''lecture après émission''' utilise des fenêtres d'instruction proprement dit, alors que la '''lecture avant émission''' utilise des stations de réservation. Rappelons que la différence est que les premières émettent les instruction puis lisent les registres, alors que la seconde lit les opérandes depuis les registres juste après le renommage de registre, pour les stocker dans la fenêtre d'instruction. La différence est donc que les étages sont répartis différemment. Avec la "lecture avant émission", l'étage de lecture des opérandes est placé entre l'étage de renommage de registres et l'étage d'émission. Avec la "lecture après émission", l'étage de lecture des registres est situé après l'étage d'émission/''select/wakeup''. Les deux méthodes de lecture avant ou après émission montrent une grande différence pour ce qui est de déterminer la disponibilité des opérandes. Pour la lecture après émission, le renommage de registre n'est pas impliqué. Mais avec la lecture avant émission, les opérandes sont lues dans les registres entre le renommage des registres et l'entrée dans la station de réservation. Ce qui implique qu'on sait quelles sont les opérandes disponibles lors du renommage de registres. Pour cela, l'unité de renommage de registre contient, pour chaque registre virtuel, un bit ''ready'' qui indique que le registre renommé contient une opérande est disponible. S'il est à 0, l'instruction est placée dans la fenêtre d'instruction, et attend que ses opérandes soient disponibles. S'il est à 1, les opérandes sont soit lues dans le registre et placées dans la fenêtre d'instruction, soit lues au moment d'exécuter l'instruction. L'ensemble de ces bits est en plus des bits de disponibilité de la station de réservation, dans l'implémentation la plus simple. Les deux sont alors mis à jour en même temps. Une autre solution regroupe les bits de disponibilité dans une structure matérielle appelée la '''table de disponibilité''', qui est consultée à la fois par les stations de réservation et l'unité de renommage de registres. L'avantage de cette solution est que les signaux de réveil sont envoyés à une seule structure matérielle, pas plus. De plus, il n'y a pas de duplication des bits de disponibilité. Non pas que quelques bits fassent une grosse différence, mais tout est bon à prendre. Notons qu'avec un banc de registres physique, le bit de disponibilité se déduit des quatre états que peut prendre un registre physique. L'unité de renommage de registre doit sait déjà quelles sont les opérandes indisponibles, celles qui ont déjà été calculées, et celles enregistrées dans un registre architectural. Notons qu'un registre virtuel, une fois attribué, peut être en état réservé ou écrit. Seul l'état écrit correspond à une opérande disponible, l'unité de renommage doit donc pouvoir faire la différence. Pour savoir si un registre virtuel contient une donnée valide, la table d’alias de registres lui associe un bit de validité qui est mis à jour lors de l'écriture du résultat dans le registre virtuel correspondant. Elle doit aussi mémoriser quels registres architecturaux ont une donnée valide. Rappelons que le processeur sait qu'une opérande est disponible parce qu'un signal de réveil est généré dans le pipeline, pour dire que telle opérande est disponible. Le signal transmet l'opérande avec le numéro de registre (virtuel ici). Le numéro de registre transmis est alors comparé avec tous les numéros de registre des opérandes, dans la fenêtre d'instruction et/ou les stations de réservation. Il est possible qu'une instruction soit dans l'étape de renommage alors que le signal de ''réveil'' est envoyé. Si rien n'est fait, l'instruction sera insérée dans la fenêtre d'instruction avec une opérande indisponible, alors qu'elle l'est. Elle restera alors indéfiniment dans la fenêtre d'instruction, vu que le signal de ''réveil'' n'est généré qu'une seule fois. Pour éviter cela, le signal de ''réveil'' doit aussi être envoyé aux instructions dans l'unité de renommage de registre. Une solution pour éviter cela est d'enregistrer directement les noms de registres renommés dans la fenêtre d'instruction, dès la sortie de l'unité de renommage. Les noms de registres sautent un étage de pipeline, donc. La comparaison avec les signaux de réveil se fait alors dans la fenêtre d'instruction, pas besoin de rajouter un comparateur dans l'étage de lecture des registres. Une autre solution est d'utiliser une table de disponibilité unique. Les signaux de réveil ne sont alors pas perdus. ==Les optimisations liées au renommage de registres== Le renommage de registre permet d'implémenter certaines optimisations annexes, qu'on ne croirait pas reliées au renommage de registre au premier abord. Par exemple, saviez-vous que le renommage de registre permet d'exécuter certaines instructions directement dans l'unité de renommage ? De nombreuses optimisations éliminent certaines instructions, en les remplaçant par des renommages de registres. Il existe plusieurs optimisations de ce genre, qui portent les noms barbabres d'élimination des MOV, de calcul trivial, et de remplacement d'idiomes. ===L'élimination des MOV=== La première optimisation de ce genre élimine les instructions de copie d'un registre dans un autre (les MOV) ou d’échange entre deux registres (XCHG). Ce qui explique pourquoi l'optimisation en question porte le nom d''''élimination des MOV''' (''MOV elimination''), sous-entendu, des MOV inter-registres. L'élimination des MOV est une technique implémenté sur les processeurs à banc de registre physique. En clair, il faut qu'il y ait un seul banc de registre. C'est important, car c'est la seule méthode qui fait que les registres virtuels n'ont pas à être copiés dans les registres architecturaux, ce qui facilite grandement l'implémentation d’une technique visant à éliminer des copies de registres. L'idée est qu'après une copie, le contenu des deux registres source et destination est identique tant qu'aucun des deux registres ne subit d'écriture. On peut alors utiliser un seul registre physique pour la valeur mémorisée, toute lecture des deux registres lisant celui-ci. Lors d'une écriture dans un de ces deux registres, le renommage attribuera un nouveau registre physique pour la nouvelle valeur écrite. : Formellement, il s'agit d'une optimisation de type '''''copy-on-write'''''. ===Le remplacement d'idiomes=== Le même genre d'optimisation peut être effectué avec des instructions inutiles, dont le résultat vaut zéro. En effet, il existe de nombreuses méthodes pour mettre un registre à zéro. Faire un XOR entre le registre et lui-même, le soustraire à lui-même ou le multiplier par zéro sont quelques exemples. Et de tels idiomes sont utilisés par les compilateurs, au lieu d'un simple MOV 0 -> registre, qui copie un 0 dans le registre. La raison est que les instructions MOV sont assez longues, vu qu'elles doivent intégrer une constante immédiate. Le renommage de registres peut être utilisé pour éliminer ces opérations et les remplacer par une mise à zéro du registre. L'optimisation en question porte le nom de '''remplacement d'idiomes'''. Elle est implémentée depuis les processeurs Pentium de microarchitecture P6, et leurs équivalents AMD. Un autre avantage de cette optimisation est que cela casse de fausses chaines de dépendances. Par exemple, une opération "reg XOR reg" faire croire au processeur qu'il y a une dépendance de donnée entre cette instruction, et les instructions précédente qui écrivent dans le registre. Le XOR est donc exécuté après ces instructions faussement dépendantes, alors que la mise à zéro du registre pourrait se faire en parallèle en utilisant un registre virtuel séparé. Les techniques précédentes permettent d'éliminer ces fausses dépendances. ===Les optimisations de calculs triviaux=== Il existe aussi des opérations dont le résultat est une des opérandes. On pourrait citer comme exemples les décalages par 0, les additions et soustractions avec 0, la multiplication par 1, et bien d'autres. En utilisant intelligemment le renommage de registres, ces calculs ne sont pas effectués. Les techniques qui permettent cela sont des techniques dites de '''calcul trivial''' (trivial computation). La détection des calculs simplifiables demande de comparer les opérandes avec 0 ou 1, via un paquets de comparateurs regroupés dans une unité de détection des calculs triviaux. Leur simplification se fait dans la table de renommage de registres, le registre de destination de l'instruction simplifiée étant renommé pour pointer sur l'opérande/résultat. Si le résultat est nul, on considère que la correspondance est invalide et on met le registre physique à une valeur invalide, similaire au pointeur NULL du C. Si un calcul lit un registre physique invalide, celui-ci est automatiquement simplifié par l'unité de renommage, l'autre opérande étant alors attribué automatiquement comme registre de destination du résultat dans la table de renommage. Mais cette technique a tendance à modifier la latence des instructions, qui est réduite après simplification. Cela pose problème au niveau des circuits d'émission et du planificateur, qui n'aiment pas les latences variables, comme on l'a vu il y a quelques chapitres. <noinclude> {{NavChapitre | book=Fonctionnement d'un ordinateur | prev=L'exécution dans le désordre | prevText=L'exécution dans le désordre | next=Le scoreboarding et l'algorithme de Tomasulo | nextText=Le scoreboarding et l'algorithme de Tomasulo }} </noinclude> o0b35n9294hxa44ljah5em0idnlt2y6 Fonctionnement d'un ordinateur/Les processeurs superscalaires 0 65956 773236 773217 2026-09-26T13:39:15Z Mewtow 31375 /* Les processeurs superscalaires dynamiques */ 773236 wikitext text/x-wiki Les processeurs vus auparavant ne peuvent émettre au maximum qu'une instruction par cycle d'horloge. Ils peuvent avoir plusieurs instructions qui s'exécutent en même temps, dans des unités de calcul séparées. C'est le cas dès qu'une instruction multi-cycles s'exécute. Par contre, ils ne peuvent démarrer qu'une seule instruction par cycle. Et quand on court après la performance, ce n'est pas assez ! Les concepteurs de processeurs ont inventés des processeurs qui émettent plusieurs instructions par cycle : les '''processeurs à émissions multiples'''. Un processeur à émission multiple charge plusieurs instructions en même temps, les décode en parallèle, puis les émet en même temps sur des unités de calculs séparées. Pour cela, il faut gérer les dépendances entre instructions, répartir les instructions sur différentes unités de calcul, et cela n'est pas une mince affaire. En général, pour un processeur à émission multiple capable d'émettre N instructions en parallèle, on parle de '''processeur à N voies''', ou encore un ''processeur à N pipelines''. [[File:Superscalarpipeline.svg|centre|vignette|upright=1.5|Pipeline RISC classique à cinq étages sur un processeur superscalaire. On voit bien que plusieurs instructions sont chargées en même temps.]] Les processeurs à émission multiple sont de deux types : les processeurs VLIW et les '''processeurs superscalaires'''. Nous mettons les processeurs VLIW de côté en attendant le prochain chapitre. La raison est qu'ils ont un jeu d'instruction spécialisé qui expose le parallélisme d'instruction directement au niveau du jeu d'instructions, là où les processeurs superscalaires restent des processeurs au jeu d'instruction normal. La différence principale entre processeur VLIW et superscalaire est qu'un processeur superscalaire répartit les instructions sur les unités de calcul à l’exécution, là où un processeur VLIW délègue cette tâche au compilateur. ==Le nombre de voies d'un processeur superscalaire== Les processeurs superscalaires les plus simples ne permettent que d'émettre deux micro-opérations à la fois, d'où leur nom de processeur ''dual issue'', ce qui se traduit en '''processeur à double émission'''. Les processeurs triple émission permettent eux d’émettre trois instructions à la fois. A l'opposé, les CPU superscalaires les plus puissants peuvent émettre jusqu'à 8-10 instructions simultanément. En 2026, les CPU superscalaires peuvent monter jusqu’à 10 µops émises en même temps, ce qui est énorme ! Et cela nous amène à faire une distinction entre les processeurs superscalaires dits '''étroits''' et ceux dits '''larges'''. Dans ce wikilivre, on part du principe que les processeurs superscalaires étroits émettent jusqu'à 4-5 µops à la fois, alors que les larges peuvent en émettre plus, l'intervalle entre les deux est une zone grise. Le nombre exact n'est pas fixé dans le marbre, la distinction est volontairement vague. Nous parlerons de la '''largeur d'un CPU superscalaire''' pour parler du nombre de voies, du nombre de µops émises en même temps. Plus un CPU superscalaire est large, plus il est performant ''toute chose égale par ailleurs''. Mais dans les faits, vous pouvez oublier la partie en italique. Élargir un CPU superscalaire demande de rajouter beaucoup de circuits. Et ce cout en circuit aura un impact sur la performance. A budget en transistors limité, il y a un compromis entre élargir un CPU et rajouter du cache ou la taille des fenêtres d'instructions/ROB et autres. ===Les contraintes d’appariement=== Prenons un processeur double émission, capable d'émettre deux instructions simultanément. Émettre deux instructions en même temps n'est pas toujours possible. Par exemple, si deux instructions sont dépendantes, on ne peut pas les émettre en même temps. Mais oublions un instant ces histoires de dépendances de données, et partons du principe que le CPU ait affaire à deux instructions indépendantes, une paire idéale. Un CPU double émission "parfait" pourrait exécuter n'importe quelle paire d'instruction. Par exemple, il pourrait émettre deux multiplications consécutives, deux lectures consécutives, une lecture et une écriture en même temps, etc. En pratique, aucun processeur ne permet cela. Implémenter un CPU superscalaire "parfait" aurait un cout en circuit beaucoup trop conséquent. Par exemple, pour émettre deux multiplications en même temps, il faudrait deux multiplieurs séparés. Idem pour exécuter deux accès mémoire simultané : cela demande d'avoir deux unités mémoire et un cache multiport. Mais en pratique, aucun CPU superscalaire ne fait cela. La totalité des CPU double émission se débrouille avec un seul circuit multiplieur et une seule unité mémoire. Et cela entraine l'apparition de dépendances structurelles, qui font que certaines paires d'instructions sont impossibles. Par exemple, impossible d'émettre deux multiplications en même temps avec un seul circuit multiplieur. Mais émettre une multiplication en parallèle d'une addition est parfaitement possible. Il y a donc des paires d'instructions autorisées et des paires interdites. Et la logique est la même sur les CPU à triple ou quadruple émission, et au-delà. : des triplets ou quadruplet d'instructions sont impossibles à émettre simultanément, alors que d'autres le sont. Pour le dire autrement, il y a des '''contraintes d'appariement''' sur les instructions à émettre en même temps. ===L'impact des contraintes d’appariement sur la performance=== Ces contraintes d’appariement ont un impact sur les performance, mais celui-ci peut être grandement mitigé. Les programmes informatiques utilisent en pratique beaucoup plus d'opérations entières de base que de multiplication, branchements et accès mémoire. Il y a environ 5 opérations entières de base pour une multiplication entière. En pratique, il est rare de devoir exécuter deux multiplications à la fois. Idem pour les décalages et rotations, qui sont assez éloignés les uns des autres. Deux branchements consécutifs sont quant à eux plus fréquents. Deux accès mémoire consécutifs est assez rare, mais ils restent assez proches les uns des autres malgré tout. La conséquence est que certaines paires d'instructions sont privilégiées par rapport au reste. Les paires regroupant deux calculs basiques (additions, soustractions, ...) sont systématiquement supportées, car dupliquer des ALU entières est facile. Les paires regroupant un calcul et une autre instruction sont elles aussi systématiquement supportées. Par contre, les paires avec deux branchements, deux accès mémoire, deux décalages/rotations, ou deux multiplications/divisions sont fréquemment interdites. Seuls les CPU superscalaires larges peuvent les permettre, et encore : sous conditions. Le résultat est que le processeur peut se débrouiller avec une seule unité mémoire et une unité de branchement. ===Terminologie pour la suite du chapitre=== Décrire les contraintes d’appariement demande de dire quelles combinaisons d’instructions sont autorisées ou interdites. Et pour cela, nous allons introduire les abréviations suivantes : * INT est un raccourcis pour parler d'une opération entière simple, qu'une ALU entière peut exécuter. * MUL correspond à une opération entière complexe, typiquement une multiplication, éventuellement à une division. * FLOAT désigne une opération flottante, peut importe laquelle. * MEM désigne un accès mémoire, à savoir une instruction ou µop mémoire. * BRANCH désigne une instruction de branchement. Cette terminologie permettra de définir des paires d'instructions, qu'elles soient possibles ou interdites. Par exemple, l'expression INT + FLOAT désigne une paire d'instruction regroupant une instruction entière et une instruction flottante, peu importe l'ordre des deux instructions. Il en sera de même pour les triplets, quadruplets, ou plus. Par exemple, l'expression INT + INT + FLOAT + MUL désigne un quadruplet d'instructions regroupant : deux instructions entières, une instruction flottante, et une multiplication entière. J'utiliserais aussi des parenthèses pour regrouper certaines combinaisons. Par exemple, l'expression INT + (MUL /FLOAT) veut parler d'une paire d'instruction où la première est une opération entière basique, la seconde est : soit une multiplication, soit une opération flottante. ==Le ''front-end'' des processeurs superscalaires== Un processeur superscalaire doit charger, décoder, et exécuter plusieurs instructions par cycle. Par exemple, un processeur triple émission doit charger 3 instructions lors d'un premier cycle, puis les décoder au cycle suivant, et les exécuter au cycle suivant. Il travaille donc pas paquets de 3 instructions, dans cet exemple. Intuitivement, charger, décoder et exécuter plusieurs instructions à la fois demande de dupliquer des circuits. Par exemple, il faut dupliquer les décodeurs pour décoder plusieurs instructions. Les unités de calcul sont aussi dupliquées, sauf sur certaines processeurs qui arrivent à ruser, mais laissons cela de côté pour plus tard. Mais d'autres circuits sont simplement modifiés, comme l'unité de chargement ou l'unité d'émission. Voyons ce qu'il en est dans le détail. {|class="wikitable" |- ! rowspan="2" | Processeur sans émission multiple | rowspan="2" | Chargement | rowspan="2" | Décodage | rowspan="2" | Renommage | rowspan="2" | Émission | Exécution / ALU | rowspan="2" | ''Commit''/ROB |- | Exécution / ALU |- ! rowspan="4" | Processeur superscalaire | rowspan="4" | Chargement | rowspan="2" | Décodage | rowspan="4" | Renommage | rowspan="4" | Émission | Exécution / ALU | rowspan="4" | ''Commit''/ROB |- | Exécution / ALU |- | rowspan="2" | Décodage | Exécution / ALU |- | Exécution / ALU |} Nous allons d'abord voir ce qu'il en est pour le ''front-end'', à savoir la partie du CPU qui s'occupe de charger et décoder les instructions. Elle regroupe l'unité de chargement, les décodeurs, l'unité de prédiction de branchement, la file de µops, la file d'instruction, et divers caches. En soi, les changements portent surtout sur l'unité de chargement et les décodeurs. ===Le chargement de plusieurs instructions consécutives=== Un processeur superscalaire charge plusieurs instructions en même temps, ''en tenant compte de la présence de branchements''. La partie en italique est importante et elle a une conséquence très forte : la manière de faire est fortement différente entre un CPU superscalaire étroit et large. Un CPU superscalaire étroit fait au plus simple : il charge un bloc de 2, 3, 4 instructions consécutives en mémoire RAM. Il faut alors doubler/tripler/quadrupler la taille du bus connecté au cache d'instruction. Par exemple, pour charger quatre instructions de 32 bits chacune, il suffit de lire un bloc de 128 bits dans le cache d'instruction. Un bloc de 8, 16, 32 octets est donc lu depuis le cache et est ensuite découpé en instructions, envoyées chacun à un décodeur. Charger plusieurs instructions consécutives ne marche bien qu'en absence de branchements. La présence de branchements casse cette dynamique, et il faut gérer leur présence. * Sans prédiction de branchement, toutes les instructions d'un bloc sont décodées et exécutées en même temps, même s'il y a un branchement dans le tas. * Avec prédiction de branchement, les instructions situées après le branchement ne sont pas décodées ni exécutées. L'unité de chargement coupe le bloc chargé au niveau du premier branchement pris, remplit les vides avec des NOP, avant d'envoyer le tout à l'unité de décodage. Les instructions à l'adresse de destination seront chargées au cycle suivant, en chargeant un nouveau bloc. [[File:Fetch sur un processeur superscalaire avec prediction de branchements.png|centre|vignette|upright=2|Fetch sur un processeur superscalaire avec prédiction de branchements.]] Un autre défaut est que sur le CPU charge un bloc de 128 bits à la fois, les instructions doivent être alignées sur 128 bits. Et on peut généraliser à n'importe quelle taille de bloc. Si les instructions ne sont pas alignées, il faut gérer la situation. Il arrive qu'une instruction soit à cheval sur deux blocs, avec un morceau dans le premier bloc, un autre dans le second. Dans ce cas, l'instruction est chargée en deux fois, et des circuits doivent gérer la situation pour recoller les morceaux. Sur les CPU avec des instructions de taille fixe, le problème ne survient que rarement. Mais il est fréquent avec des instructions de taille variable. Un autre défaut est lié aux branchements. Imaginez qu'un branchement s'exécute et branche vers une instruction de destination. Rien n'indique que l'instruction de destination sera au tout début d'un bloc. Si ça se trouve, elle sera en troisième position d'un bloc de 4 instructions. Une solution simple charge le bloc en respectant l'alignement, et les deux premières instructions du bloc ne sont pas à exécuter. Il y a donc une petite perte de performance. Certains CPU, comme l'IBM RS/6000, ont la machinerie charger un bloc de N instructions même s'il n'est pas aligné. La machinerie en question se trouve dans le cache d'instruction et est tout sauf simple à expliquer,ce qui fait que je me permets de la passer sous silence. Un détail, cependant : elle ne permet pas de charger des blocs qui sont à cheval sur deux lignes de cache. ===Le décodage parallèle des instructions=== Pour le décodage des instructions, plusieurs instructions sont décodées en même temps. On parle alors de '''décodage parallèle'''. Pour ce faire, un processeur superscalaire contient plusieurs décodeurs, chacun pouvant décoder une instruction en parallèle des autres. Prenons par exemple un processeur superscalaire à 4 voies : le décodage se fera dans quatre décodeurs séparés, qui fonctionneront en parallèle. Cependant, cette implémentation ne marche bien que pour les processeurs RISC, les processeurs CISC devant faire avec un microcode gourmand en circuits. Les processeurs CISC utilisent des décodeurs hybrides, avec un microcode qui complémente un décodeur câblé. Dupliquer le microcode aurait un cout en transistors trop important, ce qui fait que seuls les décodeurs câblés sont dupliqués. Les CPU CISC superscalaires disposent donc de plusieurs décodeurs simples, capables de décoder les instructions les plus courantes, avec un seul microcode. La conséquence est qu'il n'est pas possible de décoder deux instructions microcodées en même temps. Par contre, il reste possible de décoder plusieurs instructions non-microcodées ou une instruction microcodée couplée à une instruction non-microcodée. Vu qu'il est rare que deux instructions microcodées se suivent dans un programme, le cout en performance est extrêmement mineur. Il faut noter qu'une instruction correspond à une ou plusieurs µops. Il est donc possible de charger 5 instructions et de se retrouver avec 8 µops en sortie des décodeurs. Et c'est la source d'une distinction très importante. Plus haut, j'ai parlé de processeurs superscalaires à N voies. La terminologie peut avoir deux interprétations. La première est que le processeur charge/décode N instructions à la fois. Une seconde est qu'il est capable d'exécuter/émettre N µops en même temps. Les deux ne sont pas équivalentes, vu qu'un processeur superscalaire peut, par exemple, charger/décoder N instructions et émettre/exécuter N+2 µops. La distinction est particulièrement importante sur les processeurs superscalaires larges. Certains d'entre eux sont capables de charger 5 instructions et d'en émettre le double ! Un exemple classique était les processeurs Athlon d'AMD, qui pouvaient décoder deux instructions par cycle, mais émettre 4 µops par cycle. Ce n'était pas un problème, car beaucoup d'instructions, même basiques, étaient décodées en 2 µops. ===L'émission parallèle des instructions=== La superscalarité modifie en profondeur l'unité d'émission, notamment au niveau de son interface. Avant, elle recevait une micro-opération sur son entrée, et fournissait la micro-opération émise sur une sortie. Et cela vaut aussi bien pour une unité d'émission simple, un ''scoreboard'', une fenêtre d'instruction ou des stations de réservation. Mais avec l'émission multiple, les sorties et entrées sont dupliquées. Pour la double émission, il y a deux entrées vu qu'elle doit recevoir deux micro-opération décodées/renommées, et deux sorties pour émettre deux micro-opérations. Dans ce qui suit, nous ne parlerons pas d'entrée et de sortie, mais de '''ports de décodage''' et de '''ports d'émission'''. Les ports de décodage reçoivent des micro-opérations provenant de l'unité de décodage, les ports d'émissions sont pour les micro-opérations émises. Les ports d'émission injectent les micro-opérations émises dans le pipeline, elle sont propagée au banc de registre, aux ALUs, etc. Un processeur superscalaire a forcément plusieurs ports d'émission (et de décodage). Si l'interface de l'unité d'émission change, l'intérieur est tout autant modifié. Sur un CPU non-superscalaire, l'unité d'émission reçoit une µop "candidate à l'émission", et détecte les dépendances entre avec les µops en vol, en cours d'exécution dans le pipeline. Et c'est toujours le cas sur les processeurs superscalaires : l'unité d'émission détecte les dépendances entre les µops candidates et les µops en vol. Mais avec la siperscalarité, elle doit en plus tester les dépendances entre les µops candidates. Si l'unité d'émission est un ''scoreboard'', il doit détecter les dépendances entre instructions émises simultanément et les sérialiser si besoin. Avec une fenêtre d'instruction, il n'y a pour ainsi dire rien à faire, vu que les dépendances sont éliminées par le renommage de registre et que les signaux de réveil gèrent les dépendances RAW. C'est la raison pour laquelle les processeurs superscalaires modernes n'utilisent pas de ''scoreboard''. ==Les processeurs implémentés avec plusieurs pipelines== Nous venons de voir comment la superscalarité impacte le ''front-end'', il est maintenant de voir ce qu'il en est de l'unité d'émission et des ''back-ends''. Un processeur superscalaire peut utiliser l'exécution dans le désordre et le renommage de registres, mais ce n'est pas une obligation. Historiquement, les premiers processeurs superscalaires n'utilisaient pas d'exécution dans le désordre. Le meilleur exemple est celui du Pentium 1 et 2 d'Intel. Ils étaient tous deux des processeurs superscalaires, mais n'avaient pas d'exécution dans le désordre, qui est apparue sur le Pentium 3. les explications sont plus simples si on met de côté l'exécution dans le désordre. Vous vous dites que l'implémentation d'un processeur superscalaire n'est pas la même suivant que le CPU utilise l'exécution dans le désordre, ou non. Mais dans les faits, la distinction est plus subtile que ca. Pour l'expliquer, je vais utiliser une classification originale, qui sépare les processeurs superscalaires en trois sous-types. La distinction se fait sur la manière dont la superscalarité est implémentée en matériel.La classification que j'utilise distingue : * Les processeurs avec plusieurs pipelines, sans exécution dans le désordre. * Les processeurs superscalaires avec un pipeline dynamique. * Les processeurs avec des fenêtres d'instruction décentralisées. La distinction n'est pas qu'une question d'implémentation matérielle. Elle impacte aussi le ratio entre instructions chargées/décodées et instructions émises. Vous vous dites que les deux sont égales, mais ce n'est le cas que sur les processeurs avec N pipelines ! Sur les autres processeurs, il est possible de décoder 4 instructions, mais d'en émettre maximum 6 par cycle ! Et sur les CPU avec des fenêtres d'instruction décentralisées, il y a aussi une différence entre le nombre d'instructions qui sont ''dispatch'' et ''schedule''. La catégorie la plus simple est celle des '''processeurs avec plusieurs pipelines'''. Ce sont des processeurs superscalaires sans exécution dans le désordre. Ils partent d'un processeur non-superscalaire, et ajoutent un second pipeline, en sortie de l'unité d'émission. Le second pipeline permet l'émission parallèle d'une seconde instruction. Le second pipeline n'est pas identique au pipeline originel, sur plusieurs points. Premièrement, il n'a pas exactement les mêmes circuits. Le second pipeline est généralement composé d'une unité de calcul, d'une FPU, éventuellement d'un second banc de registres, guère plus. Il permet d'exécuter une seconde opération de calcul, pas plus, ce qui est à l'origine de contraintes d'appariemment assez fortes. De plus, il n'a pas forcément la même longueur, il peut avoir des étages en moins ou en plus. Il s'agit de la forme la plus simple de processeur superscalaire et nous allons leur consacrer cette section. ===La double émission entière-flottante=== L'exemple le plus simple utilise un pipeline pour les opérations flottantes, séparé du pipeline originel. Pour bien expliquer les choses, prenons le processeur illustré ci-dessous. Un processeur a deux ''back-end'' séparés, un pour la FPU, et un autre pour le reste. Les instructions entières et mémoire passent toutes par l'ALU entière, passent éventuellement dans l'unité mémoire, pour ensuite arriver à l'étape de ''writeback''. La FPU et le banc de registre flottant ont eux aussi leur propre ''back-end'' séparé. [[File:CPU non-superscalaire basique, avec une FPU.png|centre|vignette|upright=1.5|CPU non-superscalaire basique, avec une FPU]] Un processeur simple émission peut démarrer une seule µop par cycle : soit la µop est envoyée au ''back-end'' flottant, soit elle est envoyée au ''back-end'' général. Avec la double émission entière-flottante, le processeur peut envoyer une µop dans chaqaue pipeline, à chaque cycle. L'ajout d'un pipeline flottant permet d'émettre une micro-opération entière en même temps qu'une micro-opération flottante, ce qui s'appelle la '''double émission entière-flottante'''. La micro-opération entière s'exécute dans l'ALU entière, la micro-opération flottante dans la FPU. Le terme double émission ''entière''-flottante est un peu trompeur. En réalité, la technique permet d'émettre une instruction flottante avec n'importe quelle autre instruction, avec des restrictions pour les branchements. Il est par exemple possible d'exécuter un calcul flottant en parallèle d'un accès mémoire. [[File:Double émission entière-flottante.png|centre|vignette|upright=2|Double émission entière-flottante]] Les exemples les plus notables sont le PA-RISC 7100, le POWER 1 (RIOS .& et .9) et le PowerPC 601. L'Apollo DN10000 et l'Intel i860 utilisaient aussi cette forme de double émission, mais étaient des prototypes de CPU VLIW. Sur les CPU 32 bits, il est possible de faire les multiplications dans la FPU. Pour rappel, la FPU contient un multiplieur entier 53-54 bits, pour multiplier les mantisses de deux flottants 64 bits, qu'on peut utiliser pour faire les multiplications entières 32 bits. Cela permet d'émettre une multiplication en même temps qu'une opération entière dans l'ALU. Ainsi, le CPU pouvait émettre deux instructions : une opération entière de base, avec soit une multiplication, soit une opération flottante. En clair, une émission de type INT + (MUL /FLOAT). Par contre, on perd la possibilité d'émettre une multiplication entière avec une opération flottante, mais c'était un compromis qui en valait la peine. Le CPU PA-RISC 7100 implémentait cette optimisation. Il s'agissait d'un CPU double émission entière-flottante, qui utilisait donc un partitionnement assez simple. Le combo partitionnement + reconditionnement permettait ici de gagner en performance, pour un cout en matériel très limité. D’autant plus limité que le jeu d'instruction prévoyait le reconditionnement de la FPU. Les multiplications ne lisaient pas leurs opérandes dans les registres entiers, mais dans les registres flottants. ===La double émission entière=== Une autre possibilité ajoute un second pipeline, capable d'exécuter des opérations entières simples, appelé le pipeline entier. Le processeur supporte alors la '''double émission entière''', à savoir qu'ils peuvent émettre une opération entière en plus d'une autre instruction. L'opération entière en question est une opération simple, comme une addition, une soustraction, une opération bit à bit, une comparaison, etc. Les exemples classiques sont les processeurs Intel i960 et Pentium. L'implémentation était différente entre l'i960 et le Pentium : Le Pentium avait une seconde ALU entière, alors que le i960 rusait. Le Pentium dupliquait l'ALU entière, pour créer un second pipeline entier. Sur le Pentium originel, les contraintes d'appariement étaient assez strictes. Le Pentium avait une FPU, mais ne pouvait pas l'utiliser en double émission. Il lui était impossible d'exécuter une opération flottante en même temps qu'une opération entière (branchements inclus). Le Cyrix 6x86 était une sorte de Pentium produit par l'entreprise Cyrix, avec une microarchitecture différente. Il avait des contraintes d’appariement réduites. Par exemple, il pouvait émettre une opération flottante en parallèle d'une opération entière. Nous détaillerons ces processeurs dans le prochain chapitre, qui porte justement sur les CPU superscalaires x86. L'Intel i960 n'avait pas de FPU, juste une ALU entière et une unité mémoire. Il utilisait l'unité mémoire comme une seconde ALU entière limitée. Pour rappel, une unité mémoire contient une AGU, à savoir une ALU spécialisée dans les calculs d'adresse. Elle est capable de faire des additions, soustractions, et quelques décalages limités (des décalages par 2, 4, 8, pas plus). L'Intel i960 ajoutait des connexions entre AGU et banc de registre, afin que celle-ci serve de seconde unité de calcul. L'AGU est déjà reliée aux deux ports de lecture, pour lire les opérandes du calcul d'adresse, il suffit de la connecter sur le port d'écriture. Le processeur peut alors émettre deux instructions/µops entières, sous certaines conditions. [[File:Emission multiple des opérations entières, double émission.png|centre|vignette|upright=2|Émission multiple des opérations entières, double émission.]] ==Les processeurs superscalaires dynamiques== Les processeurs précédents, avec 2 pipelines, avaient une unité d'émission assez simple. Elle avait deux ports de décodage, et deux ports d'émission. Le nombre de ports de décodage et d'émission étaient identiques. Vous vous dites que c'est naturel, et intuitif, que c'est normal pour un processeur superscalaire. Mais en réalité, la majorité des processeurs superscalaires a plus de ports d'émission que de décodage. Expliquer pourquoi va cependant nous demander de parler de comment la superscalarité exploite les unités de calcul. Posons-nous cette question : de combien d'unités un processeur superscalaire a besoin ? Les unités en question ne sont nécessairement des unité de calcul, l'unité mémoire et l'unité de branchement comptent aussi. La réponse dépend du nombre de voies. Si le ''front-end'' charge et décode un paquet de N instructions, exécuter ces N instructions simultanément demandera là aussi au moins N unités de calcul. Notez le terme : ''au moins'', car c'est là tout l'enjeu de cette section. La réponse dépend aussi des contraintes d’appariement. Pour un CPU superscalaire "parfait", sans contraintes d’appariement, toutes les unités doivent être dupliquées, y compris l'unité mémoire. Un processeur à double émission parfaite devrait dupliquer l'ALU entière, le ''barrel shifter'', le circuit multiplieur, la FPU, l'unité mémoire, et l'unité de branchement, ce qui ferait passer de 6 unités à 12. Le cout en circuit est énorme, même pour de la double émission ! Et pour un CPU à N pipelines, le cout en circuit encore plus prohibitif ! En pratique, les processeurs superscalaires qui font ça sont très rares. La majeure partie des processeurs superscalaires utilisent d'autres techniques : le partitionnement, la duplication partielle des ALUs, la duplication des autres unités. ===Le partitionnement des unités fonctionnelles=== Les CPU superscalaires les plus simples ne dupliquent pas leurs unités de calcul, ils se contentent d'exploiter les unités existantes. Et pour expliquer comment, nous allons partir de l'exemple d'un processeur non-superscalaire contient une ALU entière, une FPU, une unité mémoire et une unité de branchement. Les quatre unités peuvent fonctionner en parallèle, à savoir qu'une unité peut exécuter une instruction à elle toute seule, sans interagir avec les autres. Cela suffit pour créer un processeur quadruple émission, qui peut émettre en même temps : une opération entière, une opération flottante, un branchement, un accès mémoire. Le principe marche aussi sur un processeur avec moins de 4 unités, par exemple sans FPU et/ou sans unité de branchement, ou avec au contraire des unités en plus. L'idée générale est d’émettre une µops séparée dans chaque unité fonctionnelle. Utiliser ainsi les unités existantes, pour rendre un CPU superscalaire, s'appelle le '''partitionnement superscalaire'''. Le terme n'est pas beaucoup utilisé en-dehors de quelques vieux articles académiques. Le partitionnement est fortement dépendant du jeu d'instruction, car il demande de bien séparer instructions entières, flottantes, branchements et instructions mémoire. Et autant c'est bien le cas sur les CPU RISC, autant les CPU CISC ne sont pas dans ce cas. Les CPU CISC ont des instructions ''load-op'', qui effectuent une instruction arithmétique et une µop mémoire. Et ces instructions sont très compliquées à implémenter avec le partitionnement. Et au-delà de ça, les CPU CISC ont beaucoup d'instructions qui sont décodées en plusieurs µops, à exécuter dans un ordre bien précis. On peut pas émettre les µops en question simultanément. Le partitionnement a un cout en circuit très limité, pour un gain en performance lui aussi limité. Pour reprendre le CPU de l'exemple précédent, il est rare que l'on ait à exécuter ces quatre instructions en même temps. En pratique, les combinaisons fréquentes sont les combinaisons calcul + accès mémoire, calcul + branchement, calcul entier + calcul flottant. Utiliser une quadruple émission de ce type n'est donc pas une bonne idée, alors que la double émission parait plus adaptée. Et c'est là que la différence entre ports d'émission et de décodage devient intéressante. L'idée est d'avoir une unité d'émission avec deux ports de décodage, et 4 port d'émission (un par unité). Les contraintes d’appariement varient alors suivant le processeur, mais la règle générale est que si deux micro-opérations tombent dans deux unités différentes, on peut les émettre en même temps. En clair, on peut émettre une opération entière avec une opération flottante, une opération entière avec un branchement, un accès mémoire avec un branchement, etc. Et cela rend le partitionnement bien plus intéressant que prévu. Les tout premiers CPU superscalaires étaient nombreux à utiliser cette technique. Les exemples classiques sont l'HyperSPARC, de l'alpha 21064 et du PowerPC 603. C'étaient des CPU double émission, avec une ALU entière, une unité mémoire et une FPU. Nous parlerons de '''partitionnement INT-FLOAT-MEM'''. Les contraintes d’appariement variaient grandement suivant le processeur. Le PowerPC 603 implémentait une double émission "parfaite". C'est à dire que toutes les combinaisons possibles à deux instructions étaient possibles, que ce soit INT + FLOAT, INT + MEM, FLOAT + MEM. En plus de cette double émission "parfaite", le CPU utilisait la technique dite de ''branch folding'', qui permettait d'exécuter un branchement sans utiliser d'unité de calcul, ce qui fait qu'il était qualifié de ''CPU 2-way + Branch''. A l'opposé, le processeur alpha 21064 avait pas mal de contraintes d’appariement, à savoir que certaines combinaisons de deux instructions étaient interdites, bien que compatibles avec les unités disponibles. Une des raisons est que le banc de registre n'avait pas assez de ports de lectures, ce qui fait que certaines combinaisons avec une écriture mémoire n'étaient pas permises. Les concepteurs avaient volontairement inséré ces contraintes afin de simplifier le design du processeur. Avant de poursuivre, précisons que la FPU regroupe un additionneur flottant et un multiplieur flottant. Et ce fait peut être exploité pour approfondir le partitionnement. La '''double émission FADD/FMUL''' permet d'émettre une addition flottante en même temps qu'une multiplication flottante. Elle a été utilisée sur les processeurs x86, Alpha et SPARC. Les autres circuits de calcul flottant sont souvent regroupés avec l'additionneur. La division est parfois e même port que le multiplieur flottant, parfois sur celui de l'additionneur (quand elle est microcodée et exécutée en enchainant les soustractions). ===La duplication des ALU entières=== Le partitionnement est une technique élégante, simple, facile à implémenter et au cout en circuits modeste. Mais il faut avouer que cela ne donne pas des CPU superscalaires très efficaces. La raison est que le partitionnement permet d'émettre des paires, triplets, quadruplets de micro-opérations qui sont rares en pratique. Les combinaisons les plus fréquentes en pratique sont des paires/triplets d'opérations entières, souvent des paires/triplets d'additions ou d'opérations entières simples. Il est donc primordial d'émettre plusieurs opérations entières simultanément. Le fait d'émettre et d'exécuter plusieurs opérations entières en même temps s'appelle l''''émission entière multiple'''. Pour exécuter la seconde/troisième opération entière, il faut dupliquer les ALU entières (ou réutiliser l'AGU comme ALU entière comme le fait l'Intel i960). Le processeur doit avoir deux ALU entières pour de la double émission entière, trois pour de triple émission entière, etc. De plus, il faut ajouter des ports au banc de registre, pour lire deux fois plus d'opérandes et décrire deux résultats. Et enfin, il faut rajouter un port d'émission pour pouvoir émettre une micro-opération entière en plus. [[File:Processeur non-superscalaire basique 03.png|centre|vignette|upright=2|Processeur non-superscalaire basique]] La tendance est de multiplier les ALU entières, mais pas le circuit multiplieur. Le cout en transistors est alors grandement réduit, au prix de l'apparition de dépendances structurelles. Le CPU peut émettre deux additions en même temps, ou une addition et une multiplication, mais pas deux multiplications. Il en est de même avec le ''barrel shifter'', qui n'est pas dupliqué. Il est donc possible d'émettre un décalage avec une opération de base, mais pas deux décalages en parallèle. Mais ces contraintes d’appariement ne sont pas un problème, car il est rare de devoir émettre deux multiplications/décalages en même temps. La plupart des programmes sont majoritairement remplis d'additions, avec des multiplications assez rares et des décalages qui le sont encore plus. La moyenne est proche d'une multiplication pour 4/5 additions. Vu que des multiplications consécutives sont très rares, dupliquer le multiplieur est presque inutile. Disposer de plusieurs circuits multiplieurs ou de plusieurs ''barrel shifters'' serait donc un cout en circuits qui ne servirait que rarement et n'en vaut pas la chandelle. Il faut noter que sur les CPU 32 bits, les multiplications peuvent être exécutées dans la FPU ou dans un multiplieur séparé, tout dépend de si le CPU exécute les multiplications dans la FPU ou pas. {|class="wikitable" |- ! Voie d'émission n°1 !! Voie d'émission n°2 !! Voie d'émission n°3 |- | ALU entière || ALU entière || FPU |} Reste à connecter les unités de calcul à l'unité d'émission (indirectement, car il y a des étages de pipeline entre les deux). Une implémentation basique utilisee un port d'émission par unité. Prenons l'exemple d'un CPU superscalaire avec 4 ALU entières, une FPU, et une unité mémoire. Vu qu'il y a 6 unités, cela fait donc 6 ports d'émission. L'exemple historique le plus impressionnant est celui du Motorola 88110, un CPU double émission qui possède 10 unités. Il contient deux ALU entières, un circuit multiplieur, un circuit diviseur, une unité de manipulation de bits, une unité mémoire, un additionneur flottant, et deux unités graphiques pour des calculs impliquant des pixels. Le multiplieur est utilisé à la fois pour les multiplications entières, flottantes et le produit de deux pixels. Idem pour le diviseur, qui sert pour les divisions entières et flottantes. Et chaque unité a son propre port d'émission. La conséquence est que les contraintes d'appariement du Motorola 88110 sont simples : tant que deux µops atterrissent dans des unités séparées, la paire est permise. Il est par exemple possible d'émettre simultanément deux opérations entières simples, vu qu'il y a deux ALU. Par contre, impossible d'émettre simultanément deux multiplications, deux divisions, deux opérations flottantes, deux manipulations de bit, deux accès mémoire, etc. De plus, le partage du multiplieur fait qu'on ne peut pas émettre une multiplication entière avec une multiplication flottante. Idem avec les divisions, vu que le diviseur est aussi partagé. Ce sont les seules contraintes. ===Le partage des ports d'émission=== La présence d'un circuit multiplieur et d'un ''barrel shifter'' pose des problèmes pour le partitionnement. Il est possible d'avoir un port d'émission dédié au multiplieur, et un autre au ''barrel shifter'', qui sont séparés de ceux pour l'ALU entière. Il est alors possible d'émettre une opération entière, une multiplication et un décalage, simultanément. Nous parlerons de '''partitionnement entier''' pour désigner cette possibilité. [[File:Partitionnement superscalaire entier.png|centre|vignette|upright=2|Partitionnement superscalaire entier.]] Le partitionnement entier est cependant très rare, car le gain en performance serait trop faible au regard des couts. Quelques processeurs historiques l'utilisaient, mais ce n'est plus le cas de nos jours. La raison est que les multiplications et les décalages sont assez rares dans le code, ce qui fait que le port d'émission associés sont sous-utilisés. Et un port d'émission a un cout en circuit assez important. L'idéal serait de trouver un moyen de réduire le nombre de ports d'émissions, sans réduire le nombre d'unités de calcul. [[File:Emission multiple des opérations entières, implémentation naive.png|vignette|Émission multiple des opérations entières, implémentation naive]] La solution consiste à '''partager les ports d'émission''', à savoir connecter un port d'émission à plusieurs unités de calcul. L'exemple classique est justement lié aux circuits multiplieur et ''barrel shifters''. L'idée est que le multiplieur et le ''barrel shifter'' sont mis sur le même port d'émission qu'une ALU entière. Prenons par exemple le processeur à double émission entière illustré ci-contre. Un port d'émission est relié à une ALU entière seule, et l'autre port d'émission alimente la seconde ALU entière et le multiplieur. La conséquence est que le processeur peut émettre deux opérations entières, dont une multiplication. Si on avait utilisé un port d'émission dédié au multiplieur, il aurait été possible d'émettre deux opérations entières et une multiplication. On perd donc une voie, mais on gagne en flexibilité et on économise des circuits. [[File:Emission multiple des opérations entières, implémentation courante.png|centre|vignette|upright=1.5|Émission multiple des opérations entières, implémentation courante]] En général, le ''barrel shifter'' et le multiplieur sont placés sur deux voies d'émission séparés, ce qui permet d'émettre un décalage et une multiplication en même temps. Les mettre sur le même port ne le permettrait pas. {|class="wikitable" |- ! Voie d'émission n°1 !! Voie d'émission n°2 !! Voie d'émission n°3 |- | * ALU entière * Multiplieur | * ALU entière * Barrel shifter | FPU |- | * Additions, soustractions, opérations bit à bit, etc. * Multiplication | * Additions, soustractions, opérations bit à bit, etc. * Décalages et rotations | Opération flottante |} Mais le partage des ports d'émission peut appliquer à n'importe quelle unité. Prenons l'exemple d'un CPU superscalaire avec 4 ALU entières, une FPU, et une unité mémoire. Vu qu'il y a 6 unités, cela fait donc 6 ports d'émission. Cependant, il est rare qu'un bloc de 6 instructions contienne exactement 4 opérations entières, une opération flottante, et un accès mémoire. Beaucoup de ports d'émission seraient sous-utilisés. La solution est de partager des ports d'émission, pour passer à la quadruple émission. Le CPU peut alors répartir 4 instructions sur les ports d'émission adéquats, à chaque cycle. Le CPU a donc 4 ports de décodage et 6 ports d'émission. Une implémentation classique permet n'importe quelle combinaison de 4 instructions compatible avec les 6 unités. Par exemple, il devient possible d'émettre simultanément 4 additions, la combinaison 2 * INT + FLOAT + MEM, 3 * INT + MEM, et bien d'autres. L'exemple relie une ALU entière, la FPU et l'unité mémoire sur le même port. Le CPU a alors quatre ports d'émission, qui ne sont pas "identiques". Trois d'entre eux sont reliés à une ALU entière chacun, rien d'autre. Le quatrième est lui relié à une ALU entière, la FPU et l'unité mémoire. On économise alors deux ports d'émission, mais cela entraine l'apparition de contraintes d’appariement. Le CPU peut émettre 3 opérations entières + n'importe quelle autre opération. Mais les autres combinaisons sont interdites. Le CPU peut émettre 6 opérations entières simultanément, grâce à 6 ALU entières. Les accès mémoire ou opérations flottantes ne viennent pas en plus, mais en remplacement des opérations entières. Regrouper plusieurs ALU sur un même port d'émission est donc à l'origine de dépendances structurelles, de nouvelles contraintes d’appariement. Dans certaines situations de '''''port contention''''', on a assez d'ALU pour exécuter N micro-opérations, mais la répartition des ALUs sur les ports l'empêche. Impossible d'émettre deux micro-opérations sur deux ALU si elles sont sur le même port d'émission. On peut réduire les cas de ''port contention'' en répartissant correctement les ALU sur les ports d'émission, mais sans jamais les supprimer complétement. Un exemple est celui du PowerPC 604. Il implémentait la quadruple émission, alors qu'il avait 5 unités de calcul. Mais deux unités partageaient un même port d'émission. Dans le détail, il avait deux ALU entières, un circuit multiplieur/diviseur, une FPU, une unité mémoire. La FPU et le multiplieur étaient sur le même port d'émission. En conséquence, le CPU pouvait émettre seulement les combinaisons INT + INT + MEM + (MUL/FLOAT). : Il est intéressant de voir l'exemple d'un processeur superscalaire parfait, sans contraintes d'appariement, à N voies. Pour cela, on part d'un processeur non-superscalaire, avec M unités de calcul. Un tel processeur peut s'implémenter avec N ports d'émission, chacun relié à M unités de calcul possibles, ce qui fait que les M unités de calcul sont dupliquées en N exemplaires. Si on prend par exemple un CPU non-superscalaire avec une ALU, une FPU, une unité mémoire et une unité de branchement, un port d'émission sera relié à une ALU, une FPU, une unité mémoire et une unité de branchement. ===La duplication des autres unités fonctionnelles=== Dupliquer les ALU entière est une première étape, mais il est aussi possible de dupliquer les autres unités fonctionnelles. Par exemple, il est possible de dupliquer l'unité mémoire ou l'unité de branchement. Reste à voir quelles unités dupliquer, et pourquoi. Précisons que dans ce qui va suivre, nous allons parler de la duplication des unités en général, pas seulement des unités mémoire/branchement. Nous allons continuer de parler de la duplication des ALU entières dans ce qui suit, mais de manière plus générale. Pour un processeur à N voies, il est théoriquement possible de dupliquer toutes les unités en N exemplaires. Mais ce n'est jamais fait en pratique, car le cout en circuits serait trop important et le gain en performance trop faible. Mieux vaut dupliquer certaines unités bien précises, quitte à avoir des contraintes d'appariement. Et à ce petit jeu, toutes les duplications ne se valent pas, certaines sont plus rentables en matière de performance, le cout en circuit n'est pas le même selon l'unité, etc. La rentabilité en performance dépend surtout des contraintes d'appariement. Les contraintes d'appariement entrainent une baisse de performance significatives si elles interdisent des paires d'instructions fréquentes. Aussi, les opérations très fréquentes, souvent appairées, gagnent à dupliquer l'unité associée. Et au contraire, les unités rarement utilisées ne doivent pas être dupliquées. Il se trouve que les unités les plus utilisées sont les ALU entières, suivies par les unités mémoire et l'unité de branchement. Le cout en circuit n'est aussi pas le même. Dupliquer les ALU entière est trivial, car elles ont un budget en transistors assez faible. Les circuits multiplieurs, diviseurs, et les ''barrel shifter'' ont eux un cout en transistor plus élevés. Si on devait faire un classement, on aurait : ALU entière < ''barrel shifter'' < multiplieur. Pour les unités mémoire, le cout en transistor est là aussi conséquent. Le résultat final est que dupliquer les ALU entières est privilégié sur le reste, comme l'a vu dans la section précédente. Les unités mémoire arrivent en seconde place, suivie par les multiplieurs/''barrel shifter'' à la troisième place, et les unités de branchement à la quatrième place. Les CPU superscalaires larges ont donc tendance à multiplier les unités mémoire en second. Les multiplieurs et ''barrel shifters'' sont eux aussi dupliqués sur les CPU superscalaires larges, mais cela vient souvent après avoir dupliqué les unités mémoire. La raison est que les accès mémoire sont critiques pour la performance des CPU à exécution dans le désordre, pas les multiplications. Pour les unités de branchement, une subtilité complique le fait d'en installer plusieurs. Que faire quand deux branchements sont pris ? La solution la plus simple est de ne pas se trouver dans cette situation : on n'utilise qu'une seule unité de branchement et on n’exécute qu'un seul branchement par cycle. La majorité des processeurs superscalaires étroits font ainsi. Les processeurs superscalaires larges ont cependant deux unités de branchements, mais ils n'acceptent qu'un seul branchement pris à la fois. Si les deux branchements pris sont exécutés en même temps, le processeur n’exécute que le plus récent, dans l'ordre du programme. ===Les CPU superscalaires mélangent les trois techniques=== Les CPU superscalaires utilisent toutes les techniques à la fois : partitionnement, duplication de l'ALU entière, duplication des autre unités fonctionnelles. Chaque processeur utilise ces trois stratégies à sa sauce, en fonction de son budget en transistor. Les quatre techniques ont cependant un cout en circuit différent, et donnent des gains de performances eux aussi forts différents. * Le '''partitionnement superscalaire''' a un cout en circuit modéré, car on n'a pas besoin de dupliquer d'ALU. * La '''duplication des ALU entières''' est la technique la plus importante : elle a un cout en circuit modéré, et donne des gains en performance impressionnants. * La '''duplication des autres unités''' a un cout en circuit variable, pour des gains en performance eux aussi variables. * Le '''partage des ports d'émission''' réduit le cout en circuit du CPU, pour des pertes de performances mineures si bien fait. L'usage du partitionnement seul, sans duplication, était surtout le fait des processeurs commercialisés entre les années 1989 et 1993. Le partage des ports d'émission a lui aussi mis un peu de temps pour s'imposer. Globalement, les processeurs superscalaires ont évolués progressivement, pour se rapprocher d'une sorte de standard. Mais pour comprendre ce qu'est ce standard, et en quoi les CPU historiques s'en sont progressivement rapprochés, il faut étudier l'histoire des CPU superscalaires. L'histoire des CPU superscalaires a commencé dès 1960, mais est resté un projet de recherche pendant plusieurs décennies. De nombreux designs ont été conçus, mais abandonnés avant la production, comme l'ACS-1 d'IBM. Les années 1980 ont vu l'arrivée de prototypes de processeurs VLIW, mais qui n'étaient pas superscalaires. Par exemple, l'Apollo DN10000 et l'Intel i860 pouvaient émettre deux instructions à la fois, sous conditions, mais demandaient que le compilateur fasse une bonne partie du travail. Les premiers CPU superscalaires commercialisés sont sortis en 1989. Deux CPUs superscalaires sont sortis cette année là : le RS/6000 d'IBM et l'i960CA d'Intel. Puis, la compétition s'est aussi lancée dans la bataille. Les CPU commercialisés avant l'année 1993 étaient à double émission, à l'exception du SuperSPARC qui utilisait la triple émission. Les modèles après 1993 hésitaient entre double et quadruple émission. Dans le tableau ci-dessous, les processeurs quadruple émission sont en bleu, les triple émission sont en rouge, les autres ne sont pas en couleur. {|class="wikitable" |- ! !! IBM !! HP PA-RISC !! SPARC !! Intel!! Alpha !! Autres |- ! 1989 | RS/6000 || || || i960CA || || |- ! 1990 | || || || || || |- ! 1991 | || || || || || Motorola 88110 |- ! 1992 | RSC || PA 7100 || class="f_rouge | SuperSPARC || || 21064 || |- ! 1993 | PPC 601 || || HyperSPARC || Pentium || || |- ! 1994 | class="f_bleu | POWER 2 || PA 7200 || || || class="f_bleu | 21164 || |- ! 1995 | 603/604 || class="f_bleu | PA 8000 || class="f_bleu | UltraSPARC 1 || Microarchitecture P6 (+ concurrence AMD) || || class="f_bleu | MIPS R1000 |- ! 1996 | class="f_bleu | 620 || || || || class="f_bleu | 21264 || |- ! ... || ... || ... || ... || ... || ... || ... |} Les premiers CPU à double émission sont assez représentatifs des CPU à double émission modernes. Il faut dire que se limiter à deux voies ne permet pas de faire beaucoup de choses. Voici le tableau chronologique disant quel CPU utilisait quelle technique. * Les CPU en vert utilisaient la double émission entière-flottante. * Les CPU en rouge utilisaient la double émission entière "pure". * Les CPU en jaune utilisaient un ''partitionnement complet'' entre ALU, FPU et unité mémoire. * Les CPU sans couleur utilisaient un grand nombre d'unités, avec beaucoup de ports d'émission. * Les CPU barrés utilisent la triple ou quadruple émission et seront vu dans la section suivante. {|class="wikitable" |- ! !! IBM !! HP PA-RISC !! SPARC !! Intel!! Alpha !! Autres |- ! 1989 | class="f_vert | RS/6000 || || || class="f_rouge | i960CA || || |- ! 1990 | || || || || || |- ! 1991 | || || || || || Motorola 88110 |- ! 1992 | class="f_vert | RSC || class="f_vert | PA 7100 || <s>SuperSPARC</s> || || class="f_jaune | 21064 || |- ! 1993 | class="f_vert | PPC 601 || || class="f_jaune | HyperSPARC || class="f_rouge | Pentium || || |- ! 1994 | <s>POWER 2</s> || PA 7200 || || || <s>21164</s> || |} C'est en 1994, que la quadruple émission est arrivée et que les CPU ont essayé divers implémentations. Il y avait alors trois stratégies pour la double/triple/quadruple émission, qui partageaient des problèmes similaires. La première implémentation dupliquait les ALUs et les FPUs, ce qui permettait d'émettre deux opérations flottantes avec deux opérations entières. Le POWER 2 dupliquait ses FPUs à la perfection, alors que le 21264 séparait l'additionneur et le multiplieur flottant. Les accès mémoire étaient traités comme des opérations entières et le processeur pouvait souvent en émettre deux à la fois. Mais une telle organisation n'est plus utilisée de nos jours, car, sans instructions flottantes à exécuter, elle est équivalente à de la double émission. La seconde implémentation utilisait deux ALU entières (dont un multiplieur), une unité mémoire et une FPU.Cette organisation est intéressante, mais encore trop stricte pour donner de bons résultats, les quadruplets avec exactement deux opérations entières, un accès mémoire et une opération flottante étant trop rares. L'usage de ces 4 unités en double ou triple émission a été utilisé sur le PA 7200 et le SuperSPARC. Malheureusement, en plus de problèmes au niveau de son cache, le SuperSPARC avait de nombreuses contraintes d’appariement, car le banc de registre n'avait pas assez de ports pour alimenter certaines combinaisons d'instructions. De plus, s'il émettait une multiplication, il ne pouvait pas émettre de seconde µop. Enfin, une troisième implémentation utilisait un grand nombre d'unités fort différentes, bien plus que quatre, avec au minimum deux ALU entières. Prenons l'exemple de l'UltraSPARC, qui avait : deux ALU entières, une unité mémoire, une unité de branchement, un additionneur flottant, un multiplieur flottant, un diviseur flottant (aussi utilisé pour le calcul des racines carrées), et 2 unités graphiques pour les calculs sur des pixels. En tout, cela fait 9 unités différentes, pour de la quadruple émission. Dans le même genre, les processeurs MIPS R8000 et R10000 avaient une implémentation plus moderne, dans le sens où toutes les unités étaient dupliquées. Le CPU avait deux ALU, deux FPU et deux unités mémoire. Il pouvait donc émettre quatre instructions à la fois, tant que le paquet collait avec ces six unités. Moitié opérations entières, moitié opérations mémoire, avec de quoi remplacer les opérations entières par des opérations flottantes. Le PA 8000, lui, dupliquait toutes ses unités en deux exemplaires, ce qui n'est pas l'idéal, aucun processeur moderne ne fait cela aujourd'hui. Le tout est résumé dans le tableau ci-dessous, avec : * en vert les CPU utilisant la première stratégie ; * en rouge les CPU utilisant la seconde stratégie ; * en jaune les CPU utilisant la troisième stratégie. {|class="wikitable" |+ Processeurs quadruple émission historiques |- ! Année !! Processeur !! Unités |- ! 1991 | class="f_jaune | Motorola 88110 || class="f_jaune | 2 ALU + MUL + DIV + BIT + MEM + FADD + 2 unités graphiques |- |- ! rowspan="2" | 1994 | class="f_vert | POWER 2 || class="f_vert | 2 ALU/MEM + 2 FPU (+ 2 Branchements via ''branch folding'') |- | class="f_rouge | 21164 || class="f_rouge | 2 ALU + FPU + MEM |- ! rowspan="3" | 1995 | PA 8000 || 2 ALU + 2 ''barrel shifters'' + 2 MEM + 2 FPU (FMA) + 2 FPU (DIV et SQRT) + 2 SIMD |- | class="f_jaune | UltraSPARC 1 || class="f_jaune | 2 ALU + MEM + BRANCH + FADD + FMULFDIV + 2 unités graphiques |- | MIPS R8000 et R10000 || 2 ALU + 2 FPU + 2 MEM |- ! rowspan="2" | 1996 | class="f_rouge | 620 || class="f_rouge | 2 ALU + FPU + MEM |- | class="f_vert | 21264 || class="f_vert | 2 ALU/MEM + 2 FPU (FADD + FMUL) |} Malheureusement, ces CPUs historiques n'avaient ni assez d'unités de calcul entières, ni d'unités mémoire, pour remplir la quadruple émission. Environ la moitié des instructions d'un programme correspond à des instructions entières. Pour en profiter, un processeur à N voies doit disposer d'au minimum N/2 ALU entières, l'idéal étant d'aller au-delà. Les CPU superscalaires historiques avaient 2 ALU pour de la quadruple émission, ce qui était très limite. En comparaison, les CPU superscalaires modernes mettent le paquet niveau ALU entière. La plupart des CPU x86 modernes descendent rarement sous les 2/3, la plupart remplit presque la totalité de la largeur du CPU. Pour donner un exemple, la microarchitecture Golden Cove d'Intel est un processeur à 6 voies, avec 5 ALU entières. Pour être précis, un programme informatique exécute environ 40% de son code dans des instructions arithmétiques, un autre 40% dans les accès mémoire, et un le reste pour... le reste. La conséquence est que les processeurs modernes dupliquent leurs unités mémoire. Par exemple, la microarchitecture ''Golden Cove'' d'Intel dispose de trois unités mémoire : deux spécialisées dans les écritures, trois dans les lectures. La microarchitecture ''Gracemont'' utilise la pentuple émission et peut émettre 4 µops entières et 4 µops mémoire. Notez que le ratio est équilibré entre opérations entières et mémoire, avec soit égalité, soit un avantage pour les ALU entières. Une autre régularité est que la largeur totale du processeur peut être occupée par des opérations entières/mémoire. Pour le dire autrement, sur un processeur quadruple émission, il pourra émettre un mix de 4 instructions entière/mémoire, avec zéro opération flottante. Idem avec des processeurs triples ou pentuple émission, voire plus. Si le processeur émet une opération flottante, elle remplace une opération entière. Prenons l'exemple des processeurs AMD K6, sortis en 1997, juste avant les AMD Athlon. Ils avaient 10 unités de calcul, 6 ports d'émission, 4 ports de décodage. Voici exactement comment étaient répartie les unités de calcul sur les ports d'émission. C'était des processeurs quadruple émission, ce qui fait que la moitié de la largeur du processeur sert pour des opérations entières, et l'autre moitié pour les accès mémoire. {|class="wikitable" |- ! Port d'émission n°1 !! Port d'émission n°2 !! Port d'émission n°3 !! Port d'émission n°4 !! Port d'émission n°5 !! Port d'émission n°6 ! |- | ALU entière || ALU entière || FPU || Unité de branchement || Unité LOAD || Unité STORE |- | || Multiplieur || || || || Unité de calcul d'adresse (instruction LEA) |- | || ''Barrel shifter'' || || || || Unité pour les instructions PUSH |} Les microarchitectures K7, K8 et K10, utilisées sur les anciens CPU AMD Athlon, sont un exemple moins représentatif. Ces microarchitectures utilisent le partage des ports d'émission au maximum. C'était des microarchitectures à triple émission, avec six ports d’émission. Il y avait trois ports d'émission pour les opérations flottantes, et trois ports d'émission pour les micro-opérations entières/mémoire. Il y avait trois ALU entières et trois unités mémoire, chaque ALU partageait un port d'émission avec une unité mémoire. Elle pouvait donc émettre soit trois opérations entières, soit trois µops mémoire, soit un mix des deux. En plus des mix avec les opérations flottantes et branchements. {|class="wikitable" |- ! Port d'émission n°1 !! Port d'émission n°2 !! Port d'émission n°3 !! Port d'émission n°4 !! Port d'émission n°5 !! Port d'émission n°6 ! |- | ALU entière || ALU entière || ALU entière || FADD || FMUL || FPU (autres opérations) |- | Multiplieur || || || || || |- | LOAD/STORE || LOAD/STORE || LOAD/STORE || || || |} Les CPU superscalaires larges dupliquent les multiplieurs et/ou les ''barrel shifters'', et même les unités de branchement. La raison est que quand la largeur du CPU augmente, le processeur traite des paquets d'instructions de plus en plus gros. Par exemple, pour un CPU à 10 voies, cela veut dire émettre 10 instructions en même temps. Il est alors plus fréquent d'avoir deux multiplications, deux décalages, deux accès mémoire, deux branchements. Par exemple, la moyenne est d'une multiplication tous les 5 instructions. Les CPU superscalaires à 10 voies ont donc tendance à avoir deux multiplications par paquet d'instruction, en moyenne. Avoir deux multiplieurs est alors un gain en performance clair. Par exemple, la microarchitecture ''Golden Cove'' a deux ''barrel shifters''. C'est une architecture à 6 voies. {|class="wikitable" |- ! Port d'émission n°1 !! Port d'émission n°2 !! Port d'émission n°3 !! Port d'émission n°4 !! Port d'émission n°5 !! Port d'émission n°6 !! Port d'émission n°7 !! Port d'émission n°8 !! Port d'émission n°9 !! Port d'émission n°10 |- | ALU entière || ALU entière || ALU entière || ALU entière || ALU entière || LOAD || LOAD || LOAD || STORE || STORE |- | ''Barrel shifter'' || Multiplieur || Multiplieur || ''Barrel shifter'' || || || || || || |- | Branchements || Diviseur || || Branchements || || || || || || |} ==Les µops mémoire sur les processeurs superscalaires== Plus haut, nous avons volontairement passé sous silence les accès mémoire. La raison à cela est que leur support sur les CPU superscalaire peut se faire de plusieurs manières différentes, qu'il vaut mieux expliquer dans une section dédiée. Les différentes implémentations sont décrites dans la liste suivante, nous allons les voir une par une. * Avec la première méthode, les accès mémoire sont exécutés par le pipeline pour les opérations entières. * Avec la seconde méthode, les accès mémoire ont un pipeline dédié ; c'est l'''émission parallèle des accès mémoire''. * Avec la troisième méthode, il est possible d'émettre plusieurs accès mémoire simultanément : c'est l'''émission multiple des accès mémoire''. Voyons ces trois cas dans le détail. Nous verrons notamment les différentes formes d'émission multiple des accès mémoire. ===Les accès mémoire traités dans le pipeline entier=== Dans le cas le plus simple, les accès mémoire sont traités comme des opérations entières un peu spéciales. L'unité mémoire est sur le même port d'émission que l'ALU entière, ce qui fait que les accès mémoire remplacent des opérations entières. Faire ainsi permet d'utiliser une ALU entière pour les calculs d'adresse, pas besoin de rajouter une unité de calcul d'adresse séparée. Typiquement, un processeur de ce type place l'unité mémoire après l'unité de calcul. Les opérations entières n'utilisent que l'ALU entière, mais pas l'unité mémoire. Les accès mémoire exécutent un calcul d'adresse dans l'ALU entière, puis l'adresse calculée est envoyée à l'unité mémoire. Le tout est illustré ci-dessous, dans le cas d'un processeur historique à double émission entière-flottante. [[File:Processeur non-superscalaire basique 01.png|centre|vignette|upright=1.5|Processeur non-superscalaire basique]] Un exemple de CPU double émission de ce type est celui du Power PC 440, un CPU destiné à l'embarqué. Vu qu'il était destiné à l'embarqué, il n'avait pas de FPU, il n'avait pas besoin de manipuler de nombres flottants. Il n'a donc que des ALU entières, un multiplieur et une unité mémoire. Il dispose de deux ports d'émission. Le premier est connecté à une ALU entière et un circuit multiplieur, le second est relié à l'unité mémoire et une seconde ALU entière. Une organisation simple, mais très efficace ! [[File:PowerPC 440.png|centre|vignette|upright=2|Microarchitecture du PowerPC 440.]] L'implémentation économise une unité de calcul d'adresse, mais n'a pas de cout en performance significatif. Les accès mémoire étant très lents, émettre une µop entière en parallèle d'un accès mémoire ne sert pas à grand-chose. On peut émettre cette µop entière un cycle plus tard, elle sera terminée avant même que l'accès mémoire soit terminé, même si l'accès mémoire se fait dans le cache L1 ou L2. Pour comprendre pourquoi, faisons quelques calculs. Un accès mémoire prend deux-trois cycles quand il tombe dans le cache L1. Et on doit attendre qu'il soit terminé pour en émettre un nouveau. Combien d'instructions arithmétiques peut-on lancer pendant ce temps, sur un CPU triple émission ? Au premier cycle, on peut en émettre 2 (l'instruction mémoire prend une place, donc 3-1), puis 3 au second cycle. Ce qui fait 5 instructions arithmétiques au total. Or, il se trouve qu'il y a une instruction d'accès mémoire pour 5 instructions arithmétiques, environ. Par contre, ce compromis change quand on passe à la quadruple émission, voire au-delà. Dans ce cas, il devient intéressant de pouvoir émettre une nouvelle instruction mémoire à chaque cycle. Ce qui nous amène à la section suivante... ===L'émission parallèle des accès mémoire=== A partir de la quadruple émission, la tendance a été de séparer l'unité mémoire des ALU entières. Cela permet d'émettre des µops mémoire en même temps que des µops entières et/ou flottantes., ce qui porte le nom d''''émission parallèle des µops mémoire''', ou encore ''émission mémoire parallèle''. Par exemple, un processeur quadruple émission peut émettre en même temps deux opérations entières, une opération flottante et un accès mémoire. L'accès mémoire vient en plus des opérations entières, il n'est pas possible de remplacer l'accès mémoire par une opération entière. La technique ne permet pas de faire les calculs d'adresse dans l'ALU entière. Il faut donc utiliser une unité de calcul d'adresse séparée. Elle est intégrée dans l'unité mémoire, qui fait l'interface entre le cache et le banc de registres. Le cout en circuits est très faible, car les unités de calcul d'adresse n'utilisent guère plus qu'un additionneur et un circuit de décalage très simple. L'émission mémoire parallèle est plus facile à implémenter sur les CPU RISC, qui ont des instructions mémoire séparées du reste. Les CPU CISC ne sont pas dans ce cas, ce qui fait que séparer l'unité mémoire du pipeline entier est compliqué. Les CPU superscalaires historiques étaient pour moité des CPU RISC et pour moitié des CPU CISC, ce qui fait qu'ils n'utilisaient pas ou peu l'émission mémoire parallèle. Les premiers processeurs superscalaires se contenaient de l'émission parallèle mémoire, ils ne pouvaient émettre qu'un seul accès mémoire à la fois, l'unité mémoire n'était pas dupliquée. Et ce n'est pas tant un problème que ça, du moins sur les processeurs à double ou triple émission. En effet, il est rare que l'unité mémoire doive faire deux accès simultanés. Notons que le processeur peut exécuter une instruction mémoire en parallèle d'un calcul ou d'un branchement, seuls les accès mémoire simultanés sont impossibles. ===L'émission multiple des accès mémoire avec une LSQ=== Les processeurs superscalaires larges travaillent sur des paquets de 8 à 10 instructions, il est plus fréquent d'avoir plusieurs accès mémoire par paquet. Dans ces conditions, il est utile d'avoir une '''émission multiple des accès mémoire''', à savoir d'émettre plusieurs micro-opérations mémoire en même temps. Les processeurs superscalaires modernes sont capables d'émettre plusieurs lectures/écritures simultanément. Par exemple, ils peuvent émettre une lecture en même temps qu'une écriture, ou plusieurs lectures, ou plusieurs écritures. Il faut noter que selon le processeur, il peut y avoir des restrictions quant aux accès mémoire émis en même temps. Par exemple, certains processeurs peuvent émettre une lecture avec une écriture en même temps, mais pas deux lectures ni deux écritures. Ou encore, ils peuvent émettre deux lectures, une lecture et une écriture, mais pas deux écritures en même temps. Dans la majorité des cas, les processeurs ne permettent pas d'émettre deux écritures en même temps, alors qu'ils supportent plusieurs lectures. Il faut dire que les lectures sont plus fréquentes que les écritures. Les processeurs qui autorisent toutes les combinaisons de lecture/écriture possibles, sont rares. L'émission multiple des accès mémoire s'implémente dans l'unité mémoire. Pour rappel, l'unité mémoire s'interpose entre le cache L1 et le reste du pipeline. Dans le cas le plus simple, elle incorpore une ''load/store queue'' et des unités de calcul d'adresse. L'émission multiple des accès mémoire demande de rendre la ''Load-store queue'' multi-ports, afin de gérer plusieurs micro-opérations mémoire simultanées. Il faut aussi dupliquer les unités de calcul d'adresse reliées à la LSQ, chaque port d'émission mémoire devant avoir sa propre unité de calcul d'adresse. : Dans ce qui suit, nous parlerons d'AGU (''Adress Generation Unit'') pour désigner les unités de calcul d'adresse, et de LSQ pour designer la ''Load-store queue''. [[File:Processeur non-superscalaire basique 02.png|vignette|upright=1|Processeur non-superscalaire basique]] Dupliquer les ports de la LSQ et les AGU est nécessaire. Mais qu'en est-il du cache ? La réponse varie suivant le processeur. Il y a deux grandes méthodes pour implémenter l'émission multiple des accès mémoire. La première méthode ajoute des ports de lecture/écriture au cache de données, pour faire des lectures/écritures simultanées. Il faut alors dupliquer des unités de calcul, rajouter les ports sur le cache, mais aussi rajouter des ports sur la ''load/store queue'', pour qu'elle échange des données/commandes mémoire avec le cache. Le cout en circuits est alors conséquent La seconde duplique les unités de calcul, mais pas les ports d'accès au cache. Les accès mémoire sont alors accumulés dans la ''load/store queue'', qui sont ensuite traités un par un par le cache. Un exemple est celui des processeurs AMD de micro-architecture K7 K8, à savoir les anciens AMD Athlon et AMD Athlon 64, qui pouvaient calculer trois adresses simultanément, mais ne pouvaient faire qu'un seul accès au cache par cycle. Le cout en circuit est modéré : les unités de calcul coutent très peu, les ports de la ''load/store queue'' coutent déjà un peu plus. Quelques processeurs utilisaient des solutions intermédiaires. Un processeur utilisant cette technique est, par exemple, capable d'émettre quatre µops mémoire en même temps, mais avec seulement deux ports sur le cache. Un exemple classique étant les Pentium 1 et 2 d'Intel, dont nous parlerons dans le chapitre suivant. ===L'émission séparée des LOAD et STORE=== De rares processeurs ont des ports d'émission qui peuvent faire indifféremment lecture comme écritures. Mais les processeurs modernes préfèrent séparer ports d'émission pour les lectures et les écritures. Un exemple est celui des processeurs Intel de micro-architecture Nehalem, qui avaient un port de lecture et un port d'écriture tous deux reliés à la ''Load-store queue'', chacun avec leur propre unité de calcul d'adresse. Comme autre exemple, les processeurs skylake ont une LSQ avec deux ports de lecture et un port d'écriture, ce qui permet d'émettre deux lectures en même temps qu'une écriture. Dans cette section, nous allons étudier le cas avec des ports séparés pour les lectures et écritures. En général, les processeurs ont un peu plus de ports de lectures que d'écritures. Une raison est qu'un programme a, en moyenne, un tout petit peu plus de lectures que d'écritures. Mais la différence n'est pas énorme, c'est un léger déséquilibre. Une seconde raison est que l'exécution dans le désordre marche bien mieux quand les lectures ont lieu le plus tôt possible. Donc mieux vaut privilégier les lectures à l'émission, pour qu'elles se fassent un peu en avance. En pratique, la différence n'est pas très marquée. Il est courant d'avoir un port de lecture et un d'écriture, les CPU superscalaires très larges ont souvent deux ports de lecture et deux d'écriture. A la rigueur, quelques CPU ont trois ports de lecture et deux d'écriture. Le cas le plus simple est celui où on peut émettre une lecture et une écriture en même temps. Il s'implémente facilement si la LSQ est en réalité une simple ''Store queue'', à savoir qu'elle met en avance les écritures uniquement. Pour rappel, les écritures sont mises en attente dans la ''Store queue'', mais les lectures sont exécutées immédiatement. Une fois l'adresse à lire connue, la lecture consulte la ''Store queue'' pour détecter les dépendances de données, puis s'exécute. La donnée à lire est lue soit depuis la ''Store queue'', soit depuis le cache. [[File:Emission LOAD - Store séparée.png|vignette|upright=1|Émission LOAD - Store séparée]] L'implémentation de l'émission multiple est alors très simple. La ''Store queue'' a déjà deux ports, avec un pour les lectures et un autre pour les écritures. Idem pour le cache, qui a naturellement un port de lecture et un d'écriture. Émettre une lecture et une écriture simultanément ne demande donc pas de modification digne de ce nom. La ''Store queue'' doit juste supporter une lecture et une écriture simultanée. Ajouter l'émission d'une seconde lecture n'est pas compliquée : il faut juste rajouter un second port de lecture sur le cache et la ''Store queue''. Pour rappel, l'unité mémoire est précédée par une structure qui met en attente les micro-opérations mémoire avant leur émission finale. La structure en question varie suivant l'implémentation, mais un cas particulier se marie bien avec l'émission multiple des µops mémoire. Il s'agit du cas avec une file de µops séparée pour les lectures et une autre pour les écritures, appelées respectivement la ''file de µops LOAD'' et la ''file de µops STORE''. Il est alors possible d'émettre une lecture et une écriture lors du même cycle d'horloge. Le port de lecture alimente une unité de calcul d'adresse dédiée, directement reliée au cache. Le port d'écriture du cache alimente une unité de calcul, qui est suivie par une file d'écriture, elle-même reliée au cache. Les processeurs AMD de micro-architecture K6 utilisaient cette technique. [[File:Double émission avec le Load Ordering, Store Ordering.jpg|centre|vignette|upright=2.5|Double émission avec le Load Ordering, Store Ordering]] ==Les CPU superscalaire à exécution dans le désordre== La superscalarité modifie en profondeur le processeur. Et cela dépasse le cadre des unités vues auparavant. Dans cette section, nous allons voir comment les autres étages du pipeline sont modifiées sur un CPU superscalaire. ===L'unité de renommage superscalaire=== Avec la superscalarité, l'unité de renommage de registre doit gérer le cas où des instructions consécutives ont des dépendances de registre. Par exemple, prenons un processeur à double émission, qui renomme deux instructions consécutives. Si elles ont une dépendance de registre, le renommage de la seconde instruction dépend du renommage de la première. La première doit être renommée et le résultat du renommage est utilisé pour renommer la seconde, ce qui empêche d'utiliser deux unités de renommage séparées. Pour cela, elle renomme les registres sans tenir compte des dépendances, pour ensuite corriger le résultat. [[File:Unité de renommage superscalaire.png|centre|vignette|upright=2|Unité de renommage superscalaire.]] Seules les dépendances lecture-après-écriture doivent être détectées, les autres étant supprimées par le renommage de registres. Repérer ce genre de dépendances se fait assez simplement : il suffit de regarder si un registre de destination d'une instruction est un opérande d'une instruction suivante. [[File:Détection des dépendances sur un processeur superscalaire.png|centre|vignette|upright=2|Détection des dépendances sur un processeur superscalaire.]] Ensuite, il faut corriger le résultat du renommage en fonction des dépendances. Si une instruction n'a pas de dépendance avec une autre, on la laisse telle quelle. Dans le cas contraire, un registre opérande sera identique avec le registre de destination d'une instruction précédente. Dans ce cas, le registre opérande n'est pas le bon après renommage : on doit le remplacer par le registre de destination de l'instruction avec laquelle il y a dépendance. Cela se fait simplement en utilisant un multiplexeur dont les entrées sont reliées à l'unité de détection des dépendances. On doit faire ce replacement pour chaque registre opérande. [[File:Correction des dépendances sur un processeur superscalaire.png|centre|vignette|upright=2|Correction des dépendances sur un processeur superscalaire.]] ===Les unités d'exécution dans le désordre=== Réduire le nombre de ports d'émission permet de garder des fenêtres d'instruction et un ROB simples, rapides et économes en énergie. Et mine de rien, c'est là un compromis important : à quoi bon augmenter le nombre de ports d'émission si c'est pour que ce soit compensé par une fréquence plus faible ? Par contre, réduire le nombre de ports augmente la ''port contention'', surtout si on a mal réparti les ALU. Il faut donc trouver un compromis entre réduire le nombre de ports d'émission d'un côté, réduire la ''port contention'' de l'autre. Le ROB doit aussi être modifié pour émettre et terminer plusieurs instructions en même temps, en ajoutant des ports de lecture/écriture. Pour un CPU superscalaire peut émettre 8 µops à la fois, on se dit que le ROB peut retirer 8 µops par cycle maximum. Et c'est très souvent le cas en pratique. Cependant, certains processeurs ont un ROB qui peut retirer le double ! Un exemple est celui des CPU de micro-architecture Skymont, qui peuvent émettre 8 µops par cycle, mais en retirer 16. Quelques CPU de marque AMD étaient aussi dans ce cas. L'avantage est que cela permet de réduire la taille du ROB, du banc de registre et de quelques autres structures matérielles liées à l'exécution dans le désordre. Le gain sur-compense le cout en transistor nécessaire pour retirer 16 µops par cycle. Un détail est que le nombre de ports de lecture n'est pas le même selon que le processeur utilisé des stations de réservation ou une fenêtre d’instruction. Rappelons la terminologie utilisée dans ce cours. Les fenêtres d'instruction se contentent de mémoriser la micro-opération à émettre et quelques bits pour la disponibilité des opérandes. Par contre, les stations de réservations mémorisent aussi les opérandes des instructions. Et cette différence a une influence le nombre de ports de lecture du banc de registre. Supposons que toutes les instructions sont dyadiques, à deux opérandes. Comptons le nombre de ports nécessaires pour alimenter les unités de calcul, sans utiliser d'arbitre de banc de registre. Avec une fenêtre d'instruction, il faut le double du nombre de ports de ''dispatch''. Mais avec des stations de réservation, il faut le double des ports de ''schedule''. La raison est que les registres sont lus après émission avec une fenêtre d'instruction, avant avec des stations de réservation (sous-entendu, après le décodage). or, comme dit plus haut, les processeurs superscalaires larges ont souvent plus de ports de ''schedule'' que de ''dispatch''. ==Les optimisations des processeurs superscalaires larges== Les processeurs superscalaires larges ont des optimisations spécifiques, qui n'ont pas de sens sur les processeurs superscalaires étroits. Dans les grandes lignes, elles se classent en deux types. Les premières permettent de charger/décoder plus de 5-10 instructions simultanément. Le problème est alors que les branchements viennent mettre leur grain de sel et qu'il faut en tenir compte. Les secondes corrigent des problèmes qui apparaissent quand un CPU intègre plus de 5 à 10 unités de calcul. ===L'étape de chargement d'un CPU superscalaire large=== Charger plusieurs instructions pose de nombreux problèmes sur les processeurs superscalaires larges, qui émettent de 5 à 10 instructions simultanées. Pour rappel, la stratégie basique pour charger N instructions est de charger un bloc de N instructions consécutives. Les instructions après un branchement ne sont pas décodées ni exécutées, ce qui entraine une petite baisse de performance. Pour un N petit, la perte de performance est mineure. Mais pour un N large, elle devient suffisamment importante pour que la conception du CPU doit alors être repensée de fond en comble. Les analyses statistiques disent qu'il y a environ un branchement tous les 4 à 6 instructions. Un CPU a donc tendance à décoder des paquets de 3 à 5 instructions consécutives, avant qu'un branchement casse la dynamique. Or, cela pose des problèmes pour utiliser plusieurs décodeurs. L'implémentation basique, qui charge N instructions consécutives pour alimenter N décodeurs, fonctionne donc jusqu'à 4/6 décodeurs. Au-delà de 5 instructions, un CPU superscalaire doit être adapté pour gérer les branchements dans les paquets d'instruction qu'il émet. Le nombre de 5 instructions ne sort donc pas de nulle part. La première solution utilise un cache de µops, avec un ''front-end'' capable de charger/décoder 4/5 instructions à la fois, pas plus. L'idée est que la majorité des instructions proviennent du cache de µops (plus de 80%), ce qui fait qu'on peut émettre environ 10 instructions à partir du cache de µops, pas besoin d'avoir beaucoup de décodeurs. Les performances sont cependant dégradées lors d'un défaut de cache de µops. Les instructions doivent alors être chargées depuis le cache d'instruction, par paquets de 4-5, puis sont décodées par 4-5 décodeurs, ce qui ne permet pas d’alimenter les unités de calcul à plein régime. La solution précédente joue sur la distinction entre pots de décodage et d'émission. Elle permet d'émettre un grand nombre de µops sans pour autant charger un grand nombre d'instructions en même temps. En quelque sorte, ce n'est qu'une méthode de contournement, le CPU superscalaire reste de type étroit. Mais il est aussi possible d'utiliser un '''''front-end large''''', capable de charger une bonne dizaine d'instructions en même temps. La seule difficulté est de tenir compte de la présence de branchements. Pour rappel, avec un ''front-end'' étroit, les instructions situées après le premier branchement pris sont annulées, elles ne sont pas décodées ni exécutées. [[File:Fetch sur un processeur superscalaire avec prediction de branchements.png|centre|vignette|upright=2|Fetch sur un processeur superscalaire avec prédiction de branchements.]] Un ''front-end'' large charge les instructions de destination du branchement et les placent à sa suite. Ils chargent deux blocs à la fois et les fusionnent en un seul qui ne contient que les instructions présumées utiles. [[File:Cache d'instructions autoaligné.png|centre|vignette|upright=2|Cache d'instructions auto-aligné.]] Mais cela demande de charger deux blocs de mémoire en une fois, ce qui demande un cache d'instruction multiports. Il faut aussi ajouter un circuit pour assembler plusieurs morceaux de blocs en un seul : le fusionneur (''merger''). En pratique, cette solution demande d'ajouter un second port de lecture au cache, pour un cout en hardware important, avec un impact négatif sur le temps d'accès au cache. Et il faut rajouter le fusionneur, qui prend un étage de pipeline à lui tout seul. Et encore, cette solution ne gère pas le cas où un bloc contient plusieurs branchements ! [[File:Implémentation d'un cache d'instructions autoaligné.png|centre|vignette|upright=2|Implémentation d'un cache d'instructions auto-aligné.]] Les exemples précédents partent du principe qu'il n'y a qu'un seul branchement dans un bloc. Mais quand les blocs deviennent assez longs, il est fréquent d'avoir plusieurs branchements dans un bloc. Et il est même possible d'avoir plusieurs branchements pris. Dans un bloc de 8-10 instructions, ce n'est pas rare, et les CPU superscalaires à 8-10 voies sont la règle dans les CPU des années 2020. Pour gérer ce cas, il faut ajouter des ports supplémentaires au cache d'instruction et adapter le fusionneur, pour qu'il puisse fusionner trois ou quatre blocs. De plus, il faut utiliser une unité de prédiction de branchement capable de ''prédire plusieurs branchements par cycle''. ===Le décodage superscalaire large=== Le décodage des instructions n'est pas censé dépendre de l'implémentation du chargement. En théorie, une fois plusieurs blocs d'instruction fusionnés, on peut décoder les instructions en parallèle. Cependant, certains processeurs effectuent le décodage en tenant compte de la manière dont les branchements découpent les blocs. Les processeurs Atom d'Intel utilisent une solution de ce type, qui couple prédiction de branchement et décodage parallèle. Ils chargent/décodent des paquets de 3 instructions consécutives, chaque paquet étant séparé du précédent par un branchement. Pour cela, en plus d'avoir un fusionneur adapté, ils regroupent les décodeurs en ''clusters'', chacun regroupant 3 décodeurs, et pouvant décoder trois instructions consécutives. Les micro-architectures Tremont et Crestmont avaient deux ''clusters'' (6 décodeurs au total), la micro-architecture Skymont en intègre trois (9 décodeurs au total). Avec deux ''clusters'', le fonctionnement est le suivant. Si le code est composé d'une suite d'instruction sans branchements, seul le premier ''cluster'' est utilisé, limitant le décodage à trois instructions par cycle. Par contre, si l'une des trois instructions décodée est un branchement, alors le second ''cluster'' décode les trois instructions situées après ce branchement, à partir de l'adresse de destination. Avec un troisième ''cluster'', c'est la même chose, sauf qu'il faut qu'il y ait un branchement dans les trois instructions décodées par le second ''cluster''. Mais comment faire pour savoir quels décodeurs activés, quels ''clusters'' sont actifs ? C'est la prédiction de branchement qui commande. Avec deux ''clusters'', elle détermine si un branchement est présent dans les trois instructions à charger, et quelle est son adresse de destination. Sans branchement, elle prévient les décodeurs et les trois instructions chargées sont décodées dans un ''cluster'' libre. Si elle prédit un branchement, elle charge les instructions adéquates, et active les deux ''clusters''. Avec trois branchements, c'est le même mécanisme, sauf que l'unité de prédiction de branchement doit être capable de prédire deux branchements par cycle. Il faut noter que les décodeurs d'un ''cluster'' sont des décodeurs simples, sans microcode. Sur les processeurs Tremont et Crestmont, le microcode était unique et était partagé entre tous les ''clusters''. Un seul ''cluster'' pouvait décoder une instruction complexe avec le microcode, l'autre devait se limiter à des instructions simples uniquement. Depuis Skymont, le microcode est partiellement dupliqué, avec un microcode par ''cluster''. Le cout en hardware peut paraitre prohibitif, pour un gain en performance faible. Sauf que c'est oublier que ce sont des processeurs Atom, sur lesquels beaucoup d'instructions sont microcodées. Les processeurs Atom ont un budget en transistor limité, ce qui fait qu'il est préférable pour eux d'implémenter des instructions en microcode que d'utiliser des unités de calcul dédiées ou des circuits de décodage complexes. Pour limiter la casse, le microcode n'est que partiellement dupliqué. Seul le microcode pour les instructions fréquente est dupliqué. Il y a un microcode secondaire pour certaines instructions, qui est lui unique et partagé par tous les ''clusters''. ===Le contournement sur les processeurs superscalaires larges=== Pour rappel, la technique du contournement (''register bypass'') permet au résultat d'une instruction d'être immédiatement utilisable en sortie de l'ALU, avant même d'être enregistré dans les registres. Implémenter la technique du contournement demande d'utiliser des multiplexeurs pour relier la sortie de l'unité de calcul sur son entrée si besoin. Il faut aussi des comparateurs pour détecter des dépendances de données. [[File:Pipeline Bypass.png|centre|vignette|upright=1|Pipeline Bypass]] La sortie de chaque ALU doit être reliée aux entrées de toutes les autres, avec les comparateurs qui vont avec. Sur les processeurs ayant plusieurs d'unités de calculs, cela demande beaucoup d’interconnexions et de comparateurs. Pour N unités de calcul, cela demande 2 * N² interconnexions, implémentées avec 2N multiplexeurs de N entrées chacun. Si c'est faisable pour 2 ou 3 ALUs, la solution est impraticable au-delà. Non seulement ce serait un enfer à câbler, mais les fils seraient tellement longs que la fréquence du processeur en prendrait un coup. Notez que cela vaut pour les processeurs superscalaires, mais aussi pour tout processeur avec beaucoup d'unités de calcul. En théorie, un CPU à exécution dans le désordre, non-superscalaire, mais avec beaucoup d'unités de calcul, fait face au même problème. En pratique, avoir une dizaine d'ALU implique processeur superscalaire à exécution dans le désordre. D'où le fait qu'on parle du problème maintenant. La seule solution praticable est de ne pas relier toutes les unités de calcul ensemble. À la place, on préfère regrouper les unités de calcul dans différents '''agglomérats''' ('''cluster'''). Le contournement est alors possible entre les unités d'un même agglomérat, mais pas entre agglomérats différents. Évidemment, certaines possibilités de contournement sont perdues, mais c'est compensé par un gain en fréquence et un cout en circuit/fils réduit. C'est un bon compromis, ce qui explique que presque tous les processeurs modernes l'utilisent. Les rares exceptions sont les processeurs POWER 4 et POWER 5, qui ont préféré se passer de contournement pour garder un processeur très simple et une fréquence élevée. ===Le banc de registre des processeurs superscalaires larges=== Pour émettre deux opérations entières simultanément, il faut lire deux fois plus d'opérandes. Et il faut aussi écrire deux résultats dans les registres, en même temps. Pour cela, on double les ports du banc de registre. En général, il faut au maximum 2 ports de lecture et un port d'écriture par unité de calcul. Le problème, c'est que plus un banc de registres a de ports, plus il utilise de circuits, est compliqué à concevoir, consomme de courant et chauffe. Avec plusieurs dizaines d'unités de calcul différentes, ajouter autant de ports impacterait la fréquence du banc de registres et réduirait les performances, voire est totalement impraticable à cause des contraintes thermiques. Les processeurs superscalaires larges doivent donc trouver des solutions. La première solution utilise un banc de registre unique, mais n'utilise pas autant de ports que le pire des cas le demanderait. Le processeur détecte quand il n'y a pas assez de ports pour servir toutes les micro-opérations. Quand ça arrive, l'unité d'émission émet assez de micro-opérations pour exploiter les ports disponibles, mais met en attente les micro-opérations en trop, le temps que les ports se libèrent. Pour cela, le banc de registre est géré par un circuit d'arbitrage spécialisé, intégré à l'unité d'émission, l’'''arbitre du banc de registres''' (''register file arbiter''). Une autre solution est de dupliquer le banc de registres en plusieurs exemplaires qui contiennent exactement les mêmes données, mais avec moins de ports de lecture/écriture. Un exemple est celui des processeurs POWER, Alpha 21264 et Alpha 21464. Sur ces processeurs, le banc de registre est dupliqué en plusieurs exemplaires, qui contiennent exactement les mêmes données. Les écritures dans les registres sont envoyées à tous les bancs de registres, afin de garantir que leur contenu est identique. Chaque exemplaire a exactement deux ports de lecture. La conception du processeur est simplifiée, que ce soit au niveau du câblage, que de la conception des bancs de registres. Une dernière solution découpe le banc de registres en plusieurs bancs de registres plus petits, avec un réseau d'interconnexions pour échanger des données entre bancs de registres. Dans la plupart des cas, cette séparation est invisible du point de vue de l'assembleur et du langage machine. Le processeur se charge de transférer les données entre bancs de registres suivant les besoins. Sur d'autres processeurs, les transferts de données se font via une instruction spéciale, souvent appelée ''COPY''. [[File:Banc de registres distribué.png|centre|vignette|upright=2|Banc de registres distribué.]] Sur de certains processeurs, un branchement est exécuté par une unité de calcul spécialisée. Or les registres à lire pour déterminer l'adresse de destination du branchement ne sont pas forcément dans le même agglomérat que cette unité de calcul. Pour éviter cela, certains processeurs disposent d'une unité de calcul des branchements dans chaque agglomérat. Dans les cas où plusieurs unités veulent modifier le ''program counter'' en même temps, un système de contrôle général décide quelle unité a la priorité sur les autres. Mais d'autres processeurs fonctionnent autrement : seul un agglomérat possède une unité de branchement, qui peut recevoir des résultats de tests de toutes les autres unités de calcul, quel que soit l’agglomérat. ==La macro-fusion : une optimisation du décodage parallèle== Les processeurs superscalaires supportent la technique dite de '''macro-fusion''', qui permet de fusionner deux-trois instructions consécutives en une seule micro-opération. Par exemple, il est possible fusionner une instruction de test et une instruction de saut en une seule micro-opération de branchement. Il s'agit là de l'utilisation la plus importante de la macro-fusion sur les processeurs x86 modernes. La macro-fusion est utilisée pour fusionner un test et un saut en un branchement unique. L'optimisation est présente sur la plupart des CPU superscalaires modernes. Les CPU x86 l'implémentent, mais aussi quelques CPU ARM et les processeurs Apple de série M (M1 à M5, et potentiellement plus quand ils seront sortis). La fusion des instructions se fait lors du décodage des instructions, grâce à la coopération des décodeurs. Elle marche systématiquement si l'instruction de test et le saut sont consécutives, qu'elles forment bien une paire d'instruction. Par contre, insérer une instruction entre les deux empêche la macro-fusion de faire son travail, sauf si l'instruction insérée est un NOP. Les processeurs ARM64 avaient une forme spécifique de macro-fusion, utilisée pour un cas très précis : charger une constante immédiate de 32 ou 64 bits, dans un registre 64 bits. Les CPU ARM 64 bits utilisent des instructions codées sur 32 bits, qui ne peuvent encoder que des constantes immédiates de 16 bits. Pour charger une constante de 32/64 bits, il faut s'y prendre en plusieurs fois. Une première instruction MOVZ charge les 16 premiers bits, puis 2 à 4 instructions MOVK remplissent le reste. MOVZ met à zéro les 48 bits de poids fort, MOVK permet de sélectionner le doublet à modifier dans le registre (on modifie 16 bits, dont on choisit la place, le reste du registre est laissé tel quel). ARM permet de fusionner cette suite d'instructions en une seule µop MOV avec une constante immédiate. On parle alors de '''''MOVZ + MOVK fusion'''''. Une dernière utilisation, [https://www2.eecs.berkeley.edu/Pubs/TechRpts/2016/EECS-2016-130.pdf proposées pour le jeu d'instruction RISC-V], fusionnait un calcul d'adresse et une instruction LOAD/STORE en une seule instruction d'accès mémoire. Elle a été envisagée à une époque où le jeu d'instruction RISC-V ne gérait pas les modes d'adressage base + indice. L'idée était d'avoir une unité mémoire qui gérait le mode d'adressage base + indice et de fusionner des instructions pour l'exploiter. Mais de nos jours, le jeu d'instruction RISC-V intègre le mode d'adressage base + indice, ce qui fait que la macro-fusion n'a aucun intérêt pour ce cas précis. La macro-fusion a de nombreuses limitations en pratique. En théorie, on pourrait l'utiliser au-delà des deux situations mentionnées plus haut. Par exemple, pour fusionner une addition et une multiplication en une seule instruction MAD. Mais l'intérêt est assez faible. Notons que la macro-fusion ne permet que de fusionner des paires d'instructions. Aller au-delà demanderait un cout en circuit trop important. Déjà que tester deux paires utilise pas mal de comparateurs... ==Le ''stack engine'' : une optimisation de la pile d'appel== Les processeurs modernes intègrent une optimisation liée au pointeur de pile. Pour rappel, sur les architectures modernes, le pointeur de pile est un registre utilisé pour gérer la pile d'appel, précisément pour savoir où se trouve le sommet de la pile. Il est manipulé par les instructions CALL, RET, PUSH et POP, mais aussi par les instructions d'addition/soustraction pour gérer des cadres de pile de taille variable. De plus, il peut servir d'opérande pour des instructions LOAD/STORE, afin de lire/écrire des variables locales, les arguments d'une fonction, et autres. C'est donc un registre général adressable, intégré au banc de registre, altéré par l'unité de calcul entière. L'incrémentation/décrémentation du pointeur de pile passe donc par l'unité de calcul, lors des instructions CALL, RET, PUSH et POP. Mais, l'optimisation que nous allons voir permet d'incrémenter/décrémenter le pointeur de pile sans passer par l'ALU, ou presque. L'idée est de s'inspirer des architectures avec une pile d'adresse de retour, qui intègrent le pointeur de pile dans le séquenceur et l'incrémentent avec un incrémenteur dédié. ===Le ''stack engine''=== L'optimisation que nous allons voir utilise un '''''stack engine''''' intégré à l'unité de contrôle, au séquenceur. Le processeur contient toujours un pointeur de pile dans le banc de registre, cela ne change pas. Par contre, il n'est pas incrémenté/décrémenté à chaque instruction CALL, RET, PUSH, POP. Un compteur intégré au séquenceur est incrémenté à la place, nous l’appellerons le '''compteur delta'''. La vraie valeur du pointeur de pile s'obtient en additionnant le compteur delta avec le registre dans le banc de registre. Précisons que si le compteur delta vaut zéro, la vraie valeur est dans le banc de registre et peut s'utiliser telle quelle. Lorsqu'une instruction ADD/SUB/LOAD/STORE utilise le pointeur de pile comme opérande, elle a besoin de la vraie valeur. Si elle n'est pas dans le banc de registre, le séquenceur déclenche l'addition compteur-registre pour calculer la vraie valeur. Finalement, le banc de registre contient alors la bonne valeur et l'instruction peut s'exécuter sans encombre. L'idée est que le pointeur de pile est généralement altéré par une série d'instruction PUSH/POP consécutives, puis des instructions LOAD/STORE/ADD/SUB utilisent le pointeur de pile final comme opérande. En clair, une bonne partie des incrémentations/décrémentation est accumulée dans le compteur delta, puis la vraie valeur est calculée une fois pour toutes et est utilisée comme opérande. On accumule un delta dans le compteur delta, et ce compteur delta est additionné quand nécessaire. Précisons que le compteur delta est placé juste après le décodeur d'instruction, avant même le cache de micro-opération, l'unité de renommage et l'unité d'émission. Ainsi, les incrémentations/décrémentations du pointeur de pile disparaissent dès l'unité de décodage. Elles ne prennent pas de place dans le cache de micro-opération, ni dans la fenêtre d'instruction, ni dans la suite du pipeline. De nombreuses ressources sont économisées dans le ''front-end''. Mais utiliser un ''stack engine'' a aussi de nombreux avantages au niveau du chemin de données/''back-end''. Déjà, sur les processeurs à exécution dans le désordre, cela libère une unité de calcul qui peut être utilisée pour faire d'autres calculs. Ensuite, le compteur delta mémorise un delta assez court, de 8 bits sur le processeur Pentium M, un peu plus pour les suivants. L'incrémentation se fait donc via un incrémenteur 8 bits, pas une grosse ALU 32/64 bits. Il y a un gain en termes de consommation d'énergie, un incrémenteur 8 bits étant moins gourmand qu'une grosse ALU 32/64 bits. ===Les points de synchronisation du delta=== La technique ne fonctionne que si la vraie valeur du pointeur de pile est calculée au bon moment, avant chaque utilisation pertinente. Il y a donc des '''points de synchronisation''' qui forcent le calcul de la vraie valeur. Plus haut, nous avions dit que c'était à chaque fois qu'une instruction adresse le pointeur de pile explicitement, qui l'utilise comme opérande. Les instructions CALL, RET, PUSH et POP ne sont pas concernées par elles utilisent le pointeur de pile de manière implicite et ne font que l'incrémenter/décrémenter. Mais dans les faits, c'est plus compliqué. D'autres situations peuvent forcer une synchronisation, notamment un débordement du compteur delta. Le compteur delta est généralement un compteur de 8 bits, ce qui fait qu'il peut déborder. En cas de débordement du compteur, le séquenceur déclenche le calcul de la vraie valeur, puis réinitialise le compteur delta. La vraie valeur est donc calculée en avance dans ce cas précis. Précisons qu'un compteur delta de 8 bits permet de gérer environ 30 instructions PUSH/POP consécutives, ce qui rend les débordements de compteur delta assez peu fréquent. À noter que si le compteur delta vaut zéro, il n'y a pas besoin de calculer la vraie valeur, le séquenceur prend cette situation en compte. Un autre point de synchronisation est celui des interruptions et exceptions matérielles. Il faut que le compteur delta soit sauvegardé lors d'une interruption et restauré quand elle se termine. Idem lors d'une commutation de contexte, quand on passe d'un programme à un autre. Pour cela, le processeur peut déclencher le calcul de la vraie valeur lors d'une interruption, avant de sauvegarder les registres. Pour cela, le processeur intègre un mécanisme de sauvegarde automatique, qui mémorise la valeur de ce compteur dans le tampon de réordonnancement, pour forcer le calcul de la vraie valeur en cas de problème. La technique du ''stack engine'' est apparue sur les processeurs Pentium M d'Intel et les processeurs K10 d'AMD, et a été conservée sur tous les modèles ultérieurs. L'implémentation est cependant différente selon les processeurs, bien qu'on n'en connaisse pas les détails et que l'on doive se contenter des résultats de micro-benchmarks et des détails fournit par Intel et AMD. Selon certaines sources, dont les manuels d'optimisation d'Agner Fog, les processeurs AMD auraient moins de points de synchronisation que les processeurs Intel. De plus, leur ''stack engine'' serait placé plus loin que prévu dans le pipeline, après la file de micro-opération. ==La micro-fusion et la délamination== La '''micro-fusion''' est une optimisation qui retarde le décodage réel de certaines instructions assez loin dans le pipeline. Elle concerne surtout les instructions ''load-op'', mais pas que. D'autres instructions mémoire peuvent subir cette optimisation. L'essentiel est que, sans micro-fusion, ces instructions sont décodées en plusieurs micro-opérations. La micro-fusion retarde le décodage final de ces instructions, qui se fait assez tard dans le pipeline. Pour comprendre l'intérêt, nous allons devoir faire quelques explications. L'optimisation en question fait le décodage en deux temps. Prenons l'exemple d'une opération ''load-op'', qui est censée être décodée en deux micro-opérations : une lecture, puis une opération. Le décodage produit une '''macro-opération''' (terme d'Intel), qui est propagée dans le pipeline, pour être scindée en deux micro-opérations un peu avant les ALU et l'unité mémoire. La macro-opération est techniquement une micro-opération comme une autre, sauf que ce n'est pas la micro-opération finale. L'unité d'émission découpe cette macro-opération en deux micro-opérations, qui sont émises l'une après l'autre. Pour résumer, les instructions ''load-op'' ne sont complétement découpées en micro-opération lors de l'émission, pas lors du décodage. L'optimisation impacte ce qui se situe entre le décodeur d'instruction et l'unité d'émission : l'unité de renommage, la file de micro-opérations, le ''Loop Stream Detector'' et/ou le cache de micro-opération. L'intérêt de l'optimisation est que l'on fusionne deux micro-opérations en une seule macro-opération. Le cache de micro-opération et la file de micro-opération peuvent mémoriser plus de micro-opérations, vu que certaines sont fusionnées. Quant à l'unité de renommage, elle renomme une seule macro-opération au lieu de renommer deux micro-opérations, ce qui augmente le débit du renommage de registre. Par contre, cela complique un peu le travail de l'unité d'émission, qui doit découper les instructions ''load-op'' en deux et émettre les micro-opérations l'une après l'autre. Il faut noter qu'il n'y a pas de fusion proprement dite. Le décodeur ne décode pas deux micro-opérations séparées, avant de les fusionner. En réalité, il génère directement une macro-opération, qui correspond à deux micro-opérations, que ce soit plus tard dans le pipeline ou sur une autre architecture. D'ailleurs, la micro-fusion a lieu à l'intérieur d'une instruction, qui est censée être traduite en plusieurs micro-opérations. Et encore : pas toutes. La conséquence est que la technique n'a de sens que sur les processeurs CISC, avec des instructions assez complexes pour être décodées en plusieurs micro-opérations. En pratique, seuls les CPU x86 sont concernés. ===Les domaines de fusion=== La micro-fusion n'est pas spécifique aux processeurs superscalaires. En théorie, on peut l'implémenter sur un processeur non-superscalaire à émission dans l'ordre. Cependant, je ne connais aucun processeur à émission dans l'ordre qui implémente la micro-fusion, même si son implémentation est en théorie possible sur de tels processeurs. La micro-fusion est implémentée différemment selon que l'émission se fasse dans l'ordre, dans le désordre, avec une fenêtre d'instruction, avec des stations de réservation, etc. J'ai dit plus haut que les micro-opérations sont générées lors de l'émission, mais le moment exact varie suivant le processeur. Pour simplifier le tout, nous parlerons de '''scission''' quand le processeur traduit une macro-opération en deux/trois micro-opérations. Le moment où a lieu la scission est théoriquement dans l'unité d'émission, mais celle-ci prend plusieurs formes selon que le processeur utilise l'émission dans l'ordre, dans le désordre, avec une fenêtre d'instruction, avec des stations de réservation, etc. Les processeurs à exécution dans l'ordre sont le cas le plus simple. L'unité d'émission est alors en charge du découpage des macro-opérations en micro-opérations. La file de micro-opération ou le cache de micro-opérations mémorisent des macro-opérations. À l'opposé, le tampon de ré-ordonnancement mémorise lui des micro-opérations. Le reste du processeur n'est pas affecté. Sur les processeurs à exécution dans le désordre, les différences sont nombreuses. Le tampon de ré-ordonnancement peut mémoriser soit les micro-opérations finales, soit des macro-opérations. Il en est de même avec la fenêtre d'instruction et/ou les stations de réservation, qu'elles soient centralisées ou décentralisées. Tout dépend de comment se fait l'implémentation. Une implémentation simple effectue la scission des macro-opérations dans l'unité d'émission, comme sur les processeurs à exécution dans l'ordre. Sur de nombreux processeurs, l'unité d'émission et de renommage sont simplement fusionnées, ce qui fait que la scission a lieu juste après le renommage de registres. En conséquence, le tampon de re-ordonnancement et les fenêtres d'instruction ne voient que des micro-opérations. Les macro-opérations disparaissent juste avant. Cette implémentation est préférée avec une fenêtre d'instruction centralisée, car on voit mal comment scinder une macro-opération juste avant l'ALU. Une autre implémentation fait la scission en sortie de la fenêtre d'instruction ou des stations de réservation, qu'elles soient centralisées ou décentralisées. Dans ce cas, le tampon de re-ordonnancement fait de même. L'avantage est que le tampon de ré-ordonnancement stocke des macro-opérations, ce qui augmente sa taille effective. Par exemple, il utilise une seule entrée pour une instruction ''load-op'', au lieu de deux sans micro-fusion de ce type. ===La délamination=== La scission peut parfois avoir lieu plus tôt, dans des circonstances très précises. Concrètement, cela ne concerne que quelques micro-architectures d'Intel, pour des instructions très précises. La raison est les limitations du format des micro-opérations, pas une histoire d'optimisation. Le processeur aimerait pouvoir faire la micro-fusion, mais ne peut pas à cause de ces limitations. Une telle situation cause alors une '''délamination''' : les micro-opérations sont scindées précocement. Par exemple, sur la micro-architecture SandyBridge d'Intel, les opérations ''load-op'' sont fusionnées, sauf si elles utilisent l'adressage indicé. Les micro-opérations de ce processeur ne peuvent avoir que deux opérandes. Trois opérandes est de trop, et les opérations ''load-op'' en adressage indicé dépassent cette limite. Dans ce cas, les micro-opérations sont fusionnées en sortie du décodeur, mais sont scindés avant d’entrer dans la file de micro-opération. L'avantage n'est pas évident, vu que l'unité de renommage suit de près le décodeur d'instruction. Il est que le cache de micro-opérations mémorise des macro-opérations, pas deux micro-opérations. ===La micro-fusion des processeurs Intel et AMD=== La micro-fusion est apparue sur le Pentium M d'Intel, et a été conservée telle quelle sur les processeurs Intel suivants. Les processeurs AMD supportent la micro-fusion depuis au moins la micro-architecture AMD K8. Mieux que ça, la délamination n'était pas utilisée car les micro-opérations acceptaient plus de 2 opérandes. Que ce soit chez AMD ou Intel, elle concerne deux types d'instructions : les écritures mémoire et les instructions ''load-op''. Sur le Pentium M, il y avait quelques limitations, l'optimisation se limitant par exemple aux registres entiers et flottants, mais pas aux registres MMX, mais pas les registres SSE (nous verrons ces registres dans quelques chapitres). Mais les processeurs modernes n'ont plus cette limitation. La micro-fusion des CPU Intel concerne deux types d'instructions : les écritures mémoire et les instructions ''load-op''. Pour les écritures, la situation est plus complexe que pour les instructions ''load-op''. Sur les processeurs Intel précédents, les écritures étaient réalisées dans deux unités séparées : le calcul d'adresse dans une unité de calcul, l'écriture proprement dite dans l'unité mémoire. Elles prenaient donc deux micro-opérations. Le processeur a changé ses unités mémoire, ce qui fait qu'elle pouvait faire les deux en une seule micro-opération. Il s'agissait alors d'une optimisation du chemin de données, plus que de la micro-fusion proprement dite, mais je la mentionne ici malgré tout. La micro-fusion sur ce processeur avait un avantage en plus des avantages usuels : elle utilisait mieux les décodeurs. Les Pentium M avaient trois décodeurs pour décoder plusieurs instructions en même temps, chose qu'on détaillera dans le chapitre sur les processeurs superscalaires. Un seul décodeur pouvait décoder toutes les instructions, les deux autres ne peuvent décoder que les instructions simples, qui se décodent en une seule micro-opération. Avec la micro-fusion, les trois décodeurs peuvent gérer les écritures mémoire et les opérations ''load-op'', vu qu'elles deviennent des instructions simples. Sans cela, elles auraient été limitées au premier décodeur. <noinclude> {{NavChapitre | book=Fonctionnement d'un ordinateur | prev=Le parallélisme mémoire | prevText=Le parallélisme mémoire | next=Exemples de microarchitectures CPU : le cas du x86 | nextText=Exemples de microarchitectures CPU : le cas du x86 }} </noinclude> gyc6v7wi13wp55zl9x500s3i9lemhb2 773237 773236 2026-09-26T13:57:18Z Mewtow 31375 /* La double émission entière-flottante */ 773237 wikitext text/x-wiki Les processeurs vus auparavant ne peuvent émettre au maximum qu'une instruction par cycle d'horloge. Ils peuvent avoir plusieurs instructions qui s'exécutent en même temps, dans des unités de calcul séparées. C'est le cas dès qu'une instruction multi-cycles s'exécute. Par contre, ils ne peuvent démarrer qu'une seule instruction par cycle. Et quand on court après la performance, ce n'est pas assez ! Les concepteurs de processeurs ont inventés des processeurs qui émettent plusieurs instructions par cycle : les '''processeurs à émissions multiples'''. Un processeur à émission multiple charge plusieurs instructions en même temps, les décode en parallèle, puis les émet en même temps sur des unités de calculs séparées. Pour cela, il faut gérer les dépendances entre instructions, répartir les instructions sur différentes unités de calcul, et cela n'est pas une mince affaire. En général, pour un processeur à émission multiple capable d'émettre N instructions en parallèle, on parle de '''processeur à N voies''', ou encore un ''processeur à N pipelines''. [[File:Superscalarpipeline.svg|centre|vignette|upright=1.5|Pipeline RISC classique à cinq étages sur un processeur superscalaire. On voit bien que plusieurs instructions sont chargées en même temps.]] Les processeurs à émission multiple sont de deux types : les processeurs VLIW et les '''processeurs superscalaires'''. Nous mettons les processeurs VLIW de côté en attendant le prochain chapitre. La raison est qu'ils ont un jeu d'instruction spécialisé qui expose le parallélisme d'instruction directement au niveau du jeu d'instructions, là où les processeurs superscalaires restent des processeurs au jeu d'instruction normal. La différence principale entre processeur VLIW et superscalaire est qu'un processeur superscalaire répartit les instructions sur les unités de calcul à l’exécution, là où un processeur VLIW délègue cette tâche au compilateur. ==Le nombre de voies d'un processeur superscalaire== Les processeurs superscalaires les plus simples ne permettent que d'émettre deux micro-opérations à la fois, d'où leur nom de processeur ''dual issue'', ce qui se traduit en '''processeur à double émission'''. Les processeurs triple émission permettent eux d’émettre trois instructions à la fois. A l'opposé, les CPU superscalaires les plus puissants peuvent émettre jusqu'à 8-10 instructions simultanément. En 2026, les CPU superscalaires peuvent monter jusqu’à 10 µops émises en même temps, ce qui est énorme ! Et cela nous amène à faire une distinction entre les processeurs superscalaires dits '''étroits''' et ceux dits '''larges'''. Dans ce wikilivre, on part du principe que les processeurs superscalaires étroits émettent jusqu'à 4-5 µops à la fois, alors que les larges peuvent en émettre plus, l'intervalle entre les deux est une zone grise. Le nombre exact n'est pas fixé dans le marbre, la distinction est volontairement vague. Nous parlerons de la '''largeur d'un CPU superscalaire''' pour parler du nombre de voies, du nombre de µops émises en même temps. Plus un CPU superscalaire est large, plus il est performant ''toute chose égale par ailleurs''. Mais dans les faits, vous pouvez oublier la partie en italique. Élargir un CPU superscalaire demande de rajouter beaucoup de circuits. Et ce cout en circuit aura un impact sur la performance. A budget en transistors limité, il y a un compromis entre élargir un CPU et rajouter du cache ou la taille des fenêtres d'instructions/ROB et autres. ===Les contraintes d’appariement=== Prenons un processeur double émission, capable d'émettre deux instructions simultanément. Émettre deux instructions en même temps n'est pas toujours possible. Par exemple, si deux instructions sont dépendantes, on ne peut pas les émettre en même temps. Mais oublions un instant ces histoires de dépendances de données, et partons du principe que le CPU ait affaire à deux instructions indépendantes, une paire idéale. Un CPU double émission "parfait" pourrait exécuter n'importe quelle paire d'instruction. Par exemple, il pourrait émettre deux multiplications consécutives, deux lectures consécutives, une lecture et une écriture en même temps, etc. En pratique, aucun processeur ne permet cela. Implémenter un CPU superscalaire "parfait" aurait un cout en circuit beaucoup trop conséquent. Par exemple, pour émettre deux multiplications en même temps, il faudrait deux multiplieurs séparés. Idem pour exécuter deux accès mémoire simultané : cela demande d'avoir deux unités mémoire et un cache multiport. Mais en pratique, aucun CPU superscalaire ne fait cela. La totalité des CPU double émission se débrouille avec un seul circuit multiplieur et une seule unité mémoire. Et cela entraine l'apparition de dépendances structurelles, qui font que certaines paires d'instructions sont impossibles. Par exemple, impossible d'émettre deux multiplications en même temps avec un seul circuit multiplieur. Mais émettre une multiplication en parallèle d'une addition est parfaitement possible. Il y a donc des paires d'instructions autorisées et des paires interdites. Et la logique est la même sur les CPU à triple ou quadruple émission, et au-delà. : des triplets ou quadruplet d'instructions sont impossibles à émettre simultanément, alors que d'autres le sont. Pour le dire autrement, il y a des '''contraintes d'appariement''' sur les instructions à émettre en même temps. ===L'impact des contraintes d’appariement sur la performance=== Ces contraintes d’appariement ont un impact sur les performance, mais celui-ci peut être grandement mitigé. Les programmes informatiques utilisent en pratique beaucoup plus d'opérations entières de base que de multiplication, branchements et accès mémoire. Il y a environ 5 opérations entières de base pour une multiplication entière. En pratique, il est rare de devoir exécuter deux multiplications à la fois. Idem pour les décalages et rotations, qui sont assez éloignés les uns des autres. Deux branchements consécutifs sont quant à eux plus fréquents. Deux accès mémoire consécutifs est assez rare, mais ils restent assez proches les uns des autres malgré tout. La conséquence est que certaines paires d'instructions sont privilégiées par rapport au reste. Les paires regroupant deux calculs basiques (additions, soustractions, ...) sont systématiquement supportées, car dupliquer des ALU entières est facile. Les paires regroupant un calcul et une autre instruction sont elles aussi systématiquement supportées. Par contre, les paires avec deux branchements, deux accès mémoire, deux décalages/rotations, ou deux multiplications/divisions sont fréquemment interdites. Seuls les CPU superscalaires larges peuvent les permettre, et encore : sous conditions. Le résultat est que le processeur peut se débrouiller avec une seule unité mémoire et une unité de branchement. ===Terminologie pour la suite du chapitre=== Décrire les contraintes d’appariement demande de dire quelles combinaisons d’instructions sont autorisées ou interdites. Et pour cela, nous allons introduire les abréviations suivantes : * INT est un raccourcis pour parler d'une opération entière simple, qu'une ALU entière peut exécuter. * MUL correspond à une opération entière complexe, typiquement une multiplication, éventuellement à une division. * FLOAT désigne une opération flottante, peut importe laquelle. * MEM désigne un accès mémoire, à savoir une instruction ou µop mémoire. * BRANCH désigne une instruction de branchement. Cette terminologie permettra de définir des paires d'instructions, qu'elles soient possibles ou interdites. Par exemple, l'expression INT + FLOAT désigne une paire d'instruction regroupant une instruction entière et une instruction flottante, peu importe l'ordre des deux instructions. Il en sera de même pour les triplets, quadruplets, ou plus. Par exemple, l'expression INT + INT + FLOAT + MUL désigne un quadruplet d'instructions regroupant : deux instructions entières, une instruction flottante, et une multiplication entière. J'utiliserais aussi des parenthèses pour regrouper certaines combinaisons. Par exemple, l'expression INT + (MUL /FLOAT) veut parler d'une paire d'instruction où la première est une opération entière basique, la seconde est : soit une multiplication, soit une opération flottante. ==Le ''front-end'' des processeurs superscalaires== Un processeur superscalaire doit charger, décoder, et exécuter plusieurs instructions par cycle. Par exemple, un processeur triple émission doit charger 3 instructions lors d'un premier cycle, puis les décoder au cycle suivant, et les exécuter au cycle suivant. Il travaille donc pas paquets de 3 instructions, dans cet exemple. Intuitivement, charger, décoder et exécuter plusieurs instructions à la fois demande de dupliquer des circuits. Par exemple, il faut dupliquer les décodeurs pour décoder plusieurs instructions. Les unités de calcul sont aussi dupliquées, sauf sur certaines processeurs qui arrivent à ruser, mais laissons cela de côté pour plus tard. Mais d'autres circuits sont simplement modifiés, comme l'unité de chargement ou l'unité d'émission. Voyons ce qu'il en est dans le détail. {|class="wikitable" |- ! rowspan="2" | Processeur sans émission multiple | rowspan="2" | Chargement | rowspan="2" | Décodage | rowspan="2" | Renommage | rowspan="2" | Émission | Exécution / ALU | rowspan="2" | ''Commit''/ROB |- | Exécution / ALU |- ! rowspan="4" | Processeur superscalaire | rowspan="4" | Chargement | rowspan="2" | Décodage | rowspan="4" | Renommage | rowspan="4" | Émission | Exécution / ALU | rowspan="4" | ''Commit''/ROB |- | Exécution / ALU |- | rowspan="2" | Décodage | Exécution / ALU |- | Exécution / ALU |} Nous allons d'abord voir ce qu'il en est pour le ''front-end'', à savoir la partie du CPU qui s'occupe de charger et décoder les instructions. Elle regroupe l'unité de chargement, les décodeurs, l'unité de prédiction de branchement, la file de µops, la file d'instruction, et divers caches. En soi, les changements portent surtout sur l'unité de chargement et les décodeurs. ===Le chargement de plusieurs instructions consécutives=== Un processeur superscalaire charge plusieurs instructions en même temps, ''en tenant compte de la présence de branchements''. La partie en italique est importante et elle a une conséquence très forte : la manière de faire est fortement différente entre un CPU superscalaire étroit et large. Un CPU superscalaire étroit fait au plus simple : il charge un bloc de 2, 3, 4 instructions consécutives en mémoire RAM. Il faut alors doubler/tripler/quadrupler la taille du bus connecté au cache d'instruction. Par exemple, pour charger quatre instructions de 32 bits chacune, il suffit de lire un bloc de 128 bits dans le cache d'instruction. Un bloc de 8, 16, 32 octets est donc lu depuis le cache et est ensuite découpé en instructions, envoyées chacun à un décodeur. Charger plusieurs instructions consécutives ne marche bien qu'en absence de branchements. La présence de branchements casse cette dynamique, et il faut gérer leur présence. * Sans prédiction de branchement, toutes les instructions d'un bloc sont décodées et exécutées en même temps, même s'il y a un branchement dans le tas. * Avec prédiction de branchement, les instructions situées après le branchement ne sont pas décodées ni exécutées. L'unité de chargement coupe le bloc chargé au niveau du premier branchement pris, remplit les vides avec des NOP, avant d'envoyer le tout à l'unité de décodage. Les instructions à l'adresse de destination seront chargées au cycle suivant, en chargeant un nouveau bloc. [[File:Fetch sur un processeur superscalaire avec prediction de branchements.png|centre|vignette|upright=2|Fetch sur un processeur superscalaire avec prédiction de branchements.]] Un autre défaut est que sur le CPU charge un bloc de 128 bits à la fois, les instructions doivent être alignées sur 128 bits. Et on peut généraliser à n'importe quelle taille de bloc. Si les instructions ne sont pas alignées, il faut gérer la situation. Il arrive qu'une instruction soit à cheval sur deux blocs, avec un morceau dans le premier bloc, un autre dans le second. Dans ce cas, l'instruction est chargée en deux fois, et des circuits doivent gérer la situation pour recoller les morceaux. Sur les CPU avec des instructions de taille fixe, le problème ne survient que rarement. Mais il est fréquent avec des instructions de taille variable. Un autre défaut est lié aux branchements. Imaginez qu'un branchement s'exécute et branche vers une instruction de destination. Rien n'indique que l'instruction de destination sera au tout début d'un bloc. Si ça se trouve, elle sera en troisième position d'un bloc de 4 instructions. Une solution simple charge le bloc en respectant l'alignement, et les deux premières instructions du bloc ne sont pas à exécuter. Il y a donc une petite perte de performance. Certains CPU, comme l'IBM RS/6000, ont la machinerie charger un bloc de N instructions même s'il n'est pas aligné. La machinerie en question se trouve dans le cache d'instruction et est tout sauf simple à expliquer,ce qui fait que je me permets de la passer sous silence. Un détail, cependant : elle ne permet pas de charger des blocs qui sont à cheval sur deux lignes de cache. ===Le décodage parallèle des instructions=== Pour le décodage des instructions, plusieurs instructions sont décodées en même temps. On parle alors de '''décodage parallèle'''. Pour ce faire, un processeur superscalaire contient plusieurs décodeurs, chacun pouvant décoder une instruction en parallèle des autres. Prenons par exemple un processeur superscalaire à 4 voies : le décodage se fera dans quatre décodeurs séparés, qui fonctionneront en parallèle. Cependant, cette implémentation ne marche bien que pour les processeurs RISC, les processeurs CISC devant faire avec un microcode gourmand en circuits. Les processeurs CISC utilisent des décodeurs hybrides, avec un microcode qui complémente un décodeur câblé. Dupliquer le microcode aurait un cout en transistors trop important, ce qui fait que seuls les décodeurs câblés sont dupliqués. Les CPU CISC superscalaires disposent donc de plusieurs décodeurs simples, capables de décoder les instructions les plus courantes, avec un seul microcode. La conséquence est qu'il n'est pas possible de décoder deux instructions microcodées en même temps. Par contre, il reste possible de décoder plusieurs instructions non-microcodées ou une instruction microcodée couplée à une instruction non-microcodée. Vu qu'il est rare que deux instructions microcodées se suivent dans un programme, le cout en performance est extrêmement mineur. Il faut noter qu'une instruction correspond à une ou plusieurs µops. Il est donc possible de charger 5 instructions et de se retrouver avec 8 µops en sortie des décodeurs. Et c'est la source d'une distinction très importante. Plus haut, j'ai parlé de processeurs superscalaires à N voies. La terminologie peut avoir deux interprétations. La première est que le processeur charge/décode N instructions à la fois. Une seconde est qu'il est capable d'exécuter/émettre N µops en même temps. Les deux ne sont pas équivalentes, vu qu'un processeur superscalaire peut, par exemple, charger/décoder N instructions et émettre/exécuter N+2 µops. La distinction est particulièrement importante sur les processeurs superscalaires larges. Certains d'entre eux sont capables de charger 5 instructions et d'en émettre le double ! Un exemple classique était les processeurs Athlon d'AMD, qui pouvaient décoder deux instructions par cycle, mais émettre 4 µops par cycle. Ce n'était pas un problème, car beaucoup d'instructions, même basiques, étaient décodées en 2 µops. ===L'émission parallèle des instructions=== La superscalarité modifie en profondeur l'unité d'émission, notamment au niveau de son interface. Avant, elle recevait une micro-opération sur son entrée, et fournissait la micro-opération émise sur une sortie. Et cela vaut aussi bien pour une unité d'émission simple, un ''scoreboard'', une fenêtre d'instruction ou des stations de réservation. Mais avec l'émission multiple, les sorties et entrées sont dupliquées. Pour la double émission, il y a deux entrées vu qu'elle doit recevoir deux micro-opération décodées/renommées, et deux sorties pour émettre deux micro-opérations. Dans ce qui suit, nous ne parlerons pas d'entrée et de sortie, mais de '''ports de décodage''' et de '''ports d'émission'''. Les ports de décodage reçoivent des micro-opérations provenant de l'unité de décodage, les ports d'émissions sont pour les micro-opérations émises. Les ports d'émission injectent les micro-opérations émises dans le pipeline, elle sont propagée au banc de registre, aux ALUs, etc. Un processeur superscalaire a forcément plusieurs ports d'émission (et de décodage). Si l'interface de l'unité d'émission change, l'intérieur est tout autant modifié. Sur un CPU non-superscalaire, l'unité d'émission reçoit une µop "candidate à l'émission", et détecte les dépendances entre avec les µops en vol, en cours d'exécution dans le pipeline. Et c'est toujours le cas sur les processeurs superscalaires : l'unité d'émission détecte les dépendances entre les µops candidates et les µops en vol. Mais avec la siperscalarité, elle doit en plus tester les dépendances entre les µops candidates. Si l'unité d'émission est un ''scoreboard'', il doit détecter les dépendances entre instructions émises simultanément et les sérialiser si besoin. Avec une fenêtre d'instruction, il n'y a pour ainsi dire rien à faire, vu que les dépendances sont éliminées par le renommage de registre et que les signaux de réveil gèrent les dépendances RAW. C'est la raison pour laquelle les processeurs superscalaires modernes n'utilisent pas de ''scoreboard''. ==Les processeurs implémentés avec plusieurs pipelines== Nous venons de voir comment la superscalarité impacte le ''front-end'', il est maintenant de voir ce qu'il en est de l'unité d'émission et des ''back-ends''. Un processeur superscalaire peut utiliser l'exécution dans le désordre et le renommage de registres, mais ce n'est pas une obligation. Historiquement, les premiers processeurs superscalaires n'utilisaient pas d'exécution dans le désordre. Le meilleur exemple est celui du Pentium 1 et 2 d'Intel. Ils étaient tous deux des processeurs superscalaires, mais n'avaient pas d'exécution dans le désordre, qui est apparue sur le Pentium 3. les explications sont plus simples si on met de côté l'exécution dans le désordre. Vous vous dites que l'implémentation d'un processeur superscalaire n'est pas la même suivant que le CPU utilise l'exécution dans le désordre, ou non. Mais dans les faits, la distinction est plus subtile que ca. Pour l'expliquer, je vais utiliser une classification originale, qui sépare les processeurs superscalaires en trois sous-types. La distinction se fait sur la manière dont la superscalarité est implémentée en matériel.La classification que j'utilise distingue : * Les processeurs avec plusieurs pipelines, sans exécution dans le désordre. * Les processeurs superscalaires avec un pipeline dynamique. * Les processeurs avec des fenêtres d'instruction décentralisées. La distinction n'est pas qu'une question d'implémentation matérielle. Elle impacte aussi le ratio entre instructions chargées/décodées et instructions émises. Vous vous dites que les deux sont égales, mais ce n'est le cas que sur les processeurs avec N pipelines ! Sur les autres processeurs, il est possible de décoder 4 instructions, mais d'en émettre maximum 6 par cycle ! Et sur les CPU avec des fenêtres d'instruction décentralisées, il y a aussi une différence entre le nombre d'instructions qui sont ''dispatch'' et ''schedule''. La catégorie la plus simple est celle des '''processeurs avec plusieurs pipelines'''. Ce sont des processeurs superscalaires sans exécution dans le désordre. Ils partent d'un processeur non-superscalaire, et ajoutent un second pipeline, en sortie de l'unité d'émission. Le second pipeline permet l'émission parallèle d'une seconde instruction. Le second pipeline n'est pas identique au pipeline originel, sur plusieurs points. Premièrement, il n'a pas exactement les mêmes circuits. Le second pipeline est généralement composé d'une unité de calcul, d'une FPU, éventuellement d'un second banc de registres, guère plus. Il permet d'exécuter une seconde opération de calcul, pas plus, ce qui est à l'origine de contraintes d'appariemment assez fortes. De plus, il n'a pas forcément la même longueur, il peut avoir des étages en moins ou en plus. Il s'agit de la forme la plus simple de processeur superscalaire et nous allons leur consacrer cette section. ===La double émission entière-flottante=== L'exemple le plus simple utilise un second pipeline pour les opérations flottantes, séparé du pipeline originel. L'ajout d'un pipeline flottant permet d'émettre une micro-opération entière en même temps qu'une micro-opération flottante, ce qui s'appelle la '''double émission entière-flottante'''. La micro-opération entière s'exécute dans l'ALU entière, la micro-opération flottante dans la FPU. Les exemples les plus notables sont le PA-RISC 7100, le POWER 1 (RIOS .& et .9) et le PowerPC 601. L'Apollo DN10000 et l'Intel i860 utilisaient aussi cette forme de double émission, mais étaient des prototypes de CPU VLIW. [[File:Double émission entière-flottante.png|centre|vignette|upright=2|Double émission entière-flottante]] Pour bien expliquer les choses, prenons le processeur illustré ci-dessous, avec deux ''back-end'' séparés : un pour la FPU, et un autre pour le reste. Les instructions entières et mémoire passent toutes par le ''back-end'' généraliste. [[File:CPU non-superscalaire basique, avec une FPU.png|centre|vignette|upright=1.5|CPU non-superscalaire basique, avec une FPU]] Les deux ''back-end'' sont des pipelines séparés, mais il faut que l'unité d'émission soit spécialement conçue pour en profiter. Un processeur simple émission peut démarrer une seule µop par cycle : soit la µop est envoyée au ''back-end'' flottant, soit elle est envoyée au ''back-end'' général. Avec la double émission entière-flottante, le processeur peut envoyer une µop dans chaqaue pipeline, à chaque cycle. {| |- |[[File:CPU simple émission avec une FPU.png|centre|vignette|upright=1.5|CPU simple émission avec une FPU.]] |[[File:Double émission entière flottante avec un second pipeline.png|centre|vignette|upright=1.5|CPU avec un second pipeline flottant.]] |} Notez que le terme double émission ''entière''-flottante est un peu trompeur. En réalité, la technique permet d'émettre une instruction flottante avec n'importe quelle autre instruction non-flottante, avec des restrictions pour les branchements. Il est par exemple possible d'exécuter un calcul flottant en parallèle d'un accès mémoire. Sur les CPU 32 bits, il est possible de faire les multiplications dans la FPU. Pour rappel, la FPU contient un multiplieur entier 53-54 bits, pour multiplier les mantisses de deux flottants 64 bits, qu'on peut utiliser pour faire les multiplications entières 32 bits. Cela permet d'émettre une multiplication en même temps qu'une opération entière dans l'ALU. Ainsi, le CPU pouvait émettre deux instructions : une opération entière de base, avec soit une multiplication, soit une opération flottante. En clair, une émission de type INT + (MUL /FLOAT). Par contre, on perd la possibilité d'émettre une multiplication entière avec une opération flottante, mais c'était un compromis qui en valait la peine. Le CPU PA-RISC 7100 implémentait cette optimisation. Il s'agissait d'un CPU double émission entière-flottante, qui utilisait donc un partitionnement assez simple. Le combo partitionnement + reconditionnement permettait ici de gagner en performance, pour un cout en matériel très limité. D’autant plus limité que le jeu d'instruction prévoyait le reconditionnement de la FPU. Les multiplications ne lisaient pas leurs opérandes dans les registres entiers, mais dans les registres flottants. ===La double émission entière=== Une autre possibilité ajoute un second pipeline, capable d'exécuter des opérations entières simples, appelé le pipeline entier. Le processeur supporte alors la '''double émission entière''', à savoir qu'ils peuvent émettre une opération entière en plus d'une autre instruction. L'opération entière en question est une opération simple, comme une addition, une soustraction, une opération bit à bit, une comparaison, etc. Les exemples classiques sont les processeurs Intel i960 et Pentium. L'implémentation était différente entre l'i960 et le Pentium : Le Pentium avait une seconde ALU entière, alors que le i960 rusait. Le Pentium dupliquait l'ALU entière, pour créer un second pipeline entier. Sur le Pentium originel, les contraintes d'appariement étaient assez strictes. Le Pentium avait une FPU, mais ne pouvait pas l'utiliser en double émission. Il lui était impossible d'exécuter une opération flottante en même temps qu'une opération entière (branchements inclus). Le Cyrix 6x86 était une sorte de Pentium produit par l'entreprise Cyrix, avec une microarchitecture différente. Il avait des contraintes d’appariement réduites. Par exemple, il pouvait émettre une opération flottante en parallèle d'une opération entière. Nous détaillerons ces processeurs dans le prochain chapitre, qui porte justement sur les CPU superscalaires x86. L'Intel i960 n'avait pas de FPU, juste une ALU entière et une unité mémoire. Il utilisait l'unité mémoire comme une seconde ALU entière limitée. Pour rappel, une unité mémoire contient une AGU, à savoir une ALU spécialisée dans les calculs d'adresse. Elle est capable de faire des additions, soustractions, et quelques décalages limités (des décalages par 2, 4, 8, pas plus). L'Intel i960 ajoutait des connexions entre AGU et banc de registre, afin que celle-ci serve de seconde unité de calcul. L'AGU est déjà reliée aux deux ports de lecture, pour lire les opérandes du calcul d'adresse, il suffit de la connecter sur le port d'écriture. Le processeur peut alors émettre deux instructions/µops entières, sous certaines conditions. [[File:Emission multiple des opérations entières, double émission.png|centre|vignette|upright=2|Émission multiple des opérations entières, double émission.]] ==Les processeurs superscalaires dynamiques== Les processeurs précédents, avec 2 pipelines, avaient une unité d'émission assez simple. Elle avait deux ports de décodage, et deux ports d'émission. Le nombre de ports de décodage et d'émission étaient identiques. Vous vous dites que c'est naturel, et intuitif, que c'est normal pour un processeur superscalaire. Mais en réalité, la majorité des processeurs superscalaires a plus de ports d'émission que de décodage. Expliquer pourquoi va cependant nous demander de parler de comment la superscalarité exploite les unités de calcul. Posons-nous cette question : de combien d'unités un processeur superscalaire a besoin ? Les unités en question ne sont nécessairement des unité de calcul, l'unité mémoire et l'unité de branchement comptent aussi. La réponse dépend du nombre de voies. Si le ''front-end'' charge et décode un paquet de N instructions, exécuter ces N instructions simultanément demandera là aussi au moins N unités de calcul. Notez le terme : ''au moins'', car c'est là tout l'enjeu de cette section. La réponse dépend aussi des contraintes d’appariement. Pour un CPU superscalaire "parfait", sans contraintes d’appariement, toutes les unités doivent être dupliquées, y compris l'unité mémoire. Un processeur à double émission parfaite devrait dupliquer l'ALU entière, le ''barrel shifter'', le circuit multiplieur, la FPU, l'unité mémoire, et l'unité de branchement, ce qui ferait passer de 6 unités à 12. Le cout en circuit est énorme, même pour de la double émission ! Et pour un CPU à N pipelines, le cout en circuit encore plus prohibitif ! En pratique, les processeurs superscalaires qui font ça sont très rares. La majeure partie des processeurs superscalaires utilisent d'autres techniques : le partitionnement, la duplication partielle des ALUs, la duplication des autres unités. ===Le partitionnement des unités fonctionnelles=== Les CPU superscalaires les plus simples ne dupliquent pas leurs unités de calcul, ils se contentent d'exploiter les unités existantes. Et pour expliquer comment, nous allons partir de l'exemple d'un processeur non-superscalaire contient une ALU entière, une FPU, une unité mémoire et une unité de branchement. Les quatre unités peuvent fonctionner en parallèle, à savoir qu'une unité peut exécuter une instruction à elle toute seule, sans interagir avec les autres. Cela suffit pour créer un processeur quadruple émission, qui peut émettre en même temps : une opération entière, une opération flottante, un branchement, un accès mémoire. Le principe marche aussi sur un processeur avec moins de 4 unités, par exemple sans FPU et/ou sans unité de branchement, ou avec au contraire des unités en plus. L'idée générale est d’émettre une µops séparée dans chaque unité fonctionnelle. Utiliser ainsi les unités existantes, pour rendre un CPU superscalaire, s'appelle le '''partitionnement superscalaire'''. Le terme n'est pas beaucoup utilisé en-dehors de quelques vieux articles académiques. Le partitionnement est fortement dépendant du jeu d'instruction, car il demande de bien séparer instructions entières, flottantes, branchements et instructions mémoire. Et autant c'est bien le cas sur les CPU RISC, autant les CPU CISC ne sont pas dans ce cas. Les CPU CISC ont des instructions ''load-op'', qui effectuent une instruction arithmétique et une µop mémoire. Et ces instructions sont très compliquées à implémenter avec le partitionnement. Et au-delà de ça, les CPU CISC ont beaucoup d'instructions qui sont décodées en plusieurs µops, à exécuter dans un ordre bien précis. On peut pas émettre les µops en question simultanément. Le partitionnement a un cout en circuit très limité, pour un gain en performance lui aussi limité. Pour reprendre le CPU de l'exemple précédent, il est rare que l'on ait à exécuter ces quatre instructions en même temps. En pratique, les combinaisons fréquentes sont les combinaisons calcul + accès mémoire, calcul + branchement, calcul entier + calcul flottant. Utiliser une quadruple émission de ce type n'est donc pas une bonne idée, alors que la double émission parait plus adaptée. Et c'est là que la différence entre ports d'émission et de décodage devient intéressante. L'idée est d'avoir une unité d'émission avec deux ports de décodage, et 4 port d'émission (un par unité). Les contraintes d’appariement varient alors suivant le processeur, mais la règle générale est que si deux micro-opérations tombent dans deux unités différentes, on peut les émettre en même temps. En clair, on peut émettre une opération entière avec une opération flottante, une opération entière avec un branchement, un accès mémoire avec un branchement, etc. Et cela rend le partitionnement bien plus intéressant que prévu. Les tout premiers CPU superscalaires étaient nombreux à utiliser cette technique. Les exemples classiques sont l'HyperSPARC, de l'alpha 21064 et du PowerPC 603. C'étaient des CPU double émission, avec une ALU entière, une unité mémoire et une FPU. Nous parlerons de '''partitionnement INT-FLOAT-MEM'''. Les contraintes d’appariement variaient grandement suivant le processeur. Le PowerPC 603 implémentait une double émission "parfaite". C'est à dire que toutes les combinaisons possibles à deux instructions étaient possibles, que ce soit INT + FLOAT, INT + MEM, FLOAT + MEM. En plus de cette double émission "parfaite", le CPU utilisait la technique dite de ''branch folding'', qui permettait d'exécuter un branchement sans utiliser d'unité de calcul, ce qui fait qu'il était qualifié de ''CPU 2-way + Branch''. A l'opposé, le processeur alpha 21064 avait pas mal de contraintes d’appariement, à savoir que certaines combinaisons de deux instructions étaient interdites, bien que compatibles avec les unités disponibles. Une des raisons est que le banc de registre n'avait pas assez de ports de lectures, ce qui fait que certaines combinaisons avec une écriture mémoire n'étaient pas permises. Les concepteurs avaient volontairement inséré ces contraintes afin de simplifier le design du processeur. Avant de poursuivre, précisons que la FPU regroupe un additionneur flottant et un multiplieur flottant. Et ce fait peut être exploité pour approfondir le partitionnement. La '''double émission FADD/FMUL''' permet d'émettre une addition flottante en même temps qu'une multiplication flottante. Elle a été utilisée sur les processeurs x86, Alpha et SPARC. Les autres circuits de calcul flottant sont souvent regroupés avec l'additionneur. La division est parfois e même port que le multiplieur flottant, parfois sur celui de l'additionneur (quand elle est microcodée et exécutée en enchainant les soustractions). ===La duplication des ALU entières=== Le partitionnement est une technique élégante, simple, facile à implémenter et au cout en circuits modeste. Mais il faut avouer que cela ne donne pas des CPU superscalaires très efficaces. La raison est que le partitionnement permet d'émettre des paires, triplets, quadruplets de micro-opérations qui sont rares en pratique. Les combinaisons les plus fréquentes en pratique sont des paires/triplets d'opérations entières, souvent des paires/triplets d'additions ou d'opérations entières simples. Il est donc primordial d'émettre plusieurs opérations entières simultanément. Le fait d'émettre et d'exécuter plusieurs opérations entières en même temps s'appelle l''''émission entière multiple'''. Pour exécuter la seconde/troisième opération entière, il faut dupliquer les ALU entières (ou réutiliser l'AGU comme ALU entière comme le fait l'Intel i960). Le processeur doit avoir deux ALU entières pour de la double émission entière, trois pour de triple émission entière, etc. De plus, il faut ajouter des ports au banc de registre, pour lire deux fois plus d'opérandes et décrire deux résultats. Et enfin, il faut rajouter un port d'émission pour pouvoir émettre une micro-opération entière en plus. [[File:Processeur non-superscalaire basique 03.png|centre|vignette|upright=2|Processeur non-superscalaire basique]] La tendance est de multiplier les ALU entières, mais pas le circuit multiplieur. Le cout en transistors est alors grandement réduit, au prix de l'apparition de dépendances structurelles. Le CPU peut émettre deux additions en même temps, ou une addition et une multiplication, mais pas deux multiplications. Il en est de même avec le ''barrel shifter'', qui n'est pas dupliqué. Il est donc possible d'émettre un décalage avec une opération de base, mais pas deux décalages en parallèle. Mais ces contraintes d’appariement ne sont pas un problème, car il est rare de devoir émettre deux multiplications/décalages en même temps. La plupart des programmes sont majoritairement remplis d'additions, avec des multiplications assez rares et des décalages qui le sont encore plus. La moyenne est proche d'une multiplication pour 4/5 additions. Vu que des multiplications consécutives sont très rares, dupliquer le multiplieur est presque inutile. Disposer de plusieurs circuits multiplieurs ou de plusieurs ''barrel shifters'' serait donc un cout en circuits qui ne servirait que rarement et n'en vaut pas la chandelle. Il faut noter que sur les CPU 32 bits, les multiplications peuvent être exécutées dans la FPU ou dans un multiplieur séparé, tout dépend de si le CPU exécute les multiplications dans la FPU ou pas. {|class="wikitable" |- ! Voie d'émission n°1 !! Voie d'émission n°2 !! Voie d'émission n°3 |- | ALU entière || ALU entière || FPU |} Reste à connecter les unités de calcul à l'unité d'émission (indirectement, car il y a des étages de pipeline entre les deux). Une implémentation basique utilisee un port d'émission par unité. Prenons l'exemple d'un CPU superscalaire avec 4 ALU entières, une FPU, et une unité mémoire. Vu qu'il y a 6 unités, cela fait donc 6 ports d'émission. L'exemple historique le plus impressionnant est celui du Motorola 88110, un CPU double émission qui possède 10 unités. Il contient deux ALU entières, un circuit multiplieur, un circuit diviseur, une unité de manipulation de bits, une unité mémoire, un additionneur flottant, et deux unités graphiques pour des calculs impliquant des pixels. Le multiplieur est utilisé à la fois pour les multiplications entières, flottantes et le produit de deux pixels. Idem pour le diviseur, qui sert pour les divisions entières et flottantes. Et chaque unité a son propre port d'émission. La conséquence est que les contraintes d'appariement du Motorola 88110 sont simples : tant que deux µops atterrissent dans des unités séparées, la paire est permise. Il est par exemple possible d'émettre simultanément deux opérations entières simples, vu qu'il y a deux ALU. Par contre, impossible d'émettre simultanément deux multiplications, deux divisions, deux opérations flottantes, deux manipulations de bit, deux accès mémoire, etc. De plus, le partage du multiplieur fait qu'on ne peut pas émettre une multiplication entière avec une multiplication flottante. Idem avec les divisions, vu que le diviseur est aussi partagé. Ce sont les seules contraintes. ===Le partage des ports d'émission=== La présence d'un circuit multiplieur et d'un ''barrel shifter'' pose des problèmes pour le partitionnement. Il est possible d'avoir un port d'émission dédié au multiplieur, et un autre au ''barrel shifter'', qui sont séparés de ceux pour l'ALU entière. Il est alors possible d'émettre une opération entière, une multiplication et un décalage, simultanément. Nous parlerons de '''partitionnement entier''' pour désigner cette possibilité. [[File:Partitionnement superscalaire entier.png|centre|vignette|upright=2|Partitionnement superscalaire entier.]] Le partitionnement entier est cependant très rare, car le gain en performance serait trop faible au regard des couts. Quelques processeurs historiques l'utilisaient, mais ce n'est plus le cas de nos jours. La raison est que les multiplications et les décalages sont assez rares dans le code, ce qui fait que le port d'émission associés sont sous-utilisés. Et un port d'émission a un cout en circuit assez important. L'idéal serait de trouver un moyen de réduire le nombre de ports d'émissions, sans réduire le nombre d'unités de calcul. [[File:Emission multiple des opérations entières, implémentation naive.png|vignette|Émission multiple des opérations entières, implémentation naive]] La solution consiste à '''partager les ports d'émission''', à savoir connecter un port d'émission à plusieurs unités de calcul. L'exemple classique est justement lié aux circuits multiplieur et ''barrel shifters''. L'idée est que le multiplieur et le ''barrel shifter'' sont mis sur le même port d'émission qu'une ALU entière. Prenons par exemple le processeur à double émission entière illustré ci-contre. Un port d'émission est relié à une ALU entière seule, et l'autre port d'émission alimente la seconde ALU entière et le multiplieur. La conséquence est que le processeur peut émettre deux opérations entières, dont une multiplication. Si on avait utilisé un port d'émission dédié au multiplieur, il aurait été possible d'émettre deux opérations entières et une multiplication. On perd donc une voie, mais on gagne en flexibilité et on économise des circuits. [[File:Emission multiple des opérations entières, implémentation courante.png|centre|vignette|upright=1.5|Émission multiple des opérations entières, implémentation courante]] En général, le ''barrel shifter'' et le multiplieur sont placés sur deux voies d'émission séparés, ce qui permet d'émettre un décalage et une multiplication en même temps. Les mettre sur le même port ne le permettrait pas. {|class="wikitable" |- ! Voie d'émission n°1 !! Voie d'émission n°2 !! Voie d'émission n°3 |- | * ALU entière * Multiplieur | * ALU entière * Barrel shifter | FPU |- | * Additions, soustractions, opérations bit à bit, etc. * Multiplication | * Additions, soustractions, opérations bit à bit, etc. * Décalages et rotations | Opération flottante |} Mais le partage des ports d'émission peut appliquer à n'importe quelle unité. Prenons l'exemple d'un CPU superscalaire avec 4 ALU entières, une FPU, et une unité mémoire. Vu qu'il y a 6 unités, cela fait donc 6 ports d'émission. Cependant, il est rare qu'un bloc de 6 instructions contienne exactement 4 opérations entières, une opération flottante, et un accès mémoire. Beaucoup de ports d'émission seraient sous-utilisés. La solution est de partager des ports d'émission, pour passer à la quadruple émission. Le CPU peut alors répartir 4 instructions sur les ports d'émission adéquats, à chaque cycle. Le CPU a donc 4 ports de décodage et 6 ports d'émission. Une implémentation classique permet n'importe quelle combinaison de 4 instructions compatible avec les 6 unités. Par exemple, il devient possible d'émettre simultanément 4 additions, la combinaison 2 * INT + FLOAT + MEM, 3 * INT + MEM, et bien d'autres. L'exemple relie une ALU entière, la FPU et l'unité mémoire sur le même port. Le CPU a alors quatre ports d'émission, qui ne sont pas "identiques". Trois d'entre eux sont reliés à une ALU entière chacun, rien d'autre. Le quatrième est lui relié à une ALU entière, la FPU et l'unité mémoire. On économise alors deux ports d'émission, mais cela entraine l'apparition de contraintes d’appariement. Le CPU peut émettre 3 opérations entières + n'importe quelle autre opération. Mais les autres combinaisons sont interdites. Le CPU peut émettre 6 opérations entières simultanément, grâce à 6 ALU entières. Les accès mémoire ou opérations flottantes ne viennent pas en plus, mais en remplacement des opérations entières. Regrouper plusieurs ALU sur un même port d'émission est donc à l'origine de dépendances structurelles, de nouvelles contraintes d’appariement. Dans certaines situations de '''''port contention''''', on a assez d'ALU pour exécuter N micro-opérations, mais la répartition des ALUs sur les ports l'empêche. Impossible d'émettre deux micro-opérations sur deux ALU si elles sont sur le même port d'émission. On peut réduire les cas de ''port contention'' en répartissant correctement les ALU sur les ports d'émission, mais sans jamais les supprimer complétement. Un exemple est celui du PowerPC 604. Il implémentait la quadruple émission, alors qu'il avait 5 unités de calcul. Mais deux unités partageaient un même port d'émission. Dans le détail, il avait deux ALU entières, un circuit multiplieur/diviseur, une FPU, une unité mémoire. La FPU et le multiplieur étaient sur le même port d'émission. En conséquence, le CPU pouvait émettre seulement les combinaisons INT + INT + MEM + (MUL/FLOAT). : Il est intéressant de voir l'exemple d'un processeur superscalaire parfait, sans contraintes d'appariement, à N voies. Pour cela, on part d'un processeur non-superscalaire, avec M unités de calcul. Un tel processeur peut s'implémenter avec N ports d'émission, chacun relié à M unités de calcul possibles, ce qui fait que les M unités de calcul sont dupliquées en N exemplaires. Si on prend par exemple un CPU non-superscalaire avec une ALU, une FPU, une unité mémoire et une unité de branchement, un port d'émission sera relié à une ALU, une FPU, une unité mémoire et une unité de branchement. ===La duplication des autres unités fonctionnelles=== Dupliquer les ALU entière est une première étape, mais il est aussi possible de dupliquer les autres unités fonctionnelles. Par exemple, il est possible de dupliquer l'unité mémoire ou l'unité de branchement. Reste à voir quelles unités dupliquer, et pourquoi. Précisons que dans ce qui va suivre, nous allons parler de la duplication des unités en général, pas seulement des unités mémoire/branchement. Nous allons continuer de parler de la duplication des ALU entières dans ce qui suit, mais de manière plus générale. Pour un processeur à N voies, il est théoriquement possible de dupliquer toutes les unités en N exemplaires. Mais ce n'est jamais fait en pratique, car le cout en circuits serait trop important et le gain en performance trop faible. Mieux vaut dupliquer certaines unités bien précises, quitte à avoir des contraintes d'appariement. Et à ce petit jeu, toutes les duplications ne se valent pas, certaines sont plus rentables en matière de performance, le cout en circuit n'est pas le même selon l'unité, etc. La rentabilité en performance dépend surtout des contraintes d'appariement. Les contraintes d'appariement entrainent une baisse de performance significatives si elles interdisent des paires d'instructions fréquentes. Aussi, les opérations très fréquentes, souvent appairées, gagnent à dupliquer l'unité associée. Et au contraire, les unités rarement utilisées ne doivent pas être dupliquées. Il se trouve que les unités les plus utilisées sont les ALU entières, suivies par les unités mémoire et l'unité de branchement. Le cout en circuit n'est aussi pas le même. Dupliquer les ALU entière est trivial, car elles ont un budget en transistors assez faible. Les circuits multiplieurs, diviseurs, et les ''barrel shifter'' ont eux un cout en transistor plus élevés. Si on devait faire un classement, on aurait : ALU entière < ''barrel shifter'' < multiplieur. Pour les unités mémoire, le cout en transistor est là aussi conséquent. Le résultat final est que dupliquer les ALU entières est privilégié sur le reste, comme l'a vu dans la section précédente. Les unités mémoire arrivent en seconde place, suivie par les multiplieurs/''barrel shifter'' à la troisième place, et les unités de branchement à la quatrième place. Les CPU superscalaires larges ont donc tendance à multiplier les unités mémoire en second. Les multiplieurs et ''barrel shifters'' sont eux aussi dupliqués sur les CPU superscalaires larges, mais cela vient souvent après avoir dupliqué les unités mémoire. La raison est que les accès mémoire sont critiques pour la performance des CPU à exécution dans le désordre, pas les multiplications. Pour les unités de branchement, une subtilité complique le fait d'en installer plusieurs. Que faire quand deux branchements sont pris ? La solution la plus simple est de ne pas se trouver dans cette situation : on n'utilise qu'une seule unité de branchement et on n’exécute qu'un seul branchement par cycle. La majorité des processeurs superscalaires étroits font ainsi. Les processeurs superscalaires larges ont cependant deux unités de branchements, mais ils n'acceptent qu'un seul branchement pris à la fois. Si les deux branchements pris sont exécutés en même temps, le processeur n’exécute que le plus récent, dans l'ordre du programme. ===Les CPU superscalaires mélangent les trois techniques=== Les CPU superscalaires utilisent toutes les techniques à la fois : partitionnement, duplication de l'ALU entière, duplication des autre unités fonctionnelles. Chaque processeur utilise ces trois stratégies à sa sauce, en fonction de son budget en transistor. Les quatre techniques ont cependant un cout en circuit différent, et donnent des gains de performances eux aussi forts différents. * Le '''partitionnement superscalaire''' a un cout en circuit modéré, car on n'a pas besoin de dupliquer d'ALU. * La '''duplication des ALU entières''' est la technique la plus importante : elle a un cout en circuit modéré, et donne des gains en performance impressionnants. * La '''duplication des autres unités''' a un cout en circuit variable, pour des gains en performance eux aussi variables. * Le '''partage des ports d'émission''' réduit le cout en circuit du CPU, pour des pertes de performances mineures si bien fait. L'usage du partitionnement seul, sans duplication, était surtout le fait des processeurs commercialisés entre les années 1989 et 1993. Le partage des ports d'émission a lui aussi mis un peu de temps pour s'imposer. Globalement, les processeurs superscalaires ont évolués progressivement, pour se rapprocher d'une sorte de standard. Mais pour comprendre ce qu'est ce standard, et en quoi les CPU historiques s'en sont progressivement rapprochés, il faut étudier l'histoire des CPU superscalaires. L'histoire des CPU superscalaires a commencé dès 1960, mais est resté un projet de recherche pendant plusieurs décennies. De nombreux designs ont été conçus, mais abandonnés avant la production, comme l'ACS-1 d'IBM. Les années 1980 ont vu l'arrivée de prototypes de processeurs VLIW, mais qui n'étaient pas superscalaires. Par exemple, l'Apollo DN10000 et l'Intel i860 pouvaient émettre deux instructions à la fois, sous conditions, mais demandaient que le compilateur fasse une bonne partie du travail. Les premiers CPU superscalaires commercialisés sont sortis en 1989. Deux CPUs superscalaires sont sortis cette année là : le RS/6000 d'IBM et l'i960CA d'Intel. Puis, la compétition s'est aussi lancée dans la bataille. Les CPU commercialisés avant l'année 1993 étaient à double émission, à l'exception du SuperSPARC qui utilisait la triple émission. Les modèles après 1993 hésitaient entre double et quadruple émission. Dans le tableau ci-dessous, les processeurs quadruple émission sont en bleu, les triple émission sont en rouge, les autres ne sont pas en couleur. {|class="wikitable" |- ! !! IBM !! HP PA-RISC !! SPARC !! Intel!! Alpha !! Autres |- ! 1989 | RS/6000 || || || i960CA || || |- ! 1990 | || || || || || |- ! 1991 | || || || || || Motorola 88110 |- ! 1992 | RSC || PA 7100 || class="f_rouge | SuperSPARC || || 21064 || |- ! 1993 | PPC 601 || || HyperSPARC || Pentium || || |- ! 1994 | class="f_bleu | POWER 2 || PA 7200 || || || class="f_bleu | 21164 || |- ! 1995 | 603/604 || class="f_bleu | PA 8000 || class="f_bleu | UltraSPARC 1 || Microarchitecture P6 (+ concurrence AMD) || || class="f_bleu | MIPS R1000 |- ! 1996 | class="f_bleu | 620 || || || || class="f_bleu | 21264 || |- ! ... || ... || ... || ... || ... || ... || ... |} Les premiers CPU à double émission sont assez représentatifs des CPU à double émission modernes. Il faut dire que se limiter à deux voies ne permet pas de faire beaucoup de choses. Voici le tableau chronologique disant quel CPU utilisait quelle technique. * Les CPU en vert utilisaient la double émission entière-flottante. * Les CPU en rouge utilisaient la double émission entière "pure". * Les CPU en jaune utilisaient un ''partitionnement complet'' entre ALU, FPU et unité mémoire. * Les CPU sans couleur utilisaient un grand nombre d'unités, avec beaucoup de ports d'émission. * Les CPU barrés utilisent la triple ou quadruple émission et seront vu dans la section suivante. {|class="wikitable" |- ! !! IBM !! HP PA-RISC !! SPARC !! Intel!! Alpha !! Autres |- ! 1989 | class="f_vert | RS/6000 || || || class="f_rouge | i960CA || || |- ! 1990 | || || || || || |- ! 1991 | || || || || || Motorola 88110 |- ! 1992 | class="f_vert | RSC || class="f_vert | PA 7100 || <s>SuperSPARC</s> || || class="f_jaune | 21064 || |- ! 1993 | class="f_vert | PPC 601 || || class="f_jaune | HyperSPARC || class="f_rouge | Pentium || || |- ! 1994 | <s>POWER 2</s> || PA 7200 || || || <s>21164</s> || |} C'est en 1994, que la quadruple émission est arrivée et que les CPU ont essayé divers implémentations. Il y avait alors trois stratégies pour la double/triple/quadruple émission, qui partageaient des problèmes similaires. La première implémentation dupliquait les ALUs et les FPUs, ce qui permettait d'émettre deux opérations flottantes avec deux opérations entières. Le POWER 2 dupliquait ses FPUs à la perfection, alors que le 21264 séparait l'additionneur et le multiplieur flottant. Les accès mémoire étaient traités comme des opérations entières et le processeur pouvait souvent en émettre deux à la fois. Mais une telle organisation n'est plus utilisée de nos jours, car, sans instructions flottantes à exécuter, elle est équivalente à de la double émission. La seconde implémentation utilisait deux ALU entières (dont un multiplieur), une unité mémoire et une FPU.Cette organisation est intéressante, mais encore trop stricte pour donner de bons résultats, les quadruplets avec exactement deux opérations entières, un accès mémoire et une opération flottante étant trop rares. L'usage de ces 4 unités en double ou triple émission a été utilisé sur le PA 7200 et le SuperSPARC. Malheureusement, en plus de problèmes au niveau de son cache, le SuperSPARC avait de nombreuses contraintes d’appariement, car le banc de registre n'avait pas assez de ports pour alimenter certaines combinaisons d'instructions. De plus, s'il émettait une multiplication, il ne pouvait pas émettre de seconde µop. Enfin, une troisième implémentation utilisait un grand nombre d'unités fort différentes, bien plus que quatre, avec au minimum deux ALU entières. Prenons l'exemple de l'UltraSPARC, qui avait : deux ALU entières, une unité mémoire, une unité de branchement, un additionneur flottant, un multiplieur flottant, un diviseur flottant (aussi utilisé pour le calcul des racines carrées), et 2 unités graphiques pour les calculs sur des pixels. En tout, cela fait 9 unités différentes, pour de la quadruple émission. Dans le même genre, les processeurs MIPS R8000 et R10000 avaient une implémentation plus moderne, dans le sens où toutes les unités étaient dupliquées. Le CPU avait deux ALU, deux FPU et deux unités mémoire. Il pouvait donc émettre quatre instructions à la fois, tant que le paquet collait avec ces six unités. Moitié opérations entières, moitié opérations mémoire, avec de quoi remplacer les opérations entières par des opérations flottantes. Le PA 8000, lui, dupliquait toutes ses unités en deux exemplaires, ce qui n'est pas l'idéal, aucun processeur moderne ne fait cela aujourd'hui. Le tout est résumé dans le tableau ci-dessous, avec : * en vert les CPU utilisant la première stratégie ; * en rouge les CPU utilisant la seconde stratégie ; * en jaune les CPU utilisant la troisième stratégie. {|class="wikitable" |+ Processeurs quadruple émission historiques |- ! Année !! Processeur !! Unités |- ! 1991 | class="f_jaune | Motorola 88110 || class="f_jaune | 2 ALU + MUL + DIV + BIT + MEM + FADD + 2 unités graphiques |- |- ! rowspan="2" | 1994 | class="f_vert | POWER 2 || class="f_vert | 2 ALU/MEM + 2 FPU (+ 2 Branchements via ''branch folding'') |- | class="f_rouge | 21164 || class="f_rouge | 2 ALU + FPU + MEM |- ! rowspan="3" | 1995 | PA 8000 || 2 ALU + 2 ''barrel shifters'' + 2 MEM + 2 FPU (FMA) + 2 FPU (DIV et SQRT) + 2 SIMD |- | class="f_jaune | UltraSPARC 1 || class="f_jaune | 2 ALU + MEM + BRANCH + FADD + FMULFDIV + 2 unités graphiques |- | MIPS R8000 et R10000 || 2 ALU + 2 FPU + 2 MEM |- ! rowspan="2" | 1996 | class="f_rouge | 620 || class="f_rouge | 2 ALU + FPU + MEM |- | class="f_vert | 21264 || class="f_vert | 2 ALU/MEM + 2 FPU (FADD + FMUL) |} Malheureusement, ces CPUs historiques n'avaient ni assez d'unités de calcul entières, ni d'unités mémoire, pour remplir la quadruple émission. Environ la moitié des instructions d'un programme correspond à des instructions entières. Pour en profiter, un processeur à N voies doit disposer d'au minimum N/2 ALU entières, l'idéal étant d'aller au-delà. Les CPU superscalaires historiques avaient 2 ALU pour de la quadruple émission, ce qui était très limite. En comparaison, les CPU superscalaires modernes mettent le paquet niveau ALU entière. La plupart des CPU x86 modernes descendent rarement sous les 2/3, la plupart remplit presque la totalité de la largeur du CPU. Pour donner un exemple, la microarchitecture Golden Cove d'Intel est un processeur à 6 voies, avec 5 ALU entières. Pour être précis, un programme informatique exécute environ 40% de son code dans des instructions arithmétiques, un autre 40% dans les accès mémoire, et un le reste pour... le reste. La conséquence est que les processeurs modernes dupliquent leurs unités mémoire. Par exemple, la microarchitecture ''Golden Cove'' d'Intel dispose de trois unités mémoire : deux spécialisées dans les écritures, trois dans les lectures. La microarchitecture ''Gracemont'' utilise la pentuple émission et peut émettre 4 µops entières et 4 µops mémoire. Notez que le ratio est équilibré entre opérations entières et mémoire, avec soit égalité, soit un avantage pour les ALU entières. Une autre régularité est que la largeur totale du processeur peut être occupée par des opérations entières/mémoire. Pour le dire autrement, sur un processeur quadruple émission, il pourra émettre un mix de 4 instructions entière/mémoire, avec zéro opération flottante. Idem avec des processeurs triples ou pentuple émission, voire plus. Si le processeur émet une opération flottante, elle remplace une opération entière. Prenons l'exemple des processeurs AMD K6, sortis en 1997, juste avant les AMD Athlon. Ils avaient 10 unités de calcul, 6 ports d'émission, 4 ports de décodage. Voici exactement comment étaient répartie les unités de calcul sur les ports d'émission. C'était des processeurs quadruple émission, ce qui fait que la moitié de la largeur du processeur sert pour des opérations entières, et l'autre moitié pour les accès mémoire. {|class="wikitable" |- ! Port d'émission n°1 !! Port d'émission n°2 !! Port d'émission n°3 !! Port d'émission n°4 !! Port d'émission n°5 !! Port d'émission n°6 ! |- | ALU entière || ALU entière || FPU || Unité de branchement || Unité LOAD || Unité STORE |- | || Multiplieur || || || || Unité de calcul d'adresse (instruction LEA) |- | || ''Barrel shifter'' || || || || Unité pour les instructions PUSH |} Les microarchitectures K7, K8 et K10, utilisées sur les anciens CPU AMD Athlon, sont un exemple moins représentatif. Ces microarchitectures utilisent le partage des ports d'émission au maximum. C'était des microarchitectures à triple émission, avec six ports d’émission. Il y avait trois ports d'émission pour les opérations flottantes, et trois ports d'émission pour les micro-opérations entières/mémoire. Il y avait trois ALU entières et trois unités mémoire, chaque ALU partageait un port d'émission avec une unité mémoire. Elle pouvait donc émettre soit trois opérations entières, soit trois µops mémoire, soit un mix des deux. En plus des mix avec les opérations flottantes et branchements. {|class="wikitable" |- ! Port d'émission n°1 !! Port d'émission n°2 !! Port d'émission n°3 !! Port d'émission n°4 !! Port d'émission n°5 !! Port d'émission n°6 ! |- | ALU entière || ALU entière || ALU entière || FADD || FMUL || FPU (autres opérations) |- | Multiplieur || || || || || |- | LOAD/STORE || LOAD/STORE || LOAD/STORE || || || |} Les CPU superscalaires larges dupliquent les multiplieurs et/ou les ''barrel shifters'', et même les unités de branchement. La raison est que quand la largeur du CPU augmente, le processeur traite des paquets d'instructions de plus en plus gros. Par exemple, pour un CPU à 10 voies, cela veut dire émettre 10 instructions en même temps. Il est alors plus fréquent d'avoir deux multiplications, deux décalages, deux accès mémoire, deux branchements. Par exemple, la moyenne est d'une multiplication tous les 5 instructions. Les CPU superscalaires à 10 voies ont donc tendance à avoir deux multiplications par paquet d'instruction, en moyenne. Avoir deux multiplieurs est alors un gain en performance clair. Par exemple, la microarchitecture ''Golden Cove'' a deux ''barrel shifters''. C'est une architecture à 6 voies. {|class="wikitable" |- ! Port d'émission n°1 !! Port d'émission n°2 !! Port d'émission n°3 !! Port d'émission n°4 !! Port d'émission n°5 !! Port d'émission n°6 !! Port d'émission n°7 !! Port d'émission n°8 !! Port d'émission n°9 !! Port d'émission n°10 |- | ALU entière || ALU entière || ALU entière || ALU entière || ALU entière || LOAD || LOAD || LOAD || STORE || STORE |- | ''Barrel shifter'' || Multiplieur || Multiplieur || ''Barrel shifter'' || || || || || || |- | Branchements || Diviseur || || Branchements || || || || || || |} ==Les µops mémoire sur les processeurs superscalaires== Plus haut, nous avons volontairement passé sous silence les accès mémoire. La raison à cela est que leur support sur les CPU superscalaire peut se faire de plusieurs manières différentes, qu'il vaut mieux expliquer dans une section dédiée. Les différentes implémentations sont décrites dans la liste suivante, nous allons les voir une par une. * Avec la première méthode, les accès mémoire sont exécutés par le pipeline pour les opérations entières. * Avec la seconde méthode, les accès mémoire ont un pipeline dédié ; c'est l'''émission parallèle des accès mémoire''. * Avec la troisième méthode, il est possible d'émettre plusieurs accès mémoire simultanément : c'est l'''émission multiple des accès mémoire''. Voyons ces trois cas dans le détail. Nous verrons notamment les différentes formes d'émission multiple des accès mémoire. ===Les accès mémoire traités dans le pipeline entier=== Dans le cas le plus simple, les accès mémoire sont traités comme des opérations entières un peu spéciales. L'unité mémoire est sur le même port d'émission que l'ALU entière, ce qui fait que les accès mémoire remplacent des opérations entières. Faire ainsi permet d'utiliser une ALU entière pour les calculs d'adresse, pas besoin de rajouter une unité de calcul d'adresse séparée. Typiquement, un processeur de ce type place l'unité mémoire après l'unité de calcul. Les opérations entières n'utilisent que l'ALU entière, mais pas l'unité mémoire. Les accès mémoire exécutent un calcul d'adresse dans l'ALU entière, puis l'adresse calculée est envoyée à l'unité mémoire. Le tout est illustré ci-dessous, dans le cas d'un processeur historique à double émission entière-flottante. [[File:Processeur non-superscalaire basique 01.png|centre|vignette|upright=1.5|Processeur non-superscalaire basique]] Un exemple de CPU double émission de ce type est celui du Power PC 440, un CPU destiné à l'embarqué. Vu qu'il était destiné à l'embarqué, il n'avait pas de FPU, il n'avait pas besoin de manipuler de nombres flottants. Il n'a donc que des ALU entières, un multiplieur et une unité mémoire. Il dispose de deux ports d'émission. Le premier est connecté à une ALU entière et un circuit multiplieur, le second est relié à l'unité mémoire et une seconde ALU entière. Une organisation simple, mais très efficace ! [[File:PowerPC 440.png|centre|vignette|upright=2|Microarchitecture du PowerPC 440.]] L'implémentation économise une unité de calcul d'adresse, mais n'a pas de cout en performance significatif. Les accès mémoire étant très lents, émettre une µop entière en parallèle d'un accès mémoire ne sert pas à grand-chose. On peut émettre cette µop entière un cycle plus tard, elle sera terminée avant même que l'accès mémoire soit terminé, même si l'accès mémoire se fait dans le cache L1 ou L2. Pour comprendre pourquoi, faisons quelques calculs. Un accès mémoire prend deux-trois cycles quand il tombe dans le cache L1. Et on doit attendre qu'il soit terminé pour en émettre un nouveau. Combien d'instructions arithmétiques peut-on lancer pendant ce temps, sur un CPU triple émission ? Au premier cycle, on peut en émettre 2 (l'instruction mémoire prend une place, donc 3-1), puis 3 au second cycle. Ce qui fait 5 instructions arithmétiques au total. Or, il se trouve qu'il y a une instruction d'accès mémoire pour 5 instructions arithmétiques, environ. Par contre, ce compromis change quand on passe à la quadruple émission, voire au-delà. Dans ce cas, il devient intéressant de pouvoir émettre une nouvelle instruction mémoire à chaque cycle. Ce qui nous amène à la section suivante... ===L'émission parallèle des accès mémoire=== A partir de la quadruple émission, la tendance a été de séparer l'unité mémoire des ALU entières. Cela permet d'émettre des µops mémoire en même temps que des µops entières et/ou flottantes., ce qui porte le nom d''''émission parallèle des µops mémoire''', ou encore ''émission mémoire parallèle''. Par exemple, un processeur quadruple émission peut émettre en même temps deux opérations entières, une opération flottante et un accès mémoire. L'accès mémoire vient en plus des opérations entières, il n'est pas possible de remplacer l'accès mémoire par une opération entière. La technique ne permet pas de faire les calculs d'adresse dans l'ALU entière. Il faut donc utiliser une unité de calcul d'adresse séparée. Elle est intégrée dans l'unité mémoire, qui fait l'interface entre le cache et le banc de registres. Le cout en circuits est très faible, car les unités de calcul d'adresse n'utilisent guère plus qu'un additionneur et un circuit de décalage très simple. L'émission mémoire parallèle est plus facile à implémenter sur les CPU RISC, qui ont des instructions mémoire séparées du reste. Les CPU CISC ne sont pas dans ce cas, ce qui fait que séparer l'unité mémoire du pipeline entier est compliqué. Les CPU superscalaires historiques étaient pour moité des CPU RISC et pour moitié des CPU CISC, ce qui fait qu'ils n'utilisaient pas ou peu l'émission mémoire parallèle. Les premiers processeurs superscalaires se contenaient de l'émission parallèle mémoire, ils ne pouvaient émettre qu'un seul accès mémoire à la fois, l'unité mémoire n'était pas dupliquée. Et ce n'est pas tant un problème que ça, du moins sur les processeurs à double ou triple émission. En effet, il est rare que l'unité mémoire doive faire deux accès simultanés. Notons que le processeur peut exécuter une instruction mémoire en parallèle d'un calcul ou d'un branchement, seuls les accès mémoire simultanés sont impossibles. ===L'émission multiple des accès mémoire avec une LSQ=== Les processeurs superscalaires larges travaillent sur des paquets de 8 à 10 instructions, il est plus fréquent d'avoir plusieurs accès mémoire par paquet. Dans ces conditions, il est utile d'avoir une '''émission multiple des accès mémoire''', à savoir d'émettre plusieurs micro-opérations mémoire en même temps. Les processeurs superscalaires modernes sont capables d'émettre plusieurs lectures/écritures simultanément. Par exemple, ils peuvent émettre une lecture en même temps qu'une écriture, ou plusieurs lectures, ou plusieurs écritures. Il faut noter que selon le processeur, il peut y avoir des restrictions quant aux accès mémoire émis en même temps. Par exemple, certains processeurs peuvent émettre une lecture avec une écriture en même temps, mais pas deux lectures ni deux écritures. Ou encore, ils peuvent émettre deux lectures, une lecture et une écriture, mais pas deux écritures en même temps. Dans la majorité des cas, les processeurs ne permettent pas d'émettre deux écritures en même temps, alors qu'ils supportent plusieurs lectures. Il faut dire que les lectures sont plus fréquentes que les écritures. Les processeurs qui autorisent toutes les combinaisons de lecture/écriture possibles, sont rares. L'émission multiple des accès mémoire s'implémente dans l'unité mémoire. Pour rappel, l'unité mémoire s'interpose entre le cache L1 et le reste du pipeline. Dans le cas le plus simple, elle incorpore une ''load/store queue'' et des unités de calcul d'adresse. L'émission multiple des accès mémoire demande de rendre la ''Load-store queue'' multi-ports, afin de gérer plusieurs micro-opérations mémoire simultanées. Il faut aussi dupliquer les unités de calcul d'adresse reliées à la LSQ, chaque port d'émission mémoire devant avoir sa propre unité de calcul d'adresse. : Dans ce qui suit, nous parlerons d'AGU (''Adress Generation Unit'') pour désigner les unités de calcul d'adresse, et de LSQ pour designer la ''Load-store queue''. [[File:Processeur non-superscalaire basique 02.png|vignette|upright=1|Processeur non-superscalaire basique]] Dupliquer les ports de la LSQ et les AGU est nécessaire. Mais qu'en est-il du cache ? La réponse varie suivant le processeur. Il y a deux grandes méthodes pour implémenter l'émission multiple des accès mémoire. La première méthode ajoute des ports de lecture/écriture au cache de données, pour faire des lectures/écritures simultanées. Il faut alors dupliquer des unités de calcul, rajouter les ports sur le cache, mais aussi rajouter des ports sur la ''load/store queue'', pour qu'elle échange des données/commandes mémoire avec le cache. Le cout en circuits est alors conséquent La seconde duplique les unités de calcul, mais pas les ports d'accès au cache. Les accès mémoire sont alors accumulés dans la ''load/store queue'', qui sont ensuite traités un par un par le cache. Un exemple est celui des processeurs AMD de micro-architecture K7 K8, à savoir les anciens AMD Athlon et AMD Athlon 64, qui pouvaient calculer trois adresses simultanément, mais ne pouvaient faire qu'un seul accès au cache par cycle. Le cout en circuit est modéré : les unités de calcul coutent très peu, les ports de la ''load/store queue'' coutent déjà un peu plus. Quelques processeurs utilisaient des solutions intermédiaires. Un processeur utilisant cette technique est, par exemple, capable d'émettre quatre µops mémoire en même temps, mais avec seulement deux ports sur le cache. Un exemple classique étant les Pentium 1 et 2 d'Intel, dont nous parlerons dans le chapitre suivant. ===L'émission séparée des LOAD et STORE=== De rares processeurs ont des ports d'émission qui peuvent faire indifféremment lecture comme écritures. Mais les processeurs modernes préfèrent séparer ports d'émission pour les lectures et les écritures. Un exemple est celui des processeurs Intel de micro-architecture Nehalem, qui avaient un port de lecture et un port d'écriture tous deux reliés à la ''Load-store queue'', chacun avec leur propre unité de calcul d'adresse. Comme autre exemple, les processeurs skylake ont une LSQ avec deux ports de lecture et un port d'écriture, ce qui permet d'émettre deux lectures en même temps qu'une écriture. Dans cette section, nous allons étudier le cas avec des ports séparés pour les lectures et écritures. En général, les processeurs ont un peu plus de ports de lectures que d'écritures. Une raison est qu'un programme a, en moyenne, un tout petit peu plus de lectures que d'écritures. Mais la différence n'est pas énorme, c'est un léger déséquilibre. Une seconde raison est que l'exécution dans le désordre marche bien mieux quand les lectures ont lieu le plus tôt possible. Donc mieux vaut privilégier les lectures à l'émission, pour qu'elles se fassent un peu en avance. En pratique, la différence n'est pas très marquée. Il est courant d'avoir un port de lecture et un d'écriture, les CPU superscalaires très larges ont souvent deux ports de lecture et deux d'écriture. A la rigueur, quelques CPU ont trois ports de lecture et deux d'écriture. Le cas le plus simple est celui où on peut émettre une lecture et une écriture en même temps. Il s'implémente facilement si la LSQ est en réalité une simple ''Store queue'', à savoir qu'elle met en avance les écritures uniquement. Pour rappel, les écritures sont mises en attente dans la ''Store queue'', mais les lectures sont exécutées immédiatement. Une fois l'adresse à lire connue, la lecture consulte la ''Store queue'' pour détecter les dépendances de données, puis s'exécute. La donnée à lire est lue soit depuis la ''Store queue'', soit depuis le cache. [[File:Emission LOAD - Store séparée.png|vignette|upright=1|Émission LOAD - Store séparée]] L'implémentation de l'émission multiple est alors très simple. La ''Store queue'' a déjà deux ports, avec un pour les lectures et un autre pour les écritures. Idem pour le cache, qui a naturellement un port de lecture et un d'écriture. Émettre une lecture et une écriture simultanément ne demande donc pas de modification digne de ce nom. La ''Store queue'' doit juste supporter une lecture et une écriture simultanée. Ajouter l'émission d'une seconde lecture n'est pas compliquée : il faut juste rajouter un second port de lecture sur le cache et la ''Store queue''. Pour rappel, l'unité mémoire est précédée par une structure qui met en attente les micro-opérations mémoire avant leur émission finale. La structure en question varie suivant l'implémentation, mais un cas particulier se marie bien avec l'émission multiple des µops mémoire. Il s'agit du cas avec une file de µops séparée pour les lectures et une autre pour les écritures, appelées respectivement la ''file de µops LOAD'' et la ''file de µops STORE''. Il est alors possible d'émettre une lecture et une écriture lors du même cycle d'horloge. Le port de lecture alimente une unité de calcul d'adresse dédiée, directement reliée au cache. Le port d'écriture du cache alimente une unité de calcul, qui est suivie par une file d'écriture, elle-même reliée au cache. Les processeurs AMD de micro-architecture K6 utilisaient cette technique. [[File:Double émission avec le Load Ordering, Store Ordering.jpg|centre|vignette|upright=2.5|Double émission avec le Load Ordering, Store Ordering]] ==Les CPU superscalaire à exécution dans le désordre== La superscalarité modifie en profondeur le processeur. Et cela dépasse le cadre des unités vues auparavant. Dans cette section, nous allons voir comment les autres étages du pipeline sont modifiées sur un CPU superscalaire. ===L'unité de renommage superscalaire=== Avec la superscalarité, l'unité de renommage de registre doit gérer le cas où des instructions consécutives ont des dépendances de registre. Par exemple, prenons un processeur à double émission, qui renomme deux instructions consécutives. Si elles ont une dépendance de registre, le renommage de la seconde instruction dépend du renommage de la première. La première doit être renommée et le résultat du renommage est utilisé pour renommer la seconde, ce qui empêche d'utiliser deux unités de renommage séparées. Pour cela, elle renomme les registres sans tenir compte des dépendances, pour ensuite corriger le résultat. [[File:Unité de renommage superscalaire.png|centre|vignette|upright=2|Unité de renommage superscalaire.]] Seules les dépendances lecture-après-écriture doivent être détectées, les autres étant supprimées par le renommage de registres. Repérer ce genre de dépendances se fait assez simplement : il suffit de regarder si un registre de destination d'une instruction est un opérande d'une instruction suivante. [[File:Détection des dépendances sur un processeur superscalaire.png|centre|vignette|upright=2|Détection des dépendances sur un processeur superscalaire.]] Ensuite, il faut corriger le résultat du renommage en fonction des dépendances. Si une instruction n'a pas de dépendance avec une autre, on la laisse telle quelle. Dans le cas contraire, un registre opérande sera identique avec le registre de destination d'une instruction précédente. Dans ce cas, le registre opérande n'est pas le bon après renommage : on doit le remplacer par le registre de destination de l'instruction avec laquelle il y a dépendance. Cela se fait simplement en utilisant un multiplexeur dont les entrées sont reliées à l'unité de détection des dépendances. On doit faire ce replacement pour chaque registre opérande. [[File:Correction des dépendances sur un processeur superscalaire.png|centre|vignette|upright=2|Correction des dépendances sur un processeur superscalaire.]] ===Les unités d'exécution dans le désordre=== Réduire le nombre de ports d'émission permet de garder des fenêtres d'instruction et un ROB simples, rapides et économes en énergie. Et mine de rien, c'est là un compromis important : à quoi bon augmenter le nombre de ports d'émission si c'est pour que ce soit compensé par une fréquence plus faible ? Par contre, réduire le nombre de ports augmente la ''port contention'', surtout si on a mal réparti les ALU. Il faut donc trouver un compromis entre réduire le nombre de ports d'émission d'un côté, réduire la ''port contention'' de l'autre. Le ROB doit aussi être modifié pour émettre et terminer plusieurs instructions en même temps, en ajoutant des ports de lecture/écriture. Pour un CPU superscalaire peut émettre 8 µops à la fois, on se dit que le ROB peut retirer 8 µops par cycle maximum. Et c'est très souvent le cas en pratique. Cependant, certains processeurs ont un ROB qui peut retirer le double ! Un exemple est celui des CPU de micro-architecture Skymont, qui peuvent émettre 8 µops par cycle, mais en retirer 16. Quelques CPU de marque AMD étaient aussi dans ce cas. L'avantage est que cela permet de réduire la taille du ROB, du banc de registre et de quelques autres structures matérielles liées à l'exécution dans le désordre. Le gain sur-compense le cout en transistor nécessaire pour retirer 16 µops par cycle. Un détail est que le nombre de ports de lecture n'est pas le même selon que le processeur utilisé des stations de réservation ou une fenêtre d’instruction. Rappelons la terminologie utilisée dans ce cours. Les fenêtres d'instruction se contentent de mémoriser la micro-opération à émettre et quelques bits pour la disponibilité des opérandes. Par contre, les stations de réservations mémorisent aussi les opérandes des instructions. Et cette différence a une influence le nombre de ports de lecture du banc de registre. Supposons que toutes les instructions sont dyadiques, à deux opérandes. Comptons le nombre de ports nécessaires pour alimenter les unités de calcul, sans utiliser d'arbitre de banc de registre. Avec une fenêtre d'instruction, il faut le double du nombre de ports de ''dispatch''. Mais avec des stations de réservation, il faut le double des ports de ''schedule''. La raison est que les registres sont lus après émission avec une fenêtre d'instruction, avant avec des stations de réservation (sous-entendu, après le décodage). or, comme dit plus haut, les processeurs superscalaires larges ont souvent plus de ports de ''schedule'' que de ''dispatch''. ==Les optimisations des processeurs superscalaires larges== Les processeurs superscalaires larges ont des optimisations spécifiques, qui n'ont pas de sens sur les processeurs superscalaires étroits. Dans les grandes lignes, elles se classent en deux types. Les premières permettent de charger/décoder plus de 5-10 instructions simultanément. Le problème est alors que les branchements viennent mettre leur grain de sel et qu'il faut en tenir compte. Les secondes corrigent des problèmes qui apparaissent quand un CPU intègre plus de 5 à 10 unités de calcul. ===L'étape de chargement d'un CPU superscalaire large=== Charger plusieurs instructions pose de nombreux problèmes sur les processeurs superscalaires larges, qui émettent de 5 à 10 instructions simultanées. Pour rappel, la stratégie basique pour charger N instructions est de charger un bloc de N instructions consécutives. Les instructions après un branchement ne sont pas décodées ni exécutées, ce qui entraine une petite baisse de performance. Pour un N petit, la perte de performance est mineure. Mais pour un N large, elle devient suffisamment importante pour que la conception du CPU doit alors être repensée de fond en comble. Les analyses statistiques disent qu'il y a environ un branchement tous les 4 à 6 instructions. Un CPU a donc tendance à décoder des paquets de 3 à 5 instructions consécutives, avant qu'un branchement casse la dynamique. Or, cela pose des problèmes pour utiliser plusieurs décodeurs. L'implémentation basique, qui charge N instructions consécutives pour alimenter N décodeurs, fonctionne donc jusqu'à 4/6 décodeurs. Au-delà de 5 instructions, un CPU superscalaire doit être adapté pour gérer les branchements dans les paquets d'instruction qu'il émet. Le nombre de 5 instructions ne sort donc pas de nulle part. La première solution utilise un cache de µops, avec un ''front-end'' capable de charger/décoder 4/5 instructions à la fois, pas plus. L'idée est que la majorité des instructions proviennent du cache de µops (plus de 80%), ce qui fait qu'on peut émettre environ 10 instructions à partir du cache de µops, pas besoin d'avoir beaucoup de décodeurs. Les performances sont cependant dégradées lors d'un défaut de cache de µops. Les instructions doivent alors être chargées depuis le cache d'instruction, par paquets de 4-5, puis sont décodées par 4-5 décodeurs, ce qui ne permet pas d’alimenter les unités de calcul à plein régime. La solution précédente joue sur la distinction entre pots de décodage et d'émission. Elle permet d'émettre un grand nombre de µops sans pour autant charger un grand nombre d'instructions en même temps. En quelque sorte, ce n'est qu'une méthode de contournement, le CPU superscalaire reste de type étroit. Mais il est aussi possible d'utiliser un '''''front-end large''''', capable de charger une bonne dizaine d'instructions en même temps. La seule difficulté est de tenir compte de la présence de branchements. Pour rappel, avec un ''front-end'' étroit, les instructions situées après le premier branchement pris sont annulées, elles ne sont pas décodées ni exécutées. [[File:Fetch sur un processeur superscalaire avec prediction de branchements.png|centre|vignette|upright=2|Fetch sur un processeur superscalaire avec prédiction de branchements.]] Un ''front-end'' large charge les instructions de destination du branchement et les placent à sa suite. Ils chargent deux blocs à la fois et les fusionnent en un seul qui ne contient que les instructions présumées utiles. [[File:Cache d'instructions autoaligné.png|centre|vignette|upright=2|Cache d'instructions auto-aligné.]] Mais cela demande de charger deux blocs de mémoire en une fois, ce qui demande un cache d'instruction multiports. Il faut aussi ajouter un circuit pour assembler plusieurs morceaux de blocs en un seul : le fusionneur (''merger''). En pratique, cette solution demande d'ajouter un second port de lecture au cache, pour un cout en hardware important, avec un impact négatif sur le temps d'accès au cache. Et il faut rajouter le fusionneur, qui prend un étage de pipeline à lui tout seul. Et encore, cette solution ne gère pas le cas où un bloc contient plusieurs branchements ! [[File:Implémentation d'un cache d'instructions autoaligné.png|centre|vignette|upright=2|Implémentation d'un cache d'instructions auto-aligné.]] Les exemples précédents partent du principe qu'il n'y a qu'un seul branchement dans un bloc. Mais quand les blocs deviennent assez longs, il est fréquent d'avoir plusieurs branchements dans un bloc. Et il est même possible d'avoir plusieurs branchements pris. Dans un bloc de 8-10 instructions, ce n'est pas rare, et les CPU superscalaires à 8-10 voies sont la règle dans les CPU des années 2020. Pour gérer ce cas, il faut ajouter des ports supplémentaires au cache d'instruction et adapter le fusionneur, pour qu'il puisse fusionner trois ou quatre blocs. De plus, il faut utiliser une unité de prédiction de branchement capable de ''prédire plusieurs branchements par cycle''. ===Le décodage superscalaire large=== Le décodage des instructions n'est pas censé dépendre de l'implémentation du chargement. En théorie, une fois plusieurs blocs d'instruction fusionnés, on peut décoder les instructions en parallèle. Cependant, certains processeurs effectuent le décodage en tenant compte de la manière dont les branchements découpent les blocs. Les processeurs Atom d'Intel utilisent une solution de ce type, qui couple prédiction de branchement et décodage parallèle. Ils chargent/décodent des paquets de 3 instructions consécutives, chaque paquet étant séparé du précédent par un branchement. Pour cela, en plus d'avoir un fusionneur adapté, ils regroupent les décodeurs en ''clusters'', chacun regroupant 3 décodeurs, et pouvant décoder trois instructions consécutives. Les micro-architectures Tremont et Crestmont avaient deux ''clusters'' (6 décodeurs au total), la micro-architecture Skymont en intègre trois (9 décodeurs au total). Avec deux ''clusters'', le fonctionnement est le suivant. Si le code est composé d'une suite d'instruction sans branchements, seul le premier ''cluster'' est utilisé, limitant le décodage à trois instructions par cycle. Par contre, si l'une des trois instructions décodée est un branchement, alors le second ''cluster'' décode les trois instructions situées après ce branchement, à partir de l'adresse de destination. Avec un troisième ''cluster'', c'est la même chose, sauf qu'il faut qu'il y ait un branchement dans les trois instructions décodées par le second ''cluster''. Mais comment faire pour savoir quels décodeurs activés, quels ''clusters'' sont actifs ? C'est la prédiction de branchement qui commande. Avec deux ''clusters'', elle détermine si un branchement est présent dans les trois instructions à charger, et quelle est son adresse de destination. Sans branchement, elle prévient les décodeurs et les trois instructions chargées sont décodées dans un ''cluster'' libre. Si elle prédit un branchement, elle charge les instructions adéquates, et active les deux ''clusters''. Avec trois branchements, c'est le même mécanisme, sauf que l'unité de prédiction de branchement doit être capable de prédire deux branchements par cycle. Il faut noter que les décodeurs d'un ''cluster'' sont des décodeurs simples, sans microcode. Sur les processeurs Tremont et Crestmont, le microcode était unique et était partagé entre tous les ''clusters''. Un seul ''cluster'' pouvait décoder une instruction complexe avec le microcode, l'autre devait se limiter à des instructions simples uniquement. Depuis Skymont, le microcode est partiellement dupliqué, avec un microcode par ''cluster''. Le cout en hardware peut paraitre prohibitif, pour un gain en performance faible. Sauf que c'est oublier que ce sont des processeurs Atom, sur lesquels beaucoup d'instructions sont microcodées. Les processeurs Atom ont un budget en transistor limité, ce qui fait qu'il est préférable pour eux d'implémenter des instructions en microcode que d'utiliser des unités de calcul dédiées ou des circuits de décodage complexes. Pour limiter la casse, le microcode n'est que partiellement dupliqué. Seul le microcode pour les instructions fréquente est dupliqué. Il y a un microcode secondaire pour certaines instructions, qui est lui unique et partagé par tous les ''clusters''. ===Le contournement sur les processeurs superscalaires larges=== Pour rappel, la technique du contournement (''register bypass'') permet au résultat d'une instruction d'être immédiatement utilisable en sortie de l'ALU, avant même d'être enregistré dans les registres. Implémenter la technique du contournement demande d'utiliser des multiplexeurs pour relier la sortie de l'unité de calcul sur son entrée si besoin. Il faut aussi des comparateurs pour détecter des dépendances de données. [[File:Pipeline Bypass.png|centre|vignette|upright=1|Pipeline Bypass]] La sortie de chaque ALU doit être reliée aux entrées de toutes les autres, avec les comparateurs qui vont avec. Sur les processeurs ayant plusieurs d'unités de calculs, cela demande beaucoup d’interconnexions et de comparateurs. Pour N unités de calcul, cela demande 2 * N² interconnexions, implémentées avec 2N multiplexeurs de N entrées chacun. Si c'est faisable pour 2 ou 3 ALUs, la solution est impraticable au-delà. Non seulement ce serait un enfer à câbler, mais les fils seraient tellement longs que la fréquence du processeur en prendrait un coup. Notez que cela vaut pour les processeurs superscalaires, mais aussi pour tout processeur avec beaucoup d'unités de calcul. En théorie, un CPU à exécution dans le désordre, non-superscalaire, mais avec beaucoup d'unités de calcul, fait face au même problème. En pratique, avoir une dizaine d'ALU implique processeur superscalaire à exécution dans le désordre. D'où le fait qu'on parle du problème maintenant. La seule solution praticable est de ne pas relier toutes les unités de calcul ensemble. À la place, on préfère regrouper les unités de calcul dans différents '''agglomérats''' ('''cluster'''). Le contournement est alors possible entre les unités d'un même agglomérat, mais pas entre agglomérats différents. Évidemment, certaines possibilités de contournement sont perdues, mais c'est compensé par un gain en fréquence et un cout en circuit/fils réduit. C'est un bon compromis, ce qui explique que presque tous les processeurs modernes l'utilisent. Les rares exceptions sont les processeurs POWER 4 et POWER 5, qui ont préféré se passer de contournement pour garder un processeur très simple et une fréquence élevée. ===Le banc de registre des processeurs superscalaires larges=== Pour émettre deux opérations entières simultanément, il faut lire deux fois plus d'opérandes. Et il faut aussi écrire deux résultats dans les registres, en même temps. Pour cela, on double les ports du banc de registre. En général, il faut au maximum 2 ports de lecture et un port d'écriture par unité de calcul. Le problème, c'est que plus un banc de registres a de ports, plus il utilise de circuits, est compliqué à concevoir, consomme de courant et chauffe. Avec plusieurs dizaines d'unités de calcul différentes, ajouter autant de ports impacterait la fréquence du banc de registres et réduirait les performances, voire est totalement impraticable à cause des contraintes thermiques. Les processeurs superscalaires larges doivent donc trouver des solutions. La première solution utilise un banc de registre unique, mais n'utilise pas autant de ports que le pire des cas le demanderait. Le processeur détecte quand il n'y a pas assez de ports pour servir toutes les micro-opérations. Quand ça arrive, l'unité d'émission émet assez de micro-opérations pour exploiter les ports disponibles, mais met en attente les micro-opérations en trop, le temps que les ports se libèrent. Pour cela, le banc de registre est géré par un circuit d'arbitrage spécialisé, intégré à l'unité d'émission, l’'''arbitre du banc de registres''' (''register file arbiter''). Une autre solution est de dupliquer le banc de registres en plusieurs exemplaires qui contiennent exactement les mêmes données, mais avec moins de ports de lecture/écriture. Un exemple est celui des processeurs POWER, Alpha 21264 et Alpha 21464. Sur ces processeurs, le banc de registre est dupliqué en plusieurs exemplaires, qui contiennent exactement les mêmes données. Les écritures dans les registres sont envoyées à tous les bancs de registres, afin de garantir que leur contenu est identique. Chaque exemplaire a exactement deux ports de lecture. La conception du processeur est simplifiée, que ce soit au niveau du câblage, que de la conception des bancs de registres. Une dernière solution découpe le banc de registres en plusieurs bancs de registres plus petits, avec un réseau d'interconnexions pour échanger des données entre bancs de registres. Dans la plupart des cas, cette séparation est invisible du point de vue de l'assembleur et du langage machine. Le processeur se charge de transférer les données entre bancs de registres suivant les besoins. Sur d'autres processeurs, les transferts de données se font via une instruction spéciale, souvent appelée ''COPY''. [[File:Banc de registres distribué.png|centre|vignette|upright=2|Banc de registres distribué.]] Sur de certains processeurs, un branchement est exécuté par une unité de calcul spécialisée. Or les registres à lire pour déterminer l'adresse de destination du branchement ne sont pas forcément dans le même agglomérat que cette unité de calcul. Pour éviter cela, certains processeurs disposent d'une unité de calcul des branchements dans chaque agglomérat. Dans les cas où plusieurs unités veulent modifier le ''program counter'' en même temps, un système de contrôle général décide quelle unité a la priorité sur les autres. Mais d'autres processeurs fonctionnent autrement : seul un agglomérat possède une unité de branchement, qui peut recevoir des résultats de tests de toutes les autres unités de calcul, quel que soit l’agglomérat. ==La macro-fusion : une optimisation du décodage parallèle== Les processeurs superscalaires supportent la technique dite de '''macro-fusion''', qui permet de fusionner deux-trois instructions consécutives en une seule micro-opération. Par exemple, il est possible fusionner une instruction de test et une instruction de saut en une seule micro-opération de branchement. Il s'agit là de l'utilisation la plus importante de la macro-fusion sur les processeurs x86 modernes. La macro-fusion est utilisée pour fusionner un test et un saut en un branchement unique. L'optimisation est présente sur la plupart des CPU superscalaires modernes. Les CPU x86 l'implémentent, mais aussi quelques CPU ARM et les processeurs Apple de série M (M1 à M5, et potentiellement plus quand ils seront sortis). La fusion des instructions se fait lors du décodage des instructions, grâce à la coopération des décodeurs. Elle marche systématiquement si l'instruction de test et le saut sont consécutives, qu'elles forment bien une paire d'instruction. Par contre, insérer une instruction entre les deux empêche la macro-fusion de faire son travail, sauf si l'instruction insérée est un NOP. Les processeurs ARM64 avaient une forme spécifique de macro-fusion, utilisée pour un cas très précis : charger une constante immédiate de 32 ou 64 bits, dans un registre 64 bits. Les CPU ARM 64 bits utilisent des instructions codées sur 32 bits, qui ne peuvent encoder que des constantes immédiates de 16 bits. Pour charger une constante de 32/64 bits, il faut s'y prendre en plusieurs fois. Une première instruction MOVZ charge les 16 premiers bits, puis 2 à 4 instructions MOVK remplissent le reste. MOVZ met à zéro les 48 bits de poids fort, MOVK permet de sélectionner le doublet à modifier dans le registre (on modifie 16 bits, dont on choisit la place, le reste du registre est laissé tel quel). ARM permet de fusionner cette suite d'instructions en une seule µop MOV avec une constante immédiate. On parle alors de '''''MOVZ + MOVK fusion'''''. Une dernière utilisation, [https://www2.eecs.berkeley.edu/Pubs/TechRpts/2016/EECS-2016-130.pdf proposées pour le jeu d'instruction RISC-V], fusionnait un calcul d'adresse et une instruction LOAD/STORE en une seule instruction d'accès mémoire. Elle a été envisagée à une époque où le jeu d'instruction RISC-V ne gérait pas les modes d'adressage base + indice. L'idée était d'avoir une unité mémoire qui gérait le mode d'adressage base + indice et de fusionner des instructions pour l'exploiter. Mais de nos jours, le jeu d'instruction RISC-V intègre le mode d'adressage base + indice, ce qui fait que la macro-fusion n'a aucun intérêt pour ce cas précis. La macro-fusion a de nombreuses limitations en pratique. En théorie, on pourrait l'utiliser au-delà des deux situations mentionnées plus haut. Par exemple, pour fusionner une addition et une multiplication en une seule instruction MAD. Mais l'intérêt est assez faible. Notons que la macro-fusion ne permet que de fusionner des paires d'instructions. Aller au-delà demanderait un cout en circuit trop important. Déjà que tester deux paires utilise pas mal de comparateurs... ==Le ''stack engine'' : une optimisation de la pile d'appel== Les processeurs modernes intègrent une optimisation liée au pointeur de pile. Pour rappel, sur les architectures modernes, le pointeur de pile est un registre utilisé pour gérer la pile d'appel, précisément pour savoir où se trouve le sommet de la pile. Il est manipulé par les instructions CALL, RET, PUSH et POP, mais aussi par les instructions d'addition/soustraction pour gérer des cadres de pile de taille variable. De plus, il peut servir d'opérande pour des instructions LOAD/STORE, afin de lire/écrire des variables locales, les arguments d'une fonction, et autres. C'est donc un registre général adressable, intégré au banc de registre, altéré par l'unité de calcul entière. L'incrémentation/décrémentation du pointeur de pile passe donc par l'unité de calcul, lors des instructions CALL, RET, PUSH et POP. Mais, l'optimisation que nous allons voir permet d'incrémenter/décrémenter le pointeur de pile sans passer par l'ALU, ou presque. L'idée est de s'inspirer des architectures avec une pile d'adresse de retour, qui intègrent le pointeur de pile dans le séquenceur et l'incrémentent avec un incrémenteur dédié. ===Le ''stack engine''=== L'optimisation que nous allons voir utilise un '''''stack engine''''' intégré à l'unité de contrôle, au séquenceur. Le processeur contient toujours un pointeur de pile dans le banc de registre, cela ne change pas. Par contre, il n'est pas incrémenté/décrémenté à chaque instruction CALL, RET, PUSH, POP. Un compteur intégré au séquenceur est incrémenté à la place, nous l’appellerons le '''compteur delta'''. La vraie valeur du pointeur de pile s'obtient en additionnant le compteur delta avec le registre dans le banc de registre. Précisons que si le compteur delta vaut zéro, la vraie valeur est dans le banc de registre et peut s'utiliser telle quelle. Lorsqu'une instruction ADD/SUB/LOAD/STORE utilise le pointeur de pile comme opérande, elle a besoin de la vraie valeur. Si elle n'est pas dans le banc de registre, le séquenceur déclenche l'addition compteur-registre pour calculer la vraie valeur. Finalement, le banc de registre contient alors la bonne valeur et l'instruction peut s'exécuter sans encombre. L'idée est que le pointeur de pile est généralement altéré par une série d'instruction PUSH/POP consécutives, puis des instructions LOAD/STORE/ADD/SUB utilisent le pointeur de pile final comme opérande. En clair, une bonne partie des incrémentations/décrémentation est accumulée dans le compteur delta, puis la vraie valeur est calculée une fois pour toutes et est utilisée comme opérande. On accumule un delta dans le compteur delta, et ce compteur delta est additionné quand nécessaire. Précisons que le compteur delta est placé juste après le décodeur d'instruction, avant même le cache de micro-opération, l'unité de renommage et l'unité d'émission. Ainsi, les incrémentations/décrémentations du pointeur de pile disparaissent dès l'unité de décodage. Elles ne prennent pas de place dans le cache de micro-opération, ni dans la fenêtre d'instruction, ni dans la suite du pipeline. De nombreuses ressources sont économisées dans le ''front-end''. Mais utiliser un ''stack engine'' a aussi de nombreux avantages au niveau du chemin de données/''back-end''. Déjà, sur les processeurs à exécution dans le désordre, cela libère une unité de calcul qui peut être utilisée pour faire d'autres calculs. Ensuite, le compteur delta mémorise un delta assez court, de 8 bits sur le processeur Pentium M, un peu plus pour les suivants. L'incrémentation se fait donc via un incrémenteur 8 bits, pas une grosse ALU 32/64 bits. Il y a un gain en termes de consommation d'énergie, un incrémenteur 8 bits étant moins gourmand qu'une grosse ALU 32/64 bits. ===Les points de synchronisation du delta=== La technique ne fonctionne que si la vraie valeur du pointeur de pile est calculée au bon moment, avant chaque utilisation pertinente. Il y a donc des '''points de synchronisation''' qui forcent le calcul de la vraie valeur. Plus haut, nous avions dit que c'était à chaque fois qu'une instruction adresse le pointeur de pile explicitement, qui l'utilise comme opérande. Les instructions CALL, RET, PUSH et POP ne sont pas concernées par elles utilisent le pointeur de pile de manière implicite et ne font que l'incrémenter/décrémenter. Mais dans les faits, c'est plus compliqué. D'autres situations peuvent forcer une synchronisation, notamment un débordement du compteur delta. Le compteur delta est généralement un compteur de 8 bits, ce qui fait qu'il peut déborder. En cas de débordement du compteur, le séquenceur déclenche le calcul de la vraie valeur, puis réinitialise le compteur delta. La vraie valeur est donc calculée en avance dans ce cas précis. Précisons qu'un compteur delta de 8 bits permet de gérer environ 30 instructions PUSH/POP consécutives, ce qui rend les débordements de compteur delta assez peu fréquent. À noter que si le compteur delta vaut zéro, il n'y a pas besoin de calculer la vraie valeur, le séquenceur prend cette situation en compte. Un autre point de synchronisation est celui des interruptions et exceptions matérielles. Il faut que le compteur delta soit sauvegardé lors d'une interruption et restauré quand elle se termine. Idem lors d'une commutation de contexte, quand on passe d'un programme à un autre. Pour cela, le processeur peut déclencher le calcul de la vraie valeur lors d'une interruption, avant de sauvegarder les registres. Pour cela, le processeur intègre un mécanisme de sauvegarde automatique, qui mémorise la valeur de ce compteur dans le tampon de réordonnancement, pour forcer le calcul de la vraie valeur en cas de problème. La technique du ''stack engine'' est apparue sur les processeurs Pentium M d'Intel et les processeurs K10 d'AMD, et a été conservée sur tous les modèles ultérieurs. L'implémentation est cependant différente selon les processeurs, bien qu'on n'en connaisse pas les détails et que l'on doive se contenter des résultats de micro-benchmarks et des détails fournit par Intel et AMD. Selon certaines sources, dont les manuels d'optimisation d'Agner Fog, les processeurs AMD auraient moins de points de synchronisation que les processeurs Intel. De plus, leur ''stack engine'' serait placé plus loin que prévu dans le pipeline, après la file de micro-opération. ==La micro-fusion et la délamination== La '''micro-fusion''' est une optimisation qui retarde le décodage réel de certaines instructions assez loin dans le pipeline. Elle concerne surtout les instructions ''load-op'', mais pas que. D'autres instructions mémoire peuvent subir cette optimisation. L'essentiel est que, sans micro-fusion, ces instructions sont décodées en plusieurs micro-opérations. La micro-fusion retarde le décodage final de ces instructions, qui se fait assez tard dans le pipeline. Pour comprendre l'intérêt, nous allons devoir faire quelques explications. L'optimisation en question fait le décodage en deux temps. Prenons l'exemple d'une opération ''load-op'', qui est censée être décodée en deux micro-opérations : une lecture, puis une opération. Le décodage produit une '''macro-opération''' (terme d'Intel), qui est propagée dans le pipeline, pour être scindée en deux micro-opérations un peu avant les ALU et l'unité mémoire. La macro-opération est techniquement une micro-opération comme une autre, sauf que ce n'est pas la micro-opération finale. L'unité d'émission découpe cette macro-opération en deux micro-opérations, qui sont émises l'une après l'autre. Pour résumer, les instructions ''load-op'' ne sont complétement découpées en micro-opération lors de l'émission, pas lors du décodage. L'optimisation impacte ce qui se situe entre le décodeur d'instruction et l'unité d'émission : l'unité de renommage, la file de micro-opérations, le ''Loop Stream Detector'' et/ou le cache de micro-opération. L'intérêt de l'optimisation est que l'on fusionne deux micro-opérations en une seule macro-opération. Le cache de micro-opération et la file de micro-opération peuvent mémoriser plus de micro-opérations, vu que certaines sont fusionnées. Quant à l'unité de renommage, elle renomme une seule macro-opération au lieu de renommer deux micro-opérations, ce qui augmente le débit du renommage de registre. Par contre, cela complique un peu le travail de l'unité d'émission, qui doit découper les instructions ''load-op'' en deux et émettre les micro-opérations l'une après l'autre. Il faut noter qu'il n'y a pas de fusion proprement dite. Le décodeur ne décode pas deux micro-opérations séparées, avant de les fusionner. En réalité, il génère directement une macro-opération, qui correspond à deux micro-opérations, que ce soit plus tard dans le pipeline ou sur une autre architecture. D'ailleurs, la micro-fusion a lieu à l'intérieur d'une instruction, qui est censée être traduite en plusieurs micro-opérations. Et encore : pas toutes. La conséquence est que la technique n'a de sens que sur les processeurs CISC, avec des instructions assez complexes pour être décodées en plusieurs micro-opérations. En pratique, seuls les CPU x86 sont concernés. ===Les domaines de fusion=== La micro-fusion n'est pas spécifique aux processeurs superscalaires. En théorie, on peut l'implémenter sur un processeur non-superscalaire à émission dans l'ordre. Cependant, je ne connais aucun processeur à émission dans l'ordre qui implémente la micro-fusion, même si son implémentation est en théorie possible sur de tels processeurs. La micro-fusion est implémentée différemment selon que l'émission se fasse dans l'ordre, dans le désordre, avec une fenêtre d'instruction, avec des stations de réservation, etc. J'ai dit plus haut que les micro-opérations sont générées lors de l'émission, mais le moment exact varie suivant le processeur. Pour simplifier le tout, nous parlerons de '''scission''' quand le processeur traduit une macro-opération en deux/trois micro-opérations. Le moment où a lieu la scission est théoriquement dans l'unité d'émission, mais celle-ci prend plusieurs formes selon que le processeur utilise l'émission dans l'ordre, dans le désordre, avec une fenêtre d'instruction, avec des stations de réservation, etc. Les processeurs à exécution dans l'ordre sont le cas le plus simple. L'unité d'émission est alors en charge du découpage des macro-opérations en micro-opérations. La file de micro-opération ou le cache de micro-opérations mémorisent des macro-opérations. À l'opposé, le tampon de ré-ordonnancement mémorise lui des micro-opérations. Le reste du processeur n'est pas affecté. Sur les processeurs à exécution dans le désordre, les différences sont nombreuses. Le tampon de ré-ordonnancement peut mémoriser soit les micro-opérations finales, soit des macro-opérations. Il en est de même avec la fenêtre d'instruction et/ou les stations de réservation, qu'elles soient centralisées ou décentralisées. Tout dépend de comment se fait l'implémentation. Une implémentation simple effectue la scission des macro-opérations dans l'unité d'émission, comme sur les processeurs à exécution dans l'ordre. Sur de nombreux processeurs, l'unité d'émission et de renommage sont simplement fusionnées, ce qui fait que la scission a lieu juste après le renommage de registres. En conséquence, le tampon de re-ordonnancement et les fenêtres d'instruction ne voient que des micro-opérations. Les macro-opérations disparaissent juste avant. Cette implémentation est préférée avec une fenêtre d'instruction centralisée, car on voit mal comment scinder une macro-opération juste avant l'ALU. Une autre implémentation fait la scission en sortie de la fenêtre d'instruction ou des stations de réservation, qu'elles soient centralisées ou décentralisées. Dans ce cas, le tampon de re-ordonnancement fait de même. L'avantage est que le tampon de ré-ordonnancement stocke des macro-opérations, ce qui augmente sa taille effective. Par exemple, il utilise une seule entrée pour une instruction ''load-op'', au lieu de deux sans micro-fusion de ce type. ===La délamination=== La scission peut parfois avoir lieu plus tôt, dans des circonstances très précises. Concrètement, cela ne concerne que quelques micro-architectures d'Intel, pour des instructions très précises. La raison est les limitations du format des micro-opérations, pas une histoire d'optimisation. Le processeur aimerait pouvoir faire la micro-fusion, mais ne peut pas à cause de ces limitations. Une telle situation cause alors une '''délamination''' : les micro-opérations sont scindées précocement. Par exemple, sur la micro-architecture SandyBridge d'Intel, les opérations ''load-op'' sont fusionnées, sauf si elles utilisent l'adressage indicé. Les micro-opérations de ce processeur ne peuvent avoir que deux opérandes. Trois opérandes est de trop, et les opérations ''load-op'' en adressage indicé dépassent cette limite. Dans ce cas, les micro-opérations sont fusionnées en sortie du décodeur, mais sont scindés avant d’entrer dans la file de micro-opération. L'avantage n'est pas évident, vu que l'unité de renommage suit de près le décodeur d'instruction. Il est que le cache de micro-opérations mémorise des macro-opérations, pas deux micro-opérations. ===La micro-fusion des processeurs Intel et AMD=== La micro-fusion est apparue sur le Pentium M d'Intel, et a été conservée telle quelle sur les processeurs Intel suivants. Les processeurs AMD supportent la micro-fusion depuis au moins la micro-architecture AMD K8. Mieux que ça, la délamination n'était pas utilisée car les micro-opérations acceptaient plus de 2 opérandes. Que ce soit chez AMD ou Intel, elle concerne deux types d'instructions : les écritures mémoire et les instructions ''load-op''. Sur le Pentium M, il y avait quelques limitations, l'optimisation se limitant par exemple aux registres entiers et flottants, mais pas aux registres MMX, mais pas les registres SSE (nous verrons ces registres dans quelques chapitres). Mais les processeurs modernes n'ont plus cette limitation. La micro-fusion des CPU Intel concerne deux types d'instructions : les écritures mémoire et les instructions ''load-op''. Pour les écritures, la situation est plus complexe que pour les instructions ''load-op''. Sur les processeurs Intel précédents, les écritures étaient réalisées dans deux unités séparées : le calcul d'adresse dans une unité de calcul, l'écriture proprement dite dans l'unité mémoire. Elles prenaient donc deux micro-opérations. Le processeur a changé ses unités mémoire, ce qui fait qu'elle pouvait faire les deux en une seule micro-opération. Il s'agissait alors d'une optimisation du chemin de données, plus que de la micro-fusion proprement dite, mais je la mentionne ici malgré tout. La micro-fusion sur ce processeur avait un avantage en plus des avantages usuels : elle utilisait mieux les décodeurs. Les Pentium M avaient trois décodeurs pour décoder plusieurs instructions en même temps, chose qu'on détaillera dans le chapitre sur les processeurs superscalaires. Un seul décodeur pouvait décoder toutes les instructions, les deux autres ne peuvent décoder que les instructions simples, qui se décodent en une seule micro-opération. Avec la micro-fusion, les trois décodeurs peuvent gérer les écritures mémoire et les opérations ''load-op'', vu qu'elles deviennent des instructions simples. Sans cela, elles auraient été limitées au premier décodeur. <noinclude> {{NavChapitre | book=Fonctionnement d'un ordinateur | prev=Le parallélisme mémoire | prevText=Le parallélisme mémoire | next=Exemples de microarchitectures CPU : le cas du x86 | nextText=Exemples de microarchitectures CPU : le cas du x86 }} </noinclude> jia1doh9tcydpncitkmpayh3l5cvg3u 773238 773237 2026-09-26T13:58:03Z Mewtow 31375 /* La double émission entière-flottante */ 773238 wikitext text/x-wiki Les processeurs vus auparavant ne peuvent émettre au maximum qu'une instruction par cycle d'horloge. Ils peuvent avoir plusieurs instructions qui s'exécutent en même temps, dans des unités de calcul séparées. C'est le cas dès qu'une instruction multi-cycles s'exécute. Par contre, ils ne peuvent démarrer qu'une seule instruction par cycle. Et quand on court après la performance, ce n'est pas assez ! Les concepteurs de processeurs ont inventés des processeurs qui émettent plusieurs instructions par cycle : les '''processeurs à émissions multiples'''. Un processeur à émission multiple charge plusieurs instructions en même temps, les décode en parallèle, puis les émet en même temps sur des unités de calculs séparées. Pour cela, il faut gérer les dépendances entre instructions, répartir les instructions sur différentes unités de calcul, et cela n'est pas une mince affaire. En général, pour un processeur à émission multiple capable d'émettre N instructions en parallèle, on parle de '''processeur à N voies''', ou encore un ''processeur à N pipelines''. [[File:Superscalarpipeline.svg|centre|vignette|upright=1.5|Pipeline RISC classique à cinq étages sur un processeur superscalaire. On voit bien que plusieurs instructions sont chargées en même temps.]] Les processeurs à émission multiple sont de deux types : les processeurs VLIW et les '''processeurs superscalaires'''. Nous mettons les processeurs VLIW de côté en attendant le prochain chapitre. La raison est qu'ils ont un jeu d'instruction spécialisé qui expose le parallélisme d'instruction directement au niveau du jeu d'instructions, là où les processeurs superscalaires restent des processeurs au jeu d'instruction normal. La différence principale entre processeur VLIW et superscalaire est qu'un processeur superscalaire répartit les instructions sur les unités de calcul à l’exécution, là où un processeur VLIW délègue cette tâche au compilateur. ==Le nombre de voies d'un processeur superscalaire== Les processeurs superscalaires les plus simples ne permettent que d'émettre deux micro-opérations à la fois, d'où leur nom de processeur ''dual issue'', ce qui se traduit en '''processeur à double émission'''. Les processeurs triple émission permettent eux d’émettre trois instructions à la fois. A l'opposé, les CPU superscalaires les plus puissants peuvent émettre jusqu'à 8-10 instructions simultanément. En 2026, les CPU superscalaires peuvent monter jusqu’à 10 µops émises en même temps, ce qui est énorme ! Et cela nous amène à faire une distinction entre les processeurs superscalaires dits '''étroits''' et ceux dits '''larges'''. Dans ce wikilivre, on part du principe que les processeurs superscalaires étroits émettent jusqu'à 4-5 µops à la fois, alors que les larges peuvent en émettre plus, l'intervalle entre les deux est une zone grise. Le nombre exact n'est pas fixé dans le marbre, la distinction est volontairement vague. Nous parlerons de la '''largeur d'un CPU superscalaire''' pour parler du nombre de voies, du nombre de µops émises en même temps. Plus un CPU superscalaire est large, plus il est performant ''toute chose égale par ailleurs''. Mais dans les faits, vous pouvez oublier la partie en italique. Élargir un CPU superscalaire demande de rajouter beaucoup de circuits. Et ce cout en circuit aura un impact sur la performance. A budget en transistors limité, il y a un compromis entre élargir un CPU et rajouter du cache ou la taille des fenêtres d'instructions/ROB et autres. ===Les contraintes d’appariement=== Prenons un processeur double émission, capable d'émettre deux instructions simultanément. Émettre deux instructions en même temps n'est pas toujours possible. Par exemple, si deux instructions sont dépendantes, on ne peut pas les émettre en même temps. Mais oublions un instant ces histoires de dépendances de données, et partons du principe que le CPU ait affaire à deux instructions indépendantes, une paire idéale. Un CPU double émission "parfait" pourrait exécuter n'importe quelle paire d'instruction. Par exemple, il pourrait émettre deux multiplications consécutives, deux lectures consécutives, une lecture et une écriture en même temps, etc. En pratique, aucun processeur ne permet cela. Implémenter un CPU superscalaire "parfait" aurait un cout en circuit beaucoup trop conséquent. Par exemple, pour émettre deux multiplications en même temps, il faudrait deux multiplieurs séparés. Idem pour exécuter deux accès mémoire simultané : cela demande d'avoir deux unités mémoire et un cache multiport. Mais en pratique, aucun CPU superscalaire ne fait cela. La totalité des CPU double émission se débrouille avec un seul circuit multiplieur et une seule unité mémoire. Et cela entraine l'apparition de dépendances structurelles, qui font que certaines paires d'instructions sont impossibles. Par exemple, impossible d'émettre deux multiplications en même temps avec un seul circuit multiplieur. Mais émettre une multiplication en parallèle d'une addition est parfaitement possible. Il y a donc des paires d'instructions autorisées et des paires interdites. Et la logique est la même sur les CPU à triple ou quadruple émission, et au-delà. : des triplets ou quadruplet d'instructions sont impossibles à émettre simultanément, alors que d'autres le sont. Pour le dire autrement, il y a des '''contraintes d'appariement''' sur les instructions à émettre en même temps. ===L'impact des contraintes d’appariement sur la performance=== Ces contraintes d’appariement ont un impact sur les performance, mais celui-ci peut être grandement mitigé. Les programmes informatiques utilisent en pratique beaucoup plus d'opérations entières de base que de multiplication, branchements et accès mémoire. Il y a environ 5 opérations entières de base pour une multiplication entière. En pratique, il est rare de devoir exécuter deux multiplications à la fois. Idem pour les décalages et rotations, qui sont assez éloignés les uns des autres. Deux branchements consécutifs sont quant à eux plus fréquents. Deux accès mémoire consécutifs est assez rare, mais ils restent assez proches les uns des autres malgré tout. La conséquence est que certaines paires d'instructions sont privilégiées par rapport au reste. Les paires regroupant deux calculs basiques (additions, soustractions, ...) sont systématiquement supportées, car dupliquer des ALU entières est facile. Les paires regroupant un calcul et une autre instruction sont elles aussi systématiquement supportées. Par contre, les paires avec deux branchements, deux accès mémoire, deux décalages/rotations, ou deux multiplications/divisions sont fréquemment interdites. Seuls les CPU superscalaires larges peuvent les permettre, et encore : sous conditions. Le résultat est que le processeur peut se débrouiller avec une seule unité mémoire et une unité de branchement. ===Terminologie pour la suite du chapitre=== Décrire les contraintes d’appariement demande de dire quelles combinaisons d’instructions sont autorisées ou interdites. Et pour cela, nous allons introduire les abréviations suivantes : * INT est un raccourcis pour parler d'une opération entière simple, qu'une ALU entière peut exécuter. * MUL correspond à une opération entière complexe, typiquement une multiplication, éventuellement à une division. * FLOAT désigne une opération flottante, peut importe laquelle. * MEM désigne un accès mémoire, à savoir une instruction ou µop mémoire. * BRANCH désigne une instruction de branchement. Cette terminologie permettra de définir des paires d'instructions, qu'elles soient possibles ou interdites. Par exemple, l'expression INT + FLOAT désigne une paire d'instruction regroupant une instruction entière et une instruction flottante, peu importe l'ordre des deux instructions. Il en sera de même pour les triplets, quadruplets, ou plus. Par exemple, l'expression INT + INT + FLOAT + MUL désigne un quadruplet d'instructions regroupant : deux instructions entières, une instruction flottante, et une multiplication entière. J'utiliserais aussi des parenthèses pour regrouper certaines combinaisons. Par exemple, l'expression INT + (MUL /FLOAT) veut parler d'une paire d'instruction où la première est une opération entière basique, la seconde est : soit une multiplication, soit une opération flottante. ==Le ''front-end'' des processeurs superscalaires== Un processeur superscalaire doit charger, décoder, et exécuter plusieurs instructions par cycle. Par exemple, un processeur triple émission doit charger 3 instructions lors d'un premier cycle, puis les décoder au cycle suivant, et les exécuter au cycle suivant. Il travaille donc pas paquets de 3 instructions, dans cet exemple. Intuitivement, charger, décoder et exécuter plusieurs instructions à la fois demande de dupliquer des circuits. Par exemple, il faut dupliquer les décodeurs pour décoder plusieurs instructions. Les unités de calcul sont aussi dupliquées, sauf sur certaines processeurs qui arrivent à ruser, mais laissons cela de côté pour plus tard. Mais d'autres circuits sont simplement modifiés, comme l'unité de chargement ou l'unité d'émission. Voyons ce qu'il en est dans le détail. {|class="wikitable" |- ! rowspan="2" | Processeur sans émission multiple | rowspan="2" | Chargement | rowspan="2" | Décodage | rowspan="2" | Renommage | rowspan="2" | Émission | Exécution / ALU | rowspan="2" | ''Commit''/ROB |- | Exécution / ALU |- ! rowspan="4" | Processeur superscalaire | rowspan="4" | Chargement | rowspan="2" | Décodage | rowspan="4" | Renommage | rowspan="4" | Émission | Exécution / ALU | rowspan="4" | ''Commit''/ROB |- | Exécution / ALU |- | rowspan="2" | Décodage | Exécution / ALU |- | Exécution / ALU |} Nous allons d'abord voir ce qu'il en est pour le ''front-end'', à savoir la partie du CPU qui s'occupe de charger et décoder les instructions. Elle regroupe l'unité de chargement, les décodeurs, l'unité de prédiction de branchement, la file de µops, la file d'instruction, et divers caches. En soi, les changements portent surtout sur l'unité de chargement et les décodeurs. ===Le chargement de plusieurs instructions consécutives=== Un processeur superscalaire charge plusieurs instructions en même temps, ''en tenant compte de la présence de branchements''. La partie en italique est importante et elle a une conséquence très forte : la manière de faire est fortement différente entre un CPU superscalaire étroit et large. Un CPU superscalaire étroit fait au plus simple : il charge un bloc de 2, 3, 4 instructions consécutives en mémoire RAM. Il faut alors doubler/tripler/quadrupler la taille du bus connecté au cache d'instruction. Par exemple, pour charger quatre instructions de 32 bits chacune, il suffit de lire un bloc de 128 bits dans le cache d'instruction. Un bloc de 8, 16, 32 octets est donc lu depuis le cache et est ensuite découpé en instructions, envoyées chacun à un décodeur. Charger plusieurs instructions consécutives ne marche bien qu'en absence de branchements. La présence de branchements casse cette dynamique, et il faut gérer leur présence. * Sans prédiction de branchement, toutes les instructions d'un bloc sont décodées et exécutées en même temps, même s'il y a un branchement dans le tas. * Avec prédiction de branchement, les instructions situées après le branchement ne sont pas décodées ni exécutées. L'unité de chargement coupe le bloc chargé au niveau du premier branchement pris, remplit les vides avec des NOP, avant d'envoyer le tout à l'unité de décodage. Les instructions à l'adresse de destination seront chargées au cycle suivant, en chargeant un nouveau bloc. [[File:Fetch sur un processeur superscalaire avec prediction de branchements.png|centre|vignette|upright=2|Fetch sur un processeur superscalaire avec prédiction de branchements.]] Un autre défaut est que sur le CPU charge un bloc de 128 bits à la fois, les instructions doivent être alignées sur 128 bits. Et on peut généraliser à n'importe quelle taille de bloc. Si les instructions ne sont pas alignées, il faut gérer la situation. Il arrive qu'une instruction soit à cheval sur deux blocs, avec un morceau dans le premier bloc, un autre dans le second. Dans ce cas, l'instruction est chargée en deux fois, et des circuits doivent gérer la situation pour recoller les morceaux. Sur les CPU avec des instructions de taille fixe, le problème ne survient que rarement. Mais il est fréquent avec des instructions de taille variable. Un autre défaut est lié aux branchements. Imaginez qu'un branchement s'exécute et branche vers une instruction de destination. Rien n'indique que l'instruction de destination sera au tout début d'un bloc. Si ça se trouve, elle sera en troisième position d'un bloc de 4 instructions. Une solution simple charge le bloc en respectant l'alignement, et les deux premières instructions du bloc ne sont pas à exécuter. Il y a donc une petite perte de performance. Certains CPU, comme l'IBM RS/6000, ont la machinerie charger un bloc de N instructions même s'il n'est pas aligné. La machinerie en question se trouve dans le cache d'instruction et est tout sauf simple à expliquer,ce qui fait que je me permets de la passer sous silence. Un détail, cependant : elle ne permet pas de charger des blocs qui sont à cheval sur deux lignes de cache. ===Le décodage parallèle des instructions=== Pour le décodage des instructions, plusieurs instructions sont décodées en même temps. On parle alors de '''décodage parallèle'''. Pour ce faire, un processeur superscalaire contient plusieurs décodeurs, chacun pouvant décoder une instruction en parallèle des autres. Prenons par exemple un processeur superscalaire à 4 voies : le décodage se fera dans quatre décodeurs séparés, qui fonctionneront en parallèle. Cependant, cette implémentation ne marche bien que pour les processeurs RISC, les processeurs CISC devant faire avec un microcode gourmand en circuits. Les processeurs CISC utilisent des décodeurs hybrides, avec un microcode qui complémente un décodeur câblé. Dupliquer le microcode aurait un cout en transistors trop important, ce qui fait que seuls les décodeurs câblés sont dupliqués. Les CPU CISC superscalaires disposent donc de plusieurs décodeurs simples, capables de décoder les instructions les plus courantes, avec un seul microcode. La conséquence est qu'il n'est pas possible de décoder deux instructions microcodées en même temps. Par contre, il reste possible de décoder plusieurs instructions non-microcodées ou une instruction microcodée couplée à une instruction non-microcodée. Vu qu'il est rare que deux instructions microcodées se suivent dans un programme, le cout en performance est extrêmement mineur. Il faut noter qu'une instruction correspond à une ou plusieurs µops. Il est donc possible de charger 5 instructions et de se retrouver avec 8 µops en sortie des décodeurs. Et c'est la source d'une distinction très importante. Plus haut, j'ai parlé de processeurs superscalaires à N voies. La terminologie peut avoir deux interprétations. La première est que le processeur charge/décode N instructions à la fois. Une seconde est qu'il est capable d'exécuter/émettre N µops en même temps. Les deux ne sont pas équivalentes, vu qu'un processeur superscalaire peut, par exemple, charger/décoder N instructions et émettre/exécuter N+2 µops. La distinction est particulièrement importante sur les processeurs superscalaires larges. Certains d'entre eux sont capables de charger 5 instructions et d'en émettre le double ! Un exemple classique était les processeurs Athlon d'AMD, qui pouvaient décoder deux instructions par cycle, mais émettre 4 µops par cycle. Ce n'était pas un problème, car beaucoup d'instructions, même basiques, étaient décodées en 2 µops. ===L'émission parallèle des instructions=== La superscalarité modifie en profondeur l'unité d'émission, notamment au niveau de son interface. Avant, elle recevait une micro-opération sur son entrée, et fournissait la micro-opération émise sur une sortie. Et cela vaut aussi bien pour une unité d'émission simple, un ''scoreboard'', une fenêtre d'instruction ou des stations de réservation. Mais avec l'émission multiple, les sorties et entrées sont dupliquées. Pour la double émission, il y a deux entrées vu qu'elle doit recevoir deux micro-opération décodées/renommées, et deux sorties pour émettre deux micro-opérations. Dans ce qui suit, nous ne parlerons pas d'entrée et de sortie, mais de '''ports de décodage''' et de '''ports d'émission'''. Les ports de décodage reçoivent des micro-opérations provenant de l'unité de décodage, les ports d'émissions sont pour les micro-opérations émises. Les ports d'émission injectent les micro-opérations émises dans le pipeline, elle sont propagée au banc de registre, aux ALUs, etc. Un processeur superscalaire a forcément plusieurs ports d'émission (et de décodage). Si l'interface de l'unité d'émission change, l'intérieur est tout autant modifié. Sur un CPU non-superscalaire, l'unité d'émission reçoit une µop "candidate à l'émission", et détecte les dépendances entre avec les µops en vol, en cours d'exécution dans le pipeline. Et c'est toujours le cas sur les processeurs superscalaires : l'unité d'émission détecte les dépendances entre les µops candidates et les µops en vol. Mais avec la siperscalarité, elle doit en plus tester les dépendances entre les µops candidates. Si l'unité d'émission est un ''scoreboard'', il doit détecter les dépendances entre instructions émises simultanément et les sérialiser si besoin. Avec une fenêtre d'instruction, il n'y a pour ainsi dire rien à faire, vu que les dépendances sont éliminées par le renommage de registre et que les signaux de réveil gèrent les dépendances RAW. C'est la raison pour laquelle les processeurs superscalaires modernes n'utilisent pas de ''scoreboard''. ==Les processeurs implémentés avec plusieurs pipelines== Nous venons de voir comment la superscalarité impacte le ''front-end'', il est maintenant de voir ce qu'il en est de l'unité d'émission et des ''back-ends''. Un processeur superscalaire peut utiliser l'exécution dans le désordre et le renommage de registres, mais ce n'est pas une obligation. Historiquement, les premiers processeurs superscalaires n'utilisaient pas d'exécution dans le désordre. Le meilleur exemple est celui du Pentium 1 et 2 d'Intel. Ils étaient tous deux des processeurs superscalaires, mais n'avaient pas d'exécution dans le désordre, qui est apparue sur le Pentium 3. les explications sont plus simples si on met de côté l'exécution dans le désordre. Vous vous dites que l'implémentation d'un processeur superscalaire n'est pas la même suivant que le CPU utilise l'exécution dans le désordre, ou non. Mais dans les faits, la distinction est plus subtile que ca. Pour l'expliquer, je vais utiliser une classification originale, qui sépare les processeurs superscalaires en trois sous-types. La distinction se fait sur la manière dont la superscalarité est implémentée en matériel.La classification que j'utilise distingue : * Les processeurs avec plusieurs pipelines, sans exécution dans le désordre. * Les processeurs superscalaires avec un pipeline dynamique. * Les processeurs avec des fenêtres d'instruction décentralisées. La distinction n'est pas qu'une question d'implémentation matérielle. Elle impacte aussi le ratio entre instructions chargées/décodées et instructions émises. Vous vous dites que les deux sont égales, mais ce n'est le cas que sur les processeurs avec N pipelines ! Sur les autres processeurs, il est possible de décoder 4 instructions, mais d'en émettre maximum 6 par cycle ! Et sur les CPU avec des fenêtres d'instruction décentralisées, il y a aussi une différence entre le nombre d'instructions qui sont ''dispatch'' et ''schedule''. La catégorie la plus simple est celle des '''processeurs avec plusieurs pipelines'''. Ce sont des processeurs superscalaires sans exécution dans le désordre. Ils partent d'un processeur non-superscalaire, et ajoutent un second pipeline, en sortie de l'unité d'émission. Le second pipeline permet l'émission parallèle d'une seconde instruction. Le second pipeline n'est pas identique au pipeline originel, sur plusieurs points. Premièrement, il n'a pas exactement les mêmes circuits. Le second pipeline est généralement composé d'une unité de calcul, d'une FPU, éventuellement d'un second banc de registres, guère plus. Il permet d'exécuter une seconde opération de calcul, pas plus, ce qui est à l'origine de contraintes d'appariemment assez fortes. De plus, il n'a pas forcément la même longueur, il peut avoir des étages en moins ou en plus. Il s'agit de la forme la plus simple de processeur superscalaire et nous allons leur consacrer cette section. ===La double émission entière-flottante=== L'exemple le plus simple utilise un second pipeline pour les opérations flottantes, séparé du pipeline originel. L'ajout d'un pipeline flottant permet d'émettre une micro-opération entière en même temps qu'une micro-opération flottante, ce qui s'appelle la '''double émission entière-flottante'''. La micro-opération entière s'exécute dans l'ALU entière, la micro-opération flottante dans la FPU. Les exemples les plus notables sont le PA-RISC 7100, le POWER 1 (RIOS .& et .9) et le PowerPC 601. L'Apollo DN10000 et l'Intel i860 utilisaient aussi cette forme de double émission, mais étaient des prototypes de CPU VLIW. [[File:Double émission entière-flottante.png|centre|vignette|upright=2|Double émission entière-flottante]] Pour bien expliquer les choses, prenons le processeur illustré ci-dessous, avec deux ''back-end'' séparés : un pour la FPU, et un autre pour le reste. Les instructions entières et mémoire passent toutes par le ''back-end'' généraliste. [[File:CPU non-superscalaire basique, avec une FPU.png|centre|vignette|upright=1.5|CPU non-superscalaire basique, avec une FPU]] Les deux ''back-end'' sont des pipelines séparés, mais il faut que l'unité d'émission soit spécialement conçue pour en profiter. Un processeur simple émission peut démarrer une seule µop par cycle : soit la µop est envoyée au ''back-end'' flottant, soit elle est envoyée au ''back-end'' général. Avec la double émission entière-flottante, le processeur peut envoyer une µop dans chaque pipeline, à chaque cycle. Vu que la FPU et son banc de registre forment déjà un pipeline séparé, les seules modifications se font au niveau du ''front-end'', unité d'émission inclue. {| |- |[[File:CPU simple émission avec une FPU.png|centre|vignette|upright=1.5|CPU simple émission avec une FPU.]] |[[File:Double émission entière flottante avec un second pipeline.png|centre|vignette|upright=1.5|CPU avec un second pipeline flottant.]] |} Notez que le terme double émission ''entière''-flottante est un peu trompeur. En réalité, la technique permet d'émettre une instruction flottante avec n'importe quelle autre instruction non-flottante, avec des restrictions pour les branchements. Il est par exemple possible d'exécuter un calcul flottant en parallèle d'un accès mémoire. Sur les CPU 32 bits, il est possible de faire les multiplications dans la FPU. Pour rappel, la FPU contient un multiplieur entier 53-54 bits, pour multiplier les mantisses de deux flottants 64 bits, qu'on peut utiliser pour faire les multiplications entières 32 bits. Cela permet d'émettre une multiplication en même temps qu'une opération entière dans l'ALU. Ainsi, le CPU pouvait émettre deux instructions : une opération entière de base, avec soit une multiplication, soit une opération flottante. En clair, une émission de type INT + (MUL /FLOAT). Par contre, on perd la possibilité d'émettre une multiplication entière avec une opération flottante, mais c'était un compromis qui en valait la peine. Le CPU PA-RISC 7100 implémentait cette optimisation. Il s'agissait d'un CPU double émission entière-flottante, qui utilisait donc un partitionnement assez simple. Le combo partitionnement + reconditionnement permettait ici de gagner en performance, pour un cout en matériel très limité. D’autant plus limité que le jeu d'instruction prévoyait le reconditionnement de la FPU. Les multiplications ne lisaient pas leurs opérandes dans les registres entiers, mais dans les registres flottants. ===La double émission entière=== Une autre possibilité ajoute un second pipeline, capable d'exécuter des opérations entières simples, appelé le pipeline entier. Le processeur supporte alors la '''double émission entière''', à savoir qu'ils peuvent émettre une opération entière en plus d'une autre instruction. L'opération entière en question est une opération simple, comme une addition, une soustraction, une opération bit à bit, une comparaison, etc. Les exemples classiques sont les processeurs Intel i960 et Pentium. L'implémentation était différente entre l'i960 et le Pentium : Le Pentium avait une seconde ALU entière, alors que le i960 rusait. Le Pentium dupliquait l'ALU entière, pour créer un second pipeline entier. Sur le Pentium originel, les contraintes d'appariement étaient assez strictes. Le Pentium avait une FPU, mais ne pouvait pas l'utiliser en double émission. Il lui était impossible d'exécuter une opération flottante en même temps qu'une opération entière (branchements inclus). Le Cyrix 6x86 était une sorte de Pentium produit par l'entreprise Cyrix, avec une microarchitecture différente. Il avait des contraintes d’appariement réduites. Par exemple, il pouvait émettre une opération flottante en parallèle d'une opération entière. Nous détaillerons ces processeurs dans le prochain chapitre, qui porte justement sur les CPU superscalaires x86. L'Intel i960 n'avait pas de FPU, juste une ALU entière et une unité mémoire. Il utilisait l'unité mémoire comme une seconde ALU entière limitée. Pour rappel, une unité mémoire contient une AGU, à savoir une ALU spécialisée dans les calculs d'adresse. Elle est capable de faire des additions, soustractions, et quelques décalages limités (des décalages par 2, 4, 8, pas plus). L'Intel i960 ajoutait des connexions entre AGU et banc de registre, afin que celle-ci serve de seconde unité de calcul. L'AGU est déjà reliée aux deux ports de lecture, pour lire les opérandes du calcul d'adresse, il suffit de la connecter sur le port d'écriture. Le processeur peut alors émettre deux instructions/µops entières, sous certaines conditions. [[File:Emission multiple des opérations entières, double émission.png|centre|vignette|upright=2|Émission multiple des opérations entières, double émission.]] ==Les processeurs superscalaires dynamiques== Les processeurs précédents, avec 2 pipelines, avaient une unité d'émission assez simple. Elle avait deux ports de décodage, et deux ports d'émission. Le nombre de ports de décodage et d'émission étaient identiques. Vous vous dites que c'est naturel, et intuitif, que c'est normal pour un processeur superscalaire. Mais en réalité, la majorité des processeurs superscalaires a plus de ports d'émission que de décodage. Expliquer pourquoi va cependant nous demander de parler de comment la superscalarité exploite les unités de calcul. Posons-nous cette question : de combien d'unités un processeur superscalaire a besoin ? Les unités en question ne sont nécessairement des unité de calcul, l'unité mémoire et l'unité de branchement comptent aussi. La réponse dépend du nombre de voies. Si le ''front-end'' charge et décode un paquet de N instructions, exécuter ces N instructions simultanément demandera là aussi au moins N unités de calcul. Notez le terme : ''au moins'', car c'est là tout l'enjeu de cette section. La réponse dépend aussi des contraintes d’appariement. Pour un CPU superscalaire "parfait", sans contraintes d’appariement, toutes les unités doivent être dupliquées, y compris l'unité mémoire. Un processeur à double émission parfaite devrait dupliquer l'ALU entière, le ''barrel shifter'', le circuit multiplieur, la FPU, l'unité mémoire, et l'unité de branchement, ce qui ferait passer de 6 unités à 12. Le cout en circuit est énorme, même pour de la double émission ! Et pour un CPU à N pipelines, le cout en circuit encore plus prohibitif ! En pratique, les processeurs superscalaires qui font ça sont très rares. La majeure partie des processeurs superscalaires utilisent d'autres techniques : le partitionnement, la duplication partielle des ALUs, la duplication des autres unités. ===Le partitionnement des unités fonctionnelles=== Les CPU superscalaires les plus simples ne dupliquent pas leurs unités de calcul, ils se contentent d'exploiter les unités existantes. Et pour expliquer comment, nous allons partir de l'exemple d'un processeur non-superscalaire contient une ALU entière, une FPU, une unité mémoire et une unité de branchement. Les quatre unités peuvent fonctionner en parallèle, à savoir qu'une unité peut exécuter une instruction à elle toute seule, sans interagir avec les autres. Cela suffit pour créer un processeur quadruple émission, qui peut émettre en même temps : une opération entière, une opération flottante, un branchement, un accès mémoire. Le principe marche aussi sur un processeur avec moins de 4 unités, par exemple sans FPU et/ou sans unité de branchement, ou avec au contraire des unités en plus. L'idée générale est d’émettre une µops séparée dans chaque unité fonctionnelle. Utiliser ainsi les unités existantes, pour rendre un CPU superscalaire, s'appelle le '''partitionnement superscalaire'''. Le terme n'est pas beaucoup utilisé en-dehors de quelques vieux articles académiques. Le partitionnement est fortement dépendant du jeu d'instruction, car il demande de bien séparer instructions entières, flottantes, branchements et instructions mémoire. Et autant c'est bien le cas sur les CPU RISC, autant les CPU CISC ne sont pas dans ce cas. Les CPU CISC ont des instructions ''load-op'', qui effectuent une instruction arithmétique et une µop mémoire. Et ces instructions sont très compliquées à implémenter avec le partitionnement. Et au-delà de ça, les CPU CISC ont beaucoup d'instructions qui sont décodées en plusieurs µops, à exécuter dans un ordre bien précis. On peut pas émettre les µops en question simultanément. Le partitionnement a un cout en circuit très limité, pour un gain en performance lui aussi limité. Pour reprendre le CPU de l'exemple précédent, il est rare que l'on ait à exécuter ces quatre instructions en même temps. En pratique, les combinaisons fréquentes sont les combinaisons calcul + accès mémoire, calcul + branchement, calcul entier + calcul flottant. Utiliser une quadruple émission de ce type n'est donc pas une bonne idée, alors que la double émission parait plus adaptée. Et c'est là que la différence entre ports d'émission et de décodage devient intéressante. L'idée est d'avoir une unité d'émission avec deux ports de décodage, et 4 port d'émission (un par unité). Les contraintes d’appariement varient alors suivant le processeur, mais la règle générale est que si deux micro-opérations tombent dans deux unités différentes, on peut les émettre en même temps. En clair, on peut émettre une opération entière avec une opération flottante, une opération entière avec un branchement, un accès mémoire avec un branchement, etc. Et cela rend le partitionnement bien plus intéressant que prévu. Les tout premiers CPU superscalaires étaient nombreux à utiliser cette technique. Les exemples classiques sont l'HyperSPARC, de l'alpha 21064 et du PowerPC 603. C'étaient des CPU double émission, avec une ALU entière, une unité mémoire et une FPU. Nous parlerons de '''partitionnement INT-FLOAT-MEM'''. Les contraintes d’appariement variaient grandement suivant le processeur. Le PowerPC 603 implémentait une double émission "parfaite". C'est à dire que toutes les combinaisons possibles à deux instructions étaient possibles, que ce soit INT + FLOAT, INT + MEM, FLOAT + MEM. En plus de cette double émission "parfaite", le CPU utilisait la technique dite de ''branch folding'', qui permettait d'exécuter un branchement sans utiliser d'unité de calcul, ce qui fait qu'il était qualifié de ''CPU 2-way + Branch''. A l'opposé, le processeur alpha 21064 avait pas mal de contraintes d’appariement, à savoir que certaines combinaisons de deux instructions étaient interdites, bien que compatibles avec les unités disponibles. Une des raisons est que le banc de registre n'avait pas assez de ports de lectures, ce qui fait que certaines combinaisons avec une écriture mémoire n'étaient pas permises. Les concepteurs avaient volontairement inséré ces contraintes afin de simplifier le design du processeur. Avant de poursuivre, précisons que la FPU regroupe un additionneur flottant et un multiplieur flottant. Et ce fait peut être exploité pour approfondir le partitionnement. La '''double émission FADD/FMUL''' permet d'émettre une addition flottante en même temps qu'une multiplication flottante. Elle a été utilisée sur les processeurs x86, Alpha et SPARC. Les autres circuits de calcul flottant sont souvent regroupés avec l'additionneur. La division est parfois e même port que le multiplieur flottant, parfois sur celui de l'additionneur (quand elle est microcodée et exécutée en enchainant les soustractions). ===La duplication des ALU entières=== Le partitionnement est une technique élégante, simple, facile à implémenter et au cout en circuits modeste. Mais il faut avouer que cela ne donne pas des CPU superscalaires très efficaces. La raison est que le partitionnement permet d'émettre des paires, triplets, quadruplets de micro-opérations qui sont rares en pratique. Les combinaisons les plus fréquentes en pratique sont des paires/triplets d'opérations entières, souvent des paires/triplets d'additions ou d'opérations entières simples. Il est donc primordial d'émettre plusieurs opérations entières simultanément. Le fait d'émettre et d'exécuter plusieurs opérations entières en même temps s'appelle l''''émission entière multiple'''. Pour exécuter la seconde/troisième opération entière, il faut dupliquer les ALU entières (ou réutiliser l'AGU comme ALU entière comme le fait l'Intel i960). Le processeur doit avoir deux ALU entières pour de la double émission entière, trois pour de triple émission entière, etc. De plus, il faut ajouter des ports au banc de registre, pour lire deux fois plus d'opérandes et décrire deux résultats. Et enfin, il faut rajouter un port d'émission pour pouvoir émettre une micro-opération entière en plus. [[File:Processeur non-superscalaire basique 03.png|centre|vignette|upright=2|Processeur non-superscalaire basique]] La tendance est de multiplier les ALU entières, mais pas le circuit multiplieur. Le cout en transistors est alors grandement réduit, au prix de l'apparition de dépendances structurelles. Le CPU peut émettre deux additions en même temps, ou une addition et une multiplication, mais pas deux multiplications. Il en est de même avec le ''barrel shifter'', qui n'est pas dupliqué. Il est donc possible d'émettre un décalage avec une opération de base, mais pas deux décalages en parallèle. Mais ces contraintes d’appariement ne sont pas un problème, car il est rare de devoir émettre deux multiplications/décalages en même temps. La plupart des programmes sont majoritairement remplis d'additions, avec des multiplications assez rares et des décalages qui le sont encore plus. La moyenne est proche d'une multiplication pour 4/5 additions. Vu que des multiplications consécutives sont très rares, dupliquer le multiplieur est presque inutile. Disposer de plusieurs circuits multiplieurs ou de plusieurs ''barrel shifters'' serait donc un cout en circuits qui ne servirait que rarement et n'en vaut pas la chandelle. Il faut noter que sur les CPU 32 bits, les multiplications peuvent être exécutées dans la FPU ou dans un multiplieur séparé, tout dépend de si le CPU exécute les multiplications dans la FPU ou pas. {|class="wikitable" |- ! Voie d'émission n°1 !! Voie d'émission n°2 !! Voie d'émission n°3 |- | ALU entière || ALU entière || FPU |} Reste à connecter les unités de calcul à l'unité d'émission (indirectement, car il y a des étages de pipeline entre les deux). Une implémentation basique utilisee un port d'émission par unité. Prenons l'exemple d'un CPU superscalaire avec 4 ALU entières, une FPU, et une unité mémoire. Vu qu'il y a 6 unités, cela fait donc 6 ports d'émission. L'exemple historique le plus impressionnant est celui du Motorola 88110, un CPU double émission qui possède 10 unités. Il contient deux ALU entières, un circuit multiplieur, un circuit diviseur, une unité de manipulation de bits, une unité mémoire, un additionneur flottant, et deux unités graphiques pour des calculs impliquant des pixels. Le multiplieur est utilisé à la fois pour les multiplications entières, flottantes et le produit de deux pixels. Idem pour le diviseur, qui sert pour les divisions entières et flottantes. Et chaque unité a son propre port d'émission. La conséquence est que les contraintes d'appariement du Motorola 88110 sont simples : tant que deux µops atterrissent dans des unités séparées, la paire est permise. Il est par exemple possible d'émettre simultanément deux opérations entières simples, vu qu'il y a deux ALU. Par contre, impossible d'émettre simultanément deux multiplications, deux divisions, deux opérations flottantes, deux manipulations de bit, deux accès mémoire, etc. De plus, le partage du multiplieur fait qu'on ne peut pas émettre une multiplication entière avec une multiplication flottante. Idem avec les divisions, vu que le diviseur est aussi partagé. Ce sont les seules contraintes. ===Le partage des ports d'émission=== La présence d'un circuit multiplieur et d'un ''barrel shifter'' pose des problèmes pour le partitionnement. Il est possible d'avoir un port d'émission dédié au multiplieur, et un autre au ''barrel shifter'', qui sont séparés de ceux pour l'ALU entière. Il est alors possible d'émettre une opération entière, une multiplication et un décalage, simultanément. Nous parlerons de '''partitionnement entier''' pour désigner cette possibilité. [[File:Partitionnement superscalaire entier.png|centre|vignette|upright=2|Partitionnement superscalaire entier.]] Le partitionnement entier est cependant très rare, car le gain en performance serait trop faible au regard des couts. Quelques processeurs historiques l'utilisaient, mais ce n'est plus le cas de nos jours. La raison est que les multiplications et les décalages sont assez rares dans le code, ce qui fait que le port d'émission associés sont sous-utilisés. Et un port d'émission a un cout en circuit assez important. L'idéal serait de trouver un moyen de réduire le nombre de ports d'émissions, sans réduire le nombre d'unités de calcul. [[File:Emission multiple des opérations entières, implémentation naive.png|vignette|Émission multiple des opérations entières, implémentation naive]] La solution consiste à '''partager les ports d'émission''', à savoir connecter un port d'émission à plusieurs unités de calcul. L'exemple classique est justement lié aux circuits multiplieur et ''barrel shifters''. L'idée est que le multiplieur et le ''barrel shifter'' sont mis sur le même port d'émission qu'une ALU entière. Prenons par exemple le processeur à double émission entière illustré ci-contre. Un port d'émission est relié à une ALU entière seule, et l'autre port d'émission alimente la seconde ALU entière et le multiplieur. La conséquence est que le processeur peut émettre deux opérations entières, dont une multiplication. Si on avait utilisé un port d'émission dédié au multiplieur, il aurait été possible d'émettre deux opérations entières et une multiplication. On perd donc une voie, mais on gagne en flexibilité et on économise des circuits. [[File:Emission multiple des opérations entières, implémentation courante.png|centre|vignette|upright=1.5|Émission multiple des opérations entières, implémentation courante]] En général, le ''barrel shifter'' et le multiplieur sont placés sur deux voies d'émission séparés, ce qui permet d'émettre un décalage et une multiplication en même temps. Les mettre sur le même port ne le permettrait pas. {|class="wikitable" |- ! Voie d'émission n°1 !! Voie d'émission n°2 !! Voie d'émission n°3 |- | * ALU entière * Multiplieur | * ALU entière * Barrel shifter | FPU |- | * Additions, soustractions, opérations bit à bit, etc. * Multiplication | * Additions, soustractions, opérations bit à bit, etc. * Décalages et rotations | Opération flottante |} Mais le partage des ports d'émission peut appliquer à n'importe quelle unité. Prenons l'exemple d'un CPU superscalaire avec 4 ALU entières, une FPU, et une unité mémoire. Vu qu'il y a 6 unités, cela fait donc 6 ports d'émission. Cependant, il est rare qu'un bloc de 6 instructions contienne exactement 4 opérations entières, une opération flottante, et un accès mémoire. Beaucoup de ports d'émission seraient sous-utilisés. La solution est de partager des ports d'émission, pour passer à la quadruple émission. Le CPU peut alors répartir 4 instructions sur les ports d'émission adéquats, à chaque cycle. Le CPU a donc 4 ports de décodage et 6 ports d'émission. Une implémentation classique permet n'importe quelle combinaison de 4 instructions compatible avec les 6 unités. Par exemple, il devient possible d'émettre simultanément 4 additions, la combinaison 2 * INT + FLOAT + MEM, 3 * INT + MEM, et bien d'autres. L'exemple relie une ALU entière, la FPU et l'unité mémoire sur le même port. Le CPU a alors quatre ports d'émission, qui ne sont pas "identiques". Trois d'entre eux sont reliés à une ALU entière chacun, rien d'autre. Le quatrième est lui relié à une ALU entière, la FPU et l'unité mémoire. On économise alors deux ports d'émission, mais cela entraine l'apparition de contraintes d’appariement. Le CPU peut émettre 3 opérations entières + n'importe quelle autre opération. Mais les autres combinaisons sont interdites. Le CPU peut émettre 6 opérations entières simultanément, grâce à 6 ALU entières. Les accès mémoire ou opérations flottantes ne viennent pas en plus, mais en remplacement des opérations entières. Regrouper plusieurs ALU sur un même port d'émission est donc à l'origine de dépendances structurelles, de nouvelles contraintes d’appariement. Dans certaines situations de '''''port contention''''', on a assez d'ALU pour exécuter N micro-opérations, mais la répartition des ALUs sur les ports l'empêche. Impossible d'émettre deux micro-opérations sur deux ALU si elles sont sur le même port d'émission. On peut réduire les cas de ''port contention'' en répartissant correctement les ALU sur les ports d'émission, mais sans jamais les supprimer complétement. Un exemple est celui du PowerPC 604. Il implémentait la quadruple émission, alors qu'il avait 5 unités de calcul. Mais deux unités partageaient un même port d'émission. Dans le détail, il avait deux ALU entières, un circuit multiplieur/diviseur, une FPU, une unité mémoire. La FPU et le multiplieur étaient sur le même port d'émission. En conséquence, le CPU pouvait émettre seulement les combinaisons INT + INT + MEM + (MUL/FLOAT). : Il est intéressant de voir l'exemple d'un processeur superscalaire parfait, sans contraintes d'appariement, à N voies. Pour cela, on part d'un processeur non-superscalaire, avec M unités de calcul. Un tel processeur peut s'implémenter avec N ports d'émission, chacun relié à M unités de calcul possibles, ce qui fait que les M unités de calcul sont dupliquées en N exemplaires. Si on prend par exemple un CPU non-superscalaire avec une ALU, une FPU, une unité mémoire et une unité de branchement, un port d'émission sera relié à une ALU, une FPU, une unité mémoire et une unité de branchement. ===La duplication des autres unités fonctionnelles=== Dupliquer les ALU entière est une première étape, mais il est aussi possible de dupliquer les autres unités fonctionnelles. Par exemple, il est possible de dupliquer l'unité mémoire ou l'unité de branchement. Reste à voir quelles unités dupliquer, et pourquoi. Précisons que dans ce qui va suivre, nous allons parler de la duplication des unités en général, pas seulement des unités mémoire/branchement. Nous allons continuer de parler de la duplication des ALU entières dans ce qui suit, mais de manière plus générale. Pour un processeur à N voies, il est théoriquement possible de dupliquer toutes les unités en N exemplaires. Mais ce n'est jamais fait en pratique, car le cout en circuits serait trop important et le gain en performance trop faible. Mieux vaut dupliquer certaines unités bien précises, quitte à avoir des contraintes d'appariement. Et à ce petit jeu, toutes les duplications ne se valent pas, certaines sont plus rentables en matière de performance, le cout en circuit n'est pas le même selon l'unité, etc. La rentabilité en performance dépend surtout des contraintes d'appariement. Les contraintes d'appariement entrainent une baisse de performance significatives si elles interdisent des paires d'instructions fréquentes. Aussi, les opérations très fréquentes, souvent appairées, gagnent à dupliquer l'unité associée. Et au contraire, les unités rarement utilisées ne doivent pas être dupliquées. Il se trouve que les unités les plus utilisées sont les ALU entières, suivies par les unités mémoire et l'unité de branchement. Le cout en circuit n'est aussi pas le même. Dupliquer les ALU entière est trivial, car elles ont un budget en transistors assez faible. Les circuits multiplieurs, diviseurs, et les ''barrel shifter'' ont eux un cout en transistor plus élevés. Si on devait faire un classement, on aurait : ALU entière < ''barrel shifter'' < multiplieur. Pour les unités mémoire, le cout en transistor est là aussi conséquent. Le résultat final est que dupliquer les ALU entières est privilégié sur le reste, comme l'a vu dans la section précédente. Les unités mémoire arrivent en seconde place, suivie par les multiplieurs/''barrel shifter'' à la troisième place, et les unités de branchement à la quatrième place. Les CPU superscalaires larges ont donc tendance à multiplier les unités mémoire en second. Les multiplieurs et ''barrel shifters'' sont eux aussi dupliqués sur les CPU superscalaires larges, mais cela vient souvent après avoir dupliqué les unités mémoire. La raison est que les accès mémoire sont critiques pour la performance des CPU à exécution dans le désordre, pas les multiplications. Pour les unités de branchement, une subtilité complique le fait d'en installer plusieurs. Que faire quand deux branchements sont pris ? La solution la plus simple est de ne pas se trouver dans cette situation : on n'utilise qu'une seule unité de branchement et on n’exécute qu'un seul branchement par cycle. La majorité des processeurs superscalaires étroits font ainsi. Les processeurs superscalaires larges ont cependant deux unités de branchements, mais ils n'acceptent qu'un seul branchement pris à la fois. Si les deux branchements pris sont exécutés en même temps, le processeur n’exécute que le plus récent, dans l'ordre du programme. ===Les CPU superscalaires mélangent les trois techniques=== Les CPU superscalaires utilisent toutes les techniques à la fois : partitionnement, duplication de l'ALU entière, duplication des autre unités fonctionnelles. Chaque processeur utilise ces trois stratégies à sa sauce, en fonction de son budget en transistor. Les quatre techniques ont cependant un cout en circuit différent, et donnent des gains de performances eux aussi forts différents. * Le '''partitionnement superscalaire''' a un cout en circuit modéré, car on n'a pas besoin de dupliquer d'ALU. * La '''duplication des ALU entières''' est la technique la plus importante : elle a un cout en circuit modéré, et donne des gains en performance impressionnants. * La '''duplication des autres unités''' a un cout en circuit variable, pour des gains en performance eux aussi variables. * Le '''partage des ports d'émission''' réduit le cout en circuit du CPU, pour des pertes de performances mineures si bien fait. L'usage du partitionnement seul, sans duplication, était surtout le fait des processeurs commercialisés entre les années 1989 et 1993. Le partage des ports d'émission a lui aussi mis un peu de temps pour s'imposer. Globalement, les processeurs superscalaires ont évolués progressivement, pour se rapprocher d'une sorte de standard. Mais pour comprendre ce qu'est ce standard, et en quoi les CPU historiques s'en sont progressivement rapprochés, il faut étudier l'histoire des CPU superscalaires. L'histoire des CPU superscalaires a commencé dès 1960, mais est resté un projet de recherche pendant plusieurs décennies. De nombreux designs ont été conçus, mais abandonnés avant la production, comme l'ACS-1 d'IBM. Les années 1980 ont vu l'arrivée de prototypes de processeurs VLIW, mais qui n'étaient pas superscalaires. Par exemple, l'Apollo DN10000 et l'Intel i860 pouvaient émettre deux instructions à la fois, sous conditions, mais demandaient que le compilateur fasse une bonne partie du travail. Les premiers CPU superscalaires commercialisés sont sortis en 1989. Deux CPUs superscalaires sont sortis cette année là : le RS/6000 d'IBM et l'i960CA d'Intel. Puis, la compétition s'est aussi lancée dans la bataille. Les CPU commercialisés avant l'année 1993 étaient à double émission, à l'exception du SuperSPARC qui utilisait la triple émission. Les modèles après 1993 hésitaient entre double et quadruple émission. Dans le tableau ci-dessous, les processeurs quadruple émission sont en bleu, les triple émission sont en rouge, les autres ne sont pas en couleur. {|class="wikitable" |- ! !! IBM !! HP PA-RISC !! SPARC !! Intel!! Alpha !! Autres |- ! 1989 | RS/6000 || || || i960CA || || |- ! 1990 | || || || || || |- ! 1991 | || || || || || Motorola 88110 |- ! 1992 | RSC || PA 7100 || class="f_rouge | SuperSPARC || || 21064 || |- ! 1993 | PPC 601 || || HyperSPARC || Pentium || || |- ! 1994 | class="f_bleu | POWER 2 || PA 7200 || || || class="f_bleu | 21164 || |- ! 1995 | 603/604 || class="f_bleu | PA 8000 || class="f_bleu | UltraSPARC 1 || Microarchitecture P6 (+ concurrence AMD) || || class="f_bleu | MIPS R1000 |- ! 1996 | class="f_bleu | 620 || || || || class="f_bleu | 21264 || |- ! ... || ... || ... || ... || ... || ... || ... |} Les premiers CPU à double émission sont assez représentatifs des CPU à double émission modernes. Il faut dire que se limiter à deux voies ne permet pas de faire beaucoup de choses. Voici le tableau chronologique disant quel CPU utilisait quelle technique. * Les CPU en vert utilisaient la double émission entière-flottante. * Les CPU en rouge utilisaient la double émission entière "pure". * Les CPU en jaune utilisaient un ''partitionnement complet'' entre ALU, FPU et unité mémoire. * Les CPU sans couleur utilisaient un grand nombre d'unités, avec beaucoup de ports d'émission. * Les CPU barrés utilisent la triple ou quadruple émission et seront vu dans la section suivante. {|class="wikitable" |- ! !! IBM !! HP PA-RISC !! SPARC !! Intel!! Alpha !! Autres |- ! 1989 | class="f_vert | RS/6000 || || || class="f_rouge | i960CA || || |- ! 1990 | || || || || || |- ! 1991 | || || || || || Motorola 88110 |- ! 1992 | class="f_vert | RSC || class="f_vert | PA 7100 || <s>SuperSPARC</s> || || class="f_jaune | 21064 || |- ! 1993 | class="f_vert | PPC 601 || || class="f_jaune | HyperSPARC || class="f_rouge | Pentium || || |- ! 1994 | <s>POWER 2</s> || PA 7200 || || || <s>21164</s> || |} C'est en 1994, que la quadruple émission est arrivée et que les CPU ont essayé divers implémentations. Il y avait alors trois stratégies pour la double/triple/quadruple émission, qui partageaient des problèmes similaires. La première implémentation dupliquait les ALUs et les FPUs, ce qui permettait d'émettre deux opérations flottantes avec deux opérations entières. Le POWER 2 dupliquait ses FPUs à la perfection, alors que le 21264 séparait l'additionneur et le multiplieur flottant. Les accès mémoire étaient traités comme des opérations entières et le processeur pouvait souvent en émettre deux à la fois. Mais une telle organisation n'est plus utilisée de nos jours, car, sans instructions flottantes à exécuter, elle est équivalente à de la double émission. La seconde implémentation utilisait deux ALU entières (dont un multiplieur), une unité mémoire et une FPU.Cette organisation est intéressante, mais encore trop stricte pour donner de bons résultats, les quadruplets avec exactement deux opérations entières, un accès mémoire et une opération flottante étant trop rares. L'usage de ces 4 unités en double ou triple émission a été utilisé sur le PA 7200 et le SuperSPARC. Malheureusement, en plus de problèmes au niveau de son cache, le SuperSPARC avait de nombreuses contraintes d’appariement, car le banc de registre n'avait pas assez de ports pour alimenter certaines combinaisons d'instructions. De plus, s'il émettait une multiplication, il ne pouvait pas émettre de seconde µop. Enfin, une troisième implémentation utilisait un grand nombre d'unités fort différentes, bien plus que quatre, avec au minimum deux ALU entières. Prenons l'exemple de l'UltraSPARC, qui avait : deux ALU entières, une unité mémoire, une unité de branchement, un additionneur flottant, un multiplieur flottant, un diviseur flottant (aussi utilisé pour le calcul des racines carrées), et 2 unités graphiques pour les calculs sur des pixels. En tout, cela fait 9 unités différentes, pour de la quadruple émission. Dans le même genre, les processeurs MIPS R8000 et R10000 avaient une implémentation plus moderne, dans le sens où toutes les unités étaient dupliquées. Le CPU avait deux ALU, deux FPU et deux unités mémoire. Il pouvait donc émettre quatre instructions à la fois, tant que le paquet collait avec ces six unités. Moitié opérations entières, moitié opérations mémoire, avec de quoi remplacer les opérations entières par des opérations flottantes. Le PA 8000, lui, dupliquait toutes ses unités en deux exemplaires, ce qui n'est pas l'idéal, aucun processeur moderne ne fait cela aujourd'hui. Le tout est résumé dans le tableau ci-dessous, avec : * en vert les CPU utilisant la première stratégie ; * en rouge les CPU utilisant la seconde stratégie ; * en jaune les CPU utilisant la troisième stratégie. {|class="wikitable" |+ Processeurs quadruple émission historiques |- ! Année !! Processeur !! Unités |- ! 1991 | class="f_jaune | Motorola 88110 || class="f_jaune | 2 ALU + MUL + DIV + BIT + MEM + FADD + 2 unités graphiques |- |- ! rowspan="2" | 1994 | class="f_vert | POWER 2 || class="f_vert | 2 ALU/MEM + 2 FPU (+ 2 Branchements via ''branch folding'') |- | class="f_rouge | 21164 || class="f_rouge | 2 ALU + FPU + MEM |- ! rowspan="3" | 1995 | PA 8000 || 2 ALU + 2 ''barrel shifters'' + 2 MEM + 2 FPU (FMA) + 2 FPU (DIV et SQRT) + 2 SIMD |- | class="f_jaune | UltraSPARC 1 || class="f_jaune | 2 ALU + MEM + BRANCH + FADD + FMULFDIV + 2 unités graphiques |- | MIPS R8000 et R10000 || 2 ALU + 2 FPU + 2 MEM |- ! rowspan="2" | 1996 | class="f_rouge | 620 || class="f_rouge | 2 ALU + FPU + MEM |- | class="f_vert | 21264 || class="f_vert | 2 ALU/MEM + 2 FPU (FADD + FMUL) |} Malheureusement, ces CPUs historiques n'avaient ni assez d'unités de calcul entières, ni d'unités mémoire, pour remplir la quadruple émission. Environ la moitié des instructions d'un programme correspond à des instructions entières. Pour en profiter, un processeur à N voies doit disposer d'au minimum N/2 ALU entières, l'idéal étant d'aller au-delà. Les CPU superscalaires historiques avaient 2 ALU pour de la quadruple émission, ce qui était très limite. En comparaison, les CPU superscalaires modernes mettent le paquet niveau ALU entière. La plupart des CPU x86 modernes descendent rarement sous les 2/3, la plupart remplit presque la totalité de la largeur du CPU. Pour donner un exemple, la microarchitecture Golden Cove d'Intel est un processeur à 6 voies, avec 5 ALU entières. Pour être précis, un programme informatique exécute environ 40% de son code dans des instructions arithmétiques, un autre 40% dans les accès mémoire, et un le reste pour... le reste. La conséquence est que les processeurs modernes dupliquent leurs unités mémoire. Par exemple, la microarchitecture ''Golden Cove'' d'Intel dispose de trois unités mémoire : deux spécialisées dans les écritures, trois dans les lectures. La microarchitecture ''Gracemont'' utilise la pentuple émission et peut émettre 4 µops entières et 4 µops mémoire. Notez que le ratio est équilibré entre opérations entières et mémoire, avec soit égalité, soit un avantage pour les ALU entières. Une autre régularité est que la largeur totale du processeur peut être occupée par des opérations entières/mémoire. Pour le dire autrement, sur un processeur quadruple émission, il pourra émettre un mix de 4 instructions entière/mémoire, avec zéro opération flottante. Idem avec des processeurs triples ou pentuple émission, voire plus. Si le processeur émet une opération flottante, elle remplace une opération entière. Prenons l'exemple des processeurs AMD K6, sortis en 1997, juste avant les AMD Athlon. Ils avaient 10 unités de calcul, 6 ports d'émission, 4 ports de décodage. Voici exactement comment étaient répartie les unités de calcul sur les ports d'émission. C'était des processeurs quadruple émission, ce qui fait que la moitié de la largeur du processeur sert pour des opérations entières, et l'autre moitié pour les accès mémoire. {|class="wikitable" |- ! Port d'émission n°1 !! Port d'émission n°2 !! Port d'émission n°3 !! Port d'émission n°4 !! Port d'émission n°5 !! Port d'émission n°6 ! |- | ALU entière || ALU entière || FPU || Unité de branchement || Unité LOAD || Unité STORE |- | || Multiplieur || || || || Unité de calcul d'adresse (instruction LEA) |- | || ''Barrel shifter'' || || || || Unité pour les instructions PUSH |} Les microarchitectures K7, K8 et K10, utilisées sur les anciens CPU AMD Athlon, sont un exemple moins représentatif. Ces microarchitectures utilisent le partage des ports d'émission au maximum. C'était des microarchitectures à triple émission, avec six ports d’émission. Il y avait trois ports d'émission pour les opérations flottantes, et trois ports d'émission pour les micro-opérations entières/mémoire. Il y avait trois ALU entières et trois unités mémoire, chaque ALU partageait un port d'émission avec une unité mémoire. Elle pouvait donc émettre soit trois opérations entières, soit trois µops mémoire, soit un mix des deux. En plus des mix avec les opérations flottantes et branchements. {|class="wikitable" |- ! Port d'émission n°1 !! Port d'émission n°2 !! Port d'émission n°3 !! Port d'émission n°4 !! Port d'émission n°5 !! Port d'émission n°6 ! |- | ALU entière || ALU entière || ALU entière || FADD || FMUL || FPU (autres opérations) |- | Multiplieur || || || || || |- | LOAD/STORE || LOAD/STORE || LOAD/STORE || || || |} Les CPU superscalaires larges dupliquent les multiplieurs et/ou les ''barrel shifters'', et même les unités de branchement. La raison est que quand la largeur du CPU augmente, le processeur traite des paquets d'instructions de plus en plus gros. Par exemple, pour un CPU à 10 voies, cela veut dire émettre 10 instructions en même temps. Il est alors plus fréquent d'avoir deux multiplications, deux décalages, deux accès mémoire, deux branchements. Par exemple, la moyenne est d'une multiplication tous les 5 instructions. Les CPU superscalaires à 10 voies ont donc tendance à avoir deux multiplications par paquet d'instruction, en moyenne. Avoir deux multiplieurs est alors un gain en performance clair. Par exemple, la microarchitecture ''Golden Cove'' a deux ''barrel shifters''. C'est une architecture à 6 voies. {|class="wikitable" |- ! Port d'émission n°1 !! Port d'émission n°2 !! Port d'émission n°3 !! Port d'émission n°4 !! Port d'émission n°5 !! Port d'émission n°6 !! Port d'émission n°7 !! Port d'émission n°8 !! Port d'émission n°9 !! Port d'émission n°10 |- | ALU entière || ALU entière || ALU entière || ALU entière || ALU entière || LOAD || LOAD || LOAD || STORE || STORE |- | ''Barrel shifter'' || Multiplieur || Multiplieur || ''Barrel shifter'' || || || || || || |- | Branchements || Diviseur || || Branchements || || || || || || |} ==Les µops mémoire sur les processeurs superscalaires== Plus haut, nous avons volontairement passé sous silence les accès mémoire. La raison à cela est que leur support sur les CPU superscalaire peut se faire de plusieurs manières différentes, qu'il vaut mieux expliquer dans une section dédiée. Les différentes implémentations sont décrites dans la liste suivante, nous allons les voir une par une. * Avec la première méthode, les accès mémoire sont exécutés par le pipeline pour les opérations entières. * Avec la seconde méthode, les accès mémoire ont un pipeline dédié ; c'est l'''émission parallèle des accès mémoire''. * Avec la troisième méthode, il est possible d'émettre plusieurs accès mémoire simultanément : c'est l'''émission multiple des accès mémoire''. Voyons ces trois cas dans le détail. Nous verrons notamment les différentes formes d'émission multiple des accès mémoire. ===Les accès mémoire traités dans le pipeline entier=== Dans le cas le plus simple, les accès mémoire sont traités comme des opérations entières un peu spéciales. L'unité mémoire est sur le même port d'émission que l'ALU entière, ce qui fait que les accès mémoire remplacent des opérations entières. Faire ainsi permet d'utiliser une ALU entière pour les calculs d'adresse, pas besoin de rajouter une unité de calcul d'adresse séparée. Typiquement, un processeur de ce type place l'unité mémoire après l'unité de calcul. Les opérations entières n'utilisent que l'ALU entière, mais pas l'unité mémoire. Les accès mémoire exécutent un calcul d'adresse dans l'ALU entière, puis l'adresse calculée est envoyée à l'unité mémoire. Le tout est illustré ci-dessous, dans le cas d'un processeur historique à double émission entière-flottante. [[File:Processeur non-superscalaire basique 01.png|centre|vignette|upright=1.5|Processeur non-superscalaire basique]] Un exemple de CPU double émission de ce type est celui du Power PC 440, un CPU destiné à l'embarqué. Vu qu'il était destiné à l'embarqué, il n'avait pas de FPU, il n'avait pas besoin de manipuler de nombres flottants. Il n'a donc que des ALU entières, un multiplieur et une unité mémoire. Il dispose de deux ports d'émission. Le premier est connecté à une ALU entière et un circuit multiplieur, le second est relié à l'unité mémoire et une seconde ALU entière. Une organisation simple, mais très efficace ! [[File:PowerPC 440.png|centre|vignette|upright=2|Microarchitecture du PowerPC 440.]] L'implémentation économise une unité de calcul d'adresse, mais n'a pas de cout en performance significatif. Les accès mémoire étant très lents, émettre une µop entière en parallèle d'un accès mémoire ne sert pas à grand-chose. On peut émettre cette µop entière un cycle plus tard, elle sera terminée avant même que l'accès mémoire soit terminé, même si l'accès mémoire se fait dans le cache L1 ou L2. Pour comprendre pourquoi, faisons quelques calculs. Un accès mémoire prend deux-trois cycles quand il tombe dans le cache L1. Et on doit attendre qu'il soit terminé pour en émettre un nouveau. Combien d'instructions arithmétiques peut-on lancer pendant ce temps, sur un CPU triple émission ? Au premier cycle, on peut en émettre 2 (l'instruction mémoire prend une place, donc 3-1), puis 3 au second cycle. Ce qui fait 5 instructions arithmétiques au total. Or, il se trouve qu'il y a une instruction d'accès mémoire pour 5 instructions arithmétiques, environ. Par contre, ce compromis change quand on passe à la quadruple émission, voire au-delà. Dans ce cas, il devient intéressant de pouvoir émettre une nouvelle instruction mémoire à chaque cycle. Ce qui nous amène à la section suivante... ===L'émission parallèle des accès mémoire=== A partir de la quadruple émission, la tendance a été de séparer l'unité mémoire des ALU entières. Cela permet d'émettre des µops mémoire en même temps que des µops entières et/ou flottantes., ce qui porte le nom d''''émission parallèle des µops mémoire''', ou encore ''émission mémoire parallèle''. Par exemple, un processeur quadruple émission peut émettre en même temps deux opérations entières, une opération flottante et un accès mémoire. L'accès mémoire vient en plus des opérations entières, il n'est pas possible de remplacer l'accès mémoire par une opération entière. La technique ne permet pas de faire les calculs d'adresse dans l'ALU entière. Il faut donc utiliser une unité de calcul d'adresse séparée. Elle est intégrée dans l'unité mémoire, qui fait l'interface entre le cache et le banc de registres. Le cout en circuits est très faible, car les unités de calcul d'adresse n'utilisent guère plus qu'un additionneur et un circuit de décalage très simple. L'émission mémoire parallèle est plus facile à implémenter sur les CPU RISC, qui ont des instructions mémoire séparées du reste. Les CPU CISC ne sont pas dans ce cas, ce qui fait que séparer l'unité mémoire du pipeline entier est compliqué. Les CPU superscalaires historiques étaient pour moité des CPU RISC et pour moitié des CPU CISC, ce qui fait qu'ils n'utilisaient pas ou peu l'émission mémoire parallèle. Les premiers processeurs superscalaires se contenaient de l'émission parallèle mémoire, ils ne pouvaient émettre qu'un seul accès mémoire à la fois, l'unité mémoire n'était pas dupliquée. Et ce n'est pas tant un problème que ça, du moins sur les processeurs à double ou triple émission. En effet, il est rare que l'unité mémoire doive faire deux accès simultanés. Notons que le processeur peut exécuter une instruction mémoire en parallèle d'un calcul ou d'un branchement, seuls les accès mémoire simultanés sont impossibles. ===L'émission multiple des accès mémoire avec une LSQ=== Les processeurs superscalaires larges travaillent sur des paquets de 8 à 10 instructions, il est plus fréquent d'avoir plusieurs accès mémoire par paquet. Dans ces conditions, il est utile d'avoir une '''émission multiple des accès mémoire''', à savoir d'émettre plusieurs micro-opérations mémoire en même temps. Les processeurs superscalaires modernes sont capables d'émettre plusieurs lectures/écritures simultanément. Par exemple, ils peuvent émettre une lecture en même temps qu'une écriture, ou plusieurs lectures, ou plusieurs écritures. Il faut noter que selon le processeur, il peut y avoir des restrictions quant aux accès mémoire émis en même temps. Par exemple, certains processeurs peuvent émettre une lecture avec une écriture en même temps, mais pas deux lectures ni deux écritures. Ou encore, ils peuvent émettre deux lectures, une lecture et une écriture, mais pas deux écritures en même temps. Dans la majorité des cas, les processeurs ne permettent pas d'émettre deux écritures en même temps, alors qu'ils supportent plusieurs lectures. Il faut dire que les lectures sont plus fréquentes que les écritures. Les processeurs qui autorisent toutes les combinaisons de lecture/écriture possibles, sont rares. L'émission multiple des accès mémoire s'implémente dans l'unité mémoire. Pour rappel, l'unité mémoire s'interpose entre le cache L1 et le reste du pipeline. Dans le cas le plus simple, elle incorpore une ''load/store queue'' et des unités de calcul d'adresse. L'émission multiple des accès mémoire demande de rendre la ''Load-store queue'' multi-ports, afin de gérer plusieurs micro-opérations mémoire simultanées. Il faut aussi dupliquer les unités de calcul d'adresse reliées à la LSQ, chaque port d'émission mémoire devant avoir sa propre unité de calcul d'adresse. : Dans ce qui suit, nous parlerons d'AGU (''Adress Generation Unit'') pour désigner les unités de calcul d'adresse, et de LSQ pour designer la ''Load-store queue''. [[File:Processeur non-superscalaire basique 02.png|vignette|upright=1|Processeur non-superscalaire basique]] Dupliquer les ports de la LSQ et les AGU est nécessaire. Mais qu'en est-il du cache ? La réponse varie suivant le processeur. Il y a deux grandes méthodes pour implémenter l'émission multiple des accès mémoire. La première méthode ajoute des ports de lecture/écriture au cache de données, pour faire des lectures/écritures simultanées. Il faut alors dupliquer des unités de calcul, rajouter les ports sur le cache, mais aussi rajouter des ports sur la ''load/store queue'', pour qu'elle échange des données/commandes mémoire avec le cache. Le cout en circuits est alors conséquent La seconde duplique les unités de calcul, mais pas les ports d'accès au cache. Les accès mémoire sont alors accumulés dans la ''load/store queue'', qui sont ensuite traités un par un par le cache. Un exemple est celui des processeurs AMD de micro-architecture K7 K8, à savoir les anciens AMD Athlon et AMD Athlon 64, qui pouvaient calculer trois adresses simultanément, mais ne pouvaient faire qu'un seul accès au cache par cycle. Le cout en circuit est modéré : les unités de calcul coutent très peu, les ports de la ''load/store queue'' coutent déjà un peu plus. Quelques processeurs utilisaient des solutions intermédiaires. Un processeur utilisant cette technique est, par exemple, capable d'émettre quatre µops mémoire en même temps, mais avec seulement deux ports sur le cache. Un exemple classique étant les Pentium 1 et 2 d'Intel, dont nous parlerons dans le chapitre suivant. ===L'émission séparée des LOAD et STORE=== De rares processeurs ont des ports d'émission qui peuvent faire indifféremment lecture comme écritures. Mais les processeurs modernes préfèrent séparer ports d'émission pour les lectures et les écritures. Un exemple est celui des processeurs Intel de micro-architecture Nehalem, qui avaient un port de lecture et un port d'écriture tous deux reliés à la ''Load-store queue'', chacun avec leur propre unité de calcul d'adresse. Comme autre exemple, les processeurs skylake ont une LSQ avec deux ports de lecture et un port d'écriture, ce qui permet d'émettre deux lectures en même temps qu'une écriture. Dans cette section, nous allons étudier le cas avec des ports séparés pour les lectures et écritures. En général, les processeurs ont un peu plus de ports de lectures que d'écritures. Une raison est qu'un programme a, en moyenne, un tout petit peu plus de lectures que d'écritures. Mais la différence n'est pas énorme, c'est un léger déséquilibre. Une seconde raison est que l'exécution dans le désordre marche bien mieux quand les lectures ont lieu le plus tôt possible. Donc mieux vaut privilégier les lectures à l'émission, pour qu'elles se fassent un peu en avance. En pratique, la différence n'est pas très marquée. Il est courant d'avoir un port de lecture et un d'écriture, les CPU superscalaires très larges ont souvent deux ports de lecture et deux d'écriture. A la rigueur, quelques CPU ont trois ports de lecture et deux d'écriture. Le cas le plus simple est celui où on peut émettre une lecture et une écriture en même temps. Il s'implémente facilement si la LSQ est en réalité une simple ''Store queue'', à savoir qu'elle met en avance les écritures uniquement. Pour rappel, les écritures sont mises en attente dans la ''Store queue'', mais les lectures sont exécutées immédiatement. Une fois l'adresse à lire connue, la lecture consulte la ''Store queue'' pour détecter les dépendances de données, puis s'exécute. La donnée à lire est lue soit depuis la ''Store queue'', soit depuis le cache. [[File:Emission LOAD - Store séparée.png|vignette|upright=1|Émission LOAD - Store séparée]] L'implémentation de l'émission multiple est alors très simple. La ''Store queue'' a déjà deux ports, avec un pour les lectures et un autre pour les écritures. Idem pour le cache, qui a naturellement un port de lecture et un d'écriture. Émettre une lecture et une écriture simultanément ne demande donc pas de modification digne de ce nom. La ''Store queue'' doit juste supporter une lecture et une écriture simultanée. Ajouter l'émission d'une seconde lecture n'est pas compliquée : il faut juste rajouter un second port de lecture sur le cache et la ''Store queue''. Pour rappel, l'unité mémoire est précédée par une structure qui met en attente les micro-opérations mémoire avant leur émission finale. La structure en question varie suivant l'implémentation, mais un cas particulier se marie bien avec l'émission multiple des µops mémoire. Il s'agit du cas avec une file de µops séparée pour les lectures et une autre pour les écritures, appelées respectivement la ''file de µops LOAD'' et la ''file de µops STORE''. Il est alors possible d'émettre une lecture et une écriture lors du même cycle d'horloge. Le port de lecture alimente une unité de calcul d'adresse dédiée, directement reliée au cache. Le port d'écriture du cache alimente une unité de calcul, qui est suivie par une file d'écriture, elle-même reliée au cache. Les processeurs AMD de micro-architecture K6 utilisaient cette technique. [[File:Double émission avec le Load Ordering, Store Ordering.jpg|centre|vignette|upright=2.5|Double émission avec le Load Ordering, Store Ordering]] ==Les CPU superscalaire à exécution dans le désordre== La superscalarité modifie en profondeur le processeur. Et cela dépasse le cadre des unités vues auparavant. Dans cette section, nous allons voir comment les autres étages du pipeline sont modifiées sur un CPU superscalaire. ===L'unité de renommage superscalaire=== Avec la superscalarité, l'unité de renommage de registre doit gérer le cas où des instructions consécutives ont des dépendances de registre. Par exemple, prenons un processeur à double émission, qui renomme deux instructions consécutives. Si elles ont une dépendance de registre, le renommage de la seconde instruction dépend du renommage de la première. La première doit être renommée et le résultat du renommage est utilisé pour renommer la seconde, ce qui empêche d'utiliser deux unités de renommage séparées. Pour cela, elle renomme les registres sans tenir compte des dépendances, pour ensuite corriger le résultat. [[File:Unité de renommage superscalaire.png|centre|vignette|upright=2|Unité de renommage superscalaire.]] Seules les dépendances lecture-après-écriture doivent être détectées, les autres étant supprimées par le renommage de registres. Repérer ce genre de dépendances se fait assez simplement : il suffit de regarder si un registre de destination d'une instruction est un opérande d'une instruction suivante. [[File:Détection des dépendances sur un processeur superscalaire.png|centre|vignette|upright=2|Détection des dépendances sur un processeur superscalaire.]] Ensuite, il faut corriger le résultat du renommage en fonction des dépendances. Si une instruction n'a pas de dépendance avec une autre, on la laisse telle quelle. Dans le cas contraire, un registre opérande sera identique avec le registre de destination d'une instruction précédente. Dans ce cas, le registre opérande n'est pas le bon après renommage : on doit le remplacer par le registre de destination de l'instruction avec laquelle il y a dépendance. Cela se fait simplement en utilisant un multiplexeur dont les entrées sont reliées à l'unité de détection des dépendances. On doit faire ce replacement pour chaque registre opérande. [[File:Correction des dépendances sur un processeur superscalaire.png|centre|vignette|upright=2|Correction des dépendances sur un processeur superscalaire.]] ===Les unités d'exécution dans le désordre=== Réduire le nombre de ports d'émission permet de garder des fenêtres d'instruction et un ROB simples, rapides et économes en énergie. Et mine de rien, c'est là un compromis important : à quoi bon augmenter le nombre de ports d'émission si c'est pour que ce soit compensé par une fréquence plus faible ? Par contre, réduire le nombre de ports augmente la ''port contention'', surtout si on a mal réparti les ALU. Il faut donc trouver un compromis entre réduire le nombre de ports d'émission d'un côté, réduire la ''port contention'' de l'autre. Le ROB doit aussi être modifié pour émettre et terminer plusieurs instructions en même temps, en ajoutant des ports de lecture/écriture. Pour un CPU superscalaire peut émettre 8 µops à la fois, on se dit que le ROB peut retirer 8 µops par cycle maximum. Et c'est très souvent le cas en pratique. Cependant, certains processeurs ont un ROB qui peut retirer le double ! Un exemple est celui des CPU de micro-architecture Skymont, qui peuvent émettre 8 µops par cycle, mais en retirer 16. Quelques CPU de marque AMD étaient aussi dans ce cas. L'avantage est que cela permet de réduire la taille du ROB, du banc de registre et de quelques autres structures matérielles liées à l'exécution dans le désordre. Le gain sur-compense le cout en transistor nécessaire pour retirer 16 µops par cycle. Un détail est que le nombre de ports de lecture n'est pas le même selon que le processeur utilisé des stations de réservation ou une fenêtre d’instruction. Rappelons la terminologie utilisée dans ce cours. Les fenêtres d'instruction se contentent de mémoriser la micro-opération à émettre et quelques bits pour la disponibilité des opérandes. Par contre, les stations de réservations mémorisent aussi les opérandes des instructions. Et cette différence a une influence le nombre de ports de lecture du banc de registre. Supposons que toutes les instructions sont dyadiques, à deux opérandes. Comptons le nombre de ports nécessaires pour alimenter les unités de calcul, sans utiliser d'arbitre de banc de registre. Avec une fenêtre d'instruction, il faut le double du nombre de ports de ''dispatch''. Mais avec des stations de réservation, il faut le double des ports de ''schedule''. La raison est que les registres sont lus après émission avec une fenêtre d'instruction, avant avec des stations de réservation (sous-entendu, après le décodage). or, comme dit plus haut, les processeurs superscalaires larges ont souvent plus de ports de ''schedule'' que de ''dispatch''. ==Les optimisations des processeurs superscalaires larges== Les processeurs superscalaires larges ont des optimisations spécifiques, qui n'ont pas de sens sur les processeurs superscalaires étroits. Dans les grandes lignes, elles se classent en deux types. Les premières permettent de charger/décoder plus de 5-10 instructions simultanément. Le problème est alors que les branchements viennent mettre leur grain de sel et qu'il faut en tenir compte. Les secondes corrigent des problèmes qui apparaissent quand un CPU intègre plus de 5 à 10 unités de calcul. ===L'étape de chargement d'un CPU superscalaire large=== Charger plusieurs instructions pose de nombreux problèmes sur les processeurs superscalaires larges, qui émettent de 5 à 10 instructions simultanées. Pour rappel, la stratégie basique pour charger N instructions est de charger un bloc de N instructions consécutives. Les instructions après un branchement ne sont pas décodées ni exécutées, ce qui entraine une petite baisse de performance. Pour un N petit, la perte de performance est mineure. Mais pour un N large, elle devient suffisamment importante pour que la conception du CPU doit alors être repensée de fond en comble. Les analyses statistiques disent qu'il y a environ un branchement tous les 4 à 6 instructions. Un CPU a donc tendance à décoder des paquets de 3 à 5 instructions consécutives, avant qu'un branchement casse la dynamique. Or, cela pose des problèmes pour utiliser plusieurs décodeurs. L'implémentation basique, qui charge N instructions consécutives pour alimenter N décodeurs, fonctionne donc jusqu'à 4/6 décodeurs. Au-delà de 5 instructions, un CPU superscalaire doit être adapté pour gérer les branchements dans les paquets d'instruction qu'il émet. Le nombre de 5 instructions ne sort donc pas de nulle part. La première solution utilise un cache de µops, avec un ''front-end'' capable de charger/décoder 4/5 instructions à la fois, pas plus. L'idée est que la majorité des instructions proviennent du cache de µops (plus de 80%), ce qui fait qu'on peut émettre environ 10 instructions à partir du cache de µops, pas besoin d'avoir beaucoup de décodeurs. Les performances sont cependant dégradées lors d'un défaut de cache de µops. Les instructions doivent alors être chargées depuis le cache d'instruction, par paquets de 4-5, puis sont décodées par 4-5 décodeurs, ce qui ne permet pas d’alimenter les unités de calcul à plein régime. La solution précédente joue sur la distinction entre pots de décodage et d'émission. Elle permet d'émettre un grand nombre de µops sans pour autant charger un grand nombre d'instructions en même temps. En quelque sorte, ce n'est qu'une méthode de contournement, le CPU superscalaire reste de type étroit. Mais il est aussi possible d'utiliser un '''''front-end large''''', capable de charger une bonne dizaine d'instructions en même temps. La seule difficulté est de tenir compte de la présence de branchements. Pour rappel, avec un ''front-end'' étroit, les instructions situées après le premier branchement pris sont annulées, elles ne sont pas décodées ni exécutées. [[File:Fetch sur un processeur superscalaire avec prediction de branchements.png|centre|vignette|upright=2|Fetch sur un processeur superscalaire avec prédiction de branchements.]] Un ''front-end'' large charge les instructions de destination du branchement et les placent à sa suite. Ils chargent deux blocs à la fois et les fusionnent en un seul qui ne contient que les instructions présumées utiles. [[File:Cache d'instructions autoaligné.png|centre|vignette|upright=2|Cache d'instructions auto-aligné.]] Mais cela demande de charger deux blocs de mémoire en une fois, ce qui demande un cache d'instruction multiports. Il faut aussi ajouter un circuit pour assembler plusieurs morceaux de blocs en un seul : le fusionneur (''merger''). En pratique, cette solution demande d'ajouter un second port de lecture au cache, pour un cout en hardware important, avec un impact négatif sur le temps d'accès au cache. Et il faut rajouter le fusionneur, qui prend un étage de pipeline à lui tout seul. Et encore, cette solution ne gère pas le cas où un bloc contient plusieurs branchements ! [[File:Implémentation d'un cache d'instructions autoaligné.png|centre|vignette|upright=2|Implémentation d'un cache d'instructions auto-aligné.]] Les exemples précédents partent du principe qu'il n'y a qu'un seul branchement dans un bloc. Mais quand les blocs deviennent assez longs, il est fréquent d'avoir plusieurs branchements dans un bloc. Et il est même possible d'avoir plusieurs branchements pris. Dans un bloc de 8-10 instructions, ce n'est pas rare, et les CPU superscalaires à 8-10 voies sont la règle dans les CPU des années 2020. Pour gérer ce cas, il faut ajouter des ports supplémentaires au cache d'instruction et adapter le fusionneur, pour qu'il puisse fusionner trois ou quatre blocs. De plus, il faut utiliser une unité de prédiction de branchement capable de ''prédire plusieurs branchements par cycle''. ===Le décodage superscalaire large=== Le décodage des instructions n'est pas censé dépendre de l'implémentation du chargement. En théorie, une fois plusieurs blocs d'instruction fusionnés, on peut décoder les instructions en parallèle. Cependant, certains processeurs effectuent le décodage en tenant compte de la manière dont les branchements découpent les blocs. Les processeurs Atom d'Intel utilisent une solution de ce type, qui couple prédiction de branchement et décodage parallèle. Ils chargent/décodent des paquets de 3 instructions consécutives, chaque paquet étant séparé du précédent par un branchement. Pour cela, en plus d'avoir un fusionneur adapté, ils regroupent les décodeurs en ''clusters'', chacun regroupant 3 décodeurs, et pouvant décoder trois instructions consécutives. Les micro-architectures Tremont et Crestmont avaient deux ''clusters'' (6 décodeurs au total), la micro-architecture Skymont en intègre trois (9 décodeurs au total). Avec deux ''clusters'', le fonctionnement est le suivant. Si le code est composé d'une suite d'instruction sans branchements, seul le premier ''cluster'' est utilisé, limitant le décodage à trois instructions par cycle. Par contre, si l'une des trois instructions décodée est un branchement, alors le second ''cluster'' décode les trois instructions situées après ce branchement, à partir de l'adresse de destination. Avec un troisième ''cluster'', c'est la même chose, sauf qu'il faut qu'il y ait un branchement dans les trois instructions décodées par le second ''cluster''. Mais comment faire pour savoir quels décodeurs activés, quels ''clusters'' sont actifs ? C'est la prédiction de branchement qui commande. Avec deux ''clusters'', elle détermine si un branchement est présent dans les trois instructions à charger, et quelle est son adresse de destination. Sans branchement, elle prévient les décodeurs et les trois instructions chargées sont décodées dans un ''cluster'' libre. Si elle prédit un branchement, elle charge les instructions adéquates, et active les deux ''clusters''. Avec trois branchements, c'est le même mécanisme, sauf que l'unité de prédiction de branchement doit être capable de prédire deux branchements par cycle. Il faut noter que les décodeurs d'un ''cluster'' sont des décodeurs simples, sans microcode. Sur les processeurs Tremont et Crestmont, le microcode était unique et était partagé entre tous les ''clusters''. Un seul ''cluster'' pouvait décoder une instruction complexe avec le microcode, l'autre devait se limiter à des instructions simples uniquement. Depuis Skymont, le microcode est partiellement dupliqué, avec un microcode par ''cluster''. Le cout en hardware peut paraitre prohibitif, pour un gain en performance faible. Sauf que c'est oublier que ce sont des processeurs Atom, sur lesquels beaucoup d'instructions sont microcodées. Les processeurs Atom ont un budget en transistor limité, ce qui fait qu'il est préférable pour eux d'implémenter des instructions en microcode que d'utiliser des unités de calcul dédiées ou des circuits de décodage complexes. Pour limiter la casse, le microcode n'est que partiellement dupliqué. Seul le microcode pour les instructions fréquente est dupliqué. Il y a un microcode secondaire pour certaines instructions, qui est lui unique et partagé par tous les ''clusters''. ===Le contournement sur les processeurs superscalaires larges=== Pour rappel, la technique du contournement (''register bypass'') permet au résultat d'une instruction d'être immédiatement utilisable en sortie de l'ALU, avant même d'être enregistré dans les registres. Implémenter la technique du contournement demande d'utiliser des multiplexeurs pour relier la sortie de l'unité de calcul sur son entrée si besoin. Il faut aussi des comparateurs pour détecter des dépendances de données. [[File:Pipeline Bypass.png|centre|vignette|upright=1|Pipeline Bypass]] La sortie de chaque ALU doit être reliée aux entrées de toutes les autres, avec les comparateurs qui vont avec. Sur les processeurs ayant plusieurs d'unités de calculs, cela demande beaucoup d’interconnexions et de comparateurs. Pour N unités de calcul, cela demande 2 * N² interconnexions, implémentées avec 2N multiplexeurs de N entrées chacun. Si c'est faisable pour 2 ou 3 ALUs, la solution est impraticable au-delà. Non seulement ce serait un enfer à câbler, mais les fils seraient tellement longs que la fréquence du processeur en prendrait un coup. Notez que cela vaut pour les processeurs superscalaires, mais aussi pour tout processeur avec beaucoup d'unités de calcul. En théorie, un CPU à exécution dans le désordre, non-superscalaire, mais avec beaucoup d'unités de calcul, fait face au même problème. En pratique, avoir une dizaine d'ALU implique processeur superscalaire à exécution dans le désordre. D'où le fait qu'on parle du problème maintenant. La seule solution praticable est de ne pas relier toutes les unités de calcul ensemble. À la place, on préfère regrouper les unités de calcul dans différents '''agglomérats''' ('''cluster'''). Le contournement est alors possible entre les unités d'un même agglomérat, mais pas entre agglomérats différents. Évidemment, certaines possibilités de contournement sont perdues, mais c'est compensé par un gain en fréquence et un cout en circuit/fils réduit. C'est un bon compromis, ce qui explique que presque tous les processeurs modernes l'utilisent. Les rares exceptions sont les processeurs POWER 4 et POWER 5, qui ont préféré se passer de contournement pour garder un processeur très simple et une fréquence élevée. ===Le banc de registre des processeurs superscalaires larges=== Pour émettre deux opérations entières simultanément, il faut lire deux fois plus d'opérandes. Et il faut aussi écrire deux résultats dans les registres, en même temps. Pour cela, on double les ports du banc de registre. En général, il faut au maximum 2 ports de lecture et un port d'écriture par unité de calcul. Le problème, c'est que plus un banc de registres a de ports, plus il utilise de circuits, est compliqué à concevoir, consomme de courant et chauffe. Avec plusieurs dizaines d'unités de calcul différentes, ajouter autant de ports impacterait la fréquence du banc de registres et réduirait les performances, voire est totalement impraticable à cause des contraintes thermiques. Les processeurs superscalaires larges doivent donc trouver des solutions. La première solution utilise un banc de registre unique, mais n'utilise pas autant de ports que le pire des cas le demanderait. Le processeur détecte quand il n'y a pas assez de ports pour servir toutes les micro-opérations. Quand ça arrive, l'unité d'émission émet assez de micro-opérations pour exploiter les ports disponibles, mais met en attente les micro-opérations en trop, le temps que les ports se libèrent. Pour cela, le banc de registre est géré par un circuit d'arbitrage spécialisé, intégré à l'unité d'émission, l’'''arbitre du banc de registres''' (''register file arbiter''). Une autre solution est de dupliquer le banc de registres en plusieurs exemplaires qui contiennent exactement les mêmes données, mais avec moins de ports de lecture/écriture. Un exemple est celui des processeurs POWER, Alpha 21264 et Alpha 21464. Sur ces processeurs, le banc de registre est dupliqué en plusieurs exemplaires, qui contiennent exactement les mêmes données. Les écritures dans les registres sont envoyées à tous les bancs de registres, afin de garantir que leur contenu est identique. Chaque exemplaire a exactement deux ports de lecture. La conception du processeur est simplifiée, que ce soit au niveau du câblage, que de la conception des bancs de registres. Une dernière solution découpe le banc de registres en plusieurs bancs de registres plus petits, avec un réseau d'interconnexions pour échanger des données entre bancs de registres. Dans la plupart des cas, cette séparation est invisible du point de vue de l'assembleur et du langage machine. Le processeur se charge de transférer les données entre bancs de registres suivant les besoins. Sur d'autres processeurs, les transferts de données se font via une instruction spéciale, souvent appelée ''COPY''. [[File:Banc de registres distribué.png|centre|vignette|upright=2|Banc de registres distribué.]] Sur de certains processeurs, un branchement est exécuté par une unité de calcul spécialisée. Or les registres à lire pour déterminer l'adresse de destination du branchement ne sont pas forcément dans le même agglomérat que cette unité de calcul. Pour éviter cela, certains processeurs disposent d'une unité de calcul des branchements dans chaque agglomérat. Dans les cas où plusieurs unités veulent modifier le ''program counter'' en même temps, un système de contrôle général décide quelle unité a la priorité sur les autres. Mais d'autres processeurs fonctionnent autrement : seul un agglomérat possède une unité de branchement, qui peut recevoir des résultats de tests de toutes les autres unités de calcul, quel que soit l’agglomérat. ==La macro-fusion : une optimisation du décodage parallèle== Les processeurs superscalaires supportent la technique dite de '''macro-fusion''', qui permet de fusionner deux-trois instructions consécutives en une seule micro-opération. Par exemple, il est possible fusionner une instruction de test et une instruction de saut en une seule micro-opération de branchement. Il s'agit là de l'utilisation la plus importante de la macro-fusion sur les processeurs x86 modernes. La macro-fusion est utilisée pour fusionner un test et un saut en un branchement unique. L'optimisation est présente sur la plupart des CPU superscalaires modernes. Les CPU x86 l'implémentent, mais aussi quelques CPU ARM et les processeurs Apple de série M (M1 à M5, et potentiellement plus quand ils seront sortis). La fusion des instructions se fait lors du décodage des instructions, grâce à la coopération des décodeurs. Elle marche systématiquement si l'instruction de test et le saut sont consécutives, qu'elles forment bien une paire d'instruction. Par contre, insérer une instruction entre les deux empêche la macro-fusion de faire son travail, sauf si l'instruction insérée est un NOP. Les processeurs ARM64 avaient une forme spécifique de macro-fusion, utilisée pour un cas très précis : charger une constante immédiate de 32 ou 64 bits, dans un registre 64 bits. Les CPU ARM 64 bits utilisent des instructions codées sur 32 bits, qui ne peuvent encoder que des constantes immédiates de 16 bits. Pour charger une constante de 32/64 bits, il faut s'y prendre en plusieurs fois. Une première instruction MOVZ charge les 16 premiers bits, puis 2 à 4 instructions MOVK remplissent le reste. MOVZ met à zéro les 48 bits de poids fort, MOVK permet de sélectionner le doublet à modifier dans le registre (on modifie 16 bits, dont on choisit la place, le reste du registre est laissé tel quel). ARM permet de fusionner cette suite d'instructions en une seule µop MOV avec une constante immédiate. On parle alors de '''''MOVZ + MOVK fusion'''''. Une dernière utilisation, [https://www2.eecs.berkeley.edu/Pubs/TechRpts/2016/EECS-2016-130.pdf proposées pour le jeu d'instruction RISC-V], fusionnait un calcul d'adresse et une instruction LOAD/STORE en une seule instruction d'accès mémoire. Elle a été envisagée à une époque où le jeu d'instruction RISC-V ne gérait pas les modes d'adressage base + indice. L'idée était d'avoir une unité mémoire qui gérait le mode d'adressage base + indice et de fusionner des instructions pour l'exploiter. Mais de nos jours, le jeu d'instruction RISC-V intègre le mode d'adressage base + indice, ce qui fait que la macro-fusion n'a aucun intérêt pour ce cas précis. La macro-fusion a de nombreuses limitations en pratique. En théorie, on pourrait l'utiliser au-delà des deux situations mentionnées plus haut. Par exemple, pour fusionner une addition et une multiplication en une seule instruction MAD. Mais l'intérêt est assez faible. Notons que la macro-fusion ne permet que de fusionner des paires d'instructions. Aller au-delà demanderait un cout en circuit trop important. Déjà que tester deux paires utilise pas mal de comparateurs... ==Le ''stack engine'' : une optimisation de la pile d'appel== Les processeurs modernes intègrent une optimisation liée au pointeur de pile. Pour rappel, sur les architectures modernes, le pointeur de pile est un registre utilisé pour gérer la pile d'appel, précisément pour savoir où se trouve le sommet de la pile. Il est manipulé par les instructions CALL, RET, PUSH et POP, mais aussi par les instructions d'addition/soustraction pour gérer des cadres de pile de taille variable. De plus, il peut servir d'opérande pour des instructions LOAD/STORE, afin de lire/écrire des variables locales, les arguments d'une fonction, et autres. C'est donc un registre général adressable, intégré au banc de registre, altéré par l'unité de calcul entière. L'incrémentation/décrémentation du pointeur de pile passe donc par l'unité de calcul, lors des instructions CALL, RET, PUSH et POP. Mais, l'optimisation que nous allons voir permet d'incrémenter/décrémenter le pointeur de pile sans passer par l'ALU, ou presque. L'idée est de s'inspirer des architectures avec une pile d'adresse de retour, qui intègrent le pointeur de pile dans le séquenceur et l'incrémentent avec un incrémenteur dédié. ===Le ''stack engine''=== L'optimisation que nous allons voir utilise un '''''stack engine''''' intégré à l'unité de contrôle, au séquenceur. Le processeur contient toujours un pointeur de pile dans le banc de registre, cela ne change pas. Par contre, il n'est pas incrémenté/décrémenté à chaque instruction CALL, RET, PUSH, POP. Un compteur intégré au séquenceur est incrémenté à la place, nous l’appellerons le '''compteur delta'''. La vraie valeur du pointeur de pile s'obtient en additionnant le compteur delta avec le registre dans le banc de registre. Précisons que si le compteur delta vaut zéro, la vraie valeur est dans le banc de registre et peut s'utiliser telle quelle. Lorsqu'une instruction ADD/SUB/LOAD/STORE utilise le pointeur de pile comme opérande, elle a besoin de la vraie valeur. Si elle n'est pas dans le banc de registre, le séquenceur déclenche l'addition compteur-registre pour calculer la vraie valeur. Finalement, le banc de registre contient alors la bonne valeur et l'instruction peut s'exécuter sans encombre. L'idée est que le pointeur de pile est généralement altéré par une série d'instruction PUSH/POP consécutives, puis des instructions LOAD/STORE/ADD/SUB utilisent le pointeur de pile final comme opérande. En clair, une bonne partie des incrémentations/décrémentation est accumulée dans le compteur delta, puis la vraie valeur est calculée une fois pour toutes et est utilisée comme opérande. On accumule un delta dans le compteur delta, et ce compteur delta est additionné quand nécessaire. Précisons que le compteur delta est placé juste après le décodeur d'instruction, avant même le cache de micro-opération, l'unité de renommage et l'unité d'émission. Ainsi, les incrémentations/décrémentations du pointeur de pile disparaissent dès l'unité de décodage. Elles ne prennent pas de place dans le cache de micro-opération, ni dans la fenêtre d'instruction, ni dans la suite du pipeline. De nombreuses ressources sont économisées dans le ''front-end''. Mais utiliser un ''stack engine'' a aussi de nombreux avantages au niveau du chemin de données/''back-end''. Déjà, sur les processeurs à exécution dans le désordre, cela libère une unité de calcul qui peut être utilisée pour faire d'autres calculs. Ensuite, le compteur delta mémorise un delta assez court, de 8 bits sur le processeur Pentium M, un peu plus pour les suivants. L'incrémentation se fait donc via un incrémenteur 8 bits, pas une grosse ALU 32/64 bits. Il y a un gain en termes de consommation d'énergie, un incrémenteur 8 bits étant moins gourmand qu'une grosse ALU 32/64 bits. ===Les points de synchronisation du delta=== La technique ne fonctionne que si la vraie valeur du pointeur de pile est calculée au bon moment, avant chaque utilisation pertinente. Il y a donc des '''points de synchronisation''' qui forcent le calcul de la vraie valeur. Plus haut, nous avions dit que c'était à chaque fois qu'une instruction adresse le pointeur de pile explicitement, qui l'utilise comme opérande. Les instructions CALL, RET, PUSH et POP ne sont pas concernées par elles utilisent le pointeur de pile de manière implicite et ne font que l'incrémenter/décrémenter. Mais dans les faits, c'est plus compliqué. D'autres situations peuvent forcer une synchronisation, notamment un débordement du compteur delta. Le compteur delta est généralement un compteur de 8 bits, ce qui fait qu'il peut déborder. En cas de débordement du compteur, le séquenceur déclenche le calcul de la vraie valeur, puis réinitialise le compteur delta. La vraie valeur est donc calculée en avance dans ce cas précis. Précisons qu'un compteur delta de 8 bits permet de gérer environ 30 instructions PUSH/POP consécutives, ce qui rend les débordements de compteur delta assez peu fréquent. À noter que si le compteur delta vaut zéro, il n'y a pas besoin de calculer la vraie valeur, le séquenceur prend cette situation en compte. Un autre point de synchronisation est celui des interruptions et exceptions matérielles. Il faut que le compteur delta soit sauvegardé lors d'une interruption et restauré quand elle se termine. Idem lors d'une commutation de contexte, quand on passe d'un programme à un autre. Pour cela, le processeur peut déclencher le calcul de la vraie valeur lors d'une interruption, avant de sauvegarder les registres. Pour cela, le processeur intègre un mécanisme de sauvegarde automatique, qui mémorise la valeur de ce compteur dans le tampon de réordonnancement, pour forcer le calcul de la vraie valeur en cas de problème. La technique du ''stack engine'' est apparue sur les processeurs Pentium M d'Intel et les processeurs K10 d'AMD, et a été conservée sur tous les modèles ultérieurs. L'implémentation est cependant différente selon les processeurs, bien qu'on n'en connaisse pas les détails et que l'on doive se contenter des résultats de micro-benchmarks et des détails fournit par Intel et AMD. Selon certaines sources, dont les manuels d'optimisation d'Agner Fog, les processeurs AMD auraient moins de points de synchronisation que les processeurs Intel. De plus, leur ''stack engine'' serait placé plus loin que prévu dans le pipeline, après la file de micro-opération. ==La micro-fusion et la délamination== La '''micro-fusion''' est une optimisation qui retarde le décodage réel de certaines instructions assez loin dans le pipeline. Elle concerne surtout les instructions ''load-op'', mais pas que. D'autres instructions mémoire peuvent subir cette optimisation. L'essentiel est que, sans micro-fusion, ces instructions sont décodées en plusieurs micro-opérations. La micro-fusion retarde le décodage final de ces instructions, qui se fait assez tard dans le pipeline. Pour comprendre l'intérêt, nous allons devoir faire quelques explications. L'optimisation en question fait le décodage en deux temps. Prenons l'exemple d'une opération ''load-op'', qui est censée être décodée en deux micro-opérations : une lecture, puis une opération. Le décodage produit une '''macro-opération''' (terme d'Intel), qui est propagée dans le pipeline, pour être scindée en deux micro-opérations un peu avant les ALU et l'unité mémoire. La macro-opération est techniquement une micro-opération comme une autre, sauf que ce n'est pas la micro-opération finale. L'unité d'émission découpe cette macro-opération en deux micro-opérations, qui sont émises l'une après l'autre. Pour résumer, les instructions ''load-op'' ne sont complétement découpées en micro-opération lors de l'émission, pas lors du décodage. L'optimisation impacte ce qui se situe entre le décodeur d'instruction et l'unité d'émission : l'unité de renommage, la file de micro-opérations, le ''Loop Stream Detector'' et/ou le cache de micro-opération. L'intérêt de l'optimisation est que l'on fusionne deux micro-opérations en une seule macro-opération. Le cache de micro-opération et la file de micro-opération peuvent mémoriser plus de micro-opérations, vu que certaines sont fusionnées. Quant à l'unité de renommage, elle renomme une seule macro-opération au lieu de renommer deux micro-opérations, ce qui augmente le débit du renommage de registre. Par contre, cela complique un peu le travail de l'unité d'émission, qui doit découper les instructions ''load-op'' en deux et émettre les micro-opérations l'une après l'autre. Il faut noter qu'il n'y a pas de fusion proprement dite. Le décodeur ne décode pas deux micro-opérations séparées, avant de les fusionner. En réalité, il génère directement une macro-opération, qui correspond à deux micro-opérations, que ce soit plus tard dans le pipeline ou sur une autre architecture. D'ailleurs, la micro-fusion a lieu à l'intérieur d'une instruction, qui est censée être traduite en plusieurs micro-opérations. Et encore : pas toutes. La conséquence est que la technique n'a de sens que sur les processeurs CISC, avec des instructions assez complexes pour être décodées en plusieurs micro-opérations. En pratique, seuls les CPU x86 sont concernés. ===Les domaines de fusion=== La micro-fusion n'est pas spécifique aux processeurs superscalaires. En théorie, on peut l'implémenter sur un processeur non-superscalaire à émission dans l'ordre. Cependant, je ne connais aucun processeur à émission dans l'ordre qui implémente la micro-fusion, même si son implémentation est en théorie possible sur de tels processeurs. La micro-fusion est implémentée différemment selon que l'émission se fasse dans l'ordre, dans le désordre, avec une fenêtre d'instruction, avec des stations de réservation, etc. J'ai dit plus haut que les micro-opérations sont générées lors de l'émission, mais le moment exact varie suivant le processeur. Pour simplifier le tout, nous parlerons de '''scission''' quand le processeur traduit une macro-opération en deux/trois micro-opérations. Le moment où a lieu la scission est théoriquement dans l'unité d'émission, mais celle-ci prend plusieurs formes selon que le processeur utilise l'émission dans l'ordre, dans le désordre, avec une fenêtre d'instruction, avec des stations de réservation, etc. Les processeurs à exécution dans l'ordre sont le cas le plus simple. L'unité d'émission est alors en charge du découpage des macro-opérations en micro-opérations. La file de micro-opération ou le cache de micro-opérations mémorisent des macro-opérations. À l'opposé, le tampon de ré-ordonnancement mémorise lui des micro-opérations. Le reste du processeur n'est pas affecté. Sur les processeurs à exécution dans le désordre, les différences sont nombreuses. Le tampon de ré-ordonnancement peut mémoriser soit les micro-opérations finales, soit des macro-opérations. Il en est de même avec la fenêtre d'instruction et/ou les stations de réservation, qu'elles soient centralisées ou décentralisées. Tout dépend de comment se fait l'implémentation. Une implémentation simple effectue la scission des macro-opérations dans l'unité d'émission, comme sur les processeurs à exécution dans l'ordre. Sur de nombreux processeurs, l'unité d'émission et de renommage sont simplement fusionnées, ce qui fait que la scission a lieu juste après le renommage de registres. En conséquence, le tampon de re-ordonnancement et les fenêtres d'instruction ne voient que des micro-opérations. Les macro-opérations disparaissent juste avant. Cette implémentation est préférée avec une fenêtre d'instruction centralisée, car on voit mal comment scinder une macro-opération juste avant l'ALU. Une autre implémentation fait la scission en sortie de la fenêtre d'instruction ou des stations de réservation, qu'elles soient centralisées ou décentralisées. Dans ce cas, le tampon de re-ordonnancement fait de même. L'avantage est que le tampon de ré-ordonnancement stocke des macro-opérations, ce qui augmente sa taille effective. Par exemple, il utilise une seule entrée pour une instruction ''load-op'', au lieu de deux sans micro-fusion de ce type. ===La délamination=== La scission peut parfois avoir lieu plus tôt, dans des circonstances très précises. Concrètement, cela ne concerne que quelques micro-architectures d'Intel, pour des instructions très précises. La raison est les limitations du format des micro-opérations, pas une histoire d'optimisation. Le processeur aimerait pouvoir faire la micro-fusion, mais ne peut pas à cause de ces limitations. Une telle situation cause alors une '''délamination''' : les micro-opérations sont scindées précocement. Par exemple, sur la micro-architecture SandyBridge d'Intel, les opérations ''load-op'' sont fusionnées, sauf si elles utilisent l'adressage indicé. Les micro-opérations de ce processeur ne peuvent avoir que deux opérandes. Trois opérandes est de trop, et les opérations ''load-op'' en adressage indicé dépassent cette limite. Dans ce cas, les micro-opérations sont fusionnées en sortie du décodeur, mais sont scindés avant d’entrer dans la file de micro-opération. L'avantage n'est pas évident, vu que l'unité de renommage suit de près le décodeur d'instruction. Il est que le cache de micro-opérations mémorise des macro-opérations, pas deux micro-opérations. ===La micro-fusion des processeurs Intel et AMD=== La micro-fusion est apparue sur le Pentium M d'Intel, et a été conservée telle quelle sur les processeurs Intel suivants. Les processeurs AMD supportent la micro-fusion depuis au moins la micro-architecture AMD K8. Mieux que ça, la délamination n'était pas utilisée car les micro-opérations acceptaient plus de 2 opérandes. Que ce soit chez AMD ou Intel, elle concerne deux types d'instructions : les écritures mémoire et les instructions ''load-op''. Sur le Pentium M, il y avait quelques limitations, l'optimisation se limitant par exemple aux registres entiers et flottants, mais pas aux registres MMX, mais pas les registres SSE (nous verrons ces registres dans quelques chapitres). Mais les processeurs modernes n'ont plus cette limitation. La micro-fusion des CPU Intel concerne deux types d'instructions : les écritures mémoire et les instructions ''load-op''. Pour les écritures, la situation est plus complexe que pour les instructions ''load-op''. Sur les processeurs Intel précédents, les écritures étaient réalisées dans deux unités séparées : le calcul d'adresse dans une unité de calcul, l'écriture proprement dite dans l'unité mémoire. Elles prenaient donc deux micro-opérations. Le processeur a changé ses unités mémoire, ce qui fait qu'elle pouvait faire les deux en une seule micro-opération. Il s'agissait alors d'une optimisation du chemin de données, plus que de la micro-fusion proprement dite, mais je la mentionne ici malgré tout. La micro-fusion sur ce processeur avait un avantage en plus des avantages usuels : elle utilisait mieux les décodeurs. Les Pentium M avaient trois décodeurs pour décoder plusieurs instructions en même temps, chose qu'on détaillera dans le chapitre sur les processeurs superscalaires. Un seul décodeur pouvait décoder toutes les instructions, les deux autres ne peuvent décoder que les instructions simples, qui se décodent en une seule micro-opération. Avec la micro-fusion, les trois décodeurs peuvent gérer les écritures mémoire et les opérations ''load-op'', vu qu'elles deviennent des instructions simples. Sans cela, elles auraient été limitées au premier décodeur. <noinclude> {{NavChapitre | book=Fonctionnement d'un ordinateur | prev=Le parallélisme mémoire | prevText=Le parallélisme mémoire | next=Exemples de microarchitectures CPU : le cas du x86 | nextText=Exemples de microarchitectures CPU : le cas du x86 }} </noinclude> 7xsx8w6soiqyhcckatv2cqvg3mqnkek 773239 773238 2026-09-26T14:05:12Z Mewtow 31375 /* La double émission entière */ 773239 wikitext text/x-wiki Les processeurs vus auparavant ne peuvent émettre au maximum qu'une instruction par cycle d'horloge. Ils peuvent avoir plusieurs instructions qui s'exécutent en même temps, dans des unités de calcul séparées. C'est le cas dès qu'une instruction multi-cycles s'exécute. Par contre, ils ne peuvent démarrer qu'une seule instruction par cycle. Et quand on court après la performance, ce n'est pas assez ! Les concepteurs de processeurs ont inventés des processeurs qui émettent plusieurs instructions par cycle : les '''processeurs à émissions multiples'''. Un processeur à émission multiple charge plusieurs instructions en même temps, les décode en parallèle, puis les émet en même temps sur des unités de calculs séparées. Pour cela, il faut gérer les dépendances entre instructions, répartir les instructions sur différentes unités de calcul, et cela n'est pas une mince affaire. En général, pour un processeur à émission multiple capable d'émettre N instructions en parallèle, on parle de '''processeur à N voies''', ou encore un ''processeur à N pipelines''. [[File:Superscalarpipeline.svg|centre|vignette|upright=1.5|Pipeline RISC classique à cinq étages sur un processeur superscalaire. On voit bien que plusieurs instructions sont chargées en même temps.]] Les processeurs à émission multiple sont de deux types : les processeurs VLIW et les '''processeurs superscalaires'''. Nous mettons les processeurs VLIW de côté en attendant le prochain chapitre. La raison est qu'ils ont un jeu d'instruction spécialisé qui expose le parallélisme d'instruction directement au niveau du jeu d'instructions, là où les processeurs superscalaires restent des processeurs au jeu d'instruction normal. La différence principale entre processeur VLIW et superscalaire est qu'un processeur superscalaire répartit les instructions sur les unités de calcul à l’exécution, là où un processeur VLIW délègue cette tâche au compilateur. ==Le nombre de voies d'un processeur superscalaire== Les processeurs superscalaires les plus simples ne permettent que d'émettre deux micro-opérations à la fois, d'où leur nom de processeur ''dual issue'', ce qui se traduit en '''processeur à double émission'''. Les processeurs triple émission permettent eux d’émettre trois instructions à la fois. A l'opposé, les CPU superscalaires les plus puissants peuvent émettre jusqu'à 8-10 instructions simultanément. En 2026, les CPU superscalaires peuvent monter jusqu’à 10 µops émises en même temps, ce qui est énorme ! Et cela nous amène à faire une distinction entre les processeurs superscalaires dits '''étroits''' et ceux dits '''larges'''. Dans ce wikilivre, on part du principe que les processeurs superscalaires étroits émettent jusqu'à 4-5 µops à la fois, alors que les larges peuvent en émettre plus, l'intervalle entre les deux est une zone grise. Le nombre exact n'est pas fixé dans le marbre, la distinction est volontairement vague. Nous parlerons de la '''largeur d'un CPU superscalaire''' pour parler du nombre de voies, du nombre de µops émises en même temps. Plus un CPU superscalaire est large, plus il est performant ''toute chose égale par ailleurs''. Mais dans les faits, vous pouvez oublier la partie en italique. Élargir un CPU superscalaire demande de rajouter beaucoup de circuits. Et ce cout en circuit aura un impact sur la performance. A budget en transistors limité, il y a un compromis entre élargir un CPU et rajouter du cache ou la taille des fenêtres d'instructions/ROB et autres. ===Les contraintes d’appariement=== Prenons un processeur double émission, capable d'émettre deux instructions simultanément. Émettre deux instructions en même temps n'est pas toujours possible. Par exemple, si deux instructions sont dépendantes, on ne peut pas les émettre en même temps. Mais oublions un instant ces histoires de dépendances de données, et partons du principe que le CPU ait affaire à deux instructions indépendantes, une paire idéale. Un CPU double émission "parfait" pourrait exécuter n'importe quelle paire d'instruction. Par exemple, il pourrait émettre deux multiplications consécutives, deux lectures consécutives, une lecture et une écriture en même temps, etc. En pratique, aucun processeur ne permet cela. Implémenter un CPU superscalaire "parfait" aurait un cout en circuit beaucoup trop conséquent. Par exemple, pour émettre deux multiplications en même temps, il faudrait deux multiplieurs séparés. Idem pour exécuter deux accès mémoire simultané : cela demande d'avoir deux unités mémoire et un cache multiport. Mais en pratique, aucun CPU superscalaire ne fait cela. La totalité des CPU double émission se débrouille avec un seul circuit multiplieur et une seule unité mémoire. Et cela entraine l'apparition de dépendances structurelles, qui font que certaines paires d'instructions sont impossibles. Par exemple, impossible d'émettre deux multiplications en même temps avec un seul circuit multiplieur. Mais émettre une multiplication en parallèle d'une addition est parfaitement possible. Il y a donc des paires d'instructions autorisées et des paires interdites. Et la logique est la même sur les CPU à triple ou quadruple émission, et au-delà. : des triplets ou quadruplet d'instructions sont impossibles à émettre simultanément, alors que d'autres le sont. Pour le dire autrement, il y a des '''contraintes d'appariement''' sur les instructions à émettre en même temps. ===L'impact des contraintes d’appariement sur la performance=== Ces contraintes d’appariement ont un impact sur les performance, mais celui-ci peut être grandement mitigé. Les programmes informatiques utilisent en pratique beaucoup plus d'opérations entières de base que de multiplication, branchements et accès mémoire. Il y a environ 5 opérations entières de base pour une multiplication entière. En pratique, il est rare de devoir exécuter deux multiplications à la fois. Idem pour les décalages et rotations, qui sont assez éloignés les uns des autres. Deux branchements consécutifs sont quant à eux plus fréquents. Deux accès mémoire consécutifs est assez rare, mais ils restent assez proches les uns des autres malgré tout. La conséquence est que certaines paires d'instructions sont privilégiées par rapport au reste. Les paires regroupant deux calculs basiques (additions, soustractions, ...) sont systématiquement supportées, car dupliquer des ALU entières est facile. Les paires regroupant un calcul et une autre instruction sont elles aussi systématiquement supportées. Par contre, les paires avec deux branchements, deux accès mémoire, deux décalages/rotations, ou deux multiplications/divisions sont fréquemment interdites. Seuls les CPU superscalaires larges peuvent les permettre, et encore : sous conditions. Le résultat est que le processeur peut se débrouiller avec une seule unité mémoire et une unité de branchement. ===Terminologie pour la suite du chapitre=== Décrire les contraintes d’appariement demande de dire quelles combinaisons d’instructions sont autorisées ou interdites. Et pour cela, nous allons introduire les abréviations suivantes : * INT est un raccourcis pour parler d'une opération entière simple, qu'une ALU entière peut exécuter. * MUL correspond à une opération entière complexe, typiquement une multiplication, éventuellement à une division. * FLOAT désigne une opération flottante, peut importe laquelle. * MEM désigne un accès mémoire, à savoir une instruction ou µop mémoire. * BRANCH désigne une instruction de branchement. Cette terminologie permettra de définir des paires d'instructions, qu'elles soient possibles ou interdites. Par exemple, l'expression INT + FLOAT désigne une paire d'instruction regroupant une instruction entière et une instruction flottante, peu importe l'ordre des deux instructions. Il en sera de même pour les triplets, quadruplets, ou plus. Par exemple, l'expression INT + INT + FLOAT + MUL désigne un quadruplet d'instructions regroupant : deux instructions entières, une instruction flottante, et une multiplication entière. J'utiliserais aussi des parenthèses pour regrouper certaines combinaisons. Par exemple, l'expression INT + (MUL /FLOAT) veut parler d'une paire d'instruction où la première est une opération entière basique, la seconde est : soit une multiplication, soit une opération flottante. ==Le ''front-end'' des processeurs superscalaires== Un processeur superscalaire doit charger, décoder, et exécuter plusieurs instructions par cycle. Par exemple, un processeur triple émission doit charger 3 instructions lors d'un premier cycle, puis les décoder au cycle suivant, et les exécuter au cycle suivant. Il travaille donc pas paquets de 3 instructions, dans cet exemple. Intuitivement, charger, décoder et exécuter plusieurs instructions à la fois demande de dupliquer des circuits. Par exemple, il faut dupliquer les décodeurs pour décoder plusieurs instructions. Les unités de calcul sont aussi dupliquées, sauf sur certaines processeurs qui arrivent à ruser, mais laissons cela de côté pour plus tard. Mais d'autres circuits sont simplement modifiés, comme l'unité de chargement ou l'unité d'émission. Voyons ce qu'il en est dans le détail. {|class="wikitable" |- ! rowspan="2" | Processeur sans émission multiple | rowspan="2" | Chargement | rowspan="2" | Décodage | rowspan="2" | Renommage | rowspan="2" | Émission | Exécution / ALU | rowspan="2" | ''Commit''/ROB |- | Exécution / ALU |- ! rowspan="4" | Processeur superscalaire | rowspan="4" | Chargement | rowspan="2" | Décodage | rowspan="4" | Renommage | rowspan="4" | Émission | Exécution / ALU | rowspan="4" | ''Commit''/ROB |- | Exécution / ALU |- | rowspan="2" | Décodage | Exécution / ALU |- | Exécution / ALU |} Nous allons d'abord voir ce qu'il en est pour le ''front-end'', à savoir la partie du CPU qui s'occupe de charger et décoder les instructions. Elle regroupe l'unité de chargement, les décodeurs, l'unité de prédiction de branchement, la file de µops, la file d'instruction, et divers caches. En soi, les changements portent surtout sur l'unité de chargement et les décodeurs. ===Le chargement de plusieurs instructions consécutives=== Un processeur superscalaire charge plusieurs instructions en même temps, ''en tenant compte de la présence de branchements''. La partie en italique est importante et elle a une conséquence très forte : la manière de faire est fortement différente entre un CPU superscalaire étroit et large. Un CPU superscalaire étroit fait au plus simple : il charge un bloc de 2, 3, 4 instructions consécutives en mémoire RAM. Il faut alors doubler/tripler/quadrupler la taille du bus connecté au cache d'instruction. Par exemple, pour charger quatre instructions de 32 bits chacune, il suffit de lire un bloc de 128 bits dans le cache d'instruction. Un bloc de 8, 16, 32 octets est donc lu depuis le cache et est ensuite découpé en instructions, envoyées chacun à un décodeur. Charger plusieurs instructions consécutives ne marche bien qu'en absence de branchements. La présence de branchements casse cette dynamique, et il faut gérer leur présence. * Sans prédiction de branchement, toutes les instructions d'un bloc sont décodées et exécutées en même temps, même s'il y a un branchement dans le tas. * Avec prédiction de branchement, les instructions situées après le branchement ne sont pas décodées ni exécutées. L'unité de chargement coupe le bloc chargé au niveau du premier branchement pris, remplit les vides avec des NOP, avant d'envoyer le tout à l'unité de décodage. Les instructions à l'adresse de destination seront chargées au cycle suivant, en chargeant un nouveau bloc. [[File:Fetch sur un processeur superscalaire avec prediction de branchements.png|centre|vignette|upright=2|Fetch sur un processeur superscalaire avec prédiction de branchements.]] Un autre défaut est que sur le CPU charge un bloc de 128 bits à la fois, les instructions doivent être alignées sur 128 bits. Et on peut généraliser à n'importe quelle taille de bloc. Si les instructions ne sont pas alignées, il faut gérer la situation. Il arrive qu'une instruction soit à cheval sur deux blocs, avec un morceau dans le premier bloc, un autre dans le second. Dans ce cas, l'instruction est chargée en deux fois, et des circuits doivent gérer la situation pour recoller les morceaux. Sur les CPU avec des instructions de taille fixe, le problème ne survient que rarement. Mais il est fréquent avec des instructions de taille variable. Un autre défaut est lié aux branchements. Imaginez qu'un branchement s'exécute et branche vers une instruction de destination. Rien n'indique que l'instruction de destination sera au tout début d'un bloc. Si ça se trouve, elle sera en troisième position d'un bloc de 4 instructions. Une solution simple charge le bloc en respectant l'alignement, et les deux premières instructions du bloc ne sont pas à exécuter. Il y a donc une petite perte de performance. Certains CPU, comme l'IBM RS/6000, ont la machinerie charger un bloc de N instructions même s'il n'est pas aligné. La machinerie en question se trouve dans le cache d'instruction et est tout sauf simple à expliquer,ce qui fait que je me permets de la passer sous silence. Un détail, cependant : elle ne permet pas de charger des blocs qui sont à cheval sur deux lignes de cache. ===Le décodage parallèle des instructions=== Pour le décodage des instructions, plusieurs instructions sont décodées en même temps. On parle alors de '''décodage parallèle'''. Pour ce faire, un processeur superscalaire contient plusieurs décodeurs, chacun pouvant décoder une instruction en parallèle des autres. Prenons par exemple un processeur superscalaire à 4 voies : le décodage se fera dans quatre décodeurs séparés, qui fonctionneront en parallèle. Cependant, cette implémentation ne marche bien que pour les processeurs RISC, les processeurs CISC devant faire avec un microcode gourmand en circuits. Les processeurs CISC utilisent des décodeurs hybrides, avec un microcode qui complémente un décodeur câblé. Dupliquer le microcode aurait un cout en transistors trop important, ce qui fait que seuls les décodeurs câblés sont dupliqués. Les CPU CISC superscalaires disposent donc de plusieurs décodeurs simples, capables de décoder les instructions les plus courantes, avec un seul microcode. La conséquence est qu'il n'est pas possible de décoder deux instructions microcodées en même temps. Par contre, il reste possible de décoder plusieurs instructions non-microcodées ou une instruction microcodée couplée à une instruction non-microcodée. Vu qu'il est rare que deux instructions microcodées se suivent dans un programme, le cout en performance est extrêmement mineur. Il faut noter qu'une instruction correspond à une ou plusieurs µops. Il est donc possible de charger 5 instructions et de se retrouver avec 8 µops en sortie des décodeurs. Et c'est la source d'une distinction très importante. Plus haut, j'ai parlé de processeurs superscalaires à N voies. La terminologie peut avoir deux interprétations. La première est que le processeur charge/décode N instructions à la fois. Une seconde est qu'il est capable d'exécuter/émettre N µops en même temps. Les deux ne sont pas équivalentes, vu qu'un processeur superscalaire peut, par exemple, charger/décoder N instructions et émettre/exécuter N+2 µops. La distinction est particulièrement importante sur les processeurs superscalaires larges. Certains d'entre eux sont capables de charger 5 instructions et d'en émettre le double ! Un exemple classique était les processeurs Athlon d'AMD, qui pouvaient décoder deux instructions par cycle, mais émettre 4 µops par cycle. Ce n'était pas un problème, car beaucoup d'instructions, même basiques, étaient décodées en 2 µops. ===L'émission parallèle des instructions=== La superscalarité modifie en profondeur l'unité d'émission, notamment au niveau de son interface. Avant, elle recevait une micro-opération sur son entrée, et fournissait la micro-opération émise sur une sortie. Et cela vaut aussi bien pour une unité d'émission simple, un ''scoreboard'', une fenêtre d'instruction ou des stations de réservation. Mais avec l'émission multiple, les sorties et entrées sont dupliquées. Pour la double émission, il y a deux entrées vu qu'elle doit recevoir deux micro-opération décodées/renommées, et deux sorties pour émettre deux micro-opérations. Dans ce qui suit, nous ne parlerons pas d'entrée et de sortie, mais de '''ports de décodage''' et de '''ports d'émission'''. Les ports de décodage reçoivent des micro-opérations provenant de l'unité de décodage, les ports d'émissions sont pour les micro-opérations émises. Les ports d'émission injectent les micro-opérations émises dans le pipeline, elle sont propagée au banc de registre, aux ALUs, etc. Un processeur superscalaire a forcément plusieurs ports d'émission (et de décodage). Si l'interface de l'unité d'émission change, l'intérieur est tout autant modifié. Sur un CPU non-superscalaire, l'unité d'émission reçoit une µop "candidate à l'émission", et détecte les dépendances entre avec les µops en vol, en cours d'exécution dans le pipeline. Et c'est toujours le cas sur les processeurs superscalaires : l'unité d'émission détecte les dépendances entre les µops candidates et les µops en vol. Mais avec la siperscalarité, elle doit en plus tester les dépendances entre les µops candidates. Si l'unité d'émission est un ''scoreboard'', il doit détecter les dépendances entre instructions émises simultanément et les sérialiser si besoin. Avec une fenêtre d'instruction, il n'y a pour ainsi dire rien à faire, vu que les dépendances sont éliminées par le renommage de registre et que les signaux de réveil gèrent les dépendances RAW. C'est la raison pour laquelle les processeurs superscalaires modernes n'utilisent pas de ''scoreboard''. ==Les processeurs implémentés avec plusieurs pipelines== Nous venons de voir comment la superscalarité impacte le ''front-end'', il est maintenant de voir ce qu'il en est de l'unité d'émission et des ''back-ends''. Un processeur superscalaire peut utiliser l'exécution dans le désordre et le renommage de registres, mais ce n'est pas une obligation. Historiquement, les premiers processeurs superscalaires n'utilisaient pas d'exécution dans le désordre. Le meilleur exemple est celui du Pentium 1 et 2 d'Intel. Ils étaient tous deux des processeurs superscalaires, mais n'avaient pas d'exécution dans le désordre, qui est apparue sur le Pentium 3. les explications sont plus simples si on met de côté l'exécution dans le désordre. Vous vous dites que l'implémentation d'un processeur superscalaire n'est pas la même suivant que le CPU utilise l'exécution dans le désordre, ou non. Mais dans les faits, la distinction est plus subtile que ca. Pour l'expliquer, je vais utiliser une classification originale, qui sépare les processeurs superscalaires en trois sous-types. La distinction se fait sur la manière dont la superscalarité est implémentée en matériel.La classification que j'utilise distingue : * Les processeurs avec plusieurs pipelines, sans exécution dans le désordre. * Les processeurs superscalaires avec un pipeline dynamique. * Les processeurs avec des fenêtres d'instruction décentralisées. La distinction n'est pas qu'une question d'implémentation matérielle. Elle impacte aussi le ratio entre instructions chargées/décodées et instructions émises. Vous vous dites que les deux sont égales, mais ce n'est le cas que sur les processeurs avec N pipelines ! Sur les autres processeurs, il est possible de décoder 4 instructions, mais d'en émettre maximum 6 par cycle ! Et sur les CPU avec des fenêtres d'instruction décentralisées, il y a aussi une différence entre le nombre d'instructions qui sont ''dispatch'' et ''schedule''. La catégorie la plus simple est celle des '''processeurs avec plusieurs pipelines'''. Ce sont des processeurs superscalaires sans exécution dans le désordre. Ils partent d'un processeur non-superscalaire, et ajoutent un second pipeline, en sortie de l'unité d'émission. Le second pipeline permet l'émission parallèle d'une seconde instruction. Le second pipeline n'est pas identique au pipeline originel, sur plusieurs points. Premièrement, il n'a pas exactement les mêmes circuits. Le second pipeline est généralement composé d'une unité de calcul, d'une FPU, éventuellement d'un second banc de registres, guère plus. Il permet d'exécuter une seconde opération de calcul, pas plus, ce qui est à l'origine de contraintes d'appariemment assez fortes. De plus, il n'a pas forcément la même longueur, il peut avoir des étages en moins ou en plus. Il s'agit de la forme la plus simple de processeur superscalaire et nous allons leur consacrer cette section. ===La double émission entière-flottante=== L'exemple le plus simple utilise un second pipeline pour les opérations flottantes, séparé du pipeline originel. L'ajout d'un pipeline flottant permet d'émettre une micro-opération entière en même temps qu'une micro-opération flottante, ce qui s'appelle la '''double émission entière-flottante'''. La micro-opération entière s'exécute dans l'ALU entière, la micro-opération flottante dans la FPU. Les exemples les plus notables sont le PA-RISC 7100, le POWER 1 (RIOS .& et .9) et le PowerPC 601. L'Apollo DN10000 et l'Intel i860 utilisaient aussi cette forme de double émission, mais étaient des prototypes de CPU VLIW. [[File:Double émission entière-flottante.png|centre|vignette|upright=2|Double émission entière-flottante]] Pour bien expliquer les choses, prenons le processeur illustré ci-dessous, avec deux ''back-end'' séparés : un pour la FPU, et un autre pour le reste. Les instructions entières et mémoire passent toutes par le ''back-end'' généraliste. [[File:CPU non-superscalaire basique, avec une FPU.png|centre|vignette|upright=1.5|CPU non-superscalaire basique, avec une FPU]] Les deux ''back-end'' sont des pipelines séparés, mais il faut que l'unité d'émission soit spécialement conçue pour en profiter. Un processeur simple émission peut démarrer une seule µop par cycle : soit la µop est envoyée au ''back-end'' flottant, soit elle est envoyée au ''back-end'' général. Avec la double émission entière-flottante, le processeur peut envoyer une µop dans chaque pipeline, à chaque cycle. Vu que la FPU et son banc de registre forment déjà un pipeline séparé, les seules modifications se font au niveau du ''front-end'', unité d'émission inclue. {| |- |[[File:CPU simple émission avec une FPU.png|centre|vignette|upright=1.5|CPU simple émission avec une FPU.]] |[[File:Double émission entière flottante avec un second pipeline.png|centre|vignette|upright=1.5|CPU avec un second pipeline flottant.]] |} Notez que le terme double émission ''entière''-flottante est un peu trompeur. En réalité, la technique permet d'émettre une instruction flottante avec n'importe quelle autre instruction non-flottante, avec des restrictions pour les branchements. Il est par exemple possible d'exécuter un calcul flottant en parallèle d'un accès mémoire. Sur les CPU 32 bits, il est possible de faire les multiplications dans la FPU. Pour rappel, la FPU contient un multiplieur entier 53-54 bits, pour multiplier les mantisses de deux flottants 64 bits, qu'on peut utiliser pour faire les multiplications entières 32 bits. Cela permet d'émettre une multiplication en même temps qu'une opération entière dans l'ALU. Ainsi, le CPU pouvait émettre deux instructions : une opération entière de base, avec soit une multiplication, soit une opération flottante. En clair, une émission de type INT + (MUL /FLOAT). Par contre, on perd la possibilité d'émettre une multiplication entière avec une opération flottante, mais c'était un compromis qui en valait la peine. Le CPU PA-RISC 7100 implémentait cette optimisation. Il s'agissait d'un CPU double émission entière-flottante, qui utilisait donc un partitionnement assez simple. Le combo partitionnement + reconditionnement permettait ici de gagner en performance, pour un cout en matériel très limité. D’autant plus limité que le jeu d'instruction prévoyait le reconditionnement de la FPU. Les multiplications ne lisaient pas leurs opérandes dans les registres entiers, mais dans les registres flottants. ===La double émission entière=== Une autre possibilité ajoute un second pipeline, capable d'exécuter des opérations entières simples, appelé le pipeline entier. Le processeur supporte alors la '''double émission entière''', à savoir qu'ils peuvent émettre une opération entière en plus d'une autre instruction. L'opération entière en question est une opération simple, comme une addition, une soustraction, une opération bit à bit, une comparaison, etc. Les exemples classiques sont les processeurs Intel i960 et Pentium. L'implémentation était différente entre l'i960 et le Pentium : Le Pentium avait une seconde ALU entière, alors que le i960 rusait. Le Pentium dupliquait l'ALU entière, pour créer un second pipeline entier. Sur le Pentium originel, les contraintes d'appariement étaient assez strictes. Le Pentium avait une FPU, mais ne pouvait pas l'utiliser en double émission. Il lui était impossible d'exécuter une opération flottante en même temps qu'une opération entière (branchements inclus). Le Cyrix 6x86 était une sorte de Pentium produit par l'entreprise Cyrix, avec une microarchitecture différente. Il avait des contraintes d’appariement réduites. Par exemple, il pouvait émettre une opération flottante en parallèle d'une opération entière. Nous détaillerons ces processeurs dans le prochain chapitre, qui porte justement sur les CPU superscalaires x86. [[File:CPU simple et double émission avec un second pipeline entier.png|centre|vignette|upright=2.5|CPU simple et double émission avec un second pipeline entier.]] L'Intel i960 n'avait pas de FPU, juste une ALU entière et une unité mémoire. Il utilisait l'unité mémoire comme une seconde ALU entière limitée. Pour rappel, une unité mémoire contient une AGU, à savoir une ALU spécialisée dans les calculs d'adresse. Elle est capable de faire des additions, soustractions, et quelques décalages limités (des décalages par 2, 4, 8, pas plus). L'Intel i960 ajoutait des connexions entre AGU et banc de registre, afin que celle-ci serve de seconde unité de calcul. L'AGU est déjà reliée aux deux ports de lecture, pour lire les opérandes du calcul d'adresse, il suffit de la connecter sur le port d'écriture. Le processeur peut alors émettre deux instructions/µops entières, sous certaines conditions. [[File:Emission multiple des opérations entières, double émission.png|centre|vignette|upright=2|Émission multiple des opérations entières, double émission.]] ==Les processeurs superscalaires dynamiques== Les processeurs précédents, avec 2 pipelines, avaient une unité d'émission assez simple. Elle avait deux ports de décodage, et deux ports d'émission. Le nombre de ports de décodage et d'émission étaient identiques. Vous vous dites que c'est naturel, et intuitif, que c'est normal pour un processeur superscalaire. Mais en réalité, la majorité des processeurs superscalaires a plus de ports d'émission que de décodage. Expliquer pourquoi va cependant nous demander de parler de comment la superscalarité exploite les unités de calcul. Posons-nous cette question : de combien d'unités un processeur superscalaire a besoin ? Les unités en question ne sont nécessairement des unité de calcul, l'unité mémoire et l'unité de branchement comptent aussi. La réponse dépend du nombre de voies. Si le ''front-end'' charge et décode un paquet de N instructions, exécuter ces N instructions simultanément demandera là aussi au moins N unités de calcul. Notez le terme : ''au moins'', car c'est là tout l'enjeu de cette section. La réponse dépend aussi des contraintes d’appariement. Pour un CPU superscalaire "parfait", sans contraintes d’appariement, toutes les unités doivent être dupliquées, y compris l'unité mémoire. Un processeur à double émission parfaite devrait dupliquer l'ALU entière, le ''barrel shifter'', le circuit multiplieur, la FPU, l'unité mémoire, et l'unité de branchement, ce qui ferait passer de 6 unités à 12. Le cout en circuit est énorme, même pour de la double émission ! Et pour un CPU à N pipelines, le cout en circuit encore plus prohibitif ! En pratique, les processeurs superscalaires qui font ça sont très rares. La majeure partie des processeurs superscalaires utilisent d'autres techniques : le partitionnement, la duplication partielle des ALUs, la duplication des autres unités. ===Le partitionnement des unités fonctionnelles=== Les CPU superscalaires les plus simples ne dupliquent pas leurs unités de calcul, ils se contentent d'exploiter les unités existantes. Et pour expliquer comment, nous allons partir de l'exemple d'un processeur non-superscalaire contient une ALU entière, une FPU, une unité mémoire et une unité de branchement. Les quatre unités peuvent fonctionner en parallèle, à savoir qu'une unité peut exécuter une instruction à elle toute seule, sans interagir avec les autres. Cela suffit pour créer un processeur quadruple émission, qui peut émettre en même temps : une opération entière, une opération flottante, un branchement, un accès mémoire. Le principe marche aussi sur un processeur avec moins de 4 unités, par exemple sans FPU et/ou sans unité de branchement, ou avec au contraire des unités en plus. L'idée générale est d’émettre une µops séparée dans chaque unité fonctionnelle. Utiliser ainsi les unités existantes, pour rendre un CPU superscalaire, s'appelle le '''partitionnement superscalaire'''. Le terme n'est pas beaucoup utilisé en-dehors de quelques vieux articles académiques. Le partitionnement est fortement dépendant du jeu d'instruction, car il demande de bien séparer instructions entières, flottantes, branchements et instructions mémoire. Et autant c'est bien le cas sur les CPU RISC, autant les CPU CISC ne sont pas dans ce cas. Les CPU CISC ont des instructions ''load-op'', qui effectuent une instruction arithmétique et une µop mémoire. Et ces instructions sont très compliquées à implémenter avec le partitionnement. Et au-delà de ça, les CPU CISC ont beaucoup d'instructions qui sont décodées en plusieurs µops, à exécuter dans un ordre bien précis. On peut pas émettre les µops en question simultanément. Le partitionnement a un cout en circuit très limité, pour un gain en performance lui aussi limité. Pour reprendre le CPU de l'exemple précédent, il est rare que l'on ait à exécuter ces quatre instructions en même temps. En pratique, les combinaisons fréquentes sont les combinaisons calcul + accès mémoire, calcul + branchement, calcul entier + calcul flottant. Utiliser une quadruple émission de ce type n'est donc pas une bonne idée, alors que la double émission parait plus adaptée. Et c'est là que la différence entre ports d'émission et de décodage devient intéressante. L'idée est d'avoir une unité d'émission avec deux ports de décodage, et 4 port d'émission (un par unité). Les contraintes d’appariement varient alors suivant le processeur, mais la règle générale est que si deux micro-opérations tombent dans deux unités différentes, on peut les émettre en même temps. En clair, on peut émettre une opération entière avec une opération flottante, une opération entière avec un branchement, un accès mémoire avec un branchement, etc. Et cela rend le partitionnement bien plus intéressant que prévu. Les tout premiers CPU superscalaires étaient nombreux à utiliser cette technique. Les exemples classiques sont l'HyperSPARC, de l'alpha 21064 et du PowerPC 603. C'étaient des CPU double émission, avec une ALU entière, une unité mémoire et une FPU. Nous parlerons de '''partitionnement INT-FLOAT-MEM'''. Les contraintes d’appariement variaient grandement suivant le processeur. Le PowerPC 603 implémentait une double émission "parfaite". C'est à dire que toutes les combinaisons possibles à deux instructions étaient possibles, que ce soit INT + FLOAT, INT + MEM, FLOAT + MEM. En plus de cette double émission "parfaite", le CPU utilisait la technique dite de ''branch folding'', qui permettait d'exécuter un branchement sans utiliser d'unité de calcul, ce qui fait qu'il était qualifié de ''CPU 2-way + Branch''. A l'opposé, le processeur alpha 21064 avait pas mal de contraintes d’appariement, à savoir que certaines combinaisons de deux instructions étaient interdites, bien que compatibles avec les unités disponibles. Une des raisons est que le banc de registre n'avait pas assez de ports de lectures, ce qui fait que certaines combinaisons avec une écriture mémoire n'étaient pas permises. Les concepteurs avaient volontairement inséré ces contraintes afin de simplifier le design du processeur. Avant de poursuivre, précisons que la FPU regroupe un additionneur flottant et un multiplieur flottant. Et ce fait peut être exploité pour approfondir le partitionnement. La '''double émission FADD/FMUL''' permet d'émettre une addition flottante en même temps qu'une multiplication flottante. Elle a été utilisée sur les processeurs x86, Alpha et SPARC. Les autres circuits de calcul flottant sont souvent regroupés avec l'additionneur. La division est parfois e même port que le multiplieur flottant, parfois sur celui de l'additionneur (quand elle est microcodée et exécutée en enchainant les soustractions). ===La duplication des ALU entières=== Le partitionnement est une technique élégante, simple, facile à implémenter et au cout en circuits modeste. Mais il faut avouer que cela ne donne pas des CPU superscalaires très efficaces. La raison est que le partitionnement permet d'émettre des paires, triplets, quadruplets de micro-opérations qui sont rares en pratique. Les combinaisons les plus fréquentes en pratique sont des paires/triplets d'opérations entières, souvent des paires/triplets d'additions ou d'opérations entières simples. Il est donc primordial d'émettre plusieurs opérations entières simultanément. Le fait d'émettre et d'exécuter plusieurs opérations entières en même temps s'appelle l''''émission entière multiple'''. Pour exécuter la seconde/troisième opération entière, il faut dupliquer les ALU entières (ou réutiliser l'AGU comme ALU entière comme le fait l'Intel i960). Le processeur doit avoir deux ALU entières pour de la double émission entière, trois pour de triple émission entière, etc. De plus, il faut ajouter des ports au banc de registre, pour lire deux fois plus d'opérandes et décrire deux résultats. Et enfin, il faut rajouter un port d'émission pour pouvoir émettre une micro-opération entière en plus. [[File:Processeur non-superscalaire basique 03.png|centre|vignette|upright=2|Processeur non-superscalaire basique]] La tendance est de multiplier les ALU entières, mais pas le circuit multiplieur. Le cout en transistors est alors grandement réduit, au prix de l'apparition de dépendances structurelles. Le CPU peut émettre deux additions en même temps, ou une addition et une multiplication, mais pas deux multiplications. Il en est de même avec le ''barrel shifter'', qui n'est pas dupliqué. Il est donc possible d'émettre un décalage avec une opération de base, mais pas deux décalages en parallèle. Mais ces contraintes d’appariement ne sont pas un problème, car il est rare de devoir émettre deux multiplications/décalages en même temps. La plupart des programmes sont majoritairement remplis d'additions, avec des multiplications assez rares et des décalages qui le sont encore plus. La moyenne est proche d'une multiplication pour 4/5 additions. Vu que des multiplications consécutives sont très rares, dupliquer le multiplieur est presque inutile. Disposer de plusieurs circuits multiplieurs ou de plusieurs ''barrel shifters'' serait donc un cout en circuits qui ne servirait que rarement et n'en vaut pas la chandelle. Il faut noter que sur les CPU 32 bits, les multiplications peuvent être exécutées dans la FPU ou dans un multiplieur séparé, tout dépend de si le CPU exécute les multiplications dans la FPU ou pas. {|class="wikitable" |- ! Voie d'émission n°1 !! Voie d'émission n°2 !! Voie d'émission n°3 |- | ALU entière || ALU entière || FPU |} Reste à connecter les unités de calcul à l'unité d'émission (indirectement, car il y a des étages de pipeline entre les deux). Une implémentation basique utilisee un port d'émission par unité. Prenons l'exemple d'un CPU superscalaire avec 4 ALU entières, une FPU, et une unité mémoire. Vu qu'il y a 6 unités, cela fait donc 6 ports d'émission. L'exemple historique le plus impressionnant est celui du Motorola 88110, un CPU double émission qui possède 10 unités. Il contient deux ALU entières, un circuit multiplieur, un circuit diviseur, une unité de manipulation de bits, une unité mémoire, un additionneur flottant, et deux unités graphiques pour des calculs impliquant des pixels. Le multiplieur est utilisé à la fois pour les multiplications entières, flottantes et le produit de deux pixels. Idem pour le diviseur, qui sert pour les divisions entières et flottantes. Et chaque unité a son propre port d'émission. La conséquence est que les contraintes d'appariement du Motorola 88110 sont simples : tant que deux µops atterrissent dans des unités séparées, la paire est permise. Il est par exemple possible d'émettre simultanément deux opérations entières simples, vu qu'il y a deux ALU. Par contre, impossible d'émettre simultanément deux multiplications, deux divisions, deux opérations flottantes, deux manipulations de bit, deux accès mémoire, etc. De plus, le partage du multiplieur fait qu'on ne peut pas émettre une multiplication entière avec une multiplication flottante. Idem avec les divisions, vu que le diviseur est aussi partagé. Ce sont les seules contraintes. ===Le partage des ports d'émission=== La présence d'un circuit multiplieur et d'un ''barrel shifter'' pose des problèmes pour le partitionnement. Il est possible d'avoir un port d'émission dédié au multiplieur, et un autre au ''barrel shifter'', qui sont séparés de ceux pour l'ALU entière. Il est alors possible d'émettre une opération entière, une multiplication et un décalage, simultanément. Nous parlerons de '''partitionnement entier''' pour désigner cette possibilité. [[File:Partitionnement superscalaire entier.png|centre|vignette|upright=2|Partitionnement superscalaire entier.]] Le partitionnement entier est cependant très rare, car le gain en performance serait trop faible au regard des couts. Quelques processeurs historiques l'utilisaient, mais ce n'est plus le cas de nos jours. La raison est que les multiplications et les décalages sont assez rares dans le code, ce qui fait que le port d'émission associés sont sous-utilisés. Et un port d'émission a un cout en circuit assez important. L'idéal serait de trouver un moyen de réduire le nombre de ports d'émissions, sans réduire le nombre d'unités de calcul. [[File:Emission multiple des opérations entières, implémentation naive.png|vignette|Émission multiple des opérations entières, implémentation naive]] La solution consiste à '''partager les ports d'émission''', à savoir connecter un port d'émission à plusieurs unités de calcul. L'exemple classique est justement lié aux circuits multiplieur et ''barrel shifters''. L'idée est que le multiplieur et le ''barrel shifter'' sont mis sur le même port d'émission qu'une ALU entière. Prenons par exemple le processeur à double émission entière illustré ci-contre. Un port d'émission est relié à une ALU entière seule, et l'autre port d'émission alimente la seconde ALU entière et le multiplieur. La conséquence est que le processeur peut émettre deux opérations entières, dont une multiplication. Si on avait utilisé un port d'émission dédié au multiplieur, il aurait été possible d'émettre deux opérations entières et une multiplication. On perd donc une voie, mais on gagne en flexibilité et on économise des circuits. [[File:Emission multiple des opérations entières, implémentation courante.png|centre|vignette|upright=1.5|Émission multiple des opérations entières, implémentation courante]] En général, le ''barrel shifter'' et le multiplieur sont placés sur deux voies d'émission séparés, ce qui permet d'émettre un décalage et une multiplication en même temps. Les mettre sur le même port ne le permettrait pas. {|class="wikitable" |- ! Voie d'émission n°1 !! Voie d'émission n°2 !! Voie d'émission n°3 |- | * ALU entière * Multiplieur | * ALU entière * Barrel shifter | FPU |- | * Additions, soustractions, opérations bit à bit, etc. * Multiplication | * Additions, soustractions, opérations bit à bit, etc. * Décalages et rotations | Opération flottante |} Mais le partage des ports d'émission peut appliquer à n'importe quelle unité. Prenons l'exemple d'un CPU superscalaire avec 4 ALU entières, une FPU, et une unité mémoire. Vu qu'il y a 6 unités, cela fait donc 6 ports d'émission. Cependant, il est rare qu'un bloc de 6 instructions contienne exactement 4 opérations entières, une opération flottante, et un accès mémoire. Beaucoup de ports d'émission seraient sous-utilisés. La solution est de partager des ports d'émission, pour passer à la quadruple émission. Le CPU peut alors répartir 4 instructions sur les ports d'émission adéquats, à chaque cycle. Le CPU a donc 4 ports de décodage et 6 ports d'émission. Une implémentation classique permet n'importe quelle combinaison de 4 instructions compatible avec les 6 unités. Par exemple, il devient possible d'émettre simultanément 4 additions, la combinaison 2 * INT + FLOAT + MEM, 3 * INT + MEM, et bien d'autres. L'exemple relie une ALU entière, la FPU et l'unité mémoire sur le même port. Le CPU a alors quatre ports d'émission, qui ne sont pas "identiques". Trois d'entre eux sont reliés à une ALU entière chacun, rien d'autre. Le quatrième est lui relié à une ALU entière, la FPU et l'unité mémoire. On économise alors deux ports d'émission, mais cela entraine l'apparition de contraintes d’appariement. Le CPU peut émettre 3 opérations entières + n'importe quelle autre opération. Mais les autres combinaisons sont interdites. Le CPU peut émettre 6 opérations entières simultanément, grâce à 6 ALU entières. Les accès mémoire ou opérations flottantes ne viennent pas en plus, mais en remplacement des opérations entières. Regrouper plusieurs ALU sur un même port d'émission est donc à l'origine de dépendances structurelles, de nouvelles contraintes d’appariement. Dans certaines situations de '''''port contention''''', on a assez d'ALU pour exécuter N micro-opérations, mais la répartition des ALUs sur les ports l'empêche. Impossible d'émettre deux micro-opérations sur deux ALU si elles sont sur le même port d'émission. On peut réduire les cas de ''port contention'' en répartissant correctement les ALU sur les ports d'émission, mais sans jamais les supprimer complétement. Un exemple est celui du PowerPC 604. Il implémentait la quadruple émission, alors qu'il avait 5 unités de calcul. Mais deux unités partageaient un même port d'émission. Dans le détail, il avait deux ALU entières, un circuit multiplieur/diviseur, une FPU, une unité mémoire. La FPU et le multiplieur étaient sur le même port d'émission. En conséquence, le CPU pouvait émettre seulement les combinaisons INT + INT + MEM + (MUL/FLOAT). : Il est intéressant de voir l'exemple d'un processeur superscalaire parfait, sans contraintes d'appariement, à N voies. Pour cela, on part d'un processeur non-superscalaire, avec M unités de calcul. Un tel processeur peut s'implémenter avec N ports d'émission, chacun relié à M unités de calcul possibles, ce qui fait que les M unités de calcul sont dupliquées en N exemplaires. Si on prend par exemple un CPU non-superscalaire avec une ALU, une FPU, une unité mémoire et une unité de branchement, un port d'émission sera relié à une ALU, une FPU, une unité mémoire et une unité de branchement. ===La duplication des autres unités fonctionnelles=== Dupliquer les ALU entière est une première étape, mais il est aussi possible de dupliquer les autres unités fonctionnelles. Par exemple, il est possible de dupliquer l'unité mémoire ou l'unité de branchement. Reste à voir quelles unités dupliquer, et pourquoi. Précisons que dans ce qui va suivre, nous allons parler de la duplication des unités en général, pas seulement des unités mémoire/branchement. Nous allons continuer de parler de la duplication des ALU entières dans ce qui suit, mais de manière plus générale. Pour un processeur à N voies, il est théoriquement possible de dupliquer toutes les unités en N exemplaires. Mais ce n'est jamais fait en pratique, car le cout en circuits serait trop important et le gain en performance trop faible. Mieux vaut dupliquer certaines unités bien précises, quitte à avoir des contraintes d'appariement. Et à ce petit jeu, toutes les duplications ne se valent pas, certaines sont plus rentables en matière de performance, le cout en circuit n'est pas le même selon l'unité, etc. La rentabilité en performance dépend surtout des contraintes d'appariement. Les contraintes d'appariement entrainent une baisse de performance significatives si elles interdisent des paires d'instructions fréquentes. Aussi, les opérations très fréquentes, souvent appairées, gagnent à dupliquer l'unité associée. Et au contraire, les unités rarement utilisées ne doivent pas être dupliquées. Il se trouve que les unités les plus utilisées sont les ALU entières, suivies par les unités mémoire et l'unité de branchement. Le cout en circuit n'est aussi pas le même. Dupliquer les ALU entière est trivial, car elles ont un budget en transistors assez faible. Les circuits multiplieurs, diviseurs, et les ''barrel shifter'' ont eux un cout en transistor plus élevés. Si on devait faire un classement, on aurait : ALU entière < ''barrel shifter'' < multiplieur. Pour les unités mémoire, le cout en transistor est là aussi conséquent. Le résultat final est que dupliquer les ALU entières est privilégié sur le reste, comme l'a vu dans la section précédente. Les unités mémoire arrivent en seconde place, suivie par les multiplieurs/''barrel shifter'' à la troisième place, et les unités de branchement à la quatrième place. Les CPU superscalaires larges ont donc tendance à multiplier les unités mémoire en second. Les multiplieurs et ''barrel shifters'' sont eux aussi dupliqués sur les CPU superscalaires larges, mais cela vient souvent après avoir dupliqué les unités mémoire. La raison est que les accès mémoire sont critiques pour la performance des CPU à exécution dans le désordre, pas les multiplications. Pour les unités de branchement, une subtilité complique le fait d'en installer plusieurs. Que faire quand deux branchements sont pris ? La solution la plus simple est de ne pas se trouver dans cette situation : on n'utilise qu'une seule unité de branchement et on n’exécute qu'un seul branchement par cycle. La majorité des processeurs superscalaires étroits font ainsi. Les processeurs superscalaires larges ont cependant deux unités de branchements, mais ils n'acceptent qu'un seul branchement pris à la fois. Si les deux branchements pris sont exécutés en même temps, le processeur n’exécute que le plus récent, dans l'ordre du programme. ===Les CPU superscalaires mélangent les trois techniques=== Les CPU superscalaires utilisent toutes les techniques à la fois : partitionnement, duplication de l'ALU entière, duplication des autre unités fonctionnelles. Chaque processeur utilise ces trois stratégies à sa sauce, en fonction de son budget en transistor. Les quatre techniques ont cependant un cout en circuit différent, et donnent des gains de performances eux aussi forts différents. * Le '''partitionnement superscalaire''' a un cout en circuit modéré, car on n'a pas besoin de dupliquer d'ALU. * La '''duplication des ALU entières''' est la technique la plus importante : elle a un cout en circuit modéré, et donne des gains en performance impressionnants. * La '''duplication des autres unités''' a un cout en circuit variable, pour des gains en performance eux aussi variables. * Le '''partage des ports d'émission''' réduit le cout en circuit du CPU, pour des pertes de performances mineures si bien fait. L'usage du partitionnement seul, sans duplication, était surtout le fait des processeurs commercialisés entre les années 1989 et 1993. Le partage des ports d'émission a lui aussi mis un peu de temps pour s'imposer. Globalement, les processeurs superscalaires ont évolués progressivement, pour se rapprocher d'une sorte de standard. Mais pour comprendre ce qu'est ce standard, et en quoi les CPU historiques s'en sont progressivement rapprochés, il faut étudier l'histoire des CPU superscalaires. L'histoire des CPU superscalaires a commencé dès 1960, mais est resté un projet de recherche pendant plusieurs décennies. De nombreux designs ont été conçus, mais abandonnés avant la production, comme l'ACS-1 d'IBM. Les années 1980 ont vu l'arrivée de prototypes de processeurs VLIW, mais qui n'étaient pas superscalaires. Par exemple, l'Apollo DN10000 et l'Intel i860 pouvaient émettre deux instructions à la fois, sous conditions, mais demandaient que le compilateur fasse une bonne partie du travail. Les premiers CPU superscalaires commercialisés sont sortis en 1989. Deux CPUs superscalaires sont sortis cette année là : le RS/6000 d'IBM et l'i960CA d'Intel. Puis, la compétition s'est aussi lancée dans la bataille. Les CPU commercialisés avant l'année 1993 étaient à double émission, à l'exception du SuperSPARC qui utilisait la triple émission. Les modèles après 1993 hésitaient entre double et quadruple émission. Dans le tableau ci-dessous, les processeurs quadruple émission sont en bleu, les triple émission sont en rouge, les autres ne sont pas en couleur. {|class="wikitable" |- ! !! IBM !! HP PA-RISC !! SPARC !! Intel!! Alpha !! Autres |- ! 1989 | RS/6000 || || || i960CA || || |- ! 1990 | || || || || || |- ! 1991 | || || || || || Motorola 88110 |- ! 1992 | RSC || PA 7100 || class="f_rouge | SuperSPARC || || 21064 || |- ! 1993 | PPC 601 || || HyperSPARC || Pentium || || |- ! 1994 | class="f_bleu | POWER 2 || PA 7200 || || || class="f_bleu | 21164 || |- ! 1995 | 603/604 || class="f_bleu | PA 8000 || class="f_bleu | UltraSPARC 1 || Microarchitecture P6 (+ concurrence AMD) || || class="f_bleu | MIPS R1000 |- ! 1996 | class="f_bleu | 620 || || || || class="f_bleu | 21264 || |- ! ... || ... || ... || ... || ... || ... || ... |} Les premiers CPU à double émission sont assez représentatifs des CPU à double émission modernes. Il faut dire que se limiter à deux voies ne permet pas de faire beaucoup de choses. Voici le tableau chronologique disant quel CPU utilisait quelle technique. * Les CPU en vert utilisaient la double émission entière-flottante. * Les CPU en rouge utilisaient la double émission entière "pure". * Les CPU en jaune utilisaient un ''partitionnement complet'' entre ALU, FPU et unité mémoire. * Les CPU sans couleur utilisaient un grand nombre d'unités, avec beaucoup de ports d'émission. * Les CPU barrés utilisent la triple ou quadruple émission et seront vu dans la section suivante. {|class="wikitable" |- ! !! IBM !! HP PA-RISC !! SPARC !! Intel!! Alpha !! Autres |- ! 1989 | class="f_vert | RS/6000 || || || class="f_rouge | i960CA || || |- ! 1990 | || || || || || |- ! 1991 | || || || || || Motorola 88110 |- ! 1992 | class="f_vert | RSC || class="f_vert | PA 7100 || <s>SuperSPARC</s> || || class="f_jaune | 21064 || |- ! 1993 | class="f_vert | PPC 601 || || class="f_jaune | HyperSPARC || class="f_rouge | Pentium || || |- ! 1994 | <s>POWER 2</s> || PA 7200 || || || <s>21164</s> || |} C'est en 1994, que la quadruple émission est arrivée et que les CPU ont essayé divers implémentations. Il y avait alors trois stratégies pour la double/triple/quadruple émission, qui partageaient des problèmes similaires. La première implémentation dupliquait les ALUs et les FPUs, ce qui permettait d'émettre deux opérations flottantes avec deux opérations entières. Le POWER 2 dupliquait ses FPUs à la perfection, alors que le 21264 séparait l'additionneur et le multiplieur flottant. Les accès mémoire étaient traités comme des opérations entières et le processeur pouvait souvent en émettre deux à la fois. Mais une telle organisation n'est plus utilisée de nos jours, car, sans instructions flottantes à exécuter, elle est équivalente à de la double émission. La seconde implémentation utilisait deux ALU entières (dont un multiplieur), une unité mémoire et une FPU.Cette organisation est intéressante, mais encore trop stricte pour donner de bons résultats, les quadruplets avec exactement deux opérations entières, un accès mémoire et une opération flottante étant trop rares. L'usage de ces 4 unités en double ou triple émission a été utilisé sur le PA 7200 et le SuperSPARC. Malheureusement, en plus de problèmes au niveau de son cache, le SuperSPARC avait de nombreuses contraintes d’appariement, car le banc de registre n'avait pas assez de ports pour alimenter certaines combinaisons d'instructions. De plus, s'il émettait une multiplication, il ne pouvait pas émettre de seconde µop. Enfin, une troisième implémentation utilisait un grand nombre d'unités fort différentes, bien plus que quatre, avec au minimum deux ALU entières. Prenons l'exemple de l'UltraSPARC, qui avait : deux ALU entières, une unité mémoire, une unité de branchement, un additionneur flottant, un multiplieur flottant, un diviseur flottant (aussi utilisé pour le calcul des racines carrées), et 2 unités graphiques pour les calculs sur des pixels. En tout, cela fait 9 unités différentes, pour de la quadruple émission. Dans le même genre, les processeurs MIPS R8000 et R10000 avaient une implémentation plus moderne, dans le sens où toutes les unités étaient dupliquées. Le CPU avait deux ALU, deux FPU et deux unités mémoire. Il pouvait donc émettre quatre instructions à la fois, tant que le paquet collait avec ces six unités. Moitié opérations entières, moitié opérations mémoire, avec de quoi remplacer les opérations entières par des opérations flottantes. Le PA 8000, lui, dupliquait toutes ses unités en deux exemplaires, ce qui n'est pas l'idéal, aucun processeur moderne ne fait cela aujourd'hui. Le tout est résumé dans le tableau ci-dessous, avec : * en vert les CPU utilisant la première stratégie ; * en rouge les CPU utilisant la seconde stratégie ; * en jaune les CPU utilisant la troisième stratégie. {|class="wikitable" |+ Processeurs quadruple émission historiques |- ! Année !! Processeur !! Unités |- ! 1991 | class="f_jaune | Motorola 88110 || class="f_jaune | 2 ALU + MUL + DIV + BIT + MEM + FADD + 2 unités graphiques |- |- ! rowspan="2" | 1994 | class="f_vert | POWER 2 || class="f_vert | 2 ALU/MEM + 2 FPU (+ 2 Branchements via ''branch folding'') |- | class="f_rouge | 21164 || class="f_rouge | 2 ALU + FPU + MEM |- ! rowspan="3" | 1995 | PA 8000 || 2 ALU + 2 ''barrel shifters'' + 2 MEM + 2 FPU (FMA) + 2 FPU (DIV et SQRT) + 2 SIMD |- | class="f_jaune | UltraSPARC 1 || class="f_jaune | 2 ALU + MEM + BRANCH + FADD + FMULFDIV + 2 unités graphiques |- | MIPS R8000 et R10000 || 2 ALU + 2 FPU + 2 MEM |- ! rowspan="2" | 1996 | class="f_rouge | 620 || class="f_rouge | 2 ALU + FPU + MEM |- | class="f_vert | 21264 || class="f_vert | 2 ALU/MEM + 2 FPU (FADD + FMUL) |} Malheureusement, ces CPUs historiques n'avaient ni assez d'unités de calcul entières, ni d'unités mémoire, pour remplir la quadruple émission. Environ la moitié des instructions d'un programme correspond à des instructions entières. Pour en profiter, un processeur à N voies doit disposer d'au minimum N/2 ALU entières, l'idéal étant d'aller au-delà. Les CPU superscalaires historiques avaient 2 ALU pour de la quadruple émission, ce qui était très limite. En comparaison, les CPU superscalaires modernes mettent le paquet niveau ALU entière. La plupart des CPU x86 modernes descendent rarement sous les 2/3, la plupart remplit presque la totalité de la largeur du CPU. Pour donner un exemple, la microarchitecture Golden Cove d'Intel est un processeur à 6 voies, avec 5 ALU entières. Pour être précis, un programme informatique exécute environ 40% de son code dans des instructions arithmétiques, un autre 40% dans les accès mémoire, et un le reste pour... le reste. La conséquence est que les processeurs modernes dupliquent leurs unités mémoire. Par exemple, la microarchitecture ''Golden Cove'' d'Intel dispose de trois unités mémoire : deux spécialisées dans les écritures, trois dans les lectures. La microarchitecture ''Gracemont'' utilise la pentuple émission et peut émettre 4 µops entières et 4 µops mémoire. Notez que le ratio est équilibré entre opérations entières et mémoire, avec soit égalité, soit un avantage pour les ALU entières. Une autre régularité est que la largeur totale du processeur peut être occupée par des opérations entières/mémoire. Pour le dire autrement, sur un processeur quadruple émission, il pourra émettre un mix de 4 instructions entière/mémoire, avec zéro opération flottante. Idem avec des processeurs triples ou pentuple émission, voire plus. Si le processeur émet une opération flottante, elle remplace une opération entière. Prenons l'exemple des processeurs AMD K6, sortis en 1997, juste avant les AMD Athlon. Ils avaient 10 unités de calcul, 6 ports d'émission, 4 ports de décodage. Voici exactement comment étaient répartie les unités de calcul sur les ports d'émission. C'était des processeurs quadruple émission, ce qui fait que la moitié de la largeur du processeur sert pour des opérations entières, et l'autre moitié pour les accès mémoire. {|class="wikitable" |- ! Port d'émission n°1 !! Port d'émission n°2 !! Port d'émission n°3 !! Port d'émission n°4 !! Port d'émission n°5 !! Port d'émission n°6 ! |- | ALU entière || ALU entière || FPU || Unité de branchement || Unité LOAD || Unité STORE |- | || Multiplieur || || || || Unité de calcul d'adresse (instruction LEA) |- | || ''Barrel shifter'' || || || || Unité pour les instructions PUSH |} Les microarchitectures K7, K8 et K10, utilisées sur les anciens CPU AMD Athlon, sont un exemple moins représentatif. Ces microarchitectures utilisent le partage des ports d'émission au maximum. C'était des microarchitectures à triple émission, avec six ports d’émission. Il y avait trois ports d'émission pour les opérations flottantes, et trois ports d'émission pour les micro-opérations entières/mémoire. Il y avait trois ALU entières et trois unités mémoire, chaque ALU partageait un port d'émission avec une unité mémoire. Elle pouvait donc émettre soit trois opérations entières, soit trois µops mémoire, soit un mix des deux. En plus des mix avec les opérations flottantes et branchements. {|class="wikitable" |- ! Port d'émission n°1 !! Port d'émission n°2 !! Port d'émission n°3 !! Port d'émission n°4 !! Port d'émission n°5 !! Port d'émission n°6 ! |- | ALU entière || ALU entière || ALU entière || FADD || FMUL || FPU (autres opérations) |- | Multiplieur || || || || || |- | LOAD/STORE || LOAD/STORE || LOAD/STORE || || || |} Les CPU superscalaires larges dupliquent les multiplieurs et/ou les ''barrel shifters'', et même les unités de branchement. La raison est que quand la largeur du CPU augmente, le processeur traite des paquets d'instructions de plus en plus gros. Par exemple, pour un CPU à 10 voies, cela veut dire émettre 10 instructions en même temps. Il est alors plus fréquent d'avoir deux multiplications, deux décalages, deux accès mémoire, deux branchements. Par exemple, la moyenne est d'une multiplication tous les 5 instructions. Les CPU superscalaires à 10 voies ont donc tendance à avoir deux multiplications par paquet d'instruction, en moyenne. Avoir deux multiplieurs est alors un gain en performance clair. Par exemple, la microarchitecture ''Golden Cove'' a deux ''barrel shifters''. C'est une architecture à 6 voies. {|class="wikitable" |- ! Port d'émission n°1 !! Port d'émission n°2 !! Port d'émission n°3 !! Port d'émission n°4 !! Port d'émission n°5 !! Port d'émission n°6 !! Port d'émission n°7 !! Port d'émission n°8 !! Port d'émission n°9 !! Port d'émission n°10 |- | ALU entière || ALU entière || ALU entière || ALU entière || ALU entière || LOAD || LOAD || LOAD || STORE || STORE |- | ''Barrel shifter'' || Multiplieur || Multiplieur || ''Barrel shifter'' || || || || || || |- | Branchements || Diviseur || || Branchements || || || || || || |} ==Les µops mémoire sur les processeurs superscalaires== Plus haut, nous avons volontairement passé sous silence les accès mémoire. La raison à cela est que leur support sur les CPU superscalaire peut se faire de plusieurs manières différentes, qu'il vaut mieux expliquer dans une section dédiée. Les différentes implémentations sont décrites dans la liste suivante, nous allons les voir une par une. * Avec la première méthode, les accès mémoire sont exécutés par le pipeline pour les opérations entières. * Avec la seconde méthode, les accès mémoire ont un pipeline dédié ; c'est l'''émission parallèle des accès mémoire''. * Avec la troisième méthode, il est possible d'émettre plusieurs accès mémoire simultanément : c'est l'''émission multiple des accès mémoire''. Voyons ces trois cas dans le détail. Nous verrons notamment les différentes formes d'émission multiple des accès mémoire. ===Les accès mémoire traités dans le pipeline entier=== Dans le cas le plus simple, les accès mémoire sont traités comme des opérations entières un peu spéciales. L'unité mémoire est sur le même port d'émission que l'ALU entière, ce qui fait que les accès mémoire remplacent des opérations entières. Faire ainsi permet d'utiliser une ALU entière pour les calculs d'adresse, pas besoin de rajouter une unité de calcul d'adresse séparée. Typiquement, un processeur de ce type place l'unité mémoire après l'unité de calcul. Les opérations entières n'utilisent que l'ALU entière, mais pas l'unité mémoire. Les accès mémoire exécutent un calcul d'adresse dans l'ALU entière, puis l'adresse calculée est envoyée à l'unité mémoire. Le tout est illustré ci-dessous, dans le cas d'un processeur historique à double émission entière-flottante. [[File:Processeur non-superscalaire basique 01.png|centre|vignette|upright=1.5|Processeur non-superscalaire basique]] Un exemple de CPU double émission de ce type est celui du Power PC 440, un CPU destiné à l'embarqué. Vu qu'il était destiné à l'embarqué, il n'avait pas de FPU, il n'avait pas besoin de manipuler de nombres flottants. Il n'a donc que des ALU entières, un multiplieur et une unité mémoire. Il dispose de deux ports d'émission. Le premier est connecté à une ALU entière et un circuit multiplieur, le second est relié à l'unité mémoire et une seconde ALU entière. Une organisation simple, mais très efficace ! [[File:PowerPC 440.png|centre|vignette|upright=2|Microarchitecture du PowerPC 440.]] L'implémentation économise une unité de calcul d'adresse, mais n'a pas de cout en performance significatif. Les accès mémoire étant très lents, émettre une µop entière en parallèle d'un accès mémoire ne sert pas à grand-chose. On peut émettre cette µop entière un cycle plus tard, elle sera terminée avant même que l'accès mémoire soit terminé, même si l'accès mémoire se fait dans le cache L1 ou L2. Pour comprendre pourquoi, faisons quelques calculs. Un accès mémoire prend deux-trois cycles quand il tombe dans le cache L1. Et on doit attendre qu'il soit terminé pour en émettre un nouveau. Combien d'instructions arithmétiques peut-on lancer pendant ce temps, sur un CPU triple émission ? Au premier cycle, on peut en émettre 2 (l'instruction mémoire prend une place, donc 3-1), puis 3 au second cycle. Ce qui fait 5 instructions arithmétiques au total. Or, il se trouve qu'il y a une instruction d'accès mémoire pour 5 instructions arithmétiques, environ. Par contre, ce compromis change quand on passe à la quadruple émission, voire au-delà. Dans ce cas, il devient intéressant de pouvoir émettre une nouvelle instruction mémoire à chaque cycle. Ce qui nous amène à la section suivante... ===L'émission parallèle des accès mémoire=== A partir de la quadruple émission, la tendance a été de séparer l'unité mémoire des ALU entières. Cela permet d'émettre des µops mémoire en même temps que des µops entières et/ou flottantes., ce qui porte le nom d''''émission parallèle des µops mémoire''', ou encore ''émission mémoire parallèle''. Par exemple, un processeur quadruple émission peut émettre en même temps deux opérations entières, une opération flottante et un accès mémoire. L'accès mémoire vient en plus des opérations entières, il n'est pas possible de remplacer l'accès mémoire par une opération entière. La technique ne permet pas de faire les calculs d'adresse dans l'ALU entière. Il faut donc utiliser une unité de calcul d'adresse séparée. Elle est intégrée dans l'unité mémoire, qui fait l'interface entre le cache et le banc de registres. Le cout en circuits est très faible, car les unités de calcul d'adresse n'utilisent guère plus qu'un additionneur et un circuit de décalage très simple. L'émission mémoire parallèle est plus facile à implémenter sur les CPU RISC, qui ont des instructions mémoire séparées du reste. Les CPU CISC ne sont pas dans ce cas, ce qui fait que séparer l'unité mémoire du pipeline entier est compliqué. Les CPU superscalaires historiques étaient pour moité des CPU RISC et pour moitié des CPU CISC, ce qui fait qu'ils n'utilisaient pas ou peu l'émission mémoire parallèle. Les premiers processeurs superscalaires se contenaient de l'émission parallèle mémoire, ils ne pouvaient émettre qu'un seul accès mémoire à la fois, l'unité mémoire n'était pas dupliquée. Et ce n'est pas tant un problème que ça, du moins sur les processeurs à double ou triple émission. En effet, il est rare que l'unité mémoire doive faire deux accès simultanés. Notons que le processeur peut exécuter une instruction mémoire en parallèle d'un calcul ou d'un branchement, seuls les accès mémoire simultanés sont impossibles. ===L'émission multiple des accès mémoire avec une LSQ=== Les processeurs superscalaires larges travaillent sur des paquets de 8 à 10 instructions, il est plus fréquent d'avoir plusieurs accès mémoire par paquet. Dans ces conditions, il est utile d'avoir une '''émission multiple des accès mémoire''', à savoir d'émettre plusieurs micro-opérations mémoire en même temps. Les processeurs superscalaires modernes sont capables d'émettre plusieurs lectures/écritures simultanément. Par exemple, ils peuvent émettre une lecture en même temps qu'une écriture, ou plusieurs lectures, ou plusieurs écritures. Il faut noter que selon le processeur, il peut y avoir des restrictions quant aux accès mémoire émis en même temps. Par exemple, certains processeurs peuvent émettre une lecture avec une écriture en même temps, mais pas deux lectures ni deux écritures. Ou encore, ils peuvent émettre deux lectures, une lecture et une écriture, mais pas deux écritures en même temps. Dans la majorité des cas, les processeurs ne permettent pas d'émettre deux écritures en même temps, alors qu'ils supportent plusieurs lectures. Il faut dire que les lectures sont plus fréquentes que les écritures. Les processeurs qui autorisent toutes les combinaisons de lecture/écriture possibles, sont rares. L'émission multiple des accès mémoire s'implémente dans l'unité mémoire. Pour rappel, l'unité mémoire s'interpose entre le cache L1 et le reste du pipeline. Dans le cas le plus simple, elle incorpore une ''load/store queue'' et des unités de calcul d'adresse. L'émission multiple des accès mémoire demande de rendre la ''Load-store queue'' multi-ports, afin de gérer plusieurs micro-opérations mémoire simultanées. Il faut aussi dupliquer les unités de calcul d'adresse reliées à la LSQ, chaque port d'émission mémoire devant avoir sa propre unité de calcul d'adresse. : Dans ce qui suit, nous parlerons d'AGU (''Adress Generation Unit'') pour désigner les unités de calcul d'adresse, et de LSQ pour designer la ''Load-store queue''. [[File:Processeur non-superscalaire basique 02.png|vignette|upright=1|Processeur non-superscalaire basique]] Dupliquer les ports de la LSQ et les AGU est nécessaire. Mais qu'en est-il du cache ? La réponse varie suivant le processeur. Il y a deux grandes méthodes pour implémenter l'émission multiple des accès mémoire. La première méthode ajoute des ports de lecture/écriture au cache de données, pour faire des lectures/écritures simultanées. Il faut alors dupliquer des unités de calcul, rajouter les ports sur le cache, mais aussi rajouter des ports sur la ''load/store queue'', pour qu'elle échange des données/commandes mémoire avec le cache. Le cout en circuits est alors conséquent La seconde duplique les unités de calcul, mais pas les ports d'accès au cache. Les accès mémoire sont alors accumulés dans la ''load/store queue'', qui sont ensuite traités un par un par le cache. Un exemple est celui des processeurs AMD de micro-architecture K7 K8, à savoir les anciens AMD Athlon et AMD Athlon 64, qui pouvaient calculer trois adresses simultanément, mais ne pouvaient faire qu'un seul accès au cache par cycle. Le cout en circuit est modéré : les unités de calcul coutent très peu, les ports de la ''load/store queue'' coutent déjà un peu plus. Quelques processeurs utilisaient des solutions intermédiaires. Un processeur utilisant cette technique est, par exemple, capable d'émettre quatre µops mémoire en même temps, mais avec seulement deux ports sur le cache. Un exemple classique étant les Pentium 1 et 2 d'Intel, dont nous parlerons dans le chapitre suivant. ===L'émission séparée des LOAD et STORE=== De rares processeurs ont des ports d'émission qui peuvent faire indifféremment lecture comme écritures. Mais les processeurs modernes préfèrent séparer ports d'émission pour les lectures et les écritures. Un exemple est celui des processeurs Intel de micro-architecture Nehalem, qui avaient un port de lecture et un port d'écriture tous deux reliés à la ''Load-store queue'', chacun avec leur propre unité de calcul d'adresse. Comme autre exemple, les processeurs skylake ont une LSQ avec deux ports de lecture et un port d'écriture, ce qui permet d'émettre deux lectures en même temps qu'une écriture. Dans cette section, nous allons étudier le cas avec des ports séparés pour les lectures et écritures. En général, les processeurs ont un peu plus de ports de lectures que d'écritures. Une raison est qu'un programme a, en moyenne, un tout petit peu plus de lectures que d'écritures. Mais la différence n'est pas énorme, c'est un léger déséquilibre. Une seconde raison est que l'exécution dans le désordre marche bien mieux quand les lectures ont lieu le plus tôt possible. Donc mieux vaut privilégier les lectures à l'émission, pour qu'elles se fassent un peu en avance. En pratique, la différence n'est pas très marquée. Il est courant d'avoir un port de lecture et un d'écriture, les CPU superscalaires très larges ont souvent deux ports de lecture et deux d'écriture. A la rigueur, quelques CPU ont trois ports de lecture et deux d'écriture. Le cas le plus simple est celui où on peut émettre une lecture et une écriture en même temps. Il s'implémente facilement si la LSQ est en réalité une simple ''Store queue'', à savoir qu'elle met en avance les écritures uniquement. Pour rappel, les écritures sont mises en attente dans la ''Store queue'', mais les lectures sont exécutées immédiatement. Une fois l'adresse à lire connue, la lecture consulte la ''Store queue'' pour détecter les dépendances de données, puis s'exécute. La donnée à lire est lue soit depuis la ''Store queue'', soit depuis le cache. [[File:Emission LOAD - Store séparée.png|vignette|upright=1|Émission LOAD - Store séparée]] L'implémentation de l'émission multiple est alors très simple. La ''Store queue'' a déjà deux ports, avec un pour les lectures et un autre pour les écritures. Idem pour le cache, qui a naturellement un port de lecture et un d'écriture. Émettre une lecture et une écriture simultanément ne demande donc pas de modification digne de ce nom. La ''Store queue'' doit juste supporter une lecture et une écriture simultanée. Ajouter l'émission d'une seconde lecture n'est pas compliquée : il faut juste rajouter un second port de lecture sur le cache et la ''Store queue''. Pour rappel, l'unité mémoire est précédée par une structure qui met en attente les micro-opérations mémoire avant leur émission finale. La structure en question varie suivant l'implémentation, mais un cas particulier se marie bien avec l'émission multiple des µops mémoire. Il s'agit du cas avec une file de µops séparée pour les lectures et une autre pour les écritures, appelées respectivement la ''file de µops LOAD'' et la ''file de µops STORE''. Il est alors possible d'émettre une lecture et une écriture lors du même cycle d'horloge. Le port de lecture alimente une unité de calcul d'adresse dédiée, directement reliée au cache. Le port d'écriture du cache alimente une unité de calcul, qui est suivie par une file d'écriture, elle-même reliée au cache. Les processeurs AMD de micro-architecture K6 utilisaient cette technique. [[File:Double émission avec le Load Ordering, Store Ordering.jpg|centre|vignette|upright=2.5|Double émission avec le Load Ordering, Store Ordering]] ==Les CPU superscalaire à exécution dans le désordre== La superscalarité modifie en profondeur le processeur. Et cela dépasse le cadre des unités vues auparavant. Dans cette section, nous allons voir comment les autres étages du pipeline sont modifiées sur un CPU superscalaire. ===L'unité de renommage superscalaire=== Avec la superscalarité, l'unité de renommage de registre doit gérer le cas où des instructions consécutives ont des dépendances de registre. Par exemple, prenons un processeur à double émission, qui renomme deux instructions consécutives. Si elles ont une dépendance de registre, le renommage de la seconde instruction dépend du renommage de la première. La première doit être renommée et le résultat du renommage est utilisé pour renommer la seconde, ce qui empêche d'utiliser deux unités de renommage séparées. Pour cela, elle renomme les registres sans tenir compte des dépendances, pour ensuite corriger le résultat. [[File:Unité de renommage superscalaire.png|centre|vignette|upright=2|Unité de renommage superscalaire.]] Seules les dépendances lecture-après-écriture doivent être détectées, les autres étant supprimées par le renommage de registres. Repérer ce genre de dépendances se fait assez simplement : il suffit de regarder si un registre de destination d'une instruction est un opérande d'une instruction suivante. [[File:Détection des dépendances sur un processeur superscalaire.png|centre|vignette|upright=2|Détection des dépendances sur un processeur superscalaire.]] Ensuite, il faut corriger le résultat du renommage en fonction des dépendances. Si une instruction n'a pas de dépendance avec une autre, on la laisse telle quelle. Dans le cas contraire, un registre opérande sera identique avec le registre de destination d'une instruction précédente. Dans ce cas, le registre opérande n'est pas le bon après renommage : on doit le remplacer par le registre de destination de l'instruction avec laquelle il y a dépendance. Cela se fait simplement en utilisant un multiplexeur dont les entrées sont reliées à l'unité de détection des dépendances. On doit faire ce replacement pour chaque registre opérande. [[File:Correction des dépendances sur un processeur superscalaire.png|centre|vignette|upright=2|Correction des dépendances sur un processeur superscalaire.]] ===Les unités d'exécution dans le désordre=== Réduire le nombre de ports d'émission permet de garder des fenêtres d'instruction et un ROB simples, rapides et économes en énergie. Et mine de rien, c'est là un compromis important : à quoi bon augmenter le nombre de ports d'émission si c'est pour que ce soit compensé par une fréquence plus faible ? Par contre, réduire le nombre de ports augmente la ''port contention'', surtout si on a mal réparti les ALU. Il faut donc trouver un compromis entre réduire le nombre de ports d'émission d'un côté, réduire la ''port contention'' de l'autre. Le ROB doit aussi être modifié pour émettre et terminer plusieurs instructions en même temps, en ajoutant des ports de lecture/écriture. Pour un CPU superscalaire peut émettre 8 µops à la fois, on se dit que le ROB peut retirer 8 µops par cycle maximum. Et c'est très souvent le cas en pratique. Cependant, certains processeurs ont un ROB qui peut retirer le double ! Un exemple est celui des CPU de micro-architecture Skymont, qui peuvent émettre 8 µops par cycle, mais en retirer 16. Quelques CPU de marque AMD étaient aussi dans ce cas. L'avantage est que cela permet de réduire la taille du ROB, du banc de registre et de quelques autres structures matérielles liées à l'exécution dans le désordre. Le gain sur-compense le cout en transistor nécessaire pour retirer 16 µops par cycle. Un détail est que le nombre de ports de lecture n'est pas le même selon que le processeur utilisé des stations de réservation ou une fenêtre d’instruction. Rappelons la terminologie utilisée dans ce cours. Les fenêtres d'instruction se contentent de mémoriser la micro-opération à émettre et quelques bits pour la disponibilité des opérandes. Par contre, les stations de réservations mémorisent aussi les opérandes des instructions. Et cette différence a une influence le nombre de ports de lecture du banc de registre. Supposons que toutes les instructions sont dyadiques, à deux opérandes. Comptons le nombre de ports nécessaires pour alimenter les unités de calcul, sans utiliser d'arbitre de banc de registre. Avec une fenêtre d'instruction, il faut le double du nombre de ports de ''dispatch''. Mais avec des stations de réservation, il faut le double des ports de ''schedule''. La raison est que les registres sont lus après émission avec une fenêtre d'instruction, avant avec des stations de réservation (sous-entendu, après le décodage). or, comme dit plus haut, les processeurs superscalaires larges ont souvent plus de ports de ''schedule'' que de ''dispatch''. ==Les optimisations des processeurs superscalaires larges== Les processeurs superscalaires larges ont des optimisations spécifiques, qui n'ont pas de sens sur les processeurs superscalaires étroits. Dans les grandes lignes, elles se classent en deux types. Les premières permettent de charger/décoder plus de 5-10 instructions simultanément. Le problème est alors que les branchements viennent mettre leur grain de sel et qu'il faut en tenir compte. Les secondes corrigent des problèmes qui apparaissent quand un CPU intègre plus de 5 à 10 unités de calcul. ===L'étape de chargement d'un CPU superscalaire large=== Charger plusieurs instructions pose de nombreux problèmes sur les processeurs superscalaires larges, qui émettent de 5 à 10 instructions simultanées. Pour rappel, la stratégie basique pour charger N instructions est de charger un bloc de N instructions consécutives. Les instructions après un branchement ne sont pas décodées ni exécutées, ce qui entraine une petite baisse de performance. Pour un N petit, la perte de performance est mineure. Mais pour un N large, elle devient suffisamment importante pour que la conception du CPU doit alors être repensée de fond en comble. Les analyses statistiques disent qu'il y a environ un branchement tous les 4 à 6 instructions. Un CPU a donc tendance à décoder des paquets de 3 à 5 instructions consécutives, avant qu'un branchement casse la dynamique. Or, cela pose des problèmes pour utiliser plusieurs décodeurs. L'implémentation basique, qui charge N instructions consécutives pour alimenter N décodeurs, fonctionne donc jusqu'à 4/6 décodeurs. Au-delà de 5 instructions, un CPU superscalaire doit être adapté pour gérer les branchements dans les paquets d'instruction qu'il émet. Le nombre de 5 instructions ne sort donc pas de nulle part. La première solution utilise un cache de µops, avec un ''front-end'' capable de charger/décoder 4/5 instructions à la fois, pas plus. L'idée est que la majorité des instructions proviennent du cache de µops (plus de 80%), ce qui fait qu'on peut émettre environ 10 instructions à partir du cache de µops, pas besoin d'avoir beaucoup de décodeurs. Les performances sont cependant dégradées lors d'un défaut de cache de µops. Les instructions doivent alors être chargées depuis le cache d'instruction, par paquets de 4-5, puis sont décodées par 4-5 décodeurs, ce qui ne permet pas d’alimenter les unités de calcul à plein régime. La solution précédente joue sur la distinction entre pots de décodage et d'émission. Elle permet d'émettre un grand nombre de µops sans pour autant charger un grand nombre d'instructions en même temps. En quelque sorte, ce n'est qu'une méthode de contournement, le CPU superscalaire reste de type étroit. Mais il est aussi possible d'utiliser un '''''front-end large''''', capable de charger une bonne dizaine d'instructions en même temps. La seule difficulté est de tenir compte de la présence de branchements. Pour rappel, avec un ''front-end'' étroit, les instructions situées après le premier branchement pris sont annulées, elles ne sont pas décodées ni exécutées. [[File:Fetch sur un processeur superscalaire avec prediction de branchements.png|centre|vignette|upright=2|Fetch sur un processeur superscalaire avec prédiction de branchements.]] Un ''front-end'' large charge les instructions de destination du branchement et les placent à sa suite. Ils chargent deux blocs à la fois et les fusionnent en un seul qui ne contient que les instructions présumées utiles. [[File:Cache d'instructions autoaligné.png|centre|vignette|upright=2|Cache d'instructions auto-aligné.]] Mais cela demande de charger deux blocs de mémoire en une fois, ce qui demande un cache d'instruction multiports. Il faut aussi ajouter un circuit pour assembler plusieurs morceaux de blocs en un seul : le fusionneur (''merger''). En pratique, cette solution demande d'ajouter un second port de lecture au cache, pour un cout en hardware important, avec un impact négatif sur le temps d'accès au cache. Et il faut rajouter le fusionneur, qui prend un étage de pipeline à lui tout seul. Et encore, cette solution ne gère pas le cas où un bloc contient plusieurs branchements ! [[File:Implémentation d'un cache d'instructions autoaligné.png|centre|vignette|upright=2|Implémentation d'un cache d'instructions auto-aligné.]] Les exemples précédents partent du principe qu'il n'y a qu'un seul branchement dans un bloc. Mais quand les blocs deviennent assez longs, il est fréquent d'avoir plusieurs branchements dans un bloc. Et il est même possible d'avoir plusieurs branchements pris. Dans un bloc de 8-10 instructions, ce n'est pas rare, et les CPU superscalaires à 8-10 voies sont la règle dans les CPU des années 2020. Pour gérer ce cas, il faut ajouter des ports supplémentaires au cache d'instruction et adapter le fusionneur, pour qu'il puisse fusionner trois ou quatre blocs. De plus, il faut utiliser une unité de prédiction de branchement capable de ''prédire plusieurs branchements par cycle''. ===Le décodage superscalaire large=== Le décodage des instructions n'est pas censé dépendre de l'implémentation du chargement. En théorie, une fois plusieurs blocs d'instruction fusionnés, on peut décoder les instructions en parallèle. Cependant, certains processeurs effectuent le décodage en tenant compte de la manière dont les branchements découpent les blocs. Les processeurs Atom d'Intel utilisent une solution de ce type, qui couple prédiction de branchement et décodage parallèle. Ils chargent/décodent des paquets de 3 instructions consécutives, chaque paquet étant séparé du précédent par un branchement. Pour cela, en plus d'avoir un fusionneur adapté, ils regroupent les décodeurs en ''clusters'', chacun regroupant 3 décodeurs, et pouvant décoder trois instructions consécutives. Les micro-architectures Tremont et Crestmont avaient deux ''clusters'' (6 décodeurs au total), la micro-architecture Skymont en intègre trois (9 décodeurs au total). Avec deux ''clusters'', le fonctionnement est le suivant. Si le code est composé d'une suite d'instruction sans branchements, seul le premier ''cluster'' est utilisé, limitant le décodage à trois instructions par cycle. Par contre, si l'une des trois instructions décodée est un branchement, alors le second ''cluster'' décode les trois instructions situées après ce branchement, à partir de l'adresse de destination. Avec un troisième ''cluster'', c'est la même chose, sauf qu'il faut qu'il y ait un branchement dans les trois instructions décodées par le second ''cluster''. Mais comment faire pour savoir quels décodeurs activés, quels ''clusters'' sont actifs ? C'est la prédiction de branchement qui commande. Avec deux ''clusters'', elle détermine si un branchement est présent dans les trois instructions à charger, et quelle est son adresse de destination. Sans branchement, elle prévient les décodeurs et les trois instructions chargées sont décodées dans un ''cluster'' libre. Si elle prédit un branchement, elle charge les instructions adéquates, et active les deux ''clusters''. Avec trois branchements, c'est le même mécanisme, sauf que l'unité de prédiction de branchement doit être capable de prédire deux branchements par cycle. Il faut noter que les décodeurs d'un ''cluster'' sont des décodeurs simples, sans microcode. Sur les processeurs Tremont et Crestmont, le microcode était unique et était partagé entre tous les ''clusters''. Un seul ''cluster'' pouvait décoder une instruction complexe avec le microcode, l'autre devait se limiter à des instructions simples uniquement. Depuis Skymont, le microcode est partiellement dupliqué, avec un microcode par ''cluster''. Le cout en hardware peut paraitre prohibitif, pour un gain en performance faible. Sauf que c'est oublier que ce sont des processeurs Atom, sur lesquels beaucoup d'instructions sont microcodées. Les processeurs Atom ont un budget en transistor limité, ce qui fait qu'il est préférable pour eux d'implémenter des instructions en microcode que d'utiliser des unités de calcul dédiées ou des circuits de décodage complexes. Pour limiter la casse, le microcode n'est que partiellement dupliqué. Seul le microcode pour les instructions fréquente est dupliqué. Il y a un microcode secondaire pour certaines instructions, qui est lui unique et partagé par tous les ''clusters''. ===Le contournement sur les processeurs superscalaires larges=== Pour rappel, la technique du contournement (''register bypass'') permet au résultat d'une instruction d'être immédiatement utilisable en sortie de l'ALU, avant même d'être enregistré dans les registres. Implémenter la technique du contournement demande d'utiliser des multiplexeurs pour relier la sortie de l'unité de calcul sur son entrée si besoin. Il faut aussi des comparateurs pour détecter des dépendances de données. [[File:Pipeline Bypass.png|centre|vignette|upright=1|Pipeline Bypass]] La sortie de chaque ALU doit être reliée aux entrées de toutes les autres, avec les comparateurs qui vont avec. Sur les processeurs ayant plusieurs d'unités de calculs, cela demande beaucoup d’interconnexions et de comparateurs. Pour N unités de calcul, cela demande 2 * N² interconnexions, implémentées avec 2N multiplexeurs de N entrées chacun. Si c'est faisable pour 2 ou 3 ALUs, la solution est impraticable au-delà. Non seulement ce serait un enfer à câbler, mais les fils seraient tellement longs que la fréquence du processeur en prendrait un coup. Notez que cela vaut pour les processeurs superscalaires, mais aussi pour tout processeur avec beaucoup d'unités de calcul. En théorie, un CPU à exécution dans le désordre, non-superscalaire, mais avec beaucoup d'unités de calcul, fait face au même problème. En pratique, avoir une dizaine d'ALU implique processeur superscalaire à exécution dans le désordre. D'où le fait qu'on parle du problème maintenant. La seule solution praticable est de ne pas relier toutes les unités de calcul ensemble. À la place, on préfère regrouper les unités de calcul dans différents '''agglomérats''' ('''cluster'''). Le contournement est alors possible entre les unités d'un même agglomérat, mais pas entre agglomérats différents. Évidemment, certaines possibilités de contournement sont perdues, mais c'est compensé par un gain en fréquence et un cout en circuit/fils réduit. C'est un bon compromis, ce qui explique que presque tous les processeurs modernes l'utilisent. Les rares exceptions sont les processeurs POWER 4 et POWER 5, qui ont préféré se passer de contournement pour garder un processeur très simple et une fréquence élevée. ===Le banc de registre des processeurs superscalaires larges=== Pour émettre deux opérations entières simultanément, il faut lire deux fois plus d'opérandes. Et il faut aussi écrire deux résultats dans les registres, en même temps. Pour cela, on double les ports du banc de registre. En général, il faut au maximum 2 ports de lecture et un port d'écriture par unité de calcul. Le problème, c'est que plus un banc de registres a de ports, plus il utilise de circuits, est compliqué à concevoir, consomme de courant et chauffe. Avec plusieurs dizaines d'unités de calcul différentes, ajouter autant de ports impacterait la fréquence du banc de registres et réduirait les performances, voire est totalement impraticable à cause des contraintes thermiques. Les processeurs superscalaires larges doivent donc trouver des solutions. La première solution utilise un banc de registre unique, mais n'utilise pas autant de ports que le pire des cas le demanderait. Le processeur détecte quand il n'y a pas assez de ports pour servir toutes les micro-opérations. Quand ça arrive, l'unité d'émission émet assez de micro-opérations pour exploiter les ports disponibles, mais met en attente les micro-opérations en trop, le temps que les ports se libèrent. Pour cela, le banc de registre est géré par un circuit d'arbitrage spécialisé, intégré à l'unité d'émission, l’'''arbitre du banc de registres''' (''register file arbiter''). Une autre solution est de dupliquer le banc de registres en plusieurs exemplaires qui contiennent exactement les mêmes données, mais avec moins de ports de lecture/écriture. Un exemple est celui des processeurs POWER, Alpha 21264 et Alpha 21464. Sur ces processeurs, le banc de registre est dupliqué en plusieurs exemplaires, qui contiennent exactement les mêmes données. Les écritures dans les registres sont envoyées à tous les bancs de registres, afin de garantir que leur contenu est identique. Chaque exemplaire a exactement deux ports de lecture. La conception du processeur est simplifiée, que ce soit au niveau du câblage, que de la conception des bancs de registres. Une dernière solution découpe le banc de registres en plusieurs bancs de registres plus petits, avec un réseau d'interconnexions pour échanger des données entre bancs de registres. Dans la plupart des cas, cette séparation est invisible du point de vue de l'assembleur et du langage machine. Le processeur se charge de transférer les données entre bancs de registres suivant les besoins. Sur d'autres processeurs, les transferts de données se font via une instruction spéciale, souvent appelée ''COPY''. [[File:Banc de registres distribué.png|centre|vignette|upright=2|Banc de registres distribué.]] Sur de certains processeurs, un branchement est exécuté par une unité de calcul spécialisée. Or les registres à lire pour déterminer l'adresse de destination du branchement ne sont pas forcément dans le même agglomérat que cette unité de calcul. Pour éviter cela, certains processeurs disposent d'une unité de calcul des branchements dans chaque agglomérat. Dans les cas où plusieurs unités veulent modifier le ''program counter'' en même temps, un système de contrôle général décide quelle unité a la priorité sur les autres. Mais d'autres processeurs fonctionnent autrement : seul un agglomérat possède une unité de branchement, qui peut recevoir des résultats de tests de toutes les autres unités de calcul, quel que soit l’agglomérat. ==La macro-fusion : une optimisation du décodage parallèle== Les processeurs superscalaires supportent la technique dite de '''macro-fusion''', qui permet de fusionner deux-trois instructions consécutives en une seule micro-opération. Par exemple, il est possible fusionner une instruction de test et une instruction de saut en une seule micro-opération de branchement. Il s'agit là de l'utilisation la plus importante de la macro-fusion sur les processeurs x86 modernes. La macro-fusion est utilisée pour fusionner un test et un saut en un branchement unique. L'optimisation est présente sur la plupart des CPU superscalaires modernes. Les CPU x86 l'implémentent, mais aussi quelques CPU ARM et les processeurs Apple de série M (M1 à M5, et potentiellement plus quand ils seront sortis). La fusion des instructions se fait lors du décodage des instructions, grâce à la coopération des décodeurs. Elle marche systématiquement si l'instruction de test et le saut sont consécutives, qu'elles forment bien une paire d'instruction. Par contre, insérer une instruction entre les deux empêche la macro-fusion de faire son travail, sauf si l'instruction insérée est un NOP. Les processeurs ARM64 avaient une forme spécifique de macro-fusion, utilisée pour un cas très précis : charger une constante immédiate de 32 ou 64 bits, dans un registre 64 bits. Les CPU ARM 64 bits utilisent des instructions codées sur 32 bits, qui ne peuvent encoder que des constantes immédiates de 16 bits. Pour charger une constante de 32/64 bits, il faut s'y prendre en plusieurs fois. Une première instruction MOVZ charge les 16 premiers bits, puis 2 à 4 instructions MOVK remplissent le reste. MOVZ met à zéro les 48 bits de poids fort, MOVK permet de sélectionner le doublet à modifier dans le registre (on modifie 16 bits, dont on choisit la place, le reste du registre est laissé tel quel). ARM permet de fusionner cette suite d'instructions en une seule µop MOV avec une constante immédiate. On parle alors de '''''MOVZ + MOVK fusion'''''. Une dernière utilisation, [https://www2.eecs.berkeley.edu/Pubs/TechRpts/2016/EECS-2016-130.pdf proposées pour le jeu d'instruction RISC-V], fusionnait un calcul d'adresse et une instruction LOAD/STORE en une seule instruction d'accès mémoire. Elle a été envisagée à une époque où le jeu d'instruction RISC-V ne gérait pas les modes d'adressage base + indice. L'idée était d'avoir une unité mémoire qui gérait le mode d'adressage base + indice et de fusionner des instructions pour l'exploiter. Mais de nos jours, le jeu d'instruction RISC-V intègre le mode d'adressage base + indice, ce qui fait que la macro-fusion n'a aucun intérêt pour ce cas précis. La macro-fusion a de nombreuses limitations en pratique. En théorie, on pourrait l'utiliser au-delà des deux situations mentionnées plus haut. Par exemple, pour fusionner une addition et une multiplication en une seule instruction MAD. Mais l'intérêt est assez faible. Notons que la macro-fusion ne permet que de fusionner des paires d'instructions. Aller au-delà demanderait un cout en circuit trop important. Déjà que tester deux paires utilise pas mal de comparateurs... ==Le ''stack engine'' : une optimisation de la pile d'appel== Les processeurs modernes intègrent une optimisation liée au pointeur de pile. Pour rappel, sur les architectures modernes, le pointeur de pile est un registre utilisé pour gérer la pile d'appel, précisément pour savoir où se trouve le sommet de la pile. Il est manipulé par les instructions CALL, RET, PUSH et POP, mais aussi par les instructions d'addition/soustraction pour gérer des cadres de pile de taille variable. De plus, il peut servir d'opérande pour des instructions LOAD/STORE, afin de lire/écrire des variables locales, les arguments d'une fonction, et autres. C'est donc un registre général adressable, intégré au banc de registre, altéré par l'unité de calcul entière. L'incrémentation/décrémentation du pointeur de pile passe donc par l'unité de calcul, lors des instructions CALL, RET, PUSH et POP. Mais, l'optimisation que nous allons voir permet d'incrémenter/décrémenter le pointeur de pile sans passer par l'ALU, ou presque. L'idée est de s'inspirer des architectures avec une pile d'adresse de retour, qui intègrent le pointeur de pile dans le séquenceur et l'incrémentent avec un incrémenteur dédié. ===Le ''stack engine''=== L'optimisation que nous allons voir utilise un '''''stack engine''''' intégré à l'unité de contrôle, au séquenceur. Le processeur contient toujours un pointeur de pile dans le banc de registre, cela ne change pas. Par contre, il n'est pas incrémenté/décrémenté à chaque instruction CALL, RET, PUSH, POP. Un compteur intégré au séquenceur est incrémenté à la place, nous l’appellerons le '''compteur delta'''. La vraie valeur du pointeur de pile s'obtient en additionnant le compteur delta avec le registre dans le banc de registre. Précisons que si le compteur delta vaut zéro, la vraie valeur est dans le banc de registre et peut s'utiliser telle quelle. Lorsqu'une instruction ADD/SUB/LOAD/STORE utilise le pointeur de pile comme opérande, elle a besoin de la vraie valeur. Si elle n'est pas dans le banc de registre, le séquenceur déclenche l'addition compteur-registre pour calculer la vraie valeur. Finalement, le banc de registre contient alors la bonne valeur et l'instruction peut s'exécuter sans encombre. L'idée est que le pointeur de pile est généralement altéré par une série d'instruction PUSH/POP consécutives, puis des instructions LOAD/STORE/ADD/SUB utilisent le pointeur de pile final comme opérande. En clair, une bonne partie des incrémentations/décrémentation est accumulée dans le compteur delta, puis la vraie valeur est calculée une fois pour toutes et est utilisée comme opérande. On accumule un delta dans le compteur delta, et ce compteur delta est additionné quand nécessaire. Précisons que le compteur delta est placé juste après le décodeur d'instruction, avant même le cache de micro-opération, l'unité de renommage et l'unité d'émission. Ainsi, les incrémentations/décrémentations du pointeur de pile disparaissent dès l'unité de décodage. Elles ne prennent pas de place dans le cache de micro-opération, ni dans la fenêtre d'instruction, ni dans la suite du pipeline. De nombreuses ressources sont économisées dans le ''front-end''. Mais utiliser un ''stack engine'' a aussi de nombreux avantages au niveau du chemin de données/''back-end''. Déjà, sur les processeurs à exécution dans le désordre, cela libère une unité de calcul qui peut être utilisée pour faire d'autres calculs. Ensuite, le compteur delta mémorise un delta assez court, de 8 bits sur le processeur Pentium M, un peu plus pour les suivants. L'incrémentation se fait donc via un incrémenteur 8 bits, pas une grosse ALU 32/64 bits. Il y a un gain en termes de consommation d'énergie, un incrémenteur 8 bits étant moins gourmand qu'une grosse ALU 32/64 bits. ===Les points de synchronisation du delta=== La technique ne fonctionne que si la vraie valeur du pointeur de pile est calculée au bon moment, avant chaque utilisation pertinente. Il y a donc des '''points de synchronisation''' qui forcent le calcul de la vraie valeur. Plus haut, nous avions dit que c'était à chaque fois qu'une instruction adresse le pointeur de pile explicitement, qui l'utilise comme opérande. Les instructions CALL, RET, PUSH et POP ne sont pas concernées par elles utilisent le pointeur de pile de manière implicite et ne font que l'incrémenter/décrémenter. Mais dans les faits, c'est plus compliqué. D'autres situations peuvent forcer une synchronisation, notamment un débordement du compteur delta. Le compteur delta est généralement un compteur de 8 bits, ce qui fait qu'il peut déborder. En cas de débordement du compteur, le séquenceur déclenche le calcul de la vraie valeur, puis réinitialise le compteur delta. La vraie valeur est donc calculée en avance dans ce cas précis. Précisons qu'un compteur delta de 8 bits permet de gérer environ 30 instructions PUSH/POP consécutives, ce qui rend les débordements de compteur delta assez peu fréquent. À noter que si le compteur delta vaut zéro, il n'y a pas besoin de calculer la vraie valeur, le séquenceur prend cette situation en compte. Un autre point de synchronisation est celui des interruptions et exceptions matérielles. Il faut que le compteur delta soit sauvegardé lors d'une interruption et restauré quand elle se termine. Idem lors d'une commutation de contexte, quand on passe d'un programme à un autre. Pour cela, le processeur peut déclencher le calcul de la vraie valeur lors d'une interruption, avant de sauvegarder les registres. Pour cela, le processeur intègre un mécanisme de sauvegarde automatique, qui mémorise la valeur de ce compteur dans le tampon de réordonnancement, pour forcer le calcul de la vraie valeur en cas de problème. La technique du ''stack engine'' est apparue sur les processeurs Pentium M d'Intel et les processeurs K10 d'AMD, et a été conservée sur tous les modèles ultérieurs. L'implémentation est cependant différente selon les processeurs, bien qu'on n'en connaisse pas les détails et que l'on doive se contenter des résultats de micro-benchmarks et des détails fournit par Intel et AMD. Selon certaines sources, dont les manuels d'optimisation d'Agner Fog, les processeurs AMD auraient moins de points de synchronisation que les processeurs Intel. De plus, leur ''stack engine'' serait placé plus loin que prévu dans le pipeline, après la file de micro-opération. ==La micro-fusion et la délamination== La '''micro-fusion''' est une optimisation qui retarde le décodage réel de certaines instructions assez loin dans le pipeline. Elle concerne surtout les instructions ''load-op'', mais pas que. D'autres instructions mémoire peuvent subir cette optimisation. L'essentiel est que, sans micro-fusion, ces instructions sont décodées en plusieurs micro-opérations. La micro-fusion retarde le décodage final de ces instructions, qui se fait assez tard dans le pipeline. Pour comprendre l'intérêt, nous allons devoir faire quelques explications. L'optimisation en question fait le décodage en deux temps. Prenons l'exemple d'une opération ''load-op'', qui est censée être décodée en deux micro-opérations : une lecture, puis une opération. Le décodage produit une '''macro-opération''' (terme d'Intel), qui est propagée dans le pipeline, pour être scindée en deux micro-opérations un peu avant les ALU et l'unité mémoire. La macro-opération est techniquement une micro-opération comme une autre, sauf que ce n'est pas la micro-opération finale. L'unité d'émission découpe cette macro-opération en deux micro-opérations, qui sont émises l'une après l'autre. Pour résumer, les instructions ''load-op'' ne sont complétement découpées en micro-opération lors de l'émission, pas lors du décodage. L'optimisation impacte ce qui se situe entre le décodeur d'instruction et l'unité d'émission : l'unité de renommage, la file de micro-opérations, le ''Loop Stream Detector'' et/ou le cache de micro-opération. L'intérêt de l'optimisation est que l'on fusionne deux micro-opérations en une seule macro-opération. Le cache de micro-opération et la file de micro-opération peuvent mémoriser plus de micro-opérations, vu que certaines sont fusionnées. Quant à l'unité de renommage, elle renomme une seule macro-opération au lieu de renommer deux micro-opérations, ce qui augmente le débit du renommage de registre. Par contre, cela complique un peu le travail de l'unité d'émission, qui doit découper les instructions ''load-op'' en deux et émettre les micro-opérations l'une après l'autre. Il faut noter qu'il n'y a pas de fusion proprement dite. Le décodeur ne décode pas deux micro-opérations séparées, avant de les fusionner. En réalité, il génère directement une macro-opération, qui correspond à deux micro-opérations, que ce soit plus tard dans le pipeline ou sur une autre architecture. D'ailleurs, la micro-fusion a lieu à l'intérieur d'une instruction, qui est censée être traduite en plusieurs micro-opérations. Et encore : pas toutes. La conséquence est que la technique n'a de sens que sur les processeurs CISC, avec des instructions assez complexes pour être décodées en plusieurs micro-opérations. En pratique, seuls les CPU x86 sont concernés. ===Les domaines de fusion=== La micro-fusion n'est pas spécifique aux processeurs superscalaires. En théorie, on peut l'implémenter sur un processeur non-superscalaire à émission dans l'ordre. Cependant, je ne connais aucun processeur à émission dans l'ordre qui implémente la micro-fusion, même si son implémentation est en théorie possible sur de tels processeurs. La micro-fusion est implémentée différemment selon que l'émission se fasse dans l'ordre, dans le désordre, avec une fenêtre d'instruction, avec des stations de réservation, etc. J'ai dit plus haut que les micro-opérations sont générées lors de l'émission, mais le moment exact varie suivant le processeur. Pour simplifier le tout, nous parlerons de '''scission''' quand le processeur traduit une macro-opération en deux/trois micro-opérations. Le moment où a lieu la scission est théoriquement dans l'unité d'émission, mais celle-ci prend plusieurs formes selon que le processeur utilise l'émission dans l'ordre, dans le désordre, avec une fenêtre d'instruction, avec des stations de réservation, etc. Les processeurs à exécution dans l'ordre sont le cas le plus simple. L'unité d'émission est alors en charge du découpage des macro-opérations en micro-opérations. La file de micro-opération ou le cache de micro-opérations mémorisent des macro-opérations. À l'opposé, le tampon de ré-ordonnancement mémorise lui des micro-opérations. Le reste du processeur n'est pas affecté. Sur les processeurs à exécution dans le désordre, les différences sont nombreuses. Le tampon de ré-ordonnancement peut mémoriser soit les micro-opérations finales, soit des macro-opérations. Il en est de même avec la fenêtre d'instruction et/ou les stations de réservation, qu'elles soient centralisées ou décentralisées. Tout dépend de comment se fait l'implémentation. Une implémentation simple effectue la scission des macro-opérations dans l'unité d'émission, comme sur les processeurs à exécution dans l'ordre. Sur de nombreux processeurs, l'unité d'émission et de renommage sont simplement fusionnées, ce qui fait que la scission a lieu juste après le renommage de registres. En conséquence, le tampon de re-ordonnancement et les fenêtres d'instruction ne voient que des micro-opérations. Les macro-opérations disparaissent juste avant. Cette implémentation est préférée avec une fenêtre d'instruction centralisée, car on voit mal comment scinder une macro-opération juste avant l'ALU. Une autre implémentation fait la scission en sortie de la fenêtre d'instruction ou des stations de réservation, qu'elles soient centralisées ou décentralisées. Dans ce cas, le tampon de re-ordonnancement fait de même. L'avantage est que le tampon de ré-ordonnancement stocke des macro-opérations, ce qui augmente sa taille effective. Par exemple, il utilise une seule entrée pour une instruction ''load-op'', au lieu de deux sans micro-fusion de ce type. ===La délamination=== La scission peut parfois avoir lieu plus tôt, dans des circonstances très précises. Concrètement, cela ne concerne que quelques micro-architectures d'Intel, pour des instructions très précises. La raison est les limitations du format des micro-opérations, pas une histoire d'optimisation. Le processeur aimerait pouvoir faire la micro-fusion, mais ne peut pas à cause de ces limitations. Une telle situation cause alors une '''délamination''' : les micro-opérations sont scindées précocement. Par exemple, sur la micro-architecture SandyBridge d'Intel, les opérations ''load-op'' sont fusionnées, sauf si elles utilisent l'adressage indicé. Les micro-opérations de ce processeur ne peuvent avoir que deux opérandes. Trois opérandes est de trop, et les opérations ''load-op'' en adressage indicé dépassent cette limite. Dans ce cas, les micro-opérations sont fusionnées en sortie du décodeur, mais sont scindés avant d’entrer dans la file de micro-opération. L'avantage n'est pas évident, vu que l'unité de renommage suit de près le décodeur d'instruction. Il est que le cache de micro-opérations mémorise des macro-opérations, pas deux micro-opérations. ===La micro-fusion des processeurs Intel et AMD=== La micro-fusion est apparue sur le Pentium M d'Intel, et a été conservée telle quelle sur les processeurs Intel suivants. Les processeurs AMD supportent la micro-fusion depuis au moins la micro-architecture AMD K8. Mieux que ça, la délamination n'était pas utilisée car les micro-opérations acceptaient plus de 2 opérandes. Que ce soit chez AMD ou Intel, elle concerne deux types d'instructions : les écritures mémoire et les instructions ''load-op''. Sur le Pentium M, il y avait quelques limitations, l'optimisation se limitant par exemple aux registres entiers et flottants, mais pas aux registres MMX, mais pas les registres SSE (nous verrons ces registres dans quelques chapitres). Mais les processeurs modernes n'ont plus cette limitation. La micro-fusion des CPU Intel concerne deux types d'instructions : les écritures mémoire et les instructions ''load-op''. Pour les écritures, la situation est plus complexe que pour les instructions ''load-op''. Sur les processeurs Intel précédents, les écritures étaient réalisées dans deux unités séparées : le calcul d'adresse dans une unité de calcul, l'écriture proprement dite dans l'unité mémoire. Elles prenaient donc deux micro-opérations. Le processeur a changé ses unités mémoire, ce qui fait qu'elle pouvait faire les deux en une seule micro-opération. Il s'agissait alors d'une optimisation du chemin de données, plus que de la micro-fusion proprement dite, mais je la mentionne ici malgré tout. La micro-fusion sur ce processeur avait un avantage en plus des avantages usuels : elle utilisait mieux les décodeurs. Les Pentium M avaient trois décodeurs pour décoder plusieurs instructions en même temps, chose qu'on détaillera dans le chapitre sur les processeurs superscalaires. Un seul décodeur pouvait décoder toutes les instructions, les deux autres ne peuvent décoder que les instructions simples, qui se décodent en une seule micro-opération. Avec la micro-fusion, les trois décodeurs peuvent gérer les écritures mémoire et les opérations ''load-op'', vu qu'elles deviennent des instructions simples. Sans cela, elles auraient été limitées au premier décodeur. <noinclude> {{NavChapitre | book=Fonctionnement d'un ordinateur | prev=Le parallélisme mémoire | prevText=Le parallélisme mémoire | next=Exemples de microarchitectures CPU : le cas du x86 | nextText=Exemples de microarchitectures CPU : le cas du x86 }} </noinclude> ea57salgqt6fnjdip05ny6t692qdvpv 773240 773239 2026-09-26T14:13:27Z Mewtow 31375 /* Le partitionnement des unités fonctionnelles */ 773240 wikitext text/x-wiki Les processeurs vus auparavant ne peuvent émettre au maximum qu'une instruction par cycle d'horloge. Ils peuvent avoir plusieurs instructions qui s'exécutent en même temps, dans des unités de calcul séparées. C'est le cas dès qu'une instruction multi-cycles s'exécute. Par contre, ils ne peuvent démarrer qu'une seule instruction par cycle. Et quand on court après la performance, ce n'est pas assez ! Les concepteurs de processeurs ont inventés des processeurs qui émettent plusieurs instructions par cycle : les '''processeurs à émissions multiples'''. Un processeur à émission multiple charge plusieurs instructions en même temps, les décode en parallèle, puis les émet en même temps sur des unités de calculs séparées. Pour cela, il faut gérer les dépendances entre instructions, répartir les instructions sur différentes unités de calcul, et cela n'est pas une mince affaire. En général, pour un processeur à émission multiple capable d'émettre N instructions en parallèle, on parle de '''processeur à N voies''', ou encore un ''processeur à N pipelines''. [[File:Superscalarpipeline.svg|centre|vignette|upright=1.5|Pipeline RISC classique à cinq étages sur un processeur superscalaire. On voit bien que plusieurs instructions sont chargées en même temps.]] Les processeurs à émission multiple sont de deux types : les processeurs VLIW et les '''processeurs superscalaires'''. Nous mettons les processeurs VLIW de côté en attendant le prochain chapitre. La raison est qu'ils ont un jeu d'instruction spécialisé qui expose le parallélisme d'instruction directement au niveau du jeu d'instructions, là où les processeurs superscalaires restent des processeurs au jeu d'instruction normal. La différence principale entre processeur VLIW et superscalaire est qu'un processeur superscalaire répartit les instructions sur les unités de calcul à l’exécution, là où un processeur VLIW délègue cette tâche au compilateur. ==Le nombre de voies d'un processeur superscalaire== Les processeurs superscalaires les plus simples ne permettent que d'émettre deux micro-opérations à la fois, d'où leur nom de processeur ''dual issue'', ce qui se traduit en '''processeur à double émission'''. Les processeurs triple émission permettent eux d’émettre trois instructions à la fois. A l'opposé, les CPU superscalaires les plus puissants peuvent émettre jusqu'à 8-10 instructions simultanément. En 2026, les CPU superscalaires peuvent monter jusqu’à 10 µops émises en même temps, ce qui est énorme ! Et cela nous amène à faire une distinction entre les processeurs superscalaires dits '''étroits''' et ceux dits '''larges'''. Dans ce wikilivre, on part du principe que les processeurs superscalaires étroits émettent jusqu'à 4-5 µops à la fois, alors que les larges peuvent en émettre plus, l'intervalle entre les deux est une zone grise. Le nombre exact n'est pas fixé dans le marbre, la distinction est volontairement vague. Nous parlerons de la '''largeur d'un CPU superscalaire''' pour parler du nombre de voies, du nombre de µops émises en même temps. Plus un CPU superscalaire est large, plus il est performant ''toute chose égale par ailleurs''. Mais dans les faits, vous pouvez oublier la partie en italique. Élargir un CPU superscalaire demande de rajouter beaucoup de circuits. Et ce cout en circuit aura un impact sur la performance. A budget en transistors limité, il y a un compromis entre élargir un CPU et rajouter du cache ou la taille des fenêtres d'instructions/ROB et autres. ===Les contraintes d’appariement=== Prenons un processeur double émission, capable d'émettre deux instructions simultanément. Émettre deux instructions en même temps n'est pas toujours possible. Par exemple, si deux instructions sont dépendantes, on ne peut pas les émettre en même temps. Mais oublions un instant ces histoires de dépendances de données, et partons du principe que le CPU ait affaire à deux instructions indépendantes, une paire idéale. Un CPU double émission "parfait" pourrait exécuter n'importe quelle paire d'instruction. Par exemple, il pourrait émettre deux multiplications consécutives, deux lectures consécutives, une lecture et une écriture en même temps, etc. En pratique, aucun processeur ne permet cela. Implémenter un CPU superscalaire "parfait" aurait un cout en circuit beaucoup trop conséquent. Par exemple, pour émettre deux multiplications en même temps, il faudrait deux multiplieurs séparés. Idem pour exécuter deux accès mémoire simultané : cela demande d'avoir deux unités mémoire et un cache multiport. Mais en pratique, aucun CPU superscalaire ne fait cela. La totalité des CPU double émission se débrouille avec un seul circuit multiplieur et une seule unité mémoire. Et cela entraine l'apparition de dépendances structurelles, qui font que certaines paires d'instructions sont impossibles. Par exemple, impossible d'émettre deux multiplications en même temps avec un seul circuit multiplieur. Mais émettre une multiplication en parallèle d'une addition est parfaitement possible. Il y a donc des paires d'instructions autorisées et des paires interdites. Et la logique est la même sur les CPU à triple ou quadruple émission, et au-delà. : des triplets ou quadruplet d'instructions sont impossibles à émettre simultanément, alors que d'autres le sont. Pour le dire autrement, il y a des '''contraintes d'appariement''' sur les instructions à émettre en même temps. ===L'impact des contraintes d’appariement sur la performance=== Ces contraintes d’appariement ont un impact sur les performance, mais celui-ci peut être grandement mitigé. Les programmes informatiques utilisent en pratique beaucoup plus d'opérations entières de base que de multiplication, branchements et accès mémoire. Il y a environ 5 opérations entières de base pour une multiplication entière. En pratique, il est rare de devoir exécuter deux multiplications à la fois. Idem pour les décalages et rotations, qui sont assez éloignés les uns des autres. Deux branchements consécutifs sont quant à eux plus fréquents. Deux accès mémoire consécutifs est assez rare, mais ils restent assez proches les uns des autres malgré tout. La conséquence est que certaines paires d'instructions sont privilégiées par rapport au reste. Les paires regroupant deux calculs basiques (additions, soustractions, ...) sont systématiquement supportées, car dupliquer des ALU entières est facile. Les paires regroupant un calcul et une autre instruction sont elles aussi systématiquement supportées. Par contre, les paires avec deux branchements, deux accès mémoire, deux décalages/rotations, ou deux multiplications/divisions sont fréquemment interdites. Seuls les CPU superscalaires larges peuvent les permettre, et encore : sous conditions. Le résultat est que le processeur peut se débrouiller avec une seule unité mémoire et une unité de branchement. ===Terminologie pour la suite du chapitre=== Décrire les contraintes d’appariement demande de dire quelles combinaisons d’instructions sont autorisées ou interdites. Et pour cela, nous allons introduire les abréviations suivantes : * INT est un raccourcis pour parler d'une opération entière simple, qu'une ALU entière peut exécuter. * MUL correspond à une opération entière complexe, typiquement une multiplication, éventuellement à une division. * FLOAT désigne une opération flottante, peut importe laquelle. * MEM désigne un accès mémoire, à savoir une instruction ou µop mémoire. * BRANCH désigne une instruction de branchement. Cette terminologie permettra de définir des paires d'instructions, qu'elles soient possibles ou interdites. Par exemple, l'expression INT + FLOAT désigne une paire d'instruction regroupant une instruction entière et une instruction flottante, peu importe l'ordre des deux instructions. Il en sera de même pour les triplets, quadruplets, ou plus. Par exemple, l'expression INT + INT + FLOAT + MUL désigne un quadruplet d'instructions regroupant : deux instructions entières, une instruction flottante, et une multiplication entière. J'utiliserais aussi des parenthèses pour regrouper certaines combinaisons. Par exemple, l'expression INT + (MUL /FLOAT) veut parler d'une paire d'instruction où la première est une opération entière basique, la seconde est : soit une multiplication, soit une opération flottante. ==Le ''front-end'' des processeurs superscalaires== Un processeur superscalaire doit charger, décoder, et exécuter plusieurs instructions par cycle. Par exemple, un processeur triple émission doit charger 3 instructions lors d'un premier cycle, puis les décoder au cycle suivant, et les exécuter au cycle suivant. Il travaille donc pas paquets de 3 instructions, dans cet exemple. Intuitivement, charger, décoder et exécuter plusieurs instructions à la fois demande de dupliquer des circuits. Par exemple, il faut dupliquer les décodeurs pour décoder plusieurs instructions. Les unités de calcul sont aussi dupliquées, sauf sur certaines processeurs qui arrivent à ruser, mais laissons cela de côté pour plus tard. Mais d'autres circuits sont simplement modifiés, comme l'unité de chargement ou l'unité d'émission. Voyons ce qu'il en est dans le détail. {|class="wikitable" |- ! rowspan="2" | Processeur sans émission multiple | rowspan="2" | Chargement | rowspan="2" | Décodage | rowspan="2" | Renommage | rowspan="2" | Émission | Exécution / ALU | rowspan="2" | ''Commit''/ROB |- | Exécution / ALU |- ! rowspan="4" | Processeur superscalaire | rowspan="4" | Chargement | rowspan="2" | Décodage | rowspan="4" | Renommage | rowspan="4" | Émission | Exécution / ALU | rowspan="4" | ''Commit''/ROB |- | Exécution / ALU |- | rowspan="2" | Décodage | Exécution / ALU |- | Exécution / ALU |} Nous allons d'abord voir ce qu'il en est pour le ''front-end'', à savoir la partie du CPU qui s'occupe de charger et décoder les instructions. Elle regroupe l'unité de chargement, les décodeurs, l'unité de prédiction de branchement, la file de µops, la file d'instruction, et divers caches. En soi, les changements portent surtout sur l'unité de chargement et les décodeurs. ===Le chargement de plusieurs instructions consécutives=== Un processeur superscalaire charge plusieurs instructions en même temps, ''en tenant compte de la présence de branchements''. La partie en italique est importante et elle a une conséquence très forte : la manière de faire est fortement différente entre un CPU superscalaire étroit et large. Un CPU superscalaire étroit fait au plus simple : il charge un bloc de 2, 3, 4 instructions consécutives en mémoire RAM. Il faut alors doubler/tripler/quadrupler la taille du bus connecté au cache d'instruction. Par exemple, pour charger quatre instructions de 32 bits chacune, il suffit de lire un bloc de 128 bits dans le cache d'instruction. Un bloc de 8, 16, 32 octets est donc lu depuis le cache et est ensuite découpé en instructions, envoyées chacun à un décodeur. Charger plusieurs instructions consécutives ne marche bien qu'en absence de branchements. La présence de branchements casse cette dynamique, et il faut gérer leur présence. * Sans prédiction de branchement, toutes les instructions d'un bloc sont décodées et exécutées en même temps, même s'il y a un branchement dans le tas. * Avec prédiction de branchement, les instructions situées après le branchement ne sont pas décodées ni exécutées. L'unité de chargement coupe le bloc chargé au niveau du premier branchement pris, remplit les vides avec des NOP, avant d'envoyer le tout à l'unité de décodage. Les instructions à l'adresse de destination seront chargées au cycle suivant, en chargeant un nouveau bloc. [[File:Fetch sur un processeur superscalaire avec prediction de branchements.png|centre|vignette|upright=2|Fetch sur un processeur superscalaire avec prédiction de branchements.]] Un autre défaut est que sur le CPU charge un bloc de 128 bits à la fois, les instructions doivent être alignées sur 128 bits. Et on peut généraliser à n'importe quelle taille de bloc. Si les instructions ne sont pas alignées, il faut gérer la situation. Il arrive qu'une instruction soit à cheval sur deux blocs, avec un morceau dans le premier bloc, un autre dans le second. Dans ce cas, l'instruction est chargée en deux fois, et des circuits doivent gérer la situation pour recoller les morceaux. Sur les CPU avec des instructions de taille fixe, le problème ne survient que rarement. Mais il est fréquent avec des instructions de taille variable. Un autre défaut est lié aux branchements. Imaginez qu'un branchement s'exécute et branche vers une instruction de destination. Rien n'indique que l'instruction de destination sera au tout début d'un bloc. Si ça se trouve, elle sera en troisième position d'un bloc de 4 instructions. Une solution simple charge le bloc en respectant l'alignement, et les deux premières instructions du bloc ne sont pas à exécuter. Il y a donc une petite perte de performance. Certains CPU, comme l'IBM RS/6000, ont la machinerie charger un bloc de N instructions même s'il n'est pas aligné. La machinerie en question se trouve dans le cache d'instruction et est tout sauf simple à expliquer,ce qui fait que je me permets de la passer sous silence. Un détail, cependant : elle ne permet pas de charger des blocs qui sont à cheval sur deux lignes de cache. ===Le décodage parallèle des instructions=== Pour le décodage des instructions, plusieurs instructions sont décodées en même temps. On parle alors de '''décodage parallèle'''. Pour ce faire, un processeur superscalaire contient plusieurs décodeurs, chacun pouvant décoder une instruction en parallèle des autres. Prenons par exemple un processeur superscalaire à 4 voies : le décodage se fera dans quatre décodeurs séparés, qui fonctionneront en parallèle. Cependant, cette implémentation ne marche bien que pour les processeurs RISC, les processeurs CISC devant faire avec un microcode gourmand en circuits. Les processeurs CISC utilisent des décodeurs hybrides, avec un microcode qui complémente un décodeur câblé. Dupliquer le microcode aurait un cout en transistors trop important, ce qui fait que seuls les décodeurs câblés sont dupliqués. Les CPU CISC superscalaires disposent donc de plusieurs décodeurs simples, capables de décoder les instructions les plus courantes, avec un seul microcode. La conséquence est qu'il n'est pas possible de décoder deux instructions microcodées en même temps. Par contre, il reste possible de décoder plusieurs instructions non-microcodées ou une instruction microcodée couplée à une instruction non-microcodée. Vu qu'il est rare que deux instructions microcodées se suivent dans un programme, le cout en performance est extrêmement mineur. Il faut noter qu'une instruction correspond à une ou plusieurs µops. Il est donc possible de charger 5 instructions et de se retrouver avec 8 µops en sortie des décodeurs. Et c'est la source d'une distinction très importante. Plus haut, j'ai parlé de processeurs superscalaires à N voies. La terminologie peut avoir deux interprétations. La première est que le processeur charge/décode N instructions à la fois. Une seconde est qu'il est capable d'exécuter/émettre N µops en même temps. Les deux ne sont pas équivalentes, vu qu'un processeur superscalaire peut, par exemple, charger/décoder N instructions et émettre/exécuter N+2 µops. La distinction est particulièrement importante sur les processeurs superscalaires larges. Certains d'entre eux sont capables de charger 5 instructions et d'en émettre le double ! Un exemple classique était les processeurs Athlon d'AMD, qui pouvaient décoder deux instructions par cycle, mais émettre 4 µops par cycle. Ce n'était pas un problème, car beaucoup d'instructions, même basiques, étaient décodées en 2 µops. ===L'émission parallèle des instructions=== La superscalarité modifie en profondeur l'unité d'émission, notamment au niveau de son interface. Avant, elle recevait une micro-opération sur son entrée, et fournissait la micro-opération émise sur une sortie. Et cela vaut aussi bien pour une unité d'émission simple, un ''scoreboard'', une fenêtre d'instruction ou des stations de réservation. Mais avec l'émission multiple, les sorties et entrées sont dupliquées. Pour la double émission, il y a deux entrées vu qu'elle doit recevoir deux micro-opération décodées/renommées, et deux sorties pour émettre deux micro-opérations. Dans ce qui suit, nous ne parlerons pas d'entrée et de sortie, mais de '''ports de décodage''' et de '''ports d'émission'''. Les ports de décodage reçoivent des micro-opérations provenant de l'unité de décodage, les ports d'émissions sont pour les micro-opérations émises. Les ports d'émission injectent les micro-opérations émises dans le pipeline, elle sont propagée au banc de registre, aux ALUs, etc. Un processeur superscalaire a forcément plusieurs ports d'émission (et de décodage). Si l'interface de l'unité d'émission change, l'intérieur est tout autant modifié. Sur un CPU non-superscalaire, l'unité d'émission reçoit une µop "candidate à l'émission", et détecte les dépendances entre avec les µops en vol, en cours d'exécution dans le pipeline. Et c'est toujours le cas sur les processeurs superscalaires : l'unité d'émission détecte les dépendances entre les µops candidates et les µops en vol. Mais avec la siperscalarité, elle doit en plus tester les dépendances entre les µops candidates. Si l'unité d'émission est un ''scoreboard'', il doit détecter les dépendances entre instructions émises simultanément et les sérialiser si besoin. Avec une fenêtre d'instruction, il n'y a pour ainsi dire rien à faire, vu que les dépendances sont éliminées par le renommage de registre et que les signaux de réveil gèrent les dépendances RAW. C'est la raison pour laquelle les processeurs superscalaires modernes n'utilisent pas de ''scoreboard''. ==Les processeurs implémentés avec plusieurs pipelines== Nous venons de voir comment la superscalarité impacte le ''front-end'', il est maintenant de voir ce qu'il en est de l'unité d'émission et des ''back-ends''. Un processeur superscalaire peut utiliser l'exécution dans le désordre et le renommage de registres, mais ce n'est pas une obligation. Historiquement, les premiers processeurs superscalaires n'utilisaient pas d'exécution dans le désordre. Le meilleur exemple est celui du Pentium 1 et 2 d'Intel. Ils étaient tous deux des processeurs superscalaires, mais n'avaient pas d'exécution dans le désordre, qui est apparue sur le Pentium 3. les explications sont plus simples si on met de côté l'exécution dans le désordre. Vous vous dites que l'implémentation d'un processeur superscalaire n'est pas la même suivant que le CPU utilise l'exécution dans le désordre, ou non. Mais dans les faits, la distinction est plus subtile que ca. Pour l'expliquer, je vais utiliser une classification originale, qui sépare les processeurs superscalaires en trois sous-types. La distinction se fait sur la manière dont la superscalarité est implémentée en matériel.La classification que j'utilise distingue : * Les processeurs avec plusieurs pipelines, sans exécution dans le désordre. * Les processeurs superscalaires avec un pipeline dynamique. * Les processeurs avec des fenêtres d'instruction décentralisées. La distinction n'est pas qu'une question d'implémentation matérielle. Elle impacte aussi le ratio entre instructions chargées/décodées et instructions émises. Vous vous dites que les deux sont égales, mais ce n'est le cas que sur les processeurs avec N pipelines ! Sur les autres processeurs, il est possible de décoder 4 instructions, mais d'en émettre maximum 6 par cycle ! Et sur les CPU avec des fenêtres d'instruction décentralisées, il y a aussi une différence entre le nombre d'instructions qui sont ''dispatch'' et ''schedule''. La catégorie la plus simple est celle des '''processeurs avec plusieurs pipelines'''. Ce sont des processeurs superscalaires sans exécution dans le désordre. Ils partent d'un processeur non-superscalaire, et ajoutent un second pipeline, en sortie de l'unité d'émission. Le second pipeline permet l'émission parallèle d'une seconde instruction. Le second pipeline n'est pas identique au pipeline originel, sur plusieurs points. Premièrement, il n'a pas exactement les mêmes circuits. Le second pipeline est généralement composé d'une unité de calcul, d'une FPU, éventuellement d'un second banc de registres, guère plus. Il permet d'exécuter une seconde opération de calcul, pas plus, ce qui est à l'origine de contraintes d'appariemment assez fortes. De plus, il n'a pas forcément la même longueur, il peut avoir des étages en moins ou en plus. Il s'agit de la forme la plus simple de processeur superscalaire et nous allons leur consacrer cette section. ===La double émission entière-flottante=== L'exemple le plus simple utilise un second pipeline pour les opérations flottantes, séparé du pipeline originel. L'ajout d'un pipeline flottant permet d'émettre une micro-opération entière en même temps qu'une micro-opération flottante, ce qui s'appelle la '''double émission entière-flottante'''. La micro-opération entière s'exécute dans l'ALU entière, la micro-opération flottante dans la FPU. Les exemples les plus notables sont le PA-RISC 7100, le POWER 1 (RIOS .& et .9) et le PowerPC 601. L'Apollo DN10000 et l'Intel i860 utilisaient aussi cette forme de double émission, mais étaient des prototypes de CPU VLIW. [[File:Double émission entière-flottante.png|centre|vignette|upright=2|Double émission entière-flottante]] Pour bien expliquer les choses, prenons le processeur illustré ci-dessous, avec deux ''back-end'' séparés : un pour la FPU, et un autre pour le reste. Les instructions entières et mémoire passent toutes par le ''back-end'' généraliste. [[File:CPU non-superscalaire basique, avec une FPU.png|centre|vignette|upright=1.5|CPU non-superscalaire basique, avec une FPU]] Les deux ''back-end'' sont des pipelines séparés, mais il faut que l'unité d'émission soit spécialement conçue pour en profiter. Un processeur simple émission peut démarrer une seule µop par cycle : soit la µop est envoyée au ''back-end'' flottant, soit elle est envoyée au ''back-end'' général. Avec la double émission entière-flottante, le processeur peut envoyer une µop dans chaque pipeline, à chaque cycle. Vu que la FPU et son banc de registre forment déjà un pipeline séparé, les seules modifications se font au niveau du ''front-end'', unité d'émission inclue. {| |- |[[File:CPU simple émission avec une FPU.png|centre|vignette|upright=1.5|CPU simple émission avec une FPU.]] |[[File:Double émission entière flottante avec un second pipeline.png|centre|vignette|upright=1.5|CPU avec un second pipeline flottant.]] |} Notez que le terme double émission ''entière''-flottante est un peu trompeur. En réalité, la technique permet d'émettre une instruction flottante avec n'importe quelle autre instruction non-flottante, avec des restrictions pour les branchements. Il est par exemple possible d'exécuter un calcul flottant en parallèle d'un accès mémoire. Sur les CPU 32 bits, il est possible de faire les multiplications dans la FPU. Pour rappel, la FPU contient un multiplieur entier 53-54 bits, pour multiplier les mantisses de deux flottants 64 bits, qu'on peut utiliser pour faire les multiplications entières 32 bits. Cela permet d'émettre une multiplication en même temps qu'une opération entière dans l'ALU. Ainsi, le CPU pouvait émettre deux instructions : une opération entière de base, avec soit une multiplication, soit une opération flottante. En clair, une émission de type INT + (MUL /FLOAT). Par contre, on perd la possibilité d'émettre une multiplication entière avec une opération flottante, mais c'était un compromis qui en valait la peine. Le CPU PA-RISC 7100 implémentait cette optimisation. Il s'agissait d'un CPU double émission entière-flottante, qui utilisait donc un partitionnement assez simple. Le combo partitionnement + reconditionnement permettait ici de gagner en performance, pour un cout en matériel très limité. D’autant plus limité que le jeu d'instruction prévoyait le reconditionnement de la FPU. Les multiplications ne lisaient pas leurs opérandes dans les registres entiers, mais dans les registres flottants. ===La double émission entière=== Une autre possibilité ajoute un second pipeline, capable d'exécuter des opérations entières simples, appelé le pipeline entier. Le processeur supporte alors la '''double émission entière''', à savoir qu'ils peuvent émettre une opération entière en plus d'une autre instruction. L'opération entière en question est une opération simple, comme une addition, une soustraction, une opération bit à bit, une comparaison, etc. Les exemples classiques sont les processeurs Intel i960 et Pentium. L'implémentation était différente entre l'i960 et le Pentium : Le Pentium avait une seconde ALU entière, alors que le i960 rusait. Le Pentium dupliquait l'ALU entière, pour créer un second pipeline entier. Sur le Pentium originel, les contraintes d'appariement étaient assez strictes. Le Pentium avait une FPU, mais ne pouvait pas l'utiliser en double émission. Il lui était impossible d'exécuter une opération flottante en même temps qu'une opération entière (branchements inclus). Le Cyrix 6x86 était une sorte de Pentium produit par l'entreprise Cyrix, avec une microarchitecture différente. Il avait des contraintes d’appariement réduites. Par exemple, il pouvait émettre une opération flottante en parallèle d'une opération entière. Nous détaillerons ces processeurs dans le prochain chapitre, qui porte justement sur les CPU superscalaires x86. [[File:CPU simple et double émission avec un second pipeline entier.png|centre|vignette|upright=2.5|CPU simple et double émission avec un second pipeline entier.]] L'Intel i960 n'avait pas de FPU, juste une ALU entière et une unité mémoire. Il utilisait l'unité mémoire comme une seconde ALU entière limitée. Pour rappel, une unité mémoire contient une AGU, à savoir une ALU spécialisée dans les calculs d'adresse. Elle est capable de faire des additions, soustractions, et quelques décalages limités (des décalages par 2, 4, 8, pas plus). L'Intel i960 ajoutait des connexions entre AGU et banc de registre, afin que celle-ci serve de seconde unité de calcul. L'AGU est déjà reliée aux deux ports de lecture, pour lire les opérandes du calcul d'adresse, il suffit de la connecter sur le port d'écriture. Le processeur peut alors émettre deux instructions/µops entières, sous certaines conditions. [[File:Emission multiple des opérations entières, double émission.png|centre|vignette|upright=2|Émission multiple des opérations entières, double émission.]] ==Les processeurs superscalaires dynamiques== Les processeurs précédents, avec 2 pipelines, avaient une unité d'émission assez simple. Elle avait deux ports de décodage, et deux ports d'émission. Le nombre de ports de décodage et d'émission étaient identiques. Vous vous dites que c'est naturel, et intuitif, que c'est normal pour un processeur superscalaire. Mais en réalité, la majorité des processeurs superscalaires a plus de ports d'émission que de décodage. Expliquer pourquoi va cependant nous demander de parler de comment la superscalarité exploite les unités de calcul. Posons-nous cette question : de combien d'unités un processeur superscalaire a besoin ? Les unités en question ne sont nécessairement des unité de calcul, l'unité mémoire et l'unité de branchement comptent aussi. La réponse dépend du nombre de voies. Si le ''front-end'' charge et décode un paquet de N instructions, exécuter ces N instructions simultanément demandera là aussi au moins N unités de calcul. Notez le terme : ''au moins'', car c'est là tout l'enjeu de cette section. La réponse dépend aussi des contraintes d’appariement. Pour un CPU superscalaire "parfait", sans contraintes d’appariement, toutes les unités doivent être dupliquées, y compris l'unité mémoire. Un processeur à double émission parfaite devrait dupliquer l'ALU entière, le ''barrel shifter'', le circuit multiplieur, la FPU, l'unité mémoire, et l'unité de branchement, ce qui ferait passer de 6 unités à 12. Le cout en circuit est énorme, même pour de la double émission ! Et pour un CPU à N pipelines, le cout en circuit encore plus prohibitif ! En pratique, les processeurs superscalaires qui font ça sont très rares. La majeure partie des processeurs superscalaires utilisent d'autres techniques : le partitionnement, la duplication partielle des ALUs, la duplication des autres unités. ===Le partitionnement des unités fonctionnelles=== Les CPU superscalaires les plus simples ne dupliquent pas leurs unités de calcul, ils se contentent d'exploiter les unités existantes. Et pour expliquer comment, nous allons partir de l'exemple d'un processeur non-superscalaire contient une ALU entière, une FPU, une unité mémoire et une unité de branchement. Les quatre unités peuvent fonctionner en parallèle, à savoir qu'une unité peut exécuter une instruction à elle toute seule, sans interagir avec les autres. Cela suffit pour créer un processeur quadruple émission, qui peut émettre en même temps : une opération entière, une opération flottante, un branchement, un accès mémoire. Le principe marche aussi sur un processeur avec moins de 4 unités, par exemple sans FPU et/ou sans unité de branchement, ou avec au contraire des unités en plus. L'idée générale est d’émettre une µops séparée dans chaque unité fonctionnelle. Utiliser ainsi les unités existantes, pour rendre un CPU superscalaire, s'appelle le '''partitionnement superscalaire'''. Le terme n'est pas beaucoup utilisé en-dehors de quelques vieux articles académiques. Le partitionnement est fortement dépendant du jeu d'instruction, car il demande de bien séparer instructions entières, flottantes, branchements et instructions mémoire. Et autant c'est bien le cas sur les CPU RISC, autant les CPU CISC ne sont pas dans ce cas. Les CPU CISC ont des instructions ''load-op'', qui effectuent une instruction arithmétique et une µop mémoire. Et ces instructions sont très compliquées à implémenter avec le partitionnement. Et au-delà de ça, les CPU CISC ont beaucoup d'instructions qui sont décodées en plusieurs µops, à exécuter dans un ordre bien précis. On peut pas émettre les µops en question simultanément. Le partitionnement a un cout en circuit très limité, pour un gain en performance lui aussi limité. Pour reprendre le CPU de l'exemple précédent, il est rare que l'on ait à exécuter ces quatre instructions en même temps. En pratique, les combinaisons fréquentes sont les combinaisons calcul + accès mémoire, calcul + branchement, calcul entier + calcul flottant. Utiliser une quadruple émission de ce type n'est donc pas une bonne idée, alors que la double émission parait plus adaptée. Et c'est là que la différence entre ports d'émission et de décodage devient intéressante. L'idée est d'avoir une unité d'émission avec deux ports de décodage, et 4 port d'émission (un par unité). Les contraintes d’appariement varient alors suivant le processeur, mais la règle générale est que si deux micro-opérations tombent dans deux unités différentes, on peut les émettre en même temps. En clair, on peut émettre une opération entière avec une opération flottante, une opération entière avec un branchement, un accès mémoire avec un branchement, etc. Et cela rend le partitionnement bien plus intéressant que prévu. Les tout premiers CPU superscalaires étaient des CPU double émission, qui utilisaient le partitionnement avec : une ALU entière, une unité mémoire et une FPU. Nous parlerons de '''partitionnement INT-FLOAT-MEM'''. L'avantage sur la double émission flottante est que les combinaisons INT + MEM et FLOAT + MEM étaient autorisées. L'intérêt était d'émettre une opération en parallèle d'un accès mémoire, ce qui est un doublet d'instruction assez fréquent. Les exemples classiques sont l'HyperSPARC, de l'alpha 21064 et du PowerPC 603. Les contraintes d’appariement variaient grandement suivant le processeur. Le PowerPC 603 implémentait une double émission "parfaite". C'est à dire que toutes les combinaisons possibles à deux instructions étaient possibles, que ce soit INT + FLOAT, INT + MEM, FLOAT + MEM. En plus de cette double émission "parfaite", le CPU utilisait la technique dite de ''branch folding'', qui permettait d'exécuter un branchement sans utiliser d'unité de calcul, ce qui fait qu'il était qualifié de ''CPU 2-way + Branch''. A l'opposé, le processeur alpha 21064 avait pas mal de contraintes d’appariement, à savoir que certaines combinaisons de deux instructions étaient interdites, bien que compatibles avec les unités disponibles. Une des raisons est que le banc de registre n'avait pas assez de ports de lectures, ce qui fait que certaines combinaisons avec une écriture mémoire n'étaient pas permises. Les concepteurs avaient volontairement inséré ces contraintes afin de simplifier le design du processeur. Avant de poursuivre, précisons que la FPU regroupe un additionneur flottant et un multiplieur flottant. Et ce fait peut être exploité pour approfondir le partitionnement. La '''double émission FADD/FMUL''' permet d'émettre une addition flottante en même temps qu'une multiplication flottante. Elle a été utilisée sur les processeurs x86, Alpha et SPARC. Les autres circuits de calcul flottant sont souvent regroupés avec l'additionneur. La division est parfois e même port que le multiplieur flottant, parfois sur celui de l'additionneur (quand elle est microcodée et exécutée en enchainant les soustractions). ===La duplication des ALU entières=== Le partitionnement est une technique élégante, simple, facile à implémenter et au cout en circuits modeste. Mais il faut avouer que cela ne donne pas des CPU superscalaires très efficaces. La raison est que le partitionnement permet d'émettre des paires, triplets, quadruplets de micro-opérations qui sont rares en pratique. Les combinaisons les plus fréquentes en pratique sont des paires/triplets d'opérations entières, souvent des paires/triplets d'additions ou d'opérations entières simples. Il est donc primordial d'émettre plusieurs opérations entières simultanément. Le fait d'émettre et d'exécuter plusieurs opérations entières en même temps s'appelle l''''émission entière multiple'''. Pour exécuter la seconde/troisième opération entière, il faut dupliquer les ALU entières (ou réutiliser l'AGU comme ALU entière comme le fait l'Intel i960). Le processeur doit avoir deux ALU entières pour de la double émission entière, trois pour de triple émission entière, etc. De plus, il faut ajouter des ports au banc de registre, pour lire deux fois plus d'opérandes et décrire deux résultats. Et enfin, il faut rajouter un port d'émission pour pouvoir émettre une micro-opération entière en plus. [[File:Processeur non-superscalaire basique 03.png|centre|vignette|upright=2|Processeur non-superscalaire basique]] La tendance est de multiplier les ALU entières, mais pas le circuit multiplieur. Le cout en transistors est alors grandement réduit, au prix de l'apparition de dépendances structurelles. Le CPU peut émettre deux additions en même temps, ou une addition et une multiplication, mais pas deux multiplications. Il en est de même avec le ''barrel shifter'', qui n'est pas dupliqué. Il est donc possible d'émettre un décalage avec une opération de base, mais pas deux décalages en parallèle. Mais ces contraintes d’appariement ne sont pas un problème, car il est rare de devoir émettre deux multiplications/décalages en même temps. La plupart des programmes sont majoritairement remplis d'additions, avec des multiplications assez rares et des décalages qui le sont encore plus. La moyenne est proche d'une multiplication pour 4/5 additions. Vu que des multiplications consécutives sont très rares, dupliquer le multiplieur est presque inutile. Disposer de plusieurs circuits multiplieurs ou de plusieurs ''barrel shifters'' serait donc un cout en circuits qui ne servirait que rarement et n'en vaut pas la chandelle. Il faut noter que sur les CPU 32 bits, les multiplications peuvent être exécutées dans la FPU ou dans un multiplieur séparé, tout dépend de si le CPU exécute les multiplications dans la FPU ou pas. {|class="wikitable" |- ! Voie d'émission n°1 !! Voie d'émission n°2 !! Voie d'émission n°3 |- | ALU entière || ALU entière || FPU |} Reste à connecter les unités de calcul à l'unité d'émission (indirectement, car il y a des étages de pipeline entre les deux). Une implémentation basique utilisee un port d'émission par unité. Prenons l'exemple d'un CPU superscalaire avec 4 ALU entières, une FPU, et une unité mémoire. Vu qu'il y a 6 unités, cela fait donc 6 ports d'émission. L'exemple historique le plus impressionnant est celui du Motorola 88110, un CPU double émission qui possède 10 unités. Il contient deux ALU entières, un circuit multiplieur, un circuit diviseur, une unité de manipulation de bits, une unité mémoire, un additionneur flottant, et deux unités graphiques pour des calculs impliquant des pixels. Le multiplieur est utilisé à la fois pour les multiplications entières, flottantes et le produit de deux pixels. Idem pour le diviseur, qui sert pour les divisions entières et flottantes. Et chaque unité a son propre port d'émission. La conséquence est que les contraintes d'appariement du Motorola 88110 sont simples : tant que deux µops atterrissent dans des unités séparées, la paire est permise. Il est par exemple possible d'émettre simultanément deux opérations entières simples, vu qu'il y a deux ALU. Par contre, impossible d'émettre simultanément deux multiplications, deux divisions, deux opérations flottantes, deux manipulations de bit, deux accès mémoire, etc. De plus, le partage du multiplieur fait qu'on ne peut pas émettre une multiplication entière avec une multiplication flottante. Idem avec les divisions, vu que le diviseur est aussi partagé. Ce sont les seules contraintes. ===Le partage des ports d'émission=== La présence d'un circuit multiplieur et d'un ''barrel shifter'' pose des problèmes pour le partitionnement. Il est possible d'avoir un port d'émission dédié au multiplieur, et un autre au ''barrel shifter'', qui sont séparés de ceux pour l'ALU entière. Il est alors possible d'émettre une opération entière, une multiplication et un décalage, simultanément. Nous parlerons de '''partitionnement entier''' pour désigner cette possibilité. [[File:Partitionnement superscalaire entier.png|centre|vignette|upright=2|Partitionnement superscalaire entier.]] Le partitionnement entier est cependant très rare, car le gain en performance serait trop faible au regard des couts. Quelques processeurs historiques l'utilisaient, mais ce n'est plus le cas de nos jours. La raison est que les multiplications et les décalages sont assez rares dans le code, ce qui fait que le port d'émission associés sont sous-utilisés. Et un port d'émission a un cout en circuit assez important. L'idéal serait de trouver un moyen de réduire le nombre de ports d'émissions, sans réduire le nombre d'unités de calcul. [[File:Emission multiple des opérations entières, implémentation naive.png|vignette|Émission multiple des opérations entières, implémentation naive]] La solution consiste à '''partager les ports d'émission''', à savoir connecter un port d'émission à plusieurs unités de calcul. L'exemple classique est justement lié aux circuits multiplieur et ''barrel shifters''. L'idée est que le multiplieur et le ''barrel shifter'' sont mis sur le même port d'émission qu'une ALU entière. Prenons par exemple le processeur à double émission entière illustré ci-contre. Un port d'émission est relié à une ALU entière seule, et l'autre port d'émission alimente la seconde ALU entière et le multiplieur. La conséquence est que le processeur peut émettre deux opérations entières, dont une multiplication. Si on avait utilisé un port d'émission dédié au multiplieur, il aurait été possible d'émettre deux opérations entières et une multiplication. On perd donc une voie, mais on gagne en flexibilité et on économise des circuits. [[File:Emission multiple des opérations entières, implémentation courante.png|centre|vignette|upright=1.5|Émission multiple des opérations entières, implémentation courante]] En général, le ''barrel shifter'' et le multiplieur sont placés sur deux voies d'émission séparés, ce qui permet d'émettre un décalage et une multiplication en même temps. Les mettre sur le même port ne le permettrait pas. {|class="wikitable" |- ! Voie d'émission n°1 !! Voie d'émission n°2 !! Voie d'émission n°3 |- | * ALU entière * Multiplieur | * ALU entière * Barrel shifter | FPU |- | * Additions, soustractions, opérations bit à bit, etc. * Multiplication | * Additions, soustractions, opérations bit à bit, etc. * Décalages et rotations | Opération flottante |} Mais le partage des ports d'émission peut appliquer à n'importe quelle unité. Prenons l'exemple d'un CPU superscalaire avec 4 ALU entières, une FPU, et une unité mémoire. Vu qu'il y a 6 unités, cela fait donc 6 ports d'émission. Cependant, il est rare qu'un bloc de 6 instructions contienne exactement 4 opérations entières, une opération flottante, et un accès mémoire. Beaucoup de ports d'émission seraient sous-utilisés. La solution est de partager des ports d'émission, pour passer à la quadruple émission. Le CPU peut alors répartir 4 instructions sur les ports d'émission adéquats, à chaque cycle. Le CPU a donc 4 ports de décodage et 6 ports d'émission. Une implémentation classique permet n'importe quelle combinaison de 4 instructions compatible avec les 6 unités. Par exemple, il devient possible d'émettre simultanément 4 additions, la combinaison 2 * INT + FLOAT + MEM, 3 * INT + MEM, et bien d'autres. L'exemple relie une ALU entière, la FPU et l'unité mémoire sur le même port. Le CPU a alors quatre ports d'émission, qui ne sont pas "identiques". Trois d'entre eux sont reliés à une ALU entière chacun, rien d'autre. Le quatrième est lui relié à une ALU entière, la FPU et l'unité mémoire. On économise alors deux ports d'émission, mais cela entraine l'apparition de contraintes d’appariement. Le CPU peut émettre 3 opérations entières + n'importe quelle autre opération. Mais les autres combinaisons sont interdites. Le CPU peut émettre 6 opérations entières simultanément, grâce à 6 ALU entières. Les accès mémoire ou opérations flottantes ne viennent pas en plus, mais en remplacement des opérations entières. Regrouper plusieurs ALU sur un même port d'émission est donc à l'origine de dépendances structurelles, de nouvelles contraintes d’appariement. Dans certaines situations de '''''port contention''''', on a assez d'ALU pour exécuter N micro-opérations, mais la répartition des ALUs sur les ports l'empêche. Impossible d'émettre deux micro-opérations sur deux ALU si elles sont sur le même port d'émission. On peut réduire les cas de ''port contention'' en répartissant correctement les ALU sur les ports d'émission, mais sans jamais les supprimer complétement. Un exemple est celui du PowerPC 604. Il implémentait la quadruple émission, alors qu'il avait 5 unités de calcul. Mais deux unités partageaient un même port d'émission. Dans le détail, il avait deux ALU entières, un circuit multiplieur/diviseur, une FPU, une unité mémoire. La FPU et le multiplieur étaient sur le même port d'émission. En conséquence, le CPU pouvait émettre seulement les combinaisons INT + INT + MEM + (MUL/FLOAT). : Il est intéressant de voir l'exemple d'un processeur superscalaire parfait, sans contraintes d'appariement, à N voies. Pour cela, on part d'un processeur non-superscalaire, avec M unités de calcul. Un tel processeur peut s'implémenter avec N ports d'émission, chacun relié à M unités de calcul possibles, ce qui fait que les M unités de calcul sont dupliquées en N exemplaires. Si on prend par exemple un CPU non-superscalaire avec une ALU, une FPU, une unité mémoire et une unité de branchement, un port d'émission sera relié à une ALU, une FPU, une unité mémoire et une unité de branchement. ===La duplication des autres unités fonctionnelles=== Dupliquer les ALU entière est une première étape, mais il est aussi possible de dupliquer les autres unités fonctionnelles. Par exemple, il est possible de dupliquer l'unité mémoire ou l'unité de branchement. Reste à voir quelles unités dupliquer, et pourquoi. Précisons que dans ce qui va suivre, nous allons parler de la duplication des unités en général, pas seulement des unités mémoire/branchement. Nous allons continuer de parler de la duplication des ALU entières dans ce qui suit, mais de manière plus générale. Pour un processeur à N voies, il est théoriquement possible de dupliquer toutes les unités en N exemplaires. Mais ce n'est jamais fait en pratique, car le cout en circuits serait trop important et le gain en performance trop faible. Mieux vaut dupliquer certaines unités bien précises, quitte à avoir des contraintes d'appariement. Et à ce petit jeu, toutes les duplications ne se valent pas, certaines sont plus rentables en matière de performance, le cout en circuit n'est pas le même selon l'unité, etc. La rentabilité en performance dépend surtout des contraintes d'appariement. Les contraintes d'appariement entrainent une baisse de performance significatives si elles interdisent des paires d'instructions fréquentes. Aussi, les opérations très fréquentes, souvent appairées, gagnent à dupliquer l'unité associée. Et au contraire, les unités rarement utilisées ne doivent pas être dupliquées. Il se trouve que les unités les plus utilisées sont les ALU entières, suivies par les unités mémoire et l'unité de branchement. Le cout en circuit n'est aussi pas le même. Dupliquer les ALU entière est trivial, car elles ont un budget en transistors assez faible. Les circuits multiplieurs, diviseurs, et les ''barrel shifter'' ont eux un cout en transistor plus élevés. Si on devait faire un classement, on aurait : ALU entière < ''barrel shifter'' < multiplieur. Pour les unités mémoire, le cout en transistor est là aussi conséquent. Le résultat final est que dupliquer les ALU entières est privilégié sur le reste, comme l'a vu dans la section précédente. Les unités mémoire arrivent en seconde place, suivie par les multiplieurs/''barrel shifter'' à la troisième place, et les unités de branchement à la quatrième place. Les CPU superscalaires larges ont donc tendance à multiplier les unités mémoire en second. Les multiplieurs et ''barrel shifters'' sont eux aussi dupliqués sur les CPU superscalaires larges, mais cela vient souvent après avoir dupliqué les unités mémoire. La raison est que les accès mémoire sont critiques pour la performance des CPU à exécution dans le désordre, pas les multiplications. Pour les unités de branchement, une subtilité complique le fait d'en installer plusieurs. Que faire quand deux branchements sont pris ? La solution la plus simple est de ne pas se trouver dans cette situation : on n'utilise qu'une seule unité de branchement et on n’exécute qu'un seul branchement par cycle. La majorité des processeurs superscalaires étroits font ainsi. Les processeurs superscalaires larges ont cependant deux unités de branchements, mais ils n'acceptent qu'un seul branchement pris à la fois. Si les deux branchements pris sont exécutés en même temps, le processeur n’exécute que le plus récent, dans l'ordre du programme. ===Les CPU superscalaires mélangent les trois techniques=== Les CPU superscalaires utilisent toutes les techniques à la fois : partitionnement, duplication de l'ALU entière, duplication des autre unités fonctionnelles. Chaque processeur utilise ces trois stratégies à sa sauce, en fonction de son budget en transistor. Les quatre techniques ont cependant un cout en circuit différent, et donnent des gains de performances eux aussi forts différents. * Le '''partitionnement superscalaire''' a un cout en circuit modéré, car on n'a pas besoin de dupliquer d'ALU. * La '''duplication des ALU entières''' est la technique la plus importante : elle a un cout en circuit modéré, et donne des gains en performance impressionnants. * La '''duplication des autres unités''' a un cout en circuit variable, pour des gains en performance eux aussi variables. * Le '''partage des ports d'émission''' réduit le cout en circuit du CPU, pour des pertes de performances mineures si bien fait. L'usage du partitionnement seul, sans duplication, était surtout le fait des processeurs commercialisés entre les années 1989 et 1993. Le partage des ports d'émission a lui aussi mis un peu de temps pour s'imposer. Globalement, les processeurs superscalaires ont évolués progressivement, pour se rapprocher d'une sorte de standard. Mais pour comprendre ce qu'est ce standard, et en quoi les CPU historiques s'en sont progressivement rapprochés, il faut étudier l'histoire des CPU superscalaires. L'histoire des CPU superscalaires a commencé dès 1960, mais est resté un projet de recherche pendant plusieurs décennies. De nombreux designs ont été conçus, mais abandonnés avant la production, comme l'ACS-1 d'IBM. Les années 1980 ont vu l'arrivée de prototypes de processeurs VLIW, mais qui n'étaient pas superscalaires. Par exemple, l'Apollo DN10000 et l'Intel i860 pouvaient émettre deux instructions à la fois, sous conditions, mais demandaient que le compilateur fasse une bonne partie du travail. Les premiers CPU superscalaires commercialisés sont sortis en 1989. Deux CPUs superscalaires sont sortis cette année là : le RS/6000 d'IBM et l'i960CA d'Intel. Puis, la compétition s'est aussi lancée dans la bataille. Les CPU commercialisés avant l'année 1993 étaient à double émission, à l'exception du SuperSPARC qui utilisait la triple émission. Les modèles après 1993 hésitaient entre double et quadruple émission. Dans le tableau ci-dessous, les processeurs quadruple émission sont en bleu, les triple émission sont en rouge, les autres ne sont pas en couleur. {|class="wikitable" |- ! !! IBM !! HP PA-RISC !! SPARC !! Intel!! Alpha !! Autres |- ! 1989 | RS/6000 || || || i960CA || || |- ! 1990 | || || || || || |- ! 1991 | || || || || || Motorola 88110 |- ! 1992 | RSC || PA 7100 || class="f_rouge | SuperSPARC || || 21064 || |- ! 1993 | PPC 601 || || HyperSPARC || Pentium || || |- ! 1994 | class="f_bleu | POWER 2 || PA 7200 || || || class="f_bleu | 21164 || |- ! 1995 | 603/604 || class="f_bleu | PA 8000 || class="f_bleu | UltraSPARC 1 || Microarchitecture P6 (+ concurrence AMD) || || class="f_bleu | MIPS R1000 |- ! 1996 | class="f_bleu | 620 || || || || class="f_bleu | 21264 || |- ! ... || ... || ... || ... || ... || ... || ... |} Les premiers CPU à double émission sont assez représentatifs des CPU à double émission modernes. Il faut dire que se limiter à deux voies ne permet pas de faire beaucoup de choses. Voici le tableau chronologique disant quel CPU utilisait quelle technique. * Les CPU en vert utilisaient la double émission entière-flottante. * Les CPU en rouge utilisaient la double émission entière "pure". * Les CPU en jaune utilisaient un ''partitionnement complet'' entre ALU, FPU et unité mémoire. * Les CPU sans couleur utilisaient un grand nombre d'unités, avec beaucoup de ports d'émission. * Les CPU barrés utilisent la triple ou quadruple émission et seront vu dans la section suivante. {|class="wikitable" |- ! !! IBM !! HP PA-RISC !! SPARC !! Intel!! Alpha !! Autres |- ! 1989 | class="f_vert | RS/6000 || || || class="f_rouge | i960CA || || |- ! 1990 | || || || || || |- ! 1991 | || || || || || Motorola 88110 |- ! 1992 | class="f_vert | RSC || class="f_vert | PA 7100 || <s>SuperSPARC</s> || || class="f_jaune | 21064 || |- ! 1993 | class="f_vert | PPC 601 || || class="f_jaune | HyperSPARC || class="f_rouge | Pentium || || |- ! 1994 | <s>POWER 2</s> || PA 7200 || || || <s>21164</s> || |} C'est en 1994, que la quadruple émission est arrivée et que les CPU ont essayé divers implémentations. Il y avait alors trois stratégies pour la double/triple/quadruple émission, qui partageaient des problèmes similaires. La première implémentation dupliquait les ALUs et les FPUs, ce qui permettait d'émettre deux opérations flottantes avec deux opérations entières. Le POWER 2 dupliquait ses FPUs à la perfection, alors que le 21264 séparait l'additionneur et le multiplieur flottant. Les accès mémoire étaient traités comme des opérations entières et le processeur pouvait souvent en émettre deux à la fois. Mais une telle organisation n'est plus utilisée de nos jours, car, sans instructions flottantes à exécuter, elle est équivalente à de la double émission. La seconde implémentation utilisait deux ALU entières (dont un multiplieur), une unité mémoire et une FPU.Cette organisation est intéressante, mais encore trop stricte pour donner de bons résultats, les quadruplets avec exactement deux opérations entières, un accès mémoire et une opération flottante étant trop rares. L'usage de ces 4 unités en double ou triple émission a été utilisé sur le PA 7200 et le SuperSPARC. Malheureusement, en plus de problèmes au niveau de son cache, le SuperSPARC avait de nombreuses contraintes d’appariement, car le banc de registre n'avait pas assez de ports pour alimenter certaines combinaisons d'instructions. De plus, s'il émettait une multiplication, il ne pouvait pas émettre de seconde µop. Enfin, une troisième implémentation utilisait un grand nombre d'unités fort différentes, bien plus que quatre, avec au minimum deux ALU entières. Prenons l'exemple de l'UltraSPARC, qui avait : deux ALU entières, une unité mémoire, une unité de branchement, un additionneur flottant, un multiplieur flottant, un diviseur flottant (aussi utilisé pour le calcul des racines carrées), et 2 unités graphiques pour les calculs sur des pixels. En tout, cela fait 9 unités différentes, pour de la quadruple émission. Dans le même genre, les processeurs MIPS R8000 et R10000 avaient une implémentation plus moderne, dans le sens où toutes les unités étaient dupliquées. Le CPU avait deux ALU, deux FPU et deux unités mémoire. Il pouvait donc émettre quatre instructions à la fois, tant que le paquet collait avec ces six unités. Moitié opérations entières, moitié opérations mémoire, avec de quoi remplacer les opérations entières par des opérations flottantes. Le PA 8000, lui, dupliquait toutes ses unités en deux exemplaires, ce qui n'est pas l'idéal, aucun processeur moderne ne fait cela aujourd'hui. Le tout est résumé dans le tableau ci-dessous, avec : * en vert les CPU utilisant la première stratégie ; * en rouge les CPU utilisant la seconde stratégie ; * en jaune les CPU utilisant la troisième stratégie. {|class="wikitable" |+ Processeurs quadruple émission historiques |- ! Année !! Processeur !! Unités |- ! 1991 | class="f_jaune | Motorola 88110 || class="f_jaune | 2 ALU + MUL + DIV + BIT + MEM + FADD + 2 unités graphiques |- |- ! rowspan="2" | 1994 | class="f_vert | POWER 2 || class="f_vert | 2 ALU/MEM + 2 FPU (+ 2 Branchements via ''branch folding'') |- | class="f_rouge | 21164 || class="f_rouge | 2 ALU + FPU + MEM |- ! rowspan="3" | 1995 | PA 8000 || 2 ALU + 2 ''barrel shifters'' + 2 MEM + 2 FPU (FMA) + 2 FPU (DIV et SQRT) + 2 SIMD |- | class="f_jaune | UltraSPARC 1 || class="f_jaune | 2 ALU + MEM + BRANCH + FADD + FMULFDIV + 2 unités graphiques |- | MIPS R8000 et R10000 || 2 ALU + 2 FPU + 2 MEM |- ! rowspan="2" | 1996 | class="f_rouge | 620 || class="f_rouge | 2 ALU + FPU + MEM |- | class="f_vert | 21264 || class="f_vert | 2 ALU/MEM + 2 FPU (FADD + FMUL) |} Malheureusement, ces CPUs historiques n'avaient ni assez d'unités de calcul entières, ni d'unités mémoire, pour remplir la quadruple émission. Environ la moitié des instructions d'un programme correspond à des instructions entières. Pour en profiter, un processeur à N voies doit disposer d'au minimum N/2 ALU entières, l'idéal étant d'aller au-delà. Les CPU superscalaires historiques avaient 2 ALU pour de la quadruple émission, ce qui était très limite. En comparaison, les CPU superscalaires modernes mettent le paquet niveau ALU entière. La plupart des CPU x86 modernes descendent rarement sous les 2/3, la plupart remplit presque la totalité de la largeur du CPU. Pour donner un exemple, la microarchitecture Golden Cove d'Intel est un processeur à 6 voies, avec 5 ALU entières. Pour être précis, un programme informatique exécute environ 40% de son code dans des instructions arithmétiques, un autre 40% dans les accès mémoire, et un le reste pour... le reste. La conséquence est que les processeurs modernes dupliquent leurs unités mémoire. Par exemple, la microarchitecture ''Golden Cove'' d'Intel dispose de trois unités mémoire : deux spécialisées dans les écritures, trois dans les lectures. La microarchitecture ''Gracemont'' utilise la pentuple émission et peut émettre 4 µops entières et 4 µops mémoire. Notez que le ratio est équilibré entre opérations entières et mémoire, avec soit égalité, soit un avantage pour les ALU entières. Une autre régularité est que la largeur totale du processeur peut être occupée par des opérations entières/mémoire. Pour le dire autrement, sur un processeur quadruple émission, il pourra émettre un mix de 4 instructions entière/mémoire, avec zéro opération flottante. Idem avec des processeurs triples ou pentuple émission, voire plus. Si le processeur émet une opération flottante, elle remplace une opération entière. Prenons l'exemple des processeurs AMD K6, sortis en 1997, juste avant les AMD Athlon. Ils avaient 10 unités de calcul, 6 ports d'émission, 4 ports de décodage. Voici exactement comment étaient répartie les unités de calcul sur les ports d'émission. C'était des processeurs quadruple émission, ce qui fait que la moitié de la largeur du processeur sert pour des opérations entières, et l'autre moitié pour les accès mémoire. {|class="wikitable" |- ! Port d'émission n°1 !! Port d'émission n°2 !! Port d'émission n°3 !! Port d'émission n°4 !! Port d'émission n°5 !! Port d'émission n°6 ! |- | ALU entière || ALU entière || FPU || Unité de branchement || Unité LOAD || Unité STORE |- | || Multiplieur || || || || Unité de calcul d'adresse (instruction LEA) |- | || ''Barrel shifter'' || || || || Unité pour les instructions PUSH |} Les microarchitectures K7, K8 et K10, utilisées sur les anciens CPU AMD Athlon, sont un exemple moins représentatif. Ces microarchitectures utilisent le partage des ports d'émission au maximum. C'était des microarchitectures à triple émission, avec six ports d’émission. Il y avait trois ports d'émission pour les opérations flottantes, et trois ports d'émission pour les micro-opérations entières/mémoire. Il y avait trois ALU entières et trois unités mémoire, chaque ALU partageait un port d'émission avec une unité mémoire. Elle pouvait donc émettre soit trois opérations entières, soit trois µops mémoire, soit un mix des deux. En plus des mix avec les opérations flottantes et branchements. {|class="wikitable" |- ! Port d'émission n°1 !! Port d'émission n°2 !! Port d'émission n°3 !! Port d'émission n°4 !! Port d'émission n°5 !! Port d'émission n°6 ! |- | ALU entière || ALU entière || ALU entière || FADD || FMUL || FPU (autres opérations) |- | Multiplieur || || || || || |- | LOAD/STORE || LOAD/STORE || LOAD/STORE || || || |} Les CPU superscalaires larges dupliquent les multiplieurs et/ou les ''barrel shifters'', et même les unités de branchement. La raison est que quand la largeur du CPU augmente, le processeur traite des paquets d'instructions de plus en plus gros. Par exemple, pour un CPU à 10 voies, cela veut dire émettre 10 instructions en même temps. Il est alors plus fréquent d'avoir deux multiplications, deux décalages, deux accès mémoire, deux branchements. Par exemple, la moyenne est d'une multiplication tous les 5 instructions. Les CPU superscalaires à 10 voies ont donc tendance à avoir deux multiplications par paquet d'instruction, en moyenne. Avoir deux multiplieurs est alors un gain en performance clair. Par exemple, la microarchitecture ''Golden Cove'' a deux ''barrel shifters''. C'est une architecture à 6 voies. {|class="wikitable" |- ! Port d'émission n°1 !! Port d'émission n°2 !! Port d'émission n°3 !! Port d'émission n°4 !! Port d'émission n°5 !! Port d'émission n°6 !! Port d'émission n°7 !! Port d'émission n°8 !! Port d'émission n°9 !! Port d'émission n°10 |- | ALU entière || ALU entière || ALU entière || ALU entière || ALU entière || LOAD || LOAD || LOAD || STORE || STORE |- | ''Barrel shifter'' || Multiplieur || Multiplieur || ''Barrel shifter'' || || || || || || |- | Branchements || Diviseur || || Branchements || || || || || || |} ==Les µops mémoire sur les processeurs superscalaires== Plus haut, nous avons volontairement passé sous silence les accès mémoire. La raison à cela est que leur support sur les CPU superscalaire peut se faire de plusieurs manières différentes, qu'il vaut mieux expliquer dans une section dédiée. Les différentes implémentations sont décrites dans la liste suivante, nous allons les voir une par une. * Avec la première méthode, les accès mémoire sont exécutés par le pipeline pour les opérations entières. * Avec la seconde méthode, les accès mémoire ont un pipeline dédié ; c'est l'''émission parallèle des accès mémoire''. * Avec la troisième méthode, il est possible d'émettre plusieurs accès mémoire simultanément : c'est l'''émission multiple des accès mémoire''. Voyons ces trois cas dans le détail. Nous verrons notamment les différentes formes d'émission multiple des accès mémoire. ===Les accès mémoire traités dans le pipeline entier=== Dans le cas le plus simple, les accès mémoire sont traités comme des opérations entières un peu spéciales. L'unité mémoire est sur le même port d'émission que l'ALU entière, ce qui fait que les accès mémoire remplacent des opérations entières. Faire ainsi permet d'utiliser une ALU entière pour les calculs d'adresse, pas besoin de rajouter une unité de calcul d'adresse séparée. Typiquement, un processeur de ce type place l'unité mémoire après l'unité de calcul. Les opérations entières n'utilisent que l'ALU entière, mais pas l'unité mémoire. Les accès mémoire exécutent un calcul d'adresse dans l'ALU entière, puis l'adresse calculée est envoyée à l'unité mémoire. Le tout est illustré ci-dessous, dans le cas d'un processeur historique à double émission entière-flottante. [[File:Processeur non-superscalaire basique 01.png|centre|vignette|upright=1.5|Processeur non-superscalaire basique]] Un exemple de CPU double émission de ce type est celui du Power PC 440, un CPU destiné à l'embarqué. Vu qu'il était destiné à l'embarqué, il n'avait pas de FPU, il n'avait pas besoin de manipuler de nombres flottants. Il n'a donc que des ALU entières, un multiplieur et une unité mémoire. Il dispose de deux ports d'émission. Le premier est connecté à une ALU entière et un circuit multiplieur, le second est relié à l'unité mémoire et une seconde ALU entière. Une organisation simple, mais très efficace ! [[File:PowerPC 440.png|centre|vignette|upright=2|Microarchitecture du PowerPC 440.]] L'implémentation économise une unité de calcul d'adresse, mais n'a pas de cout en performance significatif. Les accès mémoire étant très lents, émettre une µop entière en parallèle d'un accès mémoire ne sert pas à grand-chose. On peut émettre cette µop entière un cycle plus tard, elle sera terminée avant même que l'accès mémoire soit terminé, même si l'accès mémoire se fait dans le cache L1 ou L2. Pour comprendre pourquoi, faisons quelques calculs. Un accès mémoire prend deux-trois cycles quand il tombe dans le cache L1. Et on doit attendre qu'il soit terminé pour en émettre un nouveau. Combien d'instructions arithmétiques peut-on lancer pendant ce temps, sur un CPU triple émission ? Au premier cycle, on peut en émettre 2 (l'instruction mémoire prend une place, donc 3-1), puis 3 au second cycle. Ce qui fait 5 instructions arithmétiques au total. Or, il se trouve qu'il y a une instruction d'accès mémoire pour 5 instructions arithmétiques, environ. Par contre, ce compromis change quand on passe à la quadruple émission, voire au-delà. Dans ce cas, il devient intéressant de pouvoir émettre une nouvelle instruction mémoire à chaque cycle. Ce qui nous amène à la section suivante... ===L'émission parallèle des accès mémoire=== A partir de la quadruple émission, la tendance a été de séparer l'unité mémoire des ALU entières. Cela permet d'émettre des µops mémoire en même temps que des µops entières et/ou flottantes., ce qui porte le nom d''''émission parallèle des µops mémoire''', ou encore ''émission mémoire parallèle''. Par exemple, un processeur quadruple émission peut émettre en même temps deux opérations entières, une opération flottante et un accès mémoire. L'accès mémoire vient en plus des opérations entières, il n'est pas possible de remplacer l'accès mémoire par une opération entière. La technique ne permet pas de faire les calculs d'adresse dans l'ALU entière. Il faut donc utiliser une unité de calcul d'adresse séparée. Elle est intégrée dans l'unité mémoire, qui fait l'interface entre le cache et le banc de registres. Le cout en circuits est très faible, car les unités de calcul d'adresse n'utilisent guère plus qu'un additionneur et un circuit de décalage très simple. L'émission mémoire parallèle est plus facile à implémenter sur les CPU RISC, qui ont des instructions mémoire séparées du reste. Les CPU CISC ne sont pas dans ce cas, ce qui fait que séparer l'unité mémoire du pipeline entier est compliqué. Les CPU superscalaires historiques étaient pour moité des CPU RISC et pour moitié des CPU CISC, ce qui fait qu'ils n'utilisaient pas ou peu l'émission mémoire parallèle. Les premiers processeurs superscalaires se contenaient de l'émission parallèle mémoire, ils ne pouvaient émettre qu'un seul accès mémoire à la fois, l'unité mémoire n'était pas dupliquée. Et ce n'est pas tant un problème que ça, du moins sur les processeurs à double ou triple émission. En effet, il est rare que l'unité mémoire doive faire deux accès simultanés. Notons que le processeur peut exécuter une instruction mémoire en parallèle d'un calcul ou d'un branchement, seuls les accès mémoire simultanés sont impossibles. ===L'émission multiple des accès mémoire avec une LSQ=== Les processeurs superscalaires larges travaillent sur des paquets de 8 à 10 instructions, il est plus fréquent d'avoir plusieurs accès mémoire par paquet. Dans ces conditions, il est utile d'avoir une '''émission multiple des accès mémoire''', à savoir d'émettre plusieurs micro-opérations mémoire en même temps. Les processeurs superscalaires modernes sont capables d'émettre plusieurs lectures/écritures simultanément. Par exemple, ils peuvent émettre une lecture en même temps qu'une écriture, ou plusieurs lectures, ou plusieurs écritures. Il faut noter que selon le processeur, il peut y avoir des restrictions quant aux accès mémoire émis en même temps. Par exemple, certains processeurs peuvent émettre une lecture avec une écriture en même temps, mais pas deux lectures ni deux écritures. Ou encore, ils peuvent émettre deux lectures, une lecture et une écriture, mais pas deux écritures en même temps. Dans la majorité des cas, les processeurs ne permettent pas d'émettre deux écritures en même temps, alors qu'ils supportent plusieurs lectures. Il faut dire que les lectures sont plus fréquentes que les écritures. Les processeurs qui autorisent toutes les combinaisons de lecture/écriture possibles, sont rares. L'émission multiple des accès mémoire s'implémente dans l'unité mémoire. Pour rappel, l'unité mémoire s'interpose entre le cache L1 et le reste du pipeline. Dans le cas le plus simple, elle incorpore une ''load/store queue'' et des unités de calcul d'adresse. L'émission multiple des accès mémoire demande de rendre la ''Load-store queue'' multi-ports, afin de gérer plusieurs micro-opérations mémoire simultanées. Il faut aussi dupliquer les unités de calcul d'adresse reliées à la LSQ, chaque port d'émission mémoire devant avoir sa propre unité de calcul d'adresse. : Dans ce qui suit, nous parlerons d'AGU (''Adress Generation Unit'') pour désigner les unités de calcul d'adresse, et de LSQ pour designer la ''Load-store queue''. [[File:Processeur non-superscalaire basique 02.png|vignette|upright=1|Processeur non-superscalaire basique]] Dupliquer les ports de la LSQ et les AGU est nécessaire. Mais qu'en est-il du cache ? La réponse varie suivant le processeur. Il y a deux grandes méthodes pour implémenter l'émission multiple des accès mémoire. La première méthode ajoute des ports de lecture/écriture au cache de données, pour faire des lectures/écritures simultanées. Il faut alors dupliquer des unités de calcul, rajouter les ports sur le cache, mais aussi rajouter des ports sur la ''load/store queue'', pour qu'elle échange des données/commandes mémoire avec le cache. Le cout en circuits est alors conséquent La seconde duplique les unités de calcul, mais pas les ports d'accès au cache. Les accès mémoire sont alors accumulés dans la ''load/store queue'', qui sont ensuite traités un par un par le cache. Un exemple est celui des processeurs AMD de micro-architecture K7 K8, à savoir les anciens AMD Athlon et AMD Athlon 64, qui pouvaient calculer trois adresses simultanément, mais ne pouvaient faire qu'un seul accès au cache par cycle. Le cout en circuit est modéré : les unités de calcul coutent très peu, les ports de la ''load/store queue'' coutent déjà un peu plus. Quelques processeurs utilisaient des solutions intermédiaires. Un processeur utilisant cette technique est, par exemple, capable d'émettre quatre µops mémoire en même temps, mais avec seulement deux ports sur le cache. Un exemple classique étant les Pentium 1 et 2 d'Intel, dont nous parlerons dans le chapitre suivant. ===L'émission séparée des LOAD et STORE=== De rares processeurs ont des ports d'émission qui peuvent faire indifféremment lecture comme écritures. Mais les processeurs modernes préfèrent séparer ports d'émission pour les lectures et les écritures. Un exemple est celui des processeurs Intel de micro-architecture Nehalem, qui avaient un port de lecture et un port d'écriture tous deux reliés à la ''Load-store queue'', chacun avec leur propre unité de calcul d'adresse. Comme autre exemple, les processeurs skylake ont une LSQ avec deux ports de lecture et un port d'écriture, ce qui permet d'émettre deux lectures en même temps qu'une écriture. Dans cette section, nous allons étudier le cas avec des ports séparés pour les lectures et écritures. En général, les processeurs ont un peu plus de ports de lectures que d'écritures. Une raison est qu'un programme a, en moyenne, un tout petit peu plus de lectures que d'écritures. Mais la différence n'est pas énorme, c'est un léger déséquilibre. Une seconde raison est que l'exécution dans le désordre marche bien mieux quand les lectures ont lieu le plus tôt possible. Donc mieux vaut privilégier les lectures à l'émission, pour qu'elles se fassent un peu en avance. En pratique, la différence n'est pas très marquée. Il est courant d'avoir un port de lecture et un d'écriture, les CPU superscalaires très larges ont souvent deux ports de lecture et deux d'écriture. A la rigueur, quelques CPU ont trois ports de lecture et deux d'écriture. Le cas le plus simple est celui où on peut émettre une lecture et une écriture en même temps. Il s'implémente facilement si la LSQ est en réalité une simple ''Store queue'', à savoir qu'elle met en avance les écritures uniquement. Pour rappel, les écritures sont mises en attente dans la ''Store queue'', mais les lectures sont exécutées immédiatement. Une fois l'adresse à lire connue, la lecture consulte la ''Store queue'' pour détecter les dépendances de données, puis s'exécute. La donnée à lire est lue soit depuis la ''Store queue'', soit depuis le cache. [[File:Emission LOAD - Store séparée.png|vignette|upright=1|Émission LOAD - Store séparée]] L'implémentation de l'émission multiple est alors très simple. La ''Store queue'' a déjà deux ports, avec un pour les lectures et un autre pour les écritures. Idem pour le cache, qui a naturellement un port de lecture et un d'écriture. Émettre une lecture et une écriture simultanément ne demande donc pas de modification digne de ce nom. La ''Store queue'' doit juste supporter une lecture et une écriture simultanée. Ajouter l'émission d'une seconde lecture n'est pas compliquée : il faut juste rajouter un second port de lecture sur le cache et la ''Store queue''. Pour rappel, l'unité mémoire est précédée par une structure qui met en attente les micro-opérations mémoire avant leur émission finale. La structure en question varie suivant l'implémentation, mais un cas particulier se marie bien avec l'émission multiple des µops mémoire. Il s'agit du cas avec une file de µops séparée pour les lectures et une autre pour les écritures, appelées respectivement la ''file de µops LOAD'' et la ''file de µops STORE''. Il est alors possible d'émettre une lecture et une écriture lors du même cycle d'horloge. Le port de lecture alimente une unité de calcul d'adresse dédiée, directement reliée au cache. Le port d'écriture du cache alimente une unité de calcul, qui est suivie par une file d'écriture, elle-même reliée au cache. Les processeurs AMD de micro-architecture K6 utilisaient cette technique. [[File:Double émission avec le Load Ordering, Store Ordering.jpg|centre|vignette|upright=2.5|Double émission avec le Load Ordering, Store Ordering]] ==Les CPU superscalaire à exécution dans le désordre== La superscalarité modifie en profondeur le processeur. Et cela dépasse le cadre des unités vues auparavant. Dans cette section, nous allons voir comment les autres étages du pipeline sont modifiées sur un CPU superscalaire. ===L'unité de renommage superscalaire=== Avec la superscalarité, l'unité de renommage de registre doit gérer le cas où des instructions consécutives ont des dépendances de registre. Par exemple, prenons un processeur à double émission, qui renomme deux instructions consécutives. Si elles ont une dépendance de registre, le renommage de la seconde instruction dépend du renommage de la première. La première doit être renommée et le résultat du renommage est utilisé pour renommer la seconde, ce qui empêche d'utiliser deux unités de renommage séparées. Pour cela, elle renomme les registres sans tenir compte des dépendances, pour ensuite corriger le résultat. [[File:Unité de renommage superscalaire.png|centre|vignette|upright=2|Unité de renommage superscalaire.]] Seules les dépendances lecture-après-écriture doivent être détectées, les autres étant supprimées par le renommage de registres. Repérer ce genre de dépendances se fait assez simplement : il suffit de regarder si un registre de destination d'une instruction est un opérande d'une instruction suivante. [[File:Détection des dépendances sur un processeur superscalaire.png|centre|vignette|upright=2|Détection des dépendances sur un processeur superscalaire.]] Ensuite, il faut corriger le résultat du renommage en fonction des dépendances. Si une instruction n'a pas de dépendance avec une autre, on la laisse telle quelle. Dans le cas contraire, un registre opérande sera identique avec le registre de destination d'une instruction précédente. Dans ce cas, le registre opérande n'est pas le bon après renommage : on doit le remplacer par le registre de destination de l'instruction avec laquelle il y a dépendance. Cela se fait simplement en utilisant un multiplexeur dont les entrées sont reliées à l'unité de détection des dépendances. On doit faire ce replacement pour chaque registre opérande. [[File:Correction des dépendances sur un processeur superscalaire.png|centre|vignette|upright=2|Correction des dépendances sur un processeur superscalaire.]] ===Les unités d'exécution dans le désordre=== Réduire le nombre de ports d'émission permet de garder des fenêtres d'instruction et un ROB simples, rapides et économes en énergie. Et mine de rien, c'est là un compromis important : à quoi bon augmenter le nombre de ports d'émission si c'est pour que ce soit compensé par une fréquence plus faible ? Par contre, réduire le nombre de ports augmente la ''port contention'', surtout si on a mal réparti les ALU. Il faut donc trouver un compromis entre réduire le nombre de ports d'émission d'un côté, réduire la ''port contention'' de l'autre. Le ROB doit aussi être modifié pour émettre et terminer plusieurs instructions en même temps, en ajoutant des ports de lecture/écriture. Pour un CPU superscalaire peut émettre 8 µops à la fois, on se dit que le ROB peut retirer 8 µops par cycle maximum. Et c'est très souvent le cas en pratique. Cependant, certains processeurs ont un ROB qui peut retirer le double ! Un exemple est celui des CPU de micro-architecture Skymont, qui peuvent émettre 8 µops par cycle, mais en retirer 16. Quelques CPU de marque AMD étaient aussi dans ce cas. L'avantage est que cela permet de réduire la taille du ROB, du banc de registre et de quelques autres structures matérielles liées à l'exécution dans le désordre. Le gain sur-compense le cout en transistor nécessaire pour retirer 16 µops par cycle. Un détail est que le nombre de ports de lecture n'est pas le même selon que le processeur utilisé des stations de réservation ou une fenêtre d’instruction. Rappelons la terminologie utilisée dans ce cours. Les fenêtres d'instruction se contentent de mémoriser la micro-opération à émettre et quelques bits pour la disponibilité des opérandes. Par contre, les stations de réservations mémorisent aussi les opérandes des instructions. Et cette différence a une influence le nombre de ports de lecture du banc de registre. Supposons que toutes les instructions sont dyadiques, à deux opérandes. Comptons le nombre de ports nécessaires pour alimenter les unités de calcul, sans utiliser d'arbitre de banc de registre. Avec une fenêtre d'instruction, il faut le double du nombre de ports de ''dispatch''. Mais avec des stations de réservation, il faut le double des ports de ''schedule''. La raison est que les registres sont lus après émission avec une fenêtre d'instruction, avant avec des stations de réservation (sous-entendu, après le décodage). or, comme dit plus haut, les processeurs superscalaires larges ont souvent plus de ports de ''schedule'' que de ''dispatch''. ==Les optimisations des processeurs superscalaires larges== Les processeurs superscalaires larges ont des optimisations spécifiques, qui n'ont pas de sens sur les processeurs superscalaires étroits. Dans les grandes lignes, elles se classent en deux types. Les premières permettent de charger/décoder plus de 5-10 instructions simultanément. Le problème est alors que les branchements viennent mettre leur grain de sel et qu'il faut en tenir compte. Les secondes corrigent des problèmes qui apparaissent quand un CPU intègre plus de 5 à 10 unités de calcul. ===L'étape de chargement d'un CPU superscalaire large=== Charger plusieurs instructions pose de nombreux problèmes sur les processeurs superscalaires larges, qui émettent de 5 à 10 instructions simultanées. Pour rappel, la stratégie basique pour charger N instructions est de charger un bloc de N instructions consécutives. Les instructions après un branchement ne sont pas décodées ni exécutées, ce qui entraine une petite baisse de performance. Pour un N petit, la perte de performance est mineure. Mais pour un N large, elle devient suffisamment importante pour que la conception du CPU doit alors être repensée de fond en comble. Les analyses statistiques disent qu'il y a environ un branchement tous les 4 à 6 instructions. Un CPU a donc tendance à décoder des paquets de 3 à 5 instructions consécutives, avant qu'un branchement casse la dynamique. Or, cela pose des problèmes pour utiliser plusieurs décodeurs. L'implémentation basique, qui charge N instructions consécutives pour alimenter N décodeurs, fonctionne donc jusqu'à 4/6 décodeurs. Au-delà de 5 instructions, un CPU superscalaire doit être adapté pour gérer les branchements dans les paquets d'instruction qu'il émet. Le nombre de 5 instructions ne sort donc pas de nulle part. La première solution utilise un cache de µops, avec un ''front-end'' capable de charger/décoder 4/5 instructions à la fois, pas plus. L'idée est que la majorité des instructions proviennent du cache de µops (plus de 80%), ce qui fait qu'on peut émettre environ 10 instructions à partir du cache de µops, pas besoin d'avoir beaucoup de décodeurs. Les performances sont cependant dégradées lors d'un défaut de cache de µops. Les instructions doivent alors être chargées depuis le cache d'instruction, par paquets de 4-5, puis sont décodées par 4-5 décodeurs, ce qui ne permet pas d’alimenter les unités de calcul à plein régime. La solution précédente joue sur la distinction entre pots de décodage et d'émission. Elle permet d'émettre un grand nombre de µops sans pour autant charger un grand nombre d'instructions en même temps. En quelque sorte, ce n'est qu'une méthode de contournement, le CPU superscalaire reste de type étroit. Mais il est aussi possible d'utiliser un '''''front-end large''''', capable de charger une bonne dizaine d'instructions en même temps. La seule difficulté est de tenir compte de la présence de branchements. Pour rappel, avec un ''front-end'' étroit, les instructions situées après le premier branchement pris sont annulées, elles ne sont pas décodées ni exécutées. [[File:Fetch sur un processeur superscalaire avec prediction de branchements.png|centre|vignette|upright=2|Fetch sur un processeur superscalaire avec prédiction de branchements.]] Un ''front-end'' large charge les instructions de destination du branchement et les placent à sa suite. Ils chargent deux blocs à la fois et les fusionnent en un seul qui ne contient que les instructions présumées utiles. [[File:Cache d'instructions autoaligné.png|centre|vignette|upright=2|Cache d'instructions auto-aligné.]] Mais cela demande de charger deux blocs de mémoire en une fois, ce qui demande un cache d'instruction multiports. Il faut aussi ajouter un circuit pour assembler plusieurs morceaux de blocs en un seul : le fusionneur (''merger''). En pratique, cette solution demande d'ajouter un second port de lecture au cache, pour un cout en hardware important, avec un impact négatif sur le temps d'accès au cache. Et il faut rajouter le fusionneur, qui prend un étage de pipeline à lui tout seul. Et encore, cette solution ne gère pas le cas où un bloc contient plusieurs branchements ! [[File:Implémentation d'un cache d'instructions autoaligné.png|centre|vignette|upright=2|Implémentation d'un cache d'instructions auto-aligné.]] Les exemples précédents partent du principe qu'il n'y a qu'un seul branchement dans un bloc. Mais quand les blocs deviennent assez longs, il est fréquent d'avoir plusieurs branchements dans un bloc. Et il est même possible d'avoir plusieurs branchements pris. Dans un bloc de 8-10 instructions, ce n'est pas rare, et les CPU superscalaires à 8-10 voies sont la règle dans les CPU des années 2020. Pour gérer ce cas, il faut ajouter des ports supplémentaires au cache d'instruction et adapter le fusionneur, pour qu'il puisse fusionner trois ou quatre blocs. De plus, il faut utiliser une unité de prédiction de branchement capable de ''prédire plusieurs branchements par cycle''. ===Le décodage superscalaire large=== Le décodage des instructions n'est pas censé dépendre de l'implémentation du chargement. En théorie, une fois plusieurs blocs d'instruction fusionnés, on peut décoder les instructions en parallèle. Cependant, certains processeurs effectuent le décodage en tenant compte de la manière dont les branchements découpent les blocs. Les processeurs Atom d'Intel utilisent une solution de ce type, qui couple prédiction de branchement et décodage parallèle. Ils chargent/décodent des paquets de 3 instructions consécutives, chaque paquet étant séparé du précédent par un branchement. Pour cela, en plus d'avoir un fusionneur adapté, ils regroupent les décodeurs en ''clusters'', chacun regroupant 3 décodeurs, et pouvant décoder trois instructions consécutives. Les micro-architectures Tremont et Crestmont avaient deux ''clusters'' (6 décodeurs au total), la micro-architecture Skymont en intègre trois (9 décodeurs au total). Avec deux ''clusters'', le fonctionnement est le suivant. Si le code est composé d'une suite d'instruction sans branchements, seul le premier ''cluster'' est utilisé, limitant le décodage à trois instructions par cycle. Par contre, si l'une des trois instructions décodée est un branchement, alors le second ''cluster'' décode les trois instructions situées après ce branchement, à partir de l'adresse de destination. Avec un troisième ''cluster'', c'est la même chose, sauf qu'il faut qu'il y ait un branchement dans les trois instructions décodées par le second ''cluster''. Mais comment faire pour savoir quels décodeurs activés, quels ''clusters'' sont actifs ? C'est la prédiction de branchement qui commande. Avec deux ''clusters'', elle détermine si un branchement est présent dans les trois instructions à charger, et quelle est son adresse de destination. Sans branchement, elle prévient les décodeurs et les trois instructions chargées sont décodées dans un ''cluster'' libre. Si elle prédit un branchement, elle charge les instructions adéquates, et active les deux ''clusters''. Avec trois branchements, c'est le même mécanisme, sauf que l'unité de prédiction de branchement doit être capable de prédire deux branchements par cycle. Il faut noter que les décodeurs d'un ''cluster'' sont des décodeurs simples, sans microcode. Sur les processeurs Tremont et Crestmont, le microcode était unique et était partagé entre tous les ''clusters''. Un seul ''cluster'' pouvait décoder une instruction complexe avec le microcode, l'autre devait se limiter à des instructions simples uniquement. Depuis Skymont, le microcode est partiellement dupliqué, avec un microcode par ''cluster''. Le cout en hardware peut paraitre prohibitif, pour un gain en performance faible. Sauf que c'est oublier que ce sont des processeurs Atom, sur lesquels beaucoup d'instructions sont microcodées. Les processeurs Atom ont un budget en transistor limité, ce qui fait qu'il est préférable pour eux d'implémenter des instructions en microcode que d'utiliser des unités de calcul dédiées ou des circuits de décodage complexes. Pour limiter la casse, le microcode n'est que partiellement dupliqué. Seul le microcode pour les instructions fréquente est dupliqué. Il y a un microcode secondaire pour certaines instructions, qui est lui unique et partagé par tous les ''clusters''. ===Le contournement sur les processeurs superscalaires larges=== Pour rappel, la technique du contournement (''register bypass'') permet au résultat d'une instruction d'être immédiatement utilisable en sortie de l'ALU, avant même d'être enregistré dans les registres. Implémenter la technique du contournement demande d'utiliser des multiplexeurs pour relier la sortie de l'unité de calcul sur son entrée si besoin. Il faut aussi des comparateurs pour détecter des dépendances de données. [[File:Pipeline Bypass.png|centre|vignette|upright=1|Pipeline Bypass]] La sortie de chaque ALU doit être reliée aux entrées de toutes les autres, avec les comparateurs qui vont avec. Sur les processeurs ayant plusieurs d'unités de calculs, cela demande beaucoup d’interconnexions et de comparateurs. Pour N unités de calcul, cela demande 2 * N² interconnexions, implémentées avec 2N multiplexeurs de N entrées chacun. Si c'est faisable pour 2 ou 3 ALUs, la solution est impraticable au-delà. Non seulement ce serait un enfer à câbler, mais les fils seraient tellement longs que la fréquence du processeur en prendrait un coup. Notez que cela vaut pour les processeurs superscalaires, mais aussi pour tout processeur avec beaucoup d'unités de calcul. En théorie, un CPU à exécution dans le désordre, non-superscalaire, mais avec beaucoup d'unités de calcul, fait face au même problème. En pratique, avoir une dizaine d'ALU implique processeur superscalaire à exécution dans le désordre. D'où le fait qu'on parle du problème maintenant. La seule solution praticable est de ne pas relier toutes les unités de calcul ensemble. À la place, on préfère regrouper les unités de calcul dans différents '''agglomérats''' ('''cluster'''). Le contournement est alors possible entre les unités d'un même agglomérat, mais pas entre agglomérats différents. Évidemment, certaines possibilités de contournement sont perdues, mais c'est compensé par un gain en fréquence et un cout en circuit/fils réduit. C'est un bon compromis, ce qui explique que presque tous les processeurs modernes l'utilisent. Les rares exceptions sont les processeurs POWER 4 et POWER 5, qui ont préféré se passer de contournement pour garder un processeur très simple et une fréquence élevée. ===Le banc de registre des processeurs superscalaires larges=== Pour émettre deux opérations entières simultanément, il faut lire deux fois plus d'opérandes. Et il faut aussi écrire deux résultats dans les registres, en même temps. Pour cela, on double les ports du banc de registre. En général, il faut au maximum 2 ports de lecture et un port d'écriture par unité de calcul. Le problème, c'est que plus un banc de registres a de ports, plus il utilise de circuits, est compliqué à concevoir, consomme de courant et chauffe. Avec plusieurs dizaines d'unités de calcul différentes, ajouter autant de ports impacterait la fréquence du banc de registres et réduirait les performances, voire est totalement impraticable à cause des contraintes thermiques. Les processeurs superscalaires larges doivent donc trouver des solutions. La première solution utilise un banc de registre unique, mais n'utilise pas autant de ports que le pire des cas le demanderait. Le processeur détecte quand il n'y a pas assez de ports pour servir toutes les micro-opérations. Quand ça arrive, l'unité d'émission émet assez de micro-opérations pour exploiter les ports disponibles, mais met en attente les micro-opérations en trop, le temps que les ports se libèrent. Pour cela, le banc de registre est géré par un circuit d'arbitrage spécialisé, intégré à l'unité d'émission, l’'''arbitre du banc de registres''' (''register file arbiter''). Une autre solution est de dupliquer le banc de registres en plusieurs exemplaires qui contiennent exactement les mêmes données, mais avec moins de ports de lecture/écriture. Un exemple est celui des processeurs POWER, Alpha 21264 et Alpha 21464. Sur ces processeurs, le banc de registre est dupliqué en plusieurs exemplaires, qui contiennent exactement les mêmes données. Les écritures dans les registres sont envoyées à tous les bancs de registres, afin de garantir que leur contenu est identique. Chaque exemplaire a exactement deux ports de lecture. La conception du processeur est simplifiée, que ce soit au niveau du câblage, que de la conception des bancs de registres. Une dernière solution découpe le banc de registres en plusieurs bancs de registres plus petits, avec un réseau d'interconnexions pour échanger des données entre bancs de registres. Dans la plupart des cas, cette séparation est invisible du point de vue de l'assembleur et du langage machine. Le processeur se charge de transférer les données entre bancs de registres suivant les besoins. Sur d'autres processeurs, les transferts de données se font via une instruction spéciale, souvent appelée ''COPY''. [[File:Banc de registres distribué.png|centre|vignette|upright=2|Banc de registres distribué.]] Sur de certains processeurs, un branchement est exécuté par une unité de calcul spécialisée. Or les registres à lire pour déterminer l'adresse de destination du branchement ne sont pas forcément dans le même agglomérat que cette unité de calcul. Pour éviter cela, certains processeurs disposent d'une unité de calcul des branchements dans chaque agglomérat. Dans les cas où plusieurs unités veulent modifier le ''program counter'' en même temps, un système de contrôle général décide quelle unité a la priorité sur les autres. Mais d'autres processeurs fonctionnent autrement : seul un agglomérat possède une unité de branchement, qui peut recevoir des résultats de tests de toutes les autres unités de calcul, quel que soit l’agglomérat. ==La macro-fusion : une optimisation du décodage parallèle== Les processeurs superscalaires supportent la technique dite de '''macro-fusion''', qui permet de fusionner deux-trois instructions consécutives en une seule micro-opération. Par exemple, il est possible fusionner une instruction de test et une instruction de saut en une seule micro-opération de branchement. Il s'agit là de l'utilisation la plus importante de la macro-fusion sur les processeurs x86 modernes. La macro-fusion est utilisée pour fusionner un test et un saut en un branchement unique. L'optimisation est présente sur la plupart des CPU superscalaires modernes. Les CPU x86 l'implémentent, mais aussi quelques CPU ARM et les processeurs Apple de série M (M1 à M5, et potentiellement plus quand ils seront sortis). La fusion des instructions se fait lors du décodage des instructions, grâce à la coopération des décodeurs. Elle marche systématiquement si l'instruction de test et le saut sont consécutives, qu'elles forment bien une paire d'instruction. Par contre, insérer une instruction entre les deux empêche la macro-fusion de faire son travail, sauf si l'instruction insérée est un NOP. Les processeurs ARM64 avaient une forme spécifique de macro-fusion, utilisée pour un cas très précis : charger une constante immédiate de 32 ou 64 bits, dans un registre 64 bits. Les CPU ARM 64 bits utilisent des instructions codées sur 32 bits, qui ne peuvent encoder que des constantes immédiates de 16 bits. Pour charger une constante de 32/64 bits, il faut s'y prendre en plusieurs fois. Une première instruction MOVZ charge les 16 premiers bits, puis 2 à 4 instructions MOVK remplissent le reste. MOVZ met à zéro les 48 bits de poids fort, MOVK permet de sélectionner le doublet à modifier dans le registre (on modifie 16 bits, dont on choisit la place, le reste du registre est laissé tel quel). ARM permet de fusionner cette suite d'instructions en une seule µop MOV avec une constante immédiate. On parle alors de '''''MOVZ + MOVK fusion'''''. Une dernière utilisation, [https://www2.eecs.berkeley.edu/Pubs/TechRpts/2016/EECS-2016-130.pdf proposées pour le jeu d'instruction RISC-V], fusionnait un calcul d'adresse et une instruction LOAD/STORE en une seule instruction d'accès mémoire. Elle a été envisagée à une époque où le jeu d'instruction RISC-V ne gérait pas les modes d'adressage base + indice. L'idée était d'avoir une unité mémoire qui gérait le mode d'adressage base + indice et de fusionner des instructions pour l'exploiter. Mais de nos jours, le jeu d'instruction RISC-V intègre le mode d'adressage base + indice, ce qui fait que la macro-fusion n'a aucun intérêt pour ce cas précis. La macro-fusion a de nombreuses limitations en pratique. En théorie, on pourrait l'utiliser au-delà des deux situations mentionnées plus haut. Par exemple, pour fusionner une addition et une multiplication en une seule instruction MAD. Mais l'intérêt est assez faible. Notons que la macro-fusion ne permet que de fusionner des paires d'instructions. Aller au-delà demanderait un cout en circuit trop important. Déjà que tester deux paires utilise pas mal de comparateurs... ==Le ''stack engine'' : une optimisation de la pile d'appel== Les processeurs modernes intègrent une optimisation liée au pointeur de pile. Pour rappel, sur les architectures modernes, le pointeur de pile est un registre utilisé pour gérer la pile d'appel, précisément pour savoir où se trouve le sommet de la pile. Il est manipulé par les instructions CALL, RET, PUSH et POP, mais aussi par les instructions d'addition/soustraction pour gérer des cadres de pile de taille variable. De plus, il peut servir d'opérande pour des instructions LOAD/STORE, afin de lire/écrire des variables locales, les arguments d'une fonction, et autres. C'est donc un registre général adressable, intégré au banc de registre, altéré par l'unité de calcul entière. L'incrémentation/décrémentation du pointeur de pile passe donc par l'unité de calcul, lors des instructions CALL, RET, PUSH et POP. Mais, l'optimisation que nous allons voir permet d'incrémenter/décrémenter le pointeur de pile sans passer par l'ALU, ou presque. L'idée est de s'inspirer des architectures avec une pile d'adresse de retour, qui intègrent le pointeur de pile dans le séquenceur et l'incrémentent avec un incrémenteur dédié. ===Le ''stack engine''=== L'optimisation que nous allons voir utilise un '''''stack engine''''' intégré à l'unité de contrôle, au séquenceur. Le processeur contient toujours un pointeur de pile dans le banc de registre, cela ne change pas. Par contre, il n'est pas incrémenté/décrémenté à chaque instruction CALL, RET, PUSH, POP. Un compteur intégré au séquenceur est incrémenté à la place, nous l’appellerons le '''compteur delta'''. La vraie valeur du pointeur de pile s'obtient en additionnant le compteur delta avec le registre dans le banc de registre. Précisons que si le compteur delta vaut zéro, la vraie valeur est dans le banc de registre et peut s'utiliser telle quelle. Lorsqu'une instruction ADD/SUB/LOAD/STORE utilise le pointeur de pile comme opérande, elle a besoin de la vraie valeur. Si elle n'est pas dans le banc de registre, le séquenceur déclenche l'addition compteur-registre pour calculer la vraie valeur. Finalement, le banc de registre contient alors la bonne valeur et l'instruction peut s'exécuter sans encombre. L'idée est que le pointeur de pile est généralement altéré par une série d'instruction PUSH/POP consécutives, puis des instructions LOAD/STORE/ADD/SUB utilisent le pointeur de pile final comme opérande. En clair, une bonne partie des incrémentations/décrémentation est accumulée dans le compteur delta, puis la vraie valeur est calculée une fois pour toutes et est utilisée comme opérande. On accumule un delta dans le compteur delta, et ce compteur delta est additionné quand nécessaire. Précisons que le compteur delta est placé juste après le décodeur d'instruction, avant même le cache de micro-opération, l'unité de renommage et l'unité d'émission. Ainsi, les incrémentations/décrémentations du pointeur de pile disparaissent dès l'unité de décodage. Elles ne prennent pas de place dans le cache de micro-opération, ni dans la fenêtre d'instruction, ni dans la suite du pipeline. De nombreuses ressources sont économisées dans le ''front-end''. Mais utiliser un ''stack engine'' a aussi de nombreux avantages au niveau du chemin de données/''back-end''. Déjà, sur les processeurs à exécution dans le désordre, cela libère une unité de calcul qui peut être utilisée pour faire d'autres calculs. Ensuite, le compteur delta mémorise un delta assez court, de 8 bits sur le processeur Pentium M, un peu plus pour les suivants. L'incrémentation se fait donc via un incrémenteur 8 bits, pas une grosse ALU 32/64 bits. Il y a un gain en termes de consommation d'énergie, un incrémenteur 8 bits étant moins gourmand qu'une grosse ALU 32/64 bits. ===Les points de synchronisation du delta=== La technique ne fonctionne que si la vraie valeur du pointeur de pile est calculée au bon moment, avant chaque utilisation pertinente. Il y a donc des '''points de synchronisation''' qui forcent le calcul de la vraie valeur. Plus haut, nous avions dit que c'était à chaque fois qu'une instruction adresse le pointeur de pile explicitement, qui l'utilise comme opérande. Les instructions CALL, RET, PUSH et POP ne sont pas concernées par elles utilisent le pointeur de pile de manière implicite et ne font que l'incrémenter/décrémenter. Mais dans les faits, c'est plus compliqué. D'autres situations peuvent forcer une synchronisation, notamment un débordement du compteur delta. Le compteur delta est généralement un compteur de 8 bits, ce qui fait qu'il peut déborder. En cas de débordement du compteur, le séquenceur déclenche le calcul de la vraie valeur, puis réinitialise le compteur delta. La vraie valeur est donc calculée en avance dans ce cas précis. Précisons qu'un compteur delta de 8 bits permet de gérer environ 30 instructions PUSH/POP consécutives, ce qui rend les débordements de compteur delta assez peu fréquent. À noter que si le compteur delta vaut zéro, il n'y a pas besoin de calculer la vraie valeur, le séquenceur prend cette situation en compte. Un autre point de synchronisation est celui des interruptions et exceptions matérielles. Il faut que le compteur delta soit sauvegardé lors d'une interruption et restauré quand elle se termine. Idem lors d'une commutation de contexte, quand on passe d'un programme à un autre. Pour cela, le processeur peut déclencher le calcul de la vraie valeur lors d'une interruption, avant de sauvegarder les registres. Pour cela, le processeur intègre un mécanisme de sauvegarde automatique, qui mémorise la valeur de ce compteur dans le tampon de réordonnancement, pour forcer le calcul de la vraie valeur en cas de problème. La technique du ''stack engine'' est apparue sur les processeurs Pentium M d'Intel et les processeurs K10 d'AMD, et a été conservée sur tous les modèles ultérieurs. L'implémentation est cependant différente selon les processeurs, bien qu'on n'en connaisse pas les détails et que l'on doive se contenter des résultats de micro-benchmarks et des détails fournit par Intel et AMD. Selon certaines sources, dont les manuels d'optimisation d'Agner Fog, les processeurs AMD auraient moins de points de synchronisation que les processeurs Intel. De plus, leur ''stack engine'' serait placé plus loin que prévu dans le pipeline, après la file de micro-opération. ==La micro-fusion et la délamination== La '''micro-fusion''' est une optimisation qui retarde le décodage réel de certaines instructions assez loin dans le pipeline. Elle concerne surtout les instructions ''load-op'', mais pas que. D'autres instructions mémoire peuvent subir cette optimisation. L'essentiel est que, sans micro-fusion, ces instructions sont décodées en plusieurs micro-opérations. La micro-fusion retarde le décodage final de ces instructions, qui se fait assez tard dans le pipeline. Pour comprendre l'intérêt, nous allons devoir faire quelques explications. L'optimisation en question fait le décodage en deux temps. Prenons l'exemple d'une opération ''load-op'', qui est censée être décodée en deux micro-opérations : une lecture, puis une opération. Le décodage produit une '''macro-opération''' (terme d'Intel), qui est propagée dans le pipeline, pour être scindée en deux micro-opérations un peu avant les ALU et l'unité mémoire. La macro-opération est techniquement une micro-opération comme une autre, sauf que ce n'est pas la micro-opération finale. L'unité d'émission découpe cette macro-opération en deux micro-opérations, qui sont émises l'une après l'autre. Pour résumer, les instructions ''load-op'' ne sont complétement découpées en micro-opération lors de l'émission, pas lors du décodage. L'optimisation impacte ce qui se situe entre le décodeur d'instruction et l'unité d'émission : l'unité de renommage, la file de micro-opérations, le ''Loop Stream Detector'' et/ou le cache de micro-opération. L'intérêt de l'optimisation est que l'on fusionne deux micro-opérations en une seule macro-opération. Le cache de micro-opération et la file de micro-opération peuvent mémoriser plus de micro-opérations, vu que certaines sont fusionnées. Quant à l'unité de renommage, elle renomme une seule macro-opération au lieu de renommer deux micro-opérations, ce qui augmente le débit du renommage de registre. Par contre, cela complique un peu le travail de l'unité d'émission, qui doit découper les instructions ''load-op'' en deux et émettre les micro-opérations l'une après l'autre. Il faut noter qu'il n'y a pas de fusion proprement dite. Le décodeur ne décode pas deux micro-opérations séparées, avant de les fusionner. En réalité, il génère directement une macro-opération, qui correspond à deux micro-opérations, que ce soit plus tard dans le pipeline ou sur une autre architecture. D'ailleurs, la micro-fusion a lieu à l'intérieur d'une instruction, qui est censée être traduite en plusieurs micro-opérations. Et encore : pas toutes. La conséquence est que la technique n'a de sens que sur les processeurs CISC, avec des instructions assez complexes pour être décodées en plusieurs micro-opérations. En pratique, seuls les CPU x86 sont concernés. ===Les domaines de fusion=== La micro-fusion n'est pas spécifique aux processeurs superscalaires. En théorie, on peut l'implémenter sur un processeur non-superscalaire à émission dans l'ordre. Cependant, je ne connais aucun processeur à émission dans l'ordre qui implémente la micro-fusion, même si son implémentation est en théorie possible sur de tels processeurs. La micro-fusion est implémentée différemment selon que l'émission se fasse dans l'ordre, dans le désordre, avec une fenêtre d'instruction, avec des stations de réservation, etc. J'ai dit plus haut que les micro-opérations sont générées lors de l'émission, mais le moment exact varie suivant le processeur. Pour simplifier le tout, nous parlerons de '''scission''' quand le processeur traduit une macro-opération en deux/trois micro-opérations. Le moment où a lieu la scission est théoriquement dans l'unité d'émission, mais celle-ci prend plusieurs formes selon que le processeur utilise l'émission dans l'ordre, dans le désordre, avec une fenêtre d'instruction, avec des stations de réservation, etc. Les processeurs à exécution dans l'ordre sont le cas le plus simple. L'unité d'émission est alors en charge du découpage des macro-opérations en micro-opérations. La file de micro-opération ou le cache de micro-opérations mémorisent des macro-opérations. À l'opposé, le tampon de ré-ordonnancement mémorise lui des micro-opérations. Le reste du processeur n'est pas affecté. Sur les processeurs à exécution dans le désordre, les différences sont nombreuses. Le tampon de ré-ordonnancement peut mémoriser soit les micro-opérations finales, soit des macro-opérations. Il en est de même avec la fenêtre d'instruction et/ou les stations de réservation, qu'elles soient centralisées ou décentralisées. Tout dépend de comment se fait l'implémentation. Une implémentation simple effectue la scission des macro-opérations dans l'unité d'émission, comme sur les processeurs à exécution dans l'ordre. Sur de nombreux processeurs, l'unité d'émission et de renommage sont simplement fusionnées, ce qui fait que la scission a lieu juste après le renommage de registres. En conséquence, le tampon de re-ordonnancement et les fenêtres d'instruction ne voient que des micro-opérations. Les macro-opérations disparaissent juste avant. Cette implémentation est préférée avec une fenêtre d'instruction centralisée, car on voit mal comment scinder une macro-opération juste avant l'ALU. Une autre implémentation fait la scission en sortie de la fenêtre d'instruction ou des stations de réservation, qu'elles soient centralisées ou décentralisées. Dans ce cas, le tampon de re-ordonnancement fait de même. L'avantage est que le tampon de ré-ordonnancement stocke des macro-opérations, ce qui augmente sa taille effective. Par exemple, il utilise une seule entrée pour une instruction ''load-op'', au lieu de deux sans micro-fusion de ce type. ===La délamination=== La scission peut parfois avoir lieu plus tôt, dans des circonstances très précises. Concrètement, cela ne concerne que quelques micro-architectures d'Intel, pour des instructions très précises. La raison est les limitations du format des micro-opérations, pas une histoire d'optimisation. Le processeur aimerait pouvoir faire la micro-fusion, mais ne peut pas à cause de ces limitations. Une telle situation cause alors une '''délamination''' : les micro-opérations sont scindées précocement. Par exemple, sur la micro-architecture SandyBridge d'Intel, les opérations ''load-op'' sont fusionnées, sauf si elles utilisent l'adressage indicé. Les micro-opérations de ce processeur ne peuvent avoir que deux opérandes. Trois opérandes est de trop, et les opérations ''load-op'' en adressage indicé dépassent cette limite. Dans ce cas, les micro-opérations sont fusionnées en sortie du décodeur, mais sont scindés avant d’entrer dans la file de micro-opération. L'avantage n'est pas évident, vu que l'unité de renommage suit de près le décodeur d'instruction. Il est que le cache de micro-opérations mémorise des macro-opérations, pas deux micro-opérations. ===La micro-fusion des processeurs Intel et AMD=== La micro-fusion est apparue sur le Pentium M d'Intel, et a été conservée telle quelle sur les processeurs Intel suivants. Les processeurs AMD supportent la micro-fusion depuis au moins la micro-architecture AMD K8. Mieux que ça, la délamination n'était pas utilisée car les micro-opérations acceptaient plus de 2 opérandes. Que ce soit chez AMD ou Intel, elle concerne deux types d'instructions : les écritures mémoire et les instructions ''load-op''. Sur le Pentium M, il y avait quelques limitations, l'optimisation se limitant par exemple aux registres entiers et flottants, mais pas aux registres MMX, mais pas les registres SSE (nous verrons ces registres dans quelques chapitres). Mais les processeurs modernes n'ont plus cette limitation. La micro-fusion des CPU Intel concerne deux types d'instructions : les écritures mémoire et les instructions ''load-op''. Pour les écritures, la situation est plus complexe que pour les instructions ''load-op''. Sur les processeurs Intel précédents, les écritures étaient réalisées dans deux unités séparées : le calcul d'adresse dans une unité de calcul, l'écriture proprement dite dans l'unité mémoire. Elles prenaient donc deux micro-opérations. Le processeur a changé ses unités mémoire, ce qui fait qu'elle pouvait faire les deux en une seule micro-opération. Il s'agissait alors d'une optimisation du chemin de données, plus que de la micro-fusion proprement dite, mais je la mentionne ici malgré tout. La micro-fusion sur ce processeur avait un avantage en plus des avantages usuels : elle utilisait mieux les décodeurs. Les Pentium M avaient trois décodeurs pour décoder plusieurs instructions en même temps, chose qu'on détaillera dans le chapitre sur les processeurs superscalaires. Un seul décodeur pouvait décoder toutes les instructions, les deux autres ne peuvent décoder que les instructions simples, qui se décodent en une seule micro-opération. Avec la micro-fusion, les trois décodeurs peuvent gérer les écritures mémoire et les opérations ''load-op'', vu qu'elles deviennent des instructions simples. Sans cela, elles auraient été limitées au premier décodeur. <noinclude> {{NavChapitre | book=Fonctionnement d'un ordinateur | prev=Le parallélisme mémoire | prevText=Le parallélisme mémoire | next=Exemples de microarchitectures CPU : le cas du x86 | nextText=Exemples de microarchitectures CPU : le cas du x86 }} </noinclude> 0vcx1aradcybt4tsl44m3mvvqvg2d5r 773241 773240 2026-09-26T14:15:48Z Mewtow 31375 /* Le partage des ports d'émission */ 773241 wikitext text/x-wiki Les processeurs vus auparavant ne peuvent émettre au maximum qu'une instruction par cycle d'horloge. Ils peuvent avoir plusieurs instructions qui s'exécutent en même temps, dans des unités de calcul séparées. C'est le cas dès qu'une instruction multi-cycles s'exécute. Par contre, ils ne peuvent démarrer qu'une seule instruction par cycle. Et quand on court après la performance, ce n'est pas assez ! Les concepteurs de processeurs ont inventés des processeurs qui émettent plusieurs instructions par cycle : les '''processeurs à émissions multiples'''. Un processeur à émission multiple charge plusieurs instructions en même temps, les décode en parallèle, puis les émet en même temps sur des unités de calculs séparées. Pour cela, il faut gérer les dépendances entre instructions, répartir les instructions sur différentes unités de calcul, et cela n'est pas une mince affaire. En général, pour un processeur à émission multiple capable d'émettre N instructions en parallèle, on parle de '''processeur à N voies''', ou encore un ''processeur à N pipelines''. [[File:Superscalarpipeline.svg|centre|vignette|upright=1.5|Pipeline RISC classique à cinq étages sur un processeur superscalaire. On voit bien que plusieurs instructions sont chargées en même temps.]] Les processeurs à émission multiple sont de deux types : les processeurs VLIW et les '''processeurs superscalaires'''. Nous mettons les processeurs VLIW de côté en attendant le prochain chapitre. La raison est qu'ils ont un jeu d'instruction spécialisé qui expose le parallélisme d'instruction directement au niveau du jeu d'instructions, là où les processeurs superscalaires restent des processeurs au jeu d'instruction normal. La différence principale entre processeur VLIW et superscalaire est qu'un processeur superscalaire répartit les instructions sur les unités de calcul à l’exécution, là où un processeur VLIW délègue cette tâche au compilateur. ==Le nombre de voies d'un processeur superscalaire== Les processeurs superscalaires les plus simples ne permettent que d'émettre deux micro-opérations à la fois, d'où leur nom de processeur ''dual issue'', ce qui se traduit en '''processeur à double émission'''. Les processeurs triple émission permettent eux d’émettre trois instructions à la fois. A l'opposé, les CPU superscalaires les plus puissants peuvent émettre jusqu'à 8-10 instructions simultanément. En 2026, les CPU superscalaires peuvent monter jusqu’à 10 µops émises en même temps, ce qui est énorme ! Et cela nous amène à faire une distinction entre les processeurs superscalaires dits '''étroits''' et ceux dits '''larges'''. Dans ce wikilivre, on part du principe que les processeurs superscalaires étroits émettent jusqu'à 4-5 µops à la fois, alors que les larges peuvent en émettre plus, l'intervalle entre les deux est une zone grise. Le nombre exact n'est pas fixé dans le marbre, la distinction est volontairement vague. Nous parlerons de la '''largeur d'un CPU superscalaire''' pour parler du nombre de voies, du nombre de µops émises en même temps. Plus un CPU superscalaire est large, plus il est performant ''toute chose égale par ailleurs''. Mais dans les faits, vous pouvez oublier la partie en italique. Élargir un CPU superscalaire demande de rajouter beaucoup de circuits. Et ce cout en circuit aura un impact sur la performance. A budget en transistors limité, il y a un compromis entre élargir un CPU et rajouter du cache ou la taille des fenêtres d'instructions/ROB et autres. ===Les contraintes d’appariement=== Prenons un processeur double émission, capable d'émettre deux instructions simultanément. Émettre deux instructions en même temps n'est pas toujours possible. Par exemple, si deux instructions sont dépendantes, on ne peut pas les émettre en même temps. Mais oublions un instant ces histoires de dépendances de données, et partons du principe que le CPU ait affaire à deux instructions indépendantes, une paire idéale. Un CPU double émission "parfait" pourrait exécuter n'importe quelle paire d'instruction. Par exemple, il pourrait émettre deux multiplications consécutives, deux lectures consécutives, une lecture et une écriture en même temps, etc. En pratique, aucun processeur ne permet cela. Implémenter un CPU superscalaire "parfait" aurait un cout en circuit beaucoup trop conséquent. Par exemple, pour émettre deux multiplications en même temps, il faudrait deux multiplieurs séparés. Idem pour exécuter deux accès mémoire simultané : cela demande d'avoir deux unités mémoire et un cache multiport. Mais en pratique, aucun CPU superscalaire ne fait cela. La totalité des CPU double émission se débrouille avec un seul circuit multiplieur et une seule unité mémoire. Et cela entraine l'apparition de dépendances structurelles, qui font que certaines paires d'instructions sont impossibles. Par exemple, impossible d'émettre deux multiplications en même temps avec un seul circuit multiplieur. Mais émettre une multiplication en parallèle d'une addition est parfaitement possible. Il y a donc des paires d'instructions autorisées et des paires interdites. Et la logique est la même sur les CPU à triple ou quadruple émission, et au-delà. : des triplets ou quadruplet d'instructions sont impossibles à émettre simultanément, alors que d'autres le sont. Pour le dire autrement, il y a des '''contraintes d'appariement''' sur les instructions à émettre en même temps. ===L'impact des contraintes d’appariement sur la performance=== Ces contraintes d’appariement ont un impact sur les performance, mais celui-ci peut être grandement mitigé. Les programmes informatiques utilisent en pratique beaucoup plus d'opérations entières de base que de multiplication, branchements et accès mémoire. Il y a environ 5 opérations entières de base pour une multiplication entière. En pratique, il est rare de devoir exécuter deux multiplications à la fois. Idem pour les décalages et rotations, qui sont assez éloignés les uns des autres. Deux branchements consécutifs sont quant à eux plus fréquents. Deux accès mémoire consécutifs est assez rare, mais ils restent assez proches les uns des autres malgré tout. La conséquence est que certaines paires d'instructions sont privilégiées par rapport au reste. Les paires regroupant deux calculs basiques (additions, soustractions, ...) sont systématiquement supportées, car dupliquer des ALU entières est facile. Les paires regroupant un calcul et une autre instruction sont elles aussi systématiquement supportées. Par contre, les paires avec deux branchements, deux accès mémoire, deux décalages/rotations, ou deux multiplications/divisions sont fréquemment interdites. Seuls les CPU superscalaires larges peuvent les permettre, et encore : sous conditions. Le résultat est que le processeur peut se débrouiller avec une seule unité mémoire et une unité de branchement. ===Terminologie pour la suite du chapitre=== Décrire les contraintes d’appariement demande de dire quelles combinaisons d’instructions sont autorisées ou interdites. Et pour cela, nous allons introduire les abréviations suivantes : * INT est un raccourcis pour parler d'une opération entière simple, qu'une ALU entière peut exécuter. * MUL correspond à une opération entière complexe, typiquement une multiplication, éventuellement à une division. * FLOAT désigne une opération flottante, peut importe laquelle. * MEM désigne un accès mémoire, à savoir une instruction ou µop mémoire. * BRANCH désigne une instruction de branchement. Cette terminologie permettra de définir des paires d'instructions, qu'elles soient possibles ou interdites. Par exemple, l'expression INT + FLOAT désigne une paire d'instruction regroupant une instruction entière et une instruction flottante, peu importe l'ordre des deux instructions. Il en sera de même pour les triplets, quadruplets, ou plus. Par exemple, l'expression INT + INT + FLOAT + MUL désigne un quadruplet d'instructions regroupant : deux instructions entières, une instruction flottante, et une multiplication entière. J'utiliserais aussi des parenthèses pour regrouper certaines combinaisons. Par exemple, l'expression INT + (MUL /FLOAT) veut parler d'une paire d'instruction où la première est une opération entière basique, la seconde est : soit une multiplication, soit une opération flottante. ==Le ''front-end'' des processeurs superscalaires== Un processeur superscalaire doit charger, décoder, et exécuter plusieurs instructions par cycle. Par exemple, un processeur triple émission doit charger 3 instructions lors d'un premier cycle, puis les décoder au cycle suivant, et les exécuter au cycle suivant. Il travaille donc pas paquets de 3 instructions, dans cet exemple. Intuitivement, charger, décoder et exécuter plusieurs instructions à la fois demande de dupliquer des circuits. Par exemple, il faut dupliquer les décodeurs pour décoder plusieurs instructions. Les unités de calcul sont aussi dupliquées, sauf sur certaines processeurs qui arrivent à ruser, mais laissons cela de côté pour plus tard. Mais d'autres circuits sont simplement modifiés, comme l'unité de chargement ou l'unité d'émission. Voyons ce qu'il en est dans le détail. {|class="wikitable" |- ! rowspan="2" | Processeur sans émission multiple | rowspan="2" | Chargement | rowspan="2" | Décodage | rowspan="2" | Renommage | rowspan="2" | Émission | Exécution / ALU | rowspan="2" | ''Commit''/ROB |- | Exécution / ALU |- ! rowspan="4" | Processeur superscalaire | rowspan="4" | Chargement | rowspan="2" | Décodage | rowspan="4" | Renommage | rowspan="4" | Émission | Exécution / ALU | rowspan="4" | ''Commit''/ROB |- | Exécution / ALU |- | rowspan="2" | Décodage | Exécution / ALU |- | Exécution / ALU |} Nous allons d'abord voir ce qu'il en est pour le ''front-end'', à savoir la partie du CPU qui s'occupe de charger et décoder les instructions. Elle regroupe l'unité de chargement, les décodeurs, l'unité de prédiction de branchement, la file de µops, la file d'instruction, et divers caches. En soi, les changements portent surtout sur l'unité de chargement et les décodeurs. ===Le chargement de plusieurs instructions consécutives=== Un processeur superscalaire charge plusieurs instructions en même temps, ''en tenant compte de la présence de branchements''. La partie en italique est importante et elle a une conséquence très forte : la manière de faire est fortement différente entre un CPU superscalaire étroit et large. Un CPU superscalaire étroit fait au plus simple : il charge un bloc de 2, 3, 4 instructions consécutives en mémoire RAM. Il faut alors doubler/tripler/quadrupler la taille du bus connecté au cache d'instruction. Par exemple, pour charger quatre instructions de 32 bits chacune, il suffit de lire un bloc de 128 bits dans le cache d'instruction. Un bloc de 8, 16, 32 octets est donc lu depuis le cache et est ensuite découpé en instructions, envoyées chacun à un décodeur. Charger plusieurs instructions consécutives ne marche bien qu'en absence de branchements. La présence de branchements casse cette dynamique, et il faut gérer leur présence. * Sans prédiction de branchement, toutes les instructions d'un bloc sont décodées et exécutées en même temps, même s'il y a un branchement dans le tas. * Avec prédiction de branchement, les instructions situées après le branchement ne sont pas décodées ni exécutées. L'unité de chargement coupe le bloc chargé au niveau du premier branchement pris, remplit les vides avec des NOP, avant d'envoyer le tout à l'unité de décodage. Les instructions à l'adresse de destination seront chargées au cycle suivant, en chargeant un nouveau bloc. [[File:Fetch sur un processeur superscalaire avec prediction de branchements.png|centre|vignette|upright=2|Fetch sur un processeur superscalaire avec prédiction de branchements.]] Un autre défaut est que sur le CPU charge un bloc de 128 bits à la fois, les instructions doivent être alignées sur 128 bits. Et on peut généraliser à n'importe quelle taille de bloc. Si les instructions ne sont pas alignées, il faut gérer la situation. Il arrive qu'une instruction soit à cheval sur deux blocs, avec un morceau dans le premier bloc, un autre dans le second. Dans ce cas, l'instruction est chargée en deux fois, et des circuits doivent gérer la situation pour recoller les morceaux. Sur les CPU avec des instructions de taille fixe, le problème ne survient que rarement. Mais il est fréquent avec des instructions de taille variable. Un autre défaut est lié aux branchements. Imaginez qu'un branchement s'exécute et branche vers une instruction de destination. Rien n'indique que l'instruction de destination sera au tout début d'un bloc. Si ça se trouve, elle sera en troisième position d'un bloc de 4 instructions. Une solution simple charge le bloc en respectant l'alignement, et les deux premières instructions du bloc ne sont pas à exécuter. Il y a donc une petite perte de performance. Certains CPU, comme l'IBM RS/6000, ont la machinerie charger un bloc de N instructions même s'il n'est pas aligné. La machinerie en question se trouve dans le cache d'instruction et est tout sauf simple à expliquer,ce qui fait que je me permets de la passer sous silence. Un détail, cependant : elle ne permet pas de charger des blocs qui sont à cheval sur deux lignes de cache. ===Le décodage parallèle des instructions=== Pour le décodage des instructions, plusieurs instructions sont décodées en même temps. On parle alors de '''décodage parallèle'''. Pour ce faire, un processeur superscalaire contient plusieurs décodeurs, chacun pouvant décoder une instruction en parallèle des autres. Prenons par exemple un processeur superscalaire à 4 voies : le décodage se fera dans quatre décodeurs séparés, qui fonctionneront en parallèle. Cependant, cette implémentation ne marche bien que pour les processeurs RISC, les processeurs CISC devant faire avec un microcode gourmand en circuits. Les processeurs CISC utilisent des décodeurs hybrides, avec un microcode qui complémente un décodeur câblé. Dupliquer le microcode aurait un cout en transistors trop important, ce qui fait que seuls les décodeurs câblés sont dupliqués. Les CPU CISC superscalaires disposent donc de plusieurs décodeurs simples, capables de décoder les instructions les plus courantes, avec un seul microcode. La conséquence est qu'il n'est pas possible de décoder deux instructions microcodées en même temps. Par contre, il reste possible de décoder plusieurs instructions non-microcodées ou une instruction microcodée couplée à une instruction non-microcodée. Vu qu'il est rare que deux instructions microcodées se suivent dans un programme, le cout en performance est extrêmement mineur. Il faut noter qu'une instruction correspond à une ou plusieurs µops. Il est donc possible de charger 5 instructions et de se retrouver avec 8 µops en sortie des décodeurs. Et c'est la source d'une distinction très importante. Plus haut, j'ai parlé de processeurs superscalaires à N voies. La terminologie peut avoir deux interprétations. La première est que le processeur charge/décode N instructions à la fois. Une seconde est qu'il est capable d'exécuter/émettre N µops en même temps. Les deux ne sont pas équivalentes, vu qu'un processeur superscalaire peut, par exemple, charger/décoder N instructions et émettre/exécuter N+2 µops. La distinction est particulièrement importante sur les processeurs superscalaires larges. Certains d'entre eux sont capables de charger 5 instructions et d'en émettre le double ! Un exemple classique était les processeurs Athlon d'AMD, qui pouvaient décoder deux instructions par cycle, mais émettre 4 µops par cycle. Ce n'était pas un problème, car beaucoup d'instructions, même basiques, étaient décodées en 2 µops. ===L'émission parallèle des instructions=== La superscalarité modifie en profondeur l'unité d'émission, notamment au niveau de son interface. Avant, elle recevait une micro-opération sur son entrée, et fournissait la micro-opération émise sur une sortie. Et cela vaut aussi bien pour une unité d'émission simple, un ''scoreboard'', une fenêtre d'instruction ou des stations de réservation. Mais avec l'émission multiple, les sorties et entrées sont dupliquées. Pour la double émission, il y a deux entrées vu qu'elle doit recevoir deux micro-opération décodées/renommées, et deux sorties pour émettre deux micro-opérations. Dans ce qui suit, nous ne parlerons pas d'entrée et de sortie, mais de '''ports de décodage''' et de '''ports d'émission'''. Les ports de décodage reçoivent des micro-opérations provenant de l'unité de décodage, les ports d'émissions sont pour les micro-opérations émises. Les ports d'émission injectent les micro-opérations émises dans le pipeline, elle sont propagée au banc de registre, aux ALUs, etc. Un processeur superscalaire a forcément plusieurs ports d'émission (et de décodage). Si l'interface de l'unité d'émission change, l'intérieur est tout autant modifié. Sur un CPU non-superscalaire, l'unité d'émission reçoit une µop "candidate à l'émission", et détecte les dépendances entre avec les µops en vol, en cours d'exécution dans le pipeline. Et c'est toujours le cas sur les processeurs superscalaires : l'unité d'émission détecte les dépendances entre les µops candidates et les µops en vol. Mais avec la siperscalarité, elle doit en plus tester les dépendances entre les µops candidates. Si l'unité d'émission est un ''scoreboard'', il doit détecter les dépendances entre instructions émises simultanément et les sérialiser si besoin. Avec une fenêtre d'instruction, il n'y a pour ainsi dire rien à faire, vu que les dépendances sont éliminées par le renommage de registre et que les signaux de réveil gèrent les dépendances RAW. C'est la raison pour laquelle les processeurs superscalaires modernes n'utilisent pas de ''scoreboard''. ==Les processeurs implémentés avec plusieurs pipelines== Nous venons de voir comment la superscalarité impacte le ''front-end'', il est maintenant de voir ce qu'il en est de l'unité d'émission et des ''back-ends''. Un processeur superscalaire peut utiliser l'exécution dans le désordre et le renommage de registres, mais ce n'est pas une obligation. Historiquement, les premiers processeurs superscalaires n'utilisaient pas d'exécution dans le désordre. Le meilleur exemple est celui du Pentium 1 et 2 d'Intel. Ils étaient tous deux des processeurs superscalaires, mais n'avaient pas d'exécution dans le désordre, qui est apparue sur le Pentium 3. les explications sont plus simples si on met de côté l'exécution dans le désordre. Vous vous dites que l'implémentation d'un processeur superscalaire n'est pas la même suivant que le CPU utilise l'exécution dans le désordre, ou non. Mais dans les faits, la distinction est plus subtile que ca. Pour l'expliquer, je vais utiliser une classification originale, qui sépare les processeurs superscalaires en trois sous-types. La distinction se fait sur la manière dont la superscalarité est implémentée en matériel.La classification que j'utilise distingue : * Les processeurs avec plusieurs pipelines, sans exécution dans le désordre. * Les processeurs superscalaires avec un pipeline dynamique. * Les processeurs avec des fenêtres d'instruction décentralisées. La distinction n'est pas qu'une question d'implémentation matérielle. Elle impacte aussi le ratio entre instructions chargées/décodées et instructions émises. Vous vous dites que les deux sont égales, mais ce n'est le cas que sur les processeurs avec N pipelines ! Sur les autres processeurs, il est possible de décoder 4 instructions, mais d'en émettre maximum 6 par cycle ! Et sur les CPU avec des fenêtres d'instruction décentralisées, il y a aussi une différence entre le nombre d'instructions qui sont ''dispatch'' et ''schedule''. La catégorie la plus simple est celle des '''processeurs avec plusieurs pipelines'''. Ce sont des processeurs superscalaires sans exécution dans le désordre. Ils partent d'un processeur non-superscalaire, et ajoutent un second pipeline, en sortie de l'unité d'émission. Le second pipeline permet l'émission parallèle d'une seconde instruction. Le second pipeline n'est pas identique au pipeline originel, sur plusieurs points. Premièrement, il n'a pas exactement les mêmes circuits. Le second pipeline est généralement composé d'une unité de calcul, d'une FPU, éventuellement d'un second banc de registres, guère plus. Il permet d'exécuter une seconde opération de calcul, pas plus, ce qui est à l'origine de contraintes d'appariemment assez fortes. De plus, il n'a pas forcément la même longueur, il peut avoir des étages en moins ou en plus. Il s'agit de la forme la plus simple de processeur superscalaire et nous allons leur consacrer cette section. ===La double émission entière-flottante=== L'exemple le plus simple utilise un second pipeline pour les opérations flottantes, séparé du pipeline originel. L'ajout d'un pipeline flottant permet d'émettre une micro-opération entière en même temps qu'une micro-opération flottante, ce qui s'appelle la '''double émission entière-flottante'''. La micro-opération entière s'exécute dans l'ALU entière, la micro-opération flottante dans la FPU. Les exemples les plus notables sont le PA-RISC 7100, le POWER 1 (RIOS .& et .9) et le PowerPC 601. L'Apollo DN10000 et l'Intel i860 utilisaient aussi cette forme de double émission, mais étaient des prototypes de CPU VLIW. [[File:Double émission entière-flottante.png|centre|vignette|upright=2|Double émission entière-flottante]] Pour bien expliquer les choses, prenons le processeur illustré ci-dessous, avec deux ''back-end'' séparés : un pour la FPU, et un autre pour le reste. Les instructions entières et mémoire passent toutes par le ''back-end'' généraliste. [[File:CPU non-superscalaire basique, avec une FPU.png|centre|vignette|upright=1.5|CPU non-superscalaire basique, avec une FPU]] Les deux ''back-end'' sont des pipelines séparés, mais il faut que l'unité d'émission soit spécialement conçue pour en profiter. Un processeur simple émission peut démarrer une seule µop par cycle : soit la µop est envoyée au ''back-end'' flottant, soit elle est envoyée au ''back-end'' général. Avec la double émission entière-flottante, le processeur peut envoyer une µop dans chaque pipeline, à chaque cycle. Vu que la FPU et son banc de registre forment déjà un pipeline séparé, les seules modifications se font au niveau du ''front-end'', unité d'émission inclue. {| |- |[[File:CPU simple émission avec une FPU.png|centre|vignette|upright=1.5|CPU simple émission avec une FPU.]] |[[File:Double émission entière flottante avec un second pipeline.png|centre|vignette|upright=1.5|CPU avec un second pipeline flottant.]] |} Notez que le terme double émission ''entière''-flottante est un peu trompeur. En réalité, la technique permet d'émettre une instruction flottante avec n'importe quelle autre instruction non-flottante, avec des restrictions pour les branchements. Il est par exemple possible d'exécuter un calcul flottant en parallèle d'un accès mémoire. Sur les CPU 32 bits, il est possible de faire les multiplications dans la FPU. Pour rappel, la FPU contient un multiplieur entier 53-54 bits, pour multiplier les mantisses de deux flottants 64 bits, qu'on peut utiliser pour faire les multiplications entières 32 bits. Cela permet d'émettre une multiplication en même temps qu'une opération entière dans l'ALU. Ainsi, le CPU pouvait émettre deux instructions : une opération entière de base, avec soit une multiplication, soit une opération flottante. En clair, une émission de type INT + (MUL /FLOAT). Par contre, on perd la possibilité d'émettre une multiplication entière avec une opération flottante, mais c'était un compromis qui en valait la peine. Le CPU PA-RISC 7100 implémentait cette optimisation. Il s'agissait d'un CPU double émission entière-flottante, qui utilisait donc un partitionnement assez simple. Le combo partitionnement + reconditionnement permettait ici de gagner en performance, pour un cout en matériel très limité. D’autant plus limité que le jeu d'instruction prévoyait le reconditionnement de la FPU. Les multiplications ne lisaient pas leurs opérandes dans les registres entiers, mais dans les registres flottants. ===La double émission entière=== Une autre possibilité ajoute un second pipeline, capable d'exécuter des opérations entières simples, appelé le pipeline entier. Le processeur supporte alors la '''double émission entière''', à savoir qu'ils peuvent émettre une opération entière en plus d'une autre instruction. L'opération entière en question est une opération simple, comme une addition, une soustraction, une opération bit à bit, une comparaison, etc. Les exemples classiques sont les processeurs Intel i960 et Pentium. L'implémentation était différente entre l'i960 et le Pentium : Le Pentium avait une seconde ALU entière, alors que le i960 rusait. Le Pentium dupliquait l'ALU entière, pour créer un second pipeline entier. Sur le Pentium originel, les contraintes d'appariement étaient assez strictes. Le Pentium avait une FPU, mais ne pouvait pas l'utiliser en double émission. Il lui était impossible d'exécuter une opération flottante en même temps qu'une opération entière (branchements inclus). Le Cyrix 6x86 était une sorte de Pentium produit par l'entreprise Cyrix, avec une microarchitecture différente. Il avait des contraintes d’appariement réduites. Par exemple, il pouvait émettre une opération flottante en parallèle d'une opération entière. Nous détaillerons ces processeurs dans le prochain chapitre, qui porte justement sur les CPU superscalaires x86. [[File:CPU simple et double émission avec un second pipeline entier.png|centre|vignette|upright=2.5|CPU simple et double émission avec un second pipeline entier.]] L'Intel i960 n'avait pas de FPU, juste une ALU entière et une unité mémoire. Il utilisait l'unité mémoire comme une seconde ALU entière limitée. Pour rappel, une unité mémoire contient une AGU, à savoir une ALU spécialisée dans les calculs d'adresse. Elle est capable de faire des additions, soustractions, et quelques décalages limités (des décalages par 2, 4, 8, pas plus). L'Intel i960 ajoutait des connexions entre AGU et banc de registre, afin que celle-ci serve de seconde unité de calcul. L'AGU est déjà reliée aux deux ports de lecture, pour lire les opérandes du calcul d'adresse, il suffit de la connecter sur le port d'écriture. Le processeur peut alors émettre deux instructions/µops entières, sous certaines conditions. [[File:Emission multiple des opérations entières, double émission.png|centre|vignette|upright=2|Émission multiple des opérations entières, double émission.]] ==Les processeurs superscalaires dynamiques== Les processeurs précédents, avec 2 pipelines, avaient une unité d'émission assez simple. Elle avait deux ports de décodage, et deux ports d'émission. Le nombre de ports de décodage et d'émission étaient identiques. Vous vous dites que c'est naturel, et intuitif, que c'est normal pour un processeur superscalaire. Mais en réalité, la majorité des processeurs superscalaires a plus de ports d'émission que de décodage. Expliquer pourquoi va cependant nous demander de parler de comment la superscalarité exploite les unités de calcul. Posons-nous cette question : de combien d'unités un processeur superscalaire a besoin ? Les unités en question ne sont nécessairement des unité de calcul, l'unité mémoire et l'unité de branchement comptent aussi. La réponse dépend du nombre de voies. Si le ''front-end'' charge et décode un paquet de N instructions, exécuter ces N instructions simultanément demandera là aussi au moins N unités de calcul. Notez le terme : ''au moins'', car c'est là tout l'enjeu de cette section. La réponse dépend aussi des contraintes d’appariement. Pour un CPU superscalaire "parfait", sans contraintes d’appariement, toutes les unités doivent être dupliquées, y compris l'unité mémoire. Un processeur à double émission parfaite devrait dupliquer l'ALU entière, le ''barrel shifter'', le circuit multiplieur, la FPU, l'unité mémoire, et l'unité de branchement, ce qui ferait passer de 6 unités à 12. Le cout en circuit est énorme, même pour de la double émission ! Et pour un CPU à N pipelines, le cout en circuit encore plus prohibitif ! En pratique, les processeurs superscalaires qui font ça sont très rares. La majeure partie des processeurs superscalaires utilisent d'autres techniques : le partitionnement, la duplication partielle des ALUs, la duplication des autres unités. ===Le partitionnement des unités fonctionnelles=== Les CPU superscalaires les plus simples ne dupliquent pas leurs unités de calcul, ils se contentent d'exploiter les unités existantes. Et pour expliquer comment, nous allons partir de l'exemple d'un processeur non-superscalaire contient une ALU entière, une FPU, une unité mémoire et une unité de branchement. Les quatre unités peuvent fonctionner en parallèle, à savoir qu'une unité peut exécuter une instruction à elle toute seule, sans interagir avec les autres. Cela suffit pour créer un processeur quadruple émission, qui peut émettre en même temps : une opération entière, une opération flottante, un branchement, un accès mémoire. Le principe marche aussi sur un processeur avec moins de 4 unités, par exemple sans FPU et/ou sans unité de branchement, ou avec au contraire des unités en plus. L'idée générale est d’émettre une µops séparée dans chaque unité fonctionnelle. Utiliser ainsi les unités existantes, pour rendre un CPU superscalaire, s'appelle le '''partitionnement superscalaire'''. Le terme n'est pas beaucoup utilisé en-dehors de quelques vieux articles académiques. Le partitionnement est fortement dépendant du jeu d'instruction, car il demande de bien séparer instructions entières, flottantes, branchements et instructions mémoire. Et autant c'est bien le cas sur les CPU RISC, autant les CPU CISC ne sont pas dans ce cas. Les CPU CISC ont des instructions ''load-op'', qui effectuent une instruction arithmétique et une µop mémoire. Et ces instructions sont très compliquées à implémenter avec le partitionnement. Et au-delà de ça, les CPU CISC ont beaucoup d'instructions qui sont décodées en plusieurs µops, à exécuter dans un ordre bien précis. On peut pas émettre les µops en question simultanément. Le partitionnement a un cout en circuit très limité, pour un gain en performance lui aussi limité. Pour reprendre le CPU de l'exemple précédent, il est rare que l'on ait à exécuter ces quatre instructions en même temps. En pratique, les combinaisons fréquentes sont les combinaisons calcul + accès mémoire, calcul + branchement, calcul entier + calcul flottant. Utiliser une quadruple émission de ce type n'est donc pas une bonne idée, alors que la double émission parait plus adaptée. Et c'est là que la différence entre ports d'émission et de décodage devient intéressante. L'idée est d'avoir une unité d'émission avec deux ports de décodage, et 4 port d'émission (un par unité). Les contraintes d’appariement varient alors suivant le processeur, mais la règle générale est que si deux micro-opérations tombent dans deux unités différentes, on peut les émettre en même temps. En clair, on peut émettre une opération entière avec une opération flottante, une opération entière avec un branchement, un accès mémoire avec un branchement, etc. Et cela rend le partitionnement bien plus intéressant que prévu. Les tout premiers CPU superscalaires étaient des CPU double émission, qui utilisaient le partitionnement avec : une ALU entière, une unité mémoire et une FPU. Nous parlerons de '''partitionnement INT-FLOAT-MEM'''. L'avantage sur la double émission flottante est que les combinaisons INT + MEM et FLOAT + MEM étaient autorisées. L'intérêt était d'émettre une opération en parallèle d'un accès mémoire, ce qui est un doublet d'instruction assez fréquent. Les exemples classiques sont l'HyperSPARC, de l'alpha 21064 et du PowerPC 603. Les contraintes d’appariement variaient grandement suivant le processeur. Le PowerPC 603 implémentait une double émission "parfaite". C'est à dire que toutes les combinaisons possibles à deux instructions étaient possibles, que ce soit INT + FLOAT, INT + MEM, FLOAT + MEM. En plus de cette double émission "parfaite", le CPU utilisait la technique dite de ''branch folding'', qui permettait d'exécuter un branchement sans utiliser d'unité de calcul, ce qui fait qu'il était qualifié de ''CPU 2-way + Branch''. A l'opposé, le processeur alpha 21064 avait pas mal de contraintes d’appariement, à savoir que certaines combinaisons de deux instructions étaient interdites, bien que compatibles avec les unités disponibles. Une des raisons est que le banc de registre n'avait pas assez de ports de lectures, ce qui fait que certaines combinaisons avec une écriture mémoire n'étaient pas permises. Les concepteurs avaient volontairement inséré ces contraintes afin de simplifier le design du processeur. Avant de poursuivre, précisons que la FPU regroupe un additionneur flottant et un multiplieur flottant. Et ce fait peut être exploité pour approfondir le partitionnement. La '''double émission FADD/FMUL''' permet d'émettre une addition flottante en même temps qu'une multiplication flottante. Elle a été utilisée sur les processeurs x86, Alpha et SPARC. Les autres circuits de calcul flottant sont souvent regroupés avec l'additionneur. La division est parfois e même port que le multiplieur flottant, parfois sur celui de l'additionneur (quand elle est microcodée et exécutée en enchainant les soustractions). ===La duplication des ALU entières=== Le partitionnement est une technique élégante, simple, facile à implémenter et au cout en circuits modeste. Mais il faut avouer que cela ne donne pas des CPU superscalaires très efficaces. La raison est que le partitionnement permet d'émettre des paires, triplets, quadruplets de micro-opérations qui sont rares en pratique. Les combinaisons les plus fréquentes en pratique sont des paires/triplets d'opérations entières, souvent des paires/triplets d'additions ou d'opérations entières simples. Il est donc primordial d'émettre plusieurs opérations entières simultanément. Le fait d'émettre et d'exécuter plusieurs opérations entières en même temps s'appelle l''''émission entière multiple'''. Pour exécuter la seconde/troisième opération entière, il faut dupliquer les ALU entières (ou réutiliser l'AGU comme ALU entière comme le fait l'Intel i960). Le processeur doit avoir deux ALU entières pour de la double émission entière, trois pour de triple émission entière, etc. De plus, il faut ajouter des ports au banc de registre, pour lire deux fois plus d'opérandes et décrire deux résultats. Et enfin, il faut rajouter un port d'émission pour pouvoir émettre une micro-opération entière en plus. [[File:Processeur non-superscalaire basique 03.png|centre|vignette|upright=2|Processeur non-superscalaire basique]] La tendance est de multiplier les ALU entières, mais pas le circuit multiplieur. Le cout en transistors est alors grandement réduit, au prix de l'apparition de dépendances structurelles. Le CPU peut émettre deux additions en même temps, ou une addition et une multiplication, mais pas deux multiplications. Il en est de même avec le ''barrel shifter'', qui n'est pas dupliqué. Il est donc possible d'émettre un décalage avec une opération de base, mais pas deux décalages en parallèle. Mais ces contraintes d’appariement ne sont pas un problème, car il est rare de devoir émettre deux multiplications/décalages en même temps. La plupart des programmes sont majoritairement remplis d'additions, avec des multiplications assez rares et des décalages qui le sont encore plus. La moyenne est proche d'une multiplication pour 4/5 additions. Vu que des multiplications consécutives sont très rares, dupliquer le multiplieur est presque inutile. Disposer de plusieurs circuits multiplieurs ou de plusieurs ''barrel shifters'' serait donc un cout en circuits qui ne servirait que rarement et n'en vaut pas la chandelle. Il faut noter que sur les CPU 32 bits, les multiplications peuvent être exécutées dans la FPU ou dans un multiplieur séparé, tout dépend de si le CPU exécute les multiplications dans la FPU ou pas. {|class="wikitable" |- ! Voie d'émission n°1 !! Voie d'émission n°2 !! Voie d'émission n°3 |- | ALU entière || ALU entière || FPU |} Reste à connecter les unités de calcul à l'unité d'émission (indirectement, car il y a des étages de pipeline entre les deux). Une implémentation basique utilisee un port d'émission par unité. Prenons l'exemple d'un CPU superscalaire avec 4 ALU entières, une FPU, et une unité mémoire. Vu qu'il y a 6 unités, cela fait donc 6 ports d'émission. L'exemple historique le plus impressionnant est celui du Motorola 88110, un CPU double émission qui possède 10 unités. Il contient deux ALU entières, un circuit multiplieur, un circuit diviseur, une unité de manipulation de bits, une unité mémoire, un additionneur flottant, et deux unités graphiques pour des calculs impliquant des pixels. Le multiplieur est utilisé à la fois pour les multiplications entières, flottantes et le produit de deux pixels. Idem pour le diviseur, qui sert pour les divisions entières et flottantes. Et chaque unité a son propre port d'émission. La conséquence est que les contraintes d'appariement du Motorola 88110 sont simples : tant que deux µops atterrissent dans des unités séparées, la paire est permise. Il est par exemple possible d'émettre simultanément deux opérations entières simples, vu qu'il y a deux ALU. Par contre, impossible d'émettre simultanément deux multiplications, deux divisions, deux opérations flottantes, deux manipulations de bit, deux accès mémoire, etc. De plus, le partage du multiplieur fait qu'on ne peut pas émettre une multiplication entière avec une multiplication flottante. Idem avec les divisions, vu que le diviseur est aussi partagé. Ce sont les seules contraintes. ===Le partage des ports d'émission=== La présence d'un circuit multiplieur et d'un ''barrel shifter'' pose des problèmes pour le partitionnement. Il est possible d'avoir un port d'émission dédié au multiplieur, et un autre au ''barrel shifter'', qui sont séparés de ceux pour l'ALU entière. Il est alors possible d'émettre une opération entière, une multiplication et un décalage, simultanément. Nous parlerons de '''partitionnement entier''' pour désigner cette possibilité. [[File:Partitionnement superscalaire entier.png|centre|vignette|upright=2|Partitionnement superscalaire entier.]] Le partitionnement entier est cependant très rare, car le gain en performance serait trop faible au regard des couts en circuit. Quelques processeurs historiques l'utilisaient, mais ce n'est plus le cas de nos jours. La raison est que les multiplications et les décalages sont assez rares dans le code, ce qui fait que le port d'émission associés sont sous-utilisés. Et un port d'émission a un cout en circuit assez important. Et c'est sans compter qu'ajouter des ports sur le banc de registres juste pour un multiplier sous-utilisé n'en vaut pas la peine. L'idéal serait de trouver un moyen de réduire le nombre de ports d'émissions, sans réduire le nombre d'unités de calcul. [[File:Emission multiple des opérations entières, implémentation naive.png|vignette|Émission multiple des opérations entières, implémentation naive]] La solution consiste à '''partager les ports d'émission''', à savoir connecter un port d'émission à plusieurs unités de calcul. L'exemple classique est justement lié aux circuits multiplieur et ''barrel shifters''. L'idée est que le multiplieur et le ''barrel shifter'' sont mis sur le même port d'émission qu'une ALU entière. Prenons par exemple le processeur à double émission entière illustré ci-contre. Un port d'émission est relié à une ALU entière seule, et l'autre port d'émission alimente la seconde ALU entière et le multiplieur. La conséquence est que le processeur peut émettre deux opérations entières, dont une multiplication. Si on avait utilisé un port d'émission dédié au multiplieur, il aurait été possible d'émettre deux opérations entières et une multiplication. On perd donc une voie, mais on gagne en flexibilité et on économise des circuits. Notons que ce partage signifie aussi que le multiplieur partage les ports sur le banc de registre, ce qui fait une double économie. [[File:Emission multiple des opérations entières, implémentation courante.png|centre|vignette|upright=1.5|Émission multiple des opérations entières, implémentation courante]] En général, le ''barrel shifter'' et le multiplieur sont placés sur deux voies d'émission séparés, ce qui permet d'émettre un décalage et une multiplication en même temps. Les mettre sur le même port ne le permettrait pas. {|class="wikitable" |- ! Voie d'émission n°1 !! Voie d'émission n°2 !! Voie d'émission n°3 |- | * ALU entière * Multiplieur | * ALU entière * Barrel shifter | FPU |- | * Additions, soustractions, opérations bit à bit, etc. * Multiplication | * Additions, soustractions, opérations bit à bit, etc. * Décalages et rotations | Opération flottante |} Mais le partage des ports d'émission peut appliquer à n'importe quelle unité. Prenons l'exemple d'un CPU superscalaire avec 4 ALU entières, une FPU, et une unité mémoire. Vu qu'il y a 6 unités, cela fait donc 6 ports d'émission. Cependant, il est rare qu'un bloc de 6 instructions contienne exactement 4 opérations entières, une opération flottante, et un accès mémoire. Beaucoup de ports d'émission seraient sous-utilisés. La solution est de partager des ports d'émission, pour passer à la quadruple émission. Le CPU peut alors répartir 4 instructions sur les ports d'émission adéquats, à chaque cycle. Le CPU a donc 4 ports de décodage et 6 ports d'émission. Une implémentation classique permet n'importe quelle combinaison de 4 instructions compatible avec les 6 unités. Par exemple, il devient possible d'émettre simultanément 4 additions, la combinaison 2 * INT + FLOAT + MEM, 3 * INT + MEM, et bien d'autres. L'exemple relie une ALU entière, la FPU et l'unité mémoire sur le même port. Le CPU a alors quatre ports d'émission, qui ne sont pas "identiques". Trois d'entre eux sont reliés à une ALU entière chacun, rien d'autre. Le quatrième est lui relié à une ALU entière, la FPU et l'unité mémoire. On économise alors deux ports d'émission, mais cela entraine l'apparition de contraintes d’appariement. Le CPU peut émettre 3 opérations entières + n'importe quelle autre opération. Mais les autres combinaisons sont interdites. Le CPU peut émettre 6 opérations entières simultanément, grâce à 6 ALU entières. Les accès mémoire ou opérations flottantes ne viennent pas en plus, mais en remplacement des opérations entières. Regrouper plusieurs ALU sur un même port d'émission est donc à l'origine de dépendances structurelles, de nouvelles contraintes d’appariement. Dans certaines situations de '''''port contention''''', on a assez d'ALU pour exécuter N micro-opérations, mais la répartition des ALUs sur les ports l'empêche. Impossible d'émettre deux micro-opérations sur deux ALU si elles sont sur le même port d'émission. On peut réduire les cas de ''port contention'' en répartissant correctement les ALU sur les ports d'émission, mais sans jamais les supprimer complétement. Un exemple est celui du PowerPC 604. Il implémentait la quadruple émission, alors qu'il avait 5 unités de calcul. Mais deux unités partageaient un même port d'émission. Dans le détail, il avait deux ALU entières, un circuit multiplieur/diviseur, une FPU, une unité mémoire. La FPU et le multiplieur étaient sur le même port d'émission. En conséquence, le CPU pouvait émettre seulement les combinaisons INT + INT + MEM + (MUL/FLOAT). : Il est intéressant de voir l'exemple d'un processeur superscalaire parfait, sans contraintes d'appariement, à N voies. Pour cela, on part d'un processeur non-superscalaire, avec M unités de calcul. Un tel processeur peut s'implémenter avec N ports d'émission, chacun relié à M unités de calcul possibles, ce qui fait que les M unités de calcul sont dupliquées en N exemplaires. Si on prend par exemple un CPU non-superscalaire avec une ALU, une FPU, une unité mémoire et une unité de branchement, un port d'émission sera relié à une ALU, une FPU, une unité mémoire et une unité de branchement. ===La duplication des autres unités fonctionnelles=== Dupliquer les ALU entière est une première étape, mais il est aussi possible de dupliquer les autres unités fonctionnelles. Par exemple, il est possible de dupliquer l'unité mémoire ou l'unité de branchement. Reste à voir quelles unités dupliquer, et pourquoi. Précisons que dans ce qui va suivre, nous allons parler de la duplication des unités en général, pas seulement des unités mémoire/branchement. Nous allons continuer de parler de la duplication des ALU entières dans ce qui suit, mais de manière plus générale. Pour un processeur à N voies, il est théoriquement possible de dupliquer toutes les unités en N exemplaires. Mais ce n'est jamais fait en pratique, car le cout en circuits serait trop important et le gain en performance trop faible. Mieux vaut dupliquer certaines unités bien précises, quitte à avoir des contraintes d'appariement. Et à ce petit jeu, toutes les duplications ne se valent pas, certaines sont plus rentables en matière de performance, le cout en circuit n'est pas le même selon l'unité, etc. La rentabilité en performance dépend surtout des contraintes d'appariement. Les contraintes d'appariement entrainent une baisse de performance significatives si elles interdisent des paires d'instructions fréquentes. Aussi, les opérations très fréquentes, souvent appairées, gagnent à dupliquer l'unité associée. Et au contraire, les unités rarement utilisées ne doivent pas être dupliquées. Il se trouve que les unités les plus utilisées sont les ALU entières, suivies par les unités mémoire et l'unité de branchement. Le cout en circuit n'est aussi pas le même. Dupliquer les ALU entière est trivial, car elles ont un budget en transistors assez faible. Les circuits multiplieurs, diviseurs, et les ''barrel shifter'' ont eux un cout en transistor plus élevés. Si on devait faire un classement, on aurait : ALU entière < ''barrel shifter'' < multiplieur. Pour les unités mémoire, le cout en transistor est là aussi conséquent. Le résultat final est que dupliquer les ALU entières est privilégié sur le reste, comme l'a vu dans la section précédente. Les unités mémoire arrivent en seconde place, suivie par les multiplieurs/''barrel shifter'' à la troisième place, et les unités de branchement à la quatrième place. Les CPU superscalaires larges ont donc tendance à multiplier les unités mémoire en second. Les multiplieurs et ''barrel shifters'' sont eux aussi dupliqués sur les CPU superscalaires larges, mais cela vient souvent après avoir dupliqué les unités mémoire. La raison est que les accès mémoire sont critiques pour la performance des CPU à exécution dans le désordre, pas les multiplications. Pour les unités de branchement, une subtilité complique le fait d'en installer plusieurs. Que faire quand deux branchements sont pris ? La solution la plus simple est de ne pas se trouver dans cette situation : on n'utilise qu'une seule unité de branchement et on n’exécute qu'un seul branchement par cycle. La majorité des processeurs superscalaires étroits font ainsi. Les processeurs superscalaires larges ont cependant deux unités de branchements, mais ils n'acceptent qu'un seul branchement pris à la fois. Si les deux branchements pris sont exécutés en même temps, le processeur n’exécute que le plus récent, dans l'ordre du programme. ===Les CPU superscalaires mélangent les trois techniques=== Les CPU superscalaires utilisent toutes les techniques à la fois : partitionnement, duplication de l'ALU entière, duplication des autre unités fonctionnelles. Chaque processeur utilise ces trois stratégies à sa sauce, en fonction de son budget en transistor. Les quatre techniques ont cependant un cout en circuit différent, et donnent des gains de performances eux aussi forts différents. * Le '''partitionnement superscalaire''' a un cout en circuit modéré, car on n'a pas besoin de dupliquer d'ALU. * La '''duplication des ALU entières''' est la technique la plus importante : elle a un cout en circuit modéré, et donne des gains en performance impressionnants. * La '''duplication des autres unités''' a un cout en circuit variable, pour des gains en performance eux aussi variables. * Le '''partage des ports d'émission''' réduit le cout en circuit du CPU, pour des pertes de performances mineures si bien fait. L'usage du partitionnement seul, sans duplication, était surtout le fait des processeurs commercialisés entre les années 1989 et 1993. Le partage des ports d'émission a lui aussi mis un peu de temps pour s'imposer. Globalement, les processeurs superscalaires ont évolués progressivement, pour se rapprocher d'une sorte de standard. Mais pour comprendre ce qu'est ce standard, et en quoi les CPU historiques s'en sont progressivement rapprochés, il faut étudier l'histoire des CPU superscalaires. L'histoire des CPU superscalaires a commencé dès 1960, mais est resté un projet de recherche pendant plusieurs décennies. De nombreux designs ont été conçus, mais abandonnés avant la production, comme l'ACS-1 d'IBM. Les années 1980 ont vu l'arrivée de prototypes de processeurs VLIW, mais qui n'étaient pas superscalaires. Par exemple, l'Apollo DN10000 et l'Intel i860 pouvaient émettre deux instructions à la fois, sous conditions, mais demandaient que le compilateur fasse une bonne partie du travail. Les premiers CPU superscalaires commercialisés sont sortis en 1989. Deux CPUs superscalaires sont sortis cette année là : le RS/6000 d'IBM et l'i960CA d'Intel. Puis, la compétition s'est aussi lancée dans la bataille. Les CPU commercialisés avant l'année 1993 étaient à double émission, à l'exception du SuperSPARC qui utilisait la triple émission. Les modèles après 1993 hésitaient entre double et quadruple émission. Dans le tableau ci-dessous, les processeurs quadruple émission sont en bleu, les triple émission sont en rouge, les autres ne sont pas en couleur. {|class="wikitable" |- ! !! IBM !! HP PA-RISC !! SPARC !! Intel!! Alpha !! Autres |- ! 1989 | RS/6000 || || || i960CA || || |- ! 1990 | || || || || || |- ! 1991 | || || || || || Motorola 88110 |- ! 1992 | RSC || PA 7100 || class="f_rouge | SuperSPARC || || 21064 || |- ! 1993 | PPC 601 || || HyperSPARC || Pentium || || |- ! 1994 | class="f_bleu | POWER 2 || PA 7200 || || || class="f_bleu | 21164 || |- ! 1995 | 603/604 || class="f_bleu | PA 8000 || class="f_bleu | UltraSPARC 1 || Microarchitecture P6 (+ concurrence AMD) || || class="f_bleu | MIPS R1000 |- ! 1996 | class="f_bleu | 620 || || || || class="f_bleu | 21264 || |- ! ... || ... || ... || ... || ... || ... || ... |} Les premiers CPU à double émission sont assez représentatifs des CPU à double émission modernes. Il faut dire que se limiter à deux voies ne permet pas de faire beaucoup de choses. Voici le tableau chronologique disant quel CPU utilisait quelle technique. * Les CPU en vert utilisaient la double émission entière-flottante. * Les CPU en rouge utilisaient la double émission entière "pure". * Les CPU en jaune utilisaient un ''partitionnement complet'' entre ALU, FPU et unité mémoire. * Les CPU sans couleur utilisaient un grand nombre d'unités, avec beaucoup de ports d'émission. * Les CPU barrés utilisent la triple ou quadruple émission et seront vu dans la section suivante. {|class="wikitable" |- ! !! IBM !! HP PA-RISC !! SPARC !! Intel!! Alpha !! Autres |- ! 1989 | class="f_vert | RS/6000 || || || class="f_rouge | i960CA || || |- ! 1990 | || || || || || |- ! 1991 | || || || || || Motorola 88110 |- ! 1992 | class="f_vert | RSC || class="f_vert | PA 7100 || <s>SuperSPARC</s> || || class="f_jaune | 21064 || |- ! 1993 | class="f_vert | PPC 601 || || class="f_jaune | HyperSPARC || class="f_rouge | Pentium || || |- ! 1994 | <s>POWER 2</s> || PA 7200 || || || <s>21164</s> || |} C'est en 1994, que la quadruple émission est arrivée et que les CPU ont essayé divers implémentations. Il y avait alors trois stratégies pour la double/triple/quadruple émission, qui partageaient des problèmes similaires. La première implémentation dupliquait les ALUs et les FPUs, ce qui permettait d'émettre deux opérations flottantes avec deux opérations entières. Le POWER 2 dupliquait ses FPUs à la perfection, alors que le 21264 séparait l'additionneur et le multiplieur flottant. Les accès mémoire étaient traités comme des opérations entières et le processeur pouvait souvent en émettre deux à la fois. Mais une telle organisation n'est plus utilisée de nos jours, car, sans instructions flottantes à exécuter, elle est équivalente à de la double émission. La seconde implémentation utilisait deux ALU entières (dont un multiplieur), une unité mémoire et une FPU.Cette organisation est intéressante, mais encore trop stricte pour donner de bons résultats, les quadruplets avec exactement deux opérations entières, un accès mémoire et une opération flottante étant trop rares. L'usage de ces 4 unités en double ou triple émission a été utilisé sur le PA 7200 et le SuperSPARC. Malheureusement, en plus de problèmes au niveau de son cache, le SuperSPARC avait de nombreuses contraintes d’appariement, car le banc de registre n'avait pas assez de ports pour alimenter certaines combinaisons d'instructions. De plus, s'il émettait une multiplication, il ne pouvait pas émettre de seconde µop. Enfin, une troisième implémentation utilisait un grand nombre d'unités fort différentes, bien plus que quatre, avec au minimum deux ALU entières. Prenons l'exemple de l'UltraSPARC, qui avait : deux ALU entières, une unité mémoire, une unité de branchement, un additionneur flottant, un multiplieur flottant, un diviseur flottant (aussi utilisé pour le calcul des racines carrées), et 2 unités graphiques pour les calculs sur des pixels. En tout, cela fait 9 unités différentes, pour de la quadruple émission. Dans le même genre, les processeurs MIPS R8000 et R10000 avaient une implémentation plus moderne, dans le sens où toutes les unités étaient dupliquées. Le CPU avait deux ALU, deux FPU et deux unités mémoire. Il pouvait donc émettre quatre instructions à la fois, tant que le paquet collait avec ces six unités. Moitié opérations entières, moitié opérations mémoire, avec de quoi remplacer les opérations entières par des opérations flottantes. Le PA 8000, lui, dupliquait toutes ses unités en deux exemplaires, ce qui n'est pas l'idéal, aucun processeur moderne ne fait cela aujourd'hui. Le tout est résumé dans le tableau ci-dessous, avec : * en vert les CPU utilisant la première stratégie ; * en rouge les CPU utilisant la seconde stratégie ; * en jaune les CPU utilisant la troisième stratégie. {|class="wikitable" |+ Processeurs quadruple émission historiques |- ! Année !! Processeur !! Unités |- ! 1991 | class="f_jaune | Motorola 88110 || class="f_jaune | 2 ALU + MUL + DIV + BIT + MEM + FADD + 2 unités graphiques |- |- ! rowspan="2" | 1994 | class="f_vert | POWER 2 || class="f_vert | 2 ALU/MEM + 2 FPU (+ 2 Branchements via ''branch folding'') |- | class="f_rouge | 21164 || class="f_rouge | 2 ALU + FPU + MEM |- ! rowspan="3" | 1995 | PA 8000 || 2 ALU + 2 ''barrel shifters'' + 2 MEM + 2 FPU (FMA) + 2 FPU (DIV et SQRT) + 2 SIMD |- | class="f_jaune | UltraSPARC 1 || class="f_jaune | 2 ALU + MEM + BRANCH + FADD + FMULFDIV + 2 unités graphiques |- | MIPS R8000 et R10000 || 2 ALU + 2 FPU + 2 MEM |- ! rowspan="2" | 1996 | class="f_rouge | 620 || class="f_rouge | 2 ALU + FPU + MEM |- | class="f_vert | 21264 || class="f_vert | 2 ALU/MEM + 2 FPU (FADD + FMUL) |} Malheureusement, ces CPUs historiques n'avaient ni assez d'unités de calcul entières, ni d'unités mémoire, pour remplir la quadruple émission. Environ la moitié des instructions d'un programme correspond à des instructions entières. Pour en profiter, un processeur à N voies doit disposer d'au minimum N/2 ALU entières, l'idéal étant d'aller au-delà. Les CPU superscalaires historiques avaient 2 ALU pour de la quadruple émission, ce qui était très limite. En comparaison, les CPU superscalaires modernes mettent le paquet niveau ALU entière. La plupart des CPU x86 modernes descendent rarement sous les 2/3, la plupart remplit presque la totalité de la largeur du CPU. Pour donner un exemple, la microarchitecture Golden Cove d'Intel est un processeur à 6 voies, avec 5 ALU entières. Pour être précis, un programme informatique exécute environ 40% de son code dans des instructions arithmétiques, un autre 40% dans les accès mémoire, et un le reste pour... le reste. La conséquence est que les processeurs modernes dupliquent leurs unités mémoire. Par exemple, la microarchitecture ''Golden Cove'' d'Intel dispose de trois unités mémoire : deux spécialisées dans les écritures, trois dans les lectures. La microarchitecture ''Gracemont'' utilise la pentuple émission et peut émettre 4 µops entières et 4 µops mémoire. Notez que le ratio est équilibré entre opérations entières et mémoire, avec soit égalité, soit un avantage pour les ALU entières. Une autre régularité est que la largeur totale du processeur peut être occupée par des opérations entières/mémoire. Pour le dire autrement, sur un processeur quadruple émission, il pourra émettre un mix de 4 instructions entière/mémoire, avec zéro opération flottante. Idem avec des processeurs triples ou pentuple émission, voire plus. Si le processeur émet une opération flottante, elle remplace une opération entière. Prenons l'exemple des processeurs AMD K6, sortis en 1997, juste avant les AMD Athlon. Ils avaient 10 unités de calcul, 6 ports d'émission, 4 ports de décodage. Voici exactement comment étaient répartie les unités de calcul sur les ports d'émission. C'était des processeurs quadruple émission, ce qui fait que la moitié de la largeur du processeur sert pour des opérations entières, et l'autre moitié pour les accès mémoire. {|class="wikitable" |- ! Port d'émission n°1 !! Port d'émission n°2 !! Port d'émission n°3 !! Port d'émission n°4 !! Port d'émission n°5 !! Port d'émission n°6 ! |- | ALU entière || ALU entière || FPU || Unité de branchement || Unité LOAD || Unité STORE |- | || Multiplieur || || || || Unité de calcul d'adresse (instruction LEA) |- | || ''Barrel shifter'' || || || || Unité pour les instructions PUSH |} Les microarchitectures K7, K8 et K10, utilisées sur les anciens CPU AMD Athlon, sont un exemple moins représentatif. Ces microarchitectures utilisent le partage des ports d'émission au maximum. C'était des microarchitectures à triple émission, avec six ports d’émission. Il y avait trois ports d'émission pour les opérations flottantes, et trois ports d'émission pour les micro-opérations entières/mémoire. Il y avait trois ALU entières et trois unités mémoire, chaque ALU partageait un port d'émission avec une unité mémoire. Elle pouvait donc émettre soit trois opérations entières, soit trois µops mémoire, soit un mix des deux. En plus des mix avec les opérations flottantes et branchements. {|class="wikitable" |- ! Port d'émission n°1 !! Port d'émission n°2 !! Port d'émission n°3 !! Port d'émission n°4 !! Port d'émission n°5 !! Port d'émission n°6 ! |- | ALU entière || ALU entière || ALU entière || FADD || FMUL || FPU (autres opérations) |- | Multiplieur || || || || || |- | LOAD/STORE || LOAD/STORE || LOAD/STORE || || || |} Les CPU superscalaires larges dupliquent les multiplieurs et/ou les ''barrel shifters'', et même les unités de branchement. La raison est que quand la largeur du CPU augmente, le processeur traite des paquets d'instructions de plus en plus gros. Par exemple, pour un CPU à 10 voies, cela veut dire émettre 10 instructions en même temps. Il est alors plus fréquent d'avoir deux multiplications, deux décalages, deux accès mémoire, deux branchements. Par exemple, la moyenne est d'une multiplication tous les 5 instructions. Les CPU superscalaires à 10 voies ont donc tendance à avoir deux multiplications par paquet d'instruction, en moyenne. Avoir deux multiplieurs est alors un gain en performance clair. Par exemple, la microarchitecture ''Golden Cove'' a deux ''barrel shifters''. C'est une architecture à 6 voies. {|class="wikitable" |- ! Port d'émission n°1 !! Port d'émission n°2 !! Port d'émission n°3 !! Port d'émission n°4 !! Port d'émission n°5 !! Port d'émission n°6 !! Port d'émission n°7 !! Port d'émission n°8 !! Port d'émission n°9 !! Port d'émission n°10 |- | ALU entière || ALU entière || ALU entière || ALU entière || ALU entière || LOAD || LOAD || LOAD || STORE || STORE |- | ''Barrel shifter'' || Multiplieur || Multiplieur || ''Barrel shifter'' || || || || || || |- | Branchements || Diviseur || || Branchements || || || || || || |} ==Les µops mémoire sur les processeurs superscalaires== Plus haut, nous avons volontairement passé sous silence les accès mémoire. La raison à cela est que leur support sur les CPU superscalaire peut se faire de plusieurs manières différentes, qu'il vaut mieux expliquer dans une section dédiée. Les différentes implémentations sont décrites dans la liste suivante, nous allons les voir une par une. * Avec la première méthode, les accès mémoire sont exécutés par le pipeline pour les opérations entières. * Avec la seconde méthode, les accès mémoire ont un pipeline dédié ; c'est l'''émission parallèle des accès mémoire''. * Avec la troisième méthode, il est possible d'émettre plusieurs accès mémoire simultanément : c'est l'''émission multiple des accès mémoire''. Voyons ces trois cas dans le détail. Nous verrons notamment les différentes formes d'émission multiple des accès mémoire. ===Les accès mémoire traités dans le pipeline entier=== Dans le cas le plus simple, les accès mémoire sont traités comme des opérations entières un peu spéciales. L'unité mémoire est sur le même port d'émission que l'ALU entière, ce qui fait que les accès mémoire remplacent des opérations entières. Faire ainsi permet d'utiliser une ALU entière pour les calculs d'adresse, pas besoin de rajouter une unité de calcul d'adresse séparée. Typiquement, un processeur de ce type place l'unité mémoire après l'unité de calcul. Les opérations entières n'utilisent que l'ALU entière, mais pas l'unité mémoire. Les accès mémoire exécutent un calcul d'adresse dans l'ALU entière, puis l'adresse calculée est envoyée à l'unité mémoire. Le tout est illustré ci-dessous, dans le cas d'un processeur historique à double émission entière-flottante. [[File:Processeur non-superscalaire basique 01.png|centre|vignette|upright=1.5|Processeur non-superscalaire basique]] Un exemple de CPU double émission de ce type est celui du Power PC 440, un CPU destiné à l'embarqué. Vu qu'il était destiné à l'embarqué, il n'avait pas de FPU, il n'avait pas besoin de manipuler de nombres flottants. Il n'a donc que des ALU entières, un multiplieur et une unité mémoire. Il dispose de deux ports d'émission. Le premier est connecté à une ALU entière et un circuit multiplieur, le second est relié à l'unité mémoire et une seconde ALU entière. Une organisation simple, mais très efficace ! [[File:PowerPC 440.png|centre|vignette|upright=2|Microarchitecture du PowerPC 440.]] L'implémentation économise une unité de calcul d'adresse, mais n'a pas de cout en performance significatif. Les accès mémoire étant très lents, émettre une µop entière en parallèle d'un accès mémoire ne sert pas à grand-chose. On peut émettre cette µop entière un cycle plus tard, elle sera terminée avant même que l'accès mémoire soit terminé, même si l'accès mémoire se fait dans le cache L1 ou L2. Pour comprendre pourquoi, faisons quelques calculs. Un accès mémoire prend deux-trois cycles quand il tombe dans le cache L1. Et on doit attendre qu'il soit terminé pour en émettre un nouveau. Combien d'instructions arithmétiques peut-on lancer pendant ce temps, sur un CPU triple émission ? Au premier cycle, on peut en émettre 2 (l'instruction mémoire prend une place, donc 3-1), puis 3 au second cycle. Ce qui fait 5 instructions arithmétiques au total. Or, il se trouve qu'il y a une instruction d'accès mémoire pour 5 instructions arithmétiques, environ. Par contre, ce compromis change quand on passe à la quadruple émission, voire au-delà. Dans ce cas, il devient intéressant de pouvoir émettre une nouvelle instruction mémoire à chaque cycle. Ce qui nous amène à la section suivante... ===L'émission parallèle des accès mémoire=== A partir de la quadruple émission, la tendance a été de séparer l'unité mémoire des ALU entières. Cela permet d'émettre des µops mémoire en même temps que des µops entières et/ou flottantes., ce qui porte le nom d''''émission parallèle des µops mémoire''', ou encore ''émission mémoire parallèle''. Par exemple, un processeur quadruple émission peut émettre en même temps deux opérations entières, une opération flottante et un accès mémoire. L'accès mémoire vient en plus des opérations entières, il n'est pas possible de remplacer l'accès mémoire par une opération entière. La technique ne permet pas de faire les calculs d'adresse dans l'ALU entière. Il faut donc utiliser une unité de calcul d'adresse séparée. Elle est intégrée dans l'unité mémoire, qui fait l'interface entre le cache et le banc de registres. Le cout en circuits est très faible, car les unités de calcul d'adresse n'utilisent guère plus qu'un additionneur et un circuit de décalage très simple. L'émission mémoire parallèle est plus facile à implémenter sur les CPU RISC, qui ont des instructions mémoire séparées du reste. Les CPU CISC ne sont pas dans ce cas, ce qui fait que séparer l'unité mémoire du pipeline entier est compliqué. Les CPU superscalaires historiques étaient pour moité des CPU RISC et pour moitié des CPU CISC, ce qui fait qu'ils n'utilisaient pas ou peu l'émission mémoire parallèle. Les premiers processeurs superscalaires se contenaient de l'émission parallèle mémoire, ils ne pouvaient émettre qu'un seul accès mémoire à la fois, l'unité mémoire n'était pas dupliquée. Et ce n'est pas tant un problème que ça, du moins sur les processeurs à double ou triple émission. En effet, il est rare que l'unité mémoire doive faire deux accès simultanés. Notons que le processeur peut exécuter une instruction mémoire en parallèle d'un calcul ou d'un branchement, seuls les accès mémoire simultanés sont impossibles. ===L'émission multiple des accès mémoire avec une LSQ=== Les processeurs superscalaires larges travaillent sur des paquets de 8 à 10 instructions, il est plus fréquent d'avoir plusieurs accès mémoire par paquet. Dans ces conditions, il est utile d'avoir une '''émission multiple des accès mémoire''', à savoir d'émettre plusieurs micro-opérations mémoire en même temps. Les processeurs superscalaires modernes sont capables d'émettre plusieurs lectures/écritures simultanément. Par exemple, ils peuvent émettre une lecture en même temps qu'une écriture, ou plusieurs lectures, ou plusieurs écritures. Il faut noter que selon le processeur, il peut y avoir des restrictions quant aux accès mémoire émis en même temps. Par exemple, certains processeurs peuvent émettre une lecture avec une écriture en même temps, mais pas deux lectures ni deux écritures. Ou encore, ils peuvent émettre deux lectures, une lecture et une écriture, mais pas deux écritures en même temps. Dans la majorité des cas, les processeurs ne permettent pas d'émettre deux écritures en même temps, alors qu'ils supportent plusieurs lectures. Il faut dire que les lectures sont plus fréquentes que les écritures. Les processeurs qui autorisent toutes les combinaisons de lecture/écriture possibles, sont rares. L'émission multiple des accès mémoire s'implémente dans l'unité mémoire. Pour rappel, l'unité mémoire s'interpose entre le cache L1 et le reste du pipeline. Dans le cas le plus simple, elle incorpore une ''load/store queue'' et des unités de calcul d'adresse. L'émission multiple des accès mémoire demande de rendre la ''Load-store queue'' multi-ports, afin de gérer plusieurs micro-opérations mémoire simultanées. Il faut aussi dupliquer les unités de calcul d'adresse reliées à la LSQ, chaque port d'émission mémoire devant avoir sa propre unité de calcul d'adresse. : Dans ce qui suit, nous parlerons d'AGU (''Adress Generation Unit'') pour désigner les unités de calcul d'adresse, et de LSQ pour designer la ''Load-store queue''. [[File:Processeur non-superscalaire basique 02.png|vignette|upright=1|Processeur non-superscalaire basique]] Dupliquer les ports de la LSQ et les AGU est nécessaire. Mais qu'en est-il du cache ? La réponse varie suivant le processeur. Il y a deux grandes méthodes pour implémenter l'émission multiple des accès mémoire. La première méthode ajoute des ports de lecture/écriture au cache de données, pour faire des lectures/écritures simultanées. Il faut alors dupliquer des unités de calcul, rajouter les ports sur le cache, mais aussi rajouter des ports sur la ''load/store queue'', pour qu'elle échange des données/commandes mémoire avec le cache. Le cout en circuits est alors conséquent La seconde duplique les unités de calcul, mais pas les ports d'accès au cache. Les accès mémoire sont alors accumulés dans la ''load/store queue'', qui sont ensuite traités un par un par le cache. Un exemple est celui des processeurs AMD de micro-architecture K7 K8, à savoir les anciens AMD Athlon et AMD Athlon 64, qui pouvaient calculer trois adresses simultanément, mais ne pouvaient faire qu'un seul accès au cache par cycle. Le cout en circuit est modéré : les unités de calcul coutent très peu, les ports de la ''load/store queue'' coutent déjà un peu plus. Quelques processeurs utilisaient des solutions intermédiaires. Un processeur utilisant cette technique est, par exemple, capable d'émettre quatre µops mémoire en même temps, mais avec seulement deux ports sur le cache. Un exemple classique étant les Pentium 1 et 2 d'Intel, dont nous parlerons dans le chapitre suivant. ===L'émission séparée des LOAD et STORE=== De rares processeurs ont des ports d'émission qui peuvent faire indifféremment lecture comme écritures. Mais les processeurs modernes préfèrent séparer ports d'émission pour les lectures et les écritures. Un exemple est celui des processeurs Intel de micro-architecture Nehalem, qui avaient un port de lecture et un port d'écriture tous deux reliés à la ''Load-store queue'', chacun avec leur propre unité de calcul d'adresse. Comme autre exemple, les processeurs skylake ont une LSQ avec deux ports de lecture et un port d'écriture, ce qui permet d'émettre deux lectures en même temps qu'une écriture. Dans cette section, nous allons étudier le cas avec des ports séparés pour les lectures et écritures. En général, les processeurs ont un peu plus de ports de lectures que d'écritures. Une raison est qu'un programme a, en moyenne, un tout petit peu plus de lectures que d'écritures. Mais la différence n'est pas énorme, c'est un léger déséquilibre. Une seconde raison est que l'exécution dans le désordre marche bien mieux quand les lectures ont lieu le plus tôt possible. Donc mieux vaut privilégier les lectures à l'émission, pour qu'elles se fassent un peu en avance. En pratique, la différence n'est pas très marquée. Il est courant d'avoir un port de lecture et un d'écriture, les CPU superscalaires très larges ont souvent deux ports de lecture et deux d'écriture. A la rigueur, quelques CPU ont trois ports de lecture et deux d'écriture. Le cas le plus simple est celui où on peut émettre une lecture et une écriture en même temps. Il s'implémente facilement si la LSQ est en réalité une simple ''Store queue'', à savoir qu'elle met en avance les écritures uniquement. Pour rappel, les écritures sont mises en attente dans la ''Store queue'', mais les lectures sont exécutées immédiatement. Une fois l'adresse à lire connue, la lecture consulte la ''Store queue'' pour détecter les dépendances de données, puis s'exécute. La donnée à lire est lue soit depuis la ''Store queue'', soit depuis le cache. [[File:Emission LOAD - Store séparée.png|vignette|upright=1|Émission LOAD - Store séparée]] L'implémentation de l'émission multiple est alors très simple. La ''Store queue'' a déjà deux ports, avec un pour les lectures et un autre pour les écritures. Idem pour le cache, qui a naturellement un port de lecture et un d'écriture. Émettre une lecture et une écriture simultanément ne demande donc pas de modification digne de ce nom. La ''Store queue'' doit juste supporter une lecture et une écriture simultanée. Ajouter l'émission d'une seconde lecture n'est pas compliquée : il faut juste rajouter un second port de lecture sur le cache et la ''Store queue''. Pour rappel, l'unité mémoire est précédée par une structure qui met en attente les micro-opérations mémoire avant leur émission finale. La structure en question varie suivant l'implémentation, mais un cas particulier se marie bien avec l'émission multiple des µops mémoire. Il s'agit du cas avec une file de µops séparée pour les lectures et une autre pour les écritures, appelées respectivement la ''file de µops LOAD'' et la ''file de µops STORE''. Il est alors possible d'émettre une lecture et une écriture lors du même cycle d'horloge. Le port de lecture alimente une unité de calcul d'adresse dédiée, directement reliée au cache. Le port d'écriture du cache alimente une unité de calcul, qui est suivie par une file d'écriture, elle-même reliée au cache. Les processeurs AMD de micro-architecture K6 utilisaient cette technique. [[File:Double émission avec le Load Ordering, Store Ordering.jpg|centre|vignette|upright=2.5|Double émission avec le Load Ordering, Store Ordering]] ==Les CPU superscalaire à exécution dans le désordre== La superscalarité modifie en profondeur le processeur. Et cela dépasse le cadre des unités vues auparavant. Dans cette section, nous allons voir comment les autres étages du pipeline sont modifiées sur un CPU superscalaire. ===L'unité de renommage superscalaire=== Avec la superscalarité, l'unité de renommage de registre doit gérer le cas où des instructions consécutives ont des dépendances de registre. Par exemple, prenons un processeur à double émission, qui renomme deux instructions consécutives. Si elles ont une dépendance de registre, le renommage de la seconde instruction dépend du renommage de la première. La première doit être renommée et le résultat du renommage est utilisé pour renommer la seconde, ce qui empêche d'utiliser deux unités de renommage séparées. Pour cela, elle renomme les registres sans tenir compte des dépendances, pour ensuite corriger le résultat. [[File:Unité de renommage superscalaire.png|centre|vignette|upright=2|Unité de renommage superscalaire.]] Seules les dépendances lecture-après-écriture doivent être détectées, les autres étant supprimées par le renommage de registres. Repérer ce genre de dépendances se fait assez simplement : il suffit de regarder si un registre de destination d'une instruction est un opérande d'une instruction suivante. [[File:Détection des dépendances sur un processeur superscalaire.png|centre|vignette|upright=2|Détection des dépendances sur un processeur superscalaire.]] Ensuite, il faut corriger le résultat du renommage en fonction des dépendances. Si une instruction n'a pas de dépendance avec une autre, on la laisse telle quelle. Dans le cas contraire, un registre opérande sera identique avec le registre de destination d'une instruction précédente. Dans ce cas, le registre opérande n'est pas le bon après renommage : on doit le remplacer par le registre de destination de l'instruction avec laquelle il y a dépendance. Cela se fait simplement en utilisant un multiplexeur dont les entrées sont reliées à l'unité de détection des dépendances. On doit faire ce replacement pour chaque registre opérande. [[File:Correction des dépendances sur un processeur superscalaire.png|centre|vignette|upright=2|Correction des dépendances sur un processeur superscalaire.]] ===Les unités d'exécution dans le désordre=== Réduire le nombre de ports d'émission permet de garder des fenêtres d'instruction et un ROB simples, rapides et économes en énergie. Et mine de rien, c'est là un compromis important : à quoi bon augmenter le nombre de ports d'émission si c'est pour que ce soit compensé par une fréquence plus faible ? Par contre, réduire le nombre de ports augmente la ''port contention'', surtout si on a mal réparti les ALU. Il faut donc trouver un compromis entre réduire le nombre de ports d'émission d'un côté, réduire la ''port contention'' de l'autre. Le ROB doit aussi être modifié pour émettre et terminer plusieurs instructions en même temps, en ajoutant des ports de lecture/écriture. Pour un CPU superscalaire peut émettre 8 µops à la fois, on se dit que le ROB peut retirer 8 µops par cycle maximum. Et c'est très souvent le cas en pratique. Cependant, certains processeurs ont un ROB qui peut retirer le double ! Un exemple est celui des CPU de micro-architecture Skymont, qui peuvent émettre 8 µops par cycle, mais en retirer 16. Quelques CPU de marque AMD étaient aussi dans ce cas. L'avantage est que cela permet de réduire la taille du ROB, du banc de registre et de quelques autres structures matérielles liées à l'exécution dans le désordre. Le gain sur-compense le cout en transistor nécessaire pour retirer 16 µops par cycle. Un détail est que le nombre de ports de lecture n'est pas le même selon que le processeur utilisé des stations de réservation ou une fenêtre d’instruction. Rappelons la terminologie utilisée dans ce cours. Les fenêtres d'instruction se contentent de mémoriser la micro-opération à émettre et quelques bits pour la disponibilité des opérandes. Par contre, les stations de réservations mémorisent aussi les opérandes des instructions. Et cette différence a une influence le nombre de ports de lecture du banc de registre. Supposons que toutes les instructions sont dyadiques, à deux opérandes. Comptons le nombre de ports nécessaires pour alimenter les unités de calcul, sans utiliser d'arbitre de banc de registre. Avec une fenêtre d'instruction, il faut le double du nombre de ports de ''dispatch''. Mais avec des stations de réservation, il faut le double des ports de ''schedule''. La raison est que les registres sont lus après émission avec une fenêtre d'instruction, avant avec des stations de réservation (sous-entendu, après le décodage). or, comme dit plus haut, les processeurs superscalaires larges ont souvent plus de ports de ''schedule'' que de ''dispatch''. ==Les optimisations des processeurs superscalaires larges== Les processeurs superscalaires larges ont des optimisations spécifiques, qui n'ont pas de sens sur les processeurs superscalaires étroits. Dans les grandes lignes, elles se classent en deux types. Les premières permettent de charger/décoder plus de 5-10 instructions simultanément. Le problème est alors que les branchements viennent mettre leur grain de sel et qu'il faut en tenir compte. Les secondes corrigent des problèmes qui apparaissent quand un CPU intègre plus de 5 à 10 unités de calcul. ===L'étape de chargement d'un CPU superscalaire large=== Charger plusieurs instructions pose de nombreux problèmes sur les processeurs superscalaires larges, qui émettent de 5 à 10 instructions simultanées. Pour rappel, la stratégie basique pour charger N instructions est de charger un bloc de N instructions consécutives. Les instructions après un branchement ne sont pas décodées ni exécutées, ce qui entraine une petite baisse de performance. Pour un N petit, la perte de performance est mineure. Mais pour un N large, elle devient suffisamment importante pour que la conception du CPU doit alors être repensée de fond en comble. Les analyses statistiques disent qu'il y a environ un branchement tous les 4 à 6 instructions. Un CPU a donc tendance à décoder des paquets de 3 à 5 instructions consécutives, avant qu'un branchement casse la dynamique. Or, cela pose des problèmes pour utiliser plusieurs décodeurs. L'implémentation basique, qui charge N instructions consécutives pour alimenter N décodeurs, fonctionne donc jusqu'à 4/6 décodeurs. Au-delà de 5 instructions, un CPU superscalaire doit être adapté pour gérer les branchements dans les paquets d'instruction qu'il émet. Le nombre de 5 instructions ne sort donc pas de nulle part. La première solution utilise un cache de µops, avec un ''front-end'' capable de charger/décoder 4/5 instructions à la fois, pas plus. L'idée est que la majorité des instructions proviennent du cache de µops (plus de 80%), ce qui fait qu'on peut émettre environ 10 instructions à partir du cache de µops, pas besoin d'avoir beaucoup de décodeurs. Les performances sont cependant dégradées lors d'un défaut de cache de µops. Les instructions doivent alors être chargées depuis le cache d'instruction, par paquets de 4-5, puis sont décodées par 4-5 décodeurs, ce qui ne permet pas d’alimenter les unités de calcul à plein régime. La solution précédente joue sur la distinction entre pots de décodage et d'émission. Elle permet d'émettre un grand nombre de µops sans pour autant charger un grand nombre d'instructions en même temps. En quelque sorte, ce n'est qu'une méthode de contournement, le CPU superscalaire reste de type étroit. Mais il est aussi possible d'utiliser un '''''front-end large''''', capable de charger une bonne dizaine d'instructions en même temps. La seule difficulté est de tenir compte de la présence de branchements. Pour rappel, avec un ''front-end'' étroit, les instructions situées après le premier branchement pris sont annulées, elles ne sont pas décodées ni exécutées. [[File:Fetch sur un processeur superscalaire avec prediction de branchements.png|centre|vignette|upright=2|Fetch sur un processeur superscalaire avec prédiction de branchements.]] Un ''front-end'' large charge les instructions de destination du branchement et les placent à sa suite. Ils chargent deux blocs à la fois et les fusionnent en un seul qui ne contient que les instructions présumées utiles. [[File:Cache d'instructions autoaligné.png|centre|vignette|upright=2|Cache d'instructions auto-aligné.]] Mais cela demande de charger deux blocs de mémoire en une fois, ce qui demande un cache d'instruction multiports. Il faut aussi ajouter un circuit pour assembler plusieurs morceaux de blocs en un seul : le fusionneur (''merger''). En pratique, cette solution demande d'ajouter un second port de lecture au cache, pour un cout en hardware important, avec un impact négatif sur le temps d'accès au cache. Et il faut rajouter le fusionneur, qui prend un étage de pipeline à lui tout seul. Et encore, cette solution ne gère pas le cas où un bloc contient plusieurs branchements ! [[File:Implémentation d'un cache d'instructions autoaligné.png|centre|vignette|upright=2|Implémentation d'un cache d'instructions auto-aligné.]] Les exemples précédents partent du principe qu'il n'y a qu'un seul branchement dans un bloc. Mais quand les blocs deviennent assez longs, il est fréquent d'avoir plusieurs branchements dans un bloc. Et il est même possible d'avoir plusieurs branchements pris. Dans un bloc de 8-10 instructions, ce n'est pas rare, et les CPU superscalaires à 8-10 voies sont la règle dans les CPU des années 2020. Pour gérer ce cas, il faut ajouter des ports supplémentaires au cache d'instruction et adapter le fusionneur, pour qu'il puisse fusionner trois ou quatre blocs. De plus, il faut utiliser une unité de prédiction de branchement capable de ''prédire plusieurs branchements par cycle''. ===Le décodage superscalaire large=== Le décodage des instructions n'est pas censé dépendre de l'implémentation du chargement. En théorie, une fois plusieurs blocs d'instruction fusionnés, on peut décoder les instructions en parallèle. Cependant, certains processeurs effectuent le décodage en tenant compte de la manière dont les branchements découpent les blocs. Les processeurs Atom d'Intel utilisent une solution de ce type, qui couple prédiction de branchement et décodage parallèle. Ils chargent/décodent des paquets de 3 instructions consécutives, chaque paquet étant séparé du précédent par un branchement. Pour cela, en plus d'avoir un fusionneur adapté, ils regroupent les décodeurs en ''clusters'', chacun regroupant 3 décodeurs, et pouvant décoder trois instructions consécutives. Les micro-architectures Tremont et Crestmont avaient deux ''clusters'' (6 décodeurs au total), la micro-architecture Skymont en intègre trois (9 décodeurs au total). Avec deux ''clusters'', le fonctionnement est le suivant. Si le code est composé d'une suite d'instruction sans branchements, seul le premier ''cluster'' est utilisé, limitant le décodage à trois instructions par cycle. Par contre, si l'une des trois instructions décodée est un branchement, alors le second ''cluster'' décode les trois instructions situées après ce branchement, à partir de l'adresse de destination. Avec un troisième ''cluster'', c'est la même chose, sauf qu'il faut qu'il y ait un branchement dans les trois instructions décodées par le second ''cluster''. Mais comment faire pour savoir quels décodeurs activés, quels ''clusters'' sont actifs ? C'est la prédiction de branchement qui commande. Avec deux ''clusters'', elle détermine si un branchement est présent dans les trois instructions à charger, et quelle est son adresse de destination. Sans branchement, elle prévient les décodeurs et les trois instructions chargées sont décodées dans un ''cluster'' libre. Si elle prédit un branchement, elle charge les instructions adéquates, et active les deux ''clusters''. Avec trois branchements, c'est le même mécanisme, sauf que l'unité de prédiction de branchement doit être capable de prédire deux branchements par cycle. Il faut noter que les décodeurs d'un ''cluster'' sont des décodeurs simples, sans microcode. Sur les processeurs Tremont et Crestmont, le microcode était unique et était partagé entre tous les ''clusters''. Un seul ''cluster'' pouvait décoder une instruction complexe avec le microcode, l'autre devait se limiter à des instructions simples uniquement. Depuis Skymont, le microcode est partiellement dupliqué, avec un microcode par ''cluster''. Le cout en hardware peut paraitre prohibitif, pour un gain en performance faible. Sauf que c'est oublier que ce sont des processeurs Atom, sur lesquels beaucoup d'instructions sont microcodées. Les processeurs Atom ont un budget en transistor limité, ce qui fait qu'il est préférable pour eux d'implémenter des instructions en microcode que d'utiliser des unités de calcul dédiées ou des circuits de décodage complexes. Pour limiter la casse, le microcode n'est que partiellement dupliqué. Seul le microcode pour les instructions fréquente est dupliqué. Il y a un microcode secondaire pour certaines instructions, qui est lui unique et partagé par tous les ''clusters''. ===Le contournement sur les processeurs superscalaires larges=== Pour rappel, la technique du contournement (''register bypass'') permet au résultat d'une instruction d'être immédiatement utilisable en sortie de l'ALU, avant même d'être enregistré dans les registres. Implémenter la technique du contournement demande d'utiliser des multiplexeurs pour relier la sortie de l'unité de calcul sur son entrée si besoin. Il faut aussi des comparateurs pour détecter des dépendances de données. [[File:Pipeline Bypass.png|centre|vignette|upright=1|Pipeline Bypass]] La sortie de chaque ALU doit être reliée aux entrées de toutes les autres, avec les comparateurs qui vont avec. Sur les processeurs ayant plusieurs d'unités de calculs, cela demande beaucoup d’interconnexions et de comparateurs. Pour N unités de calcul, cela demande 2 * N² interconnexions, implémentées avec 2N multiplexeurs de N entrées chacun. Si c'est faisable pour 2 ou 3 ALUs, la solution est impraticable au-delà. Non seulement ce serait un enfer à câbler, mais les fils seraient tellement longs que la fréquence du processeur en prendrait un coup. Notez que cela vaut pour les processeurs superscalaires, mais aussi pour tout processeur avec beaucoup d'unités de calcul. En théorie, un CPU à exécution dans le désordre, non-superscalaire, mais avec beaucoup d'unités de calcul, fait face au même problème. En pratique, avoir une dizaine d'ALU implique processeur superscalaire à exécution dans le désordre. D'où le fait qu'on parle du problème maintenant. La seule solution praticable est de ne pas relier toutes les unités de calcul ensemble. À la place, on préfère regrouper les unités de calcul dans différents '''agglomérats''' ('''cluster'''). Le contournement est alors possible entre les unités d'un même agglomérat, mais pas entre agglomérats différents. Évidemment, certaines possibilités de contournement sont perdues, mais c'est compensé par un gain en fréquence et un cout en circuit/fils réduit. C'est un bon compromis, ce qui explique que presque tous les processeurs modernes l'utilisent. Les rares exceptions sont les processeurs POWER 4 et POWER 5, qui ont préféré se passer de contournement pour garder un processeur très simple et une fréquence élevée. ===Le banc de registre des processeurs superscalaires larges=== Pour émettre deux opérations entières simultanément, il faut lire deux fois plus d'opérandes. Et il faut aussi écrire deux résultats dans les registres, en même temps. Pour cela, on double les ports du banc de registre. En général, il faut au maximum 2 ports de lecture et un port d'écriture par unité de calcul. Le problème, c'est que plus un banc de registres a de ports, plus il utilise de circuits, est compliqué à concevoir, consomme de courant et chauffe. Avec plusieurs dizaines d'unités de calcul différentes, ajouter autant de ports impacterait la fréquence du banc de registres et réduirait les performances, voire est totalement impraticable à cause des contraintes thermiques. Les processeurs superscalaires larges doivent donc trouver des solutions. La première solution utilise un banc de registre unique, mais n'utilise pas autant de ports que le pire des cas le demanderait. Le processeur détecte quand il n'y a pas assez de ports pour servir toutes les micro-opérations. Quand ça arrive, l'unité d'émission émet assez de micro-opérations pour exploiter les ports disponibles, mais met en attente les micro-opérations en trop, le temps que les ports se libèrent. Pour cela, le banc de registre est géré par un circuit d'arbitrage spécialisé, intégré à l'unité d'émission, l’'''arbitre du banc de registres''' (''register file arbiter''). Une autre solution est de dupliquer le banc de registres en plusieurs exemplaires qui contiennent exactement les mêmes données, mais avec moins de ports de lecture/écriture. Un exemple est celui des processeurs POWER, Alpha 21264 et Alpha 21464. Sur ces processeurs, le banc de registre est dupliqué en plusieurs exemplaires, qui contiennent exactement les mêmes données. Les écritures dans les registres sont envoyées à tous les bancs de registres, afin de garantir que leur contenu est identique. Chaque exemplaire a exactement deux ports de lecture. La conception du processeur est simplifiée, que ce soit au niveau du câblage, que de la conception des bancs de registres. Une dernière solution découpe le banc de registres en plusieurs bancs de registres plus petits, avec un réseau d'interconnexions pour échanger des données entre bancs de registres. Dans la plupart des cas, cette séparation est invisible du point de vue de l'assembleur et du langage machine. Le processeur se charge de transférer les données entre bancs de registres suivant les besoins. Sur d'autres processeurs, les transferts de données se font via une instruction spéciale, souvent appelée ''COPY''. [[File:Banc de registres distribué.png|centre|vignette|upright=2|Banc de registres distribué.]] Sur de certains processeurs, un branchement est exécuté par une unité de calcul spécialisée. Or les registres à lire pour déterminer l'adresse de destination du branchement ne sont pas forcément dans le même agglomérat que cette unité de calcul. Pour éviter cela, certains processeurs disposent d'une unité de calcul des branchements dans chaque agglomérat. Dans les cas où plusieurs unités veulent modifier le ''program counter'' en même temps, un système de contrôle général décide quelle unité a la priorité sur les autres. Mais d'autres processeurs fonctionnent autrement : seul un agglomérat possède une unité de branchement, qui peut recevoir des résultats de tests de toutes les autres unités de calcul, quel que soit l’agglomérat. ==La macro-fusion : une optimisation du décodage parallèle== Les processeurs superscalaires supportent la technique dite de '''macro-fusion''', qui permet de fusionner deux-trois instructions consécutives en une seule micro-opération. Par exemple, il est possible fusionner une instruction de test et une instruction de saut en une seule micro-opération de branchement. Il s'agit là de l'utilisation la plus importante de la macro-fusion sur les processeurs x86 modernes. La macro-fusion est utilisée pour fusionner un test et un saut en un branchement unique. L'optimisation est présente sur la plupart des CPU superscalaires modernes. Les CPU x86 l'implémentent, mais aussi quelques CPU ARM et les processeurs Apple de série M (M1 à M5, et potentiellement plus quand ils seront sortis). La fusion des instructions se fait lors du décodage des instructions, grâce à la coopération des décodeurs. Elle marche systématiquement si l'instruction de test et le saut sont consécutives, qu'elles forment bien une paire d'instruction. Par contre, insérer une instruction entre les deux empêche la macro-fusion de faire son travail, sauf si l'instruction insérée est un NOP. Les processeurs ARM64 avaient une forme spécifique de macro-fusion, utilisée pour un cas très précis : charger une constante immédiate de 32 ou 64 bits, dans un registre 64 bits. Les CPU ARM 64 bits utilisent des instructions codées sur 32 bits, qui ne peuvent encoder que des constantes immédiates de 16 bits. Pour charger une constante de 32/64 bits, il faut s'y prendre en plusieurs fois. Une première instruction MOVZ charge les 16 premiers bits, puis 2 à 4 instructions MOVK remplissent le reste. MOVZ met à zéro les 48 bits de poids fort, MOVK permet de sélectionner le doublet à modifier dans le registre (on modifie 16 bits, dont on choisit la place, le reste du registre est laissé tel quel). ARM permet de fusionner cette suite d'instructions en une seule µop MOV avec une constante immédiate. On parle alors de '''''MOVZ + MOVK fusion'''''. Une dernière utilisation, [https://www2.eecs.berkeley.edu/Pubs/TechRpts/2016/EECS-2016-130.pdf proposées pour le jeu d'instruction RISC-V], fusionnait un calcul d'adresse et une instruction LOAD/STORE en une seule instruction d'accès mémoire. Elle a été envisagée à une époque où le jeu d'instruction RISC-V ne gérait pas les modes d'adressage base + indice. L'idée était d'avoir une unité mémoire qui gérait le mode d'adressage base + indice et de fusionner des instructions pour l'exploiter. Mais de nos jours, le jeu d'instruction RISC-V intègre le mode d'adressage base + indice, ce qui fait que la macro-fusion n'a aucun intérêt pour ce cas précis. La macro-fusion a de nombreuses limitations en pratique. En théorie, on pourrait l'utiliser au-delà des deux situations mentionnées plus haut. Par exemple, pour fusionner une addition et une multiplication en une seule instruction MAD. Mais l'intérêt est assez faible. Notons que la macro-fusion ne permet que de fusionner des paires d'instructions. Aller au-delà demanderait un cout en circuit trop important. Déjà que tester deux paires utilise pas mal de comparateurs... ==Le ''stack engine'' : une optimisation de la pile d'appel== Les processeurs modernes intègrent une optimisation liée au pointeur de pile. Pour rappel, sur les architectures modernes, le pointeur de pile est un registre utilisé pour gérer la pile d'appel, précisément pour savoir où se trouve le sommet de la pile. Il est manipulé par les instructions CALL, RET, PUSH et POP, mais aussi par les instructions d'addition/soustraction pour gérer des cadres de pile de taille variable. De plus, il peut servir d'opérande pour des instructions LOAD/STORE, afin de lire/écrire des variables locales, les arguments d'une fonction, et autres. C'est donc un registre général adressable, intégré au banc de registre, altéré par l'unité de calcul entière. L'incrémentation/décrémentation du pointeur de pile passe donc par l'unité de calcul, lors des instructions CALL, RET, PUSH et POP. Mais, l'optimisation que nous allons voir permet d'incrémenter/décrémenter le pointeur de pile sans passer par l'ALU, ou presque. L'idée est de s'inspirer des architectures avec une pile d'adresse de retour, qui intègrent le pointeur de pile dans le séquenceur et l'incrémentent avec un incrémenteur dédié. ===Le ''stack engine''=== L'optimisation que nous allons voir utilise un '''''stack engine''''' intégré à l'unité de contrôle, au séquenceur. Le processeur contient toujours un pointeur de pile dans le banc de registre, cela ne change pas. Par contre, il n'est pas incrémenté/décrémenté à chaque instruction CALL, RET, PUSH, POP. Un compteur intégré au séquenceur est incrémenté à la place, nous l’appellerons le '''compteur delta'''. La vraie valeur du pointeur de pile s'obtient en additionnant le compteur delta avec le registre dans le banc de registre. Précisons que si le compteur delta vaut zéro, la vraie valeur est dans le banc de registre et peut s'utiliser telle quelle. Lorsqu'une instruction ADD/SUB/LOAD/STORE utilise le pointeur de pile comme opérande, elle a besoin de la vraie valeur. Si elle n'est pas dans le banc de registre, le séquenceur déclenche l'addition compteur-registre pour calculer la vraie valeur. Finalement, le banc de registre contient alors la bonne valeur et l'instruction peut s'exécuter sans encombre. L'idée est que le pointeur de pile est généralement altéré par une série d'instruction PUSH/POP consécutives, puis des instructions LOAD/STORE/ADD/SUB utilisent le pointeur de pile final comme opérande. En clair, une bonne partie des incrémentations/décrémentation est accumulée dans le compteur delta, puis la vraie valeur est calculée une fois pour toutes et est utilisée comme opérande. On accumule un delta dans le compteur delta, et ce compteur delta est additionné quand nécessaire. Précisons que le compteur delta est placé juste après le décodeur d'instruction, avant même le cache de micro-opération, l'unité de renommage et l'unité d'émission. Ainsi, les incrémentations/décrémentations du pointeur de pile disparaissent dès l'unité de décodage. Elles ne prennent pas de place dans le cache de micro-opération, ni dans la fenêtre d'instruction, ni dans la suite du pipeline. De nombreuses ressources sont économisées dans le ''front-end''. Mais utiliser un ''stack engine'' a aussi de nombreux avantages au niveau du chemin de données/''back-end''. Déjà, sur les processeurs à exécution dans le désordre, cela libère une unité de calcul qui peut être utilisée pour faire d'autres calculs. Ensuite, le compteur delta mémorise un delta assez court, de 8 bits sur le processeur Pentium M, un peu plus pour les suivants. L'incrémentation se fait donc via un incrémenteur 8 bits, pas une grosse ALU 32/64 bits. Il y a un gain en termes de consommation d'énergie, un incrémenteur 8 bits étant moins gourmand qu'une grosse ALU 32/64 bits. ===Les points de synchronisation du delta=== La technique ne fonctionne que si la vraie valeur du pointeur de pile est calculée au bon moment, avant chaque utilisation pertinente. Il y a donc des '''points de synchronisation''' qui forcent le calcul de la vraie valeur. Plus haut, nous avions dit que c'était à chaque fois qu'une instruction adresse le pointeur de pile explicitement, qui l'utilise comme opérande. Les instructions CALL, RET, PUSH et POP ne sont pas concernées par elles utilisent le pointeur de pile de manière implicite et ne font que l'incrémenter/décrémenter. Mais dans les faits, c'est plus compliqué. D'autres situations peuvent forcer une synchronisation, notamment un débordement du compteur delta. Le compteur delta est généralement un compteur de 8 bits, ce qui fait qu'il peut déborder. En cas de débordement du compteur, le séquenceur déclenche le calcul de la vraie valeur, puis réinitialise le compteur delta. La vraie valeur est donc calculée en avance dans ce cas précis. Précisons qu'un compteur delta de 8 bits permet de gérer environ 30 instructions PUSH/POP consécutives, ce qui rend les débordements de compteur delta assez peu fréquent. À noter que si le compteur delta vaut zéro, il n'y a pas besoin de calculer la vraie valeur, le séquenceur prend cette situation en compte. Un autre point de synchronisation est celui des interruptions et exceptions matérielles. Il faut que le compteur delta soit sauvegardé lors d'une interruption et restauré quand elle se termine. Idem lors d'une commutation de contexte, quand on passe d'un programme à un autre. Pour cela, le processeur peut déclencher le calcul de la vraie valeur lors d'une interruption, avant de sauvegarder les registres. Pour cela, le processeur intègre un mécanisme de sauvegarde automatique, qui mémorise la valeur de ce compteur dans le tampon de réordonnancement, pour forcer le calcul de la vraie valeur en cas de problème. La technique du ''stack engine'' est apparue sur les processeurs Pentium M d'Intel et les processeurs K10 d'AMD, et a été conservée sur tous les modèles ultérieurs. L'implémentation est cependant différente selon les processeurs, bien qu'on n'en connaisse pas les détails et que l'on doive se contenter des résultats de micro-benchmarks et des détails fournit par Intel et AMD. Selon certaines sources, dont les manuels d'optimisation d'Agner Fog, les processeurs AMD auraient moins de points de synchronisation que les processeurs Intel. De plus, leur ''stack engine'' serait placé plus loin que prévu dans le pipeline, après la file de micro-opération. ==La micro-fusion et la délamination== La '''micro-fusion''' est une optimisation qui retarde le décodage réel de certaines instructions assez loin dans le pipeline. Elle concerne surtout les instructions ''load-op'', mais pas que. D'autres instructions mémoire peuvent subir cette optimisation. L'essentiel est que, sans micro-fusion, ces instructions sont décodées en plusieurs micro-opérations. La micro-fusion retarde le décodage final de ces instructions, qui se fait assez tard dans le pipeline. Pour comprendre l'intérêt, nous allons devoir faire quelques explications. L'optimisation en question fait le décodage en deux temps. Prenons l'exemple d'une opération ''load-op'', qui est censée être décodée en deux micro-opérations : une lecture, puis une opération. Le décodage produit une '''macro-opération''' (terme d'Intel), qui est propagée dans le pipeline, pour être scindée en deux micro-opérations un peu avant les ALU et l'unité mémoire. La macro-opération est techniquement une micro-opération comme une autre, sauf que ce n'est pas la micro-opération finale. L'unité d'émission découpe cette macro-opération en deux micro-opérations, qui sont émises l'une après l'autre. Pour résumer, les instructions ''load-op'' ne sont complétement découpées en micro-opération lors de l'émission, pas lors du décodage. L'optimisation impacte ce qui se situe entre le décodeur d'instruction et l'unité d'émission : l'unité de renommage, la file de micro-opérations, le ''Loop Stream Detector'' et/ou le cache de micro-opération. L'intérêt de l'optimisation est que l'on fusionne deux micro-opérations en une seule macro-opération. Le cache de micro-opération et la file de micro-opération peuvent mémoriser plus de micro-opérations, vu que certaines sont fusionnées. Quant à l'unité de renommage, elle renomme une seule macro-opération au lieu de renommer deux micro-opérations, ce qui augmente le débit du renommage de registre. Par contre, cela complique un peu le travail de l'unité d'émission, qui doit découper les instructions ''load-op'' en deux et émettre les micro-opérations l'une après l'autre. Il faut noter qu'il n'y a pas de fusion proprement dite. Le décodeur ne décode pas deux micro-opérations séparées, avant de les fusionner. En réalité, il génère directement une macro-opération, qui correspond à deux micro-opérations, que ce soit plus tard dans le pipeline ou sur une autre architecture. D'ailleurs, la micro-fusion a lieu à l'intérieur d'une instruction, qui est censée être traduite en plusieurs micro-opérations. Et encore : pas toutes. La conséquence est que la technique n'a de sens que sur les processeurs CISC, avec des instructions assez complexes pour être décodées en plusieurs micro-opérations. En pratique, seuls les CPU x86 sont concernés. ===Les domaines de fusion=== La micro-fusion n'est pas spécifique aux processeurs superscalaires. En théorie, on peut l'implémenter sur un processeur non-superscalaire à émission dans l'ordre. Cependant, je ne connais aucun processeur à émission dans l'ordre qui implémente la micro-fusion, même si son implémentation est en théorie possible sur de tels processeurs. La micro-fusion est implémentée différemment selon que l'émission se fasse dans l'ordre, dans le désordre, avec une fenêtre d'instruction, avec des stations de réservation, etc. J'ai dit plus haut que les micro-opérations sont générées lors de l'émission, mais le moment exact varie suivant le processeur. Pour simplifier le tout, nous parlerons de '''scission''' quand le processeur traduit une macro-opération en deux/trois micro-opérations. Le moment où a lieu la scission est théoriquement dans l'unité d'émission, mais celle-ci prend plusieurs formes selon que le processeur utilise l'émission dans l'ordre, dans le désordre, avec une fenêtre d'instruction, avec des stations de réservation, etc. Les processeurs à exécution dans l'ordre sont le cas le plus simple. L'unité d'émission est alors en charge du découpage des macro-opérations en micro-opérations. La file de micro-opération ou le cache de micro-opérations mémorisent des macro-opérations. À l'opposé, le tampon de ré-ordonnancement mémorise lui des micro-opérations. Le reste du processeur n'est pas affecté. Sur les processeurs à exécution dans le désordre, les différences sont nombreuses. Le tampon de ré-ordonnancement peut mémoriser soit les micro-opérations finales, soit des macro-opérations. Il en est de même avec la fenêtre d'instruction et/ou les stations de réservation, qu'elles soient centralisées ou décentralisées. Tout dépend de comment se fait l'implémentation. Une implémentation simple effectue la scission des macro-opérations dans l'unité d'émission, comme sur les processeurs à exécution dans l'ordre. Sur de nombreux processeurs, l'unité d'émission et de renommage sont simplement fusionnées, ce qui fait que la scission a lieu juste après le renommage de registres. En conséquence, le tampon de re-ordonnancement et les fenêtres d'instruction ne voient que des micro-opérations. Les macro-opérations disparaissent juste avant. Cette implémentation est préférée avec une fenêtre d'instruction centralisée, car on voit mal comment scinder une macro-opération juste avant l'ALU. Une autre implémentation fait la scission en sortie de la fenêtre d'instruction ou des stations de réservation, qu'elles soient centralisées ou décentralisées. Dans ce cas, le tampon de re-ordonnancement fait de même. L'avantage est que le tampon de ré-ordonnancement stocke des macro-opérations, ce qui augmente sa taille effective. Par exemple, il utilise une seule entrée pour une instruction ''load-op'', au lieu de deux sans micro-fusion de ce type. ===La délamination=== La scission peut parfois avoir lieu plus tôt, dans des circonstances très précises. Concrètement, cela ne concerne que quelques micro-architectures d'Intel, pour des instructions très précises. La raison est les limitations du format des micro-opérations, pas une histoire d'optimisation. Le processeur aimerait pouvoir faire la micro-fusion, mais ne peut pas à cause de ces limitations. Une telle situation cause alors une '''délamination''' : les micro-opérations sont scindées précocement. Par exemple, sur la micro-architecture SandyBridge d'Intel, les opérations ''load-op'' sont fusionnées, sauf si elles utilisent l'adressage indicé. Les micro-opérations de ce processeur ne peuvent avoir que deux opérandes. Trois opérandes est de trop, et les opérations ''load-op'' en adressage indicé dépassent cette limite. Dans ce cas, les micro-opérations sont fusionnées en sortie du décodeur, mais sont scindés avant d’entrer dans la file de micro-opération. L'avantage n'est pas évident, vu que l'unité de renommage suit de près le décodeur d'instruction. Il est que le cache de micro-opérations mémorise des macro-opérations, pas deux micro-opérations. ===La micro-fusion des processeurs Intel et AMD=== La micro-fusion est apparue sur le Pentium M d'Intel, et a été conservée telle quelle sur les processeurs Intel suivants. Les processeurs AMD supportent la micro-fusion depuis au moins la micro-architecture AMD K8. Mieux que ça, la délamination n'était pas utilisée car les micro-opérations acceptaient plus de 2 opérandes. Que ce soit chez AMD ou Intel, elle concerne deux types d'instructions : les écritures mémoire et les instructions ''load-op''. Sur le Pentium M, il y avait quelques limitations, l'optimisation se limitant par exemple aux registres entiers et flottants, mais pas aux registres MMX, mais pas les registres SSE (nous verrons ces registres dans quelques chapitres). Mais les processeurs modernes n'ont plus cette limitation. La micro-fusion des CPU Intel concerne deux types d'instructions : les écritures mémoire et les instructions ''load-op''. Pour les écritures, la situation est plus complexe que pour les instructions ''load-op''. Sur les processeurs Intel précédents, les écritures étaient réalisées dans deux unités séparées : le calcul d'adresse dans une unité de calcul, l'écriture proprement dite dans l'unité mémoire. Elles prenaient donc deux micro-opérations. Le processeur a changé ses unités mémoire, ce qui fait qu'elle pouvait faire les deux en une seule micro-opération. Il s'agissait alors d'une optimisation du chemin de données, plus que de la micro-fusion proprement dite, mais je la mentionne ici malgré tout. La micro-fusion sur ce processeur avait un avantage en plus des avantages usuels : elle utilisait mieux les décodeurs. Les Pentium M avaient trois décodeurs pour décoder plusieurs instructions en même temps, chose qu'on détaillera dans le chapitre sur les processeurs superscalaires. Un seul décodeur pouvait décoder toutes les instructions, les deux autres ne peuvent décoder que les instructions simples, qui se décodent en une seule micro-opération. Avec la micro-fusion, les trois décodeurs peuvent gérer les écritures mémoire et les opérations ''load-op'', vu qu'elles deviennent des instructions simples. Sans cela, elles auraient été limitées au premier décodeur. <noinclude> {{NavChapitre | book=Fonctionnement d'un ordinateur | prev=Le parallélisme mémoire | prevText=Le parallélisme mémoire | next=Exemples de microarchitectures CPU : le cas du x86 | nextText=Exemples de microarchitectures CPU : le cas du x86 }} </noinclude> fffyvci2zgeplrsef4a46htz1bofbpb 773242 773241 2026-09-26T14:24:25Z Mewtow 31375 /* Les CPU superscalaire à exécution dans le désordre */ 773242 wikitext text/x-wiki Les processeurs vus auparavant ne peuvent émettre au maximum qu'une instruction par cycle d'horloge. Ils peuvent avoir plusieurs instructions qui s'exécutent en même temps, dans des unités de calcul séparées. C'est le cas dès qu'une instruction multi-cycles s'exécute. Par contre, ils ne peuvent démarrer qu'une seule instruction par cycle. Et quand on court après la performance, ce n'est pas assez ! Les concepteurs de processeurs ont inventés des processeurs qui émettent plusieurs instructions par cycle : les '''processeurs à émissions multiples'''. Un processeur à émission multiple charge plusieurs instructions en même temps, les décode en parallèle, puis les émet en même temps sur des unités de calculs séparées. Pour cela, il faut gérer les dépendances entre instructions, répartir les instructions sur différentes unités de calcul, et cela n'est pas une mince affaire. En général, pour un processeur à émission multiple capable d'émettre N instructions en parallèle, on parle de '''processeur à N voies''', ou encore un ''processeur à N pipelines''. [[File:Superscalarpipeline.svg|centre|vignette|upright=1.5|Pipeline RISC classique à cinq étages sur un processeur superscalaire. On voit bien que plusieurs instructions sont chargées en même temps.]] Les processeurs à émission multiple sont de deux types : les processeurs VLIW et les '''processeurs superscalaires'''. Nous mettons les processeurs VLIW de côté en attendant le prochain chapitre. La raison est qu'ils ont un jeu d'instruction spécialisé qui expose le parallélisme d'instruction directement au niveau du jeu d'instructions, là où les processeurs superscalaires restent des processeurs au jeu d'instruction normal. La différence principale entre processeur VLIW et superscalaire est qu'un processeur superscalaire répartit les instructions sur les unités de calcul à l’exécution, là où un processeur VLIW délègue cette tâche au compilateur. ==Le nombre de voies d'un processeur superscalaire== Les processeurs superscalaires les plus simples ne permettent que d'émettre deux micro-opérations à la fois, d'où leur nom de processeur ''dual issue'', ce qui se traduit en '''processeur à double émission'''. Les processeurs triple émission permettent eux d’émettre trois instructions à la fois. A l'opposé, les CPU superscalaires les plus puissants peuvent émettre jusqu'à 8-10 instructions simultanément. En 2026, les CPU superscalaires peuvent monter jusqu’à 10 µops émises en même temps, ce qui est énorme ! Et cela nous amène à faire une distinction entre les processeurs superscalaires dits '''étroits''' et ceux dits '''larges'''. Dans ce wikilivre, on part du principe que les processeurs superscalaires étroits émettent jusqu'à 4-5 µops à la fois, alors que les larges peuvent en émettre plus, l'intervalle entre les deux est une zone grise. Le nombre exact n'est pas fixé dans le marbre, la distinction est volontairement vague. Nous parlerons de la '''largeur d'un CPU superscalaire''' pour parler du nombre de voies, du nombre de µops émises en même temps. Plus un CPU superscalaire est large, plus il est performant ''toute chose égale par ailleurs''. Mais dans les faits, vous pouvez oublier la partie en italique. Élargir un CPU superscalaire demande de rajouter beaucoup de circuits. Et ce cout en circuit aura un impact sur la performance. A budget en transistors limité, il y a un compromis entre élargir un CPU et rajouter du cache ou la taille des fenêtres d'instructions/ROB et autres. ===Les contraintes d’appariement=== Prenons un processeur double émission, capable d'émettre deux instructions simultanément. Émettre deux instructions en même temps n'est pas toujours possible. Par exemple, si deux instructions sont dépendantes, on ne peut pas les émettre en même temps. Mais oublions un instant ces histoires de dépendances de données, et partons du principe que le CPU ait affaire à deux instructions indépendantes, une paire idéale. Un CPU double émission "parfait" pourrait exécuter n'importe quelle paire d'instruction. Par exemple, il pourrait émettre deux multiplications consécutives, deux lectures consécutives, une lecture et une écriture en même temps, etc. En pratique, aucun processeur ne permet cela. Implémenter un CPU superscalaire "parfait" aurait un cout en circuit beaucoup trop conséquent. Par exemple, pour émettre deux multiplications en même temps, il faudrait deux multiplieurs séparés. Idem pour exécuter deux accès mémoire simultané : cela demande d'avoir deux unités mémoire et un cache multiport. Mais en pratique, aucun CPU superscalaire ne fait cela. La totalité des CPU double émission se débrouille avec un seul circuit multiplieur et une seule unité mémoire. Et cela entraine l'apparition de dépendances structurelles, qui font que certaines paires d'instructions sont impossibles. Par exemple, impossible d'émettre deux multiplications en même temps avec un seul circuit multiplieur. Mais émettre une multiplication en parallèle d'une addition est parfaitement possible. Il y a donc des paires d'instructions autorisées et des paires interdites. Et la logique est la même sur les CPU à triple ou quadruple émission, et au-delà. : des triplets ou quadruplet d'instructions sont impossibles à émettre simultanément, alors que d'autres le sont. Pour le dire autrement, il y a des '''contraintes d'appariement''' sur les instructions à émettre en même temps. ===L'impact des contraintes d’appariement sur la performance=== Ces contraintes d’appariement ont un impact sur les performance, mais celui-ci peut être grandement mitigé. Les programmes informatiques utilisent en pratique beaucoup plus d'opérations entières de base que de multiplication, branchements et accès mémoire. Il y a environ 5 opérations entières de base pour une multiplication entière. En pratique, il est rare de devoir exécuter deux multiplications à la fois. Idem pour les décalages et rotations, qui sont assez éloignés les uns des autres. Deux branchements consécutifs sont quant à eux plus fréquents. Deux accès mémoire consécutifs est assez rare, mais ils restent assez proches les uns des autres malgré tout. La conséquence est que certaines paires d'instructions sont privilégiées par rapport au reste. Les paires regroupant deux calculs basiques (additions, soustractions, ...) sont systématiquement supportées, car dupliquer des ALU entières est facile. Les paires regroupant un calcul et une autre instruction sont elles aussi systématiquement supportées. Par contre, les paires avec deux branchements, deux accès mémoire, deux décalages/rotations, ou deux multiplications/divisions sont fréquemment interdites. Seuls les CPU superscalaires larges peuvent les permettre, et encore : sous conditions. Le résultat est que le processeur peut se débrouiller avec une seule unité mémoire et une unité de branchement. ===Terminologie pour la suite du chapitre=== Décrire les contraintes d’appariement demande de dire quelles combinaisons d’instructions sont autorisées ou interdites. Et pour cela, nous allons introduire les abréviations suivantes : * INT est un raccourcis pour parler d'une opération entière simple, qu'une ALU entière peut exécuter. * MUL correspond à une opération entière complexe, typiquement une multiplication, éventuellement à une division. * FLOAT désigne une opération flottante, peut importe laquelle. * MEM désigne un accès mémoire, à savoir une instruction ou µop mémoire. * BRANCH désigne une instruction de branchement. Cette terminologie permettra de définir des paires d'instructions, qu'elles soient possibles ou interdites. Par exemple, l'expression INT + FLOAT désigne une paire d'instruction regroupant une instruction entière et une instruction flottante, peu importe l'ordre des deux instructions. Il en sera de même pour les triplets, quadruplets, ou plus. Par exemple, l'expression INT + INT + FLOAT + MUL désigne un quadruplet d'instructions regroupant : deux instructions entières, une instruction flottante, et une multiplication entière. J'utiliserais aussi des parenthèses pour regrouper certaines combinaisons. Par exemple, l'expression INT + (MUL /FLOAT) veut parler d'une paire d'instruction où la première est une opération entière basique, la seconde est : soit une multiplication, soit une opération flottante. ==Le ''front-end'' des processeurs superscalaires== Un processeur superscalaire doit charger, décoder, et exécuter plusieurs instructions par cycle. Par exemple, un processeur triple émission doit charger 3 instructions lors d'un premier cycle, puis les décoder au cycle suivant, et les exécuter au cycle suivant. Il travaille donc pas paquets de 3 instructions, dans cet exemple. Intuitivement, charger, décoder et exécuter plusieurs instructions à la fois demande de dupliquer des circuits. Par exemple, il faut dupliquer les décodeurs pour décoder plusieurs instructions. Les unités de calcul sont aussi dupliquées, sauf sur certaines processeurs qui arrivent à ruser, mais laissons cela de côté pour plus tard. Mais d'autres circuits sont simplement modifiés, comme l'unité de chargement ou l'unité d'émission. Voyons ce qu'il en est dans le détail. {|class="wikitable" |- ! rowspan="2" | Processeur sans émission multiple | rowspan="2" | Chargement | rowspan="2" | Décodage | rowspan="2" | Renommage | rowspan="2" | Émission | Exécution / ALU | rowspan="2" | ''Commit''/ROB |- | Exécution / ALU |- ! rowspan="4" | Processeur superscalaire | rowspan="4" | Chargement | rowspan="2" | Décodage | rowspan="4" | Renommage | rowspan="4" | Émission | Exécution / ALU | rowspan="4" | ''Commit''/ROB |- | Exécution / ALU |- | rowspan="2" | Décodage | Exécution / ALU |- | Exécution / ALU |} Nous allons d'abord voir ce qu'il en est pour le ''front-end'', à savoir la partie du CPU qui s'occupe de charger et décoder les instructions. Elle regroupe l'unité de chargement, les décodeurs, l'unité de prédiction de branchement, la file de µops, la file d'instruction, et divers caches. En soi, les changements portent surtout sur l'unité de chargement et les décodeurs. ===Le chargement de plusieurs instructions consécutives=== Un processeur superscalaire charge plusieurs instructions en même temps, ''en tenant compte de la présence de branchements''. La partie en italique est importante et elle a une conséquence très forte : la manière de faire est fortement différente entre un CPU superscalaire étroit et large. Un CPU superscalaire étroit fait au plus simple : il charge un bloc de 2, 3, 4 instructions consécutives en mémoire RAM. Il faut alors doubler/tripler/quadrupler la taille du bus connecté au cache d'instruction. Par exemple, pour charger quatre instructions de 32 bits chacune, il suffit de lire un bloc de 128 bits dans le cache d'instruction. Un bloc de 8, 16, 32 octets est donc lu depuis le cache et est ensuite découpé en instructions, envoyées chacun à un décodeur. Charger plusieurs instructions consécutives ne marche bien qu'en absence de branchements. La présence de branchements casse cette dynamique, et il faut gérer leur présence. * Sans prédiction de branchement, toutes les instructions d'un bloc sont décodées et exécutées en même temps, même s'il y a un branchement dans le tas. * Avec prédiction de branchement, les instructions situées après le branchement ne sont pas décodées ni exécutées. L'unité de chargement coupe le bloc chargé au niveau du premier branchement pris, remplit les vides avec des NOP, avant d'envoyer le tout à l'unité de décodage. Les instructions à l'adresse de destination seront chargées au cycle suivant, en chargeant un nouveau bloc. [[File:Fetch sur un processeur superscalaire avec prediction de branchements.png|centre|vignette|upright=2|Fetch sur un processeur superscalaire avec prédiction de branchements.]] Un autre défaut est que sur le CPU charge un bloc de 128 bits à la fois, les instructions doivent être alignées sur 128 bits. Et on peut généraliser à n'importe quelle taille de bloc. Si les instructions ne sont pas alignées, il faut gérer la situation. Il arrive qu'une instruction soit à cheval sur deux blocs, avec un morceau dans le premier bloc, un autre dans le second. Dans ce cas, l'instruction est chargée en deux fois, et des circuits doivent gérer la situation pour recoller les morceaux. Sur les CPU avec des instructions de taille fixe, le problème ne survient que rarement. Mais il est fréquent avec des instructions de taille variable. Un autre défaut est lié aux branchements. Imaginez qu'un branchement s'exécute et branche vers une instruction de destination. Rien n'indique que l'instruction de destination sera au tout début d'un bloc. Si ça se trouve, elle sera en troisième position d'un bloc de 4 instructions. Une solution simple charge le bloc en respectant l'alignement, et les deux premières instructions du bloc ne sont pas à exécuter. Il y a donc une petite perte de performance. Certains CPU, comme l'IBM RS/6000, ont la machinerie charger un bloc de N instructions même s'il n'est pas aligné. La machinerie en question se trouve dans le cache d'instruction et est tout sauf simple à expliquer,ce qui fait que je me permets de la passer sous silence. Un détail, cependant : elle ne permet pas de charger des blocs qui sont à cheval sur deux lignes de cache. ===Le décodage parallèle des instructions=== Pour le décodage des instructions, plusieurs instructions sont décodées en même temps. On parle alors de '''décodage parallèle'''. Pour ce faire, un processeur superscalaire contient plusieurs décodeurs, chacun pouvant décoder une instruction en parallèle des autres. Prenons par exemple un processeur superscalaire à 4 voies : le décodage se fera dans quatre décodeurs séparés, qui fonctionneront en parallèle. Cependant, cette implémentation ne marche bien que pour les processeurs RISC, les processeurs CISC devant faire avec un microcode gourmand en circuits. Les processeurs CISC utilisent des décodeurs hybrides, avec un microcode qui complémente un décodeur câblé. Dupliquer le microcode aurait un cout en transistors trop important, ce qui fait que seuls les décodeurs câblés sont dupliqués. Les CPU CISC superscalaires disposent donc de plusieurs décodeurs simples, capables de décoder les instructions les plus courantes, avec un seul microcode. La conséquence est qu'il n'est pas possible de décoder deux instructions microcodées en même temps. Par contre, il reste possible de décoder plusieurs instructions non-microcodées ou une instruction microcodée couplée à une instruction non-microcodée. Vu qu'il est rare que deux instructions microcodées se suivent dans un programme, le cout en performance est extrêmement mineur. Il faut noter qu'une instruction correspond à une ou plusieurs µops. Il est donc possible de charger 5 instructions et de se retrouver avec 8 µops en sortie des décodeurs. Et c'est la source d'une distinction très importante. Plus haut, j'ai parlé de processeurs superscalaires à N voies. La terminologie peut avoir deux interprétations. La première est que le processeur charge/décode N instructions à la fois. Une seconde est qu'il est capable d'exécuter/émettre N µops en même temps. Les deux ne sont pas équivalentes, vu qu'un processeur superscalaire peut, par exemple, charger/décoder N instructions et émettre/exécuter N+2 µops. La distinction est particulièrement importante sur les processeurs superscalaires larges. Certains d'entre eux sont capables de charger 5 instructions et d'en émettre le double ! Un exemple classique était les processeurs Athlon d'AMD, qui pouvaient décoder deux instructions par cycle, mais émettre 4 µops par cycle. Ce n'était pas un problème, car beaucoup d'instructions, même basiques, étaient décodées en 2 µops. ===L'émission parallèle des instructions=== La superscalarité modifie en profondeur l'unité d'émission, notamment au niveau de son interface. Avant, elle recevait une micro-opération sur son entrée, et fournissait la micro-opération émise sur une sortie. Et cela vaut aussi bien pour une unité d'émission simple, un ''scoreboard'', une fenêtre d'instruction ou des stations de réservation. Mais avec l'émission multiple, les sorties et entrées sont dupliquées. Pour la double émission, il y a deux entrées vu qu'elle doit recevoir deux micro-opération décodées/renommées, et deux sorties pour émettre deux micro-opérations. Dans ce qui suit, nous ne parlerons pas d'entrée et de sortie, mais de '''ports de décodage''' et de '''ports d'émission'''. Les ports de décodage reçoivent des micro-opérations provenant de l'unité de décodage, les ports d'émissions sont pour les micro-opérations émises. Les ports d'émission injectent les micro-opérations émises dans le pipeline, elle sont propagée au banc de registre, aux ALUs, etc. Un processeur superscalaire a forcément plusieurs ports d'émission (et de décodage). Si l'interface de l'unité d'émission change, l'intérieur est tout autant modifié. Sur un CPU non-superscalaire, l'unité d'émission reçoit une µop "candidate à l'émission", et détecte les dépendances entre avec les µops en vol, en cours d'exécution dans le pipeline. Et c'est toujours le cas sur les processeurs superscalaires : l'unité d'émission détecte les dépendances entre les µops candidates et les µops en vol. Mais avec la siperscalarité, elle doit en plus tester les dépendances entre les µops candidates. Si l'unité d'émission est un ''scoreboard'', il doit détecter les dépendances entre instructions émises simultanément et les sérialiser si besoin. Avec une fenêtre d'instruction, il n'y a pour ainsi dire rien à faire, vu que les dépendances sont éliminées par le renommage de registre et que les signaux de réveil gèrent les dépendances RAW. C'est la raison pour laquelle les processeurs superscalaires modernes n'utilisent pas de ''scoreboard''. ==Les processeurs implémentés avec plusieurs pipelines== Nous venons de voir comment la superscalarité impacte le ''front-end'', il est maintenant de voir ce qu'il en est de l'unité d'émission et des ''back-ends''. Un processeur superscalaire peut utiliser l'exécution dans le désordre et le renommage de registres, mais ce n'est pas une obligation. Historiquement, les premiers processeurs superscalaires n'utilisaient pas d'exécution dans le désordre. Le meilleur exemple est celui du Pentium 1 et 2 d'Intel. Ils étaient tous deux des processeurs superscalaires, mais n'avaient pas d'exécution dans le désordre, qui est apparue sur le Pentium 3. les explications sont plus simples si on met de côté l'exécution dans le désordre. Vous vous dites que l'implémentation d'un processeur superscalaire n'est pas la même suivant que le CPU utilise l'exécution dans le désordre, ou non. Mais dans les faits, la distinction est plus subtile que ca. Pour l'expliquer, je vais utiliser une classification originale, qui sépare les processeurs superscalaires en trois sous-types. La distinction se fait sur la manière dont la superscalarité est implémentée en matériel.La classification que j'utilise distingue : * Les processeurs avec plusieurs pipelines, sans exécution dans le désordre. * Les processeurs superscalaires avec un pipeline dynamique. * Les processeurs avec des fenêtres d'instruction décentralisées. La distinction n'est pas qu'une question d'implémentation matérielle. Elle impacte aussi le ratio entre instructions chargées/décodées et instructions émises. Vous vous dites que les deux sont égales, mais ce n'est le cas que sur les processeurs avec N pipelines ! Sur les autres processeurs, il est possible de décoder 4 instructions, mais d'en émettre maximum 6 par cycle ! Et sur les CPU avec des fenêtres d'instruction décentralisées, il y a aussi une différence entre le nombre d'instructions qui sont ''dispatch'' et ''schedule''. La catégorie la plus simple est celle des '''processeurs avec plusieurs pipelines'''. Ce sont des processeurs superscalaires sans exécution dans le désordre. Ils partent d'un processeur non-superscalaire, et ajoutent un second pipeline, en sortie de l'unité d'émission. Le second pipeline permet l'émission parallèle d'une seconde instruction. Le second pipeline n'est pas identique au pipeline originel, sur plusieurs points. Premièrement, il n'a pas exactement les mêmes circuits. Le second pipeline est généralement composé d'une unité de calcul, d'une FPU, éventuellement d'un second banc de registres, guère plus. Il permet d'exécuter une seconde opération de calcul, pas plus, ce qui est à l'origine de contraintes d'appariemment assez fortes. De plus, il n'a pas forcément la même longueur, il peut avoir des étages en moins ou en plus. Il s'agit de la forme la plus simple de processeur superscalaire et nous allons leur consacrer cette section. ===La double émission entière-flottante=== L'exemple le plus simple utilise un second pipeline pour les opérations flottantes, séparé du pipeline originel. L'ajout d'un pipeline flottant permet d'émettre une micro-opération entière en même temps qu'une micro-opération flottante, ce qui s'appelle la '''double émission entière-flottante'''. La micro-opération entière s'exécute dans l'ALU entière, la micro-opération flottante dans la FPU. Les exemples les plus notables sont le PA-RISC 7100, le POWER 1 (RIOS .& et .9) et le PowerPC 601. L'Apollo DN10000 et l'Intel i860 utilisaient aussi cette forme de double émission, mais étaient des prototypes de CPU VLIW. [[File:Double émission entière-flottante.png|centre|vignette|upright=2|Double émission entière-flottante]] Pour bien expliquer les choses, prenons le processeur illustré ci-dessous, avec deux ''back-end'' séparés : un pour la FPU, et un autre pour le reste. Les instructions entières et mémoire passent toutes par le ''back-end'' généraliste. [[File:CPU non-superscalaire basique, avec une FPU.png|centre|vignette|upright=1.5|CPU non-superscalaire basique, avec une FPU]] Les deux ''back-end'' sont des pipelines séparés, mais il faut que l'unité d'émission soit spécialement conçue pour en profiter. Un processeur simple émission peut démarrer une seule µop par cycle : soit la µop est envoyée au ''back-end'' flottant, soit elle est envoyée au ''back-end'' général. Avec la double émission entière-flottante, le processeur peut envoyer une µop dans chaque pipeline, à chaque cycle. Vu que la FPU et son banc de registre forment déjà un pipeline séparé, les seules modifications se font au niveau du ''front-end'', unité d'émission inclue. {| |- |[[File:CPU simple émission avec une FPU.png|centre|vignette|upright=1.5|CPU simple émission avec une FPU.]] |[[File:Double émission entière flottante avec un second pipeline.png|centre|vignette|upright=1.5|CPU avec un second pipeline flottant.]] |} Notez que le terme double émission ''entière''-flottante est un peu trompeur. En réalité, la technique permet d'émettre une instruction flottante avec n'importe quelle autre instruction non-flottante, avec des restrictions pour les branchements. Il est par exemple possible d'exécuter un calcul flottant en parallèle d'un accès mémoire. Sur les CPU 32 bits, il est possible de faire les multiplications dans la FPU. Pour rappel, la FPU contient un multiplieur entier 53-54 bits, pour multiplier les mantisses de deux flottants 64 bits, qu'on peut utiliser pour faire les multiplications entières 32 bits. Cela permet d'émettre une multiplication en même temps qu'une opération entière dans l'ALU. Ainsi, le CPU pouvait émettre deux instructions : une opération entière de base, avec soit une multiplication, soit une opération flottante. En clair, une émission de type INT + (MUL /FLOAT). Par contre, on perd la possibilité d'émettre une multiplication entière avec une opération flottante, mais c'était un compromis qui en valait la peine. Le CPU PA-RISC 7100 implémentait cette optimisation. Il s'agissait d'un CPU double émission entière-flottante, qui utilisait donc un partitionnement assez simple. Le combo partitionnement + reconditionnement permettait ici de gagner en performance, pour un cout en matériel très limité. D’autant plus limité que le jeu d'instruction prévoyait le reconditionnement de la FPU. Les multiplications ne lisaient pas leurs opérandes dans les registres entiers, mais dans les registres flottants. ===La double émission entière=== Une autre possibilité ajoute un second pipeline, capable d'exécuter des opérations entières simples, appelé le pipeline entier. Le processeur supporte alors la '''double émission entière''', à savoir qu'ils peuvent émettre une opération entière en plus d'une autre instruction. L'opération entière en question est une opération simple, comme une addition, une soustraction, une opération bit à bit, une comparaison, etc. Les exemples classiques sont les processeurs Intel i960 et Pentium. L'implémentation était différente entre l'i960 et le Pentium : Le Pentium avait une seconde ALU entière, alors que le i960 rusait. Le Pentium dupliquait l'ALU entière, pour créer un second pipeline entier. Sur le Pentium originel, les contraintes d'appariement étaient assez strictes. Le Pentium avait une FPU, mais ne pouvait pas l'utiliser en double émission. Il lui était impossible d'exécuter une opération flottante en même temps qu'une opération entière (branchements inclus). Le Cyrix 6x86 était une sorte de Pentium produit par l'entreprise Cyrix, avec une microarchitecture différente. Il avait des contraintes d’appariement réduites. Par exemple, il pouvait émettre une opération flottante en parallèle d'une opération entière. Nous détaillerons ces processeurs dans le prochain chapitre, qui porte justement sur les CPU superscalaires x86. [[File:CPU simple et double émission avec un second pipeline entier.png|centre|vignette|upright=2.5|CPU simple et double émission avec un second pipeline entier.]] L'Intel i960 n'avait pas de FPU, juste une ALU entière et une unité mémoire. Il utilisait l'unité mémoire comme une seconde ALU entière limitée. Pour rappel, une unité mémoire contient une AGU, à savoir une ALU spécialisée dans les calculs d'adresse. Elle est capable de faire des additions, soustractions, et quelques décalages limités (des décalages par 2, 4, 8, pas plus). L'Intel i960 ajoutait des connexions entre AGU et banc de registre, afin que celle-ci serve de seconde unité de calcul. L'AGU est déjà reliée aux deux ports de lecture, pour lire les opérandes du calcul d'adresse, il suffit de la connecter sur le port d'écriture. Le processeur peut alors émettre deux instructions/µops entières, sous certaines conditions. [[File:Emission multiple des opérations entières, double émission.png|centre|vignette|upright=2|Émission multiple des opérations entières, double émission.]] ==Les processeurs superscalaires dynamiques== Les processeurs précédents, avec 2 pipelines, avaient une unité d'émission assez simple. Elle avait deux ports de décodage, et deux ports d'émission. Le nombre de ports de décodage et d'émission étaient identiques. Vous vous dites que c'est naturel, et intuitif, que c'est normal pour un processeur superscalaire. Mais en réalité, la majorité des processeurs superscalaires a plus de ports d'émission que de décodage. Expliquer pourquoi va cependant nous demander de parler de comment la superscalarité exploite les unités de calcul. Posons-nous cette question : de combien d'unités un processeur superscalaire a besoin ? Les unités en question ne sont nécessairement des unité de calcul, l'unité mémoire et l'unité de branchement comptent aussi. La réponse dépend du nombre de voies. Si le ''front-end'' charge et décode un paquet de N instructions, exécuter ces N instructions simultanément demandera là aussi au moins N unités de calcul. Notez le terme : ''au moins'', car c'est là tout l'enjeu de cette section. La réponse dépend aussi des contraintes d’appariement. Pour un CPU superscalaire "parfait", sans contraintes d’appariement, toutes les unités doivent être dupliquées, y compris l'unité mémoire. Un processeur à double émission parfaite devrait dupliquer l'ALU entière, le ''barrel shifter'', le circuit multiplieur, la FPU, l'unité mémoire, et l'unité de branchement, ce qui ferait passer de 6 unités à 12. Le cout en circuit est énorme, même pour de la double émission ! Et pour un CPU à N pipelines, le cout en circuit encore plus prohibitif ! En pratique, les processeurs superscalaires qui font ça sont très rares. La majeure partie des processeurs superscalaires utilisent d'autres techniques : le partitionnement, la duplication partielle des ALUs, la duplication des autres unités. ===Le partitionnement des unités fonctionnelles=== Les CPU superscalaires les plus simples ne dupliquent pas leurs unités de calcul, ils se contentent d'exploiter les unités existantes. Et pour expliquer comment, nous allons partir de l'exemple d'un processeur non-superscalaire contient une ALU entière, une FPU, une unité mémoire et une unité de branchement. Les quatre unités peuvent fonctionner en parallèle, à savoir qu'une unité peut exécuter une instruction à elle toute seule, sans interagir avec les autres. Cela suffit pour créer un processeur quadruple émission, qui peut émettre en même temps : une opération entière, une opération flottante, un branchement, un accès mémoire. Le principe marche aussi sur un processeur avec moins de 4 unités, par exemple sans FPU et/ou sans unité de branchement, ou avec au contraire des unités en plus. L'idée générale est d’émettre une µops séparée dans chaque unité fonctionnelle. Utiliser ainsi les unités existantes, pour rendre un CPU superscalaire, s'appelle le '''partitionnement superscalaire'''. Le terme n'est pas beaucoup utilisé en-dehors de quelques vieux articles académiques. Le partitionnement est fortement dépendant du jeu d'instruction, car il demande de bien séparer instructions entières, flottantes, branchements et instructions mémoire. Et autant c'est bien le cas sur les CPU RISC, autant les CPU CISC ne sont pas dans ce cas. Les CPU CISC ont des instructions ''load-op'', qui effectuent une instruction arithmétique et une µop mémoire. Et ces instructions sont très compliquées à implémenter avec le partitionnement. Et au-delà de ça, les CPU CISC ont beaucoup d'instructions qui sont décodées en plusieurs µops, à exécuter dans un ordre bien précis. On peut pas émettre les µops en question simultanément. Le partitionnement a un cout en circuit très limité, pour un gain en performance lui aussi limité. Pour reprendre le CPU de l'exemple précédent, il est rare que l'on ait à exécuter ces quatre instructions en même temps. En pratique, les combinaisons fréquentes sont les combinaisons calcul + accès mémoire, calcul + branchement, calcul entier + calcul flottant. Utiliser une quadruple émission de ce type n'est donc pas une bonne idée, alors que la double émission parait plus adaptée. Et c'est là que la différence entre ports d'émission et de décodage devient intéressante. L'idée est d'avoir une unité d'émission avec deux ports de décodage, et 4 port d'émission (un par unité). Les contraintes d’appariement varient alors suivant le processeur, mais la règle générale est que si deux micro-opérations tombent dans deux unités différentes, on peut les émettre en même temps. En clair, on peut émettre une opération entière avec une opération flottante, une opération entière avec un branchement, un accès mémoire avec un branchement, etc. Et cela rend le partitionnement bien plus intéressant que prévu. Les tout premiers CPU superscalaires étaient des CPU double émission, qui utilisaient le partitionnement avec : une ALU entière, une unité mémoire et une FPU. Nous parlerons de '''partitionnement INT-FLOAT-MEM'''. L'avantage sur la double émission flottante est que les combinaisons INT + MEM et FLOAT + MEM étaient autorisées. L'intérêt était d'émettre une opération en parallèle d'un accès mémoire, ce qui est un doublet d'instruction assez fréquent. Les exemples classiques sont l'HyperSPARC, de l'alpha 21064 et du PowerPC 603. Les contraintes d’appariement variaient grandement suivant le processeur. Le PowerPC 603 implémentait une double émission "parfaite". C'est à dire que toutes les combinaisons possibles à deux instructions étaient possibles, que ce soit INT + FLOAT, INT + MEM, FLOAT + MEM. En plus de cette double émission "parfaite", le CPU utilisait la technique dite de ''branch folding'', qui permettait d'exécuter un branchement sans utiliser d'unité de calcul, ce qui fait qu'il était qualifié de ''CPU 2-way + Branch''. A l'opposé, le processeur alpha 21064 avait pas mal de contraintes d’appariement, à savoir que certaines combinaisons de deux instructions étaient interdites, bien que compatibles avec les unités disponibles. Une des raisons est que le banc de registre n'avait pas assez de ports de lectures, ce qui fait que certaines combinaisons avec une écriture mémoire n'étaient pas permises. Les concepteurs avaient volontairement inséré ces contraintes afin de simplifier le design du processeur. Avant de poursuivre, précisons que la FPU regroupe un additionneur flottant et un multiplieur flottant. Et ce fait peut être exploité pour approfondir le partitionnement. La '''double émission FADD/FMUL''' permet d'émettre une addition flottante en même temps qu'une multiplication flottante. Elle a été utilisée sur les processeurs x86, Alpha et SPARC. Les autres circuits de calcul flottant sont souvent regroupés avec l'additionneur. La division est parfois e même port que le multiplieur flottant, parfois sur celui de l'additionneur (quand elle est microcodée et exécutée en enchainant les soustractions). ===La duplication des ALU entières=== Le partitionnement est une technique élégante, simple, facile à implémenter et au cout en circuits modeste. Mais il faut avouer que cela ne donne pas des CPU superscalaires très efficaces. La raison est que le partitionnement permet d'émettre des paires, triplets, quadruplets de micro-opérations qui sont rares en pratique. Les combinaisons les plus fréquentes en pratique sont des paires/triplets d'opérations entières, souvent des paires/triplets d'additions ou d'opérations entières simples. Il est donc primordial d'émettre plusieurs opérations entières simultanément. Le fait d'émettre et d'exécuter plusieurs opérations entières en même temps s'appelle l''''émission entière multiple'''. Pour exécuter la seconde/troisième opération entière, il faut dupliquer les ALU entières (ou réutiliser l'AGU comme ALU entière comme le fait l'Intel i960). Le processeur doit avoir deux ALU entières pour de la double émission entière, trois pour de triple émission entière, etc. De plus, il faut ajouter des ports au banc de registre, pour lire deux fois plus d'opérandes et décrire deux résultats. Et enfin, il faut rajouter un port d'émission pour pouvoir émettre une micro-opération entière en plus. [[File:Processeur non-superscalaire basique 03.png|centre|vignette|upright=2|Processeur non-superscalaire basique]] La tendance est de multiplier les ALU entières, mais pas le circuit multiplieur. Le cout en transistors est alors grandement réduit, au prix de l'apparition de dépendances structurelles. Le CPU peut émettre deux additions en même temps, ou une addition et une multiplication, mais pas deux multiplications. Il en est de même avec le ''barrel shifter'', qui n'est pas dupliqué. Il est donc possible d'émettre un décalage avec une opération de base, mais pas deux décalages en parallèle. Mais ces contraintes d’appariement ne sont pas un problème, car il est rare de devoir émettre deux multiplications/décalages en même temps. La plupart des programmes sont majoritairement remplis d'additions, avec des multiplications assez rares et des décalages qui le sont encore plus. La moyenne est proche d'une multiplication pour 4/5 additions. Vu que des multiplications consécutives sont très rares, dupliquer le multiplieur est presque inutile. Disposer de plusieurs circuits multiplieurs ou de plusieurs ''barrel shifters'' serait donc un cout en circuits qui ne servirait que rarement et n'en vaut pas la chandelle. Il faut noter que sur les CPU 32 bits, les multiplications peuvent être exécutées dans la FPU ou dans un multiplieur séparé, tout dépend de si le CPU exécute les multiplications dans la FPU ou pas. {|class="wikitable" |- ! Voie d'émission n°1 !! Voie d'émission n°2 !! Voie d'émission n°3 |- | ALU entière || ALU entière || FPU |} Reste à connecter les unités de calcul à l'unité d'émission (indirectement, car il y a des étages de pipeline entre les deux). Une implémentation basique utilisee un port d'émission par unité. Prenons l'exemple d'un CPU superscalaire avec 4 ALU entières, une FPU, et une unité mémoire. Vu qu'il y a 6 unités, cela fait donc 6 ports d'émission. L'exemple historique le plus impressionnant est celui du Motorola 88110, un CPU double émission qui possède 10 unités. Il contient deux ALU entières, un circuit multiplieur, un circuit diviseur, une unité de manipulation de bits, une unité mémoire, un additionneur flottant, et deux unités graphiques pour des calculs impliquant des pixels. Le multiplieur est utilisé à la fois pour les multiplications entières, flottantes et le produit de deux pixels. Idem pour le diviseur, qui sert pour les divisions entières et flottantes. Et chaque unité a son propre port d'émission. La conséquence est que les contraintes d'appariement du Motorola 88110 sont simples : tant que deux µops atterrissent dans des unités séparées, la paire est permise. Il est par exemple possible d'émettre simultanément deux opérations entières simples, vu qu'il y a deux ALU. Par contre, impossible d'émettre simultanément deux multiplications, deux divisions, deux opérations flottantes, deux manipulations de bit, deux accès mémoire, etc. De plus, le partage du multiplieur fait qu'on ne peut pas émettre une multiplication entière avec une multiplication flottante. Idem avec les divisions, vu que le diviseur est aussi partagé. Ce sont les seules contraintes. ===Le partage des ports d'émission=== La présence d'un circuit multiplieur et d'un ''barrel shifter'' pose des problèmes pour le partitionnement. Il est possible d'avoir un port d'émission dédié au multiplieur, et un autre au ''barrel shifter'', qui sont séparés de ceux pour l'ALU entière. Il est alors possible d'émettre une opération entière, une multiplication et un décalage, simultanément. Nous parlerons de '''partitionnement entier''' pour désigner cette possibilité. [[File:Partitionnement superscalaire entier.png|centre|vignette|upright=2|Partitionnement superscalaire entier.]] Le partitionnement entier est cependant très rare, car le gain en performance serait trop faible au regard des couts en circuit. Quelques processeurs historiques l'utilisaient, mais ce n'est plus le cas de nos jours. La raison est que les multiplications et les décalages sont assez rares dans le code, ce qui fait que le port d'émission associés sont sous-utilisés. Et un port d'émission a un cout en circuit assez important. Et c'est sans compter qu'ajouter des ports sur le banc de registres juste pour un multiplier sous-utilisé n'en vaut pas la peine. L'idéal serait de trouver un moyen de réduire le nombre de ports d'émissions, sans réduire le nombre d'unités de calcul. [[File:Emission multiple des opérations entières, implémentation naive.png|vignette|Émission multiple des opérations entières, implémentation naive]] La solution consiste à '''partager les ports d'émission''', à savoir connecter un port d'émission à plusieurs unités de calcul. L'exemple classique est justement lié aux circuits multiplieur et ''barrel shifters''. L'idée est que le multiplieur et le ''barrel shifter'' sont mis sur le même port d'émission qu'une ALU entière. Prenons par exemple le processeur à double émission entière illustré ci-contre. Un port d'émission est relié à une ALU entière seule, et l'autre port d'émission alimente la seconde ALU entière et le multiplieur. La conséquence est que le processeur peut émettre deux opérations entières, dont une multiplication. Si on avait utilisé un port d'émission dédié au multiplieur, il aurait été possible d'émettre deux opérations entières et une multiplication. On perd donc une voie, mais on gagne en flexibilité et on économise des circuits. Notons que ce partage signifie aussi que le multiplieur partage les ports sur le banc de registre, ce qui fait une double économie. [[File:Emission multiple des opérations entières, implémentation courante.png|centre|vignette|upright=1.5|Émission multiple des opérations entières, implémentation courante]] En général, le ''barrel shifter'' et le multiplieur sont placés sur deux voies d'émission séparés, ce qui permet d'émettre un décalage et une multiplication en même temps. Les mettre sur le même port ne le permettrait pas. {|class="wikitable" |- ! Voie d'émission n°1 !! Voie d'émission n°2 !! Voie d'émission n°3 |- | * ALU entière * Multiplieur | * ALU entière * Barrel shifter | FPU |- | * Additions, soustractions, opérations bit à bit, etc. * Multiplication | * Additions, soustractions, opérations bit à bit, etc. * Décalages et rotations | Opération flottante |} Mais le partage des ports d'émission peut appliquer à n'importe quelle unité. Prenons l'exemple d'un CPU superscalaire avec 4 ALU entières, une FPU, et une unité mémoire. Vu qu'il y a 6 unités, cela fait donc 6 ports d'émission. Cependant, il est rare qu'un bloc de 6 instructions contienne exactement 4 opérations entières, une opération flottante, et un accès mémoire. Beaucoup de ports d'émission seraient sous-utilisés. La solution est de partager des ports d'émission, pour passer à la quadruple émission. Le CPU peut alors répartir 4 instructions sur les ports d'émission adéquats, à chaque cycle. Le CPU a donc 4 ports de décodage et 6 ports d'émission. Une implémentation classique permet n'importe quelle combinaison de 4 instructions compatible avec les 6 unités. Par exemple, il devient possible d'émettre simultanément 4 additions, la combinaison 2 * INT + FLOAT + MEM, 3 * INT + MEM, et bien d'autres. L'exemple relie une ALU entière, la FPU et l'unité mémoire sur le même port. Le CPU a alors quatre ports d'émission, qui ne sont pas "identiques". Trois d'entre eux sont reliés à une ALU entière chacun, rien d'autre. Le quatrième est lui relié à une ALU entière, la FPU et l'unité mémoire. On économise alors deux ports d'émission, mais cela entraine l'apparition de contraintes d’appariement. Le CPU peut émettre 3 opérations entières + n'importe quelle autre opération. Mais les autres combinaisons sont interdites. Le CPU peut émettre 6 opérations entières simultanément, grâce à 6 ALU entières. Les accès mémoire ou opérations flottantes ne viennent pas en plus, mais en remplacement des opérations entières. Regrouper plusieurs ALU sur un même port d'émission est donc à l'origine de dépendances structurelles, de nouvelles contraintes d’appariement. Dans certaines situations de '''''port contention''''', on a assez d'ALU pour exécuter N micro-opérations, mais la répartition des ALUs sur les ports l'empêche. Impossible d'émettre deux micro-opérations sur deux ALU si elles sont sur le même port d'émission. On peut réduire les cas de ''port contention'' en répartissant correctement les ALU sur les ports d'émission, mais sans jamais les supprimer complétement. Un exemple est celui du PowerPC 604. Il implémentait la quadruple émission, alors qu'il avait 5 unités de calcul. Mais deux unités partageaient un même port d'émission. Dans le détail, il avait deux ALU entières, un circuit multiplieur/diviseur, une FPU, une unité mémoire. La FPU et le multiplieur étaient sur le même port d'émission. En conséquence, le CPU pouvait émettre seulement les combinaisons INT + INT + MEM + (MUL/FLOAT). : Il est intéressant de voir l'exemple d'un processeur superscalaire parfait, sans contraintes d'appariement, à N voies. Pour cela, on part d'un processeur non-superscalaire, avec M unités de calcul. Un tel processeur peut s'implémenter avec N ports d'émission, chacun relié à M unités de calcul possibles, ce qui fait que les M unités de calcul sont dupliquées en N exemplaires. Si on prend par exemple un CPU non-superscalaire avec une ALU, une FPU, une unité mémoire et une unité de branchement, un port d'émission sera relié à une ALU, une FPU, une unité mémoire et une unité de branchement. ===La duplication des autres unités fonctionnelles=== Dupliquer les ALU entière est une première étape, mais il est aussi possible de dupliquer les autres unités fonctionnelles. Par exemple, il est possible de dupliquer l'unité mémoire ou l'unité de branchement. Reste à voir quelles unités dupliquer, et pourquoi. Précisons que dans ce qui va suivre, nous allons parler de la duplication des unités en général, pas seulement des unités mémoire/branchement. Nous allons continuer de parler de la duplication des ALU entières dans ce qui suit, mais de manière plus générale. Pour un processeur à N voies, il est théoriquement possible de dupliquer toutes les unités en N exemplaires. Mais ce n'est jamais fait en pratique, car le cout en circuits serait trop important et le gain en performance trop faible. Mieux vaut dupliquer certaines unités bien précises, quitte à avoir des contraintes d'appariement. Et à ce petit jeu, toutes les duplications ne se valent pas, certaines sont plus rentables en matière de performance, le cout en circuit n'est pas le même selon l'unité, etc. La rentabilité en performance dépend surtout des contraintes d'appariement. Les contraintes d'appariement entrainent une baisse de performance significatives si elles interdisent des paires d'instructions fréquentes. Aussi, les opérations très fréquentes, souvent appairées, gagnent à dupliquer l'unité associée. Et au contraire, les unités rarement utilisées ne doivent pas être dupliquées. Il se trouve que les unités les plus utilisées sont les ALU entières, suivies par les unités mémoire et l'unité de branchement. Le cout en circuit n'est aussi pas le même. Dupliquer les ALU entière est trivial, car elles ont un budget en transistors assez faible. Les circuits multiplieurs, diviseurs, et les ''barrel shifter'' ont eux un cout en transistor plus élevés. Si on devait faire un classement, on aurait : ALU entière < ''barrel shifter'' < multiplieur. Pour les unités mémoire, le cout en transistor est là aussi conséquent. Le résultat final est que dupliquer les ALU entières est privilégié sur le reste, comme l'a vu dans la section précédente. Les unités mémoire arrivent en seconde place, suivie par les multiplieurs/''barrel shifter'' à la troisième place, et les unités de branchement à la quatrième place. Les CPU superscalaires larges ont donc tendance à multiplier les unités mémoire en second. Les multiplieurs et ''barrel shifters'' sont eux aussi dupliqués sur les CPU superscalaires larges, mais cela vient souvent après avoir dupliqué les unités mémoire. La raison est que les accès mémoire sont critiques pour la performance des CPU à exécution dans le désordre, pas les multiplications. Pour les unités de branchement, une subtilité complique le fait d'en installer plusieurs. Que faire quand deux branchements sont pris ? La solution la plus simple est de ne pas se trouver dans cette situation : on n'utilise qu'une seule unité de branchement et on n’exécute qu'un seul branchement par cycle. La majorité des processeurs superscalaires étroits font ainsi. Les processeurs superscalaires larges ont cependant deux unités de branchements, mais ils n'acceptent qu'un seul branchement pris à la fois. Si les deux branchements pris sont exécutés en même temps, le processeur n’exécute que le plus récent, dans l'ordre du programme. ===Les CPU superscalaires mélangent les trois techniques=== Les CPU superscalaires utilisent toutes les techniques à la fois : partitionnement, duplication de l'ALU entière, duplication des autre unités fonctionnelles. Chaque processeur utilise ces trois stratégies à sa sauce, en fonction de son budget en transistor. Les quatre techniques ont cependant un cout en circuit différent, et donnent des gains de performances eux aussi forts différents. * Le '''partitionnement superscalaire''' a un cout en circuit modéré, car on n'a pas besoin de dupliquer d'ALU. * La '''duplication des ALU entières''' est la technique la plus importante : elle a un cout en circuit modéré, et donne des gains en performance impressionnants. * La '''duplication des autres unités''' a un cout en circuit variable, pour des gains en performance eux aussi variables. * Le '''partage des ports d'émission''' réduit le cout en circuit du CPU, pour des pertes de performances mineures si bien fait. L'usage du partitionnement seul, sans duplication, était surtout le fait des processeurs commercialisés entre les années 1989 et 1993. Le partage des ports d'émission a lui aussi mis un peu de temps pour s'imposer. Globalement, les processeurs superscalaires ont évolués progressivement, pour se rapprocher d'une sorte de standard. Mais pour comprendre ce qu'est ce standard, et en quoi les CPU historiques s'en sont progressivement rapprochés, il faut étudier l'histoire des CPU superscalaires. L'histoire des CPU superscalaires a commencé dès 1960, mais est resté un projet de recherche pendant plusieurs décennies. De nombreux designs ont été conçus, mais abandonnés avant la production, comme l'ACS-1 d'IBM. Les années 1980 ont vu l'arrivée de prototypes de processeurs VLIW, mais qui n'étaient pas superscalaires. Par exemple, l'Apollo DN10000 et l'Intel i860 pouvaient émettre deux instructions à la fois, sous conditions, mais demandaient que le compilateur fasse une bonne partie du travail. Les premiers CPU superscalaires commercialisés sont sortis en 1989. Deux CPUs superscalaires sont sortis cette année là : le RS/6000 d'IBM et l'i960CA d'Intel. Puis, la compétition s'est aussi lancée dans la bataille. Les CPU commercialisés avant l'année 1993 étaient à double émission, à l'exception du SuperSPARC qui utilisait la triple émission. Les modèles après 1993 hésitaient entre double et quadruple émission. Dans le tableau ci-dessous, les processeurs quadruple émission sont en bleu, les triple émission sont en rouge, les autres ne sont pas en couleur. {|class="wikitable" |- ! !! IBM !! HP PA-RISC !! SPARC !! Intel!! Alpha !! Autres |- ! 1989 | RS/6000 || || || i960CA || || |- ! 1990 | || || || || || |- ! 1991 | || || || || || Motorola 88110 |- ! 1992 | RSC || PA 7100 || class="f_rouge | SuperSPARC || || 21064 || |- ! 1993 | PPC 601 || || HyperSPARC || Pentium || || |- ! 1994 | class="f_bleu | POWER 2 || PA 7200 || || || class="f_bleu | 21164 || |- ! 1995 | 603/604 || class="f_bleu | PA 8000 || class="f_bleu | UltraSPARC 1 || Microarchitecture P6 (+ concurrence AMD) || || class="f_bleu | MIPS R1000 |- ! 1996 | class="f_bleu | 620 || || || || class="f_bleu | 21264 || |- ! ... || ... || ... || ... || ... || ... || ... |} Les premiers CPU à double émission sont assez représentatifs des CPU à double émission modernes. Il faut dire que se limiter à deux voies ne permet pas de faire beaucoup de choses. Voici le tableau chronologique disant quel CPU utilisait quelle technique. * Les CPU en vert utilisaient la double émission entière-flottante. * Les CPU en rouge utilisaient la double émission entière "pure". * Les CPU en jaune utilisaient un ''partitionnement complet'' entre ALU, FPU et unité mémoire. * Les CPU sans couleur utilisaient un grand nombre d'unités, avec beaucoup de ports d'émission. * Les CPU barrés utilisent la triple ou quadruple émission et seront vu dans la section suivante. {|class="wikitable" |- ! !! IBM !! HP PA-RISC !! SPARC !! Intel!! Alpha !! Autres |- ! 1989 | class="f_vert | RS/6000 || || || class="f_rouge | i960CA || || |- ! 1990 | || || || || || |- ! 1991 | || || || || || Motorola 88110 |- ! 1992 | class="f_vert | RSC || class="f_vert | PA 7100 || <s>SuperSPARC</s> || || class="f_jaune | 21064 || |- ! 1993 | class="f_vert | PPC 601 || || class="f_jaune | HyperSPARC || class="f_rouge | Pentium || || |- ! 1994 | <s>POWER 2</s> || PA 7200 || || || <s>21164</s> || |} C'est en 1994, que la quadruple émission est arrivée et que les CPU ont essayé divers implémentations. Il y avait alors trois stratégies pour la double/triple/quadruple émission, qui partageaient des problèmes similaires. La première implémentation dupliquait les ALUs et les FPUs, ce qui permettait d'émettre deux opérations flottantes avec deux opérations entières. Le POWER 2 dupliquait ses FPUs à la perfection, alors que le 21264 séparait l'additionneur et le multiplieur flottant. Les accès mémoire étaient traités comme des opérations entières et le processeur pouvait souvent en émettre deux à la fois. Mais une telle organisation n'est plus utilisée de nos jours, car, sans instructions flottantes à exécuter, elle est équivalente à de la double émission. La seconde implémentation utilisait deux ALU entières (dont un multiplieur), une unité mémoire et une FPU.Cette organisation est intéressante, mais encore trop stricte pour donner de bons résultats, les quadruplets avec exactement deux opérations entières, un accès mémoire et une opération flottante étant trop rares. L'usage de ces 4 unités en double ou triple émission a été utilisé sur le PA 7200 et le SuperSPARC. Malheureusement, en plus de problèmes au niveau de son cache, le SuperSPARC avait de nombreuses contraintes d’appariement, car le banc de registre n'avait pas assez de ports pour alimenter certaines combinaisons d'instructions. De plus, s'il émettait une multiplication, il ne pouvait pas émettre de seconde µop. Enfin, une troisième implémentation utilisait un grand nombre d'unités fort différentes, bien plus que quatre, avec au minimum deux ALU entières. Prenons l'exemple de l'UltraSPARC, qui avait : deux ALU entières, une unité mémoire, une unité de branchement, un additionneur flottant, un multiplieur flottant, un diviseur flottant (aussi utilisé pour le calcul des racines carrées), et 2 unités graphiques pour les calculs sur des pixels. En tout, cela fait 9 unités différentes, pour de la quadruple émission. Dans le même genre, les processeurs MIPS R8000 et R10000 avaient une implémentation plus moderne, dans le sens où toutes les unités étaient dupliquées. Le CPU avait deux ALU, deux FPU et deux unités mémoire. Il pouvait donc émettre quatre instructions à la fois, tant que le paquet collait avec ces six unités. Moitié opérations entières, moitié opérations mémoire, avec de quoi remplacer les opérations entières par des opérations flottantes. Le PA 8000, lui, dupliquait toutes ses unités en deux exemplaires, ce qui n'est pas l'idéal, aucun processeur moderne ne fait cela aujourd'hui. Le tout est résumé dans le tableau ci-dessous, avec : * en vert les CPU utilisant la première stratégie ; * en rouge les CPU utilisant la seconde stratégie ; * en jaune les CPU utilisant la troisième stratégie. {|class="wikitable" |+ Processeurs quadruple émission historiques |- ! Année !! Processeur !! Unités |- ! 1991 | class="f_jaune | Motorola 88110 || class="f_jaune | 2 ALU + MUL + DIV + BIT + MEM + FADD + 2 unités graphiques |- |- ! rowspan="2" | 1994 | class="f_vert | POWER 2 || class="f_vert | 2 ALU/MEM + 2 FPU (+ 2 Branchements via ''branch folding'') |- | class="f_rouge | 21164 || class="f_rouge | 2 ALU + FPU + MEM |- ! rowspan="3" | 1995 | PA 8000 || 2 ALU + 2 ''barrel shifters'' + 2 MEM + 2 FPU (FMA) + 2 FPU (DIV et SQRT) + 2 SIMD |- | class="f_jaune | UltraSPARC 1 || class="f_jaune | 2 ALU + MEM + BRANCH + FADD + FMULFDIV + 2 unités graphiques |- | MIPS R8000 et R10000 || 2 ALU + 2 FPU + 2 MEM |- ! rowspan="2" | 1996 | class="f_rouge | 620 || class="f_rouge | 2 ALU + FPU + MEM |- | class="f_vert | 21264 || class="f_vert | 2 ALU/MEM + 2 FPU (FADD + FMUL) |} Malheureusement, ces CPUs historiques n'avaient ni assez d'unités de calcul entières, ni d'unités mémoire, pour remplir la quadruple émission. Environ la moitié des instructions d'un programme correspond à des instructions entières. Pour en profiter, un processeur à N voies doit disposer d'au minimum N/2 ALU entières, l'idéal étant d'aller au-delà. Les CPU superscalaires historiques avaient 2 ALU pour de la quadruple émission, ce qui était très limite. En comparaison, les CPU superscalaires modernes mettent le paquet niveau ALU entière. La plupart des CPU x86 modernes descendent rarement sous les 2/3, la plupart remplit presque la totalité de la largeur du CPU. Pour donner un exemple, la microarchitecture Golden Cove d'Intel est un processeur à 6 voies, avec 5 ALU entières. Pour être précis, un programme informatique exécute environ 40% de son code dans des instructions arithmétiques, un autre 40% dans les accès mémoire, et un le reste pour... le reste. La conséquence est que les processeurs modernes dupliquent leurs unités mémoire. Par exemple, la microarchitecture ''Golden Cove'' d'Intel dispose de trois unités mémoire : deux spécialisées dans les écritures, trois dans les lectures. La microarchitecture ''Gracemont'' utilise la pentuple émission et peut émettre 4 µops entières et 4 µops mémoire. Notez que le ratio est équilibré entre opérations entières et mémoire, avec soit égalité, soit un avantage pour les ALU entières. Une autre régularité est que la largeur totale du processeur peut être occupée par des opérations entières/mémoire. Pour le dire autrement, sur un processeur quadruple émission, il pourra émettre un mix de 4 instructions entière/mémoire, avec zéro opération flottante. Idem avec des processeurs triples ou pentuple émission, voire plus. Si le processeur émet une opération flottante, elle remplace une opération entière. Prenons l'exemple des processeurs AMD K6, sortis en 1997, juste avant les AMD Athlon. Ils avaient 10 unités de calcul, 6 ports d'émission, 4 ports de décodage. Voici exactement comment étaient répartie les unités de calcul sur les ports d'émission. C'était des processeurs quadruple émission, ce qui fait que la moitié de la largeur du processeur sert pour des opérations entières, et l'autre moitié pour les accès mémoire. {|class="wikitable" |- ! Port d'émission n°1 !! Port d'émission n°2 !! Port d'émission n°3 !! Port d'émission n°4 !! Port d'émission n°5 !! Port d'émission n°6 ! |- | ALU entière || ALU entière || FPU || Unité de branchement || Unité LOAD || Unité STORE |- | || Multiplieur || || || || Unité de calcul d'adresse (instruction LEA) |- | || ''Barrel shifter'' || || || || Unité pour les instructions PUSH |} Les microarchitectures K7, K8 et K10, utilisées sur les anciens CPU AMD Athlon, sont un exemple moins représentatif. Ces microarchitectures utilisent le partage des ports d'émission au maximum. C'était des microarchitectures à triple émission, avec six ports d’émission. Il y avait trois ports d'émission pour les opérations flottantes, et trois ports d'émission pour les micro-opérations entières/mémoire. Il y avait trois ALU entières et trois unités mémoire, chaque ALU partageait un port d'émission avec une unité mémoire. Elle pouvait donc émettre soit trois opérations entières, soit trois µops mémoire, soit un mix des deux. En plus des mix avec les opérations flottantes et branchements. {|class="wikitable" |- ! Port d'émission n°1 !! Port d'émission n°2 !! Port d'émission n°3 !! Port d'émission n°4 !! Port d'émission n°5 !! Port d'émission n°6 ! |- | ALU entière || ALU entière || ALU entière || FADD || FMUL || FPU (autres opérations) |- | Multiplieur || || || || || |- | LOAD/STORE || LOAD/STORE || LOAD/STORE || || || |} Les CPU superscalaires larges dupliquent les multiplieurs et/ou les ''barrel shifters'', et même les unités de branchement. La raison est que quand la largeur du CPU augmente, le processeur traite des paquets d'instructions de plus en plus gros. Par exemple, pour un CPU à 10 voies, cela veut dire émettre 10 instructions en même temps. Il est alors plus fréquent d'avoir deux multiplications, deux décalages, deux accès mémoire, deux branchements. Par exemple, la moyenne est d'une multiplication tous les 5 instructions. Les CPU superscalaires à 10 voies ont donc tendance à avoir deux multiplications par paquet d'instruction, en moyenne. Avoir deux multiplieurs est alors un gain en performance clair. Par exemple, la microarchitecture ''Golden Cove'' a deux ''barrel shifters''. C'est une architecture à 6 voies. {|class="wikitable" |- ! Port d'émission n°1 !! Port d'émission n°2 !! Port d'émission n°3 !! Port d'émission n°4 !! Port d'émission n°5 !! Port d'émission n°6 !! Port d'émission n°7 !! Port d'émission n°8 !! Port d'émission n°9 !! Port d'émission n°10 |- | ALU entière || ALU entière || ALU entière || ALU entière || ALU entière || LOAD || LOAD || LOAD || STORE || STORE |- | ''Barrel shifter'' || Multiplieur || Multiplieur || ''Barrel shifter'' || || || || || || |- | Branchements || Diviseur || || Branchements || || || || || || |} ==Les µops mémoire sur les processeurs superscalaires== Plus haut, nous avons volontairement passé sous silence les accès mémoire. La raison à cela est que leur support sur les CPU superscalaire peut se faire de plusieurs manières différentes, qu'il vaut mieux expliquer dans une section dédiée. Les différentes implémentations sont décrites dans la liste suivante, nous allons les voir une par une. * Avec la première méthode, les accès mémoire sont exécutés par le pipeline pour les opérations entières. * Avec la seconde méthode, les accès mémoire ont un pipeline dédié ; c'est l'''émission parallèle des accès mémoire''. * Avec la troisième méthode, il est possible d'émettre plusieurs accès mémoire simultanément : c'est l'''émission multiple des accès mémoire''. Voyons ces trois cas dans le détail. Nous verrons notamment les différentes formes d'émission multiple des accès mémoire. ===Les accès mémoire traités dans le pipeline entier=== Dans le cas le plus simple, les accès mémoire sont traités comme des opérations entières un peu spéciales. L'unité mémoire est sur le même port d'émission que l'ALU entière, ce qui fait que les accès mémoire remplacent des opérations entières. Faire ainsi permet d'utiliser une ALU entière pour les calculs d'adresse, pas besoin de rajouter une unité de calcul d'adresse séparée. Typiquement, un processeur de ce type place l'unité mémoire après l'unité de calcul. Les opérations entières n'utilisent que l'ALU entière, mais pas l'unité mémoire. Les accès mémoire exécutent un calcul d'adresse dans l'ALU entière, puis l'adresse calculée est envoyée à l'unité mémoire. Le tout est illustré ci-dessous, dans le cas d'un processeur historique à double émission entière-flottante. [[File:Processeur non-superscalaire basique 01.png|centre|vignette|upright=1.5|Processeur non-superscalaire basique]] Un exemple de CPU double émission de ce type est celui du Power PC 440, un CPU destiné à l'embarqué. Vu qu'il était destiné à l'embarqué, il n'avait pas de FPU, il n'avait pas besoin de manipuler de nombres flottants. Il n'a donc que des ALU entières, un multiplieur et une unité mémoire. Il dispose de deux ports d'émission. Le premier est connecté à une ALU entière et un circuit multiplieur, le second est relié à l'unité mémoire et une seconde ALU entière. Une organisation simple, mais très efficace ! [[File:PowerPC 440.png|centre|vignette|upright=2|Microarchitecture du PowerPC 440.]] L'implémentation économise une unité de calcul d'adresse, mais n'a pas de cout en performance significatif. Les accès mémoire étant très lents, émettre une µop entière en parallèle d'un accès mémoire ne sert pas à grand-chose. On peut émettre cette µop entière un cycle plus tard, elle sera terminée avant même que l'accès mémoire soit terminé, même si l'accès mémoire se fait dans le cache L1 ou L2. Pour comprendre pourquoi, faisons quelques calculs. Un accès mémoire prend deux-trois cycles quand il tombe dans le cache L1. Et on doit attendre qu'il soit terminé pour en émettre un nouveau. Combien d'instructions arithmétiques peut-on lancer pendant ce temps, sur un CPU triple émission ? Au premier cycle, on peut en émettre 2 (l'instruction mémoire prend une place, donc 3-1), puis 3 au second cycle. Ce qui fait 5 instructions arithmétiques au total. Or, il se trouve qu'il y a une instruction d'accès mémoire pour 5 instructions arithmétiques, environ. Par contre, ce compromis change quand on passe à la quadruple émission, voire au-delà. Dans ce cas, il devient intéressant de pouvoir émettre une nouvelle instruction mémoire à chaque cycle. Ce qui nous amène à la section suivante... ===L'émission parallèle des accès mémoire=== A partir de la quadruple émission, la tendance a été de séparer l'unité mémoire des ALU entières. Cela permet d'émettre des µops mémoire en même temps que des µops entières et/ou flottantes., ce qui porte le nom d''''émission parallèle des µops mémoire''', ou encore ''émission mémoire parallèle''. Par exemple, un processeur quadruple émission peut émettre en même temps deux opérations entières, une opération flottante et un accès mémoire. L'accès mémoire vient en plus des opérations entières, il n'est pas possible de remplacer l'accès mémoire par une opération entière. La technique ne permet pas de faire les calculs d'adresse dans l'ALU entière. Il faut donc utiliser une unité de calcul d'adresse séparée. Elle est intégrée dans l'unité mémoire, qui fait l'interface entre le cache et le banc de registres. Le cout en circuits est très faible, car les unités de calcul d'adresse n'utilisent guère plus qu'un additionneur et un circuit de décalage très simple. L'émission mémoire parallèle est plus facile à implémenter sur les CPU RISC, qui ont des instructions mémoire séparées du reste. Les CPU CISC ne sont pas dans ce cas, ce qui fait que séparer l'unité mémoire du pipeline entier est compliqué. Les CPU superscalaires historiques étaient pour moité des CPU RISC et pour moitié des CPU CISC, ce qui fait qu'ils n'utilisaient pas ou peu l'émission mémoire parallèle. Les premiers processeurs superscalaires se contenaient de l'émission parallèle mémoire, ils ne pouvaient émettre qu'un seul accès mémoire à la fois, l'unité mémoire n'était pas dupliquée. Et ce n'est pas tant un problème que ça, du moins sur les processeurs à double ou triple émission. En effet, il est rare que l'unité mémoire doive faire deux accès simultanés. Notons que le processeur peut exécuter une instruction mémoire en parallèle d'un calcul ou d'un branchement, seuls les accès mémoire simultanés sont impossibles. ===L'émission multiple des accès mémoire avec une LSQ=== Les processeurs superscalaires larges travaillent sur des paquets de 8 à 10 instructions, il est plus fréquent d'avoir plusieurs accès mémoire par paquet. Dans ces conditions, il est utile d'avoir une '''émission multiple des accès mémoire''', à savoir d'émettre plusieurs micro-opérations mémoire en même temps. Les processeurs superscalaires modernes sont capables d'émettre plusieurs lectures/écritures simultanément. Par exemple, ils peuvent émettre une lecture en même temps qu'une écriture, ou plusieurs lectures, ou plusieurs écritures. Il faut noter que selon le processeur, il peut y avoir des restrictions quant aux accès mémoire émis en même temps. Par exemple, certains processeurs peuvent émettre une lecture avec une écriture en même temps, mais pas deux lectures ni deux écritures. Ou encore, ils peuvent émettre deux lectures, une lecture et une écriture, mais pas deux écritures en même temps. Dans la majorité des cas, les processeurs ne permettent pas d'émettre deux écritures en même temps, alors qu'ils supportent plusieurs lectures. Il faut dire que les lectures sont plus fréquentes que les écritures. Les processeurs qui autorisent toutes les combinaisons de lecture/écriture possibles, sont rares. L'émission multiple des accès mémoire s'implémente dans l'unité mémoire. Pour rappel, l'unité mémoire s'interpose entre le cache L1 et le reste du pipeline. Dans le cas le plus simple, elle incorpore une ''load/store queue'' et des unités de calcul d'adresse. L'émission multiple des accès mémoire demande de rendre la ''Load-store queue'' multi-ports, afin de gérer plusieurs micro-opérations mémoire simultanées. Il faut aussi dupliquer les unités de calcul d'adresse reliées à la LSQ, chaque port d'émission mémoire devant avoir sa propre unité de calcul d'adresse. : Dans ce qui suit, nous parlerons d'AGU (''Adress Generation Unit'') pour désigner les unités de calcul d'adresse, et de LSQ pour designer la ''Load-store queue''. [[File:Processeur non-superscalaire basique 02.png|vignette|upright=1|Processeur non-superscalaire basique]] Dupliquer les ports de la LSQ et les AGU est nécessaire. Mais qu'en est-il du cache ? La réponse varie suivant le processeur. Il y a deux grandes méthodes pour implémenter l'émission multiple des accès mémoire. La première méthode ajoute des ports de lecture/écriture au cache de données, pour faire des lectures/écritures simultanées. Il faut alors dupliquer des unités de calcul, rajouter les ports sur le cache, mais aussi rajouter des ports sur la ''load/store queue'', pour qu'elle échange des données/commandes mémoire avec le cache. Le cout en circuits est alors conséquent La seconde duplique les unités de calcul, mais pas les ports d'accès au cache. Les accès mémoire sont alors accumulés dans la ''load/store queue'', qui sont ensuite traités un par un par le cache. Un exemple est celui des processeurs AMD de micro-architecture K7 K8, à savoir les anciens AMD Athlon et AMD Athlon 64, qui pouvaient calculer trois adresses simultanément, mais ne pouvaient faire qu'un seul accès au cache par cycle. Le cout en circuit est modéré : les unités de calcul coutent très peu, les ports de la ''load/store queue'' coutent déjà un peu plus. Quelques processeurs utilisaient des solutions intermédiaires. Un processeur utilisant cette technique est, par exemple, capable d'émettre quatre µops mémoire en même temps, mais avec seulement deux ports sur le cache. Un exemple classique étant les Pentium 1 et 2 d'Intel, dont nous parlerons dans le chapitre suivant. ===L'émission séparée des LOAD et STORE=== De rares processeurs ont des ports d'émission qui peuvent faire indifféremment lecture comme écritures. Mais les processeurs modernes préfèrent séparer ports d'émission pour les lectures et les écritures. Un exemple est celui des processeurs Intel de micro-architecture Nehalem, qui avaient un port de lecture et un port d'écriture tous deux reliés à la ''Load-store queue'', chacun avec leur propre unité de calcul d'adresse. Comme autre exemple, les processeurs skylake ont une LSQ avec deux ports de lecture et un port d'écriture, ce qui permet d'émettre deux lectures en même temps qu'une écriture. Dans cette section, nous allons étudier le cas avec des ports séparés pour les lectures et écritures. En général, les processeurs ont un peu plus de ports de lectures que d'écritures. Une raison est qu'un programme a, en moyenne, un tout petit peu plus de lectures que d'écritures. Mais la différence n'est pas énorme, c'est un léger déséquilibre. Une seconde raison est que l'exécution dans le désordre marche bien mieux quand les lectures ont lieu le plus tôt possible. Donc mieux vaut privilégier les lectures à l'émission, pour qu'elles se fassent un peu en avance. En pratique, la différence n'est pas très marquée. Il est courant d'avoir un port de lecture et un d'écriture, les CPU superscalaires très larges ont souvent deux ports de lecture et deux d'écriture. A la rigueur, quelques CPU ont trois ports de lecture et deux d'écriture. Le cas le plus simple est celui où on peut émettre une lecture et une écriture en même temps. Il s'implémente facilement si la LSQ est en réalité une simple ''Store queue'', à savoir qu'elle met en avance les écritures uniquement. Pour rappel, les écritures sont mises en attente dans la ''Store queue'', mais les lectures sont exécutées immédiatement. Une fois l'adresse à lire connue, la lecture consulte la ''Store queue'' pour détecter les dépendances de données, puis s'exécute. La donnée à lire est lue soit depuis la ''Store queue'', soit depuis le cache. [[File:Emission LOAD - Store séparée.png|vignette|upright=1|Émission LOAD - Store séparée]] L'implémentation de l'émission multiple est alors très simple. La ''Store queue'' a déjà deux ports, avec un pour les lectures et un autre pour les écritures. Idem pour le cache, qui a naturellement un port de lecture et un d'écriture. Émettre une lecture et une écriture simultanément ne demande donc pas de modification digne de ce nom. La ''Store queue'' doit juste supporter une lecture et une écriture simultanée. Ajouter l'émission d'une seconde lecture n'est pas compliquée : il faut juste rajouter un second port de lecture sur le cache et la ''Store queue''. Pour rappel, l'unité mémoire est précédée par une structure qui met en attente les micro-opérations mémoire avant leur émission finale. La structure en question varie suivant l'implémentation, mais un cas particulier se marie bien avec l'émission multiple des µops mémoire. Il s'agit du cas avec une file de µops séparée pour les lectures et une autre pour les écritures, appelées respectivement la ''file de µops LOAD'' et la ''file de µops STORE''. Il est alors possible d'émettre une lecture et une écriture lors du même cycle d'horloge. Le port de lecture alimente une unité de calcul d'adresse dédiée, directement reliée au cache. Le port d'écriture du cache alimente une unité de calcul, qui est suivie par une file d'écriture, elle-même reliée au cache. Les processeurs AMD de micro-architecture K6 utilisaient cette technique. [[File:Double émission avec le Load Ordering, Store Ordering.jpg|centre|vignette|upright=2.5|Double émission avec le Load Ordering, Store Ordering]] ==Les CPU superscalaire à exécution dans le désordre== Les processeurs précédents utilisaient une unité d'émission simple, qui pouvait utiliser l'émission dans l'ordre ou dans le désordre. Elle peut être remplacée par une fenêtre d'instruction centralisée dans que cela quoi que ce soit aux explications précédentes, idem avec une station de réservation centralisée. En soi, l'exécution dans le désordre est donc totalement compatible avec une implémentation simple de la superscalarité. Par contre, des subtilités surviennent avec les fenêtres d'instruction décentralisées. Et au-delà de ça, la superscalarité modifie aussi les unités de renommage de registre, le ROB et les autres structures liées à l'exécution dans le désordre, au sens large. Dans cette section, nous allons voir comment les étages du pipeline dédiés à l'exécution dans le désordre sont modifiées sur un CPU superscalaire. ===L'unité de renommage superscalaire=== Avec la superscalarité, l'unité de renommage de registre doit gérer le cas où des instructions consécutives ont des dépendances de registre. Par exemple, prenons un processeur à double émission, qui renomme deux instructions consécutives. Si elles ont une dépendance de registre, le renommage de la seconde instruction dépend du renommage de la première. La première doit être renommée et le résultat du renommage est utilisé pour renommer la seconde, ce qui empêche d'utiliser deux unités de renommage séparées. Pour cela, elle renomme les registres sans tenir compte des dépendances, pour ensuite corriger le résultat. [[File:Unité de renommage superscalaire.png|centre|vignette|upright=2|Unité de renommage superscalaire.]] Seules les dépendances lecture-après-écriture doivent être détectées, les autres étant supprimées par le renommage de registres. Repérer ce genre de dépendances se fait assez simplement : il suffit de regarder si un registre de destination d'une instruction est un opérande d'une instruction suivante. [[File:Détection des dépendances sur un processeur superscalaire.png|centre|vignette|upright=2|Détection des dépendances sur un processeur superscalaire.]] Ensuite, il faut corriger le résultat du renommage en fonction des dépendances. Si une instruction n'a pas de dépendance avec une autre, on la laisse telle quelle. Dans le cas contraire, un registre opérande sera identique avec le registre de destination d'une instruction précédente. Dans ce cas, le registre opérande n'est pas le bon après renommage : on doit le remplacer par le registre de destination de l'instruction avec laquelle il y a dépendance. Cela se fait simplement en utilisant un multiplexeur dont les entrées sont reliées à l'unité de détection des dépendances. On doit faire ce replacement pour chaque registre opérande. [[File:Correction des dépendances sur un processeur superscalaire.png|centre|vignette|upright=2|Correction des dépendances sur un processeur superscalaire.]] ===Le tampon de réordonnancement et le banc de registre d'un CPU superscalaire=== Le ROB doit aussi être modifié pour émettre et terminer plusieurs instructions en même temps, en ajoutant des ports de lecture/écriture. Pour un CPU superscalaire peut émettre 8 µops à la fois, on se dit que le ROB peut retirer 8 µops par cycle maximum. Et c'est très souvent le cas en pratique. Cependant, certains processeurs ont un ROB qui peut retirer le double ! Un exemple est celui des CPU de micro-architecture Skymont, qui peuvent émettre 8 µops par cycle, mais en retirer 16. Quelques CPU de marque AMD étaient aussi dans ce cas. L'avantage est que cela permet de réduire la taille du ROB, du banc de registre et de quelques autres structures matérielles liées à l'exécution dans le désordre. Le gain sur-compense le cout en transistor nécessaire pour retirer 16 µops par cycle. Un détail est que le nombre de ports de lecture n'est pas le même selon que le processeur utilisé des stations de réservation ou une fenêtre d’instruction. Rappelons la terminologie utilisée dans ce cours. Les fenêtres d'instruction se contentent de mémoriser la micro-opération à émettre et quelques bits pour la disponibilité des opérandes. Par contre, les stations de réservations mémorisent aussi les opérandes des instructions. Et cette différence a une influence le nombre de ports de lecture du banc de registre. Supposons que toutes les instructions sont dyadiques, à deux opérandes. Comptons le nombre de ports nécessaires pour alimenter les unités de calcul, sans utiliser d'arbitre de banc de registre. Avec une fenêtre d'instruction, il faut le double du nombre de ports de ''dispatch''. Mais avec des stations de réservation, il faut le double des ports de ''schedule''. La raison est que les registres sont lus après émission avec une fenêtre d'instruction, avant avec des stations de réservation (sous-entendu, après le décodage). or, comme dit plus haut, les processeurs superscalaires larges ont souvent plus de ports de ''schedule'' que de ''dispatch''. Réduire le nombre de ports d'émission permet de garder un banc de registre, des fenêtres d'instruction et un ROB simples, rapides et économes en énergie. Et mine de rien, c'est là un compromis important : à quoi bon augmenter le nombre de ports d'émission si c'est pour que ce soit compensé par une fréquence plus faible ? Par contre, réduire le nombre de ports augmente la ''port contention'', surtout si on a mal réparti les ALU. Il faut donc trouver un compromis entre réduire le nombre de ports d'émission d'un côté, réduire la ''port contention'' de l'autre. ==Les optimisations des processeurs superscalaires larges== Les processeurs superscalaires larges ont des optimisations spécifiques, qui n'ont pas de sens sur les processeurs superscalaires étroits. Dans les grandes lignes, elles se classent en deux types. Les premières permettent de charger/décoder plus de 5-10 instructions simultanément. Le problème est alors que les branchements viennent mettre leur grain de sel et qu'il faut en tenir compte. Les secondes corrigent des problèmes qui apparaissent quand un CPU intègre plus de 5 à 10 unités de calcul. ===L'étape de chargement d'un CPU superscalaire large=== Charger plusieurs instructions pose de nombreux problèmes sur les processeurs superscalaires larges, qui émettent de 5 à 10 instructions simultanées. Pour rappel, la stratégie basique pour charger N instructions est de charger un bloc de N instructions consécutives. Les instructions après un branchement ne sont pas décodées ni exécutées, ce qui entraine une petite baisse de performance. Pour un N petit, la perte de performance est mineure. Mais pour un N large, elle devient suffisamment importante pour que la conception du CPU doit alors être repensée de fond en comble. Les analyses statistiques disent qu'il y a environ un branchement tous les 4 à 6 instructions. Un CPU a donc tendance à décoder des paquets de 3 à 5 instructions consécutives, avant qu'un branchement casse la dynamique. Or, cela pose des problèmes pour utiliser plusieurs décodeurs. L'implémentation basique, qui charge N instructions consécutives pour alimenter N décodeurs, fonctionne donc jusqu'à 4/6 décodeurs. Au-delà de 5 instructions, un CPU superscalaire doit être adapté pour gérer les branchements dans les paquets d'instruction qu'il émet. Le nombre de 5 instructions ne sort donc pas de nulle part. La première solution utilise un cache de µops, avec un ''front-end'' capable de charger/décoder 4/5 instructions à la fois, pas plus. L'idée est que la majorité des instructions proviennent du cache de µops (plus de 80%), ce qui fait qu'on peut émettre environ 10 instructions à partir du cache de µops, pas besoin d'avoir beaucoup de décodeurs. Les performances sont cependant dégradées lors d'un défaut de cache de µops. Les instructions doivent alors être chargées depuis le cache d'instruction, par paquets de 4-5, puis sont décodées par 4-5 décodeurs, ce qui ne permet pas d’alimenter les unités de calcul à plein régime. La solution précédente joue sur la distinction entre pots de décodage et d'émission. Elle permet d'émettre un grand nombre de µops sans pour autant charger un grand nombre d'instructions en même temps. En quelque sorte, ce n'est qu'une méthode de contournement, le CPU superscalaire reste de type étroit. Mais il est aussi possible d'utiliser un '''''front-end large''''', capable de charger une bonne dizaine d'instructions en même temps. La seule difficulté est de tenir compte de la présence de branchements. Pour rappel, avec un ''front-end'' étroit, les instructions situées après le premier branchement pris sont annulées, elles ne sont pas décodées ni exécutées. [[File:Fetch sur un processeur superscalaire avec prediction de branchements.png|centre|vignette|upright=2|Fetch sur un processeur superscalaire avec prédiction de branchements.]] Un ''front-end'' large charge les instructions de destination du branchement et les placent à sa suite. Ils chargent deux blocs à la fois et les fusionnent en un seul qui ne contient que les instructions présumées utiles. [[File:Cache d'instructions autoaligné.png|centre|vignette|upright=2|Cache d'instructions auto-aligné.]] Mais cela demande de charger deux blocs de mémoire en une fois, ce qui demande un cache d'instruction multiports. Il faut aussi ajouter un circuit pour assembler plusieurs morceaux de blocs en un seul : le fusionneur (''merger''). En pratique, cette solution demande d'ajouter un second port de lecture au cache, pour un cout en hardware important, avec un impact négatif sur le temps d'accès au cache. Et il faut rajouter le fusionneur, qui prend un étage de pipeline à lui tout seul. Et encore, cette solution ne gère pas le cas où un bloc contient plusieurs branchements ! [[File:Implémentation d'un cache d'instructions autoaligné.png|centre|vignette|upright=2|Implémentation d'un cache d'instructions auto-aligné.]] Les exemples précédents partent du principe qu'il n'y a qu'un seul branchement dans un bloc. Mais quand les blocs deviennent assez longs, il est fréquent d'avoir plusieurs branchements dans un bloc. Et il est même possible d'avoir plusieurs branchements pris. Dans un bloc de 8-10 instructions, ce n'est pas rare, et les CPU superscalaires à 8-10 voies sont la règle dans les CPU des années 2020. Pour gérer ce cas, il faut ajouter des ports supplémentaires au cache d'instruction et adapter le fusionneur, pour qu'il puisse fusionner trois ou quatre blocs. De plus, il faut utiliser une unité de prédiction de branchement capable de ''prédire plusieurs branchements par cycle''. ===Le décodage superscalaire large=== Le décodage des instructions n'est pas censé dépendre de l'implémentation du chargement. En théorie, une fois plusieurs blocs d'instruction fusionnés, on peut décoder les instructions en parallèle. Cependant, certains processeurs effectuent le décodage en tenant compte de la manière dont les branchements découpent les blocs. Les processeurs Atom d'Intel utilisent une solution de ce type, qui couple prédiction de branchement et décodage parallèle. Ils chargent/décodent des paquets de 3 instructions consécutives, chaque paquet étant séparé du précédent par un branchement. Pour cela, en plus d'avoir un fusionneur adapté, ils regroupent les décodeurs en ''clusters'', chacun regroupant 3 décodeurs, et pouvant décoder trois instructions consécutives. Les micro-architectures Tremont et Crestmont avaient deux ''clusters'' (6 décodeurs au total), la micro-architecture Skymont en intègre trois (9 décodeurs au total). Avec deux ''clusters'', le fonctionnement est le suivant. Si le code est composé d'une suite d'instruction sans branchements, seul le premier ''cluster'' est utilisé, limitant le décodage à trois instructions par cycle. Par contre, si l'une des trois instructions décodée est un branchement, alors le second ''cluster'' décode les trois instructions situées après ce branchement, à partir de l'adresse de destination. Avec un troisième ''cluster'', c'est la même chose, sauf qu'il faut qu'il y ait un branchement dans les trois instructions décodées par le second ''cluster''. Mais comment faire pour savoir quels décodeurs activés, quels ''clusters'' sont actifs ? C'est la prédiction de branchement qui commande. Avec deux ''clusters'', elle détermine si un branchement est présent dans les trois instructions à charger, et quelle est son adresse de destination. Sans branchement, elle prévient les décodeurs et les trois instructions chargées sont décodées dans un ''cluster'' libre. Si elle prédit un branchement, elle charge les instructions adéquates, et active les deux ''clusters''. Avec trois branchements, c'est le même mécanisme, sauf que l'unité de prédiction de branchement doit être capable de prédire deux branchements par cycle. Il faut noter que les décodeurs d'un ''cluster'' sont des décodeurs simples, sans microcode. Sur les processeurs Tremont et Crestmont, le microcode était unique et était partagé entre tous les ''clusters''. Un seul ''cluster'' pouvait décoder une instruction complexe avec le microcode, l'autre devait se limiter à des instructions simples uniquement. Depuis Skymont, le microcode est partiellement dupliqué, avec un microcode par ''cluster''. Le cout en hardware peut paraitre prohibitif, pour un gain en performance faible. Sauf que c'est oublier que ce sont des processeurs Atom, sur lesquels beaucoup d'instructions sont microcodées. Les processeurs Atom ont un budget en transistor limité, ce qui fait qu'il est préférable pour eux d'implémenter des instructions en microcode que d'utiliser des unités de calcul dédiées ou des circuits de décodage complexes. Pour limiter la casse, le microcode n'est que partiellement dupliqué. Seul le microcode pour les instructions fréquente est dupliqué. Il y a un microcode secondaire pour certaines instructions, qui est lui unique et partagé par tous les ''clusters''. ===Le contournement sur les processeurs superscalaires larges=== Pour rappel, la technique du contournement (''register bypass'') permet au résultat d'une instruction d'être immédiatement utilisable en sortie de l'ALU, avant même d'être enregistré dans les registres. Implémenter la technique du contournement demande d'utiliser des multiplexeurs pour relier la sortie de l'unité de calcul sur son entrée si besoin. Il faut aussi des comparateurs pour détecter des dépendances de données. [[File:Pipeline Bypass.png|centre|vignette|upright=1|Pipeline Bypass]] La sortie de chaque ALU doit être reliée aux entrées de toutes les autres, avec les comparateurs qui vont avec. Sur les processeurs ayant plusieurs d'unités de calculs, cela demande beaucoup d’interconnexions et de comparateurs. Pour N unités de calcul, cela demande 2 * N² interconnexions, implémentées avec 2N multiplexeurs de N entrées chacun. Si c'est faisable pour 2 ou 3 ALUs, la solution est impraticable au-delà. Non seulement ce serait un enfer à câbler, mais les fils seraient tellement longs que la fréquence du processeur en prendrait un coup. Notez que cela vaut pour les processeurs superscalaires, mais aussi pour tout processeur avec beaucoup d'unités de calcul. En théorie, un CPU à exécution dans le désordre, non-superscalaire, mais avec beaucoup d'unités de calcul, fait face au même problème. En pratique, avoir une dizaine d'ALU implique processeur superscalaire à exécution dans le désordre. D'où le fait qu'on parle du problème maintenant. La seule solution praticable est de ne pas relier toutes les unités de calcul ensemble. À la place, on préfère regrouper les unités de calcul dans différents '''agglomérats''' ('''cluster'''). Le contournement est alors possible entre les unités d'un même agglomérat, mais pas entre agglomérats différents. Évidemment, certaines possibilités de contournement sont perdues, mais c'est compensé par un gain en fréquence et un cout en circuit/fils réduit. C'est un bon compromis, ce qui explique que presque tous les processeurs modernes l'utilisent. Les rares exceptions sont les processeurs POWER 4 et POWER 5, qui ont préféré se passer de contournement pour garder un processeur très simple et une fréquence élevée. ===Le banc de registre des processeurs superscalaires larges=== Pour émettre deux opérations entières simultanément, il faut lire deux fois plus d'opérandes. Et il faut aussi écrire deux résultats dans les registres, en même temps. Pour cela, on double les ports du banc de registre. En général, il faut au maximum 2 ports de lecture et un port d'écriture par unité de calcul. Le problème, c'est que plus un banc de registres a de ports, plus il utilise de circuits, est compliqué à concevoir, consomme de courant et chauffe. Avec plusieurs dizaines d'unités de calcul différentes, ajouter autant de ports impacterait la fréquence du banc de registres et réduirait les performances, voire est totalement impraticable à cause des contraintes thermiques. Les processeurs superscalaires larges doivent donc trouver des solutions. La première solution utilise un banc de registre unique, mais n'utilise pas autant de ports que le pire des cas le demanderait. Le processeur détecte quand il n'y a pas assez de ports pour servir toutes les micro-opérations. Quand ça arrive, l'unité d'émission émet assez de micro-opérations pour exploiter les ports disponibles, mais met en attente les micro-opérations en trop, le temps que les ports se libèrent. Pour cela, le banc de registre est géré par un circuit d'arbitrage spécialisé, intégré à l'unité d'émission, l’'''arbitre du banc de registres''' (''register file arbiter''). Une autre solution est de dupliquer le banc de registres en plusieurs exemplaires qui contiennent exactement les mêmes données, mais avec moins de ports de lecture/écriture. Un exemple est celui des processeurs POWER, Alpha 21264 et Alpha 21464. Sur ces processeurs, le banc de registre est dupliqué en plusieurs exemplaires, qui contiennent exactement les mêmes données. Les écritures dans les registres sont envoyées à tous les bancs de registres, afin de garantir que leur contenu est identique. Chaque exemplaire a exactement deux ports de lecture. La conception du processeur est simplifiée, que ce soit au niveau du câblage, que de la conception des bancs de registres. Une dernière solution découpe le banc de registres en plusieurs bancs de registres plus petits, avec un réseau d'interconnexions pour échanger des données entre bancs de registres. Dans la plupart des cas, cette séparation est invisible du point de vue de l'assembleur et du langage machine. Le processeur se charge de transférer les données entre bancs de registres suivant les besoins. Sur d'autres processeurs, les transferts de données se font via une instruction spéciale, souvent appelée ''COPY''. [[File:Banc de registres distribué.png|centre|vignette|upright=2|Banc de registres distribué.]] Sur de certains processeurs, un branchement est exécuté par une unité de calcul spécialisée. Or les registres à lire pour déterminer l'adresse de destination du branchement ne sont pas forcément dans le même agglomérat que cette unité de calcul. Pour éviter cela, certains processeurs disposent d'une unité de calcul des branchements dans chaque agglomérat. Dans les cas où plusieurs unités veulent modifier le ''program counter'' en même temps, un système de contrôle général décide quelle unité a la priorité sur les autres. Mais d'autres processeurs fonctionnent autrement : seul un agglomérat possède une unité de branchement, qui peut recevoir des résultats de tests de toutes les autres unités de calcul, quel que soit l’agglomérat. ==La macro-fusion : une optimisation du décodage parallèle== Les processeurs superscalaires supportent la technique dite de '''macro-fusion''', qui permet de fusionner deux-trois instructions consécutives en une seule micro-opération. Par exemple, il est possible fusionner une instruction de test et une instruction de saut en une seule micro-opération de branchement. Il s'agit là de l'utilisation la plus importante de la macro-fusion sur les processeurs x86 modernes. La macro-fusion est utilisée pour fusionner un test et un saut en un branchement unique. L'optimisation est présente sur la plupart des CPU superscalaires modernes. Les CPU x86 l'implémentent, mais aussi quelques CPU ARM et les processeurs Apple de série M (M1 à M5, et potentiellement plus quand ils seront sortis). La fusion des instructions se fait lors du décodage des instructions, grâce à la coopération des décodeurs. Elle marche systématiquement si l'instruction de test et le saut sont consécutives, qu'elles forment bien une paire d'instruction. Par contre, insérer une instruction entre les deux empêche la macro-fusion de faire son travail, sauf si l'instruction insérée est un NOP. Les processeurs ARM64 avaient une forme spécifique de macro-fusion, utilisée pour un cas très précis : charger une constante immédiate de 32 ou 64 bits, dans un registre 64 bits. Les CPU ARM 64 bits utilisent des instructions codées sur 32 bits, qui ne peuvent encoder que des constantes immédiates de 16 bits. Pour charger une constante de 32/64 bits, il faut s'y prendre en plusieurs fois. Une première instruction MOVZ charge les 16 premiers bits, puis 2 à 4 instructions MOVK remplissent le reste. MOVZ met à zéro les 48 bits de poids fort, MOVK permet de sélectionner le doublet à modifier dans le registre (on modifie 16 bits, dont on choisit la place, le reste du registre est laissé tel quel). ARM permet de fusionner cette suite d'instructions en une seule µop MOV avec une constante immédiate. On parle alors de '''''MOVZ + MOVK fusion'''''. Une dernière utilisation, [https://www2.eecs.berkeley.edu/Pubs/TechRpts/2016/EECS-2016-130.pdf proposées pour le jeu d'instruction RISC-V], fusionnait un calcul d'adresse et une instruction LOAD/STORE en une seule instruction d'accès mémoire. Elle a été envisagée à une époque où le jeu d'instruction RISC-V ne gérait pas les modes d'adressage base + indice. L'idée était d'avoir une unité mémoire qui gérait le mode d'adressage base + indice et de fusionner des instructions pour l'exploiter. Mais de nos jours, le jeu d'instruction RISC-V intègre le mode d'adressage base + indice, ce qui fait que la macro-fusion n'a aucun intérêt pour ce cas précis. La macro-fusion a de nombreuses limitations en pratique. En théorie, on pourrait l'utiliser au-delà des deux situations mentionnées plus haut. Par exemple, pour fusionner une addition et une multiplication en une seule instruction MAD. Mais l'intérêt est assez faible. Notons que la macro-fusion ne permet que de fusionner des paires d'instructions. Aller au-delà demanderait un cout en circuit trop important. Déjà que tester deux paires utilise pas mal de comparateurs... ==Le ''stack engine'' : une optimisation de la pile d'appel== Les processeurs modernes intègrent une optimisation liée au pointeur de pile. Pour rappel, sur les architectures modernes, le pointeur de pile est un registre utilisé pour gérer la pile d'appel, précisément pour savoir où se trouve le sommet de la pile. Il est manipulé par les instructions CALL, RET, PUSH et POP, mais aussi par les instructions d'addition/soustraction pour gérer des cadres de pile de taille variable. De plus, il peut servir d'opérande pour des instructions LOAD/STORE, afin de lire/écrire des variables locales, les arguments d'une fonction, et autres. C'est donc un registre général adressable, intégré au banc de registre, altéré par l'unité de calcul entière. L'incrémentation/décrémentation du pointeur de pile passe donc par l'unité de calcul, lors des instructions CALL, RET, PUSH et POP. Mais, l'optimisation que nous allons voir permet d'incrémenter/décrémenter le pointeur de pile sans passer par l'ALU, ou presque. L'idée est de s'inspirer des architectures avec une pile d'adresse de retour, qui intègrent le pointeur de pile dans le séquenceur et l'incrémentent avec un incrémenteur dédié. ===Le ''stack engine''=== L'optimisation que nous allons voir utilise un '''''stack engine''''' intégré à l'unité de contrôle, au séquenceur. Le processeur contient toujours un pointeur de pile dans le banc de registre, cela ne change pas. Par contre, il n'est pas incrémenté/décrémenté à chaque instruction CALL, RET, PUSH, POP. Un compteur intégré au séquenceur est incrémenté à la place, nous l’appellerons le '''compteur delta'''. La vraie valeur du pointeur de pile s'obtient en additionnant le compteur delta avec le registre dans le banc de registre. Précisons que si le compteur delta vaut zéro, la vraie valeur est dans le banc de registre et peut s'utiliser telle quelle. Lorsqu'une instruction ADD/SUB/LOAD/STORE utilise le pointeur de pile comme opérande, elle a besoin de la vraie valeur. Si elle n'est pas dans le banc de registre, le séquenceur déclenche l'addition compteur-registre pour calculer la vraie valeur. Finalement, le banc de registre contient alors la bonne valeur et l'instruction peut s'exécuter sans encombre. L'idée est que le pointeur de pile est généralement altéré par une série d'instruction PUSH/POP consécutives, puis des instructions LOAD/STORE/ADD/SUB utilisent le pointeur de pile final comme opérande. En clair, une bonne partie des incrémentations/décrémentation est accumulée dans le compteur delta, puis la vraie valeur est calculée une fois pour toutes et est utilisée comme opérande. On accumule un delta dans le compteur delta, et ce compteur delta est additionné quand nécessaire. Précisons que le compteur delta est placé juste après le décodeur d'instruction, avant même le cache de micro-opération, l'unité de renommage et l'unité d'émission. Ainsi, les incrémentations/décrémentations du pointeur de pile disparaissent dès l'unité de décodage. Elles ne prennent pas de place dans le cache de micro-opération, ni dans la fenêtre d'instruction, ni dans la suite du pipeline. De nombreuses ressources sont économisées dans le ''front-end''. Mais utiliser un ''stack engine'' a aussi de nombreux avantages au niveau du chemin de données/''back-end''. Déjà, sur les processeurs à exécution dans le désordre, cela libère une unité de calcul qui peut être utilisée pour faire d'autres calculs. Ensuite, le compteur delta mémorise un delta assez court, de 8 bits sur le processeur Pentium M, un peu plus pour les suivants. L'incrémentation se fait donc via un incrémenteur 8 bits, pas une grosse ALU 32/64 bits. Il y a un gain en termes de consommation d'énergie, un incrémenteur 8 bits étant moins gourmand qu'une grosse ALU 32/64 bits. ===Les points de synchronisation du delta=== La technique ne fonctionne que si la vraie valeur du pointeur de pile est calculée au bon moment, avant chaque utilisation pertinente. Il y a donc des '''points de synchronisation''' qui forcent le calcul de la vraie valeur. Plus haut, nous avions dit que c'était à chaque fois qu'une instruction adresse le pointeur de pile explicitement, qui l'utilise comme opérande. Les instructions CALL, RET, PUSH et POP ne sont pas concernées par elles utilisent le pointeur de pile de manière implicite et ne font que l'incrémenter/décrémenter. Mais dans les faits, c'est plus compliqué. D'autres situations peuvent forcer une synchronisation, notamment un débordement du compteur delta. Le compteur delta est généralement un compteur de 8 bits, ce qui fait qu'il peut déborder. En cas de débordement du compteur, le séquenceur déclenche le calcul de la vraie valeur, puis réinitialise le compteur delta. La vraie valeur est donc calculée en avance dans ce cas précis. Précisons qu'un compteur delta de 8 bits permet de gérer environ 30 instructions PUSH/POP consécutives, ce qui rend les débordements de compteur delta assez peu fréquent. À noter que si le compteur delta vaut zéro, il n'y a pas besoin de calculer la vraie valeur, le séquenceur prend cette situation en compte. Un autre point de synchronisation est celui des interruptions et exceptions matérielles. Il faut que le compteur delta soit sauvegardé lors d'une interruption et restauré quand elle se termine. Idem lors d'une commutation de contexte, quand on passe d'un programme à un autre. Pour cela, le processeur peut déclencher le calcul de la vraie valeur lors d'une interruption, avant de sauvegarder les registres. Pour cela, le processeur intègre un mécanisme de sauvegarde automatique, qui mémorise la valeur de ce compteur dans le tampon de réordonnancement, pour forcer le calcul de la vraie valeur en cas de problème. La technique du ''stack engine'' est apparue sur les processeurs Pentium M d'Intel et les processeurs K10 d'AMD, et a été conservée sur tous les modèles ultérieurs. L'implémentation est cependant différente selon les processeurs, bien qu'on n'en connaisse pas les détails et que l'on doive se contenter des résultats de micro-benchmarks et des détails fournit par Intel et AMD. Selon certaines sources, dont les manuels d'optimisation d'Agner Fog, les processeurs AMD auraient moins de points de synchronisation que les processeurs Intel. De plus, leur ''stack engine'' serait placé plus loin que prévu dans le pipeline, après la file de micro-opération. ==La micro-fusion et la délamination== La '''micro-fusion''' est une optimisation qui retarde le décodage réel de certaines instructions assez loin dans le pipeline. Elle concerne surtout les instructions ''load-op'', mais pas que. D'autres instructions mémoire peuvent subir cette optimisation. L'essentiel est que, sans micro-fusion, ces instructions sont décodées en plusieurs micro-opérations. La micro-fusion retarde le décodage final de ces instructions, qui se fait assez tard dans le pipeline. Pour comprendre l'intérêt, nous allons devoir faire quelques explications. L'optimisation en question fait le décodage en deux temps. Prenons l'exemple d'une opération ''load-op'', qui est censée être décodée en deux micro-opérations : une lecture, puis une opération. Le décodage produit une '''macro-opération''' (terme d'Intel), qui est propagée dans le pipeline, pour être scindée en deux micro-opérations un peu avant les ALU et l'unité mémoire. La macro-opération est techniquement une micro-opération comme une autre, sauf que ce n'est pas la micro-opération finale. L'unité d'émission découpe cette macro-opération en deux micro-opérations, qui sont émises l'une après l'autre. Pour résumer, les instructions ''load-op'' ne sont complétement découpées en micro-opération lors de l'émission, pas lors du décodage. L'optimisation impacte ce qui se situe entre le décodeur d'instruction et l'unité d'émission : l'unité de renommage, la file de micro-opérations, le ''Loop Stream Detector'' et/ou le cache de micro-opération. L'intérêt de l'optimisation est que l'on fusionne deux micro-opérations en une seule macro-opération. Le cache de micro-opération et la file de micro-opération peuvent mémoriser plus de micro-opérations, vu que certaines sont fusionnées. Quant à l'unité de renommage, elle renomme une seule macro-opération au lieu de renommer deux micro-opérations, ce qui augmente le débit du renommage de registre. Par contre, cela complique un peu le travail de l'unité d'émission, qui doit découper les instructions ''load-op'' en deux et émettre les micro-opérations l'une après l'autre. Il faut noter qu'il n'y a pas de fusion proprement dite. Le décodeur ne décode pas deux micro-opérations séparées, avant de les fusionner. En réalité, il génère directement une macro-opération, qui correspond à deux micro-opérations, que ce soit plus tard dans le pipeline ou sur une autre architecture. D'ailleurs, la micro-fusion a lieu à l'intérieur d'une instruction, qui est censée être traduite en plusieurs micro-opérations. Et encore : pas toutes. La conséquence est que la technique n'a de sens que sur les processeurs CISC, avec des instructions assez complexes pour être décodées en plusieurs micro-opérations. En pratique, seuls les CPU x86 sont concernés. ===Les domaines de fusion=== La micro-fusion n'est pas spécifique aux processeurs superscalaires. En théorie, on peut l'implémenter sur un processeur non-superscalaire à émission dans l'ordre. Cependant, je ne connais aucun processeur à émission dans l'ordre qui implémente la micro-fusion, même si son implémentation est en théorie possible sur de tels processeurs. La micro-fusion est implémentée différemment selon que l'émission se fasse dans l'ordre, dans le désordre, avec une fenêtre d'instruction, avec des stations de réservation, etc. J'ai dit plus haut que les micro-opérations sont générées lors de l'émission, mais le moment exact varie suivant le processeur. Pour simplifier le tout, nous parlerons de '''scission''' quand le processeur traduit une macro-opération en deux/trois micro-opérations. Le moment où a lieu la scission est théoriquement dans l'unité d'émission, mais celle-ci prend plusieurs formes selon que le processeur utilise l'émission dans l'ordre, dans le désordre, avec une fenêtre d'instruction, avec des stations de réservation, etc. Les processeurs à exécution dans l'ordre sont le cas le plus simple. L'unité d'émission est alors en charge du découpage des macro-opérations en micro-opérations. La file de micro-opération ou le cache de micro-opérations mémorisent des macro-opérations. À l'opposé, le tampon de ré-ordonnancement mémorise lui des micro-opérations. Le reste du processeur n'est pas affecté. Sur les processeurs à exécution dans le désordre, les différences sont nombreuses. Le tampon de ré-ordonnancement peut mémoriser soit les micro-opérations finales, soit des macro-opérations. Il en est de même avec la fenêtre d'instruction et/ou les stations de réservation, qu'elles soient centralisées ou décentralisées. Tout dépend de comment se fait l'implémentation. Une implémentation simple effectue la scission des macro-opérations dans l'unité d'émission, comme sur les processeurs à exécution dans l'ordre. Sur de nombreux processeurs, l'unité d'émission et de renommage sont simplement fusionnées, ce qui fait que la scission a lieu juste après le renommage de registres. En conséquence, le tampon de re-ordonnancement et les fenêtres d'instruction ne voient que des micro-opérations. Les macro-opérations disparaissent juste avant. Cette implémentation est préférée avec une fenêtre d'instruction centralisée, car on voit mal comment scinder une macro-opération juste avant l'ALU. Une autre implémentation fait la scission en sortie de la fenêtre d'instruction ou des stations de réservation, qu'elles soient centralisées ou décentralisées. Dans ce cas, le tampon de re-ordonnancement fait de même. L'avantage est que le tampon de ré-ordonnancement stocke des macro-opérations, ce qui augmente sa taille effective. Par exemple, il utilise une seule entrée pour une instruction ''load-op'', au lieu de deux sans micro-fusion de ce type. ===La délamination=== La scission peut parfois avoir lieu plus tôt, dans des circonstances très précises. Concrètement, cela ne concerne que quelques micro-architectures d'Intel, pour des instructions très précises. La raison est les limitations du format des micro-opérations, pas une histoire d'optimisation. Le processeur aimerait pouvoir faire la micro-fusion, mais ne peut pas à cause de ces limitations. Une telle situation cause alors une '''délamination''' : les micro-opérations sont scindées précocement. Par exemple, sur la micro-architecture SandyBridge d'Intel, les opérations ''load-op'' sont fusionnées, sauf si elles utilisent l'adressage indicé. Les micro-opérations de ce processeur ne peuvent avoir que deux opérandes. Trois opérandes est de trop, et les opérations ''load-op'' en adressage indicé dépassent cette limite. Dans ce cas, les micro-opérations sont fusionnées en sortie du décodeur, mais sont scindés avant d’entrer dans la file de micro-opération. L'avantage n'est pas évident, vu que l'unité de renommage suit de près le décodeur d'instruction. Il est que le cache de micro-opérations mémorise des macro-opérations, pas deux micro-opérations. ===La micro-fusion des processeurs Intel et AMD=== La micro-fusion est apparue sur le Pentium M d'Intel, et a été conservée telle quelle sur les processeurs Intel suivants. Les processeurs AMD supportent la micro-fusion depuis au moins la micro-architecture AMD K8. Mieux que ça, la délamination n'était pas utilisée car les micro-opérations acceptaient plus de 2 opérandes. Que ce soit chez AMD ou Intel, elle concerne deux types d'instructions : les écritures mémoire et les instructions ''load-op''. Sur le Pentium M, il y avait quelques limitations, l'optimisation se limitant par exemple aux registres entiers et flottants, mais pas aux registres MMX, mais pas les registres SSE (nous verrons ces registres dans quelques chapitres). Mais les processeurs modernes n'ont plus cette limitation. La micro-fusion des CPU Intel concerne deux types d'instructions : les écritures mémoire et les instructions ''load-op''. Pour les écritures, la situation est plus complexe que pour les instructions ''load-op''. Sur les processeurs Intel précédents, les écritures étaient réalisées dans deux unités séparées : le calcul d'adresse dans une unité de calcul, l'écriture proprement dite dans l'unité mémoire. Elles prenaient donc deux micro-opérations. Le processeur a changé ses unités mémoire, ce qui fait qu'elle pouvait faire les deux en une seule micro-opération. Il s'agissait alors d'une optimisation du chemin de données, plus que de la micro-fusion proprement dite, mais je la mentionne ici malgré tout. La micro-fusion sur ce processeur avait un avantage en plus des avantages usuels : elle utilisait mieux les décodeurs. Les Pentium M avaient trois décodeurs pour décoder plusieurs instructions en même temps, chose qu'on détaillera dans le chapitre sur les processeurs superscalaires. Un seul décodeur pouvait décoder toutes les instructions, les deux autres ne peuvent décoder que les instructions simples, qui se décodent en une seule micro-opération. Avec la micro-fusion, les trois décodeurs peuvent gérer les écritures mémoire et les opérations ''load-op'', vu qu'elles deviennent des instructions simples. Sans cela, elles auraient été limitées au premier décodeur. <noinclude> {{NavChapitre | book=Fonctionnement d'un ordinateur | prev=Le parallélisme mémoire | prevText=Le parallélisme mémoire | next=Exemples de microarchitectures CPU : le cas du x86 | nextText=Exemples de microarchitectures CPU : le cas du x86 }} </noinclude> 96vkzej64pecfd8srw6ftcoxtlfdc5w 773243 773242 2026-09-26T14:59:46Z Mewtow 31375 /* Les CPU superscalaire à exécution dans le désordre */ 773243 wikitext text/x-wiki Les processeurs vus auparavant ne peuvent émettre au maximum qu'une instruction par cycle d'horloge. Ils peuvent avoir plusieurs instructions qui s'exécutent en même temps, dans des unités de calcul séparées. C'est le cas dès qu'une instruction multi-cycles s'exécute. Par contre, ils ne peuvent démarrer qu'une seule instruction par cycle. Et quand on court après la performance, ce n'est pas assez ! Les concepteurs de processeurs ont inventés des processeurs qui émettent plusieurs instructions par cycle : les '''processeurs à émissions multiples'''. Un processeur à émission multiple charge plusieurs instructions en même temps, les décode en parallèle, puis les émet en même temps sur des unités de calculs séparées. Pour cela, il faut gérer les dépendances entre instructions, répartir les instructions sur différentes unités de calcul, et cela n'est pas une mince affaire. En général, pour un processeur à émission multiple capable d'émettre N instructions en parallèle, on parle de '''processeur à N voies''', ou encore un ''processeur à N pipelines''. [[File:Superscalarpipeline.svg|centre|vignette|upright=1.5|Pipeline RISC classique à cinq étages sur un processeur superscalaire. On voit bien que plusieurs instructions sont chargées en même temps.]] Les processeurs à émission multiple sont de deux types : les processeurs VLIW et les '''processeurs superscalaires'''. Nous mettons les processeurs VLIW de côté en attendant le prochain chapitre. La raison est qu'ils ont un jeu d'instruction spécialisé qui expose le parallélisme d'instruction directement au niveau du jeu d'instructions, là où les processeurs superscalaires restent des processeurs au jeu d'instruction normal. La différence principale entre processeur VLIW et superscalaire est qu'un processeur superscalaire répartit les instructions sur les unités de calcul à l’exécution, là où un processeur VLIW délègue cette tâche au compilateur. ==Le nombre de voies d'un processeur superscalaire== Les processeurs superscalaires les plus simples ne permettent que d'émettre deux micro-opérations à la fois, d'où leur nom de processeur ''dual issue'', ce qui se traduit en '''processeur à double émission'''. Les processeurs triple émission permettent eux d’émettre trois instructions à la fois. A l'opposé, les CPU superscalaires les plus puissants peuvent émettre jusqu'à 8-10 instructions simultanément. En 2026, les CPU superscalaires peuvent monter jusqu’à 10 µops émises en même temps, ce qui est énorme ! Et cela nous amène à faire une distinction entre les processeurs superscalaires dits '''étroits''' et ceux dits '''larges'''. Dans ce wikilivre, on part du principe que les processeurs superscalaires étroits émettent jusqu'à 4-5 µops à la fois, alors que les larges peuvent en émettre plus, l'intervalle entre les deux est une zone grise. Le nombre exact n'est pas fixé dans le marbre, la distinction est volontairement vague. Nous parlerons de la '''largeur d'un CPU superscalaire''' pour parler du nombre de voies, du nombre de µops émises en même temps. Plus un CPU superscalaire est large, plus il est performant ''toute chose égale par ailleurs''. Mais dans les faits, vous pouvez oublier la partie en italique. Élargir un CPU superscalaire demande de rajouter beaucoup de circuits. Et ce cout en circuit aura un impact sur la performance. A budget en transistors limité, il y a un compromis entre élargir un CPU et rajouter du cache ou la taille des fenêtres d'instructions/ROB et autres. ===Les contraintes d’appariement=== Prenons un processeur double émission, capable d'émettre deux instructions simultanément. Émettre deux instructions en même temps n'est pas toujours possible. Par exemple, si deux instructions sont dépendantes, on ne peut pas les émettre en même temps. Mais oublions un instant ces histoires de dépendances de données, et partons du principe que le CPU ait affaire à deux instructions indépendantes, une paire idéale. Un CPU double émission "parfait" pourrait exécuter n'importe quelle paire d'instruction. Par exemple, il pourrait émettre deux multiplications consécutives, deux lectures consécutives, une lecture et une écriture en même temps, etc. En pratique, aucun processeur ne permet cela. Implémenter un CPU superscalaire "parfait" aurait un cout en circuit beaucoup trop conséquent. Par exemple, pour émettre deux multiplications en même temps, il faudrait deux multiplieurs séparés. Idem pour exécuter deux accès mémoire simultané : cela demande d'avoir deux unités mémoire et un cache multiport. Mais en pratique, aucun CPU superscalaire ne fait cela. La totalité des CPU double émission se débrouille avec un seul circuit multiplieur et une seule unité mémoire. Et cela entraine l'apparition de dépendances structurelles, qui font que certaines paires d'instructions sont impossibles. Par exemple, impossible d'émettre deux multiplications en même temps avec un seul circuit multiplieur. Mais émettre une multiplication en parallèle d'une addition est parfaitement possible. Il y a donc des paires d'instructions autorisées et des paires interdites. Et la logique est la même sur les CPU à triple ou quadruple émission, et au-delà. : des triplets ou quadruplet d'instructions sont impossibles à émettre simultanément, alors que d'autres le sont. Pour le dire autrement, il y a des '''contraintes d'appariement''' sur les instructions à émettre en même temps. ===L'impact des contraintes d’appariement sur la performance=== Ces contraintes d’appariement ont un impact sur les performance, mais celui-ci peut être grandement mitigé. Les programmes informatiques utilisent en pratique beaucoup plus d'opérations entières de base que de multiplication, branchements et accès mémoire. Il y a environ 5 opérations entières de base pour une multiplication entière. En pratique, il est rare de devoir exécuter deux multiplications à la fois. Idem pour les décalages et rotations, qui sont assez éloignés les uns des autres. Deux branchements consécutifs sont quant à eux plus fréquents. Deux accès mémoire consécutifs est assez rare, mais ils restent assez proches les uns des autres malgré tout. La conséquence est que certaines paires d'instructions sont privilégiées par rapport au reste. Les paires regroupant deux calculs basiques (additions, soustractions, ...) sont systématiquement supportées, car dupliquer des ALU entières est facile. Les paires regroupant un calcul et une autre instruction sont elles aussi systématiquement supportées. Par contre, les paires avec deux branchements, deux accès mémoire, deux décalages/rotations, ou deux multiplications/divisions sont fréquemment interdites. Seuls les CPU superscalaires larges peuvent les permettre, et encore : sous conditions. Le résultat est que le processeur peut se débrouiller avec une seule unité mémoire et une unité de branchement. ===Terminologie pour la suite du chapitre=== Décrire les contraintes d’appariement demande de dire quelles combinaisons d’instructions sont autorisées ou interdites. Et pour cela, nous allons introduire les abréviations suivantes : * INT est un raccourcis pour parler d'une opération entière simple, qu'une ALU entière peut exécuter. * MUL correspond à une opération entière complexe, typiquement une multiplication, éventuellement à une division. * FLOAT désigne une opération flottante, peut importe laquelle. * MEM désigne un accès mémoire, à savoir une instruction ou µop mémoire. * BRANCH désigne une instruction de branchement. Cette terminologie permettra de définir des paires d'instructions, qu'elles soient possibles ou interdites. Par exemple, l'expression INT + FLOAT désigne une paire d'instruction regroupant une instruction entière et une instruction flottante, peu importe l'ordre des deux instructions. Il en sera de même pour les triplets, quadruplets, ou plus. Par exemple, l'expression INT + INT + FLOAT + MUL désigne un quadruplet d'instructions regroupant : deux instructions entières, une instruction flottante, et une multiplication entière. J'utiliserais aussi des parenthèses pour regrouper certaines combinaisons. Par exemple, l'expression INT + (MUL /FLOAT) veut parler d'une paire d'instruction où la première est une opération entière basique, la seconde est : soit une multiplication, soit une opération flottante. ==Le ''front-end'' des processeurs superscalaires== Un processeur superscalaire doit charger, décoder, et exécuter plusieurs instructions par cycle. Par exemple, un processeur triple émission doit charger 3 instructions lors d'un premier cycle, puis les décoder au cycle suivant, et les exécuter au cycle suivant. Il travaille donc pas paquets de 3 instructions, dans cet exemple. Intuitivement, charger, décoder et exécuter plusieurs instructions à la fois demande de dupliquer des circuits. Par exemple, il faut dupliquer les décodeurs pour décoder plusieurs instructions. Les unités de calcul sont aussi dupliquées, sauf sur certaines processeurs qui arrivent à ruser, mais laissons cela de côté pour plus tard. Mais d'autres circuits sont simplement modifiés, comme l'unité de chargement ou l'unité d'émission. Voyons ce qu'il en est dans le détail. {|class="wikitable" |- ! rowspan="2" | Processeur sans émission multiple | rowspan="2" | Chargement | rowspan="2" | Décodage | rowspan="2" | Renommage | rowspan="2" | Émission | Exécution / ALU | rowspan="2" | ''Commit''/ROB |- | Exécution / ALU |- ! rowspan="4" | Processeur superscalaire | rowspan="4" | Chargement | rowspan="2" | Décodage | rowspan="4" | Renommage | rowspan="4" | Émission | Exécution / ALU | rowspan="4" | ''Commit''/ROB |- | Exécution / ALU |- | rowspan="2" | Décodage | Exécution / ALU |- | Exécution / ALU |} Nous allons d'abord voir ce qu'il en est pour le ''front-end'', à savoir la partie du CPU qui s'occupe de charger et décoder les instructions. Elle regroupe l'unité de chargement, les décodeurs, l'unité de prédiction de branchement, la file de µops, la file d'instruction, et divers caches. En soi, les changements portent surtout sur l'unité de chargement et les décodeurs. ===Le chargement de plusieurs instructions consécutives=== Un processeur superscalaire charge plusieurs instructions en même temps, ''en tenant compte de la présence de branchements''. La partie en italique est importante et elle a une conséquence très forte : la manière de faire est fortement différente entre un CPU superscalaire étroit et large. Un CPU superscalaire étroit fait au plus simple : il charge un bloc de 2, 3, 4 instructions consécutives en mémoire RAM. Il faut alors doubler/tripler/quadrupler la taille du bus connecté au cache d'instruction. Par exemple, pour charger quatre instructions de 32 bits chacune, il suffit de lire un bloc de 128 bits dans le cache d'instruction. Un bloc de 8, 16, 32 octets est donc lu depuis le cache et est ensuite découpé en instructions, envoyées chacun à un décodeur. Charger plusieurs instructions consécutives ne marche bien qu'en absence de branchements. La présence de branchements casse cette dynamique, et il faut gérer leur présence. * Sans prédiction de branchement, toutes les instructions d'un bloc sont décodées et exécutées en même temps, même s'il y a un branchement dans le tas. * Avec prédiction de branchement, les instructions situées après le branchement ne sont pas décodées ni exécutées. L'unité de chargement coupe le bloc chargé au niveau du premier branchement pris, remplit les vides avec des NOP, avant d'envoyer le tout à l'unité de décodage. Les instructions à l'adresse de destination seront chargées au cycle suivant, en chargeant un nouveau bloc. [[File:Fetch sur un processeur superscalaire avec prediction de branchements.png|centre|vignette|upright=2|Fetch sur un processeur superscalaire avec prédiction de branchements.]] Un autre défaut est que sur le CPU charge un bloc de 128 bits à la fois, les instructions doivent être alignées sur 128 bits. Et on peut généraliser à n'importe quelle taille de bloc. Si les instructions ne sont pas alignées, il faut gérer la situation. Il arrive qu'une instruction soit à cheval sur deux blocs, avec un morceau dans le premier bloc, un autre dans le second. Dans ce cas, l'instruction est chargée en deux fois, et des circuits doivent gérer la situation pour recoller les morceaux. Sur les CPU avec des instructions de taille fixe, le problème ne survient que rarement. Mais il est fréquent avec des instructions de taille variable. Un autre défaut est lié aux branchements. Imaginez qu'un branchement s'exécute et branche vers une instruction de destination. Rien n'indique que l'instruction de destination sera au tout début d'un bloc. Si ça se trouve, elle sera en troisième position d'un bloc de 4 instructions. Une solution simple charge le bloc en respectant l'alignement, et les deux premières instructions du bloc ne sont pas à exécuter. Il y a donc une petite perte de performance. Certains CPU, comme l'IBM RS/6000, ont la machinerie charger un bloc de N instructions même s'il n'est pas aligné. La machinerie en question se trouve dans le cache d'instruction et est tout sauf simple à expliquer,ce qui fait que je me permets de la passer sous silence. Un détail, cependant : elle ne permet pas de charger des blocs qui sont à cheval sur deux lignes de cache. ===Le décodage parallèle des instructions=== Pour le décodage des instructions, plusieurs instructions sont décodées en même temps. On parle alors de '''décodage parallèle'''. Pour ce faire, un processeur superscalaire contient plusieurs décodeurs, chacun pouvant décoder une instruction en parallèle des autres. Prenons par exemple un processeur superscalaire à 4 voies : le décodage se fera dans quatre décodeurs séparés, qui fonctionneront en parallèle. Cependant, cette implémentation ne marche bien que pour les processeurs RISC, les processeurs CISC devant faire avec un microcode gourmand en circuits. Les processeurs CISC utilisent des décodeurs hybrides, avec un microcode qui complémente un décodeur câblé. Dupliquer le microcode aurait un cout en transistors trop important, ce qui fait que seuls les décodeurs câblés sont dupliqués. Les CPU CISC superscalaires disposent donc de plusieurs décodeurs simples, capables de décoder les instructions les plus courantes, avec un seul microcode. La conséquence est qu'il n'est pas possible de décoder deux instructions microcodées en même temps. Par contre, il reste possible de décoder plusieurs instructions non-microcodées ou une instruction microcodée couplée à une instruction non-microcodée. Vu qu'il est rare que deux instructions microcodées se suivent dans un programme, le cout en performance est extrêmement mineur. Il faut noter qu'une instruction correspond à une ou plusieurs µops. Il est donc possible de charger 5 instructions et de se retrouver avec 8 µops en sortie des décodeurs. Et c'est la source d'une distinction très importante. Plus haut, j'ai parlé de processeurs superscalaires à N voies. La terminologie peut avoir deux interprétations. La première est que le processeur charge/décode N instructions à la fois. Une seconde est qu'il est capable d'exécuter/émettre N µops en même temps. Les deux ne sont pas équivalentes, vu qu'un processeur superscalaire peut, par exemple, charger/décoder N instructions et émettre/exécuter N+2 µops. La distinction est particulièrement importante sur les processeurs superscalaires larges. Certains d'entre eux sont capables de charger 5 instructions et d'en émettre le double ! Un exemple classique était les processeurs Athlon d'AMD, qui pouvaient décoder deux instructions par cycle, mais émettre 4 µops par cycle. Ce n'était pas un problème, car beaucoup d'instructions, même basiques, étaient décodées en 2 µops. ===L'émission parallèle des instructions=== La superscalarité modifie en profondeur l'unité d'émission, notamment au niveau de son interface. Avant, elle recevait une micro-opération sur son entrée, et fournissait la micro-opération émise sur une sortie. Et cela vaut aussi bien pour une unité d'émission simple, un ''scoreboard'', une fenêtre d'instruction ou des stations de réservation. Mais avec l'émission multiple, les sorties et entrées sont dupliquées. Pour la double émission, il y a deux entrées vu qu'elle doit recevoir deux micro-opération décodées/renommées, et deux sorties pour émettre deux micro-opérations. Dans ce qui suit, nous ne parlerons pas d'entrée et de sortie, mais de '''ports de décodage''' et de '''ports d'émission'''. Les ports de décodage reçoivent des micro-opérations provenant de l'unité de décodage, les ports d'émissions sont pour les micro-opérations émises. Les ports d'émission injectent les micro-opérations émises dans le pipeline, elle sont propagée au banc de registre, aux ALUs, etc. Un processeur superscalaire a forcément plusieurs ports d'émission (et de décodage). Si l'interface de l'unité d'émission change, l'intérieur est tout autant modifié. Sur un CPU non-superscalaire, l'unité d'émission reçoit une µop "candidate à l'émission", et détecte les dépendances entre avec les µops en vol, en cours d'exécution dans le pipeline. Et c'est toujours le cas sur les processeurs superscalaires : l'unité d'émission détecte les dépendances entre les µops candidates et les µops en vol. Mais avec la siperscalarité, elle doit en plus tester les dépendances entre les µops candidates. Si l'unité d'émission est un ''scoreboard'', il doit détecter les dépendances entre instructions émises simultanément et les sérialiser si besoin. Avec une fenêtre d'instruction, il n'y a pour ainsi dire rien à faire, vu que les dépendances sont éliminées par le renommage de registre et que les signaux de réveil gèrent les dépendances RAW. C'est la raison pour laquelle les processeurs superscalaires modernes n'utilisent pas de ''scoreboard''. ==Les processeurs implémentés avec plusieurs pipelines== Nous venons de voir comment la superscalarité impacte le ''front-end'', il est maintenant de voir ce qu'il en est de l'unité d'émission et des ''back-ends''. Un processeur superscalaire peut utiliser l'exécution dans le désordre et le renommage de registres, mais ce n'est pas une obligation. Historiquement, les premiers processeurs superscalaires n'utilisaient pas d'exécution dans le désordre. Le meilleur exemple est celui du Pentium 1 et 2 d'Intel. Ils étaient tous deux des processeurs superscalaires, mais n'avaient pas d'exécution dans le désordre, qui est apparue sur le Pentium 3. les explications sont plus simples si on met de côté l'exécution dans le désordre. Vous vous dites que l'implémentation d'un processeur superscalaire n'est pas la même suivant que le CPU utilise l'exécution dans le désordre, ou non. Mais dans les faits, la distinction est plus subtile que ca. Pour l'expliquer, je vais utiliser une classification originale, qui sépare les processeurs superscalaires en trois sous-types. La distinction se fait sur la manière dont la superscalarité est implémentée en matériel.La classification que j'utilise distingue : * Les processeurs avec plusieurs pipelines, sans exécution dans le désordre. * Les processeurs superscalaires avec un pipeline dynamique. * Les processeurs avec des fenêtres d'instruction décentralisées. La distinction n'est pas qu'une question d'implémentation matérielle. Elle impacte aussi le ratio entre instructions chargées/décodées et instructions émises. Vous vous dites que les deux sont égales, mais ce n'est le cas que sur les processeurs avec N pipelines ! Sur les autres processeurs, il est possible de décoder 4 instructions, mais d'en émettre maximum 6 par cycle ! Et sur les CPU avec des fenêtres d'instruction décentralisées, il y a aussi une différence entre le nombre d'instructions qui sont ''dispatch'' et ''schedule''. La catégorie la plus simple est celle des '''processeurs avec plusieurs pipelines'''. Ce sont des processeurs superscalaires sans exécution dans le désordre. Ils partent d'un processeur non-superscalaire, et ajoutent un second pipeline, en sortie de l'unité d'émission. Le second pipeline permet l'émission parallèle d'une seconde instruction. Le second pipeline n'est pas identique au pipeline originel, sur plusieurs points. Premièrement, il n'a pas exactement les mêmes circuits. Le second pipeline est généralement composé d'une unité de calcul, d'une FPU, éventuellement d'un second banc de registres, guère plus. Il permet d'exécuter une seconde opération de calcul, pas plus, ce qui est à l'origine de contraintes d'appariemment assez fortes. De plus, il n'a pas forcément la même longueur, il peut avoir des étages en moins ou en plus. Il s'agit de la forme la plus simple de processeur superscalaire et nous allons leur consacrer cette section. ===La double émission entière-flottante=== L'exemple le plus simple utilise un second pipeline pour les opérations flottantes, séparé du pipeline originel. L'ajout d'un pipeline flottant permet d'émettre une micro-opération entière en même temps qu'une micro-opération flottante, ce qui s'appelle la '''double émission entière-flottante'''. La micro-opération entière s'exécute dans l'ALU entière, la micro-opération flottante dans la FPU. Les exemples les plus notables sont le PA-RISC 7100, le POWER 1 (RIOS .& et .9) et le PowerPC 601. L'Apollo DN10000 et l'Intel i860 utilisaient aussi cette forme de double émission, mais étaient des prototypes de CPU VLIW. [[File:Double émission entière-flottante.png|centre|vignette|upright=2|Double émission entière-flottante]] Pour bien expliquer les choses, prenons le processeur illustré ci-dessous, avec deux ''back-end'' séparés : un pour la FPU, et un autre pour le reste. Les instructions entières et mémoire passent toutes par le ''back-end'' généraliste. [[File:CPU non-superscalaire basique, avec une FPU.png|centre|vignette|upright=1.5|CPU non-superscalaire basique, avec une FPU]] Les deux ''back-end'' sont des pipelines séparés, mais il faut que l'unité d'émission soit spécialement conçue pour en profiter. Un processeur simple émission peut démarrer une seule µop par cycle : soit la µop est envoyée au ''back-end'' flottant, soit elle est envoyée au ''back-end'' général. Avec la double émission entière-flottante, le processeur peut envoyer une µop dans chaque pipeline, à chaque cycle. Vu que la FPU et son banc de registre forment déjà un pipeline séparé, les seules modifications se font au niveau du ''front-end'', unité d'émission inclue. {| |- |[[File:CPU simple émission avec une FPU.png|centre|vignette|upright=1.5|CPU simple émission avec une FPU.]] |[[File:Double émission entière flottante avec un second pipeline.png|centre|vignette|upright=1.5|CPU avec un second pipeline flottant.]] |} Notez que le terme double émission ''entière''-flottante est un peu trompeur. En réalité, la technique permet d'émettre une instruction flottante avec n'importe quelle autre instruction non-flottante, avec des restrictions pour les branchements. Il est par exemple possible d'exécuter un calcul flottant en parallèle d'un accès mémoire. Sur les CPU 32 bits, il est possible de faire les multiplications dans la FPU. Pour rappel, la FPU contient un multiplieur entier 53-54 bits, pour multiplier les mantisses de deux flottants 64 bits, qu'on peut utiliser pour faire les multiplications entières 32 bits. Cela permet d'émettre une multiplication en même temps qu'une opération entière dans l'ALU. Ainsi, le CPU pouvait émettre deux instructions : une opération entière de base, avec soit une multiplication, soit une opération flottante. En clair, une émission de type INT + (MUL /FLOAT). Par contre, on perd la possibilité d'émettre une multiplication entière avec une opération flottante, mais c'était un compromis qui en valait la peine. Le CPU PA-RISC 7100 implémentait cette optimisation. Il s'agissait d'un CPU double émission entière-flottante, qui utilisait donc un partitionnement assez simple. Le combo partitionnement + reconditionnement permettait ici de gagner en performance, pour un cout en matériel très limité. D’autant plus limité que le jeu d'instruction prévoyait le reconditionnement de la FPU. Les multiplications ne lisaient pas leurs opérandes dans les registres entiers, mais dans les registres flottants. ===La double émission entière=== Une autre possibilité ajoute un second pipeline, capable d'exécuter des opérations entières simples, appelé le pipeline entier. Le processeur supporte alors la '''double émission entière''', à savoir qu'ils peuvent émettre une opération entière en plus d'une autre instruction. L'opération entière en question est une opération simple, comme une addition, une soustraction, une opération bit à bit, une comparaison, etc. Les exemples classiques sont les processeurs Intel i960 et Pentium. L'implémentation était différente entre l'i960 et le Pentium : Le Pentium avait une seconde ALU entière, alors que le i960 rusait. Le Pentium dupliquait l'ALU entière, pour créer un second pipeline entier. Sur le Pentium originel, les contraintes d'appariement étaient assez strictes. Le Pentium avait une FPU, mais ne pouvait pas l'utiliser en double émission. Il lui était impossible d'exécuter une opération flottante en même temps qu'une opération entière (branchements inclus). Le Cyrix 6x86 était une sorte de Pentium produit par l'entreprise Cyrix, avec une microarchitecture différente. Il avait des contraintes d’appariement réduites. Par exemple, il pouvait émettre une opération flottante en parallèle d'une opération entière. Nous détaillerons ces processeurs dans le prochain chapitre, qui porte justement sur les CPU superscalaires x86. [[File:CPU simple et double émission avec un second pipeline entier.png|centre|vignette|upright=2.5|CPU simple et double émission avec un second pipeline entier.]] L'Intel i960 n'avait pas de FPU, juste une ALU entière et une unité mémoire. Il utilisait l'unité mémoire comme une seconde ALU entière limitée. Pour rappel, une unité mémoire contient une AGU, à savoir une ALU spécialisée dans les calculs d'adresse. Elle est capable de faire des additions, soustractions, et quelques décalages limités (des décalages par 2, 4, 8, pas plus). L'Intel i960 ajoutait des connexions entre AGU et banc de registre, afin que celle-ci serve de seconde unité de calcul. L'AGU est déjà reliée aux deux ports de lecture, pour lire les opérandes du calcul d'adresse, il suffit de la connecter sur le port d'écriture. Le processeur peut alors émettre deux instructions/µops entières, sous certaines conditions. [[File:Emission multiple des opérations entières, double émission.png|centre|vignette|upright=2|Émission multiple des opérations entières, double émission.]] ==Les processeurs superscalaires dynamiques== Les processeurs précédents, avec 2 pipelines, avaient une unité d'émission assez simple. Elle avait deux ports de décodage, et deux ports d'émission. Le nombre de ports de décodage et d'émission étaient identiques. Vous vous dites que c'est naturel, et intuitif, que c'est normal pour un processeur superscalaire. Mais en réalité, la majorité des processeurs superscalaires a plus de ports d'émission que de décodage. Expliquer pourquoi va cependant nous demander de parler de comment la superscalarité exploite les unités de calcul. Posons-nous cette question : de combien d'unités un processeur superscalaire a besoin ? Les unités en question ne sont nécessairement des unité de calcul, l'unité mémoire et l'unité de branchement comptent aussi. La réponse dépend du nombre de voies. Si le ''front-end'' charge et décode un paquet de N instructions, exécuter ces N instructions simultanément demandera là aussi au moins N unités de calcul. Notez le terme : ''au moins'', car c'est là tout l'enjeu de cette section. La réponse dépend aussi des contraintes d’appariement. Pour un CPU superscalaire "parfait", sans contraintes d’appariement, toutes les unités doivent être dupliquées, y compris l'unité mémoire. Un processeur à double émission parfaite devrait dupliquer l'ALU entière, le ''barrel shifter'', le circuit multiplieur, la FPU, l'unité mémoire, et l'unité de branchement, ce qui ferait passer de 6 unités à 12. Le cout en circuit est énorme, même pour de la double émission ! Et pour un CPU à N pipelines, le cout en circuit encore plus prohibitif ! En pratique, les processeurs superscalaires qui font ça sont très rares. La majeure partie des processeurs superscalaires utilisent d'autres techniques : le partitionnement, la duplication partielle des ALUs, la duplication des autres unités. ===Le partitionnement des unités fonctionnelles=== Les CPU superscalaires les plus simples ne dupliquent pas leurs unités de calcul, ils se contentent d'exploiter les unités existantes. Et pour expliquer comment, nous allons partir de l'exemple d'un processeur non-superscalaire contient une ALU entière, une FPU, une unité mémoire et une unité de branchement. Les quatre unités peuvent fonctionner en parallèle, à savoir qu'une unité peut exécuter une instruction à elle toute seule, sans interagir avec les autres. Cela suffit pour créer un processeur quadruple émission, qui peut émettre en même temps : une opération entière, une opération flottante, un branchement, un accès mémoire. Le principe marche aussi sur un processeur avec moins de 4 unités, par exemple sans FPU et/ou sans unité de branchement, ou avec au contraire des unités en plus. L'idée générale est d’émettre une µops séparée dans chaque unité fonctionnelle. Utiliser ainsi les unités existantes, pour rendre un CPU superscalaire, s'appelle le '''partitionnement superscalaire'''. Le terme n'est pas beaucoup utilisé en-dehors de quelques vieux articles académiques. Le partitionnement est fortement dépendant du jeu d'instruction, car il demande de bien séparer instructions entières, flottantes, branchements et instructions mémoire. Et autant c'est bien le cas sur les CPU RISC, autant les CPU CISC ne sont pas dans ce cas. Les CPU CISC ont des instructions ''load-op'', qui effectuent une instruction arithmétique et une µop mémoire. Et ces instructions sont très compliquées à implémenter avec le partitionnement. Et au-delà de ça, les CPU CISC ont beaucoup d'instructions qui sont décodées en plusieurs µops, à exécuter dans un ordre bien précis. On peut pas émettre les µops en question simultanément. Le partitionnement a un cout en circuit très limité, pour un gain en performance lui aussi limité. Pour reprendre le CPU de l'exemple précédent, il est rare que l'on ait à exécuter ces quatre instructions en même temps. En pratique, les combinaisons fréquentes sont les combinaisons calcul + accès mémoire, calcul + branchement, calcul entier + calcul flottant. Utiliser une quadruple émission de ce type n'est donc pas une bonne idée, alors que la double émission parait plus adaptée. Et c'est là que la différence entre ports d'émission et de décodage devient intéressante. L'idée est d'avoir une unité d'émission avec deux ports de décodage, et 4 port d'émission (un par unité). Les contraintes d’appariement varient alors suivant le processeur, mais la règle générale est que si deux micro-opérations tombent dans deux unités différentes, on peut les émettre en même temps. En clair, on peut émettre une opération entière avec une opération flottante, une opération entière avec un branchement, un accès mémoire avec un branchement, etc. Et cela rend le partitionnement bien plus intéressant que prévu. Les tout premiers CPU superscalaires étaient des CPU double émission, qui utilisaient le partitionnement avec : une ALU entière, une unité mémoire et une FPU. Nous parlerons de '''partitionnement INT-FLOAT-MEM'''. L'avantage sur la double émission flottante est que les combinaisons INT + MEM et FLOAT + MEM étaient autorisées. L'intérêt était d'émettre une opération en parallèle d'un accès mémoire, ce qui est un doublet d'instruction assez fréquent. Les exemples classiques sont l'HyperSPARC, de l'alpha 21064 et du PowerPC 603. Les contraintes d’appariement variaient grandement suivant le processeur. Le PowerPC 603 implémentait une double émission "parfaite". C'est à dire que toutes les combinaisons possibles à deux instructions étaient possibles, que ce soit INT + FLOAT, INT + MEM, FLOAT + MEM. En plus de cette double émission "parfaite", le CPU utilisait la technique dite de ''branch folding'', qui permettait d'exécuter un branchement sans utiliser d'unité de calcul, ce qui fait qu'il était qualifié de ''CPU 2-way + Branch''. A l'opposé, le processeur alpha 21064 avait pas mal de contraintes d’appariement, à savoir que certaines combinaisons de deux instructions étaient interdites, bien que compatibles avec les unités disponibles. Une des raisons est que le banc de registre n'avait pas assez de ports de lectures, ce qui fait que certaines combinaisons avec une écriture mémoire n'étaient pas permises. Les concepteurs avaient volontairement inséré ces contraintes afin de simplifier le design du processeur. Avant de poursuivre, précisons que la FPU regroupe un additionneur flottant et un multiplieur flottant. Et ce fait peut être exploité pour approfondir le partitionnement. La '''double émission FADD/FMUL''' permet d'émettre une addition flottante en même temps qu'une multiplication flottante. Elle a été utilisée sur les processeurs x86, Alpha et SPARC. Les autres circuits de calcul flottant sont souvent regroupés avec l'additionneur. La division est parfois e même port que le multiplieur flottant, parfois sur celui de l'additionneur (quand elle est microcodée et exécutée en enchainant les soustractions). ===La duplication des ALU entières=== Le partitionnement est une technique élégante, simple, facile à implémenter et au cout en circuits modeste. Mais il faut avouer que cela ne donne pas des CPU superscalaires très efficaces. La raison est que le partitionnement permet d'émettre des paires, triplets, quadruplets de micro-opérations qui sont rares en pratique. Les combinaisons les plus fréquentes en pratique sont des paires/triplets d'opérations entières, souvent des paires/triplets d'additions ou d'opérations entières simples. Il est donc primordial d'émettre plusieurs opérations entières simultanément. Le fait d'émettre et d'exécuter plusieurs opérations entières en même temps s'appelle l''''émission entière multiple'''. Pour exécuter la seconde/troisième opération entière, il faut dupliquer les ALU entières (ou réutiliser l'AGU comme ALU entière comme le fait l'Intel i960). Le processeur doit avoir deux ALU entières pour de la double émission entière, trois pour de triple émission entière, etc. De plus, il faut ajouter des ports au banc de registre, pour lire deux fois plus d'opérandes et décrire deux résultats. Et enfin, il faut rajouter un port d'émission pour pouvoir émettre une micro-opération entière en plus. [[File:Processeur non-superscalaire basique 03.png|centre|vignette|upright=2|Processeur non-superscalaire basique]] La tendance est de multiplier les ALU entières, mais pas le circuit multiplieur. Le cout en transistors est alors grandement réduit, au prix de l'apparition de dépendances structurelles. Le CPU peut émettre deux additions en même temps, ou une addition et une multiplication, mais pas deux multiplications. Il en est de même avec le ''barrel shifter'', qui n'est pas dupliqué. Il est donc possible d'émettre un décalage avec une opération de base, mais pas deux décalages en parallèle. Mais ces contraintes d’appariement ne sont pas un problème, car il est rare de devoir émettre deux multiplications/décalages en même temps. La plupart des programmes sont majoritairement remplis d'additions, avec des multiplications assez rares et des décalages qui le sont encore plus. La moyenne est proche d'une multiplication pour 4/5 additions. Vu que des multiplications consécutives sont très rares, dupliquer le multiplieur est presque inutile. Disposer de plusieurs circuits multiplieurs ou de plusieurs ''barrel shifters'' serait donc un cout en circuits qui ne servirait que rarement et n'en vaut pas la chandelle. Il faut noter que sur les CPU 32 bits, les multiplications peuvent être exécutées dans la FPU ou dans un multiplieur séparé, tout dépend de si le CPU exécute les multiplications dans la FPU ou pas. {|class="wikitable" |- ! Voie d'émission n°1 !! Voie d'émission n°2 !! Voie d'émission n°3 |- | ALU entière || ALU entière || FPU |} Reste à connecter les unités de calcul à l'unité d'émission (indirectement, car il y a des étages de pipeline entre les deux). Une implémentation basique utilisee un port d'émission par unité. Prenons l'exemple d'un CPU superscalaire avec 4 ALU entières, une FPU, et une unité mémoire. Vu qu'il y a 6 unités, cela fait donc 6 ports d'émission. L'exemple historique le plus impressionnant est celui du Motorola 88110, un CPU double émission qui possède 10 unités. Il contient deux ALU entières, un circuit multiplieur, un circuit diviseur, une unité de manipulation de bits, une unité mémoire, un additionneur flottant, et deux unités graphiques pour des calculs impliquant des pixels. Le multiplieur est utilisé à la fois pour les multiplications entières, flottantes et le produit de deux pixels. Idem pour le diviseur, qui sert pour les divisions entières et flottantes. Et chaque unité a son propre port d'émission. La conséquence est que les contraintes d'appariement du Motorola 88110 sont simples : tant que deux µops atterrissent dans des unités séparées, la paire est permise. Il est par exemple possible d'émettre simultanément deux opérations entières simples, vu qu'il y a deux ALU. Par contre, impossible d'émettre simultanément deux multiplications, deux divisions, deux opérations flottantes, deux manipulations de bit, deux accès mémoire, etc. De plus, le partage du multiplieur fait qu'on ne peut pas émettre une multiplication entière avec une multiplication flottante. Idem avec les divisions, vu que le diviseur est aussi partagé. Ce sont les seules contraintes. ===Le partage des ports d'émission=== La présence d'un circuit multiplieur et d'un ''barrel shifter'' pose des problèmes pour le partitionnement. Il est possible d'avoir un port d'émission dédié au multiplieur, et un autre au ''barrel shifter'', qui sont séparés de ceux pour l'ALU entière. Il est alors possible d'émettre une opération entière, une multiplication et un décalage, simultanément. Nous parlerons de '''partitionnement entier''' pour désigner cette possibilité. [[File:Partitionnement superscalaire entier.png|centre|vignette|upright=2|Partitionnement superscalaire entier.]] Le partitionnement entier est cependant très rare, car le gain en performance serait trop faible au regard des couts en circuit. Quelques processeurs historiques l'utilisaient, mais ce n'est plus le cas de nos jours. La raison est que les multiplications et les décalages sont assez rares dans le code, ce qui fait que le port d'émission associés sont sous-utilisés. Et un port d'émission a un cout en circuit assez important. Et c'est sans compter qu'ajouter des ports sur le banc de registres juste pour un multiplier sous-utilisé n'en vaut pas la peine. L'idéal serait de trouver un moyen de réduire le nombre de ports d'émissions, sans réduire le nombre d'unités de calcul. [[File:Emission multiple des opérations entières, implémentation naive.png|vignette|Émission multiple des opérations entières, implémentation naive]] La solution consiste à '''partager les ports d'émission''', à savoir connecter un port d'émission à plusieurs unités de calcul. L'exemple classique est justement lié aux circuits multiplieur et ''barrel shifters''. L'idée est que le multiplieur et le ''barrel shifter'' sont mis sur le même port d'émission qu'une ALU entière. Prenons par exemple le processeur à double émission entière illustré ci-contre. Un port d'émission est relié à une ALU entière seule, et l'autre port d'émission alimente la seconde ALU entière et le multiplieur. La conséquence est que le processeur peut émettre deux opérations entières, dont une multiplication. Si on avait utilisé un port d'émission dédié au multiplieur, il aurait été possible d'émettre deux opérations entières et une multiplication. On perd donc une voie, mais on gagne en flexibilité et on économise des circuits. Notons que ce partage signifie aussi que le multiplieur partage les ports sur le banc de registre, ce qui fait une double économie. [[File:Emission multiple des opérations entières, implémentation courante.png|centre|vignette|upright=1.5|Émission multiple des opérations entières, implémentation courante]] En général, le ''barrel shifter'' et le multiplieur sont placés sur deux voies d'émission séparés, ce qui permet d'émettre un décalage et une multiplication en même temps. Les mettre sur le même port ne le permettrait pas. {|class="wikitable" |- ! Voie d'émission n°1 !! Voie d'émission n°2 !! Voie d'émission n°3 |- | * ALU entière * Multiplieur | * ALU entière * Barrel shifter | FPU |- | * Additions, soustractions, opérations bit à bit, etc. * Multiplication | * Additions, soustractions, opérations bit à bit, etc. * Décalages et rotations | Opération flottante |} Mais le partage des ports d'émission peut appliquer à n'importe quelle unité. Prenons l'exemple d'un CPU superscalaire avec 4 ALU entières, une FPU, et une unité mémoire. Vu qu'il y a 6 unités, cela fait donc 6 ports d'émission. Cependant, il est rare qu'un bloc de 6 instructions contienne exactement 4 opérations entières, une opération flottante, et un accès mémoire. Beaucoup de ports d'émission seraient sous-utilisés. La solution est de partager des ports d'émission, pour passer à la quadruple émission. Le CPU peut alors répartir 4 instructions sur les ports d'émission adéquats, à chaque cycle. Le CPU a donc 4 ports de décodage et 6 ports d'émission. Une implémentation classique permet n'importe quelle combinaison de 4 instructions compatible avec les 6 unités. Par exemple, il devient possible d'émettre simultanément 4 additions, la combinaison 2 * INT + FLOAT + MEM, 3 * INT + MEM, et bien d'autres. L'exemple relie une ALU entière, la FPU et l'unité mémoire sur le même port. Le CPU a alors quatre ports d'émission, qui ne sont pas "identiques". Trois d'entre eux sont reliés à une ALU entière chacun, rien d'autre. Le quatrième est lui relié à une ALU entière, la FPU et l'unité mémoire. On économise alors deux ports d'émission, mais cela entraine l'apparition de contraintes d’appariement. Le CPU peut émettre 3 opérations entières + n'importe quelle autre opération. Mais les autres combinaisons sont interdites. Le CPU peut émettre 6 opérations entières simultanément, grâce à 6 ALU entières. Les accès mémoire ou opérations flottantes ne viennent pas en plus, mais en remplacement des opérations entières. Regrouper plusieurs ALU sur un même port d'émission est donc à l'origine de dépendances structurelles, de nouvelles contraintes d’appariement. Dans certaines situations de '''''port contention''''', on a assez d'ALU pour exécuter N micro-opérations, mais la répartition des ALUs sur les ports l'empêche. Impossible d'émettre deux micro-opérations sur deux ALU si elles sont sur le même port d'émission. On peut réduire les cas de ''port contention'' en répartissant correctement les ALU sur les ports d'émission, mais sans jamais les supprimer complétement. Un exemple est celui du PowerPC 604. Il implémentait la quadruple émission, alors qu'il avait 5 unités de calcul. Mais deux unités partageaient un même port d'émission. Dans le détail, il avait deux ALU entières, un circuit multiplieur/diviseur, une FPU, une unité mémoire. La FPU et le multiplieur étaient sur le même port d'émission. En conséquence, le CPU pouvait émettre seulement les combinaisons INT + INT + MEM + (MUL/FLOAT). : Il est intéressant de voir l'exemple d'un processeur superscalaire parfait, sans contraintes d'appariement, à N voies. Pour cela, on part d'un processeur non-superscalaire, avec M unités de calcul. Un tel processeur peut s'implémenter avec N ports d'émission, chacun relié à M unités de calcul possibles, ce qui fait que les M unités de calcul sont dupliquées en N exemplaires. Si on prend par exemple un CPU non-superscalaire avec une ALU, une FPU, une unité mémoire et une unité de branchement, un port d'émission sera relié à une ALU, une FPU, une unité mémoire et une unité de branchement. ===La duplication des autres unités fonctionnelles=== Dupliquer les ALU entière est une première étape, mais il est aussi possible de dupliquer les autres unités fonctionnelles. Par exemple, il est possible de dupliquer l'unité mémoire ou l'unité de branchement. Reste à voir quelles unités dupliquer, et pourquoi. Précisons que dans ce qui va suivre, nous allons parler de la duplication des unités en général, pas seulement des unités mémoire/branchement. Nous allons continuer de parler de la duplication des ALU entières dans ce qui suit, mais de manière plus générale. Pour un processeur à N voies, il est théoriquement possible de dupliquer toutes les unités en N exemplaires. Mais ce n'est jamais fait en pratique, car le cout en circuits serait trop important et le gain en performance trop faible. Mieux vaut dupliquer certaines unités bien précises, quitte à avoir des contraintes d'appariement. Et à ce petit jeu, toutes les duplications ne se valent pas, certaines sont plus rentables en matière de performance, le cout en circuit n'est pas le même selon l'unité, etc. La rentabilité en performance dépend surtout des contraintes d'appariement. Les contraintes d'appariement entrainent une baisse de performance significatives si elles interdisent des paires d'instructions fréquentes. Aussi, les opérations très fréquentes, souvent appairées, gagnent à dupliquer l'unité associée. Et au contraire, les unités rarement utilisées ne doivent pas être dupliquées. Il se trouve que les unités les plus utilisées sont les ALU entières, suivies par les unités mémoire et l'unité de branchement. Le cout en circuit n'est aussi pas le même. Dupliquer les ALU entière est trivial, car elles ont un budget en transistors assez faible. Les circuits multiplieurs, diviseurs, et les ''barrel shifter'' ont eux un cout en transistor plus élevés. Si on devait faire un classement, on aurait : ALU entière < ''barrel shifter'' < multiplieur. Pour les unités mémoire, le cout en transistor est là aussi conséquent. Le résultat final est que dupliquer les ALU entières est privilégié sur le reste, comme l'a vu dans la section précédente. Les unités mémoire arrivent en seconde place, suivie par les multiplieurs/''barrel shifter'' à la troisième place, et les unités de branchement à la quatrième place. Les CPU superscalaires larges ont donc tendance à multiplier les unités mémoire en second. Les multiplieurs et ''barrel shifters'' sont eux aussi dupliqués sur les CPU superscalaires larges, mais cela vient souvent après avoir dupliqué les unités mémoire. La raison est que les accès mémoire sont critiques pour la performance des CPU à exécution dans le désordre, pas les multiplications. Pour les unités de branchement, une subtilité complique le fait d'en installer plusieurs. Que faire quand deux branchements sont pris ? La solution la plus simple est de ne pas se trouver dans cette situation : on n'utilise qu'une seule unité de branchement et on n’exécute qu'un seul branchement par cycle. La majorité des processeurs superscalaires étroits font ainsi. Les processeurs superscalaires larges ont cependant deux unités de branchements, mais ils n'acceptent qu'un seul branchement pris à la fois. Si les deux branchements pris sont exécutés en même temps, le processeur n’exécute que le plus récent, dans l'ordre du programme. ===Les CPU superscalaires mélangent les trois techniques=== Les CPU superscalaires utilisent toutes les techniques à la fois : partitionnement, duplication de l'ALU entière, duplication des autre unités fonctionnelles. Chaque processeur utilise ces trois stratégies à sa sauce, en fonction de son budget en transistor. Les quatre techniques ont cependant un cout en circuit différent, et donnent des gains de performances eux aussi forts différents. * Le '''partitionnement superscalaire''' a un cout en circuit modéré, car on n'a pas besoin de dupliquer d'ALU. * La '''duplication des ALU entières''' est la technique la plus importante : elle a un cout en circuit modéré, et donne des gains en performance impressionnants. * La '''duplication des autres unités''' a un cout en circuit variable, pour des gains en performance eux aussi variables. * Le '''partage des ports d'émission''' réduit le cout en circuit du CPU, pour des pertes de performances mineures si bien fait. L'usage du partitionnement seul, sans duplication, était surtout le fait des processeurs commercialisés entre les années 1989 et 1993. Le partage des ports d'émission a lui aussi mis un peu de temps pour s'imposer. Globalement, les processeurs superscalaires ont évolués progressivement, pour se rapprocher d'une sorte de standard. Mais pour comprendre ce qu'est ce standard, et en quoi les CPU historiques s'en sont progressivement rapprochés, il faut étudier l'histoire des CPU superscalaires. L'histoire des CPU superscalaires a commencé dès 1960, mais est resté un projet de recherche pendant plusieurs décennies. De nombreux designs ont été conçus, mais abandonnés avant la production, comme l'ACS-1 d'IBM. Les années 1980 ont vu l'arrivée de prototypes de processeurs VLIW, mais qui n'étaient pas superscalaires. Par exemple, l'Apollo DN10000 et l'Intel i860 pouvaient émettre deux instructions à la fois, sous conditions, mais demandaient que le compilateur fasse une bonne partie du travail. Les premiers CPU superscalaires commercialisés sont sortis en 1989. Deux CPUs superscalaires sont sortis cette année là : le RS/6000 d'IBM et l'i960CA d'Intel. Puis, la compétition s'est aussi lancée dans la bataille. Les CPU commercialisés avant l'année 1993 étaient à double émission, à l'exception du SuperSPARC qui utilisait la triple émission. Les modèles après 1993 hésitaient entre double et quadruple émission. Dans le tableau ci-dessous, les processeurs quadruple émission sont en bleu, les triple émission sont en rouge, les autres ne sont pas en couleur. {|class="wikitable" |- ! !! IBM !! HP PA-RISC !! SPARC !! Intel!! Alpha !! Autres |- ! 1989 | RS/6000 || || || i960CA || || |- ! 1990 | || || || || || |- ! 1991 | || || || || || Motorola 88110 |- ! 1992 | RSC || PA 7100 || class="f_rouge | SuperSPARC || || 21064 || |- ! 1993 | PPC 601 || || HyperSPARC || Pentium || || |- ! 1994 | class="f_bleu | POWER 2 || PA 7200 || || || class="f_bleu | 21164 || |- ! 1995 | 603/604 || class="f_bleu | PA 8000 || class="f_bleu | UltraSPARC 1 || Microarchitecture P6 (+ concurrence AMD) || || class="f_bleu | MIPS R1000 |- ! 1996 | class="f_bleu | 620 || || || || class="f_bleu | 21264 || |- ! ... || ... || ... || ... || ... || ... || ... |} Les premiers CPU à double émission sont assez représentatifs des CPU à double émission modernes. Il faut dire que se limiter à deux voies ne permet pas de faire beaucoup de choses. Voici le tableau chronologique disant quel CPU utilisait quelle technique. * Les CPU en vert utilisaient la double émission entière-flottante. * Les CPU en rouge utilisaient la double émission entière "pure". * Les CPU en jaune utilisaient un ''partitionnement complet'' entre ALU, FPU et unité mémoire. * Les CPU sans couleur utilisaient un grand nombre d'unités, avec beaucoup de ports d'émission. * Les CPU barrés utilisent la triple ou quadruple émission et seront vu dans la section suivante. {|class="wikitable" |- ! !! IBM !! HP PA-RISC !! SPARC !! Intel!! Alpha !! Autres |- ! 1989 | class="f_vert | RS/6000 || || || class="f_rouge | i960CA || || |- ! 1990 | || || || || || |- ! 1991 | || || || || || Motorola 88110 |- ! 1992 | class="f_vert | RSC || class="f_vert | PA 7100 || <s>SuperSPARC</s> || || class="f_jaune | 21064 || |- ! 1993 | class="f_vert | PPC 601 || || class="f_jaune | HyperSPARC || class="f_rouge | Pentium || || |- ! 1994 | <s>POWER 2</s> || PA 7200 || || || <s>21164</s> || |} C'est en 1994, que la quadruple émission est arrivée et que les CPU ont essayé divers implémentations. Il y avait alors trois stratégies pour la double/triple/quadruple émission, qui partageaient des problèmes similaires. La première implémentation dupliquait les ALUs et les FPUs, ce qui permettait d'émettre deux opérations flottantes avec deux opérations entières. Le POWER 2 dupliquait ses FPUs à la perfection, alors que le 21264 séparait l'additionneur et le multiplieur flottant. Les accès mémoire étaient traités comme des opérations entières et le processeur pouvait souvent en émettre deux à la fois. Mais une telle organisation n'est plus utilisée de nos jours, car, sans instructions flottantes à exécuter, elle est équivalente à de la double émission. La seconde implémentation utilisait deux ALU entières (dont un multiplieur), une unité mémoire et une FPU.Cette organisation est intéressante, mais encore trop stricte pour donner de bons résultats, les quadruplets avec exactement deux opérations entières, un accès mémoire et une opération flottante étant trop rares. L'usage de ces 4 unités en double ou triple émission a été utilisé sur le PA 7200 et le SuperSPARC. Malheureusement, en plus de problèmes au niveau de son cache, le SuperSPARC avait de nombreuses contraintes d’appariement, car le banc de registre n'avait pas assez de ports pour alimenter certaines combinaisons d'instructions. De plus, s'il émettait une multiplication, il ne pouvait pas émettre de seconde µop. Enfin, une troisième implémentation utilisait un grand nombre d'unités fort différentes, bien plus que quatre, avec au minimum deux ALU entières. Prenons l'exemple de l'UltraSPARC, qui avait : deux ALU entières, une unité mémoire, une unité de branchement, un additionneur flottant, un multiplieur flottant, un diviseur flottant (aussi utilisé pour le calcul des racines carrées), et 2 unités graphiques pour les calculs sur des pixels. En tout, cela fait 9 unités différentes, pour de la quadruple émission. Dans le même genre, les processeurs MIPS R8000 et R10000 avaient une implémentation plus moderne, dans le sens où toutes les unités étaient dupliquées. Le CPU avait deux ALU, deux FPU et deux unités mémoire. Il pouvait donc émettre quatre instructions à la fois, tant que le paquet collait avec ces six unités. Moitié opérations entières, moitié opérations mémoire, avec de quoi remplacer les opérations entières par des opérations flottantes. Le PA 8000, lui, dupliquait toutes ses unités en deux exemplaires, ce qui n'est pas l'idéal, aucun processeur moderne ne fait cela aujourd'hui. Le tout est résumé dans le tableau ci-dessous, avec : * en vert les CPU utilisant la première stratégie ; * en rouge les CPU utilisant la seconde stratégie ; * en jaune les CPU utilisant la troisième stratégie. {|class="wikitable" |+ Processeurs quadruple émission historiques |- ! Année !! Processeur !! Unités |- ! 1991 | class="f_jaune | Motorola 88110 || class="f_jaune | 2 ALU + MUL + DIV + BIT + MEM + FADD + 2 unités graphiques |- |- ! rowspan="2" | 1994 | class="f_vert | POWER 2 || class="f_vert | 2 ALU/MEM + 2 FPU (+ 2 Branchements via ''branch folding'') |- | class="f_rouge | 21164 || class="f_rouge | 2 ALU + FPU + MEM |- ! rowspan="3" | 1995 | PA 8000 || 2 ALU + 2 ''barrel shifters'' + 2 MEM + 2 FPU (FMA) + 2 FPU (DIV et SQRT) + 2 SIMD |- | class="f_jaune | UltraSPARC 1 || class="f_jaune | 2 ALU + MEM + BRANCH + FADD + FMULFDIV + 2 unités graphiques |- | MIPS R8000 et R10000 || 2 ALU + 2 FPU + 2 MEM |- ! rowspan="2" | 1996 | class="f_rouge | 620 || class="f_rouge | 2 ALU + FPU + MEM |- | class="f_vert | 21264 || class="f_vert | 2 ALU/MEM + 2 FPU (FADD + FMUL) |} Malheureusement, ces CPUs historiques n'avaient ni assez d'unités de calcul entières, ni d'unités mémoire, pour remplir la quadruple émission. Environ la moitié des instructions d'un programme correspond à des instructions entières. Pour en profiter, un processeur à N voies doit disposer d'au minimum N/2 ALU entières, l'idéal étant d'aller au-delà. Les CPU superscalaires historiques avaient 2 ALU pour de la quadruple émission, ce qui était très limite. En comparaison, les CPU superscalaires modernes mettent le paquet niveau ALU entière. La plupart des CPU x86 modernes descendent rarement sous les 2/3, la plupart remplit presque la totalité de la largeur du CPU. Pour donner un exemple, la microarchitecture Golden Cove d'Intel est un processeur à 6 voies, avec 5 ALU entières. Pour être précis, un programme informatique exécute environ 40% de son code dans des instructions arithmétiques, un autre 40% dans les accès mémoire, et un le reste pour... le reste. La conséquence est que les processeurs modernes dupliquent leurs unités mémoire. Par exemple, la microarchitecture ''Golden Cove'' d'Intel dispose de trois unités mémoire : deux spécialisées dans les écritures, trois dans les lectures. La microarchitecture ''Gracemont'' utilise la pentuple émission et peut émettre 4 µops entières et 4 µops mémoire. Notez que le ratio est équilibré entre opérations entières et mémoire, avec soit égalité, soit un avantage pour les ALU entières. Une autre régularité est que la largeur totale du processeur peut être occupée par des opérations entières/mémoire. Pour le dire autrement, sur un processeur quadruple émission, il pourra émettre un mix de 4 instructions entière/mémoire, avec zéro opération flottante. Idem avec des processeurs triples ou pentuple émission, voire plus. Si le processeur émet une opération flottante, elle remplace une opération entière. Prenons l'exemple des processeurs AMD K6, sortis en 1997, juste avant les AMD Athlon. Ils avaient 10 unités de calcul, 6 ports d'émission, 4 ports de décodage. Voici exactement comment étaient répartie les unités de calcul sur les ports d'émission. C'était des processeurs quadruple émission, ce qui fait que la moitié de la largeur du processeur sert pour des opérations entières, et l'autre moitié pour les accès mémoire. {|class="wikitable" |- ! Port d'émission n°1 !! Port d'émission n°2 !! Port d'émission n°3 !! Port d'émission n°4 !! Port d'émission n°5 !! Port d'émission n°6 ! |- | ALU entière || ALU entière || FPU || Unité de branchement || Unité LOAD || Unité STORE |- | || Multiplieur || || || || Unité de calcul d'adresse (instruction LEA) |- | || ''Barrel shifter'' || || || || Unité pour les instructions PUSH |} Les microarchitectures K7, K8 et K10, utilisées sur les anciens CPU AMD Athlon, sont un exemple moins représentatif. Ces microarchitectures utilisent le partage des ports d'émission au maximum. C'était des microarchitectures à triple émission, avec six ports d’émission. Il y avait trois ports d'émission pour les opérations flottantes, et trois ports d'émission pour les micro-opérations entières/mémoire. Il y avait trois ALU entières et trois unités mémoire, chaque ALU partageait un port d'émission avec une unité mémoire. Elle pouvait donc émettre soit trois opérations entières, soit trois µops mémoire, soit un mix des deux. En plus des mix avec les opérations flottantes et branchements. {|class="wikitable" |- ! Port d'émission n°1 !! Port d'émission n°2 !! Port d'émission n°3 !! Port d'émission n°4 !! Port d'émission n°5 !! Port d'émission n°6 ! |- | ALU entière || ALU entière || ALU entière || FADD || FMUL || FPU (autres opérations) |- | Multiplieur || || || || || |- | LOAD/STORE || LOAD/STORE || LOAD/STORE || || || |} Les CPU superscalaires larges dupliquent les multiplieurs et/ou les ''barrel shifters'', et même les unités de branchement. La raison est que quand la largeur du CPU augmente, le processeur traite des paquets d'instructions de plus en plus gros. Par exemple, pour un CPU à 10 voies, cela veut dire émettre 10 instructions en même temps. Il est alors plus fréquent d'avoir deux multiplications, deux décalages, deux accès mémoire, deux branchements. Par exemple, la moyenne est d'une multiplication tous les 5 instructions. Les CPU superscalaires à 10 voies ont donc tendance à avoir deux multiplications par paquet d'instruction, en moyenne. Avoir deux multiplieurs est alors un gain en performance clair. Par exemple, la microarchitecture ''Golden Cove'' a deux ''barrel shifters''. C'est une architecture à 6 voies. {|class="wikitable" |- ! Port d'émission n°1 !! Port d'émission n°2 !! Port d'émission n°3 !! Port d'émission n°4 !! Port d'émission n°5 !! Port d'émission n°6 !! Port d'émission n°7 !! Port d'émission n°8 !! Port d'émission n°9 !! Port d'émission n°10 |- | ALU entière || ALU entière || ALU entière || ALU entière || ALU entière || LOAD || LOAD || LOAD || STORE || STORE |- | ''Barrel shifter'' || Multiplieur || Multiplieur || ''Barrel shifter'' || || || || || || |- | Branchements || Diviseur || || Branchements || || || || || || |} ==Les µops mémoire sur les processeurs superscalaires== Plus haut, nous avons volontairement passé sous silence les accès mémoire. La raison à cela est que leur support sur les CPU superscalaire peut se faire de plusieurs manières différentes, qu'il vaut mieux expliquer dans une section dédiée. Les différentes implémentations sont décrites dans la liste suivante, nous allons les voir une par une. * Avec la première méthode, les accès mémoire sont exécutés par le pipeline pour les opérations entières. * Avec la seconde méthode, les accès mémoire ont un pipeline dédié ; c'est l'''émission parallèle des accès mémoire''. * Avec la troisième méthode, il est possible d'émettre plusieurs accès mémoire simultanément : c'est l'''émission multiple des accès mémoire''. Voyons ces trois cas dans le détail. Nous verrons notamment les différentes formes d'émission multiple des accès mémoire. ===Les accès mémoire traités dans le pipeline entier=== Dans le cas le plus simple, les accès mémoire sont traités comme des opérations entières un peu spéciales. L'unité mémoire est sur le même port d'émission que l'ALU entière, ce qui fait que les accès mémoire remplacent des opérations entières. Faire ainsi permet d'utiliser une ALU entière pour les calculs d'adresse, pas besoin de rajouter une unité de calcul d'adresse séparée. Typiquement, un processeur de ce type place l'unité mémoire après l'unité de calcul. Les opérations entières n'utilisent que l'ALU entière, mais pas l'unité mémoire. Les accès mémoire exécutent un calcul d'adresse dans l'ALU entière, puis l'adresse calculée est envoyée à l'unité mémoire. Le tout est illustré ci-dessous, dans le cas d'un processeur historique à double émission entière-flottante. [[File:Processeur non-superscalaire basique 01.png|centre|vignette|upright=1.5|Processeur non-superscalaire basique]] Un exemple de CPU double émission de ce type est celui du Power PC 440, un CPU destiné à l'embarqué. Vu qu'il était destiné à l'embarqué, il n'avait pas de FPU, il n'avait pas besoin de manipuler de nombres flottants. Il n'a donc que des ALU entières, un multiplieur et une unité mémoire. Il dispose de deux ports d'émission. Le premier est connecté à une ALU entière et un circuit multiplieur, le second est relié à l'unité mémoire et une seconde ALU entière. Une organisation simple, mais très efficace ! [[File:PowerPC 440.png|centre|vignette|upright=2|Microarchitecture du PowerPC 440.]] L'implémentation économise une unité de calcul d'adresse, mais n'a pas de cout en performance significatif. Les accès mémoire étant très lents, émettre une µop entière en parallèle d'un accès mémoire ne sert pas à grand-chose. On peut émettre cette µop entière un cycle plus tard, elle sera terminée avant même que l'accès mémoire soit terminé, même si l'accès mémoire se fait dans le cache L1 ou L2. Pour comprendre pourquoi, faisons quelques calculs. Un accès mémoire prend deux-trois cycles quand il tombe dans le cache L1. Et on doit attendre qu'il soit terminé pour en émettre un nouveau. Combien d'instructions arithmétiques peut-on lancer pendant ce temps, sur un CPU triple émission ? Au premier cycle, on peut en émettre 2 (l'instruction mémoire prend une place, donc 3-1), puis 3 au second cycle. Ce qui fait 5 instructions arithmétiques au total. Or, il se trouve qu'il y a une instruction d'accès mémoire pour 5 instructions arithmétiques, environ. Par contre, ce compromis change quand on passe à la quadruple émission, voire au-delà. Dans ce cas, il devient intéressant de pouvoir émettre une nouvelle instruction mémoire à chaque cycle. Ce qui nous amène à la section suivante... ===L'émission parallèle des accès mémoire=== A partir de la quadruple émission, la tendance a été de séparer l'unité mémoire des ALU entières. Cela permet d'émettre des µops mémoire en même temps que des µops entières et/ou flottantes., ce qui porte le nom d''''émission parallèle des µops mémoire''', ou encore ''émission mémoire parallèle''. Par exemple, un processeur quadruple émission peut émettre en même temps deux opérations entières, une opération flottante et un accès mémoire. L'accès mémoire vient en plus des opérations entières, il n'est pas possible de remplacer l'accès mémoire par une opération entière. La technique ne permet pas de faire les calculs d'adresse dans l'ALU entière. Il faut donc utiliser une unité de calcul d'adresse séparée. Elle est intégrée dans l'unité mémoire, qui fait l'interface entre le cache et le banc de registres. Le cout en circuits est très faible, car les unités de calcul d'adresse n'utilisent guère plus qu'un additionneur et un circuit de décalage très simple. L'émission mémoire parallèle est plus facile à implémenter sur les CPU RISC, qui ont des instructions mémoire séparées du reste. Les CPU CISC ne sont pas dans ce cas, ce qui fait que séparer l'unité mémoire du pipeline entier est compliqué. Les CPU superscalaires historiques étaient pour moité des CPU RISC et pour moitié des CPU CISC, ce qui fait qu'ils n'utilisaient pas ou peu l'émission mémoire parallèle. Les premiers processeurs superscalaires se contenaient de l'émission parallèle mémoire, ils ne pouvaient émettre qu'un seul accès mémoire à la fois, l'unité mémoire n'était pas dupliquée. Et ce n'est pas tant un problème que ça, du moins sur les processeurs à double ou triple émission. En effet, il est rare que l'unité mémoire doive faire deux accès simultanés. Notons que le processeur peut exécuter une instruction mémoire en parallèle d'un calcul ou d'un branchement, seuls les accès mémoire simultanés sont impossibles. ===L'émission multiple des accès mémoire avec une LSQ=== Les processeurs superscalaires larges travaillent sur des paquets de 8 à 10 instructions, il est plus fréquent d'avoir plusieurs accès mémoire par paquet. Dans ces conditions, il est utile d'avoir une '''émission multiple des accès mémoire''', à savoir d'émettre plusieurs micro-opérations mémoire en même temps. Les processeurs superscalaires modernes sont capables d'émettre plusieurs lectures/écritures simultanément. Par exemple, ils peuvent émettre une lecture en même temps qu'une écriture, ou plusieurs lectures, ou plusieurs écritures. Il faut noter que selon le processeur, il peut y avoir des restrictions quant aux accès mémoire émis en même temps. Par exemple, certains processeurs peuvent émettre une lecture avec une écriture en même temps, mais pas deux lectures ni deux écritures. Ou encore, ils peuvent émettre deux lectures, une lecture et une écriture, mais pas deux écritures en même temps. Dans la majorité des cas, les processeurs ne permettent pas d'émettre deux écritures en même temps, alors qu'ils supportent plusieurs lectures. Il faut dire que les lectures sont plus fréquentes que les écritures. Les processeurs qui autorisent toutes les combinaisons de lecture/écriture possibles, sont rares. L'émission multiple des accès mémoire s'implémente dans l'unité mémoire. Pour rappel, l'unité mémoire s'interpose entre le cache L1 et le reste du pipeline. Dans le cas le plus simple, elle incorpore une ''load/store queue'' et des unités de calcul d'adresse. L'émission multiple des accès mémoire demande de rendre la ''Load-store queue'' multi-ports, afin de gérer plusieurs micro-opérations mémoire simultanées. Il faut aussi dupliquer les unités de calcul d'adresse reliées à la LSQ, chaque port d'émission mémoire devant avoir sa propre unité de calcul d'adresse. : Dans ce qui suit, nous parlerons d'AGU (''Adress Generation Unit'') pour désigner les unités de calcul d'adresse, et de LSQ pour designer la ''Load-store queue''. [[File:Processeur non-superscalaire basique 02.png|vignette|upright=1|Processeur non-superscalaire basique]] Dupliquer les ports de la LSQ et les AGU est nécessaire. Mais qu'en est-il du cache ? La réponse varie suivant le processeur. Il y a deux grandes méthodes pour implémenter l'émission multiple des accès mémoire. La première méthode ajoute des ports de lecture/écriture au cache de données, pour faire des lectures/écritures simultanées. Il faut alors dupliquer des unités de calcul, rajouter les ports sur le cache, mais aussi rajouter des ports sur la ''load/store queue'', pour qu'elle échange des données/commandes mémoire avec le cache. Le cout en circuits est alors conséquent La seconde duplique les unités de calcul, mais pas les ports d'accès au cache. Les accès mémoire sont alors accumulés dans la ''load/store queue'', qui sont ensuite traités un par un par le cache. Un exemple est celui des processeurs AMD de micro-architecture K7 K8, à savoir les anciens AMD Athlon et AMD Athlon 64, qui pouvaient calculer trois adresses simultanément, mais ne pouvaient faire qu'un seul accès au cache par cycle. Le cout en circuit est modéré : les unités de calcul coutent très peu, les ports de la ''load/store queue'' coutent déjà un peu plus. Quelques processeurs utilisaient des solutions intermédiaires. Un processeur utilisant cette technique est, par exemple, capable d'émettre quatre µops mémoire en même temps, mais avec seulement deux ports sur le cache. Un exemple classique étant les Pentium 1 et 2 d'Intel, dont nous parlerons dans le chapitre suivant. ===L'émission séparée des LOAD et STORE=== De rares processeurs ont des ports d'émission qui peuvent faire indifféremment lecture comme écritures. Mais les processeurs modernes préfèrent séparer ports d'émission pour les lectures et les écritures. Un exemple est celui des processeurs Intel de micro-architecture Nehalem, qui avaient un port de lecture et un port d'écriture tous deux reliés à la ''Load-store queue'', chacun avec leur propre unité de calcul d'adresse. Comme autre exemple, les processeurs skylake ont une LSQ avec deux ports de lecture et un port d'écriture, ce qui permet d'émettre deux lectures en même temps qu'une écriture. Dans cette section, nous allons étudier le cas avec des ports séparés pour les lectures et écritures. En général, les processeurs ont un peu plus de ports de lectures que d'écritures. Une raison est qu'un programme a, en moyenne, un tout petit peu plus de lectures que d'écritures. Mais la différence n'est pas énorme, c'est un léger déséquilibre. Une seconde raison est que l'exécution dans le désordre marche bien mieux quand les lectures ont lieu le plus tôt possible. Donc mieux vaut privilégier les lectures à l'émission, pour qu'elles se fassent un peu en avance. En pratique, la différence n'est pas très marquée. Il est courant d'avoir un port de lecture et un d'écriture, les CPU superscalaires très larges ont souvent deux ports de lecture et deux d'écriture. A la rigueur, quelques CPU ont trois ports de lecture et deux d'écriture. Le cas le plus simple est celui où on peut émettre une lecture et une écriture en même temps. Il s'implémente facilement si la LSQ est en réalité une simple ''Store queue'', à savoir qu'elle met en avance les écritures uniquement. Pour rappel, les écritures sont mises en attente dans la ''Store queue'', mais les lectures sont exécutées immédiatement. Une fois l'adresse à lire connue, la lecture consulte la ''Store queue'' pour détecter les dépendances de données, puis s'exécute. La donnée à lire est lue soit depuis la ''Store queue'', soit depuis le cache. [[File:Emission LOAD - Store séparée.png|vignette|upright=1|Émission LOAD - Store séparée]] L'implémentation de l'émission multiple est alors très simple. La ''Store queue'' a déjà deux ports, avec un pour les lectures et un autre pour les écritures. Idem pour le cache, qui a naturellement un port de lecture et un d'écriture. Émettre une lecture et une écriture simultanément ne demande donc pas de modification digne de ce nom. La ''Store queue'' doit juste supporter une lecture et une écriture simultanée. Ajouter l'émission d'une seconde lecture n'est pas compliquée : il faut juste rajouter un second port de lecture sur le cache et la ''Store queue''. Pour rappel, l'unité mémoire est précédée par une structure qui met en attente les micro-opérations mémoire avant leur émission finale. La structure en question varie suivant l'implémentation, mais un cas particulier se marie bien avec l'émission multiple des µops mémoire. Il s'agit du cas avec une file de µops séparée pour les lectures et une autre pour les écritures, appelées respectivement la ''file de µops LOAD'' et la ''file de µops STORE''. Il est alors possible d'émettre une lecture et une écriture lors du même cycle d'horloge. Le port de lecture alimente une unité de calcul d'adresse dédiée, directement reliée au cache. Le port d'écriture du cache alimente une unité de calcul, qui est suivie par une file d'écriture, elle-même reliée au cache. Les processeurs AMD de micro-architecture K6 utilisaient cette technique. [[File:Double émission avec le Load Ordering, Store Ordering.jpg|centre|vignette|upright=2.5|Double émission avec le Load Ordering, Store Ordering]] ==Les CPU superscalaire à exécution dans le désordre== La superscalarité modifie aussi les circuits dédiés à l'exécution dans le désordre. Par contre, elle modifie les unités de renommage de registre, le ROB et les autres structures liées à l'exécution dans le désordre, au sens large. Dans cette section, nous allons voir comment les étages du pipeline dédiés à l'exécution dans le désordre sont modifiées sur un CPU superscalaire. ===L'exécution dans le désordre centralisé : la différence entre largeur de décodage et d'émission=== Plus haut, nous avons montré qu'un processeur pouvait avoir plus de ports d'émission que de ports de décodage. On dit aussi que sa ''largeur de décodage'' était supérieure à sa ''largeur d'émission''. La '''largeur de décodage''' est le nombre de µops fournies par l'unité de décodage par cycle d'horloge, la '''largeur d'émission''' est le nombre maximal de µops émises par cycle d'horloge. Et n'allez pas croire qu'un surplus de ports d'émission est du gâchis. Sur les processeurs avec exécution dans le désordre, ce surplus peut être utilisé. Mais avant toute chose, évacuons un détail. Vous avez sans doute pensé que le surplus peut être exploité par les instructions machines décodées en plusieurs µops . Par exemple, si une instruction est décodée en deux µops, on pourrait avoir un port d'émission en plus pour en profiter. Mais les ports de décodage sont *en sortie du décodeur*, et contiennent des µops, pas des instructions. Le surplus de ports d'émission peut occasionnellement utiliser le surplus de ports de décodage, mais cela implique que des µops ont été mises en attente dans l'unité d'émission, pour être émises par la suite. Et cela implique l'usage d'une fenêtre d'instruction ou d'une file de µops. Le principe est que les µops sont accumulées dans la fenêtre d'instruction, en attendant leurs opérandes ou la résolution d'une dépendance. Et si l'occasion se présente, une µops est émise sur chaque port d'émission. Il y a donc un gain en performances, mais il faut faire quelques précisions sur son origine. La largeur d'émission utilisée ne peut pas dépasser, *en moyenne*, la largeur de décodage. Et c'est intuitif : un CPU qui décode 4 µops par cycle ne peut pas émettre 6 par cycle en moyenne, sur le temps long, il n'y a pas de µops en surplus qui sortent du vide. Par exemple, sur un processeur quadruple émission, si on analyse combien d'instructions sont émises par cycle, il peut y avoir des cycles où la largeur d'émission est totalement utilisée, mais ce sera compensé par des périodes où elle est fortement sous-utilisée. Les µops sont accumulées en cas de sous-utilisation, puis consommées en cas d'utilisation complète. Les deux périodes se compensent. Et c'est là que l'on peut comprendre d'où vient le gain en performance. Les cycles où les ports d'émission sont sous-utilisés existent systématiquement, que le processeur utilise l'exécution dans le désordre ou pas. Ils sont liés à la présence de dépendances de données, aux contraintes d’appariement, etc. Elles tendent à réduire la moyenne du nombre de µops émises par cycle. Par contre, ce sont les cycles de rattrapage, où les ports en surplus sont utilisés, qui font remonter la moyenne. Le gain en performance est donc lié à ces cycles de rattrapage, où les µops accumulées sont émises dans les ports en surplus. Et ils n'existent que sur les processeurs avec une file/fenêtre d'instruction. ===Les CPU superscalaires à fenêtres d'instruction décentralisées : la largeur de ''dispatch'' et de ''schedule''=== Les processeurs précédents utilisaient une unité d'émission simple, qui pouvait utiliser l'émission dans l'ordre ou dans le désordre. Elle peut être remplacée par une fenêtre d'instruction centralisée dans que cela quoi que ce soit aux explications précédentes, idem avec une station de réservation centralisée. En soi, l'exécution dans le désordre est donc totalement compatible avec une implémentation simple de la superscalarité. Par contre, des subtilités surviennent avec les fenêtres d'instruction décentralisées. Pour rappel, sur ces processeurs, les µops sont émises en deux phases. Une première phase de ''dispatch'' qui envoie les µops vers la fenêtre d'instruction adéquate, sans détecter les dépendances de données. La seconde phase de ''schedule'' gère les dépendances de données, elle met en attente les µops en attendant que l'unité soit disponible pour les exécuter, en attendant aussi que les opérandes soient disponibles. Et à ce petit jeu, il y a une différence entre le nombre de µops qui sont ''dispatch par cycle'' et celles qui sont ''schedule''. Dit autrement, la largeur d'émission est scindée en une '''largeur de ''dispatch''''' et une '''largeur de ''schedule''. Par exemple, prenons un processeur avec deux stations de réservation : une pour les µops entières/mémoire, et une autre pour les opérations flottantes. L'unité d'émission fait de la triple émission, ce qui fait qu'elle peut ''dispatch'' maximum trois µops par cycle : soit trois µops entières/mémoire, soit trois flottantes, soit un mix de trois µops différentes. Les stations de réservation, quant à elle, peuvent chacune ''schedule'' 3 µops par cycle, vers trois ALUs entières pour la station de réservation entière/mémoire, vers trois FPS pour la station de réservation flottante. En tout, cela permet de ''dispatch'' trois µops, mais d'en ''schedule'' six, à chaque cycle. Un détail est que le nombre de ports de lecture n'est pas le même selon que le processeur utilisé des stations de réservation ou une fenêtre d’instruction. Rappelons la terminologie utilisée dans ce cours. Les fenêtres d'instruction se contentent de mémoriser la micro-opération à émettre et quelques bits pour la disponibilité des opérandes. Par contre, les stations de réservations mémorisent aussi les opérandes des instructions. Et cette différence a une influence le nombre de ports de lecture du banc de registre. Supposons que toutes les instructions sont dyadiques, à deux opérandes. Comptons le nombre de ports nécessaires pour alimenter les unités de calcul, sans utiliser d'arbitre de banc de registre. Avec une fenêtre d'instruction, il faut le double du nombre de ports de ''dispatch''. Mais avec des stations de réservation, il faut le double des ports de ''schedule''. La raison est que les registres sont lus après émission avec une fenêtre d'instruction, avant avec des stations de réservation (sous-entendu, après le décodage). or, comme dit plus haut, les processeurs superscalaires larges ont souvent plus de ports de ''schedule'' que de ''dispatch''. ===L'unité de renommage superscalaire=== Avec la superscalarité, l'unité de renommage de registre doit gérer le cas où des instructions consécutives ont des dépendances de registre. Par exemple, prenons un processeur à double émission, qui renomme deux instructions consécutives. Si elles ont une dépendance de registre, le renommage de la seconde instruction dépend du renommage de la première. La première doit être renommée et le résultat du renommage est utilisé pour renommer la seconde, ce qui empêche d'utiliser deux unités de renommage séparées. Pour cela, elle renomme les registres sans tenir compte des dépendances, pour ensuite corriger le résultat. [[File:Unité de renommage superscalaire.png|centre|vignette|upright=2|Unité de renommage superscalaire.]] Seules les dépendances lecture-après-écriture doivent être détectées, les autres étant supprimées par le renommage de registres. Repérer ce genre de dépendances se fait assez simplement : il suffit de regarder si un registre de destination d'une instruction est un opérande d'une instruction suivante. [[File:Détection des dépendances sur un processeur superscalaire.png|centre|vignette|upright=2|Détection des dépendances sur un processeur superscalaire.]] Ensuite, il faut corriger le résultat du renommage en fonction des dépendances. Si une instruction n'a pas de dépendance avec une autre, on la laisse telle quelle. Dans le cas contraire, un registre opérande sera identique avec le registre de destination d'une instruction précédente. Dans ce cas, le registre opérande n'est pas le bon après renommage : on doit le remplacer par le registre de destination de l'instruction avec laquelle il y a dépendance. Cela se fait simplement en utilisant un multiplexeur dont les entrées sont reliées à l'unité de détection des dépendances. On doit faire ce replacement pour chaque registre opérande. [[File:Correction des dépendances sur un processeur superscalaire.png|centre|vignette|upright=2|Correction des dépendances sur un processeur superscalaire.]] ===Le tampon de réordonnancement et le banc de registre d'un CPU superscalaire=== Le ROB doit aussi être modifié pour émettre et terminer plusieurs instructions en même temps, en ajoutant des ports de lecture/écriture. Pour un CPU superscalaire peut émettre 8 µops à la fois, on se dit que le ROB peut retirer 8 µops par cycle maximum. Et c'est très souvent le cas en pratique. Cependant, certains processeurs ont un ROB qui peut retirer le double ! Un exemple est celui des CPU de micro-architecture Skymont, qui peuvent émettre 8 µops par cycle, mais en retirer 16. Quelques CPU de marque AMD étaient aussi dans ce cas. L'avantage est que cela permet de réduire la taille du ROB, du banc de registre et de quelques autres structures matérielles liées à l'exécution dans le désordre. Le gain sur-compense le cout en transistor nécessaire pour retirer 16 µops par cycle. Réduire le nombre de ports d'émission permet de garder un banc de registre, des fenêtres d'instruction et un ROB simples, rapides et économes en énergie. Et mine de rien, c'est là un compromis important : à quoi bon augmenter le nombre de ports d'émission si c'est pour que ce soit compensé par une fréquence plus faible ? Par contre, réduire le nombre de ports augmente la ''port contention'', surtout si on a mal réparti les ALU. Il faut donc trouver un compromis entre réduire le nombre de ports d'émission d'un côté, réduire la ''port contention'' de l'autre. ==Les optimisations des processeurs superscalaires larges== Les processeurs superscalaires larges ont des optimisations spécifiques, qui n'ont pas de sens sur les processeurs superscalaires étroits. Dans les grandes lignes, elles se classent en deux types. Les premières permettent de charger/décoder plus de 5-10 instructions simultanément. Le problème est alors que les branchements viennent mettre leur grain de sel et qu'il faut en tenir compte. Les secondes corrigent des problèmes qui apparaissent quand un CPU intègre plus de 5 à 10 unités de calcul. ===L'étape de chargement d'un CPU superscalaire large=== Charger plusieurs instructions pose de nombreux problèmes sur les processeurs superscalaires larges, qui émettent de 5 à 10 instructions simultanées. Pour rappel, la stratégie basique pour charger N instructions est de charger un bloc de N instructions consécutives. Les instructions après un branchement ne sont pas décodées ni exécutées, ce qui entraine une petite baisse de performance. Pour un N petit, la perte de performance est mineure. Mais pour un N large, elle devient suffisamment importante pour que la conception du CPU doit alors être repensée de fond en comble. Les analyses statistiques disent qu'il y a environ un branchement tous les 4 à 6 instructions. Un CPU a donc tendance à décoder des paquets de 3 à 5 instructions consécutives, avant qu'un branchement casse la dynamique. Or, cela pose des problèmes pour utiliser plusieurs décodeurs. L'implémentation basique, qui charge N instructions consécutives pour alimenter N décodeurs, fonctionne donc jusqu'à 4/6 décodeurs. Au-delà de 5 instructions, un CPU superscalaire doit être adapté pour gérer les branchements dans les paquets d'instruction qu'il émet. Le nombre de 5 instructions ne sort donc pas de nulle part. La première solution utilise un cache de µops, avec un ''front-end'' capable de charger/décoder 4/5 instructions à la fois, pas plus. L'idée est que la majorité des instructions proviennent du cache de µops (plus de 80%), ce qui fait qu'on peut émettre environ 10 instructions à partir du cache de µops, pas besoin d'avoir beaucoup de décodeurs. Les performances sont cependant dégradées lors d'un défaut de cache de µops. Les instructions doivent alors être chargées depuis le cache d'instruction, par paquets de 4-5, puis sont décodées par 4-5 décodeurs, ce qui ne permet pas d’alimenter les unités de calcul à plein régime. La solution précédente joue sur la distinction entre pots de décodage et d'émission. Elle permet d'émettre un grand nombre de µops sans pour autant charger un grand nombre d'instructions en même temps. En quelque sorte, ce n'est qu'une méthode de contournement, le CPU superscalaire reste de type étroit. Mais il est aussi possible d'utiliser un '''''front-end large''''', capable de charger une bonne dizaine d'instructions en même temps. La seule difficulté est de tenir compte de la présence de branchements. Pour rappel, avec un ''front-end'' étroit, les instructions situées après le premier branchement pris sont annulées, elles ne sont pas décodées ni exécutées. [[File:Fetch sur un processeur superscalaire avec prediction de branchements.png|centre|vignette|upright=2|Fetch sur un processeur superscalaire avec prédiction de branchements.]] Un ''front-end'' large charge les instructions de destination du branchement et les placent à sa suite. Ils chargent deux blocs à la fois et les fusionnent en un seul qui ne contient que les instructions présumées utiles. [[File:Cache d'instructions autoaligné.png|centre|vignette|upright=2|Cache d'instructions auto-aligné.]] Mais cela demande de charger deux blocs de mémoire en une fois, ce qui demande un cache d'instruction multiports. Il faut aussi ajouter un circuit pour assembler plusieurs morceaux de blocs en un seul : le fusionneur (''merger''). En pratique, cette solution demande d'ajouter un second port de lecture au cache, pour un cout en hardware important, avec un impact négatif sur le temps d'accès au cache. Et il faut rajouter le fusionneur, qui prend un étage de pipeline à lui tout seul. Et encore, cette solution ne gère pas le cas où un bloc contient plusieurs branchements ! [[File:Implémentation d'un cache d'instructions autoaligné.png|centre|vignette|upright=2|Implémentation d'un cache d'instructions auto-aligné.]] Les exemples précédents partent du principe qu'il n'y a qu'un seul branchement dans un bloc. Mais quand les blocs deviennent assez longs, il est fréquent d'avoir plusieurs branchements dans un bloc. Et il est même possible d'avoir plusieurs branchements pris. Dans un bloc de 8-10 instructions, ce n'est pas rare, et les CPU superscalaires à 8-10 voies sont la règle dans les CPU des années 2020. Pour gérer ce cas, il faut ajouter des ports supplémentaires au cache d'instruction et adapter le fusionneur, pour qu'il puisse fusionner trois ou quatre blocs. De plus, il faut utiliser une unité de prédiction de branchement capable de ''prédire plusieurs branchements par cycle''. ===Le décodage superscalaire large=== Le décodage des instructions n'est pas censé dépendre de l'implémentation du chargement. En théorie, une fois plusieurs blocs d'instruction fusionnés, on peut décoder les instructions en parallèle. Cependant, certains processeurs effectuent le décodage en tenant compte de la manière dont les branchements découpent les blocs. Les processeurs Atom d'Intel utilisent une solution de ce type, qui couple prédiction de branchement et décodage parallèle. Ils chargent/décodent des paquets de 3 instructions consécutives, chaque paquet étant séparé du précédent par un branchement. Pour cela, en plus d'avoir un fusionneur adapté, ils regroupent les décodeurs en ''clusters'', chacun regroupant 3 décodeurs, et pouvant décoder trois instructions consécutives. Les micro-architectures Tremont et Crestmont avaient deux ''clusters'' (6 décodeurs au total), la micro-architecture Skymont en intègre trois (9 décodeurs au total). Avec deux ''clusters'', le fonctionnement est le suivant. Si le code est composé d'une suite d'instruction sans branchements, seul le premier ''cluster'' est utilisé, limitant le décodage à trois instructions par cycle. Par contre, si l'une des trois instructions décodée est un branchement, alors le second ''cluster'' décode les trois instructions situées après ce branchement, à partir de l'adresse de destination. Avec un troisième ''cluster'', c'est la même chose, sauf qu'il faut qu'il y ait un branchement dans les trois instructions décodées par le second ''cluster''. Mais comment faire pour savoir quels décodeurs activés, quels ''clusters'' sont actifs ? C'est la prédiction de branchement qui commande. Avec deux ''clusters'', elle détermine si un branchement est présent dans les trois instructions à charger, et quelle est son adresse de destination. Sans branchement, elle prévient les décodeurs et les trois instructions chargées sont décodées dans un ''cluster'' libre. Si elle prédit un branchement, elle charge les instructions adéquates, et active les deux ''clusters''. Avec trois branchements, c'est le même mécanisme, sauf que l'unité de prédiction de branchement doit être capable de prédire deux branchements par cycle. Il faut noter que les décodeurs d'un ''cluster'' sont des décodeurs simples, sans microcode. Sur les processeurs Tremont et Crestmont, le microcode était unique et était partagé entre tous les ''clusters''. Un seul ''cluster'' pouvait décoder une instruction complexe avec le microcode, l'autre devait se limiter à des instructions simples uniquement. Depuis Skymont, le microcode est partiellement dupliqué, avec un microcode par ''cluster''. Le cout en hardware peut paraitre prohibitif, pour un gain en performance faible. Sauf que c'est oublier que ce sont des processeurs Atom, sur lesquels beaucoup d'instructions sont microcodées. Les processeurs Atom ont un budget en transistor limité, ce qui fait qu'il est préférable pour eux d'implémenter des instructions en microcode que d'utiliser des unités de calcul dédiées ou des circuits de décodage complexes. Pour limiter la casse, le microcode n'est que partiellement dupliqué. Seul le microcode pour les instructions fréquente est dupliqué. Il y a un microcode secondaire pour certaines instructions, qui est lui unique et partagé par tous les ''clusters''. ===Le contournement sur les processeurs superscalaires larges=== Pour rappel, la technique du contournement (''register bypass'') permet au résultat d'une instruction d'être immédiatement utilisable en sortie de l'ALU, avant même d'être enregistré dans les registres. Implémenter la technique du contournement demande d'utiliser des multiplexeurs pour relier la sortie de l'unité de calcul sur son entrée si besoin. Il faut aussi des comparateurs pour détecter des dépendances de données. [[File:Pipeline Bypass.png|centre|vignette|upright=1|Pipeline Bypass]] La sortie de chaque ALU doit être reliée aux entrées de toutes les autres, avec les comparateurs qui vont avec. Sur les processeurs ayant plusieurs d'unités de calculs, cela demande beaucoup d’interconnexions et de comparateurs. Pour N unités de calcul, cela demande 2 * N² interconnexions, implémentées avec 2N multiplexeurs de N entrées chacun. Si c'est faisable pour 2 ou 3 ALUs, la solution est impraticable au-delà. Non seulement ce serait un enfer à câbler, mais les fils seraient tellement longs que la fréquence du processeur en prendrait un coup. Notez que cela vaut pour les processeurs superscalaires, mais aussi pour tout processeur avec beaucoup d'unités de calcul. En théorie, un CPU à exécution dans le désordre, non-superscalaire, mais avec beaucoup d'unités de calcul, fait face au même problème. En pratique, avoir une dizaine d'ALU implique processeur superscalaire à exécution dans le désordre. D'où le fait qu'on parle du problème maintenant. La seule solution praticable est de ne pas relier toutes les unités de calcul ensemble. À la place, on préfère regrouper les unités de calcul dans différents '''agglomérats''' ('''cluster'''). Le contournement est alors possible entre les unités d'un même agglomérat, mais pas entre agglomérats différents. Évidemment, certaines possibilités de contournement sont perdues, mais c'est compensé par un gain en fréquence et un cout en circuit/fils réduit. C'est un bon compromis, ce qui explique que presque tous les processeurs modernes l'utilisent. Les rares exceptions sont les processeurs POWER 4 et POWER 5, qui ont préféré se passer de contournement pour garder un processeur très simple et une fréquence élevée. ===Le banc de registre des processeurs superscalaires larges=== Pour émettre deux opérations entières simultanément, il faut lire deux fois plus d'opérandes. Et il faut aussi écrire deux résultats dans les registres, en même temps. Pour cela, on double les ports du banc de registre. En général, il faut au maximum 2 ports de lecture et un port d'écriture par unité de calcul. Le problème, c'est que plus un banc de registres a de ports, plus il utilise de circuits, est compliqué à concevoir, consomme de courant et chauffe. Avec plusieurs dizaines d'unités de calcul différentes, ajouter autant de ports impacterait la fréquence du banc de registres et réduirait les performances, voire est totalement impraticable à cause des contraintes thermiques. Les processeurs superscalaires larges doivent donc trouver des solutions. La première solution utilise un banc de registre unique, mais n'utilise pas autant de ports que le pire des cas le demanderait. Le processeur détecte quand il n'y a pas assez de ports pour servir toutes les micro-opérations. Quand ça arrive, l'unité d'émission émet assez de micro-opérations pour exploiter les ports disponibles, mais met en attente les micro-opérations en trop, le temps que les ports se libèrent. Pour cela, le banc de registre est géré par un circuit d'arbitrage spécialisé, intégré à l'unité d'émission, l’'''arbitre du banc de registres''' (''register file arbiter''). Une autre solution est de dupliquer le banc de registres en plusieurs exemplaires qui contiennent exactement les mêmes données, mais avec moins de ports de lecture/écriture. Un exemple est celui des processeurs POWER, Alpha 21264 et Alpha 21464. Sur ces processeurs, le banc de registre est dupliqué en plusieurs exemplaires, qui contiennent exactement les mêmes données. Les écritures dans les registres sont envoyées à tous les bancs de registres, afin de garantir que leur contenu est identique. Chaque exemplaire a exactement deux ports de lecture. La conception du processeur est simplifiée, que ce soit au niveau du câblage, que de la conception des bancs de registres. Une dernière solution découpe le banc de registres en plusieurs bancs de registres plus petits, avec un réseau d'interconnexions pour échanger des données entre bancs de registres. Dans la plupart des cas, cette séparation est invisible du point de vue de l'assembleur et du langage machine. Le processeur se charge de transférer les données entre bancs de registres suivant les besoins. Sur d'autres processeurs, les transferts de données se font via une instruction spéciale, souvent appelée ''COPY''. [[File:Banc de registres distribué.png|centre|vignette|upright=2|Banc de registres distribué.]] Sur de certains processeurs, un branchement est exécuté par une unité de calcul spécialisée. Or les registres à lire pour déterminer l'adresse de destination du branchement ne sont pas forcément dans le même agglomérat que cette unité de calcul. Pour éviter cela, certains processeurs disposent d'une unité de calcul des branchements dans chaque agglomérat. Dans les cas où plusieurs unités veulent modifier le ''program counter'' en même temps, un système de contrôle général décide quelle unité a la priorité sur les autres. Mais d'autres processeurs fonctionnent autrement : seul un agglomérat possède une unité de branchement, qui peut recevoir des résultats de tests de toutes les autres unités de calcul, quel que soit l’agglomérat. ==La macro-fusion : une optimisation du décodage parallèle== Les processeurs superscalaires supportent la technique dite de '''macro-fusion''', qui permet de fusionner deux-trois instructions consécutives en une seule micro-opération. Par exemple, il est possible fusionner une instruction de test et une instruction de saut en une seule micro-opération de branchement. Il s'agit là de l'utilisation la plus importante de la macro-fusion sur les processeurs x86 modernes. La macro-fusion est utilisée pour fusionner un test et un saut en un branchement unique. L'optimisation est présente sur la plupart des CPU superscalaires modernes. Les CPU x86 l'implémentent, mais aussi quelques CPU ARM et les processeurs Apple de série M (M1 à M5, et potentiellement plus quand ils seront sortis). La fusion des instructions se fait lors du décodage des instructions, grâce à la coopération des décodeurs. Elle marche systématiquement si l'instruction de test et le saut sont consécutives, qu'elles forment bien une paire d'instruction. Par contre, insérer une instruction entre les deux empêche la macro-fusion de faire son travail, sauf si l'instruction insérée est un NOP. Les processeurs ARM64 avaient une forme spécifique de macro-fusion, utilisée pour un cas très précis : charger une constante immédiate de 32 ou 64 bits, dans un registre 64 bits. Les CPU ARM 64 bits utilisent des instructions codées sur 32 bits, qui ne peuvent encoder que des constantes immédiates de 16 bits. Pour charger une constante de 32/64 bits, il faut s'y prendre en plusieurs fois. Une première instruction MOVZ charge les 16 premiers bits, puis 2 à 4 instructions MOVK remplissent le reste. MOVZ met à zéro les 48 bits de poids fort, MOVK permet de sélectionner le doublet à modifier dans le registre (on modifie 16 bits, dont on choisit la place, le reste du registre est laissé tel quel). ARM permet de fusionner cette suite d'instructions en une seule µop MOV avec une constante immédiate. On parle alors de '''''MOVZ + MOVK fusion'''''. Une dernière utilisation, [https://www2.eecs.berkeley.edu/Pubs/TechRpts/2016/EECS-2016-130.pdf proposées pour le jeu d'instruction RISC-V], fusionnait un calcul d'adresse et une instruction LOAD/STORE en une seule instruction d'accès mémoire. Elle a été envisagée à une époque où le jeu d'instruction RISC-V ne gérait pas les modes d'adressage base + indice. L'idée était d'avoir une unité mémoire qui gérait le mode d'adressage base + indice et de fusionner des instructions pour l'exploiter. Mais de nos jours, le jeu d'instruction RISC-V intègre le mode d'adressage base + indice, ce qui fait que la macro-fusion n'a aucun intérêt pour ce cas précis. La macro-fusion a de nombreuses limitations en pratique. En théorie, on pourrait l'utiliser au-delà des deux situations mentionnées plus haut. Par exemple, pour fusionner une addition et une multiplication en une seule instruction MAD. Mais l'intérêt est assez faible. Notons que la macro-fusion ne permet que de fusionner des paires d'instructions. Aller au-delà demanderait un cout en circuit trop important. Déjà que tester deux paires utilise pas mal de comparateurs... ==Le ''stack engine'' : une optimisation de la pile d'appel== Les processeurs modernes intègrent une optimisation liée au pointeur de pile. Pour rappel, sur les architectures modernes, le pointeur de pile est un registre utilisé pour gérer la pile d'appel, précisément pour savoir où se trouve le sommet de la pile. Il est manipulé par les instructions CALL, RET, PUSH et POP, mais aussi par les instructions d'addition/soustraction pour gérer des cadres de pile de taille variable. De plus, il peut servir d'opérande pour des instructions LOAD/STORE, afin de lire/écrire des variables locales, les arguments d'une fonction, et autres. C'est donc un registre général adressable, intégré au banc de registre, altéré par l'unité de calcul entière. L'incrémentation/décrémentation du pointeur de pile passe donc par l'unité de calcul, lors des instructions CALL, RET, PUSH et POP. Mais, l'optimisation que nous allons voir permet d'incrémenter/décrémenter le pointeur de pile sans passer par l'ALU, ou presque. L'idée est de s'inspirer des architectures avec une pile d'adresse de retour, qui intègrent le pointeur de pile dans le séquenceur et l'incrémentent avec un incrémenteur dédié. ===Le ''stack engine''=== L'optimisation que nous allons voir utilise un '''''stack engine''''' intégré à l'unité de contrôle, au séquenceur. Le processeur contient toujours un pointeur de pile dans le banc de registre, cela ne change pas. Par contre, il n'est pas incrémenté/décrémenté à chaque instruction CALL, RET, PUSH, POP. Un compteur intégré au séquenceur est incrémenté à la place, nous l’appellerons le '''compteur delta'''. La vraie valeur du pointeur de pile s'obtient en additionnant le compteur delta avec le registre dans le banc de registre. Précisons que si le compteur delta vaut zéro, la vraie valeur est dans le banc de registre et peut s'utiliser telle quelle. Lorsqu'une instruction ADD/SUB/LOAD/STORE utilise le pointeur de pile comme opérande, elle a besoin de la vraie valeur. Si elle n'est pas dans le banc de registre, le séquenceur déclenche l'addition compteur-registre pour calculer la vraie valeur. Finalement, le banc de registre contient alors la bonne valeur et l'instruction peut s'exécuter sans encombre. L'idée est que le pointeur de pile est généralement altéré par une série d'instruction PUSH/POP consécutives, puis des instructions LOAD/STORE/ADD/SUB utilisent le pointeur de pile final comme opérande. En clair, une bonne partie des incrémentations/décrémentation est accumulée dans le compteur delta, puis la vraie valeur est calculée une fois pour toutes et est utilisée comme opérande. On accumule un delta dans le compteur delta, et ce compteur delta est additionné quand nécessaire. Précisons que le compteur delta est placé juste après le décodeur d'instruction, avant même le cache de micro-opération, l'unité de renommage et l'unité d'émission. Ainsi, les incrémentations/décrémentations du pointeur de pile disparaissent dès l'unité de décodage. Elles ne prennent pas de place dans le cache de micro-opération, ni dans la fenêtre d'instruction, ni dans la suite du pipeline. De nombreuses ressources sont économisées dans le ''front-end''. Mais utiliser un ''stack engine'' a aussi de nombreux avantages au niveau du chemin de données/''back-end''. Déjà, sur les processeurs à exécution dans le désordre, cela libère une unité de calcul qui peut être utilisée pour faire d'autres calculs. Ensuite, le compteur delta mémorise un delta assez court, de 8 bits sur le processeur Pentium M, un peu plus pour les suivants. L'incrémentation se fait donc via un incrémenteur 8 bits, pas une grosse ALU 32/64 bits. Il y a un gain en termes de consommation d'énergie, un incrémenteur 8 bits étant moins gourmand qu'une grosse ALU 32/64 bits. ===Les points de synchronisation du delta=== La technique ne fonctionne que si la vraie valeur du pointeur de pile est calculée au bon moment, avant chaque utilisation pertinente. Il y a donc des '''points de synchronisation''' qui forcent le calcul de la vraie valeur. Plus haut, nous avions dit que c'était à chaque fois qu'une instruction adresse le pointeur de pile explicitement, qui l'utilise comme opérande. Les instructions CALL, RET, PUSH et POP ne sont pas concernées par elles utilisent le pointeur de pile de manière implicite et ne font que l'incrémenter/décrémenter. Mais dans les faits, c'est plus compliqué. D'autres situations peuvent forcer une synchronisation, notamment un débordement du compteur delta. Le compteur delta est généralement un compteur de 8 bits, ce qui fait qu'il peut déborder. En cas de débordement du compteur, le séquenceur déclenche le calcul de la vraie valeur, puis réinitialise le compteur delta. La vraie valeur est donc calculée en avance dans ce cas précis. Précisons qu'un compteur delta de 8 bits permet de gérer environ 30 instructions PUSH/POP consécutives, ce qui rend les débordements de compteur delta assez peu fréquent. À noter que si le compteur delta vaut zéro, il n'y a pas besoin de calculer la vraie valeur, le séquenceur prend cette situation en compte. Un autre point de synchronisation est celui des interruptions et exceptions matérielles. Il faut que le compteur delta soit sauvegardé lors d'une interruption et restauré quand elle se termine. Idem lors d'une commutation de contexte, quand on passe d'un programme à un autre. Pour cela, le processeur peut déclencher le calcul de la vraie valeur lors d'une interruption, avant de sauvegarder les registres. Pour cela, le processeur intègre un mécanisme de sauvegarde automatique, qui mémorise la valeur de ce compteur dans le tampon de réordonnancement, pour forcer le calcul de la vraie valeur en cas de problème. La technique du ''stack engine'' est apparue sur les processeurs Pentium M d'Intel et les processeurs K10 d'AMD, et a été conservée sur tous les modèles ultérieurs. L'implémentation est cependant différente selon les processeurs, bien qu'on n'en connaisse pas les détails et que l'on doive se contenter des résultats de micro-benchmarks et des détails fournit par Intel et AMD. Selon certaines sources, dont les manuels d'optimisation d'Agner Fog, les processeurs AMD auraient moins de points de synchronisation que les processeurs Intel. De plus, leur ''stack engine'' serait placé plus loin que prévu dans le pipeline, après la file de micro-opération. ==La micro-fusion et la délamination== La '''micro-fusion''' est une optimisation qui retarde le décodage réel de certaines instructions assez loin dans le pipeline. Elle concerne surtout les instructions ''load-op'', mais pas que. D'autres instructions mémoire peuvent subir cette optimisation. L'essentiel est que, sans micro-fusion, ces instructions sont décodées en plusieurs micro-opérations. La micro-fusion retarde le décodage final de ces instructions, qui se fait assez tard dans le pipeline. Pour comprendre l'intérêt, nous allons devoir faire quelques explications. L'optimisation en question fait le décodage en deux temps. Prenons l'exemple d'une opération ''load-op'', qui est censée être décodée en deux micro-opérations : une lecture, puis une opération. Le décodage produit une '''macro-opération''' (terme d'Intel), qui est propagée dans le pipeline, pour être scindée en deux micro-opérations un peu avant les ALU et l'unité mémoire. La macro-opération est techniquement une micro-opération comme une autre, sauf que ce n'est pas la micro-opération finale. L'unité d'émission découpe cette macro-opération en deux micro-opérations, qui sont émises l'une après l'autre. Pour résumer, les instructions ''load-op'' ne sont complétement découpées en micro-opération lors de l'émission, pas lors du décodage. L'optimisation impacte ce qui se situe entre le décodeur d'instruction et l'unité d'émission : l'unité de renommage, la file de micro-opérations, le ''Loop Stream Detector'' et/ou le cache de micro-opération. L'intérêt de l'optimisation est que l'on fusionne deux micro-opérations en une seule macro-opération. Le cache de micro-opération et la file de micro-opération peuvent mémoriser plus de micro-opérations, vu que certaines sont fusionnées. Quant à l'unité de renommage, elle renomme une seule macro-opération au lieu de renommer deux micro-opérations, ce qui augmente le débit du renommage de registre. Par contre, cela complique un peu le travail de l'unité d'émission, qui doit découper les instructions ''load-op'' en deux et émettre les micro-opérations l'une après l'autre. Il faut noter qu'il n'y a pas de fusion proprement dite. Le décodeur ne décode pas deux micro-opérations séparées, avant de les fusionner. En réalité, il génère directement une macro-opération, qui correspond à deux micro-opérations, que ce soit plus tard dans le pipeline ou sur une autre architecture. D'ailleurs, la micro-fusion a lieu à l'intérieur d'une instruction, qui est censée être traduite en plusieurs micro-opérations. Et encore : pas toutes. La conséquence est que la technique n'a de sens que sur les processeurs CISC, avec des instructions assez complexes pour être décodées en plusieurs micro-opérations. En pratique, seuls les CPU x86 sont concernés. ===Les domaines de fusion=== La micro-fusion n'est pas spécifique aux processeurs superscalaires. En théorie, on peut l'implémenter sur un processeur non-superscalaire à émission dans l'ordre. Cependant, je ne connais aucun processeur à émission dans l'ordre qui implémente la micro-fusion, même si son implémentation est en théorie possible sur de tels processeurs. La micro-fusion est implémentée différemment selon que l'émission se fasse dans l'ordre, dans le désordre, avec une fenêtre d'instruction, avec des stations de réservation, etc. J'ai dit plus haut que les micro-opérations sont générées lors de l'émission, mais le moment exact varie suivant le processeur. Pour simplifier le tout, nous parlerons de '''scission''' quand le processeur traduit une macro-opération en deux/trois micro-opérations. Le moment où a lieu la scission est théoriquement dans l'unité d'émission, mais celle-ci prend plusieurs formes selon que le processeur utilise l'émission dans l'ordre, dans le désordre, avec une fenêtre d'instruction, avec des stations de réservation, etc. Les processeurs à exécution dans l'ordre sont le cas le plus simple. L'unité d'émission est alors en charge du découpage des macro-opérations en micro-opérations. La file de micro-opération ou le cache de micro-opérations mémorisent des macro-opérations. À l'opposé, le tampon de ré-ordonnancement mémorise lui des micro-opérations. Le reste du processeur n'est pas affecté. Sur les processeurs à exécution dans le désordre, les différences sont nombreuses. Le tampon de ré-ordonnancement peut mémoriser soit les micro-opérations finales, soit des macro-opérations. Il en est de même avec la fenêtre d'instruction et/ou les stations de réservation, qu'elles soient centralisées ou décentralisées. Tout dépend de comment se fait l'implémentation. Une implémentation simple effectue la scission des macro-opérations dans l'unité d'émission, comme sur les processeurs à exécution dans l'ordre. Sur de nombreux processeurs, l'unité d'émission et de renommage sont simplement fusionnées, ce qui fait que la scission a lieu juste après le renommage de registres. En conséquence, le tampon de re-ordonnancement et les fenêtres d'instruction ne voient que des micro-opérations. Les macro-opérations disparaissent juste avant. Cette implémentation est préférée avec une fenêtre d'instruction centralisée, car on voit mal comment scinder une macro-opération juste avant l'ALU. Une autre implémentation fait la scission en sortie de la fenêtre d'instruction ou des stations de réservation, qu'elles soient centralisées ou décentralisées. Dans ce cas, le tampon de re-ordonnancement fait de même. L'avantage est que le tampon de ré-ordonnancement stocke des macro-opérations, ce qui augmente sa taille effective. Par exemple, il utilise une seule entrée pour une instruction ''load-op'', au lieu de deux sans micro-fusion de ce type. ===La délamination=== La scission peut parfois avoir lieu plus tôt, dans des circonstances très précises. Concrètement, cela ne concerne que quelques micro-architectures d'Intel, pour des instructions très précises. La raison est les limitations du format des micro-opérations, pas une histoire d'optimisation. Le processeur aimerait pouvoir faire la micro-fusion, mais ne peut pas à cause de ces limitations. Une telle situation cause alors une '''délamination''' : les micro-opérations sont scindées précocement. Par exemple, sur la micro-architecture SandyBridge d'Intel, les opérations ''load-op'' sont fusionnées, sauf si elles utilisent l'adressage indicé. Les micro-opérations de ce processeur ne peuvent avoir que deux opérandes. Trois opérandes est de trop, et les opérations ''load-op'' en adressage indicé dépassent cette limite. Dans ce cas, les micro-opérations sont fusionnées en sortie du décodeur, mais sont scindés avant d’entrer dans la file de micro-opération. L'avantage n'est pas évident, vu que l'unité de renommage suit de près le décodeur d'instruction. Il est que le cache de micro-opérations mémorise des macro-opérations, pas deux micro-opérations. ===La micro-fusion des processeurs Intel et AMD=== La micro-fusion est apparue sur le Pentium M d'Intel, et a été conservée telle quelle sur les processeurs Intel suivants. Les processeurs AMD supportent la micro-fusion depuis au moins la micro-architecture AMD K8. Mieux que ça, la délamination n'était pas utilisée car les micro-opérations acceptaient plus de 2 opérandes. Que ce soit chez AMD ou Intel, elle concerne deux types d'instructions : les écritures mémoire et les instructions ''load-op''. Sur le Pentium M, il y avait quelques limitations, l'optimisation se limitant par exemple aux registres entiers et flottants, mais pas aux registres MMX, mais pas les registres SSE (nous verrons ces registres dans quelques chapitres). Mais les processeurs modernes n'ont plus cette limitation. La micro-fusion des CPU Intel concerne deux types d'instructions : les écritures mémoire et les instructions ''load-op''. Pour les écritures, la situation est plus complexe que pour les instructions ''load-op''. Sur les processeurs Intel précédents, les écritures étaient réalisées dans deux unités séparées : le calcul d'adresse dans une unité de calcul, l'écriture proprement dite dans l'unité mémoire. Elles prenaient donc deux micro-opérations. Le processeur a changé ses unités mémoire, ce qui fait qu'elle pouvait faire les deux en une seule micro-opération. Il s'agissait alors d'une optimisation du chemin de données, plus que de la micro-fusion proprement dite, mais je la mentionne ici malgré tout. La micro-fusion sur ce processeur avait un avantage en plus des avantages usuels : elle utilisait mieux les décodeurs. Les Pentium M avaient trois décodeurs pour décoder plusieurs instructions en même temps, chose qu'on détaillera dans le chapitre sur les processeurs superscalaires. Un seul décodeur pouvait décoder toutes les instructions, les deux autres ne peuvent décoder que les instructions simples, qui se décodent en une seule micro-opération. Avec la micro-fusion, les trois décodeurs peuvent gérer les écritures mémoire et les opérations ''load-op'', vu qu'elles deviennent des instructions simples. Sans cela, elles auraient été limitées au premier décodeur. <noinclude> {{NavChapitre | book=Fonctionnement d'un ordinateur | prev=Le parallélisme mémoire | prevText=Le parallélisme mémoire | next=Exemples de microarchitectures CPU : le cas du x86 | nextText=Exemples de microarchitectures CPU : le cas du x86 }} </noinclude> mj593bzesstqst3vinwrhsgubchb04v 773244 773243 2026-09-26T15:02:28Z Mewtow 31375 /* Les CPU superscalaires à fenêtres d'instruction décentralisées : la largeur de dispatch et de schedule */ 773244 wikitext text/x-wiki Les processeurs vus auparavant ne peuvent émettre au maximum qu'une instruction par cycle d'horloge. Ils peuvent avoir plusieurs instructions qui s'exécutent en même temps, dans des unités de calcul séparées. C'est le cas dès qu'une instruction multi-cycles s'exécute. Par contre, ils ne peuvent démarrer qu'une seule instruction par cycle. Et quand on court après la performance, ce n'est pas assez ! Les concepteurs de processeurs ont inventés des processeurs qui émettent plusieurs instructions par cycle : les '''processeurs à émissions multiples'''. Un processeur à émission multiple charge plusieurs instructions en même temps, les décode en parallèle, puis les émet en même temps sur des unités de calculs séparées. Pour cela, il faut gérer les dépendances entre instructions, répartir les instructions sur différentes unités de calcul, et cela n'est pas une mince affaire. En général, pour un processeur à émission multiple capable d'émettre N instructions en parallèle, on parle de '''processeur à N voies''', ou encore un ''processeur à N pipelines''. [[File:Superscalarpipeline.svg|centre|vignette|upright=1.5|Pipeline RISC classique à cinq étages sur un processeur superscalaire. On voit bien que plusieurs instructions sont chargées en même temps.]] Les processeurs à émission multiple sont de deux types : les processeurs VLIW et les '''processeurs superscalaires'''. Nous mettons les processeurs VLIW de côté en attendant le prochain chapitre. La raison est qu'ils ont un jeu d'instruction spécialisé qui expose le parallélisme d'instruction directement au niveau du jeu d'instructions, là où les processeurs superscalaires restent des processeurs au jeu d'instruction normal. La différence principale entre processeur VLIW et superscalaire est qu'un processeur superscalaire répartit les instructions sur les unités de calcul à l’exécution, là où un processeur VLIW délègue cette tâche au compilateur. ==Le nombre de voies d'un processeur superscalaire== Les processeurs superscalaires les plus simples ne permettent que d'émettre deux micro-opérations à la fois, d'où leur nom de processeur ''dual issue'', ce qui se traduit en '''processeur à double émission'''. Les processeurs triple émission permettent eux d’émettre trois instructions à la fois. A l'opposé, les CPU superscalaires les plus puissants peuvent émettre jusqu'à 8-10 instructions simultanément. En 2026, les CPU superscalaires peuvent monter jusqu’à 10 µops émises en même temps, ce qui est énorme ! Et cela nous amène à faire une distinction entre les processeurs superscalaires dits '''étroits''' et ceux dits '''larges'''. Dans ce wikilivre, on part du principe que les processeurs superscalaires étroits émettent jusqu'à 4-5 µops à la fois, alors que les larges peuvent en émettre plus, l'intervalle entre les deux est une zone grise. Le nombre exact n'est pas fixé dans le marbre, la distinction est volontairement vague. Nous parlerons de la '''largeur d'un CPU superscalaire''' pour parler du nombre de voies, du nombre de µops émises en même temps. Plus un CPU superscalaire est large, plus il est performant ''toute chose égale par ailleurs''. Mais dans les faits, vous pouvez oublier la partie en italique. Élargir un CPU superscalaire demande de rajouter beaucoup de circuits. Et ce cout en circuit aura un impact sur la performance. A budget en transistors limité, il y a un compromis entre élargir un CPU et rajouter du cache ou la taille des fenêtres d'instructions/ROB et autres. ===Les contraintes d’appariement=== Prenons un processeur double émission, capable d'émettre deux instructions simultanément. Émettre deux instructions en même temps n'est pas toujours possible. Par exemple, si deux instructions sont dépendantes, on ne peut pas les émettre en même temps. Mais oublions un instant ces histoires de dépendances de données, et partons du principe que le CPU ait affaire à deux instructions indépendantes, une paire idéale. Un CPU double émission "parfait" pourrait exécuter n'importe quelle paire d'instruction. Par exemple, il pourrait émettre deux multiplications consécutives, deux lectures consécutives, une lecture et une écriture en même temps, etc. En pratique, aucun processeur ne permet cela. Implémenter un CPU superscalaire "parfait" aurait un cout en circuit beaucoup trop conséquent. Par exemple, pour émettre deux multiplications en même temps, il faudrait deux multiplieurs séparés. Idem pour exécuter deux accès mémoire simultané : cela demande d'avoir deux unités mémoire et un cache multiport. Mais en pratique, aucun CPU superscalaire ne fait cela. La totalité des CPU double émission se débrouille avec un seul circuit multiplieur et une seule unité mémoire. Et cela entraine l'apparition de dépendances structurelles, qui font que certaines paires d'instructions sont impossibles. Par exemple, impossible d'émettre deux multiplications en même temps avec un seul circuit multiplieur. Mais émettre une multiplication en parallèle d'une addition est parfaitement possible. Il y a donc des paires d'instructions autorisées et des paires interdites. Et la logique est la même sur les CPU à triple ou quadruple émission, et au-delà. : des triplets ou quadruplet d'instructions sont impossibles à émettre simultanément, alors que d'autres le sont. Pour le dire autrement, il y a des '''contraintes d'appariement''' sur les instructions à émettre en même temps. ===L'impact des contraintes d’appariement sur la performance=== Ces contraintes d’appariement ont un impact sur les performance, mais celui-ci peut être grandement mitigé. Les programmes informatiques utilisent en pratique beaucoup plus d'opérations entières de base que de multiplication, branchements et accès mémoire. Il y a environ 5 opérations entières de base pour une multiplication entière. En pratique, il est rare de devoir exécuter deux multiplications à la fois. Idem pour les décalages et rotations, qui sont assez éloignés les uns des autres. Deux branchements consécutifs sont quant à eux plus fréquents. Deux accès mémoire consécutifs est assez rare, mais ils restent assez proches les uns des autres malgré tout. La conséquence est que certaines paires d'instructions sont privilégiées par rapport au reste. Les paires regroupant deux calculs basiques (additions, soustractions, ...) sont systématiquement supportées, car dupliquer des ALU entières est facile. Les paires regroupant un calcul et une autre instruction sont elles aussi systématiquement supportées. Par contre, les paires avec deux branchements, deux accès mémoire, deux décalages/rotations, ou deux multiplications/divisions sont fréquemment interdites. Seuls les CPU superscalaires larges peuvent les permettre, et encore : sous conditions. Le résultat est que le processeur peut se débrouiller avec une seule unité mémoire et une unité de branchement. ===Terminologie pour la suite du chapitre=== Décrire les contraintes d’appariement demande de dire quelles combinaisons d’instructions sont autorisées ou interdites. Et pour cela, nous allons introduire les abréviations suivantes : * INT est un raccourcis pour parler d'une opération entière simple, qu'une ALU entière peut exécuter. * MUL correspond à une opération entière complexe, typiquement une multiplication, éventuellement à une division. * FLOAT désigne une opération flottante, peut importe laquelle. * MEM désigne un accès mémoire, à savoir une instruction ou µop mémoire. * BRANCH désigne une instruction de branchement. Cette terminologie permettra de définir des paires d'instructions, qu'elles soient possibles ou interdites. Par exemple, l'expression INT + FLOAT désigne une paire d'instruction regroupant une instruction entière et une instruction flottante, peu importe l'ordre des deux instructions. Il en sera de même pour les triplets, quadruplets, ou plus. Par exemple, l'expression INT + INT + FLOAT + MUL désigne un quadruplet d'instructions regroupant : deux instructions entières, une instruction flottante, et une multiplication entière. J'utiliserais aussi des parenthèses pour regrouper certaines combinaisons. Par exemple, l'expression INT + (MUL /FLOAT) veut parler d'une paire d'instruction où la première est une opération entière basique, la seconde est : soit une multiplication, soit une opération flottante. ==Le ''front-end'' des processeurs superscalaires== Un processeur superscalaire doit charger, décoder, et exécuter plusieurs instructions par cycle. Par exemple, un processeur triple émission doit charger 3 instructions lors d'un premier cycle, puis les décoder au cycle suivant, et les exécuter au cycle suivant. Il travaille donc pas paquets de 3 instructions, dans cet exemple. Intuitivement, charger, décoder et exécuter plusieurs instructions à la fois demande de dupliquer des circuits. Par exemple, il faut dupliquer les décodeurs pour décoder plusieurs instructions. Les unités de calcul sont aussi dupliquées, sauf sur certaines processeurs qui arrivent à ruser, mais laissons cela de côté pour plus tard. Mais d'autres circuits sont simplement modifiés, comme l'unité de chargement ou l'unité d'émission. Voyons ce qu'il en est dans le détail. {|class="wikitable" |- ! rowspan="2" | Processeur sans émission multiple | rowspan="2" | Chargement | rowspan="2" | Décodage | rowspan="2" | Renommage | rowspan="2" | Émission | Exécution / ALU | rowspan="2" | ''Commit''/ROB |- | Exécution / ALU |- ! rowspan="4" | Processeur superscalaire | rowspan="4" | Chargement | rowspan="2" | Décodage | rowspan="4" | Renommage | rowspan="4" | Émission | Exécution / ALU | rowspan="4" | ''Commit''/ROB |- | Exécution / ALU |- | rowspan="2" | Décodage | Exécution / ALU |- | Exécution / ALU |} Nous allons d'abord voir ce qu'il en est pour le ''front-end'', à savoir la partie du CPU qui s'occupe de charger et décoder les instructions. Elle regroupe l'unité de chargement, les décodeurs, l'unité de prédiction de branchement, la file de µops, la file d'instruction, et divers caches. En soi, les changements portent surtout sur l'unité de chargement et les décodeurs. ===Le chargement de plusieurs instructions consécutives=== Un processeur superscalaire charge plusieurs instructions en même temps, ''en tenant compte de la présence de branchements''. La partie en italique est importante et elle a une conséquence très forte : la manière de faire est fortement différente entre un CPU superscalaire étroit et large. Un CPU superscalaire étroit fait au plus simple : il charge un bloc de 2, 3, 4 instructions consécutives en mémoire RAM. Il faut alors doubler/tripler/quadrupler la taille du bus connecté au cache d'instruction. Par exemple, pour charger quatre instructions de 32 bits chacune, il suffit de lire un bloc de 128 bits dans le cache d'instruction. Un bloc de 8, 16, 32 octets est donc lu depuis le cache et est ensuite découpé en instructions, envoyées chacun à un décodeur. Charger plusieurs instructions consécutives ne marche bien qu'en absence de branchements. La présence de branchements casse cette dynamique, et il faut gérer leur présence. * Sans prédiction de branchement, toutes les instructions d'un bloc sont décodées et exécutées en même temps, même s'il y a un branchement dans le tas. * Avec prédiction de branchement, les instructions situées après le branchement ne sont pas décodées ni exécutées. L'unité de chargement coupe le bloc chargé au niveau du premier branchement pris, remplit les vides avec des NOP, avant d'envoyer le tout à l'unité de décodage. Les instructions à l'adresse de destination seront chargées au cycle suivant, en chargeant un nouveau bloc. [[File:Fetch sur un processeur superscalaire avec prediction de branchements.png|centre|vignette|upright=2|Fetch sur un processeur superscalaire avec prédiction de branchements.]] Un autre défaut est que sur le CPU charge un bloc de 128 bits à la fois, les instructions doivent être alignées sur 128 bits. Et on peut généraliser à n'importe quelle taille de bloc. Si les instructions ne sont pas alignées, il faut gérer la situation. Il arrive qu'une instruction soit à cheval sur deux blocs, avec un morceau dans le premier bloc, un autre dans le second. Dans ce cas, l'instruction est chargée en deux fois, et des circuits doivent gérer la situation pour recoller les morceaux. Sur les CPU avec des instructions de taille fixe, le problème ne survient que rarement. Mais il est fréquent avec des instructions de taille variable. Un autre défaut est lié aux branchements. Imaginez qu'un branchement s'exécute et branche vers une instruction de destination. Rien n'indique que l'instruction de destination sera au tout début d'un bloc. Si ça se trouve, elle sera en troisième position d'un bloc de 4 instructions. Une solution simple charge le bloc en respectant l'alignement, et les deux premières instructions du bloc ne sont pas à exécuter. Il y a donc une petite perte de performance. Certains CPU, comme l'IBM RS/6000, ont la machinerie charger un bloc de N instructions même s'il n'est pas aligné. La machinerie en question se trouve dans le cache d'instruction et est tout sauf simple à expliquer,ce qui fait que je me permets de la passer sous silence. Un détail, cependant : elle ne permet pas de charger des blocs qui sont à cheval sur deux lignes de cache. ===Le décodage parallèle des instructions=== Pour le décodage des instructions, plusieurs instructions sont décodées en même temps. On parle alors de '''décodage parallèle'''. Pour ce faire, un processeur superscalaire contient plusieurs décodeurs, chacun pouvant décoder une instruction en parallèle des autres. Prenons par exemple un processeur superscalaire à 4 voies : le décodage se fera dans quatre décodeurs séparés, qui fonctionneront en parallèle. Cependant, cette implémentation ne marche bien que pour les processeurs RISC, les processeurs CISC devant faire avec un microcode gourmand en circuits. Les processeurs CISC utilisent des décodeurs hybrides, avec un microcode qui complémente un décodeur câblé. Dupliquer le microcode aurait un cout en transistors trop important, ce qui fait que seuls les décodeurs câblés sont dupliqués. Les CPU CISC superscalaires disposent donc de plusieurs décodeurs simples, capables de décoder les instructions les plus courantes, avec un seul microcode. La conséquence est qu'il n'est pas possible de décoder deux instructions microcodées en même temps. Par contre, il reste possible de décoder plusieurs instructions non-microcodées ou une instruction microcodée couplée à une instruction non-microcodée. Vu qu'il est rare que deux instructions microcodées se suivent dans un programme, le cout en performance est extrêmement mineur. Il faut noter qu'une instruction correspond à une ou plusieurs µops. Il est donc possible de charger 5 instructions et de se retrouver avec 8 µops en sortie des décodeurs. Et c'est la source d'une distinction très importante. Plus haut, j'ai parlé de processeurs superscalaires à N voies. La terminologie peut avoir deux interprétations. La première est que le processeur charge/décode N instructions à la fois. Une seconde est qu'il est capable d'exécuter/émettre N µops en même temps. Les deux ne sont pas équivalentes, vu qu'un processeur superscalaire peut, par exemple, charger/décoder N instructions et émettre/exécuter N+2 µops. La distinction est particulièrement importante sur les processeurs superscalaires larges. Certains d'entre eux sont capables de charger 5 instructions et d'en émettre le double ! Un exemple classique était les processeurs Athlon d'AMD, qui pouvaient décoder deux instructions par cycle, mais émettre 4 µops par cycle. Ce n'était pas un problème, car beaucoup d'instructions, même basiques, étaient décodées en 2 µops. ===L'émission parallèle des instructions=== La superscalarité modifie en profondeur l'unité d'émission, notamment au niveau de son interface. Avant, elle recevait une micro-opération sur son entrée, et fournissait la micro-opération émise sur une sortie. Et cela vaut aussi bien pour une unité d'émission simple, un ''scoreboard'', une fenêtre d'instruction ou des stations de réservation. Mais avec l'émission multiple, les sorties et entrées sont dupliquées. Pour la double émission, il y a deux entrées vu qu'elle doit recevoir deux micro-opération décodées/renommées, et deux sorties pour émettre deux micro-opérations. Dans ce qui suit, nous ne parlerons pas d'entrée et de sortie, mais de '''ports de décodage''' et de '''ports d'émission'''. Les ports de décodage reçoivent des micro-opérations provenant de l'unité de décodage, les ports d'émissions sont pour les micro-opérations émises. Les ports d'émission injectent les micro-opérations émises dans le pipeline, elle sont propagée au banc de registre, aux ALUs, etc. Un processeur superscalaire a forcément plusieurs ports d'émission (et de décodage). Si l'interface de l'unité d'émission change, l'intérieur est tout autant modifié. Sur un CPU non-superscalaire, l'unité d'émission reçoit une µop "candidate à l'émission", et détecte les dépendances entre avec les µops en vol, en cours d'exécution dans le pipeline. Et c'est toujours le cas sur les processeurs superscalaires : l'unité d'émission détecte les dépendances entre les µops candidates et les µops en vol. Mais avec la siperscalarité, elle doit en plus tester les dépendances entre les µops candidates. Si l'unité d'émission est un ''scoreboard'', il doit détecter les dépendances entre instructions émises simultanément et les sérialiser si besoin. Avec une fenêtre d'instruction, il n'y a pour ainsi dire rien à faire, vu que les dépendances sont éliminées par le renommage de registre et que les signaux de réveil gèrent les dépendances RAW. C'est la raison pour laquelle les processeurs superscalaires modernes n'utilisent pas de ''scoreboard''. ==Les processeurs implémentés avec plusieurs pipelines== Nous venons de voir comment la superscalarité impacte le ''front-end'', il est maintenant de voir ce qu'il en est de l'unité d'émission et des ''back-ends''. Un processeur superscalaire peut utiliser l'exécution dans le désordre et le renommage de registres, mais ce n'est pas une obligation. Historiquement, les premiers processeurs superscalaires n'utilisaient pas d'exécution dans le désordre. Le meilleur exemple est celui du Pentium 1 et 2 d'Intel. Ils étaient tous deux des processeurs superscalaires, mais n'avaient pas d'exécution dans le désordre, qui est apparue sur le Pentium 3. les explications sont plus simples si on met de côté l'exécution dans le désordre. Vous vous dites que l'implémentation d'un processeur superscalaire n'est pas la même suivant que le CPU utilise l'exécution dans le désordre, ou non. Mais dans les faits, la distinction est plus subtile que ca. Pour l'expliquer, je vais utiliser une classification originale, qui sépare les processeurs superscalaires en trois sous-types. La distinction se fait sur la manière dont la superscalarité est implémentée en matériel.La classification que j'utilise distingue : * Les processeurs avec plusieurs pipelines, sans exécution dans le désordre. * Les processeurs superscalaires avec un pipeline dynamique. * Les processeurs avec des fenêtres d'instruction décentralisées. La distinction n'est pas qu'une question d'implémentation matérielle. Elle impacte aussi le ratio entre instructions chargées/décodées et instructions émises. Vous vous dites que les deux sont égales, mais ce n'est le cas que sur les processeurs avec N pipelines ! Sur les autres processeurs, il est possible de décoder 4 instructions, mais d'en émettre maximum 6 par cycle ! Et sur les CPU avec des fenêtres d'instruction décentralisées, il y a aussi une différence entre le nombre d'instructions qui sont ''dispatch'' et ''schedule''. La catégorie la plus simple est celle des '''processeurs avec plusieurs pipelines'''. Ce sont des processeurs superscalaires sans exécution dans le désordre. Ils partent d'un processeur non-superscalaire, et ajoutent un second pipeline, en sortie de l'unité d'émission. Le second pipeline permet l'émission parallèle d'une seconde instruction. Le second pipeline n'est pas identique au pipeline originel, sur plusieurs points. Premièrement, il n'a pas exactement les mêmes circuits. Le second pipeline est généralement composé d'une unité de calcul, d'une FPU, éventuellement d'un second banc de registres, guère plus. Il permet d'exécuter une seconde opération de calcul, pas plus, ce qui est à l'origine de contraintes d'appariemment assez fortes. De plus, il n'a pas forcément la même longueur, il peut avoir des étages en moins ou en plus. Il s'agit de la forme la plus simple de processeur superscalaire et nous allons leur consacrer cette section. ===La double émission entière-flottante=== L'exemple le plus simple utilise un second pipeline pour les opérations flottantes, séparé du pipeline originel. L'ajout d'un pipeline flottant permet d'émettre une micro-opération entière en même temps qu'une micro-opération flottante, ce qui s'appelle la '''double émission entière-flottante'''. La micro-opération entière s'exécute dans l'ALU entière, la micro-opération flottante dans la FPU. Les exemples les plus notables sont le PA-RISC 7100, le POWER 1 (RIOS .& et .9) et le PowerPC 601. L'Apollo DN10000 et l'Intel i860 utilisaient aussi cette forme de double émission, mais étaient des prototypes de CPU VLIW. [[File:Double émission entière-flottante.png|centre|vignette|upright=2|Double émission entière-flottante]] Pour bien expliquer les choses, prenons le processeur illustré ci-dessous, avec deux ''back-end'' séparés : un pour la FPU, et un autre pour le reste. Les instructions entières et mémoire passent toutes par le ''back-end'' généraliste. [[File:CPU non-superscalaire basique, avec une FPU.png|centre|vignette|upright=1.5|CPU non-superscalaire basique, avec une FPU]] Les deux ''back-end'' sont des pipelines séparés, mais il faut que l'unité d'émission soit spécialement conçue pour en profiter. Un processeur simple émission peut démarrer une seule µop par cycle : soit la µop est envoyée au ''back-end'' flottant, soit elle est envoyée au ''back-end'' général. Avec la double émission entière-flottante, le processeur peut envoyer une µop dans chaque pipeline, à chaque cycle. Vu que la FPU et son banc de registre forment déjà un pipeline séparé, les seules modifications se font au niveau du ''front-end'', unité d'émission inclue. {| |- |[[File:CPU simple émission avec une FPU.png|centre|vignette|upright=1.5|CPU simple émission avec une FPU.]] |[[File:Double émission entière flottante avec un second pipeline.png|centre|vignette|upright=1.5|CPU avec un second pipeline flottant.]] |} Notez que le terme double émission ''entière''-flottante est un peu trompeur. En réalité, la technique permet d'émettre une instruction flottante avec n'importe quelle autre instruction non-flottante, avec des restrictions pour les branchements. Il est par exemple possible d'exécuter un calcul flottant en parallèle d'un accès mémoire. Sur les CPU 32 bits, il est possible de faire les multiplications dans la FPU. Pour rappel, la FPU contient un multiplieur entier 53-54 bits, pour multiplier les mantisses de deux flottants 64 bits, qu'on peut utiliser pour faire les multiplications entières 32 bits. Cela permet d'émettre une multiplication en même temps qu'une opération entière dans l'ALU. Ainsi, le CPU pouvait émettre deux instructions : une opération entière de base, avec soit une multiplication, soit une opération flottante. En clair, une émission de type INT + (MUL /FLOAT). Par contre, on perd la possibilité d'émettre une multiplication entière avec une opération flottante, mais c'était un compromis qui en valait la peine. Le CPU PA-RISC 7100 implémentait cette optimisation. Il s'agissait d'un CPU double émission entière-flottante, qui utilisait donc un partitionnement assez simple. Le combo partitionnement + reconditionnement permettait ici de gagner en performance, pour un cout en matériel très limité. D’autant plus limité que le jeu d'instruction prévoyait le reconditionnement de la FPU. Les multiplications ne lisaient pas leurs opérandes dans les registres entiers, mais dans les registres flottants. ===La double émission entière=== Une autre possibilité ajoute un second pipeline, capable d'exécuter des opérations entières simples, appelé le pipeline entier. Le processeur supporte alors la '''double émission entière''', à savoir qu'ils peuvent émettre une opération entière en plus d'une autre instruction. L'opération entière en question est une opération simple, comme une addition, une soustraction, une opération bit à bit, une comparaison, etc. Les exemples classiques sont les processeurs Intel i960 et Pentium. L'implémentation était différente entre l'i960 et le Pentium : Le Pentium avait une seconde ALU entière, alors que le i960 rusait. Le Pentium dupliquait l'ALU entière, pour créer un second pipeline entier. Sur le Pentium originel, les contraintes d'appariement étaient assez strictes. Le Pentium avait une FPU, mais ne pouvait pas l'utiliser en double émission. Il lui était impossible d'exécuter une opération flottante en même temps qu'une opération entière (branchements inclus). Le Cyrix 6x86 était une sorte de Pentium produit par l'entreprise Cyrix, avec une microarchitecture différente. Il avait des contraintes d’appariement réduites. Par exemple, il pouvait émettre une opération flottante en parallèle d'une opération entière. Nous détaillerons ces processeurs dans le prochain chapitre, qui porte justement sur les CPU superscalaires x86. [[File:CPU simple et double émission avec un second pipeline entier.png|centre|vignette|upright=2.5|CPU simple et double émission avec un second pipeline entier.]] L'Intel i960 n'avait pas de FPU, juste une ALU entière et une unité mémoire. Il utilisait l'unité mémoire comme une seconde ALU entière limitée. Pour rappel, une unité mémoire contient une AGU, à savoir une ALU spécialisée dans les calculs d'adresse. Elle est capable de faire des additions, soustractions, et quelques décalages limités (des décalages par 2, 4, 8, pas plus). L'Intel i960 ajoutait des connexions entre AGU et banc de registre, afin que celle-ci serve de seconde unité de calcul. L'AGU est déjà reliée aux deux ports de lecture, pour lire les opérandes du calcul d'adresse, il suffit de la connecter sur le port d'écriture. Le processeur peut alors émettre deux instructions/µops entières, sous certaines conditions. [[File:Emission multiple des opérations entières, double émission.png|centre|vignette|upright=2|Émission multiple des opérations entières, double émission.]] ==Les processeurs superscalaires dynamiques== Les processeurs précédents, avec 2 pipelines, avaient une unité d'émission assez simple. Elle avait deux ports de décodage, et deux ports d'émission. Le nombre de ports de décodage et d'émission étaient identiques. Vous vous dites que c'est naturel, et intuitif, que c'est normal pour un processeur superscalaire. Mais en réalité, la majorité des processeurs superscalaires a plus de ports d'émission que de décodage. Expliquer pourquoi va cependant nous demander de parler de comment la superscalarité exploite les unités de calcul. Posons-nous cette question : de combien d'unités un processeur superscalaire a besoin ? Les unités en question ne sont nécessairement des unité de calcul, l'unité mémoire et l'unité de branchement comptent aussi. La réponse dépend du nombre de voies. Si le ''front-end'' charge et décode un paquet de N instructions, exécuter ces N instructions simultanément demandera là aussi au moins N unités de calcul. Notez le terme : ''au moins'', car c'est là tout l'enjeu de cette section. La réponse dépend aussi des contraintes d’appariement. Pour un CPU superscalaire "parfait", sans contraintes d’appariement, toutes les unités doivent être dupliquées, y compris l'unité mémoire. Un processeur à double émission parfaite devrait dupliquer l'ALU entière, le ''barrel shifter'', le circuit multiplieur, la FPU, l'unité mémoire, et l'unité de branchement, ce qui ferait passer de 6 unités à 12. Le cout en circuit est énorme, même pour de la double émission ! Et pour un CPU à N pipelines, le cout en circuit encore plus prohibitif ! En pratique, les processeurs superscalaires qui font ça sont très rares. La majeure partie des processeurs superscalaires utilisent d'autres techniques : le partitionnement, la duplication partielle des ALUs, la duplication des autres unités. ===Le partitionnement des unités fonctionnelles=== Les CPU superscalaires les plus simples ne dupliquent pas leurs unités de calcul, ils se contentent d'exploiter les unités existantes. Et pour expliquer comment, nous allons partir de l'exemple d'un processeur non-superscalaire contient une ALU entière, une FPU, une unité mémoire et une unité de branchement. Les quatre unités peuvent fonctionner en parallèle, à savoir qu'une unité peut exécuter une instruction à elle toute seule, sans interagir avec les autres. Cela suffit pour créer un processeur quadruple émission, qui peut émettre en même temps : une opération entière, une opération flottante, un branchement, un accès mémoire. Le principe marche aussi sur un processeur avec moins de 4 unités, par exemple sans FPU et/ou sans unité de branchement, ou avec au contraire des unités en plus. L'idée générale est d’émettre une µops séparée dans chaque unité fonctionnelle. Utiliser ainsi les unités existantes, pour rendre un CPU superscalaire, s'appelle le '''partitionnement superscalaire'''. Le terme n'est pas beaucoup utilisé en-dehors de quelques vieux articles académiques. Le partitionnement est fortement dépendant du jeu d'instruction, car il demande de bien séparer instructions entières, flottantes, branchements et instructions mémoire. Et autant c'est bien le cas sur les CPU RISC, autant les CPU CISC ne sont pas dans ce cas. Les CPU CISC ont des instructions ''load-op'', qui effectuent une instruction arithmétique et une µop mémoire. Et ces instructions sont très compliquées à implémenter avec le partitionnement. Et au-delà de ça, les CPU CISC ont beaucoup d'instructions qui sont décodées en plusieurs µops, à exécuter dans un ordre bien précis. On peut pas émettre les µops en question simultanément. Le partitionnement a un cout en circuit très limité, pour un gain en performance lui aussi limité. Pour reprendre le CPU de l'exemple précédent, il est rare que l'on ait à exécuter ces quatre instructions en même temps. En pratique, les combinaisons fréquentes sont les combinaisons calcul + accès mémoire, calcul + branchement, calcul entier + calcul flottant. Utiliser une quadruple émission de ce type n'est donc pas une bonne idée, alors que la double émission parait plus adaptée. Et c'est là que la différence entre ports d'émission et de décodage devient intéressante. L'idée est d'avoir une unité d'émission avec deux ports de décodage, et 4 port d'émission (un par unité). Les contraintes d’appariement varient alors suivant le processeur, mais la règle générale est que si deux micro-opérations tombent dans deux unités différentes, on peut les émettre en même temps. En clair, on peut émettre une opération entière avec une opération flottante, une opération entière avec un branchement, un accès mémoire avec un branchement, etc. Et cela rend le partitionnement bien plus intéressant que prévu. Les tout premiers CPU superscalaires étaient des CPU double émission, qui utilisaient le partitionnement avec : une ALU entière, une unité mémoire et une FPU. Nous parlerons de '''partitionnement INT-FLOAT-MEM'''. L'avantage sur la double émission flottante est que les combinaisons INT + MEM et FLOAT + MEM étaient autorisées. L'intérêt était d'émettre une opération en parallèle d'un accès mémoire, ce qui est un doublet d'instruction assez fréquent. Les exemples classiques sont l'HyperSPARC, de l'alpha 21064 et du PowerPC 603. Les contraintes d’appariement variaient grandement suivant le processeur. Le PowerPC 603 implémentait une double émission "parfaite". C'est à dire que toutes les combinaisons possibles à deux instructions étaient possibles, que ce soit INT + FLOAT, INT + MEM, FLOAT + MEM. En plus de cette double émission "parfaite", le CPU utilisait la technique dite de ''branch folding'', qui permettait d'exécuter un branchement sans utiliser d'unité de calcul, ce qui fait qu'il était qualifié de ''CPU 2-way + Branch''. A l'opposé, le processeur alpha 21064 avait pas mal de contraintes d’appariement, à savoir que certaines combinaisons de deux instructions étaient interdites, bien que compatibles avec les unités disponibles. Une des raisons est que le banc de registre n'avait pas assez de ports de lectures, ce qui fait que certaines combinaisons avec une écriture mémoire n'étaient pas permises. Les concepteurs avaient volontairement inséré ces contraintes afin de simplifier le design du processeur. Avant de poursuivre, précisons que la FPU regroupe un additionneur flottant et un multiplieur flottant. Et ce fait peut être exploité pour approfondir le partitionnement. La '''double émission FADD/FMUL''' permet d'émettre une addition flottante en même temps qu'une multiplication flottante. Elle a été utilisée sur les processeurs x86, Alpha et SPARC. Les autres circuits de calcul flottant sont souvent regroupés avec l'additionneur. La division est parfois e même port que le multiplieur flottant, parfois sur celui de l'additionneur (quand elle est microcodée et exécutée en enchainant les soustractions). ===La duplication des ALU entières=== Le partitionnement est une technique élégante, simple, facile à implémenter et au cout en circuits modeste. Mais il faut avouer que cela ne donne pas des CPU superscalaires très efficaces. La raison est que le partitionnement permet d'émettre des paires, triplets, quadruplets de micro-opérations qui sont rares en pratique. Les combinaisons les plus fréquentes en pratique sont des paires/triplets d'opérations entières, souvent des paires/triplets d'additions ou d'opérations entières simples. Il est donc primordial d'émettre plusieurs opérations entières simultanément. Le fait d'émettre et d'exécuter plusieurs opérations entières en même temps s'appelle l''''émission entière multiple'''. Pour exécuter la seconde/troisième opération entière, il faut dupliquer les ALU entières (ou réutiliser l'AGU comme ALU entière comme le fait l'Intel i960). Le processeur doit avoir deux ALU entières pour de la double émission entière, trois pour de triple émission entière, etc. De plus, il faut ajouter des ports au banc de registre, pour lire deux fois plus d'opérandes et décrire deux résultats. Et enfin, il faut rajouter un port d'émission pour pouvoir émettre une micro-opération entière en plus. [[File:Processeur non-superscalaire basique 03.png|centre|vignette|upright=2|Processeur non-superscalaire basique]] La tendance est de multiplier les ALU entières, mais pas le circuit multiplieur. Le cout en transistors est alors grandement réduit, au prix de l'apparition de dépendances structurelles. Le CPU peut émettre deux additions en même temps, ou une addition et une multiplication, mais pas deux multiplications. Il en est de même avec le ''barrel shifter'', qui n'est pas dupliqué. Il est donc possible d'émettre un décalage avec une opération de base, mais pas deux décalages en parallèle. Mais ces contraintes d’appariement ne sont pas un problème, car il est rare de devoir émettre deux multiplications/décalages en même temps. La plupart des programmes sont majoritairement remplis d'additions, avec des multiplications assez rares et des décalages qui le sont encore plus. La moyenne est proche d'une multiplication pour 4/5 additions. Vu que des multiplications consécutives sont très rares, dupliquer le multiplieur est presque inutile. Disposer de plusieurs circuits multiplieurs ou de plusieurs ''barrel shifters'' serait donc un cout en circuits qui ne servirait que rarement et n'en vaut pas la chandelle. Il faut noter que sur les CPU 32 bits, les multiplications peuvent être exécutées dans la FPU ou dans un multiplieur séparé, tout dépend de si le CPU exécute les multiplications dans la FPU ou pas. {|class="wikitable" |- ! Voie d'émission n°1 !! Voie d'émission n°2 !! Voie d'émission n°3 |- | ALU entière || ALU entière || FPU |} Reste à connecter les unités de calcul à l'unité d'émission (indirectement, car il y a des étages de pipeline entre les deux). Une implémentation basique utilisee un port d'émission par unité. Prenons l'exemple d'un CPU superscalaire avec 4 ALU entières, une FPU, et une unité mémoire. Vu qu'il y a 6 unités, cela fait donc 6 ports d'émission. L'exemple historique le plus impressionnant est celui du Motorola 88110, un CPU double émission qui possède 10 unités. Il contient deux ALU entières, un circuit multiplieur, un circuit diviseur, une unité de manipulation de bits, une unité mémoire, un additionneur flottant, et deux unités graphiques pour des calculs impliquant des pixels. Le multiplieur est utilisé à la fois pour les multiplications entières, flottantes et le produit de deux pixels. Idem pour le diviseur, qui sert pour les divisions entières et flottantes. Et chaque unité a son propre port d'émission. La conséquence est que les contraintes d'appariement du Motorola 88110 sont simples : tant que deux µops atterrissent dans des unités séparées, la paire est permise. Il est par exemple possible d'émettre simultanément deux opérations entières simples, vu qu'il y a deux ALU. Par contre, impossible d'émettre simultanément deux multiplications, deux divisions, deux opérations flottantes, deux manipulations de bit, deux accès mémoire, etc. De plus, le partage du multiplieur fait qu'on ne peut pas émettre une multiplication entière avec une multiplication flottante. Idem avec les divisions, vu que le diviseur est aussi partagé. Ce sont les seules contraintes. ===Le partage des ports d'émission=== La présence d'un circuit multiplieur et d'un ''barrel shifter'' pose des problèmes pour le partitionnement. Il est possible d'avoir un port d'émission dédié au multiplieur, et un autre au ''barrel shifter'', qui sont séparés de ceux pour l'ALU entière. Il est alors possible d'émettre une opération entière, une multiplication et un décalage, simultanément. Nous parlerons de '''partitionnement entier''' pour désigner cette possibilité. [[File:Partitionnement superscalaire entier.png|centre|vignette|upright=2|Partitionnement superscalaire entier.]] Le partitionnement entier est cependant très rare, car le gain en performance serait trop faible au regard des couts en circuit. Quelques processeurs historiques l'utilisaient, mais ce n'est plus le cas de nos jours. La raison est que les multiplications et les décalages sont assez rares dans le code, ce qui fait que le port d'émission associés sont sous-utilisés. Et un port d'émission a un cout en circuit assez important. Et c'est sans compter qu'ajouter des ports sur le banc de registres juste pour un multiplier sous-utilisé n'en vaut pas la peine. L'idéal serait de trouver un moyen de réduire le nombre de ports d'émissions, sans réduire le nombre d'unités de calcul. [[File:Emission multiple des opérations entières, implémentation naive.png|vignette|Émission multiple des opérations entières, implémentation naive]] La solution consiste à '''partager les ports d'émission''', à savoir connecter un port d'émission à plusieurs unités de calcul. L'exemple classique est justement lié aux circuits multiplieur et ''barrel shifters''. L'idée est que le multiplieur et le ''barrel shifter'' sont mis sur le même port d'émission qu'une ALU entière. Prenons par exemple le processeur à double émission entière illustré ci-contre. Un port d'émission est relié à une ALU entière seule, et l'autre port d'émission alimente la seconde ALU entière et le multiplieur. La conséquence est que le processeur peut émettre deux opérations entières, dont une multiplication. Si on avait utilisé un port d'émission dédié au multiplieur, il aurait été possible d'émettre deux opérations entières et une multiplication. On perd donc une voie, mais on gagne en flexibilité et on économise des circuits. Notons que ce partage signifie aussi que le multiplieur partage les ports sur le banc de registre, ce qui fait une double économie. [[File:Emission multiple des opérations entières, implémentation courante.png|centre|vignette|upright=1.5|Émission multiple des opérations entières, implémentation courante]] En général, le ''barrel shifter'' et le multiplieur sont placés sur deux voies d'émission séparés, ce qui permet d'émettre un décalage et une multiplication en même temps. Les mettre sur le même port ne le permettrait pas. {|class="wikitable" |- ! Voie d'émission n°1 !! Voie d'émission n°2 !! Voie d'émission n°3 |- | * ALU entière * Multiplieur | * ALU entière * Barrel shifter | FPU |- | * Additions, soustractions, opérations bit à bit, etc. * Multiplication | * Additions, soustractions, opérations bit à bit, etc. * Décalages et rotations | Opération flottante |} Mais le partage des ports d'émission peut appliquer à n'importe quelle unité. Prenons l'exemple d'un CPU superscalaire avec 4 ALU entières, une FPU, et une unité mémoire. Vu qu'il y a 6 unités, cela fait donc 6 ports d'émission. Cependant, il est rare qu'un bloc de 6 instructions contienne exactement 4 opérations entières, une opération flottante, et un accès mémoire. Beaucoup de ports d'émission seraient sous-utilisés. La solution est de partager des ports d'émission, pour passer à la quadruple émission. Le CPU peut alors répartir 4 instructions sur les ports d'émission adéquats, à chaque cycle. Le CPU a donc 4 ports de décodage et 6 ports d'émission. Une implémentation classique permet n'importe quelle combinaison de 4 instructions compatible avec les 6 unités. Par exemple, il devient possible d'émettre simultanément 4 additions, la combinaison 2 * INT + FLOAT + MEM, 3 * INT + MEM, et bien d'autres. L'exemple relie une ALU entière, la FPU et l'unité mémoire sur le même port. Le CPU a alors quatre ports d'émission, qui ne sont pas "identiques". Trois d'entre eux sont reliés à une ALU entière chacun, rien d'autre. Le quatrième est lui relié à une ALU entière, la FPU et l'unité mémoire. On économise alors deux ports d'émission, mais cela entraine l'apparition de contraintes d’appariement. Le CPU peut émettre 3 opérations entières + n'importe quelle autre opération. Mais les autres combinaisons sont interdites. Le CPU peut émettre 6 opérations entières simultanément, grâce à 6 ALU entières. Les accès mémoire ou opérations flottantes ne viennent pas en plus, mais en remplacement des opérations entières. Regrouper plusieurs ALU sur un même port d'émission est donc à l'origine de dépendances structurelles, de nouvelles contraintes d’appariement. Dans certaines situations de '''''port contention''''', on a assez d'ALU pour exécuter N micro-opérations, mais la répartition des ALUs sur les ports l'empêche. Impossible d'émettre deux micro-opérations sur deux ALU si elles sont sur le même port d'émission. On peut réduire les cas de ''port contention'' en répartissant correctement les ALU sur les ports d'émission, mais sans jamais les supprimer complétement. Un exemple est celui du PowerPC 604. Il implémentait la quadruple émission, alors qu'il avait 5 unités de calcul. Mais deux unités partageaient un même port d'émission. Dans le détail, il avait deux ALU entières, un circuit multiplieur/diviseur, une FPU, une unité mémoire. La FPU et le multiplieur étaient sur le même port d'émission. En conséquence, le CPU pouvait émettre seulement les combinaisons INT + INT + MEM + (MUL/FLOAT). : Il est intéressant de voir l'exemple d'un processeur superscalaire parfait, sans contraintes d'appariement, à N voies. Pour cela, on part d'un processeur non-superscalaire, avec M unités de calcul. Un tel processeur peut s'implémenter avec N ports d'émission, chacun relié à M unités de calcul possibles, ce qui fait que les M unités de calcul sont dupliquées en N exemplaires. Si on prend par exemple un CPU non-superscalaire avec une ALU, une FPU, une unité mémoire et une unité de branchement, un port d'émission sera relié à une ALU, une FPU, une unité mémoire et une unité de branchement. ===La duplication des autres unités fonctionnelles=== Dupliquer les ALU entière est une première étape, mais il est aussi possible de dupliquer les autres unités fonctionnelles. Par exemple, il est possible de dupliquer l'unité mémoire ou l'unité de branchement. Reste à voir quelles unités dupliquer, et pourquoi. Précisons que dans ce qui va suivre, nous allons parler de la duplication des unités en général, pas seulement des unités mémoire/branchement. Nous allons continuer de parler de la duplication des ALU entières dans ce qui suit, mais de manière plus générale. Pour un processeur à N voies, il est théoriquement possible de dupliquer toutes les unités en N exemplaires. Mais ce n'est jamais fait en pratique, car le cout en circuits serait trop important et le gain en performance trop faible. Mieux vaut dupliquer certaines unités bien précises, quitte à avoir des contraintes d'appariement. Et à ce petit jeu, toutes les duplications ne se valent pas, certaines sont plus rentables en matière de performance, le cout en circuit n'est pas le même selon l'unité, etc. La rentabilité en performance dépend surtout des contraintes d'appariement. Les contraintes d'appariement entrainent une baisse de performance significatives si elles interdisent des paires d'instructions fréquentes. Aussi, les opérations très fréquentes, souvent appairées, gagnent à dupliquer l'unité associée. Et au contraire, les unités rarement utilisées ne doivent pas être dupliquées. Il se trouve que les unités les plus utilisées sont les ALU entières, suivies par les unités mémoire et l'unité de branchement. Le cout en circuit n'est aussi pas le même. Dupliquer les ALU entière est trivial, car elles ont un budget en transistors assez faible. Les circuits multiplieurs, diviseurs, et les ''barrel shifter'' ont eux un cout en transistor plus élevés. Si on devait faire un classement, on aurait : ALU entière < ''barrel shifter'' < multiplieur. Pour les unités mémoire, le cout en transistor est là aussi conséquent. Le résultat final est que dupliquer les ALU entières est privilégié sur le reste, comme l'a vu dans la section précédente. Les unités mémoire arrivent en seconde place, suivie par les multiplieurs/''barrel shifter'' à la troisième place, et les unités de branchement à la quatrième place. Les CPU superscalaires larges ont donc tendance à multiplier les unités mémoire en second. Les multiplieurs et ''barrel shifters'' sont eux aussi dupliqués sur les CPU superscalaires larges, mais cela vient souvent après avoir dupliqué les unités mémoire. La raison est que les accès mémoire sont critiques pour la performance des CPU à exécution dans le désordre, pas les multiplications. Pour les unités de branchement, une subtilité complique le fait d'en installer plusieurs. Que faire quand deux branchements sont pris ? La solution la plus simple est de ne pas se trouver dans cette situation : on n'utilise qu'une seule unité de branchement et on n’exécute qu'un seul branchement par cycle. La majorité des processeurs superscalaires étroits font ainsi. Les processeurs superscalaires larges ont cependant deux unités de branchements, mais ils n'acceptent qu'un seul branchement pris à la fois. Si les deux branchements pris sont exécutés en même temps, le processeur n’exécute que le plus récent, dans l'ordre du programme. ===Les CPU superscalaires mélangent les trois techniques=== Les CPU superscalaires utilisent toutes les techniques à la fois : partitionnement, duplication de l'ALU entière, duplication des autre unités fonctionnelles. Chaque processeur utilise ces trois stratégies à sa sauce, en fonction de son budget en transistor. Les quatre techniques ont cependant un cout en circuit différent, et donnent des gains de performances eux aussi forts différents. * Le '''partitionnement superscalaire''' a un cout en circuit modéré, car on n'a pas besoin de dupliquer d'ALU. * La '''duplication des ALU entières''' est la technique la plus importante : elle a un cout en circuit modéré, et donne des gains en performance impressionnants. * La '''duplication des autres unités''' a un cout en circuit variable, pour des gains en performance eux aussi variables. * Le '''partage des ports d'émission''' réduit le cout en circuit du CPU, pour des pertes de performances mineures si bien fait. L'usage du partitionnement seul, sans duplication, était surtout le fait des processeurs commercialisés entre les années 1989 et 1993. Le partage des ports d'émission a lui aussi mis un peu de temps pour s'imposer. Globalement, les processeurs superscalaires ont évolués progressivement, pour se rapprocher d'une sorte de standard. Mais pour comprendre ce qu'est ce standard, et en quoi les CPU historiques s'en sont progressivement rapprochés, il faut étudier l'histoire des CPU superscalaires. L'histoire des CPU superscalaires a commencé dès 1960, mais est resté un projet de recherche pendant plusieurs décennies. De nombreux designs ont été conçus, mais abandonnés avant la production, comme l'ACS-1 d'IBM. Les années 1980 ont vu l'arrivée de prototypes de processeurs VLIW, mais qui n'étaient pas superscalaires. Par exemple, l'Apollo DN10000 et l'Intel i860 pouvaient émettre deux instructions à la fois, sous conditions, mais demandaient que le compilateur fasse une bonne partie du travail. Les premiers CPU superscalaires commercialisés sont sortis en 1989. Deux CPUs superscalaires sont sortis cette année là : le RS/6000 d'IBM et l'i960CA d'Intel. Puis, la compétition s'est aussi lancée dans la bataille. Les CPU commercialisés avant l'année 1993 étaient à double émission, à l'exception du SuperSPARC qui utilisait la triple émission. Les modèles après 1993 hésitaient entre double et quadruple émission. Dans le tableau ci-dessous, les processeurs quadruple émission sont en bleu, les triple émission sont en rouge, les autres ne sont pas en couleur. {|class="wikitable" |- ! !! IBM !! HP PA-RISC !! SPARC !! Intel!! Alpha !! Autres |- ! 1989 | RS/6000 || || || i960CA || || |- ! 1990 | || || || || || |- ! 1991 | || || || || || Motorola 88110 |- ! 1992 | RSC || PA 7100 || class="f_rouge | SuperSPARC || || 21064 || |- ! 1993 | PPC 601 || || HyperSPARC || Pentium || || |- ! 1994 | class="f_bleu | POWER 2 || PA 7200 || || || class="f_bleu | 21164 || |- ! 1995 | 603/604 || class="f_bleu | PA 8000 || class="f_bleu | UltraSPARC 1 || Microarchitecture P6 (+ concurrence AMD) || || class="f_bleu | MIPS R1000 |- ! 1996 | class="f_bleu | 620 || || || || class="f_bleu | 21264 || |- ! ... || ... || ... || ... || ... || ... || ... |} Les premiers CPU à double émission sont assez représentatifs des CPU à double émission modernes. Il faut dire que se limiter à deux voies ne permet pas de faire beaucoup de choses. Voici le tableau chronologique disant quel CPU utilisait quelle technique. * Les CPU en vert utilisaient la double émission entière-flottante. * Les CPU en rouge utilisaient la double émission entière "pure". * Les CPU en jaune utilisaient un ''partitionnement complet'' entre ALU, FPU et unité mémoire. * Les CPU sans couleur utilisaient un grand nombre d'unités, avec beaucoup de ports d'émission. * Les CPU barrés utilisent la triple ou quadruple émission et seront vu dans la section suivante. {|class="wikitable" |- ! !! IBM !! HP PA-RISC !! SPARC !! Intel!! Alpha !! Autres |- ! 1989 | class="f_vert | RS/6000 || || || class="f_rouge | i960CA || || |- ! 1990 | || || || || || |- ! 1991 | || || || || || Motorola 88110 |- ! 1992 | class="f_vert | RSC || class="f_vert | PA 7100 || <s>SuperSPARC</s> || || class="f_jaune | 21064 || |- ! 1993 | class="f_vert | PPC 601 || || class="f_jaune | HyperSPARC || class="f_rouge | Pentium || || |- ! 1994 | <s>POWER 2</s> || PA 7200 || || || <s>21164</s> || |} C'est en 1994, que la quadruple émission est arrivée et que les CPU ont essayé divers implémentations. Il y avait alors trois stratégies pour la double/triple/quadruple émission, qui partageaient des problèmes similaires. La première implémentation dupliquait les ALUs et les FPUs, ce qui permettait d'émettre deux opérations flottantes avec deux opérations entières. Le POWER 2 dupliquait ses FPUs à la perfection, alors que le 21264 séparait l'additionneur et le multiplieur flottant. Les accès mémoire étaient traités comme des opérations entières et le processeur pouvait souvent en émettre deux à la fois. Mais une telle organisation n'est plus utilisée de nos jours, car, sans instructions flottantes à exécuter, elle est équivalente à de la double émission. La seconde implémentation utilisait deux ALU entières (dont un multiplieur), une unité mémoire et une FPU.Cette organisation est intéressante, mais encore trop stricte pour donner de bons résultats, les quadruplets avec exactement deux opérations entières, un accès mémoire et une opération flottante étant trop rares. L'usage de ces 4 unités en double ou triple émission a été utilisé sur le PA 7200 et le SuperSPARC. Malheureusement, en plus de problèmes au niveau de son cache, le SuperSPARC avait de nombreuses contraintes d’appariement, car le banc de registre n'avait pas assez de ports pour alimenter certaines combinaisons d'instructions. De plus, s'il émettait une multiplication, il ne pouvait pas émettre de seconde µop. Enfin, une troisième implémentation utilisait un grand nombre d'unités fort différentes, bien plus que quatre, avec au minimum deux ALU entières. Prenons l'exemple de l'UltraSPARC, qui avait : deux ALU entières, une unité mémoire, une unité de branchement, un additionneur flottant, un multiplieur flottant, un diviseur flottant (aussi utilisé pour le calcul des racines carrées), et 2 unités graphiques pour les calculs sur des pixels. En tout, cela fait 9 unités différentes, pour de la quadruple émission. Dans le même genre, les processeurs MIPS R8000 et R10000 avaient une implémentation plus moderne, dans le sens où toutes les unités étaient dupliquées. Le CPU avait deux ALU, deux FPU et deux unités mémoire. Il pouvait donc émettre quatre instructions à la fois, tant que le paquet collait avec ces six unités. Moitié opérations entières, moitié opérations mémoire, avec de quoi remplacer les opérations entières par des opérations flottantes. Le PA 8000, lui, dupliquait toutes ses unités en deux exemplaires, ce qui n'est pas l'idéal, aucun processeur moderne ne fait cela aujourd'hui. Le tout est résumé dans le tableau ci-dessous, avec : * en vert les CPU utilisant la première stratégie ; * en rouge les CPU utilisant la seconde stratégie ; * en jaune les CPU utilisant la troisième stratégie. {|class="wikitable" |+ Processeurs quadruple émission historiques |- ! Année !! Processeur !! Unités |- ! 1991 | class="f_jaune | Motorola 88110 || class="f_jaune | 2 ALU + MUL + DIV + BIT + MEM + FADD + 2 unités graphiques |- |- ! rowspan="2" | 1994 | class="f_vert | POWER 2 || class="f_vert | 2 ALU/MEM + 2 FPU (+ 2 Branchements via ''branch folding'') |- | class="f_rouge | 21164 || class="f_rouge | 2 ALU + FPU + MEM |- ! rowspan="3" | 1995 | PA 8000 || 2 ALU + 2 ''barrel shifters'' + 2 MEM + 2 FPU (FMA) + 2 FPU (DIV et SQRT) + 2 SIMD |- | class="f_jaune | UltraSPARC 1 || class="f_jaune | 2 ALU + MEM + BRANCH + FADD + FMULFDIV + 2 unités graphiques |- | MIPS R8000 et R10000 || 2 ALU + 2 FPU + 2 MEM |- ! rowspan="2" | 1996 | class="f_rouge | 620 || class="f_rouge | 2 ALU + FPU + MEM |- | class="f_vert | 21264 || class="f_vert | 2 ALU/MEM + 2 FPU (FADD + FMUL) |} Malheureusement, ces CPUs historiques n'avaient ni assez d'unités de calcul entières, ni d'unités mémoire, pour remplir la quadruple émission. Environ la moitié des instructions d'un programme correspond à des instructions entières. Pour en profiter, un processeur à N voies doit disposer d'au minimum N/2 ALU entières, l'idéal étant d'aller au-delà. Les CPU superscalaires historiques avaient 2 ALU pour de la quadruple émission, ce qui était très limite. En comparaison, les CPU superscalaires modernes mettent le paquet niveau ALU entière. La plupart des CPU x86 modernes descendent rarement sous les 2/3, la plupart remplit presque la totalité de la largeur du CPU. Pour donner un exemple, la microarchitecture Golden Cove d'Intel est un processeur à 6 voies, avec 5 ALU entières. Pour être précis, un programme informatique exécute environ 40% de son code dans des instructions arithmétiques, un autre 40% dans les accès mémoire, et un le reste pour... le reste. La conséquence est que les processeurs modernes dupliquent leurs unités mémoire. Par exemple, la microarchitecture ''Golden Cove'' d'Intel dispose de trois unités mémoire : deux spécialisées dans les écritures, trois dans les lectures. La microarchitecture ''Gracemont'' utilise la pentuple émission et peut émettre 4 µops entières et 4 µops mémoire. Notez que le ratio est équilibré entre opérations entières et mémoire, avec soit égalité, soit un avantage pour les ALU entières. Une autre régularité est que la largeur totale du processeur peut être occupée par des opérations entières/mémoire. Pour le dire autrement, sur un processeur quadruple émission, il pourra émettre un mix de 4 instructions entière/mémoire, avec zéro opération flottante. Idem avec des processeurs triples ou pentuple émission, voire plus. Si le processeur émet une opération flottante, elle remplace une opération entière. Prenons l'exemple des processeurs AMD K6, sortis en 1997, juste avant les AMD Athlon. Ils avaient 10 unités de calcul, 6 ports d'émission, 4 ports de décodage. Voici exactement comment étaient répartie les unités de calcul sur les ports d'émission. C'était des processeurs quadruple émission, ce qui fait que la moitié de la largeur du processeur sert pour des opérations entières, et l'autre moitié pour les accès mémoire. {|class="wikitable" |- ! Port d'émission n°1 !! Port d'émission n°2 !! Port d'émission n°3 !! Port d'émission n°4 !! Port d'émission n°5 !! Port d'émission n°6 ! |- | ALU entière || ALU entière || FPU || Unité de branchement || Unité LOAD || Unité STORE |- | || Multiplieur || || || || Unité de calcul d'adresse (instruction LEA) |- | || ''Barrel shifter'' || || || || Unité pour les instructions PUSH |} Les microarchitectures K7, K8 et K10, utilisées sur les anciens CPU AMD Athlon, sont un exemple moins représentatif. Ces microarchitectures utilisent le partage des ports d'émission au maximum. C'était des microarchitectures à triple émission, avec six ports d’émission. Il y avait trois ports d'émission pour les opérations flottantes, et trois ports d'émission pour les micro-opérations entières/mémoire. Il y avait trois ALU entières et trois unités mémoire, chaque ALU partageait un port d'émission avec une unité mémoire. Elle pouvait donc émettre soit trois opérations entières, soit trois µops mémoire, soit un mix des deux. En plus des mix avec les opérations flottantes et branchements. {|class="wikitable" |- ! Port d'émission n°1 !! Port d'émission n°2 !! Port d'émission n°3 !! Port d'émission n°4 !! Port d'émission n°5 !! Port d'émission n°6 ! |- | ALU entière || ALU entière || ALU entière || FADD || FMUL || FPU (autres opérations) |- | Multiplieur || || || || || |- | LOAD/STORE || LOAD/STORE || LOAD/STORE || || || |} Les CPU superscalaires larges dupliquent les multiplieurs et/ou les ''barrel shifters'', et même les unités de branchement. La raison est que quand la largeur du CPU augmente, le processeur traite des paquets d'instructions de plus en plus gros. Par exemple, pour un CPU à 10 voies, cela veut dire émettre 10 instructions en même temps. Il est alors plus fréquent d'avoir deux multiplications, deux décalages, deux accès mémoire, deux branchements. Par exemple, la moyenne est d'une multiplication tous les 5 instructions. Les CPU superscalaires à 10 voies ont donc tendance à avoir deux multiplications par paquet d'instruction, en moyenne. Avoir deux multiplieurs est alors un gain en performance clair. Par exemple, la microarchitecture ''Golden Cove'' a deux ''barrel shifters''. C'est une architecture à 6 voies. {|class="wikitable" |- ! Port d'émission n°1 !! Port d'émission n°2 !! Port d'émission n°3 !! Port d'émission n°4 !! Port d'émission n°5 !! Port d'émission n°6 !! Port d'émission n°7 !! Port d'émission n°8 !! Port d'émission n°9 !! Port d'émission n°10 |- | ALU entière || ALU entière || ALU entière || ALU entière || ALU entière || LOAD || LOAD || LOAD || STORE || STORE |- | ''Barrel shifter'' || Multiplieur || Multiplieur || ''Barrel shifter'' || || || || || || |- | Branchements || Diviseur || || Branchements || || || || || || |} ==Les µops mémoire sur les processeurs superscalaires== Plus haut, nous avons volontairement passé sous silence les accès mémoire. La raison à cela est que leur support sur les CPU superscalaire peut se faire de plusieurs manières différentes, qu'il vaut mieux expliquer dans une section dédiée. Les différentes implémentations sont décrites dans la liste suivante, nous allons les voir une par une. * Avec la première méthode, les accès mémoire sont exécutés par le pipeline pour les opérations entières. * Avec la seconde méthode, les accès mémoire ont un pipeline dédié ; c'est l'''émission parallèle des accès mémoire''. * Avec la troisième méthode, il est possible d'émettre plusieurs accès mémoire simultanément : c'est l'''émission multiple des accès mémoire''. Voyons ces trois cas dans le détail. Nous verrons notamment les différentes formes d'émission multiple des accès mémoire. ===Les accès mémoire traités dans le pipeline entier=== Dans le cas le plus simple, les accès mémoire sont traités comme des opérations entières un peu spéciales. L'unité mémoire est sur le même port d'émission que l'ALU entière, ce qui fait que les accès mémoire remplacent des opérations entières. Faire ainsi permet d'utiliser une ALU entière pour les calculs d'adresse, pas besoin de rajouter une unité de calcul d'adresse séparée. Typiquement, un processeur de ce type place l'unité mémoire après l'unité de calcul. Les opérations entières n'utilisent que l'ALU entière, mais pas l'unité mémoire. Les accès mémoire exécutent un calcul d'adresse dans l'ALU entière, puis l'adresse calculée est envoyée à l'unité mémoire. Le tout est illustré ci-dessous, dans le cas d'un processeur historique à double émission entière-flottante. [[File:Processeur non-superscalaire basique 01.png|centre|vignette|upright=1.5|Processeur non-superscalaire basique]] Un exemple de CPU double émission de ce type est celui du Power PC 440, un CPU destiné à l'embarqué. Vu qu'il était destiné à l'embarqué, il n'avait pas de FPU, il n'avait pas besoin de manipuler de nombres flottants. Il n'a donc que des ALU entières, un multiplieur et une unité mémoire. Il dispose de deux ports d'émission. Le premier est connecté à une ALU entière et un circuit multiplieur, le second est relié à l'unité mémoire et une seconde ALU entière. Une organisation simple, mais très efficace ! [[File:PowerPC 440.png|centre|vignette|upright=2|Microarchitecture du PowerPC 440.]] L'implémentation économise une unité de calcul d'adresse, mais n'a pas de cout en performance significatif. Les accès mémoire étant très lents, émettre une µop entière en parallèle d'un accès mémoire ne sert pas à grand-chose. On peut émettre cette µop entière un cycle plus tard, elle sera terminée avant même que l'accès mémoire soit terminé, même si l'accès mémoire se fait dans le cache L1 ou L2. Pour comprendre pourquoi, faisons quelques calculs. Un accès mémoire prend deux-trois cycles quand il tombe dans le cache L1. Et on doit attendre qu'il soit terminé pour en émettre un nouveau. Combien d'instructions arithmétiques peut-on lancer pendant ce temps, sur un CPU triple émission ? Au premier cycle, on peut en émettre 2 (l'instruction mémoire prend une place, donc 3-1), puis 3 au second cycle. Ce qui fait 5 instructions arithmétiques au total. Or, il se trouve qu'il y a une instruction d'accès mémoire pour 5 instructions arithmétiques, environ. Par contre, ce compromis change quand on passe à la quadruple émission, voire au-delà. Dans ce cas, il devient intéressant de pouvoir émettre une nouvelle instruction mémoire à chaque cycle. Ce qui nous amène à la section suivante... ===L'émission parallèle des accès mémoire=== A partir de la quadruple émission, la tendance a été de séparer l'unité mémoire des ALU entières. Cela permet d'émettre des µops mémoire en même temps que des µops entières et/ou flottantes., ce qui porte le nom d''''émission parallèle des µops mémoire''', ou encore ''émission mémoire parallèle''. Par exemple, un processeur quadruple émission peut émettre en même temps deux opérations entières, une opération flottante et un accès mémoire. L'accès mémoire vient en plus des opérations entières, il n'est pas possible de remplacer l'accès mémoire par une opération entière. La technique ne permet pas de faire les calculs d'adresse dans l'ALU entière. Il faut donc utiliser une unité de calcul d'adresse séparée. Elle est intégrée dans l'unité mémoire, qui fait l'interface entre le cache et le banc de registres. Le cout en circuits est très faible, car les unités de calcul d'adresse n'utilisent guère plus qu'un additionneur et un circuit de décalage très simple. L'émission mémoire parallèle est plus facile à implémenter sur les CPU RISC, qui ont des instructions mémoire séparées du reste. Les CPU CISC ne sont pas dans ce cas, ce qui fait que séparer l'unité mémoire du pipeline entier est compliqué. Les CPU superscalaires historiques étaient pour moité des CPU RISC et pour moitié des CPU CISC, ce qui fait qu'ils n'utilisaient pas ou peu l'émission mémoire parallèle. Les premiers processeurs superscalaires se contenaient de l'émission parallèle mémoire, ils ne pouvaient émettre qu'un seul accès mémoire à la fois, l'unité mémoire n'était pas dupliquée. Et ce n'est pas tant un problème que ça, du moins sur les processeurs à double ou triple émission. En effet, il est rare que l'unité mémoire doive faire deux accès simultanés. Notons que le processeur peut exécuter une instruction mémoire en parallèle d'un calcul ou d'un branchement, seuls les accès mémoire simultanés sont impossibles. ===L'émission multiple des accès mémoire avec une LSQ=== Les processeurs superscalaires larges travaillent sur des paquets de 8 à 10 instructions, il est plus fréquent d'avoir plusieurs accès mémoire par paquet. Dans ces conditions, il est utile d'avoir une '''émission multiple des accès mémoire''', à savoir d'émettre plusieurs micro-opérations mémoire en même temps. Les processeurs superscalaires modernes sont capables d'émettre plusieurs lectures/écritures simultanément. Par exemple, ils peuvent émettre une lecture en même temps qu'une écriture, ou plusieurs lectures, ou plusieurs écritures. Il faut noter que selon le processeur, il peut y avoir des restrictions quant aux accès mémoire émis en même temps. Par exemple, certains processeurs peuvent émettre une lecture avec une écriture en même temps, mais pas deux lectures ni deux écritures. Ou encore, ils peuvent émettre deux lectures, une lecture et une écriture, mais pas deux écritures en même temps. Dans la majorité des cas, les processeurs ne permettent pas d'émettre deux écritures en même temps, alors qu'ils supportent plusieurs lectures. Il faut dire que les lectures sont plus fréquentes que les écritures. Les processeurs qui autorisent toutes les combinaisons de lecture/écriture possibles, sont rares. L'émission multiple des accès mémoire s'implémente dans l'unité mémoire. Pour rappel, l'unité mémoire s'interpose entre le cache L1 et le reste du pipeline. Dans le cas le plus simple, elle incorpore une ''load/store queue'' et des unités de calcul d'adresse. L'émission multiple des accès mémoire demande de rendre la ''Load-store queue'' multi-ports, afin de gérer plusieurs micro-opérations mémoire simultanées. Il faut aussi dupliquer les unités de calcul d'adresse reliées à la LSQ, chaque port d'émission mémoire devant avoir sa propre unité de calcul d'adresse. : Dans ce qui suit, nous parlerons d'AGU (''Adress Generation Unit'') pour désigner les unités de calcul d'adresse, et de LSQ pour designer la ''Load-store queue''. [[File:Processeur non-superscalaire basique 02.png|vignette|upright=1|Processeur non-superscalaire basique]] Dupliquer les ports de la LSQ et les AGU est nécessaire. Mais qu'en est-il du cache ? La réponse varie suivant le processeur. Il y a deux grandes méthodes pour implémenter l'émission multiple des accès mémoire. La première méthode ajoute des ports de lecture/écriture au cache de données, pour faire des lectures/écritures simultanées. Il faut alors dupliquer des unités de calcul, rajouter les ports sur le cache, mais aussi rajouter des ports sur la ''load/store queue'', pour qu'elle échange des données/commandes mémoire avec le cache. Le cout en circuits est alors conséquent La seconde duplique les unités de calcul, mais pas les ports d'accès au cache. Les accès mémoire sont alors accumulés dans la ''load/store queue'', qui sont ensuite traités un par un par le cache. Un exemple est celui des processeurs AMD de micro-architecture K7 K8, à savoir les anciens AMD Athlon et AMD Athlon 64, qui pouvaient calculer trois adresses simultanément, mais ne pouvaient faire qu'un seul accès au cache par cycle. Le cout en circuit est modéré : les unités de calcul coutent très peu, les ports de la ''load/store queue'' coutent déjà un peu plus. Quelques processeurs utilisaient des solutions intermédiaires. Un processeur utilisant cette technique est, par exemple, capable d'émettre quatre µops mémoire en même temps, mais avec seulement deux ports sur le cache. Un exemple classique étant les Pentium 1 et 2 d'Intel, dont nous parlerons dans le chapitre suivant. ===L'émission séparée des LOAD et STORE=== De rares processeurs ont des ports d'émission qui peuvent faire indifféremment lecture comme écritures. Mais les processeurs modernes préfèrent séparer ports d'émission pour les lectures et les écritures. Un exemple est celui des processeurs Intel de micro-architecture Nehalem, qui avaient un port de lecture et un port d'écriture tous deux reliés à la ''Load-store queue'', chacun avec leur propre unité de calcul d'adresse. Comme autre exemple, les processeurs skylake ont une LSQ avec deux ports de lecture et un port d'écriture, ce qui permet d'émettre deux lectures en même temps qu'une écriture. Dans cette section, nous allons étudier le cas avec des ports séparés pour les lectures et écritures. En général, les processeurs ont un peu plus de ports de lectures que d'écritures. Une raison est qu'un programme a, en moyenne, un tout petit peu plus de lectures que d'écritures. Mais la différence n'est pas énorme, c'est un léger déséquilibre. Une seconde raison est que l'exécution dans le désordre marche bien mieux quand les lectures ont lieu le plus tôt possible. Donc mieux vaut privilégier les lectures à l'émission, pour qu'elles se fassent un peu en avance. En pratique, la différence n'est pas très marquée. Il est courant d'avoir un port de lecture et un d'écriture, les CPU superscalaires très larges ont souvent deux ports de lecture et deux d'écriture. A la rigueur, quelques CPU ont trois ports de lecture et deux d'écriture. Le cas le plus simple est celui où on peut émettre une lecture et une écriture en même temps. Il s'implémente facilement si la LSQ est en réalité une simple ''Store queue'', à savoir qu'elle met en avance les écritures uniquement. Pour rappel, les écritures sont mises en attente dans la ''Store queue'', mais les lectures sont exécutées immédiatement. Une fois l'adresse à lire connue, la lecture consulte la ''Store queue'' pour détecter les dépendances de données, puis s'exécute. La donnée à lire est lue soit depuis la ''Store queue'', soit depuis le cache. [[File:Emission LOAD - Store séparée.png|vignette|upright=1|Émission LOAD - Store séparée]] L'implémentation de l'émission multiple est alors très simple. La ''Store queue'' a déjà deux ports, avec un pour les lectures et un autre pour les écritures. Idem pour le cache, qui a naturellement un port de lecture et un d'écriture. Émettre une lecture et une écriture simultanément ne demande donc pas de modification digne de ce nom. La ''Store queue'' doit juste supporter une lecture et une écriture simultanée. Ajouter l'émission d'une seconde lecture n'est pas compliquée : il faut juste rajouter un second port de lecture sur le cache et la ''Store queue''. Pour rappel, l'unité mémoire est précédée par une structure qui met en attente les micro-opérations mémoire avant leur émission finale. La structure en question varie suivant l'implémentation, mais un cas particulier se marie bien avec l'émission multiple des µops mémoire. Il s'agit du cas avec une file de µops séparée pour les lectures et une autre pour les écritures, appelées respectivement la ''file de µops LOAD'' et la ''file de µops STORE''. Il est alors possible d'émettre une lecture et une écriture lors du même cycle d'horloge. Le port de lecture alimente une unité de calcul d'adresse dédiée, directement reliée au cache. Le port d'écriture du cache alimente une unité de calcul, qui est suivie par une file d'écriture, elle-même reliée au cache. Les processeurs AMD de micro-architecture K6 utilisaient cette technique. [[File:Double émission avec le Load Ordering, Store Ordering.jpg|centre|vignette|upright=2.5|Double émission avec le Load Ordering, Store Ordering]] ==Les CPU superscalaire à exécution dans le désordre== La superscalarité modifie aussi les circuits dédiés à l'exécution dans le désordre. Par contre, elle modifie les unités de renommage de registre, le ROB et les autres structures liées à l'exécution dans le désordre, au sens large. Dans cette section, nous allons voir comment les étages du pipeline dédiés à l'exécution dans le désordre sont modifiées sur un CPU superscalaire. ===L'exécution dans le désordre centralisé : la différence entre largeur de décodage et d'émission=== Plus haut, nous avons montré qu'un processeur pouvait avoir plus de ports d'émission que de ports de décodage. On dit aussi que sa ''largeur de décodage'' était supérieure à sa ''largeur d'émission''. La '''largeur de décodage''' est le nombre de µops fournies par l'unité de décodage par cycle d'horloge, la '''largeur d'émission''' est le nombre maximal de µops émises par cycle d'horloge. Et n'allez pas croire qu'un surplus de ports d'émission est du gâchis. Sur les processeurs avec exécution dans le désordre, ce surplus peut être utilisé. Mais avant toute chose, évacuons un détail. Vous avez sans doute pensé que le surplus peut être exploité par les instructions machines décodées en plusieurs µops . Par exemple, si une instruction est décodée en deux µops, on pourrait avoir un port d'émission en plus pour en profiter. Mais les ports de décodage sont *en sortie du décodeur*, et contiennent des µops, pas des instructions. Le surplus de ports d'émission peut occasionnellement utiliser le surplus de ports de décodage, mais cela implique que des µops ont été mises en attente dans l'unité d'émission, pour être émises par la suite. Et cela implique l'usage d'une fenêtre d'instruction ou d'une file de µops. Le principe est que les µops sont accumulées dans la fenêtre d'instruction, en attendant leurs opérandes ou la résolution d'une dépendance. Et si l'occasion se présente, une µops est émise sur chaque port d'émission. Il y a donc un gain en performances, mais il faut faire quelques précisions sur son origine. La largeur d'émission utilisée ne peut pas dépasser, *en moyenne*, la largeur de décodage. Et c'est intuitif : un CPU qui décode 4 µops par cycle ne peut pas émettre 6 par cycle en moyenne, sur le temps long, il n'y a pas de µops en surplus qui sortent du vide. Par exemple, sur un processeur quadruple émission, si on analyse combien d'instructions sont émises par cycle, il peut y avoir des cycles où la largeur d'émission est totalement utilisée, mais ce sera compensé par des périodes où elle est fortement sous-utilisée. Les µops sont accumulées en cas de sous-utilisation, puis consommées en cas d'utilisation complète. Les deux périodes se compensent. Et c'est là que l'on peut comprendre d'où vient le gain en performance. Les cycles où les ports d'émission sont sous-utilisés existent systématiquement, que le processeur utilise l'exécution dans le désordre ou pas. Ils sont liés à la présence de dépendances de données, aux contraintes d’appariement, etc. Elles tendent à réduire la moyenne du nombre de µops émises par cycle. Par contre, ce sont les cycles de rattrapage, où les ports en surplus sont utilisés, qui font remonter la moyenne. Le gain en performance est donc lié à ces cycles de rattrapage, où les µops accumulées sont émises dans les ports en surplus. Et ils n'existent que sur les processeurs avec une file/fenêtre d'instruction. ===Les CPU superscalaires à fenêtres d'instruction décentralisées : la largeur de ''dispatch'' et de ''schedule''=== Les processeurs précédents utilisaient une unité d'émission simple, qui pouvait utiliser l'émission dans l'ordre ou dans le désordre. Elle peut être remplacée par une fenêtre d'instruction centralisée dans que cela quoi que ce soit aux explications précédentes, idem avec une station de réservation centralisée. En soi, l'exécution dans le désordre est donc totalement compatible avec une implémentation simple de la superscalarité. Par contre, des subtilités surviennent avec les fenêtres d'instruction décentralisées. Pour rappel, sur ces processeurs, les µops sont émises en deux phases. Une première phase de ''dispatch'' qui envoie les µops vers la fenêtre d'instruction adéquate, sans détecter les dépendances de données. La seconde phase de ''schedule'' gère les dépendances de données, elle met en attente les µops en attendant que l'unité soit disponible pour les exécuter, en attendant aussi que les opérandes soient disponibles. Et à ce petit jeu, il y a une différence entre le nombre de µops qui sont ''dispatch par cycle'' et celles qui sont ''schedule''. Dit autrement, la largeur d'émission est scindée en une '''largeur de ''dispatch''''' et une '''largeur de ''schedule''. Par exemple, prenons un processeur avec deux stations de réservation : une pour les µops entières/mémoire, et une autre pour les opérations flottantes. L'unité d'émission fait de la triple émission, ce qui fait qu'elle peut ''dispatch'' maximum trois µops par cycle : soit trois µops entières/mémoire, soit trois flottantes, soit un mix de trois µops différentes. Les stations de réservation, quant à elle, peuvent chacune ''schedule'' 3 µops par cycle, vers trois ALUs entières pour la station de réservation entière/mémoire, vers trois FPS pour la station de réservation flottante. En tout, cela permet de ''dispatch'' trois µops, mais d'en ''schedule'' six, à chaque cycle. Et cet exemple ne sort pas de nulle aprt : tous les CPU AMD l'ont utilisé à partir de la microarchitecture K8, pour que cela change avec la microarchitecture Bulldozer. Un détail est que le nombre de ports de lecture n'est pas le même selon que le processeur utilisé des stations de réservation ou une fenêtre d’instruction. Rappelons la terminologie utilisée dans ce cours. Les fenêtres d'instruction se contentent de mémoriser la micro-opération à émettre et quelques bits pour la disponibilité des opérandes. Par contre, les stations de réservations mémorisent aussi les opérandes des instructions. Et cette différence a une influence le nombre de ports de lecture du banc de registre. Supposons que toutes les instructions sont dyadiques, à deux opérandes. Comptons le nombre de ports nécessaires pour alimenter les unités de calcul, sans utiliser d'arbitre de banc de registre. Avec une fenêtre d'instruction, il faut le double du nombre de ports de ''dispatch''. Mais avec des stations de réservation, il faut le double des ports de ''schedule''. La raison est que les registres sont lus après émission avec une fenêtre d'instruction, avant avec des stations de réservation (sous-entendu, après le décodage). or, comme dit plus haut, les processeurs superscalaires larges ont souvent plus de ports de ''schedule'' que de ''dispatch''. ===L'unité de renommage superscalaire=== Avec la superscalarité, l'unité de renommage de registre doit gérer le cas où des instructions consécutives ont des dépendances de registre. Par exemple, prenons un processeur à double émission, qui renomme deux instructions consécutives. Si elles ont une dépendance de registre, le renommage de la seconde instruction dépend du renommage de la première. La première doit être renommée et le résultat du renommage est utilisé pour renommer la seconde, ce qui empêche d'utiliser deux unités de renommage séparées. Pour cela, elle renomme les registres sans tenir compte des dépendances, pour ensuite corriger le résultat. [[File:Unité de renommage superscalaire.png|centre|vignette|upright=2|Unité de renommage superscalaire.]] Seules les dépendances lecture-après-écriture doivent être détectées, les autres étant supprimées par le renommage de registres. Repérer ce genre de dépendances se fait assez simplement : il suffit de regarder si un registre de destination d'une instruction est un opérande d'une instruction suivante. [[File:Détection des dépendances sur un processeur superscalaire.png|centre|vignette|upright=2|Détection des dépendances sur un processeur superscalaire.]] Ensuite, il faut corriger le résultat du renommage en fonction des dépendances. Si une instruction n'a pas de dépendance avec une autre, on la laisse telle quelle. Dans le cas contraire, un registre opérande sera identique avec le registre de destination d'une instruction précédente. Dans ce cas, le registre opérande n'est pas le bon après renommage : on doit le remplacer par le registre de destination de l'instruction avec laquelle il y a dépendance. Cela se fait simplement en utilisant un multiplexeur dont les entrées sont reliées à l'unité de détection des dépendances. On doit faire ce replacement pour chaque registre opérande. [[File:Correction des dépendances sur un processeur superscalaire.png|centre|vignette|upright=2|Correction des dépendances sur un processeur superscalaire.]] ===Le tampon de réordonnancement et le banc de registre d'un CPU superscalaire=== Le ROB doit aussi être modifié pour émettre et terminer plusieurs instructions en même temps, en ajoutant des ports de lecture/écriture. Pour un CPU superscalaire peut émettre 8 µops à la fois, on se dit que le ROB peut retirer 8 µops par cycle maximum. Et c'est très souvent le cas en pratique. Cependant, certains processeurs ont un ROB qui peut retirer le double ! Un exemple est celui des CPU de micro-architecture Skymont, qui peuvent émettre 8 µops par cycle, mais en retirer 16. Quelques CPU de marque AMD étaient aussi dans ce cas. L'avantage est que cela permet de réduire la taille du ROB, du banc de registre et de quelques autres structures matérielles liées à l'exécution dans le désordre. Le gain sur-compense le cout en transistor nécessaire pour retirer 16 µops par cycle. Réduire le nombre de ports d'émission permet de garder un banc de registre, des fenêtres d'instruction et un ROB simples, rapides et économes en énergie. Et mine de rien, c'est là un compromis important : à quoi bon augmenter le nombre de ports d'émission si c'est pour que ce soit compensé par une fréquence plus faible ? Par contre, réduire le nombre de ports augmente la ''port contention'', surtout si on a mal réparti les ALU. Il faut donc trouver un compromis entre réduire le nombre de ports d'émission d'un côté, réduire la ''port contention'' de l'autre. ==Les optimisations des processeurs superscalaires larges== Les processeurs superscalaires larges ont des optimisations spécifiques, qui n'ont pas de sens sur les processeurs superscalaires étroits. Dans les grandes lignes, elles se classent en deux types. Les premières permettent de charger/décoder plus de 5-10 instructions simultanément. Le problème est alors que les branchements viennent mettre leur grain de sel et qu'il faut en tenir compte. Les secondes corrigent des problèmes qui apparaissent quand un CPU intègre plus de 5 à 10 unités de calcul. ===L'étape de chargement d'un CPU superscalaire large=== Charger plusieurs instructions pose de nombreux problèmes sur les processeurs superscalaires larges, qui émettent de 5 à 10 instructions simultanées. Pour rappel, la stratégie basique pour charger N instructions est de charger un bloc de N instructions consécutives. Les instructions après un branchement ne sont pas décodées ni exécutées, ce qui entraine une petite baisse de performance. Pour un N petit, la perte de performance est mineure. Mais pour un N large, elle devient suffisamment importante pour que la conception du CPU doit alors être repensée de fond en comble. Les analyses statistiques disent qu'il y a environ un branchement tous les 4 à 6 instructions. Un CPU a donc tendance à décoder des paquets de 3 à 5 instructions consécutives, avant qu'un branchement casse la dynamique. Or, cela pose des problèmes pour utiliser plusieurs décodeurs. L'implémentation basique, qui charge N instructions consécutives pour alimenter N décodeurs, fonctionne donc jusqu'à 4/6 décodeurs. Au-delà de 5 instructions, un CPU superscalaire doit être adapté pour gérer les branchements dans les paquets d'instruction qu'il émet. Le nombre de 5 instructions ne sort donc pas de nulle part. La première solution utilise un cache de µops, avec un ''front-end'' capable de charger/décoder 4/5 instructions à la fois, pas plus. L'idée est que la majorité des instructions proviennent du cache de µops (plus de 80%), ce qui fait qu'on peut émettre environ 10 instructions à partir du cache de µops, pas besoin d'avoir beaucoup de décodeurs. Les performances sont cependant dégradées lors d'un défaut de cache de µops. Les instructions doivent alors être chargées depuis le cache d'instruction, par paquets de 4-5, puis sont décodées par 4-5 décodeurs, ce qui ne permet pas d’alimenter les unités de calcul à plein régime. La solution précédente joue sur la distinction entre pots de décodage et d'émission. Elle permet d'émettre un grand nombre de µops sans pour autant charger un grand nombre d'instructions en même temps. En quelque sorte, ce n'est qu'une méthode de contournement, le CPU superscalaire reste de type étroit. Mais il est aussi possible d'utiliser un '''''front-end large''''', capable de charger une bonne dizaine d'instructions en même temps. La seule difficulté est de tenir compte de la présence de branchements. Pour rappel, avec un ''front-end'' étroit, les instructions situées après le premier branchement pris sont annulées, elles ne sont pas décodées ni exécutées. [[File:Fetch sur un processeur superscalaire avec prediction de branchements.png|centre|vignette|upright=2|Fetch sur un processeur superscalaire avec prédiction de branchements.]] Un ''front-end'' large charge les instructions de destination du branchement et les placent à sa suite. Ils chargent deux blocs à la fois et les fusionnent en un seul qui ne contient que les instructions présumées utiles. [[File:Cache d'instructions autoaligné.png|centre|vignette|upright=2|Cache d'instructions auto-aligné.]] Mais cela demande de charger deux blocs de mémoire en une fois, ce qui demande un cache d'instruction multiports. Il faut aussi ajouter un circuit pour assembler plusieurs morceaux de blocs en un seul : le fusionneur (''merger''). En pratique, cette solution demande d'ajouter un second port de lecture au cache, pour un cout en hardware important, avec un impact négatif sur le temps d'accès au cache. Et il faut rajouter le fusionneur, qui prend un étage de pipeline à lui tout seul. Et encore, cette solution ne gère pas le cas où un bloc contient plusieurs branchements ! [[File:Implémentation d'un cache d'instructions autoaligné.png|centre|vignette|upright=2|Implémentation d'un cache d'instructions auto-aligné.]] Les exemples précédents partent du principe qu'il n'y a qu'un seul branchement dans un bloc. Mais quand les blocs deviennent assez longs, il est fréquent d'avoir plusieurs branchements dans un bloc. Et il est même possible d'avoir plusieurs branchements pris. Dans un bloc de 8-10 instructions, ce n'est pas rare, et les CPU superscalaires à 8-10 voies sont la règle dans les CPU des années 2020. Pour gérer ce cas, il faut ajouter des ports supplémentaires au cache d'instruction et adapter le fusionneur, pour qu'il puisse fusionner trois ou quatre blocs. De plus, il faut utiliser une unité de prédiction de branchement capable de ''prédire plusieurs branchements par cycle''. ===Le décodage superscalaire large=== Le décodage des instructions n'est pas censé dépendre de l'implémentation du chargement. En théorie, une fois plusieurs blocs d'instruction fusionnés, on peut décoder les instructions en parallèle. Cependant, certains processeurs effectuent le décodage en tenant compte de la manière dont les branchements découpent les blocs. Les processeurs Atom d'Intel utilisent une solution de ce type, qui couple prédiction de branchement et décodage parallèle. Ils chargent/décodent des paquets de 3 instructions consécutives, chaque paquet étant séparé du précédent par un branchement. Pour cela, en plus d'avoir un fusionneur adapté, ils regroupent les décodeurs en ''clusters'', chacun regroupant 3 décodeurs, et pouvant décoder trois instructions consécutives. Les micro-architectures Tremont et Crestmont avaient deux ''clusters'' (6 décodeurs au total), la micro-architecture Skymont en intègre trois (9 décodeurs au total). Avec deux ''clusters'', le fonctionnement est le suivant. Si le code est composé d'une suite d'instruction sans branchements, seul le premier ''cluster'' est utilisé, limitant le décodage à trois instructions par cycle. Par contre, si l'une des trois instructions décodée est un branchement, alors le second ''cluster'' décode les trois instructions situées après ce branchement, à partir de l'adresse de destination. Avec un troisième ''cluster'', c'est la même chose, sauf qu'il faut qu'il y ait un branchement dans les trois instructions décodées par le second ''cluster''. Mais comment faire pour savoir quels décodeurs activés, quels ''clusters'' sont actifs ? C'est la prédiction de branchement qui commande. Avec deux ''clusters'', elle détermine si un branchement est présent dans les trois instructions à charger, et quelle est son adresse de destination. Sans branchement, elle prévient les décodeurs et les trois instructions chargées sont décodées dans un ''cluster'' libre. Si elle prédit un branchement, elle charge les instructions adéquates, et active les deux ''clusters''. Avec trois branchements, c'est le même mécanisme, sauf que l'unité de prédiction de branchement doit être capable de prédire deux branchements par cycle. Il faut noter que les décodeurs d'un ''cluster'' sont des décodeurs simples, sans microcode. Sur les processeurs Tremont et Crestmont, le microcode était unique et était partagé entre tous les ''clusters''. Un seul ''cluster'' pouvait décoder une instruction complexe avec le microcode, l'autre devait se limiter à des instructions simples uniquement. Depuis Skymont, le microcode est partiellement dupliqué, avec un microcode par ''cluster''. Le cout en hardware peut paraitre prohibitif, pour un gain en performance faible. Sauf que c'est oublier que ce sont des processeurs Atom, sur lesquels beaucoup d'instructions sont microcodées. Les processeurs Atom ont un budget en transistor limité, ce qui fait qu'il est préférable pour eux d'implémenter des instructions en microcode que d'utiliser des unités de calcul dédiées ou des circuits de décodage complexes. Pour limiter la casse, le microcode n'est que partiellement dupliqué. Seul le microcode pour les instructions fréquente est dupliqué. Il y a un microcode secondaire pour certaines instructions, qui est lui unique et partagé par tous les ''clusters''. ===Le contournement sur les processeurs superscalaires larges=== Pour rappel, la technique du contournement (''register bypass'') permet au résultat d'une instruction d'être immédiatement utilisable en sortie de l'ALU, avant même d'être enregistré dans les registres. Implémenter la technique du contournement demande d'utiliser des multiplexeurs pour relier la sortie de l'unité de calcul sur son entrée si besoin. Il faut aussi des comparateurs pour détecter des dépendances de données. [[File:Pipeline Bypass.png|centre|vignette|upright=1|Pipeline Bypass]] La sortie de chaque ALU doit être reliée aux entrées de toutes les autres, avec les comparateurs qui vont avec. Sur les processeurs ayant plusieurs d'unités de calculs, cela demande beaucoup d’interconnexions et de comparateurs. Pour N unités de calcul, cela demande 2 * N² interconnexions, implémentées avec 2N multiplexeurs de N entrées chacun. Si c'est faisable pour 2 ou 3 ALUs, la solution est impraticable au-delà. Non seulement ce serait un enfer à câbler, mais les fils seraient tellement longs que la fréquence du processeur en prendrait un coup. Notez que cela vaut pour les processeurs superscalaires, mais aussi pour tout processeur avec beaucoup d'unités de calcul. En théorie, un CPU à exécution dans le désordre, non-superscalaire, mais avec beaucoup d'unités de calcul, fait face au même problème. En pratique, avoir une dizaine d'ALU implique processeur superscalaire à exécution dans le désordre. D'où le fait qu'on parle du problème maintenant. La seule solution praticable est de ne pas relier toutes les unités de calcul ensemble. À la place, on préfère regrouper les unités de calcul dans différents '''agglomérats''' ('''cluster'''). Le contournement est alors possible entre les unités d'un même agglomérat, mais pas entre agglomérats différents. Évidemment, certaines possibilités de contournement sont perdues, mais c'est compensé par un gain en fréquence et un cout en circuit/fils réduit. C'est un bon compromis, ce qui explique que presque tous les processeurs modernes l'utilisent. Les rares exceptions sont les processeurs POWER 4 et POWER 5, qui ont préféré se passer de contournement pour garder un processeur très simple et une fréquence élevée. ===Le banc de registre des processeurs superscalaires larges=== Pour émettre deux opérations entières simultanément, il faut lire deux fois plus d'opérandes. Et il faut aussi écrire deux résultats dans les registres, en même temps. Pour cela, on double les ports du banc de registre. En général, il faut au maximum 2 ports de lecture et un port d'écriture par unité de calcul. Le problème, c'est que plus un banc de registres a de ports, plus il utilise de circuits, est compliqué à concevoir, consomme de courant et chauffe. Avec plusieurs dizaines d'unités de calcul différentes, ajouter autant de ports impacterait la fréquence du banc de registres et réduirait les performances, voire est totalement impraticable à cause des contraintes thermiques. Les processeurs superscalaires larges doivent donc trouver des solutions. La première solution utilise un banc de registre unique, mais n'utilise pas autant de ports que le pire des cas le demanderait. Le processeur détecte quand il n'y a pas assez de ports pour servir toutes les micro-opérations. Quand ça arrive, l'unité d'émission émet assez de micro-opérations pour exploiter les ports disponibles, mais met en attente les micro-opérations en trop, le temps que les ports se libèrent. Pour cela, le banc de registre est géré par un circuit d'arbitrage spécialisé, intégré à l'unité d'émission, l’'''arbitre du banc de registres''' (''register file arbiter''). Une autre solution est de dupliquer le banc de registres en plusieurs exemplaires qui contiennent exactement les mêmes données, mais avec moins de ports de lecture/écriture. Un exemple est celui des processeurs POWER, Alpha 21264 et Alpha 21464. Sur ces processeurs, le banc de registre est dupliqué en plusieurs exemplaires, qui contiennent exactement les mêmes données. Les écritures dans les registres sont envoyées à tous les bancs de registres, afin de garantir que leur contenu est identique. Chaque exemplaire a exactement deux ports de lecture. La conception du processeur est simplifiée, que ce soit au niveau du câblage, que de la conception des bancs de registres. Une dernière solution découpe le banc de registres en plusieurs bancs de registres plus petits, avec un réseau d'interconnexions pour échanger des données entre bancs de registres. Dans la plupart des cas, cette séparation est invisible du point de vue de l'assembleur et du langage machine. Le processeur se charge de transférer les données entre bancs de registres suivant les besoins. Sur d'autres processeurs, les transferts de données se font via une instruction spéciale, souvent appelée ''COPY''. [[File:Banc de registres distribué.png|centre|vignette|upright=2|Banc de registres distribué.]] Sur de certains processeurs, un branchement est exécuté par une unité de calcul spécialisée. Or les registres à lire pour déterminer l'adresse de destination du branchement ne sont pas forcément dans le même agglomérat que cette unité de calcul. Pour éviter cela, certains processeurs disposent d'une unité de calcul des branchements dans chaque agglomérat. Dans les cas où plusieurs unités veulent modifier le ''program counter'' en même temps, un système de contrôle général décide quelle unité a la priorité sur les autres. Mais d'autres processeurs fonctionnent autrement : seul un agglomérat possède une unité de branchement, qui peut recevoir des résultats de tests de toutes les autres unités de calcul, quel que soit l’agglomérat. ==La macro-fusion : une optimisation du décodage parallèle== Les processeurs superscalaires supportent la technique dite de '''macro-fusion''', qui permet de fusionner deux-trois instructions consécutives en une seule micro-opération. Par exemple, il est possible fusionner une instruction de test et une instruction de saut en une seule micro-opération de branchement. Il s'agit là de l'utilisation la plus importante de la macro-fusion sur les processeurs x86 modernes. La macro-fusion est utilisée pour fusionner un test et un saut en un branchement unique. L'optimisation est présente sur la plupart des CPU superscalaires modernes. Les CPU x86 l'implémentent, mais aussi quelques CPU ARM et les processeurs Apple de série M (M1 à M5, et potentiellement plus quand ils seront sortis). La fusion des instructions se fait lors du décodage des instructions, grâce à la coopération des décodeurs. Elle marche systématiquement si l'instruction de test et le saut sont consécutives, qu'elles forment bien une paire d'instruction. Par contre, insérer une instruction entre les deux empêche la macro-fusion de faire son travail, sauf si l'instruction insérée est un NOP. Les processeurs ARM64 avaient une forme spécifique de macro-fusion, utilisée pour un cas très précis : charger une constante immédiate de 32 ou 64 bits, dans un registre 64 bits. Les CPU ARM 64 bits utilisent des instructions codées sur 32 bits, qui ne peuvent encoder que des constantes immédiates de 16 bits. Pour charger une constante de 32/64 bits, il faut s'y prendre en plusieurs fois. Une première instruction MOVZ charge les 16 premiers bits, puis 2 à 4 instructions MOVK remplissent le reste. MOVZ met à zéro les 48 bits de poids fort, MOVK permet de sélectionner le doublet à modifier dans le registre (on modifie 16 bits, dont on choisit la place, le reste du registre est laissé tel quel). ARM permet de fusionner cette suite d'instructions en une seule µop MOV avec une constante immédiate. On parle alors de '''''MOVZ + MOVK fusion'''''. Une dernière utilisation, [https://www2.eecs.berkeley.edu/Pubs/TechRpts/2016/EECS-2016-130.pdf proposées pour le jeu d'instruction RISC-V], fusionnait un calcul d'adresse et une instruction LOAD/STORE en une seule instruction d'accès mémoire. Elle a été envisagée à une époque où le jeu d'instruction RISC-V ne gérait pas les modes d'adressage base + indice. L'idée était d'avoir une unité mémoire qui gérait le mode d'adressage base + indice et de fusionner des instructions pour l'exploiter. Mais de nos jours, le jeu d'instruction RISC-V intègre le mode d'adressage base + indice, ce qui fait que la macro-fusion n'a aucun intérêt pour ce cas précis. La macro-fusion a de nombreuses limitations en pratique. En théorie, on pourrait l'utiliser au-delà des deux situations mentionnées plus haut. Par exemple, pour fusionner une addition et une multiplication en une seule instruction MAD. Mais l'intérêt est assez faible. Notons que la macro-fusion ne permet que de fusionner des paires d'instructions. Aller au-delà demanderait un cout en circuit trop important. Déjà que tester deux paires utilise pas mal de comparateurs... ==Le ''stack engine'' : une optimisation de la pile d'appel== Les processeurs modernes intègrent une optimisation liée au pointeur de pile. Pour rappel, sur les architectures modernes, le pointeur de pile est un registre utilisé pour gérer la pile d'appel, précisément pour savoir où se trouve le sommet de la pile. Il est manipulé par les instructions CALL, RET, PUSH et POP, mais aussi par les instructions d'addition/soustraction pour gérer des cadres de pile de taille variable. De plus, il peut servir d'opérande pour des instructions LOAD/STORE, afin de lire/écrire des variables locales, les arguments d'une fonction, et autres. C'est donc un registre général adressable, intégré au banc de registre, altéré par l'unité de calcul entière. L'incrémentation/décrémentation du pointeur de pile passe donc par l'unité de calcul, lors des instructions CALL, RET, PUSH et POP. Mais, l'optimisation que nous allons voir permet d'incrémenter/décrémenter le pointeur de pile sans passer par l'ALU, ou presque. L'idée est de s'inspirer des architectures avec une pile d'adresse de retour, qui intègrent le pointeur de pile dans le séquenceur et l'incrémentent avec un incrémenteur dédié. ===Le ''stack engine''=== L'optimisation que nous allons voir utilise un '''''stack engine''''' intégré à l'unité de contrôle, au séquenceur. Le processeur contient toujours un pointeur de pile dans le banc de registre, cela ne change pas. Par contre, il n'est pas incrémenté/décrémenté à chaque instruction CALL, RET, PUSH, POP. Un compteur intégré au séquenceur est incrémenté à la place, nous l’appellerons le '''compteur delta'''. La vraie valeur du pointeur de pile s'obtient en additionnant le compteur delta avec le registre dans le banc de registre. Précisons que si le compteur delta vaut zéro, la vraie valeur est dans le banc de registre et peut s'utiliser telle quelle. Lorsqu'une instruction ADD/SUB/LOAD/STORE utilise le pointeur de pile comme opérande, elle a besoin de la vraie valeur. Si elle n'est pas dans le banc de registre, le séquenceur déclenche l'addition compteur-registre pour calculer la vraie valeur. Finalement, le banc de registre contient alors la bonne valeur et l'instruction peut s'exécuter sans encombre. L'idée est que le pointeur de pile est généralement altéré par une série d'instruction PUSH/POP consécutives, puis des instructions LOAD/STORE/ADD/SUB utilisent le pointeur de pile final comme opérande. En clair, une bonne partie des incrémentations/décrémentation est accumulée dans le compteur delta, puis la vraie valeur est calculée une fois pour toutes et est utilisée comme opérande. On accumule un delta dans le compteur delta, et ce compteur delta est additionné quand nécessaire. Précisons que le compteur delta est placé juste après le décodeur d'instruction, avant même le cache de micro-opération, l'unité de renommage et l'unité d'émission. Ainsi, les incrémentations/décrémentations du pointeur de pile disparaissent dès l'unité de décodage. Elles ne prennent pas de place dans le cache de micro-opération, ni dans la fenêtre d'instruction, ni dans la suite du pipeline. De nombreuses ressources sont économisées dans le ''front-end''. Mais utiliser un ''stack engine'' a aussi de nombreux avantages au niveau du chemin de données/''back-end''. Déjà, sur les processeurs à exécution dans le désordre, cela libère une unité de calcul qui peut être utilisée pour faire d'autres calculs. Ensuite, le compteur delta mémorise un delta assez court, de 8 bits sur le processeur Pentium M, un peu plus pour les suivants. L'incrémentation se fait donc via un incrémenteur 8 bits, pas une grosse ALU 32/64 bits. Il y a un gain en termes de consommation d'énergie, un incrémenteur 8 bits étant moins gourmand qu'une grosse ALU 32/64 bits. ===Les points de synchronisation du delta=== La technique ne fonctionne que si la vraie valeur du pointeur de pile est calculée au bon moment, avant chaque utilisation pertinente. Il y a donc des '''points de synchronisation''' qui forcent le calcul de la vraie valeur. Plus haut, nous avions dit que c'était à chaque fois qu'une instruction adresse le pointeur de pile explicitement, qui l'utilise comme opérande. Les instructions CALL, RET, PUSH et POP ne sont pas concernées par elles utilisent le pointeur de pile de manière implicite et ne font que l'incrémenter/décrémenter. Mais dans les faits, c'est plus compliqué. D'autres situations peuvent forcer une synchronisation, notamment un débordement du compteur delta. Le compteur delta est généralement un compteur de 8 bits, ce qui fait qu'il peut déborder. En cas de débordement du compteur, le séquenceur déclenche le calcul de la vraie valeur, puis réinitialise le compteur delta. La vraie valeur est donc calculée en avance dans ce cas précis. Précisons qu'un compteur delta de 8 bits permet de gérer environ 30 instructions PUSH/POP consécutives, ce qui rend les débordements de compteur delta assez peu fréquent. À noter que si le compteur delta vaut zéro, il n'y a pas besoin de calculer la vraie valeur, le séquenceur prend cette situation en compte. Un autre point de synchronisation est celui des interruptions et exceptions matérielles. Il faut que le compteur delta soit sauvegardé lors d'une interruption et restauré quand elle se termine. Idem lors d'une commutation de contexte, quand on passe d'un programme à un autre. Pour cela, le processeur peut déclencher le calcul de la vraie valeur lors d'une interruption, avant de sauvegarder les registres. Pour cela, le processeur intègre un mécanisme de sauvegarde automatique, qui mémorise la valeur de ce compteur dans le tampon de réordonnancement, pour forcer le calcul de la vraie valeur en cas de problème. La technique du ''stack engine'' est apparue sur les processeurs Pentium M d'Intel et les processeurs K10 d'AMD, et a été conservée sur tous les modèles ultérieurs. L'implémentation est cependant différente selon les processeurs, bien qu'on n'en connaisse pas les détails et que l'on doive se contenter des résultats de micro-benchmarks et des détails fournit par Intel et AMD. Selon certaines sources, dont les manuels d'optimisation d'Agner Fog, les processeurs AMD auraient moins de points de synchronisation que les processeurs Intel. De plus, leur ''stack engine'' serait placé plus loin que prévu dans le pipeline, après la file de micro-opération. ==La micro-fusion et la délamination== La '''micro-fusion''' est une optimisation qui retarde le décodage réel de certaines instructions assez loin dans le pipeline. Elle concerne surtout les instructions ''load-op'', mais pas que. D'autres instructions mémoire peuvent subir cette optimisation. L'essentiel est que, sans micro-fusion, ces instructions sont décodées en plusieurs micro-opérations. La micro-fusion retarde le décodage final de ces instructions, qui se fait assez tard dans le pipeline. Pour comprendre l'intérêt, nous allons devoir faire quelques explications. L'optimisation en question fait le décodage en deux temps. Prenons l'exemple d'une opération ''load-op'', qui est censée être décodée en deux micro-opérations : une lecture, puis une opération. Le décodage produit une '''macro-opération''' (terme d'Intel), qui est propagée dans le pipeline, pour être scindée en deux micro-opérations un peu avant les ALU et l'unité mémoire. La macro-opération est techniquement une micro-opération comme une autre, sauf que ce n'est pas la micro-opération finale. L'unité d'émission découpe cette macro-opération en deux micro-opérations, qui sont émises l'une après l'autre. Pour résumer, les instructions ''load-op'' ne sont complétement découpées en micro-opération lors de l'émission, pas lors du décodage. L'optimisation impacte ce qui se situe entre le décodeur d'instruction et l'unité d'émission : l'unité de renommage, la file de micro-opérations, le ''Loop Stream Detector'' et/ou le cache de micro-opération. L'intérêt de l'optimisation est que l'on fusionne deux micro-opérations en une seule macro-opération. Le cache de micro-opération et la file de micro-opération peuvent mémoriser plus de micro-opérations, vu que certaines sont fusionnées. Quant à l'unité de renommage, elle renomme une seule macro-opération au lieu de renommer deux micro-opérations, ce qui augmente le débit du renommage de registre. Par contre, cela complique un peu le travail de l'unité d'émission, qui doit découper les instructions ''load-op'' en deux et émettre les micro-opérations l'une après l'autre. Il faut noter qu'il n'y a pas de fusion proprement dite. Le décodeur ne décode pas deux micro-opérations séparées, avant de les fusionner. En réalité, il génère directement une macro-opération, qui correspond à deux micro-opérations, que ce soit plus tard dans le pipeline ou sur une autre architecture. D'ailleurs, la micro-fusion a lieu à l'intérieur d'une instruction, qui est censée être traduite en plusieurs micro-opérations. Et encore : pas toutes. La conséquence est que la technique n'a de sens que sur les processeurs CISC, avec des instructions assez complexes pour être décodées en plusieurs micro-opérations. En pratique, seuls les CPU x86 sont concernés. ===Les domaines de fusion=== La micro-fusion n'est pas spécifique aux processeurs superscalaires. En théorie, on peut l'implémenter sur un processeur non-superscalaire à émission dans l'ordre. Cependant, je ne connais aucun processeur à émission dans l'ordre qui implémente la micro-fusion, même si son implémentation est en théorie possible sur de tels processeurs. La micro-fusion est implémentée différemment selon que l'émission se fasse dans l'ordre, dans le désordre, avec une fenêtre d'instruction, avec des stations de réservation, etc. J'ai dit plus haut que les micro-opérations sont générées lors de l'émission, mais le moment exact varie suivant le processeur. Pour simplifier le tout, nous parlerons de '''scission''' quand le processeur traduit une macro-opération en deux/trois micro-opérations. Le moment où a lieu la scission est théoriquement dans l'unité d'émission, mais celle-ci prend plusieurs formes selon que le processeur utilise l'émission dans l'ordre, dans le désordre, avec une fenêtre d'instruction, avec des stations de réservation, etc. Les processeurs à exécution dans l'ordre sont le cas le plus simple. L'unité d'émission est alors en charge du découpage des macro-opérations en micro-opérations. La file de micro-opération ou le cache de micro-opérations mémorisent des macro-opérations. À l'opposé, le tampon de ré-ordonnancement mémorise lui des micro-opérations. Le reste du processeur n'est pas affecté. Sur les processeurs à exécution dans le désordre, les différences sont nombreuses. Le tampon de ré-ordonnancement peut mémoriser soit les micro-opérations finales, soit des macro-opérations. Il en est de même avec la fenêtre d'instruction et/ou les stations de réservation, qu'elles soient centralisées ou décentralisées. Tout dépend de comment se fait l'implémentation. Une implémentation simple effectue la scission des macro-opérations dans l'unité d'émission, comme sur les processeurs à exécution dans l'ordre. Sur de nombreux processeurs, l'unité d'émission et de renommage sont simplement fusionnées, ce qui fait que la scission a lieu juste après le renommage de registres. En conséquence, le tampon de re-ordonnancement et les fenêtres d'instruction ne voient que des micro-opérations. Les macro-opérations disparaissent juste avant. Cette implémentation est préférée avec une fenêtre d'instruction centralisée, car on voit mal comment scinder une macro-opération juste avant l'ALU. Une autre implémentation fait la scission en sortie de la fenêtre d'instruction ou des stations de réservation, qu'elles soient centralisées ou décentralisées. Dans ce cas, le tampon de re-ordonnancement fait de même. L'avantage est que le tampon de ré-ordonnancement stocke des macro-opérations, ce qui augmente sa taille effective. Par exemple, il utilise une seule entrée pour une instruction ''load-op'', au lieu de deux sans micro-fusion de ce type. ===La délamination=== La scission peut parfois avoir lieu plus tôt, dans des circonstances très précises. Concrètement, cela ne concerne que quelques micro-architectures d'Intel, pour des instructions très précises. La raison est les limitations du format des micro-opérations, pas une histoire d'optimisation. Le processeur aimerait pouvoir faire la micro-fusion, mais ne peut pas à cause de ces limitations. Une telle situation cause alors une '''délamination''' : les micro-opérations sont scindées précocement. Par exemple, sur la micro-architecture SandyBridge d'Intel, les opérations ''load-op'' sont fusionnées, sauf si elles utilisent l'adressage indicé. Les micro-opérations de ce processeur ne peuvent avoir que deux opérandes. Trois opérandes est de trop, et les opérations ''load-op'' en adressage indicé dépassent cette limite. Dans ce cas, les micro-opérations sont fusionnées en sortie du décodeur, mais sont scindés avant d’entrer dans la file de micro-opération. L'avantage n'est pas évident, vu que l'unité de renommage suit de près le décodeur d'instruction. Il est que le cache de micro-opérations mémorise des macro-opérations, pas deux micro-opérations. ===La micro-fusion des processeurs Intel et AMD=== La micro-fusion est apparue sur le Pentium M d'Intel, et a été conservée telle quelle sur les processeurs Intel suivants. Les processeurs AMD supportent la micro-fusion depuis au moins la micro-architecture AMD K8. Mieux que ça, la délamination n'était pas utilisée car les micro-opérations acceptaient plus de 2 opérandes. Que ce soit chez AMD ou Intel, elle concerne deux types d'instructions : les écritures mémoire et les instructions ''load-op''. Sur le Pentium M, il y avait quelques limitations, l'optimisation se limitant par exemple aux registres entiers et flottants, mais pas aux registres MMX, mais pas les registres SSE (nous verrons ces registres dans quelques chapitres). Mais les processeurs modernes n'ont plus cette limitation. La micro-fusion des CPU Intel concerne deux types d'instructions : les écritures mémoire et les instructions ''load-op''. Pour les écritures, la situation est plus complexe que pour les instructions ''load-op''. Sur les processeurs Intel précédents, les écritures étaient réalisées dans deux unités séparées : le calcul d'adresse dans une unité de calcul, l'écriture proprement dite dans l'unité mémoire. Elles prenaient donc deux micro-opérations. Le processeur a changé ses unités mémoire, ce qui fait qu'elle pouvait faire les deux en une seule micro-opération. Il s'agissait alors d'une optimisation du chemin de données, plus que de la micro-fusion proprement dite, mais je la mentionne ici malgré tout. La micro-fusion sur ce processeur avait un avantage en plus des avantages usuels : elle utilisait mieux les décodeurs. Les Pentium M avaient trois décodeurs pour décoder plusieurs instructions en même temps, chose qu'on détaillera dans le chapitre sur les processeurs superscalaires. Un seul décodeur pouvait décoder toutes les instructions, les deux autres ne peuvent décoder que les instructions simples, qui se décodent en une seule micro-opération. Avec la micro-fusion, les trois décodeurs peuvent gérer les écritures mémoire et les opérations ''load-op'', vu qu'elles deviennent des instructions simples. Sans cela, elles auraient été limitées au premier décodeur. <noinclude> {{NavChapitre | book=Fonctionnement d'un ordinateur | prev=Le parallélisme mémoire | prevText=Le parallélisme mémoire | next=Exemples de microarchitectures CPU : le cas du x86 | nextText=Exemples de microarchitectures CPU : le cas du x86 }} </noinclude> e1a316q7lvzg2acfvmziex1qiy4utmg 773245 773244 2026-09-26T15:03:48Z Mewtow 31375 /* Les CPU superscalaires à fenêtres d'instruction décentralisées : la largeur de dispatch et de schedule */ 773245 wikitext text/x-wiki Les processeurs vus auparavant ne peuvent émettre au maximum qu'une instruction par cycle d'horloge. Ils peuvent avoir plusieurs instructions qui s'exécutent en même temps, dans des unités de calcul séparées. C'est le cas dès qu'une instruction multi-cycles s'exécute. Par contre, ils ne peuvent démarrer qu'une seule instruction par cycle. Et quand on court après la performance, ce n'est pas assez ! Les concepteurs de processeurs ont inventés des processeurs qui émettent plusieurs instructions par cycle : les '''processeurs à émissions multiples'''. Un processeur à émission multiple charge plusieurs instructions en même temps, les décode en parallèle, puis les émet en même temps sur des unités de calculs séparées. Pour cela, il faut gérer les dépendances entre instructions, répartir les instructions sur différentes unités de calcul, et cela n'est pas une mince affaire. En général, pour un processeur à émission multiple capable d'émettre N instructions en parallèle, on parle de '''processeur à N voies''', ou encore un ''processeur à N pipelines''. [[File:Superscalarpipeline.svg|centre|vignette|upright=1.5|Pipeline RISC classique à cinq étages sur un processeur superscalaire. On voit bien que plusieurs instructions sont chargées en même temps.]] Les processeurs à émission multiple sont de deux types : les processeurs VLIW et les '''processeurs superscalaires'''. Nous mettons les processeurs VLIW de côté en attendant le prochain chapitre. La raison est qu'ils ont un jeu d'instruction spécialisé qui expose le parallélisme d'instruction directement au niveau du jeu d'instructions, là où les processeurs superscalaires restent des processeurs au jeu d'instruction normal. La différence principale entre processeur VLIW et superscalaire est qu'un processeur superscalaire répartit les instructions sur les unités de calcul à l’exécution, là où un processeur VLIW délègue cette tâche au compilateur. ==Le nombre de voies d'un processeur superscalaire== Les processeurs superscalaires les plus simples ne permettent que d'émettre deux micro-opérations à la fois, d'où leur nom de processeur ''dual issue'', ce qui se traduit en '''processeur à double émission'''. Les processeurs triple émission permettent eux d’émettre trois instructions à la fois. A l'opposé, les CPU superscalaires les plus puissants peuvent émettre jusqu'à 8-10 instructions simultanément. En 2026, les CPU superscalaires peuvent monter jusqu’à 10 µops émises en même temps, ce qui est énorme ! Et cela nous amène à faire une distinction entre les processeurs superscalaires dits '''étroits''' et ceux dits '''larges'''. Dans ce wikilivre, on part du principe que les processeurs superscalaires étroits émettent jusqu'à 4-5 µops à la fois, alors que les larges peuvent en émettre plus, l'intervalle entre les deux est une zone grise. Le nombre exact n'est pas fixé dans le marbre, la distinction est volontairement vague. Nous parlerons de la '''largeur d'un CPU superscalaire''' pour parler du nombre de voies, du nombre de µops émises en même temps. Plus un CPU superscalaire est large, plus il est performant ''toute chose égale par ailleurs''. Mais dans les faits, vous pouvez oublier la partie en italique. Élargir un CPU superscalaire demande de rajouter beaucoup de circuits. Et ce cout en circuit aura un impact sur la performance. A budget en transistors limité, il y a un compromis entre élargir un CPU et rajouter du cache ou la taille des fenêtres d'instructions/ROB et autres. ===Les contraintes d’appariement=== Prenons un processeur double émission, capable d'émettre deux instructions simultanément. Émettre deux instructions en même temps n'est pas toujours possible. Par exemple, si deux instructions sont dépendantes, on ne peut pas les émettre en même temps. Mais oublions un instant ces histoires de dépendances de données, et partons du principe que le CPU ait affaire à deux instructions indépendantes, une paire idéale. Un CPU double émission "parfait" pourrait exécuter n'importe quelle paire d'instruction. Par exemple, il pourrait émettre deux multiplications consécutives, deux lectures consécutives, une lecture et une écriture en même temps, etc. En pratique, aucun processeur ne permet cela. Implémenter un CPU superscalaire "parfait" aurait un cout en circuit beaucoup trop conséquent. Par exemple, pour émettre deux multiplications en même temps, il faudrait deux multiplieurs séparés. Idem pour exécuter deux accès mémoire simultané : cela demande d'avoir deux unités mémoire et un cache multiport. Mais en pratique, aucun CPU superscalaire ne fait cela. La totalité des CPU double émission se débrouille avec un seul circuit multiplieur et une seule unité mémoire. Et cela entraine l'apparition de dépendances structurelles, qui font que certaines paires d'instructions sont impossibles. Par exemple, impossible d'émettre deux multiplications en même temps avec un seul circuit multiplieur. Mais émettre une multiplication en parallèle d'une addition est parfaitement possible. Il y a donc des paires d'instructions autorisées et des paires interdites. Et la logique est la même sur les CPU à triple ou quadruple émission, et au-delà. : des triplets ou quadruplet d'instructions sont impossibles à émettre simultanément, alors que d'autres le sont. Pour le dire autrement, il y a des '''contraintes d'appariement''' sur les instructions à émettre en même temps. ===L'impact des contraintes d’appariement sur la performance=== Ces contraintes d’appariement ont un impact sur les performance, mais celui-ci peut être grandement mitigé. Les programmes informatiques utilisent en pratique beaucoup plus d'opérations entières de base que de multiplication, branchements et accès mémoire. Il y a environ 5 opérations entières de base pour une multiplication entière. En pratique, il est rare de devoir exécuter deux multiplications à la fois. Idem pour les décalages et rotations, qui sont assez éloignés les uns des autres. Deux branchements consécutifs sont quant à eux plus fréquents. Deux accès mémoire consécutifs est assez rare, mais ils restent assez proches les uns des autres malgré tout. La conséquence est que certaines paires d'instructions sont privilégiées par rapport au reste. Les paires regroupant deux calculs basiques (additions, soustractions, ...) sont systématiquement supportées, car dupliquer des ALU entières est facile. Les paires regroupant un calcul et une autre instruction sont elles aussi systématiquement supportées. Par contre, les paires avec deux branchements, deux accès mémoire, deux décalages/rotations, ou deux multiplications/divisions sont fréquemment interdites. Seuls les CPU superscalaires larges peuvent les permettre, et encore : sous conditions. Le résultat est que le processeur peut se débrouiller avec une seule unité mémoire et une unité de branchement. ===Terminologie pour la suite du chapitre=== Décrire les contraintes d’appariement demande de dire quelles combinaisons d’instructions sont autorisées ou interdites. Et pour cela, nous allons introduire les abréviations suivantes : * INT est un raccourcis pour parler d'une opération entière simple, qu'une ALU entière peut exécuter. * MUL correspond à une opération entière complexe, typiquement une multiplication, éventuellement à une division. * FLOAT désigne une opération flottante, peut importe laquelle. * MEM désigne un accès mémoire, à savoir une instruction ou µop mémoire. * BRANCH désigne une instruction de branchement. Cette terminologie permettra de définir des paires d'instructions, qu'elles soient possibles ou interdites. Par exemple, l'expression INT + FLOAT désigne une paire d'instruction regroupant une instruction entière et une instruction flottante, peu importe l'ordre des deux instructions. Il en sera de même pour les triplets, quadruplets, ou plus. Par exemple, l'expression INT + INT + FLOAT + MUL désigne un quadruplet d'instructions regroupant : deux instructions entières, une instruction flottante, et une multiplication entière. J'utiliserais aussi des parenthèses pour regrouper certaines combinaisons. Par exemple, l'expression INT + (MUL /FLOAT) veut parler d'une paire d'instruction où la première est une opération entière basique, la seconde est : soit une multiplication, soit une opération flottante. ==Le ''front-end'' des processeurs superscalaires== Un processeur superscalaire doit charger, décoder, et exécuter plusieurs instructions par cycle. Par exemple, un processeur triple émission doit charger 3 instructions lors d'un premier cycle, puis les décoder au cycle suivant, et les exécuter au cycle suivant. Il travaille donc pas paquets de 3 instructions, dans cet exemple. Intuitivement, charger, décoder et exécuter plusieurs instructions à la fois demande de dupliquer des circuits. Par exemple, il faut dupliquer les décodeurs pour décoder plusieurs instructions. Les unités de calcul sont aussi dupliquées, sauf sur certaines processeurs qui arrivent à ruser, mais laissons cela de côté pour plus tard. Mais d'autres circuits sont simplement modifiés, comme l'unité de chargement ou l'unité d'émission. Voyons ce qu'il en est dans le détail. {|class="wikitable" |- ! rowspan="2" | Processeur sans émission multiple | rowspan="2" | Chargement | rowspan="2" | Décodage | rowspan="2" | Renommage | rowspan="2" | Émission | Exécution / ALU | rowspan="2" | ''Commit''/ROB |- | Exécution / ALU |- ! rowspan="4" | Processeur superscalaire | rowspan="4" | Chargement | rowspan="2" | Décodage | rowspan="4" | Renommage | rowspan="4" | Émission | Exécution / ALU | rowspan="4" | ''Commit''/ROB |- | Exécution / ALU |- | rowspan="2" | Décodage | Exécution / ALU |- | Exécution / ALU |} Nous allons d'abord voir ce qu'il en est pour le ''front-end'', à savoir la partie du CPU qui s'occupe de charger et décoder les instructions. Elle regroupe l'unité de chargement, les décodeurs, l'unité de prédiction de branchement, la file de µops, la file d'instruction, et divers caches. En soi, les changements portent surtout sur l'unité de chargement et les décodeurs. ===Le chargement de plusieurs instructions consécutives=== Un processeur superscalaire charge plusieurs instructions en même temps, ''en tenant compte de la présence de branchements''. La partie en italique est importante et elle a une conséquence très forte : la manière de faire est fortement différente entre un CPU superscalaire étroit et large. Un CPU superscalaire étroit fait au plus simple : il charge un bloc de 2, 3, 4 instructions consécutives en mémoire RAM. Il faut alors doubler/tripler/quadrupler la taille du bus connecté au cache d'instruction. Par exemple, pour charger quatre instructions de 32 bits chacune, il suffit de lire un bloc de 128 bits dans le cache d'instruction. Un bloc de 8, 16, 32 octets est donc lu depuis le cache et est ensuite découpé en instructions, envoyées chacun à un décodeur. Charger plusieurs instructions consécutives ne marche bien qu'en absence de branchements. La présence de branchements casse cette dynamique, et il faut gérer leur présence. * Sans prédiction de branchement, toutes les instructions d'un bloc sont décodées et exécutées en même temps, même s'il y a un branchement dans le tas. * Avec prédiction de branchement, les instructions situées après le branchement ne sont pas décodées ni exécutées. L'unité de chargement coupe le bloc chargé au niveau du premier branchement pris, remplit les vides avec des NOP, avant d'envoyer le tout à l'unité de décodage. Les instructions à l'adresse de destination seront chargées au cycle suivant, en chargeant un nouveau bloc. [[File:Fetch sur un processeur superscalaire avec prediction de branchements.png|centre|vignette|upright=2|Fetch sur un processeur superscalaire avec prédiction de branchements.]] Un autre défaut est que sur le CPU charge un bloc de 128 bits à la fois, les instructions doivent être alignées sur 128 bits. Et on peut généraliser à n'importe quelle taille de bloc. Si les instructions ne sont pas alignées, il faut gérer la situation. Il arrive qu'une instruction soit à cheval sur deux blocs, avec un morceau dans le premier bloc, un autre dans le second. Dans ce cas, l'instruction est chargée en deux fois, et des circuits doivent gérer la situation pour recoller les morceaux. Sur les CPU avec des instructions de taille fixe, le problème ne survient que rarement. Mais il est fréquent avec des instructions de taille variable. Un autre défaut est lié aux branchements. Imaginez qu'un branchement s'exécute et branche vers une instruction de destination. Rien n'indique que l'instruction de destination sera au tout début d'un bloc. Si ça se trouve, elle sera en troisième position d'un bloc de 4 instructions. Une solution simple charge le bloc en respectant l'alignement, et les deux premières instructions du bloc ne sont pas à exécuter. Il y a donc une petite perte de performance. Certains CPU, comme l'IBM RS/6000, ont la machinerie charger un bloc de N instructions même s'il n'est pas aligné. La machinerie en question se trouve dans le cache d'instruction et est tout sauf simple à expliquer,ce qui fait que je me permets de la passer sous silence. Un détail, cependant : elle ne permet pas de charger des blocs qui sont à cheval sur deux lignes de cache. ===Le décodage parallèle des instructions=== Pour le décodage des instructions, plusieurs instructions sont décodées en même temps. On parle alors de '''décodage parallèle'''. Pour ce faire, un processeur superscalaire contient plusieurs décodeurs, chacun pouvant décoder une instruction en parallèle des autres. Prenons par exemple un processeur superscalaire à 4 voies : le décodage se fera dans quatre décodeurs séparés, qui fonctionneront en parallèle. Cependant, cette implémentation ne marche bien que pour les processeurs RISC, les processeurs CISC devant faire avec un microcode gourmand en circuits. Les processeurs CISC utilisent des décodeurs hybrides, avec un microcode qui complémente un décodeur câblé. Dupliquer le microcode aurait un cout en transistors trop important, ce qui fait que seuls les décodeurs câblés sont dupliqués. Les CPU CISC superscalaires disposent donc de plusieurs décodeurs simples, capables de décoder les instructions les plus courantes, avec un seul microcode. La conséquence est qu'il n'est pas possible de décoder deux instructions microcodées en même temps. Par contre, il reste possible de décoder plusieurs instructions non-microcodées ou une instruction microcodée couplée à une instruction non-microcodée. Vu qu'il est rare que deux instructions microcodées se suivent dans un programme, le cout en performance est extrêmement mineur. Il faut noter qu'une instruction correspond à une ou plusieurs µops. Il est donc possible de charger 5 instructions et de se retrouver avec 8 µops en sortie des décodeurs. Et c'est la source d'une distinction très importante. Plus haut, j'ai parlé de processeurs superscalaires à N voies. La terminologie peut avoir deux interprétations. La première est que le processeur charge/décode N instructions à la fois. Une seconde est qu'il est capable d'exécuter/émettre N µops en même temps. Les deux ne sont pas équivalentes, vu qu'un processeur superscalaire peut, par exemple, charger/décoder N instructions et émettre/exécuter N+2 µops. La distinction est particulièrement importante sur les processeurs superscalaires larges. Certains d'entre eux sont capables de charger 5 instructions et d'en émettre le double ! Un exemple classique était les processeurs Athlon d'AMD, qui pouvaient décoder deux instructions par cycle, mais émettre 4 µops par cycle. Ce n'était pas un problème, car beaucoup d'instructions, même basiques, étaient décodées en 2 µops. ===L'émission parallèle des instructions=== La superscalarité modifie en profondeur l'unité d'émission, notamment au niveau de son interface. Avant, elle recevait une micro-opération sur son entrée, et fournissait la micro-opération émise sur une sortie. Et cela vaut aussi bien pour une unité d'émission simple, un ''scoreboard'', une fenêtre d'instruction ou des stations de réservation. Mais avec l'émission multiple, les sorties et entrées sont dupliquées. Pour la double émission, il y a deux entrées vu qu'elle doit recevoir deux micro-opération décodées/renommées, et deux sorties pour émettre deux micro-opérations. Dans ce qui suit, nous ne parlerons pas d'entrée et de sortie, mais de '''ports de décodage''' et de '''ports d'émission'''. Les ports de décodage reçoivent des micro-opérations provenant de l'unité de décodage, les ports d'émissions sont pour les micro-opérations émises. Les ports d'émission injectent les micro-opérations émises dans le pipeline, elle sont propagée au banc de registre, aux ALUs, etc. Un processeur superscalaire a forcément plusieurs ports d'émission (et de décodage). Si l'interface de l'unité d'émission change, l'intérieur est tout autant modifié. Sur un CPU non-superscalaire, l'unité d'émission reçoit une µop "candidate à l'émission", et détecte les dépendances entre avec les µops en vol, en cours d'exécution dans le pipeline. Et c'est toujours le cas sur les processeurs superscalaires : l'unité d'émission détecte les dépendances entre les µops candidates et les µops en vol. Mais avec la siperscalarité, elle doit en plus tester les dépendances entre les µops candidates. Si l'unité d'émission est un ''scoreboard'', il doit détecter les dépendances entre instructions émises simultanément et les sérialiser si besoin. Avec une fenêtre d'instruction, il n'y a pour ainsi dire rien à faire, vu que les dépendances sont éliminées par le renommage de registre et que les signaux de réveil gèrent les dépendances RAW. C'est la raison pour laquelle les processeurs superscalaires modernes n'utilisent pas de ''scoreboard''. ==Les processeurs implémentés avec plusieurs pipelines== Nous venons de voir comment la superscalarité impacte le ''front-end'', il est maintenant de voir ce qu'il en est de l'unité d'émission et des ''back-ends''. Un processeur superscalaire peut utiliser l'exécution dans le désordre et le renommage de registres, mais ce n'est pas une obligation. Historiquement, les premiers processeurs superscalaires n'utilisaient pas d'exécution dans le désordre. Le meilleur exemple est celui du Pentium 1 et 2 d'Intel. Ils étaient tous deux des processeurs superscalaires, mais n'avaient pas d'exécution dans le désordre, qui est apparue sur le Pentium 3. les explications sont plus simples si on met de côté l'exécution dans le désordre. Vous vous dites que l'implémentation d'un processeur superscalaire n'est pas la même suivant que le CPU utilise l'exécution dans le désordre, ou non. Mais dans les faits, la distinction est plus subtile que ca. Pour l'expliquer, je vais utiliser une classification originale, qui sépare les processeurs superscalaires en trois sous-types. La distinction se fait sur la manière dont la superscalarité est implémentée en matériel.La classification que j'utilise distingue : * Les processeurs avec plusieurs pipelines, sans exécution dans le désordre. * Les processeurs superscalaires avec un pipeline dynamique. * Les processeurs avec des fenêtres d'instruction décentralisées. La distinction n'est pas qu'une question d'implémentation matérielle. Elle impacte aussi le ratio entre instructions chargées/décodées et instructions émises. Vous vous dites que les deux sont égales, mais ce n'est le cas que sur les processeurs avec N pipelines ! Sur les autres processeurs, il est possible de décoder 4 instructions, mais d'en émettre maximum 6 par cycle ! Et sur les CPU avec des fenêtres d'instruction décentralisées, il y a aussi une différence entre le nombre d'instructions qui sont ''dispatch'' et ''schedule''. La catégorie la plus simple est celle des '''processeurs avec plusieurs pipelines'''. Ce sont des processeurs superscalaires sans exécution dans le désordre. Ils partent d'un processeur non-superscalaire, et ajoutent un second pipeline, en sortie de l'unité d'émission. Le second pipeline permet l'émission parallèle d'une seconde instruction. Le second pipeline n'est pas identique au pipeline originel, sur plusieurs points. Premièrement, il n'a pas exactement les mêmes circuits. Le second pipeline est généralement composé d'une unité de calcul, d'une FPU, éventuellement d'un second banc de registres, guère plus. Il permet d'exécuter une seconde opération de calcul, pas plus, ce qui est à l'origine de contraintes d'appariemment assez fortes. De plus, il n'a pas forcément la même longueur, il peut avoir des étages en moins ou en plus. Il s'agit de la forme la plus simple de processeur superscalaire et nous allons leur consacrer cette section. ===La double émission entière-flottante=== L'exemple le plus simple utilise un second pipeline pour les opérations flottantes, séparé du pipeline originel. L'ajout d'un pipeline flottant permet d'émettre une micro-opération entière en même temps qu'une micro-opération flottante, ce qui s'appelle la '''double émission entière-flottante'''. La micro-opération entière s'exécute dans l'ALU entière, la micro-opération flottante dans la FPU. Les exemples les plus notables sont le PA-RISC 7100, le POWER 1 (RIOS .& et .9) et le PowerPC 601. L'Apollo DN10000 et l'Intel i860 utilisaient aussi cette forme de double émission, mais étaient des prototypes de CPU VLIW. [[File:Double émission entière-flottante.png|centre|vignette|upright=2|Double émission entière-flottante]] Pour bien expliquer les choses, prenons le processeur illustré ci-dessous, avec deux ''back-end'' séparés : un pour la FPU, et un autre pour le reste. Les instructions entières et mémoire passent toutes par le ''back-end'' généraliste. [[File:CPU non-superscalaire basique, avec une FPU.png|centre|vignette|upright=1.5|CPU non-superscalaire basique, avec une FPU]] Les deux ''back-end'' sont des pipelines séparés, mais il faut que l'unité d'émission soit spécialement conçue pour en profiter. Un processeur simple émission peut démarrer une seule µop par cycle : soit la µop est envoyée au ''back-end'' flottant, soit elle est envoyée au ''back-end'' général. Avec la double émission entière-flottante, le processeur peut envoyer une µop dans chaque pipeline, à chaque cycle. Vu que la FPU et son banc de registre forment déjà un pipeline séparé, les seules modifications se font au niveau du ''front-end'', unité d'émission inclue. {| |- |[[File:CPU simple émission avec une FPU.png|centre|vignette|upright=1.5|CPU simple émission avec une FPU.]] |[[File:Double émission entière flottante avec un second pipeline.png|centre|vignette|upright=1.5|CPU avec un second pipeline flottant.]] |} Notez que le terme double émission ''entière''-flottante est un peu trompeur. En réalité, la technique permet d'émettre une instruction flottante avec n'importe quelle autre instruction non-flottante, avec des restrictions pour les branchements. Il est par exemple possible d'exécuter un calcul flottant en parallèle d'un accès mémoire. Sur les CPU 32 bits, il est possible de faire les multiplications dans la FPU. Pour rappel, la FPU contient un multiplieur entier 53-54 bits, pour multiplier les mantisses de deux flottants 64 bits, qu'on peut utiliser pour faire les multiplications entières 32 bits. Cela permet d'émettre une multiplication en même temps qu'une opération entière dans l'ALU. Ainsi, le CPU pouvait émettre deux instructions : une opération entière de base, avec soit une multiplication, soit une opération flottante. En clair, une émission de type INT + (MUL /FLOAT). Par contre, on perd la possibilité d'émettre une multiplication entière avec une opération flottante, mais c'était un compromis qui en valait la peine. Le CPU PA-RISC 7100 implémentait cette optimisation. Il s'agissait d'un CPU double émission entière-flottante, qui utilisait donc un partitionnement assez simple. Le combo partitionnement + reconditionnement permettait ici de gagner en performance, pour un cout en matériel très limité. D’autant plus limité que le jeu d'instruction prévoyait le reconditionnement de la FPU. Les multiplications ne lisaient pas leurs opérandes dans les registres entiers, mais dans les registres flottants. ===La double émission entière=== Une autre possibilité ajoute un second pipeline, capable d'exécuter des opérations entières simples, appelé le pipeline entier. Le processeur supporte alors la '''double émission entière''', à savoir qu'ils peuvent émettre une opération entière en plus d'une autre instruction. L'opération entière en question est une opération simple, comme une addition, une soustraction, une opération bit à bit, une comparaison, etc. Les exemples classiques sont les processeurs Intel i960 et Pentium. L'implémentation était différente entre l'i960 et le Pentium : Le Pentium avait une seconde ALU entière, alors que le i960 rusait. Le Pentium dupliquait l'ALU entière, pour créer un second pipeline entier. Sur le Pentium originel, les contraintes d'appariement étaient assez strictes. Le Pentium avait une FPU, mais ne pouvait pas l'utiliser en double émission. Il lui était impossible d'exécuter une opération flottante en même temps qu'une opération entière (branchements inclus). Le Cyrix 6x86 était une sorte de Pentium produit par l'entreprise Cyrix, avec une microarchitecture différente. Il avait des contraintes d’appariement réduites. Par exemple, il pouvait émettre une opération flottante en parallèle d'une opération entière. Nous détaillerons ces processeurs dans le prochain chapitre, qui porte justement sur les CPU superscalaires x86. [[File:CPU simple et double émission avec un second pipeline entier.png|centre|vignette|upright=2.5|CPU simple et double émission avec un second pipeline entier.]] L'Intel i960 n'avait pas de FPU, juste une ALU entière et une unité mémoire. Il utilisait l'unité mémoire comme une seconde ALU entière limitée. Pour rappel, une unité mémoire contient une AGU, à savoir une ALU spécialisée dans les calculs d'adresse. Elle est capable de faire des additions, soustractions, et quelques décalages limités (des décalages par 2, 4, 8, pas plus). L'Intel i960 ajoutait des connexions entre AGU et banc de registre, afin que celle-ci serve de seconde unité de calcul. L'AGU est déjà reliée aux deux ports de lecture, pour lire les opérandes du calcul d'adresse, il suffit de la connecter sur le port d'écriture. Le processeur peut alors émettre deux instructions/µops entières, sous certaines conditions. [[File:Emission multiple des opérations entières, double émission.png|centre|vignette|upright=2|Émission multiple des opérations entières, double émission.]] ==Les processeurs superscalaires dynamiques== Les processeurs précédents, avec 2 pipelines, avaient une unité d'émission assez simple. Elle avait deux ports de décodage, et deux ports d'émission. Le nombre de ports de décodage et d'émission étaient identiques. Vous vous dites que c'est naturel, et intuitif, que c'est normal pour un processeur superscalaire. Mais en réalité, la majorité des processeurs superscalaires a plus de ports d'émission que de décodage. Expliquer pourquoi va cependant nous demander de parler de comment la superscalarité exploite les unités de calcul. Posons-nous cette question : de combien d'unités un processeur superscalaire a besoin ? Les unités en question ne sont nécessairement des unité de calcul, l'unité mémoire et l'unité de branchement comptent aussi. La réponse dépend du nombre de voies. Si le ''front-end'' charge et décode un paquet de N instructions, exécuter ces N instructions simultanément demandera là aussi au moins N unités de calcul. Notez le terme : ''au moins'', car c'est là tout l'enjeu de cette section. La réponse dépend aussi des contraintes d’appariement. Pour un CPU superscalaire "parfait", sans contraintes d’appariement, toutes les unités doivent être dupliquées, y compris l'unité mémoire. Un processeur à double émission parfaite devrait dupliquer l'ALU entière, le ''barrel shifter'', le circuit multiplieur, la FPU, l'unité mémoire, et l'unité de branchement, ce qui ferait passer de 6 unités à 12. Le cout en circuit est énorme, même pour de la double émission ! Et pour un CPU à N pipelines, le cout en circuit encore plus prohibitif ! En pratique, les processeurs superscalaires qui font ça sont très rares. La majeure partie des processeurs superscalaires utilisent d'autres techniques : le partitionnement, la duplication partielle des ALUs, la duplication des autres unités. ===Le partitionnement des unités fonctionnelles=== Les CPU superscalaires les plus simples ne dupliquent pas leurs unités de calcul, ils se contentent d'exploiter les unités existantes. Et pour expliquer comment, nous allons partir de l'exemple d'un processeur non-superscalaire contient une ALU entière, une FPU, une unité mémoire et une unité de branchement. Les quatre unités peuvent fonctionner en parallèle, à savoir qu'une unité peut exécuter une instruction à elle toute seule, sans interagir avec les autres. Cela suffit pour créer un processeur quadruple émission, qui peut émettre en même temps : une opération entière, une opération flottante, un branchement, un accès mémoire. Le principe marche aussi sur un processeur avec moins de 4 unités, par exemple sans FPU et/ou sans unité de branchement, ou avec au contraire des unités en plus. L'idée générale est d’émettre une µops séparée dans chaque unité fonctionnelle. Utiliser ainsi les unités existantes, pour rendre un CPU superscalaire, s'appelle le '''partitionnement superscalaire'''. Le terme n'est pas beaucoup utilisé en-dehors de quelques vieux articles académiques. Le partitionnement est fortement dépendant du jeu d'instruction, car il demande de bien séparer instructions entières, flottantes, branchements et instructions mémoire. Et autant c'est bien le cas sur les CPU RISC, autant les CPU CISC ne sont pas dans ce cas. Les CPU CISC ont des instructions ''load-op'', qui effectuent une instruction arithmétique et une µop mémoire. Et ces instructions sont très compliquées à implémenter avec le partitionnement. Et au-delà de ça, les CPU CISC ont beaucoup d'instructions qui sont décodées en plusieurs µops, à exécuter dans un ordre bien précis. On peut pas émettre les µops en question simultanément. Le partitionnement a un cout en circuit très limité, pour un gain en performance lui aussi limité. Pour reprendre le CPU de l'exemple précédent, il est rare que l'on ait à exécuter ces quatre instructions en même temps. En pratique, les combinaisons fréquentes sont les combinaisons calcul + accès mémoire, calcul + branchement, calcul entier + calcul flottant. Utiliser une quadruple émission de ce type n'est donc pas une bonne idée, alors que la double émission parait plus adaptée. Et c'est là que la différence entre ports d'émission et de décodage devient intéressante. L'idée est d'avoir une unité d'émission avec deux ports de décodage, et 4 port d'émission (un par unité). Les contraintes d’appariement varient alors suivant le processeur, mais la règle générale est que si deux micro-opérations tombent dans deux unités différentes, on peut les émettre en même temps. En clair, on peut émettre une opération entière avec une opération flottante, une opération entière avec un branchement, un accès mémoire avec un branchement, etc. Et cela rend le partitionnement bien plus intéressant que prévu. Les tout premiers CPU superscalaires étaient des CPU double émission, qui utilisaient le partitionnement avec : une ALU entière, une unité mémoire et une FPU. Nous parlerons de '''partitionnement INT-FLOAT-MEM'''. L'avantage sur la double émission flottante est que les combinaisons INT + MEM et FLOAT + MEM étaient autorisées. L'intérêt était d'émettre une opération en parallèle d'un accès mémoire, ce qui est un doublet d'instruction assez fréquent. Les exemples classiques sont l'HyperSPARC, de l'alpha 21064 et du PowerPC 603. Les contraintes d’appariement variaient grandement suivant le processeur. Le PowerPC 603 implémentait une double émission "parfaite". C'est à dire que toutes les combinaisons possibles à deux instructions étaient possibles, que ce soit INT + FLOAT, INT + MEM, FLOAT + MEM. En plus de cette double émission "parfaite", le CPU utilisait la technique dite de ''branch folding'', qui permettait d'exécuter un branchement sans utiliser d'unité de calcul, ce qui fait qu'il était qualifié de ''CPU 2-way + Branch''. A l'opposé, le processeur alpha 21064 avait pas mal de contraintes d’appariement, à savoir que certaines combinaisons de deux instructions étaient interdites, bien que compatibles avec les unités disponibles. Une des raisons est que le banc de registre n'avait pas assez de ports de lectures, ce qui fait que certaines combinaisons avec une écriture mémoire n'étaient pas permises. Les concepteurs avaient volontairement inséré ces contraintes afin de simplifier le design du processeur. Avant de poursuivre, précisons que la FPU regroupe un additionneur flottant et un multiplieur flottant. Et ce fait peut être exploité pour approfondir le partitionnement. La '''double émission FADD/FMUL''' permet d'émettre une addition flottante en même temps qu'une multiplication flottante. Elle a été utilisée sur les processeurs x86, Alpha et SPARC. Les autres circuits de calcul flottant sont souvent regroupés avec l'additionneur. La division est parfois e même port que le multiplieur flottant, parfois sur celui de l'additionneur (quand elle est microcodée et exécutée en enchainant les soustractions). ===La duplication des ALU entières=== Le partitionnement est une technique élégante, simple, facile à implémenter et au cout en circuits modeste. Mais il faut avouer que cela ne donne pas des CPU superscalaires très efficaces. La raison est que le partitionnement permet d'émettre des paires, triplets, quadruplets de micro-opérations qui sont rares en pratique. Les combinaisons les plus fréquentes en pratique sont des paires/triplets d'opérations entières, souvent des paires/triplets d'additions ou d'opérations entières simples. Il est donc primordial d'émettre plusieurs opérations entières simultanément. Le fait d'émettre et d'exécuter plusieurs opérations entières en même temps s'appelle l''''émission entière multiple'''. Pour exécuter la seconde/troisième opération entière, il faut dupliquer les ALU entières (ou réutiliser l'AGU comme ALU entière comme le fait l'Intel i960). Le processeur doit avoir deux ALU entières pour de la double émission entière, trois pour de triple émission entière, etc. De plus, il faut ajouter des ports au banc de registre, pour lire deux fois plus d'opérandes et décrire deux résultats. Et enfin, il faut rajouter un port d'émission pour pouvoir émettre une micro-opération entière en plus. [[File:Processeur non-superscalaire basique 03.png|centre|vignette|upright=2|Processeur non-superscalaire basique]] La tendance est de multiplier les ALU entières, mais pas le circuit multiplieur. Le cout en transistors est alors grandement réduit, au prix de l'apparition de dépendances structurelles. Le CPU peut émettre deux additions en même temps, ou une addition et une multiplication, mais pas deux multiplications. Il en est de même avec le ''barrel shifter'', qui n'est pas dupliqué. Il est donc possible d'émettre un décalage avec une opération de base, mais pas deux décalages en parallèle. Mais ces contraintes d’appariement ne sont pas un problème, car il est rare de devoir émettre deux multiplications/décalages en même temps. La plupart des programmes sont majoritairement remplis d'additions, avec des multiplications assez rares et des décalages qui le sont encore plus. La moyenne est proche d'une multiplication pour 4/5 additions. Vu que des multiplications consécutives sont très rares, dupliquer le multiplieur est presque inutile. Disposer de plusieurs circuits multiplieurs ou de plusieurs ''barrel shifters'' serait donc un cout en circuits qui ne servirait que rarement et n'en vaut pas la chandelle. Il faut noter que sur les CPU 32 bits, les multiplications peuvent être exécutées dans la FPU ou dans un multiplieur séparé, tout dépend de si le CPU exécute les multiplications dans la FPU ou pas. {|class="wikitable" |- ! Voie d'émission n°1 !! Voie d'émission n°2 !! Voie d'émission n°3 |- | ALU entière || ALU entière || FPU |} Reste à connecter les unités de calcul à l'unité d'émission (indirectement, car il y a des étages de pipeline entre les deux). Une implémentation basique utilisee un port d'émission par unité. Prenons l'exemple d'un CPU superscalaire avec 4 ALU entières, une FPU, et une unité mémoire. Vu qu'il y a 6 unités, cela fait donc 6 ports d'émission. L'exemple historique le plus impressionnant est celui du Motorola 88110, un CPU double émission qui possède 10 unités. Il contient deux ALU entières, un circuit multiplieur, un circuit diviseur, une unité de manipulation de bits, une unité mémoire, un additionneur flottant, et deux unités graphiques pour des calculs impliquant des pixels. Le multiplieur est utilisé à la fois pour les multiplications entières, flottantes et le produit de deux pixels. Idem pour le diviseur, qui sert pour les divisions entières et flottantes. Et chaque unité a son propre port d'émission. La conséquence est que les contraintes d'appariement du Motorola 88110 sont simples : tant que deux µops atterrissent dans des unités séparées, la paire est permise. Il est par exemple possible d'émettre simultanément deux opérations entières simples, vu qu'il y a deux ALU. Par contre, impossible d'émettre simultanément deux multiplications, deux divisions, deux opérations flottantes, deux manipulations de bit, deux accès mémoire, etc. De plus, le partage du multiplieur fait qu'on ne peut pas émettre une multiplication entière avec une multiplication flottante. Idem avec les divisions, vu que le diviseur est aussi partagé. Ce sont les seules contraintes. ===Le partage des ports d'émission=== La présence d'un circuit multiplieur et d'un ''barrel shifter'' pose des problèmes pour le partitionnement. Il est possible d'avoir un port d'émission dédié au multiplieur, et un autre au ''barrel shifter'', qui sont séparés de ceux pour l'ALU entière. Il est alors possible d'émettre une opération entière, une multiplication et un décalage, simultanément. Nous parlerons de '''partitionnement entier''' pour désigner cette possibilité. [[File:Partitionnement superscalaire entier.png|centre|vignette|upright=2|Partitionnement superscalaire entier.]] Le partitionnement entier est cependant très rare, car le gain en performance serait trop faible au regard des couts en circuit. Quelques processeurs historiques l'utilisaient, mais ce n'est plus le cas de nos jours. La raison est que les multiplications et les décalages sont assez rares dans le code, ce qui fait que le port d'émission associés sont sous-utilisés. Et un port d'émission a un cout en circuit assez important. Et c'est sans compter qu'ajouter des ports sur le banc de registres juste pour un multiplier sous-utilisé n'en vaut pas la peine. L'idéal serait de trouver un moyen de réduire le nombre de ports d'émissions, sans réduire le nombre d'unités de calcul. [[File:Emission multiple des opérations entières, implémentation naive.png|vignette|Émission multiple des opérations entières, implémentation naive]] La solution consiste à '''partager les ports d'émission''', à savoir connecter un port d'émission à plusieurs unités de calcul. L'exemple classique est justement lié aux circuits multiplieur et ''barrel shifters''. L'idée est que le multiplieur et le ''barrel shifter'' sont mis sur le même port d'émission qu'une ALU entière. Prenons par exemple le processeur à double émission entière illustré ci-contre. Un port d'émission est relié à une ALU entière seule, et l'autre port d'émission alimente la seconde ALU entière et le multiplieur. La conséquence est que le processeur peut émettre deux opérations entières, dont une multiplication. Si on avait utilisé un port d'émission dédié au multiplieur, il aurait été possible d'émettre deux opérations entières et une multiplication. On perd donc une voie, mais on gagne en flexibilité et on économise des circuits. Notons que ce partage signifie aussi que le multiplieur partage les ports sur le banc de registre, ce qui fait une double économie. [[File:Emission multiple des opérations entières, implémentation courante.png|centre|vignette|upright=1.5|Émission multiple des opérations entières, implémentation courante]] En général, le ''barrel shifter'' et le multiplieur sont placés sur deux voies d'émission séparés, ce qui permet d'émettre un décalage et une multiplication en même temps. Les mettre sur le même port ne le permettrait pas. {|class="wikitable" |- ! Voie d'émission n°1 !! Voie d'émission n°2 !! Voie d'émission n°3 |- | * ALU entière * Multiplieur | * ALU entière * Barrel shifter | FPU |- | * Additions, soustractions, opérations bit à bit, etc. * Multiplication | * Additions, soustractions, opérations bit à bit, etc. * Décalages et rotations | Opération flottante |} Mais le partage des ports d'émission peut appliquer à n'importe quelle unité. Prenons l'exemple d'un CPU superscalaire avec 4 ALU entières, une FPU, et une unité mémoire. Vu qu'il y a 6 unités, cela fait donc 6 ports d'émission. Cependant, il est rare qu'un bloc de 6 instructions contienne exactement 4 opérations entières, une opération flottante, et un accès mémoire. Beaucoup de ports d'émission seraient sous-utilisés. La solution est de partager des ports d'émission, pour passer à la quadruple émission. Le CPU peut alors répartir 4 instructions sur les ports d'émission adéquats, à chaque cycle. Le CPU a donc 4 ports de décodage et 6 ports d'émission. Une implémentation classique permet n'importe quelle combinaison de 4 instructions compatible avec les 6 unités. Par exemple, il devient possible d'émettre simultanément 4 additions, la combinaison 2 * INT + FLOAT + MEM, 3 * INT + MEM, et bien d'autres. L'exemple relie une ALU entière, la FPU et l'unité mémoire sur le même port. Le CPU a alors quatre ports d'émission, qui ne sont pas "identiques". Trois d'entre eux sont reliés à une ALU entière chacun, rien d'autre. Le quatrième est lui relié à une ALU entière, la FPU et l'unité mémoire. On économise alors deux ports d'émission, mais cela entraine l'apparition de contraintes d’appariement. Le CPU peut émettre 3 opérations entières + n'importe quelle autre opération. Mais les autres combinaisons sont interdites. Le CPU peut émettre 6 opérations entières simultanément, grâce à 6 ALU entières. Les accès mémoire ou opérations flottantes ne viennent pas en plus, mais en remplacement des opérations entières. Regrouper plusieurs ALU sur un même port d'émission est donc à l'origine de dépendances structurelles, de nouvelles contraintes d’appariement. Dans certaines situations de '''''port contention''''', on a assez d'ALU pour exécuter N micro-opérations, mais la répartition des ALUs sur les ports l'empêche. Impossible d'émettre deux micro-opérations sur deux ALU si elles sont sur le même port d'émission. On peut réduire les cas de ''port contention'' en répartissant correctement les ALU sur les ports d'émission, mais sans jamais les supprimer complétement. Un exemple est celui du PowerPC 604. Il implémentait la quadruple émission, alors qu'il avait 5 unités de calcul. Mais deux unités partageaient un même port d'émission. Dans le détail, il avait deux ALU entières, un circuit multiplieur/diviseur, une FPU, une unité mémoire. La FPU et le multiplieur étaient sur le même port d'émission. En conséquence, le CPU pouvait émettre seulement les combinaisons INT + INT + MEM + (MUL/FLOAT). : Il est intéressant de voir l'exemple d'un processeur superscalaire parfait, sans contraintes d'appariement, à N voies. Pour cela, on part d'un processeur non-superscalaire, avec M unités de calcul. Un tel processeur peut s'implémenter avec N ports d'émission, chacun relié à M unités de calcul possibles, ce qui fait que les M unités de calcul sont dupliquées en N exemplaires. Si on prend par exemple un CPU non-superscalaire avec une ALU, une FPU, une unité mémoire et une unité de branchement, un port d'émission sera relié à une ALU, une FPU, une unité mémoire et une unité de branchement. ===La duplication des autres unités fonctionnelles=== Dupliquer les ALU entière est une première étape, mais il est aussi possible de dupliquer les autres unités fonctionnelles. Par exemple, il est possible de dupliquer l'unité mémoire ou l'unité de branchement. Reste à voir quelles unités dupliquer, et pourquoi. Précisons que dans ce qui va suivre, nous allons parler de la duplication des unités en général, pas seulement des unités mémoire/branchement. Nous allons continuer de parler de la duplication des ALU entières dans ce qui suit, mais de manière plus générale. Pour un processeur à N voies, il est théoriquement possible de dupliquer toutes les unités en N exemplaires. Mais ce n'est jamais fait en pratique, car le cout en circuits serait trop important et le gain en performance trop faible. Mieux vaut dupliquer certaines unités bien précises, quitte à avoir des contraintes d'appariement. Et à ce petit jeu, toutes les duplications ne se valent pas, certaines sont plus rentables en matière de performance, le cout en circuit n'est pas le même selon l'unité, etc. La rentabilité en performance dépend surtout des contraintes d'appariement. Les contraintes d'appariement entrainent une baisse de performance significatives si elles interdisent des paires d'instructions fréquentes. Aussi, les opérations très fréquentes, souvent appairées, gagnent à dupliquer l'unité associée. Et au contraire, les unités rarement utilisées ne doivent pas être dupliquées. Il se trouve que les unités les plus utilisées sont les ALU entières, suivies par les unités mémoire et l'unité de branchement. Le cout en circuit n'est aussi pas le même. Dupliquer les ALU entière est trivial, car elles ont un budget en transistors assez faible. Les circuits multiplieurs, diviseurs, et les ''barrel shifter'' ont eux un cout en transistor plus élevés. Si on devait faire un classement, on aurait : ALU entière < ''barrel shifter'' < multiplieur. Pour les unités mémoire, le cout en transistor est là aussi conséquent. Le résultat final est que dupliquer les ALU entières est privilégié sur le reste, comme l'a vu dans la section précédente. Les unités mémoire arrivent en seconde place, suivie par les multiplieurs/''barrel shifter'' à la troisième place, et les unités de branchement à la quatrième place. Les CPU superscalaires larges ont donc tendance à multiplier les unités mémoire en second. Les multiplieurs et ''barrel shifters'' sont eux aussi dupliqués sur les CPU superscalaires larges, mais cela vient souvent après avoir dupliqué les unités mémoire. La raison est que les accès mémoire sont critiques pour la performance des CPU à exécution dans le désordre, pas les multiplications. Pour les unités de branchement, une subtilité complique le fait d'en installer plusieurs. Que faire quand deux branchements sont pris ? La solution la plus simple est de ne pas se trouver dans cette situation : on n'utilise qu'une seule unité de branchement et on n’exécute qu'un seul branchement par cycle. La majorité des processeurs superscalaires étroits font ainsi. Les processeurs superscalaires larges ont cependant deux unités de branchements, mais ils n'acceptent qu'un seul branchement pris à la fois. Si les deux branchements pris sont exécutés en même temps, le processeur n’exécute que le plus récent, dans l'ordre du programme. ===Les CPU superscalaires mélangent les trois techniques=== Les CPU superscalaires utilisent toutes les techniques à la fois : partitionnement, duplication de l'ALU entière, duplication des autre unités fonctionnelles. Chaque processeur utilise ces trois stratégies à sa sauce, en fonction de son budget en transistor. Les quatre techniques ont cependant un cout en circuit différent, et donnent des gains de performances eux aussi forts différents. * Le '''partitionnement superscalaire''' a un cout en circuit modéré, car on n'a pas besoin de dupliquer d'ALU. * La '''duplication des ALU entières''' est la technique la plus importante : elle a un cout en circuit modéré, et donne des gains en performance impressionnants. * La '''duplication des autres unités''' a un cout en circuit variable, pour des gains en performance eux aussi variables. * Le '''partage des ports d'émission''' réduit le cout en circuit du CPU, pour des pertes de performances mineures si bien fait. L'usage du partitionnement seul, sans duplication, était surtout le fait des processeurs commercialisés entre les années 1989 et 1993. Le partage des ports d'émission a lui aussi mis un peu de temps pour s'imposer. Globalement, les processeurs superscalaires ont évolués progressivement, pour se rapprocher d'une sorte de standard. Mais pour comprendre ce qu'est ce standard, et en quoi les CPU historiques s'en sont progressivement rapprochés, il faut étudier l'histoire des CPU superscalaires. L'histoire des CPU superscalaires a commencé dès 1960, mais est resté un projet de recherche pendant plusieurs décennies. De nombreux designs ont été conçus, mais abandonnés avant la production, comme l'ACS-1 d'IBM. Les années 1980 ont vu l'arrivée de prototypes de processeurs VLIW, mais qui n'étaient pas superscalaires. Par exemple, l'Apollo DN10000 et l'Intel i860 pouvaient émettre deux instructions à la fois, sous conditions, mais demandaient que le compilateur fasse une bonne partie du travail. Les premiers CPU superscalaires commercialisés sont sortis en 1989. Deux CPUs superscalaires sont sortis cette année là : le RS/6000 d'IBM et l'i960CA d'Intel. Puis, la compétition s'est aussi lancée dans la bataille. Les CPU commercialisés avant l'année 1993 étaient à double émission, à l'exception du SuperSPARC qui utilisait la triple émission. Les modèles après 1993 hésitaient entre double et quadruple émission. Dans le tableau ci-dessous, les processeurs quadruple émission sont en bleu, les triple émission sont en rouge, les autres ne sont pas en couleur. {|class="wikitable" |- ! !! IBM !! HP PA-RISC !! SPARC !! Intel!! Alpha !! Autres |- ! 1989 | RS/6000 || || || i960CA || || |- ! 1990 | || || || || || |- ! 1991 | || || || || || Motorola 88110 |- ! 1992 | RSC || PA 7100 || class="f_rouge | SuperSPARC || || 21064 || |- ! 1993 | PPC 601 || || HyperSPARC || Pentium || || |- ! 1994 | class="f_bleu | POWER 2 || PA 7200 || || || class="f_bleu | 21164 || |- ! 1995 | 603/604 || class="f_bleu | PA 8000 || class="f_bleu | UltraSPARC 1 || Microarchitecture P6 (+ concurrence AMD) || || class="f_bleu | MIPS R1000 |- ! 1996 | class="f_bleu | 620 || || || || class="f_bleu | 21264 || |- ! ... || ... || ... || ... || ... || ... || ... |} Les premiers CPU à double émission sont assez représentatifs des CPU à double émission modernes. Il faut dire que se limiter à deux voies ne permet pas de faire beaucoup de choses. Voici le tableau chronologique disant quel CPU utilisait quelle technique. * Les CPU en vert utilisaient la double émission entière-flottante. * Les CPU en rouge utilisaient la double émission entière "pure". * Les CPU en jaune utilisaient un ''partitionnement complet'' entre ALU, FPU et unité mémoire. * Les CPU sans couleur utilisaient un grand nombre d'unités, avec beaucoup de ports d'émission. * Les CPU barrés utilisent la triple ou quadruple émission et seront vu dans la section suivante. {|class="wikitable" |- ! !! IBM !! HP PA-RISC !! SPARC !! Intel!! Alpha !! Autres |- ! 1989 | class="f_vert | RS/6000 || || || class="f_rouge | i960CA || || |- ! 1990 | || || || || || |- ! 1991 | || || || || || Motorola 88110 |- ! 1992 | class="f_vert | RSC || class="f_vert | PA 7100 || <s>SuperSPARC</s> || || class="f_jaune | 21064 || |- ! 1993 | class="f_vert | PPC 601 || || class="f_jaune | HyperSPARC || class="f_rouge | Pentium || || |- ! 1994 | <s>POWER 2</s> || PA 7200 || || || <s>21164</s> || |} C'est en 1994, que la quadruple émission est arrivée et que les CPU ont essayé divers implémentations. Il y avait alors trois stratégies pour la double/triple/quadruple émission, qui partageaient des problèmes similaires. La première implémentation dupliquait les ALUs et les FPUs, ce qui permettait d'émettre deux opérations flottantes avec deux opérations entières. Le POWER 2 dupliquait ses FPUs à la perfection, alors que le 21264 séparait l'additionneur et le multiplieur flottant. Les accès mémoire étaient traités comme des opérations entières et le processeur pouvait souvent en émettre deux à la fois. Mais une telle organisation n'est plus utilisée de nos jours, car, sans instructions flottantes à exécuter, elle est équivalente à de la double émission. La seconde implémentation utilisait deux ALU entières (dont un multiplieur), une unité mémoire et une FPU.Cette organisation est intéressante, mais encore trop stricte pour donner de bons résultats, les quadruplets avec exactement deux opérations entières, un accès mémoire et une opération flottante étant trop rares. L'usage de ces 4 unités en double ou triple émission a été utilisé sur le PA 7200 et le SuperSPARC. Malheureusement, en plus de problèmes au niveau de son cache, le SuperSPARC avait de nombreuses contraintes d’appariement, car le banc de registre n'avait pas assez de ports pour alimenter certaines combinaisons d'instructions. De plus, s'il émettait une multiplication, il ne pouvait pas émettre de seconde µop. Enfin, une troisième implémentation utilisait un grand nombre d'unités fort différentes, bien plus que quatre, avec au minimum deux ALU entières. Prenons l'exemple de l'UltraSPARC, qui avait : deux ALU entières, une unité mémoire, une unité de branchement, un additionneur flottant, un multiplieur flottant, un diviseur flottant (aussi utilisé pour le calcul des racines carrées), et 2 unités graphiques pour les calculs sur des pixels. En tout, cela fait 9 unités différentes, pour de la quadruple émission. Dans le même genre, les processeurs MIPS R8000 et R10000 avaient une implémentation plus moderne, dans le sens où toutes les unités étaient dupliquées. Le CPU avait deux ALU, deux FPU et deux unités mémoire. Il pouvait donc émettre quatre instructions à la fois, tant que le paquet collait avec ces six unités. Moitié opérations entières, moitié opérations mémoire, avec de quoi remplacer les opérations entières par des opérations flottantes. Le PA 8000, lui, dupliquait toutes ses unités en deux exemplaires, ce qui n'est pas l'idéal, aucun processeur moderne ne fait cela aujourd'hui. Le tout est résumé dans le tableau ci-dessous, avec : * en vert les CPU utilisant la première stratégie ; * en rouge les CPU utilisant la seconde stratégie ; * en jaune les CPU utilisant la troisième stratégie. {|class="wikitable" |+ Processeurs quadruple émission historiques |- ! Année !! Processeur !! Unités |- ! 1991 | class="f_jaune | Motorola 88110 || class="f_jaune | 2 ALU + MUL + DIV + BIT + MEM + FADD + 2 unités graphiques |- |- ! rowspan="2" | 1994 | class="f_vert | POWER 2 || class="f_vert | 2 ALU/MEM + 2 FPU (+ 2 Branchements via ''branch folding'') |- | class="f_rouge | 21164 || class="f_rouge | 2 ALU + FPU + MEM |- ! rowspan="3" | 1995 | PA 8000 || 2 ALU + 2 ''barrel shifters'' + 2 MEM + 2 FPU (FMA) + 2 FPU (DIV et SQRT) + 2 SIMD |- | class="f_jaune | UltraSPARC 1 || class="f_jaune | 2 ALU + MEM + BRANCH + FADD + FMULFDIV + 2 unités graphiques |- | MIPS R8000 et R10000 || 2 ALU + 2 FPU + 2 MEM |- ! rowspan="2" | 1996 | class="f_rouge | 620 || class="f_rouge | 2 ALU + FPU + MEM |- | class="f_vert | 21264 || class="f_vert | 2 ALU/MEM + 2 FPU (FADD + FMUL) |} Malheureusement, ces CPUs historiques n'avaient ni assez d'unités de calcul entières, ni d'unités mémoire, pour remplir la quadruple émission. Environ la moitié des instructions d'un programme correspond à des instructions entières. Pour en profiter, un processeur à N voies doit disposer d'au minimum N/2 ALU entières, l'idéal étant d'aller au-delà. Les CPU superscalaires historiques avaient 2 ALU pour de la quadruple émission, ce qui était très limite. En comparaison, les CPU superscalaires modernes mettent le paquet niveau ALU entière. La plupart des CPU x86 modernes descendent rarement sous les 2/3, la plupart remplit presque la totalité de la largeur du CPU. Pour donner un exemple, la microarchitecture Golden Cove d'Intel est un processeur à 6 voies, avec 5 ALU entières. Pour être précis, un programme informatique exécute environ 40% de son code dans des instructions arithmétiques, un autre 40% dans les accès mémoire, et un le reste pour... le reste. La conséquence est que les processeurs modernes dupliquent leurs unités mémoire. Par exemple, la microarchitecture ''Golden Cove'' d'Intel dispose de trois unités mémoire : deux spécialisées dans les écritures, trois dans les lectures. La microarchitecture ''Gracemont'' utilise la pentuple émission et peut émettre 4 µops entières et 4 µops mémoire. Notez que le ratio est équilibré entre opérations entières et mémoire, avec soit égalité, soit un avantage pour les ALU entières. Une autre régularité est que la largeur totale du processeur peut être occupée par des opérations entières/mémoire. Pour le dire autrement, sur un processeur quadruple émission, il pourra émettre un mix de 4 instructions entière/mémoire, avec zéro opération flottante. Idem avec des processeurs triples ou pentuple émission, voire plus. Si le processeur émet une opération flottante, elle remplace une opération entière. Prenons l'exemple des processeurs AMD K6, sortis en 1997, juste avant les AMD Athlon. Ils avaient 10 unités de calcul, 6 ports d'émission, 4 ports de décodage. Voici exactement comment étaient répartie les unités de calcul sur les ports d'émission. C'était des processeurs quadruple émission, ce qui fait que la moitié de la largeur du processeur sert pour des opérations entières, et l'autre moitié pour les accès mémoire. {|class="wikitable" |- ! Port d'émission n°1 !! Port d'émission n°2 !! Port d'émission n°3 !! Port d'émission n°4 !! Port d'émission n°5 !! Port d'émission n°6 ! |- | ALU entière || ALU entière || FPU || Unité de branchement || Unité LOAD || Unité STORE |- | || Multiplieur || || || || Unité de calcul d'adresse (instruction LEA) |- | || ''Barrel shifter'' || || || || Unité pour les instructions PUSH |} Les microarchitectures K7, K8 et K10, utilisées sur les anciens CPU AMD Athlon, sont un exemple moins représentatif. Ces microarchitectures utilisent le partage des ports d'émission au maximum. C'était des microarchitectures à triple émission, avec six ports d’émission. Il y avait trois ports d'émission pour les opérations flottantes, et trois ports d'émission pour les micro-opérations entières/mémoire. Il y avait trois ALU entières et trois unités mémoire, chaque ALU partageait un port d'émission avec une unité mémoire. Elle pouvait donc émettre soit trois opérations entières, soit trois µops mémoire, soit un mix des deux. En plus des mix avec les opérations flottantes et branchements. {|class="wikitable" |- ! Port d'émission n°1 !! Port d'émission n°2 !! Port d'émission n°3 !! Port d'émission n°4 !! Port d'émission n°5 !! Port d'émission n°6 ! |- | ALU entière || ALU entière || ALU entière || FADD || FMUL || FPU (autres opérations) |- | Multiplieur || || || || || |- | LOAD/STORE || LOAD/STORE || LOAD/STORE || || || |} Les CPU superscalaires larges dupliquent les multiplieurs et/ou les ''barrel shifters'', et même les unités de branchement. La raison est que quand la largeur du CPU augmente, le processeur traite des paquets d'instructions de plus en plus gros. Par exemple, pour un CPU à 10 voies, cela veut dire émettre 10 instructions en même temps. Il est alors plus fréquent d'avoir deux multiplications, deux décalages, deux accès mémoire, deux branchements. Par exemple, la moyenne est d'une multiplication tous les 5 instructions. Les CPU superscalaires à 10 voies ont donc tendance à avoir deux multiplications par paquet d'instruction, en moyenne. Avoir deux multiplieurs est alors un gain en performance clair. Par exemple, la microarchitecture ''Golden Cove'' a deux ''barrel shifters''. C'est une architecture à 6 voies. {|class="wikitable" |- ! Port d'émission n°1 !! Port d'émission n°2 !! Port d'émission n°3 !! Port d'émission n°4 !! Port d'émission n°5 !! Port d'émission n°6 !! Port d'émission n°7 !! Port d'émission n°8 !! Port d'émission n°9 !! Port d'émission n°10 |- | ALU entière || ALU entière || ALU entière || ALU entière || ALU entière || LOAD || LOAD || LOAD || STORE || STORE |- | ''Barrel shifter'' || Multiplieur || Multiplieur || ''Barrel shifter'' || || || || || || |- | Branchements || Diviseur || || Branchements || || || || || || |} ==Les µops mémoire sur les processeurs superscalaires== Plus haut, nous avons volontairement passé sous silence les accès mémoire. La raison à cela est que leur support sur les CPU superscalaire peut se faire de plusieurs manières différentes, qu'il vaut mieux expliquer dans une section dédiée. Les différentes implémentations sont décrites dans la liste suivante, nous allons les voir une par une. * Avec la première méthode, les accès mémoire sont exécutés par le pipeline pour les opérations entières. * Avec la seconde méthode, les accès mémoire ont un pipeline dédié ; c'est l'''émission parallèle des accès mémoire''. * Avec la troisième méthode, il est possible d'émettre plusieurs accès mémoire simultanément : c'est l'''émission multiple des accès mémoire''. Voyons ces trois cas dans le détail. Nous verrons notamment les différentes formes d'émission multiple des accès mémoire. ===Les accès mémoire traités dans le pipeline entier=== Dans le cas le plus simple, les accès mémoire sont traités comme des opérations entières un peu spéciales. L'unité mémoire est sur le même port d'émission que l'ALU entière, ce qui fait que les accès mémoire remplacent des opérations entières. Faire ainsi permet d'utiliser une ALU entière pour les calculs d'adresse, pas besoin de rajouter une unité de calcul d'adresse séparée. Typiquement, un processeur de ce type place l'unité mémoire après l'unité de calcul. Les opérations entières n'utilisent que l'ALU entière, mais pas l'unité mémoire. Les accès mémoire exécutent un calcul d'adresse dans l'ALU entière, puis l'adresse calculée est envoyée à l'unité mémoire. Le tout est illustré ci-dessous, dans le cas d'un processeur historique à double émission entière-flottante. [[File:Processeur non-superscalaire basique 01.png|centre|vignette|upright=1.5|Processeur non-superscalaire basique]] Un exemple de CPU double émission de ce type est celui du Power PC 440, un CPU destiné à l'embarqué. Vu qu'il était destiné à l'embarqué, il n'avait pas de FPU, il n'avait pas besoin de manipuler de nombres flottants. Il n'a donc que des ALU entières, un multiplieur et une unité mémoire. Il dispose de deux ports d'émission. Le premier est connecté à une ALU entière et un circuit multiplieur, le second est relié à l'unité mémoire et une seconde ALU entière. Une organisation simple, mais très efficace ! [[File:PowerPC 440.png|centre|vignette|upright=2|Microarchitecture du PowerPC 440.]] L'implémentation économise une unité de calcul d'adresse, mais n'a pas de cout en performance significatif. Les accès mémoire étant très lents, émettre une µop entière en parallèle d'un accès mémoire ne sert pas à grand-chose. On peut émettre cette µop entière un cycle plus tard, elle sera terminée avant même que l'accès mémoire soit terminé, même si l'accès mémoire se fait dans le cache L1 ou L2. Pour comprendre pourquoi, faisons quelques calculs. Un accès mémoire prend deux-trois cycles quand il tombe dans le cache L1. Et on doit attendre qu'il soit terminé pour en émettre un nouveau. Combien d'instructions arithmétiques peut-on lancer pendant ce temps, sur un CPU triple émission ? Au premier cycle, on peut en émettre 2 (l'instruction mémoire prend une place, donc 3-1), puis 3 au second cycle. Ce qui fait 5 instructions arithmétiques au total. Or, il se trouve qu'il y a une instruction d'accès mémoire pour 5 instructions arithmétiques, environ. Par contre, ce compromis change quand on passe à la quadruple émission, voire au-delà. Dans ce cas, il devient intéressant de pouvoir émettre une nouvelle instruction mémoire à chaque cycle. Ce qui nous amène à la section suivante... ===L'émission parallèle des accès mémoire=== A partir de la quadruple émission, la tendance a été de séparer l'unité mémoire des ALU entières. Cela permet d'émettre des µops mémoire en même temps que des µops entières et/ou flottantes., ce qui porte le nom d''''émission parallèle des µops mémoire''', ou encore ''émission mémoire parallèle''. Par exemple, un processeur quadruple émission peut émettre en même temps deux opérations entières, une opération flottante et un accès mémoire. L'accès mémoire vient en plus des opérations entières, il n'est pas possible de remplacer l'accès mémoire par une opération entière. La technique ne permet pas de faire les calculs d'adresse dans l'ALU entière. Il faut donc utiliser une unité de calcul d'adresse séparée. Elle est intégrée dans l'unité mémoire, qui fait l'interface entre le cache et le banc de registres. Le cout en circuits est très faible, car les unités de calcul d'adresse n'utilisent guère plus qu'un additionneur et un circuit de décalage très simple. L'émission mémoire parallèle est plus facile à implémenter sur les CPU RISC, qui ont des instructions mémoire séparées du reste. Les CPU CISC ne sont pas dans ce cas, ce qui fait que séparer l'unité mémoire du pipeline entier est compliqué. Les CPU superscalaires historiques étaient pour moité des CPU RISC et pour moitié des CPU CISC, ce qui fait qu'ils n'utilisaient pas ou peu l'émission mémoire parallèle. Les premiers processeurs superscalaires se contenaient de l'émission parallèle mémoire, ils ne pouvaient émettre qu'un seul accès mémoire à la fois, l'unité mémoire n'était pas dupliquée. Et ce n'est pas tant un problème que ça, du moins sur les processeurs à double ou triple émission. En effet, il est rare que l'unité mémoire doive faire deux accès simultanés. Notons que le processeur peut exécuter une instruction mémoire en parallèle d'un calcul ou d'un branchement, seuls les accès mémoire simultanés sont impossibles. ===L'émission multiple des accès mémoire avec une LSQ=== Les processeurs superscalaires larges travaillent sur des paquets de 8 à 10 instructions, il est plus fréquent d'avoir plusieurs accès mémoire par paquet. Dans ces conditions, il est utile d'avoir une '''émission multiple des accès mémoire''', à savoir d'émettre plusieurs micro-opérations mémoire en même temps. Les processeurs superscalaires modernes sont capables d'émettre plusieurs lectures/écritures simultanément. Par exemple, ils peuvent émettre une lecture en même temps qu'une écriture, ou plusieurs lectures, ou plusieurs écritures. Il faut noter que selon le processeur, il peut y avoir des restrictions quant aux accès mémoire émis en même temps. Par exemple, certains processeurs peuvent émettre une lecture avec une écriture en même temps, mais pas deux lectures ni deux écritures. Ou encore, ils peuvent émettre deux lectures, une lecture et une écriture, mais pas deux écritures en même temps. Dans la majorité des cas, les processeurs ne permettent pas d'émettre deux écritures en même temps, alors qu'ils supportent plusieurs lectures. Il faut dire que les lectures sont plus fréquentes que les écritures. Les processeurs qui autorisent toutes les combinaisons de lecture/écriture possibles, sont rares. L'émission multiple des accès mémoire s'implémente dans l'unité mémoire. Pour rappel, l'unité mémoire s'interpose entre le cache L1 et le reste du pipeline. Dans le cas le plus simple, elle incorpore une ''load/store queue'' et des unités de calcul d'adresse. L'émission multiple des accès mémoire demande de rendre la ''Load-store queue'' multi-ports, afin de gérer plusieurs micro-opérations mémoire simultanées. Il faut aussi dupliquer les unités de calcul d'adresse reliées à la LSQ, chaque port d'émission mémoire devant avoir sa propre unité de calcul d'adresse. : Dans ce qui suit, nous parlerons d'AGU (''Adress Generation Unit'') pour désigner les unités de calcul d'adresse, et de LSQ pour designer la ''Load-store queue''. [[File:Processeur non-superscalaire basique 02.png|vignette|upright=1|Processeur non-superscalaire basique]] Dupliquer les ports de la LSQ et les AGU est nécessaire. Mais qu'en est-il du cache ? La réponse varie suivant le processeur. Il y a deux grandes méthodes pour implémenter l'émission multiple des accès mémoire. La première méthode ajoute des ports de lecture/écriture au cache de données, pour faire des lectures/écritures simultanées. Il faut alors dupliquer des unités de calcul, rajouter les ports sur le cache, mais aussi rajouter des ports sur la ''load/store queue'', pour qu'elle échange des données/commandes mémoire avec le cache. Le cout en circuits est alors conséquent La seconde duplique les unités de calcul, mais pas les ports d'accès au cache. Les accès mémoire sont alors accumulés dans la ''load/store queue'', qui sont ensuite traités un par un par le cache. Un exemple est celui des processeurs AMD de micro-architecture K7 K8, à savoir les anciens AMD Athlon et AMD Athlon 64, qui pouvaient calculer trois adresses simultanément, mais ne pouvaient faire qu'un seul accès au cache par cycle. Le cout en circuit est modéré : les unités de calcul coutent très peu, les ports de la ''load/store queue'' coutent déjà un peu plus. Quelques processeurs utilisaient des solutions intermédiaires. Un processeur utilisant cette technique est, par exemple, capable d'émettre quatre µops mémoire en même temps, mais avec seulement deux ports sur le cache. Un exemple classique étant les Pentium 1 et 2 d'Intel, dont nous parlerons dans le chapitre suivant. ===L'émission séparée des LOAD et STORE=== De rares processeurs ont des ports d'émission qui peuvent faire indifféremment lecture comme écritures. Mais les processeurs modernes préfèrent séparer ports d'émission pour les lectures et les écritures. Un exemple est celui des processeurs Intel de micro-architecture Nehalem, qui avaient un port de lecture et un port d'écriture tous deux reliés à la ''Load-store queue'', chacun avec leur propre unité de calcul d'adresse. Comme autre exemple, les processeurs skylake ont une LSQ avec deux ports de lecture et un port d'écriture, ce qui permet d'émettre deux lectures en même temps qu'une écriture. Dans cette section, nous allons étudier le cas avec des ports séparés pour les lectures et écritures. En général, les processeurs ont un peu plus de ports de lectures que d'écritures. Une raison est qu'un programme a, en moyenne, un tout petit peu plus de lectures que d'écritures. Mais la différence n'est pas énorme, c'est un léger déséquilibre. Une seconde raison est que l'exécution dans le désordre marche bien mieux quand les lectures ont lieu le plus tôt possible. Donc mieux vaut privilégier les lectures à l'émission, pour qu'elles se fassent un peu en avance. En pratique, la différence n'est pas très marquée. Il est courant d'avoir un port de lecture et un d'écriture, les CPU superscalaires très larges ont souvent deux ports de lecture et deux d'écriture. A la rigueur, quelques CPU ont trois ports de lecture et deux d'écriture. Le cas le plus simple est celui où on peut émettre une lecture et une écriture en même temps. Il s'implémente facilement si la LSQ est en réalité une simple ''Store queue'', à savoir qu'elle met en avance les écritures uniquement. Pour rappel, les écritures sont mises en attente dans la ''Store queue'', mais les lectures sont exécutées immédiatement. Une fois l'adresse à lire connue, la lecture consulte la ''Store queue'' pour détecter les dépendances de données, puis s'exécute. La donnée à lire est lue soit depuis la ''Store queue'', soit depuis le cache. [[File:Emission LOAD - Store séparée.png|vignette|upright=1|Émission LOAD - Store séparée]] L'implémentation de l'émission multiple est alors très simple. La ''Store queue'' a déjà deux ports, avec un pour les lectures et un autre pour les écritures. Idem pour le cache, qui a naturellement un port de lecture et un d'écriture. Émettre une lecture et une écriture simultanément ne demande donc pas de modification digne de ce nom. La ''Store queue'' doit juste supporter une lecture et une écriture simultanée. Ajouter l'émission d'une seconde lecture n'est pas compliquée : il faut juste rajouter un second port de lecture sur le cache et la ''Store queue''. Pour rappel, l'unité mémoire est précédée par une structure qui met en attente les micro-opérations mémoire avant leur émission finale. La structure en question varie suivant l'implémentation, mais un cas particulier se marie bien avec l'émission multiple des µops mémoire. Il s'agit du cas avec une file de µops séparée pour les lectures et une autre pour les écritures, appelées respectivement la ''file de µops LOAD'' et la ''file de µops STORE''. Il est alors possible d'émettre une lecture et une écriture lors du même cycle d'horloge. Le port de lecture alimente une unité de calcul d'adresse dédiée, directement reliée au cache. Le port d'écriture du cache alimente une unité de calcul, qui est suivie par une file d'écriture, elle-même reliée au cache. Les processeurs AMD de micro-architecture K6 utilisaient cette technique. [[File:Double émission avec le Load Ordering, Store Ordering.jpg|centre|vignette|upright=2.5|Double émission avec le Load Ordering, Store Ordering]] ==Les CPU superscalaire à exécution dans le désordre== La superscalarité modifie aussi les circuits dédiés à l'exécution dans le désordre. Par contre, elle modifie les unités de renommage de registre, le ROB et les autres structures liées à l'exécution dans le désordre, au sens large. Dans cette section, nous allons voir comment les étages du pipeline dédiés à l'exécution dans le désordre sont modifiées sur un CPU superscalaire. ===L'exécution dans le désordre centralisé : la différence entre largeur de décodage et d'émission=== Plus haut, nous avons montré qu'un processeur pouvait avoir plus de ports d'émission que de ports de décodage. On dit aussi que sa ''largeur de décodage'' était supérieure à sa ''largeur d'émission''. La '''largeur de décodage''' est le nombre de µops fournies par l'unité de décodage par cycle d'horloge, la '''largeur d'émission''' est le nombre maximal de µops émises par cycle d'horloge. Et n'allez pas croire qu'un surplus de ports d'émission est du gâchis. Sur les processeurs avec exécution dans le désordre, ce surplus peut être utilisé. Mais avant toute chose, évacuons un détail. Vous avez sans doute pensé que le surplus peut être exploité par les instructions machines décodées en plusieurs µops . Par exemple, si une instruction est décodée en deux µops, on pourrait avoir un port d'émission en plus pour en profiter. Mais les ports de décodage sont *en sortie du décodeur*, et contiennent des µops, pas des instructions. Le surplus de ports d'émission peut occasionnellement utiliser le surplus de ports de décodage, mais cela implique que des µops ont été mises en attente dans l'unité d'émission, pour être émises par la suite. Et cela implique l'usage d'une fenêtre d'instruction ou d'une file de µops. Le principe est que les µops sont accumulées dans la fenêtre d'instruction, en attendant leurs opérandes ou la résolution d'une dépendance. Et si l'occasion se présente, une µops est émise sur chaque port d'émission. Il y a donc un gain en performances, mais il faut faire quelques précisions sur son origine. La largeur d'émission utilisée ne peut pas dépasser, *en moyenne*, la largeur de décodage. Et c'est intuitif : un CPU qui décode 4 µops par cycle ne peut pas émettre 6 par cycle en moyenne, sur le temps long, il n'y a pas de µops en surplus qui sortent du vide. Par exemple, sur un processeur quadruple émission, si on analyse combien d'instructions sont émises par cycle, il peut y avoir des cycles où la largeur d'émission est totalement utilisée, mais ce sera compensé par des périodes où elle est fortement sous-utilisée. Les µops sont accumulées en cas de sous-utilisation, puis consommées en cas d'utilisation complète. Les deux périodes se compensent. Et c'est là que l'on peut comprendre d'où vient le gain en performance. Les cycles où les ports d'émission sont sous-utilisés existent systématiquement, que le processeur utilise l'exécution dans le désordre ou pas. Ils sont liés à la présence de dépendances de données, aux contraintes d’appariement, etc. Elles tendent à réduire la moyenne du nombre de µops émises par cycle. Par contre, ce sont les cycles de rattrapage, où les ports en surplus sont utilisés, qui font remonter la moyenne. Le gain en performance est donc lié à ces cycles de rattrapage, où les µops accumulées sont émises dans les ports en surplus. Et ils n'existent que sur les processeurs avec une file/fenêtre d'instruction. ===Les CPU superscalaires à fenêtres d'instruction décentralisées : la largeur de ''dispatch'' et de ''schedule''=== Les processeurs précédents utilisaient une unité d'émission simple, qui pouvait utiliser l'émission dans l'ordre ou dans le désordre. Elle peut être remplacée par une fenêtre d'instruction centralisée dans que cela quoi que ce soit aux explications précédentes, idem avec une station de réservation centralisée. En soi, l'exécution dans le désordre est donc totalement compatible avec une implémentation simple de la superscalarité. Par contre, des subtilités surviennent avec les fenêtres d'instruction décentralisées. Pour rappel, sur ces processeurs, les µops sont émises en deux phases. Une première phase de ''dispatch'' qui envoie les µops vers la fenêtre d'instruction adéquate, sans détecter les dépendances de données. La seconde phase de ''schedule'' gère les dépendances de données, elle met en attente les µops en attendant que l'unité soit disponible pour les exécuter, en attendant aussi que les opérandes soient disponibles. Et à ce petit jeu, il y a une différence entre le nombre de µops qui sont ''dispatch par cycle'' et celles qui sont ''schedule''. Dit autrement, la largeur d'émission est scindée en une '''largeur de ''dispatch''''' et une '''largeur de ''schedule''. Par exemple, prenons un processeur avec deux stations de réservation : une pour les µops entières/mémoire, et une autre pour les opérations flottantes. L'unité d'émission fait de la triple émission, ce qui fait qu'elle peut ''dispatch'' maximum trois µops par cycle : soit trois µops entières/mémoire, soit trois flottantes, soit un mix de trois µops différentes. Les stations de réservation, quant à elle, peuvent chacune ''schedule'' 3 µops par cycle, vers trois ALUs entières pour la station de réservation entière/mémoire, vers trois FPS pour la station de réservation flottante. En tout, cela permet de ''dispatch'' trois µops, mais d'en ''schedule'' six, à chaque cycle. Et cet exemple ne sort pas de nulle part : les CPU x86 d'Intel et AMD sont globalement dans ce cas, même si les chiffres varient d'une microarchitecture à l'autre. Un détail est que le nombre de ports de lecture n'est pas le même selon que le processeur utilisé des stations de réservation ou une fenêtre d’instruction. Rappelons la terminologie utilisée dans ce cours. Les fenêtres d'instruction se contentent de mémoriser la micro-opération à émettre et quelques bits pour la disponibilité des opérandes. Par contre, les stations de réservations mémorisent aussi les opérandes des instructions. Et cette différence a une influence le nombre de ports de lecture du banc de registre. Supposons que toutes les instructions sont dyadiques, à deux opérandes. Comptons le nombre de ports nécessaires pour alimenter les unités de calcul, sans utiliser d'arbitre de banc de registre. Avec une fenêtre d'instruction, il faut le double du nombre de ports de ''dispatch''. Mais avec des stations de réservation, il faut le double des ports de ''schedule''. La raison est que les registres sont lus après émission avec une fenêtre d'instruction, avant avec des stations de réservation (sous-entendu, après le décodage). or, comme dit plus haut, les processeurs superscalaires larges ont souvent plus de ports de ''schedule'' que de ''dispatch''. ===L'unité de renommage superscalaire=== Avec la superscalarité, l'unité de renommage de registre doit gérer le cas où des instructions consécutives ont des dépendances de registre. Par exemple, prenons un processeur à double émission, qui renomme deux instructions consécutives. Si elles ont une dépendance de registre, le renommage de la seconde instruction dépend du renommage de la première. La première doit être renommée et le résultat du renommage est utilisé pour renommer la seconde, ce qui empêche d'utiliser deux unités de renommage séparées. Pour cela, elle renomme les registres sans tenir compte des dépendances, pour ensuite corriger le résultat. [[File:Unité de renommage superscalaire.png|centre|vignette|upright=2|Unité de renommage superscalaire.]] Seules les dépendances lecture-après-écriture doivent être détectées, les autres étant supprimées par le renommage de registres. Repérer ce genre de dépendances se fait assez simplement : il suffit de regarder si un registre de destination d'une instruction est un opérande d'une instruction suivante. [[File:Détection des dépendances sur un processeur superscalaire.png|centre|vignette|upright=2|Détection des dépendances sur un processeur superscalaire.]] Ensuite, il faut corriger le résultat du renommage en fonction des dépendances. Si une instruction n'a pas de dépendance avec une autre, on la laisse telle quelle. Dans le cas contraire, un registre opérande sera identique avec le registre de destination d'une instruction précédente. Dans ce cas, le registre opérande n'est pas le bon après renommage : on doit le remplacer par le registre de destination de l'instruction avec laquelle il y a dépendance. Cela se fait simplement en utilisant un multiplexeur dont les entrées sont reliées à l'unité de détection des dépendances. On doit faire ce replacement pour chaque registre opérande. [[File:Correction des dépendances sur un processeur superscalaire.png|centre|vignette|upright=2|Correction des dépendances sur un processeur superscalaire.]] ===Le tampon de réordonnancement et le banc de registre d'un CPU superscalaire=== Le ROB doit aussi être modifié pour émettre et terminer plusieurs instructions en même temps, en ajoutant des ports de lecture/écriture. Pour un CPU superscalaire peut émettre 8 µops à la fois, on se dit que le ROB peut retirer 8 µops par cycle maximum. Et c'est très souvent le cas en pratique. Cependant, certains processeurs ont un ROB qui peut retirer le double ! Un exemple est celui des CPU de micro-architecture Skymont, qui peuvent émettre 8 µops par cycle, mais en retirer 16. Quelques CPU de marque AMD étaient aussi dans ce cas. L'avantage est que cela permet de réduire la taille du ROB, du banc de registre et de quelques autres structures matérielles liées à l'exécution dans le désordre. Le gain sur-compense le cout en transistor nécessaire pour retirer 16 µops par cycle. Réduire le nombre de ports d'émission permet de garder un banc de registre, des fenêtres d'instruction et un ROB simples, rapides et économes en énergie. Et mine de rien, c'est là un compromis important : à quoi bon augmenter le nombre de ports d'émission si c'est pour que ce soit compensé par une fréquence plus faible ? Par contre, réduire le nombre de ports augmente la ''port contention'', surtout si on a mal réparti les ALU. Il faut donc trouver un compromis entre réduire le nombre de ports d'émission d'un côté, réduire la ''port contention'' de l'autre. ==Les optimisations des processeurs superscalaires larges== Les processeurs superscalaires larges ont des optimisations spécifiques, qui n'ont pas de sens sur les processeurs superscalaires étroits. Dans les grandes lignes, elles se classent en deux types. Les premières permettent de charger/décoder plus de 5-10 instructions simultanément. Le problème est alors que les branchements viennent mettre leur grain de sel et qu'il faut en tenir compte. Les secondes corrigent des problèmes qui apparaissent quand un CPU intègre plus de 5 à 10 unités de calcul. ===L'étape de chargement d'un CPU superscalaire large=== Charger plusieurs instructions pose de nombreux problèmes sur les processeurs superscalaires larges, qui émettent de 5 à 10 instructions simultanées. Pour rappel, la stratégie basique pour charger N instructions est de charger un bloc de N instructions consécutives. Les instructions après un branchement ne sont pas décodées ni exécutées, ce qui entraine une petite baisse de performance. Pour un N petit, la perte de performance est mineure. Mais pour un N large, elle devient suffisamment importante pour que la conception du CPU doit alors être repensée de fond en comble. Les analyses statistiques disent qu'il y a environ un branchement tous les 4 à 6 instructions. Un CPU a donc tendance à décoder des paquets de 3 à 5 instructions consécutives, avant qu'un branchement casse la dynamique. Or, cela pose des problèmes pour utiliser plusieurs décodeurs. L'implémentation basique, qui charge N instructions consécutives pour alimenter N décodeurs, fonctionne donc jusqu'à 4/6 décodeurs. Au-delà de 5 instructions, un CPU superscalaire doit être adapté pour gérer les branchements dans les paquets d'instruction qu'il émet. Le nombre de 5 instructions ne sort donc pas de nulle part. La première solution utilise un cache de µops, avec un ''front-end'' capable de charger/décoder 4/5 instructions à la fois, pas plus. L'idée est que la majorité des instructions proviennent du cache de µops (plus de 80%), ce qui fait qu'on peut émettre environ 10 instructions à partir du cache de µops, pas besoin d'avoir beaucoup de décodeurs. Les performances sont cependant dégradées lors d'un défaut de cache de µops. Les instructions doivent alors être chargées depuis le cache d'instruction, par paquets de 4-5, puis sont décodées par 4-5 décodeurs, ce qui ne permet pas d’alimenter les unités de calcul à plein régime. La solution précédente joue sur la distinction entre pots de décodage et d'émission. Elle permet d'émettre un grand nombre de µops sans pour autant charger un grand nombre d'instructions en même temps. En quelque sorte, ce n'est qu'une méthode de contournement, le CPU superscalaire reste de type étroit. Mais il est aussi possible d'utiliser un '''''front-end large''''', capable de charger une bonne dizaine d'instructions en même temps. La seule difficulté est de tenir compte de la présence de branchements. Pour rappel, avec un ''front-end'' étroit, les instructions situées après le premier branchement pris sont annulées, elles ne sont pas décodées ni exécutées. [[File:Fetch sur un processeur superscalaire avec prediction de branchements.png|centre|vignette|upright=2|Fetch sur un processeur superscalaire avec prédiction de branchements.]] Un ''front-end'' large charge les instructions de destination du branchement et les placent à sa suite. Ils chargent deux blocs à la fois et les fusionnent en un seul qui ne contient que les instructions présumées utiles. [[File:Cache d'instructions autoaligné.png|centre|vignette|upright=2|Cache d'instructions auto-aligné.]] Mais cela demande de charger deux blocs de mémoire en une fois, ce qui demande un cache d'instruction multiports. Il faut aussi ajouter un circuit pour assembler plusieurs morceaux de blocs en un seul : le fusionneur (''merger''). En pratique, cette solution demande d'ajouter un second port de lecture au cache, pour un cout en hardware important, avec un impact négatif sur le temps d'accès au cache. Et il faut rajouter le fusionneur, qui prend un étage de pipeline à lui tout seul. Et encore, cette solution ne gère pas le cas où un bloc contient plusieurs branchements ! [[File:Implémentation d'un cache d'instructions autoaligné.png|centre|vignette|upright=2|Implémentation d'un cache d'instructions auto-aligné.]] Les exemples précédents partent du principe qu'il n'y a qu'un seul branchement dans un bloc. Mais quand les blocs deviennent assez longs, il est fréquent d'avoir plusieurs branchements dans un bloc. Et il est même possible d'avoir plusieurs branchements pris. Dans un bloc de 8-10 instructions, ce n'est pas rare, et les CPU superscalaires à 8-10 voies sont la règle dans les CPU des années 2020. Pour gérer ce cas, il faut ajouter des ports supplémentaires au cache d'instruction et adapter le fusionneur, pour qu'il puisse fusionner trois ou quatre blocs. De plus, il faut utiliser une unité de prédiction de branchement capable de ''prédire plusieurs branchements par cycle''. ===Le décodage superscalaire large=== Le décodage des instructions n'est pas censé dépendre de l'implémentation du chargement. En théorie, une fois plusieurs blocs d'instruction fusionnés, on peut décoder les instructions en parallèle. Cependant, certains processeurs effectuent le décodage en tenant compte de la manière dont les branchements découpent les blocs. Les processeurs Atom d'Intel utilisent une solution de ce type, qui couple prédiction de branchement et décodage parallèle. Ils chargent/décodent des paquets de 3 instructions consécutives, chaque paquet étant séparé du précédent par un branchement. Pour cela, en plus d'avoir un fusionneur adapté, ils regroupent les décodeurs en ''clusters'', chacun regroupant 3 décodeurs, et pouvant décoder trois instructions consécutives. Les micro-architectures Tremont et Crestmont avaient deux ''clusters'' (6 décodeurs au total), la micro-architecture Skymont en intègre trois (9 décodeurs au total). Avec deux ''clusters'', le fonctionnement est le suivant. Si le code est composé d'une suite d'instruction sans branchements, seul le premier ''cluster'' est utilisé, limitant le décodage à trois instructions par cycle. Par contre, si l'une des trois instructions décodée est un branchement, alors le second ''cluster'' décode les trois instructions situées après ce branchement, à partir de l'adresse de destination. Avec un troisième ''cluster'', c'est la même chose, sauf qu'il faut qu'il y ait un branchement dans les trois instructions décodées par le second ''cluster''. Mais comment faire pour savoir quels décodeurs activés, quels ''clusters'' sont actifs ? C'est la prédiction de branchement qui commande. Avec deux ''clusters'', elle détermine si un branchement est présent dans les trois instructions à charger, et quelle est son adresse de destination. Sans branchement, elle prévient les décodeurs et les trois instructions chargées sont décodées dans un ''cluster'' libre. Si elle prédit un branchement, elle charge les instructions adéquates, et active les deux ''clusters''. Avec trois branchements, c'est le même mécanisme, sauf que l'unité de prédiction de branchement doit être capable de prédire deux branchements par cycle. Il faut noter que les décodeurs d'un ''cluster'' sont des décodeurs simples, sans microcode. Sur les processeurs Tremont et Crestmont, le microcode était unique et était partagé entre tous les ''clusters''. Un seul ''cluster'' pouvait décoder une instruction complexe avec le microcode, l'autre devait se limiter à des instructions simples uniquement. Depuis Skymont, le microcode est partiellement dupliqué, avec un microcode par ''cluster''. Le cout en hardware peut paraitre prohibitif, pour un gain en performance faible. Sauf que c'est oublier que ce sont des processeurs Atom, sur lesquels beaucoup d'instructions sont microcodées. Les processeurs Atom ont un budget en transistor limité, ce qui fait qu'il est préférable pour eux d'implémenter des instructions en microcode que d'utiliser des unités de calcul dédiées ou des circuits de décodage complexes. Pour limiter la casse, le microcode n'est que partiellement dupliqué. Seul le microcode pour les instructions fréquente est dupliqué. Il y a un microcode secondaire pour certaines instructions, qui est lui unique et partagé par tous les ''clusters''. ===Le contournement sur les processeurs superscalaires larges=== Pour rappel, la technique du contournement (''register bypass'') permet au résultat d'une instruction d'être immédiatement utilisable en sortie de l'ALU, avant même d'être enregistré dans les registres. Implémenter la technique du contournement demande d'utiliser des multiplexeurs pour relier la sortie de l'unité de calcul sur son entrée si besoin. Il faut aussi des comparateurs pour détecter des dépendances de données. [[File:Pipeline Bypass.png|centre|vignette|upright=1|Pipeline Bypass]] La sortie de chaque ALU doit être reliée aux entrées de toutes les autres, avec les comparateurs qui vont avec. Sur les processeurs ayant plusieurs d'unités de calculs, cela demande beaucoup d’interconnexions et de comparateurs. Pour N unités de calcul, cela demande 2 * N² interconnexions, implémentées avec 2N multiplexeurs de N entrées chacun. Si c'est faisable pour 2 ou 3 ALUs, la solution est impraticable au-delà. Non seulement ce serait un enfer à câbler, mais les fils seraient tellement longs que la fréquence du processeur en prendrait un coup. Notez que cela vaut pour les processeurs superscalaires, mais aussi pour tout processeur avec beaucoup d'unités de calcul. En théorie, un CPU à exécution dans le désordre, non-superscalaire, mais avec beaucoup d'unités de calcul, fait face au même problème. En pratique, avoir une dizaine d'ALU implique processeur superscalaire à exécution dans le désordre. D'où le fait qu'on parle du problème maintenant. La seule solution praticable est de ne pas relier toutes les unités de calcul ensemble. À la place, on préfère regrouper les unités de calcul dans différents '''agglomérats''' ('''cluster'''). Le contournement est alors possible entre les unités d'un même agglomérat, mais pas entre agglomérats différents. Évidemment, certaines possibilités de contournement sont perdues, mais c'est compensé par un gain en fréquence et un cout en circuit/fils réduit. C'est un bon compromis, ce qui explique que presque tous les processeurs modernes l'utilisent. Les rares exceptions sont les processeurs POWER 4 et POWER 5, qui ont préféré se passer de contournement pour garder un processeur très simple et une fréquence élevée. ===Le banc de registre des processeurs superscalaires larges=== Pour émettre deux opérations entières simultanément, il faut lire deux fois plus d'opérandes. Et il faut aussi écrire deux résultats dans les registres, en même temps. Pour cela, on double les ports du banc de registre. En général, il faut au maximum 2 ports de lecture et un port d'écriture par unité de calcul. Le problème, c'est que plus un banc de registres a de ports, plus il utilise de circuits, est compliqué à concevoir, consomme de courant et chauffe. Avec plusieurs dizaines d'unités de calcul différentes, ajouter autant de ports impacterait la fréquence du banc de registres et réduirait les performances, voire est totalement impraticable à cause des contraintes thermiques. Les processeurs superscalaires larges doivent donc trouver des solutions. La première solution utilise un banc de registre unique, mais n'utilise pas autant de ports que le pire des cas le demanderait. Le processeur détecte quand il n'y a pas assez de ports pour servir toutes les micro-opérations. Quand ça arrive, l'unité d'émission émet assez de micro-opérations pour exploiter les ports disponibles, mais met en attente les micro-opérations en trop, le temps que les ports se libèrent. Pour cela, le banc de registre est géré par un circuit d'arbitrage spécialisé, intégré à l'unité d'émission, l’'''arbitre du banc de registres''' (''register file arbiter''). Une autre solution est de dupliquer le banc de registres en plusieurs exemplaires qui contiennent exactement les mêmes données, mais avec moins de ports de lecture/écriture. Un exemple est celui des processeurs POWER, Alpha 21264 et Alpha 21464. Sur ces processeurs, le banc de registre est dupliqué en plusieurs exemplaires, qui contiennent exactement les mêmes données. Les écritures dans les registres sont envoyées à tous les bancs de registres, afin de garantir que leur contenu est identique. Chaque exemplaire a exactement deux ports de lecture. La conception du processeur est simplifiée, que ce soit au niveau du câblage, que de la conception des bancs de registres. Une dernière solution découpe le banc de registres en plusieurs bancs de registres plus petits, avec un réseau d'interconnexions pour échanger des données entre bancs de registres. Dans la plupart des cas, cette séparation est invisible du point de vue de l'assembleur et du langage machine. Le processeur se charge de transférer les données entre bancs de registres suivant les besoins. Sur d'autres processeurs, les transferts de données se font via une instruction spéciale, souvent appelée ''COPY''. [[File:Banc de registres distribué.png|centre|vignette|upright=2|Banc de registres distribué.]] Sur de certains processeurs, un branchement est exécuté par une unité de calcul spécialisée. Or les registres à lire pour déterminer l'adresse de destination du branchement ne sont pas forcément dans le même agglomérat que cette unité de calcul. Pour éviter cela, certains processeurs disposent d'une unité de calcul des branchements dans chaque agglomérat. Dans les cas où plusieurs unités veulent modifier le ''program counter'' en même temps, un système de contrôle général décide quelle unité a la priorité sur les autres. Mais d'autres processeurs fonctionnent autrement : seul un agglomérat possède une unité de branchement, qui peut recevoir des résultats de tests de toutes les autres unités de calcul, quel que soit l’agglomérat. ==La macro-fusion : une optimisation du décodage parallèle== Les processeurs superscalaires supportent la technique dite de '''macro-fusion''', qui permet de fusionner deux-trois instructions consécutives en une seule micro-opération. Par exemple, il est possible fusionner une instruction de test et une instruction de saut en une seule micro-opération de branchement. Il s'agit là de l'utilisation la plus importante de la macro-fusion sur les processeurs x86 modernes. La macro-fusion est utilisée pour fusionner un test et un saut en un branchement unique. L'optimisation est présente sur la plupart des CPU superscalaires modernes. Les CPU x86 l'implémentent, mais aussi quelques CPU ARM et les processeurs Apple de série M (M1 à M5, et potentiellement plus quand ils seront sortis). La fusion des instructions se fait lors du décodage des instructions, grâce à la coopération des décodeurs. Elle marche systématiquement si l'instruction de test et le saut sont consécutives, qu'elles forment bien une paire d'instruction. Par contre, insérer une instruction entre les deux empêche la macro-fusion de faire son travail, sauf si l'instruction insérée est un NOP. Les processeurs ARM64 avaient une forme spécifique de macro-fusion, utilisée pour un cas très précis : charger une constante immédiate de 32 ou 64 bits, dans un registre 64 bits. Les CPU ARM 64 bits utilisent des instructions codées sur 32 bits, qui ne peuvent encoder que des constantes immédiates de 16 bits. Pour charger une constante de 32/64 bits, il faut s'y prendre en plusieurs fois. Une première instruction MOVZ charge les 16 premiers bits, puis 2 à 4 instructions MOVK remplissent le reste. MOVZ met à zéro les 48 bits de poids fort, MOVK permet de sélectionner le doublet à modifier dans le registre (on modifie 16 bits, dont on choisit la place, le reste du registre est laissé tel quel). ARM permet de fusionner cette suite d'instructions en une seule µop MOV avec une constante immédiate. On parle alors de '''''MOVZ + MOVK fusion'''''. Une dernière utilisation, [https://www2.eecs.berkeley.edu/Pubs/TechRpts/2016/EECS-2016-130.pdf proposées pour le jeu d'instruction RISC-V], fusionnait un calcul d'adresse et une instruction LOAD/STORE en une seule instruction d'accès mémoire. Elle a été envisagée à une époque où le jeu d'instruction RISC-V ne gérait pas les modes d'adressage base + indice. L'idée était d'avoir une unité mémoire qui gérait le mode d'adressage base + indice et de fusionner des instructions pour l'exploiter. Mais de nos jours, le jeu d'instruction RISC-V intègre le mode d'adressage base + indice, ce qui fait que la macro-fusion n'a aucun intérêt pour ce cas précis. La macro-fusion a de nombreuses limitations en pratique. En théorie, on pourrait l'utiliser au-delà des deux situations mentionnées plus haut. Par exemple, pour fusionner une addition et une multiplication en une seule instruction MAD. Mais l'intérêt est assez faible. Notons que la macro-fusion ne permet que de fusionner des paires d'instructions. Aller au-delà demanderait un cout en circuit trop important. Déjà que tester deux paires utilise pas mal de comparateurs... ==Le ''stack engine'' : une optimisation de la pile d'appel== Les processeurs modernes intègrent une optimisation liée au pointeur de pile. Pour rappel, sur les architectures modernes, le pointeur de pile est un registre utilisé pour gérer la pile d'appel, précisément pour savoir où se trouve le sommet de la pile. Il est manipulé par les instructions CALL, RET, PUSH et POP, mais aussi par les instructions d'addition/soustraction pour gérer des cadres de pile de taille variable. De plus, il peut servir d'opérande pour des instructions LOAD/STORE, afin de lire/écrire des variables locales, les arguments d'une fonction, et autres. C'est donc un registre général adressable, intégré au banc de registre, altéré par l'unité de calcul entière. L'incrémentation/décrémentation du pointeur de pile passe donc par l'unité de calcul, lors des instructions CALL, RET, PUSH et POP. Mais, l'optimisation que nous allons voir permet d'incrémenter/décrémenter le pointeur de pile sans passer par l'ALU, ou presque. L'idée est de s'inspirer des architectures avec une pile d'adresse de retour, qui intègrent le pointeur de pile dans le séquenceur et l'incrémentent avec un incrémenteur dédié. ===Le ''stack engine''=== L'optimisation que nous allons voir utilise un '''''stack engine''''' intégré à l'unité de contrôle, au séquenceur. Le processeur contient toujours un pointeur de pile dans le banc de registre, cela ne change pas. Par contre, il n'est pas incrémenté/décrémenté à chaque instruction CALL, RET, PUSH, POP. Un compteur intégré au séquenceur est incrémenté à la place, nous l’appellerons le '''compteur delta'''. La vraie valeur du pointeur de pile s'obtient en additionnant le compteur delta avec le registre dans le banc de registre. Précisons que si le compteur delta vaut zéro, la vraie valeur est dans le banc de registre et peut s'utiliser telle quelle. Lorsqu'une instruction ADD/SUB/LOAD/STORE utilise le pointeur de pile comme opérande, elle a besoin de la vraie valeur. Si elle n'est pas dans le banc de registre, le séquenceur déclenche l'addition compteur-registre pour calculer la vraie valeur. Finalement, le banc de registre contient alors la bonne valeur et l'instruction peut s'exécuter sans encombre. L'idée est que le pointeur de pile est généralement altéré par une série d'instruction PUSH/POP consécutives, puis des instructions LOAD/STORE/ADD/SUB utilisent le pointeur de pile final comme opérande. En clair, une bonne partie des incrémentations/décrémentation est accumulée dans le compteur delta, puis la vraie valeur est calculée une fois pour toutes et est utilisée comme opérande. On accumule un delta dans le compteur delta, et ce compteur delta est additionné quand nécessaire. Précisons que le compteur delta est placé juste après le décodeur d'instruction, avant même le cache de micro-opération, l'unité de renommage et l'unité d'émission. Ainsi, les incrémentations/décrémentations du pointeur de pile disparaissent dès l'unité de décodage. Elles ne prennent pas de place dans le cache de micro-opération, ni dans la fenêtre d'instruction, ni dans la suite du pipeline. De nombreuses ressources sont économisées dans le ''front-end''. Mais utiliser un ''stack engine'' a aussi de nombreux avantages au niveau du chemin de données/''back-end''. Déjà, sur les processeurs à exécution dans le désordre, cela libère une unité de calcul qui peut être utilisée pour faire d'autres calculs. Ensuite, le compteur delta mémorise un delta assez court, de 8 bits sur le processeur Pentium M, un peu plus pour les suivants. L'incrémentation se fait donc via un incrémenteur 8 bits, pas une grosse ALU 32/64 bits. Il y a un gain en termes de consommation d'énergie, un incrémenteur 8 bits étant moins gourmand qu'une grosse ALU 32/64 bits. ===Les points de synchronisation du delta=== La technique ne fonctionne que si la vraie valeur du pointeur de pile est calculée au bon moment, avant chaque utilisation pertinente. Il y a donc des '''points de synchronisation''' qui forcent le calcul de la vraie valeur. Plus haut, nous avions dit que c'était à chaque fois qu'une instruction adresse le pointeur de pile explicitement, qui l'utilise comme opérande. Les instructions CALL, RET, PUSH et POP ne sont pas concernées par elles utilisent le pointeur de pile de manière implicite et ne font que l'incrémenter/décrémenter. Mais dans les faits, c'est plus compliqué. D'autres situations peuvent forcer une synchronisation, notamment un débordement du compteur delta. Le compteur delta est généralement un compteur de 8 bits, ce qui fait qu'il peut déborder. En cas de débordement du compteur, le séquenceur déclenche le calcul de la vraie valeur, puis réinitialise le compteur delta. La vraie valeur est donc calculée en avance dans ce cas précis. Précisons qu'un compteur delta de 8 bits permet de gérer environ 30 instructions PUSH/POP consécutives, ce qui rend les débordements de compteur delta assez peu fréquent. À noter que si le compteur delta vaut zéro, il n'y a pas besoin de calculer la vraie valeur, le séquenceur prend cette situation en compte. Un autre point de synchronisation est celui des interruptions et exceptions matérielles. Il faut que le compteur delta soit sauvegardé lors d'une interruption et restauré quand elle se termine. Idem lors d'une commutation de contexte, quand on passe d'un programme à un autre. Pour cela, le processeur peut déclencher le calcul de la vraie valeur lors d'une interruption, avant de sauvegarder les registres. Pour cela, le processeur intègre un mécanisme de sauvegarde automatique, qui mémorise la valeur de ce compteur dans le tampon de réordonnancement, pour forcer le calcul de la vraie valeur en cas de problème. La technique du ''stack engine'' est apparue sur les processeurs Pentium M d'Intel et les processeurs K10 d'AMD, et a été conservée sur tous les modèles ultérieurs. L'implémentation est cependant différente selon les processeurs, bien qu'on n'en connaisse pas les détails et que l'on doive se contenter des résultats de micro-benchmarks et des détails fournit par Intel et AMD. Selon certaines sources, dont les manuels d'optimisation d'Agner Fog, les processeurs AMD auraient moins de points de synchronisation que les processeurs Intel. De plus, leur ''stack engine'' serait placé plus loin que prévu dans le pipeline, après la file de micro-opération. ==La micro-fusion et la délamination== La '''micro-fusion''' est une optimisation qui retarde le décodage réel de certaines instructions assez loin dans le pipeline. Elle concerne surtout les instructions ''load-op'', mais pas que. D'autres instructions mémoire peuvent subir cette optimisation. L'essentiel est que, sans micro-fusion, ces instructions sont décodées en plusieurs micro-opérations. La micro-fusion retarde le décodage final de ces instructions, qui se fait assez tard dans le pipeline. Pour comprendre l'intérêt, nous allons devoir faire quelques explications. L'optimisation en question fait le décodage en deux temps. Prenons l'exemple d'une opération ''load-op'', qui est censée être décodée en deux micro-opérations : une lecture, puis une opération. Le décodage produit une '''macro-opération''' (terme d'Intel), qui est propagée dans le pipeline, pour être scindée en deux micro-opérations un peu avant les ALU et l'unité mémoire. La macro-opération est techniquement une micro-opération comme une autre, sauf que ce n'est pas la micro-opération finale. L'unité d'émission découpe cette macro-opération en deux micro-opérations, qui sont émises l'une après l'autre. Pour résumer, les instructions ''load-op'' ne sont complétement découpées en micro-opération lors de l'émission, pas lors du décodage. L'optimisation impacte ce qui se situe entre le décodeur d'instruction et l'unité d'émission : l'unité de renommage, la file de micro-opérations, le ''Loop Stream Detector'' et/ou le cache de micro-opération. L'intérêt de l'optimisation est que l'on fusionne deux micro-opérations en une seule macro-opération. Le cache de micro-opération et la file de micro-opération peuvent mémoriser plus de micro-opérations, vu que certaines sont fusionnées. Quant à l'unité de renommage, elle renomme une seule macro-opération au lieu de renommer deux micro-opérations, ce qui augmente le débit du renommage de registre. Par contre, cela complique un peu le travail de l'unité d'émission, qui doit découper les instructions ''load-op'' en deux et émettre les micro-opérations l'une après l'autre. Il faut noter qu'il n'y a pas de fusion proprement dite. Le décodeur ne décode pas deux micro-opérations séparées, avant de les fusionner. En réalité, il génère directement une macro-opération, qui correspond à deux micro-opérations, que ce soit plus tard dans le pipeline ou sur une autre architecture. D'ailleurs, la micro-fusion a lieu à l'intérieur d'une instruction, qui est censée être traduite en plusieurs micro-opérations. Et encore : pas toutes. La conséquence est que la technique n'a de sens que sur les processeurs CISC, avec des instructions assez complexes pour être décodées en plusieurs micro-opérations. En pratique, seuls les CPU x86 sont concernés. ===Les domaines de fusion=== La micro-fusion n'est pas spécifique aux processeurs superscalaires. En théorie, on peut l'implémenter sur un processeur non-superscalaire à émission dans l'ordre. Cependant, je ne connais aucun processeur à émission dans l'ordre qui implémente la micro-fusion, même si son implémentation est en théorie possible sur de tels processeurs. La micro-fusion est implémentée différemment selon que l'émission se fasse dans l'ordre, dans le désordre, avec une fenêtre d'instruction, avec des stations de réservation, etc. J'ai dit plus haut que les micro-opérations sont générées lors de l'émission, mais le moment exact varie suivant le processeur. Pour simplifier le tout, nous parlerons de '''scission''' quand le processeur traduit une macro-opération en deux/trois micro-opérations. Le moment où a lieu la scission est théoriquement dans l'unité d'émission, mais celle-ci prend plusieurs formes selon que le processeur utilise l'émission dans l'ordre, dans le désordre, avec une fenêtre d'instruction, avec des stations de réservation, etc. Les processeurs à exécution dans l'ordre sont le cas le plus simple. L'unité d'émission est alors en charge du découpage des macro-opérations en micro-opérations. La file de micro-opération ou le cache de micro-opérations mémorisent des macro-opérations. À l'opposé, le tampon de ré-ordonnancement mémorise lui des micro-opérations. Le reste du processeur n'est pas affecté. Sur les processeurs à exécution dans le désordre, les différences sont nombreuses. Le tampon de ré-ordonnancement peut mémoriser soit les micro-opérations finales, soit des macro-opérations. Il en est de même avec la fenêtre d'instruction et/ou les stations de réservation, qu'elles soient centralisées ou décentralisées. Tout dépend de comment se fait l'implémentation. Une implémentation simple effectue la scission des macro-opérations dans l'unité d'émission, comme sur les processeurs à exécution dans l'ordre. Sur de nombreux processeurs, l'unité d'émission et de renommage sont simplement fusionnées, ce qui fait que la scission a lieu juste après le renommage de registres. En conséquence, le tampon de re-ordonnancement et les fenêtres d'instruction ne voient que des micro-opérations. Les macro-opérations disparaissent juste avant. Cette implémentation est préférée avec une fenêtre d'instruction centralisée, car on voit mal comment scinder une macro-opération juste avant l'ALU. Une autre implémentation fait la scission en sortie de la fenêtre d'instruction ou des stations de réservation, qu'elles soient centralisées ou décentralisées. Dans ce cas, le tampon de re-ordonnancement fait de même. L'avantage est que le tampon de ré-ordonnancement stocke des macro-opérations, ce qui augmente sa taille effective. Par exemple, il utilise une seule entrée pour une instruction ''load-op'', au lieu de deux sans micro-fusion de ce type. ===La délamination=== La scission peut parfois avoir lieu plus tôt, dans des circonstances très précises. Concrètement, cela ne concerne que quelques micro-architectures d'Intel, pour des instructions très précises. La raison est les limitations du format des micro-opérations, pas une histoire d'optimisation. Le processeur aimerait pouvoir faire la micro-fusion, mais ne peut pas à cause de ces limitations. Une telle situation cause alors une '''délamination''' : les micro-opérations sont scindées précocement. Par exemple, sur la micro-architecture SandyBridge d'Intel, les opérations ''load-op'' sont fusionnées, sauf si elles utilisent l'adressage indicé. Les micro-opérations de ce processeur ne peuvent avoir que deux opérandes. Trois opérandes est de trop, et les opérations ''load-op'' en adressage indicé dépassent cette limite. Dans ce cas, les micro-opérations sont fusionnées en sortie du décodeur, mais sont scindés avant d’entrer dans la file de micro-opération. L'avantage n'est pas évident, vu que l'unité de renommage suit de près le décodeur d'instruction. Il est que le cache de micro-opérations mémorise des macro-opérations, pas deux micro-opérations. ===La micro-fusion des processeurs Intel et AMD=== La micro-fusion est apparue sur le Pentium M d'Intel, et a été conservée telle quelle sur les processeurs Intel suivants. Les processeurs AMD supportent la micro-fusion depuis au moins la micro-architecture AMD K8. Mieux que ça, la délamination n'était pas utilisée car les micro-opérations acceptaient plus de 2 opérandes. Que ce soit chez AMD ou Intel, elle concerne deux types d'instructions : les écritures mémoire et les instructions ''load-op''. Sur le Pentium M, il y avait quelques limitations, l'optimisation se limitant par exemple aux registres entiers et flottants, mais pas aux registres MMX, mais pas les registres SSE (nous verrons ces registres dans quelques chapitres). Mais les processeurs modernes n'ont plus cette limitation. La micro-fusion des CPU Intel concerne deux types d'instructions : les écritures mémoire et les instructions ''load-op''. Pour les écritures, la situation est plus complexe que pour les instructions ''load-op''. Sur les processeurs Intel précédents, les écritures étaient réalisées dans deux unités séparées : le calcul d'adresse dans une unité de calcul, l'écriture proprement dite dans l'unité mémoire. Elles prenaient donc deux micro-opérations. Le processeur a changé ses unités mémoire, ce qui fait qu'elle pouvait faire les deux en une seule micro-opération. Il s'agissait alors d'une optimisation du chemin de données, plus que de la micro-fusion proprement dite, mais je la mentionne ici malgré tout. La micro-fusion sur ce processeur avait un avantage en plus des avantages usuels : elle utilisait mieux les décodeurs. Les Pentium M avaient trois décodeurs pour décoder plusieurs instructions en même temps, chose qu'on détaillera dans le chapitre sur les processeurs superscalaires. Un seul décodeur pouvait décoder toutes les instructions, les deux autres ne peuvent décoder que les instructions simples, qui se décodent en une seule micro-opération. Avec la micro-fusion, les trois décodeurs peuvent gérer les écritures mémoire et les opérations ''load-op'', vu qu'elles deviennent des instructions simples. Sans cela, elles auraient été limitées au premier décodeur. <noinclude> {{NavChapitre | book=Fonctionnement d'un ordinateur | prev=Le parallélisme mémoire | prevText=Le parallélisme mémoire | next=Exemples de microarchitectures CPU : le cas du x86 | nextText=Exemples de microarchitectures CPU : le cas du x86 }} </noinclude> cq0czn0guis7sohurdy0g714m2293tw 773246 773245 2026-09-26T15:07:13Z Mewtow 31375 /* Les CPU superscalaires à fenêtres d'instruction décentralisées : la largeur de dispatch et de schedule */ 773246 wikitext text/x-wiki Les processeurs vus auparavant ne peuvent émettre au maximum qu'une instruction par cycle d'horloge. Ils peuvent avoir plusieurs instructions qui s'exécutent en même temps, dans des unités de calcul séparées. C'est le cas dès qu'une instruction multi-cycles s'exécute. Par contre, ils ne peuvent démarrer qu'une seule instruction par cycle. Et quand on court après la performance, ce n'est pas assez ! Les concepteurs de processeurs ont inventés des processeurs qui émettent plusieurs instructions par cycle : les '''processeurs à émissions multiples'''. Un processeur à émission multiple charge plusieurs instructions en même temps, les décode en parallèle, puis les émet en même temps sur des unités de calculs séparées. Pour cela, il faut gérer les dépendances entre instructions, répartir les instructions sur différentes unités de calcul, et cela n'est pas une mince affaire. En général, pour un processeur à émission multiple capable d'émettre N instructions en parallèle, on parle de '''processeur à N voies''', ou encore un ''processeur à N pipelines''. [[File:Superscalarpipeline.svg|centre|vignette|upright=1.5|Pipeline RISC classique à cinq étages sur un processeur superscalaire. On voit bien que plusieurs instructions sont chargées en même temps.]] Les processeurs à émission multiple sont de deux types : les processeurs VLIW et les '''processeurs superscalaires'''. Nous mettons les processeurs VLIW de côté en attendant le prochain chapitre. La raison est qu'ils ont un jeu d'instruction spécialisé qui expose le parallélisme d'instruction directement au niveau du jeu d'instructions, là où les processeurs superscalaires restent des processeurs au jeu d'instruction normal. La différence principale entre processeur VLIW et superscalaire est qu'un processeur superscalaire répartit les instructions sur les unités de calcul à l’exécution, là où un processeur VLIW délègue cette tâche au compilateur. ==Le nombre de voies d'un processeur superscalaire== Les processeurs superscalaires les plus simples ne permettent que d'émettre deux micro-opérations à la fois, d'où leur nom de processeur ''dual issue'', ce qui se traduit en '''processeur à double émission'''. Les processeurs triple émission permettent eux d’émettre trois instructions à la fois. A l'opposé, les CPU superscalaires les plus puissants peuvent émettre jusqu'à 8-10 instructions simultanément. En 2026, les CPU superscalaires peuvent monter jusqu’à 10 µops émises en même temps, ce qui est énorme ! Et cela nous amène à faire une distinction entre les processeurs superscalaires dits '''étroits''' et ceux dits '''larges'''. Dans ce wikilivre, on part du principe que les processeurs superscalaires étroits émettent jusqu'à 4-5 µops à la fois, alors que les larges peuvent en émettre plus, l'intervalle entre les deux est une zone grise. Le nombre exact n'est pas fixé dans le marbre, la distinction est volontairement vague. Nous parlerons de la '''largeur d'un CPU superscalaire''' pour parler du nombre de voies, du nombre de µops émises en même temps. Plus un CPU superscalaire est large, plus il est performant ''toute chose égale par ailleurs''. Mais dans les faits, vous pouvez oublier la partie en italique. Élargir un CPU superscalaire demande de rajouter beaucoup de circuits. Et ce cout en circuit aura un impact sur la performance. A budget en transistors limité, il y a un compromis entre élargir un CPU et rajouter du cache ou la taille des fenêtres d'instructions/ROB et autres. ===Les contraintes d’appariement=== Prenons un processeur double émission, capable d'émettre deux instructions simultanément. Émettre deux instructions en même temps n'est pas toujours possible. Par exemple, si deux instructions sont dépendantes, on ne peut pas les émettre en même temps. Mais oublions un instant ces histoires de dépendances de données, et partons du principe que le CPU ait affaire à deux instructions indépendantes, une paire idéale. Un CPU double émission "parfait" pourrait exécuter n'importe quelle paire d'instruction. Par exemple, il pourrait émettre deux multiplications consécutives, deux lectures consécutives, une lecture et une écriture en même temps, etc. En pratique, aucun processeur ne permet cela. Implémenter un CPU superscalaire "parfait" aurait un cout en circuit beaucoup trop conséquent. Par exemple, pour émettre deux multiplications en même temps, il faudrait deux multiplieurs séparés. Idem pour exécuter deux accès mémoire simultané : cela demande d'avoir deux unités mémoire et un cache multiport. Mais en pratique, aucun CPU superscalaire ne fait cela. La totalité des CPU double émission se débrouille avec un seul circuit multiplieur et une seule unité mémoire. Et cela entraine l'apparition de dépendances structurelles, qui font que certaines paires d'instructions sont impossibles. Par exemple, impossible d'émettre deux multiplications en même temps avec un seul circuit multiplieur. Mais émettre une multiplication en parallèle d'une addition est parfaitement possible. Il y a donc des paires d'instructions autorisées et des paires interdites. Et la logique est la même sur les CPU à triple ou quadruple émission, et au-delà. : des triplets ou quadruplet d'instructions sont impossibles à émettre simultanément, alors que d'autres le sont. Pour le dire autrement, il y a des '''contraintes d'appariement''' sur les instructions à émettre en même temps. ===L'impact des contraintes d’appariement sur la performance=== Ces contraintes d’appariement ont un impact sur les performance, mais celui-ci peut être grandement mitigé. Les programmes informatiques utilisent en pratique beaucoup plus d'opérations entières de base que de multiplication, branchements et accès mémoire. Il y a environ 5 opérations entières de base pour une multiplication entière. En pratique, il est rare de devoir exécuter deux multiplications à la fois. Idem pour les décalages et rotations, qui sont assez éloignés les uns des autres. Deux branchements consécutifs sont quant à eux plus fréquents. Deux accès mémoire consécutifs est assez rare, mais ils restent assez proches les uns des autres malgré tout. La conséquence est que certaines paires d'instructions sont privilégiées par rapport au reste. Les paires regroupant deux calculs basiques (additions, soustractions, ...) sont systématiquement supportées, car dupliquer des ALU entières est facile. Les paires regroupant un calcul et une autre instruction sont elles aussi systématiquement supportées. Par contre, les paires avec deux branchements, deux accès mémoire, deux décalages/rotations, ou deux multiplications/divisions sont fréquemment interdites. Seuls les CPU superscalaires larges peuvent les permettre, et encore : sous conditions. Le résultat est que le processeur peut se débrouiller avec une seule unité mémoire et une unité de branchement. ===Terminologie pour la suite du chapitre=== Décrire les contraintes d’appariement demande de dire quelles combinaisons d’instructions sont autorisées ou interdites. Et pour cela, nous allons introduire les abréviations suivantes : * INT est un raccourcis pour parler d'une opération entière simple, qu'une ALU entière peut exécuter. * MUL correspond à une opération entière complexe, typiquement une multiplication, éventuellement à une division. * FLOAT désigne une opération flottante, peut importe laquelle. * MEM désigne un accès mémoire, à savoir une instruction ou µop mémoire. * BRANCH désigne une instruction de branchement. Cette terminologie permettra de définir des paires d'instructions, qu'elles soient possibles ou interdites. Par exemple, l'expression INT + FLOAT désigne une paire d'instruction regroupant une instruction entière et une instruction flottante, peu importe l'ordre des deux instructions. Il en sera de même pour les triplets, quadruplets, ou plus. Par exemple, l'expression INT + INT + FLOAT + MUL désigne un quadruplet d'instructions regroupant : deux instructions entières, une instruction flottante, et une multiplication entière. J'utiliserais aussi des parenthèses pour regrouper certaines combinaisons. Par exemple, l'expression INT + (MUL /FLOAT) veut parler d'une paire d'instruction où la première est une opération entière basique, la seconde est : soit une multiplication, soit une opération flottante. ==Le ''front-end'' des processeurs superscalaires== Un processeur superscalaire doit charger, décoder, et exécuter plusieurs instructions par cycle. Par exemple, un processeur triple émission doit charger 3 instructions lors d'un premier cycle, puis les décoder au cycle suivant, et les exécuter au cycle suivant. Il travaille donc pas paquets de 3 instructions, dans cet exemple. Intuitivement, charger, décoder et exécuter plusieurs instructions à la fois demande de dupliquer des circuits. Par exemple, il faut dupliquer les décodeurs pour décoder plusieurs instructions. Les unités de calcul sont aussi dupliquées, sauf sur certaines processeurs qui arrivent à ruser, mais laissons cela de côté pour plus tard. Mais d'autres circuits sont simplement modifiés, comme l'unité de chargement ou l'unité d'émission. Voyons ce qu'il en est dans le détail. {|class="wikitable" |- ! rowspan="2" | Processeur sans émission multiple | rowspan="2" | Chargement | rowspan="2" | Décodage | rowspan="2" | Renommage | rowspan="2" | Émission | Exécution / ALU | rowspan="2" | ''Commit''/ROB |- | Exécution / ALU |- ! rowspan="4" | Processeur superscalaire | rowspan="4" | Chargement | rowspan="2" | Décodage | rowspan="4" | Renommage | rowspan="4" | Émission | Exécution / ALU | rowspan="4" | ''Commit''/ROB |- | Exécution / ALU |- | rowspan="2" | Décodage | Exécution / ALU |- | Exécution / ALU |} Nous allons d'abord voir ce qu'il en est pour le ''front-end'', à savoir la partie du CPU qui s'occupe de charger et décoder les instructions. Elle regroupe l'unité de chargement, les décodeurs, l'unité de prédiction de branchement, la file de µops, la file d'instruction, et divers caches. En soi, les changements portent surtout sur l'unité de chargement et les décodeurs. ===Le chargement de plusieurs instructions consécutives=== Un processeur superscalaire charge plusieurs instructions en même temps, ''en tenant compte de la présence de branchements''. La partie en italique est importante et elle a une conséquence très forte : la manière de faire est fortement différente entre un CPU superscalaire étroit et large. Un CPU superscalaire étroit fait au plus simple : il charge un bloc de 2, 3, 4 instructions consécutives en mémoire RAM. Il faut alors doubler/tripler/quadrupler la taille du bus connecté au cache d'instruction. Par exemple, pour charger quatre instructions de 32 bits chacune, il suffit de lire un bloc de 128 bits dans le cache d'instruction. Un bloc de 8, 16, 32 octets est donc lu depuis le cache et est ensuite découpé en instructions, envoyées chacun à un décodeur. Charger plusieurs instructions consécutives ne marche bien qu'en absence de branchements. La présence de branchements casse cette dynamique, et il faut gérer leur présence. * Sans prédiction de branchement, toutes les instructions d'un bloc sont décodées et exécutées en même temps, même s'il y a un branchement dans le tas. * Avec prédiction de branchement, les instructions situées après le branchement ne sont pas décodées ni exécutées. L'unité de chargement coupe le bloc chargé au niveau du premier branchement pris, remplit les vides avec des NOP, avant d'envoyer le tout à l'unité de décodage. Les instructions à l'adresse de destination seront chargées au cycle suivant, en chargeant un nouveau bloc. [[File:Fetch sur un processeur superscalaire avec prediction de branchements.png|centre|vignette|upright=2|Fetch sur un processeur superscalaire avec prédiction de branchements.]] Un autre défaut est que sur le CPU charge un bloc de 128 bits à la fois, les instructions doivent être alignées sur 128 bits. Et on peut généraliser à n'importe quelle taille de bloc. Si les instructions ne sont pas alignées, il faut gérer la situation. Il arrive qu'une instruction soit à cheval sur deux blocs, avec un morceau dans le premier bloc, un autre dans le second. Dans ce cas, l'instruction est chargée en deux fois, et des circuits doivent gérer la situation pour recoller les morceaux. Sur les CPU avec des instructions de taille fixe, le problème ne survient que rarement. Mais il est fréquent avec des instructions de taille variable. Un autre défaut est lié aux branchements. Imaginez qu'un branchement s'exécute et branche vers une instruction de destination. Rien n'indique que l'instruction de destination sera au tout début d'un bloc. Si ça se trouve, elle sera en troisième position d'un bloc de 4 instructions. Une solution simple charge le bloc en respectant l'alignement, et les deux premières instructions du bloc ne sont pas à exécuter. Il y a donc une petite perte de performance. Certains CPU, comme l'IBM RS/6000, ont la machinerie charger un bloc de N instructions même s'il n'est pas aligné. La machinerie en question se trouve dans le cache d'instruction et est tout sauf simple à expliquer,ce qui fait que je me permets de la passer sous silence. Un détail, cependant : elle ne permet pas de charger des blocs qui sont à cheval sur deux lignes de cache. ===Le décodage parallèle des instructions=== Pour le décodage des instructions, plusieurs instructions sont décodées en même temps. On parle alors de '''décodage parallèle'''. Pour ce faire, un processeur superscalaire contient plusieurs décodeurs, chacun pouvant décoder une instruction en parallèle des autres. Prenons par exemple un processeur superscalaire à 4 voies : le décodage se fera dans quatre décodeurs séparés, qui fonctionneront en parallèle. Cependant, cette implémentation ne marche bien que pour les processeurs RISC, les processeurs CISC devant faire avec un microcode gourmand en circuits. Les processeurs CISC utilisent des décodeurs hybrides, avec un microcode qui complémente un décodeur câblé. Dupliquer le microcode aurait un cout en transistors trop important, ce qui fait que seuls les décodeurs câblés sont dupliqués. Les CPU CISC superscalaires disposent donc de plusieurs décodeurs simples, capables de décoder les instructions les plus courantes, avec un seul microcode. La conséquence est qu'il n'est pas possible de décoder deux instructions microcodées en même temps. Par contre, il reste possible de décoder plusieurs instructions non-microcodées ou une instruction microcodée couplée à une instruction non-microcodée. Vu qu'il est rare que deux instructions microcodées se suivent dans un programme, le cout en performance est extrêmement mineur. Il faut noter qu'une instruction correspond à une ou plusieurs µops. Il est donc possible de charger 5 instructions et de se retrouver avec 8 µops en sortie des décodeurs. Et c'est la source d'une distinction très importante. Plus haut, j'ai parlé de processeurs superscalaires à N voies. La terminologie peut avoir deux interprétations. La première est que le processeur charge/décode N instructions à la fois. Une seconde est qu'il est capable d'exécuter/émettre N µops en même temps. Les deux ne sont pas équivalentes, vu qu'un processeur superscalaire peut, par exemple, charger/décoder N instructions et émettre/exécuter N+2 µops. La distinction est particulièrement importante sur les processeurs superscalaires larges. Certains d'entre eux sont capables de charger 5 instructions et d'en émettre le double ! Un exemple classique était les processeurs Athlon d'AMD, qui pouvaient décoder deux instructions par cycle, mais émettre 4 µops par cycle. Ce n'était pas un problème, car beaucoup d'instructions, même basiques, étaient décodées en 2 µops. ===L'émission parallèle des instructions=== La superscalarité modifie en profondeur l'unité d'émission, notamment au niveau de son interface. Avant, elle recevait une micro-opération sur son entrée, et fournissait la micro-opération émise sur une sortie. Et cela vaut aussi bien pour une unité d'émission simple, un ''scoreboard'', une fenêtre d'instruction ou des stations de réservation. Mais avec l'émission multiple, les sorties et entrées sont dupliquées. Pour la double émission, il y a deux entrées vu qu'elle doit recevoir deux micro-opération décodées/renommées, et deux sorties pour émettre deux micro-opérations. Dans ce qui suit, nous ne parlerons pas d'entrée et de sortie, mais de '''ports de décodage''' et de '''ports d'émission'''. Les ports de décodage reçoivent des micro-opérations provenant de l'unité de décodage, les ports d'émissions sont pour les micro-opérations émises. Les ports d'émission injectent les micro-opérations émises dans le pipeline, elle sont propagée au banc de registre, aux ALUs, etc. Un processeur superscalaire a forcément plusieurs ports d'émission (et de décodage). Si l'interface de l'unité d'émission change, l'intérieur est tout autant modifié. Sur un CPU non-superscalaire, l'unité d'émission reçoit une µop "candidate à l'émission", et détecte les dépendances entre avec les µops en vol, en cours d'exécution dans le pipeline. Et c'est toujours le cas sur les processeurs superscalaires : l'unité d'émission détecte les dépendances entre les µops candidates et les µops en vol. Mais avec la siperscalarité, elle doit en plus tester les dépendances entre les µops candidates. Si l'unité d'émission est un ''scoreboard'', il doit détecter les dépendances entre instructions émises simultanément et les sérialiser si besoin. Avec une fenêtre d'instruction, il n'y a pour ainsi dire rien à faire, vu que les dépendances sont éliminées par le renommage de registre et que les signaux de réveil gèrent les dépendances RAW. C'est la raison pour laquelle les processeurs superscalaires modernes n'utilisent pas de ''scoreboard''. ==Les processeurs implémentés avec plusieurs pipelines== Nous venons de voir comment la superscalarité impacte le ''front-end'', il est maintenant de voir ce qu'il en est de l'unité d'émission et des ''back-ends''. Un processeur superscalaire peut utiliser l'exécution dans le désordre et le renommage de registres, mais ce n'est pas une obligation. Historiquement, les premiers processeurs superscalaires n'utilisaient pas d'exécution dans le désordre. Le meilleur exemple est celui du Pentium 1 et 2 d'Intel. Ils étaient tous deux des processeurs superscalaires, mais n'avaient pas d'exécution dans le désordre, qui est apparue sur le Pentium 3. les explications sont plus simples si on met de côté l'exécution dans le désordre. Vous vous dites que l'implémentation d'un processeur superscalaire n'est pas la même suivant que le CPU utilise l'exécution dans le désordre, ou non. Mais dans les faits, la distinction est plus subtile que ca. Pour l'expliquer, je vais utiliser une classification originale, qui sépare les processeurs superscalaires en trois sous-types. La distinction se fait sur la manière dont la superscalarité est implémentée en matériel.La classification que j'utilise distingue : * Les processeurs avec plusieurs pipelines, sans exécution dans le désordre. * Les processeurs superscalaires avec un pipeline dynamique. * Les processeurs avec des fenêtres d'instruction décentralisées. La distinction n'est pas qu'une question d'implémentation matérielle. Elle impacte aussi le ratio entre instructions chargées/décodées et instructions émises. Vous vous dites que les deux sont égales, mais ce n'est le cas que sur les processeurs avec N pipelines ! Sur les autres processeurs, il est possible de décoder 4 instructions, mais d'en émettre maximum 6 par cycle ! Et sur les CPU avec des fenêtres d'instruction décentralisées, il y a aussi une différence entre le nombre d'instructions qui sont ''dispatch'' et ''schedule''. La catégorie la plus simple est celle des '''processeurs avec plusieurs pipelines'''. Ce sont des processeurs superscalaires sans exécution dans le désordre. Ils partent d'un processeur non-superscalaire, et ajoutent un second pipeline, en sortie de l'unité d'émission. Le second pipeline permet l'émission parallèle d'une seconde instruction. Le second pipeline n'est pas identique au pipeline originel, sur plusieurs points. Premièrement, il n'a pas exactement les mêmes circuits. Le second pipeline est généralement composé d'une unité de calcul, d'une FPU, éventuellement d'un second banc de registres, guère plus. Il permet d'exécuter une seconde opération de calcul, pas plus, ce qui est à l'origine de contraintes d'appariemment assez fortes. De plus, il n'a pas forcément la même longueur, il peut avoir des étages en moins ou en plus. Il s'agit de la forme la plus simple de processeur superscalaire et nous allons leur consacrer cette section. ===La double émission entière-flottante=== L'exemple le plus simple utilise un second pipeline pour les opérations flottantes, séparé du pipeline originel. L'ajout d'un pipeline flottant permet d'émettre une micro-opération entière en même temps qu'une micro-opération flottante, ce qui s'appelle la '''double émission entière-flottante'''. La micro-opération entière s'exécute dans l'ALU entière, la micro-opération flottante dans la FPU. Les exemples les plus notables sont le PA-RISC 7100, le POWER 1 (RIOS .& et .9) et le PowerPC 601. L'Apollo DN10000 et l'Intel i860 utilisaient aussi cette forme de double émission, mais étaient des prototypes de CPU VLIW. [[File:Double émission entière-flottante.png|centre|vignette|upright=2|Double émission entière-flottante]] Pour bien expliquer les choses, prenons le processeur illustré ci-dessous, avec deux ''back-end'' séparés : un pour la FPU, et un autre pour le reste. Les instructions entières et mémoire passent toutes par le ''back-end'' généraliste. [[File:CPU non-superscalaire basique, avec une FPU.png|centre|vignette|upright=1.5|CPU non-superscalaire basique, avec une FPU]] Les deux ''back-end'' sont des pipelines séparés, mais il faut que l'unité d'émission soit spécialement conçue pour en profiter. Un processeur simple émission peut démarrer une seule µop par cycle : soit la µop est envoyée au ''back-end'' flottant, soit elle est envoyée au ''back-end'' général. Avec la double émission entière-flottante, le processeur peut envoyer une µop dans chaque pipeline, à chaque cycle. Vu que la FPU et son banc de registre forment déjà un pipeline séparé, les seules modifications se font au niveau du ''front-end'', unité d'émission inclue. {| |- |[[File:CPU simple émission avec une FPU.png|centre|vignette|upright=1.5|CPU simple émission avec une FPU.]] |[[File:Double émission entière flottante avec un second pipeline.png|centre|vignette|upright=1.5|CPU avec un second pipeline flottant.]] |} Notez que le terme double émission ''entière''-flottante est un peu trompeur. En réalité, la technique permet d'émettre une instruction flottante avec n'importe quelle autre instruction non-flottante, avec des restrictions pour les branchements. Il est par exemple possible d'exécuter un calcul flottant en parallèle d'un accès mémoire. Sur les CPU 32 bits, il est possible de faire les multiplications dans la FPU. Pour rappel, la FPU contient un multiplieur entier 53-54 bits, pour multiplier les mantisses de deux flottants 64 bits, qu'on peut utiliser pour faire les multiplications entières 32 bits. Cela permet d'émettre une multiplication en même temps qu'une opération entière dans l'ALU. Ainsi, le CPU pouvait émettre deux instructions : une opération entière de base, avec soit une multiplication, soit une opération flottante. En clair, une émission de type INT + (MUL /FLOAT). Par contre, on perd la possibilité d'émettre une multiplication entière avec une opération flottante, mais c'était un compromis qui en valait la peine. Le CPU PA-RISC 7100 implémentait cette optimisation. Il s'agissait d'un CPU double émission entière-flottante, qui utilisait donc un partitionnement assez simple. Le combo partitionnement + reconditionnement permettait ici de gagner en performance, pour un cout en matériel très limité. D’autant plus limité que le jeu d'instruction prévoyait le reconditionnement de la FPU. Les multiplications ne lisaient pas leurs opérandes dans les registres entiers, mais dans les registres flottants. ===La double émission entière=== Une autre possibilité ajoute un second pipeline, capable d'exécuter des opérations entières simples, appelé le pipeline entier. Le processeur supporte alors la '''double émission entière''', à savoir qu'ils peuvent émettre une opération entière en plus d'une autre instruction. L'opération entière en question est une opération simple, comme une addition, une soustraction, une opération bit à bit, une comparaison, etc. Les exemples classiques sont les processeurs Intel i960 et Pentium. L'implémentation était différente entre l'i960 et le Pentium : Le Pentium avait une seconde ALU entière, alors que le i960 rusait. Le Pentium dupliquait l'ALU entière, pour créer un second pipeline entier. Sur le Pentium originel, les contraintes d'appariement étaient assez strictes. Le Pentium avait une FPU, mais ne pouvait pas l'utiliser en double émission. Il lui était impossible d'exécuter une opération flottante en même temps qu'une opération entière (branchements inclus). Le Cyrix 6x86 était une sorte de Pentium produit par l'entreprise Cyrix, avec une microarchitecture différente. Il avait des contraintes d’appariement réduites. Par exemple, il pouvait émettre une opération flottante en parallèle d'une opération entière. Nous détaillerons ces processeurs dans le prochain chapitre, qui porte justement sur les CPU superscalaires x86. [[File:CPU simple et double émission avec un second pipeline entier.png|centre|vignette|upright=2.5|CPU simple et double émission avec un second pipeline entier.]] L'Intel i960 n'avait pas de FPU, juste une ALU entière et une unité mémoire. Il utilisait l'unité mémoire comme une seconde ALU entière limitée. Pour rappel, une unité mémoire contient une AGU, à savoir une ALU spécialisée dans les calculs d'adresse. Elle est capable de faire des additions, soustractions, et quelques décalages limités (des décalages par 2, 4, 8, pas plus). L'Intel i960 ajoutait des connexions entre AGU et banc de registre, afin que celle-ci serve de seconde unité de calcul. L'AGU est déjà reliée aux deux ports de lecture, pour lire les opérandes du calcul d'adresse, il suffit de la connecter sur le port d'écriture. Le processeur peut alors émettre deux instructions/µops entières, sous certaines conditions. [[File:Emission multiple des opérations entières, double émission.png|centre|vignette|upright=2|Émission multiple des opérations entières, double émission.]] ==Les processeurs superscalaires dynamiques== Les processeurs précédents, avec 2 pipelines, avaient une unité d'émission assez simple. Elle avait deux ports de décodage, et deux ports d'émission. Le nombre de ports de décodage et d'émission étaient identiques. Vous vous dites que c'est naturel, et intuitif, que c'est normal pour un processeur superscalaire. Mais en réalité, la majorité des processeurs superscalaires a plus de ports d'émission que de décodage. Expliquer pourquoi va cependant nous demander de parler de comment la superscalarité exploite les unités de calcul. Posons-nous cette question : de combien d'unités un processeur superscalaire a besoin ? Les unités en question ne sont nécessairement des unité de calcul, l'unité mémoire et l'unité de branchement comptent aussi. La réponse dépend du nombre de voies. Si le ''front-end'' charge et décode un paquet de N instructions, exécuter ces N instructions simultanément demandera là aussi au moins N unités de calcul. Notez le terme : ''au moins'', car c'est là tout l'enjeu de cette section. La réponse dépend aussi des contraintes d’appariement. Pour un CPU superscalaire "parfait", sans contraintes d’appariement, toutes les unités doivent être dupliquées, y compris l'unité mémoire. Un processeur à double émission parfaite devrait dupliquer l'ALU entière, le ''barrel shifter'', le circuit multiplieur, la FPU, l'unité mémoire, et l'unité de branchement, ce qui ferait passer de 6 unités à 12. Le cout en circuit est énorme, même pour de la double émission ! Et pour un CPU à N pipelines, le cout en circuit encore plus prohibitif ! En pratique, les processeurs superscalaires qui font ça sont très rares. La majeure partie des processeurs superscalaires utilisent d'autres techniques : le partitionnement, la duplication partielle des ALUs, la duplication des autres unités. ===Le partitionnement des unités fonctionnelles=== Les CPU superscalaires les plus simples ne dupliquent pas leurs unités de calcul, ils se contentent d'exploiter les unités existantes. Et pour expliquer comment, nous allons partir de l'exemple d'un processeur non-superscalaire contient une ALU entière, une FPU, une unité mémoire et une unité de branchement. Les quatre unités peuvent fonctionner en parallèle, à savoir qu'une unité peut exécuter une instruction à elle toute seule, sans interagir avec les autres. Cela suffit pour créer un processeur quadruple émission, qui peut émettre en même temps : une opération entière, une opération flottante, un branchement, un accès mémoire. Le principe marche aussi sur un processeur avec moins de 4 unités, par exemple sans FPU et/ou sans unité de branchement, ou avec au contraire des unités en plus. L'idée générale est d’émettre une µops séparée dans chaque unité fonctionnelle. Utiliser ainsi les unités existantes, pour rendre un CPU superscalaire, s'appelle le '''partitionnement superscalaire'''. Le terme n'est pas beaucoup utilisé en-dehors de quelques vieux articles académiques. Le partitionnement est fortement dépendant du jeu d'instruction, car il demande de bien séparer instructions entières, flottantes, branchements et instructions mémoire. Et autant c'est bien le cas sur les CPU RISC, autant les CPU CISC ne sont pas dans ce cas. Les CPU CISC ont des instructions ''load-op'', qui effectuent une instruction arithmétique et une µop mémoire. Et ces instructions sont très compliquées à implémenter avec le partitionnement. Et au-delà de ça, les CPU CISC ont beaucoup d'instructions qui sont décodées en plusieurs µops, à exécuter dans un ordre bien précis. On peut pas émettre les µops en question simultanément. Le partitionnement a un cout en circuit très limité, pour un gain en performance lui aussi limité. Pour reprendre le CPU de l'exemple précédent, il est rare que l'on ait à exécuter ces quatre instructions en même temps. En pratique, les combinaisons fréquentes sont les combinaisons calcul + accès mémoire, calcul + branchement, calcul entier + calcul flottant. Utiliser une quadruple émission de ce type n'est donc pas une bonne idée, alors que la double émission parait plus adaptée. Et c'est là que la différence entre ports d'émission et de décodage devient intéressante. L'idée est d'avoir une unité d'émission avec deux ports de décodage, et 4 port d'émission (un par unité). Les contraintes d’appariement varient alors suivant le processeur, mais la règle générale est que si deux micro-opérations tombent dans deux unités différentes, on peut les émettre en même temps. En clair, on peut émettre une opération entière avec une opération flottante, une opération entière avec un branchement, un accès mémoire avec un branchement, etc. Et cela rend le partitionnement bien plus intéressant que prévu. Les tout premiers CPU superscalaires étaient des CPU double émission, qui utilisaient le partitionnement avec : une ALU entière, une unité mémoire et une FPU. Nous parlerons de '''partitionnement INT-FLOAT-MEM'''. L'avantage sur la double émission flottante est que les combinaisons INT + MEM et FLOAT + MEM étaient autorisées. L'intérêt était d'émettre une opération en parallèle d'un accès mémoire, ce qui est un doublet d'instruction assez fréquent. Les exemples classiques sont l'HyperSPARC, de l'alpha 21064 et du PowerPC 603. Les contraintes d’appariement variaient grandement suivant le processeur. Le PowerPC 603 implémentait une double émission "parfaite". C'est à dire que toutes les combinaisons possibles à deux instructions étaient possibles, que ce soit INT + FLOAT, INT + MEM, FLOAT + MEM. En plus de cette double émission "parfaite", le CPU utilisait la technique dite de ''branch folding'', qui permettait d'exécuter un branchement sans utiliser d'unité de calcul, ce qui fait qu'il était qualifié de ''CPU 2-way + Branch''. A l'opposé, le processeur alpha 21064 avait pas mal de contraintes d’appariement, à savoir que certaines combinaisons de deux instructions étaient interdites, bien que compatibles avec les unités disponibles. Une des raisons est que le banc de registre n'avait pas assez de ports de lectures, ce qui fait que certaines combinaisons avec une écriture mémoire n'étaient pas permises. Les concepteurs avaient volontairement inséré ces contraintes afin de simplifier le design du processeur. Avant de poursuivre, précisons que la FPU regroupe un additionneur flottant et un multiplieur flottant. Et ce fait peut être exploité pour approfondir le partitionnement. La '''double émission FADD/FMUL''' permet d'émettre une addition flottante en même temps qu'une multiplication flottante. Elle a été utilisée sur les processeurs x86, Alpha et SPARC. Les autres circuits de calcul flottant sont souvent regroupés avec l'additionneur. La division est parfois e même port que le multiplieur flottant, parfois sur celui de l'additionneur (quand elle est microcodée et exécutée en enchainant les soustractions). ===La duplication des ALU entières=== Le partitionnement est une technique élégante, simple, facile à implémenter et au cout en circuits modeste. Mais il faut avouer que cela ne donne pas des CPU superscalaires très efficaces. La raison est que le partitionnement permet d'émettre des paires, triplets, quadruplets de micro-opérations qui sont rares en pratique. Les combinaisons les plus fréquentes en pratique sont des paires/triplets d'opérations entières, souvent des paires/triplets d'additions ou d'opérations entières simples. Il est donc primordial d'émettre plusieurs opérations entières simultanément. Le fait d'émettre et d'exécuter plusieurs opérations entières en même temps s'appelle l''''émission entière multiple'''. Pour exécuter la seconde/troisième opération entière, il faut dupliquer les ALU entières (ou réutiliser l'AGU comme ALU entière comme le fait l'Intel i960). Le processeur doit avoir deux ALU entières pour de la double émission entière, trois pour de triple émission entière, etc. De plus, il faut ajouter des ports au banc de registre, pour lire deux fois plus d'opérandes et décrire deux résultats. Et enfin, il faut rajouter un port d'émission pour pouvoir émettre une micro-opération entière en plus. [[File:Processeur non-superscalaire basique 03.png|centre|vignette|upright=2|Processeur non-superscalaire basique]] La tendance est de multiplier les ALU entières, mais pas le circuit multiplieur. Le cout en transistors est alors grandement réduit, au prix de l'apparition de dépendances structurelles. Le CPU peut émettre deux additions en même temps, ou une addition et une multiplication, mais pas deux multiplications. Il en est de même avec le ''barrel shifter'', qui n'est pas dupliqué. Il est donc possible d'émettre un décalage avec une opération de base, mais pas deux décalages en parallèle. Mais ces contraintes d’appariement ne sont pas un problème, car il est rare de devoir émettre deux multiplications/décalages en même temps. La plupart des programmes sont majoritairement remplis d'additions, avec des multiplications assez rares et des décalages qui le sont encore plus. La moyenne est proche d'une multiplication pour 4/5 additions. Vu que des multiplications consécutives sont très rares, dupliquer le multiplieur est presque inutile. Disposer de plusieurs circuits multiplieurs ou de plusieurs ''barrel shifters'' serait donc un cout en circuits qui ne servirait que rarement et n'en vaut pas la chandelle. Il faut noter que sur les CPU 32 bits, les multiplications peuvent être exécutées dans la FPU ou dans un multiplieur séparé, tout dépend de si le CPU exécute les multiplications dans la FPU ou pas. {|class="wikitable" |- ! Voie d'émission n°1 !! Voie d'émission n°2 !! Voie d'émission n°3 |- | ALU entière || ALU entière || FPU |} Reste à connecter les unités de calcul à l'unité d'émission (indirectement, car il y a des étages de pipeline entre les deux). Une implémentation basique utilisee un port d'émission par unité. Prenons l'exemple d'un CPU superscalaire avec 4 ALU entières, une FPU, et une unité mémoire. Vu qu'il y a 6 unités, cela fait donc 6 ports d'émission. L'exemple historique le plus impressionnant est celui du Motorola 88110, un CPU double émission qui possède 10 unités. Il contient deux ALU entières, un circuit multiplieur, un circuit diviseur, une unité de manipulation de bits, une unité mémoire, un additionneur flottant, et deux unités graphiques pour des calculs impliquant des pixels. Le multiplieur est utilisé à la fois pour les multiplications entières, flottantes et le produit de deux pixels. Idem pour le diviseur, qui sert pour les divisions entières et flottantes. Et chaque unité a son propre port d'émission. La conséquence est que les contraintes d'appariement du Motorola 88110 sont simples : tant que deux µops atterrissent dans des unités séparées, la paire est permise. Il est par exemple possible d'émettre simultanément deux opérations entières simples, vu qu'il y a deux ALU. Par contre, impossible d'émettre simultanément deux multiplications, deux divisions, deux opérations flottantes, deux manipulations de bit, deux accès mémoire, etc. De plus, le partage du multiplieur fait qu'on ne peut pas émettre une multiplication entière avec une multiplication flottante. Idem avec les divisions, vu que le diviseur est aussi partagé. Ce sont les seules contraintes. ===Le partage des ports d'émission=== La présence d'un circuit multiplieur et d'un ''barrel shifter'' pose des problèmes pour le partitionnement. Il est possible d'avoir un port d'émission dédié au multiplieur, et un autre au ''barrel shifter'', qui sont séparés de ceux pour l'ALU entière. Il est alors possible d'émettre une opération entière, une multiplication et un décalage, simultanément. Nous parlerons de '''partitionnement entier''' pour désigner cette possibilité. [[File:Partitionnement superscalaire entier.png|centre|vignette|upright=2|Partitionnement superscalaire entier.]] Le partitionnement entier est cependant très rare, car le gain en performance serait trop faible au regard des couts en circuit. Quelques processeurs historiques l'utilisaient, mais ce n'est plus le cas de nos jours. La raison est que les multiplications et les décalages sont assez rares dans le code, ce qui fait que le port d'émission associés sont sous-utilisés. Et un port d'émission a un cout en circuit assez important. Et c'est sans compter qu'ajouter des ports sur le banc de registres juste pour un multiplier sous-utilisé n'en vaut pas la peine. L'idéal serait de trouver un moyen de réduire le nombre de ports d'émissions, sans réduire le nombre d'unités de calcul. [[File:Emission multiple des opérations entières, implémentation naive.png|vignette|Émission multiple des opérations entières, implémentation naive]] La solution consiste à '''partager les ports d'émission''', à savoir connecter un port d'émission à plusieurs unités de calcul. L'exemple classique est justement lié aux circuits multiplieur et ''barrel shifters''. L'idée est que le multiplieur et le ''barrel shifter'' sont mis sur le même port d'émission qu'une ALU entière. Prenons par exemple le processeur à double émission entière illustré ci-contre. Un port d'émission est relié à une ALU entière seule, et l'autre port d'émission alimente la seconde ALU entière et le multiplieur. La conséquence est que le processeur peut émettre deux opérations entières, dont une multiplication. Si on avait utilisé un port d'émission dédié au multiplieur, il aurait été possible d'émettre deux opérations entières et une multiplication. On perd donc une voie, mais on gagne en flexibilité et on économise des circuits. Notons que ce partage signifie aussi que le multiplieur partage les ports sur le banc de registre, ce qui fait une double économie. [[File:Emission multiple des opérations entières, implémentation courante.png|centre|vignette|upright=1.5|Émission multiple des opérations entières, implémentation courante]] En général, le ''barrel shifter'' et le multiplieur sont placés sur deux voies d'émission séparés, ce qui permet d'émettre un décalage et une multiplication en même temps. Les mettre sur le même port ne le permettrait pas. {|class="wikitable" |- ! Voie d'émission n°1 !! Voie d'émission n°2 !! Voie d'émission n°3 |- | * ALU entière * Multiplieur | * ALU entière * Barrel shifter | FPU |- | * Additions, soustractions, opérations bit à bit, etc. * Multiplication | * Additions, soustractions, opérations bit à bit, etc. * Décalages et rotations | Opération flottante |} Mais le partage des ports d'émission peut appliquer à n'importe quelle unité. Prenons l'exemple d'un CPU superscalaire avec 4 ALU entières, une FPU, et une unité mémoire. Vu qu'il y a 6 unités, cela fait donc 6 ports d'émission. Cependant, il est rare qu'un bloc de 6 instructions contienne exactement 4 opérations entières, une opération flottante, et un accès mémoire. Beaucoup de ports d'émission seraient sous-utilisés. La solution est de partager des ports d'émission, pour passer à la quadruple émission. Le CPU peut alors répartir 4 instructions sur les ports d'émission adéquats, à chaque cycle. Le CPU a donc 4 ports de décodage et 6 ports d'émission. Une implémentation classique permet n'importe quelle combinaison de 4 instructions compatible avec les 6 unités. Par exemple, il devient possible d'émettre simultanément 4 additions, la combinaison 2 * INT + FLOAT + MEM, 3 * INT + MEM, et bien d'autres. L'exemple relie une ALU entière, la FPU et l'unité mémoire sur le même port. Le CPU a alors quatre ports d'émission, qui ne sont pas "identiques". Trois d'entre eux sont reliés à une ALU entière chacun, rien d'autre. Le quatrième est lui relié à une ALU entière, la FPU et l'unité mémoire. On économise alors deux ports d'émission, mais cela entraine l'apparition de contraintes d’appariement. Le CPU peut émettre 3 opérations entières + n'importe quelle autre opération. Mais les autres combinaisons sont interdites. Le CPU peut émettre 6 opérations entières simultanément, grâce à 6 ALU entières. Les accès mémoire ou opérations flottantes ne viennent pas en plus, mais en remplacement des opérations entières. Regrouper plusieurs ALU sur un même port d'émission est donc à l'origine de dépendances structurelles, de nouvelles contraintes d’appariement. Dans certaines situations de '''''port contention''''', on a assez d'ALU pour exécuter N micro-opérations, mais la répartition des ALUs sur les ports l'empêche. Impossible d'émettre deux micro-opérations sur deux ALU si elles sont sur le même port d'émission. On peut réduire les cas de ''port contention'' en répartissant correctement les ALU sur les ports d'émission, mais sans jamais les supprimer complétement. Un exemple est celui du PowerPC 604. Il implémentait la quadruple émission, alors qu'il avait 5 unités de calcul. Mais deux unités partageaient un même port d'émission. Dans le détail, il avait deux ALU entières, un circuit multiplieur/diviseur, une FPU, une unité mémoire. La FPU et le multiplieur étaient sur le même port d'émission. En conséquence, le CPU pouvait émettre seulement les combinaisons INT + INT + MEM + (MUL/FLOAT). : Il est intéressant de voir l'exemple d'un processeur superscalaire parfait, sans contraintes d'appariement, à N voies. Pour cela, on part d'un processeur non-superscalaire, avec M unités de calcul. Un tel processeur peut s'implémenter avec N ports d'émission, chacun relié à M unités de calcul possibles, ce qui fait que les M unités de calcul sont dupliquées en N exemplaires. Si on prend par exemple un CPU non-superscalaire avec une ALU, une FPU, une unité mémoire et une unité de branchement, un port d'émission sera relié à une ALU, une FPU, une unité mémoire et une unité de branchement. ===La duplication des autres unités fonctionnelles=== Dupliquer les ALU entière est une première étape, mais il est aussi possible de dupliquer les autres unités fonctionnelles. Par exemple, il est possible de dupliquer l'unité mémoire ou l'unité de branchement. Reste à voir quelles unités dupliquer, et pourquoi. Précisons que dans ce qui va suivre, nous allons parler de la duplication des unités en général, pas seulement des unités mémoire/branchement. Nous allons continuer de parler de la duplication des ALU entières dans ce qui suit, mais de manière plus générale. Pour un processeur à N voies, il est théoriquement possible de dupliquer toutes les unités en N exemplaires. Mais ce n'est jamais fait en pratique, car le cout en circuits serait trop important et le gain en performance trop faible. Mieux vaut dupliquer certaines unités bien précises, quitte à avoir des contraintes d'appariement. Et à ce petit jeu, toutes les duplications ne se valent pas, certaines sont plus rentables en matière de performance, le cout en circuit n'est pas le même selon l'unité, etc. La rentabilité en performance dépend surtout des contraintes d'appariement. Les contraintes d'appariement entrainent une baisse de performance significatives si elles interdisent des paires d'instructions fréquentes. Aussi, les opérations très fréquentes, souvent appairées, gagnent à dupliquer l'unité associée. Et au contraire, les unités rarement utilisées ne doivent pas être dupliquées. Il se trouve que les unités les plus utilisées sont les ALU entières, suivies par les unités mémoire et l'unité de branchement. Le cout en circuit n'est aussi pas le même. Dupliquer les ALU entière est trivial, car elles ont un budget en transistors assez faible. Les circuits multiplieurs, diviseurs, et les ''barrel shifter'' ont eux un cout en transistor plus élevés. Si on devait faire un classement, on aurait : ALU entière < ''barrel shifter'' < multiplieur. Pour les unités mémoire, le cout en transistor est là aussi conséquent. Le résultat final est que dupliquer les ALU entières est privilégié sur le reste, comme l'a vu dans la section précédente. Les unités mémoire arrivent en seconde place, suivie par les multiplieurs/''barrel shifter'' à la troisième place, et les unités de branchement à la quatrième place. Les CPU superscalaires larges ont donc tendance à multiplier les unités mémoire en second. Les multiplieurs et ''barrel shifters'' sont eux aussi dupliqués sur les CPU superscalaires larges, mais cela vient souvent après avoir dupliqué les unités mémoire. La raison est que les accès mémoire sont critiques pour la performance des CPU à exécution dans le désordre, pas les multiplications. Pour les unités de branchement, une subtilité complique le fait d'en installer plusieurs. Que faire quand deux branchements sont pris ? La solution la plus simple est de ne pas se trouver dans cette situation : on n'utilise qu'une seule unité de branchement et on n’exécute qu'un seul branchement par cycle. La majorité des processeurs superscalaires étroits font ainsi. Les processeurs superscalaires larges ont cependant deux unités de branchements, mais ils n'acceptent qu'un seul branchement pris à la fois. Si les deux branchements pris sont exécutés en même temps, le processeur n’exécute que le plus récent, dans l'ordre du programme. ===Les CPU superscalaires mélangent les trois techniques=== Les CPU superscalaires utilisent toutes les techniques à la fois : partitionnement, duplication de l'ALU entière, duplication des autre unités fonctionnelles. Chaque processeur utilise ces trois stratégies à sa sauce, en fonction de son budget en transistor. Les quatre techniques ont cependant un cout en circuit différent, et donnent des gains de performances eux aussi forts différents. * Le '''partitionnement superscalaire''' a un cout en circuit modéré, car on n'a pas besoin de dupliquer d'ALU. * La '''duplication des ALU entières''' est la technique la plus importante : elle a un cout en circuit modéré, et donne des gains en performance impressionnants. * La '''duplication des autres unités''' a un cout en circuit variable, pour des gains en performance eux aussi variables. * Le '''partage des ports d'émission''' réduit le cout en circuit du CPU, pour des pertes de performances mineures si bien fait. L'usage du partitionnement seul, sans duplication, était surtout le fait des processeurs commercialisés entre les années 1989 et 1993. Le partage des ports d'émission a lui aussi mis un peu de temps pour s'imposer. Globalement, les processeurs superscalaires ont évolués progressivement, pour se rapprocher d'une sorte de standard. Mais pour comprendre ce qu'est ce standard, et en quoi les CPU historiques s'en sont progressivement rapprochés, il faut étudier l'histoire des CPU superscalaires. L'histoire des CPU superscalaires a commencé dès 1960, mais est resté un projet de recherche pendant plusieurs décennies. De nombreux designs ont été conçus, mais abandonnés avant la production, comme l'ACS-1 d'IBM. Les années 1980 ont vu l'arrivée de prototypes de processeurs VLIW, mais qui n'étaient pas superscalaires. Par exemple, l'Apollo DN10000 et l'Intel i860 pouvaient émettre deux instructions à la fois, sous conditions, mais demandaient que le compilateur fasse une bonne partie du travail. Les premiers CPU superscalaires commercialisés sont sortis en 1989. Deux CPUs superscalaires sont sortis cette année là : le RS/6000 d'IBM et l'i960CA d'Intel. Puis, la compétition s'est aussi lancée dans la bataille. Les CPU commercialisés avant l'année 1993 étaient à double émission, à l'exception du SuperSPARC qui utilisait la triple émission. Les modèles après 1993 hésitaient entre double et quadruple émission. Dans le tableau ci-dessous, les processeurs quadruple émission sont en bleu, les triple émission sont en rouge, les autres ne sont pas en couleur. {|class="wikitable" |- ! !! IBM !! HP PA-RISC !! SPARC !! Intel!! Alpha !! Autres |- ! 1989 | RS/6000 || || || i960CA || || |- ! 1990 | || || || || || |- ! 1991 | || || || || || Motorola 88110 |- ! 1992 | RSC || PA 7100 || class="f_rouge | SuperSPARC || || 21064 || |- ! 1993 | PPC 601 || || HyperSPARC || Pentium || || |- ! 1994 | class="f_bleu | POWER 2 || PA 7200 || || || class="f_bleu | 21164 || |- ! 1995 | 603/604 || class="f_bleu | PA 8000 || class="f_bleu | UltraSPARC 1 || Microarchitecture P6 (+ concurrence AMD) || || class="f_bleu | MIPS R1000 |- ! 1996 | class="f_bleu | 620 || || || || class="f_bleu | 21264 || |- ! ... || ... || ... || ... || ... || ... || ... |} Les premiers CPU à double émission sont assez représentatifs des CPU à double émission modernes. Il faut dire que se limiter à deux voies ne permet pas de faire beaucoup de choses. Voici le tableau chronologique disant quel CPU utilisait quelle technique. * Les CPU en vert utilisaient la double émission entière-flottante. * Les CPU en rouge utilisaient la double émission entière "pure". * Les CPU en jaune utilisaient un ''partitionnement complet'' entre ALU, FPU et unité mémoire. * Les CPU sans couleur utilisaient un grand nombre d'unités, avec beaucoup de ports d'émission. * Les CPU barrés utilisent la triple ou quadruple émission et seront vu dans la section suivante. {|class="wikitable" |- ! !! IBM !! HP PA-RISC !! SPARC !! Intel!! Alpha !! Autres |- ! 1989 | class="f_vert | RS/6000 || || || class="f_rouge | i960CA || || |- ! 1990 | || || || || || |- ! 1991 | || || || || || Motorola 88110 |- ! 1992 | class="f_vert | RSC || class="f_vert | PA 7100 || <s>SuperSPARC</s> || || class="f_jaune | 21064 || |- ! 1993 | class="f_vert | PPC 601 || || class="f_jaune | HyperSPARC || class="f_rouge | Pentium || || |- ! 1994 | <s>POWER 2</s> || PA 7200 || || || <s>21164</s> || |} C'est en 1994, que la quadruple émission est arrivée et que les CPU ont essayé divers implémentations. Il y avait alors trois stratégies pour la double/triple/quadruple émission, qui partageaient des problèmes similaires. La première implémentation dupliquait les ALUs et les FPUs, ce qui permettait d'émettre deux opérations flottantes avec deux opérations entières. Le POWER 2 dupliquait ses FPUs à la perfection, alors que le 21264 séparait l'additionneur et le multiplieur flottant. Les accès mémoire étaient traités comme des opérations entières et le processeur pouvait souvent en émettre deux à la fois. Mais une telle organisation n'est plus utilisée de nos jours, car, sans instructions flottantes à exécuter, elle est équivalente à de la double émission. La seconde implémentation utilisait deux ALU entières (dont un multiplieur), une unité mémoire et une FPU.Cette organisation est intéressante, mais encore trop stricte pour donner de bons résultats, les quadruplets avec exactement deux opérations entières, un accès mémoire et une opération flottante étant trop rares. L'usage de ces 4 unités en double ou triple émission a été utilisé sur le PA 7200 et le SuperSPARC. Malheureusement, en plus de problèmes au niveau de son cache, le SuperSPARC avait de nombreuses contraintes d’appariement, car le banc de registre n'avait pas assez de ports pour alimenter certaines combinaisons d'instructions. De plus, s'il émettait une multiplication, il ne pouvait pas émettre de seconde µop. Enfin, une troisième implémentation utilisait un grand nombre d'unités fort différentes, bien plus que quatre, avec au minimum deux ALU entières. Prenons l'exemple de l'UltraSPARC, qui avait : deux ALU entières, une unité mémoire, une unité de branchement, un additionneur flottant, un multiplieur flottant, un diviseur flottant (aussi utilisé pour le calcul des racines carrées), et 2 unités graphiques pour les calculs sur des pixels. En tout, cela fait 9 unités différentes, pour de la quadruple émission. Dans le même genre, les processeurs MIPS R8000 et R10000 avaient une implémentation plus moderne, dans le sens où toutes les unités étaient dupliquées. Le CPU avait deux ALU, deux FPU et deux unités mémoire. Il pouvait donc émettre quatre instructions à la fois, tant que le paquet collait avec ces six unités. Moitié opérations entières, moitié opérations mémoire, avec de quoi remplacer les opérations entières par des opérations flottantes. Le PA 8000, lui, dupliquait toutes ses unités en deux exemplaires, ce qui n'est pas l'idéal, aucun processeur moderne ne fait cela aujourd'hui. Le tout est résumé dans le tableau ci-dessous, avec : * en vert les CPU utilisant la première stratégie ; * en rouge les CPU utilisant la seconde stratégie ; * en jaune les CPU utilisant la troisième stratégie. {|class="wikitable" |+ Processeurs quadruple émission historiques |- ! Année !! Processeur !! Unités |- ! 1991 | class="f_jaune | Motorola 88110 || class="f_jaune | 2 ALU + MUL + DIV + BIT + MEM + FADD + 2 unités graphiques |- |- ! rowspan="2" | 1994 | class="f_vert | POWER 2 || class="f_vert | 2 ALU/MEM + 2 FPU (+ 2 Branchements via ''branch folding'') |- | class="f_rouge | 21164 || class="f_rouge | 2 ALU + FPU + MEM |- ! rowspan="3" | 1995 | PA 8000 || 2 ALU + 2 ''barrel shifters'' + 2 MEM + 2 FPU (FMA) + 2 FPU (DIV et SQRT) + 2 SIMD |- | class="f_jaune | UltraSPARC 1 || class="f_jaune | 2 ALU + MEM + BRANCH + FADD + FMULFDIV + 2 unités graphiques |- | MIPS R8000 et R10000 || 2 ALU + 2 FPU + 2 MEM |- ! rowspan="2" | 1996 | class="f_rouge | 620 || class="f_rouge | 2 ALU + FPU + MEM |- | class="f_vert | 21264 || class="f_vert | 2 ALU/MEM + 2 FPU (FADD + FMUL) |} Malheureusement, ces CPUs historiques n'avaient ni assez d'unités de calcul entières, ni d'unités mémoire, pour remplir la quadruple émission. Environ la moitié des instructions d'un programme correspond à des instructions entières. Pour en profiter, un processeur à N voies doit disposer d'au minimum N/2 ALU entières, l'idéal étant d'aller au-delà. Les CPU superscalaires historiques avaient 2 ALU pour de la quadruple émission, ce qui était très limite. En comparaison, les CPU superscalaires modernes mettent le paquet niveau ALU entière. La plupart des CPU x86 modernes descendent rarement sous les 2/3, la plupart remplit presque la totalité de la largeur du CPU. Pour donner un exemple, la microarchitecture Golden Cove d'Intel est un processeur à 6 voies, avec 5 ALU entières. Pour être précis, un programme informatique exécute environ 40% de son code dans des instructions arithmétiques, un autre 40% dans les accès mémoire, et un le reste pour... le reste. La conséquence est que les processeurs modernes dupliquent leurs unités mémoire. Par exemple, la microarchitecture ''Golden Cove'' d'Intel dispose de trois unités mémoire : deux spécialisées dans les écritures, trois dans les lectures. La microarchitecture ''Gracemont'' utilise la pentuple émission et peut émettre 4 µops entières et 4 µops mémoire. Notez que le ratio est équilibré entre opérations entières et mémoire, avec soit égalité, soit un avantage pour les ALU entières. Une autre régularité est que la largeur totale du processeur peut être occupée par des opérations entières/mémoire. Pour le dire autrement, sur un processeur quadruple émission, il pourra émettre un mix de 4 instructions entière/mémoire, avec zéro opération flottante. Idem avec des processeurs triples ou pentuple émission, voire plus. Si le processeur émet une opération flottante, elle remplace une opération entière. Prenons l'exemple des processeurs AMD K6, sortis en 1997, juste avant les AMD Athlon. Ils avaient 10 unités de calcul, 6 ports d'émission, 4 ports de décodage. Voici exactement comment étaient répartie les unités de calcul sur les ports d'émission. C'était des processeurs quadruple émission, ce qui fait que la moitié de la largeur du processeur sert pour des opérations entières, et l'autre moitié pour les accès mémoire. {|class="wikitable" |- ! Port d'émission n°1 !! Port d'émission n°2 !! Port d'émission n°3 !! Port d'émission n°4 !! Port d'émission n°5 !! Port d'émission n°6 ! |- | ALU entière || ALU entière || FPU || Unité de branchement || Unité LOAD || Unité STORE |- | || Multiplieur || || || || Unité de calcul d'adresse (instruction LEA) |- | || ''Barrel shifter'' || || || || Unité pour les instructions PUSH |} Les microarchitectures K7, K8 et K10, utilisées sur les anciens CPU AMD Athlon, sont un exemple moins représentatif. Ces microarchitectures utilisent le partage des ports d'émission au maximum. C'était des microarchitectures à triple émission, avec six ports d’émission. Il y avait trois ports d'émission pour les opérations flottantes, et trois ports d'émission pour les micro-opérations entières/mémoire. Il y avait trois ALU entières et trois unités mémoire, chaque ALU partageait un port d'émission avec une unité mémoire. Elle pouvait donc émettre soit trois opérations entières, soit trois µops mémoire, soit un mix des deux. En plus des mix avec les opérations flottantes et branchements. {|class="wikitable" |- ! Port d'émission n°1 !! Port d'émission n°2 !! Port d'émission n°3 !! Port d'émission n°4 !! Port d'émission n°5 !! Port d'émission n°6 ! |- | ALU entière || ALU entière || ALU entière || FADD || FMUL || FPU (autres opérations) |- | Multiplieur || || || || || |- | LOAD/STORE || LOAD/STORE || LOAD/STORE || || || |} Les CPU superscalaires larges dupliquent les multiplieurs et/ou les ''barrel shifters'', et même les unités de branchement. La raison est que quand la largeur du CPU augmente, le processeur traite des paquets d'instructions de plus en plus gros. Par exemple, pour un CPU à 10 voies, cela veut dire émettre 10 instructions en même temps. Il est alors plus fréquent d'avoir deux multiplications, deux décalages, deux accès mémoire, deux branchements. Par exemple, la moyenne est d'une multiplication tous les 5 instructions. Les CPU superscalaires à 10 voies ont donc tendance à avoir deux multiplications par paquet d'instruction, en moyenne. Avoir deux multiplieurs est alors un gain en performance clair. Par exemple, la microarchitecture ''Golden Cove'' a deux ''barrel shifters''. C'est une architecture à 6 voies. {|class="wikitable" |- ! Port d'émission n°1 !! Port d'émission n°2 !! Port d'émission n°3 !! Port d'émission n°4 !! Port d'émission n°5 !! Port d'émission n°6 !! Port d'émission n°7 !! Port d'émission n°8 !! Port d'émission n°9 !! Port d'émission n°10 |- | ALU entière || ALU entière || ALU entière || ALU entière || ALU entière || LOAD || LOAD || LOAD || STORE || STORE |- | ''Barrel shifter'' || Multiplieur || Multiplieur || ''Barrel shifter'' || || || || || || |- | Branchements || Diviseur || || Branchements || || || || || || |} ==Les µops mémoire sur les processeurs superscalaires== Plus haut, nous avons volontairement passé sous silence les accès mémoire. La raison à cela est que leur support sur les CPU superscalaire peut se faire de plusieurs manières différentes, qu'il vaut mieux expliquer dans une section dédiée. Les différentes implémentations sont décrites dans la liste suivante, nous allons les voir une par une. * Avec la première méthode, les accès mémoire sont exécutés par le pipeline pour les opérations entières. * Avec la seconde méthode, les accès mémoire ont un pipeline dédié ; c'est l'''émission parallèle des accès mémoire''. * Avec la troisième méthode, il est possible d'émettre plusieurs accès mémoire simultanément : c'est l'''émission multiple des accès mémoire''. Voyons ces trois cas dans le détail. Nous verrons notamment les différentes formes d'émission multiple des accès mémoire. ===Les accès mémoire traités dans le pipeline entier=== Dans le cas le plus simple, les accès mémoire sont traités comme des opérations entières un peu spéciales. L'unité mémoire est sur le même port d'émission que l'ALU entière, ce qui fait que les accès mémoire remplacent des opérations entières. Faire ainsi permet d'utiliser une ALU entière pour les calculs d'adresse, pas besoin de rajouter une unité de calcul d'adresse séparée. Typiquement, un processeur de ce type place l'unité mémoire après l'unité de calcul. Les opérations entières n'utilisent que l'ALU entière, mais pas l'unité mémoire. Les accès mémoire exécutent un calcul d'adresse dans l'ALU entière, puis l'adresse calculée est envoyée à l'unité mémoire. Le tout est illustré ci-dessous, dans le cas d'un processeur historique à double émission entière-flottante. [[File:Processeur non-superscalaire basique 01.png|centre|vignette|upright=1.5|Processeur non-superscalaire basique]] Un exemple de CPU double émission de ce type est celui du Power PC 440, un CPU destiné à l'embarqué. Vu qu'il était destiné à l'embarqué, il n'avait pas de FPU, il n'avait pas besoin de manipuler de nombres flottants. Il n'a donc que des ALU entières, un multiplieur et une unité mémoire. Il dispose de deux ports d'émission. Le premier est connecté à une ALU entière et un circuit multiplieur, le second est relié à l'unité mémoire et une seconde ALU entière. Une organisation simple, mais très efficace ! [[File:PowerPC 440.png|centre|vignette|upright=2|Microarchitecture du PowerPC 440.]] L'implémentation économise une unité de calcul d'adresse, mais n'a pas de cout en performance significatif. Les accès mémoire étant très lents, émettre une µop entière en parallèle d'un accès mémoire ne sert pas à grand-chose. On peut émettre cette µop entière un cycle plus tard, elle sera terminée avant même que l'accès mémoire soit terminé, même si l'accès mémoire se fait dans le cache L1 ou L2. Pour comprendre pourquoi, faisons quelques calculs. Un accès mémoire prend deux-trois cycles quand il tombe dans le cache L1. Et on doit attendre qu'il soit terminé pour en émettre un nouveau. Combien d'instructions arithmétiques peut-on lancer pendant ce temps, sur un CPU triple émission ? Au premier cycle, on peut en émettre 2 (l'instruction mémoire prend une place, donc 3-1), puis 3 au second cycle. Ce qui fait 5 instructions arithmétiques au total. Or, il se trouve qu'il y a une instruction d'accès mémoire pour 5 instructions arithmétiques, environ. Par contre, ce compromis change quand on passe à la quadruple émission, voire au-delà. Dans ce cas, il devient intéressant de pouvoir émettre une nouvelle instruction mémoire à chaque cycle. Ce qui nous amène à la section suivante... ===L'émission parallèle des accès mémoire=== A partir de la quadruple émission, la tendance a été de séparer l'unité mémoire des ALU entières. Cela permet d'émettre des µops mémoire en même temps que des µops entières et/ou flottantes., ce qui porte le nom d''''émission parallèle des µops mémoire''', ou encore ''émission mémoire parallèle''. Par exemple, un processeur quadruple émission peut émettre en même temps deux opérations entières, une opération flottante et un accès mémoire. L'accès mémoire vient en plus des opérations entières, il n'est pas possible de remplacer l'accès mémoire par une opération entière. La technique ne permet pas de faire les calculs d'adresse dans l'ALU entière. Il faut donc utiliser une unité de calcul d'adresse séparée. Elle est intégrée dans l'unité mémoire, qui fait l'interface entre le cache et le banc de registres. Le cout en circuits est très faible, car les unités de calcul d'adresse n'utilisent guère plus qu'un additionneur et un circuit de décalage très simple. L'émission mémoire parallèle est plus facile à implémenter sur les CPU RISC, qui ont des instructions mémoire séparées du reste. Les CPU CISC ne sont pas dans ce cas, ce qui fait que séparer l'unité mémoire du pipeline entier est compliqué. Les CPU superscalaires historiques étaient pour moité des CPU RISC et pour moitié des CPU CISC, ce qui fait qu'ils n'utilisaient pas ou peu l'émission mémoire parallèle. Les premiers processeurs superscalaires se contenaient de l'émission parallèle mémoire, ils ne pouvaient émettre qu'un seul accès mémoire à la fois, l'unité mémoire n'était pas dupliquée. Et ce n'est pas tant un problème que ça, du moins sur les processeurs à double ou triple émission. En effet, il est rare que l'unité mémoire doive faire deux accès simultanés. Notons que le processeur peut exécuter une instruction mémoire en parallèle d'un calcul ou d'un branchement, seuls les accès mémoire simultanés sont impossibles. ===L'émission multiple des accès mémoire avec une LSQ=== Les processeurs superscalaires larges travaillent sur des paquets de 8 à 10 instructions, il est plus fréquent d'avoir plusieurs accès mémoire par paquet. Dans ces conditions, il est utile d'avoir une '''émission multiple des accès mémoire''', à savoir d'émettre plusieurs micro-opérations mémoire en même temps. Les processeurs superscalaires modernes sont capables d'émettre plusieurs lectures/écritures simultanément. Par exemple, ils peuvent émettre une lecture en même temps qu'une écriture, ou plusieurs lectures, ou plusieurs écritures. Il faut noter que selon le processeur, il peut y avoir des restrictions quant aux accès mémoire émis en même temps. Par exemple, certains processeurs peuvent émettre une lecture avec une écriture en même temps, mais pas deux lectures ni deux écritures. Ou encore, ils peuvent émettre deux lectures, une lecture et une écriture, mais pas deux écritures en même temps. Dans la majorité des cas, les processeurs ne permettent pas d'émettre deux écritures en même temps, alors qu'ils supportent plusieurs lectures. Il faut dire que les lectures sont plus fréquentes que les écritures. Les processeurs qui autorisent toutes les combinaisons de lecture/écriture possibles, sont rares. L'émission multiple des accès mémoire s'implémente dans l'unité mémoire. Pour rappel, l'unité mémoire s'interpose entre le cache L1 et le reste du pipeline. Dans le cas le plus simple, elle incorpore une ''load/store queue'' et des unités de calcul d'adresse. L'émission multiple des accès mémoire demande de rendre la ''Load-store queue'' multi-ports, afin de gérer plusieurs micro-opérations mémoire simultanées. Il faut aussi dupliquer les unités de calcul d'adresse reliées à la LSQ, chaque port d'émission mémoire devant avoir sa propre unité de calcul d'adresse. : Dans ce qui suit, nous parlerons d'AGU (''Adress Generation Unit'') pour désigner les unités de calcul d'adresse, et de LSQ pour designer la ''Load-store queue''. [[File:Processeur non-superscalaire basique 02.png|vignette|upright=1|Processeur non-superscalaire basique]] Dupliquer les ports de la LSQ et les AGU est nécessaire. Mais qu'en est-il du cache ? La réponse varie suivant le processeur. Il y a deux grandes méthodes pour implémenter l'émission multiple des accès mémoire. La première méthode ajoute des ports de lecture/écriture au cache de données, pour faire des lectures/écritures simultanées. Il faut alors dupliquer des unités de calcul, rajouter les ports sur le cache, mais aussi rajouter des ports sur la ''load/store queue'', pour qu'elle échange des données/commandes mémoire avec le cache. Le cout en circuits est alors conséquent La seconde duplique les unités de calcul, mais pas les ports d'accès au cache. Les accès mémoire sont alors accumulés dans la ''load/store queue'', qui sont ensuite traités un par un par le cache. Un exemple est celui des processeurs AMD de micro-architecture K7 K8, à savoir les anciens AMD Athlon et AMD Athlon 64, qui pouvaient calculer trois adresses simultanément, mais ne pouvaient faire qu'un seul accès au cache par cycle. Le cout en circuit est modéré : les unités de calcul coutent très peu, les ports de la ''load/store queue'' coutent déjà un peu plus. Quelques processeurs utilisaient des solutions intermédiaires. Un processeur utilisant cette technique est, par exemple, capable d'émettre quatre µops mémoire en même temps, mais avec seulement deux ports sur le cache. Un exemple classique étant les Pentium 1 et 2 d'Intel, dont nous parlerons dans le chapitre suivant. ===L'émission séparée des LOAD et STORE=== De rares processeurs ont des ports d'émission qui peuvent faire indifféremment lecture comme écritures. Mais les processeurs modernes préfèrent séparer ports d'émission pour les lectures et les écritures. Un exemple est celui des processeurs Intel de micro-architecture Nehalem, qui avaient un port de lecture et un port d'écriture tous deux reliés à la ''Load-store queue'', chacun avec leur propre unité de calcul d'adresse. Comme autre exemple, les processeurs skylake ont une LSQ avec deux ports de lecture et un port d'écriture, ce qui permet d'émettre deux lectures en même temps qu'une écriture. Dans cette section, nous allons étudier le cas avec des ports séparés pour les lectures et écritures. En général, les processeurs ont un peu plus de ports de lectures que d'écritures. Une raison est qu'un programme a, en moyenne, un tout petit peu plus de lectures que d'écritures. Mais la différence n'est pas énorme, c'est un léger déséquilibre. Une seconde raison est que l'exécution dans le désordre marche bien mieux quand les lectures ont lieu le plus tôt possible. Donc mieux vaut privilégier les lectures à l'émission, pour qu'elles se fassent un peu en avance. En pratique, la différence n'est pas très marquée. Il est courant d'avoir un port de lecture et un d'écriture, les CPU superscalaires très larges ont souvent deux ports de lecture et deux d'écriture. A la rigueur, quelques CPU ont trois ports de lecture et deux d'écriture. Le cas le plus simple est celui où on peut émettre une lecture et une écriture en même temps. Il s'implémente facilement si la LSQ est en réalité une simple ''Store queue'', à savoir qu'elle met en avance les écritures uniquement. Pour rappel, les écritures sont mises en attente dans la ''Store queue'', mais les lectures sont exécutées immédiatement. Une fois l'adresse à lire connue, la lecture consulte la ''Store queue'' pour détecter les dépendances de données, puis s'exécute. La donnée à lire est lue soit depuis la ''Store queue'', soit depuis le cache. [[File:Emission LOAD - Store séparée.png|vignette|upright=1|Émission LOAD - Store séparée]] L'implémentation de l'émission multiple est alors très simple. La ''Store queue'' a déjà deux ports, avec un pour les lectures et un autre pour les écritures. Idem pour le cache, qui a naturellement un port de lecture et un d'écriture. Émettre une lecture et une écriture simultanément ne demande donc pas de modification digne de ce nom. La ''Store queue'' doit juste supporter une lecture et une écriture simultanée. Ajouter l'émission d'une seconde lecture n'est pas compliquée : il faut juste rajouter un second port de lecture sur le cache et la ''Store queue''. Pour rappel, l'unité mémoire est précédée par une structure qui met en attente les micro-opérations mémoire avant leur émission finale. La structure en question varie suivant l'implémentation, mais un cas particulier se marie bien avec l'émission multiple des µops mémoire. Il s'agit du cas avec une file de µops séparée pour les lectures et une autre pour les écritures, appelées respectivement la ''file de µops LOAD'' et la ''file de µops STORE''. Il est alors possible d'émettre une lecture et une écriture lors du même cycle d'horloge. Le port de lecture alimente une unité de calcul d'adresse dédiée, directement reliée au cache. Le port d'écriture du cache alimente une unité de calcul, qui est suivie par une file d'écriture, elle-même reliée au cache. Les processeurs AMD de micro-architecture K6 utilisaient cette technique. [[File:Double émission avec le Load Ordering, Store Ordering.jpg|centre|vignette|upright=2.5|Double émission avec le Load Ordering, Store Ordering]] ==Les CPU superscalaire à exécution dans le désordre== La superscalarité modifie aussi les circuits dédiés à l'exécution dans le désordre. Par contre, elle modifie les unités de renommage de registre, le ROB et les autres structures liées à l'exécution dans le désordre, au sens large. Dans cette section, nous allons voir comment les étages du pipeline dédiés à l'exécution dans le désordre sont modifiées sur un CPU superscalaire. ===L'exécution dans le désordre centralisé : la différence entre largeur de décodage et d'émission=== Plus haut, nous avons montré qu'un processeur pouvait avoir plus de ports d'émission que de ports de décodage. On dit aussi que sa ''largeur de décodage'' était supérieure à sa ''largeur d'émission''. La '''largeur de décodage''' est le nombre de µops fournies par l'unité de décodage par cycle d'horloge, la '''largeur d'émission''' est le nombre maximal de µops émises par cycle d'horloge. Et n'allez pas croire qu'un surplus de ports d'émission est du gâchis. Sur les processeurs avec exécution dans le désordre, ce surplus peut être utilisé. Mais avant toute chose, évacuons un détail. Vous avez sans doute pensé que le surplus peut être exploité par les instructions machines décodées en plusieurs µops . Par exemple, si une instruction est décodée en deux µops, on pourrait avoir un port d'émission en plus pour en profiter. Mais les ports de décodage sont *en sortie du décodeur*, et contiennent des µops, pas des instructions. Le surplus de ports d'émission peut occasionnellement utiliser le surplus de ports de décodage, mais cela implique que des µops ont été mises en attente dans l'unité d'émission, pour être émises par la suite. Et cela implique l'usage d'une fenêtre d'instruction ou d'une file de µops. Le principe est que les µops sont accumulées dans la fenêtre d'instruction, en attendant leurs opérandes ou la résolution d'une dépendance. Et si l'occasion se présente, une µops est émise sur chaque port d'émission. Il y a donc un gain en performances, mais il faut faire quelques précisions sur son origine. La largeur d'émission utilisée ne peut pas dépasser, *en moyenne*, la largeur de décodage. Et c'est intuitif : un CPU qui décode 4 µops par cycle ne peut pas émettre 6 par cycle en moyenne, sur le temps long, il n'y a pas de µops en surplus qui sortent du vide. Par exemple, sur un processeur quadruple émission, si on analyse combien d'instructions sont émises par cycle, il peut y avoir des cycles où la largeur d'émission est totalement utilisée, mais ce sera compensé par des périodes où elle est fortement sous-utilisée. Les µops sont accumulées en cas de sous-utilisation, puis consommées en cas d'utilisation complète. Les deux périodes se compensent. Et c'est là que l'on peut comprendre d'où vient le gain en performance. Les cycles où les ports d'émission sont sous-utilisés existent systématiquement, que le processeur utilise l'exécution dans le désordre ou pas. Ils sont liés à la présence de dépendances de données, aux contraintes d’appariement, etc. Elles tendent à réduire la moyenne du nombre de µops émises par cycle. Par contre, ce sont les cycles de rattrapage, où les ports en surplus sont utilisés, qui font remonter la moyenne. Le gain en performance est donc lié à ces cycles de rattrapage, où les µops accumulées sont émises dans les ports en surplus. Et ils n'existent que sur les processeurs avec une file/fenêtre d'instruction. ===Les CPU superscalaires à fenêtres d'instruction décentralisées : la largeur de ''dispatch'' et de ''schedule''=== Les processeurs précédents utilisaient une unité d'émission simple, qui pouvait utiliser l'émission dans l'ordre ou dans le désordre. Elle peut être remplacée par une fenêtre d'instruction centralisée dans que cela quoi que ce soit aux explications précédentes, idem avec une station de réservation centralisée. En soi, l'exécution dans le désordre est donc totalement compatible avec une implémentation simple de la superscalarité. Par contre, des subtilités surviennent avec les fenêtres d'instruction décentralisées. Pour rappel, sur ces processeurs, les µops sont émises en deux phases. Une première phase de ''dispatch'' qui envoie les µops vers la fenêtre d'instruction adéquate, sans détecter les dépendances de données. La seconde phase de ''schedule'' gère les dépendances de données, elle met en attente les µops en attendant que l'unité soit disponible pour les exécuter, en attendant aussi que les opérandes soient disponibles. Et à ce petit jeu, il y a une différence entre le nombre de µops qui sont ''dispatch par cycle'' et celles qui sont ''schedule''. Dit autrement, la largeur d'émission est scindée en une '''largeur de ''dispatch''''' et une '''largeur de ''schedule''. Par exemple, prenons un processeur triple émission avec deux stations de réservation : une pour les µops entières/mémoire, et une autre pour les µops flottantes. L'unité d'émission peut ''dispatch'' maximum trois µops par cycle : soit trois µops entières/mémoire, soit trois flottantes, soit un mix de trois µops différentes. Les stations de réservation, quant à elles, peuvent chacune ''schedule'' 3 µops par cycle, vers trois ALUs entières pour la station de réservation entière/mémoire, vers trois FPS pour la station de réservation flottante. De quoi ''dispatch'' trois µops, mais d'en ''schedule'' six, à chaque cycle. Et cet exemple ne sort pas de nulle part : les CPU x86 d'Intel et AMD sont globalement dans ce cas, même si les chiffres varient d'une microarchitecture à l'autre. L'exemple précédent colle parfaitement à la microarchitecture K7 d'AMD, utilisée sur les processeurs Athlon dans les années 2000. [[File:AMD K7.png|centre|vignette|upright=2.5|AMD K7, microarchitecture.]] Un détail est que le nombre de ports de lecture n'est pas le même selon que le processeur utilisé des stations de réservation ou une fenêtre d’instruction. Rappelons la terminologie utilisée dans ce cours. Les fenêtres d'instruction se contentent de mémoriser la micro-opération à émettre et quelques bits pour la disponibilité des opérandes. Par contre, les stations de réservations mémorisent aussi les opérandes des instructions. Et cette différence a une influence le nombre de ports de lecture du banc de registre. Supposons que toutes les instructions sont dyadiques, à deux opérandes. Comptons le nombre de ports nécessaires pour alimenter les unités de calcul, sans utiliser d'arbitre de banc de registre. Avec une fenêtre d'instruction, il faut le double du nombre de ports de ''dispatch''. Mais avec des stations de réservation, il faut le double des ports de ''schedule''. La raison est que les registres sont lus après émission avec une fenêtre d'instruction, avant avec des stations de réservation (sous-entendu, après le décodage). or, comme dit plus haut, les processeurs superscalaires larges ont souvent plus de ports de ''schedule'' que de ''dispatch''. ===L'unité de renommage superscalaire=== Avec la superscalarité, l'unité de renommage de registre doit gérer le cas où des instructions consécutives ont des dépendances de registre. Par exemple, prenons un processeur à double émission, qui renomme deux instructions consécutives. Si elles ont une dépendance de registre, le renommage de la seconde instruction dépend du renommage de la première. La première doit être renommée et le résultat du renommage est utilisé pour renommer la seconde, ce qui empêche d'utiliser deux unités de renommage séparées. Pour cela, elle renomme les registres sans tenir compte des dépendances, pour ensuite corriger le résultat. [[File:Unité de renommage superscalaire.png|centre|vignette|upright=2|Unité de renommage superscalaire.]] Seules les dépendances lecture-après-écriture doivent être détectées, les autres étant supprimées par le renommage de registres. Repérer ce genre de dépendances se fait assez simplement : il suffit de regarder si un registre de destination d'une instruction est un opérande d'une instruction suivante. [[File:Détection des dépendances sur un processeur superscalaire.png|centre|vignette|upright=2|Détection des dépendances sur un processeur superscalaire.]] Ensuite, il faut corriger le résultat du renommage en fonction des dépendances. Si une instruction n'a pas de dépendance avec une autre, on la laisse telle quelle. Dans le cas contraire, un registre opérande sera identique avec le registre de destination d'une instruction précédente. Dans ce cas, le registre opérande n'est pas le bon après renommage : on doit le remplacer par le registre de destination de l'instruction avec laquelle il y a dépendance. Cela se fait simplement en utilisant un multiplexeur dont les entrées sont reliées à l'unité de détection des dépendances. On doit faire ce replacement pour chaque registre opérande. [[File:Correction des dépendances sur un processeur superscalaire.png|centre|vignette|upright=2|Correction des dépendances sur un processeur superscalaire.]] ===Le tampon de réordonnancement et le banc de registre d'un CPU superscalaire=== Le ROB doit aussi être modifié pour émettre et terminer plusieurs instructions en même temps, en ajoutant des ports de lecture/écriture. Pour un CPU superscalaire peut émettre 8 µops à la fois, on se dit que le ROB peut retirer 8 µops par cycle maximum. Et c'est très souvent le cas en pratique. Cependant, certains processeurs ont un ROB qui peut retirer le double ! Un exemple est celui des CPU de micro-architecture Skymont, qui peuvent émettre 8 µops par cycle, mais en retirer 16. Quelques CPU de marque AMD étaient aussi dans ce cas. L'avantage est que cela permet de réduire la taille du ROB, du banc de registre et de quelques autres structures matérielles liées à l'exécution dans le désordre. Le gain sur-compense le cout en transistor nécessaire pour retirer 16 µops par cycle. Réduire le nombre de ports d'émission permet de garder un banc de registre, des fenêtres d'instruction et un ROB simples, rapides et économes en énergie. Et mine de rien, c'est là un compromis important : à quoi bon augmenter le nombre de ports d'émission si c'est pour que ce soit compensé par une fréquence plus faible ? Par contre, réduire le nombre de ports augmente la ''port contention'', surtout si on a mal réparti les ALU. Il faut donc trouver un compromis entre réduire le nombre de ports d'émission d'un côté, réduire la ''port contention'' de l'autre. ==Les optimisations des processeurs superscalaires larges== Les processeurs superscalaires larges ont des optimisations spécifiques, qui n'ont pas de sens sur les processeurs superscalaires étroits. Dans les grandes lignes, elles se classent en deux types. Les premières permettent de charger/décoder plus de 5-10 instructions simultanément. Le problème est alors que les branchements viennent mettre leur grain de sel et qu'il faut en tenir compte. Les secondes corrigent des problèmes qui apparaissent quand un CPU intègre plus de 5 à 10 unités de calcul. ===L'étape de chargement d'un CPU superscalaire large=== Charger plusieurs instructions pose de nombreux problèmes sur les processeurs superscalaires larges, qui émettent de 5 à 10 instructions simultanées. Pour rappel, la stratégie basique pour charger N instructions est de charger un bloc de N instructions consécutives. Les instructions après un branchement ne sont pas décodées ni exécutées, ce qui entraine une petite baisse de performance. Pour un N petit, la perte de performance est mineure. Mais pour un N large, elle devient suffisamment importante pour que la conception du CPU doit alors être repensée de fond en comble. Les analyses statistiques disent qu'il y a environ un branchement tous les 4 à 6 instructions. Un CPU a donc tendance à décoder des paquets de 3 à 5 instructions consécutives, avant qu'un branchement casse la dynamique. Or, cela pose des problèmes pour utiliser plusieurs décodeurs. L'implémentation basique, qui charge N instructions consécutives pour alimenter N décodeurs, fonctionne donc jusqu'à 4/6 décodeurs. Au-delà de 5 instructions, un CPU superscalaire doit être adapté pour gérer les branchements dans les paquets d'instruction qu'il émet. Le nombre de 5 instructions ne sort donc pas de nulle part. La première solution utilise un cache de µops, avec un ''front-end'' capable de charger/décoder 4/5 instructions à la fois, pas plus. L'idée est que la majorité des instructions proviennent du cache de µops (plus de 80%), ce qui fait qu'on peut émettre environ 10 instructions à partir du cache de µops, pas besoin d'avoir beaucoup de décodeurs. Les performances sont cependant dégradées lors d'un défaut de cache de µops. Les instructions doivent alors être chargées depuis le cache d'instruction, par paquets de 4-5, puis sont décodées par 4-5 décodeurs, ce qui ne permet pas d’alimenter les unités de calcul à plein régime. La solution précédente joue sur la distinction entre pots de décodage et d'émission. Elle permet d'émettre un grand nombre de µops sans pour autant charger un grand nombre d'instructions en même temps. En quelque sorte, ce n'est qu'une méthode de contournement, le CPU superscalaire reste de type étroit. Mais il est aussi possible d'utiliser un '''''front-end large''''', capable de charger une bonne dizaine d'instructions en même temps. La seule difficulté est de tenir compte de la présence de branchements. Pour rappel, avec un ''front-end'' étroit, les instructions situées après le premier branchement pris sont annulées, elles ne sont pas décodées ni exécutées. [[File:Fetch sur un processeur superscalaire avec prediction de branchements.png|centre|vignette|upright=2|Fetch sur un processeur superscalaire avec prédiction de branchements.]] Un ''front-end'' large charge les instructions de destination du branchement et les placent à sa suite. Ils chargent deux blocs à la fois et les fusionnent en un seul qui ne contient que les instructions présumées utiles. [[File:Cache d'instructions autoaligné.png|centre|vignette|upright=2|Cache d'instructions auto-aligné.]] Mais cela demande de charger deux blocs de mémoire en une fois, ce qui demande un cache d'instruction multiports. Il faut aussi ajouter un circuit pour assembler plusieurs morceaux de blocs en un seul : le fusionneur (''merger''). En pratique, cette solution demande d'ajouter un second port de lecture au cache, pour un cout en hardware important, avec un impact négatif sur le temps d'accès au cache. Et il faut rajouter le fusionneur, qui prend un étage de pipeline à lui tout seul. Et encore, cette solution ne gère pas le cas où un bloc contient plusieurs branchements ! [[File:Implémentation d'un cache d'instructions autoaligné.png|centre|vignette|upright=2|Implémentation d'un cache d'instructions auto-aligné.]] Les exemples précédents partent du principe qu'il n'y a qu'un seul branchement dans un bloc. Mais quand les blocs deviennent assez longs, il est fréquent d'avoir plusieurs branchements dans un bloc. Et il est même possible d'avoir plusieurs branchements pris. Dans un bloc de 8-10 instructions, ce n'est pas rare, et les CPU superscalaires à 8-10 voies sont la règle dans les CPU des années 2020. Pour gérer ce cas, il faut ajouter des ports supplémentaires au cache d'instruction et adapter le fusionneur, pour qu'il puisse fusionner trois ou quatre blocs. De plus, il faut utiliser une unité de prédiction de branchement capable de ''prédire plusieurs branchements par cycle''. ===Le décodage superscalaire large=== Le décodage des instructions n'est pas censé dépendre de l'implémentation du chargement. En théorie, une fois plusieurs blocs d'instruction fusionnés, on peut décoder les instructions en parallèle. Cependant, certains processeurs effectuent le décodage en tenant compte de la manière dont les branchements découpent les blocs. Les processeurs Atom d'Intel utilisent une solution de ce type, qui couple prédiction de branchement et décodage parallèle. Ils chargent/décodent des paquets de 3 instructions consécutives, chaque paquet étant séparé du précédent par un branchement. Pour cela, en plus d'avoir un fusionneur adapté, ils regroupent les décodeurs en ''clusters'', chacun regroupant 3 décodeurs, et pouvant décoder trois instructions consécutives. Les micro-architectures Tremont et Crestmont avaient deux ''clusters'' (6 décodeurs au total), la micro-architecture Skymont en intègre trois (9 décodeurs au total). Avec deux ''clusters'', le fonctionnement est le suivant. Si le code est composé d'une suite d'instruction sans branchements, seul le premier ''cluster'' est utilisé, limitant le décodage à trois instructions par cycle. Par contre, si l'une des trois instructions décodée est un branchement, alors le second ''cluster'' décode les trois instructions situées après ce branchement, à partir de l'adresse de destination. Avec un troisième ''cluster'', c'est la même chose, sauf qu'il faut qu'il y ait un branchement dans les trois instructions décodées par le second ''cluster''. Mais comment faire pour savoir quels décodeurs activés, quels ''clusters'' sont actifs ? C'est la prédiction de branchement qui commande. Avec deux ''clusters'', elle détermine si un branchement est présent dans les trois instructions à charger, et quelle est son adresse de destination. Sans branchement, elle prévient les décodeurs et les trois instructions chargées sont décodées dans un ''cluster'' libre. Si elle prédit un branchement, elle charge les instructions adéquates, et active les deux ''clusters''. Avec trois branchements, c'est le même mécanisme, sauf que l'unité de prédiction de branchement doit être capable de prédire deux branchements par cycle. Il faut noter que les décodeurs d'un ''cluster'' sont des décodeurs simples, sans microcode. Sur les processeurs Tremont et Crestmont, le microcode était unique et était partagé entre tous les ''clusters''. Un seul ''cluster'' pouvait décoder une instruction complexe avec le microcode, l'autre devait se limiter à des instructions simples uniquement. Depuis Skymont, le microcode est partiellement dupliqué, avec un microcode par ''cluster''. Le cout en hardware peut paraitre prohibitif, pour un gain en performance faible. Sauf que c'est oublier que ce sont des processeurs Atom, sur lesquels beaucoup d'instructions sont microcodées. Les processeurs Atom ont un budget en transistor limité, ce qui fait qu'il est préférable pour eux d'implémenter des instructions en microcode que d'utiliser des unités de calcul dédiées ou des circuits de décodage complexes. Pour limiter la casse, le microcode n'est que partiellement dupliqué. Seul le microcode pour les instructions fréquente est dupliqué. Il y a un microcode secondaire pour certaines instructions, qui est lui unique et partagé par tous les ''clusters''. ===Le contournement sur les processeurs superscalaires larges=== Pour rappel, la technique du contournement (''register bypass'') permet au résultat d'une instruction d'être immédiatement utilisable en sortie de l'ALU, avant même d'être enregistré dans les registres. Implémenter la technique du contournement demande d'utiliser des multiplexeurs pour relier la sortie de l'unité de calcul sur son entrée si besoin. Il faut aussi des comparateurs pour détecter des dépendances de données. [[File:Pipeline Bypass.png|centre|vignette|upright=1|Pipeline Bypass]] La sortie de chaque ALU doit être reliée aux entrées de toutes les autres, avec les comparateurs qui vont avec. Sur les processeurs ayant plusieurs d'unités de calculs, cela demande beaucoup d’interconnexions et de comparateurs. Pour N unités de calcul, cela demande 2 * N² interconnexions, implémentées avec 2N multiplexeurs de N entrées chacun. Si c'est faisable pour 2 ou 3 ALUs, la solution est impraticable au-delà. Non seulement ce serait un enfer à câbler, mais les fils seraient tellement longs que la fréquence du processeur en prendrait un coup. Notez que cela vaut pour les processeurs superscalaires, mais aussi pour tout processeur avec beaucoup d'unités de calcul. En théorie, un CPU à exécution dans le désordre, non-superscalaire, mais avec beaucoup d'unités de calcul, fait face au même problème. En pratique, avoir une dizaine d'ALU implique processeur superscalaire à exécution dans le désordre. D'où le fait qu'on parle du problème maintenant. La seule solution praticable est de ne pas relier toutes les unités de calcul ensemble. À la place, on préfère regrouper les unités de calcul dans différents '''agglomérats''' ('''cluster'''). Le contournement est alors possible entre les unités d'un même agglomérat, mais pas entre agglomérats différents. Évidemment, certaines possibilités de contournement sont perdues, mais c'est compensé par un gain en fréquence et un cout en circuit/fils réduit. C'est un bon compromis, ce qui explique que presque tous les processeurs modernes l'utilisent. Les rares exceptions sont les processeurs POWER 4 et POWER 5, qui ont préféré se passer de contournement pour garder un processeur très simple et une fréquence élevée. ===Le banc de registre des processeurs superscalaires larges=== Pour émettre deux opérations entières simultanément, il faut lire deux fois plus d'opérandes. Et il faut aussi écrire deux résultats dans les registres, en même temps. Pour cela, on double les ports du banc de registre. En général, il faut au maximum 2 ports de lecture et un port d'écriture par unité de calcul. Le problème, c'est que plus un banc de registres a de ports, plus il utilise de circuits, est compliqué à concevoir, consomme de courant et chauffe. Avec plusieurs dizaines d'unités de calcul différentes, ajouter autant de ports impacterait la fréquence du banc de registres et réduirait les performances, voire est totalement impraticable à cause des contraintes thermiques. Les processeurs superscalaires larges doivent donc trouver des solutions. La première solution utilise un banc de registre unique, mais n'utilise pas autant de ports que le pire des cas le demanderait. Le processeur détecte quand il n'y a pas assez de ports pour servir toutes les micro-opérations. Quand ça arrive, l'unité d'émission émet assez de micro-opérations pour exploiter les ports disponibles, mais met en attente les micro-opérations en trop, le temps que les ports se libèrent. Pour cela, le banc de registre est géré par un circuit d'arbitrage spécialisé, intégré à l'unité d'émission, l’'''arbitre du banc de registres''' (''register file arbiter''). Une autre solution est de dupliquer le banc de registres en plusieurs exemplaires qui contiennent exactement les mêmes données, mais avec moins de ports de lecture/écriture. Un exemple est celui des processeurs POWER, Alpha 21264 et Alpha 21464. Sur ces processeurs, le banc de registre est dupliqué en plusieurs exemplaires, qui contiennent exactement les mêmes données. Les écritures dans les registres sont envoyées à tous les bancs de registres, afin de garantir que leur contenu est identique. Chaque exemplaire a exactement deux ports de lecture. La conception du processeur est simplifiée, que ce soit au niveau du câblage, que de la conception des bancs de registres. Une dernière solution découpe le banc de registres en plusieurs bancs de registres plus petits, avec un réseau d'interconnexions pour échanger des données entre bancs de registres. Dans la plupart des cas, cette séparation est invisible du point de vue de l'assembleur et du langage machine. Le processeur se charge de transférer les données entre bancs de registres suivant les besoins. Sur d'autres processeurs, les transferts de données se font via une instruction spéciale, souvent appelée ''COPY''. [[File:Banc de registres distribué.png|centre|vignette|upright=2|Banc de registres distribué.]] Sur de certains processeurs, un branchement est exécuté par une unité de calcul spécialisée. Or les registres à lire pour déterminer l'adresse de destination du branchement ne sont pas forcément dans le même agglomérat que cette unité de calcul. Pour éviter cela, certains processeurs disposent d'une unité de calcul des branchements dans chaque agglomérat. Dans les cas où plusieurs unités veulent modifier le ''program counter'' en même temps, un système de contrôle général décide quelle unité a la priorité sur les autres. Mais d'autres processeurs fonctionnent autrement : seul un agglomérat possède une unité de branchement, qui peut recevoir des résultats de tests de toutes les autres unités de calcul, quel que soit l’agglomérat. ==La macro-fusion : une optimisation du décodage parallèle== Les processeurs superscalaires supportent la technique dite de '''macro-fusion''', qui permet de fusionner deux-trois instructions consécutives en une seule micro-opération. Par exemple, il est possible fusionner une instruction de test et une instruction de saut en une seule micro-opération de branchement. Il s'agit là de l'utilisation la plus importante de la macro-fusion sur les processeurs x86 modernes. La macro-fusion est utilisée pour fusionner un test et un saut en un branchement unique. L'optimisation est présente sur la plupart des CPU superscalaires modernes. Les CPU x86 l'implémentent, mais aussi quelques CPU ARM et les processeurs Apple de série M (M1 à M5, et potentiellement plus quand ils seront sortis). La fusion des instructions se fait lors du décodage des instructions, grâce à la coopération des décodeurs. Elle marche systématiquement si l'instruction de test et le saut sont consécutives, qu'elles forment bien une paire d'instruction. Par contre, insérer une instruction entre les deux empêche la macro-fusion de faire son travail, sauf si l'instruction insérée est un NOP. Les processeurs ARM64 avaient une forme spécifique de macro-fusion, utilisée pour un cas très précis : charger une constante immédiate de 32 ou 64 bits, dans un registre 64 bits. Les CPU ARM 64 bits utilisent des instructions codées sur 32 bits, qui ne peuvent encoder que des constantes immédiates de 16 bits. Pour charger une constante de 32/64 bits, il faut s'y prendre en plusieurs fois. Une première instruction MOVZ charge les 16 premiers bits, puis 2 à 4 instructions MOVK remplissent le reste. MOVZ met à zéro les 48 bits de poids fort, MOVK permet de sélectionner le doublet à modifier dans le registre (on modifie 16 bits, dont on choisit la place, le reste du registre est laissé tel quel). ARM permet de fusionner cette suite d'instructions en une seule µop MOV avec une constante immédiate. On parle alors de '''''MOVZ + MOVK fusion'''''. Une dernière utilisation, [https://www2.eecs.berkeley.edu/Pubs/TechRpts/2016/EECS-2016-130.pdf proposées pour le jeu d'instruction RISC-V], fusionnait un calcul d'adresse et une instruction LOAD/STORE en une seule instruction d'accès mémoire. Elle a été envisagée à une époque où le jeu d'instruction RISC-V ne gérait pas les modes d'adressage base + indice. L'idée était d'avoir une unité mémoire qui gérait le mode d'adressage base + indice et de fusionner des instructions pour l'exploiter. Mais de nos jours, le jeu d'instruction RISC-V intègre le mode d'adressage base + indice, ce qui fait que la macro-fusion n'a aucun intérêt pour ce cas précis. La macro-fusion a de nombreuses limitations en pratique. En théorie, on pourrait l'utiliser au-delà des deux situations mentionnées plus haut. Par exemple, pour fusionner une addition et une multiplication en une seule instruction MAD. Mais l'intérêt est assez faible. Notons que la macro-fusion ne permet que de fusionner des paires d'instructions. Aller au-delà demanderait un cout en circuit trop important. Déjà que tester deux paires utilise pas mal de comparateurs... ==Le ''stack engine'' : une optimisation de la pile d'appel== Les processeurs modernes intègrent une optimisation liée au pointeur de pile. Pour rappel, sur les architectures modernes, le pointeur de pile est un registre utilisé pour gérer la pile d'appel, précisément pour savoir où se trouve le sommet de la pile. Il est manipulé par les instructions CALL, RET, PUSH et POP, mais aussi par les instructions d'addition/soustraction pour gérer des cadres de pile de taille variable. De plus, il peut servir d'opérande pour des instructions LOAD/STORE, afin de lire/écrire des variables locales, les arguments d'une fonction, et autres. C'est donc un registre général adressable, intégré au banc de registre, altéré par l'unité de calcul entière. L'incrémentation/décrémentation du pointeur de pile passe donc par l'unité de calcul, lors des instructions CALL, RET, PUSH et POP. Mais, l'optimisation que nous allons voir permet d'incrémenter/décrémenter le pointeur de pile sans passer par l'ALU, ou presque. L'idée est de s'inspirer des architectures avec une pile d'adresse de retour, qui intègrent le pointeur de pile dans le séquenceur et l'incrémentent avec un incrémenteur dédié. ===Le ''stack engine''=== L'optimisation que nous allons voir utilise un '''''stack engine''''' intégré à l'unité de contrôle, au séquenceur. Le processeur contient toujours un pointeur de pile dans le banc de registre, cela ne change pas. Par contre, il n'est pas incrémenté/décrémenté à chaque instruction CALL, RET, PUSH, POP. Un compteur intégré au séquenceur est incrémenté à la place, nous l’appellerons le '''compteur delta'''. La vraie valeur du pointeur de pile s'obtient en additionnant le compteur delta avec le registre dans le banc de registre. Précisons que si le compteur delta vaut zéro, la vraie valeur est dans le banc de registre et peut s'utiliser telle quelle. Lorsqu'une instruction ADD/SUB/LOAD/STORE utilise le pointeur de pile comme opérande, elle a besoin de la vraie valeur. Si elle n'est pas dans le banc de registre, le séquenceur déclenche l'addition compteur-registre pour calculer la vraie valeur. Finalement, le banc de registre contient alors la bonne valeur et l'instruction peut s'exécuter sans encombre. L'idée est que le pointeur de pile est généralement altéré par une série d'instruction PUSH/POP consécutives, puis des instructions LOAD/STORE/ADD/SUB utilisent le pointeur de pile final comme opérande. En clair, une bonne partie des incrémentations/décrémentation est accumulée dans le compteur delta, puis la vraie valeur est calculée une fois pour toutes et est utilisée comme opérande. On accumule un delta dans le compteur delta, et ce compteur delta est additionné quand nécessaire. Précisons que le compteur delta est placé juste après le décodeur d'instruction, avant même le cache de micro-opération, l'unité de renommage et l'unité d'émission. Ainsi, les incrémentations/décrémentations du pointeur de pile disparaissent dès l'unité de décodage. Elles ne prennent pas de place dans le cache de micro-opération, ni dans la fenêtre d'instruction, ni dans la suite du pipeline. De nombreuses ressources sont économisées dans le ''front-end''. Mais utiliser un ''stack engine'' a aussi de nombreux avantages au niveau du chemin de données/''back-end''. Déjà, sur les processeurs à exécution dans le désordre, cela libère une unité de calcul qui peut être utilisée pour faire d'autres calculs. Ensuite, le compteur delta mémorise un delta assez court, de 8 bits sur le processeur Pentium M, un peu plus pour les suivants. L'incrémentation se fait donc via un incrémenteur 8 bits, pas une grosse ALU 32/64 bits. Il y a un gain en termes de consommation d'énergie, un incrémenteur 8 bits étant moins gourmand qu'une grosse ALU 32/64 bits. ===Les points de synchronisation du delta=== La technique ne fonctionne que si la vraie valeur du pointeur de pile est calculée au bon moment, avant chaque utilisation pertinente. Il y a donc des '''points de synchronisation''' qui forcent le calcul de la vraie valeur. Plus haut, nous avions dit que c'était à chaque fois qu'une instruction adresse le pointeur de pile explicitement, qui l'utilise comme opérande. Les instructions CALL, RET, PUSH et POP ne sont pas concernées par elles utilisent le pointeur de pile de manière implicite et ne font que l'incrémenter/décrémenter. Mais dans les faits, c'est plus compliqué. D'autres situations peuvent forcer une synchronisation, notamment un débordement du compteur delta. Le compteur delta est généralement un compteur de 8 bits, ce qui fait qu'il peut déborder. En cas de débordement du compteur, le séquenceur déclenche le calcul de la vraie valeur, puis réinitialise le compteur delta. La vraie valeur est donc calculée en avance dans ce cas précis. Précisons qu'un compteur delta de 8 bits permet de gérer environ 30 instructions PUSH/POP consécutives, ce qui rend les débordements de compteur delta assez peu fréquent. À noter que si le compteur delta vaut zéro, il n'y a pas besoin de calculer la vraie valeur, le séquenceur prend cette situation en compte. Un autre point de synchronisation est celui des interruptions et exceptions matérielles. Il faut que le compteur delta soit sauvegardé lors d'une interruption et restauré quand elle se termine. Idem lors d'une commutation de contexte, quand on passe d'un programme à un autre. Pour cela, le processeur peut déclencher le calcul de la vraie valeur lors d'une interruption, avant de sauvegarder les registres. Pour cela, le processeur intègre un mécanisme de sauvegarde automatique, qui mémorise la valeur de ce compteur dans le tampon de réordonnancement, pour forcer le calcul de la vraie valeur en cas de problème. La technique du ''stack engine'' est apparue sur les processeurs Pentium M d'Intel et les processeurs K10 d'AMD, et a été conservée sur tous les modèles ultérieurs. L'implémentation est cependant différente selon les processeurs, bien qu'on n'en connaisse pas les détails et que l'on doive se contenter des résultats de micro-benchmarks et des détails fournit par Intel et AMD. Selon certaines sources, dont les manuels d'optimisation d'Agner Fog, les processeurs AMD auraient moins de points de synchronisation que les processeurs Intel. De plus, leur ''stack engine'' serait placé plus loin que prévu dans le pipeline, après la file de micro-opération. ==La micro-fusion et la délamination== La '''micro-fusion''' est une optimisation qui retarde le décodage réel de certaines instructions assez loin dans le pipeline. Elle concerne surtout les instructions ''load-op'', mais pas que. D'autres instructions mémoire peuvent subir cette optimisation. L'essentiel est que, sans micro-fusion, ces instructions sont décodées en plusieurs micro-opérations. La micro-fusion retarde le décodage final de ces instructions, qui se fait assez tard dans le pipeline. Pour comprendre l'intérêt, nous allons devoir faire quelques explications. L'optimisation en question fait le décodage en deux temps. Prenons l'exemple d'une opération ''load-op'', qui est censée être décodée en deux micro-opérations : une lecture, puis une opération. Le décodage produit une '''macro-opération''' (terme d'Intel), qui est propagée dans le pipeline, pour être scindée en deux micro-opérations un peu avant les ALU et l'unité mémoire. La macro-opération est techniquement une micro-opération comme une autre, sauf que ce n'est pas la micro-opération finale. L'unité d'émission découpe cette macro-opération en deux micro-opérations, qui sont émises l'une après l'autre. Pour résumer, les instructions ''load-op'' ne sont complétement découpées en micro-opération lors de l'émission, pas lors du décodage. L'optimisation impacte ce qui se situe entre le décodeur d'instruction et l'unité d'émission : l'unité de renommage, la file de micro-opérations, le ''Loop Stream Detector'' et/ou le cache de micro-opération. L'intérêt de l'optimisation est que l'on fusionne deux micro-opérations en une seule macro-opération. Le cache de micro-opération et la file de micro-opération peuvent mémoriser plus de micro-opérations, vu que certaines sont fusionnées. Quant à l'unité de renommage, elle renomme une seule macro-opération au lieu de renommer deux micro-opérations, ce qui augmente le débit du renommage de registre. Par contre, cela complique un peu le travail de l'unité d'émission, qui doit découper les instructions ''load-op'' en deux et émettre les micro-opérations l'une après l'autre. Il faut noter qu'il n'y a pas de fusion proprement dite. Le décodeur ne décode pas deux micro-opérations séparées, avant de les fusionner. En réalité, il génère directement une macro-opération, qui correspond à deux micro-opérations, que ce soit plus tard dans le pipeline ou sur une autre architecture. D'ailleurs, la micro-fusion a lieu à l'intérieur d'une instruction, qui est censée être traduite en plusieurs micro-opérations. Et encore : pas toutes. La conséquence est que la technique n'a de sens que sur les processeurs CISC, avec des instructions assez complexes pour être décodées en plusieurs micro-opérations. En pratique, seuls les CPU x86 sont concernés. ===Les domaines de fusion=== La micro-fusion n'est pas spécifique aux processeurs superscalaires. En théorie, on peut l'implémenter sur un processeur non-superscalaire à émission dans l'ordre. Cependant, je ne connais aucun processeur à émission dans l'ordre qui implémente la micro-fusion, même si son implémentation est en théorie possible sur de tels processeurs. La micro-fusion est implémentée différemment selon que l'émission se fasse dans l'ordre, dans le désordre, avec une fenêtre d'instruction, avec des stations de réservation, etc. J'ai dit plus haut que les micro-opérations sont générées lors de l'émission, mais le moment exact varie suivant le processeur. Pour simplifier le tout, nous parlerons de '''scission''' quand le processeur traduit une macro-opération en deux/trois micro-opérations. Le moment où a lieu la scission est théoriquement dans l'unité d'émission, mais celle-ci prend plusieurs formes selon que le processeur utilise l'émission dans l'ordre, dans le désordre, avec une fenêtre d'instruction, avec des stations de réservation, etc. Les processeurs à exécution dans l'ordre sont le cas le plus simple. L'unité d'émission est alors en charge du découpage des macro-opérations en micro-opérations. La file de micro-opération ou le cache de micro-opérations mémorisent des macro-opérations. À l'opposé, le tampon de ré-ordonnancement mémorise lui des micro-opérations. Le reste du processeur n'est pas affecté. Sur les processeurs à exécution dans le désordre, les différences sont nombreuses. Le tampon de ré-ordonnancement peut mémoriser soit les micro-opérations finales, soit des macro-opérations. Il en est de même avec la fenêtre d'instruction et/ou les stations de réservation, qu'elles soient centralisées ou décentralisées. Tout dépend de comment se fait l'implémentation. Une implémentation simple effectue la scission des macro-opérations dans l'unité d'émission, comme sur les processeurs à exécution dans l'ordre. Sur de nombreux processeurs, l'unité d'émission et de renommage sont simplement fusionnées, ce qui fait que la scission a lieu juste après le renommage de registres. En conséquence, le tampon de re-ordonnancement et les fenêtres d'instruction ne voient que des micro-opérations. Les macro-opérations disparaissent juste avant. Cette implémentation est préférée avec une fenêtre d'instruction centralisée, car on voit mal comment scinder une macro-opération juste avant l'ALU. Une autre implémentation fait la scission en sortie de la fenêtre d'instruction ou des stations de réservation, qu'elles soient centralisées ou décentralisées. Dans ce cas, le tampon de re-ordonnancement fait de même. L'avantage est que le tampon de ré-ordonnancement stocke des macro-opérations, ce qui augmente sa taille effective. Par exemple, il utilise une seule entrée pour une instruction ''load-op'', au lieu de deux sans micro-fusion de ce type. ===La délamination=== La scission peut parfois avoir lieu plus tôt, dans des circonstances très précises. Concrètement, cela ne concerne que quelques micro-architectures d'Intel, pour des instructions très précises. La raison est les limitations du format des micro-opérations, pas une histoire d'optimisation. Le processeur aimerait pouvoir faire la micro-fusion, mais ne peut pas à cause de ces limitations. Une telle situation cause alors une '''délamination''' : les micro-opérations sont scindées précocement. Par exemple, sur la micro-architecture SandyBridge d'Intel, les opérations ''load-op'' sont fusionnées, sauf si elles utilisent l'adressage indicé. Les micro-opérations de ce processeur ne peuvent avoir que deux opérandes. Trois opérandes est de trop, et les opérations ''load-op'' en adressage indicé dépassent cette limite. Dans ce cas, les micro-opérations sont fusionnées en sortie du décodeur, mais sont scindés avant d’entrer dans la file de micro-opération. L'avantage n'est pas évident, vu que l'unité de renommage suit de près le décodeur d'instruction. Il est que le cache de micro-opérations mémorise des macro-opérations, pas deux micro-opérations. ===La micro-fusion des processeurs Intel et AMD=== La micro-fusion est apparue sur le Pentium M d'Intel, et a été conservée telle quelle sur les processeurs Intel suivants. Les processeurs AMD supportent la micro-fusion depuis au moins la micro-architecture AMD K8. Mieux que ça, la délamination n'était pas utilisée car les micro-opérations acceptaient plus de 2 opérandes. Que ce soit chez AMD ou Intel, elle concerne deux types d'instructions : les écritures mémoire et les instructions ''load-op''. Sur le Pentium M, il y avait quelques limitations, l'optimisation se limitant par exemple aux registres entiers et flottants, mais pas aux registres MMX, mais pas les registres SSE (nous verrons ces registres dans quelques chapitres). Mais les processeurs modernes n'ont plus cette limitation. La micro-fusion des CPU Intel concerne deux types d'instructions : les écritures mémoire et les instructions ''load-op''. Pour les écritures, la situation est plus complexe que pour les instructions ''load-op''. Sur les processeurs Intel précédents, les écritures étaient réalisées dans deux unités séparées : le calcul d'adresse dans une unité de calcul, l'écriture proprement dite dans l'unité mémoire. Elles prenaient donc deux micro-opérations. Le processeur a changé ses unités mémoire, ce qui fait qu'elle pouvait faire les deux en une seule micro-opération. Il s'agissait alors d'une optimisation du chemin de données, plus que de la micro-fusion proprement dite, mais je la mentionne ici malgré tout. La micro-fusion sur ce processeur avait un avantage en plus des avantages usuels : elle utilisait mieux les décodeurs. Les Pentium M avaient trois décodeurs pour décoder plusieurs instructions en même temps, chose qu'on détaillera dans le chapitre sur les processeurs superscalaires. Un seul décodeur pouvait décoder toutes les instructions, les deux autres ne peuvent décoder que les instructions simples, qui se décodent en une seule micro-opération. Avec la micro-fusion, les trois décodeurs peuvent gérer les écritures mémoire et les opérations ''load-op'', vu qu'elles deviennent des instructions simples. Sans cela, elles auraient été limitées au premier décodeur. <noinclude> {{NavChapitre | book=Fonctionnement d'un ordinateur | prev=Le parallélisme mémoire | prevText=Le parallélisme mémoire | next=Exemples de microarchitectures CPU : le cas du x86 | nextText=Exemples de microarchitectures CPU : le cas du x86 }} </noinclude> he5eyaehzgiudcjzw9681ld6x95a0sa 773247 773246 2026-09-26T15:15:37Z Mewtow 31375 /* Les CPU superscalaire à exécution dans le désordre */ 773247 wikitext text/x-wiki Les processeurs vus auparavant ne peuvent émettre au maximum qu'une instruction par cycle d'horloge. Ils peuvent avoir plusieurs instructions qui s'exécutent en même temps, dans des unités de calcul séparées. C'est le cas dès qu'une instruction multi-cycles s'exécute. Par contre, ils ne peuvent démarrer qu'une seule instruction par cycle. Et quand on court après la performance, ce n'est pas assez ! Les concepteurs de processeurs ont inventés des processeurs qui émettent plusieurs instructions par cycle : les '''processeurs à émissions multiples'''. Un processeur à émission multiple charge plusieurs instructions en même temps, les décode en parallèle, puis les émet en même temps sur des unités de calculs séparées. Pour cela, il faut gérer les dépendances entre instructions, répartir les instructions sur différentes unités de calcul, et cela n'est pas une mince affaire. En général, pour un processeur à émission multiple capable d'émettre N instructions en parallèle, on parle de '''processeur à N voies''', ou encore un ''processeur à N pipelines''. [[File:Superscalarpipeline.svg|centre|vignette|upright=1.5|Pipeline RISC classique à cinq étages sur un processeur superscalaire. On voit bien que plusieurs instructions sont chargées en même temps.]] Les processeurs à émission multiple sont de deux types : les processeurs VLIW et les '''processeurs superscalaires'''. Nous mettons les processeurs VLIW de côté en attendant le prochain chapitre. La raison est qu'ils ont un jeu d'instruction spécialisé qui expose le parallélisme d'instruction directement au niveau du jeu d'instructions, là où les processeurs superscalaires restent des processeurs au jeu d'instruction normal. La différence principale entre processeur VLIW et superscalaire est qu'un processeur superscalaire répartit les instructions sur les unités de calcul à l’exécution, là où un processeur VLIW délègue cette tâche au compilateur. ==Le nombre de voies d'un processeur superscalaire== Les processeurs superscalaires les plus simples ne permettent que d'émettre deux micro-opérations à la fois, d'où leur nom de processeur ''dual issue'', ce qui se traduit en '''processeur à double émission'''. Les processeurs triple émission permettent eux d’émettre trois instructions à la fois. A l'opposé, les CPU superscalaires les plus puissants peuvent émettre jusqu'à 8-10 instructions simultanément. En 2026, les CPU superscalaires peuvent monter jusqu’à 10 µops émises en même temps, ce qui est énorme ! Et cela nous amène à faire une distinction entre les processeurs superscalaires dits '''étroits''' et ceux dits '''larges'''. Dans ce wikilivre, on part du principe que les processeurs superscalaires étroits émettent jusqu'à 4-5 µops à la fois, alors que les larges peuvent en émettre plus, l'intervalle entre les deux est une zone grise. Le nombre exact n'est pas fixé dans le marbre, la distinction est volontairement vague. Nous parlerons de la '''largeur d'un CPU superscalaire''' pour parler du nombre de voies, du nombre de µops émises en même temps. Plus un CPU superscalaire est large, plus il est performant ''toute chose égale par ailleurs''. Mais dans les faits, vous pouvez oublier la partie en italique. Élargir un CPU superscalaire demande de rajouter beaucoup de circuits. Et ce cout en circuit aura un impact sur la performance. A budget en transistors limité, il y a un compromis entre élargir un CPU et rajouter du cache ou la taille des fenêtres d'instructions/ROB et autres. ===Les contraintes d’appariement=== Prenons un processeur double émission, capable d'émettre deux instructions simultanément. Émettre deux instructions en même temps n'est pas toujours possible. Par exemple, si deux instructions sont dépendantes, on ne peut pas les émettre en même temps. Mais oublions un instant ces histoires de dépendances de données, et partons du principe que le CPU ait affaire à deux instructions indépendantes, une paire idéale. Un CPU double émission "parfait" pourrait exécuter n'importe quelle paire d'instruction. Par exemple, il pourrait émettre deux multiplications consécutives, deux lectures consécutives, une lecture et une écriture en même temps, etc. En pratique, aucun processeur ne permet cela. Implémenter un CPU superscalaire "parfait" aurait un cout en circuit beaucoup trop conséquent. Par exemple, pour émettre deux multiplications en même temps, il faudrait deux multiplieurs séparés. Idem pour exécuter deux accès mémoire simultané : cela demande d'avoir deux unités mémoire et un cache multiport. Mais en pratique, aucun CPU superscalaire ne fait cela. La totalité des CPU double émission se débrouille avec un seul circuit multiplieur et une seule unité mémoire. Et cela entraine l'apparition de dépendances structurelles, qui font que certaines paires d'instructions sont impossibles. Par exemple, impossible d'émettre deux multiplications en même temps avec un seul circuit multiplieur. Mais émettre une multiplication en parallèle d'une addition est parfaitement possible. Il y a donc des paires d'instructions autorisées et des paires interdites. Et la logique est la même sur les CPU à triple ou quadruple émission, et au-delà. : des triplets ou quadruplet d'instructions sont impossibles à émettre simultanément, alors que d'autres le sont. Pour le dire autrement, il y a des '''contraintes d'appariement''' sur les instructions à émettre en même temps. ===L'impact des contraintes d’appariement sur la performance=== Ces contraintes d’appariement ont un impact sur les performance, mais celui-ci peut être grandement mitigé. Les programmes informatiques utilisent en pratique beaucoup plus d'opérations entières de base que de multiplication, branchements et accès mémoire. Il y a environ 5 opérations entières de base pour une multiplication entière. En pratique, il est rare de devoir exécuter deux multiplications à la fois. Idem pour les décalages et rotations, qui sont assez éloignés les uns des autres. Deux branchements consécutifs sont quant à eux plus fréquents. Deux accès mémoire consécutifs est assez rare, mais ils restent assez proches les uns des autres malgré tout. La conséquence est que certaines paires d'instructions sont privilégiées par rapport au reste. Les paires regroupant deux calculs basiques (additions, soustractions, ...) sont systématiquement supportées, car dupliquer des ALU entières est facile. Les paires regroupant un calcul et une autre instruction sont elles aussi systématiquement supportées. Par contre, les paires avec deux branchements, deux accès mémoire, deux décalages/rotations, ou deux multiplications/divisions sont fréquemment interdites. Seuls les CPU superscalaires larges peuvent les permettre, et encore : sous conditions. Le résultat est que le processeur peut se débrouiller avec une seule unité mémoire et une unité de branchement. ===Terminologie pour la suite du chapitre=== Décrire les contraintes d’appariement demande de dire quelles combinaisons d’instructions sont autorisées ou interdites. Et pour cela, nous allons introduire les abréviations suivantes : * INT est un raccourcis pour parler d'une opération entière simple, qu'une ALU entière peut exécuter. * MUL correspond à une opération entière complexe, typiquement une multiplication, éventuellement à une division. * FLOAT désigne une opération flottante, peut importe laquelle. * MEM désigne un accès mémoire, à savoir une instruction ou µop mémoire. * BRANCH désigne une instruction de branchement. Cette terminologie permettra de définir des paires d'instructions, qu'elles soient possibles ou interdites. Par exemple, l'expression INT + FLOAT désigne une paire d'instruction regroupant une instruction entière et une instruction flottante, peu importe l'ordre des deux instructions. Il en sera de même pour les triplets, quadruplets, ou plus. Par exemple, l'expression INT + INT + FLOAT + MUL désigne un quadruplet d'instructions regroupant : deux instructions entières, une instruction flottante, et une multiplication entière. J'utiliserais aussi des parenthèses pour regrouper certaines combinaisons. Par exemple, l'expression INT + (MUL /FLOAT) veut parler d'une paire d'instruction où la première est une opération entière basique, la seconde est : soit une multiplication, soit une opération flottante. ==Le ''front-end'' des processeurs superscalaires== Un processeur superscalaire doit charger, décoder, et exécuter plusieurs instructions par cycle. Par exemple, un processeur triple émission doit charger 3 instructions lors d'un premier cycle, puis les décoder au cycle suivant, et les exécuter au cycle suivant. Il travaille donc pas paquets de 3 instructions, dans cet exemple. Intuitivement, charger, décoder et exécuter plusieurs instructions à la fois demande de dupliquer des circuits. Par exemple, il faut dupliquer les décodeurs pour décoder plusieurs instructions. Les unités de calcul sont aussi dupliquées, sauf sur certaines processeurs qui arrivent à ruser, mais laissons cela de côté pour plus tard. Mais d'autres circuits sont simplement modifiés, comme l'unité de chargement ou l'unité d'émission. Voyons ce qu'il en est dans le détail. {|class="wikitable" |- ! rowspan="2" | Processeur sans émission multiple | rowspan="2" | Chargement | rowspan="2" | Décodage | rowspan="2" | Renommage | rowspan="2" | Émission | Exécution / ALU | rowspan="2" | ''Commit''/ROB |- | Exécution / ALU |- ! rowspan="4" | Processeur superscalaire | rowspan="4" | Chargement | rowspan="2" | Décodage | rowspan="4" | Renommage | rowspan="4" | Émission | Exécution / ALU | rowspan="4" | ''Commit''/ROB |- | Exécution / ALU |- | rowspan="2" | Décodage | Exécution / ALU |- | Exécution / ALU |} Nous allons d'abord voir ce qu'il en est pour le ''front-end'', à savoir la partie du CPU qui s'occupe de charger et décoder les instructions. Elle regroupe l'unité de chargement, les décodeurs, l'unité de prédiction de branchement, la file de µops, la file d'instruction, et divers caches. En soi, les changements portent surtout sur l'unité de chargement et les décodeurs. ===Le chargement de plusieurs instructions consécutives=== Un processeur superscalaire charge plusieurs instructions en même temps, ''en tenant compte de la présence de branchements''. La partie en italique est importante et elle a une conséquence très forte : la manière de faire est fortement différente entre un CPU superscalaire étroit et large. Un CPU superscalaire étroit fait au plus simple : il charge un bloc de 2, 3, 4 instructions consécutives en mémoire RAM. Il faut alors doubler/tripler/quadrupler la taille du bus connecté au cache d'instruction. Par exemple, pour charger quatre instructions de 32 bits chacune, il suffit de lire un bloc de 128 bits dans le cache d'instruction. Un bloc de 8, 16, 32 octets est donc lu depuis le cache et est ensuite découpé en instructions, envoyées chacun à un décodeur. Charger plusieurs instructions consécutives ne marche bien qu'en absence de branchements. La présence de branchements casse cette dynamique, et il faut gérer leur présence. * Sans prédiction de branchement, toutes les instructions d'un bloc sont décodées et exécutées en même temps, même s'il y a un branchement dans le tas. * Avec prédiction de branchement, les instructions situées après le branchement ne sont pas décodées ni exécutées. L'unité de chargement coupe le bloc chargé au niveau du premier branchement pris, remplit les vides avec des NOP, avant d'envoyer le tout à l'unité de décodage. Les instructions à l'adresse de destination seront chargées au cycle suivant, en chargeant un nouveau bloc. [[File:Fetch sur un processeur superscalaire avec prediction de branchements.png|centre|vignette|upright=2|Fetch sur un processeur superscalaire avec prédiction de branchements.]] Un autre défaut est que sur le CPU charge un bloc de 128 bits à la fois, les instructions doivent être alignées sur 128 bits. Et on peut généraliser à n'importe quelle taille de bloc. Si les instructions ne sont pas alignées, il faut gérer la situation. Il arrive qu'une instruction soit à cheval sur deux blocs, avec un morceau dans le premier bloc, un autre dans le second. Dans ce cas, l'instruction est chargée en deux fois, et des circuits doivent gérer la situation pour recoller les morceaux. Sur les CPU avec des instructions de taille fixe, le problème ne survient que rarement. Mais il est fréquent avec des instructions de taille variable. Un autre défaut est lié aux branchements. Imaginez qu'un branchement s'exécute et branche vers une instruction de destination. Rien n'indique que l'instruction de destination sera au tout début d'un bloc. Si ça se trouve, elle sera en troisième position d'un bloc de 4 instructions. Une solution simple charge le bloc en respectant l'alignement, et les deux premières instructions du bloc ne sont pas à exécuter. Il y a donc une petite perte de performance. Certains CPU, comme l'IBM RS/6000, ont la machinerie charger un bloc de N instructions même s'il n'est pas aligné. La machinerie en question se trouve dans le cache d'instruction et est tout sauf simple à expliquer,ce qui fait que je me permets de la passer sous silence. Un détail, cependant : elle ne permet pas de charger des blocs qui sont à cheval sur deux lignes de cache. ===Le décodage parallèle des instructions=== Pour le décodage des instructions, plusieurs instructions sont décodées en même temps. On parle alors de '''décodage parallèle'''. Pour ce faire, un processeur superscalaire contient plusieurs décodeurs, chacun pouvant décoder une instruction en parallèle des autres. Prenons par exemple un processeur superscalaire à 4 voies : le décodage se fera dans quatre décodeurs séparés, qui fonctionneront en parallèle. Cependant, cette implémentation ne marche bien que pour les processeurs RISC, les processeurs CISC devant faire avec un microcode gourmand en circuits. Les processeurs CISC utilisent des décodeurs hybrides, avec un microcode qui complémente un décodeur câblé. Dupliquer le microcode aurait un cout en transistors trop important, ce qui fait que seuls les décodeurs câblés sont dupliqués. Les CPU CISC superscalaires disposent donc de plusieurs décodeurs simples, capables de décoder les instructions les plus courantes, avec un seul microcode. La conséquence est qu'il n'est pas possible de décoder deux instructions microcodées en même temps. Par contre, il reste possible de décoder plusieurs instructions non-microcodées ou une instruction microcodée couplée à une instruction non-microcodée. Vu qu'il est rare que deux instructions microcodées se suivent dans un programme, le cout en performance est extrêmement mineur. Il faut noter qu'une instruction correspond à une ou plusieurs µops. Il est donc possible de charger 5 instructions et de se retrouver avec 8 µops en sortie des décodeurs. Et c'est la source d'une distinction très importante. Plus haut, j'ai parlé de processeurs superscalaires à N voies. La terminologie peut avoir deux interprétations. La première est que le processeur charge/décode N instructions à la fois. Une seconde est qu'il est capable d'exécuter/émettre N µops en même temps. Les deux ne sont pas équivalentes, vu qu'un processeur superscalaire peut, par exemple, charger/décoder N instructions et émettre/exécuter N+2 µops. La distinction est particulièrement importante sur les processeurs superscalaires larges. Certains d'entre eux sont capables de charger 5 instructions et d'en émettre le double ! Un exemple classique était les processeurs Athlon d'AMD, qui pouvaient décoder deux instructions par cycle, mais émettre 4 µops par cycle. Ce n'était pas un problème, car beaucoup d'instructions, même basiques, étaient décodées en 2 µops. ===L'émission parallèle des instructions=== La superscalarité modifie en profondeur l'unité d'émission, notamment au niveau de son interface. Avant, elle recevait une micro-opération sur son entrée, et fournissait la micro-opération émise sur une sortie. Et cela vaut aussi bien pour une unité d'émission simple, un ''scoreboard'', une fenêtre d'instruction ou des stations de réservation. Mais avec l'émission multiple, les sorties et entrées sont dupliquées. Pour la double émission, il y a deux entrées vu qu'elle doit recevoir deux micro-opération décodées/renommées, et deux sorties pour émettre deux micro-opérations. Dans ce qui suit, nous ne parlerons pas d'entrée et de sortie, mais de '''ports de décodage''' et de '''ports d'émission'''. Les ports de décodage reçoivent des micro-opérations provenant de l'unité de décodage, les ports d'émissions sont pour les micro-opérations émises. Les ports d'émission injectent les micro-opérations émises dans le pipeline, elle sont propagée au banc de registre, aux ALUs, etc. Un processeur superscalaire a forcément plusieurs ports d'émission (et de décodage). Si l'interface de l'unité d'émission change, l'intérieur est tout autant modifié. Sur un CPU non-superscalaire, l'unité d'émission reçoit une µop "candidate à l'émission", et détecte les dépendances entre avec les µops en vol, en cours d'exécution dans le pipeline. Et c'est toujours le cas sur les processeurs superscalaires : l'unité d'émission détecte les dépendances entre les µops candidates et les µops en vol. Mais avec la siperscalarité, elle doit en plus tester les dépendances entre les µops candidates. Si l'unité d'émission est un ''scoreboard'', il doit détecter les dépendances entre instructions émises simultanément et les sérialiser si besoin. Avec une fenêtre d'instruction, il n'y a pour ainsi dire rien à faire, vu que les dépendances sont éliminées par le renommage de registre et que les signaux de réveil gèrent les dépendances RAW. C'est la raison pour laquelle les processeurs superscalaires modernes n'utilisent pas de ''scoreboard''. ==Les processeurs implémentés avec plusieurs pipelines== Nous venons de voir comment la superscalarité impacte le ''front-end'', il est maintenant de voir ce qu'il en est de l'unité d'émission et des ''back-ends''. Un processeur superscalaire peut utiliser l'exécution dans le désordre et le renommage de registres, mais ce n'est pas une obligation. Historiquement, les premiers processeurs superscalaires n'utilisaient pas d'exécution dans le désordre. Le meilleur exemple est celui du Pentium 1 et 2 d'Intel. Ils étaient tous deux des processeurs superscalaires, mais n'avaient pas d'exécution dans le désordre, qui est apparue sur le Pentium 3. les explications sont plus simples si on met de côté l'exécution dans le désordre. Vous vous dites que l'implémentation d'un processeur superscalaire n'est pas la même suivant que le CPU utilise l'exécution dans le désordre, ou non. Mais dans les faits, la distinction est plus subtile que ca. Pour l'expliquer, je vais utiliser une classification originale, qui sépare les processeurs superscalaires en trois sous-types. La distinction se fait sur la manière dont la superscalarité est implémentée en matériel.La classification que j'utilise distingue : * Les processeurs avec plusieurs pipelines, sans exécution dans le désordre. * Les processeurs superscalaires avec un pipeline dynamique. * Les processeurs avec des fenêtres d'instruction décentralisées. La distinction n'est pas qu'une question d'implémentation matérielle. Elle impacte aussi le ratio entre instructions chargées/décodées et instructions émises. Vous vous dites que les deux sont égales, mais ce n'est le cas que sur les processeurs avec N pipelines ! Sur les autres processeurs, il est possible de décoder 4 instructions, mais d'en émettre maximum 6 par cycle ! Et sur les CPU avec des fenêtres d'instruction décentralisées, il y a aussi une différence entre le nombre d'instructions qui sont ''dispatch'' et ''schedule''. La catégorie la plus simple est celle des '''processeurs avec plusieurs pipelines'''. Ce sont des processeurs superscalaires sans exécution dans le désordre. Ils partent d'un processeur non-superscalaire, et ajoutent un second pipeline, en sortie de l'unité d'émission. Le second pipeline permet l'émission parallèle d'une seconde instruction. Le second pipeline n'est pas identique au pipeline originel, sur plusieurs points. Premièrement, il n'a pas exactement les mêmes circuits. Le second pipeline est généralement composé d'une unité de calcul, d'une FPU, éventuellement d'un second banc de registres, guère plus. Il permet d'exécuter une seconde opération de calcul, pas plus, ce qui est à l'origine de contraintes d'appariemment assez fortes. De plus, il n'a pas forcément la même longueur, il peut avoir des étages en moins ou en plus. Il s'agit de la forme la plus simple de processeur superscalaire et nous allons leur consacrer cette section. ===La double émission entière-flottante=== L'exemple le plus simple utilise un second pipeline pour les opérations flottantes, séparé du pipeline originel. L'ajout d'un pipeline flottant permet d'émettre une micro-opération entière en même temps qu'une micro-opération flottante, ce qui s'appelle la '''double émission entière-flottante'''. La micro-opération entière s'exécute dans l'ALU entière, la micro-opération flottante dans la FPU. Les exemples les plus notables sont le PA-RISC 7100, le POWER 1 (RIOS .& et .9) et le PowerPC 601. L'Apollo DN10000 et l'Intel i860 utilisaient aussi cette forme de double émission, mais étaient des prototypes de CPU VLIW. [[File:Double émission entière-flottante.png|centre|vignette|upright=2|Double émission entière-flottante]] Pour bien expliquer les choses, prenons le processeur illustré ci-dessous, avec deux ''back-end'' séparés : un pour la FPU, et un autre pour le reste. Les instructions entières et mémoire passent toutes par le ''back-end'' généraliste. [[File:CPU non-superscalaire basique, avec une FPU.png|centre|vignette|upright=1.5|CPU non-superscalaire basique, avec une FPU]] Les deux ''back-end'' sont des pipelines séparés, mais il faut que l'unité d'émission soit spécialement conçue pour en profiter. Un processeur simple émission peut démarrer une seule µop par cycle : soit la µop est envoyée au ''back-end'' flottant, soit elle est envoyée au ''back-end'' général. Avec la double émission entière-flottante, le processeur peut envoyer une µop dans chaque pipeline, à chaque cycle. Vu que la FPU et son banc de registre forment déjà un pipeline séparé, les seules modifications se font au niveau du ''front-end'', unité d'émission inclue. {| |- |[[File:CPU simple émission avec une FPU.png|centre|vignette|upright=1.5|CPU simple émission avec une FPU.]] |[[File:Double émission entière flottante avec un second pipeline.png|centre|vignette|upright=1.5|CPU avec un second pipeline flottant.]] |} Notez que le terme double émission ''entière''-flottante est un peu trompeur. En réalité, la technique permet d'émettre une instruction flottante avec n'importe quelle autre instruction non-flottante, avec des restrictions pour les branchements. Il est par exemple possible d'exécuter un calcul flottant en parallèle d'un accès mémoire. Sur les CPU 32 bits, il est possible de faire les multiplications dans la FPU. Pour rappel, la FPU contient un multiplieur entier 53-54 bits, pour multiplier les mantisses de deux flottants 64 bits, qu'on peut utiliser pour faire les multiplications entières 32 bits. Cela permet d'émettre une multiplication en même temps qu'une opération entière dans l'ALU. Ainsi, le CPU pouvait émettre deux instructions : une opération entière de base, avec soit une multiplication, soit une opération flottante. En clair, une émission de type INT + (MUL /FLOAT). Par contre, on perd la possibilité d'émettre une multiplication entière avec une opération flottante, mais c'était un compromis qui en valait la peine. Le CPU PA-RISC 7100 implémentait cette optimisation. Il s'agissait d'un CPU double émission entière-flottante, qui utilisait donc un partitionnement assez simple. Le combo partitionnement + reconditionnement permettait ici de gagner en performance, pour un cout en matériel très limité. D’autant plus limité que le jeu d'instruction prévoyait le reconditionnement de la FPU. Les multiplications ne lisaient pas leurs opérandes dans les registres entiers, mais dans les registres flottants. ===La double émission entière=== Une autre possibilité ajoute un second pipeline, capable d'exécuter des opérations entières simples, appelé le pipeline entier. Le processeur supporte alors la '''double émission entière''', à savoir qu'ils peuvent émettre une opération entière en plus d'une autre instruction. L'opération entière en question est une opération simple, comme une addition, une soustraction, une opération bit à bit, une comparaison, etc. Les exemples classiques sont les processeurs Intel i960 et Pentium. L'implémentation était différente entre l'i960 et le Pentium : Le Pentium avait une seconde ALU entière, alors que le i960 rusait. Le Pentium dupliquait l'ALU entière, pour créer un second pipeline entier. Sur le Pentium originel, les contraintes d'appariement étaient assez strictes. Le Pentium avait une FPU, mais ne pouvait pas l'utiliser en double émission. Il lui était impossible d'exécuter une opération flottante en même temps qu'une opération entière (branchements inclus). Le Cyrix 6x86 était une sorte de Pentium produit par l'entreprise Cyrix, avec une microarchitecture différente. Il avait des contraintes d’appariement réduites. Par exemple, il pouvait émettre une opération flottante en parallèle d'une opération entière. Nous détaillerons ces processeurs dans le prochain chapitre, qui porte justement sur les CPU superscalaires x86. [[File:CPU simple et double émission avec un second pipeline entier.png|centre|vignette|upright=2.5|CPU simple et double émission avec un second pipeline entier.]] L'Intel i960 n'avait pas de FPU, juste une ALU entière et une unité mémoire. Il utilisait l'unité mémoire comme une seconde ALU entière limitée. Pour rappel, une unité mémoire contient une AGU, à savoir une ALU spécialisée dans les calculs d'adresse. Elle est capable de faire des additions, soustractions, et quelques décalages limités (des décalages par 2, 4, 8, pas plus). L'Intel i960 ajoutait des connexions entre AGU et banc de registre, afin que celle-ci serve de seconde unité de calcul. L'AGU est déjà reliée aux deux ports de lecture, pour lire les opérandes du calcul d'adresse, il suffit de la connecter sur le port d'écriture. Le processeur peut alors émettre deux instructions/µops entières, sous certaines conditions. [[File:Emission multiple des opérations entières, double émission.png|centre|vignette|upright=2|Émission multiple des opérations entières, double émission.]] ==Les processeurs superscalaires dynamiques== Les processeurs précédents, avec 2 pipelines, avaient une unité d'émission assez simple. Elle avait deux ports de décodage, et deux ports d'émission. Le nombre de ports de décodage et d'émission étaient identiques. Vous vous dites que c'est naturel, et intuitif, que c'est normal pour un processeur superscalaire. Mais en réalité, la majorité des processeurs superscalaires a plus de ports d'émission que de décodage. Expliquer pourquoi va cependant nous demander de parler de comment la superscalarité exploite les unités de calcul. Posons-nous cette question : de combien d'unités un processeur superscalaire a besoin ? Les unités en question ne sont nécessairement des unité de calcul, l'unité mémoire et l'unité de branchement comptent aussi. La réponse dépend du nombre de voies. Si le ''front-end'' charge et décode un paquet de N instructions, exécuter ces N instructions simultanément demandera là aussi au moins N unités de calcul. Notez le terme : ''au moins'', car c'est là tout l'enjeu de cette section. La réponse dépend aussi des contraintes d’appariement. Pour un CPU superscalaire "parfait", sans contraintes d’appariement, toutes les unités doivent être dupliquées, y compris l'unité mémoire. Un processeur à double émission parfaite devrait dupliquer l'ALU entière, le ''barrel shifter'', le circuit multiplieur, la FPU, l'unité mémoire, et l'unité de branchement, ce qui ferait passer de 6 unités à 12. Le cout en circuit est énorme, même pour de la double émission ! Et pour un CPU à N pipelines, le cout en circuit encore plus prohibitif ! En pratique, les processeurs superscalaires qui font ça sont très rares. La majeure partie des processeurs superscalaires utilisent d'autres techniques : le partitionnement, la duplication partielle des ALUs, la duplication des autres unités. ===Le partitionnement des unités fonctionnelles=== Les CPU superscalaires les plus simples ne dupliquent pas leurs unités de calcul, ils se contentent d'exploiter les unités existantes. Et pour expliquer comment, nous allons partir de l'exemple d'un processeur non-superscalaire contient une ALU entière, une FPU, une unité mémoire et une unité de branchement. Les quatre unités peuvent fonctionner en parallèle, à savoir qu'une unité peut exécuter une instruction à elle toute seule, sans interagir avec les autres. Cela suffit pour créer un processeur quadruple émission, qui peut émettre en même temps : une opération entière, une opération flottante, un branchement, un accès mémoire. Le principe marche aussi sur un processeur avec moins de 4 unités, par exemple sans FPU et/ou sans unité de branchement, ou avec au contraire des unités en plus. L'idée générale est d’émettre une µops séparée dans chaque unité fonctionnelle. Utiliser ainsi les unités existantes, pour rendre un CPU superscalaire, s'appelle le '''partitionnement superscalaire'''. Le terme n'est pas beaucoup utilisé en-dehors de quelques vieux articles académiques. Le partitionnement est fortement dépendant du jeu d'instruction, car il demande de bien séparer instructions entières, flottantes, branchements et instructions mémoire. Et autant c'est bien le cas sur les CPU RISC, autant les CPU CISC ne sont pas dans ce cas. Les CPU CISC ont des instructions ''load-op'', qui effectuent une instruction arithmétique et une µop mémoire. Et ces instructions sont très compliquées à implémenter avec le partitionnement. Et au-delà de ça, les CPU CISC ont beaucoup d'instructions qui sont décodées en plusieurs µops, à exécuter dans un ordre bien précis. On peut pas émettre les µops en question simultanément. Le partitionnement a un cout en circuit très limité, pour un gain en performance lui aussi limité. Pour reprendre le CPU de l'exemple précédent, il est rare que l'on ait à exécuter ces quatre instructions en même temps. En pratique, les combinaisons fréquentes sont les combinaisons calcul + accès mémoire, calcul + branchement, calcul entier + calcul flottant. Utiliser une quadruple émission de ce type n'est donc pas une bonne idée, alors que la double émission parait plus adaptée. Et c'est là que la différence entre ports d'émission et de décodage devient intéressante. L'idée est d'avoir une unité d'émission avec deux ports de décodage, et 4 port d'émission (un par unité). Les contraintes d’appariement varient alors suivant le processeur, mais la règle générale est que si deux micro-opérations tombent dans deux unités différentes, on peut les émettre en même temps. En clair, on peut émettre une opération entière avec une opération flottante, une opération entière avec un branchement, un accès mémoire avec un branchement, etc. Et cela rend le partitionnement bien plus intéressant que prévu. Les tout premiers CPU superscalaires étaient des CPU double émission, qui utilisaient le partitionnement avec : une ALU entière, une unité mémoire et une FPU. Nous parlerons de '''partitionnement INT-FLOAT-MEM'''. L'avantage sur la double émission flottante est que les combinaisons INT + MEM et FLOAT + MEM étaient autorisées. L'intérêt était d'émettre une opération en parallèle d'un accès mémoire, ce qui est un doublet d'instruction assez fréquent. Les exemples classiques sont l'HyperSPARC, de l'alpha 21064 et du PowerPC 603. Les contraintes d’appariement variaient grandement suivant le processeur. Le PowerPC 603 implémentait une double émission "parfaite". C'est à dire que toutes les combinaisons possibles à deux instructions étaient possibles, que ce soit INT + FLOAT, INT + MEM, FLOAT + MEM. En plus de cette double émission "parfaite", le CPU utilisait la technique dite de ''branch folding'', qui permettait d'exécuter un branchement sans utiliser d'unité de calcul, ce qui fait qu'il était qualifié de ''CPU 2-way + Branch''. A l'opposé, le processeur alpha 21064 avait pas mal de contraintes d’appariement, à savoir que certaines combinaisons de deux instructions étaient interdites, bien que compatibles avec les unités disponibles. Une des raisons est que le banc de registre n'avait pas assez de ports de lectures, ce qui fait que certaines combinaisons avec une écriture mémoire n'étaient pas permises. Les concepteurs avaient volontairement inséré ces contraintes afin de simplifier le design du processeur. Avant de poursuivre, précisons que la FPU regroupe un additionneur flottant et un multiplieur flottant. Et ce fait peut être exploité pour approfondir le partitionnement. La '''double émission FADD/FMUL''' permet d'émettre une addition flottante en même temps qu'une multiplication flottante. Elle a été utilisée sur les processeurs x86, Alpha et SPARC. Les autres circuits de calcul flottant sont souvent regroupés avec l'additionneur. La division est parfois e même port que le multiplieur flottant, parfois sur celui de l'additionneur (quand elle est microcodée et exécutée en enchainant les soustractions). ===La duplication des ALU entières=== Le partitionnement est une technique élégante, simple, facile à implémenter et au cout en circuits modeste. Mais il faut avouer que cela ne donne pas des CPU superscalaires très efficaces. La raison est que le partitionnement permet d'émettre des paires, triplets, quadruplets de micro-opérations qui sont rares en pratique. Les combinaisons les plus fréquentes en pratique sont des paires/triplets d'opérations entières, souvent des paires/triplets d'additions ou d'opérations entières simples. Il est donc primordial d'émettre plusieurs opérations entières simultanément. Le fait d'émettre et d'exécuter plusieurs opérations entières en même temps s'appelle l''''émission entière multiple'''. Pour exécuter la seconde/troisième opération entière, il faut dupliquer les ALU entières (ou réutiliser l'AGU comme ALU entière comme le fait l'Intel i960). Le processeur doit avoir deux ALU entières pour de la double émission entière, trois pour de triple émission entière, etc. De plus, il faut ajouter des ports au banc de registre, pour lire deux fois plus d'opérandes et décrire deux résultats. Et enfin, il faut rajouter un port d'émission pour pouvoir émettre une micro-opération entière en plus. [[File:Processeur non-superscalaire basique 03.png|centre|vignette|upright=2|Processeur non-superscalaire basique]] La tendance est de multiplier les ALU entières, mais pas le circuit multiplieur. Le cout en transistors est alors grandement réduit, au prix de l'apparition de dépendances structurelles. Le CPU peut émettre deux additions en même temps, ou une addition et une multiplication, mais pas deux multiplications. Il en est de même avec le ''barrel shifter'', qui n'est pas dupliqué. Il est donc possible d'émettre un décalage avec une opération de base, mais pas deux décalages en parallèle. Mais ces contraintes d’appariement ne sont pas un problème, car il est rare de devoir émettre deux multiplications/décalages en même temps. La plupart des programmes sont majoritairement remplis d'additions, avec des multiplications assez rares et des décalages qui le sont encore plus. La moyenne est proche d'une multiplication pour 4/5 additions. Vu que des multiplications consécutives sont très rares, dupliquer le multiplieur est presque inutile. Disposer de plusieurs circuits multiplieurs ou de plusieurs ''barrel shifters'' serait donc un cout en circuits qui ne servirait que rarement et n'en vaut pas la chandelle. Il faut noter que sur les CPU 32 bits, les multiplications peuvent être exécutées dans la FPU ou dans un multiplieur séparé, tout dépend de si le CPU exécute les multiplications dans la FPU ou pas. {|class="wikitable" |- ! Voie d'émission n°1 !! Voie d'émission n°2 !! Voie d'émission n°3 |- | ALU entière || ALU entière || FPU |} Reste à connecter les unités de calcul à l'unité d'émission (indirectement, car il y a des étages de pipeline entre les deux). Une implémentation basique utilisee un port d'émission par unité. Prenons l'exemple d'un CPU superscalaire avec 4 ALU entières, une FPU, et une unité mémoire. Vu qu'il y a 6 unités, cela fait donc 6 ports d'émission. L'exemple historique le plus impressionnant est celui du Motorola 88110, un CPU double émission qui possède 10 unités. Il contient deux ALU entières, un circuit multiplieur, un circuit diviseur, une unité de manipulation de bits, une unité mémoire, un additionneur flottant, et deux unités graphiques pour des calculs impliquant des pixels. Le multiplieur est utilisé à la fois pour les multiplications entières, flottantes et le produit de deux pixels. Idem pour le diviseur, qui sert pour les divisions entières et flottantes. Et chaque unité a son propre port d'émission. La conséquence est que les contraintes d'appariement du Motorola 88110 sont simples : tant que deux µops atterrissent dans des unités séparées, la paire est permise. Il est par exemple possible d'émettre simultanément deux opérations entières simples, vu qu'il y a deux ALU. Par contre, impossible d'émettre simultanément deux multiplications, deux divisions, deux opérations flottantes, deux manipulations de bit, deux accès mémoire, etc. De plus, le partage du multiplieur fait qu'on ne peut pas émettre une multiplication entière avec une multiplication flottante. Idem avec les divisions, vu que le diviseur est aussi partagé. Ce sont les seules contraintes. ===Le partage des ports d'émission=== La présence d'un circuit multiplieur et d'un ''barrel shifter'' pose des problèmes pour le partitionnement. Il est possible d'avoir un port d'émission dédié au multiplieur, et un autre au ''barrel shifter'', qui sont séparés de ceux pour l'ALU entière. Il est alors possible d'émettre une opération entière, une multiplication et un décalage, simultanément. Nous parlerons de '''partitionnement entier''' pour désigner cette possibilité. [[File:Partitionnement superscalaire entier.png|centre|vignette|upright=2|Partitionnement superscalaire entier.]] Le partitionnement entier est cependant très rare, car le gain en performance serait trop faible au regard des couts en circuit. Quelques processeurs historiques l'utilisaient, mais ce n'est plus le cas de nos jours. La raison est que les multiplications et les décalages sont assez rares dans le code, ce qui fait que le port d'émission associés sont sous-utilisés. Et un port d'émission a un cout en circuit assez important. Et c'est sans compter qu'ajouter des ports sur le banc de registres juste pour un multiplier sous-utilisé n'en vaut pas la peine. L'idéal serait de trouver un moyen de réduire le nombre de ports d'émissions, sans réduire le nombre d'unités de calcul. [[File:Emission multiple des opérations entières, implémentation naive.png|vignette|Émission multiple des opérations entières, implémentation naive]] La solution consiste à '''partager les ports d'émission''', à savoir connecter un port d'émission à plusieurs unités de calcul. L'exemple classique est justement lié aux circuits multiplieur et ''barrel shifters''. L'idée est que le multiplieur et le ''barrel shifter'' sont mis sur le même port d'émission qu'une ALU entière. Prenons par exemple le processeur à double émission entière illustré ci-contre. Un port d'émission est relié à une ALU entière seule, et l'autre port d'émission alimente la seconde ALU entière et le multiplieur. La conséquence est que le processeur peut émettre deux opérations entières, dont une multiplication. Si on avait utilisé un port d'émission dédié au multiplieur, il aurait été possible d'émettre deux opérations entières et une multiplication. On perd donc une voie, mais on gagne en flexibilité et on économise des circuits. Notons que ce partage signifie aussi que le multiplieur partage les ports sur le banc de registre, ce qui fait une double économie. [[File:Emission multiple des opérations entières, implémentation courante.png|centre|vignette|upright=1.5|Émission multiple des opérations entières, implémentation courante]] En général, le ''barrel shifter'' et le multiplieur sont placés sur deux voies d'émission séparés, ce qui permet d'émettre un décalage et une multiplication en même temps. Les mettre sur le même port ne le permettrait pas. {|class="wikitable" |- ! Voie d'émission n°1 !! Voie d'émission n°2 !! Voie d'émission n°3 |- | * ALU entière * Multiplieur | * ALU entière * Barrel shifter | FPU |- | * Additions, soustractions, opérations bit à bit, etc. * Multiplication | * Additions, soustractions, opérations bit à bit, etc. * Décalages et rotations | Opération flottante |} Mais le partage des ports d'émission peut appliquer à n'importe quelle unité. Prenons l'exemple d'un CPU superscalaire avec 4 ALU entières, une FPU, et une unité mémoire. Vu qu'il y a 6 unités, cela fait donc 6 ports d'émission. Cependant, il est rare qu'un bloc de 6 instructions contienne exactement 4 opérations entières, une opération flottante, et un accès mémoire. Beaucoup de ports d'émission seraient sous-utilisés. La solution est de partager des ports d'émission, pour passer à la quadruple émission. Le CPU peut alors répartir 4 instructions sur les ports d'émission adéquats, à chaque cycle. Le CPU a donc 4 ports de décodage et 6 ports d'émission. Une implémentation classique permet n'importe quelle combinaison de 4 instructions compatible avec les 6 unités. Par exemple, il devient possible d'émettre simultanément 4 additions, la combinaison 2 * INT + FLOAT + MEM, 3 * INT + MEM, et bien d'autres. L'exemple relie une ALU entière, la FPU et l'unité mémoire sur le même port. Le CPU a alors quatre ports d'émission, qui ne sont pas "identiques". Trois d'entre eux sont reliés à une ALU entière chacun, rien d'autre. Le quatrième est lui relié à une ALU entière, la FPU et l'unité mémoire. On économise alors deux ports d'émission, mais cela entraine l'apparition de contraintes d’appariement. Le CPU peut émettre 3 opérations entières + n'importe quelle autre opération. Mais les autres combinaisons sont interdites. Le CPU peut émettre 6 opérations entières simultanément, grâce à 6 ALU entières. Les accès mémoire ou opérations flottantes ne viennent pas en plus, mais en remplacement des opérations entières. Regrouper plusieurs ALU sur un même port d'émission est donc à l'origine de dépendances structurelles, de nouvelles contraintes d’appariement. Dans certaines situations de '''''port contention''''', on a assez d'ALU pour exécuter N micro-opérations, mais la répartition des ALUs sur les ports l'empêche. Impossible d'émettre deux micro-opérations sur deux ALU si elles sont sur le même port d'émission. On peut réduire les cas de ''port contention'' en répartissant correctement les ALU sur les ports d'émission, mais sans jamais les supprimer complétement. Un exemple est celui du PowerPC 604. Il implémentait la quadruple émission, alors qu'il avait 5 unités de calcul. Mais deux unités partageaient un même port d'émission. Dans le détail, il avait deux ALU entières, un circuit multiplieur/diviseur, une FPU, une unité mémoire. La FPU et le multiplieur étaient sur le même port d'émission. En conséquence, le CPU pouvait émettre seulement les combinaisons INT + INT + MEM + (MUL/FLOAT). : Il est intéressant de voir l'exemple d'un processeur superscalaire parfait, sans contraintes d'appariement, à N voies. Pour cela, on part d'un processeur non-superscalaire, avec M unités de calcul. Un tel processeur peut s'implémenter avec N ports d'émission, chacun relié à M unités de calcul possibles, ce qui fait que les M unités de calcul sont dupliquées en N exemplaires. Si on prend par exemple un CPU non-superscalaire avec une ALU, une FPU, une unité mémoire et une unité de branchement, un port d'émission sera relié à une ALU, une FPU, une unité mémoire et une unité de branchement. ===La duplication des autres unités fonctionnelles=== Dupliquer les ALU entière est une première étape, mais il est aussi possible de dupliquer les autres unités fonctionnelles. Par exemple, il est possible de dupliquer l'unité mémoire ou l'unité de branchement. Reste à voir quelles unités dupliquer, et pourquoi. Précisons que dans ce qui va suivre, nous allons parler de la duplication des unités en général, pas seulement des unités mémoire/branchement. Nous allons continuer de parler de la duplication des ALU entières dans ce qui suit, mais de manière plus générale. Pour un processeur à N voies, il est théoriquement possible de dupliquer toutes les unités en N exemplaires. Mais ce n'est jamais fait en pratique, car le cout en circuits serait trop important et le gain en performance trop faible. Mieux vaut dupliquer certaines unités bien précises, quitte à avoir des contraintes d'appariement. Et à ce petit jeu, toutes les duplications ne se valent pas, certaines sont plus rentables en matière de performance, le cout en circuit n'est pas le même selon l'unité, etc. La rentabilité en performance dépend surtout des contraintes d'appariement. Les contraintes d'appariement entrainent une baisse de performance significatives si elles interdisent des paires d'instructions fréquentes. Aussi, les opérations très fréquentes, souvent appairées, gagnent à dupliquer l'unité associée. Et au contraire, les unités rarement utilisées ne doivent pas être dupliquées. Il se trouve que les unités les plus utilisées sont les ALU entières, suivies par les unités mémoire et l'unité de branchement. Le cout en circuit n'est aussi pas le même. Dupliquer les ALU entière est trivial, car elles ont un budget en transistors assez faible. Les circuits multiplieurs, diviseurs, et les ''barrel shifter'' ont eux un cout en transistor plus élevés. Si on devait faire un classement, on aurait : ALU entière < ''barrel shifter'' < multiplieur. Pour les unités mémoire, le cout en transistor est là aussi conséquent. Le résultat final est que dupliquer les ALU entières est privilégié sur le reste, comme l'a vu dans la section précédente. Les unités mémoire arrivent en seconde place, suivie par les multiplieurs/''barrel shifter'' à la troisième place, et les unités de branchement à la quatrième place. Les CPU superscalaires larges ont donc tendance à multiplier les unités mémoire en second. Les multiplieurs et ''barrel shifters'' sont eux aussi dupliqués sur les CPU superscalaires larges, mais cela vient souvent après avoir dupliqué les unités mémoire. La raison est que les accès mémoire sont critiques pour la performance des CPU à exécution dans le désordre, pas les multiplications. Pour les unités de branchement, une subtilité complique le fait d'en installer plusieurs. Que faire quand deux branchements sont pris ? La solution la plus simple est de ne pas se trouver dans cette situation : on n'utilise qu'une seule unité de branchement et on n’exécute qu'un seul branchement par cycle. La majorité des processeurs superscalaires étroits font ainsi. Les processeurs superscalaires larges ont cependant deux unités de branchements, mais ils n'acceptent qu'un seul branchement pris à la fois. Si les deux branchements pris sont exécutés en même temps, le processeur n’exécute que le plus récent, dans l'ordre du programme. ===Les CPU superscalaires mélangent les trois techniques=== Les CPU superscalaires utilisent toutes les techniques à la fois : partitionnement, duplication de l'ALU entière, duplication des autre unités fonctionnelles. Chaque processeur utilise ces trois stratégies à sa sauce, en fonction de son budget en transistor. Les quatre techniques ont cependant un cout en circuit différent, et donnent des gains de performances eux aussi forts différents. * Le '''partitionnement superscalaire''' a un cout en circuit modéré, car on n'a pas besoin de dupliquer d'ALU. * La '''duplication des ALU entières''' est la technique la plus importante : elle a un cout en circuit modéré, et donne des gains en performance impressionnants. * La '''duplication des autres unités''' a un cout en circuit variable, pour des gains en performance eux aussi variables. * Le '''partage des ports d'émission''' réduit le cout en circuit du CPU, pour des pertes de performances mineures si bien fait. L'usage du partitionnement seul, sans duplication, était surtout le fait des processeurs commercialisés entre les années 1989 et 1993. Le partage des ports d'émission a lui aussi mis un peu de temps pour s'imposer. Globalement, les processeurs superscalaires ont évolués progressivement, pour se rapprocher d'une sorte de standard. Mais pour comprendre ce qu'est ce standard, et en quoi les CPU historiques s'en sont progressivement rapprochés, il faut étudier l'histoire des CPU superscalaires. L'histoire des CPU superscalaires a commencé dès 1960, mais est resté un projet de recherche pendant plusieurs décennies. De nombreux designs ont été conçus, mais abandonnés avant la production, comme l'ACS-1 d'IBM. Les années 1980 ont vu l'arrivée de prototypes de processeurs VLIW, mais qui n'étaient pas superscalaires. Par exemple, l'Apollo DN10000 et l'Intel i860 pouvaient émettre deux instructions à la fois, sous conditions, mais demandaient que le compilateur fasse une bonne partie du travail. Les premiers CPU superscalaires commercialisés sont sortis en 1989. Deux CPUs superscalaires sont sortis cette année là : le RS/6000 d'IBM et l'i960CA d'Intel. Puis, la compétition s'est aussi lancée dans la bataille. Les CPU commercialisés avant l'année 1993 étaient à double émission, à l'exception du SuperSPARC qui utilisait la triple émission. Les modèles après 1993 hésitaient entre double et quadruple émission. Dans le tableau ci-dessous, les processeurs quadruple émission sont en bleu, les triple émission sont en rouge, les autres ne sont pas en couleur. {|class="wikitable" |- ! !! IBM !! HP PA-RISC !! SPARC !! Intel!! Alpha !! Autres |- ! 1989 | RS/6000 || || || i960CA || || |- ! 1990 | || || || || || |- ! 1991 | || || || || || Motorola 88110 |- ! 1992 | RSC || PA 7100 || class="f_rouge | SuperSPARC || || 21064 || |- ! 1993 | PPC 601 || || HyperSPARC || Pentium || || |- ! 1994 | class="f_bleu | POWER 2 || PA 7200 || || || class="f_bleu | 21164 || |- ! 1995 | 603/604 || class="f_bleu | PA 8000 || class="f_bleu | UltraSPARC 1 || Microarchitecture P6 (+ concurrence AMD) || || class="f_bleu | MIPS R1000 |- ! 1996 | class="f_bleu | 620 || || || || class="f_bleu | 21264 || |- ! ... || ... || ... || ... || ... || ... || ... |} Les premiers CPU à double émission sont assez représentatifs des CPU à double émission modernes. Il faut dire que se limiter à deux voies ne permet pas de faire beaucoup de choses. Voici le tableau chronologique disant quel CPU utilisait quelle technique. * Les CPU en vert utilisaient la double émission entière-flottante. * Les CPU en rouge utilisaient la double émission entière "pure". * Les CPU en jaune utilisaient un ''partitionnement complet'' entre ALU, FPU et unité mémoire. * Les CPU sans couleur utilisaient un grand nombre d'unités, avec beaucoup de ports d'émission. * Les CPU barrés utilisent la triple ou quadruple émission et seront vu dans la section suivante. {|class="wikitable" |- ! !! IBM !! HP PA-RISC !! SPARC !! Intel!! Alpha !! Autres |- ! 1989 | class="f_vert | RS/6000 || || || class="f_rouge | i960CA || || |- ! 1990 | || || || || || |- ! 1991 | || || || || || Motorola 88110 |- ! 1992 | class="f_vert | RSC || class="f_vert | PA 7100 || <s>SuperSPARC</s> || || class="f_jaune | 21064 || |- ! 1993 | class="f_vert | PPC 601 || || class="f_jaune | HyperSPARC || class="f_rouge | Pentium || || |- ! 1994 | <s>POWER 2</s> || PA 7200 || || || <s>21164</s> || |} C'est en 1994, que la quadruple émission est arrivée et que les CPU ont essayé divers implémentations. Il y avait alors trois stratégies pour la double/triple/quadruple émission, qui partageaient des problèmes similaires. La première implémentation dupliquait les ALUs et les FPUs, ce qui permettait d'émettre deux opérations flottantes avec deux opérations entières. Le POWER 2 dupliquait ses FPUs à la perfection, alors que le 21264 séparait l'additionneur et le multiplieur flottant. Les accès mémoire étaient traités comme des opérations entières et le processeur pouvait souvent en émettre deux à la fois. Mais une telle organisation n'est plus utilisée de nos jours, car, sans instructions flottantes à exécuter, elle est équivalente à de la double émission. La seconde implémentation utilisait deux ALU entières (dont un multiplieur), une unité mémoire et une FPU.Cette organisation est intéressante, mais encore trop stricte pour donner de bons résultats, les quadruplets avec exactement deux opérations entières, un accès mémoire et une opération flottante étant trop rares. L'usage de ces 4 unités en double ou triple émission a été utilisé sur le PA 7200 et le SuperSPARC. Malheureusement, en plus de problèmes au niveau de son cache, le SuperSPARC avait de nombreuses contraintes d’appariement, car le banc de registre n'avait pas assez de ports pour alimenter certaines combinaisons d'instructions. De plus, s'il émettait une multiplication, il ne pouvait pas émettre de seconde µop. Enfin, une troisième implémentation utilisait un grand nombre d'unités fort différentes, bien plus que quatre, avec au minimum deux ALU entières. Prenons l'exemple de l'UltraSPARC, qui avait : deux ALU entières, une unité mémoire, une unité de branchement, un additionneur flottant, un multiplieur flottant, un diviseur flottant (aussi utilisé pour le calcul des racines carrées), et 2 unités graphiques pour les calculs sur des pixels. En tout, cela fait 9 unités différentes, pour de la quadruple émission. Dans le même genre, les processeurs MIPS R8000 et R10000 avaient une implémentation plus moderne, dans le sens où toutes les unités étaient dupliquées. Le CPU avait deux ALU, deux FPU et deux unités mémoire. Il pouvait donc émettre quatre instructions à la fois, tant que le paquet collait avec ces six unités. Moitié opérations entières, moitié opérations mémoire, avec de quoi remplacer les opérations entières par des opérations flottantes. Le PA 8000, lui, dupliquait toutes ses unités en deux exemplaires, ce qui n'est pas l'idéal, aucun processeur moderne ne fait cela aujourd'hui. Le tout est résumé dans le tableau ci-dessous, avec : * en vert les CPU utilisant la première stratégie ; * en rouge les CPU utilisant la seconde stratégie ; * en jaune les CPU utilisant la troisième stratégie. {|class="wikitable" |+ Processeurs quadruple émission historiques |- ! Année !! Processeur !! Unités |- ! 1991 | class="f_jaune | Motorola 88110 || class="f_jaune | 2 ALU + MUL + DIV + BIT + MEM + FADD + 2 unités graphiques |- |- ! rowspan="2" | 1994 | class="f_vert | POWER 2 || class="f_vert | 2 ALU/MEM + 2 FPU (+ 2 Branchements via ''branch folding'') |- | class="f_rouge | 21164 || class="f_rouge | 2 ALU + FPU + MEM |- ! rowspan="3" | 1995 | PA 8000 || 2 ALU + 2 ''barrel shifters'' + 2 MEM + 2 FPU (FMA) + 2 FPU (DIV et SQRT) + 2 SIMD |- | class="f_jaune | UltraSPARC 1 || class="f_jaune | 2 ALU + MEM + BRANCH + FADD + FMULFDIV + 2 unités graphiques |- | MIPS R8000 et R10000 || 2 ALU + 2 FPU + 2 MEM |- ! rowspan="2" | 1996 | class="f_rouge | 620 || class="f_rouge | 2 ALU + FPU + MEM |- | class="f_vert | 21264 || class="f_vert | 2 ALU/MEM + 2 FPU (FADD + FMUL) |} Malheureusement, ces CPUs historiques n'avaient ni assez d'unités de calcul entières, ni d'unités mémoire, pour remplir la quadruple émission. Environ la moitié des instructions d'un programme correspond à des instructions entières. Pour en profiter, un processeur à N voies doit disposer d'au minimum N/2 ALU entières, l'idéal étant d'aller au-delà. Les CPU superscalaires historiques avaient 2 ALU pour de la quadruple émission, ce qui était très limite. En comparaison, les CPU superscalaires modernes mettent le paquet niveau ALU entière. La plupart des CPU x86 modernes descendent rarement sous les 2/3, la plupart remplit presque la totalité de la largeur du CPU. Pour donner un exemple, la microarchitecture Golden Cove d'Intel est un processeur à 6 voies, avec 5 ALU entières. Pour être précis, un programme informatique exécute environ 40% de son code dans des instructions arithmétiques, un autre 40% dans les accès mémoire, et un le reste pour... le reste. La conséquence est que les processeurs modernes dupliquent leurs unités mémoire. Par exemple, la microarchitecture ''Golden Cove'' d'Intel dispose de trois unités mémoire : deux spécialisées dans les écritures, trois dans les lectures. La microarchitecture ''Gracemont'' utilise la pentuple émission et peut émettre 4 µops entières et 4 µops mémoire. Notez que le ratio est équilibré entre opérations entières et mémoire, avec soit égalité, soit un avantage pour les ALU entières. Une autre régularité est que la largeur totale du processeur peut être occupée par des opérations entières/mémoire. Pour le dire autrement, sur un processeur quadruple émission, il pourra émettre un mix de 4 instructions entière/mémoire, avec zéro opération flottante. Idem avec des processeurs triples ou pentuple émission, voire plus. Si le processeur émet une opération flottante, elle remplace une opération entière. Prenons l'exemple des processeurs AMD K6, sortis en 1997, juste avant les AMD Athlon. Ils avaient 10 unités de calcul, 6 ports d'émission, 4 ports de décodage. Voici exactement comment étaient répartie les unités de calcul sur les ports d'émission. C'était des processeurs quadruple émission, ce qui fait que la moitié de la largeur du processeur sert pour des opérations entières, et l'autre moitié pour les accès mémoire. {|class="wikitable" |- ! Port d'émission n°1 !! Port d'émission n°2 !! Port d'émission n°3 !! Port d'émission n°4 !! Port d'émission n°5 !! Port d'émission n°6 ! |- | ALU entière || ALU entière || FPU || Unité de branchement || Unité LOAD || Unité STORE |- | || Multiplieur || || || || Unité de calcul d'adresse (instruction LEA) |- | || ''Barrel shifter'' || || || || Unité pour les instructions PUSH |} Les microarchitectures K7, K8 et K10, utilisées sur les anciens CPU AMD Athlon, sont un exemple moins représentatif. Ces microarchitectures utilisent le partage des ports d'émission au maximum. C'était des microarchitectures à triple émission, avec six ports d’émission. Il y avait trois ports d'émission pour les opérations flottantes, et trois ports d'émission pour les micro-opérations entières/mémoire. Il y avait trois ALU entières et trois unités mémoire, chaque ALU partageait un port d'émission avec une unité mémoire. Elle pouvait donc émettre soit trois opérations entières, soit trois µops mémoire, soit un mix des deux. En plus des mix avec les opérations flottantes et branchements. {|class="wikitable" |- ! Port d'émission n°1 !! Port d'émission n°2 !! Port d'émission n°3 !! Port d'émission n°4 !! Port d'émission n°5 !! Port d'émission n°6 ! |- | ALU entière || ALU entière || ALU entière || FADD || FMUL || FPU (autres opérations) |- | Multiplieur || || || || || |- | LOAD/STORE || LOAD/STORE || LOAD/STORE || || || |} Les CPU superscalaires larges dupliquent les multiplieurs et/ou les ''barrel shifters'', et même les unités de branchement. La raison est que quand la largeur du CPU augmente, le processeur traite des paquets d'instructions de plus en plus gros. Par exemple, pour un CPU à 10 voies, cela veut dire émettre 10 instructions en même temps. Il est alors plus fréquent d'avoir deux multiplications, deux décalages, deux accès mémoire, deux branchements. Par exemple, la moyenne est d'une multiplication tous les 5 instructions. Les CPU superscalaires à 10 voies ont donc tendance à avoir deux multiplications par paquet d'instruction, en moyenne. Avoir deux multiplieurs est alors un gain en performance clair. Par exemple, la microarchitecture ''Golden Cove'' a deux ''barrel shifters''. C'est une architecture à 6 voies. {|class="wikitable" |- ! Port d'émission n°1 !! Port d'émission n°2 !! Port d'émission n°3 !! Port d'émission n°4 !! Port d'émission n°5 !! Port d'émission n°6 !! Port d'émission n°7 !! Port d'émission n°8 !! Port d'émission n°9 !! Port d'émission n°10 |- | ALU entière || ALU entière || ALU entière || ALU entière || ALU entière || LOAD || LOAD || LOAD || STORE || STORE |- | ''Barrel shifter'' || Multiplieur || Multiplieur || ''Barrel shifter'' || || || || || || |- | Branchements || Diviseur || || Branchements || || || || || || |} ==Les µops mémoire sur les processeurs superscalaires== Plus haut, nous avons volontairement passé sous silence les accès mémoire. La raison à cela est que leur support sur les CPU superscalaire peut se faire de plusieurs manières différentes, qu'il vaut mieux expliquer dans une section dédiée. Les différentes implémentations sont décrites dans la liste suivante, nous allons les voir une par une. * Avec la première méthode, les accès mémoire sont exécutés par le pipeline pour les opérations entières. * Avec la seconde méthode, les accès mémoire ont un pipeline dédié ; c'est l'''émission parallèle des accès mémoire''. * Avec la troisième méthode, il est possible d'émettre plusieurs accès mémoire simultanément : c'est l'''émission multiple des accès mémoire''. Voyons ces trois cas dans le détail. Nous verrons notamment les différentes formes d'émission multiple des accès mémoire. ===Les accès mémoire traités dans le pipeline entier=== Dans le cas le plus simple, les accès mémoire sont traités comme des opérations entières un peu spéciales. L'unité mémoire est sur le même port d'émission que l'ALU entière, ce qui fait que les accès mémoire remplacent des opérations entières. Faire ainsi permet d'utiliser une ALU entière pour les calculs d'adresse, pas besoin de rajouter une unité de calcul d'adresse séparée. Typiquement, un processeur de ce type place l'unité mémoire après l'unité de calcul. Les opérations entières n'utilisent que l'ALU entière, mais pas l'unité mémoire. Les accès mémoire exécutent un calcul d'adresse dans l'ALU entière, puis l'adresse calculée est envoyée à l'unité mémoire. Le tout est illustré ci-dessous, dans le cas d'un processeur historique à double émission entière-flottante. [[File:Processeur non-superscalaire basique 01.png|centre|vignette|upright=1.5|Processeur non-superscalaire basique]] Un exemple de CPU double émission de ce type est celui du Power PC 440, un CPU destiné à l'embarqué. Vu qu'il était destiné à l'embarqué, il n'avait pas de FPU, il n'avait pas besoin de manipuler de nombres flottants. Il n'a donc que des ALU entières, un multiplieur et une unité mémoire. Il dispose de deux ports d'émission. Le premier est connecté à une ALU entière et un circuit multiplieur, le second est relié à l'unité mémoire et une seconde ALU entière. Une organisation simple, mais très efficace ! [[File:PowerPC 440.png|centre|vignette|upright=2|Microarchitecture du PowerPC 440.]] L'implémentation économise une unité de calcul d'adresse, mais n'a pas de cout en performance significatif. Les accès mémoire étant très lents, émettre une µop entière en parallèle d'un accès mémoire ne sert pas à grand-chose. On peut émettre cette µop entière un cycle plus tard, elle sera terminée avant même que l'accès mémoire soit terminé, même si l'accès mémoire se fait dans le cache L1 ou L2. Pour comprendre pourquoi, faisons quelques calculs. Un accès mémoire prend deux-trois cycles quand il tombe dans le cache L1. Et on doit attendre qu'il soit terminé pour en émettre un nouveau. Combien d'instructions arithmétiques peut-on lancer pendant ce temps, sur un CPU triple émission ? Au premier cycle, on peut en émettre 2 (l'instruction mémoire prend une place, donc 3-1), puis 3 au second cycle. Ce qui fait 5 instructions arithmétiques au total. Or, il se trouve qu'il y a une instruction d'accès mémoire pour 5 instructions arithmétiques, environ. Par contre, ce compromis change quand on passe à la quadruple émission, voire au-delà. Dans ce cas, il devient intéressant de pouvoir émettre une nouvelle instruction mémoire à chaque cycle. Ce qui nous amène à la section suivante... ===L'émission parallèle des accès mémoire=== A partir de la quadruple émission, la tendance a été de séparer l'unité mémoire des ALU entières. Cela permet d'émettre des µops mémoire en même temps que des µops entières et/ou flottantes., ce qui porte le nom d''''émission parallèle des µops mémoire''', ou encore ''émission mémoire parallèle''. Par exemple, un processeur quadruple émission peut émettre en même temps deux opérations entières, une opération flottante et un accès mémoire. L'accès mémoire vient en plus des opérations entières, il n'est pas possible de remplacer l'accès mémoire par une opération entière. La technique ne permet pas de faire les calculs d'adresse dans l'ALU entière. Il faut donc utiliser une unité de calcul d'adresse séparée. Elle est intégrée dans l'unité mémoire, qui fait l'interface entre le cache et le banc de registres. Le cout en circuits est très faible, car les unités de calcul d'adresse n'utilisent guère plus qu'un additionneur et un circuit de décalage très simple. L'émission mémoire parallèle est plus facile à implémenter sur les CPU RISC, qui ont des instructions mémoire séparées du reste. Les CPU CISC ne sont pas dans ce cas, ce qui fait que séparer l'unité mémoire du pipeline entier est compliqué. Les CPU superscalaires historiques étaient pour moité des CPU RISC et pour moitié des CPU CISC, ce qui fait qu'ils n'utilisaient pas ou peu l'émission mémoire parallèle. Les premiers processeurs superscalaires se contenaient de l'émission parallèle mémoire, ils ne pouvaient émettre qu'un seul accès mémoire à la fois, l'unité mémoire n'était pas dupliquée. Et ce n'est pas tant un problème que ça, du moins sur les processeurs à double ou triple émission. En effet, il est rare que l'unité mémoire doive faire deux accès simultanés. Notons que le processeur peut exécuter une instruction mémoire en parallèle d'un calcul ou d'un branchement, seuls les accès mémoire simultanés sont impossibles. ===L'émission multiple des accès mémoire avec une LSQ=== Les processeurs superscalaires larges travaillent sur des paquets de 8 à 10 instructions, il est plus fréquent d'avoir plusieurs accès mémoire par paquet. Dans ces conditions, il est utile d'avoir une '''émission multiple des accès mémoire''', à savoir d'émettre plusieurs micro-opérations mémoire en même temps. Les processeurs superscalaires modernes sont capables d'émettre plusieurs lectures/écritures simultanément. Par exemple, ils peuvent émettre une lecture en même temps qu'une écriture, ou plusieurs lectures, ou plusieurs écritures. Il faut noter que selon le processeur, il peut y avoir des restrictions quant aux accès mémoire émis en même temps. Par exemple, certains processeurs peuvent émettre une lecture avec une écriture en même temps, mais pas deux lectures ni deux écritures. Ou encore, ils peuvent émettre deux lectures, une lecture et une écriture, mais pas deux écritures en même temps. Dans la majorité des cas, les processeurs ne permettent pas d'émettre deux écritures en même temps, alors qu'ils supportent plusieurs lectures. Il faut dire que les lectures sont plus fréquentes que les écritures. Les processeurs qui autorisent toutes les combinaisons de lecture/écriture possibles, sont rares. L'émission multiple des accès mémoire s'implémente dans l'unité mémoire. Pour rappel, l'unité mémoire s'interpose entre le cache L1 et le reste du pipeline. Dans le cas le plus simple, elle incorpore une ''load/store queue'' et des unités de calcul d'adresse. L'émission multiple des accès mémoire demande de rendre la ''Load-store queue'' multi-ports, afin de gérer plusieurs micro-opérations mémoire simultanées. Il faut aussi dupliquer les unités de calcul d'adresse reliées à la LSQ, chaque port d'émission mémoire devant avoir sa propre unité de calcul d'adresse. : Dans ce qui suit, nous parlerons d'AGU (''Adress Generation Unit'') pour désigner les unités de calcul d'adresse, et de LSQ pour designer la ''Load-store queue''. [[File:Processeur non-superscalaire basique 02.png|vignette|upright=1|Processeur non-superscalaire basique]] Dupliquer les ports de la LSQ et les AGU est nécessaire. Mais qu'en est-il du cache ? La réponse varie suivant le processeur. Il y a deux grandes méthodes pour implémenter l'émission multiple des accès mémoire. La première méthode ajoute des ports de lecture/écriture au cache de données, pour faire des lectures/écritures simultanées. Il faut alors dupliquer des unités de calcul, rajouter les ports sur le cache, mais aussi rajouter des ports sur la ''load/store queue'', pour qu'elle échange des données/commandes mémoire avec le cache. Le cout en circuits est alors conséquent La seconde duplique les unités de calcul, mais pas les ports d'accès au cache. Les accès mémoire sont alors accumulés dans la ''load/store queue'', qui sont ensuite traités un par un par le cache. Un exemple est celui des processeurs AMD de micro-architecture K7 K8, à savoir les anciens AMD Athlon et AMD Athlon 64, qui pouvaient calculer trois adresses simultanément, mais ne pouvaient faire qu'un seul accès au cache par cycle. Le cout en circuit est modéré : les unités de calcul coutent très peu, les ports de la ''load/store queue'' coutent déjà un peu plus. Quelques processeurs utilisaient des solutions intermédiaires. Un processeur utilisant cette technique est, par exemple, capable d'émettre quatre µops mémoire en même temps, mais avec seulement deux ports sur le cache. Un exemple classique étant les Pentium 1 et 2 d'Intel, dont nous parlerons dans le chapitre suivant. ===L'émission séparée des LOAD et STORE=== De rares processeurs ont des ports d'émission qui peuvent faire indifféremment lecture comme écritures. Mais les processeurs modernes préfèrent séparer ports d'émission pour les lectures et les écritures. Un exemple est celui des processeurs Intel de micro-architecture Nehalem, qui avaient un port de lecture et un port d'écriture tous deux reliés à la ''Load-store queue'', chacun avec leur propre unité de calcul d'adresse. Comme autre exemple, les processeurs skylake ont une LSQ avec deux ports de lecture et un port d'écriture, ce qui permet d'émettre deux lectures en même temps qu'une écriture. Dans cette section, nous allons étudier le cas avec des ports séparés pour les lectures et écritures. En général, les processeurs ont un peu plus de ports de lectures que d'écritures. Une raison est qu'un programme a, en moyenne, un tout petit peu plus de lectures que d'écritures. Mais la différence n'est pas énorme, c'est un léger déséquilibre. Une seconde raison est que l'exécution dans le désordre marche bien mieux quand les lectures ont lieu le plus tôt possible. Donc mieux vaut privilégier les lectures à l'émission, pour qu'elles se fassent un peu en avance. En pratique, la différence n'est pas très marquée. Il est courant d'avoir un port de lecture et un d'écriture, les CPU superscalaires très larges ont souvent deux ports de lecture et deux d'écriture. A la rigueur, quelques CPU ont trois ports de lecture et deux d'écriture. Le cas le plus simple est celui où on peut émettre une lecture et une écriture en même temps. Il s'implémente facilement si la LSQ est en réalité une simple ''Store queue'', à savoir qu'elle met en avance les écritures uniquement. Pour rappel, les écritures sont mises en attente dans la ''Store queue'', mais les lectures sont exécutées immédiatement. Une fois l'adresse à lire connue, la lecture consulte la ''Store queue'' pour détecter les dépendances de données, puis s'exécute. La donnée à lire est lue soit depuis la ''Store queue'', soit depuis le cache. [[File:Emission LOAD - Store séparée.png|vignette|upright=1|Émission LOAD - Store séparée]] L'implémentation de l'émission multiple est alors très simple. La ''Store queue'' a déjà deux ports, avec un pour les lectures et un autre pour les écritures. Idem pour le cache, qui a naturellement un port de lecture et un d'écriture. Émettre une lecture et une écriture simultanément ne demande donc pas de modification digne de ce nom. La ''Store queue'' doit juste supporter une lecture et une écriture simultanée. Ajouter l'émission d'une seconde lecture n'est pas compliquée : il faut juste rajouter un second port de lecture sur le cache et la ''Store queue''. Pour rappel, l'unité mémoire est précédée par une structure qui met en attente les micro-opérations mémoire avant leur émission finale. La structure en question varie suivant l'implémentation, mais un cas particulier se marie bien avec l'émission multiple des µops mémoire. Il s'agit du cas avec une file de µops séparée pour les lectures et une autre pour les écritures, appelées respectivement la ''file de µops LOAD'' et la ''file de µops STORE''. Il est alors possible d'émettre une lecture et une écriture lors du même cycle d'horloge. Le port de lecture alimente une unité de calcul d'adresse dédiée, directement reliée au cache. Le port d'écriture du cache alimente une unité de calcul, qui est suivie par une file d'écriture, elle-même reliée au cache. Les processeurs AMD de micro-architecture K6 utilisaient cette technique. [[File:Double émission avec le Load Ordering, Store Ordering.jpg|centre|vignette|upright=2.5|Double émission avec le Load Ordering, Store Ordering]] ==Les CPU superscalaire à exécution dans le désordre== La superscalarité modifie aussi les circuits dédiés à l'exécution dans le désordre. Par contre, elle modifie les unités de renommage de registre, le ROB et les autres structures liées à l'exécution dans le désordre, au sens large. Dans cette section, nous allons voir comment les étages du pipeline dédiés à l'exécution dans le désordre sont modifiées sur un CPU superscalaire. ===La superscalarité avec une fenêtre d'instruction centralisée : largeur de décodage et d'émission=== Plus haut, nous avons montré qu'un processeur pouvait avoir plus de ports d'émission que de ports de décodage. On dit aussi que sa ''largeur de décodage'' était supérieure à sa ''largeur d'émission''. La '''largeur de décodage''' est le nombre de µops fournies par l'unité de décodage par cycle d'horloge, la '''largeur d'émission''' est le nombre maximal de µops émises par cycle d'horloge. Et n'allez pas croire qu'un surplus de ports d'émission est du gâchis. Sur les processeurs avec exécution dans le désordre, ce surplus peut être utilisé. Mais avant toute chose, évacuons un détail. Vous avez sans doute pensé que le surplus peut être exploité par les instructions machines décodées en plusieurs µops . Par exemple, si une instruction est décodée en deux µops, on pourrait avoir un port d'émission en plus pour en profiter. Mais les ports de décodage sont *en sortie du décodeur*, et contiennent des µops, pas des instructions. Le surplus de ports d'émission peut occasionnellement utiliser le surplus de ports de décodage, mais cela implique que des µops ont été mises en attente dans l'unité d'émission, pour être émises par la suite. Et cela implique l'usage d'une fenêtre d'instruction ou d'une file de µops. Le principe est que les µops sont accumulées dans la fenêtre d'instruction, en attendant leurs opérandes ou la résolution d'une dépendance. Et si l'occasion se présente, une µops est émise sur chaque port d'émission. Il y a donc un gain en performances, mais il faut faire quelques précisions sur son origine. La largeur d'émission utilisée ne peut pas dépasser, *en moyenne*, la largeur de décodage. Et c'est intuitif : un CPU qui décode 4 µops par cycle ne peut pas émettre 6 par cycle en moyenne, sur le temps long, il n'y a pas de µops en surplus qui sortent du vide. Par exemple, sur un processeur quadruple émission, si on analyse combien d'instructions sont émises par cycle, il peut y avoir des cycles où la largeur d'émission est totalement utilisée, mais ce sera compensé par des périodes où elle est fortement sous-utilisée. Les µops sont accumulées en cas de sous-utilisation, puis consommées en cas d'utilisation complète. Les deux périodes se compensent. Et c'est là que l'on peut comprendre d'où vient le gain en performance. Les cycles où les ports d'émission sont sous-utilisés existent systématiquement, que le processeur utilise l'exécution dans le désordre ou pas. Ils sont liés à la présence de dépendances de données, aux contraintes d’appariement, etc. Elles tendent à réduire la moyenne du nombre de µops émises par cycle. Par contre, ce sont les cycles de rattrapage, où les ports en surplus sont utilisés, qui font remonter la moyenne. Le gain en performance est donc lié à ces cycles de rattrapage, où les µops accumulées sont émises dans les ports en surplus. Et ils n'existent que sur les processeurs avec une file/fenêtre d'instruction. La différence entre largeur de décodage et d'émission a un impact assez fort sur le banc de registre, et précisément sur son nombre de ports de lecture. Le banc de registre a besoin de plus de ports de lecture avec une fenêtre d’instruction qu'avec des stations de réservation. Rappelons la terminologie utilisée dans ce cours. Les fenêtres d'instruction se contentent de mémoriser la micro-opération et quelques bits pour détecter la disponibilité des opérandes. Par contre, les stations de réservations mémorisent aussi les opérandes des instructions. Et cette différence a une influence le nombre de ports de lecture du banc de registre. Supposons que toutes les instructions sont dyadiques, à deux opérandes. Comptons le nombre de ports nécessaires pour alimenter les unités de calcul, sans utiliser d'arbitre de banc de registre. Avec une fenêtre d'instruction, il faut le double du nombre de ports d'émission. Mais avec des stations de réservation, il faut le double des ports de décodage. La raison est que les registres sont lus après émission avec une fenêtre d'instruction, avant avec des stations de réservation (sous-entendu, après le décodage). Or, comme dit plus haut, les processeurs superscalaires larges ont souvent plus de ports d'émission que de décodage. ===La superscalarité à fenêtres d'instruction décentralisées : la largeur de ''dispatch'' et de ''schedule''=== Les processeurs précédents utilisaient une unité d'émission simple, qui pouvait utiliser l'émission dans l'ordre ou dans le désordre. Elle peut être remplacée par une fenêtre d'instruction centralisée dans que cela quoi que ce soit aux explications précédentes, idem avec une station de réservation centralisée. En soi, l'exécution dans le désordre est donc totalement compatible avec une implémentation simple de la superscalarité. Par contre, des subtilités surviennent avec les fenêtres d'instruction décentralisées. Pour rappel, sur ces processeurs, les µops sont émises en deux phases. Une première phase de ''dispatch'' qui envoie les µops vers la fenêtre d'instruction adéquate, sans détecter les dépendances de données. La seconde phase de ''schedule'' gère les dépendances de données, elle met en attente les µops en attendant que l'unité soit disponible pour les exécuter, en attendant aussi que les opérandes soient disponibles. Et à ce petit jeu, il y a une différence entre le nombre de µops qui sont ''dispatch par cycle'' et celles qui sont ''schedule''. Dit autrement, la largeur d'émission est scindée en une '''largeur de ''dispatch''''' et une '''largeur de ''schedule''. Par exemple, prenons un processeur triple émission avec deux stations de réservation : une pour les µops entières/mémoire, et une autre pour les µops flottantes. L'unité d'émission peut ''dispatch'' maximum trois µops par cycle : soit trois µops entières/mémoire, soit trois flottantes, soit un mix de trois µops différentes. Les stations de réservation, quant à elles, peuvent chacune ''schedule'' 3 µops par cycle, vers trois ALUs entières pour la station de réservation entière/mémoire, vers trois FPS pour la station de réservation flottante. De quoi ''dispatch'' trois µops, mais d'en ''schedule'' six, à chaque cycle. Et cet exemple ne sort pas de nulle part : les CPU x86 d'Intel et AMD sont globalement dans ce cas, même si les chiffres varient d'une microarchitecture à l'autre. L'exemple précédent colle parfaitement à la microarchitecture K7 d'AMD, utilisée sur les processeurs Athlon dans les années 2000. [[File:AMD K7.png|centre|vignette|upright=2.5|AMD K7, microarchitecture.]] ===L'unité de renommage superscalaire=== Avec la superscalarité, l'unité de renommage de registre doit gérer le cas où des instructions consécutives ont des dépendances de registre. Par exemple, prenons un processeur à double émission, qui renomme deux instructions consécutives. Si elles ont une dépendance de registre, le renommage de la seconde instruction dépend du renommage de la première. La première doit être renommée et le résultat du renommage est utilisé pour renommer la seconde, ce qui empêche d'utiliser deux unités de renommage séparées. Pour cela, elle renomme les registres sans tenir compte des dépendances, pour ensuite corriger le résultat. [[File:Unité de renommage superscalaire.png|centre|vignette|upright=2|Unité de renommage superscalaire.]] Seules les dépendances lecture-après-écriture doivent être détectées, les autres étant supprimées par le renommage de registres. Repérer ce genre de dépendances se fait assez simplement : il suffit de regarder si un registre de destination d'une instruction est un opérande d'une instruction suivante. [[File:Détection des dépendances sur un processeur superscalaire.png|centre|vignette|upright=2|Détection des dépendances sur un processeur superscalaire.]] Ensuite, il faut corriger le résultat du renommage en fonction des dépendances. Si une instruction n'a pas de dépendance avec une autre, on la laisse telle quelle. Dans le cas contraire, un registre opérande sera identique avec le registre de destination d'une instruction précédente. Dans ce cas, le registre opérande n'est pas le bon après renommage : on doit le remplacer par le registre de destination de l'instruction avec laquelle il y a dépendance. Cela se fait simplement en utilisant un multiplexeur dont les entrées sont reliées à l'unité de détection des dépendances. On doit faire ce replacement pour chaque registre opérande. [[File:Correction des dépendances sur un processeur superscalaire.png|centre|vignette|upright=2|Correction des dépendances sur un processeur superscalaire.]] ===Le tampon de réordonnancement et le banc de registre d'un CPU superscalaire=== Le ROB doit aussi être modifié pour émettre et terminer plusieurs instructions en même temps, en ajoutant des ports de lecture/écriture. Pour un CPU superscalaire peut émettre 8 µops à la fois, on se dit que le ROB peut retirer 8 µops par cycle maximum. Et c'est très souvent le cas en pratique. Cependant, certains processeurs ont un ROB qui peut retirer le double ! Un exemple est celui des CPU de micro-architecture Skymont, qui peuvent émettre 8 µops par cycle, mais en retirer 16. Quelques CPU de marque AMD étaient aussi dans ce cas. L'avantage est que cela permet de réduire la taille du ROB, du banc de registre et de quelques autres structures matérielles liées à l'exécution dans le désordre. Le gain sur-compense le cout en transistor nécessaire pour retirer 16 µops par cycle. Réduire le nombre de ports d'émission permet de garder un banc de registre, des fenêtres d'instruction et un ROB simples, rapides et économes en énergie. Et mine de rien, c'est là un compromis important : à quoi bon augmenter le nombre de ports d'émission si c'est pour que ce soit compensé par une fréquence plus faible ? Par contre, réduire le nombre de ports augmente la ''port contention'', surtout si on a mal réparti les ALU. Il faut donc trouver un compromis entre réduire le nombre de ports d'émission d'un côté, réduire la ''port contention'' de l'autre. ==Les optimisations des processeurs superscalaires larges== Les processeurs superscalaires larges ont des optimisations spécifiques, qui n'ont pas de sens sur les processeurs superscalaires étroits. Dans les grandes lignes, elles se classent en deux types. Les premières permettent de charger/décoder plus de 5-10 instructions simultanément. Le problème est alors que les branchements viennent mettre leur grain de sel et qu'il faut en tenir compte. Les secondes corrigent des problèmes qui apparaissent quand un CPU intègre plus de 5 à 10 unités de calcul. ===L'étape de chargement d'un CPU superscalaire large=== Charger plusieurs instructions pose de nombreux problèmes sur les processeurs superscalaires larges, qui émettent de 5 à 10 instructions simultanées. Pour rappel, la stratégie basique pour charger N instructions est de charger un bloc de N instructions consécutives. Les instructions après un branchement ne sont pas décodées ni exécutées, ce qui entraine une petite baisse de performance. Pour un N petit, la perte de performance est mineure. Mais pour un N large, elle devient suffisamment importante pour que la conception du CPU doit alors être repensée de fond en comble. Les analyses statistiques disent qu'il y a environ un branchement tous les 4 à 6 instructions. Un CPU a donc tendance à décoder des paquets de 3 à 5 instructions consécutives, avant qu'un branchement casse la dynamique. Or, cela pose des problèmes pour utiliser plusieurs décodeurs. L'implémentation basique, qui charge N instructions consécutives pour alimenter N décodeurs, fonctionne donc jusqu'à 4/6 décodeurs. Au-delà de 5 instructions, un CPU superscalaire doit être adapté pour gérer les branchements dans les paquets d'instruction qu'il émet. Le nombre de 5 instructions ne sort donc pas de nulle part. La première solution utilise un cache de µops, avec un ''front-end'' capable de charger/décoder 4/5 instructions à la fois, pas plus. L'idée est que la majorité des instructions proviennent du cache de µops (plus de 80%), ce qui fait qu'on peut émettre environ 10 instructions à partir du cache de µops, pas besoin d'avoir beaucoup de décodeurs. Les performances sont cependant dégradées lors d'un défaut de cache de µops. Les instructions doivent alors être chargées depuis le cache d'instruction, par paquets de 4-5, puis sont décodées par 4-5 décodeurs, ce qui ne permet pas d’alimenter les unités de calcul à plein régime. La solution précédente joue sur la distinction entre pots de décodage et d'émission. Elle permet d'émettre un grand nombre de µops sans pour autant charger un grand nombre d'instructions en même temps. En quelque sorte, ce n'est qu'une méthode de contournement, le CPU superscalaire reste de type étroit. Mais il est aussi possible d'utiliser un '''''front-end large''''', capable de charger une bonne dizaine d'instructions en même temps. La seule difficulté est de tenir compte de la présence de branchements. Pour rappel, avec un ''front-end'' étroit, les instructions situées après le premier branchement pris sont annulées, elles ne sont pas décodées ni exécutées. [[File:Fetch sur un processeur superscalaire avec prediction de branchements.png|centre|vignette|upright=2|Fetch sur un processeur superscalaire avec prédiction de branchements.]] Un ''front-end'' large charge les instructions de destination du branchement et les placent à sa suite. Ils chargent deux blocs à la fois et les fusionnent en un seul qui ne contient que les instructions présumées utiles. [[File:Cache d'instructions autoaligné.png|centre|vignette|upright=2|Cache d'instructions auto-aligné.]] Mais cela demande de charger deux blocs de mémoire en une fois, ce qui demande un cache d'instruction multiports. Il faut aussi ajouter un circuit pour assembler plusieurs morceaux de blocs en un seul : le fusionneur (''merger''). En pratique, cette solution demande d'ajouter un second port de lecture au cache, pour un cout en hardware important, avec un impact négatif sur le temps d'accès au cache. Et il faut rajouter le fusionneur, qui prend un étage de pipeline à lui tout seul. Et encore, cette solution ne gère pas le cas où un bloc contient plusieurs branchements ! [[File:Implémentation d'un cache d'instructions autoaligné.png|centre|vignette|upright=2|Implémentation d'un cache d'instructions auto-aligné.]] Les exemples précédents partent du principe qu'il n'y a qu'un seul branchement dans un bloc. Mais quand les blocs deviennent assez longs, il est fréquent d'avoir plusieurs branchements dans un bloc. Et il est même possible d'avoir plusieurs branchements pris. Dans un bloc de 8-10 instructions, ce n'est pas rare, et les CPU superscalaires à 8-10 voies sont la règle dans les CPU des années 2020. Pour gérer ce cas, il faut ajouter des ports supplémentaires au cache d'instruction et adapter le fusionneur, pour qu'il puisse fusionner trois ou quatre blocs. De plus, il faut utiliser une unité de prédiction de branchement capable de ''prédire plusieurs branchements par cycle''. ===Le décodage superscalaire large=== Le décodage des instructions n'est pas censé dépendre de l'implémentation du chargement. En théorie, une fois plusieurs blocs d'instruction fusionnés, on peut décoder les instructions en parallèle. Cependant, certains processeurs effectuent le décodage en tenant compte de la manière dont les branchements découpent les blocs. Les processeurs Atom d'Intel utilisent une solution de ce type, qui couple prédiction de branchement et décodage parallèle. Ils chargent/décodent des paquets de 3 instructions consécutives, chaque paquet étant séparé du précédent par un branchement. Pour cela, en plus d'avoir un fusionneur adapté, ils regroupent les décodeurs en ''clusters'', chacun regroupant 3 décodeurs, et pouvant décoder trois instructions consécutives. Les micro-architectures Tremont et Crestmont avaient deux ''clusters'' (6 décodeurs au total), la micro-architecture Skymont en intègre trois (9 décodeurs au total). Avec deux ''clusters'', le fonctionnement est le suivant. Si le code est composé d'une suite d'instruction sans branchements, seul le premier ''cluster'' est utilisé, limitant le décodage à trois instructions par cycle. Par contre, si l'une des trois instructions décodée est un branchement, alors le second ''cluster'' décode les trois instructions situées après ce branchement, à partir de l'adresse de destination. Avec un troisième ''cluster'', c'est la même chose, sauf qu'il faut qu'il y ait un branchement dans les trois instructions décodées par le second ''cluster''. Mais comment faire pour savoir quels décodeurs activés, quels ''clusters'' sont actifs ? C'est la prédiction de branchement qui commande. Avec deux ''clusters'', elle détermine si un branchement est présent dans les trois instructions à charger, et quelle est son adresse de destination. Sans branchement, elle prévient les décodeurs et les trois instructions chargées sont décodées dans un ''cluster'' libre. Si elle prédit un branchement, elle charge les instructions adéquates, et active les deux ''clusters''. Avec trois branchements, c'est le même mécanisme, sauf que l'unité de prédiction de branchement doit être capable de prédire deux branchements par cycle. Il faut noter que les décodeurs d'un ''cluster'' sont des décodeurs simples, sans microcode. Sur les processeurs Tremont et Crestmont, le microcode était unique et était partagé entre tous les ''clusters''. Un seul ''cluster'' pouvait décoder une instruction complexe avec le microcode, l'autre devait se limiter à des instructions simples uniquement. Depuis Skymont, le microcode est partiellement dupliqué, avec un microcode par ''cluster''. Le cout en hardware peut paraitre prohibitif, pour un gain en performance faible. Sauf que c'est oublier que ce sont des processeurs Atom, sur lesquels beaucoup d'instructions sont microcodées. Les processeurs Atom ont un budget en transistor limité, ce qui fait qu'il est préférable pour eux d'implémenter des instructions en microcode que d'utiliser des unités de calcul dédiées ou des circuits de décodage complexes. Pour limiter la casse, le microcode n'est que partiellement dupliqué. Seul le microcode pour les instructions fréquente est dupliqué. Il y a un microcode secondaire pour certaines instructions, qui est lui unique et partagé par tous les ''clusters''. ===Le contournement sur les processeurs superscalaires larges=== Pour rappel, la technique du contournement (''register bypass'') permet au résultat d'une instruction d'être immédiatement utilisable en sortie de l'ALU, avant même d'être enregistré dans les registres. Implémenter la technique du contournement demande d'utiliser des multiplexeurs pour relier la sortie de l'unité de calcul sur son entrée si besoin. Il faut aussi des comparateurs pour détecter des dépendances de données. [[File:Pipeline Bypass.png|centre|vignette|upright=1|Pipeline Bypass]] La sortie de chaque ALU doit être reliée aux entrées de toutes les autres, avec les comparateurs qui vont avec. Sur les processeurs ayant plusieurs d'unités de calculs, cela demande beaucoup d’interconnexions et de comparateurs. Pour N unités de calcul, cela demande 2 * N² interconnexions, implémentées avec 2N multiplexeurs de N entrées chacun. Si c'est faisable pour 2 ou 3 ALUs, la solution est impraticable au-delà. Non seulement ce serait un enfer à câbler, mais les fils seraient tellement longs que la fréquence du processeur en prendrait un coup. Notez que cela vaut pour les processeurs superscalaires, mais aussi pour tout processeur avec beaucoup d'unités de calcul. En théorie, un CPU à exécution dans le désordre, non-superscalaire, mais avec beaucoup d'unités de calcul, fait face au même problème. En pratique, avoir une dizaine d'ALU implique processeur superscalaire à exécution dans le désordre. D'où le fait qu'on parle du problème maintenant. La seule solution praticable est de ne pas relier toutes les unités de calcul ensemble. À la place, on préfère regrouper les unités de calcul dans différents '''agglomérats''' ('''cluster'''). Le contournement est alors possible entre les unités d'un même agglomérat, mais pas entre agglomérats différents. Évidemment, certaines possibilités de contournement sont perdues, mais c'est compensé par un gain en fréquence et un cout en circuit/fils réduit. C'est un bon compromis, ce qui explique que presque tous les processeurs modernes l'utilisent. Les rares exceptions sont les processeurs POWER 4 et POWER 5, qui ont préféré se passer de contournement pour garder un processeur très simple et une fréquence élevée. ===Le banc de registre des processeurs superscalaires larges=== Pour émettre deux opérations entières simultanément, il faut lire deux fois plus d'opérandes. Et il faut aussi écrire deux résultats dans les registres, en même temps. Pour cela, on double les ports du banc de registre. En général, il faut au maximum 2 ports de lecture et un port d'écriture par unité de calcul. Le problème, c'est que plus un banc de registres a de ports, plus il utilise de circuits, est compliqué à concevoir, consomme de courant et chauffe. Avec plusieurs dizaines d'unités de calcul différentes, ajouter autant de ports impacterait la fréquence du banc de registres et réduirait les performances, voire est totalement impraticable à cause des contraintes thermiques. Les processeurs superscalaires larges doivent donc trouver des solutions. La première solution utilise un banc de registre unique, mais n'utilise pas autant de ports que le pire des cas le demanderait. Le processeur détecte quand il n'y a pas assez de ports pour servir toutes les micro-opérations. Quand ça arrive, l'unité d'émission émet assez de micro-opérations pour exploiter les ports disponibles, mais met en attente les micro-opérations en trop, le temps que les ports se libèrent. Pour cela, le banc de registre est géré par un circuit d'arbitrage spécialisé, intégré à l'unité d'émission, l’'''arbitre du banc de registres''' (''register file arbiter''). Une autre solution est de dupliquer le banc de registres en plusieurs exemplaires qui contiennent exactement les mêmes données, mais avec moins de ports de lecture/écriture. Un exemple est celui des processeurs POWER, Alpha 21264 et Alpha 21464. Sur ces processeurs, le banc de registre est dupliqué en plusieurs exemplaires, qui contiennent exactement les mêmes données. Les écritures dans les registres sont envoyées à tous les bancs de registres, afin de garantir que leur contenu est identique. Chaque exemplaire a exactement deux ports de lecture. La conception du processeur est simplifiée, que ce soit au niveau du câblage, que de la conception des bancs de registres. Une dernière solution découpe le banc de registres en plusieurs bancs de registres plus petits, avec un réseau d'interconnexions pour échanger des données entre bancs de registres. Dans la plupart des cas, cette séparation est invisible du point de vue de l'assembleur et du langage machine. Le processeur se charge de transférer les données entre bancs de registres suivant les besoins. Sur d'autres processeurs, les transferts de données se font via une instruction spéciale, souvent appelée ''COPY''. [[File:Banc de registres distribué.png|centre|vignette|upright=2|Banc de registres distribué.]] Sur de certains processeurs, un branchement est exécuté par une unité de calcul spécialisée. Or les registres à lire pour déterminer l'adresse de destination du branchement ne sont pas forcément dans le même agglomérat que cette unité de calcul. Pour éviter cela, certains processeurs disposent d'une unité de calcul des branchements dans chaque agglomérat. Dans les cas où plusieurs unités veulent modifier le ''program counter'' en même temps, un système de contrôle général décide quelle unité a la priorité sur les autres. Mais d'autres processeurs fonctionnent autrement : seul un agglomérat possède une unité de branchement, qui peut recevoir des résultats de tests de toutes les autres unités de calcul, quel que soit l’agglomérat. ==La macro-fusion : une optimisation du décodage parallèle== Les processeurs superscalaires supportent la technique dite de '''macro-fusion''', qui permet de fusionner deux-trois instructions consécutives en une seule micro-opération. Par exemple, il est possible fusionner une instruction de test et une instruction de saut en une seule micro-opération de branchement. Il s'agit là de l'utilisation la plus importante de la macro-fusion sur les processeurs x86 modernes. La macro-fusion est utilisée pour fusionner un test et un saut en un branchement unique. L'optimisation est présente sur la plupart des CPU superscalaires modernes. Les CPU x86 l'implémentent, mais aussi quelques CPU ARM et les processeurs Apple de série M (M1 à M5, et potentiellement plus quand ils seront sortis). La fusion des instructions se fait lors du décodage des instructions, grâce à la coopération des décodeurs. Elle marche systématiquement si l'instruction de test et le saut sont consécutives, qu'elles forment bien une paire d'instruction. Par contre, insérer une instruction entre les deux empêche la macro-fusion de faire son travail, sauf si l'instruction insérée est un NOP. Les processeurs ARM64 avaient une forme spécifique de macro-fusion, utilisée pour un cas très précis : charger une constante immédiate de 32 ou 64 bits, dans un registre 64 bits. Les CPU ARM 64 bits utilisent des instructions codées sur 32 bits, qui ne peuvent encoder que des constantes immédiates de 16 bits. Pour charger une constante de 32/64 bits, il faut s'y prendre en plusieurs fois. Une première instruction MOVZ charge les 16 premiers bits, puis 2 à 4 instructions MOVK remplissent le reste. MOVZ met à zéro les 48 bits de poids fort, MOVK permet de sélectionner le doublet à modifier dans le registre (on modifie 16 bits, dont on choisit la place, le reste du registre est laissé tel quel). ARM permet de fusionner cette suite d'instructions en une seule µop MOV avec une constante immédiate. On parle alors de '''''MOVZ + MOVK fusion'''''. Une dernière utilisation, [https://www2.eecs.berkeley.edu/Pubs/TechRpts/2016/EECS-2016-130.pdf proposées pour le jeu d'instruction RISC-V], fusionnait un calcul d'adresse et une instruction LOAD/STORE en une seule instruction d'accès mémoire. Elle a été envisagée à une époque où le jeu d'instruction RISC-V ne gérait pas les modes d'adressage base + indice. L'idée était d'avoir une unité mémoire qui gérait le mode d'adressage base + indice et de fusionner des instructions pour l'exploiter. Mais de nos jours, le jeu d'instruction RISC-V intègre le mode d'adressage base + indice, ce qui fait que la macro-fusion n'a aucun intérêt pour ce cas précis. La macro-fusion a de nombreuses limitations en pratique. En théorie, on pourrait l'utiliser au-delà des deux situations mentionnées plus haut. Par exemple, pour fusionner une addition et une multiplication en une seule instruction MAD. Mais l'intérêt est assez faible. Notons que la macro-fusion ne permet que de fusionner des paires d'instructions. Aller au-delà demanderait un cout en circuit trop important. Déjà que tester deux paires utilise pas mal de comparateurs... ==Le ''stack engine'' : une optimisation de la pile d'appel== Les processeurs modernes intègrent une optimisation liée au pointeur de pile. Pour rappel, sur les architectures modernes, le pointeur de pile est un registre utilisé pour gérer la pile d'appel, précisément pour savoir où se trouve le sommet de la pile. Il est manipulé par les instructions CALL, RET, PUSH et POP, mais aussi par les instructions d'addition/soustraction pour gérer des cadres de pile de taille variable. De plus, il peut servir d'opérande pour des instructions LOAD/STORE, afin de lire/écrire des variables locales, les arguments d'une fonction, et autres. C'est donc un registre général adressable, intégré au banc de registre, altéré par l'unité de calcul entière. L'incrémentation/décrémentation du pointeur de pile passe donc par l'unité de calcul, lors des instructions CALL, RET, PUSH et POP. Mais, l'optimisation que nous allons voir permet d'incrémenter/décrémenter le pointeur de pile sans passer par l'ALU, ou presque. L'idée est de s'inspirer des architectures avec une pile d'adresse de retour, qui intègrent le pointeur de pile dans le séquenceur et l'incrémentent avec un incrémenteur dédié. ===Le ''stack engine''=== L'optimisation que nous allons voir utilise un '''''stack engine''''' intégré à l'unité de contrôle, au séquenceur. Le processeur contient toujours un pointeur de pile dans le banc de registre, cela ne change pas. Par contre, il n'est pas incrémenté/décrémenté à chaque instruction CALL, RET, PUSH, POP. Un compteur intégré au séquenceur est incrémenté à la place, nous l’appellerons le '''compteur delta'''. La vraie valeur du pointeur de pile s'obtient en additionnant le compteur delta avec le registre dans le banc de registre. Précisons que si le compteur delta vaut zéro, la vraie valeur est dans le banc de registre et peut s'utiliser telle quelle. Lorsqu'une instruction ADD/SUB/LOAD/STORE utilise le pointeur de pile comme opérande, elle a besoin de la vraie valeur. Si elle n'est pas dans le banc de registre, le séquenceur déclenche l'addition compteur-registre pour calculer la vraie valeur. Finalement, le banc de registre contient alors la bonne valeur et l'instruction peut s'exécuter sans encombre. L'idée est que le pointeur de pile est généralement altéré par une série d'instruction PUSH/POP consécutives, puis des instructions LOAD/STORE/ADD/SUB utilisent le pointeur de pile final comme opérande. En clair, une bonne partie des incrémentations/décrémentation est accumulée dans le compteur delta, puis la vraie valeur est calculée une fois pour toutes et est utilisée comme opérande. On accumule un delta dans le compteur delta, et ce compteur delta est additionné quand nécessaire. Précisons que le compteur delta est placé juste après le décodeur d'instruction, avant même le cache de micro-opération, l'unité de renommage et l'unité d'émission. Ainsi, les incrémentations/décrémentations du pointeur de pile disparaissent dès l'unité de décodage. Elles ne prennent pas de place dans le cache de micro-opération, ni dans la fenêtre d'instruction, ni dans la suite du pipeline. De nombreuses ressources sont économisées dans le ''front-end''. Mais utiliser un ''stack engine'' a aussi de nombreux avantages au niveau du chemin de données/''back-end''. Déjà, sur les processeurs à exécution dans le désordre, cela libère une unité de calcul qui peut être utilisée pour faire d'autres calculs. Ensuite, le compteur delta mémorise un delta assez court, de 8 bits sur le processeur Pentium M, un peu plus pour les suivants. L'incrémentation se fait donc via un incrémenteur 8 bits, pas une grosse ALU 32/64 bits. Il y a un gain en termes de consommation d'énergie, un incrémenteur 8 bits étant moins gourmand qu'une grosse ALU 32/64 bits. ===Les points de synchronisation du delta=== La technique ne fonctionne que si la vraie valeur du pointeur de pile est calculée au bon moment, avant chaque utilisation pertinente. Il y a donc des '''points de synchronisation''' qui forcent le calcul de la vraie valeur. Plus haut, nous avions dit que c'était à chaque fois qu'une instruction adresse le pointeur de pile explicitement, qui l'utilise comme opérande. Les instructions CALL, RET, PUSH et POP ne sont pas concernées par elles utilisent le pointeur de pile de manière implicite et ne font que l'incrémenter/décrémenter. Mais dans les faits, c'est plus compliqué. D'autres situations peuvent forcer une synchronisation, notamment un débordement du compteur delta. Le compteur delta est généralement un compteur de 8 bits, ce qui fait qu'il peut déborder. En cas de débordement du compteur, le séquenceur déclenche le calcul de la vraie valeur, puis réinitialise le compteur delta. La vraie valeur est donc calculée en avance dans ce cas précis. Précisons qu'un compteur delta de 8 bits permet de gérer environ 30 instructions PUSH/POP consécutives, ce qui rend les débordements de compteur delta assez peu fréquent. À noter que si le compteur delta vaut zéro, il n'y a pas besoin de calculer la vraie valeur, le séquenceur prend cette situation en compte. Un autre point de synchronisation est celui des interruptions et exceptions matérielles. Il faut que le compteur delta soit sauvegardé lors d'une interruption et restauré quand elle se termine. Idem lors d'une commutation de contexte, quand on passe d'un programme à un autre. Pour cela, le processeur peut déclencher le calcul de la vraie valeur lors d'une interruption, avant de sauvegarder les registres. Pour cela, le processeur intègre un mécanisme de sauvegarde automatique, qui mémorise la valeur de ce compteur dans le tampon de réordonnancement, pour forcer le calcul de la vraie valeur en cas de problème. La technique du ''stack engine'' est apparue sur les processeurs Pentium M d'Intel et les processeurs K10 d'AMD, et a été conservée sur tous les modèles ultérieurs. L'implémentation est cependant différente selon les processeurs, bien qu'on n'en connaisse pas les détails et que l'on doive se contenter des résultats de micro-benchmarks et des détails fournit par Intel et AMD. Selon certaines sources, dont les manuels d'optimisation d'Agner Fog, les processeurs AMD auraient moins de points de synchronisation que les processeurs Intel. De plus, leur ''stack engine'' serait placé plus loin que prévu dans le pipeline, après la file de micro-opération. ==La micro-fusion et la délamination== La '''micro-fusion''' est une optimisation qui retarde le décodage réel de certaines instructions assez loin dans le pipeline. Elle concerne surtout les instructions ''load-op'', mais pas que. D'autres instructions mémoire peuvent subir cette optimisation. L'essentiel est que, sans micro-fusion, ces instructions sont décodées en plusieurs micro-opérations. La micro-fusion retarde le décodage final de ces instructions, qui se fait assez tard dans le pipeline. Pour comprendre l'intérêt, nous allons devoir faire quelques explications. L'optimisation en question fait le décodage en deux temps. Prenons l'exemple d'une opération ''load-op'', qui est censée être décodée en deux micro-opérations : une lecture, puis une opération. Le décodage produit une '''macro-opération''' (terme d'Intel), qui est propagée dans le pipeline, pour être scindée en deux micro-opérations un peu avant les ALU et l'unité mémoire. La macro-opération est techniquement une micro-opération comme une autre, sauf que ce n'est pas la micro-opération finale. L'unité d'émission découpe cette macro-opération en deux micro-opérations, qui sont émises l'une après l'autre. Pour résumer, les instructions ''load-op'' ne sont complétement découpées en micro-opération lors de l'émission, pas lors du décodage. L'optimisation impacte ce qui se situe entre le décodeur d'instruction et l'unité d'émission : l'unité de renommage, la file de micro-opérations, le ''Loop Stream Detector'' et/ou le cache de micro-opération. L'intérêt de l'optimisation est que l'on fusionne deux micro-opérations en une seule macro-opération. Le cache de micro-opération et la file de micro-opération peuvent mémoriser plus de micro-opérations, vu que certaines sont fusionnées. Quant à l'unité de renommage, elle renomme une seule macro-opération au lieu de renommer deux micro-opérations, ce qui augmente le débit du renommage de registre. Par contre, cela complique un peu le travail de l'unité d'émission, qui doit découper les instructions ''load-op'' en deux et émettre les micro-opérations l'une après l'autre. Il faut noter qu'il n'y a pas de fusion proprement dite. Le décodeur ne décode pas deux micro-opérations séparées, avant de les fusionner. En réalité, il génère directement une macro-opération, qui correspond à deux micro-opérations, que ce soit plus tard dans le pipeline ou sur une autre architecture. D'ailleurs, la micro-fusion a lieu à l'intérieur d'une instruction, qui est censée être traduite en plusieurs micro-opérations. Et encore : pas toutes. La conséquence est que la technique n'a de sens que sur les processeurs CISC, avec des instructions assez complexes pour être décodées en plusieurs micro-opérations. En pratique, seuls les CPU x86 sont concernés. ===Les domaines de fusion=== La micro-fusion n'est pas spécifique aux processeurs superscalaires. En théorie, on peut l'implémenter sur un processeur non-superscalaire à émission dans l'ordre. Cependant, je ne connais aucun processeur à émission dans l'ordre qui implémente la micro-fusion, même si son implémentation est en théorie possible sur de tels processeurs. La micro-fusion est implémentée différemment selon que l'émission se fasse dans l'ordre, dans le désordre, avec une fenêtre d'instruction, avec des stations de réservation, etc. J'ai dit plus haut que les micro-opérations sont générées lors de l'émission, mais le moment exact varie suivant le processeur. Pour simplifier le tout, nous parlerons de '''scission''' quand le processeur traduit une macro-opération en deux/trois micro-opérations. Le moment où a lieu la scission est théoriquement dans l'unité d'émission, mais celle-ci prend plusieurs formes selon que le processeur utilise l'émission dans l'ordre, dans le désordre, avec une fenêtre d'instruction, avec des stations de réservation, etc. Les processeurs à exécution dans l'ordre sont le cas le plus simple. L'unité d'émission est alors en charge du découpage des macro-opérations en micro-opérations. La file de micro-opération ou le cache de micro-opérations mémorisent des macro-opérations. À l'opposé, le tampon de ré-ordonnancement mémorise lui des micro-opérations. Le reste du processeur n'est pas affecté. Sur les processeurs à exécution dans le désordre, les différences sont nombreuses. Le tampon de ré-ordonnancement peut mémoriser soit les micro-opérations finales, soit des macro-opérations. Il en est de même avec la fenêtre d'instruction et/ou les stations de réservation, qu'elles soient centralisées ou décentralisées. Tout dépend de comment se fait l'implémentation. Une implémentation simple effectue la scission des macro-opérations dans l'unité d'émission, comme sur les processeurs à exécution dans l'ordre. Sur de nombreux processeurs, l'unité d'émission et de renommage sont simplement fusionnées, ce qui fait que la scission a lieu juste après le renommage de registres. En conséquence, le tampon de re-ordonnancement et les fenêtres d'instruction ne voient que des micro-opérations. Les macro-opérations disparaissent juste avant. Cette implémentation est préférée avec une fenêtre d'instruction centralisée, car on voit mal comment scinder une macro-opération juste avant l'ALU. Une autre implémentation fait la scission en sortie de la fenêtre d'instruction ou des stations de réservation, qu'elles soient centralisées ou décentralisées. Dans ce cas, le tampon de re-ordonnancement fait de même. L'avantage est que le tampon de ré-ordonnancement stocke des macro-opérations, ce qui augmente sa taille effective. Par exemple, il utilise une seule entrée pour une instruction ''load-op'', au lieu de deux sans micro-fusion de ce type. ===La délamination=== La scission peut parfois avoir lieu plus tôt, dans des circonstances très précises. Concrètement, cela ne concerne que quelques micro-architectures d'Intel, pour des instructions très précises. La raison est les limitations du format des micro-opérations, pas une histoire d'optimisation. Le processeur aimerait pouvoir faire la micro-fusion, mais ne peut pas à cause de ces limitations. Une telle situation cause alors une '''délamination''' : les micro-opérations sont scindées précocement. Par exemple, sur la micro-architecture SandyBridge d'Intel, les opérations ''load-op'' sont fusionnées, sauf si elles utilisent l'adressage indicé. Les micro-opérations de ce processeur ne peuvent avoir que deux opérandes. Trois opérandes est de trop, et les opérations ''load-op'' en adressage indicé dépassent cette limite. Dans ce cas, les micro-opérations sont fusionnées en sortie du décodeur, mais sont scindés avant d’entrer dans la file de micro-opération. L'avantage n'est pas évident, vu que l'unité de renommage suit de près le décodeur d'instruction. Il est que le cache de micro-opérations mémorise des macro-opérations, pas deux micro-opérations. ===La micro-fusion des processeurs Intel et AMD=== La micro-fusion est apparue sur le Pentium M d'Intel, et a été conservée telle quelle sur les processeurs Intel suivants. Les processeurs AMD supportent la micro-fusion depuis au moins la micro-architecture AMD K8. Mieux que ça, la délamination n'était pas utilisée car les micro-opérations acceptaient plus de 2 opérandes. Que ce soit chez AMD ou Intel, elle concerne deux types d'instructions : les écritures mémoire et les instructions ''load-op''. Sur le Pentium M, il y avait quelques limitations, l'optimisation se limitant par exemple aux registres entiers et flottants, mais pas aux registres MMX, mais pas les registres SSE (nous verrons ces registres dans quelques chapitres). Mais les processeurs modernes n'ont plus cette limitation. La micro-fusion des CPU Intel concerne deux types d'instructions : les écritures mémoire et les instructions ''load-op''. Pour les écritures, la situation est plus complexe que pour les instructions ''load-op''. Sur les processeurs Intel précédents, les écritures étaient réalisées dans deux unités séparées : le calcul d'adresse dans une unité de calcul, l'écriture proprement dite dans l'unité mémoire. Elles prenaient donc deux micro-opérations. Le processeur a changé ses unités mémoire, ce qui fait qu'elle pouvait faire les deux en une seule micro-opération. Il s'agissait alors d'une optimisation du chemin de données, plus que de la micro-fusion proprement dite, mais je la mentionne ici malgré tout. La micro-fusion sur ce processeur avait un avantage en plus des avantages usuels : elle utilisait mieux les décodeurs. Les Pentium M avaient trois décodeurs pour décoder plusieurs instructions en même temps, chose qu'on détaillera dans le chapitre sur les processeurs superscalaires. Un seul décodeur pouvait décoder toutes les instructions, les deux autres ne peuvent décoder que les instructions simples, qui se décodent en une seule micro-opération. Avec la micro-fusion, les trois décodeurs peuvent gérer les écritures mémoire et les opérations ''load-op'', vu qu'elles deviennent des instructions simples. Sans cela, elles auraient été limitées au premier décodeur. <noinclude> {{NavChapitre | book=Fonctionnement d'un ordinateur | prev=Le parallélisme mémoire | prevText=Le parallélisme mémoire | next=Exemples de microarchitectures CPU : le cas du x86 | nextText=Exemples de microarchitectures CPU : le cas du x86 }} </noinclude> th05fmlfbue9z1sngkwgl7w6e4s8oaa 773248 773247 2026-09-26T15:18:05Z Mewtow 31375 /* Les CPU superscalaire à exécution dans le désordre */ 773248 wikitext text/x-wiki Les processeurs vus auparavant ne peuvent émettre au maximum qu'une instruction par cycle d'horloge. Ils peuvent avoir plusieurs instructions qui s'exécutent en même temps, dans des unités de calcul séparées. C'est le cas dès qu'une instruction multi-cycles s'exécute. Par contre, ils ne peuvent démarrer qu'une seule instruction par cycle. Et quand on court après la performance, ce n'est pas assez ! Les concepteurs de processeurs ont inventés des processeurs qui émettent plusieurs instructions par cycle : les '''processeurs à émissions multiples'''. Un processeur à émission multiple charge plusieurs instructions en même temps, les décode en parallèle, puis les émet en même temps sur des unités de calculs séparées. Pour cela, il faut gérer les dépendances entre instructions, répartir les instructions sur différentes unités de calcul, et cela n'est pas une mince affaire. En général, pour un processeur à émission multiple capable d'émettre N instructions en parallèle, on parle de '''processeur à N voies''', ou encore un ''processeur à N pipelines''. [[File:Superscalarpipeline.svg|centre|vignette|upright=1.5|Pipeline RISC classique à cinq étages sur un processeur superscalaire. On voit bien que plusieurs instructions sont chargées en même temps.]] Les processeurs à émission multiple sont de deux types : les processeurs VLIW et les '''processeurs superscalaires'''. Nous mettons les processeurs VLIW de côté en attendant le prochain chapitre. La raison est qu'ils ont un jeu d'instruction spécialisé qui expose le parallélisme d'instruction directement au niveau du jeu d'instructions, là où les processeurs superscalaires restent des processeurs au jeu d'instruction normal. La différence principale entre processeur VLIW et superscalaire est qu'un processeur superscalaire répartit les instructions sur les unités de calcul à l’exécution, là où un processeur VLIW délègue cette tâche au compilateur. ==Le nombre de voies d'un processeur superscalaire== Les processeurs superscalaires les plus simples ne permettent que d'émettre deux micro-opérations à la fois, d'où leur nom de processeur ''dual issue'', ce qui se traduit en '''processeur à double émission'''. Les processeurs triple émission permettent eux d’émettre trois instructions à la fois. A l'opposé, les CPU superscalaires les plus puissants peuvent émettre jusqu'à 8-10 instructions simultanément. En 2026, les CPU superscalaires peuvent monter jusqu’à 10 µops émises en même temps, ce qui est énorme ! Et cela nous amène à faire une distinction entre les processeurs superscalaires dits '''étroits''' et ceux dits '''larges'''. Dans ce wikilivre, on part du principe que les processeurs superscalaires étroits émettent jusqu'à 4-5 µops à la fois, alors que les larges peuvent en émettre plus, l'intervalle entre les deux est une zone grise. Le nombre exact n'est pas fixé dans le marbre, la distinction est volontairement vague. Nous parlerons de la '''largeur d'un CPU superscalaire''' pour parler du nombre de voies, du nombre de µops émises en même temps. Plus un CPU superscalaire est large, plus il est performant ''toute chose égale par ailleurs''. Mais dans les faits, vous pouvez oublier la partie en italique. Élargir un CPU superscalaire demande de rajouter beaucoup de circuits. Et ce cout en circuit aura un impact sur la performance. A budget en transistors limité, il y a un compromis entre élargir un CPU et rajouter du cache ou la taille des fenêtres d'instructions/ROB et autres. ===Les contraintes d’appariement=== Prenons un processeur double émission, capable d'émettre deux instructions simultanément. Émettre deux instructions en même temps n'est pas toujours possible. Par exemple, si deux instructions sont dépendantes, on ne peut pas les émettre en même temps. Mais oublions un instant ces histoires de dépendances de données, et partons du principe que le CPU ait affaire à deux instructions indépendantes, une paire idéale. Un CPU double émission "parfait" pourrait exécuter n'importe quelle paire d'instruction. Par exemple, il pourrait émettre deux multiplications consécutives, deux lectures consécutives, une lecture et une écriture en même temps, etc. En pratique, aucun processeur ne permet cela. Implémenter un CPU superscalaire "parfait" aurait un cout en circuit beaucoup trop conséquent. Par exemple, pour émettre deux multiplications en même temps, il faudrait deux multiplieurs séparés. Idem pour exécuter deux accès mémoire simultané : cela demande d'avoir deux unités mémoire et un cache multiport. Mais en pratique, aucun CPU superscalaire ne fait cela. La totalité des CPU double émission se débrouille avec un seul circuit multiplieur et une seule unité mémoire. Et cela entraine l'apparition de dépendances structurelles, qui font que certaines paires d'instructions sont impossibles. Par exemple, impossible d'émettre deux multiplications en même temps avec un seul circuit multiplieur. Mais émettre une multiplication en parallèle d'une addition est parfaitement possible. Il y a donc des paires d'instructions autorisées et des paires interdites. Et la logique est la même sur les CPU à triple ou quadruple émission, et au-delà. : des triplets ou quadruplet d'instructions sont impossibles à émettre simultanément, alors que d'autres le sont. Pour le dire autrement, il y a des '''contraintes d'appariement''' sur les instructions à émettre en même temps. ===L'impact des contraintes d’appariement sur la performance=== Ces contraintes d’appariement ont un impact sur les performance, mais celui-ci peut être grandement mitigé. Les programmes informatiques utilisent en pratique beaucoup plus d'opérations entières de base que de multiplication, branchements et accès mémoire. Il y a environ 5 opérations entières de base pour une multiplication entière. En pratique, il est rare de devoir exécuter deux multiplications à la fois. Idem pour les décalages et rotations, qui sont assez éloignés les uns des autres. Deux branchements consécutifs sont quant à eux plus fréquents. Deux accès mémoire consécutifs est assez rare, mais ils restent assez proches les uns des autres malgré tout. La conséquence est que certaines paires d'instructions sont privilégiées par rapport au reste. Les paires regroupant deux calculs basiques (additions, soustractions, ...) sont systématiquement supportées, car dupliquer des ALU entières est facile. Les paires regroupant un calcul et une autre instruction sont elles aussi systématiquement supportées. Par contre, les paires avec deux branchements, deux accès mémoire, deux décalages/rotations, ou deux multiplications/divisions sont fréquemment interdites. Seuls les CPU superscalaires larges peuvent les permettre, et encore : sous conditions. Le résultat est que le processeur peut se débrouiller avec une seule unité mémoire et une unité de branchement. ===Terminologie pour la suite du chapitre=== Décrire les contraintes d’appariement demande de dire quelles combinaisons d’instructions sont autorisées ou interdites. Et pour cela, nous allons introduire les abréviations suivantes : * INT est un raccourcis pour parler d'une opération entière simple, qu'une ALU entière peut exécuter. * MUL correspond à une opération entière complexe, typiquement une multiplication, éventuellement à une division. * FLOAT désigne une opération flottante, peut importe laquelle. * MEM désigne un accès mémoire, à savoir une instruction ou µop mémoire. * BRANCH désigne une instruction de branchement. Cette terminologie permettra de définir des paires d'instructions, qu'elles soient possibles ou interdites. Par exemple, l'expression INT + FLOAT désigne une paire d'instruction regroupant une instruction entière et une instruction flottante, peu importe l'ordre des deux instructions. Il en sera de même pour les triplets, quadruplets, ou plus. Par exemple, l'expression INT + INT + FLOAT + MUL désigne un quadruplet d'instructions regroupant : deux instructions entières, une instruction flottante, et une multiplication entière. J'utiliserais aussi des parenthèses pour regrouper certaines combinaisons. Par exemple, l'expression INT + (MUL /FLOAT) veut parler d'une paire d'instruction où la première est une opération entière basique, la seconde est : soit une multiplication, soit une opération flottante. ==Le ''front-end'' des processeurs superscalaires== Un processeur superscalaire doit charger, décoder, et exécuter plusieurs instructions par cycle. Par exemple, un processeur triple émission doit charger 3 instructions lors d'un premier cycle, puis les décoder au cycle suivant, et les exécuter au cycle suivant. Il travaille donc pas paquets de 3 instructions, dans cet exemple. Intuitivement, charger, décoder et exécuter plusieurs instructions à la fois demande de dupliquer des circuits. Par exemple, il faut dupliquer les décodeurs pour décoder plusieurs instructions. Les unités de calcul sont aussi dupliquées, sauf sur certaines processeurs qui arrivent à ruser, mais laissons cela de côté pour plus tard. Mais d'autres circuits sont simplement modifiés, comme l'unité de chargement ou l'unité d'émission. Voyons ce qu'il en est dans le détail. {|class="wikitable" |- ! rowspan="2" | Processeur sans émission multiple | rowspan="2" | Chargement | rowspan="2" | Décodage | rowspan="2" | Renommage | rowspan="2" | Émission | Exécution / ALU | rowspan="2" | ''Commit''/ROB |- | Exécution / ALU |- ! rowspan="4" | Processeur superscalaire | rowspan="4" | Chargement | rowspan="2" | Décodage | rowspan="4" | Renommage | rowspan="4" | Émission | Exécution / ALU | rowspan="4" | ''Commit''/ROB |- | Exécution / ALU |- | rowspan="2" | Décodage | Exécution / ALU |- | Exécution / ALU |} Nous allons d'abord voir ce qu'il en est pour le ''front-end'', à savoir la partie du CPU qui s'occupe de charger et décoder les instructions. Elle regroupe l'unité de chargement, les décodeurs, l'unité de prédiction de branchement, la file de µops, la file d'instruction, et divers caches. En soi, les changements portent surtout sur l'unité de chargement et les décodeurs. ===Le chargement de plusieurs instructions consécutives=== Un processeur superscalaire charge plusieurs instructions en même temps, ''en tenant compte de la présence de branchements''. La partie en italique est importante et elle a une conséquence très forte : la manière de faire est fortement différente entre un CPU superscalaire étroit et large. Un CPU superscalaire étroit fait au plus simple : il charge un bloc de 2, 3, 4 instructions consécutives en mémoire RAM. Il faut alors doubler/tripler/quadrupler la taille du bus connecté au cache d'instruction. Par exemple, pour charger quatre instructions de 32 bits chacune, il suffit de lire un bloc de 128 bits dans le cache d'instruction. Un bloc de 8, 16, 32 octets est donc lu depuis le cache et est ensuite découpé en instructions, envoyées chacun à un décodeur. Charger plusieurs instructions consécutives ne marche bien qu'en absence de branchements. La présence de branchements casse cette dynamique, et il faut gérer leur présence. * Sans prédiction de branchement, toutes les instructions d'un bloc sont décodées et exécutées en même temps, même s'il y a un branchement dans le tas. * Avec prédiction de branchement, les instructions situées après le branchement ne sont pas décodées ni exécutées. L'unité de chargement coupe le bloc chargé au niveau du premier branchement pris, remplit les vides avec des NOP, avant d'envoyer le tout à l'unité de décodage. Les instructions à l'adresse de destination seront chargées au cycle suivant, en chargeant un nouveau bloc. [[File:Fetch sur un processeur superscalaire avec prediction de branchements.png|centre|vignette|upright=2|Fetch sur un processeur superscalaire avec prédiction de branchements.]] Un autre défaut est que sur le CPU charge un bloc de 128 bits à la fois, les instructions doivent être alignées sur 128 bits. Et on peut généraliser à n'importe quelle taille de bloc. Si les instructions ne sont pas alignées, il faut gérer la situation. Il arrive qu'une instruction soit à cheval sur deux blocs, avec un morceau dans le premier bloc, un autre dans le second. Dans ce cas, l'instruction est chargée en deux fois, et des circuits doivent gérer la situation pour recoller les morceaux. Sur les CPU avec des instructions de taille fixe, le problème ne survient que rarement. Mais il est fréquent avec des instructions de taille variable. Un autre défaut est lié aux branchements. Imaginez qu'un branchement s'exécute et branche vers une instruction de destination. Rien n'indique que l'instruction de destination sera au tout début d'un bloc. Si ça se trouve, elle sera en troisième position d'un bloc de 4 instructions. Une solution simple charge le bloc en respectant l'alignement, et les deux premières instructions du bloc ne sont pas à exécuter. Il y a donc une petite perte de performance. Certains CPU, comme l'IBM RS/6000, ont la machinerie charger un bloc de N instructions même s'il n'est pas aligné. La machinerie en question se trouve dans le cache d'instruction et est tout sauf simple à expliquer,ce qui fait que je me permets de la passer sous silence. Un détail, cependant : elle ne permet pas de charger des blocs qui sont à cheval sur deux lignes de cache. ===Le décodage parallèle des instructions=== Pour le décodage des instructions, plusieurs instructions sont décodées en même temps. On parle alors de '''décodage parallèle'''. Pour ce faire, un processeur superscalaire contient plusieurs décodeurs, chacun pouvant décoder une instruction en parallèle des autres. Prenons par exemple un processeur superscalaire à 4 voies : le décodage se fera dans quatre décodeurs séparés, qui fonctionneront en parallèle. Cependant, cette implémentation ne marche bien que pour les processeurs RISC, les processeurs CISC devant faire avec un microcode gourmand en circuits. Les processeurs CISC utilisent des décodeurs hybrides, avec un microcode qui complémente un décodeur câblé. Dupliquer le microcode aurait un cout en transistors trop important, ce qui fait que seuls les décodeurs câblés sont dupliqués. Les CPU CISC superscalaires disposent donc de plusieurs décodeurs simples, capables de décoder les instructions les plus courantes, avec un seul microcode. La conséquence est qu'il n'est pas possible de décoder deux instructions microcodées en même temps. Par contre, il reste possible de décoder plusieurs instructions non-microcodées ou une instruction microcodée couplée à une instruction non-microcodée. Vu qu'il est rare que deux instructions microcodées se suivent dans un programme, le cout en performance est extrêmement mineur. Il faut noter qu'une instruction correspond à une ou plusieurs µops. Il est donc possible de charger 5 instructions et de se retrouver avec 8 µops en sortie des décodeurs. Et c'est la source d'une distinction très importante. Plus haut, j'ai parlé de processeurs superscalaires à N voies. La terminologie peut avoir deux interprétations. La première est que le processeur charge/décode N instructions à la fois. Une seconde est qu'il est capable d'exécuter/émettre N µops en même temps. Les deux ne sont pas équivalentes, vu qu'un processeur superscalaire peut, par exemple, charger/décoder N instructions et émettre/exécuter N+2 µops. La distinction est particulièrement importante sur les processeurs superscalaires larges. Certains d'entre eux sont capables de charger 5 instructions et d'en émettre le double ! Un exemple classique était les processeurs Athlon d'AMD, qui pouvaient décoder deux instructions par cycle, mais émettre 4 µops par cycle. Ce n'était pas un problème, car beaucoup d'instructions, même basiques, étaient décodées en 2 µops. ===L'émission parallèle des instructions=== La superscalarité modifie en profondeur l'unité d'émission, notamment au niveau de son interface. Avant, elle recevait une micro-opération sur son entrée, et fournissait la micro-opération émise sur une sortie. Et cela vaut aussi bien pour une unité d'émission simple, un ''scoreboard'', une fenêtre d'instruction ou des stations de réservation. Mais avec l'émission multiple, les sorties et entrées sont dupliquées. Pour la double émission, il y a deux entrées vu qu'elle doit recevoir deux micro-opération décodées/renommées, et deux sorties pour émettre deux micro-opérations. Dans ce qui suit, nous ne parlerons pas d'entrée et de sortie, mais de '''ports de décodage''' et de '''ports d'émission'''. Les ports de décodage reçoivent des micro-opérations provenant de l'unité de décodage, les ports d'émissions sont pour les micro-opérations émises. Les ports d'émission injectent les micro-opérations émises dans le pipeline, elle sont propagée au banc de registre, aux ALUs, etc. Un processeur superscalaire a forcément plusieurs ports d'émission (et de décodage). Si l'interface de l'unité d'émission change, l'intérieur est tout autant modifié. Sur un CPU non-superscalaire, l'unité d'émission reçoit une µop "candidate à l'émission", et détecte les dépendances entre avec les µops en vol, en cours d'exécution dans le pipeline. Et c'est toujours le cas sur les processeurs superscalaires : l'unité d'émission détecte les dépendances entre les µops candidates et les µops en vol. Mais avec la siperscalarité, elle doit en plus tester les dépendances entre les µops candidates. Si l'unité d'émission est un ''scoreboard'', il doit détecter les dépendances entre instructions émises simultanément et les sérialiser si besoin. Avec une fenêtre d'instruction, il n'y a pour ainsi dire rien à faire, vu que les dépendances sont éliminées par le renommage de registre et que les signaux de réveil gèrent les dépendances RAW. C'est la raison pour laquelle les processeurs superscalaires modernes n'utilisent pas de ''scoreboard''. ==Les processeurs implémentés avec plusieurs pipelines== Nous venons de voir comment la superscalarité impacte le ''front-end'', il est maintenant de voir ce qu'il en est de l'unité d'émission et des ''back-ends''. Un processeur superscalaire peut utiliser l'exécution dans le désordre et le renommage de registres, mais ce n'est pas une obligation. Historiquement, les premiers processeurs superscalaires n'utilisaient pas d'exécution dans le désordre. Le meilleur exemple est celui du Pentium 1 et 2 d'Intel. Ils étaient tous deux des processeurs superscalaires, mais n'avaient pas d'exécution dans le désordre, qui est apparue sur le Pentium 3. les explications sont plus simples si on met de côté l'exécution dans le désordre. Vous vous dites que l'implémentation d'un processeur superscalaire n'est pas la même suivant que le CPU utilise l'exécution dans le désordre, ou non. Mais dans les faits, la distinction est plus subtile que ca. Pour l'expliquer, je vais utiliser une classification originale, qui sépare les processeurs superscalaires en trois sous-types. La distinction se fait sur la manière dont la superscalarité est implémentée en matériel.La classification que j'utilise distingue : * Les processeurs avec plusieurs pipelines, sans exécution dans le désordre. * Les processeurs superscalaires avec un pipeline dynamique. * Les processeurs avec des fenêtres d'instruction décentralisées. La distinction n'est pas qu'une question d'implémentation matérielle. Elle impacte aussi le ratio entre instructions chargées/décodées et instructions émises. Vous vous dites que les deux sont égales, mais ce n'est le cas que sur les processeurs avec N pipelines ! Sur les autres processeurs, il est possible de décoder 4 instructions, mais d'en émettre maximum 6 par cycle ! Et sur les CPU avec des fenêtres d'instruction décentralisées, il y a aussi une différence entre le nombre d'instructions qui sont ''dispatch'' et ''schedule''. La catégorie la plus simple est celle des '''processeurs avec plusieurs pipelines'''. Ce sont des processeurs superscalaires sans exécution dans le désordre. Ils partent d'un processeur non-superscalaire, et ajoutent un second pipeline, en sortie de l'unité d'émission. Le second pipeline permet l'émission parallèle d'une seconde instruction. Le second pipeline n'est pas identique au pipeline originel, sur plusieurs points. Premièrement, il n'a pas exactement les mêmes circuits. Le second pipeline est généralement composé d'une unité de calcul, d'une FPU, éventuellement d'un second banc de registres, guère plus. Il permet d'exécuter une seconde opération de calcul, pas plus, ce qui est à l'origine de contraintes d'appariemment assez fortes. De plus, il n'a pas forcément la même longueur, il peut avoir des étages en moins ou en plus. Il s'agit de la forme la plus simple de processeur superscalaire et nous allons leur consacrer cette section. ===La double émission entière-flottante=== L'exemple le plus simple utilise un second pipeline pour les opérations flottantes, séparé du pipeline originel. L'ajout d'un pipeline flottant permet d'émettre une micro-opération entière en même temps qu'une micro-opération flottante, ce qui s'appelle la '''double émission entière-flottante'''. La micro-opération entière s'exécute dans l'ALU entière, la micro-opération flottante dans la FPU. Les exemples les plus notables sont le PA-RISC 7100, le POWER 1 (RIOS .& et .9) et le PowerPC 601. L'Apollo DN10000 et l'Intel i860 utilisaient aussi cette forme de double émission, mais étaient des prototypes de CPU VLIW. [[File:Double émission entière-flottante.png|centre|vignette|upright=2|Double émission entière-flottante]] Pour bien expliquer les choses, prenons le processeur illustré ci-dessous, avec deux ''back-end'' séparés : un pour la FPU, et un autre pour le reste. Les instructions entières et mémoire passent toutes par le ''back-end'' généraliste. [[File:CPU non-superscalaire basique, avec une FPU.png|centre|vignette|upright=1.5|CPU non-superscalaire basique, avec une FPU]] Les deux ''back-end'' sont des pipelines séparés, mais il faut que l'unité d'émission soit spécialement conçue pour en profiter. Un processeur simple émission peut démarrer une seule µop par cycle : soit la µop est envoyée au ''back-end'' flottant, soit elle est envoyée au ''back-end'' général. Avec la double émission entière-flottante, le processeur peut envoyer une µop dans chaque pipeline, à chaque cycle. Vu que la FPU et son banc de registre forment déjà un pipeline séparé, les seules modifications se font au niveau du ''front-end'', unité d'émission inclue. {| |- |[[File:CPU simple émission avec une FPU.png|centre|vignette|upright=1.5|CPU simple émission avec une FPU.]] |[[File:Double émission entière flottante avec un second pipeline.png|centre|vignette|upright=1.5|CPU avec un second pipeline flottant.]] |} Notez que le terme double émission ''entière''-flottante est un peu trompeur. En réalité, la technique permet d'émettre une instruction flottante avec n'importe quelle autre instruction non-flottante, avec des restrictions pour les branchements. Il est par exemple possible d'exécuter un calcul flottant en parallèle d'un accès mémoire. Sur les CPU 32 bits, il est possible de faire les multiplications dans la FPU. Pour rappel, la FPU contient un multiplieur entier 53-54 bits, pour multiplier les mantisses de deux flottants 64 bits, qu'on peut utiliser pour faire les multiplications entières 32 bits. Cela permet d'émettre une multiplication en même temps qu'une opération entière dans l'ALU. Ainsi, le CPU pouvait émettre deux instructions : une opération entière de base, avec soit une multiplication, soit une opération flottante. En clair, une émission de type INT + (MUL /FLOAT). Par contre, on perd la possibilité d'émettre une multiplication entière avec une opération flottante, mais c'était un compromis qui en valait la peine. Le CPU PA-RISC 7100 implémentait cette optimisation. Il s'agissait d'un CPU double émission entière-flottante, qui utilisait donc un partitionnement assez simple. Le combo partitionnement + reconditionnement permettait ici de gagner en performance, pour un cout en matériel très limité. D’autant plus limité que le jeu d'instruction prévoyait le reconditionnement de la FPU. Les multiplications ne lisaient pas leurs opérandes dans les registres entiers, mais dans les registres flottants. ===La double émission entière=== Une autre possibilité ajoute un second pipeline, capable d'exécuter des opérations entières simples, appelé le pipeline entier. Le processeur supporte alors la '''double émission entière''', à savoir qu'ils peuvent émettre une opération entière en plus d'une autre instruction. L'opération entière en question est une opération simple, comme une addition, une soustraction, une opération bit à bit, une comparaison, etc. Les exemples classiques sont les processeurs Intel i960 et Pentium. L'implémentation était différente entre l'i960 et le Pentium : Le Pentium avait une seconde ALU entière, alors que le i960 rusait. Le Pentium dupliquait l'ALU entière, pour créer un second pipeline entier. Sur le Pentium originel, les contraintes d'appariement étaient assez strictes. Le Pentium avait une FPU, mais ne pouvait pas l'utiliser en double émission. Il lui était impossible d'exécuter une opération flottante en même temps qu'une opération entière (branchements inclus). Le Cyrix 6x86 était une sorte de Pentium produit par l'entreprise Cyrix, avec une microarchitecture différente. Il avait des contraintes d’appariement réduites. Par exemple, il pouvait émettre une opération flottante en parallèle d'une opération entière. Nous détaillerons ces processeurs dans le prochain chapitre, qui porte justement sur les CPU superscalaires x86. [[File:CPU simple et double émission avec un second pipeline entier.png|centre|vignette|upright=2.5|CPU simple et double émission avec un second pipeline entier.]] L'Intel i960 n'avait pas de FPU, juste une ALU entière et une unité mémoire. Il utilisait l'unité mémoire comme une seconde ALU entière limitée. Pour rappel, une unité mémoire contient une AGU, à savoir une ALU spécialisée dans les calculs d'adresse. Elle est capable de faire des additions, soustractions, et quelques décalages limités (des décalages par 2, 4, 8, pas plus). L'Intel i960 ajoutait des connexions entre AGU et banc de registre, afin que celle-ci serve de seconde unité de calcul. L'AGU est déjà reliée aux deux ports de lecture, pour lire les opérandes du calcul d'adresse, il suffit de la connecter sur le port d'écriture. Le processeur peut alors émettre deux instructions/µops entières, sous certaines conditions. [[File:Emission multiple des opérations entières, double émission.png|centre|vignette|upright=2|Émission multiple des opérations entières, double émission.]] ==Les processeurs superscalaires dynamiques== Les processeurs précédents, avec 2 pipelines, avaient une unité d'émission assez simple. Elle avait deux ports de décodage, et deux ports d'émission. Le nombre de ports de décodage et d'émission étaient identiques. Vous vous dites que c'est naturel, et intuitif, que c'est normal pour un processeur superscalaire. Mais en réalité, la majorité des processeurs superscalaires a plus de ports d'émission que de décodage. Expliquer pourquoi va cependant nous demander de parler de comment la superscalarité exploite les unités de calcul. Posons-nous cette question : de combien d'unités un processeur superscalaire a besoin ? Les unités en question ne sont nécessairement des unité de calcul, l'unité mémoire et l'unité de branchement comptent aussi. La réponse dépend du nombre de voies. Si le ''front-end'' charge et décode un paquet de N instructions, exécuter ces N instructions simultanément demandera là aussi au moins N unités de calcul. Notez le terme : ''au moins'', car c'est là tout l'enjeu de cette section. La réponse dépend aussi des contraintes d’appariement. Pour un CPU superscalaire "parfait", sans contraintes d’appariement, toutes les unités doivent être dupliquées, y compris l'unité mémoire. Un processeur à double émission parfaite devrait dupliquer l'ALU entière, le ''barrel shifter'', le circuit multiplieur, la FPU, l'unité mémoire, et l'unité de branchement, ce qui ferait passer de 6 unités à 12. Le cout en circuit est énorme, même pour de la double émission ! Et pour un CPU à N pipelines, le cout en circuit encore plus prohibitif ! En pratique, les processeurs superscalaires qui font ça sont très rares. La majeure partie des processeurs superscalaires utilisent d'autres techniques : le partitionnement, la duplication partielle des ALUs, la duplication des autres unités. ===Le partitionnement des unités fonctionnelles=== Les CPU superscalaires les plus simples ne dupliquent pas leurs unités de calcul, ils se contentent d'exploiter les unités existantes. Et pour expliquer comment, nous allons partir de l'exemple d'un processeur non-superscalaire contient une ALU entière, une FPU, une unité mémoire et une unité de branchement. Les quatre unités peuvent fonctionner en parallèle, à savoir qu'une unité peut exécuter une instruction à elle toute seule, sans interagir avec les autres. Cela suffit pour créer un processeur quadruple émission, qui peut émettre en même temps : une opération entière, une opération flottante, un branchement, un accès mémoire. Le principe marche aussi sur un processeur avec moins de 4 unités, par exemple sans FPU et/ou sans unité de branchement, ou avec au contraire des unités en plus. L'idée générale est d’émettre une µops séparée dans chaque unité fonctionnelle. Utiliser ainsi les unités existantes, pour rendre un CPU superscalaire, s'appelle le '''partitionnement superscalaire'''. Le terme n'est pas beaucoup utilisé en-dehors de quelques vieux articles académiques. Le partitionnement est fortement dépendant du jeu d'instruction, car il demande de bien séparer instructions entières, flottantes, branchements et instructions mémoire. Et autant c'est bien le cas sur les CPU RISC, autant les CPU CISC ne sont pas dans ce cas. Les CPU CISC ont des instructions ''load-op'', qui effectuent une instruction arithmétique et une µop mémoire. Et ces instructions sont très compliquées à implémenter avec le partitionnement. Et au-delà de ça, les CPU CISC ont beaucoup d'instructions qui sont décodées en plusieurs µops, à exécuter dans un ordre bien précis. On peut pas émettre les µops en question simultanément. Le partitionnement a un cout en circuit très limité, pour un gain en performance lui aussi limité. Pour reprendre le CPU de l'exemple précédent, il est rare que l'on ait à exécuter ces quatre instructions en même temps. En pratique, les combinaisons fréquentes sont les combinaisons calcul + accès mémoire, calcul + branchement, calcul entier + calcul flottant. Utiliser une quadruple émission de ce type n'est donc pas une bonne idée, alors que la double émission parait plus adaptée. Et c'est là que la différence entre ports d'émission et de décodage devient intéressante. L'idée est d'avoir une unité d'émission avec deux ports de décodage, et 4 port d'émission (un par unité). Les contraintes d’appariement varient alors suivant le processeur, mais la règle générale est que si deux micro-opérations tombent dans deux unités différentes, on peut les émettre en même temps. En clair, on peut émettre une opération entière avec une opération flottante, une opération entière avec un branchement, un accès mémoire avec un branchement, etc. Et cela rend le partitionnement bien plus intéressant que prévu. Les tout premiers CPU superscalaires étaient des CPU double émission, qui utilisaient le partitionnement avec : une ALU entière, une unité mémoire et une FPU. Nous parlerons de '''partitionnement INT-FLOAT-MEM'''. L'avantage sur la double émission flottante est que les combinaisons INT + MEM et FLOAT + MEM étaient autorisées. L'intérêt était d'émettre une opération en parallèle d'un accès mémoire, ce qui est un doublet d'instruction assez fréquent. Les exemples classiques sont l'HyperSPARC, de l'alpha 21064 et du PowerPC 603. Les contraintes d’appariement variaient grandement suivant le processeur. Le PowerPC 603 implémentait une double émission "parfaite". C'est à dire que toutes les combinaisons possibles à deux instructions étaient possibles, que ce soit INT + FLOAT, INT + MEM, FLOAT + MEM. En plus de cette double émission "parfaite", le CPU utilisait la technique dite de ''branch folding'', qui permettait d'exécuter un branchement sans utiliser d'unité de calcul, ce qui fait qu'il était qualifié de ''CPU 2-way + Branch''. A l'opposé, le processeur alpha 21064 avait pas mal de contraintes d’appariement, à savoir que certaines combinaisons de deux instructions étaient interdites, bien que compatibles avec les unités disponibles. Une des raisons est que le banc de registre n'avait pas assez de ports de lectures, ce qui fait que certaines combinaisons avec une écriture mémoire n'étaient pas permises. Les concepteurs avaient volontairement inséré ces contraintes afin de simplifier le design du processeur. Avant de poursuivre, précisons que la FPU regroupe un additionneur flottant et un multiplieur flottant. Et ce fait peut être exploité pour approfondir le partitionnement. La '''double émission FADD/FMUL''' permet d'émettre une addition flottante en même temps qu'une multiplication flottante. Elle a été utilisée sur les processeurs x86, Alpha et SPARC. Les autres circuits de calcul flottant sont souvent regroupés avec l'additionneur. La division est parfois e même port que le multiplieur flottant, parfois sur celui de l'additionneur (quand elle est microcodée et exécutée en enchainant les soustractions). ===La duplication des ALU entières=== Le partitionnement est une technique élégante, simple, facile à implémenter et au cout en circuits modeste. Mais il faut avouer que cela ne donne pas des CPU superscalaires très efficaces. La raison est que le partitionnement permet d'émettre des paires, triplets, quadruplets de micro-opérations qui sont rares en pratique. Les combinaisons les plus fréquentes en pratique sont des paires/triplets d'opérations entières, souvent des paires/triplets d'additions ou d'opérations entières simples. Il est donc primordial d'émettre plusieurs opérations entières simultanément. Le fait d'émettre et d'exécuter plusieurs opérations entières en même temps s'appelle l''''émission entière multiple'''. Pour exécuter la seconde/troisième opération entière, il faut dupliquer les ALU entières (ou réutiliser l'AGU comme ALU entière comme le fait l'Intel i960). Le processeur doit avoir deux ALU entières pour de la double émission entière, trois pour de triple émission entière, etc. De plus, il faut ajouter des ports au banc de registre, pour lire deux fois plus d'opérandes et décrire deux résultats. Et enfin, il faut rajouter un port d'émission pour pouvoir émettre une micro-opération entière en plus. [[File:Processeur non-superscalaire basique 03.png|centre|vignette|upright=2|Processeur non-superscalaire basique]] La tendance est de multiplier les ALU entières, mais pas le circuit multiplieur. Le cout en transistors est alors grandement réduit, au prix de l'apparition de dépendances structurelles. Le CPU peut émettre deux additions en même temps, ou une addition et une multiplication, mais pas deux multiplications. Il en est de même avec le ''barrel shifter'', qui n'est pas dupliqué. Il est donc possible d'émettre un décalage avec une opération de base, mais pas deux décalages en parallèle. Mais ces contraintes d’appariement ne sont pas un problème, car il est rare de devoir émettre deux multiplications/décalages en même temps. La plupart des programmes sont majoritairement remplis d'additions, avec des multiplications assez rares et des décalages qui le sont encore plus. La moyenne est proche d'une multiplication pour 4/5 additions. Vu que des multiplications consécutives sont très rares, dupliquer le multiplieur est presque inutile. Disposer de plusieurs circuits multiplieurs ou de plusieurs ''barrel shifters'' serait donc un cout en circuits qui ne servirait que rarement et n'en vaut pas la chandelle. Il faut noter que sur les CPU 32 bits, les multiplications peuvent être exécutées dans la FPU ou dans un multiplieur séparé, tout dépend de si le CPU exécute les multiplications dans la FPU ou pas. {|class="wikitable" |- ! Voie d'émission n°1 !! Voie d'émission n°2 !! Voie d'émission n°3 |- | ALU entière || ALU entière || FPU |} Reste à connecter les unités de calcul à l'unité d'émission (indirectement, car il y a des étages de pipeline entre les deux). Une implémentation basique utilisee un port d'émission par unité. Prenons l'exemple d'un CPU superscalaire avec 4 ALU entières, une FPU, et une unité mémoire. Vu qu'il y a 6 unités, cela fait donc 6 ports d'émission. L'exemple historique le plus impressionnant est celui du Motorola 88110, un CPU double émission qui possède 10 unités. Il contient deux ALU entières, un circuit multiplieur, un circuit diviseur, une unité de manipulation de bits, une unité mémoire, un additionneur flottant, et deux unités graphiques pour des calculs impliquant des pixels. Le multiplieur est utilisé à la fois pour les multiplications entières, flottantes et le produit de deux pixels. Idem pour le diviseur, qui sert pour les divisions entières et flottantes. Et chaque unité a son propre port d'émission. La conséquence est que les contraintes d'appariement du Motorola 88110 sont simples : tant que deux µops atterrissent dans des unités séparées, la paire est permise. Il est par exemple possible d'émettre simultanément deux opérations entières simples, vu qu'il y a deux ALU. Par contre, impossible d'émettre simultanément deux multiplications, deux divisions, deux opérations flottantes, deux manipulations de bit, deux accès mémoire, etc. De plus, le partage du multiplieur fait qu'on ne peut pas émettre une multiplication entière avec une multiplication flottante. Idem avec les divisions, vu que le diviseur est aussi partagé. Ce sont les seules contraintes. ===Le partage des ports d'émission=== La présence d'un circuit multiplieur et d'un ''barrel shifter'' pose des problèmes pour le partitionnement. Il est possible d'avoir un port d'émission dédié au multiplieur, et un autre au ''barrel shifter'', qui sont séparés de ceux pour l'ALU entière. Il est alors possible d'émettre une opération entière, une multiplication et un décalage, simultanément. Nous parlerons de '''partitionnement entier''' pour désigner cette possibilité. [[File:Partitionnement superscalaire entier.png|centre|vignette|upright=2|Partitionnement superscalaire entier.]] Le partitionnement entier est cependant très rare, car le gain en performance serait trop faible au regard des couts en circuit. Quelques processeurs historiques l'utilisaient, mais ce n'est plus le cas de nos jours. La raison est que les multiplications et les décalages sont assez rares dans le code, ce qui fait que le port d'émission associés sont sous-utilisés. Et un port d'émission a un cout en circuit assez important. Et c'est sans compter qu'ajouter des ports sur le banc de registres juste pour un multiplier sous-utilisé n'en vaut pas la peine. L'idéal serait de trouver un moyen de réduire le nombre de ports d'émissions, sans réduire le nombre d'unités de calcul. [[File:Emission multiple des opérations entières, implémentation naive.png|vignette|Émission multiple des opérations entières, implémentation naive]] La solution consiste à '''partager les ports d'émission''', à savoir connecter un port d'émission à plusieurs unités de calcul. L'exemple classique est justement lié aux circuits multiplieur et ''barrel shifters''. L'idée est que le multiplieur et le ''barrel shifter'' sont mis sur le même port d'émission qu'une ALU entière. Prenons par exemple le processeur à double émission entière illustré ci-contre. Un port d'émission est relié à une ALU entière seule, et l'autre port d'émission alimente la seconde ALU entière et le multiplieur. La conséquence est que le processeur peut émettre deux opérations entières, dont une multiplication. Si on avait utilisé un port d'émission dédié au multiplieur, il aurait été possible d'émettre deux opérations entières et une multiplication. On perd donc une voie, mais on gagne en flexibilité et on économise des circuits. Notons que ce partage signifie aussi que le multiplieur partage les ports sur le banc de registre, ce qui fait une double économie. [[File:Emission multiple des opérations entières, implémentation courante.png|centre|vignette|upright=1.5|Émission multiple des opérations entières, implémentation courante]] En général, le ''barrel shifter'' et le multiplieur sont placés sur deux voies d'émission séparés, ce qui permet d'émettre un décalage et une multiplication en même temps. Les mettre sur le même port ne le permettrait pas. {|class="wikitable" |- ! Voie d'émission n°1 !! Voie d'émission n°2 !! Voie d'émission n°3 |- | * ALU entière * Multiplieur | * ALU entière * Barrel shifter | FPU |- | * Additions, soustractions, opérations bit à bit, etc. * Multiplication | * Additions, soustractions, opérations bit à bit, etc. * Décalages et rotations | Opération flottante |} Mais le partage des ports d'émission peut appliquer à n'importe quelle unité. Prenons l'exemple d'un CPU superscalaire avec 4 ALU entières, une FPU, et une unité mémoire. Vu qu'il y a 6 unités, cela fait donc 6 ports d'émission. Cependant, il est rare qu'un bloc de 6 instructions contienne exactement 4 opérations entières, une opération flottante, et un accès mémoire. Beaucoup de ports d'émission seraient sous-utilisés. La solution est de partager des ports d'émission, pour passer à la quadruple émission. Le CPU peut alors répartir 4 instructions sur les ports d'émission adéquats, à chaque cycle. Le CPU a donc 4 ports de décodage et 6 ports d'émission. Une implémentation classique permet n'importe quelle combinaison de 4 instructions compatible avec les 6 unités. Par exemple, il devient possible d'émettre simultanément 4 additions, la combinaison 2 * INT + FLOAT + MEM, 3 * INT + MEM, et bien d'autres. L'exemple relie une ALU entière, la FPU et l'unité mémoire sur le même port. Le CPU a alors quatre ports d'émission, qui ne sont pas "identiques". Trois d'entre eux sont reliés à une ALU entière chacun, rien d'autre. Le quatrième est lui relié à une ALU entière, la FPU et l'unité mémoire. On économise alors deux ports d'émission, mais cela entraine l'apparition de contraintes d’appariement. Le CPU peut émettre 3 opérations entières + n'importe quelle autre opération. Mais les autres combinaisons sont interdites. Le CPU peut émettre 6 opérations entières simultanément, grâce à 6 ALU entières. Les accès mémoire ou opérations flottantes ne viennent pas en plus, mais en remplacement des opérations entières. Regrouper plusieurs ALU sur un même port d'émission est donc à l'origine de dépendances structurelles, de nouvelles contraintes d’appariement. Dans certaines situations de '''''port contention''''', on a assez d'ALU pour exécuter N micro-opérations, mais la répartition des ALUs sur les ports l'empêche. Impossible d'émettre deux micro-opérations sur deux ALU si elles sont sur le même port d'émission. On peut réduire les cas de ''port contention'' en répartissant correctement les ALU sur les ports d'émission, mais sans jamais les supprimer complétement. Un exemple est celui du PowerPC 604. Il implémentait la quadruple émission, alors qu'il avait 5 unités de calcul. Mais deux unités partageaient un même port d'émission. Dans le détail, il avait deux ALU entières, un circuit multiplieur/diviseur, une FPU, une unité mémoire. La FPU et le multiplieur étaient sur le même port d'émission. En conséquence, le CPU pouvait émettre seulement les combinaisons INT + INT + MEM + (MUL/FLOAT). : Il est intéressant de voir l'exemple d'un processeur superscalaire parfait, sans contraintes d'appariement, à N voies. Pour cela, on part d'un processeur non-superscalaire, avec M unités de calcul. Un tel processeur peut s'implémenter avec N ports d'émission, chacun relié à M unités de calcul possibles, ce qui fait que les M unités de calcul sont dupliquées en N exemplaires. Si on prend par exemple un CPU non-superscalaire avec une ALU, une FPU, une unité mémoire et une unité de branchement, un port d'émission sera relié à une ALU, une FPU, une unité mémoire et une unité de branchement. ===La duplication des autres unités fonctionnelles=== Dupliquer les ALU entière est une première étape, mais il est aussi possible de dupliquer les autres unités fonctionnelles. Par exemple, il est possible de dupliquer l'unité mémoire ou l'unité de branchement. Reste à voir quelles unités dupliquer, et pourquoi. Précisons que dans ce qui va suivre, nous allons parler de la duplication des unités en général, pas seulement des unités mémoire/branchement. Nous allons continuer de parler de la duplication des ALU entières dans ce qui suit, mais de manière plus générale. Pour un processeur à N voies, il est théoriquement possible de dupliquer toutes les unités en N exemplaires. Mais ce n'est jamais fait en pratique, car le cout en circuits serait trop important et le gain en performance trop faible. Mieux vaut dupliquer certaines unités bien précises, quitte à avoir des contraintes d'appariement. Et à ce petit jeu, toutes les duplications ne se valent pas, certaines sont plus rentables en matière de performance, le cout en circuit n'est pas le même selon l'unité, etc. La rentabilité en performance dépend surtout des contraintes d'appariement. Les contraintes d'appariement entrainent une baisse de performance significatives si elles interdisent des paires d'instructions fréquentes. Aussi, les opérations très fréquentes, souvent appairées, gagnent à dupliquer l'unité associée. Et au contraire, les unités rarement utilisées ne doivent pas être dupliquées. Il se trouve que les unités les plus utilisées sont les ALU entières, suivies par les unités mémoire et l'unité de branchement. Le cout en circuit n'est aussi pas le même. Dupliquer les ALU entière est trivial, car elles ont un budget en transistors assez faible. Les circuits multiplieurs, diviseurs, et les ''barrel shifter'' ont eux un cout en transistor plus élevés. Si on devait faire un classement, on aurait : ALU entière < ''barrel shifter'' < multiplieur. Pour les unités mémoire, le cout en transistor est là aussi conséquent. Le résultat final est que dupliquer les ALU entières est privilégié sur le reste, comme l'a vu dans la section précédente. Les unités mémoire arrivent en seconde place, suivie par les multiplieurs/''barrel shifter'' à la troisième place, et les unités de branchement à la quatrième place. Les CPU superscalaires larges ont donc tendance à multiplier les unités mémoire en second. Les multiplieurs et ''barrel shifters'' sont eux aussi dupliqués sur les CPU superscalaires larges, mais cela vient souvent après avoir dupliqué les unités mémoire. La raison est que les accès mémoire sont critiques pour la performance des CPU à exécution dans le désordre, pas les multiplications. Pour les unités de branchement, une subtilité complique le fait d'en installer plusieurs. Que faire quand deux branchements sont pris ? La solution la plus simple est de ne pas se trouver dans cette situation : on n'utilise qu'une seule unité de branchement et on n’exécute qu'un seul branchement par cycle. La majorité des processeurs superscalaires étroits font ainsi. Les processeurs superscalaires larges ont cependant deux unités de branchements, mais ils n'acceptent qu'un seul branchement pris à la fois. Si les deux branchements pris sont exécutés en même temps, le processeur n’exécute que le plus récent, dans l'ordre du programme. ===Les CPU superscalaires mélangent les trois techniques=== Les CPU superscalaires utilisent toutes les techniques à la fois : partitionnement, duplication de l'ALU entière, duplication des autre unités fonctionnelles. Chaque processeur utilise ces trois stratégies à sa sauce, en fonction de son budget en transistor. Les quatre techniques ont cependant un cout en circuit différent, et donnent des gains de performances eux aussi forts différents. * Le '''partitionnement superscalaire''' a un cout en circuit modéré, car on n'a pas besoin de dupliquer d'ALU. * La '''duplication des ALU entières''' est la technique la plus importante : elle a un cout en circuit modéré, et donne des gains en performance impressionnants. * La '''duplication des autres unités''' a un cout en circuit variable, pour des gains en performance eux aussi variables. * Le '''partage des ports d'émission''' réduit le cout en circuit du CPU, pour des pertes de performances mineures si bien fait. L'usage du partitionnement seul, sans duplication, était surtout le fait des processeurs commercialisés entre les années 1989 et 1993. Le partage des ports d'émission a lui aussi mis un peu de temps pour s'imposer. Globalement, les processeurs superscalaires ont évolués progressivement, pour se rapprocher d'une sorte de standard. Mais pour comprendre ce qu'est ce standard, et en quoi les CPU historiques s'en sont progressivement rapprochés, il faut étudier l'histoire des CPU superscalaires. L'histoire des CPU superscalaires a commencé dès 1960, mais est resté un projet de recherche pendant plusieurs décennies. De nombreux designs ont été conçus, mais abandonnés avant la production, comme l'ACS-1 d'IBM. Les années 1980 ont vu l'arrivée de prototypes de processeurs VLIW, mais qui n'étaient pas superscalaires. Par exemple, l'Apollo DN10000 et l'Intel i860 pouvaient émettre deux instructions à la fois, sous conditions, mais demandaient que le compilateur fasse une bonne partie du travail. Les premiers CPU superscalaires commercialisés sont sortis en 1989. Deux CPUs superscalaires sont sortis cette année là : le RS/6000 d'IBM et l'i960CA d'Intel. Puis, la compétition s'est aussi lancée dans la bataille. Les CPU commercialisés avant l'année 1993 étaient à double émission, à l'exception du SuperSPARC qui utilisait la triple émission. Les modèles après 1993 hésitaient entre double et quadruple émission. Dans le tableau ci-dessous, les processeurs quadruple émission sont en bleu, les triple émission sont en rouge, les autres ne sont pas en couleur. {|class="wikitable" |- ! !! IBM !! HP PA-RISC !! SPARC !! Intel!! Alpha !! Autres |- ! 1989 | RS/6000 || || || i960CA || || |- ! 1990 | || || || || || |- ! 1991 | || || || || || Motorola 88110 |- ! 1992 | RSC || PA 7100 || class="f_rouge | SuperSPARC || || 21064 || |- ! 1993 | PPC 601 || || HyperSPARC || Pentium || || |- ! 1994 | class="f_bleu | POWER 2 || PA 7200 || || || class="f_bleu | 21164 || |- ! 1995 | 603/604 || class="f_bleu | PA 8000 || class="f_bleu | UltraSPARC 1 || Microarchitecture P6 (+ concurrence AMD) || || class="f_bleu | MIPS R1000 |- ! 1996 | class="f_bleu | 620 || || || || class="f_bleu | 21264 || |- ! ... || ... || ... || ... || ... || ... || ... |} Les premiers CPU à double émission sont assez représentatifs des CPU à double émission modernes. Il faut dire que se limiter à deux voies ne permet pas de faire beaucoup de choses. Voici le tableau chronologique disant quel CPU utilisait quelle technique. * Les CPU en vert utilisaient la double émission entière-flottante. * Les CPU en rouge utilisaient la double émission entière "pure". * Les CPU en jaune utilisaient un ''partitionnement complet'' entre ALU, FPU et unité mémoire. * Les CPU sans couleur utilisaient un grand nombre d'unités, avec beaucoup de ports d'émission. * Les CPU barrés utilisent la triple ou quadruple émission et seront vu dans la section suivante. {|class="wikitable" |- ! !! IBM !! HP PA-RISC !! SPARC !! Intel!! Alpha !! Autres |- ! 1989 | class="f_vert | RS/6000 || || || class="f_rouge | i960CA || || |- ! 1990 | || || || || || |- ! 1991 | || || || || || Motorola 88110 |- ! 1992 | class="f_vert | RSC || class="f_vert | PA 7100 || <s>SuperSPARC</s> || || class="f_jaune | 21064 || |- ! 1993 | class="f_vert | PPC 601 || || class="f_jaune | HyperSPARC || class="f_rouge | Pentium || || |- ! 1994 | <s>POWER 2</s> || PA 7200 || || || <s>21164</s> || |} C'est en 1994, que la quadruple émission est arrivée et que les CPU ont essayé divers implémentations. Il y avait alors trois stratégies pour la double/triple/quadruple émission, qui partageaient des problèmes similaires. La première implémentation dupliquait les ALUs et les FPUs, ce qui permettait d'émettre deux opérations flottantes avec deux opérations entières. Le POWER 2 dupliquait ses FPUs à la perfection, alors que le 21264 séparait l'additionneur et le multiplieur flottant. Les accès mémoire étaient traités comme des opérations entières et le processeur pouvait souvent en émettre deux à la fois. Mais une telle organisation n'est plus utilisée de nos jours, car, sans instructions flottantes à exécuter, elle est équivalente à de la double émission. La seconde implémentation utilisait deux ALU entières (dont un multiplieur), une unité mémoire et une FPU.Cette organisation est intéressante, mais encore trop stricte pour donner de bons résultats, les quadruplets avec exactement deux opérations entières, un accès mémoire et une opération flottante étant trop rares. L'usage de ces 4 unités en double ou triple émission a été utilisé sur le PA 7200 et le SuperSPARC. Malheureusement, en plus de problèmes au niveau de son cache, le SuperSPARC avait de nombreuses contraintes d’appariement, car le banc de registre n'avait pas assez de ports pour alimenter certaines combinaisons d'instructions. De plus, s'il émettait une multiplication, il ne pouvait pas émettre de seconde µop. Enfin, une troisième implémentation utilisait un grand nombre d'unités fort différentes, bien plus que quatre, avec au minimum deux ALU entières. Prenons l'exemple de l'UltraSPARC, qui avait : deux ALU entières, une unité mémoire, une unité de branchement, un additionneur flottant, un multiplieur flottant, un diviseur flottant (aussi utilisé pour le calcul des racines carrées), et 2 unités graphiques pour les calculs sur des pixels. En tout, cela fait 9 unités différentes, pour de la quadruple émission. Dans le même genre, les processeurs MIPS R8000 et R10000 avaient une implémentation plus moderne, dans le sens où toutes les unités étaient dupliquées. Le CPU avait deux ALU, deux FPU et deux unités mémoire. Il pouvait donc émettre quatre instructions à la fois, tant que le paquet collait avec ces six unités. Moitié opérations entières, moitié opérations mémoire, avec de quoi remplacer les opérations entières par des opérations flottantes. Le PA 8000, lui, dupliquait toutes ses unités en deux exemplaires, ce qui n'est pas l'idéal, aucun processeur moderne ne fait cela aujourd'hui. Le tout est résumé dans le tableau ci-dessous, avec : * en vert les CPU utilisant la première stratégie ; * en rouge les CPU utilisant la seconde stratégie ; * en jaune les CPU utilisant la troisième stratégie. {|class="wikitable" |+ Processeurs quadruple émission historiques |- ! Année !! Processeur !! Unités |- ! 1991 | class="f_jaune | Motorola 88110 || class="f_jaune | 2 ALU + MUL + DIV + BIT + MEM + FADD + 2 unités graphiques |- |- ! rowspan="2" | 1994 | class="f_vert | POWER 2 || class="f_vert | 2 ALU/MEM + 2 FPU (+ 2 Branchements via ''branch folding'') |- | class="f_rouge | 21164 || class="f_rouge | 2 ALU + FPU + MEM |- ! rowspan="3" | 1995 | PA 8000 || 2 ALU + 2 ''barrel shifters'' + 2 MEM + 2 FPU (FMA) + 2 FPU (DIV et SQRT) + 2 SIMD |- | class="f_jaune | UltraSPARC 1 || class="f_jaune | 2 ALU + MEM + BRANCH + FADD + FMULFDIV + 2 unités graphiques |- | MIPS R8000 et R10000 || 2 ALU + 2 FPU + 2 MEM |- ! rowspan="2" | 1996 | class="f_rouge | 620 || class="f_rouge | 2 ALU + FPU + MEM |- | class="f_vert | 21264 || class="f_vert | 2 ALU/MEM + 2 FPU (FADD + FMUL) |} Malheureusement, ces CPUs historiques n'avaient ni assez d'unités de calcul entières, ni d'unités mémoire, pour remplir la quadruple émission. Environ la moitié des instructions d'un programme correspond à des instructions entières. Pour en profiter, un processeur à N voies doit disposer d'au minimum N/2 ALU entières, l'idéal étant d'aller au-delà. Les CPU superscalaires historiques avaient 2 ALU pour de la quadruple émission, ce qui était très limite. En comparaison, les CPU superscalaires modernes mettent le paquet niveau ALU entière. La plupart des CPU x86 modernes descendent rarement sous les 2/3, la plupart remplit presque la totalité de la largeur du CPU. Pour donner un exemple, la microarchitecture Golden Cove d'Intel est un processeur à 6 voies, avec 5 ALU entières. Pour être précis, un programme informatique exécute environ 40% de son code dans des instructions arithmétiques, un autre 40% dans les accès mémoire, et un le reste pour... le reste. La conséquence est que les processeurs modernes dupliquent leurs unités mémoire. Par exemple, la microarchitecture ''Golden Cove'' d'Intel dispose de trois unités mémoire : deux spécialisées dans les écritures, trois dans les lectures. La microarchitecture ''Gracemont'' utilise la pentuple émission et peut émettre 4 µops entières et 4 µops mémoire. Notez que le ratio est équilibré entre opérations entières et mémoire, avec soit égalité, soit un avantage pour les ALU entières. Une autre régularité est que la largeur totale du processeur peut être occupée par des opérations entières/mémoire. Pour le dire autrement, sur un processeur quadruple émission, il pourra émettre un mix de 4 instructions entière/mémoire, avec zéro opération flottante. Idem avec des processeurs triples ou pentuple émission, voire plus. Si le processeur émet une opération flottante, elle remplace une opération entière. Prenons l'exemple des processeurs AMD K6, sortis en 1997, juste avant les AMD Athlon. Ils avaient 10 unités de calcul, 6 ports d'émission, 4 ports de décodage. Voici exactement comment étaient répartie les unités de calcul sur les ports d'émission. C'était des processeurs quadruple émission, ce qui fait que la moitié de la largeur du processeur sert pour des opérations entières, et l'autre moitié pour les accès mémoire. {|class="wikitable" |- ! Port d'émission n°1 !! Port d'émission n°2 !! Port d'émission n°3 !! Port d'émission n°4 !! Port d'émission n°5 !! Port d'émission n°6 ! |- | ALU entière || ALU entière || FPU || Unité de branchement || Unité LOAD || Unité STORE |- | || Multiplieur || || || || Unité de calcul d'adresse (instruction LEA) |- | || ''Barrel shifter'' || || || || Unité pour les instructions PUSH |} Les microarchitectures K7, K8 et K10, utilisées sur les anciens CPU AMD Athlon, sont un exemple moins représentatif. Ces microarchitectures utilisent le partage des ports d'émission au maximum. C'était des microarchitectures à triple émission, avec six ports d’émission. Il y avait trois ports d'émission pour les opérations flottantes, et trois ports d'émission pour les micro-opérations entières/mémoire. Il y avait trois ALU entières et trois unités mémoire, chaque ALU partageait un port d'émission avec une unité mémoire. Elle pouvait donc émettre soit trois opérations entières, soit trois µops mémoire, soit un mix des deux. En plus des mix avec les opérations flottantes et branchements. {|class="wikitable" |- ! Port d'émission n°1 !! Port d'émission n°2 !! Port d'émission n°3 !! Port d'émission n°4 !! Port d'émission n°5 !! Port d'émission n°6 ! |- | ALU entière || ALU entière || ALU entière || FADD || FMUL || FPU (autres opérations) |- | Multiplieur || || || || || |- | LOAD/STORE || LOAD/STORE || LOAD/STORE || || || |} Les CPU superscalaires larges dupliquent les multiplieurs et/ou les ''barrel shifters'', et même les unités de branchement. La raison est que quand la largeur du CPU augmente, le processeur traite des paquets d'instructions de plus en plus gros. Par exemple, pour un CPU à 10 voies, cela veut dire émettre 10 instructions en même temps. Il est alors plus fréquent d'avoir deux multiplications, deux décalages, deux accès mémoire, deux branchements. Par exemple, la moyenne est d'une multiplication tous les 5 instructions. Les CPU superscalaires à 10 voies ont donc tendance à avoir deux multiplications par paquet d'instruction, en moyenne. Avoir deux multiplieurs est alors un gain en performance clair. Par exemple, la microarchitecture ''Golden Cove'' a deux ''barrel shifters''. C'est une architecture à 6 voies. {|class="wikitable" |- ! Port d'émission n°1 !! Port d'émission n°2 !! Port d'émission n°3 !! Port d'émission n°4 !! Port d'émission n°5 !! Port d'émission n°6 !! Port d'émission n°7 !! Port d'émission n°8 !! Port d'émission n°9 !! Port d'émission n°10 |- | ALU entière || ALU entière || ALU entière || ALU entière || ALU entière || LOAD || LOAD || LOAD || STORE || STORE |- | ''Barrel shifter'' || Multiplieur || Multiplieur || ''Barrel shifter'' || || || || || || |- | Branchements || Diviseur || || Branchements || || || || || || |} ==Les µops mémoire sur les processeurs superscalaires== Plus haut, nous avons volontairement passé sous silence les accès mémoire. La raison à cela est que leur support sur les CPU superscalaire peut se faire de plusieurs manières différentes, qu'il vaut mieux expliquer dans une section dédiée. Les différentes implémentations sont décrites dans la liste suivante, nous allons les voir une par une. * Avec la première méthode, les accès mémoire sont exécutés par le pipeline pour les opérations entières. * Avec la seconde méthode, les accès mémoire ont un pipeline dédié ; c'est l'''émission parallèle des accès mémoire''. * Avec la troisième méthode, il est possible d'émettre plusieurs accès mémoire simultanément : c'est l'''émission multiple des accès mémoire''. Voyons ces trois cas dans le détail. Nous verrons notamment les différentes formes d'émission multiple des accès mémoire. ===Les accès mémoire traités dans le pipeline entier=== Dans le cas le plus simple, les accès mémoire sont traités comme des opérations entières un peu spéciales. L'unité mémoire est sur le même port d'émission que l'ALU entière, ce qui fait que les accès mémoire remplacent des opérations entières. Faire ainsi permet d'utiliser une ALU entière pour les calculs d'adresse, pas besoin de rajouter une unité de calcul d'adresse séparée. Typiquement, un processeur de ce type place l'unité mémoire après l'unité de calcul. Les opérations entières n'utilisent que l'ALU entière, mais pas l'unité mémoire. Les accès mémoire exécutent un calcul d'adresse dans l'ALU entière, puis l'adresse calculée est envoyée à l'unité mémoire. Le tout est illustré ci-dessous, dans le cas d'un processeur historique à double émission entière-flottante. [[File:Processeur non-superscalaire basique 01.png|centre|vignette|upright=1.5|Processeur non-superscalaire basique]] Un exemple de CPU double émission de ce type est celui du Power PC 440, un CPU destiné à l'embarqué. Vu qu'il était destiné à l'embarqué, il n'avait pas de FPU, il n'avait pas besoin de manipuler de nombres flottants. Il n'a donc que des ALU entières, un multiplieur et une unité mémoire. Il dispose de deux ports d'émission. Le premier est connecté à une ALU entière et un circuit multiplieur, le second est relié à l'unité mémoire et une seconde ALU entière. Une organisation simple, mais très efficace ! [[File:PowerPC 440.png|centre|vignette|upright=2|Microarchitecture du PowerPC 440.]] L'implémentation économise une unité de calcul d'adresse, mais n'a pas de cout en performance significatif. Les accès mémoire étant très lents, émettre une µop entière en parallèle d'un accès mémoire ne sert pas à grand-chose. On peut émettre cette µop entière un cycle plus tard, elle sera terminée avant même que l'accès mémoire soit terminé, même si l'accès mémoire se fait dans le cache L1 ou L2. Pour comprendre pourquoi, faisons quelques calculs. Un accès mémoire prend deux-trois cycles quand il tombe dans le cache L1. Et on doit attendre qu'il soit terminé pour en émettre un nouveau. Combien d'instructions arithmétiques peut-on lancer pendant ce temps, sur un CPU triple émission ? Au premier cycle, on peut en émettre 2 (l'instruction mémoire prend une place, donc 3-1), puis 3 au second cycle. Ce qui fait 5 instructions arithmétiques au total. Or, il se trouve qu'il y a une instruction d'accès mémoire pour 5 instructions arithmétiques, environ. Par contre, ce compromis change quand on passe à la quadruple émission, voire au-delà. Dans ce cas, il devient intéressant de pouvoir émettre une nouvelle instruction mémoire à chaque cycle. Ce qui nous amène à la section suivante... ===L'émission parallèle des accès mémoire=== A partir de la quadruple émission, la tendance a été de séparer l'unité mémoire des ALU entières. Cela permet d'émettre des µops mémoire en même temps que des µops entières et/ou flottantes., ce qui porte le nom d''''émission parallèle des µops mémoire''', ou encore ''émission mémoire parallèle''. Par exemple, un processeur quadruple émission peut émettre en même temps deux opérations entières, une opération flottante et un accès mémoire. L'accès mémoire vient en plus des opérations entières, il n'est pas possible de remplacer l'accès mémoire par une opération entière. La technique ne permet pas de faire les calculs d'adresse dans l'ALU entière. Il faut donc utiliser une unité de calcul d'adresse séparée. Elle est intégrée dans l'unité mémoire, qui fait l'interface entre le cache et le banc de registres. Le cout en circuits est très faible, car les unités de calcul d'adresse n'utilisent guère plus qu'un additionneur et un circuit de décalage très simple. L'émission mémoire parallèle est plus facile à implémenter sur les CPU RISC, qui ont des instructions mémoire séparées du reste. Les CPU CISC ne sont pas dans ce cas, ce qui fait que séparer l'unité mémoire du pipeline entier est compliqué. Les CPU superscalaires historiques étaient pour moité des CPU RISC et pour moitié des CPU CISC, ce qui fait qu'ils n'utilisaient pas ou peu l'émission mémoire parallèle. Les premiers processeurs superscalaires se contenaient de l'émission parallèle mémoire, ils ne pouvaient émettre qu'un seul accès mémoire à la fois, l'unité mémoire n'était pas dupliquée. Et ce n'est pas tant un problème que ça, du moins sur les processeurs à double ou triple émission. En effet, il est rare que l'unité mémoire doive faire deux accès simultanés. Notons que le processeur peut exécuter une instruction mémoire en parallèle d'un calcul ou d'un branchement, seuls les accès mémoire simultanés sont impossibles. ===L'émission multiple des accès mémoire avec une LSQ=== Les processeurs superscalaires larges travaillent sur des paquets de 8 à 10 instructions, il est plus fréquent d'avoir plusieurs accès mémoire par paquet. Dans ces conditions, il est utile d'avoir une '''émission multiple des accès mémoire''', à savoir d'émettre plusieurs micro-opérations mémoire en même temps. Les processeurs superscalaires modernes sont capables d'émettre plusieurs lectures/écritures simultanément. Par exemple, ils peuvent émettre une lecture en même temps qu'une écriture, ou plusieurs lectures, ou plusieurs écritures. Il faut noter que selon le processeur, il peut y avoir des restrictions quant aux accès mémoire émis en même temps. Par exemple, certains processeurs peuvent émettre une lecture avec une écriture en même temps, mais pas deux lectures ni deux écritures. Ou encore, ils peuvent émettre deux lectures, une lecture et une écriture, mais pas deux écritures en même temps. Dans la majorité des cas, les processeurs ne permettent pas d'émettre deux écritures en même temps, alors qu'ils supportent plusieurs lectures. Il faut dire que les lectures sont plus fréquentes que les écritures. Les processeurs qui autorisent toutes les combinaisons de lecture/écriture possibles, sont rares. L'émission multiple des accès mémoire s'implémente dans l'unité mémoire. Pour rappel, l'unité mémoire s'interpose entre le cache L1 et le reste du pipeline. Dans le cas le plus simple, elle incorpore une ''load/store queue'' et des unités de calcul d'adresse. L'émission multiple des accès mémoire demande de rendre la ''Load-store queue'' multi-ports, afin de gérer plusieurs micro-opérations mémoire simultanées. Il faut aussi dupliquer les unités de calcul d'adresse reliées à la LSQ, chaque port d'émission mémoire devant avoir sa propre unité de calcul d'adresse. : Dans ce qui suit, nous parlerons d'AGU (''Adress Generation Unit'') pour désigner les unités de calcul d'adresse, et de LSQ pour designer la ''Load-store queue''. [[File:Processeur non-superscalaire basique 02.png|vignette|upright=1|Processeur non-superscalaire basique]] Dupliquer les ports de la LSQ et les AGU est nécessaire. Mais qu'en est-il du cache ? La réponse varie suivant le processeur. Il y a deux grandes méthodes pour implémenter l'émission multiple des accès mémoire. La première méthode ajoute des ports de lecture/écriture au cache de données, pour faire des lectures/écritures simultanées. Il faut alors dupliquer des unités de calcul, rajouter les ports sur le cache, mais aussi rajouter des ports sur la ''load/store queue'', pour qu'elle échange des données/commandes mémoire avec le cache. Le cout en circuits est alors conséquent La seconde duplique les unités de calcul, mais pas les ports d'accès au cache. Les accès mémoire sont alors accumulés dans la ''load/store queue'', qui sont ensuite traités un par un par le cache. Un exemple est celui des processeurs AMD de micro-architecture K7 K8, à savoir les anciens AMD Athlon et AMD Athlon 64, qui pouvaient calculer trois adresses simultanément, mais ne pouvaient faire qu'un seul accès au cache par cycle. Le cout en circuit est modéré : les unités de calcul coutent très peu, les ports de la ''load/store queue'' coutent déjà un peu plus. Quelques processeurs utilisaient des solutions intermédiaires. Un processeur utilisant cette technique est, par exemple, capable d'émettre quatre µops mémoire en même temps, mais avec seulement deux ports sur le cache. Un exemple classique étant les Pentium 1 et 2 d'Intel, dont nous parlerons dans le chapitre suivant. ===L'émission séparée des LOAD et STORE=== De rares processeurs ont des ports d'émission qui peuvent faire indifféremment lecture comme écritures. Mais les processeurs modernes préfèrent séparer ports d'émission pour les lectures et les écritures. Un exemple est celui des processeurs Intel de micro-architecture Nehalem, qui avaient un port de lecture et un port d'écriture tous deux reliés à la ''Load-store queue'', chacun avec leur propre unité de calcul d'adresse. Comme autre exemple, les processeurs skylake ont une LSQ avec deux ports de lecture et un port d'écriture, ce qui permet d'émettre deux lectures en même temps qu'une écriture. Dans cette section, nous allons étudier le cas avec des ports séparés pour les lectures et écritures. En général, les processeurs ont un peu plus de ports de lectures que d'écritures. Une raison est qu'un programme a, en moyenne, un tout petit peu plus de lectures que d'écritures. Mais la différence n'est pas énorme, c'est un léger déséquilibre. Une seconde raison est que l'exécution dans le désordre marche bien mieux quand les lectures ont lieu le plus tôt possible. Donc mieux vaut privilégier les lectures à l'émission, pour qu'elles se fassent un peu en avance. En pratique, la différence n'est pas très marquée. Il est courant d'avoir un port de lecture et un d'écriture, les CPU superscalaires très larges ont souvent deux ports de lecture et deux d'écriture. A la rigueur, quelques CPU ont trois ports de lecture et deux d'écriture. Le cas le plus simple est celui où on peut émettre une lecture et une écriture en même temps. Il s'implémente facilement si la LSQ est en réalité une simple ''Store queue'', à savoir qu'elle met en avance les écritures uniquement. Pour rappel, les écritures sont mises en attente dans la ''Store queue'', mais les lectures sont exécutées immédiatement. Une fois l'adresse à lire connue, la lecture consulte la ''Store queue'' pour détecter les dépendances de données, puis s'exécute. La donnée à lire est lue soit depuis la ''Store queue'', soit depuis le cache. [[File:Emission LOAD - Store séparée.png|vignette|upright=1|Émission LOAD - Store séparée]] L'implémentation de l'émission multiple est alors très simple. La ''Store queue'' a déjà deux ports, avec un pour les lectures et un autre pour les écritures. Idem pour le cache, qui a naturellement un port de lecture et un d'écriture. Émettre une lecture et une écriture simultanément ne demande donc pas de modification digne de ce nom. La ''Store queue'' doit juste supporter une lecture et une écriture simultanée. Ajouter l'émission d'une seconde lecture n'est pas compliquée : il faut juste rajouter un second port de lecture sur le cache et la ''Store queue''. Pour rappel, l'unité mémoire est précédée par une structure qui met en attente les micro-opérations mémoire avant leur émission finale. La structure en question varie suivant l'implémentation, mais un cas particulier se marie bien avec l'émission multiple des µops mémoire. Il s'agit du cas avec une file de µops séparée pour les lectures et une autre pour les écritures, appelées respectivement la ''file de µops LOAD'' et la ''file de µops STORE''. Il est alors possible d'émettre une lecture et une écriture lors du même cycle d'horloge. Le port de lecture alimente une unité de calcul d'adresse dédiée, directement reliée au cache. Le port d'écriture du cache alimente une unité de calcul, qui est suivie par une file d'écriture, elle-même reliée au cache. Les processeurs AMD de micro-architecture K6 utilisaient cette technique. [[File:Double émission avec le Load Ordering, Store Ordering.jpg|centre|vignette|upright=2.5|Double émission avec le Load Ordering, Store Ordering]] ==Les CPU superscalaire à exécution dans le désordre== La superscalarité modifie aussi les circuits dédiés à l'exécution dans le désordre. Par contre, elle modifie les unités de renommage de registre, le ROB et les autres structures liées à l'exécution dans le désordre, au sens large. Dans cette section, nous allons voir comment les étages du pipeline dédiés à l'exécution dans le désordre sont modifiées sur un CPU superscalaire. ===La superscalarité avec une file/fenêtre d'instruction centralisée=== Plus haut, nous avons montré qu'un processeur pouvait avoir plus de ports d'émission que de ports de décodage. On dit aussi que sa ''largeur de décodage'' était supérieure à sa ''largeur d'émission''. La '''largeur de décodage''' est le nombre de µops fournies par l'unité de décodage par cycle d'horloge, la '''largeur d'émission''' est le nombre maximal de µops émises par cycle d'horloge. Et n'allez pas croire qu'un surplus de ports d'émission est du gâchis. Sur les processeurs avec exécution dans le désordre, ce surplus peut être utilisé. Mais avant toute chose, évacuons un détail. Vous avez sans doute pensé que le surplus peut être exploité par les instructions machines décodées en plusieurs µops . Par exemple, si une instruction est décodée en deux µops, on pourrait avoir un port d'émission en plus pour en profiter. Mais les ports de décodage sont *en sortie du décodeur*, et contiennent des µops, pas des instructions. Le surplus de ports d'émission peut occasionnellement utiliser le surplus de ports de décodage, mais cela implique que des µops ont été mises en attente dans l'unité d'émission, pour être émises par la suite. Et cela implique l'usage d'une fenêtre d'instruction ou d'une file de µops. Le principe est que les µops sont accumulées dans la fenêtre d'instruction, en attendant leurs opérandes ou la résolution d'une dépendance. Et si l'occasion se présente, une µops est émise sur chaque port d'émission. Il y a donc un gain en performances, mais il faut faire quelques précisions sur son origine. La largeur d'émission utilisée ne peut pas dépasser, *en moyenne*, la largeur de décodage. Et c'est intuitif : un CPU qui décode 4 µops par cycle ne peut pas émettre 6 par cycle en moyenne, sur le temps long, il n'y a pas de µops en surplus qui sortent du vide. Par exemple, sur un processeur quadruple émission, si on analyse combien d'instructions sont émises par cycle, il peut y avoir des cycles où la largeur d'émission est totalement utilisée, mais ce sera compensé par des périodes où elle est fortement sous-utilisée. Les µops sont accumulées en cas de sous-utilisation, puis consommées en cas d'utilisation complète. Les deux périodes se compensent. Et c'est là que l'on peut comprendre d'où vient le gain en performance. Les cycles où les ports d'émission sont sous-utilisés existent systématiquement, que le processeur utilise l'exécution dans le désordre ou pas. Ils sont liés à la présence de dépendances de données, aux contraintes d’appariement, etc. Elles tendent à réduire la moyenne du nombre de µops émises par cycle. Par contre, ce sont les cycles de rattrapage, où les ports en surplus sont utilisés, qui font remonter la moyenne. Le gain en performance est donc lié à ces cycles de rattrapage, où les µops accumulées sont émises dans les ports en surplus. Et ils n'existent que sur les processeurs avec une file/fenêtre d'instruction. La différence entre largeur de décodage et d'émission a un impact assez fort sur le banc de registre, et précisément sur son nombre de ports de lecture. Le banc de registre a besoin de plus de ports de lecture avec une fenêtre d’instruction qu'avec des stations de réservation. Rappelons la terminologie utilisée dans ce cours. Les fenêtres d'instruction se contentent de mémoriser la micro-opération et quelques bits pour détecter la disponibilité des opérandes. Par contre, les stations de réservations mémorisent aussi les opérandes des instructions. Et cette différence a une influence le nombre de ports de lecture du banc de registre. Supposons que toutes les instructions sont dyadiques, à deux opérandes. Comptons le nombre de ports nécessaires pour alimenter les unités de calcul, sans utiliser d'arbitre de banc de registre. Avec une fenêtre d'instruction, il faut le double du nombre de ports d'émission. Mais avec des stations de réservation, il faut le double des ports de décodage. La raison est que les registres sont lus après émission avec une fenêtre d'instruction, avant avec des stations de réservation (sous-entendu, après le décodage). Or, comme dit plus haut, les processeurs superscalaires larges ont souvent plus de ports d'émission que de décodage. ===La superscalarité à fenêtres d'instruction décentralisées=== Les processeurs précédents utilisaient une unité d'émission simple, qui pouvait utiliser l'émission dans l'ordre ou dans le désordre. Elle peut être remplacée par une fenêtre d'instruction centralisée dans que cela quoi que ce soit aux explications précédentes, idem avec une station de réservation centralisée. En soi, l'exécution dans le désordre est donc totalement compatible avec une implémentation simple de la superscalarité. Par contre, des subtilités surviennent avec les fenêtres d'instruction décentralisées. Pour rappel, sur ces processeurs, les µops sont émises en deux phases. Une première phase de ''dispatch'' qui envoie les µops vers la fenêtre d'instruction adéquate, sans détecter les dépendances de données. La seconde phase de ''schedule'' gère les dépendances de données, elle met en attente les µops en attendant que l'unité soit disponible pour les exécuter, en attendant aussi que les opérandes soient disponibles. Et à ce petit jeu, il y a une différence entre le nombre de µops qui sont ''dispatch par cycle'' et celles qui sont ''schedule''. Dit autrement, la largeur d'émission est scindée en une '''largeur de ''dispatch''''' et une '''largeur de ''schedule''. Par exemple, prenons un processeur triple émission avec deux stations de réservation : une pour les µops entières/mémoire, et une autre pour les µops flottantes. L'unité d'émission peut ''dispatch'' maximum trois µops par cycle : soit trois µops entières/mémoire, soit trois flottantes, soit un mix de trois µops différentes. Les stations de réservation, quant à elles, peuvent chacune ''schedule'' 3 µops par cycle, vers trois ALUs entières pour la station de réservation entière/mémoire, vers trois FPS pour la station de réservation flottante. De quoi ''dispatch'' trois µops, mais d'en ''schedule'' six, à chaque cycle. Et cet exemple ne sort pas de nulle part : les CPU x86 d'Intel et AMD sont globalement dans ce cas, même si les chiffres varient d'une microarchitecture à l'autre. L'exemple précédent colle parfaitement à la microarchitecture K7 d'AMD, utilisée sur les processeurs Athlon dans les années 2000. [[File:AMD K7.png|centre|vignette|upright=2.5|AMD K7, microarchitecture.]] ===L'unité de renommage superscalaire=== Avec la superscalarité, l'unité de renommage de registre doit gérer le cas où des instructions consécutives ont des dépendances de registre. Par exemple, prenons un processeur à double émission, qui renomme deux instructions consécutives. Si elles ont une dépendance de registre, le renommage de la seconde instruction dépend du renommage de la première. La première doit être renommée et le résultat du renommage est utilisé pour renommer la seconde, ce qui empêche d'utiliser deux unités de renommage séparées. Pour cela, elle renomme les registres sans tenir compte des dépendances, pour ensuite corriger le résultat. [[File:Unité de renommage superscalaire.png|centre|vignette|upright=2|Unité de renommage superscalaire.]] Seules les dépendances lecture-après-écriture doivent être détectées, les autres étant supprimées par le renommage de registres. Repérer ce genre de dépendances se fait assez simplement : il suffit de regarder si un registre de destination d'une instruction est un opérande d'une instruction suivante. [[File:Détection des dépendances sur un processeur superscalaire.png|centre|vignette|upright=2|Détection des dépendances sur un processeur superscalaire.]] Ensuite, il faut corriger le résultat du renommage en fonction des dépendances. Si une instruction n'a pas de dépendance avec une autre, on la laisse telle quelle. Dans le cas contraire, un registre opérande sera identique avec le registre de destination d'une instruction précédente. Dans ce cas, le registre opérande n'est pas le bon après renommage : on doit le remplacer par le registre de destination de l'instruction avec laquelle il y a dépendance. Cela se fait simplement en utilisant un multiplexeur dont les entrées sont reliées à l'unité de détection des dépendances. On doit faire ce replacement pour chaque registre opérande. [[File:Correction des dépendances sur un processeur superscalaire.png|centre|vignette|upright=2|Correction des dépendances sur un processeur superscalaire.]] ===Le tampon de réordonnancement et le banc de registre d'un CPU superscalaire=== Le ROB doit aussi être modifié pour émettre et terminer plusieurs instructions en même temps, en ajoutant des ports de lecture/écriture. Pour un CPU superscalaire peut émettre 8 µops à la fois, on se dit que le ROB peut retirer 8 µops par cycle maximum. Et c'est très souvent le cas en pratique. Cependant, certains processeurs ont un ROB qui peut retirer le double ! Un exemple est celui des CPU de micro-architecture Skymont, qui peuvent émettre 8 µops par cycle, mais en retirer 16. Quelques CPU de marque AMD étaient aussi dans ce cas. L'avantage est que cela permet de réduire la taille du ROB, du banc de registre et de quelques autres structures matérielles liées à l'exécution dans le désordre. Le gain sur-compense le cout en transistor nécessaire pour retirer 16 µops par cycle. Réduire le nombre de ports d'émission permet de garder un banc de registre, des fenêtres d'instruction et un ROB simples, rapides et économes en énergie. Et mine de rien, c'est là un compromis important : à quoi bon augmenter le nombre de ports d'émission si c'est pour que ce soit compensé par une fréquence plus faible ? Par contre, réduire le nombre de ports augmente la ''port contention'', surtout si on a mal réparti les ALU. Il faut donc trouver un compromis entre réduire le nombre de ports d'émission d'un côté, réduire la ''port contention'' de l'autre. ==Les optimisations des processeurs superscalaires larges== Les processeurs superscalaires larges ont des optimisations spécifiques, qui n'ont pas de sens sur les processeurs superscalaires étroits. Dans les grandes lignes, elles se classent en deux types. Les premières permettent de charger/décoder plus de 5-10 instructions simultanément. Le problème est alors que les branchements viennent mettre leur grain de sel et qu'il faut en tenir compte. Les secondes corrigent des problèmes qui apparaissent quand un CPU intègre plus de 5 à 10 unités de calcul. ===L'étape de chargement d'un CPU superscalaire large=== Charger plusieurs instructions pose de nombreux problèmes sur les processeurs superscalaires larges, qui émettent de 5 à 10 instructions simultanées. Pour rappel, la stratégie basique pour charger N instructions est de charger un bloc de N instructions consécutives. Les instructions après un branchement ne sont pas décodées ni exécutées, ce qui entraine une petite baisse de performance. Pour un N petit, la perte de performance est mineure. Mais pour un N large, elle devient suffisamment importante pour que la conception du CPU doit alors être repensée de fond en comble. Les analyses statistiques disent qu'il y a environ un branchement tous les 4 à 6 instructions. Un CPU a donc tendance à décoder des paquets de 3 à 5 instructions consécutives, avant qu'un branchement casse la dynamique. Or, cela pose des problèmes pour utiliser plusieurs décodeurs. L'implémentation basique, qui charge N instructions consécutives pour alimenter N décodeurs, fonctionne donc jusqu'à 4/6 décodeurs. Au-delà de 5 instructions, un CPU superscalaire doit être adapté pour gérer les branchements dans les paquets d'instruction qu'il émet. Le nombre de 5 instructions ne sort donc pas de nulle part. La première solution utilise un cache de µops, avec un ''front-end'' capable de charger/décoder 4/5 instructions à la fois, pas plus. L'idée est que la majorité des instructions proviennent du cache de µops (plus de 80%), ce qui fait qu'on peut émettre environ 10 instructions à partir du cache de µops, pas besoin d'avoir beaucoup de décodeurs. Les performances sont cependant dégradées lors d'un défaut de cache de µops. Les instructions doivent alors être chargées depuis le cache d'instruction, par paquets de 4-5, puis sont décodées par 4-5 décodeurs, ce qui ne permet pas d’alimenter les unités de calcul à plein régime. La solution précédente joue sur la distinction entre pots de décodage et d'émission. Elle permet d'émettre un grand nombre de µops sans pour autant charger un grand nombre d'instructions en même temps. En quelque sorte, ce n'est qu'une méthode de contournement, le CPU superscalaire reste de type étroit. Mais il est aussi possible d'utiliser un '''''front-end large''''', capable de charger une bonne dizaine d'instructions en même temps. La seule difficulté est de tenir compte de la présence de branchements. Pour rappel, avec un ''front-end'' étroit, les instructions situées après le premier branchement pris sont annulées, elles ne sont pas décodées ni exécutées. [[File:Fetch sur un processeur superscalaire avec prediction de branchements.png|centre|vignette|upright=2|Fetch sur un processeur superscalaire avec prédiction de branchements.]] Un ''front-end'' large charge les instructions de destination du branchement et les placent à sa suite. Ils chargent deux blocs à la fois et les fusionnent en un seul qui ne contient que les instructions présumées utiles. [[File:Cache d'instructions autoaligné.png|centre|vignette|upright=2|Cache d'instructions auto-aligné.]] Mais cela demande de charger deux blocs de mémoire en une fois, ce qui demande un cache d'instruction multiports. Il faut aussi ajouter un circuit pour assembler plusieurs morceaux de blocs en un seul : le fusionneur (''merger''). En pratique, cette solution demande d'ajouter un second port de lecture au cache, pour un cout en hardware important, avec un impact négatif sur le temps d'accès au cache. Et il faut rajouter le fusionneur, qui prend un étage de pipeline à lui tout seul. Et encore, cette solution ne gère pas le cas où un bloc contient plusieurs branchements ! [[File:Implémentation d'un cache d'instructions autoaligné.png|centre|vignette|upright=2|Implémentation d'un cache d'instructions auto-aligné.]] Les exemples précédents partent du principe qu'il n'y a qu'un seul branchement dans un bloc. Mais quand les blocs deviennent assez longs, il est fréquent d'avoir plusieurs branchements dans un bloc. Et il est même possible d'avoir plusieurs branchements pris. Dans un bloc de 8-10 instructions, ce n'est pas rare, et les CPU superscalaires à 8-10 voies sont la règle dans les CPU des années 2020. Pour gérer ce cas, il faut ajouter des ports supplémentaires au cache d'instruction et adapter le fusionneur, pour qu'il puisse fusionner trois ou quatre blocs. De plus, il faut utiliser une unité de prédiction de branchement capable de ''prédire plusieurs branchements par cycle''. ===Le décodage superscalaire large=== Le décodage des instructions n'est pas censé dépendre de l'implémentation du chargement. En théorie, une fois plusieurs blocs d'instruction fusionnés, on peut décoder les instructions en parallèle. Cependant, certains processeurs effectuent le décodage en tenant compte de la manière dont les branchements découpent les blocs. Les processeurs Atom d'Intel utilisent une solution de ce type, qui couple prédiction de branchement et décodage parallèle. Ils chargent/décodent des paquets de 3 instructions consécutives, chaque paquet étant séparé du précédent par un branchement. Pour cela, en plus d'avoir un fusionneur adapté, ils regroupent les décodeurs en ''clusters'', chacun regroupant 3 décodeurs, et pouvant décoder trois instructions consécutives. Les micro-architectures Tremont et Crestmont avaient deux ''clusters'' (6 décodeurs au total), la micro-architecture Skymont en intègre trois (9 décodeurs au total). Avec deux ''clusters'', le fonctionnement est le suivant. Si le code est composé d'une suite d'instruction sans branchements, seul le premier ''cluster'' est utilisé, limitant le décodage à trois instructions par cycle. Par contre, si l'une des trois instructions décodée est un branchement, alors le second ''cluster'' décode les trois instructions situées après ce branchement, à partir de l'adresse de destination. Avec un troisième ''cluster'', c'est la même chose, sauf qu'il faut qu'il y ait un branchement dans les trois instructions décodées par le second ''cluster''. Mais comment faire pour savoir quels décodeurs activés, quels ''clusters'' sont actifs ? C'est la prédiction de branchement qui commande. Avec deux ''clusters'', elle détermine si un branchement est présent dans les trois instructions à charger, et quelle est son adresse de destination. Sans branchement, elle prévient les décodeurs et les trois instructions chargées sont décodées dans un ''cluster'' libre. Si elle prédit un branchement, elle charge les instructions adéquates, et active les deux ''clusters''. Avec trois branchements, c'est le même mécanisme, sauf que l'unité de prédiction de branchement doit être capable de prédire deux branchements par cycle. Il faut noter que les décodeurs d'un ''cluster'' sont des décodeurs simples, sans microcode. Sur les processeurs Tremont et Crestmont, le microcode était unique et était partagé entre tous les ''clusters''. Un seul ''cluster'' pouvait décoder une instruction complexe avec le microcode, l'autre devait se limiter à des instructions simples uniquement. Depuis Skymont, le microcode est partiellement dupliqué, avec un microcode par ''cluster''. Le cout en hardware peut paraitre prohibitif, pour un gain en performance faible. Sauf que c'est oublier que ce sont des processeurs Atom, sur lesquels beaucoup d'instructions sont microcodées. Les processeurs Atom ont un budget en transistor limité, ce qui fait qu'il est préférable pour eux d'implémenter des instructions en microcode que d'utiliser des unités de calcul dédiées ou des circuits de décodage complexes. Pour limiter la casse, le microcode n'est que partiellement dupliqué. Seul le microcode pour les instructions fréquente est dupliqué. Il y a un microcode secondaire pour certaines instructions, qui est lui unique et partagé par tous les ''clusters''. ===Le contournement sur les processeurs superscalaires larges=== Pour rappel, la technique du contournement (''register bypass'') permet au résultat d'une instruction d'être immédiatement utilisable en sortie de l'ALU, avant même d'être enregistré dans les registres. Implémenter la technique du contournement demande d'utiliser des multiplexeurs pour relier la sortie de l'unité de calcul sur son entrée si besoin. Il faut aussi des comparateurs pour détecter des dépendances de données. [[File:Pipeline Bypass.png|centre|vignette|upright=1|Pipeline Bypass]] La sortie de chaque ALU doit être reliée aux entrées de toutes les autres, avec les comparateurs qui vont avec. Sur les processeurs ayant plusieurs d'unités de calculs, cela demande beaucoup d’interconnexions et de comparateurs. Pour N unités de calcul, cela demande 2 * N² interconnexions, implémentées avec 2N multiplexeurs de N entrées chacun. Si c'est faisable pour 2 ou 3 ALUs, la solution est impraticable au-delà. Non seulement ce serait un enfer à câbler, mais les fils seraient tellement longs que la fréquence du processeur en prendrait un coup. Notez que cela vaut pour les processeurs superscalaires, mais aussi pour tout processeur avec beaucoup d'unités de calcul. En théorie, un CPU à exécution dans le désordre, non-superscalaire, mais avec beaucoup d'unités de calcul, fait face au même problème. En pratique, avoir une dizaine d'ALU implique processeur superscalaire à exécution dans le désordre. D'où le fait qu'on parle du problème maintenant. La seule solution praticable est de ne pas relier toutes les unités de calcul ensemble. À la place, on préfère regrouper les unités de calcul dans différents '''agglomérats''' ('''cluster'''). Le contournement est alors possible entre les unités d'un même agglomérat, mais pas entre agglomérats différents. Évidemment, certaines possibilités de contournement sont perdues, mais c'est compensé par un gain en fréquence et un cout en circuit/fils réduit. C'est un bon compromis, ce qui explique que presque tous les processeurs modernes l'utilisent. Les rares exceptions sont les processeurs POWER 4 et POWER 5, qui ont préféré se passer de contournement pour garder un processeur très simple et une fréquence élevée. ===Le banc de registre des processeurs superscalaires larges=== Pour émettre deux opérations entières simultanément, il faut lire deux fois plus d'opérandes. Et il faut aussi écrire deux résultats dans les registres, en même temps. Pour cela, on double les ports du banc de registre. En général, il faut au maximum 2 ports de lecture et un port d'écriture par unité de calcul. Le problème, c'est que plus un banc de registres a de ports, plus il utilise de circuits, est compliqué à concevoir, consomme de courant et chauffe. Avec plusieurs dizaines d'unités de calcul différentes, ajouter autant de ports impacterait la fréquence du banc de registres et réduirait les performances, voire est totalement impraticable à cause des contraintes thermiques. Les processeurs superscalaires larges doivent donc trouver des solutions. La première solution utilise un banc de registre unique, mais n'utilise pas autant de ports que le pire des cas le demanderait. Le processeur détecte quand il n'y a pas assez de ports pour servir toutes les micro-opérations. Quand ça arrive, l'unité d'émission émet assez de micro-opérations pour exploiter les ports disponibles, mais met en attente les micro-opérations en trop, le temps que les ports se libèrent. Pour cela, le banc de registre est géré par un circuit d'arbitrage spécialisé, intégré à l'unité d'émission, l’'''arbitre du banc de registres''' (''register file arbiter''). Une autre solution est de dupliquer le banc de registres en plusieurs exemplaires qui contiennent exactement les mêmes données, mais avec moins de ports de lecture/écriture. Un exemple est celui des processeurs POWER, Alpha 21264 et Alpha 21464. Sur ces processeurs, le banc de registre est dupliqué en plusieurs exemplaires, qui contiennent exactement les mêmes données. Les écritures dans les registres sont envoyées à tous les bancs de registres, afin de garantir que leur contenu est identique. Chaque exemplaire a exactement deux ports de lecture. La conception du processeur est simplifiée, que ce soit au niveau du câblage, que de la conception des bancs de registres. Une dernière solution découpe le banc de registres en plusieurs bancs de registres plus petits, avec un réseau d'interconnexions pour échanger des données entre bancs de registres. Dans la plupart des cas, cette séparation est invisible du point de vue de l'assembleur et du langage machine. Le processeur se charge de transférer les données entre bancs de registres suivant les besoins. Sur d'autres processeurs, les transferts de données se font via une instruction spéciale, souvent appelée ''COPY''. [[File:Banc de registres distribué.png|centre|vignette|upright=2|Banc de registres distribué.]] Sur de certains processeurs, un branchement est exécuté par une unité de calcul spécialisée. Or les registres à lire pour déterminer l'adresse de destination du branchement ne sont pas forcément dans le même agglomérat que cette unité de calcul. Pour éviter cela, certains processeurs disposent d'une unité de calcul des branchements dans chaque agglomérat. Dans les cas où plusieurs unités veulent modifier le ''program counter'' en même temps, un système de contrôle général décide quelle unité a la priorité sur les autres. Mais d'autres processeurs fonctionnent autrement : seul un agglomérat possède une unité de branchement, qui peut recevoir des résultats de tests de toutes les autres unités de calcul, quel que soit l’agglomérat. ==La macro-fusion : une optimisation du décodage parallèle== Les processeurs superscalaires supportent la technique dite de '''macro-fusion''', qui permet de fusionner deux-trois instructions consécutives en une seule micro-opération. Par exemple, il est possible fusionner une instruction de test et une instruction de saut en une seule micro-opération de branchement. Il s'agit là de l'utilisation la plus importante de la macro-fusion sur les processeurs x86 modernes. La macro-fusion est utilisée pour fusionner un test et un saut en un branchement unique. L'optimisation est présente sur la plupart des CPU superscalaires modernes. Les CPU x86 l'implémentent, mais aussi quelques CPU ARM et les processeurs Apple de série M (M1 à M5, et potentiellement plus quand ils seront sortis). La fusion des instructions se fait lors du décodage des instructions, grâce à la coopération des décodeurs. Elle marche systématiquement si l'instruction de test et le saut sont consécutives, qu'elles forment bien une paire d'instruction. Par contre, insérer une instruction entre les deux empêche la macro-fusion de faire son travail, sauf si l'instruction insérée est un NOP. Les processeurs ARM64 avaient une forme spécifique de macro-fusion, utilisée pour un cas très précis : charger une constante immédiate de 32 ou 64 bits, dans un registre 64 bits. Les CPU ARM 64 bits utilisent des instructions codées sur 32 bits, qui ne peuvent encoder que des constantes immédiates de 16 bits. Pour charger une constante de 32/64 bits, il faut s'y prendre en plusieurs fois. Une première instruction MOVZ charge les 16 premiers bits, puis 2 à 4 instructions MOVK remplissent le reste. MOVZ met à zéro les 48 bits de poids fort, MOVK permet de sélectionner le doublet à modifier dans le registre (on modifie 16 bits, dont on choisit la place, le reste du registre est laissé tel quel). ARM permet de fusionner cette suite d'instructions en une seule µop MOV avec une constante immédiate. On parle alors de '''''MOVZ + MOVK fusion'''''. Une dernière utilisation, [https://www2.eecs.berkeley.edu/Pubs/TechRpts/2016/EECS-2016-130.pdf proposées pour le jeu d'instruction RISC-V], fusionnait un calcul d'adresse et une instruction LOAD/STORE en une seule instruction d'accès mémoire. Elle a été envisagée à une époque où le jeu d'instruction RISC-V ne gérait pas les modes d'adressage base + indice. L'idée était d'avoir une unité mémoire qui gérait le mode d'adressage base + indice et de fusionner des instructions pour l'exploiter. Mais de nos jours, le jeu d'instruction RISC-V intègre le mode d'adressage base + indice, ce qui fait que la macro-fusion n'a aucun intérêt pour ce cas précis. La macro-fusion a de nombreuses limitations en pratique. En théorie, on pourrait l'utiliser au-delà des deux situations mentionnées plus haut. Par exemple, pour fusionner une addition et une multiplication en une seule instruction MAD. Mais l'intérêt est assez faible. Notons que la macro-fusion ne permet que de fusionner des paires d'instructions. Aller au-delà demanderait un cout en circuit trop important. Déjà que tester deux paires utilise pas mal de comparateurs... ==Le ''stack engine'' : une optimisation de la pile d'appel== Les processeurs modernes intègrent une optimisation liée au pointeur de pile. Pour rappel, sur les architectures modernes, le pointeur de pile est un registre utilisé pour gérer la pile d'appel, précisément pour savoir où se trouve le sommet de la pile. Il est manipulé par les instructions CALL, RET, PUSH et POP, mais aussi par les instructions d'addition/soustraction pour gérer des cadres de pile de taille variable. De plus, il peut servir d'opérande pour des instructions LOAD/STORE, afin de lire/écrire des variables locales, les arguments d'une fonction, et autres. C'est donc un registre général adressable, intégré au banc de registre, altéré par l'unité de calcul entière. L'incrémentation/décrémentation du pointeur de pile passe donc par l'unité de calcul, lors des instructions CALL, RET, PUSH et POP. Mais, l'optimisation que nous allons voir permet d'incrémenter/décrémenter le pointeur de pile sans passer par l'ALU, ou presque. L'idée est de s'inspirer des architectures avec une pile d'adresse de retour, qui intègrent le pointeur de pile dans le séquenceur et l'incrémentent avec un incrémenteur dédié. ===Le ''stack engine''=== L'optimisation que nous allons voir utilise un '''''stack engine''''' intégré à l'unité de contrôle, au séquenceur. Le processeur contient toujours un pointeur de pile dans le banc de registre, cela ne change pas. Par contre, il n'est pas incrémenté/décrémenté à chaque instruction CALL, RET, PUSH, POP. Un compteur intégré au séquenceur est incrémenté à la place, nous l’appellerons le '''compteur delta'''. La vraie valeur du pointeur de pile s'obtient en additionnant le compteur delta avec le registre dans le banc de registre. Précisons que si le compteur delta vaut zéro, la vraie valeur est dans le banc de registre et peut s'utiliser telle quelle. Lorsqu'une instruction ADD/SUB/LOAD/STORE utilise le pointeur de pile comme opérande, elle a besoin de la vraie valeur. Si elle n'est pas dans le banc de registre, le séquenceur déclenche l'addition compteur-registre pour calculer la vraie valeur. Finalement, le banc de registre contient alors la bonne valeur et l'instruction peut s'exécuter sans encombre. L'idée est que le pointeur de pile est généralement altéré par une série d'instruction PUSH/POP consécutives, puis des instructions LOAD/STORE/ADD/SUB utilisent le pointeur de pile final comme opérande. En clair, une bonne partie des incrémentations/décrémentation est accumulée dans le compteur delta, puis la vraie valeur est calculée une fois pour toutes et est utilisée comme opérande. On accumule un delta dans le compteur delta, et ce compteur delta est additionné quand nécessaire. Précisons que le compteur delta est placé juste après le décodeur d'instruction, avant même le cache de micro-opération, l'unité de renommage et l'unité d'émission. Ainsi, les incrémentations/décrémentations du pointeur de pile disparaissent dès l'unité de décodage. Elles ne prennent pas de place dans le cache de micro-opération, ni dans la fenêtre d'instruction, ni dans la suite du pipeline. De nombreuses ressources sont économisées dans le ''front-end''. Mais utiliser un ''stack engine'' a aussi de nombreux avantages au niveau du chemin de données/''back-end''. Déjà, sur les processeurs à exécution dans le désordre, cela libère une unité de calcul qui peut être utilisée pour faire d'autres calculs. Ensuite, le compteur delta mémorise un delta assez court, de 8 bits sur le processeur Pentium M, un peu plus pour les suivants. L'incrémentation se fait donc via un incrémenteur 8 bits, pas une grosse ALU 32/64 bits. Il y a un gain en termes de consommation d'énergie, un incrémenteur 8 bits étant moins gourmand qu'une grosse ALU 32/64 bits. ===Les points de synchronisation du delta=== La technique ne fonctionne que si la vraie valeur du pointeur de pile est calculée au bon moment, avant chaque utilisation pertinente. Il y a donc des '''points de synchronisation''' qui forcent le calcul de la vraie valeur. Plus haut, nous avions dit que c'était à chaque fois qu'une instruction adresse le pointeur de pile explicitement, qui l'utilise comme opérande. Les instructions CALL, RET, PUSH et POP ne sont pas concernées par elles utilisent le pointeur de pile de manière implicite et ne font que l'incrémenter/décrémenter. Mais dans les faits, c'est plus compliqué. D'autres situations peuvent forcer une synchronisation, notamment un débordement du compteur delta. Le compteur delta est généralement un compteur de 8 bits, ce qui fait qu'il peut déborder. En cas de débordement du compteur, le séquenceur déclenche le calcul de la vraie valeur, puis réinitialise le compteur delta. La vraie valeur est donc calculée en avance dans ce cas précis. Précisons qu'un compteur delta de 8 bits permet de gérer environ 30 instructions PUSH/POP consécutives, ce qui rend les débordements de compteur delta assez peu fréquent. À noter que si le compteur delta vaut zéro, il n'y a pas besoin de calculer la vraie valeur, le séquenceur prend cette situation en compte. Un autre point de synchronisation est celui des interruptions et exceptions matérielles. Il faut que le compteur delta soit sauvegardé lors d'une interruption et restauré quand elle se termine. Idem lors d'une commutation de contexte, quand on passe d'un programme à un autre. Pour cela, le processeur peut déclencher le calcul de la vraie valeur lors d'une interruption, avant de sauvegarder les registres. Pour cela, le processeur intègre un mécanisme de sauvegarde automatique, qui mémorise la valeur de ce compteur dans le tampon de réordonnancement, pour forcer le calcul de la vraie valeur en cas de problème. La technique du ''stack engine'' est apparue sur les processeurs Pentium M d'Intel et les processeurs K10 d'AMD, et a été conservée sur tous les modèles ultérieurs. L'implémentation est cependant différente selon les processeurs, bien qu'on n'en connaisse pas les détails et que l'on doive se contenter des résultats de micro-benchmarks et des détails fournit par Intel et AMD. Selon certaines sources, dont les manuels d'optimisation d'Agner Fog, les processeurs AMD auraient moins de points de synchronisation que les processeurs Intel. De plus, leur ''stack engine'' serait placé plus loin que prévu dans le pipeline, après la file de micro-opération. ==La micro-fusion et la délamination== La '''micro-fusion''' est une optimisation qui retarde le décodage réel de certaines instructions assez loin dans le pipeline. Elle concerne surtout les instructions ''load-op'', mais pas que. D'autres instructions mémoire peuvent subir cette optimisation. L'essentiel est que, sans micro-fusion, ces instructions sont décodées en plusieurs micro-opérations. La micro-fusion retarde le décodage final de ces instructions, qui se fait assez tard dans le pipeline. Pour comprendre l'intérêt, nous allons devoir faire quelques explications. L'optimisation en question fait le décodage en deux temps. Prenons l'exemple d'une opération ''load-op'', qui est censée être décodée en deux micro-opérations : une lecture, puis une opération. Le décodage produit une '''macro-opération''' (terme d'Intel), qui est propagée dans le pipeline, pour être scindée en deux micro-opérations un peu avant les ALU et l'unité mémoire. La macro-opération est techniquement une micro-opération comme une autre, sauf que ce n'est pas la micro-opération finale. L'unité d'émission découpe cette macro-opération en deux micro-opérations, qui sont émises l'une après l'autre. Pour résumer, les instructions ''load-op'' ne sont complétement découpées en micro-opération lors de l'émission, pas lors du décodage. L'optimisation impacte ce qui se situe entre le décodeur d'instruction et l'unité d'émission : l'unité de renommage, la file de micro-opérations, le ''Loop Stream Detector'' et/ou le cache de micro-opération. L'intérêt de l'optimisation est que l'on fusionne deux micro-opérations en une seule macro-opération. Le cache de micro-opération et la file de micro-opération peuvent mémoriser plus de micro-opérations, vu que certaines sont fusionnées. Quant à l'unité de renommage, elle renomme une seule macro-opération au lieu de renommer deux micro-opérations, ce qui augmente le débit du renommage de registre. Par contre, cela complique un peu le travail de l'unité d'émission, qui doit découper les instructions ''load-op'' en deux et émettre les micro-opérations l'une après l'autre. Il faut noter qu'il n'y a pas de fusion proprement dite. Le décodeur ne décode pas deux micro-opérations séparées, avant de les fusionner. En réalité, il génère directement une macro-opération, qui correspond à deux micro-opérations, que ce soit plus tard dans le pipeline ou sur une autre architecture. D'ailleurs, la micro-fusion a lieu à l'intérieur d'une instruction, qui est censée être traduite en plusieurs micro-opérations. Et encore : pas toutes. La conséquence est que la technique n'a de sens que sur les processeurs CISC, avec des instructions assez complexes pour être décodées en plusieurs micro-opérations. En pratique, seuls les CPU x86 sont concernés. ===Les domaines de fusion=== La micro-fusion n'est pas spécifique aux processeurs superscalaires. En théorie, on peut l'implémenter sur un processeur non-superscalaire à émission dans l'ordre. Cependant, je ne connais aucun processeur à émission dans l'ordre qui implémente la micro-fusion, même si son implémentation est en théorie possible sur de tels processeurs. La micro-fusion est implémentée différemment selon que l'émission se fasse dans l'ordre, dans le désordre, avec une fenêtre d'instruction, avec des stations de réservation, etc. J'ai dit plus haut que les micro-opérations sont générées lors de l'émission, mais le moment exact varie suivant le processeur. Pour simplifier le tout, nous parlerons de '''scission''' quand le processeur traduit une macro-opération en deux/trois micro-opérations. Le moment où a lieu la scission est théoriquement dans l'unité d'émission, mais celle-ci prend plusieurs formes selon que le processeur utilise l'émission dans l'ordre, dans le désordre, avec une fenêtre d'instruction, avec des stations de réservation, etc. Les processeurs à exécution dans l'ordre sont le cas le plus simple. L'unité d'émission est alors en charge du découpage des macro-opérations en micro-opérations. La file de micro-opération ou le cache de micro-opérations mémorisent des macro-opérations. À l'opposé, le tampon de ré-ordonnancement mémorise lui des micro-opérations. Le reste du processeur n'est pas affecté. Sur les processeurs à exécution dans le désordre, les différences sont nombreuses. Le tampon de ré-ordonnancement peut mémoriser soit les micro-opérations finales, soit des macro-opérations. Il en est de même avec la fenêtre d'instruction et/ou les stations de réservation, qu'elles soient centralisées ou décentralisées. Tout dépend de comment se fait l'implémentation. Une implémentation simple effectue la scission des macro-opérations dans l'unité d'émission, comme sur les processeurs à exécution dans l'ordre. Sur de nombreux processeurs, l'unité d'émission et de renommage sont simplement fusionnées, ce qui fait que la scission a lieu juste après le renommage de registres. En conséquence, le tampon de re-ordonnancement et les fenêtres d'instruction ne voient que des micro-opérations. Les macro-opérations disparaissent juste avant. Cette implémentation est préférée avec une fenêtre d'instruction centralisée, car on voit mal comment scinder une macro-opération juste avant l'ALU. Une autre implémentation fait la scission en sortie de la fenêtre d'instruction ou des stations de réservation, qu'elles soient centralisées ou décentralisées. Dans ce cas, le tampon de re-ordonnancement fait de même. L'avantage est que le tampon de ré-ordonnancement stocke des macro-opérations, ce qui augmente sa taille effective. Par exemple, il utilise une seule entrée pour une instruction ''load-op'', au lieu de deux sans micro-fusion de ce type. ===La délamination=== La scission peut parfois avoir lieu plus tôt, dans des circonstances très précises. Concrètement, cela ne concerne que quelques micro-architectures d'Intel, pour des instructions très précises. La raison est les limitations du format des micro-opérations, pas une histoire d'optimisation. Le processeur aimerait pouvoir faire la micro-fusion, mais ne peut pas à cause de ces limitations. Une telle situation cause alors une '''délamination''' : les micro-opérations sont scindées précocement. Par exemple, sur la micro-architecture SandyBridge d'Intel, les opérations ''load-op'' sont fusionnées, sauf si elles utilisent l'adressage indicé. Les micro-opérations de ce processeur ne peuvent avoir que deux opérandes. Trois opérandes est de trop, et les opérations ''load-op'' en adressage indicé dépassent cette limite. Dans ce cas, les micro-opérations sont fusionnées en sortie du décodeur, mais sont scindés avant d’entrer dans la file de micro-opération. L'avantage n'est pas évident, vu que l'unité de renommage suit de près le décodeur d'instruction. Il est que le cache de micro-opérations mémorise des macro-opérations, pas deux micro-opérations. ===La micro-fusion des processeurs Intel et AMD=== La micro-fusion est apparue sur le Pentium M d'Intel, et a été conservée telle quelle sur les processeurs Intel suivants. Les processeurs AMD supportent la micro-fusion depuis au moins la micro-architecture AMD K8. Mieux que ça, la délamination n'était pas utilisée car les micro-opérations acceptaient plus de 2 opérandes. Que ce soit chez AMD ou Intel, elle concerne deux types d'instructions : les écritures mémoire et les instructions ''load-op''. Sur le Pentium M, il y avait quelques limitations, l'optimisation se limitant par exemple aux registres entiers et flottants, mais pas aux registres MMX, mais pas les registres SSE (nous verrons ces registres dans quelques chapitres). Mais les processeurs modernes n'ont plus cette limitation. La micro-fusion des CPU Intel concerne deux types d'instructions : les écritures mémoire et les instructions ''load-op''. Pour les écritures, la situation est plus complexe que pour les instructions ''load-op''. Sur les processeurs Intel précédents, les écritures étaient réalisées dans deux unités séparées : le calcul d'adresse dans une unité de calcul, l'écriture proprement dite dans l'unité mémoire. Elles prenaient donc deux micro-opérations. Le processeur a changé ses unités mémoire, ce qui fait qu'elle pouvait faire les deux en une seule micro-opération. Il s'agissait alors d'une optimisation du chemin de données, plus que de la micro-fusion proprement dite, mais je la mentionne ici malgré tout. La micro-fusion sur ce processeur avait un avantage en plus des avantages usuels : elle utilisait mieux les décodeurs. Les Pentium M avaient trois décodeurs pour décoder plusieurs instructions en même temps, chose qu'on détaillera dans le chapitre sur les processeurs superscalaires. Un seul décodeur pouvait décoder toutes les instructions, les deux autres ne peuvent décoder que les instructions simples, qui se décodent en une seule micro-opération. Avec la micro-fusion, les trois décodeurs peuvent gérer les écritures mémoire et les opérations ''load-op'', vu qu'elles deviennent des instructions simples. Sans cela, elles auraient été limitées au premier décodeur. <noinclude> {{NavChapitre | book=Fonctionnement d'un ordinateur | prev=Le parallélisme mémoire | prevText=Le parallélisme mémoire | next=Exemples de microarchitectures CPU : le cas du x86 | nextText=Exemples de microarchitectures CPU : le cas du x86 }} </noinclude> 5xuotvbdurt6aejvjnp3gb0gw1ow0cs 773249 773248 2026-09-26T15:21:03Z Mewtow 31375 /* Les CPU superscalaire à exécution dans le désordre */ 773249 wikitext text/x-wiki Les processeurs vus auparavant ne peuvent émettre au maximum qu'une instruction par cycle d'horloge. Ils peuvent avoir plusieurs instructions qui s'exécutent en même temps, dans des unités de calcul séparées. C'est le cas dès qu'une instruction multi-cycles s'exécute. Par contre, ils ne peuvent démarrer qu'une seule instruction par cycle. Et quand on court après la performance, ce n'est pas assez ! Les concepteurs de processeurs ont inventés des processeurs qui émettent plusieurs instructions par cycle : les '''processeurs à émissions multiples'''. Un processeur à émission multiple charge plusieurs instructions en même temps, les décode en parallèle, puis les émet en même temps sur des unités de calculs séparées. Pour cela, il faut gérer les dépendances entre instructions, répartir les instructions sur différentes unités de calcul, et cela n'est pas une mince affaire. En général, pour un processeur à émission multiple capable d'émettre N instructions en parallèle, on parle de '''processeur à N voies''', ou encore un ''processeur à N pipelines''. [[File:Superscalarpipeline.svg|centre|vignette|upright=1.5|Pipeline RISC classique à cinq étages sur un processeur superscalaire. On voit bien que plusieurs instructions sont chargées en même temps.]] Les processeurs à émission multiple sont de deux types : les processeurs VLIW et les '''processeurs superscalaires'''. Nous mettons les processeurs VLIW de côté en attendant le prochain chapitre. La raison est qu'ils ont un jeu d'instruction spécialisé qui expose le parallélisme d'instruction directement au niveau du jeu d'instructions, là où les processeurs superscalaires restent des processeurs au jeu d'instruction normal. La différence principale entre processeur VLIW et superscalaire est qu'un processeur superscalaire répartit les instructions sur les unités de calcul à l’exécution, là où un processeur VLIW délègue cette tâche au compilateur. ==Le nombre de voies d'un processeur superscalaire== Les processeurs superscalaires les plus simples ne permettent que d'émettre deux micro-opérations à la fois, d'où leur nom de processeur ''dual issue'', ce qui se traduit en '''processeur à double émission'''. Les processeurs triple émission permettent eux d’émettre trois instructions à la fois. A l'opposé, les CPU superscalaires les plus puissants peuvent émettre jusqu'à 8-10 instructions simultanément. En 2026, les CPU superscalaires peuvent monter jusqu’à 10 µops émises en même temps, ce qui est énorme ! Et cela nous amène à faire une distinction entre les processeurs superscalaires dits '''étroits''' et ceux dits '''larges'''. Dans ce wikilivre, on part du principe que les processeurs superscalaires étroits émettent jusqu'à 4-5 µops à la fois, alors que les larges peuvent en émettre plus, l'intervalle entre les deux est une zone grise. Le nombre exact n'est pas fixé dans le marbre, la distinction est volontairement vague. Nous parlerons de la '''largeur d'un CPU superscalaire''' pour parler du nombre de voies, du nombre de µops émises en même temps. Plus un CPU superscalaire est large, plus il est performant ''toute chose égale par ailleurs''. Mais dans les faits, vous pouvez oublier la partie en italique. Élargir un CPU superscalaire demande de rajouter beaucoup de circuits. Et ce cout en circuit aura un impact sur la performance. A budget en transistors limité, il y a un compromis entre élargir un CPU et rajouter du cache ou la taille des fenêtres d'instructions/ROB et autres. ===Les contraintes d’appariement=== Prenons un processeur double émission, capable d'émettre deux instructions simultanément. Émettre deux instructions en même temps n'est pas toujours possible. Par exemple, si deux instructions sont dépendantes, on ne peut pas les émettre en même temps. Mais oublions un instant ces histoires de dépendances de données, et partons du principe que le CPU ait affaire à deux instructions indépendantes, une paire idéale. Un CPU double émission "parfait" pourrait exécuter n'importe quelle paire d'instruction. Par exemple, il pourrait émettre deux multiplications consécutives, deux lectures consécutives, une lecture et une écriture en même temps, etc. En pratique, aucun processeur ne permet cela. Implémenter un CPU superscalaire "parfait" aurait un cout en circuit beaucoup trop conséquent. Par exemple, pour émettre deux multiplications en même temps, il faudrait deux multiplieurs séparés. Idem pour exécuter deux accès mémoire simultané : cela demande d'avoir deux unités mémoire et un cache multiport. Mais en pratique, aucun CPU superscalaire ne fait cela. La totalité des CPU double émission se débrouille avec un seul circuit multiplieur et une seule unité mémoire. Et cela entraine l'apparition de dépendances structurelles, qui font que certaines paires d'instructions sont impossibles. Par exemple, impossible d'émettre deux multiplications en même temps avec un seul circuit multiplieur. Mais émettre une multiplication en parallèle d'une addition est parfaitement possible. Il y a donc des paires d'instructions autorisées et des paires interdites. Et la logique est la même sur les CPU à triple ou quadruple émission, et au-delà. : des triplets ou quadruplet d'instructions sont impossibles à émettre simultanément, alors que d'autres le sont. Pour le dire autrement, il y a des '''contraintes d'appariement''' sur les instructions à émettre en même temps. ===L'impact des contraintes d’appariement sur la performance=== Ces contraintes d’appariement ont un impact sur les performance, mais celui-ci peut être grandement mitigé. Les programmes informatiques utilisent en pratique beaucoup plus d'opérations entières de base que de multiplication, branchements et accès mémoire. Il y a environ 5 opérations entières de base pour une multiplication entière. En pratique, il est rare de devoir exécuter deux multiplications à la fois. Idem pour les décalages et rotations, qui sont assez éloignés les uns des autres. Deux branchements consécutifs sont quant à eux plus fréquents. Deux accès mémoire consécutifs est assez rare, mais ils restent assez proches les uns des autres malgré tout. La conséquence est que certaines paires d'instructions sont privilégiées par rapport au reste. Les paires regroupant deux calculs basiques (additions, soustractions, ...) sont systématiquement supportées, car dupliquer des ALU entières est facile. Les paires regroupant un calcul et une autre instruction sont elles aussi systématiquement supportées. Par contre, les paires avec deux branchements, deux accès mémoire, deux décalages/rotations, ou deux multiplications/divisions sont fréquemment interdites. Seuls les CPU superscalaires larges peuvent les permettre, et encore : sous conditions. Le résultat est que le processeur peut se débrouiller avec une seule unité mémoire et une unité de branchement. ===Terminologie pour la suite du chapitre=== Décrire les contraintes d’appariement demande de dire quelles combinaisons d’instructions sont autorisées ou interdites. Et pour cela, nous allons introduire les abréviations suivantes : * INT est un raccourcis pour parler d'une opération entière simple, qu'une ALU entière peut exécuter. * MUL correspond à une opération entière complexe, typiquement une multiplication, éventuellement à une division. * FLOAT désigne une opération flottante, peut importe laquelle. * MEM désigne un accès mémoire, à savoir une instruction ou µop mémoire. * BRANCH désigne une instruction de branchement. Cette terminologie permettra de définir des paires d'instructions, qu'elles soient possibles ou interdites. Par exemple, l'expression INT + FLOAT désigne une paire d'instruction regroupant une instruction entière et une instruction flottante, peu importe l'ordre des deux instructions. Il en sera de même pour les triplets, quadruplets, ou plus. Par exemple, l'expression INT + INT + FLOAT + MUL désigne un quadruplet d'instructions regroupant : deux instructions entières, une instruction flottante, et une multiplication entière. J'utiliserais aussi des parenthèses pour regrouper certaines combinaisons. Par exemple, l'expression INT + (MUL /FLOAT) veut parler d'une paire d'instruction où la première est une opération entière basique, la seconde est : soit une multiplication, soit une opération flottante. ==Le ''front-end'' des processeurs superscalaires== Un processeur superscalaire doit charger, décoder, et exécuter plusieurs instructions par cycle. Par exemple, un processeur triple émission doit charger 3 instructions lors d'un premier cycle, puis les décoder au cycle suivant, et les exécuter au cycle suivant. Il travaille donc pas paquets de 3 instructions, dans cet exemple. Intuitivement, charger, décoder et exécuter plusieurs instructions à la fois demande de dupliquer des circuits. Par exemple, il faut dupliquer les décodeurs pour décoder plusieurs instructions. Les unités de calcul sont aussi dupliquées, sauf sur certaines processeurs qui arrivent à ruser, mais laissons cela de côté pour plus tard. Mais d'autres circuits sont simplement modifiés, comme l'unité de chargement ou l'unité d'émission. Voyons ce qu'il en est dans le détail. {|class="wikitable" |- ! rowspan="2" | Processeur sans émission multiple | rowspan="2" | Chargement | rowspan="2" | Décodage | rowspan="2" | Renommage | rowspan="2" | Émission | Exécution / ALU | rowspan="2" | ''Commit''/ROB |- | Exécution / ALU |- ! rowspan="4" | Processeur superscalaire | rowspan="4" | Chargement | rowspan="2" | Décodage | rowspan="4" | Renommage | rowspan="4" | Émission | Exécution / ALU | rowspan="4" | ''Commit''/ROB |- | Exécution / ALU |- | rowspan="2" | Décodage | Exécution / ALU |- | Exécution / ALU |} Nous allons d'abord voir ce qu'il en est pour le ''front-end'', à savoir la partie du CPU qui s'occupe de charger et décoder les instructions. Elle regroupe l'unité de chargement, les décodeurs, l'unité de prédiction de branchement, la file de µops, la file d'instruction, et divers caches. En soi, les changements portent surtout sur l'unité de chargement et les décodeurs. ===Le chargement de plusieurs instructions consécutives=== Un processeur superscalaire charge plusieurs instructions en même temps, ''en tenant compte de la présence de branchements''. La partie en italique est importante et elle a une conséquence très forte : la manière de faire est fortement différente entre un CPU superscalaire étroit et large. Un CPU superscalaire étroit fait au plus simple : il charge un bloc de 2, 3, 4 instructions consécutives en mémoire RAM. Il faut alors doubler/tripler/quadrupler la taille du bus connecté au cache d'instruction. Par exemple, pour charger quatre instructions de 32 bits chacune, il suffit de lire un bloc de 128 bits dans le cache d'instruction. Un bloc de 8, 16, 32 octets est donc lu depuis le cache et est ensuite découpé en instructions, envoyées chacun à un décodeur. Charger plusieurs instructions consécutives ne marche bien qu'en absence de branchements. La présence de branchements casse cette dynamique, et il faut gérer leur présence. * Sans prédiction de branchement, toutes les instructions d'un bloc sont décodées et exécutées en même temps, même s'il y a un branchement dans le tas. * Avec prédiction de branchement, les instructions situées après le branchement ne sont pas décodées ni exécutées. L'unité de chargement coupe le bloc chargé au niveau du premier branchement pris, remplit les vides avec des NOP, avant d'envoyer le tout à l'unité de décodage. Les instructions à l'adresse de destination seront chargées au cycle suivant, en chargeant un nouveau bloc. [[File:Fetch sur un processeur superscalaire avec prediction de branchements.png|centre|vignette|upright=2|Fetch sur un processeur superscalaire avec prédiction de branchements.]] Un autre défaut est que sur le CPU charge un bloc de 128 bits à la fois, les instructions doivent être alignées sur 128 bits. Et on peut généraliser à n'importe quelle taille de bloc. Si les instructions ne sont pas alignées, il faut gérer la situation. Il arrive qu'une instruction soit à cheval sur deux blocs, avec un morceau dans le premier bloc, un autre dans le second. Dans ce cas, l'instruction est chargée en deux fois, et des circuits doivent gérer la situation pour recoller les morceaux. Sur les CPU avec des instructions de taille fixe, le problème ne survient que rarement. Mais il est fréquent avec des instructions de taille variable. Un autre défaut est lié aux branchements. Imaginez qu'un branchement s'exécute et branche vers une instruction de destination. Rien n'indique que l'instruction de destination sera au tout début d'un bloc. Si ça se trouve, elle sera en troisième position d'un bloc de 4 instructions. Une solution simple charge le bloc en respectant l'alignement, et les deux premières instructions du bloc ne sont pas à exécuter. Il y a donc une petite perte de performance. Certains CPU, comme l'IBM RS/6000, ont la machinerie charger un bloc de N instructions même s'il n'est pas aligné. La machinerie en question se trouve dans le cache d'instruction et est tout sauf simple à expliquer,ce qui fait que je me permets de la passer sous silence. Un détail, cependant : elle ne permet pas de charger des blocs qui sont à cheval sur deux lignes de cache. ===Le décodage parallèle des instructions=== Pour le décodage des instructions, plusieurs instructions sont décodées en même temps. On parle alors de '''décodage parallèle'''. Pour ce faire, un processeur superscalaire contient plusieurs décodeurs, chacun pouvant décoder une instruction en parallèle des autres. Prenons par exemple un processeur superscalaire à 4 voies : le décodage se fera dans quatre décodeurs séparés, qui fonctionneront en parallèle. Cependant, cette implémentation ne marche bien que pour les processeurs RISC, les processeurs CISC devant faire avec un microcode gourmand en circuits. Les processeurs CISC utilisent des décodeurs hybrides, avec un microcode qui complémente un décodeur câblé. Dupliquer le microcode aurait un cout en transistors trop important, ce qui fait que seuls les décodeurs câblés sont dupliqués. Les CPU CISC superscalaires disposent donc de plusieurs décodeurs simples, capables de décoder les instructions les plus courantes, avec un seul microcode. La conséquence est qu'il n'est pas possible de décoder deux instructions microcodées en même temps. Par contre, il reste possible de décoder plusieurs instructions non-microcodées ou une instruction microcodée couplée à une instruction non-microcodée. Vu qu'il est rare que deux instructions microcodées se suivent dans un programme, le cout en performance est extrêmement mineur. Il faut noter qu'une instruction correspond à une ou plusieurs µops. Il est donc possible de charger 5 instructions et de se retrouver avec 8 µops en sortie des décodeurs. Et c'est la source d'une distinction très importante. Plus haut, j'ai parlé de processeurs superscalaires à N voies. La terminologie peut avoir deux interprétations. La première est que le processeur charge/décode N instructions à la fois. Une seconde est qu'il est capable d'exécuter/émettre N µops en même temps. Les deux ne sont pas équivalentes, vu qu'un processeur superscalaire peut, par exemple, charger/décoder N instructions et émettre/exécuter N+2 µops. La distinction est particulièrement importante sur les processeurs superscalaires larges. Certains d'entre eux sont capables de charger 5 instructions et d'en émettre le double ! Un exemple classique était les processeurs Athlon d'AMD, qui pouvaient décoder deux instructions par cycle, mais émettre 4 µops par cycle. Ce n'était pas un problème, car beaucoup d'instructions, même basiques, étaient décodées en 2 µops. ===L'émission parallèle des instructions=== La superscalarité modifie en profondeur l'unité d'émission, notamment au niveau de son interface. Avant, elle recevait une micro-opération sur son entrée, et fournissait la micro-opération émise sur une sortie. Et cela vaut aussi bien pour une unité d'émission simple, un ''scoreboard'', une fenêtre d'instruction ou des stations de réservation. Mais avec l'émission multiple, les sorties et entrées sont dupliquées. Pour la double émission, il y a deux entrées vu qu'elle doit recevoir deux micro-opération décodées/renommées, et deux sorties pour émettre deux micro-opérations. Dans ce qui suit, nous ne parlerons pas d'entrée et de sortie, mais de '''ports de décodage''' et de '''ports d'émission'''. Les ports de décodage reçoivent des micro-opérations provenant de l'unité de décodage, les ports d'émissions sont pour les micro-opérations émises. Les ports d'émission injectent les micro-opérations émises dans le pipeline, elle sont propagée au banc de registre, aux ALUs, etc. Un processeur superscalaire a forcément plusieurs ports d'émission (et de décodage). Si l'interface de l'unité d'émission change, l'intérieur est tout autant modifié. Sur un CPU non-superscalaire, l'unité d'émission reçoit une µop "candidate à l'émission", et détecte les dépendances entre avec les µops en vol, en cours d'exécution dans le pipeline. Et c'est toujours le cas sur les processeurs superscalaires : l'unité d'émission détecte les dépendances entre les µops candidates et les µops en vol. Mais avec la siperscalarité, elle doit en plus tester les dépendances entre les µops candidates. Si l'unité d'émission est un ''scoreboard'', il doit détecter les dépendances entre instructions émises simultanément et les sérialiser si besoin. Avec une fenêtre d'instruction, il n'y a pour ainsi dire rien à faire, vu que les dépendances sont éliminées par le renommage de registre et que les signaux de réveil gèrent les dépendances RAW. C'est la raison pour laquelle les processeurs superscalaires modernes n'utilisent pas de ''scoreboard''. ==Les processeurs implémentés avec plusieurs pipelines== Nous venons de voir comment la superscalarité impacte le ''front-end'', il est maintenant de voir ce qu'il en est de l'unité d'émission et des ''back-ends''. Un processeur superscalaire peut utiliser l'exécution dans le désordre et le renommage de registres, mais ce n'est pas une obligation. Historiquement, les premiers processeurs superscalaires n'utilisaient pas d'exécution dans le désordre. Le meilleur exemple est celui du Pentium 1 et 2 d'Intel. Ils étaient tous deux des processeurs superscalaires, mais n'avaient pas d'exécution dans le désordre, qui est apparue sur le Pentium 3. les explications sont plus simples si on met de côté l'exécution dans le désordre. Vous vous dites que l'implémentation d'un processeur superscalaire n'est pas la même suivant que le CPU utilise l'exécution dans le désordre, ou non. Mais dans les faits, la distinction est plus subtile que ca. Pour l'expliquer, je vais utiliser une classification originale, qui sépare les processeurs superscalaires en trois sous-types. La distinction se fait sur la manière dont la superscalarité est implémentée en matériel.La classification que j'utilise distingue : * Les processeurs avec plusieurs pipelines, sans exécution dans le désordre. * Les processeurs superscalaires avec un pipeline dynamique. * Les processeurs avec des fenêtres d'instruction décentralisées. La distinction n'est pas qu'une question d'implémentation matérielle. Elle impacte aussi le ratio entre instructions chargées/décodées et instructions émises. Vous vous dites que les deux sont égales, mais ce n'est le cas que sur les processeurs avec N pipelines ! Sur les autres processeurs, il est possible de décoder 4 instructions, mais d'en émettre maximum 6 par cycle ! Et sur les CPU avec des fenêtres d'instruction décentralisées, il y a aussi une différence entre le nombre d'instructions qui sont ''dispatch'' et ''schedule''. La catégorie la plus simple est celle des '''processeurs avec plusieurs pipelines'''. Ce sont des processeurs superscalaires sans exécution dans le désordre. Ils partent d'un processeur non-superscalaire, et ajoutent un second pipeline, en sortie de l'unité d'émission. Le second pipeline permet l'émission parallèle d'une seconde instruction. Le second pipeline n'est pas identique au pipeline originel, sur plusieurs points. Premièrement, il n'a pas exactement les mêmes circuits. Le second pipeline est généralement composé d'une unité de calcul, d'une FPU, éventuellement d'un second banc de registres, guère plus. Il permet d'exécuter une seconde opération de calcul, pas plus, ce qui est à l'origine de contraintes d'appariemment assez fortes. De plus, il n'a pas forcément la même longueur, il peut avoir des étages en moins ou en plus. Il s'agit de la forme la plus simple de processeur superscalaire et nous allons leur consacrer cette section. ===La double émission entière-flottante=== L'exemple le plus simple utilise un second pipeline pour les opérations flottantes, séparé du pipeline originel. L'ajout d'un pipeline flottant permet d'émettre une micro-opération entière en même temps qu'une micro-opération flottante, ce qui s'appelle la '''double émission entière-flottante'''. La micro-opération entière s'exécute dans l'ALU entière, la micro-opération flottante dans la FPU. Les exemples les plus notables sont le PA-RISC 7100, le POWER 1 (RIOS .& et .9) et le PowerPC 601. L'Apollo DN10000 et l'Intel i860 utilisaient aussi cette forme de double émission, mais étaient des prototypes de CPU VLIW. [[File:Double émission entière-flottante.png|centre|vignette|upright=2|Double émission entière-flottante]] Pour bien expliquer les choses, prenons le processeur illustré ci-dessous, avec deux ''back-end'' séparés : un pour la FPU, et un autre pour le reste. Les instructions entières et mémoire passent toutes par le ''back-end'' généraliste. [[File:CPU non-superscalaire basique, avec une FPU.png|centre|vignette|upright=1.5|CPU non-superscalaire basique, avec une FPU]] Les deux ''back-end'' sont des pipelines séparés, mais il faut que l'unité d'émission soit spécialement conçue pour en profiter. Un processeur simple émission peut démarrer une seule µop par cycle : soit la µop est envoyée au ''back-end'' flottant, soit elle est envoyée au ''back-end'' général. Avec la double émission entière-flottante, le processeur peut envoyer une µop dans chaque pipeline, à chaque cycle. Vu que la FPU et son banc de registre forment déjà un pipeline séparé, les seules modifications se font au niveau du ''front-end'', unité d'émission inclue. {| |- |[[File:CPU simple émission avec une FPU.png|centre|vignette|upright=1.5|CPU simple émission avec une FPU.]] |[[File:Double émission entière flottante avec un second pipeline.png|centre|vignette|upright=1.5|CPU avec un second pipeline flottant.]] |} Notez que le terme double émission ''entière''-flottante est un peu trompeur. En réalité, la technique permet d'émettre une instruction flottante avec n'importe quelle autre instruction non-flottante, avec des restrictions pour les branchements. Il est par exemple possible d'exécuter un calcul flottant en parallèle d'un accès mémoire. Sur les CPU 32 bits, il est possible de faire les multiplications dans la FPU. Pour rappel, la FPU contient un multiplieur entier 53-54 bits, pour multiplier les mantisses de deux flottants 64 bits, qu'on peut utiliser pour faire les multiplications entières 32 bits. Cela permet d'émettre une multiplication en même temps qu'une opération entière dans l'ALU. Ainsi, le CPU pouvait émettre deux instructions : une opération entière de base, avec soit une multiplication, soit une opération flottante. En clair, une émission de type INT + (MUL /FLOAT). Par contre, on perd la possibilité d'émettre une multiplication entière avec une opération flottante, mais c'était un compromis qui en valait la peine. Le CPU PA-RISC 7100 implémentait cette optimisation. Il s'agissait d'un CPU double émission entière-flottante, qui utilisait donc un partitionnement assez simple. Le combo partitionnement + reconditionnement permettait ici de gagner en performance, pour un cout en matériel très limité. D’autant plus limité que le jeu d'instruction prévoyait le reconditionnement de la FPU. Les multiplications ne lisaient pas leurs opérandes dans les registres entiers, mais dans les registres flottants. ===La double émission entière=== Une autre possibilité ajoute un second pipeline, capable d'exécuter des opérations entières simples, appelé le pipeline entier. Le processeur supporte alors la '''double émission entière''', à savoir qu'ils peuvent émettre une opération entière en plus d'une autre instruction. L'opération entière en question est une opération simple, comme une addition, une soustraction, une opération bit à bit, une comparaison, etc. Les exemples classiques sont les processeurs Intel i960 et Pentium. L'implémentation était différente entre l'i960 et le Pentium : Le Pentium avait une seconde ALU entière, alors que le i960 rusait. Le Pentium dupliquait l'ALU entière, pour créer un second pipeline entier. Sur le Pentium originel, les contraintes d'appariement étaient assez strictes. Le Pentium avait une FPU, mais ne pouvait pas l'utiliser en double émission. Il lui était impossible d'exécuter une opération flottante en même temps qu'une opération entière (branchements inclus). Le Cyrix 6x86 était une sorte de Pentium produit par l'entreprise Cyrix, avec une microarchitecture différente. Il avait des contraintes d’appariement réduites. Par exemple, il pouvait émettre une opération flottante en parallèle d'une opération entière. Nous détaillerons ces processeurs dans le prochain chapitre, qui porte justement sur les CPU superscalaires x86. [[File:CPU simple et double émission avec un second pipeline entier.png|centre|vignette|upright=2.5|CPU simple et double émission avec un second pipeline entier.]] L'Intel i960 n'avait pas de FPU, juste une ALU entière et une unité mémoire. Il utilisait l'unité mémoire comme une seconde ALU entière limitée. Pour rappel, une unité mémoire contient une AGU, à savoir une ALU spécialisée dans les calculs d'adresse. Elle est capable de faire des additions, soustractions, et quelques décalages limités (des décalages par 2, 4, 8, pas plus). L'Intel i960 ajoutait des connexions entre AGU et banc de registre, afin que celle-ci serve de seconde unité de calcul. L'AGU est déjà reliée aux deux ports de lecture, pour lire les opérandes du calcul d'adresse, il suffit de la connecter sur le port d'écriture. Le processeur peut alors émettre deux instructions/µops entières, sous certaines conditions. [[File:Emission multiple des opérations entières, double émission.png|centre|vignette|upright=2|Émission multiple des opérations entières, double émission.]] ==Les processeurs superscalaires dynamiques== Les processeurs précédents, avec 2 pipelines, avaient une unité d'émission assez simple. Elle avait deux ports de décodage, et deux ports d'émission. Le nombre de ports de décodage et d'émission étaient identiques. Vous vous dites que c'est naturel, et intuitif, que c'est normal pour un processeur superscalaire. Mais en réalité, la majorité des processeurs superscalaires a plus de ports d'émission que de décodage. Expliquer pourquoi va cependant nous demander de parler de comment la superscalarité exploite les unités de calcul. Posons-nous cette question : de combien d'unités un processeur superscalaire a besoin ? Les unités en question ne sont nécessairement des unité de calcul, l'unité mémoire et l'unité de branchement comptent aussi. La réponse dépend du nombre de voies. Si le ''front-end'' charge et décode un paquet de N instructions, exécuter ces N instructions simultanément demandera là aussi au moins N unités de calcul. Notez le terme : ''au moins'', car c'est là tout l'enjeu de cette section. La réponse dépend aussi des contraintes d’appariement. Pour un CPU superscalaire "parfait", sans contraintes d’appariement, toutes les unités doivent être dupliquées, y compris l'unité mémoire. Un processeur à double émission parfaite devrait dupliquer l'ALU entière, le ''barrel shifter'', le circuit multiplieur, la FPU, l'unité mémoire, et l'unité de branchement, ce qui ferait passer de 6 unités à 12. Le cout en circuit est énorme, même pour de la double émission ! Et pour un CPU à N pipelines, le cout en circuit encore plus prohibitif ! En pratique, les processeurs superscalaires qui font ça sont très rares. La majeure partie des processeurs superscalaires utilisent d'autres techniques : le partitionnement, la duplication partielle des ALUs, la duplication des autres unités. ===Le partitionnement des unités fonctionnelles=== Les CPU superscalaires les plus simples ne dupliquent pas leurs unités de calcul, ils se contentent d'exploiter les unités existantes. Et pour expliquer comment, nous allons partir de l'exemple d'un processeur non-superscalaire contient une ALU entière, une FPU, une unité mémoire et une unité de branchement. Les quatre unités peuvent fonctionner en parallèle, à savoir qu'une unité peut exécuter une instruction à elle toute seule, sans interagir avec les autres. Cela suffit pour créer un processeur quadruple émission, qui peut émettre en même temps : une opération entière, une opération flottante, un branchement, un accès mémoire. Le principe marche aussi sur un processeur avec moins de 4 unités, par exemple sans FPU et/ou sans unité de branchement, ou avec au contraire des unités en plus. L'idée générale est d’émettre une µops séparée dans chaque unité fonctionnelle. Utiliser ainsi les unités existantes, pour rendre un CPU superscalaire, s'appelle le '''partitionnement superscalaire'''. Le terme n'est pas beaucoup utilisé en-dehors de quelques vieux articles académiques. Le partitionnement est fortement dépendant du jeu d'instruction, car il demande de bien séparer instructions entières, flottantes, branchements et instructions mémoire. Et autant c'est bien le cas sur les CPU RISC, autant les CPU CISC ne sont pas dans ce cas. Les CPU CISC ont des instructions ''load-op'', qui effectuent une instruction arithmétique et une µop mémoire. Et ces instructions sont très compliquées à implémenter avec le partitionnement. Et au-delà de ça, les CPU CISC ont beaucoup d'instructions qui sont décodées en plusieurs µops, à exécuter dans un ordre bien précis. On peut pas émettre les µops en question simultanément. Le partitionnement a un cout en circuit très limité, pour un gain en performance lui aussi limité. Pour reprendre le CPU de l'exemple précédent, il est rare que l'on ait à exécuter ces quatre instructions en même temps. En pratique, les combinaisons fréquentes sont les combinaisons calcul + accès mémoire, calcul + branchement, calcul entier + calcul flottant. Utiliser une quadruple émission de ce type n'est donc pas une bonne idée, alors que la double émission parait plus adaptée. Et c'est là que la différence entre ports d'émission et de décodage devient intéressante. L'idée est d'avoir une unité d'émission avec deux ports de décodage, et 4 port d'émission (un par unité). Les contraintes d’appariement varient alors suivant le processeur, mais la règle générale est que si deux micro-opérations tombent dans deux unités différentes, on peut les émettre en même temps. En clair, on peut émettre une opération entière avec une opération flottante, une opération entière avec un branchement, un accès mémoire avec un branchement, etc. Et cela rend le partitionnement bien plus intéressant que prévu. Les tout premiers CPU superscalaires étaient des CPU double émission, qui utilisaient le partitionnement avec : une ALU entière, une unité mémoire et une FPU. Nous parlerons de '''partitionnement INT-FLOAT-MEM'''. L'avantage sur la double émission flottante est que les combinaisons INT + MEM et FLOAT + MEM étaient autorisées. L'intérêt était d'émettre une opération en parallèle d'un accès mémoire, ce qui est un doublet d'instruction assez fréquent. Les exemples classiques sont l'HyperSPARC, de l'alpha 21064 et du PowerPC 603. Les contraintes d’appariement variaient grandement suivant le processeur. Le PowerPC 603 implémentait une double émission "parfaite". C'est à dire que toutes les combinaisons possibles à deux instructions étaient possibles, que ce soit INT + FLOAT, INT + MEM, FLOAT + MEM. En plus de cette double émission "parfaite", le CPU utilisait la technique dite de ''branch folding'', qui permettait d'exécuter un branchement sans utiliser d'unité de calcul, ce qui fait qu'il était qualifié de ''CPU 2-way + Branch''. A l'opposé, le processeur alpha 21064 avait pas mal de contraintes d’appariement, à savoir que certaines combinaisons de deux instructions étaient interdites, bien que compatibles avec les unités disponibles. Une des raisons est que le banc de registre n'avait pas assez de ports de lectures, ce qui fait que certaines combinaisons avec une écriture mémoire n'étaient pas permises. Les concepteurs avaient volontairement inséré ces contraintes afin de simplifier le design du processeur. Avant de poursuivre, précisons que la FPU regroupe un additionneur flottant et un multiplieur flottant. Et ce fait peut être exploité pour approfondir le partitionnement. La '''double émission FADD/FMUL''' permet d'émettre une addition flottante en même temps qu'une multiplication flottante. Elle a été utilisée sur les processeurs x86, Alpha et SPARC. Les autres circuits de calcul flottant sont souvent regroupés avec l'additionneur. La division est parfois e même port que le multiplieur flottant, parfois sur celui de l'additionneur (quand elle est microcodée et exécutée en enchainant les soustractions). ===La duplication des ALU entières=== Le partitionnement est une technique élégante, simple, facile à implémenter et au cout en circuits modeste. Mais il faut avouer que cela ne donne pas des CPU superscalaires très efficaces. La raison est que le partitionnement permet d'émettre des paires, triplets, quadruplets de micro-opérations qui sont rares en pratique. Les combinaisons les plus fréquentes en pratique sont des paires/triplets d'opérations entières, souvent des paires/triplets d'additions ou d'opérations entières simples. Il est donc primordial d'émettre plusieurs opérations entières simultanément. Le fait d'émettre et d'exécuter plusieurs opérations entières en même temps s'appelle l''''émission entière multiple'''. Pour exécuter la seconde/troisième opération entière, il faut dupliquer les ALU entières (ou réutiliser l'AGU comme ALU entière comme le fait l'Intel i960). Le processeur doit avoir deux ALU entières pour de la double émission entière, trois pour de triple émission entière, etc. De plus, il faut ajouter des ports au banc de registre, pour lire deux fois plus d'opérandes et décrire deux résultats. Et enfin, il faut rajouter un port d'émission pour pouvoir émettre une micro-opération entière en plus. [[File:Processeur non-superscalaire basique 03.png|centre|vignette|upright=2|Processeur non-superscalaire basique]] La tendance est de multiplier les ALU entières, mais pas le circuit multiplieur. Le cout en transistors est alors grandement réduit, au prix de l'apparition de dépendances structurelles. Le CPU peut émettre deux additions en même temps, ou une addition et une multiplication, mais pas deux multiplications. Il en est de même avec le ''barrel shifter'', qui n'est pas dupliqué. Il est donc possible d'émettre un décalage avec une opération de base, mais pas deux décalages en parallèle. Mais ces contraintes d’appariement ne sont pas un problème, car il est rare de devoir émettre deux multiplications/décalages en même temps. La plupart des programmes sont majoritairement remplis d'additions, avec des multiplications assez rares et des décalages qui le sont encore plus. La moyenne est proche d'une multiplication pour 4/5 additions. Vu que des multiplications consécutives sont très rares, dupliquer le multiplieur est presque inutile. Disposer de plusieurs circuits multiplieurs ou de plusieurs ''barrel shifters'' serait donc un cout en circuits qui ne servirait que rarement et n'en vaut pas la chandelle. Il faut noter que sur les CPU 32 bits, les multiplications peuvent être exécutées dans la FPU ou dans un multiplieur séparé, tout dépend de si le CPU exécute les multiplications dans la FPU ou pas. {|class="wikitable" |- ! Voie d'émission n°1 !! Voie d'émission n°2 !! Voie d'émission n°3 |- | ALU entière || ALU entière || FPU |} Reste à connecter les unités de calcul à l'unité d'émission (indirectement, car il y a des étages de pipeline entre les deux). Une implémentation basique utilisee un port d'émission par unité. Prenons l'exemple d'un CPU superscalaire avec 4 ALU entières, une FPU, et une unité mémoire. Vu qu'il y a 6 unités, cela fait donc 6 ports d'émission. L'exemple historique le plus impressionnant est celui du Motorola 88110, un CPU double émission qui possède 10 unités. Il contient deux ALU entières, un circuit multiplieur, un circuit diviseur, une unité de manipulation de bits, une unité mémoire, un additionneur flottant, et deux unités graphiques pour des calculs impliquant des pixels. Le multiplieur est utilisé à la fois pour les multiplications entières, flottantes et le produit de deux pixels. Idem pour le diviseur, qui sert pour les divisions entières et flottantes. Et chaque unité a son propre port d'émission. La conséquence est que les contraintes d'appariement du Motorola 88110 sont simples : tant que deux µops atterrissent dans des unités séparées, la paire est permise. Il est par exemple possible d'émettre simultanément deux opérations entières simples, vu qu'il y a deux ALU. Par contre, impossible d'émettre simultanément deux multiplications, deux divisions, deux opérations flottantes, deux manipulations de bit, deux accès mémoire, etc. De plus, le partage du multiplieur fait qu'on ne peut pas émettre une multiplication entière avec une multiplication flottante. Idem avec les divisions, vu que le diviseur est aussi partagé. Ce sont les seules contraintes. ===Le partage des ports d'émission=== La présence d'un circuit multiplieur et d'un ''barrel shifter'' pose des problèmes pour le partitionnement. Il est possible d'avoir un port d'émission dédié au multiplieur, et un autre au ''barrel shifter'', qui sont séparés de ceux pour l'ALU entière. Il est alors possible d'émettre une opération entière, une multiplication et un décalage, simultanément. Nous parlerons de '''partitionnement entier''' pour désigner cette possibilité. [[File:Partitionnement superscalaire entier.png|centre|vignette|upright=2|Partitionnement superscalaire entier.]] Le partitionnement entier est cependant très rare, car le gain en performance serait trop faible au regard des couts en circuit. Quelques processeurs historiques l'utilisaient, mais ce n'est plus le cas de nos jours. La raison est que les multiplications et les décalages sont assez rares dans le code, ce qui fait que le port d'émission associés sont sous-utilisés. Et un port d'émission a un cout en circuit assez important. Et c'est sans compter qu'ajouter des ports sur le banc de registres juste pour un multiplier sous-utilisé n'en vaut pas la peine. L'idéal serait de trouver un moyen de réduire le nombre de ports d'émissions, sans réduire le nombre d'unités de calcul. [[File:Emission multiple des opérations entières, implémentation naive.png|vignette|Émission multiple des opérations entières, implémentation naive]] La solution consiste à '''partager les ports d'émission''', à savoir connecter un port d'émission à plusieurs unités de calcul. L'exemple classique est justement lié aux circuits multiplieur et ''barrel shifters''. L'idée est que le multiplieur et le ''barrel shifter'' sont mis sur le même port d'émission qu'une ALU entière. Prenons par exemple le processeur à double émission entière illustré ci-contre. Un port d'émission est relié à une ALU entière seule, et l'autre port d'émission alimente la seconde ALU entière et le multiplieur. La conséquence est que le processeur peut émettre deux opérations entières, dont une multiplication. Si on avait utilisé un port d'émission dédié au multiplieur, il aurait été possible d'émettre deux opérations entières et une multiplication. On perd donc une voie, mais on gagne en flexibilité et on économise des circuits. Notons que ce partage signifie aussi que le multiplieur partage les ports sur le banc de registre, ce qui fait une double économie. [[File:Emission multiple des opérations entières, implémentation courante.png|centre|vignette|upright=1.5|Émission multiple des opérations entières, implémentation courante]] En général, le ''barrel shifter'' et le multiplieur sont placés sur deux voies d'émission séparés, ce qui permet d'émettre un décalage et une multiplication en même temps. Les mettre sur le même port ne le permettrait pas. {|class="wikitable" |- ! Voie d'émission n°1 !! Voie d'émission n°2 !! Voie d'émission n°3 |- | * ALU entière * Multiplieur | * ALU entière * Barrel shifter | FPU |- | * Additions, soustractions, opérations bit à bit, etc. * Multiplication | * Additions, soustractions, opérations bit à bit, etc. * Décalages et rotations | Opération flottante |} Mais le partage des ports d'émission peut appliquer à n'importe quelle unité. Prenons l'exemple d'un CPU superscalaire avec 4 ALU entières, une FPU, et une unité mémoire. Vu qu'il y a 6 unités, cela fait donc 6 ports d'émission. Cependant, il est rare qu'un bloc de 6 instructions contienne exactement 4 opérations entières, une opération flottante, et un accès mémoire. Beaucoup de ports d'émission seraient sous-utilisés. La solution est de partager des ports d'émission, pour passer à la quadruple émission. Le CPU peut alors répartir 4 instructions sur les ports d'émission adéquats, à chaque cycle. Le CPU a donc 4 ports de décodage et 6 ports d'émission. Une implémentation classique permet n'importe quelle combinaison de 4 instructions compatible avec les 6 unités. Par exemple, il devient possible d'émettre simultanément 4 additions, la combinaison 2 * INT + FLOAT + MEM, 3 * INT + MEM, et bien d'autres. L'exemple relie une ALU entière, la FPU et l'unité mémoire sur le même port. Le CPU a alors quatre ports d'émission, qui ne sont pas "identiques". Trois d'entre eux sont reliés à une ALU entière chacun, rien d'autre. Le quatrième est lui relié à une ALU entière, la FPU et l'unité mémoire. On économise alors deux ports d'émission, mais cela entraine l'apparition de contraintes d’appariement. Le CPU peut émettre 3 opérations entières + n'importe quelle autre opération. Mais les autres combinaisons sont interdites. Le CPU peut émettre 6 opérations entières simultanément, grâce à 6 ALU entières. Les accès mémoire ou opérations flottantes ne viennent pas en plus, mais en remplacement des opérations entières. Regrouper plusieurs ALU sur un même port d'émission est donc à l'origine de dépendances structurelles, de nouvelles contraintes d’appariement. Dans certaines situations de '''''port contention''''', on a assez d'ALU pour exécuter N micro-opérations, mais la répartition des ALUs sur les ports l'empêche. Impossible d'émettre deux micro-opérations sur deux ALU si elles sont sur le même port d'émission. On peut réduire les cas de ''port contention'' en répartissant correctement les ALU sur les ports d'émission, mais sans jamais les supprimer complétement. Un exemple est celui du PowerPC 604. Il implémentait la quadruple émission, alors qu'il avait 5 unités de calcul. Mais deux unités partageaient un même port d'émission. Dans le détail, il avait deux ALU entières, un circuit multiplieur/diviseur, une FPU, une unité mémoire. La FPU et le multiplieur étaient sur le même port d'émission. En conséquence, le CPU pouvait émettre seulement les combinaisons INT + INT + MEM + (MUL/FLOAT). : Il est intéressant de voir l'exemple d'un processeur superscalaire parfait, sans contraintes d'appariement, à N voies. Pour cela, on part d'un processeur non-superscalaire, avec M unités de calcul. Un tel processeur peut s'implémenter avec N ports d'émission, chacun relié à M unités de calcul possibles, ce qui fait que les M unités de calcul sont dupliquées en N exemplaires. Si on prend par exemple un CPU non-superscalaire avec une ALU, une FPU, une unité mémoire et une unité de branchement, un port d'émission sera relié à une ALU, une FPU, une unité mémoire et une unité de branchement. ===La duplication des autres unités fonctionnelles=== Dupliquer les ALU entière est une première étape, mais il est aussi possible de dupliquer les autres unités fonctionnelles. Par exemple, il est possible de dupliquer l'unité mémoire ou l'unité de branchement. Reste à voir quelles unités dupliquer, et pourquoi. Précisons que dans ce qui va suivre, nous allons parler de la duplication des unités en général, pas seulement des unités mémoire/branchement. Nous allons continuer de parler de la duplication des ALU entières dans ce qui suit, mais de manière plus générale. Pour un processeur à N voies, il est théoriquement possible de dupliquer toutes les unités en N exemplaires. Mais ce n'est jamais fait en pratique, car le cout en circuits serait trop important et le gain en performance trop faible. Mieux vaut dupliquer certaines unités bien précises, quitte à avoir des contraintes d'appariement. Et à ce petit jeu, toutes les duplications ne se valent pas, certaines sont plus rentables en matière de performance, le cout en circuit n'est pas le même selon l'unité, etc. La rentabilité en performance dépend surtout des contraintes d'appariement. Les contraintes d'appariement entrainent une baisse de performance significatives si elles interdisent des paires d'instructions fréquentes. Aussi, les opérations très fréquentes, souvent appairées, gagnent à dupliquer l'unité associée. Et au contraire, les unités rarement utilisées ne doivent pas être dupliquées. Il se trouve que les unités les plus utilisées sont les ALU entières, suivies par les unités mémoire et l'unité de branchement. Le cout en circuit n'est aussi pas le même. Dupliquer les ALU entière est trivial, car elles ont un budget en transistors assez faible. Les circuits multiplieurs, diviseurs, et les ''barrel shifter'' ont eux un cout en transistor plus élevés. Si on devait faire un classement, on aurait : ALU entière < ''barrel shifter'' < multiplieur. Pour les unités mémoire, le cout en transistor est là aussi conséquent. Le résultat final est que dupliquer les ALU entières est privilégié sur le reste, comme l'a vu dans la section précédente. Les unités mémoire arrivent en seconde place, suivie par les multiplieurs/''barrel shifter'' à la troisième place, et les unités de branchement à la quatrième place. Les CPU superscalaires larges ont donc tendance à multiplier les unités mémoire en second. Les multiplieurs et ''barrel shifters'' sont eux aussi dupliqués sur les CPU superscalaires larges, mais cela vient souvent après avoir dupliqué les unités mémoire. La raison est que les accès mémoire sont critiques pour la performance des CPU à exécution dans le désordre, pas les multiplications. Pour les unités de branchement, une subtilité complique le fait d'en installer plusieurs. Que faire quand deux branchements sont pris ? La solution la plus simple est de ne pas se trouver dans cette situation : on n'utilise qu'une seule unité de branchement et on n’exécute qu'un seul branchement par cycle. La majorité des processeurs superscalaires étroits font ainsi. Les processeurs superscalaires larges ont cependant deux unités de branchements, mais ils n'acceptent qu'un seul branchement pris à la fois. Si les deux branchements pris sont exécutés en même temps, le processeur n’exécute que le plus récent, dans l'ordre du programme. ===Les CPU superscalaires mélangent les trois techniques=== Les CPU superscalaires utilisent toutes les techniques à la fois : partitionnement, duplication de l'ALU entière, duplication des autre unités fonctionnelles. Chaque processeur utilise ces trois stratégies à sa sauce, en fonction de son budget en transistor. Les quatre techniques ont cependant un cout en circuit différent, et donnent des gains de performances eux aussi forts différents. * Le '''partitionnement superscalaire''' a un cout en circuit modéré, car on n'a pas besoin de dupliquer d'ALU. * La '''duplication des ALU entières''' est la technique la plus importante : elle a un cout en circuit modéré, et donne des gains en performance impressionnants. * La '''duplication des autres unités''' a un cout en circuit variable, pour des gains en performance eux aussi variables. * Le '''partage des ports d'émission''' réduit le cout en circuit du CPU, pour des pertes de performances mineures si bien fait. L'usage du partitionnement seul, sans duplication, était surtout le fait des processeurs commercialisés entre les années 1989 et 1993. Le partage des ports d'émission a lui aussi mis un peu de temps pour s'imposer. Globalement, les processeurs superscalaires ont évolués progressivement, pour se rapprocher d'une sorte de standard. Mais pour comprendre ce qu'est ce standard, et en quoi les CPU historiques s'en sont progressivement rapprochés, il faut étudier l'histoire des CPU superscalaires. L'histoire des CPU superscalaires a commencé dès 1960, mais est resté un projet de recherche pendant plusieurs décennies. De nombreux designs ont été conçus, mais abandonnés avant la production, comme l'ACS-1 d'IBM. Les années 1980 ont vu l'arrivée de prototypes de processeurs VLIW, mais qui n'étaient pas superscalaires. Par exemple, l'Apollo DN10000 et l'Intel i860 pouvaient émettre deux instructions à la fois, sous conditions, mais demandaient que le compilateur fasse une bonne partie du travail. Les premiers CPU superscalaires commercialisés sont sortis en 1989. Deux CPUs superscalaires sont sortis cette année là : le RS/6000 d'IBM et l'i960CA d'Intel. Puis, la compétition s'est aussi lancée dans la bataille. Les CPU commercialisés avant l'année 1993 étaient à double émission, à l'exception du SuperSPARC qui utilisait la triple émission. Les modèles après 1993 hésitaient entre double et quadruple émission. Dans le tableau ci-dessous, les processeurs quadruple émission sont en bleu, les triple émission sont en rouge, les autres ne sont pas en couleur. {|class="wikitable" |- ! !! IBM !! HP PA-RISC !! SPARC !! Intel!! Alpha !! Autres |- ! 1989 | RS/6000 || || || i960CA || || |- ! 1990 | || || || || || |- ! 1991 | || || || || || Motorola 88110 |- ! 1992 | RSC || PA 7100 || class="f_rouge | SuperSPARC || || 21064 || |- ! 1993 | PPC 601 || || HyperSPARC || Pentium || || |- ! 1994 | class="f_bleu | POWER 2 || PA 7200 || || || class="f_bleu | 21164 || |- ! 1995 | 603/604 || class="f_bleu | PA 8000 || class="f_bleu | UltraSPARC 1 || Microarchitecture P6 (+ concurrence AMD) || || class="f_bleu | MIPS R1000 |- ! 1996 | class="f_bleu | 620 || || || || class="f_bleu | 21264 || |- ! ... || ... || ... || ... || ... || ... || ... |} Les premiers CPU à double émission sont assez représentatifs des CPU à double émission modernes. Il faut dire que se limiter à deux voies ne permet pas de faire beaucoup de choses. Voici le tableau chronologique disant quel CPU utilisait quelle technique. * Les CPU en vert utilisaient la double émission entière-flottante. * Les CPU en rouge utilisaient la double émission entière "pure". * Les CPU en jaune utilisaient un ''partitionnement complet'' entre ALU, FPU et unité mémoire. * Les CPU sans couleur utilisaient un grand nombre d'unités, avec beaucoup de ports d'émission. * Les CPU barrés utilisent la triple ou quadruple émission et seront vu dans la section suivante. {|class="wikitable" |- ! !! IBM !! HP PA-RISC !! SPARC !! Intel!! Alpha !! Autres |- ! 1989 | class="f_vert | RS/6000 || || || class="f_rouge | i960CA || || |- ! 1990 | || || || || || |- ! 1991 | || || || || || Motorola 88110 |- ! 1992 | class="f_vert | RSC || class="f_vert | PA 7100 || <s>SuperSPARC</s> || || class="f_jaune | 21064 || |- ! 1993 | class="f_vert | PPC 601 || || class="f_jaune | HyperSPARC || class="f_rouge | Pentium || || |- ! 1994 | <s>POWER 2</s> || PA 7200 || || || <s>21164</s> || |} C'est en 1994, que la quadruple émission est arrivée et que les CPU ont essayé divers implémentations. Il y avait alors trois stratégies pour la double/triple/quadruple émission, qui partageaient des problèmes similaires. La première implémentation dupliquait les ALUs et les FPUs, ce qui permettait d'émettre deux opérations flottantes avec deux opérations entières. Le POWER 2 dupliquait ses FPUs à la perfection, alors que le 21264 séparait l'additionneur et le multiplieur flottant. Les accès mémoire étaient traités comme des opérations entières et le processeur pouvait souvent en émettre deux à la fois. Mais une telle organisation n'est plus utilisée de nos jours, car, sans instructions flottantes à exécuter, elle est équivalente à de la double émission. La seconde implémentation utilisait deux ALU entières (dont un multiplieur), une unité mémoire et une FPU.Cette organisation est intéressante, mais encore trop stricte pour donner de bons résultats, les quadruplets avec exactement deux opérations entières, un accès mémoire et une opération flottante étant trop rares. L'usage de ces 4 unités en double ou triple émission a été utilisé sur le PA 7200 et le SuperSPARC. Malheureusement, en plus de problèmes au niveau de son cache, le SuperSPARC avait de nombreuses contraintes d’appariement, car le banc de registre n'avait pas assez de ports pour alimenter certaines combinaisons d'instructions. De plus, s'il émettait une multiplication, il ne pouvait pas émettre de seconde µop. Enfin, une troisième implémentation utilisait un grand nombre d'unités fort différentes, bien plus que quatre, avec au minimum deux ALU entières. Prenons l'exemple de l'UltraSPARC, qui avait : deux ALU entières, une unité mémoire, une unité de branchement, un additionneur flottant, un multiplieur flottant, un diviseur flottant (aussi utilisé pour le calcul des racines carrées), et 2 unités graphiques pour les calculs sur des pixels. En tout, cela fait 9 unités différentes, pour de la quadruple émission. Dans le même genre, les processeurs MIPS R8000 et R10000 avaient une implémentation plus moderne, dans le sens où toutes les unités étaient dupliquées. Le CPU avait deux ALU, deux FPU et deux unités mémoire. Il pouvait donc émettre quatre instructions à la fois, tant que le paquet collait avec ces six unités. Moitié opérations entières, moitié opérations mémoire, avec de quoi remplacer les opérations entières par des opérations flottantes. Le PA 8000, lui, dupliquait toutes ses unités en deux exemplaires, ce qui n'est pas l'idéal, aucun processeur moderne ne fait cela aujourd'hui. Le tout est résumé dans le tableau ci-dessous, avec : * en vert les CPU utilisant la première stratégie ; * en rouge les CPU utilisant la seconde stratégie ; * en jaune les CPU utilisant la troisième stratégie. {|class="wikitable" |+ Processeurs quadruple émission historiques |- ! Année !! Processeur !! Unités |- ! 1991 | class="f_jaune | Motorola 88110 || class="f_jaune | 2 ALU + MUL + DIV + BIT + MEM + FADD + 2 unités graphiques |- |- ! rowspan="2" | 1994 | class="f_vert | POWER 2 || class="f_vert | 2 ALU/MEM + 2 FPU (+ 2 Branchements via ''branch folding'') |- | class="f_rouge | 21164 || class="f_rouge | 2 ALU + FPU + MEM |- ! rowspan="3" | 1995 | PA 8000 || 2 ALU + 2 ''barrel shifters'' + 2 MEM + 2 FPU (FMA) + 2 FPU (DIV et SQRT) + 2 SIMD |- | class="f_jaune | UltraSPARC 1 || class="f_jaune | 2 ALU + MEM + BRANCH + FADD + FMULFDIV + 2 unités graphiques |- | MIPS R8000 et R10000 || 2 ALU + 2 FPU + 2 MEM |- ! rowspan="2" | 1996 | class="f_rouge | 620 || class="f_rouge | 2 ALU + FPU + MEM |- | class="f_vert | 21264 || class="f_vert | 2 ALU/MEM + 2 FPU (FADD + FMUL) |} Malheureusement, ces CPUs historiques n'avaient ni assez d'unités de calcul entières, ni d'unités mémoire, pour remplir la quadruple émission. Environ la moitié des instructions d'un programme correspond à des instructions entières. Pour en profiter, un processeur à N voies doit disposer d'au minimum N/2 ALU entières, l'idéal étant d'aller au-delà. Les CPU superscalaires historiques avaient 2 ALU pour de la quadruple émission, ce qui était très limite. En comparaison, les CPU superscalaires modernes mettent le paquet niveau ALU entière. La plupart des CPU x86 modernes descendent rarement sous les 2/3, la plupart remplit presque la totalité de la largeur du CPU. Pour donner un exemple, la microarchitecture Golden Cove d'Intel est un processeur à 6 voies, avec 5 ALU entières. Pour être précis, un programme informatique exécute environ 40% de son code dans des instructions arithmétiques, un autre 40% dans les accès mémoire, et un le reste pour... le reste. La conséquence est que les processeurs modernes dupliquent leurs unités mémoire. Par exemple, la microarchitecture ''Golden Cove'' d'Intel dispose de trois unités mémoire : deux spécialisées dans les écritures, trois dans les lectures. La microarchitecture ''Gracemont'' utilise la pentuple émission et peut émettre 4 µops entières et 4 µops mémoire. Notez que le ratio est équilibré entre opérations entières et mémoire, avec soit égalité, soit un avantage pour les ALU entières. Une autre régularité est que la largeur totale du processeur peut être occupée par des opérations entières/mémoire. Pour le dire autrement, sur un processeur quadruple émission, il pourra émettre un mix de 4 instructions entière/mémoire, avec zéro opération flottante. Idem avec des processeurs triples ou pentuple émission, voire plus. Si le processeur émet une opération flottante, elle remplace une opération entière. Prenons l'exemple des processeurs AMD K6, sortis en 1997, juste avant les AMD Athlon. Ils avaient 10 unités de calcul, 6 ports d'émission, 4 ports de décodage. Voici exactement comment étaient répartie les unités de calcul sur les ports d'émission. C'était des processeurs quadruple émission, ce qui fait que la moitié de la largeur du processeur sert pour des opérations entières, et l'autre moitié pour les accès mémoire. {|class="wikitable" |- ! Port d'émission n°1 !! Port d'émission n°2 !! Port d'émission n°3 !! Port d'émission n°4 !! Port d'émission n°5 !! Port d'émission n°6 ! |- | ALU entière || ALU entière || FPU || Unité de branchement || Unité LOAD || Unité STORE |- | || Multiplieur || || || || Unité de calcul d'adresse (instruction LEA) |- | || ''Barrel shifter'' || || || || Unité pour les instructions PUSH |} Les microarchitectures K7, K8 et K10, utilisées sur les anciens CPU AMD Athlon, sont un exemple moins représentatif. Ces microarchitectures utilisent le partage des ports d'émission au maximum. C'était des microarchitectures à triple émission, avec six ports d’émission. Il y avait trois ports d'émission pour les opérations flottantes, et trois ports d'émission pour les micro-opérations entières/mémoire. Il y avait trois ALU entières et trois unités mémoire, chaque ALU partageait un port d'émission avec une unité mémoire. Elle pouvait donc émettre soit trois opérations entières, soit trois µops mémoire, soit un mix des deux. En plus des mix avec les opérations flottantes et branchements. {|class="wikitable" |- ! Port d'émission n°1 !! Port d'émission n°2 !! Port d'émission n°3 !! Port d'émission n°4 !! Port d'émission n°5 !! Port d'émission n°6 ! |- | ALU entière || ALU entière || ALU entière || FADD || FMUL || FPU (autres opérations) |- | Multiplieur || || || || || |- | LOAD/STORE || LOAD/STORE || LOAD/STORE || || || |} Les CPU superscalaires larges dupliquent les multiplieurs et/ou les ''barrel shifters'', et même les unités de branchement. La raison est que quand la largeur du CPU augmente, le processeur traite des paquets d'instructions de plus en plus gros. Par exemple, pour un CPU à 10 voies, cela veut dire émettre 10 instructions en même temps. Il est alors plus fréquent d'avoir deux multiplications, deux décalages, deux accès mémoire, deux branchements. Par exemple, la moyenne est d'une multiplication tous les 5 instructions. Les CPU superscalaires à 10 voies ont donc tendance à avoir deux multiplications par paquet d'instruction, en moyenne. Avoir deux multiplieurs est alors un gain en performance clair. Par exemple, la microarchitecture ''Golden Cove'' a deux ''barrel shifters''. C'est une architecture à 6 voies. {|class="wikitable" |- ! Port d'émission n°1 !! Port d'émission n°2 !! Port d'émission n°3 !! Port d'émission n°4 !! Port d'émission n°5 !! Port d'émission n°6 !! Port d'émission n°7 !! Port d'émission n°8 !! Port d'émission n°9 !! Port d'émission n°10 |- | ALU entière || ALU entière || ALU entière || ALU entière || ALU entière || LOAD || LOAD || LOAD || STORE || STORE |- | ''Barrel shifter'' || Multiplieur || Multiplieur || ''Barrel shifter'' || || || || || || |- | Branchements || Diviseur || || Branchements || || || || || || |} ==Les µops mémoire sur les processeurs superscalaires== Plus haut, nous avons volontairement passé sous silence les accès mémoire. La raison à cela est que leur support sur les CPU superscalaire peut se faire de plusieurs manières différentes, qu'il vaut mieux expliquer dans une section dédiée. Les différentes implémentations sont décrites dans la liste suivante, nous allons les voir une par une. * Avec la première méthode, les accès mémoire sont exécutés par le pipeline pour les opérations entières. * Avec la seconde méthode, les accès mémoire ont un pipeline dédié ; c'est l'''émission parallèle des accès mémoire''. * Avec la troisième méthode, il est possible d'émettre plusieurs accès mémoire simultanément : c'est l'''émission multiple des accès mémoire''. Voyons ces trois cas dans le détail. Nous verrons notamment les différentes formes d'émission multiple des accès mémoire. ===Les accès mémoire traités dans le pipeline entier=== Dans le cas le plus simple, les accès mémoire sont traités comme des opérations entières un peu spéciales. L'unité mémoire est sur le même port d'émission que l'ALU entière, ce qui fait que les accès mémoire remplacent des opérations entières. Faire ainsi permet d'utiliser une ALU entière pour les calculs d'adresse, pas besoin de rajouter une unité de calcul d'adresse séparée. Typiquement, un processeur de ce type place l'unité mémoire après l'unité de calcul. Les opérations entières n'utilisent que l'ALU entière, mais pas l'unité mémoire. Les accès mémoire exécutent un calcul d'adresse dans l'ALU entière, puis l'adresse calculée est envoyée à l'unité mémoire. Le tout est illustré ci-dessous, dans le cas d'un processeur historique à double émission entière-flottante. [[File:Processeur non-superscalaire basique 01.png|centre|vignette|upright=1.5|Processeur non-superscalaire basique]] Un exemple de CPU double émission de ce type est celui du Power PC 440, un CPU destiné à l'embarqué. Vu qu'il était destiné à l'embarqué, il n'avait pas de FPU, il n'avait pas besoin de manipuler de nombres flottants. Il n'a donc que des ALU entières, un multiplieur et une unité mémoire. Il dispose de deux ports d'émission. Le premier est connecté à une ALU entière et un circuit multiplieur, le second est relié à l'unité mémoire et une seconde ALU entière. Une organisation simple, mais très efficace ! [[File:PowerPC 440.png|centre|vignette|upright=2|Microarchitecture du PowerPC 440.]] L'implémentation économise une unité de calcul d'adresse, mais n'a pas de cout en performance significatif. Les accès mémoire étant très lents, émettre une µop entière en parallèle d'un accès mémoire ne sert pas à grand-chose. On peut émettre cette µop entière un cycle plus tard, elle sera terminée avant même que l'accès mémoire soit terminé, même si l'accès mémoire se fait dans le cache L1 ou L2. Pour comprendre pourquoi, faisons quelques calculs. Un accès mémoire prend deux-trois cycles quand il tombe dans le cache L1. Et on doit attendre qu'il soit terminé pour en émettre un nouveau. Combien d'instructions arithmétiques peut-on lancer pendant ce temps, sur un CPU triple émission ? Au premier cycle, on peut en émettre 2 (l'instruction mémoire prend une place, donc 3-1), puis 3 au second cycle. Ce qui fait 5 instructions arithmétiques au total. Or, il se trouve qu'il y a une instruction d'accès mémoire pour 5 instructions arithmétiques, environ. Par contre, ce compromis change quand on passe à la quadruple émission, voire au-delà. Dans ce cas, il devient intéressant de pouvoir émettre une nouvelle instruction mémoire à chaque cycle. Ce qui nous amène à la section suivante... ===L'émission parallèle des accès mémoire=== A partir de la quadruple émission, la tendance a été de séparer l'unité mémoire des ALU entières. Cela permet d'émettre des µops mémoire en même temps que des µops entières et/ou flottantes., ce qui porte le nom d''''émission parallèle des µops mémoire''', ou encore ''émission mémoire parallèle''. Par exemple, un processeur quadruple émission peut émettre en même temps deux opérations entières, une opération flottante et un accès mémoire. L'accès mémoire vient en plus des opérations entières, il n'est pas possible de remplacer l'accès mémoire par une opération entière. La technique ne permet pas de faire les calculs d'adresse dans l'ALU entière. Il faut donc utiliser une unité de calcul d'adresse séparée. Elle est intégrée dans l'unité mémoire, qui fait l'interface entre le cache et le banc de registres. Le cout en circuits est très faible, car les unités de calcul d'adresse n'utilisent guère plus qu'un additionneur et un circuit de décalage très simple. L'émission mémoire parallèle est plus facile à implémenter sur les CPU RISC, qui ont des instructions mémoire séparées du reste. Les CPU CISC ne sont pas dans ce cas, ce qui fait que séparer l'unité mémoire du pipeline entier est compliqué. Les CPU superscalaires historiques étaient pour moité des CPU RISC et pour moitié des CPU CISC, ce qui fait qu'ils n'utilisaient pas ou peu l'émission mémoire parallèle. Les premiers processeurs superscalaires se contenaient de l'émission parallèle mémoire, ils ne pouvaient émettre qu'un seul accès mémoire à la fois, l'unité mémoire n'était pas dupliquée. Et ce n'est pas tant un problème que ça, du moins sur les processeurs à double ou triple émission. En effet, il est rare que l'unité mémoire doive faire deux accès simultanés. Notons que le processeur peut exécuter une instruction mémoire en parallèle d'un calcul ou d'un branchement, seuls les accès mémoire simultanés sont impossibles. ===L'émission multiple des accès mémoire avec une LSQ=== Les processeurs superscalaires larges travaillent sur des paquets de 8 à 10 instructions, il est plus fréquent d'avoir plusieurs accès mémoire par paquet. Dans ces conditions, il est utile d'avoir une '''émission multiple des accès mémoire''', à savoir d'émettre plusieurs micro-opérations mémoire en même temps. Les processeurs superscalaires modernes sont capables d'émettre plusieurs lectures/écritures simultanément. Par exemple, ils peuvent émettre une lecture en même temps qu'une écriture, ou plusieurs lectures, ou plusieurs écritures. Il faut noter que selon le processeur, il peut y avoir des restrictions quant aux accès mémoire émis en même temps. Par exemple, certains processeurs peuvent émettre une lecture avec une écriture en même temps, mais pas deux lectures ni deux écritures. Ou encore, ils peuvent émettre deux lectures, une lecture et une écriture, mais pas deux écritures en même temps. Dans la majorité des cas, les processeurs ne permettent pas d'émettre deux écritures en même temps, alors qu'ils supportent plusieurs lectures. Il faut dire que les lectures sont plus fréquentes que les écritures. Les processeurs qui autorisent toutes les combinaisons de lecture/écriture possibles, sont rares. L'émission multiple des accès mémoire s'implémente dans l'unité mémoire. Pour rappel, l'unité mémoire s'interpose entre le cache L1 et le reste du pipeline. Dans le cas le plus simple, elle incorpore une ''load/store queue'' et des unités de calcul d'adresse. L'émission multiple des accès mémoire demande de rendre la ''Load-store queue'' multi-ports, afin de gérer plusieurs micro-opérations mémoire simultanées. Il faut aussi dupliquer les unités de calcul d'adresse reliées à la LSQ, chaque port d'émission mémoire devant avoir sa propre unité de calcul d'adresse. : Dans ce qui suit, nous parlerons d'AGU (''Adress Generation Unit'') pour désigner les unités de calcul d'adresse, et de LSQ pour designer la ''Load-store queue''. [[File:Processeur non-superscalaire basique 02.png|vignette|upright=1|Processeur non-superscalaire basique]] Dupliquer les ports de la LSQ et les AGU est nécessaire. Mais qu'en est-il du cache ? La réponse varie suivant le processeur. Il y a deux grandes méthodes pour implémenter l'émission multiple des accès mémoire. La première méthode ajoute des ports de lecture/écriture au cache de données, pour faire des lectures/écritures simultanées. Il faut alors dupliquer des unités de calcul, rajouter les ports sur le cache, mais aussi rajouter des ports sur la ''load/store queue'', pour qu'elle échange des données/commandes mémoire avec le cache. Le cout en circuits est alors conséquent La seconde duplique les unités de calcul, mais pas les ports d'accès au cache. Les accès mémoire sont alors accumulés dans la ''load/store queue'', qui sont ensuite traités un par un par le cache. Un exemple est celui des processeurs AMD de micro-architecture K7 K8, à savoir les anciens AMD Athlon et AMD Athlon 64, qui pouvaient calculer trois adresses simultanément, mais ne pouvaient faire qu'un seul accès au cache par cycle. Le cout en circuit est modéré : les unités de calcul coutent très peu, les ports de la ''load/store queue'' coutent déjà un peu plus. Quelques processeurs utilisaient des solutions intermédiaires. Un processeur utilisant cette technique est, par exemple, capable d'émettre quatre µops mémoire en même temps, mais avec seulement deux ports sur le cache. Un exemple classique étant les Pentium 1 et 2 d'Intel, dont nous parlerons dans le chapitre suivant. ===L'émission séparée des LOAD et STORE=== De rares processeurs ont des ports d'émission qui peuvent faire indifféremment lecture comme écritures. Mais les processeurs modernes préfèrent séparer ports d'émission pour les lectures et les écritures. Un exemple est celui des processeurs Intel de micro-architecture Nehalem, qui avaient un port de lecture et un port d'écriture tous deux reliés à la ''Load-store queue'', chacun avec leur propre unité de calcul d'adresse. Comme autre exemple, les processeurs skylake ont une LSQ avec deux ports de lecture et un port d'écriture, ce qui permet d'émettre deux lectures en même temps qu'une écriture. Dans cette section, nous allons étudier le cas avec des ports séparés pour les lectures et écritures. En général, les processeurs ont un peu plus de ports de lectures que d'écritures. Une raison est qu'un programme a, en moyenne, un tout petit peu plus de lectures que d'écritures. Mais la différence n'est pas énorme, c'est un léger déséquilibre. Une seconde raison est que l'exécution dans le désordre marche bien mieux quand les lectures ont lieu le plus tôt possible. Donc mieux vaut privilégier les lectures à l'émission, pour qu'elles se fassent un peu en avance. En pratique, la différence n'est pas très marquée. Il est courant d'avoir un port de lecture et un d'écriture, les CPU superscalaires très larges ont souvent deux ports de lecture et deux d'écriture. A la rigueur, quelques CPU ont trois ports de lecture et deux d'écriture. Le cas le plus simple est celui où on peut émettre une lecture et une écriture en même temps. Il s'implémente facilement si la LSQ est en réalité une simple ''Store queue'', à savoir qu'elle met en avance les écritures uniquement. Pour rappel, les écritures sont mises en attente dans la ''Store queue'', mais les lectures sont exécutées immédiatement. Une fois l'adresse à lire connue, la lecture consulte la ''Store queue'' pour détecter les dépendances de données, puis s'exécute. La donnée à lire est lue soit depuis la ''Store queue'', soit depuis le cache. [[File:Emission LOAD - Store séparée.png|vignette|upright=1|Émission LOAD - Store séparée]] L'implémentation de l'émission multiple est alors très simple. La ''Store queue'' a déjà deux ports, avec un pour les lectures et un autre pour les écritures. Idem pour le cache, qui a naturellement un port de lecture et un d'écriture. Émettre une lecture et une écriture simultanément ne demande donc pas de modification digne de ce nom. La ''Store queue'' doit juste supporter une lecture et une écriture simultanée. Ajouter l'émission d'une seconde lecture n'est pas compliquée : il faut juste rajouter un second port de lecture sur le cache et la ''Store queue''. Pour rappel, l'unité mémoire est précédée par une structure qui met en attente les micro-opérations mémoire avant leur émission finale. La structure en question varie suivant l'implémentation, mais un cas particulier se marie bien avec l'émission multiple des µops mémoire. Il s'agit du cas avec une file de µops séparée pour les lectures et une autre pour les écritures, appelées respectivement la ''file de µops LOAD'' et la ''file de µops STORE''. Il est alors possible d'émettre une lecture et une écriture lors du même cycle d'horloge. Le port de lecture alimente une unité de calcul d'adresse dédiée, directement reliée au cache. Le port d'écriture du cache alimente une unité de calcul, qui est suivie par une file d'écriture, elle-même reliée au cache. Les processeurs AMD de micro-architecture K6 utilisaient cette technique. [[File:Double émission avec le Load Ordering, Store Ordering.jpg|centre|vignette|upright=2.5|Double émission avec le Load Ordering, Store Ordering]] ==Les CPU superscalaire à exécution dans le désordre== La superscalarité modifie aussi les circuits dédiés à l'exécution dans le désordre. Elle modifie les unités de renommage de registre, le ROB et les autres structures liées à l'exécution dans le désordre, au sens large. Aucune structure n'est épargnée. Elles ont tous des prots de lecture/écriture en plus, afin d'encaisser l'émission, le ''commit'' ou le renommage simultané de plusieurs µops. Par exemple, le ROB doit être modifié pour émettre et terminer plusieurs instructions en même temps, en ajoutant des ports de lecture/écriture. Pour un CPU superscalaire à 8 voies, on se dit que le ROB peut retirer 8 µops par cycle maximum. Mais certains processeurs récents ont un ROB qui peut retirer le double ! Un exemple est celui des CPU de micro-architecture Skymont, qui peuvent émettre 8 µops par cycle, mais en retirer 16. Quelques CPU de marque AMD étaient aussi dans ce cas. L'avantage est que cela permet de réduire la taille du ROB, du banc de registre et de quelques autres structures matérielles liées à l'exécution dans le désordre. Le gain sur-compense le cout en transistor nécessaire pour retirer 16 µops par cycle. Dans cette section, nous allons voir comment les étages du pipeline dédiés à l'exécution dans le désordre sont modifiées sur un CPU superscalaire. ===La superscalarité avec une file/fenêtre d'instruction centralisée=== Plus haut, nous avons montré qu'un processeur pouvait avoir plus de ports d'émission que de ports de décodage. On dit aussi que sa ''largeur de décodage'' était supérieure à sa ''largeur d'émission''. La '''largeur de décodage''' est le nombre de µops fournies par l'unité de décodage par cycle d'horloge, la '''largeur d'émission''' est le nombre maximal de µops émises par cycle d'horloge. Et n'allez pas croire qu'un surplus de ports d'émission est du gâchis. Sur les processeurs avec exécution dans le désordre, ce surplus peut être utilisé. Mais avant toute chose, évacuons un détail. Vous avez sans doute pensé que le surplus peut être exploité par les instructions machines décodées en plusieurs µops . Par exemple, si une instruction est décodée en deux µops, on pourrait avoir un port d'émission en plus pour en profiter. Mais les ports de décodage sont *en sortie du décodeur*, et contiennent des µops, pas des instructions. Le surplus de ports d'émission peut occasionnellement utiliser le surplus de ports de décodage, mais cela implique que des µops ont été mises en attente dans l'unité d'émission, pour être émises par la suite. Et cela implique l'usage d'une fenêtre d'instruction ou d'une file de µops. Le principe est que les µops sont accumulées dans la fenêtre d'instruction, en attendant leurs opérandes ou la résolution d'une dépendance. Et si l'occasion se présente, une µops est émise sur chaque port d'émission. Il y a donc un gain en performances, mais il faut faire quelques précisions sur son origine. La largeur d'émission utilisée ne peut pas dépasser, *en moyenne*, la largeur de décodage. Et c'est intuitif : un CPU qui décode 4 µops par cycle ne peut pas émettre 6 par cycle en moyenne, sur le temps long, il n'y a pas de µops en surplus qui sortent du vide. Par exemple, sur un processeur quadruple émission, si on analyse combien d'instructions sont émises par cycle, il peut y avoir des cycles où la largeur d'émission est totalement utilisée, mais ce sera compensé par des périodes où elle est fortement sous-utilisée. Les µops sont accumulées en cas de sous-utilisation, puis consommées en cas d'utilisation complète. Les deux périodes se compensent. Et c'est là que l'on peut comprendre d'où vient le gain en performance. Les cycles où les ports d'émission sont sous-utilisés existent systématiquement, que le processeur utilise l'exécution dans le désordre ou pas. Ils sont liés à la présence de dépendances de données, aux contraintes d’appariement, etc. Elles tendent à réduire la moyenne du nombre de µops émises par cycle. Par contre, ce sont les cycles de rattrapage, où les ports en surplus sont utilisés, qui font remonter la moyenne. Le gain en performance est donc lié à ces cycles de rattrapage, où les µops accumulées sont émises dans les ports en surplus. Et ils n'existent que sur les processeurs avec une file/fenêtre d'instruction. Réduire le nombre de ports d'émission permet de garder des fenêtres d'instruction et un ROB simples, rapides et économes en énergie. Et mine de rien, c'est là un compromis important : à quoi bon augmenter le nombre de ports d'émission si c'est pour que ce soit compensé par une fréquence plus faible ? Par contre, réduire le nombre de ports augmente la ''port contention'', surtout si on a mal réparti les ALU. Il faut donc trouver un compromis entre réduire le nombre de ports d'émission d'un côté, réduire la ''port contention'' de l'autre. La différence entre largeur de décodage et d'émission a un impact assez fort sur le banc de registre, et précisément sur son nombre de ports de lecture. Le banc de registre a besoin de plus de ports de lecture avec une fenêtre d’instruction qu'avec des stations de réservation. Rappelons la terminologie utilisée dans ce cours. Les fenêtres d'instruction se contentent de mémoriser la micro-opération et quelques bits pour détecter la disponibilité des opérandes. Par contre, les stations de réservations mémorisent aussi les opérandes des instructions. Et cette différence a une influence le nombre de ports de lecture du banc de registre. Supposons que toutes les instructions sont dyadiques, à deux opérandes. Comptons le nombre de ports nécessaires pour alimenter les unités de calcul, sans utiliser d'arbitre de banc de registre. Avec une fenêtre d'instruction, il faut le double du nombre de ports d'émission. Mais avec des stations de réservation, il faut le double des ports de décodage. La raison est que les registres sont lus après émission avec une fenêtre d'instruction, avant avec des stations de réservation (sous-entendu, après le décodage). Or, comme dit plus haut, les processeurs superscalaires larges ont souvent plus de ports d'émission que de décodage. ===La superscalarité à fenêtres d'instruction décentralisées=== Les processeurs précédents utilisaient une unité d'émission simple, qui pouvait utiliser l'émission dans l'ordre ou dans le désordre. Elle peut être remplacée par une fenêtre d'instruction centralisée dans que cela quoi que ce soit aux explications précédentes, idem avec une station de réservation centralisée. En soi, l'exécution dans le désordre est donc totalement compatible avec une implémentation simple de la superscalarité. Par contre, des subtilités surviennent avec les fenêtres d'instruction décentralisées. Pour rappel, sur ces processeurs, les µops sont émises en deux phases. Une première phase de ''dispatch'' qui envoie les µops vers la fenêtre d'instruction adéquate, sans détecter les dépendances de données. La seconde phase de ''schedule'' gère les dépendances de données, elle met en attente les µops en attendant que l'unité soit disponible pour les exécuter, en attendant aussi que les opérandes soient disponibles. Et à ce petit jeu, il y a une différence entre le nombre de µops qui sont ''dispatch par cycle'' et celles qui sont ''schedule''. Dit autrement, la largeur d'émission est scindée en une '''largeur de ''dispatch''''' et une '''largeur de ''schedule''. Par exemple, prenons un processeur triple émission avec deux stations de réservation : une pour les µops entières/mémoire, et une autre pour les µops flottantes. L'unité d'émission peut ''dispatch'' maximum trois µops par cycle : soit trois µops entières/mémoire, soit trois flottantes, soit un mix de trois µops différentes. Les stations de réservation, quant à elles, peuvent chacune ''schedule'' 3 µops par cycle, vers trois ALUs entières pour la station de réservation entière/mémoire, vers trois FPS pour la station de réservation flottante. De quoi ''dispatch'' trois µops, mais d'en ''schedule'' six, à chaque cycle. Et cet exemple ne sort pas de nulle part : les CPU x86 d'Intel et AMD sont globalement dans ce cas, même si les chiffres varient d'une microarchitecture à l'autre. L'exemple précédent colle parfaitement à la microarchitecture K7 d'AMD, utilisée sur les processeurs Athlon dans les années 2000. [[File:AMD K7.png|centre|vignette|upright=2.5|AMD K7, microarchitecture.]] ===L'unité de renommage superscalaire=== Avec la superscalarité, l'unité de renommage de registre doit gérer le cas où des instructions consécutives ont des dépendances de registre. Par exemple, prenons un processeur à double émission, qui renomme deux instructions consécutives. Si elles ont une dépendance de registre, le renommage de la seconde instruction dépend du renommage de la première. La première doit être renommée et le résultat du renommage est utilisé pour renommer la seconde, ce qui empêche d'utiliser deux unités de renommage séparées. Pour cela, elle renomme les registres sans tenir compte des dépendances, pour ensuite corriger le résultat. [[File:Unité de renommage superscalaire.png|centre|vignette|upright=2|Unité de renommage superscalaire.]] Seules les dépendances lecture-après-écriture doivent être détectées, les autres étant supprimées par le renommage de registres. Repérer ce genre de dépendances se fait assez simplement : il suffit de regarder si un registre de destination d'une instruction est un opérande d'une instruction suivante. [[File:Détection des dépendances sur un processeur superscalaire.png|centre|vignette|upright=2|Détection des dépendances sur un processeur superscalaire.]] Ensuite, il faut corriger le résultat du renommage en fonction des dépendances. Si une instruction n'a pas de dépendance avec une autre, on la laisse telle quelle. Dans le cas contraire, un registre opérande sera identique avec le registre de destination d'une instruction précédente. Dans ce cas, le registre opérande n'est pas le bon après renommage : on doit le remplacer par le registre de destination de l'instruction avec laquelle il y a dépendance. Cela se fait simplement en utilisant un multiplexeur dont les entrées sont reliées à l'unité de détection des dépendances. On doit faire ce replacement pour chaque registre opérande. [[File:Correction des dépendances sur un processeur superscalaire.png|centre|vignette|upright=2|Correction des dépendances sur un processeur superscalaire.]] ==Les optimisations des processeurs superscalaires larges== Les processeurs superscalaires larges ont des optimisations spécifiques, qui n'ont pas de sens sur les processeurs superscalaires étroits. Dans les grandes lignes, elles se classent en deux types. Les premières permettent de charger/décoder plus de 5-10 instructions simultanément. Le problème est alors que les branchements viennent mettre leur grain de sel et qu'il faut en tenir compte. Les secondes corrigent des problèmes qui apparaissent quand un CPU intègre plus de 5 à 10 unités de calcul. ===L'étape de chargement d'un CPU superscalaire large=== Charger plusieurs instructions pose de nombreux problèmes sur les processeurs superscalaires larges, qui émettent de 5 à 10 instructions simultanées. Pour rappel, la stratégie basique pour charger N instructions est de charger un bloc de N instructions consécutives. Les instructions après un branchement ne sont pas décodées ni exécutées, ce qui entraine une petite baisse de performance. Pour un N petit, la perte de performance est mineure. Mais pour un N large, elle devient suffisamment importante pour que la conception du CPU doit alors être repensée de fond en comble. Les analyses statistiques disent qu'il y a environ un branchement tous les 4 à 6 instructions. Un CPU a donc tendance à décoder des paquets de 3 à 5 instructions consécutives, avant qu'un branchement casse la dynamique. Or, cela pose des problèmes pour utiliser plusieurs décodeurs. L'implémentation basique, qui charge N instructions consécutives pour alimenter N décodeurs, fonctionne donc jusqu'à 4/6 décodeurs. Au-delà de 5 instructions, un CPU superscalaire doit être adapté pour gérer les branchements dans les paquets d'instruction qu'il émet. Le nombre de 5 instructions ne sort donc pas de nulle part. La première solution utilise un cache de µops, avec un ''front-end'' capable de charger/décoder 4/5 instructions à la fois, pas plus. L'idée est que la majorité des instructions proviennent du cache de µops (plus de 80%), ce qui fait qu'on peut émettre environ 10 instructions à partir du cache de µops, pas besoin d'avoir beaucoup de décodeurs. Les performances sont cependant dégradées lors d'un défaut de cache de µops. Les instructions doivent alors être chargées depuis le cache d'instruction, par paquets de 4-5, puis sont décodées par 4-5 décodeurs, ce qui ne permet pas d’alimenter les unités de calcul à plein régime. La solution précédente joue sur la distinction entre pots de décodage et d'émission. Elle permet d'émettre un grand nombre de µops sans pour autant charger un grand nombre d'instructions en même temps. En quelque sorte, ce n'est qu'une méthode de contournement, le CPU superscalaire reste de type étroit. Mais il est aussi possible d'utiliser un '''''front-end large''''', capable de charger une bonne dizaine d'instructions en même temps. La seule difficulté est de tenir compte de la présence de branchements. Pour rappel, avec un ''front-end'' étroit, les instructions situées après le premier branchement pris sont annulées, elles ne sont pas décodées ni exécutées. [[File:Fetch sur un processeur superscalaire avec prediction de branchements.png|centre|vignette|upright=2|Fetch sur un processeur superscalaire avec prédiction de branchements.]] Un ''front-end'' large charge les instructions de destination du branchement et les placent à sa suite. Ils chargent deux blocs à la fois et les fusionnent en un seul qui ne contient que les instructions présumées utiles. [[File:Cache d'instructions autoaligné.png|centre|vignette|upright=2|Cache d'instructions auto-aligné.]] Mais cela demande de charger deux blocs de mémoire en une fois, ce qui demande un cache d'instruction multiports. Il faut aussi ajouter un circuit pour assembler plusieurs morceaux de blocs en un seul : le fusionneur (''merger''). En pratique, cette solution demande d'ajouter un second port de lecture au cache, pour un cout en hardware important, avec un impact négatif sur le temps d'accès au cache. Et il faut rajouter le fusionneur, qui prend un étage de pipeline à lui tout seul. Et encore, cette solution ne gère pas le cas où un bloc contient plusieurs branchements ! [[File:Implémentation d'un cache d'instructions autoaligné.png|centre|vignette|upright=2|Implémentation d'un cache d'instructions auto-aligné.]] Les exemples précédents partent du principe qu'il n'y a qu'un seul branchement dans un bloc. Mais quand les blocs deviennent assez longs, il est fréquent d'avoir plusieurs branchements dans un bloc. Et il est même possible d'avoir plusieurs branchements pris. Dans un bloc de 8-10 instructions, ce n'est pas rare, et les CPU superscalaires à 8-10 voies sont la règle dans les CPU des années 2020. Pour gérer ce cas, il faut ajouter des ports supplémentaires au cache d'instruction et adapter le fusionneur, pour qu'il puisse fusionner trois ou quatre blocs. De plus, il faut utiliser une unité de prédiction de branchement capable de ''prédire plusieurs branchements par cycle''. ===Le décodage superscalaire large=== Le décodage des instructions n'est pas censé dépendre de l'implémentation du chargement. En théorie, une fois plusieurs blocs d'instruction fusionnés, on peut décoder les instructions en parallèle. Cependant, certains processeurs effectuent le décodage en tenant compte de la manière dont les branchements découpent les blocs. Les processeurs Atom d'Intel utilisent une solution de ce type, qui couple prédiction de branchement et décodage parallèle. Ils chargent/décodent des paquets de 3 instructions consécutives, chaque paquet étant séparé du précédent par un branchement. Pour cela, en plus d'avoir un fusionneur adapté, ils regroupent les décodeurs en ''clusters'', chacun regroupant 3 décodeurs, et pouvant décoder trois instructions consécutives. Les micro-architectures Tremont et Crestmont avaient deux ''clusters'' (6 décodeurs au total), la micro-architecture Skymont en intègre trois (9 décodeurs au total). Avec deux ''clusters'', le fonctionnement est le suivant. Si le code est composé d'une suite d'instruction sans branchements, seul le premier ''cluster'' est utilisé, limitant le décodage à trois instructions par cycle. Par contre, si l'une des trois instructions décodée est un branchement, alors le second ''cluster'' décode les trois instructions situées après ce branchement, à partir de l'adresse de destination. Avec un troisième ''cluster'', c'est la même chose, sauf qu'il faut qu'il y ait un branchement dans les trois instructions décodées par le second ''cluster''. Mais comment faire pour savoir quels décodeurs activés, quels ''clusters'' sont actifs ? C'est la prédiction de branchement qui commande. Avec deux ''clusters'', elle détermine si un branchement est présent dans les trois instructions à charger, et quelle est son adresse de destination. Sans branchement, elle prévient les décodeurs et les trois instructions chargées sont décodées dans un ''cluster'' libre. Si elle prédit un branchement, elle charge les instructions adéquates, et active les deux ''clusters''. Avec trois branchements, c'est le même mécanisme, sauf que l'unité de prédiction de branchement doit être capable de prédire deux branchements par cycle. Il faut noter que les décodeurs d'un ''cluster'' sont des décodeurs simples, sans microcode. Sur les processeurs Tremont et Crestmont, le microcode était unique et était partagé entre tous les ''clusters''. Un seul ''cluster'' pouvait décoder une instruction complexe avec le microcode, l'autre devait se limiter à des instructions simples uniquement. Depuis Skymont, le microcode est partiellement dupliqué, avec un microcode par ''cluster''. Le cout en hardware peut paraitre prohibitif, pour un gain en performance faible. Sauf que c'est oublier que ce sont des processeurs Atom, sur lesquels beaucoup d'instructions sont microcodées. Les processeurs Atom ont un budget en transistor limité, ce qui fait qu'il est préférable pour eux d'implémenter des instructions en microcode que d'utiliser des unités de calcul dédiées ou des circuits de décodage complexes. Pour limiter la casse, le microcode n'est que partiellement dupliqué. Seul le microcode pour les instructions fréquente est dupliqué. Il y a un microcode secondaire pour certaines instructions, qui est lui unique et partagé par tous les ''clusters''. ===Le contournement sur les processeurs superscalaires larges=== Pour rappel, la technique du contournement (''register bypass'') permet au résultat d'une instruction d'être immédiatement utilisable en sortie de l'ALU, avant même d'être enregistré dans les registres. Implémenter la technique du contournement demande d'utiliser des multiplexeurs pour relier la sortie de l'unité de calcul sur son entrée si besoin. Il faut aussi des comparateurs pour détecter des dépendances de données. [[File:Pipeline Bypass.png|centre|vignette|upright=1|Pipeline Bypass]] La sortie de chaque ALU doit être reliée aux entrées de toutes les autres, avec les comparateurs qui vont avec. Sur les processeurs ayant plusieurs d'unités de calculs, cela demande beaucoup d’interconnexions et de comparateurs. Pour N unités de calcul, cela demande 2 * N² interconnexions, implémentées avec 2N multiplexeurs de N entrées chacun. Si c'est faisable pour 2 ou 3 ALUs, la solution est impraticable au-delà. Non seulement ce serait un enfer à câbler, mais les fils seraient tellement longs que la fréquence du processeur en prendrait un coup. Notez que cela vaut pour les processeurs superscalaires, mais aussi pour tout processeur avec beaucoup d'unités de calcul. En théorie, un CPU à exécution dans le désordre, non-superscalaire, mais avec beaucoup d'unités de calcul, fait face au même problème. En pratique, avoir une dizaine d'ALU implique processeur superscalaire à exécution dans le désordre. D'où le fait qu'on parle du problème maintenant. La seule solution praticable est de ne pas relier toutes les unités de calcul ensemble. À la place, on préfère regrouper les unités de calcul dans différents '''agglomérats''' ('''cluster'''). Le contournement est alors possible entre les unités d'un même agglomérat, mais pas entre agglomérats différents. Évidemment, certaines possibilités de contournement sont perdues, mais c'est compensé par un gain en fréquence et un cout en circuit/fils réduit. C'est un bon compromis, ce qui explique que presque tous les processeurs modernes l'utilisent. Les rares exceptions sont les processeurs POWER 4 et POWER 5, qui ont préféré se passer de contournement pour garder un processeur très simple et une fréquence élevée. ===Le banc de registre des processeurs superscalaires larges=== Pour émettre deux opérations entières simultanément, il faut lire deux fois plus d'opérandes. Et il faut aussi écrire deux résultats dans les registres, en même temps. Pour cela, on double les ports du banc de registre. En général, il faut au maximum 2 ports de lecture et un port d'écriture par unité de calcul. Le problème, c'est que plus un banc de registres a de ports, plus il utilise de circuits, est compliqué à concevoir, consomme de courant et chauffe. Avec plusieurs dizaines d'unités de calcul différentes, ajouter autant de ports impacterait la fréquence du banc de registres et réduirait les performances, voire est totalement impraticable à cause des contraintes thermiques. Les processeurs superscalaires larges doivent donc trouver des solutions. La première solution utilise un banc de registre unique, mais n'utilise pas autant de ports que le pire des cas le demanderait. Le processeur détecte quand il n'y a pas assez de ports pour servir toutes les micro-opérations. Quand ça arrive, l'unité d'émission émet assez de micro-opérations pour exploiter les ports disponibles, mais met en attente les micro-opérations en trop, le temps que les ports se libèrent. Pour cela, le banc de registre est géré par un circuit d'arbitrage spécialisé, intégré à l'unité d'émission, l’'''arbitre du banc de registres''' (''register file arbiter''). Une autre solution est de dupliquer le banc de registres en plusieurs exemplaires qui contiennent exactement les mêmes données, mais avec moins de ports de lecture/écriture. Un exemple est celui des processeurs POWER, Alpha 21264 et Alpha 21464. Sur ces processeurs, le banc de registre est dupliqué en plusieurs exemplaires, qui contiennent exactement les mêmes données. Les écritures dans les registres sont envoyées à tous les bancs de registres, afin de garantir que leur contenu est identique. Chaque exemplaire a exactement deux ports de lecture. La conception du processeur est simplifiée, que ce soit au niveau du câblage, que de la conception des bancs de registres. Une dernière solution découpe le banc de registres en plusieurs bancs de registres plus petits, avec un réseau d'interconnexions pour échanger des données entre bancs de registres. Dans la plupart des cas, cette séparation est invisible du point de vue de l'assembleur et du langage machine. Le processeur se charge de transférer les données entre bancs de registres suivant les besoins. Sur d'autres processeurs, les transferts de données se font via une instruction spéciale, souvent appelée ''COPY''. [[File:Banc de registres distribué.png|centre|vignette|upright=2|Banc de registres distribué.]] Sur de certains processeurs, un branchement est exécuté par une unité de calcul spécialisée. Or les registres à lire pour déterminer l'adresse de destination du branchement ne sont pas forcément dans le même agglomérat que cette unité de calcul. Pour éviter cela, certains processeurs disposent d'une unité de calcul des branchements dans chaque agglomérat. Dans les cas où plusieurs unités veulent modifier le ''program counter'' en même temps, un système de contrôle général décide quelle unité a la priorité sur les autres. Mais d'autres processeurs fonctionnent autrement : seul un agglomérat possède une unité de branchement, qui peut recevoir des résultats de tests de toutes les autres unités de calcul, quel que soit l’agglomérat. ==La macro-fusion : une optimisation du décodage parallèle== Les processeurs superscalaires supportent la technique dite de '''macro-fusion''', qui permet de fusionner deux-trois instructions consécutives en une seule micro-opération. Par exemple, il est possible fusionner une instruction de test et une instruction de saut en une seule micro-opération de branchement. Il s'agit là de l'utilisation la plus importante de la macro-fusion sur les processeurs x86 modernes. La macro-fusion est utilisée pour fusionner un test et un saut en un branchement unique. L'optimisation est présente sur la plupart des CPU superscalaires modernes. Les CPU x86 l'implémentent, mais aussi quelques CPU ARM et les processeurs Apple de série M (M1 à M5, et potentiellement plus quand ils seront sortis). La fusion des instructions se fait lors du décodage des instructions, grâce à la coopération des décodeurs. Elle marche systématiquement si l'instruction de test et le saut sont consécutives, qu'elles forment bien une paire d'instruction. Par contre, insérer une instruction entre les deux empêche la macro-fusion de faire son travail, sauf si l'instruction insérée est un NOP. Les processeurs ARM64 avaient une forme spécifique de macro-fusion, utilisée pour un cas très précis : charger une constante immédiate de 32 ou 64 bits, dans un registre 64 bits. Les CPU ARM 64 bits utilisent des instructions codées sur 32 bits, qui ne peuvent encoder que des constantes immédiates de 16 bits. Pour charger une constante de 32/64 bits, il faut s'y prendre en plusieurs fois. Une première instruction MOVZ charge les 16 premiers bits, puis 2 à 4 instructions MOVK remplissent le reste. MOVZ met à zéro les 48 bits de poids fort, MOVK permet de sélectionner le doublet à modifier dans le registre (on modifie 16 bits, dont on choisit la place, le reste du registre est laissé tel quel). ARM permet de fusionner cette suite d'instructions en une seule µop MOV avec une constante immédiate. On parle alors de '''''MOVZ + MOVK fusion'''''. Une dernière utilisation, [https://www2.eecs.berkeley.edu/Pubs/TechRpts/2016/EECS-2016-130.pdf proposées pour le jeu d'instruction RISC-V], fusionnait un calcul d'adresse et une instruction LOAD/STORE en une seule instruction d'accès mémoire. Elle a été envisagée à une époque où le jeu d'instruction RISC-V ne gérait pas les modes d'adressage base + indice. L'idée était d'avoir une unité mémoire qui gérait le mode d'adressage base + indice et de fusionner des instructions pour l'exploiter. Mais de nos jours, le jeu d'instruction RISC-V intègre le mode d'adressage base + indice, ce qui fait que la macro-fusion n'a aucun intérêt pour ce cas précis. La macro-fusion a de nombreuses limitations en pratique. En théorie, on pourrait l'utiliser au-delà des deux situations mentionnées plus haut. Par exemple, pour fusionner une addition et une multiplication en une seule instruction MAD. Mais l'intérêt est assez faible. Notons que la macro-fusion ne permet que de fusionner des paires d'instructions. Aller au-delà demanderait un cout en circuit trop important. Déjà que tester deux paires utilise pas mal de comparateurs... ==Le ''stack engine'' : une optimisation de la pile d'appel== Les processeurs modernes intègrent une optimisation liée au pointeur de pile. Pour rappel, sur les architectures modernes, le pointeur de pile est un registre utilisé pour gérer la pile d'appel, précisément pour savoir où se trouve le sommet de la pile. Il est manipulé par les instructions CALL, RET, PUSH et POP, mais aussi par les instructions d'addition/soustraction pour gérer des cadres de pile de taille variable. De plus, il peut servir d'opérande pour des instructions LOAD/STORE, afin de lire/écrire des variables locales, les arguments d'une fonction, et autres. C'est donc un registre général adressable, intégré au banc de registre, altéré par l'unité de calcul entière. L'incrémentation/décrémentation du pointeur de pile passe donc par l'unité de calcul, lors des instructions CALL, RET, PUSH et POP. Mais, l'optimisation que nous allons voir permet d'incrémenter/décrémenter le pointeur de pile sans passer par l'ALU, ou presque. L'idée est de s'inspirer des architectures avec une pile d'adresse de retour, qui intègrent le pointeur de pile dans le séquenceur et l'incrémentent avec un incrémenteur dédié. ===Le ''stack engine''=== L'optimisation que nous allons voir utilise un '''''stack engine''''' intégré à l'unité de contrôle, au séquenceur. Le processeur contient toujours un pointeur de pile dans le banc de registre, cela ne change pas. Par contre, il n'est pas incrémenté/décrémenté à chaque instruction CALL, RET, PUSH, POP. Un compteur intégré au séquenceur est incrémenté à la place, nous l’appellerons le '''compteur delta'''. La vraie valeur du pointeur de pile s'obtient en additionnant le compteur delta avec le registre dans le banc de registre. Précisons que si le compteur delta vaut zéro, la vraie valeur est dans le banc de registre et peut s'utiliser telle quelle. Lorsqu'une instruction ADD/SUB/LOAD/STORE utilise le pointeur de pile comme opérande, elle a besoin de la vraie valeur. Si elle n'est pas dans le banc de registre, le séquenceur déclenche l'addition compteur-registre pour calculer la vraie valeur. Finalement, le banc de registre contient alors la bonne valeur et l'instruction peut s'exécuter sans encombre. L'idée est que le pointeur de pile est généralement altéré par une série d'instruction PUSH/POP consécutives, puis des instructions LOAD/STORE/ADD/SUB utilisent le pointeur de pile final comme opérande. En clair, une bonne partie des incrémentations/décrémentation est accumulée dans le compteur delta, puis la vraie valeur est calculée une fois pour toutes et est utilisée comme opérande. On accumule un delta dans le compteur delta, et ce compteur delta est additionné quand nécessaire. Précisons que le compteur delta est placé juste après le décodeur d'instruction, avant même le cache de micro-opération, l'unité de renommage et l'unité d'émission. Ainsi, les incrémentations/décrémentations du pointeur de pile disparaissent dès l'unité de décodage. Elles ne prennent pas de place dans le cache de micro-opération, ni dans la fenêtre d'instruction, ni dans la suite du pipeline. De nombreuses ressources sont économisées dans le ''front-end''. Mais utiliser un ''stack engine'' a aussi de nombreux avantages au niveau du chemin de données/''back-end''. Déjà, sur les processeurs à exécution dans le désordre, cela libère une unité de calcul qui peut être utilisée pour faire d'autres calculs. Ensuite, le compteur delta mémorise un delta assez court, de 8 bits sur le processeur Pentium M, un peu plus pour les suivants. L'incrémentation se fait donc via un incrémenteur 8 bits, pas une grosse ALU 32/64 bits. Il y a un gain en termes de consommation d'énergie, un incrémenteur 8 bits étant moins gourmand qu'une grosse ALU 32/64 bits. ===Les points de synchronisation du delta=== La technique ne fonctionne que si la vraie valeur du pointeur de pile est calculée au bon moment, avant chaque utilisation pertinente. Il y a donc des '''points de synchronisation''' qui forcent le calcul de la vraie valeur. Plus haut, nous avions dit que c'était à chaque fois qu'une instruction adresse le pointeur de pile explicitement, qui l'utilise comme opérande. Les instructions CALL, RET, PUSH et POP ne sont pas concernées par elles utilisent le pointeur de pile de manière implicite et ne font que l'incrémenter/décrémenter. Mais dans les faits, c'est plus compliqué. D'autres situations peuvent forcer une synchronisation, notamment un débordement du compteur delta. Le compteur delta est généralement un compteur de 8 bits, ce qui fait qu'il peut déborder. En cas de débordement du compteur, le séquenceur déclenche le calcul de la vraie valeur, puis réinitialise le compteur delta. La vraie valeur est donc calculée en avance dans ce cas précis. Précisons qu'un compteur delta de 8 bits permet de gérer environ 30 instructions PUSH/POP consécutives, ce qui rend les débordements de compteur delta assez peu fréquent. À noter que si le compteur delta vaut zéro, il n'y a pas besoin de calculer la vraie valeur, le séquenceur prend cette situation en compte. Un autre point de synchronisation est celui des interruptions et exceptions matérielles. Il faut que le compteur delta soit sauvegardé lors d'une interruption et restauré quand elle se termine. Idem lors d'une commutation de contexte, quand on passe d'un programme à un autre. Pour cela, le processeur peut déclencher le calcul de la vraie valeur lors d'une interruption, avant de sauvegarder les registres. Pour cela, le processeur intègre un mécanisme de sauvegarde automatique, qui mémorise la valeur de ce compteur dans le tampon de réordonnancement, pour forcer le calcul de la vraie valeur en cas de problème. La technique du ''stack engine'' est apparue sur les processeurs Pentium M d'Intel et les processeurs K10 d'AMD, et a été conservée sur tous les modèles ultérieurs. L'implémentation est cependant différente selon les processeurs, bien qu'on n'en connaisse pas les détails et que l'on doive se contenter des résultats de micro-benchmarks et des détails fournit par Intel et AMD. Selon certaines sources, dont les manuels d'optimisation d'Agner Fog, les processeurs AMD auraient moins de points de synchronisation que les processeurs Intel. De plus, leur ''stack engine'' serait placé plus loin que prévu dans le pipeline, après la file de micro-opération. ==La micro-fusion et la délamination== La '''micro-fusion''' est une optimisation qui retarde le décodage réel de certaines instructions assez loin dans le pipeline. Elle concerne surtout les instructions ''load-op'', mais pas que. D'autres instructions mémoire peuvent subir cette optimisation. L'essentiel est que, sans micro-fusion, ces instructions sont décodées en plusieurs micro-opérations. La micro-fusion retarde le décodage final de ces instructions, qui se fait assez tard dans le pipeline. Pour comprendre l'intérêt, nous allons devoir faire quelques explications. L'optimisation en question fait le décodage en deux temps. Prenons l'exemple d'une opération ''load-op'', qui est censée être décodée en deux micro-opérations : une lecture, puis une opération. Le décodage produit une '''macro-opération''' (terme d'Intel), qui est propagée dans le pipeline, pour être scindée en deux micro-opérations un peu avant les ALU et l'unité mémoire. La macro-opération est techniquement une micro-opération comme une autre, sauf que ce n'est pas la micro-opération finale. L'unité d'émission découpe cette macro-opération en deux micro-opérations, qui sont émises l'une après l'autre. Pour résumer, les instructions ''load-op'' ne sont complétement découpées en micro-opération lors de l'émission, pas lors du décodage. L'optimisation impacte ce qui se situe entre le décodeur d'instruction et l'unité d'émission : l'unité de renommage, la file de micro-opérations, le ''Loop Stream Detector'' et/ou le cache de micro-opération. L'intérêt de l'optimisation est que l'on fusionne deux micro-opérations en une seule macro-opération. Le cache de micro-opération et la file de micro-opération peuvent mémoriser plus de micro-opérations, vu que certaines sont fusionnées. Quant à l'unité de renommage, elle renomme une seule macro-opération au lieu de renommer deux micro-opérations, ce qui augmente le débit du renommage de registre. Par contre, cela complique un peu le travail de l'unité d'émission, qui doit découper les instructions ''load-op'' en deux et émettre les micro-opérations l'une après l'autre. Il faut noter qu'il n'y a pas de fusion proprement dite. Le décodeur ne décode pas deux micro-opérations séparées, avant de les fusionner. En réalité, il génère directement une macro-opération, qui correspond à deux micro-opérations, que ce soit plus tard dans le pipeline ou sur une autre architecture. D'ailleurs, la micro-fusion a lieu à l'intérieur d'une instruction, qui est censée être traduite en plusieurs micro-opérations. Et encore : pas toutes. La conséquence est que la technique n'a de sens que sur les processeurs CISC, avec des instructions assez complexes pour être décodées en plusieurs micro-opérations. En pratique, seuls les CPU x86 sont concernés. ===Les domaines de fusion=== La micro-fusion n'est pas spécifique aux processeurs superscalaires. En théorie, on peut l'implémenter sur un processeur non-superscalaire à émission dans l'ordre. Cependant, je ne connais aucun processeur à émission dans l'ordre qui implémente la micro-fusion, même si son implémentation est en théorie possible sur de tels processeurs. La micro-fusion est implémentée différemment selon que l'émission se fasse dans l'ordre, dans le désordre, avec une fenêtre d'instruction, avec des stations de réservation, etc. J'ai dit plus haut que les micro-opérations sont générées lors de l'émission, mais le moment exact varie suivant le processeur. Pour simplifier le tout, nous parlerons de '''scission''' quand le processeur traduit une macro-opération en deux/trois micro-opérations. Le moment où a lieu la scission est théoriquement dans l'unité d'émission, mais celle-ci prend plusieurs formes selon que le processeur utilise l'émission dans l'ordre, dans le désordre, avec une fenêtre d'instruction, avec des stations de réservation, etc. Les processeurs à exécution dans l'ordre sont le cas le plus simple. L'unité d'émission est alors en charge du découpage des macro-opérations en micro-opérations. La file de micro-opération ou le cache de micro-opérations mémorisent des macro-opérations. À l'opposé, le tampon de ré-ordonnancement mémorise lui des micro-opérations. Le reste du processeur n'est pas affecté. Sur les processeurs à exécution dans le désordre, les différences sont nombreuses. Le tampon de ré-ordonnancement peut mémoriser soit les micro-opérations finales, soit des macro-opérations. Il en est de même avec la fenêtre d'instruction et/ou les stations de réservation, qu'elles soient centralisées ou décentralisées. Tout dépend de comment se fait l'implémentation. Une implémentation simple effectue la scission des macro-opérations dans l'unité d'émission, comme sur les processeurs à exécution dans l'ordre. Sur de nombreux processeurs, l'unité d'émission et de renommage sont simplement fusionnées, ce qui fait que la scission a lieu juste après le renommage de registres. En conséquence, le tampon de re-ordonnancement et les fenêtres d'instruction ne voient que des micro-opérations. Les macro-opérations disparaissent juste avant. Cette implémentation est préférée avec une fenêtre d'instruction centralisée, car on voit mal comment scinder une macro-opération juste avant l'ALU. Une autre implémentation fait la scission en sortie de la fenêtre d'instruction ou des stations de réservation, qu'elles soient centralisées ou décentralisées. Dans ce cas, le tampon de re-ordonnancement fait de même. L'avantage est que le tampon de ré-ordonnancement stocke des macro-opérations, ce qui augmente sa taille effective. Par exemple, il utilise une seule entrée pour une instruction ''load-op'', au lieu de deux sans micro-fusion de ce type. ===La délamination=== La scission peut parfois avoir lieu plus tôt, dans des circonstances très précises. Concrètement, cela ne concerne que quelques micro-architectures d'Intel, pour des instructions très précises. La raison est les limitations du format des micro-opérations, pas une histoire d'optimisation. Le processeur aimerait pouvoir faire la micro-fusion, mais ne peut pas à cause de ces limitations. Une telle situation cause alors une '''délamination''' : les micro-opérations sont scindées précocement. Par exemple, sur la micro-architecture SandyBridge d'Intel, les opérations ''load-op'' sont fusionnées, sauf si elles utilisent l'adressage indicé. Les micro-opérations de ce processeur ne peuvent avoir que deux opérandes. Trois opérandes est de trop, et les opérations ''load-op'' en adressage indicé dépassent cette limite. Dans ce cas, les micro-opérations sont fusionnées en sortie du décodeur, mais sont scindés avant d’entrer dans la file de micro-opération. L'avantage n'est pas évident, vu que l'unité de renommage suit de près le décodeur d'instruction. Il est que le cache de micro-opérations mémorise des macro-opérations, pas deux micro-opérations. ===La micro-fusion des processeurs Intel et AMD=== La micro-fusion est apparue sur le Pentium M d'Intel, et a été conservée telle quelle sur les processeurs Intel suivants. Les processeurs AMD supportent la micro-fusion depuis au moins la micro-architecture AMD K8. Mieux que ça, la délamination n'était pas utilisée car les micro-opérations acceptaient plus de 2 opérandes. Que ce soit chez AMD ou Intel, elle concerne deux types d'instructions : les écritures mémoire et les instructions ''load-op''. Sur le Pentium M, il y avait quelques limitations, l'optimisation se limitant par exemple aux registres entiers et flottants, mais pas aux registres MMX, mais pas les registres SSE (nous verrons ces registres dans quelques chapitres). Mais les processeurs modernes n'ont plus cette limitation. La micro-fusion des CPU Intel concerne deux types d'instructions : les écritures mémoire et les instructions ''load-op''. Pour les écritures, la situation est plus complexe que pour les instructions ''load-op''. Sur les processeurs Intel précédents, les écritures étaient réalisées dans deux unités séparées : le calcul d'adresse dans une unité de calcul, l'écriture proprement dite dans l'unité mémoire. Elles prenaient donc deux micro-opérations. Le processeur a changé ses unités mémoire, ce qui fait qu'elle pouvait faire les deux en une seule micro-opération. Il s'agissait alors d'une optimisation du chemin de données, plus que de la micro-fusion proprement dite, mais je la mentionne ici malgré tout. La micro-fusion sur ce processeur avait un avantage en plus des avantages usuels : elle utilisait mieux les décodeurs. Les Pentium M avaient trois décodeurs pour décoder plusieurs instructions en même temps, chose qu'on détaillera dans le chapitre sur les processeurs superscalaires. Un seul décodeur pouvait décoder toutes les instructions, les deux autres ne peuvent décoder que les instructions simples, qui se décodent en une seule micro-opération. Avec la micro-fusion, les trois décodeurs peuvent gérer les écritures mémoire et les opérations ''load-op'', vu qu'elles deviennent des instructions simples. Sans cela, elles auraient été limitées au premier décodeur. <noinclude> {{NavChapitre | book=Fonctionnement d'un ordinateur | prev=Le parallélisme mémoire | prevText=Le parallélisme mémoire | next=Exemples de microarchitectures CPU : le cas du x86 | nextText=Exemples de microarchitectures CPU : le cas du x86 }} </noinclude> c9o2tx81v14og9pwm5m6n2h1kv3flkl 773250 773249 2026-09-26T20:12:36Z Mewtow 31375 /* La superscalarité à fenêtres d'instruction décentralisées */ 773250 wikitext text/x-wiki Les processeurs vus auparavant ne peuvent émettre au maximum qu'une instruction par cycle d'horloge. Ils peuvent avoir plusieurs instructions qui s'exécutent en même temps, dans des unités de calcul séparées. C'est le cas dès qu'une instruction multi-cycles s'exécute. Par contre, ils ne peuvent démarrer qu'une seule instruction par cycle. Et quand on court après la performance, ce n'est pas assez ! Les concepteurs de processeurs ont inventés des processeurs qui émettent plusieurs instructions par cycle : les '''processeurs à émissions multiples'''. Un processeur à émission multiple charge plusieurs instructions en même temps, les décode en parallèle, puis les émet en même temps sur des unités de calculs séparées. Pour cela, il faut gérer les dépendances entre instructions, répartir les instructions sur différentes unités de calcul, et cela n'est pas une mince affaire. En général, pour un processeur à émission multiple capable d'émettre N instructions en parallèle, on parle de '''processeur à N voies''', ou encore un ''processeur à N pipelines''. [[File:Superscalarpipeline.svg|centre|vignette|upright=1.5|Pipeline RISC classique à cinq étages sur un processeur superscalaire. On voit bien que plusieurs instructions sont chargées en même temps.]] Les processeurs à émission multiple sont de deux types : les processeurs VLIW et les '''processeurs superscalaires'''. Nous mettons les processeurs VLIW de côté en attendant le prochain chapitre. La raison est qu'ils ont un jeu d'instruction spécialisé qui expose le parallélisme d'instruction directement au niveau du jeu d'instructions, là où les processeurs superscalaires restent des processeurs au jeu d'instruction normal. La différence principale entre processeur VLIW et superscalaire est qu'un processeur superscalaire répartit les instructions sur les unités de calcul à l’exécution, là où un processeur VLIW délègue cette tâche au compilateur. ==Le nombre de voies d'un processeur superscalaire== Les processeurs superscalaires les plus simples ne permettent que d'émettre deux micro-opérations à la fois, d'où leur nom de processeur ''dual issue'', ce qui se traduit en '''processeur à double émission'''. Les processeurs triple émission permettent eux d’émettre trois instructions à la fois. A l'opposé, les CPU superscalaires les plus puissants peuvent émettre jusqu'à 8-10 instructions simultanément. En 2026, les CPU superscalaires peuvent monter jusqu’à 10 µops émises en même temps, ce qui est énorme ! Et cela nous amène à faire une distinction entre les processeurs superscalaires dits '''étroits''' et ceux dits '''larges'''. Dans ce wikilivre, on part du principe que les processeurs superscalaires étroits émettent jusqu'à 4-5 µops à la fois, alors que les larges peuvent en émettre plus, l'intervalle entre les deux est une zone grise. Le nombre exact n'est pas fixé dans le marbre, la distinction est volontairement vague. Nous parlerons de la '''largeur d'un CPU superscalaire''' pour parler du nombre de voies, du nombre de µops émises en même temps. Plus un CPU superscalaire est large, plus il est performant ''toute chose égale par ailleurs''. Mais dans les faits, vous pouvez oublier la partie en italique. Élargir un CPU superscalaire demande de rajouter beaucoup de circuits. Et ce cout en circuit aura un impact sur la performance. A budget en transistors limité, il y a un compromis entre élargir un CPU et rajouter du cache ou la taille des fenêtres d'instructions/ROB et autres. ===Les contraintes d’appariement=== Prenons un processeur double émission, capable d'émettre deux instructions simultanément. Émettre deux instructions en même temps n'est pas toujours possible. Par exemple, si deux instructions sont dépendantes, on ne peut pas les émettre en même temps. Mais oublions un instant ces histoires de dépendances de données, et partons du principe que le CPU ait affaire à deux instructions indépendantes, une paire idéale. Un CPU double émission "parfait" pourrait exécuter n'importe quelle paire d'instruction. Par exemple, il pourrait émettre deux multiplications consécutives, deux lectures consécutives, une lecture et une écriture en même temps, etc. En pratique, aucun processeur ne permet cela. Implémenter un CPU superscalaire "parfait" aurait un cout en circuit beaucoup trop conséquent. Par exemple, pour émettre deux multiplications en même temps, il faudrait deux multiplieurs séparés. Idem pour exécuter deux accès mémoire simultané : cela demande d'avoir deux unités mémoire et un cache multiport. Mais en pratique, aucun CPU superscalaire ne fait cela. La totalité des CPU double émission se débrouille avec un seul circuit multiplieur et une seule unité mémoire. Et cela entraine l'apparition de dépendances structurelles, qui font que certaines paires d'instructions sont impossibles. Par exemple, impossible d'émettre deux multiplications en même temps avec un seul circuit multiplieur. Mais émettre une multiplication en parallèle d'une addition est parfaitement possible. Il y a donc des paires d'instructions autorisées et des paires interdites. Et la logique est la même sur les CPU à triple ou quadruple émission, et au-delà. : des triplets ou quadruplet d'instructions sont impossibles à émettre simultanément, alors que d'autres le sont. Pour le dire autrement, il y a des '''contraintes d'appariement''' sur les instructions à émettre en même temps. ===L'impact des contraintes d’appariement sur la performance=== Ces contraintes d’appariement ont un impact sur les performance, mais celui-ci peut être grandement mitigé. Les programmes informatiques utilisent en pratique beaucoup plus d'opérations entières de base que de multiplication, branchements et accès mémoire. Il y a environ 5 opérations entières de base pour une multiplication entière. En pratique, il est rare de devoir exécuter deux multiplications à la fois. Idem pour les décalages et rotations, qui sont assez éloignés les uns des autres. Deux branchements consécutifs sont quant à eux plus fréquents. Deux accès mémoire consécutifs est assez rare, mais ils restent assez proches les uns des autres malgré tout. La conséquence est que certaines paires d'instructions sont privilégiées par rapport au reste. Les paires regroupant deux calculs basiques (additions, soustractions, ...) sont systématiquement supportées, car dupliquer des ALU entières est facile. Les paires regroupant un calcul et une autre instruction sont elles aussi systématiquement supportées. Par contre, les paires avec deux branchements, deux accès mémoire, deux décalages/rotations, ou deux multiplications/divisions sont fréquemment interdites. Seuls les CPU superscalaires larges peuvent les permettre, et encore : sous conditions. Le résultat est que le processeur peut se débrouiller avec une seule unité mémoire et une unité de branchement. ===Terminologie pour la suite du chapitre=== Décrire les contraintes d’appariement demande de dire quelles combinaisons d’instructions sont autorisées ou interdites. Et pour cela, nous allons introduire les abréviations suivantes : * INT est un raccourcis pour parler d'une opération entière simple, qu'une ALU entière peut exécuter. * MUL correspond à une opération entière complexe, typiquement une multiplication, éventuellement à une division. * FLOAT désigne une opération flottante, peut importe laquelle. * MEM désigne un accès mémoire, à savoir une instruction ou µop mémoire. * BRANCH désigne une instruction de branchement. Cette terminologie permettra de définir des paires d'instructions, qu'elles soient possibles ou interdites. Par exemple, l'expression INT + FLOAT désigne une paire d'instruction regroupant une instruction entière et une instruction flottante, peu importe l'ordre des deux instructions. Il en sera de même pour les triplets, quadruplets, ou plus. Par exemple, l'expression INT + INT + FLOAT + MUL désigne un quadruplet d'instructions regroupant : deux instructions entières, une instruction flottante, et une multiplication entière. J'utiliserais aussi des parenthèses pour regrouper certaines combinaisons. Par exemple, l'expression INT + (MUL /FLOAT) veut parler d'une paire d'instruction où la première est une opération entière basique, la seconde est : soit une multiplication, soit une opération flottante. ==Le ''front-end'' des processeurs superscalaires== Un processeur superscalaire doit charger, décoder, et exécuter plusieurs instructions par cycle. Par exemple, un processeur triple émission doit charger 3 instructions lors d'un premier cycle, puis les décoder au cycle suivant, et les exécuter au cycle suivant. Il travaille donc pas paquets de 3 instructions, dans cet exemple. Intuitivement, charger, décoder et exécuter plusieurs instructions à la fois demande de dupliquer des circuits. Par exemple, il faut dupliquer les décodeurs pour décoder plusieurs instructions. Les unités de calcul sont aussi dupliquées, sauf sur certaines processeurs qui arrivent à ruser, mais laissons cela de côté pour plus tard. Mais d'autres circuits sont simplement modifiés, comme l'unité de chargement ou l'unité d'émission. Voyons ce qu'il en est dans le détail. {|class="wikitable" |- ! rowspan="2" | Processeur sans émission multiple | rowspan="2" | Chargement | rowspan="2" | Décodage | rowspan="2" | Renommage | rowspan="2" | Émission | Exécution / ALU | rowspan="2" | ''Commit''/ROB |- | Exécution / ALU |- ! rowspan="4" | Processeur superscalaire | rowspan="4" | Chargement | rowspan="2" | Décodage | rowspan="4" | Renommage | rowspan="4" | Émission | Exécution / ALU | rowspan="4" | ''Commit''/ROB |- | Exécution / ALU |- | rowspan="2" | Décodage | Exécution / ALU |- | Exécution / ALU |} Nous allons d'abord voir ce qu'il en est pour le ''front-end'', à savoir la partie du CPU qui s'occupe de charger et décoder les instructions. Elle regroupe l'unité de chargement, les décodeurs, l'unité de prédiction de branchement, la file de µops, la file d'instruction, et divers caches. En soi, les changements portent surtout sur l'unité de chargement et les décodeurs. ===Le chargement de plusieurs instructions consécutives=== Un processeur superscalaire charge plusieurs instructions en même temps, ''en tenant compte de la présence de branchements''. La partie en italique est importante et elle a une conséquence très forte : la manière de faire est fortement différente entre un CPU superscalaire étroit et large. Un CPU superscalaire étroit fait au plus simple : il charge un bloc de 2, 3, 4 instructions consécutives en mémoire RAM. Il faut alors doubler/tripler/quadrupler la taille du bus connecté au cache d'instruction. Par exemple, pour charger quatre instructions de 32 bits chacune, il suffit de lire un bloc de 128 bits dans le cache d'instruction. Un bloc de 8, 16, 32 octets est donc lu depuis le cache et est ensuite découpé en instructions, envoyées chacun à un décodeur. Charger plusieurs instructions consécutives ne marche bien qu'en absence de branchements. La présence de branchements casse cette dynamique, et il faut gérer leur présence. * Sans prédiction de branchement, toutes les instructions d'un bloc sont décodées et exécutées en même temps, même s'il y a un branchement dans le tas. * Avec prédiction de branchement, les instructions situées après le branchement ne sont pas décodées ni exécutées. L'unité de chargement coupe le bloc chargé au niveau du premier branchement pris, remplit les vides avec des NOP, avant d'envoyer le tout à l'unité de décodage. Les instructions à l'adresse de destination seront chargées au cycle suivant, en chargeant un nouveau bloc. [[File:Fetch sur un processeur superscalaire avec prediction de branchements.png|centre|vignette|upright=2|Fetch sur un processeur superscalaire avec prédiction de branchements.]] Un autre défaut est que sur le CPU charge un bloc de 128 bits à la fois, les instructions doivent être alignées sur 128 bits. Et on peut généraliser à n'importe quelle taille de bloc. Si les instructions ne sont pas alignées, il faut gérer la situation. Il arrive qu'une instruction soit à cheval sur deux blocs, avec un morceau dans le premier bloc, un autre dans le second. Dans ce cas, l'instruction est chargée en deux fois, et des circuits doivent gérer la situation pour recoller les morceaux. Sur les CPU avec des instructions de taille fixe, le problème ne survient que rarement. Mais il est fréquent avec des instructions de taille variable. Un autre défaut est lié aux branchements. Imaginez qu'un branchement s'exécute et branche vers une instruction de destination. Rien n'indique que l'instruction de destination sera au tout début d'un bloc. Si ça se trouve, elle sera en troisième position d'un bloc de 4 instructions. Une solution simple charge le bloc en respectant l'alignement, et les deux premières instructions du bloc ne sont pas à exécuter. Il y a donc une petite perte de performance. Certains CPU, comme l'IBM RS/6000, ont la machinerie charger un bloc de N instructions même s'il n'est pas aligné. La machinerie en question se trouve dans le cache d'instruction et est tout sauf simple à expliquer,ce qui fait que je me permets de la passer sous silence. Un détail, cependant : elle ne permet pas de charger des blocs qui sont à cheval sur deux lignes de cache. ===Le décodage parallèle des instructions=== Pour le décodage des instructions, plusieurs instructions sont décodées en même temps. On parle alors de '''décodage parallèle'''. Pour ce faire, un processeur superscalaire contient plusieurs décodeurs, chacun pouvant décoder une instruction en parallèle des autres. Prenons par exemple un processeur superscalaire à 4 voies : le décodage se fera dans quatre décodeurs séparés, qui fonctionneront en parallèle. Cependant, cette implémentation ne marche bien que pour les processeurs RISC, les processeurs CISC devant faire avec un microcode gourmand en circuits. Les processeurs CISC utilisent des décodeurs hybrides, avec un microcode qui complémente un décodeur câblé. Dupliquer le microcode aurait un cout en transistors trop important, ce qui fait que seuls les décodeurs câblés sont dupliqués. Les CPU CISC superscalaires disposent donc de plusieurs décodeurs simples, capables de décoder les instructions les plus courantes, avec un seul microcode. La conséquence est qu'il n'est pas possible de décoder deux instructions microcodées en même temps. Par contre, il reste possible de décoder plusieurs instructions non-microcodées ou une instruction microcodée couplée à une instruction non-microcodée. Vu qu'il est rare que deux instructions microcodées se suivent dans un programme, le cout en performance est extrêmement mineur. Il faut noter qu'une instruction correspond à une ou plusieurs µops. Il est donc possible de charger 5 instructions et de se retrouver avec 8 µops en sortie des décodeurs. Et c'est la source d'une distinction très importante. Plus haut, j'ai parlé de processeurs superscalaires à N voies. La terminologie peut avoir deux interprétations. La première est que le processeur charge/décode N instructions à la fois. Une seconde est qu'il est capable d'exécuter/émettre N µops en même temps. Les deux ne sont pas équivalentes, vu qu'un processeur superscalaire peut, par exemple, charger/décoder N instructions et émettre/exécuter N+2 µops. La distinction est particulièrement importante sur les processeurs superscalaires larges. Certains d'entre eux sont capables de charger 5 instructions et d'en émettre le double ! Un exemple classique était les processeurs Athlon d'AMD, qui pouvaient décoder deux instructions par cycle, mais émettre 4 µops par cycle. Ce n'était pas un problème, car beaucoup d'instructions, même basiques, étaient décodées en 2 µops. ===L'émission parallèle des instructions=== La superscalarité modifie en profondeur l'unité d'émission, notamment au niveau de son interface. Avant, elle recevait une micro-opération sur son entrée, et fournissait la micro-opération émise sur une sortie. Et cela vaut aussi bien pour une unité d'émission simple, un ''scoreboard'', une fenêtre d'instruction ou des stations de réservation. Mais avec l'émission multiple, les sorties et entrées sont dupliquées. Pour la double émission, il y a deux entrées vu qu'elle doit recevoir deux micro-opération décodées/renommées, et deux sorties pour émettre deux micro-opérations. Dans ce qui suit, nous ne parlerons pas d'entrée et de sortie, mais de '''ports de décodage''' et de '''ports d'émission'''. Les ports de décodage reçoivent des micro-opérations provenant de l'unité de décodage, les ports d'émissions sont pour les micro-opérations émises. Les ports d'émission injectent les micro-opérations émises dans le pipeline, elle sont propagée au banc de registre, aux ALUs, etc. Un processeur superscalaire a forcément plusieurs ports d'émission (et de décodage). Si l'interface de l'unité d'émission change, l'intérieur est tout autant modifié. Sur un CPU non-superscalaire, l'unité d'émission reçoit une µop "candidate à l'émission", et détecte les dépendances entre avec les µops en vol, en cours d'exécution dans le pipeline. Et c'est toujours le cas sur les processeurs superscalaires : l'unité d'émission détecte les dépendances entre les µops candidates et les µops en vol. Mais avec la siperscalarité, elle doit en plus tester les dépendances entre les µops candidates. Si l'unité d'émission est un ''scoreboard'', il doit détecter les dépendances entre instructions émises simultanément et les sérialiser si besoin. Avec une fenêtre d'instruction, il n'y a pour ainsi dire rien à faire, vu que les dépendances sont éliminées par le renommage de registre et que les signaux de réveil gèrent les dépendances RAW. C'est la raison pour laquelle les processeurs superscalaires modernes n'utilisent pas de ''scoreboard''. ==Les processeurs implémentés avec plusieurs pipelines== Nous venons de voir comment la superscalarité impacte le ''front-end'', il est maintenant de voir ce qu'il en est de l'unité d'émission et des ''back-ends''. Un processeur superscalaire peut utiliser l'exécution dans le désordre et le renommage de registres, mais ce n'est pas une obligation. Historiquement, les premiers processeurs superscalaires n'utilisaient pas d'exécution dans le désordre. Le meilleur exemple est celui du Pentium 1 et 2 d'Intel. Ils étaient tous deux des processeurs superscalaires, mais n'avaient pas d'exécution dans le désordre, qui est apparue sur le Pentium 3. les explications sont plus simples si on met de côté l'exécution dans le désordre. Vous vous dites que l'implémentation d'un processeur superscalaire n'est pas la même suivant que le CPU utilise l'exécution dans le désordre, ou non. Mais dans les faits, la distinction est plus subtile que ca. Pour l'expliquer, je vais utiliser une classification originale, qui sépare les processeurs superscalaires en trois sous-types. La distinction se fait sur la manière dont la superscalarité est implémentée en matériel.La classification que j'utilise distingue : * Les processeurs avec plusieurs pipelines, sans exécution dans le désordre. * Les processeurs superscalaires avec un pipeline dynamique. * Les processeurs avec des fenêtres d'instruction décentralisées. La distinction n'est pas qu'une question d'implémentation matérielle. Elle impacte aussi le ratio entre instructions chargées/décodées et instructions émises. Vous vous dites que les deux sont égales, mais ce n'est le cas que sur les processeurs avec N pipelines ! Sur les autres processeurs, il est possible de décoder 4 instructions, mais d'en émettre maximum 6 par cycle ! Et sur les CPU avec des fenêtres d'instruction décentralisées, il y a aussi une différence entre le nombre d'instructions qui sont ''dispatch'' et ''schedule''. La catégorie la plus simple est celle des '''processeurs avec plusieurs pipelines'''. Ce sont des processeurs superscalaires sans exécution dans le désordre. Ils partent d'un processeur non-superscalaire, et ajoutent un second pipeline, en sortie de l'unité d'émission. Le second pipeline permet l'émission parallèle d'une seconde instruction. Le second pipeline n'est pas identique au pipeline originel, sur plusieurs points. Premièrement, il n'a pas exactement les mêmes circuits. Le second pipeline est généralement composé d'une unité de calcul, d'une FPU, éventuellement d'un second banc de registres, guère plus. Il permet d'exécuter une seconde opération de calcul, pas plus, ce qui est à l'origine de contraintes d'appariemment assez fortes. De plus, il n'a pas forcément la même longueur, il peut avoir des étages en moins ou en plus. Il s'agit de la forme la plus simple de processeur superscalaire et nous allons leur consacrer cette section. ===La double émission entière-flottante=== L'exemple le plus simple utilise un second pipeline pour les opérations flottantes, séparé du pipeline originel. L'ajout d'un pipeline flottant permet d'émettre une micro-opération entière en même temps qu'une micro-opération flottante, ce qui s'appelle la '''double émission entière-flottante'''. La micro-opération entière s'exécute dans l'ALU entière, la micro-opération flottante dans la FPU. Les exemples les plus notables sont le PA-RISC 7100, le POWER 1 (RIOS .& et .9) et le PowerPC 601. L'Apollo DN10000 et l'Intel i860 utilisaient aussi cette forme de double émission, mais étaient des prototypes de CPU VLIW. [[File:Double émission entière-flottante.png|centre|vignette|upright=2|Double émission entière-flottante]] Pour bien expliquer les choses, prenons le processeur illustré ci-dessous, avec deux ''back-end'' séparés : un pour la FPU, et un autre pour le reste. Les instructions entières et mémoire passent toutes par le ''back-end'' généraliste. [[File:CPU non-superscalaire basique, avec une FPU.png|centre|vignette|upright=1.5|CPU non-superscalaire basique, avec une FPU]] Les deux ''back-end'' sont des pipelines séparés, mais il faut que l'unité d'émission soit spécialement conçue pour en profiter. Un processeur simple émission peut démarrer une seule µop par cycle : soit la µop est envoyée au ''back-end'' flottant, soit elle est envoyée au ''back-end'' général. Avec la double émission entière-flottante, le processeur peut envoyer une µop dans chaque pipeline, à chaque cycle. Vu que la FPU et son banc de registre forment déjà un pipeline séparé, les seules modifications se font au niveau du ''front-end'', unité d'émission inclue. {| |- |[[File:CPU simple émission avec une FPU.png|centre|vignette|upright=1.5|CPU simple émission avec une FPU.]] |[[File:Double émission entière flottante avec un second pipeline.png|centre|vignette|upright=1.5|CPU avec un second pipeline flottant.]] |} Notez que le terme double émission ''entière''-flottante est un peu trompeur. En réalité, la technique permet d'émettre une instruction flottante avec n'importe quelle autre instruction non-flottante, avec des restrictions pour les branchements. Il est par exemple possible d'exécuter un calcul flottant en parallèle d'un accès mémoire. Sur les CPU 32 bits, il est possible de faire les multiplications dans la FPU. Pour rappel, la FPU contient un multiplieur entier 53-54 bits, pour multiplier les mantisses de deux flottants 64 bits, qu'on peut utiliser pour faire les multiplications entières 32 bits. Cela permet d'émettre une multiplication en même temps qu'une opération entière dans l'ALU. Ainsi, le CPU pouvait émettre deux instructions : une opération entière de base, avec soit une multiplication, soit une opération flottante. En clair, une émission de type INT + (MUL /FLOAT). Par contre, on perd la possibilité d'émettre une multiplication entière avec une opération flottante, mais c'était un compromis qui en valait la peine. Le CPU PA-RISC 7100 implémentait cette optimisation. Il s'agissait d'un CPU double émission entière-flottante, qui utilisait donc un partitionnement assez simple. Le combo partitionnement + reconditionnement permettait ici de gagner en performance, pour un cout en matériel très limité. D’autant plus limité que le jeu d'instruction prévoyait le reconditionnement de la FPU. Les multiplications ne lisaient pas leurs opérandes dans les registres entiers, mais dans les registres flottants. ===La double émission entière=== Une autre possibilité ajoute un second pipeline, capable d'exécuter des opérations entières simples, appelé le pipeline entier. Le processeur supporte alors la '''double émission entière''', à savoir qu'ils peuvent émettre une opération entière en plus d'une autre instruction. L'opération entière en question est une opération simple, comme une addition, une soustraction, une opération bit à bit, une comparaison, etc. Les exemples classiques sont les processeurs Intel i960 et Pentium. L'implémentation était différente entre l'i960 et le Pentium : Le Pentium avait une seconde ALU entière, alors que le i960 rusait. Le Pentium dupliquait l'ALU entière, pour créer un second pipeline entier. Sur le Pentium originel, les contraintes d'appariement étaient assez strictes. Le Pentium avait une FPU, mais ne pouvait pas l'utiliser en double émission. Il lui était impossible d'exécuter une opération flottante en même temps qu'une opération entière (branchements inclus). Le Cyrix 6x86 était une sorte de Pentium produit par l'entreprise Cyrix, avec une microarchitecture différente. Il avait des contraintes d’appariement réduites. Par exemple, il pouvait émettre une opération flottante en parallèle d'une opération entière. Nous détaillerons ces processeurs dans le prochain chapitre, qui porte justement sur les CPU superscalaires x86. [[File:CPU simple et double émission avec un second pipeline entier.png|centre|vignette|upright=2.5|CPU simple et double émission avec un second pipeline entier.]] L'Intel i960 n'avait pas de FPU, juste une ALU entière et une unité mémoire. Il utilisait l'unité mémoire comme une seconde ALU entière limitée. Pour rappel, une unité mémoire contient une AGU, à savoir une ALU spécialisée dans les calculs d'adresse. Elle est capable de faire des additions, soustractions, et quelques décalages limités (des décalages par 2, 4, 8, pas plus). L'Intel i960 ajoutait des connexions entre AGU et banc de registre, afin que celle-ci serve de seconde unité de calcul. L'AGU est déjà reliée aux deux ports de lecture, pour lire les opérandes du calcul d'adresse, il suffit de la connecter sur le port d'écriture. Le processeur peut alors émettre deux instructions/µops entières, sous certaines conditions. [[File:Emission multiple des opérations entières, double émission.png|centre|vignette|upright=2|Émission multiple des opérations entières, double émission.]] ==Les processeurs superscalaires dynamiques== Les processeurs précédents, avec 2 pipelines, avaient une unité d'émission assez simple. Elle avait deux ports de décodage, et deux ports d'émission. Le nombre de ports de décodage et d'émission étaient identiques. Vous vous dites que c'est naturel, et intuitif, que c'est normal pour un processeur superscalaire. Mais en réalité, la majorité des processeurs superscalaires a plus de ports d'émission que de décodage. Expliquer pourquoi va cependant nous demander de parler de comment la superscalarité exploite les unités de calcul. Posons-nous cette question : de combien d'unités un processeur superscalaire a besoin ? Les unités en question ne sont nécessairement des unité de calcul, l'unité mémoire et l'unité de branchement comptent aussi. La réponse dépend du nombre de voies. Si le ''front-end'' charge et décode un paquet de N instructions, exécuter ces N instructions simultanément demandera là aussi au moins N unités de calcul. Notez le terme : ''au moins'', car c'est là tout l'enjeu de cette section. La réponse dépend aussi des contraintes d’appariement. Pour un CPU superscalaire "parfait", sans contraintes d’appariement, toutes les unités doivent être dupliquées, y compris l'unité mémoire. Un processeur à double émission parfaite devrait dupliquer l'ALU entière, le ''barrel shifter'', le circuit multiplieur, la FPU, l'unité mémoire, et l'unité de branchement, ce qui ferait passer de 6 unités à 12. Le cout en circuit est énorme, même pour de la double émission ! Et pour un CPU à N pipelines, le cout en circuit encore plus prohibitif ! En pratique, les processeurs superscalaires qui font ça sont très rares. La majeure partie des processeurs superscalaires utilisent d'autres techniques : le partitionnement, la duplication partielle des ALUs, la duplication des autres unités. ===Le partitionnement des unités fonctionnelles=== Les CPU superscalaires les plus simples ne dupliquent pas leurs unités de calcul, ils se contentent d'exploiter les unités existantes. Et pour expliquer comment, nous allons partir de l'exemple d'un processeur non-superscalaire contient une ALU entière, une FPU, une unité mémoire et une unité de branchement. Les quatre unités peuvent fonctionner en parallèle, à savoir qu'une unité peut exécuter une instruction à elle toute seule, sans interagir avec les autres. Cela suffit pour créer un processeur quadruple émission, qui peut émettre en même temps : une opération entière, une opération flottante, un branchement, un accès mémoire. Le principe marche aussi sur un processeur avec moins de 4 unités, par exemple sans FPU et/ou sans unité de branchement, ou avec au contraire des unités en plus. L'idée générale est d’émettre une µops séparée dans chaque unité fonctionnelle. Utiliser ainsi les unités existantes, pour rendre un CPU superscalaire, s'appelle le '''partitionnement superscalaire'''. Le terme n'est pas beaucoup utilisé en-dehors de quelques vieux articles académiques. Le partitionnement est fortement dépendant du jeu d'instruction, car il demande de bien séparer instructions entières, flottantes, branchements et instructions mémoire. Et autant c'est bien le cas sur les CPU RISC, autant les CPU CISC ne sont pas dans ce cas. Les CPU CISC ont des instructions ''load-op'', qui effectuent une instruction arithmétique et une µop mémoire. Et ces instructions sont très compliquées à implémenter avec le partitionnement. Et au-delà de ça, les CPU CISC ont beaucoup d'instructions qui sont décodées en plusieurs µops, à exécuter dans un ordre bien précis. On peut pas émettre les µops en question simultanément. Le partitionnement a un cout en circuit très limité, pour un gain en performance lui aussi limité. Pour reprendre le CPU de l'exemple précédent, il est rare que l'on ait à exécuter ces quatre instructions en même temps. En pratique, les combinaisons fréquentes sont les combinaisons calcul + accès mémoire, calcul + branchement, calcul entier + calcul flottant. Utiliser une quadruple émission de ce type n'est donc pas une bonne idée, alors que la double émission parait plus adaptée. Et c'est là que la différence entre ports d'émission et de décodage devient intéressante. L'idée est d'avoir une unité d'émission avec deux ports de décodage, et 4 port d'émission (un par unité). Les contraintes d’appariement varient alors suivant le processeur, mais la règle générale est que si deux micro-opérations tombent dans deux unités différentes, on peut les émettre en même temps. En clair, on peut émettre une opération entière avec une opération flottante, une opération entière avec un branchement, un accès mémoire avec un branchement, etc. Et cela rend le partitionnement bien plus intéressant que prévu. Les tout premiers CPU superscalaires étaient des CPU double émission, qui utilisaient le partitionnement avec : une ALU entière, une unité mémoire et une FPU. Nous parlerons de '''partitionnement INT-FLOAT-MEM'''. L'avantage sur la double émission flottante est que les combinaisons INT + MEM et FLOAT + MEM étaient autorisées. L'intérêt était d'émettre une opération en parallèle d'un accès mémoire, ce qui est un doublet d'instruction assez fréquent. Les exemples classiques sont l'HyperSPARC, de l'alpha 21064 et du PowerPC 603. Les contraintes d’appariement variaient grandement suivant le processeur. Le PowerPC 603 implémentait une double émission "parfaite". C'est à dire que toutes les combinaisons possibles à deux instructions étaient possibles, que ce soit INT + FLOAT, INT + MEM, FLOAT + MEM. En plus de cette double émission "parfaite", le CPU utilisait la technique dite de ''branch folding'', qui permettait d'exécuter un branchement sans utiliser d'unité de calcul, ce qui fait qu'il était qualifié de ''CPU 2-way + Branch''. A l'opposé, le processeur alpha 21064 avait pas mal de contraintes d’appariement, à savoir que certaines combinaisons de deux instructions étaient interdites, bien que compatibles avec les unités disponibles. Une des raisons est que le banc de registre n'avait pas assez de ports de lectures, ce qui fait que certaines combinaisons avec une écriture mémoire n'étaient pas permises. Les concepteurs avaient volontairement inséré ces contraintes afin de simplifier le design du processeur. Avant de poursuivre, précisons que la FPU regroupe un additionneur flottant et un multiplieur flottant. Et ce fait peut être exploité pour approfondir le partitionnement. La '''double émission FADD/FMUL''' permet d'émettre une addition flottante en même temps qu'une multiplication flottante. Elle a été utilisée sur les processeurs x86, Alpha et SPARC. Les autres circuits de calcul flottant sont souvent regroupés avec l'additionneur. La division est parfois e même port que le multiplieur flottant, parfois sur celui de l'additionneur (quand elle est microcodée et exécutée en enchainant les soustractions). ===La duplication des ALU entières=== Le partitionnement est une technique élégante, simple, facile à implémenter et au cout en circuits modeste. Mais il faut avouer que cela ne donne pas des CPU superscalaires très efficaces. La raison est que le partitionnement permet d'émettre des paires, triplets, quadruplets de micro-opérations qui sont rares en pratique. Les combinaisons les plus fréquentes en pratique sont des paires/triplets d'opérations entières, souvent des paires/triplets d'additions ou d'opérations entières simples. Il est donc primordial d'émettre plusieurs opérations entières simultanément. Le fait d'émettre et d'exécuter plusieurs opérations entières en même temps s'appelle l''''émission entière multiple'''. Pour exécuter la seconde/troisième opération entière, il faut dupliquer les ALU entières (ou réutiliser l'AGU comme ALU entière comme le fait l'Intel i960). Le processeur doit avoir deux ALU entières pour de la double émission entière, trois pour de triple émission entière, etc. De plus, il faut ajouter des ports au banc de registre, pour lire deux fois plus d'opérandes et décrire deux résultats. Et enfin, il faut rajouter un port d'émission pour pouvoir émettre une micro-opération entière en plus. [[File:Processeur non-superscalaire basique 03.png|centre|vignette|upright=2|Processeur non-superscalaire basique]] La tendance est de multiplier les ALU entières, mais pas le circuit multiplieur. Le cout en transistors est alors grandement réduit, au prix de l'apparition de dépendances structurelles. Le CPU peut émettre deux additions en même temps, ou une addition et une multiplication, mais pas deux multiplications. Il en est de même avec le ''barrel shifter'', qui n'est pas dupliqué. Il est donc possible d'émettre un décalage avec une opération de base, mais pas deux décalages en parallèle. Mais ces contraintes d’appariement ne sont pas un problème, car il est rare de devoir émettre deux multiplications/décalages en même temps. La plupart des programmes sont majoritairement remplis d'additions, avec des multiplications assez rares et des décalages qui le sont encore plus. La moyenne est proche d'une multiplication pour 4/5 additions. Vu que des multiplications consécutives sont très rares, dupliquer le multiplieur est presque inutile. Disposer de plusieurs circuits multiplieurs ou de plusieurs ''barrel shifters'' serait donc un cout en circuits qui ne servirait que rarement et n'en vaut pas la chandelle. Il faut noter que sur les CPU 32 bits, les multiplications peuvent être exécutées dans la FPU ou dans un multiplieur séparé, tout dépend de si le CPU exécute les multiplications dans la FPU ou pas. {|class="wikitable" |- ! Voie d'émission n°1 !! Voie d'émission n°2 !! Voie d'émission n°3 |- | ALU entière || ALU entière || FPU |} Reste à connecter les unités de calcul à l'unité d'émission (indirectement, car il y a des étages de pipeline entre les deux). Une implémentation basique utilisee un port d'émission par unité. Prenons l'exemple d'un CPU superscalaire avec 4 ALU entières, une FPU, et une unité mémoire. Vu qu'il y a 6 unités, cela fait donc 6 ports d'émission. L'exemple historique le plus impressionnant est celui du Motorola 88110, un CPU double émission qui possède 10 unités. Il contient deux ALU entières, un circuit multiplieur, un circuit diviseur, une unité de manipulation de bits, une unité mémoire, un additionneur flottant, et deux unités graphiques pour des calculs impliquant des pixels. Le multiplieur est utilisé à la fois pour les multiplications entières, flottantes et le produit de deux pixels. Idem pour le diviseur, qui sert pour les divisions entières et flottantes. Et chaque unité a son propre port d'émission. La conséquence est que les contraintes d'appariement du Motorola 88110 sont simples : tant que deux µops atterrissent dans des unités séparées, la paire est permise. Il est par exemple possible d'émettre simultanément deux opérations entières simples, vu qu'il y a deux ALU. Par contre, impossible d'émettre simultanément deux multiplications, deux divisions, deux opérations flottantes, deux manipulations de bit, deux accès mémoire, etc. De plus, le partage du multiplieur fait qu'on ne peut pas émettre une multiplication entière avec une multiplication flottante. Idem avec les divisions, vu que le diviseur est aussi partagé. Ce sont les seules contraintes. ===Le partage des ports d'émission=== La présence d'un circuit multiplieur et d'un ''barrel shifter'' pose des problèmes pour le partitionnement. Il est possible d'avoir un port d'émission dédié au multiplieur, et un autre au ''barrel shifter'', qui sont séparés de ceux pour l'ALU entière. Il est alors possible d'émettre une opération entière, une multiplication et un décalage, simultanément. Nous parlerons de '''partitionnement entier''' pour désigner cette possibilité. [[File:Partitionnement superscalaire entier.png|centre|vignette|upright=2|Partitionnement superscalaire entier.]] Le partitionnement entier est cependant très rare, car le gain en performance serait trop faible au regard des couts en circuit. Quelques processeurs historiques l'utilisaient, mais ce n'est plus le cas de nos jours. La raison est que les multiplications et les décalages sont assez rares dans le code, ce qui fait que le port d'émission associés sont sous-utilisés. Et un port d'émission a un cout en circuit assez important. Et c'est sans compter qu'ajouter des ports sur le banc de registres juste pour un multiplier sous-utilisé n'en vaut pas la peine. L'idéal serait de trouver un moyen de réduire le nombre de ports d'émissions, sans réduire le nombre d'unités de calcul. [[File:Emission multiple des opérations entières, implémentation naive.png|vignette|Émission multiple des opérations entières, implémentation naive]] La solution consiste à '''partager les ports d'émission''', à savoir connecter un port d'émission à plusieurs unités de calcul. L'exemple classique est justement lié aux circuits multiplieur et ''barrel shifters''. L'idée est que le multiplieur et le ''barrel shifter'' sont mis sur le même port d'émission qu'une ALU entière. Prenons par exemple le processeur à double émission entière illustré ci-contre. Un port d'émission est relié à une ALU entière seule, et l'autre port d'émission alimente la seconde ALU entière et le multiplieur. La conséquence est que le processeur peut émettre deux opérations entières, dont une multiplication. Si on avait utilisé un port d'émission dédié au multiplieur, il aurait été possible d'émettre deux opérations entières et une multiplication. On perd donc une voie, mais on gagne en flexibilité et on économise des circuits. Notons que ce partage signifie aussi que le multiplieur partage les ports sur le banc de registre, ce qui fait une double économie. [[File:Emission multiple des opérations entières, implémentation courante.png|centre|vignette|upright=1.5|Émission multiple des opérations entières, implémentation courante]] En général, le ''barrel shifter'' et le multiplieur sont placés sur deux voies d'émission séparés, ce qui permet d'émettre un décalage et une multiplication en même temps. Les mettre sur le même port ne le permettrait pas. {|class="wikitable" |- ! Voie d'émission n°1 !! Voie d'émission n°2 !! Voie d'émission n°3 |- | * ALU entière * Multiplieur | * ALU entière * Barrel shifter | FPU |- | * Additions, soustractions, opérations bit à bit, etc. * Multiplication | * Additions, soustractions, opérations bit à bit, etc. * Décalages et rotations | Opération flottante |} Mais le partage des ports d'émission peut appliquer à n'importe quelle unité. Prenons l'exemple d'un CPU superscalaire avec 4 ALU entières, une FPU, et une unité mémoire. Vu qu'il y a 6 unités, cela fait donc 6 ports d'émission. Cependant, il est rare qu'un bloc de 6 instructions contienne exactement 4 opérations entières, une opération flottante, et un accès mémoire. Beaucoup de ports d'émission seraient sous-utilisés. La solution est de partager des ports d'émission, pour passer à la quadruple émission. Le CPU peut alors répartir 4 instructions sur les ports d'émission adéquats, à chaque cycle. Le CPU a donc 4 ports de décodage et 6 ports d'émission. Une implémentation classique permet n'importe quelle combinaison de 4 instructions compatible avec les 6 unités. Par exemple, il devient possible d'émettre simultanément 4 additions, la combinaison 2 * INT + FLOAT + MEM, 3 * INT + MEM, et bien d'autres. L'exemple relie une ALU entière, la FPU et l'unité mémoire sur le même port. Le CPU a alors quatre ports d'émission, qui ne sont pas "identiques". Trois d'entre eux sont reliés à une ALU entière chacun, rien d'autre. Le quatrième est lui relié à une ALU entière, la FPU et l'unité mémoire. On économise alors deux ports d'émission, mais cela entraine l'apparition de contraintes d’appariement. Le CPU peut émettre 3 opérations entières + n'importe quelle autre opération. Mais les autres combinaisons sont interdites. Le CPU peut émettre 6 opérations entières simultanément, grâce à 6 ALU entières. Les accès mémoire ou opérations flottantes ne viennent pas en plus, mais en remplacement des opérations entières. Regrouper plusieurs ALU sur un même port d'émission est donc à l'origine de dépendances structurelles, de nouvelles contraintes d’appariement. Dans certaines situations de '''''port contention''''', on a assez d'ALU pour exécuter N micro-opérations, mais la répartition des ALUs sur les ports l'empêche. Impossible d'émettre deux micro-opérations sur deux ALU si elles sont sur le même port d'émission. On peut réduire les cas de ''port contention'' en répartissant correctement les ALU sur les ports d'émission, mais sans jamais les supprimer complétement. Un exemple est celui du PowerPC 604. Il implémentait la quadruple émission, alors qu'il avait 5 unités de calcul. Mais deux unités partageaient un même port d'émission. Dans le détail, il avait deux ALU entières, un circuit multiplieur/diviseur, une FPU, une unité mémoire. La FPU et le multiplieur étaient sur le même port d'émission. En conséquence, le CPU pouvait émettre seulement les combinaisons INT + INT + MEM + (MUL/FLOAT). : Il est intéressant de voir l'exemple d'un processeur superscalaire parfait, sans contraintes d'appariement, à N voies. Pour cela, on part d'un processeur non-superscalaire, avec M unités de calcul. Un tel processeur peut s'implémenter avec N ports d'émission, chacun relié à M unités de calcul possibles, ce qui fait que les M unités de calcul sont dupliquées en N exemplaires. Si on prend par exemple un CPU non-superscalaire avec une ALU, une FPU, une unité mémoire et une unité de branchement, un port d'émission sera relié à une ALU, une FPU, une unité mémoire et une unité de branchement. ===La duplication des autres unités fonctionnelles=== Dupliquer les ALU entière est une première étape, mais il est aussi possible de dupliquer les autres unités fonctionnelles. Par exemple, il est possible de dupliquer l'unité mémoire ou l'unité de branchement. Reste à voir quelles unités dupliquer, et pourquoi. Précisons que dans ce qui va suivre, nous allons parler de la duplication des unités en général, pas seulement des unités mémoire/branchement. Nous allons continuer de parler de la duplication des ALU entières dans ce qui suit, mais de manière plus générale. Pour un processeur à N voies, il est théoriquement possible de dupliquer toutes les unités en N exemplaires. Mais ce n'est jamais fait en pratique, car le cout en circuits serait trop important et le gain en performance trop faible. Mieux vaut dupliquer certaines unités bien précises, quitte à avoir des contraintes d'appariement. Et à ce petit jeu, toutes les duplications ne se valent pas, certaines sont plus rentables en matière de performance, le cout en circuit n'est pas le même selon l'unité, etc. La rentabilité en performance dépend surtout des contraintes d'appariement. Les contraintes d'appariement entrainent une baisse de performance significatives si elles interdisent des paires d'instructions fréquentes. Aussi, les opérations très fréquentes, souvent appairées, gagnent à dupliquer l'unité associée. Et au contraire, les unités rarement utilisées ne doivent pas être dupliquées. Il se trouve que les unités les plus utilisées sont les ALU entières, suivies par les unités mémoire et l'unité de branchement. Le cout en circuit n'est aussi pas le même. Dupliquer les ALU entière est trivial, car elles ont un budget en transistors assez faible. Les circuits multiplieurs, diviseurs, et les ''barrel shifter'' ont eux un cout en transistor plus élevés. Si on devait faire un classement, on aurait : ALU entière < ''barrel shifter'' < multiplieur. Pour les unités mémoire, le cout en transistor est là aussi conséquent. Le résultat final est que dupliquer les ALU entières est privilégié sur le reste, comme l'a vu dans la section précédente. Les unités mémoire arrivent en seconde place, suivie par les multiplieurs/''barrel shifter'' à la troisième place, et les unités de branchement à la quatrième place. Les CPU superscalaires larges ont donc tendance à multiplier les unités mémoire en second. Les multiplieurs et ''barrel shifters'' sont eux aussi dupliqués sur les CPU superscalaires larges, mais cela vient souvent après avoir dupliqué les unités mémoire. La raison est que les accès mémoire sont critiques pour la performance des CPU à exécution dans le désordre, pas les multiplications. Pour les unités de branchement, une subtilité complique le fait d'en installer plusieurs. Que faire quand deux branchements sont pris ? La solution la plus simple est de ne pas se trouver dans cette situation : on n'utilise qu'une seule unité de branchement et on n’exécute qu'un seul branchement par cycle. La majorité des processeurs superscalaires étroits font ainsi. Les processeurs superscalaires larges ont cependant deux unités de branchements, mais ils n'acceptent qu'un seul branchement pris à la fois. Si les deux branchements pris sont exécutés en même temps, le processeur n’exécute que le plus récent, dans l'ordre du programme. ===Les CPU superscalaires mélangent les trois techniques=== Les CPU superscalaires utilisent toutes les techniques à la fois : partitionnement, duplication de l'ALU entière, duplication des autre unités fonctionnelles. Chaque processeur utilise ces trois stratégies à sa sauce, en fonction de son budget en transistor. Les quatre techniques ont cependant un cout en circuit différent, et donnent des gains de performances eux aussi forts différents. * Le '''partitionnement superscalaire''' a un cout en circuit modéré, car on n'a pas besoin de dupliquer d'ALU. * La '''duplication des ALU entières''' est la technique la plus importante : elle a un cout en circuit modéré, et donne des gains en performance impressionnants. * La '''duplication des autres unités''' a un cout en circuit variable, pour des gains en performance eux aussi variables. * Le '''partage des ports d'émission''' réduit le cout en circuit du CPU, pour des pertes de performances mineures si bien fait. L'usage du partitionnement seul, sans duplication, était surtout le fait des processeurs commercialisés entre les années 1989 et 1993. Le partage des ports d'émission a lui aussi mis un peu de temps pour s'imposer. Globalement, les processeurs superscalaires ont évolués progressivement, pour se rapprocher d'une sorte de standard. Mais pour comprendre ce qu'est ce standard, et en quoi les CPU historiques s'en sont progressivement rapprochés, il faut étudier l'histoire des CPU superscalaires. L'histoire des CPU superscalaires a commencé dès 1960, mais est resté un projet de recherche pendant plusieurs décennies. De nombreux designs ont été conçus, mais abandonnés avant la production, comme l'ACS-1 d'IBM. Les années 1980 ont vu l'arrivée de prototypes de processeurs VLIW, mais qui n'étaient pas superscalaires. Par exemple, l'Apollo DN10000 et l'Intel i860 pouvaient émettre deux instructions à la fois, sous conditions, mais demandaient que le compilateur fasse une bonne partie du travail. Les premiers CPU superscalaires commercialisés sont sortis en 1989. Deux CPUs superscalaires sont sortis cette année là : le RS/6000 d'IBM et l'i960CA d'Intel. Puis, la compétition s'est aussi lancée dans la bataille. Les CPU commercialisés avant l'année 1993 étaient à double émission, à l'exception du SuperSPARC qui utilisait la triple émission. Les modèles après 1993 hésitaient entre double et quadruple émission. Dans le tableau ci-dessous, les processeurs quadruple émission sont en bleu, les triple émission sont en rouge, les autres ne sont pas en couleur. {|class="wikitable" |- ! !! IBM !! HP PA-RISC !! SPARC !! Intel!! Alpha !! Autres |- ! 1989 | RS/6000 || || || i960CA || || |- ! 1990 | || || || || || |- ! 1991 | || || || || || Motorola 88110 |- ! 1992 | RSC || PA 7100 || class="f_rouge | SuperSPARC || || 21064 || |- ! 1993 | PPC 601 || || HyperSPARC || Pentium || || |- ! 1994 | class="f_bleu | POWER 2 || PA 7200 || || || class="f_bleu | 21164 || |- ! 1995 | 603/604 || class="f_bleu | PA 8000 || class="f_bleu | UltraSPARC 1 || Microarchitecture P6 (+ concurrence AMD) || || class="f_bleu | MIPS R1000 |- ! 1996 | class="f_bleu | 620 || || || || class="f_bleu | 21264 || |- ! ... || ... || ... || ... || ... || ... || ... |} Les premiers CPU à double émission sont assez représentatifs des CPU à double émission modernes. Il faut dire que se limiter à deux voies ne permet pas de faire beaucoup de choses. Voici le tableau chronologique disant quel CPU utilisait quelle technique. * Les CPU en vert utilisaient la double émission entière-flottante. * Les CPU en rouge utilisaient la double émission entière "pure". * Les CPU en jaune utilisaient un ''partitionnement complet'' entre ALU, FPU et unité mémoire. * Les CPU sans couleur utilisaient un grand nombre d'unités, avec beaucoup de ports d'émission. * Les CPU barrés utilisent la triple ou quadruple émission et seront vu dans la section suivante. {|class="wikitable" |- ! !! IBM !! HP PA-RISC !! SPARC !! Intel!! Alpha !! Autres |- ! 1989 | class="f_vert | RS/6000 || || || class="f_rouge | i960CA || || |- ! 1990 | || || || || || |- ! 1991 | || || || || || Motorola 88110 |- ! 1992 | class="f_vert | RSC || class="f_vert | PA 7100 || <s>SuperSPARC</s> || || class="f_jaune | 21064 || |- ! 1993 | class="f_vert | PPC 601 || || class="f_jaune | HyperSPARC || class="f_rouge | Pentium || || |- ! 1994 | <s>POWER 2</s> || PA 7200 || || || <s>21164</s> || |} C'est en 1994, que la quadruple émission est arrivée et que les CPU ont essayé divers implémentations. Il y avait alors trois stratégies pour la double/triple/quadruple émission, qui partageaient des problèmes similaires. La première implémentation dupliquait les ALUs et les FPUs, ce qui permettait d'émettre deux opérations flottantes avec deux opérations entières. Le POWER 2 dupliquait ses FPUs à la perfection, alors que le 21264 séparait l'additionneur et le multiplieur flottant. Les accès mémoire étaient traités comme des opérations entières et le processeur pouvait souvent en émettre deux à la fois. Mais une telle organisation n'est plus utilisée de nos jours, car, sans instructions flottantes à exécuter, elle est équivalente à de la double émission. La seconde implémentation utilisait deux ALU entières (dont un multiplieur), une unité mémoire et une FPU.Cette organisation est intéressante, mais encore trop stricte pour donner de bons résultats, les quadruplets avec exactement deux opérations entières, un accès mémoire et une opération flottante étant trop rares. L'usage de ces 4 unités en double ou triple émission a été utilisé sur le PA 7200 et le SuperSPARC. Malheureusement, en plus de problèmes au niveau de son cache, le SuperSPARC avait de nombreuses contraintes d’appariement, car le banc de registre n'avait pas assez de ports pour alimenter certaines combinaisons d'instructions. De plus, s'il émettait une multiplication, il ne pouvait pas émettre de seconde µop. Enfin, une troisième implémentation utilisait un grand nombre d'unités fort différentes, bien plus que quatre, avec au minimum deux ALU entières. Prenons l'exemple de l'UltraSPARC, qui avait : deux ALU entières, une unité mémoire, une unité de branchement, un additionneur flottant, un multiplieur flottant, un diviseur flottant (aussi utilisé pour le calcul des racines carrées), et 2 unités graphiques pour les calculs sur des pixels. En tout, cela fait 9 unités différentes, pour de la quadruple émission. Dans le même genre, les processeurs MIPS R8000 et R10000 avaient une implémentation plus moderne, dans le sens où toutes les unités étaient dupliquées. Le CPU avait deux ALU, deux FPU et deux unités mémoire. Il pouvait donc émettre quatre instructions à la fois, tant que le paquet collait avec ces six unités. Moitié opérations entières, moitié opérations mémoire, avec de quoi remplacer les opérations entières par des opérations flottantes. Le PA 8000, lui, dupliquait toutes ses unités en deux exemplaires, ce qui n'est pas l'idéal, aucun processeur moderne ne fait cela aujourd'hui. Le tout est résumé dans le tableau ci-dessous, avec : * en vert les CPU utilisant la première stratégie ; * en rouge les CPU utilisant la seconde stratégie ; * en jaune les CPU utilisant la troisième stratégie. {|class="wikitable" |+ Processeurs quadruple émission historiques |- ! Année !! Processeur !! Unités |- ! 1991 | class="f_jaune | Motorola 88110 || class="f_jaune | 2 ALU + MUL + DIV + BIT + MEM + FADD + 2 unités graphiques |- |- ! rowspan="2" | 1994 | class="f_vert | POWER 2 || class="f_vert | 2 ALU/MEM + 2 FPU (+ 2 Branchements via ''branch folding'') |- | class="f_rouge | 21164 || class="f_rouge | 2 ALU + FPU + MEM |- ! rowspan="3" | 1995 | PA 8000 || 2 ALU + 2 ''barrel shifters'' + 2 MEM + 2 FPU (FMA) + 2 FPU (DIV et SQRT) + 2 SIMD |- | class="f_jaune | UltraSPARC 1 || class="f_jaune | 2 ALU + MEM + BRANCH + FADD + FMULFDIV + 2 unités graphiques |- | MIPS R8000 et R10000 || 2 ALU + 2 FPU + 2 MEM |- ! rowspan="2" | 1996 | class="f_rouge | 620 || class="f_rouge | 2 ALU + FPU + MEM |- | class="f_vert | 21264 || class="f_vert | 2 ALU/MEM + 2 FPU (FADD + FMUL) |} Malheureusement, ces CPUs historiques n'avaient ni assez d'unités de calcul entières, ni d'unités mémoire, pour remplir la quadruple émission. Environ la moitié des instructions d'un programme correspond à des instructions entières. Pour en profiter, un processeur à N voies doit disposer d'au minimum N/2 ALU entières, l'idéal étant d'aller au-delà. Les CPU superscalaires historiques avaient 2 ALU pour de la quadruple émission, ce qui était très limite. En comparaison, les CPU superscalaires modernes mettent le paquet niveau ALU entière. La plupart des CPU x86 modernes descendent rarement sous les 2/3, la plupart remplit presque la totalité de la largeur du CPU. Pour donner un exemple, la microarchitecture Golden Cove d'Intel est un processeur à 6 voies, avec 5 ALU entières. Pour être précis, un programme informatique exécute environ 40% de son code dans des instructions arithmétiques, un autre 40% dans les accès mémoire, et un le reste pour... le reste. La conséquence est que les processeurs modernes dupliquent leurs unités mémoire. Par exemple, la microarchitecture ''Golden Cove'' d'Intel dispose de trois unités mémoire : deux spécialisées dans les écritures, trois dans les lectures. La microarchitecture ''Gracemont'' utilise la pentuple émission et peut émettre 4 µops entières et 4 µops mémoire. Notez que le ratio est équilibré entre opérations entières et mémoire, avec soit égalité, soit un avantage pour les ALU entières. Une autre régularité est que la largeur totale du processeur peut être occupée par des opérations entières/mémoire. Pour le dire autrement, sur un processeur quadruple émission, il pourra émettre un mix de 4 instructions entière/mémoire, avec zéro opération flottante. Idem avec des processeurs triples ou pentuple émission, voire plus. Si le processeur émet une opération flottante, elle remplace une opération entière. Prenons l'exemple des processeurs AMD K6, sortis en 1997, juste avant les AMD Athlon. Ils avaient 10 unités de calcul, 6 ports d'émission, 4 ports de décodage. Voici exactement comment étaient répartie les unités de calcul sur les ports d'émission. C'était des processeurs quadruple émission, ce qui fait que la moitié de la largeur du processeur sert pour des opérations entières, et l'autre moitié pour les accès mémoire. {|class="wikitable" |- ! Port d'émission n°1 !! Port d'émission n°2 !! Port d'émission n°3 !! Port d'émission n°4 !! Port d'émission n°5 !! Port d'émission n°6 ! |- | ALU entière || ALU entière || FPU || Unité de branchement || Unité LOAD || Unité STORE |- | || Multiplieur || || || || Unité de calcul d'adresse (instruction LEA) |- | || ''Barrel shifter'' || || || || Unité pour les instructions PUSH |} Les microarchitectures K7, K8 et K10, utilisées sur les anciens CPU AMD Athlon, sont un exemple moins représentatif. Ces microarchitectures utilisent le partage des ports d'émission au maximum. C'était des microarchitectures à triple émission, avec six ports d’émission. Il y avait trois ports d'émission pour les opérations flottantes, et trois ports d'émission pour les micro-opérations entières/mémoire. Il y avait trois ALU entières et trois unités mémoire, chaque ALU partageait un port d'émission avec une unité mémoire. Elle pouvait donc émettre soit trois opérations entières, soit trois µops mémoire, soit un mix des deux. En plus des mix avec les opérations flottantes et branchements. {|class="wikitable" |- ! Port d'émission n°1 !! Port d'émission n°2 !! Port d'émission n°3 !! Port d'émission n°4 !! Port d'émission n°5 !! Port d'émission n°6 ! |- | ALU entière || ALU entière || ALU entière || FADD || FMUL || FPU (autres opérations) |- | Multiplieur || || || || || |- | LOAD/STORE || LOAD/STORE || LOAD/STORE || || || |} Les CPU superscalaires larges dupliquent les multiplieurs et/ou les ''barrel shifters'', et même les unités de branchement. La raison est que quand la largeur du CPU augmente, le processeur traite des paquets d'instructions de plus en plus gros. Par exemple, pour un CPU à 10 voies, cela veut dire émettre 10 instructions en même temps. Il est alors plus fréquent d'avoir deux multiplications, deux décalages, deux accès mémoire, deux branchements. Par exemple, la moyenne est d'une multiplication tous les 5 instructions. Les CPU superscalaires à 10 voies ont donc tendance à avoir deux multiplications par paquet d'instruction, en moyenne. Avoir deux multiplieurs est alors un gain en performance clair. Par exemple, la microarchitecture ''Golden Cove'' a deux ''barrel shifters''. C'est une architecture à 6 voies. {|class="wikitable" |- ! Port d'émission n°1 !! Port d'émission n°2 !! Port d'émission n°3 !! Port d'émission n°4 !! Port d'émission n°5 !! Port d'émission n°6 !! Port d'émission n°7 !! Port d'émission n°8 !! Port d'émission n°9 !! Port d'émission n°10 |- | ALU entière || ALU entière || ALU entière || ALU entière || ALU entière || LOAD || LOAD || LOAD || STORE || STORE |- | ''Barrel shifter'' || Multiplieur || Multiplieur || ''Barrel shifter'' || || || || || || |- | Branchements || Diviseur || || Branchements || || || || || || |} ==Les µops mémoire sur les processeurs superscalaires== Plus haut, nous avons volontairement passé sous silence les accès mémoire. La raison à cela est que leur support sur les CPU superscalaire peut se faire de plusieurs manières différentes, qu'il vaut mieux expliquer dans une section dédiée. Les différentes implémentations sont décrites dans la liste suivante, nous allons les voir une par une. * Avec la première méthode, les accès mémoire sont exécutés par le pipeline pour les opérations entières. * Avec la seconde méthode, les accès mémoire ont un pipeline dédié ; c'est l'''émission parallèle des accès mémoire''. * Avec la troisième méthode, il est possible d'émettre plusieurs accès mémoire simultanément : c'est l'''émission multiple des accès mémoire''. Voyons ces trois cas dans le détail. Nous verrons notamment les différentes formes d'émission multiple des accès mémoire. ===Les accès mémoire traités dans le pipeline entier=== Dans le cas le plus simple, les accès mémoire sont traités comme des opérations entières un peu spéciales. L'unité mémoire est sur le même port d'émission que l'ALU entière, ce qui fait que les accès mémoire remplacent des opérations entières. Faire ainsi permet d'utiliser une ALU entière pour les calculs d'adresse, pas besoin de rajouter une unité de calcul d'adresse séparée. Typiquement, un processeur de ce type place l'unité mémoire après l'unité de calcul. Les opérations entières n'utilisent que l'ALU entière, mais pas l'unité mémoire. Les accès mémoire exécutent un calcul d'adresse dans l'ALU entière, puis l'adresse calculée est envoyée à l'unité mémoire. Le tout est illustré ci-dessous, dans le cas d'un processeur historique à double émission entière-flottante. [[File:Processeur non-superscalaire basique 01.png|centre|vignette|upright=1.5|Processeur non-superscalaire basique]] Un exemple de CPU double émission de ce type est celui du Power PC 440, un CPU destiné à l'embarqué. Vu qu'il était destiné à l'embarqué, il n'avait pas de FPU, il n'avait pas besoin de manipuler de nombres flottants. Il n'a donc que des ALU entières, un multiplieur et une unité mémoire. Il dispose de deux ports d'émission. Le premier est connecté à une ALU entière et un circuit multiplieur, le second est relié à l'unité mémoire et une seconde ALU entière. Une organisation simple, mais très efficace ! [[File:PowerPC 440.png|centre|vignette|upright=2|Microarchitecture du PowerPC 440.]] L'implémentation économise une unité de calcul d'adresse, mais n'a pas de cout en performance significatif. Les accès mémoire étant très lents, émettre une µop entière en parallèle d'un accès mémoire ne sert pas à grand-chose. On peut émettre cette µop entière un cycle plus tard, elle sera terminée avant même que l'accès mémoire soit terminé, même si l'accès mémoire se fait dans le cache L1 ou L2. Pour comprendre pourquoi, faisons quelques calculs. Un accès mémoire prend deux-trois cycles quand il tombe dans le cache L1. Et on doit attendre qu'il soit terminé pour en émettre un nouveau. Combien d'instructions arithmétiques peut-on lancer pendant ce temps, sur un CPU triple émission ? Au premier cycle, on peut en émettre 2 (l'instruction mémoire prend une place, donc 3-1), puis 3 au second cycle. Ce qui fait 5 instructions arithmétiques au total. Or, il se trouve qu'il y a une instruction d'accès mémoire pour 5 instructions arithmétiques, environ. Par contre, ce compromis change quand on passe à la quadruple émission, voire au-delà. Dans ce cas, il devient intéressant de pouvoir émettre une nouvelle instruction mémoire à chaque cycle. Ce qui nous amène à la section suivante... ===L'émission parallèle des accès mémoire=== A partir de la quadruple émission, la tendance a été de séparer l'unité mémoire des ALU entières. Cela permet d'émettre des µops mémoire en même temps que des µops entières et/ou flottantes., ce qui porte le nom d''''émission parallèle des µops mémoire''', ou encore ''émission mémoire parallèle''. Par exemple, un processeur quadruple émission peut émettre en même temps deux opérations entières, une opération flottante et un accès mémoire. L'accès mémoire vient en plus des opérations entières, il n'est pas possible de remplacer l'accès mémoire par une opération entière. La technique ne permet pas de faire les calculs d'adresse dans l'ALU entière. Il faut donc utiliser une unité de calcul d'adresse séparée. Elle est intégrée dans l'unité mémoire, qui fait l'interface entre le cache et le banc de registres. Le cout en circuits est très faible, car les unités de calcul d'adresse n'utilisent guère plus qu'un additionneur et un circuit de décalage très simple. L'émission mémoire parallèle est plus facile à implémenter sur les CPU RISC, qui ont des instructions mémoire séparées du reste. Les CPU CISC ne sont pas dans ce cas, ce qui fait que séparer l'unité mémoire du pipeline entier est compliqué. Les CPU superscalaires historiques étaient pour moité des CPU RISC et pour moitié des CPU CISC, ce qui fait qu'ils n'utilisaient pas ou peu l'émission mémoire parallèle. Les premiers processeurs superscalaires se contenaient de l'émission parallèle mémoire, ils ne pouvaient émettre qu'un seul accès mémoire à la fois, l'unité mémoire n'était pas dupliquée. Et ce n'est pas tant un problème que ça, du moins sur les processeurs à double ou triple émission. En effet, il est rare que l'unité mémoire doive faire deux accès simultanés. Notons que le processeur peut exécuter une instruction mémoire en parallèle d'un calcul ou d'un branchement, seuls les accès mémoire simultanés sont impossibles. ===L'émission multiple des accès mémoire avec une LSQ=== Les processeurs superscalaires larges travaillent sur des paquets de 8 à 10 instructions, il est plus fréquent d'avoir plusieurs accès mémoire par paquet. Dans ces conditions, il est utile d'avoir une '''émission multiple des accès mémoire''', à savoir d'émettre plusieurs micro-opérations mémoire en même temps. Les processeurs superscalaires modernes sont capables d'émettre plusieurs lectures/écritures simultanément. Par exemple, ils peuvent émettre une lecture en même temps qu'une écriture, ou plusieurs lectures, ou plusieurs écritures. Il faut noter que selon le processeur, il peut y avoir des restrictions quant aux accès mémoire émis en même temps. Par exemple, certains processeurs peuvent émettre une lecture avec une écriture en même temps, mais pas deux lectures ni deux écritures. Ou encore, ils peuvent émettre deux lectures, une lecture et une écriture, mais pas deux écritures en même temps. Dans la majorité des cas, les processeurs ne permettent pas d'émettre deux écritures en même temps, alors qu'ils supportent plusieurs lectures. Il faut dire que les lectures sont plus fréquentes que les écritures. Les processeurs qui autorisent toutes les combinaisons de lecture/écriture possibles, sont rares. L'émission multiple des accès mémoire s'implémente dans l'unité mémoire. Pour rappel, l'unité mémoire s'interpose entre le cache L1 et le reste du pipeline. Dans le cas le plus simple, elle incorpore une ''load/store queue'' et des unités de calcul d'adresse. L'émission multiple des accès mémoire demande de rendre la ''Load-store queue'' multi-ports, afin de gérer plusieurs micro-opérations mémoire simultanées. Il faut aussi dupliquer les unités de calcul d'adresse reliées à la LSQ, chaque port d'émission mémoire devant avoir sa propre unité de calcul d'adresse. : Dans ce qui suit, nous parlerons d'AGU (''Adress Generation Unit'') pour désigner les unités de calcul d'adresse, et de LSQ pour designer la ''Load-store queue''. [[File:Processeur non-superscalaire basique 02.png|vignette|upright=1|Processeur non-superscalaire basique]] Dupliquer les ports de la LSQ et les AGU est nécessaire. Mais qu'en est-il du cache ? La réponse varie suivant le processeur. Il y a deux grandes méthodes pour implémenter l'émission multiple des accès mémoire. La première méthode ajoute des ports de lecture/écriture au cache de données, pour faire des lectures/écritures simultanées. Il faut alors dupliquer des unités de calcul, rajouter les ports sur le cache, mais aussi rajouter des ports sur la ''load/store queue'', pour qu'elle échange des données/commandes mémoire avec le cache. Le cout en circuits est alors conséquent La seconde duplique les unités de calcul, mais pas les ports d'accès au cache. Les accès mémoire sont alors accumulés dans la ''load/store queue'', qui sont ensuite traités un par un par le cache. Un exemple est celui des processeurs AMD de micro-architecture K7 K8, à savoir les anciens AMD Athlon et AMD Athlon 64, qui pouvaient calculer trois adresses simultanément, mais ne pouvaient faire qu'un seul accès au cache par cycle. Le cout en circuit est modéré : les unités de calcul coutent très peu, les ports de la ''load/store queue'' coutent déjà un peu plus. Quelques processeurs utilisaient des solutions intermédiaires. Un processeur utilisant cette technique est, par exemple, capable d'émettre quatre µops mémoire en même temps, mais avec seulement deux ports sur le cache. Un exemple classique étant les Pentium 1 et 2 d'Intel, dont nous parlerons dans le chapitre suivant. ===L'émission séparée des LOAD et STORE=== De rares processeurs ont des ports d'émission qui peuvent faire indifféremment lecture comme écritures. Mais les processeurs modernes préfèrent séparer ports d'émission pour les lectures et les écritures. Un exemple est celui des processeurs Intel de micro-architecture Nehalem, qui avaient un port de lecture et un port d'écriture tous deux reliés à la ''Load-store queue'', chacun avec leur propre unité de calcul d'adresse. Comme autre exemple, les processeurs skylake ont une LSQ avec deux ports de lecture et un port d'écriture, ce qui permet d'émettre deux lectures en même temps qu'une écriture. Dans cette section, nous allons étudier le cas avec des ports séparés pour les lectures et écritures. En général, les processeurs ont un peu plus de ports de lectures que d'écritures. Une raison est qu'un programme a, en moyenne, un tout petit peu plus de lectures que d'écritures. Mais la différence n'est pas énorme, c'est un léger déséquilibre. Une seconde raison est que l'exécution dans le désordre marche bien mieux quand les lectures ont lieu le plus tôt possible. Donc mieux vaut privilégier les lectures à l'émission, pour qu'elles se fassent un peu en avance. En pratique, la différence n'est pas très marquée. Il est courant d'avoir un port de lecture et un d'écriture, les CPU superscalaires très larges ont souvent deux ports de lecture et deux d'écriture. A la rigueur, quelques CPU ont trois ports de lecture et deux d'écriture. Le cas le plus simple est celui où on peut émettre une lecture et une écriture en même temps. Il s'implémente facilement si la LSQ est en réalité une simple ''Store queue'', à savoir qu'elle met en avance les écritures uniquement. Pour rappel, les écritures sont mises en attente dans la ''Store queue'', mais les lectures sont exécutées immédiatement. Une fois l'adresse à lire connue, la lecture consulte la ''Store queue'' pour détecter les dépendances de données, puis s'exécute. La donnée à lire est lue soit depuis la ''Store queue'', soit depuis le cache. [[File:Emission LOAD - Store séparée.png|vignette|upright=1|Émission LOAD - Store séparée]] L'implémentation de l'émission multiple est alors très simple. La ''Store queue'' a déjà deux ports, avec un pour les lectures et un autre pour les écritures. Idem pour le cache, qui a naturellement un port de lecture et un d'écriture. Émettre une lecture et une écriture simultanément ne demande donc pas de modification digne de ce nom. La ''Store queue'' doit juste supporter une lecture et une écriture simultanée. Ajouter l'émission d'une seconde lecture n'est pas compliquée : il faut juste rajouter un second port de lecture sur le cache et la ''Store queue''. Pour rappel, l'unité mémoire est précédée par une structure qui met en attente les micro-opérations mémoire avant leur émission finale. La structure en question varie suivant l'implémentation, mais un cas particulier se marie bien avec l'émission multiple des µops mémoire. Il s'agit du cas avec une file de µops séparée pour les lectures et une autre pour les écritures, appelées respectivement la ''file de µops LOAD'' et la ''file de µops STORE''. Il est alors possible d'émettre une lecture et une écriture lors du même cycle d'horloge. Le port de lecture alimente une unité de calcul d'adresse dédiée, directement reliée au cache. Le port d'écriture du cache alimente une unité de calcul, qui est suivie par une file d'écriture, elle-même reliée au cache. Les processeurs AMD de micro-architecture K6 utilisaient cette technique. [[File:Double émission avec le Load Ordering, Store Ordering.jpg|centre|vignette|upright=2.5|Double émission avec le Load Ordering, Store Ordering]] ==Les CPU superscalaire à exécution dans le désordre== La superscalarité modifie aussi les circuits dédiés à l'exécution dans le désordre. Elle modifie les unités de renommage de registre, le ROB et les autres structures liées à l'exécution dans le désordre, au sens large. Aucune structure n'est épargnée. Elles ont tous des prots de lecture/écriture en plus, afin d'encaisser l'émission, le ''commit'' ou le renommage simultané de plusieurs µops. Par exemple, le ROB doit être modifié pour émettre et terminer plusieurs instructions en même temps, en ajoutant des ports de lecture/écriture. Pour un CPU superscalaire à 8 voies, on se dit que le ROB peut retirer 8 µops par cycle maximum. Mais certains processeurs récents ont un ROB qui peut retirer le double ! Un exemple est celui des CPU de micro-architecture Skymont, qui peuvent émettre 8 µops par cycle, mais en retirer 16. Quelques CPU de marque AMD étaient aussi dans ce cas. L'avantage est que cela permet de réduire la taille du ROB, du banc de registre et de quelques autres structures matérielles liées à l'exécution dans le désordre. Le gain sur-compense le cout en transistor nécessaire pour retirer 16 µops par cycle. Dans cette section, nous allons voir comment les étages du pipeline dédiés à l'exécution dans le désordre sont modifiées sur un CPU superscalaire. ===La superscalarité avec une file/fenêtre d'instruction centralisée=== Plus haut, nous avons montré qu'un processeur pouvait avoir plus de ports d'émission que de ports de décodage. On dit aussi que sa ''largeur de décodage'' était supérieure à sa ''largeur d'émission''. La '''largeur de décodage''' est le nombre de µops fournies par l'unité de décodage par cycle d'horloge, la '''largeur d'émission''' est le nombre maximal de µops émises par cycle d'horloge. Et n'allez pas croire qu'un surplus de ports d'émission est du gâchis. Sur les processeurs avec exécution dans le désordre, ce surplus peut être utilisé. Mais avant toute chose, évacuons un détail. Vous avez sans doute pensé que le surplus peut être exploité par les instructions machines décodées en plusieurs µops . Par exemple, si une instruction est décodée en deux µops, on pourrait avoir un port d'émission en plus pour en profiter. Mais les ports de décodage sont *en sortie du décodeur*, et contiennent des µops, pas des instructions. Le surplus de ports d'émission peut occasionnellement utiliser le surplus de ports de décodage, mais cela implique que des µops ont été mises en attente dans l'unité d'émission, pour être émises par la suite. Et cela implique l'usage d'une fenêtre d'instruction ou d'une file de µops. Le principe est que les µops sont accumulées dans la fenêtre d'instruction, en attendant leurs opérandes ou la résolution d'une dépendance. Et si l'occasion se présente, une µops est émise sur chaque port d'émission. Il y a donc un gain en performances, mais il faut faire quelques précisions sur son origine. La largeur d'émission utilisée ne peut pas dépasser, *en moyenne*, la largeur de décodage. Et c'est intuitif : un CPU qui décode 4 µops par cycle ne peut pas émettre 6 par cycle en moyenne, sur le temps long, il n'y a pas de µops en surplus qui sortent du vide. Par exemple, sur un processeur quadruple émission, si on analyse combien d'instructions sont émises par cycle, il peut y avoir des cycles où la largeur d'émission est totalement utilisée, mais ce sera compensé par des périodes où elle est fortement sous-utilisée. Les µops sont accumulées en cas de sous-utilisation, puis consommées en cas d'utilisation complète. Les deux périodes se compensent. Et c'est là que l'on peut comprendre d'où vient le gain en performance. Les cycles où les ports d'émission sont sous-utilisés existent systématiquement, que le processeur utilise l'exécution dans le désordre ou pas. Ils sont liés à la présence de dépendances de données, aux contraintes d’appariement, etc. Elles tendent à réduire la moyenne du nombre de µops émises par cycle. Par contre, ce sont les cycles de rattrapage, où les ports en surplus sont utilisés, qui font remonter la moyenne. Le gain en performance est donc lié à ces cycles de rattrapage, où les µops accumulées sont émises dans les ports en surplus. Et ils n'existent que sur les processeurs avec une file/fenêtre d'instruction. Réduire le nombre de ports d'émission permet de garder des fenêtres d'instruction et un ROB simples, rapides et économes en énergie. Et mine de rien, c'est là un compromis important : à quoi bon augmenter le nombre de ports d'émission si c'est pour que ce soit compensé par une fréquence plus faible ? Par contre, réduire le nombre de ports augmente la ''port contention'', surtout si on a mal réparti les ALU. Il faut donc trouver un compromis entre réduire le nombre de ports d'émission d'un côté, réduire la ''port contention'' de l'autre. La différence entre largeur de décodage et d'émission a un impact assez fort sur le banc de registre, et précisément sur son nombre de ports de lecture. Le banc de registre a besoin de plus de ports de lecture avec une fenêtre d’instruction qu'avec des stations de réservation. Rappelons la terminologie utilisée dans ce cours. Les fenêtres d'instruction se contentent de mémoriser la micro-opération et quelques bits pour détecter la disponibilité des opérandes. Par contre, les stations de réservations mémorisent aussi les opérandes des instructions. Et cette différence a une influence le nombre de ports de lecture du banc de registre. Supposons que toutes les instructions sont dyadiques, à deux opérandes. Comptons le nombre de ports nécessaires pour alimenter les unités de calcul, sans utiliser d'arbitre de banc de registre. Avec une fenêtre d'instruction, il faut le double du nombre de ports d'émission. Mais avec des stations de réservation, il faut le double des ports de décodage. La raison est que les registres sont lus après émission avec une fenêtre d'instruction, avant avec des stations de réservation (sous-entendu, après le décodage). Or, comme dit plus haut, les processeurs superscalaires larges ont souvent plus de ports d'émission que de décodage. ===La superscalarité à fenêtres d'instruction décentralisées=== Les processeurs précédents utilisaient une unité d'émission simple, qui pouvait utiliser l'émission dans l'ordre ou dans le désordre. Elle peut être remplacée par une fenêtre d'instruction centralisée dans que cela quoi que ce soit aux explications précédentes, idem avec une station de réservation centralisée. En soi, l'exécution dans le désordre est donc totalement compatible avec une implémentation simple de la superscalarité. Par contre, des subtilités surviennent avec les fenêtres d'instruction décentralisées. Un exemple est le MIPS R10000, qui utilisait un mélange de files de µops et de fenêtres d'instructions : une file de µops les accès mémoire, une fenêtre pour les instructions entières et une autre pour les opérations flottantes. Les branchements et NOPs étaient exécutés dans une unité de branchement distincte. Les trois files/fenêtres peuvent mémoriser 16 µops. Le processeur avait deux ALU entières, une AGU de calcul d'adresse, un additionneur flottant et un multiplieur/diviseur flottant. Il y avait un multiplieur entier sur le port d'émission l'ALU 2. [[File:R 10000.png|centre|vignette|upright=2.5|MIPS R10000, microarchitecture.]] Pour rappel, sur ces processeurs, les µops sont émises en deux phases. Une première phase de ''dispatch'' qui envoie les µops vers la fenêtre d'instruction adéquate, sans détecter les dépendances de données. La seconde phase de ''schedule'' gère les dépendances de données, elle met en attente les µops en attendant que l'unité soit disponible pour les exécuter, en attendant aussi que les opérandes soient disponibles. Et à ce petit jeu, il y a une différence entre le nombre de µops qui sont ''dispatch par cycle'' et celles qui sont ''schedule''. Dit autrement, la largeur d'émission est scindée en une '''largeur de ''dispatch''''' et une '''largeur de ''schedule''. Par exemple, prenons un processeur triple émission avec deux stations de réservation : une pour les µops entières/mémoire, et une autre pour les µops flottantes. L'unité d'émission peut ''dispatch'' maximum trois µops par cycle : soit trois µops entières/mémoire, soit trois flottantes, soit un mix de trois µops différentes. Les stations de réservation, quant à elles, peuvent chacune ''schedule'' 3 µops par cycle, vers trois ALUs entières pour la station de réservation entière/mémoire, vers trois FPS pour la station de réservation flottante. De quoi ''dispatch'' trois µops, mais d'en ''schedule'' six, à chaque cycle. Et cet exemple ne sort pas de nulle part : les CPU x86 d'Intel et AMD sont globalement dans ce cas, même si les chiffres varient d'une microarchitecture à l'autre. L'exemple précédent colle parfaitement à la microarchitecture K7 d'AMD, utilisée sur les processeurs Athlon dans les années 2000. [[File:AMD K7.png|centre|vignette|upright=2.5|AMD K7, microarchitecture.]] ===L'unité de renommage superscalaire=== Avec la superscalarité, l'unité de renommage de registre doit gérer le cas où des instructions consécutives ont des dépendances de registre. Par exemple, prenons un processeur à double émission, qui renomme deux instructions consécutives. Si elles ont une dépendance de registre, le renommage de la seconde instruction dépend du renommage de la première. La première doit être renommée et le résultat du renommage est utilisé pour renommer la seconde, ce qui empêche d'utiliser deux unités de renommage séparées. Pour cela, elle renomme les registres sans tenir compte des dépendances, pour ensuite corriger le résultat. [[File:Unité de renommage superscalaire.png|centre|vignette|upright=2|Unité de renommage superscalaire.]] Seules les dépendances lecture-après-écriture doivent être détectées, les autres étant supprimées par le renommage de registres. Repérer ce genre de dépendances se fait assez simplement : il suffit de regarder si un registre de destination d'une instruction est un opérande d'une instruction suivante. [[File:Détection des dépendances sur un processeur superscalaire.png|centre|vignette|upright=2|Détection des dépendances sur un processeur superscalaire.]] Ensuite, il faut corriger le résultat du renommage en fonction des dépendances. Si une instruction n'a pas de dépendance avec une autre, on la laisse telle quelle. Dans le cas contraire, un registre opérande sera identique avec le registre de destination d'une instruction précédente. Dans ce cas, le registre opérande n'est pas le bon après renommage : on doit le remplacer par le registre de destination de l'instruction avec laquelle il y a dépendance. Cela se fait simplement en utilisant un multiplexeur dont les entrées sont reliées à l'unité de détection des dépendances. On doit faire ce replacement pour chaque registre opérande. [[File:Correction des dépendances sur un processeur superscalaire.png|centre|vignette|upright=2|Correction des dépendances sur un processeur superscalaire.]] ==Les optimisations des processeurs superscalaires larges== Les processeurs superscalaires larges ont des optimisations spécifiques, qui n'ont pas de sens sur les processeurs superscalaires étroits. Dans les grandes lignes, elles se classent en deux types. Les premières permettent de charger/décoder plus de 5-10 instructions simultanément. Le problème est alors que les branchements viennent mettre leur grain de sel et qu'il faut en tenir compte. Les secondes corrigent des problèmes qui apparaissent quand un CPU intègre plus de 5 à 10 unités de calcul. ===L'étape de chargement d'un CPU superscalaire large=== Charger plusieurs instructions pose de nombreux problèmes sur les processeurs superscalaires larges, qui émettent de 5 à 10 instructions simultanées. Pour rappel, la stratégie basique pour charger N instructions est de charger un bloc de N instructions consécutives. Les instructions après un branchement ne sont pas décodées ni exécutées, ce qui entraine une petite baisse de performance. Pour un N petit, la perte de performance est mineure. Mais pour un N large, elle devient suffisamment importante pour que la conception du CPU doit alors être repensée de fond en comble. Les analyses statistiques disent qu'il y a environ un branchement tous les 4 à 6 instructions. Un CPU a donc tendance à décoder des paquets de 3 à 5 instructions consécutives, avant qu'un branchement casse la dynamique. Or, cela pose des problèmes pour utiliser plusieurs décodeurs. L'implémentation basique, qui charge N instructions consécutives pour alimenter N décodeurs, fonctionne donc jusqu'à 4/6 décodeurs. Au-delà de 5 instructions, un CPU superscalaire doit être adapté pour gérer les branchements dans les paquets d'instruction qu'il émet. Le nombre de 5 instructions ne sort donc pas de nulle part. La première solution utilise un cache de µops, avec un ''front-end'' capable de charger/décoder 4/5 instructions à la fois, pas plus. L'idée est que la majorité des instructions proviennent du cache de µops (plus de 80%), ce qui fait qu'on peut émettre environ 10 instructions à partir du cache de µops, pas besoin d'avoir beaucoup de décodeurs. Les performances sont cependant dégradées lors d'un défaut de cache de µops. Les instructions doivent alors être chargées depuis le cache d'instruction, par paquets de 4-5, puis sont décodées par 4-5 décodeurs, ce qui ne permet pas d’alimenter les unités de calcul à plein régime. La solution précédente joue sur la distinction entre pots de décodage et d'émission. Elle permet d'émettre un grand nombre de µops sans pour autant charger un grand nombre d'instructions en même temps. En quelque sorte, ce n'est qu'une méthode de contournement, le CPU superscalaire reste de type étroit. Mais il est aussi possible d'utiliser un '''''front-end large''''', capable de charger une bonne dizaine d'instructions en même temps. La seule difficulté est de tenir compte de la présence de branchements. Pour rappel, avec un ''front-end'' étroit, les instructions situées après le premier branchement pris sont annulées, elles ne sont pas décodées ni exécutées. [[File:Fetch sur un processeur superscalaire avec prediction de branchements.png|centre|vignette|upright=2|Fetch sur un processeur superscalaire avec prédiction de branchements.]] Un ''front-end'' large charge les instructions de destination du branchement et les placent à sa suite. Ils chargent deux blocs à la fois et les fusionnent en un seul qui ne contient que les instructions présumées utiles. [[File:Cache d'instructions autoaligné.png|centre|vignette|upright=2|Cache d'instructions auto-aligné.]] Mais cela demande de charger deux blocs de mémoire en une fois, ce qui demande un cache d'instruction multiports. Il faut aussi ajouter un circuit pour assembler plusieurs morceaux de blocs en un seul : le fusionneur (''merger''). En pratique, cette solution demande d'ajouter un second port de lecture au cache, pour un cout en hardware important, avec un impact négatif sur le temps d'accès au cache. Et il faut rajouter le fusionneur, qui prend un étage de pipeline à lui tout seul. Et encore, cette solution ne gère pas le cas où un bloc contient plusieurs branchements ! [[File:Implémentation d'un cache d'instructions autoaligné.png|centre|vignette|upright=2|Implémentation d'un cache d'instructions auto-aligné.]] Les exemples précédents partent du principe qu'il n'y a qu'un seul branchement dans un bloc. Mais quand les blocs deviennent assez longs, il est fréquent d'avoir plusieurs branchements dans un bloc. Et il est même possible d'avoir plusieurs branchements pris. Dans un bloc de 8-10 instructions, ce n'est pas rare, et les CPU superscalaires à 8-10 voies sont la règle dans les CPU des années 2020. Pour gérer ce cas, il faut ajouter des ports supplémentaires au cache d'instruction et adapter le fusionneur, pour qu'il puisse fusionner trois ou quatre blocs. De plus, il faut utiliser une unité de prédiction de branchement capable de ''prédire plusieurs branchements par cycle''. ===Le décodage superscalaire large=== Le décodage des instructions n'est pas censé dépendre de l'implémentation du chargement. En théorie, une fois plusieurs blocs d'instruction fusionnés, on peut décoder les instructions en parallèle. Cependant, certains processeurs effectuent le décodage en tenant compte de la manière dont les branchements découpent les blocs. Les processeurs Atom d'Intel utilisent une solution de ce type, qui couple prédiction de branchement et décodage parallèle. Ils chargent/décodent des paquets de 3 instructions consécutives, chaque paquet étant séparé du précédent par un branchement. Pour cela, en plus d'avoir un fusionneur adapté, ils regroupent les décodeurs en ''clusters'', chacun regroupant 3 décodeurs, et pouvant décoder trois instructions consécutives. Les micro-architectures Tremont et Crestmont avaient deux ''clusters'' (6 décodeurs au total), la micro-architecture Skymont en intègre trois (9 décodeurs au total). Avec deux ''clusters'', le fonctionnement est le suivant. Si le code est composé d'une suite d'instruction sans branchements, seul le premier ''cluster'' est utilisé, limitant le décodage à trois instructions par cycle. Par contre, si l'une des trois instructions décodée est un branchement, alors le second ''cluster'' décode les trois instructions situées après ce branchement, à partir de l'adresse de destination. Avec un troisième ''cluster'', c'est la même chose, sauf qu'il faut qu'il y ait un branchement dans les trois instructions décodées par le second ''cluster''. Mais comment faire pour savoir quels décodeurs activés, quels ''clusters'' sont actifs ? C'est la prédiction de branchement qui commande. Avec deux ''clusters'', elle détermine si un branchement est présent dans les trois instructions à charger, et quelle est son adresse de destination. Sans branchement, elle prévient les décodeurs et les trois instructions chargées sont décodées dans un ''cluster'' libre. Si elle prédit un branchement, elle charge les instructions adéquates, et active les deux ''clusters''. Avec trois branchements, c'est le même mécanisme, sauf que l'unité de prédiction de branchement doit être capable de prédire deux branchements par cycle. Il faut noter que les décodeurs d'un ''cluster'' sont des décodeurs simples, sans microcode. Sur les processeurs Tremont et Crestmont, le microcode était unique et était partagé entre tous les ''clusters''. Un seul ''cluster'' pouvait décoder une instruction complexe avec le microcode, l'autre devait se limiter à des instructions simples uniquement. Depuis Skymont, le microcode est partiellement dupliqué, avec un microcode par ''cluster''. Le cout en hardware peut paraitre prohibitif, pour un gain en performance faible. Sauf que c'est oublier que ce sont des processeurs Atom, sur lesquels beaucoup d'instructions sont microcodées. Les processeurs Atom ont un budget en transistor limité, ce qui fait qu'il est préférable pour eux d'implémenter des instructions en microcode que d'utiliser des unités de calcul dédiées ou des circuits de décodage complexes. Pour limiter la casse, le microcode n'est que partiellement dupliqué. Seul le microcode pour les instructions fréquente est dupliqué. Il y a un microcode secondaire pour certaines instructions, qui est lui unique et partagé par tous les ''clusters''. ===Le contournement sur les processeurs superscalaires larges=== Pour rappel, la technique du contournement (''register bypass'') permet au résultat d'une instruction d'être immédiatement utilisable en sortie de l'ALU, avant même d'être enregistré dans les registres. Implémenter la technique du contournement demande d'utiliser des multiplexeurs pour relier la sortie de l'unité de calcul sur son entrée si besoin. Il faut aussi des comparateurs pour détecter des dépendances de données. [[File:Pipeline Bypass.png|centre|vignette|upright=1|Pipeline Bypass]] La sortie de chaque ALU doit être reliée aux entrées de toutes les autres, avec les comparateurs qui vont avec. Sur les processeurs ayant plusieurs d'unités de calculs, cela demande beaucoup d’interconnexions et de comparateurs. Pour N unités de calcul, cela demande 2 * N² interconnexions, implémentées avec 2N multiplexeurs de N entrées chacun. Si c'est faisable pour 2 ou 3 ALUs, la solution est impraticable au-delà. Non seulement ce serait un enfer à câbler, mais les fils seraient tellement longs que la fréquence du processeur en prendrait un coup. Notez que cela vaut pour les processeurs superscalaires, mais aussi pour tout processeur avec beaucoup d'unités de calcul. En théorie, un CPU à exécution dans le désordre, non-superscalaire, mais avec beaucoup d'unités de calcul, fait face au même problème. En pratique, avoir une dizaine d'ALU implique processeur superscalaire à exécution dans le désordre. D'où le fait qu'on parle du problème maintenant. La seule solution praticable est de ne pas relier toutes les unités de calcul ensemble. À la place, on préfère regrouper les unités de calcul dans différents '''agglomérats''' ('''cluster'''). Le contournement est alors possible entre les unités d'un même agglomérat, mais pas entre agglomérats différents. Évidemment, certaines possibilités de contournement sont perdues, mais c'est compensé par un gain en fréquence et un cout en circuit/fils réduit. C'est un bon compromis, ce qui explique que presque tous les processeurs modernes l'utilisent. Les rares exceptions sont les processeurs POWER 4 et POWER 5, qui ont préféré se passer de contournement pour garder un processeur très simple et une fréquence élevée. ===Le banc de registre des processeurs superscalaires larges=== Pour émettre deux opérations entières simultanément, il faut lire deux fois plus d'opérandes. Et il faut aussi écrire deux résultats dans les registres, en même temps. Pour cela, on double les ports du banc de registre. En général, il faut au maximum 2 ports de lecture et un port d'écriture par unité de calcul. Le problème, c'est que plus un banc de registres a de ports, plus il utilise de circuits, est compliqué à concevoir, consomme de courant et chauffe. Avec plusieurs dizaines d'unités de calcul différentes, ajouter autant de ports impacterait la fréquence du banc de registres et réduirait les performances, voire est totalement impraticable à cause des contraintes thermiques. Les processeurs superscalaires larges doivent donc trouver des solutions. La première solution utilise un banc de registre unique, mais n'utilise pas autant de ports que le pire des cas le demanderait. Le processeur détecte quand il n'y a pas assez de ports pour servir toutes les micro-opérations. Quand ça arrive, l'unité d'émission émet assez de micro-opérations pour exploiter les ports disponibles, mais met en attente les micro-opérations en trop, le temps que les ports se libèrent. Pour cela, le banc de registre est géré par un circuit d'arbitrage spécialisé, intégré à l'unité d'émission, l’'''arbitre du banc de registres''' (''register file arbiter''). Une autre solution est de dupliquer le banc de registres en plusieurs exemplaires qui contiennent exactement les mêmes données, mais avec moins de ports de lecture/écriture. Un exemple est celui des processeurs POWER, Alpha 21264 et Alpha 21464. Sur ces processeurs, le banc de registre est dupliqué en plusieurs exemplaires, qui contiennent exactement les mêmes données. Les écritures dans les registres sont envoyées à tous les bancs de registres, afin de garantir que leur contenu est identique. Chaque exemplaire a exactement deux ports de lecture. La conception du processeur est simplifiée, que ce soit au niveau du câblage, que de la conception des bancs de registres. Une dernière solution découpe le banc de registres en plusieurs bancs de registres plus petits, avec un réseau d'interconnexions pour échanger des données entre bancs de registres. Dans la plupart des cas, cette séparation est invisible du point de vue de l'assembleur et du langage machine. Le processeur se charge de transférer les données entre bancs de registres suivant les besoins. Sur d'autres processeurs, les transferts de données se font via une instruction spéciale, souvent appelée ''COPY''. [[File:Banc de registres distribué.png|centre|vignette|upright=2|Banc de registres distribué.]] Sur de certains processeurs, un branchement est exécuté par une unité de calcul spécialisée. Or les registres à lire pour déterminer l'adresse de destination du branchement ne sont pas forcément dans le même agglomérat que cette unité de calcul. Pour éviter cela, certains processeurs disposent d'une unité de calcul des branchements dans chaque agglomérat. Dans les cas où plusieurs unités veulent modifier le ''program counter'' en même temps, un système de contrôle général décide quelle unité a la priorité sur les autres. Mais d'autres processeurs fonctionnent autrement : seul un agglomérat possède une unité de branchement, qui peut recevoir des résultats de tests de toutes les autres unités de calcul, quel que soit l’agglomérat. ==La macro-fusion : une optimisation du décodage parallèle== Les processeurs superscalaires supportent la technique dite de '''macro-fusion''', qui permet de fusionner deux-trois instructions consécutives en une seule micro-opération. Par exemple, il est possible fusionner une instruction de test et une instruction de saut en une seule micro-opération de branchement. Il s'agit là de l'utilisation la plus importante de la macro-fusion sur les processeurs x86 modernes. La macro-fusion est utilisée pour fusionner un test et un saut en un branchement unique. L'optimisation est présente sur la plupart des CPU superscalaires modernes. Les CPU x86 l'implémentent, mais aussi quelques CPU ARM et les processeurs Apple de série M (M1 à M5, et potentiellement plus quand ils seront sortis). La fusion des instructions se fait lors du décodage des instructions, grâce à la coopération des décodeurs. Elle marche systématiquement si l'instruction de test et le saut sont consécutives, qu'elles forment bien une paire d'instruction. Par contre, insérer une instruction entre les deux empêche la macro-fusion de faire son travail, sauf si l'instruction insérée est un NOP. Les processeurs ARM64 avaient une forme spécifique de macro-fusion, utilisée pour un cas très précis : charger une constante immédiate de 32 ou 64 bits, dans un registre 64 bits. Les CPU ARM 64 bits utilisent des instructions codées sur 32 bits, qui ne peuvent encoder que des constantes immédiates de 16 bits. Pour charger une constante de 32/64 bits, il faut s'y prendre en plusieurs fois. Une première instruction MOVZ charge les 16 premiers bits, puis 2 à 4 instructions MOVK remplissent le reste. MOVZ met à zéro les 48 bits de poids fort, MOVK permet de sélectionner le doublet à modifier dans le registre (on modifie 16 bits, dont on choisit la place, le reste du registre est laissé tel quel). ARM permet de fusionner cette suite d'instructions en une seule µop MOV avec une constante immédiate. On parle alors de '''''MOVZ + MOVK fusion'''''. Une dernière utilisation, [https://www2.eecs.berkeley.edu/Pubs/TechRpts/2016/EECS-2016-130.pdf proposées pour le jeu d'instruction RISC-V], fusionnait un calcul d'adresse et une instruction LOAD/STORE en une seule instruction d'accès mémoire. Elle a été envisagée à une époque où le jeu d'instruction RISC-V ne gérait pas les modes d'adressage base + indice. L'idée était d'avoir une unité mémoire qui gérait le mode d'adressage base + indice et de fusionner des instructions pour l'exploiter. Mais de nos jours, le jeu d'instruction RISC-V intègre le mode d'adressage base + indice, ce qui fait que la macro-fusion n'a aucun intérêt pour ce cas précis. La macro-fusion a de nombreuses limitations en pratique. En théorie, on pourrait l'utiliser au-delà des deux situations mentionnées plus haut. Par exemple, pour fusionner une addition et une multiplication en une seule instruction MAD. Mais l'intérêt est assez faible. Notons que la macro-fusion ne permet que de fusionner des paires d'instructions. Aller au-delà demanderait un cout en circuit trop important. Déjà que tester deux paires utilise pas mal de comparateurs... ==Le ''stack engine'' : une optimisation de la pile d'appel== Les processeurs modernes intègrent une optimisation liée au pointeur de pile. Pour rappel, sur les architectures modernes, le pointeur de pile est un registre utilisé pour gérer la pile d'appel, précisément pour savoir où se trouve le sommet de la pile. Il est manipulé par les instructions CALL, RET, PUSH et POP, mais aussi par les instructions d'addition/soustraction pour gérer des cadres de pile de taille variable. De plus, il peut servir d'opérande pour des instructions LOAD/STORE, afin de lire/écrire des variables locales, les arguments d'une fonction, et autres. C'est donc un registre général adressable, intégré au banc de registre, altéré par l'unité de calcul entière. L'incrémentation/décrémentation du pointeur de pile passe donc par l'unité de calcul, lors des instructions CALL, RET, PUSH et POP. Mais, l'optimisation que nous allons voir permet d'incrémenter/décrémenter le pointeur de pile sans passer par l'ALU, ou presque. L'idée est de s'inspirer des architectures avec une pile d'adresse de retour, qui intègrent le pointeur de pile dans le séquenceur et l'incrémentent avec un incrémenteur dédié. ===Le ''stack engine''=== L'optimisation que nous allons voir utilise un '''''stack engine''''' intégré à l'unité de contrôle, au séquenceur. Le processeur contient toujours un pointeur de pile dans le banc de registre, cela ne change pas. Par contre, il n'est pas incrémenté/décrémenté à chaque instruction CALL, RET, PUSH, POP. Un compteur intégré au séquenceur est incrémenté à la place, nous l’appellerons le '''compteur delta'''. La vraie valeur du pointeur de pile s'obtient en additionnant le compteur delta avec le registre dans le banc de registre. Précisons que si le compteur delta vaut zéro, la vraie valeur est dans le banc de registre et peut s'utiliser telle quelle. Lorsqu'une instruction ADD/SUB/LOAD/STORE utilise le pointeur de pile comme opérande, elle a besoin de la vraie valeur. Si elle n'est pas dans le banc de registre, le séquenceur déclenche l'addition compteur-registre pour calculer la vraie valeur. Finalement, le banc de registre contient alors la bonne valeur et l'instruction peut s'exécuter sans encombre. L'idée est que le pointeur de pile est généralement altéré par une série d'instruction PUSH/POP consécutives, puis des instructions LOAD/STORE/ADD/SUB utilisent le pointeur de pile final comme opérande. En clair, une bonne partie des incrémentations/décrémentation est accumulée dans le compteur delta, puis la vraie valeur est calculée une fois pour toutes et est utilisée comme opérande. On accumule un delta dans le compteur delta, et ce compteur delta est additionné quand nécessaire. Précisons que le compteur delta est placé juste après le décodeur d'instruction, avant même le cache de micro-opération, l'unité de renommage et l'unité d'émission. Ainsi, les incrémentations/décrémentations du pointeur de pile disparaissent dès l'unité de décodage. Elles ne prennent pas de place dans le cache de micro-opération, ni dans la fenêtre d'instruction, ni dans la suite du pipeline. De nombreuses ressources sont économisées dans le ''front-end''. Mais utiliser un ''stack engine'' a aussi de nombreux avantages au niveau du chemin de données/''back-end''. Déjà, sur les processeurs à exécution dans le désordre, cela libère une unité de calcul qui peut être utilisée pour faire d'autres calculs. Ensuite, le compteur delta mémorise un delta assez court, de 8 bits sur le processeur Pentium M, un peu plus pour les suivants. L'incrémentation se fait donc via un incrémenteur 8 bits, pas une grosse ALU 32/64 bits. Il y a un gain en termes de consommation d'énergie, un incrémenteur 8 bits étant moins gourmand qu'une grosse ALU 32/64 bits. ===Les points de synchronisation du delta=== La technique ne fonctionne que si la vraie valeur du pointeur de pile est calculée au bon moment, avant chaque utilisation pertinente. Il y a donc des '''points de synchronisation''' qui forcent le calcul de la vraie valeur. Plus haut, nous avions dit que c'était à chaque fois qu'une instruction adresse le pointeur de pile explicitement, qui l'utilise comme opérande. Les instructions CALL, RET, PUSH et POP ne sont pas concernées par elles utilisent le pointeur de pile de manière implicite et ne font que l'incrémenter/décrémenter. Mais dans les faits, c'est plus compliqué. D'autres situations peuvent forcer une synchronisation, notamment un débordement du compteur delta. Le compteur delta est généralement un compteur de 8 bits, ce qui fait qu'il peut déborder. En cas de débordement du compteur, le séquenceur déclenche le calcul de la vraie valeur, puis réinitialise le compteur delta. La vraie valeur est donc calculée en avance dans ce cas précis. Précisons qu'un compteur delta de 8 bits permet de gérer environ 30 instructions PUSH/POP consécutives, ce qui rend les débordements de compteur delta assez peu fréquent. À noter que si le compteur delta vaut zéro, il n'y a pas besoin de calculer la vraie valeur, le séquenceur prend cette situation en compte. Un autre point de synchronisation est celui des interruptions et exceptions matérielles. Il faut que le compteur delta soit sauvegardé lors d'une interruption et restauré quand elle se termine. Idem lors d'une commutation de contexte, quand on passe d'un programme à un autre. Pour cela, le processeur peut déclencher le calcul de la vraie valeur lors d'une interruption, avant de sauvegarder les registres. Pour cela, le processeur intègre un mécanisme de sauvegarde automatique, qui mémorise la valeur de ce compteur dans le tampon de réordonnancement, pour forcer le calcul de la vraie valeur en cas de problème. La technique du ''stack engine'' est apparue sur les processeurs Pentium M d'Intel et les processeurs K10 d'AMD, et a été conservée sur tous les modèles ultérieurs. L'implémentation est cependant différente selon les processeurs, bien qu'on n'en connaisse pas les détails et que l'on doive se contenter des résultats de micro-benchmarks et des détails fournit par Intel et AMD. Selon certaines sources, dont les manuels d'optimisation d'Agner Fog, les processeurs AMD auraient moins de points de synchronisation que les processeurs Intel. De plus, leur ''stack engine'' serait placé plus loin que prévu dans le pipeline, après la file de micro-opération. ==La micro-fusion et la délamination== La '''micro-fusion''' est une optimisation qui retarde le décodage réel de certaines instructions assez loin dans le pipeline. Elle concerne surtout les instructions ''load-op'', mais pas que. D'autres instructions mémoire peuvent subir cette optimisation. L'essentiel est que, sans micro-fusion, ces instructions sont décodées en plusieurs micro-opérations. La micro-fusion retarde le décodage final de ces instructions, qui se fait assez tard dans le pipeline. Pour comprendre l'intérêt, nous allons devoir faire quelques explications. L'optimisation en question fait le décodage en deux temps. Prenons l'exemple d'une opération ''load-op'', qui est censée être décodée en deux micro-opérations : une lecture, puis une opération. Le décodage produit une '''macro-opération''' (terme d'Intel), qui est propagée dans le pipeline, pour être scindée en deux micro-opérations un peu avant les ALU et l'unité mémoire. La macro-opération est techniquement une micro-opération comme une autre, sauf que ce n'est pas la micro-opération finale. L'unité d'émission découpe cette macro-opération en deux micro-opérations, qui sont émises l'une après l'autre. Pour résumer, les instructions ''load-op'' ne sont complétement découpées en micro-opération lors de l'émission, pas lors du décodage. L'optimisation impacte ce qui se situe entre le décodeur d'instruction et l'unité d'émission : l'unité de renommage, la file de micro-opérations, le ''Loop Stream Detector'' et/ou le cache de micro-opération. L'intérêt de l'optimisation est que l'on fusionne deux micro-opérations en une seule macro-opération. Le cache de micro-opération et la file de micro-opération peuvent mémoriser plus de micro-opérations, vu que certaines sont fusionnées. Quant à l'unité de renommage, elle renomme une seule macro-opération au lieu de renommer deux micro-opérations, ce qui augmente le débit du renommage de registre. Par contre, cela complique un peu le travail de l'unité d'émission, qui doit découper les instructions ''load-op'' en deux et émettre les micro-opérations l'une après l'autre. Il faut noter qu'il n'y a pas de fusion proprement dite. Le décodeur ne décode pas deux micro-opérations séparées, avant de les fusionner. En réalité, il génère directement une macro-opération, qui correspond à deux micro-opérations, que ce soit plus tard dans le pipeline ou sur une autre architecture. D'ailleurs, la micro-fusion a lieu à l'intérieur d'une instruction, qui est censée être traduite en plusieurs micro-opérations. Et encore : pas toutes. La conséquence est que la technique n'a de sens que sur les processeurs CISC, avec des instructions assez complexes pour être décodées en plusieurs micro-opérations. En pratique, seuls les CPU x86 sont concernés. ===Les domaines de fusion=== La micro-fusion n'est pas spécifique aux processeurs superscalaires. En théorie, on peut l'implémenter sur un processeur non-superscalaire à émission dans l'ordre. Cependant, je ne connais aucun processeur à émission dans l'ordre qui implémente la micro-fusion, même si son implémentation est en théorie possible sur de tels processeurs. La micro-fusion est implémentée différemment selon que l'émission se fasse dans l'ordre, dans le désordre, avec une fenêtre d'instruction, avec des stations de réservation, etc. J'ai dit plus haut que les micro-opérations sont générées lors de l'émission, mais le moment exact varie suivant le processeur. Pour simplifier le tout, nous parlerons de '''scission''' quand le processeur traduit une macro-opération en deux/trois micro-opérations. Le moment où a lieu la scission est théoriquement dans l'unité d'émission, mais celle-ci prend plusieurs formes selon que le processeur utilise l'émission dans l'ordre, dans le désordre, avec une fenêtre d'instruction, avec des stations de réservation, etc. Les processeurs à exécution dans l'ordre sont le cas le plus simple. L'unité d'émission est alors en charge du découpage des macro-opérations en micro-opérations. La file de micro-opération ou le cache de micro-opérations mémorisent des macro-opérations. À l'opposé, le tampon de ré-ordonnancement mémorise lui des micro-opérations. Le reste du processeur n'est pas affecté. Sur les processeurs à exécution dans le désordre, les différences sont nombreuses. Le tampon de ré-ordonnancement peut mémoriser soit les micro-opérations finales, soit des macro-opérations. Il en est de même avec la fenêtre d'instruction et/ou les stations de réservation, qu'elles soient centralisées ou décentralisées. Tout dépend de comment se fait l'implémentation. Une implémentation simple effectue la scission des macro-opérations dans l'unité d'émission, comme sur les processeurs à exécution dans l'ordre. Sur de nombreux processeurs, l'unité d'émission et de renommage sont simplement fusionnées, ce qui fait que la scission a lieu juste après le renommage de registres. En conséquence, le tampon de re-ordonnancement et les fenêtres d'instruction ne voient que des micro-opérations. Les macro-opérations disparaissent juste avant. Cette implémentation est préférée avec une fenêtre d'instruction centralisée, car on voit mal comment scinder une macro-opération juste avant l'ALU. Une autre implémentation fait la scission en sortie de la fenêtre d'instruction ou des stations de réservation, qu'elles soient centralisées ou décentralisées. Dans ce cas, le tampon de re-ordonnancement fait de même. L'avantage est que le tampon de ré-ordonnancement stocke des macro-opérations, ce qui augmente sa taille effective. Par exemple, il utilise une seule entrée pour une instruction ''load-op'', au lieu de deux sans micro-fusion de ce type. ===La délamination=== La scission peut parfois avoir lieu plus tôt, dans des circonstances très précises. Concrètement, cela ne concerne que quelques micro-architectures d'Intel, pour des instructions très précises. La raison est les limitations du format des micro-opérations, pas une histoire d'optimisation. Le processeur aimerait pouvoir faire la micro-fusion, mais ne peut pas à cause de ces limitations. Une telle situation cause alors une '''délamination''' : les micro-opérations sont scindées précocement. Par exemple, sur la micro-architecture SandyBridge d'Intel, les opérations ''load-op'' sont fusionnées, sauf si elles utilisent l'adressage indicé. Les micro-opérations de ce processeur ne peuvent avoir que deux opérandes. Trois opérandes est de trop, et les opérations ''load-op'' en adressage indicé dépassent cette limite. Dans ce cas, les micro-opérations sont fusionnées en sortie du décodeur, mais sont scindés avant d’entrer dans la file de micro-opération. L'avantage n'est pas évident, vu que l'unité de renommage suit de près le décodeur d'instruction. Il est que le cache de micro-opérations mémorise des macro-opérations, pas deux micro-opérations. ===La micro-fusion des processeurs Intel et AMD=== La micro-fusion est apparue sur le Pentium M d'Intel, et a été conservée telle quelle sur les processeurs Intel suivants. Les processeurs AMD supportent la micro-fusion depuis au moins la micro-architecture AMD K8. Mieux que ça, la délamination n'était pas utilisée car les micro-opérations acceptaient plus de 2 opérandes. Que ce soit chez AMD ou Intel, elle concerne deux types d'instructions : les écritures mémoire et les instructions ''load-op''. Sur le Pentium M, il y avait quelques limitations, l'optimisation se limitant par exemple aux registres entiers et flottants, mais pas aux registres MMX, mais pas les registres SSE (nous verrons ces registres dans quelques chapitres). Mais les processeurs modernes n'ont plus cette limitation. La micro-fusion des CPU Intel concerne deux types d'instructions : les écritures mémoire et les instructions ''load-op''. Pour les écritures, la situation est plus complexe que pour les instructions ''load-op''. Sur les processeurs Intel précédents, les écritures étaient réalisées dans deux unités séparées : le calcul d'adresse dans une unité de calcul, l'écriture proprement dite dans l'unité mémoire. Elles prenaient donc deux micro-opérations. Le processeur a changé ses unités mémoire, ce qui fait qu'elle pouvait faire les deux en une seule micro-opération. Il s'agissait alors d'une optimisation du chemin de données, plus que de la micro-fusion proprement dite, mais je la mentionne ici malgré tout. La micro-fusion sur ce processeur avait un avantage en plus des avantages usuels : elle utilisait mieux les décodeurs. Les Pentium M avaient trois décodeurs pour décoder plusieurs instructions en même temps, chose qu'on détaillera dans le chapitre sur les processeurs superscalaires. Un seul décodeur pouvait décoder toutes les instructions, les deux autres ne peuvent décoder que les instructions simples, qui se décodent en une seule micro-opération. Avec la micro-fusion, les trois décodeurs peuvent gérer les écritures mémoire et les opérations ''load-op'', vu qu'elles deviennent des instructions simples. Sans cela, elles auraient été limitées au premier décodeur. <noinclude> {{NavChapitre | book=Fonctionnement d'un ordinateur | prev=Le parallélisme mémoire | prevText=Le parallélisme mémoire | next=Exemples de microarchitectures CPU : le cas du x86 | nextText=Exemples de microarchitectures CPU : le cas du x86 }} </noinclude> oioxn19wxewoaubrvxg9esjlhj1f6tb 773251 773250 2026-09-26T20:20:36Z Mewtow 31375 /* La superscalarité à fenêtres d'instruction décentralisées */ 773251 wikitext text/x-wiki Les processeurs vus auparavant ne peuvent émettre au maximum qu'une instruction par cycle d'horloge. Ils peuvent avoir plusieurs instructions qui s'exécutent en même temps, dans des unités de calcul séparées. C'est le cas dès qu'une instruction multi-cycles s'exécute. Par contre, ils ne peuvent démarrer qu'une seule instruction par cycle. Et quand on court après la performance, ce n'est pas assez ! Les concepteurs de processeurs ont inventés des processeurs qui émettent plusieurs instructions par cycle : les '''processeurs à émissions multiples'''. Un processeur à émission multiple charge plusieurs instructions en même temps, les décode en parallèle, puis les émet en même temps sur des unités de calculs séparées. Pour cela, il faut gérer les dépendances entre instructions, répartir les instructions sur différentes unités de calcul, et cela n'est pas une mince affaire. En général, pour un processeur à émission multiple capable d'émettre N instructions en parallèle, on parle de '''processeur à N voies''', ou encore un ''processeur à N pipelines''. [[File:Superscalarpipeline.svg|centre|vignette|upright=1.5|Pipeline RISC classique à cinq étages sur un processeur superscalaire. On voit bien que plusieurs instructions sont chargées en même temps.]] Les processeurs à émission multiple sont de deux types : les processeurs VLIW et les '''processeurs superscalaires'''. Nous mettons les processeurs VLIW de côté en attendant le prochain chapitre. La raison est qu'ils ont un jeu d'instruction spécialisé qui expose le parallélisme d'instruction directement au niveau du jeu d'instructions, là où les processeurs superscalaires restent des processeurs au jeu d'instruction normal. La différence principale entre processeur VLIW et superscalaire est qu'un processeur superscalaire répartit les instructions sur les unités de calcul à l’exécution, là où un processeur VLIW délègue cette tâche au compilateur. ==Le nombre de voies d'un processeur superscalaire== Les processeurs superscalaires les plus simples ne permettent que d'émettre deux micro-opérations à la fois, d'où leur nom de processeur ''dual issue'', ce qui se traduit en '''processeur à double émission'''. Les processeurs triple émission permettent eux d’émettre trois instructions à la fois. A l'opposé, les CPU superscalaires les plus puissants peuvent émettre jusqu'à 8-10 instructions simultanément. En 2026, les CPU superscalaires peuvent monter jusqu’à 10 µops émises en même temps, ce qui est énorme ! Et cela nous amène à faire une distinction entre les processeurs superscalaires dits '''étroits''' et ceux dits '''larges'''. Dans ce wikilivre, on part du principe que les processeurs superscalaires étroits émettent jusqu'à 4-5 µops à la fois, alors que les larges peuvent en émettre plus, l'intervalle entre les deux est une zone grise. Le nombre exact n'est pas fixé dans le marbre, la distinction est volontairement vague. Nous parlerons de la '''largeur d'un CPU superscalaire''' pour parler du nombre de voies, du nombre de µops émises en même temps. Plus un CPU superscalaire est large, plus il est performant ''toute chose égale par ailleurs''. Mais dans les faits, vous pouvez oublier la partie en italique. Élargir un CPU superscalaire demande de rajouter beaucoup de circuits. Et ce cout en circuit aura un impact sur la performance. A budget en transistors limité, il y a un compromis entre élargir un CPU et rajouter du cache ou la taille des fenêtres d'instructions/ROB et autres. ===Les contraintes d’appariement=== Prenons un processeur double émission, capable d'émettre deux instructions simultanément. Émettre deux instructions en même temps n'est pas toujours possible. Par exemple, si deux instructions sont dépendantes, on ne peut pas les émettre en même temps. Mais oublions un instant ces histoires de dépendances de données, et partons du principe que le CPU ait affaire à deux instructions indépendantes, une paire idéale. Un CPU double émission "parfait" pourrait exécuter n'importe quelle paire d'instruction. Par exemple, il pourrait émettre deux multiplications consécutives, deux lectures consécutives, une lecture et une écriture en même temps, etc. En pratique, aucun processeur ne permet cela. Implémenter un CPU superscalaire "parfait" aurait un cout en circuit beaucoup trop conséquent. Par exemple, pour émettre deux multiplications en même temps, il faudrait deux multiplieurs séparés. Idem pour exécuter deux accès mémoire simultané : cela demande d'avoir deux unités mémoire et un cache multiport. Mais en pratique, aucun CPU superscalaire ne fait cela. La totalité des CPU double émission se débrouille avec un seul circuit multiplieur et une seule unité mémoire. Et cela entraine l'apparition de dépendances structurelles, qui font que certaines paires d'instructions sont impossibles. Par exemple, impossible d'émettre deux multiplications en même temps avec un seul circuit multiplieur. Mais émettre une multiplication en parallèle d'une addition est parfaitement possible. Il y a donc des paires d'instructions autorisées et des paires interdites. Et la logique est la même sur les CPU à triple ou quadruple émission, et au-delà. : des triplets ou quadruplet d'instructions sont impossibles à émettre simultanément, alors que d'autres le sont. Pour le dire autrement, il y a des '''contraintes d'appariement''' sur les instructions à émettre en même temps. ===L'impact des contraintes d’appariement sur la performance=== Ces contraintes d’appariement ont un impact sur les performance, mais celui-ci peut être grandement mitigé. Les programmes informatiques utilisent en pratique beaucoup plus d'opérations entières de base que de multiplication, branchements et accès mémoire. Il y a environ 5 opérations entières de base pour une multiplication entière. En pratique, il est rare de devoir exécuter deux multiplications à la fois. Idem pour les décalages et rotations, qui sont assez éloignés les uns des autres. Deux branchements consécutifs sont quant à eux plus fréquents. Deux accès mémoire consécutifs est assez rare, mais ils restent assez proches les uns des autres malgré tout. La conséquence est que certaines paires d'instructions sont privilégiées par rapport au reste. Les paires regroupant deux calculs basiques (additions, soustractions, ...) sont systématiquement supportées, car dupliquer des ALU entières est facile. Les paires regroupant un calcul et une autre instruction sont elles aussi systématiquement supportées. Par contre, les paires avec deux branchements, deux accès mémoire, deux décalages/rotations, ou deux multiplications/divisions sont fréquemment interdites. Seuls les CPU superscalaires larges peuvent les permettre, et encore : sous conditions. Le résultat est que le processeur peut se débrouiller avec une seule unité mémoire et une unité de branchement. ===Terminologie pour la suite du chapitre=== Décrire les contraintes d’appariement demande de dire quelles combinaisons d’instructions sont autorisées ou interdites. Et pour cela, nous allons introduire les abréviations suivantes : * INT est un raccourcis pour parler d'une opération entière simple, qu'une ALU entière peut exécuter. * MUL correspond à une opération entière complexe, typiquement une multiplication, éventuellement à une division. * FLOAT désigne une opération flottante, peut importe laquelle. * MEM désigne un accès mémoire, à savoir une instruction ou µop mémoire. * BRANCH désigne une instruction de branchement. Cette terminologie permettra de définir des paires d'instructions, qu'elles soient possibles ou interdites. Par exemple, l'expression INT + FLOAT désigne une paire d'instruction regroupant une instruction entière et une instruction flottante, peu importe l'ordre des deux instructions. Il en sera de même pour les triplets, quadruplets, ou plus. Par exemple, l'expression INT + INT + FLOAT + MUL désigne un quadruplet d'instructions regroupant : deux instructions entières, une instruction flottante, et une multiplication entière. J'utiliserais aussi des parenthèses pour regrouper certaines combinaisons. Par exemple, l'expression INT + (MUL /FLOAT) veut parler d'une paire d'instruction où la première est une opération entière basique, la seconde est : soit une multiplication, soit une opération flottante. ==Le ''front-end'' des processeurs superscalaires== Un processeur superscalaire doit charger, décoder, et exécuter plusieurs instructions par cycle. Par exemple, un processeur triple émission doit charger 3 instructions lors d'un premier cycle, puis les décoder au cycle suivant, et les exécuter au cycle suivant. Il travaille donc pas paquets de 3 instructions, dans cet exemple. Intuitivement, charger, décoder et exécuter plusieurs instructions à la fois demande de dupliquer des circuits. Par exemple, il faut dupliquer les décodeurs pour décoder plusieurs instructions. Les unités de calcul sont aussi dupliquées, sauf sur certaines processeurs qui arrivent à ruser, mais laissons cela de côté pour plus tard. Mais d'autres circuits sont simplement modifiés, comme l'unité de chargement ou l'unité d'émission. Voyons ce qu'il en est dans le détail. {|class="wikitable" |- ! rowspan="2" | Processeur sans émission multiple | rowspan="2" | Chargement | rowspan="2" | Décodage | rowspan="2" | Renommage | rowspan="2" | Émission | Exécution / ALU | rowspan="2" | ''Commit''/ROB |- | Exécution / ALU |- ! rowspan="4" | Processeur superscalaire | rowspan="4" | Chargement | rowspan="2" | Décodage | rowspan="4" | Renommage | rowspan="4" | Émission | Exécution / ALU | rowspan="4" | ''Commit''/ROB |- | Exécution / ALU |- | rowspan="2" | Décodage | Exécution / ALU |- | Exécution / ALU |} Nous allons d'abord voir ce qu'il en est pour le ''front-end'', à savoir la partie du CPU qui s'occupe de charger et décoder les instructions. Elle regroupe l'unité de chargement, les décodeurs, l'unité de prédiction de branchement, la file de µops, la file d'instruction, et divers caches. En soi, les changements portent surtout sur l'unité de chargement et les décodeurs. ===Le chargement de plusieurs instructions consécutives=== Un processeur superscalaire charge plusieurs instructions en même temps, ''en tenant compte de la présence de branchements''. La partie en italique est importante et elle a une conséquence très forte : la manière de faire est fortement différente entre un CPU superscalaire étroit et large. Un CPU superscalaire étroit fait au plus simple : il charge un bloc de 2, 3, 4 instructions consécutives en mémoire RAM. Il faut alors doubler/tripler/quadrupler la taille du bus connecté au cache d'instruction. Par exemple, pour charger quatre instructions de 32 bits chacune, il suffit de lire un bloc de 128 bits dans le cache d'instruction. Un bloc de 8, 16, 32 octets est donc lu depuis le cache et est ensuite découpé en instructions, envoyées chacun à un décodeur. Charger plusieurs instructions consécutives ne marche bien qu'en absence de branchements. La présence de branchements casse cette dynamique, et il faut gérer leur présence. * Sans prédiction de branchement, toutes les instructions d'un bloc sont décodées et exécutées en même temps, même s'il y a un branchement dans le tas. * Avec prédiction de branchement, les instructions situées après le branchement ne sont pas décodées ni exécutées. L'unité de chargement coupe le bloc chargé au niveau du premier branchement pris, remplit les vides avec des NOP, avant d'envoyer le tout à l'unité de décodage. Les instructions à l'adresse de destination seront chargées au cycle suivant, en chargeant un nouveau bloc. [[File:Fetch sur un processeur superscalaire avec prediction de branchements.png|centre|vignette|upright=2|Fetch sur un processeur superscalaire avec prédiction de branchements.]] Un autre défaut est que sur le CPU charge un bloc de 128 bits à la fois, les instructions doivent être alignées sur 128 bits. Et on peut généraliser à n'importe quelle taille de bloc. Si les instructions ne sont pas alignées, il faut gérer la situation. Il arrive qu'une instruction soit à cheval sur deux blocs, avec un morceau dans le premier bloc, un autre dans le second. Dans ce cas, l'instruction est chargée en deux fois, et des circuits doivent gérer la situation pour recoller les morceaux. Sur les CPU avec des instructions de taille fixe, le problème ne survient que rarement. Mais il est fréquent avec des instructions de taille variable. Un autre défaut est lié aux branchements. Imaginez qu'un branchement s'exécute et branche vers une instruction de destination. Rien n'indique que l'instruction de destination sera au tout début d'un bloc. Si ça se trouve, elle sera en troisième position d'un bloc de 4 instructions. Une solution simple charge le bloc en respectant l'alignement, et les deux premières instructions du bloc ne sont pas à exécuter. Il y a donc une petite perte de performance. Certains CPU, comme l'IBM RS/6000, ont la machinerie charger un bloc de N instructions même s'il n'est pas aligné. La machinerie en question se trouve dans le cache d'instruction et est tout sauf simple à expliquer,ce qui fait que je me permets de la passer sous silence. Un détail, cependant : elle ne permet pas de charger des blocs qui sont à cheval sur deux lignes de cache. ===Le décodage parallèle des instructions=== Pour le décodage des instructions, plusieurs instructions sont décodées en même temps. On parle alors de '''décodage parallèle'''. Pour ce faire, un processeur superscalaire contient plusieurs décodeurs, chacun pouvant décoder une instruction en parallèle des autres. Prenons par exemple un processeur superscalaire à 4 voies : le décodage se fera dans quatre décodeurs séparés, qui fonctionneront en parallèle. Cependant, cette implémentation ne marche bien que pour les processeurs RISC, les processeurs CISC devant faire avec un microcode gourmand en circuits. Les processeurs CISC utilisent des décodeurs hybrides, avec un microcode qui complémente un décodeur câblé. Dupliquer le microcode aurait un cout en transistors trop important, ce qui fait que seuls les décodeurs câblés sont dupliqués. Les CPU CISC superscalaires disposent donc de plusieurs décodeurs simples, capables de décoder les instructions les plus courantes, avec un seul microcode. La conséquence est qu'il n'est pas possible de décoder deux instructions microcodées en même temps. Par contre, il reste possible de décoder plusieurs instructions non-microcodées ou une instruction microcodée couplée à une instruction non-microcodée. Vu qu'il est rare que deux instructions microcodées se suivent dans un programme, le cout en performance est extrêmement mineur. Il faut noter qu'une instruction correspond à une ou plusieurs µops. Il est donc possible de charger 5 instructions et de se retrouver avec 8 µops en sortie des décodeurs. Et c'est la source d'une distinction très importante. Plus haut, j'ai parlé de processeurs superscalaires à N voies. La terminologie peut avoir deux interprétations. La première est que le processeur charge/décode N instructions à la fois. Une seconde est qu'il est capable d'exécuter/émettre N µops en même temps. Les deux ne sont pas équivalentes, vu qu'un processeur superscalaire peut, par exemple, charger/décoder N instructions et émettre/exécuter N+2 µops. La distinction est particulièrement importante sur les processeurs superscalaires larges. Certains d'entre eux sont capables de charger 5 instructions et d'en émettre le double ! Un exemple classique était les processeurs Athlon d'AMD, qui pouvaient décoder deux instructions par cycle, mais émettre 4 µops par cycle. Ce n'était pas un problème, car beaucoup d'instructions, même basiques, étaient décodées en 2 µops. ===L'émission parallèle des instructions=== La superscalarité modifie en profondeur l'unité d'émission, notamment au niveau de son interface. Avant, elle recevait une micro-opération sur son entrée, et fournissait la micro-opération émise sur une sortie. Et cela vaut aussi bien pour une unité d'émission simple, un ''scoreboard'', une fenêtre d'instruction ou des stations de réservation. Mais avec l'émission multiple, les sorties et entrées sont dupliquées. Pour la double émission, il y a deux entrées vu qu'elle doit recevoir deux micro-opération décodées/renommées, et deux sorties pour émettre deux micro-opérations. Dans ce qui suit, nous ne parlerons pas d'entrée et de sortie, mais de '''ports de décodage''' et de '''ports d'émission'''. Les ports de décodage reçoivent des micro-opérations provenant de l'unité de décodage, les ports d'émissions sont pour les micro-opérations émises. Les ports d'émission injectent les micro-opérations émises dans le pipeline, elle sont propagée au banc de registre, aux ALUs, etc. Un processeur superscalaire a forcément plusieurs ports d'émission (et de décodage). Si l'interface de l'unité d'émission change, l'intérieur est tout autant modifié. Sur un CPU non-superscalaire, l'unité d'émission reçoit une µop "candidate à l'émission", et détecte les dépendances entre avec les µops en vol, en cours d'exécution dans le pipeline. Et c'est toujours le cas sur les processeurs superscalaires : l'unité d'émission détecte les dépendances entre les µops candidates et les µops en vol. Mais avec la siperscalarité, elle doit en plus tester les dépendances entre les µops candidates. Si l'unité d'émission est un ''scoreboard'', il doit détecter les dépendances entre instructions émises simultanément et les sérialiser si besoin. Avec une fenêtre d'instruction, il n'y a pour ainsi dire rien à faire, vu que les dépendances sont éliminées par le renommage de registre et que les signaux de réveil gèrent les dépendances RAW. C'est la raison pour laquelle les processeurs superscalaires modernes n'utilisent pas de ''scoreboard''. ==Les processeurs implémentés avec plusieurs pipelines== Nous venons de voir comment la superscalarité impacte le ''front-end'', il est maintenant de voir ce qu'il en est de l'unité d'émission et des ''back-ends''. Un processeur superscalaire peut utiliser l'exécution dans le désordre et le renommage de registres, mais ce n'est pas une obligation. Historiquement, les premiers processeurs superscalaires n'utilisaient pas d'exécution dans le désordre. Le meilleur exemple est celui du Pentium 1 et 2 d'Intel. Ils étaient tous deux des processeurs superscalaires, mais n'avaient pas d'exécution dans le désordre, qui est apparue sur le Pentium 3. les explications sont plus simples si on met de côté l'exécution dans le désordre. Vous vous dites que l'implémentation d'un processeur superscalaire n'est pas la même suivant que le CPU utilise l'exécution dans le désordre, ou non. Mais dans les faits, la distinction est plus subtile que ca. Pour l'expliquer, je vais utiliser une classification originale, qui sépare les processeurs superscalaires en trois sous-types. La distinction se fait sur la manière dont la superscalarité est implémentée en matériel.La classification que j'utilise distingue : * Les processeurs avec plusieurs pipelines, sans exécution dans le désordre. * Les processeurs superscalaires avec un pipeline dynamique. * Les processeurs avec des fenêtres d'instruction décentralisées. La distinction n'est pas qu'une question d'implémentation matérielle. Elle impacte aussi le ratio entre instructions chargées/décodées et instructions émises. Vous vous dites que les deux sont égales, mais ce n'est le cas que sur les processeurs avec N pipelines ! Sur les autres processeurs, il est possible de décoder 4 instructions, mais d'en émettre maximum 6 par cycle ! Et sur les CPU avec des fenêtres d'instruction décentralisées, il y a aussi une différence entre le nombre d'instructions qui sont ''dispatch'' et ''schedule''. La catégorie la plus simple est celle des '''processeurs avec plusieurs pipelines'''. Ce sont des processeurs superscalaires sans exécution dans le désordre. Ils partent d'un processeur non-superscalaire, et ajoutent un second pipeline, en sortie de l'unité d'émission. Le second pipeline permet l'émission parallèle d'une seconde instruction. Le second pipeline n'est pas identique au pipeline originel, sur plusieurs points. Premièrement, il n'a pas exactement les mêmes circuits. Le second pipeline est généralement composé d'une unité de calcul, d'une FPU, éventuellement d'un second banc de registres, guère plus. Il permet d'exécuter une seconde opération de calcul, pas plus, ce qui est à l'origine de contraintes d'appariemment assez fortes. De plus, il n'a pas forcément la même longueur, il peut avoir des étages en moins ou en plus. Il s'agit de la forme la plus simple de processeur superscalaire et nous allons leur consacrer cette section. ===La double émission entière-flottante=== L'exemple le plus simple utilise un second pipeline pour les opérations flottantes, séparé du pipeline originel. L'ajout d'un pipeline flottant permet d'émettre une micro-opération entière en même temps qu'une micro-opération flottante, ce qui s'appelle la '''double émission entière-flottante'''. La micro-opération entière s'exécute dans l'ALU entière, la micro-opération flottante dans la FPU. Les exemples les plus notables sont le PA-RISC 7100, le POWER 1 (RIOS .& et .9) et le PowerPC 601. L'Apollo DN10000 et l'Intel i860 utilisaient aussi cette forme de double émission, mais étaient des prototypes de CPU VLIW. [[File:Double émission entière-flottante.png|centre|vignette|upright=2|Double émission entière-flottante]] Pour bien expliquer les choses, prenons le processeur illustré ci-dessous, avec deux ''back-end'' séparés : un pour la FPU, et un autre pour le reste. Les instructions entières et mémoire passent toutes par le ''back-end'' généraliste. [[File:CPU non-superscalaire basique, avec une FPU.png|centre|vignette|upright=1.5|CPU non-superscalaire basique, avec une FPU]] Les deux ''back-end'' sont des pipelines séparés, mais il faut que l'unité d'émission soit spécialement conçue pour en profiter. Un processeur simple émission peut démarrer une seule µop par cycle : soit la µop est envoyée au ''back-end'' flottant, soit elle est envoyée au ''back-end'' général. Avec la double émission entière-flottante, le processeur peut envoyer une µop dans chaque pipeline, à chaque cycle. Vu que la FPU et son banc de registre forment déjà un pipeline séparé, les seules modifications se font au niveau du ''front-end'', unité d'émission inclue. {| |- |[[File:CPU simple émission avec une FPU.png|centre|vignette|upright=1.5|CPU simple émission avec une FPU.]] |[[File:Double émission entière flottante avec un second pipeline.png|centre|vignette|upright=1.5|CPU avec un second pipeline flottant.]] |} Notez que le terme double émission ''entière''-flottante est un peu trompeur. En réalité, la technique permet d'émettre une instruction flottante avec n'importe quelle autre instruction non-flottante, avec des restrictions pour les branchements. Il est par exemple possible d'exécuter un calcul flottant en parallèle d'un accès mémoire. Sur les CPU 32 bits, il est possible de faire les multiplications dans la FPU. Pour rappel, la FPU contient un multiplieur entier 53-54 bits, pour multiplier les mantisses de deux flottants 64 bits, qu'on peut utiliser pour faire les multiplications entières 32 bits. Cela permet d'émettre une multiplication en même temps qu'une opération entière dans l'ALU. Ainsi, le CPU pouvait émettre deux instructions : une opération entière de base, avec soit une multiplication, soit une opération flottante. En clair, une émission de type INT + (MUL /FLOAT). Par contre, on perd la possibilité d'émettre une multiplication entière avec une opération flottante, mais c'était un compromis qui en valait la peine. Le CPU PA-RISC 7100 implémentait cette optimisation. Il s'agissait d'un CPU double émission entière-flottante, qui utilisait donc un partitionnement assez simple. Le combo partitionnement + reconditionnement permettait ici de gagner en performance, pour un cout en matériel très limité. D’autant plus limité que le jeu d'instruction prévoyait le reconditionnement de la FPU. Les multiplications ne lisaient pas leurs opérandes dans les registres entiers, mais dans les registres flottants. ===La double émission entière=== Une autre possibilité ajoute un second pipeline, capable d'exécuter des opérations entières simples, appelé le pipeline entier. Le processeur supporte alors la '''double émission entière''', à savoir qu'ils peuvent émettre une opération entière en plus d'une autre instruction. L'opération entière en question est une opération simple, comme une addition, une soustraction, une opération bit à bit, une comparaison, etc. Les exemples classiques sont les processeurs Intel i960 et Pentium. L'implémentation était différente entre l'i960 et le Pentium : Le Pentium avait une seconde ALU entière, alors que le i960 rusait. Le Pentium dupliquait l'ALU entière, pour créer un second pipeline entier. Sur le Pentium originel, les contraintes d'appariement étaient assez strictes. Le Pentium avait une FPU, mais ne pouvait pas l'utiliser en double émission. Il lui était impossible d'exécuter une opération flottante en même temps qu'une opération entière (branchements inclus). Le Cyrix 6x86 était une sorte de Pentium produit par l'entreprise Cyrix, avec une microarchitecture différente. Il avait des contraintes d’appariement réduites. Par exemple, il pouvait émettre une opération flottante en parallèle d'une opération entière. Nous détaillerons ces processeurs dans le prochain chapitre, qui porte justement sur les CPU superscalaires x86. [[File:CPU simple et double émission avec un second pipeline entier.png|centre|vignette|upright=2.5|CPU simple et double émission avec un second pipeline entier.]] L'Intel i960 n'avait pas de FPU, juste une ALU entière et une unité mémoire. Il utilisait l'unité mémoire comme une seconde ALU entière limitée. Pour rappel, une unité mémoire contient une AGU, à savoir une ALU spécialisée dans les calculs d'adresse. Elle est capable de faire des additions, soustractions, et quelques décalages limités (des décalages par 2, 4, 8, pas plus). L'Intel i960 ajoutait des connexions entre AGU et banc de registre, afin que celle-ci serve de seconde unité de calcul. L'AGU est déjà reliée aux deux ports de lecture, pour lire les opérandes du calcul d'adresse, il suffit de la connecter sur le port d'écriture. Le processeur peut alors émettre deux instructions/µops entières, sous certaines conditions. [[File:Emission multiple des opérations entières, double émission.png|centre|vignette|upright=2|Émission multiple des opérations entières, double émission.]] ==Les processeurs superscalaires dynamiques== Les processeurs précédents, avec 2 pipelines, avaient une unité d'émission assez simple. Elle avait deux ports de décodage, et deux ports d'émission. Le nombre de ports de décodage et d'émission étaient identiques. Vous vous dites que c'est naturel, et intuitif, que c'est normal pour un processeur superscalaire. Mais en réalité, la majorité des processeurs superscalaires a plus de ports d'émission que de décodage. Expliquer pourquoi va cependant nous demander de parler de comment la superscalarité exploite les unités de calcul. Posons-nous cette question : de combien d'unités un processeur superscalaire a besoin ? Les unités en question ne sont nécessairement des unité de calcul, l'unité mémoire et l'unité de branchement comptent aussi. La réponse dépend du nombre de voies. Si le ''front-end'' charge et décode un paquet de N instructions, exécuter ces N instructions simultanément demandera là aussi au moins N unités de calcul. Notez le terme : ''au moins'', car c'est là tout l'enjeu de cette section. La réponse dépend aussi des contraintes d’appariement. Pour un CPU superscalaire "parfait", sans contraintes d’appariement, toutes les unités doivent être dupliquées, y compris l'unité mémoire. Un processeur à double émission parfaite devrait dupliquer l'ALU entière, le ''barrel shifter'', le circuit multiplieur, la FPU, l'unité mémoire, et l'unité de branchement, ce qui ferait passer de 6 unités à 12. Le cout en circuit est énorme, même pour de la double émission ! Et pour un CPU à N pipelines, le cout en circuit encore plus prohibitif ! En pratique, les processeurs superscalaires qui font ça sont très rares. La majeure partie des processeurs superscalaires utilisent d'autres techniques : le partitionnement, la duplication partielle des ALUs, la duplication des autres unités. ===Le partitionnement des unités fonctionnelles=== Les CPU superscalaires les plus simples ne dupliquent pas leurs unités de calcul, ils se contentent d'exploiter les unités existantes. Et pour expliquer comment, nous allons partir de l'exemple d'un processeur non-superscalaire contient une ALU entière, une FPU, une unité mémoire et une unité de branchement. Les quatre unités peuvent fonctionner en parallèle, à savoir qu'une unité peut exécuter une instruction à elle toute seule, sans interagir avec les autres. Cela suffit pour créer un processeur quadruple émission, qui peut émettre en même temps : une opération entière, une opération flottante, un branchement, un accès mémoire. Le principe marche aussi sur un processeur avec moins de 4 unités, par exemple sans FPU et/ou sans unité de branchement, ou avec au contraire des unités en plus. L'idée générale est d’émettre une µops séparée dans chaque unité fonctionnelle. Utiliser ainsi les unités existantes, pour rendre un CPU superscalaire, s'appelle le '''partitionnement superscalaire'''. Le terme n'est pas beaucoup utilisé en-dehors de quelques vieux articles académiques. Le partitionnement est fortement dépendant du jeu d'instruction, car il demande de bien séparer instructions entières, flottantes, branchements et instructions mémoire. Et autant c'est bien le cas sur les CPU RISC, autant les CPU CISC ne sont pas dans ce cas. Les CPU CISC ont des instructions ''load-op'', qui effectuent une instruction arithmétique et une µop mémoire. Et ces instructions sont très compliquées à implémenter avec le partitionnement. Et au-delà de ça, les CPU CISC ont beaucoup d'instructions qui sont décodées en plusieurs µops, à exécuter dans un ordre bien précis. On peut pas émettre les µops en question simultanément. Le partitionnement a un cout en circuit très limité, pour un gain en performance lui aussi limité. Pour reprendre le CPU de l'exemple précédent, il est rare que l'on ait à exécuter ces quatre instructions en même temps. En pratique, les combinaisons fréquentes sont les combinaisons calcul + accès mémoire, calcul + branchement, calcul entier + calcul flottant. Utiliser une quadruple émission de ce type n'est donc pas une bonne idée, alors que la double émission parait plus adaptée. Et c'est là que la différence entre ports d'émission et de décodage devient intéressante. L'idée est d'avoir une unité d'émission avec deux ports de décodage, et 4 port d'émission (un par unité). Les contraintes d’appariement varient alors suivant le processeur, mais la règle générale est que si deux micro-opérations tombent dans deux unités différentes, on peut les émettre en même temps. En clair, on peut émettre une opération entière avec une opération flottante, une opération entière avec un branchement, un accès mémoire avec un branchement, etc. Et cela rend le partitionnement bien plus intéressant que prévu. Les tout premiers CPU superscalaires étaient des CPU double émission, qui utilisaient le partitionnement avec : une ALU entière, une unité mémoire et une FPU. Nous parlerons de '''partitionnement INT-FLOAT-MEM'''. L'avantage sur la double émission flottante est que les combinaisons INT + MEM et FLOAT + MEM étaient autorisées. L'intérêt était d'émettre une opération en parallèle d'un accès mémoire, ce qui est un doublet d'instruction assez fréquent. Les exemples classiques sont l'HyperSPARC, de l'alpha 21064 et du PowerPC 603. Les contraintes d’appariement variaient grandement suivant le processeur. Le PowerPC 603 implémentait une double émission "parfaite". C'est à dire que toutes les combinaisons possibles à deux instructions étaient possibles, que ce soit INT + FLOAT, INT + MEM, FLOAT + MEM. En plus de cette double émission "parfaite", le CPU utilisait la technique dite de ''branch folding'', qui permettait d'exécuter un branchement sans utiliser d'unité de calcul, ce qui fait qu'il était qualifié de ''CPU 2-way + Branch''. A l'opposé, le processeur alpha 21064 avait pas mal de contraintes d’appariement, à savoir que certaines combinaisons de deux instructions étaient interdites, bien que compatibles avec les unités disponibles. Une des raisons est que le banc de registre n'avait pas assez de ports de lectures, ce qui fait que certaines combinaisons avec une écriture mémoire n'étaient pas permises. Les concepteurs avaient volontairement inséré ces contraintes afin de simplifier le design du processeur. Avant de poursuivre, précisons que la FPU regroupe un additionneur flottant et un multiplieur flottant. Et ce fait peut être exploité pour approfondir le partitionnement. La '''double émission FADD/FMUL''' permet d'émettre une addition flottante en même temps qu'une multiplication flottante. Elle a été utilisée sur les processeurs x86, Alpha et SPARC. Les autres circuits de calcul flottant sont souvent regroupés avec l'additionneur. La division est parfois e même port que le multiplieur flottant, parfois sur celui de l'additionneur (quand elle est microcodée et exécutée en enchainant les soustractions). ===La duplication des ALU entières=== Le partitionnement est une technique élégante, simple, facile à implémenter et au cout en circuits modeste. Mais il faut avouer que cela ne donne pas des CPU superscalaires très efficaces. La raison est que le partitionnement permet d'émettre des paires, triplets, quadruplets de micro-opérations qui sont rares en pratique. Les combinaisons les plus fréquentes en pratique sont des paires/triplets d'opérations entières, souvent des paires/triplets d'additions ou d'opérations entières simples. Il est donc primordial d'émettre plusieurs opérations entières simultanément. Le fait d'émettre et d'exécuter plusieurs opérations entières en même temps s'appelle l''''émission entière multiple'''. Pour exécuter la seconde/troisième opération entière, il faut dupliquer les ALU entières (ou réutiliser l'AGU comme ALU entière comme le fait l'Intel i960). Le processeur doit avoir deux ALU entières pour de la double émission entière, trois pour de triple émission entière, etc. De plus, il faut ajouter des ports au banc de registre, pour lire deux fois plus d'opérandes et décrire deux résultats. Et enfin, il faut rajouter un port d'émission pour pouvoir émettre une micro-opération entière en plus. [[File:Processeur non-superscalaire basique 03.png|centre|vignette|upright=2|Processeur non-superscalaire basique]] La tendance est de multiplier les ALU entières, mais pas le circuit multiplieur. Le cout en transistors est alors grandement réduit, au prix de l'apparition de dépendances structurelles. Le CPU peut émettre deux additions en même temps, ou une addition et une multiplication, mais pas deux multiplications. Il en est de même avec le ''barrel shifter'', qui n'est pas dupliqué. Il est donc possible d'émettre un décalage avec une opération de base, mais pas deux décalages en parallèle. Mais ces contraintes d’appariement ne sont pas un problème, car il est rare de devoir émettre deux multiplications/décalages en même temps. La plupart des programmes sont majoritairement remplis d'additions, avec des multiplications assez rares et des décalages qui le sont encore plus. La moyenne est proche d'une multiplication pour 4/5 additions. Vu que des multiplications consécutives sont très rares, dupliquer le multiplieur est presque inutile. Disposer de plusieurs circuits multiplieurs ou de plusieurs ''barrel shifters'' serait donc un cout en circuits qui ne servirait que rarement et n'en vaut pas la chandelle. Il faut noter que sur les CPU 32 bits, les multiplications peuvent être exécutées dans la FPU ou dans un multiplieur séparé, tout dépend de si le CPU exécute les multiplications dans la FPU ou pas. {|class="wikitable" |- ! Voie d'émission n°1 !! Voie d'émission n°2 !! Voie d'émission n°3 |- | ALU entière || ALU entière || FPU |} Reste à connecter les unités de calcul à l'unité d'émission (indirectement, car il y a des étages de pipeline entre les deux). Une implémentation basique utilisee un port d'émission par unité. Prenons l'exemple d'un CPU superscalaire avec 4 ALU entières, une FPU, et une unité mémoire. Vu qu'il y a 6 unités, cela fait donc 6 ports d'émission. L'exemple historique le plus impressionnant est celui du Motorola 88110, un CPU double émission qui possède 10 unités. Il contient deux ALU entières, un circuit multiplieur, un circuit diviseur, une unité de manipulation de bits, une unité mémoire, un additionneur flottant, et deux unités graphiques pour des calculs impliquant des pixels. Le multiplieur est utilisé à la fois pour les multiplications entières, flottantes et le produit de deux pixels. Idem pour le diviseur, qui sert pour les divisions entières et flottantes. Et chaque unité a son propre port d'émission. La conséquence est que les contraintes d'appariement du Motorola 88110 sont simples : tant que deux µops atterrissent dans des unités séparées, la paire est permise. Il est par exemple possible d'émettre simultanément deux opérations entières simples, vu qu'il y a deux ALU. Par contre, impossible d'émettre simultanément deux multiplications, deux divisions, deux opérations flottantes, deux manipulations de bit, deux accès mémoire, etc. De plus, le partage du multiplieur fait qu'on ne peut pas émettre une multiplication entière avec une multiplication flottante. Idem avec les divisions, vu que le diviseur est aussi partagé. Ce sont les seules contraintes. ===Le partage des ports d'émission=== La présence d'un circuit multiplieur et d'un ''barrel shifter'' pose des problèmes pour le partitionnement. Il est possible d'avoir un port d'émission dédié au multiplieur, et un autre au ''barrel shifter'', qui sont séparés de ceux pour l'ALU entière. Il est alors possible d'émettre une opération entière, une multiplication et un décalage, simultanément. Nous parlerons de '''partitionnement entier''' pour désigner cette possibilité. [[File:Partitionnement superscalaire entier.png|centre|vignette|upright=2|Partitionnement superscalaire entier.]] Le partitionnement entier est cependant très rare, car le gain en performance serait trop faible au regard des couts en circuit. Quelques processeurs historiques l'utilisaient, mais ce n'est plus le cas de nos jours. La raison est que les multiplications et les décalages sont assez rares dans le code, ce qui fait que le port d'émission associés sont sous-utilisés. Et un port d'émission a un cout en circuit assez important. Et c'est sans compter qu'ajouter des ports sur le banc de registres juste pour un multiplier sous-utilisé n'en vaut pas la peine. L'idéal serait de trouver un moyen de réduire le nombre de ports d'émissions, sans réduire le nombre d'unités de calcul. [[File:Emission multiple des opérations entières, implémentation naive.png|vignette|Émission multiple des opérations entières, implémentation naive]] La solution consiste à '''partager les ports d'émission''', à savoir connecter un port d'émission à plusieurs unités de calcul. L'exemple classique est justement lié aux circuits multiplieur et ''barrel shifters''. L'idée est que le multiplieur et le ''barrel shifter'' sont mis sur le même port d'émission qu'une ALU entière. Prenons par exemple le processeur à double émission entière illustré ci-contre. Un port d'émission est relié à une ALU entière seule, et l'autre port d'émission alimente la seconde ALU entière et le multiplieur. La conséquence est que le processeur peut émettre deux opérations entières, dont une multiplication. Si on avait utilisé un port d'émission dédié au multiplieur, il aurait été possible d'émettre deux opérations entières et une multiplication. On perd donc une voie, mais on gagne en flexibilité et on économise des circuits. Notons que ce partage signifie aussi que le multiplieur partage les ports sur le banc de registre, ce qui fait une double économie. [[File:Emission multiple des opérations entières, implémentation courante.png|centre|vignette|upright=1.5|Émission multiple des opérations entières, implémentation courante]] En général, le ''barrel shifter'' et le multiplieur sont placés sur deux voies d'émission séparés, ce qui permet d'émettre un décalage et une multiplication en même temps. Les mettre sur le même port ne le permettrait pas. {|class="wikitable" |- ! Voie d'émission n°1 !! Voie d'émission n°2 !! Voie d'émission n°3 |- | * ALU entière * Multiplieur | * ALU entière * Barrel shifter | FPU |- | * Additions, soustractions, opérations bit à bit, etc. * Multiplication | * Additions, soustractions, opérations bit à bit, etc. * Décalages et rotations | Opération flottante |} Mais le partage des ports d'émission peut appliquer à n'importe quelle unité. Prenons l'exemple d'un CPU superscalaire avec 4 ALU entières, une FPU, et une unité mémoire. Vu qu'il y a 6 unités, cela fait donc 6 ports d'émission. Cependant, il est rare qu'un bloc de 6 instructions contienne exactement 4 opérations entières, une opération flottante, et un accès mémoire. Beaucoup de ports d'émission seraient sous-utilisés. La solution est de partager des ports d'émission, pour passer à la quadruple émission. Le CPU peut alors répartir 4 instructions sur les ports d'émission adéquats, à chaque cycle. Le CPU a donc 4 ports de décodage et 6 ports d'émission. Une implémentation classique permet n'importe quelle combinaison de 4 instructions compatible avec les 6 unités. Par exemple, il devient possible d'émettre simultanément 4 additions, la combinaison 2 * INT + FLOAT + MEM, 3 * INT + MEM, et bien d'autres. L'exemple relie une ALU entière, la FPU et l'unité mémoire sur le même port. Le CPU a alors quatre ports d'émission, qui ne sont pas "identiques". Trois d'entre eux sont reliés à une ALU entière chacun, rien d'autre. Le quatrième est lui relié à une ALU entière, la FPU et l'unité mémoire. On économise alors deux ports d'émission, mais cela entraine l'apparition de contraintes d’appariement. Le CPU peut émettre 3 opérations entières + n'importe quelle autre opération. Mais les autres combinaisons sont interdites. Le CPU peut émettre 6 opérations entières simultanément, grâce à 6 ALU entières. Les accès mémoire ou opérations flottantes ne viennent pas en plus, mais en remplacement des opérations entières. Regrouper plusieurs ALU sur un même port d'émission est donc à l'origine de dépendances structurelles, de nouvelles contraintes d’appariement. Dans certaines situations de '''''port contention''''', on a assez d'ALU pour exécuter N micro-opérations, mais la répartition des ALUs sur les ports l'empêche. Impossible d'émettre deux micro-opérations sur deux ALU si elles sont sur le même port d'émission. On peut réduire les cas de ''port contention'' en répartissant correctement les ALU sur les ports d'émission, mais sans jamais les supprimer complétement. Un exemple est celui du PowerPC 604. Il implémentait la quadruple émission, alors qu'il avait 5 unités de calcul. Mais deux unités partageaient un même port d'émission. Dans le détail, il avait deux ALU entières, un circuit multiplieur/diviseur, une FPU, une unité mémoire. La FPU et le multiplieur étaient sur le même port d'émission. En conséquence, le CPU pouvait émettre seulement les combinaisons INT + INT + MEM + (MUL/FLOAT). : Il est intéressant de voir l'exemple d'un processeur superscalaire parfait, sans contraintes d'appariement, à N voies. Pour cela, on part d'un processeur non-superscalaire, avec M unités de calcul. Un tel processeur peut s'implémenter avec N ports d'émission, chacun relié à M unités de calcul possibles, ce qui fait que les M unités de calcul sont dupliquées en N exemplaires. Si on prend par exemple un CPU non-superscalaire avec une ALU, une FPU, une unité mémoire et une unité de branchement, un port d'émission sera relié à une ALU, une FPU, une unité mémoire et une unité de branchement. ===La duplication des autres unités fonctionnelles=== Dupliquer les ALU entière est une première étape, mais il est aussi possible de dupliquer les autres unités fonctionnelles. Par exemple, il est possible de dupliquer l'unité mémoire ou l'unité de branchement. Reste à voir quelles unités dupliquer, et pourquoi. Précisons que dans ce qui va suivre, nous allons parler de la duplication des unités en général, pas seulement des unités mémoire/branchement. Nous allons continuer de parler de la duplication des ALU entières dans ce qui suit, mais de manière plus générale. Pour un processeur à N voies, il est théoriquement possible de dupliquer toutes les unités en N exemplaires. Mais ce n'est jamais fait en pratique, car le cout en circuits serait trop important et le gain en performance trop faible. Mieux vaut dupliquer certaines unités bien précises, quitte à avoir des contraintes d'appariement. Et à ce petit jeu, toutes les duplications ne se valent pas, certaines sont plus rentables en matière de performance, le cout en circuit n'est pas le même selon l'unité, etc. La rentabilité en performance dépend surtout des contraintes d'appariement. Les contraintes d'appariement entrainent une baisse de performance significatives si elles interdisent des paires d'instructions fréquentes. Aussi, les opérations très fréquentes, souvent appairées, gagnent à dupliquer l'unité associée. Et au contraire, les unités rarement utilisées ne doivent pas être dupliquées. Il se trouve que les unités les plus utilisées sont les ALU entières, suivies par les unités mémoire et l'unité de branchement. Le cout en circuit n'est aussi pas le même. Dupliquer les ALU entière est trivial, car elles ont un budget en transistors assez faible. Les circuits multiplieurs, diviseurs, et les ''barrel shifter'' ont eux un cout en transistor plus élevés. Si on devait faire un classement, on aurait : ALU entière < ''barrel shifter'' < multiplieur. Pour les unités mémoire, le cout en transistor est là aussi conséquent. Le résultat final est que dupliquer les ALU entières est privilégié sur le reste, comme l'a vu dans la section précédente. Les unités mémoire arrivent en seconde place, suivie par les multiplieurs/''barrel shifter'' à la troisième place, et les unités de branchement à la quatrième place. Les CPU superscalaires larges ont donc tendance à multiplier les unités mémoire en second. Les multiplieurs et ''barrel shifters'' sont eux aussi dupliqués sur les CPU superscalaires larges, mais cela vient souvent après avoir dupliqué les unités mémoire. La raison est que les accès mémoire sont critiques pour la performance des CPU à exécution dans le désordre, pas les multiplications. Pour les unités de branchement, une subtilité complique le fait d'en installer plusieurs. Que faire quand deux branchements sont pris ? La solution la plus simple est de ne pas se trouver dans cette situation : on n'utilise qu'une seule unité de branchement et on n’exécute qu'un seul branchement par cycle. La majorité des processeurs superscalaires étroits font ainsi. Les processeurs superscalaires larges ont cependant deux unités de branchements, mais ils n'acceptent qu'un seul branchement pris à la fois. Si les deux branchements pris sont exécutés en même temps, le processeur n’exécute que le plus récent, dans l'ordre du programme. ===Les CPU superscalaires mélangent les trois techniques=== Les CPU superscalaires utilisent toutes les techniques à la fois : partitionnement, duplication de l'ALU entière, duplication des autre unités fonctionnelles. Chaque processeur utilise ces trois stratégies à sa sauce, en fonction de son budget en transistor. Les quatre techniques ont cependant un cout en circuit différent, et donnent des gains de performances eux aussi forts différents. * Le '''partitionnement superscalaire''' a un cout en circuit modéré, car on n'a pas besoin de dupliquer d'ALU. * La '''duplication des ALU entières''' est la technique la plus importante : elle a un cout en circuit modéré, et donne des gains en performance impressionnants. * La '''duplication des autres unités''' a un cout en circuit variable, pour des gains en performance eux aussi variables. * Le '''partage des ports d'émission''' réduit le cout en circuit du CPU, pour des pertes de performances mineures si bien fait. L'usage du partitionnement seul, sans duplication, était surtout le fait des processeurs commercialisés entre les années 1989 et 1993. Le partage des ports d'émission a lui aussi mis un peu de temps pour s'imposer. Globalement, les processeurs superscalaires ont évolués progressivement, pour se rapprocher d'une sorte de standard. Mais pour comprendre ce qu'est ce standard, et en quoi les CPU historiques s'en sont progressivement rapprochés, il faut étudier l'histoire des CPU superscalaires. L'histoire des CPU superscalaires a commencé dès 1960, mais est resté un projet de recherche pendant plusieurs décennies. De nombreux designs ont été conçus, mais abandonnés avant la production, comme l'ACS-1 d'IBM. Les années 1980 ont vu l'arrivée de prototypes de processeurs VLIW, mais qui n'étaient pas superscalaires. Par exemple, l'Apollo DN10000 et l'Intel i860 pouvaient émettre deux instructions à la fois, sous conditions, mais demandaient que le compilateur fasse une bonne partie du travail. Les premiers CPU superscalaires commercialisés sont sortis en 1989. Deux CPUs superscalaires sont sortis cette année là : le RS/6000 d'IBM et l'i960CA d'Intel. Puis, la compétition s'est aussi lancée dans la bataille. Les CPU commercialisés avant l'année 1993 étaient à double émission, à l'exception du SuperSPARC qui utilisait la triple émission. Les modèles après 1993 hésitaient entre double et quadruple émission. Dans le tableau ci-dessous, les processeurs quadruple émission sont en bleu, les triple émission sont en rouge, les autres ne sont pas en couleur. {|class="wikitable" |- ! !! IBM !! HP PA-RISC !! SPARC !! Intel!! Alpha !! Autres |- ! 1989 | RS/6000 || || || i960CA || || |- ! 1990 | || || || || || |- ! 1991 | || || || || || Motorola 88110 |- ! 1992 | RSC || PA 7100 || class="f_rouge | SuperSPARC || || 21064 || |- ! 1993 | PPC 601 || || HyperSPARC || Pentium || || |- ! 1994 | class="f_bleu | POWER 2 || PA 7200 || || || class="f_bleu | 21164 || |- ! 1995 | 603/604 || class="f_bleu | PA 8000 || class="f_bleu | UltraSPARC 1 || Microarchitecture P6 (+ concurrence AMD) || || class="f_bleu | MIPS R1000 |- ! 1996 | class="f_bleu | 620 || || || || class="f_bleu | 21264 || |- ! ... || ... || ... || ... || ... || ... || ... |} Les premiers CPU à double émission sont assez représentatifs des CPU à double émission modernes. Il faut dire que se limiter à deux voies ne permet pas de faire beaucoup de choses. Voici le tableau chronologique disant quel CPU utilisait quelle technique. * Les CPU en vert utilisaient la double émission entière-flottante. * Les CPU en rouge utilisaient la double émission entière "pure". * Les CPU en jaune utilisaient un ''partitionnement complet'' entre ALU, FPU et unité mémoire. * Les CPU sans couleur utilisaient un grand nombre d'unités, avec beaucoup de ports d'émission. * Les CPU barrés utilisent la triple ou quadruple émission et seront vu dans la section suivante. {|class="wikitable" |- ! !! IBM !! HP PA-RISC !! SPARC !! Intel!! Alpha !! Autres |- ! 1989 | class="f_vert | RS/6000 || || || class="f_rouge | i960CA || || |- ! 1990 | || || || || || |- ! 1991 | || || || || || Motorola 88110 |- ! 1992 | class="f_vert | RSC || class="f_vert | PA 7100 || <s>SuperSPARC</s> || || class="f_jaune | 21064 || |- ! 1993 | class="f_vert | PPC 601 || || class="f_jaune | HyperSPARC || class="f_rouge | Pentium || || |- ! 1994 | <s>POWER 2</s> || PA 7200 || || || <s>21164</s> || |} C'est en 1994, que la quadruple émission est arrivée et que les CPU ont essayé divers implémentations. Il y avait alors trois stratégies pour la double/triple/quadruple émission, qui partageaient des problèmes similaires. La première implémentation dupliquait les ALUs et les FPUs, ce qui permettait d'émettre deux opérations flottantes avec deux opérations entières. Le POWER 2 dupliquait ses FPUs à la perfection, alors que le 21264 séparait l'additionneur et le multiplieur flottant. Les accès mémoire étaient traités comme des opérations entières et le processeur pouvait souvent en émettre deux à la fois. Mais une telle organisation n'est plus utilisée de nos jours, car, sans instructions flottantes à exécuter, elle est équivalente à de la double émission. La seconde implémentation utilisait deux ALU entières (dont un multiplieur), une unité mémoire et une FPU.Cette organisation est intéressante, mais encore trop stricte pour donner de bons résultats, les quadruplets avec exactement deux opérations entières, un accès mémoire et une opération flottante étant trop rares. L'usage de ces 4 unités en double ou triple émission a été utilisé sur le PA 7200 et le SuperSPARC. Malheureusement, en plus de problèmes au niveau de son cache, le SuperSPARC avait de nombreuses contraintes d’appariement, car le banc de registre n'avait pas assez de ports pour alimenter certaines combinaisons d'instructions. De plus, s'il émettait une multiplication, il ne pouvait pas émettre de seconde µop. Enfin, une troisième implémentation utilisait un grand nombre d'unités fort différentes, bien plus que quatre, avec au minimum deux ALU entières. Prenons l'exemple de l'UltraSPARC, qui avait : deux ALU entières, une unité mémoire, une unité de branchement, un additionneur flottant, un multiplieur flottant, un diviseur flottant (aussi utilisé pour le calcul des racines carrées), et 2 unités graphiques pour les calculs sur des pixels. En tout, cela fait 9 unités différentes, pour de la quadruple émission. Dans le même genre, les processeurs MIPS R8000 et R10000 avaient une implémentation plus moderne, dans le sens où toutes les unités étaient dupliquées. Le CPU avait deux ALU, deux FPU et deux unités mémoire. Il pouvait donc émettre quatre instructions à la fois, tant que le paquet collait avec ces six unités. Moitié opérations entières, moitié opérations mémoire, avec de quoi remplacer les opérations entières par des opérations flottantes. Le PA 8000, lui, dupliquait toutes ses unités en deux exemplaires, ce qui n'est pas l'idéal, aucun processeur moderne ne fait cela aujourd'hui. Le tout est résumé dans le tableau ci-dessous, avec : * en vert les CPU utilisant la première stratégie ; * en rouge les CPU utilisant la seconde stratégie ; * en jaune les CPU utilisant la troisième stratégie. {|class="wikitable" |+ Processeurs quadruple émission historiques |- ! Année !! Processeur !! Unités |- ! 1991 | class="f_jaune | Motorola 88110 || class="f_jaune | 2 ALU + MUL + DIV + BIT + MEM + FADD + 2 unités graphiques |- |- ! rowspan="2" | 1994 | class="f_vert | POWER 2 || class="f_vert | 2 ALU/MEM + 2 FPU (+ 2 Branchements via ''branch folding'') |- | class="f_rouge | 21164 || class="f_rouge | 2 ALU + FPU + MEM |- ! rowspan="3" | 1995 | PA 8000 || 2 ALU + 2 ''barrel shifters'' + 2 MEM + 2 FPU (FMA) + 2 FPU (DIV et SQRT) + 2 SIMD |- | class="f_jaune | UltraSPARC 1 || class="f_jaune | 2 ALU + MEM + BRANCH + FADD + FMULFDIV + 2 unités graphiques |- | MIPS R8000 et R10000 || 2 ALU + 2 FPU + 2 MEM |- ! rowspan="2" | 1996 | class="f_rouge | 620 || class="f_rouge | 2 ALU + FPU + MEM |- | class="f_vert | 21264 || class="f_vert | 2 ALU/MEM + 2 FPU (FADD + FMUL) |} Malheureusement, ces CPUs historiques n'avaient ni assez d'unités de calcul entières, ni d'unités mémoire, pour remplir la quadruple émission. Environ la moitié des instructions d'un programme correspond à des instructions entières. Pour en profiter, un processeur à N voies doit disposer d'au minimum N/2 ALU entières, l'idéal étant d'aller au-delà. Les CPU superscalaires historiques avaient 2 ALU pour de la quadruple émission, ce qui était très limite. En comparaison, les CPU superscalaires modernes mettent le paquet niveau ALU entière. La plupart des CPU x86 modernes descendent rarement sous les 2/3, la plupart remplit presque la totalité de la largeur du CPU. Pour donner un exemple, la microarchitecture Golden Cove d'Intel est un processeur à 6 voies, avec 5 ALU entières. Pour être précis, un programme informatique exécute environ 40% de son code dans des instructions arithmétiques, un autre 40% dans les accès mémoire, et un le reste pour... le reste. La conséquence est que les processeurs modernes dupliquent leurs unités mémoire. Par exemple, la microarchitecture ''Golden Cove'' d'Intel dispose de trois unités mémoire : deux spécialisées dans les écritures, trois dans les lectures. La microarchitecture ''Gracemont'' utilise la pentuple émission et peut émettre 4 µops entières et 4 µops mémoire. Notez que le ratio est équilibré entre opérations entières et mémoire, avec soit égalité, soit un avantage pour les ALU entières. Une autre régularité est que la largeur totale du processeur peut être occupée par des opérations entières/mémoire. Pour le dire autrement, sur un processeur quadruple émission, il pourra émettre un mix de 4 instructions entière/mémoire, avec zéro opération flottante. Idem avec des processeurs triples ou pentuple émission, voire plus. Si le processeur émet une opération flottante, elle remplace une opération entière. Prenons l'exemple des processeurs AMD K6, sortis en 1997, juste avant les AMD Athlon. Ils avaient 10 unités de calcul, 6 ports d'émission, 4 ports de décodage. Voici exactement comment étaient répartie les unités de calcul sur les ports d'émission. C'était des processeurs quadruple émission, ce qui fait que la moitié de la largeur du processeur sert pour des opérations entières, et l'autre moitié pour les accès mémoire. {|class="wikitable" |- ! Port d'émission n°1 !! Port d'émission n°2 !! Port d'émission n°3 !! Port d'émission n°4 !! Port d'émission n°5 !! Port d'émission n°6 ! |- | ALU entière || ALU entière || FPU || Unité de branchement || Unité LOAD || Unité STORE |- | || Multiplieur || || || || Unité de calcul d'adresse (instruction LEA) |- | || ''Barrel shifter'' || || || || Unité pour les instructions PUSH |} Les microarchitectures K7, K8 et K10, utilisées sur les anciens CPU AMD Athlon, sont un exemple moins représentatif. Ces microarchitectures utilisent le partage des ports d'émission au maximum. C'était des microarchitectures à triple émission, avec six ports d’émission. Il y avait trois ports d'émission pour les opérations flottantes, et trois ports d'émission pour les micro-opérations entières/mémoire. Il y avait trois ALU entières et trois unités mémoire, chaque ALU partageait un port d'émission avec une unité mémoire. Elle pouvait donc émettre soit trois opérations entières, soit trois µops mémoire, soit un mix des deux. En plus des mix avec les opérations flottantes et branchements. {|class="wikitable" |- ! Port d'émission n°1 !! Port d'émission n°2 !! Port d'émission n°3 !! Port d'émission n°4 !! Port d'émission n°5 !! Port d'émission n°6 ! |- | ALU entière || ALU entière || ALU entière || FADD || FMUL || FPU (autres opérations) |- | Multiplieur || || || || || |- | LOAD/STORE || LOAD/STORE || LOAD/STORE || || || |} Les CPU superscalaires larges dupliquent les multiplieurs et/ou les ''barrel shifters'', et même les unités de branchement. La raison est que quand la largeur du CPU augmente, le processeur traite des paquets d'instructions de plus en plus gros. Par exemple, pour un CPU à 10 voies, cela veut dire émettre 10 instructions en même temps. Il est alors plus fréquent d'avoir deux multiplications, deux décalages, deux accès mémoire, deux branchements. Par exemple, la moyenne est d'une multiplication tous les 5 instructions. Les CPU superscalaires à 10 voies ont donc tendance à avoir deux multiplications par paquet d'instruction, en moyenne. Avoir deux multiplieurs est alors un gain en performance clair. Par exemple, la microarchitecture ''Golden Cove'' a deux ''barrel shifters''. C'est une architecture à 6 voies. {|class="wikitable" |- ! Port d'émission n°1 !! Port d'émission n°2 !! Port d'émission n°3 !! Port d'émission n°4 !! Port d'émission n°5 !! Port d'émission n°6 !! Port d'émission n°7 !! Port d'émission n°8 !! Port d'émission n°9 !! Port d'émission n°10 |- | ALU entière || ALU entière || ALU entière || ALU entière || ALU entière || LOAD || LOAD || LOAD || STORE || STORE |- | ''Barrel shifter'' || Multiplieur || Multiplieur || ''Barrel shifter'' || || || || || || |- | Branchements || Diviseur || || Branchements || || || || || || |} ==Les µops mémoire sur les processeurs superscalaires== Plus haut, nous avons volontairement passé sous silence les accès mémoire. La raison à cela est que leur support sur les CPU superscalaire peut se faire de plusieurs manières différentes, qu'il vaut mieux expliquer dans une section dédiée. Les différentes implémentations sont décrites dans la liste suivante, nous allons les voir une par une. * Avec la première méthode, les accès mémoire sont exécutés par le pipeline pour les opérations entières. * Avec la seconde méthode, les accès mémoire ont un pipeline dédié ; c'est l'''émission parallèle des accès mémoire''. * Avec la troisième méthode, il est possible d'émettre plusieurs accès mémoire simultanément : c'est l'''émission multiple des accès mémoire''. Voyons ces trois cas dans le détail. Nous verrons notamment les différentes formes d'émission multiple des accès mémoire. ===Les accès mémoire traités dans le pipeline entier=== Dans le cas le plus simple, les accès mémoire sont traités comme des opérations entières un peu spéciales. L'unité mémoire est sur le même port d'émission que l'ALU entière, ce qui fait que les accès mémoire remplacent des opérations entières. Faire ainsi permet d'utiliser une ALU entière pour les calculs d'adresse, pas besoin de rajouter une unité de calcul d'adresse séparée. Typiquement, un processeur de ce type place l'unité mémoire après l'unité de calcul. Les opérations entières n'utilisent que l'ALU entière, mais pas l'unité mémoire. Les accès mémoire exécutent un calcul d'adresse dans l'ALU entière, puis l'adresse calculée est envoyée à l'unité mémoire. Le tout est illustré ci-dessous, dans le cas d'un processeur historique à double émission entière-flottante. [[File:Processeur non-superscalaire basique 01.png|centre|vignette|upright=1.5|Processeur non-superscalaire basique]] Un exemple de CPU double émission de ce type est celui du Power PC 440, un CPU destiné à l'embarqué. Vu qu'il était destiné à l'embarqué, il n'avait pas de FPU, il n'avait pas besoin de manipuler de nombres flottants. Il n'a donc que des ALU entières, un multiplieur et une unité mémoire. Il dispose de deux ports d'émission. Le premier est connecté à une ALU entière et un circuit multiplieur, le second est relié à l'unité mémoire et une seconde ALU entière. Une organisation simple, mais très efficace ! [[File:PowerPC 440.png|centre|vignette|upright=2|Microarchitecture du PowerPC 440.]] L'implémentation économise une unité de calcul d'adresse, mais n'a pas de cout en performance significatif. Les accès mémoire étant très lents, émettre une µop entière en parallèle d'un accès mémoire ne sert pas à grand-chose. On peut émettre cette µop entière un cycle plus tard, elle sera terminée avant même que l'accès mémoire soit terminé, même si l'accès mémoire se fait dans le cache L1 ou L2. Pour comprendre pourquoi, faisons quelques calculs. Un accès mémoire prend deux-trois cycles quand il tombe dans le cache L1. Et on doit attendre qu'il soit terminé pour en émettre un nouveau. Combien d'instructions arithmétiques peut-on lancer pendant ce temps, sur un CPU triple émission ? Au premier cycle, on peut en émettre 2 (l'instruction mémoire prend une place, donc 3-1), puis 3 au second cycle. Ce qui fait 5 instructions arithmétiques au total. Or, il se trouve qu'il y a une instruction d'accès mémoire pour 5 instructions arithmétiques, environ. Par contre, ce compromis change quand on passe à la quadruple émission, voire au-delà. Dans ce cas, il devient intéressant de pouvoir émettre une nouvelle instruction mémoire à chaque cycle. Ce qui nous amène à la section suivante... ===L'émission parallèle des accès mémoire=== A partir de la quadruple émission, la tendance a été de séparer l'unité mémoire des ALU entières. Cela permet d'émettre des µops mémoire en même temps que des µops entières et/ou flottantes., ce qui porte le nom d''''émission parallèle des µops mémoire''', ou encore ''émission mémoire parallèle''. Par exemple, un processeur quadruple émission peut émettre en même temps deux opérations entières, une opération flottante et un accès mémoire. L'accès mémoire vient en plus des opérations entières, il n'est pas possible de remplacer l'accès mémoire par une opération entière. La technique ne permet pas de faire les calculs d'adresse dans l'ALU entière. Il faut donc utiliser une unité de calcul d'adresse séparée. Elle est intégrée dans l'unité mémoire, qui fait l'interface entre le cache et le banc de registres. Le cout en circuits est très faible, car les unités de calcul d'adresse n'utilisent guère plus qu'un additionneur et un circuit de décalage très simple. L'émission mémoire parallèle est plus facile à implémenter sur les CPU RISC, qui ont des instructions mémoire séparées du reste. Les CPU CISC ne sont pas dans ce cas, ce qui fait que séparer l'unité mémoire du pipeline entier est compliqué. Les CPU superscalaires historiques étaient pour moité des CPU RISC et pour moitié des CPU CISC, ce qui fait qu'ils n'utilisaient pas ou peu l'émission mémoire parallèle. Les premiers processeurs superscalaires se contenaient de l'émission parallèle mémoire, ils ne pouvaient émettre qu'un seul accès mémoire à la fois, l'unité mémoire n'était pas dupliquée. Et ce n'est pas tant un problème que ça, du moins sur les processeurs à double ou triple émission. En effet, il est rare que l'unité mémoire doive faire deux accès simultanés. Notons que le processeur peut exécuter une instruction mémoire en parallèle d'un calcul ou d'un branchement, seuls les accès mémoire simultanés sont impossibles. ===L'émission multiple des accès mémoire avec une LSQ=== Les processeurs superscalaires larges travaillent sur des paquets de 8 à 10 instructions, il est plus fréquent d'avoir plusieurs accès mémoire par paquet. Dans ces conditions, il est utile d'avoir une '''émission multiple des accès mémoire''', à savoir d'émettre plusieurs micro-opérations mémoire en même temps. Les processeurs superscalaires modernes sont capables d'émettre plusieurs lectures/écritures simultanément. Par exemple, ils peuvent émettre une lecture en même temps qu'une écriture, ou plusieurs lectures, ou plusieurs écritures. Il faut noter que selon le processeur, il peut y avoir des restrictions quant aux accès mémoire émis en même temps. Par exemple, certains processeurs peuvent émettre une lecture avec une écriture en même temps, mais pas deux lectures ni deux écritures. Ou encore, ils peuvent émettre deux lectures, une lecture et une écriture, mais pas deux écritures en même temps. Dans la majorité des cas, les processeurs ne permettent pas d'émettre deux écritures en même temps, alors qu'ils supportent plusieurs lectures. Il faut dire que les lectures sont plus fréquentes que les écritures. Les processeurs qui autorisent toutes les combinaisons de lecture/écriture possibles, sont rares. L'émission multiple des accès mémoire s'implémente dans l'unité mémoire. Pour rappel, l'unité mémoire s'interpose entre le cache L1 et le reste du pipeline. Dans le cas le plus simple, elle incorpore une ''load/store queue'' et des unités de calcul d'adresse. L'émission multiple des accès mémoire demande de rendre la ''Load-store queue'' multi-ports, afin de gérer plusieurs micro-opérations mémoire simultanées. Il faut aussi dupliquer les unités de calcul d'adresse reliées à la LSQ, chaque port d'émission mémoire devant avoir sa propre unité de calcul d'adresse. : Dans ce qui suit, nous parlerons d'AGU (''Adress Generation Unit'') pour désigner les unités de calcul d'adresse, et de LSQ pour designer la ''Load-store queue''. [[File:Processeur non-superscalaire basique 02.png|vignette|upright=1|Processeur non-superscalaire basique]] Dupliquer les ports de la LSQ et les AGU est nécessaire. Mais qu'en est-il du cache ? La réponse varie suivant le processeur. Il y a deux grandes méthodes pour implémenter l'émission multiple des accès mémoire. La première méthode ajoute des ports de lecture/écriture au cache de données, pour faire des lectures/écritures simultanées. Il faut alors dupliquer des unités de calcul, rajouter les ports sur le cache, mais aussi rajouter des ports sur la ''load/store queue'', pour qu'elle échange des données/commandes mémoire avec le cache. Le cout en circuits est alors conséquent La seconde duplique les unités de calcul, mais pas les ports d'accès au cache. Les accès mémoire sont alors accumulés dans la ''load/store queue'', qui sont ensuite traités un par un par le cache. Un exemple est celui des processeurs AMD de micro-architecture K7 K8, à savoir les anciens AMD Athlon et AMD Athlon 64, qui pouvaient calculer trois adresses simultanément, mais ne pouvaient faire qu'un seul accès au cache par cycle. Le cout en circuit est modéré : les unités de calcul coutent très peu, les ports de la ''load/store queue'' coutent déjà un peu plus. Quelques processeurs utilisaient des solutions intermédiaires. Un processeur utilisant cette technique est, par exemple, capable d'émettre quatre µops mémoire en même temps, mais avec seulement deux ports sur le cache. Un exemple classique étant les Pentium 1 et 2 d'Intel, dont nous parlerons dans le chapitre suivant. ===L'émission séparée des LOAD et STORE=== De rares processeurs ont des ports d'émission qui peuvent faire indifféremment lecture comme écritures. Mais les processeurs modernes préfèrent séparer ports d'émission pour les lectures et les écritures. Un exemple est celui des processeurs Intel de micro-architecture Nehalem, qui avaient un port de lecture et un port d'écriture tous deux reliés à la ''Load-store queue'', chacun avec leur propre unité de calcul d'adresse. Comme autre exemple, les processeurs skylake ont une LSQ avec deux ports de lecture et un port d'écriture, ce qui permet d'émettre deux lectures en même temps qu'une écriture. Dans cette section, nous allons étudier le cas avec des ports séparés pour les lectures et écritures. En général, les processeurs ont un peu plus de ports de lectures que d'écritures. Une raison est qu'un programme a, en moyenne, un tout petit peu plus de lectures que d'écritures. Mais la différence n'est pas énorme, c'est un léger déséquilibre. Une seconde raison est que l'exécution dans le désordre marche bien mieux quand les lectures ont lieu le plus tôt possible. Donc mieux vaut privilégier les lectures à l'émission, pour qu'elles se fassent un peu en avance. En pratique, la différence n'est pas très marquée. Il est courant d'avoir un port de lecture et un d'écriture, les CPU superscalaires très larges ont souvent deux ports de lecture et deux d'écriture. A la rigueur, quelques CPU ont trois ports de lecture et deux d'écriture. Le cas le plus simple est celui où on peut émettre une lecture et une écriture en même temps. Il s'implémente facilement si la LSQ est en réalité une simple ''Store queue'', à savoir qu'elle met en avance les écritures uniquement. Pour rappel, les écritures sont mises en attente dans la ''Store queue'', mais les lectures sont exécutées immédiatement. Une fois l'adresse à lire connue, la lecture consulte la ''Store queue'' pour détecter les dépendances de données, puis s'exécute. La donnée à lire est lue soit depuis la ''Store queue'', soit depuis le cache. [[File:Emission LOAD - Store séparée.png|vignette|upright=1|Émission LOAD - Store séparée]] L'implémentation de l'émission multiple est alors très simple. La ''Store queue'' a déjà deux ports, avec un pour les lectures et un autre pour les écritures. Idem pour le cache, qui a naturellement un port de lecture et un d'écriture. Émettre une lecture et une écriture simultanément ne demande donc pas de modification digne de ce nom. La ''Store queue'' doit juste supporter une lecture et une écriture simultanée. Ajouter l'émission d'une seconde lecture n'est pas compliquée : il faut juste rajouter un second port de lecture sur le cache et la ''Store queue''. Pour rappel, l'unité mémoire est précédée par une structure qui met en attente les micro-opérations mémoire avant leur émission finale. La structure en question varie suivant l'implémentation, mais un cas particulier se marie bien avec l'émission multiple des µops mémoire. Il s'agit du cas avec une file de µops séparée pour les lectures et une autre pour les écritures, appelées respectivement la ''file de µops LOAD'' et la ''file de µops STORE''. Il est alors possible d'émettre une lecture et une écriture lors du même cycle d'horloge. Le port de lecture alimente une unité de calcul d'adresse dédiée, directement reliée au cache. Le port d'écriture du cache alimente une unité de calcul, qui est suivie par une file d'écriture, elle-même reliée au cache. Les processeurs AMD de micro-architecture K6 utilisaient cette technique. [[File:Double émission avec le Load Ordering, Store Ordering.jpg|centre|vignette|upright=2.5|Double émission avec le Load Ordering, Store Ordering]] ==Les CPU superscalaire à exécution dans le désordre== La superscalarité modifie aussi les circuits dédiés à l'exécution dans le désordre. Elle modifie les unités de renommage de registre, le ROB et les autres structures liées à l'exécution dans le désordre, au sens large. Aucune structure n'est épargnée. Elles ont tous des prots de lecture/écriture en plus, afin d'encaisser l'émission, le ''commit'' ou le renommage simultané de plusieurs µops. Par exemple, le ROB doit être modifié pour émettre et terminer plusieurs instructions en même temps, en ajoutant des ports de lecture/écriture. Pour un CPU superscalaire à 8 voies, on se dit que le ROB peut retirer 8 µops par cycle maximum. Mais certains processeurs récents ont un ROB qui peut retirer le double ! Un exemple est celui des CPU de micro-architecture Skymont, qui peuvent émettre 8 µops par cycle, mais en retirer 16. Quelques CPU de marque AMD étaient aussi dans ce cas. L'avantage est que cela permet de réduire la taille du ROB, du banc de registre et de quelques autres structures matérielles liées à l'exécution dans le désordre. Le gain sur-compense le cout en transistor nécessaire pour retirer 16 µops par cycle. Dans cette section, nous allons voir comment les étages du pipeline dédiés à l'exécution dans le désordre sont modifiées sur un CPU superscalaire. ===La superscalarité avec une file/fenêtre d'instruction centralisée=== Plus haut, nous avons montré qu'un processeur pouvait avoir plus de ports d'émission que de ports de décodage. On dit aussi que sa ''largeur de décodage'' était supérieure à sa ''largeur d'émission''. La '''largeur de décodage''' est le nombre de µops fournies par l'unité de décodage par cycle d'horloge, la '''largeur d'émission''' est le nombre maximal de µops émises par cycle d'horloge. Et n'allez pas croire qu'un surplus de ports d'émission est du gâchis. Sur les processeurs avec exécution dans le désordre, ce surplus peut être utilisé. Mais avant toute chose, évacuons un détail. Vous avez sans doute pensé que le surplus peut être exploité par les instructions machines décodées en plusieurs µops . Par exemple, si une instruction est décodée en deux µops, on pourrait avoir un port d'émission en plus pour en profiter. Mais les ports de décodage sont *en sortie du décodeur*, et contiennent des µops, pas des instructions. Le surplus de ports d'émission peut occasionnellement utiliser le surplus de ports de décodage, mais cela implique que des µops ont été mises en attente dans l'unité d'émission, pour être émises par la suite. Et cela implique l'usage d'une fenêtre d'instruction ou d'une file de µops. Le principe est que les µops sont accumulées dans la fenêtre d'instruction, en attendant leurs opérandes ou la résolution d'une dépendance. Et si l'occasion se présente, une µops est émise sur chaque port d'émission. Il y a donc un gain en performances, mais il faut faire quelques précisions sur son origine. La largeur d'émission utilisée ne peut pas dépasser, *en moyenne*, la largeur de décodage. Et c'est intuitif : un CPU qui décode 4 µops par cycle ne peut pas émettre 6 par cycle en moyenne, sur le temps long, il n'y a pas de µops en surplus qui sortent du vide. Par exemple, sur un processeur quadruple émission, si on analyse combien d'instructions sont émises par cycle, il peut y avoir des cycles où la largeur d'émission est totalement utilisée, mais ce sera compensé par des périodes où elle est fortement sous-utilisée. Les µops sont accumulées en cas de sous-utilisation, puis consommées en cas d'utilisation complète. Les deux périodes se compensent. Et c'est là que l'on peut comprendre d'où vient le gain en performance. Les cycles où les ports d'émission sont sous-utilisés existent systématiquement, que le processeur utilise l'exécution dans le désordre ou pas. Ils sont liés à la présence de dépendances de données, aux contraintes d’appariement, etc. Elles tendent à réduire la moyenne du nombre de µops émises par cycle. Par contre, ce sont les cycles de rattrapage, où les ports en surplus sont utilisés, qui font remonter la moyenne. Le gain en performance est donc lié à ces cycles de rattrapage, où les µops accumulées sont émises dans les ports en surplus. Et ils n'existent que sur les processeurs avec une file/fenêtre d'instruction. Réduire le nombre de ports d'émission permet de garder des fenêtres d'instruction et un ROB simples, rapides et économes en énergie. Et mine de rien, c'est là un compromis important : à quoi bon augmenter le nombre de ports d'émission si c'est pour que ce soit compensé par une fréquence plus faible ? Par contre, réduire le nombre de ports augmente la ''port contention'', surtout si on a mal réparti les ALU. Il faut donc trouver un compromis entre réduire le nombre de ports d'émission d'un côté, réduire la ''port contention'' de l'autre. La différence entre largeur de décodage et d'émission a un impact assez fort sur le banc de registre, et précisément sur son nombre de ports de lecture. Le banc de registre a besoin de plus de ports de lecture avec une fenêtre d’instruction qu'avec des stations de réservation. Rappelons la terminologie utilisée dans ce cours. Les fenêtres d'instruction se contentent de mémoriser la micro-opération et quelques bits pour détecter la disponibilité des opérandes. Par contre, les stations de réservations mémorisent aussi les opérandes des instructions. Et cette différence a une influence le nombre de ports de lecture du banc de registre. Supposons que toutes les instructions sont dyadiques, à deux opérandes. Comptons le nombre de ports nécessaires pour alimenter les unités de calcul, sans utiliser d'arbitre de banc de registre. Avec une fenêtre d'instruction, il faut le double du nombre de ports d'émission. Mais avec des stations de réservation, il faut le double des ports de décodage. La raison est que les registres sont lus après émission avec une fenêtre d'instruction, avant avec des stations de réservation (sous-entendu, après le décodage). Or, comme dit plus haut, les processeurs superscalaires larges ont souvent plus de ports d'émission que de décodage. ===La superscalarité à fenêtres d'instruction décentralisées=== Les processeurs précédents utilisaient une unité d'émission simple, qui pouvait utiliser l'émission dans l'ordre ou dans le désordre. Elle peut être remplacée par une fenêtre d'instruction centralisée dans que cela quoi que ce soit aux explications précédentes, idem avec une station de réservation centralisée. En soi, l'exécution dans le désordre est donc totalement compatible avec une implémentation simple de la superscalarité. Par contre, des subtilités surviennent avec les fenêtres d'instruction décentralisées. Un exemple est le MIPS R10000, qui utilisait un mélange de files de µops et de fenêtres d'instructions : une file de µops les accès mémoire, une fenêtre pour les instructions entières et une autre pour les opérations flottantes. Les branchements et NOPs étaient exécutés dans une unité de branchement distincte. Les trois files/fenêtres peuvent mémoriser 16 µops. Le processeur avait deux ALU entières, une AGU de calcul d'adresse, un additionneur flottant et un multiplieur/diviseur flottant. Il y avait un multiplieur entier sur le port d'émission l'ALU 2. [[File:R 10000.png|centre|vignette|upright=2.5|MIPS R10000, microarchitecture.]] Pour rappel, sur ces processeurs, les µops sont émises en deux phases. Une première phase de ''dispatch'' qui envoie les µops vers la fenêtre d'instruction adéquate, sans détecter les dépendances de données. La seconde phase de ''schedule'' gère les dépendances de données, elle met en attente les µops en attendant que l'unité soit disponible pour les exécuter, en attendant aussi que les opérandes soient disponibles. Et à ce petit jeu, il y a une différence entre le nombre de µops qui sont ''dispatch par cycle'' et celles qui sont ''schedule''. Dit autrement, la largeur d'émission est scindée en une '''largeur de ''dispatch''''' et une '''largeur de ''schedule''. Par exemple, prenons un processeur triple émission avec deux stations de réservation : une pour les µops entières/mémoire, et une autre pour les µops flottantes. L'unité d'émission peut ''dispatch'' maximum trois µops par cycle : soit trois µops entières/mémoire, soit trois flottantes, soit un mix de trois µops différentes. Les stations de réservation, quant à elles, peuvent chacune ''schedule'' 3 µops par cycle, vers trois ALUs entières pour la station de réservation entière/mémoire, vers trois FPS pour la station de réservation flottante. De quoi ''dispatch'' trois µops, mais d'en ''schedule'' six, à chaque cycle. Et cet exemple ne sort pas de nulle part : les CPU x86 d'Intel et AMD sont globalement dans ce cas, même si les chiffres varient d'une microarchitecture à l'autre. L'exemple précédent colle parfaitement à la microarchitecture K7 d'AMD, utilisée sur les processeurs Athlon dans les années 2000. [[File:AMD K7.png|centre|vignette|upright=2.5|AMD K7, microarchitecture.]] Un autre exemple est le MIPS R10000, qui utilisait un mélange de files de µops et de fenêtres d'instructions : une file de µops les accès mémoire, une fenêtre pour les instructions entières et une autre pour les opérations flottantes. Les branchements et NOPs étaient exécutés dans une unité de branchement distincte. Les trois files/fenêtres peuvent mémoriser 16 µops. Le processeur avait deux ALU entières, une AGU de calcul d'adresse, un additionneur flottant et un multiplieur/diviseur flottant. Il y avait un multiplieur entier sur le port d'émission l'ALU 2. Le processeur décodait 4 instructions par cycle et produisait jusqu'à 4µops, qui étaient redistribuées sur les trois fenêtres d'instructions. Chacune pouvait accepter 4 µops par cycle, ce qui fait que processeur pouvait ''dispatch'' 4 µops entières, ou 4 µops flottantes, ou 4 µops mémoire, ou un mélange de deux ou trois types de µops. Pour ce qui est du ''schedule'', la fenêtre d'instruction entière pouvait émettre 2 µops, la fenêtre flottante pouvait émettre une addition en même temps qu'une multiplication/divison flottante et qu'un branchement conditionnel flottant, et la file de µops mémoire ne pouvait émettre qu'une µop mémoire à la fois. En tout, cela fait 6µops ''schedule'' pour 4 µops au ''dispatch''. [[File:R 10000.png|centre|vignette|upright=2.5|MIPS R10000, microarchitecture.]] ===L'unité de renommage superscalaire=== Avec la superscalarité, l'unité de renommage de registre doit gérer le cas où des instructions consécutives ont des dépendances de registre. Par exemple, prenons un processeur à double émission, qui renomme deux instructions consécutives. Si elles ont une dépendance de registre, le renommage de la seconde instruction dépend du renommage de la première. La première doit être renommée et le résultat du renommage est utilisé pour renommer la seconde, ce qui empêche d'utiliser deux unités de renommage séparées. Pour cela, elle renomme les registres sans tenir compte des dépendances, pour ensuite corriger le résultat. [[File:Unité de renommage superscalaire.png|centre|vignette|upright=2|Unité de renommage superscalaire.]] Seules les dépendances lecture-après-écriture doivent être détectées, les autres étant supprimées par le renommage de registres. Repérer ce genre de dépendances se fait assez simplement : il suffit de regarder si un registre de destination d'une instruction est un opérande d'une instruction suivante. [[File:Détection des dépendances sur un processeur superscalaire.png|centre|vignette|upright=2|Détection des dépendances sur un processeur superscalaire.]] Ensuite, il faut corriger le résultat du renommage en fonction des dépendances. Si une instruction n'a pas de dépendance avec une autre, on la laisse telle quelle. Dans le cas contraire, un registre opérande sera identique avec le registre de destination d'une instruction précédente. Dans ce cas, le registre opérande n'est pas le bon après renommage : on doit le remplacer par le registre de destination de l'instruction avec laquelle il y a dépendance. Cela se fait simplement en utilisant un multiplexeur dont les entrées sont reliées à l'unité de détection des dépendances. On doit faire ce replacement pour chaque registre opérande. [[File:Correction des dépendances sur un processeur superscalaire.png|centre|vignette|upright=2|Correction des dépendances sur un processeur superscalaire.]] ==Les optimisations des processeurs superscalaires larges== Les processeurs superscalaires larges ont des optimisations spécifiques, qui n'ont pas de sens sur les processeurs superscalaires étroits. Dans les grandes lignes, elles se classent en deux types. Les premières permettent de charger/décoder plus de 5-10 instructions simultanément. Le problème est alors que les branchements viennent mettre leur grain de sel et qu'il faut en tenir compte. Les secondes corrigent des problèmes qui apparaissent quand un CPU intègre plus de 5 à 10 unités de calcul. ===L'étape de chargement d'un CPU superscalaire large=== Charger plusieurs instructions pose de nombreux problèmes sur les processeurs superscalaires larges, qui émettent de 5 à 10 instructions simultanées. Pour rappel, la stratégie basique pour charger N instructions est de charger un bloc de N instructions consécutives. Les instructions après un branchement ne sont pas décodées ni exécutées, ce qui entraine une petite baisse de performance. Pour un N petit, la perte de performance est mineure. Mais pour un N large, elle devient suffisamment importante pour que la conception du CPU doit alors être repensée de fond en comble. Les analyses statistiques disent qu'il y a environ un branchement tous les 4 à 6 instructions. Un CPU a donc tendance à décoder des paquets de 3 à 5 instructions consécutives, avant qu'un branchement casse la dynamique. Or, cela pose des problèmes pour utiliser plusieurs décodeurs. L'implémentation basique, qui charge N instructions consécutives pour alimenter N décodeurs, fonctionne donc jusqu'à 4/6 décodeurs. Au-delà de 5 instructions, un CPU superscalaire doit être adapté pour gérer les branchements dans les paquets d'instruction qu'il émet. Le nombre de 5 instructions ne sort donc pas de nulle part. La première solution utilise un cache de µops, avec un ''front-end'' capable de charger/décoder 4/5 instructions à la fois, pas plus. L'idée est que la majorité des instructions proviennent du cache de µops (plus de 80%), ce qui fait qu'on peut émettre environ 10 instructions à partir du cache de µops, pas besoin d'avoir beaucoup de décodeurs. Les performances sont cependant dégradées lors d'un défaut de cache de µops. Les instructions doivent alors être chargées depuis le cache d'instruction, par paquets de 4-5, puis sont décodées par 4-5 décodeurs, ce qui ne permet pas d’alimenter les unités de calcul à plein régime. La solution précédente joue sur la distinction entre pots de décodage et d'émission. Elle permet d'émettre un grand nombre de µops sans pour autant charger un grand nombre d'instructions en même temps. En quelque sorte, ce n'est qu'une méthode de contournement, le CPU superscalaire reste de type étroit. Mais il est aussi possible d'utiliser un '''''front-end large''''', capable de charger une bonne dizaine d'instructions en même temps. La seule difficulté est de tenir compte de la présence de branchements. Pour rappel, avec un ''front-end'' étroit, les instructions situées après le premier branchement pris sont annulées, elles ne sont pas décodées ni exécutées. [[File:Fetch sur un processeur superscalaire avec prediction de branchements.png|centre|vignette|upright=2|Fetch sur un processeur superscalaire avec prédiction de branchements.]] Un ''front-end'' large charge les instructions de destination du branchement et les placent à sa suite. Ils chargent deux blocs à la fois et les fusionnent en un seul qui ne contient que les instructions présumées utiles. [[File:Cache d'instructions autoaligné.png|centre|vignette|upright=2|Cache d'instructions auto-aligné.]] Mais cela demande de charger deux blocs de mémoire en une fois, ce qui demande un cache d'instruction multiports. Il faut aussi ajouter un circuit pour assembler plusieurs morceaux de blocs en un seul : le fusionneur (''merger''). En pratique, cette solution demande d'ajouter un second port de lecture au cache, pour un cout en hardware important, avec un impact négatif sur le temps d'accès au cache. Et il faut rajouter le fusionneur, qui prend un étage de pipeline à lui tout seul. Et encore, cette solution ne gère pas le cas où un bloc contient plusieurs branchements ! [[File:Implémentation d'un cache d'instructions autoaligné.png|centre|vignette|upright=2|Implémentation d'un cache d'instructions auto-aligné.]] Les exemples précédents partent du principe qu'il n'y a qu'un seul branchement dans un bloc. Mais quand les blocs deviennent assez longs, il est fréquent d'avoir plusieurs branchements dans un bloc. Et il est même possible d'avoir plusieurs branchements pris. Dans un bloc de 8-10 instructions, ce n'est pas rare, et les CPU superscalaires à 8-10 voies sont la règle dans les CPU des années 2020. Pour gérer ce cas, il faut ajouter des ports supplémentaires au cache d'instruction et adapter le fusionneur, pour qu'il puisse fusionner trois ou quatre blocs. De plus, il faut utiliser une unité de prédiction de branchement capable de ''prédire plusieurs branchements par cycle''. ===Le décodage superscalaire large=== Le décodage des instructions n'est pas censé dépendre de l'implémentation du chargement. En théorie, une fois plusieurs blocs d'instruction fusionnés, on peut décoder les instructions en parallèle. Cependant, certains processeurs effectuent le décodage en tenant compte de la manière dont les branchements découpent les blocs. Les processeurs Atom d'Intel utilisent une solution de ce type, qui couple prédiction de branchement et décodage parallèle. Ils chargent/décodent des paquets de 3 instructions consécutives, chaque paquet étant séparé du précédent par un branchement. Pour cela, en plus d'avoir un fusionneur adapté, ils regroupent les décodeurs en ''clusters'', chacun regroupant 3 décodeurs, et pouvant décoder trois instructions consécutives. Les micro-architectures Tremont et Crestmont avaient deux ''clusters'' (6 décodeurs au total), la micro-architecture Skymont en intègre trois (9 décodeurs au total). Avec deux ''clusters'', le fonctionnement est le suivant. Si le code est composé d'une suite d'instruction sans branchements, seul le premier ''cluster'' est utilisé, limitant le décodage à trois instructions par cycle. Par contre, si l'une des trois instructions décodée est un branchement, alors le second ''cluster'' décode les trois instructions situées après ce branchement, à partir de l'adresse de destination. Avec un troisième ''cluster'', c'est la même chose, sauf qu'il faut qu'il y ait un branchement dans les trois instructions décodées par le second ''cluster''. Mais comment faire pour savoir quels décodeurs activés, quels ''clusters'' sont actifs ? C'est la prédiction de branchement qui commande. Avec deux ''clusters'', elle détermine si un branchement est présent dans les trois instructions à charger, et quelle est son adresse de destination. Sans branchement, elle prévient les décodeurs et les trois instructions chargées sont décodées dans un ''cluster'' libre. Si elle prédit un branchement, elle charge les instructions adéquates, et active les deux ''clusters''. Avec trois branchements, c'est le même mécanisme, sauf que l'unité de prédiction de branchement doit être capable de prédire deux branchements par cycle. Il faut noter que les décodeurs d'un ''cluster'' sont des décodeurs simples, sans microcode. Sur les processeurs Tremont et Crestmont, le microcode était unique et était partagé entre tous les ''clusters''. Un seul ''cluster'' pouvait décoder une instruction complexe avec le microcode, l'autre devait se limiter à des instructions simples uniquement. Depuis Skymont, le microcode est partiellement dupliqué, avec un microcode par ''cluster''. Le cout en hardware peut paraitre prohibitif, pour un gain en performance faible. Sauf que c'est oublier que ce sont des processeurs Atom, sur lesquels beaucoup d'instructions sont microcodées. Les processeurs Atom ont un budget en transistor limité, ce qui fait qu'il est préférable pour eux d'implémenter des instructions en microcode que d'utiliser des unités de calcul dédiées ou des circuits de décodage complexes. Pour limiter la casse, le microcode n'est que partiellement dupliqué. Seul le microcode pour les instructions fréquente est dupliqué. Il y a un microcode secondaire pour certaines instructions, qui est lui unique et partagé par tous les ''clusters''. ===Le contournement sur les processeurs superscalaires larges=== Pour rappel, la technique du contournement (''register bypass'') permet au résultat d'une instruction d'être immédiatement utilisable en sortie de l'ALU, avant même d'être enregistré dans les registres. Implémenter la technique du contournement demande d'utiliser des multiplexeurs pour relier la sortie de l'unité de calcul sur son entrée si besoin. Il faut aussi des comparateurs pour détecter des dépendances de données. [[File:Pipeline Bypass.png|centre|vignette|upright=1|Pipeline Bypass]] La sortie de chaque ALU doit être reliée aux entrées de toutes les autres, avec les comparateurs qui vont avec. Sur les processeurs ayant plusieurs d'unités de calculs, cela demande beaucoup d’interconnexions et de comparateurs. Pour N unités de calcul, cela demande 2 * N² interconnexions, implémentées avec 2N multiplexeurs de N entrées chacun. Si c'est faisable pour 2 ou 3 ALUs, la solution est impraticable au-delà. Non seulement ce serait un enfer à câbler, mais les fils seraient tellement longs que la fréquence du processeur en prendrait un coup. Notez que cela vaut pour les processeurs superscalaires, mais aussi pour tout processeur avec beaucoup d'unités de calcul. En théorie, un CPU à exécution dans le désordre, non-superscalaire, mais avec beaucoup d'unités de calcul, fait face au même problème. En pratique, avoir une dizaine d'ALU implique processeur superscalaire à exécution dans le désordre. D'où le fait qu'on parle du problème maintenant. La seule solution praticable est de ne pas relier toutes les unités de calcul ensemble. À la place, on préfère regrouper les unités de calcul dans différents '''agglomérats''' ('''cluster'''). Le contournement est alors possible entre les unités d'un même agglomérat, mais pas entre agglomérats différents. Évidemment, certaines possibilités de contournement sont perdues, mais c'est compensé par un gain en fréquence et un cout en circuit/fils réduit. C'est un bon compromis, ce qui explique que presque tous les processeurs modernes l'utilisent. Les rares exceptions sont les processeurs POWER 4 et POWER 5, qui ont préféré se passer de contournement pour garder un processeur très simple et une fréquence élevée. ===Le banc de registre des processeurs superscalaires larges=== Pour émettre deux opérations entières simultanément, il faut lire deux fois plus d'opérandes. Et il faut aussi écrire deux résultats dans les registres, en même temps. Pour cela, on double les ports du banc de registre. En général, il faut au maximum 2 ports de lecture et un port d'écriture par unité de calcul. Le problème, c'est que plus un banc de registres a de ports, plus il utilise de circuits, est compliqué à concevoir, consomme de courant et chauffe. Avec plusieurs dizaines d'unités de calcul différentes, ajouter autant de ports impacterait la fréquence du banc de registres et réduirait les performances, voire est totalement impraticable à cause des contraintes thermiques. Les processeurs superscalaires larges doivent donc trouver des solutions. La première solution utilise un banc de registre unique, mais n'utilise pas autant de ports que le pire des cas le demanderait. Le processeur détecte quand il n'y a pas assez de ports pour servir toutes les micro-opérations. Quand ça arrive, l'unité d'émission émet assez de micro-opérations pour exploiter les ports disponibles, mais met en attente les micro-opérations en trop, le temps que les ports se libèrent. Pour cela, le banc de registre est géré par un circuit d'arbitrage spécialisé, intégré à l'unité d'émission, l’'''arbitre du banc de registres''' (''register file arbiter''). Une autre solution est de dupliquer le banc de registres en plusieurs exemplaires qui contiennent exactement les mêmes données, mais avec moins de ports de lecture/écriture. Un exemple est celui des processeurs POWER, Alpha 21264 et Alpha 21464. Sur ces processeurs, le banc de registre est dupliqué en plusieurs exemplaires, qui contiennent exactement les mêmes données. Les écritures dans les registres sont envoyées à tous les bancs de registres, afin de garantir que leur contenu est identique. Chaque exemplaire a exactement deux ports de lecture. La conception du processeur est simplifiée, que ce soit au niveau du câblage, que de la conception des bancs de registres. Une dernière solution découpe le banc de registres en plusieurs bancs de registres plus petits, avec un réseau d'interconnexions pour échanger des données entre bancs de registres. Dans la plupart des cas, cette séparation est invisible du point de vue de l'assembleur et du langage machine. Le processeur se charge de transférer les données entre bancs de registres suivant les besoins. Sur d'autres processeurs, les transferts de données se font via une instruction spéciale, souvent appelée ''COPY''. [[File:Banc de registres distribué.png|centre|vignette|upright=2|Banc de registres distribué.]] Sur de certains processeurs, un branchement est exécuté par une unité de calcul spécialisée. Or les registres à lire pour déterminer l'adresse de destination du branchement ne sont pas forcément dans le même agglomérat que cette unité de calcul. Pour éviter cela, certains processeurs disposent d'une unité de calcul des branchements dans chaque agglomérat. Dans les cas où plusieurs unités veulent modifier le ''program counter'' en même temps, un système de contrôle général décide quelle unité a la priorité sur les autres. Mais d'autres processeurs fonctionnent autrement : seul un agglomérat possède une unité de branchement, qui peut recevoir des résultats de tests de toutes les autres unités de calcul, quel que soit l’agglomérat. ==La macro-fusion : une optimisation du décodage parallèle== Les processeurs superscalaires supportent la technique dite de '''macro-fusion''', qui permet de fusionner deux-trois instructions consécutives en une seule micro-opération. Par exemple, il est possible fusionner une instruction de test et une instruction de saut en une seule micro-opération de branchement. Il s'agit là de l'utilisation la plus importante de la macro-fusion sur les processeurs x86 modernes. La macro-fusion est utilisée pour fusionner un test et un saut en un branchement unique. L'optimisation est présente sur la plupart des CPU superscalaires modernes. Les CPU x86 l'implémentent, mais aussi quelques CPU ARM et les processeurs Apple de série M (M1 à M5, et potentiellement plus quand ils seront sortis). La fusion des instructions se fait lors du décodage des instructions, grâce à la coopération des décodeurs. Elle marche systématiquement si l'instruction de test et le saut sont consécutives, qu'elles forment bien une paire d'instruction. Par contre, insérer une instruction entre les deux empêche la macro-fusion de faire son travail, sauf si l'instruction insérée est un NOP. Les processeurs ARM64 avaient une forme spécifique de macro-fusion, utilisée pour un cas très précis : charger une constante immédiate de 32 ou 64 bits, dans un registre 64 bits. Les CPU ARM 64 bits utilisent des instructions codées sur 32 bits, qui ne peuvent encoder que des constantes immédiates de 16 bits. Pour charger une constante de 32/64 bits, il faut s'y prendre en plusieurs fois. Une première instruction MOVZ charge les 16 premiers bits, puis 2 à 4 instructions MOVK remplissent le reste. MOVZ met à zéro les 48 bits de poids fort, MOVK permet de sélectionner le doublet à modifier dans le registre (on modifie 16 bits, dont on choisit la place, le reste du registre est laissé tel quel). ARM permet de fusionner cette suite d'instructions en une seule µop MOV avec une constante immédiate. On parle alors de '''''MOVZ + MOVK fusion'''''. Une dernière utilisation, [https://www2.eecs.berkeley.edu/Pubs/TechRpts/2016/EECS-2016-130.pdf proposées pour le jeu d'instruction RISC-V], fusionnait un calcul d'adresse et une instruction LOAD/STORE en une seule instruction d'accès mémoire. Elle a été envisagée à une époque où le jeu d'instruction RISC-V ne gérait pas les modes d'adressage base + indice. L'idée était d'avoir une unité mémoire qui gérait le mode d'adressage base + indice et de fusionner des instructions pour l'exploiter. Mais de nos jours, le jeu d'instruction RISC-V intègre le mode d'adressage base + indice, ce qui fait que la macro-fusion n'a aucun intérêt pour ce cas précis. La macro-fusion a de nombreuses limitations en pratique. En théorie, on pourrait l'utiliser au-delà des deux situations mentionnées plus haut. Par exemple, pour fusionner une addition et une multiplication en une seule instruction MAD. Mais l'intérêt est assez faible. Notons que la macro-fusion ne permet que de fusionner des paires d'instructions. Aller au-delà demanderait un cout en circuit trop important. Déjà que tester deux paires utilise pas mal de comparateurs... ==Le ''stack engine'' : une optimisation de la pile d'appel== Les processeurs modernes intègrent une optimisation liée au pointeur de pile. Pour rappel, sur les architectures modernes, le pointeur de pile est un registre utilisé pour gérer la pile d'appel, précisément pour savoir où se trouve le sommet de la pile. Il est manipulé par les instructions CALL, RET, PUSH et POP, mais aussi par les instructions d'addition/soustraction pour gérer des cadres de pile de taille variable. De plus, il peut servir d'opérande pour des instructions LOAD/STORE, afin de lire/écrire des variables locales, les arguments d'une fonction, et autres. C'est donc un registre général adressable, intégré au banc de registre, altéré par l'unité de calcul entière. L'incrémentation/décrémentation du pointeur de pile passe donc par l'unité de calcul, lors des instructions CALL, RET, PUSH et POP. Mais, l'optimisation que nous allons voir permet d'incrémenter/décrémenter le pointeur de pile sans passer par l'ALU, ou presque. L'idée est de s'inspirer des architectures avec une pile d'adresse de retour, qui intègrent le pointeur de pile dans le séquenceur et l'incrémentent avec un incrémenteur dédié. ===Le ''stack engine''=== L'optimisation que nous allons voir utilise un '''''stack engine''''' intégré à l'unité de contrôle, au séquenceur. Le processeur contient toujours un pointeur de pile dans le banc de registre, cela ne change pas. Par contre, il n'est pas incrémenté/décrémenté à chaque instruction CALL, RET, PUSH, POP. Un compteur intégré au séquenceur est incrémenté à la place, nous l’appellerons le '''compteur delta'''. La vraie valeur du pointeur de pile s'obtient en additionnant le compteur delta avec le registre dans le banc de registre. Précisons que si le compteur delta vaut zéro, la vraie valeur est dans le banc de registre et peut s'utiliser telle quelle. Lorsqu'une instruction ADD/SUB/LOAD/STORE utilise le pointeur de pile comme opérande, elle a besoin de la vraie valeur. Si elle n'est pas dans le banc de registre, le séquenceur déclenche l'addition compteur-registre pour calculer la vraie valeur. Finalement, le banc de registre contient alors la bonne valeur et l'instruction peut s'exécuter sans encombre. L'idée est que le pointeur de pile est généralement altéré par une série d'instruction PUSH/POP consécutives, puis des instructions LOAD/STORE/ADD/SUB utilisent le pointeur de pile final comme opérande. En clair, une bonne partie des incrémentations/décrémentation est accumulée dans le compteur delta, puis la vraie valeur est calculée une fois pour toutes et est utilisée comme opérande. On accumule un delta dans le compteur delta, et ce compteur delta est additionné quand nécessaire. Précisons que le compteur delta est placé juste après le décodeur d'instruction, avant même le cache de micro-opération, l'unité de renommage et l'unité d'émission. Ainsi, les incrémentations/décrémentations du pointeur de pile disparaissent dès l'unité de décodage. Elles ne prennent pas de place dans le cache de micro-opération, ni dans la fenêtre d'instruction, ni dans la suite du pipeline. De nombreuses ressources sont économisées dans le ''front-end''. Mais utiliser un ''stack engine'' a aussi de nombreux avantages au niveau du chemin de données/''back-end''. Déjà, sur les processeurs à exécution dans le désordre, cela libère une unité de calcul qui peut être utilisée pour faire d'autres calculs. Ensuite, le compteur delta mémorise un delta assez court, de 8 bits sur le processeur Pentium M, un peu plus pour les suivants. L'incrémentation se fait donc via un incrémenteur 8 bits, pas une grosse ALU 32/64 bits. Il y a un gain en termes de consommation d'énergie, un incrémenteur 8 bits étant moins gourmand qu'une grosse ALU 32/64 bits. ===Les points de synchronisation du delta=== La technique ne fonctionne que si la vraie valeur du pointeur de pile est calculée au bon moment, avant chaque utilisation pertinente. Il y a donc des '''points de synchronisation''' qui forcent le calcul de la vraie valeur. Plus haut, nous avions dit que c'était à chaque fois qu'une instruction adresse le pointeur de pile explicitement, qui l'utilise comme opérande. Les instructions CALL, RET, PUSH et POP ne sont pas concernées par elles utilisent le pointeur de pile de manière implicite et ne font que l'incrémenter/décrémenter. Mais dans les faits, c'est plus compliqué. D'autres situations peuvent forcer une synchronisation, notamment un débordement du compteur delta. Le compteur delta est généralement un compteur de 8 bits, ce qui fait qu'il peut déborder. En cas de débordement du compteur, le séquenceur déclenche le calcul de la vraie valeur, puis réinitialise le compteur delta. La vraie valeur est donc calculée en avance dans ce cas précis. Précisons qu'un compteur delta de 8 bits permet de gérer environ 30 instructions PUSH/POP consécutives, ce qui rend les débordements de compteur delta assez peu fréquent. À noter que si le compteur delta vaut zéro, il n'y a pas besoin de calculer la vraie valeur, le séquenceur prend cette situation en compte. Un autre point de synchronisation est celui des interruptions et exceptions matérielles. Il faut que le compteur delta soit sauvegardé lors d'une interruption et restauré quand elle se termine. Idem lors d'une commutation de contexte, quand on passe d'un programme à un autre. Pour cela, le processeur peut déclencher le calcul de la vraie valeur lors d'une interruption, avant de sauvegarder les registres. Pour cela, le processeur intègre un mécanisme de sauvegarde automatique, qui mémorise la valeur de ce compteur dans le tampon de réordonnancement, pour forcer le calcul de la vraie valeur en cas de problème. La technique du ''stack engine'' est apparue sur les processeurs Pentium M d'Intel et les processeurs K10 d'AMD, et a été conservée sur tous les modèles ultérieurs. L'implémentation est cependant différente selon les processeurs, bien qu'on n'en connaisse pas les détails et que l'on doive se contenter des résultats de micro-benchmarks et des détails fournit par Intel et AMD. Selon certaines sources, dont les manuels d'optimisation d'Agner Fog, les processeurs AMD auraient moins de points de synchronisation que les processeurs Intel. De plus, leur ''stack engine'' serait placé plus loin que prévu dans le pipeline, après la file de micro-opération. ==La micro-fusion et la délamination== La '''micro-fusion''' est une optimisation qui retarde le décodage réel de certaines instructions assez loin dans le pipeline. Elle concerne surtout les instructions ''load-op'', mais pas que. D'autres instructions mémoire peuvent subir cette optimisation. L'essentiel est que, sans micro-fusion, ces instructions sont décodées en plusieurs micro-opérations. La micro-fusion retarde le décodage final de ces instructions, qui se fait assez tard dans le pipeline. Pour comprendre l'intérêt, nous allons devoir faire quelques explications. L'optimisation en question fait le décodage en deux temps. Prenons l'exemple d'une opération ''load-op'', qui est censée être décodée en deux micro-opérations : une lecture, puis une opération. Le décodage produit une '''macro-opération''' (terme d'Intel), qui est propagée dans le pipeline, pour être scindée en deux micro-opérations un peu avant les ALU et l'unité mémoire. La macro-opération est techniquement une micro-opération comme une autre, sauf que ce n'est pas la micro-opération finale. L'unité d'émission découpe cette macro-opération en deux micro-opérations, qui sont émises l'une après l'autre. Pour résumer, les instructions ''load-op'' ne sont complétement découpées en micro-opération lors de l'émission, pas lors du décodage. L'optimisation impacte ce qui se situe entre le décodeur d'instruction et l'unité d'émission : l'unité de renommage, la file de micro-opérations, le ''Loop Stream Detector'' et/ou le cache de micro-opération. L'intérêt de l'optimisation est que l'on fusionne deux micro-opérations en une seule macro-opération. Le cache de micro-opération et la file de micro-opération peuvent mémoriser plus de micro-opérations, vu que certaines sont fusionnées. Quant à l'unité de renommage, elle renomme une seule macro-opération au lieu de renommer deux micro-opérations, ce qui augmente le débit du renommage de registre. Par contre, cela complique un peu le travail de l'unité d'émission, qui doit découper les instructions ''load-op'' en deux et émettre les micro-opérations l'une après l'autre. Il faut noter qu'il n'y a pas de fusion proprement dite. Le décodeur ne décode pas deux micro-opérations séparées, avant de les fusionner. En réalité, il génère directement une macro-opération, qui correspond à deux micro-opérations, que ce soit plus tard dans le pipeline ou sur une autre architecture. D'ailleurs, la micro-fusion a lieu à l'intérieur d'une instruction, qui est censée être traduite en plusieurs micro-opérations. Et encore : pas toutes. La conséquence est que la technique n'a de sens que sur les processeurs CISC, avec des instructions assez complexes pour être décodées en plusieurs micro-opérations. En pratique, seuls les CPU x86 sont concernés. ===Les domaines de fusion=== La micro-fusion n'est pas spécifique aux processeurs superscalaires. En théorie, on peut l'implémenter sur un processeur non-superscalaire à émission dans l'ordre. Cependant, je ne connais aucun processeur à émission dans l'ordre qui implémente la micro-fusion, même si son implémentation est en théorie possible sur de tels processeurs. La micro-fusion est implémentée différemment selon que l'émission se fasse dans l'ordre, dans le désordre, avec une fenêtre d'instruction, avec des stations de réservation, etc. J'ai dit plus haut que les micro-opérations sont générées lors de l'émission, mais le moment exact varie suivant le processeur. Pour simplifier le tout, nous parlerons de '''scission''' quand le processeur traduit une macro-opération en deux/trois micro-opérations. Le moment où a lieu la scission est théoriquement dans l'unité d'émission, mais celle-ci prend plusieurs formes selon que le processeur utilise l'émission dans l'ordre, dans le désordre, avec une fenêtre d'instruction, avec des stations de réservation, etc. Les processeurs à exécution dans l'ordre sont le cas le plus simple. L'unité d'émission est alors en charge du découpage des macro-opérations en micro-opérations. La file de micro-opération ou le cache de micro-opérations mémorisent des macro-opérations. À l'opposé, le tampon de ré-ordonnancement mémorise lui des micro-opérations. Le reste du processeur n'est pas affecté. Sur les processeurs à exécution dans le désordre, les différences sont nombreuses. Le tampon de ré-ordonnancement peut mémoriser soit les micro-opérations finales, soit des macro-opérations. Il en est de même avec la fenêtre d'instruction et/ou les stations de réservation, qu'elles soient centralisées ou décentralisées. Tout dépend de comment se fait l'implémentation. Une implémentation simple effectue la scission des macro-opérations dans l'unité d'émission, comme sur les processeurs à exécution dans l'ordre. Sur de nombreux processeurs, l'unité d'émission et de renommage sont simplement fusionnées, ce qui fait que la scission a lieu juste après le renommage de registres. En conséquence, le tampon de re-ordonnancement et les fenêtres d'instruction ne voient que des micro-opérations. Les macro-opérations disparaissent juste avant. Cette implémentation est préférée avec une fenêtre d'instruction centralisée, car on voit mal comment scinder une macro-opération juste avant l'ALU. Une autre implémentation fait la scission en sortie de la fenêtre d'instruction ou des stations de réservation, qu'elles soient centralisées ou décentralisées. Dans ce cas, le tampon de re-ordonnancement fait de même. L'avantage est que le tampon de ré-ordonnancement stocke des macro-opérations, ce qui augmente sa taille effective. Par exemple, il utilise une seule entrée pour une instruction ''load-op'', au lieu de deux sans micro-fusion de ce type. ===La délamination=== La scission peut parfois avoir lieu plus tôt, dans des circonstances très précises. Concrètement, cela ne concerne que quelques micro-architectures d'Intel, pour des instructions très précises. La raison est les limitations du format des micro-opérations, pas une histoire d'optimisation. Le processeur aimerait pouvoir faire la micro-fusion, mais ne peut pas à cause de ces limitations. Une telle situation cause alors une '''délamination''' : les micro-opérations sont scindées précocement. Par exemple, sur la micro-architecture SandyBridge d'Intel, les opérations ''load-op'' sont fusionnées, sauf si elles utilisent l'adressage indicé. Les micro-opérations de ce processeur ne peuvent avoir que deux opérandes. Trois opérandes est de trop, et les opérations ''load-op'' en adressage indicé dépassent cette limite. Dans ce cas, les micro-opérations sont fusionnées en sortie du décodeur, mais sont scindés avant d’entrer dans la file de micro-opération. L'avantage n'est pas évident, vu que l'unité de renommage suit de près le décodeur d'instruction. Il est que le cache de micro-opérations mémorise des macro-opérations, pas deux micro-opérations. ===La micro-fusion des processeurs Intel et AMD=== La micro-fusion est apparue sur le Pentium M d'Intel, et a été conservée telle quelle sur les processeurs Intel suivants. Les processeurs AMD supportent la micro-fusion depuis au moins la micro-architecture AMD K8. Mieux que ça, la délamination n'était pas utilisée car les micro-opérations acceptaient plus de 2 opérandes. Que ce soit chez AMD ou Intel, elle concerne deux types d'instructions : les écritures mémoire et les instructions ''load-op''. Sur le Pentium M, il y avait quelques limitations, l'optimisation se limitant par exemple aux registres entiers et flottants, mais pas aux registres MMX, mais pas les registres SSE (nous verrons ces registres dans quelques chapitres). Mais les processeurs modernes n'ont plus cette limitation. La micro-fusion des CPU Intel concerne deux types d'instructions : les écritures mémoire et les instructions ''load-op''. Pour les écritures, la situation est plus complexe que pour les instructions ''load-op''. Sur les processeurs Intel précédents, les écritures étaient réalisées dans deux unités séparées : le calcul d'adresse dans une unité de calcul, l'écriture proprement dite dans l'unité mémoire. Elles prenaient donc deux micro-opérations. Le processeur a changé ses unités mémoire, ce qui fait qu'elle pouvait faire les deux en une seule micro-opération. Il s'agissait alors d'une optimisation du chemin de données, plus que de la micro-fusion proprement dite, mais je la mentionne ici malgré tout. La micro-fusion sur ce processeur avait un avantage en plus des avantages usuels : elle utilisait mieux les décodeurs. Les Pentium M avaient trois décodeurs pour décoder plusieurs instructions en même temps, chose qu'on détaillera dans le chapitre sur les processeurs superscalaires. Un seul décodeur pouvait décoder toutes les instructions, les deux autres ne peuvent décoder que les instructions simples, qui se décodent en une seule micro-opération. Avec la micro-fusion, les trois décodeurs peuvent gérer les écritures mémoire et les opérations ''load-op'', vu qu'elles deviennent des instructions simples. Sans cela, elles auraient été limitées au premier décodeur. <noinclude> {{NavChapitre | book=Fonctionnement d'un ordinateur | prev=Le parallélisme mémoire | prevText=Le parallélisme mémoire | next=Exemples de microarchitectures CPU : le cas du x86 | nextText=Exemples de microarchitectures CPU : le cas du x86 }} </noinclude> j8rc6uk2ozdx0m20u9friujwulqr4o2 773252 773251 2026-09-26T20:20:49Z Mewtow 31375 /* La superscalarité à fenêtres d'instruction décentralisées */ 773252 wikitext text/x-wiki Les processeurs vus auparavant ne peuvent émettre au maximum qu'une instruction par cycle d'horloge. Ils peuvent avoir plusieurs instructions qui s'exécutent en même temps, dans des unités de calcul séparées. C'est le cas dès qu'une instruction multi-cycles s'exécute. Par contre, ils ne peuvent démarrer qu'une seule instruction par cycle. Et quand on court après la performance, ce n'est pas assez ! Les concepteurs de processeurs ont inventés des processeurs qui émettent plusieurs instructions par cycle : les '''processeurs à émissions multiples'''. Un processeur à émission multiple charge plusieurs instructions en même temps, les décode en parallèle, puis les émet en même temps sur des unités de calculs séparées. Pour cela, il faut gérer les dépendances entre instructions, répartir les instructions sur différentes unités de calcul, et cela n'est pas une mince affaire. En général, pour un processeur à émission multiple capable d'émettre N instructions en parallèle, on parle de '''processeur à N voies''', ou encore un ''processeur à N pipelines''. [[File:Superscalarpipeline.svg|centre|vignette|upright=1.5|Pipeline RISC classique à cinq étages sur un processeur superscalaire. On voit bien que plusieurs instructions sont chargées en même temps.]] Les processeurs à émission multiple sont de deux types : les processeurs VLIW et les '''processeurs superscalaires'''. Nous mettons les processeurs VLIW de côté en attendant le prochain chapitre. La raison est qu'ils ont un jeu d'instruction spécialisé qui expose le parallélisme d'instruction directement au niveau du jeu d'instructions, là où les processeurs superscalaires restent des processeurs au jeu d'instruction normal. La différence principale entre processeur VLIW et superscalaire est qu'un processeur superscalaire répartit les instructions sur les unités de calcul à l’exécution, là où un processeur VLIW délègue cette tâche au compilateur. ==Le nombre de voies d'un processeur superscalaire== Les processeurs superscalaires les plus simples ne permettent que d'émettre deux micro-opérations à la fois, d'où leur nom de processeur ''dual issue'', ce qui se traduit en '''processeur à double émission'''. Les processeurs triple émission permettent eux d’émettre trois instructions à la fois. A l'opposé, les CPU superscalaires les plus puissants peuvent émettre jusqu'à 8-10 instructions simultanément. En 2026, les CPU superscalaires peuvent monter jusqu’à 10 µops émises en même temps, ce qui est énorme ! Et cela nous amène à faire une distinction entre les processeurs superscalaires dits '''étroits''' et ceux dits '''larges'''. Dans ce wikilivre, on part du principe que les processeurs superscalaires étroits émettent jusqu'à 4-5 µops à la fois, alors que les larges peuvent en émettre plus, l'intervalle entre les deux est une zone grise. Le nombre exact n'est pas fixé dans le marbre, la distinction est volontairement vague. Nous parlerons de la '''largeur d'un CPU superscalaire''' pour parler du nombre de voies, du nombre de µops émises en même temps. Plus un CPU superscalaire est large, plus il est performant ''toute chose égale par ailleurs''. Mais dans les faits, vous pouvez oublier la partie en italique. Élargir un CPU superscalaire demande de rajouter beaucoup de circuits. Et ce cout en circuit aura un impact sur la performance. A budget en transistors limité, il y a un compromis entre élargir un CPU et rajouter du cache ou la taille des fenêtres d'instructions/ROB et autres. ===Les contraintes d’appariement=== Prenons un processeur double émission, capable d'émettre deux instructions simultanément. Émettre deux instructions en même temps n'est pas toujours possible. Par exemple, si deux instructions sont dépendantes, on ne peut pas les émettre en même temps. Mais oublions un instant ces histoires de dépendances de données, et partons du principe que le CPU ait affaire à deux instructions indépendantes, une paire idéale. Un CPU double émission "parfait" pourrait exécuter n'importe quelle paire d'instruction. Par exemple, il pourrait émettre deux multiplications consécutives, deux lectures consécutives, une lecture et une écriture en même temps, etc. En pratique, aucun processeur ne permet cela. Implémenter un CPU superscalaire "parfait" aurait un cout en circuit beaucoup trop conséquent. Par exemple, pour émettre deux multiplications en même temps, il faudrait deux multiplieurs séparés. Idem pour exécuter deux accès mémoire simultané : cela demande d'avoir deux unités mémoire et un cache multiport. Mais en pratique, aucun CPU superscalaire ne fait cela. La totalité des CPU double émission se débrouille avec un seul circuit multiplieur et une seule unité mémoire. Et cela entraine l'apparition de dépendances structurelles, qui font que certaines paires d'instructions sont impossibles. Par exemple, impossible d'émettre deux multiplications en même temps avec un seul circuit multiplieur. Mais émettre une multiplication en parallèle d'une addition est parfaitement possible. Il y a donc des paires d'instructions autorisées et des paires interdites. Et la logique est la même sur les CPU à triple ou quadruple émission, et au-delà. : des triplets ou quadruplet d'instructions sont impossibles à émettre simultanément, alors que d'autres le sont. Pour le dire autrement, il y a des '''contraintes d'appariement''' sur les instructions à émettre en même temps. ===L'impact des contraintes d’appariement sur la performance=== Ces contraintes d’appariement ont un impact sur les performance, mais celui-ci peut être grandement mitigé. Les programmes informatiques utilisent en pratique beaucoup plus d'opérations entières de base que de multiplication, branchements et accès mémoire. Il y a environ 5 opérations entières de base pour une multiplication entière. En pratique, il est rare de devoir exécuter deux multiplications à la fois. Idem pour les décalages et rotations, qui sont assez éloignés les uns des autres. Deux branchements consécutifs sont quant à eux plus fréquents. Deux accès mémoire consécutifs est assez rare, mais ils restent assez proches les uns des autres malgré tout. La conséquence est que certaines paires d'instructions sont privilégiées par rapport au reste. Les paires regroupant deux calculs basiques (additions, soustractions, ...) sont systématiquement supportées, car dupliquer des ALU entières est facile. Les paires regroupant un calcul et une autre instruction sont elles aussi systématiquement supportées. Par contre, les paires avec deux branchements, deux accès mémoire, deux décalages/rotations, ou deux multiplications/divisions sont fréquemment interdites. Seuls les CPU superscalaires larges peuvent les permettre, et encore : sous conditions. Le résultat est que le processeur peut se débrouiller avec une seule unité mémoire et une unité de branchement. ===Terminologie pour la suite du chapitre=== Décrire les contraintes d’appariement demande de dire quelles combinaisons d’instructions sont autorisées ou interdites. Et pour cela, nous allons introduire les abréviations suivantes : * INT est un raccourcis pour parler d'une opération entière simple, qu'une ALU entière peut exécuter. * MUL correspond à une opération entière complexe, typiquement une multiplication, éventuellement à une division. * FLOAT désigne une opération flottante, peut importe laquelle. * MEM désigne un accès mémoire, à savoir une instruction ou µop mémoire. * BRANCH désigne une instruction de branchement. Cette terminologie permettra de définir des paires d'instructions, qu'elles soient possibles ou interdites. Par exemple, l'expression INT + FLOAT désigne une paire d'instruction regroupant une instruction entière et une instruction flottante, peu importe l'ordre des deux instructions. Il en sera de même pour les triplets, quadruplets, ou plus. Par exemple, l'expression INT + INT + FLOAT + MUL désigne un quadruplet d'instructions regroupant : deux instructions entières, une instruction flottante, et une multiplication entière. J'utiliserais aussi des parenthèses pour regrouper certaines combinaisons. Par exemple, l'expression INT + (MUL /FLOAT) veut parler d'une paire d'instruction où la première est une opération entière basique, la seconde est : soit une multiplication, soit une opération flottante. ==Le ''front-end'' des processeurs superscalaires== Un processeur superscalaire doit charger, décoder, et exécuter plusieurs instructions par cycle. Par exemple, un processeur triple émission doit charger 3 instructions lors d'un premier cycle, puis les décoder au cycle suivant, et les exécuter au cycle suivant. Il travaille donc pas paquets de 3 instructions, dans cet exemple. Intuitivement, charger, décoder et exécuter plusieurs instructions à la fois demande de dupliquer des circuits. Par exemple, il faut dupliquer les décodeurs pour décoder plusieurs instructions. Les unités de calcul sont aussi dupliquées, sauf sur certaines processeurs qui arrivent à ruser, mais laissons cela de côté pour plus tard. Mais d'autres circuits sont simplement modifiés, comme l'unité de chargement ou l'unité d'émission. Voyons ce qu'il en est dans le détail. {|class="wikitable" |- ! rowspan="2" | Processeur sans émission multiple | rowspan="2" | Chargement | rowspan="2" | Décodage | rowspan="2" | Renommage | rowspan="2" | Émission | Exécution / ALU | rowspan="2" | ''Commit''/ROB |- | Exécution / ALU |- ! rowspan="4" | Processeur superscalaire | rowspan="4" | Chargement | rowspan="2" | Décodage | rowspan="4" | Renommage | rowspan="4" | Émission | Exécution / ALU | rowspan="4" | ''Commit''/ROB |- | Exécution / ALU |- | rowspan="2" | Décodage | Exécution / ALU |- | Exécution / ALU |} Nous allons d'abord voir ce qu'il en est pour le ''front-end'', à savoir la partie du CPU qui s'occupe de charger et décoder les instructions. Elle regroupe l'unité de chargement, les décodeurs, l'unité de prédiction de branchement, la file de µops, la file d'instruction, et divers caches. En soi, les changements portent surtout sur l'unité de chargement et les décodeurs. ===Le chargement de plusieurs instructions consécutives=== Un processeur superscalaire charge plusieurs instructions en même temps, ''en tenant compte de la présence de branchements''. La partie en italique est importante et elle a une conséquence très forte : la manière de faire est fortement différente entre un CPU superscalaire étroit et large. Un CPU superscalaire étroit fait au plus simple : il charge un bloc de 2, 3, 4 instructions consécutives en mémoire RAM. Il faut alors doubler/tripler/quadrupler la taille du bus connecté au cache d'instruction. Par exemple, pour charger quatre instructions de 32 bits chacune, il suffit de lire un bloc de 128 bits dans le cache d'instruction. Un bloc de 8, 16, 32 octets est donc lu depuis le cache et est ensuite découpé en instructions, envoyées chacun à un décodeur. Charger plusieurs instructions consécutives ne marche bien qu'en absence de branchements. La présence de branchements casse cette dynamique, et il faut gérer leur présence. * Sans prédiction de branchement, toutes les instructions d'un bloc sont décodées et exécutées en même temps, même s'il y a un branchement dans le tas. * Avec prédiction de branchement, les instructions situées après le branchement ne sont pas décodées ni exécutées. L'unité de chargement coupe le bloc chargé au niveau du premier branchement pris, remplit les vides avec des NOP, avant d'envoyer le tout à l'unité de décodage. Les instructions à l'adresse de destination seront chargées au cycle suivant, en chargeant un nouveau bloc. [[File:Fetch sur un processeur superscalaire avec prediction de branchements.png|centre|vignette|upright=2|Fetch sur un processeur superscalaire avec prédiction de branchements.]] Un autre défaut est que sur le CPU charge un bloc de 128 bits à la fois, les instructions doivent être alignées sur 128 bits. Et on peut généraliser à n'importe quelle taille de bloc. Si les instructions ne sont pas alignées, il faut gérer la situation. Il arrive qu'une instruction soit à cheval sur deux blocs, avec un morceau dans le premier bloc, un autre dans le second. Dans ce cas, l'instruction est chargée en deux fois, et des circuits doivent gérer la situation pour recoller les morceaux. Sur les CPU avec des instructions de taille fixe, le problème ne survient que rarement. Mais il est fréquent avec des instructions de taille variable. Un autre défaut est lié aux branchements. Imaginez qu'un branchement s'exécute et branche vers une instruction de destination. Rien n'indique que l'instruction de destination sera au tout début d'un bloc. Si ça se trouve, elle sera en troisième position d'un bloc de 4 instructions. Une solution simple charge le bloc en respectant l'alignement, et les deux premières instructions du bloc ne sont pas à exécuter. Il y a donc une petite perte de performance. Certains CPU, comme l'IBM RS/6000, ont la machinerie charger un bloc de N instructions même s'il n'est pas aligné. La machinerie en question se trouve dans le cache d'instruction et est tout sauf simple à expliquer,ce qui fait que je me permets de la passer sous silence. Un détail, cependant : elle ne permet pas de charger des blocs qui sont à cheval sur deux lignes de cache. ===Le décodage parallèle des instructions=== Pour le décodage des instructions, plusieurs instructions sont décodées en même temps. On parle alors de '''décodage parallèle'''. Pour ce faire, un processeur superscalaire contient plusieurs décodeurs, chacun pouvant décoder une instruction en parallèle des autres. Prenons par exemple un processeur superscalaire à 4 voies : le décodage se fera dans quatre décodeurs séparés, qui fonctionneront en parallèle. Cependant, cette implémentation ne marche bien que pour les processeurs RISC, les processeurs CISC devant faire avec un microcode gourmand en circuits. Les processeurs CISC utilisent des décodeurs hybrides, avec un microcode qui complémente un décodeur câblé. Dupliquer le microcode aurait un cout en transistors trop important, ce qui fait que seuls les décodeurs câblés sont dupliqués. Les CPU CISC superscalaires disposent donc de plusieurs décodeurs simples, capables de décoder les instructions les plus courantes, avec un seul microcode. La conséquence est qu'il n'est pas possible de décoder deux instructions microcodées en même temps. Par contre, il reste possible de décoder plusieurs instructions non-microcodées ou une instruction microcodée couplée à une instruction non-microcodée. Vu qu'il est rare que deux instructions microcodées se suivent dans un programme, le cout en performance est extrêmement mineur. Il faut noter qu'une instruction correspond à une ou plusieurs µops. Il est donc possible de charger 5 instructions et de se retrouver avec 8 µops en sortie des décodeurs. Et c'est la source d'une distinction très importante. Plus haut, j'ai parlé de processeurs superscalaires à N voies. La terminologie peut avoir deux interprétations. La première est que le processeur charge/décode N instructions à la fois. Une seconde est qu'il est capable d'exécuter/émettre N µops en même temps. Les deux ne sont pas équivalentes, vu qu'un processeur superscalaire peut, par exemple, charger/décoder N instructions et émettre/exécuter N+2 µops. La distinction est particulièrement importante sur les processeurs superscalaires larges. Certains d'entre eux sont capables de charger 5 instructions et d'en émettre le double ! Un exemple classique était les processeurs Athlon d'AMD, qui pouvaient décoder deux instructions par cycle, mais émettre 4 µops par cycle. Ce n'était pas un problème, car beaucoup d'instructions, même basiques, étaient décodées en 2 µops. ===L'émission parallèle des instructions=== La superscalarité modifie en profondeur l'unité d'émission, notamment au niveau de son interface. Avant, elle recevait une micro-opération sur son entrée, et fournissait la micro-opération émise sur une sortie. Et cela vaut aussi bien pour une unité d'émission simple, un ''scoreboard'', une fenêtre d'instruction ou des stations de réservation. Mais avec l'émission multiple, les sorties et entrées sont dupliquées. Pour la double émission, il y a deux entrées vu qu'elle doit recevoir deux micro-opération décodées/renommées, et deux sorties pour émettre deux micro-opérations. Dans ce qui suit, nous ne parlerons pas d'entrée et de sortie, mais de '''ports de décodage''' et de '''ports d'émission'''. Les ports de décodage reçoivent des micro-opérations provenant de l'unité de décodage, les ports d'émissions sont pour les micro-opérations émises. Les ports d'émission injectent les micro-opérations émises dans le pipeline, elle sont propagée au banc de registre, aux ALUs, etc. Un processeur superscalaire a forcément plusieurs ports d'émission (et de décodage). Si l'interface de l'unité d'émission change, l'intérieur est tout autant modifié. Sur un CPU non-superscalaire, l'unité d'émission reçoit une µop "candidate à l'émission", et détecte les dépendances entre avec les µops en vol, en cours d'exécution dans le pipeline. Et c'est toujours le cas sur les processeurs superscalaires : l'unité d'émission détecte les dépendances entre les µops candidates et les µops en vol. Mais avec la siperscalarité, elle doit en plus tester les dépendances entre les µops candidates. Si l'unité d'émission est un ''scoreboard'', il doit détecter les dépendances entre instructions émises simultanément et les sérialiser si besoin. Avec une fenêtre d'instruction, il n'y a pour ainsi dire rien à faire, vu que les dépendances sont éliminées par le renommage de registre et que les signaux de réveil gèrent les dépendances RAW. C'est la raison pour laquelle les processeurs superscalaires modernes n'utilisent pas de ''scoreboard''. ==Les processeurs implémentés avec plusieurs pipelines== Nous venons de voir comment la superscalarité impacte le ''front-end'', il est maintenant de voir ce qu'il en est de l'unité d'émission et des ''back-ends''. Un processeur superscalaire peut utiliser l'exécution dans le désordre et le renommage de registres, mais ce n'est pas une obligation. Historiquement, les premiers processeurs superscalaires n'utilisaient pas d'exécution dans le désordre. Le meilleur exemple est celui du Pentium 1 et 2 d'Intel. Ils étaient tous deux des processeurs superscalaires, mais n'avaient pas d'exécution dans le désordre, qui est apparue sur le Pentium 3. les explications sont plus simples si on met de côté l'exécution dans le désordre. Vous vous dites que l'implémentation d'un processeur superscalaire n'est pas la même suivant que le CPU utilise l'exécution dans le désordre, ou non. Mais dans les faits, la distinction est plus subtile que ca. Pour l'expliquer, je vais utiliser une classification originale, qui sépare les processeurs superscalaires en trois sous-types. La distinction se fait sur la manière dont la superscalarité est implémentée en matériel.La classification que j'utilise distingue : * Les processeurs avec plusieurs pipelines, sans exécution dans le désordre. * Les processeurs superscalaires avec un pipeline dynamique. * Les processeurs avec des fenêtres d'instruction décentralisées. La distinction n'est pas qu'une question d'implémentation matérielle. Elle impacte aussi le ratio entre instructions chargées/décodées et instructions émises. Vous vous dites que les deux sont égales, mais ce n'est le cas que sur les processeurs avec N pipelines ! Sur les autres processeurs, il est possible de décoder 4 instructions, mais d'en émettre maximum 6 par cycle ! Et sur les CPU avec des fenêtres d'instruction décentralisées, il y a aussi une différence entre le nombre d'instructions qui sont ''dispatch'' et ''schedule''. La catégorie la plus simple est celle des '''processeurs avec plusieurs pipelines'''. Ce sont des processeurs superscalaires sans exécution dans le désordre. Ils partent d'un processeur non-superscalaire, et ajoutent un second pipeline, en sortie de l'unité d'émission. Le second pipeline permet l'émission parallèle d'une seconde instruction. Le second pipeline n'est pas identique au pipeline originel, sur plusieurs points. Premièrement, il n'a pas exactement les mêmes circuits. Le second pipeline est généralement composé d'une unité de calcul, d'une FPU, éventuellement d'un second banc de registres, guère plus. Il permet d'exécuter une seconde opération de calcul, pas plus, ce qui est à l'origine de contraintes d'appariemment assez fortes. De plus, il n'a pas forcément la même longueur, il peut avoir des étages en moins ou en plus. Il s'agit de la forme la plus simple de processeur superscalaire et nous allons leur consacrer cette section. ===La double émission entière-flottante=== L'exemple le plus simple utilise un second pipeline pour les opérations flottantes, séparé du pipeline originel. L'ajout d'un pipeline flottant permet d'émettre une micro-opération entière en même temps qu'une micro-opération flottante, ce qui s'appelle la '''double émission entière-flottante'''. La micro-opération entière s'exécute dans l'ALU entière, la micro-opération flottante dans la FPU. Les exemples les plus notables sont le PA-RISC 7100, le POWER 1 (RIOS .& et .9) et le PowerPC 601. L'Apollo DN10000 et l'Intel i860 utilisaient aussi cette forme de double émission, mais étaient des prototypes de CPU VLIW. [[File:Double émission entière-flottante.png|centre|vignette|upright=2|Double émission entière-flottante]] Pour bien expliquer les choses, prenons le processeur illustré ci-dessous, avec deux ''back-end'' séparés : un pour la FPU, et un autre pour le reste. Les instructions entières et mémoire passent toutes par le ''back-end'' généraliste. [[File:CPU non-superscalaire basique, avec une FPU.png|centre|vignette|upright=1.5|CPU non-superscalaire basique, avec une FPU]] Les deux ''back-end'' sont des pipelines séparés, mais il faut que l'unité d'émission soit spécialement conçue pour en profiter. Un processeur simple émission peut démarrer une seule µop par cycle : soit la µop est envoyée au ''back-end'' flottant, soit elle est envoyée au ''back-end'' général. Avec la double émission entière-flottante, le processeur peut envoyer une µop dans chaque pipeline, à chaque cycle. Vu que la FPU et son banc de registre forment déjà un pipeline séparé, les seules modifications se font au niveau du ''front-end'', unité d'émission inclue. {| |- |[[File:CPU simple émission avec une FPU.png|centre|vignette|upright=1.5|CPU simple émission avec une FPU.]] |[[File:Double émission entière flottante avec un second pipeline.png|centre|vignette|upright=1.5|CPU avec un second pipeline flottant.]] |} Notez que le terme double émission ''entière''-flottante est un peu trompeur. En réalité, la technique permet d'émettre une instruction flottante avec n'importe quelle autre instruction non-flottante, avec des restrictions pour les branchements. Il est par exemple possible d'exécuter un calcul flottant en parallèle d'un accès mémoire. Sur les CPU 32 bits, il est possible de faire les multiplications dans la FPU. Pour rappel, la FPU contient un multiplieur entier 53-54 bits, pour multiplier les mantisses de deux flottants 64 bits, qu'on peut utiliser pour faire les multiplications entières 32 bits. Cela permet d'émettre une multiplication en même temps qu'une opération entière dans l'ALU. Ainsi, le CPU pouvait émettre deux instructions : une opération entière de base, avec soit une multiplication, soit une opération flottante. En clair, une émission de type INT + (MUL /FLOAT). Par contre, on perd la possibilité d'émettre une multiplication entière avec une opération flottante, mais c'était un compromis qui en valait la peine. Le CPU PA-RISC 7100 implémentait cette optimisation. Il s'agissait d'un CPU double émission entière-flottante, qui utilisait donc un partitionnement assez simple. Le combo partitionnement + reconditionnement permettait ici de gagner en performance, pour un cout en matériel très limité. D’autant plus limité que le jeu d'instruction prévoyait le reconditionnement de la FPU. Les multiplications ne lisaient pas leurs opérandes dans les registres entiers, mais dans les registres flottants. ===La double émission entière=== Une autre possibilité ajoute un second pipeline, capable d'exécuter des opérations entières simples, appelé le pipeline entier. Le processeur supporte alors la '''double émission entière''', à savoir qu'ils peuvent émettre une opération entière en plus d'une autre instruction. L'opération entière en question est une opération simple, comme une addition, une soustraction, une opération bit à bit, une comparaison, etc. Les exemples classiques sont les processeurs Intel i960 et Pentium. L'implémentation était différente entre l'i960 et le Pentium : Le Pentium avait une seconde ALU entière, alors que le i960 rusait. Le Pentium dupliquait l'ALU entière, pour créer un second pipeline entier. Sur le Pentium originel, les contraintes d'appariement étaient assez strictes. Le Pentium avait une FPU, mais ne pouvait pas l'utiliser en double émission. Il lui était impossible d'exécuter une opération flottante en même temps qu'une opération entière (branchements inclus). Le Cyrix 6x86 était une sorte de Pentium produit par l'entreprise Cyrix, avec une microarchitecture différente. Il avait des contraintes d’appariement réduites. Par exemple, il pouvait émettre une opération flottante en parallèle d'une opération entière. Nous détaillerons ces processeurs dans le prochain chapitre, qui porte justement sur les CPU superscalaires x86. [[File:CPU simple et double émission avec un second pipeline entier.png|centre|vignette|upright=2.5|CPU simple et double émission avec un second pipeline entier.]] L'Intel i960 n'avait pas de FPU, juste une ALU entière et une unité mémoire. Il utilisait l'unité mémoire comme une seconde ALU entière limitée. Pour rappel, une unité mémoire contient une AGU, à savoir une ALU spécialisée dans les calculs d'adresse. Elle est capable de faire des additions, soustractions, et quelques décalages limités (des décalages par 2, 4, 8, pas plus). L'Intel i960 ajoutait des connexions entre AGU et banc de registre, afin que celle-ci serve de seconde unité de calcul. L'AGU est déjà reliée aux deux ports de lecture, pour lire les opérandes du calcul d'adresse, il suffit de la connecter sur le port d'écriture. Le processeur peut alors émettre deux instructions/µops entières, sous certaines conditions. [[File:Emission multiple des opérations entières, double émission.png|centre|vignette|upright=2|Émission multiple des opérations entières, double émission.]] ==Les processeurs superscalaires dynamiques== Les processeurs précédents, avec 2 pipelines, avaient une unité d'émission assez simple. Elle avait deux ports de décodage, et deux ports d'émission. Le nombre de ports de décodage et d'émission étaient identiques. Vous vous dites que c'est naturel, et intuitif, que c'est normal pour un processeur superscalaire. Mais en réalité, la majorité des processeurs superscalaires a plus de ports d'émission que de décodage. Expliquer pourquoi va cependant nous demander de parler de comment la superscalarité exploite les unités de calcul. Posons-nous cette question : de combien d'unités un processeur superscalaire a besoin ? Les unités en question ne sont nécessairement des unité de calcul, l'unité mémoire et l'unité de branchement comptent aussi. La réponse dépend du nombre de voies. Si le ''front-end'' charge et décode un paquet de N instructions, exécuter ces N instructions simultanément demandera là aussi au moins N unités de calcul. Notez le terme : ''au moins'', car c'est là tout l'enjeu de cette section. La réponse dépend aussi des contraintes d’appariement. Pour un CPU superscalaire "parfait", sans contraintes d’appariement, toutes les unités doivent être dupliquées, y compris l'unité mémoire. Un processeur à double émission parfaite devrait dupliquer l'ALU entière, le ''barrel shifter'', le circuit multiplieur, la FPU, l'unité mémoire, et l'unité de branchement, ce qui ferait passer de 6 unités à 12. Le cout en circuit est énorme, même pour de la double émission ! Et pour un CPU à N pipelines, le cout en circuit encore plus prohibitif ! En pratique, les processeurs superscalaires qui font ça sont très rares. La majeure partie des processeurs superscalaires utilisent d'autres techniques : le partitionnement, la duplication partielle des ALUs, la duplication des autres unités. ===Le partitionnement des unités fonctionnelles=== Les CPU superscalaires les plus simples ne dupliquent pas leurs unités de calcul, ils se contentent d'exploiter les unités existantes. Et pour expliquer comment, nous allons partir de l'exemple d'un processeur non-superscalaire contient une ALU entière, une FPU, une unité mémoire et une unité de branchement. Les quatre unités peuvent fonctionner en parallèle, à savoir qu'une unité peut exécuter une instruction à elle toute seule, sans interagir avec les autres. Cela suffit pour créer un processeur quadruple émission, qui peut émettre en même temps : une opération entière, une opération flottante, un branchement, un accès mémoire. Le principe marche aussi sur un processeur avec moins de 4 unités, par exemple sans FPU et/ou sans unité de branchement, ou avec au contraire des unités en plus. L'idée générale est d’émettre une µops séparée dans chaque unité fonctionnelle. Utiliser ainsi les unités existantes, pour rendre un CPU superscalaire, s'appelle le '''partitionnement superscalaire'''. Le terme n'est pas beaucoup utilisé en-dehors de quelques vieux articles académiques. Le partitionnement est fortement dépendant du jeu d'instruction, car il demande de bien séparer instructions entières, flottantes, branchements et instructions mémoire. Et autant c'est bien le cas sur les CPU RISC, autant les CPU CISC ne sont pas dans ce cas. Les CPU CISC ont des instructions ''load-op'', qui effectuent une instruction arithmétique et une µop mémoire. Et ces instructions sont très compliquées à implémenter avec le partitionnement. Et au-delà de ça, les CPU CISC ont beaucoup d'instructions qui sont décodées en plusieurs µops, à exécuter dans un ordre bien précis. On peut pas émettre les µops en question simultanément. Le partitionnement a un cout en circuit très limité, pour un gain en performance lui aussi limité. Pour reprendre le CPU de l'exemple précédent, il est rare que l'on ait à exécuter ces quatre instructions en même temps. En pratique, les combinaisons fréquentes sont les combinaisons calcul + accès mémoire, calcul + branchement, calcul entier + calcul flottant. Utiliser une quadruple émission de ce type n'est donc pas une bonne idée, alors que la double émission parait plus adaptée. Et c'est là que la différence entre ports d'émission et de décodage devient intéressante. L'idée est d'avoir une unité d'émission avec deux ports de décodage, et 4 port d'émission (un par unité). Les contraintes d’appariement varient alors suivant le processeur, mais la règle générale est que si deux micro-opérations tombent dans deux unités différentes, on peut les émettre en même temps. En clair, on peut émettre une opération entière avec une opération flottante, une opération entière avec un branchement, un accès mémoire avec un branchement, etc. Et cela rend le partitionnement bien plus intéressant que prévu. Les tout premiers CPU superscalaires étaient des CPU double émission, qui utilisaient le partitionnement avec : une ALU entière, une unité mémoire et une FPU. Nous parlerons de '''partitionnement INT-FLOAT-MEM'''. L'avantage sur la double émission flottante est que les combinaisons INT + MEM et FLOAT + MEM étaient autorisées. L'intérêt était d'émettre une opération en parallèle d'un accès mémoire, ce qui est un doublet d'instruction assez fréquent. Les exemples classiques sont l'HyperSPARC, de l'alpha 21064 et du PowerPC 603. Les contraintes d’appariement variaient grandement suivant le processeur. Le PowerPC 603 implémentait une double émission "parfaite". C'est à dire que toutes les combinaisons possibles à deux instructions étaient possibles, que ce soit INT + FLOAT, INT + MEM, FLOAT + MEM. En plus de cette double émission "parfaite", le CPU utilisait la technique dite de ''branch folding'', qui permettait d'exécuter un branchement sans utiliser d'unité de calcul, ce qui fait qu'il était qualifié de ''CPU 2-way + Branch''. A l'opposé, le processeur alpha 21064 avait pas mal de contraintes d’appariement, à savoir que certaines combinaisons de deux instructions étaient interdites, bien que compatibles avec les unités disponibles. Une des raisons est que le banc de registre n'avait pas assez de ports de lectures, ce qui fait que certaines combinaisons avec une écriture mémoire n'étaient pas permises. Les concepteurs avaient volontairement inséré ces contraintes afin de simplifier le design du processeur. Avant de poursuivre, précisons que la FPU regroupe un additionneur flottant et un multiplieur flottant. Et ce fait peut être exploité pour approfondir le partitionnement. La '''double émission FADD/FMUL''' permet d'émettre une addition flottante en même temps qu'une multiplication flottante. Elle a été utilisée sur les processeurs x86, Alpha et SPARC. Les autres circuits de calcul flottant sont souvent regroupés avec l'additionneur. La division est parfois e même port que le multiplieur flottant, parfois sur celui de l'additionneur (quand elle est microcodée et exécutée en enchainant les soustractions). ===La duplication des ALU entières=== Le partitionnement est une technique élégante, simple, facile à implémenter et au cout en circuits modeste. Mais il faut avouer que cela ne donne pas des CPU superscalaires très efficaces. La raison est que le partitionnement permet d'émettre des paires, triplets, quadruplets de micro-opérations qui sont rares en pratique. Les combinaisons les plus fréquentes en pratique sont des paires/triplets d'opérations entières, souvent des paires/triplets d'additions ou d'opérations entières simples. Il est donc primordial d'émettre plusieurs opérations entières simultanément. Le fait d'émettre et d'exécuter plusieurs opérations entières en même temps s'appelle l''''émission entière multiple'''. Pour exécuter la seconde/troisième opération entière, il faut dupliquer les ALU entières (ou réutiliser l'AGU comme ALU entière comme le fait l'Intel i960). Le processeur doit avoir deux ALU entières pour de la double émission entière, trois pour de triple émission entière, etc. De plus, il faut ajouter des ports au banc de registre, pour lire deux fois plus d'opérandes et décrire deux résultats. Et enfin, il faut rajouter un port d'émission pour pouvoir émettre une micro-opération entière en plus. [[File:Processeur non-superscalaire basique 03.png|centre|vignette|upright=2|Processeur non-superscalaire basique]] La tendance est de multiplier les ALU entières, mais pas le circuit multiplieur. Le cout en transistors est alors grandement réduit, au prix de l'apparition de dépendances structurelles. Le CPU peut émettre deux additions en même temps, ou une addition et une multiplication, mais pas deux multiplications. Il en est de même avec le ''barrel shifter'', qui n'est pas dupliqué. Il est donc possible d'émettre un décalage avec une opération de base, mais pas deux décalages en parallèle. Mais ces contraintes d’appariement ne sont pas un problème, car il est rare de devoir émettre deux multiplications/décalages en même temps. La plupart des programmes sont majoritairement remplis d'additions, avec des multiplications assez rares et des décalages qui le sont encore plus. La moyenne est proche d'une multiplication pour 4/5 additions. Vu que des multiplications consécutives sont très rares, dupliquer le multiplieur est presque inutile. Disposer de plusieurs circuits multiplieurs ou de plusieurs ''barrel shifters'' serait donc un cout en circuits qui ne servirait que rarement et n'en vaut pas la chandelle. Il faut noter que sur les CPU 32 bits, les multiplications peuvent être exécutées dans la FPU ou dans un multiplieur séparé, tout dépend de si le CPU exécute les multiplications dans la FPU ou pas. {|class="wikitable" |- ! Voie d'émission n°1 !! Voie d'émission n°2 !! Voie d'émission n°3 |- | ALU entière || ALU entière || FPU |} Reste à connecter les unités de calcul à l'unité d'émission (indirectement, car il y a des étages de pipeline entre les deux). Une implémentation basique utilisee un port d'émission par unité. Prenons l'exemple d'un CPU superscalaire avec 4 ALU entières, une FPU, et une unité mémoire. Vu qu'il y a 6 unités, cela fait donc 6 ports d'émission. L'exemple historique le plus impressionnant est celui du Motorola 88110, un CPU double émission qui possède 10 unités. Il contient deux ALU entières, un circuit multiplieur, un circuit diviseur, une unité de manipulation de bits, une unité mémoire, un additionneur flottant, et deux unités graphiques pour des calculs impliquant des pixels. Le multiplieur est utilisé à la fois pour les multiplications entières, flottantes et le produit de deux pixels. Idem pour le diviseur, qui sert pour les divisions entières et flottantes. Et chaque unité a son propre port d'émission. La conséquence est que les contraintes d'appariement du Motorola 88110 sont simples : tant que deux µops atterrissent dans des unités séparées, la paire est permise. Il est par exemple possible d'émettre simultanément deux opérations entières simples, vu qu'il y a deux ALU. Par contre, impossible d'émettre simultanément deux multiplications, deux divisions, deux opérations flottantes, deux manipulations de bit, deux accès mémoire, etc. De plus, le partage du multiplieur fait qu'on ne peut pas émettre une multiplication entière avec une multiplication flottante. Idem avec les divisions, vu que le diviseur est aussi partagé. Ce sont les seules contraintes. ===Le partage des ports d'émission=== La présence d'un circuit multiplieur et d'un ''barrel shifter'' pose des problèmes pour le partitionnement. Il est possible d'avoir un port d'émission dédié au multiplieur, et un autre au ''barrel shifter'', qui sont séparés de ceux pour l'ALU entière. Il est alors possible d'émettre une opération entière, une multiplication et un décalage, simultanément. Nous parlerons de '''partitionnement entier''' pour désigner cette possibilité. [[File:Partitionnement superscalaire entier.png|centre|vignette|upright=2|Partitionnement superscalaire entier.]] Le partitionnement entier est cependant très rare, car le gain en performance serait trop faible au regard des couts en circuit. Quelques processeurs historiques l'utilisaient, mais ce n'est plus le cas de nos jours. La raison est que les multiplications et les décalages sont assez rares dans le code, ce qui fait que le port d'émission associés sont sous-utilisés. Et un port d'émission a un cout en circuit assez important. Et c'est sans compter qu'ajouter des ports sur le banc de registres juste pour un multiplier sous-utilisé n'en vaut pas la peine. L'idéal serait de trouver un moyen de réduire le nombre de ports d'émissions, sans réduire le nombre d'unités de calcul. [[File:Emission multiple des opérations entières, implémentation naive.png|vignette|Émission multiple des opérations entières, implémentation naive]] La solution consiste à '''partager les ports d'émission''', à savoir connecter un port d'émission à plusieurs unités de calcul. L'exemple classique est justement lié aux circuits multiplieur et ''barrel shifters''. L'idée est que le multiplieur et le ''barrel shifter'' sont mis sur le même port d'émission qu'une ALU entière. Prenons par exemple le processeur à double émission entière illustré ci-contre. Un port d'émission est relié à une ALU entière seule, et l'autre port d'émission alimente la seconde ALU entière et le multiplieur. La conséquence est que le processeur peut émettre deux opérations entières, dont une multiplication. Si on avait utilisé un port d'émission dédié au multiplieur, il aurait été possible d'émettre deux opérations entières et une multiplication. On perd donc une voie, mais on gagne en flexibilité et on économise des circuits. Notons que ce partage signifie aussi que le multiplieur partage les ports sur le banc de registre, ce qui fait une double économie. [[File:Emission multiple des opérations entières, implémentation courante.png|centre|vignette|upright=1.5|Émission multiple des opérations entières, implémentation courante]] En général, le ''barrel shifter'' et le multiplieur sont placés sur deux voies d'émission séparés, ce qui permet d'émettre un décalage et une multiplication en même temps. Les mettre sur le même port ne le permettrait pas. {|class="wikitable" |- ! Voie d'émission n°1 !! Voie d'émission n°2 !! Voie d'émission n°3 |- | * ALU entière * Multiplieur | * ALU entière * Barrel shifter | FPU |- | * Additions, soustractions, opérations bit à bit, etc. * Multiplication | * Additions, soustractions, opérations bit à bit, etc. * Décalages et rotations | Opération flottante |} Mais le partage des ports d'émission peut appliquer à n'importe quelle unité. Prenons l'exemple d'un CPU superscalaire avec 4 ALU entières, une FPU, et une unité mémoire. Vu qu'il y a 6 unités, cela fait donc 6 ports d'émission. Cependant, il est rare qu'un bloc de 6 instructions contienne exactement 4 opérations entières, une opération flottante, et un accès mémoire. Beaucoup de ports d'émission seraient sous-utilisés. La solution est de partager des ports d'émission, pour passer à la quadruple émission. Le CPU peut alors répartir 4 instructions sur les ports d'émission adéquats, à chaque cycle. Le CPU a donc 4 ports de décodage et 6 ports d'émission. Une implémentation classique permet n'importe quelle combinaison de 4 instructions compatible avec les 6 unités. Par exemple, il devient possible d'émettre simultanément 4 additions, la combinaison 2 * INT + FLOAT + MEM, 3 * INT + MEM, et bien d'autres. L'exemple relie une ALU entière, la FPU et l'unité mémoire sur le même port. Le CPU a alors quatre ports d'émission, qui ne sont pas "identiques". Trois d'entre eux sont reliés à une ALU entière chacun, rien d'autre. Le quatrième est lui relié à une ALU entière, la FPU et l'unité mémoire. On économise alors deux ports d'émission, mais cela entraine l'apparition de contraintes d’appariement. Le CPU peut émettre 3 opérations entières + n'importe quelle autre opération. Mais les autres combinaisons sont interdites. Le CPU peut émettre 6 opérations entières simultanément, grâce à 6 ALU entières. Les accès mémoire ou opérations flottantes ne viennent pas en plus, mais en remplacement des opérations entières. Regrouper plusieurs ALU sur un même port d'émission est donc à l'origine de dépendances structurelles, de nouvelles contraintes d’appariement. Dans certaines situations de '''''port contention''''', on a assez d'ALU pour exécuter N micro-opérations, mais la répartition des ALUs sur les ports l'empêche. Impossible d'émettre deux micro-opérations sur deux ALU si elles sont sur le même port d'émission. On peut réduire les cas de ''port contention'' en répartissant correctement les ALU sur les ports d'émission, mais sans jamais les supprimer complétement. Un exemple est celui du PowerPC 604. Il implémentait la quadruple émission, alors qu'il avait 5 unités de calcul. Mais deux unités partageaient un même port d'émission. Dans le détail, il avait deux ALU entières, un circuit multiplieur/diviseur, une FPU, une unité mémoire. La FPU et le multiplieur étaient sur le même port d'émission. En conséquence, le CPU pouvait émettre seulement les combinaisons INT + INT + MEM + (MUL/FLOAT). : Il est intéressant de voir l'exemple d'un processeur superscalaire parfait, sans contraintes d'appariement, à N voies. Pour cela, on part d'un processeur non-superscalaire, avec M unités de calcul. Un tel processeur peut s'implémenter avec N ports d'émission, chacun relié à M unités de calcul possibles, ce qui fait que les M unités de calcul sont dupliquées en N exemplaires. Si on prend par exemple un CPU non-superscalaire avec une ALU, une FPU, une unité mémoire et une unité de branchement, un port d'émission sera relié à une ALU, une FPU, une unité mémoire et une unité de branchement. ===La duplication des autres unités fonctionnelles=== Dupliquer les ALU entière est une première étape, mais il est aussi possible de dupliquer les autres unités fonctionnelles. Par exemple, il est possible de dupliquer l'unité mémoire ou l'unité de branchement. Reste à voir quelles unités dupliquer, et pourquoi. Précisons que dans ce qui va suivre, nous allons parler de la duplication des unités en général, pas seulement des unités mémoire/branchement. Nous allons continuer de parler de la duplication des ALU entières dans ce qui suit, mais de manière plus générale. Pour un processeur à N voies, il est théoriquement possible de dupliquer toutes les unités en N exemplaires. Mais ce n'est jamais fait en pratique, car le cout en circuits serait trop important et le gain en performance trop faible. Mieux vaut dupliquer certaines unités bien précises, quitte à avoir des contraintes d'appariement. Et à ce petit jeu, toutes les duplications ne se valent pas, certaines sont plus rentables en matière de performance, le cout en circuit n'est pas le même selon l'unité, etc. La rentabilité en performance dépend surtout des contraintes d'appariement. Les contraintes d'appariement entrainent une baisse de performance significatives si elles interdisent des paires d'instructions fréquentes. Aussi, les opérations très fréquentes, souvent appairées, gagnent à dupliquer l'unité associée. Et au contraire, les unités rarement utilisées ne doivent pas être dupliquées. Il se trouve que les unités les plus utilisées sont les ALU entières, suivies par les unités mémoire et l'unité de branchement. Le cout en circuit n'est aussi pas le même. Dupliquer les ALU entière est trivial, car elles ont un budget en transistors assez faible. Les circuits multiplieurs, diviseurs, et les ''barrel shifter'' ont eux un cout en transistor plus élevés. Si on devait faire un classement, on aurait : ALU entière < ''barrel shifter'' < multiplieur. Pour les unités mémoire, le cout en transistor est là aussi conséquent. Le résultat final est que dupliquer les ALU entières est privilégié sur le reste, comme l'a vu dans la section précédente. Les unités mémoire arrivent en seconde place, suivie par les multiplieurs/''barrel shifter'' à la troisième place, et les unités de branchement à la quatrième place. Les CPU superscalaires larges ont donc tendance à multiplier les unités mémoire en second. Les multiplieurs et ''barrel shifters'' sont eux aussi dupliqués sur les CPU superscalaires larges, mais cela vient souvent après avoir dupliqué les unités mémoire. La raison est que les accès mémoire sont critiques pour la performance des CPU à exécution dans le désordre, pas les multiplications. Pour les unités de branchement, une subtilité complique le fait d'en installer plusieurs. Que faire quand deux branchements sont pris ? La solution la plus simple est de ne pas se trouver dans cette situation : on n'utilise qu'une seule unité de branchement et on n’exécute qu'un seul branchement par cycle. La majorité des processeurs superscalaires étroits font ainsi. Les processeurs superscalaires larges ont cependant deux unités de branchements, mais ils n'acceptent qu'un seul branchement pris à la fois. Si les deux branchements pris sont exécutés en même temps, le processeur n’exécute que le plus récent, dans l'ordre du programme. ===Les CPU superscalaires mélangent les trois techniques=== Les CPU superscalaires utilisent toutes les techniques à la fois : partitionnement, duplication de l'ALU entière, duplication des autre unités fonctionnelles. Chaque processeur utilise ces trois stratégies à sa sauce, en fonction de son budget en transistor. Les quatre techniques ont cependant un cout en circuit différent, et donnent des gains de performances eux aussi forts différents. * Le '''partitionnement superscalaire''' a un cout en circuit modéré, car on n'a pas besoin de dupliquer d'ALU. * La '''duplication des ALU entières''' est la technique la plus importante : elle a un cout en circuit modéré, et donne des gains en performance impressionnants. * La '''duplication des autres unités''' a un cout en circuit variable, pour des gains en performance eux aussi variables. * Le '''partage des ports d'émission''' réduit le cout en circuit du CPU, pour des pertes de performances mineures si bien fait. L'usage du partitionnement seul, sans duplication, était surtout le fait des processeurs commercialisés entre les années 1989 et 1993. Le partage des ports d'émission a lui aussi mis un peu de temps pour s'imposer. Globalement, les processeurs superscalaires ont évolués progressivement, pour se rapprocher d'une sorte de standard. Mais pour comprendre ce qu'est ce standard, et en quoi les CPU historiques s'en sont progressivement rapprochés, il faut étudier l'histoire des CPU superscalaires. L'histoire des CPU superscalaires a commencé dès 1960, mais est resté un projet de recherche pendant plusieurs décennies. De nombreux designs ont été conçus, mais abandonnés avant la production, comme l'ACS-1 d'IBM. Les années 1980 ont vu l'arrivée de prototypes de processeurs VLIW, mais qui n'étaient pas superscalaires. Par exemple, l'Apollo DN10000 et l'Intel i860 pouvaient émettre deux instructions à la fois, sous conditions, mais demandaient que le compilateur fasse une bonne partie du travail. Les premiers CPU superscalaires commercialisés sont sortis en 1989. Deux CPUs superscalaires sont sortis cette année là : le RS/6000 d'IBM et l'i960CA d'Intel. Puis, la compétition s'est aussi lancée dans la bataille. Les CPU commercialisés avant l'année 1993 étaient à double émission, à l'exception du SuperSPARC qui utilisait la triple émission. Les modèles après 1993 hésitaient entre double et quadruple émission. Dans le tableau ci-dessous, les processeurs quadruple émission sont en bleu, les triple émission sont en rouge, les autres ne sont pas en couleur. {|class="wikitable" |- ! !! IBM !! HP PA-RISC !! SPARC !! Intel!! Alpha !! Autres |- ! 1989 | RS/6000 || || || i960CA || || |- ! 1990 | || || || || || |- ! 1991 | || || || || || Motorola 88110 |- ! 1992 | RSC || PA 7100 || class="f_rouge | SuperSPARC || || 21064 || |- ! 1993 | PPC 601 || || HyperSPARC || Pentium || || |- ! 1994 | class="f_bleu | POWER 2 || PA 7200 || || || class="f_bleu | 21164 || |- ! 1995 | 603/604 || class="f_bleu | PA 8000 || class="f_bleu | UltraSPARC 1 || Microarchitecture P6 (+ concurrence AMD) || || class="f_bleu | MIPS R1000 |- ! 1996 | class="f_bleu | 620 || || || || class="f_bleu | 21264 || |- ! ... || ... || ... || ... || ... || ... || ... |} Les premiers CPU à double émission sont assez représentatifs des CPU à double émission modernes. Il faut dire que se limiter à deux voies ne permet pas de faire beaucoup de choses. Voici le tableau chronologique disant quel CPU utilisait quelle technique. * Les CPU en vert utilisaient la double émission entière-flottante. * Les CPU en rouge utilisaient la double émission entière "pure". * Les CPU en jaune utilisaient un ''partitionnement complet'' entre ALU, FPU et unité mémoire. * Les CPU sans couleur utilisaient un grand nombre d'unités, avec beaucoup de ports d'émission. * Les CPU barrés utilisent la triple ou quadruple émission et seront vu dans la section suivante. {|class="wikitable" |- ! !! IBM !! HP PA-RISC !! SPARC !! Intel!! Alpha !! Autres |- ! 1989 | class="f_vert | RS/6000 || || || class="f_rouge | i960CA || || |- ! 1990 | || || || || || |- ! 1991 | || || || || || Motorola 88110 |- ! 1992 | class="f_vert | RSC || class="f_vert | PA 7100 || <s>SuperSPARC</s> || || class="f_jaune | 21064 || |- ! 1993 | class="f_vert | PPC 601 || || class="f_jaune | HyperSPARC || class="f_rouge | Pentium || || |- ! 1994 | <s>POWER 2</s> || PA 7200 || || || <s>21164</s> || |} C'est en 1994, que la quadruple émission est arrivée et que les CPU ont essayé divers implémentations. Il y avait alors trois stratégies pour la double/triple/quadruple émission, qui partageaient des problèmes similaires. La première implémentation dupliquait les ALUs et les FPUs, ce qui permettait d'émettre deux opérations flottantes avec deux opérations entières. Le POWER 2 dupliquait ses FPUs à la perfection, alors que le 21264 séparait l'additionneur et le multiplieur flottant. Les accès mémoire étaient traités comme des opérations entières et le processeur pouvait souvent en émettre deux à la fois. Mais une telle organisation n'est plus utilisée de nos jours, car, sans instructions flottantes à exécuter, elle est équivalente à de la double émission. La seconde implémentation utilisait deux ALU entières (dont un multiplieur), une unité mémoire et une FPU.Cette organisation est intéressante, mais encore trop stricte pour donner de bons résultats, les quadruplets avec exactement deux opérations entières, un accès mémoire et une opération flottante étant trop rares. L'usage de ces 4 unités en double ou triple émission a été utilisé sur le PA 7200 et le SuperSPARC. Malheureusement, en plus de problèmes au niveau de son cache, le SuperSPARC avait de nombreuses contraintes d’appariement, car le banc de registre n'avait pas assez de ports pour alimenter certaines combinaisons d'instructions. De plus, s'il émettait une multiplication, il ne pouvait pas émettre de seconde µop. Enfin, une troisième implémentation utilisait un grand nombre d'unités fort différentes, bien plus que quatre, avec au minimum deux ALU entières. Prenons l'exemple de l'UltraSPARC, qui avait : deux ALU entières, une unité mémoire, une unité de branchement, un additionneur flottant, un multiplieur flottant, un diviseur flottant (aussi utilisé pour le calcul des racines carrées), et 2 unités graphiques pour les calculs sur des pixels. En tout, cela fait 9 unités différentes, pour de la quadruple émission. Dans le même genre, les processeurs MIPS R8000 et R10000 avaient une implémentation plus moderne, dans le sens où toutes les unités étaient dupliquées. Le CPU avait deux ALU, deux FPU et deux unités mémoire. Il pouvait donc émettre quatre instructions à la fois, tant que le paquet collait avec ces six unités. Moitié opérations entières, moitié opérations mémoire, avec de quoi remplacer les opérations entières par des opérations flottantes. Le PA 8000, lui, dupliquait toutes ses unités en deux exemplaires, ce qui n'est pas l'idéal, aucun processeur moderne ne fait cela aujourd'hui. Le tout est résumé dans le tableau ci-dessous, avec : * en vert les CPU utilisant la première stratégie ; * en rouge les CPU utilisant la seconde stratégie ; * en jaune les CPU utilisant la troisième stratégie. {|class="wikitable" |+ Processeurs quadruple émission historiques |- ! Année !! Processeur !! Unités |- ! 1991 | class="f_jaune | Motorola 88110 || class="f_jaune | 2 ALU + MUL + DIV + BIT + MEM + FADD + 2 unités graphiques |- |- ! rowspan="2" | 1994 | class="f_vert | POWER 2 || class="f_vert | 2 ALU/MEM + 2 FPU (+ 2 Branchements via ''branch folding'') |- | class="f_rouge | 21164 || class="f_rouge | 2 ALU + FPU + MEM |- ! rowspan="3" | 1995 | PA 8000 || 2 ALU + 2 ''barrel shifters'' + 2 MEM + 2 FPU (FMA) + 2 FPU (DIV et SQRT) + 2 SIMD |- | class="f_jaune | UltraSPARC 1 || class="f_jaune | 2 ALU + MEM + BRANCH + FADD + FMULFDIV + 2 unités graphiques |- | MIPS R8000 et R10000 || 2 ALU + 2 FPU + 2 MEM |- ! rowspan="2" | 1996 | class="f_rouge | 620 || class="f_rouge | 2 ALU + FPU + MEM |- | class="f_vert | 21264 || class="f_vert | 2 ALU/MEM + 2 FPU (FADD + FMUL) |} Malheureusement, ces CPUs historiques n'avaient ni assez d'unités de calcul entières, ni d'unités mémoire, pour remplir la quadruple émission. Environ la moitié des instructions d'un programme correspond à des instructions entières. Pour en profiter, un processeur à N voies doit disposer d'au minimum N/2 ALU entières, l'idéal étant d'aller au-delà. Les CPU superscalaires historiques avaient 2 ALU pour de la quadruple émission, ce qui était très limite. En comparaison, les CPU superscalaires modernes mettent le paquet niveau ALU entière. La plupart des CPU x86 modernes descendent rarement sous les 2/3, la plupart remplit presque la totalité de la largeur du CPU. Pour donner un exemple, la microarchitecture Golden Cove d'Intel est un processeur à 6 voies, avec 5 ALU entières. Pour être précis, un programme informatique exécute environ 40% de son code dans des instructions arithmétiques, un autre 40% dans les accès mémoire, et un le reste pour... le reste. La conséquence est que les processeurs modernes dupliquent leurs unités mémoire. Par exemple, la microarchitecture ''Golden Cove'' d'Intel dispose de trois unités mémoire : deux spécialisées dans les écritures, trois dans les lectures. La microarchitecture ''Gracemont'' utilise la pentuple émission et peut émettre 4 µops entières et 4 µops mémoire. Notez que le ratio est équilibré entre opérations entières et mémoire, avec soit égalité, soit un avantage pour les ALU entières. Une autre régularité est que la largeur totale du processeur peut être occupée par des opérations entières/mémoire. Pour le dire autrement, sur un processeur quadruple émission, il pourra émettre un mix de 4 instructions entière/mémoire, avec zéro opération flottante. Idem avec des processeurs triples ou pentuple émission, voire plus. Si le processeur émet une opération flottante, elle remplace une opération entière. Prenons l'exemple des processeurs AMD K6, sortis en 1997, juste avant les AMD Athlon. Ils avaient 10 unités de calcul, 6 ports d'émission, 4 ports de décodage. Voici exactement comment étaient répartie les unités de calcul sur les ports d'émission. C'était des processeurs quadruple émission, ce qui fait que la moitié de la largeur du processeur sert pour des opérations entières, et l'autre moitié pour les accès mémoire. {|class="wikitable" |- ! Port d'émission n°1 !! Port d'émission n°2 !! Port d'émission n°3 !! Port d'émission n°4 !! Port d'émission n°5 !! Port d'émission n°6 ! |- | ALU entière || ALU entière || FPU || Unité de branchement || Unité LOAD || Unité STORE |- | || Multiplieur || || || || Unité de calcul d'adresse (instruction LEA) |- | || ''Barrel shifter'' || || || || Unité pour les instructions PUSH |} Les microarchitectures K7, K8 et K10, utilisées sur les anciens CPU AMD Athlon, sont un exemple moins représentatif. Ces microarchitectures utilisent le partage des ports d'émission au maximum. C'était des microarchitectures à triple émission, avec six ports d’émission. Il y avait trois ports d'émission pour les opérations flottantes, et trois ports d'émission pour les micro-opérations entières/mémoire. Il y avait trois ALU entières et trois unités mémoire, chaque ALU partageait un port d'émission avec une unité mémoire. Elle pouvait donc émettre soit trois opérations entières, soit trois µops mémoire, soit un mix des deux. En plus des mix avec les opérations flottantes et branchements. {|class="wikitable" |- ! Port d'émission n°1 !! Port d'émission n°2 !! Port d'émission n°3 !! Port d'émission n°4 !! Port d'émission n°5 !! Port d'émission n°6 ! |- | ALU entière || ALU entière || ALU entière || FADD || FMUL || FPU (autres opérations) |- | Multiplieur || || || || || |- | LOAD/STORE || LOAD/STORE || LOAD/STORE || || || |} Les CPU superscalaires larges dupliquent les multiplieurs et/ou les ''barrel shifters'', et même les unités de branchement. La raison est que quand la largeur du CPU augmente, le processeur traite des paquets d'instructions de plus en plus gros. Par exemple, pour un CPU à 10 voies, cela veut dire émettre 10 instructions en même temps. Il est alors plus fréquent d'avoir deux multiplications, deux décalages, deux accès mémoire, deux branchements. Par exemple, la moyenne est d'une multiplication tous les 5 instructions. Les CPU superscalaires à 10 voies ont donc tendance à avoir deux multiplications par paquet d'instruction, en moyenne. Avoir deux multiplieurs est alors un gain en performance clair. Par exemple, la microarchitecture ''Golden Cove'' a deux ''barrel shifters''. C'est une architecture à 6 voies. {|class="wikitable" |- ! Port d'émission n°1 !! Port d'émission n°2 !! Port d'émission n°3 !! Port d'émission n°4 !! Port d'émission n°5 !! Port d'émission n°6 !! Port d'émission n°7 !! Port d'émission n°8 !! Port d'émission n°9 !! Port d'émission n°10 |- | ALU entière || ALU entière || ALU entière || ALU entière || ALU entière || LOAD || LOAD || LOAD || STORE || STORE |- | ''Barrel shifter'' || Multiplieur || Multiplieur || ''Barrel shifter'' || || || || || || |- | Branchements || Diviseur || || Branchements || || || || || || |} ==Les µops mémoire sur les processeurs superscalaires== Plus haut, nous avons volontairement passé sous silence les accès mémoire. La raison à cela est que leur support sur les CPU superscalaire peut se faire de plusieurs manières différentes, qu'il vaut mieux expliquer dans une section dédiée. Les différentes implémentations sont décrites dans la liste suivante, nous allons les voir une par une. * Avec la première méthode, les accès mémoire sont exécutés par le pipeline pour les opérations entières. * Avec la seconde méthode, les accès mémoire ont un pipeline dédié ; c'est l'''émission parallèle des accès mémoire''. * Avec la troisième méthode, il est possible d'émettre plusieurs accès mémoire simultanément : c'est l'''émission multiple des accès mémoire''. Voyons ces trois cas dans le détail. Nous verrons notamment les différentes formes d'émission multiple des accès mémoire. ===Les accès mémoire traités dans le pipeline entier=== Dans le cas le plus simple, les accès mémoire sont traités comme des opérations entières un peu spéciales. L'unité mémoire est sur le même port d'émission que l'ALU entière, ce qui fait que les accès mémoire remplacent des opérations entières. Faire ainsi permet d'utiliser une ALU entière pour les calculs d'adresse, pas besoin de rajouter une unité de calcul d'adresse séparée. Typiquement, un processeur de ce type place l'unité mémoire après l'unité de calcul. Les opérations entières n'utilisent que l'ALU entière, mais pas l'unité mémoire. Les accès mémoire exécutent un calcul d'adresse dans l'ALU entière, puis l'adresse calculée est envoyée à l'unité mémoire. Le tout est illustré ci-dessous, dans le cas d'un processeur historique à double émission entière-flottante. [[File:Processeur non-superscalaire basique 01.png|centre|vignette|upright=1.5|Processeur non-superscalaire basique]] Un exemple de CPU double émission de ce type est celui du Power PC 440, un CPU destiné à l'embarqué. Vu qu'il était destiné à l'embarqué, il n'avait pas de FPU, il n'avait pas besoin de manipuler de nombres flottants. Il n'a donc que des ALU entières, un multiplieur et une unité mémoire. Il dispose de deux ports d'émission. Le premier est connecté à une ALU entière et un circuit multiplieur, le second est relié à l'unité mémoire et une seconde ALU entière. Une organisation simple, mais très efficace ! [[File:PowerPC 440.png|centre|vignette|upright=2|Microarchitecture du PowerPC 440.]] L'implémentation économise une unité de calcul d'adresse, mais n'a pas de cout en performance significatif. Les accès mémoire étant très lents, émettre une µop entière en parallèle d'un accès mémoire ne sert pas à grand-chose. On peut émettre cette µop entière un cycle plus tard, elle sera terminée avant même que l'accès mémoire soit terminé, même si l'accès mémoire se fait dans le cache L1 ou L2. Pour comprendre pourquoi, faisons quelques calculs. Un accès mémoire prend deux-trois cycles quand il tombe dans le cache L1. Et on doit attendre qu'il soit terminé pour en émettre un nouveau. Combien d'instructions arithmétiques peut-on lancer pendant ce temps, sur un CPU triple émission ? Au premier cycle, on peut en émettre 2 (l'instruction mémoire prend une place, donc 3-1), puis 3 au second cycle. Ce qui fait 5 instructions arithmétiques au total. Or, il se trouve qu'il y a une instruction d'accès mémoire pour 5 instructions arithmétiques, environ. Par contre, ce compromis change quand on passe à la quadruple émission, voire au-delà. Dans ce cas, il devient intéressant de pouvoir émettre une nouvelle instruction mémoire à chaque cycle. Ce qui nous amène à la section suivante... ===L'émission parallèle des accès mémoire=== A partir de la quadruple émission, la tendance a été de séparer l'unité mémoire des ALU entières. Cela permet d'émettre des µops mémoire en même temps que des µops entières et/ou flottantes., ce qui porte le nom d''''émission parallèle des µops mémoire''', ou encore ''émission mémoire parallèle''. Par exemple, un processeur quadruple émission peut émettre en même temps deux opérations entières, une opération flottante et un accès mémoire. L'accès mémoire vient en plus des opérations entières, il n'est pas possible de remplacer l'accès mémoire par une opération entière. La technique ne permet pas de faire les calculs d'adresse dans l'ALU entière. Il faut donc utiliser une unité de calcul d'adresse séparée. Elle est intégrée dans l'unité mémoire, qui fait l'interface entre le cache et le banc de registres. Le cout en circuits est très faible, car les unités de calcul d'adresse n'utilisent guère plus qu'un additionneur et un circuit de décalage très simple. L'émission mémoire parallèle est plus facile à implémenter sur les CPU RISC, qui ont des instructions mémoire séparées du reste. Les CPU CISC ne sont pas dans ce cas, ce qui fait que séparer l'unité mémoire du pipeline entier est compliqué. Les CPU superscalaires historiques étaient pour moité des CPU RISC et pour moitié des CPU CISC, ce qui fait qu'ils n'utilisaient pas ou peu l'émission mémoire parallèle. Les premiers processeurs superscalaires se contenaient de l'émission parallèle mémoire, ils ne pouvaient émettre qu'un seul accès mémoire à la fois, l'unité mémoire n'était pas dupliquée. Et ce n'est pas tant un problème que ça, du moins sur les processeurs à double ou triple émission. En effet, il est rare que l'unité mémoire doive faire deux accès simultanés. Notons que le processeur peut exécuter une instruction mémoire en parallèle d'un calcul ou d'un branchement, seuls les accès mémoire simultanés sont impossibles. ===L'émission multiple des accès mémoire avec une LSQ=== Les processeurs superscalaires larges travaillent sur des paquets de 8 à 10 instructions, il est plus fréquent d'avoir plusieurs accès mémoire par paquet. Dans ces conditions, il est utile d'avoir une '''émission multiple des accès mémoire''', à savoir d'émettre plusieurs micro-opérations mémoire en même temps. Les processeurs superscalaires modernes sont capables d'émettre plusieurs lectures/écritures simultanément. Par exemple, ils peuvent émettre une lecture en même temps qu'une écriture, ou plusieurs lectures, ou plusieurs écritures. Il faut noter que selon le processeur, il peut y avoir des restrictions quant aux accès mémoire émis en même temps. Par exemple, certains processeurs peuvent émettre une lecture avec une écriture en même temps, mais pas deux lectures ni deux écritures. Ou encore, ils peuvent émettre deux lectures, une lecture et une écriture, mais pas deux écritures en même temps. Dans la majorité des cas, les processeurs ne permettent pas d'émettre deux écritures en même temps, alors qu'ils supportent plusieurs lectures. Il faut dire que les lectures sont plus fréquentes que les écritures. Les processeurs qui autorisent toutes les combinaisons de lecture/écriture possibles, sont rares. L'émission multiple des accès mémoire s'implémente dans l'unité mémoire. Pour rappel, l'unité mémoire s'interpose entre le cache L1 et le reste du pipeline. Dans le cas le plus simple, elle incorpore une ''load/store queue'' et des unités de calcul d'adresse. L'émission multiple des accès mémoire demande de rendre la ''Load-store queue'' multi-ports, afin de gérer plusieurs micro-opérations mémoire simultanées. Il faut aussi dupliquer les unités de calcul d'adresse reliées à la LSQ, chaque port d'émission mémoire devant avoir sa propre unité de calcul d'adresse. : Dans ce qui suit, nous parlerons d'AGU (''Adress Generation Unit'') pour désigner les unités de calcul d'adresse, et de LSQ pour designer la ''Load-store queue''. [[File:Processeur non-superscalaire basique 02.png|vignette|upright=1|Processeur non-superscalaire basique]] Dupliquer les ports de la LSQ et les AGU est nécessaire. Mais qu'en est-il du cache ? La réponse varie suivant le processeur. Il y a deux grandes méthodes pour implémenter l'émission multiple des accès mémoire. La première méthode ajoute des ports de lecture/écriture au cache de données, pour faire des lectures/écritures simultanées. Il faut alors dupliquer des unités de calcul, rajouter les ports sur le cache, mais aussi rajouter des ports sur la ''load/store queue'', pour qu'elle échange des données/commandes mémoire avec le cache. Le cout en circuits est alors conséquent La seconde duplique les unités de calcul, mais pas les ports d'accès au cache. Les accès mémoire sont alors accumulés dans la ''load/store queue'', qui sont ensuite traités un par un par le cache. Un exemple est celui des processeurs AMD de micro-architecture K7 K8, à savoir les anciens AMD Athlon et AMD Athlon 64, qui pouvaient calculer trois adresses simultanément, mais ne pouvaient faire qu'un seul accès au cache par cycle. Le cout en circuit est modéré : les unités de calcul coutent très peu, les ports de la ''load/store queue'' coutent déjà un peu plus. Quelques processeurs utilisaient des solutions intermédiaires. Un processeur utilisant cette technique est, par exemple, capable d'émettre quatre µops mémoire en même temps, mais avec seulement deux ports sur le cache. Un exemple classique étant les Pentium 1 et 2 d'Intel, dont nous parlerons dans le chapitre suivant. ===L'émission séparée des LOAD et STORE=== De rares processeurs ont des ports d'émission qui peuvent faire indifféremment lecture comme écritures. Mais les processeurs modernes préfèrent séparer ports d'émission pour les lectures et les écritures. Un exemple est celui des processeurs Intel de micro-architecture Nehalem, qui avaient un port de lecture et un port d'écriture tous deux reliés à la ''Load-store queue'', chacun avec leur propre unité de calcul d'adresse. Comme autre exemple, les processeurs skylake ont une LSQ avec deux ports de lecture et un port d'écriture, ce qui permet d'émettre deux lectures en même temps qu'une écriture. Dans cette section, nous allons étudier le cas avec des ports séparés pour les lectures et écritures. En général, les processeurs ont un peu plus de ports de lectures que d'écritures. Une raison est qu'un programme a, en moyenne, un tout petit peu plus de lectures que d'écritures. Mais la différence n'est pas énorme, c'est un léger déséquilibre. Une seconde raison est que l'exécution dans le désordre marche bien mieux quand les lectures ont lieu le plus tôt possible. Donc mieux vaut privilégier les lectures à l'émission, pour qu'elles se fassent un peu en avance. En pratique, la différence n'est pas très marquée. Il est courant d'avoir un port de lecture et un d'écriture, les CPU superscalaires très larges ont souvent deux ports de lecture et deux d'écriture. A la rigueur, quelques CPU ont trois ports de lecture et deux d'écriture. Le cas le plus simple est celui où on peut émettre une lecture et une écriture en même temps. Il s'implémente facilement si la LSQ est en réalité une simple ''Store queue'', à savoir qu'elle met en avance les écritures uniquement. Pour rappel, les écritures sont mises en attente dans la ''Store queue'', mais les lectures sont exécutées immédiatement. Une fois l'adresse à lire connue, la lecture consulte la ''Store queue'' pour détecter les dépendances de données, puis s'exécute. La donnée à lire est lue soit depuis la ''Store queue'', soit depuis le cache. [[File:Emission LOAD - Store séparée.png|vignette|upright=1|Émission LOAD - Store séparée]] L'implémentation de l'émission multiple est alors très simple. La ''Store queue'' a déjà deux ports, avec un pour les lectures et un autre pour les écritures. Idem pour le cache, qui a naturellement un port de lecture et un d'écriture. Émettre une lecture et une écriture simultanément ne demande donc pas de modification digne de ce nom. La ''Store queue'' doit juste supporter une lecture et une écriture simultanée. Ajouter l'émission d'une seconde lecture n'est pas compliquée : il faut juste rajouter un second port de lecture sur le cache et la ''Store queue''. Pour rappel, l'unité mémoire est précédée par une structure qui met en attente les micro-opérations mémoire avant leur émission finale. La structure en question varie suivant l'implémentation, mais un cas particulier se marie bien avec l'émission multiple des µops mémoire. Il s'agit du cas avec une file de µops séparée pour les lectures et une autre pour les écritures, appelées respectivement la ''file de µops LOAD'' et la ''file de µops STORE''. Il est alors possible d'émettre une lecture et une écriture lors du même cycle d'horloge. Le port de lecture alimente une unité de calcul d'adresse dédiée, directement reliée au cache. Le port d'écriture du cache alimente une unité de calcul, qui est suivie par une file d'écriture, elle-même reliée au cache. Les processeurs AMD de micro-architecture K6 utilisaient cette technique. [[File:Double émission avec le Load Ordering, Store Ordering.jpg|centre|vignette|upright=2.5|Double émission avec le Load Ordering, Store Ordering]] ==Les CPU superscalaire à exécution dans le désordre== La superscalarité modifie aussi les circuits dédiés à l'exécution dans le désordre. Elle modifie les unités de renommage de registre, le ROB et les autres structures liées à l'exécution dans le désordre, au sens large. Aucune structure n'est épargnée. Elles ont tous des prots de lecture/écriture en plus, afin d'encaisser l'émission, le ''commit'' ou le renommage simultané de plusieurs µops. Par exemple, le ROB doit être modifié pour émettre et terminer plusieurs instructions en même temps, en ajoutant des ports de lecture/écriture. Pour un CPU superscalaire à 8 voies, on se dit que le ROB peut retirer 8 µops par cycle maximum. Mais certains processeurs récents ont un ROB qui peut retirer le double ! Un exemple est celui des CPU de micro-architecture Skymont, qui peuvent émettre 8 µops par cycle, mais en retirer 16. Quelques CPU de marque AMD étaient aussi dans ce cas. L'avantage est que cela permet de réduire la taille du ROB, du banc de registre et de quelques autres structures matérielles liées à l'exécution dans le désordre. Le gain sur-compense le cout en transistor nécessaire pour retirer 16 µops par cycle. Dans cette section, nous allons voir comment les étages du pipeline dédiés à l'exécution dans le désordre sont modifiées sur un CPU superscalaire. ===La superscalarité avec une file/fenêtre d'instruction centralisée=== Plus haut, nous avons montré qu'un processeur pouvait avoir plus de ports d'émission que de ports de décodage. On dit aussi que sa ''largeur de décodage'' était supérieure à sa ''largeur d'émission''. La '''largeur de décodage''' est le nombre de µops fournies par l'unité de décodage par cycle d'horloge, la '''largeur d'émission''' est le nombre maximal de µops émises par cycle d'horloge. Et n'allez pas croire qu'un surplus de ports d'émission est du gâchis. Sur les processeurs avec exécution dans le désordre, ce surplus peut être utilisé. Mais avant toute chose, évacuons un détail. Vous avez sans doute pensé que le surplus peut être exploité par les instructions machines décodées en plusieurs µops . Par exemple, si une instruction est décodée en deux µops, on pourrait avoir un port d'émission en plus pour en profiter. Mais les ports de décodage sont *en sortie du décodeur*, et contiennent des µops, pas des instructions. Le surplus de ports d'émission peut occasionnellement utiliser le surplus de ports de décodage, mais cela implique que des µops ont été mises en attente dans l'unité d'émission, pour être émises par la suite. Et cela implique l'usage d'une fenêtre d'instruction ou d'une file de µops. Le principe est que les µops sont accumulées dans la fenêtre d'instruction, en attendant leurs opérandes ou la résolution d'une dépendance. Et si l'occasion se présente, une µops est émise sur chaque port d'émission. Il y a donc un gain en performances, mais il faut faire quelques précisions sur son origine. La largeur d'émission utilisée ne peut pas dépasser, *en moyenne*, la largeur de décodage. Et c'est intuitif : un CPU qui décode 4 µops par cycle ne peut pas émettre 6 par cycle en moyenne, sur le temps long, il n'y a pas de µops en surplus qui sortent du vide. Par exemple, sur un processeur quadruple émission, si on analyse combien d'instructions sont émises par cycle, il peut y avoir des cycles où la largeur d'émission est totalement utilisée, mais ce sera compensé par des périodes où elle est fortement sous-utilisée. Les µops sont accumulées en cas de sous-utilisation, puis consommées en cas d'utilisation complète. Les deux périodes se compensent. Et c'est là que l'on peut comprendre d'où vient le gain en performance. Les cycles où les ports d'émission sont sous-utilisés existent systématiquement, que le processeur utilise l'exécution dans le désordre ou pas. Ils sont liés à la présence de dépendances de données, aux contraintes d’appariement, etc. Elles tendent à réduire la moyenne du nombre de µops émises par cycle. Par contre, ce sont les cycles de rattrapage, où les ports en surplus sont utilisés, qui font remonter la moyenne. Le gain en performance est donc lié à ces cycles de rattrapage, où les µops accumulées sont émises dans les ports en surplus. Et ils n'existent que sur les processeurs avec une file/fenêtre d'instruction. Réduire le nombre de ports d'émission permet de garder des fenêtres d'instruction et un ROB simples, rapides et économes en énergie. Et mine de rien, c'est là un compromis important : à quoi bon augmenter le nombre de ports d'émission si c'est pour que ce soit compensé par une fréquence plus faible ? Par contre, réduire le nombre de ports augmente la ''port contention'', surtout si on a mal réparti les ALU. Il faut donc trouver un compromis entre réduire le nombre de ports d'émission d'un côté, réduire la ''port contention'' de l'autre. La différence entre largeur de décodage et d'émission a un impact assez fort sur le banc de registre, et précisément sur son nombre de ports de lecture. Le banc de registre a besoin de plus de ports de lecture avec une fenêtre d’instruction qu'avec des stations de réservation. Rappelons la terminologie utilisée dans ce cours. Les fenêtres d'instruction se contentent de mémoriser la micro-opération et quelques bits pour détecter la disponibilité des opérandes. Par contre, les stations de réservations mémorisent aussi les opérandes des instructions. Et cette différence a une influence le nombre de ports de lecture du banc de registre. Supposons que toutes les instructions sont dyadiques, à deux opérandes. Comptons le nombre de ports nécessaires pour alimenter les unités de calcul, sans utiliser d'arbitre de banc de registre. Avec une fenêtre d'instruction, il faut le double du nombre de ports d'émission. Mais avec des stations de réservation, il faut le double des ports de décodage. La raison est que les registres sont lus après émission avec une fenêtre d'instruction, avant avec des stations de réservation (sous-entendu, après le décodage). Or, comme dit plus haut, les processeurs superscalaires larges ont souvent plus de ports d'émission que de décodage. ===La superscalarité à fenêtres d'instruction décentralisées=== Les processeurs précédents utilisaient une unité d'émission simple, qui pouvait utiliser l'émission dans l'ordre ou dans le désordre. Elle peut être remplacée par une fenêtre d'instruction centralisée dans que cela quoi que ce soit aux explications précédentes, idem avec une station de réservation centralisée. En soi, l'exécution dans le désordre est donc totalement compatible avec une implémentation simple de la superscalarité. Par contre, des subtilités surviennent avec les fenêtres d'instruction décentralisées. Pour rappel, sur ces processeurs, les µops sont émises en deux phases. Une première phase de ''dispatch'' qui envoie les µops vers la fenêtre d'instruction adéquate, sans détecter les dépendances de données. La seconde phase de ''schedule'' gère les dépendances de données, elle met en attente les µops en attendant que l'unité soit disponible pour les exécuter, en attendant aussi que les opérandes soient disponibles. Et à ce petit jeu, il y a une différence entre le nombre de µops qui sont ''dispatch par cycle'' et celles qui sont ''schedule''. Dit autrement, la largeur d'émission est scindée en une '''largeur de ''dispatch''''' et une '''largeur de ''schedule''. Par exemple, prenons un processeur triple émission avec deux stations de réservation : une pour les µops entières/mémoire, et une autre pour les µops flottantes. L'unité d'émission peut ''dispatch'' maximum trois µops par cycle : soit trois µops entières/mémoire, soit trois flottantes, soit un mix de trois µops différentes. Les stations de réservation, quant à elles, peuvent chacune ''schedule'' 3 µops par cycle, vers trois ALUs entières pour la station de réservation entière/mémoire, vers trois FPS pour la station de réservation flottante. De quoi ''dispatch'' trois µops, mais d'en ''schedule'' six, à chaque cycle. Et cet exemple ne sort pas de nulle part : les CPU x86 d'Intel et AMD sont globalement dans ce cas, même si les chiffres varient d'une microarchitecture à l'autre. L'exemple précédent colle parfaitement à la microarchitecture K7 d'AMD, utilisée sur les processeurs Athlon dans les années 2000. [[File:AMD K7.png|centre|vignette|upright=2.5|AMD K7, microarchitecture.]] Un autre exemple est le MIPS R10000, qui utilisait un mélange de files de µops et de fenêtres d'instructions : une file de µops les accès mémoire, une fenêtre pour les instructions entières et une autre pour les opérations flottantes. Les branchements et NOPs étaient exécutés dans une unité de branchement distincte. Les trois files/fenêtres peuvent mémoriser 16 µops. Le processeur avait deux ALU entières, une AGU de calcul d'adresse, un additionneur flottant et un multiplieur/diviseur flottant. Il y avait un multiplieur entier sur le port d'émission l'ALU 2. Le processeur décodait 4 instructions par cycle et produisait jusqu'à 4µops, qui étaient redistribuées sur les trois fenêtres d'instructions. Chacune pouvait accepter 4 µops par cycle, ce qui fait que processeur pouvait ''dispatch'' 4 µops entières, ou 4 µops flottantes, ou 4 µops mémoire, ou un mélange de deux ou trois types de µops. Pour ce qui est du ''schedule'', la fenêtre d'instruction entière pouvait émettre 2 µops, la fenêtre flottante pouvait émettre une addition en même temps qu'une multiplication/divison flottante et qu'un branchement conditionnel flottant, et la file de µops mémoire ne pouvait émettre qu'une µop mémoire à la fois. En tout, cela fait 6µops ''schedule'' pour 4 µops au ''dispatch''. [[File:R 10000.png|centre|vignette|upright=2.5|MIPS R10000, microarchitecture.]] ===L'unité de renommage superscalaire=== Avec la superscalarité, l'unité de renommage de registre doit gérer le cas où des instructions consécutives ont des dépendances de registre. Par exemple, prenons un processeur à double émission, qui renomme deux instructions consécutives. Si elles ont une dépendance de registre, le renommage de la seconde instruction dépend du renommage de la première. La première doit être renommée et le résultat du renommage est utilisé pour renommer la seconde, ce qui empêche d'utiliser deux unités de renommage séparées. Pour cela, elle renomme les registres sans tenir compte des dépendances, pour ensuite corriger le résultat. [[File:Unité de renommage superscalaire.png|centre|vignette|upright=2|Unité de renommage superscalaire.]] Seules les dépendances lecture-après-écriture doivent être détectées, les autres étant supprimées par le renommage de registres. Repérer ce genre de dépendances se fait assez simplement : il suffit de regarder si un registre de destination d'une instruction est un opérande d'une instruction suivante. [[File:Détection des dépendances sur un processeur superscalaire.png|centre|vignette|upright=2|Détection des dépendances sur un processeur superscalaire.]] Ensuite, il faut corriger le résultat du renommage en fonction des dépendances. Si une instruction n'a pas de dépendance avec une autre, on la laisse telle quelle. Dans le cas contraire, un registre opérande sera identique avec le registre de destination d'une instruction précédente. Dans ce cas, le registre opérande n'est pas le bon après renommage : on doit le remplacer par le registre de destination de l'instruction avec laquelle il y a dépendance. Cela se fait simplement en utilisant un multiplexeur dont les entrées sont reliées à l'unité de détection des dépendances. On doit faire ce replacement pour chaque registre opérande. [[File:Correction des dépendances sur un processeur superscalaire.png|centre|vignette|upright=2|Correction des dépendances sur un processeur superscalaire.]] ==Les optimisations des processeurs superscalaires larges== Les processeurs superscalaires larges ont des optimisations spécifiques, qui n'ont pas de sens sur les processeurs superscalaires étroits. Dans les grandes lignes, elles se classent en deux types. Les premières permettent de charger/décoder plus de 5-10 instructions simultanément. Le problème est alors que les branchements viennent mettre leur grain de sel et qu'il faut en tenir compte. Les secondes corrigent des problèmes qui apparaissent quand un CPU intègre plus de 5 à 10 unités de calcul. ===L'étape de chargement d'un CPU superscalaire large=== Charger plusieurs instructions pose de nombreux problèmes sur les processeurs superscalaires larges, qui émettent de 5 à 10 instructions simultanées. Pour rappel, la stratégie basique pour charger N instructions est de charger un bloc de N instructions consécutives. Les instructions après un branchement ne sont pas décodées ni exécutées, ce qui entraine une petite baisse de performance. Pour un N petit, la perte de performance est mineure. Mais pour un N large, elle devient suffisamment importante pour que la conception du CPU doit alors être repensée de fond en comble. Les analyses statistiques disent qu'il y a environ un branchement tous les 4 à 6 instructions. Un CPU a donc tendance à décoder des paquets de 3 à 5 instructions consécutives, avant qu'un branchement casse la dynamique. Or, cela pose des problèmes pour utiliser plusieurs décodeurs. L'implémentation basique, qui charge N instructions consécutives pour alimenter N décodeurs, fonctionne donc jusqu'à 4/6 décodeurs. Au-delà de 5 instructions, un CPU superscalaire doit être adapté pour gérer les branchements dans les paquets d'instruction qu'il émet. Le nombre de 5 instructions ne sort donc pas de nulle part. La première solution utilise un cache de µops, avec un ''front-end'' capable de charger/décoder 4/5 instructions à la fois, pas plus. L'idée est que la majorité des instructions proviennent du cache de µops (plus de 80%), ce qui fait qu'on peut émettre environ 10 instructions à partir du cache de µops, pas besoin d'avoir beaucoup de décodeurs. Les performances sont cependant dégradées lors d'un défaut de cache de µops. Les instructions doivent alors être chargées depuis le cache d'instruction, par paquets de 4-5, puis sont décodées par 4-5 décodeurs, ce qui ne permet pas d’alimenter les unités de calcul à plein régime. La solution précédente joue sur la distinction entre pots de décodage et d'émission. Elle permet d'émettre un grand nombre de µops sans pour autant charger un grand nombre d'instructions en même temps. En quelque sorte, ce n'est qu'une méthode de contournement, le CPU superscalaire reste de type étroit. Mais il est aussi possible d'utiliser un '''''front-end large''''', capable de charger une bonne dizaine d'instructions en même temps. La seule difficulté est de tenir compte de la présence de branchements. Pour rappel, avec un ''front-end'' étroit, les instructions situées après le premier branchement pris sont annulées, elles ne sont pas décodées ni exécutées. [[File:Fetch sur un processeur superscalaire avec prediction de branchements.png|centre|vignette|upright=2|Fetch sur un processeur superscalaire avec prédiction de branchements.]] Un ''front-end'' large charge les instructions de destination du branchement et les placent à sa suite. Ils chargent deux blocs à la fois et les fusionnent en un seul qui ne contient que les instructions présumées utiles. [[File:Cache d'instructions autoaligné.png|centre|vignette|upright=2|Cache d'instructions auto-aligné.]] Mais cela demande de charger deux blocs de mémoire en une fois, ce qui demande un cache d'instruction multiports. Il faut aussi ajouter un circuit pour assembler plusieurs morceaux de blocs en un seul : le fusionneur (''merger''). En pratique, cette solution demande d'ajouter un second port de lecture au cache, pour un cout en hardware important, avec un impact négatif sur le temps d'accès au cache. Et il faut rajouter le fusionneur, qui prend un étage de pipeline à lui tout seul. Et encore, cette solution ne gère pas le cas où un bloc contient plusieurs branchements ! [[File:Implémentation d'un cache d'instructions autoaligné.png|centre|vignette|upright=2|Implémentation d'un cache d'instructions auto-aligné.]] Les exemples précédents partent du principe qu'il n'y a qu'un seul branchement dans un bloc. Mais quand les blocs deviennent assez longs, il est fréquent d'avoir plusieurs branchements dans un bloc. Et il est même possible d'avoir plusieurs branchements pris. Dans un bloc de 8-10 instructions, ce n'est pas rare, et les CPU superscalaires à 8-10 voies sont la règle dans les CPU des années 2020. Pour gérer ce cas, il faut ajouter des ports supplémentaires au cache d'instruction et adapter le fusionneur, pour qu'il puisse fusionner trois ou quatre blocs. De plus, il faut utiliser une unité de prédiction de branchement capable de ''prédire plusieurs branchements par cycle''. ===Le décodage superscalaire large=== Le décodage des instructions n'est pas censé dépendre de l'implémentation du chargement. En théorie, une fois plusieurs blocs d'instruction fusionnés, on peut décoder les instructions en parallèle. Cependant, certains processeurs effectuent le décodage en tenant compte de la manière dont les branchements découpent les blocs. Les processeurs Atom d'Intel utilisent une solution de ce type, qui couple prédiction de branchement et décodage parallèle. Ils chargent/décodent des paquets de 3 instructions consécutives, chaque paquet étant séparé du précédent par un branchement. Pour cela, en plus d'avoir un fusionneur adapté, ils regroupent les décodeurs en ''clusters'', chacun regroupant 3 décodeurs, et pouvant décoder trois instructions consécutives. Les micro-architectures Tremont et Crestmont avaient deux ''clusters'' (6 décodeurs au total), la micro-architecture Skymont en intègre trois (9 décodeurs au total). Avec deux ''clusters'', le fonctionnement est le suivant. Si le code est composé d'une suite d'instruction sans branchements, seul le premier ''cluster'' est utilisé, limitant le décodage à trois instructions par cycle. Par contre, si l'une des trois instructions décodée est un branchement, alors le second ''cluster'' décode les trois instructions situées après ce branchement, à partir de l'adresse de destination. Avec un troisième ''cluster'', c'est la même chose, sauf qu'il faut qu'il y ait un branchement dans les trois instructions décodées par le second ''cluster''. Mais comment faire pour savoir quels décodeurs activés, quels ''clusters'' sont actifs ? C'est la prédiction de branchement qui commande. Avec deux ''clusters'', elle détermine si un branchement est présent dans les trois instructions à charger, et quelle est son adresse de destination. Sans branchement, elle prévient les décodeurs et les trois instructions chargées sont décodées dans un ''cluster'' libre. Si elle prédit un branchement, elle charge les instructions adéquates, et active les deux ''clusters''. Avec trois branchements, c'est le même mécanisme, sauf que l'unité de prédiction de branchement doit être capable de prédire deux branchements par cycle. Il faut noter que les décodeurs d'un ''cluster'' sont des décodeurs simples, sans microcode. Sur les processeurs Tremont et Crestmont, le microcode était unique et était partagé entre tous les ''clusters''. Un seul ''cluster'' pouvait décoder une instruction complexe avec le microcode, l'autre devait se limiter à des instructions simples uniquement. Depuis Skymont, le microcode est partiellement dupliqué, avec un microcode par ''cluster''. Le cout en hardware peut paraitre prohibitif, pour un gain en performance faible. Sauf que c'est oublier que ce sont des processeurs Atom, sur lesquels beaucoup d'instructions sont microcodées. Les processeurs Atom ont un budget en transistor limité, ce qui fait qu'il est préférable pour eux d'implémenter des instructions en microcode que d'utiliser des unités de calcul dédiées ou des circuits de décodage complexes. Pour limiter la casse, le microcode n'est que partiellement dupliqué. Seul le microcode pour les instructions fréquente est dupliqué. Il y a un microcode secondaire pour certaines instructions, qui est lui unique et partagé par tous les ''clusters''. ===Le contournement sur les processeurs superscalaires larges=== Pour rappel, la technique du contournement (''register bypass'') permet au résultat d'une instruction d'être immédiatement utilisable en sortie de l'ALU, avant même d'être enregistré dans les registres. Implémenter la technique du contournement demande d'utiliser des multiplexeurs pour relier la sortie de l'unité de calcul sur son entrée si besoin. Il faut aussi des comparateurs pour détecter des dépendances de données. [[File:Pipeline Bypass.png|centre|vignette|upright=1|Pipeline Bypass]] La sortie de chaque ALU doit être reliée aux entrées de toutes les autres, avec les comparateurs qui vont avec. Sur les processeurs ayant plusieurs d'unités de calculs, cela demande beaucoup d’interconnexions et de comparateurs. Pour N unités de calcul, cela demande 2 * N² interconnexions, implémentées avec 2N multiplexeurs de N entrées chacun. Si c'est faisable pour 2 ou 3 ALUs, la solution est impraticable au-delà. Non seulement ce serait un enfer à câbler, mais les fils seraient tellement longs que la fréquence du processeur en prendrait un coup. Notez que cela vaut pour les processeurs superscalaires, mais aussi pour tout processeur avec beaucoup d'unités de calcul. En théorie, un CPU à exécution dans le désordre, non-superscalaire, mais avec beaucoup d'unités de calcul, fait face au même problème. En pratique, avoir une dizaine d'ALU implique processeur superscalaire à exécution dans le désordre. D'où le fait qu'on parle du problème maintenant. La seule solution praticable est de ne pas relier toutes les unités de calcul ensemble. À la place, on préfère regrouper les unités de calcul dans différents '''agglomérats''' ('''cluster'''). Le contournement est alors possible entre les unités d'un même agglomérat, mais pas entre agglomérats différents. Évidemment, certaines possibilités de contournement sont perdues, mais c'est compensé par un gain en fréquence et un cout en circuit/fils réduit. C'est un bon compromis, ce qui explique que presque tous les processeurs modernes l'utilisent. Les rares exceptions sont les processeurs POWER 4 et POWER 5, qui ont préféré se passer de contournement pour garder un processeur très simple et une fréquence élevée. ===Le banc de registre des processeurs superscalaires larges=== Pour émettre deux opérations entières simultanément, il faut lire deux fois plus d'opérandes. Et il faut aussi écrire deux résultats dans les registres, en même temps. Pour cela, on double les ports du banc de registre. En général, il faut au maximum 2 ports de lecture et un port d'écriture par unité de calcul. Le problème, c'est que plus un banc de registres a de ports, plus il utilise de circuits, est compliqué à concevoir, consomme de courant et chauffe. Avec plusieurs dizaines d'unités de calcul différentes, ajouter autant de ports impacterait la fréquence du banc de registres et réduirait les performances, voire est totalement impraticable à cause des contraintes thermiques. Les processeurs superscalaires larges doivent donc trouver des solutions. La première solution utilise un banc de registre unique, mais n'utilise pas autant de ports que le pire des cas le demanderait. Le processeur détecte quand il n'y a pas assez de ports pour servir toutes les micro-opérations. Quand ça arrive, l'unité d'émission émet assez de micro-opérations pour exploiter les ports disponibles, mais met en attente les micro-opérations en trop, le temps que les ports se libèrent. Pour cela, le banc de registre est géré par un circuit d'arbitrage spécialisé, intégré à l'unité d'émission, l’'''arbitre du banc de registres''' (''register file arbiter''). Une autre solution est de dupliquer le banc de registres en plusieurs exemplaires qui contiennent exactement les mêmes données, mais avec moins de ports de lecture/écriture. Un exemple est celui des processeurs POWER, Alpha 21264 et Alpha 21464. Sur ces processeurs, le banc de registre est dupliqué en plusieurs exemplaires, qui contiennent exactement les mêmes données. Les écritures dans les registres sont envoyées à tous les bancs de registres, afin de garantir que leur contenu est identique. Chaque exemplaire a exactement deux ports de lecture. La conception du processeur est simplifiée, que ce soit au niveau du câblage, que de la conception des bancs de registres. Une dernière solution découpe le banc de registres en plusieurs bancs de registres plus petits, avec un réseau d'interconnexions pour échanger des données entre bancs de registres. Dans la plupart des cas, cette séparation est invisible du point de vue de l'assembleur et du langage machine. Le processeur se charge de transférer les données entre bancs de registres suivant les besoins. Sur d'autres processeurs, les transferts de données se font via une instruction spéciale, souvent appelée ''COPY''. [[File:Banc de registres distribué.png|centre|vignette|upright=2|Banc de registres distribué.]] Sur de certains processeurs, un branchement est exécuté par une unité de calcul spécialisée. Or les registres à lire pour déterminer l'adresse de destination du branchement ne sont pas forcément dans le même agglomérat que cette unité de calcul. Pour éviter cela, certains processeurs disposent d'une unité de calcul des branchements dans chaque agglomérat. Dans les cas où plusieurs unités veulent modifier le ''program counter'' en même temps, un système de contrôle général décide quelle unité a la priorité sur les autres. Mais d'autres processeurs fonctionnent autrement : seul un agglomérat possède une unité de branchement, qui peut recevoir des résultats de tests de toutes les autres unités de calcul, quel que soit l’agglomérat. ==La macro-fusion : une optimisation du décodage parallèle== Les processeurs superscalaires supportent la technique dite de '''macro-fusion''', qui permet de fusionner deux-trois instructions consécutives en une seule micro-opération. Par exemple, il est possible fusionner une instruction de test et une instruction de saut en une seule micro-opération de branchement. Il s'agit là de l'utilisation la plus importante de la macro-fusion sur les processeurs x86 modernes. La macro-fusion est utilisée pour fusionner un test et un saut en un branchement unique. L'optimisation est présente sur la plupart des CPU superscalaires modernes. Les CPU x86 l'implémentent, mais aussi quelques CPU ARM et les processeurs Apple de série M (M1 à M5, et potentiellement plus quand ils seront sortis). La fusion des instructions se fait lors du décodage des instructions, grâce à la coopération des décodeurs. Elle marche systématiquement si l'instruction de test et le saut sont consécutives, qu'elles forment bien une paire d'instruction. Par contre, insérer une instruction entre les deux empêche la macro-fusion de faire son travail, sauf si l'instruction insérée est un NOP. Les processeurs ARM64 avaient une forme spécifique de macro-fusion, utilisée pour un cas très précis : charger une constante immédiate de 32 ou 64 bits, dans un registre 64 bits. Les CPU ARM 64 bits utilisent des instructions codées sur 32 bits, qui ne peuvent encoder que des constantes immédiates de 16 bits. Pour charger une constante de 32/64 bits, il faut s'y prendre en plusieurs fois. Une première instruction MOVZ charge les 16 premiers bits, puis 2 à 4 instructions MOVK remplissent le reste. MOVZ met à zéro les 48 bits de poids fort, MOVK permet de sélectionner le doublet à modifier dans le registre (on modifie 16 bits, dont on choisit la place, le reste du registre est laissé tel quel). ARM permet de fusionner cette suite d'instructions en une seule µop MOV avec une constante immédiate. On parle alors de '''''MOVZ + MOVK fusion'''''. Une dernière utilisation, [https://www2.eecs.berkeley.edu/Pubs/TechRpts/2016/EECS-2016-130.pdf proposées pour le jeu d'instruction RISC-V], fusionnait un calcul d'adresse et une instruction LOAD/STORE en une seule instruction d'accès mémoire. Elle a été envisagée à une époque où le jeu d'instruction RISC-V ne gérait pas les modes d'adressage base + indice. L'idée était d'avoir une unité mémoire qui gérait le mode d'adressage base + indice et de fusionner des instructions pour l'exploiter. Mais de nos jours, le jeu d'instruction RISC-V intègre le mode d'adressage base + indice, ce qui fait que la macro-fusion n'a aucun intérêt pour ce cas précis. La macro-fusion a de nombreuses limitations en pratique. En théorie, on pourrait l'utiliser au-delà des deux situations mentionnées plus haut. Par exemple, pour fusionner une addition et une multiplication en une seule instruction MAD. Mais l'intérêt est assez faible. Notons que la macro-fusion ne permet que de fusionner des paires d'instructions. Aller au-delà demanderait un cout en circuit trop important. Déjà que tester deux paires utilise pas mal de comparateurs... ==Le ''stack engine'' : une optimisation de la pile d'appel== Les processeurs modernes intègrent une optimisation liée au pointeur de pile. Pour rappel, sur les architectures modernes, le pointeur de pile est un registre utilisé pour gérer la pile d'appel, précisément pour savoir où se trouve le sommet de la pile. Il est manipulé par les instructions CALL, RET, PUSH et POP, mais aussi par les instructions d'addition/soustraction pour gérer des cadres de pile de taille variable. De plus, il peut servir d'opérande pour des instructions LOAD/STORE, afin de lire/écrire des variables locales, les arguments d'une fonction, et autres. C'est donc un registre général adressable, intégré au banc de registre, altéré par l'unité de calcul entière. L'incrémentation/décrémentation du pointeur de pile passe donc par l'unité de calcul, lors des instructions CALL, RET, PUSH et POP. Mais, l'optimisation que nous allons voir permet d'incrémenter/décrémenter le pointeur de pile sans passer par l'ALU, ou presque. L'idée est de s'inspirer des architectures avec une pile d'adresse de retour, qui intègrent le pointeur de pile dans le séquenceur et l'incrémentent avec un incrémenteur dédié. ===Le ''stack engine''=== L'optimisation que nous allons voir utilise un '''''stack engine''''' intégré à l'unité de contrôle, au séquenceur. Le processeur contient toujours un pointeur de pile dans le banc de registre, cela ne change pas. Par contre, il n'est pas incrémenté/décrémenté à chaque instruction CALL, RET, PUSH, POP. Un compteur intégré au séquenceur est incrémenté à la place, nous l’appellerons le '''compteur delta'''. La vraie valeur du pointeur de pile s'obtient en additionnant le compteur delta avec le registre dans le banc de registre. Précisons que si le compteur delta vaut zéro, la vraie valeur est dans le banc de registre et peut s'utiliser telle quelle. Lorsqu'une instruction ADD/SUB/LOAD/STORE utilise le pointeur de pile comme opérande, elle a besoin de la vraie valeur. Si elle n'est pas dans le banc de registre, le séquenceur déclenche l'addition compteur-registre pour calculer la vraie valeur. Finalement, le banc de registre contient alors la bonne valeur et l'instruction peut s'exécuter sans encombre. L'idée est que le pointeur de pile est généralement altéré par une série d'instruction PUSH/POP consécutives, puis des instructions LOAD/STORE/ADD/SUB utilisent le pointeur de pile final comme opérande. En clair, une bonne partie des incrémentations/décrémentation est accumulée dans le compteur delta, puis la vraie valeur est calculée une fois pour toutes et est utilisée comme opérande. On accumule un delta dans le compteur delta, et ce compteur delta est additionné quand nécessaire. Précisons que le compteur delta est placé juste après le décodeur d'instruction, avant même le cache de micro-opération, l'unité de renommage et l'unité d'émission. Ainsi, les incrémentations/décrémentations du pointeur de pile disparaissent dès l'unité de décodage. Elles ne prennent pas de place dans le cache de micro-opération, ni dans la fenêtre d'instruction, ni dans la suite du pipeline. De nombreuses ressources sont économisées dans le ''front-end''. Mais utiliser un ''stack engine'' a aussi de nombreux avantages au niveau du chemin de données/''back-end''. Déjà, sur les processeurs à exécution dans le désordre, cela libère une unité de calcul qui peut être utilisée pour faire d'autres calculs. Ensuite, le compteur delta mémorise un delta assez court, de 8 bits sur le processeur Pentium M, un peu plus pour les suivants. L'incrémentation se fait donc via un incrémenteur 8 bits, pas une grosse ALU 32/64 bits. Il y a un gain en termes de consommation d'énergie, un incrémenteur 8 bits étant moins gourmand qu'une grosse ALU 32/64 bits. ===Les points de synchronisation du delta=== La technique ne fonctionne que si la vraie valeur du pointeur de pile est calculée au bon moment, avant chaque utilisation pertinente. Il y a donc des '''points de synchronisation''' qui forcent le calcul de la vraie valeur. Plus haut, nous avions dit que c'était à chaque fois qu'une instruction adresse le pointeur de pile explicitement, qui l'utilise comme opérande. Les instructions CALL, RET, PUSH et POP ne sont pas concernées par elles utilisent le pointeur de pile de manière implicite et ne font que l'incrémenter/décrémenter. Mais dans les faits, c'est plus compliqué. D'autres situations peuvent forcer une synchronisation, notamment un débordement du compteur delta. Le compteur delta est généralement un compteur de 8 bits, ce qui fait qu'il peut déborder. En cas de débordement du compteur, le séquenceur déclenche le calcul de la vraie valeur, puis réinitialise le compteur delta. La vraie valeur est donc calculée en avance dans ce cas précis. Précisons qu'un compteur delta de 8 bits permet de gérer environ 30 instructions PUSH/POP consécutives, ce qui rend les débordements de compteur delta assez peu fréquent. À noter que si le compteur delta vaut zéro, il n'y a pas besoin de calculer la vraie valeur, le séquenceur prend cette situation en compte. Un autre point de synchronisation est celui des interruptions et exceptions matérielles. Il faut que le compteur delta soit sauvegardé lors d'une interruption et restauré quand elle se termine. Idem lors d'une commutation de contexte, quand on passe d'un programme à un autre. Pour cela, le processeur peut déclencher le calcul de la vraie valeur lors d'une interruption, avant de sauvegarder les registres. Pour cela, le processeur intègre un mécanisme de sauvegarde automatique, qui mémorise la valeur de ce compteur dans le tampon de réordonnancement, pour forcer le calcul de la vraie valeur en cas de problème. La technique du ''stack engine'' est apparue sur les processeurs Pentium M d'Intel et les processeurs K10 d'AMD, et a été conservée sur tous les modèles ultérieurs. L'implémentation est cependant différente selon les processeurs, bien qu'on n'en connaisse pas les détails et que l'on doive se contenter des résultats de micro-benchmarks et des détails fournit par Intel et AMD. Selon certaines sources, dont les manuels d'optimisation d'Agner Fog, les processeurs AMD auraient moins de points de synchronisation que les processeurs Intel. De plus, leur ''stack engine'' serait placé plus loin que prévu dans le pipeline, après la file de micro-opération. ==La micro-fusion et la délamination== La '''micro-fusion''' est une optimisation qui retarde le décodage réel de certaines instructions assez loin dans le pipeline. Elle concerne surtout les instructions ''load-op'', mais pas que. D'autres instructions mémoire peuvent subir cette optimisation. L'essentiel est que, sans micro-fusion, ces instructions sont décodées en plusieurs micro-opérations. La micro-fusion retarde le décodage final de ces instructions, qui se fait assez tard dans le pipeline. Pour comprendre l'intérêt, nous allons devoir faire quelques explications. L'optimisation en question fait le décodage en deux temps. Prenons l'exemple d'une opération ''load-op'', qui est censée être décodée en deux micro-opérations : une lecture, puis une opération. Le décodage produit une '''macro-opération''' (terme d'Intel), qui est propagée dans le pipeline, pour être scindée en deux micro-opérations un peu avant les ALU et l'unité mémoire. La macro-opération est techniquement une micro-opération comme une autre, sauf que ce n'est pas la micro-opération finale. L'unité d'émission découpe cette macro-opération en deux micro-opérations, qui sont émises l'une après l'autre. Pour résumer, les instructions ''load-op'' ne sont complétement découpées en micro-opération lors de l'émission, pas lors du décodage. L'optimisation impacte ce qui se situe entre le décodeur d'instruction et l'unité d'émission : l'unité de renommage, la file de micro-opérations, le ''Loop Stream Detector'' et/ou le cache de micro-opération. L'intérêt de l'optimisation est que l'on fusionne deux micro-opérations en une seule macro-opération. Le cache de micro-opération et la file de micro-opération peuvent mémoriser plus de micro-opérations, vu que certaines sont fusionnées. Quant à l'unité de renommage, elle renomme une seule macro-opération au lieu de renommer deux micro-opérations, ce qui augmente le débit du renommage de registre. Par contre, cela complique un peu le travail de l'unité d'émission, qui doit découper les instructions ''load-op'' en deux et émettre les micro-opérations l'une après l'autre. Il faut noter qu'il n'y a pas de fusion proprement dite. Le décodeur ne décode pas deux micro-opérations séparées, avant de les fusionner. En réalité, il génère directement une macro-opération, qui correspond à deux micro-opérations, que ce soit plus tard dans le pipeline ou sur une autre architecture. D'ailleurs, la micro-fusion a lieu à l'intérieur d'une instruction, qui est censée être traduite en plusieurs micro-opérations. Et encore : pas toutes. La conséquence est que la technique n'a de sens que sur les processeurs CISC, avec des instructions assez complexes pour être décodées en plusieurs micro-opérations. En pratique, seuls les CPU x86 sont concernés. ===Les domaines de fusion=== La micro-fusion n'est pas spécifique aux processeurs superscalaires. En théorie, on peut l'implémenter sur un processeur non-superscalaire à émission dans l'ordre. Cependant, je ne connais aucun processeur à émission dans l'ordre qui implémente la micro-fusion, même si son implémentation est en théorie possible sur de tels processeurs. La micro-fusion est implémentée différemment selon que l'émission se fasse dans l'ordre, dans le désordre, avec une fenêtre d'instruction, avec des stations de réservation, etc. J'ai dit plus haut que les micro-opérations sont générées lors de l'émission, mais le moment exact varie suivant le processeur. Pour simplifier le tout, nous parlerons de '''scission''' quand le processeur traduit une macro-opération en deux/trois micro-opérations. Le moment où a lieu la scission est théoriquement dans l'unité d'émission, mais celle-ci prend plusieurs formes selon que le processeur utilise l'émission dans l'ordre, dans le désordre, avec une fenêtre d'instruction, avec des stations de réservation, etc. Les processeurs à exécution dans l'ordre sont le cas le plus simple. L'unité d'émission est alors en charge du découpage des macro-opérations en micro-opérations. La file de micro-opération ou le cache de micro-opérations mémorisent des macro-opérations. À l'opposé, le tampon de ré-ordonnancement mémorise lui des micro-opérations. Le reste du processeur n'est pas affecté. Sur les processeurs à exécution dans le désordre, les différences sont nombreuses. Le tampon de ré-ordonnancement peut mémoriser soit les micro-opérations finales, soit des macro-opérations. Il en est de même avec la fenêtre d'instruction et/ou les stations de réservation, qu'elles soient centralisées ou décentralisées. Tout dépend de comment se fait l'implémentation. Une implémentation simple effectue la scission des macro-opérations dans l'unité d'émission, comme sur les processeurs à exécution dans l'ordre. Sur de nombreux processeurs, l'unité d'émission et de renommage sont simplement fusionnées, ce qui fait que la scission a lieu juste après le renommage de registres. En conséquence, le tampon de re-ordonnancement et les fenêtres d'instruction ne voient que des micro-opérations. Les macro-opérations disparaissent juste avant. Cette implémentation est préférée avec une fenêtre d'instruction centralisée, car on voit mal comment scinder une macro-opération juste avant l'ALU. Une autre implémentation fait la scission en sortie de la fenêtre d'instruction ou des stations de réservation, qu'elles soient centralisées ou décentralisées. Dans ce cas, le tampon de re-ordonnancement fait de même. L'avantage est que le tampon de ré-ordonnancement stocke des macro-opérations, ce qui augmente sa taille effective. Par exemple, il utilise une seule entrée pour une instruction ''load-op'', au lieu de deux sans micro-fusion de ce type. ===La délamination=== La scission peut parfois avoir lieu plus tôt, dans des circonstances très précises. Concrètement, cela ne concerne que quelques micro-architectures d'Intel, pour des instructions très précises. La raison est les limitations du format des micro-opérations, pas une histoire d'optimisation. Le processeur aimerait pouvoir faire la micro-fusion, mais ne peut pas à cause de ces limitations. Une telle situation cause alors une '''délamination''' : les micro-opérations sont scindées précocement. Par exemple, sur la micro-architecture SandyBridge d'Intel, les opérations ''load-op'' sont fusionnées, sauf si elles utilisent l'adressage indicé. Les micro-opérations de ce processeur ne peuvent avoir que deux opérandes. Trois opérandes est de trop, et les opérations ''load-op'' en adressage indicé dépassent cette limite. Dans ce cas, les micro-opérations sont fusionnées en sortie du décodeur, mais sont scindés avant d’entrer dans la file de micro-opération. L'avantage n'est pas évident, vu que l'unité de renommage suit de près le décodeur d'instruction. Il est que le cache de micro-opérations mémorise des macro-opérations, pas deux micro-opérations. ===La micro-fusion des processeurs Intel et AMD=== La micro-fusion est apparue sur le Pentium M d'Intel, et a été conservée telle quelle sur les processeurs Intel suivants. Les processeurs AMD supportent la micro-fusion depuis au moins la micro-architecture AMD K8. Mieux que ça, la délamination n'était pas utilisée car les micro-opérations acceptaient plus de 2 opérandes. Que ce soit chez AMD ou Intel, elle concerne deux types d'instructions : les écritures mémoire et les instructions ''load-op''. Sur le Pentium M, il y avait quelques limitations, l'optimisation se limitant par exemple aux registres entiers et flottants, mais pas aux registres MMX, mais pas les registres SSE (nous verrons ces registres dans quelques chapitres). Mais les processeurs modernes n'ont plus cette limitation. La micro-fusion des CPU Intel concerne deux types d'instructions : les écritures mémoire et les instructions ''load-op''. Pour les écritures, la situation est plus complexe que pour les instructions ''load-op''. Sur les processeurs Intel précédents, les écritures étaient réalisées dans deux unités séparées : le calcul d'adresse dans une unité de calcul, l'écriture proprement dite dans l'unité mémoire. Elles prenaient donc deux micro-opérations. Le processeur a changé ses unités mémoire, ce qui fait qu'elle pouvait faire les deux en une seule micro-opération. Il s'agissait alors d'une optimisation du chemin de données, plus que de la micro-fusion proprement dite, mais je la mentionne ici malgré tout. La micro-fusion sur ce processeur avait un avantage en plus des avantages usuels : elle utilisait mieux les décodeurs. Les Pentium M avaient trois décodeurs pour décoder plusieurs instructions en même temps, chose qu'on détaillera dans le chapitre sur les processeurs superscalaires. Un seul décodeur pouvait décoder toutes les instructions, les deux autres ne peuvent décoder que les instructions simples, qui se décodent en une seule micro-opération. Avec la micro-fusion, les trois décodeurs peuvent gérer les écritures mémoire et les opérations ''load-op'', vu qu'elles deviennent des instructions simples. Sans cela, elles auraient été limitées au premier décodeur. <noinclude> {{NavChapitre | book=Fonctionnement d'un ordinateur | prev=Le parallélisme mémoire | prevText=Le parallélisme mémoire | next=Exemples de microarchitectures CPU : le cas du x86 | nextText=Exemples de microarchitectures CPU : le cas du x86 }} </noinclude> gnvhwwogbrv0yy194fxa2wylbeamhgc 773253 773252 2026-09-26T20:21:49Z Mewtow 31375 /* La superscalarité à fenêtres d'instruction décentralisées */ 773253 wikitext text/x-wiki Les processeurs vus auparavant ne peuvent émettre au maximum qu'une instruction par cycle d'horloge. Ils peuvent avoir plusieurs instructions qui s'exécutent en même temps, dans des unités de calcul séparées. C'est le cas dès qu'une instruction multi-cycles s'exécute. Par contre, ils ne peuvent démarrer qu'une seule instruction par cycle. Et quand on court après la performance, ce n'est pas assez ! Les concepteurs de processeurs ont inventés des processeurs qui émettent plusieurs instructions par cycle : les '''processeurs à émissions multiples'''. Un processeur à émission multiple charge plusieurs instructions en même temps, les décode en parallèle, puis les émet en même temps sur des unités de calculs séparées. Pour cela, il faut gérer les dépendances entre instructions, répartir les instructions sur différentes unités de calcul, et cela n'est pas une mince affaire. En général, pour un processeur à émission multiple capable d'émettre N instructions en parallèle, on parle de '''processeur à N voies''', ou encore un ''processeur à N pipelines''. [[File:Superscalarpipeline.svg|centre|vignette|upright=1.5|Pipeline RISC classique à cinq étages sur un processeur superscalaire. On voit bien que plusieurs instructions sont chargées en même temps.]] Les processeurs à émission multiple sont de deux types : les processeurs VLIW et les '''processeurs superscalaires'''. Nous mettons les processeurs VLIW de côté en attendant le prochain chapitre. La raison est qu'ils ont un jeu d'instruction spécialisé qui expose le parallélisme d'instruction directement au niveau du jeu d'instructions, là où les processeurs superscalaires restent des processeurs au jeu d'instruction normal. La différence principale entre processeur VLIW et superscalaire est qu'un processeur superscalaire répartit les instructions sur les unités de calcul à l’exécution, là où un processeur VLIW délègue cette tâche au compilateur. ==Le nombre de voies d'un processeur superscalaire== Les processeurs superscalaires les plus simples ne permettent que d'émettre deux micro-opérations à la fois, d'où leur nom de processeur ''dual issue'', ce qui se traduit en '''processeur à double émission'''. Les processeurs triple émission permettent eux d’émettre trois instructions à la fois. A l'opposé, les CPU superscalaires les plus puissants peuvent émettre jusqu'à 8-10 instructions simultanément. En 2026, les CPU superscalaires peuvent monter jusqu’à 10 µops émises en même temps, ce qui est énorme ! Et cela nous amène à faire une distinction entre les processeurs superscalaires dits '''étroits''' et ceux dits '''larges'''. Dans ce wikilivre, on part du principe que les processeurs superscalaires étroits émettent jusqu'à 4-5 µops à la fois, alors que les larges peuvent en émettre plus, l'intervalle entre les deux est une zone grise. Le nombre exact n'est pas fixé dans le marbre, la distinction est volontairement vague. Nous parlerons de la '''largeur d'un CPU superscalaire''' pour parler du nombre de voies, du nombre de µops émises en même temps. Plus un CPU superscalaire est large, plus il est performant ''toute chose égale par ailleurs''. Mais dans les faits, vous pouvez oublier la partie en italique. Élargir un CPU superscalaire demande de rajouter beaucoup de circuits. Et ce cout en circuit aura un impact sur la performance. A budget en transistors limité, il y a un compromis entre élargir un CPU et rajouter du cache ou la taille des fenêtres d'instructions/ROB et autres. ===Les contraintes d’appariement=== Prenons un processeur double émission, capable d'émettre deux instructions simultanément. Émettre deux instructions en même temps n'est pas toujours possible. Par exemple, si deux instructions sont dépendantes, on ne peut pas les émettre en même temps. Mais oublions un instant ces histoires de dépendances de données, et partons du principe que le CPU ait affaire à deux instructions indépendantes, une paire idéale. Un CPU double émission "parfait" pourrait exécuter n'importe quelle paire d'instruction. Par exemple, il pourrait émettre deux multiplications consécutives, deux lectures consécutives, une lecture et une écriture en même temps, etc. En pratique, aucun processeur ne permet cela. Implémenter un CPU superscalaire "parfait" aurait un cout en circuit beaucoup trop conséquent. Par exemple, pour émettre deux multiplications en même temps, il faudrait deux multiplieurs séparés. Idem pour exécuter deux accès mémoire simultané : cela demande d'avoir deux unités mémoire et un cache multiport. Mais en pratique, aucun CPU superscalaire ne fait cela. La totalité des CPU double émission se débrouille avec un seul circuit multiplieur et une seule unité mémoire. Et cela entraine l'apparition de dépendances structurelles, qui font que certaines paires d'instructions sont impossibles. Par exemple, impossible d'émettre deux multiplications en même temps avec un seul circuit multiplieur. Mais émettre une multiplication en parallèle d'une addition est parfaitement possible. Il y a donc des paires d'instructions autorisées et des paires interdites. Et la logique est la même sur les CPU à triple ou quadruple émission, et au-delà. : des triplets ou quadruplet d'instructions sont impossibles à émettre simultanément, alors que d'autres le sont. Pour le dire autrement, il y a des '''contraintes d'appariement''' sur les instructions à émettre en même temps. ===L'impact des contraintes d’appariement sur la performance=== Ces contraintes d’appariement ont un impact sur les performance, mais celui-ci peut être grandement mitigé. Les programmes informatiques utilisent en pratique beaucoup plus d'opérations entières de base que de multiplication, branchements et accès mémoire. Il y a environ 5 opérations entières de base pour une multiplication entière. En pratique, il est rare de devoir exécuter deux multiplications à la fois. Idem pour les décalages et rotations, qui sont assez éloignés les uns des autres. Deux branchements consécutifs sont quant à eux plus fréquents. Deux accès mémoire consécutifs est assez rare, mais ils restent assez proches les uns des autres malgré tout. La conséquence est que certaines paires d'instructions sont privilégiées par rapport au reste. Les paires regroupant deux calculs basiques (additions, soustractions, ...) sont systématiquement supportées, car dupliquer des ALU entières est facile. Les paires regroupant un calcul et une autre instruction sont elles aussi systématiquement supportées. Par contre, les paires avec deux branchements, deux accès mémoire, deux décalages/rotations, ou deux multiplications/divisions sont fréquemment interdites. Seuls les CPU superscalaires larges peuvent les permettre, et encore : sous conditions. Le résultat est que le processeur peut se débrouiller avec une seule unité mémoire et une unité de branchement. ===Terminologie pour la suite du chapitre=== Décrire les contraintes d’appariement demande de dire quelles combinaisons d’instructions sont autorisées ou interdites. Et pour cela, nous allons introduire les abréviations suivantes : * INT est un raccourcis pour parler d'une opération entière simple, qu'une ALU entière peut exécuter. * MUL correspond à une opération entière complexe, typiquement une multiplication, éventuellement à une division. * FLOAT désigne une opération flottante, peut importe laquelle. * MEM désigne un accès mémoire, à savoir une instruction ou µop mémoire. * BRANCH désigne une instruction de branchement. Cette terminologie permettra de définir des paires d'instructions, qu'elles soient possibles ou interdites. Par exemple, l'expression INT + FLOAT désigne une paire d'instruction regroupant une instruction entière et une instruction flottante, peu importe l'ordre des deux instructions. Il en sera de même pour les triplets, quadruplets, ou plus. Par exemple, l'expression INT + INT + FLOAT + MUL désigne un quadruplet d'instructions regroupant : deux instructions entières, une instruction flottante, et une multiplication entière. J'utiliserais aussi des parenthèses pour regrouper certaines combinaisons. Par exemple, l'expression INT + (MUL /FLOAT) veut parler d'une paire d'instruction où la première est une opération entière basique, la seconde est : soit une multiplication, soit une opération flottante. ==Le ''front-end'' des processeurs superscalaires== Un processeur superscalaire doit charger, décoder, et exécuter plusieurs instructions par cycle. Par exemple, un processeur triple émission doit charger 3 instructions lors d'un premier cycle, puis les décoder au cycle suivant, et les exécuter au cycle suivant. Il travaille donc pas paquets de 3 instructions, dans cet exemple. Intuitivement, charger, décoder et exécuter plusieurs instructions à la fois demande de dupliquer des circuits. Par exemple, il faut dupliquer les décodeurs pour décoder plusieurs instructions. Les unités de calcul sont aussi dupliquées, sauf sur certaines processeurs qui arrivent à ruser, mais laissons cela de côté pour plus tard. Mais d'autres circuits sont simplement modifiés, comme l'unité de chargement ou l'unité d'émission. Voyons ce qu'il en est dans le détail. {|class="wikitable" |- ! rowspan="2" | Processeur sans émission multiple | rowspan="2" | Chargement | rowspan="2" | Décodage | rowspan="2" | Renommage | rowspan="2" | Émission | Exécution / ALU | rowspan="2" | ''Commit''/ROB |- | Exécution / ALU |- ! rowspan="4" | Processeur superscalaire | rowspan="4" | Chargement | rowspan="2" | Décodage | rowspan="4" | Renommage | rowspan="4" | Émission | Exécution / ALU | rowspan="4" | ''Commit''/ROB |- | Exécution / ALU |- | rowspan="2" | Décodage | Exécution / ALU |- | Exécution / ALU |} Nous allons d'abord voir ce qu'il en est pour le ''front-end'', à savoir la partie du CPU qui s'occupe de charger et décoder les instructions. Elle regroupe l'unité de chargement, les décodeurs, l'unité de prédiction de branchement, la file de µops, la file d'instruction, et divers caches. En soi, les changements portent surtout sur l'unité de chargement et les décodeurs. ===Le chargement de plusieurs instructions consécutives=== Un processeur superscalaire charge plusieurs instructions en même temps, ''en tenant compte de la présence de branchements''. La partie en italique est importante et elle a une conséquence très forte : la manière de faire est fortement différente entre un CPU superscalaire étroit et large. Un CPU superscalaire étroit fait au plus simple : il charge un bloc de 2, 3, 4 instructions consécutives en mémoire RAM. Il faut alors doubler/tripler/quadrupler la taille du bus connecté au cache d'instruction. Par exemple, pour charger quatre instructions de 32 bits chacune, il suffit de lire un bloc de 128 bits dans le cache d'instruction. Un bloc de 8, 16, 32 octets est donc lu depuis le cache et est ensuite découpé en instructions, envoyées chacun à un décodeur. Charger plusieurs instructions consécutives ne marche bien qu'en absence de branchements. La présence de branchements casse cette dynamique, et il faut gérer leur présence. * Sans prédiction de branchement, toutes les instructions d'un bloc sont décodées et exécutées en même temps, même s'il y a un branchement dans le tas. * Avec prédiction de branchement, les instructions situées après le branchement ne sont pas décodées ni exécutées. L'unité de chargement coupe le bloc chargé au niveau du premier branchement pris, remplit les vides avec des NOP, avant d'envoyer le tout à l'unité de décodage. Les instructions à l'adresse de destination seront chargées au cycle suivant, en chargeant un nouveau bloc. [[File:Fetch sur un processeur superscalaire avec prediction de branchements.png|centre|vignette|upright=2|Fetch sur un processeur superscalaire avec prédiction de branchements.]] Un autre défaut est que sur le CPU charge un bloc de 128 bits à la fois, les instructions doivent être alignées sur 128 bits. Et on peut généraliser à n'importe quelle taille de bloc. Si les instructions ne sont pas alignées, il faut gérer la situation. Il arrive qu'une instruction soit à cheval sur deux blocs, avec un morceau dans le premier bloc, un autre dans le second. Dans ce cas, l'instruction est chargée en deux fois, et des circuits doivent gérer la situation pour recoller les morceaux. Sur les CPU avec des instructions de taille fixe, le problème ne survient que rarement. Mais il est fréquent avec des instructions de taille variable. Un autre défaut est lié aux branchements. Imaginez qu'un branchement s'exécute et branche vers une instruction de destination. Rien n'indique que l'instruction de destination sera au tout début d'un bloc. Si ça se trouve, elle sera en troisième position d'un bloc de 4 instructions. Une solution simple charge le bloc en respectant l'alignement, et les deux premières instructions du bloc ne sont pas à exécuter. Il y a donc une petite perte de performance. Certains CPU, comme l'IBM RS/6000, ont la machinerie charger un bloc de N instructions même s'il n'est pas aligné. La machinerie en question se trouve dans le cache d'instruction et est tout sauf simple à expliquer,ce qui fait que je me permets de la passer sous silence. Un détail, cependant : elle ne permet pas de charger des blocs qui sont à cheval sur deux lignes de cache. ===Le décodage parallèle des instructions=== Pour le décodage des instructions, plusieurs instructions sont décodées en même temps. On parle alors de '''décodage parallèle'''. Pour ce faire, un processeur superscalaire contient plusieurs décodeurs, chacun pouvant décoder une instruction en parallèle des autres. Prenons par exemple un processeur superscalaire à 4 voies : le décodage se fera dans quatre décodeurs séparés, qui fonctionneront en parallèle. Cependant, cette implémentation ne marche bien que pour les processeurs RISC, les processeurs CISC devant faire avec un microcode gourmand en circuits. Les processeurs CISC utilisent des décodeurs hybrides, avec un microcode qui complémente un décodeur câblé. Dupliquer le microcode aurait un cout en transistors trop important, ce qui fait que seuls les décodeurs câblés sont dupliqués. Les CPU CISC superscalaires disposent donc de plusieurs décodeurs simples, capables de décoder les instructions les plus courantes, avec un seul microcode. La conséquence est qu'il n'est pas possible de décoder deux instructions microcodées en même temps. Par contre, il reste possible de décoder plusieurs instructions non-microcodées ou une instruction microcodée couplée à une instruction non-microcodée. Vu qu'il est rare que deux instructions microcodées se suivent dans un programme, le cout en performance est extrêmement mineur. Il faut noter qu'une instruction correspond à une ou plusieurs µops. Il est donc possible de charger 5 instructions et de se retrouver avec 8 µops en sortie des décodeurs. Et c'est la source d'une distinction très importante. Plus haut, j'ai parlé de processeurs superscalaires à N voies. La terminologie peut avoir deux interprétations. La première est que le processeur charge/décode N instructions à la fois. Une seconde est qu'il est capable d'exécuter/émettre N µops en même temps. Les deux ne sont pas équivalentes, vu qu'un processeur superscalaire peut, par exemple, charger/décoder N instructions et émettre/exécuter N+2 µops. La distinction est particulièrement importante sur les processeurs superscalaires larges. Certains d'entre eux sont capables de charger 5 instructions et d'en émettre le double ! Un exemple classique était les processeurs Athlon d'AMD, qui pouvaient décoder deux instructions par cycle, mais émettre 4 µops par cycle. Ce n'était pas un problème, car beaucoup d'instructions, même basiques, étaient décodées en 2 µops. ===L'émission parallèle des instructions=== La superscalarité modifie en profondeur l'unité d'émission, notamment au niveau de son interface. Avant, elle recevait une micro-opération sur son entrée, et fournissait la micro-opération émise sur une sortie. Et cela vaut aussi bien pour une unité d'émission simple, un ''scoreboard'', une fenêtre d'instruction ou des stations de réservation. Mais avec l'émission multiple, les sorties et entrées sont dupliquées. Pour la double émission, il y a deux entrées vu qu'elle doit recevoir deux micro-opération décodées/renommées, et deux sorties pour émettre deux micro-opérations. Dans ce qui suit, nous ne parlerons pas d'entrée et de sortie, mais de '''ports de décodage''' et de '''ports d'émission'''. Les ports de décodage reçoivent des micro-opérations provenant de l'unité de décodage, les ports d'émissions sont pour les micro-opérations émises. Les ports d'émission injectent les micro-opérations émises dans le pipeline, elle sont propagée au banc de registre, aux ALUs, etc. Un processeur superscalaire a forcément plusieurs ports d'émission (et de décodage). Si l'interface de l'unité d'émission change, l'intérieur est tout autant modifié. Sur un CPU non-superscalaire, l'unité d'émission reçoit une µop "candidate à l'émission", et détecte les dépendances entre avec les µops en vol, en cours d'exécution dans le pipeline. Et c'est toujours le cas sur les processeurs superscalaires : l'unité d'émission détecte les dépendances entre les µops candidates et les µops en vol. Mais avec la siperscalarité, elle doit en plus tester les dépendances entre les µops candidates. Si l'unité d'émission est un ''scoreboard'', il doit détecter les dépendances entre instructions émises simultanément et les sérialiser si besoin. Avec une fenêtre d'instruction, il n'y a pour ainsi dire rien à faire, vu que les dépendances sont éliminées par le renommage de registre et que les signaux de réveil gèrent les dépendances RAW. C'est la raison pour laquelle les processeurs superscalaires modernes n'utilisent pas de ''scoreboard''. ==Les processeurs implémentés avec plusieurs pipelines== Nous venons de voir comment la superscalarité impacte le ''front-end'', il est maintenant de voir ce qu'il en est de l'unité d'émission et des ''back-ends''. Un processeur superscalaire peut utiliser l'exécution dans le désordre et le renommage de registres, mais ce n'est pas une obligation. Historiquement, les premiers processeurs superscalaires n'utilisaient pas d'exécution dans le désordre. Le meilleur exemple est celui du Pentium 1 et 2 d'Intel. Ils étaient tous deux des processeurs superscalaires, mais n'avaient pas d'exécution dans le désordre, qui est apparue sur le Pentium 3. les explications sont plus simples si on met de côté l'exécution dans le désordre. Vous vous dites que l'implémentation d'un processeur superscalaire n'est pas la même suivant que le CPU utilise l'exécution dans le désordre, ou non. Mais dans les faits, la distinction est plus subtile que ca. Pour l'expliquer, je vais utiliser une classification originale, qui sépare les processeurs superscalaires en trois sous-types. La distinction se fait sur la manière dont la superscalarité est implémentée en matériel.La classification que j'utilise distingue : * Les processeurs avec plusieurs pipelines, sans exécution dans le désordre. * Les processeurs superscalaires avec un pipeline dynamique. * Les processeurs avec des fenêtres d'instruction décentralisées. La distinction n'est pas qu'une question d'implémentation matérielle. Elle impacte aussi le ratio entre instructions chargées/décodées et instructions émises. Vous vous dites que les deux sont égales, mais ce n'est le cas que sur les processeurs avec N pipelines ! Sur les autres processeurs, il est possible de décoder 4 instructions, mais d'en émettre maximum 6 par cycle ! Et sur les CPU avec des fenêtres d'instruction décentralisées, il y a aussi une différence entre le nombre d'instructions qui sont ''dispatch'' et ''schedule''. La catégorie la plus simple est celle des '''processeurs avec plusieurs pipelines'''. Ce sont des processeurs superscalaires sans exécution dans le désordre. Ils partent d'un processeur non-superscalaire, et ajoutent un second pipeline, en sortie de l'unité d'émission. Le second pipeline permet l'émission parallèle d'une seconde instruction. Le second pipeline n'est pas identique au pipeline originel, sur plusieurs points. Premièrement, il n'a pas exactement les mêmes circuits. Le second pipeline est généralement composé d'une unité de calcul, d'une FPU, éventuellement d'un second banc de registres, guère plus. Il permet d'exécuter une seconde opération de calcul, pas plus, ce qui est à l'origine de contraintes d'appariemment assez fortes. De plus, il n'a pas forcément la même longueur, il peut avoir des étages en moins ou en plus. Il s'agit de la forme la plus simple de processeur superscalaire et nous allons leur consacrer cette section. ===La double émission entière-flottante=== L'exemple le plus simple utilise un second pipeline pour les opérations flottantes, séparé du pipeline originel. L'ajout d'un pipeline flottant permet d'émettre une micro-opération entière en même temps qu'une micro-opération flottante, ce qui s'appelle la '''double émission entière-flottante'''. La micro-opération entière s'exécute dans l'ALU entière, la micro-opération flottante dans la FPU. Les exemples les plus notables sont le PA-RISC 7100, le POWER 1 (RIOS .& et .9) et le PowerPC 601. L'Apollo DN10000 et l'Intel i860 utilisaient aussi cette forme de double émission, mais étaient des prototypes de CPU VLIW. [[File:Double émission entière-flottante.png|centre|vignette|upright=2|Double émission entière-flottante]] Pour bien expliquer les choses, prenons le processeur illustré ci-dessous, avec deux ''back-end'' séparés : un pour la FPU, et un autre pour le reste. Les instructions entières et mémoire passent toutes par le ''back-end'' généraliste. [[File:CPU non-superscalaire basique, avec une FPU.png|centre|vignette|upright=1.5|CPU non-superscalaire basique, avec une FPU]] Les deux ''back-end'' sont des pipelines séparés, mais il faut que l'unité d'émission soit spécialement conçue pour en profiter. Un processeur simple émission peut démarrer une seule µop par cycle : soit la µop est envoyée au ''back-end'' flottant, soit elle est envoyée au ''back-end'' général. Avec la double émission entière-flottante, le processeur peut envoyer une µop dans chaque pipeline, à chaque cycle. Vu que la FPU et son banc de registre forment déjà un pipeline séparé, les seules modifications se font au niveau du ''front-end'', unité d'émission inclue. {| |- |[[File:CPU simple émission avec une FPU.png|centre|vignette|upright=1.5|CPU simple émission avec une FPU.]] |[[File:Double émission entière flottante avec un second pipeline.png|centre|vignette|upright=1.5|CPU avec un second pipeline flottant.]] |} Notez que le terme double émission ''entière''-flottante est un peu trompeur. En réalité, la technique permet d'émettre une instruction flottante avec n'importe quelle autre instruction non-flottante, avec des restrictions pour les branchements. Il est par exemple possible d'exécuter un calcul flottant en parallèle d'un accès mémoire. Sur les CPU 32 bits, il est possible de faire les multiplications dans la FPU. Pour rappel, la FPU contient un multiplieur entier 53-54 bits, pour multiplier les mantisses de deux flottants 64 bits, qu'on peut utiliser pour faire les multiplications entières 32 bits. Cela permet d'émettre une multiplication en même temps qu'une opération entière dans l'ALU. Ainsi, le CPU pouvait émettre deux instructions : une opération entière de base, avec soit une multiplication, soit une opération flottante. En clair, une émission de type INT + (MUL /FLOAT). Par contre, on perd la possibilité d'émettre une multiplication entière avec une opération flottante, mais c'était un compromis qui en valait la peine. Le CPU PA-RISC 7100 implémentait cette optimisation. Il s'agissait d'un CPU double émission entière-flottante, qui utilisait donc un partitionnement assez simple. Le combo partitionnement + reconditionnement permettait ici de gagner en performance, pour un cout en matériel très limité. D’autant plus limité que le jeu d'instruction prévoyait le reconditionnement de la FPU. Les multiplications ne lisaient pas leurs opérandes dans les registres entiers, mais dans les registres flottants. ===La double émission entière=== Une autre possibilité ajoute un second pipeline, capable d'exécuter des opérations entières simples, appelé le pipeline entier. Le processeur supporte alors la '''double émission entière''', à savoir qu'ils peuvent émettre une opération entière en plus d'une autre instruction. L'opération entière en question est une opération simple, comme une addition, une soustraction, une opération bit à bit, une comparaison, etc. Les exemples classiques sont les processeurs Intel i960 et Pentium. L'implémentation était différente entre l'i960 et le Pentium : Le Pentium avait une seconde ALU entière, alors que le i960 rusait. Le Pentium dupliquait l'ALU entière, pour créer un second pipeline entier. Sur le Pentium originel, les contraintes d'appariement étaient assez strictes. Le Pentium avait une FPU, mais ne pouvait pas l'utiliser en double émission. Il lui était impossible d'exécuter une opération flottante en même temps qu'une opération entière (branchements inclus). Le Cyrix 6x86 était une sorte de Pentium produit par l'entreprise Cyrix, avec une microarchitecture différente. Il avait des contraintes d’appariement réduites. Par exemple, il pouvait émettre une opération flottante en parallèle d'une opération entière. Nous détaillerons ces processeurs dans le prochain chapitre, qui porte justement sur les CPU superscalaires x86. [[File:CPU simple et double émission avec un second pipeline entier.png|centre|vignette|upright=2.5|CPU simple et double émission avec un second pipeline entier.]] L'Intel i960 n'avait pas de FPU, juste une ALU entière et une unité mémoire. Il utilisait l'unité mémoire comme une seconde ALU entière limitée. Pour rappel, une unité mémoire contient une AGU, à savoir une ALU spécialisée dans les calculs d'adresse. Elle est capable de faire des additions, soustractions, et quelques décalages limités (des décalages par 2, 4, 8, pas plus). L'Intel i960 ajoutait des connexions entre AGU et banc de registre, afin que celle-ci serve de seconde unité de calcul. L'AGU est déjà reliée aux deux ports de lecture, pour lire les opérandes du calcul d'adresse, il suffit de la connecter sur le port d'écriture. Le processeur peut alors émettre deux instructions/µops entières, sous certaines conditions. [[File:Emission multiple des opérations entières, double émission.png|centre|vignette|upright=2|Émission multiple des opérations entières, double émission.]] ==Les processeurs superscalaires dynamiques== Les processeurs précédents, avec 2 pipelines, avaient une unité d'émission assez simple. Elle avait deux ports de décodage, et deux ports d'émission. Le nombre de ports de décodage et d'émission étaient identiques. Vous vous dites que c'est naturel, et intuitif, que c'est normal pour un processeur superscalaire. Mais en réalité, la majorité des processeurs superscalaires a plus de ports d'émission que de décodage. Expliquer pourquoi va cependant nous demander de parler de comment la superscalarité exploite les unités de calcul. Posons-nous cette question : de combien d'unités un processeur superscalaire a besoin ? Les unités en question ne sont nécessairement des unité de calcul, l'unité mémoire et l'unité de branchement comptent aussi. La réponse dépend du nombre de voies. Si le ''front-end'' charge et décode un paquet de N instructions, exécuter ces N instructions simultanément demandera là aussi au moins N unités de calcul. Notez le terme : ''au moins'', car c'est là tout l'enjeu de cette section. La réponse dépend aussi des contraintes d’appariement. Pour un CPU superscalaire "parfait", sans contraintes d’appariement, toutes les unités doivent être dupliquées, y compris l'unité mémoire. Un processeur à double émission parfaite devrait dupliquer l'ALU entière, le ''barrel shifter'', le circuit multiplieur, la FPU, l'unité mémoire, et l'unité de branchement, ce qui ferait passer de 6 unités à 12. Le cout en circuit est énorme, même pour de la double émission ! Et pour un CPU à N pipelines, le cout en circuit encore plus prohibitif ! En pratique, les processeurs superscalaires qui font ça sont très rares. La majeure partie des processeurs superscalaires utilisent d'autres techniques : le partitionnement, la duplication partielle des ALUs, la duplication des autres unités. ===Le partitionnement des unités fonctionnelles=== Les CPU superscalaires les plus simples ne dupliquent pas leurs unités de calcul, ils se contentent d'exploiter les unités existantes. Et pour expliquer comment, nous allons partir de l'exemple d'un processeur non-superscalaire contient une ALU entière, une FPU, une unité mémoire et une unité de branchement. Les quatre unités peuvent fonctionner en parallèle, à savoir qu'une unité peut exécuter une instruction à elle toute seule, sans interagir avec les autres. Cela suffit pour créer un processeur quadruple émission, qui peut émettre en même temps : une opération entière, une opération flottante, un branchement, un accès mémoire. Le principe marche aussi sur un processeur avec moins de 4 unités, par exemple sans FPU et/ou sans unité de branchement, ou avec au contraire des unités en plus. L'idée générale est d’émettre une µops séparée dans chaque unité fonctionnelle. Utiliser ainsi les unités existantes, pour rendre un CPU superscalaire, s'appelle le '''partitionnement superscalaire'''. Le terme n'est pas beaucoup utilisé en-dehors de quelques vieux articles académiques. Le partitionnement est fortement dépendant du jeu d'instruction, car il demande de bien séparer instructions entières, flottantes, branchements et instructions mémoire. Et autant c'est bien le cas sur les CPU RISC, autant les CPU CISC ne sont pas dans ce cas. Les CPU CISC ont des instructions ''load-op'', qui effectuent une instruction arithmétique et une µop mémoire. Et ces instructions sont très compliquées à implémenter avec le partitionnement. Et au-delà de ça, les CPU CISC ont beaucoup d'instructions qui sont décodées en plusieurs µops, à exécuter dans un ordre bien précis. On peut pas émettre les µops en question simultanément. Le partitionnement a un cout en circuit très limité, pour un gain en performance lui aussi limité. Pour reprendre le CPU de l'exemple précédent, il est rare que l'on ait à exécuter ces quatre instructions en même temps. En pratique, les combinaisons fréquentes sont les combinaisons calcul + accès mémoire, calcul + branchement, calcul entier + calcul flottant. Utiliser une quadruple émission de ce type n'est donc pas une bonne idée, alors que la double émission parait plus adaptée. Et c'est là que la différence entre ports d'émission et de décodage devient intéressante. L'idée est d'avoir une unité d'émission avec deux ports de décodage, et 4 port d'émission (un par unité). Les contraintes d’appariement varient alors suivant le processeur, mais la règle générale est que si deux micro-opérations tombent dans deux unités différentes, on peut les émettre en même temps. En clair, on peut émettre une opération entière avec une opération flottante, une opération entière avec un branchement, un accès mémoire avec un branchement, etc. Et cela rend le partitionnement bien plus intéressant que prévu. Les tout premiers CPU superscalaires étaient des CPU double émission, qui utilisaient le partitionnement avec : une ALU entière, une unité mémoire et une FPU. Nous parlerons de '''partitionnement INT-FLOAT-MEM'''. L'avantage sur la double émission flottante est que les combinaisons INT + MEM et FLOAT + MEM étaient autorisées. L'intérêt était d'émettre une opération en parallèle d'un accès mémoire, ce qui est un doublet d'instruction assez fréquent. Les exemples classiques sont l'HyperSPARC, de l'alpha 21064 et du PowerPC 603. Les contraintes d’appariement variaient grandement suivant le processeur. Le PowerPC 603 implémentait une double émission "parfaite". C'est à dire que toutes les combinaisons possibles à deux instructions étaient possibles, que ce soit INT + FLOAT, INT + MEM, FLOAT + MEM. En plus de cette double émission "parfaite", le CPU utilisait la technique dite de ''branch folding'', qui permettait d'exécuter un branchement sans utiliser d'unité de calcul, ce qui fait qu'il était qualifié de ''CPU 2-way + Branch''. A l'opposé, le processeur alpha 21064 avait pas mal de contraintes d’appariement, à savoir que certaines combinaisons de deux instructions étaient interdites, bien que compatibles avec les unités disponibles. Une des raisons est que le banc de registre n'avait pas assez de ports de lectures, ce qui fait que certaines combinaisons avec une écriture mémoire n'étaient pas permises. Les concepteurs avaient volontairement inséré ces contraintes afin de simplifier le design du processeur. Avant de poursuivre, précisons que la FPU regroupe un additionneur flottant et un multiplieur flottant. Et ce fait peut être exploité pour approfondir le partitionnement. La '''double émission FADD/FMUL''' permet d'émettre une addition flottante en même temps qu'une multiplication flottante. Elle a été utilisée sur les processeurs x86, Alpha et SPARC. Les autres circuits de calcul flottant sont souvent regroupés avec l'additionneur. La division est parfois e même port que le multiplieur flottant, parfois sur celui de l'additionneur (quand elle est microcodée et exécutée en enchainant les soustractions). ===La duplication des ALU entières=== Le partitionnement est une technique élégante, simple, facile à implémenter et au cout en circuits modeste. Mais il faut avouer que cela ne donne pas des CPU superscalaires très efficaces. La raison est que le partitionnement permet d'émettre des paires, triplets, quadruplets de micro-opérations qui sont rares en pratique. Les combinaisons les plus fréquentes en pratique sont des paires/triplets d'opérations entières, souvent des paires/triplets d'additions ou d'opérations entières simples. Il est donc primordial d'émettre plusieurs opérations entières simultanément. Le fait d'émettre et d'exécuter plusieurs opérations entières en même temps s'appelle l''''émission entière multiple'''. Pour exécuter la seconde/troisième opération entière, il faut dupliquer les ALU entières (ou réutiliser l'AGU comme ALU entière comme le fait l'Intel i960). Le processeur doit avoir deux ALU entières pour de la double émission entière, trois pour de triple émission entière, etc. De plus, il faut ajouter des ports au banc de registre, pour lire deux fois plus d'opérandes et décrire deux résultats. Et enfin, il faut rajouter un port d'émission pour pouvoir émettre une micro-opération entière en plus. [[File:Processeur non-superscalaire basique 03.png|centre|vignette|upright=2|Processeur non-superscalaire basique]] La tendance est de multiplier les ALU entières, mais pas le circuit multiplieur. Le cout en transistors est alors grandement réduit, au prix de l'apparition de dépendances structurelles. Le CPU peut émettre deux additions en même temps, ou une addition et une multiplication, mais pas deux multiplications. Il en est de même avec le ''barrel shifter'', qui n'est pas dupliqué. Il est donc possible d'émettre un décalage avec une opération de base, mais pas deux décalages en parallèle. Mais ces contraintes d’appariement ne sont pas un problème, car il est rare de devoir émettre deux multiplications/décalages en même temps. La plupart des programmes sont majoritairement remplis d'additions, avec des multiplications assez rares et des décalages qui le sont encore plus. La moyenne est proche d'une multiplication pour 4/5 additions. Vu que des multiplications consécutives sont très rares, dupliquer le multiplieur est presque inutile. Disposer de plusieurs circuits multiplieurs ou de plusieurs ''barrel shifters'' serait donc un cout en circuits qui ne servirait que rarement et n'en vaut pas la chandelle. Il faut noter que sur les CPU 32 bits, les multiplications peuvent être exécutées dans la FPU ou dans un multiplieur séparé, tout dépend de si le CPU exécute les multiplications dans la FPU ou pas. {|class="wikitable" |- ! Voie d'émission n°1 !! Voie d'émission n°2 !! Voie d'émission n°3 |- | ALU entière || ALU entière || FPU |} Reste à connecter les unités de calcul à l'unité d'émission (indirectement, car il y a des étages de pipeline entre les deux). Une implémentation basique utilisee un port d'émission par unité. Prenons l'exemple d'un CPU superscalaire avec 4 ALU entières, une FPU, et une unité mémoire. Vu qu'il y a 6 unités, cela fait donc 6 ports d'émission. L'exemple historique le plus impressionnant est celui du Motorola 88110, un CPU double émission qui possède 10 unités. Il contient deux ALU entières, un circuit multiplieur, un circuit diviseur, une unité de manipulation de bits, une unité mémoire, un additionneur flottant, et deux unités graphiques pour des calculs impliquant des pixels. Le multiplieur est utilisé à la fois pour les multiplications entières, flottantes et le produit de deux pixels. Idem pour le diviseur, qui sert pour les divisions entières et flottantes. Et chaque unité a son propre port d'émission. La conséquence est que les contraintes d'appariement du Motorola 88110 sont simples : tant que deux µops atterrissent dans des unités séparées, la paire est permise. Il est par exemple possible d'émettre simultanément deux opérations entières simples, vu qu'il y a deux ALU. Par contre, impossible d'émettre simultanément deux multiplications, deux divisions, deux opérations flottantes, deux manipulations de bit, deux accès mémoire, etc. De plus, le partage du multiplieur fait qu'on ne peut pas émettre une multiplication entière avec une multiplication flottante. Idem avec les divisions, vu que le diviseur est aussi partagé. Ce sont les seules contraintes. ===Le partage des ports d'émission=== La présence d'un circuit multiplieur et d'un ''barrel shifter'' pose des problèmes pour le partitionnement. Il est possible d'avoir un port d'émission dédié au multiplieur, et un autre au ''barrel shifter'', qui sont séparés de ceux pour l'ALU entière. Il est alors possible d'émettre une opération entière, une multiplication et un décalage, simultanément. Nous parlerons de '''partitionnement entier''' pour désigner cette possibilité. [[File:Partitionnement superscalaire entier.png|centre|vignette|upright=2|Partitionnement superscalaire entier.]] Le partitionnement entier est cependant très rare, car le gain en performance serait trop faible au regard des couts en circuit. Quelques processeurs historiques l'utilisaient, mais ce n'est plus le cas de nos jours. La raison est que les multiplications et les décalages sont assez rares dans le code, ce qui fait que le port d'émission associés sont sous-utilisés. Et un port d'émission a un cout en circuit assez important. Et c'est sans compter qu'ajouter des ports sur le banc de registres juste pour un multiplier sous-utilisé n'en vaut pas la peine. L'idéal serait de trouver un moyen de réduire le nombre de ports d'émissions, sans réduire le nombre d'unités de calcul. [[File:Emission multiple des opérations entières, implémentation naive.png|vignette|Émission multiple des opérations entières, implémentation naive]] La solution consiste à '''partager les ports d'émission''', à savoir connecter un port d'émission à plusieurs unités de calcul. L'exemple classique est justement lié aux circuits multiplieur et ''barrel shifters''. L'idée est que le multiplieur et le ''barrel shifter'' sont mis sur le même port d'émission qu'une ALU entière. Prenons par exemple le processeur à double émission entière illustré ci-contre. Un port d'émission est relié à une ALU entière seule, et l'autre port d'émission alimente la seconde ALU entière et le multiplieur. La conséquence est que le processeur peut émettre deux opérations entières, dont une multiplication. Si on avait utilisé un port d'émission dédié au multiplieur, il aurait été possible d'émettre deux opérations entières et une multiplication. On perd donc une voie, mais on gagne en flexibilité et on économise des circuits. Notons que ce partage signifie aussi que le multiplieur partage les ports sur le banc de registre, ce qui fait une double économie. [[File:Emission multiple des opérations entières, implémentation courante.png|centre|vignette|upright=1.5|Émission multiple des opérations entières, implémentation courante]] En général, le ''barrel shifter'' et le multiplieur sont placés sur deux voies d'émission séparés, ce qui permet d'émettre un décalage et une multiplication en même temps. Les mettre sur le même port ne le permettrait pas. {|class="wikitable" |- ! Voie d'émission n°1 !! Voie d'émission n°2 !! Voie d'émission n°3 |- | * ALU entière * Multiplieur | * ALU entière * Barrel shifter | FPU |- | * Additions, soustractions, opérations bit à bit, etc. * Multiplication | * Additions, soustractions, opérations bit à bit, etc. * Décalages et rotations | Opération flottante |} Mais le partage des ports d'émission peut appliquer à n'importe quelle unité. Prenons l'exemple d'un CPU superscalaire avec 4 ALU entières, une FPU, et une unité mémoire. Vu qu'il y a 6 unités, cela fait donc 6 ports d'émission. Cependant, il est rare qu'un bloc de 6 instructions contienne exactement 4 opérations entières, une opération flottante, et un accès mémoire. Beaucoup de ports d'émission seraient sous-utilisés. La solution est de partager des ports d'émission, pour passer à la quadruple émission. Le CPU peut alors répartir 4 instructions sur les ports d'émission adéquats, à chaque cycle. Le CPU a donc 4 ports de décodage et 6 ports d'émission. Une implémentation classique permet n'importe quelle combinaison de 4 instructions compatible avec les 6 unités. Par exemple, il devient possible d'émettre simultanément 4 additions, la combinaison 2 * INT + FLOAT + MEM, 3 * INT + MEM, et bien d'autres. L'exemple relie une ALU entière, la FPU et l'unité mémoire sur le même port. Le CPU a alors quatre ports d'émission, qui ne sont pas "identiques". Trois d'entre eux sont reliés à une ALU entière chacun, rien d'autre. Le quatrième est lui relié à une ALU entière, la FPU et l'unité mémoire. On économise alors deux ports d'émission, mais cela entraine l'apparition de contraintes d’appariement. Le CPU peut émettre 3 opérations entières + n'importe quelle autre opération. Mais les autres combinaisons sont interdites. Le CPU peut émettre 6 opérations entières simultanément, grâce à 6 ALU entières. Les accès mémoire ou opérations flottantes ne viennent pas en plus, mais en remplacement des opérations entières. Regrouper plusieurs ALU sur un même port d'émission est donc à l'origine de dépendances structurelles, de nouvelles contraintes d’appariement. Dans certaines situations de '''''port contention''''', on a assez d'ALU pour exécuter N micro-opérations, mais la répartition des ALUs sur les ports l'empêche. Impossible d'émettre deux micro-opérations sur deux ALU si elles sont sur le même port d'émission. On peut réduire les cas de ''port contention'' en répartissant correctement les ALU sur les ports d'émission, mais sans jamais les supprimer complétement. Un exemple est celui du PowerPC 604. Il implémentait la quadruple émission, alors qu'il avait 5 unités de calcul. Mais deux unités partageaient un même port d'émission. Dans le détail, il avait deux ALU entières, un circuit multiplieur/diviseur, une FPU, une unité mémoire. La FPU et le multiplieur étaient sur le même port d'émission. En conséquence, le CPU pouvait émettre seulement les combinaisons INT + INT + MEM + (MUL/FLOAT). : Il est intéressant de voir l'exemple d'un processeur superscalaire parfait, sans contraintes d'appariement, à N voies. Pour cela, on part d'un processeur non-superscalaire, avec M unités de calcul. Un tel processeur peut s'implémenter avec N ports d'émission, chacun relié à M unités de calcul possibles, ce qui fait que les M unités de calcul sont dupliquées en N exemplaires. Si on prend par exemple un CPU non-superscalaire avec une ALU, une FPU, une unité mémoire et une unité de branchement, un port d'émission sera relié à une ALU, une FPU, une unité mémoire et une unité de branchement. ===La duplication des autres unités fonctionnelles=== Dupliquer les ALU entière est une première étape, mais il est aussi possible de dupliquer les autres unités fonctionnelles. Par exemple, il est possible de dupliquer l'unité mémoire ou l'unité de branchement. Reste à voir quelles unités dupliquer, et pourquoi. Précisons que dans ce qui va suivre, nous allons parler de la duplication des unités en général, pas seulement des unités mémoire/branchement. Nous allons continuer de parler de la duplication des ALU entières dans ce qui suit, mais de manière plus générale. Pour un processeur à N voies, il est théoriquement possible de dupliquer toutes les unités en N exemplaires. Mais ce n'est jamais fait en pratique, car le cout en circuits serait trop important et le gain en performance trop faible. Mieux vaut dupliquer certaines unités bien précises, quitte à avoir des contraintes d'appariement. Et à ce petit jeu, toutes les duplications ne se valent pas, certaines sont plus rentables en matière de performance, le cout en circuit n'est pas le même selon l'unité, etc. La rentabilité en performance dépend surtout des contraintes d'appariement. Les contraintes d'appariement entrainent une baisse de performance significatives si elles interdisent des paires d'instructions fréquentes. Aussi, les opérations très fréquentes, souvent appairées, gagnent à dupliquer l'unité associée. Et au contraire, les unités rarement utilisées ne doivent pas être dupliquées. Il se trouve que les unités les plus utilisées sont les ALU entières, suivies par les unités mémoire et l'unité de branchement. Le cout en circuit n'est aussi pas le même. Dupliquer les ALU entière est trivial, car elles ont un budget en transistors assez faible. Les circuits multiplieurs, diviseurs, et les ''barrel shifter'' ont eux un cout en transistor plus élevés. Si on devait faire un classement, on aurait : ALU entière < ''barrel shifter'' < multiplieur. Pour les unités mémoire, le cout en transistor est là aussi conséquent. Le résultat final est que dupliquer les ALU entières est privilégié sur le reste, comme l'a vu dans la section précédente. Les unités mémoire arrivent en seconde place, suivie par les multiplieurs/''barrel shifter'' à la troisième place, et les unités de branchement à la quatrième place. Les CPU superscalaires larges ont donc tendance à multiplier les unités mémoire en second. Les multiplieurs et ''barrel shifters'' sont eux aussi dupliqués sur les CPU superscalaires larges, mais cela vient souvent après avoir dupliqué les unités mémoire. La raison est que les accès mémoire sont critiques pour la performance des CPU à exécution dans le désordre, pas les multiplications. Pour les unités de branchement, une subtilité complique le fait d'en installer plusieurs. Que faire quand deux branchements sont pris ? La solution la plus simple est de ne pas se trouver dans cette situation : on n'utilise qu'une seule unité de branchement et on n’exécute qu'un seul branchement par cycle. La majorité des processeurs superscalaires étroits font ainsi. Les processeurs superscalaires larges ont cependant deux unités de branchements, mais ils n'acceptent qu'un seul branchement pris à la fois. Si les deux branchements pris sont exécutés en même temps, le processeur n’exécute que le plus récent, dans l'ordre du programme. ===Les CPU superscalaires mélangent les trois techniques=== Les CPU superscalaires utilisent toutes les techniques à la fois : partitionnement, duplication de l'ALU entière, duplication des autre unités fonctionnelles. Chaque processeur utilise ces trois stratégies à sa sauce, en fonction de son budget en transistor. Les quatre techniques ont cependant un cout en circuit différent, et donnent des gains de performances eux aussi forts différents. * Le '''partitionnement superscalaire''' a un cout en circuit modéré, car on n'a pas besoin de dupliquer d'ALU. * La '''duplication des ALU entières''' est la technique la plus importante : elle a un cout en circuit modéré, et donne des gains en performance impressionnants. * La '''duplication des autres unités''' a un cout en circuit variable, pour des gains en performance eux aussi variables. * Le '''partage des ports d'émission''' réduit le cout en circuit du CPU, pour des pertes de performances mineures si bien fait. L'usage du partitionnement seul, sans duplication, était surtout le fait des processeurs commercialisés entre les années 1989 et 1993. Le partage des ports d'émission a lui aussi mis un peu de temps pour s'imposer. Globalement, les processeurs superscalaires ont évolués progressivement, pour se rapprocher d'une sorte de standard. Mais pour comprendre ce qu'est ce standard, et en quoi les CPU historiques s'en sont progressivement rapprochés, il faut étudier l'histoire des CPU superscalaires. L'histoire des CPU superscalaires a commencé dès 1960, mais est resté un projet de recherche pendant plusieurs décennies. De nombreux designs ont été conçus, mais abandonnés avant la production, comme l'ACS-1 d'IBM. Les années 1980 ont vu l'arrivée de prototypes de processeurs VLIW, mais qui n'étaient pas superscalaires. Par exemple, l'Apollo DN10000 et l'Intel i860 pouvaient émettre deux instructions à la fois, sous conditions, mais demandaient que le compilateur fasse une bonne partie du travail. Les premiers CPU superscalaires commercialisés sont sortis en 1989. Deux CPUs superscalaires sont sortis cette année là : le RS/6000 d'IBM et l'i960CA d'Intel. Puis, la compétition s'est aussi lancée dans la bataille. Les CPU commercialisés avant l'année 1993 étaient à double émission, à l'exception du SuperSPARC qui utilisait la triple émission. Les modèles après 1993 hésitaient entre double et quadruple émission. Dans le tableau ci-dessous, les processeurs quadruple émission sont en bleu, les triple émission sont en rouge, les autres ne sont pas en couleur. {|class="wikitable" |- ! !! IBM !! HP PA-RISC !! SPARC !! Intel!! Alpha !! Autres |- ! 1989 | RS/6000 || || || i960CA || || |- ! 1990 | || || || || || |- ! 1991 | || || || || || Motorola 88110 |- ! 1992 | RSC || PA 7100 || class="f_rouge | SuperSPARC || || 21064 || |- ! 1993 | PPC 601 || || HyperSPARC || Pentium || || |- ! 1994 | class="f_bleu | POWER 2 || PA 7200 || || || class="f_bleu | 21164 || |- ! 1995 | 603/604 || class="f_bleu | PA 8000 || class="f_bleu | UltraSPARC 1 || Microarchitecture P6 (+ concurrence AMD) || || class="f_bleu | MIPS R1000 |- ! 1996 | class="f_bleu | 620 || || || || class="f_bleu | 21264 || |- ! ... || ... || ... || ... || ... || ... || ... |} Les premiers CPU à double émission sont assez représentatifs des CPU à double émission modernes. Il faut dire que se limiter à deux voies ne permet pas de faire beaucoup de choses. Voici le tableau chronologique disant quel CPU utilisait quelle technique. * Les CPU en vert utilisaient la double émission entière-flottante. * Les CPU en rouge utilisaient la double émission entière "pure". * Les CPU en jaune utilisaient un ''partitionnement complet'' entre ALU, FPU et unité mémoire. * Les CPU sans couleur utilisaient un grand nombre d'unités, avec beaucoup de ports d'émission. * Les CPU barrés utilisent la triple ou quadruple émission et seront vu dans la section suivante. {|class="wikitable" |- ! !! IBM !! HP PA-RISC !! SPARC !! Intel!! Alpha !! Autres |- ! 1989 | class="f_vert | RS/6000 || || || class="f_rouge | i960CA || || |- ! 1990 | || || || || || |- ! 1991 | || || || || || Motorola 88110 |- ! 1992 | class="f_vert | RSC || class="f_vert | PA 7100 || <s>SuperSPARC</s> || || class="f_jaune | 21064 || |- ! 1993 | class="f_vert | PPC 601 || || class="f_jaune | HyperSPARC || class="f_rouge | Pentium || || |- ! 1994 | <s>POWER 2</s> || PA 7200 || || || <s>21164</s> || |} C'est en 1994, que la quadruple émission est arrivée et que les CPU ont essayé divers implémentations. Il y avait alors trois stratégies pour la double/triple/quadruple émission, qui partageaient des problèmes similaires. La première implémentation dupliquait les ALUs et les FPUs, ce qui permettait d'émettre deux opérations flottantes avec deux opérations entières. Le POWER 2 dupliquait ses FPUs à la perfection, alors que le 21264 séparait l'additionneur et le multiplieur flottant. Les accès mémoire étaient traités comme des opérations entières et le processeur pouvait souvent en émettre deux à la fois. Mais une telle organisation n'est plus utilisée de nos jours, car, sans instructions flottantes à exécuter, elle est équivalente à de la double émission. La seconde implémentation utilisait deux ALU entières (dont un multiplieur), une unité mémoire et une FPU.Cette organisation est intéressante, mais encore trop stricte pour donner de bons résultats, les quadruplets avec exactement deux opérations entières, un accès mémoire et une opération flottante étant trop rares. L'usage de ces 4 unités en double ou triple émission a été utilisé sur le PA 7200 et le SuperSPARC. Malheureusement, en plus de problèmes au niveau de son cache, le SuperSPARC avait de nombreuses contraintes d’appariement, car le banc de registre n'avait pas assez de ports pour alimenter certaines combinaisons d'instructions. De plus, s'il émettait une multiplication, il ne pouvait pas émettre de seconde µop. Enfin, une troisième implémentation utilisait un grand nombre d'unités fort différentes, bien plus que quatre, avec au minimum deux ALU entières. Prenons l'exemple de l'UltraSPARC, qui avait : deux ALU entières, une unité mémoire, une unité de branchement, un additionneur flottant, un multiplieur flottant, un diviseur flottant (aussi utilisé pour le calcul des racines carrées), et 2 unités graphiques pour les calculs sur des pixels. En tout, cela fait 9 unités différentes, pour de la quadruple émission. Dans le même genre, les processeurs MIPS R8000 et R10000 avaient une implémentation plus moderne, dans le sens où toutes les unités étaient dupliquées. Le CPU avait deux ALU, deux FPU et deux unités mémoire. Il pouvait donc émettre quatre instructions à la fois, tant que le paquet collait avec ces six unités. Moitié opérations entières, moitié opérations mémoire, avec de quoi remplacer les opérations entières par des opérations flottantes. Le PA 8000, lui, dupliquait toutes ses unités en deux exemplaires, ce qui n'est pas l'idéal, aucun processeur moderne ne fait cela aujourd'hui. Le tout est résumé dans le tableau ci-dessous, avec : * en vert les CPU utilisant la première stratégie ; * en rouge les CPU utilisant la seconde stratégie ; * en jaune les CPU utilisant la troisième stratégie. {|class="wikitable" |+ Processeurs quadruple émission historiques |- ! Année !! Processeur !! Unités |- ! 1991 | class="f_jaune | Motorola 88110 || class="f_jaune | 2 ALU + MUL + DIV + BIT + MEM + FADD + 2 unités graphiques |- |- ! rowspan="2" | 1994 | class="f_vert | POWER 2 || class="f_vert | 2 ALU/MEM + 2 FPU (+ 2 Branchements via ''branch folding'') |- | class="f_rouge | 21164 || class="f_rouge | 2 ALU + FPU + MEM |- ! rowspan="3" | 1995 | PA 8000 || 2 ALU + 2 ''barrel shifters'' + 2 MEM + 2 FPU (FMA) + 2 FPU (DIV et SQRT) + 2 SIMD |- | class="f_jaune | UltraSPARC 1 || class="f_jaune | 2 ALU + MEM + BRANCH + FADD + FMULFDIV + 2 unités graphiques |- | MIPS R8000 et R10000 || 2 ALU + 2 FPU + 2 MEM |- ! rowspan="2" | 1996 | class="f_rouge | 620 || class="f_rouge | 2 ALU + FPU + MEM |- | class="f_vert | 21264 || class="f_vert | 2 ALU/MEM + 2 FPU (FADD + FMUL) |} Malheureusement, ces CPUs historiques n'avaient ni assez d'unités de calcul entières, ni d'unités mémoire, pour remplir la quadruple émission. Environ la moitié des instructions d'un programme correspond à des instructions entières. Pour en profiter, un processeur à N voies doit disposer d'au minimum N/2 ALU entières, l'idéal étant d'aller au-delà. Les CPU superscalaires historiques avaient 2 ALU pour de la quadruple émission, ce qui était très limite. En comparaison, les CPU superscalaires modernes mettent le paquet niveau ALU entière. La plupart des CPU x86 modernes descendent rarement sous les 2/3, la plupart remplit presque la totalité de la largeur du CPU. Pour donner un exemple, la microarchitecture Golden Cove d'Intel est un processeur à 6 voies, avec 5 ALU entières. Pour être précis, un programme informatique exécute environ 40% de son code dans des instructions arithmétiques, un autre 40% dans les accès mémoire, et un le reste pour... le reste. La conséquence est que les processeurs modernes dupliquent leurs unités mémoire. Par exemple, la microarchitecture ''Golden Cove'' d'Intel dispose de trois unités mémoire : deux spécialisées dans les écritures, trois dans les lectures. La microarchitecture ''Gracemont'' utilise la pentuple émission et peut émettre 4 µops entières et 4 µops mémoire. Notez que le ratio est équilibré entre opérations entières et mémoire, avec soit égalité, soit un avantage pour les ALU entières. Une autre régularité est que la largeur totale du processeur peut être occupée par des opérations entières/mémoire. Pour le dire autrement, sur un processeur quadruple émission, il pourra émettre un mix de 4 instructions entière/mémoire, avec zéro opération flottante. Idem avec des processeurs triples ou pentuple émission, voire plus. Si le processeur émet une opération flottante, elle remplace une opération entière. Prenons l'exemple des processeurs AMD K6, sortis en 1997, juste avant les AMD Athlon. Ils avaient 10 unités de calcul, 6 ports d'émission, 4 ports de décodage. Voici exactement comment étaient répartie les unités de calcul sur les ports d'émission. C'était des processeurs quadruple émission, ce qui fait que la moitié de la largeur du processeur sert pour des opérations entières, et l'autre moitié pour les accès mémoire. {|class="wikitable" |- ! Port d'émission n°1 !! Port d'émission n°2 !! Port d'émission n°3 !! Port d'émission n°4 !! Port d'émission n°5 !! Port d'émission n°6 ! |- | ALU entière || ALU entière || FPU || Unité de branchement || Unité LOAD || Unité STORE |- | || Multiplieur || || || || Unité de calcul d'adresse (instruction LEA) |- | || ''Barrel shifter'' || || || || Unité pour les instructions PUSH |} Les microarchitectures K7, K8 et K10, utilisées sur les anciens CPU AMD Athlon, sont un exemple moins représentatif. Ces microarchitectures utilisent le partage des ports d'émission au maximum. C'était des microarchitectures à triple émission, avec six ports d’émission. Il y avait trois ports d'émission pour les opérations flottantes, et trois ports d'émission pour les micro-opérations entières/mémoire. Il y avait trois ALU entières et trois unités mémoire, chaque ALU partageait un port d'émission avec une unité mémoire. Elle pouvait donc émettre soit trois opérations entières, soit trois µops mémoire, soit un mix des deux. En plus des mix avec les opérations flottantes et branchements. {|class="wikitable" |- ! Port d'émission n°1 !! Port d'émission n°2 !! Port d'émission n°3 !! Port d'émission n°4 !! Port d'émission n°5 !! Port d'émission n°6 ! |- | ALU entière || ALU entière || ALU entière || FADD || FMUL || FPU (autres opérations) |- | Multiplieur || || || || || |- | LOAD/STORE || LOAD/STORE || LOAD/STORE || || || |} Les CPU superscalaires larges dupliquent les multiplieurs et/ou les ''barrel shifters'', et même les unités de branchement. La raison est que quand la largeur du CPU augmente, le processeur traite des paquets d'instructions de plus en plus gros. Par exemple, pour un CPU à 10 voies, cela veut dire émettre 10 instructions en même temps. Il est alors plus fréquent d'avoir deux multiplications, deux décalages, deux accès mémoire, deux branchements. Par exemple, la moyenne est d'une multiplication tous les 5 instructions. Les CPU superscalaires à 10 voies ont donc tendance à avoir deux multiplications par paquet d'instruction, en moyenne. Avoir deux multiplieurs est alors un gain en performance clair. Par exemple, la microarchitecture ''Golden Cove'' a deux ''barrel shifters''. C'est une architecture à 6 voies. {|class="wikitable" |- ! Port d'émission n°1 !! Port d'émission n°2 !! Port d'émission n°3 !! Port d'émission n°4 !! Port d'émission n°5 !! Port d'émission n°6 !! Port d'émission n°7 !! Port d'émission n°8 !! Port d'émission n°9 !! Port d'émission n°10 |- | ALU entière || ALU entière || ALU entière || ALU entière || ALU entière || LOAD || LOAD || LOAD || STORE || STORE |- | ''Barrel shifter'' || Multiplieur || Multiplieur || ''Barrel shifter'' || || || || || || |- | Branchements || Diviseur || || Branchements || || || || || || |} ==Les µops mémoire sur les processeurs superscalaires== Plus haut, nous avons volontairement passé sous silence les accès mémoire. La raison à cela est que leur support sur les CPU superscalaire peut se faire de plusieurs manières différentes, qu'il vaut mieux expliquer dans une section dédiée. Les différentes implémentations sont décrites dans la liste suivante, nous allons les voir une par une. * Avec la première méthode, les accès mémoire sont exécutés par le pipeline pour les opérations entières. * Avec la seconde méthode, les accès mémoire ont un pipeline dédié ; c'est l'''émission parallèle des accès mémoire''. * Avec la troisième méthode, il est possible d'émettre plusieurs accès mémoire simultanément : c'est l'''émission multiple des accès mémoire''. Voyons ces trois cas dans le détail. Nous verrons notamment les différentes formes d'émission multiple des accès mémoire. ===Les accès mémoire traités dans le pipeline entier=== Dans le cas le plus simple, les accès mémoire sont traités comme des opérations entières un peu spéciales. L'unité mémoire est sur le même port d'émission que l'ALU entière, ce qui fait que les accès mémoire remplacent des opérations entières. Faire ainsi permet d'utiliser une ALU entière pour les calculs d'adresse, pas besoin de rajouter une unité de calcul d'adresse séparée. Typiquement, un processeur de ce type place l'unité mémoire après l'unité de calcul. Les opérations entières n'utilisent que l'ALU entière, mais pas l'unité mémoire. Les accès mémoire exécutent un calcul d'adresse dans l'ALU entière, puis l'adresse calculée est envoyée à l'unité mémoire. Le tout est illustré ci-dessous, dans le cas d'un processeur historique à double émission entière-flottante. [[File:Processeur non-superscalaire basique 01.png|centre|vignette|upright=1.5|Processeur non-superscalaire basique]] Un exemple de CPU double émission de ce type est celui du Power PC 440, un CPU destiné à l'embarqué. Vu qu'il était destiné à l'embarqué, il n'avait pas de FPU, il n'avait pas besoin de manipuler de nombres flottants. Il n'a donc que des ALU entières, un multiplieur et une unité mémoire. Il dispose de deux ports d'émission. Le premier est connecté à une ALU entière et un circuit multiplieur, le second est relié à l'unité mémoire et une seconde ALU entière. Une organisation simple, mais très efficace ! [[File:PowerPC 440.png|centre|vignette|upright=2|Microarchitecture du PowerPC 440.]] L'implémentation économise une unité de calcul d'adresse, mais n'a pas de cout en performance significatif. Les accès mémoire étant très lents, émettre une µop entière en parallèle d'un accès mémoire ne sert pas à grand-chose. On peut émettre cette µop entière un cycle plus tard, elle sera terminée avant même que l'accès mémoire soit terminé, même si l'accès mémoire se fait dans le cache L1 ou L2. Pour comprendre pourquoi, faisons quelques calculs. Un accès mémoire prend deux-trois cycles quand il tombe dans le cache L1. Et on doit attendre qu'il soit terminé pour en émettre un nouveau. Combien d'instructions arithmétiques peut-on lancer pendant ce temps, sur un CPU triple émission ? Au premier cycle, on peut en émettre 2 (l'instruction mémoire prend une place, donc 3-1), puis 3 au second cycle. Ce qui fait 5 instructions arithmétiques au total. Or, il se trouve qu'il y a une instruction d'accès mémoire pour 5 instructions arithmétiques, environ. Par contre, ce compromis change quand on passe à la quadruple émission, voire au-delà. Dans ce cas, il devient intéressant de pouvoir émettre une nouvelle instruction mémoire à chaque cycle. Ce qui nous amène à la section suivante... ===L'émission parallèle des accès mémoire=== A partir de la quadruple émission, la tendance a été de séparer l'unité mémoire des ALU entières. Cela permet d'émettre des µops mémoire en même temps que des µops entières et/ou flottantes., ce qui porte le nom d''''émission parallèle des µops mémoire''', ou encore ''émission mémoire parallèle''. Par exemple, un processeur quadruple émission peut émettre en même temps deux opérations entières, une opération flottante et un accès mémoire. L'accès mémoire vient en plus des opérations entières, il n'est pas possible de remplacer l'accès mémoire par une opération entière. La technique ne permet pas de faire les calculs d'adresse dans l'ALU entière. Il faut donc utiliser une unité de calcul d'adresse séparée. Elle est intégrée dans l'unité mémoire, qui fait l'interface entre le cache et le banc de registres. Le cout en circuits est très faible, car les unités de calcul d'adresse n'utilisent guère plus qu'un additionneur et un circuit de décalage très simple. L'émission mémoire parallèle est plus facile à implémenter sur les CPU RISC, qui ont des instructions mémoire séparées du reste. Les CPU CISC ne sont pas dans ce cas, ce qui fait que séparer l'unité mémoire du pipeline entier est compliqué. Les CPU superscalaires historiques étaient pour moité des CPU RISC et pour moitié des CPU CISC, ce qui fait qu'ils n'utilisaient pas ou peu l'émission mémoire parallèle. Les premiers processeurs superscalaires se contenaient de l'émission parallèle mémoire, ils ne pouvaient émettre qu'un seul accès mémoire à la fois, l'unité mémoire n'était pas dupliquée. Et ce n'est pas tant un problème que ça, du moins sur les processeurs à double ou triple émission. En effet, il est rare que l'unité mémoire doive faire deux accès simultanés. Notons que le processeur peut exécuter une instruction mémoire en parallèle d'un calcul ou d'un branchement, seuls les accès mémoire simultanés sont impossibles. ===L'émission multiple des accès mémoire avec une LSQ=== Les processeurs superscalaires larges travaillent sur des paquets de 8 à 10 instructions, il est plus fréquent d'avoir plusieurs accès mémoire par paquet. Dans ces conditions, il est utile d'avoir une '''émission multiple des accès mémoire''', à savoir d'émettre plusieurs micro-opérations mémoire en même temps. Les processeurs superscalaires modernes sont capables d'émettre plusieurs lectures/écritures simultanément. Par exemple, ils peuvent émettre une lecture en même temps qu'une écriture, ou plusieurs lectures, ou plusieurs écritures. Il faut noter que selon le processeur, il peut y avoir des restrictions quant aux accès mémoire émis en même temps. Par exemple, certains processeurs peuvent émettre une lecture avec une écriture en même temps, mais pas deux lectures ni deux écritures. Ou encore, ils peuvent émettre deux lectures, une lecture et une écriture, mais pas deux écritures en même temps. Dans la majorité des cas, les processeurs ne permettent pas d'émettre deux écritures en même temps, alors qu'ils supportent plusieurs lectures. Il faut dire que les lectures sont plus fréquentes que les écritures. Les processeurs qui autorisent toutes les combinaisons de lecture/écriture possibles, sont rares. L'émission multiple des accès mémoire s'implémente dans l'unité mémoire. Pour rappel, l'unité mémoire s'interpose entre le cache L1 et le reste du pipeline. Dans le cas le plus simple, elle incorpore une ''load/store queue'' et des unités de calcul d'adresse. L'émission multiple des accès mémoire demande de rendre la ''Load-store queue'' multi-ports, afin de gérer plusieurs micro-opérations mémoire simultanées. Il faut aussi dupliquer les unités de calcul d'adresse reliées à la LSQ, chaque port d'émission mémoire devant avoir sa propre unité de calcul d'adresse. : Dans ce qui suit, nous parlerons d'AGU (''Adress Generation Unit'') pour désigner les unités de calcul d'adresse, et de LSQ pour designer la ''Load-store queue''. [[File:Processeur non-superscalaire basique 02.png|vignette|upright=1|Processeur non-superscalaire basique]] Dupliquer les ports de la LSQ et les AGU est nécessaire. Mais qu'en est-il du cache ? La réponse varie suivant le processeur. Il y a deux grandes méthodes pour implémenter l'émission multiple des accès mémoire. La première méthode ajoute des ports de lecture/écriture au cache de données, pour faire des lectures/écritures simultanées. Il faut alors dupliquer des unités de calcul, rajouter les ports sur le cache, mais aussi rajouter des ports sur la ''load/store queue'', pour qu'elle échange des données/commandes mémoire avec le cache. Le cout en circuits est alors conséquent La seconde duplique les unités de calcul, mais pas les ports d'accès au cache. Les accès mémoire sont alors accumulés dans la ''load/store queue'', qui sont ensuite traités un par un par le cache. Un exemple est celui des processeurs AMD de micro-architecture K7 K8, à savoir les anciens AMD Athlon et AMD Athlon 64, qui pouvaient calculer trois adresses simultanément, mais ne pouvaient faire qu'un seul accès au cache par cycle. Le cout en circuit est modéré : les unités de calcul coutent très peu, les ports de la ''load/store queue'' coutent déjà un peu plus. Quelques processeurs utilisaient des solutions intermédiaires. Un processeur utilisant cette technique est, par exemple, capable d'émettre quatre µops mémoire en même temps, mais avec seulement deux ports sur le cache. Un exemple classique étant les Pentium 1 et 2 d'Intel, dont nous parlerons dans le chapitre suivant. ===L'émission séparée des LOAD et STORE=== De rares processeurs ont des ports d'émission qui peuvent faire indifféremment lecture comme écritures. Mais les processeurs modernes préfèrent séparer ports d'émission pour les lectures et les écritures. Un exemple est celui des processeurs Intel de micro-architecture Nehalem, qui avaient un port de lecture et un port d'écriture tous deux reliés à la ''Load-store queue'', chacun avec leur propre unité de calcul d'adresse. Comme autre exemple, les processeurs skylake ont une LSQ avec deux ports de lecture et un port d'écriture, ce qui permet d'émettre deux lectures en même temps qu'une écriture. Dans cette section, nous allons étudier le cas avec des ports séparés pour les lectures et écritures. En général, les processeurs ont un peu plus de ports de lectures que d'écritures. Une raison est qu'un programme a, en moyenne, un tout petit peu plus de lectures que d'écritures. Mais la différence n'est pas énorme, c'est un léger déséquilibre. Une seconde raison est que l'exécution dans le désordre marche bien mieux quand les lectures ont lieu le plus tôt possible. Donc mieux vaut privilégier les lectures à l'émission, pour qu'elles se fassent un peu en avance. En pratique, la différence n'est pas très marquée. Il est courant d'avoir un port de lecture et un d'écriture, les CPU superscalaires très larges ont souvent deux ports de lecture et deux d'écriture. A la rigueur, quelques CPU ont trois ports de lecture et deux d'écriture. Le cas le plus simple est celui où on peut émettre une lecture et une écriture en même temps. Il s'implémente facilement si la LSQ est en réalité une simple ''Store queue'', à savoir qu'elle met en avance les écritures uniquement. Pour rappel, les écritures sont mises en attente dans la ''Store queue'', mais les lectures sont exécutées immédiatement. Une fois l'adresse à lire connue, la lecture consulte la ''Store queue'' pour détecter les dépendances de données, puis s'exécute. La donnée à lire est lue soit depuis la ''Store queue'', soit depuis le cache. [[File:Emission LOAD - Store séparée.png|vignette|upright=1|Émission LOAD - Store séparée]] L'implémentation de l'émission multiple est alors très simple. La ''Store queue'' a déjà deux ports, avec un pour les lectures et un autre pour les écritures. Idem pour le cache, qui a naturellement un port de lecture et un d'écriture. Émettre une lecture et une écriture simultanément ne demande donc pas de modification digne de ce nom. La ''Store queue'' doit juste supporter une lecture et une écriture simultanée. Ajouter l'émission d'une seconde lecture n'est pas compliquée : il faut juste rajouter un second port de lecture sur le cache et la ''Store queue''. Pour rappel, l'unité mémoire est précédée par une structure qui met en attente les micro-opérations mémoire avant leur émission finale. La structure en question varie suivant l'implémentation, mais un cas particulier se marie bien avec l'émission multiple des µops mémoire. Il s'agit du cas avec une file de µops séparée pour les lectures et une autre pour les écritures, appelées respectivement la ''file de µops LOAD'' et la ''file de µops STORE''. Il est alors possible d'émettre une lecture et une écriture lors du même cycle d'horloge. Le port de lecture alimente une unité de calcul d'adresse dédiée, directement reliée au cache. Le port d'écriture du cache alimente une unité de calcul, qui est suivie par une file d'écriture, elle-même reliée au cache. Les processeurs AMD de micro-architecture K6 utilisaient cette technique. [[File:Double émission avec le Load Ordering, Store Ordering.jpg|centre|vignette|upright=2.5|Double émission avec le Load Ordering, Store Ordering]] ==Les CPU superscalaire à exécution dans le désordre== La superscalarité modifie aussi les circuits dédiés à l'exécution dans le désordre. Elle modifie les unités de renommage de registre, le ROB et les autres structures liées à l'exécution dans le désordre, au sens large. Aucune structure n'est épargnée. Elles ont tous des prots de lecture/écriture en plus, afin d'encaisser l'émission, le ''commit'' ou le renommage simultané de plusieurs µops. Par exemple, le ROB doit être modifié pour émettre et terminer plusieurs instructions en même temps, en ajoutant des ports de lecture/écriture. Pour un CPU superscalaire à 8 voies, on se dit que le ROB peut retirer 8 µops par cycle maximum. Mais certains processeurs récents ont un ROB qui peut retirer le double ! Un exemple est celui des CPU de micro-architecture Skymont, qui peuvent émettre 8 µops par cycle, mais en retirer 16. Quelques CPU de marque AMD étaient aussi dans ce cas. L'avantage est que cela permet de réduire la taille du ROB, du banc de registre et de quelques autres structures matérielles liées à l'exécution dans le désordre. Le gain sur-compense le cout en transistor nécessaire pour retirer 16 µops par cycle. Dans cette section, nous allons voir comment les étages du pipeline dédiés à l'exécution dans le désordre sont modifiées sur un CPU superscalaire. ===La superscalarité avec une file/fenêtre d'instruction centralisée=== Plus haut, nous avons montré qu'un processeur pouvait avoir plus de ports d'émission que de ports de décodage. On dit aussi que sa ''largeur de décodage'' était supérieure à sa ''largeur d'émission''. La '''largeur de décodage''' est le nombre de µops fournies par l'unité de décodage par cycle d'horloge, la '''largeur d'émission''' est le nombre maximal de µops émises par cycle d'horloge. Et n'allez pas croire qu'un surplus de ports d'émission est du gâchis. Sur les processeurs avec exécution dans le désordre, ce surplus peut être utilisé. Mais avant toute chose, évacuons un détail. Vous avez sans doute pensé que le surplus peut être exploité par les instructions machines décodées en plusieurs µops . Par exemple, si une instruction est décodée en deux µops, on pourrait avoir un port d'émission en plus pour en profiter. Mais les ports de décodage sont *en sortie du décodeur*, et contiennent des µops, pas des instructions. Le surplus de ports d'émission peut occasionnellement utiliser le surplus de ports de décodage, mais cela implique que des µops ont été mises en attente dans l'unité d'émission, pour être émises par la suite. Et cela implique l'usage d'une fenêtre d'instruction ou d'une file de µops. Le principe est que les µops sont accumulées dans la fenêtre d'instruction, en attendant leurs opérandes ou la résolution d'une dépendance. Et si l'occasion se présente, une µops est émise sur chaque port d'émission. Il y a donc un gain en performances, mais il faut faire quelques précisions sur son origine. La largeur d'émission utilisée ne peut pas dépasser, *en moyenne*, la largeur de décodage. Et c'est intuitif : un CPU qui décode 4 µops par cycle ne peut pas émettre 6 par cycle en moyenne, sur le temps long, il n'y a pas de µops en surplus qui sortent du vide. Par exemple, sur un processeur quadruple émission, si on analyse combien d'instructions sont émises par cycle, il peut y avoir des cycles où la largeur d'émission est totalement utilisée, mais ce sera compensé par des périodes où elle est fortement sous-utilisée. Les µops sont accumulées en cas de sous-utilisation, puis consommées en cas d'utilisation complète. Les deux périodes se compensent. Et c'est là que l'on peut comprendre d'où vient le gain en performance. Les cycles où les ports d'émission sont sous-utilisés existent systématiquement, que le processeur utilise l'exécution dans le désordre ou pas. Ils sont liés à la présence de dépendances de données, aux contraintes d’appariement, etc. Elles tendent à réduire la moyenne du nombre de µops émises par cycle. Par contre, ce sont les cycles de rattrapage, où les ports en surplus sont utilisés, qui font remonter la moyenne. Le gain en performance est donc lié à ces cycles de rattrapage, où les µops accumulées sont émises dans les ports en surplus. Et ils n'existent que sur les processeurs avec une file/fenêtre d'instruction. Réduire le nombre de ports d'émission permet de garder des fenêtres d'instruction et un ROB simples, rapides et économes en énergie. Et mine de rien, c'est là un compromis important : à quoi bon augmenter le nombre de ports d'émission si c'est pour que ce soit compensé par une fréquence plus faible ? Par contre, réduire le nombre de ports augmente la ''port contention'', surtout si on a mal réparti les ALU. Il faut donc trouver un compromis entre réduire le nombre de ports d'émission d'un côté, réduire la ''port contention'' de l'autre. La différence entre largeur de décodage et d'émission a un impact assez fort sur le banc de registre, et précisément sur son nombre de ports de lecture. Le banc de registre a besoin de plus de ports de lecture avec une fenêtre d’instruction qu'avec des stations de réservation. Rappelons la terminologie utilisée dans ce cours. Les fenêtres d'instruction se contentent de mémoriser la micro-opération et quelques bits pour détecter la disponibilité des opérandes. Par contre, les stations de réservations mémorisent aussi les opérandes des instructions. Et cette différence a une influence le nombre de ports de lecture du banc de registre. Supposons que toutes les instructions sont dyadiques, à deux opérandes. Comptons le nombre de ports nécessaires pour alimenter les unités de calcul, sans utiliser d'arbitre de banc de registre. Avec une fenêtre d'instruction, il faut le double du nombre de ports d'émission. Mais avec des stations de réservation, il faut le double des ports de décodage. La raison est que les registres sont lus après émission avec une fenêtre d'instruction, avant avec des stations de réservation (sous-entendu, après le décodage). Or, comme dit plus haut, les processeurs superscalaires larges ont souvent plus de ports d'émission que de décodage. ===La superscalarité à fenêtres d'instruction décentralisées=== Les processeurs précédents utilisaient une unité d'émission simple, qui pouvait utiliser l'émission dans l'ordre ou dans le désordre. Elle peut être remplacée par une fenêtre d'instruction centralisée dans que cela quoi que ce soit aux explications précédentes, idem avec une station de réservation centralisée. En soi, l'exécution dans le désordre est donc totalement compatible avec une implémentation simple de la superscalarité. Par contre, des subtilités surviennent avec les fenêtres d'instruction décentralisées. Pour rappel, sur ces processeurs, les µops sont émises en deux phases. Une première phase de ''dispatch'' qui envoie les µops vers la fenêtre d'instruction adéquate, sans détecter les dépendances de données. La seconde phase de ''schedule'' gère les dépendances de données, elle met en attente les µops en attendant que l'unité soit disponible pour les exécuter, en attendant aussi que les opérandes soient disponibles. Et à ce petit jeu, il y a une différence entre le nombre de µops qui sont ''dispatch par cycle'' et celles qui sont ''schedule''. Dit autrement, la largeur d'émission est scindée en une '''largeur de ''dispatch''''' et une '''largeur de ''schedule''. Par exemple, prenons un processeur triple émission avec deux stations de réservation : une pour les µops entières/mémoire, et une autre pour les µops flottantes. L'unité d'émission peut ''dispatch'' maximum trois µops par cycle : soit trois µops entières/mémoire, soit trois flottantes, soit un mix de trois µops différentes. Les stations de réservation, quant à elles, peuvent chacune ''schedule'' 3 µops par cycle, vers trois ALUs entières pour la station de réservation entière/mémoire, vers trois FPS pour la station de réservation flottante. De quoi ''dispatch'' trois µops, mais d'en ''schedule'' six, à chaque cycle. Et cet exemple ne sort pas de nulle part : les CPU x86 d'Intel et AMD sont globalement dans ce cas, même si les chiffres varient d'une microarchitecture à l'autre. L'exemple précédent colle parfaitement à la microarchitecture K7 d'AMD, utilisée sur les processeurs Athlon dans les années 2000. [[File:AMD K7.png|centre|vignette|upright=2.5|AMD K7, microarchitecture.]] L'exemple précédent avait assez d'unités entières/flottantes pour couvrir toute la largeur de décodage/''Dispatch''. Mais ce n'est pas toujours le cas, comme va le montrer l'exemple suivant. Le MIPS R10000, qui utilisait un mélange de files de µops et de fenêtres d'instructions : une file de µops les accès mémoire, une fenêtre pour les instructions entières et une autre pour les opérations flottantes. Les branchements et NOPs étaient exécutés dans une unité de branchement distincte. Les trois files/fenêtres peuvent mémoriser 16 µops. Le processeur avait deux ALU entières, une AGU de calcul d'adresse, un additionneur flottant et un multiplieur/diviseur flottant. Il y avait un multiplieur entier sur le port d'émission l'ALU 2. Le processeur décodait 4 instructions par cycle et produisait jusqu'à 4µops, qui étaient redistribuées sur les trois fenêtres d'instructions. Chacune pouvait accepter 4 µops par cycle, ce qui fait que processeur pouvait ''dispatch'' 4 µops entières, ou 4 µops flottantes, ou 4 µops mémoire, ou un mélange de deux ou trois types de µops. Pour ce qui est du ''schedule'', la fenêtre d'instruction entière pouvait émettre 2 µops, la fenêtre flottante pouvait émettre une addition en même temps qu'une multiplication/divison flottante et qu'un branchement conditionnel flottant, et la file de µops mémoire ne pouvait émettre qu'une µop mémoire à la fois. En tout, cela fait 6µops ''schedule'' pour 4 µops au ''dispatch''. [[File:R 10000.png|centre|vignette|upright=2.5|MIPS R10000, microarchitecture.]] ===L'unité de renommage superscalaire=== Avec la superscalarité, l'unité de renommage de registre doit gérer le cas où des instructions consécutives ont des dépendances de registre. Par exemple, prenons un processeur à double émission, qui renomme deux instructions consécutives. Si elles ont une dépendance de registre, le renommage de la seconde instruction dépend du renommage de la première. La première doit être renommée et le résultat du renommage est utilisé pour renommer la seconde, ce qui empêche d'utiliser deux unités de renommage séparées. Pour cela, elle renomme les registres sans tenir compte des dépendances, pour ensuite corriger le résultat. [[File:Unité de renommage superscalaire.png|centre|vignette|upright=2|Unité de renommage superscalaire.]] Seules les dépendances lecture-après-écriture doivent être détectées, les autres étant supprimées par le renommage de registres. Repérer ce genre de dépendances se fait assez simplement : il suffit de regarder si un registre de destination d'une instruction est un opérande d'une instruction suivante. [[File:Détection des dépendances sur un processeur superscalaire.png|centre|vignette|upright=2|Détection des dépendances sur un processeur superscalaire.]] Ensuite, il faut corriger le résultat du renommage en fonction des dépendances. Si une instruction n'a pas de dépendance avec une autre, on la laisse telle quelle. Dans le cas contraire, un registre opérande sera identique avec le registre de destination d'une instruction précédente. Dans ce cas, le registre opérande n'est pas le bon après renommage : on doit le remplacer par le registre de destination de l'instruction avec laquelle il y a dépendance. Cela se fait simplement en utilisant un multiplexeur dont les entrées sont reliées à l'unité de détection des dépendances. On doit faire ce replacement pour chaque registre opérande. [[File:Correction des dépendances sur un processeur superscalaire.png|centre|vignette|upright=2|Correction des dépendances sur un processeur superscalaire.]] ==Les optimisations des processeurs superscalaires larges== Les processeurs superscalaires larges ont des optimisations spécifiques, qui n'ont pas de sens sur les processeurs superscalaires étroits. Dans les grandes lignes, elles se classent en deux types. Les premières permettent de charger/décoder plus de 5-10 instructions simultanément. Le problème est alors que les branchements viennent mettre leur grain de sel et qu'il faut en tenir compte. Les secondes corrigent des problèmes qui apparaissent quand un CPU intègre plus de 5 à 10 unités de calcul. ===L'étape de chargement d'un CPU superscalaire large=== Charger plusieurs instructions pose de nombreux problèmes sur les processeurs superscalaires larges, qui émettent de 5 à 10 instructions simultanées. Pour rappel, la stratégie basique pour charger N instructions est de charger un bloc de N instructions consécutives. Les instructions après un branchement ne sont pas décodées ni exécutées, ce qui entraine une petite baisse de performance. Pour un N petit, la perte de performance est mineure. Mais pour un N large, elle devient suffisamment importante pour que la conception du CPU doit alors être repensée de fond en comble. Les analyses statistiques disent qu'il y a environ un branchement tous les 4 à 6 instructions. Un CPU a donc tendance à décoder des paquets de 3 à 5 instructions consécutives, avant qu'un branchement casse la dynamique. Or, cela pose des problèmes pour utiliser plusieurs décodeurs. L'implémentation basique, qui charge N instructions consécutives pour alimenter N décodeurs, fonctionne donc jusqu'à 4/6 décodeurs. Au-delà de 5 instructions, un CPU superscalaire doit être adapté pour gérer les branchements dans les paquets d'instruction qu'il émet. Le nombre de 5 instructions ne sort donc pas de nulle part. La première solution utilise un cache de µops, avec un ''front-end'' capable de charger/décoder 4/5 instructions à la fois, pas plus. L'idée est que la majorité des instructions proviennent du cache de µops (plus de 80%), ce qui fait qu'on peut émettre environ 10 instructions à partir du cache de µops, pas besoin d'avoir beaucoup de décodeurs. Les performances sont cependant dégradées lors d'un défaut de cache de µops. Les instructions doivent alors être chargées depuis le cache d'instruction, par paquets de 4-5, puis sont décodées par 4-5 décodeurs, ce qui ne permet pas d’alimenter les unités de calcul à plein régime. La solution précédente joue sur la distinction entre pots de décodage et d'émission. Elle permet d'émettre un grand nombre de µops sans pour autant charger un grand nombre d'instructions en même temps. En quelque sorte, ce n'est qu'une méthode de contournement, le CPU superscalaire reste de type étroit. Mais il est aussi possible d'utiliser un '''''front-end large''''', capable de charger une bonne dizaine d'instructions en même temps. La seule difficulté est de tenir compte de la présence de branchements. Pour rappel, avec un ''front-end'' étroit, les instructions situées après le premier branchement pris sont annulées, elles ne sont pas décodées ni exécutées. [[File:Fetch sur un processeur superscalaire avec prediction de branchements.png|centre|vignette|upright=2|Fetch sur un processeur superscalaire avec prédiction de branchements.]] Un ''front-end'' large charge les instructions de destination du branchement et les placent à sa suite. Ils chargent deux blocs à la fois et les fusionnent en un seul qui ne contient que les instructions présumées utiles. [[File:Cache d'instructions autoaligné.png|centre|vignette|upright=2|Cache d'instructions auto-aligné.]] Mais cela demande de charger deux blocs de mémoire en une fois, ce qui demande un cache d'instruction multiports. Il faut aussi ajouter un circuit pour assembler plusieurs morceaux de blocs en un seul : le fusionneur (''merger''). En pratique, cette solution demande d'ajouter un second port de lecture au cache, pour un cout en hardware important, avec un impact négatif sur le temps d'accès au cache. Et il faut rajouter le fusionneur, qui prend un étage de pipeline à lui tout seul. Et encore, cette solution ne gère pas le cas où un bloc contient plusieurs branchements ! [[File:Implémentation d'un cache d'instructions autoaligné.png|centre|vignette|upright=2|Implémentation d'un cache d'instructions auto-aligné.]] Les exemples précédents partent du principe qu'il n'y a qu'un seul branchement dans un bloc. Mais quand les blocs deviennent assez longs, il est fréquent d'avoir plusieurs branchements dans un bloc. Et il est même possible d'avoir plusieurs branchements pris. Dans un bloc de 8-10 instructions, ce n'est pas rare, et les CPU superscalaires à 8-10 voies sont la règle dans les CPU des années 2020. Pour gérer ce cas, il faut ajouter des ports supplémentaires au cache d'instruction et adapter le fusionneur, pour qu'il puisse fusionner trois ou quatre blocs. De plus, il faut utiliser une unité de prédiction de branchement capable de ''prédire plusieurs branchements par cycle''. ===Le décodage superscalaire large=== Le décodage des instructions n'est pas censé dépendre de l'implémentation du chargement. En théorie, une fois plusieurs blocs d'instruction fusionnés, on peut décoder les instructions en parallèle. Cependant, certains processeurs effectuent le décodage en tenant compte de la manière dont les branchements découpent les blocs. Les processeurs Atom d'Intel utilisent une solution de ce type, qui couple prédiction de branchement et décodage parallèle. Ils chargent/décodent des paquets de 3 instructions consécutives, chaque paquet étant séparé du précédent par un branchement. Pour cela, en plus d'avoir un fusionneur adapté, ils regroupent les décodeurs en ''clusters'', chacun regroupant 3 décodeurs, et pouvant décoder trois instructions consécutives. Les micro-architectures Tremont et Crestmont avaient deux ''clusters'' (6 décodeurs au total), la micro-architecture Skymont en intègre trois (9 décodeurs au total). Avec deux ''clusters'', le fonctionnement est le suivant. Si le code est composé d'une suite d'instruction sans branchements, seul le premier ''cluster'' est utilisé, limitant le décodage à trois instructions par cycle. Par contre, si l'une des trois instructions décodée est un branchement, alors le second ''cluster'' décode les trois instructions situées après ce branchement, à partir de l'adresse de destination. Avec un troisième ''cluster'', c'est la même chose, sauf qu'il faut qu'il y ait un branchement dans les trois instructions décodées par le second ''cluster''. Mais comment faire pour savoir quels décodeurs activés, quels ''clusters'' sont actifs ? C'est la prédiction de branchement qui commande. Avec deux ''clusters'', elle détermine si un branchement est présent dans les trois instructions à charger, et quelle est son adresse de destination. Sans branchement, elle prévient les décodeurs et les trois instructions chargées sont décodées dans un ''cluster'' libre. Si elle prédit un branchement, elle charge les instructions adéquates, et active les deux ''clusters''. Avec trois branchements, c'est le même mécanisme, sauf que l'unité de prédiction de branchement doit être capable de prédire deux branchements par cycle. Il faut noter que les décodeurs d'un ''cluster'' sont des décodeurs simples, sans microcode. Sur les processeurs Tremont et Crestmont, le microcode était unique et était partagé entre tous les ''clusters''. Un seul ''cluster'' pouvait décoder une instruction complexe avec le microcode, l'autre devait se limiter à des instructions simples uniquement. Depuis Skymont, le microcode est partiellement dupliqué, avec un microcode par ''cluster''. Le cout en hardware peut paraitre prohibitif, pour un gain en performance faible. Sauf que c'est oublier que ce sont des processeurs Atom, sur lesquels beaucoup d'instructions sont microcodées. Les processeurs Atom ont un budget en transistor limité, ce qui fait qu'il est préférable pour eux d'implémenter des instructions en microcode que d'utiliser des unités de calcul dédiées ou des circuits de décodage complexes. Pour limiter la casse, le microcode n'est que partiellement dupliqué. Seul le microcode pour les instructions fréquente est dupliqué. Il y a un microcode secondaire pour certaines instructions, qui est lui unique et partagé par tous les ''clusters''. ===Le contournement sur les processeurs superscalaires larges=== Pour rappel, la technique du contournement (''register bypass'') permet au résultat d'une instruction d'être immédiatement utilisable en sortie de l'ALU, avant même d'être enregistré dans les registres. Implémenter la technique du contournement demande d'utiliser des multiplexeurs pour relier la sortie de l'unité de calcul sur son entrée si besoin. Il faut aussi des comparateurs pour détecter des dépendances de données. [[File:Pipeline Bypass.png|centre|vignette|upright=1|Pipeline Bypass]] La sortie de chaque ALU doit être reliée aux entrées de toutes les autres, avec les comparateurs qui vont avec. Sur les processeurs ayant plusieurs d'unités de calculs, cela demande beaucoup d’interconnexions et de comparateurs. Pour N unités de calcul, cela demande 2 * N² interconnexions, implémentées avec 2N multiplexeurs de N entrées chacun. Si c'est faisable pour 2 ou 3 ALUs, la solution est impraticable au-delà. Non seulement ce serait un enfer à câbler, mais les fils seraient tellement longs que la fréquence du processeur en prendrait un coup. Notez que cela vaut pour les processeurs superscalaires, mais aussi pour tout processeur avec beaucoup d'unités de calcul. En théorie, un CPU à exécution dans le désordre, non-superscalaire, mais avec beaucoup d'unités de calcul, fait face au même problème. En pratique, avoir une dizaine d'ALU implique processeur superscalaire à exécution dans le désordre. D'où le fait qu'on parle du problème maintenant. La seule solution praticable est de ne pas relier toutes les unités de calcul ensemble. À la place, on préfère regrouper les unités de calcul dans différents '''agglomérats''' ('''cluster'''). Le contournement est alors possible entre les unités d'un même agglomérat, mais pas entre agglomérats différents. Évidemment, certaines possibilités de contournement sont perdues, mais c'est compensé par un gain en fréquence et un cout en circuit/fils réduit. C'est un bon compromis, ce qui explique que presque tous les processeurs modernes l'utilisent. Les rares exceptions sont les processeurs POWER 4 et POWER 5, qui ont préféré se passer de contournement pour garder un processeur très simple et une fréquence élevée. ===Le banc de registre des processeurs superscalaires larges=== Pour émettre deux opérations entières simultanément, il faut lire deux fois plus d'opérandes. Et il faut aussi écrire deux résultats dans les registres, en même temps. Pour cela, on double les ports du banc de registre. En général, il faut au maximum 2 ports de lecture et un port d'écriture par unité de calcul. Le problème, c'est que plus un banc de registres a de ports, plus il utilise de circuits, est compliqué à concevoir, consomme de courant et chauffe. Avec plusieurs dizaines d'unités de calcul différentes, ajouter autant de ports impacterait la fréquence du banc de registres et réduirait les performances, voire est totalement impraticable à cause des contraintes thermiques. Les processeurs superscalaires larges doivent donc trouver des solutions. La première solution utilise un banc de registre unique, mais n'utilise pas autant de ports que le pire des cas le demanderait. Le processeur détecte quand il n'y a pas assez de ports pour servir toutes les micro-opérations. Quand ça arrive, l'unité d'émission émet assez de micro-opérations pour exploiter les ports disponibles, mais met en attente les micro-opérations en trop, le temps que les ports se libèrent. Pour cela, le banc de registre est géré par un circuit d'arbitrage spécialisé, intégré à l'unité d'émission, l’'''arbitre du banc de registres''' (''register file arbiter''). Une autre solution est de dupliquer le banc de registres en plusieurs exemplaires qui contiennent exactement les mêmes données, mais avec moins de ports de lecture/écriture. Un exemple est celui des processeurs POWER, Alpha 21264 et Alpha 21464. Sur ces processeurs, le banc de registre est dupliqué en plusieurs exemplaires, qui contiennent exactement les mêmes données. Les écritures dans les registres sont envoyées à tous les bancs de registres, afin de garantir que leur contenu est identique. Chaque exemplaire a exactement deux ports de lecture. La conception du processeur est simplifiée, que ce soit au niveau du câblage, que de la conception des bancs de registres. Une dernière solution découpe le banc de registres en plusieurs bancs de registres plus petits, avec un réseau d'interconnexions pour échanger des données entre bancs de registres. Dans la plupart des cas, cette séparation est invisible du point de vue de l'assembleur et du langage machine. Le processeur se charge de transférer les données entre bancs de registres suivant les besoins. Sur d'autres processeurs, les transferts de données se font via une instruction spéciale, souvent appelée ''COPY''. [[File:Banc de registres distribué.png|centre|vignette|upright=2|Banc de registres distribué.]] Sur de certains processeurs, un branchement est exécuté par une unité de calcul spécialisée. Or les registres à lire pour déterminer l'adresse de destination du branchement ne sont pas forcément dans le même agglomérat que cette unité de calcul. Pour éviter cela, certains processeurs disposent d'une unité de calcul des branchements dans chaque agglomérat. Dans les cas où plusieurs unités veulent modifier le ''program counter'' en même temps, un système de contrôle général décide quelle unité a la priorité sur les autres. Mais d'autres processeurs fonctionnent autrement : seul un agglomérat possède une unité de branchement, qui peut recevoir des résultats de tests de toutes les autres unités de calcul, quel que soit l’agglomérat. ==La macro-fusion : une optimisation du décodage parallèle== Les processeurs superscalaires supportent la technique dite de '''macro-fusion''', qui permet de fusionner deux-trois instructions consécutives en une seule micro-opération. Par exemple, il est possible fusionner une instruction de test et une instruction de saut en une seule micro-opération de branchement. Il s'agit là de l'utilisation la plus importante de la macro-fusion sur les processeurs x86 modernes. La macro-fusion est utilisée pour fusionner un test et un saut en un branchement unique. L'optimisation est présente sur la plupart des CPU superscalaires modernes. Les CPU x86 l'implémentent, mais aussi quelques CPU ARM et les processeurs Apple de série M (M1 à M5, et potentiellement plus quand ils seront sortis). La fusion des instructions se fait lors du décodage des instructions, grâce à la coopération des décodeurs. Elle marche systématiquement si l'instruction de test et le saut sont consécutives, qu'elles forment bien une paire d'instruction. Par contre, insérer une instruction entre les deux empêche la macro-fusion de faire son travail, sauf si l'instruction insérée est un NOP. Les processeurs ARM64 avaient une forme spécifique de macro-fusion, utilisée pour un cas très précis : charger une constante immédiate de 32 ou 64 bits, dans un registre 64 bits. Les CPU ARM 64 bits utilisent des instructions codées sur 32 bits, qui ne peuvent encoder que des constantes immédiates de 16 bits. Pour charger une constante de 32/64 bits, il faut s'y prendre en plusieurs fois. Une première instruction MOVZ charge les 16 premiers bits, puis 2 à 4 instructions MOVK remplissent le reste. MOVZ met à zéro les 48 bits de poids fort, MOVK permet de sélectionner le doublet à modifier dans le registre (on modifie 16 bits, dont on choisit la place, le reste du registre est laissé tel quel). ARM permet de fusionner cette suite d'instructions en une seule µop MOV avec une constante immédiate. On parle alors de '''''MOVZ + MOVK fusion'''''. Une dernière utilisation, [https://www2.eecs.berkeley.edu/Pubs/TechRpts/2016/EECS-2016-130.pdf proposées pour le jeu d'instruction RISC-V], fusionnait un calcul d'adresse et une instruction LOAD/STORE en une seule instruction d'accès mémoire. Elle a été envisagée à une époque où le jeu d'instruction RISC-V ne gérait pas les modes d'adressage base + indice. L'idée était d'avoir une unité mémoire qui gérait le mode d'adressage base + indice et de fusionner des instructions pour l'exploiter. Mais de nos jours, le jeu d'instruction RISC-V intègre le mode d'adressage base + indice, ce qui fait que la macro-fusion n'a aucun intérêt pour ce cas précis. La macro-fusion a de nombreuses limitations en pratique. En théorie, on pourrait l'utiliser au-delà des deux situations mentionnées plus haut. Par exemple, pour fusionner une addition et une multiplication en une seule instruction MAD. Mais l'intérêt est assez faible. Notons que la macro-fusion ne permet que de fusionner des paires d'instructions. Aller au-delà demanderait un cout en circuit trop important. Déjà que tester deux paires utilise pas mal de comparateurs... ==Le ''stack engine'' : une optimisation de la pile d'appel== Les processeurs modernes intègrent une optimisation liée au pointeur de pile. Pour rappel, sur les architectures modernes, le pointeur de pile est un registre utilisé pour gérer la pile d'appel, précisément pour savoir où se trouve le sommet de la pile. Il est manipulé par les instructions CALL, RET, PUSH et POP, mais aussi par les instructions d'addition/soustraction pour gérer des cadres de pile de taille variable. De plus, il peut servir d'opérande pour des instructions LOAD/STORE, afin de lire/écrire des variables locales, les arguments d'une fonction, et autres. C'est donc un registre général adressable, intégré au banc de registre, altéré par l'unité de calcul entière. L'incrémentation/décrémentation du pointeur de pile passe donc par l'unité de calcul, lors des instructions CALL, RET, PUSH et POP. Mais, l'optimisation que nous allons voir permet d'incrémenter/décrémenter le pointeur de pile sans passer par l'ALU, ou presque. L'idée est de s'inspirer des architectures avec une pile d'adresse de retour, qui intègrent le pointeur de pile dans le séquenceur et l'incrémentent avec un incrémenteur dédié. ===Le ''stack engine''=== L'optimisation que nous allons voir utilise un '''''stack engine''''' intégré à l'unité de contrôle, au séquenceur. Le processeur contient toujours un pointeur de pile dans le banc de registre, cela ne change pas. Par contre, il n'est pas incrémenté/décrémenté à chaque instruction CALL, RET, PUSH, POP. Un compteur intégré au séquenceur est incrémenté à la place, nous l’appellerons le '''compteur delta'''. La vraie valeur du pointeur de pile s'obtient en additionnant le compteur delta avec le registre dans le banc de registre. Précisons que si le compteur delta vaut zéro, la vraie valeur est dans le banc de registre et peut s'utiliser telle quelle. Lorsqu'une instruction ADD/SUB/LOAD/STORE utilise le pointeur de pile comme opérande, elle a besoin de la vraie valeur. Si elle n'est pas dans le banc de registre, le séquenceur déclenche l'addition compteur-registre pour calculer la vraie valeur. Finalement, le banc de registre contient alors la bonne valeur et l'instruction peut s'exécuter sans encombre. L'idée est que le pointeur de pile est généralement altéré par une série d'instruction PUSH/POP consécutives, puis des instructions LOAD/STORE/ADD/SUB utilisent le pointeur de pile final comme opérande. En clair, une bonne partie des incrémentations/décrémentation est accumulée dans le compteur delta, puis la vraie valeur est calculée une fois pour toutes et est utilisée comme opérande. On accumule un delta dans le compteur delta, et ce compteur delta est additionné quand nécessaire. Précisons que le compteur delta est placé juste après le décodeur d'instruction, avant même le cache de micro-opération, l'unité de renommage et l'unité d'émission. Ainsi, les incrémentations/décrémentations du pointeur de pile disparaissent dès l'unité de décodage. Elles ne prennent pas de place dans le cache de micro-opération, ni dans la fenêtre d'instruction, ni dans la suite du pipeline. De nombreuses ressources sont économisées dans le ''front-end''. Mais utiliser un ''stack engine'' a aussi de nombreux avantages au niveau du chemin de données/''back-end''. Déjà, sur les processeurs à exécution dans le désordre, cela libère une unité de calcul qui peut être utilisée pour faire d'autres calculs. Ensuite, le compteur delta mémorise un delta assez court, de 8 bits sur le processeur Pentium M, un peu plus pour les suivants. L'incrémentation se fait donc via un incrémenteur 8 bits, pas une grosse ALU 32/64 bits. Il y a un gain en termes de consommation d'énergie, un incrémenteur 8 bits étant moins gourmand qu'une grosse ALU 32/64 bits. ===Les points de synchronisation du delta=== La technique ne fonctionne que si la vraie valeur du pointeur de pile est calculée au bon moment, avant chaque utilisation pertinente. Il y a donc des '''points de synchronisation''' qui forcent le calcul de la vraie valeur. Plus haut, nous avions dit que c'était à chaque fois qu'une instruction adresse le pointeur de pile explicitement, qui l'utilise comme opérande. Les instructions CALL, RET, PUSH et POP ne sont pas concernées par elles utilisent le pointeur de pile de manière implicite et ne font que l'incrémenter/décrémenter. Mais dans les faits, c'est plus compliqué. D'autres situations peuvent forcer une synchronisation, notamment un débordement du compteur delta. Le compteur delta est généralement un compteur de 8 bits, ce qui fait qu'il peut déborder. En cas de débordement du compteur, le séquenceur déclenche le calcul de la vraie valeur, puis réinitialise le compteur delta. La vraie valeur est donc calculée en avance dans ce cas précis. Précisons qu'un compteur delta de 8 bits permet de gérer environ 30 instructions PUSH/POP consécutives, ce qui rend les débordements de compteur delta assez peu fréquent. À noter que si le compteur delta vaut zéro, il n'y a pas besoin de calculer la vraie valeur, le séquenceur prend cette situation en compte. Un autre point de synchronisation est celui des interruptions et exceptions matérielles. Il faut que le compteur delta soit sauvegardé lors d'une interruption et restauré quand elle se termine. Idem lors d'une commutation de contexte, quand on passe d'un programme à un autre. Pour cela, le processeur peut déclencher le calcul de la vraie valeur lors d'une interruption, avant de sauvegarder les registres. Pour cela, le processeur intègre un mécanisme de sauvegarde automatique, qui mémorise la valeur de ce compteur dans le tampon de réordonnancement, pour forcer le calcul de la vraie valeur en cas de problème. La technique du ''stack engine'' est apparue sur les processeurs Pentium M d'Intel et les processeurs K10 d'AMD, et a été conservée sur tous les modèles ultérieurs. L'implémentation est cependant différente selon les processeurs, bien qu'on n'en connaisse pas les détails et que l'on doive se contenter des résultats de micro-benchmarks et des détails fournit par Intel et AMD. Selon certaines sources, dont les manuels d'optimisation d'Agner Fog, les processeurs AMD auraient moins de points de synchronisation que les processeurs Intel. De plus, leur ''stack engine'' serait placé plus loin que prévu dans le pipeline, après la file de micro-opération. ==La micro-fusion et la délamination== La '''micro-fusion''' est une optimisation qui retarde le décodage réel de certaines instructions assez loin dans le pipeline. Elle concerne surtout les instructions ''load-op'', mais pas que. D'autres instructions mémoire peuvent subir cette optimisation. L'essentiel est que, sans micro-fusion, ces instructions sont décodées en plusieurs micro-opérations. La micro-fusion retarde le décodage final de ces instructions, qui se fait assez tard dans le pipeline. Pour comprendre l'intérêt, nous allons devoir faire quelques explications. L'optimisation en question fait le décodage en deux temps. Prenons l'exemple d'une opération ''load-op'', qui est censée être décodée en deux micro-opérations : une lecture, puis une opération. Le décodage produit une '''macro-opération''' (terme d'Intel), qui est propagée dans le pipeline, pour être scindée en deux micro-opérations un peu avant les ALU et l'unité mémoire. La macro-opération est techniquement une micro-opération comme une autre, sauf que ce n'est pas la micro-opération finale. L'unité d'émission découpe cette macro-opération en deux micro-opérations, qui sont émises l'une après l'autre. Pour résumer, les instructions ''load-op'' ne sont complétement découpées en micro-opération lors de l'émission, pas lors du décodage. L'optimisation impacte ce qui se situe entre le décodeur d'instruction et l'unité d'émission : l'unité de renommage, la file de micro-opérations, le ''Loop Stream Detector'' et/ou le cache de micro-opération. L'intérêt de l'optimisation est que l'on fusionne deux micro-opérations en une seule macro-opération. Le cache de micro-opération et la file de micro-opération peuvent mémoriser plus de micro-opérations, vu que certaines sont fusionnées. Quant à l'unité de renommage, elle renomme une seule macro-opération au lieu de renommer deux micro-opérations, ce qui augmente le débit du renommage de registre. Par contre, cela complique un peu le travail de l'unité d'émission, qui doit découper les instructions ''load-op'' en deux et émettre les micro-opérations l'une après l'autre. Il faut noter qu'il n'y a pas de fusion proprement dite. Le décodeur ne décode pas deux micro-opérations séparées, avant de les fusionner. En réalité, il génère directement une macro-opération, qui correspond à deux micro-opérations, que ce soit plus tard dans le pipeline ou sur une autre architecture. D'ailleurs, la micro-fusion a lieu à l'intérieur d'une instruction, qui est censée être traduite en plusieurs micro-opérations. Et encore : pas toutes. La conséquence est que la technique n'a de sens que sur les processeurs CISC, avec des instructions assez complexes pour être décodées en plusieurs micro-opérations. En pratique, seuls les CPU x86 sont concernés. ===Les domaines de fusion=== La micro-fusion n'est pas spécifique aux processeurs superscalaires. En théorie, on peut l'implémenter sur un processeur non-superscalaire à émission dans l'ordre. Cependant, je ne connais aucun processeur à émission dans l'ordre qui implémente la micro-fusion, même si son implémentation est en théorie possible sur de tels processeurs. La micro-fusion est implémentée différemment selon que l'émission se fasse dans l'ordre, dans le désordre, avec une fenêtre d'instruction, avec des stations de réservation, etc. J'ai dit plus haut que les micro-opérations sont générées lors de l'émission, mais le moment exact varie suivant le processeur. Pour simplifier le tout, nous parlerons de '''scission''' quand le processeur traduit une macro-opération en deux/trois micro-opérations. Le moment où a lieu la scission est théoriquement dans l'unité d'émission, mais celle-ci prend plusieurs formes selon que le processeur utilise l'émission dans l'ordre, dans le désordre, avec une fenêtre d'instruction, avec des stations de réservation, etc. Les processeurs à exécution dans l'ordre sont le cas le plus simple. L'unité d'émission est alors en charge du découpage des macro-opérations en micro-opérations. La file de micro-opération ou le cache de micro-opérations mémorisent des macro-opérations. À l'opposé, le tampon de ré-ordonnancement mémorise lui des micro-opérations. Le reste du processeur n'est pas affecté. Sur les processeurs à exécution dans le désordre, les différences sont nombreuses. Le tampon de ré-ordonnancement peut mémoriser soit les micro-opérations finales, soit des macro-opérations. Il en est de même avec la fenêtre d'instruction et/ou les stations de réservation, qu'elles soient centralisées ou décentralisées. Tout dépend de comment se fait l'implémentation. Une implémentation simple effectue la scission des macro-opérations dans l'unité d'émission, comme sur les processeurs à exécution dans l'ordre. Sur de nombreux processeurs, l'unité d'émission et de renommage sont simplement fusionnées, ce qui fait que la scission a lieu juste après le renommage de registres. En conséquence, le tampon de re-ordonnancement et les fenêtres d'instruction ne voient que des micro-opérations. Les macro-opérations disparaissent juste avant. Cette implémentation est préférée avec une fenêtre d'instruction centralisée, car on voit mal comment scinder une macro-opération juste avant l'ALU. Une autre implémentation fait la scission en sortie de la fenêtre d'instruction ou des stations de réservation, qu'elles soient centralisées ou décentralisées. Dans ce cas, le tampon de re-ordonnancement fait de même. L'avantage est que le tampon de ré-ordonnancement stocke des macro-opérations, ce qui augmente sa taille effective. Par exemple, il utilise une seule entrée pour une instruction ''load-op'', au lieu de deux sans micro-fusion de ce type. ===La délamination=== La scission peut parfois avoir lieu plus tôt, dans des circonstances très précises. Concrètement, cela ne concerne que quelques micro-architectures d'Intel, pour des instructions très précises. La raison est les limitations du format des micro-opérations, pas une histoire d'optimisation. Le processeur aimerait pouvoir faire la micro-fusion, mais ne peut pas à cause de ces limitations. Une telle situation cause alors une '''délamination''' : les micro-opérations sont scindées précocement. Par exemple, sur la micro-architecture SandyBridge d'Intel, les opérations ''load-op'' sont fusionnées, sauf si elles utilisent l'adressage indicé. Les micro-opérations de ce processeur ne peuvent avoir que deux opérandes. Trois opérandes est de trop, et les opérations ''load-op'' en adressage indicé dépassent cette limite. Dans ce cas, les micro-opérations sont fusionnées en sortie du décodeur, mais sont scindés avant d’entrer dans la file de micro-opération. L'avantage n'est pas évident, vu que l'unité de renommage suit de près le décodeur d'instruction. Il est que le cache de micro-opérations mémorise des macro-opérations, pas deux micro-opérations. ===La micro-fusion des processeurs Intel et AMD=== La micro-fusion est apparue sur le Pentium M d'Intel, et a été conservée telle quelle sur les processeurs Intel suivants. Les processeurs AMD supportent la micro-fusion depuis au moins la micro-architecture AMD K8. Mieux que ça, la délamination n'était pas utilisée car les micro-opérations acceptaient plus de 2 opérandes. Que ce soit chez AMD ou Intel, elle concerne deux types d'instructions : les écritures mémoire et les instructions ''load-op''. Sur le Pentium M, il y avait quelques limitations, l'optimisation se limitant par exemple aux registres entiers et flottants, mais pas aux registres MMX, mais pas les registres SSE (nous verrons ces registres dans quelques chapitres). Mais les processeurs modernes n'ont plus cette limitation. La micro-fusion des CPU Intel concerne deux types d'instructions : les écritures mémoire et les instructions ''load-op''. Pour les écritures, la situation est plus complexe que pour les instructions ''load-op''. Sur les processeurs Intel précédents, les écritures étaient réalisées dans deux unités séparées : le calcul d'adresse dans une unité de calcul, l'écriture proprement dite dans l'unité mémoire. Elles prenaient donc deux micro-opérations. Le processeur a changé ses unités mémoire, ce qui fait qu'elle pouvait faire les deux en une seule micro-opération. Il s'agissait alors d'une optimisation du chemin de données, plus que de la micro-fusion proprement dite, mais je la mentionne ici malgré tout. La micro-fusion sur ce processeur avait un avantage en plus des avantages usuels : elle utilisait mieux les décodeurs. Les Pentium M avaient trois décodeurs pour décoder plusieurs instructions en même temps, chose qu'on détaillera dans le chapitre sur les processeurs superscalaires. Un seul décodeur pouvait décoder toutes les instructions, les deux autres ne peuvent décoder que les instructions simples, qui se décodent en une seule micro-opération. Avec la micro-fusion, les trois décodeurs peuvent gérer les écritures mémoire et les opérations ''load-op'', vu qu'elles deviennent des instructions simples. Sans cela, elles auraient été limitées au premier décodeur. <noinclude> {{NavChapitre | book=Fonctionnement d'un ordinateur | prev=Le parallélisme mémoire | prevText=Le parallélisme mémoire | next=Exemples de microarchitectures CPU : le cas du x86 | nextText=Exemples de microarchitectures CPU : le cas du x86 }} </noinclude> h98lwmfulq7ebm1g1awv8f4kjf5qsyo 773254 773253 2026-09-26T20:25:50Z Mewtow 31375 /* La superscalarité à fenêtres d'instruction décentralisées */ 773254 wikitext text/x-wiki Les processeurs vus auparavant ne peuvent émettre au maximum qu'une instruction par cycle d'horloge. Ils peuvent avoir plusieurs instructions qui s'exécutent en même temps, dans des unités de calcul séparées. C'est le cas dès qu'une instruction multi-cycles s'exécute. Par contre, ils ne peuvent démarrer qu'une seule instruction par cycle. Et quand on court après la performance, ce n'est pas assez ! Les concepteurs de processeurs ont inventés des processeurs qui émettent plusieurs instructions par cycle : les '''processeurs à émissions multiples'''. Un processeur à émission multiple charge plusieurs instructions en même temps, les décode en parallèle, puis les émet en même temps sur des unités de calculs séparées. Pour cela, il faut gérer les dépendances entre instructions, répartir les instructions sur différentes unités de calcul, et cela n'est pas une mince affaire. En général, pour un processeur à émission multiple capable d'émettre N instructions en parallèle, on parle de '''processeur à N voies''', ou encore un ''processeur à N pipelines''. [[File:Superscalarpipeline.svg|centre|vignette|upright=1.5|Pipeline RISC classique à cinq étages sur un processeur superscalaire. On voit bien que plusieurs instructions sont chargées en même temps.]] Les processeurs à émission multiple sont de deux types : les processeurs VLIW et les '''processeurs superscalaires'''. Nous mettons les processeurs VLIW de côté en attendant le prochain chapitre. La raison est qu'ils ont un jeu d'instruction spécialisé qui expose le parallélisme d'instruction directement au niveau du jeu d'instructions, là où les processeurs superscalaires restent des processeurs au jeu d'instruction normal. La différence principale entre processeur VLIW et superscalaire est qu'un processeur superscalaire répartit les instructions sur les unités de calcul à l’exécution, là où un processeur VLIW délègue cette tâche au compilateur. ==Le nombre de voies d'un processeur superscalaire== Les processeurs superscalaires les plus simples ne permettent que d'émettre deux micro-opérations à la fois, d'où leur nom de processeur ''dual issue'', ce qui se traduit en '''processeur à double émission'''. Les processeurs triple émission permettent eux d’émettre trois instructions à la fois. A l'opposé, les CPU superscalaires les plus puissants peuvent émettre jusqu'à 8-10 instructions simultanément. En 2026, les CPU superscalaires peuvent monter jusqu’à 10 µops émises en même temps, ce qui est énorme ! Et cela nous amène à faire une distinction entre les processeurs superscalaires dits '''étroits''' et ceux dits '''larges'''. Dans ce wikilivre, on part du principe que les processeurs superscalaires étroits émettent jusqu'à 4-5 µops à la fois, alors que les larges peuvent en émettre plus, l'intervalle entre les deux est une zone grise. Le nombre exact n'est pas fixé dans le marbre, la distinction est volontairement vague. Nous parlerons de la '''largeur d'un CPU superscalaire''' pour parler du nombre de voies, du nombre de µops émises en même temps. Plus un CPU superscalaire est large, plus il est performant ''toute chose égale par ailleurs''. Mais dans les faits, vous pouvez oublier la partie en italique. Élargir un CPU superscalaire demande de rajouter beaucoup de circuits. Et ce cout en circuit aura un impact sur la performance. A budget en transistors limité, il y a un compromis entre élargir un CPU et rajouter du cache ou la taille des fenêtres d'instructions/ROB et autres. ===Les contraintes d’appariement=== Prenons un processeur double émission, capable d'émettre deux instructions simultanément. Émettre deux instructions en même temps n'est pas toujours possible. Par exemple, si deux instructions sont dépendantes, on ne peut pas les émettre en même temps. Mais oublions un instant ces histoires de dépendances de données, et partons du principe que le CPU ait affaire à deux instructions indépendantes, une paire idéale. Un CPU double émission "parfait" pourrait exécuter n'importe quelle paire d'instruction. Par exemple, il pourrait émettre deux multiplications consécutives, deux lectures consécutives, une lecture et une écriture en même temps, etc. En pratique, aucun processeur ne permet cela. Implémenter un CPU superscalaire "parfait" aurait un cout en circuit beaucoup trop conséquent. Par exemple, pour émettre deux multiplications en même temps, il faudrait deux multiplieurs séparés. Idem pour exécuter deux accès mémoire simultané : cela demande d'avoir deux unités mémoire et un cache multiport. Mais en pratique, aucun CPU superscalaire ne fait cela. La totalité des CPU double émission se débrouille avec un seul circuit multiplieur et une seule unité mémoire. Et cela entraine l'apparition de dépendances structurelles, qui font que certaines paires d'instructions sont impossibles. Par exemple, impossible d'émettre deux multiplications en même temps avec un seul circuit multiplieur. Mais émettre une multiplication en parallèle d'une addition est parfaitement possible. Il y a donc des paires d'instructions autorisées et des paires interdites. Et la logique est la même sur les CPU à triple ou quadruple émission, et au-delà. : des triplets ou quadruplet d'instructions sont impossibles à émettre simultanément, alors que d'autres le sont. Pour le dire autrement, il y a des '''contraintes d'appariement''' sur les instructions à émettre en même temps. ===L'impact des contraintes d’appariement sur la performance=== Ces contraintes d’appariement ont un impact sur les performance, mais celui-ci peut être grandement mitigé. Les programmes informatiques utilisent en pratique beaucoup plus d'opérations entières de base que de multiplication, branchements et accès mémoire. Il y a environ 5 opérations entières de base pour une multiplication entière. En pratique, il est rare de devoir exécuter deux multiplications à la fois. Idem pour les décalages et rotations, qui sont assez éloignés les uns des autres. Deux branchements consécutifs sont quant à eux plus fréquents. Deux accès mémoire consécutifs est assez rare, mais ils restent assez proches les uns des autres malgré tout. La conséquence est que certaines paires d'instructions sont privilégiées par rapport au reste. Les paires regroupant deux calculs basiques (additions, soustractions, ...) sont systématiquement supportées, car dupliquer des ALU entières est facile. Les paires regroupant un calcul et une autre instruction sont elles aussi systématiquement supportées. Par contre, les paires avec deux branchements, deux accès mémoire, deux décalages/rotations, ou deux multiplications/divisions sont fréquemment interdites. Seuls les CPU superscalaires larges peuvent les permettre, et encore : sous conditions. Le résultat est que le processeur peut se débrouiller avec une seule unité mémoire et une unité de branchement. ===Terminologie pour la suite du chapitre=== Décrire les contraintes d’appariement demande de dire quelles combinaisons d’instructions sont autorisées ou interdites. Et pour cela, nous allons introduire les abréviations suivantes : * INT est un raccourcis pour parler d'une opération entière simple, qu'une ALU entière peut exécuter. * MUL correspond à une opération entière complexe, typiquement une multiplication, éventuellement à une division. * FLOAT désigne une opération flottante, peut importe laquelle. * MEM désigne un accès mémoire, à savoir une instruction ou µop mémoire. * BRANCH désigne une instruction de branchement. Cette terminologie permettra de définir des paires d'instructions, qu'elles soient possibles ou interdites. Par exemple, l'expression INT + FLOAT désigne une paire d'instruction regroupant une instruction entière et une instruction flottante, peu importe l'ordre des deux instructions. Il en sera de même pour les triplets, quadruplets, ou plus. Par exemple, l'expression INT + INT + FLOAT + MUL désigne un quadruplet d'instructions regroupant : deux instructions entières, une instruction flottante, et une multiplication entière. J'utiliserais aussi des parenthèses pour regrouper certaines combinaisons. Par exemple, l'expression INT + (MUL /FLOAT) veut parler d'une paire d'instruction où la première est une opération entière basique, la seconde est : soit une multiplication, soit une opération flottante. ==Le ''front-end'' des processeurs superscalaires== Un processeur superscalaire doit charger, décoder, et exécuter plusieurs instructions par cycle. Par exemple, un processeur triple émission doit charger 3 instructions lors d'un premier cycle, puis les décoder au cycle suivant, et les exécuter au cycle suivant. Il travaille donc pas paquets de 3 instructions, dans cet exemple. Intuitivement, charger, décoder et exécuter plusieurs instructions à la fois demande de dupliquer des circuits. Par exemple, il faut dupliquer les décodeurs pour décoder plusieurs instructions. Les unités de calcul sont aussi dupliquées, sauf sur certaines processeurs qui arrivent à ruser, mais laissons cela de côté pour plus tard. Mais d'autres circuits sont simplement modifiés, comme l'unité de chargement ou l'unité d'émission. Voyons ce qu'il en est dans le détail. {|class="wikitable" |- ! rowspan="2" | Processeur sans émission multiple | rowspan="2" | Chargement | rowspan="2" | Décodage | rowspan="2" | Renommage | rowspan="2" | Émission | Exécution / ALU | rowspan="2" | ''Commit''/ROB |- | Exécution / ALU |- ! rowspan="4" | Processeur superscalaire | rowspan="4" | Chargement | rowspan="2" | Décodage | rowspan="4" | Renommage | rowspan="4" | Émission | Exécution / ALU | rowspan="4" | ''Commit''/ROB |- | Exécution / ALU |- | rowspan="2" | Décodage | Exécution / ALU |- | Exécution / ALU |} Nous allons d'abord voir ce qu'il en est pour le ''front-end'', à savoir la partie du CPU qui s'occupe de charger et décoder les instructions. Elle regroupe l'unité de chargement, les décodeurs, l'unité de prédiction de branchement, la file de µops, la file d'instruction, et divers caches. En soi, les changements portent surtout sur l'unité de chargement et les décodeurs. ===Le chargement de plusieurs instructions consécutives=== Un processeur superscalaire charge plusieurs instructions en même temps, ''en tenant compte de la présence de branchements''. La partie en italique est importante et elle a une conséquence très forte : la manière de faire est fortement différente entre un CPU superscalaire étroit et large. Un CPU superscalaire étroit fait au plus simple : il charge un bloc de 2, 3, 4 instructions consécutives en mémoire RAM. Il faut alors doubler/tripler/quadrupler la taille du bus connecté au cache d'instruction. Par exemple, pour charger quatre instructions de 32 bits chacune, il suffit de lire un bloc de 128 bits dans le cache d'instruction. Un bloc de 8, 16, 32 octets est donc lu depuis le cache et est ensuite découpé en instructions, envoyées chacun à un décodeur. Charger plusieurs instructions consécutives ne marche bien qu'en absence de branchements. La présence de branchements casse cette dynamique, et il faut gérer leur présence. * Sans prédiction de branchement, toutes les instructions d'un bloc sont décodées et exécutées en même temps, même s'il y a un branchement dans le tas. * Avec prédiction de branchement, les instructions situées après le branchement ne sont pas décodées ni exécutées. L'unité de chargement coupe le bloc chargé au niveau du premier branchement pris, remplit les vides avec des NOP, avant d'envoyer le tout à l'unité de décodage. Les instructions à l'adresse de destination seront chargées au cycle suivant, en chargeant un nouveau bloc. [[File:Fetch sur un processeur superscalaire avec prediction de branchements.png|centre|vignette|upright=2|Fetch sur un processeur superscalaire avec prédiction de branchements.]] Un autre défaut est que sur le CPU charge un bloc de 128 bits à la fois, les instructions doivent être alignées sur 128 bits. Et on peut généraliser à n'importe quelle taille de bloc. Si les instructions ne sont pas alignées, il faut gérer la situation. Il arrive qu'une instruction soit à cheval sur deux blocs, avec un morceau dans le premier bloc, un autre dans le second. Dans ce cas, l'instruction est chargée en deux fois, et des circuits doivent gérer la situation pour recoller les morceaux. Sur les CPU avec des instructions de taille fixe, le problème ne survient que rarement. Mais il est fréquent avec des instructions de taille variable. Un autre défaut est lié aux branchements. Imaginez qu'un branchement s'exécute et branche vers une instruction de destination. Rien n'indique que l'instruction de destination sera au tout début d'un bloc. Si ça se trouve, elle sera en troisième position d'un bloc de 4 instructions. Une solution simple charge le bloc en respectant l'alignement, et les deux premières instructions du bloc ne sont pas à exécuter. Il y a donc une petite perte de performance. Certains CPU, comme l'IBM RS/6000, ont la machinerie charger un bloc de N instructions même s'il n'est pas aligné. La machinerie en question se trouve dans le cache d'instruction et est tout sauf simple à expliquer,ce qui fait que je me permets de la passer sous silence. Un détail, cependant : elle ne permet pas de charger des blocs qui sont à cheval sur deux lignes de cache. ===Le décodage parallèle des instructions=== Pour le décodage des instructions, plusieurs instructions sont décodées en même temps. On parle alors de '''décodage parallèle'''. Pour ce faire, un processeur superscalaire contient plusieurs décodeurs, chacun pouvant décoder une instruction en parallèle des autres. Prenons par exemple un processeur superscalaire à 4 voies : le décodage se fera dans quatre décodeurs séparés, qui fonctionneront en parallèle. Cependant, cette implémentation ne marche bien que pour les processeurs RISC, les processeurs CISC devant faire avec un microcode gourmand en circuits. Les processeurs CISC utilisent des décodeurs hybrides, avec un microcode qui complémente un décodeur câblé. Dupliquer le microcode aurait un cout en transistors trop important, ce qui fait que seuls les décodeurs câblés sont dupliqués. Les CPU CISC superscalaires disposent donc de plusieurs décodeurs simples, capables de décoder les instructions les plus courantes, avec un seul microcode. La conséquence est qu'il n'est pas possible de décoder deux instructions microcodées en même temps. Par contre, il reste possible de décoder plusieurs instructions non-microcodées ou une instruction microcodée couplée à une instruction non-microcodée. Vu qu'il est rare que deux instructions microcodées se suivent dans un programme, le cout en performance est extrêmement mineur. Il faut noter qu'une instruction correspond à une ou plusieurs µops. Il est donc possible de charger 5 instructions et de se retrouver avec 8 µops en sortie des décodeurs. Et c'est la source d'une distinction très importante. Plus haut, j'ai parlé de processeurs superscalaires à N voies. La terminologie peut avoir deux interprétations. La première est que le processeur charge/décode N instructions à la fois. Une seconde est qu'il est capable d'exécuter/émettre N µops en même temps. Les deux ne sont pas équivalentes, vu qu'un processeur superscalaire peut, par exemple, charger/décoder N instructions et émettre/exécuter N+2 µops. La distinction est particulièrement importante sur les processeurs superscalaires larges. Certains d'entre eux sont capables de charger 5 instructions et d'en émettre le double ! Un exemple classique était les processeurs Athlon d'AMD, qui pouvaient décoder deux instructions par cycle, mais émettre 4 µops par cycle. Ce n'était pas un problème, car beaucoup d'instructions, même basiques, étaient décodées en 2 µops. ===L'émission parallèle des instructions=== La superscalarité modifie en profondeur l'unité d'émission, notamment au niveau de son interface. Avant, elle recevait une micro-opération sur son entrée, et fournissait la micro-opération émise sur une sortie. Et cela vaut aussi bien pour une unité d'émission simple, un ''scoreboard'', une fenêtre d'instruction ou des stations de réservation. Mais avec l'émission multiple, les sorties et entrées sont dupliquées. Pour la double émission, il y a deux entrées vu qu'elle doit recevoir deux micro-opération décodées/renommées, et deux sorties pour émettre deux micro-opérations. Dans ce qui suit, nous ne parlerons pas d'entrée et de sortie, mais de '''ports de décodage''' et de '''ports d'émission'''. Les ports de décodage reçoivent des micro-opérations provenant de l'unité de décodage, les ports d'émissions sont pour les micro-opérations émises. Les ports d'émission injectent les micro-opérations émises dans le pipeline, elle sont propagée au banc de registre, aux ALUs, etc. Un processeur superscalaire a forcément plusieurs ports d'émission (et de décodage). Si l'interface de l'unité d'émission change, l'intérieur est tout autant modifié. Sur un CPU non-superscalaire, l'unité d'émission reçoit une µop "candidate à l'émission", et détecte les dépendances entre avec les µops en vol, en cours d'exécution dans le pipeline. Et c'est toujours le cas sur les processeurs superscalaires : l'unité d'émission détecte les dépendances entre les µops candidates et les µops en vol. Mais avec la siperscalarité, elle doit en plus tester les dépendances entre les µops candidates. Si l'unité d'émission est un ''scoreboard'', il doit détecter les dépendances entre instructions émises simultanément et les sérialiser si besoin. Avec une fenêtre d'instruction, il n'y a pour ainsi dire rien à faire, vu que les dépendances sont éliminées par le renommage de registre et que les signaux de réveil gèrent les dépendances RAW. C'est la raison pour laquelle les processeurs superscalaires modernes n'utilisent pas de ''scoreboard''. ==Les processeurs implémentés avec plusieurs pipelines== Nous venons de voir comment la superscalarité impacte le ''front-end'', il est maintenant de voir ce qu'il en est de l'unité d'émission et des ''back-ends''. Un processeur superscalaire peut utiliser l'exécution dans le désordre et le renommage de registres, mais ce n'est pas une obligation. Historiquement, les premiers processeurs superscalaires n'utilisaient pas d'exécution dans le désordre. Le meilleur exemple est celui du Pentium 1 et 2 d'Intel. Ils étaient tous deux des processeurs superscalaires, mais n'avaient pas d'exécution dans le désordre, qui est apparue sur le Pentium 3. les explications sont plus simples si on met de côté l'exécution dans le désordre. Vous vous dites que l'implémentation d'un processeur superscalaire n'est pas la même suivant que le CPU utilise l'exécution dans le désordre, ou non. Mais dans les faits, la distinction est plus subtile que ca. Pour l'expliquer, je vais utiliser une classification originale, qui sépare les processeurs superscalaires en trois sous-types. La distinction se fait sur la manière dont la superscalarité est implémentée en matériel.La classification que j'utilise distingue : * Les processeurs avec plusieurs pipelines, sans exécution dans le désordre. * Les processeurs superscalaires avec un pipeline dynamique. * Les processeurs avec des fenêtres d'instruction décentralisées. La distinction n'est pas qu'une question d'implémentation matérielle. Elle impacte aussi le ratio entre instructions chargées/décodées et instructions émises. Vous vous dites que les deux sont égales, mais ce n'est le cas que sur les processeurs avec N pipelines ! Sur les autres processeurs, il est possible de décoder 4 instructions, mais d'en émettre maximum 6 par cycle ! Et sur les CPU avec des fenêtres d'instruction décentralisées, il y a aussi une différence entre le nombre d'instructions qui sont ''dispatch'' et ''schedule''. La catégorie la plus simple est celle des '''processeurs avec plusieurs pipelines'''. Ce sont des processeurs superscalaires sans exécution dans le désordre. Ils partent d'un processeur non-superscalaire, et ajoutent un second pipeline, en sortie de l'unité d'émission. Le second pipeline permet l'émission parallèle d'une seconde instruction. Le second pipeline n'est pas identique au pipeline originel, sur plusieurs points. Premièrement, il n'a pas exactement les mêmes circuits. Le second pipeline est généralement composé d'une unité de calcul, d'une FPU, éventuellement d'un second banc de registres, guère plus. Il permet d'exécuter une seconde opération de calcul, pas plus, ce qui est à l'origine de contraintes d'appariemment assez fortes. De plus, il n'a pas forcément la même longueur, il peut avoir des étages en moins ou en plus. Il s'agit de la forme la plus simple de processeur superscalaire et nous allons leur consacrer cette section. ===La double émission entière-flottante=== L'exemple le plus simple utilise un second pipeline pour les opérations flottantes, séparé du pipeline originel. L'ajout d'un pipeline flottant permet d'émettre une micro-opération entière en même temps qu'une micro-opération flottante, ce qui s'appelle la '''double émission entière-flottante'''. La micro-opération entière s'exécute dans l'ALU entière, la micro-opération flottante dans la FPU. Les exemples les plus notables sont le PA-RISC 7100, le POWER 1 (RIOS .& et .9) et le PowerPC 601. L'Apollo DN10000 et l'Intel i860 utilisaient aussi cette forme de double émission, mais étaient des prototypes de CPU VLIW. [[File:Double émission entière-flottante.png|centre|vignette|upright=2|Double émission entière-flottante]] Pour bien expliquer les choses, prenons le processeur illustré ci-dessous, avec deux ''back-end'' séparés : un pour la FPU, et un autre pour le reste. Les instructions entières et mémoire passent toutes par le ''back-end'' généraliste. [[File:CPU non-superscalaire basique, avec une FPU.png|centre|vignette|upright=1.5|CPU non-superscalaire basique, avec une FPU]] Les deux ''back-end'' sont des pipelines séparés, mais il faut que l'unité d'émission soit spécialement conçue pour en profiter. Un processeur simple émission peut démarrer une seule µop par cycle : soit la µop est envoyée au ''back-end'' flottant, soit elle est envoyée au ''back-end'' général. Avec la double émission entière-flottante, le processeur peut envoyer une µop dans chaque pipeline, à chaque cycle. Vu que la FPU et son banc de registre forment déjà un pipeline séparé, les seules modifications se font au niveau du ''front-end'', unité d'émission inclue. {| |- |[[File:CPU simple émission avec une FPU.png|centre|vignette|upright=1.5|CPU simple émission avec une FPU.]] |[[File:Double émission entière flottante avec un second pipeline.png|centre|vignette|upright=1.5|CPU avec un second pipeline flottant.]] |} Notez que le terme double émission ''entière''-flottante est un peu trompeur. En réalité, la technique permet d'émettre une instruction flottante avec n'importe quelle autre instruction non-flottante, avec des restrictions pour les branchements. Il est par exemple possible d'exécuter un calcul flottant en parallèle d'un accès mémoire. Sur les CPU 32 bits, il est possible de faire les multiplications dans la FPU. Pour rappel, la FPU contient un multiplieur entier 53-54 bits, pour multiplier les mantisses de deux flottants 64 bits, qu'on peut utiliser pour faire les multiplications entières 32 bits. Cela permet d'émettre une multiplication en même temps qu'une opération entière dans l'ALU. Ainsi, le CPU pouvait émettre deux instructions : une opération entière de base, avec soit une multiplication, soit une opération flottante. En clair, une émission de type INT + (MUL /FLOAT). Par contre, on perd la possibilité d'émettre une multiplication entière avec une opération flottante, mais c'était un compromis qui en valait la peine. Le CPU PA-RISC 7100 implémentait cette optimisation. Il s'agissait d'un CPU double émission entière-flottante, qui utilisait donc un partitionnement assez simple. Le combo partitionnement + reconditionnement permettait ici de gagner en performance, pour un cout en matériel très limité. D’autant plus limité que le jeu d'instruction prévoyait le reconditionnement de la FPU. Les multiplications ne lisaient pas leurs opérandes dans les registres entiers, mais dans les registres flottants. ===La double émission entière=== Une autre possibilité ajoute un second pipeline, capable d'exécuter des opérations entières simples, appelé le pipeline entier. Le processeur supporte alors la '''double émission entière''', à savoir qu'ils peuvent émettre une opération entière en plus d'une autre instruction. L'opération entière en question est une opération simple, comme une addition, une soustraction, une opération bit à bit, une comparaison, etc. Les exemples classiques sont les processeurs Intel i960 et Pentium. L'implémentation était différente entre l'i960 et le Pentium : Le Pentium avait une seconde ALU entière, alors que le i960 rusait. Le Pentium dupliquait l'ALU entière, pour créer un second pipeline entier. Sur le Pentium originel, les contraintes d'appariement étaient assez strictes. Le Pentium avait une FPU, mais ne pouvait pas l'utiliser en double émission. Il lui était impossible d'exécuter une opération flottante en même temps qu'une opération entière (branchements inclus). Le Cyrix 6x86 était une sorte de Pentium produit par l'entreprise Cyrix, avec une microarchitecture différente. Il avait des contraintes d’appariement réduites. Par exemple, il pouvait émettre une opération flottante en parallèle d'une opération entière. Nous détaillerons ces processeurs dans le prochain chapitre, qui porte justement sur les CPU superscalaires x86. [[File:CPU simple et double émission avec un second pipeline entier.png|centre|vignette|upright=2.5|CPU simple et double émission avec un second pipeline entier.]] L'Intel i960 n'avait pas de FPU, juste une ALU entière et une unité mémoire. Il utilisait l'unité mémoire comme une seconde ALU entière limitée. Pour rappel, une unité mémoire contient une AGU, à savoir une ALU spécialisée dans les calculs d'adresse. Elle est capable de faire des additions, soustractions, et quelques décalages limités (des décalages par 2, 4, 8, pas plus). L'Intel i960 ajoutait des connexions entre AGU et banc de registre, afin que celle-ci serve de seconde unité de calcul. L'AGU est déjà reliée aux deux ports de lecture, pour lire les opérandes du calcul d'adresse, il suffit de la connecter sur le port d'écriture. Le processeur peut alors émettre deux instructions/µops entières, sous certaines conditions. [[File:Emission multiple des opérations entières, double émission.png|centre|vignette|upright=2|Émission multiple des opérations entières, double émission.]] ==Les processeurs superscalaires dynamiques== Les processeurs précédents, avec 2 pipelines, avaient une unité d'émission assez simple. Elle avait deux ports de décodage, et deux ports d'émission. Le nombre de ports de décodage et d'émission étaient identiques. Vous vous dites que c'est naturel, et intuitif, que c'est normal pour un processeur superscalaire. Mais en réalité, la majorité des processeurs superscalaires a plus de ports d'émission que de décodage. Expliquer pourquoi va cependant nous demander de parler de comment la superscalarité exploite les unités de calcul. Posons-nous cette question : de combien d'unités un processeur superscalaire a besoin ? Les unités en question ne sont nécessairement des unité de calcul, l'unité mémoire et l'unité de branchement comptent aussi. La réponse dépend du nombre de voies. Si le ''front-end'' charge et décode un paquet de N instructions, exécuter ces N instructions simultanément demandera là aussi au moins N unités de calcul. Notez le terme : ''au moins'', car c'est là tout l'enjeu de cette section. La réponse dépend aussi des contraintes d’appariement. Pour un CPU superscalaire "parfait", sans contraintes d’appariement, toutes les unités doivent être dupliquées, y compris l'unité mémoire. Un processeur à double émission parfaite devrait dupliquer l'ALU entière, le ''barrel shifter'', le circuit multiplieur, la FPU, l'unité mémoire, et l'unité de branchement, ce qui ferait passer de 6 unités à 12. Le cout en circuit est énorme, même pour de la double émission ! Et pour un CPU à N pipelines, le cout en circuit encore plus prohibitif ! En pratique, les processeurs superscalaires qui font ça sont très rares. La majeure partie des processeurs superscalaires utilisent d'autres techniques : le partitionnement, la duplication partielle des ALUs, la duplication des autres unités. ===Le partitionnement des unités fonctionnelles=== Les CPU superscalaires les plus simples ne dupliquent pas leurs unités de calcul, ils se contentent d'exploiter les unités existantes. Et pour expliquer comment, nous allons partir de l'exemple d'un processeur non-superscalaire contient une ALU entière, une FPU, une unité mémoire et une unité de branchement. Les quatre unités peuvent fonctionner en parallèle, à savoir qu'une unité peut exécuter une instruction à elle toute seule, sans interagir avec les autres. Cela suffit pour créer un processeur quadruple émission, qui peut émettre en même temps : une opération entière, une opération flottante, un branchement, un accès mémoire. Le principe marche aussi sur un processeur avec moins de 4 unités, par exemple sans FPU et/ou sans unité de branchement, ou avec au contraire des unités en plus. L'idée générale est d’émettre une µops séparée dans chaque unité fonctionnelle. Utiliser ainsi les unités existantes, pour rendre un CPU superscalaire, s'appelle le '''partitionnement superscalaire'''. Le terme n'est pas beaucoup utilisé en-dehors de quelques vieux articles académiques. Le partitionnement est fortement dépendant du jeu d'instruction, car il demande de bien séparer instructions entières, flottantes, branchements et instructions mémoire. Et autant c'est bien le cas sur les CPU RISC, autant les CPU CISC ne sont pas dans ce cas. Les CPU CISC ont des instructions ''load-op'', qui effectuent une instruction arithmétique et une µop mémoire. Et ces instructions sont très compliquées à implémenter avec le partitionnement. Et au-delà de ça, les CPU CISC ont beaucoup d'instructions qui sont décodées en plusieurs µops, à exécuter dans un ordre bien précis. On peut pas émettre les µops en question simultanément. Le partitionnement a un cout en circuit très limité, pour un gain en performance lui aussi limité. Pour reprendre le CPU de l'exemple précédent, il est rare que l'on ait à exécuter ces quatre instructions en même temps. En pratique, les combinaisons fréquentes sont les combinaisons calcul + accès mémoire, calcul + branchement, calcul entier + calcul flottant. Utiliser une quadruple émission de ce type n'est donc pas une bonne idée, alors que la double émission parait plus adaptée. Et c'est là que la différence entre ports d'émission et de décodage devient intéressante. L'idée est d'avoir une unité d'émission avec deux ports de décodage, et 4 port d'émission (un par unité). Les contraintes d’appariement varient alors suivant le processeur, mais la règle générale est que si deux micro-opérations tombent dans deux unités différentes, on peut les émettre en même temps. En clair, on peut émettre une opération entière avec une opération flottante, une opération entière avec un branchement, un accès mémoire avec un branchement, etc. Et cela rend le partitionnement bien plus intéressant que prévu. Les tout premiers CPU superscalaires étaient des CPU double émission, qui utilisaient le partitionnement avec : une ALU entière, une unité mémoire et une FPU. Nous parlerons de '''partitionnement INT-FLOAT-MEM'''. L'avantage sur la double émission flottante est que les combinaisons INT + MEM et FLOAT + MEM étaient autorisées. L'intérêt était d'émettre une opération en parallèle d'un accès mémoire, ce qui est un doublet d'instruction assez fréquent. Les exemples classiques sont l'HyperSPARC, de l'alpha 21064 et du PowerPC 603. Les contraintes d’appariement variaient grandement suivant le processeur. Le PowerPC 603 implémentait une double émission "parfaite". C'est à dire que toutes les combinaisons possibles à deux instructions étaient possibles, que ce soit INT + FLOAT, INT + MEM, FLOAT + MEM. En plus de cette double émission "parfaite", le CPU utilisait la technique dite de ''branch folding'', qui permettait d'exécuter un branchement sans utiliser d'unité de calcul, ce qui fait qu'il était qualifié de ''CPU 2-way + Branch''. A l'opposé, le processeur alpha 21064 avait pas mal de contraintes d’appariement, à savoir que certaines combinaisons de deux instructions étaient interdites, bien que compatibles avec les unités disponibles. Une des raisons est que le banc de registre n'avait pas assez de ports de lectures, ce qui fait que certaines combinaisons avec une écriture mémoire n'étaient pas permises. Les concepteurs avaient volontairement inséré ces contraintes afin de simplifier le design du processeur. Avant de poursuivre, précisons que la FPU regroupe un additionneur flottant et un multiplieur flottant. Et ce fait peut être exploité pour approfondir le partitionnement. La '''double émission FADD/FMUL''' permet d'émettre une addition flottante en même temps qu'une multiplication flottante. Elle a été utilisée sur les processeurs x86, Alpha et SPARC. Les autres circuits de calcul flottant sont souvent regroupés avec l'additionneur. La division est parfois e même port que le multiplieur flottant, parfois sur celui de l'additionneur (quand elle est microcodée et exécutée en enchainant les soustractions). ===La duplication des ALU entières=== Le partitionnement est une technique élégante, simple, facile à implémenter et au cout en circuits modeste. Mais il faut avouer que cela ne donne pas des CPU superscalaires très efficaces. La raison est que le partitionnement permet d'émettre des paires, triplets, quadruplets de micro-opérations qui sont rares en pratique. Les combinaisons les plus fréquentes en pratique sont des paires/triplets d'opérations entières, souvent des paires/triplets d'additions ou d'opérations entières simples. Il est donc primordial d'émettre plusieurs opérations entières simultanément. Le fait d'émettre et d'exécuter plusieurs opérations entières en même temps s'appelle l''''émission entière multiple'''. Pour exécuter la seconde/troisième opération entière, il faut dupliquer les ALU entières (ou réutiliser l'AGU comme ALU entière comme le fait l'Intel i960). Le processeur doit avoir deux ALU entières pour de la double émission entière, trois pour de triple émission entière, etc. De plus, il faut ajouter des ports au banc de registre, pour lire deux fois plus d'opérandes et décrire deux résultats. Et enfin, il faut rajouter un port d'émission pour pouvoir émettre une micro-opération entière en plus. [[File:Processeur non-superscalaire basique 03.png|centre|vignette|upright=2|Processeur non-superscalaire basique]] La tendance est de multiplier les ALU entières, mais pas le circuit multiplieur. Le cout en transistors est alors grandement réduit, au prix de l'apparition de dépendances structurelles. Le CPU peut émettre deux additions en même temps, ou une addition et une multiplication, mais pas deux multiplications. Il en est de même avec le ''barrel shifter'', qui n'est pas dupliqué. Il est donc possible d'émettre un décalage avec une opération de base, mais pas deux décalages en parallèle. Mais ces contraintes d’appariement ne sont pas un problème, car il est rare de devoir émettre deux multiplications/décalages en même temps. La plupart des programmes sont majoritairement remplis d'additions, avec des multiplications assez rares et des décalages qui le sont encore plus. La moyenne est proche d'une multiplication pour 4/5 additions. Vu que des multiplications consécutives sont très rares, dupliquer le multiplieur est presque inutile. Disposer de plusieurs circuits multiplieurs ou de plusieurs ''barrel shifters'' serait donc un cout en circuits qui ne servirait que rarement et n'en vaut pas la chandelle. Il faut noter que sur les CPU 32 bits, les multiplications peuvent être exécutées dans la FPU ou dans un multiplieur séparé, tout dépend de si le CPU exécute les multiplications dans la FPU ou pas. {|class="wikitable" |- ! Voie d'émission n°1 !! Voie d'émission n°2 !! Voie d'émission n°3 |- | ALU entière || ALU entière || FPU |} Reste à connecter les unités de calcul à l'unité d'émission (indirectement, car il y a des étages de pipeline entre les deux). Une implémentation basique utilisee un port d'émission par unité. Prenons l'exemple d'un CPU superscalaire avec 4 ALU entières, une FPU, et une unité mémoire. Vu qu'il y a 6 unités, cela fait donc 6 ports d'émission. L'exemple historique le plus impressionnant est celui du Motorola 88110, un CPU double émission qui possède 10 unités. Il contient deux ALU entières, un circuit multiplieur, un circuit diviseur, une unité de manipulation de bits, une unité mémoire, un additionneur flottant, et deux unités graphiques pour des calculs impliquant des pixels. Le multiplieur est utilisé à la fois pour les multiplications entières, flottantes et le produit de deux pixels. Idem pour le diviseur, qui sert pour les divisions entières et flottantes. Et chaque unité a son propre port d'émission. La conséquence est que les contraintes d'appariement du Motorola 88110 sont simples : tant que deux µops atterrissent dans des unités séparées, la paire est permise. Il est par exemple possible d'émettre simultanément deux opérations entières simples, vu qu'il y a deux ALU. Par contre, impossible d'émettre simultanément deux multiplications, deux divisions, deux opérations flottantes, deux manipulations de bit, deux accès mémoire, etc. De plus, le partage du multiplieur fait qu'on ne peut pas émettre une multiplication entière avec une multiplication flottante. Idem avec les divisions, vu que le diviseur est aussi partagé. Ce sont les seules contraintes. ===Le partage des ports d'émission=== La présence d'un circuit multiplieur et d'un ''barrel shifter'' pose des problèmes pour le partitionnement. Il est possible d'avoir un port d'émission dédié au multiplieur, et un autre au ''barrel shifter'', qui sont séparés de ceux pour l'ALU entière. Il est alors possible d'émettre une opération entière, une multiplication et un décalage, simultanément. Nous parlerons de '''partitionnement entier''' pour désigner cette possibilité. [[File:Partitionnement superscalaire entier.png|centre|vignette|upright=2|Partitionnement superscalaire entier.]] Le partitionnement entier est cependant très rare, car le gain en performance serait trop faible au regard des couts en circuit. Quelques processeurs historiques l'utilisaient, mais ce n'est plus le cas de nos jours. La raison est que les multiplications et les décalages sont assez rares dans le code, ce qui fait que le port d'émission associés sont sous-utilisés. Et un port d'émission a un cout en circuit assez important. Et c'est sans compter qu'ajouter des ports sur le banc de registres juste pour un multiplier sous-utilisé n'en vaut pas la peine. L'idéal serait de trouver un moyen de réduire le nombre de ports d'émissions, sans réduire le nombre d'unités de calcul. [[File:Emission multiple des opérations entières, implémentation naive.png|vignette|Émission multiple des opérations entières, implémentation naive]] La solution consiste à '''partager les ports d'émission''', à savoir connecter un port d'émission à plusieurs unités de calcul. L'exemple classique est justement lié aux circuits multiplieur et ''barrel shifters''. L'idée est que le multiplieur et le ''barrel shifter'' sont mis sur le même port d'émission qu'une ALU entière. Prenons par exemple le processeur à double émission entière illustré ci-contre. Un port d'émission est relié à une ALU entière seule, et l'autre port d'émission alimente la seconde ALU entière et le multiplieur. La conséquence est que le processeur peut émettre deux opérations entières, dont une multiplication. Si on avait utilisé un port d'émission dédié au multiplieur, il aurait été possible d'émettre deux opérations entières et une multiplication. On perd donc une voie, mais on gagne en flexibilité et on économise des circuits. Notons que ce partage signifie aussi que le multiplieur partage les ports sur le banc de registre, ce qui fait une double économie. [[File:Emission multiple des opérations entières, implémentation courante.png|centre|vignette|upright=1.5|Émission multiple des opérations entières, implémentation courante]] En général, le ''barrel shifter'' et le multiplieur sont placés sur deux voies d'émission séparés, ce qui permet d'émettre un décalage et une multiplication en même temps. Les mettre sur le même port ne le permettrait pas. {|class="wikitable" |- ! Voie d'émission n°1 !! Voie d'émission n°2 !! Voie d'émission n°3 |- | * ALU entière * Multiplieur | * ALU entière * Barrel shifter | FPU |- | * Additions, soustractions, opérations bit à bit, etc. * Multiplication | * Additions, soustractions, opérations bit à bit, etc. * Décalages et rotations | Opération flottante |} Mais le partage des ports d'émission peut appliquer à n'importe quelle unité. Prenons l'exemple d'un CPU superscalaire avec 4 ALU entières, une FPU, et une unité mémoire. Vu qu'il y a 6 unités, cela fait donc 6 ports d'émission. Cependant, il est rare qu'un bloc de 6 instructions contienne exactement 4 opérations entières, une opération flottante, et un accès mémoire. Beaucoup de ports d'émission seraient sous-utilisés. La solution est de partager des ports d'émission, pour passer à la quadruple émission. Le CPU peut alors répartir 4 instructions sur les ports d'émission adéquats, à chaque cycle. Le CPU a donc 4 ports de décodage et 6 ports d'émission. Une implémentation classique permet n'importe quelle combinaison de 4 instructions compatible avec les 6 unités. Par exemple, il devient possible d'émettre simultanément 4 additions, la combinaison 2 * INT + FLOAT + MEM, 3 * INT + MEM, et bien d'autres. L'exemple relie une ALU entière, la FPU et l'unité mémoire sur le même port. Le CPU a alors quatre ports d'émission, qui ne sont pas "identiques". Trois d'entre eux sont reliés à une ALU entière chacun, rien d'autre. Le quatrième est lui relié à une ALU entière, la FPU et l'unité mémoire. On économise alors deux ports d'émission, mais cela entraine l'apparition de contraintes d’appariement. Le CPU peut émettre 3 opérations entières + n'importe quelle autre opération. Mais les autres combinaisons sont interdites. Le CPU peut émettre 6 opérations entières simultanément, grâce à 6 ALU entières. Les accès mémoire ou opérations flottantes ne viennent pas en plus, mais en remplacement des opérations entières. Regrouper plusieurs ALU sur un même port d'émission est donc à l'origine de dépendances structurelles, de nouvelles contraintes d’appariement. Dans certaines situations de '''''port contention''''', on a assez d'ALU pour exécuter N micro-opérations, mais la répartition des ALUs sur les ports l'empêche. Impossible d'émettre deux micro-opérations sur deux ALU si elles sont sur le même port d'émission. On peut réduire les cas de ''port contention'' en répartissant correctement les ALU sur les ports d'émission, mais sans jamais les supprimer complétement. Un exemple est celui du PowerPC 604. Il implémentait la quadruple émission, alors qu'il avait 5 unités de calcul. Mais deux unités partageaient un même port d'émission. Dans le détail, il avait deux ALU entières, un circuit multiplieur/diviseur, une FPU, une unité mémoire. La FPU et le multiplieur étaient sur le même port d'émission. En conséquence, le CPU pouvait émettre seulement les combinaisons INT + INT + MEM + (MUL/FLOAT). : Il est intéressant de voir l'exemple d'un processeur superscalaire parfait, sans contraintes d'appariement, à N voies. Pour cela, on part d'un processeur non-superscalaire, avec M unités de calcul. Un tel processeur peut s'implémenter avec N ports d'émission, chacun relié à M unités de calcul possibles, ce qui fait que les M unités de calcul sont dupliquées en N exemplaires. Si on prend par exemple un CPU non-superscalaire avec une ALU, une FPU, une unité mémoire et une unité de branchement, un port d'émission sera relié à une ALU, une FPU, une unité mémoire et une unité de branchement. ===La duplication des autres unités fonctionnelles=== Dupliquer les ALU entière est une première étape, mais il est aussi possible de dupliquer les autres unités fonctionnelles. Par exemple, il est possible de dupliquer l'unité mémoire ou l'unité de branchement. Reste à voir quelles unités dupliquer, et pourquoi. Précisons que dans ce qui va suivre, nous allons parler de la duplication des unités en général, pas seulement des unités mémoire/branchement. Nous allons continuer de parler de la duplication des ALU entières dans ce qui suit, mais de manière plus générale. Pour un processeur à N voies, il est théoriquement possible de dupliquer toutes les unités en N exemplaires. Mais ce n'est jamais fait en pratique, car le cout en circuits serait trop important et le gain en performance trop faible. Mieux vaut dupliquer certaines unités bien précises, quitte à avoir des contraintes d'appariement. Et à ce petit jeu, toutes les duplications ne se valent pas, certaines sont plus rentables en matière de performance, le cout en circuit n'est pas le même selon l'unité, etc. La rentabilité en performance dépend surtout des contraintes d'appariement. Les contraintes d'appariement entrainent une baisse de performance significatives si elles interdisent des paires d'instructions fréquentes. Aussi, les opérations très fréquentes, souvent appairées, gagnent à dupliquer l'unité associée. Et au contraire, les unités rarement utilisées ne doivent pas être dupliquées. Il se trouve que les unités les plus utilisées sont les ALU entières, suivies par les unités mémoire et l'unité de branchement. Le cout en circuit n'est aussi pas le même. Dupliquer les ALU entière est trivial, car elles ont un budget en transistors assez faible. Les circuits multiplieurs, diviseurs, et les ''barrel shifter'' ont eux un cout en transistor plus élevés. Si on devait faire un classement, on aurait : ALU entière < ''barrel shifter'' < multiplieur. Pour les unités mémoire, le cout en transistor est là aussi conséquent. Le résultat final est que dupliquer les ALU entières est privilégié sur le reste, comme l'a vu dans la section précédente. Les unités mémoire arrivent en seconde place, suivie par les multiplieurs/''barrel shifter'' à la troisième place, et les unités de branchement à la quatrième place. Les CPU superscalaires larges ont donc tendance à multiplier les unités mémoire en second. Les multiplieurs et ''barrel shifters'' sont eux aussi dupliqués sur les CPU superscalaires larges, mais cela vient souvent après avoir dupliqué les unités mémoire. La raison est que les accès mémoire sont critiques pour la performance des CPU à exécution dans le désordre, pas les multiplications. Pour les unités de branchement, une subtilité complique le fait d'en installer plusieurs. Que faire quand deux branchements sont pris ? La solution la plus simple est de ne pas se trouver dans cette situation : on n'utilise qu'une seule unité de branchement et on n’exécute qu'un seul branchement par cycle. La majorité des processeurs superscalaires étroits font ainsi. Les processeurs superscalaires larges ont cependant deux unités de branchements, mais ils n'acceptent qu'un seul branchement pris à la fois. Si les deux branchements pris sont exécutés en même temps, le processeur n’exécute que le plus récent, dans l'ordre du programme. ===Les CPU superscalaires mélangent les trois techniques=== Les CPU superscalaires utilisent toutes les techniques à la fois : partitionnement, duplication de l'ALU entière, duplication des autre unités fonctionnelles. Chaque processeur utilise ces trois stratégies à sa sauce, en fonction de son budget en transistor. Les quatre techniques ont cependant un cout en circuit différent, et donnent des gains de performances eux aussi forts différents. * Le '''partitionnement superscalaire''' a un cout en circuit modéré, car on n'a pas besoin de dupliquer d'ALU. * La '''duplication des ALU entières''' est la technique la plus importante : elle a un cout en circuit modéré, et donne des gains en performance impressionnants. * La '''duplication des autres unités''' a un cout en circuit variable, pour des gains en performance eux aussi variables. * Le '''partage des ports d'émission''' réduit le cout en circuit du CPU, pour des pertes de performances mineures si bien fait. L'usage du partitionnement seul, sans duplication, était surtout le fait des processeurs commercialisés entre les années 1989 et 1993. Le partage des ports d'émission a lui aussi mis un peu de temps pour s'imposer. Globalement, les processeurs superscalaires ont évolués progressivement, pour se rapprocher d'une sorte de standard. Mais pour comprendre ce qu'est ce standard, et en quoi les CPU historiques s'en sont progressivement rapprochés, il faut étudier l'histoire des CPU superscalaires. L'histoire des CPU superscalaires a commencé dès 1960, mais est resté un projet de recherche pendant plusieurs décennies. De nombreux designs ont été conçus, mais abandonnés avant la production, comme l'ACS-1 d'IBM. Les années 1980 ont vu l'arrivée de prototypes de processeurs VLIW, mais qui n'étaient pas superscalaires. Par exemple, l'Apollo DN10000 et l'Intel i860 pouvaient émettre deux instructions à la fois, sous conditions, mais demandaient que le compilateur fasse une bonne partie du travail. Les premiers CPU superscalaires commercialisés sont sortis en 1989. Deux CPUs superscalaires sont sortis cette année là : le RS/6000 d'IBM et l'i960CA d'Intel. Puis, la compétition s'est aussi lancée dans la bataille. Les CPU commercialisés avant l'année 1993 étaient à double émission, à l'exception du SuperSPARC qui utilisait la triple émission. Les modèles après 1993 hésitaient entre double et quadruple émission. Dans le tableau ci-dessous, les processeurs quadruple émission sont en bleu, les triple émission sont en rouge, les autres ne sont pas en couleur. {|class="wikitable" |- ! !! IBM !! HP PA-RISC !! SPARC !! Intel!! Alpha !! Autres |- ! 1989 | RS/6000 || || || i960CA || || |- ! 1990 | || || || || || |- ! 1991 | || || || || || Motorola 88110 |- ! 1992 | RSC || PA 7100 || class="f_rouge | SuperSPARC || || 21064 || |- ! 1993 | PPC 601 || || HyperSPARC || Pentium || || |- ! 1994 | class="f_bleu | POWER 2 || PA 7200 || || || class="f_bleu | 21164 || |- ! 1995 | 603/604 || class="f_bleu | PA 8000 || class="f_bleu | UltraSPARC 1 || Microarchitecture P6 (+ concurrence AMD) || || class="f_bleu | MIPS R1000 |- ! 1996 | class="f_bleu | 620 || || || || class="f_bleu | 21264 || |- ! ... || ... || ... || ... || ... || ... || ... |} Les premiers CPU à double émission sont assez représentatifs des CPU à double émission modernes. Il faut dire que se limiter à deux voies ne permet pas de faire beaucoup de choses. Voici le tableau chronologique disant quel CPU utilisait quelle technique. * Les CPU en vert utilisaient la double émission entière-flottante. * Les CPU en rouge utilisaient la double émission entière "pure". * Les CPU en jaune utilisaient un ''partitionnement complet'' entre ALU, FPU et unité mémoire. * Les CPU sans couleur utilisaient un grand nombre d'unités, avec beaucoup de ports d'émission. * Les CPU barrés utilisent la triple ou quadruple émission et seront vu dans la section suivante. {|class="wikitable" |- ! !! IBM !! HP PA-RISC !! SPARC !! Intel!! Alpha !! Autres |- ! 1989 | class="f_vert | RS/6000 || || || class="f_rouge | i960CA || || |- ! 1990 | || || || || || |- ! 1991 | || || || || || Motorola 88110 |- ! 1992 | class="f_vert | RSC || class="f_vert | PA 7100 || <s>SuperSPARC</s> || || class="f_jaune | 21064 || |- ! 1993 | class="f_vert | PPC 601 || || class="f_jaune | HyperSPARC || class="f_rouge | Pentium || || |- ! 1994 | <s>POWER 2</s> || PA 7200 || || || <s>21164</s> || |} C'est en 1994, que la quadruple émission est arrivée et que les CPU ont essayé divers implémentations. Il y avait alors trois stratégies pour la double/triple/quadruple émission, qui partageaient des problèmes similaires. La première implémentation dupliquait les ALUs et les FPUs, ce qui permettait d'émettre deux opérations flottantes avec deux opérations entières. Le POWER 2 dupliquait ses FPUs à la perfection, alors que le 21264 séparait l'additionneur et le multiplieur flottant. Les accès mémoire étaient traités comme des opérations entières et le processeur pouvait souvent en émettre deux à la fois. Mais une telle organisation n'est plus utilisée de nos jours, car, sans instructions flottantes à exécuter, elle est équivalente à de la double émission. La seconde implémentation utilisait deux ALU entières (dont un multiplieur), une unité mémoire et une FPU.Cette organisation est intéressante, mais encore trop stricte pour donner de bons résultats, les quadruplets avec exactement deux opérations entières, un accès mémoire et une opération flottante étant trop rares. L'usage de ces 4 unités en double ou triple émission a été utilisé sur le PA 7200 et le SuperSPARC. Malheureusement, en plus de problèmes au niveau de son cache, le SuperSPARC avait de nombreuses contraintes d’appariement, car le banc de registre n'avait pas assez de ports pour alimenter certaines combinaisons d'instructions. De plus, s'il émettait une multiplication, il ne pouvait pas émettre de seconde µop. Enfin, une troisième implémentation utilisait un grand nombre d'unités fort différentes, bien plus que quatre, avec au minimum deux ALU entières. Prenons l'exemple de l'UltraSPARC, qui avait : deux ALU entières, une unité mémoire, une unité de branchement, un additionneur flottant, un multiplieur flottant, un diviseur flottant (aussi utilisé pour le calcul des racines carrées), et 2 unités graphiques pour les calculs sur des pixels. En tout, cela fait 9 unités différentes, pour de la quadruple émission. Dans le même genre, les processeurs MIPS R8000 et R10000 avaient une implémentation plus moderne, dans le sens où toutes les unités étaient dupliquées. Le CPU avait deux ALU, deux FPU et deux unités mémoire. Il pouvait donc émettre quatre instructions à la fois, tant que le paquet collait avec ces six unités. Moitié opérations entières, moitié opérations mémoire, avec de quoi remplacer les opérations entières par des opérations flottantes. Le PA 8000, lui, dupliquait toutes ses unités en deux exemplaires, ce qui n'est pas l'idéal, aucun processeur moderne ne fait cela aujourd'hui. Le tout est résumé dans le tableau ci-dessous, avec : * en vert les CPU utilisant la première stratégie ; * en rouge les CPU utilisant la seconde stratégie ; * en jaune les CPU utilisant la troisième stratégie. {|class="wikitable" |+ Processeurs quadruple émission historiques |- ! Année !! Processeur !! Unités |- ! 1991 | class="f_jaune | Motorola 88110 || class="f_jaune | 2 ALU + MUL + DIV + BIT + MEM + FADD + 2 unités graphiques |- |- ! rowspan="2" | 1994 | class="f_vert | POWER 2 || class="f_vert | 2 ALU/MEM + 2 FPU (+ 2 Branchements via ''branch folding'') |- | class="f_rouge | 21164 || class="f_rouge | 2 ALU + FPU + MEM |- ! rowspan="3" | 1995 | PA 8000 || 2 ALU + 2 ''barrel shifters'' + 2 MEM + 2 FPU (FMA) + 2 FPU (DIV et SQRT) + 2 SIMD |- | class="f_jaune | UltraSPARC 1 || class="f_jaune | 2 ALU + MEM + BRANCH + FADD + FMULFDIV + 2 unités graphiques |- | MIPS R8000 et R10000 || 2 ALU + 2 FPU + 2 MEM |- ! rowspan="2" | 1996 | class="f_rouge | 620 || class="f_rouge | 2 ALU + FPU + MEM |- | class="f_vert | 21264 || class="f_vert | 2 ALU/MEM + 2 FPU (FADD + FMUL) |} Malheureusement, ces CPUs historiques n'avaient ni assez d'unités de calcul entières, ni d'unités mémoire, pour remplir la quadruple émission. Environ la moitié des instructions d'un programme correspond à des instructions entières. Pour en profiter, un processeur à N voies doit disposer d'au minimum N/2 ALU entières, l'idéal étant d'aller au-delà. Les CPU superscalaires historiques avaient 2 ALU pour de la quadruple émission, ce qui était très limite. En comparaison, les CPU superscalaires modernes mettent le paquet niveau ALU entière. La plupart des CPU x86 modernes descendent rarement sous les 2/3, la plupart remplit presque la totalité de la largeur du CPU. Pour donner un exemple, la microarchitecture Golden Cove d'Intel est un processeur à 6 voies, avec 5 ALU entières. Pour être précis, un programme informatique exécute environ 40% de son code dans des instructions arithmétiques, un autre 40% dans les accès mémoire, et un le reste pour... le reste. La conséquence est que les processeurs modernes dupliquent leurs unités mémoire. Par exemple, la microarchitecture ''Golden Cove'' d'Intel dispose de trois unités mémoire : deux spécialisées dans les écritures, trois dans les lectures. La microarchitecture ''Gracemont'' utilise la pentuple émission et peut émettre 4 µops entières et 4 µops mémoire. Notez que le ratio est équilibré entre opérations entières et mémoire, avec soit égalité, soit un avantage pour les ALU entières. Une autre régularité est que la largeur totale du processeur peut être occupée par des opérations entières/mémoire. Pour le dire autrement, sur un processeur quadruple émission, il pourra émettre un mix de 4 instructions entière/mémoire, avec zéro opération flottante. Idem avec des processeurs triples ou pentuple émission, voire plus. Si le processeur émet une opération flottante, elle remplace une opération entière. Prenons l'exemple des processeurs AMD K6, sortis en 1997, juste avant les AMD Athlon. Ils avaient 10 unités de calcul, 6 ports d'émission, 4 ports de décodage. Voici exactement comment étaient répartie les unités de calcul sur les ports d'émission. C'était des processeurs quadruple émission, ce qui fait que la moitié de la largeur du processeur sert pour des opérations entières, et l'autre moitié pour les accès mémoire. {|class="wikitable" |- ! Port d'émission n°1 !! Port d'émission n°2 !! Port d'émission n°3 !! Port d'émission n°4 !! Port d'émission n°5 !! Port d'émission n°6 ! |- | ALU entière || ALU entière || FPU || Unité de branchement || Unité LOAD || Unité STORE |- | || Multiplieur || || || || Unité de calcul d'adresse (instruction LEA) |- | || ''Barrel shifter'' || || || || Unité pour les instructions PUSH |} Les microarchitectures K7, K8 et K10, utilisées sur les anciens CPU AMD Athlon, sont un exemple moins représentatif. Ces microarchitectures utilisent le partage des ports d'émission au maximum. C'était des microarchitectures à triple émission, avec six ports d’émission. Il y avait trois ports d'émission pour les opérations flottantes, et trois ports d'émission pour les micro-opérations entières/mémoire. Il y avait trois ALU entières et trois unités mémoire, chaque ALU partageait un port d'émission avec une unité mémoire. Elle pouvait donc émettre soit trois opérations entières, soit trois µops mémoire, soit un mix des deux. En plus des mix avec les opérations flottantes et branchements. {|class="wikitable" |- ! Port d'émission n°1 !! Port d'émission n°2 !! Port d'émission n°3 !! Port d'émission n°4 !! Port d'émission n°5 !! Port d'émission n°6 ! |- | ALU entière || ALU entière || ALU entière || FADD || FMUL || FPU (autres opérations) |- | Multiplieur || || || || || |- | LOAD/STORE || LOAD/STORE || LOAD/STORE || || || |} Les CPU superscalaires larges dupliquent les multiplieurs et/ou les ''barrel shifters'', et même les unités de branchement. La raison est que quand la largeur du CPU augmente, le processeur traite des paquets d'instructions de plus en plus gros. Par exemple, pour un CPU à 10 voies, cela veut dire émettre 10 instructions en même temps. Il est alors plus fréquent d'avoir deux multiplications, deux décalages, deux accès mémoire, deux branchements. Par exemple, la moyenne est d'une multiplication tous les 5 instructions. Les CPU superscalaires à 10 voies ont donc tendance à avoir deux multiplications par paquet d'instruction, en moyenne. Avoir deux multiplieurs est alors un gain en performance clair. Par exemple, la microarchitecture ''Golden Cove'' a deux ''barrel shifters''. C'est une architecture à 6 voies. {|class="wikitable" |- ! Port d'émission n°1 !! Port d'émission n°2 !! Port d'émission n°3 !! Port d'émission n°4 !! Port d'émission n°5 !! Port d'émission n°6 !! Port d'émission n°7 !! Port d'émission n°8 !! Port d'émission n°9 !! Port d'émission n°10 |- | ALU entière || ALU entière || ALU entière || ALU entière || ALU entière || LOAD || LOAD || LOAD || STORE || STORE |- | ''Barrel shifter'' || Multiplieur || Multiplieur || ''Barrel shifter'' || || || || || || |- | Branchements || Diviseur || || Branchements || || || || || || |} ==Les µops mémoire sur les processeurs superscalaires== Plus haut, nous avons volontairement passé sous silence les accès mémoire. La raison à cela est que leur support sur les CPU superscalaire peut se faire de plusieurs manières différentes, qu'il vaut mieux expliquer dans une section dédiée. Les différentes implémentations sont décrites dans la liste suivante, nous allons les voir une par une. * Avec la première méthode, les accès mémoire sont exécutés par le pipeline pour les opérations entières. * Avec la seconde méthode, les accès mémoire ont un pipeline dédié ; c'est l'''émission parallèle des accès mémoire''. * Avec la troisième méthode, il est possible d'émettre plusieurs accès mémoire simultanément : c'est l'''émission multiple des accès mémoire''. Voyons ces trois cas dans le détail. Nous verrons notamment les différentes formes d'émission multiple des accès mémoire. ===Les accès mémoire traités dans le pipeline entier=== Dans le cas le plus simple, les accès mémoire sont traités comme des opérations entières un peu spéciales. L'unité mémoire est sur le même port d'émission que l'ALU entière, ce qui fait que les accès mémoire remplacent des opérations entières. Faire ainsi permet d'utiliser une ALU entière pour les calculs d'adresse, pas besoin de rajouter une unité de calcul d'adresse séparée. Typiquement, un processeur de ce type place l'unité mémoire après l'unité de calcul. Les opérations entières n'utilisent que l'ALU entière, mais pas l'unité mémoire. Les accès mémoire exécutent un calcul d'adresse dans l'ALU entière, puis l'adresse calculée est envoyée à l'unité mémoire. Le tout est illustré ci-dessous, dans le cas d'un processeur historique à double émission entière-flottante. [[File:Processeur non-superscalaire basique 01.png|centre|vignette|upright=1.5|Processeur non-superscalaire basique]] Un exemple de CPU double émission de ce type est celui du Power PC 440, un CPU destiné à l'embarqué. Vu qu'il était destiné à l'embarqué, il n'avait pas de FPU, il n'avait pas besoin de manipuler de nombres flottants. Il n'a donc que des ALU entières, un multiplieur et une unité mémoire. Il dispose de deux ports d'émission. Le premier est connecté à une ALU entière et un circuit multiplieur, le second est relié à l'unité mémoire et une seconde ALU entière. Une organisation simple, mais très efficace ! [[File:PowerPC 440.png|centre|vignette|upright=2|Microarchitecture du PowerPC 440.]] L'implémentation économise une unité de calcul d'adresse, mais n'a pas de cout en performance significatif. Les accès mémoire étant très lents, émettre une µop entière en parallèle d'un accès mémoire ne sert pas à grand-chose. On peut émettre cette µop entière un cycle plus tard, elle sera terminée avant même que l'accès mémoire soit terminé, même si l'accès mémoire se fait dans le cache L1 ou L2. Pour comprendre pourquoi, faisons quelques calculs. Un accès mémoire prend deux-trois cycles quand il tombe dans le cache L1. Et on doit attendre qu'il soit terminé pour en émettre un nouveau. Combien d'instructions arithmétiques peut-on lancer pendant ce temps, sur un CPU triple émission ? Au premier cycle, on peut en émettre 2 (l'instruction mémoire prend une place, donc 3-1), puis 3 au second cycle. Ce qui fait 5 instructions arithmétiques au total. Or, il se trouve qu'il y a une instruction d'accès mémoire pour 5 instructions arithmétiques, environ. Par contre, ce compromis change quand on passe à la quadruple émission, voire au-delà. Dans ce cas, il devient intéressant de pouvoir émettre une nouvelle instruction mémoire à chaque cycle. Ce qui nous amène à la section suivante... ===L'émission parallèle des accès mémoire=== A partir de la quadruple émission, la tendance a été de séparer l'unité mémoire des ALU entières. Cela permet d'émettre des µops mémoire en même temps que des µops entières et/ou flottantes., ce qui porte le nom d''''émission parallèle des µops mémoire''', ou encore ''émission mémoire parallèle''. Par exemple, un processeur quadruple émission peut émettre en même temps deux opérations entières, une opération flottante et un accès mémoire. L'accès mémoire vient en plus des opérations entières, il n'est pas possible de remplacer l'accès mémoire par une opération entière. La technique ne permet pas de faire les calculs d'adresse dans l'ALU entière. Il faut donc utiliser une unité de calcul d'adresse séparée. Elle est intégrée dans l'unité mémoire, qui fait l'interface entre le cache et le banc de registres. Le cout en circuits est très faible, car les unités de calcul d'adresse n'utilisent guère plus qu'un additionneur et un circuit de décalage très simple. L'émission mémoire parallèle est plus facile à implémenter sur les CPU RISC, qui ont des instructions mémoire séparées du reste. Les CPU CISC ne sont pas dans ce cas, ce qui fait que séparer l'unité mémoire du pipeline entier est compliqué. Les CPU superscalaires historiques étaient pour moité des CPU RISC et pour moitié des CPU CISC, ce qui fait qu'ils n'utilisaient pas ou peu l'émission mémoire parallèle. Les premiers processeurs superscalaires se contenaient de l'émission parallèle mémoire, ils ne pouvaient émettre qu'un seul accès mémoire à la fois, l'unité mémoire n'était pas dupliquée. Et ce n'est pas tant un problème que ça, du moins sur les processeurs à double ou triple émission. En effet, il est rare que l'unité mémoire doive faire deux accès simultanés. Notons que le processeur peut exécuter une instruction mémoire en parallèle d'un calcul ou d'un branchement, seuls les accès mémoire simultanés sont impossibles. ===L'émission multiple des accès mémoire avec une LSQ=== Les processeurs superscalaires larges travaillent sur des paquets de 8 à 10 instructions, il est plus fréquent d'avoir plusieurs accès mémoire par paquet. Dans ces conditions, il est utile d'avoir une '''émission multiple des accès mémoire''', à savoir d'émettre plusieurs micro-opérations mémoire en même temps. Les processeurs superscalaires modernes sont capables d'émettre plusieurs lectures/écritures simultanément. Par exemple, ils peuvent émettre une lecture en même temps qu'une écriture, ou plusieurs lectures, ou plusieurs écritures. Il faut noter que selon le processeur, il peut y avoir des restrictions quant aux accès mémoire émis en même temps. Par exemple, certains processeurs peuvent émettre une lecture avec une écriture en même temps, mais pas deux lectures ni deux écritures. Ou encore, ils peuvent émettre deux lectures, une lecture et une écriture, mais pas deux écritures en même temps. Dans la majorité des cas, les processeurs ne permettent pas d'émettre deux écritures en même temps, alors qu'ils supportent plusieurs lectures. Il faut dire que les lectures sont plus fréquentes que les écritures. Les processeurs qui autorisent toutes les combinaisons de lecture/écriture possibles, sont rares. L'émission multiple des accès mémoire s'implémente dans l'unité mémoire. Pour rappel, l'unité mémoire s'interpose entre le cache L1 et le reste du pipeline. Dans le cas le plus simple, elle incorpore une ''load/store queue'' et des unités de calcul d'adresse. L'émission multiple des accès mémoire demande de rendre la ''Load-store queue'' multi-ports, afin de gérer plusieurs micro-opérations mémoire simultanées. Il faut aussi dupliquer les unités de calcul d'adresse reliées à la LSQ, chaque port d'émission mémoire devant avoir sa propre unité de calcul d'adresse. : Dans ce qui suit, nous parlerons d'AGU (''Adress Generation Unit'') pour désigner les unités de calcul d'adresse, et de LSQ pour designer la ''Load-store queue''. [[File:Processeur non-superscalaire basique 02.png|vignette|upright=1|Processeur non-superscalaire basique]] Dupliquer les ports de la LSQ et les AGU est nécessaire. Mais qu'en est-il du cache ? La réponse varie suivant le processeur. Il y a deux grandes méthodes pour implémenter l'émission multiple des accès mémoire. La première méthode ajoute des ports de lecture/écriture au cache de données, pour faire des lectures/écritures simultanées. Il faut alors dupliquer des unités de calcul, rajouter les ports sur le cache, mais aussi rajouter des ports sur la ''load/store queue'', pour qu'elle échange des données/commandes mémoire avec le cache. Le cout en circuits est alors conséquent La seconde duplique les unités de calcul, mais pas les ports d'accès au cache. Les accès mémoire sont alors accumulés dans la ''load/store queue'', qui sont ensuite traités un par un par le cache. Un exemple est celui des processeurs AMD de micro-architecture K7 K8, à savoir les anciens AMD Athlon et AMD Athlon 64, qui pouvaient calculer trois adresses simultanément, mais ne pouvaient faire qu'un seul accès au cache par cycle. Le cout en circuit est modéré : les unités de calcul coutent très peu, les ports de la ''load/store queue'' coutent déjà un peu plus. Quelques processeurs utilisaient des solutions intermédiaires. Un processeur utilisant cette technique est, par exemple, capable d'émettre quatre µops mémoire en même temps, mais avec seulement deux ports sur le cache. Un exemple classique étant les Pentium 1 et 2 d'Intel, dont nous parlerons dans le chapitre suivant. ===L'émission séparée des LOAD et STORE=== De rares processeurs ont des ports d'émission qui peuvent faire indifféremment lecture comme écritures. Mais les processeurs modernes préfèrent séparer ports d'émission pour les lectures et les écritures. Un exemple est celui des processeurs Intel de micro-architecture Nehalem, qui avaient un port de lecture et un port d'écriture tous deux reliés à la ''Load-store queue'', chacun avec leur propre unité de calcul d'adresse. Comme autre exemple, les processeurs skylake ont une LSQ avec deux ports de lecture et un port d'écriture, ce qui permet d'émettre deux lectures en même temps qu'une écriture. Dans cette section, nous allons étudier le cas avec des ports séparés pour les lectures et écritures. En général, les processeurs ont un peu plus de ports de lectures que d'écritures. Une raison est qu'un programme a, en moyenne, un tout petit peu plus de lectures que d'écritures. Mais la différence n'est pas énorme, c'est un léger déséquilibre. Une seconde raison est que l'exécution dans le désordre marche bien mieux quand les lectures ont lieu le plus tôt possible. Donc mieux vaut privilégier les lectures à l'émission, pour qu'elles se fassent un peu en avance. En pratique, la différence n'est pas très marquée. Il est courant d'avoir un port de lecture et un d'écriture, les CPU superscalaires très larges ont souvent deux ports de lecture et deux d'écriture. A la rigueur, quelques CPU ont trois ports de lecture et deux d'écriture. Le cas le plus simple est celui où on peut émettre une lecture et une écriture en même temps. Il s'implémente facilement si la LSQ est en réalité une simple ''Store queue'', à savoir qu'elle met en avance les écritures uniquement. Pour rappel, les écritures sont mises en attente dans la ''Store queue'', mais les lectures sont exécutées immédiatement. Une fois l'adresse à lire connue, la lecture consulte la ''Store queue'' pour détecter les dépendances de données, puis s'exécute. La donnée à lire est lue soit depuis la ''Store queue'', soit depuis le cache. [[File:Emission LOAD - Store séparée.png|vignette|upright=1|Émission LOAD - Store séparée]] L'implémentation de l'émission multiple est alors très simple. La ''Store queue'' a déjà deux ports, avec un pour les lectures et un autre pour les écritures. Idem pour le cache, qui a naturellement un port de lecture et un d'écriture. Émettre une lecture et une écriture simultanément ne demande donc pas de modification digne de ce nom. La ''Store queue'' doit juste supporter une lecture et une écriture simultanée. Ajouter l'émission d'une seconde lecture n'est pas compliquée : il faut juste rajouter un second port de lecture sur le cache et la ''Store queue''. Pour rappel, l'unité mémoire est précédée par une structure qui met en attente les micro-opérations mémoire avant leur émission finale. La structure en question varie suivant l'implémentation, mais un cas particulier se marie bien avec l'émission multiple des µops mémoire. Il s'agit du cas avec une file de µops séparée pour les lectures et une autre pour les écritures, appelées respectivement la ''file de µops LOAD'' et la ''file de µops STORE''. Il est alors possible d'émettre une lecture et une écriture lors du même cycle d'horloge. Le port de lecture alimente une unité de calcul d'adresse dédiée, directement reliée au cache. Le port d'écriture du cache alimente une unité de calcul, qui est suivie par une file d'écriture, elle-même reliée au cache. Les processeurs AMD de micro-architecture K6 utilisaient cette technique. [[File:Double émission avec le Load Ordering, Store Ordering.jpg|centre|vignette|upright=2.5|Double émission avec le Load Ordering, Store Ordering]] ==Les CPU superscalaire à exécution dans le désordre== La superscalarité modifie aussi les circuits dédiés à l'exécution dans le désordre. Elle modifie les unités de renommage de registre, le ROB et les autres structures liées à l'exécution dans le désordre, au sens large. Aucune structure n'est épargnée. Elles ont tous des prots de lecture/écriture en plus, afin d'encaisser l'émission, le ''commit'' ou le renommage simultané de plusieurs µops. Par exemple, le ROB doit être modifié pour émettre et terminer plusieurs instructions en même temps, en ajoutant des ports de lecture/écriture. Pour un CPU superscalaire à 8 voies, on se dit que le ROB peut retirer 8 µops par cycle maximum. Mais certains processeurs récents ont un ROB qui peut retirer le double ! Un exemple est celui des CPU de micro-architecture Skymont, qui peuvent émettre 8 µops par cycle, mais en retirer 16. Quelques CPU de marque AMD étaient aussi dans ce cas. L'avantage est que cela permet de réduire la taille du ROB, du banc de registre et de quelques autres structures matérielles liées à l'exécution dans le désordre. Le gain sur-compense le cout en transistor nécessaire pour retirer 16 µops par cycle. Dans cette section, nous allons voir comment les étages du pipeline dédiés à l'exécution dans le désordre sont modifiées sur un CPU superscalaire. ===La superscalarité avec une file/fenêtre d'instruction centralisée=== Plus haut, nous avons montré qu'un processeur pouvait avoir plus de ports d'émission que de ports de décodage. On dit aussi que sa ''largeur de décodage'' était supérieure à sa ''largeur d'émission''. La '''largeur de décodage''' est le nombre de µops fournies par l'unité de décodage par cycle d'horloge, la '''largeur d'émission''' est le nombre maximal de µops émises par cycle d'horloge. Et n'allez pas croire qu'un surplus de ports d'émission est du gâchis. Sur les processeurs avec exécution dans le désordre, ce surplus peut être utilisé. Mais avant toute chose, évacuons un détail. Vous avez sans doute pensé que le surplus peut être exploité par les instructions machines décodées en plusieurs µops . Par exemple, si une instruction est décodée en deux µops, on pourrait avoir un port d'émission en plus pour en profiter. Mais les ports de décodage sont *en sortie du décodeur*, et contiennent des µops, pas des instructions. Le surplus de ports d'émission peut occasionnellement utiliser le surplus de ports de décodage, mais cela implique que des µops ont été mises en attente dans l'unité d'émission, pour être émises par la suite. Et cela implique l'usage d'une fenêtre d'instruction ou d'une file de µops. Le principe est que les µops sont accumulées dans la fenêtre d'instruction, en attendant leurs opérandes ou la résolution d'une dépendance. Et si l'occasion se présente, une µops est émise sur chaque port d'émission. Il y a donc un gain en performances, mais il faut faire quelques précisions sur son origine. La largeur d'émission utilisée ne peut pas dépasser, *en moyenne*, la largeur de décodage. Et c'est intuitif : un CPU qui décode 4 µops par cycle ne peut pas émettre 6 par cycle en moyenne, sur le temps long, il n'y a pas de µops en surplus qui sortent du vide. Par exemple, sur un processeur quadruple émission, si on analyse combien d'instructions sont émises par cycle, il peut y avoir des cycles où la largeur d'émission est totalement utilisée, mais ce sera compensé par des périodes où elle est fortement sous-utilisée. Les µops sont accumulées en cas de sous-utilisation, puis consommées en cas d'utilisation complète. Les deux périodes se compensent. Et c'est là que l'on peut comprendre d'où vient le gain en performance. Les cycles où les ports d'émission sont sous-utilisés existent systématiquement, que le processeur utilise l'exécution dans le désordre ou pas. Ils sont liés à la présence de dépendances de données, aux contraintes d’appariement, etc. Elles tendent à réduire la moyenne du nombre de µops émises par cycle. Par contre, ce sont les cycles de rattrapage, où les ports en surplus sont utilisés, qui font remonter la moyenne. Le gain en performance est donc lié à ces cycles de rattrapage, où les µops accumulées sont émises dans les ports en surplus. Et ils n'existent que sur les processeurs avec une file/fenêtre d'instruction. Réduire le nombre de ports d'émission permet de garder des fenêtres d'instruction et un ROB simples, rapides et économes en énergie. Et mine de rien, c'est là un compromis important : à quoi bon augmenter le nombre de ports d'émission si c'est pour que ce soit compensé par une fréquence plus faible ? Par contre, réduire le nombre de ports augmente la ''port contention'', surtout si on a mal réparti les ALU. Il faut donc trouver un compromis entre réduire le nombre de ports d'émission d'un côté, réduire la ''port contention'' de l'autre. La différence entre largeur de décodage et d'émission a un impact assez fort sur le banc de registre, et précisément sur son nombre de ports de lecture. Le banc de registre a besoin de plus de ports de lecture avec une fenêtre d’instruction qu'avec des stations de réservation. Rappelons la terminologie utilisée dans ce cours. Les fenêtres d'instruction se contentent de mémoriser la micro-opération et quelques bits pour détecter la disponibilité des opérandes. Par contre, les stations de réservations mémorisent aussi les opérandes des instructions. Et cette différence a une influence le nombre de ports de lecture du banc de registre. Supposons que toutes les instructions sont dyadiques, à deux opérandes. Comptons le nombre de ports nécessaires pour alimenter les unités de calcul, sans utiliser d'arbitre de banc de registre. Avec une fenêtre d'instruction, il faut le double du nombre de ports d'émission. Mais avec des stations de réservation, il faut le double des ports de décodage. La raison est que les registres sont lus après émission avec une fenêtre d'instruction, avant avec des stations de réservation (sous-entendu, après le décodage). Or, comme dit plus haut, les processeurs superscalaires larges ont souvent plus de ports d'émission que de décodage. ===La superscalarité à fenêtres d'instruction décentralisées=== Les processeurs précédents utilisaient une unité d'émission simple, qui pouvait utiliser l'émission dans l'ordre ou dans le désordre. Elle peut être remplacée par une fenêtre d'instruction centralisée dans que cela quoi que ce soit aux explications précédentes, idem avec une station de réservation centralisée. En soi, l'exécution dans le désordre est donc totalement compatible avec une implémentation simple de la superscalarité. Par contre, des subtilités surviennent avec les fenêtres d'instruction décentralisées. Pour rappel, sur ces processeurs, les µops sont émises en deux phases. Une première phase de ''dispatch'' qui envoie les µops vers la fenêtre d'instruction adéquate, sans détecter les dépendances de données. La seconde phase de ''schedule'' gère les dépendances de données, elle met en attente les µops en attendant que l'unité soit disponible pour les exécuter, en attendant aussi que les opérandes soient disponibles. Et à ce petit jeu, il y a une différence entre le nombre de µops qui sont ''dispatch par cycle'' et celles qui sont ''schedule''. Dit autrement, la largeur d'émission est scindée en une '''largeur de ''dispatch''''' et une '''largeur de ''schedule''. Par exemple, prenons un processeur triple émission avec deux stations de réservation : une pour les µops entières/mémoire, et une autre pour les µops flottantes. L'unité d'émission peut ''dispatch'' maximum trois µops par cycle : soit trois µops entières/mémoire, soit trois flottantes, soit un mix de trois µops différentes. Les stations de réservation, quant à elles, peuvent chacune ''schedule'' 3 µops par cycle, vers trois ALUs entières pour la station de réservation entière/mémoire, vers trois FPS pour la station de réservation flottante. De quoi ''dispatch'' trois µops, mais d'en ''schedule'' six, à chaque cycle. Et cet exemple ne sort pas de nulle part : les CPU x86 d'Intel et AMD sont globalement dans ce cas, même si les chiffres varient d'une microarchitecture à l'autre. L'exemple précédent colle parfaitement à la microarchitecture K7 d'AMD, utilisée sur les processeurs Athlon dans les années 2000. [[File:AMD K7.png|centre|vignette|upright=2.5|AMD K7, microarchitecture.]] L'exemple précédent avait assez d'unités entières/flottantes pour couvrir toute la largeur de décodage/''Dispatch''. Mais ce n'est pas toujours le cas, comme va le montrer l'exemple suivant. Le MIPS R10000, qui utilisait un mélange de files de µops et de fenêtres d'instructions : une file de µops les accès mémoire, une fenêtre pour les instructions entières et une autre pour les opérations flottantes. Les branchements et NOPs étaient exécutés dans une unité de branchement distincte. Les trois files/fenêtres peuvent mémoriser 16 µops. Le processeur avait deux ALU entières, une AGU de calcul d'adresse, un additionneur flottant et un multiplieur/diviseur flottant. Il y avait un multiplieur entier sur le port d'émission l'ALU 2. Le processeur décodait 4 instructions par cycle et produisait jusqu'à 4µops, qui étaient redistribuées sur les trois fenêtres d'instructions. Chacune pouvait accepter 4 µops par cycle, ce qui fait que processeur pouvait ''dispatch'' 4 µops entières, ou 4 µops flottantes, ou 4 µops mémoire, ou un mélange de deux ou trois types de µops. Pour ce qui est du ''schedule'', la fenêtre d'instruction entière pouvait émettre 2 µops, la fenêtre flottante pouvait émettre une addition en même temps qu'une multiplication/divison flottante et qu'un branchement conditionnel flottant, et la file de µops mémoire ne pouvait émettre qu'une µop mémoire à la fois. En tout, cela fait 6 µops ''schedule'' pour 4 µops au ''dispatch''. [[File:R 10000.png|centre|vignette|upright=2.5|MIPS R10000, microarchitecture.]] ===L'unité de renommage superscalaire=== Avec la superscalarité, l'unité de renommage de registre doit gérer le cas où des instructions consécutives ont des dépendances de registre. Par exemple, prenons un processeur à double émission, qui renomme deux instructions consécutives. Si elles ont une dépendance de registre, le renommage de la seconde instruction dépend du renommage de la première. La première doit être renommée et le résultat du renommage est utilisé pour renommer la seconde, ce qui empêche d'utiliser deux unités de renommage séparées. Pour cela, elle renomme les registres sans tenir compte des dépendances, pour ensuite corriger le résultat. [[File:Unité de renommage superscalaire.png|centre|vignette|upright=2|Unité de renommage superscalaire.]] Seules les dépendances lecture-après-écriture doivent être détectées, les autres étant supprimées par le renommage de registres. Repérer ce genre de dépendances se fait assez simplement : il suffit de regarder si un registre de destination d'une instruction est un opérande d'une instruction suivante. [[File:Détection des dépendances sur un processeur superscalaire.png|centre|vignette|upright=2|Détection des dépendances sur un processeur superscalaire.]] Ensuite, il faut corriger le résultat du renommage en fonction des dépendances. Si une instruction n'a pas de dépendance avec une autre, on la laisse telle quelle. Dans le cas contraire, un registre opérande sera identique avec le registre de destination d'une instruction précédente. Dans ce cas, le registre opérande n'est pas le bon après renommage : on doit le remplacer par le registre de destination de l'instruction avec laquelle il y a dépendance. Cela se fait simplement en utilisant un multiplexeur dont les entrées sont reliées à l'unité de détection des dépendances. On doit faire ce replacement pour chaque registre opérande. [[File:Correction des dépendances sur un processeur superscalaire.png|centre|vignette|upright=2|Correction des dépendances sur un processeur superscalaire.]] ==Les optimisations des processeurs superscalaires larges== Les processeurs superscalaires larges ont des optimisations spécifiques, qui n'ont pas de sens sur les processeurs superscalaires étroits. Dans les grandes lignes, elles se classent en deux types. Les premières permettent de charger/décoder plus de 5-10 instructions simultanément. Le problème est alors que les branchements viennent mettre leur grain de sel et qu'il faut en tenir compte. Les secondes corrigent des problèmes qui apparaissent quand un CPU intègre plus de 5 à 10 unités de calcul. ===L'étape de chargement d'un CPU superscalaire large=== Charger plusieurs instructions pose de nombreux problèmes sur les processeurs superscalaires larges, qui émettent de 5 à 10 instructions simultanées. Pour rappel, la stratégie basique pour charger N instructions est de charger un bloc de N instructions consécutives. Les instructions après un branchement ne sont pas décodées ni exécutées, ce qui entraine une petite baisse de performance. Pour un N petit, la perte de performance est mineure. Mais pour un N large, elle devient suffisamment importante pour que la conception du CPU doit alors être repensée de fond en comble. Les analyses statistiques disent qu'il y a environ un branchement tous les 4 à 6 instructions. Un CPU a donc tendance à décoder des paquets de 3 à 5 instructions consécutives, avant qu'un branchement casse la dynamique. Or, cela pose des problèmes pour utiliser plusieurs décodeurs. L'implémentation basique, qui charge N instructions consécutives pour alimenter N décodeurs, fonctionne donc jusqu'à 4/6 décodeurs. Au-delà de 5 instructions, un CPU superscalaire doit être adapté pour gérer les branchements dans les paquets d'instruction qu'il émet. Le nombre de 5 instructions ne sort donc pas de nulle part. La première solution utilise un cache de µops, avec un ''front-end'' capable de charger/décoder 4/5 instructions à la fois, pas plus. L'idée est que la majorité des instructions proviennent du cache de µops (plus de 80%), ce qui fait qu'on peut émettre environ 10 instructions à partir du cache de µops, pas besoin d'avoir beaucoup de décodeurs. Les performances sont cependant dégradées lors d'un défaut de cache de µops. Les instructions doivent alors être chargées depuis le cache d'instruction, par paquets de 4-5, puis sont décodées par 4-5 décodeurs, ce qui ne permet pas d’alimenter les unités de calcul à plein régime. La solution précédente joue sur la distinction entre pots de décodage et d'émission. Elle permet d'émettre un grand nombre de µops sans pour autant charger un grand nombre d'instructions en même temps. En quelque sorte, ce n'est qu'une méthode de contournement, le CPU superscalaire reste de type étroit. Mais il est aussi possible d'utiliser un '''''front-end large''''', capable de charger une bonne dizaine d'instructions en même temps. La seule difficulté est de tenir compte de la présence de branchements. Pour rappel, avec un ''front-end'' étroit, les instructions situées après le premier branchement pris sont annulées, elles ne sont pas décodées ni exécutées. [[File:Fetch sur un processeur superscalaire avec prediction de branchements.png|centre|vignette|upright=2|Fetch sur un processeur superscalaire avec prédiction de branchements.]] Un ''front-end'' large charge les instructions de destination du branchement et les placent à sa suite. Ils chargent deux blocs à la fois et les fusionnent en un seul qui ne contient que les instructions présumées utiles. [[File:Cache d'instructions autoaligné.png|centre|vignette|upright=2|Cache d'instructions auto-aligné.]] Mais cela demande de charger deux blocs de mémoire en une fois, ce qui demande un cache d'instruction multiports. Il faut aussi ajouter un circuit pour assembler plusieurs morceaux de blocs en un seul : le fusionneur (''merger''). En pratique, cette solution demande d'ajouter un second port de lecture au cache, pour un cout en hardware important, avec un impact négatif sur le temps d'accès au cache. Et il faut rajouter le fusionneur, qui prend un étage de pipeline à lui tout seul. Et encore, cette solution ne gère pas le cas où un bloc contient plusieurs branchements ! [[File:Implémentation d'un cache d'instructions autoaligné.png|centre|vignette|upright=2|Implémentation d'un cache d'instructions auto-aligné.]] Les exemples précédents partent du principe qu'il n'y a qu'un seul branchement dans un bloc. Mais quand les blocs deviennent assez longs, il est fréquent d'avoir plusieurs branchements dans un bloc. Et il est même possible d'avoir plusieurs branchements pris. Dans un bloc de 8-10 instructions, ce n'est pas rare, et les CPU superscalaires à 8-10 voies sont la règle dans les CPU des années 2020. Pour gérer ce cas, il faut ajouter des ports supplémentaires au cache d'instruction et adapter le fusionneur, pour qu'il puisse fusionner trois ou quatre blocs. De plus, il faut utiliser une unité de prédiction de branchement capable de ''prédire plusieurs branchements par cycle''. ===Le décodage superscalaire large=== Le décodage des instructions n'est pas censé dépendre de l'implémentation du chargement. En théorie, une fois plusieurs blocs d'instruction fusionnés, on peut décoder les instructions en parallèle. Cependant, certains processeurs effectuent le décodage en tenant compte de la manière dont les branchements découpent les blocs. Les processeurs Atom d'Intel utilisent une solution de ce type, qui couple prédiction de branchement et décodage parallèle. Ils chargent/décodent des paquets de 3 instructions consécutives, chaque paquet étant séparé du précédent par un branchement. Pour cela, en plus d'avoir un fusionneur adapté, ils regroupent les décodeurs en ''clusters'', chacun regroupant 3 décodeurs, et pouvant décoder trois instructions consécutives. Les micro-architectures Tremont et Crestmont avaient deux ''clusters'' (6 décodeurs au total), la micro-architecture Skymont en intègre trois (9 décodeurs au total). Avec deux ''clusters'', le fonctionnement est le suivant. Si le code est composé d'une suite d'instruction sans branchements, seul le premier ''cluster'' est utilisé, limitant le décodage à trois instructions par cycle. Par contre, si l'une des trois instructions décodée est un branchement, alors le second ''cluster'' décode les trois instructions situées après ce branchement, à partir de l'adresse de destination. Avec un troisième ''cluster'', c'est la même chose, sauf qu'il faut qu'il y ait un branchement dans les trois instructions décodées par le second ''cluster''. Mais comment faire pour savoir quels décodeurs activés, quels ''clusters'' sont actifs ? C'est la prédiction de branchement qui commande. Avec deux ''clusters'', elle détermine si un branchement est présent dans les trois instructions à charger, et quelle est son adresse de destination. Sans branchement, elle prévient les décodeurs et les trois instructions chargées sont décodées dans un ''cluster'' libre. Si elle prédit un branchement, elle charge les instructions adéquates, et active les deux ''clusters''. Avec trois branchements, c'est le même mécanisme, sauf que l'unité de prédiction de branchement doit être capable de prédire deux branchements par cycle. Il faut noter que les décodeurs d'un ''cluster'' sont des décodeurs simples, sans microcode. Sur les processeurs Tremont et Crestmont, le microcode était unique et était partagé entre tous les ''clusters''. Un seul ''cluster'' pouvait décoder une instruction complexe avec le microcode, l'autre devait se limiter à des instructions simples uniquement. Depuis Skymont, le microcode est partiellement dupliqué, avec un microcode par ''cluster''. Le cout en hardware peut paraitre prohibitif, pour un gain en performance faible. Sauf que c'est oublier que ce sont des processeurs Atom, sur lesquels beaucoup d'instructions sont microcodées. Les processeurs Atom ont un budget en transistor limité, ce qui fait qu'il est préférable pour eux d'implémenter des instructions en microcode que d'utiliser des unités de calcul dédiées ou des circuits de décodage complexes. Pour limiter la casse, le microcode n'est que partiellement dupliqué. Seul le microcode pour les instructions fréquente est dupliqué. Il y a un microcode secondaire pour certaines instructions, qui est lui unique et partagé par tous les ''clusters''. ===Le contournement sur les processeurs superscalaires larges=== Pour rappel, la technique du contournement (''register bypass'') permet au résultat d'une instruction d'être immédiatement utilisable en sortie de l'ALU, avant même d'être enregistré dans les registres. Implémenter la technique du contournement demande d'utiliser des multiplexeurs pour relier la sortie de l'unité de calcul sur son entrée si besoin. Il faut aussi des comparateurs pour détecter des dépendances de données. [[File:Pipeline Bypass.png|centre|vignette|upright=1|Pipeline Bypass]] La sortie de chaque ALU doit être reliée aux entrées de toutes les autres, avec les comparateurs qui vont avec. Sur les processeurs ayant plusieurs d'unités de calculs, cela demande beaucoup d’interconnexions et de comparateurs. Pour N unités de calcul, cela demande 2 * N² interconnexions, implémentées avec 2N multiplexeurs de N entrées chacun. Si c'est faisable pour 2 ou 3 ALUs, la solution est impraticable au-delà. Non seulement ce serait un enfer à câbler, mais les fils seraient tellement longs que la fréquence du processeur en prendrait un coup. Notez que cela vaut pour les processeurs superscalaires, mais aussi pour tout processeur avec beaucoup d'unités de calcul. En théorie, un CPU à exécution dans le désordre, non-superscalaire, mais avec beaucoup d'unités de calcul, fait face au même problème. En pratique, avoir une dizaine d'ALU implique processeur superscalaire à exécution dans le désordre. D'où le fait qu'on parle du problème maintenant. La seule solution praticable est de ne pas relier toutes les unités de calcul ensemble. À la place, on préfère regrouper les unités de calcul dans différents '''agglomérats''' ('''cluster'''). Le contournement est alors possible entre les unités d'un même agglomérat, mais pas entre agglomérats différents. Évidemment, certaines possibilités de contournement sont perdues, mais c'est compensé par un gain en fréquence et un cout en circuit/fils réduit. C'est un bon compromis, ce qui explique que presque tous les processeurs modernes l'utilisent. Les rares exceptions sont les processeurs POWER 4 et POWER 5, qui ont préféré se passer de contournement pour garder un processeur très simple et une fréquence élevée. ===Le banc de registre des processeurs superscalaires larges=== Pour émettre deux opérations entières simultanément, il faut lire deux fois plus d'opérandes. Et il faut aussi écrire deux résultats dans les registres, en même temps. Pour cela, on double les ports du banc de registre. En général, il faut au maximum 2 ports de lecture et un port d'écriture par unité de calcul. Le problème, c'est que plus un banc de registres a de ports, plus il utilise de circuits, est compliqué à concevoir, consomme de courant et chauffe. Avec plusieurs dizaines d'unités de calcul différentes, ajouter autant de ports impacterait la fréquence du banc de registres et réduirait les performances, voire est totalement impraticable à cause des contraintes thermiques. Les processeurs superscalaires larges doivent donc trouver des solutions. La première solution utilise un banc de registre unique, mais n'utilise pas autant de ports que le pire des cas le demanderait. Le processeur détecte quand il n'y a pas assez de ports pour servir toutes les micro-opérations. Quand ça arrive, l'unité d'émission émet assez de micro-opérations pour exploiter les ports disponibles, mais met en attente les micro-opérations en trop, le temps que les ports se libèrent. Pour cela, le banc de registre est géré par un circuit d'arbitrage spécialisé, intégré à l'unité d'émission, l’'''arbitre du banc de registres''' (''register file arbiter''). Une autre solution est de dupliquer le banc de registres en plusieurs exemplaires qui contiennent exactement les mêmes données, mais avec moins de ports de lecture/écriture. Un exemple est celui des processeurs POWER, Alpha 21264 et Alpha 21464. Sur ces processeurs, le banc de registre est dupliqué en plusieurs exemplaires, qui contiennent exactement les mêmes données. Les écritures dans les registres sont envoyées à tous les bancs de registres, afin de garantir que leur contenu est identique. Chaque exemplaire a exactement deux ports de lecture. La conception du processeur est simplifiée, que ce soit au niveau du câblage, que de la conception des bancs de registres. Une dernière solution découpe le banc de registres en plusieurs bancs de registres plus petits, avec un réseau d'interconnexions pour échanger des données entre bancs de registres. Dans la plupart des cas, cette séparation est invisible du point de vue de l'assembleur et du langage machine. Le processeur se charge de transférer les données entre bancs de registres suivant les besoins. Sur d'autres processeurs, les transferts de données se font via une instruction spéciale, souvent appelée ''COPY''. [[File:Banc de registres distribué.png|centre|vignette|upright=2|Banc de registres distribué.]] Sur de certains processeurs, un branchement est exécuté par une unité de calcul spécialisée. Or les registres à lire pour déterminer l'adresse de destination du branchement ne sont pas forcément dans le même agglomérat que cette unité de calcul. Pour éviter cela, certains processeurs disposent d'une unité de calcul des branchements dans chaque agglomérat. Dans les cas où plusieurs unités veulent modifier le ''program counter'' en même temps, un système de contrôle général décide quelle unité a la priorité sur les autres. Mais d'autres processeurs fonctionnent autrement : seul un agglomérat possède une unité de branchement, qui peut recevoir des résultats de tests de toutes les autres unités de calcul, quel que soit l’agglomérat. ==La macro-fusion : une optimisation du décodage parallèle== Les processeurs superscalaires supportent la technique dite de '''macro-fusion''', qui permet de fusionner deux-trois instructions consécutives en une seule micro-opération. Par exemple, il est possible fusionner une instruction de test et une instruction de saut en une seule micro-opération de branchement. Il s'agit là de l'utilisation la plus importante de la macro-fusion sur les processeurs x86 modernes. La macro-fusion est utilisée pour fusionner un test et un saut en un branchement unique. L'optimisation est présente sur la plupart des CPU superscalaires modernes. Les CPU x86 l'implémentent, mais aussi quelques CPU ARM et les processeurs Apple de série M (M1 à M5, et potentiellement plus quand ils seront sortis). La fusion des instructions se fait lors du décodage des instructions, grâce à la coopération des décodeurs. Elle marche systématiquement si l'instruction de test et le saut sont consécutives, qu'elles forment bien une paire d'instruction. Par contre, insérer une instruction entre les deux empêche la macro-fusion de faire son travail, sauf si l'instruction insérée est un NOP. Les processeurs ARM64 avaient une forme spécifique de macro-fusion, utilisée pour un cas très précis : charger une constante immédiate de 32 ou 64 bits, dans un registre 64 bits. Les CPU ARM 64 bits utilisent des instructions codées sur 32 bits, qui ne peuvent encoder que des constantes immédiates de 16 bits. Pour charger une constante de 32/64 bits, il faut s'y prendre en plusieurs fois. Une première instruction MOVZ charge les 16 premiers bits, puis 2 à 4 instructions MOVK remplissent le reste. MOVZ met à zéro les 48 bits de poids fort, MOVK permet de sélectionner le doublet à modifier dans le registre (on modifie 16 bits, dont on choisit la place, le reste du registre est laissé tel quel). ARM permet de fusionner cette suite d'instructions en une seule µop MOV avec une constante immédiate. On parle alors de '''''MOVZ + MOVK fusion'''''. Une dernière utilisation, [https://www2.eecs.berkeley.edu/Pubs/TechRpts/2016/EECS-2016-130.pdf proposées pour le jeu d'instruction RISC-V], fusionnait un calcul d'adresse et une instruction LOAD/STORE en une seule instruction d'accès mémoire. Elle a été envisagée à une époque où le jeu d'instruction RISC-V ne gérait pas les modes d'adressage base + indice. L'idée était d'avoir une unité mémoire qui gérait le mode d'adressage base + indice et de fusionner des instructions pour l'exploiter. Mais de nos jours, le jeu d'instruction RISC-V intègre le mode d'adressage base + indice, ce qui fait que la macro-fusion n'a aucun intérêt pour ce cas précis. La macro-fusion a de nombreuses limitations en pratique. En théorie, on pourrait l'utiliser au-delà des deux situations mentionnées plus haut. Par exemple, pour fusionner une addition et une multiplication en une seule instruction MAD. Mais l'intérêt est assez faible. Notons que la macro-fusion ne permet que de fusionner des paires d'instructions. Aller au-delà demanderait un cout en circuit trop important. Déjà que tester deux paires utilise pas mal de comparateurs... ==Le ''stack engine'' : une optimisation de la pile d'appel== Les processeurs modernes intègrent une optimisation liée au pointeur de pile. Pour rappel, sur les architectures modernes, le pointeur de pile est un registre utilisé pour gérer la pile d'appel, précisément pour savoir où se trouve le sommet de la pile. Il est manipulé par les instructions CALL, RET, PUSH et POP, mais aussi par les instructions d'addition/soustraction pour gérer des cadres de pile de taille variable. De plus, il peut servir d'opérande pour des instructions LOAD/STORE, afin de lire/écrire des variables locales, les arguments d'une fonction, et autres. C'est donc un registre général adressable, intégré au banc de registre, altéré par l'unité de calcul entière. L'incrémentation/décrémentation du pointeur de pile passe donc par l'unité de calcul, lors des instructions CALL, RET, PUSH et POP. Mais, l'optimisation que nous allons voir permet d'incrémenter/décrémenter le pointeur de pile sans passer par l'ALU, ou presque. L'idée est de s'inspirer des architectures avec une pile d'adresse de retour, qui intègrent le pointeur de pile dans le séquenceur et l'incrémentent avec un incrémenteur dédié. ===Le ''stack engine''=== L'optimisation que nous allons voir utilise un '''''stack engine''''' intégré à l'unité de contrôle, au séquenceur. Le processeur contient toujours un pointeur de pile dans le banc de registre, cela ne change pas. Par contre, il n'est pas incrémenté/décrémenté à chaque instruction CALL, RET, PUSH, POP. Un compteur intégré au séquenceur est incrémenté à la place, nous l’appellerons le '''compteur delta'''. La vraie valeur du pointeur de pile s'obtient en additionnant le compteur delta avec le registre dans le banc de registre. Précisons que si le compteur delta vaut zéro, la vraie valeur est dans le banc de registre et peut s'utiliser telle quelle. Lorsqu'une instruction ADD/SUB/LOAD/STORE utilise le pointeur de pile comme opérande, elle a besoin de la vraie valeur. Si elle n'est pas dans le banc de registre, le séquenceur déclenche l'addition compteur-registre pour calculer la vraie valeur. Finalement, le banc de registre contient alors la bonne valeur et l'instruction peut s'exécuter sans encombre. L'idée est que le pointeur de pile est généralement altéré par une série d'instruction PUSH/POP consécutives, puis des instructions LOAD/STORE/ADD/SUB utilisent le pointeur de pile final comme opérande. En clair, une bonne partie des incrémentations/décrémentation est accumulée dans le compteur delta, puis la vraie valeur est calculée une fois pour toutes et est utilisée comme opérande. On accumule un delta dans le compteur delta, et ce compteur delta est additionné quand nécessaire. Précisons que le compteur delta est placé juste après le décodeur d'instruction, avant même le cache de micro-opération, l'unité de renommage et l'unité d'émission. Ainsi, les incrémentations/décrémentations du pointeur de pile disparaissent dès l'unité de décodage. Elles ne prennent pas de place dans le cache de micro-opération, ni dans la fenêtre d'instruction, ni dans la suite du pipeline. De nombreuses ressources sont économisées dans le ''front-end''. Mais utiliser un ''stack engine'' a aussi de nombreux avantages au niveau du chemin de données/''back-end''. Déjà, sur les processeurs à exécution dans le désordre, cela libère une unité de calcul qui peut être utilisée pour faire d'autres calculs. Ensuite, le compteur delta mémorise un delta assez court, de 8 bits sur le processeur Pentium M, un peu plus pour les suivants. L'incrémentation se fait donc via un incrémenteur 8 bits, pas une grosse ALU 32/64 bits. Il y a un gain en termes de consommation d'énergie, un incrémenteur 8 bits étant moins gourmand qu'une grosse ALU 32/64 bits. ===Les points de synchronisation du delta=== La technique ne fonctionne que si la vraie valeur du pointeur de pile est calculée au bon moment, avant chaque utilisation pertinente. Il y a donc des '''points de synchronisation''' qui forcent le calcul de la vraie valeur. Plus haut, nous avions dit que c'était à chaque fois qu'une instruction adresse le pointeur de pile explicitement, qui l'utilise comme opérande. Les instructions CALL, RET, PUSH et POP ne sont pas concernées par elles utilisent le pointeur de pile de manière implicite et ne font que l'incrémenter/décrémenter. Mais dans les faits, c'est plus compliqué. D'autres situations peuvent forcer une synchronisation, notamment un débordement du compteur delta. Le compteur delta est généralement un compteur de 8 bits, ce qui fait qu'il peut déborder. En cas de débordement du compteur, le séquenceur déclenche le calcul de la vraie valeur, puis réinitialise le compteur delta. La vraie valeur est donc calculée en avance dans ce cas précis. Précisons qu'un compteur delta de 8 bits permet de gérer environ 30 instructions PUSH/POP consécutives, ce qui rend les débordements de compteur delta assez peu fréquent. À noter que si le compteur delta vaut zéro, il n'y a pas besoin de calculer la vraie valeur, le séquenceur prend cette situation en compte. Un autre point de synchronisation est celui des interruptions et exceptions matérielles. Il faut que le compteur delta soit sauvegardé lors d'une interruption et restauré quand elle se termine. Idem lors d'une commutation de contexte, quand on passe d'un programme à un autre. Pour cela, le processeur peut déclencher le calcul de la vraie valeur lors d'une interruption, avant de sauvegarder les registres. Pour cela, le processeur intègre un mécanisme de sauvegarde automatique, qui mémorise la valeur de ce compteur dans le tampon de réordonnancement, pour forcer le calcul de la vraie valeur en cas de problème. La technique du ''stack engine'' est apparue sur les processeurs Pentium M d'Intel et les processeurs K10 d'AMD, et a été conservée sur tous les modèles ultérieurs. L'implémentation est cependant différente selon les processeurs, bien qu'on n'en connaisse pas les détails et que l'on doive se contenter des résultats de micro-benchmarks et des détails fournit par Intel et AMD. Selon certaines sources, dont les manuels d'optimisation d'Agner Fog, les processeurs AMD auraient moins de points de synchronisation que les processeurs Intel. De plus, leur ''stack engine'' serait placé plus loin que prévu dans le pipeline, après la file de micro-opération. ==La micro-fusion et la délamination== La '''micro-fusion''' est une optimisation qui retarde le décodage réel de certaines instructions assez loin dans le pipeline. Elle concerne surtout les instructions ''load-op'', mais pas que. D'autres instructions mémoire peuvent subir cette optimisation. L'essentiel est que, sans micro-fusion, ces instructions sont décodées en plusieurs micro-opérations. La micro-fusion retarde le décodage final de ces instructions, qui se fait assez tard dans le pipeline. Pour comprendre l'intérêt, nous allons devoir faire quelques explications. L'optimisation en question fait le décodage en deux temps. Prenons l'exemple d'une opération ''load-op'', qui est censée être décodée en deux micro-opérations : une lecture, puis une opération. Le décodage produit une '''macro-opération''' (terme d'Intel), qui est propagée dans le pipeline, pour être scindée en deux micro-opérations un peu avant les ALU et l'unité mémoire. La macro-opération est techniquement une micro-opération comme une autre, sauf que ce n'est pas la micro-opération finale. L'unité d'émission découpe cette macro-opération en deux micro-opérations, qui sont émises l'une après l'autre. Pour résumer, les instructions ''load-op'' ne sont complétement découpées en micro-opération lors de l'émission, pas lors du décodage. L'optimisation impacte ce qui se situe entre le décodeur d'instruction et l'unité d'émission : l'unité de renommage, la file de micro-opérations, le ''Loop Stream Detector'' et/ou le cache de micro-opération. L'intérêt de l'optimisation est que l'on fusionne deux micro-opérations en une seule macro-opération. Le cache de micro-opération et la file de micro-opération peuvent mémoriser plus de micro-opérations, vu que certaines sont fusionnées. Quant à l'unité de renommage, elle renomme une seule macro-opération au lieu de renommer deux micro-opérations, ce qui augmente le débit du renommage de registre. Par contre, cela complique un peu le travail de l'unité d'émission, qui doit découper les instructions ''load-op'' en deux et émettre les micro-opérations l'une après l'autre. Il faut noter qu'il n'y a pas de fusion proprement dite. Le décodeur ne décode pas deux micro-opérations séparées, avant de les fusionner. En réalité, il génère directement une macro-opération, qui correspond à deux micro-opérations, que ce soit plus tard dans le pipeline ou sur une autre architecture. D'ailleurs, la micro-fusion a lieu à l'intérieur d'une instruction, qui est censée être traduite en plusieurs micro-opérations. Et encore : pas toutes. La conséquence est que la technique n'a de sens que sur les processeurs CISC, avec des instructions assez complexes pour être décodées en plusieurs micro-opérations. En pratique, seuls les CPU x86 sont concernés. ===Les domaines de fusion=== La micro-fusion n'est pas spécifique aux processeurs superscalaires. En théorie, on peut l'implémenter sur un processeur non-superscalaire à émission dans l'ordre. Cependant, je ne connais aucun processeur à émission dans l'ordre qui implémente la micro-fusion, même si son implémentation est en théorie possible sur de tels processeurs. La micro-fusion est implémentée différemment selon que l'émission se fasse dans l'ordre, dans le désordre, avec une fenêtre d'instruction, avec des stations de réservation, etc. J'ai dit plus haut que les micro-opérations sont générées lors de l'émission, mais le moment exact varie suivant le processeur. Pour simplifier le tout, nous parlerons de '''scission''' quand le processeur traduit une macro-opération en deux/trois micro-opérations. Le moment où a lieu la scission est théoriquement dans l'unité d'émission, mais celle-ci prend plusieurs formes selon que le processeur utilise l'émission dans l'ordre, dans le désordre, avec une fenêtre d'instruction, avec des stations de réservation, etc. Les processeurs à exécution dans l'ordre sont le cas le plus simple. L'unité d'émission est alors en charge du découpage des macro-opérations en micro-opérations. La file de micro-opération ou le cache de micro-opérations mémorisent des macro-opérations. À l'opposé, le tampon de ré-ordonnancement mémorise lui des micro-opérations. Le reste du processeur n'est pas affecté. Sur les processeurs à exécution dans le désordre, les différences sont nombreuses. Le tampon de ré-ordonnancement peut mémoriser soit les micro-opérations finales, soit des macro-opérations. Il en est de même avec la fenêtre d'instruction et/ou les stations de réservation, qu'elles soient centralisées ou décentralisées. Tout dépend de comment se fait l'implémentation. Une implémentation simple effectue la scission des macro-opérations dans l'unité d'émission, comme sur les processeurs à exécution dans l'ordre. Sur de nombreux processeurs, l'unité d'émission et de renommage sont simplement fusionnées, ce qui fait que la scission a lieu juste après le renommage de registres. En conséquence, le tampon de re-ordonnancement et les fenêtres d'instruction ne voient que des micro-opérations. Les macro-opérations disparaissent juste avant. Cette implémentation est préférée avec une fenêtre d'instruction centralisée, car on voit mal comment scinder une macro-opération juste avant l'ALU. Une autre implémentation fait la scission en sortie de la fenêtre d'instruction ou des stations de réservation, qu'elles soient centralisées ou décentralisées. Dans ce cas, le tampon de re-ordonnancement fait de même. L'avantage est que le tampon de ré-ordonnancement stocke des macro-opérations, ce qui augmente sa taille effective. Par exemple, il utilise une seule entrée pour une instruction ''load-op'', au lieu de deux sans micro-fusion de ce type. ===La délamination=== La scission peut parfois avoir lieu plus tôt, dans des circonstances très précises. Concrètement, cela ne concerne que quelques micro-architectures d'Intel, pour des instructions très précises. La raison est les limitations du format des micro-opérations, pas une histoire d'optimisation. Le processeur aimerait pouvoir faire la micro-fusion, mais ne peut pas à cause de ces limitations. Une telle situation cause alors une '''délamination''' : les micro-opérations sont scindées précocement. Par exemple, sur la micro-architecture SandyBridge d'Intel, les opérations ''load-op'' sont fusionnées, sauf si elles utilisent l'adressage indicé. Les micro-opérations de ce processeur ne peuvent avoir que deux opérandes. Trois opérandes est de trop, et les opérations ''load-op'' en adressage indicé dépassent cette limite. Dans ce cas, les micro-opérations sont fusionnées en sortie du décodeur, mais sont scindés avant d’entrer dans la file de micro-opération. L'avantage n'est pas évident, vu que l'unité de renommage suit de près le décodeur d'instruction. Il est que le cache de micro-opérations mémorise des macro-opérations, pas deux micro-opérations. ===La micro-fusion des processeurs Intel et AMD=== La micro-fusion est apparue sur le Pentium M d'Intel, et a été conservée telle quelle sur les processeurs Intel suivants. Les processeurs AMD supportent la micro-fusion depuis au moins la micro-architecture AMD K8. Mieux que ça, la délamination n'était pas utilisée car les micro-opérations acceptaient plus de 2 opérandes. Que ce soit chez AMD ou Intel, elle concerne deux types d'instructions : les écritures mémoire et les instructions ''load-op''. Sur le Pentium M, il y avait quelques limitations, l'optimisation se limitant par exemple aux registres entiers et flottants, mais pas aux registres MMX, mais pas les registres SSE (nous verrons ces registres dans quelques chapitres). Mais les processeurs modernes n'ont plus cette limitation. La micro-fusion des CPU Intel concerne deux types d'instructions : les écritures mémoire et les instructions ''load-op''. Pour les écritures, la situation est plus complexe que pour les instructions ''load-op''. Sur les processeurs Intel précédents, les écritures étaient réalisées dans deux unités séparées : le calcul d'adresse dans une unité de calcul, l'écriture proprement dite dans l'unité mémoire. Elles prenaient donc deux micro-opérations. Le processeur a changé ses unités mémoire, ce qui fait qu'elle pouvait faire les deux en une seule micro-opération. Il s'agissait alors d'une optimisation du chemin de données, plus que de la micro-fusion proprement dite, mais je la mentionne ici malgré tout. La micro-fusion sur ce processeur avait un avantage en plus des avantages usuels : elle utilisait mieux les décodeurs. Les Pentium M avaient trois décodeurs pour décoder plusieurs instructions en même temps, chose qu'on détaillera dans le chapitre sur les processeurs superscalaires. Un seul décodeur pouvait décoder toutes les instructions, les deux autres ne peuvent décoder que les instructions simples, qui se décodent en une seule micro-opération. Avec la micro-fusion, les trois décodeurs peuvent gérer les écritures mémoire et les opérations ''load-op'', vu qu'elles deviennent des instructions simples. Sans cela, elles auraient été limitées au premier décodeur. <noinclude> {{NavChapitre | book=Fonctionnement d'un ordinateur | prev=Le parallélisme mémoire | prevText=Le parallélisme mémoire | next=Exemples de microarchitectures CPU : le cas du x86 | nextText=Exemples de microarchitectures CPU : le cas du x86 }} </noinclude> 1p9efsglwkjds8vjji200y8bwdh7igp Mathc initiation/a522 0 80975 773255 773222 2026-09-26T21:23:06Z Xhungab 23827 773255 wikitext text/x-wiki __NOTOC__ [[Catégorie:Mathc initiation (livre)]] [[Mathc initiation/Sommaire#Analyse IV|Sommaire]] Je vous propose comme cours de référence les trois livres de '''openstax''' en accès libre. Vous pouvez les lire en ligne ou télécharger les PDF. * [https://openstax.org/details/books/calculus-volume-1 Calculus 1], * [https://openstax.org/details/books/calculus-volume-2 Calculus 2], * [https://openstax.org/details/books/calculus-volume-3 Calculus 3]. : . : {{Partie{{{type|}}}|[[Mathc initiation/a10| Analyse IV : Les équations différentielles]]}} . {{Partie{{{type|}}}|[[Mathc initiation/a594| Analyse IV : Se familiariser avec les séries de Fourier]]}} . {{Partie{{{type|}}}|[[Mathc initiation/a592| Analyse IV : Se familiariser avec la transformée de Fourier discrète]]}} . {{Partie{{{type|}}}|[[Mathc initiation/a512| Analyse IV : Se familiariser avec la Transformée de Laplace]]}} {{Partie{{{type|}}}|[[Mathc initiation/006y| Analyse IV : la Transformée Inverse de Laplace]]}} {{Partie{{{type|}}}|[[Mathc initiation/006s| Analyse IV : la Transformée de Laplace : Quelques exercices]]}} {{Partie{{{type|}}}|[[Mathc initiation/006r| Analyse IV : la Transformée de Laplace : Quelques applications]]}} . {{Partie{{{type|}}}|[[Mathc initiation/a584| Analyse IV : Se familiariser avec la transformée en Z]]}} {{Partie{{{type|}}}|[[Mathc initiation/a583| Analyse IV : Transformée en Z : Quelques propriétés]]}} {{Partie{{{type|}}}|[[Mathc initiation/0075| Analyse IV : Transformée en Z : Quelques applications]]}} {{Partie{{{type|}}}|[[Mathc initiation/0079| Analyse IV : Transformée en Z : Un peu d'entraînement]]}} {{Partie{{{type|}}}|[[Mathc initiation/007a| Analyse IV : Transformée en Z inverse : Un peu d'entraînement]]}} {{Partie{{{type|}}}|[[Mathc initiation/0073| Analyse IV : Transformée en Z : Quelques astuces]]}} {{AutoCat}} c9cfufz3loi4is0no7bby3cy3xtka1c 773259 773255 2026-09-26T21:51:23Z Xhungab 23827 773259 wikitext text/x-wiki __NOTOC__ [[Catégorie:Mathc initiation (livre)]] [[Mathc initiation/Sommaire#Analyse IV|Sommaire]] * [[#Transformée en Z :| '''Transformée en Z :''']] : . : {{Partie{{{type|}}}|[[Mathc initiation/a10| Analyse IV : Les équations différentielles]]}} . {{Partie{{{type|}}}|[[Mathc initiation/a594| Analyse IV : Se familiariser avec les séries de Fourier]]}} . {{Partie{{{type|}}}|[[Mathc initiation/a592| Analyse IV : Se familiariser avec la transformée de Fourier discrète]]}} . {{Partie{{{type|}}}|[[Mathc initiation/a512| Analyse IV : Se familiariser avec la Transformée de Laplace]]}} {{Partie{{{type|}}}|[[Mathc initiation/006y| Analyse IV : la Transformée Inverse de Laplace]]}} {{Partie{{{type|}}}|[[Mathc initiation/006s| Analyse IV : la Transformée de Laplace : Quelques exercices]]}} {{Partie{{{type|}}}|[[Mathc initiation/006r| Analyse IV : la Transformée de Laplace : Quelques applications]]}} . {{Partie{{{type|}}}|Transformée en Z :}} {{Partie{{{type|}}}|[[Mathc initiation/a584| Analyse IV : Se familiariser avec la transformée en Z]]}} {{Partie{{{type|}}}|[[Mathc initiation/a583| Analyse IV : Transformée en Z : Quelques propriétés]]}} {{Partie{{{type|}}}|[[Mathc initiation/0075| Analyse IV : Transformée en Z : Quelques applications]]}} {{Partie{{{type|}}}|[[Mathc initiation/0079| Analyse IV : Transformée en Z : Un peu d'entraînement]]}} {{Partie{{{type|}}}|[[Mathc initiation/007a| Analyse IV : Transformée en Z inverse : Un peu d'entraînement]]}} {{Partie{{{type|}}}|[[Mathc initiation/0073| Analyse IV : Transformée en Z : Quelques astuces]]}} {{AutoCat}} d238aocdk9gem1fjnvx98lzjvvlsy41 773260 773259 2026-09-26T21:53:25Z Xhungab 23827 773260 wikitext text/x-wiki __NOTOC__ [[Catégorie:Mathc initiation (livre)]] [[Mathc initiation/Sommaire#Analyse IV|Sommaire]] * [[#Transformée de Laplace :| '''Transformée de Laplace :''']] * [[#Transformée en Z :| '''Transformée en Z :''']] : . : {{Partie{{{type|}}}|[[Mathc initiation/a10| Analyse IV : Les équations différentielles]]}} . {{Partie{{{type|}}}|[[Mathc initiation/a594| Analyse IV : Se familiariser avec les séries de Fourier]]}} . {{Partie{{{type|}}}|[[Mathc initiation/a592| Analyse IV : Se familiariser avec la transformée de Fourier discrète]]}} . {{Partie{{{type|}}}|Transformée de Laplace :}} {{Partie{{{type|}}}|[[Mathc initiation/a512| Analyse IV : Se familiariser avec la Transformée de Laplace]]}} {{Partie{{{type|}}}|[[Mathc initiation/006y| Analyse IV : la Transformée Inverse de Laplace]]}} {{Partie{{{type|}}}|[[Mathc initiation/006s| Analyse IV : la Transformée de Laplace : Quelques exercices]]}} {{Partie{{{type|}}}|[[Mathc initiation/006r| Analyse IV : la Transformée de Laplace : Quelques applications]]}} . {{Partie{{{type|}}}|Transformée en Z :}} {{Partie{{{type|}}}|[[Mathc initiation/a584| Analyse IV : Se familiariser avec la transformée en Z]]}} {{Partie{{{type|}}}|[[Mathc initiation/a583| Analyse IV : Transformée en Z : Quelques propriétés]]}} {{Partie{{{type|}}}|[[Mathc initiation/0075| Analyse IV : Transformée en Z : Quelques applications]]}} {{Partie{{{type|}}}|[[Mathc initiation/0079| Analyse IV : Transformée en Z : Un peu d'entraînement]]}} {{Partie{{{type|}}}|[[Mathc initiation/007a| Analyse IV : Transformée en Z inverse : Un peu d'entraînement]]}} {{Partie{{{type|}}}|[[Mathc initiation/0073| Analyse IV : Transformée en Z : Quelques astuces]]}} {{AutoCat}} 6748nrtx9pnov02xaliygra2exfkbw5 773261 773260 2026-09-26T21:55:27Z Xhungab 23827 773261 wikitext text/x-wiki __NOTOC__ [[Catégorie:Mathc initiation (livre)]] [[Mathc initiation/Sommaire#Analyse IV|Sommaire]] * [[#Transformée de Fourier discrète :| '''Transformée de Fourier discrète :''']] * [[#Transformée de Laplace :| '''Transformée de Laplace :''']] * [[#Transformée en Z :| '''Transformée en Z :''']] : . : {{Partie{{{type|}}}|[[Mathc initiation/a10| Analyse IV : Les équations différentielles]]}} . {{Partie{{{type|}}}|[[Mathc initiation/a594| Analyse IV : Se familiariser avec les séries de Fourier]]}} . {{Partie{{{type|}}}|Transformée de Fourier discrète :}} {{Partie{{{type|}}}|[[Mathc initiation/a592| Analyse IV : Se familiariser avec la transformée de Fourier discrète]]}} . {{Partie{{{type|}}}|Transformée de Laplace :}} {{Partie{{{type|}}}|[[Mathc initiation/a512| Analyse IV : Se familiariser avec la Transformée de Laplace]]}} {{Partie{{{type|}}}|[[Mathc initiation/006y| Analyse IV : la Transformée Inverse de Laplace]]}} {{Partie{{{type|}}}|[[Mathc initiation/006s| Analyse IV : la Transformée de Laplace : Quelques exercices]]}} {{Partie{{{type|}}}|[[Mathc initiation/006r| Analyse IV : la Transformée de Laplace : Quelques applications]]}} . {{Partie{{{type|}}}|Transformée en Z :}} {{Partie{{{type|}}}|[[Mathc initiation/a584| Analyse IV : Se familiariser avec la transformée en Z]]}} {{Partie{{{type|}}}|[[Mathc initiation/a583| Analyse IV : Transformée en Z : Quelques propriétés]]}} {{Partie{{{type|}}}|[[Mathc initiation/0075| Analyse IV : Transformée en Z : Quelques applications]]}} {{Partie{{{type|}}}|[[Mathc initiation/0079| Analyse IV : Transformée en Z : Un peu d'entraînement]]}} {{Partie{{{type|}}}|[[Mathc initiation/007a| Analyse IV : Transformée en Z inverse : Un peu d'entraînement]]}} {{Partie{{{type|}}}|[[Mathc initiation/0073| Analyse IV : Transformée en Z : Quelques astuces]]}} {{AutoCat}} 20vp7o98jbf7knfshafc1ic1mtgmbva 773262 773261 2026-09-26T21:58:40Z Xhungab 23827 773262 wikitext text/x-wiki __NOTOC__ [[Catégorie:Mathc initiation (livre)]] [[Mathc initiation/Sommaire#Analyse IV|Sommaire]] * [[#Equations différentielles :| '''Equations différentielles :''']] * [[#Séries de Fourier :| '''Séries de Fourier :''']] * [[#Transformée de Fourier discrète :| '''Transformée de Fourier discrète :''']] * [[#Transformée de Laplace :| '''Transformée de Laplace :''']] * [[#Transformée en Z :| '''Transformée en Z :''']] : . : {{Partie{{{type|}}}|Equations différentielles :}} {{Partie{{{type|}}}|[[Mathc initiation/a10| Analyse IV : Les équations différentielles]]}} . {{Partie{{{type|}}}|Transformée de Fourier discrète :}} {{Partie{{{type|}}}|[[Mathc initiation/a594| Analyse IV : Se familiariser avec les séries de Fourier]]}} . {{Partie{{{type|}}}|Transformée de Fourier discrète :}} {{Partie{{{type|}}}|[[Mathc initiation/a592| Analyse IV : Se familiariser avec la transformée de Fourier discrète]]}} . {{Partie{{{type|}}}|Transformée de Laplace :}} {{Partie{{{type|}}}|[[Mathc initiation/a512| Analyse IV : Se familiariser avec la Transformée de Laplace]]}} {{Partie{{{type|}}}|[[Mathc initiation/006y| Analyse IV : la Transformée Inverse de Laplace]]}} {{Partie{{{type|}}}|[[Mathc initiation/006s| Analyse IV : la Transformée de Laplace : Quelques exercices]]}} {{Partie{{{type|}}}|[[Mathc initiation/006r| Analyse IV : la Transformée de Laplace : Quelques applications]]}} . {{Partie{{{type|}}}|Transformée en Z :}} {{Partie{{{type|}}}|[[Mathc initiation/a584| Analyse IV : Se familiariser avec la transformée en Z]]}} {{Partie{{{type|}}}|[[Mathc initiation/a583| Analyse IV : Transformée en Z : Quelques propriétés]]}} {{Partie{{{type|}}}|[[Mathc initiation/0075| Analyse IV : Transformée en Z : Quelques applications]]}} {{Partie{{{type|}}}|[[Mathc initiation/0079| Analyse IV : Transformée en Z : Un peu d'entraînement]]}} {{Partie{{{type|}}}|[[Mathc initiation/007a| Analyse IV : Transformée en Z inverse : Un peu d'entraînement]]}} {{Partie{{{type|}}}|[[Mathc initiation/0073| Analyse IV : Transformée en Z : Quelques astuces]]}} {{AutoCat}} g1xuppwfc9xppw6oea4u4ae23y9mhlj 773263 773262 2026-09-26T21:59:15Z Xhungab 23827 773263 wikitext text/x-wiki __NOTOC__ [[Catégorie:Mathc initiation (livre)]] [[Mathc initiation/Sommaire#Analyse IV|Sommaire]] * [[#Equations différentielles :| '''Equations différentielles :''']] * [[#Séries de Fourier :| '''Séries de Fourier :''']] * [[#Transformée de Fourier discrète :| '''Transformée de Fourier discrète :''']] * [[#Transformée de Laplace :| '''Transformée de Laplace :''']] * [[#Transformée en Z :| '''Transformée en Z :''']] : . : {{Partie{{{type|}}}|Equations différentielles :}} {{Partie{{{type|}}}|[[Mathc initiation/a10| Analyse IV : Les équations différentielles]]}} . {{Partie{{{type|}}}|Séries de Fourier :}} {{Partie{{{type|}}}|[[Mathc initiation/a594| Analyse IV : Se familiariser avec les séries de Fourier]]}} . {{Partie{{{type|}}}|Transformée de Fourier discrète :}} {{Partie{{{type|}}}|[[Mathc initiation/a592| Analyse IV : Se familiariser avec la transformée de Fourier discrète]]}} . {{Partie{{{type|}}}|Transformée de Laplace :}} {{Partie{{{type|}}}|[[Mathc initiation/a512| Analyse IV : Se familiariser avec la Transformée de Laplace]]}} {{Partie{{{type|}}}|[[Mathc initiation/006y| Analyse IV : la Transformée Inverse de Laplace]]}} {{Partie{{{type|}}}|[[Mathc initiation/006s| Analyse IV : la Transformée de Laplace : Quelques exercices]]}} {{Partie{{{type|}}}|[[Mathc initiation/006r| Analyse IV : la Transformée de Laplace : Quelques applications]]}} . {{Partie{{{type|}}}|Transformée en Z :}} {{Partie{{{type|}}}|[[Mathc initiation/a584| Analyse IV : Se familiariser avec la transformée en Z]]}} {{Partie{{{type|}}}|[[Mathc initiation/a583| Analyse IV : Transformée en Z : Quelques propriétés]]}} {{Partie{{{type|}}}|[[Mathc initiation/0075| Analyse IV : Transformée en Z : Quelques applications]]}} {{Partie{{{type|}}}|[[Mathc initiation/0079| Analyse IV : Transformée en Z : Un peu d'entraînement]]}} {{Partie{{{type|}}}|[[Mathc initiation/007a| Analyse IV : Transformée en Z inverse : Un peu d'entraînement]]}} {{Partie{{{type|}}}|[[Mathc initiation/0073| Analyse IV : Transformée en Z : Quelques astuces]]}} {{AutoCat}} kvvbmsw8no538ez19wxa4erj1y7s35b 773264 773263 2026-09-26T22:01:42Z Xhungab 23827 773264 wikitext text/x-wiki __NOTOC__ [[Catégorie:Mathc initiation (livre)]] [[Mathc initiation/Sommaire#Analyse IV|Sommaire]] * [[#Équations différentielles :| '''Equations différentielles :''']] * [[#Séries de Fourier :| '''Séries de Fourier :''']] * [[#Transformée de Fourier discrète :| '''Transformée de Fourier discrète :''']] * [[#Transformée de Laplace :| '''Transformée de Laplace :''']] * [[#Transformée en Z :| '''Transformée en Z :''']] {{Partie{{{type|}}}|Équations différentielles :}} {{Partie{{{type|}}}|[[Mathc initiation/a10| Analyse IV : Les équations différentielles]]}} {{Partie{{{type|}}}|Séries de Fourier :}} {{Partie{{{type|}}}|[[Mathc initiation/a594| Analyse IV : Se familiariser avec les séries de Fourier]]}} {{Partie{{{type|}}}|Transformée de Fourier discrète :}} {{Partie{{{type|}}}|[[Mathc initiation/a592| Analyse IV : Se familiariser avec la transformée de Fourier discrète]]}} {{Partie{{{type|}}}|Transformée de Laplace :}} {{Partie{{{type|}}}|[[Mathc initiation/a512| Analyse IV : Se familiariser avec la Transformée de Laplace]]}} {{Partie{{{type|}}}|[[Mathc initiation/006y| Analyse IV : la Transformée Inverse de Laplace]]}} {{Partie{{{type|}}}|[[Mathc initiation/006s| Analyse IV : la Transformée de Laplace : Quelques exercices]]}} {{Partie{{{type|}}}|[[Mathc initiation/006r| Analyse IV : la Transformée de Laplace : Quelques applications]]}} {{Partie{{{type|}}}|Transformée en Z :}} {{Partie{{{type|}}}|[[Mathc initiation/a584| Analyse IV : Se familiariser avec la transformée en Z]]}} {{Partie{{{type|}}}|[[Mathc initiation/a583| Analyse IV : Transformée en Z : Quelques propriétés]]}} {{Partie{{{type|}}}|[[Mathc initiation/0075| Analyse IV : Transformée en Z : Quelques applications]]}} {{Partie{{{type|}}}|[[Mathc initiation/0079| Analyse IV : Transformée en Z : Un peu d'entraînement]]}} {{Partie{{{type|}}}|[[Mathc initiation/007a| Analyse IV : Transformée en Z inverse : Un peu d'entraînement]]}} {{Partie{{{type|}}}|[[Mathc initiation/0073| Analyse IV : Transformée en Z : Quelques astuces]]}} {{AutoCat}} kjtpm33meac2mumcgkneblmrpizd2pt Discussion Wikilivres:Le Bistro/2026 5 83406 773270 773107 2026-09-27T11:56:29Z MediaWiki message delivery 36013 /* Actualités techniques n° 2026-39 */ nouvelle section 773270 wikitext text/x-wiki == Actualités techniques n° 2026-03 == <section begin="technews-2026-W03"/><div class="plainlinks"> Dernières '''[[m:Special:MyLanguage/Tech/News|actualités techniques]]''' de la communauté technique de Wikimedia. N’hésitez pas à informer les autres utilisateurs de ces changements. Certains changements ne vous concernent pas. [[m:Special:MyLanguage/Tech/News/2026/03|D’autres traductions]] sont disponibles. '''En lumière cette semaine''' * La Fondation Wikimedia a publié des questions directrices pour son plan annuel de juillet 2026 à juin 2027 sur les plateformes [[m:Special:MyLanguage/Wikimedia Foundation Annual Plan/2026-2027/Product & Technology OKRs|Meta]] et ''[[diffblog:2025/12/10/shaping-wikimedia-foundations-2026-2027-annual-goals-key-questions-for-the-wikimedia-movement/|Diff]]''. Celles-ci portent sur les tendances mondiales, une expérimentation plus rapide et plus constructive, un meilleur accompagnement des nouveaux contributeurs, le renforcement du rôle des éditeurs et des utilisateurs avancés, l'amélioration de la collaboration entre les projets, ainsi que le développement et la fidélisation du lectorat. Des commentaires et suggestions sont les bienvenus sur la [[m:Talk:Wikimedia Foundation Annual Plan/2026-2027|page de discussion]]. '''Actualités pour la contribution''' * Dans le cadre des travaux en cours de l'équipe technique communautaire sur le projet [[m:Special:MyLanguage/Community Wishlist/W372|Listes de surveillance multiples]], l'affichage de [[Special:EditWatchlist|Modifier la liste de surveillance]] sera mis à jour entant que qu'une première étape vers la prise en charge de plusieurs listes de surveillance. De plus, la pagination de [[Special:Search|Recherche]] sera également mise à jour, dans le cadre du travail sur le souhait [[m:Special:MyLanguage/Community Wishlist/W186|Refonte de la pagination / navigation des pages]]. [https://phabricator.wikimedia.org/T411596] * [[m:Special:GlobalWatchlist|La Liste de Surveillance Globale]] est une [[mw:Special:MyLanguage/Extension:GlobalWatchlist|extension]] de MediaWiki qui vous permet de voir vos listes de surveillance provenant de différents wikis sur la même page. Il a récemment été mis à jour pour ressembler davantage à la [[Special:Watchlist|Liste de surveillance]] régulière, par exemple en le préparant pour les comptes temporaires dans le masquage IP (y compris le réacheminement des liens des utilisateurs vers les pages de contributions), en mettant les titres de page en gras et en ouvrant les liens dans les résumés d'édition et les balises dans de nouveaux onglets du navigateur. [https://phabricator.wikimedia.org/T398361][https://phabricator.wikimedia.org/T298919][https://phabricator.wikimedia.org/T273526][https://phabricator.wikimedia.org/T286309] * [[File:Reload icon with two arrows.svg|12px|link=|class=skin-invert|Sujet récurrent]] Voir {{PLURAL:28|la tâche soumise|les {{formatnum:28}} tâches soumises}} par la communauté [[m:Special:MyLanguage/Tech/News/Recently resolved community tasks|résolue{{PLURAL:28||s}} la semaine dernière]]. Par exemple, le problème selon lequel les blocs globaux ne disposaient pas de l'option permettant de désactiver l'envoi d'e-mails a maintenant été résolu et sera disponible à l'utilisation à partir de la semaine du 13 janvier. [https://phabricator.wikimedia.org/T401293] '''Actualités pour la contribution technique''' * L'[[mw:Special:MyLanguage/VisualEditor/Citation tool|outil de citation VisualEditor]] et les [[mw:Special:MyLanguage/Help:Reference Previews|Aperçus de référence]] prennent désormais en charge "carte" comme type de référence. [https://phabricator.wikimedia.org/T411083] * [[File:Reload icon with two arrows.svg|12px|link=|class=skin-invert|Sujet récurrent]] Détail des mises-à-jour à venir cette semaine : [[mw:MediaWiki 1.46/wmf.10|MediaWiki]]/[[mw:MediaWiki 1.46/wmf.11|MediaWiki]] '''''[[m:Special:MyLanguage/Tech/News|Actualités techniques]]''' préparées par les [[m:Special:MyLanguage/Tech/News/Writers|rédacteurs des actualités techniques]] et postées par [[m:Special:MyLanguage/User:MediaWiki message delivery|robot]]. [[m:Special:MyLanguage/Tech/News#contribute|Contribuer]]&nbsp;• [[m:Special:MyLanguage/Tech/News/2026/03|Traduire]]&nbsp;• [[m:Tech|Obtenir de l’aide]]&nbsp;• [[m:Talk:Tech/News|Donner son avis]]&nbsp;• [[m:Global message delivery/Targets/Tech ambassadors|S’abonner ou se désabonner]].'' </div><section end="technews-2026-W03"/> <bdi lang="en" dir="ltr">[[User:MediaWiki message delivery|MediaWiki message delivery]]</bdi> 12 janvier 2026 à 20:33 (CET) <!-- Message envoyé par User:STei (WMF)@metawiki en utilisant la liste sur https://meta.wikimedia.org/w/index.php?title=Global_message_delivery/Targets/Tech_ambassadors&oldid=29907192 --> == Thank You for Last Year – Join Wiki Loves Ramadan 2026 == Dear Wikimedia communities, We hope you are doing well, and we wish you a happy New Year. ''Last year, we captured light. This year, we’ll capture legacy.'' In 2025, communities around the world shared the glow of Ramadan nights and the warmth of collective iftars. In 2026, ''Wiki Loves Ramadan'' is expanding, bringing more stories, more cultures, and deeper global connections across Wikimedia projects. We invite you to explore the ''Wiki Loves Ramadan 2026'' [[m:Special:MyLanguage/Wiki Loves Ramadan 2026|Meta page]] to learn how you can participate and [[m:Special:MyLanguage/Wiki Loves Ramadan 2026/Participating communities|sign up]] your community. 📷 ''Photo campaign on '' [[c:Special:MyLanguage/Commons:Wiki Loves Ramadan 2026|Wikimedia Commons]] If you have questions about the project, please refer to the FAQs: * [[m:Special:MyLanguage/Wiki Loves Ramadan/FAQ/|Meta-Wiki]] * [[c:Special:MyLanguage/Commons:Wiki Loves Ramadan/FAQ|Wikimedia Commons]] ''Early registration for updates is now open via the '''[[m:Special:RegisterForEvent/2710|Event page]]''''' ''Stay connected and receive updates:'' * [https://t.me/WikiLovesRamadan Telegram channel] * [https://lists.wikimedia.org/postorius/lists/wikilovesramadan.lists.wikimedia.org/ Mailing list] We look forward to collaborating with you and your community. '''The Wiki Loves Ramadan 2026 Organizing Team''' 16 janvier 2026 à 20:44 (CET) <!-- Message envoyé par User:ZI Jony@metawiki en utilisant la liste sur https://meta.wikimedia.org/w/index.php?title=Distribution_list/Non-Technical_Village_Pumps_distribution_list&oldid=29879549 --> == <span lang="en" dir="ltr">Tech News: 2026-04</span> == <div lang="en" dir="ltr"> <section begin="technews-2026-W04"/><div class="plainlinks"> Latest '''[[m:Special:MyLanguage/Tech/News|tech news]]''' from the Wikimedia technical community. Please tell other users about these changes. Not all changes will affect you. [[m:Special:MyLanguage/Tech/News/2026/04|Translations]] are available. '''Updates for editors''' * The tray shown on [[Special:Diff|Special:Diff]] in mobile view has been redesigned. It is now collapsed by default, and incorporates a link to undo the edit being viewed, making it easier for mobile editors and reviewers to take action while keeping the interface uncluttered. [https://phabricator.wikimedia.org/T402297] * [[m:Special:GlobalWatchlist|The Global Watchlist]] lets you view your watchlists from multiple wikis on one page. The [[mw:Special:MyLanguage/Extension:GlobalWatchlist|extension]] continues to improve — it now automatically determines the text direction (ensuring correct display of sites with unusual domain names) and shows detailed descriptions for log actions. Later this week, a new permanent link for page creations and CSS classes for each entry element will be added. [https://phabricator.wikimedia.org/T412505][https://phabricator.wikimedia.org/T287929][https://phabricator.wikimedia.org/T262768][https://phabricator.wikimedia.org/T414135] * [[File:Reload icon with two arrows.svg|12px|link=|class=skin-invert|Recurrent item]] View all {{formatnum:32}} community-submitted {{PLURAL:32|task|tasks}} that were [[m:Special:MyLanguage/Tech/News/Recently resolved community tasks|resolved last week]]. For example, the previously observed issue in Vector 2022, where anchor link targets were obscured by the sticky header, has now been addressed. [https://phabricator.wikimedia.org/T406114] '''Updates for technical contributors''' * As mentioned in the [[m:Special:MyLanguage/Tech/News/2025/44|October 2025 deprecation announcement]], MediaWiki Interfaces team will begin sunsetting all transform endpoints containing a trailing slash from the MediaWiki REST API the week of January 26. Changes are expected to roll out to all wikis on or before January 30th. All API users currently calling them are encouraged to transition to the non-trailing slash versions. Both endpoint variations can be found, compared, and tested using the [https://test.wikipedia.org/wiki/Special:RestSandbox REST Sandbox]. If you have questions or encounter any problems, please file a ticket in Phabricator to the [https://phabricator.wikimedia.org/project/view/6931/ #MW-Interfaces-Team board]. * Interactive reference documentation for the [[mw:Special:MyLanguage/Wikimedia REST API|Wikimedia REST API]] has moved. Requests to API docs previously hosted through [[mw:Special:MyLanguage/RESTBase|RESTBase]] (e.g.: <code dir=ltr>https://en.wikipedia.org/api/rest_v1/</code>) are now redirected to the [[w:en:Special:RestSandbox|REST Sandbox]]. * The [[mw:Special:MyLanguage/Wikidata Platform|WMF Wikidata Platform team]] (WDP) has published its [[d:Special:MyLanguage/Wikidata:Wikidata Platform team/Newsletter|January 2026 newsletter]]. It includes updates on the legacy full-graph endpoint decommissioning, the User-Agent policy change, the monthly Blazegraph migration office hours, and efforts to reduce regressions caused by the legacy endpoint shutdown. As a reminder, you can [[m:Special:MyLanguage/Global message delivery/Targets/WDP team updates|subscribe to the WDP newsletter]]! * [[File:Reload icon with two arrows.svg|12px|link=|class=skin-invert|Recurrent item]] Detailed code updates later this week: [[mw:MediaWiki 1.46/wmf.12|MediaWiki]] '''Meetings and events''' * The [[mw:Wikimedia Hackathon Northwestern Europe 2026|Wikimedia Hackathon Northwestern Europe 2026]] will take place on 13-14 March 2026 in Arnhem, the Netherlands. Applications opened mid-December and will close soon or when capacity is reached. It's a two-day, technically oriented hackathon bringing together Wikimedians from the region. Hope to see you there! '''''[[m:Special:MyLanguage/Tech/News|Tech news]]''' prepared by [[m:Special:MyLanguage/Tech/News/Writers|Tech News writers]] and posted by [[m:Special:MyLanguage/User:MediaWiki message delivery|bot]]&nbsp;• [[m:Special:MyLanguage/Tech/News#contribute|Contribute]]&nbsp;• [[m:Special:MyLanguage/Tech/News/2026/04|Translate]]&nbsp;• [[m:Tech|Get help]]&nbsp;• [[m:Talk:Tech/News|Give feedback]]&nbsp;• [[m:Global message delivery/Targets/Tech ambassadors|Subscribe or unsubscribe]].'' </div><section end="technews-2026-W04"/> </div> <bdi lang="en" dir="ltr">[[User:MediaWiki message delivery|MediaWiki message delivery]]</bdi> 19 janvier 2026 à 21:29 (CET) <!-- Message envoyé par User:STei (WMF)@metawiki en utilisant la liste sur https://meta.wikimedia.org/w/index.php?title=Global_message_delivery/Targets/Tech_ambassadors&oldid=29943403 --> == Révision annuelle du code universel de conduite et des lignes directrices de l'application == <section begin="announcement-content" /> Nous vous informons que la période de relecture annuelle du Code de conduite universel et des règles d'applications est actuellement ouverte. Vous pouvez faire vos commentaires sur les modifications que vous souhaitez apporter jusqu'au 9 février 2026. C'est la première d'une série d'étapes nécessaires pour la révision annuelle. Vous trouverez [[m:Special:MyLanguage/Universal Code of Conduct/Annual review/2026|d'autres informations et les discussions auxquelles participer sur la page UCoC de Meta]]. Le [[m:Special:MyLanguage/Universal Code of Conduct/Coordinating Committee|Comité de coordination du code universel de conduite]] (U4C &mdash; Universal Code of Conduct Coordinating Committee) est un groupe global dont le rôle est de fournir une implémentation équitable et cohérente de l'UCoC. Cette relecture annuelle a été envisagée et mise en place par l'U4C. Pour plus d'informations et les responsabilités de l'U4C, veuillez lire la [[m:Special:MyLanguage/Universal Code of Conduct/Coordinating Committee/Charter|Charte de l'U4C]]. Veuillez partager ces informations avec les autres membres concernés de votre communauté. -- En coopération avec l'U4C, [[m:User:Keegan (WMF)|Keegan (WMF)]] ([[m:User talk:Keegan (WMF)|discussion]])<section end="announcement-content" /> 19 janvier 2026 à 22:01 (CET) <!-- Message envoyé par User:Keegan (WMF)@metawiki en utilisant la liste sur https://meta.wikimedia.org/w/index.php?title=Distribution_list/Global_message_delivery&oldid=29905753 --> == Actualités techniques n° 2026-05 == <section begin="technews-2026-W05"/><div class="plainlinks"> Dernières '''[[m:Special:MyLanguage/Tech/News|actualités techniques]]''' de la communauté technique de Wikimedia. N’hésitez pas à informer les autres utilisateurs de ces changements. Certains changements ne vous concernent pas. [[m:Special:MyLanguage/Tech/News/2026/05|D’autres traductions]] sont disponibles. '''Actualités pour la contribution''' * La Fondation Wikimedia invite à donner des commentaires sur [[m:Special:MyLanguage/Product and Technology Advisory Council/Year1 Reflections and Proposed Way Forward 2026 Update|l’avenir proposé]] du [[:m:Special:MyLanguage/Product and Technology Advisory Council|Conseil consultatif des produits et technologies]] jusqu’au 28 février. * Tous les utilisateurs disposant d'un compte enregistré peuvent désormais utiliser des clés d'accès pour la [[m:Special:MyLanguage/Help:Two-factor authentication|double authentification]] (2FA). Les clés d'accès sont un moyen simple de se connecter sans utiliser un second appareil. Elles vérifient l'identité de l'utilisateur à l'aide d'une empreinte digitale, d'une reconnaissance faciale ou d'un code PIN. Pour configurer une clé d'accès, configurez d'abord une méthode 2FA classique. Actuellement, pour se connecter avec une clé d'accès, les utilisateurs doivent également utiliser un mot de passe. Plus tard ce trimestre, la connexion sans mot de passe permettra aux utilisateurs de se connecter d'un simple clic avec une clé d'accès. Les utilisateurs disposant de droits avancés devront également avoir la 2FA activée. Cela fait partie du projet [[mw:Special:MyLanguage/Product Safety and Integrity/Account Security|Sécurité du compte]]. * Les contributeurs non enregistrés sur des IP bloquées ou des plages d'IP bloquées peuvent désormais interagir sur le wiki pour faire appel d'un blocage en créant un compte temporaire afin de contester un blocage sur la page de discussion de l'utilisateur, sauf si l'option « empêcher cet utilisateur de modifier sa propre page de discussion » est activée. Cela résout le problème des utilisateurs déconnectés incapables d'utiliser le processus de déblocage par défaut via la page de discussion de l'utilisateur. [https://phabricator.wikimedia.org/T398673] * [[File:Reload icon with two arrows.svg|12px|link=|class=skin-invert|Sujet récurrent]] Voir {{PLURAL:20|la tâche soumise|les {{formatnum:20}} tâches soumises}} par la communauté [[m:Special:MyLanguage/Tech/News/Recently resolved community tasks|résolue{{PLURAL:20||s}} la semaine dernière]]. Par exemple, la description des méthodes d'authentification à deux facteurs (2FA) sur la page de gestion a été mise à jour. Il est désormais plus clair et plus facile pour les utilisateurs à comprendre et à utiliser. [https://phabricator.wikimedia.org/T332385] '''Actualités pour la contribution technique''' * Une nouvelle variable AbuseFilter, <code>account_type</code>, a été ajoutée pour fournir un moyen fiable de déterminer le type de compte créé dans les actions <code>createaccount</code> et <code>autocreateaccount</code>. Dans le cadre de ce changement, la variable <code>accountname</code> a été renommée en <code>account_name</code>, et <code>accountname</code> est désormais obsolète. Les gestionnaires de filtres doivent mettre à jour tous les filtres qui utilisent des vérifications de type de compte codées en dur ou la variable obsolète. [https://phabricator.wikimedia.org/T414049] * Les vignettes d'images demandées dans des tailles non standard, et en utilisant des méthodes non standard telles que les requêtes directes à <code dir=ltr><nowiki>upload.wikimedia.org/…</nowiki></code>, cesseront de fonctionner dans un proche avenir. Ce changement vise à prévenir les abus externes continus par des robots et des aspirateurs web. Certains utilisateurs ayant des CSS/JS personnalisés, les administrateurs d'interface qui peuvent corriger les gadgets et les thèmes locaux, ainsi que les auteurs d'outils, devront mettre à jour leur code pour utiliser des tailles de vignettes standard. [[phab:T414805|Des détails, des liens de recherche et des exemples de correction sont disponibles dans la tâche]]. * [[File:Reload icon with two arrows.svg|12px|link=|class=skin-invert|Sujet récurrent]] Détail des mises-à-jour à venir cette semaine : [[mw:MediaWiki 1.46/wmf.13|MediaWiki]] '''''[[m:Special:MyLanguage/Tech/News|Actualités techniques]]''' préparées par les [[m:Special:MyLanguage/Tech/News/Writers|rédacteurs des actualités techniques]] et postées par [[m:Special:MyLanguage/User:MediaWiki message delivery|robot]]. [[m:Special:MyLanguage/Tech/News#contribute|Contribuer]]&nbsp;• [[m:Special:MyLanguage/Tech/News/2026/05|Traduire]]&nbsp;• [[m:Tech|Obtenir de l’aide]]&nbsp;• [[m:Talk:Tech/News|Donner son avis]]&nbsp;• [[m:Global message delivery/Targets/Tech ambassadors|S’abonner ou se désabonner]].'' </div><section end="technews-2026-W05"/> <bdi lang="en" dir="ltr">[[User:MediaWiki message delivery|MediaWiki message delivery]]</bdi> 26 janvier 2026 à 22:17 (CET) <!-- Message envoyé par User:UOzurumba (WMF)@metawiki en utilisant la liste sur https://meta.wikimedia.org/w/index.php?title=Global_message_delivery/Targets/Tech_ambassadors&oldid=29969530 --> == <span lang="en" dir="ltr">Tech News: 2026-06</span> == <div lang="en" dir="ltr"> <section begin="technews-2026-W06"/><div class="plainlinks"> Latest '''[[m:Special:MyLanguage/Tech/News|tech news]]''' from the Wikimedia technical community. Please tell other users about these changes. Not all changes will affect you. [[m:Special:MyLanguage/Tech/News/2026/06|Translations]] are available. '''Updates for editors''' * The "{{int:pageinfo-toolboxlink}}" feature, which gives validating information about a page ([{{fullurl:{{FULLPAGENAME}}|action=info}} example]), now automatically includes a table of contents. If there is a local [[{{ns:8}}:Pageinfo-header]] page created by individual users, it can now be removed. [https://phabricator.wikimedia.org/T363726] * [[File:Reload icon with two arrows.svg|12px|link=|class=skin-invert|Recurrent item]] View all {{formatnum:21}} community-submitted {{PLURAL:21|task|tasks}} that were [[m:Special:MyLanguage/Tech/News/Recently resolved community tasks|resolved last week]]. For example, VisualEditor previously added bold or italic formatting inside link descriptions, making the wikicode complex. This has now been fixed. [https://phabricator.wikimedia.org/T409669] '''Updates for technical contributors''' * There was no XML dump on 20 January. Additionally, from now on, dumps will be generated once per month only. [https://phabricator.wikimedia.org/T414389] * The MediaWiki Interfaces team removed support for all transform endpoints containing a trailing slash from the [https://www.mediawiki.org/wiki/Special:MyLanguage/API:REST%20API MediaWiki REST API]. All API users currently calling those endpoints are encouraged to transition to the non-trailing slash versions. If you have questions or encounter any problems, please file a ticket in phabricator to the [https://phabricator.wikimedia.org/project/view/6931/ #MW-Interfaces-Team board]. * [[File:Reload icon with two arrows.svg|12px|link=|class=skin-invert|Recurrent item]] Detailed code updates later this week: [[mw:MediaWiki 1.46/wmf.14|MediaWiki]] '''Weekly highlight''' * Users are reminded that the Wikimedia Foundation has shared some guiding questions for the July 2026–June 2027 Annual Plan on [[m:Special:MyLanguage/Wikimedia Foundation Annual Plan/2026-2027/Product & Technology OKRs|Meta]] and ''[[diffblog:2025/12/10/shaping-wikimedia-foundations-2026-2027-annual-goals-key-questions-for-the-wikimedia-movement/|Diff]]''. These focus on global trends, faster and healthier experimentation, better support for newcomers, strengthening editors and advanced users, improving collaboration across projects, and growing and retaining readership. Feedback and ideas are welcome on the [[m:Talk:Wikimedia Foundation Annual Plan/2026-2027|talk page]]. '''''[[m:Special:MyLanguage/Tech/News|Tech news]]''' prepared by [[m:Special:MyLanguage/Tech/News/Writers|Tech News writers]] and posted by [[m:Special:MyLanguage/User:MediaWiki message delivery|bot]]&nbsp;• [[m:Special:MyLanguage/Tech/News#contribute|Contribute]]&nbsp;• [[m:Special:MyLanguage/Tech/News/2026/06|Translate]]&nbsp;• [[m:Tech|Get help]]&nbsp;• [[m:Talk:Tech/News|Give feedback]]&nbsp;• [[m:Global message delivery/Targets/Tech ambassadors|Subscribe or unsubscribe]].'' </div><section end="technews-2026-W06"/> </div> <bdi lang="en" dir="ltr">[[User:MediaWiki message delivery|MediaWiki message delivery]]</bdi> 2 février 2026 à 18:43 (CET) <!-- Message envoyé par User:STei (WMF)@metawiki en utilisant la liste sur https://meta.wikimedia.org/w/index.php?title=Global_message_delivery/Targets/Tech_ambassadors&oldid=30000986 --> == Actualités techniques n° 2026-07 == <section begin="technews-2026-W07"/><div class="plainlinks"> Dernières '''[[m:Special:MyLanguage/Tech/News|actualités techniques]]''' de la communauté technique de Wikimedia. N’hésitez pas à informer les autres utilisateurs de ces changements. Certains changements ne vous concernent pas. [[m:Special:MyLanguage/Tech/News/2026/07|D’autres traductions]] sont disponibles. '''Actualités pour la contribution''' * [[File:Maki-gift-15.svg|12px|link=|class=skin-invert|Concerne un souhait]] Les contributeurs connectés qui gèrent de grandes ou complexes listes de suivi peuvent désormais organiser et filtrer les pages surveillées de manière à améliorer leurs flux de travail grâce à la nouvelle fonctionnalité [[mw:Special:MyLanguage/Help:Watchlist labels|Étiquettes de liste de suivi]]. En ajoutant des étiquettes personnalisées (par exemple : pages que vous avez créées, pages surveillées pour vandalisme, ou pages de discussion), les utilisateurs peuvent identifier plus rapidement ce qui nécessite une attention, réduire la charge cognitive et répondre plus efficacement. Cela améliore l'utilisabilité de la liste de suivi, en particulier pour les éditeurs très actifs. * Une nouvelle fonctionnalité disponible sur [[Special:Contributions|Special:Contributions]] montre [[mw:Special:MyLanguage/Trust and Safety Product/Temporary Accounts|des comptes temporaires]] qui sont probablement utilisés par la même personne, et rend ainsi le patrouillage moins chronophage. En vérifiant les contributions d'un compte temporaire, les utilisateurs ayant accès aux adresses IP des comptes temporaires peuvent désormais avoir une vue des contributions des comptes temporaires associés. La fonctionnalité recherche toutes les adresses IP associées à un compte temporaire donné pendant la période de conservation des données et affiche toutes les contributions de tous les comptes temporaires ayant utilisé ces adresses IP. [[mw:Special:MyLanguage/Trust and Safety Product/Temporary Accounts#February 2026: Improvements to the patroller tooling|Plus...]] [https://phabricator.wikimedia.org/T415674] * Lorsque les éditeurs prévisualisent une modification de wikitexte, la boîte de rappel indiquant qu'ils ne voient qu'une prévisualisation (qui est affichée en haut) a désormais un fond gris/neutre au lieu d'un fond jaune/d'avertissement. Cela facilite la distinction entre les notes de prévisualisation et les avertissements réels (par exemple, les conflits de modification ou les cibles de redirection problématiques), qui seront désormais affichés dans des boîtes d'avertissement ou d'erreur séparées. [https://phabricator.wikimedia.org/T414742] * La [[m:Special:GlobalWatchlist|Liste de suivi globale]] vous permet de consulter vos listes de suivi provenant de plusieurs wikis sur une seule page. L' [[mw:Special:MyLanguage/Extension:GlobalWatchlist|extension]] continue de s'améliorer — elle prend désormais en charge correctement plus d'un site Wikibase, par exemple à la fois [[d:|Wikidata]] et [[testwikidata:|testwikidata]]. De plus, des problèmes concernant la direction du texte ont été résolus pour les utilisateurs qui préfèrent Wikidata ou d'autres sites Wikibase dans des langues de droite à gauche (RTL). [https://phabricator.wikimedia.org/T415440][https://phabricator.wikimedia.org/T415458] * <span lang="en" dir="ltr" class="mw-content-ltr">The automatic "magic links" for ISBN, RFC, and PMID numbers have been [[mw:Special:MyLanguage/Help:Magic links|deprecated in wikitext since 2021]] due to inflexibility and difficulties with localization. Several wikis have successfully replaced RFC and PMID magic links with equivalent external links, but a template was often required to replace the functionality of the ISBN magic link. There is now a new [[mw:Special:MyLanguage/Help:Magic words#isbn|built-in parser function]] <code dir=ltr><nowiki>{{#isbn}}</nowiki></code> available to replace the basic functionality of the ISBN magic link. This makes it easier for wikis who wish to migrate off of the deprecated magic link functionality to do so.</span> [https://phabricator.wikimedia.org/T145604] * Deux nouveaux wikis ont été créés : ** un {{int:project-localized-name-group-wikipedia}} dans [[d:Q35401|Jju]] ([[w:kaj:|<code>w:kaj:</code>]]) [https://phabricator.wikimedia.org/T413283] ** un {{int:project-localized-name-group-wikipedia}} dans [[d:Q1186896|Nawat]] ([[w:ppl:|<code>w:ppl:</code>]]) [https://phabricator.wikimedia.org/T413273] * [[File:Reload icon with two arrows.svg|12px|link=|class=skin-invert|Sujet récurrent]] Voir {{PLURAL:23|la tâche soumise|les {{formatnum:23}} tâches soumises}} par la communauté [[m:Special:MyLanguage/Tech/News/Recently resolved community tasks|résolue{{PLURAL:23||s}} la semaine dernière]]. '''Actualités pour la contribution technique''' * Un nouveau groupe d'utilisateurs global a été créé : [[{{int:grouppage-local-bot}}|{{int:group-local-bot}}]]. Il sera utilisé en interne par le logiciel pour permettre aux robots communautaires de contourner les limites de débit appliquées aux [[w:en:Web_scraping|web scrapers]] abusifs. Les comptes approuvés en tant que robots sur au moins un wiki Wikimedia seront automatiquement ajoutés à ce groupe. Cela ne changera pas les autorisations dont dispose le robot. [https://phabricator.wikimedia.org/T415588] * [[File:Reload icon with two arrows.svg|12px|link=|class=skin-invert|Sujet récurrent]] Détail des mises-à-jour à venir cette semaine : [[mw:MediaWiki 1.46/wmf.15|MediaWiki]] '''Rencontres et évènements''' * La [[mw:Special:MyLanguage/MediaWiki Users and Developers Conference Spring 2026|Conférence des utilisateurs et des développeurs de MediaWiki, Printemps 2026]] se tiendra du 25 au 27 mars à Salt Lake City, États-Unis. Cet événement est organisé par et pour la communauté MediaWiki de tiers. Vous pouvez proposer des sessions et vous inscrire pour y assister. [https://lists.wikimedia.org/hyperkitty/list/wikitech-l@lists.wikimedia.org/thread/AZBWVI46SDEB65PGR5J6E4TYOQQEZXM7/] '''''[[m:Special:MyLanguage/Tech/News|Actualités techniques]]''' préparées par les [[m:Special:MyLanguage/Tech/News/Writers|rédacteurs des actualités techniques]] et postées par [[m:Special:MyLanguage/User:MediaWiki message delivery|robot]]. [[m:Special:MyLanguage/Tech/News#contribute|Contribuer]]&nbsp;• [[m:Special:MyLanguage/Tech/News/2026/07|Traduire]]&nbsp;• [[m:Tech|Obtenir de l’aide]]&nbsp;• [[m:Talk:Tech/News|Donner son avis]]&nbsp;• [[m:Global message delivery/Targets/Tech ambassadors|S’abonner ou se désabonner]].'' </div><section end="technews-2026-W07"/> <bdi lang="en" dir="ltr">[[User:MediaWiki message delivery|MediaWiki message delivery]]</bdi> 10 février 2026 à 00:30 (CET) <!-- Message envoyé par User:Quiddity (WMF)@metawiki en utilisant la liste sur https://meta.wikimedia.org/w/index.php?title=Global_message_delivery/Targets/Tech_ambassadors&oldid=30026671 --> == Actualités techniques n° 2026-08 == <section begin="technews-2026-W08"/><div class="plainlinks"> Dernières '''[[m:Special:MyLanguage/Tech/News|actualités techniques]]''' de la communauté technique de Wikimedia. N’hésitez pas à informer les autres utilisateurs de ces changements. Certains changements ne vous concernent pas. [[m:Special:MyLanguage/Tech/News/2026/08|D’autres traductions]] sont disponibles. '''En lumière cette semaine''' * <span class="mw-translate-fuzzy">L'[[mw:Special:MyLanguage/Wikimedia Site Reliability Engineering|équipe SRE]] va procéder au nettoyage d'[[m:Special:MyLanguage/Etherpad|Etherpad]], l'éditeur web open source de documents collaboratifs en temps réel. Tous les blocs-notes seront définitivement supprimés après le 30 avril 2026 – si des projets de migration sont encore en cours à cette date, l'équipe pourra réexaminer la date au cas par cas. Veuillez effectuer des sauvegardes locales de tout contenu que vous souhaitez conserver, car les données supprimées ne pourront pas être récupérées. Ce nettoyage permet de réduire la taille de la base de données et l'empreinte de l'infrastructure. Etherpad continuera de prendre en charge la collaboration en temps réel, mais le stockage à long terme n'est plus assuré. D'autres nettoyages pourront avoir lieu ultérieurement sans préavis.</span> [https://phabricator.wikimedia.org/T415237] '''Actualités pour la contribution''' * L'équipe de Recherche d'Informations lancera une [[mw:Special:MyLanguage/Readers/Information Retrieval/Phase 1|expérimentation sur l'application mobile Android]], afin de tester des fonctionnalités de recherche hybrides capables de gérer à la fois les requêtes sémantiques et par mots-clés. L'amélioration de la recherche sur la plateforme permettra aux lecteurs de trouver plus facilement ce qu'ils cherchent, directement sur Wikipédia. L'expérimentation sera d'abord lancée sur Wikipédia en grec fin février, puis sur les versions anglaise, française et portugaise en mars. [https://diff.wikimedia.org/2026/01/08/semantic-search-making-it-easier-to-find-the-information-readers-want/ En savoir plus] sur le blog ''Diff''. [https://www.mediawiki.org/wiki/Readers/Information_Retrieval] * L'équipe « Croissance des lecteurs » mènera [[mw:Special:MyLanguage/Readers/Reader Growth/WE3.10.2 Mobile Table of Contents|une expérience]] auprès des utilisateurs de la version mobile du site web qui ajoute une table des matières et développe automatiquement toutes les sections des articles, afin de mieux comprendre les problèmes de navigation qu'ils rencontrent. Le test sera disponible sur les versions arabe, chinoise, anglaise, française, indonésienne et vietnamienne de Wikipedia. * Auparavant, les notifications ([[{{ns:8}}:Sitenotice]] et [[{{ns:8}}:Anonnotice]]) du site ne s'affichaient que sur la version ordinateur. Maintenant, elles s'afficheront désormais sur toutes les plateformes. Les utilisateurs mobiles verront ces notifications. Les administrateurs du site doivent être prêts à tester et à corriger les notifications sur les appareils mobiles afin d'éviter toute interférence avec les articles. Pour désactiver ces notifications, les administrateurs d'interface peuvent ajouter <code dir="ltr">#siteNotice { display: none; }</code> à [[{{ns:8}}:Minerva.css]]. [https://phabricator.wikimedia.org/T138572][https://phabricator.wikimedia.org/T416644] * [[File:Reload icon with two arrows.svg|12px|link=|class=skin-invert|Sujet récurrent]] Voir {{PLURAL:19|la tâche soumise|les {{formatnum:19}} tâches soumises}} par la communauté [[m:Special:MyLanguage/Tech/News/Recently resolved community tasks|résolue{{PLURAL:19||s}} la semaine dernière]]. Par exemple, un problème concernant la section ''[[Special:RecentChanges|Spécial:Modifications récentes]]'' a été résolu. Auparavant, cliquer sur « Masquer » dans les filtres actifs entraînait la disparition du bouton « Afficher les nouvelles modifications depuis… », alors qu'il aurait dû rester visible. Ce bouton fonctionne désormais correctement. [https://phabricator.wikimedia.org/T406339] '''Actualités pour la contribution technique''' * Une nouvelle documentation est désormais disponible pour aider les rédacteurs à déboguer les fonctionnalités de recherche interne. Elle facilite le dépannage lorsque des pages n'apparaissent pas dans les résultats, lorsque le classement semble inattendu et lorsqu'il est nécessaire d'inspecter le contenu indexé, ce qui permet de mieux comprendre et d'analyser le comportement de la recherche. [[mw:Help:CirrusSearch/Debug|En savoir plus]]. [https://phabricator.wikimedia.org/T411169] * [[File:Reload icon with two arrows.svg|12px|link=|class=skin-invert|Sujet récurrent]] Détail des mises-à-jour à venir cette semaine : [[mw:MediaWiki 1.46/wmf.16|MediaWiki]] '''''[[m:Special:MyLanguage/Tech/News|Actualités techniques]]''' préparées par les [[m:Special:MyLanguage/Tech/News/Writers|rédacteurs des actualités techniques]] et postées par [[m:Special:MyLanguage/User:MediaWiki message delivery|robot]]. [[m:Special:MyLanguage/Tech/News#contribute|Contribuer]]&nbsp;• [[m:Special:MyLanguage/Tech/News/2026/08|Traduire]]&nbsp;• [[m:Tech|Obtenir de l’aide]]&nbsp;• [[m:Talk:Tech/News|Donner son avis]]&nbsp;• [[m:Global message delivery/Targets/Tech ambassadors|S’abonner ou se désabonner]].'' </div><section end="technews-2026-W08"/> <bdi lang="en" dir="ltr">[[User:MediaWiki message delivery|MediaWiki message delivery]]</bdi> 16 février 2026 à 20:17 (CET) <!-- Message envoyé par User:STei (WMF)@metawiki en utilisant la liste sur https://meta.wikimedia.org/w/index.php?title=Global_message_delivery/Targets/Tech_ambassadors&oldid=30086330 --> == <span lang="en" dir="ltr">Tech News: 2026-09</span> == <div lang="en" dir="ltr"> <section begin="technews-2026-W09"/><div class="plainlinks"> Latest '''[[m:Special:MyLanguage/Tech/News|tech news]]''' from the Wikimedia technical community. Please tell other users about these changes. Not all changes will affect you. [[m:Special:MyLanguage/Tech/News/2026/09|Translations]] are available. '''Weekly highlight''' * [[mw:Special:MyLanguage/Edit check/Reference Check|Reference Check]] has been deployed to English Wikipedia, completing its rollout across all Wikipedias. The feature prompts newcomers to add a citation before publishing new content, helping reduce common citation-related reverts and improve verifiability. In A/B testing, the impact was substantial: newcomers shown Reference Check were approximately 2.2 times more likely to include a reference on desktop and about 17.5 times more likely on mobile web. [https://analytics.wikimedia.org/published/reports/editing/reference_check_ab_test_report_final_2025.html] '''Updates for editors''' * The [[mw:Special:MyLanguage/Extension:InterwikiSorting|InterwikiSorting extension]], which allowed for the [[m:Special:MyLanguage/Interwiki sorting order|sorting of interwiki links]], has been undeployed from Wikipedia. As a result, editors who had enabled interwiki link sorting in non-compact mode (full list format) will now see links reordered. The links moving forward will be listed in the alphabetical order of language code. [https://phabricator.wikimedia.org/T253764] * Later this week, people who are editing a page-section using the mobile visual editor, will notice a new "Edit full page" button. When tapped, you will be able to edit the entire article. This helps when the change you want to make is outside the section you initially opened. [https://phabricator.wikimedia.org/T387175][https://phabricator.wikimedia.org/T409112] * [[mw:Special:MyLanguage/Readers/Reader Experience|The Reader Experience team]] is inviting editors to assess whether dark mode should still be considered "beta" on their wiki, based on their experience of how well it functions on desktop and mobile. If the feature is deemed mature, editors can update the interface messages in <code dir=ltr>MediaWiki:skin-theme-description</code> and <code dir=ltr>MediaWiki:Vector-night-mode-beta-tag</code> to indicate that dark mode is ready and no longer considered beta. * The improved [[mw:Wikimedia_Apps/Team/iOS/Activity_Tab|Activity tab]] which displays user-insights is now available to all users of the Wikipedia iOS app (version 7.9.0 and later). Following earlier A/B testing that showed higher account creation among users with access to the feature, it has been rolled out to 100% of users along with some updates. The Activity tab now shows your edited articles in the timeline, offers editing impact insights like contribution counts and article view trends, and customization options to improve in-app experience for users. * [[File:Reload icon with two arrows.svg|12px|link=|class=skin-invert|Recurrent item]] View all {{formatnum:21}} community-submitted {{PLURAL:21|task|tasks}} that were [[m:Special:MyLanguage/Tech/News/Recently resolved community tasks|resolved last week]]. For example, a bug that prevented [[mw:Special:MyLanguage/Extension:DiscussionTools|DiscussionTools]] from working on mobile has now been fixed, restoring full functionality. [https://phabricator.wikimedia.org/T415303] '''Updates for technical contributors''' * The [[m:Special:GlobalWatchlist|Global Watchlist]] lets you view your watchlists from multiple wikis on one page. The [[mw:Special:MyLanguage/Extension:GlobalWatchlist|extension]] that makes this possible continues to improve. The latest upgrade is the inclusion of a [[mw:Extension:GlobalWatchlist#hook|new hook]], <code dir=ltr>ext.globalwatchlist.rebuild</code>, which fires after each watchlist rebuild. This allows you to run gadgets and user scripts for the Special page. [https://phabricator.wikimedia.org/T275159] * [[File:Reload icon with two arrows.svg|12px|link=|class=skin-invert|Recurrent item]] Detailed code updates later this week: [[mw:MediaWiki 1.46/wmf.17|MediaWiki]] '''''[[m:Special:MyLanguage/Tech/News|Tech news]]''' prepared by [[m:Special:MyLanguage/Tech/News/Writers|Tech News writers]] and posted by [[m:Special:MyLanguage/User:MediaWiki message delivery|bot]]&nbsp;• [[m:Special:MyLanguage/Tech/News#contribute|Contribute]]&nbsp;• [[m:Special:MyLanguage/Tech/News/2026/09|Translate]]&nbsp;• [[m:Tech|Get help]]&nbsp;• [[m:Talk:Tech/News|Give feedback]]&nbsp;• [[m:Global message delivery/Targets/Tech ambassadors|Subscribe or unsubscribe]].'' </div><section end="technews-2026-W09"/> </div> <bdi lang="en" dir="ltr">[[User:MediaWiki message delivery|MediaWiki message delivery]]</bdi> 23 février 2026 à 20:03 (CET) <!-- Message envoyé par User:STei (WMF)@metawiki en utilisant la liste sur https://meta.wikimedia.org/w/index.php?title=Global_message_delivery/Targets/Tech_ambassadors&oldid=30119102 --> == Actualités techniques n° 2026-10 == <section begin="technews-2026-W10"/><div class="plainlinks"> Dernières '''[[m:Special:MyLanguage/Tech/News|actualités techniques]]''' de la communauté technique de Wikimedia. N’hésitez pas à informer les autres utilisateurs de ces changements. Certains changements ne vous concernent pas. [[m:Special:MyLanguage/Tech/News/2026/10|D’autres traductions]] sont disponibles. '''En lumière cette semaine''' * Le [[m:Special:MyLanguage/Wikipedia 25/Easter egg experiments|mode Anniversaire]] Wikipedia 25 est maintenant disponible sur Wikipédia en français, anglais, betawi, breton, chinois, espagnol, gorontalo, indonésien, italien, luxembourgeois, madurais, néerlandais, sicilien, tchèque, thaï et vietnamien ! Cette campagne à temps limitée célèbre 25 ans de Wikipédia avec une mascotte : « Baby Globe », disponible sous la forme d'un réglage. Lorsque ce réglage est activé, Baby Globe est montrée sur [[m:Special:MyLanguage/Wikipedia 25/Easter egg experiments/article configuration|environ 2 500 articles]], attendant d'être découverte par des lecteurs. Chaque communauté peut choisir d'activer le mode Anniversaire par consensus et en demandant à un administrateur de le rendre disponible et de le personaliser via une [[m:Special:MyLanguage/Wikipedia 25/Easter egg experiments#Community Configuration Demo|configuration]] sur le wiki local. '''Actualités pour la contribution''' * Le [[:m:Special:MyLanguage/WMDE Technical Wishes/Sub-referencing|sous-référencement]], une nouvelle fonctionalité pour réutiliser des références avec des détails différents est maintenant disponible sur Wikipédia en suédois, polonais et [[:phab:T418209|quelques autres]]. Vous pouvez [[:m:Special:MyLanguage/WMDE Technical Wishes/Sub-referencing#test|essayer la fonctionalité]] sur ces projets ou sur testwiki et [https://en.wikipedia.beta.wmcloud.org/wiki/Sub-referencing betawiki]. Les retours des premiers essais sur Wikipédia en allemand ont été [[:m:Special:MyLanguage/WMDE Technical Wishes/Sub-referencing/Learnings|publiés dans un rapport]]. Contactez l'équipe de Wikimédia Allemagne si vous êtes [[:m:Talk:WMDE Technical Wishes/Sub-referencing#Pilot wikis|intéressés pour devenir un wiki pilote]]. * La [[mw:Special:MyLanguage/Help:Edit check#Paste check|vérification du collage clavier]] sera disponible sur tous les Wikipédias cette semaine. Cette fonctionalité avertit les nouveaux contributeurs qui collent du texte qu'ils n'ont probablement pas écrit de vérifier si laisser celui-ci risque de causer une violation du droit d'auteur. La vérification du collage clavier [[mw:Special:MyLanguage/Edit check/Tags|marque]] toutes les modifications où l'avertissement a été montré pour permettre leur vérification. Les administrateurs locaux peuvent configurer les différents aspects de cette fonctionalité à travers [[{{#special:EditChecks}}]]. Des [[mw:Special:MyLanguage/Edit check/Paste Check#A/B Experiment|études]] sur 22 wikis ont montré que cette vérification permet une réduction de 18% des annulations comparé au groupe de contrôle. Les traducteurs peuvent [https://translatewiki.net/w/i.php?title=Special%3ATranslate&group=ext-visualeditor-ve-mw-editcheck&filter=&optional=1&action=translate aider à traduire] cette fonctionalité. * <span lang="en" dir="ltr" class="mw-content-ltr">The [[mw:Special:MyLanguage/Readers/Reader Experience|Reader Experience team]] will be standardizing the user menu in the top right for all mobile users so that it is closer to the desktop experience. Currently this user menu is only visible to users with Advanced Mobile Controls (AMC) turned on. The only change is that a couple buttons previously in the left-side menu will move to the top right for users who do not have AMC turned on. This change is expected to go out March 9 and seeks to improve the user interface.</span> [https://phabricator.wikimedia.org/T413912] * À partir de la semaine du 2 mars, les emails envoyés lorsqu'une adresse email a été ajoutée, supprimée ou changée pour un compte changera pour adopter un formattage HTML beaucoup plus agréable et plus clair que le texte brut précédent. [https://phabricator.wikimedia.org/T410807] * Les notifications sont actuellement limitées à 2 000 entrées historiques par utilisateur et remontent à 2013 lorsque la fonctionnalité a été publiée. Le système va être modifié pour ne stocker que les notifications des 5 dernières années, mais jusqu'à 10 000 d'entre elles. Cela contribuera à la santé à long terme des infrastructures et à empêcher que les notifications plus récentes disparaissent trop tôt. [https://phabricator.wikimedia.org/T383948] * <span lang="en" dir="ltr" class="mw-content-ltr">The [[m:Special:GlobalWatchlist|Global Watchlist]] which lets you view your watchlists from multiple wikis on a single page continues to see improvements. The latest update improves label usage experience. The [[mw:Special:MyLanguage/Extension:GlobalWatchlist|extension]] now allows activating the [[mw:Special:MyLanguage/Manual:Language#Fallback languages|language fallback system]] for Wikidata items without labels in the viewed language, and showing those labels in the user’s preferred Wikidata language if no <code dir=ltr>uselang=</code> URL parameter is provided.</span> [https://phabricator.wikimedia.org/T373686][https://phabricator.wikimedia.org/T416111] * L'équipe Wikipédia Android a commencé un test beta de la [[mw:Special:MyLanguage/Readers/Information Retrieval/Phase 1|recherche hybride]] sur Wikipédia en grec. Cette recherche hybride supporte les requêtes sémantique et par mot clés, permettant aux utilisateurs de trouver ce qu'ils cherchent plus facilement. * Pour des raisons de sécurité, les membres de certains groupes sont [[m:Special:MyLanguage/Mandatory two-factor authentication for users with some extended rights|forcés d'avoir la double authentification]] (A2F) d'activée. Actuellement, l'A2F n'est nécessaire que pour utiliser les droits du groupe, et non pour en faire partie. Vu que ce système admet certaines failles, il sera [[phab:T418580|changé graduellement en mars]]. Les membres de ces groupes ne pourront plus désactiver la dernière méthose d'A2F sur leur compte, et il sera impossible d'ajouter des utilisateurs sans A2F à ces groupes. Il sera toujours possible de rajouter d'autres méthodes d'authentification et d'en enlever, tant qu'une est toujours activée. Dans la seconde moitié de mars, les utilisateurs sans A2F seront retirés de ces groupes. Cela s'applique aux administrateurs CentralNotice, aux vérificateurs d'utilisateurs, aux administrateurs d'interface, aux masqueurs, aux staff de Wikidata et Wikifonctions ainsi qu'aux bureaux IT et Confiance et sécurité de la WMF. Rien ne changera pour les autres utilisateurs. Voir la tâche liée pour le calendrier de déploiement. [https://phabricator.wikimedia.org/T418580] * [[File:Reload icon with two arrows.svg|12px|link=|class=skin-invert|Sujet récurrent]] Voir {{PLURAL:27|la tâche soumise|les {{formatnum:27}} tâches soumises}} par la communauté [[m:Special:MyLanguage/Tech/News/Recently resolved community tasks|résolue{{PLURAL:27||s}} la semaine dernière]]. Par exemple, le problème empêchant les utilisateurs de créer une instance dans [https://www.wikibase.cloud/ Wikibase.cloud] a maintenant été résolu. [https://phabricator.wikimedia.org/T416807] '''Actualités pour la contribution technique''' * <span lang="en" dir="ltr" class="mw-content-ltr">To help ensure [[mw:Special:MyLanguage/MediaWiki Product Insights/Responsible Reuse|fair use of infrastructure]], over the next month the Wikimedia Foundation will implement global API rate limits across our APIs. In early March, stricter limits will be applied to unidentified requests from outside Toolforge/WMCS and API requests that are made from web browsers. In April, higher limits will be applied to identified traffic. These limits are intentionally set as high as possible to minimise impact on the community. Bots running in Toolforge/WMCS or with the bot user right on any wiki should not be affected for now. However, all developers are advised to follow updated best practices. For more information, see [[mw:Special:MyLanguage/Wikimedia APIs/Rate limits|Wikimedia APIs/Rate limits]].</span> * <span lang="en" dir="ltr" class="mw-content-ltr">The Wikidata Query Service Linked Data Fragment (LDF) endpoint will be decommissioned in February. This endpoint served limited traffic, which was successfully migrated to other data access methods that were better suited to support existing use cases. The hardware used to support the LDF endpoint will be reallocated to support the ongoing backend migration efforts.</span> [https://phabricator.wikimedia.org/T415696] * Le nouvel analyseur syntaxique Parsoid [[mw:Special:MyLanguage/Parsoid/Parser Unification/Updates|continue d'être déployés sur plus de wikis]], améliorant la pérennité de la platforme et rendant plus facile l'ajout de nouvelles fonctionalités de lecture et de modification. Parsoid est maintenant l'analyseur par défaut sur 488 wikis de la WMF (268 Wikipédias), couvrant plus de 10% de toutes les lectures de pages Wikipédia. * Le processus et les critères pour [[Special:MyLanguage/Wikimedia Enterprise#Access|demander un accès exceptionnel]] au flux à fort volume de l'API ''Wikimédia Entreprise'' (sans coût pour des utilisations en rapport à notre mission) [[m:Talk:Wikimedia Enterprise#Exceptional access criteria|ont maintenant été publiés]]. Notre but est de donner une documentation plus claire et plus complète aux utilisateurs. * [https://techblog.wikimedia.org/ Le blog Tech], dédié à la communité technique de Wikimédia [https://techblog.wikimedia.org/2026/02/24/a-tech-blog-diff/ va migrer] vers [[diffblog:|Diff]], le blog pour les nouvelles et événements de la communauté. La migration devrait être terminée en Avril 2026, après quoi les nouveaux posts seront acceptés pour être publiés. Les lecteurs pourront lire les posts - anciens ou nouveaux - sur https://diff.wikimedia.org/. * [[File:Reload icon with two arrows.svg|12px|link=|class=skin-invert|Sujet récurrent]] Détail des mises-à-jour à venir cette semaine : [[mw:MediaWiki 1.46/wmf.18|MediaWiki]] '''''[[m:Special:MyLanguage/Tech/News|Actualités techniques]]''' préparées par les [[m:Special:MyLanguage/Tech/News/Writers|rédacteurs des actualités techniques]] et postées par [[m:Special:MyLanguage/User:MediaWiki message delivery|robot]]. [[m:Special:MyLanguage/Tech/News#contribute|Contribuer]]&nbsp;• [[m:Special:MyLanguage/Tech/News/2026/10|Traduire]]&nbsp;• [[m:Tech|Obtenir de l’aide]]&nbsp;• [[m:Talk:Tech/News|Donner son avis]]&nbsp;• [[m:Global message delivery/Targets/Tech ambassadors|S’abonner ou se désabonner]].'' </div><section end="technews-2026-W10"/> <bdi lang="en" dir="ltr">[[User:MediaWiki message delivery|MediaWiki message delivery]]</bdi> 2 mars 2026 à 18:51 (CET) <!-- Message envoyé par User:STei (WMF)@metawiki en utilisant la liste sur https://meta.wikimedia.org/w/index.php?title=Global_message_delivery/Targets/Tech_ambassadors&oldid=30137798 --> == <span lang="en" dir="ltr">Tech News: 2026-11</span> == <div lang="en" dir="ltr"> <section begin="technews-2026-W11"/><div class="plainlinks"> Latest '''[[m:Special:MyLanguage/Tech/News|tech news]]''' from the Wikimedia technical community. Please tell other users about these changes. Not all changes will affect you. [[m:Special:MyLanguage/Tech/News/2026/11|Translations]] are available. '''Weekly highlight''' * [[m:Special:MyLanguage/Tech/Server switch|All wikis will be read-only]] for a few minutes on Wednesday, 25 March 2026 at [https://zonestamp.toolforge.org/1774450800 15:00 UTC]. This is for the datacenter server switchover backup tests, [[wikitech:Deployments/Yearly calendar|which happen twice a year]]. During the switchover, all Wikimedia website traffic is shifted from one primary data center to the backup data center to test availability and prevent service disruption even in emergencies. * Last week, all wikis had 2 hours of read-only time, and extended unavailability for user-scripts and gadgets. This was due to a security incident which has since been resolved. Work is ongoing to prevent re-occurrences. For current information please see the [[m:Steward's noticeboard#Statement on Meta about today's user script security incident|post on the Stewards' noticeboard]] ([[m:Special:MyLanguage/Wikimedia Foundation/Product and Technology/Product Safety and Integrity/March 2026 User Script Incident|translations]]). '''Updates for editors''' * Users facing multiple blocks on mobile will now see the reasons for each block separately, instead of a generic message. This helps them understand why they are blocked and what steps they can take to resolve the issue. For example, users affected for using common VPNs (such as [[Special:MyLanguage/Apple iCloud Private Relay|iCloud Private Relay]]) will receive clearer guidance on what they need to do to start editing again. [https://phabricator.wikimedia.org/T357118] * Later this week, [[mw:Special:MyLanguage/VisualEditor/Suggestion Mode|Suggestion Mode]] will become available as a beta feature within the visual editor at all Wikipedias. This feature proactively suggests various types of actions that people can consider taking to improve Wikipedia articles, and learn about related guidelines. The feature is locally configurable, and can also be locally expanded with custom Suggestions. Current settings can be seen at [[Special:EditChecks]] and there are [[mw:Special:MyLanguage/Help:Suggestion mode#For administrators %E2%80%93 local customization|instructions for how administrators can customize]] the links to point to local guidelines. The feature is connected to [[mw:Special:MyLanguage/Help:Edit check|Edit check]] which suggests improvements while someone is writing new content. In the future, the Editing team plans to evaluate the feature's impact with newcomers through a controlled experiment. [https://phabricator.wikimedia.org/T404600] * [[File:Reload icon with two arrows.svg|12px|link=|class=skin-invert|Recurrent item]] View all {{formatnum:23}} community-submitted {{PLURAL:23|task|tasks}} that were [[m:Special:MyLanguage/Tech/News/Recently resolved community tasks|resolved last week]]. For example, the issue where the cursor became misaligned during the use of CodeMirror’s syntax highlighting, which makes wikitext and code easier to read, has now been fixed. This problem specifically affected users who defined a font rule in a custom stylesheet while creating a new topic with DiscussionTools. [https://phabricator.wikimedia.org/T418793] '''Updates for technical contributors''' * API rate limiting update: To help ensure [[mw:Special:MyLanguage/MediaWiki Product Insights/Responsible Reuse|fair use of infrastructure]], global API rate limits will be applied this week to requests without a compliant User-Agent that originate from outside Toolforge/WMCS and to unauthenticated requests made from web browsers. Higher limits will be applied to identified traffic in April. Bots running in Toolforge/WMCS or with the bot user right on any wiki should not be affected for now. However, all developers are advised to follow updated best practices. For more information, see [[mw:Special:MyLanguage/Wikimedia APIs/Rate limits|Wikimedia APIs/Rate limits]]. * The new GraphQL API has been released. The API was developed as a flexible alternative to select features of the Wikidata Query Service (WDQS), to improve developer experience and foster adaptability, and efficient data access. Try it out and [[d:Wikidata:Wikibase GraphQL#Feedback and development|give feedback]]. You can also [https://greatquestion.co/wikimediadeutschland/GraphQLAPI/apply sign up for usability tests]. * The [[m:Special:MyLanguage/Product and Technology Advisory Council/Unsupported Tools Working Group|PTAC Unsupported Tools Working Group]] continued improvements to [[commons:Special:MyLanguage/Commons:Video2commons#|Video2Commons]] in February, with fixes addressing authentication errors, large-file handling, task queue visibility, and clearer upload behavior. Work is still ongoing in some areas, including changes related to deprecated server-side uploads. Read [[m:Special:MyLanguage/Product and Technology Advisory Council/Unsupported Tools Working Group#February 2026|this update]] to learn more. * [[File:Reload icon with two arrows.svg|12px|link=|class=skin-invert|Recurrent item]] Detailed code updates later this week: [[mw:MediaWiki 1.46/wmf.19|MediaWiki]] '''In depth''' * The Article Guidance team invites experienced Wikipedia editors from selected [[mw:Special:MyLanguage/Article guidance/Pilot wikis and collaborators#Collaborators|pilot wikis]] and interested contributors from other Wikipedias to fill out this questionnaire which is available in [https://docs.google.com/forms/d/e/1FAIpQLSfmLeVWnxmsCbPoI_UF2jyRcn73WRGWCVPHzerXb4Cz97X_Ag/viewform English], [https://docs.google.com/forms/d/e/1FAIpQLSd6rzr4XXQw8r4024fE3geTPFe13M_6w7Mitj-YJi0sOlWTAw/viewform?usp=header Arabic], [https://docs.google.com/forms/d/e/1FAIpQLSdok3-RfB18lcugYTUMGkpwmqG_8p760Wv4dCXitOXOszjUDw/viewform?usp=header Bengali], [https://docs.google.com/forms/d/e/1FAIpQLSfjTfYp4jEo0akA4B1e-Nfg3QZPCudUjhJzHzzDi6AHyAaMGA/viewform?usp=header Japanese], [https://docs.google.com/forms/d/e/1FAIpQLScteVoI29Aue4xc72dekk-6RYtvmMgQxzMI900UOawrFrSTWg/viewform?usp=header Portuguese], [https://docs.google.com/forms/d/e/1FAIpQLSetdxnYwL3ub2vqA7awCg5hJZPMIYcDPaiTe12rY9h0GYnVlw/viewform?usp=header Persian], and [https://docs.google.com/forms/d/e/1FAIpQLScNvfJF-Ot-4pzA4qAN771_0QDJ4Li19YcUsaTgSKW8Nc7U_Q/viewform?usp=header Turkish]. Your answers will help the team customize guidance for less experienced editors and help them learn community policies and practices while creating an article. Learn more [[mw:Special:MyLanguage/Article guidance|on the project page]]. '''''[[m:Special:MyLanguage/Tech/News|Tech news]]''' prepared by [[m:Special:MyLanguage/Tech/News/Writers|Tech News writers]] and posted by [[m:Special:MyLanguage/User:MediaWiki message delivery|bot]]&nbsp;• [[m:Special:MyLanguage/Tech/News#contribute|Contribute]]&nbsp;• [[m:Special:MyLanguage/Tech/News/2026/11|Translate]]&nbsp;• [[m:Tech|Get help]]&nbsp;• [[m:Talk:Tech/News|Give feedback]]&nbsp;• [[m:Global message delivery/Targets/Tech ambassadors|Subscribe or unsubscribe]].'' </div><section end="technews-2026-W11"/> </div> <bdi lang="en" dir="ltr">[[User:MediaWiki message delivery|MediaWiki message delivery]]</bdi> 9 mars 2026 à 19:52 (CET) <!-- Message envoyé par User:STei (WMF)@metawiki en utilisant la liste sur https://meta.wikimedia.org/w/index.php?title=Global_message_delivery/Targets/Tech_ambassadors&oldid=30213008 --> == <span lang="en" dir="ltr">Tech News: 2026-12</span> == <div lang="en" dir="ltr"> <section begin="technews-2026-W12"/><div class="plainlinks"> Latest '''[[m:Special:MyLanguage/Tech/News|tech news]]''' from the Wikimedia technical community. Please tell other users about these changes. Not all changes will affect you. [[m:Special:MyLanguage/Tech/News/2026/12|Translations]] are available. '''Updates for editors''' * The [[mw:Special:MyLanguage/Help:Extension:CodeMirror|{{int:codemirror-beta-feature-title}}]] beta feature, also known as [[mw:Special:MyLanguage/Extension:CodeMirror|CodeMirror 6]], has been used for wikitext syntax highlighting since November 2024. It will be promoted out of beta by May 2026 in order to bring improvements and new [[mw:Special:MyLanguage/Help:Extension:CodeMirror#Features|features]] to all editors who use the standard syntax highlighter. If you have any questions or concerns about promoting the feature out of beta, [[mw:Special:MyLanguage/Help talk:Extension:CodeMirror|please share]]. [https://phabricator.wikimedia.org/T259059] * Some changes to local user groups are performed by stewards on Meta-Wiki and logged there only. Now, interwiki rights changes will be logged both on Meta-Wiki and the wiki of the target user to make it easier to access a full record of user's rights changes on a local wiki. Past log entries for such changes will be backfilled in the coming weeks. [https://phabricator.wikimedia.org/T6055] * On wikis using [[m:Special:MyLanguage/Flagged Revisions|Flagged Revisions]], the number of pending changes shown on [[{{#Special:PendingChanges}}]] previously counted pages which were no longer pending review, because they have been removed from the system without being reviewed, e.g. due to being deleted, moved to a different namespace, or due to wiki configuration changes. The count will be correct now. On some wikis the number shown will be much smaller than before. There should be no change to the list of pages itself. [https://phabricator.wikimedia.org/T413016] * Wikifunctions composition language has been rewritten, resulting in a new version of the language. This change aims to increase service stability by reducing the orchestrator's memory consumption. This rewrite also enables substantial latency reduction, code simplification, and better abstractions, which will open the door to later feature additions. Read more about [[f:Special:MyLanguage/Wikifunctions:Status updates/2026-03-11|the changes]]. * Users can now sort search results alphabetically by page title. The update gives an additional option to finding pages more easily and quickly. Previously, results could be sorted by Edit date, Creation date, or Relevance. To use the new option, open 'Advanced Search' on the search results page and select 'Alphabetically' under 'Sorting Order'. [https://phabricator.wikimedia.org/T403775] * [[File:Reload icon with two arrows.svg|12px|link=|class=skin-invert|Recurrent item]] View all {{formatnum:28}} community-submitted {{PLURAL:28|task|tasks}} that were [[m:Special:MyLanguage/Tech/News/Recently resolved community tasks|resolved last week]]. For example, the bug that prevented UploadWizard on Wikimedia Commons from importing files from Flickr has now been fixed. [https://phabricator.wikimedia.org/T419263] '''Updates for technical contributors''' * A new special page, [[{{#special:LintTemplateErrors}}]], has been created to list transcluded pages that are flagged as containing lint errors to help users discover them easily. The list is sorted by the number of transclusions with errors. For example: [[{{#special:LintTemplateErrors}}/night-mode-unaware-background-color]]. [https://phabricator.wikimedia.org/T170874] * Users of the [[mw:Special:MyLanguage/Help:Extension:CodeMirror|{{int:codemirror-beta-feature-title}}]] beta feature have been using [[mw:Special:MyLanguage/Extension:CodeMirror|CodeMirror]] instead of [[mw:Special:MyLanguage/Extension:CodeEditor|CodeEditor]] for syntax highlighting when editing JavaScript, CSS, JSON, Vue and Lua content pages, for some time now. Along with promoting CodeMirror 6 out of beta, the plan is to replace CodeEditor as the standard editor for these content models by May 2026. [[mw:Special:MyLanguage/Help talk:Extension:CodeMirror|Feedback or concerns are welcome]]. [https://phabricator.wikimedia.org/T419332] * The [[mw:Special:MyLanguage/Extension:CodeMirror|CodeMirror]] JavaScript modules will soon be upgraded to CodeMirror 6. Leading up to the upgrade, loading the <code dir=ltr>ext.CodeMirror</code> or <code dir=ltr>ext.CodeMirror.lib</code> modules from gadgets and user scripts was deprecated in July 2025. The use of the <code dir=ltr>ext.CodeMirror.switch</code> hook was also deprecated in March 2025. Contributors can now make their scripts or gadgets compatible with CodeMirror 6. See the [[mw:Special:MyLanguage/Extension:CodeMirror#Gadgets and user scripts|migration guide]] for more information. [https://phabricator.wikimedia.org/T373720] * The MediaWiki Interfaces team is expanding coverage of REST API module definitions to include [[mw:Special:MyLanguage/API:REST API/Extensions|extension APIs]]. REST API modules are groups of related endpoints that can be independently managed and versioned. Modules now exist for [https://phabricator.wikimedia.org/T414470 GrowthExperiments] and [https://phabricator.wikimedia.org/T419053 Wikifunctions] APIs. As we migrate extension APIs to this structure, documentation will move out of the main MediaWiki OpenAPI spec and REST Sandbox view, and will instead be accessible via module-specific options in the dropdown on the [https://test.wikipedia.org/wiki/Special:RestSandbox REST Sandbox] (i.e., [[{{#Special:RestSandbox}}]], available on all wiki projects). * The [[mw:Special:MyLanguage/Extension:Scribunto|Scribunto]] extension provides different pieces of information about the wiki where the module is being used via the [[mw:Special:MyLanguage/Extension:Scribunto/Lua reference manual|mw.site]] library. Starting last week, the library also provides a [[mw:Special:MyLanguage/Extension:Scribunto/Lua reference manual#mw.site.wikiId|way]] of accessing the [[mw:Special:MyLanguage/Manual:Wiki ID|wiki ID]] that can be used to facilitate cross-wiki module maintenance. [https://phabricator.wikimedia.org/T146616] * [[File:Reload icon with two arrows.svg|12px|link=|class=skin-invert|Recurrent item]] Detailed code updates later this week: [[mw:MediaWiki 1.46/wmf.20|MediaWiki]] '''In depth''' * The [[m:Special:MyLanguage/Coolest Tool Award|2026 Coolest Tool Award]] celebrating outstanding community tools, is now open for nominations! Nominate your favorite tool using the [https://wikimediafoundation.limesurvey.net/435684?lang=en nomination survey] form by 23 March 2026. For more information on privacy and data handling, please see the [[foundation:Special:MyLanguage/Legal:Coolest_Tool_Award_2026_Survey_Privacy_Statement|survey privacy statement]]. '''''[[m:Special:MyLanguage/Tech/News|Tech news]]''' prepared by [[m:Special:MyLanguage/Tech/News/Writers|Tech News writers]] and posted by [[m:Special:MyLanguage/User:MediaWiki message delivery|bot]]&nbsp;• [[m:Special:MyLanguage/Tech/News#contribute|Contribute]]&nbsp;• [[m:Special:MyLanguage/Tech/News/2026/12|Translate]]&nbsp;• [[m:Tech|Get help]]&nbsp;• [[m:Talk:Tech/News|Give feedback]]&nbsp;• [[m:Global message delivery/Targets/Tech ambassadors|Subscribe or unsubscribe]].'' </div><section end="technews-2026-W12"/> </div> <bdi lang="en" dir="ltr">[[User:MediaWiki message delivery|MediaWiki message delivery]]</bdi> 16 mars 2026 à 20:35 (CET) <!-- Message envoyé par User:STei (WMF)@metawiki en utilisant la liste sur https://meta.wikimedia.org/w/index.php?title=Global_message_delivery/Targets/Tech_ambassadors&oldid=30260505 --> == <span lang="en" dir="ltr">Upcoming deployment of CampaignEvents extension to Wikibooks</span> == <div lang="en" dir="ltr"> <section begin="message"/> Hello everyone, We are writing to inform you that the [[mw:Help:Extension:CampaignEvents|CampaignEvents extension]] will be deployed to all Wikibooks projects during the week of '''23 March 2026'''. This follows last year’s broader rollout across Wikimedia projects. We realized that Wikibooks was not included at the time, and we’re now addressing that to ensure consistency across all communities. The CampaignEvents extension provides tools to support event and campaign organization on-wiki, including features like on-wiki event registration and collaboration lists(global event list). We welcome any questions, feedback, or concerns you may have. We are also happy to support anyone interested in trying out the tools. ''Apologies if this message is not in your preferred language. If you’re able to help translate it for your community, please feel free to do so.'' <section end="message"/> </div> <bdi lang="en" dir="ltr">[[User:Udehb-WMF|Udehb-WMF]] ([[User talk:Udehb-WMF|discussion]]) 19 mars 2026 à 19:22 (CET)</bdi> <!-- Message envoyé par User:Udehb-WMF@metawiki en utilisant la liste sur https://meta.wikimedia.org/w/index.php?title=User:Udehb-WMF/sandbox/MM_target&oldid=30284073 --> == <span lang="en" dir="ltr">Tech News: 2026-13</span> == <div lang="en" dir="ltr"> <section begin="technews-2026-W13"/><div class="plainlinks"> Latest '''[[m:Special:MyLanguage/Tech/News|tech news]]''' from the Wikimedia technical community. Please tell other users about these changes. Not all changes will affect you. [[m:Special:MyLanguage/Tech/News/2026/13|Translations]] are available. '''Weekly highlight''' * Wikimedia site users can now log in without a password using passkeys. This is a secure method supported by fingerprint, facial recognition, or PIN. With this change, all users who opt for passwordless login will find it easier, faster, and more secure to log in to their accounts using any device. The new passkey login option currently appears as an autofill suggestion in the username field. An additional [[phab:T417120|"Log in with passkey" button]] will soon be available for users who have already registered a passkey. This update will improve security and user experience. The [[c:File:Passwordless_login_screencast.webm|screen recording]] demonstrates the passwordless login process step by step. * [[m:Special:MyLanguage/Tech/Server switch|All wikis will be read-only]] for a few minutes on Wednesday, 25 March 2026 at [https://zonestamp.toolforge.org/1774450800 15:00 UTC]. This is for the datacenter server switchover backup tests, [[wikitech:Deployments/Yearly calendar|which happen twice a year]]. During the switchover, all Wikimedia website traffic is shifted from one primary data center to the backup data center to test availability and prevent service disruption even in emergencies. '''Updates for editors''' * Wikimedia site users can now export their notifications older than 5 years using a [[toolforge:echo-chamber|new Toolforge tool]]. This will ensure that users retain their important notifications and avoid them being lost based on the planned change to delete notifications older than 5 years, as previously announced. [https://phabricator.wikimedia.org/T383948] * Wikipedia editors in Indonesian, Thai, Turkish, and Simple English now have access to Special:PersonalDashboard. This is an [[mw:Special:MyLanguage/Moderator Tools/Dashboard|early version of an experience]] that introduces newer editors to patrolling workflows, making it easier for them to move from making edits to participating in more advanced moderation work on their project. [https://phabricator.wikimedia.org/T402647] * The [[Special:Block]] now has two minor interface changes. Administrators can now easily perform indefinite blocks through a dedicated radio button in the expiry section. Also, choosing an indefinite expiry provides a different set of common reasons to select from, which can be changed at: [[MediaWiki:Ipbreason-indef-dropdown]]. [https://phabricator.wikimedia.org/T401823] * Mobile editors [[mw:Special:MyLanguage/Contributors/Account Creation Experiments#Logged-out|at several wikis]] can now see an improved logged-out edit warning, thanks to the recent updates from the Growth team. These changes released last week are part of ongoing efforts and tests to enhance [[mw:Special:MyLanguage/Contributors/Account Creation Experiments|account creation experience on mobile]] and then increase participation. [https://phabricator.wikimedia.org/T408484] * [[File:Reload icon with two arrows.svg|12px|link=|class=skin-invert|Recurrent item]] View all {{formatnum:36}} community-submitted {{PLURAL:36|task|tasks}} that were [[m:Special:MyLanguage/Tech/News/Recently resolved community tasks|resolved last week]]. For example, the bug that prevented mobile web users from seeing the block information when affected by multiple blocks has been fixed. They can now see messages of all the blocks currently affecting them when they access Wikipedia. '''Updates for technical contributors''' * Images built using Toolforge will soon get the upgraded buildpacks version, bringing support for newer language versions and other upstream improvements and fixes. If you use Toolforge Build Service, review the recent [https://lists.wikimedia.org/hyperkitty/list/cloud-announce@lists.wikimedia.org/thread/EMYTA32EV2V5SQ2JIEOD2CL66YFIZEKV/ cloud-announce email] and update your build configuration as necessary to ensure your tools are compatible. [https://wikitech.wikimedia.org/w/index.php?title=Help:Toolforge/Building_container_images&oldid=2392097#Buildpack_environment_upgrade_process][https://phabricator.wikimedia.org/T380127] * The [https://api.wikimedia.org/wiki/Main_Page API Portal] documentation wiki will shut down in June 2026. API keys created on the API Portal will continue to work normally. api.wikimedia.org endpoints will be deprecated gradually starting in July 2026. Documentation on the API Portal is moving to [[mw:Wikimedia APIs|mediawiki.org]]. Learn more on the [[wikitech:API Portal/Deprecation|project page]]. * [[File:Reload icon with two arrows.svg|12px|link=|class=skin-invert|Recurrent item]] Detailed code updates later this week: [[mw:MediaWiki 1.46/wmf.21|MediaWiki]] '''In depth''' * [[m:Special:MyLanguage/WMDE Technical Wishes|WMDE Technical Wishes]] is considering improvements to [[m:WMDE Technical Wishes/References/VisualEditor automatic reference names|automatically generated reference names in VisualEditor]]. Please check out the [[m:WMDE Technical Wishes/References/VisualEditor automatic reference names#Proposed solutions|proposed solutions]] and participate in the [[m:Talk:WMDE Technical Wishes/References/VisualEditor automatic reference names#Request for comment|request for comment]]. '''''[[m:Special:MyLanguage/Tech/News|Tech news]]''' prepared by [[m:Special:MyLanguage/Tech/News/Writers|Tech News writers]] and posted by [[m:Special:MyLanguage/User:MediaWiki message delivery|bot]]&nbsp;• [[m:Special:MyLanguage/Tech/News#contribute|Contribute]]&nbsp;• [[m:Special:MyLanguage/Tech/News/2026/13|Translate]]&nbsp;• [[m:Tech|Get help]]&nbsp;• [[m:Talk:Tech/News|Give feedback]]&nbsp;• [[m:Global message delivery/Targets/Tech ambassadors|Subscribe or unsubscribe]].'' </div><section end="technews-2026-W13"/> </div> <bdi lang="en" dir="ltr">[[User:MediaWiki message delivery|MediaWiki message delivery]]</bdi> 23 mars 2026 à 17:51 (CET) <!-- Message envoyé par User:UOzurumba (WMF)@metawiki en utilisant la liste sur https://meta.wikimedia.org/w/index.php?title=Global_message_delivery/Targets/Tech_ambassadors&oldid=30268305 --> == Actualités techniques n° 2026-14 == <section begin="technews-2026-W14"/><div class="plainlinks"> Dernières '''[[m:Special:MyLanguage/Tech/News|actualités techniques]]''' de la communauté technique de Wikimedia. N’hésitez pas à informer les autres utilisateurs de ces changements. Certains changements ne vous concernent pas. [[m:Special:MyLanguage/Tech/News/2026/14|D’autres traductions]] sont disponibles. '''En lumière cette semaine''' * Le version Beta de [[abstract:|Abstract Wikipedia]], un nouveau projet Wikimédia indépendant du langage, a été lancée la semaine dernière. Ce projet permet aux communautés de construire des articles Wikipédia dans leur langue natale, qui peuvent directement être lus par les autres utilisateurs et utilisatrices dans leur propre langage. Le wiki fonctionne grâce à des instructions de Wikifunctions et au contenu structuré issu de Wikidata. [[:f:Special:MyLanguage/Wikifunctions:Status updates/2026-03-26|En savoir plus]]. '''Actualités pour la contribution''' * L'équipe Croissance mène un test A/B afin d'évaluer l'effet d'un message plus clair et plus convivial encourageant à la création de comptes sur les wikis. Actuellement, lorsqu'un utilisateur mobile non connecté lance la modification, un message d'avertissement s'affiche, pouvant paraître abrupt et décourageant. Il présente également la modification par compte temporaire comme option par défaut, au lieu d'inciter à la création d'un compte. Le test est mené sur dix Wikipédia, dont les versions en arabe, français, espagnol et allemand. [[mw:Special:MyLanguage/Contributors/Account Creation Experiments#2. Improve logged-out warning message (T415160)|En savoir plus]]. * L'équipe des applications Wikimédia sollicite vos commentaires sur [[mw:Special:MyLanguage/Wikimedia Apps/Team/Future of Editing on the Mobile Apps|comment devrait fonctionner l'édition dans les applications mobiles Wikipédia]]. La discussion porte sur l'amélioration de l'accès aux outils d'édition lorsque les utilisateurs appuient sur « Modifier ». Cette initiative s'inscrit dans un effort plus large visant à offrir aux lecteurs intéressés par la contribution une expérience utilisateur plus intuitive. * [[File:Reload icon with two arrows.svg|12px|link=|class=skin-invert|Sujet récurrent]] Voir {{PLURAL:45|la tâche soumise|les {{formatnum:45}} tâches soumises}} par la communauté [[m:Special:MyLanguage/Tech/News/Recently resolved community tasks|résolue{{PLURAL:45||s}} la semaine dernière]]. Par exemple, un problème avec la récupération de citations à partir du site d'archive de journaux [https://www.newspapers.com Newspapers.com], qui ne fonctionnait plus en raison d'un blocage des requêtes [[mw:Special:MyLanguage/Citoid|Citoid]], a maintenant été résolu. [https://phabricator.wikimedia.org/T419903] '''Actualités pour la contribution technique''' * [[File:Reload icon with two arrows.svg|12px|link=|class=skin-invert|Sujet récurrent]] Détail des mises-à-jour à venir cette semaine : [[mw:MediaWiki 1.46/wmf.22|MediaWiki]] '''''[[m:Special:MyLanguage/Tech/News|Actualités techniques]]''' préparées par les [[m:Special:MyLanguage/Tech/News/Writers|rédacteurs des actualités techniques]] et postées par [[m:Special:MyLanguage/User:MediaWiki message delivery|robot]]. [[m:Special:MyLanguage/Tech/News#contribute|Contribuer]]&nbsp;• [[m:Special:MyLanguage/Tech/News/2026/14|Traduire]]&nbsp;• [[m:Tech|Obtenir de l’aide]]&nbsp;• [[m:Talk:Tech/News|Donner son avis]]&nbsp;• [[m:Global message delivery/Targets/Tech ambassadors|S’abonner ou se désabonner]].'' </div><section end="technews-2026-W14"/> <bdi lang="en" dir="ltr">[[User:MediaWiki message delivery|MediaWiki message delivery]]</bdi> 30 mars 2026 à 21:25 (CEST) <!-- Message envoyé par User:STei (WMF)@metawiki en utilisant la liste sur https://meta.wikimedia.org/w/index.php?title=Global_message_delivery/Targets/Tech_ambassadors&oldid=30329462 --> == Action Required: Update templates/modules for electoral maps (Migrating from P1846 to P14226) == Hello everyone, This is a notice regarding an ongoing data migration on Wikidata that may affect your election-related templates and Lua modules (such as <code>Module:Itemgroup/list</code>). '''The Change:'''<br /> Currently, many templates pull electoral maps from Wikidata using the property [[:d:Property:P1846|P1846]], combined with the qualifier [[:d:Property:P180|P180]]: [[:d:Q19571328|Q19571328]]. We are migrating this data (across roughly 4,000 items) to a newly created, dedicated property: '''[[:d:Property:P14226|P14226]]'''. '''What You Need To Do:'''<br /> To ensure your templates and infoboxes do not break or lose their maps, please update your local code to fetch data from [[:d:Property:P14226|P14226]] instead of the old [[:d:Property:P1846|P1846]] + [[:d:Property:P180|P180]] structure. A [[m:Wikidata/Property Migration: P1846 to P14226/List|list of pages]] was generated using Wikimedia Global Search. '''Deadline:'''<br /> We are temporarily retaining the old data on [[:d:Property:P1846|P1846]] to allow for a smooth transition. However, to complete the data cleanup on Wikidata, the old [[:d:Property:P1846|P1846]] statements will be removed after '''May 1, 2026'''. Please update your modules and templates before this date to prevent any disruption to your wiki's election articles. Let us know if you have any questions or need assistance with the query logic. Thank you for your help! [[User:ZI Jony|ZI Jony]] using [[Utilisateur:MediaWiki message delivery|MediaWiki message delivery]] ([[Discussion utilisateur:MediaWiki message delivery|discussion]]) 3 avril 2026 à 19:11 (CEST) <!-- Message envoyé par User:ZI Jony@metawiki en utilisant la liste sur https://meta.wikimedia.org/w/index.php?title=Distribution_list/Non-Technical_Village_Pumps_distribution_list&oldid=29941252 --> == Actualités techniques n° 2026-15 == <section begin="technews-2026-W15"/><div class="plainlinks"> Dernières '''[[m:Special:MyLanguage/Tech/News|actualités techniques]]''' de la communauté technique de Wikimedia. N’hésitez pas à informer les autres utilisateurs de ces changements. Certains changements ne vous concernent pas. [[m:Special:MyLanguage/Tech/News/2026/15|D’autres traductions]] sont disponibles. '''Actualités pour la contribution''' * L’[[mw:Special:MyLanguage/Help:Extension:CampaignEvents|extension CampaignEvents]] comprend désormais une nouvelle fonctionnalité de définition d’objectifs de groupe, permettant aux organisateurs de définir et de suivre les objectifs de l’événement, tels que le nombre d’articles créés et de contributeurs participants en temps réel. De même, les participants peuvent travailler vers des cibles communes et voir leur impact collectif au fur et à mesure que l’événement se déroule. Cette fonctionnalité est désormais disponible sur tous les wikis Wikimedia. Pour en savoir plus, consultez [[mw:Special:MyLanguage/Help:Extension:CampaignEvents/Registration/Collaborative contributions#Goal setting|la documentation]]. * [[File:Maki-gift-15.svg|12px|link=|class=skin-invert|Concerne un souhait]] La nouvelle fonctionnalité d'[[mw:Special:MyLanguage/Help:Watchlist labels|étiquettes de liste de suivi]] (annoncée dans les [[m:Special:MyLanguage/Tech/News/2026/07|Actualités techniques 2026-07 ]]) est désormais disponible via l'ÉditeurVisuel, l'éditeur de code et l'«étoile de suivi»(ou le lien de suivi, pour les habillages qui n'ont pas d'icône d'étoile). Auparavant, il n'était possible d'attribuer des étiquettes que via [[Special:EditWatchlist|Modifier la liste de suivi]]. Dans ces trois emplacements, il s'agit d'un nouveau champ situé après le champ d'expiration. * [[File:Reload icon with two arrows.svg|12px|link=|class=skin-invert|Sujet récurrent]] Voir {{PLURAL:23|la tâche soumise|les {{formatnum:23}} tâches soumises}} par la communauté [[m:Special:MyLanguage/Tech/News/Recently resolved community tasks|résolue{{PLURAL:23||s}} la semaine dernière]]. Par exemple, le problème où les pages de discussion sur mobile avec Parsoid sont inutilisables après les en-têtes de section vides, a maintenant été résolu. [https://phabricator.wikimedia.org/T419171] '''Actualités pour la contribution technique''' * La [[m:Special:MyLanguage/WMDE Technical Wishes/Sub-referencing|fonctionnalité de sous-référencement]], qui permet aux contributeurs d'ajouter des détails à une référence existante sans la dupliquer, sera progressivement déployée sur [[phab:T414094|davantage de wikis]] plus tard cette année. Les wikis utilisant le gadget [[mw:Special:MyLanguage/Reference Tooltips|Reference Tooltips]] sont encouragés à mettre à jour leur version (généralement sur [[m:MediaWiki:Gadget-ReferenceTooltips.js|MediaWiki:Gadget-ReferenceTooltips.js]] comme indiqué [https://en.wikipedia.org/w/index.php?diff=1344408362 ici]) pour assurer la compatibilité. D'autres gadgets liés aux références pourraient également être affectés. [https://phabricator.wikimedia.org/T416304] * Toutes les éditions de Wikinews seront fermées et passeront en mode lecture seule le 4 mai 2026. Le contenu restera accessible, mais aucune nouvelle modification ni aucun nouvel article ne pourra être ajouté. Cette fermeture a été approuvée par le Conseil d'administration de la Fondation Wikimedia à la suite de discussions prolongées. [[m:Wikimedia Foundation Board noticeboard#Board of Trustees Approves Closure of Wikinews|En savoir plus]]. * L'[[:mw:Special:MyLanguage/API:Action API|API d'action]] a proposé plusieurs formats pour les résultats demandés. L'un d'entre eux, <bdi lang="zxx" dir="ltr"><code><nowiki>format=php</nowiki></code></bdi>, sera bientôt supprimé. Veuillez vous assurer que vos scripts ou robots utilisent le [[mw:Special:MyLanguage/API:Data formats#Output|format JSON]]. Cette suppression devrait affecter très peu de scripts et de robots. [https://phabricator.wikimedia.org/T118538] * La page [[Special:NamespaceInfo|Special:NamespaceInfo]] inclut désormais les alias d'espace de noms. Par exemple «WP» pour l'espace de noms ''Projet'' (''Wikipédia'') sur la Wikipédia en allemand. [https://phabricator.wikimedia.org/T381455] * [[File:Reload icon with two arrows.svg|12px|link=|class=skin-invert|Sujet récurrent]] Détail des mises-à-jour à venir cette semaine : [[mw:MediaWiki 1.46/wmf.23|MediaWiki]] '''''[[m:Special:MyLanguage/Tech/News|Actualités techniques]]''' préparées par les [[m:Special:MyLanguage/Tech/News/Writers|rédacteurs des actualités techniques]] et postées par [[m:Special:MyLanguage/User:MediaWiki message delivery|robot]]. [[m:Special:MyLanguage/Tech/News#contribute|Contribuer]]&nbsp;• [[m:Special:MyLanguage/Tech/News/2026/15|Traduire]]&nbsp;• [[m:Tech|Obtenir de l’aide]]&nbsp;• [[m:Talk:Tech/News|Donner son avis]]&nbsp;• [[m:Global message delivery/Targets/Tech ambassadors|S’abonner ou se désabonner]].'' </div><section end="technews-2026-W15"/> <bdi lang="en" dir="ltr">[[User:MediaWiki message delivery|MediaWiki message delivery]]</bdi> 6 avril 2026 à 18:19 (CEST) <!-- Message envoyé par User:STei (WMF)@metawiki en utilisant la liste sur https://meta.wikimedia.org/w/index.php?title=Global_message_delivery/Targets/Tech_ambassadors&oldid=30362761 --> == <span lang="en" dir="ltr">Tech News: 2026-16</span> == <div lang="en" dir="ltr"> <section begin="technews-2026-W16"/><div class="plainlinks"> Latest '''[[m:Special:MyLanguage/Tech/News|tech news]]''' from the Wikimedia technical community. Please tell other users about these changes. Not all changes will affect you. [[m:Special:MyLanguage/Tech/News/2026/16|Translations]] are available. '''Weekly highlight''' * Experienced editors are invited to [https://b24e11a4f1.catalyst.wmcloud.org/wiki/Main_Page test] the [[mw:Special:MyLanguage/Article guidance|Article guidance]] feature, designed to help less-experienced editors create well-structured, policy-compliant Wikipedia articles. Testing instructions are [[mw:Special:MyLanguage/Article guidance/Test feature guide|available]]. Also, after reviewing [https://b24e11a4f1.catalyst.wmcloud.org/wiki/Category:Pages_using_article_guidance the outlines], please provide feedback on the [[mw:Talk:Article guidance|project talk page]]. Based on your input, the feature will be refined and transferred to the pilot Wikipedias to translate and adapt. Check out [[c:File:Article Guidance workflow demo - April 2026.webm|the video]] explaining the feature. '''Updates for editors''' * On most wikis, all autoconfirmed users can now use [[Special:ChangeContentModel|Special:ChangeContentModel]] page to [[mw:Special:MyLanguage/Help:ChangeContentModel|create new pages with custom content models]], such as mass message lists, making custom page formats more accessible. Check [[Special:ListGroupRights|Special:ListGroupRights]] for the status of your wiki. [https://phabricator.wikimedia.org/T248294] * The Growth team has launched an [[mw:Special:MyLanguage/Contributors/Account_Creation_Experiments|account creation experiment]] to evaluate whether adding an account creation button to the mobile web header increases new account registrations and encourages more mobile users to contribute to the wikis. The experiment is currently live on Hindi, Indonesian, Bengali, Thai, and Hebrew Wikipedia, and targets 10% of logged-out mobile web users. * [[File:Reload icon with two arrows.svg|12px|link=|class=skin-invert|Recurrent item]] View all {{formatnum:30}} community-submitted {{PLURAL:30|task|tasks}} that were [[m:Special:MyLanguage/Tech/News/Recently resolved community tasks|resolved last week]]. For example, an issue where VisualEditor could get stuck loading on Windows devices with animations turned off, has now been fixed. [https://phabricator.wikimedia.org/T382856] '''Updates for technical contributors''' * Starting later this week, {{int:group-abusefilter}} who have the [[mw:Special:MyLanguage/Help:Extension:CodeMirror|{{int:codemirror-beta-feature-title}}]] beta feature enabled will have [[mw:Special:MyLanguage/Extension:CodeMirror|CodeMirror]] instead of [[mw:Special:MyLanguage/Extension:CodeEditor|CodeEditor]] as the editor at [[Special:AbuseFilter|Special:AbuseFilter]]. This is part of the broader effort to make the user experience more consistent across all editors. [https://phabricator.wikimedia.org/T399673][https://phabricator.wikimedia.org/T419332] * Tools and bots that access the [[mw:Special:MyLanguage/Notifications/API|Notifications API]] (<bdi lang="zxx" dir="ltr"><code><nowiki>action=query&meta=notifications</nowiki></code></bdi>) will need to update their OAuth or BotPassword grants to also include access to private notifications. [https://phabricator.wikimedia.org/T421991] * Due to a library upgrade, listings on category pages may be displayed out of order starting on Monday, 20th April. A migration script will be run to correct this, and will take hours to days depending on the size of the wiki (up to a week for English Wikipedia). [https://phabricator.wikimedia.org/T422544] * [[File:Reload icon with two arrows.svg|12px|link=|class=skin-invert|Recurrent item]] Detailed code updates later this week: [[mw:MediaWiki 1.46/wmf.24|MediaWiki]] '''''[[m:Special:MyLanguage/Tech/News|Tech news]]''' prepared by [[m:Special:MyLanguage/Tech/News/Writers|Tech News writers]] and posted by [[m:Special:MyLanguage/User:MediaWiki message delivery|bot]]&nbsp;• [[m:Special:MyLanguage/Tech/News#contribute|Contribute]]&nbsp;• [[m:Special:MyLanguage/Tech/News/2026/16|Translate]]&nbsp;• [[m:Tech|Get help]]&nbsp;• [[m:Talk:Tech/News|Give feedback]]&nbsp;• [[m:Global message delivery/Targets/Tech ambassadors|Subscribe or unsubscribe]].'' </div><section end="technews-2026-W16"/> </div> <bdi lang="en" dir="ltr">[[User:MediaWiki message delivery|MediaWiki message delivery]]</bdi> 13 avril 2026 à 17:19 (CEST) <!-- Message envoyé par User:STei (WMF)@metawiki en utilisant la liste sur https://meta.wikimedia.org/w/index.php?title=Global_message_delivery/Targets/Tech_ambassadors&oldid=30380527 --> == Actualités techniques n° 2026-17 == <section begin="technews-2026-W17"/><div class="plainlinks"> Dernières '''[[m:Special:MyLanguage/Tech/News|actualités techniques]]''' de la communauté technique de Wikimedia. N’hésitez pas à informer les autres utilisateurs de ces changements. Certains changements ne vous concernent pas. [[m:Special:MyLanguage/Tech/News/2026/17|D’autres traductions]] sont disponibles. '''En lumière cette semaine''' * Après deux ans de développement, la version [[mw:Special:MyLanguage/Help:Extension:CodeMirror|{{int:codemirror-beta-feature-title}}]], également connue sous le nom de [[mw:Special:MyLanguage/Extension:CodeMirror|CodeMirror 6]], sortira de sa phase bêta le mardi 21 avril. Elle offrira une meilleure lisibilité du code et du wikitext, une réduction des fautes de frappe et d'autres [[mw:Special:MyLanguage/Help:Extension:CodeMirror|avantages]] à tous les utilisateurs du surligneur de syntaxe standard. Un grand merci au bénévole [https://phabricator.wikimedia.org/p/Bhsd/ Bhsd] qui a développé de nombreuses nouvelles fonctionnalités, notamment [[mw:Special:MyLanguage/Help:Extension:CodeMirror#Code folding|le repliement de code]], [[mw:Special:MyLanguage/Help:Extension:CodeMirror#Autocompletion|la saisie semi-automatique]] et [[mw:Special:MyLanguage/Help:Extension:CodeMirror#Linting|l'analyse statique du code]]. [https://phabricator.wikimedia.org/T259059] * Une mise à jour majeure de l'application Wikipédia pour iOS est en cours de déploiement, en restructurant l'interface pour s'harmoniser avec le tout nouveau design visuel "Liquid Glass" d'Apple. [https://apps.apple.com/us/app/wikipedia/id324715238 Télécharger la dernière version] et découvrez les nouveautés. '''Actualités pour la contribution''' * [[mw:Special:MyLanguage/Readers/Reader Experience/WE3.3.4 Reading lists|Les listes de lecture]] est une fonctionnalité qui permet aux lecteurs d'enregistrer des articles dans une liste pour les lire ultérieurement. Cette fonctionnalité est actuellement en version bêta sur les Wikipédias en arabe, français, indonésien, vietnamien et chinois, et activée par défaut pour tous les nouveaux comptes sur toutes les Wikipédias. * Une expérimentation visant à étendre [[mw:Special:MyLanguage/Readers/Reader Growth/Mobile page previews|les aperçus de page au web mobile]] sera lancée la semaine du 20 avril sur les versions arabe, anglaise, française, italienne, polonaise et vietnamienne de Wikipédia. Les aperçus de page sont des fenêtres contextuelles affichant une miniature, un premier paragraphe et un lien bleu permettant d'ouvrir l'article complet, facilitant ainsi la découverte de contenu. Cette fonctionnalité est déjà disponible sur ordinateur et dans les applications. [[m:Special:MyLanguage/List of experiments in Product and Technology#Template|En savoir plus sur cette expérimentation et d'autres]]. * Sur plusieurs wikis, les contributeurs connectés qui n'ont pas [[mw:Special:MyLanguage/Help:Email confirmation|confirmé leur adresse électronique]] peuvent désormais voir une bannière les invitant à le faire. La confirmation de l'adresse électronique permet à un utilisateur de récupérer l'accès à son compte en cas de perte. [[mw:Special:MyLanguage/Product Safety and Integrity/Account Security#Encouraging users to confirm their email addresses|En savoir plus]]. [https://phabricator.wikimedia.org/T421366] * [[File:Reload icon with two arrows.svg|12px|link=|class=skin-invert|Sujet récurrent]] Voir {{PLURAL:15|la tâche soumise|les {{formatnum:15}} tâches soumises}} par la communauté [[m:Special:MyLanguage/Tech/News/Recently resolved community tasks|résolue{{PLURAL:15||s}} la semaine dernière]]. Par exemple, un problème qui entraînait des ralentissements lors de la modification de très grandes pages wiki dans l'éditeur wikitext de 2017, des problèmes de chargement, de prévisualisation et de défilement, ainsi que des problèmes de performance lors de la sélection, de la découpe ou du collage de contenu, a maintenant été résolu. [https://phabricator.wikimedia.org/T184857] '''Actualités pour la contribution technique''' * Dans le cadre de la promotion de [[mw:Special:MyLanguage/Help:Extension:CodeMirror|CodeMirror]] à partir d'une fonctionnalité bêta, tous les utilisateurs se serviront de [[mw:Special:MyLanguage/Extension:CodeMirror|CodeMirror]] au lieu de [[mw:Special:MyLanguage/Extension:CodeEditor|CodeEditor]] pour la coloration syntaxique lors de l'édition de pages de contenu JavaScript, CSS, JSON, Vue et Lua. [https://phabricator.wikimedia.org/T419332] * <span class="mw-translate-fuzzy">Le service <code>mirrors.wikimedia.org</code> pour les utilisateurs de Debian et Ubuntu sera définitivement arrêté le 15 mai. Le matériel du serveur sera remplacé par des solutions plus performantes. Certains utilisateurs devront peut-être migrer vers un autre serveur qui ne devra prendre qu'une minute. [https://lists.wikimedia.org/hyperkitty/list/wikitech-l@lists.wikimedia.org/thread/LJYRIS4WB66HIRCAO4GIDTXCMDVZRBMA/ Vous pouvez en savoir plus].</span> [https://phabricator.wikimedia.org/T416707] * Les tables <bdi lang="zxx" dir="ltr"><code><nowiki>image</nowiki></code></bdi> et <bdi lang="zxx" dir="ltr"><code><nowiki>oldimage</nowiki></code></bdi> seront supprimées de [[wikitech:Help:Wiki Replicas|wikireplicas]]. Si vos outils ou requêtes accèdent directement à <bdi lang="zxx" dir="ltr"><code><nowiki>image</nowiki></code></bdi> ou <bdi lang="zxx" dir="ltr"><code><nowiki>oldimage</nowiki></code></bdi>, veuillez les mettre à jour pour utiliser les tables <bdi lang="zxx" dir="ltr"><code><nowiki>file</nowiki></code></bdi> et <bdi lang="zxx" dir="ltr"><code><nowiki>filerevision</nowiki></code></bdi> avant le 28 mai. [https://phabricator.wikimedia.org/T28741] * Suite à la récente mise en place de limites de débit globales pour les API non identifiées, la Fondation Wikimedia poursuit ses efforts pour garantir [[mw:Special:MyLanguage/MediaWiki Product Insights/Responsible Reuse|une utilisation équitable de l'infrastructure]] en appliquant des limites globales au trafic des API identifiées à partir de la dernière semaine d'avril. Ces limites sont volontairement fixées au niveau le plus élevé possible afin de minimiser l'impact sur la communauté. Les bots exécutés dans Toolforge/WMCS ou disposant des droits d'utilisateur de bot sur un wiki ne devraient pas être affectés pour le moment. Toutefois, il est conseillé à tous les développeurs de suivre les bonnes pratiques mises à jour. Pour plus d'informations, consultez la page [[mw:Special:MyLanguage/Wikimedia APIs/Rate limits|API Wikimedia/Limites de débit]] et la [[mw:Special:MyLanguage/Wikimedia APIs/Rate limits/FAQ|Foire aux questions]]. * L'[[mw:Special:MyLanguage/Attribution API|API d'attribution]] est désormais disponible en [[mw:Special:MyLanguage/Wikimedia APIs/Stability policy|version bêta]]. Elle récupère les informations nécessaires pour créditer les articles et les fichiers multimédias de Wikimedia, quel que soit leur lieu d'utilisation. La documentation de référence est disponible sur la page dédiée au Sandbox REST, accessible sur tous les wikis Wikimedia (comme [https://en.wikipedia.org/w/index.php?api=attribution.v0-beta&title=Special%3ARestSandbox le sandbox REST de Wikipédia en anglais]). N'hésitez pas à partager vos commentaires sur la [[mw:Talk:Attribution API|page de discussion du projet]]. * Il n'y aura pas de nouvelle version de MediaWiki cette semaine. '''''[[m:Special:MyLanguage/Tech/News|Actualités techniques]]''' préparées par les [[m:Special:MyLanguage/Tech/News/Writers|rédacteurs des actualités techniques]] et postées par [[m:Special:MyLanguage/User:MediaWiki message delivery|robot]]. [[m:Special:MyLanguage/Tech/News#contribute|Contribuer]]&nbsp;• [[m:Special:MyLanguage/Tech/News/2026/17|Traduire]]&nbsp;• [[m:Tech|Obtenir de l’aide]]&nbsp;• [[m:Talk:Tech/News|Donner son avis]]&nbsp;• [[m:Global message delivery/Targets/Tech ambassadors|S’abonner ou se désabonner]].'' </div><section end="technews-2026-W17"/> <bdi lang="en" dir="ltr">[[User:MediaWiki message delivery|MediaWiki message delivery]]</bdi> 20 avril 2026 à 17:00 (CEST) <!-- Message envoyé par User:STei (WMF)@metawiki en utilisant la liste sur https://meta.wikimedia.org/w/index.php?title=Global_message_delivery/Targets/Tech_ambassadors&oldid=30432763 --> == Request for comment (global AI policy) == <bdi lang="en" dir="ltr" class="mw-content-ltr"> Apologies for writing in English. {{int:Please-translate}} A [[:m:Requests for comment/Artificial intelligence policy|request for comment]] is currently being held to decide on a global AI policy. {{int:Feedback-thanks-title}} [[Utilisateur:MediaWiki message delivery|MediaWiki message delivery]] ([[Discussion utilisateur:MediaWiki message delivery|discussion]]) 26 avril 2026 à 02:57 (CEST) </bdi> <!-- Message envoyé par User:Codename Noreste@metawiki en utilisant la liste sur https://meta.wikimedia.org/w/index.php?title=Distribution_list/Global_message_delivery&oldid=30424282 --> == Actualités techniques n° 2026-18 == <section begin="technews-2026-W18"/><div class="plainlinks"> Dernières '''[[m:Special:MyLanguage/Tech/News|actualités techniques]]''' de la communauté technique de Wikimedia. N’hésitez pas à informer les autres utilisateurs de ces changements. Certains changements ne vous concernent pas. [[m:Special:MyLanguage/Tech/News/2026/18|D’autres traductions]] sont disponibles. '''Actualités pour la contribution''' * Un changement dans la manière dont les utilisateurs et utilisatrices sont automatiquement confirmés est en cours pour améliorer la protection contre le vandalisme. Actuellement, il suffit d’avoir un compte depuis quelques jours avec quelques contributions pour être ajouté au groupe [[{{int:grouppage-autoconfirmed/{{CONTENTLANGUAGE}}}}|{{int:group-autoconfirmed}}]]. Cette configuration tend à être exploitée par certains vandales qui créent des comptes et commencent à les utiliser après un certain temps. Pour réduire ce problème, la configuration va changer la semaine prochaine afin que l’âge du compte minimum pour être confirmé automatiquement ne soit calculé qu’à partir de la première modification, au lieu de la date d’inscription. L’âge minimum du compte restera le même, c’est seulement le point de départ pour calculer cet âge qui change. Ce changement ne sera déployé que sur les wikis qui nécessitent au moins une contribution pour satisfaire les conditions de confirmation automatique. [https://phabricator.wikimedia.org/T418484] * Tous les utilisateurs et utilisatrices de Wikipédia avec un nouveau compte et ceux qui ont activé l’option « activer automatiquement la plupart des fonctionnalités bêta » peuvent désormais utiliser la fonctionnalité bêta de [[mw:Special:MyLanguage/Readers/Reader Experience/WE3.3.4 Reading lists|listes de lecture]] pour enregistrer des articles à lire plus tard. Cela permet d’organiser les lectures qui nous intéressent à un endroit unique pour y accéder facilement. * [[File:Reload icon with two arrows.svg|12px|link=|class=skin-invert|Sujet récurrent]] Voir {{PLURAL:30|la tâche soumise|les {{formatnum:30}} tâches soumises}} par la communauté [[m:Special:MyLanguage/Tech/News/Recently resolved community tasks|résolue{{PLURAL:30||s}} la semaine dernière]]. Par exemple, le problème avec les images d’infoboite qui avaient une marge intérieure immense dans Firefox a été corrigé. [https://phabricator.wikimedia.org/T423676] '''Actualités pour la contribution technique''' * Pour rappel, la limite globale d’accès à l’API sera appliquée cette semaine pour identifier le trafic de l’API. Le but est d’aider à garantir un [[mw:MediaWiki Product Insights/Responsible Reuse|accès équitable à l’infrastructure]]. Les robots qui s’exécutent dans Toolforge ou WMCS, ou avec le droit utilisateur ''robot'' sur les wikis, ne devraient pas être affectés pour le moment. Cependant, il est conseillé à tous les développeurs et développeuses de se conformer aux nouvelles bonnes pratiques à suivre. Pour plus d’informations, notamment la limite globale d’accès effective, consultez [[mw:Wikimedia APIs/Rate limits|la page sur la limite d’accès des API de Wikimedia]] et les [[mw:Wikimedia APIs/Rate limits/FAQ|questions-réponses]]. * [[File:Reload icon with two arrows.svg|12px|link=|class=skin-invert|Sujet récurrent]] Détail des mises-à-jour à venir cette semaine : [[mw:MediaWiki 1.46/wmf.26|MediaWiki]] '''''[[m:Special:MyLanguage/Tech/News|Actualités techniques]]''' préparées par les [[m:Special:MyLanguage/Tech/News/Writers|rédacteurs des actualités techniques]] et postées par [[m:Special:MyLanguage/User:MediaWiki message delivery|robot]]. [[m:Special:MyLanguage/Tech/News#contribute|Contribuer]]&nbsp;• [[m:Special:MyLanguage/Tech/News/2026/18|Traduire]]&nbsp;• [[m:Tech|Obtenir de l’aide]]&nbsp;• [[m:Talk:Tech/News|Donner son avis]]&nbsp;• [[m:Global message delivery/Targets/Tech ambassadors|S’abonner ou se désabonner]].'' </div><section end="technews-2026-W18"/> <bdi lang="en" dir="ltr">[[User:MediaWiki message delivery|MediaWiki message delivery]]</bdi> 27 avril 2026 à 20:06 (CEST) <!-- Message envoyé par User:UOzurumba (WMF)@metawiki en utilisant la liste sur https://meta.wikimedia.org/w/index.php?title=Global_message_delivery/Targets/Tech_ambassadors&oldid=30458046 --> == Actualités techniques n° 2026-19 == <section begin="technews-2026-W19"/><div class="plainlinks"> Dernières '''[[m:Special:MyLanguage/Tech/News|actualités techniques]]''' de la communauté technique de Wikimedia. N’hésitez pas à informer les autres utilisateurs de ces changements. Certains changements ne vous concernent pas. [[m:Special:MyLanguage/Tech/News/2026/19|D’autres traductions]] sont disponibles. '''En lumière cette semaine''' * L’équipe chargée des fonctionnalités de [[mw:Special:MyLanguage/Article guidance|Guidage des articles]] invite les contributeurs et contributrices expérimentés des [[mw:Special:MyLanguage/Article guidance/Pilot wikis and collaborators|Wikipédia pilotes]] (arabe, bangla, japonais, portugais, persan, turc, anglais simplifié, espagnol et français) à contribuer à la traduction et à l’adaptation des [https://b24e11a4f1.catalyst.wmcloud.org/wiki/Category:Pages_using_article_guidance exemples de trames d’articles]. Ces trames guideront les contributeurs dans la création d’articles clairs, bien structurés et conformes aux règles lors de l’utilisation de [https://b24e11a4f1.catalyst.wmcloud.org/wiki/Special:NewArticle la fonctionnalité] dès son lancement en mai 2026. Des [[mw:Special:MyLanguage/Article guidance#Adapting a sample outline in a Wikipedia|instructions simples]] expliquant comment traduire et adapter ces trames sont disponibles. '''Actualités pour la contribution''' * Le [[:m:Special:MyLanguage/Product and Technology Advisory Council|Conseil consultatif sur les produits et les technologies]] a publié [[:m:Special:MyLanguage/Product and Technology Advisory Council/May 2026 draft PTAC recommendation for feedback|une proposition de recommandation]] d’une procédure type que les organisations affiliées à Wikimedia pourraient suivre pour contribuer au domaine technique. Les membres de la communauté sont invités à donner leur avis sur cette recommandation avant le 8 mai [[:m:Talk:Product and Technology Advisory Council/May 2026 draft PTAC recommendation for feedback|sur la page de discussion]]. * Le nombre de préférences de taille de la miniature disponibles dans MediaWiki va être réduit à trois options standardisées : ''petite'' (180 px), ''moyenne'' (250 px) et ''large'' (400 px), dans le cadre du travail en cours pour améliorer les performances et réduire la pression sur les services de miniatures. Par conséquent, les préférences existantes seront automatiquement adaptées à la nouvelle taille la plus proche (par exemple, les petites tailles comme 120 px ou 150 px s’afficheront à 180 px, tandis que les grandes tailles comme 300 px ou 360 px s’afficheront à 400 px). L’interface des préférences sera bientôt mise à jour pour refléter ces changements, et les utilisateurs qui souhaitent s’y opposer ou donner leur avis peuvent le faire. [https://phabricator.wikimedia.org/T424909] * Dorénavant, même lorsqu’une permission expire automatiquement, les utilisateurs recevront une notification Echo similaire à la notification normale pour les changements de permissions. Quant au [[m:Special:MyLanguage/Global reminder bot|robot global de rappel]], il continue de prévenir les utilisateurs une semaine ''avant'' que leurs droits ne soient sur le point d’expirer, afin qu’ils puissent les faire renouveler. * [[File:Reload icon with two arrows.svg|12px|link=|class=skin-invert|Sujet récurrent]] Voir {{PLURAL:32|la tâche soumise|les {{formatnum:32}} tâches soumises}} par la communauté [[m:Special:MyLanguage/Tech/News/Recently resolved community tasks|résolue{{PLURAL:32||s}} la semaine dernière]]. Par exemple, le problème du sélecteur de langue ULS dans [[m:Special:Translate|Special:Translate]] qui faisait défiler verticalement alors qu’il ne devait pas, a été résolu. Auparavant, lorsque les utilisateurs ouvraient le menu déroulant « Traduire en français » et commençaient à saisir le nom d’une langue, la boîte de dialogue défilait verticalement de quelques pixels même lorsqu'il y avait suffisamment d’espace pour afficher tous les résultats. Le menu déroulant ne se déplace plus inutilement lors du filtrage des langues. [https://phabricator.wikimedia.org/T358864] * La [[m:Special:GlobalWatchlist|liste de suivi globale]], qui vous permet de consulter vos listes de suivi provenant de plusieurs wikis sur une seule page, continue de s’améliorer. Par exemple, les listes de suivi pour les sites avec Wikibase tels que [[:d:|Wikidata]] prennent désormais en charge les éléments [[mw:Special:MyLanguage/Extension:EntitySchema|EntitySchema]] pour un meilleur suivi. Le mode Mises à jour en direct actualise désormais la page spéciale toutes les 60 secondes afin de se conformer aux [[mw:Special:MyLanguage/Wikimedia APIs/Rate limits|nouvelles limites globales d’accès à l’API]] pour une meilleure réactivité en temps réel. Par ailleurs, un bug de directionnalité du texte qui affichait les liens comme « changements 3 » au lieu de « 3 changements » dans les listes à directions mixtes a été corrigé. [https://phabricator.wikimedia.org/T415450][https://phabricator.wikimedia.org/T424422][https://phabricator.wikimedia.org/T418091] '''Actualités pour la contribution technique''' * La deuxième phase de [[mw:Special:MyLanguage/Wikimedia APIs/Rate limits|limitations globales d’accès à l’API]] a été déployée pour réduire l’[[diffblog:2026/03/26/quo-vadis-crawlers-progress-and-whats-next-on-safeguarding-our-infrastructure/|impact des robots IA]] et assurer un accès équitable et durable aux ressources de Wikimedia, en donnant la priorité au trafic humain et conforme à notre mission. Les [[mw:Special:MyLanguage/Wikimedia APIs/Rate limits#Limits|limites]] ne s’appliquent plus par heure mais par minute, produisant une meilleure répartition dans les structures de trafic ainsi qu’une meilleure prévisibilité de la charge de l’API. Les utilisateurs de la communauté ne devraient pas être affectés, et aucune action n’est requise. Les premières indications montrent que certains requérants basés sur l'agent utilisateur ajustent leur comportement, et environ 64 % du trafic API automatisé a été identifié. La surveillance continue, et Wikimedia Enterprise reste disponible pour l’assistance commerciale. * [[File:Reload icon with two arrows.svg|12px|link=|class=skin-invert|Sujet récurrent]] Détail des mises-à-jour à venir cette semaine : [[mw:MediaWiki 1.46/wmf.27|MediaWiki]] '''''[[m:Special:MyLanguage/Tech/News|Actualités techniques]]''' préparées par les [[m:Special:MyLanguage/Tech/News/Writers|rédacteurs des actualités techniques]] et postées par [[m:Special:MyLanguage/User:MediaWiki message delivery|robot]]. [[m:Special:MyLanguage/Tech/News#contribute|Contribuer]]&nbsp;• [[m:Special:MyLanguage/Tech/News/2026/19|Traduire]]&nbsp;• [[m:Tech|Obtenir de l’aide]]&nbsp;• [[m:Talk:Tech/News|Donner son avis]]&nbsp;• [[m:Global message delivery/Targets/Tech ambassadors|S’abonner ou se désabonner]].'' </div><section end="technews-2026-W19"/> <bdi lang="en" dir="ltr">[[User:MediaWiki message delivery|MediaWiki message delivery]]</bdi> 4 mai 2026 à 22:43 (CEST) <!-- Message envoyé par User:STei (WMF)@metawiki en utilisant la liste sur https://meta.wikimedia.org/w/index.php?title=Global_message_delivery/Targets/Tech_ambassadors&oldid=30498077 --> == Actualités techniques n° 2026-20 == <section begin="technews-2026-W20"/><div class="plainlinks"> Dernières '''[[m:Special:MyLanguage/Tech/News|actualités techniques]]''' de la communauté technique de Wikimedia. N’hésitez pas à informer les autres utilisateurs de ces changements. Certains changements ne vous concernent pas. [[m:Special:MyLanguage/Tech/News/2026/20|D’autres traductions]] sont disponibles. '''En lumière cette semaine''' * La Communauté Technique a publié [[m:Special:MyLanguage/Community Wishlist/How to write a good wish|de nouvelles directives]] expliquant comment les souhaits sur la Liste de souhaits de la communauté sont triés et priorisés. La documentation vise à aider les contributeurs à rédiger des propositions plus solides en clarifiant les facteurs qui influencent les décisions de priorisation. Au-delà du nombre de votes, les directives mettent en avant des considérations telles que l'impact potentiel sur la communauté pour déterminer quels souhaits avanceront. '''Actualités pour la contribution''' * L'équipe de croissance des lecteurs lance une expérience pour tester une nouvelle [[mw:Special:MyLanguage/Readers/Reader_Growth/Share_Card|fonctionnalité de Partage de Carte]] qui permet aux lecteurs de créer des cartes visuellement attrayantes à partir d'articles Wikipédia ou de sections d'articles sélectionnées et de les partager en ligne, chaque carte renvoyant à l'article original afin d'aider à augmenter le lectorat et la découverte des articles. Le test A/B réservé aux mobiles ne sera disponible qu'à une partie des lecteurs sur les Wikipédia en arabe, chinois, français, vietnamien et anglais afin de mieux comprendre les habitudes de lecture et de partage, et est prévu pour commencer la semaine du 18 mai pour une durée de quatre semaines. * Les applications Wikipedia pour Android et iOS ont récemment publié en version bêta le [[mw:Special:MyLanguage/Wikimedia_Apps/Team/25th_Birthday_Reading_Challenge|défi de lecture de 25 jours]], dans le cadre des efforts visant à stimuler l'engagement des lecteurs en encourageant les utilisateurs à atteindre des objectifs de lecture. Pour suivre leur série de lectures pendant le défi, les utilisateurs de l'application peuvent ajouter un widget avec Baby Globe à leur écran d'accueil. Le défi commence officiellement le 11 mai. * [[File:Reload icon with two arrows.svg|12px|link=|class=skin-invert|Sujet récurrent]] Voir {{PLURAL:17|la tâche soumise|les {{formatnum:17}} tâches soumises}} par la communauté [[m:Special:MyLanguage/Tech/News/Recently resolved community tasks|résolue{{PLURAL:17||s}} la semaine dernière]]. Par exemple, un problème où la préférence globale pour activer la coloration syntaxique dans le wikitexte pouvait s'éteindre de manière inattendue après avoir été activée a maintenant été corrigé. [https://phabricator.wikimedia.org/T425286] '''Actualités pour la contribution technique''' * [[File:Octicons-tools.svg|12px|link=|alt=|Sujet technique]] Le module ResourceLoader <bdi lang="zxx" dir="ltr"><code><nowiki>mediawiki.ui.input</nowiki></code></bdi>, obsolète depuis [[m:Special:MyLanguage/Tech/News/2023/39|septembre 2023]], sera supprimé cette semaine. Il existe un [[mw:Special:MyLanguage/Codex/Migrating_from_MediaWiki_UI|guide pour migrer de l’interface MediaWiki UI vers Codex]] pour tous les outils qui l’utilisent. [https://phabricator.wikimedia.org/T420125] * [[File:Reload icon with two arrows.svg|12px|link=|class=skin-invert|Sujet récurrent]] Détail des mises-à-jour à venir cette semaine : [[mw:MediaWiki 1.47/wmf.2|MediaWiki]] '''''[[m:Special:MyLanguage/Tech/News|Actualités techniques]]''' préparées par les [[m:Special:MyLanguage/Tech/News/Writers|rédacteurs des actualités techniques]] et postées par [[m:Special:MyLanguage/User:MediaWiki message delivery|robot]]. [[m:Special:MyLanguage/Tech/News#contribute|Contribuer]]&nbsp;• [[m:Special:MyLanguage/Tech/News/2026/20|Traduire]]&nbsp;• [[m:Tech|Obtenir de l’aide]]&nbsp;• [[m:Talk:Tech/News|Donner son avis]]&nbsp;• [[m:Global message delivery/Targets/Tech ambassadors|S’abonner ou se désabonner]].'' </div><section end="technews-2026-W20"/> <bdi lang="en" dir="ltr">[[User:MediaWiki message delivery|MediaWiki message delivery]]</bdi> 11 mai 2026 à 21:20 (CEST) <!-- Message envoyé par User:STei (WMF)@metawiki en utilisant la liste sur https://meta.wikimedia.org/w/index.php?title=Global_message_delivery/Targets/Tech_ambassadors&oldid=30524429 --> == Actualités techniques n° 2026-21 == <section begin="technews-2026-W21"/><div class="plainlinks"> Dernières '''[[m:Special:MyLanguage/Tech/News|actualités techniques]]''' de la communauté technique de Wikimedia. N’hésitez pas à informer les autres utilisateurs de ces changements. Certains changements ne vous concernent pas. [[m:Special:MyLanguage/Tech/News/2026/21|D’autres traductions]] sont disponibles. '''En lumière cette semaine''' * L'équipe de Wikipédia abstraite a identifié cinq wikis pilotes potentiels pour évaluer leur intérêt à adopter des articles abstraits sur leurs wikis. Les pilotes sont Wikipédia en Malayalam, en Bengali, en Dagbani, en Arabe et en Indonésien. La période de retour d'information sera ouverte jusqu'au 22 mai. Si votre communauté est intéressée à devenir un pilote, [[m:Talk:Abstract Wikipedia|faites-nous savoir sur Meta]]. '''Actualités pour la contribution''' * Une expérience visant à afficher [[mw:Special:MyLanguage/Readers/Reader Experience/Reading lists|les listes de lecture]] aux lecteurs non connectés sur le web mobile sera lancée le 18 mai sur les Wikipédias Allemande, Espagnole, Italienne, Portugaise, Polonaise, Néerlandaise, Turque et Ourdou, et durera un mois. Cet effort soutient des objectifs plus larges consistant à aider les lecteurs à enregistrer et organiser des articles pour une lecture ultérieure, tout en encourageant des habitudes qui pourraient mener à de futures contributions sur Wikipédia. * Pour prendre en charge un bouton de marquage dans la fonctionnalité bêta Liste de lecture, le menu "Outils > Action" a été mis à jour pour afficher des icônes, y compris l'indicateur en forme d'étoile de suivi qui aide les éditeurs à identifier les articles suivis temporairement. Les icônes correspondent désormais également à celles utilisées sur mobile, améliorant la cohérence entre les plateformes. Le changement est actuellement limité au menu des actions et concerne principalement les éditeurs ayant des droits d'utilisateur privilégié. [https://phabricator.wikimedia.org/T426008] * [[mw:Special:MyLanguage/VisualEditor/Suggestion Mode|Mode de Suggestion]] a été publié en tant qu'[[w:en:A/B test|test A/B]] pour les nouveaux éditeurs sur le site mobile à [[phab:T421189|~15 Wikipédias]]. L'expérience mesurera l'impact que le Mode de Suggestion a sur la proportion de sessions d'édition sur le web mobile par des nouveaux éditeurs qui aboutissent à des modifications constructives (non annulées) des articles. L'expérience évaluera également l'impact de la fonctionnalité sur la rétention des éditeurs et surveillera les changements dans les taux d'annulation et de blocage. * [[File:Reload icon with two arrows.svg|12px|link=|class=skin-invert|Sujet récurrent]] Voir {{PLURAL:27|la tâche soumise|les {{formatnum:27}} tâches soumises}} par la communauté [[m:Special:MyLanguage/Tech/News/Recently resolved community tasks|résolue{{PLURAL:27||s}} la semaine dernière]]. Par exemple, un problème dans l'application Android de Wikipédia où les images pourraient parfois ne pas se charger après avoir ouvert une notification de liste de lecture recommandée, a maintenant été corrigé. [https://phabricator.wikimedia.org/T418231] '''Actualités pour la contribution technique''' * L'[[mw:Special:MyLanguage/Wikidata Platform|équipe de la Plateforme Wikidata]] a publié sa [[d:Special:MyLanguage/Wikidata:SPARQL query service/WDQS backend update/Backend Replacement|recommandation de remplacement du backend]] et l'[[wikitech:Wikidata Query Service/WDQS Architecture re-design|architecture technique]] qui l'accompagne pour la migration du Wikidata Query Service (WDQS) hors de Blazegraph grap. Les retours sont attendus jusqu'au 25 mai 2026, en particulier sur les éventuelles lacunes et impacts sur les cas d'utilisation avancés. Les membres de la communauté Wikidata et les utilisateurs de WDQS sont également encouragés à aider à identifier les outils et flux de travail à fort impact qui pourraient nécessiter une attention sur [[d:Wikidata:SPARQL query service/WDQS backend update/High-Impact Use Cases|cette page]]. Les retours peuvent être partagés sur la [[d:Wikidata talk:SPARQL query service/WDQS backend update|page de discussion de la migration]] ou lors de la [[d:Special:MyLanguage/Wikidata:Blazegraph Migration Office Hours|prochaine heure de bureau]]. Voir le [[d:Special:MyLanguage/Wikidata:Wikidata Platform team/Newsletter|bulletin de l'équipe WDP]] pour plus de détails. * [[File:Reload icon with two arrows.svg|12px|link=|class=skin-invert|Sujet récurrent]] Détail des mises-à-jour à venir cette semaine : [[mw:MediaWiki 1.47/wmf.3|MediaWiki]] '''En détails''' * Sur les Wikipédia en anglais, en français, en japonais et quelques autres, il y a eu un [[diffblog:2025/09/02/better-detecting-bots-and-replacing-our-captcha/|essai de hCaptcha]], un service tiers de détection de robots. L'essai a montré que hCaptcha détecte et dissuade efficacement certaines activités automatisées de mauvaise foi, à la fois par lui-même et en donnant des signaux aux [[w:en:Wikipedia:Village pump (technical)/Archive 225#Introducing SuggestedInvestigations|checkusers et stewards]] pour qu'ils enquêtent. Comme les résultats étaient positifs, hCaptcha sera déployé sur toutes les wikis au cours des prochaines semaines. [[mw:Special:MyLanguage/Product Safety and Integrity/Anti-abuse signals/hCaptcha|Voir la page du projet hCaptcha]] pour des informations techniques sur la mise en œuvre et les protections de la vie privée. [[diffblog:2026/05/04/better-detecting-bots-and-replacing-our-captcha-part-2/|En savoir plus]]. * La dernière mise à jour de la Technologie communautaire est désormais disponible, avec des progrès dans plusieurs initiatives de la Liste de souhaits communautaire, y compris l'extension des listes de lecture de l'application mobile au site web, la prise en charge de nouvelles langues pour "Who Wrote That" et le Tableau de bord personnel, des améliorations du rendu 3D et des graphiques, ainsi que des travaux à venir sur le tri des pages de discussion, la lecture audio et les flux de travail d'édition. La mise à jour partage également les priorités actuelles, les tendances de l'état de la Liste de souhaits et les opportunités de retour d'information de la communauté sur les domaines de concentration futurs et le Plan annuel 2026–2027 de la Wikimedia Foundation. [[m:Special:MyLanguage/Community Wishlist/Updates#May 13, 2026: Latest updates from the Community Tech team|Lisez le bulletin d'information complet pour plus de détails]]. '''''[[m:Special:MyLanguage/Tech/News|Actualités techniques]]''' préparées par les [[m:Special:MyLanguage/Tech/News/Writers|rédacteurs des actualités techniques]] et postées par [[m:Special:MyLanguage/User:MediaWiki message delivery|robot]]. [[m:Special:MyLanguage/Tech/News#contribute|Contribuer]]&nbsp;• [[m:Special:MyLanguage/Tech/News/2026/21|Traduire]]&nbsp;• [[m:Tech|Obtenir de l’aide]]&nbsp;• [[m:Talk:Tech/News|Donner son avis]]&nbsp;• [[m:Global message delivery/Targets/Tech ambassadors|S’abonner ou se désabonner]].'' </div><section end="technews-2026-W21"/> <bdi lang="en" dir="ltr">[[User:MediaWiki message delivery|MediaWiki message delivery]]</bdi> 18 mai 2026 à 22:21 (CEST) <!-- Message envoyé par User:STei (WMF)@metawiki en utilisant la liste sur https://meta.wikimedia.org/w/index.php?title=Global_message_delivery/Targets/Tech_ambassadors&oldid=30539262 --> == Actualités techniques n° 2026-22 == <section begin="technews-2026-W22"/><div class="plainlinks"> Dernières '''[[m:Special:MyLanguage/Tech/News|actualités techniques]]''' de la communauté technique de Wikimedia. N’hésitez pas à informer les autres utilisateurs de ces changements. Certains changements ne vous concernent pas. [[m:Special:MyLanguage/Tech/News/2026/22|D’autres traductions]] sont disponibles. '''En lumière cette semaine''' * Faisant suite à une [[mw:Special:MyLanguage/Contributors/Account Creation Experiments#LOWM|expérience fructueuse sur la création de comptes]], un message d'avertissement pour les personnes déconnectées sera déployé sur les wikis Wikimédia durant la première semaine de juin. Ce changement n'affectera que les personnes déconnectées sur l'interface web mobile qui commencent à modifier. Cette nouvelle expérience est faite pour encourager la création de comptes, tout en autorisant aux utilisateurs de modifier à l'aide de comptes temporaires. Les résultats de l'expérience ont montré une augmentation de la création de compte d'environ 27 % pour ceux ayant vu le nouveau message. Comme prévu, puisque plus de personnes créent un compte, la création de comptes temporaires a diminué de 16 %. L'expérience n'a pas montré d'autres changements sur la qualité des modifications ou sur les autres indicateurs surveillés. [https://phabricator.wikimedia.org/T424595] '''Actualités pour la contribution''' * Pour des raisons de sécurité, les membres de certains groupes d’utilisateurs sont [[m:Special:MyLanguage/Mandatory two-factor authentication for users with some extended rights|forcés d'avoir l'authentification à 2 facteurs]] (A2F) d'activée. Les membres de ces groupes seront dans l'impossibilité de désactiver la dernière méthode d'A2F sur leur compte, et il sera impossible d'ajouter des utilisateurs sans A2F à ces groupes. Ces utilisateurs auront toujours la possibilité d'ajouter ou d'enlever des nouvelles méthodes d'authentification, tant qu'une de ces méthodes est toujours activée. Dans les prochaines semaines, les utilisateurs sans A2F seront retirés de ces groupes. Cela s'applique entre autres aux bureaucrates. Veuillez lire les tâches liées pour les dates de déploiement. [https://phabricator.wikimedia.org/T423119][https://phabricator.wikimedia.org/T423120] * L'[[m:Special:MyLanguage/WMDE Technical Wishes|équipe des souhaits techniques de Wikimédia Allemagne (WMDE)]] va lancer un [[w:fr:Test A/B|test A/B]] sur [[:phab:T415904|10 wikis]], pour essayer des [[m:WMDE Technical Wishes/References/Reference Previews|améliorations potentielles pour les aperçus de références]]. Cette expérience durera environ 2 semaines à la fin mai ou début juin et affectera 10 % du lectorat sur ordinateur sur les wikis participants. * Après deux expériences fructueuses, l'équipe Croissance du lectorat déploiera une fonctionnalité de [[mw:Special:MyLanguage/Readers/Reader Growth/Image Browsing|visionnage d'images]] en bêta pour toutes les Wikipédia sur mobile le 25 mai. Cela veut dire que toutes les personnes ayant les fonctionnalités bêtas activées verront cette fonctionnalité. Les autres pourront l’activer dans leurs préférences. Cette fonctionnalité inclura un carrousel de toutes les images d'un article en haut de celui-ci, avec la possibilité pour les contributeurs d’[[mw:Readers/Reader_Growth/Image_Browsing#Phase_2.1_beta_feature|exclure des images du carrousel d'un article ou d'enlever la fonctionnalité pour l'entièreté de l'article]]. * [[File:Reload icon with two arrows.svg|12px|link=|class=skin-invert|Sujet récurrent]] Voir {{PLURAL:30|la tâche soumise|les {{formatnum:30}} tâches soumises}} par la communauté [[m:Special:MyLanguage/Tech/News/Recently resolved community tasks|résolue{{PLURAL:30||s}} la semaine dernière]]. Par exemple, les fichiers STL tridimensionnels étaient affichés incorrectement par l'extension 3D du lecteur multimédia, ce qui est maintenant corrigé. [https://phabricator.wikimedia.org/T416723] '''Actualités pour la contribution technique''' * Les classes CSS dépréciées <bdi lang="zxx" dir="ltr"><code><nowiki>tleft</nowiki></code></bdi> et <bdi lang="zxx" dir="ltr"><code><nowiki>tright</nowiki></code></bdi> ont été remplacées par <bdi lang="zxx" dir="ltr"><code><nowiki>floatleft</nowiki></code></bdi> et <bdi lang="zxx" dir="ltr"><code><nowiki>floatright</nowiki></code></bdi> car les premières ne fonctionnent pas correctement sur toutes les plateformes, dont l'interface web mobile et l'application mobile. Les projets se servant de ces classes sont encouragés à vérifier leur usage et à planifier leur migration. Sachez que <bdi lang="zxx" dir="ltr"><code><nowiki>floatleft</nowiki></code></bdi> et <bdi lang="zxx" dir="ltr"><code><nowiki>floatright</nowiki></code></bdi> pourraient aussi être dépréciées dans le futur, même s’il n'y a pas de calendrier défini. [[phab:T426452|En savoir plus]]. * [[File:Reload icon with two arrows.svg|12px|link=|class=skin-invert|Sujet récurrent]] Détail des mises-à-jour à venir cette semaine : [[mw:MediaWiki 1.47/wmf.4|MediaWiki]] '''''[[m:Special:MyLanguage/Tech/News|Actualités techniques]]''' préparées par les [[m:Special:MyLanguage/Tech/News/Writers|rédacteurs des actualités techniques]] et postées par [[m:Special:MyLanguage/User:MediaWiki message delivery|robot]]. [[m:Special:MyLanguage/Tech/News#contribute|Contribuer]]&nbsp;• [[m:Special:MyLanguage/Tech/News/2026/22|Traduire]]&nbsp;• [[m:Tech|Obtenir de l’aide]]&nbsp;• [[m:Talk:Tech/News|Donner son avis]]&nbsp;• [[m:Global message delivery/Targets/Tech ambassadors|S’abonner ou se désabonner]].'' </div><section end="technews-2026-W22"/> <bdi lang="en" dir="ltr">[[User:MediaWiki message delivery|MediaWiki message delivery]]</bdi> 25 mai 2026 à 23:52 (CEST) <!-- Message envoyé par User:Quiddity (WMF)@metawiki en utilisant la liste sur https://meta.wikimedia.org/w/index.php?title=Global_message_delivery/Targets/Tech_ambassadors&oldid=30584502 --> == Votez maintenant aux élections 2026 de l'U4C == <section begin="announcement-content" /> Les votants éligibles sont invités à participer à l'élection 2026 du [[m:Special:MyLanguage/Universal_Code_of_Conduct/Coordinating_Committee|Comité de coordination du Code de conduite universel]]. De plus amples informations – notamment sur la vérification de l'éligibilité, le processus de vote, les candidats et un lien vers le scrutin – sont disponibles sur Meta à la [[m:Special:MyLanguage/Universal_Code_of_Conduct/Coordinating_Committee/Election/2026|page d'informations sur les élections de 2026]]. Le scrutin se termine le 2 juin 2026 à [https://zonestamp.toolforge.org/1780358400 00 h 00 UTC]. Veuillez voter si votre compte est éligble. Les résultats seront disponibles avant le 14 juin 2026. -- en coopération avec l'U4C.<section end="announcement-content" /> [[m:User:Keegan (WMF)|Keegan (WMF)]] ([[m:User talk:Keegan (WMF)|talk]]) 27 mai 2026 à 19:14 (CEST) <!-- Message envoyé par User:Keegan (WMF)@metawiki en utilisant la liste sur https://meta.wikimedia.org/w/index.php?title=Distribution_list/Global_message_delivery&oldid=30513860 --> == Actualités techniques n° 2026-23 == <section begin="technews-2026-W23"/><div class="plainlinks"> Dernières '''[[m:Special:MyLanguage/Tech/News|actualités techniques]]''' de la communauté technique de Wikimedia. N’hésitez pas à informer les autres utilisateurs de ces changements. Certains changements ne vous concernent pas. [[m:Special:MyLanguage/Tech/News/2026/23|D’autres traductions]] sont disponibles. '''Actualités pour la contribution''' * L'équipe [[mw:Special:MyLanguage/Readers/Reader Experience|Reader Experience]] mène une expérience pour montrer la fonctionnalité [[mw:Special:MyLanguage/Readers/Reader Experience/Reading lists|listes de lecture]], qui est encore en développement, aux lecteurs non connectés sur mobile afin de tester si elle encourage la création de compte à un rythme plus élevé que le bouton watchstar. L'[[mw:Special:MyLanguage/Readers/Reader Experience/Reading lists#Experiment timeline|expérience]] a été lancée le 18 mai sur les wikis en allemand, espagnol, italien, portugais, polonais, néerlandais, turc et ourdou, et elle durera un mois. * L'équipe Wikimedia Apps a publié la [[mw:Special:MyLanguage/Wikimedia Apps/Team/Explore Feed Refresh/Phase 1|Phase 1]] du flux d'accueil repensé pour l'application Android Beta. Le nouveau flux d'accueil comprend un onglet « Communauté » actualisé et un onglet « Pour vous » personnalisé contenant des recommandations de lecture mises à jour quotidiennement. La refonte fait partie d'un effort plus large visant à améliorer la découverte de contenu et à créer des expériences d'apprentissage plus engageantes dans les applications Wikipédia. * [[File:Reload icon with two arrows.svg|12px|link=|class=skin-invert|Sujet récurrent]] Voir {{PLURAL:18|la tâche soumise|les {{formatnum:18}} tâches soumises}} par la communauté [[m:Special:MyLanguage/Tech/News/Recently resolved community tasks|résolue{{PLURAL:18||s}} la semaine dernière]]. Par exemple, un problème où les images pouvaient ne pas se charger pour certaines modifications suggérées sur [[w:Special:Homepage|Special:Homepage]], laissant la vignette bloquée dans un état de chargement, a maintenant été corrigé. [https://phabricator.wikimedia.org/T424048] '''Actualités pour la contribution technique''' * [[File:Reload icon with two arrows.svg|12px|link=|class=skin-invert|Sujet récurrent]] Détail des mises-à-jour à venir cette semaine : [[mw:MediaWiki 1.47/wmf.5|MediaWiki]] '''''[[m:Special:MyLanguage/Tech/News|Actualités techniques]]''' préparées par les [[m:Special:MyLanguage/Tech/News/Writers|rédacteurs des actualités techniques]] et postées par [[m:Special:MyLanguage/User:MediaWiki message delivery|robot]]. [[m:Special:MyLanguage/Tech/News#contribute|Contribuer]]&nbsp;• [[m:Special:MyLanguage/Tech/News/2026/23|Traduire]]&nbsp;• [[m:Tech|Obtenir de l’aide]]&nbsp;• [[m:Talk:Tech/News|Donner son avis]]&nbsp;• [[m:Global message delivery/Targets/Tech ambassadors|S’abonner ou se désabonner]].'' </div><section end="technews-2026-W23"/> <bdi lang="en" dir="ltr">[[User:MediaWiki message delivery|MediaWiki message delivery]]</bdi> 1 juin 2026 à 23:08 (CEST) <!-- Message envoyé par User:STei (WMF)@metawiki en utilisant la liste sur https://meta.wikimedia.org/w/index.php?title=Global_message_delivery/Targets/Tech_ambassadors&oldid=30613639 --> == Actualités techniques n° 2026-24 == <section begin="technews-2026-W24"/><div class="plainlinks"> Dernières '''[[m:Special:MyLanguage/Tech/News|actualités techniques]]''' de la communauté technique de Wikimedia. N’hésitez pas à informer les autres utilisateurs de ces changements. Certains changements ne vous concernent pas. [[m:Special:MyLanguage/Tech/News/2026/24|D’autres traductions]] sont disponibles. '''En lumière cette semaine''' * Wikimedia Entreprise a relevé les limites d’utilisation gratuite de ses API. La limite mensuelle de requêtes pour l’API « à la demande » (<i lang="en">On-demand</i>) est passée de {{formatnum:5000}} à {{formatnum:50000}} requêtes, tandis que celle de l’API des instantanés (<i lang="en">Snapshot</i>) est passée de 15 à 30 requêtes par mois. De plus, les instantanés de contenus structurés sont désormais accessibles aux comptes gratuits. Ces changements élargissent l’accès aux données de Wikimedia Entreprise pour les développeurs et développeuses, les chercheurs et chercheuses et les organisations qui utilisent les contenus Wikimédia. [https://enterprise.wikimedia.com/blog/enhanced-free-api] '''Actualités pour la contribution''' * La [[mw:Special:MyLanguage/Wikimedia_Apps/Team/Explore Feed Refresh/Phase 1|nouvelle version du Fil d’exploration]], désormais appelé « Fil d’accueil », est en cours de déploiement auprès de 50 % des utilisateurs de l’application Wikipédia pour Android. Le fil d’accueil aide le lectorat à découvrir du contenu pertinent grâce à deux nouveaux onglets : « Communauté » et « Pour vous ». L’onglet « Communauté » propose un flux défilant de contenus sélectionnés et d’actualités provenant de l’ensemble de la communauté et du mouvement Wikimédia, tandis que l’onglet « Pour vous » offre une expérience en plein écran et par glissement qui présente des contenus adaptés aux centres d’intérêt de l’utilisateur ou utilisatrice. Cette refonte s’inscrit dans le cadre d’un travail en cours visant à améliorer la découverte et à enrichir l’expérience d’apprentissage au sein de l’application Wikipédia. * Le jeu-questionnaire quotidien [[mw:Special:MyLanguage/Wikimedia Apps/Team/iOS/"Which came first?" Game|Qu’est-ce qui est arrivé en premier ?]] est désormais disponible dans la version bêta de l’application Wikipédia pour iOS en anglais, allemand, français, portugais, russe, espagnol, arabe, chinois et turc. Le jeu s’appuie sur des événements historiques tirés de la rubrique « Éphéméride » de Wikipédia et met les lecteurs au défi de deviner lequel des deux événements s’est produit en premier. Le jeu avait déjà été lancé sur Android. Les communautés souhaitant rendre le jeu disponible dans leur langue peuvent [[mw:Special:MyLanguage/Wikimedia_Apps/Team/Games#Game availability by language|consulter les instructions et les conditions requises]]. * [[m:Special:MyLanguage/WMDE Technical Wishes/Sub-referencing|Les sous-références]], une nouvelle fonctionnalité de MediaWiki permettant aux contributeurs de réutiliser des références avec des détails différents, va commencer à être déployée sur les wikis Wikimédia après une phase pilote réussie. Le déploiement débutera le 8 juin pour la plupart des [[wikitech:Deployments/Train#Wednesday|wikis du groupe 1]] et Wikipédia en français, puis d'autres éditions linguistiques de Wikipédia bénéficieront de cette fonctionnalité au cours des prochains mois. Les communautés sont invitées à se préparer en vérifiant s’il existe des [https://translatewiki.net/w/i.php?title=Special%3ATranslate&group=ext-cite&language=en&action_source=search&filter=%21translated&optional=1&action=translate messages non traduits de l’extension Cite] dans leur langue et en passant en revue toute utilisation de l’outil [[mw:Special:MyLanguage/Reference Tooltips|Infobulles des références]], qui pourraient nécessiter des [[:phab:T416304#11668731|mises à jour]] pour prendre en charge la nouvelle fonctionnalité. Les wikis utilisant les [[mw:Special:MyLanguage/Help:Reference Previews|aperçus de référence]] n’ont aucune action à entreprendre. Les communautés peuvent également créer la [[Special:TrackingCategories|catégorie de suivi]] ''cite-tracking-category-ref-details'' en tant que catégorie cachée à l’aide de <code><nowiki>__HIDDENCAT__</nowiki></code> (ou d’un modèle dédié), et la relier à l’élément Wikidata correspondant [[d:Q129764848]]. [https://phabricator.wikimedia.org/T425662] * L'[[mw:Special:MyLanguage/Readers/Reader Growth/Mobile page previews#Experimentation|expérience d'Aperçus de page]] sur le Web mobile a pris fin. L'équipe a décidé de ne pas déployer cette fonctionnalité après que les résultats ont montré qu'elle n'avait pas d'impact statistiquement significatif sur la fidélisation des lecteurs, l'amélioration de la fidélisation étant le principal indicateur de réussite. Les « Aperçus de page », déjà disponibles sur ordinateur et dans les applications, affichent une vignette, le premier paragraphe et un lien vers l'article complet lorsque les lecteurs cliquent sur un lien bleu. L'expérience a testé cette fonctionnalité sur le Web mobile sur six versions de Wikipédia. * La [[mw:Special:MyLanguage/Codex/Design/Icons|bibliothèque d'icônes de l'interface utilisateur]] sera [[phab:T399175|mise à jour dans le courant de cette semaine ou la semaine prochaine]]. La plupart des quelque 300 icônes ont été légèrement peaufinées et une trentaine de nouvelles icônes ont été ajoutées. Ces modifications améliorent les icônes afin de les rendre plus cohérentes et plus compréhensibles, et d'offrir un meilleur équilibre visuel lorsqu'elles sont utilisées en groupe. * L'interface [[mw:Special:MyLanguage/Universal Language Selector|Sélecteur universel de langue]] (ULS) de MediaWiki, qui aide les utilisateurs à sélectionner du contenu dans d'autres langues, a été mise à jour. La nouvelle version améliore la rapidité et l'accessibilité, et les utilisateurs des projets Wikimédia peuvent désormais épingler des langues pour changer de langue plus rapidement. Le déploiement sur les sites Wikimédia se fera progressivement au cours des prochaines semaines. Vous pouvez la tester dès maintenant en tant que fonctionnalité bêta en sélectionnant [[Special:Preferences#mw-prefsection-betafeatures|les fonctionnalités bêta]] dans les préférences de votre profil et partager vos commentaires sur [[mw:Special:MyLanguage/Universal Language Selector/New ULS|la page du projet]]. * [[File:Reload icon with two arrows.svg|12px|link=|class=skin-invert|Sujet récurrent]] Voir {{PLURAL:21|la tâche soumise|les {{formatnum:21}} tâches soumises}} par la communauté [[m:Special:MyLanguage/Tech/News/Recently resolved community tasks|résolue{{PLURAL:21||s}} la semaine dernière]]. Par exemple, un problème du le tableau de bord d'analyse des pages vues sur pageviews.wmcloud.org qui a arrêté de mettre à jour les données graphiques en mai 2026, affectant tous les utilisateurs, a été résolu. [https://phabricator.wikimedia.org/T427171] '''Actualités pour la contribution technique''' * La signature de la fonction <bdi lang="zxx" dir="ltr"><code><nowiki>mw.util.addPortletLink()</nowiki></code></bdi> a été simplifiée. Les développeurs peuvent désormais passer un objet de configuration à la place d'une liste de paramètres positionnels lors de la création de liens vers des portlets. L'ancienne signature de la fonction reste prise en charge à des fins de compatibilité ascendante. Par exemple, au lieu de : <bdi lang="zxx" dir="ltr"><code><nowiki>mw.util.addPortletLink('p-cactions', '#', 'Stub', 'ca-stubtag', 'Add a stub tag to this page');</nowiki></code></bdi>, utilisez <bdi lang="zxx" dir="ltr"><code><nowiki>mw.util.addPortletLink('p-cactions', { href: '#', text: 'Stub', id: 'ca-stubtag', tooltip: 'Add a stub tag to this page' });</nowiki></code></bdi>. Les responsables de la maintenance des scripts sont invités à passer en revue les utilisations existantes de <bdi lang="zxx" dir="ltr"><code><nowiki>addPortletLink()</nowiki></code></bdi> et à les mettre à jour si nécessaire. Cette modification sera disponible sur tous les wikis à partir du 11 juin. Merci à Gerges, bénévole de la communauté, d'avoir apporté cette amélioration. [https://phabricator.wikimedia.org/T427945] * '''Discussion sur la liste de souhaits de la communauté''': les [[m:Special:MyLanguage/Community Wishlist/Updates#May 20, 2026: Community Tech becomes a program|changements introduits]] par les équipes Produit et Technologie visent à augmenter le nombre et la complexité des souhaits exaucés, notamment par la dissolution de l'équipe Community Tech. Ils [[m:Special:MyLanguage/Community Wishlist/Updates|mènent actuellement des discussions]] sur une [[m:Talk:Community Wishlist#Proposed direction for Wishlist|orientation proposée pour la liste de souhaits]] émanant des membres de la communauté. Cela inclut des moyens de structurer le vote annuel, un meilleur suivi des souhaits, la suppression de certains domaines prioritaires et des [[m:Special:MyLanguage/Community Wishlist/Updates|mises à jour concernant le personnel]]. * [[File:Reload icon with two arrows.svg|12px|link=|class=skin-invert|Sujet récurrent]] Détail des mises-à-jour à venir cette semaine : [[mw:MediaWiki 1.47/wmf.6|MediaWiki]] '''''[[m:Special:MyLanguage/Tech/News|Actualités techniques]]''' préparées par les [[m:Special:MyLanguage/Tech/News/Writers|rédacteurs des actualités techniques]] et postées par [[m:Special:MyLanguage/User:MediaWiki message delivery|robot]]. [[m:Special:MyLanguage/Tech/News#contribute|Contribuer]]&nbsp;• [[m:Special:MyLanguage/Tech/News/2026/24|Traduire]]&nbsp;• [[m:Tech|Obtenir de l’aide]]&nbsp;• [[m:Talk:Tech/News|Donner son avis]]&nbsp;• [[m:Global message delivery/Targets/Tech ambassadors|S’abonner ou se désabonner]].'' </div><section end="technews-2026-W24"/> <bdi lang="en" dir="ltr">[[User:MediaWiki message delivery|MediaWiki message delivery]]</bdi> 8 juin 2026 à 23:29 (CEST) <!-- Message envoyé par User:STei (WMF)@metawiki en utilisant la liste sur https://meta.wikimedia.org/w/index.php?title=Global_message_delivery/Targets/Tech_ambassadors&oldid=30650573 --> == Actualités techniques n° 2026-25 == <section begin="technews-2026-W25"/><div class="plainlinks"> Dernières '''[[m:Special:MyLanguage/Tech/News|actualités techniques]]''' de la communauté technique de Wikimedia. N’hésitez pas à informer les autres utilisateurs de ces changements. Certains changements ne vous concernent pas. [[m:Special:MyLanguage/Tech/News/2026/25|D’autres traductions]] sont disponibles. '''En lumière cette semaine''' * L'[[mw:Special:MyLanguage/Readers/Reader Growth|équipe chargée de la croissance du lectorat]] a lancé une fonctionnalité bêta d'[[mw:Special:MyLanguage/Readers/Reader Growth/Image Browsing|exploration des images]] sur la version mobile de toutes les Wikipédias. Cette fonctionnalité affiche un carrousel d'images en haut des articles contenant au moins trois images. Les contributeurs peuvent configurer cette fonctionnalité à l'aide des commandes suivantes : pour masquer une image spécifique sur une page, utilisez soit <code>class=notpageimage</code> pour l'exclure des aperçus miniatures, soit <code>class=noviewer</code> pour l'exclure de MediaViewer. Le carrousel peut également être désactivé complètement sur une page à l'aide du mot magique <code><nowiki>__NOMEDIAVIEWERCAROUSEL__</nowiki></code>. Pour faire des retours ou signaler des bugs, rendez-vous sur la [[mw:Talk:Readers/Reader Growth/Image Browsing|page de discussion du projet]]. * Les [[mw:Special:MyLanguage/Help:Tables#class="wikitable"|Wikitables]] peuvent désormais être [[mw:Special:MyLanguage/Help:Sortable tables#Forcing the initial sort direction|triées par ordre décroissant]] dès le premier clic en ajoutant <code dir=ltr>data-sort-order="desc"</code> à la cellule d'en-tête. Auparavant, par défaut, cliquer une première fois sur l'en-tête d'une colonne entraînait un tri par ordre croissant. Cette nouveauté offre davantage de contrôle et de flexibilité pour les Wikitables, tandis que le comportement par défaut pour les clics suivants reste inchangé. [https://phabricator.wikimedia.org/T398416] '''Actualités pour la contribution''' * La fonctionnalité d'[[mw:Special:MyLanguage/Article guidance|Aide à la rédaction d'articles]] est actuellement en phase de test auprès de certains contributeurs qui créent de nouveaux articles sur les Wikipédias en anglais simplifié, en français et en turc. L'expérience débutera bientôt sur les Wikipédias en arabe et en bengali également. [[w:simple:Special:NewArticle|Cette fonctionnalité]] fournit aux contributeurs des conseils élaborés par la communauté afin de les aider à créer des articles conformes aux normes communautaires. Les contributeurs expérimentés peuvent continuer à créer ou à adapter des modèles pour des types d'articles spécifiques qui sont couramment créés par des contributeurs moins expérimentés. Ces modèles guident les contributeurs moins expérimentés dans la création d'articles de haute qualité. Un guide rapide des balises utilisées dans les modèles est disponible sur [[mw:Special:MyLanguage/Article guidance/Test feature guide#Markups in outlines|cette page]]. [[w:fr:Projet:Aide à la rédaction d'articles#Liste de plans d'aide à la rédaction|Des exemples de modèles]] pouvant être adaptés, ainsi que des instructions sur la manière de les adapter, se trouvent dans [[mw:Special:MyLanguage/Article guidance#Adapting a sample outline in a Wikipedia|cette section]] de la page du projet. * Les wikis qui souhaitent remplacer le bouton « indéfiniment » dans la page Special:Block pour les comptes temporaires (par exemple, les wikis qui bloquent les utilisateurs temporaires uniquement jusqu'à l'expiration de leur compte) pourront le faire en créant [[MediaWiki:ipb-indefinite-expiry-temporary-account]] avec la durée de blocage souhaitée. [https://phabricator.wikimedia.org/T427125] * [[File:Reload icon with two arrows.svg|12px|link=|class=skin-invert|Sujet récurrent]] Voir {{PLURAL:41|la tâche soumise|les {{formatnum:41}} tâches soumises}} par la communauté [[m:Special:MyLanguage/Tech/News/Recently resolved community tasks|résolue{{PLURAL:41||s}} la semaine dernière]]. '''Actualités pour la contribution technique''' * D'ici la fin du mois de juin, une chaîne « user-agent » valide sera requise pour les téléchargements automatisés de sauvegardes depuis le site dumps.wikimedia.org. Les requêtes automatisées fournissant une chaîne « user-agent » générique ou vide seront bloquées. Cette mesure [[phab:T400119|renforce l'application]] de la [[foundation:Special:MyLanguage/Policy:Wikimedia Foundation User-Agent Policy|politique relative à l'agent utilisateur]] en vigueur depuis longtemps. L'accès aux sauvegardes via Wikimedia Cloud Services restera inchangé. * La mise en place des [[mw:Wikimedia APIs/Rate limits|limites de débit des API]] à l'échelle mondiale est désormais achevée ; ces limites s'appliquent à toutes les API et sont fixées aux niveaux indiqués dans la documentation pour tous les groupes. Les bots fonctionnant sur Toolforge/WMCS ou disposant du droit d'utilisateur « bot » sur n'importe quel wiki restent exemptés. Tous les bots doivent continuer à respecter les bonnes pratiques décrites dans la documentation afin d'éviter d'être soumis à des limites de débit. * Le [https://api.wikimedia.org/wiki/Main_Page wiki du portail API] sera en lecture seule à partir de cette semaine (du 15 au 18 juin). La semaine suivante (du 22 au 25 juin), toutes les URL du wiki du portail API redirigeront vers [[mw:Wikimedia APIs|les API Wikimedia sur mediawiki.org]]. Pour en savoir plus, consultez la [[wikitech:API Portal/Deprecation|page du projet]]. * [[File:Reload icon with two arrows.svg|12px|link=|class=skin-invert|Sujet récurrent]] Détail des mises-à-jour à venir cette semaine : [[mw:MediaWiki 1.47/wmf.7|MediaWiki]] '''Rencontres et évènements''' * Le 17 juin à 18 h (UTC), la WMF organisera une réunion sur Discord consacrée à la revue de code. L'[[mw:Special:MyLanguage/Developer Satisfaction Survey/2026|enquête sur la satisfaction des développeurs]] nous a permis de constater que les bénévoles rencontrent des difficultés avec la revue de code, et nous souhaitons discuter de ces expériences afin de trouver des solutions concrètes. Vous pouvez rejoindre la réunion [https://discord.gg/wikipedia?event=1514727511102062664 via le serveur Discord de la communauté Wikimedia]. * La [[m:Special:MyLanguage/Conferencia Wikimedia de América Latina 2026|Conférence Wikimedia d'Amérique latine]] organisera un hackathon régional qui réunira la communauté technique du mouvement Wikimedia, notamment des développeurs, des administrateurs système, des data scientists et des utilisateurs disposant de droits étendus. Les contributeurs techniques intéressés peuvent [https://docs.google.com/forms/d/e/1FAIpQLSf4osJzTHBJjQbYJk7TMVEJjTEQv7IgtsUDfP-o-qTgeRQQxw/viewform postuler à une bourse] pour y participer jusqu'au 21 juin à minuit (heure de la Bolivie, UTC-4). * Inscrivez-vous aux Wikimania Team Challenges pour participer à cet événement exceptionnel. Les défis par équipe se dérouleront en ligne et en présentiel les 21 et 22 juillet, avant la conférence Wikimania. Tout le monde est le bienvenu, quelles que soient ses compétences ou son inscription à Wikimania. Les équipes travailleront sur 10 défis importants visant à soutenir la communauté Wikimedia. Pour plus de détails, rendez-vous sur [[wmania:Special:MyLanguage/2026:Team challenges|la page des défis par équipe]] et [https://wikimedia.eventyay.com/wm/teamchallenges/ inscrivez-vous ici]. Les inscriptions se terminent le 20 juin à 23 h UTC. '''''[[m:Special:MyLanguage/Tech/News|Actualités techniques]]''' préparées par les [[m:Special:MyLanguage/Tech/News/Writers|rédacteurs des actualités techniques]] et postées par [[m:Special:MyLanguage/User:MediaWiki message delivery|robot]]. [[m:Special:MyLanguage/Tech/News#contribute|Contribuer]]&nbsp;• [[m:Special:MyLanguage/Tech/News/2026/25|Traduire]]&nbsp;• [[m:Tech|Obtenir de l’aide]]&nbsp;• [[m:Talk:Tech/News|Donner son avis]]&nbsp;• [[m:Global message delivery/Targets/Tech ambassadors|S’abonner ou se désabonner]].'' </div><section end="technews-2026-W25"/> <bdi lang="en" dir="ltr">[[User:MediaWiki message delivery|MediaWiki message delivery]]</bdi> 15 juin 2026 à 18:48 (CEST) <!-- Message envoyé par User:UOzurumba (WMF)@metawiki en utilisant la liste sur https://meta.wikimedia.org/w/index.php?title=Global_message_delivery/Targets/Tech_ambassadors&oldid=30689604 --> == Actualités techniques n° 2026-26 == <section begin="technews-2026-W26"/><div class="plainlinks"> Dernières '''[[m:Special:MyLanguage/Tech/News|actualités techniques]]''' de la communauté technique de Wikimedia. N’hésitez pas à informer les autres utilisateurs de ces changements. Certains changements ne vous concernent pas. [[m:Special:MyLanguage/Tech/News/2026/26|D’autres traductions]] sont disponibles. '''En lumière cette semaine''' * Les [[mw:Special:MyLanguage/Growth/Feature summary|fonctionnalités de croissance]] sont [[phab:T418115|désormais disponibles sur Wikidata]]. Cette mise à jour permet d'accéder au mentorat ([[mw:Special:MyLanguage/Help:Growth/Mentorship|s'il est configuré]]), au module Impact, au panneau d'aide et à une page d'accueil simplifiée pour les nouveaux arrivants (sans les suggestions de modifications). Les administrateurs de Wikidata continuent de paramétrer ces fonctionnalités via la configuration communautaire. '''Actualités pour la contribution''' * La page spéciale [[{{#special:RangeCalculator}}]] a été créée. Elle permet aux utilisateurs de trouver une plage d'adresses IP sans avoir à recourir à des outils externes. Jusqu'à présent, cet outil n'était accessible qu'aux CheckUsers. [https://phabricator.wikimedia.org/T268429] * Les [[m:Special:MyLanguage/WMDE Technical Wishes/Sub-referencing|sous-références]] sont une nouvelle fonctionnalité de MediaWiki qui permet aux contributeurs de réutiliser des références en modifiant certains détails. Elle sera déployée le 23 juin, sur la plupart des versions de Wikipédia de petite et moyenne taille. La [[m:Special:MyLanguage/WMDE Technical Wishes/Sub-referencing#deployment|FAQ]] répertorie les mesures à prendre sur votre wiki pour faciliter ce déploiement. Consultez le [[:phab:T414094|plan de déploiement]] pour connaître les prochaines étapes. [https://phabricator.wikimedia.org/T428902] * À partir de la semaine prochaine, les utilisateurs recevront une notification lorsqu'ils seront bloqués ou débloqués pour l'édition, ou si ce blocage venait à changer. [https://phabricator.wikimedia.org/T100974] * [[File:Reload icon with two arrows.svg|12px|link=|class=skin-invert|Sujet récurrent]] Voir {{PLURAL:32|la tâche soumise|les {{formatnum:32}} tâches soumises}} par la communauté [[m:Special:MyLanguage/Tech/News/Recently resolved community tasks|résolue{{PLURAL:32||s}} la semaine dernière]]. '''Actualités pour la contribution technique''' * À partir de la semaine prochaine, les filtres anti-abus configurés pour « exiger une vérification par CAPTCHA » s'appliqueront également aux utilisateurs disposant du droit <code>skipcaptcha</code>, ce qui inclut la plupart des utilisateurs auto-confirmés. Les bots en sont exemptés. Ce changement ne concerne que les modifications qui déclenchent un filtre anti-abus. Le droit <code>skipcaptcha</code> continuera à exempter les utilisateurs de l'obligation de résoudre des CAPTCHA dans le cadre d'une utilisation normale des wikis. [https://phabricator.wikimedia.org/T402595] * La documentation de référence relative à l'[[wikitech:Machine_Learning/LiftWing/API|API Lift Wing]] a été déplacée du portail API vers le [https://wikitech.wikimedia.org/w/index.php?api=lift-wing&title=Special%3ARestSandbox bac à sable REST] interactif. * Le wiki du Portail API est désormais fermé. Pour consulter la documentation relative aux API, rendez-vous sur [[mw:Special:MyLanguage/Wikimedia_APIs|Wikimedia APIs sur mediawiki.org]]. À compter du 22 juin, toutes les URL du wiki du Portail API (https://api.wikimedia.org/wiki/) redirigeront vers la page de mediawiki.org. [https://phabricator.wikimedia.org/T427537] * [[File:Reload icon with two arrows.svg|12px|link=|class=skin-invert|Sujet récurrent]] Détail des mises-à-jour à venir cette semaine : [[mw:MediaWiki 1.47/wmf.8|MediaWiki]] '''Rencontres et évènements''' * Participez à une visioconférence le 25 juin à 14 h 30 UTC pour rencontrer les stagiaires actuels de Wikimédia participant au [[mw:Google_Summer_of_Code/2026|Google Summer of Code]] et à [[mw:Outreachy/Round_32|Outreachy]]. Les stagiaires présenteront leurs projets et feront une brève démonstration du travail qu'ils ont réalisé jusqu'à présent. Les participants sont invités à [[mw:event:Google_Summer_of_Code/Summer_2026_June_Internship_open_session|partager leurs idées et leurs contacts au sein de leur communauté]]. '''''[[m:Special:MyLanguage/Tech/News|Actualités techniques]]''' préparées par les [[m:Special:MyLanguage/Tech/News/Writers|rédacteurs des actualités techniques]] et postées par [[m:Special:MyLanguage/User:MediaWiki message delivery|robot]]. [[m:Special:MyLanguage/Tech/News#contribute|Contribuer]]&nbsp;• [[m:Special:MyLanguage/Tech/News/2026/26|Traduire]]&nbsp;• [[m:Tech|Obtenir de l’aide]]&nbsp;• [[m:Talk:Tech/News|Donner son avis]]&nbsp;• [[m:Global message delivery/Targets/Tech ambassadors|S’abonner ou se désabonner]].'' </div><section end="technews-2026-W26"/> <bdi lang="en" dir="ltr">[[User:MediaWiki message delivery|MediaWiki message delivery]]</bdi> 23 juin 2026 à 15:05 (CEST) <!-- Message envoyé par User:Trizek (WMF)@metawiki en utilisant la liste sur https://meta.wikimedia.org/w/index.php?title=Global_message_delivery/Targets/Tech_ambassadors&oldid=30722494 --> == RFC about AI-generated content in Wikimedia Commons == <bdi lang="en" dir="ltr">Apologies for writing in English, please help translate this message to your language. You are invited to participate in a [[c:Commons:Requests for comment/Policy update for AI content|request for comment on Wikimedia Commons about a policy update for AI content]]. This may affect files that are uploaded to Wikimedia Commons for use on this project. Thank you. [[m:User:Codename Noreste|Codename Noreste]] ([[m:User talk:Codename Noreste|discussion]])</bdi> 23 juin 2026 à 19:11 (CEST) <!-- Message envoyé par User:Codename Noreste@metawiki en utilisant la liste sur https://meta.wikimedia.org/w/index.php?title=Distribution_list/Global_message_delivery&oldid=30513860 --> == Intégration du lien vers les contacts juridiques et de sécurité dans le pied de page de votre wiki == <section begin="Message"/> '''Contacts juridiques et de sécurité''' Bonjour à toute la communauté, la Fondation Wikimedia a mis à disposition une [[wmf:Special:MyLanguage/Legal:Wikimedia Foundation Legal and Safety Contact Information|page unique dédiée aux mentions légales et à la sécurité]], à ajouter en pied de page de votre wiki, afin de garantir l'accès à des informations juridiques exactes. Il s'agit d'une exigence réglementaire. Nous avons déjà mis en place des liens vers les wikis en anglais, allemand, italien, espagnol et d'autres langues de Wikipedia, et nous les déploierons bientôt sur votre wiki. Pour en savoir plus, [[m:Special:MyLanguage/Wikimedia_Foundation_Legal_and_Safety_Contacts_FAQ|consultez la page du projet]] et n'hésitez pas à laisser vos commentaires dans ce fil de discussion ou sur la [[m:Special:MyLanguage/Talk:Wikimedia Foundation Legal and Safety Contacts FAQ|page de discussion]]. <section end="Message"/> -- [[User:Sannita (WMF)|User:Sannita (WMF)]] ([[User talk:Sannita (WMF)|talk]]) 25 juin 2026 à 15:30 (CEST) <!-- Message envoyé par User:Sannita (WMF)@metawiki en utilisant la liste sur https://meta.wikimedia.org/w/index.php?title=User:Sannita_(WMF)/Mass_sending_test&oldid=30731267 --> == Actualités techniques n° 2026-27 == <section begin="technews-2026-W27"/><div class="plainlinks"> Dernières '''[[m:Special:MyLanguage/Tech/News|actualités techniques]]''' de la communauté technique de Wikimedia. N’hésitez pas à informer les autres utilisateurs de ces changements. Certains changements ne vous concernent pas. [[m:Special:MyLanguage/Tech/News/2026/27|D’autres traductions]] sont disponibles. '''Actualités pour la contribution''' * Dans le cadre des [[mw:Special:MyLanguage/Contributors/Account Creation Experiments|expériences sur la création de comptes]], l'équipe Growth a testé l'ajout d'une icône de compte utilisateur dans l'en-tête du site mobile pour les utilisateurs non connectés, offrant ainsi un accès direct aux actions « Créer un compte » et « Se connecter ». Cette expérience a permis d'augmenter le nombre de créations de comptes d'environ 20 % sans nuire à la qualité des modifications ni au taux de modifications constructives. Cette fonctionnalité sera désormais déployée sur tous les wikis de la Fondation Wikimédia sur le site mobile au cours de la première semaine de juillet. [https://phabricator.wikimedia.org/T428220] * À la suite d'une [[phab:T426248|expérience concluante]], les utilisateurs connectés qui n'ont pas [[mw:Special:MyLanguage/Help:Email_confirmation|confirmé leur adresse e-mail]] lors de la création de leur compte voient s'afficher une nouvelle bannière leur demandant de finaliser cette procédure. Cela permet de réduire le risque que les utilisateurs se retrouvent bloqués hors de leur compte et rend les adresses e-mail associées aux comptes globalement plus fiables. Cette mesure s'inscrit dans le cadre du projet [[mw:Special:MyLanguage/Product Safety and Integrity/Account Security|Sécurité des comptes]]. [https://phabricator.wikimedia.org/T428292] * Une mise à jour de [[Special:Search|Recherche]] affine le comportement de <bdi lang="zxx" dir="ltr"><code><nowiki>-prefix:</nowiki></code></bdi> lorsqu'il est utilisé pour exclure des résultats. Auparavant, l'utilisation de <bdi lang="zxx" dir="ltr"><code><nowiki>-prefix:</nowiki></code></bdi> avec la négation pouvait élargir involontairement les résultats de recherche en ajoutant les espaces de noms inclus dans le champ de recherche, ce qui entraînait un comportement déroutant pour les utilisateurs s'attendant à un filtre d'exclusion simple. Avec cette mise à jour, <bdi lang="zxx" dir="ltr"><code><nowiki>-prefix:</nowiki></code></bdi> exclura désormais strictement les titres de pages correspondants comme prévu et pourra afficher un avertissement si l'espace de noms concerné n'a pas été explicitement sélectionné. Le comportement de <bdi lang="zxx" dir="ltr"><code><nowiki>prefix:</nowiki></code></bdi> sans négation reste toutefois inchangé. [https://phabricator.wikimedia.org/T427443] * [[File:Reload icon with two arrows.svg|12px|link=|class=skin-invert|Sujet récurrent]] Voir {{PLURAL:33|la tâche soumise|les {{formatnum:33}} tâches soumises}} par la communauté [[m:Special:MyLanguage/Tech/News/Recently resolved community tasks|résolue{{PLURAL:33||s}} la semaine dernière]]. Par exemple, le problème qui empêchait les réviseurs utilisant la barre d'outils « Page Curation » d'être automatiquement abonnés aux discussions qu'ils avaient lancées sur les pages de discussion a désormais été résolu. Les réviseurs recevront désormais des notifications lorsqu'une personne répondra à ces discussions. [https://phabricator.wikimedia.org/T329346] '''Actualités pour la contribution technique''' * À compter du 29 juin, les téléchargements automatisés depuis le site web « dumps.wikimedia.org » seront soumis à la [[Foundation:Special:MyLanguage/Policy:Wikimedia Foundation User-Agent Policy|politique relative aux user-agents]]. Les requêtes automatisées utilisant un user-agent générique ou vide seront bloquées. L'accès aux sauvegardes via Wikimedia Cloud Services n'est pas affecté. Cette mesure fait suite à l'annonce publiée dans le [[m:Special:MyLanguage/Tech/News/2026/25|numéro 2026/25 de Tech News]]. * [[File:Reload icon with two arrows.svg|12px|link=|class=skin-invert|Sujet récurrent]] Détail des mises-à-jour à venir cette semaine : [[mw:MediaWiki 1.47/wmf.9|MediaWiki]] '''''[[m:Special:MyLanguage/Tech/News|Actualités techniques]]''' préparées par les [[m:Special:MyLanguage/Tech/News/Writers|rédacteurs des actualités techniques]] et postées par [[m:Special:MyLanguage/User:MediaWiki message delivery|robot]]. [[m:Special:MyLanguage/Tech/News#contribute|Contribuer]]&nbsp;• [[m:Special:MyLanguage/Tech/News/2026/27|Traduire]]&nbsp;• [[m:Tech|Obtenir de l’aide]]&nbsp;• [[m:Talk:Tech/News|Donner son avis]]&nbsp;• [[m:Global message delivery/Targets/Tech ambassadors|S’abonner ou se désabonner]].'' </div><section end="technews-2026-W27"/> <bdi lang="en" dir="ltr">[[User:MediaWiki message delivery|MediaWiki message delivery]]</bdi> 29 juin 2026 à 13:48 (CEST) <!-- Message envoyé par User:STei (WMF)@metawiki en utilisant la liste sur https://meta.wikimedia.org/w/index.php?title=Global_message_delivery/Targets/Tech_ambassadors&oldid=30744833 --> == Actualités techniques n° 2026-28 == <section begin="technews-2026-W28"/><div class="plainlinks"> Dernières '''[[m:Special:MyLanguage/Tech/News|actualités techniques]]''' de la communauté technique de Wikimedia. N’hésitez pas à informer les autres utilisateurs de ces changements. Certains changements ne vous concernent pas. [[m:Special:MyLanguage/Tech/News/2026/28|D’autres traductions]] sont disponibles. '''Actualités pour la contribution''' * [[File:Reload icon with two arrows.svg|12px|link=|class=skin-invert|Sujet récurrent]] Voir {{PLURAL:34|la tâche soumise|les {{formatnum:34}} tâches soumises}} par la communauté [[m:Special:MyLanguage/Tech/News/Recently resolved community tasks|résolue{{PLURAL:34||s}} la semaine dernière]]. Par exemple, les résultats de la barre de recherche sur Wikidata étaient en anglais au lieu d’utiliser la bonne langue de repli pour les utilisateurs de variantes de langues. Ce problème a maintenant été corrigé : les suggestions de recherche suivront désormais correctement la chaîne des langues de repli. [https://phabricator.wikimedia.org/T429769] '''Actualités pour la contribution technique''' * En préparation de la [[m:Special:MyLanguage/Event:Celebrate Women|campagne Célébrons les femmes]] prévues pour mars 2027, l’[[m:Special:MyLanguage/Wikimedia Foundation/Advancement/Community Growth/Content Enablement|équipe Activation du contenu]] de Wikimedia Foundation a lancé un sondage composé de 22 questions pour mieux comprendre les contributions techniques des personnes s’identifiant comme femmes sur les projets Wikimedia. Répondre au sondage prend environ 15 à 20 minutes ; il restera ouvert jusqu’au 20 juillet 2026. Les [[m:Special:MyLanguage/Celebrate Women/Technical contributions survey|questions]] sont aussi sur wiki pour les regarder auparavant. * L’[[mw:Special:MyLanguage/Extension:Score|extension Partitions]] prend désormais en charge le rendu de partition musicales comme images SVG, en plus du format PNG. Cela répond à [[:phab:T49578|une vieille demande]] et résout des problèmes anciens de qualité de l’image. Les deux formats sont désormais fournis aux clients web : PNG dans l’attribut <bdi lang="zxx" dir="ltr"><code><nowiki>src</nowiki></code></bdi> et SVG dans l’attribut <bdi lang="zxx" dir="ltr"><code><nowiki>srcset</nowiki></code></bdi>. * Le nouvel analyseur syntaxique [[wikitech:Parsoid|Parsoid]] continue [[mw:Special:MyLanguage/Parsoid/Parser_Unification/Updates|d’être déployé sur d’autres wikis]], ce qui rend plus facile l’introduction de nouvelles fonctionnalités de lecture et de modification. Il a été activé sur Wikipédia en français, amenant la couverture totale à 78,9 % des pages vues de Wikipédia. Le déploiement sur Wikipédia en anglais sur ordinateur va progresser cette semaine. * [[File:Reload icon with two arrows.svg|12px|link=|class=skin-invert|Sujet récurrent]] Détail des mises-à-jour à venir cette semaine : [[mw:MediaWiki 1.47/wmf.10|MediaWiki]] '''En détails''' * La [[diffblog:2026/06/29/wikimedia-hackathon-2026-building-collaborating-and-shaping-the-future-together/|publication sur le blog récapitulant]] le Hackathon Wikimedia 2026 est désormais en ligne. Il met en avant les projets, séances et activités sociales de l’évènement de cette année et présente les premières grandes lignes du Hackathon Wikimedia 2027. '''''[[m:Special:MyLanguage/Tech/News|Actualités techniques]]''' préparées par les [[m:Special:MyLanguage/Tech/News/Writers|rédacteurs des actualités techniques]] et postées par [[m:Special:MyLanguage/User:MediaWiki message delivery|robot]]. [[m:Special:MyLanguage/Tech/News#contribute|Contribuer]]&nbsp;• [[m:Special:MyLanguage/Tech/News/2026/28|Traduire]]&nbsp;• [[m:Tech|Obtenir de l’aide]]&nbsp;• [[m:Talk:Tech/News|Donner son avis]]&nbsp;• [[m:Global message delivery/Targets/Tech ambassadors|S’abonner ou se désabonner]].'' </div><section end="technews-2026-W28"/> <bdi lang="en" dir="ltr">[[User:MediaWiki message delivery|MediaWiki message delivery]]</bdi> 6 juillet 2026 à 15:56 (CEST) <!-- Message envoyé par User:STei (WMF)@metawiki en utilisant la liste sur https://meta.wikimedia.org/w/index.php?title=Global_message_delivery/Targets/Tech_ambassadors&oldid=30773578 --> == Actualités techniques n° 2026-29 == <section begin="technews-2026-W29"/><div class="plainlinks"> Dernières '''[[m:Special:MyLanguage/Tech/News|actualités techniques]]''' de la communauté technique de Wikimedia. N’hésitez pas à informer les autres utilisateurs de ces changements. Certains changements ne vous concernent pas. [[m:Special:MyLanguage/Tech/News/2026/29|D’autres traductions]] sont disponibles. '''Actualités pour la contribution''' * [[mw:Special:MyLanguage/Growth/Revise_Tone|Revise Tone]] aide les nouveaux contributeurs à identifier les passages des articles de Wikipédia susceptibles de contenir un langage non encyclopédique et les encourage à envisager d'en réviser le ton. Cette fonctionnalité a fait l'objet d'un [[w:en:A/B_testing|test A/B]] sur les Wikipédias en arabe, en anglais, en français et en portugais, où le taux d'achèvement des tâches par les nouveaux contributeurs [[mw:Special:MyLanguage/Growth/Revise_Tone#Experiment_Results|a augmenté de 38,7 %]] par rapport à la tâche de relecture par défaut, sans que la qualité des modifications n'en soit affectée. Le test s'est achevé le 9 juillet et la fonctionnalité est désormais accessible à tous sur ces wikis ; elle est configurable via la configuration communautaire. [[phab:T426364|Il est prévu]] de déployer « Revise Tone » sur d’autres wikis. * La configuration communautaire permettant la [[mw:Special:MyLanguage/Help:Growth/Mentorship#Automated mentor list cleanup|suppression automatique des mentors inactifs]] selon des critères configurables sera activée le jeudi 16, [[mw:Special:MyLanguage/Growth/Deployment|sur certains wikis]], afin de maintenir à jour les listes de mentors. Les mentors sont des contributeurs expérimentés qui choisissent d'aider les nouveaux utilisateurs sur le wiki grâce aux [[mw:Special:MyLanguage/Growth/Feature summary|fonctionnalités de croissance]]. Les administrateurs peuvent désormais préparer les paramètres via [[w:Special:CommunityConfiguration/Mentorship|Special:CommunityConfiguration/Mentorship]] ; ces fonctionnalités prendront effet à partir de jeudi. * [[File:Reload icon with two arrows.svg|12px|link=|class=skin-invert|Sujet récurrent]] Voir {{PLURAL:38|la tâche soumise|les {{formatnum:38}} tâches soumises}} par la communauté [[m:Special:MyLanguage/Tech/News/Recently resolved community tasks|résolue{{PLURAL:38||s}} la semaine dernière]]. Par exemple, un problème où certains utilisateurs de l'application Android Wikipedia ont été déconnectés immédiatement après leur connexion, les empêchant de rester connectés et de modifier les pages, a maintenant été résolu. [https://phabricator.wikimedia.org/T316916] '''Actualités pour la contribution technique''' * La modification d'une page à l'aide de scripts utilisateur ou de gadgets entraînait la réinitialisation des étiquettes de liste de suivi que l'utilisateur avait attribuées à cette page. Ce problème a désormais été résolu. [https://phabricator.wikimedia.org/T423778] * Pour contourner un bug de Safari (voir [[phab:T425211]]), sur les wikis utilisant Parsoid, wikilink hrefs utilise désormais des URL absolues au lieu d'URL relatives au protocole. La sortie de l'API REST reste inchangée et continue d'utiliser des URL relatives au protocole. Les gadgets, les scripts utilisateur, les bots et les feuilles de style CSS devront probablement être adaptés s'ils s'appuyaient sur la présence d'URL relatives au protocole dans wikilink hrefs. [https://phabricator.wikimedia.org/T431358] * [[File:Reload icon with two arrows.svg|12px|link=|class=skin-invert|Sujet récurrent]] Détail des mises-à-jour à venir cette semaine : [[mw:MediaWiki 1.47/wmf.11|MediaWiki]] '''En détails''' * L'équipe chargée de la plateforme d'expérimentation de la Fondation Wikimedia a publié un article de blog faisant le bilan de sa première année d'expérimentation structurée. Cet article met en avant des expériences couronnées de succès, telles que « Paste Check », « Reference Check » et « Tone Check », qui ont permis d'améliorer les résultats des modifications et ont été étendues à un plus grand nombre d'utilisateurs, ainsi que des expériences qui n'ont pas abouti à des changements au niveau du produit. [[diffblog:2026/07/07/moving-the-needle-how-we-test-new-ideas-across-wikimedia-projects|En savoir plus]]. '''''[[m:Special:MyLanguage/Tech/News|Actualités techniques]]''' préparées par les [[m:Special:MyLanguage/Tech/News/Writers|rédacteurs des actualités techniques]] et postées par [[m:Special:MyLanguage/User:MediaWiki message delivery|robot]]. [[m:Special:MyLanguage/Tech/News#contribute|Contribuer]]&nbsp;• [[m:Special:MyLanguage/Tech/News/2026/29|Traduire]]&nbsp;• [[m:Tech|Obtenir de l’aide]]&nbsp;• [[m:Talk:Tech/News|Donner son avis]]&nbsp;• [[m:Global message delivery/Targets/Tech ambassadors|S’abonner ou se désabonner]].'' </div><section end="technews-2026-W29"/> <bdi lang="en" dir="ltr">[[User:MediaWiki message delivery|MediaWiki message delivery]]</bdi> 13 juillet 2026 à 18:11 (CEST) <!-- Message envoyé par User:STei (WMF)@metawiki en utilisant la liste sur https://meta.wikimedia.org/w/index.php?title=Global_message_delivery/Targets/Tech_ambassadors&oldid=30804401 --> == Actualités techniques n° 2026-30 == <section begin="technews-2026-W30"/><div class="plainlinks"> Dernières '''[[m:Special:MyLanguage/Tech/News|actualités techniques]]''' de la communauté technique de Wikimedia. N’hésitez pas à informer les autres utilisateurs de ces changements. Certains changements ne vous concernent pas. [[m:Special:MyLanguage/Tech/News/2026/30|D’autres traductions]] sont disponibles. '''Actualités pour la contribution''' * L'[[mw:Special:MyLanguage/Reader/Reader Experience|équipe chargée de l'expérience utilisateur]] a pris en compte les commentaires de la communauté concernant l'emplacement des boutons « Watchstar » et « Watchlist » pour la [[mw:Special:MyLanguage/Readers/Reader Experience/WE3.3.4 Reading lists|fonctionnalité bêta « Listes de lecture »]], qui permettrait d'enregistrer des articles pour les lire plus tard – un [[m:Special:MyLanguage/Community Wishlist/W102|élément de la liste de souhaits]] visant à intégrer cette fonctionnalité sur le site web. Les contributeurs sont invités à activer cette fonctionnalité bêta pour la tester et [[phab:T426453|partager leurs impressions]]. * Le [[mw:Special:MyLanguage/VisualEditor/Suggestion Mode|mode Suggestions]] propose des suggestions d'édition au sein de l'éditeur visuel afin d'améliorer les articles de Wikipédia. [[Special:EditChecks|Toutes les suggestions]] sont [[mw:Special:MyLanguage/Help:Suggestion mode#For administrators – local customization|configurables par la communauté]]. La fonctionnalité [[mw:Special:MyLanguage/Edit check/TextMatch|TextMatch]] permet aux bénévoles de créer des suggestions locales personnalisées. Cette fonctionnalité recherche des chaînes de texte dans les articles et prend désormais en charge les expressions régulières. Cela offre aux bénévoles davantage de précision et de flexibilité quant aux types de suggestions locales qu’ils peuvent créer. Remarque : les suggestions [[mw:Special:MyLanguage/Edit check/Configuration#:~:text=maximumEditcount|peuvent être ciblées]] en fonction du nombre de modifications effectuées par la personne qui édite, ainsi que d’autres aspects de la page. Vous pouvez [[mw:Special:MyLanguage/Help:Suggestion mode#Create custom local types of Suggestions|trouver des exemples provenant d’autres communautés]] pour vous inspirer, notamment des TextMatches qui détectent : les fautes de frappe, les erreurs grammaticales, les publicités potentielles, les clichés, l’utilisation incorrecte des tirets ou des traits d’union, les mots-clés temporels non spécifiques, les noms obsolètes, et bien plus encore. Tout [[mw:Special:MyLanguage/Talk:VisualEditor/Suggestion Mode|retour]] est le bienvenu. * [[File:Reload icon with two arrows.svg|12px|link=|class=skin-invert|Sujet récurrent]] Voir {{PLURAL:27|la tâche soumise|les {{formatnum:27}} tâches soumises}} par la communauté [[m:Special:MyLanguage/Tech/News/Recently resolved community tasks|résolue{{PLURAL:27||s}} la semaine dernière]]. Par exemple, un problème lié à l'outil SVG Translate, qui pouvait utiliser une version obsolète d'un fichier, entraînant ainsi le remplacement des traductions existantes lors du téléchargement de nouvelles traductions, a désormais [[phab:T430577|été résolu]]. Au total, au cours du dernier trimestre, d'avril à juin 2026, environ 337 tâches communautaires ont été résolues par la Fondation Wikimedia. '''Actualités pour la contribution technique''' * Sur les wikis utilisant Parsoid, ce dernier affiche désormais un maximum de 1 250 images par page. Une nouvelle catégorie de suivi, « media-limit-reached », sera bientôt disponible pour identifier les pages où cette limite est atteinte, ce qui facilitera la recherche des contenus dont l'affichage des médias a pu être restreint lors du rendu. Consultez [[phab:T430854]] pour plus d'informations et pour nous faire part de vos commentaires. * [[File:Reload icon with two arrows.svg|12px|link=|class=skin-invert|Sujet récurrent]] Détail des mises-à-jour à venir cette semaine : [[mw:MediaWiki 1.47/wmf.12|MediaWiki]] '''''[[m:Special:MyLanguage/Tech/News|Actualités techniques]]''' préparées par les [[m:Special:MyLanguage/Tech/News/Writers|rédacteurs des actualités techniques]] et postées par [[m:Special:MyLanguage/User:MediaWiki message delivery|robot]]. [[m:Special:MyLanguage/Tech/News#contribute|Contribuer]]&nbsp;• [[m:Special:MyLanguage/Tech/News/2026/30|Traduire]]&nbsp;• [[m:Tech|Obtenir de l’aide]]&nbsp;• [[m:Talk:Tech/News|Donner son avis]]&nbsp;• [[m:Global message delivery/Targets/Tech ambassadors|S’abonner ou se désabonner]].'' </div><section end="technews-2026-W30"/> <bdi lang="en" dir="ltr">[[User:MediaWiki message delivery|MediaWiki message delivery]]</bdi> 21 juillet 2026 à 07:46 (CEST) <!-- Message envoyé par User:STei (WMF)@metawiki en utilisant la liste sur https://meta.wikimedia.org/w/index.php?title=Global_message_delivery/Targets/Tech_ambassadors&oldid=30836091 --> == Request for comment (the future of Abstract Wikipedia) == <bdi lang="en" dir="ltr" class="mw-content-ltr"> Apologies if this has not yet been translated into your wiki's language. {{int:Please-translate}} You are invited to voice your opinions in a [[:m:Requests for comment/The future of Abstract Wikipedia|request for comment about the future of Abstract Wikipedia]]. {{Int:Feedback-thanks-title}} [[:m:User:Kowal2701|Kowal2701]] ([[:m:User talk:Kowal2701|talk]]) 24 juillet 2026 à 14:24 (CEST) </bdi> <!-- Message envoyé par User:DreamRimmer@metawiki en utilisant la liste sur https://meta.wikimedia.org/w/index.php?title=Distribution_list/Global_message_delivery&oldid=30513860 --> == <span lang="en" dir="ltr">Tech News: 2026-31</span> == <div lang="en" dir="ltr"> <section begin="technews-2026-W31"/><div class="plainlinks"> Latest '''[[m:Special:MyLanguage/Tech/News|tech news]]''' from the Wikimedia technical community. Please tell other users about these changes. Not all changes will affect you. [[m:Special:MyLanguage/Tech/News/2026/31|Translations]] are available. '''Updates for editors''' * [[File:Maki-gift-15.svg|12px|link=|class=skin-invert|Wishlist item]] [[mw:Special:MyLanguage/ContentTranslation|Content Translation]] now supports dark mode, fulfilling a [[m:Community Wishlist/W544|Community Wishlist request]]. This brings the tool in line with the accessibility features available in the Vector 2022 and Minerva skins, helping reduce visual fatigue for users translating content. [https://phabricator.wikimedia.org/T367077] * DiscussionTools' source mode and the 2017 wikitext editor will now offer autocomplete for links (<bdi lang="zxx" dir="ltr"><code><nowiki>[[</nowiki></code></bdi>), templates (<bdi lang="zxx" dir="ltr"><code><nowiki>{{</nowiki></code></bdi>), HTML and parser tags (<bdi lang="zxx" dir="ltr"><code><nowiki><</nowiki></code></bdi>), and magic words (<bdi lang="zxx" dir="ltr"><code><nowiki>__</nowiki></code></bdi>), making it quicker and easier to insert links, templates, and other wiki markup while editing. [https://phabricator.wikimedia.org/T432400] * The [[mw:Special:MyLanguage/Readers/Reader Growth/Mobile page previews|Readers Growth team]] has concluded its experiment with mobile page previews and will not roll out the feature. Page Previews are a pop-up bottom sheet that appears when readers tap a blue link, showing a thumbnail, lead paragraph, and an option to open the article. The experiment showed flat retention and negative indicator metrics, suggesting that mobile web readers preferred navigating directly to linked articles rather than using page previews. * The Reader Experience team has seen encouraging early results from the [[mw:Special:MyLanguage/Readers/Reader Experience/Reading lists|Reading Lists feature]], with 93% of participating users reporting that it was useful. Reading Lists help active readers save articles for future reading and support their learning goals on Wikimedia projects. The team plans further improvements before expanding the feature to more users. * The [[mw:Special:MyLanguage/Wikimedia Apps/Team/Explore Feed Refresh|Explore Feed Refresh]] initiative was tested with new and casual Wikipedia app readers. The refreshed feed helps readers discover new and relevant content. After a 10.5% increase in engagement with the feed, Wikimedia Apps team has decided to scale the Home Feed redesign to iOS with the learnings from the Android release applied. * [[File:Reload icon with two arrows.svg|12px|link=|class=skin-invert|Recurrent item]] View all {{formatnum:23}} community-submitted {{PLURAL:23|task|tasks}} that were [[m:Special:MyLanguage/Tech/News/Recently resolved community tasks|resolved last week]]. For example, an issue where subject names in the Article Guidance feature were displayed with incorrect capitalization on French Wikipedia, has now been fixed. Subject names will now follow the correct capitalization rules for the language. [https://phabricator.wikimedia.org/T427201] '''Updates for technical contributors''' * After running several [[mw:Special:MyLanguage/Contributors/Account Creation Experiments|Account Creation Experiments]] to improve registration completion rates, a new version of the username field on [[Special:CreateAccount|Create Account]] has been rolled out. It includes [[:c:File:Create account - July 2026 updates.png|a popover summarizing the username policy]] to provide clearer guidance during account creation. As part of this change, the messages <bdi lang="zxx" dir="ltr"><code><nowiki>createacct-helpusername</nowiki></code></bdi> and <bdi lang="zxx" dir="ltr"><code><nowiki>createacct-username-help</nowiki></code></bdi> that several communities have configured will no longer be used. If communities want to customize the guidance shown in the new popover, they can instead edit the following messages: <bdi lang="zxx" dir="ltr"><code><nowiki>createacct-username-policy-popover-bullet1</nowiki></code></bdi>, <bdi lang="zxx" dir="ltr"><code><nowiki>createacct-username-policy-popover-bullet2</nowiki></code></bdi>, and <bdi lang="zxx" dir="ltr"><code><nowiki>createacct-username-policy-popover-bullet3</nowiki></code></bdi>. [https://phabricator.wikimedia.org/T430604] * Later this week, the [[mw:Special:MyLanguage/Help:Extension:CodeMirror|CodeMirror syntax highlighter]] will offer [[w:en:Theme (computing)|themes]]. The themes can be picked from a dropdown menu in the full [[mw:Special:MyLanguage/Help:Extension:CodeMirror#CodeMirror preferences|CodeMirror preferences]] dialog. For wikitext, available themes are default, colorblind-friendly (previously the colorblind preference option on [[Special:Preferences#mw-prefsection-editing]]) and no-highlighting. For code languages (i.e., CSS/JavaScript/JSON/Vue/Lua), there are several themes available. These same themes will eventually be available for wikitext, too. [https://phabricator.wikimedia.org/T163533] * From now on, wikis can restrict editing in the "User" namespace to only the page owner and certain user groups. [[mw:Special:MyLanguage/Manual:$wgRestrictUserPageEditing|Read the configuration documentation]] to learn more. * [[File:Reload icon with two arrows.svg|12px|link=|class=skin-invert|Recurrent item]] Detailed code updates later this week: [[mw:MediaWiki 1.47/wmf.14|MediaWiki]] '''''[[m:Special:MyLanguage/Tech/News|Tech news]]''' prepared by [[m:Special:MyLanguage/Tech/News/Writers|Tech News writers]] and posted by [[m:Special:MyLanguage/User:MediaWiki message delivery|bot]]&nbsp;• [[m:Special:MyLanguage/Tech/News#contribute|Contribute]]&nbsp;• [[m:Special:MyLanguage/Tech/News/2026/31|Translate]]&nbsp;• [[m:Tech|Get help]]&nbsp;• [[m:Talk:Tech/News|Give feedback]]&nbsp;• [[m:Global message delivery/Targets/Tech ambassadors|Subscribe or unsubscribe]].'' </div><section end="technews-2026-W31"/> </div> <bdi lang="en" dir="ltr">[[User:MediaWiki message delivery|MediaWiki message delivery]]</bdi> 27 juillet 2026 à 20:49 (CEST) <!-- Message envoyé par User:STei (WMF)@metawiki en utilisant la liste sur https://meta.wikimedia.org/w/index.php?title=Global_message_delivery/Targets/Tech_ambassadors&oldid=30856308 --> == Actualités techniques n° 2026-32 == <section begin="technews-2026-W32"/><div class="plainlinks"> Dernières '''[[m:Special:MyLanguage/Tech/News|actualités techniques]]''' de la communauté technique de Wikimedia. N’hésitez pas à informer les autres utilisateurs de ces changements. Certains changements ne vous concernent pas. [[m:Special:MyLanguage/Tech/News/2026/32|D’autres traductions]] sont disponibles. '''Actualités pour la contribution''' * L’[[mw:Special:MyLanguage/Readers/Reader Experience|équipe Expérience de lecture]] a développé une [https://82db7c8d4b.catalyst.wmcloud.org/w/index.php?title=Regent%27s_Park&uselang=de démonstration d’un correctif] qui entoure la barre d’outils sur deux lignes quand il n’y a pas assez d’espace horizontalement pour tous les boutons. Le but est de réduire l’encombrement de la barre d’outil de Vector 2022 qui se constate parfois sur Wikipédia dans certaines langues avec certaines tailles d’écran. [https://phabricator.wikimedia.org/T429518] * L’équipe Expérience de lecture prévoit de lancer les [[mw:Special:MyLanguage/Readers/Reader Experience/Reading lists|Listes de lecture]], un [[m:Special:MyLanguage/Community Wishlist Survey 2021/Mobile and apps/Have Apps reading lists available on Destop/Mobile|souhait de la communauté]] que vous pouvez déjà essayer en bêta, à partir de septembre. Mais avant cela, l’aide des traducteurs et traductrices est nécessaire pour [https://translatewiki.net/w/i.php?title=Special%3ATranslate&group=ext-readinglists&filter=&action=translate plusieurs messages] à traduire dans un maximum de langues. Cette fonctionnalité appuie les objectifs de lecture et d’apprentissage sur Wikipédia. * La semaine prochaine, le sommaire sur les pages des fichiers de Wikimedia Commons sera amélioré en fusionnant le sommaire spécifique aux pages de fichier avec le sommaire classique des pages. Cela rendra plus facile la compréhension de la structure des pages de fichier, la navigation vers une section spécifique et le partage de liens vers une section en particulier. [https://phabricator.wikimedia.org/T332644] * [[File:Reload icon with two arrows.svg|12px|link=|class=skin-invert|Sujet récurrent]] Voir {{PLURAL:24|la tâche soumise|les {{formatnum:24}} tâches soumises}} par la communauté [[m:Special:MyLanguage/Tech/News/Recently resolved community tasks|résolue{{PLURAL:24||s}} la semaine dernière]]. Par exemple, certaines images [[w:fr:Tagged Image File Format|TIFF]] ne s’affichaient pas après avoir cliqué sur leur vignette : une image cassé s’affichait au lieu de l’image en taille réelle. Ce problème est désormais corrigé. [https://phabricator.wikimedia.org/T429326] '''Actualités pour la contribution technique''' * Le sélecteur de variables et de fonctions dans les Filtres anti-abus a été mis à jour pour prendre en charge la recherche et l’autocomplétion. Il permettra désormais aux mainteneurs de filtres de retrouver la variable ou fonction désirée plus rapidement. [https://phabricator.wikimedia.org/T323698] * Les formats MJPEG et VP8 sont retirés du lecteur vidéo. Le format MP4 (MPEG-4 Partie 2) est ajouté à la place : il fournit une meilleure qualité pour les vieux modèles d’iPhone. Plusieurs semaines seront peut-être nécessaires pour mettre à jour toutes les vidéos déjà en ligne. Le format par défaut pour les appareils modernes ne change pas (VP9/WebM). [https://phabricator.wikimedia.org/T358266] * [[File:Reload icon with two arrows.svg|12px|link=|class=skin-invert|Sujet récurrent]] Détail des mises-à-jour à venir cette semaine : [[mw:MediaWiki 1.47/wmf.15|MediaWiki]] '''''[[m:Special:MyLanguage/Tech/News|Actualités techniques]]''' préparées par les [[m:Special:MyLanguage/Tech/News/Writers|rédacteurs des actualités techniques]] et postées par [[m:Special:MyLanguage/User:MediaWiki message delivery|robot]]. [[m:Special:MyLanguage/Tech/News#contribute|Contribuer]]&nbsp;• [[m:Special:MyLanguage/Tech/News/2026/32|Traduire]]&nbsp;• [[m:Tech|Obtenir de l’aide]]&nbsp;• [[m:Talk:Tech/News|Donner son avis]]&nbsp;• [[m:Global message delivery/Targets/Tech ambassadors|S’abonner ou se désabonner]].'' </div><section end="technews-2026-W32"/> <bdi lang="en" dir="ltr">[[User:MediaWiki message delivery|MediaWiki message delivery]]</bdi> 3 août 2026 à 21:46 (CEST) <!-- Message envoyé par User:STei (WMF)@metawiki en utilisant la liste sur https://meta.wikimedia.org/w/index.php?title=Global_message_delivery/Targets/Tech_ambassadors&oldid=30872536 --> == Actualités techniques n° 2026-33 == <section begin="technews-2026-W33"/><div class="plainlinks"> Dernières '''[[m:Special:MyLanguage/Tech/News|actualités techniques]]''' de la communauté technique de Wikimedia. N’hésitez pas à informer les autres utilisateurs de ces changements. Certains changements ne vous concernent pas. [[m:Special:MyLanguage/Tech/News/2026/33|D’autres traductions]] sont disponibles. '''Actualités pour la contribution''' * [[File:Maki-gift-15.svg|12px|link=|class=skin-invert|Concerne un souhait]] Un nouvel assistant à diagrammes (''ChartWizard'') est [[c:Special:ChartWizard/Data:Example.Pie.chart|désormais disponible sur Wikimedia Commons]] pour les utilisateurs et utilisatrices qui souhaitent créer des graphiques à partir de leurs propres données. Cet assistant rend l’[[mw:Special:MyLanguage/Extension:Chart|extension Chart]] plus accessible aux débutants en permettant la création de diagrammes (par exemple des histogrammes ou des graphiques en secteurs) sans devoir utiliser du JSON. Les utilisateurs peuvent encore repasser à l’éditeur JSON s’ils le préfèrent. Vos retours sur ce nouvel outil sont les bienvenus sur la [[m:Talk:Community Wishlist/W414|page de discussion du souhait]]. * [[File:Reload icon with two arrows.svg|12px|link=|class=skin-invert|Sujet récurrent]] Voir {{PLURAL:19|la tâche soumise|les {{formatnum:19}} tâches soumises}} par la communauté [[m:Special:MyLanguage/Tech/News/Recently resolved community tasks|résolue{{PLURAL:19||s}} la semaine dernière]]. Par exemple, le gadget Photo du jour de l’appli Wikipédia pour iOS affichait par erreur la même image chaque jour au lieu d’en afficher une nouvelle quotidiennement. Cela a désormais été corrigé. [https://phabricator.wikimedia.org/T430692] '''Actualités pour la contribution technique''' * Les images SVG de [[mw:Special:MyLanguage/Extension:Math|formules mathématiques]] seront bientôt générées par le navigateur plutôt que côté serveur. MathML continue d’être généré côté serveur et est rendu dans le navigateur sans JavaScript. Ce changement aura lieu sur Wikibooks le 12 aout, sur Wikisource le 19 aout et sur Wikipédia du 20 au 27 aout. Vous pouvez l’essayer en choisissant « {{int:Mw-math-mathjax}} » dans vos préférences. Cela fait partie de la [[mw:Special:MyLanguage/RESTBase/deprecation|dépréciation de RESTBase]] et [[phab:T431372|de celle de Mathoid]]. [https://phabricator.wikimedia.org/T271001] * <span class="mw-translate-fuzzy">Les pages de catégorie permettront bientôt de trier les pages de la catégorie par ordre chronologique d’ajout dans la catégorie. Cela permettra de retrouver facilement les pages récemment ajoutées ou celles depuis longtemps dans la catégorie. Cela améliorera aussi le traitement des catégories de maintenance, par exemple les demandes de suppression ou d’autres tâches nécessitant un traitement chronologique. Utilisez l’argument <bdi lang="zxx" dir="ltr"><code><nowiki>cldsort=timestamp</nowiki></code></bdi> dans l’URL de la vue d’une catégorie pour trier la liste de pages.</span> [https://phabricator.wikimedia.org/T433768] * Les [[mw:Special:MyLanguage/Extension:Gadgets|gadgets]] et scripts utilisateur sur les wikis Wikimedia peuvent désormais utiliser les [[phab:T395347|fonctionnalités de ES 2018]] et [[phab:T419142|celles de ES 2019]] dans le code JavaScript. Auparavant, la plateforme n’acceptait qu’ES 2017. MediaWiki valide le code source pour protéger les fonctionnalités d’erreurs de syntaxes et pour vérifier que les scripts sont fonctionnels dans tous [[mw:Special:MyLanguage/Compatibility#Browser_support_matrix|les navigateurs pris en charges]]. [https://phabricator.wikimedia.org/T419142] * [[File:Reload icon with two arrows.svg|12px|link=|class=skin-invert|Sujet récurrent]] Détail des mises-à-jour à venir cette semaine : [[mw:MediaWiki 1.47/wmf.16|MediaWiki]] '''''[[m:Special:MyLanguage/Tech/News|Actualités techniques]]''' préparées par les [[m:Special:MyLanguage/Tech/News/Writers|rédacteurs des actualités techniques]] et postées par [[m:Special:MyLanguage/User:MediaWiki message delivery|robot]]. [[m:Special:MyLanguage/Tech/News#contribute|Contribuer]]&nbsp;• [[m:Special:MyLanguage/Tech/News/2026/33|Traduire]]&nbsp;• [[m:Tech|Obtenir de l’aide]]&nbsp;• [[m:Talk:Tech/News|Donner son avis]]&nbsp;• [[m:Global message delivery/Targets/Tech ambassadors|S’abonner ou se désabonner]].'' </div><section end="technews-2026-W33"/> <bdi lang="en" dir="ltr">[[User:MediaWiki message delivery|MediaWiki message delivery]]</bdi> 10 août 2026 à 22:45 (CEST) <!-- Message envoyé par User:STei (WMF)@metawiki en utilisant la liste sur https://meta.wikimedia.org/w/index.php?title=Global_message_delivery/Targets/Tech_ambassadors&oldid=30901051 --> == <span lang="en" dir="ltr">Tech News: 2026-34</span> == <div lang="en" dir="ltr"> <section begin="technews-2026-W34"/><div class="plainlinks"> Latest '''[[m:Special:MyLanguage/Tech/News|tech news]]''' from the Wikimedia technical community. Please tell other users about these changes. Not all changes will affect you. [[m:Special:MyLanguage/Tech/News/2026/34|Translations]] are available. '''Weekly highlight''' * The [[mw:Special:MyLanguage/Help:Extension:CampaignEvents/Registration/Worklist|Worklist feature]] for the Event Registration tool is now live on all Wikimedia wikis. With Worklist, event organizers can add the articles their event will focus on directly to the event page. The Worklist also powers [[mw:Special:MyLanguage/Help:Extension:CampaignEvents/Registration/Worklist#How Event Pathways uses the worklist|Event Pathways]] which notifies other editors of the upcoming or ongoing event when they edit an article featured in the event's Worklist. This is the minimum viable version (MVP), and feedback is welcome. Organizers are encouraged to try the feature. A hands-on [[m:Special:MyLanguage/Event:Worklist Setup Workshop: Get Your Event Ready|Worklist Setup Workshop]] will take place on 18 August at 16:00 UTC and 19 August at 11:00 UTC. '''Updates for editors''' * [[Special:ShortPages]] displays short pages by their size, but in many cases it gets filled with disambiguations and soft redirects, making it harder to find the short articles themselves. Starting this weekend, you will be able to choose not to include an article in the special page by adding the magic word <bdi lang="zxx" dir="ltr"><code><nowiki>__EXPECTSHORTPAGE__</nowiki></code></bdi>. [https://phabricator.wikimedia.org/T433203] * One new wiki has been created: a {{int:project-localized-name-group-wikipedia/en}} in [[d:Q3436680|Bole]] ([[w:bol:|<bdi lang="zxx" dir="ltr"><code><nowiki>w:bol:</nowiki></code></bdi>]]) [https://phabricator.wikimedia.org/T429921] * Starting the week of August 17, the page toolbar will wrap onto two lines when there is not enough horizontal space for all the buttons. This is a [[phab:T429518|fully merged patch from the Reader Experience team]] which aims to reduce crowding in the Vector 2022 toolbar, that may occur on some language Wikipedias at certain screen widths. * [[File:Reload icon with two arrows.svg|12px|link=|class=skin-invert|Recurrent item]] View all {{formatnum:16}} community-submitted {{PLURAL:16|task|tasks}} that were [[m:Special:MyLanguage/Tech/News/Recently resolved community tasks|resolved last week]]. For example, uploading large files to Wikimedia Commons has become more stable and less prone to failure following some fixes related to the “Could not acquire lock” upload error. [https://phabricator.wikimedia.org/T386640] '''Updates for technical contributors''' * Debian Bullseye will reach the end of its Long Term Support on 31 August 2026. [[phab:T434103|Some Cloud VPS projects]] still have instances running Debian Bullseye. Maintainers of those projects are encouraged to migrate to Debian Bookworm or Debian Trixie. A [[wikitech:Help:Cloud VPS instance operating system migration|migration guide]] is available to help with the process, and users may also want to consider whether their workload is better suited to Toolforge. If you need help or cannot complete the migration by 31 August, please contact the Cloud VPS admins as soon as possible. [[listarchive:list/cloud-announce@lists.wikimedia.org/thread/RVIPQSYLKMSL5M46JP6NEJVGVE6I2RXQ/|Read more]]. * [[File:Reload icon with two arrows.svg|12px|link=|class=skin-invert|Recurrent item]] Detailed code updates later this week: [[mw:MediaWiki 1.47/wmf.16|MediaWiki]] '''''[[m:Special:MyLanguage/Tech/News|Tech news]]''' prepared by [[m:Special:MyLanguage/Tech/News/Writers|Tech News writers]] and posted by [[m:Special:MyLanguage/User:MediaWiki message delivery|bot]]&nbsp;• [[m:Special:MyLanguage/Tech/News#contribute|Contribute]]&nbsp;• [[m:Special:MyLanguage/Tech/News/2026/34|Translate]]&nbsp;• [[m:Tech|Get help]]&nbsp;• [[m:Talk:Tech/News|Give feedback]]&nbsp;• [[m:Global message delivery/Targets/Tech ambassadors|Subscribe or unsubscribe]].'' </div><section end="technews-2026-W34"/> </div> <bdi lang="en" dir="ltr">[[User:MediaWiki message delivery|MediaWiki message delivery]]</bdi> 17 août 2026 à 23:03 (CEST) <!-- Message envoyé par User:STei (WMF)@metawiki en utilisant la liste sur https://meta.wikimedia.org/w/index.php?title=Global_message_delivery/Targets/Tech_ambassadors&oldid=30922806 --> == Actualités techniques n° 2026-35 == <section begin="technews-2026-W35"/><div class="plainlinks"> Dernières '''[[m:Special:MyLanguage/Tech/News|actualités techniques]]''' de la communauté technique de Wikimedia. N’hésitez pas à informer les autres utilisateurs de ces changements. Certains changements ne vous concernent pas. [[m:Special:MyLanguage/Tech/News/2026/35|D’autres traductions]] sont disponibles. '''Actualités pour la contribution''' * La page [[Special:CreateAccount|Spécial:Créer_un_compte]] a été simplifiée dans le cadre des travaux en cours visant à moderniser le processus de création de compte. Le panneau affichant les statistiques du projet n'apparaît plus à côté du formulaire, que ce soit sur ordinateur ou sur mobile. Plusieurs tests portant sur la création de comptes ont montré qu'un formulaire plus simple aidait les nouveaux utilisateurs à mener à bien leur inscription. [https://phabricator.wikimedia.org/T433783] * Afin d'améliorer les performances de la page, les images ne se chargent désormais que lorsqu'elles sont visualisées. Cela signifie que les images situées plus bas dans un article ne se chargeront pas si le lecteur ne fait jamais défiler la page jusqu'à cette partie, ce qui peut avoir une incidence sur certains indicateurs liés aux images. [https://phabricator.wikimedia.org/T148047] * [[File:Reload icon with two arrows.svg|12px|link=|class=skin-invert|Sujet récurrent]] Voir {{PLURAL:42|la tâche soumise|les {{formatnum:42}} tâches soumises}} par la communauté [[m:Special:MyLanguage/Tech/News/Recently resolved community tasks|résolue{{PLURAL:42||s}} la semaine dernière]]. Par exemple, un problème empêchant l'affichage des vignettes d'images sur Abstract Wikipedia après le déplacement du fichier correspondant sur Wikimedia Commons a désormais été résolu. Les vignettes s'actualisent désormais correctement lorsque les fichiers sont déplacés. [https://phabricator.wikimedia.org/T433448] '''Actualités pour la contribution technique''' * La fiche d'informations sur l'utilisateur est une fonctionnalité qui permet aux modérateurs de consulter des informations sur les comptes d'utilisateurs. Jusqu'à présent, elle n'était disponible que dans des sections telles que l'historique des pages, les journaux et les modifications récentes. Désormais, il est également possible de [[mw:Special:MyLanguage/Help:Extension:CheckUser#User_Info_card_in_page_content|l'insérer dans le contenu d'une page]] à l'aide de la fonction d'analyse <bdi lang="zxx" dir="ltr"><code><nowiki>{{#uic:}}</nowiki></code></bdi>. Cela peut s'avérer particulièrement utile dans des modèles tels que [[:en:Template:Userlinks|<bdi lang="zxx" dir="ltr"><code><nowiki>{{Userlinks}}</nowiki></code></bdi>]] (ou leurs variantes spécialisées), car cela facilitera la compréhension du contexte relatif à un ou une utilisatrice sur les divers forums communautaires. La fiche ne s'affichera que pour les utilisateurs qui l'ont activée dans leurs [[Special:Preferences#mw-input-wpcheckuser-userinfocard-enable|préférences]]. [https://phabricator.wikimedia.org/T424466] * En raison des risques liés à la sécurité et à la confidentialité des utilisateurs, nous avons désactivé l'accès aux URL utilisant <bdi lang="zxx" dir="ltr"><code><nowiki>Special:MyPage</nowiki></code></bdi> (Spécial:Ma_page) avec l’argument <bdi lang="zxx" dir="ltr"><code><nowiki>action=raw</nowiki></code></bdi>. Si vous êtes concerné par cette mesure, demandez-vous s'il est possible d'adopter une autre approche. Les URL utilisant <bdi lang="zxx" dir="ltr"><code><nowiki>Special:MyPage</nowiki></code></bdi> restent accessibles et utilisables sans <bdi lang="zxx" dir="ltr"><code><nowiki>action=raw</nowiki></code></bdi>. Les URL de pages utilisateur spécifiées (par exemple, <bdi lang="zxx" dir="ltr"><code><nowiki>User:Myusername</nowiki></code></bdi>) peuvent toujours être utilisées avec <bdi lang="zxx" dir="ltr"><code><nowiki>action=raw</nowiki></code></bdi>. [https://phabricator.wikimedia.org/T120386] * Suite à une mise à jour, le logiciel de création de vignettes a été amélioré. En effet, <bdi lang="zxx" dir="ltr"><code><nowiki>librsvg</nowiki></code></bdi> a été mis à jour vers la version 2.60 et <bdi lang="zxx" dir="ltr"><code><nowiki>ImageMagick</nowiki></code></bdi> vers la version 7. Cela a permis la résolution d’un certain nombre de bugs de longue date liés à la création de vignettes, tels que des erreurs de rendu. [https://phabricator.wikimedia.org/T419815#12222841] * [[File:Reload icon with two arrows.svg|12px|link=|class=skin-invert|Sujet récurrent]] Détail des mises-à-jour à venir cette semaine : [[mw:MediaWiki 1.47/wmf.17|MediaWiki]] '''''[[m:Special:MyLanguage/Tech/News|Actualités techniques]]''' préparées par les [[m:Special:MyLanguage/Tech/News/Writers|rédacteurs des actualités techniques]] et postées par [[m:Special:MyLanguage/User:MediaWiki message delivery|robot]]. [[m:Special:MyLanguage/Tech/News#contribute|Contribuer]]&nbsp;• [[m:Special:MyLanguage/Tech/News/2026/35|Traduire]]&nbsp;• [[m:Tech|Obtenir de l’aide]]&nbsp;• [[m:Talk:Tech/News|Donner son avis]]&nbsp;• [[m:Global message delivery/Targets/Tech ambassadors|S’abonner ou se désabonner]].'' </div><section end="technews-2026-W35"/> <bdi lang="en" dir="ltr">[[User:MediaWiki message delivery|MediaWiki message delivery]]</bdi> 24 août 2026 à 22:45 (CEST) <!-- Message envoyé par User:STei (WMF)@metawiki en utilisant la liste sur https://meta.wikimedia.org/w/index.php?title=Global_message_delivery/Targets/Tech_ambassadors&oldid=30961208 --> == Actualités techniques n° 2026-36 == <section begin="technews-2026-W36"/><div class="plainlinks"> Dernières '''[[m:Special:MyLanguage/Tech/News|actualités techniques]]''' de la communauté technique de Wikimedia. N’hésitez pas à informer les autres utilisateurs de ces changements. Certains changements ne vous concernent pas. [[m:Special:MyLanguage/Tech/News/2026/36|D’autres traductions]] sont disponibles. '''En lumière cette semaine''' * Un nouveau format pour la liste de souhaits de la communauté est ouvert aux commentaires. Vous pouvez [[m:Special:MyLanguage/Community Wishlist/Community Wishlist 2027|lire les idées proposées sur Meta]]. Ce nouveau processus prévoit d'améliorer la manière dont les souhaits sont triés, votés et priorisés d'une manière transparente et équilibrée entre les familles de projets et les éditions linguistiques. Cette consultation est ouverte pendant deux semaines. '''Actualités pour la contribution''' * La dernière version de l'application Wikipédia pour Android comprend des mises à jour de la fonctionnalité «Enregistrés», qui rapprochent l'expérience d'enregistrement de l'application de celle proposée sur iOS et sur le Web. Cette mise à jour repense l'onglet «Enregistrés» en proposant une vue «Tous les articles», supprime la liste de lecture par défaut «Enregistrés», renomme les listes de lecture «Collections» et modernise l'expérience de sauvegarde des articles. [https://phabricator.wikimedia.org/T420788] * La fonctionnalité [[mw:Special:MyLanguage/Readers/Reader Experience/Reading lists|Listes de lecture]] a été activée pour tous les utilisateurs connectés sur les versions de Wikipédia en bengali, chinois, tchèque et vietnamien le 25 août, après plusieurs mois en tant que fonctionnalité bêta. Les listes de lecture seront disponibles pour tous les utilisateurs connectés sur les versions de Wikipédia en arabe, français et indonésien le 1er septembre, suivies par la Wikipédia en anglais le 14 septembre, et toutes les autres versions de Wikipédia le 28 septembre. * À la fin du mois, certains lecteurs non connectés sur les versions en bengali, tchèque, persan, anglais et polonais de Wikipédia utilisant l'habillage Minerva sur mobile verront une [[mw:Special:MyLanguage/Readers/Reader_Growth/Minimal_Minerva|barre de navigation mise à jour]] dans le cadre d'un [[w:A/B test|test A/B]]. Le test comparera la barre de navigation actuelle avec une nouvelle version conçue pour faciliter la recherche d'informations plus rapidement. L'objectif est de déterminer si ces changements encouragent les lecteurs à revenir plus souvent. Cette expérience n'aura aucune incidence sur l'expérience des lecteurs et/ou des rédacteurs connectés. * Les contributeurs qui gèrent les redirections, les modèles et les catégories utilisés sur les pages de redirection disposent désormais de meilleurs moyens pour trouver et organiser les redirections. Auparavant, il n'était pas possible de rechercher les pages de redirection. Deux nouveaux mots-clés de recherche, <bdi lang="zxx" dir="ltr"><code><nowiki>onlyredirects:</nowiki></code></bdi> et <bdi lang="zxx" dir="ltr"><code><nowiki>withredirects:</nowiki></code></bdi>, permettent désormais de rechercher directement les redirections et peuvent être combinés avec des mots-clés existants tels que <bdi lang="zxx" dir="ltr"><code><nowiki>incategory:</nowiki></code></bdi>, <bdi lang="zxx" dir="ltr"><code><nowiki>intitle:</nowiki></code></bdi> et <bdi lang="zxx" dir="ltr"><code><nowiki>insource:</nowiki></code></bdi>. [https://phabricator.wikimedia.org/T204089] * Les outils de recherche ISBN pour générer des citations ne fonctionnaient pas récemment en raison de problèmes de service externe. Les développeurs travaillent sur des solutions. [https://phabricator.wikimedia.org/T435179] * [[File:Reload icon with two arrows.svg|12px|link=|class=skin-invert|Sujet récurrent]] Voir {{PLURAL:30|la tâche soumise|les {{formatnum:30}} tâches soumises}} par la communauté [[m:Special:MyLanguage/Tech/News/Recently resolved community tasks|résolue{{PLURAL:30||s}} la semaine dernière]]. Par exemple, un problème où la recherche de pages par catégorie à l'aide de <bdi lang="zxx" dir="ltr"><code><nowiki>deepcat</nowiki></code></bdi> pouvait ne renvoyer aucun résultat ou des résultats sans rapport a été corrigé. [https://phabricator.wikimedia.org/T414859] '''Actualités pour la contribution technique''' * Le domaine des URL pour les miniatures passe de upload.wikimedia.org à thumb.wikimedia.org. Les anciennes URL continueront de fonctionner pour le moment, mais MediaWiki utilisera le nouveau domaine à la place. Les URL vers d'autres types de médias tels que les fichiers originaux, les vidéos et les transcodages seront toujours servies depuis upload.wikimedia.org. [https://phabricator.wikimedia.org/T427465] * L'API Math de Wikimédia [https://www.mediawiki.org/w/index.php?api=wmf-math%2Fv1&title=Special%3ARestSandbox] est désormais obsolète. Ces points de terminaison seront complètement arrêtés d’ici la fin de septembre 2026. Les développeurs qui appellent ces points de terminaison devraient passer à des solutions alternatives de rendu mathématique, telles que le [https://developer.mozilla.org/en-US/docs/Web/MathML MathML] natif ou [https://www.mathjax.org/ MathJax]. Les installations tierces de MediaWiki utilisant l'extension Math pour le rendu des formules doivent effectuer une mise à jour vers la version 1.43+ afin d'éviter toute interruption de service. * [[File:Reload icon with two arrows.svg|12px|link=|class=skin-invert|Sujet récurrent]] Détail des mises-à-jour à venir cette semaine : [[mw:MediaWiki 1.47/wmf.18|MediaWiki]] '''En détails''' * Lisez-en plus sur [[mw:Special:MyLanguage/Edit_check/TextMatch|TextMatch]] dans un article de Diff intitulé [[diffblog:2026/08/28/custom-edit-suggestions-for-every-wiki-how-communities-are-shaping-suggestion-mode-with-textmatch/|Suggestions de modifications personnalisées pour chaque wiki : comment les communautés façonnent le mode Suggestion avec TextMatch]]. '''''[[m:Special:MyLanguage/Tech/News|Actualités techniques]]''' préparées par les [[m:Special:MyLanguage/Tech/News/Writers|rédacteurs des actualités techniques]] et postées par [[m:Special:MyLanguage/User:MediaWiki message delivery|robot]]. [[m:Special:MyLanguage/Tech/News#contribute|Contribuer]]&nbsp;• [[m:Special:MyLanguage/Tech/News/2026/36|Traduire]]&nbsp;• [[m:Tech|Obtenir de l’aide]]&nbsp;• [[m:Talk:Tech/News|Donner son avis]]&nbsp;• [[m:Global message delivery/Targets/Tech ambassadors|S’abonner ou se désabonner]].'' </div><section end="technews-2026-W36"/> <bdi lang="en" dir="ltr">[[User:MediaWiki message delivery|MediaWiki message delivery]]</bdi> 31 août 2026 à 22:53 (CEST) <!-- Message envoyé par User:STei (WMF)@metawiki en utilisant la liste sur https://meta.wikimedia.org/w/index.php?title=Global_message_delivery/Targets/Tech_ambassadors&oldid=30990659 --> == Actualités techniques n° 2026-37 == <section begin="technews-2026-W37"/><div class="plainlinks"> Dernières '''[[m:Special:MyLanguage/Tech/News|actualités techniques]]''' de la communauté technique de Wikimedia. N’hésitez pas à informer les autres utilisateurs de ces changements. Certains changements ne vous concernent pas. [[m:Special:MyLanguage/Tech/News/2026/37|D’autres traductions]] sont disponibles. '''Actualités pour la contribution''' * La fonctionnalité [[mw:Special:MyLanguage/Help:Growth/Tools/Add_a_link|Ajouter un lien]] a été améliorée pour les utilisateurs de Wikipédia en anglais, avec une meilleure détection des liens présentant des différences de majuscules. Cela permettra de réduire les suggestions ambiguës dues aux différences de majuscules dans les titres d'articles, notamment ceux consacrés aux biens culturels. Une deuxième phase d'améliorations est prévue, qui permettra également de préparer cette fonctionnalité en vue de son déploiement dans d'autres langues. [https://phabricator.wikimedia.org/T435526#12240876] [https://phabricator.wikimedia.org/T434259] * La fonctionnalité [[mw:Special:MyLanguage/Article_guidance|Guide des articles]] sera activée par défaut pour les contributeurs débutants sur Wikipédia en anglais simplifié et en turc à compter du 10 septembre 2026, à la suite d'[[mw:Special:MyLanguage/Article_guidance/Updates#Summary_of_experiment_result|une expérience]]. Les contributeurs débutants ayant effectué entre 0 et 99 modifications verront automatiquement s'afficher cette fonctionnalité lorsqu'ils cliqueront sur un lien rouge ou utiliseront l'option « [[tr:Vikipedi:Madde_sihirbazı|Madde oluşturto]] » sur Wikipédia en turc pour créer un nouvel article. Ce changement vise à aider les contributeurs débutants à créer des articles de meilleure qualité, conformes aux normes de chaque Wikipédia. [[phab:maniphest/query/z3tcTjmLMxk5/#R|D'autres améliorations]] seront apportées en fonction des résultats de l'expérience et des retours de la communauté. * L'outil [https://pageviews.wmcloud.org « Analyse des pages vues »], qui permet aux utilisateurs de comparer le nombre de pages vues sur plusieurs pages, fête cette année ses 10 ans et s'est enrichi de plusieurs nouvelles fonctionnalités. Parmi celles-ci figurent [[toolforge:wikinav|WikiNav]], qui donne un aperçu de la manière dont les lecteurs de Wikipédia explorent le contenu, les statistiques d'édition dans [https://pageviews.wmcloud.org/siteviews?range=latest-30&sites=en.wikipedia.org « Vues du site »], la possibilité de [https://pageviews.wmcloud.org/massviews?source=wikiproject&project=en.wikipedia.org rechercher des articles appartenant à un projet Wiki] et la prise en charge du mode sombre. [https://phabricator.wikimedia.org/T378549] * Une barre de navigation [[mw:Special:MyLanguage/Readers/Reader_Growth/Minimal_Minerva|Minerva, visuellement simplifiée]] est actuellement testée auprès des lecteurs non connectés utilisant la version mobile des Wikipédias en bengali, tchèque, anglais, farsi et polonais. Cette expérience vise à déterminer si la simplification de la navigation permet de fidéliser davantage les lecteurs. Le test se déroulera du 31 août au 28 septembre et ne nécessite aucune intervention de la part des utilisateurs. * [[m:Special:MyLanguage/WMDE Technical Wishes|WMDE Technical Wishes]] travaille à l'amélioration des [[en:Wikipedia:VisualEditor/Named references|noms de référence générés automatiquement dans VisualEditor]]. Les contributeurs ne remarqueront qu'un léger changement à partir de cette semaine. Lors de l'ajout de noms de référence automatiques, la numérotation commencera par <bdi lang="zxx" dir="ltr"><code><nowiki>:1</nowiki></code></bdi> au lieu de <bdi lang="zxx" dir="ltr"><code><nowiki>:0</nowiki></code></bdi>. Pour en savoir plus, consultez la [[m:WMDE Technical Wishes/References/VisualEditor automatic reference names|page du projet]]. [https://gerrit.wikimedia.org/r/c/VisualEditor/VisualEditor/+/1332704] * Tous les wikis seront [[m:Special:MyLanguage/Tech/Server switch|en lecture seule pendant quelques minutes]] le 23 septembre. Cette opération est prévue à 14h UTC. De plus amples informations seront publiées dans la rubrique « Tech News » et seront également mises en ligne sur chaque wiki au cours des prochaines semaines. [https://phabricator.wikimedia.org/T433363] * [[File:Reload icon with two arrows.svg|12px|link=|class=skin-invert|Sujet récurrent]] Voir {{PLURAL:25|la tâche soumise|les {{formatnum:25}} tâches soumises}} par la communauté [[m:Special:MyLanguage/Tech/News/Recently resolved community tasks|résolue{{PLURAL:25||s}} la semaine dernière]]. Par exemple, un problème lié au fait que [[mw:Special:MyLanguage/Parsoid|Parsoid]] pouvait mal gérer les balises nowiki imbriquées, entraînant la perte de contenu et l'affichage de texte indésirable, a désormais été corrigé. [https://phabricator.wikimedia.org/T435116] '''Actualités pour la contribution technique''' * Les administrateurs de l'interface peuvent configurer les gadgets à partir de [[MediaWiki:Gadgets-definition]]. Le format de définition a été mis à jour et ne nécessite plus l'option <bdi lang="zxx" dir="ltr"><code><nowiki>ResourceLoader</nowiki></code></bdi>, car les gadgets sont toujours chargés via <bdi lang="zxx" dir="ltr"><code><nowiki>ResourceLoader</nowiki></code></bdi>. Cela simplifie la configuration des gadgets en supprimant une option qui n'est plus nécessaire, ce qui facilite la tâche des administrateurs lors de la définition des gadgets. [https://phabricator.wikimedia.org/T298199] * [[File:Reload icon with two arrows.svg|12px|link=|class=skin-invert|Sujet récurrent]] Détail des mises-à-jour à venir cette semaine : [[mw:MediaWiki 1.47/wmf.19|MediaWiki]] '''''[[m:Special:MyLanguage/Tech/News|Actualités techniques]]''' préparées par les [[m:Special:MyLanguage/Tech/News/Writers|rédacteurs des actualités techniques]] et postées par [[m:Special:MyLanguage/User:MediaWiki message delivery|robot]]. [[m:Special:MyLanguage/Tech/News#contribute|Contribuer]]&nbsp;• [[m:Special:MyLanguage/Tech/News/2026/37|Traduire]]&nbsp;• [[m:Tech|Obtenir de l’aide]]&nbsp;• [[m:Talk:Tech/News|Donner son avis]]&nbsp;• [[m:Global message delivery/Targets/Tech ambassadors|S’abonner ou se désabonner]].'' </div><section end="technews-2026-W37"/> <bdi lang="en" dir="ltr">[[User:MediaWiki message delivery|MediaWiki message delivery]]</bdi> 7 septembre 2026 à 20:44 (CEST) <!-- Message envoyé par User:STei (WMF)@metawiki en utilisant la liste sur https://meta.wikimedia.org/w/index.php?title=Global_message_delivery/Targets/Tech_ambassadors&oldid=31008437 --> == <span lang="en" dir="ltr">Tech News: 2026-38</span> == <div lang="en" dir="ltr"> <section begin="technews-2026-W38"/><div class="plainlinks"> Latest '''[[m:Special:MyLanguage/Tech/News|tech news]]''' from the Wikimedia technical community. Please tell other users about these changes. Not all changes will affect you. [[m:Special:MyLanguage/Tech/News/2026/38|Translations]] are available. '''Weekly highlight''' * The Future Audiences team has started a [[en:Special:MyLanguage/Wikipedia:Village pump (proposals)#New proposed experiment to let readers know about “Google preferred sources”|discussion on English Wikipedia]] about a proposed new experiment to show a temporary notice about the new [https://blog.google/products-and-platforms/products/search/original-high-quality-content-search/ Google Preferred Sources] feature to Wikipedia readers coming from Google. The experiment is to test whether this notice makes visitors come back to Wikipedia more often. Preferred Sources feature lets users choose websites they trust so Google can highlight content from those sources more prominently in Search and AI experiences. We are interested in testing this on other languages that Google supports and would welcome assistance with starting conversations on other wikis. If interested, please reach out to Future Audiences on the [[m:Special:MyLanguage/Future Audiences/Preferred sources|talk page]]. '''Updates for editors''' * The chat platform Discord is releasing a new self-service framework that websites can use to specify how their links should look when shared on Discord. The Future Audiences team is planning to start using this new framework to improve how Wikipedia links look when shared on Discord, giving better attribution and credit to contributors. The link appearance and behavior won't change in the first phase of this project as we want to first collect some baseline data to assess the impact of future changes. However, if community members who are active on Discord spot any issues, they can reach out on [[phab:tag/future-audiences/|Phabricator]] or [[m:Special:MyLanguage/Future Audiences|Metawiki]]. * The Reader Growth team will be running an experiment to test whether [[mw:Special:MyLanguage/Readers/Reader_Growth/Compact Lead|compacting article lead sections on mobiles]] with the addition of a "read more" button improves reader retention. The test will begin on September 17 on Arabic, Spanish, French, Indonesian, Italian, Japanese, Portuguese, Vietnamese, and Chinese Wikipedias and will run for four weeks. * All wikis will be [[m:Special:MyLanguage/Tech/Server switch|read-only for a few minutes]] on September 23. This is planned at 14:00 UTC. More information will be published in Tech News and will also be posted on individual wikis in the coming week. [https://phabricator.wikimedia.org/T433363] * [[File:Reload icon with two arrows.svg|12px|link=|class=skin-invert|Recurrent item]] View all {{formatnum:28}} community-submitted {{PLURAL:28|task|tasks}} that were [[m:Special:MyLanguage/Tech/News/Recently resolved community tasks|resolved last week]]. For example, an issue where [https://wikistats.wmcloud.org/ Wikistats] for Wikipedias was returning an HTTP 500 error and could not be reached, has now been fixed. [https://phabricator.wikimedia.org/T435959] '''Updates for technical contributors''' * Developers who maintain a tool that queries the Wikimedia Commons links tables need to update their code to connect to the new x4 database cluster. The links tables have been moved from the s4 cluster to x4, and will no longer receive updates on s4. The page and redirect tables remain available on both clusters. A wiki replica for the x4 cluster will be set up afterwards. The change is being made because the s4 cluster has grown too large to operate efficiently. You can [[wikitech:Special:MyLanguage/News/2026 Commons links tables database split|read more]]. * [[File:Reload icon with two arrows.svg|12px|link=|class=skin-invert|Recurrent item]] Detailed code updates later this week: [[mw:MediaWiki 1.47/wmf.20|MediaWiki]] '''''[[m:Special:MyLanguage/Tech/News|Tech news]]''' prepared by [[m:Special:MyLanguage/Tech/News/Writers|Tech News writers]] and posted by [[m:Special:MyLanguage/User:MediaWiki message delivery|bot]]&nbsp;• [[m:Special:MyLanguage/Tech/News#contribute|Contribute]]&nbsp;• [[m:Special:MyLanguage/Tech/News/2026/38|Translate]]&nbsp;• [[m:Tech|Get help]]&nbsp;• [[m:Talk:Tech/News|Give feedback]]&nbsp;• [[m:Global message delivery/Targets/Tech ambassadors|Subscribe or unsubscribe]].'' </div><section end="technews-2026-W38"/> </div> <bdi lang="en" dir="ltr">[[User:MediaWiki message delivery|MediaWiki message delivery]]</bdi> 14 septembre 2026 à 17:31 (CEST) <!-- Message envoyé par User:STei (WMF)@metawiki en utilisant la liste sur https://meta.wikimedia.org/w/index.php?title=Global_message_delivery/Targets/Tech_ambassadors&oldid=31039826 --> == Bascule de serveur : votre wiki sera bientôt en lecture seule durant un court instant == <section begin="server-switch"/><div class="plainlinks"> [[:m:Special:MyLanguage/Tech/Server switch|Lire ce message dans une autre langue]] • [https://meta.wikimedia.org/w/index.php?title=Special:Translate&group=page-Tech%2FServer+switch&language=&action=page&filter= {{int:please-translate}}] La [[foundation:|Fondation Wikimédia]] va basculer le trafic entre ses centres de données. Cela permettra de s’assurer que Wikipédia et les autres wikis de Wikimédia peuvent rester en ligne même après une catastrophe. Le trafic sera basculé le '''{{#time:j xg|2026-09-23|fr}}'''. La bascule débutera à '''[https://zonestamp.toolforge.org/{{#time:U|2026-09-23T14:00|en}} {{#time:H:i e|2026-09-23T14:00}}]'''. Malheureusement, en raison de certaines limites de [[mw:Special:MyLanguage/Manual:What is MediaWiki?|MediaWiki]], toutes les modifications de pages devront être arrêtées durant le passage d’un centre de données à l’autre. Nous nous excusons pour ce dérangement, que nous nous efforçons de réduire pour le futur. Une bannière sera affichée sur tous les wikis 30 minutes avant le début de l’opération. Cette bannière restera visible jusqu’à la fin de l’opération. Vous pouvez contribuer à la [https://meta.wikimedia.org/w/index.php?title=Special%3ATranslate&group=Centralnotice-tgroup-read_only_banner&task=view&language=&filter=&action=translate traduction ou relecture] du texte de cette bannière. '''Pendant une courte période, vous pourrez lire les wikis mais pas les modifier.''' * Vous ne pourrez pas effectuer de modification pendant une durée pouvant aller jusqu’à une heure, le {{#time:l j xg Y|2026-09-23|fr}}. * Si vous essayez de faire une modification ou de sauvegarder pendant cette période, vous verrez un message d’erreur. Nous espérons qu’aucune modification ne sera perdue durant ce temps, mais nous ne pouvons le garantir. Si vous voyez un message d’erreur, merci de patienter jusqu’au retour à la normale. Vous pourrez alors enregistrer votre modification. Nous vous conseillons cependant de faire une copie de votre modification avant, au cas où. ''Autres conséquences :'' * Les tâches de fond seront ralenties et certaines pourraient être stoppées. Les liens rouges ne seront pas mis à jour aussi vite que d’habitude. Si vous créez un article qui est déjà lié depuis une autre page, le lien rouge pourrait rester rouge plus longtemps que d’habitude. Certains scripts ayant un long temps d’exécution devront être stoppés. * Le déploiement de code devrait se dérouler comme chaque semaine. Cependant, certains codes particuliers pourraient être gelés si l’opération le nécessitait. * [[mw:Special:MyLanguage/GitLab|GitLab]] sera indisponible durant environ 90 minutes. Ce projet pourra être reporté si nécessaire. Vous pouvez [[wikitech:Switch_Datacenter|consulter le calendrier sur wikitech.wikimedia.org]]. Tout changement sera annoncé dans le calendrier. '''Merci de partager ces informations avec votre communauté.'''</div><section end="server-switch"/> [[m:user:Trizek (WMF)|Trizek (WMF)]] 15 septembre 2026 à 14:58 (CEST) <!-- Message envoyé par User:Trizek (WMF)@metawiki en utilisant la liste sur https://meta.wikimedia.org/w/index.php?title=Distribution_list/Non-Technical_Village_Pumps_distribution_list&oldid=30513874 --> == Your feedback wanted: Remove ''(Language link added:) and similar'' edits from Special:RecentChanges == ''(Apologies for posting in English. You can help by translating it)'' Hello, I’m Danny from [[:en:w:Wikimedia_Deutschland|Wikimedia Deutschland’s]] [[m:Wikidata_For_Wikimedia_Projects|Wikidata For Wikimedia Projects]] team.<br/> We have been investigating and examining how Wikidata’s use in the [[Special:RecentChanges]] and [[Special:Watchlist]] pages can be made to be more useful, or less ‘noisy’ (fewer edits that have little value to the reader). We are currently exploring to make a change in the volume and the type of Wikidata edits that appear in these pages, and '''we need your feedback''' to help us ensure this is a positive change for the Wikis and won’t disrupt your workflows.<br/> Language links are also known as sitelinks or interlanguage links and describe when a Wikidata item is connected to a Wiki, adding it to the language dropdown tool for other connected Wikis. [[File:Types of Lang Link changes.png|x350px|frame|A variety of "Language Link" edits as seen in the Recent Changes feed]] The request for feedback is whether you support the removal of these language link edits from the Recent Changes and Watchlist feed, or would you oppose such a change and if so, why? Please share you thoughts on the dedicated '''[[m:Talk:Wikidata_For_Wikimedia_Projects/Clearer_Wikidata_Edit_Summaries/Hide_Language_Link_edits|metawiki Talk page]]'''. Further information about this change: [[m:Wikidata_For_Wikimedia_Projects/Clearer_Wikidata_Edit_Summaries/Hide_Language_Link_edits|m:Wikidata_For_Wikimedia_Projects/Hide_Language_Link_edits]] Thank you for any participation or feedback you give, -- [[m:User:Danny_Benjafield_(WMDE)|User:Danny Benjafield (WMDE)]] 25 septembre 2026 à 13:46 (CEST) <!-- Message envoyé par User:Danny Benjafield (WMDE)@metawiki en utilisant la liste sur https://meta.wikimedia.org/w/index.php?title=User:Danny_Benjafield_(WMDE)/MassMessage_test_list&oldid=31080832 --> == Actualités techniques n° 2026-39 == <section begin="technews-2026-W39"/><div class="plainlinks"> Dernières '''[[m:Special:MyLanguage/Tech/News|actualités techniques]]''' de la communauté technique de Wikimedia. N’hésitez pas à informer les autres utilisateurs de ces changements. Certains changements ne vous concernent pas. [[m:Special:MyLanguage/Tech/News/2026/39|D’autres traductions]] sont disponibles. '''En lumière cette semaine''' * [[m:Special:MyLanguage/Tech/Server switch|Tous les wikis seront en lecture seule]] pendant quelques minutes le mercredi 23 septembre 2026 à [https://zonestamp.toolforge.org/1790172000 14 h 00 UTC]. Cette interruption est nécessaire pour effectuer des tests de sauvegarde dans le cadre de la bascule des serveurs du centre de données, [[wikitech:Special:MyLanguage/Deployments/Yearly calendar|qui a lieu deux fois par an]]. Pendant cette bascule, tout le trafic des sites web de Wikimédia est redirigé d'un centre de données principal vers le centre de données de secours afin de tester la disponibilité et d'éviter toute interruption de service, même en cas d'urgence. [https://phabricator.wikimedia.org/T433363] '''Actualités pour la contribution''' * L'équipe Growth a testé un nouvel avis post-édition destiné à [[mw:Special:MyLanguage/Contributors/Account Creation Experiments#4. Encourage Temporary Accounts to Register|encourager les titulaires de comptes temporaires à créer un compte permanent]]. Ce nouvel avis a remplacé plusieurs boîtes de dialogue et une notification de bienvenue simultanée par un message unique mettant en avant les avantages de la création d'un compte. L'expérience a permis d'augmenter le taux de création de comptes permanents de 1,94 % à 3,66 %, soit une hausse relative de 87 %. Cette modification va désormais être déployée sur tous les wikis. [https://phabricator.wikimedia.org/T433551] * Pour les contributeurs de Wikipédia utilisant la [[Special:Preferences#mw-prefsection-betafeatures|fonctionnalité bêta « Mode suggestion »]], il existe une [[Special:Preferences#mw-input-wpvisualeditor-editcheck-experimental|nouvelle préférence utilisateur à activer]] permettant d’afficher des contrôles d’édition et des suggestions « expérimentaux ». Ces types sont répertoriés sur la page [[Special:EditChecks#experimental-checks|Special:EditChecks]] et sont destinés aux premiers tests par les développeurs ainsi qu’à la collecte de retours d’expérience de la part d’utilisateurs expérimentés. Les administrateurs peuvent modifier les [[mw:Special:MyLanguage/Edit check/Configuration|détails de configuration]] de chaque suggestion comme d'habitude. Ils peuvent également [[mw:Special:MyLanguage/Help:Suggestion mode#Experimental Suggestions|utiliser cette fonctionnalité]] pour tester les idées de leur communauté concernant des contrôles et des suggestions créés localement. Les types expérimentaux bénéficieront d'un style visuel plus distinct dès la fin de cette semaine. [[mw:Talk:VisualEditor/Suggestion Mode|Vos retours sont les bienvenus]]. * Le [[m:CEE Technical Village Pump|CEE Technical Village Pump]] a été lancé afin d'offrir un espace dédié aux discussions techniques et à la collaboration entre les communautés Wikimédia d'Europe centrale et orientale. * Un nouvel outil, [[mw:Special:MyLanguage/Language Onboarding and Development/Starter kit|Starter Kit]], est désormais disponible pour les communautés Wikipédia de langues nouvelles ou peu répandues. Il rassemble des tâches guidées, des outils et des ressources destinés à aider les communautés à se lancer, à suivre leurs progrès et à collaborer avec l'ensemble de la communauté Wikimédia. Le Starter Kit est hébergé sur Wikimedia Toolforge et est destiné aux Wikipédias des langues comptant moins de 50 000 articles. Pour en savoir plus sur le fonctionnement du Starter Kit et sur la manière dont les communautés l'utilisent, consultez [[diffblog:2026/09/18/introducing-starter-kit-for-new-and-small-language-wikipedias/|cet article du blog Diff]]. * L'équipe chargée de la croissance du nombre de lecteurs lance une nouvelle phase de test de l'[[mw:Special:MyLanguage/Readers/Reader Growth/Image Browsing|expérience sur le carrousel d'images]]. Ce nouveau test utilisera trois nouvelles versions du design, mises à jour en fonction des commentaires de la communauté. L'équipe évaluera les résultats et déterminera avec les communautés s'il convient ou non de mettre en place cette fonctionnalité. L'expérience débutera la semaine du 28 septembre et se déroulera sur les Wikipédias en arabe, bengali, chinois, tchèque, anglais, farsi, français, allemand, indonésien, japonais, polonais, portugais, espagnol, suédois et vietnamien. * Après avoir été déployée avec succès sur les Wikipédias en arabe, en bengali, en chinois, en tchèque, en anglais, en français, en indonésien et en vietnamien, la [[mw:Special:MyLanguage/Readers/Reader Experience/Reading lists|fonctionnalité « Listes de lecture »]] développée par l'équipe chargée de l'expérience utilisateur sera accessible à tous les utilisateurs connectés sur l'ensemble des wikis Wikipédia à compter du 28 septembre 2026. * [[File:Reload icon with two arrows.svg|12px|link=|class=skin-invert|Sujet récurrent]] Voir {{PLURAL:28|la tâche soumise|les {{formatnum:28}} tâches soumises}} par la communauté [[m:Special:MyLanguage/Tech/News/Recently resolved community tasks|résolue{{PLURAL:28||s}} la semaine dernière]]. Par exemple, un problème lié au script de statut des mentors, qui mettait à jour de manière répétée le statut des mentors alors qu'aucun changement n'était intervenu, générant ainsi des entrées inutiles dans la section « Modifications récentes », a désormais été corrigé. [https://phabricator.wikimedia.org/T436659] '''Actualités pour la contribution technique''' * L'API de requête de liste <bdi lang="zxx" dir="ltr"><code><nowiki>abusefilters</nowiki></code></bdi>, utilisée pour récupérer des informations spécifiques sur les filtres anti-abus actifs ou historiques configurés sur un wiki, a été mise à jour afin de prendre en charge le paramètre de configuration <bdi lang="zxx" dir="ltr"><code><nowiki>formatversion=2</nowiki></code></bdi>, ce qui entraîne des changements majeurs dans la réponse de l'API. Tous les utilisateurs qui gèrent des scripts utilisateur ou tout autre code doivent vérifier s'ils utilisent l'API de requête de liste <bdi lang="zxx" dir="ltr"><code><nowiki>abusefilters</nowiki></code></bdi> et s'adapter à ces changements majeurs. [https://phabricator.wikimedia.org/T435828] * [[File:Reload icon with two arrows.svg|12px|link=|class=skin-invert|Sujet récurrent]] Détail des mises-à-jour à venir cette semaine : [[mw:MediaWiki 1.47/wmf.21|MediaWiki]] '''''[[m:Special:MyLanguage/Tech/News|Actualités techniques]]''' préparées par les [[m:Special:MyLanguage/Tech/News/Writers|rédacteurs des actualités techniques]] et postées par [[m:Special:MyLanguage/User:MediaWiki message delivery|robot]]. [[m:Special:MyLanguage/Tech/News#contribute|Contribuer]]&nbsp;• [[m:Special:MyLanguage/Tech/News/2026/39|Traduire]]&nbsp;• [[m:Tech|Obtenir de l’aide]]&nbsp;• [[m:Talk:Tech/News|Donner son avis]]&nbsp;• [[m:Global message delivery/Targets/Tech ambassadors|S’abonner ou se désabonner]].'' </div><section end="technews-2026-W39"/> <bdi lang="en" dir="ltr">[[User:MediaWiki message delivery|MediaWiki message delivery]]</bdi> 27 septembre 2026 à 13:56 (CEST) <!-- Message envoyé par User:STei (WMF)@metawiki en utilisant la liste sur https://meta.wikimedia.org/w/index.php?title=Global_message_delivery/Targets/Tech_ambassadors&oldid=31085309 --> 8w62h3y45sy6x9mu1zqc8cfz1tzard2 Mathc initiation/0070 0 84515 773269 773009 2026-09-27T09:49:02Z Xhungab 23827 773269 wikitext text/x-wiki __NOTOC__ [[Catégorie:Mathc initiation (livre)]] [[Mathc initiation/a582#Transformées en Z usuelles, des signaux causales discrets :| Sommaire]] ==L'échelon unitaire : u(n) = 1== '''La transformée en Z de u(n) est : U(z) = z/(z-1)''' sum u(n) z^(-n), n=0 to infinity u(n) = 1 sum (1) z^(-n), n=0 to infinity sum (z^(-1))^n, n=0 to infinity sum (1/z)^n, n=0 to infinity Remarque : sum x^n, n=0 to infinity = 1/(1-x) |x|<1 sum (1/z)^n, n=0 to infinity = 1/(1-(1/z)) x = (1/z) = 1/(1-1/z) Multiplions par z/z = '''z/(z-1)''' ==La rampe : r(n) = n== '''La transformée en Z de r(n) est : R(z) = z/(z-1)^2''' '''Travaillons sur x :''' sum x^(n), n=0 to infinity = 1/(1-x) |x| < 1 sum x'^(n), n=0 to infinity = (1/(1-x))' Dérivons sum n x^(n-1), n=0 to infinity = 1/(1-x)^2 (x) [sum n x^(n-1), n=0 to infinity = 1/(1-x)^2] Multiplions par x (*) sum n x^(n ), n=0 to infinity = x/(1-x)^2 '''Travaillons sur z :''' x -> z^(-1) sum n (z^(-1))^(n), n=0 to infinity = (*) Remplaçons x par (z^(-1)) (z^(-1))/(1-(z^(-1)))^2 sum n z^(-n)), n=0 to infinity = a) Simplifions (z^(-1))^(n) (z^(-1))/(1-(z^(-1)))^2 sum n z^(-n)), n=0 to infinity = b) Simplifions (z^(-1)) = (1/z) (1/z)/(1-(1/z))^2 sum n z^(-n)), n=0 to infinity = c) Développons les (1/z) 1/[((z-1)/z))^2 z] sum n z^(-n)), n=0 to infinity = d) Distribuons les carrés 1/[(z-1)^2/z^2) z] sum n z^(-n)), n=0 to infinity = e) Simplifions le z^2 z^2 /[(z-1))^2 z] sum n z^(-n)), n=0 to infinity = f) Cela donne '''z/(z-1)^2''' ==Le carré : c(n) = n^2== '''La transformée en Z de c(n) = n^2 est : C(z) = z(z+1)/(z-1)^3''' Utilisons la transformé en Z de la rampe r(n) = n R(z) = z/(z-1)^2 Si nous utilisons la propriété de [[Mathc initiation/a588|la Multiplication par la variable d'évolution]] sur la transformé de la rampe nous obtenons : x(n) = n r(n) = n n = n^2 alors Z[x(n)] = Z[n r(n)] = -z R'(z) R'(z) = (z/(z-1)^2)' = -(z+1)/(z-1)^3 Z[n r(n)] = (-z) R'(z) = (-z) (-(z+1)/(z-1)^3) Z[n r(n)] = '''z (z+1)/(z-1)^3 = C(n)''' ==L'exponentiel : f(n) = a^n== '''La transformée en Z de f(n) est : F(z) = z/(z-a)''' sum f(n) z^(-n), n=0 to infinity f(n) = a^n sum (a^n) z^(-n), n=0 to infinity sum (a^n) (z^(-1))^(n), n=0 to infinity sum (a z^(-1))^(n), n=0 to infinity sum (a 1/z )^(n), n=0 to infinity z^(-1) = 1/z sum (a/z)^(n), n=0 to infinity Remarque * sum x^n, n=0 to infinity = 1/(1-x) |x|<1 * sum (a/z)^(n), n=0 to infinity = 1/(1-(a/z)) Remplaçons x par (a/z) = 1/(1-(a/z)) Multiplions par z/z = z/(z-(a)) = '''z/(z- a)''' {{AutoCat}} 28xa9xxyp7p9aotqjnhzbugbz4ci4bx Mathc initiation/007a 0 84526 773256 2026-09-26T21:25:18Z Xhungab 23827 news 773256 wikitext text/x-wiki __NOTOC__ [[Catégorie:Mathc initiation (livre)]] [[Mathc initiation/a522#Analyse IV : Se familiariser avec la transformée en Z|Sommaire]] {{Partie{{{type|}}}|Transformée en Z inverse : Un peu d'entraînement}} {{Partie{{{type|}}}|[[Mathc initiation/007b|* Utilisons la méthode des fractions partielles]]}} {{AutoCat}} cu2wpuv3e3zzffc1q0wb1fezf782wyk 773265 773256 2026-09-27T09:22:21Z Xhungab 23827 773265 wikitext text/x-wiki __NOTOC__ [[Catégorie:Mathc initiation (livre)]] [[Mathc initiation/a522#Analyse IV : Se familiariser avec la transformée en Z|Sommaire]] {{Partie{{{type|}}}|Transformée en Z inverse : Un peu d'entraînement}} {{Partie{{{type|}}}|[[Mathc initiation/007b|* Utilisons la méthode des fractions partielles]]}} ==Étudions quelques cas :== {{Partie{{{type|}}}|[[Mathc initiation/007c|* Z[n x(n)] = -z X'(z)]]}} {{AutoCat}} 1b3see6sce7decd8yuacu31zuw203c1 Mathc initiation/007b 0 84527 773257 2026-09-26T21:45:27Z Xhungab 23827 news 773257 wikitext text/x-wiki __NOTOC__ [[Catégorie:Mathc initiation (livre)]] [[Mathc initiation/007a|Sommaire]] * [[#X(z) = 2 z^2 / ((z−2)(z−3))| '''X(z) = 2 z^2 / ((z−2)(z−3))''']] * [[#X(z) = z/((z−1)(z−2)(z−3))| '''X(z) = z/((z−1)(z−2)(z−3))''']] == X(z) = 2 z^2/((z−2)(z−3)) == On veut retrouver x[n] X(z) = 2 z^2/[(z−2)(z−3)] Introduisons la méthode des fractions partielles : 2 z^2/[(z−2)(z−3)] = A z/(z−2) + B z/(z−3) Soit après suppression des dénominateurs : 2 z = A (z−3) + B (z−2) Trouvez A et B. Si z = (2) 2 (2) = A ((2) − 3) alors A = -4 Si z = (3) 2 (3) = B ((3) − 2) alors B = +6 Donc : X(z) = -4 z/(z − 2) + 6 z/(z − 3) x[n] = -4 2^n u[n] + 6 3^n u[n] x[n] = -2^2 2^n u[n] + 2 3 3^n u[n] x[n] = -2^(n+2) u[n] + 2 3^(n+1) u[n] == X(z) = z/((z−1)(z−2)(z−3)) == On veut retrouver x[n] X(z) = z/((z−1)(z−2)(z−3)) Introduisons la méthode des fractions partielles : z/((z−1)(z−2)(z−3)) = A z/(z−1) + B z/(z−2) + C z/(z−3) Soit après suppression des dénominateurs : 1 = A(z−2)(z−3) + B(z−1)(z−3) + C(z−1)(z−2) Trouvez A, B et C. Si z = (1) 1 = A ((1)−2)((1)−3) alors A = (+1/2) Si z = (2) 1 = B ((2)−1)((2)−3) alors B = (-1 ) Si z = (3) 1 = C ((3)−1)((3)−2) alors B = (+1/2) Donc : X(z) = A z/(z−1) + B z/(z−2) + C z/(z−3) X(z) = 1/2 z/(z−1) - z/(z−2) + 1/2 z/(z−3) x[n] = 1/2 1^n u[n] - 2^n u[n] + 1/2 3^n u[n] x[n] = (1/2 - 2^n + 3^n/2) u[n] {{AutoCat}} duezu9zur4lqifsifdoc9byud7irh4d 773258 773257 2026-09-26T21:46:08Z Xhungab 23827 773258 wikitext text/x-wiki __NOTOC__ [[Catégorie:Mathc initiation (livre)]] [[Mathc initiation/007a|Sommaire]] * [[#X(z) = 2 z^2/((z−2)(z−3))| '''X(z) = 2 z^2 / ((z−2)(z−3))''']] * [[#X(z) = z/((z−1)(z−2)(z−3))| '''X(z) = z/((z−1)(z−2)(z−3))''']] == X(z) = 2 z^2/((z−2)(z−3)) == On veut retrouver x[n] X(z) = 2 z^2/[(z−2)(z−3)] Introduisons la méthode des fractions partielles : 2 z^2/[(z−2)(z−3)] = A z/(z−2) + B z/(z−3) Soit après suppression des dénominateurs : 2 z = A (z−3) + B (z−2) Trouvez A et B. Si z = (2) 2 (2) = A ((2) − 3) alors A = -4 Si z = (3) 2 (3) = B ((3) − 2) alors B = +6 Donc : X(z) = -4 z/(z − 2) + 6 z/(z − 3) x[n] = -4 2^n u[n] + 6 3^n u[n] x[n] = -2^2 2^n u[n] + 2 3 3^n u[n] x[n] = -2^(n+2) u[n] + 2 3^(n+1) u[n] == X(z) = z/((z−1)(z−2)(z−3)) == On veut retrouver x[n] X(z) = z/((z−1)(z−2)(z−3)) Introduisons la méthode des fractions partielles : z/((z−1)(z−2)(z−3)) = A z/(z−1) + B z/(z−2) + C z/(z−3) Soit après suppression des dénominateurs : 1 = A(z−2)(z−3) + B(z−1)(z−3) + C(z−1)(z−2) Trouvez A, B et C. Si z = (1) 1 = A ((1)−2)((1)−3) alors A = (+1/2) Si z = (2) 1 = B ((2)−1)((2)−3) alors B = (-1 ) Si z = (3) 1 = C ((3)−1)((3)−2) alors B = (+1/2) Donc : X(z) = A z/(z−1) + B z/(z−2) + C z/(z−3) X(z) = 1/2 z/(z−1) - z/(z−2) + 1/2 z/(z−3) x[n] = 1/2 1^n u[n] - 2^n u[n] + 1/2 3^n u[n] x[n] = (1/2 - 2^n + 3^n/2) u[n] {{AutoCat}} 93atunclbxglec0dvxabd5b1tikib66 Mathc initiation/007c 0 84528 773266 2026-09-27T09:34:29Z Xhungab 23827 news 773266 wikitext text/x-wiki __NOTOC__ [[Catégorie:Mathc initiation (livre)]] [[Mathc initiation/a522#Analyse IV : Se familiariser avec la transformée en Z|Sommaire]] * [[# X(z) = z/(z-3)^2 | ''' X(z) = z/(z-3)^2 ''']] * [[# X(z) = 3 z/(z-3)^2 | ''' X(z) = 3 z/(z-3)^2 ''']] * [[# X(z) = 9 z/(z-3)^2 | ''' X(z) = 9 z/(z-3)^2 ''']] * [[# X(z) = 27 z/(z-3)^2 | ''' X(z) = 27 z/(z-3)^2 ''']] * [[# X(z) = 6 z/(z-3)^2 | ''' X(z) = 6 z/(z-3)^2 ''']] == X(z) = z/(z-3)^2 == On veut retrouver x[n] X(z) = z/(z-3)^2 x[n] = n 3^(n-1) u[n] Remarque 1 : Remarque 2 : z/(z-a)^2 <-> n a^(n-1) u[n] (z/(z-a))' = -a 1/(z-a)^2 '''a z/(z-a)^2''' <-> n a^(n) u[n] '''-z'''(z/(z-a))' = '''a z/(z-a)^2''' [[Mathc initiation/a588|-z X'(z) = Z(n x(n))]] == X(z) = 3 z/(z-3)^2 == On veut retrouver x[n] X(z) = 3 z/(z-3)^2 x[n] = 3 n 3^(n-1) u[n] x[n] = n 3^n u[n] == X(z) = 9 z/(z-3)^2 == On veut retrouver x[n] X(z) = 9 z/(z-3)^2 x[n] = 9 n 3^(n-1) u[n] x[n] = 3 n 3^n u[n] == X(z) = 27 z/(z-3)^2 == On veut retrouver x[n] X(z) = 27 z/(z-3)^2 x[n] = 27 n 3^(n-1) u[n] x[n] = 9 n 3^n u[n] == X(z) = 6 z/(z-3)^2 == On veut retrouver x[n] X(z) = 6 z/(z-3)^2 x[n] = 6 n 3^(n-1) u[n] x[n] = 2 n 3^n u[n] {{AutoCat}} e1z432r6744kozttv6lgpfkvg4qho06 773267 773266 2026-09-27T09:35:56Z Xhungab 23827 773267 wikitext text/x-wiki __NOTOC__ [[Catégorie:Mathc initiation (livre)]] [[Mathc initiation/a522#Analyse IV : Se familiariser avec la transformée en Z|Sommaire]] * [[#X(z) = z/(z-3)^2| ''' X(z) = z/(z-3)^2 ''']] * [[#X(z) = 3 z/(z-3)^2| ''' X(z) = 3 z/(z-3)^2 ''']] * [[#X(z) = 9 z/(z-3)^2| ''' X(z) = 9 z/(z-3)^2 ''']] * [[#X(z) = 27 z/(z-3)^2| ''' X(z) = 27 z/(z-3)^2 ''']] * [[#X(z) = 6 z/(z-3)^2| ''' X(z) = 6 z/(z-3)^2 ''']] ==X(z) = z/(z-3)^2== On veut retrouver x[n] X(z) = z/(z-3)^2 x[n] = n 3^(n-1) u[n] Remarque 1 : Remarque 2 : z/(z-a)^2 <-> n a^(n-1) u[n] (z/(z-a))' = -a 1/(z-a)^2 '''a z/(z-a)^2''' <-> n a^(n) u[n] '''-z'''(z/(z-a))' = '''a z/(z-a)^2''' [[Mathc initiation/a588|-z X'(z) = Z(n x(n))]] ==X(z) = 3 z/(z-3)^2== On veut retrouver x[n] X(z) = 3 z/(z-3)^2 x[n] = 3 n 3^(n-1) u[n] x[n] = n 3^n u[n] == X(z) = 9 z/(z-3)^2 == On veut retrouver x[n] X(z) = 9 z/(z-3)^2 x[n] = 9 n 3^(n-1) u[n] x[n] = 3 n 3^n u[n] ==X(z) = 27 z/(z-3)^2== On veut retrouver x[n] X(z) = 27 z/(z-3)^2 x[n] = 27 n 3^(n-1) u[n] x[n] = 9 n 3^n u[n] ==X(z) = 6 z/(z-3)^2== On veut retrouver x[n] X(z) = 6 z/(z-3)^2 x[n] = 6 n 3^(n-1) u[n] x[n] = 2 n 3^n u[n] {{AutoCat}} lgnv5ms70cenkjepjcnrb2bn5hg9qdx 773268 773267 2026-09-27T09:36:35Z Xhungab 23827 773268 wikitext text/x-wiki __NOTOC__ [[Catégorie:Mathc initiation (livre)]] [[Mathc initiation/a522#Analyse IV : Se familiariser avec la transformée en Z|Sommaire]] * [[#X(z) = z/(z-3)^2| ''' X(z) = z/(z-3)^2 ''']] * [[#X(z) = 3 z/(z-3)^2| ''' X(z) = 3 z/(z-3)^2 ''']] * [[#X(z) = 9 z/(z-3)^2| ''' X(z) = 9 z/(z-3)^2 ''']] * [[#X(z) = 27 z/(z-3)^2| ''' X(z) = 27 z/(z-3)^2 ''']] * [[#X(z) = 6 z/(z-3)^2| ''' X(z) = 6 z/(z-3)^2 ''']] ==X(z) = z/(z-3)^2== On veut retrouver x[n] X(z) = z/(z-3)^2 x[n] = n 3^(n-1) u[n] Remarque 1 : Remarque 2 : z/(z-a)^2 <-> n a^(n-1) u[n] (z/(z-a))' = -a 1/(z-a)^2 '''a z/(z-a)^2''' <-> n a^(n) u[n] '''-z'''(z/(z-a))' = '''a z/(z-a)^2''' [[Mathc initiation/a588|-z X'(z) = Z(n x(n))]] ==X(z) = 3 z/(z-3)^2== On veut retrouver x[n] X(z) = 3 z/(z-3)^2 x[n] = 3 n 3^(n-1) u[n] x[n] = n 3^n u[n] == X(z) = 9 z/(z-3)^2 == On veut retrouver x[n] X(z) = 9 z/(z-3)^2 x[n] = 9 n 3^(n-1) u[n] x[n] = 3 n 3^n u[n] ==X(z) = 27 z/(z-3)^2== On veut retrouver x[n] X(z) = 27 z/(z-3)^2 x[n] = 27 n 3^(n-1) u[n] x[n] = 9 n 3^n u[n] ==X(z) = 6 z/(z-3)^2== On veut retrouver x[n] X(z) = 6 z/(z-3)^2 x[n] = 6 n 3^(n-1) u[n] x[n] = 2 n 3^n u[n] {{AutoCat}} gbop5j1lp4mghpzkp5qitiukjno64qv