Aller au contenu

Fonctionnement d'un ordinateur/Les unités mémoires à exécution dans l'ordre

Un livre de Wikilivres.

Dans ce chapitre, nous allons voir comment fonctionne l'unité mémoire d'un processeur avec un pipeline. Du point de vue du pipeline, une unité mémoire avec calcul d'adresse intégré se comporte comme n'importe quelle unité de calcul : elle prend en entrée des opérandes et fournit un résultat en sortie, ce qu'il y a à l'intérieur n'a pas d'importance du point de vue de l'unité d'émission ou des autres circuits. Les opérandes envoyées en entrée sont les adresses à lire/écrire, et la donnée à écrire pour une écriture. La sortie n'est utilisée que pour les lectures, elle présente la donnée lue.

Cependant, il y a quelques points qui font que l'unité mémoire est un peu à part. Premièrement, les accès mémoire ont un temps d'exécution variable. Un accès au cache peut prendre deux à trois cycles d’horloge, un accès au cache L2 prend facilement 10 cycles, et un accès en RAM une centaine. Les latences sont pas très compatibles avec un pipeline, car elles sont très variables. De plus, la latence d'un défaut de caches est souvent supérieure à la longueur du pipeline. Gérer les accès mémoire est donc assez compliqué.

Vous vous dites sans doute que les accès mémoire sont des instructions multicycles, et ce n'est pas faux. Mais vu que leur latence n'est pas connue à l'avance, les techniques utilisées sur les pipelines multicycle/dynamiques ne fonctionnent pas parfaitement. La gestion des dépendances de données est notamment totalement différente. Dans ce chapitre, nous allons voir une unité mémoire qui ne peut pas gérer plusieurs accès mémoire simultanés. Pour le dire autrement, quand l'unité mémoire est en train d'effectuer un accès mémoire, elle ne peut pas accepter un nouvel accès mémoire. Nous les appellerons les unités mémoire bloquantes. Bloquante, sous-entendu qu'elles sont bloquées pendant qu'elles font un accès mémoire.

L'unité mémoire : la Load-Store Unit

[modifier | modifier le wikicode]

Les micro-opérations mémoires sont gérées par un circuit spécialisé, appelé l'unité mémoire, ou encore la Load-Store Unit. Nous utiliserons le terme unité mémoire dans la suite du cours. En soi, nous avons déjà vu cette unité, dans le chapitre sur le chemin de données, dans la section de quatre chapitres portant sur la microarchitecture d'un processeur. Nous avions parlé de l'unité de communication avec la mémoire, sous-entendu la mémoire RAM. Mais sur un pipeline, il y a quelques petites différences avec l'unité précédente.

La première différence est que l'unité mémoire doit faire avec la présence d'un cache de données. Elle s'interface avec le cache, pas avec la mémoire RAM. De plus, gérer une hiérarchie de cache demande quelques modifications. La seconde différence tient dans la manière dont sont calculées les adresses, qui force à intégrer l'unité mémoire différemment dans le processeur. Avec un pipeline, il n'y a pas pas de bus de communication qui relie entre eux unités de calcul, registres et unité mémoire. A la place, l'unité mémoire est reliée aux registres et aux réseau de contournement. Suivant comment s'effectue le calcul d'adresse, le réseau de contournement est connecté d'une manière ou d'une autre à l'unité mémoire. Notamment, les registres d’interfaçage mémoire disparaissent et sont remplacés par des registres de pipeline reliés au réseau de contournement.

L'interface de l'unité mémoire

[modifier | modifier le wikicode]

L'unité mémoire est généralement multiport, avec un port d'entrée et un port de sortie, afin de faciliter son insertion dans le pipeline.

  • Le port d'entrée est là où on envoie l'adresse à lire/écrire, ainsi que la donnée à écrire pour les écritures. L'adresse peut provenir du décodeur pour l'adressage absolu, des registres pour les modes d'adressage indirects. Pour les modes d'adressages à calcul d'adresse, les choses sont un peu compliquées, mais on va dire qu'ils proviennent des unités de calcul.
  • Le port de sortie est utilisé pour récupérer le résultat des lectures. Il faut aussi tenir compte du fait que les données lues sont envoyées aux registres, éventuellement aux ALU via le réseau de contournement. Pour cela, le port de sortie fournit le registre de destination afin de configurer correctement le banc de registre et le réseau de contournement.

Une unité mémoire est donc reliée au chemin de données via deux entrées et une sortie : une entrée d'adresse, une entrée de données, et une sortie pour les données lues. La sortie est connectée aux registres, via le port d'écriture du banc de registres, et éventuellement au réseau de contournement des ALU.

Unité d'accès mémoire LOAD-STORE.

L'unité mémoire a généralement deux ports : une pour les micro-opérations de lecture, et un pour les micro-opérations d'écriture. Le port d'écriture contient deux entrées : une pour l'adresse à écrire, et une autre pour la donnée à écrire. Le port de lecture n'a qu'une seule entrée pour l'adresse à lire. Les deux entrées d'adresse sont directement reliées aux unités de calcul d'adresse intégrées dans l'unité mémoire.

Les calculs d'adresse et l'unité mémoire

[modifier | modifier le wikicode]

Les micro-opérations mémoire ont besoin d'une adresse pour faire une lecture/écriture. En général, l'adresse est lue dans les registres, mais elle peut aussi être fournie par le réseau de contournement. Mais certains modes d'adressage font que l'adresse doit être calculée. L'adressage base + indice est un bon exemple : il demande de calculer l'adresse à lire/écrire avec une addition et éventuellement un décalage. Les calculs d'adresse peuvent être effectués soit dans les unités de calcul entières, soit dans des unité de calcul d'adresse séparées, soit dans l'unité mémoire elle-même.

La première solution effectue les calculs d'adresse dans une unité mémoire séparée, totalement autonome et séparée du reste du pipeline. Les deux autres solutions utilisent des ALU séparées. Les deux cas se distinguent sur un point d'implémentation : est-ce qu'il y a un aval séparé pour les accès mémoire ? Si l'unité mémoire effectue les calculs d'adresse, alors un a un aval séparé pour les micro-opérations mémoire, qui se comporte comme une unité de calcul. Il ne communique avec le reste du pipeline que via les registres et le réseau de contournement. Mais si une ALU externe fait les calculs d'adresse, alors ce n'est pas le cas. Il y a un aval qui s'occupe à la fois des micro-opérations entières et des micro-opérations mémoire.

La conséquence est que les instructions mémoire sont décodées en une ou deux micro-opérations. Si l'unité mémoire fait les calculs d'adresse, une instruction mémoire n'utilise qu'une seule micro-opération exécutée par l'unité d'accès mémoire, qui se charge de la mettre en attente et de l'exécuter dès que possible. Si le calcul d'adresse est réalisé par une ALU externe, une instruction mémoire est décodée en deux micro-opérations : une micro-opération de calcul d'adresse et une une micro-opération d'accès mémoire. La première micro-opération s'exécute dans une ALU, et son résultat est envoyé à l'unité mémoire, qui fera le lien entre adresse calculée et micro-opération mémoire.

La solution la plus simple est celle qui effectue les calculs d'adresse dans l'unité mémoire elle-même. Celle-ci incorpore des additionneurs et éventuellement un circuit de décalage pour faire les calculs d'adresse. Les circuits de calcul d'adresse sont alors redondants entre l'ALU entière et l'unité mémoire, ce qui a un léger cout en circuit. Mais un accès mémoire est garantit de se faire en une seule micro-opération, ce qui simplifie le décodage. L'interface de l'unité mémoire doit aussi être modifiée, de manière à pouvoir lui envoyer les indices et décalages nécessaires au calcul d'adresse.

Du point de vue du pipeline, une unité mémoire avec calcul d'adresse intégré se comporte comme n'importe quelle unité de calcul : elle prend en entrée des opérandes et fournit un résultat en sortie, ce qu'il y a à l'intérieur n'a pas d'importance du point de vue de l'unité d'émission ou des autres circuits. Les opérandes envoyées en entrée sont les adresses à lire/écrire, et la donnée à écrire pour une écriture. La sortie n'est utilisée que pour les lectures, elle présente la donnée lue.

Si une ALU externe calcule l'adresse, l'adresse calculée est envoyée à l'unité mémoire via un système de contournement dédié, qui relie la sortie de ces unités de calcul à l'unité mémoire, sur les ports d'adresse. L'unité mémoire doit déterminer à quelle accès mémoire l'adresse calculée correspond. Cette mise en correspondance est gérée par des mécanismes qu'on verra plus bas.

Le choix entre ALU entière ou ALU de calcul d'adresse est un compromis entre performance et cout en interconnexions. Utiliser l'ALU entière est moins couteux en transistors, pas besoin d'ajouter des circuits de calcul d'adresse. Par contre, utiliser des unités de calcul d'adresse séparées simplifie drastiquement le réseau de contournement. L'unité de calcul d'adresse a sa sortie reliée uniquement à l'unité mémoire, alors qu'une ALU entière est déjà intégrée dans un réseau de contournement complexe. Rajouter une connexion entre ALU entière et unité mémoire n'est pas trivial, et a un cout en termes de performance.

L'intérieur d'une unité mémoire

[modifier | modifier le wikicode]

L'intérieur de l'unité mémoire n'est pas très complexe. Elle contient parfois des unités de calcul d'adresse pour gérer les modes d'adressage base + indice et autres. Et puis, elle contient le cache de données et tout ce qui le relie au reste de la hiérarchie de cache. L'unité mémoire s'occupe de l'accès au cache L1, mais délègue les défauts de cache aux circuits du cache. Elle détecte les succès ou défaut de cache et communique avec le reste du pipeline pour gérer les défauts de cache.

Le pipelining des micro-opérations mémoire

[modifier | modifier le wikicode]

Les unités mémoire évoluées sont capables de gérer plusieurs accès mémoire simultanés. La solution la plus simple pour cela met en attente les micro-opérations mémoire dans l'unité mémoire, et les exécute une par une. On peut alors envoyer plusieurs accès mémoire simultanés du point de vue de l'unité d'émission, mais qui sont exécutés en série par l'unité mémoire. Une solution assez simple, qui évite de bloquer le pipeline quand deux accès mémoire simultanés ont lieu. Mais faire ainsi pose de nombreux problèmes, car ils faut mettre en attente les accès mémoire, gérer le calcul des adresses, et ce genre de choses. Aussi, il faut aussi gérer les dépendances mémoire.

Une autre solution consiste à pipeliner l'accès à la mémoire. La méthode la plus simple pipeline l'accès au cache, l'autre pipeline l'accès à la mémoire. Pipeliner la mémoire est une méthode assez ancienne, utilisée sur les anciens ordinateurs historiques, qui ne disposaient pas de cache, mais avaient bien un pipeline (et parfois, de l'exécution dans le désordre et du renommage de registres). Typiquement, pipeliner l'accès à la mémoire est assez simple : il suffit d'utiliser des mémoires entrelacées. Pipeliner l'accès au cache est une technique en vigueur dans les processeur modernes, même ceux avec un pipeline dynamique. Voyons là plus en détail.

Beaucoup de processeurs des années 2000 avaient une fréquence peu élevée comparé aux standard d'aujourd'hui, ce qui fait que leur cache avait bien un temps d'accès d'un cycle d'horloge. Mais ils étaient quand même capables de pipeliner les accès mémoire. L'accès au cache se faisait en deux cycles d'horloge, avec un cycle pour calculer une adresse et un cycle pour l'accès mémoire proprement dit. C'était le cas sur les processeurs AMD d'architecture K5 et K6, sur les processeurs Intel d'architecture P6 (Pentium 2 et 3) et quelques autres.

L'avantage de faire ainsi est que l'on peut pipeliner les micro-opérations mémoire alors que l'accès au cache se fait en un cycle d'horloge. Deux micro-opérations mémoire peuvent s'exécuter en même temps sans trop de problèmes. Un autre avantage est que l'on peut réutiliser les unités de calcul normale pour faire les calculs d'adresse. Les processeurs AMD Athlon utilisaient cette technique : les unités de calcul étaient soit utilisées comme unité de calcul entières, soit pour calculer des adresses.

La technique marche aussi bien sur les pipeline dynamiques que multicycle. Avec un pipeline dynamique, Les micro-opérations entières sont alors plus courtes d'un cycle que les micro-opérations de lecture/écriture. Mais avec un pipeline multicycle, on se retrouve avec une organisation assez proche du pipeline RISC classique, avec deux étages EXEC et MEM pour chaque instruction.

L'exécution en parallèle des micro-opérations mémoire

[modifier | modifier le wikicode]

Passons maintenant à la manière dont pipelines dynamiques, de longueur variable, gèrent les accès mémoire. Les accès mémoire ont une latence qui est inconnue, ce qui pose de nombreux problèmes, notamment pour détecter les dépendances de données. Pour éviter tout problème, l'idéal est de stopper l'exécution d'autres instructions tant qu'un accès mémoire a lieu. Quand une micro-opération d'accès mémoire est émise, le processeur cesse d'émettre des instructions, il émet juste des bulles de pipeline. C'est la solution la plus simple, mais elle est imparfaite.

Les lectures non-bloquantes

[modifier | modifier le wikicode]

Une optimisation profite de l'unité de calcul en parallèle de l'unité d'accès mémoire. L'idée est de continuer à faire des calculs en parallèle de l'accès mémoire, sous condition que cela ne pose pas de problèmes. L'idée de base est assez simple : si le processeur rencontre une lecture, il continue d’exécuter des instructions malgré tout, en parallèle de la lecture, si elles sont indépendantes de la lecture. Les instructions indépendantes d'une lecture s’exécutent pendant que celle-ci attend la mémoire ou le cache. Cette technique s'appelle les lectures non-bloquantes. Elle a été utilisée sur le processeur ARM Cortex A53 et sur son successeur, l'ARM Cortex A510, avec un certain succès.

Les lectures non-bloquante peuvent être vues comme une sorte d'exécution dans le désordre limitée aux lectures. La différence avec l'exécution dans le désordre est que la technique des lectures non-bloquantes détecte si une instruction est dépendante d'une lecture, alors que l’exécution dans le désordre détectent toutes les dépendances entre instructions, pas seulement les dépendances avec un accès mémoire.

Mais faire ainsi fait naitre pas mal de problèmes liés aux dépendances de données. Le premier problème est qu'il se peut qu'une instruction ultérieure utilise la donnée lue par l'accès mémoire. Par exemple, la lecture charge une donnée dans un registre et ce registre est ensuite utilisé comme opérande d'une addition. Dans ce cas, l'instruction de calcul ne doit pas s’exécuter tant que la donnée n'a pas été chargée. Un autre cas est celui où une instruction écrit dans le registre de destination de la lecture. Sans lectures non-bloquantes, la lecture a lieu avant, ce qui fait que la lecture écrit dans ce registre, qui est ensuite modifié par l'instruction ultérieure. Mais avec des lectures non-bloquantes, l'instruction qui écrit dans ce registre ne doit pas démarrer tant que la lecture n'est pas terminée.

L'implémentation de cette technique est assez simple, et se fait soit dans l'unité de décodage, soit dans l'unité d'émission. Les deux sont possibles mais nous allons partir sur l'unité d'émission. Dans les deux cas précédents, on remarque que les problèmes ont lieu quand une instruction ultérieure veut lire/écrire le registre de destination de la lecture, à savoir le registre où on charge la donnée lue. Pour détecter les instructions dépendantes d'une lecture, l'unité d'émission marque le registre de destination de la lecture comme « invalide », du moins tant que la lecture n'a pas écrit son résultat dedans. Pour marquer les registres comme valides ou invalides, on utilise un registre dont chaque bit est associé à un registre. Chaque bit indique si le registre associé contient une donnée valide ou non. Le bit de validité est automatiquement comme invalide quand le processeur démarre une lecture.

Toute instruction qui a un registre invalide comme opérande est mise en attente. idem pour les instructions qui ont un registre invalide comme registre de destination. A chaque cycle, l'unité d'émission tente de démarrer une nouvelle instruction et vérifie si elle est dépendante de la lecture. Elle vérifie si l'instruction à émettre a le registre de destination de la lecture comme opérande, ou comme registre de destination. Si c'est le cas, alors l'instruction est bloquée dans le pipeline. Mais dans le cas contraire, l'instruction de calcul est indépendante de l'accès mémoire, et peuvent démarrer sans trop de problèmes.

Le processeur ROCK, annulé en 2010, utilisait une version améliorée de la technique précédente, appelée la technique des éclaireurs matériels. La différence est que le processeur ne stoppe pas quand il rencontre une instruction dépendante de la lecture. A la place, les instructions dépendantes de la lecture sont mises en attente dans une file d'attente spécialisée, appelée la file d’attente différée (deferred queue), qui est une mémoire FIFO un peu modifiée. Une fois que la lecture renvoie un résultat, le processeur cesse l’exécution des instructions et exécute les instructions mises en attente. Les instructions mises en attente sont exécutées dans l'ordre, avant de reprendre là où le programme s'était arrêté.

L'émission optimiste des micro-opérations mémoire

[modifier | modifier le wikicode]

Une autre solution part du principe que l'accès mémoire va accéder au cache, qu'il n'y aura pas de défaut de cache. Le pipeline est conçu pour fonctionner avec la durée d'accès au cache L1 et gère les défauts de cache autrement. Pour simplifier, disons que l'accès au cache L1 se fait en deux cycle d'horloge, dans deux étages dédiés, avec accès pipeliné au cache. Si l'accès mémoire entraine un succès de cache, alors l'instruction s’exécute normalement et en temps imparti, elle suit la progression normale dans le pipeline. Mais dès qu'on défaut de cache est détecté, on gèle le processeur en attendant que le défaut de cache se termine. L'unité d'accès mémoire prévient le séquenceur quand la lecture ou écriture est terminée, ce qui lui permet de savoir quand reprendre l’exécution.

Pour cela, il y a plusieurs solutions. La solution la plus simple gèle le pipeline en cas de défaut de cache. Par geler, on veut dire que le processeur cesse de charge des instructions et que chaque instruction reste dans l'étage où elle est au moment du gel. L'implémentation la plus simple de ce gel est la suivante : chaque étage fournit un signal qui indique s'il faut geler le pipeline ou non, et le tout est envoyé au séquenceur. Le séquenceur déclenche un gel en cessant d'envoyer le signal d'horloge aux registres du pipeline, qui restent dans leur état en attendant que le gel soit levé. Ce gel du pipeline s'appelle un pipeline stall.

Un point important est qu'un pipeline stall n'est pas exactement une bulle de pipeline. Une bulle de pipeline géle le pipeline, comme un pipeline stall, mais c'est leur seule ressemblance. La différence est qu'une bulle de pipeline est le fait de l'unité d'émission, éventuellement de l'unité de décodage. Mais un pipeline stall est quelque chose de plus général, qui n'est pas forcément déclenché par l'unité d'émission/décodage. Ici, en l’occurrence, le stall est géré par l'unité d'accès mémoire.

Une autre solution gère les défauts de cache avec les circuits pour les exceptions précises. L'idée est qu'un défaut de cache est géré comme une exception, mais qui ne quitterait pas le processeur. L'instruction mémoire déclenche un défaut de cache est traitée comme une exception, à savoir que les instructions mémoire qui la suivent sont invalidées. Le processeur est alors mis en mode exception, où il ne charge rien, en attendant que le défaut de cache soit terminé. Une fois le défaut de cache traité, l'instruction mémoire redémarre, mais elle finira sur un succès de cache lors de cette seconde tentative.

Une autre solution, bien plus agressive, est utilisée sur le processeur Pentium4. Il s'agit d'une modification de la technique précédente, qui utilise une solution élégante pour gérer ces ratés de prédiction. La solution évite de geler le pipeline lors d'un défaut de cache. La solution est appelée un pipeline à répétition.

Les pipelines à répétition

[modifier | modifier le wikicode]

Sur les pipelines à répétition (replay pipeline), on lance une instruction sans savoir quelle est la latence et on exécute celle-ci en boucle tant que son résultat n'est pas valide. Pour cela, le pipeline se voit ajouter une sorte de boucle, en plus du pipeline mémoire normal. Les instructions se propagent à la fois dans la boucle et dans le pipeline normal. Les étages de la boucle servent juste à propager les signaux de commande de l'instruction, sans rien faire de spécial. Dans le pipeline qui exécute l'instruction, ces signaux de commande sont consommés au fur et à mesure, ce qui fait qu'à la fin du pipeline, il ne reste plus rien de l'instruction originale. D'où la présence de la boucle, qui sert à conserver les signaux de commande.

L'étage final de la boucle vérifie que l'instruction n'a pas été émise trop tôt avec un scoreboard, et il regarde si l'instruction a donné lieu à un défaut de cache ou non. Si l'instruction a donné un bon résultat, une nouvelle instruction est envoyée dans le pipeline. Dans le cas contraire, l'instruction refera encore un tour dans le pipeline. Dans ce cas, l'unité de vérification va devoir envoyer un signal à l'unité d'émission pour lui dire « réserve un cycle pour l'instruction que je vais faire boucler ».

Pipeline à répétition.

Un point intéressant est que les micro-opérations dépendantes de la lecture sont elles aussi exécutées en avance et ré-exécutées si besoin. Prenons l'exemple d'une lecture qui lit l'opérande manquante d'une addition. Vu qu'il y a a quelques cycles entre l'émission et les unités de calcul, le processeur émet l'addition en avance de quelques cycles, pour qu'elle arrive à l'ALU en temps voulu. En théorie, l'addition ne doit être lancée en avance que si on sait avec certitude que les opérandes seront lues une fois qu'elle arrive à l'ALU. Une addition dépendante d'une lecture doit donc attendre que la lecture termine avant d'être démarrée. Mais avec le système de replay, l'addition est exécutée en avance, avant qu'on sache si ses opérandes sont disponibles. Si jamais un défaut de cache a lieu, l'addition aura atteint l'ALU sans les bonnes opérandes. Elle est alors r-exécutée par le système de replay autant de fois que nécessaire.

Le principe peut s'adapter pour fonctionner avec une hiérarchie de cache. Prenons un exemple : un succès dans le cache L1 dure 3 cycles d'horloge, un succès dans le L2 dure 8 cycles, et un défaut de cache 12 cycles. Imaginons qu'une instruction fasse un défaut de cache dans le L1, et un succès de cache dans le L2. La boucle de 3 cycles utilisée pour le L1 ne permettra pas de gérer efficacement la latence de 8 cycles du L2 : l'instruction devra faire trois tours, soit 9 cycles d'attente, contre 8 idéalement. La solution consiste à retarder le second tour de boucle de quelques cycles, ajoutant une seconde boucle. La seconde boucle ajoute en théorie un retard de 5 cycles : 8 cycles, dont trois en moins pour le premier tour. Pour injecter l'instruction dans la bonne boucle, il suffit d'un multiplexeur commandé par le signal cache hit/miss.

La seconde boucle peut être raccourcie pour une lecture car les micro-opérations dépendantes de la lecture sont émises en avance.
Pipeline à répétition avec une latence de 3 cycles pour le L1, et 8 cycles pour le L2.

Le même principe peut s'appliquer pour gérer les latences avec des niveaux de cache supérieurs : il faut alors utiliser plusieurs boucles de tailles différentes, en ajoutant des multiplexeurs. Il arrive que plusieurs boucles veuillent faire rentrer une instruction dans le pipeline en même temps, au niveau de l'endroit où les boucles se referment. Dans ce cas, une seule boucle peut réémettre son instruction, les autres étant mises en attente.

Divers mécanismes d'arbitrage, de choix de la boucle sélectionnée pour l'émission, sont possibles : privilégier la boucle dont l'instruction est la plus ancienne (et donc la boucle la plus longue) est la technique la plus fréquente. Mais dans certains cas, mettre une boucle en attente peut bloquer tous les étages précédents, ce qui peut bloquer l'émission de la nouvelle instruction : le processeur se retrouve définitivement bloqué. Dans ce cas, le processeur doit disposer d'un système de détection de ces blocages, ainsi que d'un moyen pour s'en sortir et revenir à la normale (en vidant le pipeline, par exemple).

Pipeline à répétition pour une hiérarchie de cache.

Pour gérer au mieux les accès à la mémoire RAM, on remplace la boucle dédiée à la latence mémoire par une FIFO, dans laquelle les instructions sont accumulées en attendant le retour de la donnée en mémoire. Quand la donnée est disponible, lue ou écrite en mémoire, un signal est envoyé à cette mémoire, et l'instruction est envoyée directement dans le pipeline. Là encore, il faut gérer les conflits d'accès à l'émission entre les différentes boucles et la file d’attente de répétition, qui peuvent vouloir émettre une instruction en même temps.

Gestion des accès RAM sur un pipeline à répétition.

On peut aussi adapter le pipeline à répétition pour qu'il gère certaines exceptions : certaines exceptions sont en effet récupérables, et disparaissent si on réexécute l'instruction. Ces exceptions peuvent provenir d'une erreur de prédiction de dépendance entre instructions (on a émis une instruction sans savoir si ses opérandes étaient prêts), ou d'autres problèmes dans le genre. Si jamais une exception récupérable a eu lieu, l'instruction doit être réexécutée, et donc réémise. Elle doit refaire une boucle dans le pipeline. Seul problème : ces exceptions se détectent à des étages très différents dans le pipeline. Dans ce cas, on peut adapter le pipeline pour que chaque exception soit vérifiée et éventuellement réémise dès que possible. On doit donc ajouter plusieurs étages de vérification, ainsi que de nombreuses boucles de réémission.