Aller au contenu

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 :

XYY

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, et 00 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.

LDA 10
LDB 11
ADD
STR 99
HLT

Donnez la représentation en mémoire ce programme et de ses données initiales.

On commence par convertir les mnémoniques en leur valeur numérique.

110
211
400
399
900

On les écrit ensuite en mémoire en commençant à la case 0. On n'oublie pas les autres cases initialisées.

Case Valeur
0 110
1 211
2 400
3 399
4 900
... ...
10 ?
11 ?

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.

LDA 10 
LDB 99
ADD
STR 99
LDA 11
LDB 12
SUB
STR 11
JNZ 0
HLT

Donnez la représentation en mémoire ce programme et de ses données initiales.

On commence par traduire les instructions :

110
299
400
399
111
212
500
311
700
900

On les écrit ensuite en mémoire, sans oublier les données.

Case Valeur
0 110
1 299
2 400
3 399
4 111
5 212
6 500
7 311
8 700
9 900
10 ?
11 ?
12 1
... ...
99 0

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 :

LDA 10
LDB 11
SUB
STR 99
HLT

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 :

LDA 10
LDB 11
SUB
JNZ 8
LDA 12
LDB 12
ADD
STR 99
HLT

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.

LDA 0
LDB 1
ADD
STR 99
HLT

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 :

LDA 4
LDB 4
ADD
STR 4
450

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


  1. le cache peut d'ailleur être organisé selon une architecture Harvard. https://learn.adafruit.com/memories-of-an-arduino/arduino-memory-architecture