Portes logiques et langage machine
Quand tu additionnes $1 + 1$ sur ta calculatrice, le résultat apparaît instantanément. Pourtant, à l'intérieur du processeur, il n'y a que des fils qui portent soit 0 soit 1. Comment une machine qui ne sait manipuler que des 0 et des 1 peut-elle additionner deux nombres, puis exécuter un programme entier ? Réponse dans cette page : avec des opérateurs booléens, puis avec une suite d'instructions très simples.
Les opérateurs booléens et le xor
Une valeur booléenne vaut 0 ou 1 (en Python : False ou True). Trois opérateurs de base :
| $a$ | $b$ | $a$ and $b$ | $a$ or $b$ | not $a$ |
|---|---|---|---|---|
| 0 | 0 | 0 | 0 | 1 |
| 0 | 1 | 0 | 1 | 1 |
| 1 | 0 | 0 | 1 | 0 |
| 1 | 1 | 1 | 1 | 0 |
Un quatrième opérateur est très utile : le ou exclusif, noté xor ou $\oplus$. Il vaut 1 quand les deux entrées sont différentes.
| $a$ | $b$ | $a \oplus b$ |
|---|---|---|
| 0 | 0 | 0 |
| 0 | 1 | 1 |
| 1 | 0 | 1 |
| 1 | 1 | 0 |
Calculons 0b1100 ^ 0b1010 : on applique le xor colonne par colonne.
Résultat : $0110$. En refaisant le xor avec $1010$, on retrouve $1100$.
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 en Python
En Python, and et or évaluent leurs opérandes de gauche à droite et s'arrêtent dès que le résultat est connu. C'est l'évaluation séquentielle paresseuse.
-
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é.
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 !
Cette propriété sert à se protéger d'une erreur : if a != 0 and b // a > 2: ne divise jamais par zéro, car si a vaut 0 la première condition est fausse.
Les lois de De Morgan permettent de réécrire une condition : $\overline{a \land b} = \bar a \lor \bar b$ et $\overline{a \lor b} = \bar a \land \bar b$. Ainsi a != 0 and C équivaut à not (a == 0 or not C).
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
Additionne deux bits $a$ et $b$ : $0+0=0$, $0+1=1$, $1+0=1$, $1+1=10_2$. Il faut donc deux sorties : la somme $s$ et la retenue $r$.
| $a$ | $b$ | $r$ | $s$ |
|---|---|---|---|
| 0 | 0 | 0 | 0 |
| 0 | 1 | 0 | 1 |
| 1 | 0 | 0 | 1 |
| 1 | 1 | 1 | 0 |
On reconnaît : $s = a \oplus b$ et $r = a \land b$. C'est le demi-additionneur.
Pour additionner des nombres de plusieurs bits, chaque colonne doit aussi absorber la retenue entrante $r_{in}$ : c'est l'additionneur complet, avec trois entrées $a$, $b$, $r_{in}$.
Pourquoi ? La somme vaut 1 s'il y a un nombre impair de 1 parmi les trois entrées (propriété du xor). Il y a une retenue sortante dès que deux bits au moins valent 1 : soit $a$ et $b$ valent 1, soit un seul des deux vaut 1 et $r_{in}$ aussi.
Colonne 3 : $s=1$, $r_{out}=0$. Résultat : $1010_2 = 10$. ✓
On chaîne les additionneurs : la retenue sortante de la colonne $i$ est la retenue entrante de la colonne $i+1$. Pour $n$ bits, il faut $n$ additionneurs complets. Chacun contient 2 xor, 2 and et 1 or, soit 5 portes : 40 portes pour 8 bits. Comme chaque colonne attend la retenue de la précédente, le temps de calcul croît avec $n$ : c'est le temps de propagation de la retenue.
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
Dans le modèle de von Neumann, la mémoire contient à la fois les instructions et les données. Le processeur répète sans cesse le cycle :
-
chercher l'instruction dont l'adresse est dans le compteur ordinal (PC) ;
-
décoder cette instruction (unité de contrôle) ;
-
exécuter (UAL, registres, mémoire) et mettre à jour le PC (PC + 1, sauf saut).
Voici quelques instructions d'un langage machine simplifié :
| Instruction | Effet |
|---|---|
MOV Rd, #n |
$R_d \leftarrow n$ |
LDR Rd, a |
$R_d \leftarrow$ mémoire[a] |
STR Rs, a |
mémoire[a] $\leftarrow R_s$ |
ADD Rd, Rs / SUB Rd, Rs |
$R_d \leftarrow R_d \pm R_s$ |
SUB Rd, #n |
$R_d \leftarrow R_d - n$ |
CMP Rd, #n |
compare $R_d$ et $n$ (mémorise le résultat) |
BGT a, BNE a, BRA a |
saut à l'adresse a si $>$, si $\neq$, toujours |
HALT |
arrêt |
R1 = 8 − 13 = −5. Une bonne méthode : tracer un tableau avec une ligne par instruction et une colonne par registre (et par case mémoire utile).
Une boucle se construit avec un saut en arrière.
Tour 3 : R0 = 5 + 1 = 6, R1 = 0, le saut n'est pas pris, PC = 5 puis HALT. Résultat : R0 = 6 = 3 + 2 + 1. Ici, le variant est R1 : il vaut 3, 2, 1 tant que la condition de la boucle est vraie, puis 0 à la sortie. Pour qu'une boucle se termine, il faut un variant (un entier naturel, c'est-à-dire positif ou nul, qui décroît strictement à chaque tour tant que la condition de la boucle est vraie) : par exemple un compteur décrémenté de 1 et comparé à 0 avec BNE.
# équivalent Python de la somme 1 + 2 + ... + n
r0 = 0
r1 = n
while r1 != 0: # variant : r1, entier décroissant, borné par 0
r0 += r1
r1 -= 1
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 : un processeur ne manipule que des 0 et des 1, mais avec des portes xor et and il additionne des bits, et en répétant le cycle chercher-décoder-exécuter, il déroule n'importe quel programme.