Académique Documents
Professionnel Documents
Culture Documents
Concevoir Son Microprocesseur Structure Des Systèmes Logiques
Concevoir Son Microprocesseur Structure Des Systèmes Logiques
Jean-Christophe Buisson
Collection Technosup
Ellipses
Avant-propos
iv
Table des Matières
Avant-propos . . . . . . . . . . . . . . . . . . . . . . . iii
Références . . . . . . . . . . . . . . . . . . . . . . . . 219
vi
Annexe A. Tables diverses . . . . . . . . . . . . . . . . . . 220
Glossaire . . . . . . . . . . . . . . . . . . . . . . . . 233
Index . . . . . . . . . . . . . . . . . . . . . . . . . . 242
vii
Chapitre I
Principes généraux
mémoire centrale
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.
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.
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.
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
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’.
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
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.
76543210
10011010
S = 128+16+8+2=154
2
128
8
16
1 0 −1 −2 −3 −4 −5 −6
1 1 , 0 0 1 0 0 1 ...
2 0,125
0,015625
1
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.
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.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
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 :
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.
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
+ 66 + 01000010 + +66
Figure I.10. Une même opération ’mécanique’ d’addition s’interprète de deux façons dif -
férentes.
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
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
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.
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
Figure I.12. Construction récursive d’un code de Gray sur 2 bits, puis sur 3 bits.
6. Exercices corrigés
É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 :
On recommence avec 21553 : il contient 214 = 16384, et il reste 21553 - 16384 = 5169,
donc :
54321 = 1101.0100.0011.00012
54321 = D43116
É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.
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].
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 :
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.
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
A −
A
0 1
1 0
−
−
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
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
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
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 :
A B A↑ B
0 0 1
0 1 1
1 0 1
1 1 0
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
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
− −
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 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
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
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
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.
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).
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 :
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
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
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
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
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
(A,B,C,D) (a,b,c,d,e,f,g) f b
g
4 7 e c
d
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 *
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
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.
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.
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
−−
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
On place ensuite ces minterms dans un tableau, en les groupant selon le nombre de 1
qu’ils possèdent (figure II.26).
Figure II.26. On classe les minterms selon le nombre de 1 de leur valeur binaire.
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
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
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.
A B S R
0 0 0 0
0 1 1 0
1 0 1 0
1 1 0 1
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.
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.
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.
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.
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
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
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
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
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.
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.
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
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.
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
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.
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 :
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
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))
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 = 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.
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
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
É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.
É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
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
Énoncé
Concevoir les transcodeurs suivants :
1. Binaire pur vers Gray réfléchi.
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.
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
−
Z lorsque Y vaut 1, donc : C = Y ⊕ Z. On trouve de même D = Z ⊕ T.
É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
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
...
...
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
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
(R, S) = (0, 1)
(R, S) = (1, 0)
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.
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.
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
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.
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
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.
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.
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
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
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.
1 1
0
S=0
a 1 0 1
0 b c f
S=0 S=0 S=1
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.
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
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.
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
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
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
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.
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.
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 ;
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).
(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
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.
(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
É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.
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
(a) (b)
(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.
é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.
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.
1 1
0
S=0
a 1 0 1
0 b c f
S=0 S=0 S=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
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.
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.
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
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.
0/0 1/0
a 1 0/0
b c
1/1
0/0
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
X
Y 0 1
0 a b
1 c
Figure III.37. Assignation des états : chaque état est associé à une configuration binaire
des bascules.
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
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
front d’horloge
t
setup hold
temps de propagation
Figure III.43. Si la bascule a est trop rapide, le temps de hold de b sera violé.
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.
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.
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).
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
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.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
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
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
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).
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.
É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 :
2 appuyer sur A,
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
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
X
Y 0 1
0 a b
1 e c
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
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
3 M = XY
É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.
É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
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
A 0
S
B 1 C
A
A
C S S
B B
A=0 High−Z
C=1 S=1
B=1 1
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.
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
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).
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
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.
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
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
n
A 1 n
S
n
B 0
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
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
− −− −−−
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.
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.
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
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é.
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.
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
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
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
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 :
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.
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.
cpt 0 1 2 3 4 5 0
n3..n0 = 2
p = 6 (p3..p0 = 5)
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.)
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.
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).
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.
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
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.
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
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
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
m
ADR
amplificateurs
n n
Figure IV.37. Structure interne d’une mémoire dynamique de 22m mots de n bits.
É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).
É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
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
É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 :
É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
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
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
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.
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.
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
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.
• %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
• %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.
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).
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
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.
op Format 1 : call
0 1 disp30
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
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
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.
mov %ri,%rj copie %ri dans %rj orcc %ri, %r0, %rj
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
cmp %ri, %rj compare %ri et %rj subcc %ri, %rj, %r0
tst %ri teste nullité et signe de %ri orcc %ri, %r0, %r0
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
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.
• 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 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
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 :
%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 :
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
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 :
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.
1 l’adressage registre : la donnée est dans un registre, spécifié par son numéro. Par
exemple :
addcc %r1, %r2, %r3
Ici la deuxième donnée (2) est codée dans le champ simm13 de l’instruction.
%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
%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.
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.
.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.
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
.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
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
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
...
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
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 :
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 :
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
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 :
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
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
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 :
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.
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]
fond de
pile %r30
7
%r30
−8
−4 5 5
%r30
%r30
7 9
%r30
5 5
%r2
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.
Ces indications sont utiles notamment lorsqu’on débogue un programme et qu’on souhaite
savoir précisément ce que la pile contient.
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.
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
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.
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
1 les valeurs de registres que l’on souhaite conserver au travers de cet appel.
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.
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.
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.
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.
STACK = 0x1000100
;; 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.
2 la valeur de retour,
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
pile
var. locale #m
...
var. locale #1
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.
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.
IO[3] 1K
Vcc
IO[2] 1K
CRAPS Vcc
IO[1] 220
IO[0] 220
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
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
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.
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
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.
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
31 16 15 0
registre
commande #1 N P
31 16 1 0
registre
commande #2 prediv
start
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
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
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.
DT = 64DH = 1, 28·10− 6s
;; 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 *
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 :
• bus error. Le processeur tente d’effectuer un accès mémoire à une adresse où aucune
mémoire n’est implantée.
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 :
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.
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
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
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
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.
simplification de celle du SPARC, mais en conserve les traits les plus importants.
• ET = 1 (trap enable = 1) et
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.
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.
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.
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.
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
É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.
É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 :
2 appuyer sur A,
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; 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
À chaque état dans le graphe correspondra une position dans le programme. Cela
conduit à l’algorithme suivant :
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.
É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
.org RAM
STR .byte ’azertyuiop’,0 ; chaîne de test
É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 ;
.org RAM
STR .byte ’azertyuiop’,0 ; chaîne de test
1. Les registres
CRAPS possède 32 registres banalisés pour le programmeur : %r0, …, %r31, ainsi que
des registres utilisés pour son fonctionnement :
• %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 :
• %tbr : %r60
• %psr : %r61
• %pc : %r62
• %ir : %r63
186
1. Les registres 187
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
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
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.
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.
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 :
La figure VI.3 montre sur le bit 23 (flag N) comment cela peut être implémenté.
= 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;
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.
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.
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.
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.
mul32(a15..a0,b15..b0:s31..s0,Z)
• 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.
shift32(a31..a0,sens,nb4..nb0:s31..s0)
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
// 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);
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
• 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.
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
• les adresses sont des numéros d’octets (alors qu’on manipule des mots de 32 bits, de 4
octets de large),
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.
La structure du sous-système mémoire qui met en oeuvre cette cartographie est donnée
figure VI.12.
RAM = /abus31*/abus30*...*/abus25*abus24
200 CHAPITRE VI. CONSTRUCTION DU PROCESSEUR 32 BITS CRAPS
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 :
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.
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 :
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.
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
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
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
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.
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
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)
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.
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
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.
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
N,Z,V,C
excep_req
CLK
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
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.
op = 10, %ir[13]=1 /
read=0, write=0, excep_mode=0
cmd_ual=SIGNEXT13
... areg=63, breg=0, dreg=32
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.
op3 = ld /
read=1, write=0, excep_mode=0 read
cmd_ual=ADD
pc+4 areg=32, breg=0, dreg=rd
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
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.
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
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
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.
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
9. Exercices corrigés
9. Exercices corrigés 217
É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;
RAM=CS*/adr15*/adr14*/adr13*/adr12*/adr11*/adr10*adr9*/adr8;
Énoncé
Concevoir un circuit d’entrées/sorties au fonctionnement simplifié :
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
W opération
0 lire l’état des entrées
1 écrire sur les sorties
[2] Hennessy and Patterson. Computer Organisation and Design. Morgan Kaufman.
Second Edition, 1998.
[3] J.-J. Schwarz. Architecture des Ordinateurs. Eyrolles. 2nd Edition, 2005.
[6] John F. Wakely. Digital Design. Prentice Hall. 5th Edition, 2000.
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
220
Annexe B
CRAPS : guide du programmeur
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.
mov %ri,%rj copie %ri dans %rj orcc %ri, %r0, %rj
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
cmp %ri, %rj compare %ri et %rj subcc %ri, %rj, %r0
tst %ri teste nullité et signe de %ri orcc %ri, %r0, %r0
221
222 ANNEXE B. CRAPS : GUIDE DU PROGRAMMEUR
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
op Format 1 : call
0 1 disp30
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
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
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
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
233
234 GLOSSAIRE
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.
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