Portes logiques et langage machine
Ton ordinateur calcule 6 + 7 sans connaître autre chose que des 0 et des 1. Comment un processeur, fabriqué avec de simples portes logiques, arrive-t-il à additionner ? Et comment exécute-t-il un programme, instruction après instruction ? Dans cette page, tu vas construire la réponse en trois étapes : les opérations booléennes, l'additionneur, puis le langage machine.
Le ou exclusif (xor)
Tu connais déjà and, or et not. Il manque une opération très utilisée en informatique : le ou exclusif, noté $\oplus$ (en Python : ^ sur les entiers). Il vaut 1 lorsque les deux bits sont différents. Le « ou » ordinaire (or) accepte que les deux soient à 1 ; le xor, non.
| $a$ | $b$ | $a$ and $b$ | $a$ or $b$ | $a \oplus b$ |
|---|---|---|---|---|
| 0 | 0 | 0 | 0 | 0 |
| 0 | 1 | 0 | 1 | 1 |
| 1 | 0 | 0 | 1 | 1 |
| 1 | 1 | 1 | 1 | 0 |
Essaie les entrées de la porte XOR :
Deux propriétés à connaître, elles servent partout :
Sur des entiers, ^ agit bit à bit, colonne par colonne.
Le xor est la base du masque jetable (chiffrement de Vernam). On chiffre un message $m$ avec une clé $k$ de même longueur : $c = m \oplus k$. Pour déchiffrer, on refait le même xor, car $c \oplus k = m \oplus k \oplus k = m$.
a) Donner la table de vérité de l'opération xor (ou exclusif) sur deux variables $a$ et $b$. b) Calculer 0b1100 ^ 0b1010 en binaire. c) Quelle est la valeur de $a$ xor…
- a) Donner la table de vérité de l'opération xor (ou exclusif) sur deux variables $a$ et $b$.
- b) Calculer
0b1100 ^ 0b1010en binaire.
- c) Quelle est la valeur de $a$ xor $a$ ? De $a$ xor 0 ?
Dans le chiffrement par masque jetable, on calcule $c = m \oplus k$ bit à bit. a) Chiffrer $m = (1011\,0010)2$ avec la clé $k = (0110\,1100)2$. b) Montrer que…
Dans le chiffrement par masque jetable, on calcule $c = m \oplus k$ bit à bit.
- a) Chiffrer $m = (1011\,0010)_2$ avec la clé $k = (0110\,1100)_2$.
- b) Montrer que $c \oplus k = m$.
- c) Pourquoi la clé ne doit-elle jamais être réutilisée ?
Évaluation séquentielle paresseuse
En mathématiques, $a \land b$ et $b \land a$ sont la même chose. En Python, l'ordre compte : les opérateurs and et or évaluent leurs opérandes de gauche à droite, et s'arrêtent dès que le résultat est connu.
-
A and B: siAest faux, le résultat est faux,Bn'est pas évalué. -
A or B: siAest vrai, le résultat est vrai,Bn'est pas évalué.
C'est ce qu'on appelle l'évaluation paresseuse (ou séquentielle).
t = [1, 2]
x = 0
print(x == 0 or t[5] == 1) # True : t[5] n'est jamais lu
print(x != 0 and t[5] == 1) # False : t[5] n'est jamais lu
print(t[5] == 1 or x == 0) # IndexError : t[5] est évalué en premier
On en tire un réflexe très utile : placer en premier le test qui « protège » le second.
if a != 0 and b // a > 2:
print("quotient supérieur à 2")
Pour $a = 0$, le premier test est faux : b // a n'est jamais calculé et il n'y a pas de ZeroDivisionError. Si on écrit b // a > 2 and a != 0, l'erreur se produit.
Les lois de De Morgan permettent de transformer une condition :
Pourquoi ? $A \land B$ est faux dès qu'au moins un des deux est faux : c'est exactement (not A) or (not B).
On considère les expressions Python avec x = 0 et t = [1, 2]. a) Donner la valeur de x == 0 or t[5] == 1. b) Donner la valeur de x != 0 and t[5] == 1. c) Expliquer…
On considère les expressions Python avec x = 0 et t = [1, 2].
- a) Donner la valeur de
x == 0 or t[5] == 1.
- b) Donner la valeur de
x != 0 and t[5] == 1.
- c) Expliquer pourquoi aucune erreur ne se produit dans ces deux cas (évaluation séquentielle paresseuse).
Soit une fonction f(a, b) qui, dans le cas où a est un entier non nul, calcule b // a. On écrit : if a != 0 and b // a > 2: .... a) Montrer que cette expression ne…
Soit une fonction f(a, b) qui, dans le cas où a est un entier non nul, calcule b // a. On écrit : if a != 0 and b // a > 2: ....
- a) Montrer que cette expression ne provoque pas d'erreur pour $a = 0$.
- b) Que se passe-t-il si on inverse l'ordre des deux conditions ?
- c) Donner une expression équivalente avec
oret la négation, et vérifier son équivalence par les lois de De Morgan.
Additionner avec des portes logiques
Additionnons deux bits $a$ et $b$ comme à l'école, avec une retenue :
-
$0 + 0 = 0$ ; $0 + 1 = 1$ ; $1 + 0 = 1$ ;
-
$1 + 1 = (10)_2$ : on écrit 0 et on retient 1.
On repère les deux colonnes de la table : le bit de somme $s$ vaut 1 quand $a \ne b$ (c'est un xor), et la retenue $r$ vaut 1 quand les deux bits sont à 1 (c'est un and).
| $a$ | $b$ | $s = a \oplus b$ | $r = a \land b$ |
|---|---|---|---|
| 0 | 0 | 0 | 0 |
| 0 | 1 | 1 | 0 |
| 1 | 0 | 1 | 0 |
| 1 | 1 | 0 | 1 |
Ce circuit à deux portes s'appelle le demi-additionneur :
Le demi-additionneur ne sait pas prendre en compte une retenue entrante. Pour additionner les colonnes suivantes, on utilise l'additionneur complet, qui a trois entrées : $a$, $b$ et la retenue $r_{in}$.
Pour additionner deux nombres de $n$ bits, on chaîne $n$ additionneurs complets : la retenue sortante de la colonne $i$ devient la retenue entrante de la colonne $i+1$.
Un additionneur un bit calcule la somme $s$ et la retenue $r$ de deux bits $a$ et $b$. a) Exprimer $s$ et $r$ à l'aide de xor et de and. b) Compléter la table de vérité…
Un additionneur un bit calcule la somme $s$ et la retenue $r$ de deux bits $a$ et $b$.
- a) Exprimer $s$ et $r$ à l'aide de xor et de and.
- b) Compléter la table de vérité pour les quatre cas possibles.
- c) Expliquer comment chaîner des additionneurs complets pour additionner deux nombres de 4 bits.
On veut construire un additionneur complet à partir des opérations xor, and et or. Il reçoit deux bits $a$ et $b$ à additionner et une retenue entrante $r_{in}$ ; il…
On veut construire un additionneur complet à partir des opérations xor, and et or. Il reçoit deux bits $a$ et $b$ à additionner et une retenue entrante $r_{in}$ ; il fournit le bit de somme $s$ et la retenue sortante $r_{out}$.
- a) Écrire $s$ et $r_{out}$ en fonction de $a$, $b$ et $r_{in}$.
- b) Vérifier le résultat par la table de vérité complète (huit lignes).
- c) En n'utilisant que des portes xor, and et or à deux entrées et un additionneur complet pour chaque bit, déterminer le nombre de portes nécessaires pour additionner deux nombres de 8 bits et justifier pourquoi le temps de propagation de la retenue limite la rapidité.
Dérouler un programme en langage machine
Un processeur n'exécute pas du Python, mais des instructions machine très simples. Elles sont stockées dans la mémoire, avec les données (modèle de von Neumann). Le processeur contient :
-
des registres (R0, R1, …) : de toutes petites mémoires très rapides ;
-
une UAL (unité arithmétique et logique) qui fait les calculs, construite avec des portes comme celles de la section précédente ;
-
une unité de contrôle et un compteur ordinal (PC) qui contient l'adresse de la prochaine instruction.
Pour chaque instruction, le processeur répète le même cycle : chercher l'instruction à l'adresse PC, la décoder, l'exécuter, puis passer à la suivante (PC augmente de 1, sauf en cas de branchement).
Voici le jeu d'instructions simplifié utilisé dans les exercices :
| Instruction | Effet |
|---|---|
MOV Rd, #n |
$R_d \leftarrow n$ |
LDR Rd, a |
$R_d \leftarrow$ contenu de la mémoire à l'adresse $a$ |
STR Rs, a |
mémoire à l'adresse $a$ $\leftarrow R_s$ |
ADD Rd, Rs |
$R_d \leftarrow R_d + R_s$ |
SUB Rd, Rs |
$R_d \leftarrow R_d - R_s$ |
CMP Rd, #n |
compare $R_d$ et $n$ (mémorise le résultat) |
BGT a / BNE a |
si la comparaison l'indique (strictement supérieur / différent), PC $\leftarrow a$ |
BRA a |
PC $\leftarrow a$ (branchement inconditionnel) |
HALT |
arrêt |
Pour les boucles, le branchement modifie PC : au lieu de passer à l'instruction suivante, on « saute » à une autre adresse. Pour être sûr qu'une boucle se termine, on cherche son variant : une quantité entière, positive, qui diminue strictement à chaque tour. Elle ne peut pas diminuer indéfiniment, donc la boucle s'arrête.
Par exemple, dans while n > 0: n = n - 1, la valeur de n est un entier positif ou nul qui diminue de 1 à chaque tour : c'est un variant, donc la boucle se termine.
Pour prouver qu'une boucle calcule le bon résultat, on utilise un invariant de boucle : une propriété vraie avant la boucle et conservée à chaque tour. Dans s = 0 puis for i in range(1, n + 1): s = s + i, l'invariant est « après $k$ tours, s vaut $1 + 2 + \dots + k$ ». À la fin, il donne le résultat voulu.
Un processeur simplifié possède des registres R0 à R3 et exécute les instructions : MOV Rd, #n (Rd reçoit n), ADD Rd, Rs (Rd reçoit Rd + Rs), SUB Rd, Rs. On exécute :…
Un processeur simplifié possède des registres R0 à R3 et exécute les instructions : MOV Rd, #n (Rd reçoit n), ADD Rd, Rs (Rd reçoit Rd + Rs), SUB Rd, Rs.
On exécute : MOV R0, #5, MOV R1, #8, ADD R0, R1, SUB R1, R0.
Donner le contenu de R0 et R1 après chaque instruction.
Un programme en assembleur simplifié est stocké en mémoire aux adresses 0 à 6 : On suppose que les cases 10 et 11 contiennent 7 et 9, et que la case 12 contient…
Un programme en assembleur simplifié est stocké en mémoire aux adresses 0 à 6 :
0: LDR R0, 10
1: LDR R1, 11
2: ADD R0, R1
3: CMP R0, #20
4: BGT 6
5: STR R0, 12
6: HALT
On suppose que les cases 10 et 11 contiennent 7 et 9, et que la case 12 contient initialement 0. BGT branche à l'adresse indiquée si le résultat est strictement supérieur.
- a) Suivre l'exécution et donner le contenu final de la mémoire à l'adresse 12.
- b) Même question si les cases 10 et 11 contiennent 15 et 9.
- c) Décrire en une phrase ce que fait ce programme.
Écrire un programme assembleur simplifié (instructions LDR, STR, ADD, SUB, CMP, BNE, BRA, HALT) qui calcule dans R0 la somme $1 + 2 + \dots + n$, où $n$ est lu à…
Écrire un programme assembleur simplifié (instructions LDR, STR, ADD, SUB, CMP, BNE, BRA, HALT) qui calcule dans R0 la somme $1 + 2 + \dots + n$, où $n$ est lu à l'adresse 20.
- a) Écrire le programme en commentant chaque instruction.
- b) Dérouler l'exécution pour $n = 3$ en donnant les registres à chaque étape.
- c) Identifier le variant de la boucle et justifier la terminaison.
Retour au début : le processeur calcule $6 + 7$ en chaînant des additionneurs complets (xor, and, or) et en exécutant des instructions comme LDR, ADD et STR.
L'essentiel
-
Le xor ($\oplus$,
^en Python) vaut 1 quand les bits sont différents : $a \oplus a = 0$, $a \oplus 0 = a$, donc $(m \oplus k) \oplus k = m$. -
En Python,
andetorsont évalués de gauche à droite et paresseusement : on place le test protecteur en premier (a != 0 and b // a > 2). -
De Morgan : $\lnot(A \land B) = \lnot A \lor \lnot B$ et $\lnot(A \lor B) = \lnot A \land \lnot B$.
-
Demi-additionneur : $s = a \oplus b$, $r = a \land b$. Additionneur complet : $s = a \oplus b \oplus r_{in}$, $r_{out} = (a \land b) \lor (r_{in} \land (a \oplus b))$. On les chaîne pour additionner des nombres de $n$ bits.
-
Un processeur exécute le cycle chercher-décoder-exécuter ; le compteur ordinal PC désigne la prochaine instruction et les branchements le modifient.
-
Pour montrer qu'une boucle se termine, on exhibe un variant.