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

Un livre de Wikilivres.
Sauter à la navigation Sauter à la recherche

Il est maintenant temps de voir l'unité de contrôle. Pour rappel, celle-ci s'occupe du chargement des instructions et de leur interprétation. Elle contient l'unité de chargement et le séquenceur. La première charge l'instruction depuis la mémoire, tandis que le second commande le chemin de données.

L’étape de chargement[modifier | modifier le wikicode]

Au démarrage d'un programme, le program counter est initialisé à l'adresse de sa première instruction. Cette adresse n'est pas toujours l'adresse 0 : les premières adresses peuvent être réservées à des trucs assez importants, comme la pile ou le vecteur d'interruptions. Le program counter est mis à jour à la fin de chaque instruction pour le faire pointer sur l'adresse suivante. Dans la grosse majorité des cas, cette adresse est calculée par l'unité de calcul ou par un circuit dédié : le compteur ordinal.

Il faut noter que l'instruction chargée est parfois stockée dans un registre, pour mettre celle-ci a disposition du séquenceur. Ce registre est appelé le registre d'instruction. Cela arrive sur les processeurs ne disposant que d'une seule mémoire. Sans cela, pas d'accès mémoire aux données : le bus mémoire serait occupé en permanence par l'instruction chargée, et ne pourrait pas servir à charger ou écrire de données. Le bus de données serait donc inaccessible, rendant toute lecture ou écriture impossible. Les processeurs basés sur une architecture Harvard arrivent cependant à se passer de ce registre, vu que le bus des instructions est séparé du bus dédié aux données.

Registre d'instruction.

L'étape de chargement (ou fetch) est toujours décomposée en trois étapes :

  • l'envoi du contenu du program counter sur le bus d'adresse ;
  • la lecture de l'instruction sur le bus de données ;
  • la mise à jour du program counter (parfois faite en parallèle de la seconde étape).
Étape de chargement.

Mise à jour du program counter sans branchement[modifier | modifier le wikicode]

Sur certains processeurs assez rares, chaque instruction précise l'adresse de la suivante, qui est incorporée dans l'instruction. Mais sur la majorité des processeurs, on profite du fait que les instructions sont placées dans l'ordre d’exécution dans la RAM. En faisant ainsi, on peut calculer l'adresse de la prochaine instruction en ajoutant la longueur de l'instruction au contenu du 'program counter. L'adresse de l'instruction en cours est connue dans le program counter, reste à connaitre la longueur cette instruction. Lorsque les instructions ont toutes la même taille, ce calcul est simple : un simple additionneur suffit. Mais certains processeurs ont des instructions de taille variable, ce qui complique le calcul. Il y a plusieurs solutions à ce problème.

La plus simple consiste à indiquer la longueur de l'instruction dans une partie de l'opcode ou de la représentation en binaire de l'instruction.

Une autre solution consiste à charger l'instruction byte par byte, par morceaux de taille fixe. Ceux-ci sont alors accumulés les uns à la suite des autres dans le registre d'instruction, jusqu'à ce que le séquenceur détecte l'obtention d'une instruction complète. Le seul défaut de cette approche, c'est qu'il faut détecter quand une instruction complète à été chargée : un nouveau circuit est requis. Une solution similaire permet de se passer d'un registre d'instruction, en transformant le séquenceur en circuit séquentiel. Les bytes sont chargés à chaque cycle, faisant passer le séquenceur d'un état interne à un autre à chaque mot mémoire. Tant qu'il n'a pas reçu tous les mots mémoire de l'instruction, chaque mot mémoire non terminal le fera passer d'un état interne d'attente à un autre. S'il tombe sur le dernier mot mémoire d'une instruction, il rentre dans un état de décodage.

Et enfin, il existe une dernière solution, qui est celle qui est utilisée dans les processeurs haute performance de nos PC : charger un bloc de mots mémoire qu'on découpe en instructions, en déduisant leurs longueurs au fur et à mesure. Généralement, la taille de ce bloc est conçue pour être de la même longueur que l'instruction la plus longue du processeur : on est sûr de charger obligatoirement au moins une instruction complète, et peut-être même plusieurs. Dit autrement, les instructions doivent être alignées en mémoire (relisez le chapitre sur l'alignement des données en mémoire si besoin). Cette solution pose quelques problèmes : il se peut qu'on n'ait pas une instruction complète lorsque l'on arrive à la fin du bloc, mais seulement un morceau. On peut se retrouver avec des instructions à cheval entre deux blocs.

Instructions non alignées.

Dans ce cas, on doit décaler le morceau de bloc pour le mettre au bon endroit (au début du registre d'instruction), et charger le prochain bloc juste à coté. On a donc besoin d'un circuit qui décale tous les bits du registre d'instruction, couplé à un circuit qui décide où placer dans le registre d'instruction ce qui a été chargé, avec quelque circuits autour pour configurer les deux circuits précédents.

Décaleur d’instruction.

Unité de chargement avec branchements[modifier | modifier le wikicode]

Lors d'un branchement, l'adresse de destination du branchement est copiée dans le program counter. Il faut alors court-circuiter l'adresse calculée par le compteur ordinal si le branchement s’exécute. Pour les branchements directs, l'adresse de destination est fournie par le séquenceur. Pour les branchements indirects, il suffit de relier le program counter au bus interne au processeur, et de lire le registre voulu.

Unité de chargement qui gère les branchements directs.

Pour prendre en compte les branchements relatifs, on a encore deux solutions : réutiliser l'ALU pour calculer l'adresse, ou rajouter un circuit qui fait le calcul.

Unité de chargement qui gère les branchements relatifs.

Le séquenceur[modifier | modifier le wikicode]

Comme on l'a vu plus haut, le chemin de données est rempli de composants à configurer d'une certaine manière pour exécuter une micro-instruction. Mais cela demande de déduire, pour l'instruction à éxecuter, quelles sont les micro-opérations à exécuter et dans quel ordre. C'est le rôle de l'unité de décodage d'instruction, une portion du séquenceur qui « décode » l'instruction. Ce séquenceur va traduire une instruction en suit de micro-opération et configurer le chemin de données pour effectuer chaque micro-opération. Pour configurer le chemin de données, il va envoyer ce qu'il faut sur l'entrée de sélection de l'unité de calcul, les entrées du banc de registres, ou les circuits du bus du processeur. On appelle ces bits des signaux de commande. Lorsque nos unités de calcul (ou d'autres circuits du processeur) reçoivent un de ces signaux de commande, elles sont conçues pour effectuer une action précise et déterminée.

Unité de décodage d'instruction

Séquenceurs câblés[modifier | modifier le wikicode]

Il existe des processeurs dans lesquels chaque instruction est décodée par un circuit combinatoire ou séquentiel : on parle de séquenceur câblé. Plus le nombre d'instructions à câbler est important, plus le nombre de portes utilisées pour fabriquer le séquenceur augmente. Autant dire que les processeurs CISC n'utilisent pas trop ce genre de séquenceurs et préfèrent utiliser des séquenceurs différents. Par contre, les séquenceurs câblés sont souvent utilisés sur les processeurs RISC, qui ont peu d'instructions, pour lesquels la complexité du séquenceur et le nombre de portes est assez faible et supportable.

Sur certains processeurs assez rares, toute instruction s’exécute en une seule micro-opération (chargement inclus) et donc en un seul cycle d'horloge : un accès mémoire prendra autant de temps qu'une addition, ou qu'une multiplication, etc. Dans ces conditions, le séquenceur se résume à un simple circuit combinatoire. La durée d'un cycle d'horloge est calée sur l'instruction la plus lente, diminuant fortement les performances. Mais sur la majorité des processeurs, les instructions s’exécutent en plusieurs micro-opérations. Pour enchainer les micro-opérations, le séquenceur doit savoir à quelle micro-opération il en est, et mémoriser cette information dans une mémoire interne. En conséquence, ces séquenceurs câblés sont des circuits séquentiels. Ces séquenceurs doivent éviter de changer d'instruction à chaque cycle : le processeur doit autoriser ou interdire les modifications du program counter tant qu'il est en train de traiter une instruction.

Commande de la mise à jour du program counter.

Séquenceur microcodé[modifier | modifier le wikicode]

Créer des séquenceurs câblés est très compliqué quand le processeur doit gérer un grand nombre d'instructions machine. Pour limiter la complexité du séquenceur, on peut ruser afin de remplacer le séquenceur par quelque chose de moins complexe. Au lieu de déterminer à l’exécution la suite de micro-opérations à exécuter, on va la pré-calculer dans une mémoire ROM (pour chaque instruction) : cela donne des séquenceurs microcodés. Cela a un avantage : remplir une mémoire ROM est beaucoup plus simple à faire que créer un circuit combinatoire.

Un séquenceur microcodé contient donc une mémoire ROM : le control store. Celui-ci stocke, pour chaque instruction microcodée, la suite de micro-opérations équivalente. Le contenu du control store s'appelle le microcode. Parfois, le control store est une EEPROM : on peut changer son contenu pour corriger des bugs ou ajouter des instructions. Pour retrouver la suite de micro-opérations correspondante, le séquenceur 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 la suite de micro-opérations correspondante. La micro-opération est parfois recopiée dans un registre, le registre de micro-opération, qui est aux micro-opérations ce que le registre d'instruction est aux instructions machine : il sert à stocker une micro-opération pour que le séquenceur puisse décoder celle-ci.

Control store.

Il existe plusieurs sous-types de séquenceurs microcodés, qui se distinguent par la façon dont sont stocké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. Avec un microcode vertical, il faut traduire les micro-opérations en signaux de commande à l'aide d'un séquenceur câblé. Un séquenceur microcodé utilisant un microcode vertical est divisé en deux parties : un microséquenceur, et une unité de décodage câblée pour décoder les micro-opérations en signaux de commandes. Son avantage est le faible nombre de bits utilisé pour chaque micro-opération : il n'est pas rare que les instructions d'un microcode horizontal fassent plus d'une centaine de bits !

Dans tous les cas, 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 se charge de l’exécution des micro-opérations les unes après les autres, dans l'ordre demande. Ce circuit est l'équivalent du circuit de chargement, mais pour les micro-opérations. Un séquenceur microcodé peut même gérer des micro-instructions de branchement, qui précisent la prochaine micro-instruction à exécuter : on peut faire des boucles de micro-opérations, par exemple.

Dans le cas le plus rare, chaque micro-instruction va stocker l'adresse de la micro-instruction suivante.

Microcode sans microséquenceur.

Pour gérer les micro-branchements, il faut rajouter une seconde adresse : celle de la destination d'un éventuel branchement. Cette adresse est rajoutée pour toutes les instructions, vu que toutes les micro-opérations ont la même taille. Elle n'est simplement pas prise en compte si la micro-opération n'est pas un micro-branchement.

Branchements avec microcode horizontal sans microséquenceur.

Mais dans la majorité des cas, le séquenceur contient un équivalent du program counter pour le microcode : le registre d’adresse de micro-opération, couplé à un microcompteur ordinal. On peut modifier ce microcompteur ordinal de façon à permettre les branchements entre micro-opérations, d'une manière identique à celle vue pour l'unité de chargement.

Microcode avec un microséquenceur.
Branchements avec microcode horizontal avec microséquenceur.

Séquenceurs hybrides[modifier | modifier le wikicode]

Les séquenceurs hybrides sont un compromis entre séquenceurs câblés et microcodés : une partie des instructions est décodée par une partie câblée, et l'autre par une partie microcodée. Cela évite de câbler une partie du séquenceur qui prendrait beaucoup de portes pour décoder des instructions complexes, généralement rarement utilisées, tout en gardant un décodage rapide pour les instructions simples, souvent utilisées.