Architecture de Von Neumann
En cours
Ce chapitre est en cours d'écriture, il est donc déconseillé de s'y attaquer.
Ce chapitre est en grande partie inspiré du cours de Christophe Mieszczak sur le même sujet, en particulier pour l'usage du M999, qui est inspiré de https://github.com/InfoSansOrdi/M999
Ce cours s'appuie sur le modèle m999. Vous pouvez télécharger la fiche du modèle en cliquant sur ce lien
L'architecture de Von Neumann, est l'architecture utilisée dans les ordinateurs personnels et les smartphones.
Principe
Le principe de l'architecture de Von Neumann est que les programmes sont aussi stockés dans la mémoire RAM, comme des données. Les instructions d'un programme sont des données comme les autres.
Ainsi, un programme peut manipuler d'autres programmes directement. L'implémentation de systèmes d'exploitation et de fonctionnalités avancées en est simplifiée.
Programme machine
On va coder directement le programme dans la mémoire. Les instructions vont donc être dans la mémoire sous forme d'un paquet de bits que l'on représentera par un nombre et non par une mnémonique.
Chaque code d'instruction a la structure suivante :
Où :
X
est le code opération ou opcode, qui détermine l'opération à effectuer.YY
est l'argument de l'opération. En général, les deux chiffres d'une adresse de cellule, et00
si l'adresse n'a pas de cellule.
Mnémonique | Opcode | Argument |
---|---|---|
LDA | 1 | adresse |
LDB | 2 | adresse |
STR | 3 | adresse |
ADD | 4 | - |
SUB | 5 | - |
JMP | 6 | adresse |
JNZ | 7 | adresse |
JPZ | 8 | adresse |
HLT | 9 | - |
Les instructions de contrôlent ciblent donc maintenant l'adresse de la case ou se trouve l'instruction à executer. La première instruction exécutée est celle à l'adresse 0.
Par exemple, L'instruction LDA 22
se code par le nombre 122
.
Activité : codage en mémoire
Exercice : addition
Le chapitre précédent montrait un programme assembleur réalisant une addition. En voici une version modifiée:
Cases mémoires utilisées : 0, 1, 2, 3, 4, 10, 11, 99
Ce programme ajoute les valeurs dans les cases 10 et 11 et mets le résultat dans la case 99.
Donnez la représentation en mémoire ce programme et de ses données initiales.
Exercice : multiplication
Le chapitre précédent montrait un programme assembleur réalisant une multiplication. En voici une version modifiée:
Cases mémoires utilisées : 0-9, 10, 11, 12, 99
Ce programme multiplie la valeur de la case 10 par la valeur de la case 11, et mets le résultat dans la case 99. La case 12 a pour valeur initale 1. La case 99 a pour valeur initiale 0.
Donnez la représentation en mémoire ce programme et de ses données initiales.
Activité : programmation
Exercice : soustraction
Ecrivez, en utilisant les instructions présentées au chapitre précédent, un programme qui soustrait la case mémoire 11 à la case 10, et écrit le résultat dans la case 99, et s'arrête.
Voici les valeurs en mémoire au démarrage du programme (hors instructions):
Case | Valeur |
---|---|
10 | ? |
11 | ? |
Codez-le ensuite en mémoire.
- On charge les valeurs des cases 10 et 11 dans les registres A et B respectivement.
- On soustrait les valeurs
- On écrit le résultat dans la case 99.
- On s'arrête
Voici un tel programme :
L'état initial de la mémoire au démarrage du programme est comme suit :
Case | Valeur |
---|---|
0 | 110 |
1 | 111 |
2 | 500 |
3 | 399 |
4 | 900 |
... | ... |
10 | ? |
11 | ? |
Exercice (difficile): égalité
Ecrivez, en utilisant les instructions présentées au chapitre précédent, un programme qui compare les valeurs dans les cases mémoire 10 et 11, et change la valeur de la case 99 (initialement à 0) en une valeur différente de 0 si les valeurs sont égales, puis s'arrête.
Voici les valeurs en mémoire au démarrage du programme (hors instructions):
Case | Valeur |
---|---|
10 | ? |
11 | ? |
12 | 1 |
99 | 0 |
Codez-le ensuite en mémoire.
Vous pouvez dans un premier temps écrire le programme en Python pour vous aider. Attention toutefois, les instructions assembleur auxquelles on a accès sont bien plus restreintes que celles en Python ! Ainsi, vous ne pourrez pas comparer directement deux nombres (a == b
) dans unif
, mais seulement comparer un nombre et zéro (a == 0
).
- On commence par charger dans les registres les valeurs des cases 10 et 11.
- On soustrait ensuite ces valeurs.
- Si le résultat est 0 (les valeurs sont égales):
- On charge la valeur de la case 12 dans les deux regitres
- On les additionne
- On écrit le résultat dans la case 99
- On s'arrête
Voici un tel programme :
L'instruction JNZ 8 permet de sauter directement à l'instruction HLT, et donc d'éviter la partie ou l'on écrit une valeur dans 99.
L'état initial de la mémoire au démarrage du programme est comme suit :
Case | Valeur |
---|---|
0 | 110 |
1 | 111 |
2 | 500 |
3 | 708 |
4 | 112 |
5 | 112 |
6 | 400 |
7 | 399 |
8 | 900 |
... | ... |
10 | ? |
11 | ? |
12 | 1 |
99 | 0 |
Exercice (très difficile) : comparaison
Ecrivez, en utilisant les instructions présentées au chapitre précédent, un programme qui compare les valeurs (strictement positive) dans les cases mémoire 10 et 11, et change la valeur de la case 99 (initialement à 0) en une valeur différente de 0 si la valeur dans la case 30 est strictement plus grande que la valeur dans la case 31, puis s'arrête.
Voici les valeurs en mémoire au démarrage du programme (hors instructions):
Case | Valeur |
---|---|
30 | ? |
31 | ? |
32 | 1 |
99 | 0 |
Codez-le ensuite en mémoire.
Débrouillez-vous ! Non plus sérieusement il faudra ajouter une aide. Demandez au prof pour le moment.
L'idée est que si le valeurs des cases 30 et 31 sont différentes, on va leur soustraire 1 tour à tour jusqu'a qu'une arrive à zéro. La première à zéro est la plus petite des deix valeurs.
- On commence par vérifier si les valeurs sont égales comme à l'exercice précédent. Si c'est le cas, on termine le programme directement.
- Ensuite, on va répéter à l'infini :
- On soustrait 1 à la case 30
- Si le résultat est égal à 0, on change la valeur de la case 99, et on termine le programme.
- On soustrait 1 à la case 30
- Si le résultat est égal à 0, on termine le programme directement.
Voici un tel programme. On utilisera des #
pour signifier des commentaires :
LDA 30 #Début comparaison égalité
LDB 31
SUB
JPZ 19 #Si égal, on arrête le programme
LDA 30 #On répète à partir d'ici (1)
LDB 32
SUB
STR 30
JPZ 15 #Si zéro ici alors 30 plus petite
LDA 31
LDB 32
SUB
STR 31
JPZ 19 #Si zéro ici alors 31 plus petite, on termine
JMP 4 #On recommence en (1)
LDA 32 #On change la valeur de la case 99
LDB 32
ADD
STR 99
HLT
L'état initial de la mémoire au démarrage du programme est comme suit :
Cases 0x | Valeur | Cases 1x | Valeur | Cases 3x | Valeur | Cases 9x | Valeur |
---|---|---|---|---|---|---|---|
0 | 130 | 10 | 132 | 30 | ? | 90 | - |
1 | 131 | 11 | 500 | 31 | ? | 91 | - |
2 | 500 | 12 | 331 | 32 | 1 | 92 | - |
3 | 719 | 13 | 719 | 33 | - | 93 | - |
4 | 130 | 14 | 604 | 34 | - | 94 | - |
5 | 132 | 15 | 132 | 35 | - | 95 | - |
6 | 500 | 16 | 132 | 36 | - | 96 | - |
7 | 330 | 17 | 400 | 37 | - | 97 | - |
8 | 815 | 18 | 399 | 38 | - | 98 | - |
9 | 131 | 19 | 900 | 39 | - | 99 | 0 |
Typage
Difficile
Cette section est sans doute trop complexe. Il faudra l'aborder avec précautions, et sans doute la reformuler.
Activité
Observez ce programme. Le numéro d'une ligne corresponds à l'adresse de la case mémoire où est stockée l'instruction.
A la fin de l'execution de ce programme, quelle valeur contient la case 99 ?
Codez les instructions LDA 0
et LDB 1
par des nombres, ça peut vous aider !
Ce programme charge les contenus des cases 0 et 1, les additionne, puis écrit le résultat dans la case 99 et s'arrête.
Or, les cases 0 et 1 contiennent les codes des instructions LDA 0
et LDB 0
, qui sont respectivement 100
et 101
.
A la fin du programme, la case 99 contient la valeur 201.
Normalement, après cette activité, vous devez être un peu confus. On a additionné des ... instructions ?
Et oui, la machine ne fait AUCUNE différence entre une instruction et un nombre "donnée", elle se contente d'executer le programme, et c'est les opérations faites par le programme décident si le contenu d'une case est une instruction où un nombre, ou autre chose.
Le programme de l'activité précédente a donc le sens suivant :
LDA 0 # charger contenu case 0 dans registre A
LDB 1 # charger contenu case 1 dans registre B
ADD # ajouter contenu registres A et B comme des nombres
STR 99 # stocker le résultat dans case 99
HLT # terminer le programme
Activité
Voici un exemple de programme qui écrit une instruction :
Quelle instruction est exécutée en dernier par ce programme ?
La valeur 450 est chargée dans les registres A et B par les deux premières instructions.
Les deux registres sont ensuite ajoutés.
Le resultat, 900, est mis dans la case 4.
A ce moment, le PC prends la valeur 4. La valeur de la case, 900, est alors interprété comme une instruction, HLT
.
La dernière instruction exécutée par ce programme est donc HLT
.
Cette non distinction permet de garder une syntaxe relativement simple pour les langages d'assemblages, tout en permettant des opérations et des optimisations complexes. Ces dernières dépassent largement le cadre de ce cours.
Toutefois, celà veut dire qu'écrire un programme en langage d'assemblage demande énormément de précautions, parce que l'assembleur est incapable de détecter les erreurs d'inattention, même les plus idiotes.
C'est pour cette raison que la très grande majorité des logiciels modernes sont réalisés avec des langages ayant des règles sémantiques plus strictes, de manière à détecter les erreurs d'inattention potentielles.
Architecture de type Harvard
Par opposition à l'architecture de Von Neumann, l'architecture de type Harvard a plusieurs mémoires, en général une pour les programmes et une pour les données.
Avoir deux mémoires permet d'augmenter les performances à relativement bas coût, puisque la lecture des instructions du programme peut se faire en simultané de l'accès aux données, ce qui est impossible dans une architecture Von Neumann.
Sur les ordinateurs modernes conçus sur le principe de l'architecture de Von Neumann, il existe une mémoire très rapide, le cache1, qui sert d'intérmédiaire entre la RAM et le processeur pour accélérer la vitesse des transferts. Le cache permet d'avoir des performances similaires voir meilleures qu'une architecture type Harvard, mais complexifie énormément les composants.
On trouve donc en général des architectures de type Harvard dans des composants peu chers, comme des circuits de calcul spécialisé ou certains microcontroleurs, comme ceux qu'on trouve sur des Arduino.
(Avancé) Processeurs multicoeurs
Avoir plusieurs coeurs dans un processeur peut permettre d'augmenter significativement le nombre d'opérations par secondes que le processur peut réaliser. De nombreux programmes, comme par exemple la plupart des jeux vidéos, savent tirer parti de multiples coeurs. Toutefois, avoir plusieurs coeurs qui travaillent simultanément sur la même mémoire peut être source d'erreurs.
TODO (prof) dérouler un prog. d'addition sur une meme mémoire mais avec 2 coeurs, en montrant que si les deux sont séquences sont entrelacées, celà peut "écraser" le résultat.
(Avancé) Somme d'un tableau
TODO (prof) exemple de la somme d'un tableau avec explications ?
(Avancé) Dépassement de tampon
TODO (prof) montrer un exemple de dépassement de tampon
-
le cache peut d'ailleur être organisé selon une architecture Harvard. https://learn.adafruit.com/memories-of-an-arduino/arduino-memory-architecture. ↩