Aller au contenu

Fonctionnement d'un ordinateur/L'unité de contrôle

Un livre de Wikilivres.

Pour rappel, les instructions se font en plusieurs étapes, appelées micro-opérations. Pour chaque instruction, il faut déduire quelles sont les micro-opérations à exécuter et dans quel ordre. Mais l'instruction chargée depuis la mémoire ne précise pas les micro-opérations à faire, elle se contente juste de dire quelle opération effectuer et sur quels opérandes. Le processeur doit donc traduire l'instruction en une séquence de micro-opérations, en une séquence de signaux de commandes adéquats. C'est le rôle de l'unité de décodage d'instruction, une portion du processeur qui « décode » l'instruction.

Unité de décodage d'instruction

Une micro-opération configure le chemin de donnée d'une manière bien précise, afin de faire une opération de base : copie entre registres, accès mémoire, opération sur l'ALU. Pour cela, il faut configurer l'ALU pour qu'elle fasse l'opération adéquate, configurer le banc de registre pour lire /écrire les bons registres, etc. La micro-opération envoie des signaux de commande adéquats au chemin de données. Pour simplifier, une micro-opération est encodée en concaténant les signaux de commande pour l'ALU, ceux pour les registres, pour l'unité mémoire, etc. Chaque micro-opération encode les signaux de commande à destination du chemin de données.

Micro-opération, encodage en binaire
Signaux de commande pour l'ALU Signaux de commande pour les registres Signaux de commande pour l'unité d'accès mémoire Autres signaux de commande

Il existe des processeurs assez rares où chaque instruction machine est une micro-opération. Son encodage précise directement les signaux de commande, pas besoin d'une unité de décodage d'instruction. De telles architectures sont appelées des architectures actionnées par déplacement. Elles feront l'objet d'un chapitre dédié, nous allons les mettre de côté pour le moment et nous concentrer sur des architectures plus courantes.

Les séquenceurs câblés et microcodés

[modifier | modifier le wikicode]

Pour un même jeu d'instruction, des processeurs de marque différente peuvent avoir des séquenceurs différents. Les différences entre séquenceurs sont nombreuses, une partie étant liée à des optimisations plus ou moins sophistiquées du décodage. Mais l'une d'entre elle permet de distinguer deux types purs de séquenceurs, sur un critère assez pertinent. La distinction se fait sur la nature du séquenceur, sur le circuit de décodage utilisé.

Le séquenceur est un circuit séquentiel, c’est-à-dire qu'il contient un circuit combinatoire et des registres. Or, nous avons vu dans les chapitres précédents que tout circuit combinatoire peut être remplacé ainsi par une ROM avec le contenu adéquat. Et le circuit combinatoire dans le séquenceur ne fait pas exception à cette règle. Le circuit combinatoire peut être implémenté de trois grandes manières différentes.

  • La première méthode est d'utiliser un circuit combinatoire proprement dit, construit avec des portes logiques, en utilisant les méthodes du chapitre sur les portes logiques.
  • La seconde remplace ce circuit par une mémoire ROM dans laquelle on écrit la table de vérité du circuit.
  • La troisième solution est une solution intermédiaire qui utilise un circuit dit PLA (Programmable Logic Array).

Il y a donc un choix à faire : est-ce le séquenceur incorpore un circuit combinatoire ou une mémoire ROM ? Cela permet de distinguer les séquenceurs câblés, basés sur un circuit combinatoire/séquentiel, et les séquenceurs microcodés, basés sur une mémoire ROM. Les deux ont évidemment des avantages et des inconvénients différents, comme nous allons le voir.

Les séquenceurs câblés

[modifier | modifier le wikicode]

Si les instructions sont décodées par un assemblage de portes logiques et de registres, on parle de séquenceur câblé. Plus le nombre d'instructions est important, plus un séquenceur câblé est compliqué à concevoir par rapport à ses alternatives. La complexité du séquenceur dépend aussi de la complexité des instructions machine. Autant dire que les processeurs CISC n'utilisent pas trop ce genre de séquenceurs et préfèrent utiliser des séquenceurs microcodés ou hybrides, alors que les séquenceurs câblés sont préférés sur les processeurs RISC.

L'implémentation du séquenceur

[modifier | modifier le wikicode]

Sur certains processeurs assez rares, toute instruction s’exécute en une seule micro-opération, ce qui fait que le séquenceur se résume alors à un simple circuit combinatoire. C'est très rare, car cela implique que toutes les instructions doivent se faire en moins d'un cycle d'horloge. Pour cela, la durée d'un cycle d'horloge doit se caler sur l'instruction la plus lente : un accès mémoire prendra autant de temps qu'une addition, ou qu'une multiplication, etc. Ensuite, il faut que le processeur soit une architecture Harvard, afin de charge l'instruction tout en accédant aux données en parallèle, le tout en un seul cycle d'horloge processeur.

Séquenceur combinatoire

Sur les autres processeurs, il y a des instructions qui demandent d’exécuter une suite de micro-opérations. Pour cela, le séquenceur devient un circuit séquentiel, qui intègre un registre/compteur. La présence de ce registre s’explique par le fait que le séquenceur a besoin de savoir à quelle micro-opération il en est, information qui est mémorisée dans un registre.

Séquenceur séquentiel

Dans le cas le plus simple, le séquenceur est basé sur un simple compteur couplé à un circuit combinatoire. Le compteur mémorise à quelle micro-opération il en est, en lui attribuant un numéro : s'il en est à la première, seconde, troisième micro-opération, etc. Le compteur est incrémenté à chaque micro-opération réussie (les accès mémoires peuvent prendre plusieurs cycles pour une seule micro-opération, si le CPU doit attendre la RAM). Il est réinitialisé quand l'instruction se termine, à savoir quand le compteur a atteint le nombre de micro-opérations adéquat pour exécuter l'instruction.

Le compteur n'est pas forcément un compteur normal, qui stocke une valeur en binaire. Il s'agit souvent d'un compteur basé un registre à décalage, appelé un compteur one-hot, ou encore un compteur en anneau. La raison est que les compteurs en anneau sont très rapides et utilisent peu de circuits, sans compter qu'ils permettent de se passer de comparateur pour déterminer la valeur du compteur. Leur seul défaut est que les économies en portes logiques sont contrebalancées par un plus grand nombre de bascules, qui est cependant acceptable si le compteur encode peu de valeurs. Si on veut un séquenceur qui fonctionne rapidement, en moins d'un cycle d'horloge, c'est la meilleure solution qui soit.

En combinant le compteur avec l'opcode, le séquenceur détermine quel est la micro-opération à effectuer. Pour être plus précis, un circuit combinatoire intégré au séquenceur prend en entrée le compteur et l'opcode de l'instruction machine, puis fournit en sortie la micro-opération adéquate. Dans son implémentation la plus simple, ce circuit combinatoire est composé de deux sous-circuits : un décodeur et une "matrice" de portes logiques. Le décodeur prend en entrée l'opcode et a une sortie pour chaque instruction possible, ce qui fait qu'on l'appelle le décodeur d'instruction. La matrice de portes prend en entrée les sorties du décodeur et le compteur, et sort les signaux de commande adéquats. Pour chaque instruction et chaque valeur de compteur, elle sort les signaux de commande correspondant à la micro-opération adéquate.

Un exemple est illustré ci-dessous. L'exemple est celui de l'exécution d'une instruction qui charge une donnée dans le registre dit accumulateur d'un processeur à accumulateur (qui n'a qu'un seul registre, le dit accumulateur). Le tout se fait en 6 cycles, dont 4 servent à gérer le chargement de l'instruction et le program counter.

  • Le premier cycle copie le program counter dans le registre d’interfaçage pour les adresses.
  • Le second cycle lance une lecture, la donnée lue est sur le bus de données à la fin du cycle.
  • Le troisième copie l'instruction lue dans le registre d’interfaçage pour les données et dans le registre d'instruction, et incrémente le program counter en parallèle.
  • Le quatrième copie l'adresse à lire dans le registre d’interfaçage d'adresse.
  • Le cinquième lit la donnée à lire depuis la mémoire.
  • Le sixième copie la donnée lue du registre d’interfaçage dans l'accumulateur.
Implémentation de la matrice de portes d'un séquenceur câblé. Les sorties du décodeur sont à gauche, le compteur (one hot) est en haut, les signaux de commandes sont émis vers le bas.

Pour résumer, un séquenceur câblé est composé d'un compteur de micro-opération, d'un décodeur d'instruction et d'une matrice de portes logiques. Dans le schéma précédent, vous voyez que l'usage d'un compteur one hot facilite l'implémentation de la matrice de portes logiques.

La détermination de la fin d'une instruction

[modifier | modifier le wikicode]

Notons que le compteur interne au séquenceur est aussi utilisé pour déterminer quand une instruction se termine. Quand une instruction se termine, le processeur doit faire deux choses : réinitialiser le compteur du séquenceur, et surtout : incrémenter le program counter pour passer à l'instruction suivante. Pour cela, on ajoute un circuit combinatoire qui détermine si l'instruction en cours est terminée. Une instruction se termine quand la dernière micro-opération est atteinte, à savoir qu'une instruction qui se termine à la énième micro-opération se termine quand le compteur atteint N. Par exemple, pour une instruction de multiplication de 6 cycles d'horloge, le décodeur sait que l'instruction est terminée le compteur atteint 5 (signe qu'il en est à sa sixième micro-opération, soit la dernière). Le circuit combinatoire qui détermine si l'instruction est terminée est donc trivial : il associe une table qui attribue pour chaque opcode le numéro de la dernière micro-opération, et un comparateur qui vérifier si le compteur a atteint cette valeur.

Une manière de faire plus simple est d'utiliser un décompteur, qui est décrémenté à chaque micro-opération exécutée, et de l'initialiser avec le nombre de micro-opérations de l'instruction exécutée. L’instruction est alors terminée quand le compteur atteint zéro. Ce faisant, le circuit qui détecte la fin d'une instruction est terriblement simple, sans compter qu'il gère naturellement le cas où les instructions n'ont qu'une seule micro-opération. Mais cela n'élimine pas le circuit qui détermine le nombre de cycles d'une instruction, car celui-ci sert pour initialiser le compteur. Cette solution n'est pas toujours utilisée, pour des raisons assez diverses, notamment le fait qu'elle se marie assez mal avec diverses techniques d'optimisation.

Les deux techniques précédentes fonctionnent bien à condition qu'une instruction machine corresponde toujours à la même séquence de micro-opérations. Mais ce n'est pas toujours le cas et la séquence exacte peut différer selon l'état du processeur. Le cas classique est celui des accès mémoires, où le processeur doit attendre que la donnée demandée soit lue ou écrite. Comme autre exemple, certaines étapes/micro-opérations peuvent être facultatives et ne s’exécuter que sous certaines conditions. Pensez par exemple au cas des instructions à prédicats ou des branchements. Mais on peut avoir la même chose avec des instructions de multiplication ou de division, pour lesquelles le calcul peut être plus rapide avec certains opérandes.

Dans ce cas, le compteur doit pouvoir sauter certaines micro-opérations et passer par exemple de la deuxième micro-opération à la dixième directement. Et cela demande d'ajouter quelques circuits combinatoires pour cela. Par exemple, le décodeur peut incorporer une sortie pour préciser le numéro de la micro-opération suivante, ce numéro servant à réinitialiser le registre du compteur. Le séquenceur prend en entrée le compteur, l'opcode de l'instruction, éventuellement d'autres entrées, et fournit en sortie : les signaux de commande, et le prochain état du compteur. Ou alors, le décodeur d'instruction dit de combien il faut sauter de micro-opération, de combien il faut augmenter le compteur.

Les séquenceurs microcodés

[modifier | modifier le wikicode]

Pour limiter la complexité du séquenceur, les concepteurs de processeurs ont inventé les séquenceurs microcodés. L'idée derrière ces séquenceurs microcodés est que, pour chaque instruction, la suite de micro-opérations à exécuter est pré-calculée et mémorisée dans une mémoire ROM, au lieu d'être déterminée à l’exécution par un circuit combinatoire. La mémoire ROM qui stocke la suite de micro-opérations équivalente pour chaque instruction microcodée s'appelle le control store, tandis que son contenu s'appelle le microcode.

Par abus de langage, nous parlerons parfois de microcode pour désigner la suite de microinstructions correspondant à une instruction machine. Nous parlerons alors de microcode de l'addition pour désigner la suite de microinstructions correspondant à l'instruction machine de l'addition. Faire cette petite erreur rendra la lecture de cette section beaucoup plus fluide.

Les séquenceurs microcodés étaient surtout utilisés sur les architectures CISC, celles avec un jeu d'instruction étoffé et beaucoup de modes d'adressages différents. Leur grand nombre d'instructions favorisait un microcode. De plus, le budget en transistor de ces processeur était assez limité, ce qui fait que ces opérations aujourd'hui banales n'avaient pas leur propre circuit et étaient émulées en microcode.

Les premiers microprocesseurs 16 bits utilisaient souvent le microcode pour implémenter des instructions comme la multiplication et la division. Un exemple est le 8086 d'Intel, qui n'avait pas de circuit multiplieur/diviseur. À la place, il émulait la multiplication avec une série d'additions et de décalages, et la division avec des soustractions/décalages. Les processeurs de ce type utilisaient un microcode pour beaucoup d'instructions, pas seulement la multiplication et la division. En conséquence, ajouter des instructions dans un microcode "existant" coutait moins cher que d'ajouter un circuit multiplieur.

Un autre exemple d'utilisation du microcode est celui des premiers processeurs capables d'effectuer des calculs flottants. Sur les premiers processeurs de ce type, il n'y avait pas de FPU, pas de circuits pour les calculs flottants. Les instructions flottantes étaient en réalité émulées par des calculs entiers : chaque instruction flottante était convertie en interne en une suite d'instructions entières qui émulaient l'instruction voulue. Pour cela, les instructions flottantes étaient microcodées. De nos jours, les processeurs contiennent des circuits de calcul flottant, ce qui fait que les instructions ne sont plus émulées sauf pour quelques-unes.

Les séquenceurs micro-codés sont plus simples à concevoir et simplifient beaucoup le travail des concepteurs de processeurs. L'usage du microcode permet aussi d'ajouter des instructions facilement, en modifiant le microcode, sans pour autant modifier en profondeur le processeur. En contrepartie, un séquenceur microcodé utilise plus de portes logiques, vu qu'une ROM est un circuit gourmand en portes logique.

En théorie, les instructions microcodées peuvent être plus rapides que leur équivalent logiciel, à savoir une instruction émulée par une suite d'instructions machines. Le microcode peut être optimisé de manière à mieux utiliser les ressources internes au processeur. Mais force est de constater que ces opportunités d’optimisation étaient rares dans la réalité. Mais cela n'était pas l'intérêt principal, car les architectures CISC qui privilégiaient la taille du programme - la code size. L'usage d'un microcode n’a plus trop d'intérêt de nos jours, et surtout pas sur les architectures RISC qui se contentent d'un séquenceur câblé.

Le control store

[modifier | modifier le wikicode]

La caractéristique principale du control store est sa capacité, qui est souvent assez petite. La capacité du control store dépend non seulement du nombre de micro-instructions qu'il contient, mais aussi de la taille de ces dernières. Un byte du control store correspond à une micro-instruction, les exceptions étant très très rares. Et la taille des micro-instructions varie grandement d'un processeur à l'autre. Dans les grandes lignes, la différence principale tient beaucoup la manière dont sont encodées les micro-instructions. Il existe plusieurs sous-types de séquenceurs microcodés, qui se distinguent par la façon dont sont codées les micro-opérations.

  • Avec le microcode horizontal, chaque instruction du microcode encode directement les signaux de commande à envoyer aux unités de calcul. Vu Le grand nombre de signaux de commande, il n'est pas rare que les micro-opérations d'un microcode horizontal fassent plus d'une centaine de bits !
  • Avec un microcode vertical, les instructions du microcode sont traduites en signaux de commande par un séquenceur câblé qui suit le control store. Son avantage est que les micro-opérations sont plus compactes, elles font moins de bits. Cela permet d'utiliser un control store plus petit ou d'avoir un microcode plus important, au détriment de la complexité du séquenceur.

Un exemple de microcode vertical est le microcode du 8086, encore lui ! Pour ce qui est de la commande de l'ALU, le microcode envoie une commande abstraite qui est décodée par un circuit combinatoire (un PLA), pour obtenir les signaux de commande de l'ALU.

L'implémentation interne du control store ne suit pas forcément à la lettre l'organisation en byte. Pour faire comprendre ce que je veux dire, prenons l'exemple de l'Intel 8086, dont le control store contenait 512 bytes/microinstructions de 21 bits chacune. Le control store n'était pas une ROM de 512 lignes et de 21 colonnes, comme on pourrait s'y attendre. Les dimensions 512 par 21 donneraient une ROM très allongée, rendant son placement sur la puce de silicium peu pratique. À la place, elle regroupait 4 bytes par ligne, ce qui donnait 84 lignes et 128 colonnes.

L'optimisation du microcode

[modifier | modifier le wikicode]

Le control store a souvent une capacité très faible, même pour une mémoire ROM. Une ROM prend de la place, ce qui fait que les concepteurs de processeurs préfèrent utiliser une ROM assez petite. Néanmoins, malgré la petitesse des ROM de l'époque, il arrivait souvent que le control store contienne des vides, des bytes inoccupés. Cela arrive si le microcode n'a pas une taille égale à une puissance de deux. Par exemple, si l'on a un microcode qui occupe 120 bytes, on doit utiliser un control store de 128 bytes, ce qui laisse 8 bytes vides. On pourrait croire que les vides sont placés à la fin du control store, mais il est parfois préférable de disperser les vides dans le control store, afin de simplifier les circuits adossés au microcode, ce que nous allons voir dans ce qui suit.

Pour les concepteurs de processeurs, une difficulté majeure est de faire rentrer le microcode dans le control store. C'est encore un problème à l'heure actuelle, mais ce l'était encore plus sur les architectures anciennes, qui devaient faire avec des ROM limitées qu'actuellement. De plus, sur les anciennes architectures CISC, le grand nombre d'instructions recherchait se mariait mal à la petite capacité des mémoires ROM de l'époque. Les concepteurs de processeurs devaient ruser pour faire rentrer un microcode souvent complexe dans une petite ROM. Diverses optimisations étaient possibles.

La première optimisation de ce genre consiste à gérer des fonctions/sous-programmes/routines logicielles dans le microcode. Pour cela, les circuits en charge du microcode géraient l’exécution de fonctions dans le microcode, avec des registres pour l'appel de retour, des microinstructions pour faire des branchements dans le microcode et tout ce qui va avec. Mais le tout était généralement simplifié et rares étaient les processeurs qui incorporaient une pile d'appel complète pour le microcode. Beaucoup se limitaient à ajouter un registre pour l'adresse de retour, quelques instructions de branchement interne au microcode, et guère plus.

Un exemple assez intéressant est celui du processeur Intel 8086, dont le microcode contient une sous-routine pour gérer chaque mode d'adressage. Sans optimisations, il faudrait un microcode par instruction et par mode d'adressage. Par exemple, le microcode pour une addition en mode d'adressage immédiat n'est pas la même que pour une instruction d'addition en mode d'adressage direct. Cependant, elles partagent un même cœur qui s'occupe de l'addition et de la gestion de l'accumulateur, même si la gestion des opérandes est totalement différente suivant le mode d'adressage. Pour éliminer cette redondance, le microcode du 8086 délègue la gestion des modes d'adressages et des opérandes à des sous-programmes spécialisés, une par mode d'adressage.

La seconde optimisation est de réduire la taille des micro-instructions en jouant sur leur encodage. L'usage d'un microcode vertical est une première solution. Décoder certaines instructions simples sans passer par le microcode en est une autre, et elle donne les séquenceurs hybrides dont nous parlerons dans la suite du chapitre. Mais d'autres techniques sont possibles, comme le fait de déporter une partie du décodage en-dehors du control store, dans des circuits logiques séparés.

Un bon exemple de cela est celui de l'Intel 8086, encore lui, sur lequel beaucoup d'instructions existaient en deux exemplaires : une version 8 bits et une version 16 bits. Il n'y avait pas de microcode séparé pour les deux versions, mais un seul microcode qui s'occupait autant de la version 8 bits que de la version 16 bits de l'instruction. La différence entre les deux se faisait au niveau du bus interne du processeur. Un bit de l'instruction machine indiquait s'il s'agissait d'une version 8 ou 16 bits et ce bit était transmis à la machinerie du bus interne, sans passer par le microcode.

Les circuits d’exécution du microcode

[modifier | modifier le wikicode]

Le processeur doit trouver un moyen de dérouler les micro-instructions les unes après les autres, ce qui est la même chose qu'avec des instructions machines. Le micro-code est donc couplé à un circuit qui de l’exécution des micro-opérations les unes après les autres, dans l'ordre. Ce circuit est l'équivalent du circuit de chargement, mais pour les micro-opérations. Pour cela, il y a deux méthodes, que voici.

La première méthode fait que chaque micro-instruction contient l'adresse de la micro-instruction suivante. Avec cette méthode, on peut disperser une suite de microinstructions dans le control store, au lieu de garder des microinstructions consécutives. L'utilité de cette méthode n'est pas évidente, mais elle deviendra plus claire dans la section suivante.

Microcode sans microséquenceur.

La seconde méthode fait que le séquenceur contient un équivalent du program counter pour le microcode. On trouve ainsi un micro-séquenceur qui regroupe un registre d’adresse de micro-opération et un micro-compteur ordinal. Le registre d’adresse de micro-opération est initialisé avec l'opcode de l'instruction à exécuter, qui pointe vers la première micro-instruction. Le micro-compteur ordinal se charge d'incrémenter ce registre à chaque fois qu'une micro-instruction est exécutée, afin de pointer sur la suivante.

Microcode avec un microséquenceur.

Un séquenceur microcodé peut même gérer des micro-instructions de branchement, qui précisent la prochaine micro-instruction à exécuter. Grâce à cela, on peut faire des boucles de micro-opérations, par exemple. Pour gérer les micro-branchements, il faut rajouter la destination d'un éventuel branchement dans les micro-instructions de branchement. La taille des micro-instructions augmente alors, vu que toutes les micro-opérations ont la même taille.

Voici ce que cela donne pour les microcodes avec un microcompteur ordinal. On voit que l'ajout des branchements modifie le microcompteur ordinal de façon à permettre les branchements entre micro-opérations, d'une manière identique à celle vue pour l'unité de chargement.

Branchements avec microcode horizontal avec microséquenceur.

Voici ce que cela donne pour les microcodes où chaque micro-instruction contient l'adresse de la suivante :

Branchements avec microcode horizontal sans microséquenceur.

Il est possible de créer des fonctions/sous-programmes/sous-routines dans le microcode, grâce à ces micro-branchements et en ajoutant un registre pour gérer l'adresse de retour.

Localiser la première microinstruction à exécuter dans le control store

[modifier | modifier le wikicode]

Un premier problème à résoudre avec un microcode, est de localiser la suite de micro-instructions à exécuter. Si l'on veut exécuter une instruction machine, le microcode doit trouver le début de la suite de microinstruction dans le microcode et démarrer l’exécution des microinstructions à partir de là. Pour le dire autrement, le séquenceur doit déterminer, à partir de l'opcode, quelle est l'adresse de départ dans le control store. Pour cela, il y a plusieurs solutions.

La première solution fait une traduction de l'opcode vers l'adresse de départ, en utilisant un circuit combinatoire et/ou une mémoire ROM. Elle a l'inconvénient de complexifier le processeur, dans le sens où on doit ajouter des circuits en plus. De plus, le circuit ou la ROM ajoutés mettent un certain temps avant de donner leur résultat, ce qui ralentit quelque peu le décodage des instructions. L'avantage principal est que l'on peut utiliser facilement un microséquenceur basique et placer les microinstructions les unes à la suite des autres dans le control store. Cette technique s'utilise aussi bien avec un micro-séquenceur que sans. Dans les faits, elle s'utilise de préférence avec un micro-compteur ordinal. L'usage de ce dernier réduit fortement la taille du control store, ce qui compense le fait de devoir ajouter des circuits pour faire la traduction opcode -> adresse.

Control store adressé par predecodage de l'opcode

L'autre solution considère l'opcode de l'instruction microcodée comme une adresse : le control store est conçu pour que cette adresse pointe directement sur le début de la suite de micro-opérations correspondante, la première micro-instruction de cette suite. Du moins, c'est le principe général, mais un détail vient mettre son grain de sel : un control store utilise systématiquement des adresses plus grandes que l'opcode. Ce qui fait qu'il faut rajouter des bits à l'opcode pour obtenir l'adresse, on doit concaténer des zéros à l'opcode pour obtenir l'adresse finale. On fait alors face à deux choix : soit on met l'opcode dans les bits de poids faible de l'adresse, soit on la place dans les bits de poids fort. Les deux solutions ont des avantages et inconvénients différents.

Control store d'un microcode horizontal.

La première méthode place les opcodes dans les bits de poids faible et les zéros dans les bits de poids fort. Le défaut principal de cette méthode vient du fait que de nombreux opcodes ont des représentations binaires proches, ce qui fait que leurs adresses de départs sont proches dans le control store. Il n'y a alors pas assez d'espace entre les deux adresses de départ pour y placer une suite de microninstructions. En clair, cette méthode ne peut pas s'utiliser avec un micro-séquenceur. Par contre, elle se marie très bien avec un control store où chaque microinstruction contient l'adresse de la suivante. En faisant cela, l'opcode pointe vers l'adresse de départ, mais le reste de la suite de microinstructions est placé ailleurs dans le control store, dans des adresses qui ne correspondent pas à des opcodes. Les adresses de départ occupent donc le bas de la ROM du control store, alors que le haut de la ROM contient les suites de microinstructions et éventuellement des vides.

Control store adressé par l'opcode - opcode sur bits de poids faible

La seconde méthode met l'opcode dans les bits de poids fort de l'adresse et les zéros dans les bits de poids faible. En faisant cela, les adresses de départ sont dispersées dans le control store, elles sont séparées par des intervalles de taille de fixe. Cela garantit qu'il y a un espace fixe entre deux adresses de départ, dans lequel on peut placer une suite de microinstructions. Un bon exemple est celui du 8086, dont le microcode, très complexe, espace chaque instruction/opcode tous les 16 bytes, ce qui permet d'avoir 16 microinstructions par instruction machine. Son control store contenait 512 micro-instructions, 512 bytes, ce qui donne des adresses de 13 bits. Mais l'opcode occupait les 9 bits de poids fort de l'adresse de microcode, ce qui laissait 4 bits de poids faible libres. En conséquence, chaque instruction machine disposait de maximum 16 microinstructions consécutives.

L'avantage de cette méthode est que l'on peut utiliser un microséquenceur plus petit, avec un incrémenteur de plus petite taille. De plus, les adresses utilisées pour les branchements dans le microcode sont plus petites. Par exemple, le microcode du 8086, qui espacait ses microinstructions toutes les 16 bytes, avait un microséquenceur de 4 bits. Ce dernier contenait un incrémenteur de micro-program counter de 4 bits et non 13. De plus, les adresses utilisées pour les branchements dans le microcode ne faisaient que 4 bits, à savoir qu'il s'agissait de branchements relatifs. Tout cela rendait le microséquenceur beaucoup plus économe en circuits.

Cette solution a cependant pour défaut de laisser beaucoup de vides dans le control store. Le microcode de certaines instructions était assez court, d'autres avaient un microcode plus long. L'espace entre deux opcodes, entre deux adresses de départ, est fixe et se cale sur le microcode le plus long. En conséquence, le microcode de certaines instructions laisse des vides à sa suite. Si on sépare les adresses de départ par un espace assez court, alors les suites d'instructions trop longues ne rentrent pas, sauf en trichant. Par tricher, on veut dire que le microcode de ces instruction est découpé en morceaux et dispersé dans les vides du control store. L’exécution d'un microcode dispersé ainsi se fait normalement grâce aux microinstructions de branchement.

Control store adressé par l'opcode - opcode sur bits de poids fort

Pour comparer les trois méthodes, on peut comparer ce qu'il en est pour le remplissage du control store. Les deux premières méthodes remplissent le control store au mieux, alors que la dernière laisse des vides et disperse les suites de microinstructions dans le control store. Par contre, il faut aussi tenir compte d'autres paramètres. La première solution demande d'ajouter des circuits de traduction opcode -> adresse qui prennent de la place, pas les deux dernières solutions. Enfin, la deuxième solution impose de rallonger les bytes du control store, car on se prive de micro-séquenceur, ce qui n'est pas le cas des deux autres. Au final, comparer les trois solutions ne donne pas de gagnant absolu : tout dépend de l'implémentation du jeu d'instruction choisit, de son encodage, etc.

La mise à jour du microcode

[modifier | modifier le wikicode]

Parfois, le processeur permet une mise à jour du control store, ce qui permet de modifier le microcode pour corriger des bugs ou ajouter des instructions. L'utilisation principale est de corriger des bugs ou des problèmes de sécurité assez tordus. Il est fréquent que les processeurs aient des bugs matériels, présents à cause de défauts de conception parfois subtils. Les grands fabricants comme Intel et AMD documentent ces bugs dans leur documentation officielle. Une petite partie de ces bugs peuvent se corriger avec une mise à jour du microcode, et ils ne sont pas forcément dans le microcode lui-même. Un exemple serait la désactivation des instructions TSX sur les processeurs x86 Haswell, en 2014, qui ont été désactivées par une mise à jour du microcode, après qu'un bug de sécurité ait été découvert.

La mise à jour du microcode est rarement permanente. Une mise à jour permanente du microcode implique que le control store est une EEPROM ou une mémoire ROM reprogrammable, donc des mémoire très difficiles à mettre en œuvre dans les processeurs. Or, le control store doit être une mémoire extrêmement performante, capable de fonctionner à très haute fréquence, avec des temps d'accès minuscules, aux performances proches d'une SRAM. En réalité, le control store est mis à jour temporairement, et est réinitialisé à chaque boot de l'ordinateur, à chaque boot du processeur.

Pour cela, le control store est implémenté avec deux mémoires : une ROM qui contient le microcode originel, et une SRAM. Pour simplifier les explications, nous allons appeler ces deux mémoires la micro-ROM et la micro-RAM. Au démarrage de l'ordinateur, le microcode contenu dans la micro-ROM est copié dans la micro-RAM. Peu après l'allumage du processeur, le contenu de la micro-RAM peut être remplacé par un microcode mis à jours. Typiquement, le microcode corrigé est fourni soit par le BIOS, soit par le système d'exploitation. Les mises à jour de microcode sont généralement soumises à des mesures de sécurité drastiques intégrées au processeur. Par exemple, le microcode fournit par le fabricant est chiffré avec des clés connues seulement des fabricants de CPU, autres), et un microcode n'est chargé par le processeur que si la clé correspond.

Les microcodes réinscriptibles

[modifier | modifier le wikicode]

Il existe des processeurs dont le microcode est directement reprogrammable, accessible par le programmeur. Le programmeur peut écrire ce qu'il veut dans la micro-RAM, à sa guise. On peut ainsi changer le jeu d'instruction du processeur au besoin, afin d'ajouter des instructions utiles. Il s'agit de processeurs destinés à l'embarqué, qui doivent être conçus sur mesure, on ne trouve pas de systèmes de ce genre dans les PCs. Ils sont appelés des processeurs à microcode réinscriptible.

L'utilité est que les programmes peuvent disposer des instructions les plus adéquates pour leur fonction, ce qui réduit la taille du code (la mémoire prise par le programme exécutable) et facilite la programmation en assembleur. Ces deux avantages n'ont pas grand intérêt de nos jours. De plus, l'utilisation de cette technique demande un control store assez imposant, de grande taille, rarement rapide.

Par contre, cette fonctionnalité a de nombreux défauts. Si chaque programme peut changer à la volée le jeu d'instruction du processeur, cela peut mettre le bazar. Si un programme change le microcode, les programmes qui passent après lui doivent réinitialiser le microcode pour ne pas exécuter des instructions incorrectes. Cela peut aussi poser des problèmes de sécurité, les hackers étant doués pour utiliser ce genre de fonctionnalités à des fins malveillantes. Cependant, les processeurs de ce type sont utilisés pour l'embarqué, sur des systèmes où un seul programme s'exécute, sans accès à un réseau local, il n'y a donc pas de problèmes liés à des programmes malveillants ou des interactions entre programmes.

Un processeur de ce type est le GP1000 d'Imsys, qui a été décrit entres autres dans l'article "GP1000 Has Rewritable Microcode" du magazine Microprocessor report. Il disposait d'une micro-ROM de 36 kibioctets, couplées à une micro-RAM de 18 kibioctets. Une partie du microcode était donc réinscriptible : un tiers l'était, les deux autres tiers étaient dans une micro-ROM impossible à modifier. Il y avait donc un microcode permanent en micro-ROM et un microcode variable en en micro-RAM. Le microcode permanent contient un chargeur de microcode, qui recopie le microcode variable dans la micro-RAM, à l'allumage de l'ordinateur. Par la suite, le microcode variable est chargé avec une instruction machine dédiée.

Un point très original est que le GP1000 supporte l'exécution de plusieurs programmes à tour de rôle. Le microcode permanent contient de quoi gérer des interruptions, émuler des périphériques virtuels, mais aussi gérer la présence de plusieurs programmes en cours d'exécution. Les programmes s'exécutent à tour de rôle et le microcode décide qui s’exécute et pour combien de temps. Le microcode permanent contient donc une sorte de mini-système d'exploitation rudimentaire !

Au-delà ce ça, Imsys fournissait des microcodes près à l'emploi, qui pouvaient être chargés à la demande. L'un d'entre eux permettait d'implémenter la machine virtuelle Java directement dans le processeur. Pour information, la machine virtuelle Java standardise le langage machine d'un processeur fictif, précisément une machine à pile simple, qui peut en théorie être implémentée en matériel. C'est exactement ce que faisait l'un des microcodes près à l'emploi d'Imsys.

Les séquenceurs hybrides

[modifier | modifier le wikicode]

Les séquenceurs hybrides sont un compromis entre séquenceurs câblés et microcodés. Ils permettent de profiter des avantages et inconvénients des deux types de séquenceurs. Sur le principe, une partie des instructions est décodée par une partie câblée, et l'autre passe par le microcode. Typiquement, de tels séquenceurs sont très fréquents sur les architectures CISC, où ils permettent un décodage rapide pour les instructions simples, alors que les instructions complexes le sont par le microcode, plus lent.

L'organisation interne d'un séquenceur hybride varie grandement selon le processeur et le jeu d'instruction. Dans le cas le plus simple, on a un séquenceur câblé secondé par un séquenceur microcodé, les deux étant précédés par un circuit de prédécodage. Le circuit de prédécodage reçoit les instructions et les redirige soit vers le séquenceur câblé, soit vers le séquenceur microcodé. Les instructions les plus simples sont dirigées vers le séquenceur câblé, alors que les instructions complexes vont vers le microcode (généralement les instructions avec des modes d'adressage exotiques). Une solution intéressante est de décoder les instructions qui prennent un seul cycle dans un séquenceur câblé, alors que les instructions multicycles sont décodées par un séquenceur microcodé séparé.

Mais dans le cas général, la séparation en deux séquenceurs n'est pas évidente et on trouve un control store entouré de circuits câblés, avec certaines instructions qui n'ont pas besoin du microcode pour être décodées, d'autres qui passent par le microcode, d'autres qui sont décodé partiellement par microcode et partiellement par des circuits câblés. Notons que le microcode vertical n'est pas un séquenceur hybride, car toutes les instructions passent par le microcode. Par contre, un séquenceur hybride peut utiliser un microcode vertical, ce qui rend le séquenceur assez compliqué.

Sur les processeurs x86 modernes, on trouve plusieurs séquenceurs : plusieurs décodeurs câblés spécialisés, et un microcode séparé.

Le séquenceur hybride du 8086

[modifier | modifier le wikicode]

Un bon exemple de séquenceur de ce type est celui du processeur x86 8086 d'Intel, ainsi que ceux qui ont suivi. Le jeu d'instruction x86 est tellement complexe qu'il utilise un séquenceur hybride. Le séquenceur de l'Intel 8086 est organisé comme suit : un control store de 512 microinstructions (512 bytes) couplé à de nombreux circuit câblés, et une Group Decode ROM qui décide pour chaque instruction si elle est décodée par le séquenceur câblé ou le microcodé.

La mal-nommée Group Decode ROM est en réalité un petit circuit combinatoire un peu particulier (basé sur un PAL, composant proche d'une ROM), qui commande le séquenceur proprement dit. Il fournit 15 signaux qui configurent le séquenceur et disent si le décodage doit utiliser ou non le microcode. Il permet aussi de configurer le microcode pour gérer les différents modes d'adressage, ou encore de configurer les circuits câblés en aval du microcode. Sur ce processeur, les instructions qui s’exécutent en un seul cycle d'horloge sont décodés sans utiliser le microcode.

Le 8086 utilise une sorte de micro-code vertical pour commander l'ALU. Entre l'ALU et le microcode, on trouve un mini-décodeur, qui décode l'opération envoyée par le microcode en signaux de commande. C'est un simple circuit combinatoire (un PLA pour être précis). La raison est que l'ALU du 8086 est quelque peu complexe. Nous l'avions vu dans le chapitre sur les unités de calcul, l'ALU du 8086 est basée sur un additionneur à propagation de retenue, où deux portes logiques sont remplacées par une porte logique universelle. Il faut commander ces deux portes universelles pour obtenir l'opération voulue, ce qui demande pas mal de signaux de commande. Et pour économiser de la place dans le microcode, l'opération à faire est encodée sur plusieurs bits, qui sont décodés pour générer les signaux de commande.

Ce qui vient d'être dit est une simplification. En vérité, certaines instructions ne sont pas encodées dans le microcode. Pour ces instructions, l'opération est récupérée directement dans l'opcode de l'instruction. C'est le cas des opérations d'addition, soustraction, les opérations logiques, les décalages et autres opérations gérées naturellement par l'ALU. Pour elles, l'opération à faire est extraite de l'opcode, pas du microcode. Pour ces opérations, le microcode encode l'opération à exécuter sur l'ALU par une micro-instruction généraliste nommée XI. La micro-opération XI indique qu'il faut activer le multiplexeur.