Notes
Notes
01001001
01000110
01010100
00110010
00110000
00111001
00000000
Michael Blondin
22 février 2024
Avant-propos
cbn
Cette œuvre est mise à disposition selon les termes de la licence
Creative Commons Attribution-NonCommercial 4.0 International.
ii
Légende
Observation.
Remarque.
Les exercices marqués par « ⋆ » sont considérés plus avancés que les autres.
Les exercices marqués par « ⋆⋆ » sont difficiles ou dépassent le cadre du cours.
iii
Table des matières
1 Systèmes de numération 1
1.1 Représentation des nombres . . . . . . . . . . . . . . . . . . . . 1
1.1.1 Système unaire . . . . . . . . . . . . . . . . . . . . . . . 1
1.1.2 Système de numération positionnelle . . . . . . . . . . . 2
1.2 Changement de base . . . . . . . . . . . . . . . . . . . . . . . . 4
1.2.1 Base b vers base 10 . . . . . . . . . . . . . . . . . . . . . 4
1.2.2 Base 10 vers base b . . . . . . . . . . . . . . . . . . . . . 5
1.2.3 Base b vers base b′ . . . . . . . . . . . . . . . . . . . . . 6
1.3 Addition . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6
1.4 Nombres fractionnaires . . . . . . . . . . . . . . . . . . . . . . . 7
1.5 Quiz récapitulatif . . . . . . . . . . . . . . . . . . . . . . . . . . 9
1.6 Exercices . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9
iv
TABLE DES MATIÈRES v
4 Nombres entiers 37
4.1 Représentation des entiers signés . . . . . . . . . . . . . . . . . 37
4.2 Addition . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 39
4.2.1 Report . . . . . . . . . . . . . . . . . . . . . . . . . . . . 39
4.2.2 Débordement . . . . . . . . . . . . . . . . . . . . . . . . 39
4.3 Soustraction . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 40
4.4 Multiplication . . . . . . . . . . . . . . . . . . . . . . . . . . . . 40
4.4.1 Multiplication non signée . . . . . . . . . . . . . . . . . 40
4.4.2 Multiplication signée . . . . . . . . . . . . . . . . . . . . 42
4.5 Division . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 43
4.5.1 Division non signée . . . . . . . . . . . . . . . . . . . . . 43
4.5.2 Division signée . . . . . . . . . . . . . . . . . . . . . . . 44
4.6 Particularités de l’architecture ARMv8 . . . . . . . . . . . . . . 44
4.6.1 Codes de condition . . . . . . . . . . . . . . . . . . . . . 44
4.6.2 Accès mémoire . . . . . . . . . . . . . . . . . . . . . . . 45
4.7 Quiz récapitulatif . . . . . . . . . . . . . . . . . . . . . . . . . . 47
4.8 Exercices . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 47
6 Tableaux 57
TABLE DES MATIÈRES vi
7 Programmation structurée 68
7.1 Structures de contrôle . . . . . . . . . . . . . . . . . . . . . . . 68
7.1.1 Séquence . . . . . . . . . . . . . . . . . . . . . . . . . . 68
7.1.2 Sélection . . . . . . . . . . . . . . . . . . . . . . . . . . 69
7.1.3 Itération . . . . . . . . . . . . . . . . . . . . . . . . . . . 72
7.2 Sous-programmes . . . . . . . . . . . . . . . . . . . . . . . . . . 73
7.2.1 Paramètres et appel . . . . . . . . . . . . . . . . . . . . 73
7.2.2 Retour . . . . . . . . . . . . . . . . . . . . . . . . . . . . 74
7.2.3 Sauvegarde des registres . . . . . . . . . . . . . . . . . . 75
7.3 Autres particularités de l’architecture ARMv8 . . . . . . . . . . 76
7.3.1 Distance des adresses . . . . . . . . . . . . . . . . . . . 76
7.3.2 Assignation par sélection . . . . . . . . . . . . . . . . . . 76
7.4 Quiz récapitulatif . . . . . . . . . . . . . . . . . . . . . . . . . . 78
7.5 Exercices . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 78
8 Circuits logiques 79
8.1 Arithmétique . . . . . . . . . . . . . . . . . . . . . . . . . . . . 79
8.1.1 Addition de deux bits . . . . . . . . . . . . . . . . . . . . 79
8.1.2 Addition de deux nombres . . . . . . . . . . . . . . . . . 81
8.2 Décodage du jeu d’instructions . . . . . . . . . . . . . . . . . . 82
8.3 Mémoire . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 84
8.4 Quiz récapitulatif . . . . . . . . . . . . . . . . . . . . . . . . . . 85
8.5 Exercices . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 85
10 Chaînes de caractères 99
10.1 ASCII . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 99
10.2 ISO 8859-1 (Latin-1) . . . . . . . . . . . . . . . . . . . . . . . . 100
10.3 UTF-8 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 102
10.4 Chaînes de caractères . . . . . . . . . . . . . . . . . . . . . . . 103
10.5 Quiz récapitulatif . . . . . . . . . . . . . . . . . . . . . . . . . . 104
10.6 Exercices . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 104
14 Entrées/sorties 142
14.1 Attente active . . . . . . . . . . . . . . . . . . . . . . . . . . . . 142
14.2 Interruptions . . . . . . . . . . . . . . . . . . . . . . . . . . . . 143
14.2.1 Gestionnaires d’interruption . . . . . . . . . . . . . . . . 143
14.2.2 Traitement des interruptions . . . . . . . . . . . . . . . . 144
14.2.3 Niveaux de priorité . . . . . . . . . . . . . . . . . . . . . 146
14.2.4 Interruptions logicielles . . . . . . . . . . . . . . . . . . 147
14.3 Accès direct à la mémoire . . . . . . . . . . . . . . . . . . . . . 147
14.4 Appels système . . . . . . . . . . . . . . . . . . . . . . . . . . . 148
14.5 Quiz récapitulatif . . . . . . . . . . . . . . . . . . . . . . . . . . 151
14.6 Exercices . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 151
Bibliographie 199
Index 200
1
Systèmes de numération
1
CHAPITRE 1. SYSTÈMES DE NUMÉRATION 2
Remarque.
1 := succ(0)
2 := succ(succ(0))
n := succ(succ(· · · succ(0))) .
| {z }
n fois
L’une des lacunes considérable du système unaire est son manque de conci-
sion: la représentation d’un nombre n ∈ N requiert n symboles. D’autres sys-
tèmes de numération plus concis ont été inventés au fil du temps. Par exemple,
la numération romaine peut être vue comme une extension du système unaire
où l’on contracte certaines répétitions de symboles par d’autres symboles. Ce
système abrège notamment IIIII par V, et VV par X. Ainsi, le nombre 16 s’écrit
avec seulement trois symboles (XVI) plutôt que seize symboles (IIIIIIIIIIIIIIII).
Cette concision a un coût; les opérations arithmétiques sont plus complexes que
dans le système unaire; par ex. pensez à un algorithme pour l’addition.
∑
n−1
(xn−1 · · · x1 x0 )b := x i · bi .
i=0
CHAPITRE 1. SYSTÈMES DE NUMÉRATION 3
Exemple.
1037 = 1 · 72 + 0 · 71 + 3 · 70 = 52,
5647 = 5 · 72 + 6 · 71 + 4 · 70 = 291.
Notons que l’ajout du chiffre 0 à gauche d’un nombre ne change pas sa valeur
puisque cela ajoute zéro fois une puissance de b, donc zéro. Autrement dit, nous
avons xb = (0x)b = (00x)b = (00 · · · 0x)b , peu importe les valeurs de x et b. Nous
disons que de tels zéros sont non significatifs.
Remarquons que le plus grand nombre formé de n chiffres dans le système
décimal est 10n − 1. Par exemple, pour n = 3, le plus grand nombre est 999 =
103 − 1. Cette observation se généralise à une base arbitraire:
Proposition 1. Soit b une base et soit n ∈ N≥1 . Le plus grand nombre pouvant
être représenté en base b avec n chiffres est bn − 1.
Exemple.
Les 32 premiers nombres naturels décrits dans les bases 10, 2, 16 et 8 ap-
paraissent à la figure 1.1.
∑
n−1
xi · bi .
i=0
Notons que si ce calcul est implémenté de façon naïve, il requiert 1+2+. . .+n =
n(n + 1)/2 multiplications et n − 1 additions. Il est possible d’obtenir la même
CHAPITRE 1. SYSTÈMES DE NUMÉRATION 5
Exemple.
0 + 2 · (1 + 2 · (1 + 2 · (0 + 2 · 1))) = 0 + 2 · (1 + 2 · (1 + 2 · 2))
= 0 + 2 · (1 + 2 · 5)
= 0 + 2 · 11
= 22.
22 ÷ 2 = 11 reste 0,
11 ÷ 2 = 5 reste 1,
5 ÷ 2 = 2 reste 1,
2 ÷ 2 = 1 reste 0,
1 ÷ 2 = 0 reste 1.
Base b vers base bm . Soit x un nombre décrit en base b et soit m ∈ N>1 . Afin
de convertir x en base bm , nous procédons en deux étapes:
— les chiffres de x sont regroupés en blocs de taille m, de la droite vers la
gauche, en ajoutant des 0 non significatifs tout à gauche au besoin;
— chaque bloc est remplacé par le chiffre correspondant en base bm .
Exemple.
Base bm vers base b. Soit m ∈ N>1 et soit x un nombre décrit en base bm . Afin
de convertir x en base b, nous éclatons chaque chiffre de x vers sa représentation
de taille m en base b, puis nous effaçons les 0 non significatifs à gauche.
Exemple.
1.3 Addition
Voyons comment additionner deux nombres décrits dans une base commune b.
Nous couvrirons la soustraction, la multiplication et la division plus tard au cha-
pitre 4. Soient x et y deux nombres de n chiffres décrits en base b. La somme de
CHAPITRE 1. SYSTÈMES DE NUMÉRATION 7
Exemple.
Exemple.
Nous avons:
Exemple.
1.6 Exercices
1.1) Expliquez comment effectuer la soustraction et la multiplication dans le
système unaire.
1.4) Quel est la plus petite quantité de bits nécessaire afin de représenter le
nombre 2FEDCB16 en binaire?
1.5) Quel est le plus grand multiple de 5 pouvant être représenté par un nombre
de 9 chiffres en base 4?
1.6) Quel est le plus grand nombre inférieur à 1 pouvant être représenté par
un nombre fractionnaire binaire possédant jusqu’à 8 bits avant la virgule
et 6 bits après la virgule?
1.7) Dites s’il existe une base b dans laquelle le nombre 111b est pair.
1.8) ⋆ Expliquez pourquoi il est impossible d’obtenir une retenue excédant 1
lors de l’addition de deux nombres dans une base commune.
1.9) ⋆ Démontrez par l’absurde que 0,1 ne peut pas être représenté de façon
finie en base 2.
CHAPITRE 1. SYSTÈMES DE NUMÉRATION 10
1.11) L’outil chmod sur les systèmes de type UNIX permet d’accorder des droits
en accès d’un fichier aux classes « user », « group » et « other ». Le format
est comme suit, où r, w et x correspondent aux droits de lecture (« read »),
d’écriture (« write »), et d’exécution (« execute »):
2.1 Registres
L’architecture ARMv8 possède ces 32 registres de 64 bits [ARM15, Sect. 9.1.1]:
11
CHAPITRE 2. PROGRAMMATION EN LANGAGE D’ASSEMBLAGE 12
La conjecture de Collatz affirme que cet algorithme se termine sur toute en-
trée; autrement dit, que la séquence sn définie par f (n), f (f (n)), f (f (f (n))), . . .
atteint éventuellement 1 peu importe la valeur n de départ. Par exemple, la sé-
quence s3 atteint 1 en sept étapes:
s3 = 10, 5, 16, 8, 4, 2, 1, . . . ,
et la séquence s14 atteint 1 en dix-sept étapes:
s14 = 7, 22, 11, 34, 17, 52, 26, 13, 40, 20, 10, 5, 16, 8, 4, 2, 1, . . . .
Le nombre d’étapes afin d’atteindre 1 à partir de n est son temps de vol, que
nous dénotons tn . Par exemple, t3 = 7 et t14 = 17. Écrivons un programme qui
lit une entrée n ∈ N et affiche son temps de vol tn . Autrement dit, nous cher-
chons à calculer le nombre d’itérations de la boucle tant que de l’algorithme 3.
Plutôt que d’écrire le programme complet directement, nous présenterons
d’abord des segments de code qui accomplissent des sous-tâches, puis nous uni-
fierons et raffinerons ces segments de code afin d’obtenir un programme.
CHAPITRE 2. PROGRAMMATION EN LANGAGE D’ASSEMBLAGE 13
Remarque.
L’instruction « b fin » effectue un saut vers la toute dernière ligne afin d’indi-
quer que le calcul de f (n) est complété. Les sauts sont aussi connus sous le nom
de branchement, d’où la lettre « b ».
Si n est impair, le programme calcule 3n + 1 à l’aide de ces instructions:
CHAPITRE 2. PROGRAMMATION EN LANGAGE D’ASSEMBLAGE 14
.section ".rodata"
msgRes: .asciz "Résultat: %lu"
L’identifiant « .asciz » indique que nous désirons allouer une chaîne de ca-
ractères 1 . La chaîne de caractères "Résultat: %lu" contient le spécificateur de
format "%lu" qui spécifie un entier non négatif de 64 bits. Le deuxième argu-
ment de printf est donc, dans ce cas, un entier non négatif à afficher.
Comme brièvement mentionné à la section 2.1, les paramètres d’un sous-
programme sont passés via les registres x0 –x7 . Ainsi, le code suivant stocke
l’adresse de la chaîne msgRes et la valeur du registre x19 dans les registres x0 et
x1 respectivement, et appelle la fonction printf grâce à l’instruction bl:
1. Plus précisément: une chaîne de caractères se terminant par le caractère nul. Nous discute-
rons de ce détail technique au chapitre 10.
CHAPITRE 2. PROGRAMMATION EN LANGAGE D’ASSEMBLAGE 15
.section ".bss"
.align 8
temp: .skip 8
.section ".rodata"
fmtEntree: .asciz "%lu"
// Lecture de n
adr x0, fmtEntree // x0 ← adresse(fmtEntree)
adr x1, temp // x1 ← adresse(temp)
bl scanf // scanf(x0, x1)
// Calcul de f(n)
tbnz x19, 0, impair // si x19[0] ≠ 0: aller à impair
pair:
mov x20, 2 // x20 ← 2
udiv x19, x19, x20 // x19 ← x19 ÷ x20
b fin // aller à fin
impair:
mov x20, 3 // x20 ← 3
mul x20, x20, x19 // x20 ← x20 * x19
add x19, x20, 1 // x19 ← x20 + 1
fin:
// Affichage de f(n)
adr x0, msgRes // x0 ← adresse(msgRes)
mov x1, x19 // x1 ← x19
bl printf // printf(x0, x1)
.section ".bss"
.align 8
temp: .skip 8
.section ".rodata"
fmtEntree: .asciz "%lu"
msgRes: .asciz "Résultat: %lu"
main:
// Lecture de n
adr x0, fmtEntree // x0 ← adresse(fmtEntree)
adr x1, temp // x1 ← adresse(temp)
bl scanf // scanf(x0, x1)
// Calcul de f(x19)
tbnz x19, 0, impair // si x19[0] ≠ 0: aller à impair
pair:
mov x20, 2 // x20 ← 2
udiv x19, x19, x20 // x19 ← x19 ÷ x20
CHAPITRE 2. PROGRAMMATION EN LANGAGE D’ASSEMBLAGE 18
mov x0, 0
bl exit // Quitter le programme
.section ".bss"
.align 8
temp: .skip 8
.section ".rodata"
fmtEntree: .asciz "%lu"
msgRes: .asciz "Résultat: %lu"
Remarque.
Présentation. Comme les registres ne peuvent pas être renommés, il est pra-
tique d’associer implicitement des noms symboliques aux registres et de com-
menter chaque instruction par un commentaire décrivant l’effet de l’instruction,
en pseudocode ou dans un langage de plus haut niveau tel que C. Par exemple:
ou encore:
directive contenu
.section ".text" instructions
.section ".rodata" données en lecture seule
.section ".data" données initialisées
.section ".bss" données non-initialisées
Si aucun segment n’est spécifié, alors ".text" est supposé par défaut.
Des données statiques et globales peuvent être déclarées et/ou initialisées
dans les segments autres que ".text" grâce aux mots-clés suivants:
%d entier décimal
%u entier décimal non négatif
Nombres sur 32 bits
%X entier hexadécimal non négatif
%f nombre en virgule flottante
%c caractère (1 octet)
Caractères
%s chaîne de caractères
2. En général, le nombre de bits de chacun des formats dépend de l’architecture sur laquelle le
code est exécuté. Nous faisons ici référence à ARMv8.
CHAPITRE 2. PROGRAMMATION EN LANGAGE D’ASSEMBLAGE 22
— xkcd c
CHAPITRE 2. PROGRAMMATION EN LANGAGE D’ASSEMBLAGE 23
2.5 Exercices
2.1) Écrivez un programme ARMv8 qui lit quatre entiers non négatifs x, y, x′ , y ′
de 64 bits, et qui affiche l’aire et le périmètre du rectangle dont deux coins
opposés sont situés aux coordonnées (x, y) et (x′ , y ′ ) dans le plan.
(tiré de [SD11])
2.2) Écrivez un programme ARMv8 qui lit deux entiers non négatifs m et n de
64 bits, l’un à la suite de l’autre, et qui affiche toutes les paires (i, j) telles
que 0 ≤ i < m et 0 ≤ j < n.
2.3) Écrivez un programme ARMv8 qui lit un entier non négatif n de 64 bits
et qui affiche le nème terme de la suite de Fibonacci.
24
CHAPITRE 3. ARCHITECTURE DES ORDINATEURS 25
Processeur
Unité arithmétique
Unité de contrôle et logique
Bus
Figure 3.1 – Architecture de von Neumann. (Figure reproduite à partir de [SD11, Fig. 2.1])
l’aide de câbles et d’interrupteurs, alors que les données étaient entrées à l’aide
de cartes perforées.
En 1944, Eckert et Mauchly cherchent déjà à simplifier l’entrée fastidieuse
des programmes dans l’ENIAC. John von Neumann, qui se joint entre temps au
groupe, écrit un mémo en 1945 sur une proposition d’ordinateur à programme
enregistré, où programmes et données sont stockés dans la même mémoire. Un
tel ordinateur, l’EDVAC (Electronic Discrete Variable Automatic Computer), sera
construit quelques anneés plus tard [PH17].
Le modèle général de l’EDVAC, connu sous le nom d’architecture de von Neu-
mann, est celui de la plupart des ordinateurs modernes. Dans cette architecture,
un ordinateur est composé:
— d’un processeur constitué de registres, d’une unité de contrôle, et d’une
unité arithmétique et logique;
— d’une mémoire principale qui stocke données et programmes;
— d’unités d’entrée/sortie.
Ces différents composants sont connectés par des systèmes de communica-
tion appelés bus. Le bus interne relie le processeur et la mémoire principale, alors
que les bus externes relient l’ordinateur aux unités d’entrée/sortie. Ces compo-
sants sont illustrés à la figure 3.1.
séquence de 8 bits se nomme un octet. Les bits d’un octet n’ont à priori aucune
signification, leur interpétation est faite par un langage de programmation ou
par la personne qui écrit un programme. Par exemple, l’octet 01000001 peut
autant représenter le nombre 65 que le caractère A. Lors du chargement d’un
programme, son code est stocké dans les cellules de la mémoire principale aux
côtés de ses données (variables, constantes, etc.) On pourrait donc en théorie
imaginer un programme qui manipule son propre code, par ex. un virus. La
figure 3.2 illustre un contenu possible d’une mémoire principale.
0 00000000
1 01011000
2 01000000
3 00001111
4 00011000
5 01010101
6 11110000
7 00001111
.. .
.
. .
n−2 11111111
n−1 01100001
Figure 3.2 – Exemple de contenu d’une mémoire principale. Chaque celulle est
représentée par une case rectangulaire dont l’adresse apparaît à sa gauche.
Remarque.
Plusieurs outils, comme les débogueurs, affichent les adresses sous leur
valeur hexadécimale plutôt que décimale, souvent avec le préfixe « 0x ».
Par exemple, l’adresse 0x0000555548ee affichée par un tel outil corres-
pond à l’adresse 0000555548EE16 = 1431652590.
unité adresses
octet i
demi-mot i, i + 1
mot i, i + 1, i + 2, i + 3
double mot i, i + 1, i + 2, i + 3, . . . , i + 7
Exemple.
00000000,
00000000 01011000,
00000000 01011000 01000000 00001111,
00000000 01011000 01000000 00001111 00011000 01010101 11110000 00001111,
Exemple.
union Mot
{
uint32_t valeur;
uint8_t octets[4];
};
int main()
{
union Mot mot;
un double mot si elle se termine par 0 ou 8; pour un mot si elle se termine par
0, 4, 8 ou C; et pour un demi-mot si elle se termine par 0, 2, 4, 6, 8, A, C ou E.
Exemple.
..
.
Remarque.
L’ordre dans lequelles les données d’une structure sont déclarées en C/C++
a une incidence sur le nombres d’octets utilisés en raison des contraintes
d’alignement.
Remarque.
Dans le langage courant, on utilise souvent {kilo, méga, giga, tera, péta,
exa}octets pour référer aux puissances de 1024, bien qu’elles réfèrent
techniquement aux puissances de 1000.
Notons qu’une architecture, dont les adresses sont représentées sur k bits,
peut accéder jusqu’à 2k cellules de mémoire. Ainsi, une architecture 32 bits peut
accéder à un maximum de:
3.1.2 Processeur
Le processeur est l’unité centrale de traitement de l’ordinateur, il s’agit donc en
quelque sorte du « cerveau » de l’ordinateur. Chaque processeur est associé à un
jeu d’instructions: un ensemble fini d’instructions machines formant les opéra-
tions élémentaires pouvant être exécutées par l’ordinateur. L’unité de contrôle
du processeur coordonne l’exécution des instructions machines. Son unité ari-
thmétique et logique performe les opérations arithmétique et logique nécessaires
à l’exécution des instructions. Le processeur peut communiquer avec la mémoire
principale via certaines instructions, et possède également une petite quantité
de mémoire interne connue sous le nom de registres.
Exemple.
Exemple.
foo a, b, c
bar a, b
baz a, b, c
qux
Sur les architecture ARMv8 et RISC-V, toutes les instructions sont de taille
fixe. Chaque instruction est traduite vers une suite de 32 bits dans un format de
bits bien précis.
Exemple.
Sur l’architecture ARMv8, « add x10, x11, x12 » se traduit vers le code
machine B0C016A16 , ou plus précisément en binaire:
CHAPITRE 3. ARCHITECTURE DES ORDINATEURS 32
1 0 0 01011 00 0 01100
| {z } 000000 01011
| {z } 01010
| {z }.
x12 x11 x10
Remarque.
Exemple.
3.2 Organisation
3.2.1 Pipeline
L’exécution d’une instruction par le processeur se fait souvent en plusieurs étapes.
Par exemple, le processeur:
1. récupère l’instruction à partir de la mémoire principale;
2. décode le code d’opération et les opérandes de l’instruction;
3. charge les opérandes nécessaires à partir des registres ou de la mémoire;
4. exécute l’instruction;
5. stocke le résultat de l’instruction.
CHAPITRE 3. ARCHITECTURE DES ORDINATEURS 34
1 2 3 4 5 6 7 8 9
Récupération Décodage Chargement Exécution Stockage Récupération Décodage Chargement Exécution ···
1 2 3 4 5 6 7 8 9
..
L’utilisation d’un pipeline peut créer plusieurs problèmes qui doivent être
résolus par le processeur. Par exemple, si une instruction x écrit une valeur en
mémoire à l’adresse i, et qu’une instruction subséquente y lit la valeur à la même
adresse i, alors la valeur lue par l’instruction y risque d’être erronée puisqu’elle
n’aura pas encore été mise à jour par x. Dans ce cas, le processeur peut, par
exemple, décider de retarder l’exécution de y, ce qui mitige les avantages du
pipeline.
Les instructions qui effectuent des sauts peuvent aussi causer des problèmes.
Par exemple, supposons que la première instruction x de la figure 3.5 effectue
un saut au 4ème cycle vers une instruction z située plus loin en mémoire. Au
5ème cycle, la seconde instruction y est exécutée. Cela ne devrait pas être le cas;
c’est plutôt l’instruction z qui devrait être exécutée après x. Le processeur doit
donc corriger la situation. Pour pallier à ce problème, les processeurs utilisent
différentes heuristiques de prédiction de branchement afin de « deviner » si un
saut sera effectué ou non.
CHAPITRE 3. ARCHITECTURE DES ORDINATEURS 35
3.4 Exercices
3.1) Considérons le contenu suivant de la mémoire principale:
adresse contenu
0 0116
1 A416
2 BC16
3 4816
4 5F16
5 1116
6 FF16
7 4316
.. .
.
. .
foo a, b, c
bar a, b
baz a, b, c
qux
3.4) Imaginons une architecture qui offre une instruction « addm x y a » qui
additionne le contenu du registre y et la valeur de l’octet situé à l’adresse a
de la mémoire principale, et stocke le résultat dans le registre x. Pourrions-
nous qualifier cette architecture de RISC?
4
Nombres entiers
∑
n−2
−xn−1 · 2n−1 + xi · 2 i .
i=0
Exemple.
37
CHAPITRE 4. NOMBRES ENTIERS 38
Bien que cette représentation puisse paraître complexe à première vue, elle
jouit de plusieurs propriétés intéressantes. Premièrement, un nombre est né-
gatif si et seulement si son bit tout à gauche vaut 1. Deuxièmement, l’addition
s’effectue exactement de la même façon que l’addition d’entiers non signés.
Exemple.
Exemple.
Il est important de noter que le concept de bits non significatifs n’est pas
le même que pour les entiers non signés. Par exemple, considérons le nombre
−2 représenté par 110. L’ajout d’un zéro à gauche mène à la suite 0110, qui ne
représente pas la valeur −2. En effet, les nombres pouvant être représentés sur
quatre bits sont:
Exemple.
La suite 110 qui représente −2 sur trois bits peut être étendue à 1110
sur quatre bits. Similairement, la suite 011 qui représente 3 sur trois bits
peut être étendue à 0011 sur quatre bits.
4.2 Addition
Comme annoncé, l’addition d’entiers signés s’effectue exactement de la même
façon que l’addition d’entiers non signés. Ainsi, l’unité arithmétique et logique
peut utiliser les mêmes circuits logiques (que nous couvrirons au chapitre 8).
4.2.1 Report
Lorsqu’une addition, en arithmétique signée ou non, engendre une retenue lors
de la somme des bits les plus à gauche, nous disons qu’il y a report. La détection
d’un report permet notamment d’identifier une erreur ou d’étendre l’addition
sur n bits à l’addition sur 2n bits.
Exemple.
4.2.2 Débordement
Lors de l’addition de deux nombres de n bits, nous disons qu’il y a débordement
si la somme ne peut pas être représentée sur n bits. Dans le cas de l’arithmé-
CHAPITRE 4. NOMBRES ENTIERS 40
Exemple.
Cette addition n’a pas de report, mais son résultat est erronné. En effet,
la suite 1011 représente −5 plutôt que la somme attendue de 11. Ce pro-
blème surgit lorsque la somme de deux nombres positifs (resp. négatifs)
excède la plus grande (resp. plus petite) valeur signée représentable.
4.3 Soustraction
La soustraction a − b de deux entiers peut être effectuée en calculant le com-
plément à deux de b, puis en effectuant une addition standard.
Exemple.
4.4 Multiplication
Cette méthode a l’avantage d’être simple, mais elle est lente lorsque a est grand.
Il est possible de faire mieux en effectuant des multiplications par des puissances
de 2, et en exploitant le fait que cela correspond à des décalages de bits.
CHAPITRE 4. NOMBRES ENTIERS 41
Exemple.
13 · 11 = 13 · (23 + 21 + 20 )
= 13 · 23 + 13 · 21 + 20
= 11012 · 23 + 11012 · 21 + 11012 · 20
= 11010002 + 110102 + 11012
= 100011112
= 143.
Exemple.
itération hi lo
0 0000 1011
1 0110 1101
2 1001 1110
3 0100 1111
4 1000 1111
Proposition 3. Soit n ∈ N≥2 et soit a le plus grand entier non signé de n bits. La
représentation binaire de a · a requiert 2n bits.
Exemple.
ou bien ainsi:
CHAPITRE 4. NOMBRES ENTIERS 43
11111001 (-7)
×
00000101 (5)
11111001
00000000
+
11111001
10011011101 (-35)
Cet algorithme peut être implémenté de manière à ce que les bits addition-
nels ne soient pas ajoutés explicitement, et ainsi que la multiplication se fasse
directement sur 2n bits.
4.5 Division
Exemple.
Exemple.
itération q r
0 10011 00000
1 00110 00001
2 01100 00010
3 11001 00001
4 10011 00000
5 00110 00001
Exemple.
Entiers signés
Condition Signification Codes de condition
eq = Z
ne ̸ = ¬Z
ge ≥ N=V
gt > ¬Z ∧ (N = V)
le ≤ Z ∨ (N ̸= V)
lt < N ̸= V
vs débordement V
vc pas de débordement ¬V
mi négatif N
pl non négatif ¬N
Par exemple, ldrsw charge un mot signé y dans les 32 bits de poids faible du
registre, et remplit les 32 bits de poids fort avec le bit de signe de y.
Remarquons que si « ldrsh xd, etiquette », par exemple, ne compile pas
en raison des détails spécifiques du code machine, il est possible d’utiliser:
adr xm, etiquette
ldrsh xd, [xm]
Remarque.
4.8 Exercices
4.1) Écrivez les entiers signés 43, −43, 1, −1, 127, −128 sur 8 bits.
4.2) Donnez le résultat des opérations signées suivantes sur 8 bits: 43 + 25,
43 − 25 127 + 127, −128 − 128 et 127 − 128.
4.6) ⋆⋆ Montrez que l’extension d’un entier signé par son bit de signe pré-
serve bel et bien sa valeur.
4.7) ⋆⋆ Montrez que le complément à deux inverse bel et bien le signe d’un
nombre.
5
Accès aux données
Dans ce chapitre, nous explorons les façons d’accéder et de référer aux données
de la mémoire principale ainsi que des registres du processeur. En particulier,
nous traitons d’adressage et des étapes de vie d’un programme.
5.1 Adresses
Nous avons indirectement vu deux façons de spécifier une adresse de la mémoire
principale. Une adresse numérique est une adresse spécifiée par un entier non
négatif. Par exemple, l’adresse 26 est numérique et réfère à la cellule c26 de la
mémoire principale. Les adresses numériques sont souvent écrites en notation
hexadécimale, par ex. 0000001A16 ou 0x0000001A plutôt que 26.
Remarque.
Rappelons que lors de l’exécution d’un programme, celui-ci vit dans la mé-
moire principale. Ainsi, lors du saut vers l’étiquette main:, le compteur d’instruc-
tion doit être déplacé vers l’adresse a de la mémoire qui contient l’instruction
48
CHAPITRE 5. ACCÈS AUX DONNÉES 49
sous cette étiquette. Rappelons que le code machine d’une instruction est gé-
néralement stocké sur plusieurs octets, par ex. 4 octets dans le cas d’ARMv8.
Ainsi, il s’agit ici de l’adresse a pointant vers la première celulle ca qui contient
une portion du code machine de l’instruction « sub x19, x19, 1 »:
.. .
.
. .
a
a+1
a+2 sub x19, x19, 1
a+3
a+4
a+5
a+6 cbnz x19, a
a+7
.. .
.
. .
Remarque.
5.2 Adressage
Comme nous l’avons vu aux chapitres précédents, la plupart des instructions
d’un langage d’assemblage possèdent des opérandes. Il existe plusieurs façons
d’interpréter les opérandes afin de localiser la valeur qui leur est associée. Nous
appelons ces méthodes de localisation des modes d’adressage. Dans cette section,
nous décrivons quelques modes d’adressage répandus, notamment sur ARMv8.
Nous utiliserons i afin de dénoter une valeur immédiate, c’est-à-dire une
constante figée dans le code; n afin de référer au nom d’un registre; et a pour
dénoter une adresse de la mémoire principale. Nous écrivons reg[n] pour référer
au contenu du registre n, et nous écrivons mem[a] pour référer au contenu de
la mémoire principale à l’adresse a (sans se soucier pour l’instant du nombre
d’octets adressés).
5.2.1 Immédiat
Le mode d’adressage immédiat est le plus simple. Il associe simplement à l’opé-
rande i la valeur i elle-même:
i 7→ i.
CHAPITRE 5. ACCÈS AUX DONNÉES 50
Exemple.
mov x19, 42
5.2.2 Direct
Le mode d’adressage direct récupère, à partir d’une adresse a, la valeur contenue
à l’adresse a de la mémoire principale:
a 7→ mem[a].
Exemple.
n 7→ reg[n].
Exemple.
5.2.4 Indirect
Le mode d’adressage indirect récupère, à partir d’une adresse a, la valeur conte-
nue à l’adresse b, où b est la valeur contenue à l’adresse a:
a 7→ mem[mem[a]].
CHAPITRE 5. ACCÈS AUX DONNÉES 51
Exemple.
n 7→ mem[reg[n]].
Exemple.
n, i 7→ mem[reg[n] + i].
Exemple.
n, m 7→ mem[reg[n] + reg[m]].
CHAPITRE 5. ACCÈS AUX DONNÉES 52
Notons que ces deux modes d’adressage ont des effets de bord: le contenu du
registre n est modifié lors de l’interprétation de l’opérande.
Exemple.
5.2.8 Relatif
Le mode d’adressage relatif constitue un cas particulier de l’adressage indirect
par registre indexé, où le registre utilisé est le compteur d’instruction:
i 7→ mem[reg[pc] + i].
Exemple.
.section ".data"
var: .xword 42
a. Comme une instruction est codée sur 4 octets sur ARMv8, i est un multiple de 4.
direct a 7→ mem[a] —
indirect a 7→ mem[mem[a]] —
L’adresse numérique associée à une étiquette etiq: peut être chargée dans
un registre r grâce à l’instruction:
adr r, etiq
Remarque.
Remarque.
Remarque.
Des fichiers « makefile » seront fournis pour les laboratoires et les devoirs
afin d’automatiser l’assemblage.
CHAPITRE 5. ACCÈS AUX DONNÉES 56
5.6 Exercices
5.1) Considérons le contenu suivant de la mémoire principale:
adresse contenu
0 0116
1 A416
2 BC16
3 4816
4 5F16
5 1116
6 FF16
7 0416
.. .
.
. .
mov x19, 2
ldr x20, [x19], 3
sub x19, x19, 1
ldrb w20, [x19, 2]
5.3) Imaginons une instruction « load r, etiq » qui utilise le mode d’adres-
sage indirect afin de charger dans le registre r le double mot stocké à
l’adresse b, où b est le mot stocké à l’adresse représentée par l’étiquette
etiq. Expliquez comment émuler cette instruction sur ARMv8.
6
Tableaux
Exemple.
0 ≤ i0 < n0 ,
0 ≤ i1 < n1 ,
.. .. ..
. . .
0 ≤ id−1 < nd−1 .
Chaque indice i d’un tableau t est associé à un unique élément t[i] dont la valeur
est stockée sur un nombre fixe d’octets k. Ainsi, un tableau possède n = n0 ·
n1 · · · nd−1 éléments représentés globalement sur k · n octets.
57
CHAPITRE 6. TABLEAUX 58
Exemple.
Remarquons que la dimension d’un tableau, ainsi que ses indices, sont seule-
ment connus implicitement. Par exemple, ces trois tableaux peuvent être repré-
sentés exactement de la même façon en mémoire:
2 33 ( )
65535 73 2 33 65535
[2, 33, 65535, 73, 9000, 255]
73 9000 255
9000 255
Similairement, le type des éléments d’un tableau n’est qu’implicite et peut
être interprété différemment à l’exécution. Par exemple, considérons un tableau
initialisé avec m blocs de 8 octets. Ses éléments peuvent autant être vus comme
des entiers non signés que des entiers signés de 64 bits.
Observation.
En fait, ils pourraient même être vus comme 2m entiers de 32 bits (signés
ou non), ou n’avoir simplement aucun type.
à laquelle est stocké un élément de t se nomme son index. Autrement dit, l’index
correspond à la distance d’un élément rapport à l’adresse de base a.
.. .
.
. .
a
a+1
.. t[0]
.
a + (k − 1)
a+k
a + (k + 1)
.. t[1]
.
a + (2k − 1)
.. .
.
. .
a + n0 · k
a + (n0 · k + 1)
.. t[n0 − 1]
.
a + (n0 · k − 1)
.. .
.
. .
a + (i · n1 + j) · k .
| {z }
index de l’élém. (i, j)
.section ".bss"
.align 2
tab: .skip 12
Afin de faciliter la lecture du code, des macros peuvent aussi être utilisées
afin d’indiquer explicitement les bornes du tableau:
N0 = 3
N1 = 2
.section ".bss"
.align 2
tab: .skip N0*N1*2
Le tableau peut être rempli à l’aide de l’instruction strh (et non str, puisque
les éléments sont sur 2 octets):
CHAPITRE 6. TABLEAUX 61
Notons que nous utilisons ici le mode d’adressage indirect par registre post-
incrémenté, ce qui évite d’incrémenter x19 manuellement.
Alternativement, les éléments du tableau peuvent être alloués et initialisés
directement dans le segment de données initialisées:
.section ".data"
tab: .hword 2, 33, 65535, 73, 9000, 255
N0 = 3
N1 = 2
main:
adr x19, tab //
mov x20, 0 // i = 0
afficher: // do {
adr x0, fmt //
mov x21, 2 //
mul x21, x21, x20 //
ldrh w1, [x19, x21] //
bl printf // afficher tab[i]
//
add x20, x20, 1 // i++
cmp x20, N0*N1 // }
CHAPITRE 6. TABLEAUX 62
mov x0, 0
bl exit
.section ".data"
tab: .hword 2, 33, 65535, 73, 9000, 255
.section ".rodata"
fmt: .asciz "%u\n"
N0 = 3
N1 = 2
main:
adr x19, tab //
mov x20, 0 // i = 0
afficher: // do {
adr x0, fmt //
ldrh w1, [x19], 2 //
bl printf // afficher tab[i]
//
add x20, x20, 1 // i++
cmp x20, N0*N1 // }
b.lo afficher // while (i < N0*N1)
mov x0, 0
bl exit
.section ".data"
tab: .hword 2, 33, 65535, 73, 9000, 255
.section ".rodata"
fmt: .asciz "%u\n"
N0 = 3
N1 = 2
main:
adr x19, tab //
mov x20, 0 // i = 0
//
prochaineLigne: // do {
mov x21, 0 // j = 0
//
afficherLigne: // do {
adr x0, fmtElem //
//
// Calcul de l'index //
mov x22, N1 //
mul x22, x20, x22 //
add x22, x22, x21 //
add x22, x22, x22 // index = (i*N1 + j)*2
//
ldrh w1, [x19, x22] //
bl printf // afficher tab[i, j]
//
add x21, x21, 1 // j++
cmp x21, N1 // }
b.lo afficherLigne // while (j < N1)
//
adr x0, fmtSaut //
bl printf // afficher saut de ligne
//
add x20, x20, 1 // i++
cmp x20, N0 // }
b.lo prochaineLigne // while (i < N0)
mov x0, 0
bl exit
.section ".data"
tab: .hword 2, 33, 65535, 73, 9000, 255
.section ".rodata"
fmtElem: .asciz "%u "
fmtSaut: .asciz "\n"
Comme dans le cas linéaire, on peut accéder aux éléments du tableau à l’aide
du mode d’adressage indirect par registre indexé post-incrémenté « [x19], 2 »,
ce qui simplifie le code:
CHAPITRE 6. TABLEAUX 64
N0 = 3
N1 = 2
main:
adr x19, tab //
mov x20, 0 // i = 0
//
prochaineLigne: // do {
mov x21, 0 // j = 0
//
afficherLigne: // do {
adr x0, fmtElem //
ldrh w1, [x19], 2 //
bl printf // afficher tab[i, j]
//
add x21, x21, 1 // j++
cmp x21, N1 // }
b.lo afficherLigne // while (j < N1)
//
adr x0, fmtSaut //
bl printf // afficher saut de ligne
//
add x20, x20, 1 // i++
cmp x20, N0 // }
b.lo prochaineLigne // while (i < N0)
mov x0, 0
bl exit
.section ".data"
tab: .hword 2, 33, 65535, 73, 9000, 255
.section ".rodata"
fmtElem: .asciz "%u "
fmtSaut: .asciz "\n"
main:
// Initialiser tab //
adr x19, tab //
adr x20, somme //
str w20, [x19], 4 //
adr x20, produit //
str w20, [x19], 4 // tab = {&somme,
adr x20, diff // &produit,
str w20, [x19] // &diff}
//
// Lire chiffre //
adr x0, fmtEntree //
adr x1, temp //
bl scanf // scanf("%u", &temp)
ldr x21, temp // i = temp
//
// Évaluer f(7, 5) où f := tab[i] //
mov x20, 4 //
mul x22, x20, x21 //
adr x19, tab //
ldr w20, [x19, x22] // f = tab[i]
mov x27, 7 // x = 7
mov x28, 5 // y = 5
br x20 // f(x, y)
//
somme: // somme(x, y) {
add x1, x27, x28 // z = x + y
b afficher // }
produit: // produit(x, y) {
mul x1, x27, x28 // z = x * y
b afficher // }
diff: // diff(x, y) {
sub x1, x27, x28 // z = x - y
b afficher // }
//
// Afficher résultat //
afficher: //
adr x0, fmtSortie //
bl printf // printf("%lu\n", z)
//
CHAPITRE 6. TABLEAUX 66
mov x0, 0 //
bl exit //
.section ".bss"
tab: .skip 12 // tableau de 3 adresses
temp: .skip 4
.section ".rodata"
fmtEntree: .asciz "%u"
fmtSortie: .asciz "%lu\n"
Remarque.
6.5 Exercices
6.1) Considérons ce tableau bidimensionnel stocké à l’adresse a = FF00AB16
et dont les éléments sont des mots. À quelle adresse se situe le mot qui
contient 999?
0 65536 −10
42 50 100
1 999 1234
7 500 4321
6.3) Nous avons représenté les matrices par des tableaux bidimensionnels en
stockant les lignes l’une à la suite des autres. Considérez la représentation
alternative où ce sont plutôt les colonnes qui sont stockées l’une à la suite
des autres. Revoyez la formule du calcul d’index et écrivez un programme
qui affiche une telle matrice.
6.6) ⋆ Écrivez un programme qui implémente le jeu de la vie. Il doit lire une
matrice binaire (où 0 représente une cellule morte, et 1 représente une
cellule vivante), puis retourner une matrice qui représente le nouvel état
des cellules après une application des règles.
7
Programmation structurée
7.1.1 Séquence
La séquence consiste simplement à composer des instructions de façon séquen-
tielle. Cette structure est implémentée en traduisant chaque instruction vers du
code de bas niveau équivalent:
68
CHAPITRE 7. PROGRAMMATION STRUCTURÉE 69
mov x20, 7
mul x19, x19, x20
7.1.2 Sélection
La sélection est une structure de contrôle qui permet d’exécuter certaines instruc-
tions conditionnellement à la validité d’un ou plusieurs prédicats; par exemple:
une exécution conditionnelle à l’égalité de deux variables. Voyons comment im-
plémenter les structures de sélection si/sinon–si/sinon et de type « switch ».
Si.
Si/sinon.
Si/sinon–si/sinon.
Remarque.
7.1.3 Itération
L’itération est une structure de contrôle qui répète l’exécution de certaines ins-
tructions en fonction de l’état du programme; par exemple, une répétition qui
dépend de l’égalité de deux variables. Voyons comment implémenter les struc-
tures tant que, faire/tant que et pour.
Tant que.
Faire/tant que.
do { boucle:
// code // code
} while (cond(xd, xn)); cmp xd, xn
b.cond boucle
fin:
Boucle « pour ». L’un des cas les plus répandus de la boucle « pour » consiste
à incrémenter une variable, initialisée à 0, tant que sa valeur est inférieure ou
égale à la valeur d’une autre variable. Cette structure s’implémente ainsi:
7.2 Sous-programmes
Les langages structurés offrent des primitives pour décrire des sous-routines qui
modularisent le code; par exemple, sous la forme de procédures, de fonctions
ou de méthodes. De telles primitives n’existent pas dans les langages de bas
niveau. Elles doivent être implémentées à l’aide de sous-programmes: des blocs
de code exécutables à l’aide de branchements, de passage d’arguments et de
sauvegarde de registres.
À titre d’exemple, nous allons implémenter une fonction qui reçoit deux va-
leurs entières et qui retourne le maximum de ces valeurs. En C/C++, cette fonc-
tion peut être implémentée ainsi:
Passage par adresse. Dans notre exemple, les arguments sont passés par va-
leur puisqu’il s’agit d’entiers de 64 bits stockables dans les registres. Les struc-
tures de données plus complexes et stockées dans la mémoire principale doivent
plutôt être passées par adresse. Autrement dit, plutôt que de passer le contenu
de la structure, son adresse en mémoire est passée via un registre. Par exemple,
un tableau peut être passé comme premier argument d’un sous-programme en
chargeant son adresse dans x0 ; le sous-programme peut ensuite accéder aux
éléments du tableau grâce aux instructions d’accès mémoire comme « ldr ».
7.2.2 Retour
L’instruction « ret » termine l’exécution d’un sous-programme et saute vers l’en-
droit où l’appel a été effectué. La valeur de retour du sous-programme doit être
stockée dans le registre x0 avant le retour. Ainsi, notre fonction « max » peut être
implémentée ainsi:
max:
cmp x0, x1 //
b.lt max_sinon // if (a >= b) {
mov x19, x0 // m = a
b max_retour // }
max_sinon: // else {
mov x19, x1 // m = b
max_retour: // }
mov x0, x19 //
ret // return m
CHAPITRE 7. PROGRAMMATION STRUCTURÉE 75
max:
cmp x0, x1 //
b.ge max_retour // if (a >= b) return a
mov x0, x1 // else return b
max_retour: //
ret //
Ces macros utilisent une pile située dans la mémoire principale afin de sauve-
garder temporairement la valeur des registres. Nous discuterons du fonctionne-
ment de cette pile plus tard au chapitre 11. En particulier, la pile nous permettra
de mettre au point des sous-programmes récursifs.
CHAPITRE 7. PROGRAMMATION STRUCTURÉE 76
main:
mov x0, x19 // a = x19
mov x1, x20 // b = x20
bl max // m = max(a, b)
mov x21, x0 // x21 = m
//
bl exit //
//
max: //
SAVE //
cmp x0, x1 //
b.lt max_sinon // if (a >= b) {
mov x19, x0 // m = a
b max_retour // }
max_sinon: // else {
mov x19, x1 // m = b
max_retour: // }
mov x0, x19 //
RESTORE //
ret // return m
instruction effet
csel xd, xn, xm, cond si cond: xd ← xn , sinon: xd ← xm
max:
cmp x0, x1 //
csel x0, x0, x1, ge //
ret // return (a >= b) ? a : b
— xkcd c
CHAPITRE 7. PROGRAMMATION STRUCTURÉE 78
7.5 Exercices
7.1) Traduisez le code C/C++ ci-dessous en langage d’assemblage. Interprétez
x19 , x20 et x21 comme des entiers signés.
x x y x y x y
Figure 8.1 – De gauche à droite: portes NON, ET, OU, OU exclusif. Les bits
d’entrée et de sortie sont situés respectivement au-dessus et au bas des portes.
8.1 Arithmétique
79
CHAPITRE 8. CIRCUITS LOGIQUES 80
x y x∧y x⊕y
(retenue) (somme)
0 0 0 0
0 1 0 1
1 0 0 1
1 1 1 0
x y
retenue somme
r x y r+x+y
(sur deux bits)
0 0 0 00
0 0 1 01
0 1 0 01
0 1 1 10
1 0 0 01
1 0 1 10
1 1 0 10
1 1 1 11
r x y (x ∧ y) ∨ (r ∧ (x ⊕ y)) r ⊕ (x ⊕ y)
(retenue) (somme)
0 0 0 0 0
0 0 1 0 1
0 1 0 0 1
0 1 1 1 0
1 0 0 0 1
1 0 1 1 0
1 1 0 1 0
1 1 1 1 1
r x y
retenue somme
Remarque.
Des additionneurs plus sophistiqués, par ex. avec regard en avant (« carry-
lookahead »), permettent de calculer la somme plus rapidement.
CHAPITRE 8. CIRCUITS LOGIQUES 82
Demi-additionneur
Additionneur complet
Additionneur complet
Additionneur complet
r zn ··· z2 z1 z0
x1 x0
y3 y2 y1 y0
Exemple.
yx
Exemple.
Sur entrée x = 11, la sortie de ce circuit est y3 , alors que sur entrée
x = 01, sa sortie est y1 .
Remarque.
8.3 Mémoire
Les circuits logiques présentés jusqu’ici sont dits combinatoires: ils transforment
des entrées en sorties, sans mémoriser d’état interne. Afin d’implémenter la mé-
moire principale et les registres, on peut plutôt exploiter des circuits logiques
dits séquentiels. Ceux-ci contiennent des cycles qui stockent des bits d’informa-
tion. Nous donnons un exemple simple et classique qui montre comment stocker
un bit. La figure 8.7 illustre un circuit à verrouillage (ou verrou). Ce circuit pos-
sède deux bits d’entrée: s et r de l’anglais « set » et « reset ».
r s b (valeur stockée)
sortie
0 0 b b
0 1 b 1
1 0 b 0
1 1 b 0
8.5 Exercices
8.1) Quelles portes implémentent le maximum et le minimum de deux bits?
8.2) Décrivez un circuit logique qui teste si un nombre de n bits vaut zéro.
8.3) Décrivez un circuit logique qui teste si deux nombres sont égaux.
8.4) Décrivez un circuit logique qui incrémente un nombre de 3 bits.
8.5) Reproduisez le décodeur et le multiplexeur présentés, mais cette fois avec
3 bits. Décrivez comment obtenir de tels circuits en général pour n bits.
8.6) Adaptez le multiplexeur présenté afin qu’il sélectionne parmi quatre suites
de n bits plutôt que quatre bits.
8.7) Donnez un circuit qui affiche un chiffre parmi {0, 1, 2, 3} sur un compteur
digital à partir de deux bits d’entrées x1 x0 . Chaque segment du compteur
digital est associé à une variable booléenne yi . Par exemple, sur entrée
x = 10, nous avons:
y2
y1 y3
y6
y0 y4
y5
8.8) Donnez un circuit logique qui calcule le complément à deux d’un nombre,
sans réutiliser les circuits précédents. Réfléchissez d’abord à une entrée
d’un bit, de deux bits, de trois bits, etc. puis généralisez à n bits.
9
Valeurs booléennes et chaînes de bits
x ¬x x y x∧y x y x∨y
0 1 0 0 0 0 0 0
1 0 0 1 0 0 1 1
1 0 0 1 0 1
1 1 1 1 1 1
86
CHAPITRE 9. VALEURS BOOLÉENNES ET CHAÎNES DE BITS 87
#include <iostream>
int main()
{
std::cout << sizeof(bool) << std::endl;
}
¬x ≡ 1 − x
x ∧ y ≡ min(x, y) ≡ x · y
x ∨ y ≡ max(x, y)
x ⊕ y ≡ (x + y) mod 2
Exemple.
x ⊕ y = y ⊕ x,
x ⊕ x = 0,
x ⊕ 0 = x.
Remarque.
Les opérations bit à bit s’avèrent aussi utiles pour la technique crypto-
graphique du masque jetable où on chiffre et déchiffre des données sim-
plement à l’aide de l’opérateur OU exclusif.
Exemple.
Octet x: 10110101
Sur l’architecture ARMv8, ces opérations peuvent être effectuées sur 64 bits
à l’aide de ces instructions:
instructions effet
lsr xd, xn, j stocke dans xd le décalage de xn de j bits vers la droite
lsl xd, xn, j stocke dans xd le décalage de xn de j bits vers la gauche
Exemple.
Octet x: 10110101
instructions effet
ror xd, xn, j stocke dans xd le décalage circulaire de xn de
j bits vers la droite
Exemple.
Octet x: 11100101
instructions effet
asr xd, xn, j stocke dans xd le décalage arithmétique de xn
de j bits vers la droite
9.4 Masquage
Les opérateurs logiques permettent également d’isoler certains bits d’une chaîne.
Par exemple, l’instruction « and x19, x19, 4 » met tous les bits de xx19 à zéro,
à l’exception du troisième bit de poids faible qui demeure inchangé. En effet,
nous avons:
b63 ··· b3 b2 b1 b0
∧ ··· ∧ ∧ ∧ ∧
0 ··· 0 1 0 0
0 ··· 0 b2 0 0
b63 ··· b3 b2 b1 b0
∧ ··· ∧ ∧ ∧ ∧
0 ··· 1 0 0 1
0 ··· b3 0 0 b0
L’effet du masque varie selon les opérateurs logiques utilisés. Voici certaines
variantes pratiques:
Sur l’architecture ARMv8, ces opérations peuvent être effectuées à l’aide des
instructions and, orr, bic et eor respectivement.
Remarque.
image B image C
Si l’on considère noir comme le bit 1 et blanc comme le bit 0, alors on obtient
A[i, j] = B[i, j] ⊕ C[i, j] pour chaque pixel (i, j). En effet, cela découle du fait
que 0 = 0 ⊕ 0 = 1 ⊕ 1 et 1 = 0 ⊕ 1 = 1 ⊕ 0. De plus, les images B et C sont
individuellement indistinguables d’une image aléatoire.
CHAPITRE 9. VALEURS BOOLÉENNES ET CHAÎNES DE BITS 95
P4
5 4
Le reste du fichier décrit les pixels. Plus précisément, chaque ligne du fichier
dicte les pixels d’une ligne de l’image. Dans notre exemple, nous voulons donc
représenter:
01010
00000
10001
01110
Le format PBM stocke chaque bloc de 8 pixels d’une ligne dans un octet, et,
au besoin, ajoute des zéros afin de compléter le dernier octet d’une ligne. Dans
notre cas, il faut donc ajouter 3 bits à zéro sur chaque ligne, ce qui mène aux
octets suivants:
0x50
0x00
0x88
0x70
9.5.2 Implémentation
Ce programme implémente la procédure de déchiffrement. Autrement dit,
il lit consécutivement le contenu de deux images B et C au format PBM, puis
affiche le contenu de l’image A obtenue en calculant A[i, j] = B[i, j] ⊕ C[i, j]
pour chaque pixel (i, j). Afin de manipuler une image, on lit son en-tête:
P4
n m
CHAPITRE 9. VALEURS BOOLÉENNES ET CHAÎNES DE BITS 96
9.7 Exercices
9.1) Considérons les chaînes de six bits a = 101101 et b = 101010. Calculez
¬a, a ∧ b, a ∨ b et a ⊕ b bit à bit. Répétez l’exercice avec d’autres chaînes.
9.3) À partir d’un registre xd , comment peut-on obtenir une valeur booléenne
qui indique si xd contient un nombre pair?
9.4) Supposons que 8n bits soient représentés par un tableau de n octets. Ex-
pliquez comment extraire le ième bit.
9.6) Si le registre x19 contient l’entier signé −488410 , alors quelle est sa valeur
après l’exécution de l’instruction « asr x19, x19, 2 »?
9.7) Deux des programmes ci-dessous terminent avec une même valeur n dans
x19 , alors que l’autre programme termine avec une valeur différente de n
dans x19 . Quelle est la valeur décimale de n?
b4 b3 b2 b1 b0 b4 b3 b2 b1 b0
? ?
? ? ? ? ? ? ? ? ? ?
b4 0 b2 b1 0 ¬b4 b3 b2 b1 ¬b0
b4 b3 b2 b1 b0
?
? ? ? ? ?
b4 1 1 b1 b0
10
Chaînes de caractères
10.1 ASCII
Le codage ASCII (American Standard Code for Information Interchange) utilise
7 bits afin de représenter 128 caractères: les lettres de l’alphabet latin (en mi-
nuscules et majuscules), les chiffres (indo-)arabes, certains symboles mathé-
matiques et de ponctuation, ainsi que des caractères spéciaux. Les caractères
graphiques du codage ASCII sont répertoriés à la figure 10.1. Par exemple, la
lettre « m » est représentée par le code 109 = 6D16 = 11011012 , et le chiffre
« 6 » est représenté par 54 = 3616 = 01101102 .
Les autres caractères qui n’apparaissent pas à la figure 10.1 sont des ca-
ractères spéciaux non graphiques. Par exemple, le code 9 représente une tabu-
lation; le code 10 représente un saut de ligne (\n); le code 13 représente un
retour de chariot (\r); et le code 32 représente une espace.
Notons que le code d’une lettre minuscule se situe à 32 positions de la même
lettre en majuscule. En fait, le codage d’une lettre majuscule et minuscule ne dif-
fère qu’au 6ème bit de poids faible. Par exemple, a = 11000012 et A = 10000012 .
99
CHAPITRE 10. CHAÎNES DE CARACTÈRES 100
naux supplémentaires. Historiquement, une foule de codages ont été créés afin
d’étendre l’ASCII. En particulier, le codage ISO 8859-1 (Latin-1) étend l’ASCII
avec suffisamment de caractères pour la représentation du français et de plu-
sieurs langues indo-européennes dont le système d’écriture se base sur le latin.
Les caractères graphiques additionnels du codage ISO 8859-1 sont répertoriés
à la figure 10.2.
10.3 UTF-8
Bien que le format ISO 8859-1 soit suffisant pour plusieurs langues européennes,
il ne permet pas de représenter d’autres langues comme le grec, l’arabe, l’hé-
breu, le japonais et les langues chinoises. Afin de palier ce problème, le standard
Unicode spécifie plus de 143 859 caractères (et permet d’en accommoder plus
d’un million). Chaque caractère est associé à un code numérique, appelé « code
point » en anglais. Le codage UTF-8 utilise jusqu’à 21 bits afin de représenter
les caractères spécifiés par Unicode. Contrairement aux codages ASCII et ISO
8859-1, le codage UTF-8 code les caractères avec un nombre variable d’octets:
1, 2, 3 ou 4 selon le caractère.
Pour des raisons de rétrocompatibilité et d’économie de mémoire, les 128
premiers caractères de l’UTF-8 sont précisément ceux de l’ASCII codés sur un
seul octet. Les 128 caractères suivants sont ceux de l’ISO 8859-1 codés sur deux
octets. Par exemple, le caractère « é » codé par E916 = 111010012 sous le codage
ISO 8859-1, est codé par C3A916 = 11000011 101010012 sous le codage UTF-8.
Le format général de l’UTF-8 se décrit ainsi [Yer03]:
Exemple.
Remarque.
Il existe des codages alternatifs pour le standard Unicode, par ex. UTF-16
et UTF-32. L’UTF-32 code tous les caractères sur 4 octets, ce qui permet
notamment d’accéder rapidement au ième caractère d’une suite de carac-
1. Pour des raisons techniques, les codes D80016 à DFFF16 sont considérés invalides et ne re-
présentent donc aucun caractère.
CHAPITRE 10. CHAÎNES DE CARACTÈRES 103
Dans notre cas, une chaîne de caractères lue dans un terminal avec scanf
est codée sous UTF-8. Si seules des lettres du codage ASCII sont entrées,
alors on obtient une chaîne sous codage ASCII (avec un 0 au 8ème bit).
Remarque.
— xkcd c
CHAPITRE 10. CHAÎNES DE CARACTÈRES 104
10.6 Exercices
10.1) Donnez le codage UTF-8 des caractères associés à ces codes numériques:
0006AB16 , 00007316 , 01234516 et 00A0B116 .
10.2) Écrivez un sous-programme qui reçoit une chaîne de caractères sous co-
dage ASCII et qui retourne sa taille (excluant son caractère nul).
10.3) Écrivez un sous-programme qui reçoit une chaîne de caractères sous co-
dage ASCII, ainsi que sa taille, et qui retourne une valeur booléenne indi-
quant si la chaîne est un palindrome.
10.4) Écrivez un sous-programme qui reçoit une chaîne de caractères et qui re-
tourne une valeur booléenne indiquant si son premier caractère fait partie
du codage ASCII.
10.5) Supposons que x19 contienne le code ASCII d’une lettre. Donnez une seule
instruction qui inverse la casse de x19 ; autrement dit, une lettre minuscule
doit être mise en majuscule et vice-versa.
106
CHAPITRE 11. SOUS-PROGRAMMES ET MÉMOIRE 107
00000 · · · 000016
Instructions (text)
Initialisées (data)
Tas
Données dynamiques
↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓
↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ sp (pointeur de pile)
Pile
FFFF · · · FFFF16
Remarque.
.macro SAVE
stp x29, x30, [sp, -96]!
mov x29, sp
stp x27, x28, [sp, 16]
stp x25, x26, [sp, 32]
stp x23, x24, [sp, 48]
stp x21, x22, [sp, 64]
stp x19, x20, [sp, 80]
.endm
sp
x29
sp + 8
x30
sp + 16
x27
sp + 24
x28
.. ..
. .
sp + 80
x19
sp + 88
x20
(ancien sommet) sp + 96
contenu antérieur
Notons que l’instruction « mov x29, sp » a pour but de stocker un pointeur
vers l’information qui permet de restaurer l’ancien sommet de la pile. Cela est
requis par la convention d’appel d’ARMv8 [ARM13, Sec. 5.2.3]. La portion de
la mémoire située entre x29 et l’ancien sommet se nomme le bloc d’activation
de l’appel. En particulier, l’adresse stockée dans x29 permet de rétablir la pile.
.macro RESTORE
ldp x27, x28, [sp, 16]
ldp x25, x26, [sp, 32]
ldp x23, x24, [sp, 48]
ldp x21, x22, [sp, 64]
ldp x19, x20, [sp, 80]
ldp x29, x30, [sp], 96
.endm
11.2 Récursion
La pile d’exécution peut être utilisée afin d’implémenter des sous-programmes
récursifs: c’est-à-dire des sous-programmes qui s’appellent eux-mêmes. À titre
CHAPITRE 11. SOUS-PROGRAMMES ET MÉMOIRE 110
Le code ci-dessus utilise les macros SAVE et RESTORE. Ainsi, chaque ap-
pel ajoute 96 octets à la pile d’exécution. Plusieurs de ces octets sont inutiles
puisque la plupart des registres sont inutilisés.
Le code suivant utilise trois fois moins de mémoire en ajoutant 32 octets
plutôt que 96 octets:
fib: // fib(n)
// Sauvegarder environnement // {
stp x29, x30, [sp, -32]! //
mov x29, sp //
stp x19, x20, [sp, 16] //
//
// Calculer fib(n) //
mov x19, x0 //
cmp x19, 2 // if (n >= 2)
b.lo fin // {
//
sub x0, x19, 1 //
bl fib //
mov x20, x0 // r = fib(n - 1)
CHAPITRE 11. SOUS-PROGRAMMES ET MÉMOIRE 111
//
sub x0, x19, 2 //
bl fib //
add x0, x20, x0 // n = r + fib(n - 2)
fin: // }
// Restaurer environnement //
ldp x29, x30, [sp], 16 //
ldp x19, x20, [sp], 16 //
ret // return n
// }
11.3 Limitations
La taille de la pile d’exécution est bornée par la taille de la mémoire principale,
et elle est souvent restreinte à une taille plus petite par le système d’exploitation.
Ainsi, une récursion trop profonde peut mener à une erreur de segmentation, et
plus précisément à un débordement de pile. En effet, si la pile se remplit lors
d’un appel, l’appel suivant tentera d’écrire en mémoire en dehors de la pile.
Remarque.
Le célèbre site Web de questions et réponses Stack Overflow tire son nom
du terme anglais signifiant « débordement de pile ».
CHAPITRE 11. SOUS-PROGRAMMES ET MÉMOIRE 112
11.5 Exercices
11.2) Si sp contient AF0916 et que l’on empile le contenu de quatre registres sur
la pile d’exécution, que devient sp? Puis, que devient sp si l’on en dépile
deux?
11.3) L’instruction « stp » empile le contenu de deux registres, ce qui nous force
à empiler un nombre pair de double mots. Comment peut-on gérer l’em-
pilement d’un nombre impair de double mots?
Vous n’avez pas à gérer les débordements. Vous pouvez utiliser les macros
SAVE et RESTORE.
11.5) Supposons que vous n’ayez pas accès aux macros SAVE et RESTORE. Consi-
dérons un sous-programme qui effectue des appels récursifs et qui utilise
uniquement les registres x19 , x20 , x23 et x25 . Complétez le code des macros
suivantes afin d’implémenter la sauvegarde et la restauration des registres
spécifiquement pour ce sous-programme:
CHAPITRE 11. SOUS-PROGRAMMES ET MÉMOIRE 113
11.6) Le programme ci-dessous affiche les nombres de 1 à 10, puis boucle à l’in-
fini plutôt que de terminer. Expliquez pourquoi le programme ne termine
pas. Plus précisément, identifiez les étiquettes qui sont atteintes infini-
ment souvent et la raison pour laquelle elles le sont.
.global main
main:
main0: bl foo
main1: bl exit
foo:
foo0: mov x19, 0
foo1: add x19, x19, 1
foo2: adr x0, fmt
foo3: mov x1, x19
foo4: bl printf
foo5: cmp x19, 10
foo6: b.lo foo1
foo7: ret
.section ".rodata"
fmt: .asciz "%lu\n"
11.7) Implémentez cet algorithme sous forme de sous-programme sans les ma-
cros SAVE et RESTORE:
Entrée: tableau d’entiers signés de 64 bits spécifié par une adresse t et
un nombre d’éléments n; un entier signé de 64 bits x
Retour: 1 si le tableau contient x, 0 sinon
elem(t, n, x):
si n = 0 alors
retourner 0 // tableau vide, donc forcément faux
sinon
u ← adresse de l’élément à l’indice 1 du tableau
retourner (t[0] = x) ∨ elem(u, n − 1, x)
CHAPITRE 11. SOUS-PROGRAMMES ET MÉMOIRE 114
11.8) Implémentez cet algorithme sous forme de sous-programme sans les ma-
cros SAVE et RESTORE:
Nous avons vu jusqu’ici comment manipuler les entiers sur un ordinateur. Bien
que ce type élémentaire numérique soit suffisant pour une foule d’applications,
il ne l’est pas nécessairement pour d’autres comme la simulation, le calcul scien-
tifique, l’infographie, le traitement de signal, l’apprentissage automatique et les
méthodes numériques de la recherche opérationnelle. Dans ce chapitre, nous
considérons donc la représentation et la manipulation de nombres réels. Soient
a, b ∈ R des nombres réels tels que a < b. L’intervalle [a, b] contient une in-
finité de nombres. Ainsi, il est impossible de représenter tous les nombres de
l’intervalle [a, b] à l’aide d’un ordinateur, comme nous l’avons fait pour N et
Z. Nous étudions donc la représentation en nombres en virgule flottante qui
permet d’approximer raisonnablement les nombres réels.
12.1 Représentation
Un nombre en virgule flottante est un nombre de la forme
exposant
signe
z}|{ z}|{
± d0 ,d1 d2 · · · dn−1 × β e
| {z } |{z}
mantisse base
Exemple.
115
CHAPITRE 12. NOMBRES EN VIRGULE FLOTTANTE 116
La plus petite valeur absolue xmin et la plus grande valeur absolue xmax repré-
sentables par un nombre normalisé sont:
xmin = 1,0 · · · 0 × β emin xmax = (β − 1),(β − 1) · · · (β − 1) × β emax
= β emin , = (β − 1)(β − 1) · · · (β − 1),0 × β emax −(n−1)
= (β n − 1) · β emax −n+1
= β emax +1 − β emax +1−n .
Exemple.
−3 ⁄2
7
−2 −3⁄2 ⁄4
7 ⁄2
5
−1 −3⁄4 −1⁄2 5
⁄8 ⁄8
7 ⁄4
5
12.2 Précision
Puisque la vaste majorité 1 des nombres réels ne sont pas représentables par
un nombre en virgule flottante, ceux-ci doivent être approximés. Par exemple,
1. En fait, il y en a une infinité non dénombrable!
CHAPITRE 12. NOMBRES EN VIRGULE FLOTTANTE 117
décimal binaire
Exemple.
Remarque.
12.3 Arithmétique
Voyons comment calculer la somme et le produit de deux nombres normalisés:
x = 1,u × β e ,
y = 1,v × β f .
12.3.1 Addition
Supposons que e ≤ f . Si ce n’est pas le cas, on peut simplement inverser x et y.
Observons que:
x + y = (1,u × β e ) + (1,v × β f )
= (1,u · β e−f × β f ) + (1,v × β f )
= (1,u · β e−f + 1,v) × β f .
Exemple.
12.3.2 Multiplication
Observons que:
x · y = (1,u × β e ) · (1,v × β f )
= (1,u · 1,v) × β e+f .
Exemple.
Leurs plus petits et plus grands nombres positifs normalisés sont donc:
format ϵ
simple 2−24 = 0,000000059604644775390625
double 2−53 = 0,00000000000000011102230246251565404236316680908203125
2. Dans la version 2008 de la norme IEEE 754, ces deux formats sont officiellement nommés
binary32 et binary64, respectivement. Nous utilisons plutôt la nomenclature traditionnelle simple
et double afin de référer à ces deux formats.
CHAPITRE 12. NOMBRES EN VIRGULE FLOTTANTE 121
Zéro. Le nombre 0, qu’on ne peut pas normaliser, est représenté par un bit de
signe suivi de tous les autres bits assignés à 0:
b31 00 · · · 000 · · · 0.
Ainsi, il existe deux zéros: −0 et +0 qui valent tous deux 0.
Infini. La norme IEEE 754 permet de représenter l’infini en assignant tous les
bits de l’exposant à 1 et les bits de la mantisse à 0:
b31 11 · · · 100 · · · 00.
Le bit de signe détermine s’il s’agit de −∞ ou +∞.
CHAPITRE 12. NOMBRES EN VIRGULE FLOTTANTE 122
b31 11 · · · 1 e 00 · · · 01.
Le bit e indique si une erreur doit être lancée lors de l’obtention de NaN.
Observation.
Certains entiers de 64 bits (ou 32 bits) ne sont pas représentables par des
nombres en virgule flottante de précision double (ou simple).
Par exemple, 253 + 1 est clairement représentable en tant qu’entier
de 64 bits, mais pas en tant que nombre en virgule flottante double pré-
cision. En effet, 253 + 1 s’écrit de cette façon:
1, 0| ·{z
· · 0} 1 × 253 .
52 fois
Observation.
Des calculs successifs peuvent créer des comportements surprenants. Par
exemple, le résultat de l’addition d’une liste de nombres en virgule flot-
tante dépend de l’ordre dans lequel les valeurs sont sommées.
CHAPITRE 12. NOMBRES EN VIRGULE FLOTTANTE 123
Remarque.
12.5.1 Registres
L’architecture ARMv8 possède 32 registres de nombres en virgule flottante de
64 bits [ARM13, Sect. 5.1.2] dont l’usage est comme suit:
registres utilisation
d0 – d7 registres d’arguments et de retour de sous-programmes
d8 – d15 registres sauvegardés par l’appelé
d16 – d31 registres sauvegardés par l’appelant
12.5.2 Instructions
Le jeu d’instruction des nombres en virgule flottante ressemble à celui des en-
tiers. Les conditions de branchement sont les mêmes que pour les entiers et sont
déterminées à partir de codes de condition mis à jour par fcmp. Voici quelques-
unes des instructions:
CHAPITRE 12. NOMBRES EN VIRGULE FLOTTANTE 124
convertit le nombre en
virgule flottante dans vn vers
fcvt fcvt vd, vn un nombre en virgule fcvt d8, s9
flottante d’une autre
précision dans vd
CHAPITRE 12. NOMBRES EN VIRGULE FLOTTANTE 125
Remarque.
main: // main()
// Lire x // {
adr x0, fmtEntree
//
adr x1, temp //
bl scanf // scanf("%lf", &temp)
ldr d8, temp // x = temp
//
// Lire y //
adr x0, fmtEntree //
adr x1, temp //
bl scanf //
ldr d9, temp // scanf("%lf", &temp)
// y = temp
// Calculer ||(x, y)|| //
fmov d0, d8 //
fmov d1, d9 //
bl norme //
fmov d8, d0 // n = norme(x, y)
//
// Afficher ||(x, y)|| //
adr x0, fmtSortie //
fmov d0, d8 //
bl printf // printf("%lf\n", n)
//
// Quitter //
mov x0, 0 //
bl exit // return 0
// }
/***************************************************************
Entrée: nombres x, y en virgule flottante précision double
Sortie: norme euclidienne du vecteur (x, y)
***************************************************************/
CHAPITRE 12. NOMBRES EN VIRGULE FLOTTANTE 126
.section ".rodata"
fmtEntree: .asciz "%lf"
fmtSortie: .asciz "%lf\n"
.section ".bss"
.align 8
temp: .skip 8
CHAPITRE 12. NOMBRES EN VIRGULE FLOTTANTE 127
12.7 Exercices
12.1) Normalisez ces nombres et donnez leur valeur décimale:
123,456 × 102 − 0,0909 × 10−1 0,000101 × 2−3 − 11101,11 × 24
(1,11 × 20 ) + (1,01 × 21 ).
(1,11 × 20 ) · (1,01 × 21 ).
12.6) Ces deux sous-questions portent sur les nombres en virgule flottante simple
précision IEEE 754.
— Expliquez pourquoi la chaîne 00001101 représente l’exposant −114.
— Une chaîne de 8 bits permet de représenter 256 exposants différents.
Un nombre simple précision est pourtant limité à 254 exposants dif-
férents. Pourquoi y a-t-il 254 exposants possibles plutôt que 256?
12.7) Quel est le codage binaire du nombre décimal 42,5 en tant que nombre
en virgule flottante simple précision (IEEE 754)?
while (x != 1.0) {
x += 0.1;
}
Dans les chapitres précédents, nous n’avons considéré qu’une seule forme d’en-
trée/sortie: celles d’un terminal. De plus, celles-ci se réalisaient avec des fonc-
tions de la librairie standard du langage C. Dans ce chapitre, nous voyons comme
réaliser des entrées/sorties de bas niveau. Afin d’illustrer ces concepts, nous sur-
volons l’architecture de la console de jeux vidéo Nintendo Entertainment System
(NES) illustrée aux figures 13.1 et 13.2. Cela permettra aussi de mettre en pra-
tique l’ensemble de nos connaissances sur une autre architecture qu’ARMv8.
129
CHAPITRE 13. INTRODUCTION AUX ENTRÉES/SORTIES: NES 130
■ mémoire primaire: possède une portion de mémoire tout usage (dite zero-
page), une pile d’exécution, ainsi qu’une autre portion généralement des-
tinée au stockage temporaire de tuiles;
■ mémoire d’entrée/sortie: contient des registres permettant d’effectuer des
entrées/sorties notamment avec le processeur d’images et les manettes (il
ne s’agit pas de registres au sens usuel puisqu’ils se situent en mémoire principale);
■ cartouche de jeu: contient le code du programme (donc du jeu vidéo), et
d’autres segments de mémoire optionnels (pour sauvegarde et expansions).
■ tuiles: contient deux tables des tuiles du jeu: sprites (personnages, objets,
etc.) et de l’arrière-plan (collines, nuages, etc.);
■ arrière-plan: quatre tables spécifiant les tuiles qui forment l’arrière-plan
actuel (tuiles, positions, couleurs, orientations, etc.);
■ palettes de couleur: décrit les couleurs disponibles pour les tuiles (permet,
par exemple, de spécifier la couleur des personnages dans un monde sur terre et
dans un monde sous-terrain).
Comme dans le cas de la mémoire principale, certaines portions sont simple-
ment des mirroirs. Par exemple, l’adresse 300016 pointe en fait vers l’adresse
200016 , et l’adresse 3F2016 pointe en fait vers l’adresse 3F0016 .
Notons que le processeur d’images a également accès à une mémoire de
sprites de 256 octets située en dehors de la mémoire vidéo.
000016
Mémoire générale 000016
Tuiles
Table des sprites
Mémoire primaire
(zero-page) 100016
010016 Table d’arrière-plan
Pile Table de tuiles 0 200016
23C016
Mémoire générale 020016 Table d’attributs 0
240016
(stockage de tuiles) Table de tuiles 1
080016 27C016
Mirroir des registres Table d’attributs 1
Arrière-plan
402016 Palettes
Mémoire
Cartouche de jeu
d’arrière-plan
d’expansion 3F1016
600016 Palettes des
Mémoire
sprites
de sauvegarde 3F2016
800016 Mirroir des registres
Mémoire
3F0016 à 3F1F16
de programme 400016
1000016
13.2 Registres
Le processeur du NES possède quatre registres d’un octet (8 bits) chacun:
Exemple.
expression valeur
#5 510
#$FF FF16
#%00010011 000100112
$FF adresse FF16
CHAPITRE 13. INTRODUCTION AUX ENTRÉES/SORTIES: NES 133
Les valeurs numériques possèdent 8 bits, alors que les adresses peuvent par-
fois posséder jusqu’à 16 bits.
13.3.4 Arithmétique
13.3.5 Logique
13.4.1 Tuiles
L’image à l’écran est constituée de tuiles de 8 × 8 pixels. Chacune de ces tuiles
est stockée dans la cartouche de jeu et chargée dans une table de la mémoire
vidéo. Afin d’afficher une tuile, il faut spécifier sur 4 octets (dans cet ordre):
— sa position verticale comprise entre 0 et 255;
— son identifiant (sa position dans la table de tuiles);
— ses attributs tels qu’une palette de couleur;
— sa position horizontale comprise entre 0 et 255.
Les attributs d’une tuile sont déterminés par ces huit bits:
n?
?
le
e?
r
la
ta
eu
al
-p
on
ic
ul
re
rt
riz
és
co
è
ve
i
rr
s
ho
ili
de
l’a
on
ut
on
xi
te
re
in
xi
fle
t
iè
le
fle
ts
rr
Ré
Pa
Ré
De
Bi
b7 b6 b5 b4 à b2 b1 à b0
CHAPITRE 13. INTRODUCTION AUX ENTRÉES/SORTIES: NES 136
Exemple.
Exemple.
lda #$02
sta $4014
Afin d’éviter des incohérences visuelles, l’affichage ne doit se faire que durant
l’intervalle de rafraîchissement vertical (VBLANK). Cet intervalle correspond à la
période de temps où le canon a électron se repositionne au haut de l’écran avant
d’effectuer un nouvel affichage. Afin d’en être informé, il est possible de spécifier
une sous-routine qui est appelée chaque fois que cet intervalle se produit. Durant
l’appel de cette sous-routine, le processeur met en suspens ce qu’il était en train
d’exécuter, puis reprend lorsque la sous-routine est complétée. Un jeu possède
donc normalement une boucle infinie qui alterne entre traitement et affichage.
SELECT START
A B
Pour chacune des lectures, le bit de poids faible vaut 1 si (et seulement si)
le bouton était enfoncé lors de l’initialisation du protocole. Par exemple, le code
suivant vérifie si le bouton A est enfoncé:
lda #1 ;
sta $4016 ;
lda #0 ;
sta $4016 ; demander une lecture des boutons
;
lda $4016 ;
and #%00000001 ; obtenir l'état du bouton A
On pourrait préserver l’état des huit boutons dans un seul octet en accumu-
lant chacun des bits à l’aide de décalages et d’opérations de masquages.
main: ; main()
; initialisation technique ; {
;
jsr init_variables ; init_variables()
;
; autres init. technique ; }
init_variables: ; init_variables()
lda #0 ; {
sta posX ; posX = 0
lda #100 ;
sta posY ; posY = 100
lda #1 ;
sta chiffre ; chiffre = 1 (no. de tuile)
lda #24 ;
sta iter ; iter = 24
rts ; }
update: ; update()
lda #$02 ; {
sta $4014 ; copier tuiles 0x0200 à
; 0x02FF vers PPU
;
jsr deplacer_chiffre ; deplacer_chiffre()
jsr update_chiffre ; update_chiffre()
;
rti ; }
deplacer_chiffre: ; deplacer_chiffre() {
inc posX ; posX++
CHAPITRE 13. INTRODUCTION AUX ENTRÉES/SORTIES: NES 139
;
lda #1 ;
sta $4016 ;
lda #0 ;
sta $4016 ; demander lecture des boutons
;
lda $4016 ; lire bit b de poids
and #%00000001 ; faible du bouton A
;
clc ;
adc posY ;
sta posY ; posY += b (incrémente posY
; si A est enfoncé)
rts ; }
update_chiffre: ; update_chiffre()
; Position verticale ; {
lda posY ;
sta $0200 ; mem[0x0200] = posY
;
; Choix de la tuile ;
lda chiffre ; mem[0x0201] = chiffre
sta $0201 ;
;
lda iter ;
cmp #0 ; if (iter != 0) {
beq prochain_chiffre ;
;
prochaine_iteration: ;
sec ;
dec iter ; iter--
jmp continuer ; }
prochain_chiffre: ; else {
lda #24 ;
sta iter ; iter = 24
CHAPITRE 13. INTRODUCTION AUX ENTRÉES/SORTIES: NES 140
Remarque.
13.8 Exercices
13.1) Complétez le sous-programme « lecture: » ci-dessous afin qu’il assigne
la valeur 1 à la variable appuye si les boutons select et start de la première
manette sont tous deux appuyés, et 0 autrement.
lecture:
/* code ici */
rts
Rappel:
— l’adresse $4016 du NES est liée au port de sa première manette;
— pour initier une lecture, il faut envoyer 1, puis 0, vers son port;
— l’ordre des boutons est: A, B, select, start, haut, bas, gauche, droite.
renverser:
/* code ici */
rts
14
Entrées/sorties
attendre_vblank: ; attendre_vblank()
lda $2002 ; {
and #%10000000 ; while (!VBLANK) {
cmp #%10000000 ; // ne rien faire
bne attendre_vblank ; }
rts ; }
142
CHAPITRE 14. ENTRÉES/SORTIES 143
En général, cette méthode est connue sous le nom d’attente active puisqu’elle
attend en répétant constamment une opération.
14.2 Interruptions
L’attente active se démarque par sa simplicité, mais elle monopolise les cycles
du processeur et empêche toute autre instruction d’être exécutée. Ainsi, elle
fonctionne relativement bien sur le NES puisque l’intervalle de rafraîchissement
se produit aux 16,6 millisecondes, mais elle est peu adaptée aux systèmes où les
entrées/sorties se produisent rarement ou à des fréquences variables.
Les interruptions offrent une solution élégante à cette problématique. Plutôt
que d’attendre incessamment qu’un événement se produise, un signal est lancé
lorsqu’il se produit. Le processeur s’interrompt alors et lance une sous-routine
qui traite l’événement. Lorsque la sous-routine se termine, le processeur reprend
ses activités. Ainsi, un programme qui lit une touche au clavier, par exemple,
n’a pas à bloquer l’ordinateur jusqu’à ce que l’on appuie sur une touche.
..
.
FFFA16
NMI
FFFC16
RESET
FFFE16
IRQ
Exemple.
Considérons ce programme:
foo: ; Sous-programme
cmp #0
beq foo
rts
tax ; x = a
pla ; dépiler vers a
main: ;
lda #%00000000 ;
sta $2000 ; désactiver les interruptions NMI
; Initialisation ici
lda #%10000000 ;
sta $2000 ; activer les interruptions NMI
; Début de l'affichage
Cette méthode est simple, mais requiert 4n accès mémoire afin de stocker
n tuiles, ce qui accapare plusieurs cycles du processeur. Le NES offre un autre
mécanisme plus efficace afin de transférer plusieurs tuiles: l’accès direct à la
mémoire (DMA). Celui-ci permet au processeur d’initier un transfert de données,
puis de laisser un contrôleur effectuer lui-même le transfert.
Pour ce faire, il faut:
— stocker la description des tuiles dans un segment contigu de la mémoire
principale, par exemple de $0200 à $02FF;
— écrire l’octet de poids fort du segment à l’adresse $4014.
Ainsi, les tuiles peuvent être envoyées comme ceci:
; Position verticale ;
lda #100 ;
sta $0200 ; mem[0x0200] = 100
;
; Numéro de la tuile ;
lda #1 ;
sta $0201 ; mem[0x0201] = 1
;
; Attributs de la tuile ;
lda #%00000000 ;
sta $0202 ; mem[0x0202] = 0
;
; Position horizontale ;
lda #127 ;
sta $0203 ; mem[0x0203] = 127
;
; Stocker les autres tuiles ; ...
;
; Initier un transfert ;
lda #$02 ; copier mem[0x0200, 0x02FF]
sta $4014 ; vers la mémoire de sprites
main: // main()
adr x0, temp // {
mov x1, 10 //
bl lire // lire(&temp, 10)
//
adr x0, temp //
mov x1, 10 //
bl afficher // afficher(&temp, 10)
//
mov x0, 0 //
bl exit // }
//
afficher: // afficher(chaine, taille)
mov x9, x0 // {
mov x10, x1 //
//
mov x8, 64 // /* write = 64
mov x0, 1 // stdout = 1 */
mov x1, x9 //
mov x2, x10 //
svc 0 // write(stdout, chaine, taille)
//
ret // }
//
lire: // lire(tampon, taille)
mov x9, x0 // {
mov x10, x1 //
CHAPITRE 14. ENTRÉES/SORTIES 150
//
mov x8, 63 // /* read = 63
mov x0, 0 // stdin = 0 */
mov x1, x9 //
mov x2, x10 //
svc 0 // read(stdin, tampon, taille)
//
ret // }
.section ".bss"
temp: .skip 10
Remarque.
Remarque.
14.6 Exercices
14.1) Écrivez un sous-programme pour le NES qui attend activement que le bou-
ton start soit enfoncé afin d’appeler le sous-programme « faire_pause ».
14.4) Nos programmes ARMv8 se terminent par l’appel exit(0) afin de deman-
der la terminaison du programme sans erreur. Nous utilisions jusqu’ici la
fonction offerte par la librairie standard C. Comment peut-on plutôt ap-
peler le service exit du noyau de Linux (sachant que son code est 93)?
A
Fiches récapitulatives
Les fiches des pages suivantes résument le contenu de chacun des chapitres.
Elles peuvent être imprimées recto-verso, ou bien au recto seulement afin d’être
découpées et pliées en deux. À l’ordinateur, il est possible de cliquer sur la plu-
part des puces « ▶ » pour accéder à la section du contenu correspondant.
152
1. Systèmes de numération
Conversions
Système unaire n fois
z }| { ▶ b à 10: x0 + b · (x1 + b · (x2 + b · (. . . + b · xn−1 )))
▶ Chaque nombre n ∈ N se représente par 1 · · · 11
▶ 10 à b: diviser à répétition par b et concaténer les restes de
▶ L’addition correspond à la concaténation droite à gauche, par ex. 62 = 110:
▶ Pas concis 6 ÷ 2 = 3 reste 0, 3 ÷ 2 = 1 reste 1, 1 ÷ 2 = 0 reste 1
▶ b à bm : remplacer chaque bloc de taille m par sa valeur en
Représentation positionnelle base bm , par ex. si bm = 23 : 10110 → 26
▶ Généralisation du système décimal à une base b ∈ N≥2 ▶ bm à b: éclater chaque symbole vers sa représentation de
▶ Systèmes particuliers: binaire (b = 2), octal (b = 8), décimal taille m en base b, par ex. si bm = 23 : 73 → 111011
(b = 10), hexadécimal (b = 16)
Addition
▶ Chiffres: éléments de {0, 1, . . . , b − 1}
▶ Comme en base 10: additionner chiffre à chiffre en base b et
▶ Chiffres au-delà de 9: A = 10, B = 11, . . . , F = 15, . . . propager une retenue vers la gauche
▶ Valeur de x en base b: xb = xn−1 · bn−1 + . . . + x1 · b1 + x0 · b0
Fractions
▶ Exemple: 8B516 = 8 · 162 + 11 · 161 + 5 · 160
▶ Exemple: (11,01)2 = 1 · 21 + 1 · 20 + 0 · 2−1 + 1 · 2−2 = 3,25
▶ Les zéros tout à gauche ne changent rien: (0 · · · 0x)b = xb
▶ Chiffres non significatifs: (0 · · · 0x,y0 · · · 0)b = (x,y)b
6. Tableaux
Généralités Calcul d’adresse
▶ Tableau: collection d’éléments identifiés par des indices ▶ Index: adresse relative à laquelle est stocké un élément
▶ Éléments: tous de même taille, contigus en mémoire ▶ Calcul: si a = adresse du tableau et k = nombre d’octets d’un
▶ Indice: d-uplet i où d ≥ 1 est la dimension élément, alors l’adresse d’un élément correspond à:
▶ Bornes: 0 ≤ ij < nj pour chaque dimension j
i·k
a + |{z} a + (i · n1 + j) · k
▶ Taille: n0 · n1 · · · nd−1 éléments | {z }
index élém. i (tableau 1D) index élém. (i, j) (tableau 2D)
▶ Types: le type des éléments est implicite
▶ Exemples de tableau 1D et tableau 2D: Allocation/accès mémoire
(0, 0) 2 ▶ Tableau non initialisé:
0 01010101
(0, 1) 33 .section ".bss"
1 11110000
(1, 0) 65535 .align 2
2 01101101 tab: .skip 3*2*2 // n0 * n1 * # octets
(1, 1) 73
3 11111111
4 11110101
(2, 0) 9000 ▶ Tableau initialisé:
(2, 1) 255
.section ".data"
tab: .hword 2, 33, 65535, 73, 9000, 255 // six demi-mots
n0 = 5
n0 = 3, n1 = 2
5 éléments ▶ Accès: avec str / ldr (ou variantes) + modes d’adressage
6 éléments
7. Programmation structurée
Séquence Itération
▶ Composition séquentielle d’instructions ▶ Exécution répétée d’instructions (while, do while, for, ...)
▶ Une instruction de haut niveau peut nécessiter plusieurs ins- ▶ Implémentation: branchements arrière, et parfois avant:
tructions de bas niveau; par ex. « x19 *= 7 » devient:
while (cond(xd, xn)) { boucle:
mov x20, 7 // code cmp xd, xn
mul x19, x19, x20 } b.¬cond fin
// code
Sélection b boucle
fin:
▶ Exécution conditionnelle d’instructions (if, switch, ...)
▶ Implémentation: branchements avant: Sous-programmes
if (cond(xd, xn)) { si: ▶ Permettent de modulariser le code en sous-routines
// code si cmp xd, xn
}
▶ Registres partagés par programme et sous-programmes
b.¬cond sinon
else { // code si ▶ Arguments: passés par valeur ou adresse dans x0 –x7 (en ordre)
// code sinon b fin
} sinon: ▶ Appel: « bl sprog » assigne x30 ← pc+4 et branche à sprog:
// code sinon
fin: ▶ Retour: « ret » branche vers l’adresse de retour x30
▶ Conditions multiples: obtenues avec plusieurs sélections ▶ Sauvegarde: l’appelé doit rétablir les registres x19 à x30
8. Circuits logiques
Circuits Décodage
▶ « Blocs » de base constitués de portes logiques qui permettent ▶ Décodeur: sur entrée x, sortie: yx = 1 et yj = 0 pour j ̸= x
d’implémenter l’ordinateur:
▶ Multiplexeur: sur entrée x, sélectionne le bit yx
x xy xy xy
▶ Instructions: décodables/exécutables à l’aide de tels circuits
x1 x0 y3 y2 y1 y0 x1 x0
Décodeur
¬x x∧y x∨y x⊕y
Arithmétique
▶ Demi-additionneur: somme de deux bits
▶ Additionneur complet: somme de deux bits et d’une retenue
▶ Addition: somme sur n bits avec un demi-additionneur et une y3 y2 y1 y0
cascade de n − 1 additionneurs complets r x y r sb
Mémoire yx
x y
▶ Circuits séquentiels: peuvent mémoriser des bits
▶ Verrou: stocke un bit b,
remise à 0 avec r, et mise à 1 avec s
retenue somme
retenue somme sortie
Instructions (text)
▶ Contient les données allouées dynamiquement: structures de
données, objets, etc.
Données statiques
Tas
▶ Empiler: décrémenter sp + stocker avec stp xd, xn, a
↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ▶ Dépiler: incrémenter sp + charger avec ldp xd, xn, a
Récursion.
sp (pointeur de pile)
↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑
▶ Implémentée par: appels de sous-prog. + usage de la pile
Pile ▶ Récursion trop profonde: erreur car la pile est bornée
FFFF · · · FFFF16 ▶ Solution (partielle): empiler le moins de données possibles
14. Entrées/sorties
Mécanismes d’entrée/sortie. Accès direct à la mémoire (DMA).
▶ Attente active: interrogation continue d’un registre d’état jus- ▶ DMA: permet au processeur d’initier un accès mémoire et de
qu’à un événement (ex. VBLANK) laisser un contrôleur effectuer le transfert de données
▶ Interruption: signal lancé vers le processeur lors d’un événe- ▶ Sur le NES: envoi des tuiles mem[0x0200, 0x02FF] vers la
ment (ex. NMI, RESET, IRQ) mémoire de sprites via DMA:
lda #$02
Interruptions. sta $4014
Appels système.
▶ Gestionnaire: sous-routine qui traite une interruption ▶ Accès E/S: empêché par le système d’exploitation (sécurité)
▶ Table d’interruptions: contient l’adresse des gestionnaires ▶ Appel système: service offert par le noyau du système d’ex-
▶ Traitement: sauvegarder l’état du processeur; appeler le ges- ploitation; appelé via une interruption logicielle
tionnaire; restaurer l’état ▶ Exemples UNIX + ARMv8: // Afficher chaine
▶ Priorité: valeur numérique assignée à une interruption mov x8, 64
code appel système
▶ Gestion des priorités: interruption ignorée si une interruption mov x0, 1
64 write(flux, chaine, #octets) adr x1, chaine
de priorité > est en cours; gestionnaire en exécution mis en
read(flux, tampon, #octets) mov x2, 10
attente si une interruption de priorité ≥ est lancée 63
svc 0
▶ Non masquable: top priorité, ne peut pas ignorer (ex. RESET) Flux d’entrée standard = 0
Flux de sortie standard = 1
B
Solutions des exercices
Cette section présente des solutions à certains des exercices du document. Dans
certains cas, il ne peut s’agir que d’ébauches de solutions.
158
ANNEXE B. SOLUTIONS DES EXERCICES 159
Chapitre 1
1.1) Si l’on suppose que x ≥ y, alors on peut calculer x − y en retirant en alter-
nance un symbole de la représentation de x et y jusqu’à ce que ce dernier
devienne vide, auquel cas le premier nombre contient la différence.
Pour calculer x × y, on: (1) initialise une séquence vide z; (2) retourne z
si x est vide; (3) retire un symbole de x; (4) concatène y à z; (5) répète
l’étape 2.
1.4) 22 bits: deux bits pour le chiffre 2, et quatre bits pour chaque autre chiffre
hexadécimal.
1.5) Le plus grand nombre pouvant être représenté par 9 chiffres en base 4
est: 49 − 1 = 262143. Ainsi, le plus grand multiple de 5 représentable est
262140. Autrement dit, il s’agit de ((49 − 1) ÷ 5) · 5 = 262140.
1.6) Un nombre fractionnaire est inférieur à 1 ssi il ne possède que des zéros à
gauche de la virgule. Ainsi, la plus grande valeur est 00000000,1111112 =
0,1111112 = 1/2 + 1/4 + 1/8 + 1/16 + 1/32 + 1/64 = 63/64 = 0, 984375.
1.7) Non, il n’en existe pas. Le nombre 111b = b2 + b + 1 est pair ssi b2 + b est
impair. Or, b2 + b = b(b + 1) est forcément pair comme il s’agit du produit
de deux nombres consécutifs.
1.8) ⋆ Soit r ∈ {0, 1} une retenue et soient x et y deux chiffres en base b. Lors
de l’addition r + x + y, la retenue suivante est r′ := (r + x + y) ÷ b. Cette
nouvelle retenue n’excède pas 1 car:
r′ = (r + x + y) ÷ b
≤ (1 + (b − 1) + (b − 1)) ÷ b (car x, y ∈ {0, 1, . . . , b − 1})
= (2b − 1) ÷ b
=1 (car b entre 1 fois dans 2b − 1).
ANNEXE B. SOLUTIONS DES EXERCICES 160
que m est un entier non nul puisque y contient au moins un bit non nul.
Cela signifie que l’entier 2k−1 > 0 se divise par 5, ce qui est impossible.
1.10) ⋆⋆
(a) 1337
(b) +00++0
(c) On a 5 = +--, 8 = +0-, −5 = -++ et −8 = -0+. En général, pour
obtenir −a, il suffit d’intervertir les chiffres + et -. Montrons pourquoi
cela fonctionne. Soit a un nombre qui s’écrit de la forme xn−1 ·3n−1 +
. . . + x1 · 31 + x0 · 30 . Nous avons:
−a = −(xn−1 · 3n−1 + . . . + x1 · 31 + x0 · 30 )
= (−xn−1 ) · 3n−1 + . . . + (−x1 ) · 31 + ·(−x0 ) · 30 .
Puisque −(−1) = 1, −(0) = 0 et −(1) = −1, cela montre qu’il suffit
de remplacer - par +, 0 par 0, et + par - pour passer de a à −a.
(d) On utilise l’approche présentée dans le chapitre, c.-à-d. qu’on addi-
tionne de droite à gauche en propageant une retenue. Il faut cepen-
dant changer les règles d’addition. Premièrement, la table d’addition
de deux chiffres est:
+ - 0 +
- -+ - 0
0 - 0 +
+ 0 + +-
ANNEXE B. SOLUTIONS DES EXERCICES 161
1.11)
a) 7778
b) rwxrw-r-- ≡ 1111101002 = 7648
c) 6528 = 1101010102 ≡ rw-r-x-w-
ANNEXE B. SOLUTIONS DES EXERCICES 162
Chapitre 2
2.1) Voir sur GitHub .
2.2) Voir sur GitHub .
2.3) Voir sur GitHub .
ANNEXE B. SOLUTIONS DES EXERCICES 163
Chapitre 3
3.1) Le mot (4 octets) stocké à l’adresse 2 est: BC16 4816 5F16 1116 . Sa
valeur est donc 115F48BC16 dans le format « little-endian » et BC485F1116
dans le format « big-endian ». Cette adresse ne respecte pas les contraintes
d’alignement pour un mot puisqu’un mot contient 4 octets et que l’adresse
2 n’est pas un multiple de 4.
Le mot stocké à l’adresse 4 est: 5F16 1116 FF16 4316 . Sa valeur est
donc 43FF115F16 dans le format « little-endian » et 5F11FF4316 dans le
format « big-endian ». En binaire, ces deux valeurs correspondent respec-
tivement à
10000111111111100010001010111112 , et
10111110001000111111111010000112 .
3.2) Une architecture de 64 bits peut accéder à 264 octets = 224 · 240 octets =
16 777 216·(210 )4 octets = 16 777 216·10244 octets = 16 777 216 tébioctets.
Une architecture de 128 bits peut accéder à 2128 octets = 268 ·260 octets =
268 · (210 )6 octets = 268 · 10246 octets = 295 147 905 179 352 825 856 exbi-
octets.
3.4) Non, car elle possède une instruction qui effectue à la fois un accès à la
mémoire principale et une opération arithmétique.
ANNEXE B. SOLUTIONS DES EXERCICES 164
Chapitre 4
4.1)
43 → 00101011
−43 → 11010101
1 → 00000001
−1 → 11111111
127 → 01111111
−128 → 10000000
4.2)
4.3)
00000110 (6)
×
00000111 (7)
00000110
00000110
+
00000110
000000000101010 (42)
4.4) L’addition 0111 (7) + 0001 (1) = 1000 (−8) mène à un débordement sans
report. L’addition 1111 (−1) +1111 (−1) = 1110 (−2) mène à un report sans
débordement.
4.5)
— Mettons d’abord les deux nombres sur 7 bits: a = 1101011 et b =
1100100. Afin de calculer a − b, on peut calculer a + (−b). La valeur de
−b s’obtient par complément à deux: −b = 0011100. Nous obtenons
donc: a + (−b) = 1101011 + 0011100 = 0000111
— 4 + 2 + 1 = 7.
ANNEXE B. SOLUTIONS DES EXERCICES 165
val(compl(x))
= −val(x) − 1.
Chapitre 5
5.1) x19 = 4 et x20 = FF16 , puisque:
Chapitre 6
6.1) Rappelons que dans notre contexte, un mot correspond à 4 octets. Le mot
qui contient 999 est associé à l’indice (2, 1). Son index est donc ℓ = (2 · 3 +
1) · 4 = 28. Le mot se situe ainsi à l’adresse a + ℓ = FF00AB16 + 1C16 =
FF00C716 .
6.2)
main:
adr x19, tab // a = &tab
mov x20, N // n = N
//
// Calculer le produit //
mov x21, 0 // i = 0
mov x22, 1 // acc = 1
mov x24, 8 //
add x23, x20, 1 //
mul x23, x23, x24 // k = (n+1)*8
//
boucle: // do {
mul x25, x21, x23 // index = i*k
ldr x26, [x19, x25] // d = mem[a+index]
mul x22, x22, x26 // acc *= d
add x21, x21, 1 // i++
cmp x21, x20 // }
b.lo boucle // while (i < n)
//
// Afficher le produit //
adr x0, fmtSortie //
mov x1, x22 //
bl printf // printf("%ld\n", acc)
//
// Quitter //
mov x0, 0 //
bl exit // exit(0)
N = 5
.section ".data"
tab: .xword 10, 3, 4, 5, 6
.xword 2, 1, 2, 9, 5
.xword 1, 4, 5, 8, 7
.xword 1, 1, 1, 1, 1
.xword 0, 2, 6, 0, -2
ANNEXE B. SOLUTIONS DES EXERCICES 168
.section ".rodata"
fmtSortie: .asciz "%ld\n"
6.4) Comme 23 est premier, il n’est un multiple d’aucun autre nombre que 1 et
23. Le tableau est donc forcément unidimensionnel.
6.5) (i0 · n1 · n2 + i1 · n2 + i2 ) · k.
6.6) ⋆ Voir sur GitHub .
ANNEXE B. SOLUTIONS DES EXERCICES 169
Chapitre 7
7.1)
7.2) Il doit recevoir le tableau par son adresse ainsi que sa taille. Autrement, il
n’y a aucune façon d’identifier la fin du tableau.
Chapitre 8
8.1) max(x, y) = x ∨ y et min(x, y) = x ∧ y.
8.2) Afin de tester si un un nombre de n bits ne vaut pas zéro, il suffit d’utili-
ser une « cascade » de portes OU. Ainsi, en ajoutant une porte NON, on
obtient le circuit recherché. Autrement dit, on construit un circuit pour
l’expression booléenne
∨
n−1
¬ xi .
i=0
8.3) On peut tester si deux bits xi et yi sont égaux grâce à ¬(xi ⊕ yi ). Afin de
tester x = y, il suffit donc de comparer x0 avec y0 , x1 avec y1 , . . ., puis de
combiner ces résultats à l’aide d’une cascade de portes ET. Autrement dit,
on construit un circuit pour l’expression booléenne
∧
n−1
¬(xi ⊕ yi ).
i=0
y0 = ¬x0 ,
y1 = x0 ⊕ x1 ,
y2 = (x0 ∧ x1 ) ⊕ x2 .
x1 ∨ ¬x0
¬x1 ∧ ¬x0 vrai
x1
¬x0 ¬x1 ∨ x0
x1 ∨ ¬x0
8.8) Soient x l’entrée de n bits. On doit d’abord complémenter x, puis lui addi-
ANNEXE B. SOLUTIONS DES EXERCICES 171
tionner 1. Soit yi le ième bit en sortie et soit ri la retenue créée par l’addition
à la position i. On a:
y0 = x0 ,
r0 = ¬x0 ,
yi = ri−1 ⊕ ¬xi pour 1 ≤ i < n,
ri = ri−1 ∧ ¬xi pour 1 ≤ i < n.
x3 x2 x1 x0
y3 y2 y1 y0
ANNEXE B. SOLUTIONS DES EXERCICES 172
Chapitre 9
9.1) ¬a = 010010, a ∧ b = 101000, a ∨ b = 101111, a ⊕ b = 000111
9.2) Pour effectuer un décalage circulaire d’une chaîne de n bits de k bits vers
la gauche, on effectue un décalage circulaire de n − k bits vers la droite.
9.3) and xd, xd, 1
eor xd, xd, 1
b4 b3 b2 b1 b0
∨
0 1 1 0 0
b4 1 1 b1 b0
ANNEXE B. SOLUTIONS DES EXERCICES 173
Chapitre 10
10.1) En inspectant la table des plages de code, on voit que ces codes donnent
lieu à des codages de 2, 1, 4 et 3 octets, respectivement. Après conversion
des codes numériques de l’hexadécimal vers le binaire, on obtient:
0006AB16 → 11010101011 → 11011010 10101011
00007316 → 1110011 → 01110011
01234516 → 10010001101000101 → 11110000 10010010 10001101 10000101
00A0B116 → 1010000010110001 → 11101010 10000010 10110001
codage:
SAVE
and x19, x0, 0xFF00
cmp x19, 0
b.eq un_octet
deux_octets:
and x19, x0, 0x3000
and x20, x0, 0x0F00
orr x19, x19, x20
lsr x19, x19, 3
and x20, x0, 0x1F
orr x19, x19, x20
b fin
un_octet:
and x19, x0, 0x7F
fin:
mov x0, x19
RESTORE
ret
Chapitre 11
11.1) Voir sur GitHub .
11.2) AEE916 (décrémenté de 4 · 8 = 32), AEF916 (incrémenté de 2 · 8 = 16).
11.3) On peut empiler xzr , ce qui gaspille 8 octets mis à zéro. Lorsque l’on dépile
les quatre double mots, on dépile ces 8 octets et les ignore.
11.4)
exp: // exp(n)
SAVE // {
mov x19, x0 //
mov x20, 1 // r = 1
//
cbz x19, fin // if (n != 0)
// {
lsr x0, x19, 1 //
bl exp // k = exp(n / 2)
mul x20, x0, x0 // r = k * k
//
tbz x19, 0, fin // if (n est pair)
lsl x20, x20, 1 // r *= 2
fin: // }
mov x0, x20 //
RESTORE //
ret // return r
// }
11.5)
11.8)
Chapitre 12
12.1) Nombres normalisés:
1,23456 × 104 − 9,09 × 10−3 1,01 × 2−7 − 1,110111 × 28
Valeurs décimales:
12.2) Les deux variables sont représentées par des nombres en virgule flottante
double précision de la norme IEEE 754. Nous avons:
1, 0| ·{z
· · 0} 1 × 240
52 fois
= (1,11 · 1,01) × 21 .
« 0 10000100 01010100000000000000000 ».
12.8) ⋆ Remarquons d’abord que les nombres x et y sont codés par des chaînes
de 32 bits cx et cy . Les entiers vx et vy correspondent simplement à l’in-
terprétation de cx et cy en tant qu’entiers non signés. Écrivons ≺ afin
de dénoter l’ordre lexicographique. Rappelons que vx < vy ssi cx ≺ cy .
Puisque x et y sont positifs et normalisés, ils sont de la forme x = 1,u × 2e
et y = 1,v × 2f . Notons que
x < y ⇐⇒ (e < f ) ∨ (e = f ∧ u < v).
Autrement dit, on peut comparer x et y, en comparant d’abord leurs ex-
posants, puis leurs mantisses en cas d’égalité. De plus, tester e < f est
équivalent à tester e + 127 < f + 127. Ainsi, puisque les codages cx et cy
sont de la forme « 0 (exposant+127) mantisse », tester cx ≺ cy correspond
précisément à comparer les exposants, puis les mantisses en cas d’égalité.
On a donc x < y ssi cx ≺ cy ssi vx < vy .
ANNEXE B. SOLUTIONS DES EXERCICES 179
12.10) ⋆⋆ Soit x ∈ R tel que xmin ≤ |x| ≤ xmax . Le nombre x s’écrit de la forme
d0 ,d1 d2 · · · dn−1 dn · · · × β e où 0 < d0 < β, 0 ≤ (0,d1 d2 · · · dn−1 · · · )β < 1
et emin ≤ e ≤ emax . Observons que la mantisse de x est obtenue à partir
de la mantisse de x en retirant au plus
(0, |0 ·{z
· · 0} (β − 1)(β − 1) · · · )β .
n − 1 fois
|u − v| ≤ β · β −n . (B.2)
Par conséquent:
12.11) ⋆
(a) Comme nous l’avons vu au chapitre 1, il est impossible de représenter
0,1 de façon finie en binaire. Ainsi, 0.1 est un nombre en virgule flot-
tante simple précision qui approxime 0,1. Plus précisément, il s’agit
de
0,100000001490116119384765625.
Après la dixième itération, x excède donc 1 et ainsi le critère d’arrêt
n’est pas atteint.
(b) Après avoir excédé 10, x continue de croître. À un certain point, la
valeur de x est si grande que l’incrément est négligeable. À partir
de ce moment, la valeur de x ne change plus jamais et la boucle se
répète à l’infini.
ANNEXE B. SOLUTIONS DES EXERCICES 180
b := 0,100000001490116119384765625.
Remarquons que
a + b = 2097152 + 0,100000001490116119384765625
= 1,0 × 221 + 1,0011001100110011001101 × 2−4
21 fois
z }| {
= 1 0 · · · 0 ,0 × 20 + 0,00010011001100110011001101 × 20
21 fois
z }| {
= (1 0 · · · 0 ,0 + 0,00010011001100110011001101) × 20
21 fois
z }| {
= 1 0 · · · 0 ,00010011001100110011001101) × 20 .
Chapitre 13
13.1) On établit une connexion avec le port de manette; on ignore les boutons
A et B ; puis on combine l’état des deux boutons suivants avec un ET:
appuye: .rs 1
lecture: ; lecture()
; Demander lecture boutons ; {
lda #1 ; envoyer 1
sta $4016 ; au port de manette 1
lda #0 ; envoyer 0
sta $4016 ; au port de manette 1
;
; Déterminer si SELECT ;
; et START sont appuyés ;
lda $4016 ; lire et ignorer A
lda $4016 ; lire et ignorer B
;
lda $4016 ;
and #%00000001 ; lire état x de SELECT
sta appuye ; appuye = x
;
lda $4016 ;
and #%00000001 ; lire état y de START
and appuye ;
sta appuye ; appuye &= y
;
rts ; }
Remarquons que la lecture du bouton start est inutile lorsque select n’est
pas appuyé. On pourrait donc l’éviter à l’aide d’un branchement.
13.2) On établit une connexion avec le port de manette; on ignore les six pre-
miers boutons; puis on décrémente posX: si le bouton suivant est enfoncé:
avancer: ; avancer()
; Demander lecture boutons ; {
lda #1 ; envoyer 1
sta $4016 ; au port de manette 1
lda #0 ; envoyer 0
sta $4016 ; au port de manette 1
;
; Ignorer six 1er boutons ;
ldx #0 ; x = 0
ANNEXE B. SOLUTIONS DES EXERCICES 182
;
avancer_lire: ; do {
lda $4016 ; lire/ignorer bouton
inx ; x++
cpx #6 ; }
bne avancer_lire ; while (x != 6)
;
; Déplacer selon flèche ;
lda $4016 ;
and #%00000001 ; a = bit d'état de <--
cmp #0 ;
beq avancer_fin ; if (a != 0)
dec posX ; posX--
avancer_fin: ;
rts ; }
renverser: ; renverser()
ldx #2 ; {
lda dir ;
cmp #0 ;
bne renverser_un ; if (dir == 0)
renverser_zero: ; {
lda tuile, x ;
and #%10111111 ; a = tuile[2] & 0xBF
jmp renverser_fin ; }
renverser_un: ; else
lda tuile, x ; {
ora #%01000000 ; a = tuile[2] | 0x40
renverser_fin: ; }
sta tuile, x ; tuile[2] = a
rts ; }
renverser: ; renverser()
; Créer masque (dir <<= 6) ; {
ldx 0 ; x = 0
renverser_decal_gauche: ; do {
asl dir ; dir <<= 1
inx ; x++
cpx #6 ; }
bne renverser_decal_gauche; while (x != 6)
;
; Renverser tuile selon dir ;
ldx #2 ;
lda tuile, x ; a = tuile[2]
and #%10111111 ; a &= 0xBF
ora dir ; a |= dir
sta tuile, x ; tuile[2] = a
;
; Restaurer dir (dir >>= 6) ;
ldx 0 ; x = 0
renverser_decal_droite: ; do {
lsr dir ; dir >>= 1
inx ; x++
cpx #6 ; }
bne renverser_decal_droite ; while (x != 6)
;
rts ; }
ANNEXE B. SOLUTIONS DES EXERCICES 184
Chapitre 14
14.1)
attendre_start: ; attendre_start()
lda #1 ; {
sta $4016 ; do {
lda #0 ;
sta $4016 ; demander lecture boutons
;
lda $4016 ; ignorer A
lda $4016 ; ignorer B
lda $4016 ; ignorer SELECT
lda $4016 ;
and #%00000001 ; obtenir état a de START
cmp #0 ; }
beq attendre_start ; while (a == 0)
;
jsr faire_pause ; faire_pause()
rts ; }
14.2)
; Lire mem_video[0x2000]
lda #$20
sta $2006
lda #$00
sta $2006
lda $2007
tax
Notons qu’on pourrait aussi utiliser la pile plutôt que le registre x afin de
mettre le contenu de l’accumulateur a temporairement de côté.
14.3) Afin d’indiquer au processeur de restaurer le registre d’état (donc les codes
de condition) avant de reprendre son exécution.
14.4)
ANNEXE B. SOLUTIONS DES EXERCICES 185
mov x8, 93
mov x0, 0
svc 0
C
Matériel additionnel
Proposition 1. Soit b une base et soit n ∈ N≥1 . Le plus grand nombre pouvant
être représenté en base b avec n chiffres est bn − 1.
mb,n+1 = (b − 1) · bn + mb,n
= (b − 1) · bn + (bn − 1) (par hypothèse d’induction)
=b n+1
−b +b −1
n n
=b n+1
− 1.
Proposition 3. Soit n ∈ N≥2 et soit a le plus grand entier non signé de n bits. La
représentation binaire de a · a requiert 2n bits.
186
ANNEXE C. MATÉRIEL ADDITIONNEL 187
Démonstration. Nous devons démontrer que a est plus grand que le plus grand
entier non signé pouvant être représenté sur 2n − 1 bits. Nous avons:
≥2 2n
− 22n−1 (car 2n − 1 ≥ n + 1 pour tout n ≥ 2)
=2·2 2n−1
−2 2n−1
2n−1
=2
> 22n−1 − 1.
Proposition 4. |err(x)| ≤ ε pour tout x ∈ R \ {0} tel que xmin ≤ |x| ≤ xmax .
Démonstration. Soit x ∈ R tel que xmin ≤ |x| ≤ xmax . Le nombre x s’écrit de la
forme d0 ,d1 d2 · · · dn−1 dn · · · × β e où 0 < d0 < β, 0 ≤ (0,d1 d2 · · · dn−1 · · · )β < 1
et emin ≤ e ≤ emax . Observons que la mantisse de x est obtenue en arrondissant
la mantisse de x, donc en lui ajoutant ou en retirant au plus
(0, 0| ·{z
· · 0} (β/2))β .
n − 1 fois
|u − v| ≤ (β/2) · β −n . (C.1)
ANNEXE C. MATÉRIEL ADDITIONNEL 188
Par conséquent:
= (|u − v| · β e )/(u · β e )
≤ β e−n+1 /(2 · u · β e ) (par (C.1))
= β/(2 · u · β n )
≤ β/(2 · β n ). (car u ≥ 1 par d0 > 0)
= ε. (par déf. de ε).
D
Architecture ARMv8: sommaire
189
Registres. [22 février 2024]
Arithmétique (entiers).
— Les codes de condition sont modifiés par cmp, adds, adcs, subs, sbcs et negs
— À cette différence près, adds, adcs, subs, sbcs et negs se comportent respectivement comme add, adc, sub, sbc et neg
— Instructions, où i est une valeur immédiate de 12 bits et j est une valeur immédiate de 6 bits:
Conditions de branchement.
— Codes de condition: N (négatif), Z (zéro), C (report), V (débordement)
— C indique aussi l’absence d’emprunt lors d’une soustraction
— Conditions de branchement:
Branchement.
— Instructions de branchement, où j est une valeur immédiate de 6 bits:
Autres instructions.
Code d’op. Syntaxe Effet Exemple
csel csel rd, rn, rm, cond si cond: rd ← rn , sinon: rd ← rm csel x19, x20, x21, eq
Données statiques.
Par exemple, x/10ug &tab affiche les 10 premiers éléments de 64 bits non signés d’un
tableau tab
E
Architecture du NES: sommaire
196
Registres. [22 février 2024]
Valeurs immédiates.
— #: valeur numérique, sans #: adresse
— $: valeur hexadécimale
expression valeur
— %: valeur binaire
#5 510
— Exemples: #$FF FF16
#%00010011 000100112
$FF adresse FF16
Modes d’adressage.
Nom. Syntaxe Adresse Exemple
absolu i i lda $D010
i, x i+x lda $D010, x
indexé par x
etiq, x etiq + x lda tab, x
i, y i+y lda $D010, y
indexé par y
etiq, y etiq + y lda tab, y
Accès mémoire.
— Instructions, où mem1 [a] dénote l’octet situé à l’adresse a de la mémoire principale:
Logique.
Code d’op. Syntaxe Effet Exemple
asl asl adr décalage logique de mem1 [adr] d’un asl var
bit à gauche (directement en mémoire)
lsr lsr adr décalage logique de mem1 [adr] d’un lsr var
bit à droite (directement en mémoire)
and #i a←a∧i and #%00100011
and
and adr a ← a ∧ mem1 [adr] and var
ora #i a←a∨i ora #%00100011
ora
ora adr a ← a ∨ mem1 [adr] ora var
eor #i a←a⊕i eor #%00100011
eor
eor adr a ← a ⊕ mem1 [adr] eor var
Comparaisons et branchements.
Code d’op. Syntaxe Effet Exemple
cmp #i compare a et i cmp #0
cmp
cmp adr compare a et mem1 [adr] cmp var
cpx #i compare x et i cpx #0
cpx
cpx adr compare x et mem1 [adr] cpx var
cpy #i compare y et i cpy #0
cpy
cpy adr compare y et mem1 [adr] cpy var
beq beq etiq branche à etiq: si = beq boucle
bne bne etiq branche à etiq: si ̸= bne boucle
jmp jmp etiq branche à etiq: jmp boucle
jsr jsr etiq branche au sous-programme etiq: jsr func
et empile l’adresse de retour
rts rts branche à l’adresse de retour d’un rts
sous-programme
rti rti branche à l’adresse de retour d’une rti
interruption
Bibliographie
[ARM13] ARM Limited. Procedure Call Standard for the ARM 64-bit Architecture
(AArch64), 2013. Version 1.0.
[ARM15] ARM Limited. ARM® Cortex®-A Series: Programmer’s Guide for
ARMv8-A, 2015. Version 1.0.
[ARM18] ARM Limited. ARM® Architecture Reference Manual: ARMv8, for
ARMv8-A architecture profile, 2018. Version D.a.
[PH17] David Patterson and John Hennessy. Computer Organization and De-
sign RISC-V Edition. Elsevier, 2017.
[SD11] Richard St-Denis. L’architecture du processeur SPARC et sa program-
mation en langage d’assemblage. Éditions GGC, 2011.
[Yer03] F. Yergeau. UTF-8, a transformation format of ISO 10646. RFC 3629,
2003.
199
Index
200
INDEX 201