Architecture & systèmes · L1 · Section 1/6
Architecture de Von Neumann
Progression
#Architecture de Von Neumann
#Prérequis et objectifs
Prérequis: représentation binaire des entiers, notion de variable et de tableau, premiers pas en Python.
Objectifs d'apprentissage:
- décrire les cinq composants du modèle (mémoire, unité de contrôle, ALU, entrée, sortie) et le rôle de chaque bus;
- dérouler le cycle fetch–decode–execute sur un programme de trois instructions, en traçant PC et ACC à chaque étape;
- expliquer le goulot de Von Neumann et pourquoi les caches l'atténuent;
- relier l'idée « code et données dans le même espace » à des mesures de sécurité concrètes (bit NX, attaques par injection de code).
#Le modèle et ses composants
Un ordinateur de Von Neumann exécute un programme stocké en mémoire, comme les données qu'il manipule. C'est ce « programme enregistré » qui distingue ce modèle des machines à câblage fixe: changer de programme revient à changer le contenu de la mémoire, pas le matériel.
- Mémoire: instructions et données, adressées uniformément par des entiers;
- Unité de contrôle: séquence le cycle, pilote les bus;
- ALU: opérations arithmétiques et logiques;
- Bus: adresse (où), données (quoi), contrôle (comment);
- E/S: communication avec le monde extérieur.
#Cycle d'instruction
- Fetch: le contenu de PC est mis sur le bus d'adresse, l'instruction arrive sur le bus de données;
- Decode: l'unité de contrôle décode l'opcode et repère les opérandes;
- Execute: ALU, accès mémoire ou E/S selon l'opcode;
- Update: PC avance (ou est écrasé par un saut).
Deux précisions utiles pour la suite. D'abord, « PC avance » signifie presque toujours PC := PC + longueur d'instruction, car une instruction peut occuper plusieurs octets. Ensuite, le modèle de base est séquentiel: les CPU réels le pipelinent (plusieurs instructions en cours simultanément, chacune à une étape différente), ce qui rend les branchements coûteux: un saut mal prédit vide le pipeline.
#Pipeline pas à pas
Cycle fetch → decode → execute
Choisissez un scénario pour observer la progression des instructions et les bulles injectées par les aléas (latence, branchement).
Trois instructions qui se nourrissent les unes des autres. Idéal pour visualiser un pipeline fluide sans aléa : chaque cycle introduit une instruction tandis que les précédentes avancent d’un stage.
| Cycle | Fetch | Decode | Execute | Mémoire | Write-back |
|---|---|---|---|---|---|
| 1 | LOAD R1, [R2 + #4] PC place l’adresse de l’instruction sur le bus ; la mémoire renvoie l’opcode LOA… | — | — | — | — |
| 2 | ADD R3, R3, R1 PC → mémoire : on récupère l’opcode ADD | LOAD R1, [R2 + #4] Le décodeur repère un accès mémoire, récupère registre base (R2) et offset imméd… | — | — | — |
| 3 | STORE R3 → [R4] PC lit l’opcode STORE en mémoire | ADD R3, R3, R1 Lecture des opérandes sources (R3, R1) dans le banc de registres | LOAD R1, [R2 + #4] ALU additionne R2 et l’immédiat pour produire l’adresse effective | — | — |
| 4 | — | STORE R3 → [R4] Chargement de R3 comme source, R4 comme base d’adresse | ADD R3, R3, R1 ALU additionne R3 + R1, met à jour les flags | LOAD R1, [R2 + #4] Bus de données lit la valeur située à l’adresse calculée | — |
| 5 | — | — | STORE R3 → [R4] ALU calcule l’adresse de destination (R4 + offset) | ADD R3, R3, R1 Pas d’accès mémoire: on utilise le bypass de résultat | LOAD R1, [R2 + #4] Registre R1 reçoit la donnée ; flags inchangés, PC incrémenté |
| 6 | — | — | — | STORE R3 → [R4] Bus de données écrit R3 vers la mémoire (write-back) | ADD R3, R3, R1 Résultat stocké dans R3, flags conditionnels mis à jour |
| 7 | — | — | — | — | STORE R3 → [R4] Pas d’écriture registre ; PC avance, file store commit |
Pipeline inactif
Aucune instruction ne traverse ce stage durant ce cycle.
- Le résultat du LOAD est transféré directement à l’ADD via forwarding (bypass)
- Le STORE lit la mémoire dans le stage MEM, ce qui libère le bus pour la prochaine instruction
#Ce qu’il y a vraiment dans un processeur
Le modèle de Von Neumann décrit l’organisation logique ; il reste à savoir avec quoi on le construit. La réponse tient en cinq briques, toutes déjà connues du cours d’électronique numérique de première année :
- un registre — un alignement de bascules D partageant la même horloge — pour mémoriser un mot ;
- une ALU (Arithmetic and Logic Unit) pour les opérations arithmétiques et logiques ;
- un multiplexeur pour aiguiller les données vers l’entrée de l’ALU ;
- un démultiplexeur pour diriger le résultat vers le bon destinataire ;
- un séquenceur programmable et un compteur, qui produisent les commandes dans le bon ordre.
Ce dernier point mérite d’être souligné : le séquenceur est un automate, dont la séquence évolue en fonction d’entrées externes (la commande). Un processeur n’est donc pas autre chose qu’une machine à états finis un peu large — la même que celle qu’on synthétise sous Logisim au TP 5 du SPUE100. Le cycle fetch–decode–execute vu plus haut est littéralement le graphe d’états de cet automate.
Autre conséquence, moins intuitive : la structure exacte d’un processeur dépend très fortement des instructions qu’il est censé exécuter. Le nombre d’opérandes, leur nature, les opérations à réaliser déterminent le nombre de registres, la largeur des bus internes et les commandes du séquenceur. Une structure de processeur est l’image d’un langage machine : c’est pourquoi on parle de jeu d’instruction, document unique où toutes les instructions sont répertoriées, codées et séquencées.
#Un exemple d’instruction encodée
Prenons l’instruction ADDI 5. Le mnémonique ADDI indique une addition ; la lettre I précise que la valeur à additionner est fournie immédiatement après, dans l’instruction elle-même, et non lue en mémoire. L’instruction complète se code sur deux octets :
1ADDI 5 → 00100101 000001012 \______/ \______/3 opcode 5 en binaireChaque instruction s’exécute en principe en plusieurs cycles de l’horloge principale — le cours en compte cinq pour cet exemple. Le séquenceur décompose donc chaque instruction en une suite d’états élémentaires : chercher l’opérande, présenter les entrées à l’ALU, sélectionner l’opération, écrire le résultat, incrémenter le PC.
#Trois façons d’organiser un processeur
Le même jeu d’instructions se réalise avec des structures internes très différentes, et le choix conditionne la façon d’écrire les programmes.
| Structure | Principe | Exemple |
|---|---|---|
| À accumulateur | Un registre unique sert à la fois d’opérande et de résultat : ADDI 5 signifie « registre ← registre + 5 » | Intel 4004 (1971), 8080 (1974), 6800, 6502 |
| À registres généraux | Plusieurs registres, n’importe quelle opération entre n’importe lesquels (ADD Rd, Rr) | Intel 8086 (1978), tous les processeurs modernes |
| À pile | Les opérandes sont empilés ; les opérateurs consomment et produisent le sommet de pile | calculatrices HP |
L’évaluation d’une expression arithmétique illustre parfaitement le clivage. Une machine à pile évalue une expression à partir de son arbre d’évaluation : il suffit de parcourir l’arbre en profondeur et d’empiler les opérandes, sans jamais nommer de registre ni gérer d’allocation. C’est élégant, mais la pile est le goulot : moins il y a de références mémoire, mieux cela vaut, et le programmeur doit constamment réorganiser les variables avec dup, over, swap, roll, drop pour faire ressortir l’invariant de boucle. Les machines à pile ont longtemps prospéré dans les calculatrices Hewlett-Packard, mais n’ont quasiment jamais été réellement accélérées : ce sont les architectures à registres généraux qui ont emporté le marché.
La limite historique des machines à accumulateur était simple : le nombre de registres. À partir des années 1980, la demande de performance a imposé de multiplier les registres, d’où le passage généralisé à l’architecture à registres généraux.
#Repères historiques
| Processeur | Année | Fréquence max | Données / adresses | Transistors |
|---|---|---|---|---|
| Intel 4004 | 1971 | 740 kHz | 4 bits | 2 300 |
| Intel 8008 | 1972 | 800 kHz | 8 / 14 bits | 3 500 |
| Intel 8080 | 1974 | 3,125 MHz | 8 / 16 bits | 6 000 |
| Motorola 6800 | 1975 | 1 MHz | 8 / 16 bits | 7 000 |
| MOS 6502 | 1975 | 3 MHz | 8 / 16 bits | 3 510 |
| Intel 8086 | 1978 | 10 MHz | 16 / 20 bits | 20 000 |
Le microprocesseur est né chez Intel, inventé par Marcian Hoff et Federico Faggin en 1970. À sa création il exécutait quelques dizaines de milliers d’instructions par seconde ; aujourd’hui il en traite plus de dix milliards. Le premier ordinateur réellement personnel, l’IBM PC de 1981, reposait sur un 8088 à 8 bits de données, 16 bits d’adresse, 4,77 MHz et 64 Ko de mémoire — les proportions d’un téléphone actuel.
#Le registre d’état
Pour que les branchements conditionnels soient possibles, le processeur doit conserver la trace de la dernière opération effectuée par l’ALU. C’est le rôle du registre d’état (SREG sur les processeurs AVR), organisé en drapeaux :
| Bit | Nom | Signification |
|---|---|---|
| 7 | I | autorisation globale des interruptions |
| 6 | T | drapeau de test, utilisé par les instructions de copie de bit |
| 5 | H | retenue du demi-octet (bit 3) |
| 4 | S | drapeau de test signé, N ⊕ V |
| 3 | V | dépassement de capacité (overflow) |
| 2 | N | résultat négatif |
| 1 | Z | résultat nul |
| 0 | C | retenue déclenchée (carry) |
Z passe à 1 si le résultat de l’opération vaut 0 et à 0 sinon ; N passe à 1 si le résultat est négatif ; V signale un dépassement de capacité ; C une retenue. Le registre d’état ne sert pas seulement à mémoriser : il est la condition même des branchements. Une instruction de comparaison (CPI R16, 0) ne modifie que les drapeaux, et l’instruction de branchement qui suit (BREQ, BRNE, BRLT…) teste l’un d’eux pour décider si le PC doit être écrasé. Sans SREG, pas de if, pas de boucle, pas d’appel conditionnel — c’est le pont matériel entre le calcul et le contrôle.
#Le goulot de Von Neumann
Instructions et données partagent le même chemin vers la mémoire: le CPU ne peut pas chercher une instruction et une donnée au même cycle sur un seul bus. Les machines réelles atténuent ce goulot avec des caches séparés pour instructions (L1i) et données (L1d), et des mémoires plus larges capables de livrer plusieurs mots par cycle. La conséquence pédagogique reste: la bande passante mémoire est une ressource finie, et un programme qui la gaspille ralentit même s'il fait peu de calculs.
#Sécurité: quand les données deviennent du code
Comme la mémoire est uniforme, rien dans le modèle n'interdit d'exécuter ce qui était censé être une donnée. Un attaquant qui déborde un tampon peut y placer du code machine et détourner le flux vers lui. Les OS modernes marquent les pages de données comme non exécutables (bit NX/DEP): le CPU refuse d'exécuter une page sans droit d'exécution. C'est un exemple direct où une propriété de l'architecture de Von Neumann devient une décision de sécurité au niveau de l'OS.
#Exercice: simulateur du cycle d'instruction
On simule un processeur minimal avec un jeu d'instructions réduit. La subtilité de l'exercice: les instructions ne sont pas encodées en mémoire (pas d'opcode numérique), on garde un tableau Python d'instructions à part. Le programme ci-dessous implémente le simulateur complet; exécutez-le, puis modifiez-le selon les questions.
Rappel des règles du processeur fictif:
LOAD addr: ACC := mémoire[addr]ADD addr: ACC := ACC + mémoire[addr]STORE addr: mémoire[addr] := ACCHALT: arrêt
1# Programme qui calcule 5 + 3 et stocke le résultat2# Mémoire (instructions) :3# 0: LOAD 10 (charger 5 dans ACC)4# 1: ADD 11 (ajouter 3 à ACC)5# 2: STORE 12 (stocker le résultat)6# 3: HALT7# Mémoire (données) :8# 10: 5 11: 3 12: 09 10def simulate_von_neumann():11 # Instructions séparées de la mémoire de données12 program = ["LOAD 10", "ADD 11", "STORE 12", "HALT"]13 memory = {10: 5, 11: 3, 12: 0}14 Correction attendue: la trace affiche quatre lignes (PC=0 à PC=3) et Résultat final (adresse 12): 8. La version interactive ci-dessous corrige le décodage avec un vrai partition et une trace par instruction.