You are on page 1of 251

Concevoir son microprocesseur

structure des systèmes logiques

Jean-Christophe Buisson

Collection Technosup

Ellipses
Avant-propos

Ce livre s’adresse aux étudiants en informatique de licence et maîtrise, aux élèves


ingénieurs, ainsi qu’aux professionnels de l’informatique - programmeurs ou autres -
soucieux de comprendre le fonctionnement des systèmes logiques présents dans les
dispositifs électroniques qui nous entourent, et en particulier les microprocesseurs et les
microcontrôleurs. Il est basé sur le cours, les travaux dirigés et les travaux pratiques
d’architecture des ordinateurs dispensés en 1ère année du département informatique
de l’Ecole Nationale Supérieure d’Electronique, d’Informatique, d’Hydraulique et de
Télécommunications (ENSEEIHT) de Toulouse, et il est le fruit de plus de 15 ans
d’expérience dans ce domaine.
Le livre est organisé en deux grandes parties. La première (chapitres 1 à 3) présente
d’abord toutes les notions de base préalables à la compréhension : le système de
numération binaire et son utilisation dans le codage des nombres entiers naturels et relatifs,
des caractères et des nombres réels ; ses propriétés dans les opérations d’addition, de
comparaison, etc. On décrit ensuite les atomes de base des systèmes logiques que sont les
portes et les bascules, et on décrit les méthodes d’analyse et de synthèse qui permettent
d’assembler de tels éléments pour former des modules de complexité croissante. Le
lecteur est ainsi amené à créer des circuits combinatoires et séquentiels, en employant
des méthodes réutilisables dans des contextes variés. On construit des encodeurs et des
décodeurs, des compteurs, des séquenceurs de différents types pour automatismes, etc. On
examine également en détail des modules qui seront utilisés dans des microprocesseurs :
comparateurs, unités arithmétiques et logiques, multiplexeurs, mémoires, etc. Dans toute
cette partie, l’accent est mis sur la modularité et la réutilisabilité, notion aussi importante ici
que dans les disciplines logicielles.
La deuxième partie (chapitres 4 à 6) décrit le fonctionnement des microprocesseurs
et des microcontrôleurs au travers d’un processeur 32 bits spécifique à ce livre appelé
CRAPS, dont tous les organes sont des modules qui auront été étudiés dans la première
partie. Son langage machine de type RISC est un sous-ensemble de celui du SPARC, et il
est suffisamment riche pour pouvoir être utilisé dans des applications concrètes, ou pour
qu’on y incorpore un système d’exploitation comme Linux. Le but de cette partie est de
dévoiler le mystère de l’exécution d’un programme, qu’on pourra observer jusqu’à l’échelle
du bit élémentaire. De nombreux programmes écrits en assembleur CRAPS/SPARC sont
fournis, qui permettent de mettre en oeuvre des aspects importants de la vie d’un processeur :
déclenchement et prise en compte d’une interruption, dialogue avec des périphériques
d’entrées-sorties, appels de sous-programmes récursifs, etc.
Au delà de la simple compréhension, cet ouvrage a pour objectif de permettre la
conception de systèmes logiques digitaux, et il en fournit tous les moyens. De nombreuses
méthodes de conception sont présentées, parfois algébriques ou tabulaires, parfois
fonctionnelles ou intuitives ; pour chacune, plusieurs exemples détaillés sont fournis, qui
appliquent les techniques présentées à des problèmes concrets, et qui donnent au lecteur la
possibilité de se construire un véritable savoir-faire dans ce domaine. Un outil logiciel de
simulation appelé SHDL est présenté, libre et gratuit, à la fois basé sur un langage structurel
de description matérielle, et qui permet de voir fonctionner les dispositifs conçus. Un second
outil logiciel libre appelé CRAPSEMU permet de programmer le processeur décrit avec de
nombreux exemples fournis, afin que le lecteur puisse s’imprégner correctement des modes
de fonctionnement du processeur CRAPS.
Enfin, des éléments concrets sont fournis au lecteur pour lui permettre d’implémenter
tous les circuits décrits, jusque et y compris le processeur CRAPS, dans des circuits
logiques programmables de type CPLD ou FPGA, maintenant utilisables à l’aide de cartes
d’expérimentation à moindre coût.

iv
Table des Matières

Avant-propos . . . . . . . . . . . . . . . . . . . . . . . iii

Chapitre I. Principes généraux . . . . . . . . . . . . . . . . 1


1. Organisation générale d’un ordinateur . . . . . . . . . . . 1
2. Bits et signaux électriques . . . . . . . . . . . . . . . 3
3. Technologies de fabrication des circuits intégrés . . . . . . . . 5
4. Mots binaires . . . . . . . . . . . . . . . . . . . 6
5. Codage des principaux types de données . . . . . . . . . . 10
6. Exercices corrigés . . . . . . . . . . . . . . . . . . 15

Chapitre II. Éléments de logique combinatoire . . . . . . . . . . . 18


1. Circuits combinatoires . . . . . . . . . . . . . . . . 18
2. Tables de vérité . . . . . . . . . . . . . . . . . . . 20
3. Algèbre et opérateurs de base . . . . . . . . . . . . . . 20
4. Autres portes logiques . . . . . . . . . . . . . . . . . 23
5. SHDL, un langage de description matérielle pédagogique . . . . . 26
6. Circuits logiques reconfigurables ; PLD et FPGA . . . . . . . . 28
7. Méthodes de simplification des fonctions combinatoires . . . . . 30
8. Circuits combinatoires réutilisables . . . . . . . . . . . . 37
9. Exercices corrigés . . . . . . . . . . . . . . . . . . 56

Chapitre III. Éléments de logique séquentielle . . . . . . . . . . . 61


1. Définition . . . . . . . . . . . . . . . . . . . . . 61
2. Latch RS . . . . . . . . . . . . . . . . . . . . . 62
3. Fronts et niveaux ; signaux d’horloge . . . . . . . . . . . . 63
4. Circuits séquentiels synchrones et asynchrones : définitions . . . . 63
5. Graphes d’états . . . . . . . . . . . . . . . . . . . 64
6. Tables de transitions . . . . . . . . . . . . . . . . . 66
7. Bascules synchrones . . . . . . . . . . . . . . . . . 69
8. Synthèse d’un circuit séquentiel synchrone . . . . . . . . . . 77
9. Chronologie d’un circuit séquentiel synchrone . . . . . . . . 84
10. Circuits séquentiels réutilisables . . . . . . . . . . . . . 85
11. Exercices corrigés . . . . . . . . . . . . . . . . . 92
Chapitre IV. Éléments fonctionnels d’un processeur . . . . . . . . . 98
1. Sorties haute-impédance . . . . . . . . . . . . . . . . 98
2. Les bus parallèles . . . . . . . . . . . . . . . . . . 101
3. Décodeurs . . . . . . . . . . . . . . . . . . . . 103
4. Multiplexeurs . . . . . . . . . . . . . . . . . . . 104
5. Encodeurs de priorité . . . . . . . . . . . . . . . . . 105
6. Circuit d’extension de signe . . . . . . . . . . . . . . . 107
7. Décaleur à barillet . . . . . . . . . . . . . . . . . . 108
8. L’unité arithmétique et logique . . . . . . . . . . . . . . 111
9. Circuit timer/PWM . . . . . . . . . . . . . . . . . 114
10. RAMs et ROMs . . . . . . . . . . . . . . . . . . 115
11. Exercices corrigés . . . . . . . . . . . . . . . . . 122

Chapitre V. Programmation du processeur 32 bits CRAPS/SPARC . . . 129


1. Le langage machine, à la frontière entre logiciel et matériel . . . . 129
2. Ressources du programmeur CRAPS/SPARC . . . . . . . . . 131
3. Directives d’un programme assembleur . . . . . . . . . . . 145
4. Exemple de programme : crible d’Ératosthène . . . . . . . . . 148
5. Appels de sous-programmes terminaux . . . . . . . . . . . 152
6. Utilisation de la pile . . . . . . . . . . . . . . . . . 153
7. Techniques de programmation diverses . . . . . . . . . . . 158
8. Langages à structure de blocs et ’stack-frames’ . . . . . . . . 159
9. Programmation des entrées/sorties . . . . . . . . . . . . 163
10. Programmation des exceptions . . . . . . . . . . . . . 170
11. Exercices corrigés . . . . . . . . . . . . . . . . . 178

Chapitre VI. Construction du processeur 32 bits CRAPS . . . . . . . 186


1. Les registres . . . . . . . . . . . . . . . . . . . . 186
2. L’unité arithmétique et logique . . . . . . . . . . . . . . 190
3. Les bus du processeur CRAPS . . . . . . . . . . . . . . 195
4. Structure du sous-système mémoire . . . . . . . . . . . . 198
5. Structure du circuit d’entrées/sorties . . . . . . . . . . . . 201
6. Structure du circuit timer/PWM . . . . . . . . . . . . . 203
7. Structure du sous-système d’exceptions . . . . . . . . . . . 205
8. Séquencement des instructions . . . . . . . . . . . . . . 209
9. Exercices corrigés . . . . . . . . . . . . . . . . . . 216

Références . . . . . . . . . . . . . . . . . . . . . . . . 219

vi
Annexe A. Tables diverses . . . . . . . . . . . . . . . . . . 220

Annexe B. CRAPS : guide du programmeur . . . . . . . . . . . . 221


B.1. Instructions synthétiques . . . . . . . . . . . . . . . 221
B.2. Jeu d’instructions du processeur CRAPS . . . . . . . . . . 223
B.3. Tables des branchements conditionnels . . . . . . . . . . 228
B.4. Format binaire des instructions de CRAPS . . . . . . . . . 228
B.5. Directives de l’assembleur CRAPS . . . . . . . . . . . 230
B.6. Cartographie mémoire de CRAPS . . . . . . . . . . . . 231
B.7. Programmation des entrées/sorties . . . . . . . . . . . . 231
B.8. Programmation du timer . . . . . . . . . . . . . . . 231
B.9. Liste des exceptions . . . . . . . . . . . . . . . . . 232

Glossaire . . . . . . . . . . . . . . . . . . . . . . . . 233

Index . . . . . . . . . . . . . . . . . . . . . . . . . . 242

vii
Chapitre I

Principes généraux

1. Organisation générale d’un ordinateur

1.1. Le modèle de Von Neumann


La plupart des ordinateurs ont l’organisation générale de la figure I.1.

mémoire centrale

adr#1 instruction #1 processeur


=
adr#2 instruction #2 instruction unité de contrôle
adr#3 instruction #3 et de calcul
registres
adr#4 donnée #1 données r1
adr#5 donnée #2 r2
r3

données
données

contrôleurs
périphériques
de périphériques

Figure I.1. Organisation générale d’un ordinateur; les flèches sont des chemins de
données.

Cette organisation est dite de ’Von-Neumann’, car elle ressemble beaucoup à la


machine conçue par John Von Neumann sur la machine IAS peu après la deuxième guerre
mondiale. Elle suppose et implique un certain nombre de propriétés caractéristiques des
ordinateurs actuels :

La mémoire est un composant central ; c’est un tableau de mots de taille fixe


On voit sur le schéma que la mémoire centrale est au départ et à l’arrivée de
l’exécution d’un programme. C’est elle qui contient le programme à exécuter et les données

1
2 CHAPITRE I. PRINCIPES GÉNÉRAUX

initiales ; c’est dans elle que seront stockés les résultats intermédiaires ou finaux.
C’est un tableau de mots binaires de taille fixe. Chaque mot est repéré par un nombre
appelé adresse, et les programmes feront référence aux données et aux instructions en
mémoire par leurs adresses.

Le programme exécuté est dans la mémoire centrale


Le programme qu’exécute un ordinateur n’est pas sur le disque dur, comme le
pensent beaucoup de gens. Une copie du programme est effectivement souvent stockée
sur un CDROM ou un disque dur, mais ce n’est pas avant d’avoir été copié en mémoire
centrale, comme le serait n’importe quelle autre ensemble de données, que son exécution
peut commencer.
Un programme qu’exécute un ordinateur est composé d’instructions sous forme de
mots mémoire. Le langage utilisé pour coder les instructions en mots binaires est appelé
langage machine, c’est le seul que comprenne le processeur. La taille des instructions
varie beaucoup selon le type des processeurs. Parfois toutes les instructions ont la même
taille, égale à un ou plusieurs mots mémoire ; parfois elles ont des tailles variables selon la
complexité de l’instruction.

Le processeur est le coordonnateur de l’exécution


Le processeur contrôle l’exécution des programmes, en organisant le chargement
des instructions et le détail de leur exécution ; c’est aussi lui qui effectue tous les calculs,
notamment sur les nombres entiers. Ces deux fonctions sont associées dans la même unité
car le contrôle de l’exécution nécessite souvent des calculs entiers, lorsque par exemple il
faut faire une somme pour déterminer l’adresse de l’instruction suivante.
Le processeur a besoin de garder la trace d’un certain nombre d’informations pendant
l’exécution des instructions : l’adresse de l’instruction en cours par exemple, ou le résultat
de la dernière opération arithmétique. Il utilise pour cela des registres, qui sont des mots
mémoire fixes, à accès très rapide.

Le processeur ne dialogue pas directement avec les périphériques


On voit sur la figure que le processeur dialogue avec des contrôleurs de périphériques
et non avec les périphériques eux-même. Par exemple un processeur n’a aucune conscience
de l’existence d’un disque dur ; il dialogue avec un contrôleur de disque, à l’aide
d’instructions génériques d’entrées/sorties.

1.2. Organisation en bus


Les flèches entre les composants de la figure I.1 étaient à prendre dans un sens
fonctionnel; si elles devaient représenter effectivement les chemins par lesquels transitent
les données, le fonctionnement serait très inefficace. En pratique, les données circulent entre
les différents sous-systèmes d’un ordinateur sur des bus, sortes d’autoroutes, et des circuits
spécialisés jouent le rôle d’échangeurs aux carrefours de ces bus, de façon à permettre des
transferts simultanés dans certaines circonstances. La figure I.2 montre une organisation
typique d’un système de type compatible PC.
Un circuit appelé north-bridge règle les échanges à très grande vitesse, notamment
entre le processeur et la mémoire centrale. Il exploite de façon synchrone le bus FSB (front
side bus), encore appelé bus système, dont la vitesse est très corrélée à la puissance générale
1. Organisation générale d’un ordinateur 3

cache 64/256 bits


niveau 2 processeur

FSB 64 bits
66/100/133/200 MHz
64 bits
66/100/133/200 MHz north
mémoire RAM bridge
bus mémoire

533/800 Mo/s

IDE LAN
south
bridge

USB PCI−E

PCI

Figure I.2. Architecture générale des bus d’un ordinateur compatible PC.

de l’ensemble. Un second circuit appelé south-bridge effectue quant à lui des échanges
moins rapides avec des périphériques SATA, réseau, etc.
La figure I.3 montre une photo de carte mère typique, avec l’emplacement physique des
différents bus. La mémoire et le north bridge sont placés très près du processeur, car ils sont
sur les bus les plus rapides. Le north-bridge est équipé d’un radiateur à cause de l’intensité
du travail de transfert qu’il réalise.

2. Bits et signaux électriques


L’information est stockée et véhiculée dans un ordinateur, logiquement sous forme de
chiffres binaires ou bits (binary digit), et physiquement sous forme de signaux électriques.
La logique binaire est utilisée parce qu’il est plus facile de construire des systèmes
électriques possédant deux états stables, et parce qu’on a maintenant beaucoup de résultats
mathématiques en algèbre de Boole, mais des implémentations sur des systèmes possédant
plus de deux états stables sont envisageables.
Ces deux états sont notés ’0’ et ’1’ ; ils correspondent le plus souvent aux deux tensions
électriques 0v et +Vcc respectivement, Vcc valant exactement +5v pour des circuits utilisant
la technologie TTL, et une valeur comprise entre 2v et 15v environ en technologie CMOS
(des valeurs de Vcc=3.3v, 2.5v et 1.8v étant utilisée de plus en plus souvent). Pour tenir
compte du fait que ces signaux électriques vont être déformés lors des transmissions,
des plages de valeurs plus larges ont été affectées à ces états. Par exemple, en technologie
TTL, elles sont représentées figure I.4. Il faut noter que ces assignations sont légèrement
différentes selon qu’une tension est considérée en entrée ou en sortie d’un circuit.
Les circuits logiques qui sont utilisés pour combiner et transmettre les signaux
électriques logiques ont des caractéristiques non linéaires qui ont entre autres pour fonction
de remettre en forme un signal déformé par le bruit. On voit figure I.5 une porte ’non’ qui
4 CHAPITRE I. PRINCIPES GÉNÉRAUX

Figure I.3. Carte mère typique. Les bus rouges (FSB et mémoire), très rapides, sont
gérés par le north bridge ; les bus verts (PCI, SATA, etc.), plus lents, sont gérés par le
south bridge.

+ 5v + 5v

’1’ logique ’1’ logique

2.4v 2.4v

interdit
interdit

0.4v 0.8v
’0’ logique ’0’ logique
0v 0v

(a) (b)

Figure I.4. Assignation des tensions électriques aux valeurs logiques (a) en entrée de
circuit, (b) en sortie de circuit (technologie TTL).

inverse le bit d’entrée, tout en produisant un signal de sortie de meilleure qualité que le signal
d’entrée.
Les signaux pourront donc traverser un nombre quelconque d’étages de circuits
logiques sans que les déformations initiales ne soient amplifiées.
On notera sur la figure I.5 la présence d’un temps de propagation tpHL (HL indiquant :
de haut vers bas) qui correspond à la durée que met le circuit pour calculer sa sortie après que
ses entrées se soient modifiées. Ces temps de propagation se cumulent lors de la traversée
de plusieurs étages, et c’est finalement eux qui limiteront la vitesse de fonctionnement
maximale d’un circuit.
2. Bits et signaux électriques 5

E
5v
2.4v

0.4v

tpHL

Figure I.5. Remise en forme d’un signal après traversée d’une porte ’non’.

3. Technologies de fabrication des circuits intégrés


Les premiers ordinateurs ont été construits durant la deuxième guerre mondiale
avec des tubes cathodiques (des ’lampes’) comme dispositif de commutation. En 1947,
les laboratoires Bell inventent le premier transistor, qui va très vite supplanter la lampe
et apporter un facteur de réduction de taille important. Mais la véritable révolution
n’interviendra qu’avec l’invention des circuits intégrés VLSI dans les années 1970, qui
amèneront à la réalisation des premiers microprocesseurs.
Un grand nombre de technologies de fabrication de circuits intégrés existent, qui
se distinguent par des qualités différentes en termes de vitesse de propagation, densité
d’intégration, consommation et dissipation thermique. On va ébaucher rapidement dans
cette section les trois familles les plus connues, en terminant par celle qui est actuellement
la plus répandue et la plus significative, la famille CMOS.

Technologie TTL
La technologie TTL était la technologie standard utilisée à partir des années 1960
pour la réalisation de tous les types d’ordinateurs. Elle s’alimente typiquement en +5V, a
une consommation modérée, est robuste et a des règles d’interface simples. Une très grande
famille de circuits spécialisés, la famille 7400, est disponible sous forme de circuits en
boîtier DIL, et fournit des modules tout prêts : portes, bascules, compteurs, etc. Les capacités
d’intégration de circuits TTL sont faibles, et on ne peut guère mettre plus de quelques
milliers de portes TTL sur une puce de circuit intégré. On l’utilise encore aujourd’hui pour
des petites réalisations, notamment dans l’interfaçage avec d’autres circuits.

Technologie ECL
La technologie ECL est caractérisée par sa très grande rapidité, mais aussi par une
consommation de courant élevée. Elle est de plus assez difficile à utiliser, notamment en
raison de l’emploi de tensions d’alimentation négatives. Elle est utilisée dans des petites
réalisations où la vitesse est critique, mais certains experts prévoient un développement de
6 CHAPITRE I. PRINCIPES GÉNÉRAUX

la technologie ECL avec l’utilisation de nouveaux types de semi-conducteurs.

Technologie CMOS
C’est la technologie reine actuelle. La technologie CMOS a d’abord été développée
et vendue comme une alternative au TTL, plus lente mais avec une consommation réduite.
Comme par ailleurs elle peut utiliser des tensions d’alimentation faibles (jusqu’à 1V), elle a
immédiatement été très utilisée par les fabricants de montres digitales, pour qui la vitesse de
traitement importait peu par rapport à la consommation. Mais on a progressivement réalisé
que sa grande qualité était son taux d’intégration très élevé ; de plus, des tailles de transistors
de plus en plus petites ont amenées avec elles des temps de commutation de plus en plus
faibles. C’est la technologie CMOS qui est utilisée actuellement pour la fabrication de tous
les processeurs et microcontrôleurs du marché, ainsi que pour toutes les mémoires statiques
et les mémoires flash.
Par ailleurs, un circuit CMOS ne consomme significativement du courant que lors des
commutations des transistors. Cette propriété a été à l’origine de circuits qui consomment
moins de courant que leur équivalent TTL, qui eux en consomment au repos et lors des
commutations. Néanmoins, au fur et à mesure qu’on diminue leur tension d’alimentation,
les courants de fuite des transistors CMOS deviennent de plus en plus proches des courants
de commutation, et par conséquent la consommation de ces circuits au repos n’est plus aussi
négligeable qu’auparavant.

4. Mots binaires
Les signaux binaires sont souvent regroupés pour former des mots. La largeur d’un
mot binaire est le nombre des signaux qui sont regroupés. On appelle par exemple octet un
mot de 8 bits (un mot de largeur 8). Un mot de n bits permet de coder 2n valeurs différentes ;
256 valeurs par exemple pour un octet. On peut utiliser un octet pour coder un caractère
alphanumérique à la norme ISO-8859 ; un mot de 32 bits pour représenter un nombre entier
relatif ; un mot de 16 à 96 bits pour représenter des nombres réels en notation scientifique
flottante selon la norme IEEE 754. Des structures de données plus complexes (tableaux,
listes, ensembles, dictionnaires, etc.) nécessitent une agrégation de ces types simples.

Codage en binaire pur


On écrira les nombres binaires avec le chiffre 2 en indice. Par exemple : 011011012.
Sur n bits, l’intervalle des valeurs représentées est [0, 2n − 1]. Comme en décimal, le total est
une somme pondérée, plus précisément le produit de chaque chiffre par la puissance de 2 de
même rang :
n−1
s= ∑ 2i bi
i=0

où bi est le bit de rang i du mot.


Par exemple, on a représenté sur la figure suivante les poids respectifs des bits du mot
de 8 bits 100110102 = 15410 :
4. Mots binaires 7

76543210
10011010
S = 128+16+8+2=154

2
128
8
16

Poids forts, poids faibles


Par référence à cette notion de pondération des chiffres, on emploie couramment des
expressions telles que ’les n bits de poids les plus forts’, ou ’les n bits de poids les plus faibles’
pour désigner les n bits les plus à gauche (respectivement les plus à droite) dans un mot
binaire. Dans les documentations en anglais, on trouvera fréquemment des libellés tels que
MSB (most significant bits) ou LSB (least significant bits) avec les même sens.
1 Kilo n’est pas 1000 (en informatique)
Lorsque vous achetez un kilowatt-heure d’électricité, il est bien entendu avec votre
fournisseur qu’il s’agit de 1000 watts-heure. Ce n’est pas la même chose en informatique !
Pour continuer à énumérer en utilisant la base 2, les informaticiens ont décidé d’utiliser
l’unité 1K = 210 = 1024, donc un peu plus de 1000. Le suffixe ’méga’ est : 1M = 1K ×1K =
220 ; un ’giga’ est : 1G = 1K×1M = 230; un ’Tera’ est : 1T = 1K×1G = 240. Un grand nombre
de valeurs usuelles s’expriment alors directement en multiples de 1K, 1M, 1G ou 1T, par
exemple la taille des mémoires RAM, car elle est nécessairement une puissance de 2 à cause
de leur organisation matricielle.
1K, 1M et 1G valent environ mille, un million et un milliard, mais la petite différence
entre 1000 et 1K augmente lorsqu’on passe à 1M et 1G : 1M = 1024 * 1024 = 1 048 576,
et 1G = 1024 * 1048576 = 1 073 741 824, soit presque 1,1 milliard. Les vendeurs de disques
durs exploitent cette ambiguïté : le plus souvent si vous achetez un disque étiqueté ’100
giga-octets’, il s’agit d’un disque de 100 milliards d’octets, et non de 107 374 182 400 octets,
soit une différence de 7%.

64 bits, la taille idéale pour les données ?


Les microprocesseurs manipulent des mots binaires de taille fixe, les plus petits de 8
bits, d’autres de 16 bits, 32 bits et maintenant 64 bits. Un processeur 8 bits, s’il veut ajouter
1000 et 1000, doit effectuer deux additions et non une seule, puisque 1000 ne ’rentre’ pas
dans un codage 8 bits. Un processeur 16 bits code directement des nombres entiers de
l’intervalle [-32768,+32767], ce qui ne permet pas de coder des nombres un tant soit peu
grands. On semble définitivement à l’aise avec 32 bits, mais ce n’est pas le cas : le plus grand
entier qui peut être codé est 232, soit environ 4 milliards, ce qui ne suffit pas à représenter le
montant en euros d’une journée de transactions à la bourse de Paris.
Avec 64 bits, on est définitivement sauvés côté entiers, à moins de vouloir compter les
grains de sable sur la plage. Pour le codage des nombres réels, 64 bits est la taille de codage
en double précision la plus usuelle, qui donne une précision d’une dizaine de chiffres après
la virgule, suffisante dans la majorité des applications. Comme on doit aussi souvent stocker
des adresses de mémoire dans des mots, 64 bits est aussi une taille suffisante, alors que 32
8 CHAPITRE I. PRINCIPES GÉNÉRAUX

bits ne permettent pas de dépasser 4 giga-octets.


Une taille de mot de 64 bits a donc des qualités intrinsèques que n’avaient pas jusque là
16 bits ou 32 bits. Utiliser une taille plus grande de 128 bits dans un processeur conduirait à
beaucoup de gaspillage, puisque la plupart des mots manipulés seraient très peu remplis. Ma
prédiction personnelle est que, dans le futur, cette taille du mot mémoire ne va pas augmenter
indéfiniment, mais va se stabiliser à 64 bits du fait des qualités qui viennent d’être décrites.

Nombres binaires à virgule


Comme en décimal, on peut manipuler des nombres à virgule en binaire. Les chiffres
placés après la virgule ont alors des poids qui sont les puissances négatives décroissantes de
2. Ainsi, 0.12 = 2 − 1 = 0.5 ; 0.012 = 2 − 2 = 0.25, etc.
Si on cherche la représentation binaire approchée du nombre π, on trouvera π =
11.001001… (figure I.7).

1 0 −1 −2 −3 −4 −5 −6
1 1 , 0 0 1 0 0 1 ...

pi = 2 + 1 + 0.125 + 0.015625 + ...

2 0,125
0,015625
1

Figure I.7. Écriture binaire à virgule (approchée) du nombre Pi.

Pour chaque position, on met 1 ou 0 de façon à ce que la valeur courante ne dépasse


pas la valeur à représenter, tout en s’en approchant le plus possible. Par exemple après avoir
choisi 11.001 (total courant = 21 + 20 + 2 − 3 = 2 + 1 + 0.125 = 3.125), on ne peut pas mettre
de 1 en position -4 ou -5, car cela dépasserait π. On doit attendre la position -6, et on a une
valeur approchée de 21 + 20 + 2 − 3 + 2 − 6 = 3.140625.
L’annexe A fournit les valeurs des premières puissances négatives de 2.

Incrémenter et décrémenter en binaire


Une opération fréquente effectuée sur des nombres binaires est le comptage. Par
exemple sur 3 bits, passer de 000 à 001, puis 010, etc. Selon quel algorithme une valeur se
transforme-t-elle en la suivante ?
On peut essayer de s’inspirer du comptage en décimal, qu’on sait faire depuis notre
petite enfance, et pour lequel on ne nous a pourtant jamais donné l’algorithmique. Pour
passer de 124 à 125, seul le chiffre des unités s’est incrémenté.En fait ce chiffre s’incrémente
toujours, avec un passage de 9 à 0. Le chiffre des dizaines sera recopié avec incrémentation,
si le chiffre des unités vaut 9, comme dans le passage de 129 à 130, sinon il sera recopié sans
changement, comme dans le passage de 124 à 125. On tient notre algorithme : pour passer
d’un nombre décimal à son suivant, on considère chaque chiffre un par un, et on le recopie
en l’incrémentant si tous les chiffres qui sont à sa droite valent 9, ou sinon on le recopie
sans changement. On remarquera que l’ordre de traitement sur les chiffres est indifférent, et
qu’on peut effectuer cet algorithme en parallèle sur tous les chiffres. La figure I.8 montre un
4. Mots binaires 9

exemple.

1 2 9 9
des chiffres tous les chiffres tous les chiffres
à droite à droite à droite
différents valent 9 valent 9 toujours
de 9 incrémente
on recopie on recopie
on recopie en incrémentant en incrémentant
sans changer
1 3 0 0
Figure I.8. Incrémentation d’un nombre décimal.Un chiffre est recopié avec incrémentation
si tous les chiffres à sa droite valent 9 ; sinon il est recopié sans changement.

Le même algorithme s’applique en binaire, mais cette fois l’opération d’incrémentation


d’un chiffre est encore plus simple, car elle se résume à une inversion du bit. Pour
incrémenter un nombre binaire, on considère chaque bit du mot initial, et on le recopie avec
inversion si tous les bits qui sont à sa droite valent 1, ou on le recopie sans changement sinon.
Le bit le plus à droite est toujours inversé. La figure I.9 montre un exemple.

1 0 1 1
des bits tous les bits tous les bits
à droite à droite à droite
différents valent 1 valent 1 toujours
de 1 inversion
on recopie on recopie
on recopie en inversant en inversant
sans changer
1 1 0 0
Figure I.9. Incrémentation d’un nombre binaire.Un bit est inversé si tous les bits à sa droite
valent 1 ; sinon il est recopié sans changement.

Là encore l’algorithme peut s’effectuer en parallèle sur tous les bits, ce qui est un
énorme avantage pour un circuit logique, dont c’est le mode de fonctionnement naturel.
Attention, il faut noter qu’on ne peut pas incrémenter la valeur ’sur place’: le mot binaire qui
contient le nombre de départ et le mot binaire qui contient le résultat doivent être distincts,
et ne peuvent pas être confondus.
Un algorithme analogue existe pour le décomptage : on recopie un bit avec inversion
si tous les bits à sa droite valent 0, sinon on le recopie sans changement. Par vacuité, le bit le
plus à droite est toujours inversé. Par exemple, on passe de 110 à 101 en inversant les deux
bits de poids faible, car tous les bits qui sont à leur droite valent 0.

Hexadécimal
Les mots manipulés par un ordinateur ont souvent une largeur supérieure à 16 ou
32 bits, et sont donc difficiles à transcrire en binaire. Pour obtenir une écriture concise,
10 CHAPITRE I. PRINCIPES GÉNÉRAUX

on utilise souvent la base 16, appelée hexadécimal, dont les 16 chiffres sont notés :
0,1,2,3,4,5,6,7,9,A,B,C,D,E,F.Chaque chiffre hexadécimal permet de coder 4 bits (24 = 16).
Pour passer d’une notation binaire à une notation hexadécimale, il suffit de grouper les bits
par paquets de 4, de droite à gauche à partir des poids faibles. Par exemple :

0110.11012 = 6D16 = 10910

0110.1000.1010.11002 = 68AC16

Si l’on considère le dernier exemple, on peut montrer le regroupement par groupes de 4 bits
en utilisant une notation matricielle :
0110.1000.1010.11002 =
 214   210   26   223 
15 11 7

2
0 1 1 0 ×  213  + 1 0 0 0 × 9  + 1 0 1 0 ×  25  + 1 1 0 0 ×  21 
       
       

2 2
       

2 2
 212   28   24   20 
 223   223   223   223 
2 × 28×  21  + × 24×  21  + × 20 ×  21 
0 ×2 × 1  +
12
= 0 1 1 1 0 0 0 1 0 1 0 1 1 0 0
       
       

2 2 2 2
       

 20   20   20   20 
en introduisant les puissances de 16 :
0110.1000.1010.11002 =
 223   223   223   223 
0 1 1 0 × 163×  21  + 1 0 0 0 × 162 ×  21  + 1 0 1 0 × 161×  21  + 1 1 0 0 × 160 ×  21 
       
       

2 2 2 2
       

 20   20   20   20 
= 6× 163 + 8× 162 + 10× 161 + 12× 160
= 68AC16

5. Codage des principaux types de données

5.1. Codage des caractères


Les caractères usuels anglo-saxons utilisent généralement un code sur 7 bits appelé
codage ASCII ; les codes varient entre 0 et 127. Comme ces codes de 7 bits sont toujours
plongés dans un octet (mot de 8 bits), des codages étendus sur 8 bits ont été développés,
d’abord de façon propriétaire, puis de façon organisée par la norme ISO-8859, qui est
en fait un ensemble de normes spécifiques à différentes régions du monde : ISO-8859-1
(dite aussi ISO-Latin-1, la notre, qui couvre toute l’Europe de l’ouest, y compris les
caractères espagnols, allemands et scandinaves), ISO-8859-2 (Europe de l’est), etc. Il y a
actuellement 15 sous normes spécifiques ; chacune d’elles est un sur ensemble du codage
ASCII ; pour les codes de 0 à 127. Par contre, l’aspect graphique et typographique fin des
caractères (ligatures, etc.) n’est pas bien pris en compte par ces codages, qui ont été conçus
essentiellement pour l’échange de données.
Pour tenter de résoudre les problèmes de codage de toutes les langues écrites du
5. Codage des principaux types de données 11

monde, le consortium UNICODE, qui incorpore la plupart des grands constructeurs et


éditeurs informatiques, a créé un encodage de caractères universel. Il est maintenant utilisé
comme codage interne dans des langages tels que Java, dans les navigateurs Internet et de
nombreuses applications ; son adoption généralisée permettrait de résoudre définitivement
les disparités d’encodages de caractères entre pays. Chaque caractère possède un code
point unique, noté par exemple U+03B3 (pour la lettre grecque minuscule gamma γ). Les
codes-points sont dans l’intervalle U+0000 et U+10FFFF (plus d’un million d’entrées !) ;
les 256 premiers codes-points sont identiques à l’ISO-8859-1, et d’autres mappings par
blocs existent pour faciliter le passage d’anciens codes à UNICODE. La suite d’octets
représentant un caractère n’est pas l’écriture binaire du code-point ; un encodage doit
être utilisé, et plusieurs d’entre eux existent. Le plus répandu est sans doute l’UTF-8, qui
représente chaque code-point par une suite de 1 à 4 octets. Les codes-points inférieurs à
0+0080 (les codes ASCII) sont représentés par un unique octet contenant cette valeur :
l’UTF-8 est donc rétrocompatible avec l’ASCII et c’est cette qualité qui a provoqué sa
diffusion rapide. Les autres codes-points sont encodés par un nombre d’octets entre 2 et
4 ; par exemple le caractère U+03B3, qui représente la lettre grecque minuscule gamma,
est codé en UTF-8 : CE16 , B316. Les codes-points supérieurs à 0+FFFF, les plus rarement
employés, sont encodés par une suite de 4 octets.

5.2. Codage des nombres entiers naturels


On utilise le binaire pur, déjà décrit en section 4. Avec n bits, on peut coder les nombres
entiers positifs de l’intervalle [0, 2n − 1].
L’addition et la soustraction s’effectuent de la manière habituelle ; il y a débordement
de l’addition lorsqu’une retenue doit être propagée au delà du dernier bit de rang n.

Lecteur et imprimeur décimal


Si un programme utilise des nombres entiers naturels codés en binaire pur, la
représentation externe de ce nombre à l’utilisateur du programme est généralement faite
dans une autre base, le plus souvent la base 10. Un sous-programme appelé imprimeur est
chargé de convertir une valeur en binaire pur en son écriture décimale, et un autre appelé
lecteur fait la traduction inverse. On peut examiner rapidement les algorithmes qu’ils
utilisent. Ils seraient bien sûr facilement transposables à d’autres bases.

Traduction du binaire vers le décimal


Considérons par exemple un mot de 8 bits N = 101101102 ; on cherche la suite des
caractères qui représente ce nombre en écriture décimale. C’est un programme d’ordinateur
qui réalise cette conversion, et qui dispose donc d’opérateurs arithmétiques (binaires) tels
que addition et multiplications, qui sait faire des comparaisons, etc. Cette traduction ne
comportera pas plus de 3 chiffres décimaux, notons les : XYZ. On choisit pour X le chiffre
le plus grand tel que 100X ≤ N. Ici X = 1 convient car 1×100 ≤ N et 2×100 > N. Pour
faire ce choix de X, on peut effectivement réaliser des multiplications, ou utiliser une table
préconstruite. Il reste à traduire N 1 = N − X×100 = 10100102. Pour cette valeur, on cherche
la plus grande des valeurs de Y telle que 10×Y ≤ N 1 ; on trouve Y = 8, et il reste à convertir
N 2 = N 1 − Y ×10 = 000000102 ; on trouve alors Z = 2. Finalement, les trois chiffres
décimaux cherchés sont XYZ = 182.
12 CHAPITRE I. PRINCIPES GÉNÉRAUX

Traduction du décimal vers le binaire


On cherche donc à convertir l’écriture décimale d’un nombre en un mot binaire.
Le problème n’est pas le symétrique du précédent, car l’ordinateur sait faire toutes les
opérations sur le codage binaire, alors qu’il ne sait en faire aucune directement en base 10.
Pour un nombre décimal tel que ’XYZ’ (qui conduit à une valeur binaire sur 8 bits), il suffit
en fait de faire le calcul n = 100x + 10y + z où x, y et z sont les numéros d’ordre associés
aux chiffres ’X’, ’Y’ et ’Z’ respectivement.

5.3. Codage des nombres entiers relatifs


Aux débuts de l’informatique, on codait généralement les nombres entiers signés en
mettant dans le premier bit le signe (’1’ signifiant ’moins’) et dans les suivants la valeur
absolue. -3 se codait par exemple 10000011 sur 8 bits. L’encodage et le décodage d’un tel
nombre étaient simple (bien qu’on notera que 0 peut être codé +0 ou -0), mais on ne pouvait
plus réaliser une simple addition comme avec des nombres naturels. Si par exemple on tente
de faire l’addition bit à bit de 10000011 (-3) et de 00000100 (+4), on obtient : 10000111,
c’est à dire -7, ce qui est absurde.

Codage en complément à 2
Un codage appelé complément à deux s’est rapidement imposé dans les années 1970.
Il utilise les deux règles suivantes :

• un nombre positif est représenté en binaire pur.

• pour calculer la représentation de l’inverse arithmétique d’un nombre, on inverse tous


ses bits et on ajoute 1, sans tenir compte de l’éventuelle retenue.

Considérons par exemple le codage des nombres relatifs sur 8 bits. +3 va être codé
00000011. Pour obtenir le codage de -3, on inverse tous les bits du codage précédent
(00000011 → 11111100) et on ajoute 1, soit 11111101.

Propriétés
On peut prouver que ce codage a les propriétés remarquables suivantes :

• c’est un codage équilibré: sur n bits, on représente les nombres relatifs de l’intervalle
[ − 2n − 1, 2n − 1 − 1] soit autant de nombre positifs ou nuls que de nombres strictement
négatifs.

• 0 se code sous forme d’un champ uniforme de 0 ; il n’a qu’un seul codage.

• le bit de poids fort du codage représente le signe du nombre.

• les opérations d’addition ou de soustraction en binaire pur s’appliquent également avec


des opérandes codés en complément à deux, avec la différence que la retenue n’a pas
de sens et doit être négligée lors d’une opération en complément à deux.

Pour illustrer le fait que le même opérateur d’addition est utilisé pour le binaire pur et
le complément à 2, considérons l’addition de la figure I.10.
5. Codage des principaux types de données 13

interprétation non signée interprétation signée


(retenue prise en compte) (retenue ignorée)

233 11101001 −23

+ 66 + 01000010 + +66

299 1 00101011 +43

Figure I.10. Une même opération ’mécanique’ d’addition s’interprète de deux façons dif -
férentes.

La même opération effectuée mécaniquement sur des codages (colonne centrale)


s’interprète de deux façons différentes selon qu’on considère les nombres comme étant
signés ou non.

Cercle des nombres


Les différentes propriétés du codage en complément à 2 peuvent être retrouvées avec
la métaphore du cercle des nombres, ici représenté pour un codage sur 3 bits (figure I.11).

positifs
négatifs
0 soustraction
000 1
−1 001
111

−2 110 010 2

101 011
−3 100 3
−4 addition

limite de franchissement
de signe

Figure I.11. Cercle des nombres de 3 bits en complément à 2.

On effectue une addition en parcourant le cercle dans le sens des aiguilles d’une montre,
et une soustraction dans le sens inverse.
Si on recherche le codage de -3, on part de 0 et on tourne de 3 positions dans le sens
inverse des aiguilles d’une montre, pour trouver : 1012. Si on veut lui ajouter 5, on tourne de
5 positions dans le sens des aiguilles d’une montre, et on trouve : 0102, c’est à dire 2.
14 CHAPITRE I. PRINCIPES GÉNÉRAUX

Si on franchit la limite -4 / +3 pendant une opération, cela signifie qu’il y a


eu débordement.

Débordement
Quand y a-t-il débordement lors d’une addition de deux nombres signés ? Il est
clair qu’il ne peut pas se produire lorsque les opérandes sont de signes opposés, puisque la
valeur absolue du résultat est alors inférieure à la plus grande des valeurs absolues des deux
opérandes. Il ne peut donc se produire que s’ils sont de même signe, tous les deux positifs ou
tous les deux négatifs. On démontre facilement qu’alors il y a débordement si et seulement si
l’addition des deux donne un résultat de signe opposé au signe commun des deux opérandes.
Graphiquement sur le cercle des nombres, cela se produit lorsqu’on traverse la frontière -4
→ +3.

Comparaison de nombres en complément à 2


On se pose ici le problème de savoir les positions respectives de deux nombres entiers
codés en complément à 2 A et B : sont ils égaux, ou l’un est-il plus grand que l’autre ? Il faut
examiner les signes des deux nombres à comparer A et B, c’est à dire leurs bits de poids forts.
Si les signes sont différents, la comparaison est immédiate. Si les signes sont identiques, il
suffit de comparer les n − 1 autres bits, l’ordre étant direct si leur signe commun est positif,
inversé si leur signe commun est négatif.

Écriture sous forme d’une somme pondérée du codage en complément à 2


Les propriétés remarquables du codage en complément à 2 peuvent être facilement
démontrées si on remarque qu’en fait, il s’agit également d’une somme pondérée comme en
binaire pur, mais avec un poids particulier de − 2n − 1 pour le bit de poids le plus fort de rang
n − 1.
n−2
s= −2 n−1
bn − 1 + ∑ 2i bi
i=0

où bi est le bit de rang i du mot.


n−2
On peut maintenant démontrer pourquoi le premier bit est un bit de signe. Puisque ∑
i=0
2i est toujours plus petit ou égal à 2n − 1 − 1, alors s < 0 si et seulement si bn − 1 = 1.

5.4. Codes de Gray


Un code de Gray sur n bits est tel que deux codes successifs ne diffèrent que d’un
seul bit. On s’en sert par exemple pour encoder la position d’un axe en rotation, tel
qu’un commutateur rotatif ou une girouette. Plusieurs codes de Gray sont possibles pour
un nombre donné de bits ; on parle de code de Gray réfléchi lorsqu’il possède certaines
propriétés de symétrie. Une méthode récursive pour construire un code de Gray réfléchi sur
n + 1 bits à partir d’un code sur n bits est la suivante :

• sous les codes à n bits, on recopie la liste en miroir.

• on ajoute un bit à gauche, valant 0 pour la première moitié, et 1 pour la deuxième.


5. Codage des principaux types de données 15

On a représenté figure I.12 la construction récursive par cette méthode des codes de Gray
réfléchis à 2 puis 3 bits. Par construction, les codes ne diffèrent que d’un bit pour chacune
des deux moitiés, au passage entre les deux moitiés, ainsi que de la dernière à la première
ligne : c’est donc un code de Gray.

code de Gray
code de Gray code de Gray
sur 2 bits sur 3 bits
sur 1 bit

0 0 0 0 0
0 0 0
0 1 0 0 1
1 0 1
0 1 1 0 1 1
1 1 1
1 1 0 0 1 0
0 1 0
1 0 1 1 0

copie en miroir 1 1 ajouts de 0 1 1 1


copie en miroir ajouts de 0
en première moitié 0 1 en première moitié 1 0 1
et de 1 en deuxième et de 1 en deuxième
0 0 1 0 0

Figure I.12. Construction récursive d’un code de Gray sur 2 bits, puis sur 3 bits.

6. Exercices corrigés

6.1. Exercice 1 : conversion

Énoncé
Écrire 54321 en binaire, puis en hexadécimal.

Solution
On peut se reporter à la table donnée en annexe A pour obtenir la liste des premières
puissances de 2. La plus grande qui soit inférieure à 54321 est 215 = 32768. On peut écrire
alors :

54321 = 215 + 21553.

On recommence avec 21553 : il contient 214 = 16384, et il reste 21553 - 16384 = 5169,
donc :

54321 = 215 + 214 + 5169.

5169 contient 212 = 4096, reste 1073 :

54321 = 215 + 214 + 212 + 1073.

1073 contient 210 = 1024, reste 49 :

54321 = 215 + 214 + 212 + 210 + 49.


En continuant ce processus, on trouve :
16 CHAPITRE I. PRINCIPES GÉNÉRAUX

54321 = 215 + 214 + 212 + 210 + 25 + 24 + 20.


L’écriture binaire est alors :
54321 = 11010100001100012

Pour trouver l’écriture hexadécimale, on regroupe les bits par paquets de 4 :

54321 = 1101.0100.0011.00012

On convertit ensuite chaque groupe en un chiffre hexadécimal équivalent :

54321 = D43116

6.2. Exercice 2 : arithmétique binaire

Énoncé
1 Donner l’intervalle des valeurs possibles pour des nombres non signés codés en binaire
pur sur 11 bits.

2 Donner l’intervalle des valeurs possibles pour des nombres signés codés en
complément à 2 sur 11 bits.

3 Effectuer la somme en binaire : 11012 + 01112 et interpréter le résultat en arithmétique


signée et non signée.

Solution
1 Intervalle des valeurs possibles en binaire pur sur 11 bits.
Avec 11 bits, le nombre de combinaisons possibles est 211 = 2048. L’intervalle des
valeurs est donc : [0, 2047].

2 Intervalle des valeurs possibles en complément à 2 sur 11 bits.


On a vu dans le chapitre précédent que le codage en complément à 2 était équilibré,
c’est à dire qu’il code autant de nombres strictement négatifs que de nombres positifs
ou nuls. Pour respecter cet équilibre avec 2048 codes, il nous faut nécessairement
1024 codes positifs ou nuls et 1024 codes strictements négatifs, soit l’intervalle :
[-1024, +1023].

3 Somme binaire 11012 + 01112.


En effectuant la somme bit à bit, on trouve 01002 avec 1 de retenue sortante.
En arithmétique non signée, la retenue doit être prise en compte, et elle signale un
débordement ; on peut tout de même intégrer ce bit de retenue comme bit de poids
fort du résultat. On a donc fait la somme 13 + 7 et on trouve 101002, soit la valeur
attendue 20.
En arithmétique en complément à 2, la retenue est ignorée. Les nombres ajoutés sont -3
et +7 et le résultat est +4, ce qui est également correct.Un débordement était impossible
6. Exercices corrigés 17

ici, car on ajoutait un nombre positif à un nombre négatif.


Chapitre II

Éléments de logique combinatoire

1. Circuits combinatoires
En 1854, Georges Boole publia son ouvrage séminal sur une algèbre manipulant des
informations factuelles vraies ou fausses. Son travail a été redécouvert et développé sous
la forme que nous connaissons maintenant par Shannon. Le nom de Boole est entré dans
le vocabulaire courant, avec l’adjectif booléen qui désigne ce qui ne peut prendre que deux
valeurs distinctes ’vrai’ ou ’faux’.
Circuits combinatoires : définition
Un circuit combinatoire est un module tel que l’état des sorties ne dépend que de l’état
des entrées. L’état des sorties doit être évalué une fois que les entrées sont stables, et après
avoir attendu un temps suffisant à la propagation des signaux. On comprend intuitivement
que les circuits combinatoires correspondent à des circuits sans état interne : face à la même
situation (les mêmes entrées), ils produisent toujours les mêmes résultats (les mêmes sorties).
Un module d’addition en est un bon exemple : il a en entrées les signaux A et B à additionner,
ainsi qu’une retenue CIN d’un étage précédent ; il fournit en sortie la somme S et une retenue
COUT pour un étage suivant :

Si un circuit combinatoire est composé lui-même de sous-circuits combinatoires,


on démontre que la définition de base est équivalente au fait que ces sous-circuits sont
connectés entre eux sans rebouclages. Plus précisément, si on range les signaux de gauche à
droite, en mettant tout a fait à gauche les entrées, puis dans chaque colonne les sous-circuits
qui ont des entrées parmis les signaux produits dans la colonne de gauche, ou de colonnes
plus à gauche, alors le circuit est combinatoire si et seulement s’il n’y a aucune liaison qui
reboucle ’en arrière’, en allant d’un étage à un étage plus à gauche (figure II.2).
L’implication dans le sens ’sans rebouclage’ → ’combinatoire’ est facile à démontrer :
si les entrées à gauche sont stables, alors par construction toutes les sorties des sous-circuits
de la première colonne sont stables, et ne dépendent que de ces entrées. Donc les sorties des
sous-circuits de la colonne 2 sont stables, et ne dépendent que des entrées. On démontre ainsi
par récursion que les sorties des circuits de chaque colonne sont stables et ne dépendent que
des valeurs des entrées, jusqu’aux sorties finales. Le circuit est donc combinatoire.

18
1. Circuits combinatoires 19

entrées sorties

...
...

Figure II.2. Circuit combinatoire : les signaux internes et les sorties ne rebouclent pas
en arrière.

Les figures II.3 et II.4 montrent des exemples de circuits combinatoires et non
combinatoires formés à partir de portes logiques, dont on verra dans les sous-sections
suivantes qu’elles sont elles-mêmes combinatoires.

Figure II.3. Exemples de circuits combinatoires : les éléments internes sont eux-mêmes
combinatoires, et il n’y a pas de rebouclage interne.

Figure II.4. Exemples de circuits non combinatoires : il y a des rebouclages internes.


20 CHAPITRE II. ÉLÉMENTS DE LOGIQUE COMBINATOIRE

2. Tables de vérité
Une des contributions essentielles de Boole est la table de vérité, qui permet de capturer
les relations logiques entre les entrées et les sorties d’un circuit combinatoire sous une
forme tabulaire.
Considérons par exemple un circuit inconnu possédant 2 entrées A et B et une sortie S.
Nous pouvons analyser exhaustivement ce circuit en lui présentant les 22 = 4 jeux d’entrées
différents, et en mesurant à chaque fois l’état de la sortie. Le tout est consigné dans un
tableau qu’on appelle table de vérité du circuit (figure II.5).

A B S
0 0 0
0 1 0
1 0 0
1 1 1

Figure II.5. Un circuit à deux entrées et une sortie, et sa table de vérité.

Ici, on reconnaît l’opération logique ET : S vaut 1 si et seulement si A et B valent 1.


La construction d’une table de vérité est bien sûr généralisable à un nombre
quelconque n d’entrées et un nombre m de sorties. Elle possédera alors 2n lignes, ce qui n’est
praticable que pour une valeur de n assez petite.

3. Algèbre et opérateurs de base


Plutôt que de décrire en extension la relation entre les entrées et une sortie, on peut
la décrire en intention à l’aide d’une formule de calcul utilisant seulement trois opérateurs
booléens : NON, ET, OU.
NON
NON (NOT en anglais) est un opérateur unaire, qui inverse son argument. On notera

généralement NOT (A) = A ; et sa table de vérité est :

A −
A
0 1
1 0

Figure II.6. Table de vérité du NON, et dessin de la porte correspondante.



On a bien sûr : A = A.
3. Algèbre et opérateurs de base 21

ET
ET (AND en anglais) est un opérateur à 2 entrées ou plus, dont la sortie vaut 1 si et
seulement si toutes ses entrées valent 1. On le note algébriquement comme un produit, c’est
à dire S = A ⋅ B ou S = AB. La table de vérité d’un ET à deux entrées, et le dessin usuel de
la porte correspondante sont représentés figure II.7.

A B A⋅B
0 0 0
0 1 0
1 0 0
1 1 1

Figure II.7. Table de vérité du ET, et symbole de la porte correspondante.

De par sa définition, le ET est associatif : A ⋅ B ⋅ C = (A ⋅ B) ⋅ C = A ⋅ (B ⋅ C). Il est


également idempotent : A ⋅ A = A.

OU
OU (OR en anglais) est un opérateur à 2 entrées ou plus, dont la sortie vaut 1 si et
seulement si une de ses entrées vaut 1. On le note algébriquement comme une somme, c’est
à dire S = A + B. La table de vérité d’un OU à deux entrées, et le dessin usuel de la porte
correspondante sont représentés figure II.8.

A B A+B
0 0 0
0 1 1
1 0 1
1 1 1

Figure II.8. Table de vérité du OU, et symbole de la porte correspondante.

De par sa définition, le OU est associatif : A + B + C = (A + B) + C = A + (B + C). Il


est également idempotent : A + A = A.

De la table de vérité à la formule algébrique


On peut automatiquement écrire la formule algébrique d’une fonction booléenne
combinatoire dès lors qu’on a sa table de vérité. Il suffit de considérer seulement les lignes
qui donnent une sortie égale à 1, d’écrire le minterm correspondant, qui code sous forme
d’un ET la combinaison de ces entrées. Par exemple, le minterm associé au vecteur (A, B,

C) = (1, 0, 1) est : ABC. La formule algébrique qui décrit toute la fonction est le OU de tous
ces minterms.
22 CHAPITRE II. ÉLÉMENTS DE LOGIQUE COMBINATOIRE

Considérons par exemple la table de vérité de la fonction majorité à 3 entrées, qui vaut
1 lorsqu’une majorité de ses entrées (2 ou 3) vaut 1 (figure II.9).

A B C MAJ(A,B,C)
0 0 0 0
0 0 1 0
0 1 0 0
0 1 1 1
1 0 0 0
1 0 1 1
1 1 0 1
1 1 1 1

Figure II.9. Table de vérité de la fonction majorité à 3 entrées.

La table de vérité contient 4 lignes avec une sortie S à 1, ce qui donne :


− − −
S = ABC + ABC + ABC + ABC

Cette formule est simplifiable, comme on le verra dans une section ultérieure.
Est-on sûr que la formule obtenue par cette méthode est correcte ? Pour une
combinaison des entrées qui doit donner une sortie à 1, la formule possède le minterm
correspondant, et donne aussi une valeur de 1. Pour toutes les autres combinaisons d’entrées,
la table donne une sortie à 0, et la formule aussi, puisqu’aucun minterm n’est présent qui
corresponde à ces combinaisons. La formule donne donc toujours le même résultat que
la table.
Corollairement, cela démontre le résultat fondamental suivant :

N’importe quelle fonction combinatoire s’exprime sous forme d’une somme


de minterms.

Théorèmes de l’algèbre de Boole


Le tableau de la figure II.10 résume les principaux théorèmes et propriétés de l’algèbre
de Boole. On peut les démontrer de façon algébrique, ou en utilisant des tables de vérité.
Les théorèmes de De Morgan permettent de transformer les ET en OU et vice-versa.
Le couple (ET,NON) ou le couple (OU,NON) suffisent donc à exprimer n’importe quelle
formule algébrique combinatoire.
Le théorème d’absorption A + AB = A montre qu’un terme plus spécifique disparaît au
− − −
profit d’un terme moins spécifique. Par exemple dans l’équation ⋅ ⋅ ⋅ + AB + ABC ⋅ ⋅ ⋅ , le
− − −
terme ABC s’efface au profit de AB, moins spécifique et qui donc l’inclut.
3. Algèbre et opérateurs de base 23

relation relation duale Propriété


AB = BA A+B= B+A Commutativité
A(B + C) = AB + AC A + BC = (A + B)(A + C) Distributivité
1⋅A = A 0+A=A Identité
− −
AA = 0 A+A= 1 Complément
0⋅A = 0 1+ A = 1 Zéro et un
A⋅A = A A+A= A Idempotence
A(BC) = (AB)C A + (B + C) = (A + B) + C Associativité

− Involution
A=A
− − − − −−
AB = A + B A + B = AB Théorèmes de De Morgan
A(A + B) = A A + AB = A Théorèmes d’absorption
− −
A + AB = A + B A(A + B) = AB Théorèmes d’absorption

Figure II.10. Propriétés et théorèmes de l’algèbre de Boole.

4. Autres portes logiques


NAND
NAND (= NOT AND) est un opérateur à 2 entrées ou plus, dont la sortie vaut 0 si et

seulement si toutes ses entrées valent 1. On le note ↑, et on a donc à deux entrées : A↑B = A ⋅ B
La table de vérité d’un NAND à deux entrées, et le dessin usuel de la porte correspondante
sont représentés figure II.11.

A B A↑ B
0 0 1
0 1 1
1 0 1
1 1 0

Figure II.11. Table de vérité du NAND, et dessin de la porte correspondante.

Le NAND est un opérateur dit complet, c’est à dire qu’il permet à lui seul d’exprimer
n’importe quelle fonction combinatoire. Il permet en effet de former un NOT, en reliant ses

deux entrées : A = A↑ A. Les théorèmes de De Morgan assurent donc qu’il peut exprimer un
ET et un OU, et donc n’importe quelle expression combinatoire.

NOR
NOR (= NOT OR) est un opérateur à 2 entrées ou plus, dont la sortie vaut 0 si
et seulement au moins une de ses entrées 1. On le note ↓, et on a donc à deux entrées : A
24 CHAPITRE II. ÉLÉMENTS DE LOGIQUE COMBINATOIRE


↓B = A + B La table de vérité d’un NOR à deux entrées, et le dessin usuel de la porte
correspondante sont représentés figure II.12.

A B A↓ B
0 0 1
0 1 0
1 0 0
1 1 0

Figure II.12. Table de vérité du NOR, et dessin de la porte correspondante.

Comme le NAND, le NOR est également un opérateur complet. Il permet en effet



de former un NOT, en reliant ses deux entrées : A = A ↓ A. Les théorèmes de De Morgan
assurent donc qu’il peut exprimer un ET et un OU, et donc n’importe quelle expression
combinatoire.

XOR
XOR (= EXCLUSIVE OR) est un opérateur à 2 entrées ou plus. On peut le définir de
plusieurs façons ; la définition qui permet le plus directement de démontrer ses propriétés
est celle qui consiste à en faire un détecteur d’imparité : sa sortie vaut 1 si et seulement si
un nombre impair de ses entrées est à 1. On le note ⊕ ; la table de vérité d’un XOR à deux
entrées, et le dessin usuel de la porte correspondante sont représentés figure II.13.

A B A⊕B
0 0 0
0 1 1
1 0 1
1 1 0

Figure II.13. Table de vérité du XOR, et dessin de la porte correspondante.

− −
On voit à partir de la table que A ⊕ B = AB + AB. Lorsqu’il a deux entrées, il mérite
son nom de OU exclusif puisque sa sortie ne vaut 1 que si l’une de ses entrées vaut 1, mais
seulement une. Il possède plusieurs autres propriétés intéressantes :

• lorsqu’on inverse une entrée quelconque d’un XOR, sa sortie s’inverse. C’est évident
puisque cela incrémente ou décrémente le nombre d’entrées à 1, et donc inverse la
parité.

• le XOR à deux entrées est un inverseur commandé. En effet, en examinant la table de


vérité du XOR (figure II.13)), on constate que lorsque A vaut 0, la sortie S reproduit
4. Autres portes logiques 25

l’entrée B, et lorsque A vaut 1, la sortie S reproduit l’inverse de l’entrée B. L’entrée


A peut être alors vue comme une commande d’inversion dans le passage de B vers S.
Cette fonction sera très utile lorsqu’on souhaitera corriger une erreur détectée sur une
ligne, par exemple.

• le XOR est un opérateur d’addition arithmétique. Cette propriété est valable quel que
soit le nombre d’entrées du XOR. Bien sûr, il n’est question ici que du bit de poids
faible du résultat (ajouter n termes de 1 bit produit un résultat sur log2 (n) bits). Et en
effet, si on regarde la table de vérité du XOR à deux entrées, on constate que la sortie
est bien la somme arithmétique des deux entrées. Bien sûr à la dernière ligne, le résultat
à une retenue que le XOR à lui seul ne peut pas produire.
On peut démontrer que ce résultat est général par une simple récurrence. Si en
effet on suppose que XOR(e1, e2, …, en ) fournit le bit de poids faible de la somme sn =
e1 + e2 + … + en, alors :
XOR(e e …, e e ) = s si e = 0 ; −
1, 2, n, n + 1 n n+1
s si e = 1
n n+1
= sn avec inversion commandée par en + 1
= sn ⊕ en + 1

• XOR est un opérateur associatif.Sa définition même, en tant qu’indicateur d’imparité,


assure cette propriété. Quelle que soit la façon de grouper les termes, le calcul donnera
le même résultat. Ce résultat permet de construire avec un minimum de circuits des
XORs à 3 entrées ou plus, en cascadant des XORs à 2 entrées.

Formule algébrique d’un XOR à plus de 2 entrées


Considérons le calcul du XOR de trois variables A, B et C. On sait que son expression
algébrique peut se mettre sous forme d’une somme de minterms ; on va donc chercher parmi
tous les minterms possibles ceux qu’il faut inclure dans la formule, et ceux qu’il faut rejeter.
−−− −− − − −−−
Avec ces trois variables A, B et C, il y a 8 minterms possibles : ABC, ABC, ABC, … ABC ne
doit pas être inclus dans le résultat, puisqu’il donne 1 pour le vecteur (0,0,0) qui ne comporte
−−
pas un nombre impair de 1. ABC doit l’être, car il donne 1 pour le vecteur (0,0,1) qui possède
un nombre impair (1) de 1. La règle est simple : on doit inclure seulement les minterms qui
ont un nombre impair de variables non barrées. Pour le XOR à trois entrées, on peut le faire
de façon organisée en mettant d’abord les 3 minterms ayant une variable non barrée, puis le
minterm ayant les 3 variables non barrées :
−− − − −−
XOR(A, B, C) = ABC + ABC + ABC + ABC

On peut montrer que cette formule n’est pas simplifiable en tant que somme de termes.
Pour un XOR à 4 entrées, on inclut les 4 minterms ayant 1 variable non barrée, puis les 4
minterms ayant 3 variables non barrées :
−−− − −− −− − −−− − − − −
XOR(A, B, C, D) = ABC D + AB C D + ABC D + AB CD + ABC D + AB CD + ABCD + AB
CD
Pour un nombre quelconque d’entrées, on peut montrer que le résultat comporte toujours la
moitié de tous les minterms possibles, et que cette somme n’est jamais simplifiable en tant
26 CHAPITRE II. ÉLÉMENTS DE LOGIQUE COMBINATOIRE

que somme de termes.

Le multiplexeur
Le multiplexeur est une porte à trois entrées, qui joue un rôle d’aiguillage. Son dessin
indique bien cette fonction (figure II.14).

SEL S
0 A
1 B

Figure II.14. Table de vérité condensée du multiplexeur, et dessin de la porte corres-


pondante.

Lorsque la commande de sélection vaut 0, l’aiguillage est du côté de A, et S prend la valeur


de A ; lorsqu’elle vaut 1, S prend la valeur de B. Algébriquement, on a donc :

S = SEL·A + SEL·B

À partir de la compréhension du rôle du multiplexeur, il est clair que :


− −− −
S = SEL·A + SEL·B

Or ce n’est pas si facile à établir algébriquement :


− − − −
− −
S = SEL·A + SEL·B = SEL·A·SEL·B
− − − − −− − −−
S = (SEL + A)·(SEL + B) = A·SEL + SEL·B + A·B
−− −− −−
On a un terme en trop, A·B ! En fait, on peut le faire disparaître en écrivant A·B = A·B·
−−−
SEL + A·B·SEL :
− −− − −− −−−
S = A·SEL + SEL·B + A·B·SEL + A·B·SEL
− −− − − − −− −
S = A·SEL(1 + B) + SEL·B(1 + A) = SEL·A + SEL·B
On a ici un exemple où la compréhension d’une fonction a permis de se dispenser de
calculs algébriques ou de méthodes de simplification.

5. SHDL, un langage de description matérielle pédagogique


Les langages de description matérielle (HDL)
Un langage de description matérielle, ou HDL (Hardware Description language),
permet la description de circuits logiques sous une forme textuelle. Cette description peut
ensuite être utilisée par un simulateur pour être testée, ou elle peut être traduite par un
5. SHDL, un langage de description matérielle pédagogique 27

programme de synthèse afin d’être utilisée pour la fabrication d’un circuit logique spécifique
(ASIC), ou pour pouvoir programmer un circuit logique reconfigurable tel qu’un PLD ou
un FPGA (section 6).
Certains langages de description sont structurels et d’autres sont fonctionnels. Un
langage de description structurel précise explicitement tous les modules et sous modules
qui sont utilisés dans le circuit. Si l’on veut implémenter une addition, il va falloir entrer
dans tous les détails de sa conception. À l’inverse, un langage fonctionnel va décrire certains
aspects de son fonctionnement sous forme d’une fonction et donc de façon non explicite ni
extensive. L’addition de deux registres A et B sera par exemple écrite A + B, sans entrer dans
les détails de sa réalisation matérielle. La plus grande lourdeur des langages de description
structurels doit être tempérée par le fait qu’ils sont généralement tous modulaires, et que des
circuits tels que des additionneurs existent déjà sous forme de modules tout prêts disponibles
dans des bibliothèques. Néanmoins, ce sont les langages fonctionnels qui deviennent les
plus largement utilisés, tels que VHDL et Verilog. ABEL et PALASM sont des exemples de
langages structurels, ABEL étant le plus populaire. ABEL existe depuis 1983, et il a encore
une grande communauté de concepteurs qui l’utilise.

SHDL, un langage structurel pédagogique


SHDL est un langage de description matérielle issu du langage MDL conçu
initialement par Jean Conter, au département informatique de l’ENSEEIHT de Toulouse.
SHDL signifie Simple Hardware Description Language; il est d’une écriture très simple et
il encourage en effet une conception hautement modulaire, en supprimant tout simplement
les déclarations habituelles à faire pour qu’un circuit déjà conçu puisse être réutilisé en tant
que ’boîte noire’ dans un circuit de niveau supérieur. La syntaxe de SHDL est proche de
celle d’ABEL, en en étant encore plus simple, et il est comme lui un langage de description
structurel. Comme il a d’abord une visée pédagogique, ce choix permet d’examiner le
fonctionnement des circuits conçus, jusqu’à la plus petite échelle.
Un éditeur graphique et un simulateur ont été conçus pour le langage SHDL.
Cet environnement est disponible sous forme de licence GPL, donc gratuit, utilisable et
modifiable à volonté. Le lecteur est fortement encouragé à simuler tous les exemples de ce
livre dans l’environnement SHDL.
L’environnement libre et complet d’édition graphique et de simulation,
ainsi qu’une documentation complète du logiciel sont disponibles à l’adresse
http://diabeto.enseeiht.fr/download/shdl.

Premiers éléments de syntaxe SHDL


La syntaxe de SHDL sera décrite progressivement au fil des chapitres.
Un exemple simple sera plus parlant qu’un long discours :

module half_adder(a,b: s,r)


s = /a*b+a*/b ;
r = a*b ;
end module

Ce module, appelé half_adder, possède 2 entrées appelées a et b, et deux sorties s et r.


La première ligne module half_adder(a,b :s,r) fournit donc le nom et l’interface
du module.
28 CHAPITRE II. ÉLÉMENTS DE LOGIQUE COMBINATOIRE

On notera la présence du signe ’:’ pour marquer la séparation entre les entrées et les
sorties ; en réalité ce signe n’est pas obligatoire car le logiciel peut deviner seul les
fonctions de ces signaux, mais ils peuvent rendre l’écriture plus lisible. L’écriture module
half_adder(a,b,s,r) aurait donc été équivalente. On notera également la présence de
la dernière ligne end module, qui termine la définition du module. Elle est obligatoire, et
elle permet ainsi de placer plusieurs définitions de modules dans un même fichier, chaque
définition étant encadrée par un début commençant par module et une fin end module.
Viennent ensuite des lignes, terminées par le signe ’;’, qui décrivent comment les
signaux s et r sont produits à partir de a et b. Le signe ’/’ indique la négation, et s’applique

au symbole immédiatement à sa droite. /a et /b signifient donc −a et b. Les signes ’+’ et ’*’
indiquent les opérations OU et ET respectivement ; les deux lignes s’interprètent donc : s =
−ab + a−b et r = ab.
Les parenthèses sont interdites en SHDL dans les expressions ; elles ne peuvent
apparaître que dans la description de l’interface. En particulier, on ne peut pas écrire
des négations de la forme /(a+b) ; il faudra utiliser le théorème de De Morgan et
écrire /a*/b. Cette contrainte n’est pas une limitation, puisqu’on sait que toute formule
algébrique peut se mettre sous forme d’une somme de minterms. Par ailleurs cette écriture
est adaptée à la synthèse de circuits logiques reconfigurables de type PLD ou FPGA.
Finalement, l’éditeur graphique de l’environnement SHDL permet d’afficher le
contenu d’un tel module (figure II.15).

module half_adder(a,b: s,r)


s = /a*b+a*/b ;
r = a*b ;
end module

Figure II.15. Demi-additionneur en SHDL.

6. Circuits logiques reconfigurables ; PLD et FPGA


Un circuit logique programmable est un composant électronique qui réalise des
fonctions combinatoires et séquentielles, mais dont le contenu est modifiable, décrit lors
d’une phase de programmation (ou reconfiguration).Les premiers modèles ne pouvaient être
programmés qu’une fois, mais la plupart des modèles actuels permettent plusieurs milliers
6. Circuits logiques reconfigurables ; PLD et FPGA 29

de cycles d’effaçage et réécriture. Un tel circuit est un peu plus lent que le même circuit fixe
qui aurait été produit spécifiquement (ou ASIC), mais son coût est faible pour des petites
productions, et sa reprogrammmation permet une phase de mise au point interactive, et
autorise des mises à jours après déploiement.
PLD
PLD signifie ’Programmable Logic Device’. Un PLD se caractérise par :

• un nombre de composants plus faible qu’un FPGA.

• une consommation plus faible que celle d’un FPGA.

• une absence de phase de ’bootstrap’. Une fois configuré, un PLD conserve sa


configuration même hors tension, et il est opérationnel immédiatement après la mise
sous tension.
Les premiers PLDs s’appelaient PALs et GALs, et ils n’étaient programmables qu’une fois.
Leur programmation nécessitait l’utilisation d’un programmateur spécial, et ils devaient être
placés sur un support si on voulait profiter de leur reprogrammabilité.
Les PLDs plus complexes sont souvent appelés CPLDs. Leur configuration est stockée
dans une EEPROM ou mémoire flash, qui peut être mise à jour en utilisant le protocole
JTAG (voir plus loin).

FPGA
Un FPGA (Field Programmable Gate Array) utilise une organisation de type matrice
de portes, qui permet un nombre de composants plus grands. Sa configuration est stockée
dans une RAM, qui doit être rechargée à chaque remise sous tension, généralement à partir
d’une ROM présente sur la carte. Cette particularité qui semble être un inconvénient lui
donne la possibilité très intéressante de pouvoir être reconfiguré dynamiquement, durant
son fonctionnement.

Programmation JTAG
La plupart des PLDs et des FPGAs sont configurables directement sur le circuit
imprimé où ils vont être placés, sans programmateur, en utilisant un protocole standard
de programmation à l’aide d’un flux sériel de données appelé protocole JTAG. Tous les
composants programmables présents sur le circuit imprimé sont reliés ensemble en ’daisy
chain’ (figure II.16).

TDO

TDI TDI TDO TDI TDO TDI TDO


CPLD FPGA ROM
TMS
Flash
TMS TMS TMS
TCK TCK TCK TCK

Figure II.16. Chaîne de programmation JTAG.On peut mélanger des circuits de tous types,
et même parfois de différents constructeurs.
30 CHAPITRE II. ÉLÉMENTS DE LOGIQUE COMBINATOIRE

Un interface très simple permet généralement d’exploiter les 5 signaux du protocole à


partir de l’ordinateur de développement. Un logiciel de programmation permet d’effectuer
une opération de lecture ou de configuration sur un ou plusieurs des composants de la chaîne,
sans affecter les autres. Comme ce protocole est standard, il est généralement possible de
mélanger dans cette chaîne des composants de différents constructeurs.
Des cartes d’expérimentation à base de PLD ou de FPGA, programmables à l’aide
d’un tel câble JTAG sont maintenant accessibles à faible coût, et permettent de réaliser des
systèmes équivalents à plusieurs centaines de milliers de portes. Les outils de développement
et de configuration sont tous fermés et propriétaires, et le développement d’environnements
ouverts est découragé par la non divulgation des spécifications internes précises des
composants.

7. Méthodes de simplification des fonctions combinatoires

7.1. Tables de Karnaugh


Une table de Karnaugh est utilisée pour trouver visuellement les simplifications à faire
sur une somme de termes. Elle est très facile à utiliser, pour peu qu’il n’y ait pas plus de 4
variables en jeu. Au delà, il vaut mieux employer la méthode de Quine-Mc Cluskey décrite
plus bas.
Considérons par exemple la fonction ayant la table de vérité de la figure II.17.

A B C D S
0 0 0 0 0
0 0 0 1 1
0 0 1 0 1
0 0 1 1 1
0 1 0 0 0
0 1 0 1 1
0 1 1 0 0
0 1 1 1 1
1 0 0 0 0
1 0 0 1 1
1 0 1 0 0
1 0 1 1 1
1 1 0 0 1
1 1 0 1 1
1 1 1 0 1
1 1 1 1 1

Figure II.17. Table de vérité d’une fonction à simplifier.

On va représenter les valeurs de S dans un tableau 4X4, chaque case correspondant à


un des 16 minterms possibles avec 4 variables A,B,C et D (figure II.18).
On remarquera d’abord les libellés des lignes et des colonnes, qui forment un code de
Gray : d’une ligne à l’autre ou d’une colonne à l’autre, il n’y a qu’un bit d’entrée qui change.
La table a une forme de cylindre dans les deux directions, c’est à dire que les colonnes ’00’
et ’10’ doivent être considérées comme côte à côte, tout comme les lignes ’00’ et ’10’.
7. Méthodes de simplification des fonctions combinatoires 31

A,B
C,D 0,0 0,1 1,1 1,0
0,0 1
0,1 1 1 1 1
1,1 1 1 1 1
1,0 1 1

Figure II.18. Table de Karnaugh représentant la table de vérité.

Les 11 sorties ’1’ de la table de vérité ont été reportées dans la table de Karnaugh. En
effectuant des groupes de ’1’ connexes de 2, 4 ou 8, on réalise une simplification (figure
II.19).

AB
A,B
C,D 0,0 0,1 1,1 1,0
0,0 1
D
0,1 1 1 1 1
1,1 1 1 1 1
1,0 1 1

ABC
Figure II.19. Table de Karnaugh avec les regroupements.

−− −− −− −
Par exemple le regroupement ABC correspond à la simplification ABCD + ABC D
−− − −−
= ABC(D + D) = ABC. De même, le regroupement AB correspond à la simplification des 4
−− − − −− − −
termes : ABC D + ABCD + ABC D + ABCD = AB(C D + CD + C D + CD) = AB. On repère
facilement le résultat en regardant les libellés des lignes et des colonnes impliqués dans
la simplification. Pour le terme simplifié D par exemple, il concerne les lignes CD=01 ;11
et les colonnes AB=**; seul D = 1 est constant, et toutes les autres variables disparaissent.
Finalement on a :
−−
S = D + AB + ABC

Utilisation des combinaisons non spécifiées


Il y a des situations dans lesquelles certaines combinaisons des entrées ne se produisent
jamais, qu’on peut appeler combinaisons interdites ou combinaisons non spécifiées.
Considérons par exemple un décodeur 7 segments, qui convertit un nombre binaire sur 4
bits en un vecteur de 7 bits qui commande un afficheur à Leds de type ’calculette’ (figure
II.20).
La spécification de ce convertisseur nous précise qu’il ne faut convertir que les chiffres
de 0 à 9, c’est à dire les valeurs du vecteur (A,B,C,D) de (0,0,0,0) à (1,0,0,1). Ainsi le
fonctionnement du circuit n’est pas spécifié pour les valeurs de (1,0,1,0) à (1,1,1,1), et on va
32 CHAPITRE II. ÉLÉMENTS DE LOGIQUE COMBINATOIRE

(A,B,C,D) (a,b,c,d,e,f,g) f b
g

4 7 e c
d

Figure II.20. Convertisseur 7 segments pour afficheur de calculette.

exploiter cette latitude pour simplifier les équations.


Si on considère par exemple la table de vérité du segment a, elle comporte ainsi 6 lignes
pour lesquelles la valeur de la sortie n’est pas spécifiée, qu’on note ’*’ (figure II.21).

A B C D a
0 0 0 0 1
0 0 0 1 0
0 0 1 0 1
0 0 1 1 1
0 1 0 0 0
0 1 0 1 1
0 1 1 0 1
0 1 1 1 1
1 0 0 0 1
1 0 0 1 1
1 0 1 0 *
1 0 1 1 *
1 1 0 0 *
1 1 0 1 *
1 1 1 0 *
1 1 1 1 *

Figure II.21. Table de vérité du segment a.

Dans la table de Karnaugh correspondante, les ’*’ peuvent être incluses dans les
regroupements (figure II.22) ce qui conduit à leur donner la valeur ’1’. Bien sûr on ne doit
pas chercher à inclure toutes les ’*’dans les regroupements : on cherche à inclure tous les ’1’,
mais les ’*’permettent de trouver des regroupements plus grands. Ici par exemple, on trouve
le résultat très simple :
−−
a = C + A + BD + BD

Détection et correction des glitches


Un autre usage des tables de Karnaugh est la détection des glitchs, c’est à dire
des changements très brefs des signaux de sortie d’une fonction combinatoire lors de
changements des entrées. Considérons par exemple la fonction suivante :
−−−− − −− −− − −
S = ABC D + ABC D + ABC D + ABCD + ABC D + ABCD

On construit directement la table de Karnaugh associée à cette fonction, et on trouve


7. Méthodes de simplification des fonctions combinatoires 33

BD
A,B
C,D 0,0 0,1 1,1 1,0
A
0,0 1 * 1
0,1 1 * 1 C
1,1 1 1 * *
1,0 1 1 * *

BD

Figure II.22. Table de Karnaugh du segment a, avec les combinaisons non spécifiées
marquées par des ’*’. On peut inclure les ’*’ pour former des regroupements plus grands.

immédiatement deux regroupements (figure II.23).

A,B
C,D 0,0 0,1 1,1 1,0
0,0 1 1a 1 b
0,1 1
1,1 1
1,0 1

Figure II.23. Table de Karnaugh avec les regroupements. Un glitch est possible dans le
passage de a à b, car les deux regroupements sont tangents à cet endroit.

L’équation simplifiée est alors :


−−−
S = AB + AC D
Cette équation est plus simple que la première, mais elle va se comporter légèrement
différemment lors de certains changements de valeurs des entrées. Considérons par exemple
le changement du vecteur d’entrées (A,B,C,D) de (0,1,0,0) à (1,1,0,0), qui correspond au
passage de a à b sur la figure II.23. Dans ce cas, seul A a changé, et la sortie S devrait rester
à 1, aussi bien pour l’équation initiale que pour l’équation simplifiée. Or pour l’équation
−−−
simplifiée, le terme AB va passer de 0 à 1 tandis que le terme AC D va passer de 1 à 0 ; si ce
changement se fait exactement en même temps, S restera à 1, mais si le premier terme passe
à 0 légèrement avant que le deuxième ne passe à 1, un léger glitch sur S apparaîtra (figure
II.24).
Ce glitch était prévisible visuellement sur la table de Karnaugh, par le fait des deux
regroupements qui sont adjacents sans se recouvrir. On constate également que c’est le seul
changement d’une seule variable qui peut conduire à un tel problème. La solution est de
rajouter un regroupement qui va faire la liaison entre ces deux groupes, en valant 1 pendant
toute la transition (figure II.25).
Finalement, on obtient l’expression simplifiée sans glitch :
34 CHAPITRE II. ÉLÉMENTS DE LOGIQUE COMBINATOIRE

A.B

A.C.D

S = A.B + A.C.D
glitch
−−−
Figure II.24. Glitch sur l’équation S = AB + ACD,lors du passage de (A,B,C,D) de (0,1,0,0)
à (1,1,0,0).

terme supplémentaire
A
A,B
C,D 0,0 0,1 1,1 1,0
0,0 1 1a 1 b A.B

0,1 1
1,1 1 A.C.D

1,0 1
B.C.D

S = A.B + A.C.D + B.C.D

−−
Figure II.25. Ajout du terme BC D pour maintenir S à 1 lors de la transition de a vers b. Le
glitch disparaît sur le signal de sortie S.

−−− −−
S = AB + AC D + BC D

7.2. Méthode de Quine-Mc Cluskey


On va montrer son usage en simplifiant la fonction à 5 variables suivante :
−−−−− −−− − − −−− − − − − −− − −
f (A, B, C, D, E) = AB C D E + AB CD E + AB C D E + AB CD E + ABC D E + ABC DE
− − − −
+ ABCDE + ABC DE + ABCDE
Une table de Karnaugh serait délicate à utiliser dans ce cas, car elle aurait une taille de 4 x
8, et certains regroupements possibles pourraient ne pas être connexes.
Par économie d’écriture, on commence par représenter chaque minterm par le nombre
associé à sa représentation binaire :

f (A, B, C, D, E) = ∑(0, 2, 8, 10, 12, 13, 26, 29, 30)


7. Méthodes de simplification des fonctions combinatoires 35

On place ensuite ces minterms dans un tableau, en les groupant selon le nombre de 1
qu’ils possèdent (figure II.26).

nombre de 1 minterm valeur binaire


0 0 00000
1 2 00010
8 01000
2 10 01010
12 01100
3 13 01101
26 11010
4 29 11101
30 11110

Figure II.26. On classe les minterms selon le nombre de 1 de leur valeur binaire.

Les simplifications ne peuvent se produire qu’entre groupes adjacents. La


simplification entre les minterms 1 et 2 se notera par exemple : 000-0, en mettant un tiret à
l’endroit où la variable a disparu. Lorsqu’on a effectué toutes les simplifications possibles,
on recommence à partir des nouveaux groupes formés, jusqu’à ce qu’aucune simplification
ne soit possible (figure II.27).

étape 1 étape 2 étape 3

0 00000 ✓ 0-2 000-0 ✓ 0-2-8-10 0-0-0


2 00010 ✓ 0-8 0-000 ✓
8 01000 ✓ 2-10 0-010 ✓
10 01010 ✓ 8-10 010-0 ✓
12 01100 ✓ 8-12 01-00
13 01101 ✓ 10-26 -1010
26 11010 ✓ 12-13 0110-
29 11101 ✓ 13-29 -1101
30 11110 ✓ 26-30 11-10

Figure II.27. On simplifie entre groupes adjacents, étape par étape. Les termes qui ont été
utilisés dans une simplification sont cochés.

Maintenant, il suffit de choisir dans ce tableau les termes qui vont inclure tous les
minterms de départ, en essayant de trouver ceux qui correspondent à l’expression la plus
36 CHAPITRE II. ÉLÉMENTS DE LOGIQUE COMBINATOIRE

simple. On peut le faire de façon purement intuitive, en commençant par les termes les plus à
droite : par exemple les groupes 0-2-8-10, 8-12, 13-29, 26-30 vont inclure tous les minterms.
On trouve donc :
−−− − −− − −
S = AC E + ABDE + BC DE + ABDE
Dans les cas complexes, ou si on veut programmer cet algorithme, on énumère la liste des
implicants premiers, qui sont les termes du tableau précédent qui n’ont été utilisés dans
aucune simplification. Dans le tableau précédent, on a coché avec une ’✓’ les termes qui ont
été utilisés dans une simplification, donc la liste des implicants premiers est : 8-12, 10-26,
12-13, 13-29, 26-30, 0-2-8-10. Il est clair que le résultat simplifié ne comportera que des
termes de cette liste, et le but est de déterminer parmi eux ceux qu’il est indispensable de
placer dans le résultat, appelés implicants premiers essentiels. On construit pour cela la table
des implicants premiers (figure II.28).

0 2 8 10 12 13 26 29 30
8-12 01-00 ✓ ✓
10-26 -1010 ✓ ✓
12-13 0110- ✓ ✓
13-29 -1101 * ✓ ✓
26-30 11-10 * ✓ ✓
0-2-8-10 0-0-0 * ✓ ✓ ✓ ✓

Figure II.28. Table des implicants premiers ; les implicants premiers sont en lignes et les
minterms de départ sont en colonnes. Un implicant premier est dit essentiel s’il est le seul
à couvrir un des minterms en colonne ; on le repère avec une marque ’*’.

Parmi les 5 minterms placés en ligne dans le tableau, 3 sont dits essentiels, parce qu’ils
sont les seuls à couvrir un des minterms placés en colonnes : l’implicant premier 13-29 est
le seul à couvrir le minterm 29, 26-30 est le seul à couvrir 26 et 30, et 0-2-8-10 est le seul à
couvrir les minterms 0 et 2. 8-12 et 12-13 ne sont pas essentiels, car 8, 12 et 13 sont couverts
par d’autres implicants premiers.
Les implicants premiers essentiels sont nécessaires, mais pas forcement suffisants. Ici
par exemple, si on ne prenait qu’eux, le minterm 12 ne serait pas couvert. Il reste donc une
phase heuristique de choix parmi les implicants premiers non essentiels pour couvrir tous
les minterms non encore couverts. Dans notre exemple, on peut ajouter, soit 8-12, soit 12-13
pour couvrir le 12 manquant, ce qui donne les deux possibilités suivantes :
−−− − −− − −
version avec 8-12 : S = AC E + ABDE + BC DE + ABDE

−−− − − − −
version avec 12-13 : S = AC E + ABC D + BC DE + ABDE
8. Circuits combinatoires réutilisables 37

8. Circuits combinatoires réutilisables


Nous cherchons à développer une bibliothèque de circuits combinatoires (et ensuite
séquentiels) qui seront réutilisables dans différents contextes, et notamment celui des
microprocesseurs et des microcontrôleurs. Parmi eux figurent des opérateurs efficaces
d’arithmétique entière : additionneurs, multiplieurs, comparateurs. Un interface adapté
permettra une récursivité ou une interconnection aisées.

8.1. Problématique de l’addition


Lorsqu’on effectue une addition en binaire, la méthode la plus simple consiste à
l’effectuer bit à bit, en commençant par le bit de poids faible (figure II.29).

0 1 1
0
A[3..0] 1 0 0 1
B[3..0] 0 0 1 1

S[3..0] 1 1 0 0

addition demi
complète addition

Figure II.29. Addition en binaire bit à bit.

A chaque rang i de l’addition, on fait la somme des bits A[i] et B[i], ainsi que de
l’éventuelle retenue qui viendrait du rang précédent i − 1. Ce calcul produit le bit S[i] du
résultat, ainsi qu’une retenue qui sera passée au rang suivant. Lorsqu’on a effectué toutes
ces additions depuis le bit de poids le plus faible (rang 0) jusqu’au bit de poids le plus fort,
la retenue finale sera la retenue qui sort du dernier rang d’addition.
On appelle demi-additionneur le circuit qui fait le calcul du rang 0, car il n’a pas à
prendre en compte de retenue qui viendrait d’un étage précédent. On appelle additionneur
complet le circuit qui prend en compte le calcul d’un bit de rang quelconque différent de 0.

8.2. Demi-additionneur
Un demi-additionneur est le circuit qui réalise une addition entre deux bits A et B, et
qui produit la somme sur un bit S avec l’éventuelle retenue R. La table de vérité et le module
correspondant sont représentés figure II.30.
Il est clair en effet que la somme est le XOR entre A et B, et la retenue ne peut intervenir
que lorsque A et B valent 1 tous les deux.

8.3. Additionneur complet


Un additionneur complet est un demi-additionneur qui comporte en plus en entrée une
retenue entrante qui viendrait d’un autre étage d’addition. Les entrées sont donc les deux
38 CHAPITRE II. ÉLÉMENTS DE LOGIQUE COMBINATOIRE

A B S R
0 0 0 0
0 1 1 0
1 0 1 0
1 1 0 1

Figure II.30. Table de vérité et schéma du demi-additionneur.

bits A et B et la retenue entrante RE ; les sorties sont le bit résultat S et la retenue sortante
RS. Il est facile de déterminer S et RS au vu de leur fonction, sans avoir besoin de leur table
de vérité :
• S est la somme des trois bits A, B et RE, et on sait depuis la section 3 que c’est un XOR
à trois entrées : S = A ⊕ B ⊕ RE.
• RS est la retenue de cette addition, et vaut 1 lorsqu’il y a au moins deux valeurs à 1 dans
le triplet (a, b, c) : c’est donc la fonction majorité déjà vue en section 3, RS = A·B + A
·RE + B·RE.

module FULLADDER(A,B,RE: S,RS)


S = /A*/B*RE+/A*B*/RE+A*/B*/RE+A*B*RE ;
RS = A*B + A*RE + B*RE ;
end module

Figure II.31. Additionneur complet : la somme est un XOR, et il y a retenue lorsqu’une


majorité des entrées vaut 1.

8.4. Additionneur ripple-carry


Un additionneur ripple carry réalise littéralement la méthode d’addition colonne par
colonne, en commençant par une demi-addition des poids faibles et en enchaînant ensuite
l’addition complète des bits suivants. Sur n bits, il nécessitera un demi-additionneur, et n −
1 additionneurs complets. Sur 4 bits par exemple, il prend la forme de la figure II.32.
La description en langage SHDL est très simple : il suffit de combiner les écritures d’un
demi-additionneur et de 3 additionneurs complets (figure II.33).
Ce type d’additionneur est simple, mais lent : il faut attendre que toutes les retenues se
soient propagées depuis le bit de poids le plus faible jusqu’au bit de poids le plus fort pour
que le calcul soit terminé. Or un calcul d’addition est un calcul combinatoire, donc qui peut
être effectué en théorie en une étape de propagation, sous forme d’une somme de minterms,
8. Circuits combinatoires réutilisables 39

Figure II.32. Addition ripple carry 4 bits. Le rang 0 est calculé avec un demi-additionneur,
et les autres rangs avec des additionneurs complets.

module ripplecarry4(a3,b2,a1,a0,b3,b2,b1,b0: s3,s2,s1,s0,carry)


halfadder(a0,b0:s0,c0);
fulladder(a1,b1,c0:s1,c1);
fulladder(a2,b2,c1:s2,c2);
fulladder(a3,b3,c2:s3,carry);
end module

module halfadder(a,b: s,cout)


s = /a*b+a*/b;
cout = a*b;
end module

module fulladder(a,b,cin: s,cout)


s = /a*/b*cin+/a*b*/cin+a*/b*/cin+a*b*cin;
cout = a*b + a*cin + b*cin;
end module

Figure II.33. Écriture SHDL d’un additionneur ripple-carry 4 bits.On combine un module
halfadder et 3 modules fulladder.

et sans tous ces termes intermédiaires que représentent les retenues. Mais les équations à
écrire deviennent très complexes dès le rang 3, et sont donc impraticables. Entre ces deux
extrêmes, il est nécessaire de concevoir des additionneurs de complexité raisonnable pour
un nombre de bits supérieur à 8, et avec des temps de propagation qui ne croissent pas
linéairement avec ce nombre de bits. On va voir en section 8.5 comment les additionneurs
carry lookahead parviennent à ce compromis.

8.5. Additionneur carry-lookahead


On a vu en section 8.1 que c’était la propagation de la retenue qui ralentissait le calcul
d’une somme dans un additionneur ripple carry. Si on calcule an..a0 + bn..b0, on a pour chaque
additionneur complet :

si = ai ⊕ bi ⊕ ci
40 CHAPITRE II. ÉLÉMENTS DE LOGIQUE COMBINATOIRE

ci = ai bi + ai ci + bi ci

Calculer s3 par exemple nécessite de calculer c3, donc s2, donc c2, etc.
La méthode carry lookahead part du constat qu’on peut facilement déterminer à
chaque étage s’il y aura une retenue ou non, en raisonnant en termes de retenue propagée et
de retenue générée. Considérons l’addition des deux termes suivants :

P P P G G P P
1 0 1 1 1 0 0 1
0 1 0 1 1 1 0 0

Un ’G’ est placé au dessus d’une colonne de rang i lorsque les termes à ajouter ai et
bi valent tous deux ’1’ : on est alors sûr qu’une retenue sera ’Générée’ pour l’étage suivant.
Sinon, un ’P’ est placé lorsqu’un des deux termes vaut ’1’ : si une retenue arrive depuis
l’étage précédent, alors cet étage va la ’Propager’, puisqu’on sera alors sûr qu’il y a au moins
deux ’1’ parmi les trois bits à additionner à ce rang. En raisonnant uniquement sur les ’P’
et les ’G’, on trouve facilement quels étages vont émettre une retenue. Ainsi sur l’exemple,
l’étage 0 peut propager une retenue, mais aucune n’a encore été générée. Les étages 3 et 4
génèrent une retenue, et les étages suivants 5, 6 et 7 la propagent.
Les termes Gi et Pi sont très faciles à calculer :
Gi = ai ·bi
Pi = ai + bi

Un module autonome peut prendre en entrées les valeurs des Gi et Pi de tous les rangs
et les utiliser pour calculer les retenues entrantes de chaque additionneur complet, selon le
schéma général de la figure II.34.
On notera que les retenues sortantes cout des additionneurs complets ne sont pas
exploitées, puisque c’est le module carry_lookahead qui s’occupe du calcul de toutes
les retenues.
Il faut maintenant réaliser un module carry_lookahead qui effectue ce calcul dans le
temps le plus court. L’idée générale est qu’il y a une retenue au rang i si Gi = 1 ou si Pi = 1
et si une retenue existe au rang i − 1 :

ci + 1 = Gi + ci ·Pi

On trouve :
c1 = G0 + cin·P0
c2 = G1 + c1·P1 = G1 + G0 ·P1 + cin·P0 ·P1
c3 = G2 + c2 ·P2 = G2 + G1·P2 + G0 ·P1·P2 + cin·P0 ·P1·P2
c4 = G3 + c3·P3 = G3 + G2 ·P3 + G1·P2 ·P3 + G0 ·P1·P2 ·P3 + cin·P0 ·P1·P2 ·P3
Il ne faut pas se leurrer : il y a là aussi une cascade dans les calculs, mais elle porte
sur des termes plus simples que si on avait opéré directement sur les terme ai et bi. Au delà
8. Circuits combinatoires réutilisables 41

Figure II.34. Schéma général d’un additionneur carry-lookahead.On calcule pour chaque
rang i deux bits Gi et Pi qui indiquent si cet étage va générer une retenue, ou en propager
une de l’étage précédent. Un module spécifique carry_lookahead calcule rapidement à
partir des Gi et Pi les retenues ci pour tous les rangs.

de 7 à 8 bits, les équations du module carry_lookahead deviennent trop complexes


pour être implémentées sous forme de sommes de termes, et des signaux intermédiaires
doivent être introduits. Si on considère la somme de produits comme unité de calcul et de
propagation (hypothèse d’implémentation dans un CPLD ou un FPGA), le calcul complet
d’une somme nécessite 1temps pour le calcul des Gi et Pi, 1temps pour le calcul des retenues,
et 1temps pour l’additionneur complet, soit un total de 3 temps de propagation, contre 4 pour
l’additionneur ripple carry. Le gain est faible, mais il augmente à mesure que le nombre de
bits grandit.
L’écriture complète d’un tel additionneur 4 bits en langage SHDL est donnée figure
II.35.

Association d’additionneurs carry-lookahead


La technique de l’additionneur carry-lookahead ne peut pas être utilisée au delà de 7
à 8 bits, mais on souhaite continuer à l’exploiter sur des additionneurs de plus grande taille.
On peut subdiviser les mots à additionner en groupes de 4 bits par exemple, et effectuer dans
chaque groupe une addition carry-lookahead. Se pose alors le problème de l’association de
ces groupes : va-t-on se contenter de les chaîner en ripple carry ?
On peut en fait appliquer à nouveau la technique carry-lookahead à l’échelle des
groupes. Chaque additionneur carry-lookahead sur 4 bits CLAi va générer deux signaux
GGi (group generate) et GPi (group propagate). GGi indiquera que le groupe génèrera
42 CHAPITRE II. ÉLÉMENTS DE LOGIQUE COMBINATOIRE

module full_adder(a, b, rin : s, rout)


s = /a*/b*rin+/a*b*/rin+a*/b*/rin+a*b*rin;
rout = a*b+a*rin+b*rin;
end module

module carry_lookahead(G3,P3,G2,P2,G1,P1,G0,P0,c0:c4,c3,c2,c1)
c1=G0+P0*c0;
c2=G1+P1*G0+P1*P0*c0;
c3=G2+P2*G1+P2*P1*G0+P2*P1*P0*c0;
c4=G3+P3*G2+P3*P2*G1+P3*P2*P1*G0+P3*P2*P1*P0*c0;
end module

module cla4(a3..a0,cin,b3..b0:s3..s0,cout)
G3..G0 = a3..a0 * b3..b0;
P3..P0 = a3..a0 + b3..b0;
carry_lookahead(G3,P3,G2,P2,G1,P1,G0,P0,cin:cout,c3,c2,c1);
full_adder(a3,b3,c3:s3,c4_);
full_adder(a2,b2,c2:s2,c3_);
full_adder(a1,b1,c1:s1,c2_);
full_adder(a0,b0,cin:s0,c1_);
end module

Figure II.35. Écriture SHDL d’un additionneur 4 bits carry-lookahead. On notera


l’écriture vectorielle pour les équations des termes Gi et Pi.

nécessairement une retenue et GPi indiquera que si une retenue arrive dans ce groupe
(par son entrée cin), elle sera propagée à travers lui au groupe suivant. La même méthode
est ainsi appliquée à l’échelle des groupes, chaque groupe jouant ici le même rôle que
jouait un étage de 1 bit dans l’additionneur carry-lookahead ordinaire. Le même module
carry_lookahead est d’ailleurs employé pour calculer rapidement les retenues c 4 , c
8, c12 et c16 qui sont envoyées aux différents groupes. La figure II.36 montre un exemple
d’addition , et la figure II.37 présente l’organisation générale d’un tel groupement.

#3 #2 #1 #0
cin=0
0000 0000 1010 0100
0000 0000 0101 1100
0000 0001 0000 0000
GG=0 GG=1
GP=1 GP=0

c8=1 c4=1

Figure II.36. Exemple d’addition 16 bits avec un additionneur group carry-lookahead.Le


groupe #0 génère une retenue,qui est propagée au travers du groupe #1, et dont l’effet vient
se terminer dans le groupe #2.

Il nous reste seulement à trouver les équations de GG et GP. GG indique qu’une retenue
est générée par le groupe, c’est à dire générée an niveau du bit 3, ou générée au niveau du
bit 2 et propagée au rang 3, etc. :
GG = G3+G2*P3+G1*P2*P3+G0*P1*P2*P3
8. Circuits combinatoires réutilisables 43

Figure II.37. Schéma général d’un groupement de 4 additionneurs 4 bits carry-lookahead.


Chaque additionneur fournit deux signaux GG et GP qui indiquent s’il génère ou propage
une retenue pour le groupe suivant.Un module carry_lookahead effectue le calcul rapide
des retenues entrantes de chaque groupe à partir de ces signaux.

GP indique qu’une retenue arrivant par cin sera propagée tout au long du groupe, c’est à
dire qu’à chaque rang de bit i il y a au moins un ’1’sur ai ou bi, c’est à dire encore qu’à chaque
rang i on a Pi = 1 :

GP = P0*P1*P2*P3

La figure II.39 donne l’ensemble complet des équations SHDL de cet additionneur. Les
modules carry_lookahead et fulladder sont les même qu’à la figure II.35, cla4 a été
légèrement modifié puisqu’il produit maintenant les signaux GG et GP.

8.6. Soustraction
Problématique de la soustraction
Comme l’addition, la soustraction peut être effectuée bit à bit, en commençant par le
bit de poids faible (figure II.38).

0 0
1 1 0
A[3..0] 1 0 0 1
B[3..0] 0 0 1 1
1 1 0 0
S[3..0] 0 1 1 0

Figure II.38. Soustraction en binaire bit à bit. Un bit d’emprunt est propagé des bits de
poids faibles vers les bits de poids forts.

Le bit qui est propagé de rang en rang est un bit d’emprunt : il vaut 1 lorsque ai est plus
44 CHAPITRE II. ÉLÉMENTS DE LOGIQUE COMBINATOIRE

module cla16(a15..a0,b15..b0,cin:s15..s0,cout)
cla4(a3..a0,b3..b0,cin:s3..s0,c4_,GG0,GP0);
cla4(a7..a4,b7..b4,c4:s7..s4,c8_,GG1,GP1);
cla4(a11..a8,b11..b8,c8:s11..s8,c12_,GG2,GP2);
cla4(a15..a12,b15..b12,c12:s15..s12,c16_,GG3,GP3);
carry_lookahead(GG3,GP3,GG2,GP2,GG1,GP1,GG0,GP0,cin:cout,c12,c8,c4);
end module

module cla4(a3,a2,a1,a0,b3,b2,b1,b0,cin:s3,s2,s1,s0,cout,GG,GP)
G3..G0 = a3..a0 * b3..b0;
P3..P0 = a3..a0 + b3..b0;
carry_lookahead(G3,P3,G2,P2,G1,P1,G0,P0:cout,c2,c1,c0);
full_adder(a3,b3,c2:s3,cout3);
full_adder(a2,b2,c1:s2,cout2);
full_adder(a1,b1,c0:s1,cout1);
full_adder(a0,b0,cin:s0,cout0);
GG = G3+G2*P3+G1*P2*P3+G0*P1*P2*P3;
GP = P0*P1*P2*P3;
end module

Figure II.39. Écriture SHDL d’un additionneur 16 bits carry-lookahead. Les modules
cla4, carry_lookahead et fulladder sont ceux utilisés dans la version 4 bits, cla4
ayant été légèrement modifié pour produire les signaux GG et GP.

petit que bi, ou plus exactement lorsque ai est plus petit que bi plus le bit d’emprunt de l’étage
précédent. Chaque rang de soustraction est un circuit combinatoire à 3 entrées et 2 sorties
appelé soustracteur complet dont la table de vérité est donnée figure II.40. On a noté EE
l’emprunt entrant, qui vient du rang précédent et ES l’emprunt sortant qui est passé au rang
suivant.

A B EE S ES
0 0 0 0 0
0 0 1 1 1
0 1 0 1 1
0 1 1 0 1
1 0 0 1 0
1 0 1 0 0
1 1 0 0 0
1 1 1 1 1

Figure II.40. Table de vérité d’un soustracteur complet. On soustrait B à A ; EE est


l’emprunt entrant et ES l’emprunt sortant.

On voit immédiatement que S est le XOR des trois entrées A, B et EE. L’emprunt sortant
ES vaut 1 lorsque A vaut 0 et qu’un des deux B ou EE vaut 1, ou lorsque A vaut 1 et que B et
− −
EE valent tous les deux 1. On trouve donc ES = A·B + A·EE + EE·B.
8. Circuits combinatoires réutilisables 45

Le schéma du soustracteur complet et son écriture SHDL sont donnés figure II.41.

module FULLSUB(A,B,EE: S,ES)


S = /A*/B*EE+/A*B*/EE+A*/B*/EE+A*B*EE;
ES = /A*B + /A*EE + B*EE;
end module

Figure II.41. Soustracteur complet : la différence est un XOR, et il y a retenue lorsqu’une


majorité des signaux (/A,B,EE) vaut 1.

Soustracteur ripple-borrow
Comme un additionneur ripple-carry, un soustracteur ripple-borrow est formé par
chaînage de plusieurs étages de soustracteurs complets. Comme lui il est très simple de
conception, mais présente aussi les mêmes inconvénients de lenteur dus à la propagation des
signaux d’emprunt. Les figures II.42 et II.43 montrent un additionneur 4 bits ripple-borrow
et l’écriture SHDL associée.

Figure II.42. Schéma général d’un soustracteur 4 bits ripple-borrow.

Transformation d’un additionneur en soustracteur


On peut faire la soustraction A − B en effectuant l’addition A + ( − B). − B est obtenu
en prenant le complément à 2 de B, c’est à dire le complément à 1 de B auquel on ajoute 1.
L’ajout +1 peut se faire en exploitant un signal de retenue entrante, comme on peut le voir
sur la figure II.44.
Cette figure montre un additionneur/soustracteur : si ADD/SUB vaut 0 il effectue une
addition ; s’il vaut 1 les n XORs effectuent le complément à 1 du vecteur B, et le forçage à
1 de la retenue entrante va conduire à ajouter 1, donc à construire le complément à 2 de B,
donc à réaliser une soustraction. On perd malheureusement le signal de retenue entrante ; la
retenue sortante cout par contre a bien le sens d’une retenue pour l’addition, et son inverse
46 CHAPITRE II. ÉLÉMENTS DE LOGIQUE COMBINATOIRE

module rippleborrow4(a3..a0,b3..b0,ee: s3..s0,es)


fullsub(a0,b0,ee:s0,e1);
fullsub(a1,b1,e1:s1,e2);
fullsub(a2,b2,e2:s2,e3);
fullsub(a3,b3,e3:s3,es);
end module

module fullsub(a,b,ee:s,es)
s = /a*/b*ee+/a*b*/ee+a*/b*/ee+a*b*ee;
es = /a*b + /a*ee + b*ee;
end module

Figure II.43. Équations SHDL d’un soustracteur 4 bits ripple-borrow.

Figure II.44. Additionneur/soustracteur réalisé à partir d’un additionneur. Lorsque l’en-


trée ADD/ SUB vaut 0 l’addition A + B est réalisée ; lorsqu’elle vaut 1, les XORs calculent
le complément à 1 de B et le forçage à 1 de la retenue entrante cin créé le complément à 2,
d’où la soustraction.

le sens d’un emprunt, d’où l’inversion finale commandée par le signal ADD/SUB.
La figure II.45 donne le code SHDL associé pour un additionneur/soustracteur 16
bits ; elle reprend le module additionneur carry-lookahead 16 bits cla16 étudié à la section
précédente.

module addsub16(a[15..0],b[15..0],addsub: s[15..0],cout)


bb[15..0] = /addsub*b[15..0] + addsub*/b[15..0];
cla16(a[15..0],bb[15..0],addsub:s[15..0],co);
cout=/addsub*co+addsub*/co;
end module

Figure II.45. Équations SHDL d’un additionneur/soustracteur 16 bits. On réutilise


l’additionneur carry-lookahead 16 bits étudié auparavant.
47

8.7. Multiplicateur systolique


Problématique de la multiplication
On souhaite multiplier deux nombres de n bits non signés. Le résultat est sur 2n bits,
puisque la multiplication des deux plus grands nombres sur n bits donne :

(2 − 1)(2 − 1) = 22n − 2·2 + 1 < 22n − 1


n n n

La problématique est la même que pour l’addition : puisqu’il s’agit d’un calcul
combinatoire, il peut se faire en théorie sous forme d’une somme de minterms, en une étape
de propagation (en considérant une fois encore que l’unité de propagation est la somme
de produit). Mais bien sûr en pratique, les équations à implémenter seraient beaucoup trop
complexes. En binaire, la méthode la plus directe pour effectuer une multiplication consiste
à la poser comme à l’école (figure II.46).

0 1 1 0
ai
X 1 1 0 1
0 1 1 0 bi
0 0 0 0
0 1 1 0
0 1 1 0 ai bi
1 0 0 1 1 1 0
Figure II.46. Exemple de multiplication de nombres non signés de 4 bits.

Cette opération est plus facile à faire en binaire qu’en décimal, car il n’y a pas à
connaître ses tables de multiplication ! Plus précisément, lorsqu’on multiplie un chiffre ai
avec un chiffre bi, on produit (c’est le cas de le dire) ai bi (figure II.46). Il reste ensuite à faire
la somme des produits partiels ainsi obtenus.
Quand on fait cette somme à la main, on la fait colonne par colonne, en commençant
par la colonne la plus à droite, et on additionne à la fois tous les chiffres qui apparaissent
dans une colonne et la ou les retenues qui proviennent de la colonne précédente. Il peut en
effet y avoir une retenue supérieure à 1 lors de la sommation des chiffres d’une colonne,
contrairement au cas de l’addition.
L’idée du multiplicateur systolique (matriciel) consiste à réaliser cette somme des
produits partiels dans une matrice de cellules qui a la même forme que les rangées à ajouter
(figure II.47). Chaque fois qu’une cellule produit une retenue, elle est passée directement à
la colonne immédiatement à gauche, un rang plus bas (situation (a)). Cette technique permet
de ne pas laisser les retenues s’accumuler à chaque colonne, en les traitant immédiatement à
un niveau local. Chaque cellule doit faire l’addition de trois termes : le produit partiel aibi, la
somme du niveau précédent, et la retenue qui provient de la cellule en haut à droite (situation
(b)). Un additionneur complet permet d’effectuer cette somme, qui est passée à la cellule
immédiatement en bas, et de calculer une retenue à passer à la cellule en bas à gauche (figure
II.48).
Le schéma général d’un tel multiplicateur est donné figure II.49. Les cellules ont
48

0 1 1 1 0 1 1 1
0 1
X 1 0 1 1 X 1 0 1 1
1
0 1 1 1 0 1 1 1
1
0 1 1 1 0 1 1 1
1 1 0 1 1
0 0 0 0 0 0 0 0
1 1 1
0 1 1 1 1
0 1 1 1
1 1 1 1
1 0 0 1 1 0 1 1 0 0 1 1 0 1

(a) (b)

Figure II.47. (a) Les retenues peuvent être propagées à un niveau local, et non à l’échelle
de toute la colonne. (b) entrées et sorties au niveau d’une cellule.

0 1 1

0 1 a b cin

1 full adder
1
0 s cout

0 1

Figure II.48. Multiplicateur systolique : chaque cellule est un additionneur complet.

la même disposition que la somme des termes partiels, avec un recadrage à droite. On
remarquera la dernière rangée, qui fournit les bits de poids forts du résultat final s7..s4. Les
bits de poids faibles s3..s0 proviennent quant à eux des cellules de la colonne de droite.
Le texte SHDL correspondant à ce schéma est donné figure II.50.
On voit facilement que ce multiplicateur 4x4 nécessite 6 temps de propagation (unité :
somme de termes). De façon plus générale, un multiplicateur systolique n bits x n bits
nécessite n+2 temps de propagation. C’est donc une méthode assez efficace, qui est facile à
implémenter dans des circuits matriciels tels que CPLDs et FPGAs.

8.8. Division entière

Problématique de la division
Comme pour la multiplication, on va essayer de concevoir un réseau de cellules qui
réalise une division non signée en utilisant la méthode euclidienne. Prenons un exemple
(figure II.51).
On a réalisé ici une division non signée 8 bits / 4 bits -> 8 bits. Le dividende est p7..p0
49

Figure II.49. Schéma général d’un multiplicateur systolique 4 bits x 4 bits vers 8 bits.

= 11010101012 = 213 et le diviseur est d3..d0 = 10112 = 11; on trouve un quotient q7..q0
= 000100112 = 19 et un reste r3..r0 = 01002 = 4.
On voit que le nombre d’étages de cette division est égal à la largeur du dividende, 8
dans l’exemple. À chaque étape, on effectue une soustraction entre un terme courant et le
diviseur. Au départ, ce terme courant est le poids fort du dividende complété de 4 zéros à
gauche. Le diviseur est également complété d’un zéro à gauche et c’est une soustraction sur
5 bits qui est réalisée. Il y a alors deux cas :

• la soustraction donne un résultat négatif, signalé par le bit d’emprunt à 1; on ne doit


alors pas prendre son résultat pour le terme courant, mais le laisser tel quel. On lui
rajoutera à droite un bit supplémentaire du dividende, à l’étage suivant. On ajoute un
zéro à droite au quotient.
50

module multsyst(a[3..0], b[3..0]: s[7..0])

// première rangée ; produit s0


p30=a3*b0; p20=a2*b0; p10=a1*b0; p00=a0*b0;
fulladder(0,0,p30:s30,c30);
fulladder(0,0,p20:s20,c20);
fulladder(0,0,p10:s10,c10);
fulladder(0,0,p00:s0,c00);

// deuxième rangée ; produit s1


p31=a3*b1; p21=a2*b1; p11=a1*b1; p01=a0*b1;
fulladder(c30,0,p31:s31,c31);
fulladder(c20,s30,p21:s21,c21);
fulladder(c10,s20,p11:s11,c11);
fulladder(c00,s10,p01:s1,c01);

// troisième rangée ; produit s2


p32=a3*b2; p22=a2*b2; p12=a1*b2; p02=a0*b2;
fulladder(c31,0,p32:s32,c32);
fulladder(c21,s31,p22:s22,c22);
fulladder(c11,s21,p12:s12,c12);
fulladder(c01,s11,p02:s2,c02);

// quatrième rangée ; produit s3


p33=a3*b3; p23=a2*b3; p13=a1*b3; p03=a0*b3;
fulladder(c32,0,p33:s33,c33);
fulladder(c22,s32,p23:s23,c23);
fulladder(c12,s22,p13:s13,c13);
fulladder(c02,s12,p03:s3,c03);

// rangée du total final pour s7..s4


fulladder(0,c33,0:s7,c3x);
fulladder(s33,c23,0:s6,c2x);
fulladder(s23,c13,0:s5,c1x);
fulladder(s13,c03,0:s4,c0x);
end module

Figure II.50. Écriture SHDL d’un multiplicateur systolique 4 bits x 4 bits.

• la soustraction donne un résultat positif, signalé par le bit d’emprunt à 0; on remplace


le terme courant par le résultat de cette soustraction, et on lui ajoutera à droite un bit
supplémentaire du dividende, à l’étage suivant. On ajoute un 1 à droite au quotient.

Le reste r3..r0 est la valeur du terme courant après la dernière étape.

Structure du diviseur
On souhaite ainsi réaliser un diviseur 2n bits / n bits -> 2n bits; on va prendre ici comme
dans l’exemple n = 4, mais la technique est bien sûr généralisable à toute valeur de n. La
figure II.53 montre la structure nécessaire du diviseur.
À chaque étage on réalise la soustraction entre le terme issu de l’étage précédent et
d3,..d0. On utilise pour cela des soustracteurs 5 bits sans retenue entrante, et avec une
retenue sortante; on a vu aux sections précédentes comment les réaliser.
À chaque étage il faut aussi construire le terme courant, en fonction des deux cas
examinés dans la discussion précédente et déterminés par la valeur du bit d’emprunt de
la soustraction. On devra pour cela utiliser des multiplexeurs dont la commande sera le
bit d’emprunt.
La figure II.54 donne le code SHDL complet associé à ce diviseur.
51

p7 p0 d3 d0
0 0 0 0 1 1 0 1 0 1 0 1 1 0 1 1
0 1 0 1 1
q7=0 1 0 0 0 1 0 0 1 1
0 0 1 1 0 q7 q0
0 0 0 1 1
0 1 0 1 1
q6=0 1 1 1 0 0 0
0 0 1 1 0
0 1 0 1 1
q5=0 1 1 1 0 1 1
0 1 1 0 1
0 1 0 1 1
q4=1 0 0 0 0 1 0 0
0 1 0 1 1
q3=0 1 1 1 0 0 1
0 1 0 0 1
0 1 0 1 1
q2=0 1
1 1 1 1 0
1 0 0 1 0
0 1 0 1 1
q1=1 0 0 0 1 1 1 1
0 1 0 1 1
q0=1 0 0 0 1 0 0
r3 r0

Figure II.51. Exemple de division euclidienne en binaire.

8.9. Comparateurs
L’opération de comparaison entre nombres entiers est fondamentale dans un
ordinateur. Si on souhaite comparer deux nombres de n bits, l’interface générale qu’on peut
utiliser est celle de la figure II.52.

Figure II.52. Interface d’un comparateur sur n bits. Une sortie indique que A et B sont
égaux, une autre indique que A > B.

Une sortie SUP indique que A > B, l’autre EQ indique que A = B. Avoir ces deux sorties
permet de tout savoir sur les positions respectives de A et B :

• A > B : SUP = 1.
52

Figure II.53. Structure du diviseur non signé 8 bits / 4 bits -> 8 bits. À chaque étage on
teste si on peut soustraire le diviseur au terme courant.Les bits du quotient sont les inverses
des bits d’emprunt des soustractions; le reste est la valeur du terme courant après la
dernière étape.

• A ≥ B : SUP = 1 ou EQ = 1.

• A = B : EQ = 1

• A < B : SUP = 0 et EQ = 0.

• A ≤ B : SUP = 0.
Il faut maintenant savoir si les nombres que l’on compare sont des nombres entiers naturels,
ou des nombres relatifs codés en complément à 2. En effet, si A = 1111 et B = 0101, une
53

module div8(p7..p0,d3..d0:q7..q0,r3..r0)
// soustracteur
sub5(0,0,0,0,p7,0,d3,d2,d1,d0:x74,x73,x72,x71,x70,nq7);
q7=/nq7;
// multiplexeur
s74=nq7*z+/nq7*x73;
s73=nq7*z+/nq7*x73;
s72=nq7*z+/nq7*x72;
s71=nq7*z+/nq7*x71;
s70=nq7*p7+/nq7*x70;

sub5(s73,s72,s71,s70,p6,0,d3,d2,d1,d0:x64,x63,x62,x61,x60,nq6);
q6=/nq6;
s64=nq6*s73+/nq6*x64;
s63=nq6*s72+/nq6*x63;
s62=nq6*s71+/nq6*x62;
s61=nq6*s70+/nq6*x61;
s60=nq6*p6+/nq6*x60;
...
// code analogue pour q4,...,q1
...
sub5(s13,s12,s11,s10,p0,0,d3,d2,d1,d0:x04,x03,x02,x01,x00,nq0);
q0=/nq0;
s04=nq0*s13+/nq0*x04;
r3=nq0*s12+/nq0*x03;
r2=nq0*s11+/nq0*x02;
r1=nq0*s10+/nq0*x01;
r0=nq0*p0+/nq0*x00;
end module

Figure II.54. Écriture SHDL d’un diviseur non signé 8 bits / 4 bits -> 4 bits

comparaison non signée va donner A > B (car A = 15 et B = 5), alors qu’une comparaison
signée donnera A < B (car A = − 1 et B = + 5).
Comparateur non signé
Supposons que A et B soient codés en binaire pur, par exemple sur 4 bits. On commence
d’abord par comparer leurs poids forts A3 et B3 ; si celui de A est plus grand que celui de B,
on peut conclure SUP = 1 ; sinon SUP ne peut valoir 1 que si A3 = B3 et que si A2..A0 > B
2..B0. On a ainsi une définition récursive de SUP :
SUP = (A3 > B3) + (A3 = B3)·(A2..A0 > B2..B0)
SUP = (A3 > B3) + (A3 = B3)·((A2 > B2) + (A2 = B2)·(A1..A0 > B1..B0))

SUP = (A3 > B3) + (A3 = B3)·((A2 > B2) + (A2 = B2)·((A1 > B1) + (A1 = B1)·(A0
> B0)))

Ai > B1 correspond en fait à l’unique situation Ai = 1 et Bi = 0 et s’exprime donc Ai Bi. Pour
Ai = Bi, on peut utiliser le fait qu’un XOR est un opérateur de différence, et donc que son
− −−
inverse est un opérateur de coïncidence : (Ai = Bi ) = Ai ⊕ Bi = Ai ·Bi + Ai ·Bi
− − − − − − −
SUP = A3B3 + A3 ⊕ B3·(A2B2 + A2 ⊕ B2·(A1B1 + A1 ⊕ B1·A0B0))

Le code SHDL correspondant est :


54

module cmpu4(a[3..0], b[3..0]: sup, eq)


sup=a[3]*/b[3]+eq3*sup2;
eq3=a[3]*b[3]+/a[3]*/b[3];
sup2=a[2]*/b[2]+eq2*sup1;
eq2=a[2]*b[2]+/a[2]*/b[2];
sup1=a[1]*/b[1]+eq1*a[0]*/b[0];
eq1=a[1]*b[1]+/a[1]*/b[1];
eq0=a[0]*b[0]+/a[0]*/b[0];
eq=eq3*eq2*eq1*eq0;
end module

Comparateur signé
Une comparaison signée est un peu plus délicate qu’une comparaison non signée ;
l’essentiel de la comparaison se joue en examinant les signes an − 1 et bn − 1 des termes A =
an − 1..a0 et B = bn − 1..b0 à comparer :

• si A < 0 (an − 1 = 1) et B > 0 (bn − 1 = 0) alors la comparaison est immédiate : SUP = 0 et


EQ = 0.

• si A > 0 (an − 1 = 0) et B < 0 (bn − 1 = 1) alors la comparaison est immédiate : SUP = 1 et


EQ = 0.

• si A > 0 (an − 1 = 0) et B > 0 (bn − 1 = 0) alors il suffit de comparer les n − 1 bits de


poids faibles.

• si A < 0 (an − 1 = 1) et B < 0 (bn − 1 = 1) alors il suffit également de comparer les n − 1 bits
de poids faibles. En effet, les nombres de l’intervalle [2n − 1,0] s’écrivent en complément
à 2 : [1000..000, 1000..001, 1111..111] et on voit que les n − 1 bits de poids faible
évoluent de façon croissante.

On a représenté sur la figure II.56 un comparateur signé sur 5 bits utilisant un compteur non
signé de 4 bits. Un multiplexeur 4 vers 1 sépare les 4 situations décrites.

module cmps5(a[4..0],b[4..0]: sup, eq)


cmpu4(a[3..0], b[3..0]: sup1, eq1);
mux4(sup1, 1, 0, sup1, a4, b4: sup);
eq=a4*b4*eq1+/a4*/b4*eq1;
eq=eq4*eq1;
end module

Figure II.55. Écriture SHDL d’un comparateur signé sur 5 bits.

Assemblage de comparateurs non signés


Il est facile de chaîner 2 comparateurs non signés de n et m bits pour former un
comparateur non signé de n + m bits (figure II.57).
On compare d’abord les n bits de poids forts ; si ceux de A sont plus grands que ceux de
55

Figure II.56. Comparateur signé sur 5 bits. Si les signes a4 et b4 sont différents, la com-
paraison est immédiate ; s’ils égaux on compare en non signé les 4 bits de poids faibles.

Figure II.57. Association de deux comparateurs n bits non signés. Le résultat forme un
comparateur 2n bits avec le même interface.

B, le SUP final est asserté au travers du OU. Si les poids forts de A et ceux de B sont égaux, on
compare les m bits de poids faibles et on conclue. La sortie finale EQ est quant à elle assertée
lorsque les deux sorties EQ sont assertées.On a représenté en figure II.58 le code SHDL d’un
comparateur non signé sur 10 bits, formé par l’assemblage de deux comparateurs non signés
de 5 bits.
La sortie de module EQ a été essentielle ici pour permettre d’associer les circuits.
Par ailleurs, le nouveau module formé expose le même interface que ses parties : A et B
en entrées, et SUP et EQ en sorties, ce qui permet à nouveau et récursivement d’associer
ces circuits. Comme dans les disciplines logicielles de l’informatique, il est intéressant de
trouver le bon interface à un module, qui va permettre une bonne réutilisation.
56

module cmpu(a[9..0], b[9..0]: sup, eq)


cmpu5(a[9..5], b[9..5]: sup1, eq1);
cmpu5(a[4..0], b[4..0]: sup2,eq2);
eq=eq1*eq2;
sup=sup1+eq1*sup2;
end module

Figure II.58. Écriture SHDL d’un comparateur non signé sur 10 bits formé par association
de deux comparateurs non signés de 5 bits.

9. Exercices corrigés

9.1. Exercice 1 : simplifications algébriques

Énoncé
Simplifier algébriquement :
− − − −
1. F1 = ABC + AC + AB + A B C
−− −
2. F2 = ABC D + ABD + AC + BC + AC
− − − − −
3. F3 = A + BC + ADE + ABCDE + BCDE + ACD

Solution
− − − −
1. F1 = ABC + AC + AB + A B C
Ici des termes disparaissent car ils sont plus spécifiques que d’autres. Par exemple
− −
ABC est plus spécifique que AB et le premier disparaît au profit du deuxième. De
− − −
la même façon, ABC est plus spécifique que AC et disparaît. On a : F1 = A B +

AC
−− −
2. F2 = ABC D + ABD + AC + BC + AC
−− −
F2 = AB(C D + D) + AC + BC + AC
−− −
Avec le théorème d’absorption, C D + D = C + D, donc :
− −
F2 = ABD + ABC + AC + BC + AC

Par ailleurs, AC + AC = C, donc :

F2 = ABD + ABC + BC + C

F2 = ABD + ABC + C

On peut encore utiliser le théorème d’absorption sur ABC + C :
F2 = ABD + AB + C
F2 = AB + C
− − − − −
3. F3 = A + BC + BCDE + ACD (A inclue ADE)
9. Exercices corrigés 57

− − − −
F3 = A + BC + BCDE + CD (absorption entre A et ACD)
− −−
F3 = A + BC + D(BC E + C)
− − −
F3 = A + BC + D(BE + C) (absorption de C)
− −
F3 = A + BC + BDE + CD
Avec des tables de Kanaugh, des simplifications non adjascentes donneraient le
même résultat.

9.2. Exercice 2 : utilisation des tables de Karnaugh

Énoncé
Concevoir un circuit qui détecte la présence d’un chiffre décimal (entre 0 et 9) sur ses
entrées A, B, C, D.

Solution
La table de vérité de ce détecteur donne ’1’ pour les valeurs de (0,0,0,0) à (1,0,0,1), et
’0’ pour les valeurs suivantes (figure II.59).

A B C D S
0 0 0 0 1
0 0 0 1 1
0 0 1 0 1
0 0 1 1 1
0 1 0 0 1
0 1 0 1 1
0 1 1 0 1
0 1 1 1 1
1 0 0 0 1
1 0 0 1 1
1 0 1 0 0
1 0 1 1 0
1 1 0 0 0
1 1 0 1 0
1 1 1 0 0
1 1 1 1 0

Figure II.59. Table de vérité d’un détecteur de chiffre décimal.

On peut maintenant dessiner la table de Karnaugh correspondante, et chercher le plus


petit nombre de regroupements qui recouvrent tous les ’1’ (figure II.60).

Il y a deux regroupements, ce qui donne l’expression simplifiée :


58 CHAPITRE II. ÉLÉMENTS DE LOGIQUE COMBINATOIRE

B.C.D
A,B
C,D 0,0 0,1 1,1 1,0
0,0 1 1 1
0,1 1 1
1,1 1 1
1,0 1 1

A
Figure II.60. Table de Karnaugh avec les regroupements pour le détecteur de chiffres dé-
cimaux.

− −−
S = A + BC D

On peut trouver le même résultat de façon algébrique :


− −
S = ((A, B, C, D) ≤ 8) + ((A, B, C, D) = 9)) = A + ABCD
Par absorption de A :
− −
S = A + BCD

9.3. Exercice 3 : analyse inductive des tables de vérité

Énoncé
Concevoir les transcodeurs suivants :
1. Binaire pur vers Gray réfléchi.

2. Gray réfléchi vers binaire pur.

Solution
La figure II.61montre la table de vérité qui relie une valeur binaire (X,Y,Z,T) à un code
de Gray (A,B,C,D). Le code de Gray a été construit selon la méthode récursive décrite à la
section 5.4.
On pourrait dessiner des tables de Karnaugh, mais elles ne donneraient rien ici. Par
ailleurs on va voir que les codes de Gray sont très étroitement associés à l’opérateur XOR,
et on va essayer de deviner les relations directement.

1. Binaire pur vers Gray réfléchi.


On voit tout de suite que A = X. B est égal à Y pendant la première moitié de la

table, puis il est égal à Y. ’Être dans la première moitié de la table’ est gouverné par X ;
en résumé B est égal à Y avec une inversion commandée par X. C’est XOR l’opérateur
d’inversion commandée, donc : B = X ⊕ Y.
De façon analogue, on remarque que C est égal à Z lorsque Y vaut 0, et est égal à
9. Exercices corrigés 59

X Y Z T A B C D
0 0 0 0 0 0 0 0
0 0 0 1 0 0 0 1
0 0 1 0 0 0 1 1
0 0 1 1 0 0 1 0
0 1 0 0 0 1 1 0
0 1 0 1 0 1 1 1
0 1 1 0 0 1 0 1
0 1 1 1 0 1 0 0
1 0 0 0 1 1 0 0
1 0 0 1 1 1 0 1
1 0 1 0 1 1 1 1
1 0 1 1 1 1 1 0
1 1 0 0 1 0 1 0
1 1 0 1 1 0 1 1
1 1 1 0 1 0 0 1
1 1 1 1 1 0 0 0

Figure II.61. Binaire ↔ code de Gray.


Z lorsque Y vaut 1, donc : C = Y ⊕ Z. On trouve de même D = Z ⊕ T.

2. Gray réfléchi vers binaire pur.


On a bien sûr X = A, et on voit aussi que Y = A ⊕ B. Par contre pour Z, il est bien

égal à C ou C, mais l’inversion n’est pas simplement commandée par B. Il y a inversion
durant le deuxième et le troisième quart de la table : cela évoque un XOR. Il y a en fait
inversion lorsque A ⊕ B = 1, donc : Z = A ⊕ B ⊕ C. Par induction on devine, et on
vérifie ensuite, que T = A ⊕ B ⊕ C ⊕ D.

9.4. Exercice 4 : circuit incrémenteur

Énoncé
Concevoir un circuit combinatoire prenant en entrée un nombre binaire a3 … a0
et donnant en sortie b3 …b0 la valeur d’entrée incrémentée de 1. Écrire le code SHDL
correspondant.

Solution
L’algorithme de base de l’incrémentation a été donné en section 4. Le bit de poids faible
b0 est toujours inversé par rapport à a0. Ensuite, bn est l’inverse de an si et seulement si tous
les bits qui sont à la droite de an valent 1, soit lorsque an − 1…a0 = 1. On a donc une inversion
commandée par cette valeur, et on a vu en section 4 que le XOR est la porte adaptée à cette
opération :

bn = an ⊕ (an − 1…a0)
60 CHAPITRE II. ÉLÉMENTS DE LOGIQUE COMBINATOIRE

Finalement, sur 4 bits :


b0 = a0
b1 = a1 ⊕ a0
b2 = a2 ⊕ (a1a0 )
b3 = a3 ⊕ (a2 a1a0 )
Exprimé sous forme de sommes de termes :
b0 = a0
b =a−
1
a +−
1 0
aa 1 0
b2 = −
a2 a1a0 + a2 −
a1a0 = −
a2 a1a0 + a2 (−
a1 + −
a0 ) = −
a2 a1a0 + a2 −
a1 + a2 −
a0
b =−
3
aaaa +a−
3 2 1 0
aaa =−
3 2 1 0
aaaa +a−
3 2 1 0
a +a−
3 2
a +a−
3 1
a
3 0
Finalement, le module SHDL correspondant est :

module incr(a3..a0: b3..b0)


b0=/a0;
b1=/a1*a0+a1*/a0;
b2=/a2*a1*a0+a2*/a1+a2*/a0;
b3=/a3*a2*a1*a0+a3*/a2+a3*/a1+a3*/a0;
end module
Chapitre III

Éléments de logique séquentielle

1. Définition
Un circuit est dit séquentiel, si les valeurs de ses sorties ne dépendent pas que des
valeurs de ses entrées.
C’est donc le contraire d’un circuit combinatoire, dont les valeurs des sorties ne
dépendaient que de celles de ses entrées, avec une propagation sans retour arrière des
signaux des entrées vers les sorties.
À l’inverse, les sorties d’un circuit séquentiel dépendent non seulement des valeurs
des entrées au moment présent, mais aussi d’un état interne qui dépend de l’historique des
valeurs d’entrées précédentes. En vertu de ce que nous avions démontré pour les circuits
combinatoires, cela implique un rebouclage vers l’arrière de certains signaux internes au
circuit (figure III.1).

entrées sorties

...
...

Figure III.1. Circuit séquentiel : certains signaux internes ou sorties rebouclent en


arrière.

Ce type de circuit peut donner lieu à des fonctionnements très complexes ; il peut
également être instable dans certaines configurations. Plus encore que pour les circuits
combinatoires, des méthodes pour maîtriser leur complexité sont nécessaires. Une des voies
pour la simplification de leur conception consiste à synchroniser les changements d’états sur
les fronts d’une horloge unique et globale, et cela conduit à l’étude des circuits séquentiels
dits synchrones purs, qu’on étudiera principalement dans ce livre.

61
62 CHAPITRE III. ÉLÉMENTS DE LOGIQUE SÉQUENTIELLE

2. Latch RS
La notion de circuit séquentiel avec ses états internes incorpore la notion de mémoire
(retour sur son passé). La cellule mémoire la plus simple est le latch RS (RS étant les initiales
de Reset-Set), parfois appelé bistable (figure III.2.

module latch_rs(s,r: q)
q=/nq*/r ;
nq=/q*/s ;
end module

Figure III.2. Latch RS, cellule mémoire de 1 bit.

Ce circuit a clairement des connexions qui reviennent en arrière. Il s’agit d’une cellule
de mémorisation de 1 bit, dont le mode d’emploi est le suivant :

1. la cellule est en mode ’lecture’ lorsque les entrées R et S sont toutes les deux à 0 ; la

paire Q, Q est alors nécessairement dans un des deux états (0, 1) ou (1, 0), car on peut
constater que ces deux états, et seulement eux, s’auto-entretiennent lorsque R et S
valent 0.

2. pour mémoriser 0 (c’est à dire que les sorties soient dans l’état Q, Q = (0, 1) lorsque
la cellule reviendra dans le mode ’lecture’), il faut appliquer un 1 sur R (reset), puis le
remettre à 0. Quelqu’ait été son état antérieur, la cellule se retrouve alors dans l’état

auto-stable Q, Q = (0, 1).

3. pour mémoriser 1 (c’est à dire que les sorties soient dans l’état Q, Q = (1, 0) lorsque
la cellule reviendra dans le mode ’lecture’), il faut appliquer un 1 sur S (set), puis le
remettre à 0. Quelqu’ait été son état antérieur, la cellule se retrouve alors dans l’état

auto-stable Q, Q = (1, 0).

Ce circuit est bien séquentiel : ses sorties ne dépendent pas uniquement de ses entrées. La
notion d’état interne est ici clairement présente sous la forme d’un bit mémorisé.

Graphe d’états
Les modes de fonctionnement décrits précédemment peuvent être résumés dans le
graphe de la figure III.3.
Un tel graphe est appelé graphe d’état du circuit ; il synthétise complètement son
fonctionnement. Les deux noeuds correspondent aux deux états stables du circuit ; les arcs
montrent les passages du circuit d’un état vers un autre lors d’un changement des valeurs
des entrées. La valeur de la sortie Q est associée à chacun des deux états : 0 pour l’état a
et 1 pour l’état b. On ne représente que les états stables du circuit, et non les configurations
intermédiaires fugitives entre deux états.
2. Latch RS 63

Q=0 (R, S) = (0, 0)


(R, S) = (1, 0)
a

(R, S) = (0, 1)
(R, S) = (1, 0)

(R, S) = (0, 1) (R, S) = (0, 0)


b

Q=1
Figure III.3. Graphe d’états d’un latch RS. Les noeuds correspondent aux états stables du
circuit, et les arcs représentent les transitions entre les états.

3. Fronts et niveaux ; signaux d’horloge


Le niveau d’un signal, c’est sa valeur 0 ou 1. On parle de front lors d’un changement
de niveau : front montant lorsque le signal passe de 0 à 1, front descendant lorsqu’il passe
de 1 à 0. En pratique, ces transitions ne sont pas tout à fait instantanées, mais leur durée est
très inférieure aux temps de propagation des portes (figure III.4).

niveau ’1’

1 1
niveau ’0’

0 0

t t
front montant : front descendant :
dérivée très positive dérivée très négative

Figure III.4. Fronts et niveaux d’un signal réel, et leur équivalent logique simplifié.

Une horloge est un signal périodique, produit à partir d’un quartz ou d’un réseau RC.
Il produit donc une succession périodique de fronts montants et/ou descendants.
Comme en physique ou en mathématiques, on emploie les termes de fréquence et de
période pour qualifier la chronologie d’un signal d’horloge ; elle a généralement une forme
carrée, mais ce n’est pas obligatoire.

4. Circuits séquentiels synchrones et asynchrones : définitions


64 CHAPITRE III. ÉLÉMENTS DE LOGIQUE SÉQUENTIELLE

Circuits asynchrones purs


On appelle circuits séquentiels asynchrones purs des circuits séquentiels sans signaux
d’horloge, et dans lesquels ce sont les changements de valeur des entrées qui sont à la source
des changements d’états. Le latch RS est un exemple de circuit asynchrone pur : il n’est
pas gouverné par une horloge, et ce sont les changements des entrées qui provoquent les
changements d’état.

Circuits synchrones purs


On appelle circuit séquentiels synchrones purs des circuits séquentiels qui possèdent
une horloge unique, et dont l’état interne se modifie précisément après chaque front
(montant ou descendant) de l’horloge.
Les circuits séquentiels synchrones purs sont plus simples à étudier et à concevoir
que les circuits séquentiels asynchrones purs, car le moment de la transition est défini de
l’extérieur et sur un seul signal. Ils peuvent par contre être moins rapides qu’un équivalent
asynchrone, et consommer plus d’énergie. En effet, on se rappelle (section 3) que les
circuits CMOS consomment essentiellement du courant lors des changements d’états.
Un circuit séquentiel synchrone consommera ainsi du courant à chaque front d’horloge,
contrairement à un circuit asynchrone pur. Cet effet est toutefois à relativiser avec certains
circuits CMOS dont on a beaucoup abaissé la tension d’alimentation, et pour lesquels le
courant de fuite (consommé au repos) est proche du courant de commutation (consommé
lors des changements).

Autres circuits séquentiels


Les circuits séquentiels qui ne sont, ni synchrones purs, ni asynchrones purs, n’ont
pas de dénomination particulière. Ils sont souvent l’union de plusieurs sous-ensembles
qui sont chacun gouvernés de façon synchrone par une horloge, mais l’interface entre ces
sous-domaines est souvent la cause de problèmes de fiabilité. On n’étudiera pas de tels
circuits dans cet ouvrage.

5. Graphes d’états
Considérons le fonctionnement du circuit séquentiel synchrone de la figure III.5,
détecteur de la séquence ’1,0,1’ :

E S

CLK

Figure III.5. Détecteur de séquence 1,0,1.

Une suite de chiffres binaires arrive sur E, et les instants d’échantillonnage (moments
où la valeur de E est prise en compte) sont les fronts montants de CLK. On souhaite que la
sortie S vaille 1 si et seulement si les deux dernières valeurs de E sont : 1,0,1.
Cette spécification est en fait imprécise, car elle ne dit pas exactement à quel moment
par rapport à l’horloge CLK la sortie doit prendre sa valeur. Il y a deux possibilités :
5. Graphes d’états 65

1. la sortie ne dépend que de l’état interne du circuit, et ne peut changer que juste après
chaque front d’horloge. Elle ne pourra changer ensuite qu’au prochain front d’horloge,
même si les entrées changent entre temps. On appelle schéma de MOORE une telle
conception, et elle conduit au graphe de MOORE de la figure III.6.

S=0
0 e
0
S=0 ...100
1
c 0 S=1
0 ...010 f 0
S=0 S=0 1
a 1 ...101
0 b
1 1 S=0
...000 ...001 1
S=0 0 g
d ...110
0
...011
1 S=0
h 1
...111

Figure III.6. Détecteur de séquence 1,0,1 de type Moore, non simplifié. Chaque état est
associé à la réception d’une certaine séquence des 3 derniers bits. Seul l’état g provoque la
sortie S = 1.

La valeur de la sortie S est clairement associée aux états, et ne peut donc changer
qu’avec eux. Les changements d’état se produisent à chaque front de l’horloge CLK,
qui n’est pas représentée sur la graphe, mais dont la présence est implicite. Un arc
libellé ’0’ par exemple indique un changement d’état lorsque E = 0.

2. la sortie dépend de l’état interne du circuit et de ses entrées, et on dit alors qu’il s’agit
d’un schéma de MEALY. Pour le détecteur de séquence par exemple, la sortie S
pourrait valoir 1 dès que E passe à 1 pour la deuxième fois, avant même que sa valeur
ne soit ’officiellement’ échantillonnée au prochain front d’horloge. Cela conduit au
graphe de MEALY de la figure III.7.
L’arc libellé ’1/0’ de a vers b par exemple indique un changement d’état de a vers
b lorsque E = 1, avec une valeur de la sortie S=0. Mais attention : le changement d’état
ne se produira qu’au prochain front d’horloge, alors que le changement de sortie se
produit immédiatement après le changement des entrées.

Différences entre les circuits de MOORE et de MEALY


La principale différence tient dans le fait que dans un circuit de MEALY, on obtient les
sorties un temps d’horloge plus tôt. Par exemple dans la transition de c vers f pour E = 1, la
sortie S = 1 sera échantillonnable (= mémorisée et utilisable) au front d’horloge suivant, en
même temps que l’état courant passera à f . Dans le circuit de MOORE, la sortie passera à 1
après le front d’horloge suivant, et ne sera stable et échantillonnable qu’au front d’horloge
encore ultérieur, c’est à dire un temps plus tard que dans le circuit de MEALY.
66 CHAPITRE III. ÉLÉMENTS DE LOGIQUE SÉQUENTIELLE

0/0 e
0/0
...100
1/0
c 0/0
0/0 ...010 f
0/0 1/1 0/0
a 1/0 ...101
b
1/0 1/1
...000 ...001 1/0
0/0 g
d ...110
0/0
...011
1/0
h 1/0
...111

Figure III.7. Détecteur de séquence 1,0,1 de type Mealy, non simplifié.

Pour certains types de circuits, il est plus facile de dessiner un graphe de MOORE, qui
correspond à une vision plus ’statique’ du problème. La bonne nouvelle est qu’il existe une
méthode automatique de transformation d’un graphe de MOORE en graphe de MEALY,
lorsque l’on veut profiter des avantages de ce dernier.

Transformation d’un graphe de Moore en graphe de Mealy


On voit sur la figure III.8 comment un arc dans un graphe de MOORE se transforme en
un arc dans un graphe de MEALY équivalent.

S
a e a’ e/S b’
b

Figure III.8. Un arc d’un graphe de MOORE transformé en un arc équivalent d’un graphe
de MEALY. Si avec l’entrée e on va en a associé à une sortie S, alors cette sortie peut être
directement attachée à l’arc : e/S.

On a donc une méthode automatique pour transformer les graphes de MOORE en


graphes de MEALY, très utile car les graphes de MOORE sont souvent plus faciles à
concevoir. C’est elle qu’on a utilisée par exemple pour transformer le graphe de MOORE de
la figure III.6 en graphe de MEALY (figure III.7).
Transformation d’un graphe de Mealy en graphe de Moore
Ce n’est pas toujours possible, et donc aucune méthode générale ne peut exister. Les
circuits de MEALY sont donc intrinsèquement plus riches que ceux de MOORE.

6. Tables de transitions
Les tables de transitions, encore appelées tables de Huffman ne sont rien d’autre
qu’une version tabulaire des graphes de transitions. Par exemple, les tables associées aux
graphes de MOORE et de MEALY du détecteur de séquence sont représentées figure III.9
et III.10.
6. Tables de transitions 67

avant après
état E état
a 0 a
a 1 b
b 0 c
état S
b 1 d
a 0
c 0 e
b 0
c 1 f
c 0
d 0 g
d 0
d 1 h
e 0
e 0 a
f 1
e 1 b
g 0
f 0 c
h 0
f 1 d
g 0 e
g 1 f
h 0 g
h 1 h

Figure III.9. Table de transitions du graphe de MOORE pour le détecteur de séquence


1,0,1. À chaque arc du graphe correspond une ligne dans la table. On notera la table
complémentaire,qui donne les sorties associées à chaque état.

avant après
état E état S
a 0 a 0
a 1 b 0
b 0 c 0
b 1 d 0
c 0 e 0
c 1 f 1
d 0 g 0
d 1 h 0
e 0 a 0
e 1 b 0
f 0 c 0
f 1 d 0
g 0 e 0
g 1 f 1
h 0 g 0
h 1 h 0

Figure III.10. Table de transitions du graphe de MEALY pour le détecteur de séquence


1,0,1. À chaque transition du graphe correspond une ligne de la table.La valeur de la sortie
S est associée à chaque transition.

Simplification des tables de transition


Une table de transitions permet notamment de repérer facilement des états équivalents.
Un groupe G d’états sont dits équivalents si, pour chaque combinaison possible des entrées,
ils conduisent à des états du groupe G avec les mêmes sorties. On peut alors diminuer le
nombre d’états en remplaçant tous les états du groupe G par un seul, puis tenter de rappliquer
68 CHAPITRE III. ÉLÉMENTS DE LOGIQUE SÉQUENTIELLE

cette procédure de réduction sur l’ensemble d’états réduit.


Par exemple, sur la table de transitions précédente associée au diagramme de MOORE,
on constate que a et e sont équivalents, que c et g sont équivalents, que d et h sont équivalents.
Attention : b et f ne sont pas équivalents car, bien qu’ils aient mêmes états suivants, ils sont
associés à des sorties différentes. On peut réécrire la table de transitions en supprimant les
lignes associées à e, g et h, et en remplaçant partout e par a, g par c, h par d (figure III.11).

avant après
état E état
a 0 a
a 1 b état S
b 0 c a 0
b 1 d b 0
c 0 a c 0
c 1 f d 0
d 0 c f 1
d 1 d
f 0 c
f 1 d

Figure III.11. Table de transitions du détecteur de séquence 1,0,1après une première phase
de simplification.

Sur cette table, on constate que b et d sont équivalents, ce qui ne pouvait pas être
déterminé à l’étape précédente. La figure III.12 montre la table encore simplifiée.

avant après
état E état
a 0 a
état S
a 1 b
a 0
b 0 c
b 0
b 1 b
c 0
c 0 a
f 1
c 1 f
f 0 c
f 1 b

Figure III.12. Table de transitions du détecteur de séquence 1,0,1 après une deuxième
phase de simplification.

Le processus de simplification s’arrête ici. Finalement, 4 états suffisent pour ce


détecteur de séquence. Ceux-ci ont perdu la signification qu’ils avaient initialement dans le
graphe à 8 états. Le graphe simplifié est représenté figure III.13.
Cette procédure de simplification s’applique également aux graphes de MEALY, en
cherchant des identités dans les couples état/sortie. Sur l’exemple précédent, on constate
que a et e sont équivalents, que b et f sont équivalents, que c et g sont équivalents, et que d
et h sont équivalents. En procédant aux suppressions et substitutions associées, on trouve la
6. Tables de transitions 69

1 1
0
S=0
a 1 0 1
0 b c f
S=0 S=0 S=1

Figure III.13. Graphe de MOORE simplifié du détecteur de séquence 1,0,1.

nouvelle table (figure III.14).

avant après
état E état S
a 0 a 0
a 1 b 0
b 0 c 0
b 1 d 0
c 0 a 0
c 1 b 1
d 0 c 0
d 1 d 0

Figure III.14. Table de transitions de MEALY pour le détecteur de séquence 1,0,1, après
une première phase de simplification.

Cette fois, on voit que b et d sont équivalents (figure III.15).

avant après
état E état S
a 0 a 0
a 1 b 0
b 0 c 0
b 1 b 0
c 0 a 0
c 1 b 1

Figure III.15. Table de transitions de MEALY pour le détecteur de séquence 1,0,1, après
une deuxième phase de simplification.

Le processus s’arrête ici. Comme c’est souvent le cas, le graphe de MEALY est plus
simple que le graphe de MOORE associé au même problème, ne comportant que 3 états
(figure III.16).

7. Bascules synchrones
Notre but est maintenant de réaliser à l’aide de circuits logiques les machines
séquentielles telles qu’elles peuvent être décrites par les graphes d’états. Pour faciliter
cette synthèse, il a été imaginé de créer des composants appelés bascules, qui formeraient
70 CHAPITRE III. ÉLÉMENTS DE LOGIQUE SÉQUENTIELLE

0/0 1/0

a 1 0/0
b c
1/1

0/0

Figure III.16. Graphe de MEALY simplifié du détecteur de séquence 1,0,1.

les briques de base à utiliser. Une bascule mémorise un seul bit et permet de représenter
seulement deux états distincts, mais l’utilisation d’un vecteur de n bascules permet de coder
jusqu’à 2n états distincts. Par exemple, pour le détecteur de séquence analysé à la section
précédente, le graphe de Moore simplifié comportait 4 états, et un vecteur de deux bascules
va permettre de les encoder.
Par ailleurs, pour que tout un vecteur de bascules évolue de façon fiable d’état en état,
il est souhaitable que chacune d’elles ait une entrée d’horloge reliée à une horloge commune
(circuit synchrone), et ne puisse changer d’état qu’au moment précis d’un front de cette
horloge (montant ou descendant), et jamais entre deux fronts, même si les autres entrées du
circuit sont modifiées plusieurs fois entre temps. De tels circuits sont de conception difficile ;
on les présente dans cette section.

7.1. Latch RS actif sur un niveau d’horloge


Dans le but de rendre synchrone le latch RS vu précédemment, on peut limiter la
période d’écriture (figure III.17).

Figure III.17. Latch RS.

L’état de ce circuit est modifiable lorsque H = 1, c’est à dire sur un niveau du signal
H. Tant que H est au niveau 1, les modifications de S et de R vont avoir un effet sur l’état du
bistable. Un tel latch peut avoir certaines applications, mais il n’est pas adapté à la réalisation
de circuits séquentiels synchrones, où les changements d’états de leurs différentes parties
doivent se produire de façon parfaitement synchronisée, au moment précis défini par un
front d’une horloge commune. La section suivante va présenter un tel circuit.
7. Bascules synchrones 71

7.2. Bascule active sur front d’horloge


Nous avons donc besoin de composants séquentiels qui changent d’état sur un front
d’horloge, et seulement à ce moment précis. Le latch RS vu à la section précédente ne
remplissait pas cette condition, puisque le bistable pouvait changer de valeur tant que H était
au niveau 1. Le circuit de la figure III.18, qui réalise ce qu’on va appeler une bascule D (D
pour ’delay’), ne change d’état quant à lui que sur le front descendant de l’horloge H.

module basculeD(h,d: q)
s=/h*/r*/ns ;
ns=/s*/d ;
r=/nr*/h ;
nr=/ns*/r ;
q=/r*/nq ;
nq=/s*/q ;
end module

Figure III.18. Bascule D synchrone : l’état du bistable q ne change qu’au moment où h


passe de 1 à 0.

La compréhension détaillée du fonctionnement de ce circuit est difficile, et l’utilisation


d’un simulateur tel que celui de SHDL peut y aider. On peut déjà remarquer que lorsque h
vaut 1, on a nécessairement R=0 et S=0, et donc le bistable final qui produit q est dans l’état
de repos : tant que h vaut 1, l’état interne q ne peut pas changer, même si d change de valeur
un nombre quelconque de fois. Lorsque h passe de 1 à 0, on peut montrer que le bistable

du haut va stocker la valeur de D et la placer sur R, et que celui du bas va stocker la valeur
de D et la placer sur S. On a donc au moment du front descendant un ’set’ ou un ’reset’ du
bistable final, dans le sens de la mémorisation de la donnée d en entrée. Enfin, et c’est le point
important, on peut montrer également que lorsque h est maintenu à 0, les valeurs de R et de
S ne peuvent pas changer même si d change, et donc les changements de d n’affectent pas
le bistable final. On a donc bien démontré la propriété essentielle de ce circuit, qui est que
son état interne q est affecté durant la période très courte où l’horloge h passe de 1 à 0, c’est
à dire durant un front descendant de celle ci, et que tous les changements sur d en dehors de
cette période n’ont pas d’effet sur q.

7.3. Bascule D
Une telle bascule D est représentée couramment par le schéma de la figure ?? On a
indiqué également l’écriture SHDL associée à cette bascule.
Le signal de remise à zéro RST n’était pas présent sur la figure III.18. Il s’agit d’une
remise à zéro asynchrone, qui provoque le forçage à 0 du contenu de la bascule, sans
qu’il y ait besoin d’un front d’horloge. Tant que le signal RST est actif (sur niveau 1 sur la
figure), la bascule est forcée à 0 et son contenu ne peut pas évoluer. Il s’agit du même signal
reset que celui qui est présent sur votre micro-ordinateur, votre téléphone portable, etc. : en
72 CHAPITRE III. ÉLÉMENTS DE LOGIQUE SÉQUENTIELLE

module test(e,h,reset: x)
x:=e ;
x.clk=h ;
x.rst=reset ;
end module

Figure III.19. Représentation habituelle d’une bascule D synchrone.

l’appliquant, vous remettez dans l’état 0 tous les composants séquentiels de votre machine.
Du point de vue graphique, le triangle devant l’entrée CLK indique un signal d’horloge.
S’il était précédé d’un rond, cela indiquerait une horloge active sur front descendant ; en son
absence elle est active sur front montant. De la même façon, un rond devant l’entrée RST
indique une activité sur niveau bas. La figure III.20 illustre ces différentes situations.

module test(e,h,reset: x) module test(e,h,reset: x) module test(e,h,reset: x)


x:=e ; x:=e ; x:=e ;
x.clk=/h ; x.clk=h ; x.clk=/h ;
x.rst=reset ; x.rst=reset ; x.rst=/reset ;
end module end module end module

(a) (b) (c)

Figure III.20. Différentes bascules D. (a) horloge sur front descendant. (b) reset actif sur
niveau bas. (c) horloge front descendant et reset actif sur niveau bas.

Bascules et langage SHDL


En SHDL, la notation ’:=’ est appelée affectation séquentielle, et les suffixes ’.clk’ et
’.rst’ servent à repérer les signaux d’horloge et de remise à zéro. Des horloges actives sur
front descendant, ou des reset actifs sur niveau bas se traduisent par un signe ’/’ devant le
nom du signal d’horloge ou de reset. Par ailleurs, on notera qu’on ne peut pas écrire quelque
chose comme :
x.clk = a*b ; // incorrect

Cela impliquerait l’existence d’une bascule et d’une porte combinatoire, ce qui doit se
traduire par deux équations distinctes :

x.clk = i ;
i = a*b ;

Lorsqu’on écrit x.clk = /h ;, on ne fait pas appel à une porte supplémentaire


d’inversion : on utilise une bascule active sur front descendant, et donc on n’a pas à créer de
7. Bascules synchrones 73

ligne supplémentaire. Pour les mêmes raisons, on ne doit pas écrire : x:=a*b ;, mais :

x := i ;
i = a*b ;

On a tout de même la possibilité d’écrire x:=/i ;, qui correspond à une bascule D qui
charge l’inverse de son entrée, et qui se traduit par la présence d’un rond devant l’entrée D
(figure III.21).

module test(e,h,reset:x) module test(e,h,reset:x)


x:=e ; x:=/e ;
x.clk=/h ; x.clk=h ;
x.rst=reset ; x.rst=reset ;
end module end module

(a) (b)

Figure III.21. Les deux formes possibles d’une bascule D. (a) entrée normale. (b) entrée
inversée.

Équation d’évolution
L’équation d’évolution d’une bascule, c’est l’équation de la valeur que prendra l’état
après le front d’horloge. Dans le cas de la bascule D c’est tout simplement la valeur présente
à son entrée :
Q := D

7.4. Bascule T
Tout comme la bascule D, la bascule T (trigger) mémorise également un bit de façon
synchrone, mais elle a des modalités différentes d’utilisation. Son schéma, une table de
transitions synthétique et l’écriture SHDL associée sont donnés figure III.22.
L’état mémorisé de la bascule T s’inverse (après le front d’horloge) si et seulement si
son entrée T vaut 1. Lorsque son entrée T vaut 0, cet état reste inchangé.Elle est donc adaptée
aux problématiques de changements d’une valeur et non à un simple stockage comme la
bascule D. En dehors des variations sur les fronts d’horloge et la ligne de reset asynchrone,
les deux seules versions possibles de la bascule T sont données figure III.23.

Équation d’évolution
− −
Q := T Q + T Q
74 CHAPITRE III. ÉLÉMENTS DE LOGIQUE SÉQUENTIELLE

avant après module test(e,h,reset: x)


x:=/e*x+e*/x ;
T Q Q
x.clk=h ;
0 x x x.rst=reset ;
1 x −x end module

Figure III.22. Bascule T synchrone. Lorsque l’entrée T est à 0 elle ne change pas d’état ;
lorsque l’entrée T vaut 1 son état s’inverse.

module test(e,h,reset: x) module test(e,h,reset: x)


x:=/e*x+e*/x ; x:=e*x+/e*/x ;
x.clk=h ; x.clk=h ;
x.rst=reset ; x.rst=reset ;
end module end module

(a) (b)

Figure III.23. Les deux formes possibles d’une bascule T. (a) entrée normale. (b) entrée
inversée. On notera la différence d’écriture en langage SHDL.

Le premier terme exprime bien que, si T est à 0, Q ne change pas, et que si T est à 1, Q
s’inverse. On retrouve l’écriture SHDL de l’affectation séquentielle.

7.5. Bascule JK
Comme les bascules D et T, la bascule JK (Jack-Kilby) mémorise un bit de façon
synchrone. Son schéma, sa table de transitions simplifiée et l’écriture SHDL associée sont
donnés figure III.24.

avant après
J K Q Q module test(reset,h,j,k:x)
x:=/k*x+j*/x ;
0 0 x x
x.clk=h ;
0 1 - 0 x.rst=reset ;
end module
1 0 - 1
1 1 x −x

Figure III.24. Bascule JK synchrone,table de transitions simplifiée et écriture SHDL.Selon


les valeurs de ses entrées J et K, elle permet le forçage de son état à 0 ou à 1, mais aussi la
conservation ou l’inversion de son état.
7. Bascules synchrones 75

Elle fonctionne de façon analogue au latch RS, J jouant le rôle de S et K de R. Lorsque


J et K sont à 0, l’état de la bascule ne change pas au front d’horloge. Si on veut faire une
mise à 1ou une mise à 0, on applique 1sur J (respectivement sur K) puis on applique un front
d’horloge. De plus, mettre à la fois J et K à 1 est autorisé, et l’état de la bascule s’inverse au
front d’horloge.

Équation d’évolution
− −
Q := K Q + J Q

Elle est moins immédiatement évidente que celle des bascules D et T. On vérifie que dans
tous les cas, on a le résultat attendu :
− −
• si J = 0 et K=0, K Q + J Q = 0 + Q = Q
− −
• si J = 0 et K=1, K Q + J Q = 0 + 0 = 0
− − −
• si J = 1 et K=0, K Q + J Q = Q + Q = 1
− − − −
• si J = 1 et K=1, K Q + J Q = 0 + Q = Q

La bascule JK existe également avec des entrées J et K inversées. La figure III.25 montre ces
différentes formes, et l’écriture SHDL associée.

7.6. Choix des bascules


La bascule D est clairement adaptée aux situations où on doit stocker un bit présent.
La bascule T est adaptée aux situations qui se posent en termes de changement ou
d’inversion. La bascule JK semble plus versatile, et elle est d’ailleurs souvent appelée
bascule universelle. Par analogie avec les portes combinatoires, on pourrait se demander
si cette bascule JK est ’plus puissante’ que les bascules D ou T, c’est à dire s’il est possible
de fabriquer avec elle des circuits que les autres ne pourraient pas faire. La réponse est non.
Ces trois types de bascules ont une puissance d’expression équivalente : elle mémorisent un
bit, et elles ne diffèrent que par les modalités de stockage de ce bit. Par exemple, on vérifie
facilement qu’on peut fabriquer une bascule JK avec une bascule T et réciproquement
(figure III.26).
Dans le cas (a), il est clair qu’en reliant les deux entrées J et K, la bascule reste dans le
même état si t vaut 0, et elle s’inverse si t vaut 1, ce qui correspond bien au fonctionnement
d’une bascule T.
Dans le cas (b), on a figuré la solution avec un multiplexeur pour plus de clarté. La
valeur mise en entrée de la bascule T dépend de la valeur du couple j, k, et on vérifie qu’on
a le résultat souhaité dans tous les cas :
1 si jk = 00, on place 0 sur l’entrée de la bascule T et elle ne changera pas de valeur au
front d’horloge.

2 Si jk = 10, on place sur l’entrée de la bascule T l’inverse de son état interne, et il est
facile de voir que cela conduit à la mise à 1 de la bascule au prochain front d’horloge.

3 Si jk = 01, on place l’état courant de la bascule T sur son entrée, ce qui conduit à la
76 CHAPITRE III. ÉLÉMENTS DE LOGIQUE SÉQUENTIELLE

module test(reset,h,j,k: x) module test(reset,h,j,k: x)


x:=/k*x+j*/x ; x:=/k*x+/j*/x ;
x.clk=h ; x.clk=h ;
x.rst=reset ; x.rst=reset ;
end module end module

(a) (b)

module test(reset,h,j,k: x) module test(reset,h,j,k: x)


x:=k*x+j*/x ; x:=k*x+/j*/x ;
x.clk=h ; x.clk=h ;
x.rst=reset ; x.rst=reset ;
end module end module

(c) (d)

Figure III.25. Les différentes formes possibles d’une bascule JK et leurs écritures SHDL
associées. (a) entrée normales. (b) entrée J inversée. (c) entrée K inversée. (d) entrées J et
K inversées.

(a) (b)

Figure III.26. (a) construction d’une bascule T avec une bascule JK,(b) construction d’une
bascule JK avec une bascule T.

forcer à 0 au prochain front.

4 Enfin si jk = 11, on place 1 en entrée de la bascule T ce qui va déclencher son


inversion.
7. Bascules synchrones 77

7.7. Schéma général d’un circuit séquentiel synchrone de type MOORE


La figure III.27 montre le schéma général d’un circuit séquentiel synchrone de type
MOORE. L’état interne est mémorisé dans un vecteur de n bits (2n état au plus) constitué
de bascules de type quelconque. Pour que ce circuit soit bien de type synchrone pur, toutes
les horloges des bascules ont bien été reliées ensemble directement à l’horloge générale, de
façon à garantir un changement d’état de toutes les bascules exactement au même moment.
Les propriétés des bascules garantissent que cet état ne peut pas changer entre deux fronts
d’horloge, même si les entrées du circuit changent plusieurs fois entre temps. Par ailleurs
on voit que les sorties ne dépendent que de l’état interne, étant un simple transcodage
combinatoire de celui-ci.

état
entrées m interne

Q
CLK

transcodeur p sorties
Q

Figure III.27. Schéma général d’un circuit séquentiel synchrone de type MOORE.

7.8. Schéma général d’un circuit séquentiel synchrone de type MEALY


La figure III.28 montre le schéma général d’un circuit séquentiel synchrone de type
MEALY. Il s’agit également d’un circuit synchrone pur, dans lequel les horloges de toutes
les bascules sont reliées entre elles à l’horloge générale.Avec un vecteur d’état composé de n
bascules, on peut également coder au plus 2n états différents, et la mécanique de changement
d’états est analogue à celle d’un circuit de type MOORE. La seule différence est dans le
calcul des sorties : on voit qu’elles ne dépendent pas que de l’état interne, mais qu’elles
dépendent aussi des entrées. C’est ce qui va leur permettre d’obtenir les mêmes sorties qu’un
circuit de MOORE, un temps plus tôt.

8. Synthèse d’un circuit séquentiel synchrone

8.1. Étapes de la synthèse d’un circuit séquentiel synchrone


Lors de la synthèse d’un circuit séquentiel synchrone à l’aide de bascules, qu’il soit de
type MOORE ou de type MEALY, on obtiendra le circuit le plus simple en suivant les étapes
suivantes :
1 dessin du graphe d’états : il permet une spécification claire du circuit.
78 CHAPITRE III. ÉLÉMENTS DE LOGIQUE SÉQUENTIELLE

entrées m

n
Q
CLK

transcodeur p sorties

état
interne

Figure III.28. Schéma général d’un circuit séquentiel synchrone de type MEALY.

2 table de transitions : forme plus lisible du graphe, qui permet de vérifier qu’aucune
transition n’est oubliée, et prépare la phase de simplification.

3 simplification de la table de transitions : on applique la méthode vue précédemment,


parfois en plusieurs étapes.

4 détermination du nombre de bascules : le nombre d’états du graphe simplifié permet


d’établir un nombre minimum n de bascules à employer. On ne choisit pas encore
le type des bascules à employer, car la phase d’assignation fournira de nouvelles
informations.
5 assignation des états : pour chaque état, un vecteur unique de n bits est assigné, appelé
vecteur d’état. Des règles heuristiques d’assignation permettent de trouver celle qui
conduira à des calculs simples.

6 table de transitions instanciée : on réécrit la table de transitions, en remplaçant chaque


symbole d’état par le vecteur associé.

7 choix des bascules : en observant la table de transitions instanciée, certains types de


bascules peuvent être préférés. On emploiera une bascule D lorsqu’un état suivant
est la recopie d’une valeur de l’état précédent, une bascule T lorsque l’état suivant est
l’inverse d’un état précédent ; dans les autres cas, la bascule JK peut être employée.

8 calcul des entrées des bascules et calcul des sorties du circuit.

8.2. Synthèse du détecteur de séquence, version MOORE


A titre d’exemple, on va détailler la synthèse du circuit détecteur de la séquence 1,0,1
décrit précédemment, en version MOORE. On reprend donc toutes les étapes énumérées
en introduction.

Graphe d’état, table de transitions, simplification


Ces trois étapes ont été réalisées en section 6, et ont abouties au graphe simplifié et à la
table de transitions des figures III.29 et III.30.
8. Synthèse d’un circuit séquentiel synchrone 79

1 1
0
S=0
a 1 0 1
0 b c f
S=0 S=0 S=1

Figure III.29. Graphe de MOORE simplifié du détecteur de séquence 1,0,1.

avant après
état E état
a 0 a
état S
a 1 b
a 0
b 0 c
b 0
b 1 b
c 0
c 0 a
f 1
c 1 f
f 0 c
f 1 b

Figure III.30. Table de transitions simplifiée du détecteur de séquence 1,0,1.

Détermination du nombre de bascules


Il y a quatre états à coder, donc 2 bascules seront employées, dont on appellera les
sorties X et Y.

Assignation des états


La phase d’assignation consiste donc à affecter un vecteur d’état à chacun des états
du circuit ; ici un couple (X,Y) pour chacun des 4 états. N’importe quelle assignation peut
être employée. Néanmoins, on peut toujours affecter à l’état initial le vecteur d’état (0,0,…),
qui sera forcé lors d’un RESET asynchrone du circuit. Par ailleurs, certaines assignations
donnent lieu à des calculs plus simples que d’autres. On les trouve souvent en appliquant les
règles heuristiques suivantes, par ordre de priorité décroissante :

1 rendre adjacents les états de départ qui ont même état d’arrivée dans le graphe.

2 rendre adjacents les états d’arrivée qui ont même état de départ dans le graphe.

3 rendre adjacents les états qui ont mêmes sorties.

L’idée est de minimiser le nombre de bits qui changent (état ou sorties) lors des changements
d’états.
Appliqué à notre circuit, la première règle préconise d’associer a et c, a et f, b et f ;
la deuxième règle préconise d’associer a et b, b et c, a et f ; la troisième règle préconise
d’associer a, b et c. La figure III.31 propose une assignation qui respecte au mieux ces
règles.
80 CHAPITRE III. ÉLÉMENTS DE LOGIQUE SÉQUENTIELLE

X
Y 0 1
0 a f

1 c b

Figure III.31. Assignation des états : chaque état est associé à une configuration binaire
des bascules.

Table de transitions instanciée


On recopie la table de transitions précédente, en remplaçant chaque symbole d’état par
son vecteur d’état (figure III.32).

avant après
XY E XY
00 0 00
XY S
00 1 11
00 0
11 0 01
11 0
11 1 11
01 0
01 0 00
10 1
01 1 10
10 0 01
10 1 11

Figure III.32. Table de transitions instanciée.

Choix des bascules


En observant la table instanciée, on constate que X à l’état suivant reproduit l’entrée E :
une bascule D s’impose pour X. Dans le cas de Y, il reproduit presque la valeur de X de l’état
précédent, sauf pour la deuxième ligne. A titre d’exemple, on va choisir une bascule JK.

Calcul des entrées des bascules et de la sortie du circuit


Plusieurs méthodes sont possibles pour ces calculs. Celle qui sera employée ici est
basée sur une réécriture de la table de transitions, à laquelle on rajoute les entrées des
bascules, ici DX pour la bascule X et JY,KY pour la bascule Y (figure III.33).
En fait on connaissait déjà le résultat pour X : DX = E, qui est la raison pour laquelle

on avait choisi une bascule D. Pour Y, on trouve facilement que JY = E + X et KY = X. Par

ailleurs, on a S = X Y.
La synthèse est terminée, et notre séquenceur se réduit au schéma de la figure III.34. On
notera l’écriture des affectations séquentielles de x et y : x := e est l’équation d’évolution
de la bascule D, et y := x*y+jy*/y est une équation de la forme q := /k*q+j*/q, pour
laquelle l’entrée K est inversée.

8.3. Synthèse du détecteur de séquence, version MEALY


On effectue ici la synthèse du même détecteur de séquence 1,0,1, cette fois en version
8. Synthèse d’un circuit séquentiel synchrone 81

avant après
XY E DX JY KY XY
00 0 0 0 * 00
XY S
00 1 1 1 * 11
00 0
11 0 0 * 0 01
11 0
11 1 1 * 0 11
01 0
01 0 0 * 1 00
10 1
01 1 1 * 1 10
10 0 0 1 * 01
10 1 1 1 * 11

Figure III.33. Table de transitions instanciée avec les entrées des bascules choisies.

module seq101(rst,h,e: s)
x := e ;
x.clk = h ;
x.rst = rst ;
y := x*y+jy*/y ;
y.clk = h ;
y.rst = rst ;
jy = e+x ;
s = x*/y ;
end module

Figure III.34. Schéma final synthétisé du détecteur de séquence 1,0,1 (type MOORE) et
description SHDL associée.

MEALY. Il permettra d’obtenir la sortie un front d’horloge avant la version MOORE, et on


peut espérer un circuit encore plus simple puisque son graphe simplifié comporte un état de
moins que celui de MOORE.

Graphe d’état, table de transitions, simplification


Ces trois étapes ont été réalisées en section 6, et ont abouties au graphe simplifié et à la
table de transitions des figures III.35 et III.36.

0/0 1/0

a 1 0/0
b c
1/1

0/0

Figure III.35. Graphe de MEALY simplifié du détecteur de séquence 1,0,1.

Détermination du nombre de bascules


Il y a trois états à coder, donc 2 bascules seront employées, dont on appellera les sorties
X et Y.
82 CHAPITRE III. ÉLÉMENTS DE LOGIQUE SÉQUENTIELLE

avant après
état E état S
a 0 a 0
a 1 b 0
b 0 c 0
b 1 b 0
c 0 a 0
c 1 b 1

Figure III.36. Table de transitions simplifiée du détecteur de séquence 1,0,1.

Assignation des états


La phase d’assignation consiste donc à affecter un vecteur d’état à chacun des états
du circuit ; ici un couple (X,Y) pour chacun des 3 états. Afin d’obtenir les résultats les plus
simples possibles, on essaie d’appliquer au mieux les heuristiques d’assignation décrites à
la section précédente.
Appliquée à notre circuit, la première règle préconise d’associer a et c ; la deuxième
règle préconise d’associer a et b. La figure III.37 propose une assignation qui respecte au
mieux ces règles.

X
Y 0 1
0 a b

1 c

Figure III.37. Assignation des états : chaque état est associé à une configuration binaire
des bascules.

Table de transitions instanciée


On recopie la table de transitions précédente, en remplaçant chaque symbole d’état par
son vecteur d’état (figure III.38).

avant après
XY E XY S
00 0 00 0
00 1 10 0
10 0 01 0
10 1 10 0
01 0 00 0
01 1 10 1

Figure III.38. Table de transitions instanciée.

Choix des bascules


En observant la table instanciée, on constate que X à l’état suivant reproduit l’entrée
E : une bascule D s’impose pour X. Dans le cas de Y, il n’y a qu’une ligne où Y passe à 1 ;
on peut choisir également une bascule D, dont on appellera l’entrée DY.
8. Synthèse d’un circuit séquentiel synchrone 83

Calcul des entrées des bascules et de la sortie du circuit


Ici encore on va réécrire la table de transitions, à laquelle on va rajouter les entrées des
bascules, ici DX pour la bascule X et DY pour la bascule Y (figure III.39).

avant
XY E DX DY XY S
00 0 0 0 00 0
00 1 1 0 10 0
01 0 0 1 01 0
01 1 1 0 10 0
10 0 0 0 00 0
10 1 1 0 10 0

Figure III.39. Table de transitions instanciée avec les entrées des bascules choisies.

En fait on connaissait déjà le résultat pour X : DX = E, qui est la raison pour laquelle on
avait choisi une bascule D. Pour Y, on peut faire une table de Karnaugh qui va nous permettre
d’exploiter les combinaisons non spécifiées dues au fait que la table d’assignation n’est pas
complètement remplie (figure III.40).

X,Y
E 0,0 0,1 1,1 1,0
0 * 1
1 *

Figure III.40. Table de Karnaugh pour le calcul de DX. Le vecteur XY=11 est non spé-
cifié.


On trouve donc DX = EX. Par ailleurs, on a S = EY.
La synthèse est terminée, et notre séquenceur se réduit au schéma de la figure III.41.
On voit que cette fois la sortie ne dépend plus seulement de l’état interne comme dans le
circuit de MOORE ; elle dépend aussi de l’entrée E. Avec le simulateur SHDL, on pourra
se convaincre que dans le circuit de MEALY on obtient bien la sortie S un front d’horloge
avant le circuit de MOORE.

module seq101(rst,h,e: s)
x := e ;
x.clk = h ;
x.rst = rst ;
y := dy ;
y.clk = h ;
y.rst = rst ;
dy = /e*x ;
s = e*y ;
end module

Figure III.41. Schéma final synthétisé du détecteur de séquence 1,0,1 (type MEALY) et
description SHDL associée. On voit que la sortie S dépend de l’état interne, mais aussi de
l’entrée E.
84 CHAPITRE III. ÉLÉMENTS DE LOGIQUE SÉQUENTIELLE

9. Chronologie d’un circuit séquentiel synchrone

9.1. Temps de setup, de hold et de propagation des bascules


Parmi les caractéristiques temporelles d’une bascule figurent les temps de setup et de
hold. Le temps de setup est la durée avant le front d’horloge durant laquelle les entrées de
la bascule doivent être stables pour que le basculement se passe correctement. Le temps de
hold est la durée analogue après le front d’horloge. L’union des deux définit une période
autour du front d’horloge durant laquelle les entrées doivent être stables, faute de quoi le
basculement ne se produirait pas comme prévu.
Le temps de propagation quant à lui est le temps après lequel on est garanti d’avoir la
nouvelle valeur de la bascule, après la période d’instabilité de la transition.
Toutes ces durées sont illustrées sur la figure III.42.

front d’horloge

t
setup hold

temps de propagation

période où les entrées valeurs stables


de la bascule doivent être stables de l’état suivant

Figure III.42. Temps de setup, de hold et de propagation d’une bascule.

9.2. Chronologie détaillée d’une transition.


La lecture de cette section n’est pas nécessaire pour qui veut concevoir un circuit
séquentiel. Il lui suffit d’admettre qu’après le front d’horloge, au moment où les bascules
du circuit changent éventuellement d’état, on passe sans coup férir des valeurs de l’état
précédent aux valeurs de l’état suivant.
On peut aussi chercher à comprendre en détail ce qui se passe lors d’une transition, et
même craindre qu’elle ne se passe mal. Même si le front d’horloge est le même pour toutes
les bascules, les valeurs des signaux de l’état précédent ne peuvent-elles pas interférer avec
celles de l’état suivant ? C’est en effet possible dans certaines conditions. Considérons par
exemple le schéma de la figure III.43.
Si la bascule a a une commutation beaucoup plus rapide que la bascule b, la valeur de
a va changer durant la période de hold de la bascule b, provoquant ainsi un fonctionnement
incorrect.
Le plus souvent, c’est le temps de setup qui est violé, lorsqu’on augmente la vitesse
d’horloge à un rythme tel qu’un front d’horloge arrive alors que les entrées des bascules,
qui sont des fonctions combinatoires des entrées et de l’état courant, n’ont pas eu un temps
suffisant pour se stabiliser.
9. Chronologie d’un circuit séquentiel synchrone 85

Figure III.43. Si la bascule a est trop rapide, le temps de hold de b sera violé.

En pratique, on emploie des bascules aux caractéristiques temporelles analogues.

9.3. Bascules maître-esclave


Une façon de résoudre le problème de timing précédent consiste à séparer clairement
le moment de capture (d’échantillonnage) des valeurs de l’état précédent du moment où on
libère les valeurs pour l’état suivant, plutôt que de compter sur des délais de propagation
trop serrés.
On utilise pour cela des bascules spéciales appelées bascules maître-esclave, câblées
selon le modèle de la figure III.44.

Figure III.44. Bascule maître-esclave. On capture les valeurs de l’état précédent au front
montant de l’horloge, et on libère les valeurs pour l’état suivant au front descendant.

Sur le front montant de l’horloge h, les valeurs issues de l’état précédent sont prises en
compte, sans être libérées encore vers les sorties, et donc sans risque de rétroaction néfaste.
Plus tard, sur le front descendant de h, les nouvelles valeurs des bascules associées à l’état
suivant sont libérées.
Les bascules maître-esclave peuvent être employées à peu près partout où sont
employées des bascules ordinaires, mais il faut cette fois bien contrôler les deux fronts de
l’horloge, et non un seul.

10. Circuits séquentiels réutilisables


On présente dans cette section des circuits séquentiels utilisés dans la conception des
microprocesseurs ou des microcontrôleurs. Nous continuons notre travail de modularité, en
créant des briques réutilisables dans d’autres constructions ; ici encore une interface adapté
permettra une récursivité ou une interconnexion aisés.
86 CHAPITRE III. ÉLÉMENTS DE LOGIQUE SÉQUENTIELLE

10.1. Compteur binaire

Compteur de base
Un compteur binaire sur n bits incrémente à chaque front d’horloge une valeur de n
bits. On pourrait d’abord penser à créer un registre de n bits avec des bascules D, puis à
relier leurs entrées à un circuit d’incrémentation, mais il y a une solution plus directe et
efficace. On se rappelle en effet (section 4) l’algorithme de comptage : un bit est recopié
avec inversion si tous les bits à sa droite valent 1, et le bit de poids faible est toujours inversé.
Cette description en termes d’inversion conduit à une conception à base de bascules T, dans
laquelle l’entrée T d’une bascule est reliée au ET de tous les bits qui sont à sa droite. La
figure III.45 montre un exemple d’un tel compteur sur 4 bits.

module cpt4(rst,h: a,b,c,d) b.rst = rst ;


d := /d ; tb = c*d ;
d.clk = h ; a := /ta*a+ta*/a ;
d.rst = rst ; a.clk = h ;
c := /d*c+d*/c ; a.rst = rst ;
c.clk = h ; ta = b*c*d ;
c.rst = rst ; end module
b := /tb*b+tb*/b ;
b.clk = h ;

Figure III.45. Compteur binaire 4 bits. Un bit est inversé lorsque tous les bits situés à sa
droite valent 1.

Ce compteur compte bien sûr modulo 2n : après la valeur ’1111’, suit la valeur ’0000’
sans qu’on puisse être prévenu de ce débordement.
On notera l’affectation séquentielle SHDL utilisée pour la bascule de poids faible d :
d := /d;, qui se traduit par une bascule T avec un ’1’sur l’entrée T. Pour le bit c, l’écriture
c := d*c+/d*/c; se traduit également par une bascule T, mais avec une inversion avant
son entrée. Les autres bascules T ont une écriture directement tirée de l’équation d’évolution
standard d’une bascule T.
Dans la foulée, on peut également créer un circuit décompteur selon la même méthode :
un bit est inversé lorsque tous les bits qui sont à sa droite valent 0 (figure III.46).

Ajout d’une entrée de validation


Ces circuits comptent ou décomptent en permanence, et il n’y a aucun moyen de
suspendre et de reprendre leur comptage. On va maintenant ajouter une entrée de validation
en (pour ’enable’) qui ne permettra au compteur de compter que si elle vaut ’1’. Lorsqu’elle
vaudra ’0’, le compteur restera figé sur la dernière valeur produite, malgré l’arrivée de fronts
10. Circuits séquentiels réutilisables 87

module dec4(rst,h: a,b,c,d) b.clk = h ;


d := /d ; b.rst = rst ;
d.clk = h ; tb = /c*/d ;
d.rst = rst ; a := /ta*a+ta*/a ;
c := d*c+/d*/c ; a.clk = h ;
c.clk = h ; a.rst = rst ;
c.rst = rst ; ta = /b*/c*/d ;
b := /tb*b+tb*/b ; end module

Figure III.46. Décompteur binaire 4 bits.Un bit est inversé lorsque tous les bits situés à sa
droite valent 0.

d’horloge sur h.
Il suffit pour cela d’ajouter une entrée de validation à chaque bascule T. Le langage
SHDL autorise l’emploi de l’écriture : q.ena = en; qui réalise exactement cela, et qu’il
suffit d’employer pour chacune des bascules du compteur. Si on ne dispose que de bascules
sans entrée de validation, la transformation de la figure III.47 permet de l’obtenir.

module bascten(rst,h,en,t:q)
q := /tq*q+tq*/q ;
q.clk = h ;
q.rst = rst ;
tq = en*t ;
end module

Figure III.47. Bascule T avec entrée de validation.

Ajout d’une remise à zéro synchrone


On souhaite maintenant ajouter une entrée sclr de remise à zéro synchrone, qui remet
le compteur à zéro au prochain front d’horloge. Elle n’est bien sûr pas de même nature
que l’entrée de reset asynchrone, qui n’est là que pour l’initialisation. Par ailleurs il ne faut
pas essayer d’utiliser ce reset asynchrone pour implémenter notre remise à zéro synchrone.
La règle d’or des circuits séquentiels synchrones purs, c’est de relier entre-elles toutes
les horloges et de relier entre eux tous les signaux de reset asynchrone, sans chercher à
les modifier. Nous sommes heureux de pouvoir réinitialiser à tout coup notre ordinateur
personnel en appuyant sur le bouton de reset, sans que cela ne dépende d’une condition plus
ou moins bien pensée par un concepteur peu rigoureux.
Comme pour l’entrée de validation, le langage SHDL a une écriture
q.sclr = sclr; qui fait exactement ce dont nous avons besoin. Si on ne dispose pas de
bascules T avec remise à zéro synchrone, la figure III.48 remplira cette fonction.
88 CHAPITRE III. ÉLÉMENTS DE LOGIQUE SÉQUENTIELLE

module tclr(rst,h,sclr,t:q)
q := /tq*q+tq*/q ;
q.clk = h ;
q.rst = rst ;
tq = /sclr*t+sclr*q ;
end module

Figure III.48. Bascule T avec remise à zéro synchrone.

On notera l’écriture SHDL tq = /sclr*t+sclr*q; qui conduit à l’utilisation d’un


multiplexeur 2 vers 1. Si sclr vaut 0, la bascule T reçoit son entrée t normalement ; si sclr
vaut 1, la bascule T reçoit q en entrée, qui provoque la remise à 0 au prochain front d’horloge.
En effet, si la bascule avait l’état 0, en ayant 0 en entrée elle reste à l’état 0 ; si elle avait l’état
1, en recevant 1 en entrée, elle s’inverse et passe à 0 : dans tous les cas elle passe à zéro.

Chaînage de modules de comptage


Nous souhaitons maintenant chaîner plusieurs modules de comptage de n bits afin de
former des compteurs de m × n bits. L’algorithmique de comptage par bloc est analogue à
celle par bits : un bloc incrémente sa valeur si tous les blocs qui sont à sa droite sont à leur
valeur maximum. Il nous manque donc pour chaque bloc un signal qui indique qu’il a atteint
la valeur maximum. Pour économiser l’usage de portes ET, on va plutôt produire un signal
ripl qui vaut 1 lorsque le compteur est à la valeur maximum et que en = 1. Pour chaîner les
modules, il suffit maintenant de relier le ripl de l’un au en de l’autre. La figure III.50 montre
le chaînage de 4 modules de 4 bits pour former un compteur 16 bits.

10.2. Diviseur de fréquence


On souhaite construire un circuit qui fournisse des signaux d’horloge de fréquences
différentes et réglables, à partir d’une horloge de base. Dans beaucoup de système réels, y
compris nos ordinateurs de bureau, les signaux d’horloge sont bien souvent divisés avant
d’être effectivement exploités. On utilisera aussi un diviseur de fréquence dans le timer de
notre processeur CRAPS, pour régler la vitesse de la base de temps.
Plus précisément on souhaite que la fréquence du signal de sortie soit divisée par 2n,
n étant une valeur fournie sur une entrée du circuit. L’interface du diviseur est donné figure
III.49.

Figure III.49. Interface du circuit diviseur de fréquence.La sortie s est un signal de même
forme que h, avec une fréquence divisée par 2n
10. Circuits séquentiels réutilisables 89

module cpt4(rst,h,en:a,b,c,d,ripl) module cpt16(rst,h,en:s15..s0)


d:=/en*d+en*/d ; cpt4(rst,h,en:s3,s2,s1,s0,ripl1)
d.clk=h ; cpt4(rst,h,ripl1:s7,s6,s5,s4,ripl2)
d.rst=rst ; cpt4(rst,h,ripl2:s11,s10,s9,s8,ripl3)
c:=/tc*c+tc*/c ; cpt4(rst,h,ripl3:s15,s14,s13,s12,ripl)
c.clk=h ; end module
c.rst=rst ;
tc=en*d ;
b:=/tb*b+tb*/b ;
b.clk=h ;
b.rst=rst ;
tb=en*c*d ;
a:=/ta*a+ta*/a ;
a.clk=h ;
a.rst=rst ;
ta=en*b*c*d ;
ripl=en*a*b*c*d ;
end module

Figure III.50. Compteur 16 bits formé avec 4 compteurs 4 bits. La sortie ripl d’un module
indique qu’il est à la valeur maximum et que tous ceux qui sont à sa droite sont à leur
maximum. Le module qui est à sa gauche a son entrée en reliée à ce signal, et ne peut
s’incrémenter que lorsqu’il vaut 1.

On va tirer parti du principe qu’une bascule T dont on relie l’entrée à 1 produit sur sa
sortie un signal rectangulaire dont la fréquence est égale à la fréquence d’horloge divisée
par 2. On peut s’en convaincre en examinant le chronogramme de la figure III.51.

CLK

Figure III.51. Chronogramme d’évolution d’une bascule T dont on a forcé l’entrée T à 1.


La sortie Q a une fréquence égale à la fréquence de l’horloge divisée par 2.

On voit en effet sur le chronogramme que la sortie de la bascule s’inverse, donc


change de demi-période, à chaque front montant de l’horloge, c’est à dire toutes les deux
demi-périodes de l’horloge. Il y a bien un rapport de 1 sur 2 entre les deux signaux.
On va ainsi créer une chaîne de trois de ces bascules T avec des entrées forcées à 1, afin
d’obtenir les 4 fréquences d’horloge successives : f (la fréquence de l’horloge générale clk),
f / 2, f / 4 et f / 8. On utilisera un multiplexeur 2 vers 4 commandé par l’entrée n1..n0 pour
90 CHAPITRE III. ÉLÉMENTS DE LOGIQUE SÉQUENTIELLE

sélectionner parmi elles celle qui est désirée. On trouvera figures III.52 et III.53 le schéma
associé et son codage en langage SHDL. Ce sera la seule fois dans tout l’ouvrage où on
aura fait une entorse au principe de base des circuits séquentiels synchrones qui consiste à
relier entre-eux tous les signaux d’horloge pour toutes les bascules. Mais ici, cela est fait
précisément pour produire un signal d’horloge, qui sera possiblement exploité quant à lui de
façon strictement synchrone.

Figure III.52. Structure du circuit diviseur de fréquence. Une suite de bascules T sont
chaînées, divisant par 2 la fréquence à chaque étage.

module divfreq(rst,clk,d1..d0: s)
q0 := /q0 ;
q0.clk = clk ;
q0.rst = rst ;
q1 := /q1 ;
q1.clk = q0 ;
q1.rst = rst ;
q2 := /q2 ;
q2.clk = q1 ;
q2.rst = rst ;
s = /d1*/d0*clk + /d1*d0*q0 + d1*/dO*q1 + d1*d0*q2 ;
end module

Figure III.53. Écriture SHDL du module diviseur de fréquence.

10.3. Registres
Un registre de n bits est un ensemble de n bascules D synchrones mises en parallèle et
partageant la même horloge (figure III.54).

On notera l’écriture vectorielle de type d[31..d0]. Elle est strictement équivalente à


l’écriture d31..d0 qui a été utilisée jusqu’ici ; elle est plus courte lorsque le nom du signal
est long, car il n’est mentionné qu’une fois.

Registre avec ligne de sélection


Un registre peut être équipé d’une ligne de sélection qui doit être activée si l’on veut
faire une écriture ; ce type de signal a souvent pour nom en (enable), ou cs (chip select) ou
10. Circuits séquentiels réutilisables 91

// Description SHDL d’un registre 32 bits


module reg32(d[31..0],h,reset: q[31..0])
q[31..0] := d[31..0] ;
q[31..0].clk = h ;
q[31..0].rst = reset ;
END MODULE

Figure III.54. Registre de 32 bits avec son écriture SHDL.

encore ce (chip enable). On a vu à la section précédente que le langage SHDL disposait de


l’écriture .ena pour prendre en compte directement un tel signal. La figure III.55 met en
oeuvre cette écriture pour notre registre de 32 bits.

// description SHDL directe


module reg32_2(reset,h,en,d31..d0: q31..q0)
q[31..0] := d[31..0] ;
q[31..0].clk = clk ;
q[31..0].rst = reset ;
q[31..0].ena = en ;
end module

Figure III.55. Registre avec entrée de sélection, écriture SHDL directe.

Pour mieux comprendre son fonctionnement, on peut le réaliser avec un multiplexeur


et par rebouclage de la sortie sur l’entrée. La figure III.56 montre cette construction, avec le
code SHDL correspondant.

module reg32_3(reset,h,en,d31..d0: q31..q0)


reg32(reset,h,in[31..0]: out[31..0]) ;
in[31..0] = d[31..0]*en + q[31..0]*/en ;
q[31..0] = out[31..0] ;
end module

Figure III.56. Registre avec entrée de sélection construit avec un multiplexeur.

Lorsque l’entrée en est à 0, le multiplexeur reconduit la valeur du registre en entrée :


il ne change alors pas de valeur au prochain front d’horloge. Lorsque en vaut 1, c’est la
valeur présente sur les entrées d31..d0 qui est aiguillée par le multiplexeur, puis mémorisée.
92 CHAPITRE III. ÉLÉMENTS DE LOGIQUE SÉQUENTIELLE

Cette construction est bien sûr moins performante qu’avec des bascules D déjà équipées
d’une entrée de validation ; en particulier elle demande un temps de setup plus long dû à la
propagation au travers du multiplexeur.

11. Exercices corrigés

11.1. Exercice 1 : système de sécurité

Énoncé
Réaliser un système de mise en marche de sécurité de machine dangereuse.Il est équipé
de deux entrées A et B et la mise en marche est contrôlée par la sortie M. Il faut pour cela
que la procédure suivante soit respectée :

1 A et B doivent être relâchés initialement ;

2 appuyer sur A,

3 appuyer sur B : la machine se met en marche.

Toute autre manipulation arrête ou laisse la machine arrêtée ; il faut ensuite reprendre la
procédure au point 1 pour la mettre en marche.

Solution
On va adopter une solution de type MOORE. On fera également une conception
synchrone, c’est à dire qu’on supposera présente une horloge suffisamment rapide pour ne
rater aucun événement sur A et B. Le graphe d’état de ce problème est donné figure III.57.

00 10 11

M=0 10 11
a b c
M=0
01 M=1
11 00
01 00
01
00 10
e
M=0

Figure III.57. Graphe d’état du système de sécurité, type MOORE.

En partant de l’état a, on progresse jusqu’à l’état c pour lequel M=1 si on respecte


la séquence de mise en marche. Sur ce chemin, on tombe dans l’état d’erreur e dès qu’on
s’écarte de la procédure. Lorsqu’on est en e, on y reste tant que A et B ne sont pas relâchés
tous les deux, conformément à la spécification de l’énoncé.
On réécrit ce graphe sous une forme tabulaire pour chercher s’il est simplifiable (figure
III.58).
11. Exercices corrigés 93

avant après
état A B état
a 0 0 a
a 0 1 e
a 1 0 b
a 1 1 e
b 0 0 e
état M
b 0 1 e
a 0
b 1 0 b
b 0
b 1 1 c
c 1
c 0 0 e
e 0
c 0 1 e
c 1 0 e
c 1 1 c
e 0 0 a
e 0 1 e
e 1 0 e
e 1 1 e

Figure III.58. Table de transitions du graphe de sécurité.

Aucune simplification n’est possible ; le circuit possède donc un nombre minimal


d’états de 4. Il faut donc 2 bascules pour coder cet état, dont on appellera les sorties X et Y.
Vient maintenant l’étape d’assignation, durant laquelle nous devons choisir une
configuration des bascules X et Y pour chacun des états. On assignera à l’état a le vecteur
XY=00, associer au reset des bascules. Pour les autres, il est souhaitable pour obtenir des
calculs plus simples de respecter les heuristiques d’assignation, qui recommandent de
placer côte à côte les états fortement reliés dans le graphe. On peut par exemple choisir
l’assignation de la figure III.59.

X
Y 0 1
0 a b

1 e c

Figure III.59. Assignation des états du système de sécurité.

On peut maintenant réécrire la table de transition, en remplaçant les états symbolique


par leur vecteur associé (figure III.60).
Aucune bascule ne semble particulièrement adaptée à ces changements d’état. À titre
d’exemple, on va utiliser deux bascules T. On peut à nouveau écrire la table de transition
instanciée, en rajoutant 2 colonnes TX et TY qui sont les entrées T des bascules X et Y
(figure III.61).
Il suffit maintenant de trouver les formules algébriques pour TX et TY, en fonction de
X, Y, A et B. On peut pour cela utiliser des tables de Karnaugh (figure III.62). On trouve :
−−− − −
1 TX = ABX Y + AX + BXY
−−− − − −
2 TY = ABXY + BY X + AX Y
94 CHAPITRE III. ÉLÉMENTS DE LOGIQUE SÉQUENTIELLE

avant après
XY A B XY
00 0 0 00
00 0 1 01
00 1 0 10
00 1 1 01
10 0 0 01
XY S
10 0 1 01
00 0
10 1 0 10
10 0
10 1 1 11
11 1
11 0 0 01
01 0
11 0 1 01
11 1 0 01
11 1 1 11
01 0 0 00
01 0 1 01
01 1 0 01
01 1 1 01

Figure III.60. Table de transitions instanciée du graphe de sécurité.

avant après
XY A B TX TY XY
00 0 0 0 0 00
00 0 1 0 1 01
00 1 0 1 0 10
00 1 1 0 1 01
10 0 0 1 1 01
XY S
10 0 1 1 1 01
00 0
10 1 0 0 0 10
10 0
10 1 1 0 1 11
11 1
11 0 0 1 0 01
01 0
11 0 1 1 0 01
11 1 0 1 0 01
11 1 1 0 0 11
01 0 0 0 1 00
01 0 1 0 0 01
01 1 0 0 0 01
01 1 1 0 0 01

Figure III.61. Table de transitions instanciée du graphe de sécurité, avec les entrées des 2
bascules T.

TX TY
X,Y X,Y
A,B 0,0 0,1 1,1 1,0 A,B 0,0 0,1 1,1 1,0
0,0 1 1 0,0 1 1
0,1 1 1 0,1 1 1
1,1 1,1 1 1
1,0 1 1 1,0

Figure III.62. Simplification des équations du système de sécurité.


11. Exercices corrigés 95

3 M = XY

11.2. Exercice 2 : compteur/décompteur

Énoncé
Concevoir un circuit séquentiel synchrone avec une entrée e et 4 sorties a,b,c,d, qui
compte en binaire sur a,b,c,d lorsque e=0 et qui décompte lorsque e=1.

Solution
On pourrait concevoir ce circuit en construisant un graphe d’états, une table de
transitions, etc. Mais cette méthode ne serait pas générique et avec 4 bits le graphe serait déjà
de grande taille avec 16 états. Il est préférable ici de combiner deux circuits que l’on connaît
déjà, le compteur et le décompteur.
On a déjà présenté les compteurs et les algorithmes de comptage et de décomptage
en binaire (section 4). On réalise un compteur binaire 4 bits avec 4 bascules T ; la bascule
de poids faible a pour entrée 1 car elle change d’état à chaque front d’horloge ; les autres
bascules ont pour entrée le ET des bits qui sont à leur droite. Un décompteur se fait
également avec 4 bascules T ; la bascule de poids faible change également à chaque front ;
les autres bascules changent d’état lorsque tous les bits qui sont à leur droite sont des 0.
La synthèse des deux est donc simple : la bascule de poids faible est commune, car
elle change d’état à chaque front d’horloge dans les deux cas. Pour les autres bits, il s’agit
également de faire un ET dans les deux cas, mais des bits qui sont à droite pour le comptage
et de l’inverse de ces mêmes bits pour le décomptage. Il suffit donc de mettre un inverseur
commandé (un XOR, voir section 4) par le signal e devant chacun des bits à prendre
en compte dans le ET. Cela conduit au schéma de la figure III.63, avec un code SHDL
immédiat.

11.3. Exercice 3 : utilisation des bascules

Énoncé
Construire une bascule D avec une bascule T, puis une bascule T avec une bascule D.
On pourra utiliser des circuits combinatoires.

Solution
1 Bascule D avec une bascule T.
On peut se poser une question simple : comment mettre à 0 une bascule T ? Il suffit de
mettre la valeur Q de la bascule sur son entrée T. Si elle valait 0 elle y reste et si elle
valait 1 elle s’inverse et passe à 0.
Pour mettre à 1 une bascule T, on voit aussi facilement qu’il suffit d’appliquer l’inverse
de Q sur l’entrée T.
Dans les deux cas, on a appliqué sur l’entrée T la valeur Q de la bascule, avec une
inversion commandée par la valeur D à mémoriser : XOR(D,Q).
96 CHAPITRE III. ÉLÉMENTS DE LOGIQUE SÉQUENTIELLE

module cptdec(rst, h, e : a, b, c, d)
// bascule a
a := /ta*a+ta*/a;
a.clk = clk;
a.rst = rst;
ta = nb * nc * nd;
// bascule b
b := /tb*b+tb*/b;
b.clk = clk;
b.rst = rst;
tb = nc * nd;
// bascule c
c := /tc*c+tc*/c;
c.clk = clk;
c.rst = rst;
tc = nd;
// bascule d
d := /d;
d.clk = clk;
d.rst = rst;
// inversion de a,b,c,d
// commandée par e
na = a*/e + /a*e;
nb = b*/e + /b*e;
nc = c*/e + /c*e;
nd = d*/e + /d*e;
end module

Figure III.63. Compteur/décompteur : schéma et écriture SHDL.

2 Bascule T avec une bascule D.


La solution est ici plus directe : il suffit d’appliquer sur D l’équation d’évolution de la
− −
bascule T, qui est Q := T Q + T Q.
11. Exercices corrigés 97
Chapitre IV

Éléments fonctionnels d’un processeur

Le présent chapitre décrit les modules combinatoires et séquentiels les plus


communément utilisés dans les architectures d’ordinateurs, permettant ainsi la transition
avec le chapitre suivant décrivant en détail l’architecture d’un processeur 32 bits. Les
circuits que nous allons étudier à partir de maintenant sont trop complexes pour pouvoir être
analysés et synthétisés dans leur ensemble à l’aide des méthodes tabulaires et algébriques
vues aux chapitres précédents. Comme en logiciel, le concept de module va être utilisé
pour circonscrire et donc maîtriser cette complexité ; il prendra même parfois une forme
récursive, lorsqu’un module sera défini par l’assemblage de modules semblables, mais avec
un nombre d’entrées et de sorties plus petit. Le découpage en modules de l’ensemble d’un
système complexe va quant à lui faire appel à des méthodes plus intuitives, que nous allons
tenter d’introduire au travers de nombreux exemples.
Les microprocesseurs et microcontrôleurs sont des circuits séquentiels synchrones
complexes, dans lesquels nous opérerons ce découpage en modules, chaque partie étant un
circuit combinatoire ou séquentiel standard qui a souvent une structure récursive au sens
défini précédemment. Par exemple on va voir que l’Unité Arithmétique et Logique (UAL),
qui fait les calculs au sein d’un processeur, a la même structure quel que soit le nombre de
bits des données qu’elle traite, et qu’on pourra associer des ’tranches’d’UAL pour construire
des UALs de plus grosse capacité. Les concepteurs de processeurs peuvent donc faire appel
à ce concept de modularité récursive lors de l’augmentation de la taille des données par
exemple, en minimisant les temps de conception.
On présente également dans cette partie une nouvelle notion électrique, celle des sorties
en haute impédance, qui facilite la gestion de bus de grande taille. On présente aussi les
circuits mémoire, qui sont au sens strict des circuits séquentiels, mais qui ne se réduisent pas
à des assemblages de bascules ou de latchs. Une section entière leur est consacrée, vu leur
importance dans les systèmes informatiques et la diversité croissante de leurs types.
Une fois achevée la compréhension de ces modules de base, la conception d’un
processeur va ressembler à un jeu de construction avec des blocs qui vont s’emboîter
harmonieusement.

1. Sorties haute-impédance
Certains circuits possèdent des sorties dites trois états (tri-state), c’est à dire qu’en
plus de pouvoir être dans l’état ’0’ ou l’état ’1’ (c’est à dire dans un état électrique de ’basse
impédance’), elles peuvent être dans un troisième état dit de ’haute-impédance’, souvent
noté High-Z. Lorsqu’une sortie est en haute-impédance, tout se passe comme si elle n’était
plus connectée, car elle ne produit plus ni ne consomme plus aucun courant. Cette propriété
permettra de relier directement entre-elles plusieurs sorties de ce type, sous réserve de

98
1. Sorties haute-impédance 99

garantir qu’au plus une seule de ces sorties produise du courant à un moment donné (sous
peine de court-circuit !).
Les circuits ayant des sorties trois états possèdent en interne des composants appelés
buffer trois-états, qui se représentent tels que sur la figure IV.1.

E S E S
// écriture SHDL
S = E:OE ;
VALIDE VALIDE

Figure IV.1. Buffer 3-états - La sortie est en haute-impédance tant que VALIDE = 0.

La ligne VALIDE qui arrive sur le côté du triangle, souvent notée OE (pour ’Output
Enable’), commande l’état de la sortie. Tant que cette ligne est inactive, la sortie reste dans
l’état High-Z.
Du point de vue de l’écriture SHDL, cette commande Output Enable sur un signal tel
que S s’indique par l’ajout après le nom du signal d’une commande telle que :OE
Si on considère cet état High-Z comme un troisième état logique (ce qui n’est pas très
exact sur le plan mathématique), on a la table de vérité de la figure IV.2.

E OE S
* 0 High-Z
0 1 0
1 1 1

Figure IV.2. Table de vérité d’un buffer 3-états.

On peut illustrer l’utilisation de ces buffers lors de la réalisation d’un multiplexeur


(figure IV.3). Les sorties des deux buffers 3-états sont reliées entre-elles, car leurs lignes OE
s’activent de façon mutuellement exclusive. L’idée de ce schéma est que, soit on doit laisser
passer le courant qui vient de A (avec l’interrupteur commandé que forme le buffer trois
états), soit on doit le laisser passer depuis B. Comme ces deux conditions sont mutuellement
exclusives, on peut relier les sorties des deux buffers en toute sécurité. On économise ainsi
le circuit OU du schéma classique de droite, qui n’opérait jamais avec ses deux entrées à 1
simultanément.
On notera l’écriture SHDL telle que : S = A:/C;. L’équipotentielle s’appelle S, mais
deux sources de courant peuvent y imposer leur potentiel ; on les note alors A:/C et B:C.
On voit sur la figure IV.4 une configuration électrique particulière de ce circuit : la
commande C étant à 1, seul le buffer 3-états du bas va être dans un état de basse impédance,
et va imposer son état électrique à la sortie S. Le buffer trois états du haut a sa sortie en
haute impédance et n’entre pas en conflit avec l’autre pour l’établissement du potentiel de
S ; tout se passe comme s’il était déconnecté du circuit, ce qui a été figuré par les deux lignes
brisées.
100 CHAPITRE IV. ÉLÉMENTS FONCTIONNELS D’UN PROCESSEUR

A 0
S
B 1 C

A
A
C S S

B B

MODULE MUX_1(A,B,C :S) MODULE MUX_2(A,B,C :S)


S = A:/C ; S1 = /C * A ;
S = B:C ; S2 = C * B ;
END MODULE S = S1 + S2 ;
END MODULE

Figure IV.3. Multiplexeur réalisé à l’aide de buffers 3-états, comparé à sa réalisation en


logique combinatoire classique. On économise le ’ou’ final.

A=0 High−Z

C=1 S=1

B=1 1

Figure IV.4. Fonctionnement du multiplexeur. Dans cette configuration, c’est le buffer du


bas qui impose son potentiel.

Si on relie ensemble deux sorties de buffers trois états, et qu’à un moment donné leurs
lignes OE sont à 1en même temps, le court circuit est réel, et les circuits se mettent à chauffer
dangereusement, entraînant parfois la destruction ou l’endommagement du plus faible.
Cela peut se produire dans des réalisations composées de plusieurs circuits intégrés, dont
les sorties trois états sont reliées entre elles. Cela peut se produire également à l’intérieur
d’un seul circuit comme un FPGA, dans lequel des lignes internes à trois états peuvent être
reliées entre elles lors de la programmation - configuration. On évite parfois le pire avec un
bon odorat, en détectant l’odeur de cuisson de la poussière présente à la surface des circuits
à des températures avoisinant parfois les 100 degrés Celcius, et en débranchant en hâte
l’alimentation !
C’est en fait la seule situation dangereuse pour les composants lorsque l’on fait de la
logique digitale. Tant qu’on n’utilise pas de composants avec des sorties trois états, rien ne
peut être endommagé si on prend soin de ne relier chaque sortie qu’à des entrées. Avec les
1. Sorties haute-impédance 101

composants ayant des sorties trois états, il faudra impérativement connecter leurs lignes OE
à des circuits tels que des décodeurs (voir section 3) qui vont garantir l’exclusion mutuelle
entre les sorties reliées entre elles.

2. Les bus parallèles

2.1. Vocabulaire et concepts


On appelle bus parallèle un ensemble d’équipotentielles électriques qui
interconnectent plusieurs modules à l’intérieur d’un câblage. On voit figure IV.5 comment
on représente graphiquement un bus parallèle de largeur m. Le trait est épaissi par rapport à
un signal simple, et une barre surmontée d’un nombre indique la largeur du bus. On montre

m m

Figure IV.5. Les deux représentations graphiques d’un bus parallèle de largeur m.

également figure IV.6 les façons habituelles de représenter sur un schéma les sous-bus d’un
bus donné.

16 16 data[15..0]
data[15..0]

5 5

data[15..11]
data[15..11]
5
5 data[15..11] 16
data[15..0] 16
data[10..0]
11
11

Figure IV.6. Exemples de scindements / regroupements de bus.

On trouve des bus parallèles à tous les niveaux d’une architecture d’ordinateur : dans
les structures internes du processeur, entre le processeur et les autres composants présents
sur la carte mère, etc. Ils peuvent être présents partout où plusieurs composants doivent
échanger des données. Un bus large va permettre de transférer en une transaction une donnée
de grande taille, mais va occuper beaucoup de place sur les circuits, et donc coûter plus cher.
La même donnée pourra être copiée en deux transactions successives, avec un bus deux fois
moins large ; un compromis est donc à trouver entre coût et vitesse de traitement.
Pour que des modules puissent dialoguer sur un même bus, sur le plan électrique, il faut
102 CHAPITRE IV. ÉLÉMENTS FONCTIONNELS D’UN PROCESSEUR

adopter bien sûr les mêmes tensions d’alimentation, courants de sortie et d’entrée, etc. Mais
il faut aussi empêcher les courts-circuits sur les lignes de données, lorsque plusieurs modules
ont la possibilité d’y écrire. Par ailleurs, un protocole de communication est indispensable,
afin de régler la chronologie des échanges sur le bus. Dans ce domaine, comme dans
beaucoup d’autres de l’architecture des ordinateurs, on peut classer ces protocoles en deux
catégories : les synchrones et les asynchrones. Dans ces deux catégories, un grand nombre
de protocoles différents existent, dont l’étude sort du cadre de ce livre. Les protocoles
synchrones sont les plus répandus et les plus simples, car ils règlent tous les échanges sur un
signal d’horloge unique (qui fait partie du bus).

2.2. Exemple de bus synchrone simple


Afin d’illustrer quelques aspects de la communication sur un bus synchrone,
considérons le circuit de la figure IV.7. Le bus est composé de 11 lignes : 8 lignes de

esclaves

maitre
a[7..0] b[7..0] c[7..0] d[7..0]

compteur 2 bits 8 8 8 8

8
n1*n0 n1*/n0 /n1*n0 /n1*/n0
8 8 8 8
2
8 data[7..0]

2 n[1..0]
bus
clk

Figure IV.7. Exemple de bus synchrone, à un maître et 4 esclaves. Les sorties des esclaves
sont des buffers 3-états, dont les lignes OE s’excluent mutuellement.

données data[7..0] et 3 lignes de contrôle n[1..0] et clk. Le bloc de gauche joue le rôle
de maître, c’est à dire qu’il dirige les échanges sur le bus. C’est lui qui génère les signaux
de contrôle, gouvernés par l’horloge clk qui cadence tous les échanges. À chaque front de
clk, le maître génère à l’aide d’un compteur un nouveau numéro sur les lignes n[1..0]. Les
lignes OE de chaque esclave sont associées à un numéro spécifique ; par exemple l’esclave
dont la ligne OE est reliée à n1*/n0 est associé au numéro 2 (10 en binaire), et ne placera
sa donnée b[7..0] sur le bus que si ce numéro est présent sur les lignes n[1..0] du sous-bus
de contrôle.
Dans ce protocole synchrone particulièrement simple, les 4 esclaves placent à tour
de rôle leur donnée sur le sous-bus data[7..0], gouvernés par le module maître. Ici le
bus est monodirectionnel, les échanges allant toujours dans le sens esclave vers maître ;
certains protocoles pourront être bidirectionnels. Il n’y a ici qu’un seul maître, donc pas de
possibilité de conflit. S’il y avait plusieurs maîtres, une logique d’arbitrage serait nécessaire
pour décider quel maître peut prendre le contrôle du bus en cas de requêtes simultanées.
Il s’agit d’un protocole à une phase, puisqu’un échange de données est effectué en un
cycle d’horloge. Des protocoles complexes peuvent nécessiter de nombreuses phases pour
réaliser un échange, plusieurs phases pouvant être nécessaires pour que le maître décrive les
paramètres de sa demande à l’esclave, et plusieurs autres pouvant être nécessaires pour que
2. Les bus parallèles 103

l’esclave transmette la donnée demandée.

2.3. Exemple de bus parallèle, externe et normalisé : le bus PCI


Pour mieux comprendre la problématique des bus, on peut penser au bus PCI parallèle
présent sur la plupart des ordinateurs personnels et des stations de travail (même s’il est
progressivement remplacé maintenant par le bus PCI Express, plus rapide). Lorsqu’on
ouvre une de ces machines, on voit sur la carte mère plusieurs connecteurs exactement
identiques, comportant deux fois 94 broches, disposés parallèlement à quelques centimètres
d’intervalle.On peut parfois apercevoir sur le circuit imprimé une partie des interconnexions
entre ces connecteurs. Elles sont très simples : les broches de même position sont toutes
reliées entre elles pour tous les connecteurs. Il y a donc 188 pistes sur le circuit imprimé qui
relient entre elles les 188 broches de chaque connecteur (figure IV.8).

Figure IV.8. Bus PCI, avec 4 connecteurs à 188 broches.

Si on considère maintenant une carte placée dans un de ces connecteurs, comme une
carte d’acquisition vidéo, ou une carte réseau, il faut comprendre que ces 188 signaux sont
son unique moyen de communication avec le reste du système, et qu’il faut donc trouver un
moyen de dialoguer, rationnel à la fois sur le plan électrique et sur le plan logique. Pour le
bus PCI comme pour tous les bus externes standardisés, ce protocole de communication doit
être très général, utilisable par des cartes de types variés, en nombre non fixé à l’avance, qui
peuvent être par exemple principalement émettrices de données (cartes d’acquisition vidéo),
ou principalement réceptrices (cartes audio), ou les deux (cartes réseau).

3. Décodeurs
Un décodeur est un circuit possédant n entrées et 2n sorties numérotées (figure IV.9).
À tout moment, une et une seule sortie est active : celle dont le numéro correspond à la
valeur binaire présente sur les n entrées. Le décodeur traduit donc la valeur d’entrée en une
information de position spatiale.

S0 MODULE DECODER3TO8(EN,E2..E0 :S7..S0)


0
S1 S7=EN*E2*E1*E0 ;
E2 0 S6=EN*E2*E1*/E0 ;
1 S2
E1 0 S5=EN*E2*/E1*E0 ;
1 S3
E0 0 S4=EN*E2*/E1*/E0 ;
0 S4 S3=EN*/E2*E1*E0 ;
0
S5 S2=EN*/E2*E1*/E0 ;
EN 0
1 S6 S1=EN*/E2*/E1*E0 ;
1 S0=EN*/E2*/E1*/E0 ;
S7
0 END MODULE

Figure IV.9. Décodeur 3 vers 8.Avec la valeur 6 en entrée (110 en binaire),la sortie numéro
6 est activée.

Un décodeur peut être utilisé pour n’activer qu’au plus un composant 3-états à la fois,
puisqu’au plus une seule de ses sorties est active à la fois.
104 CHAPITRE IV. ÉLÉMENTS FONCTIONNELS D’UN PROCESSEUR

Les décodeurs peuvent également être utilisés pour implémenter des fonctions
logiques booléennes. Puisque chaque sortie représente un des minterms possibles, et que
toute fonction combinatoire booléenne s’exprime sous forme d’une somme de minterms,
on reliera avec un OU les sorties correspondants aux minterms désirés. On peut voir figure
IV.10 la réalisation d’un OU exclusif (XOR) à trois entrées avec un décodeur et un OU à
4 entrées.

000
001
010
E2
011
E1 100
E0 101
110
111

Figure IV.10. Un décodeur 3 vers 8 utilisé pour réaliser un OU exclusif à 3 entrées.

4. Multiplexeurs
Ces sont des circuits d’aiguillage pour les signaux logiques. Un multiplexeur possède
2 entrées de données, n entrées de commandes, et une seule sortie. On indique sur la
n

commande le numéro (en binaire) de l’entrée de donnée qui va être aiguillée en sortie. On a
en figure IV.11 un exemple de multiplexeur 8 vers 1 sur la commande duquel est écrit 1102
= 610 ; la sortie reflète alors l’état de la sixième entrée : E6. On donne aussi figure IV.11
l’écriture SHDL de ce multiplexeur, qui est celle d’un circuit combinatoire simple.

0 E0
0 E1
0 E2 MODULE MUX8TO1(E7..E0,C2..C0 :S)
S=/C2*/C1*/C0*E0+
1 E3 S /C2*/C1*C0*E1+
1
0 E4 /C2*C1*/C0*E2+
/C2*C1*C0*E3+
1 E5 C2*/C1*/C0*E4+
1 E6 C2*/C1*C0*E5+
C2*C1*/C0*E6+
1 E7 C2*C1*C0*E7;
C2..C0 END MODULE

110

Figure IV.11. Multiplexeur 8 vers 1. L’entrée numéro 6 est aiguillée vers la sortie.

Bien sûr, les multiplexeurs peuvent être mis en parallèle pour aiguiller des bus entiers.
On mettra alors en commun les lignes de commande, et en parallèle les lignes de données.
4. Multiplexeurs 105

On peut voir figure IV.12 un multiplexeur 2 vers 1aiguillant des bus de 32 bits, avec l’écriture
SHDL correspondante.

32
A31..A0
32
MODULE MUX2TO1_32(A31..A0,B31..B0,C :S31..S0)
S31..S0 S31..S0 = /C*A31..A0 + C*B31..B0 ;
32
B31..B0 END MODULE
C

Figure IV.12. Multiplexeur 2 vers 1, aiguillant des bus de 32 bits.

Un multiplexeur 2 vers 1 implémente matériellement une situation de type


si-alors-sinon ; plus précisément : si C alors S=A sinon S=B (figure IV.13). C’est une
remarque qui peut sembler banale, mais qui en fait va permettre d’organiser le découpage
en modules d’un système complexe, dès lors qu’un tel genre de situation aura été identifié.

n
A 1 n
S
n
B 0

Figure IV.13. Un multiplexeur 2 vers 1implémente une situation ’si-alors-sinon’ : si C alors


S = A sinon S = B.

Un multiplexeur 2n vers 1 peut également être utilisé directement pour implémenter


des fonctions combinatoires à n entrées. Il suffit de relier les entrées de la fonction à réaliser
aux entrées de commande du multiplexeur : chaque combinaison va conduire à un aiguillage
spécifique, et il n’y a plus qu’à mettre la valeur 0 ou 1 attendue sur l’entrée de donnée
correspondante du multiplexeur. Il n’y a cette fois aucun composant supplémentaire à
rajouter, et on peut voir figure IV.14 l’implémentation d’une fonction XOR à 3 entrées avec
un décodeur 8 vers 1. Bien sûr, cette solution n’est pas optimale en nombre de transistors
utilisés, puisque toutes les entrées forcées à 0 sont reliées à des portes ET inutilisées.

5. Encodeurs de priorité
Un encodeur de priorité possède 2n entrées et n sorties. Les entrées sont numérotées,
et correspondent à des événements de priorité croissante. On verra en particulier comment
utiliser les encodeurs de priorité pour gérer l’arrivée d’interruptions simultanées dans un
processeur, telles que les événements réseau, les événements disque, les événements USB ou
clavier ou souris, etc.
La sortie NUM contient le numéro de l’entrée activée la plus prioritaire, c’est à dire de
numéro le plus élevé. Une autre sortie (ACT sur la figure) peut aussi indiquer s’il y a au moins
106 CHAPITRE IV. ÉLÉMENTS FONCTIONNELS D’UN PROCESSEUR

0 0
1 1
1 2
0 3 XOR3(E2..E0)
1 4
0 5
0 6
1 7

E2..E0

Figure IV.14. Un multiplexeur 8 vers 1 utilisé pour réaliser un OU exclusif à 3 entrées.

une entrée active. Le schéma de la figure IV.15 montre un tel encodeur pour 23 entrées avec
les entrées 0, 3 et 6 activées, et la valeur binaire 6 placée sur les sorties.

1 E0 3
0 E1 NUM2..0
MODULE ENCODEUR(E[7..0]: NUM[2..0],ACT)
0 E2 110 (6) NUM2=E7+E6+E5+E4 ;
1 E3 encodeur NUM1=E7+E6+/E5*/E4*E3+/E5*/E4*E2 ;
0 E4 de NUM0=E7+/E6*E5+/E6*/E4*E3+/E6*/E4*/E2*E1 ;
0 E5 priorité ACT=E7+E6+E5+E4+E3+E2+E1+E0 ;
1 END MODULE
E6 ACT
0 E7 1

Figure IV.15. Encodeur de priorités à 8 entrées. L’entrée active #6 est la plus prioritaire.

La table de vérité d’un tel circuit à 8 entrées possède 256 lignes ; on peut néanmoins la
représenter de façon condensée (figure IV.16).
L’entrée #0 est souvent non câblée : l’absence d’entrée active est alors détectée lorsque
NUM = 0, évitant ainsi d’avoir la sortie supplémentaire ACT.
Une façon élégante d’obtenir le résultat consiste à utiliser une écriture vectorielle, qui
permet d’avoir tous les bits à la fois. On traduit le fait qu’on obtient la sortie 111 lorsqu’on
a E7 ; qu’on obtient 110 lorsqu’on a E6 et pas E7, etc. :
 NUM2   1   1 −  1  −−  1  −−−
 NUM1  =  1  ×E7 +  1  × E7E6 +  0  × E7E6E5 +  0  × E7E6E5E4
 NUM0   1   0  1  0
 0  −−−−  0  −−−−−  0  −−−−−−
+  1  × E7E6E5E4E3 +  1  × E7E6E5E4E3E2 +  0  × E7E6E5E4E3E2E1
 1  0  1
5. Encodeurs de priorité 107

E7 E6 E5 E4 E3 E2 E1 E0 NUM2..0 ACT
1 * * * * * * * 111 1
0 1 * * * * * * 110 1
0 0 1 * * * * * 101 1
0 0 0 1 * * * * 100 1
0 0 0 0 1 * * * 011 1
0 0 0 0 0 1 * * 010 1
0 0 0 0 0 0 1 * 001 1
0 0 0 0 0 0 0 1 000 1
0 0 0 0 0 0 0 0 000 0

Figure IV.16. Table de vérité condensée d’un encodeur de priorités à 8 entrées.

− −− −−−
 NUM2   E7   E7E6   E7E6E5   E7E6E5E4 
 NUM1  =  E7  +  −E7E6  +  0 + 0 
 NUM0   E7   0   E7E6E5   − − 0 
 0   0   0 
−−−−
+  E7E6E5E4E3  +  − −−−−
E7E6E5E4E3E2   + 0 
−−−−−− 
− −−−  
E7E6E5E4E3 0   E7 E6 E5 E4 E3 E2E1
− −− −−−
 NUM2   E7 + E7E6 + E7E6E5 + E7E6E5E4 
− − − − − − − − − −
 NUM1  =  E7 + E7E6 + E7E6E5E4E3 + E7E6E5E4E3E2 
 NUM0   E7 + − − −−−− −−−−−−
E7E6E5 + E7E6E5E4E3 + E7E6E5E4E3E2E1 
Le théorème d’absorption s’applique plusieurs fois, et on obtient finalement :
 NUM2   E7 + E6 + E5 + E4 
−− −−
 NUM1  =  E7 + E6 + E5E4E3 + E5E4E2 
 NUM0   E7 + − −− −−−
E6E5 + E6E4E3 + E6E4E2E1 
L’écriture SHDL d’un encodeur à 8 entrées est donnée figure IV.15. On reprendra à
l’exercice 11.3 le calcul des équations des signaux NUM2..0.

6. Circuit d’extension de signe


Un circuit d’extension de signe de p bits vers n bits, p < n est un circuit combinatoire
qui transforme un nombre signé codé en complément à 2 sur p bits en ce même nombre codé
en complément à 2 sur un nombre de bits n plus grand.
Considérons par exemple une extension de signe de 4 bits vers 8 bits, et quelques cas
particuliers :

• 00112 (= 3) sera converti en 000000112

• 11112 (= -1) sera converti en 111111112

• 10002 (= -8) sera converti en 111110002


108 CHAPITRE IV. ÉLÉMENTS FONCTIONNELS D’UN PROCESSEUR

On voit donc que le mot de n bits est formé de la recopie du mot de p bits, complété à gauche
de n − p bits tous égaux à 0 ou à 1 selon le signe du nombre. On recopie en fait n − p fois le
bit de signe du mot de départ, c’est à dire le bit de rang p − 1 (figure IV.17).

1010

1111 1010
Figure IV.17. Opération d’extension de signe. On duplique les p bits d’entrée et on ajoute
à gauche n-p bits de signe.

La figure IV.18 montre à titre d’exemple le code SHDL correspondant à un circuit


d’extension de signe de 13 bits vers 32 bits.

module signext13(e12..e0: s31..s0)


s12..s0 = e12..e0;
s31..s13 = e12;
end module

Figure IV.18. Écriture SHDL d’un circuit d’extension de signe 13 bits vers 32 bits. Les 13
bits de poids faibles sont recopiés et on ajoute 19 bits de poids forts, tous égaux au bit de
signe e12 du nombre de départ.

7. Décaleur à barillet
Le décaleur à barillet (barrel shifter) décale un mot binaire de n bits vers la gauche ou
vers la droite, d’un nombre variable de bits. Le sens du décalage est défini par une commande
R (pour right) et le nombre de bits du décalage est donné dans une commande NB sur p
bits (figure IV.19). C’est un circuit directement employé à l’exécution des instructions de
décalage et de rotation des processeurs, telles que les instructions sll et slr de CRAPS que
p
nous verrons au chapitre suivant. Généralement, n = 2 : un décaleur à barillet sur 8 bits aura
une valeur de décalage codée sur 3 bits ; un décaleur sur 32 bits aura une valeur de décalage
sur 5 bits, etc.
Sur 8 bits par exemple, la figure IV.20 présente la table de vérité condensée d’un tel
circuit, avec une valeur décalage sur 3 bits qui permet tous les décalages de 0 à 7 dans le sens
droit (R = 1) comme dans le sens gauche (R = 0).

Un tel circuit est difficile à concevoir directement ; par ailleurs, si on voulait le réaliser
sous forme d’un seul étage de propagation - ce qui est possible en théorie - les équations
seraient complexes, et ce d’autant plus que les valeurs de n et p seraient grandes.
À l’inverse, une conception modulaire et récursive est possible de façon très simple, si
on procède par étage, avec des décalages par puissances de 2 successives. Cette organisation
générale est dite de décalage à barillet, et elle est montrée figure IV.21 pour un décaleur 8
7. Décaleur à barillet 109

Figure IV.19. Interface général d’un décaleur à barillet.Le mot de n bits est décalé à droite
(resp. gauche) si R = 1 (resp. 0), d’un nombre de bits NB. Les bits qui sortent sont perdus, et
les bits entrants sont des 0.

R nb entrées sorties
* 000 e7 e6 e5 e4 e3 e2 e1 e0 e7 e6 e5 e4 e3 e2 e1 e0
0 001 e7 e6 e5 e4 e3 e2 e1 e0 e6 e5 e4 e3 e2 e1 e0 0
0 0 10 e7 e6 e5 e4 e3 e2 e1 e0 e5 e4 e3 e2 e1 e0 0 0
0 0 11 e7 e6 e5 e4 e3 e2 e1 e0 e4 e3 e2 e1 e0 0 0 0
0 10 0 e7 e6 e5 e4 e3 e2 e1 e0 e3 e2 e1 e0 0 0 0 0
0 10 1 e7 e6 e5 e4 e3 e2 e1 e0 e2 e1 e0 0 0 0 0 0
0 110 e7 e6 e5 e4 e3 e2 e1 e0 e1 e0 0 0 0 0 0 0
0 111 e7 e6 e5 e4 e3 e2 e1 e0 e0 0 0 0 0 0 0 0
1 001 e7 e6 e5 e4 e3 e2 e1 e0 0 e7 e6 e5 e4 e3 e2 e1
1 0 10 e7 e6 e5 e4 e3 e2 e1 e0 0 0 e7 e6 e5 e4 e3 e2
1 0 11 e7 e6 e5 e4 e3 e2 e1 e0 0 0 0 e7 e6 e5 e4 e3
1 10 0 e7 e6 e5 e4 e3 e2 e1 e0 0 0 0 0 e7 e6 e5 e4
1 10 1 e7 e6 e5 e4 e3 e2 e1 e0 0 0 0 0 0 e7 e6 e5
1 110 e7 e6 e5 e4 e3 e2 e1 e0 0 0 0 0 0 0 e7 e6
1 111 e7 e6 e5 e4 e3 e2 e1 e0 0 0 0 0 0 0 0 e7

Figure IV.20. Table de vérité condensée d’un décaleur à barillet sur 8 bits.

bits.
Tous les décaleurs ont leurs lignes R reliées ensemble, et donc opèrent dans le même
sens. Ils sont tous équipés d’une ligne CS, et un décalage n’est effectué que si CS = 1.
Ils effectuent chacun un nombre de décalages fixe, 4, 2 ou 1. Leur organisation en barillet
permet d’effectuer toutes les valeurs de décalage entre 0 et 7. Le premier niveau effectue un
décalage de 4 bits ou non, selon que nb[2] = 1 ou non. Le second niveau effectue ou non un
décalage de 2 bits, selon la valeur de nb[1]. Le dernier niveau effectue un décalage de 1 bit,
selon la valeur de nb[0].
Par exemple si nb = 101 et si R = 0, le premier niveau va décaler de 4 bits vers la gauche
(sa ligne CS est à 1), transformant E = (e7, e6, e5, e4, e3, e2, e1, e0) en (e3, e2, e1, e0, 0, 0, 0, 0). Le
second niveau va laisser passer ce vecteur sans le transformer. Le niveau du bas va effectuer
un décalage de 1, transformant le vecteur en (e2, e1, e0, 0, 0, 0, 0, 0). Au total, c’est donc bien
4 + 1 = 5 décalages à gauche qui ont été effectués.
Bien sûr, une telle organisation est généralisable à un nombre quelconque de bits. Un
110 CHAPITRE IV. ÉLÉMENTS FONCTIONNELS D’UN PROCESSEUR

Figure IV.21. Organisation en 3 niveaux (barillets) d’un décaleur 8 bits. Chaque niveau
décale dans un sens ou un autre d’un nombre de positions égal à une puissance de deux, si
sa ligne CS est à 1. Leurs combinaisons permettent toutes les valeurs de décalage.

décaleur 32 bits serait constitué de 5 étages, formés de décaleurs fixes de 16, 8, 4, 2 et 1 bits.
L’accroissement en complexité et en temps de propagation augmente donc en log(n), ce qui
est très satisfaisant.
Il nous reste seulement à résoudre le problème de la fabrication des décaleurs sur un
nombre de positions fixes. Ce sont des circuits combinatoires simples ; voici par exemple le
code SHDL d’un décaleur 2 bits :
module shift2(r,cs,e7,e6,e5,e4,e3,e2,e1,e0 :s7,s6,s5,s4,s3,s2,s1,s0)
s7=e7*/cs+cs*/r*e5 ;
s6=e6*/cs+cs*/r*e4 ;
s5=e5*/cs+cs*/r*e3+cs*r*e7 ;
s4=e4*/cs+cs*/r*e2+cs*r*e6 ;
s3=e3*/cs+cs*/r*e1+cs*r*e5 ;
s2=e2*/cs+cs*/r*e0+cs*r*e4 ;
s1=e1*/cs+cs*r*e3 ;
s0=e0*/cs+cs*r*e2 ;
end module

Les termes en /cs recopient l’entrée sur la sortie de même rang, et il n’y a donc pas de
décalage. Les termes en cs*/r gèrent le décalage à gauche de 2 positions, et les termes en
cs*r gèrent le décalage à droite de 2 positions.
Finalement, le code SHDL complet d’un décaleur à barillet sur 8 bits est donné
figure IV.22.
7. Décaleur à barillet 111

;; Décaleur à barillet 8 bits.


;; r = sens décalage (1=droite); amplitude = nb2..0
module barrelshifter8(r,nb2,nb1,nb0,e7,e6,e5,e4,e2,e1,e0:s7,s6,s5,s4,s3,s2,s1,s0)
shift4(r,nb2,e7,e6,e5,e4,e3,e2,e1,e0:s7,x6,x5,x4,x3,x2,x1,x0);
shift2(r,nb1,x7,x6,x5,x4,x3,x2,x1,x0:y7,y6,y5,y4,y3,y2,y1,y0);
shift1(r,nb0,y7,y6,y5,y4,y3,y2,y1,y0:s7,s6,s5,s4,s3,s2,s1,s0);
end module

;; Décale de 4 bits si cs=1, laisse inchangé sinon. r = sens de décalage (1=droite)


module shift4(r,cs,e7,e6,e5,e4,e3,e2,e1,e0:s7,s6,s5,s4,s3,s2,s1,s0)
s7=e7*/cs+cs*/r*e3 ;
s6=e6*/cs+cs*/r*e2 ;
s5=e5*/cs+cs*/r*e1 ;
s4=e4*/cs+cs*/r*e0 ;
s3=e3*/cs+cs*r*e7 ;
s2=e2*/cs+cs*r*e6 ;
s1=e1*/cs+cs*r*e5 ;
s0=e0*/cs+cs*r*e4 ;
end module

;; Décale de 2 bits si cs=1, laisse inchangé sinon. r = sens de décalage (1=droite)


module shift2(r,cs,e7,e6,e5,e4,e3,e2,e1,e0:s7,s6,s5,s4,s3,s2,s1,s0)
s7=e7*/cs+cs*/r*e5 ;
s6=e6*/cs+cs*/r*e4 ;
s5=e5*/cs+cs*/r*e3+cs*r*e7 ;
s4=e4*/cs+cs*/r*e2+cs*r*e6 ;
s3=e3*/cs+cs*/r*e1+cs*r*e5 ;
s2=e2*/cs+cs*/r*e0+cs*r*e4 ;
s1=e1*/cs+cs*r*e3 ;
s0=e0*/cs+cs*r*e2 ;
end module

;; Décale de 1 bit si cs=1, laisse inchangé sinon. r = sens de décalage (1=droite)


module shift1(r,cs,e7,e6,e5,e4,e3,e2,e1,e0:s7,s6,s5,s4,s3,s2,s1,s0)
s7=e7*/cs+cs*/r*e6 ;
s6=e6*/cs+cs*/r*e5+cs*r*e7 ;
s5=e5*/cs+cs*/r*e4+cs*r*e6 ;
s4=e4*/cs+cs*/r*e3+cs*r*e5 ;
s3=e3*/cs+cs*/r*e2+cs*r*e4 ;
s2=e2*/cs+cs*/r*e1+cs*r*e3 ;
s1=e1*/cs+cs*/r*e0+cs*r*e2 ;
s0=e0*/cs+cs*r*e1 ;
end module

Figure IV.22. Code SHDL complet d’un décaleur à barillet sur 8 bits.Chaque décaleur fixe
de 4, 2 et 1 bits est présent sous forme de module séparé.

8. L’unité arithmétique et logique

8.1. Fonction d’une UAL


Dans un processeur, on regroupe souvent dans la même unité fonctionnelle les
différents opérateurs d’arithmétique entière (additionneurs,multiplieurs, etc.), les opérateurs
de logique booléenne (AND, OR, etc.) et les opérations de décalage et de rotation de bits.
On appelle un tel module unité arithmétique et logique (UAL), et elle a la forme de la figure
IV.23 suivante :
Les opérations que réalise l’UAL ont une arité de 2, et parfois 1. Les opérandes sont
présentés sur deux bus A et B de même largeur ; on indique sur F le code d’une opération à
effectuer (par exemple, une addition), et le résultat est disponible sur le bus de sortie F(A,B)
après un certain temps de propagation des calculs. Les flags ou indicateurs, notés souvent
112 CHAPITRE IV. ÉLÉMENTS FONCTIONNELS D’UN PROCESSEUR

A B

F N,Z,V,C
UAL

F(A,B)

Figure IV.23. Unité arithmétique et logique.F code l’opération à effectuer ; les opérandes
sont A et B et le résultat est S. Les indicateurs N,Z,V,C donnent des informations sur la
nature du résultat.

N, Z, V, C donnent des informations sur le résultat de l’opération. N indique que le résultat


est négatif, Z indique qu’il est nul, V indique un débordement et C indique la présence
d’une retenue.
Il faut noter que l’UAL est un opérateur combinatoire : il n’a pas d’horloge, et le résultat
d’une opération n’est disponible qu’après un temps variable (mais borné).

8.2. UAL par tranches de 1 bit


Assez souvent, une UAL de n bits est formée de l’association de n UAL de 1 bit,
appelées tranches de bit (bit slice). Considérons par exemple les schémas des figures IV.24
et IV.25 d’une UAL 1 bit qui effectue l’une des 4 opérations : ET logique, OU logique,
complément ou addition sur les deux entrées A et B.

A B
2
F1,F0 F1 F0

UAL 0 0 AND
1 bit 0 1 OR
1 0 NOT A
CIN COUT
S 1 1 ADD

Figure IV.24. Unité arithmétique et logique de 1 bit.

CIN et COUT ne sont utilisés que pour l’opération d’addition. CIN est une retenue
entrante et COUT une retenue sortante : lorsque l’addition est sélectionnée (F1F0=11),
c’est la somme A + B + CIN qui est effectuée, et le résultat est disponible sur S, avec une
éventuelle retenue sortante placée sur COUT. On voit que l’équation du résultat S est un
8. L’unité arithmétique et logique 113

F1,F0
// multiplexeur 4 vers 1
MODULE MUX4TO1(E[3..0],C[1..0] :S)
S=/C1*/C0*E0+/C1*C0*E1+C1*/C0*E2*C1*C0*E3 ;
A END MODULE
B 0
// majorité à 3 entrées
MODULE MAJ3(X,Y,Z :MAJ)
1 S MAJ=X*Y+X*Z+Y*Z;
END MODULE

CIN 2 MODULE UAL1BIT(F1,F0,A,B,CIN :S, COUT)


MUX4TO1(E3,E2,E1,E0,F1,F0 :S);
3 E0=A*B ;
E1=A+B ;
E2=/A ;
COUT E3=/A*/B*CIN+/A*B*/CIN+A*/B*/CIN+A*B*CIN ;
MAJ3(A,B,CIN :COUT)
MAJ3
END MODULE

Figure IV.25. Structure interne et description SHDL de l’UAL 1 bit.

multiplexeur, commandé par les quatre combinaisons de F1et F0.Les quatre termes associés
à l’addition représentent le ou exclusif entre A, B et CIN.
Les termes correspondants aux autres opérations parlent d’eux-mêmes ; on voit que
seule l’opération de complémentation est unaire.
La figure IV.26 montre l’assemblage de 4 tranches d’UAL 1 bit, pour former une UAL
de 4 bits. Le module traitant les bits d’indice 0 a une retenue entrante forcée à 0 ; ensuite
la retenue sortante d’un module devient la retenue entrante du module suivant, jusqu’à la
retenue finale CARRY.

A0 B0 A1 B1 A2 B2 A3 B3

A B A B A B A B
2
F1,F0 F1,F0 F1,F0 F1,F0 F1,F0
UAL UAL UAL UAL
1 bit 1 bit 1 bit 1 bit
0 CIN COUT CIN COUT CIN COUT CIN COUT COUT
S S S S

S0 S1 S2 S3

MODULE UAL4BIT(F1,F0,A3..A0,B3..B0 :S3..S0,CARRY)


UAL1BIT(F1,F0,A0,B0,0:S0,C0);
UAL1BIT(F1,F0,A1,B1,C0:S1,C1);
UAL1BIT(F1,F0,A2,B2,C1:S2,C2);
UAL1BIT(F1,F0,A3,B3,C2:S3,CARRY);
END MODULE

Figure IV.26. Unité arithmétique de 4 bits formée par assemblage de 4 modules d’UAL
1 bit.

Bien sûr, cette technique a ses limites. Elle ne peut pas implémenter des fonctions qui
opèrent globalement sur les n bits, telles que les décalages ou les multiplications et divisions.
Par ailleurs, l’addition est particulièrement inefficace à cause de la propagation de la retenue,
et en particulier si le nombre n de modules est grand. Pour n = 32 par exemple, le calcul
114 CHAPITRE IV. ÉLÉMENTS FONCTIONNELS D’UN PROCESSEUR

d’une addition prendra 32 × p où p est le temps de calcul de l’addition sur l’UAL 1 bit. On
peut tout de même dans ce cas adopter la même approche avec de meilleurs résultats, avec
des blocs élémentaires de 4 bits par exemple, dans lesquels on aura optimisé le calcul de
la retenue.

9. Circuit timer/PWM
Principe
Un circuit timer/PWM génère un signal cyclique rectangulaire dont les paramètres sont
programmables. Ces paramètres sont :

1 la période P, exprimée en nombre de cycles de l’horloge générale.

2 la durée D à l’intérieur de cette période où la sortie vaut 0, exprimée en nombre


de cycles.

La figure IV.27 illustre la forme du signal recherché.

Figure IV.27. Signal périodique généré par un circuit timer/PWM.

Le terme PWM (Pulse Width Modulation : modulation en largeur d’impulsion) fait


référence à une technique utilisée fréquemment pour moduler l’énergie fournie à certains
dispositifs tels que des lampes, des moteurs à courant continu, etc. Plutôt que de leur fournir
une tension variable, difficile à produire avec précision, on leur fournit un signal périodique
dont on fait varier le rapport cyclique entre les moments où la tension est maximale et ceux
où la tension est nulle (P − N sur la figure). La fréquence 1 doit être assez élevée pour éviter
P P
les accoups ou les clignotements, mais pas trop élevée pour éviter d’éventuels effets néfastes.
Par exemple, les LEDs à haute luminosité qui équipent les feux de stop et arrière des voitures
ont leur luminosité commandée de cette manière.
Le circuit sera plutôt appelé ’timer’ si on n’exploite que les fronts montants ou
descendants du signal de sortie, pour générer une interruption par exemple.

Organisation
Pour réaliser un timer/PWM sur n bits, on utilise un compteur n bits et deux
comparateurs n bits, qui comparent en permanence la valeur du compteur aux deux
paramètres P et N (figure IV.28).
Le compteur dispose d’une remise à zéro synchrone (voir section 10.1), qui est activée
dès que le compteur arrive à la valeur P. Ainsi il va compter 0, 1, …, P, c’est à dire avec une
période de P+1 cycles d’horloge.
9. Circuit timer/PWM 115

Figure IV.28. Organisation d’un timer/PWM sur n bits. La valeur d’un compteur n bits est
comparée aux valeurs de N et P ; quand il atteint P il est remis à 0 ; quand il dépasse N la
valeur de la sortie m change. La bascule D finale prévient d’éventuels glitchs.

Durant cette période P+1, la valeur du compteur est comparée à la valeur de N, et la


sortie m est le reflet de cette comparaison. La sortie m n’est pas directement le résultat
combinatoire de la comparaison, mais on le fait passer au travers d’une bascule D, pour deux
raisons :
1 il pourrait être affecté de glitchs dans certaines situations ; en le faisant passer au travers
d’une bascule D on est certain d’avoir un signal parfaitement propre.

2 son timing est exactement aligné avec les fronts d’horloge, ce qui améliore
la précision.

La figure IV.29 donne le code SHDL d’un tel circuit timer/PWM sur 4 bits. On réutilise
les circuits de comptage et de comparaison déjà décrits en SHDL aux sections 10.1 et 8.9.
La période p est égale à p3…p0 + 1 en nombre de cycles de l’horloge clk ; la valeur
n3…n0 définit la valeur n. Le timing précis de ce module est défini par le chronogramme
figure IV.30.

10. RAMs et ROMs


10.1. Introduction
Cette section est consacrée aux circuits utilisés dans la mémoire centrale des
ordinateurs, qui contient les programmes et les données manipulés par les programmes.
L’information y est stockée sous forme de mots de taille fixe, dont la largeur dépend de
l’architecture utilisée. Ces mots mémoire sont ordonnés et possèdent chacun une adresse,
sous forme d’un nombre. On peut donc voir une mémoire comme un ruban de cases
numérotées :
116 CHAPITRE IV. ÉLÉMENTS FONCTIONNELS D’UN PROCESSEUR

// période = p3..p0, m à 1 après n3..n0 // comparateur 4 bits non signé


module pwm(rst,clk,en,p3..p0,n3..n0: m) module cmp4(a3..a0,b3..b0 :sup3,eq)
cpt4(rst,clk,eq,en :c3..c0); eq=eq3*eq2*eq1*eq0 ;
cmp4(c3..c0,p3..p0 :sup,eq); sup3=a3*/b3+eq3*sup2 ;
cmp4(c3..c0,n3..n0 :supn,eqn); eq3=a3*b3+/a3*/b3 ;
m:=dm ; sup2=a2*/b2+eq2*sup1 ;
m.clk=clk ; eq2=a2*b2+/a2*/b2 ;
m.rst=rst ; sup1=a1*/b1+eq1*a0*/b0 ;
dm=supn+eqn ; eq1=a1*b1+/a1*/b1 ;
end module eq0=a0*b0+/a0*/b0 ;
end module
// compteur 4 bits avec raz synchrone
// utilise des bascules T avec raz sync. // bascule T avec raz synchrone
module cpt4(rst,clk,sclr,en :a,b,c,d) module tclr(rst,clk,sclr,t :q)
tclr(rst,clk,sclr,en :d); q:=/t1*q+t1*/q ;
tclr(rst,clk,sclr,tc :c); q.clk=clk ;
tc=en*d ; q.rst=rst ;
tclr(rst,clk,sclr,tb :b); t1=/sclr*t+sclr*q ;
tb=en*c*d ; end module
tclr(rst,clk,sclr,ta :a);
ta=en*b*c*d ;
end module

Figure IV.29. Écriture SHDL d’un timer/PWM sur 4 bits.

cpt 0 1 2 3 4 5 0

n3..n0 = 2

p = 6 (p3..p0 = 5)

Figure IV.30. Exemple de chronologie des signaux du timer/PWM sur 4 bits.

données ...
adresses 0 1 2 3 4 5 6 7 8 n−2 n−1 n

… ou comme un tableau :
donnée = mem[adresse], adresse ∈ [0, n].

De telles mémoires se classent en deux grands types, selon qu’elles sont en lecture seule
ou en lecture-écriture : les RAMs (Random Access Memory) peuvent être lues et écrites, et
l’attribut random indique que ces lectures-écritures peuvent se faire à des suites d’adresses
totalement quelconques, par opposition à des mémoires de type séquentiel (comme les
disques durs) qui font des séries d’accès à des adresses consécutives. Les ROMs (Read Only
10. RAMs et ROMs 117

Memory) sont fonctionnellement identiques aux RAMs, mais ne permettent que la lecture
et pas l’écriture. Utilisées dans la mémoire centrale d’un ordinateur, les RAMs pourront
stocker les programmes et les données, alors que les ROMs vont seulement stocker des
programmes invariables, comme par exemple le programme exécuté au démarrage de
la machine, et stocké dans la ROM dite de BIOS. Dans certains ordinateurs plus anciens,
tout le système d’exploitation était stocké en ROM, ce qui permettait d’avoir un système
incorruptible et au démarrage rapide.
Une RAM peut être statique ou dynamique. Chaque bit mémoire d’une RAM statique
(SRAM) est constitué d’une bascule, et conserve son état tant qu’elle est alimentée. A
l’inverse, chaque bit d’une RAM dynamique (DRAM) est composé d’une capacité, qui doit
être rafraîchie périodiquement par une électronique séparée. Les RAMs statiques ont un
taux d’intégration plus faible que les RAM dynamiques, puisqu’un bit mémoire nécessite 6
transistors dans un cas, et une capacité plus un transistor dans l’autre.
Une RAM peut être synchrone ou asynchrone, une RAM synchrone étant en fait
une RAM asynchrone à laquelle on a ajouté une machine à états finis synchrone qui place
les commandes de lecture et d’écriture dans un pipeline, afin de permettre d’accepter une
nouvelle commande avant que la précédente n’ait été complétée.
Les barrettes de RAM de nos ordinateurs personnels sont des SDRAM, c’est à dire
des RAM dynamiques synchrones, fonctionnant à des fréquences de 200MHz et plus. Elles
sont souvent de type DDR (double data rate), quand les fronts montants et descendants de
l’horloge sont exploités pour les changements d’état.
Dans beaucoup d’autres appareils (assistants personnels, consoles de jeux, etc.), la
RAM est de type statique asynchrone (SRAM), sous la forme de circuits intégrés.
Les ROMs existent également dans un grand nombre de types différents,
principalement selon la façon dont on peut programmer leur contenu (invariable, par
définition). Il y a d’abord les ROMs programmées par masque à l’usine ; elles sont produites
en grand nombre avec un faible coût à l’unité, mais leur contenu ne peut jamais être mis à
jour ultérieurement.
Les PROMs (Programmable Rom) sont programmables par un appareil spécial,
qui généralement détruit par un courant fort une liaison interne correspondant à un
bit. Les EPROMs (Erasable PROM) fonctionnent de la même façon, mais possèdent
une fenêtre transparente et peuvent être effacées par une exposition d’une vingtaine
de minutes aux rayons ultraviolets. Elles sont maintenant souvent remplacées par des
EEPROMs (Electrically EPROM), reprogrammables électriquement. Les mémoires
Flash sont également une forme de mémoires effaçables électriquement, mais on réserve
généralement le terme EEPROM aux mémoires capables d’effacer à l’échelle du mot, et
le terme ’mémoires Flash’ à celles qui effacent à l’échelle de blocs. Dans les deux cas, le
temps d’effacement est long par rapport au temps de lecture, et elles ne peuvent pas être
considérées comme une forme spéciale de RAM. On trouve de la mémoire EEPROM
et flash dans les assistants personnels, dans les sticks mémoire sur ports USB, pour le
stockage du firmware de nombreux appareils (BIOS d’ordinateurs, lecteurs de DVD, tuners
satellites, etc.)

10.2. Mode d’emploi des mémoires statiques asynchrones


Les mémoires statiques ont généralement une organisation et un mode d’emploi
118 CHAPITRE IV. ÉLÉMENTS FONCTIONNELS D’UN PROCESSEUR

spécifique. On a vu à la section précédente que chaque bit d’une telle mémoire était
stocké dans une bascule, ce qui conduit pour les formes les plus simples à séparer les
données à écrire des données lues, de la même façon que sont séparées l’entrée et la sortie
d’une bascule.
La figure IV.31 montre l’interface d’un tel circuit.

n n
DIN DOUT
m
ADR

CE

Figure IV.31. Interface d’un boîtier de RAM statique asynchrone de 2m mots de n bits,avec
séparation des données lues et des données à écrire.

Le mode d’emploi de ce circuit est le suivant :

Lecture d’un mot mémoire


1. activer la ligne CE (Chip Enable), placer l’adresse du mot sur les m lignes d’adresse
ADR et garder inactive la ligne W (write).

2. après un certain temps appelé temps d’accès en lecture, le mot mémoire (de largeur n)
placé à cette adresse est disponible sur les lignes DOUT (Data Out).

Écriture d’un mot mémoire


1. activer la ligne CE (Chip Enable), placer l’adresse du mot sur les m lignes d’adresse
ADR, placer la donnée à écrire sur les n lignes DIN (DataIn) et activer la ligne
W (write).

2. après un certain temps appelé temps d’écriture, le mot (de largeur n) est écrit en
mémoire, et pourra être lu ultérieurement.

Le temps d’écriture est généralement voisin du temps d’accès en lecture ; ces temps varient
d’une dizaine de nanosecondes pour les mémoires statiques les plus rapides à une centaine
de nanosecondes.
En pratique, les boîtiers disponibles mettent en commun les lignes DIN et DOUT, de
façon à minimiser le nombre de broches, et à faciliter l’interconnexion de plusieurs boîtiers.
De tels circuits ont l’interface et la structure suivante :
Une entrée supplémentaire OE (Output Enable) est nécessaire, qui fonctionne de la
façon décrite page 98. Lors de l’écriture d’un mot mémoire, la ligne OE doit être inactive,
et la donnée à écrire peut être placée sur DATA sans risque de court-circuit. Lors d’une
lecture en mémoire, la ligne OE doit être activée après le temps d’accès en lecture pour
que la donnée lue soit disponible sur DATA. On verra plus loin comment interconnecter ces
circuits ; les précautions déjà évoquées doivent être respectées ici, et en particulier le risque
10. RAMs et ROMs 119

n n DATA
m n DIN DOUT
ADR DATA
m
ADR ADR
W
CE W W

OE CE CE

OE

Figure IV.32. Interface d’un boîtier de RAM statique asynchrone de 2m mots de n bits,avec
données lues et écrites communes.

qu’il y a d’avoir à la fois OE active et une donnée présente en entrée de DATA, qui peut
conduire à la destruction du circuit.

10.3. Structure interne d’une mémoire statique asynchrone


Une mémoire statique de 2m mots de n bits est réalisée sous forme d’une matrice de
2 colonnes et n lignes de bascules D. Pour montrer un exemple facilement descriptible en
m

langage SHDL, on peut la voir également comme une ligne de 2m registres de n bits, tels qu’il
ont été décrits au chapitre précédent. La figure IV.33 montre l’implémentation d’une RAM
de 4 mots de 8 bits.

A1 A0 A1 A0 A1 A0 A1 A0

W W W W
mot #2 mot #3
mot #0 mot #1

D D D D
CLK CLK CLK CLK
CE CE CE CE
OE Q OE Q OE Q OE Q
OE
CE
W
8

DATA

MODULE RAM4X8(A[1..0],DIN[7..0],CE,W,OE: DOUT[7..0])


SEL0 = /A0*/A1 ;
SEL1 = /A0*A1 ;
SEL2 = A0*/A1 ;
SEL3 = A0*A1 ;
CLK[3..0] = SEL[3..0] * W ;
OEG = OE * CE * /W ;
OE_R[3..0] = OEG * SEL[3..0] ;
REG8(DIN[7..0],CLK0,SEL0,OE_R0:DOUT[7..0]);
REG8(DIN[7..0],CLK1,SEL1,OE_R1:DOUT[7..0]);
REG8(DIN[7..0],CLK2,SEL2,OE_R2:DOUT[7..0]);
REG8(DIN[7..0],CLK3,SEL3,OE_R3:DOUT[7..0]);
END MODULE

Figure IV.33. Implémentation en SHDL d’une RAM statique asynchrone de 4 mots de


8 bits.
120 CHAPITRE IV. ÉLÉMENTS FONCTIONNELS D’UN PROCESSEUR

Les sorties des registres sont reliées entre elles, mais il n’y a pas de risque de
court-circuit car leurs lignes OE sont indirectement reliées à quatre termes SEL3..SEL0
mutuellement exclusifs. Lorsqu’une adresse i est présentée sur A1,A0, seul le registre
d’indice i va avoir sa ligne CE activée, c’est à dire que lui seul va être sélectionné. S’il s’agit
d’une lecture (W = 0), il va fournir son contenu sur sa sortie si OE = 1. S’il s’agit d’une
écriture, le front montant sur W va créer un front montant sur l’entrée CLK de la bascule, et
donc écrire la donnée placée sur DATA dans le registre.
En fait, chacun de ces registres doit être vu comme une colonne de plusieurs bascules
D, ce qui donne en réalité l’organisation matricielle évoquée en début de section, qui dans
l’exemple comporterait 8 lignes et 4 colonnes. Ce type d’organisation est idéal pour une
réalisation industrielle ; ajouter une ligne d’adresse revient à multiplier par deux le nombre
de colonnes, tout en gardant la même structure générale. C’est ainsi que, la technologie
s’améliorant, la taille des puces mémoire continue de croître à peut près d’un facteur 2 tous
les 18 mois, c’est à dire à la vitesse prévue par la loi de Moore.

10.4. Mode d’emploi des mémoires dynamiques asynchrones


Les mémoires dynamiques ont généralement une organisation légèrement différente
des mémoires statiques, même si cette différence n’est pas à attribuer à leur nature statique
ou dynamique. Leur interface est celui de la figure IV.34.

n n
DIN DOUT
m
ADR

RAS
CAS
W

Figure IV.34. Interface d’un boîtier de RAM dynamique asynchrone de 22m mots de n bits.

On remarque d’abord qu’il n’y a pas de ligne de validation (CE ou CS), et que deux
nouvelles lignes RAS et CAS sont présentes. La légende de la figure parle de 22m mots de
n bits, alors qu’on ne voit que m lignes d’adresses. On va voir qu’en fait, ces m lignes sont
multiplexées, et qu’on y présente successivement un numéro de ligne sur m bits, puis un
numéro de colonne sur m bits, pour accéder à une cellule mémoire dans une matrice de
2 x 2 = 22m cellules mémoire. La présentation des numéros de ligne et de colonne est
m m

synchronisée par les signaux RAS et CAS.


RAS signifie sélection de ligne d’adresse (Row Address Select); CAS signifie sélection
de colonne d’adresse (Column Address Selection).Le boîtier est inactif tant que RAS et CAS
sont inactifs. Les chronologies de lecture et d’écriture sont montrées figures IV.35 et IV.36.
Pour une lecture ou une écriture, on place d’abord un numéro de ligne sur ADR, qui
est obtenu en prenant la moitié de l’adresse complète, généralement les poids forts et on
active ensuite la ligne RAS (front descendant). On place ensuite sur ADR l’autre moitié
de l’adresse complète, généralement les poids faibles, et on active ensuite la ligne CAS
(front descendant). En cas de lecture (W = 1) la donnée sera disponible sur DOUT ; en cas
10. RAMs et ROMs 121

11111
00000 111111
000000
00000
11111 000000
111111
ADR
numéro ligne numéro colonne

RAS

CAS

W
11111111111111111111
00000000000000000000
00000000000000000000
11111111111111111111
DOUT donnée
lue

Figure IV.35. Chronogramme de lecture dans une mémoire dynamique.

11111
00000 111111
000000
00000
11111 000000
111111
ADR
numéro ligne numéro colonne

RAS

CAS

W
11111111111111111111
00000000000000000000
00000000000000000000
11111111111111111111
DIN donnée
à écrire

Figure IV.36. Chronogramme d’écriture dans une mémoire dynamique.

d’écriture il faudra placer la donnée sur DIN.


Un cycle spécifique de rafraîchissement existe également, qui opère sur des lignes
entières de cellules. Chaque ligne doit être rafraîchie typiquement toutes les 5ms, et cela
n’est pas fait automatiquement par le circuit : c’est la logique de commande qui est autour
qui doit s’en charger.
Un tel mode de fonctionnement correspond à la structure interne de la figure IV.37.
On voit que, lorsque le numéro de ligne est présenté sur ADR et qu’un front descendant
est appliqué sur RAS, ce numéro de ligne est stocké dans le registre de ligne. Sa valeur est
décodée et sélectionne une des 2m lignes de la matrice. De la même façon, lorsque le numéro
de colonne est présenté sur ADR et qu’un front descendant est appliqué sur CAS, ce numéro
est stocké dans le registre de colonne, dont la valeur est également décodée et sélectionne
une des colonnes. Une des cellules mémoire est ainsi sélectionnée. En cas de lecture (W = 1)
la donnée sera disponible sur DOUT ; en cas d’écriture il faudra placer la donnée sur DIN.
La mise en oeuvre de ces circuits est plus complexe que celle des mémoires
asynchrones, puisqu’on ne peut plus présenter l’adresse entière directement. On est obligé
de la présenter en deux fois, généralement poids forts d’abord (numéro de ligne) et poids
faibles ensuite (numéro de colonne), ce qui peut être fait à l’aide d’un multiplexeur.
122 CHAPITRE IV. ÉLÉMENTS FONCTIONNELS D’UN PROCESSEUR

m
ADR

registre + décodeur colonne CAS

registre + décodeur ligne


m

amplificateurs

n n

RAS W DIN DOUT

Figure IV.37. Structure interne d’une mémoire dynamique de 22m mots de n bits.

11. Exercices corrigés

11.1. Exercice 1 : association de décodeurs

Énoncé
Associer deux décodeurs 2 vers 4 avec entrée de validation, pour former un décodeur
3 vers 8 avec entrée de validation. On pourra ajouter quelques composants combinatoires
simples si nécessaire.

Solution
Posons le problème en termes plus visibles. On cherche en fait à compléter la figure
suivante :
11. Exercices corrigés 123

S3 S7
E2 E1
S2 S6
E1 E0
S1 S5
E0
EN S0 S4

#1
S3 S3
E1
S2 S2
E0
S1 S1
EN
EN S0 S0
#2
Il est clair que les sorties du décodeur final seront l’union des sorties des deux
décodeurs #1et #2. Par ailleurs, lorsque EN = 0, aucun des deux décodeurs ne doit être actif,
ce qui peut être garanti avec une porte ET sur leur entrée EN. Cela donne le schéma partiel
suivant :
S7
S3
E2 E1
S6
S2
E1 E0
S5
S1
E0 S4
EN S0

#1
S3
S3
E1
S2
S2
E0
EN S1
S1
S0
EN S0

#2
Considérons maintenant le statut de E2 : lorsqu’il vaut 0, c’est que la valeur présente
sur le vecteur E2..E0 est comprise entre 0 et 3, et c’est une sortie du décodeur #2 qui doit
être activée ; lorsqu’il vaut 1, c’est que la valeur de E2..E0 est comprise entre 4 et 7, et c’est
une sortie du décodeur #1qui doit être activée.Par ailleurs, que ce soit pour le décodeur #1ou
#2, il est facile de voir que le numéro de la sortie à activer a pour indice la valeur de E1,E0.
Finalement, la solution est visible sur le schéma suivant :
124 CHAPITRE IV. ÉLÉMENTS FONCTIONNELS D’UN PROCESSEUR

S7
S3
E2 E1
S6
S2
E1 E0
S5
S1
E0 S4
EN S0

#1
S3
S3
E1
S2
S2
E0
EN S1
S1
S0
EN S0

#2
Bien sûr, puisque le module construit a lui même une entrée de validation, il peut
être associé à un autre pour former un décodeur 4 vers 16, etc. On a ici un bel exemple de
cascadabilité (ou récursivité matérielle).

11.2. Exercice 2 : fonctions combinatoires booléennes réalisées avec des


décodeurs, multiplexeurs ou mémoires

Énoncé
Réaliser la fonction majorité à trois entrées avec : un décodeur 3 vers 8, ou un
multiplexeur 8 vers 1, ou une ROM 8 × 1.

Solution
Le circuit qu’on cherche à implémenter possède trois entrées et une sortie. On rappelle
que la fonction majorité à trois entrées vaut 1 si et seulement si au moins deux de ses
entrées (sur trois) sont à 1 ; elle est utilisée notamment pour le calcul de la retenue dans
les additionneurs. D’un point de vue algébrique, si on appelle E2,E1,E0 les entrées, les 4
− −
minterms souhaités dans le résultat sont : E2 ∗ E1 ∗ E0, E2 ∗ E1 ∗ E0, E2 ∗ E1 ∗ E0, E2 ∗

E1 ∗ E0.
Avec un décodeur 3 vers 8, il suffit de relier les trois entrées aux entrées du décodeur.
Chacune des 8 sorties est associée à chacun des 8 minterms possibles pour une fonction
combinatoire à 3 entrées. On relie ensuite par un OU les 4 sorties correspondant aux 4
minterms désirés.

000
001
010
E2
011
E1
100
E0 101
110
111

Figure IV.38. Fonction majorité à 3 entrées réalisée avec un décodeur 3 vers 8.


11. Exercices corrigés 125

Avec un multiplexeur 8 vers 1 (figure IV.39), on sait que les entrées doivent être reliées
aux lignes de commande, qui sont bien au nombre de 3 pour un multiplexeur 8 vers 1.
Chacune des 8 entrées du multiplexeur correspond à un des 8 minterms possibles, et on force
un 1 sur chaque entrée associée à un minterm qu’on souhaite inclure.

0 0
0 1
0 2
1 3 MAJ3(E2..E0)
0 4
1 5
1 6
1 7

E2..E0

Figure IV.39. Fonction majorité à 3 entrées réalisée avec un multiplexeur 8 vers 1.On force
un 1 sur chaque entrée associée à un minterm qu’on souhaite inclure dans la fonction. Le
vecteur E2..E0 sert à sélectionner cette entrée.

Avec une ROM, on relie les trois entrées aux 3 lignes d’adresse de la mémoire (figure
IV.40). Elle en a bien trois, puisque l’énoncé nous parle d’une mémoire de 8 mots, soit 23. On
sait qu’alors la ROM peut implémenter n’importe quelle fonction combinatoire à 3 entrées,
pourvu que les mots mémoire qu’elle contient aient les bonnes valeurs. En l’occurrence,
chaque vecteur d’entrée est vu comme une adresse, et son contenu (ici un seul bit) doit être
la valeur attendue pour la fonction. On a indiqué sur le schéma le contenu de la ROM pour
chacune des adresses.

E2 000 : 0
E1 ADR 001 : 0 DATA
E0
010 : 0
011 : 1
100 : 0
1 EN 101 : 1
110 : 1
1 OE 111 : 1

Figure IV.40. Fonction majorité à 3 entrées réalisée avec une ROM 8 x 1. Chaque adresse
correspond à un minterm, et le contenu de la ROM à cette adresse est la valeur attendue
pour le fonction représentée.

On notera que les entrées de validation EN et de sortie OE sont forcées à 1, pour que
la mémoire soit active en permanence.
126 CHAPITRE IV. ÉLÉMENTS FONCTIONNELS D’UN PROCESSEUR

11.3. Exercice 3 : Équations d’un encodeur de priorité à 8 entrées

Énoncé
Écrire sous forme d’une somme de termes les plus simples possible les équations des
sorties d’un encodeur de priorité à 8 entrées.

Solution
On se reportera à la page 106 pour l’usage d’un encodeur de priorité. On rappelle la
table de vérité d’un tel encodeur :

E7 E6 E5 E4 E3 E2 E1 E0 NUM2..0 ACT
1 * * * * * * * 111 1
0 1 * * * * * * 110 1
0 0 1 * * * * * 101 1
0 0 0 1 * * * * 100 1
0 0 0 0 1 * * * 011 1
0 0 0 0 0 1 * * 010 1
0 0 0 0 0 0 1 * 001 1
0 0 0 0 0 0 0 1 000 1
0 0 0 0 0 0 0 0 000 0

NUM2 vaut 1 dès lors qu’une des entrées E4, E5, E6 ou E7 est active :
NUM2=E7+E6+E5+E4.
NUM1 vaut 1 pour deux groupes de lignes dans la table de vérité. On a :
NUM1 = E7 + /E7*E6 + /E7*/E6*/E5*/E4*E3 + /E7*/E6*/E5*/E4*/E3*E2
D’après la loi d’absorption, la variable E7 disparaît de plusieurs termes :
NUM1 = E7 + E6 + /E6*/E5*/E4*E3 + /E6*/E5*/E4*/E3*E2
E7 ayant disparu, la loi d’absorption s’applique pour E6, puis sur E3 :
NUM1 = E7 + E6 + /E5*/E4*E3 + /E5*/E4*/E3*E2
NUM1 = E7 + E6 + /E5*/E4*E3 + /E5*/E4*E2
On procède de façon analogue pour la simplification de NUM0 :

NUM0 = E7 + /E7*/E6*E5 + /E7*/E6*/E5*/E4*E3 + /E7*/E6*/E5*/E4*/E3*/E2*E1


NUM0 = E7 + /E6*E5 + /E6*/E5*/E4*E3 + /E6*/E5*/E4*/E3*/E2*E1
NUM0 = E7 + /E6*E5 + /E6*/E4*E3 + /E6*/E4*/E3*/E2*E1
NUM0 = E7 + /E6*E5 + /E6*/E4*E3 + /E6*/E4*/E2*E1
On retrouve ainsi les équations vues plus haut à la figure IV.15.
11. Exercices corrigés 127

11.4. Exercice 4 : association de mémoires statiques asynchrones

Énoncé
Associer deux mémoires asynchrones de 2n mots de m bits pour former une mémoire
de 2n mots de 2m bits, puis une mémoire de 2n + 1 mots de m bits.

Solution
première association
Il faut d’abord remarquer que dans les deux cas, la mémoire obtenue a bien un nombre
total de bits mémoire égal à la somme des bits mémoire des deux parties. Chaque boîtier
initial possède m × 2n bits mémoire, et leur somme fait donc m × 2n + 1. Dans la première
association, on aura 2n mots de 2m bits soit 2 × m × 2n = m × 2n + 1 ; dans la deuxième on
aura aussi les m × 2n + 1 bits.
Pour former une mémoire de 2n mots de 2m bits, il faut mettre essentiellement toutes
les lignes des deux boîtiers en commun, à l’exception des lignes de données qu’il faut mettre
en parallèle. Cela donne le circuit de la figure IV.41.

m
Data 2xm
Data

n RAM
ADR ADR
n
2 mots
CS CS
de m bits
OE OE
W W

m
Data

n RAM
ADR
n
2 mots
CS
de m bits
OE
R/W

Figure IV.41. Association de mémoires asynchrones, avec doublement de la largeur


des données.

Lorsqu’un accès mémoire (lecture ou écriture) est effectué, les deux boîtiers sont
sélectionnés en même temps à la même adresse, et fournissent ou acceptent simultanément
les deux moitiés de largeur m de la donnée globale de largeur 2m.
deuxième association
Pour former une mémoire de 2n + 1 mots de m bits, il va falloir mettre en commun les
m lignes de données, et donc il ne va plus être possible de sélectionner les deux boîtiers en
même temps, sous peine de court-circuit.Pour une moitié des adresses, il faudra sélectionner
128 CHAPITRE IV. ÉLÉMENTS FONCTIONNELS D’UN PROCESSEUR

le premier boîtier, et l’autre pour l’autre moitié des adresses. La manière la plus simple est ici
d’utiliser un des bits de l’adresse de n + 1 bits, par exemple adr[n], pour faire cette sélection
(figure IV.42).

m m
Data Data

addr[n−1..0] n
n+1 ADR
ADR addr[n] RAM
n
2 mots
0 CS
de m bits
OE
1:2
W
1
CS m
CS Data
n
ADR
OE RAM
n
CS 2 mots
de m bits
OE
W W

Figure IV.42. Association de mémoires asynchrones, avec doublement du nombre


d’adresses.

Ce bit est mis en entrée d’un décodeur 1 vers 2 : s’il vaut 0, c’est le boîtier mémoire du
haut qui est sélectionné ; s’il vaut 1 c’est le boîtier du bas. Tout cela suppose que le signal
CS global soit actif, car s’il ne l’est pas, le décodeur ne sélectionne aucun boîtier Le signal
OE suit un traitement particulier : pour chaque boîtier, il ne doit être actif que si le OE global
est actif, et si le boîtier est sélectionné (CS = 1). On est alors assuré qu’aucun court-circuit
n’est possible, puisque les OE des deux boîtiers sont alors mutuellement exclusifs.
Dans cette configuration, l’espace des adresses de l’ensemble est [ 0 , 2n + 1 − 1] ;
la première moitié [0 , 2n − 1] correspond au premier boîtier ; la seconde [2 2n + 1 − 1]
n,

correspond au second. Si on avait utilisé le bit de poids faible de l’adresse globale comme
entrée du décodeur, la distribution des adresses entre les boîtiers aurait été différente : l’un
aurait contenu les adresses paires, et l’autre les adresses impaires.
Chapitre V

Programmation du processeur 32 bits


CRAPS/SPARC
Le langage machine (et sa forme équivalente symbolique dite assembleur) est exécuté
directement par le processeur, et il est à ce titre l’intermédiaire obligé de tous les composants
logiciels d’un ordinateur. Même s’il est vrai que peu de gens programment directement en
assembleur, son étude est éclairante pour la compréhension de la relation entre logiciel et
matériel. Le but de ce présent chapitre est d’initier à la programmation en assembleur du
processeur CRAPS au travers de programmes de différentes natures, de façon à ensuite
mieux comprendre son architecture et son fonctionnement.

1. Le langage machine, à la frontière entre logiciel et matériel


Langage machine et ISA
Les instructions qu’exécute un processeur sont écrites dans un langage appelé langage
machine, et sont formées de codes écrits dans des mots mémoires consécutifs ; on appelle
ISA (Instruction Set Architecture) ce langage et son organisation. L’informatique utilise un
grand nombre de langages différents : des langages de programmation tels que C ou Java
pour les programmeurs, des langages interprétés tels que les langages de macro des suites
bureautiques, des langages de gestion de bases de données, etc. Mais la particularité du
langage machine, c’est d’être le langage intermédiaire en lequel tous les autres langages vont
être traduits, en une ou plusieurs étapes. Il est situé à la frontière entre le processeur et les
logiciels, entre hardware et software, et le processeur est l’interpréteur de ce langage (figure
V.1), celui qui met en action ses instructions.

ISA générale ou spécialisée ?


Bien sûr, il serait plus efficace d’avoir un processeur qui exécute directement
un langage évolué, tout au moins pour les programmes écrits dans ce langage. De tels
processeurs existent ou ont existé, notamment pour le langage Lisp, et plus récemment pour
le langage intermédiaire (bytecode) utilisé en Java. Ces deux langages sont généralistes et
peuvent être utilisés dans toutes les situations concrètes de l’informatique, et l’espoir des
concepteurs de ces processeurs était d’élever au niveau de ces langages la frontière entre
matériel et logiciel. Mais le bytecode Java exploite une machine à base de pile, peu adaptée
à la compilation d’autres langages de programmation tels que C. Finalement, le compromis
généralement adopté lors de la conception d’un nouveau processeur est le développement
d’une ISA adaptée à la compilation de la plupart des langages de programmation courants
.

129
130 CHAPITRE V. PROGRAMMATION DU PROCESSEUR 32 BITS CRAPS/SPARC

programme
gestionnaire en Javascript
de bases de données
(langage SQL) programme
en langage C
interprétation

interprétation
compilation

logiciel
langage machine
matériel

interprétation

processeur

Figure V.1. Tous les programmes produisent du langage machine, qui est exécuté directe-
ment par le processeur.

Évolution des ISA


Comme toute structure située à une frontière, l’ISA est un équilibre entre des forces
opposées. Les concepteurs de compilateurs souhaitent une ISA régulière et possédant le plus
de possibilités en termes d’adressage mémoire, d’opérations arithmétiques, etc., alors que
les architectes des processeurs veulent une ISA simple à implémenter efficacement.
Une force importante qui limite l’apparition de nouvelles ISAs (de nouveaux langages
machine) est la demande des clients d’une compatibilité ascendante d’un processeur
d’une génération à l’autre, de façon à pouvoir faire fonctionner sans changement leurs
anciens programmes. Cette force se manifeste de façon très puissante avec les processeurs
de PC (dont l’ISA est souvent désignée par x86, parce que les noms des processeurs se
terminent par 86 : 8086, 80286, etc.), pour laquelle tout changement conduisant à une non
compatibilité est exclu. Ainsi, les processeurs x86 les plus récents continuent à manifester la
bizarrerie d’écriture en mémoire inversée de type little-endian, parce qu’elle était présente
à l’origine sur le processeur 8 bits Intel 8080 pour des motifs de simplicité de conception,
et qu’elle s’est transmise pour raison de compatibilité tout le long de la chaîne 8080, 8085,
8086, 80286, 80386, Pentium, Pentium 2, etc. jusqu’aux processeurs des PCs actuels.
D’autres processeurs moins connus ont également une lignée très longue et respectable. On
peut citer le processeur ARM, un des premiers processeurs 32 bits commercialisés, qui fait
évoluer son ISA de façon ascendante depuis les années 80 jusqu’à maintenant, et qui équipe
un grand nombre de téléphones portables, les consoles de jeux portables Nintendo et un
grand nombre d’appareils mobiles.

Processeurs CISC et RISC


Une nouvelle génération de processeurs est apparue dans les années 80, caractérisée par
un jeu d’instruction limité à l’essentiel, d’où leur nom de processeurs RISC (Reduced
Instruction Set Computer). Par ailleurs, leur ISA est généralement très régulière, contient
beaucoup de registres utilisateurs, et les instructions sont codées sous forme d’un mot
mémoire unique. Tout cela rend heureux les architectes du processeur, qui peuvent
concevoir des architectures très efficaces pour cette ISA, et ne rend pas trop malheureux les
1. Le langage machine, à la frontière entre logiciel et matériel 131

concepteurs de compilateurs, qui ont un peu plus de difficulté à traduire les programmes,
mais qui apprécient le gain en vitesse d’exécution. Par opposition, les anciens processeurs
tels que les x86 ont été rebaptisés processeurs CISC (Complex Instruction Set Computer),
et si ce n’était l’énorme investissement dû au succès du PC avec l’ISA x86, les processeurs
RISC auraient connus un développement plus immédiat et plus large. On les trouve
néanmoins dans la plupart des stations de travail, dans les processeurs de Macintosh, et dans
les consoles de jeux Sony PS[123], avec les performances que l’on sait.

2. Ressources du programmeur CRAPS/SPARC


Le langage machine de CRAPS est un sous ensemble du langage machine du SPARC
de SUN (au cas où vous ne l’auriez pas encore remarqué, ’CRAPS’ = ’SPARC’ à l’envers !),
avec un certain nombre de limitations. C’est donc un langage de type RISC, qui sera
compatible avec le SPARC au niveau binaire. Néanmoins il n’exploite pas la notion SPARC
de fenêtre de registres et il n’a pas d’instruction d’arithmétique flottante.

Figure V.2. Le simulateur du processeur CRAPS. Il permet de développer des programmes


et d’observer leur fonctionnement logiciel et matériel jusqu’à l’échelle du bit. Une fois au
point, ils peuvent être transférés en mémoire et exécutés par le processeur réel.

Afin de rendre plus concrètes les descriptions qui sont suivre, on va progressivement
programmer le petit algorithme suivant qui fait la somme des 10 éléments d’un tableau :

résultat <- 0 ;
pour i de 0 à 9 faire
résultat <- résultat + TAB[i] ;
fin pour

2.1. Types de données de CRAPS


CRAPS est un processeur 32 bits : cela signifie que les mots mémoire qu’il manipule
le plus directement ont 32 bits de large, et que tous ses registres sont de 32 bits. Il peut
néanmoins lire et écrire en mémoire des mots de 8 bits (octets) avec des instructions d’accès
mémoire spécifiques.
Les entiers signés sont codés en complément à 2 et les opérations arithmétiques
disponibles sont l’addition et la soustraction sur 32 bits, la multiplication 16 bits X 16 bits
vers 32 bits et la division 32 bits / 16 bits vers 32 bits. Il ne dispose pas de registres pour
les nombres flottants, ni d’instruction d’arithmétique flottante. Si des calculs sur des réels
doivent être effectués, ils doivent être exécutés par des librairies logicielles spécialisées.
Aucun autre type de données n’est explicitement supporté. Les chaînes de caractères,
les listes etc. devront être assemblées et manipulées en combinant les opérations sur les
octets et les mots de 32 bits.
132 CHAPITRE V. PROGRAMMATION DU PROCESSEUR 32 BITS CRAPS/SPARC

2.2. Les registres de CRAPS

Les registres banalisés


CRAPS possède 32 registres directement accessibles au programmeur (figure V.3). On

toujours 0 %r0
%r1
%r2 registres
banalisés

...

%r29
pointeur
de pile %r30

utilisé %r31
par call

Figure V.3. Les registres du processeur CRAPS. Certains ont déjà des fonctions assignées.

les note %r0, … %r31. Ils sont tous accessibles librement en lecture et en écriture, mais
%r0 a un statut spécial : il vaut toujours 0 en lecture, quelles que soient les écritures qu’on
fasse dedans. Cette bizarrerie est en fait très commode et on verra par la suite comment elle
est exploitée.
%r30 sera généralement utilisé comme pointeur de pile (mais c’est une convention qui
n’est pas imposée par le matériel), et %r31 sert à stocker l’adresse de retour des instructions
call. On utilisera souvent un troisième registre tel que %r29 comme pointeur de stack
frame (voir plus loin). Si on excepte donc ces 4 registres spéciaux, le programmeur dispose
de 28 registres qu’il peut utiliser à sa guise dans ses opérations locales, ce qui est un grand
confort et permet de limiter grandement le nombre d’accès à la mémoire centrale.
Notre algorithme se transforme de la façon suivante :
%r1 <- 0;
pour %r2 de 0 à 9 faire
%r3 <- TAB[%r2] ;
%r1 <- %r1 + %r3 ;
fin pour

Trois registres utilisateurs sont déjà mobilisés, et ce n’est qu’un début ! On voit l’intérêt
qu’offre au programmeur une ISA fournissant beaucoup de registres utilisateurs.

Les registres spéciaux


Les autres registres ne sont généralement pas accessibles directement par le programmeur
et sont utilisés pour la gestion du fonctionnement interne du processeur. On trouve :

• %pc (Program Counter), qui contient l’adresse de l’instruction en cours d’exécution.

• %ir (Instruction Register), qui contient l’instruction en cours d’exécution.


2. Ressources du programmeur CRAPS/SPARC 133

• %psr (Program Status Register) qui contient des informations sur l’état de l’exécution.
Son format est donné figure V.4, et son rôle détaillé sera décrit dans des sections
ultérieures.

icc

N Z V C PIL S ET
31 23 20 11 8 7 5 0

icc = codes conditions (N, Z, V, C)


PIL = niveau d’exécution courant
S = bit superviseur
ET = enable trap = exceptions autorisées

Figure V.4. %psr. (Program Status Register)

• %tbr (Trap Base Register), qui contient l’adresse de la table des exceptions. Son rôle
détaillé sera décrit dans les sections ultérieures.

2.3. Le modèle mémoire de CRAPS


Comme la plupart des processeurs actuels, l’ISA de CRAPS voit la mémoire sous
forme d’un ruban d’octets situés à des adresses consécutives (figure V.5).

octets ...
32 32 32
adresse 0 1 2 3 4 2 −3 2 −2 2 −1

Figure V.5. La mémoire de CRAPS est un ruban d’octets,numérotés par des adresses allant
de 0 à 232 − 1.

Les adresses seront présentes dans CRAPS sous forme de mots de 32 bits, définissant
ainsi une mémoire centrale pouvant compter jusqu’à 232 cases, soit 4 giga-octets, les
adresses allant de 0 à 232 − 1. Le fait d’avoir 32 lignes d’adresses sera commode dans CRAPS
pour manipuler les adresses à l’intérieur des registres. Ce choix n’était pas obligatoire, et on
aurait pu imaginer un nombre de lignes d’adresses inférieur ou supérieur à la taille du mot
mémoire du processeur, ici 32.
En pratique, l’ensemble de cet espace d’adressage n’est souvent pas entièrement
occupé par de la mémoire physique. Un mécanisme de décodage des adresses que l’on
étudiera au chapitre suivant permettra d’associer un bloc d’adresses particulier à un boîtier
mémoire particulier ; par ailleurs la lecture et l’écriture à certaines adresses ne conduira pas
à un simple stockage, mais déclenchera la commande de certains circuits, tels que le timer ou
le circuit d’entrées-sorties,selon un mécanisme appelé entrées/sortiesmappées en mémoire,
dont on étudiera l’implémentation au chapitre suivant, mais que l’on apprendra à utiliser en
tant que programmeur un peu plus loin dans ce chapitre.
Reprenons notre petit algorithme de somme des nombres d’un tableau. On va désigner
par mem[adr] l’octet en mémoire situé à l’adresse adr. Notre tableau TAB en mémoire
centrale est formé de mots de 32 bits situés consécutivement. Comme chaque nombre de
134 CHAPITRE V. PROGRAMMATION DU PROCESSEUR 32 BITS CRAPS/SPARC

32 bits occupe nécessairement 4 octets, les valeurs du tableau sont espacées de 4 octets en 4
octets en mémoire centrale, à des adresses croissantes à partir de l’adresse TAB de début du
tableau. Les 4 octets qui forment le nombre TAB[i] pour un indice i quelconque sont ainsi
situés de l’adresse TAB + 4i à l’adresse TAB + 4i + 3 (figure V.6).

élément i élément i+1

octets ... ... ...

adresses TAB TAB+4i TAB+4i+3

Figure V.6. Implantation mémoire d’un tableau de nombres de 32 bits.

Notre algorithme devient :


%r1 <- 0 ;
%r4 <- adresse de TAB ;
pour %r2 de 0 à 9 faire
%r3 <- mem[%r4+4*%r2,...,%r4+4*%r2+3] ;
%r1 <- %r1 + %r3 ;
fin pour

Pour éviter d’avoir à effectuer la multiplication par 4, on peut incrémenter %r4 de 4 à chaque
tour de boucle :
%r1 <- 0 ;
%r4 <- adresse de TAB ;
pour %r2 de 0 à 9 faire
%r3 <- mem[%r4,...,%r4+3] ;
%r1 <- %r1 + %r3 ;
%r4 <- %r4 + 4 ;
fin pour

Écriture en mémoire big-endian et little-endian


Un détail d’implémentation n’a pas été précisé. On a dit qu’un mot de 32 bits était stocké en
mémoire centrale sous forme de 4 octets consécutifs à des adresses i, i + 1, i + 2, i + 3, mais
on n’a pas précisé exactement dans quel ordre. Bien sûr, par des opérations fines d’écriture
au niveau de l’octet, notre programme pourrait les stocker dans n’importe lequel des 24 =
16 ordres possibles. Mais un processeur 32 bits est là pour manipuler directement des mots
de 32 bits, et en particulier pour lire et écrire en mémoire des mots de 32 bits, et il fait cela
selon un ordre qui lui est propre. En pratique, seuls deux tels ordres de stockage sont utilisés.
L’ordre dit big-endian, le plus naturel, est utilisé par la grande majorité des processeurs, à
l’exception (très notable) des processeurs x86. Il consiste à stocker les octets de telle façon
que le mot mémoire se lise dans l’ordre naturel, si on visualise le ruban mémoire de gauche
à droite. Ainsi pour stocker en mémoire le mot de 32 bits 0x11223344, on stockera à des
adresses croissantes les octets 0x11, 0x22, 0x33, 0x44. L’ordre dit little-endian, utilisé par les
processeurs x86, stockera ce même mot dans l’ordre inverse 0x44, 0x33, 0x22, 0x11 (figure
V.7).
2. Ressources du programmeur CRAPS/SPARC 135

octets ... 0x11 0x22 0x33 0x44 ... big−endian

adresse 0 ... i i+1 i+2 i+3

octets ... 0x44 0x33 0x22 0x11 ... little−endian

adresse 0 ... i i+1 i+2 i+3

Figure V.7. Un même mot de 32 bits 0x11223344 stocké en mémoire centrale dans l’ordre
big-endian et little-endian.

Cet ordre de stockage peu naturel des processeurs x86 prend son origine dans le
processeur Intel 8080, qui stockait déjà un mot de 16 bits en inversant les deux octets,
par commodité de conception. Pour des raison de compatibilité ascendante de leur ISA,
Intel a été obligé de conserver cet état de fait dans les successeurs 8 bits du 8080, et même
de l’étendre de façon logique au stockage des mots de 32 bits, puis de 64 bits dans leurs
dernières versions de Pentium.
Les termes étranges big-endian et little-endian proviennent des ’Voyages de Gulliver’
d’Oliver Swift, lorsque Gulliver est au pays des politiciens. Une bataille politique fait rage
sur la façon de casser son oeuf à la coque, entre le parti des ’big-endians’, partisans de le
casser par le gros bout, et celui des ’little-endians’ partisans du petit bout ! Cette différence
d’ordre de stockage est une source d’incompatibilité majeure entre les processeurs x86 et
les autres. On peut noter que le SPARC peut modifier son ordre de stockage à la volée, par
le positionnement d’un bit de contrôle spécial.

2.4. Les instructions de CRAPS


Toutes les instructions de CRAPS sont constituées d’un seul mot de 32 bits. Cette
caractéristique des instructions d’avoir une taille fixe d’un mot est typique des processeurs
RISC, et elle facilite grandement certains aspects de leur conception. Les instructions de
CRAPS sont formées de plusieurs groupes, chacun ayant une certaine structure de codage
(figure V.8).
La valeur du champ op permet d’identifier immédiatement le groupe de l’instruction.
À l’intérieur des groupes 2 et 3, ce sont les champs op2 et op3 qui permettent de distinguer
les sous-groupes. Ces champs op, op2, op3 sont appelés opcodes et sont un moyen de classer
les instructions. Le tableau de la figure V.9 donne la signification de leurs valeurs pour
le processeur CRAPS. Toutes les instructions du SPARC n’ont pas été reprises, mais on a
conservé les mêmes valeurs d’opcodes pour celles qui l’ont été.
Les différentes combinaisons possibles entre ces valeurs d’opcodes sur les formats
d’instructions conduisent au tableau d’instructions de la figure V.10.
Ce tableau est remarquablement réduit ; SPARC/CRAPS mérite véritablement
son qualificatif de processeur RISC. Les versions suivantes du SPARC, notamment
l’UltraSparc II sur 64 bits, vont ajouter à cet ensemble beaucoup de formats d’instructions
supplémentaires, et perdre de cette élégante simplicité.
136 CHAPITRE V. PROGRAMMATION DU PROCESSEUR 32 BITS CRAPS/SPARC

op Format 1 : call
0 1 disp30

Format 2 : sethi et branchements


op
0 0 rd op2 imm22 2a
op
0 0 0 cond op2 disp22 2b

Format 3 : accès mémoire, instructions arithmétiques


op
1t rd op3 rs1 0 −− −− − − − − rs2 3a

1t rd op3 rs1 1 simm13 3b


t = 1 : accès mémoire, t = 0 : instruction arithmétique

Figure V.8. Structure binaire des différents groupes d’instructions de CRAPS.

op2 Instr. op3 (t = 0) Instr. op3 (t = 1) Instr. cond Branchement


010 branch 000000 add 000000 ld 1000 ba
100 sethi 010000 addcc 000001 ldub 0001 be
000100 sub 000100 st 1001 bne
010100 subcc 000101 stb 0101 bcs
011010 umulcc 1101 bcc
010001 andcc 1110 bpos
010010 orcc 0110 bneg
010011 xorcc 0111 bvs
010111 xnorcc 1111 bvc
110101 sll 1010 bg
110110 srl 0010 ble
111000 jmpl 1011 bge
0011 bl
1100 bgu
0100 bleu

Figure V.9. Significations des valeurs d’opcodes.

Langage assembleur
Dans ce tableau V.10, les instructions ont été écrites, non pas en langage machine qui
aurait été peu compréhensible, mais dans une écriture symbolique équivalente dite
écriture assembleur. Pour chaque processeur, le constructeur fournit le descriptif complet
2. Ressources du programmeur CRAPS/SPARC 137

load/store
ld [%ri+reg/cst],%rj lecture mémoire 32 bits
ldub [%ri+reg/cst],%rk lecture mémoire 8 bits
st %ri,[%rj+reg/cst] écriture mémoire 32 bits
stb %ri,[%rj+reg/cst] lecture mémoire 8 bits

contrôle
call addr appel de sous-programme
jmpl addr, %ri jump and link
ba addr branchement inconditionnel
bcc addr branchement si condition cc vraie

système
rdpsr lecture de %psr
wrpsr écriture dans %psr
wrtbr écriture dans %tbr

arithmétiques et logiques
addcc %ri,reg/cst,%rj addition %rj←%ri+reg/cst
subcc %ri,reg/cst,%rj soustraction %rj←%ri-reg/cst
umulcc %ri,reg/cst,%rj multiplication %rj←%ri*reg/cst
udivcc %ri,reg/cst,%rj division %rj←%ri/reg/cst
andcc %ri,reg/cst,%rj AND %rj←%ri and reg/cst
orcc %ri,reg/cst,%rj OR %rj←%ri or reg/cst
xorcc %ri,reg/cst,%rj XOR %rj←%ri xor reg/cst
xnorcc %ri,reg/cst,%rj XNOR %rj←%ri xnor reg/cst
sll %ri,reg/cst,%rj décalage à gauche %rj←%ri << reg/cst
srl %ri,reg/cst,%rj décalage à droite %rj←%ri >> reg/cst
sethi val22, %ri %ri21..0 ← val22
reg/cst : registre ou constante signée sur 13 bits
val22 : constante non signée sur 22 bits

Figure V.10. Les groupes d’instructions du processeur CRAPS.

du jeu d’instruction en langage machine, mais aussi une écriture assembleur conseillée
équivalente. Les développeurs peuvent alors écrire des programmes assembleurs, dits
encore assembleurs, dont le rôle est de traduire un texte source écrit en langage assembleur,
en la suite des codes machine correspondants. Le terme assembleur désigne donc à la
fois le langage du processeur (sous sa forme symbolique) et le programme qui traduit un
texte source du langage assembleur vers le langage machine. La figure V.11 montre une
138 CHAPITRE V. PROGRAMMATION DU PROCESSEUR 32 BITS CRAPS/SPARC

instruction écrite en assembleur, et son code machine équivalent.

addcc %r2, 10, %r3 ↔ 10.00011.010000.00010.1.0000000001010

Figure V.11. Une instruction en assembleur CRAPS/SPARC et le code machine corres-


pondant.

Instructions synthétiques
Certaines instructions semblent manquer. Par exemple, il n’y a pas d’instruction de copie
d’un registre vers un autre, ou d’instruction qui compare 2 registres, ou encore d’instruction
qui mette à zéro un registre. En fait, il n’est pas nécessaire que ces instructions existent, car
elles sont des cas particuliers d’instructions plus générales. Par exemple la mise à 0 d’un
registre %ri peut se faire par l’instruction orcc %r0, %r0, %ri. On exploite ici le fait
que %r0 vaut toujours 0 ; le OR de 0 avec %r0 (qui vaut 0) est ainsi stocké dans %ri, ce qui
est le résultat souhaité.
Dans la syntaxe du processeur CRAPS/SPARC figurent ainsi des instructions dites
synthétiques, qui sont en fait remplacées au moment de l’assemblage par la ou les
instructions équivalentes. Le tableau V.12 montre l’ensemble des instructions synthétiques
disponibles pour CRAPS.

Initialisation d’un registre par une constante


L’instruction synthétique set val,%ri sert à initialiser un registre %ri avec une
constante dite immédiate, c’est à dire fournie directement dans le programme. Cette
instruction synthétique est remplacée par deux instructions sethi val31..10,%ri et orcc
%ri,val9..0,%ri qui vont effectivement bien faire cette affectation, en positionnant
d’abord les 22 bits de poids fort avec sethi, puis les 10 bits de poids faibles avec orcc. Le
fait d’avoir à exécuter deux instructions pour initialiser un registre à une valeur immédiate
est typique des processeurs RISC. Ceux-ci ayant des instructions de la taille de leur mot
mémoire, ils ne peuvent pas dans une seule instruction fournir une donnée immédiate de
cette taille. On notera la présence de l’instruction synthétique setq (rajoutée par rapport
au SPARC) qui permet de faire une telle initialisation en une seule instruction lorsque la
constante est petite (13 bits signée, c’est à dire entre -4096 et +4095). Elle est équivalente à
orcc %r0, val, %ri qui tire encore parti du fait que %r0 vaut 0.
On notera également la très classique instruction mov %r1,%rj qui en réalité est :
orcc %ri,%r0,%rj,réalisant bien la copie du contenu de %ri dans %rj.
Nous pouvons maintenant mettre un certain nombre d’instructions CRAPS en face des
lignes de notre petit algorithme :
clr %r1 ; %r1 <- 0 ;
set TAB, %r4 ; %r4 <- adresse de TAB ;

pour %r2 de 0 à 9 faire

ld [%r4], %r3 ; %r3 <- mem[%r4,...,%r4+3];


addcc %r1, %r3, %r1 ; %r1 <- %r1 + %r3 ;
addcc %r4, 4, %r4 ; %r5 <- %r5 + 4 ;
2. Ressources du programmeur CRAPS/SPARC 139

Instruction Effet Implémentation

clr %ri met à zéro %ri orcc %r0, %r0, %ri

mov %ri,%rj copie %ri dans %rj orcc %ri, %r0, %rj

inccc %ri incrémente %ri addcc %ri, 1, %ri

deccc %ri décrémente %ri subcc %ri, 1, %ri

notcc %ri,%rj %rj <- complément de %ri xnorcc %ri, %ri, %rj

set val31..0, %ri copie val dans %ri sethi val31..10, %ri
orcc %ri, val9..0, %ri

setq val, %ri copie val dans %ri orcc %r0, val, %ri

( − 4096 ≤ val ≤ 4095)

cmp %ri, %rj compare %ri et %rj subcc %ri, %rj, %r0

tst %ri teste nullité et signe de %ri orcc %ri, %r0, %r0

negcc %ri inverse %ri subcc %r0, %ri, %ri

notcc %ri,%rj complémente %ri en %rj xnorcc %ri, %r0, %rj

nop no operation sethi 0,%r0

jmp %ri branchement à l’adresse absolue %ri jmpl %ri, %r0

ret retour de procédure terminale jmpl %r31+4, %r0

push %ri empile %ri sub %r30, 4, %r30


st %ri, [%r30]

pop %ri dépile %ri ld [%r30], %ri


add %r30, 4, %r30

Figure V.12. Les instructions synthétiques de CRAPS. Elles sont traduites en une écriture
équivalente.

fin pour

On peut remarquer la syntaxe des commentaires : un caractère ’;’et tout ce qui le suit jusqu’à
la fin de la ligne est ignoré, de façon analogue à la séquence ’//’ du C/C++.
Dans ce programme, ne manquent que les instructions qui vont permettre
d’implémenter la structure de contrôle pour … fin pour. Toutes les autres instructions
sont de nature séquentielle, c’est à dire qu’elles s’exécutent dans l’ordre où elles sont
présentes en mémoire, disposées à des adresses croissantes de 4 en 4.
140 CHAPITRE V. PROGRAMMATION DU PROCESSEUR 32 BITS CRAPS/SPARC

2.5. Ruptures de séquence en CRAPS


Bien sûr, si un processeur n’était doté que d’instructions séquentielles, il serait
incapable d’exécuter la moindre tâche un peu complexe, puisqu’incapable d’implémenter
des boucles ou des structures de contrôle de type tant que. Il faut donc absolument
qu’il soit doté d’instructions dites de rupture de séquence, qui permettent, non seulement
de ’sauter’ à une instruction qui n’est pas la suivante, mais aussi de faire ce saut de façon
conditionnelle, en fonction des résultats des opérations précédentes.
Un registre spécial de CRAPS/SPARC appelé %pc (program counter), non
directement accessible au programmeur, contient en permanence l’adresse de l’instruction
en cours d’exécution. À la fin de l’exécution d’une instruction séquentielle, %pc est
systématiquement incrémenté de 4, de sorte qu’au cycle d’exécution suivant, le contrôle est
passé à l’instruction suivante en mémoire, placée 4 octets plus loin.

Branchements inconditionnels
Pour effectuer un saut inconditionnel tel que ba addr, il suffit que cette instruction
n’effectue pas l’incrémentation de %pc de 4, mais au contraire ’force’ la valeur de
l’adresse addr dans %pc. Ainsi au cycle d’exécution suivant, le processeur chargera
l’instruction située à cette nouvelle adresse, et le contrôle sera dont transféré à ce point dans
le programme.

Branchements conditionnels
Ce saut peut également être fait de façon conditionnelle avec les instruction bcc telles que
bne, beq, bpos, etc. A chacune de ces instructions est associé un test booléen ; si ce test est
vérifié, le saut est effectué (%pc est alors affecté avec l’adresse de saut) ; sinon %pc est
incrémenté de 4, c’est à dire que le saut n’est pas fait et que le contrôle continue en séquence
dans le programme, à l’instruction qui suit ce branchement.
Le test en question est fait sur la valeur de 4 bits N, Z, V, C appelés indicateurs ou
flags, qui sont positionnés après certaines instructions arithmétiques et logiques. Ils ont les
significations suivantes :

• N indique que le résultat de l’opération est négatif, c’est à dire que son poids fort est
à 1.
• Z indique que le résultat de l’opération est nul.

• V indique qu’il y a eu débordement d’une addition ou d’une soustraction signée.

• C indique qu’il y a eu une retenue lors d’une addition ou emprunt lors d’une
soustraction.
Les instructions qui affectent les flags ont un nom qui se termine généralement par ’cc’ :
addcc, xorcc, etc. Pour être sûr des flags qui sont affectés ou non par une instruction, il faut se
reporter à sa description détaillée, présente à la fin de l’ouvrage. Par exemple les instructions
ld et ldub de lecture en mémoire affectent les flags Z et N selon que la valeur lue est (ou
non) nulle ou négative (respectivement). Ce choix est contraire à celui du SPARC, pour
lequel les instructions de lecture en mémoire n’affectent pas les flags. Nous avons jugé quant
à nous qu’une lecture en mémoire est souvent suivie d’un test sur la nullité ou le signe de la
valeur, et donc qu’il était intéressant d’avoir les flags positionnés.
2. Ressources du programmeur CRAPS/SPARC 141

Les instructions de branchement conditionnel bcc effectuent le saut pour certaines


combinaisons de valeurs des flags. Les tableaux des 3 figures V.13 à V.15 les décrivent,
le premier tableau présentant les branchements conditionnels les plus simples qui ne font
intervenir qu’un seul flag, le second présentant les branchements associés à une arithmétique
signée et le troisième les branchements associés à une arithmétique non signée.

instruction opération test


ba Branch Always 1
beq (synonyme : bz) Branch on Equal Z
bne (synonyme : bnz) Branch on Not Equal not Z
bneg (synonyme : bn) Branch on Negative N
bpos (synonyme : bnn) Branch on Positive not N
bcs Branch on Carry Set C
bcc Branch on Carry Clear not C
bvs Branch on Overflow Set V
bvc Branch on Overflow Clear not V

Figure V.13. Branchements conditionnels associés à un seul flag.

instruction opération test


bg Branch on Greater not (Z or (N xor V))
bge Branch on Greater or Equal not (N xor V)
bl Branch on Less (N xor V)
ble Branch on Less or Equal Z or (N xor V)

Figure V.14. Branchements conditionnels associés à une arithmétique signée.

instruction opération test


bgu Branch on Greater Unsigned not (Z or C)
bcc Branch on greater than, or equal, unsigned not C
bcs Branch on less than, unsigned C
bleu Branch on Less or Equal Unsigned Z or C

Figure V.15. Branchements conditionnels associés à une arithmétique non signée.

Les branchements du premier tableau qui ne font intervenir qu’un seul flag peuvent être
utilisés dans la plupart des situations simples, et on peut en les combinant n’utiliser qu’eux
dans des situations plus complexes.
Les branchements associés à l’arithmétique signée et non signée des deux tableaux
suivants ne se comprennent que si on suppose qu’une comparaison entre deux valeurs vient
142 CHAPITRE V. PROGRAMMATION DU PROCESSEUR 32 BITS CRAPS/SPARC

d’être faite, c’est à dire dans un programme de la forme :

cmp %ri, reg/val


bxx saut

bxx représente une des instructions de branchement conditionnel, et reg/val représente un


registre ou une constante. L’instruction synthétique cmp %ri,reg/val est équivalente à
subcc %ri,reg/val,%r0 c’est à dire qu’elle effectue la soustraction %ri - reg/val
dans le but d’obtenir, non pas la valeur du résultat en tant que telle (qui est éliminée dans
%r0), mais plutôt la valeur des flags après cette soustraction. Ceux-ci permettent en effet de
tout savoir sur les positions respectives de %ri et de reg/val :

• %ri = reg/val ⇔ %ri − reg/val = 0, soit Z.



• %ri > reg/val ⇔ %ri − reg/val > 0, soit N = 0 et Z = 0, soit N + Z.

• %ri ≥ reg/val ⇔ %ri − reg/val ≥ 0, soit N.

• %ri < reg/val ⇔ %ri − reg/val < 0, soit N.

• %ri ≤ reg/val ⇔ %ri − reg/val ≤ 0, soit N = 1 ou Z = 1, soit Z + N.

En arithmétique signée, on doit remplacer N par (N + V) dans ces équations pour tenir
compte des situations où on compare des grands nombres en valeur absolue, et où la
soustraction provoque un débordement mais qui permet tout de même de conclure. Cela
conduit alors aux équations du tableau V.14.
En arithmétique non signée, on doit remplacer N par C, car C va avoir la même valeur
que N lors de la soustraction de petites valeurs, mais va également permettre de conclure
dans des situations où la soustraction a une grande amplitude et change le signe du résultat,
telles que la comparaison de 0x00000001 et 0xFFFFFFFF : leur soustraction donne
0x00000002, avec N=0 et C=1. Seul C indique dans tous les cas qu’il a fallu emprunter pour
soustraire les deux arguments. Cela conduit aux équations du tableau V.15.
Maintenant, nous sommes prêts à terminer la programmation de notre algorithme :

;;;;;;;;;; somme des éléments d’un tableau ;;;;;;;;;


clr %r1 ; %r1 <- 0 ;
set TAB, %r4 ; %r4 <- adresse de TAB ;
clr %r2 ; pour i de 0 à 9
loop: ld [%r4], %r3 ; %r3 <- mem[%r4,...,%r4+3];
addcc %r1, %r3, %r1 ; %r1 <- %r1 + %r3 ;
addcc %r4, 4, %r4 ; %r5 <- %r5 + 4 ;
inccc %r2
cmp %r2, 9
bleu loop ; fin pour

%r2 est initialisé à 0 au début de la boucle pour … fin pour. En fin de boucle, %r2 est
incrémenté (incc %r2), puis comparé à 9. En toute rigueur il faut utiliser bleu puisqu’on
fait un test ≤ non signé, mais bpos aurait fonctionné également car les valeurs des indices
restent petites.
2. Ressources du programmeur CRAPS/SPARC 143

Ici l’indice i n’a été utilisé que comme un compteur. On peut raccourcir le programme
en initialisant %r2 à 10, et en le décrémentant à chaque tour de boucle jusqu’à 0 :

;;;;;;;; somme des éléments d’un tableau ;;;;;;;;


clr %r1 ; %r1 <- 0 ;
set TAB, %r4 ; %r4 <- adresse de TAB ;
set 10, %r2 ; compteur à 10
loop: ld [%r4], %r3 ; %r3 <- mem[%r4,...,%r4+3];
addcc %r1, %r3, %r1 ; %r1 <- %r1 + %r3 ;
addcc %r4, 4, %r4 ; %r5 <- %r5 + 4 ;
deccc %r2 ; décrémente compteur
bne loop ; reboucle tq compteur <> 0

On n’a rien gagné en nombre d’instructions brut, car l’instruction synthétique set
10,%r2 occupe en réalité 2 instructions. Mais on a fait passer le corps de la boucle de 5
à 4 instructions, soit un gain en vitesse de 20%. L’optimisation est négligeable sur ce
programme, mais elle peut devenir significative quand les boucles sont parcourues un grand
nombre de fois.

Branchements relatifs
Pour tous les branchements que nous venons de voir, ba et bcc, ce n’est pas l’adresse absolue
du saut qui est codée, mais un déplacement relatif à cette adresse. Si on reprend le codage
binaire de ces instructions (figure V.8), on voit que le branchement est codé dans le champ
disp22 qui code un déplacement signé, en nombre de mots. Le déplacement est signé, ce
qui signifie qu’une valeur négative conduira à un saut en arrière dans le programme, et un
déplacement positif à un saut en avant. Le déplacement est codé en nombre de mots, c’est
à dire qu’il faut le multiplier par 4 pour obtenir le déplacement en nombre d’octets. Ainsi,
puisque l’instruction en cours d’exécution a son adresse stockée dans le registre %pc, le saut
conduira à faire l’affectation :
%pc ← %pc + 4 * disp22

Par exemple, l’instruction de notre programme bne loop décrit un saut (conditionnel) de 4
instructions en arrière, soit un saut à une adresse plus petite de 16. C’est la valeur -4 qui est
codée, soit :
00.0.1001.010.1111111111111111111100 bne loop

Coder le saut sous forme d’un déplacement présente deux avantages :

1 c’est plus économique en mémoire, puisque tout le saut est codé en une instruction
d’un mot.
2 l’instruction obtenue est translatable, c’est à dire qu’un programme qui ne comprend
que de telles instructions peut être placé n’importe où en mémoire. C’est une
caractéristique importante des programmes lorsqu’ils doivent être chargés par le
système d’exploitation à une place libre en mémoire.

Le saut effectué par ces instructions ne peut pas être fait d’une extrémité de la mémoire à
l’autre. On peut voir sur le format binaire des instructions figure V.8 que le déplacement est
codé sur 22 bits, et donc on ne peut pas faire un saut de plus de 221 instructions en avant ou
144 CHAPITRE V. PROGRAMMATION DU PROCESSEUR 32 BITS CRAPS/SPARC

en arrière. En pratique c’est une valeur très suffisante tant que le saut est fait à l’intérieur
d’un même programme.
Branchements absolus
Dans certaines situations, on peut avoir le besoin de se brancher à une adresse absolue, et
non une adresse locale et relative au bloc de code courant. Le système d’exploitation a ce
besoin, lorsque son scheduler de tâches va enlever le contrôle à une tâche pour le donner à
une autre par exemple, ou lorsqu’il va exécuter un sous-programme d’interruption. Nous en
verrons des exemples plus loin dans ce chapitre.
L’instruction jmpl (jump and link) remplit cette fonction. Elle a la forme :

jmpl %ri+%rj/cst, %rk

L’adresse de saut est %ri+%rj/cst ; dans %rk est stockée l’adresse de l’instruction jmpl,
c’est à dire l’adresse d’où l’on part. Si on désire faire un saut simple à l’adresse contenue
dans %ri, on écrira :
jmpl %ri, %r0

Comme d’habitude dans cette écriture, le terme %rj/cst a disparu car il a été choisi égal à
%r0 ou à la constante 0, au choix. Cette expression peut s’écrire de façon plus simple avec
l’instruction synthétique jmp %ri.

2.6. Les modes d’adressage de CRAPS


Un mode d’adressage est un type d’accès aux données pour le processeur. Pour
CRAPS, ils sont au nombre de 4 :

1 l’adressage registre : la donnée est dans un registre, spécifié par son numéro. Par
exemple :
addcc %r1, %r2, %r3

Ici les 3 données manipulées sont dans des registres.

2 l’adressage immédiat : l’instruction elle-même, dans son codage en langage machine,


contient la donnée. Par exemple :
addcc %r1, 2, %r3

Ici la deuxième donnée (2) est codée dans le champ simm13 de l’instruction.

3 l’adressage direct (ou absolu) : l’instruction contient l’adresse de la donnée. Par


exemple :
ld [600], %r1

%r1 est chargé avec la donnée située en mémoire à l’adresse 600. Cette adresse 600
est codée dans l’instruction elle-même (un peu comme dans un adressage immédiat),
dans son champ simm13. Comme cette valeur ne peut pas dépasser 4095, ce mode
d’adressage est quasiment inutilisable dans CRAPS.
2. Ressources du programmeur CRAPS/SPARC 145

4 l’adressage indirect : l’instruction contient l’adresse de l’adresse de la donnée. Par


exemple :
ld [%r1], %r2

%r2 est chargé avec la donnée située à l’adresse contenue dans %r1. ’L’adresse de
l’adresse’est ici le numéro du registre (1) qui contient l’adresse de la donnée. Ce mode
d’adressage est indispensable pour accéder aux éléments d’un tableau de taille non
connue a priori. C’est par exemple celui qu’on a utilisé dans notre programme de calcul
de la somme des éléments d’un tableau, pour accéder aux nombres en mémoire.

5 l’adressage indirect avec déplacement. Par exemple :


ld [%r1-2], %r2

Ce cas est similaire au précédent, mais on ajoute au contenu du registre une constante
(ici -2) pour obtenir l’adresse de la donnée.

3. Directives d’un programme assembleur


Si on cherche à assembler effectivement, à l’aide de CRAPSEMU par exemple, le
programme de somme des éléments d’un tableau étudié à la section précédente, le symbole
TAB sera déclaré comme indéterminé. Il est en effet censé représenter l’adresse de début
du tableau, mais rien n’a été spécifié pour l’initialiser. Une directive dans un programme
assembleur est une ligne qui ne correspond pas à une instruction, mais qui spécifie :

1 la localisation en mémoire d’un bloc d’instructions ou de données.


2 la réservation et/ou l’initialisation d’un bloc de données.
Pour notre tableau TAB, nous avons besoin de spécifier à la fois son adresse, et son contenu
initial, si on suppose qu’il est donné et non qu’il est le résultat d’un calcul antérieur. Le
programme suivant contient les directives nécessaires :
;;;;;;;;;; zone de programme ;;;;;;;;;
.org 0

clr %r1 ; %r1 <- 0 ;


set TAB, %r4 ; %r4 <- adresse de TAB ;
clr %r2 ; pour i de 0 à 9
loop: ld [%r4], %r3 ; %r3 <- mem[%r4,...,%r4+3];
addcc %r1, %r3, %r1 ; %r1 <- %r1 + %r3 ;
addcc %r4, 4, %r4 ; %r5 <- %r5 + 4 ;
inccc %r2
cmp %r2, 9
bleu loop ; fin pour
ba *

;;;;;;;;;; zone de données ;;;;;;;;;


146 CHAPITRE V. PROGRAMMATION DU PROCESSEUR 32 BITS CRAPS/SPARC

.org 0x1000
TAB .word 12, -4, 5, 8, 3, 10, 4, -1, 1, 9

Adresse d’assemblage
La directive .org change l’adresse d’assemblage courante. Avec .org 0, le programme
sera assemblé à partir de l’adresse 0. Le tableau TAB sera situé plus loin en mémoire, à partir
de l’adresse 0x1000, grâce à la directive .org 0x1000.

Allocation et initialisation de mots en mémoire


La directive .word fournit une liste de nombres, écrits en décimal, hexadécimal ou binaire,
qui seront placés en mémoire à partir de l’adresse d’assemblage courante. Dans notre
programme exemple, cela nous permet de créer un tableau et de l’initialiser statiquement
(c’est à dire avant le lancement du programme) avec 10 mots de 32 bits. Il occupera 40 octets,
de 0x1000 à 0x1027 inclus. Voici ce que donne le listing d’assemblage complet, qui imprime
toutes les adresses mémoire (colonne de gauche) et leur contenu :
00000000 .org 0

00000000 82900000 clr %r1


00000004 12000002 set TAB, %r4
00000008 88912000
00000008 84900000 clr %r2
0000000C C6010000 loop: ld [%r4], %r3
00000010 82804003 addcc %r1, %r3, %r1
00000014 88812004 addcc %r4, 4, %r4
00000018 8480A001 inccc %r2
0000001C 80A0A009 cmp %r2, 9
00000020 08BFFFFB bleu loop
00000024 10800000 ba *

00001000 .org 0x1000


00001000 0000000C TAB .word 12,-4,5,8,3,10,4,-1,1,9
00001004 FFFFFFFC
00001008 00000005
0000100C 00000008
00001010 00000003
00001014 0000000A
00001018 00000004
0000101C FFFFFFFF
00001020 00000001
00001024 00000009

Par exemple à l’adresse 0x1000 on trouve 0x0000000C, c’est à dire 12 ; à l’adresse 0x1004
on trouve 0xFFFFFFFC, c’est à dire -4, etc.
3. Directives d’un programme assembleur 147

Allocation d’une chaîne de caractères


Si on souhaite allouer une chaîne de caractères ou n’importe quelle séquence d’octets en
mémoire, on utilisera la directive .byte. Un exemple fera mieux qu’un long discours :
MESSAGE .byte ’Tapez un nombre : ’, 10, 13, 0

.byte est suivi d’un nombre quelconque d’arguments séparés par des virgules. Ces
arguments peuvent être, soit des nombres entre 0 et 255 (comme 10, 13 et 0), soit des chaînes
de caractères qui seront remplacées par la suite de leurs codes ASCII. Dans l’exemple, les
octets suivants seront placés en mémoire à partir de l’adresse TEXT :
00001000 .org 0x1000
00001000 54977065 TEXT .byte ’Tapez un nombre :’,10,13,0
00001004 7A20756E
00001008 206E6F6D
0000100C 62726520
00001010 3A0A0D00

10 et 13 sont les codes ASCII des caractères ’retour chariot’et ’line feed’, qui provoquent un
retour à la ligne lors d’un affichage sur un terminal.

Utilisation de symboles
On peut utiliser des symboles pour représenter des valeurs constantes de façon plus lisible
dans un programme. On utilise pour cela une écriture du type :
N = 1000

Dans cet exemple, partout où N apparaîtra, il sera remplacé par 1000. Par exemple si on
écrit : set N, %r1, tout se passe comme si on avait écrit set N, %r1.
Par ailleurs, une arithmétique est utilisable sur de tels symboles. Si on voulait initialiser
un tableau avec les 3 premiers multiples de 15, on écrirait :
N = 15
TAB .word N, 2*N, 3*N

De telles expressions arithmétiques peuvent comporter des signes ’-’, ’+’, ’*’, ’/’ et des
parenthèses avec la syntaxe et la sémantique habituelle. Bien sûr, on aura compris que
les calculs sur ces expressions sont effectués au moment de la phase d’assemblage et
qu’ils ne conduisent à aucun de ces calculs par le processeur au moment de l’exécution du
programme. Si par exemple on écrit :
N = 15
set 2*N+4, %r1

,tout se passe comme si on avait écrit :


set 34, %r1
148 CHAPITRE V. PROGRAMMATION DU PROCESSEUR 32 BITS CRAPS/SPARC

Réservation d’un zone non initialisée en mémoire


La directive .malloc <n> réserve sans les initialiser <n> octets en mémoire. Elle
correspond en fait à un simple déplacement de l’adresse d’assemblage <n> octets plus
loin. Elle permet de réserver la place pour un tableau, ou n’importe quelle autre structure
de données, qui sera initialisée dynamiquement par programme, au lieu d’être définie
statiquement avec des directives .word et .byte.

4. Exemple de programme : crible d’Ératosthène


Afin d’illustrer de façon plus parlante les éléments de programmation en assembleur
qui viennent d’être présentés, on va écrire un programme qui recherche tous les nombres
premiers inférieurs à une certaine limite N, par la méthode du crible d’Eratosthène.

Algorithme
Cette méthode consiste à construire un tableau TAB[i], 0 ≤ i ≤ n dans lequel TAB[i] = 0
signifie que i est premier, et TAB[i] = 1 signifie que i n’est pas premier. Pour cela on remplit
d’abord tout le tableau de zéros. Ensuite, à partir de i = 2, on met à 1 TAB[2 ∗ i], TAB[3
∗ i], … jusqu’à la fin du tableau, puisque 2 ∗ i, 3 ∗ i, … ne sont pas premiers. On cherche
ensuite, après 2, le premier indice i du tableau pour lequel TAB[i] = 0. En l’occurrence c’est
3, qui est nécessairement premier car sinon il aurait été coché précédemment. On met alors
à 1 TAB[2i], TAB[3i], … jusqu’à la fin du tableau, puisque 2i, 3i, … ne sont pas premiers.
On recommence ensuite avec la prochaine valeur de i pour laquelle TAB [i ] = 0, qui est
nécessairement première, etc. On peut arrêter ce processus dès que i atteint √ N, car on peut
facilement montrer qu’alors tous les nombres non premiers ont déjà nécessairement été
marqués à 1 dans le tableau.
On peut traduire cette méthode par l’algorithme suivant :

pour i de 0 à N-1
TAB[i] <- 0 ;
fin pour
pour i de 2 à racine(N)
si TAB[i] = 0 alors
j <- 2*i ;
tant que (j < N)
TAB[j] <- 1 ;
j <- j + i ;
fin tq
fin si
fin pour

Programmation
D’après l’algorithme, les éléments du tableau TAB sont seulement les valeurs 0 ou 1. Un
tableau de bits aurait fourni le codage le plus compact, mais aurait été difficile à manipuler.
Un tableau de mots de 32 bits aurait été le plus facile d’accès, mais cela aurait été un
gaspillage de mémoire un peu inutile. On va donc utiliser un tableau d’octets, facile à
4. Exemple de programme : crible d’Ératosthène 149

accéder, et qui occupera quatre fois moins d’espace en mémoire qu’un tableau de mots de
32 bits.
On va d’abord mettre en place les directives de déclaration, notamment l’allocation des
N octets du tableau :
N = 1000
RAM = 0x1000000

.org RAM ; adresse en RAM


TAB .malloc N ; allocation de N octets

La valeur de N est définie de façon symbolique (ici avec 1000) ; il suffit de changer cette
ligne pour changer toutes les références à ce maximum. De même, le symbole RAM
définit le début de la zone de mémoire RAM sur la machine ; on va supposer ici que c’est
l’adresse 0x1000000.
La directive .org RAM déplace l’adresse d’assemblage au début de la zone de RAM,
de sorte que le tableau TAB sera situé au début de cette zone. La directive .malloc N
réserve un espace de N octets, sans l’initialiser, de sorte que l’adresse d’assemblage suivant
cette directive est RAM+N. Ainsi le programme dispose de N octets à partir de TAB, dans
lesquels il va stocker les éléments du tableau.
Passons maintenant à la mise à zéro des éléments du tableau :
N = 1000
RAM = 0x1000000

.org 0
erato set TAB,%r1
clr %r2 ; pour i de 0 à N-1
loop stb %r0,[%r1+%r2] ; TAB[i] <- 0
inccc %r2 ; élément suivant
set N,%r5
cmp %r2,%r5
bcs loop ; fin pour
...

.org RAM ; adresse en RAM


TAB .malloc N ; allocation de N octets

On a implémenté strictement l’algorithme, avec %r2 comme indice dans le tableau et %r1
comme pointeur sur le début du tableau. Pour écrire dans le tableau, on ne peut employer
qu’une des deux instructions d’écriture en mémoire, st ou stb. Puisqu’on écrit seulement
un octet, c’est stb qu’il faut employer. La forme générale de cette instruction étant :
stb %ri, [%rj+cst/%rk], le registre à écrire sera nécessairement %r0 (qui vaut 0)
et l’adresse de l’octet à écrire sera %r1+%r2 (adresse de début du tableau + indice dans le
tableau). Avec les choix qui ont été faits avant sur le type du tableau, on est nécessairement
conduits à l’instruction :
stb %r0,[%r1+%r2] ; TAB[i] <- 0
150 CHAPITRE V. PROGRAMMATION DU PROCESSEUR 32 BITS CRAPS/SPARC

Les trois instructions suivantes sont relatives à la recherche de l’indice suivant. On


commence d’abord par incrémenter l’indice :

inccc %r2 ; élément suivant

Il faut ensuite vérifier qu’il est inférieur à la limite N − 1 :


set N,%r5
cmp %r2,%r5

L’instruction synthétique cmp %r2,N n’est pas possible ici. Elle équivaut en effet à
l’instruction subcc %r2, N, %r0 et la valeur de N est trop grande pour la constante
possible à cette place dans le cas général, car elle est limitée à 13 bits, c’est à dire à
l’intervalle [ − 4096, 4095]. On doit donc passer par un registre intermédiaire %r5, avec
lequel on fait la comparaison. Les flags sont alors positionnés en fonction de la valeur de %r
2 − %r5, c’est à dire de i − N. Puisque l’algorithme mentionne une relation d’inégalité, c’est
la valeur du flag N qui nous intéresse. On a :

N = 1⇔ i−N < 0 ⇔ i < N ⇔ i ≤ N −1


On doit donc utiliser l’instruction bneg, qui effectue le branchement vers le début de la
boucle si N = 1 :
bneg loop ; fin pour

En fait, puisqu’il s’agit d’une comparaison non signée, il est même préférable d’utiliser
le branchement bcs, qui fait une comparaison de type inférieur strict, non signée. Elle
permettra en effet d’étendre l’algorithme à des valeurs de N très grandes, jusqu’au maximum
possible sur 32 bits. On écrira :

bcs loop ; fin pour

On vient de traduire littéralement l’algorithme qui était donné, à la façon d’un compilateur.
On peut néanmoins remarquer que l’indice %r2 n’a pas besoin d’être utilisé de façon
croissante dans cette partie du programme. On peut optimiser ce code en initialisant %r2 à
N − 1 et en le décrémentant à chaque tour de boucle avant de tester s’il est positif ou nul.
Cela donne :
. set TAB, %r1
set N-1, %r2 ; pour i de N-1 à 0
loop stb %r0,[%r1+%r2] ; TAB[i] <- 0
deccc %r2 ; élément suivant
bpos loop ; fin pour

On ajoute une instruction dans la partie initialisation (set occupe 2 instructions), mais
le corps de la boucle passe de 5 à 3 instructions. Le code global est plus petit d’une
instruction, et on a un gain en vitesse de 40% dans l’exécution de la boucle. On remarquera
que l’instruction cmp n’est plus nécessaire dans cette deuxième version, car le flag N est
directement utilisable après l’instruction deccc.
Dans le reste de l’algorithme, la structure de contrôle de plus haut niveau est de la forme
pour i de 2 à racine(N) ... fin pour. Cette fois l’indice i est utilisé de façon
croissante. On va le traduire comme dans la première version de l’initialisation du tableau :
4. Exemple de programme : crible d’Ératosthène 151

. setq 2,%r2 ; pour i de 2 a racine(N)


loopi ...
<corps de la boucle>
...
nexti inccc %r2
set RN,%r5
cmp %r2,%r5
bleu loopi ; fin pour

On remarquera l’usage du setq pour initialiser une petite constante, plus économique que
set qui utilise deux instructions.
Dans le corps de la boucle, on doit ensuite lire l’élément TAB[i] du tableau, et choisir
une autre valeur de i si cet élément de tableau vaut 1 :
. setq 2,%r2 ; pour i de 2 a racine(N)
loopi ldub [%r1+%r2], %r6
bne nexti ; si TAB[i] = 0
...

L’élément de tableau est lu en mémoire, c’est donc une instruction ld ou ldub qui doit être
utilisée, ici ldub puisque c’est une lecture d’octet. Le tableau commence à l’adresse %r1 et
on lit l’élément d’indice %r2 : il se trouve donc à l’adresse %r1+%r2. Il est lu dans %r6 et
cette lecture modifie les flags (voir détail de l’instruction ldub annexe B). L’instruction bne
nexti provoque la recherche d’un nouvel indice i si Z = 0, c’est à dire si %r6 est différent
de 0, donc égal à 1 ici.
L’instruction j <- 2*i~; s’implémente très simplement sous forme d’une
addition :
addcc %r2, %r2, %r3 ; j <- 2*i

On a ensuite une structure de contrôle tant que (j < N), qu’on va traduire :

loopj set N,%r5


cmp %r3,%r5
bpos nexti ; tant que (j < N)
...
<corps de la boucle>
...
ba loopj ; fin tq

Elle est analogue à l’implémentation de la structure pour … fin pour, mais le test est fait
en tête de boucle. %r5 est, ici encore, utilisé très temporairement pour stocker N, trop grand
pour être manipulé a priori comme une ’petite’ constante sur 13 bits.
Le corps de la boucle se traduit ensuite directement :
check setq 1, %r4
stb %r4, [%r1+%r3] ; TAB[i] <- 1
addcc %r3, %r2, %r3 ; j <- j + i
152 CHAPITRE V. PROGRAMMATION DU PROCESSEUR 32 BITS CRAPS/SPARC

Finalement, le programme complet est donné figure V.16. On notera l’instruction


d’arrêt sous forme de boucle infinie (’*’ représente l’adresse de l’instruction courante).

N = 1000
RN = 32
RAM = 0x1000000

.org 0
erato set TAB, %r1
set N-1, %r2 ; pour i de N-1 à 0
loop stb %r0, [%r1+%r2] ; TAB[i] <- 0
deccc %r2 ; élément suivant
bpos loop ; fin pour

setq 2, %r2 ; pour i de 2 a rac(N)


loopi ldub [%r1+%r2], %r6
bne nexti ; si TAB[i] = 0
addcc %r2, %r2, %r3 ; j <- 2*i
loopj set N, %r5
cmp %r3, %r5
bpos nexti ; tant que (j < N)
check setq 1, %r4
stb %r4, [%r1+%r3] ; TAB[i] <- 1
addcc %r3, %r2, %r3 ; j <- j + i
ba loopj
nexti inccc %r2
set RN, %r5
cmp %r2, %r5
bleu loopi ; fin pour

ba * ; boucle infinie (arrêt)

.org RAM ; adresse en RAM


TAB .malloc N ; allocation de N octets

Figure V.16. Programme complet du crible d’Ératosthène en CRAPS/SPARC. Il construit


un tableau d’octets tel qu’un élément d’indice i valant 0 indique que i est premier.

5. Appels de sous-programmes terminaux


Pour le moment, nous n’avons écrit que des programmes monolithiques, utilisables
une seule fois. Pourtant, il serait utile qu’un programme tel que la somme des éléments
d’un tableau puisse être utilisé plusieurs fois sur différents tableau durant l’exécution d’un
programme, sans pour autant avoir à dupliquer son code.
CRAPS fournit un tel mécanisme sous la forme des deux instructions call et ret :
1 call <adresse> sauvegarde l’adresse courante d’exécution dans le registre %r31,
puis provoque un branchement à l’adresse spécifiée, où est situé le sous-programme
qu’on souhaite appeler.

2 ret provoque le branchement à l’adresse %r31+4. Si le contenu de %r31 n’a pas


été modifié depuis l’appel call, l’exécution reprend à l’adresse qui suit celle de
l’instruction call.
5. Appels de sous-programmes terminaux 153

Tout se passe comme si call <adresse> était une instruction de somme de tableaux. On
montre sur le programme suivant comment notre programme de somme des éléments d’un
tableau a été transformé en sous-programme, et invoqué sur deux tableaux différents :

;;;;;;;;;; programme principal ;;;;;;;;;


set TAB1, %r4 ; arg. 1er appel
call tabsum ; 1er appel
set TAB2, %r4 ; arg. 2eme appel
call tabsum ; 2eme appel
ba * ; arrêt

;;;;;;;;;; sous-programme ;;;;;;;;;


tabsum clr %r1 ; %r1 <- 0 ;
clr %r2 ; pour i de 0 à 9
loop: ld [%r4], %r3 ; %r3 <- mem[%r4,...,%r4+3];
addcc %r1, %r3, %r1 ; %r1 <- %r1 + %r3 ;
addcc %r4, 4, %r4 ; %r5 <- %r5 + 4 ;
inccc %r2
cmp %r2, 9
bleu loop ; fin pour
ret

;;;;;;;;;; zone de données ~;;;;;;;;;


.org 0x1000
TAB1 .word 1, 2, 3, 4, 5, 6, 7, 8, 9, 10
TAB2 .word 2, 4, 6, 8, 10, 12, 14, 16, 18, 20

Ce mécanisme call … ret est appelé appel de sous-programmes terminaux, c’est à dire
de sous-programmes situés au dernier niveau d’appel. On l’aura sans doute déjà compris, il
n’est pas possible avec ce mécanisme d’appeler un autre sous-programme à partir du corps
d’un sous-programme appelé, puisque la première adresse de retour serait écrasée par la
seconde dans %r31. Il nous manque maintenant un mécanisme qui permette de sauvegarder
cette valeur ; il est présenté à la section suivante.

6. Utilisation de la pile
Principe
Une pile est une structure de données qui se manipule uniquement avec les deux opérations
suivantes :
1 PUSH data : sauvegarde (empile) data dans la pile.

2 POP : enlève le sommet de la pile, et le renvoie.

On peut sauvegarder a priori autant de données qu’on le souhaite ; l’inconvénient est


qu’on ne peut pas récupérer n’importe quand une donnée sauvegardée : on doit avant avoir
récupéré toutes celles qui ont été empilées après elle.
Une telle pile pourrait être implémentée de plusieurs façons, à commencer par une pile
154 CHAPITRE V. PROGRAMMATION DU PROCESSEUR 32 BITS CRAPS/SPARC

matérielle à base d’un grand tableau de registres. La méthode la plus simple (mais aussi la
plus lente) est de stocker la pile sous forme de mots consécutifs dans la mémoire centrale, et
d’utiliser un registre qui pointe sur la dernière valeur empilée, appelé pointeur de pile.
Dans le cas de CRAPS, on utilisera le registre %r30, et les opérations push et pop
existent déjà sous forme d’instructions synthétiques :
push %ri : sub %r30, 4, %r30
st %ri, [%r30]

pop %ri : ld [%r30], %ri


add %r30, 4, %r30
La figure V.17 montre ce qu’on observe en mémoire, après quatre opérations
successives d’empilement et de dépilement.

(a) (b) (c)

fond de
pile %r30
7
%r30
−8
−4 5 5
%r30

initialement set 5, %r1 set 7, %r1


push %r1 push %r1
(d) (e)

%r30
7 9
%r30
5 5
%r2

pop %r2 set 9, %r1


push %r1

Figure V.17. Opérations successives d’empilement et de dépilement.

En disposant le ruban mémoire de haut en bas, on observe que les opérations


d’empilement se traduisent visuellement par un empilement des valeurs les unes sur les
autres : le 7 par dessus le 5, etc. Réciproquement un dépilement se traduit par la saisie de
la valeur au sommet, c’est à dire celle qui est pointée par %r30. On notera cependant que
la valeur dépilée n’est pas effacée de la mémoire, mais qu’elle est laissée telle quelle. Cela
n’a aucune importance, car elle n’est plus accessible par des opération pop, et elle peut être
écrasée par une opération push ultérieure. C’est ce qui se passe lors du push de la valeur 9
(situation (e)).
6. Utilisation de la pile 155

La pile de CRAPS, comme celle de la plupart des processeurs, a les deux propriétés
suivantes :
1 lors d’un empilement, le sommet de la pile ’remonte’, c’est à dire que la valeur du
pointeur de pile diminue.

2 le pointeur de pile pointe toujours sur la dernière valeur empilée.

Ces indications sont utiles notamment lorsqu’on débogue un programme et qu’on souhaite
savoir précisément ce que la pile contient.

Allocation de l’espace de pile


La pile étant en mémoire centrale, doit être dans une zone de RAM. Par ailleurs, il faut
prévoir qu’avec les empilements successifs elle va ’grignoter’ de la place en mémoire, en
progressant vers les adresses basses (figure V.18).

RAM

adresse 0

programme,
données

pile

fond de pile

Figure V.18. La pile grandit en mémoire en progressant vers les adresses basses. Elle peut
déborder sur des zones de programme ou de données.

Il y a donc un danger potentiel d’écrasement d’une zone de programmes ou de


données par la pile, si celle-ci grandit trop. Lorsqu’on utilise une pile dans un programme,
il faut donc initialiser le pointeur de pile en prenant en compte la position des autres
données en mémoire, et la taille maximale que la pile peut prendre au cours de l’exécution
des programmes.

Sauvegarde de l’adresse de retour de sous-programme


Un premier usage que l’on peut faire d’une pile est de résoudre notre problème de
sauvegarde de l’adresse de retour lors d’un appel de sous-programme de type call …
ret.
La solution est très simple :

1 juste avant l’appel call, il faut empiler l’adresse de retour %r31.


156 CHAPITRE V. PROGRAMMATION DU PROCESSEUR 32 BITS CRAPS/SPARC

2 juste après le retour du call, on dépile dans %r31 cette adresse.

Ainsi, même si %r31 est modifié dans l’appel du sous-programme, on est sûr de retrouver
la valeur de %r31 que l’on avait avant le call, c’est à dire l’adresse de retour (sous réserve
que la pile soit au même niveau au retour qu’au départ).
On peut illustrer cette méthode par le petit programme suivant :
RAM = 0x1000000
STACK = RAM+100 ; 100 octets réservés

;;;; calcul de 2*5+4 par appels successifs ;;;;


;;;; de ssprog1 et ssprog2 ;;;;;
set STACK, %r30 ; initialisation %r30
main: set 5, %r1 ; init. arguments d’appel
set 4, %r2
adr1: call ssprog1 ; appel ss programme
ba * ; arrêt (boucle infinie)

;;;;;; calcule 2*%r1+%r2, résultat dans %r3 ;;;;;;


ssprog1: push %r31 ; empile adresse retour
adr2: call ssprog2 ; calcul de 2*%r1
pop %r31 ; restaure adresse retour
addcc %r1, %r2, %r3 ; ajoute %r2
ret ; retour à main

;;;;;; calcule 2*%r1, résultat dans %r1 ;;;;;;


ssprog2: addcc %r1, %r1, %r1
ret

Avant toute chose, il faut initialiser le pointeur de pile. Ici il est placé 100 octets après
le début de la zone de RAM, laissant la possibilité de 100/4 empilements. main appelle
ssprog1, qui va faire le calcul 2x + y. Ce n’est pas la peine ici de sauvegarder %r31, car
on ne vient de nulle part et donc on n’a pas d’adresse de retour à préserver. %r31 prend
pour valeur adr1, qu’il va falloir préserver si on veut revenir dans main à la fin. ssprog1
appelle ssprog2 pour faire le calcul du double. Avant de partir, il sauvegarde son adresse
adr1 de retour à main, qui sera écrasée sinon. En effet à l’exécution de call ssprog2,
la valeur de %r31 qui était adr1 est écrasée, et remplacée par adr2. Au ret de ssprog2
le contrôle reprend à l’adresse adr2+4, après l’appel call ssprog2. L’exécution de pop
%r31 redonne à %r31 la valeur adr1 qu’il avait avant l’appel, et le retour à main redevient
possible.

Appels de fonctions récursives


Cette méthode ouvre d’un seul coup la possibilité de réaliser des programmes récursifs
et mutuellement récursifs, puisque les sauvegardes/restaurations des adresses de retour
peuvent être faites en nombre quelconque dans la pile.
On va l’illustrer par un calcul de plus grand commun diviseur (PGCD), qui s’obtient
par l’algorithme récursif suivant :

fonction int pgcd(int x, int y)


6. Utilisation de la pile 157

si (x = y) alors
val <- x ;
sinon si (x > y) alors
val <- pgcd(x-y, y) ;
sinon
val <- pgcd(x, y-x) ;
return val ;
fin fonction

Par exemple, PGCD(78,143) = PGCD(78,65) = PGCD(13,65) = PGCD(13,52) =


PGCD(13,39) = PGCD(13,26) = PGCD(13,13) = 13.
Comme par ailleurs 78 = 2×3×13 et 143 = 11×13, on vérifie bien que 13 est leur plus
grand commun diviseur.
Pour le programmer récursivement en CRAPS, il nous suffit à l’intérieur du
sous-programme pgcd de rappeler par call le sous-programme pgcd, en prenant soin de
sauvegarder dans la pile tout l’environnement local nécessaire au calcul, c’est à dire :

1 les valeurs de registres que l’on souhaite conserver au travers de cet appel.

2 l’adresse de retour %r31.


La figure V.19 donne le listing complet du programme.

Le programme est tout à fait simple et lisible, traduction littérale de l’algorithme


récursif. Seule l’adresse de retour est sauvegardée avant l’appel récursif, car il n’y a pas de
registre particulier à sauvegarder à ce moment. Si le calcul nécessite n appels récursifs, la
pile va grossir progressivement jusqu’à occuper n mots en mémoire, adresses de retour
sauvegardées à chaque appel. Ensuite, n exécutions de l’instruction ret vont remettre la pile
à son niveau initial.
L’utilisation de la récursivité dans le cas de ce programme est maladroite, car elle est
assez inefficace à l’exécution, vu le grand nombre d’accès mémoire effectués. L’algorithme
peut très simplement être traduit de façon itérative :

fonction int pgcd(int x, int y)


tant que (x <> y) faire
si (x > y) alors
x <- x - y ;
sinon
y <- y -x ;
fin si
fin tq ;
return x ;
fin fonction

Cet algorithme se traduit directement par le programme de la figure V.20.

La leçon à tirer de cet exemple est qu’il ne faut pas abuser de la récursivité dans les
158 CHAPITRE V. PROGRAMMATION DU PROCESSEUR 32 BITS CRAPS/SPARC

RAM = 0x1000000
STACK = 0x1000100

.org 0
main set STACK, %r6 ; init pointeur pile
set 78, %r1 ; valeur de x
set 143, %r2 ; valeur de y
call pgcd ; appel pgcd
ba * ; arrêt

;; calcule pgcd(x,y)
;; in : x = %r1, y = %r2
;; out : résultat dans %r3
pgcd: cmp %r2, %r1
bne skip ; x=y ?
; x = y : return x
mov %r1, %r3 ; val <- x
ba retour ; retour
skip: bneg sup ; x>y ?
; x < y : préparation de pgcd(x, y-x)
subcc %r2, %r1, %r2 ; y <- y - x
push %r31 ; sauvegarde @ retour
call pgcd ; appel récursif
pop %r31 ; restaur. @ retour
ba retour
sup: ; x > y : préparation de pgcd(x-y, y)
subcc %r1, %r2, %r1 ; x <- x - y
push %r31 ; sauvegarde @ retour
call pgcd ; appel récursif
pop %r31 ; restaur. @ retour
retour ret

Figure V.19. Programme récursif de calcul du PGCD.Le programme est très lisible,au prix
d’une exécution plus lente.

fonctions de bas niveau, si un algorithme itératif est disponible. Elle est en effet gourmande
en temps et en espace occupé en mémoire. La récursivité est par contre indispensable dans
les fonctions de haut niveau, pour des raisons de clarté et de modularité dont la discussion
sort du cadre de cet ouvrage.
Enfin on peut noter que lorsqu’on paramètre la compilation d’un programme pour
qu’il produise un code plus efficace, il cherche entre autres techniques à dérécursiver les
récursions terminales comme celles de notre fonction PGCD, afin de les rendre itératives.

7. Techniques de programmation diverses


Multiplication entière par une puissance de 2
Sur notre machine CRAPS, l’opération de multiplication entière occupera un nombre de
cycles relativement important. Dans le cas assez fréquent où l’on souhaite multiplier par
une puissance de 2, on peut utiliser plus efficacement une opération de décalage à gauche
sll. À chaque décalage d’un cran à gauche (avec introduction d’un 0 à droite), la valeur
décalée est multipliée par 2, qu’elle soit codée en binaire pur (non signée) ou en complément
à deux (signée).
7. Techniques de programmation diverses 159

RAM = 0x1000000
STACK = 0x1000100

.org 0
main set 78, %r1 ; valeur de x
set 143, %r2 ; valeur de y
call pgcd ; appel pgcd
ba * ; arrêt

;; calcule pgcd(x,y)
;; in : x = %r1, y = %r2
;; out : résultat dans %r3
pgcd: cmp %r2, %r1 ; tant que x <> y
bne skip ; x=y ?
; x = y : return x
mov %r1, %r3 ; val <- x
ret ; retour
skip: bneg sup ; x>y ?
; x < y
subcc %r2, %r1, %r2 ; y <- y - x
ba pgcd
sup: ; x > y
subcc %r1, %r2, %r1 ; x <- x - y
ba pgcd

Figure V.20. Programme itératif de calcul du PGCD. Il est plus efficace que la version
récursive car il n’y a plus d’accès en mémoire.

Division entière par une puissance de 2


De la même façon, on peut vouloir calculer le quotient de la division entière d’un nombre
N entier signé par un diviseur entier positif égal à une puissance de 2, de façon plus rapide
que l’instruction de division udivcc.
On utilise pour cela l’instruction de décalage à droite srl : chaque décalage à droite
est équivalent à une division par 2. Par exemple, si %r1 est positif, srl %r1, 4, %r2 le
divise par 16 et place le résultat dans %r2.
Cette technique ne fonctionne pas si %r1 est négatif, car srl va introduire des ’0’ par
la gauche, et de négatif il va devenir positif, ce qui est absurde. L’instruction sra (shift right
arithmetic) est là pour cela : au lieu de faire rentrer systématiquement un ’0’ par la gauche,
elle fait entrer le bit de poids fort du registre en cours de décalage, en préservant ainsi son
signe. La figure V.21 illustre cette opération.
Ainsi donc sra %r1, 4, %r2 divise %r1 par 16 dans tous les cas, même s’il
est négatif.

8. Langages à structure de blocs et ’stack-frames’


On s’intéresse dans cette section aux problèmes que posent aux compilateurs les
langages à structure de blocs tels que C, Java, etc. lors de la phase de génération de code.
Un compilateur est en effet essentiellement un traducteur du langage source vers un langage
cible qui est généralement le langage assembleur du processeur visé. On cherche dans cette
section quelles méthodes ces compilateurs mettent en oeuvre de façon systématique pour
160 CHAPITRE V. PROGRAMMATION DU PROCESSEUR 32 BITS CRAPS/SPARC

sra : décalage arithmétique

srl : décalage logique

Figure V.21. Décalage à droite logique et arithmétique. Dans le décalage arithmétique, le


signe de l’opérande est préservé.

implémenter la notion de bloc, ainsi que lors des appels de fonctions.

Implémentation des blocs sous forme de stack-frames


Les programmes écrits dans des langages tels que C et Java sont organisés en blocs, encadrés
par des symboles tels que {... } ou begin … end. À l’intérieur de chaque bloc on peut
déclarer de nouvelles variables, dont la portée est limitée au bloc.
Généralement un bloc est associé à une structure de contrôle (boucle for ou while)
ou au corps d’une fonction. On peut néanmoins créer un bloc à tout moment dans un
programme, et y déclarer les variables locales de son choix : leur portée sera limitée à ce bloc.
Ainsi en langage C, on peut écrire le petit programme (inutile) suivant :

int main(int argn,char** args) {


int x = 1 ;
int y = 10 ;
printf("x=%d\n", x) ;
{
int x = 2 ;
int z = x + y ;
printf("x=%d\n", x) ;
}
printf("x=%d\n", x) ;
}

À l’exécution, il va produire l’affichage :


x=1
x=2
x=1

Il y a deux blocs dans ce programme : celui associé au corps de la procédure main, et un


bloc interne situé au beau milieu, et qui n’est là que pour illustrer notre propos. La portée
de la variable x située dans le bloc interne est limitée à ce bloc, et elle a temporairement pris
la place de la variable x du bloc plus externe. La variable z n’est visible que dans le bloc
interne, et la variable y est visible dans les deux blocs.
La pile est utilisée pour mettre en place cette visibilité temporaire des variables. Lors de
l’entrée dans un bloc, le compilateur crée un stack-frame dans la pile, c’est à dire une zone
8. Langages à structure de blocs et ’stack-frames’ 161

de mémoire dans laquelle seront placées les valeurs des variables déclarées dans le bloc. Les
stack-frames associés aux deux blocs de notre programme pourraient être implantés comme
sur la figure V.22.

pile

z = 12
stack
x=2 frame 2
pied frame 2 prec.

y = 10
stack
x=1 frame 1
pied frame 1
prec.

Figure V.22. Stack-frames associés aux deux blocs de code du programme.Chaque variable
locale a sa place dans le stack-frame associé au bloc. On remarquera le pointeur vers le
stack-frame précédent.

Un registre est généralement affecté au pointage au pied du stack-frame courant, et les


accès aux différents éléments du stack-frame se font très simplement par adressage indirect
avec déplacement. Par exemple, si on utilise %r29 comme pointeur de stack-frame, on aura
le mécanisme d’adressage de la figure V.23.

pile

%r29−8
z = 12
stack
%r29−4 x=2 frame 2
%r29 %r29 prec.

y = 10
stack
x=1 frame 1
prec.

Figure V.23. Utilisation du registre %r29 comme pointeur à la base du stack-frame courant.
Les accès aux variables locales se font par adressage indirect avec déplacement.

Le code assembleur traduisant ce programme pourrait être :


162 CHAPITRE V. PROGRAMMATION DU PROCESSEUR 32 BITS CRAPS/SPARC

STACK = 0x1000100

main: ;; initialisation pointeur de pile


set STACK, %sp

;; création du stack-frame 1
push %r29 ; empile val. prec.de %r29
mov %sp, %r29 ; %r29 reste fixe au pied du stack frame
add %sp, 2*4, %sp ; réserve 2 mots dans la pile

setq 1, %r1
st %r1, [%r29-4] ; x <- 1
setq 10, %r1
st %r1, [%r29-8] ; y <- 10

;; création du stack-frame 2
push %r29 ; empile val. prec.de %r29
mov %sp, %r29 ; %r29 reste fixe au pied du stack frame
add %sp, 2*4, %sp ; réserve 2 mots dans la pile

setq 2, %r1
st %r1, [%r29-4] ; x <- 2
;; recherche de la valeur de y par double indirection
ld [%r29], %r2 ; %r2 <- "@stack-frame 1"
ld [%r2-8], %r1 ; %r1 <- y
ld [%r29-4], %r3 ; %r3 <- x
addcc %r1, %r3, %r4 ; %r4 <- x + y
st %r4, [%r29-8] ; stockage de z
...

La création d’un stack frame consiste essentiellement à réserver la place nécessaire aux
variables locales ; il s’agit d’une simple soustraction sur la valeur de %sp. À chaque création
d’un stack-frame, la valeur courante de %r29 est empilée, et %r29 est positionné au pied du
stack-frame.L’accès à une variable locale à ce bloc se fait directement par référence à %r29 ;
par exemple l’accès à x dans le stack-frame 2 se fait par [%r29-4]. Plus délicat est l’accès
à la variable y dans l’expression z = x + y ; en effet y n’appartient pas au bloc courant,
mais au bloc de rang inférieur. La référence à la variable y est correcte, mais le compilateur
doit dans ce cas accéder à y par une double indirection, en allant chercher d’abord l’adresse
du stack-frame de rang inférieur, puis en faisant à partir de là un accès indirect avec
déplacement. Selon les cas, plusieurs de ces indirections peuvent être nécessaires.

Appel de fonction récursive


Appeler une fonction, c’est créer dynamiquement un stack-frame qui contiendra toutes les
données manipulées par elle, à savoir :

1 les paramètres d’appel,

2 la valeur de retour,

3 les variables locales au bloc principal de la fonction,

4 l’adresse de retour à l’instruction d’appel.

Ainsi, chaque nouvel appel aura son propre contexte de travail sous forme d’un stack-frame,
et les appels successifs ne mélangeront pas leurs variables locales et les paramètres d’appel.
À la sortie d’un appel de fonction, la valeur renvoyée par la fonction sera récupérée
avant que l’espace du stack-frame ne soit désalloué de la pile (figure V.24). On récupérera
8. Langages à structure de blocs et ’stack-frames’ 163

également l’adresse de retour, valeur de %r31 dans le cas de CRAPS.

pile

var. locale #m
...

var. locale #1

val. renvoyee stack


frame
param #n
...

param #1

@ retour

@ prec.

Figure V.24. Stack-frame créé lors d’un appel de fonction. On y trouve les paramètres
d’appel, l’emplacement de la valeur à renvoyer, les variables locales et l’adresse de retour.

9. Programmation des entrées/sorties


Jusqu’ici nous n’avons écrit que des programmes ’autistes’ : ils n’ont effectué aucun
échange avec l’extérieur. Les données sur lesquelles ils travaillaient leurs étaient fournies
directement sous forme de zones de mémoire préremplies avec les valeurs du problème et ils
produisaient des résultats sous forme de données écrites en RAM.
On va considérer que l’interface d’un ordinateur avec l’extérieur est fait au travers
d’un ensemble de lignes digitales, chacune d’elle étant à un moment donné une entrée ou
une sortie. Si c’est une entrée, le processeur doit être capable de lire son état 0 ou 1 ; si c’est
une sortie il doit pouvoir forcer son état à 0 ou à 1. Des amplificateurs ou des atténuateurs en
entrée ou en sortie peuvent permettre la lecture ou la commande de courants très forts ou très
faibles ; même la commande ou l’acquisition de signaux analogiques rentre dans ce cadre,
puisqu’on peut utiliser des convertisseurs numérique/analogique qui vont faire la conversion
d’un monde vers l’autre.
Certaines de ces lignes sont des entrées ou des sorties sans pouvoir changer de nature :
ce sera par exemple le cas des sorties des circuits appelés timer/PWM chargés de produire
des signaux paramétrables de forme rectangulaire. Certaines lignes d’usage plus souple
seront des lignes d’entrée/sortie générales, qui pourront être configurées individuellement et
par programme en entrée ou en sortie.

Instructions spéciales d’entrées/sorties


Sur certains processeurs existent des instructions spéciales d’entrées/sorties. Le x86 par
exemple possède une instruction IN AL,PORT qui lit un octet de PORT vers AL et une
instruction OUT PORT,AL qui écrit un octet de AL vers PORT. PORT désigne un numéro de
port, élément parmi un espace d’adressage spécial, propre aux entrées/sorties. Les écritures
164 CHAPITRE V. PROGRAMMATION DU PROCESSEUR 32 BITS CRAPS/SPARC

sur des numéros de port particuliers mettent en activité les périphériques associés tels que
contrôleurs USB, contrôleurs de bus PCI Express, etc.

Entrées/sorties mappées en mémoire


Sur d’autres processeurs tels que CRAPS/SPARC, il n’existe pas de telles instructions
spécialisées. Comment alors peuvent-ils faire pour interagir avec l’extérieur ? Ils utilisent
en fait un mécanisme appelé entrées/sorties mappées en mémoire : c’est en lisant et en
écrivant à certains emplacements mémoire particuliers, que le processeur va dialoguer avec
les périphériques. L’avantage de cette méthode, c’est qu’il n’y a pas d’instructions spéciales
à utiliser et que, en CRAPS par exemple, de simples ld et st suffiront. Son inconvénient,
c’est de gaspiller certains emplacements de l’espace d’adressage mémoire, qui ne peuvent
plus être utilisés pour de la véritable RAM ou ROM.
La machine CRAPS, telle qu’elle sera décrite en détail au chapitre suivant, et telle
qu’elle est utilisable au travers du simulateur CrapsEmu et de son implantation dans un
FPGA, est équipée d’un timer/PWM et de 32 lignes d’entrée/sortie générales. Ces deux
périphériques sont donc mappés en mémoire et on va décrire maintenant la façon de
les programmer.

9.1. Programmation des lignes d’entrées/sorties générales


CRAPS est équipé de 32 lignes d’entrées/sorties générales, qu’on nommera IOi,
0 ≤ i ≤ 31. Par ’générales’, il faut comprendre que chaque ligne peut être configurée
individuellement en entrée ou en sortie, et ensuite lue ou écrite à volonté. On pourra par
exemple les utiliser de la façon décrite figure V.25.

IO[3] 1K
Vcc

IO[2] 1K
CRAPS Vcc

IO[1] 220

IO[0] 220

Figure V.25. Exemple d’utilisation des lignes d’entrées/sorties générales de CRAPS.

Les lignes IO[0] et IO[1] doivent être configurées en sorties, et elles servent ici à
commander l’allumage de diodes électroluminescentes : si une de ces lignes est à ’0’, la
diode correspondante est éteinte ; si elle est à ’1’, un courant d’environ 20mA traverse la
diode et l’allume.
Les lignes IO[2] et IO[3] doivent être configurées en entrées, et servent à
l’acquisition de l’état de deux interrupteurs. Lorsqu’un des interrupteurs est ouvert, la ligne
correspondante lit ’1’(car la résistance de 1K induit un courant entrant de 5mA si Vcc=5v) ;
s’il est fermé elle lit ’0’.
9. Programmation des entrées/sorties 165

Adresses mémoire utilisées


Dans la configuration que nous allons décrire, les lignes d’entrées/sorties de CRAPS sont
mappées aux adresses de 0x600000 à 0x60000C selon le tableau de la figure V.26.

adresse W opération
0x600000 0 lire l’état des entrées
0x600000 1 écrire sur les sorties
0x600004 0 lire configuration lignes
0x600004 1 configurer les lignes
0x600008 0 lire interruptions mémorisées
0x600008 1 raz des interruptions mémorisées
0x60000C 0 lire sens du front d’interruption (0 = front montant)
0x60000C 1 écrire sens du front d’interruption

Figure V.26. Tableau de configuration et d’utilisation des entrées/sorties de CRAPS. W=1


indique une écriture.La configuration des lignes se fait par écriture en 0x600004 ; la lecture
et l’écriture des lignes se fait par lecture et écriture à l’adresse 0x600000. La lecture et
l’écriture en 0x600008 et 0x60000C concernent la gestion des interruptions (section 10).

Les valeurs 0x600000, 0x600004 etc. ne dépendent pas du processeur CRAPS, mais
de la façon dont il est interfacé avec les circuits mémoire et d’entrées/sorties. On détaillera
complètement cet aspect de ce qu’on appelle le décodage des adresses au chapitre suivant.
Par contre, on reportera à la section 10 l’utilisation des lignes d’entrées/sorties comme lignes
d’interruption, au travers de la lecture et de l’écriture aux adresses 0x600008 et 0x60000C.

Configuration des lignes


D’après le tableau V.26, la configuration se fait en écrivant à l’adresse 0x600002 un mot de
32 bits val[31..0]. La règle de configuration est simple : si val[i] vaut 0, la ligne IO[i]
sera configurée en entrée ; s’il vaut 1 la ligne sera configurée en sortie.
Ainsi pour configurer les lignes IO[0] et IO[1] en sorties et toutes les autres en
entrées, on écrira le mot mémoire 000..00001132 en 0x600004, par exemple avec le code
suivant :
BASEIO = 0x600000 ; adresse de base des IO

set BASEIO, %r1


set 0b11, %r2 ; IO[0..1] sorties, les autres en entrées
st %r2, [%r1+4] ; écriture configuration

Lecture/écriture sur les lignes


Pour lire l’état des entrées, il suffit de lire en 0x600000, et de tester dans le mot lu les bits
des rangs correspondants aux entrées. Pour positionner l’état des sorties, il suffit d’écrire
en 0x600000 un mot dont les bits correspondent aux valeurs que l’on souhaite placer sur
les lignes.
166 CHAPITRE V. PROGRAMMATION DU PROCESSEUR 32 BITS CRAPS/SPARC

Par exemple sur le schéma de la figure V.25, pour lire l’état de l’interrupteur situé sur
la ligne IO[2] et allumer la diode située sur IO[0] si cet interrupteur est ouvert (IO[2]=1) on
écrira le code suivant :
BASEIO = 0x600000 ; adresse de base des IO

set BASEIO, %r1


set 0b0011, %r2 ; IO[0..1] sorties, IO[2..3] entrées
st %r2, [%r1+4] ; écriture configuration

ld [%r1], %r3 ; lecture des IO en entrées


andcc %r3, 0b100, %r3 ; isolation du bit 2
srl %r3, 2, %r3 ; déplacement de ce bit au rang 0
st %r3, [%r1] ; écriture sur les IO en sorties

Exemple : programmation d’un jeu de ’pile ou face’


Le programme de la figure V.27 exploite le câblage des entrées/sorties de la figure V.25, et
fait allumer en alternance l’une des deux LEDs situées sur IO[0] et IO[1]. Lorsque le joueur
appuie sur l’interrupteur situé sur IO[2], l’état des LEDs se fige, faisant apparaître une des
deux combinaisons.

BASEIO = 0x600000 ; adresse de base des IO

set BASEIO, %r1


set 0b011, %r2 ; IO[0..1] sorties, IO[2] entrée
st %r2, [%r1+4] ; écriture configuration

setq 0b10, %r4 ; état des LEDs


st %r4, [%r1] ; écriture état des LEDs
loop ld [%r1], %r3 ; lecture des IO en entrées
andcc %r3, 0b100, %r3 ; isolation du bit 2
bne loop ; attend qu’il vaille 1
;; interrupteur ouvert : on inverse l’état des LEDs
xorcc %r4, 0b11, %r4 ; inversion par xor
st %r4, [%r1] ; écriture état des LEDs
ba loop ; reboucle éternellement

Figure V.27. Programme d’un jeu de ’pile ou face’. Les LEDs #0 et #1 s’allument en
alternance, et un appui sur l’interrupteur #2 fige la configuration.

Les commentaires du programme parlent d’eux-mêmes. On notera l’inversion par le


xor des deux bits de poids faibles de l’état des LEDs dans %r4.

9.2. Programmation du timer/PWM

Principe général
CRAPS possède un timer/PWM qui lui permet de générer sur une ligne de sortie dédiée, des
signaux rectangulaires dont la période et le rapport cyclique sont programmables. La forme
du signal est donnée figure V.28.
9. Programmation des entrées/sorties 167

t
DT = periode
horloge timer

Figure V.28. Forme et paramètres du signal de sortie du timer/PWM de CRAPS.

Les usages d’un timer/PWM sont nombreux. Dans un système multi-tâches, la


sortie d’un timer sert à déclencher une interruption qui effectue la commutation entre
les différentes tâches. Il fonctionne à une fréquence d’environ 50 hertz, ce qui donne à
l’utilisateur l’illusion que ses différents programmes fonctionnent en parallèle.
On s’en sert également pour contrôler avec précision l’énergie communiquée à un
appareil de type lampe ou moteur à courant continu (après amplification), celle-ci étant
proportionnelle au rapport cyclique P − N .
P
Registres de commande
Il s’agit d’un timer 16 bits, c’est à dire que les paramètres P et N sont codés sur 16 bits,
et représentent un nombre de cycles de l’horloge du timer. L’horloge du timer est obtenue
à partir de l’horloge générale, dont la fréquence peut éventuellement être prédivisée par
un facteur prediv codé sur 4 bits, d’où 16 valeurs de prédivision. Si f est la fréquence de
l’horloge générale, la fréquence du timer sera programmable parmi les 16 valeurs : f , f /2, …,
f /32768. C’est elle qui définit le pas minimal de fonctionnement du timer.
Le timer possède en interne deux registres qui stockent ces valeurs nécessaires à son
fonctionnement. Ils sont appelés commande #1 et commande #2 (figure V.29).

31 16 15 0
registre
commande #1 N P

31 16 1 0
registre
commande #2 prediv

start

Figure V.29. Structure des registres de commande du timer de CRAPS.

Du point de vue du programmeur, le timer/PWM de CRAPS est mappé aux adresses


0x400000 à 0x400008 selon le tableau de la figure V.30.
La configuration du timer se fait par écriture dans le registre de commande #1 pour
les valeurs de P et de N, et dans le registre de commande #2 pour la valeur de prediv. Le
lancement du timer se fait par écriture d’un ’1’ dans le bit 0 du registre de commande #2.
À tout moment du fonctionnement du timer, il peut être arrêté par écriture d’un ’0’ dans
168 CHAPITRE V. PROGRAMMATION DU PROCESSEUR 32 BITS CRAPS/SPARC

adresse W opération
0x400000 0 lire le registre de commande #1 (P + N)
0x400000 1 écrire le registre de commande #1 (P + N)
0x400004 0 lire le registre de commande #2 (prediv + start)
0x400004 1 écrire le registre de commande #2 (prediv + start)
0x400008 0 lire bit d’interruption timer
0x400008 1 raz bit d’interruption timer

Figure V.30. Tableau de configuration et d’utilisation du timer/PWM de CRAPS. W=1 in-


dique une écriture.La configuration des valeurs de P et N se fait par écriture en 0x400000 ;
le lancement du timer et la valeur de prédivision de l’horloge se font par écriture en
0x400004.La lecture et l’écriture en 0x400008 concerne l’utilisation du timer comme source
d’interruption (voir section 10).

ce bit.
Si par exemple on souhaite générer un signal carré de période 100 cycles d’horloge, on
écrira le code suivant :
BASETIMER = 0x400000 ; adresse de base du timer

set BASETIMER, %r1 ; %r1 = base timer


set 0x00320064, %r2 ; N=50 & P=100
st %r2, [%r1] ; écriture commande #1

set 0x00000001, %r2 ; prediv=0 & start=1


st %r2, [%r1+4] ; écriture commande #2
;; ----> lancement du timer
;; ...
st %r0, [%r1+4] ; arrêt du timer
ld [%r1], %r3 ; lecture du compteur

%r1 pointe au début de la zone mémoire [0x400000,400008[ et toutes les références


mémoire sont faites par rapport à lui : [%r1] pour commande #1, [%r1+4] pour
commande #2. La valeur écrite dans commande #1 est 0x00320064, soit N=50 et P=100.
On va voir que le facteur de prédivision est ici de 1, donc la période sera bien de 100 cycles
d’horloge, et le timer sera à 0 durant les 50 premiers cycles, puis à 1 les 50 cycles suivants.
La valeur écrite dans commande #2 est 0x00000001, soit prediv=0 et start=1 : le facteur de
prédivision est 1, et la mise à 1 du bit start provoque le démarrage du timer. Plus loin dans le
programme, le timer est arrêté par mise à 0 de ce bit, et la valeur courante du compteur est
lue et stockée dans %r4.

Exemple d’utilisation : commande d’un servo-moteur


Un servo-moteur est composé d’un moteur à courant continu avec un ensemble
d’engrenages démultiplicateurs qui lui donnent un couple très élevé. Il est muni d’une
électronique de commande qui permet de contrôler avec précision, non pas la vitesse de
rotation, mais la position angulaire de son axe, qui n’a un débattement que de 180° (figure
V.31).
9. Programmation des entrées/sorties 169

180 degrés 0 degrés

Figure V.31. Servo-moteur. La rotation de l’axe est très démultipliée et a un couple élevé.
La commande ajuste la position angulaire de l’axe, qui peut varier de 0° à 180°.

La commande se fait sur un seul fil, à la norme TTL (0 ou 5v), et consiste en une
impulsion périodique dont la largeur code la position angulaire de l’axe (figure V.32).

1 ms = 0 degrés
1 ms < d < 2 ms 2 ms = 180 degrés

période 20ms

Figure V.32. Forme du signal de commande d’un servo-moteur.Il est périodique de période
20ms ; l’impulsion a une largeur comprise entre 1ms et 2ms, qui code la position angulaire
de l’axe entre 0° et 180°.

La période du signal reste constante à 20ms (valeur non critique) ; ce qui varie, c’est
la largeur de l’impulsion, entre 1ms et 2ms. Dans cet intervalle il y a proportionnalité entre
la largeur de l’impulsion et la position angulaire, de 0° pour une largeur de 1ms à 180° pour
une largeur de 2ms.

Détermination de la fréquence de l’horloge du timer


On souhaite avoir environ 1000 pas de précision sur la commande, c’est à dire qu’on
souhaite faire varier la largeur de l’impulsion de 1 ms à 2 ms par pas de 1/1000 ms. C’est
−6
donc cette valeur de 1/1000 ms qui doit être la période de base du timer : DT ∼
− 10 s.
On suppose que l’horloge générale est de 50 MHz, soit une période de DH = 2·10− 8s.
Le rapport de prédivision de DH doit donc être DT / DH = 50. La puissance de 2 la plus
proche est 64, soit une valeur de prediv de 6. Finalement la véritable fréquence de l’horloge
sera :

DT = 64DH = 1, 28·10− 6s

P restera constant égal à 20 ms / 1, 28·10− 6 s = 15625 pour assurer la période générale de


20 ms du signal transmis au servo-moteur, et N variera entre 12501et 14063 pour faire varier
la largeur de l’impulsion entre 1 ms et 2 ms, par 1562 pas.
La figure V.33 donne un sous-programme de positionnement du servo-moteur, auquel
on donne comme paramètre l’angle de positionnement dans %r1 en dixièmes de degrés (de
0 à 1800).
170 CHAPITRE V. PROGRAMMATION DU PROCESSEUR 32 BITS CRAPS/SPARC

;; commande de servo-moteur
BASETIMER = 0x400000 ; adresse de base du timer

.org 0
;; main : exemple d’utilisation
main set 900, %r1 ; %r1 <- 90 degrés
call cmdservo
ba *

;; sous-programme de commande du servo


;; in : %r1=angle en dixièmes de degrés
cmdservo umulcc %r1, 1561, %r3 ; %r3 = 1561 * angle
udivcc %r3, 1800, %r3 ; %r3 = 1561*angle/1800
set 12501, %r1
add %r3, %r1, %r3 ; %r3 = 12501+1561*angle/1800
sll %r3, 16, %r3 ; décalage de 16 positions à gauche
set 15625, %r1 ; valeur de P
or %r1, %r3, %r3 ; fusion de N et P dans %r3
set BASETIMER, %r2 ; %r2 = base timer
st %r3, [%r2] ; écriture commande #1
set 0b1101, %r3 ; prediv=6, start=1
st %r3, [%r2+4] ; écriture commande #2
ret

Figure V.33. Sous-programme de commande de servo-moteur.

Le sous-programme cmdservo effectue d’abord une mise à l’échelle de la valeur de


%r1 (de 0 à 1800) : une transformation affine remplace %r1 par une valeur entre 12501 et
14063, qui est la valeur qui doit être placée dans le registre N du timer/PWM. On notera que
les constantes 1561et 1800 ont été utilisées directement comme constantes immédiates dans
les instructions umulcc %r1, 1561, %r3 et udivcc %r3, 1800, %r3 car elles sont
codables sur 13 bits (voir format des instructions en section 2.4). Cela n’est pas le cas par
contre pour la constante 15625, qui ne tient pas sur 13 bits et qui doit d’abord être mise dans
un registre par une instruction synthétique set.
On comprend tout l’intérêt de commander un servo-moteur par un tel circuit
timer/PWM : le signal périodique est généré de façon autonome et le processeur n’a
besoin d’intervenir que lors d’un changement de position ou d’une demande d’arrêt ou de
lancement, qui se traduisent par une simple écriture en mémoire.

10. Programmation des exceptions

10.1. Définitions
Une exception est un événement exceptionnel qui intervient au cours du
fonctionnement du processeur.La prise en compte d’une exception se traduit par l’exécution
d’un sous-programme qui lui est associé, appelé gestionnaire d’exception. On pourrait dire
qu’une exception est un appel de sous-programme inattendu.
On classe les exceptions en deux groupes, selon que leur cause est interne ou externe.
10. Programmation des exceptions 171

Les traps
Les traps sont des exceptions à cause interne, provoqués par l’exécution d’une instruction
par le processeur. Mieux qu’un long discours, la liste des traps du processeur CRAPS fera
comprendre leur nature :

• instruction illégale. Le processeur tente d’exécuter une instruction dont le code


machine ne correspond à aucune instruction connue.

• bus error. Le processeur tente d’effectuer un accès mémoire à une adresse où aucune
mémoire n’est implantée.

• division par 0. Le processeur exécute une instruction de division et le diviseur


est nul.
• erreur d’alignement. Le processeur tente d’effectuer un accès mémoire à une adresse
qui n’est pas un multiple de 4 si par exemple il s’agit d’un processeur 32 bits. La plupart
des processeurs exigent en effet que la lecture ou l’écriture des mots respectent de telles
contraintes d’alignement.

• instruction privilégiée. Le processeur tente d’exécuter une instruction privilégiée


(réservée aux programmes en mode superviseur) mais il n’est pas en mode
superviseur.

On dit de telles exceptions qu’elles sont synchrones, car elles surviennent à un endroit
prévisible du programme. Elles correspondent généralement à un fonctionnement incorrect
de celui-ci et conduisent souvent à l’arrêt de la tâche associée.
Les traps sont ainsi l’équivalent matériel des exceptions logicielles, qui elles aussi
se déclenchent en cas d’exécution incorrecte d’instructions, et qui ont un gestionnaire
associé.

Les interruptions
Une interruption est une exception à cause externe, généralement liée à un événement
d’entrée/sortie. Par exemple :

• la lecture de données par un disque est terminée et le contrôleur de disques envoie au


processeur une interruption pour l’en informer.

• un paquet de réseau vient d’arriver.

• un événement clavier ou souris vient de se produire.

• un changement est intervenu sur une des lignes d’entrées/sorties.

• un événement est intervenu sur la chaîne USB.


Les interruptions sont dites asynchrones, car elles surviennent à des moments totalement
imprévisibles du fonctionnement du processeur, et non en rapport avec des instructions
précises. Le processeur est prévenu de l’occurrence de l’interruption par l’activation d’une
ligne externe spécifique, souvent commune à plusieurs ou à toutes les interruptions. C’est
seulement ensuite que le type de l’interruption sera identifié, et qu’un sous-programme
172 CHAPITRE V. PROGRAMMATION DU PROCESSEUR 32 BITS CRAPS/SPARC

associé sera exécuté, et ce d’une façon qui devra rester transparente pour le programme
principal qui était en cours d’exécution à ce moment. Par exemple lorsque vous appuyez
sur une touche de votre clavier d’ordinateur, aucun programme n’attend spécifiquement
et activement cette frappe. Lors de la frappe, une interruption est générée par le contrôleur
de clavier vers le processeur, et celui-ci suspend le programme en cours d’exécution, et
appelle un sous-programme gestionnaire de cette interruption, dont le rôle va être d’obtenir
du contrôleur de clavier le code de la touche frappée et de le placer dans un buffer situé au
niveau du système d’exploitation. L’exécution de ce sous-programme sera très rapide et on
verra qu’elle sera faite de façon transparente pour le programme qui a été interrompu.
Dans CRAPS, il n’y a que deux sources d’interruption : le timer et les lignes
d’entrées/sorties. On pourra programmer le timer pour qu’il déclenche une interruption
à chaque front montant de sa sortie, et cela permettra par exemple d’implanter un
scheduler de tâches ou une horloge temps réel. On pourra également programmer les
lignes d’entrées/sorties pour déclencher une interruption à l’arrivée d’un front montant ou
descendant (sens programmable au choix) sur une ligne configurée en entrée.

Une interruption = un coup de sonnette


Pour mieux faire comprendre les traps et les interruptions, on peut employer une
analogie domestique. Supposons que nous soyons en train de faire un gâteau, en suivant
les instructions d’une recette : ce sera l’équivalent du programme principal en cours
d’exécution. La plupart des instructions de la recette sont sans problème : remuer la pâte,
etc. Un petit nombre d’entre-elles peuvent mal se passer : si on doit casser un oeuf mais
qu’il n’est pas frais ; si on doit mettre une pincée de sel mais qu’on n’a plus de sel, etc. Ces
problèmes spécifiques sont associés à des instructions spécifiques, ce sont l’équivalent des
traps. Le plus souvent, leur prise en compte consistera à abandonner la tâche en cours.
Supposons maintenant que pendant que nous faisons cette recette nous attendions la
visite du facteur, mais que notre sonnette soit en panne. Comme le moment précis de cette
visite n’est pas défini, cela va nous obliger à interrompre sans cesse notre recette pour aller
regarder par la fenêtre. Cela sera pénalisant pour la recette, mais aussi pas très efficace : le
facteur peut très bien être venu et reparti en pensant qu’il n’y avait personne, si on laisse
passer trop de temps entre deux coups d’oeil. Avec une sonnette, qui joue le rôle d’une
ligne d’interruption, on peut réaliser la recette sans penser sans cesse au facteur. Si un coup
de sonnette survient, on terminera l’instruction en cours, (par exemple, il faut terminer de
casser un oeuf si on a déjà commencé) et on cochera sur la recette l’endroit où on s’est arrêté.
On réagira ensuite à l’interruption par une procédure adaptée : aller à la porte, ouvrir, saluer,
récupérer éventuellement des données, etc. De retour de cette procédure, dont le traitement
aura été court, on reprendra la recette à l’instruction qui suit celle qu’on avait cochée.
Cette analogie permet même de présenter le problème des niveaux de priorité entre
exceptions. Comment doit-on faire si par exemple le téléphone sonne alors qu’on est en
train de répondre au visiteur à la porte ? Si on juge que le téléphone est plus prioritaire que
la porte d’entrée, alors on interrompra le dialogue en cours avec le visiteur pour effectuer
la procédure associée au téléphone. Une fois qu’on aura fini de répondre, on reprendra
le dialogue avec le visiteur là où on l’avait arrêté, pour finalement revenir au programme
principal (la recette), dont l’exécution n’aura pas été perturbée.
10. Programmation des exceptions 173

Niveaux de priorité
Ainsi, à chaque exception (trap ou interruption) est associé un niveau de priorité, codé
généralement sous forme d’un nombre entier. Par ailleurs le processeur entretient dans un
registre d’état une valeur de niveau courant d’exécution. Lorsque le processeur exécute
un programme à un niveau p, il ne peut être interrompu que par une exception de niveau
q supérieur à p. Lorsque cette interruption est prise en compte et que son gestionnaire est
exécuté, le processeur élève son niveau d’exécution à q de façon à ne plus pouvoir être
interrompu que par des exceptions de niveau supérieur à q. Lorsque le processeur sort du
gestionnaire de cette exception, il reprend le niveau d’exécution qu’il avait avant l’appel. Le
programme principal s’exécute au niveau 0, et il est par conséquent interruptible par toutes
les exceptions.
La figure V.34 montre un exemple de scénario d’exécution du programme principal et
de deux gestionnaires d’exception de niveaux de priorité différents.

priorité

gestionnaire
excep.#13 6

gestionnaire
excep.#25 3

programme 0
principal

Figure V.34. Niveaux de priorités et gestionnaires d’exceptions. Le programme principal


est interrompu par l’exception #25,qui lui même est interrompu par l’exception #13; chaque
interruption est transparente pour celui qui est interrompu.

Dans CRAPS, il y a 16 niveaux de priorité, codés sous forme d’un entier de 4 bits. Le
niveau d’exécution courant du processeur est stocké dans le champ PIL du registre %psr
(voir section 2.2). Au reset, le niveau d’exécution PIL est à 0.

Numéro de l’exception
À chaque exception est également associé un numéro de type, unique par type d’exception,
qui va servir à localiser l’adresse de son gestionnaire d’exception.Ce numéro n’a en principe
pas de rapport direct avec le niveau de priorité de l’exception.
Dans CRAPS, comme dans le SPARC, le numéro de type d’exception est codé sur 8
bits, et il y a donc 256 sortes d’exceptions différentes. Seul un sous-ensemble des exceptions
du SPARC a été implémenté, donné figure V.35.

Le cas de reset est particulier : il est listé dans le tableau comme l’exception la
plus prioritaire, mais elle n’a pas de numéro. En fait, l’occurrence d’un reset matériel
va provoquer la remise à zéro de tous les registres, y compris %pc, et le contrôle va
reprendre en mode non superviseur à l’adresse 0, avec un niveau d’exécution de 0, ce qui
174 CHAPITRE V. PROGRAMMATION DU PROCESSEUR 32 BITS CRAPS/SPARC

exception priorité numéro de type (tt)


reset 15 voir texte
bus error 14 14
instruction privilégiée 13 13
instruction illégale 12 12
division par zéro 11 11
erreur d’alignement 10 10
interruption timer 8 8
interruption entrées/sorties 7 7

Figure V.35. Tableau des exceptions, de leurs priorités et de leurs numéros de type.

est le comportement souhaité. Le reset est considéré comme une exception sur un plan
’théorique’, mais il est géré d’une façon différente en pratique.
Par ailleurs, afin de simplifier la structure du sous-sytème d’exceptions, les numéros de
type d’exception et le niveau de priorité ont été rendus égaux.

Gestionnaires d’exceptions
Lorsqu’une exception est prise en compte, un sous-programme appelé gestionnaire
d’exception ou handler d’exception est appelé, associé au numéro de type tt de l’exception.
Il existe généralement une table de vecteurs d’exception qui contient les adresses de tous les
gestionnaires d’exception, classée par numéro de type tt. Cette table est initialisée lors du
chargement du système d’exploitation, ou elle peut être en ROM.
Dans CRAPS, lorsqu’une exception est prise en compte, son numéro tt est copié dans
le champ tt du registre %tbr. On rappelle figure V.36 sa structure, déjà présentée section
2.2. L’adresse ainsi formée est unique par numéro tt, et les adresses possibles sont espacées

TBA (trap base address) tt zéros


31 12 11 4 0
TBA = poids forts adresse table des exceptions
tt = trap type = numero de l’exception

Figure V.36. %tbr (Trap Base Register).

de 16 en 16, puisque les 4 bits de poids faibles valent 0. À cette adresse sont placés, non
pas l’adresse du gestionnaire comme c’est le cas dans beaucoup de processeurs, mais les
premières instructions elles-mêmes du gestionnaire associé à l’exception. Comme il n’y a
a-priori la place que pour 4 instructions (16 octets), celui-ci doit très vite effectuer un saut
vers un autre emplacement en mémoire pour continuer son exécution.

10.2. Prise en compte d’une exception en CRAPS


On va maintenant décrire exactement comment les exceptions sont prises en compte
pour le processeur CRAPS, sachant que la procédure est globalement la même pour les
autres processeurs, mais que de nombreux détails peuvent varier. Celle du CRAPS est une
10. Programmation des exceptions 175

simplification de celle du SPARC, mais en conserve les traits les plus importants.

Conditions de prise en compte d’une exception


Une exception est prise en compte si :

• ET = 1 (trap enable = 1) et

• le niveau de priorité de l’exception est strictement supérieur au niveau courant


d’exécution. (champ PIL du registre %psr)

Si une exception se produit pendant que ET = 0, elle est ignorée et non mémorisée ; tout se
passe comme si elle n’avait jamais eu lieu. Si une exception se produit pendant que ET = 1
mais que son niveau est inférieur ou égal au niveau d’exécution courant, elle est mémorisée
et sera prise en compte lorsque le niveau d’exécution redescendra au niveau ou au dessous du
niveau de l’exception. Par contre si durant cette période une exception de même numéro se
produit, elle sera perdue car celle qui est mémorisée en attente empêchera la mémorisation
de la nouvelle.

Procédure matérielle de prise en compte de l’exception


Une fois les conditions de prise en compte réunies, la procédure suivant, ininterruptible, est
exécutée automatiquement par le processeur (hardware) :

1 le processeur termine l’instruction en cours.

2 le processeur passe en mode superviseur : S ← 1. Le gestionnaire d’interruption aura


ainsi accès à toutes les instructions.
3 %pc et %psr sont sauvegardés dans la pile.

4 le niveau de priorité de l’exception est copié dans le champ PIL du registre %psr,
élevant ainsi le niveau d’exécution du processeur.

5 l’adresse du gestionnaire d’exception est calculée par la formule : %tbr + 16 * numéro


de type.

6 le contrôle est passé à l’adresse du gestionnaire de l’exception : %pc ← adresse


gestionnaire. On notera qu’à cet endroit, il n’y a la place que pour 4 instructions et
qu’un saut vers un autre emplacement est nécessaire.

Contenu du gestionnaire d’exception


L’écriture d’un gestionnaire d’exception est une tâche délicate ; elle est généralement
réservée aux concepteurs des systèmes d’exploitation. Sur certaines plates-formes comme
Linux, il est possible à un programmeur averti de le faire ; c’est bien sûr possible aussi sur
la machine CRAPS.
S’il s’agit d’un gestionnaire de trap, c’est à dire d’une exception provoquée par
l’exécution incorrecte d’une instruction d’un programme, le code associé va généralement
consister en l’abandon de la tâche en cours. L’écriture de ce code sort du cadre de cette
discussion.
176 CHAPITRE V. PROGRAMMATION DU PROCESSEUR 32 BITS CRAPS/SPARC

S’il s’agit du gestionnaire d’une interruption, il faut faire en sorte que son exécution
soit transparente pour le programme en cours de fonctionnement. Cela nécessite plusieurs
conditions :
• tous les registres que ce gestionnaire va utiliser doivent être sauvegardés (dans la pile)
dès l’entrée dans le gestionnaire et doivent être restaurés juste avant d’en sortir.

• le gestionnaire doit être terminé par l’instruction RETE.

L’instruction RETE dépile les valeurs de %pc et %psr qui avaient été empilées
automatiquement par le processeur lors de la prise en compte de l’interruption, puis
incrémente %pc de 4. Les deux aspects cruciaux à comprendre sont, d’une part que la
restauration de %pc permet le retour à l’instruction qui suit celle qui a été interrompue,
d’autre part que la restauration de %psr permet de retrouver les valeurs de départ du niveau
d’exécution et du bit superviseur. Au retour du gestionnaire d’exception, le programme
principal n’a aucun moyen de savoir qu’il a été interrompu, car rien n’a changé pour lui entre
le moment de l’interruption et celui du retour.

10.3. Exemple 1 : horloge temps réel


À titre d’exemple, on va mettre en place une horloge qui permettra à un programme
d’obtenir une heure d’une précision parfaite, sous forme d’une fonction getTimeMillis.
À chaque appel de cette fonction, on obtiendra dans %r1 le nombre de millisecondes
écoulées depuis la mise en route de la machine ; cette valeur s’incrémentera de façon
exacte entre chaque appel, sans que le programme principal en cours d’exécution ne s’en
occupe explicitement.
On va pour cela programmer le timer pour qu’il fournisse des impulsions toutes les
millisecondes, et le déclarer comme source d’interruption. C’est en effet possible dans
CRAPS comme on peut le voir dans le tableau de la figure V.35 et cette interruption a le
numéro 8 et la valeur de priorité 8.

10.4. Exemple 2 : programmation d’un scheduler de tâches en CRAPS


La figure V.38 montre un programme complet de scheduler de tâches pour CRAPS,
qui utilise le timer pour fournir une source d’interruption régulière pour effectuer la
commutation d’une tâche à la suivante. Le programme est initialisé avec 4 tâches prog0,…
prog3 qui effectuent infiniment l’inversion et la désinversion d’une chaîne de caractères ;
chacune d’elles possède son propre espace de pile.
Le scheduler entretient le numéro de la tâche en cours d’exécution, qui sert d’index
dans une table de pointeurs de pile, TABSP. Lors de l’arrivée d’une interruption timer, le
gestionnaire de cette exception sauvegarde le contexte d’exécution de la tâche qui va être
suspendue dans la propre pile de la tâche. %pc et %psr ont déjà été sauvegardés par le
traitement de l’exception ; il sauvegarde ensuite tous les registres.Le pointeur de pile de cette
tâche en cours de suspension est sauvegardé dans TABSP, le numéro de la tâche courante est
incrémenté (modulo 4) et le pointeur de pile de la tâche qui est réveillé est lu. Son contexte
est relu à partir de sa propre pile et l’instruction finale rete va restaurer dans %pc l’adresse
courante d’exécution de cette tâche.
10. Programmation des exceptions 177

RAM = 0x1000000 ; début de la RAM


STACK = 0x1000100 ; sommet de la pile
TIMER_BASE = 0x400000 ; adresse de base du timer
TT_TIMER = 8 ; numéro de type de l’interruption timer

set STACK, %sp ; initialise sommet de pile


;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; installe la table des exceptions
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
set TABEXCEP, %r1
wrtbr %r1 ; initialisation champ TBA de %tbr
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; initialisation timer
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
set BASETIMER, %r1 ; %r1 = base timer
set 0x00320064, %r2 ; N=50 & P=100
st %r2, [%r1] ; écriture commande #1
set 0x00000001, %r2 ; prediv=0 & start=1
st %r2, [%r1+4] ; écriture commande #2
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; boucle infinie
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
loop call getTimeMillis
ba loop
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; fonction getTimeTicks : renvoie dans %r1 le nombre
; de millisecondes écoulées depuis reset
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
getTimeMillis set TIME, %r1
ld [%r1], %r1
ret
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
TABEXCEP ; table des exceptions
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
.org TABEXCEP + 2*TT_TIMER ; adresse handler timer
.word HDL_TIMER ; branchement à un endroit plus spacieux
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; timer handler : incrementation du mot mémoire TIME
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
HDL_TIMER: ;; sauvegarde de %r1 et %r2
subcc %sp, 2, %sp
st %r1, [%sp]
subcc %sp, 2, %sp
st %r2, [%sp]
set BASETIMER, %r1
setq 1, %r2
st %r2, [%r1+8] ; raz int. timer
set TIME, %r1
ld [%r1], %r2 ; lecture de TIME
inc %r2 ; incrémentation
st %r2, [%r1] ; stockage de la valeur incrémentée
;; restauration de %r2 et %r1
ld [%sp], %r2
addcc %sp, 2, %sp
ld [%sp], %r1
addcc %sp, 2, %sp
rete
.org RAM
TIME .word 0

Figure V.37. Programme d’horloge temps réel par interruptions.


178 CHAPITRE V. PROGRAMMATION DU PROCESSEUR 32 BITS CRAPS/SPARC

RAM = 0x1000000 ; incrémentation numéro tâche courante modulo 3


STACK0 = 0x1000100 inc %r2
STACK1 = 0x1000200 subcc %r2, 3, %r0
STACK2 = 0x1000300 bneg sav
;; installe la table des vecteurs d’exceptions clr %r2
set 0xF000, %sp sav st %r2, [%r1]
set TABEXCEP, %r1 ; restauration depuis TABSP du %sp de
wrtbr %r1 ; la tâche suivante
;; installe les tâches à exécuter addcc %r2, %r2, %r3
set prog1, %r1 ld [%r4+%r3], %sp
setq 1, %r2
call creer_tache ; restauration des registres %r1..%r5,%r7
set prog2, %r1 ; depuis la nouvelle pile
setq 2, %r2 ld [%sp],%r7
call creer_tache addcc %sp, 2, %sp
;; lancement du timer qui cadence le scheduling ld [%sp],%r5
set 0x4000, %r2 ; %r2 = timer base address addcc %sp, 2, %sp
set 1000, %r1 ; periode = 1000 ld [%sp],%r4
st %r1, [%r2+2] addcc %sp, 2, %sp
set 0b11, %r1 ; start + cyclic ld [%sp],%r3
st %r1, [%r2] ; -> lance le scheduler addcc %sp, 2, %sp
; lancement de la première tâche ld [%sp],%r2
set TABSP, %r1 addcc %sp, 2, %sp
ld [%r1], %sp ; lecture de son %sp ld [%sp],%r1
ba prog0 addcc %sp, 2, %sp
;;; cree une nouvelle tâche de numéro %r2 et ; restauration des anciens %psr et %pc
;;; d’adresse de depart %r1 ; donc retour de la d’ou on etait parti
creer_tache set TABSP, %r3 rete
addcc %r2, %r2, %r4
ld [%r3+%r4], %sp ; lecture du %sp ;;;;;;;;;;; TACHES UTILISATEURS ;;;;;;;;;;
subcc %sp, 2, %sp
st %r1, [%sp] ; empilement %pc ;;;;; tâche #0 : inversion et desinversion de STR0
setq 7, %r5 ; empile 7 x 0 prog0: set STR0, %r1
loop subcc %sp, 2, %sp l0: call inverse
st %r0, [%sp] ba l0
dec %r5
bne loop ;;;;; tâche #1 : inversion et desinversion de STR1
st %sp, [%r3+%r4] ; sauve %sp dans TABSP prog1: set STR1, %r1
ret l1: call inverse
ba l1
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
TABEXCEP ; exception table ;;;;; tâche #2 : inversion et desinversion de STR2
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; prog2: set STR2, %r1
.org TABEXCEP + 2*3 ; handler timer l2: call inverse
jmp HDL_TIMER ba l2

HDL_TIMER: ; %pc et %psr deja empiles dans la pile inverse: clr %r3 ; i <- 0
; du process en cours sethi 0,%r4
; sauvegarde dans cette pile rev1 ldub [%r1+%r3], %r4
; les registres %r1..%r5,%r7 tst %r4 ; tq (str[i]<>0)
addcc %sp, -2, %sp be rev2
st %r1, [%sp] inc %r3 ; i <- i + 1
addcc %sp, -2, %sp ba rev1 ; fin tq
st %r2, [%sp] rev2 clr %r5 ; j <- 0
addcc %sp, -2, %sp dec %r3 ; i <- i - 1
st %r3, [%sp] rev3 cmp %r5, %r3
addcc %sp, -2, %sp bpos outrev ; tq (i>j)
st %r4, [%sp] ldub [%r1+%r3], %r4 ; x <- str[i]
addcc %sp, -2, %sp ldub [%r1+%r5], %r2 ; y <- str[j]
st %r5, [%sp] stb %r4, [%r1+%r5] ; str[i] <- y
addcc %sp, -2, %sp stb %r2, [%r1+%r3] ; str[j] <- x
st %r7, [%sp] dec %r3 ; i <- i - 1
; raz int.timer inc %r5 ; j <- j + 1
set BASETIMER, %r1 ba rev3 ; fin tq
setq 1, %r2 outrev ret
st %r2, [%r1+8]
; sauvegarde du %sp de la tâche courante .org RAM
; dans TABSP ; numero de la tâche courante
set NUMPROC, %r1 NUMPROC .word 0
ld [%r1], %r2 ; table des tâches
addcc %r2, %r2, %r3 TABSP .word STACK0, STACK1, STACK2
set TABSP, %r4
st %sp, [%r4+%r3] ; chaînes pour inversion en RAM
STR0 .byte ’abcdefg’,0
STR1 .byte ’123456789’,0
STR2 .byte ’xyz’,0

Figure V.38. Programme complet du scheduler de tâches.

11. Exercices corrigés


11. Exercices corrigés 179

11.1. Exercice 1 : multiplication programmée

Énoncé
Écrire un sous-programme qui effectue la multiplication de deux nombres non signés de 16
bits placés dans les poids faibles de %r1 et %r2 respectivement, avec un résultat sur 32 bits
dans %r3.

Solution
On pourrait mettre en place une boucle qu’on effectuerait %r1 fois, et dans laquelle on
cumulerait %r2 dans %r3. Cette méthode serait simple, mais particulièrement inefficace
pour de grandes valeurs de %r1. On va plutôt utiliser une méthode inspirée de la figure II.46,
dans laquelle on prend les bits de %r2 un par un en commençant par le poids faible, et on
cumule dans %r3 (initialisé à 0) la valeur de %r1, que l’on décale vers la gauche à chaque
étape. Cela correspond à l’algorithme suivant :

; calcul du produit A x B
résultat <- 0 ;
tant que (B <> 0) faire
b0 <- poids_faible(B) ;
décaler B d’un bit vers la droite ;
si b0 = 1 faire
résultat <- résultat + A ;
fin si
décaler A d’un bit vers la gauche ;
fin tq

Le programme donné figure V.39 est une traduction littérale de l’algorithme. On notera le
test bne noadd qui n’est pas fait immédiatement après l’instruction andcc %r2, 1, %r0
qui positionne le flag Z ; cela est possible car l’instruction qui la suit srl %r2, 1, %r2
ne modifie pas les flags.

11.2. Exercice 2 : système de sécurité

Énoncé
On souhaite utiliser les lignes d’entrées/sorties de CRAPS pour réaliser un système de mise
en marche de machine dangereuse. Les lignes IO[0] et IO[1] seront configurées en entrées ;
on supposera qu’elles sont reliées à des boutons poussoirs appelés A et B respectivement,
équipés d’un dispositif anti-rebond. La ligne IO[2] sera configurée en sortie, et commandera
la mise en marche de la machine. On demande d’écrire un programme qui fonctionne en
permanence, et qui déclenche la mise en marche de la machine lorsque la procédure suivante
est respectée :

1 A et B doivent être relâchés initialement ;

2 appuyer sur A,

3 appuyer sur B : la machine se met en marche.


180 CHAPITRE V. PROGRAMMATION DU PROCESSEUR 32 BITS CRAPS/SPARC

main ;; programme principal de test


set 17, %r1 ; A <- 17
set 14, %r2 ; B <- 14
call mulu16 ; calcul de A x B
ba * ; arrêt

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; multiplication non signé
;; calcule %r1.L x %r2.L, résultat dans %r3
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
mulu16 clr %r3 ; résultat <- 0
loop tst %r2 ; tant que B <> 0
be fin ;
andcc %r2, 1, %r0 ; b0 (Z) <- poids faible de B
srl %r2, 1, %r2 ; décale B vers la droite
bne noadd ; si b0 = 1 faire
addcc %r1, %r3, %r3 ; résultat <- résultat + A
noadd sll %r1, 1, %r1 ; décale A à gauche
ba loop ; fin tq
fin ret

Figure V.39. Sous-programme de multiplication non signée 16 bits x 16 bits vers 32 bits.

Toute autre manipulation arrête ou laisse la machine arrêtée ; il faut ensuite reprendre la
procédure au point 1 pour la mettre en marche.

Solution
On a déjà réalisé une telle commande à l’aide de circuits séquentiels. On demande
maintenant de le réaliser par programme, comme on pourrait le faire avec un
microcontrôleur.
On rappelle le graphe d’états de ce système figure V.40.

00 10 11

M=0 10 11
a b c
M=0
01 M=1
11 00
01 00
01
00 10
e
M=0

Figure V.40. Graphe d’états du système de sécurité.

À chaque état dans le graphe correspondra une position dans le programme. Cela
conduit à l’algorithme suivant :

configurer IO[1] (A) et IO[0] (B) en entrées,


IO[2] (M) en sortie ;
label a:
écrire 0 sur M ;
11. Exercices corrigés 181

label a1:
lire état de A et B ;
cas (A,B)
(0,0): aller en a1 ;
(1,0): aller en b ;
autre: aller en e ;
fin cas
label b:
écrire 0 sur M ;
label b1:
lire état de A et B ;
cas (A,B)
(1,0): aller en b1 ;
(1,1): aller en c ;
autre: aller en e ;
fin cas
label c:
écrire 1 sur M ;
label c1:
lire état de A et B ;
cas (A,B)
(1,1): aller en c1 ;
autre: aller en e ;
fin cas

Cet algorithme se traduit de façon littérale par le programme donné figure V.41.

11.3. Exercice 3 : inversion d’une chaîne de caractères

Énoncé
Écrire un sous-programme qui inverse ’sur place’ une chaîne de caractères ASCII située en
RAM et pointée par %r1.

Solution
Les caractères sont ici stockés sous forme de codes ASCII dans des octets consécutifs. On
emploiera donc les instruction ldub et stb pour les lire et les écrire en mémoire. L’idée de
l’algorithme à implémenter est de conserver un pointeur en début de chaîne et d’en placer
un autre en fin de chaîne. On intervertit alors les caractères qu’ils pointent, et on les déplace
d’un cran l’un vers l’autre. On recommence ensuite l’échange et le déplacement jusqu’à ce
qu’ils se rencontrent. Cela donne plus précisément l’algorithme suivant :

i <- 0 ;
tant que (str[i] <> 0) faire
i <- i + 1 ;
fin tq ;
i <- i - 1 ;
j <- 0 ;
182 CHAPITRE V. PROGRAMMATION DU PROCESSEUR 32 BITS CRAPS/SPARC

IOBASE = 0x600000 ; adresse de base entrées/sorties

main set IOBASE, %r1 ; adresse de base I/O dans %r1


setq 0b100, %r2 ; configuration IO[1..0] entrées
st %r2, [%r1+2] ; IO[2] sortie

a: setq 0b000, %r2 ; M <- 0


st %r2, [%r1]
a1: ld [%r1], %r2 ; lecture état de A et B
andcc %r2, 0b11, %r2 ; isolation des bits 0 et 1
subcc %r2, 0b00, %r0
be a1 ; (A,B) = (0,0) : aller en a1
subcc %r2, 0b10, %r0 ; (A,B) = (1,0) : aller en b
bne e ; sinon aller en e ;
b: setq 0b000, %r2
st %r2, [%r1] ; M <- 0
b1: ld [%r1], %r2 ; lecture état de A et B
andcc %r2, 0b11, %r2 ; isolation des bits 0 et 1
subcc %r2, 0b10, %r0
be b1 ; (A,B) = (1,0) : aller en b1
subcc %r2, 0b11, %r0 ; (A,B) = (1,1) : aller en c
bne e ; sinon aller en e
c: setq 0b100, %r2
st %r2, [%r1] ; M <- 1
c1: ld [%r1], %r2 ; lecture état de A et B
andcc %r2, 0b11, %r2 ; isolation des bits 0 et 1
subcc %r2, 0b11, %r0 ; (A,B) = (1,1) : aller en c1
be c1 ; sinon aller en e
e: setq 0b000, %r2
st %r2, [%r1] ; M <- 0
e1: ld [%r1], %r2 ; lecture état de A et B
andcc %r2, 0b11, %r2 ; isolation des bits 0 et 1
subcc %r2, 0b00, %r0 ; (A,B) <> (0,0) : aller en e1
bne e1
ba a ; sinon aller en a

Figure V.41. Programme de mise en marche de sécurité pour machine dangereuse.

tant que (i > j) faire


x <- str[i];
y <- str[j];
str[i] <- y ;
str[j] <- x ;
i <- i - 1 ;
j <- j + 1 ;
fin tq ;

La traduction littérale de cet algorithme est donnée figure V.42.


11. Exercices corrigés 183

RAM = 0x10000000 ; adresse début de RAM


STACK = RAM + 0x1000 ; adresse sommet pile

main ;; programme de test


set STACK, %sp
set STR, %r1
call reverse
ba *

reverse clr %r3 ; i <- 0


sethi 0,%r4
rev1 ldub [%r1+%r3], %r4
tst %r4 ; tq (str[i]<>0)
be rev2
inc %r3 ; i <- i + 1
ba rev1 ; fin tq
rev2 clr %r5 ; j <- 0
dec %r3 ; i <- i - 1
rev3 cmp %r5, %r3
bpos outrev ; tq (i>j)
ldub [%r1+%r3], %r4 ; x <- str[i]
ldub [%r1+%r5], %r2 ; y <- str[j]
stb %r4, [%r1+%r5] ; str[i] <- y
stb %r2, [%r1+%r3] ; str[j] <- x
dec %r3 ; i <- i - 1
inc %r5 ; j <- j + 1
ba rev3 ; fin tq
ret

.org RAM
STR .byte ’azertyuiop’,0 ; chaîne de test

Figure V.42. Sous-programme d’inversion sur place d’une chaîne de caractères.

11.4. Exercice 4 : conversion binaire / ASCII

Énoncé
Écrire un sous-programme qui convertit un nombre entier placé dans %r1, codé en binaire
non signé, en une chaîne de caractères ASCII qui en est sa représentation décimale, à partir
de l’adresse mémoire contenue dans %r2.

Solution
On applique un algorithme connu sous le nom de ’schéma de Horner’, qui consiste à diviser
le nombre par 10, à conserver le reste, puis à diviser à nouveau le quotient par 10 et à
garder le reste, et ainsi de suite jusqu’à ce que le nombre soit plus petit que 10 ; la suite des
restes obtenus forme la suite inversée des chiffres décimaux cherchés. Plus précisément,
l’algorithme est :
184 CHAPITRE V. PROGRAMMATION DU PROCESSEUR 32 BITS CRAPS/SPARC

i <- 0 ;
tant que (n >= 10) faire
(q,r) <- n / 10 ;
n <- q ;
str[i] <- r + ’0’;
i <- i + 1 ;
fin tq
str[i] <- n + ’0’;
i <- i + 1 ;
str[i] <- 0 ;
inverser sur place str ;

La traduction littérale de cet algorithme est donnée figure V.43.

RAM = 0x10000000 ; adresse début de RAM


STACK = RAM + 0x1000 ; adresse sommet pile

main ;; programme de test


set STACK, %sp
set 1234, %r1
set STR, %r2
call bin2ascii
ba *

bin2ascii clr %r5 ; i <- 0


b1 cmp %r1, 10 ; tq (n>=10)
bneg b2

udivcc %r5, 10, %r5


sll %r5, 16, %r3
srl %r3, 16, %r3 ; r dans %r3
srl %r5, 16, %r5 ; n <- q

addcc %r3, ’0’, %r3


stb %r3, [%r2+%r5] ; str[i] <- r + ’0’
inc %r5 ; i <- i + 1
ba b1 ; fin tq
b2 addcc %r1, ’0’, %r1
stb %r1, [%r2+%r5] ; str[i] <- n + ’0’
inc %r5 ; i <- i + 1
stb %r0, [%r2+%r5] ; str[i] <- 0

mov %r2, %r1


call reverse
ret

.org RAM
STR .byte ’azertyuiop’,0 ; chaîne de test

Figure V.43. Programme de conversion binaire vers décimal.


185
Chapitre VI

Construction du processeur 32 bits CRAPS

Nous avons appris à programmer le processeur CRAPS dans le chapitre précédent,


afin d’avoir une idée plus précise de la nature des opérations qu’il va devoir effectuer. On
va maintenant réaliser effectivement CRAPS, uniquement par l’assemblage de modules qui
ont été présentés au chapitre 4 ; les principes de modularité qui ont été mis en avant dans
cet ouvrage sont donc effectivement mis en oeuvre d’un bout à l’autre de la conception
du processeur.
Au delà de la construction de ce processeur particulier, ce sont les principes de base de
la conception de tous les processeurs et microcontrôleurs qui seront abordés.

1. Les registres
CRAPS possède 32 registres banalisés pour le programmeur : %r0, …, %r31, ainsi que
des registres utilisés pour son fonctionnement :

• %pc : program counter, qui contient l’adresse de l’instruction en cours d’exécution.

• %ir : instruction register, qui contient l’instruction en cours d’exécution.

• %psr : program status register, qui contient les flags et des informations relatives
aux exceptions.

• %tbr : trap base register, qui contient l’adresse de la table des exceptions.

À cette liste on ajoutera deux registres qu’on appellera %tmp1 et %tmp2, inaccessibles
au programmeur, qui seront utilisés par le processeur lors de l’exécution de certaines
instructions.
Pour uniformiser les accès, on a donné également des numéros à ces derniers registres,
dans l’intervalle [32, 63]. Ainsi, tous les registres sont désignés par un numéro codable sur
6 bits. Les numéros des registres non banalisés de CRAPS sont :

• %tmp1 & %tmp2 : %r32 & %r33

• %tbr : %r60
• %psr : %r61

• %pc : %r62

• %ir : %r63

186
1. Les registres 187

Le sous-ensemble des registres a la structure générale montrée à la figure VI.1.

0 0 %r0 0
décodeur 1 64
1 %r1
64 1 décodeur 6
bus D bsel[63..0] bus B breg[5..0]
dsel[63..0] 2 %r2
63 63
CLK ...
6 dreg[5..0] 0
30 %r14 64 1 décodeur 6

31 %r15 bus A areg[5..0]


asel[63..0]
63
32 %tmp1
33 %tmp2

32
32 32
60 %tbr setNZ,setVC
61
%psr NZVC PIL
62 %pc
63 op rd rs1 rs2 4 4
%ir
Bus D Bus A Bus B
dbus[31..0] abus[31..0] bbus[31..0]

N,Z,V,C (UAL)
N,Z,V,C

Figure VI.1. Le sous-ensemble des registres du processeur CRAPS. Le programmeur n’a


un accès banalisé qu’aux 32 premiers d’entre-eux.

On voit trois bus de 32 bits sur cette figure, qui sont les trois grands bus du processeur
sur lesquels circuleront toutes les données manipulées.

Les bus A et B
Sur les bus A et B on peut faire venir les valeurs de deux registres simultanément : il
suffit de mettre leurs numéros sur areg et breg, respectivement.Par exemple, si on souhaite
avoir %tbr sur le bus A et %r1 sur le bus B, on place 60 sur areg et 1 sur breg. Les valeurs
des deux registres apparaissent alors sur les bus A et B respectivement, de façon asynchrone.
Les deux bus fonctionnent indépendamment et en parallèle.
Le bus D
Sur le bus D (qui sera souvent un bus de données, d’où son nom) on place une valeur
qu’on souhaite écrire dans un registre. On doit placer le numéro de ce registre dans dreg, et
envoyer un front montant sur l’horloge clk : la valeur sur le bus D est alors écrite dans le
registre de numéro dreg.

Le registre %psr et l’écriture des flags


On l’a vu au chapitre précédent, les flags N, Z, V, C sont les valeurs des bits 23 à 20
du registre %psr, c’est à dire du registre %r61. Pourtant ils seront positionnés, tantôt par
la procédure ’normale’ d’écriture en %r61 (par exemple lors de la restauration de %psr à
un retour de gestionnaire d’exception), mais aussi également lors de l’activation de signaux
188 CHAPITRE VI. CONSTRUCTION DU PROCESSEUR 32 BITS CRAPS

dédiés setN, setZ et setVC. On désire par exemple que, lorsque setVC vaut 1 au front
d’horloge, les bits V et C mémorisent les valeurs V_UAL et C_UAL qui proviennent de
l’UAL, et cela concurremment à l’écriture standard dans un autre registre (dont le numéro
est sur dreg). V et C sont groupés, car ils sont toujours positionnés ensemble par des
instructions telles que addcc. N et Z ne sont pas groupés, car une opération comme umulcc
ne positionne que Z.

Les cas particuliers des registres %r0 et %r34 à %r38


Du chapitre précédent sur la programmation de CRAPS on connaît déjà le registre %r0
et son usage très pratique. Il doit nécessairement être traité de façon particulière : il sera par
exemple inutile de prévoir des bascules de mémorisation pour lui, et on devra faire en sorte
que sa lecture donne toujours 0.
De la même façon, il sera nécessaire de disposer des constantes 2, 4, 8, 16 et 24
lors du séquencement des instructions. La constante 4 sera utilisée par exemple lors de
l’incrémentation de %pc de 4 pour le passage à l’instruction suivante. Les autres constantes
ont des usages encore plus spécifiques, qui seront décrits plus loin. On a choisi de faire en
sorte que ces constantes soient les valeurs des registres %r34 (= 2), %r35 (= 4), %r36 (= 8),
%r37 (= 16) et %r38 (= 24) ; de même que pour %r0, l’écriture dans ces registres spéciaux
sera sans effet, et leur lecture renverra toujours ces valeurs constantes.

Structure d’un registre ordinaire


On voit figure VI.2 comment est construit un registre ordinaire, c’est à dire avec un
indice i qui n’est, ni 0 (pour %r0), ni 34 à 38 (pour les constantes), ni 61 (pour %psr).

Figure VI.2. Structure d’un registre ordinaire.

Un registre est bien composé de 32 bascules D. Une valeur de 1pour le signal dsel[i]
indique qu’une écriture est souhaitée dans le registre d’indice i. Comme dsel[i] est
1. Les registres 189

branché sur l’entrée de validation des bascules, les bascules vont se charger avec les valeurs
présentes sur les lignes dbus[31..0], c’est à dire le bus D.
En ce qui concerne la lecture sur les bus A et B, elle s’effectue bien de façon asynchrone
par l’intermédiaire des buffers 3-états du bas de la figure. Quand par exemple asel[i]
est activée (c’est à dire que areg[5..0] = i), les registres vont placer leurs valeurs sur les
lignes abus[31..0], c’est à dire le bus A. Il n’y aura pas de conflit avec les autres registres,
car les lignes qui activent les buffers 3-états sont les sorties d’un même décodeur, et donc
toutes mutuellement exclusives.
Cela conduit à l’écriture SHDL suivante, pour %r1 par exemple :
// %r1
r1[31..0] := dbus[31..0];
r1[31..0].clk = clk;
r1[31..0].rst = rst;
r1[31..0].ena = dsel1;
abus[31..0] = r1[31..0]:asel1;
bbus[31..0] = r1[31..0]:bsel1;

Structure de %psr
Pour le registre %r61 (%psr), seuls les bits 23..20 (N,Z,V,C), 11..7 (PIL), 7 (S) et 5 (ET)
sont mémorisés. Les bits N,Z,V,C peuvent être écrits de deux façons :

1 directement par écriture dans %r61 (dsel61 = 1).

2 par activation de setN, setZ ou setVC.

La figure VI.3 montre sur le bit 23 (flag N) comment cela peut être implémenté.

Figure VI.3. Structure du bit N du registre %psr.

Si on a, ni dsel23 = 1, ni setN = 1, le registre ne peut pas changer de valeur au front


d’horloge (entrée en à 0). Si setN = 1, on voit que le registre va se charger avec la valeur
N_UAL en provenance de l’UAL. Cette écriture, qui aura lieu au front d’horloge, pourra avoir
lieu en même temps qu’une écriture normale dans un autre registre. Si setN = 0 et dsel23
190 CHAPITRE VI. CONSTRUCTION DU PROCESSEUR 32 BITS CRAPS

= 1, on retombe dans le cas de l’écriture ’normale’ dans %r61, avec mémorisation du bit
dbus23 en provenance du bus D. Les sorties sur les bus A et B sont identiques aux autres
registres ; on notera que le bit N (mémorisé) sort par ailleurs directement vers l’extérieur.
Cette gestion du bit N s’écrit en SHDL :
// %r61 = %psr, bit N (bit #23)
dr[23] = dsel61*dbus23+setN*N_UAL; // N
ena23 = dsel61 + setN;
r61[23] = dr[23];
r61[23].ena = ena23;
r61[23].clk = clk;
r61[23].rst = rst;
abus[23] = r61[23]:asel61;
bbus[23] = r61[23]:bsel61;

Lecture et écriture dans %r0 et %r34 à %r38.


On rappelle que l’écriture dans les registres %r0 et %r34 à %r38 doit être sans effet et
que la lecture doit renvoyer les valeurs constantes 0, 2, 4, 8, 16 et 24 respectivement. Aucune
bascule ne doit leur être associée, puisqu’il n’y a rien à stocker. Lorsque leurs lignes de
sélection sont activées, les valeurs constantes doivent être placées directement sur le bus par
l’intermédiaire d’un buffer 3-états (figure VI.4).

Figure VI.4. Structure des registres %r0 et %r34 à %r38. L’écriture dans ces registres est
sans effet, et la lecture renvoie des valeurs constantes.

Finalement on peut écrire les équations SHDL de tout le sous-système de registres


(figure VI.5). Il est nécessaire de dupliquer le même bloc de lignes autant de fois qu’il y a de
registres, car le langage ne permet pas d’autre raccourci que l’écriture vectorielle simple à
une dimension.

2. L’unité arithmétique et logique


L’unité arithmétique et logique (UAL) est le sous-système du processeur CRAPS qui
va effectuer, non seulement les calculs explicitement associés aux instructions arithmétiques
et logiques du programme en cours d’exécution, mais aussi tous les calculs implicitement
demandés par leur mise en oeuvre, comme par exemple l’incrémentation de %pc de 4 après
l’exécution d’une instruction séquentielle, ou le calcul du déplacement lors d’un saut relatif
(type ba), etc.
2. L’unité arithmétique et logique 191

module registres(rst,clk, areg5..areg0,breg5..breg0, // %r61 = %psr


dreg5..dreg0, dbus31..dbus0,setN,setZ,setVC, D_N = dsel61*dbus23+setN*N_UAL; // N
N_UAL,Z_UAL,V_UAL,C_UAL : EN_N = dsel61 + setN;
abus31..abus0, bbus31..bbus0, N,Z,V,C) r61[23] := D_N;
r61[23].ena = EN_N;
// décodeurs 6 vers 64 D_Z = dsel61*dbus22+setZ*Z_UAL; // Z
decodeur6to64(areg5..areg0:asel63..asel0); EN_Z = dsel61 + setZ;
decodeur6to64(breg5..breg0:bsel63..bsel0); r61[22] := D_Z;
decodeur6to64(dreg5..dreg0:dsel63..dsel0); r61[22].ena = EN_Z;
// %r1
r1[31..0] := dbus[31..0]; EN_VC = dsel61 + setVC;
r1[31..0].clk = clk; D_V = dsel61*dbus21+setVC*V_UAL; // V
r1[31..0].rst = rst; D_C = dsel61*dbus20+setVC*C_UAL; // C
r1[31..0].ena = dsel1; r61[21] := D_V;
abus[31..0] = r1[31..0]:asel1; r61[20] := D_C;
bbus[31..0] = r1[31..0]:bsel1; r61[21..20].ena = EN_VC;
...
// idem pour %r2..r33; %r60,%r62..%r63 r61[11..7,5] := dbus[11..7,5]; // PIL & S & ET
... r61[11..7,5].ena = dsel[11..7,5];
// %r0 = 0
abus[31..0] = 0:asel0; r61[23..22,11..7,5].clk = clk;
bbus[31..0] = 0:bsel0; r61[23..22,11..7,5].rst = rst;
// %r34 = 2
abus[31..0] = 2:asel34; abus[23..22,11..7,5] = r61[23..22,11..7,5]:asel61;
bbus[31..0] = 2:bsel34; bbus[23..22,11..7,5] = r61[23..22,11..7,5]:bsel61;
... end module
// idem pour %r35 = 4,.., %r38 = 24
...

Figure VI.5. Écriture SHDL du sous-système des registres.

La figure VI.6 montre l’interface de ce sous-système. C’est un circuit asynchrone pur,


sans horloge. Les bus d’entrée et le bus de sortie ont une largeur de 32 bits ; la commande
cmd_ual qui indique l’opération à effectuer est codée sur 6 bits. Les signaux N,Z,V,C
sortants donnent des indications sur le résultat de l’opération qui vient d’être effectuée ; les
signaux setN, setZ et setVC indiquent si ces flags doivent être copiés dans les bits N,Z,V,C
du registre %psr au prochain front d’horloge. Un dernier signal div0 indique une tentative
de division par 0.

Figure VI.6. Interface de l’UAL.

2.1. Fonctions de l’UAL


On peut énumérer dans un tableau toutes les opérations de l’UAL, avec leurs codes
192 CHAPITRE VI. CONSTRUCTION DU PROCESSEUR 32 BITS CRAPS

associés (figure VI.7). Ces codes sont sur 6 bits alors que 5 bits suffiraient ; cela est dû au fait
que l’on a rendus égaux les codes du champ op3 des instructions arithmétiques et logiques
(voir figure V.8 du chapitre précédent) avec les codes de l’opération correspondante de
l’UAL, ce qui introduira une simplification de conception. Par exemple la valeur 010000 du
champ op3 d’une instruction addcc est également le code de l’opération d’addition dans
l’UAL.

cmd_ual opération setN setZ setVC


010000 ADDCC, addition 1 1 1
010100 SUBCC, soustraction 1 1 1
011010 UMULCC, multiplication non-signée 0 1 0
001110 UDIVCC, division non-signée 0 1 0
010001 ANDCC, et logique 1 1 0
010010 ORCC, ou logique 1 1 0
010011 XORCC, xor logique 1 1 0
010111 XNORCC, xnor logique 1 1 0
110101 SLL, shift gauche logique 0 0 0
110110 SRL, shift droit logique 0 0 0
000000 ADD, addition 0 0 0
000100 SUB, soustraction 0 0 0
100000 SIGNEXT13, extension de signe bus A, 13 bits → 32 bits 0 0 0
100001 SIGNEXT22, extension de signe bus A, 22 bits → 32 bits 0 0 0
100010 SIGNEXT30, extension de signe bus A, 30 bits → 32 bits 0 0 0
101000 NOPB, no operation bus B 0 0 0
111000 INSBYTE0, insertion d’un octet dans un mot 0 0 0
111001 INSBYTE1, insertion d’un octet dans un mot 0 0 0
111010 INSBYTE2, insertion d’un octet dans un mot 0 0 0
111011 INSBYTE3, insertion d’un octet dans un mot 0 0 0

Figure VI.7. Table des opérations de l’UAL. Les codes des opérations arithmétiques et
logiques sont identiques aux valeurs du champ op3 dans les instructions de calcul.

La première partie du tableau concerne des opérations directement associées aux


fonctions des instructions, bien que certaines seront aussi utilisées pour d’autres tâches
internes à l’exécution. La deuxième partie ne concerne que ces opérations internes :
l’addition et la soustraction sans positionnement des flags sont utilisées pour modifier la
valeur du compteur ordinal par exemple ; l’extension de signe bus A, 13 → 32 bits, consiste
à prendre l’entrée A, à conserver les 13 bits de poids faibles et à remplacer tous les autres bits
de poids forts par des zéros ou des uns, selon que le nombre sur 13 bits était positif ou négatif
(respectivement),réalisant ainsi une extension de signe. Cette opération sera nécessaire pour
extraire la constante de 13 bits qui est incluse dans une instruction arithmétique ou logique
avec constante immédiate, telle que addcc %r1, 5, %r2. Les deux autres opérations
d’extension de signe fonctionnent de manière analogue sur 22 et 30 bits, et servent à extraire
2. L’unité arithmétique et logique 193

les valeurs de déplacement dans les instructions de branchement et dans l’instruction call.
La commande NOPB consiste à laisser passer la valeur présente sur le bus B vers la sortie,
sans changement. Enfin les commandes INSBYTE0,…,INSBYTE3, qui seront utilisées lors
des instructions stb, insèrent l’octet présent sur le bus B dans le mot présent sur le bus A,
en position 0, 1, 2 ou 3 respectivement.

2.2. Structure de l’UAL


On pourrait concevoir une UAL en associant 32 tranches d’UAL de 1 bits, comme cela
a été présenté à la section 8, mais ce serait trop inefficace. On va plutôt chercher à associer
des modules efficaces tels que l’additionneur carry-lookahead, le multiplicateur systolique,
etc. dont on a étudié la conception au chapitre 2.
On suppose donc qu’on dispose de :

• un additionneur/soustracteur 32 bits d’interface SHDL :


addsub32(a31..a0,b31..b0,op:s31..s0,N,Z,V,C)

On pourra utiliser la technique vue en section 8.6 pour mettre en commun un


additionneur 32 bits pour les deux opérations. op=0 indique qu’on fait une addition ;
op=1 une soustraction. Les signaux N, Z, V, C ont la signification habituelle.

• un multiplicateur 16 bits x 16 bits → 32 bits non signé, d’interface SHDL :

mul32(a15..a0,b15..b0:s31..s0,Z)

Seul un indicateur de nullité Z a un sens dans le contexte d’une multiplication non


signée, pour laquelle aucun débordement n’est possible.

• un diviseur 16 bits / 16 bits, avec calcul du quotient sur 16 bits et calcul du reste sur 16
bits, d’interface SHDL :

div32(a31..a0,b15..b0:q15..q0,r15..r0,Z,DIV0)

Cette fois, on a une indication supplémentaire qui détecte une division par 0.

• un décaleur à barillet 32 bits, d’interface SHDL :

shift32(a31..a0,sens,nb4..nb0:s31..s0)

L’amplitude du décalage est codée dans nb4..nb0, de 0 à 31 bits ; le sens du décalage


est codé dans sens (0=gauche, 1=droite).

• un circuit d’extension de signe, d’interface SHDL :


signext(a12..a0,b1,b0:s31..s0)
On a déjà étudié ces circuits auparavant; ils effectuent le transcodage d’un nombre
signé codé en complément à 2, d’une certaine largeur de bits à une largeur de bits
plus grande, en recopiant autant de fois que nécessaire le bit de signe sur la gauche. Le
présent module est paramétrable : si b1,b0 = 00 (respectivement 01, 10) il effectue
l’extension de 13 (respectivement 22, 30) vers 32 bits.
194 CHAPITRE VI. CONSTRUCTION DU PROCESSEUR 32 BITS CRAPS

• un circuit d’insertion d’octet, d’interface SHDL :


insbyte(a31..a0,b7..b0,n1,n0:s31..s0)
Ce circuit recopie a31..a0 vers s31..s0, sauf un groupe de 8 bits dont la position est
donnée par n1,n0 (de 0 à 3), qui est le numéro de l’octet dans le mot (0 représentant les
rangs de 31 à 24 et 3 représentant les rangs de 7 à 0), les bits copiés étant alors ceux de
b7..b0. L’écriture d’un tel module est très simple et peut être faite à titre d’exercice.

La figure VI.8 montre comment associer tous ces modules en une seule UAL.
Un décodeur (en haut à droite du schéma) produit les signaux addsub, etc. qui
indiquent le type d’opération à effectuer. Chaque module de calcul est suivi de buffers
trois-états qui sont activés par la sortie correspondante du décodeur ; il est donc garanti que
ces buffers n’entreront pas en conflit. Par ailleurs le décodeur a une entrée de sélection CS
qui est reliée au signal OE : si OE=0, aucune sortie du décodeur n’est active et donc tout le
bus S est en haute impédance. Enfin le décodeur produit les signaux setN, setZ, setVC
qui indiquent, selon le code de l’opération, si les flags correspondants ont un sens.
Les sorties des buffers trois-états sont toutes reliées à la sortie S, qui est donc la synthèse
entre tous ces signaux. Si par exemple le code de l’opération est cmd_ual=010011 (xor)
et si OE=1, alors seule la sortie xor du décodeur sera activée ; le buffer trois-états associé à
l’opération xor laissera alors passer sur la sortie S le résultat de l’opération.
Comme le module addsub exécute à la fois l’addition et la soustraction, la sortie est
contrôlée par le signal addsub qui regroupe les 4 codes associés à des opérations d’addition
et de soustraction dans la table VI.7. Le choix entre addition et soustraction sur l’entrée op
du module addsub est relié au signal cmd_ual[2] : en effet, si on examine les 4 codes
en question, le bit 2 vaut 0 pour les 2 opérations d’addition, et 1 pour les 2 opérations
de soustraction.
De façon analogue le décaleur à barillet shifter a sa sortie contrôlée par le signal shift
qui regroupe les opérations sll et srl ; c’est le signal cmd_ual[1] qui va faire la distinction
et être relié à l’entrée sens du décaleur, car entre les deux opérations de décalage, c’est le
bit 1 du code qui indique le sens. On notera que l’entrée nb (le nombre de bits à décaler) est
reliée aux 5 bits de poids faibles de l’entrée B31..B0, pour une valeur de décalage entre 0
et 31.
En ce qui concerne le flag N, il n’a de sens que pour les opérations add/sub, and, or,
xor, xnor ; il n’a pas de sens pour la multiplication et la division, qui sont non signées ici.
Les modules associés produisent ainsi les signaux N1,…,N5 respectivement, qui sont en fait
constitués du bit de poids fort du résultat. Ces signaux sont ensuite synthétisés en un seul
signal N en fonction de l’opération effectivement demandée sur cmd_ual.
Un traitement identique est réalisé pour produire le bit Z, qui a un sens pour les mêmes
opérations que N, mais aussi pour la multiplication et la division. Chaque module génère
ainsi un signal Z1,…,Z7 ; le module ZERO sert à produire ce signal pour les opérations
logiques.
Les flags V et C quant à eux ne peuvent être produits que par le module addsub, qui les
exporte donc directement.
La figure VI.9 donne l’écriture complète du module d’UAL en langage SHDL.
2. L’unité arithmétique et logique 195

Figure VI.8. Structure de l’UAL.

3. Les bus du processeur CRAPS


Il nous faut maintenant examiner comment les grands sous-systèmes de CRAPS
196 CHAPITRE VI. CONSTRUCTION DU PROCESSEUR 32 BITS CRAPS

module ual(rst,clk, A31..A0, B31..B0, cmd_ual5...cmd_ual0, OE:


S31..S0, setN,setZ,setVC, N,Z,V,C, div0)

// decodage de l’opération
addsub=/cmd_ual5*cmd_ual4*/cmd_ual3*/cmd_ual1*/cmd_ual0 +
/cmd_ual5*/cmd_ual4*/cmd_ual3*/cmd_ual1*/cmd_ual0;
mul=/cmd_ual5*cmd_ual4*cmd_ual3*/cmd_ual2*cmd_ual1*/cmd_ual0;
div=/cmd_ual5*/cmd_ual4*cmd_ual3*cmd_ual2*cmd_ual1*/cmd_ual0;
and=/cmd_ual5*cmd_ual4*/cmd_ual3*/cmd_ual2*/cmd_ual1*cmd_ual0;
or=/cmd_ual5*cmd_ual4*/cmd_ual3*/cmd_ual2*cmd_ual1*/cmd_ual0;
xor=/cmd_ual5*cmd_ual4*/cmd_ual3*/cmd_ual2*cmd_ual1*cmd_ual0;
xnor=/cmd_ual5*cmd_ual4*/cmd_ual3*cmd_ual2*cmd_ual1*cmd_ual0;
shift=cmd_ual5*cmd_ual4*/cmd_ual3*cmd_ual2*cmd_ual0;
sext=cmd_ual5*/cmd_ual4*/cmd_ual3*/cmd_ual2;

// opérations
addsub32(A31..A0,B31..B0,op:S1_31..S1_0,N1,Z1,V,C);
mul32(A15..A0,B15..B0:S2_31..S2_0,Z2);
div32(A31..A0,B15..B0:S3_31..S3_16,S3_15..S3_0,Z3,div0);
shift32(A31..A0,cmd_ual0,A4..A0:S4_31..S4_0);
S5_31..S5_0=A31..A0*B31..B0; // and
ZERO(S5_31..S5_0:Z4);
S6_31..S6_0=A31..A0+B31..B0; // or
ZERO(S6_31..S6_0:Z5);
S7_31..S7_0=A31..A0+/B31..B0+/A31..A0+B31..B0; // xor
ZERO(S7_31..S7_0:Z6);
S8_31..S8_0=A31..A0+/B31..B0+/A31..A0+B31..B0; // xnor
ZERO(S8_31..S8_0:Z7);
signext(A29..A0,B1,B0:S9_31..S9_0);

// gestion du bus trois états S31..S0


S31..S0 = S1_31..S1_0:addsub;
S31..S0 = S2_31..S2_0:mul;
S31..S0 = S3_31..S3_0:div;
S31..S0 = S4_31..S4_0:shift;
S31..S0 = S5_31..S5_0:and;
S31..S0 = S6_31..S6_0:or;
S31..S0 = S7_31..S7_0:xor;
S31..S0 = S8_31..S8_0:xnor;
S31..S0 = S9_31..S9_0:sext;

// gestion des flags


setN=addsub+and+or+xor+xnor;
setZ=addsub+mul+div+and+or+xor+xnor;
setVC=addsub;
N=addsub*N1+and*N2+or*N3+xor*N4+xnor*N5;
Z=addsub*Z1+mul*Z2+div*Z3+and*Z4+or*Z5+xor*Z6+xnor*Z7;
end module

Figure VI.9. Écriture SHDL de l’UAL du processeur CRAPS.

communiquent entre eux. CRAPS utilise trois bus de 32 bits (figure VI.10) :

• Le bus A, monodirectionnel : seul le bloc des registres peut y déposer une valeur.
Il est relié à une des entrées de l’UAL, mais une dérivation part également vers le
sous-système mémoire, où il servira de bus d’adresses. Il porte bien son nom : bus A
comme bus d’adresses.
3. Les bus du processeur CRAPS 197

clk
registres 32
32

32 Bus A Bus B

Bus D

vers sous−système
d’exceptions cmd_ual[5..0]
(OE = excep_mode) UAL 6

OE = /read * /excep_mode

Bus D Bus A
bus de données bus d’adresses

vers mémoire centrale


(OE = read * /excep_mode)

Figure VI.10. Les bus du processeur CRAPS.

• Le bus B, monodirectionnel. Il sert uniquement à fournir un second argument


pour l’UAL.

• Le bus D, bi-directionnel. C’est sur lui que circulent toutes les données : bus D pour bus
de données. Sa valeur est lue par le bloc des registres pour stockage dans un registre au
front d’horloge, et/ou par le sous-système mémoire pour écriture en mémoire.

Le bus D est celui dont la gestion est la plus délicate. En effet, trois sources différentes
peuvent y déposer une valeur :

1 l’UAL, pour communiquer le résultat de ses calculs. Son écriture sur le bus est
commandée par : OE=/read*/excep_mode.

2 le sous-système mémoire, qui y déposera la valeur des données lues en mémoire. Ce


dépôt sera commandé par OE=read*/excep_mode.

3 le sous-système des exceptions, qui pourra y déposer le numéro de l’exception dans


certaines circonstances. Ce dépôt est commandé par OE=excep_mode.

Même si nous ne connaissons pas encore le rôle exact des signaux excep_mode et read,
il est clair que les trois équations des signaux OE de dépôt de valeurs sur le bus D sont
mutuellement exclusives ; aucun court-circuit n’est donc possible.
198 CHAPITRE VI. CONSTRUCTION DU PROCESSEUR 32 BITS CRAPS

4. Structure du sous-système mémoire


Le sous-système mémoire ne fait pas partie du processeur à proprement parler. Sur
nos ordinateurs de bureau, il est extérieur au processeur, placé sur la carte-mère, sous
forme de barrettes mémoire et de circuits de décodages. Mais c’est un élément central
dont l’importance est telle que nous allons étudier en détail celui qui est associé à la
cartographie mémoire que nous avons vue au chapitre consacré à la programmation de
CRAPS. On expliquera notamment les phénomènes suivants, visibles du point de vue du
programmeur :

• les adresses sont des numéros d’octets (alors qu’on manipule des mots de 32 bits, de 4
octets de large),

• la ROM commence à l’adresse 0x0, la RAM à l’adresse 0x1000000, etc.

• certaines adresses semblent des ’miroirs’ d’autres : par exemple 0x400000 et


0x400010,

• l’accès à des adresses non implantées provoque l’exception bus_error.

La cartographie mémoire exacte qu’on souhaite mettre en oeuvre est donnée figure VI.11.
On y retrouve les adresses vues dans les exemples de programmation : RAM en 0x1000000,
timer en 0x400000, entrées/sorties en 0x600000.

[0000 0000 − 0000 FFFF] ROM (64K) commande #1 0040 0000


commande #2 0040 0004
IT timer 0040 0008
[0040 0000 − 0040 000B] timer

[0060 0000 − 0060 000F] entrées / sorties


données 0060 0000
direction 0060 0004
ITs memorisees 0060 0008
sens front ITs 0060 000C
[0100 0000 − 01FF FFFF] RAM (16 Mo)

Figure VI.11. Cartographie mémoire de CRAPS.

La structure du sous-système mémoire qui met en oeuvre cette cartographie est donnée
figure VI.12.

Décodage des adresses


Le décodeur situé sur la gauche du schéma prend en entrée les 16 bits de poids
faibles du bus d’adresse et produit les signaux de décodage ROM, RAM, etc. Chacun de ces
signaux correspond au décodage de la plage mémoire associée au boîtier correspondant.
Précisément :
4. Structure du sous-système mémoire 199

Figure VI.12. Structure du sous-système mémoire.

1 la ROM de 64Ko occupe le segment [0x00000000,0x00010000[. Le signal ROM qui


indique son décodage vaut 1 lorsque les 16 bits de poids forts de l’adresse sont tous à
0:
ROM = /abus31*/abus30*...*/abus16

2 la RAM de 16Mo occupe le segment [0x01000000,0x02000000[. Le signal RAM qui


décode ces adresses vaut 1 lorsque les 8 bits de poids forts de l’adresse sont égaux
à 000000012 :

RAM = /abus31*/abus30*...*/abus25*abus24
200 CHAPITRE VI. CONSTRUCTION DU PROCESSEUR 32 BITS CRAPS

3 le timer occupe le segment [0x00400000,0x0040000C[. Par souci de simplification,


on ne va effectuer qu’un décodage partiel de ces adresses en ne détectant que le fait
que les 12 bits de poids forts valent 0x004. Cela conduit à l’équation du signal timer
suivante :
timer = /abus31*/abus30*...*/abus23*abus22*/abus21*/abus20

4 le circuit d’entrées/sorties occupe le segment [0x00600000,0x00600010[. De façon


analogue au timer, on ne détectera que le fait que les 16 bits de poids forts sont égaux
à 0x0060. Cela conduit à l’équation du signal io suivante :

io = /abus31*/abus30*...*/abus23*abus22*abus21*/abus20

5 toutes les autres valeurs des 12 bits de poids forts activeront la ligne bus_error, qui
indiquera donc que l’adresse présente sur le bus ne correspond à aucun circuit implanté
dans l’espace d’adressage. Ce signal devra déclencher une exception de niveau 14.

Lecture en mémoire
Les sorties du décodeur sont reliées aux boîtiers qui leurs correspondent, expliquant
ainsi pourquoi chacun d’eux est activé par les adresses indiquées sur la cartographie
mémoire figure VI.11. On notera que l’entrée de sélection CS du décodeur est reliée au OU
des signaux read et write, c’est à dire qu’aucun des quatre circuits ne sera activé si aucune
lecture ni écriture n’est demandée par le processeur.
Si une lecture est demandée (read = 1) et que le processeur n’est pas en train de gérer
une exception, alors :

1 OE=1 (en bas à gauche du schéma). On avait déjà étudié l’équation de OE :


OE = /excep_mode*read, qui indique qu’un des circuits mémoire va déposer une
valeur sur le bus.
2 CS=1 et OE=1 pour le boîtier mémoire activé par l’adresse présente, en vertu du ET
situé devant l’entrée OE de chaque circuit.

Ainsi, le circuit mémoire sélectionné par l’adresse va déposer une valeur sur le bus D.

Écriture en mémoire
Si une lecture est demandée (write = 1) alors on voit que, pour le circuit activé par
l’adresse présente, CS = 1, OE = 0 et W = 1 : le circuit va déclencher une écriture à l’adresse
présente sur ses entrées ADR.

Oubliés, les deux bits de poids faibles de l’adresse !


On va étudier enfin la façon dont les lignes d’adresses arrivent sur les circuits mémoire.
On constate d’abord que, nulle part sur le schéma, n’apparaissent les 2 bits de poids faibles
de l’adresse, abus1..abus0 ! Par exemple, pour le circuit de ROM, il y a bien 14 lignes
d’adresses puisqu’il contient 16K mots, mais ce ne sont pas les bits abus[13..0] qui
sont utilisés, mais abus[15..2]. Si par exemple une lecture à l’adresse 0x00000008
4. Structure du sous-système mémoire 201

est effectuée, la ROM verra quant à elle l’adresse 0x0002. En fait, toute adresse entre
0x00000008 et 0x0000000B sera vue de la même façon par le circuit de ROM : accès au
mot mémoire numéro 2. Mais il faut se souvenir qu’il est interdit de faire des lectures et
des écritures en mémoire à des adresses qui ne sont pas des multiples de 4, sous peine de
déclencher l’exception ’erreur d’alignement’ (numéro de type = 10, voir figure V.35). Par
contre, c’est autorisé dans le cas des instructions ldub et stb dont on verra plus loin
comment elles sont gérées.
De la même façon, l’adresse qui est envoyée à la RAM est obtenue par la formule :

adr_ram[21..0] = (abus[31..0] − 0x01000000)/ 4

L’adresse avec laquelle la RAM est adressée s’obtient d’abord par une translation par
rapport à l’adresse de base 0x01000000, et cette valeur est divisée par 4. Par exemple, un
programmeur qui effectue une lecture ou une écriture à l’adresse 0x01000010 va en réalité
adresser la RAM au mot mémoire (de 32 bits) d’indice 5 (0x01000010 - 0x01000000) /
4). La RAM n’a jamais entendu parler de l’adresse 0x01000000 et ne pensait sans doute
pas qu’il existait des adresses aussi grandes (!) : elle ne connaît que des adresses de 0 à
0xFFFFFF. De même la RAM croit que tous les mots mémoire ont 32 bits de large ; elle
serait étonnée d’apprendre que les adresses manipulées par les programmeurs sont des
numéros d’octets.
C’est encore plus frappant pour les circuits timer et d’entrées/sorties, qui ne possèdent
que 2 lignes d’adresse. De leur point de vue il n’existe que 4 mots mémoire, d’indices 0 à
3. C’est le système de décodage qui fait apparaître ces mots aux adresses 0x00400000 à
0x0040000F pour le timer, et 0x00600000 à 0x0060000F pour les entrées/sorties.
Finalement, l’écriture SHDL de l’ensemble est donnée figure VI.14. On y fait référence
à des modules mémoire ROM et RAM prédéfinis.

5. Structure du circuit d’entrées/sorties


On va détailler dans cette partie la structure du circuit d’entrées/sorties et on va montrer
comment il peut avoir le mode de fonctionnement qu’on a déjà décrit dans le chapitre
consacré à la programmation. Ils sont résumés dans le tableau VI.13. On rappelle que le
circuit a l’interface de la figure VI.15.

ADR W opération
00 0 lire l’état des entrées
00 1 écrire sur les sorties
01 0 lire configuration lignes
01 1 configurer les lignes

Figure VI.13. Spécification de fonctionnement du circuit d’entrées/sorties.

Il peut être considéré comme la juxtaposition de 32 circuits de 1 bits ; la structure pour


un bit i est montrée en figure VI.16.
202 CHAPITRE VI. CONSTRUCTION DU PROCESSEUR 32 BITS CRAPS

module memoire(rst,clk,abus[31..0],dbus[31..0],read,write,excep_mode,
timer_out, io[31..0], bus_error)
decode_mem(abus[31..16],cs_mem:cs_rom,cs_timer,cs_io,cs_ram,bus_error);
cs_mem=read+write;
rom16Kx32(abus[15..2],cs_rom,oe_rom,dbus[31..0]);
oe_rom=cs_rom*OE;
ram4Mx32(abus[23..2],cs_ram,oe_ram,write,dbus[31..0]);
oe_ram=cs_ram*OE;
timer_pwm(abus[3..2],cs_timer,oe_timer,write,dbus[31..0],timer_out);
oe_timer=cs_timer*OE;
io(abus[3..2],cs_io,oe_io,write,dbus[31..0],io[31..0]);
oe_io=cs_io*OE;
OE=/excep_mode*read;
end module

module decode_mem(adr[15..0],CS:ROM,timer,io,RAM,bus_error)
commun=/adr15*/adr14*/adr13*/adr12*/adr11*/adr10*/adr9;
ROM=CS*commun*/adr8*/adr7*/adr6*/adr5*/adr4*/adr3*/adr2*/adr1*/adr0;
RAM=CS*commun*adr8;
timer=CS*commun*/adr8*/adr7*adr6*/adr5*/adr4;
io=CS*commun*/adr8*/adr7*adr6*adr5*/adr4;
bus_error=CS*/ROM*/RAM*/timer*/io;
end module

Figure VI.14. Écriture SHDL du sous-système mémoire.

Figure VI.15. Interface du circuit d’entrées/sorties.

La bascule D du haut mémorise le bit dir[i] qui vaut 1 lorsqu’on souhaite que la
ligne io[i] soit configurée en sortie et 0 lorsqu’on souhaite qu’elle soit une entrée. La
bascule ayant comme entrée de sélection le signal CS*W*/ADR[1]*ADR[0], mémorisera le
bit data[i] si et seulement si l’écriture est demandée pour ADR = 01. Du point de vue du
programmeur, cela correspond à une écriture à l’adresse 0x600004 comme on l’a expliqué
à la section 4.
De la même façon, la bascule D du bas mémorise le bit DATA[i], lorsqu’une écriture
est demandée pour ADR = 00 ; du point de vue du programmeur cela correspond à une
écriture à l’adresse 0x600000.
Maintenant, dir[i] gouverne bien le fait que la ligne io[i] est une entrée ou une
sortie : si dir[i] = 1, le buffer trois-états de droite fait de io[i] une sortie, dont la
valeur est justement le signal out[i], ce qui est le comportement spécifié tableau VI.13.
Si dir[i] = 0, le buffer trois-états de droite isole io[i] de out[i] ; si maintenant une
lecture est demandée pour ADR = 00, la condition CS*/W*OE*/ADR[1]*/ADR[0] vaut 1
5. Structure du circuit d’entrées/sorties 203

Figure VI.16. Structure du circuit d’entrées/sorties,pour un bit i.

et io[i] passe sur la ligne DATA[i]. Du point de vue du programmeur, cela équivaut à une
lecture à l’adresse 0x600000 et elle lui retourne l’état des 32 lignes d’entrées/sorties. Enfin,
si une lecture est demandée pour ADR[1..0] = 01, le buffer trois-états du bas laisse passer
sur DATA[i] la valeur dir[i] : du point de vue du programmeur, une lecture à l’adresse
0x600004 lui renvoie l’état de configuration des entrées/sorties.
On a ainsi expliqué tous les aspects du fonctionnement du circuit d’entrées/sorties du
tableau VI.13. Les autres aspects relatifs aux exceptions seront décrits section 7.
Finalement, l’écriture SHDL de l’ensemble est donnée figure VI.17.

6. Structure du circuit timer/PWM


Le circuit timer/PWM est un bel exemple de construction modulaire, par assemblage
du module timer/PWM étudié à la section 9 et du module diviseur de fréquence étudié
section 10.2. On l’a déjà utilisé du point de vue du programmeur ; sa spécification est
redonnée tableau VI.18 et on rappelle son interface figure VI.19.
Sur la figure VI.20, les registres de 32 bits situés à gauche fonctionnent de la manière
habituelle : celui du haut mémorise le mot de commande #1 lorsqu’une écriture pour
ADR = 00 est demandée, ce qui correspond à une écriture en 0x400000 du point de vue
du programmeur. Le registre 16 bits en dessous et la bascule D tout en bas mémorisent
prediv et start respectivement, lorsqu’une écriture pour ADR = 01 est demandée, soit
204 CHAPITRE VI. CONSTRUCTION DU PROCESSEUR 32 BITS CRAPS

module io(rst,clk,ADR[1..0],CS,OE,W,DATA[31..0],io[31..0])
ENA_IO = CS*W*/ADR1*/ADR0;
ENA_DIR = CS*W*/ADR1*ADR0;
// écriture dans dir[i]
dir[31..0] := DATA[31..0];
dir[31..0].ena = ENA_DIR
dir[31..0].clk = clk;
dir[31..0].rst = rst;
// écriture dans out[i]
out[31..0] := DATA[31..0];
out[31..0].ena = ENA_IO;
out[31..0].clk = clk;
out[31..0].rst = rst;
// sortie sur io[i]
io[31..0] = out[31..0]:dir[31..0];
// lecture de io[i]
OE_IO = CS*/W*OE*/ADR1*/ADR0;
DATA[31..0] = io[31..0]:OE_IO;
// lecture de dir[i]
OE_DIR = CS*/W*OE*/ADR1*ADR0;
DATA[31..0] = dir[31..0]:OE_DIR;
end module

Figure VI.17. Écriture SHDL du circuit d’entrées/sorties.

ADR W opération
00 0 lire le registre de commande #1 (P + N)
00 1 écrire le registre de commande #1 (P + N)
01 0 lire le registre de commande #2 (prediv + start)
01 1 écrire le registre de commande #2 (prediv + start)

Figure VI.18. Spécifications du circuit timer/PWM.

Figure VI.19. Interface du circuit timer/PWM.

une écriture en 0x400004 pour le programmeur.


La valeur de prediv est mise en entrée d’un circuit diviseur de fréquence, qui divise la
fréquence de l’horloge générale clk. La sortie du diviseur de fréquence devient l’horloge de
comptage d’un module timer identique à celui étudié section 9. On lui transmet également
les parties N et P du mot de commande #1 ; par ailleurs son entrée de sélection est reliée au
signal start et il ne fonctionne donc que si ce bit est à 1.
6. Structure du circuit timer/PWM 205

Figure VI.20. Structure du circuit timer/PWM.

Enfin, une lecture demandée pour ADR = 00 active le premier buffer trois-états du
bas et laisse passer la valeur de la commande #1 sur DATA (lecture en 0x400000 pour le
programmeur). Une lecture pour ADR = 01 (0x400004 pour le programmeur) donne la
valeur de la commande #2.
Ainsi, tous les aspects du fonctionnement du circuit timer/PWM spécifiés tableau VI.18
sont expliqués par cette construction.
Finalement, l’écriture SHDL de l’ensemble est donnée figure VI.21.

7. Structure du sous-système d’exceptions


On va décrire dans cette section la structure complète du sous-ensemble effectuant la
gestion des exceptions. On a déjà décrit en détails (section 10) dans quelles cironstances une
exception (trap ou interruption) était déclenchée, puis prise en compte. Plusieurs aspects de
cette prise en compte sont effectués par le matériel :

1 la détermination de l’exception qui a le niveau de priorité le plus élevé.

2 la mémorisation des interruptions timer et entrées/sorties, le temps qu’elles soient


prises en compte.

3 la possibilité de remettre à zéro par programme les interruptions timer et entrées/sorties


mémorisées.
4 la possibilité de lire par programme les interruptions mémorisées.

La figure VI.22 montre le schéma complet qui prend en compte tous ces aspects.
206 CHAPITRE VI. CONSTRUCTION DU PROCESSEUR 32 BITS CRAPS

module timer_pwm(rst,clk,ADR[1..0],CS,OE,W,DATA[31..0],out)
ENA_CMD1 = CS*W*/ADR1*/ADR0;
ENA_CMD2 = CS*W*/ADR1*ADR0;
// écriture dans P[i]
P[15..0] := DATA[31..16];
P[15..0].ena = ENA_CMD1;
P[15..0].clk = clk;
P[15..0].rst = rst;
// écriture dans N[i]
N[15..0] := DATA[15..0];
N[15..0].ena = ENA_CMD1;
N[15..0].clk = clk;
N[15..0].rst = rst;
// écriture dans prediv[i]
prediv[3..0] := DATA[19..16];
prediv[3..0].ena = ENA_CMD2;
prediv[3..0].clk = clk;
prediv[3..0].rst = rst;
// écriture dans start
start := DATA[0];
start.ena = ENA_CMD2;
start.clk = clk;
start.rst = rst;
//diviseur de fréquence
divfreq(rst,clk,prediv[3..0]: h);
// timer/PWM
pwm(rst,h,start,P[15..0],N[15..0]: out);
// lecture du mot de commande #1
OE_CMD1 = CS*/W*OE*/ADR1*/ADR0;
DATA[31..16] = P[15..0]:OE_CMD1;
DATA[15..0] = N[15..0]:OE_CMD1;
// lecture du mot de commande #2
OE_CMD2 = CS*/W*OE*/ADR1*ADR0;
DATA[19..16] = prediv[3..0]:OE_CMD2;
DATA[0] = start:OE_CMD2;
end module

Figure VI.21. Écriture SHDL du circuit timer/PWM.

Détermination de l’exception la plus prioritaire


Cette détermination est faite dans la partie droite du schéma de la figure VI.22. On
y trouve le circuit encodeur de priorité, qui reçoit les signaux de détection des différentes
exceptions. On retrouve les numéros de priorité de chacune d’elles : 14 pour bus_error,
13 pour instr_priv (viol de privilège instruction), etc. L’occurrence d’une interruption
timer est signalée par timer_int et a la priorité 8 ; les interruptions d’entrées/sorties sont
signalées par le vecteur io_int[31..0] ; elles sont toutes reliées à un OR pour en faire
la synthèse au niveau 7.
La sortie de l’encodeur de priorité donne le numéro de l’exception la plus prioritaire
et cette valeur est ensuite comparée au niveau courant d’exécution PIL, valeur issue d’un
champ du registre d’état %psr. Une demande de prise en compte d’exception excep_req
n’est faite que si le numéro de l’exception la plus prioritaire est plus élevé que le numéro
d’exécution courant.
7. Structure du sous-système d’exceptions 207

Figure VI.22. Structure du sous-système d’exceptions.

Masquage des interruptions


On se souvient (section 10) que les interruptions sont ignorées lorsque le bit ET
du registre %psr est à 0. C’est bien le cas sur le schéma : les deux sources d’interruption
timer_out (en provenance du circuit timer) et io[31..0] (en provenance du circuit
d’entrées/sorties) passent d’abord par un AND avec le signal ET ; si ET = 0 l’occurrence
de l’interruption est purement et simplement ignorée.

Mémorisation des interruptions


Les interruptions timer et entrées/sorties ont besoin d’être mémorisées, car ce sont des
occurrences fugitives de fronts montants ou descendants. Des latchs RS asynchrones sont
alors le moyen le plus adapté : le vecteur de 32 latchs du haut mémorise les interruptions sur
les entrées/sorties et le latch du bas mémorise une interruption timer.
Le signal timer_out, après passage au travers du AND avec ET, va sur l’entrée S
du latch : l’arrivée d’un front montant sur timer_out va se traduire par la mémorisation
208 CHAPITRE VI. CONSTRUCTION DU PROCESSEUR 32 BITS CRAPS

d’un 1 dans le latch. De la même façon, chaque signal io[i] passe d’abord au travers du
AND, puis au travers d’un XOR qui va éventuellement l’inverser, selon la valeur du signal
dir_ioint[i] qui permet de régler le sens du front (montant ou descendant) qui va
déclencher l’interruption.

Commandes de gestion des interruptions mémorisées


Le tableau de la figure VI.23 résume toutes les opérations qui doivent être rendues
possibles par programme pour gérer les interruptions timer et entrées/sorties.Chacune d’elle
est mise en place par un élément du schéma général.

adresse read write opération


0x600008 1 0 lire interruptions entrées/sorties mémorisées
0x600008 0 1 raz des interruptions entrées/sorties mémorisées
0x60000C 1 0 lire sens du front d’interruption (0 = front montant)
0x60000C 0 1 écrire sens du front d’interruption
0x400008 1 0 lire bit d’interruption timer
0x400008 0 1 raz bit d’interruption timer

Figure VI.23. Commandes de gestion des interruptions timer et entrées/sorties.

La lecture des interruptions d’entrées/sorties mémorisées est faite par le buffer


trois-états du bas du schéma qui a pour entrée io_int[31..0]. Cette lecture a lieu lorsque
cs_io*read*/excep_mode*abus[3]*/abus[2], c’est à dire pour une adresse qui
active cs_io (le segment [0x600000,0x600010[) et qui se termine par 10XX : 0x600008.
La remise à zéro des interruptions d’entrées/sorties mémorisées est réalisée par le
registre dont les sorties se nomment reset_ioint[31..0] ; elles vont en effet sur les
entrées R des latchs de mémorisation des interruptions d’entrées/sorties. Cette écriture a
lieu lorsque cs_io*write*/excep_mode*abus[3]*/abus[2], c’est à dire à la même
adresse que la lecture précédente : 0x600008.
Les sens des fronts des interruptions d’entrées/sorties sont configurables
individuellement par écriture dans le registre dont les sorties se nomment
dir_ioint[31..0] ; elles commandent l’inversion éventuelle des signaux
en provenance du circuit d’entrées/sorties io[31..0]. Cette écriture a lieu pour
cs_io*write*/excep_mode*abus[3]*abus[2], c’est à dire 0x60000C.
Le bit d’interruption timer timer_int arrive sur un des buffers trois-états du bas ; il est
lu pour cs_timer*write*/excep_mode*abus[3]*/abus[2], c’est à dire 0x400008.
La remise à zéro du latch de mémorisation de cette interruption se fait par écriture dans la
bascule dont la sortie se nomme reset_tint, à l’adresse 0x400008.
Enfin, la valeur du niveau d’exception le plus élevé est placée automatiquement sur le
bus dès que excep_mode = 1 ; on se souvient (figure VI.10) qu’il s’agit de la troisième
source d’écriture sur le bus de données dbus, qui se produira lorsqu’une exception sera
effectivement prise en compte et qu’il s’agira d’obtenir son niveau pour élever le niveau
d’exécution du processeur.
8. Séquencement des instructions 209

8. Séquencement des instructions

8.1. Problématique du séquencement


Nous avons jusqu’ici décrit ce qui s’appelle la micromachine, c’est à dire la réunion
des différents sous-systèmes registres, mémoire, UAL et exceptions. Cette micromachine
se manoeuvre avec des microcommandes, dont on peut donner la liste complète pour la
machine CRAPS :
• read et write pour l’accès à la mémoire.

• areg, breg et dreg pour l’accès aux registres.

• cmd_ual pour la commande de l’opération UAL.

• excep_mode pour obtenir le niveau de priorité de l’exception en cours de traitement.

C’est tout ! Tous les autres signaux, abus, bbus, dbus, etc. ne sont pas des commandes ;
ce sont des éléments de la micromachine, qui sont indirectement modifiés par les
microcommandes listées ci-dessus.
On appelle séquenceur le sous-système du processeur chargé de générer les bonnes
séquences de microcommandes en fonction de l’instruction en cours d’exécution. Il
fonctionne avec le reste du processeur selon l’organisation de la figure VI.24.

microcommandes

séquenceur %ir micromachine

N,Z,V,C

excep_req

CLK

Figure VI.24. Le séquenceur envoie des microcommandes en fonction de l’instruction en


cours, de la valeur des flags et de l’éventuelle présence d’une interruption.

Le séquenceur est l’âme du processeur : sans lui la micromachine reste inerte. À chaque
cycle d’horloge, le séquenceur présente une nouvelle combinaison de microcommandes,
afin de poursuivre l’exécution de l’instruction en cours. On peut le comparer à un pianiste et
les microcommandes à des touches de piano : à chaque tempo il effectue un accord, c’est à
dire qu’il actionne plusieurs microcommandes en parallèle. Il effectue le séquencement des
instructions l’une après l’autre, le registre %pc lui servant à connaître l’emplacement de la
prochaine instruction à séquencer.
210 CHAPITRE VI. CONSTRUCTION DU PROCESSEUR 32 BITS CRAPS

Le début du séquencement d’une instruction s’appelle la phase FETCH ; il consiste à


aller charger dans le registre %ir l’instruction en mémoire centrale située à l’adresse %pc.
Cette partie initiale du séquencement est fixe, car le séquenceur ne sait pas encore à quelle
instruction il va avoir à faire. À l’issue de cette phase, l’instruction est disponible dans %ir
et le séquenceur y a accès. Il peut ainsi catégoriser l’instruction parmi des groupes dont
le séquencement va être commun. Pour les instructions de branchement conditionnel, le
séquencement correct nécessite qu’il dispose de la valeur des flags.
Il ne faut pas oublier les exceptions : lorsque l’une d’elle se produit (excep_req = 1),
elle affecte le séquencement d’une manière qui sera décrite plus loin.

8.2. Séquenceurs câblés et microprogrammés


Le séquenceur est un circuit séquentiel synchrone parfaitement classique et on pourra
le réaliser en utilisant les méthodes présentées au chapitre 3, une fois qu’on aura spécifié
exactement son graphe d’états. On utilisera alors des portes et des bascules pour réaliser la
machine de changements d’états dont les entrées seront les signaux %ir, excep_req et les
flags ; un transcodeur générera les microcommandes qui seront les sorties du circuit.
On parle dans ce cas de séquenceur câblé ; c’est bien sûr la méthode qui assure le
séquencement le plus rapide et qui occupe le moins de surface sur la puce.
Pour les processeurs complexes et en particulier pour les processeurs CISC, le graphe
peut devenir tellement complexe qu’il est préférable d’utiliser une autre approche dite de
séquenceur microprogrammé dont l’étude détaillée sort du cadre de cet ouvrage. On peut
tout de même dire qu’alors, le séquenceur est lui même un petit processeur qui exécute
des sortes de routines associées à chaque instruction, contenues dans une mémoire interne
appelée mémoire de microprogramme.Cette approche est plus souple que l’approche câblée
car on peut facilement modifier le séquenceur en modifiant les microprogrammes, mais elle
ajoute bien sûr un surcoût en temps et en espace.
Dans le cas de CRAPS, le graphe d’états est suffisamment simple pour permettre sans
difficulté un séquenceur câblé ; cela est essentiellement dû au fait que son jeu d’instructions
de type RISC est particulièrement réduit et régulier.
Le graphe d’états complet est trop grand pour être présenté d’un seul morceau et il sera
donné par parties, d’abord pour la phase FETCH commune à toutes les instructions, puis
pour chacun des groupes d’instructions. Il s’agit d’un graphe de MEALY, qui est plus adapté
qu’un graphe de MOORE car, en fournissant au plus tôt les valeurs des sorties, les parties
asynchrones comme l’UAL et la mémoire peuvent commencer leur travail plus tôt.

8.3. Séquencement de la phase FETCH


La phase FETCH forme le début commun au séquencement de toutes les instructions.
Elle consiste à lire en mémoire à l’adresse contenue dans %pc, puis à stocker la valeur lue
dans le registre %ir (figure VI.25).
L’état de départ s’appelle fetch ; c’est également l’état vers lequel on retourne
à la fin de l’exécution de toute instruction. On va de fetch vers decode seulement si
excep_req=0, c’est à dire si aucune exception n’est en attente de traitement. Sinon il faut
aller vers le sous-graphe de gestion des exceptions qui sera décrit plus loin.
Si donc le processeur n’a pas d’exception en attente, il doit aller lire le mot mémoire
8. Séquencement des instructions 211

excep_req = 0 / vers sous−graphe


read=1, write=0, de séquencement de SETHI et
ual=000000 (ADD), excep_mode=0 des instructions de branchement
areg=62, breg=0, creg=63 op = 00
vers sous−graphe
op = 01 de séquencement de CALL
fetch decode
op = 10 vers sous−graphe
de séquencement des
except_req = 1 instructions de calcul
op = 11
vers sous−graphe
de séquencement des exceptions vers sous−graphe
de séquencement des
instructions d’accès memoire

Figure VI.25. Sous-graphe de séquencement de la phase FETCH.

situé à l’adresse contenue dans le registre %pc (= %r62). Pour cela, il faut se débrouiller
pour placer la valeur de %pc sur le bus A : c’est ce qui est fait en plaçant 62 sur areg.
On place également une valeur quelconque sur breg et on commande une opération
quelconque sur l’UAL, qui toutefois ne modifie pas les flags (ici ADD). read est également
forcé à 1, ce qui déclenche immédiatement (avant le prochain front d’horloge) la lecture
en mémoire à l’adresse pc. À l’arrivée du prochain front d’horloge, si la période a été
assez longue, la mémoire aura placé son résultat sur le bus D, et il sera écrit dans le registre
%ir (= %r63), puisqu’on a placé 63 sur la commande dreg.
Si la mémoire est un peu lente par rapport à la fréquence de l’horloge, on peut rajouter
un état d’attente intermédiaire entre les états fetch et decode. C’est technique pourra être
utilisée également avec l’UAL lors des calculs de multiplication et de division qui prennent
un temps important.
Arrivé à l’état decode, le séquencement va être orienté vers des sous-graphes
différents selon le groupe auquel appartient l’instruction courante, maintenant contenue
dans %ir. Si on se réfère à la structure des instructions vue à la section 2.4, ce groupe est très
simplement identifié par le champ op constitué des deux bits de poids forts de %ir.

8.4. Séquencement d’une instruction de calcul


Les instructions dont le séquencement est le plus simple sont les instructions de calcul
arithmétique et logique. Elles sont repérée à partir de l’état decode par le fait que le champ
op de %ir vaut 102. (figure VI.26).
Il y a néanmoins deux cas, selon que le deuxième argument de l’instruction est une
valeur immédiate (comme dans addcc %r1, 5, %r2) ou un registre (comme dans addcc
%r1, %r2, %r3). Ces deux cas sont distingués par le bit 13 de %psr, comme on le voit sur
le graphe.

Instruction de calcul avec une constante comme deuxième argument


Si %ir[13]=1, le deuxième argument est une constante immédiate sur 13 bits, qui
est logée à l’intérieur même de l’instruction %ir. On va alors l’extraire de %ir et la copier
dans le registre temporaire %tmp1, puis faire ensuite l’opération entre le registre de numéro
rs1 et tmp1, pour enfin mettre le résultat dans le registre de numéro rd. rs1 et rd sont
212 CHAPITRE VI. CONSTRUCTION DU PROCESSEUR 32 BITS CRAPS

op = 10, %ir[13]=1 /
read=0, write=0, excep_mode=0
cmd_ual=SIGNEXT13
... areg=63, breg=0, dreg=32

fetch decode calc_imm

null / op = 10, %ir[13]=0 /


read=0, write=0, oe_num=01 read=0, write=0, oe_num=01
cmd_ual=ADD cmd_ual=op3
areg=62, breg=35, dreg=62 areg=rs1, breg=rs2, dreg=rd null /
pc+4 read=0, write=0, oe_num=01
cmd_ual=op3
areg=rs1, breg=32, dreg=rd

Figure VI.26. Sous-graphe de séquencement d’une instruction de calcul.

des champs de 5 bits du registre %ir qui contiennent les numéros des registres traités par
l’instruction (voir figure 2.4).
L’extraction de la constante de 13 bits est effectuée entre l’état decode et l’état
calc_imm, par envoi du registre %ir sur le bus A (areg = 63) et par application de
la commande cmd_ual = SIGNEXT13 sur l’UAL. On a déjà vu (section 2.2) que cette
opération avait pour effet de ne garder que les 13 bits de poids faibles de la valeur (ici %ir)
et d’effectuer une extension du signe. La constante ainsi ’nettoyée’, est copiée dans %tmp1
(dreg = 32) au prochain front d’horloge.
Avant de revenir à l’état fetch pour exécuter l’instruction suivante, il faut incrémenter
le registre %pc de 4, pour qu’il pointe sur l’instruction suivante. C’est ce qui est fait à partir
de l’état pc+4, qui effectue une addition sans modification des flags (cmd_ual = ADD)
entre la valeur de %pc (areg = 62) et la valeur du registre %r35 (breg = 35) dont on
se rappelle qu’il vaut toujours 4 (voir section 1). Le résultat est stocké dans %pc (dreg =
32) avant de retourner à l’état fetch, prêt pour l’exécution de l’instruction suivante qui se
trouve en séquence.

Instruction de calcul avec un registre comme deuxième argument


Lorsque le deuxième argument de l’instruction de calcul est un registre (%ir[13] =
0), il n’est pas nécessaire d’avoir cet état intermédiaire calc_imm ; on peut directement
demander l’opération entre les registres, ce qui est fait dans la transition entre decode et
pc+4 : cmd_ual = op3, areg = rs1, breg = rs2, dreg = rd. On incrémente
ensuite %pc de 4 pour passer à l’instruction suivante.

8.5. Séquencement des instructions d’accès mémoire


Les instructions d’accès mémoire ont des arguments similaires à ceux des instructions
de calcul ; ils partagent d’ailleurs le même format d’instruction (format 3, figure V.8).

Séquencement des instructions ld et ldub


La figure VI.27 montre le sous-graphe de séquencement de ces deux instructions. Le
séquencement de ld est simple; le séquencement de ldub l’est moins et occupe l’essentiel
du bas de la figure.
Le début du séquencement est le même que pour une instruction de calcul. L’adresse
8. Séquencement des instructions 213

op = 11, op3 = ld+ldub, %ir[13] = 1 /


read=0, write=0, excep_mode=0
excep_req = 0 / cmd_ual=SIGNEXT13
... areg=63, breg=0, dreg=32

fetch decode ld_imm

null / op = 11, op3 = ld+ldub, %ir[13] = 0 / null /


read=0, write=0, excep_mode=0 read=0, write=0, excep_mode=0 read=0, write=0, excep_mode=0
cmd_ual=ADD cmd_ual=ADD cmd_ual=ADD
areg=62, breg=35, dreg=62 areg=rs1, breg=rs2, dreg=32 areg=rs1, breg=32, dreg=32

op3 = ld /
read=1, write=0, excep_mode=0 read
cmd_ual=ADD
pc+4 areg=32, breg=0, dreg=rd

op3 = ldub, op3 = ldub, op3 = ldub, op3 = ldub,


null / %tmp1[1,0]=00 / %tmp1[1,0]=01 / %tmp1[1,0]=10 / %tmp1[1,0]=11 /
read=0, write=0 read=1, write=0 read=1, write=0 read=1, write=0 read=1, write=0
excep_mode = 0, excep_mode = 0, excep_mode = 0, excep_mode = 0, excep_mode = 0,
cmd_ual=SRL cmd_ual=ADD cmd_ual=ADD cmd_ual=ADD cmd_ual=ADD
areg=32, breg=38 areg=32, breg=0 areg=32, breg=0 areg=32, breg=0 areg=32, breg=0
dreg=rd dreg=32 dreg=32 dreg=32 dreg=32

byte0 byte1 byte2 byte3

null /
null / read=0, write=0 null /
read=0, write=0 read=0, write=0 null /
excep_mode = 0, read=0, write=0
excep_mode = 0, cmd_ual=SLL excep_mode = 0,
cmd_ual=SRL cmd_ual=SLL excep_mode = 0,
areg=32, breg=36 cmd_ual=SLL
areg=32, breg=38 dreg=32 areg=32, breg=37
dreg=rd dreg=32 areg=32, breg=38
dreg=32
ldshift

Figure VI.27. Sous-graphe de séquencement des instructions ld et ldub.

de la donnée à lire est le résultat d’une addition, dont le deuxième terme peut être un registre
(comme dans ld [%r1+%r2], %r3) ou une constante immédiate (comme dans ldub
[%r1-5], %r2). Cette différence est également distinguée par le bit %ir[13], ce qui
conduit aux deux branches à partir de decode. Une fois en read, l’adresse de la donnée à
lire en mémoire est dans le registre %tmp1 (numéro 32).
À partir de read, si l’instruction est ld et non ldub, les choses sont simples : lire le mot
en mémoire et le stocker dans le registre de numéro rd, ce qui est fait entre read et pc+4 ;
il ne reste plus qu’à incrémenter %pc de 4.
Si l’instruction est ldub, il va falloir lire le mot mémoire de 32 bits situé à l’adresse
contenue dans %tmp1 et isoler parmi les 4 octets qui composent ce mot, celui qui est désigné
par l’instruction ldub. On rappelle (voir annexe B) que ldub place dans les 8 bits de poids
faibles du registre destination l’octet d’adresse désignée et force à 0 les 24 bits de poids forts.
On rappelle également que lors d’un accès mémoire, les circuits mémoire ne reçoivent pas
les 2 bits de poids faibles de l’adresse (voir section 4) et par conséquent lisent 4 octets à la
fois. Si on considère par exemple la situation de la figure VI.28 et qu’on souhaite lire l’octet
E6 situé à l’adresse 0x1000005, la demande de lecture à cette adresse va lire tout le mot
mémoire de 32 bits 0x7AE6329F qui entoure cet octet, à partir de l’adresse 0x1000004 qui
est le multiple de 4 le plus petit avant 0x1000005.
C’est cette lecture de 32 bits qui est faite entre l’état read et les états byte0, byte1,
etc. Le choix de passer vers byte0, byte1, etc. est fait à partir de la valeur des deux bits de
214 CHAPITRE VI. CONSTRUCTION DU PROCESSEUR 32 BITS CRAPS

7A E6 32 9F

1000004 1000008

Figure VI.28. Situation de lecture pour l’instruction ldub. L’adresse lue étant 0x1000005,
on souhaite isoler l’octet E6 du reste du mot.

poids faibles de l’adresse contenue dans %tmp1. Cela implique d’ailleurs que le schéma de
la figure VI.24 a été légèrement simplifié et qu’il y a en réalité les deux signaux %tmp[1,0]
qui vont également de la micromachine vers le séquenceur, nécessaires dans les instructions
ldub et stb pour repérer l’octet à isoler.
Dans le cas de ldub, cette isolation de l’octet sur les 8 bits de poids faibles du registre
destination, avec remplissage à gauche de 24 zéros, est réalisée à l’aide des commandes SRL
et SLL de l’UAL. Pour notre exemple, si on veut extraire E6 du mot 7AE6329F, il suffit
de le décaler à gauche de 8 positions : E6329F00 (8 zéros entrent par la droite), puis de le
décaler à droite de 24 position : 000000E6 (24 zéros entrent par la gauche). Pour faire ces
différents décalages, il est nécessaire de disposer des constantes 8, 16 et 24. C’est dans ce but
qu’on a fait en sorte que ces constantes soient les valeurs des registres %r36, %r37 et %r38
respectivement (voir section 1). À partir de byte0, seul un décalage à droite de 24 positions
est nécessaire. À partir de byte1, byte2 et byte3, un décalage à gauche préalable de 8, 16
et 24 positions (respectivement) est nécessaire.

Séquencement des instructions st et stb


La figure VI.29 montre le sous-graphe de séquencement de ces deux instructions.
Après l’état decode, il y a à nouveau la bifurcation entre les instructions avec une
valeur immédiate (telle que st %r1, [%r2+12]) et celles sans constante immédiate (telle
que stb %r1, [%r2+%r4]). Comme pour ld/ldub, on arrive à l’état w_read après avoir
calculé dans %tmp1 (= %r32) la somme de ces deux arguments, qui forme l’adresse où doit
être effectuée l’écriture.
Dans le cas d’une écriture standard stb, on va directement de l’état w_read à l’état
pc+4 en effectuant la commande d’écriture write=1. On place l’adresse d’écriture sur le
bus A avec areg=32 (=%tmp1); on place la valeur à écrire sur le bus D en la faisant venir sur
le bus B (breg=rd) puis en lui faisant traverser l’UAL sans changement avec la commande
NOPB. Après l’écriture en mémoire, qui est supposée durer ici un seul cycle d’horloge, on
passe à l’état pc+4 pour le passage à l’instruction suivante.
Le cas d’une écriture dans un octet (stb) est beaucoup plus complexe et inefficace, car
on a pris le parti dans l’architecture de CRAPS de ne pas faciliter cette instruction par des
connexions spécialisées entre la mémoire et le bus D. On se rappelle que cette écriture doit
se traduire par la modification d’un seul octet en mémoire, c’est à dire la modification de
8 bits à l’intérieur d’un mot mémoire de 32 bits, tous les autres bits restants inchangés. La
seule façon de faire consiste à :

1. lire à l’adresse %tmp1 le mot de 32 bits dans lequel se situe l’octet à modifier et le
placer dans le registre temporaire %tmp2.

2. remplacer au sein de %tmp2 les 8 bits concernés par les 8 bits de poids faibles du registre
à écrire.On utilisera pour cela les commandes INSBYTE0, etc. de l’UAL, spécialement
8. Séquencement des instructions 215

op = 11, op3 = st+stb, %ir[13] = 1 /


read=0, write=0, excep_mode=0
excep_req = 0 / cmd_ual=SIGNEXT13
... areg=63, breg=0, dreg=32

fetch decode st_imm

null / op = 11, op3 = st+stb, %ir[13] = 0 / null /


read=0, write=0, excep_mode=0 read=0, write=0, excep_mode=0 read=0, write=0, excep_mode=0
cmd_ual=ADD cmd_ual=ADD cmd_ual=ADD
areg=62, breg=33, dreg=62 areg=rs1, breg=rs2, dreg=32 areg=rs1, breg=32, dreg=32

op3 = st /
read=0, write=1, excep_mode=0 w_read
cmd_ual=NOPB
pc+4 areg=32, breg=rd, dreg=0 op3 = stb /
read=1, write=0, excep_mode=0
cmd_ual=ADD
null / areg=32, breg=0, dreg=33
read=0, write=1
excep_mode=0
cmd_ual=NOPB insbyte
areg=32, breg=33, dreg=0 op3 = stb, tmp1[1..0]=00 /
read=0, write=0 op3 = stb, tmp1[1..0]=10 /
excep_mode=0 read=0, write=0
cmd_ual=INSBYTE0 excep_mode=0
areg=rs2, breg=33, dreg=33 cmd_ual=INSBYTE2
areg=rs2, breg=33, dreg=33
op3 = stb, tmp1[1..0]=01 /
read=0, write=0 op3 = stb, tmp1[1..0]=11 /
excep_mode=0 read=0, write=0
cmd_ual=INSBYTE1 excep_mode=0
areg=rs2, breg=33, dreg=33 cmd_ual=INSBYTE3
areg=rs2, breg=33, dreg=33

write

Figure VI.29. Sous-graphe de séquencement des instructions st et stb.

conçues à cet effet.

3. réécrire tout le mot de 32 bits %tmp2 à l’adresse d’origine, toujours contenue dans
%tmp1.

L’étape 1est réalisée entre les états w_read et insbyte, par lancement d’une commande de
lecture à l’adresse %tmp1 (areg=32 et read=1) et stockage du résultat dans le registre %tmp2
(=%r33). On l’aura compris, c’est tout un mot de 32 bits qui est lu, c’est à dire 4 octets.
L’étape 2 est réalisée entre insbyte et write, avec 4 chemins possibles selon le numéro de
l’octet à modifier ; ce numéro est constitué des deux bits de poids faibles de l’adresse %tmp1.
L’étape 3 de réécriture est faite entre write et pc+4 : placement de l’adresse %tmp1 sur
le bus A (areg=32) et placement de la donnée à écrire %tmp2 sur le bus D en la plaçant sur
le bus B (breg=33) et en appliquant la commande NOPB pour lui faire traverser l’UAL.

8.6. Séquencement des instructions de branchement


Avec les instructions de branchement il n’y a que deux cas possibles : soit le
branchement doit être fait, auquel cas il faut ajouter à la valeur de %pc le déplacement,
préalablement multiplié par 4 ; soit le branchement ne doit pas être fait auquel cas il faut
passer à l’instruction suivante en ajoutant 4 à %pc.
Ces deux cas correspondent aux deux flèches qui partent de l’état decode ; le
216 CHAPITRE VI. CONSTRUCTION DU PROCESSEUR 32 BITS CRAPS

séquenceur dispose de l’instruction et des valeurs des flags et a donc tous les moyens
de savoir si le branchement doit être effectué. Il doit l’être si l’instruction est ba, ou si
l’instruction est be et que Z=1, etc. Il ne doit pas l’être dans toutes les autres situations.
En cas de non branchement, on revient simplement à l’état fetch en ajoutant 4 à %pc.
En cas de branchement, il faut d’abord extraire la constante de déplacement relatif codée
sur 22 bits qui est stockée au sein même de l’instruction ; on utilise pour cela la commande
SIGNEXT22 de l’UAL, qui stocke son résultat dans %tmp1 (dreg=32). Ce déplacement
étant codé en nombre de mots mémoire, il faut maintenant le multiplier par 4 en le décalant
de 2 bits vers la gauche, en plaçant %tmp1 sur le bus A (areg=32) et en plaçant la constante 2
sur le bus B (breg=34) avec la commande SLL. Le résultat est à nouveau stocké dans %tmp1
(dreg=32), et il est finalement ajouté à %pc (areg=62, breg=32, dreg=62, cmd_ual=ADD)
avant le retour à l’état fetch.

excep_req = 0 /
...
fetch decode
op=00, op2=010, (op3=be et Z=0) ou
(op3=bneg et N=0) ou etc. /
read=0, write=0 op=00, op2=010, op3 = ba
null / excep_mode=0 ou (op3=be et Z=1) ou etc. /
read=0, write=0 cmd_ual=ADD read=0, write=0
excep_mode=0 areg=62, breg=35, dreg=62 excep_mode=0
cmd_ual=ADD cmd_ual=SIGNEXT22
areg=62, breg=32, dreg=62 areg=63, breg=0, dreg=32

null /
read=0, write=0
excep_mode=0 branch
dispx4 cmd_ual=SLL
areg=32, breg=34, dreg=32

Figure VI.30. Sous-graphe de séquencement des instructions de branchement.

8.7. Réalisation du séquenceur


Tous les sous-graphes de séquencement n’ont pas été présentés ; ils utilisent tous
les techniques qui viennent d’être présentées. Le lecteur est encouragé à les réaliser à
titre d’exercice.
On pourrait également concevoir sans difficultés le circuit séquentiel synchrone,
de type MEALY, qui implémente le séquenceur de CRAPS. On utiliserait pour cela les
techniques présentées au chapitre 3 ; il est d’une complexité modeste, avec moins de 50
états et ses quelques signaux d’entrées et de sorties. Son étude détaillée serait lourde et d’un
intérêt limité ; il a été effectivement mis en oeuvre sur une carte à base de FPGA XILINX
Spartan 3. Il fonctionne à une fréquence d’horloge de 25 MHz ce qui lui donne une puissance
de calcul raisonnable, mais elle pourrait être grandement augmentée par l’utilisation de
techniques avancées qui sortent du cadre de cet ouvrage.

9. Exercices corrigés
9. Exercices corrigés 217

9.1. Exercice 1 : décodage d’adresse

Énoncé
Modifier le câblage du sous-système mémoire de CRAPS pour que la RAM soit vue
par le programmeur à partir de l’adresse 0x02000000.

Solution
Il ne s’agit ici que de modifier le signal de décodage RAM produit par le sous-module
decod_mem.
On imposait que les 8 bits de poids forts de l’adresse soient : 000000012, c’est à dire
une adresse de la forme 0000 0001 xxxx xxxx xxxx xxxx xxxx xxxx, c’est à dire encore en
hexadécimal une adresse de l’intervalle [0x01000000,0x01FFFFFF].En notation SHDL, on
a l’écriture suivante, où adr[15..0] représentent les 16 bits de poids forts de l’adresse :
RAM=CS*/adr15*/adr14*/adr13*/adr12*/adr11*/adr10*/adr9*adr8;

On désire maintenant que les adresses d’implantation de la RAM soient celles du


segment [0x02000000,0x02FFFFFF], c’est à dire que les 8 bits de poids forts de l’adresse
soient 000000102. Il suffit donc de modifier la fin de l’équation de décodage de la RAM :

RAM=CS*/adr15*/adr14*/adr13*/adr12*/adr11*/adr10*adr9*/adr8;

9.2. Exercice 2 : construction d’un circuit d’entrées/sorties simplifié

Énoncé
Concevoir un circuit d’entrées/sorties au fonctionnement simplifié :

1 il possède 32 lignes d’entrées ins[31..0] et 32 lignes de sorties outs[31..0],

2 l’écriture à l’adresse 0x600000 (pour le programmeur) projète la valeur écrite sur


outs,

3 la lecture à l’adresse 0x600000 fournit les valeurs échantillonnées sur ins,

4 les changements de valeurs sur les entrées ne déclenchent aucune interruption.

Solution
L’adresse 0x00600000 à laquelle réagit le circuit est celle qui est vue par le
programmeur; son utilisation en lecture ou en écriture conduit à l’activation de la ligne CS
par la logique de décodage. Comme le circuit ne réagit qu’à cette seule adresse, il n’a plus
besoin d’aucune ligne d’adresse (alors qu’il en utilisait 2 dans sa version initiale), ce qui
conduit à l’interface de la figure VI.31 et au mode d’utilisation de la figure VI.32.
Finalement, le schéma qui réalise ces fonctions s’obtient par simplification du schéma
du circuit initial (figure VI.33).
218 CHAPITRE VI. CONSTRUCTION DU PROCESSEUR 32 BITS CRAPS

Figure VI.31. Interface du circuit d’entrées/sorties.

W opération
0 lire l’état des entrées
1 écrire sur les sorties

Figure VI.32. Spécification de fonctionnement du circuit d’entrées/sorties.

Figure VI.33. Structure du circuit d’entrées/sorties,pour un bit i.

L’écriture en 0x00600000 provoque CS*W = 1, soit un stockage de DATA[i] dans


la bascule, avec sortie de la valeur sur outs[i]. La lecture en 0x00600000 provoque
CS*/W = 1, soit le passage de la valeur présente sur ins[i] sur le bus de données
DATA[i].
Références
[1] A. Cezes et J. Delacroix. Architecture des Machines et des Systèmes Informatiques.
Dunod. 2nd Édition, 2005.

[2] Hennessy and Patterson. Computer Organisation and Design. Morgan Kaufman.
Second Edition, 1998.

[3] J.-J. Schwarz. Architecture des Ordinateurs. Eyrolles. 2nd Edition, 2005.

[4] R. Strandh et I. Durand. Architecture de l’ordinateur. Dunod, 2005.

[5] Andrew S. Tanenbaum. Architecture de l’ordinateur. Pearson Education, 2005.

[6] John F. Wakely. Digital Design. Prentice Hall. 5th Edition, 2000.

[7] P. Zanela et Y. Ligier. Architecture et Technologie des Ordinateurs. Dunod, 2005.

219
Annexe A
Tables diverses

Puissances de 2
2− 1 0,5 21 2
2− 2 0,25 22 4
2− 3 0,125 23 8
2− 4 0,0625 24 16
2− 5 0,03125 25 32
2− 6 0,015625 26 64
2− 7 0,0078125 27 128
2− 8 0,00390625 28 256
2− 9 0,001953125 29 512
2 − 10 0,0009765625 210 1024
2 − 11 0,00048828125 211 2048
2 − 12 0,000244140625 212 4096
2 − 13 0,0001220703125 213 8192
2 − 14 0,00006103515625 214 16384
2 − 15 0,000030517578125 215 32768
2 − 16 0,0000152587890625 216 65536

unités
milliseconde (ms) 10− 3 s Kilo (K) 210 ∼
− 10
3

microseconde (µs) 10− 6 s Mega (M) 220 ∼


− 10
6

nanoseconde (ns) 10− 9 s Giga (G) 230 ∼


− 10
9

picoseconde (ps) 10− 12 s Tera (T) 240 ∼


− 10
12

220
Annexe B
CRAPS : guide du programmeur

Le langage assembleur du processeur CRAPS est largement inspiré de celui du SPARC


version 8. Il n’implémente pas la notion de fenêtre de registres et les instructions qui suivent
les branchements n’ont pas de statut spécial. Il ne possède pas d’unité de calcul flottant.

B.1. Instructions synthétiques

Les instructions du tableau suivant sont appelées instructions synthétiques; ce sont des cas
particuliers d’instructions plus générales. Plusieurs d’entre-elles s’appuient sur le fait que
%r0 vaut toujours 0.

Instruction Effet Implémentation

clr %ri met à zéro %ri orcc %r0, %r0, %ri

mov %ri,%rj copie %ri dans %rj orcc %ri, %r0, %rj

inccc %ri incrémente %ri addcc %ri, 1, %ri

deccc %ri décrémente %ri subcc %ri, 1, %ri

notcc %ri,%rj %rj <- complément de %ri xnorcc %ri, %ri, %rj

set val31..0, %ri copie val dans %ri sethi val31..10, %ri
orcc %ri, val9..0, %ri

setq val, %ri copie val dans %ri orcc %r0, val, %ri

( − 4096 ≤ val ≤ 4095)

cmp %ri, %rj compare %ri et %rj subcc %ri, %rj, %r0

tst %ri teste nullité et signe de %ri orcc %ri, %r0, %r0

negcc %ri inverse %ri subcc %r0, %ri, %ri

notcc %ri,%rj complémente %ri en %rj xnorcc %ri, %r0, %rj

nop no operation sethi 0,%r0

221
222 ANNEXE B. CRAPS : GUIDE DU PROGRAMMEUR

jmp %ri branchement à l’adresse absolue %ri jmpl %ri, %r0

ret retour de procédure terminale jmpl %r31+4, %r0

push %ri empile %ri sub %r30, 4, %r30


st %ri, [%r30]

pop %ri dépile %ri ld [%r30], %ri


add %r30, 4, %r30
B.1. Instructions synthétiques 223

B.2. Jeu d’instructions du processeur CRAPS

Les instructions marquées d’un † sont privilégiées, et ne peuvent être exécutées que si le bit
superviseur dans %psr est à 1.

Instruction : add
Description : Effectue l’addition en complément à deux des deux opérandes, et place le
résultat dans l’opérande résultat. La retenue C n’est pas ajoutée aux arguments. Les flags
(condition codes, cc) ne sont pas modifiés par cette opération, contrairement à addcc.
Flags affectés : aucun
Exemple : add %r1, 5, %r1
Ajoute 5 au contenu de %r1; ne modifie pas les flags.

Instruction : addcc
Description : Effectue l’addition en complément à deux des deux opérandes, et place le
résultat dans l’opérande résultat. La retenue C n’est pas ajoutée aux arguments. Les flags
(condition codes, cc) sont positionnés conformément au résultat (voir add).
Flags affectés : N, Z, V, C
Exemple : addcc %r1, 5, %r1
Ajoute 5 au contenu de %r1, et positionne les flags.

Instruction : andcc
Description : Effectue un ET logique bit à bit entre les opérandes sources, et place le résultat
dans l’opérande résultat. Les flags (condition codes, cc) sont positionnés conformément
au résultat.
Flags affectés : N, Z
Exemple : andcc %r1, %r2, %r3
Effectue le ET logique bit à bit entre les contenus de %r1 et %r2, et place le résultat
dans %r3
Instruction : ba
Description : Se branche à l’adresse obtenue en ajoutant 4 x disp22 à l’adresse de
l’instruction courante (ba elle-même). disp22 peut être négatif, ce qui correspond à un
branchement à un point antérieur du programme.
Flags affectés : aucun
Exemple : ba label
Se branche à label. Le déplacement disp22 codé dans l’instruction est égal à la distance
(en mots) qui sépare l’instruction courante de l’adresse label.

Instruction : bcc
Description : Si la condition cc est vérifiée, se branche à l’adresse obtenue en ajoutant
4 x disp22 à l’adresse de l’instruction courante. Si la condition n’est pas vérifiée, passe
à l’instruction suivante en séquence. disp22 peut être négatif, ce qui correspond à un
branchement à un point antérieur du programme. On trouvera en annexe B.3 les tables
complètes des conditions de test possibles.
Flags affectés : aucun
Exemple : bcs label
224 ANNEXE B. CRAPS : GUIDE DU PROGRAMMEUR

Se branche à label si C vaut 1. C’est le déplacement relatif entre label et l’adresse courante
de l’instruction, en nombre de mots, qui est codé dans le champ disp22 de l’instruction.

Instruction : call
Description : Appelle un sous-programme et stocke l’adresse de l’instruction courante
(celle du call lui-même) dans %r31. Le champ disp30 du code machine contient une
valeur de déplacement par rapport à l’adresse de l’instruction courante, comptée en mots.
Autrement dit, l’adresse de la prochaine instruction à exécuter est calculée en ajoutant 4 x
disp30 à l’adresse de l’instruction courante (dans %pc). disp30 peut bien sûr être négatif.
Flags affectés : aucun
Exemple : call ssprog
Sauvegarde %pc dans %r31, puis se branche au sous-programme qui commence à l’adresse
ssprog.

Instruction : jmpl
Description : Jump and Link: retour d’un sous-programme. Effectue un saut à l’adresse
définie par l’opérande source, et sauve l’adresse de l’instruction courante (le jmpl) dans
l’opérande résultat.
Flags affectés : aucun
Exemple : jmpl %r31 + 4, %r0
Retour d’un sous-programme, équivalent à l’instruction synthétique ret. La valeur
du PC ayant été sauvegardée dans %r31 lors du call précédent, l’adresse de retour doit
effectivement être %r31 + 4. L’adresse courante est éliminée dans %r0.
Instruction : ld
Description : Load word. Charge un registre à partir d’un mot de 32 bits de la mémoire
centrale, soit 4 octets consécutifs. L’adresse doit être alignée en mémoire sur un mot,
c’est à dire qu’elle doit être un multiple de 4, sinon une exception align_error est générée.
L’adresse est calculée en ajoutant le contenu du registre du champ rs1 au contenu du champ
rs2 ou de la valeur immédiate contenue dans le champ simm13, selon le cas.
Flags affectés : N et Z.
Exemple : ld [%r2+%r3], %r1
Copie dans le registre %r1 les 4 octets en mémoire commençant à l’adresse obtenue en
additionnant les valeurs des registres %r2 et %r3. Le premier octet (celui d’adresse %r2 +
%r3) sera copié dans les poids forts de %r1, le quatrième octet (d’adresse %r2 + %r3 + 3)
sera copié dans les poids faibles de %r1.

Instruction : ldub
Description : Load unsigned byte. Charge les 8 bits de poids faibles d’un registre depuis la
mémoire centrale. L’adresse peut être quelconque. Elle est calculée en ajoutant le contenu
du registre du champ rs1 au contenu du champ rs2 ou à valeur immédiate contenue dans le
champ simm13, selon le cas. Les 24 bits de poids forts du registre sont forcés à 0.
Flags affectés : N et Z.
Exemple : ldub [%r2+6], %r1
Copie l’octet situé à l’adresse %r2 + 6 dans les 8 bits de poids faibles du registre %r1, et force
à 0 les 24 bits de poids forts.
B.2. Jeu d’instructions du processeur CRAPS 225

Instruction : orcc
Description : Effectue un OU logique bit à bit entre les opérandes sources, et place
le résultat dans l’opérande résultat. Les flags (condition codes, cc) sont positionnés
conformément au résultat.
Flags affectés : N, Z
Exemple : orcc %r1, 1, %r1
Positionne à 1 le bit de poids le plus faible de %r1 et laisse tous les autres inchangés.

Instruction : rdpsr †
Description : Lit le registre d’état %psr et le copie dans un registre %ri.
Flags affectés : aucun
Exemple : rdpsr %r1
Lit %psr et le copie dans %r1

Instruction : rete
Description : Défait les empilements de %pc et de %psr qui sont réalisés lors de la prise
en compte d’une exception. %psr reprend donc sa valeur initiale (et en particulier le niveau
d’exécution pil), et %pc reprend la valeur qu’il avait au moment du départ. Cette instruction
doit être placée à la fin de chaque handler d’exception.
Flags affectés : potentiellement tous, lors du dépilement de %psr
Exemple : rete

Instruction : sethi
Description : Positionne les 22 bits de poids forts d’un registre, et force à zéro les 10 bits de
poids faibles.
Flags affectés : aucun
Exemple : sethi 0xE2F1, %r1
Positionne les 22 bits de poids forts de %r1 à E2F116 et force à 0 les 10 bits de poids
faibles.
Instruction : sll
Description : Décale le contenu d’un registre vers la gauche d’un nombre de positions
désigné par le deuxième argument, compris entre 0 et 31. Si ce nombre est fourni sous forme
d’une valeur de registre, seuls les 5 bits de poids faibles sont utilisés.
Flags affectés : aucun
Exemple : sll %r1, 7, %r2
Décale le contenu de %r1 vers la gauche de 7 bits, avec insertion de 7 zéros par la droite, et
stocke le résultat dans %r2. Aucun flag n’est affecté.

Instruction : srl
Description : Décale le contenu d’un registre vers la droite d’un nombre de positions
désigné par le deuxième argument, compris entre 0 et 31. Si ce nombre est fourni sous forme
d’une valeur de registre, seuls les 5 bits de poids faibles sont utilisés.
Flags affectés : aucun
Exemple : srl %r1, 7, %r2
Décale le contenu de %r1 vers la droite de 7 bits, avec insertion de 7 zéros par la gauche, et
stocke le résultat dans %r2. Aucun flag n’est affecté.
226 ANNEXE B. CRAPS : GUIDE DU PROGRAMMEUR

Instruction : st
Description : Stocke le contenu d’un registre en mémoire centrale. L’adresse doit être
alignée en mémoire sur un mot, c’est à dire qu’elle doit être un multiple de 4. L’adresse est
calculée en ajoutant le contenu du registre du champ rs1 au contenu du champ rs2 ou de la
valeur contenue dans le champ simm13, selon le cas. Le champ rd est utilisé pour désigner
le registre source.
Flags affectés : aucun
Exemple : st %r1, [%r2]
Copie le contenu du registre %r1 dans la case mémoire dont l’adresse est la valeur de %r2.
Dans ce cas particulier, rs2 = 0, et l’instruction pourrait être écrite : st %r1, [%r2+%r0]

Instruction : stb
Description : Stocke les 8 bits de poids faibles d’un registre, à une adresse quelconque en
mémoire centrale. Seul un octet de la mémoire est affecté. L’adresse est calculée en ajoutant
le contenu du registre du champ rs1 au contenu du champ rs2 ou de la valeur contenue dans
le champ simm13, selon le cas. Le champ rd est utilisé pour désigner le registre source.
Flags affectés : aucun
Exemple : stb %r1, [%r2+5]
Copie les 8 bits de poids faibles de %r1 dans la case mémoire dont l’adresse est la valeur de
%r2+5. Seul cet octet est affecté en mémoire.
Instruction : sub
Description : Effectue la soustraction en complément à deux des deux opérandes, et place
le résultat dans l’opérande résultat. les flags (condition codes, cc) ne sont pas affectés.
Flags affectés : aucun
Exemple : sub %r1, 5, %r1
Soustrait 5 au contenu de %r1, et ne modifie pas les flags.

Instruction : subcc
Description : Effectue la soustraction en complément à deux des deux opérandes, et
place le résultat dans l’opérande résultat. Les flags (condition codes, cc) sont positionnés
conformément au résultat.
Flags affectés : N, Z, V, C
Exemple : subcc %r1, 5, %r1
Soustrait 5 au contenu de %r1, et positionne les flags.

Instruction : udivcc
Description : Unsigned divide. Effectue une division non signée 32 bits / 16 bits avec
calcul du quotient et du reste. Le quotient est placé dans les 16 bits de poids forts du registre
destination, le reste dans les 16 bits de poids faibles.
Flags affectés : Z
Exemple : udiv %r7, %r1, %r6
Effectue la division entière non signée entre les 16 bits de poids faibles de %r7 et les 16 bits
de poids faible de %r1, et place le quotient dans les 16 bits de poids forts de %r6 et le reste
dans les 16 bits de poids faibles de %r6.

Instruction : umulcc
Description : Unsigned multiply. Effectue une multiplication non signée 16 bits x 16 bits
vers 32 bits. Seuls les 16 bits de poids faibles des deux opérandes source sont pris en compte
B.2. Jeu d’instructions du processeur CRAPS 227

dans le calcul.
Flags affectés : Z
Exemple : umul %r7, %r1, %r6
Copie dans %r6 le résultat de la multiplication non signée entre les 16 bits de poids faibles
de %r7 et les 16 bits de poids faibles de %r1.

Instruction : wrpsr †
Description : Copie dans %psr (Processor Status Register) le contenu d’un registre %ri.
Flags affectés : aucun
Exemple : wrpsr %r1
Copie dans %psr le contenu de %r1.

Instruction : wrtbr †
Description : Copie dans %tbr (Trap Base Register) le contenu d’un registre %ri.
Flags affectés : aucun
Exemple : wrtbr %r1
Copie dans %tbr le contenu de %r1.

Instruction : xnorcc
Description : Effectue un XNOR logique (inverse du XOR, encore appelé coïncidence)
bit à bit entre les opérandes sources et place le résultat dans l’opérande résultat. Les flags
(condition codes, cc) sont positionnés conformément au résultat.
Flags affectés : N, Z
Exemple : xnorcc %r7, %r1, %r6
Copie dans %r6 le xnor calculé bit à bit entre %r7 et %r1 et positionne les flags N et Z
en conséquence.

Instruction : xorcc
Description : Effectue un XOR logique bit à bit entre les opérandes sources et place
le résultat dans l’opérande résultat. Les flags (condition codes, cc) sont positionnés
conformément au résultat.
Flags affectés : N, Z
Exemple : xorcc %r7, %r1, %r6
Copie dans %r6 le xor calculé bit à bit entre %r7 et %r1, et positionne les flags N et Z
en conséquence.
228 ANNEXE B. CRAPS : GUIDE DU PROGRAMMEUR

B.3. Tables des branchements conditionnels

instruction opération test


ba Branch Always 1
beq (synonyme : bz) Branch on Equal Z
bne (synonyme : bnz) Branch on Not Equal not Z
bneg (synonyme : bn) Branch on Negative N
bpos (synonyme : bnn) Branch on Positive not N
bcs Branch on Carry Set C
bcc Branch on Carry Clear not C
bvs Branch on Overflow Set V
bvc Branch on Overflow Clear not V

Figure B.1. Branchements conditionnels associés à un seul flag.

instruction opération test


bg Branch on Greater not (Z or (N xor V))
bge Branch on Greater or Equal not (N xor V)
bl Branch on Less (N xor V)
ble Branch on Less or Equal Z or (N xor V)

Figure B.2. Branchements conditionnels associés à une arithmétique signée.

instruction opération test


bgu Branch on Greater Unsigned not (Z or C)
bcc Branch on greater than, or equal, unsigned not C
bcs Branch on less than, unsigned C
bleu Branch on Less or Equal Unsigned Z or C

Figure B.3. Branchements conditionnels associés à une arithmétique non signée.

B.4. Format binaire des instructions de CRAPS


B.4. Format binaire des instructions de CRAPS 229

op Format 1 : call
0 1 disp30

Format 2 : sethi et branchements


op
0 0 rd op2 imm22 2a
op
0 0 0 cond op2 disp22 2b

Format 3 : accès mémoire, instructions arithmétiques


op
1t rd op3 rs1 0 −− −− − − − − rs2 3a

1t rd op3 rs1 1 simm13 3b


t = 1 : accès mémoire, t = 0 : instruction arithmétique

op2 Instr. op3 (t = 0) Instr. op3 (t = 1) Instr. cond Branchement


010 branch 000000 add 000000 ld 1000 ba
100 sethi 010000 addcc 000001 ldub 0001 be
000100 sub 000100 st 1001 bne
010100 subcc 000101 stb 0101 bcs
011010 umulcc 1101 bcc
010001 andcc 1110 bpos
010010 orcc 0110 bneg
010011 xorcc 0111 bvs
010111 xnorcc 1111 bvc
110101 sll 1010 bg
110110 srl 0010 ble
111000 jmpl 1011 bge
0011 bl
1100 bgu
0100 bleu
230 ANNEXE B. CRAPS : GUIDE DU PROGRAMMEUR

B.5. Directives de l’assembleur CRAPS

Syntaxe Rôle
.org val32 force à val la nouvelle adresse
d’assemblage
ex : data .org 0x8000
label .eq val32 associe val 3 2 à label dans la table des
symboles
label = val32 idem
ex : NB .eq 5

.word val32(, val32) ∗ alloue et initialise des mots mémoire de 32


bits consécutifs
ex : list .word 2,3,5,7

.byte vbyte(, vbyte) ∗ alloue et initialise en mémoire des octets


consécutifs
ex text .byte 10,13,"erreur",0
.malloc n alloue sans initialiser n octets consécutifs
ex : buffer .malloc 100
.global sym Fait de sym un symbole global, visible par
les autres modules
ex : .global strout

val32 représente un nombre de l’intervalle [0, 232 − 1], ou de l’intervalle [ − 232 , 232 − 1].
vbyte représente une ou plusieurs descriptions d’octets : un nombre de [0, 255] ou une suite
de caractères entre apostrophes.
B.5. Directives de l’assembleur CRAPS 231

B.6. Cartographie mémoire de CRAPS

[0000 0000 − 0000 FFFF] ROM (64K) commande #1 0040 0000


commande #2 0040 0004
IT timer 0040 0008
[0040 0000 − 0040 000B] timer

[0060 0000 − 0060 000F] entrées / sorties


données 0060 0000
direction 0060 0004
ITs memorisees 0060 0008
sens front ITs 0060 000C
[0100 0000 − 01FF FFFF] RAM (16 Mo)

B.7. Programmation des entrées/sorties

adresse W opération
0x600000 0 lire l’état des entrées
0x600000 1 écrire sur les sorties
0x600004 0 lire configuration lignes
0x600004 1 configurer les lignes
0x600008 0 lire interruptions mémorisées
0x600008 1 raz des interruptions mémorisées
0x60000C 0 lire sens du front d’interruption (0 = front montant)
0x60000C 1 écrire sens du front d’interruption

B.8. Programmation du timer

adresse W opération
0x400000 0 lire le registre de commande #1 (P + N)
0x400000 1 écrire le registre de commande #1 (P + N)
0x400004 0 lire le registre de commande #2 (prediv + start)
0x400004 1 écrire le registre de commande #2 (prediv + start)
0x400008 0 lire bit d’interruption timer
0x400008 1 raz bit d’interruption timer
232 ANNEXE B. CRAPS : GUIDE DU PROGRAMMEUR

B.9. Liste des exceptions

exception priorité numéro de type (tt)


reset 15 voir texte
bus error 14 14
instruction privilégiée 13 13
instruction illégale 12 12
division par zéro 11 11
erreur d’alignement 10 10
interruption timer 8 8
interruption entrées/sorties 7 7
Glossaire
accès aléatoire algèbre de Boole
Pour une mémoire, indique la possibilité Voir Boole.
de faire des opérations successives de
lecture et-ou d’écriture à des adresses ASCII
quelconques, dans n’importe quel ordre. American Standard Code for International
Pour un fichier, indique la possibilité Interchange. Codage des caractères
d’effectuer une suite d’accès en lecture ou anglo-saxons sur 7 bits. La plupart des
en écriture à des enregistrements situés à codages de caractères (par exemple
des positions quelconques.Voir aussi accès ISO-Latin 1) sont des extensions sur 8 bits
séquentiel. de l’encodage ASCII.

accès direct mémoire ASIC


Voir DMA. Un ASIC (Application Specific
Integrated Circuit) est un circuit intégré
accès séquentiel réalisé pour une application spécifique,
Serial access. Mode de lecture ou contrairement aux PLDs et FPGAs qui
d’écriture de données qui suit un ordre de sont reprogrammables et peuvent réaliser
rangement préétabli. un ensemble varié de fonctions. Un ASIC
est un peu plus rapide que son équivalent
adressage absolu PLD ou FPGA, mais il doit être produit
Voir adressage direct. en grandes quantités pour que son coût à
l’unité soit raisonnable.
adressage direct
assembleur (langage)
Utilisé en langage d’assemblage et en
langage machine. Mode d’adressage dans Language symbolique de programmation
lequel l’adresse (fixe) de la donnée en associé à un processeur, qui est en fait
mémoire est fournie dans l’instruction. Est une expression directe plus lisible d’un
aussi appelé adressage absolu. programme écrit en langage machine.

adressage immédiat assembleur (programme)


Utilisé en langage d’assemblage et en Programme de traduction qui convertit en
langage machine. Mode d’adressage langage machine un programme écrit en
dans lequel la donnée est fournie dans langage assembleur.
l’instruction elle même. Aucun autre accès
en mémoire n’est nécessaire.
asynchrone
Se dit d’un circuit ou d’un composant qui
AGP n’est pas synchrone, c’est à dire dont les
Advanced Graphics Port. Port sorties changent peu de temps après que
de communication spécialisé entre le les entrées aient changées, et non après un
processeur graphique et la carte mère, front d’horloge.
opérant à un débit de 522 MBs.

233
234 GLOSSAIRE

big-endian processeur, la mémoire centrale et les


Voir little-endian. contrôleurs des principaux périphériques
et bus d’un ordinateur. Des cartes
bistable additionnelles (carte graphique, carte
Voir latch. son, etc.) viennent s’enficher sur les
connecteurs qui y sont présents (PCI, AGP,
bit USB, etc.)
Forme abrégée de ’binary digit’.
Information représentée par un symbole à
chargeur
deux valeurs, généralement notées 0 et 1, Module du système d’exploitation
associées aux deux états d’un dispositif. responsable du placement en mémoire
centrale d’un programme à exécuter.
bogue
Traduction officielle française de ’bug’.
CISC
Voir bug. Complex Instruction Set Computer. Ce
terme a été créé en même temps et
Boole pour s’opposer au terme RISC. Désigne
Georges Boole (1815-1864), les processeurs aux jeux d’instructions
mathématicien anglais, publie en 1954 complexes et de longueurs variables, qui
ses travaux sur une algèbre permettant utilisent généralement un séquenceur
de représenter des informations factuelles microprogrammé pour s’exécuter. L’Intel
binaires telles que ’La porte est fermée’ Pentium, le Motorola 68000 sont des
ou ’La porte n’est pas fermée’. L’algèbre exemples de processeurs CISC.
de Boole a été ensuite développée
par Shannon sous la forme que nous
CMOS
connaissons aujourd’hui. Complementary Metal Oxyde
Semiconductor. Technologie de
buffer fabrication de circuits intégrés utilisée pour
Se dit d’un élément logiciel ou matériel. les processeurs, les microcontrôleurs et
Mémoire ou partie de mémoire permettant les mémoires statiques. Ils se distinguent
le stockage temporaire de données entre par leur fort taux d’intégration, leur faible
deux organes. dissipation thermique, et le fait qu’ils ne
consomment du courant significativement
bug que lors des changements d’état.
Défaut d’un programme se manifestant par codage par champs
des anomalies de fonctionnement.
Façon de coder les instructions d’un
bus langage machine, dans laquelle une
Ensemble de lignes électriques véhiculant succession hiérarchique de champs de bits
un groupe de signaux particuliers. dans l’instruction permet de la décoder et
de l’exécuter.
byte
compilateur
Forme anglaise du mot ’octet’. Groupe de
Traducteur d’un langage vers un autre,
8 bits.
généralement d’un langage évolué vers un
carte mère code objet proche du langage machine.
Circuit imprimé sur lequel est situé le
CPLD
Glossaire 235

Complex Programmable Logic Device. Un éditeur de texte


CPLD est un PLD de grande capacité. Programme permettant l’édition de fichiers
contenant uniquement des lignes de texte,
CPU
sans aucune indication de formatage.
Central Processing Unit. Souvent utilisé L’encodage des caractères peut être ASCII,
comme synonyme de processeur. Voir un encodage ISO sur 8 bits ou Unicode.
unité centrale.
édition de lien
debugger
Opération de fusion de plusieurs fichiers
Outil logiciel permettant d’exécuter un
de code objet pour produire un fichier de
programme sous contrôle, en y plaçant des
code exécutable.
points d’arrêt et en permettant l’inspection
de l’état des variables et de la mémoire. EEPROM
décodage Electrically Erasable PROM. Mémoire
morte reprogrammable, effaçable
Voir décodeur. électriquement par octet.
décodeur
enregistrement
Circuit traduisant une adresse en
Dans un fichier, se dit d’un élément
une information d’appartenance à un
logique de celui-ci. Dans un programme,
sous-espace d’adresse.
synonyme de structure de données.
DMA entrance
Direct Memory Acces = accès direct
Voir fan-in.
mémoire. Dispositif lié à la mémoire qui
permet d’y faire des lectures et-ou des EPROM
écritures sans passer par l’unité centrale.
Erasable PROM. Est parfois appelé
Les disques durs, les cartes graphiques,
REPROM (REprogramable PROM).
les cartes son fonctionnent le plus souvent
Mémoire morte reprogrammable munie
en DMA.
d’une petite fenêtre de quartz, et effaçable
DRAM par une exposition aux ultra-violets.
Voir mémoire dynamique.
exception
EBCDIC Événement exceptionnel qui intervient
Ancien codage de caractères tombé en pendant le déroulement d’un
désuétude, qui était employé dans les programme. Les exceptions à cause
machines de gestion. interne sont appelées traps (division par
0, débordement arithmétique, bus error,
échantillonnage etc.) et les exception à cause externe
sont appelées interruptions (fin
Mémorisation de la valeur d’un signal
d’entrée/sortie disque, réseau ; événement
(0 ou 1 en logique digitale) à un moment
clavier, USB ; etc.). La prise en compte
précis, généralement déterminé par le front
de l’exception provoque l’exécution
montant ou descendant d’un autre signal.
d’un sous-programme associé appelé
éditeur de lien gestionnaire d’exception.
Voir édition de lien. fan-in
236 GLOSSAIRE

Caractérise le courant qu’absorbe l’entrée différence de temps de propagation sur


d’une porte logique, mesuré en nombre de des sous-termes d’un terme complexe. Les
telles entrées qui peuvent être reliées à une tables de Karnaugh permettent de détecter
sortie de même technologie. et d’éliminer les glitchs des expressions
combinatoires.
fan-out
Caractérise le courant maximum
handler d’exception
que peut générer la sortie d’une porte Voir gestionnaire d’exception.
logique, mesuré en nombre d’entrées de
même technologie qui peuvent lui être Harvard (modèle de)
connectées. Organisation générale d’un ordinateur
caractérisée par la présence de deux
flag mémoires séparées pour le stockage du
Voir indicateur. programme et des données. Voir Von
Neumann (modèle de).
flash
Voir mémoire flash. harware
Partie matérielle d’un ordinateur.
FPGA
Field Programmable Gate Array. Type de haute impédance
circuit logique reconfigurable basé sur la Se dit d’une sortie d’un circuit logique.
technologie des ’gate arrays’ (matrices Lorsqu’elle est dans cet état, tout se passe
de portes). Ils sont généralement de plus comme si elle était déconnectée du circuit,
grande capacité que les PLDs ; leur car elle ne produit ni ne consomme plus
configuration est stockée dans une RAM aucun courant. Des sorties qui peuvent se
qui doit être rechargée à chaque mise sous mettre en état haute impédance peuvent
tension. être reliées entre elles directement (sur
un bus par exemple), pourvu qu’une
génération de code logique de décodage garantisse qu’au
Phase finale lors de la compilation d’un plus une seule de ces sorties est source de
programme, durant laquelle le code courant à un moment donné (sous peine
assembleur associé au programme source de court-circuit).
est effectivement généré.
HDL
gestionnaire d’exception Harware Description Language. Langage
Sous-programme associé à une exception, de description de circuits, logique ou
qui est exécuté à chaque occurrence de analogiques. Certains sont structurels,
l’exception, de façon transparente pour le dérivant en détails la composition et la
programme en cours. structure de tous leurs composants, tandis
que d’autres sont fonctionnels, décrivant
glitch les relations fonctionnelles entre les flux
Un glitch est une erreur fugitive dans un d’entrées/sorties.
système informatique. Dans les circuits
combinatoires ou séquentiels, c’est un
hexadécimal
état fugitif 0 ou 1 qui apparaît sur un Système de comptage en base 16. Les
signal, à un moment où il devrait être chiffres sont : 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, A, B,
stable, et qui est dû à un problème de C, D, E, F. On l’utilise comme écriture plus
lisible du binaire, en regroupant les bits par
Glossaire 237

paquets de 4. circuits reconfigurables tels que FPGAs et


PLDs.
hold (temps de)
Durée après le front d’horloge durant
langage machine
laquelle les entrées d’une bascule doivent Language qu’exécute un microprocesseur,
être stables pour que le basculement se formé d’une suite d’instructions binaires.
passe correctement. Voir setup.
latch
indicateur Cellule de mémorisation de 1 bit.
Bit mémorisé par un processeur, link
fournissant une indication sur un calcul
qui vient d’être effectué par l’unité Voir édition de lien.
arithmétique et logique. Ils s’appellent little-endian, big-endian
le plus souvent N (negative), Z (zero), V
Ces termes sont issus des ’Voyages de
(overflow), C (Carry).
Gulliver’ de Swift, lorsque Gulliver est au
interpréteur pays des politiciens. Une guerre d’opinion
fait rage à propos de la manière de
Programme ou dispositif matériel
manger son oeuf à la coque, entre le
fonctionnant cycliquement, en décodant
parti des ’big-endian’ qui pense qu’on
et exécutant à chaque cycle une nouvelle
doit le casser par le gros bout, et le parti
instruction d’un programme. Un
des ’little-endian’ qui veut commencer
processeur est toujours un interpréteur
par le petit. En informatique, désigne
(matériel) de son langage machine.
deux conventions d’écriture d’un nombre
interruption binaire sur plusieurs octets successifs.
Dans l’écriture big-endian, les octets sont
Evénement asynchrone, déclenché par copiés en terminant par les 8 bits de
l’environnement matériel d’un ordinateur poids les plus faibles ; dans l’écriture
(disque, réseau, souris, etc.), destiné à little-endian on termine par les 8 bits
prévenir le processeur d’une condition de poids fort. L’écriture little-endian
particulière qui doit être traitée rapidement (la moins naturelle) n’est actuellement
en suspendant le programme en cours utilisée que dans les processeurs x86
d’exécution de manière transparente. Les d’Intel, et elle est une des principales
interruptions sont des exceptions à cause sources d’incompatibilité entre structures
interne. et fichiers de données issues de machines
ISA (bus) différentes.
Industry Standard Architecture. Nom d’un load-store
bus de communication pour compatible Se dit d’une architecture d’ordinateur qui
PC, maintenant obsolète. possède un petit nombre d’instructions de
transfert entre la mémoire et les registres
ISA (instructions)
de type load et store, et dont toutes les
Instruction Set Architecture. Jeu autres instructions opèrent sur les registres
d’instructions d’un microprocesseur. internes au processeur. C’est souvent une
caractéristique des processeurs RISC.
JTAG
Norme internationale permettant loader
l’interconnexion et la configuration de Voir chargeur.
238 GLOSSAIRE

logiciel nécessite plusieurs autres composants


Voir software. autour de lui pour fonctionner.

mappée en mémoire microprogramme


Se dit des entrées/sorties d’un processeur, Ensemble d’instructions placées dans
lorsque celui-ci ne peut agir sur elles une mémoire interne au processeur, non
que par des opérations de lecture ou accessible au programmeur, qui permet
d’écriture en mémoire, à des adresses l’exécution des instructions non câblées du
dédiées. Un tel type de programmation langage machine.
des entrées/sorties s’oppose à l’utilisation
modes d’adressage
d’instructions spéciales d’entrées/sorties
comme sur les x86 par exemple. Méthodes d’accès aux données pour
un microprocesseur. Voir par exemple
mémoire dynamique adressage direct.
Mémoire à accès aléatoire dont les cellules
module
de mémorisation sont des capacités
mémoire. Les mémoires dynamiques ont En software, bloc de code réutilisable.
un taux d’intégration plus élevé que les En hardware, circuit réutilisable. Dans les
mémoires statiques, mais elles nécessitent deux cas, c’est un outil fondamental de
une électronique de rafraîchissement du maîtrise de la complexité.
contenu des cellules.
Moore (loi de)
mémoire flash Gordon Moore est un des fondateurs
Mémoire morte reprogrammable, d’Intel Corporation. Il a remarqué que tous
effaçable électriquement par blocs. les 18 mois environ, la puissance de calcul
et la capacité mémoire des ordinateurs sont
mémoire statique multipliées par deux. Cette progression a
Mémoire à accès aléatoire dont les cellules été appelée loi de Moore, et elle est encore
de mémorisation sont des bistables. valable actuellement.

mémoire tampon multiprocesseur


Voir buffer. Se dit d’un ordinateur qui possède
plusieurs processeurs centraux.
microcontrôleur
octet
C’est un circuit intégré unique, qui
contient à la fois le microprocesseur, Byte en anglais. Groupe de 8 bits.
sa mémoire centrale et des circuits
d’entrées/sorties. Il fonctionne donc de
PCI
façon autonome avec un minimum de Bus de communication entre un processeur
composants extérieurs, et ce sont ces et des cartes additionnelles, fonctionnant
circuits qui équipent nos voitures, jouets, à une fréquence typique de 33 MHz. Il a
appareils électroménagers, etc. été conçu par Intel, qui en a placé tous les
brevets dans le domaine publique.
microprocesseur
Processeur dont tous les éléments sont
PLD
rassemblés en un seul circuit intégré. Programmable Logic Device. Circuit
Il n’est généralement pas autonome et logique dont les fonctions combinatoire
Glossaire 239

ou séquentielles sont programmables. Se dit d’une partie de code machine qui


On appelle souvent PLD uniquement les peut s’exécuter où qu’elle soit placée
circuits dont la configuration est stockée en mémoire ; qui peut donc être relogée.
dans une EEPROM ou une mémoire flash, Tous les compilateurs produisent un code
et qui sont opérationnels dès la mise sous relogeable, pour que le chargeur puisse
tension, contrairement aux FPGAs. le placer en mémoire à l’endroit le plus
favorable.
processeur
Organe principal d’un ordinateur, qui REPROM
permet d’interpréter les instructions de son Voir EPROM.
langage machine.
RISC
progiciel Reduced Instruction Set Computer. Se dit
Ensemble de programmes professionnels, de processeurs possédant des instructions
documentés et livrés sous forme d’un en nombre limité, de taille fixe, et qui
package. les exécutent généralement en un cycle
d’horloge.
PROM
Programable ROM. Mémoire morte ROM
programmable dont les bits sont analogues Read Only Memory. Mémoire morte,
à des fusibles que l’on fait fondre par accessible en lecture seulement.
application d’un courant élevé lors de la
programmation. routine
Voir sous-programme.
RAM
Random Acces Memory. Mémoire vive, SDRAM
accessible en lecture et en écriture. Synchronous Dynamic RAM : RAM
dynamique synchrone.
random access
Voir accès aléatoire. setup (temps de)
Durée avant le front d’horloge durant
record laquelle les entrées d’une bascule doivent
Voir enregistrement. être stables pour que le basculement se
passe correctement. Voir hold.
réentrant
Se dit d’un programme dont une seule software
copie en mémoire peut être exploitée Logiciel. Tout ce qui concerne la
par plusieurs processus dans un système programmation d’un ordinateur, par
multitâches, sans que leur données ne opposition à hardware.
se confondent. Tous les compilateurs
produisent automatiquement du code sortance
réentrant. Voir fan-out.
registre sous-programme
Circuit synchrone de mémorisation de Partie de programme réutilisable. On
plusieurs bits. l’invoque par une instruction spécialisée,
et on lui communique en général des
relogeable arguments par la pile ou dans des registres.
240 GLOSSAIRE

Lorsque son exécution est terminée, une translatable


instructions spéciale reprend le contrôle Voir relogeable.
dans le programme appelant après
l’instruction d’appel. trap
Éxception à cause interne, provoquée par
stack frame
l’exécution d’une instruction, telle que
Portion de la pile d’un programme en cours division par 0, débordement arithmétique,
d’exécution, dans laquelle sont stockées bus error, etc.
les valeurs associées au bloc en cours
(généralement les paramètres d’appel et les tri-state
variables locales d’un sous-programme). Voir haute-impédance.
statique trois-états
Voir mémoire statique. Voir haute-impédance.
synchrone TTL
Se dit d’un circuit qui possède une entrée Transistor Transistor Logic. Technologie
d’horloge qui synchronise toutes ses de réalisation de circuits logiques, dont
opérations. la caractéristique principale est sa vitesse
d’exécution. On lui préfère actuellement
tampon
la technologie CMOS pour les circuits à
Voir buffer. forte intégration.
temps d’accès Unicode
Se dit d’un dispositif de mémorisation, Nom d’un système de codage de caractères
disque ou mémoire centrale. Temps et d’idéogrammes dont l’ambition est
maximum de réalisation d’une opération de permettre de prendre en compte de
de lecture ou d’écriture. façon unifiée la plupart des langues
écrites du monde. Ce système fait la
temps de propagation
différence entre les codes-points, nombres
Durée qui s’écoule entre le moment où les entiers associés de façon unique à chaque
entrées d’un circuit changent et le moment caractère ou idéogramme, et l’encodage
où ses sorties prennent une nouvelle valeur de ces codes-points, suite d’octets qui les
stable. représentent en mémoire. UTF-8 est le
timer nom de l’encodage le plus répandu.
Circuit périphérique à un microprocesseur, unité centrale
qui lui fournit des impulsions à intervalles
Partie de l’ordinateur qui effectue
réguliers pour lui donner une unité de
l’interprétation et le traitement des
temps.
instructions.
transistor USB
Élément actif de base des portes logiques. Universal Serial Bus. Bus de connexion
Il est dans ce cas employé en mode de périphériques série. C’est un bus en
commutation, c’est à dire en tout ou rien. étoile, qui nécessite des hubs de connexion.
L’Intel 8088 comportait 29000 transistors ; Les périphériques peuvent être branchés à
le Pentium II 7,5 millions. chaud. Dans son mode lent, il permet des
Glossaire 241

échanges à 1.5 MBs

VLSI
Very Large Scale Integration. Technique de
réalisation de circuits intégrés qui permet
de mettre sur la même puce des millions
de transistors, en utilisant des méthodes
lithographiques.

Von Neumann (modèle de)


Organisation générale d’un ordinateur
caractérisée par la présence d’une unité
de calcul, une unité de contrôle et d’une
unique mémoire centrale qui contient à
la fois les instructions du programme et
les données manipulées. Voir Harvard
(modèle de).

x86
Terme générique désignant un membre
de la famille Intel dans la descendance
8080, 8085, 8086, 80186, 80286, …,
80x86 (x étant maintenant supérieur à 7).
À partir du 80586, Intel a commencé à
utiliser également le terme commercial de
Pentium.
Index
ABEL, 27 bus (suite)
addition, 37 de données, 195
additionneur synchrone, 102
carry-lookahead, 39 système, 2
complet, 37, 47 carte mère, 2
group carry-lookahead, 41 cartographie, 198
ripple-carry, 38 cercle des nombres, 13
adressage circuit
direct, 145 combinatoire, 18
immédiat, 145 séquentiel, 61
indirect, 145 CISC, 130
registre, 144 CMOS, 3, 6
adresse, 115 code
algèbre de Boole, 3, 18, 22 code point, 10
allocation, 146 de Gray de Gray, 14, 30
and, 21 combinaison interdite, 31
ASCII, 10 comparaison, 14
ASIC, 26 comparateur, 50
asynchrone, 63 compatibilité ascendante, 130
barrel shifter, 108 complément à 2, 12
bascule compteur, 86
D, 71 contrôleur de périphérique, 2
JK, 74 CPLD, 29
maître-esclave, 85 CRAPS, 129
synchrone, 69 débordement, 14
T, 73 décaleur à barillet, 108
big-endian, 130, 134 décodage, 198
binaire pur, 11 décodeur, 103
bistable, 62 décompteur, 86
bit, 3 décrémentation, 8
bloc, 160 demi-additionneur, 37
Boole, 3, 18 détecteur de séquence, 64
booléen, 18 directive, 145
branchement diviseur de fréquence, 88
absolu, 144 division, 48
conditionnel, 140 ECL, 5
inconditionnel, 140 EEPROM, 29, 117
relatif, 143 emprunt, 43
buffer 3-états, 99 encodeur de priorité, 105
bus, 2, 101, 187, 195 entrées-sorties, 163, 201
d’adresses, 195 EPROM, 117

242
Index 243

équation JTAG, 29
d’évolution, 73 Karnaugh, 30
Eratosthène, 184 kilo, 7
et, 21 langage
état langage machine, 2
équivalent, 67 assembleur assembleur, 129, 135
exception, 170, 205 machine, 129
extension de signe, 107 latch, 62, 70
FETCH, 210 lecteur, 11
flag, 111 little-endian, 130, 134
fonctionnel, 27 LSB, 7
FPGA, 26, 29 maître-esclave, 85
front majorité, 22
d’horloge, 63 mappé en mémoire, 133, 164
descendant, 63 masquage, 207
montant, 63 MDL, 27
GAL, 29 Mealy, 65, 77
génération de code, 159 méga, 7
gestionnaire d’exception, 170, 174 mémoire, 62, 198
giga, 7 centrale, 1
glitch, 32 dynamique, 117, 120
graphe flash, 29, 117
graphe d’état, 62 statique, 6, 117
graphe de Mealy, 65 microcommande, 209
graphe de Moore, 64 micromachine, 209
Gray, 30 minterm, 21, 30
handler d’exception, 174 MLI, 114
hardware, 129 mode d’adressage, 144
haute-impédance, 98 Moore, 64, 77
HDL, 26 mot, 6
hexadécimal, 9 MSB, 7
high-Z, 98 multiplexeur, 26, 99, 104
hold, 84 multiplicateur
horloge, 63 systolique, 47
IEEE 754, 6 multiplication, 47
implicant premier, 36 NAND, 23
essentiel, 36 niveau, 63
imprimeur, 11 niveau de priorité, 172
incrémentation, 8 non, 20
indicateur, 111 NOR, 23
instruction, 2 not, 20
de rupture de séquence, 140 octet, 6
synthétique, 138 opérateur
interpréteur, 129 booléen, 20
interruption, 171, 207 complet, 24
inverseur commandé, 25 logique, 20
ISA, 129 OR, 21
ISO_8859, 10 ou, 21
244 I NDEX

PALASM, 27 symbole, 147


parité, 24 synchrone, 63
PCI, 103 table
périphérique, 2 de Huffman, 66
pile, 153 de Karnaugh, 30
PLD, 26, 29 de transitions, 66
poids de vérité, 20
faible, 7 temps
fort, 7 temps d’accès, 118
pointeur de pile, 153 de hold, 84
porte de propagation, 4
et, 21 de setup, 84
logique, 20 théorème
non, 20 d’absorption, 22
ou, 21 de De Morgan, 22
processeur, 2 timer, 114, 166, 203
PROM, 117 tranche de bits, 112
PWM, 114, 166, 203 transistor, 5
Quine-Mc Cluskey, 34 trap, 170
RAM, 116 trois états, 98
récursif, 156, 162 TTL, 3, 5
réfléchi, 14 UAL, 111, 190
registre, 2, 90, 132, 186 UNICODE, 10
regroupement, 31 unité
RISC, 130 arithmétique et logique
ROM, 116 arithmétique et logique, 111, 190
scheduler de tâches, 176 de calcul, 2
SDRAM, 117 de contrôle, 2
séquenceur UTF-8, 10
séquenceur.cablé vecteur d’exception, 174
séquenceur.microprogrammé VHDL, 27
séquenceur, 209 VLSI, 5
servo-moteur, 168 Von-Neumann, 1
setup, 84 XOR, 24, 38, 44
Shannon, 18
SHDL, 26, 27
simplification, 31, 35
software, 129
sous-programme, 152
terminal, 153
soustracteur
complet, 44
ripple borrow, 45
soustraction, 43
SPARC, 131
stack-frame, 159
structurel, 27