Aller au contenu

Chapitre 3.2 - Architecture Von Neumann/Assembleur⚓︎

image

image

John Von Neumann (1903-1957) est un mathématicien et physicien (et bien d'autres choses) américano-hongrois. Il a le premier théorisé l'architecture des processeurs, tels qu'ils fonctionnent encore aujourd'hui.

1. Architecture von Neumann⚓︎

image

On distingue 4 zones essentielles :

  • le CPU (Central Processing Unit) qui contient lui-même :
    • l'Unité Arithmétique et Logique (UAL) dans laquelle sont effectuées les opérations de base (addition, multiplication...) Cette zone comporte notamment les registres (peu nombreux, de l'ordre de la dizaine) qui sont les espaces de travail ultra-rapides dans lesquels l'UAL va effectuer ses calculs. Une fois ceux-ci effectués, les valeurs des registres repartent dans la mémoire.
    • l'Unité de contrôle, qui va séquencer les opérations. Lorsqu'on parle d'un processeur à 3 GHz, cela signifie (approximativement) que Unité de Contrôle va envoyer l'ordre d'une nouvelle opération à l'UAL 3 milliards de fois par seconde.
  • la mémoire, qui contient à la fois les données à traiter et les instructions du programme. Cette idée de stocker au même endroit données et programme est l'idée centrale de l'architecture von Neumann.
  • les bus de communication (des fils électriques permettant de transporter les données entre les différents composants).
  • les Entrées/Sorties, permettant de gérer les informations avec l'extérieur.

2. Activité : simulation d'un programme en assembleur⚓︎

On l'a vu, dans l'architecture de Von Neumann, la mémoire vive (RAM) contient à la fois les données et les programmes (suite d'instructions).

Dans la mémoire d'un ordinateur on peut donc stocker des données (Entiers, flottants, booléens, caractères,...) sous forme binaire. Par exemple 00100110 correspond au nombre entier 38. Mais on peut également y stocker des instructions à destination de l'unité de traitement (UAL).

Ce même code binaire 00100110 pourrait très bien correspondre au code d'une instruction machine, par exemple "Stopper le programme".

C'est le rôle du système d'exploitation (windows,linux,...) et du programmeur de faire en sorte de distinguer en mémoire ce qui correspond à des instructions ou des donnéees.

Les instructions machine⚓︎

Une instruction machine se présente de la façon suivante :

A retenir

Champ de code de l'opération | Champ de l'opérande

Remarque : chaque processeur possède ses propres codes d'opération (opcode). C'est son jeu d'instructions.

Exemple

Additionner | Valeur contenue dans le registre R1, Nombre 37

Chaque instruction peut occuper un ou plusieurs mots dans la mémoire d'un ordinateur.

Remarque : un mot correspond à l'unité de base pouvant être traitée par le processeur. Avec un proceseur 8 bits la taille du mot correspond à 8 bits soit 1 octet. Avec un processeur 64 bits la taille du mots correspond à 64 bits soit 8 octets.

Au début de l'informatique les programmeurs devaient coder leur programme directement en binaire : le langage machine.

Par exemple, le langage machine suivant est une instruction :

Exemple

01001100 00100101

  • le premier octet 01001100 correspond au code de l'opération à effectuer (opcode) : "ajouter la valeur suivante au registre R1".
  • le second octet 00100101 (37 en décimal) est l'opérande : la valeur à ajouter à celle contenue dans le registre R1.

Le langage assembleur⚓︎

La programmation en binaire étant loin d'être évidente pour un humain, on a inventé le langage assembleur qui permet d'écrire les instructions de manière plus compréhensible. Dans notre exemple le code 0100110000100101 est remplacé par :

0100110000100101

ADD R1,37

Ce qui est tout de même déjà beaucoup plus lisible !

Voici un exemple de programme assembleur :

Programme

Text Only
INP R0,2
INP R1,2
ADD R2,R1,R0
OUT R2,4
HALT

Le langage assembleur est donc une simple traduction brute du langage machine. Pour résumer :

  • Le langage machine est une succession de bits qui est directement interprétable par le processeur d'un ordinateur.
  • Un langage assembleur est le langage machine où les combinaisons de bits sont représentées par des "symboles" qu'un être humain peut mémoriser.
  • Un programme assembleur convertit ces "symboles" en la combinaison de bits correspondante pour que le processeur puisse traiter l'information. Le programme assembleur traduit donc le langage assembleur en langage machine.

Remarques : - Un langage assembleur est souvent spécifique à un type de processeur. - Un langage assembleur est appelé "langage de bas niveau" car il est très proche du langage machine.

Les compilateurs / interpréteurs⚓︎

Le langage assembleur n'est toutefois pas facile à manipuler. C'est pourquoi il a été conçu des langages de programmation plus agréable à utiliser : les langages de haut niveau (Ex : C, Python, Javascript,...).

On parle également de niveau d'abstraction d'un langage. Plus celui-ci est proche de notre langage naturel et plus son niveau d'abstraction est élevé. Plus le langage est proche de la machine (binaire) plus celui-ci est de bas niveau.

image

Mais tout langage de programmation, pour être exécuté par une machine, doit être à un moment où à un autre traduit en langage binaire.

image

Il existe plusieurs manières de procéder :

  • La première consiste à traduire le programme dans son ensemble une fois pour toute et générer un fichier avec le code binaire prêt à être exécuté. Il s'agit de la méthode dîte de compilation, réalisée par un compilateur. Le langage C est un exemple de langage compilé.
  • La deuxième méthode consiste à traduire les instructions en langage binaire au fur et à mesure de la lecture du programme. Il s'agit de la méthode dîte d'interprétation, réalisée par un interpréteur. Le langage Basic est un exemple de langage interpété.
  • Enfin il existe des méthodes mixtes qui consistent à traduire le programme en pseudo-code (bytecode). Ce pseudo-code est interprété par une machine virtuelle au moment de l'execution. L'intérêt de cette approche est que l'execution et la traduction du pseudo-code en langage binaire est plus rapide. Mais également, le fait que ce pseudo-code permet une certaine indépendance vis à vis du processeur sur lequel il est exécuté. En effet, il suffit juste de disposer d'une machine virtuelle spécifique au processeur en question. Python et Java sont des exemples de langages utilisant cette technique.

3. L'assembleur, en pratique⚓︎

Présentation du simulateur de processeur AQA⚓︎

Nous allons utiliser un simulateur d’architecture de Von Neumann, réalisé par Peter Higginson pour préparer des étudiants anglais à leur examen de Computer Science. Il se nomme AQUA et on peut l’exécuter en ligne.

Quelques principes de base :

  • On ne peut pas définir de variables. Les données manipulées sont soient stockées à un endroit précis en mémoire soit dans un des registres R0 à R12.
  • Pour calculer avec une donnée en mémoire, il faut d’abord la transférer dans un registre.
  • L’interface se divise verticalement en trois zones :
  • À gauche, l’éditeur de programme en assembleur.
    • On remplit le formulaire et on le soumet avec submit.
    • Puis on assemble le programme en mémoire avec assemble (parfois fait automatiquement).
    • On l’exécute avec run (Plusieurs vitesses d’exécution sont disponibles).
  • Au centre, le processeur, avec :
    • les treize registres de données de R0 à R12.
    • le Compteur de Programme PC.
    • l’ Unité de Contrôle avec son Registre d’Instruction CIR.
    • l’ALU avec ses quatre drapeaux de test (Z pour zéro, N pour négatif, C pour carry, retenue et V pour overflow).
    • les bus reliant les différents composants du processeur et la mémoire (en bleu).
    • Les registres MAR et MBR servent à transférer des données entre la mémoire et les registres :
    • MAR contient l’adresse (en décimal) où l’on veut lire ou écrire.
    • MBR la valeur lue ou à écrire (en hexadécimal).
  • À droite, la mémoire divisée en mots de largeur 32 bits et dont les adresses commencent à 0. Dans "OPTIONS" on peut choisir le format d’affichage (décimal signé ou non, binaire, hexadécimal).

Remarque : il n’existe pas de structure de contrôle conditionnelle comme le "if... then... else" ou les boucles "while", "for". Pour les implémenter, on utilise des instructions de saut inconditionnel ou conditionnel en fonction du résultat de la comparaison précédente. Les points de chute de saut sont repérés par des étiquettes placées dans le programme.

AQA et le modèle de Von Neumann⚓︎

Ci-dessous, sont encadrés les quatre éléments constitutifs d'une architecture de Von Neumann :

image

Légende des encadrements : - En rouge, le processeur (CPU), comprenant : - En rose, l'unité de contrôle (UC) - En bleu, l'untité arithmétique et logique (UAL) - En vert, la mémoire vive (RAM)

La RAM⚓︎

Le contenu des différentes cellules de la mémoire peut être affiché dans différents formats ![AQA_options_RAM.image

  • base 10 (entier signé, signed) – par défaut
  • base 10 (entier non-signé, unsigned),
  • base 16 (hex),
  • base 2 (binary).
  • ...

Chaque cellule de la mémoire est accessible par son adresse. Il existe deux formats d’adressage des cellules de la mémoire : - 32 bits – format mot (option word mode) (par défaut) les adresses vont de 000 à 199 (codée en base 10). - 8 bits – format octet (option byte mode) les adresses vont de 000 à 799 (codée en base 10).

image

Le CPU⚓︎

Dans la partie centrale du simulateur, on trouve les différent composants du microprocesseur : - les registres (Registers) : 13 registres (R0 à R12) image - 1 registre spécial (PC) qui contient l’adresse mémoire de l’instruction en cours d’exécution image

  • L’unité de commande (Control Unit) qui contient l’instruction machine en cours d’exécution (au format hexadécimal) image
  • l’unité arithmétique et logique (Arithmetic and Logic Unit) image

Mon premier programme en assembleur⚓︎

Le jeu d’instructions AQA est précisé dans la documentation.

Remarque préalable : les opérandes ont la syntaxe suivante... - Rn correspond au registre numéro n. - #n correspond à une valeur entière immédiate n (sans passer par une mémoire). - n correspond à une adresse mémoire n (dans la RAM).

Voici quelques exemples d’instructions d’opérations arithmétiques et de transfert de mémoire :

Instruction Traduction
LDR R1, 78 Charge dans le registre R1 la valeur stockée en mémoire à l’adresse 78
STR R1, 123 Stocke le contenu du registre R1 à l’adresse 123 en mémoire
LDR R1, [R2] Charge dans le registre R1 la valeur stockée en mémoire à l’adresse contenue dans le registre R2
ADD R1, R0, #128 Additionne le nombre 128 et la valeur stockée dans le registre R0. Place le résultat dans le registre R1
SUB R1,R0,#128 Soustrait le nombre 128 de la valeur stockée dans le registre R0, place le résultat dans le registre R1
SUB R0,R1,R2 Soustrait la valeur stockée dans le registre R2 de la valeur stockée dans le registre R1, place le résultat dans le registre R0
MOV R1, #23 Place le nombre 23 dans le registre R1
MOV R0, R3 Place la valeur stockée dans le registre R3 dans le registre R0
OUT R1, 4 Affiche en sortie 4 la valeur contenue dans le registre R1
HALT Symbole de fin de programme, indispensable pour que le programme se termine sans erreur
  1. Ouvrir le simulateur AQUA. Régler la mémoire de sorte d’avoir un affichage hexadécimal, avec des cellules au format 32 bits.

  2. Saisir le programme ci-dessous dans la fenêtre d’édition puis le soumettre avec submit .

Text Only
    MOV R0, #42  
    STR R0, 150 
    HALT

Le programme vient d’être mis dans les cellules mémoires d’adresses 000, 001 et 002 : image

L’assembleur a converti les 3 lignes du programme en instructions machines, chacune occupant une cellule de 32 bits :

Text Only
    adresse 000 : MOV R0,#42
    adresse 001 : STR R0,150
    adresse 002 : HALT

Pour exécuter un programme, il suffit de cliquer sur le bouton RUN (exécution en continu) ou STEP (exécution pas à pas).

Par défaut, le simulateur montre comment il « travaille » par une animation. La vitesse d’animation est réglable à l’aide des boutons << et >> apparaissant à coté du bouton STOP qui permet de mettre l’exécution en pause. Tester l’exécution du code, en ralentissant suffisamment la vitesse afin de bien comprendre toutes les étapes de cette exécution.

On constate que deux types de valeurs circulent au sein du système : - des données (valeurs lues/écrites de/vers la mémoire/les registres/l’unité de commande) - des adresses des cellules de mémoire image

Une fois la simulation terminée, on peut constater que la cellule mémoire d’adresse 150, contient bien le nombre 42 (en base 10). il en est de même pour le registre R0.

Pour remettre la mémoire à 0, il faut cliquer sur le bouton OPTIONS et choisir clr memory.

Mon deuxième programme en assembleur⚓︎

Programme

Text Only
MOV R0, #10  
MOV R1, #250  
ADD R2, R1, R0  
OUT R2, 4  
HALT
  1. Constater que...
    • les 5 lignes d'instructions (ligne 0 à 4) sont bien enregistrées dans la RAM aux emplacements mémoire de 0 à 4.
    • les emplacements de mémoire de registre sont vides (valeurs nulles).
  2. Répérer...
    • l'Unité de Contrôle (UC) qui décode l'instruction en cours.
    • l'Unité Arithmétique et Logique (UAL) qui prend deux opérandes en entrée pour sortir le résultat de son calcul.
    • le registre du compteur de programme (PC) qui stocke l'emplacement mémoire de la prochaine instruction à aller chercher.
    • Le champ d'entrée clavier (input).
    • le champ de sortie qui servira d'affichage (output).
  3. Exécuter le programme pas à pas (step) en vitesse lente ("OPTIONS" : "def slow"). A la fin, relancer l'exécution pour essayer de comprendre au mieux les étapes de ce programme.

  4. Décrire à l'écrit l’enchaînement des opérations élémentaires effectuées lors de l’exécution des instructions.

  5. Pour chaque ligne d'instruction, décrire le rôle du PC, de l'UC, de la RAM, du l'UAL. On considérera l'état avant toute exécution de la ligne étudiée (ex: initialement, on étudie l'état des composants avant le début d'exécution de la ligne 0)
  6. Résumer vos notes dans ce tableau à compléter :
Ligne Instruction en assembleur Instruction en hexadécimal Etat/rôle du PC Etat/rôle de l'UC Etat/rôle de l'UAL Etat/rôle de la RAM Etat/rôle du registre
0 MOV R0, #10 e3a0000a Prochaine adresse à chercher : 0 Vide Aucun rôle Zone 0 en attente de chargement Vide
1 MOV R0, #250 e3a010fa Prochaine adresse à chercher : 1 A décodé l'instruction e3a0000a. En attente de l'instruction e3a010fa Aucun rôle Zone 0 chargée. Zone 1 en attente de chargement valeur 10 affectée au registre R0
... ... ... ... ... ... ... ...

Mon troisième programme en assembleur⚓︎

Comprendre le programme suivant :

Programme

Text Only
MOV R0, #10  
LDR R1, 10  
ADD R2, R1, R0  
STR R2, 11  
HALT

Pour l'étude vous pouvez : Sélectionner l’affichage Unsigned. Exécuter le programme pas à pas (step) en vitesse lente (options puis def slow).

  • Analysez l’enchaînement d’opérations élémentaires lors de l’exécution des instructions de transfert de mémoire MOV R0,#10 puis LDR R1,10.
  • Observer l’évolution des registres PC (Compteur de programme), CIR (Registre d’instructions), MAR (adresse d’écriture/lecture en mémoire) et MBR (donnée à lire/écrire).
  • Observez pour quelle(s) instruction(s), l’ALU est-elle sollicitée ?

Mon quatrième programme en assembleur⚓︎

Analysez l’enchaînement d’opérations élémentaires lors de l’exécution des instructions.

Sélectionner l’affichage Unsigned. Exécuter le programme pas à pas (step) en vitesse lente (options puis def slow).

  • Où sont stockées dans la mémoire centrale les quatre valeurs calculées par ce programme ? Il s’agit des premières valeurs d’une suite célèbre, laquelle ?
  • Rajouter les calculs pour le prochain terme de la suite, par copier-coller (rajouter donc l'itération 5), puis exécuter le programme dans le simulateur. Observer l’état de la mémoire.
  • même travail pour l'itération 6.
  • Et enfin compléter le programme pour réaliser l'itération 7. Expliquer l’erreur signalée par l’Unité de Contrôle et corriger le programme.
Text Only
//initialisation 
    MOV R0, #30
    MOV R1, #1
    STR R1, [R0]
    ADD R0, R0, #1
    MOV R2, #1
//itération 1
    STR R2, [R0]
    ADD R2, R2, R1
    LDR R1, [R0]
    ADD R0, R0, #1
//itération 2
    STR R2, [R0]
    ADD R2, R2, R1
    LDR R1, [R0]
    ADD R0, R0, #1
//itération 3
    STR R2, [R0]
    ADD R2, R2, R1
    LDR R1, [R0]
    ADD R0, R0, #1
//itération 4
    STR R2, [R0]
    ADD R2, R2, R1
    LDR R1, [R0]
    ADD R0, R0, #1
//fin
    HALT

Mon cinquième programme en assembleur : Entrées - sorties en assembleur (INP ; OUT)⚓︎

Dans le menu "SELECT", choisir le programme "add".

Le programme va se charger dans la zone d'édition.

Quelles sont les nouvelles instructions utilisées dans ce programme ?

Comme précédemment, décrire précisément la suite des instructions de ce programme d'addition.

Mon sixième programme en assembleur.⚓︎

Programmer une instruction conditionnelle en assembleur

Soit le programme :

Text Only
//Lecture d’un entier dans Input et chargement dans le registre R0
    INP R0, 2
//Comparaison du registre R0 avec le nombre 0
    CMP R0, #0
//Branchement conditionnel sur l’étiquette else si R0 négatif
    BLT else
    MOV R1, R0
//Branchement inconditionnel sur l’étiquette fin
    B fin
//étiquette else
else:
    MOV R2, #0
    SUB R1, R2, R0
//étiquette fin
fin:
//affichage du registre R1 dans Output
    OUT R1, 4
    HALT
  • else: et fin: sont des étiquettes qui jouent le rôle de repères / points de chute, dans les instructions de branchement / saut. Une étiquette est un mot suivi du symbole colonne :

  • CMP R0,#0 est une instruction de comparaison qui compare le contenu du registre R0 au nombre 0. Elle est suivie d’une instruction de branchement (ou saut) conditionnel BLT else : le programme se poursuit soit à partir de l’étiquette else si R0 est plus petit que 0, sinon avec l’instruction de la ligne suivante (comportement par défaut).

  • B fin est une instruction de branchement / saut inconditionnel : le programme se poursuit à partir de l’étiquette fin, le flux normal (passage à la ligne suivante) est interrompu.

  • Pour bien comprendre, le fonctionnement des instructions de branchement, exécuter le programme dans le simulateur en mode pas à pas, avec une vitesse lente au niveau des instructions BLT else et B fin. Effectuer un test avec une valeur positive 4 et l’autre avec une valeur négative −4. Noter que le Compteur de Programme PC est incrémenté par défaut de 1 pour chaque instruction mais qu’il peut être de plus modifié par une instruction de branchement.

Ci-dessous une aide pour comprendre les opérations qu'un microprocesseur peut effectuer :

image

Mon septième programme en assembleur.⚓︎

On considère le programme Python ci-dessous :

Python
    a = int(input()) #entier lu stocké dans le registre R0
    b = int(input()) #entier lu stocké dans le registre R1
    if a > b:
        m = a
    else:
        m = b
    #le maximum m de a et b est stocké dans le registre R2
    #et en mémoire centrale à l’adresse 20
    print(m)
Traduire ce programme en assembleur puis le tester dans le simulateur.

Mon huitième programme en assembleur.⚓︎

Programmer une boucle en assembleur

Dans le simulateur AQUA, sélectionner puis exécuter le programme ascii en mode pas à pas. Observer l’évolution du Compteur de Programme PC lors de chaque exécution du branchement conditionnel BLT LOOP.

Text Only
//initialise le registre R2 avec le nombre 32
    MOV R2,#32
LOOP:
//affiche dans Output le caractère dont le code ascii est contenu dans R2
    OUT R2,7
//incrémente R2
    ADD R2,R2,#1
//compare R2 avec 127
    CMP R2,#127
//si R2 < 127 branchement conditionnel sur l’étiquette loop
BLT LOOP
//sinon le programme se poursuit
    MOV R2,#10
//affichage du caractère de code ascii 10 qui est un saut de ligne
    OUT R2,7
    HALT
Ce programme permet d’afficher tous les caractères dont le code ascii est compris entre 32 et 126, par ordre croissant du code. C’est une implémentation de boucle while en assembleur, une traduction en Python pourrait être :

Python
   code_ascii = 32
   while code_ascii < 127:
        print(chr(code_ascii), end =’’)
        code_ascii = code_ascii + 1
    print()

Résumé

Le langage assembleur est donc une simple traduction brute du langage machine. Pour résumer :

  • Le langage machine est une succession de bits qui est directement interprétable par le processeur d'un ordinateur.
  • Un langage assembleur est le langage machine où les combinaisons de bits sont représentées par des "symboles" qu'un être humain peut mémoriser.
  • Un programme assembleur convertit ces "symboles" en la combinaison de bits correspondante pour que le processeur puisse traiter l'information. Le programme assembleur traduit donc le langage assembleur en langage machine.

Remarques : - Un langage assembleur est souvent spécifique à un type de processeur. - Un langage assembleur est appelé "langage de bas niveau" car il est très proche du langage machine.

4. Exercices⚓︎

Exercices