Aller au contenu

Fonctionnement d'un ordinateur/Les composants d'un processeur

Un livre de Wikilivres.

Dans le chapitre sur le langage machine, on a vu le processeur comme une espèce de boite noire contenant des registres qui exécutait des instructions les unes après les autres. Mais on n'a pas encore vu ce qu'il y a dans la boite noire. Pour cela, nous allons attaquer la micro-architecture du processeur, ce qu'il y a à l'intérieur et comment il fait pour exécuter une instruction.

Les trois cycles d'une instruction.

Le but d'un processeur, c'est d’exécuter des instructions. Cela nécessite de faire quelques manipulations assez spécifiques et qui sont toutes les mêmes quel que soit l'ordinateur. Exécuter une instruction est un processeur en trois étapes :

  • Le processeur charge l'instruction depuis la mémoire : c'est l'étape de chargement (Fetch) ;
  • Ensuite, le processeur « étudie » la suite de bits de l'instruction et en déduit quelle est l'instruction à exécuter : c'est l'étape de décodage (Decode) ;
  • Enfin, le processeur exécute l'instruction : c'est l'étape d’exécution (Execute).*

Une quatrième étape d'interruption s'occupe des interruptions, précisément des interruptions matérielles et des exceptions. Elle est optionnelle, car de rares processeurs ne supportent pas les interruptions. Nous ne parlerons pas de l'étape d'interruption ici et allons nous concentrer sur les trois premières étapes. Il se trouve qu'elles sont réalisées chacune par un circuit séparé des autres.

La micro-architecture d'un processeur

[modifier | modifier le wikicode]

A l'intérieur du processeur, il y a un circuit dédié pour le chargement de l'instruction, un circuit dédié au décodage des instruction, et un autre pour leur exécution. Ils portent respectivement les noms d'unité de chargement, unité de décodage d'instruction, et de chemin de données.

  • Le chemin de données contient de quoi exécuter les instructions. Il regroupe des circuits de calcul, les registres, un circuit de communication avec la mémoire, et des interconnexions entre les circuits précédents.
  • L’unité de contrôle regroupe l'unité de chargement et le décodeur d'instruction. L'unité de chargement charge l'instruction depuis la mémoire, le décodeur/séquenceur commande le chemin de données. Les deux circuits sont séparés, mais ils communiquent entre eux pour gérer les branchements, pour charger les instructions au bon moment, etc.

Le chemin de données

[modifier | modifier le wikicode]

Pour effectuer des calculs, le processeur contient un circuit spécialisé : l'unité de calcul. De plus, le processeur contient des registres, ainsi qu'un circuit d'interface mémoire. Les registres, l'unité de calcul, et l'interface mémoire sont reliés entre eux par un ensemble d'interconnexions, afin de pouvoir échanger des informations. Ces interconnexions forment ce qu'on appelle le bus interne du processeur.

L'ensemble formé par ces composants s’appelle le chemin de données. Son nom vient du fait que les données circulent dans le chemin de données, dans le bus interne, l'unité de calcul, les registres et l'unité mémoire. Pour le dire autrement, il regroupe tous les circuits dans lesquels sont traités des données.

Chemin de données

L'unité de calcul, les registres et l'unité mémoire sont configurables. Par exemple, l'unité de calcul peut faire plusieurs opérations : addition, soustraction, opérations logiques, et bien d'autres. Mais il faut préciser quelle opération effectuer. Pour cela, l'ALU a une entrée de commande sur laquelle on précise l'opération à effectuer. L'opération est encodée en binaire, chaque opération a son propre numéro. Il s'agit d'un signal de commande, qui indique comment configurer l'unité de calcul pour faire ce qui est demandé.

Les registres et l'unité mémoire ont aussi leurs propres signaux de commande dédiés. Par exemple, les noms/numéros de registres sont des signaux de commande qui permettent de sélectionner les registres utilisés comme opérande ou pour enregistrer le résultat. De même, l'unité mémoire ne fonctionne que si on lui précise quelle est l'adresse à lire/écrire, et s'il faut faire une lecture ou une écriture : les deux sont des signaux de commande.

Signaux de commandes CPU

Le séquenceur : signaux de commande et micro-opérations

[modifier | modifier le wikicode]

Le chemin de données est en charge de l'exécution des instructions, au minimum (on verra qu'il peut être impliqué dans l'étape de chargement, dans quelques paragraphes). Intuitivement, exécuter une instruction revient à configurer le chemin de données de manière adéquate. Si on veut faire une addition entre un registre et une adresse mémoire, on configure l'unité de calcul pour faire une addition, on configure les registres de manière à sélectionner les registres opérande/destination, et on envoie l'adresse à l'unité mémoire.

Le décodeur d'instruction détermine quels sont les signaux de commande adéquats, à partir de l'instruction machine. Les signaux de commande des registres sont déterminés à partir des noms/numéros de registre encodés dans l'instruction, les signaux pour l'ALU sont déterminés à partir de l'opcode, etc. Le terme décodeur trahit le fait qu'il traduit une instruction machine en signaux de commandes séparés, qui se déduisent de l'instruction elle-même, de son encodage.

Intérieur d'un processeur

Mais cette explication ne marche que pour des instructions simples, qui, s'effectuent en une seule étape. Or, l'exécution d'une instruction complexe se fait en plusieurs étapes distinctes. Suivant l'instruction machine, le nombre d'étapes n'est pas le même. Voyons quelles instructions tendent à se faire une une ou plusieurs étapes, avec quelques exemples.

Commençons par l'exemple d'une instruction de lecture en mode d'adressage absolu. L'adresse à lire est envoyée à l'unité mémoire directement, le nom de registre est envoyé aux registres et basta. L’exécution de l'instruction se fait donc en une seule étape : la lecture proprement dite. Maintenant, voyons la même instruction, mais avec un mode d'adressage base + indice. Avec ce mode d'adressage, l'adresse doit être calculée en additionnant une adresse de base et d'un indice, les deux étant stockés dans des registres. En plus de devoir lire la donnée, l'instruction va devoir calculer l'adresse, dans l'unité de calcul. L'étape d’exécution s'effectue dorénavant en deux étapes : une étape de calcul d'adresse, suivie de la lecture en mémoire.

Bref, on voit bien que l’exécution d'une instruction s'effectue en plusieurs étapes distinctes, qui vont soit faire un calcul, soit échanger des données entre registres, soit communiquer avec la RAM. Chaque étape s'appelle une micro-opération, ou encore une micro-instruction. Les micro-opérations se résument à des opérations basiques : échange de données entre registres, calcul sur l'ALU, accès mémoire. Chaque micro-opération encode les signaux de commande à destination du chemin de données, pour le configurer et le commander. 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.

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 Signaux de commande autres

Toute instruction machine est équivalente à une suite de micro-opérations exécutée dans un ordre précis. Dit autrement, chaque instruction machine est traduite en suite de micro-opérations à chaque fois qu'on l’exécute. C'est le décodeur d'instruction qui transforme une instruction machine en une série de micro-opérations. Pour cela, le décodeur devient un circuit séquentiel. Il est parfois appelé le séquenceur, terme qui trahit bien le fait qu'il traduit une instruction machine en une séquence de micro-opérations.

Micro-operations

Certaines µinstructions font un cycle d'horloge, alors que d'autres peuvent prendre plusieurs cycles. Un accès mémoire en RAM peut prendre 200 cycles d'horloge et ne représenter qu'une seule µinstruction, par exemple. Même chose pour certaines opérations de calcul, comme des divisions ou multiplication, qui correspondent à une seule µinstruction mais prennent plusieurs cycles.

Typiquement, les instructions des processeurs RISC se font en une seule micro-opération, alors que les instructions complexes des processeurs CISC demandent plusieurs micro-opérations, particulièrement celles qui demandent de faire des accès mémoire ou qui utilisent des modes d'adressage complexes. Ce n'est pas une règle absolue, quelques instructions RISC se font en plusieurs micro-opérations, de même que les processeurs CISC ont des instructions simples qui se font en une seule micro-opération. Mais la corrélation est assez bonne. En conséquence, les processeurs RISC ont des décodeurs d'instruction très simples.

L'unité de chargement

[modifier | modifier le wikicode]

L'unité de chargement est placée avant le décodeur/séquenceur. Son rôle est de charger les instructions les unes après les autres, dans le bon ordre. Pour exécuter une suite d'instructions dans le bon ordre, le processeur doit savoir quelle est la prochaine instruction à exécuter : il doit donc contenir une mémoire qui stocke cette information. C'est le rôle du registre d'adresse d'instruction, aussi appelé program counter.

L'adresse de la prochaine instruction ne sort pas de nulle part : on peut la déduire de l'adresse de l'instruction en cours d’exécution par divers moyens plus ou moins simples. Généralement, on profite du fait que le programmeur/compilateur place les instructions les unes à la suite des autres en mémoire, dans l'ordre où elles doivent être exécutées. Ainsi, on peut calculer l'adresse de la prochaine instruction en ajoutant la longueur de l'instruction chargée au program counter.

Mais sur d'autres processeurs, chaque instruction précise l'adresse de la suivante. Ces processeurs n'ont pas besoin de calculer une adresse qui leur est fournie sur un plateau d'argent. Sur de tels processeurs, chaque instruction précise quelle est la prochaine instruction, directement dans la suite de bit représentant l'instruction en mémoire. Les processeurs de ce type contiennent toujours un registre d'adresse d'instruction, pour faciliter l’interfaçage avec le bus d'adresse. La partie de l'instruction stockant l'adresse de la prochaine instruction est alors recopiée dans ce registre, pour faciliter sa copie sur le bus d'adresse. Mais le compteur ordinal n'existe pas. Sur des processeurs aussi bizarres, pas besoin de stocker les instructions en mémoire dans l'ordre dans lesquelles elles sont censées être exécutées. Mais ces processeurs sont très très rares et peuvent être considérés comme des exceptions à la règle.

Encodage d'une instruction sur un processeur sans Program Counter.

L'unité de chargement s'occupe de tout ce qui a trait au program counter, mais aussi de l'accès mémoire pour charger l'instruction. Il faut noter que sur certains processeurs, le chargement d'une instruction machine est une opération réalisée par le chemin de données. C'est le cas sur les architectures Von Neumann, où il n'y a qu'un seul bus mémoire, qui sert à la fois pour lire/écrire des données et pour charger des instructions. Dans ce cas, l'unité de communication avec la mémoire s'occupe aussi de charger les instructions. Le chargement d'une instruction est alors réalisée par une micro-opération d'accès mémoire, qui copie l'instruction chargée dans un registre dédié, appelé le registre d'instruction, lui-même relié au séquenceur.

Les autres circuits

[modifier | modifier le wikicode]

Un processeur contient au minimum une unité de chargement, le chemin de données et le décodeur d'instruction. Mis c'est là le minimum, un processeur peut parfaitement contenir d'autres circuits en plus. Par exemple, il peut intégrer des circuits pour gérer les interruptions matérielles, des circuits timer pour compter des durées, des circuits dit DMA pour communiquer avec les périphériques, etc.

L'exemple du 80186 d'Intel est illustré ci-dessous. Il est composé de deux circuits principaux : une Execution Unit qui regroupe l'unité de calcul et les registres, une Bus Interface Unit qui regroupe unité mémoire, unité de chargement et séquenceur. Mais en plus de ces deux circuits principaux, le processeur incorpore des circuits timers, de quoi gérer les interruption, un circuit DMA, et bien d'autres encore.

Intel 80186 80188 arch

Des processeurs vendus en kit aux premiers microprocesseurs

[modifier | modifier le wikicode]

Un processeur est un circuit assez complexe et qui utilise beaucoup de transistors. Avant les années 1970, il n'était pas possible de produire un processeur en un seul morceau. Impossible de mettre un processeur dans un seul boitier. Les tout premiers processeurs étaient fabriqués porte logique par porte logique et comprenaient plusieurs milliers de boitiers reliés entre eux. Par la suite, les progrès de la miniaturisation permirent de faire des pièces plus grandes. L'invention du microprocesseur permis de placer tout le processeur dans un seul boitier, une seule puce électronique.

Avant l'invention du microprocesseur

[modifier | modifier le wikicode]

Avant l'invention du microprocesseur, les processeurs étaient fournis en pièces détachées qu'il fallait relier entre elles. Le processeur était composé de plusieurs circuits intégrés, placés sur la même carte mère et connectés ensemble par des fils métalliques. Un exemple de processeur conçu en kit est la série des Intel 3000. Elle regroupe plusieurs circuits séparés : l'Intel 3001 est le séquenceur, l'Intel 3002 est le chemin de données (ALU et registres), le 3003 est un circuit d'anticipation de retenue censé être combiné avec l'ALU, le 3212 est une mémoire tampon, le 3214 est une unité de gestion des interruptions, les 3216/3226 sont des interfaces de bus mémoire. On pourrait aussi citer la famille de circuits intégrés AMD Am2900.

Les ALUs en pièces détachées de l'époque étaient assez simples et géraient 2, 4, 8 bits, rarement 16 bits. Et il était possible d'assembler plusieurs ALU pour créer des ALU plus grandes, par exemple combiner plusieurs ALU 4 bits afin de créer une unité de calcul 8 bits, 12 bits, 16 bits, etc. Il s'agit de la méthode du bit slicing que nous avions abordée dans le chapitre sur les unités de calcul.

L'intel 4004 : le premier microprocesseur

[modifier | modifier le wikicode]

Par la suite, les progrès de la miniaturisation ont permis de mettre un processeur entier dans un seul circuit intégré. C'est ainsi que sont nés les microprocesseurs, à savoir des processeurs qui tiennent tout entier sur une seule puce de silicium. Les tout premiers microprocesseurs étaient des processeurs à application militaire, comme le processeur du F-14 CADC ou celui de l'Air data computer.

Le tout premier microprocesseur commercialisé au grand public est le 4004 d'Intel, sorti en 1971. Il comprenait environ 2300 transistors, avait une fréquence de 740 MHz, et manipulait des entiers de 4 bits. De plus, le processeur manipulait des entiers en BCD, ce qui fait qu'il pouvait manipuler un chiffre BCD à la fois (un chiffre BCD est codé sur 4 bits). Il pouvait faire 46 opérations différentes. C'était au départ un processeur de commande, prévu pour être intégré dans la calculatrice Busicom calculator 141-P, mais il fut utilisé pour d'autres applications quelque temps plus tard. Son successeur, l'Intel 4040, garda ces caractéristiques et n'apportait que quelques améliorations mineures : plus de registres, plus d'opérations, etc.

Immédiatement après le 4004, les premiers microprocesseurs 8 bits furent commercialisés. Le 4004 fut suivi par le 8008 et quelques autres processeurs 8 bits extrêmement connus, comme le 8080 d'Intel, le 68000 de Motorola, le 6502 ou le Z80. Ces processeurs utilisaient là encore des boitiers similaires au 4004, mais avec plus de broches, vu qu'ils étaient passés de 4 à 8 bits. Par exemple, le 8008 utilisait 18 broches, le 8080 était une version améliorée du 8008 avec 40 broches. Le 8086 fut le premier processeur 16 bits.

L'évolution des processeurs dans le temps

[modifier | modifier le wikicode]

La miniaturisation a eu des conséquences notables sur la manière dont sont conçus les processeurs, les mémoires et tous les circuits électroniques en général. On pourrait croire que la miniaturisation a entrainé une augmentation de la complexité des processeurs avec le temps, mais les choses sont à nuancer. Certes, on peut faire beaucoup plus de choses avec un milliard de transistors qu'avec seulement 10000 transistors, ce qui fait que les puces modernes sont d'une certaine manière plus complexes. Mais les anciens processeurs avaient une complexité cachée liée justement au faible nombre de transistors.

Il est difficile de concevoir des circuits avec un faible nombre de transistors, ce qui fait que les fabricants de processeurs devaient utiliser des ruses de sioux pour économiser des transistors. Les circuits des processeurs étaient ainsi fortement optimisés pour économiser des portes logiques, à tous les niveaux. Les circuits les plus simples étaient optimisés à mort, on évitait de dupliquer des circuits, on partageait les circuits au maximum, etc. La conception interne de ces processeurs était simple au premier abord, mais avec quelques pointes de complexité dispersées dans toute la puce.

De nos jours, les processeurs n'ont plus à économiser du transistor et le résultat est à double tranchant. Certes, ils n'ont plus à utiliser des optimisations pour économiser du circuit, mais ils vont au contraire utiliser leurs transistors pour rendre le processeur plus rapide. Beaucoup des techniques que nous verrons dans ce cours, comme l’exécution dans le désordre, le renommage de registres, les mémoires caches, la présence de plusieurs circuits de calcul, et bien d'autres ; améliorent les performances du processeur en ajoutant des circuits en plus. De plus, on n'hésite plus à dupliquer des circuits qu'on aurait autrefois mis en un seul exemplaire partagé. Tout cela rend le processeur plus complexe à l'intérieur.

Une autre contrainte est la facilité de programmation. Les premiers processeurs devaient faciliter au plus la vie du programmeur. Il s'agissait d'une époque où on programmait en assembleur, c'est à dire en utilisant directement les instructions du processeur ! Les processeurs de l'époque utilisaient des jeu d'instruction CISC pour faciliter la vie du programmeur. Pourtant, ils avaient aussi des caractéristiques gênantes pour les programmeurs qui s'expliquent surtout par le faible nombre de transistors de l'époque : peu de registres, registres spécialisés, architectures à pile ou à accumulateur, etc. Ces processeurs étaient assez étranges pour les programmeurs : très simples sur certains points, difficiles pour d'autres.

Les processeurs modernes ont d'autres contraintes. Grâce à la grande quantité de transistors dont ils disposent, ils incorporent des caractéristiques qui les rendent plus simples à programmer et à comprendre (registres banalisés, architectures LOAD-STORE, beaucoup de registres, moins d'instructions complexes, autres). De plus, si on ne programme plus les processeurs à la main, les langages de haut niveau passe par des compilateurs qui eux, programment le processeur. Leur interface avec le logiciel a été simplifiée pour coller au mieux avec ce que savent faire les compilateurs. En conséquence, l’interface logicielle des processeurs modernes est paradoxalement plus minimaliste que pour les vieux processeurs.

Tout cela pour dire que la conception d'un processeur est une affaire de compromis, comme n'importe quelle tâche d'ingénierie. Il n'y a pas de solution parfaite, pas de solution miracle, juste différentes manières de faire qui collent plus ou moins avec la situation. Et les compromis changent avec l'époque et l'évolution de la technologie. Les technologies sont toutes interdépendantes, chaque évolution concernant les transistors influence la conception des puces électroniques, les technologies architecturales utilisées, ce qui influence l'interface avec le logiciel, ce qui influence ce qu'il est possible de faire en logiciel. Et inversement, les contraintes du logiciel influencent les niveaux les plus bas, et ainsi de suite. Cette morale nous suivra dans le reste du cours, où nous verrons qu'il est souvent possible de résoudre un problème de plusieurs manières différentes, toutes utiles, mais avec des avantages et inconvénients différents.