Unité de traitement, unité de commande, mémoire et entrées/sorties ; bus de données, d'adresses et de contrôle ; cycle fetch-decode-execute, horloge et fréquence.
Reprogrammer l'ENIAC, en 1945, prenait plusieurs jours. Non pas d'écriture — de câblage : une équipe d'opératrices rebranchait des centaines de fiches et repositionnait des milliers d'interrupteurs. La machine n'exécutait pas un programme, elle était le programme.
La même année, un rapport resté célèbre pour son statut de brouillon — le First Draft of a Report on the EDVAC, signé du seul John von Neumann alors que l'idée est collective — propose autre chose : ranger les instructions dans la mémoire, à côté des données. Changer de programme devient alors ce que c'est aujourd'hui, écrire des octets quelque part.
Ce chapitre pose cette architecture. Il est court et il est central : les chapitres 6, 7 et 8 ne feront qu'en détailler des morceaux — le jeu d'instructions, la mémoire, les entrées/sorties.
Le programme est une donnée
L'idée tient en une phrase, et ses conséquences occupent l'informatique entière.
Puisque le programme est en mémoire, un programme peut lire, écrire et fabriquer un autre programme. Un compilateur devient possible : c'est un programme dont la sortie est un programme. Un système d'exploitation devient possible : il charge en mémoire le code d'une application, puis lui donne la main. Un virus devient possible, pour la même raison.
Le revers est immédiat. Rien, dans la mémoire, ne distingue physiquement une instruction d'une donnée : ce sont des motifs de bits, dans les mêmes cases, sur le même bus. C'est le processeur qui décide, par le seul fait de savoir où il en est. Faire pointer le compteur ordinal sur des données, et la machine les exécutera consciencieusement — c'est le mécanisme d'une bonne part des attaques par débordement de tampon, et la raison pour laquelle les systèmes modernes marquent certaines zones « non exécutables ».
Les quatre organes
Le modèle décompose la machine en quatre parties, et le vocabulaire est stable depuis 1945.
L'unité arithmétique et logique (UAL) calcule : additions, soustractions, opérations booléennes, comparaisons. C'est l'additionneur du chapitre 4, entouré de quelques portes et d'un multiplexeur qui sélectionne l'opération.
L'unité de commande ne calcule rien. Elle lit l'instruction courante, la décode, et active les bons signaux au bon moment : lire la mémoire, écrire dans un registre, régler l'UAL sur « additionner ». C'est le chef d'orchestre, et son travail est intégralement combinatoire et séquentiel — décodeurs, multiplexeurs, bascules.
Ensemble, UAL et unité de commande forment le processeur. On y ajoute quelques registres, mémoires internes minuscules et très rapides :
| Registre | Rôle |
|---|---|
| Compteur ordinal (CO) | adresse de la prochaine instruction |
| Registre d'instruction (RI) | l'instruction en cours de traitement |
| Accumulateur / registres généraux | les opérandes et les résultats |
| Registre d'état | les indicateurs du chapitre 2 : retenue, débordement, zéro, signe |
La mémoire est un tableau de cases numérotées. Donner une adresse, recevoir un mot ; donner une adresse et un mot, l'écrire. Elle ne sait rien d'autre, et surtout pas ce qu'elle contient.
Les entrées/sorties relient le tout au monde. Le chapitre 8 leur est consacré.
Trois bus
Ces organes communiquent par des faisceaux de fils appelés bus, et il y en a exactement trois, chacun avec sa nature propre.
Le bus d'adresses est unidirectionnel : seul le processeur émet, la mémoire écoute. Sa largeur détermine l'espace adressable, et c'est le calcul le plus utile du chapitre : fils donnent adresses. Avec 16 fils, 64 Kio. Avec 32 fils, octets, soit exactement 4 Gio — la fameuse limite des systèmes 32 bits, qui n'est pas une décision commerciale mais un nombre de fils.
Le bus de données est bidirectionnel : un mot y circule dans un sens à la lecture, dans l'autre à l'écriture. Sa largeur — 8, 32, 64 bits — est la quantité transférée par accès.
Le bus de contrôle transporte les signaux qui disent quoi faire et quand : lecture ou écriture, horloge, demandes d'interruption du chapitre 8.
Un processeur dispose d'un bus d'adresses de 20 fils et d'un bus de données de 16 bits. Quelle est la quantité maximale de mémoire adressable, et pourquoi ?
Le cycle recherche - décodage - exécution
Le processeur ne fait qu'une chose, indéfiniment, à chaque coup d'horloge : le même cycle en trois temps.
Recherche (fetch). Le contenu du compteur ordinal part sur le bus d'adresses ; la mémoire renvoie le mot ; ce mot entre dans le registre d'instruction. Le compteur ordinal est incrémenté immédiatement, avant même qu'on sache ce que l'instruction fait. Ce détail compte : c'est ce qui permet à un branchement, plus tard dans le cycle, d'écraser proprement cette valeur.
Décodage (decode). L'unité de commande découpe le registre d'instruction : d'un côté le code opération, de l'autre les opérandes. Le chapitre 6 détaillera ce découpage. À l'issue du décodage, les signaux de commande sont positionnés ; rien n'a encore bougé dans les données.
Exécution (execute). Le travail a lieu : l'UAL calcule, ou la mémoire est lue ou écrite, ou le compteur ordinal reçoit une nouvelle valeur s'il s'agit d'un branchement.
Puis on recommence. Une machine allumée ne fait rien d'autre — et lorsqu'elle « ne fait rien », elle exécute une boucle d'attente du système d'exploitation.
Le compteur ordinal (CO) contient l'adresse de la prochaine instruction, ici 0. Le registre d'instruction (RI) est vide : rien n'a encore été lu. Programme et données sont dans la MÊME mémoire — c'est tout le principe de von Neumann.
Horloge, fréquence, et le vrai goulot d'étranglement
L'horloge cadence tout. À chaque front, l'état bascule vers sa valeur suivante — c'est la bascule sur front du chapitre 4. La fréquence est le nombre de fronts par seconde : 3 GHz signifie trois milliards de cycles par seconde, soit une période de 0,33 nanoseconde.
Il faut résister à une tentation, que le chapitre 8 démontera pour de bon : la fréquence ne mesure pas la performance. Le temps d'exécution vaut
où le CPI est le nombre moyen de cycles par instruction. Deux processeurs de même fréquence peuvent différer d'un facteur trois selon leur CPI et selon la qualité du code compilé.
Reste le défaut structurel du modèle, connu sous le nom de goulot d'étranglement de von Neumann : programme et données partageant la même mémoire et le même bus, le processeur passe son temps à attendre la mémoire. L'animation ci-dessus le montre à nu — deux accès mémoire pour la seule instruction CHARGE 10, l'un pour lire l'instruction, l'autre pour lire la donnée.
L'écart s'est creusé pendant quarante ans : les processeurs ont accéléré bien plus vite que la mémoire. Deux réponses existent. L'architecture Harvard sépare physiquement mémoire d'instructions et mémoire de données, ce que font les microcontrôleurs et, à l'intérieur, les caches de premier niveau. Et le cache lui-même, qui est le sujet du chapitre 7 — sa raison d'être tient tout entière dans le paragraphe que vous venez de lire.
Pourquoi le compteur ordinal est-il incrémenté pendant la phase de recherche, avant même que l'instruction soit décodée ?
À vous
L'exercice écrit la boucle du processeur : un tableau d'entiers qui contient à la fois le programme et les données, un compteur ordinal, un accumulateur, et une fonction qui répète recherche, décodage, exécution jusqu'à rencontrer l'arrêt.
Le programme fourni est déjà rangé en mémoire sous forme de nombres — pas de texte, pas de symboles. C'est le point du chapitre : à ce niveau, une instruction est un entier, et le dernier test le prouve en faisant modifier au programme sa propre instruction.
Complétez le cycle recherche-décodage-exécution d'une machine à accumulateur.
// Une instruction tient dans un entier : opcode * 100 + adresse. const STOP = 0, CHARGE = 1, AJOUTE = 2, RANGE = 3, SAUTE_SI_ZERO = 4, SOUSTRAIT = 5; // Mémoire unique. Cases 0..7 : le programme. Cases 10.. : les données. const MEM = [ CHARGE * 100 + 10, // 0 : ACC <- Mem[10] AJOUTE * 100 + 11, // 1 : ACC <- ACC + Mem[11] RANGE * 100 + 12, // 2 : Mem[12] <- ACC STOP * 100 + 0, // 3 : arrêt 0, 0, 0, 0, 0, 0, 7, // 10 : donnée 3, // 11 : donnée 0, // 12 : résultat ]; function executer(mem, tracer) { let CO = 0, ACC = 0, cycles = 0; while (cycles++ < 100) { // RECHERCHE : lire l'instruction pointée par le CO, puis incrémenter. const RI = mem[CO]; CO = CO + 1; // DÉCODAGE : séparer le code opération de l'adresse. const op = Math.floor(RI / 100); const adr = RI % 100; if (tracer) console.log(" CO=" + String(CO - 1).padStart(2) + " RI=" + String(RI).padStart(4) + " op=" + op + " adr=" + adr + " ACC=" + ACC); // EXÉCUTION. if (op === STOP) return { ACC, mem, cycles }; if (op === CHARGE) ACC = mem[adr]; if (op === AJOUTE) ACC = ACC + mem[adr]; // ← il manque RANGE, SOUSTRAIT et SAUTE_SI_ZERO } return { ACC, mem, cycles: -1 }; } // ── À VOUS ──────────────────────────────────────────────────────────────── // 1. Complétez l'exécution : RANGE écrit l'accumulateur en mémoire, // SOUSTRAIT retranche, SAUTE_SI_ZERO écrit adr dans CO si ACC vaut 0. // 2. Écrivez en mémoire un programme qui calcule 7 - 3 et le range en 12. const r = executer([...MEM], true); console.log("ACC =", r.ACC, "| Mem[12] =", r.mem[12], "| attendu 10 et 10");
En travaux pratiques
Exécuter un programme à la main
Suivre le cycle chercher–décoder–exécuter cycle par cycle, registre par registre, jusqu'à ce que le mécanisme devienne évident — puis en mesurer le goulot.
- Le TP 4 : registres, compteur, UAL
- Un simulateur de machine de von Neumann, ou une feuille et un tableau de trace
- 1. Charger
Placez en mémoire un programme de quatre instructions qui calcule la somme de deux valeurs et la range. Notez le contenu de chaque adresse, instructions et données mélangées.
- 2. La trace complète
Exécutez pas à pas et remplissez un tableau : à chaque cycle, la valeur du compteur ordinal, du registre d'instruction, de l'accumulateur, et l'adresse mémoire accédée. Ne sautez aucune ligne.
- 3. Compter les accès
Sur votre trace, comptez combien de cycles accèdent à la mémoire et pour quoi faire : instruction ou donnée. Calculez la proportion.
- 4. Le programme est une donnée
Écrivez un programme qui MODIFIE une de ses propres instructions avant de l'exécuter. Faites-le tourner et expliquez pourquoi c'est possible.
- 5. Ce que cela coûte
Écrivez maintenant un programme qui, par une erreur d'adresse, écrit une donnée par-dessus une instruction. Observez le comportement et rapprochez-le d'une classe de failles connue.
- 6. L'autre architecture
Dessinez le même schéma avec des mémoires séparées pour les instructions et les données. Dites ce que l'étape 4 devient, et ce que le débit devient.
- 7. Un saut
Ajoutez un branchement conditionnel et tracez une boucle de trois tours. Identifiez le cycle exact où le compteur ordinal cesse de s'incrémenter normalement.
- Votre trace ne comporte aucun trou : chaque cycle a ses quatre valeurs
- Vous savez dire combien d'accès mémoire coûte UNE instruction de votre jeu
- Vous savez expliquer en une phrase pourquoi une donnée peut devenir du code
Ce que la suite en fait
Le chapitre 6 ouvre le registre d'instruction et regarde comment un entier de 32 bits se
découpe en code opération et opérandes ; il montrera aussi comment un pour de haut niveau
devient une suite de branchements écrivant dans le compteur ordinal.
Le chapitre 7 s'attaque au goulot d'étranglement, et le chapitre 8 aux entrées/sorties et à la performance — dont la loi d'Amdahl, qui dira combien il est vain d'accélérer une partie de la machine sans regarder le reste.
À retenir
Vous avez parcouru les 9 sections.
Marquez-la terminée pour faire avancer votre parcours, ou revenez sur un point avant de passer à la suite.