Fonctionnement d'un ordinateur/L'émission dans l'ordre des instructions

Dans ce chapitre, nous allons voir les processeurs à émission dans l'ordre et allons parler longuement des dépendances de données. Pour rappel, deux instructions ont une dépendance de données quand elles accèdent (en lecture ou écriture) au même registre ou à la même adresse mémoire. Le cas typique est celui où une instruction a besoin du résultat d'une instruction précédente pour s’exécuter. La seconde instruction ne peut pas s’exécuter tant que la première n'a pas enregistré son résultat dans les registres. C'est le cas le plus évident, mais on a aussi des cas moins intuitifs. Commençons par voir quels sont les différentes dépendances entre registres, avant de voir comment le processeur gère celles-ci.

Les dépendances de registre

modifier

Dans ce qui va suivre, nous allons nous limiter aux dépendances liées aux registres, qui apparaissent quand deux instructions lisent ou écrivent dans le même registre. Elles sont opposées aux dépendances d'adresse, où deux instructions lisent/écrivent la même adresse mémoire. Les dépendances d'adresse sont complétement à part. Elles concernent les micro-opérations mémoire et sont gérées dans l'unité d'accès mémoire. De plus, elles n'apparaissent pas sur tous les pipelines. Il faut que le processeur puisse exécuter plusieurs accès mémoire simultanés. Et les processeurs que nous avons vu pour le moment n'en sont pas capables, ils sont capables de ne faire qu'un seul accès mémoire à la fois. Le cas des dépendances mémoire fera l'objet d'un chapitre à part sur la désambiguïsation mémoire. Concentrons-nous donc sur les dépendances de registres.

Les sous-types de dépendances de registre

modifier

Deux instructions ont une dépendance de registre si elles manipulent les mêmes registres. DAsn ce chapitre, nous allons nous préoccuper des trois dépendances de registres suivantes :

  • Read after write : Une instruction lit un registre dans laquelle l'instruction précédente a écrit.
  • Write after read : Une instruction lit un registre pour récupérer une opérande, mais ce registre est ensuite écrasé par une instruction ultérieure.
  • Write after write : Deux instructions écrivent dans le même registre, mais à des instants différents.

Pour simplifier les explications, nous allons appeler ces trois dépendances : dépendances RAW, WAR et WAW. Les dépendances WAR et WAW sont souvent appelées des dépendances de nom (naming dependency). Elles sont 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. 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é. Et on peut les faire disparaitre avec des techniques dites de renommage de registre, qui feront l'objet d'un chapitre ultérieur.

Les dépendances RAW et WAW, imposent que la première instruction ait écrit son résultat avant que la seconde lise ses opérandes. Si on ne fait pas ça, la seconde instruction lit des opérandes invalides ou voit son écriture écrasée par une écriture précédente. Pour éviter cela, la seconde instruction doit être démarrée quand la première s'est totalement terminée. Insérer des bulles de pipeline est donc une nécessité. Mais diverses optimisations permettent de mitiger l'impact des dépendances de données. Par exemple, dans le prochain chapitre, nous parlerons de la technique du contournement, qui permet de supprimer les bulles de pipeline pour les dépendances RAW. Les dépendances WAW et WAR peuvent aussi être totalement supprimées par des techniques de renommages de registres, qui auront leur chapitre dédié.

Les dépendances présentes selon le type de pipeline

modifier

Dans ce chapitre, nous n'allons pas tenir compte des dépendances WAR. Sur la plupart des pipelines dynamiques, les dépendances WAR n'existent tout simplement pas. Les dépendances WAR nécessitent que le pipeline ait quelques particularités pour se manifester, aussi on les passe sous le tapis pour le moment. Elles ne se manifestent que lorsque le processeur utilise des techniques dites d'émission dans le désordre, ce qui fait que nous en reparlerons uniquement dans les chapitres ultérieurs.

La raison est que ces dépendances surviennent quand une écriture dans un registre est avant une lecture d'opérande. Or, les données sont lues au tout début du pipeline et les écritures ont lieu à la fin. Il faut vraiment chercher des cas très particuliers de pipeline spécialement conçus pour pour trouver des dépendances WAR, et ils ne sont pas utilisés dans des processeurs commerciaux. En pratique, les dépendances WAR n'apparaissent que sur les processeurs dits à exécution dans le désordre, et encore, seulement si on n'utilise pas de renommage de registre, ce que presque tous les processeurs avec exécution dans le désordre font. On va donc les mettre de côté dans ce qui suit.

Les dépendances WAW n'apparaissent que sur les pipelines dynamiques. Elles n'existent pas sur un pipeline de longueur fixe, sauf en cas de "défaut de conception" ou de pipeline conçu avec des limitations précises. Pour être plus précis, les dépendances WAW n'apparaissent que sur les pipelines dynamiques ou si les écritures prennent plusieurs étages. Les pipelines de longueur fixe ont l'avantage de ne pas avoir de dépendances WAW, ni WAR, seules les dépendances RAW posent un problème sur ceux-ci.

Dépendance Pipeline Solutions
RAW Tous les pipelines Bulles de pipeline Contournement
WAW Pipelines dynamiques Renommage de registres
WAR Pipelines dynamiques avec exécution dans le désordre

La détection des dépendances de données

modifier

Le processeur doit détecter les dépendances de données entre instructions. Les dépendances de registres se détectent en comparant entre eux les registres utilisés par les instructions. Les registres étant connus dès la sortie de l'étape de décodage, la détection des dépendances de données se fait de manière précoce, avant même l'exécution des instructions. Une sacré différence avec les dépendances d'adresse, qui demandent que les adresses soient calculées et lues dans les registres avant qu'on puisse détecter les dépendances.

La détection des dépendances de registre se fait dans un étage appelé l'étage d'émission, situé juste après l'étage de décodage d'instruction. En théorie, les deux pourraient être fusionnés, et ils le sont sur certains processeurs. Mais ils sont séparés dans la plupart des cas, car l'unité d'émission est un circuits légèrement complexe. L'unité d'émission reçoit une instruction décodée et vérifie si elle une dépendance avec une instruction dans le pipeline. Si aucune dépendance n'est détectée, elle envoie l'instruction aux unités de calcul. Mais si une dépendance est détectée, elle réagit en conséquence en utilisant une des techniques qu'on verra dans la section suivante.

A chaque cycle d'horloge, l'unité d'émission analyse une instruction décodée appelée instruction candidate. Elle détecte si cette instruction candidate a une dépendance avec une instruction déjà dans le pipeline. Les instructions dans le pipeline n'ont pas encore enregistrées leurs résultats dans les registres, soit parce qu'elles sont en cours d'exécution, qu'elles lisent leurs opérandes, peut importe. Elles seront appelées instructions "en vol".

Pour cela, l'unité d'émission compare les registres utilisés par l'instruction candidate et les instructions dans le pipeline. Il n'y a pas de dépendance si ces registres sont différents, alors qu'il y a dépendance dans le cas contraire. Pour être plus précis, il faut cependant faire une différence entre les registres opérandes et de destination. Les registres opérandes sont ceux qui sont lus, ceux où sont les opérandes. Le registre de destination d'une instruction est celui dans lequel on enregistre le résultat. Les quatre situations possibles sont les suivantes :

  • Un des registres opérande est à lire (opérande) par une instruction dans le pipeline : pas de dépendance.
  • Un des registres opérande est écrit (destination) par une instruction dans le pipeline : dépendance RAW.
  • Un des registres de destination est à lire (opérande) par une instruction dans le pipeline : potentielle dépendance WAR, mais seulement sur des pipelines pathologiques jamais utilisés.
  • Un des registres de destination est écrit (destination) par une instruction dans le pipeline : dépendance WAW.

Notez que pour les lectures, on part du principe que les instructions en vol n'ont pas encore lu leurs registres opérandes. Dès qu'elles ont lu les opérandes en question, les dépendances liées disparaissent. Pareil pour les écritures, sauf que celles-ci ont lieu à la toute fin du pipeline. Les écritures en attente ont donc lieu quand l'instruction termine.

Instruction candidate
Registres opérandes Registre destination
Instructions en vol Registres pas encore lus (opérandes) Pas de dépendances (dépendance RAR inoffensive) Dépendance WAR potentielle, pas en pratique
Registre pas encore écrits (destination) Dépendance RAW Dépendance WAW

L'algorithme est donc le suivant : une lecture est autorisée en absence d'écriture (dans le même registre), une écriture est autorisée en absence de lecture (dans le même registre). Cependant, on peut grandement simplifier l'algorithme et ne pas tenir compte des dépendances WAR, qui n'existent pas sur la plupart des pipelines dynamiques sans exécution dans le désordre. Le tableau se simplifie en retirant les dépendances inutiles, seule la dernière ligne nous intéresse au final.

Instruction candidate
Registres opérandes Registre destination
Instructions en vol Registres pas encore lus (opérandes) Pas de dépendances (dépendance RAR inoffensive) Dépendance WAR potentielle, pas en pratique

L'exécution dans l'ordre et dans le désordre des instructions

modifier

Nous venons de voir que les dépendances de données posent des problèmes qu'un pipeline de longueur variable doit résoudre. La plupart de ces problèmes de dépendances n'existent pas sur un pipeline à longueur fixe, car ils n'ont pas de dépendances WAR et WAW. Le fait que leurs écritures dans les registres se font dans l'ordre du programme résout ce genre de dépendances. Mais les pipelines de longueur variables doivent trouver des solutions alternatives.

La solution la plus simple retarde les instructions candidates problématiques de quelques cycles d'horloges. Si une instruction a une dépendance problématique avec une autre, il suffit simplement de l’exécuter avec un retard de cycles. Pour cela, lorsqu'une instruction multicycle est démarrée, les instructions problématiques sont bloquées à l'étage de décodage en attendant le bon moment. Pour cela, on utilise des bulles de pipeline (pipeline bubble), pour retarder l'instruction fautive dans l'unité de décodage.

 
Bulle de pipeline.

Notons que cette technique exécute des instructions indépendantes dans des unités de calcul séparées. Dans l'exemple précédent avec une instruction multicycle suivie par une instruction normale, le processeur détecte les situations où ce n'est pas grave si la seconde instruction finisse avant la première : on peut lors lancer la seconde instruction immédiatement sans attendre. Il y a donc un gain en performance. Un point important est que cette technique émet les instructions dans l'ordre, mais qu'elles peuvent s'exécuter dans le désordre et terminer dans le désordre. En clair, les instructions sont injectées dans les unités de calcul dans l'ordre, mais en sortent dans le désordre.

Pour éviter les bulles de pipeline, les processeurs modernes incorporent des optimisations assez intéressantes. Et nous avons déjà vu une des plus intéressante : utiliser le contournement pour réduire l'usage de bulles de pipeline en cas de dépendance Read after write. Le contournement doit adapté sur les processeurs à instructions multicycle, mais il reste le même dans les grandes lignes. La différence est qu'il y a plus d'unités de calculs avec un pipeline dynamique, ce qui fait que le réseau d'interconnexion est alors plus complexe.

Une autre solution est ce qu'on appelle l'exécution dans le désordre, une optimisation commune sur les processeurs modernes qui sera vue en détail dans plusieurs chapitres dans la suite du cours. L'idée est que si une instruction problématique est bloquée dans l'unité de décodage/émission, elle est juste mise en attente, elle ne bloque pas le pipeline. Le processeur regarde les instructions suivantes et vérifie si certaines d'entre elles peuvent s'exécuter. Les instructions mises en attente sont exécutées dès que possible, elles ont la priorité. La gestion de la mise en attente des instructions problématiques est assez complexe et implique de les stocker dans des mémoires spécifiques. Là encore, il faut que les instructions exécutées en parallèle ne manipulent pas les mêmes données, ni les mêmes registres, ni les mêmes adresses mémoires.

La toute première implémentation de l'exécution dans le désordre était sur l'ordinateur CDC 6600, suivi par son successeur le CDC 7600. Il intégrait un circuit appelé le scoreboard, qui gérait l'exécution dans le désordre. Décrire le circuit en question est assez compliqué, aussi je le laisse de côté pour le moment. Sachez le terme scoreboard est resté et a été élargit à tout circuit d'émission, qu'il soit dans le désordre ou dans l'ordre.

L'étage d'émission : la génération de bulles de pipeline

modifier

Pour éviter tout problème, il faut impérativement détecter à l'avance les dépendances de données, et agir en fonction. Pour cela, on rajoute un étage au pipeline, l'étage d'émission (issue), qui détecte les situations problématiques et rajoute des bulles de pipeline si besoin. Il suit immédiatement l'étage de décodage, et est situé avant le chemin de données. L'unité d'émission gère aussi bien les accès mémoire que les instructions multicycle, il peut aussi configurer les circuits de contournement pour les utiliser au mieux.

Le rôle de l'unité d'émission est de générer les bulles de pipeline. Si elle reçoit une instruction problématique, elle la bloque dans l'unité d'émission et génère des bulles de pipeline, tant que la dépendance n'est pas réglée. Mais si l'instruction peut s'exécuter, elle l'envoie dans la suite du pipeline. On dit qu'elle émet l'instruction. L'émission est donc l'envoi d'une micro-opération dans le pipeline, si les conditions sont réunies. Un point important de l'émission est que le processeur émet une instruction par cycle, en absence de dépendance de données, mais aussi en absence de dépendances structurelles, les deux étant détectées par l'unité d'émission.

La gestion des dépendances de données

modifier

Pour rappel, l'unité d'émission détecte les dépendances entre instructions une instruction candidate et les instructions déjà dans le pipeline, aussi appelées instructions "en vol". L'unité d'émission détecte les dépendances entre l'instruction candidate et chaque instruction en vol. Pour rappel, les dépendances de données se détectent en comparant les instructions de deux opérandes. Vu que les dépendances WAR sont très rares, et n'existent pas sur les pipelines normaux, on ne tient compte que des dépendances RAW et WAW. Les détecter demande de comparer les registres de l'instruction candidate, avec les registres de destination des instructions en vol.

Instruction candidate
Registres opérandes Registre destination
Instructions en vol

Registres pas encore écrits (destination)

Dépendance RAW Dépendance WAW

L'unité d'émission doit connaitre les registres de destination des instructions en vol, ce qui peut se faire de plusieurs manières. La première méthode utilise le fait que les noms de registres sont propagés dans le pipeline, comme tous les signaux de commande. Dans ces conditions, rien n’empêche de relier les registres tampons chargés de la propagation à l'unité d'émission. L'unité d'émission est donc un paquet de comparateurs reliés par des portes OU. En sortie, elle fournit un signal STALL, qui indique s'il faut sortir une bulle de pipeline ou non. Pour un pipeline à N étages maximum, cela demande de faire N comparaisons.

 
Détection des dépendances dans un pipeline.

Une autre implémentation, beaucoup plus économe en circuits, se limite à un simple registre de réservation. Il permet à une instruction de réserver en écriture son registre de destination. Par réservé, on veut dire que le registre ne peut pas être écrit par une autre instruction. Les registres qui ne sont pas réservés sont dit libres ou disponibles. Si une instruction candidate veut lire ce registre, c'est signe qu'il y a une dépendance RAW : elle veut lire un registre réservé, qui est destiné à être écrit par une instruction en vol. Si elle veut écrire, c'est signe qu'elle veut écrire dans un registre où une autre instruction veut écrire : c'est une dépendance WAW. Dans les deux cas, l'instruction n'est pas émise.

Le registre encode les registres réservés en représentation one-hot modifiée. Chaque bit est associé à un registre. Le bit numéro 0 est associé au registre numéro 0, le bit numéro 1 au registre numéro 1, etc. Un registre est réservé en mettant le bit associé à 1.

Registre de réservation
Registre 7 Registre 6 Registre 5 Registre 4 Registre 3 Registre 2 Registre 1 Registre 0
0 0 1 0 0 1 0 0

Il existe une seconde possibilité, où les registres réservés sont à 0 et les registres libres à 1. Nous l’appellerons le registre de disponibilité.

Avant d'émettre une instruction, l'unité d'émission extrait les registres opérande/destination et les convertis dans la même représentation que le registre de réservation, à savoir en représentation one-hot. Le circuit de traduction binaire vers one-hot est, pour rappel, un simple décodeur. Puis, on fait un OU logique entre les sorties des décodeurs. Le résultat est un masque qui indique quels registres sont lus par l'instruction, encodé en représentation one-hot. Appelons-le le masque d'opérandes. Le masque est alors comparé au registre de réservation pour vérifier si l'instruction peut être émise.

 
Unité d'émission simple, dans l'ordre

Si l'émission est autorisée, le registre de réservation est là aussi mis à jour. La mise à jour du registre de réservation à l'émission est simple : on prend le registre de destination, encodé en one-hot, et on le combine avec le registre de réservation, en faisant un simple OU logique. Le registre de réservationest aussi mis à jour dès qu'une instruction enregistre son résultat dans les registres, ou alors dès qu'il est disponible pour le contournement. Le bit associé au registre repasse alors à 0 (ou à 1). Sans contournement, la mise à jour se fait alors à la toute fin de l'instruction, quand elle se termine, lors de la dernière étape d'enregistrement, à la fin de son pipeline. Avec, elle se fait quand l'instruction quitte l'unité de calcul.

Vous aurez peut-être remarqué, mais une différence est que les pipelines fixes retardent les écritures dans le banc de registre avec des registres d'échelonnage, les pipelines dynamique retardent l'émission des instructions. Les deux méthodes ont sensiblement le même résultat.

Le registre de consultation permet de désigner les registres qui vont être lus par les instructions en vol. Je précise bien : les registres qui n'ont pas encore été lus. Les registres en question sont dit consulté, les autres sont dit non-consultés. Il est interdit d'écrire dans un registre consulté, c'est signe d'une dépendance WAR. Il faut noter que beaucoup de pipeline s'en passent car ils n'ont pas de dépendances WAR. Le registre de consultation est mis à jour dès que l'opérande est lue. Et cela arrive assez tôt dans le pipeline, au bout de quelques cycles grand maximum. Sur beaucoup de pipelines, il n'est pas présent dans il est impossible qu'une écriture ait lieu avant qu'une lecture ait démarrée. Mais sur les pipelines où de telles dépendances sont possibles, il faut en tenir compte.

Il existe quelques rares processeurs qui fusionnent le registre de réservation et de consultation en un seul. Le registre contient autant de bits que de registres, chaque bit indique si le registre associé est pas encore lu/écrit. De tels implémentations détectent alors toutes les dépendances, même les dépendances Read After Read. Elles tendent à être très conservatrices. Un exemple est le co-processeur vectoriel VFP9-S d'ARM. Leur implémentation est simple, mais il peut y avoir un cout en performance.

La gestion de la disponibilité des ALUs

modifier

L'étage d'émission détecte aussi les dépendances structurelles au niveau de l'unité de calcul. Elle apparaissent quand deux instructions veulent exécuter une instruction en même temps, sur la même ALU. Si un tel conflit potentiel est détecté, il met en attente l'instruction tant qu'un conflit est possible. Reste à détecter les conflits d'accès, les dépendances structurelles.

Une solution consiste à déterminer les paires d'instructions consécutives qui peuvent donner lieu à des dépendances structurelles. Par exemple, si une instruction de multiplication est dans le pipeline, je sais que je ne peux pas démarrer de nouvelle multiplication immédiatement après, s'il n'y a qu'un seul multiplieur. Détecter les dépendances structurelles consiste à comparer l'opcode de l'instruction à émettre, avec les opcodes des instructions en cours d’exécution dans le pipeline. Si jamais l'opcode de l'instruction à émettre et une opcode dans le pipeline forment une paire d'opérations interdite, il y a dépendance structurelle et on doit insérer des bulles de pipeline.

Une autre solution mémorise un bit pour chaque ALU qui indique si elle est occupée. L'ensemble des bits est appelé le vecteur de disponibilité des ALUs.

Vecteur de disponibilité des ALUs
ADD1 BUSY ADD2 BUSY MUL BUSY COMP CUSY LOAD /STORE BUSY
0 0 1 0 0

Avant d'émettre une instruction, l'unité d'émission vérifie si une unité de calcul adéquate est disponible. Pour cela, chaque instruction est associée à un masque qui précise quelles unité de calcul peuvent l'exécuter. Détecter une dépendance structurelle demande une simple opération de masquage entre ce masque et le vecteur de disponibilité des ALUs.

Masque par instruction, associé au vecteur de disponibilité précédent
ADD1 BUSY ADD2 BUSY MUL BUSY COMP CUSY LOAD /STORE BUSY
ADD 1 1 0 0 0
MUL 0 0 1 0 0
COMP 0 0 0 1 0
LOAD 0 0 0 0 1
STORE 0 0 0 0 1

Toute la difficulté tient dans la mise à jour des bits de disponibilité des ALUs. Une solution assez simple intègre des compteurs dans le processeur pour chaque bit de disponibilité. La technique part du principe que la durée de chaque instruction est connue et qu'on peut l'utiliser pour initialiser un compteur. Lors de l'émission d'une instruction sur une ALU, le compteur associé à l'ALU est initialisé avec le nombre de cycles de l'instruction. Il est décrémenté à chaque cycle d'horloge. Une fois qu'il atteint 0, le bit de disponibilité est mis à 0.

Une autre solution délègue cette tâche à l'unité de calcul. Dès qu'un calcul se termine sur l'ALU, l'unité de calcul prévient qu'elle est libre avec un signal FREE qu'elle envoie à l'unité d'émission. L'avantage est que la solution prend en charge naturellement le cas où les opérations sont de durée variable. Un exemple est celui d'un additionneur à saut de retenue, où la durée d'une addition dépend de la propagation de la retenue. Un autre exemple est celui de certains multiplieurs itératifs (qui font la multiplication en effectuant une suite de décalages et d'addition), qui fournissent le résultat en avance pour certaines opérandes. Ou encore les circuits diviseurs par décalage/soustraction qui s’arrêtent dès que le dividende partiel tombe à zéro. Et d'ailleurs, on va voit que cette solution marche assez bien pour les instructions mémoire.

La gestion des accès mémoire

modifier

Il faut noter que l'unité d'émission s'occupe aussi de l'unité mémoire. L'unité mémoire est un peu à part, comme on le verra plus tard, elle aura droit à plusieurs chapitres dédiés. Disons qu'il est commun de subdiviser un processeur avec un pipeline en trois sections : le front-end qui gére le chargement et le décodage des instructions, le cœur d'exécution qui s'occupe des registres et des unités de calcul, et l'unité mémoire proprement dite.

L'unité mémoire est pour le moment une sorte de boite noire, dont on ne détaillera pas l'intérieur, et dont seules les entrées et sorties nous sont accessibles. L'unité d'émission la considère d'ailleurs comme telle. L'unité mémoire prend en entrée des opérandes, à savoir les adresses à lire/écrire, éventuellement la donnée à écrire. En sortie, elle fournit la donnée lue et le registre de destination, dans lequel enregistrer la donnée. Les entrées et sorties sont reliées au réseau de contournement, ainsi qu'aux registres.

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

Gérer les accès mémoire demande, entre autres, de gérer la disponibilité de l'unité mémoire. Mais contrairement aux instructions de calcul, les accès mémoire ont pour défaut qu'ils ont un nombre de cycles variable. Sur la plupart des processeurs, l'accès au cache se fait rapidement, mais l'accès à la mémoire est très lent. Et l'unité d'émission doit gérer la disponibilité de l'unité mémoire.

Dans ce qui suit, nous partons du principe que l'unité mémoire ne peut faire qu'un seul accès mémoire à la fois. Hypothèse crédible, mais certains processeurs avec exécution dans le désordre fonctionnent autrement. L'unité mémoire prévient elle-même de sa disponibilité. Elle est reliée à l'unité d'émission via un fil MEM_UNIT_READY qui prévient qu'elle est disponible et peut accepter un nouvel accès mémoire. Si le bit MEM_UNIT_READY est à 1, l'unité mémoire est inutilisé, il n'y a pas d'accès mémoire en cours. Par contre, si ce bit est à 0, alors un accès mémoire est en cours et l'unité mémoire ne peut pas accepter de nouvel accès mémoire.

L'unité d'émission tient en compte de ce bit pour décider de l'émission des instructions en cours. De plus, elle l'utilise pour déterminer si le registre de destination est disponible. Quand le signal MEM_UNIT_READY passe de 0 à 1, le no/numéro du registre destination est disponible en sortie de l'unité mémoire. Il est alors envoyé à l'unité d'émission et le registre de disponibilité est mis à jour.

 
Implémentation du stall lors d'un accès mémoire dans l'unité d'émission

La solution la plus simple bloque tout le pipeline pendant un accès mémoire. Les instructions qui suivent l'accès mémoire sont mises en attente tant qu'il n'est pas terminé. Pour cela, dès que l'unité d'émission émet une micro-opération mémoire, elle émet des bulles de pipeline à sa suite tant que le signal MEM_UNIT_READY est à 0. Une fois l'accès mémoire terminé, l'unité d'accès mémoire met le signal MEM_UNIT_READY à 1, ce qui prévient l'unité d'émission. L'émission des instructions reprend.

Une autre solution, bien plus intuitive, est celle des lectures non-bloquante. L'idée est de laisser les unités de calcul travailler en parallèle de l'unité mémoire, 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'unité d'émission ne peut pas émettre de nouvel accès mémoire, mais peut émettre d'autres instructions si elles sont indépendantes. Rappelons que les dépendances pertinentes ici sont des dépendances de registre. Et une fois émises, seules les lectures ont des dépendances de registres. Voyons pourquoi.

Les lectures et écritures lisent des opérandes dans les registres et ne sont pas émises tant qu'il y a une dépendance avec ces registres. La technique des lectures non-bloquantes se préoccupe des dépendances avec les accès mémoire déjà émis. Une fois émises, les écritures n'écrivent dans aucun registre et n'ont donc pas de dépendances de registre. Par contre, les lectures écrivent la donnée lue dans un registre de destination, ce qui peut faire apparaitre des dépendances. En clair, il suffit de vérifier les dépendances avec les lectures pour détecter tout problème. C'est pour cela que cette technique porte le nom de 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.

L'implémentation est assez simple. L'unité d'émission vérifie les dépendances avec le registre de destination. Si l'unité d'émission émet une lecture, elle marque le registre de destination comme réservé. Puis, elle continue d’émettre des instructions si elles sont indépendantes de ce registre destination. Elle bloque l'émission de toute instruction qui l'utilise. Ainsi, les instructions qui ne lisent ou n'écrivent pas ce registre peuvent s'exécuter dans les unités de calcul. Les lectures non-bloquantes peuvent être vues comme une sorte d'exécution dans le désordre limitée aux lectures.