25 - Assembleur

QCM – vérification des prérequis

Pour chaque question, une seule réponse est correcte.

1. Quel composant exécute directement les instructions en langage machine ?

  • A. La mémoire vive (RAM)
  • B. Le disque dur
  • C. Le processeur (CPU)
  • D. La carte graphique
Correction

Réponse : C. Le processeur lit, décode et exécute les instructions binaires. Il contient l’unité arithmétique et logique (UAL) et l’unité de commande.

  • A est faux : la RAM stocke les instructions et données, mais ne les exécute pas.
  • B est faux : le disque dur est une mémoire de masse (stockage permanent).
  • D est faux : la carte graphique (GPU) est spécialisée dans le calcul graphique.

2. Dans le modèle de von Neumann, où sont stockés les programmes en cours d’exécution ?

  • A. Dans les registres du processeur
  • B. En mémoire vive (RAM)
  • C. Sur le disque dur
  • D. Dans la ROM
Correction

Réponse : B. Le principe fondamental du modèle de von Neumann est que les programmes et les données sont stockés dans la même mémoire (la mémoire vive), ce qui permet au processeur de les lire et de les modifier.

  • A est faux : les registres sont de petites mémoires ultra-rapides internes au processeur (quelques dizaines), pas suffisantes pour stocker un programme entier.
  • C est faux : le disque dur est trop lent ; le programme est chargé en RAM avant exécution.
  • D est faux : la ROM contient le BIOS/firmware, pas les programmes utilisateur.

3. Que signifie l’instruction assembleur li $t0, 10 ?

  • A. Additionner 10 au registre $t0
  • B. Charger la valeur 10 dans le registre $t0
  • C. Stocker le contenu de $t0 à l’adresse mémoire 10
  • D. Comparer $t0 à 10
Correction

Réponse : B. li signifie load immediate : charger une valeur constante (immédiate) dans un registre. Après cette instruction, $t0 contient la valeur 10.

  • A est faux : l’addition serait addi $t0, $t0, 10.
  • C est faux : le stockage en mémoire utilise l’instruction sw (store word).
  • D est faux : la comparaison utilise beq, bne ou slt.

4. Quel est le rôle du registre « compteur de programme » (PC) ?

  • A. Compter le nombre de programmes en cours d’exécution
  • B. Stocker l’adresse de la prochaine instruction à exécuter
  • C. Contenir le résultat du dernier calcul
  • D. Mesurer le temps d’exécution
Correction

Réponse : B. Le compteur de programme (Program Counter, PC) contient l’adresse mémoire de la prochaine instruction à exécuter. Après chaque instruction, il est incrémenté (ou modifié par un branchement).

  • A est faux : le PC ne compte pas les programmes, il pointe vers l’instruction suivante.
  • C est faux : les résultats des calculs sont stockés dans les registres de données.
  • D est faux : le PC n’a rien à voir avec le chronométrage.

Rappels de cours

Le langage assembleur

Un processeur est constitué de circuits logiques, etc. Il ne prend en entrée que des 0 et des 1, et ne ressort que des 0 et des 1. À l’aide, par exemple, de circuits comme un multiplexeur, on peut voir qu’il peut utiliser des parties de circuits différentes suivant le code binaire reçu. Ainsi, on peut imaginer qu’un signal 00 1100 0101 lui fasse réaliser l’addition entre 1100 et 0101, et qu’un signal 01 1100 0101 lui fasse réaliser la soustraction entre 1100 et 0101.

Un ordinateur fonctionne donc avec des instructions passées en binaire (des 0 et des 1), toutes codées sur le même nombre de bits (par ex : 32 bits pour un processeur 32 bits).

Par exemple :

En binaireEn hexadécimalEn assembleur
001001000000100000000000000010102408000ali $t0, 10
001001000000100100000000000011002409000cli $t1, 12
0000000100001001010100000010000001095020add $t2, $t0, $t1

Pour plus de lisibilité, on peut utiliser un langage d’assemblage, ou langage assembleur, qui représente les instructions par des codes faciles à lire et à retenir pour un humain. Lors de l’assemblage, chaque code d’instruction est traduit en nombre. Chaque instruction correspond à une des tâches basiques que sait faire le CPU : charger une valeur dans une des cases du registre, additionner deux valeurs et charger le résultat dans une des cases du registre, etc.

C’est quoi le registre ? Comment fonctionne un processeur ?

Prenons l’exemple d’un processeur MIPS R2000. Il s’agit d’un processeur 32 bits (= 4 octets, c’est la taille des registres) constitué de :

  • 32 registres de 32 bits (stocke les valeurs temporaires, les pointeurs d’adresses, etc.)
  • un compteur de programmes PC (= Program Counter) sur 32 bits (stocke l’adresse de l’instruction en cours d’exécution)
  • un registre d’instruction RI sur 32 bits (stocke l’instruction en cours d’exécution, chaque instruction est donc codée sur 32 bits.)
  • une mémoire vive adressable de 2^32 octets (permet de stocker les instructions du programme, les données, etc.)

La mémoire est comme un grand tableau dont chaque case mesure 32 bits (4 octets), la mémoire mesure \(2^{32}\) octets, donc elle contient \(2^{30}\) cases de 4 octets. Pour les repérer, on utilise des adresses, également sur 32 bits, il y a donc \(2^{32}\) adresses différentes. Chaque case mémoire (qui prend 4 octets) a donc une adresse qui est un multiple de 4. Ces adresses peuvent être représentées :

  • en binaire : \(00000000 00000000 00000000 00000000_2\) est la toute première adresse (0) ; et \(11111111 11111111 11111111 11111100_2\) est l’adresse de la dernière case.
  • en hexadécimal : \(00 00 00 00_{16}\) est la toute première adresse (0) ; et \(FF FF FF FC_{16}\) est l’adresse de la dernière case.

Voir schéma en bas de cette page (annexe A)

Concrètement, que se passe-t-il lorsqu’on exécute un programme ?

Le langage Python que nous utilisons en classe est un langage de « haut niveau », évolué. Lorsqu’il est interprété, il est traduit en langage « machine », de manière à pouvoir être exécuté sur un ordinateur.

À partir du langage machine, lorsqu’on fait exécuter le programme :

  • le programme est stocké en mémoire :
    • les lignes de code sont chargées dans les cases de la mémoire allouées au code,
    • les constantes (données = .data) du programme sont chargées dans les cases de la mémoire allouées aux données.
  • Le PC s’initialise à la première ligne de code, l’exécute, il peut s’agir :
    • d’affectations de variables,
    • de calculs par le CPU,
    • de sauts (branchements) dans le programme en modifiant la valeur du PC.

S’il n’y a pas eu de branchement, le PC passe à la ligne suivante (son adresse prend \(+4\)).

Exemple 1

Langage assembleurRôleEn Python
.textAnnonce que les lignes suivantes sont des instructions à sauvegarder ligne par ligne dans la mémoire, et à exécuter.On suppose que la variable x est stockée dans le registre $t0 et y dans le registre $t1.
li $t0, 10Charge la valeur immédiate 10 dans le registre $t0.x = 10
li $t1, 12Charge la valeur immédiate 12 dans le registre $t1y = 12
add $t2, $t0, $t1Copie dans le registre $t2 le résultat de la somme du contenu du registre $t0 avec le contenu du registre $t1.z = x + y
li $v0, 1Charge la valeur immédiate 1 dans le registre $v0 pour annoncer que le prochain appel système syscall sera un print_int.print(z)
move $a0, $t2Copie le contenu de $t2 dans le registre $a0 pour annoncer que le prochain appel système syscall utilisera le contenu de la variable $a0, c’est-à-dire le contenu de $t2.print(z)
syscallAppel système : exécute le print_int contenu dans $a0print(z)
li $v0,10Charge la valeur immédiate 10 dans le registre $v0 pour annoncer que le prochain appel système syscall sera exit.Fin du programme.
syscallAppel système : exit.

Pour ce qui suit, on se servira des tables en bas de cette page (annexe B): MIPS Reference Card.

Exercice 1

Traduire ce programme en Python le programme en assembleur suivant.

.text

      li $v0, 5
      syscall
      move $t0, $v0

      li $v0, 5
      syscall
      move $t1, $v0

      add $t2, $t0, $t1

      li $v0, 1
      move $a0, $t2
      syscall

      li $v0,10
      syscall

Structures conditionnelle et boucles

Toutes ces structures ont pour conséquence de ne pas toujours exécuter toutes les lignes d’instructions les unes après les autres, dans l’ordre dans lequel elles sont écrites. On réalise donc des branchements (jump) en affectant au PC l’adresse de la prochaine instruction à réaliser.

En assembleur, les labels servent à référencer certaines parties du programme en donnant un nom à une ligne du programme. Ceci permet d’y faire référence pour faire des sauts dans le programme, pour réaliser des branchements, pour des structures conditionnelles ou des boucles. Nous ignorerons les questions de « délais de branchement ».

Les branchements conditionnels

Le résultat d’un test peut envoyer à une autre adresse d’instruction. Par exemple, pour la ligne d’instruction : beq $t0, $t1, Label ; si la valeur contenue dans le registre $t0 est égale à celle stockée dans le registre $t1 alors la prochaine instruction à exécuter est celle placée après l’étiquette Label. beq vient de branch on equal (on fait le branchement si c’est égal). Il y a aussi bne (branch on not equal), bgt (branch greater than) etc.

Exemple 2

      li $t0, 2
      li $t1, 3
      blt $t0, $t1, endif # branch less than
      move $t1, $t0

endif:li $v0, 1     # print_int
      move $a0, $t1
      syscall

Code Python

x = 2
y = 3

if x >= y : # si x < y, on va directement à endif, sinon, on exécute tout
   y = x
print(y) # on affiche le max des deux

Exercice 2

Traduire le programme suivant en Python.

.text
      li $v0, 5     # read_int
      syscall
      move $t0, $v0
      bge $t0, $zero, endif # greater than or equal 0
      sub $t0, $zero, $t0

endif:li $v0, 1     # print_int
      move $a0, $t0
      syscall

Les branchements inconditionnels

Une instruction peut envoyer directement (sans condition) à une autre adresse d’instruction. Par exemple, la ligne d’instruction : j Label renvoie à la ligne Label.

Exemple 3

      .data
msg : .asciiz "gagné !"    # on sauve une donnée string
.text
      li $t0, 5
      li $t1, 0
while:beq $t0, $t1, end    # si $t0=$t1, aller à end
      li $v0, 5        # read_int
      syscall
      move $t1, $v0
      j while    # renvoie à la ligne while
end:  li $v0, 4        # print_string
      la $a0, msg    # adresse de la chaîne à imprimer
      syscall    # imprime le message

Code Python

x = 5
y = 0

while x != y : # si x = y, on arrête la boucle
   y = int(input())
print("gagné !")

Exercice 3

Traduire ce programme en Python. Que fait-il ?

      .data
msg:  .asciiz "reste ="    # on sauve une donnée string
.text
      li $v0, 5        # read_int
      syscall
      move $t0, $v0

      li $v0, 5        # read_int
      syscall
      move $t1, $v0

      while:blt $t0, $t1, end    # si $t0 < $t1, aller à end
      sub $t0, $t0, $t1
      j while    #retour au Label while

end:  li $v0, 4        # print_string
      la $a0, msg    # adresse de la chaîne à imprimer
      syscall    # on l’imprime

      li $v0, 1        # print_int
      move $a0, $t0    # valeur de l’entier à imprimer
      syscall    # on l’imprime

Exercice 4

Traduire ce programme en Python. Que fait-il ?

      .data
msg:  .asciiz "résultat ="   # on sauve une donnée string
.text
      li $t0, 15
      li $t1, 1
      li $t2, 0

while:bgt $t1, $t0, end   #si $t2 > $t0, aller à end
      add $t2, $t2, $t1
      addi $t1, $t1, 1
      j while   #retour au Label while

end:  li $v0, 4       # print_string
      la $a0, msg   # adresse de la chaîne à imprimer
      syscall   # on l’imprime

      li $v0, 1       # print_int
      move $a0, $t2   # valeur de l’entier à imprimer
      syscall   # on l’imprime

Exercice 5

Traduire ce programme en Python. Que fait-il ?

      .data
msg:  .asciiz "résultat ="   # on sauve une donnée string
.text
      li $t0, 10
      li $t1, 1
      li $t2, 2

while:bgt $t2, $t0, end   #si $t2 > $t0, aller à end
      mul $t1, $t1, $t2
      addi $t2, $t2, 1
      j while   #retour au Label while

end:  li $v0, 4       # print_string
      la $a0, msg   # adresse de la chaîne à imprimer
      syscall   # on l’imprime

      li $v0, 1       # print_int
      move $a0, $t1   # valeur de l’entier à imprimer
      syscall   # on l’imprime

Annexes

Annexe A

Annexe B