Concevoir son microprocesseur structure des systèmes logiques
Jean-Christophe Buisson
Collection Technosup
Ellipses
Avant-propos Ce livre s’adresse aux étudiants en informatique de licence et maîtrise, aux élèves ingénieurs, ainsi qu’aux professionnels de l’informatique - programmeurs ou autres soucieux de comprendre le fonctionnement des systèmes logiques présents dans les dispositifs électroniques qui nous entourent, et en particulier les microprocesseurs et les microcontrôleurs. Il est basé sur le cours, les travaux dirigés et les travaux pratiques d’architecture des ordinateurs dispensés en 1ère année du département informatique de l’Ecole Nationale Supérieure d’Electronique, d’Informatique, d’Hydraulique et de Télécommunications (ENSEEIHT) de Toulouse, et il est le fruit de plus de 15 ans d’expérience dans ce domaine. Le livre est organisé en deux grandes parties. La première (chapitres 1 à 3) présente d’abord toutes les notions de base préalables à la compréhension : le système de numération binaire et son utilisation dans le codage des nombres entiers naturels et relatifs, des caractères et des nombres réels ; ses propriétés dans les opérations d’addition, de comparaison, etc. On décrit ensuite les atomes de base des systèmes logiques que sont les portes et les bascules, et on décrit les méthodes d’analyse et de synthèse qui permettent d’assembler de tels éléments pour former des modules de complexité croissante. Le lecteur est ainsi amené à créer des circuits combinatoires et séquentiels, en employant des méthodes réutilisables dans des contextes variés. On construit des encodeurs et des décodeurs, des compteurs, des séquenceurs de différents types pour automatismes, etc. On examine également en détail des modules qui seront utilisés dans des microprocesseurs : comparateurs, unités arithmétiques et logiques, multiplexeurs, mémoires, etc. Dans toute cette partie, l’accent est mis sur la modularité et la réutilisabilité, notion aussi importante ici que dans les disciplines logicielles. La deuxième partie (chapitres 4 à 6) décrit le fonctionnement des microprocesseurs et des microcontrôleurs au travers d’un processeur 32 bits spécifique à ce livre appelé CRAPS, dont tous les organes sont des modules qui auront été étudiés dans la première partie. Son langage machine de type RISC est un sous-ensemble de celui du SPARC, et il est suffisamment riche pour pouvoir être utilisé dans des applications concrètes, ou pour qu’on y incorpore un système d’exploitation comme Linux. Le but de cette partie est de dévoiler le mystère de l’exécution d’un programme, qu’on pourra observer jusqu’à l’échelle du bit élémentaire. De nombreux programmes écrits en assembleur CRAPS/SPARC sont fournis, qui permettent de mettre en oeuvre des aspects importants de la vie d’un processeur : déclenchement et prise en compte d’une interruption, dialogue avec des périphériques d’entrées-sorties, appels de sous-programmes récursifs, etc. Au delà de la simple compréhension, cet ouvrage a pour objectif de permettre la conception de systèmes logiques digitaux, et il en fournit tous les moyens. De nombreuses méthodes de conception sont présentées, parfois algébriques ou tabulaires, parfois fonctionnelles ou intuitives ; pour chacune, plusieurs exemples détaillés sont fournis, qui appliquent les techniques présentées à des problèmes concrets, et qui donnent au lecteur la possibilité de se construire un véritable savoir-faire dans ce domaine. Un outil logiciel de simulation appelé SHDL est présenté, libre et gratuit, à la fois basé sur un langage structurel
de description matérielle, et qui permet de voir fonctionner les dispositifs conçus. Un second outil logiciel libre appelé CRAPSEMU permet de programmer le processeur décrit avec de nombreux exemples fournis, afin que le lecteur puisse s’imprégner correctement des modes de fonctionnement du processeur CRAPS. Enfin, des éléments concrets sont fournis au lecteur pour lui permettre d’implémenter tous les circuits décrits, jusque et y compris le processeur CRAPS, dans des circuits logiques programmables de type CPLD ou FPGA, maintenant utilisables à l’aide de cartes d’expérimentation à moindre coût.
iv
Table des Matières Avant-propos
. . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . Chapitre I. Principes généraux 1. Organisation générale d’un ordinateur . . . 2. Bits et signaux électriques . . . . . . . 3. Technologies de fabrication des circuits intégrés 4. Mots binaires . . . . . . . . . . . 5. Codage des principaux types de données . . 6. Exercices corrigés . . . . . . . . . .
. . . . . . .
. . . . . . .
iii
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
1 1 3 5 6 10 15
Chapitre II. Éléments de logique combinatoire . . . . . . 1. Circuits combinatoires . . . . . . . . . . . 2. Tables de vérité . . . . . . . . . . . . . . 3. Algèbre et opérateurs de base . . . . . . . . . 4. Autres portes logiques . . . . . . . . . . . . 5. SHDL, un langage de description matérielle pédagogique 6. Circuits logiques reconfigurables ; PLD et FPGA . . . 7. Méthodes de simplification des fonctions combinatoires 8. Circuits combinatoires réutilisables . . . . . . . 9. Exercices corrigés . . . . . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
18 18 20 20 23 26 28 30 37 56
Chapitre III. Éléments de logique séquentielle . . . . . . 1. Définition . . . . . . . . . . . . . . . . 2. Latch RS . . . . . . . . . . . . . . . . 3. Fronts et niveaux ; signaux d’horloge . . . . . . . 4. Circuits séquentiels synchrones et asynchrones : définitions 5. Graphes d’états . . . . . . . . . . . . . . 6. Tables de transitions . . . . . . . . . . . . 7. Bascules synchrones . . . . . . . . . . . . 8. Synthèse d’un circuit séquentiel synchrone . . . . . 9. Chronologie d’un circuit séquentiel synchrone . . . 10. Circuits séquentiels réutilisables . . . . . . . . 11. Exercices corrigés . . . . . . . . . . . .
. . . .
. . . . . . . . . . . .
. . . . . . . . . . . .
. . . . . . . . . . . .
. . . . . . . . . . . .
61 61 62 63 63 64 66 69 77 84 85 92
. . . . . . .
. . . . . . . . . . . .
. . . . . . . . . . . .
98 98 101 103 104 105 107 108 111 114 115 122
. . Chapitre V. Programmation du processeur 32 bits CRAPS/SPARC 1. Le langage machine, à la frontière entre logiciel et matériel . . . 2. Ressources du programmeur CRAPS/SPARC . . . . . . . . 3. Directives d’un programme assembleur . . . . . . . . . .
. . . .
129 129 131 145
Chapitre IV. Éléments fonctionnels d’un processeur 1. Sorties haute-impédance . . . . . . . 2. Les bus parallèles . . . . . . . . . 3. Décodeurs . . . . . . . . . . . 4. Multiplexeurs . . . . . . . . . . 5. Encodeurs de priorité . . . . . . . . 6. Circuit d’extension de signe . . . . . . 7. Décaleur à barillet . . . . . . . . . 8. L’unité arithmétique et logique . . . . . 9. Circuit timer/PWM . . . . . . . . 10. RAMs et ROMs . . . . . . . . . 11. Exercices corrigés . . . . . . . .
4. Exemple de programme : crible d’Ératosthène 5. Appels de sous-programmes terminaux . . 6. Utilisation de la pile . . . . . . . . 7. Techniques de programmation diverses . . 8. Langages à structure de blocs et ’stack-frames’ 9. Programmation des entrées/sorties . . . 10. Programmation des exceptions . . . . 11. Exercices corrigés . . . . . . . .
. . . . . . . . . . . .
. . . .
. . . . . . . . . . .
. . . . . . . . . . . .
. . . . . . . . . . . .
. . . . . . . . . . . .
. . . . . . . . . . . .
. . . . . . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
148 152 153 158 159 163 170 178
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
186 186 190 195 198 201 203 205 209 216
. . . . . . . . . . . . . . . . . . . . . . . .
219
Chapitre VI. Construction du processeur 32 bits CRAPS 1. Les registres . . . . . . . . . . . . 2. L’unité arithmétique et logique . . . . . . 3. Les bus du processeur CRAPS . . . . . . 4. Structure du sous-système mémoire . . . . 5. Structure du circuit d’entrées/sorties . . . . 6. Structure du circuit timer/PWM . . . . . 7. Structure du sous-système d’exceptions . . . 8. Séquencement des instructions . . . . . . 9. Exercices corrigés . . . . . . . . . . Références
. . . . . . . . . . . .
. . . . . . . . .
vi
Annexe A. Tables diverses
. . . . . . . . . . . . . . . . . .
Annexe B. CRAPS : guide du programmeur . . B.1. Instructions synthétiques . . . . . B.2. Jeu d’instructions du processeur CRAPS B.3. Tables des branchements conditionnels B.4. Format binaire des instructions de CRAPS B.5. Directives de l’assembleur CRAPS . B.6. Cartographie mémoire de CRAPS . . B.7. Programmation des entrées/sorties . . B.8. Programmation du timer . . . . . B.9. Liste des exceptions . . . . . . .
. . . . . . . . . .
221 221 223 228 228 230 231 231 231 232
. . . . . . . . . . . . . . . . . . . . . . . .
233
. . . . . . . . . . . . . . . . . . . . . . . . . .
242
Glossaire Index
vii
. . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
220
Chapitre I Principes généraux
1. Organisation générale d’un ordinateur 1.1. Le modèle de Von Neumann La plupart des ordinateurs ont l’organisation générale de la figure I.1. mémoire centrale
adr#1 adr#2 adr#3
instruction #1 instruction #2 instruction #3
processeur = unité de contrôle et de calcul
instruction
registres adr#4 adr#5
donnée #1 donnée #2
r1 r2 r3
données
données
données
contrôleurs de périphériques
périphériques
Figure I.1. Organisation générale d’un ordinateur; les flèches sont des chemins de données. Cette organisation est dite de ’Von-Neumann’, car elle ressemble beaucoup à la machine conçue par John Von Neumann sur la machine IAS peu après la deuxième guerre mondiale. Elle suppose et implique un certain nombre de propriétés caractéristiques des ordinateurs actuels : La mémoire est un composant central ; c’est un tableau de mots de taille fixe On voit sur le schéma que la mémoire centrale est au départ et à l’arrivée de l’exécution d’un programme. C’est elle qui contient le programme à exécuter et les données
1
2
CHAPITRE I. PRINCIPES GÉNÉRAUX
initiales ; c’est dans elle que seront stockés les résultats intermédiaires ou finaux. C’est un tableau de mots binaires de taille fixe. Chaque mot est repéré par un nombre appelé adresse, et les programmes feront référence aux données et aux instructions en mémoire par leurs adresses. Le programme exécuté est dans la mémoire centrale Le programme qu’exécute un ordinateur n’est pas sur le disque dur, comme le pensent beaucoup de gens. Une copie du programme est effectivement souvent stockée sur un CDROM ou un disque dur, mais ce n’est pas avant d’avoir été copié en mémoire centrale, comme le serait n’importe quelle autre ensemble de données, que son exécution peut commencer. Un programme qu’exécute un ordinateur est composé d’instructions sous forme de mots mémoire. Le langage utilisé pour coder les instructions en mots binaires est appelé langage machine, c’est le seul que comprenne le processeur. La taille des instructions varie beaucoup selon le type des processeurs. Parfois toutes les instructions ont la même taille, égale à un ou plusieurs mots mémoire ; parfois elles ont des tailles variables selon la complexité de l’instruction. Le processeur est le coordonnateur de l’exécution Le processeur contrôle l’exécution des programmes, en organisant le chargement des instructions et le détail de leur exécution ; c’est aussi lui qui effectue tous les calculs, notamment sur les nombres entiers. Ces deux fonctions sont associées dans la même unité car le contrôle de l’exécution nécessite souvent des calculs entiers, lorsque par exemple il faut faire une somme pour déterminer l’adresse de l’instruction suivante. Le processeur a besoin de garder la trace d’un certain nombre d’informations pendant l’exécution des instructions : l’adresse de l’instruction en cours par exemple, ou le résultat de la dernière opération arithmétique. Il utilise pour cela des registres, qui sont des mots mémoire fixes, à accès très rapide. Le processeur ne dialogue pas directement avec les périphériques On voit sur la figure que le processeur dialogue avec des contrôleurs de périphériques et non avec les périphériques eux-même. Par exemple un processeur n’a aucune conscience de l’existence d’un disque dur ; il dialogue avec un contrôleur de disque, à l’aide d’instructions génériques d’entrées/sorties.
1.2. Organisation en bus Les flèches entre les composants de la figure I.1 étaient à prendre dans un sens fonctionnel; si elles devaient représenter effectivement les chemins par lesquels transitent les données, le fonctionnement serait très inefficace. En pratique, les données circulent entre les différents sous-systèmes d’un ordinateur sur des bus, sortes d’autoroutes, et des circuits spécialisés jouent le rôle d’échangeurs aux carrefours de ces bus, de façon à permettre des transferts simultanés dans certaines circonstances. La figure I.2 montre une organisation typique d’un système de type compatible PC. Un circuit appelé north-bridge règle les échanges à très grande vitesse, notamment entre le processeur et la mémoire centrale. Il exploite de façon synchrone le bus FSB (front side bus), encore appelé bus système, dont la vitesse est très corrélée à la puissance générale
1. Organisation générale d’un ordinateur
cache niveau 2
64/256 bits
3
processeur 64 bits 66/100/133/200 MHz
FSB 64 bits 66/100/133/200 MHz mémoire RAM
bus mémoire
north bridge 533/800 Mo/s LAN
IDE south bridge USB
PCI−E PCI
Figure I.2. Architecture générale des bus d’un ordinateur compatible PC. de l’ensemble. Un second circuit appelé south-bridge effectue quant à lui des échanges moins rapides avec des périphériques SATA, réseau, etc. La figure I.3 montre une photo de carte mère typique, avec l’emplacement physique des différents bus. La mémoire et le north bridge sont placés très près du processeur, car ils sont sur les bus les plus rapides. Le north-bridge est équipé d’un radiateur à cause de l’intensité du travail de transfert qu’il réalise.
2. Bits et signaux électriques L’information est stockée et véhiculée dans un ordinateur, logiquement sous forme de chiffres binaires ou bits (binary digit), et physiquement sous forme de signaux électriques. La logique binaire est utilisée parce qu’il est plus facile de construire des systèmes électriques possédant deux états stables, et parce qu’on a maintenant beaucoup de résultats mathématiques en algèbre de Boole, mais des implémentations sur des systèmes possédant plus de deux états stables sont envisageables. Ces deux états sont notés ’0’ et ’1’ ; ils correspondent le plus souvent aux deux tensions électriques 0v et +Vcc respectivement, Vcc valant exactement +5v pour des circuits utilisant la technologie TTL, et une valeur comprise entre 2v et 15v environ en technologie CMOS (des valeurs de Vcc=3.3v, 2.5v et 1.8v étant utilisée de plus en plus souvent). Pour tenir compte du fait que ces signaux électriques vont être déformés lors des transmissions, des plages de valeurs plus larges ont été affectées à ces états. Par exemple, en technologie TTL, elles sont représentées figure I.4. Il faut noter que ces assignations sont légèrement différentes selon qu’une tension est considérée en entrée ou en sortie d’un circuit. Les circuits logiques qui sont utilisés pour combiner et transmettre les signaux électriques logiques ont des caractéristiques non linéaires qui ont entre autres pour fonction de remettre en forme un signal déformé par le bruit. On voit figure I.5 une porte ’non’ qui
CHAPITRE I. PRINCIPES GÉNÉRAUX
4
Figure I.3. Carte mère typique. Les bus rouges (FSB et mémoire), très rapides, sont gérés par le north bridge ; les bus verts (PCI, SATA, etc.), plus lents, sont gérés par le south bridge. + 5v
+ 5v
’1’ logique
’1’ logique
2.4v
2.4v
interdit
interdit 0.8v
0.4v 0v
’0’ logique
(a)
’0’ logique 0v (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
S
tpHL
Figure I.5. Remise en forme d’un signal après traversée d’une porte ’non’.
3. Technologies de fabrication des circuits intégrés Les premiers ordinateurs ont été construits durant la deuxième guerre mondiale avec des tubes cathodiques (des ’lampes’) comme dispositif de commutation. En 1947, les laboratoires Bell inventent le premier transistor, qui va très vite supplanter la lampe et apporter un facteur de réduction de taille important. Mais la véritable révolution n’interviendra qu’avec l’invention des circuits intégrés VLSI dans les années 1970, qui amèneront à la réalisation des premiers microprocesseurs. Un grand nombre de technologies de fabrication de circuits intégrés existent, qui se distinguent par des qualités différentes en termes de vitesse de propagation, densité d’intégration, consommation et dissipation thermique. On va ébaucher rapidement dans cette section les trois familles les plus connues, en terminant par celle qui est actuellement la plus répandue et la plus significative, la famille CMOS. Technologie TTL La technologie TTL était la technologie standard utilisée à partir des années 1960 pour la réalisation de tous les types d’ordinateurs. Elle s’alimente typiquement en +5V, a une consommation modérée, est robuste et a des règles d’interface simples. Une très grande famille de circuits spécialisés, la famille 7400, est disponible sous forme de circuits en boîtier DIL, et fournit des modules tout prêts : portes, bascules, compteurs, etc. Les capacités d’intégration de circuits TTL sont faibles, et on ne peut guère mettre plus de quelques milliers de portes TTL sur une puce de circuit intégré. On l’utilise encore aujourd’hui pour des petites réalisations, notamment dans l’interfaçage avec d’autres circuits. Technologie ECL La technologie ECL est caractérisée par sa très grande rapidité, mais aussi par une consommation de courant élevée. Elle est de plus assez difficile à utiliser, notamment en raison de l’emploi de tensions d’alimentation négatives. Elle est utilisée dans des petites réalisations où la vitesse est critique, mais certains experts prévoient un développement de
CHAPITRE I. PRINCIPES GÉNÉRAUX
6
la technologie ECL avec l’utilisation de nouveaux types de semi-conducteurs. Technologie CMOS C’est la technologie reine actuelle. La technologie CMOS a d’abord été développée et vendue comme une alternative au TTL, plus lente mais avec une consommation réduite. Comme par ailleurs elle peut utiliser des tensions d’alimentation faibles (jusqu’à 1V), elle a immédiatement été très utilisée par les fabricants de montres digitales, pour qui la vitesse de traitement importait peu par rapport à la consommation. Mais on a progressivement réalisé que sa grande qualité était son taux d’intégration très élevé ; de plus, des tailles de transistors de plus en plus petites ont amenées avec elles des temps de commutation de plus en plus faibles. C’est la technologie CMOS qui est utilisée actuellement pour la fabrication de tous les processeurs et microcontrôleurs du marché, ainsi que pour toutes les mémoires statiques et les mémoires flash. Par ailleurs, un circuit CMOS ne consomme significativement du courant que lors des commutations des transistors. Cette propriété a été à l’origine de circuits qui consomment moins de courant que leur équivalent TTL, qui eux en consomment au repos et lors des commutations. Néanmoins, au fur et à mesure qu’on diminue leur tension d’alimentation, les courants de fuite des transistors CMOS deviennent de plus en plus proches des courants de commutation, et par conséquent la consommation de ces circuits au repos n’est plus aussi négligeable qu’auparavant.
4. Mots binaires Les signaux binaires sont souvent regroupés pour former des mots. La largeur d’un mot binaire est le nombre des signaux qui sont regroupés. On appelle par exemple octet un mot de 8 bits (un mot de largeur 8). Un mot de n bits permet de coder 2n valeurs différentes ; 256 valeurs par exemple pour un octet. On peut utiliser un octet pour coder un caractère alphanumérique à la norme ISO-8859 ; un mot de 32 bits pour représenter un nombre entier relatif ; un mot de 16 à 96 bits pour représenter des nombres réels en notation scientifique flottante selon la norme IEEE 754. Des structures de données plus complexes (tableaux, listes, ensembles, dictionnaires, etc.) nécessitent une agrégation de ces types simples. Codage en binaire pur On écrira les nombres binaires avec le chiffre 2 en indice. Par exemple : 011011012. Sur n bits, l’intervalle des valeurs représentées est [0, 2n − 1]. Comme en décimal, le total est une somme pondérée, plus précisément le produit de chaque chiffre par la puissance de 2 de même rang : s=
n−1
∑ 2i bi
i=0
où bi est le bit de rang i du mot. Par exemple, on a représenté sur la figure suivante les poids respectifs des bits du mot de 8 bits 100110102 = 15410 :
4. Mots binaires
7 76543210
10011010 S = 128+16+8+2=154 2 128
8 16
Poids forts, poids faibles Par référence à cette notion de pondération des chiffres, on emploie couramment des expressions telles que ’les n bits de poids les plus forts’, ou ’les n bits de poids les plus faibles’ pour désigner les n bits les plus à gauche (respectivement les plus à droite) dans un mot binaire. Dans les documentations en anglais, on trouvera fréquemment des libellés tels que MSB (most significant bits) ou LSB (least significant bits) avec les même sens. 1 Kilo n’est pas 1000 (en informatique) Lorsque vous achetez un kilowatt-heure d’électricité, il est bien entendu avec votre fournisseur qu’il s’agit de 1000 watts-heure. Ce n’est pas la même chose en informatique ! Pour continuer à énumérer en utilisant la base 2, les informaticiens ont décidé d’utiliser l’unité 1K = 210 = 1024, donc un peu plus de 1000. Le suffixe ’méga’ est : 1M = 1K ×1K = 220 ; un ’giga’ est : 1G = 1K×1M = 230; un ’Tera’ est : 1T = 1K×1G = 240. Un grand nombre de valeurs usuelles s’expriment alors directement en multiples de 1K, 1M, 1G ou 1T, par exemple la taille des mémoires RAM, car elle est nécessairement une puissance de 2 à cause de leur organisation matricielle. 1K, 1M et 1G valent environ mille, un million et un milliard, mais la petite différence entre 1000 et 1K augmente lorsqu’on passe à 1M et 1G : 1M = 1024 * 1024 = 1 048 576, et 1G = 1024 * 1048576 = 1 073 741 824, soit presque 1,1 milliard. Les vendeurs de disques durs exploitent cette ambiguïté : le plus souvent si vous achetez un disque étiqueté ’100 giga-octets’, il s’agit d’un disque de 100 milliards d’octets, et non de 107 374 182 400 octets, soit une différence de 7%. 64 bits, la taille idéale pour les données ? Les microprocesseurs manipulent des mots binaires de taille fixe, les plus petits de 8 bits, d’autres de 16 bits, 32 bits et maintenant 64 bits. Un processeur 8 bits, s’il veut ajouter 1000 et 1000, doit effectuer deux additions et non une seule, puisque 1000 ne ’rentre’ pas dans un codage 8 bits. Un processeur 16 bits code directement des nombres entiers de l’intervalle [-32768,+32767], ce qui ne permet pas de coder des nombres un tant soit peu grands. On semble définitivement à l’aise avec 32 bits, mais ce n’est pas le cas : le plus grand entier qui peut être codé est 232, soit environ 4 milliards, ce qui ne suffit pas à représenter le montant en euros d’une journée de transactions à la bourse de Paris. Avec 64 bits, on est définitivement sauvés côté entiers, à moins de vouloir compter les grains de sable sur la plage. Pour le codage des nombres réels, 64 bits est la taille de codage en double précision la plus usuelle, qui donne une précision d’une dizaine de chiffres après la virgule, suffisante dans la majorité des applications. Comme on doit aussi souvent stocker des adresses de mémoire dans des mots, 64 bits est aussi une taille suffisante, alors que 32
CHAPITRE I. PRINCIPES GÉNÉRAUX
8 bits ne permettent pas de dépasser 4 giga-octets.
Une taille de mot de 64 bits a donc des qualités intrinsèques que n’avaient pas jusque là 16 bits ou 32 bits. Utiliser une taille plus grande de 128 bits dans un processeur conduirait à beaucoup de gaspillage, puisque la plupart des mots manipulés seraient très peu remplis. Ma prédiction personnelle est que, dans le futur, cette taille du mot mémoire ne va pas augmenter indéfiniment, mais va se stabiliser à 64 bits du fait des qualités qui viennent d’être décrites. Nombres binaires à virgule Comme en décimal, on peut manipuler des nombres à virgule en binaire. Les chiffres placés après la virgule ont alors des poids qui sont les puissances négatives décroissantes de 2. Ainsi, 0.12 = 2 − 1 = 0.5 ; 0.012 = 2 − 2 = 0.25, etc. Si on cherche la représentation binaire approchée du nombre π, on trouvera π = 11.001001… (figure I.7). 1 0 −1 −2 −3 −4 −5 −6
1 1 , 0 0 1 0 0 1 ... pi = 2 + 1 + 0.125 + 0.015625 + ... 2
0,125 1
0,015625
Figure I.7. Écriture binaire à virgule (approchée) du nombre Pi. Pour chaque position, on met 1 ou 0 de façon à ce que la valeur courante ne dépasse pas la valeur à représenter, tout en s’en approchant le plus possible. Par exemple après avoir choisi 11.001 (total courant = 21 + 20 + 2 − 3 = 2 + 1 + 0.125 = 3.125), on ne peut pas mettre de 1 en position -4 ou -5, car cela dépasserait π. On doit attendre la position -6, et on a une valeur approchée de 21 + 20 + 2 − 3 + 2 − 6 = 3.140625. L’annexe A fournit les valeurs des premières puissances négatives de 2. Incrémenter et décrémenter en binaire Une opération fréquente effectuée sur des nombres binaires est le comptage. Par exemple sur 3 bits, passer de 000 à 001, puis 010, etc. Selon quel algorithme une valeur se transforme-t-elle en la suivante ? On peut essayer de s’inspirer du comptage en décimal, qu’on sait faire depuis notre petite enfance, et pour lequel on ne nous a pourtant jamais donné l’algorithmique. Pour passer de 124 à 125, seul le chiffre des unités s’est incrémenté.En fait ce chiffre s’incrémente toujours, avec un passage de 9 à 0. Le chiffre des dizaines sera recopié avec incrémentation, si le chiffre des unités vaut 9, comme dans le passage de 129 à 130, sinon il sera recopié sans changement, comme dans le passage de 124 à 125. On tient notre algorithme : pour passer d’un nombre décimal à son suivant, on considère chaque chiffre un par un, et on le recopie en l’incrémentant si tous les chiffres qui sont à sa droite valent 9, ou sinon on le recopie sans changement. On remarquera que l’ordre de traitement sur les chiffres est indifférent, et qu’on peut effectuer cet algorithme en parallèle sur tous les chiffres. La figure I.8 montre un
4. Mots binaires
9
exemple. 1
2 des chiffres à droite différents de 9 on recopie sans changer
1
9
9
tous les chiffres à droite valent 9
tous les chiffres à droite valent 9
on recopie en incrémentant
on recopie en incrémentant
3
0
toujours incrémente
0
Figure I.8. Incrémentation d’un nombre décimal.Un chiffre est recopié avec incrémentation si tous les chiffres à sa droite valent 9 ; sinon il est recopié sans changement. Le même algorithme s’applique en binaire, mais cette fois l’opération d’incrémentation d’un chiffre est encore plus simple, car elle se résume à une inversion du bit. Pour incrémenter un nombre binaire, on considère chaque bit du mot initial, et on le recopie avec inversion si tous les bits qui sont à sa droite valent 1, ou on le recopie sans changement sinon. Le bit le plus à droite est toujours inversé. La figure I.9 montre un exemple. 1
0 des bits à droite différents de 1 on recopie sans changer
1
1
1
1
tous les bits à droite valent 1
tous les bits à droite valent 1
on recopie en inversant
on recopie en inversant
0
toujours inversion
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,
CHAPITRE I. PRINCIPES GÉNÉRAUX
10
on utilise souvent la base 16, appelée hexadécimal, dont les 16 chiffres sont notés : 0,1,2,3,4,5,6,7,9,A,B,C,D,E,F.Chaque chiffre hexadécimal permet de coder 4 bits (24 = 16). Pour passer d’une notation binaire à une notation hexadécimale, il suffit de grouper les bits par paquets de 4, de droite à gauche à partir des poids faibles. Par exemple : 0110.11012 = 6D16 = 10910 0110.1000.1010.11002 = 68AC16 Si l’on considère le dernier exemple, on peut montrer le regroupement par groupes de 4 bits en utilisant une notation matricielle : 0110.1000.1010.11002 =
214 × 213 + 1 0 2 212 223 2 12 0 ×2 × 1 + 2 20
0 1 1 0
= 0 1 1
0 0
210 2 × 9 + 2 28 11
15
1 0 0 0
26 × 25 + 2 24 7
1 0 1 0
223 × 28× 21 + 2 20
1 0 1 0
1 1 0 0
223 × 24× 21 + 2 20
223 × 21 2 20
1 1 0 0
223 × 20 × 21 2 20
en introduisant les puissances de 16 : 0110.1000.1010.11002 =
0 1 1 0
223 × 163× 21 + 2 20
1 0 0 0
223 × 162 × 21 + 2 20
1 0 1 0
223 × 161× 21 + 2 20
= 6× 163 + 8× 162 + 10× 161 + 12× 160
1 1 0 0
223 × 160 × 21 2 20
= 68AC16
5. Codage des principaux types de données 5.1. Codage des caractères Les caractères usuels anglo-saxons utilisent généralement un code sur 7 bits appelé codage ASCII ; les codes varient entre 0 et 127. Comme ces codes de 7 bits sont toujours plongés dans un octet (mot de 8 bits), des codages étendus sur 8 bits ont été développés, d’abord de façon propriétaire, puis de façon organisée par la norme ISO-8859, qui est en fait un ensemble de normes spécifiques à différentes régions du monde : ISO-8859-1 (dite aussi ISO-Latin-1, la notre, qui couvre toute l’Europe de l’ouest, y compris les caractères espagnols, allemands et scandinaves), ISO-8859-2 (Europe de l’est), etc. Il y a actuellement 15 sous normes spécifiques ; chacune d’elles est un sur ensemble du codage ASCII ; pour les codes de 0 à 127. Par contre, l’aspect graphique et typographique fin des caractères (ligatures, etc.) n’est pas bien pris en compte par ces codages, qui ont été conçus essentiellement pour l’échange de données. Pour tenter de résoudre les problèmes de codage de toutes les langues écrites du
5. Codage des principaux types de données
11
monde, le consortium UNICODE, qui incorpore la plupart des grands constructeurs et éditeurs informatiques, a créé un encodage de caractères universel. Il est maintenant utilisé comme codage interne dans des langages tels que Java, dans les navigateurs Internet et de nombreuses applications ; son adoption généralisée permettrait de résoudre définitivement les disparités d’encodages de caractères entre pays. Chaque caractère possède un code point unique, noté par exemple U+03B3 (pour la lettre grecque minuscule gamma γ). Les codes-points sont dans l’intervalle U+0000 et U+10FFFF (plus d’un million d’entrées !) ; les 256 premiers codes-points sont identiques à l’ISO-8859-1, et d’autres mappings par blocs existent pour faciliter le passage d’anciens codes à UNICODE. La suite d’octets représentant un caractère n’est pas l’écriture binaire du code-point ; un encodage doit être utilisé, et plusieurs d’entre eux existent. Le plus répandu est sans doute l’UTF-8, qui représente chaque code-point par une suite de 1 à 4 octets. Les codes-points inférieurs à 0+0080 (les codes ASCII) sont représentés par un unique octet contenant cette valeur : l’UTF-8 est donc rétrocompatible avec l’ASCII et c’est cette qualité qui a provoqué sa diffusion rapide. Les autres codes-points sont encodés par un nombre d’octets entre 2 et 4 ; par exemple le caractère U+03B3, qui représente la lettre grecque minuscule gamma, est codé en UTF-8 : CE16 , B316. Les codes-points supérieurs à 0+FFFF, les plus rarement employés, sont encodés par une suite de 4 octets.
5.2. Codage des nombres entiers naturels On utilise le binaire pur, déjà décrit en section 4. Avec n bits, on peut coder les nombres entiers positifs de l’intervalle [0, 2n − 1]. L’addition et la soustraction s’effectuent de la manière habituelle ; il y a débordement de l’addition lorsqu’une retenue doit être propagée au delà du dernier bit de rang n. Lecteur et imprimeur décimal Si un programme utilise des nombres entiers naturels codés en binaire pur, la représentation externe de ce nombre à l’utilisateur du programme est généralement faite dans une autre base, le plus souvent la base 10. Un sous-programme appelé imprimeur est chargé de convertir une valeur en binaire pur en son écriture décimale, et un autre appelé lecteur fait la traduction inverse. On peut examiner rapidement les algorithmes qu’ils utilisent. Ils seraient bien sûr facilement transposables à d’autres bases. Traduction du binaire vers le décimal Considérons par exemple un mot de 8 bits N = 101101102 ; on cherche la suite des caractères qui représente ce nombre en écriture décimale. C’est un programme d’ordinateur qui réalise cette conversion, et qui dispose donc d’opérateurs arithmétiques (binaires) tels que addition et multiplications, qui sait faire des comparaisons, etc. Cette traduction ne comportera pas plus de 3 chiffres décimaux, notons les : XYZ. On choisit pour X le chiffre le plus grand tel que 100X ≤ N. Ici X = 1 convient car 1×100 ≤ N et 2×100 > N. Pour faire ce choix de X, on peut effectivement réaliser des multiplications, ou utiliser une table préconstruite. Il reste à traduire N 1 = N − X×100 = 10100102. Pour cette valeur, on cherche la plus grande des valeurs de Y telle que 10×Y ≤ N 1 ; on trouve Y = 8, et il reste à convertir N 2 = N 1 − Y ×10 = 000000102 ; on trouve alors Z = 2. Finalement, les trois chiffres décimaux cherchés sont XYZ = 182.
CHAPITRE I. PRINCIPES GÉNÉRAUX
12 Traduction du décimal vers le binaire
On cherche donc à convertir l’écriture décimale d’un nombre en un mot binaire. Le problème n’est pas le symétrique du précédent, car l’ordinateur sait faire toutes les opérations sur le codage binaire, alors qu’il ne sait en faire aucune directement en base 10. Pour un nombre décimal tel que ’XYZ’ (qui conduit à une valeur binaire sur 8 bits), il suffit en fait de faire le calcul n = 100x + 10y + z où x, y et z sont les numéros d’ordre associés aux chiffres ’X’, ’Y’ et ’Z’ respectivement.
5.3. Codage des nombres entiers relatifs Aux débuts de l’informatique, on codait généralement les nombres entiers signés en mettant dans le premier bit le signe (’1’ signifiant ’moins’) et dans les suivants la valeur absolue. -3 se codait par exemple 10000011 sur 8 bits. L’encodage et le décodage d’un tel nombre étaient simple (bien qu’on notera que 0 peut être codé +0 ou -0), mais on ne pouvait plus réaliser une simple addition comme avec des nombres naturels. Si par exemple on tente de faire l’addition bit à bit de 10000011 (-3) et de 00000100 (+4), on obtient : 10000111, c’est à dire -7, ce qui est absurde. Codage en complément à 2 Un codage appelé complément à deux s’est rapidement imposé dans les années 1970. Il utilise les deux règles suivantes : •
un nombre positif est représenté en binaire pur.
•
pour calculer la représentation de l’inverse arithmétique d’un nombre, on inverse tous ses bits et on ajoute 1, sans tenir compte de l’éventuelle retenue.
Considérons par exemple le codage des nombres relatifs sur 8 bits. +3 va être codé 00000011. Pour obtenir le codage de -3, on inverse tous les bits du codage précédent (00000011 → 11111100) et on ajoute 1, soit 11111101. Propriétés On peut prouver que ce codage a les propriétés remarquables suivantes : •
c’est un codage équilibré: sur n bits, on représente les nombres relatifs de l’intervalle [ − 2n − 1, 2n − 1 − 1] soit autant de nombre positifs ou nuls que de nombres strictement négatifs.
•
0 se code sous forme d’un champ uniforme de 0 ; il n’a qu’un seul codage.
•
le bit de poids fort du codage représente le signe du nombre.
•
les opérations d’addition ou de soustraction en binaire pur s’appliquent également avec des opérandes codés en complément à deux, avec la différence que la retenue n’a pas de sens et doit être négligée lors d’une opération en complément à deux.
Pour illustrer le fait que le même opérateur d’addition est utilisé pour le binaire pur et le complément à 2, considérons l’addition de la figure I.10.
5. Codage des principaux types de données
13
interprétation non signée (retenue prise en compte)
+
interprétation signée (retenue ignorée)
233
11101001
−23
66
+ 01000010
+ +66
00101011
+43
1
299
Figure I.10. Une même opération ’mécanique’ d’addition s’interprète de deux façons dif férentes. La même opération effectuée mécaniquement sur des codages (colonne centrale) s’interprète de deux façons différentes selon qu’on considère les nombres comme étant signés ou non. Cercle des nombres Les différentes propriétés du codage en complément à 2 peuvent être retrouvées avec la métaphore du cercle des nombres, ici représenté pour un codage sur 3 bits (figure I.11).
positifs négatifs soustraction
0 000
−1
1 001
111 −2 110
010 2
101 −3
011 100 −4
3 addition limite de franchissement de signe
Figure I.11. Cercle des nombres de 3 bits en complément à 2. On effectue une addition en parcourant le cercle dans le sens des aiguilles d’une montre, et une soustraction dans le sens inverse. Si on recherche le codage de -3, on part de 0 et on tourne de 3 positions dans le sens inverse des aiguilles d’une montre, pour trouver : 1012. Si on veut lui ajouter 5, on tourne de 5 positions dans le sens des aiguilles d’une montre, et on trouve : 0102, c’est à dire 2.
CHAPITRE I. PRINCIPES GÉNÉRAUX
14
Si on franchit la limite -4 / +3 pendant une opération, cela signifie qu’il y a eu débordement. Débordement Quand y a-t-il débordement lors d’une addition de deux nombres signés ? Il est clair qu’il ne peut pas se produire lorsque les opérandes sont de signes opposés, puisque la valeur absolue du résultat est alors inférieure à la plus grande des valeurs absolues des deux opérandes. Il ne peut donc se produire que s’ils sont de même signe, tous les deux positifs ou tous les deux négatifs. On démontre facilement qu’alors il y a débordement si et seulement si l’addition des deux donne un résultat de signe opposé au signe commun des deux opérandes. Graphiquement sur le cercle des nombres, cela se produit lorsqu’on traverse la frontière -4 → +3. Comparaison de nombres en complément à 2 On se pose ici le problème de savoir les positions respectives de deux nombres entiers codés en complément à 2 A et B : sont ils égaux, ou l’un est-il plus grand que l’autre ? Il faut examiner les signes des deux nombres à comparer A et B, c’est à dire leurs bits de poids forts. Si les signes sont différents, la comparaison est immédiate. Si les signes sont identiques, il suffit de comparer les n − 1 autres bits, l’ordre étant direct si leur signe commun est positif, inversé si leur signe commun est négatif. Écriture sous forme d’une somme pondérée du codage en complément à 2 Les propriétés remarquables du codage en complément à 2 peuvent être facilement démontrées si on remarque qu’en fait, il s’agit également d’une somme pondérée comme en binaire pur, mais avec un poids particulier de − 2n − 1 pour le bit de poids le plus fort de rang n − 1. n−1
s= −2
bn − 1 +
n−2
∑ 2i bi
i=0
où bi est le bit de rang i du mot. On peut maintenant démontrer pourquoi le premier bit est un bit de signe. Puisque 2i est toujours plus petit ou égal à 2n − 1 − 1, alors s < 0 si et seulement si bn − 1 = 1.
n−2
∑
i=0
5.4. Codes de Gray Un code de Gray sur n bits est tel que deux codes successifs ne diffèrent que d’un seul bit. On s’en sert par exemple pour encoder la position d’un axe en rotation, tel qu’un commutateur rotatif ou une girouette. Plusieurs codes de Gray sont possibles pour un nombre donné de bits ; on parle de code de Gray réfléchi lorsqu’il possède certaines propriétés de symétrie. Une méthode récursive pour construire un code de Gray réfléchi sur n + 1 bits à partir d’un code sur n bits est la suivante : •
sous les codes à n bits, on recopie la liste en miroir.
•
on ajoute un bit à gauche, valant 0 pour la première moitié, et 1 pour la deuxième.
5. Codage des principaux types de données
15
On a représenté figure I.12 la construction récursive par cette méthode des codes de Gray réfléchis à 2 puis 3 bits. Par construction, les codes ne diffèrent que d’un bit pour chacune des deux moitiés, au passage entre les deux moitiés, ainsi que de la dernière à la première ligne : c’est donc un code de Gray.
0 1
copie en miroir
code de Gray sur 3 bits
code de Gray sur 2 bits
code de Gray sur 1 bit 0
0
0
1
0
1
1
1
1
0
1
0
copie en miroir ajouts de 0 en première moitié et de 1 en deuxième
0
0
0
0
0
0
1
0
0
1
1
1
0
1
1
1
0
0
1
0
1
0
1
1
0
1
1
1
1
0
1
0
0
1
0
1 ajouts de 0 en première moitié 1 0 1 et de 1 en deuxième 0
Figure I.12. Construction récursive d’un code de Gray sur 2 bits, puis sur 3 bits.
6. Exercices corrigés 6.1. Exercice 1 : conversion Énoncé Écrire 54321 en binaire, puis en hexadécimal. Solution On peut se reporter à la table donnée en annexe A pour obtenir la liste des premières puissances de 2. La plus grande qui soit inférieure à 54321 est 215 = 32768. On peut écrire alors : 54321 = 215 + 21553. On recommence avec 21553 : il contient 214 = 16384, et il reste 21553 - 16384 = 5169, donc : 54321 = 215 + 214 + 5169. 5169 contient 212 = 4096, reste 1073 : 54321 = 215 + 214 + 212 + 1073. 1073 contient 210 = 1024, reste 49 : 54321 = 215 + 214 + 212 + 210 + 49. En continuant ce processus, on trouve :
CHAPITRE I. PRINCIPES GÉNÉRAUX
16 54321 = 215 + 214 + 212 + 210 + 25 + 24 + 20. L’écriture binaire est alors : 54321 = 11010100001100012
Pour trouver l’écriture hexadécimale, on regroupe les bits par paquets de 4 : 54321 = 1101.0100.0011.00012 On convertit ensuite chaque groupe en un chiffre hexadécimal équivalent : 54321 = D43116
6.2. Exercice 2 : arithmétique binaire Énoncé 1
Donner l’intervalle des valeurs possibles pour des nombres non signés codés en binaire pur sur 11 bits.
2
Donner l’intervalle des valeurs possibles pour des nombres signés codés en complément à 2 sur 11 bits.
3
Effectuer la somme en binaire : 11012 + 01112 et interpréter le résultat en arithmétique signée et non signée.
Solution 1
Intervalle des valeurs possibles en binaire pur sur 11 bits. Avec 11 bits, le nombre de combinaisons possibles est 211 = 2048. L’intervalle des valeurs est donc : [0, 2047].
2
Intervalle des valeurs possibles en complément à 2 sur 11 bits. On a vu dans le chapitre précédent que le codage en complément à 2 était équilibré, c’est à dire qu’il code autant de nombres strictement négatifs que de nombres positifs ou nuls. Pour respecter cet équilibre avec 2048 codes, il nous faut nécessairement 1024 codes positifs ou nuls et 1024 codes strictements négatifs, soit l’intervalle : [-1024, +1023].
3
Somme binaire 11012 + 01112.
En effectuant la somme bit à bit, on trouve 01002 avec 1 de retenue sortante. En arithmétique non signée, la retenue doit être prise en compte, et elle signale un débordement ; on peut tout de même intégrer ce bit de retenue comme bit de poids fort du résultat. On a donc fait la somme 13 + 7 et on trouve 101002, soit la valeur attendue 20. En arithmétique en complément à 2, la retenue est ignorée. Les nombres ajoutés sont -3 et +7 et le résultat est +4, ce qui est également correct.Un débordement était impossible
6. Exercices corrigés ici, car on ajoutait un nombre positif à un nombre négatif.
17
Chapitre II Éléments de logique combinatoire 1. Circuits combinatoires En 1854, Georges Boole publia son ouvrage séminal sur une algèbre manipulant des informations factuelles vraies ou fausses. Son travail a été redécouvert et développé sous la forme que nous connaissons maintenant par Shannon. Le nom de Boole est entré dans le vocabulaire courant, avec l’adjectif booléen qui désigne ce qui ne peut prendre que deux valeurs distinctes ’vrai’ ou ’faux’. Circuits combinatoires : définition Un circuit combinatoire est un module tel que l’état des sorties ne dépend que de l’état des entrées. L’état des sorties doit être évalué une fois que les entrées sont stables, et après avoir attendu un temps suffisant à la propagation des signaux. On comprend intuitivement que les circuits combinatoires correspondent à des circuits sans état interne : face à la même situation (les mêmes entrées), ils produisent toujours les mêmes résultats (les mêmes sorties). Un module d’addition en est un bon exemple : il a en entrées les signaux A et B à additionner, ainsi qu’une retenue CIN d’un étage précédent ; il fournit en sortie la somme S et une retenue COUT pour un étage suivant :
Si un circuit combinatoire est composé lui-même de sous-circuits combinatoires, on démontre que la définition de base est équivalente au fait que ces sous-circuits sont connectés entre eux sans rebouclages. Plus précisément, si on range les signaux de gauche à droite, en mettant tout a fait à gauche les entrées, puis dans chaque colonne les sous-circuits qui ont des entrées parmis les signaux produits dans la colonne de gauche, ou de colonnes plus à gauche, alors le circuit est combinatoire si et seulement s’il n’y a aucune liaison qui reboucle ’en arrière’, en allant d’un étage à un étage plus à gauche (figure II.2). L’implication dans le sens ’sans rebouclage’ → ’combinatoire’ est facile à démontrer : si les entrées à gauche sont stables, alors par construction toutes les sorties des sous-circuits de la première colonne sont stables, et ne dépendent que de ces entrées. Donc les sorties des sous-circuits de la colonne 2 sont stables, et ne dépendent que des entrées. On démontre ainsi par récursion que les sorties des circuits de chaque colonne sont stables et ne dépendent que des valeurs des entrées, jusqu’aux sorties finales. Le circuit est donc combinatoire. 18
1. Circuits combinatoires entrées
19 sorties
... ...
Figure II.2. Circuit combinatoire : les signaux internes et les sorties ne rebouclent pas en arrière. Les figures II.3 et II.4 montrent des exemples de circuits combinatoires et non combinatoires formés à partir de portes logiques, dont on verra dans les sous-sections suivantes qu’elles sont elles-mêmes combinatoires.
Figure II.3. Exemples de circuits combinatoires : les éléments internes sont eux-mêmes combinatoires, et il n’y a pas de rebouclage interne.
Figure II.4. Exemples de circuits non combinatoires : il y a des rebouclages internes.
CHAPITRE II. ÉLÉMENTS DE LOGIQUE COMBINATOIRE
20
2. Tables de vérité Une des contributions essentielles de Boole est la table de vérité, qui permet de capturer les relations logiques entre les entrées et les sorties d’un circuit combinatoire sous une forme tabulaire. Considérons par exemple un circuit inconnu possédant 2 entrées A et B et une sortie S. Nous pouvons analyser exhaustivement ce circuit en lui présentant les 22 = 4 jeux d’entrées différents, et en mesurant à chaque fois l’état de la sortie. Le tout est consigné dans un tableau qu’on appelle table de vérité du circuit (figure II.5).
A
B
S
0
0
0
0
1
0
1
0
0
1
1
1
Figure II.5. Un circuit à deux entrées et une sortie, et sa table de vérité. Ici, on reconnaît l’opération logique ET : S vaut 1 si et seulement si A et B valent 1. La construction d’une table de vérité est bien sûr généralisable à un nombre quelconque n d’entrées et un nombre m de sorties. Elle possédera alors 2n lignes, ce qui n’est praticable que pour une valeur de n assez petite.
3. Algèbre et opérateurs de base Plutôt que de décrire en extension la relation entre les entrées et une sortie, on peut la décrire en intention à l’aide d’une formule de calcul utilisant seulement trois opérateurs booléens : NON, ET, OU. NON NON (NOT en anglais) est un opérateur unaire, qui inverse son argument. On notera − généralement NOT (A) = A ; et sa table de vérité est :
A
− A
0
1
1
0
Figure II.6. Table de vérité du NON, et dessin de la porte correspondante. − − On a bien sûr : A = A.
3. Algèbre et opérateurs de base
21
ET ET (AND en anglais) est un opérateur à 2 entrées ou plus, dont la sortie vaut 1 si et seulement si toutes ses entrées valent 1. On le note algébriquement comme un produit, c’est à dire S = A ⋅ B ou S = AB. La table de vérité d’un ET à deux entrées, et le dessin usuel de la porte correspondante sont représentés figure II.7.
A
B
A⋅B
0
0
0
0
1
0
1
0
0
1
1
1
Figure II.7. Table de vérité du ET, et symbole de la porte correspondante. De par sa définition, le ET est associatif : A ⋅ B ⋅ C = (A ⋅ B) ⋅ C = A ⋅ (B ⋅ C). Il est également idempotent : A ⋅ A = A. OU OU (OR en anglais) est un opérateur à 2 entrées ou plus, dont la sortie vaut 1 si et seulement si une de ses entrées vaut 1. On le note algébriquement comme une somme, c’est à dire S = A + B. La table de vérité d’un OU à deux entrées, et le dessin usuel de la porte correspondante sont représentés figure II.8.
A
B
A+B
0
0
0
0
1
1
1
0
1
1
1
1
Figure II.8. Table de vérité du OU, et symbole de la porte correspondante. De par sa définition, le OU est associatif : A + B + C = (A + B) + C = A + (B + C). Il est également idempotent : A + A = A. De la table de vérité à la formule algébrique On peut automatiquement écrire la formule algébrique d’une fonction booléenne combinatoire dès lors qu’on a sa table de vérité. Il suffit de considérer seulement les lignes qui donnent une sortie égale à 1, d’écrire le minterm correspondant, qui code sous forme d’un ET la combinaison de ces entrées. Par exemple, le minterm associé au vecteur (A, B, − C) = (1, 0, 1) est : ABC. La formule algébrique qui décrit toute la fonction est le OU de tous ces minterms.
CHAPITRE II. ÉLÉMENTS DE LOGIQUE COMBINATOIRE
22
Considérons par exemple la table de vérité de la fonction majorité à 3 entrées, qui vaut 1 lorsqu’une majorité de ses entrées (2 ou 3) vaut 1 (figure II.9). A
B
C
MAJ(A,B,C)
0
0
0
0
0
0
1
0
0
1
0
0
0
1
1
1
1
0
0
0
1
0
1
1
1
1
0
1
1
1
1
1
Figure II.9. Table de vérité de la fonction majorité à 3 entrées. La table de vérité contient 4 lignes avec une sortie S à 1, ce qui donne : − − − S = ABC + ABC + ABC + ABC Cette formule est simplifiable, comme on le verra dans une section ultérieure. Est-on sûr que la formule obtenue par cette méthode est correcte ? Pour une combinaison des entrées qui doit donner une sortie à 1, la formule possède le minterm correspondant, et donne aussi une valeur de 1. Pour toutes les autres combinaisons d’entrées, la table donne une sortie à 0, et la formule aussi, puisqu’aucun minterm n’est présent qui corresponde à ces combinaisons. La formule donne donc toujours le même résultat que la table. Corollairement, cela démontre le résultat fondamental suivant : N’importe quelle fonction combinatoire s’exprime sous forme d’une somme de minterms.
Théorèmes de l’algèbre de Boole Le tableau de la figure II.10 résume les principaux théorèmes et propriétés de l’algèbre de Boole. On peut les démontrer de façon algébrique, ou en utilisant des tables de vérité. Les théorèmes de De Morgan permettent de transformer les ET en OU et vice-versa. Le couple (ET,NON) ou le couple (OU,NON) suffisent donc à exprimer n’importe quelle formule algébrique combinatoire. Le théorème d’absorption A + AB = A montre qu’un terme plus spécifique disparaît au − − − profit d’un terme moins spécifique. Par exemple dans l’équation ⋅ ⋅ ⋅ + AB + ABC ⋅ ⋅ ⋅ , le − − − terme ABC s’efface au profit de AB, moins spécifique et qui donc l’inclut.
3. Algèbre et opérateurs de base
23
relation
relation duale
Propriété
AB = BA
A+B= B+A
Commutativité
A(B + C) = AB + AC
A + BC = (A + B)(A + C)
Distributivité
1⋅A = A − AA = 0
0+A=A − A+A= 1
Identité
0⋅A = 0
1+ A = 1
Zéro et un
A⋅A = A
A+A= A
Idempotence
A(BC) = (AB)C − − A=A − − − AB = A + B
A + (B + C) = (A + B) + C
Associativité
− −− A + B = AB
A(A + B) = A − A + AB = A + B
A + AB = A − A(A + B) = AB
Complément
Involution Théorèmes de De Morgan Théorèmes d’absorption Théorèmes d’absorption
Figure II.10. Propriétés et théorèmes de l’algèbre de Boole.
4. Autres portes logiques NAND NAND (= NOT AND) est un opérateur à 2 entrées ou plus, dont la sortie vaut 0 si et − seulement si toutes ses entrées valent 1. On le note ↑, et on a donc à deux entrées : A↑B = A ⋅ B La table de vérité d’un NAND à deux entrées, et le dessin usuel de la porte correspondante sont représentés figure II.11.
A
B
A↑ B
0
0
1
0
1
1
1
0
1
1
1
0
Figure II.11. Table de vérité du NAND, et dessin de la porte correspondante. Le NAND est un opérateur dit complet, c’est à dire qu’il permet à lui seul d’exprimer n’importe quelle fonction combinatoire. Il permet en effet de former un NOT, en reliant ses − deux entrées : A = A↑ A. Les théorèmes de De Morgan assurent donc qu’il peut exprimer un ET et un OU, et donc n’importe quelle expression combinatoire. NOR NOR (= NOT OR) est un opérateur à 2 entrées ou plus, dont la sortie vaut 0 si et seulement au moins une de ses entrées 1. On le note ↓, et on a donc à deux entrées : A
CHAPITRE II. ÉLÉMENTS DE LOGIQUE COMBINATOIRE
24
− = 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. ↓B
A
B
A↓ B
0
0
1
0
1
0
1
0
0
1
1
0
Figure II.12. Table de vérité du NOR, et dessin de la porte correspondante. Comme le NAND, le NOR est également un opérateur complet. Il permet en effet − de former un NOT, en reliant ses deux entrées : A = A ↓ A. Les théorèmes de De Morgan assurent donc qu’il peut exprimer un ET et un OU, et donc n’importe quelle expression combinatoire. XOR XOR (= EXCLUSIVE OR) est un opérateur à 2 entrées ou plus. On peut le définir de plusieurs façons ; la définition qui permet le plus directement de démontrer ses propriétés est celle qui consiste à en faire un détecteur d’imparité : sa sortie vaut 1 si et seulement si un nombre impair de ses entrées est à 1. On le note ⊕ ; la table de vérité d’un XOR à deux entrées, et le dessin usuel de la porte correspondante sont représentés figure II.13.
A
B
A⊕B
0
0
0
0
1
1
1
0
1
1
1
0
Figure II.13. Table de vérité du XOR, et dessin de la porte correspondante. − − On voit à partir de la table que A ⊕ B = AB + AB. Lorsqu’il a deux entrées, il mérite son nom de OU exclusif puisque sa sortie ne vaut 1 que si l’une de ses entrées vaut 1, mais seulement une. Il possède plusieurs autres propriétés intéressantes : •
lorsqu’on inverse une entrée quelconque d’un XOR, sa sortie s’inverse. C’est évident puisque cela incrémente ou décrémente le nombre d’entrées à 1, et donc inverse la parité.
•
le XOR à deux entrées est un inverseur commandé. En effet, en examinant la table de vérité du XOR (figure II.13)), on constate que lorsque A vaut 0, la sortie S reproduit
4. Autres portes logiques
25
l’entrée B, et lorsque A vaut 1, la sortie S reproduit l’inverse de l’entrée B. L’entrée A peut être alors vue comme une commande d’inversion dans le passage de B vers S. Cette fonction sera très utile lorsqu’on souhaitera corriger une erreur détectée sur une ligne, par exemple. •
le XOR est un opérateur d’addition arithmétique. Cette propriété est valable quel que soit le nombre d’entrées du XOR. Bien sûr, il n’est question ici que du bit de poids faible du résultat (ajouter n termes de 1 bit produit un résultat sur log2 (n) bits). Et en effet, si on regarde la table de vérité du XOR à deux entrées, on constate que la sortie est bien la somme arithmétique des deux entrées. Bien sûr à la dernière ligne, le résultat à une retenue que le XOR à lui seul ne peut pas produire. On peut démontrer que ce résultat est général par une simple récurrence. Si en effet on suppose que XOR(e1, e2, …, en ) fournit le bit de poids faible de la somme sn = e1 + e2 + … + en, alors : s si e = 1 XOR(e e …, e e ) = s si e = 0 ; − 1, 2,
n, n + 1
n
n+1
n
= sn avec inversion commandée par en + 1
n+1
= sn ⊕ en + 1
•
XOR est un opérateur associatif.Sa définition même, en tant qu’indicateur d’imparité, assure cette propriété. Quelle que soit la façon de grouper les termes, le calcul donnera le même résultat. Ce résultat permet de construire avec un minimum de circuits des XORs à 3 entrées ou plus, en cascadant des XORs à 2 entrées.
Formule algébrique d’un XOR à plus de 2 entrées Considérons le calcul du XOR de trois variables A, B et C. On sait que son expression algébrique peut se mettre sous forme d’une somme de minterms ; on va donc chercher parmi tous les minterms possibles ceux qu’il faut inclure dans la formule, et ceux qu’il faut rejeter. −−− −− − − −−− Avec ces trois variables A, B et C, il y a 8 minterms possibles : ABC, ABC, ABC, … ABC ne doit pas être inclus dans le résultat, puisqu’il donne 1 pour le vecteur (0,0,0) qui ne comporte −− pas un nombre impair de 1. ABC doit l’être, car il donne 1 pour le vecteur (0,0,1) qui possède un nombre impair (1) de 1. La règle est simple : on doit inclure seulement les minterms qui ont un nombre impair de variables non barrées. Pour le XOR à trois entrées, on peut le faire de façon organisée en mettant d’abord les 3 minterms ayant une variable non barrée, puis le minterm ayant les 3 variables non barrées : −− − − −− XOR(A, B, C) = ABC + ABC + ABC + ABC On peut montrer que cette formule n’est pas simplifiable en tant que somme de termes. Pour un XOR à 4 entrées, on inclut les 4 minterms ayant 1 variable non barrée, puis les 4 minterms ayant 3 variables non barrées : − − −−− − −− −− − −−− − − XOR(A, B, C, D) = ABC D + AB C D + ABC D + AB CD + ABC D + AB CD + ABCD + AB CD Pour un nombre quelconque d’entrées, on peut montrer que le résultat comporte toujours la moitié de tous les minterms possibles, et que cette somme n’est jamais simplifiable en tant
CHAPITRE II. ÉLÉMENTS DE LOGIQUE COMBINATOIRE
26 que somme de termes. Le multiplexeur
Le multiplexeur est une porte à trois entrées, qui joue un rôle d’aiguillage. Son dessin indique bien cette fonction (figure II.14).
SEL
S
0
A
1
B
Figure II.14. Table de vérité condensée du multiplexeur, et dessin de la porte correspondante. Lorsque la commande de sélection vaut 0, l’aiguillage est du côté de A, et S prend la valeur de A ; lorsqu’elle vaut 1, S prend la valeur de B. Algébriquement, on a donc : − S = SEL·A + SEL·B À partir de la compréhension du rôle du multiplexeur, il est clair que : − −− − S = SEL·A + SEL·B Or ce n’est pas si facile à établir algébriquement : − − − − − − S = SEL·A + SEL·B = SEL·A·SEL·B − − − − −− − −− S = (SEL + A)·(SEL + B) = A·SEL + SEL·B + A·B −− −− −− On a un terme en trop, A·B ! En fait, on peut le faire disparaître en écrivant A·B = A·B· −−− SEL + A·B·SEL : − −− −−− − −− S = A·SEL + SEL·B + A·B·SEL + A·B·SEL − −− − −− − − − S = A·SEL(1 + B) + SEL·B(1 + A) = SEL·A + SEL·B On a ici un exemple où la compréhension d’une fonction a permis de se dispenser de calculs algébriques ou de méthodes de simplification.
5. SHDL, un langage de description matérielle pédagogique Les langages de description matérielle (HDL) Un langage de description matérielle, ou HDL (Hardware Description language), permet la description de circuits logiques sous une forme textuelle. Cette description peut ensuite être utilisée par un simulateur pour être testée, ou elle peut être traduite par un
5. SHDL, un langage de description matérielle pédagogique
27
programme de synthèse afin d’être utilisée pour la fabrication d’un circuit logique spécifique (ASIC), ou pour pouvoir programmer un circuit logique reconfigurable tel qu’un PLD ou un FPGA (section 6). Certains langages de description sont structurels et d’autres sont fonctionnels. Un langage de description structurel précise explicitement tous les modules et sous modules qui sont utilisés dans le circuit. Si l’on veut implémenter une addition, il va falloir entrer dans tous les détails de sa conception. À l’inverse, un langage fonctionnel va décrire certains aspects de son fonctionnement sous forme d’une fonction et donc de façon non explicite ni extensive. L’addition de deux registres A et B sera par exemple écrite A + B, sans entrer dans les détails de sa réalisation matérielle. La plus grande lourdeur des langages de description structurels doit être tempérée par le fait qu’ils sont généralement tous modulaires, et que des circuits tels que des additionneurs existent déjà sous forme de modules tout prêts disponibles dans des bibliothèques. Néanmoins, ce sont les langages fonctionnels qui deviennent les plus largement utilisés, tels que VHDL et Verilog. ABEL et PALASM sont des exemples de langages structurels, ABEL étant le plus populaire. ABEL existe depuis 1983, et il a encore une grande communauté de concepteurs qui l’utilise. SHDL, un langage structurel pédagogique SHDL est un langage de description matérielle issu du langage MDL conçu initialement par Jean Conter, au département informatique de l’ENSEEIHT de Toulouse. SHDL signifie Simple Hardware Description Language; il est d’une écriture très simple et il encourage en effet une conception hautement modulaire, en supprimant tout simplement les déclarations habituelles à faire pour qu’un circuit déjà conçu puisse être réutilisé en tant que ’boîte noire’ dans un circuit de niveau supérieur. La syntaxe de SHDL est proche de celle d’ABEL, en en étant encore plus simple, et il est comme lui un langage de description structurel. Comme il a d’abord une visée pédagogique, ce choix permet d’examiner le fonctionnement des circuits conçus, jusqu’à la plus petite échelle. Un éditeur graphique et un simulateur ont été conçus pour le langage SHDL. Cet environnement est disponible sous forme de licence GPL, donc gratuit, utilisable et modifiable à volonté. Le lecteur est fortement encouragé à simuler tous les exemples de ce livre dans l’environnement SHDL. L’environnement libre et complet d’édition graphique et de simulation, ainsi qu’une documentation complète du logiciel sont disponibles à l’adresse http://diabeto.enseeiht.fr/download/shdl. Premiers éléments de syntaxe SHDL La syntaxe de SHDL sera décrite progressivement au fil des chapitres. Un exemple simple sera plus parlant qu’un long discours : module half_adder(a,b: s,r) s = /a*b+a*/b ; r = a*b ; end module
Ce module, appelé half_adder, possède 2 entrées appelées a et b, et deux sorties s et r. La première ligne module half_adder(a,b :s,r) fournit donc le nom et l’interface du module.
28
CHAPITRE II. ÉLÉMENTS DE LOGIQUE COMBINATOIRE
On notera la présence du signe ’:’ pour marquer la séparation entre les entrées et les sorties ; en réalité ce signe n’est pas obligatoire car le logiciel peut deviner seul les fonctions de ces signaux, mais ils peuvent rendre l’écriture plus lisible. L’écriture module half_adder(a,b,s,r) aurait donc été équivalente. On notera également la présence de la dernière ligne end module, qui termine la définition du module. Elle est obligatoire, et elle permet ainsi de placer plusieurs définitions de modules dans un même fichier, chaque définition étant encadrée par un début commençant par module et une fin end module. Viennent ensuite des lignes, terminées par le signe ’;’, qui décrivent comment les signaux s et r sont produits à partir de a et b. Le signe ’/’ indique la négation, et s’applique − au symbole immédiatement à sa droite. /a et /b signifient donc −a et b. Les signes ’+’ et ’*’ indiquent les opérations OU et ET respectivement ; les deux lignes s’interprètent donc : s = −ab + a−b et r = ab. Les parenthèses sont interdites en SHDL dans les expressions ; elles ne peuvent apparaître que dans la description de l’interface. En particulier, on ne peut pas écrire des négations de la forme /(a+b) ; il faudra utiliser le théorème de De Morgan et écrire /a*/b. Cette contrainte n’est pas une limitation, puisqu’on sait que toute formule algébrique peut se mettre sous forme d’une somme de minterms. Par ailleurs cette écriture est adaptée à la synthèse de circuits logiques reconfigurables de type PLD ou FPGA. Finalement, l’éditeur graphique de l’environnement SHDL permet d’afficher le contenu d’un tel module (figure II.15).
module half_adder(a,b: s,r) s = /a*b+a*/b ; r = a*b ; end module
Figure II.15. Demi-additionneur en SHDL.
6. Circuits logiques reconfigurables ; PLD et FPGA Un circuit logique programmable est un composant électronique qui réalise des fonctions combinatoires et séquentielles, mais dont le contenu est modifiable, décrit lors d’une phase de programmation (ou reconfiguration).Les premiers modèles ne pouvaient être programmés qu’une fois, mais la plupart des modèles actuels permettent plusieurs milliers
6. Circuits logiques reconfigurables ; PLD et FPGA
29
de cycles d’effaçage et réécriture. Un tel circuit est un peu plus lent que le même circuit fixe qui aurait été produit spécifiquement (ou ASIC), mais son coût est faible pour des petites productions, et sa reprogrammmation permet une phase de mise au point interactive, et autorise des mises à jours après déploiement. PLD PLD signifie ’Programmable Logic Device’. Un PLD se caractérise par : •
un nombre de composants plus faible qu’un FPGA.
•
une consommation plus faible que celle d’un FPGA.
•
une absence de phase de ’bootstrap’. Une fois configuré, un PLD conserve sa configuration même hors tension, et il est opérationnel immédiatement après la mise sous tension.
Les premiers PLDs s’appelaient PALs et GALs, et ils n’étaient programmables qu’une fois. Leur programmation nécessitait l’utilisation d’un programmateur spécial, et ils devaient être placés sur un support si on voulait profiter de leur reprogrammabilité. Les PLDs plus complexes sont souvent appelés CPLDs. Leur configuration est stockée dans une EEPROM ou mémoire flash, qui peut être mise à jour en utilisant le protocole JTAG (voir plus loin). FPGA Un FPGA (Field Programmable Gate Array) utilise une organisation de type matrice de portes, qui permet un nombre de composants plus grands. Sa configuration est stockée dans une RAM, qui doit être rechargée à chaque remise sous tension, généralement à partir d’une ROM présente sur la carte. Cette particularité qui semble être un inconvénient lui donne la possibilité très intéressante de pouvoir être reconfiguré dynamiquement, durant son fonctionnement. Programmation JTAG La plupart des PLDs et des FPGAs sont configurables directement sur le circuit imprimé où ils vont être placés, sans programmateur, en utilisant un protocole standard de programmation à l’aide d’un flux sériel de données appelé protocole JTAG. Tous les composants programmables présents sur le circuit imprimé sont reliés ensemble en ’daisy chain’ (figure II.16). TDO
TDI
TDI
TDO
CPLD TMS TCK
TMS TCK
TDI
TDO
FPGA TMS TCK
TDI
TDO
ROM Flash TMS TCK
Figure II.16. Chaîne de programmation JTAG.On peut mélanger des circuits de tous types, et même parfois de différents constructeurs.
CHAPITRE II. ÉLÉMENTS DE LOGIQUE COMBINATOIRE
30
Un interface très simple permet généralement d’exploiter les 5 signaux du protocole à partir de l’ordinateur de développement. Un logiciel de programmation permet d’effectuer une opération de lecture ou de configuration sur un ou plusieurs des composants de la chaîne, sans affecter les autres. Comme ce protocole est standard, il est généralement possible de mélanger dans cette chaîne des composants de différents constructeurs. Des cartes d’expérimentation à base de PLD ou de FPGA, programmables à l’aide d’un tel câble JTAG sont maintenant accessibles à faible coût, et permettent de réaliser des systèmes équivalents à plusieurs centaines de milliers de portes. Les outils de développement et de configuration sont tous fermés et propriétaires, et le développement d’environnements ouverts est découragé par la non divulgation des spécifications internes précises des composants.
7. Méthodes de simplification des fonctions combinatoires 7.1. Tables de Karnaugh Une table de Karnaugh est utilisée pour trouver visuellement les simplifications à faire sur une somme de termes. Elle est très facile à utiliser, pour peu qu’il n’y ait pas plus de 4 variables en jeu. Au delà, il vaut mieux employer la méthode de Quine-Mc Cluskey décrite plus bas. Considérons par exemple la fonction ayant la table de vérité de la figure II.17. A 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1
B 0 0 0 0 1 1 1 1 0 0 0 0 1 1 1 1
C 0 0 1 1 0 0 1 1 0 0 1 1 0 0 1 1
D 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1
S 0 1 1 1 0 1 0 1 0 1 0 1 1 1 1 1
Figure II.17. Table de vérité d’une fonction à simplifier. On va représenter les valeurs de S dans un tableau 4X4, chaque case correspondant à un des 16 minterms possibles avec 4 variables A,B,C et D (figure II.18). On remarquera d’abord les libellés des lignes et des colonnes, qui forment un code de Gray : d’une ligne à l’autre ou d’une colonne à l’autre, il n’y a qu’un bit d’entrée qui change. La table a une forme de cylindre dans les deux directions, c’est à dire que les colonnes ’00’ et ’10’ doivent être considérées comme côte à côte, tout comme les lignes ’00’ et ’10’.
7. Méthodes de simplification des fonctions combinatoires
31
A,B 0,0 0,1 1,1 1,0
C,D 0,0
1
0,1
1
1
1
1
1,1
1
1
1
1
1,0
1
1
Figure II.18. Table de Karnaugh représentant la table de vérité. Les 11 sorties ’1’ de la table de vérité ont été reportées dans la table de Karnaugh. En effectuant des groupes de ’1’ connexes de 2, 4 ou 8, on réalise une simplification (figure II.19). AB A,B 0,0 0,1 1,1 1,0
C,D 0,0
1
D
0,1
1
1
1
1
1,1
1
1
1
1
1,0
1
1 ABC
Figure II.19. Table de Karnaugh avec les regroupements. −− −− −− − Par exemple le regroupement ABC correspond à la simplification ABCD + ABC D −− − −− = ABC(D + D) = ABC. De même, le regroupement AB correspond à la simplification des 4 −− − −− − − − termes : ABC D + ABCD + ABC D + ABCD = AB(C D + CD + C D + CD) = AB. On repère facilement le résultat en regardant les libellés des lignes et des colonnes impliqués dans la simplification. Pour le terme simplifié D par exemple, il concerne les lignes CD=01 ;11 et les colonnes AB=**; seul D = 1 est constant, et toutes les autres variables disparaissent. Finalement on a : −− S = D + AB + ABC Utilisation des combinaisons non spécifiées Il y a des situations dans lesquelles certaines combinaisons des entrées ne se produisent jamais, qu’on peut appeler combinaisons interdites ou combinaisons non spécifiées. Considérons par exemple un décodeur 7 segments, qui convertit un nombre binaire sur 4 bits en un vecteur de 7 bits qui commande un afficheur à Leds de type ’calculette’ (figure II.20). La spécification de ce convertisseur nous précise qu’il ne faut convertir que les chiffres de 0 à 9, c’est à dire les valeurs du vecteur (A,B,C,D) de (0,0,0,0) à (1,0,0,1). Ainsi le fonctionnement du circuit n’est pas spécifié pour les valeurs de (1,0,1,0) à (1,1,1,1), et on va
CHAPITRE II. ÉLÉMENTS DE LOGIQUE COMBINATOIRE
32
a f
(a,b,c,d,e,f,g)
(A,B,C,D) 4
7
b
g e d
c
Figure II.20. Convertisseur 7 segments pour afficheur de calculette. exploiter cette latitude pour simplifier les équations. Si on considère par exemple la table de vérité du segment a, elle comporte ainsi 6 lignes pour lesquelles la valeur de la sortie n’est pas spécifiée, qu’on note ’*’ (figure II.21). A 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1
B 0 0 0 0 1 1 1 1 0 0 0 0 1 1 1 1
C 0 0 1 1 0 0 1 1 0 0 1 1 0 0 1 1
D 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1
a 1 0 1 1 0 1 1 1 1 1 * * * * * *
Figure II.21. Table de vérité du segment a. Dans la table de Karnaugh correspondante, les ’*’ peuvent être incluses dans les regroupements (figure II.22) ce qui conduit à leur donner la valeur ’1’. Bien sûr on ne doit pas chercher à inclure toutes les ’*’dans les regroupements : on cherche à inclure tous les ’1’, mais les ’*’permettent de trouver des regroupements plus grands. Ici par exemple, on trouve le résultat très simple : −− a = C + A + BD + BD Détection et correction des glitches Un autre usage des tables de Karnaugh est la détection des glitchs, c’est à dire des changements très brefs des signaux de sortie d’une fonction combinatoire lors de changements des entrées. Considérons par exemple la fonction suivante : −−−− − −− −− − − S = ABC D + ABC D + ABC D + ABCD + ABC D + ABCD On construit directement la table de Karnaugh associée à cette fonction, et on trouve
7. Méthodes de simplification des fonctions combinatoires
33
BD A,B C,D
0,0 0,1
1,1
1,0
1
*
1
1
*
1
0,0 0,1 1,1
1
1
*
*
1,0
1
1
*
*
A C
BD
Figure II.22. Table de Karnaugh du segment a, avec les combinaisons non spécifiées marquées par des ’*’. On peut inclure les ’*’ pour former des regroupements plus grands. immédiatement deux regroupements (figure II.23). A,B C,D
0,0 0,1 1,1 1,0 0,0 1 1a 1 b 0,1
1
1,1
1
1,0
1
Figure II.23. Table de Karnaugh avec les regroupements. Un glitch est possible dans le passage de a à b, car les deux regroupements sont tangents à cet endroit. L’équation simplifiée est alors : −−− S = AB + AC D Cette équation est plus simple que la première, mais elle va se comporter légèrement différemment lors de certains changements de valeurs des entrées. Considérons par exemple le changement du vecteur d’entrées (A,B,C,D) de (0,1,0,0) à (1,1,0,0), qui correspond au passage de a à b sur la figure II.23. Dans ce cas, seul A a changé, et la sortie S devrait rester à 1, aussi bien pour l’équation initiale que pour l’équation simplifiée. Or pour l’équation −−− simplifiée, le terme AB va passer de 0 à 1 tandis que le terme AC D va passer de 1 à 0 ; si ce changement se fait exactement en même temps, S restera à 1, mais si le premier terme passe à 0 légèrement avant que le deuxième ne passe à 1, un léger glitch sur S apparaîtra (figure II.24). Ce glitch était prévisible visuellement sur la table de Karnaugh, par le fait des deux regroupements qui sont adjacents sans se recouvrir. On constate également que c’est le seul changement d’une seule variable qui peut conduire à un tel problème. La solution est de rajouter un regroupement qui va faire la liaison entre ces deux groupes, en valant 1 pendant toute la transition (figure II.25). Finalement, on obtient l’expression simplifiée sans glitch :
CHAPITRE II. ÉLÉMENTS DE LOGIQUE COMBINATOIRE
34
A
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,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
A
A.B
A.C.D
B.C.D
S = A.B + A.C.D + B.C.D
−− Figure II.25. Ajout du terme BC D pour maintenir S à 1 lors de la transition de a vers b. Le glitch disparaît sur le signal de sortie S. −−− −− S = AB + AC D + BC D
7.2. Méthode de Quine-Mc Cluskey On va montrer son usage en simplifiant la fonction à 5 variables suivante : −−−−− −−− − − −−− − − − − −− − − f (A, B, C, D, E) = AB C D E + AB CD E + AB C D E + AB CD E + ABC D E + ABC DE − − − − + ABCDE + ABC DE + ABCDE Une table de Karnaugh serait délicate à utiliser dans ce cas, car elle aurait une taille de 4 x 8, et certains regroupements possibles pourraient ne pas être connexes. Par économie d’écriture, on commence par représenter chaque minterm par le nombre associé à sa représentation binaire : f (A, B, C, D, E) =
∑(0, 2, 8, 10, 12, 13, 26, 29, 30)
7. Méthodes de simplification des fonctions combinatoires
35
On place ensuite ces minterms dans un tableau, en les groupant selon le nombre de 1 qu’ils possèdent (figure II.26). nombre de 1
minterm
valeur binaire
0
0
00000
1
2
00010
8
01000
10
01010
12
01100
13
01101
26
11010
29
11101
30
11110
2 3 4
Figure II.26. On classe les minterms selon le nombre de 1 de leur valeur binaire. Les simplifications ne peuvent se produire qu’entre groupes adjacents. La simplification entre les minterms 1 et 2 se notera par exemple : 000-0, en mettant un tiret à l’endroit où la variable a disparu. Lorsqu’on a effectué toutes les simplifications possibles, on recommence à partir des nouveaux groupes formés, jusqu’à ce qu’aucune simplification ne soit possible (figure II.27). étape 1
étape 2
étape 3
0
00000
✓
0-2
000-0
✓
2
00010
✓
0-8
0-000
✓
8
01000
✓
2-10
0-010
✓
10
01010
✓
8-10
010-0
✓
12
01100
✓
8-12
01-00
13
01101
✓
10-26
-1010
26
11010
✓
12-13
0110-
29
11101
✓
13-29
-1101
30
11110
✓
26-30
11-10
0-2-8-10
0-0-0
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
CHAPITRE II. ÉLÉMENTS DE LOGIQUE COMBINATOIRE
36
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
✓
8-12
01-00
10-26
-1010
12-13
0110-
13-29
-1101
*
26-30
11-10
*
0-2-8-10
0-0-0
*
12
13
26 ✓
✓
✓ ✓
✓ ✓
✓
✓
30
✓ ✓
✓
29
✓
✓
Figure II.28. Table des implicants premiers ; les implicants premiers sont en lignes et les minterms de départ sont en colonnes. Un implicant premier est dit essentiel s’il est le seul à couvrir un des minterms en colonne ; on le repère avec une marque ’*’. Parmi les 5 minterms placés en ligne dans le tableau, 3 sont dits essentiels, parce qu’ils sont les seuls à couvrir un des minterms placés en colonnes : l’implicant premier 13-29 est le seul à couvrir le minterm 29, 26-30 est le seul à couvrir 26 et 30, et 0-2-8-10 est le seul à couvrir les minterms 0 et 2. 8-12 et 12-13 ne sont pas essentiels, car 8, 12 et 13 sont couverts par d’autres implicants premiers. Les implicants premiers essentiels sont nécessaires, mais pas forcement suffisants. Ici par exemple, si on ne prenait qu’eux, le minterm 12 ne serait pas couvert. Il reste donc une phase heuristique de choix parmi les implicants premiers non essentiels pour couvrir tous les minterms non encore couverts. Dans notre exemple, on peut ajouter, soit 8-12, soit 12-13 pour couvrir le 12 manquant, ce qui donne les deux possibilités suivantes : −−− − −− − − version avec 8-12 : S = AC E + ABDE + BC DE + ABDE −−− − − − − version avec 12-13 : S = AC E + ABC D + BC DE + ABDE
8. Circuits combinatoires réutilisables
37
8. Circuits combinatoires réutilisables Nous cherchons à développer une bibliothèque de circuits combinatoires (et ensuite séquentiels) qui seront réutilisables dans différents contextes, et notamment celui des microprocesseurs et des microcontrôleurs. Parmi eux figurent des opérateurs efficaces d’arithmétique entière : additionneurs, multiplieurs, comparateurs. Un interface adapté permettra une récursivité ou une interconnection aisées.
8.1. Problématique de l’addition Lorsqu’on effectue une addition en binaire, la méthode la plus simple consiste à l’effectuer bit à bit, en commençant par le bit de poids faible (figure II.29).
0
1
1
A[3..0]
1
0
0
1
B[3..0]
0
0
1
1
S[3..0]
1
1
0
0
0
addition demi complète addition
Figure II.29. Addition en binaire bit à bit. A chaque rang i de l’addition, on fait la somme des bits A[i] et B[i], ainsi que de l’éventuelle retenue qui viendrait du rang précédent i − 1. Ce calcul produit le bit S[i] du résultat, ainsi qu’une retenue qui sera passée au rang suivant. Lorsqu’on a effectué toutes ces additions depuis le bit de poids le plus faible (rang 0) jusqu’au bit de poids le plus fort, la retenue finale sera la retenue qui sort du dernier rang d’addition. On appelle demi-additionneur le circuit qui fait le calcul du rang 0, car il n’a pas à prendre en compte de retenue qui viendrait d’un étage précédent. On appelle additionneur complet le circuit qui prend en compte le calcul d’un bit de rang quelconque différent de 0.
8.2. Demi-additionneur Un demi-additionneur est le circuit qui réalise une addition entre deux bits A et B, et qui produit la somme sur un bit S avec l’éventuelle retenue R. La table de vérité et le module correspondant sont représentés figure II.30. Il est clair en effet que la somme est le XOR entre A et B, et la retenue ne peut intervenir que lorsque A et B valent 1 tous les deux.
8.3. Additionneur complet Un additionneur complet est un demi-additionneur qui comporte en plus en entrée une retenue entrante qui viendrait d’un autre étage d’addition. Les entrées sont donc les deux
CHAPITRE II. ÉLÉMENTS DE LOGIQUE COMBINATOIRE
38
A
B
S
R
0
0
0
0
0
1
1
0
1
0
1
0
1
1
0
1
Figure II.30. Table de vérité et schéma du demi-additionneur. bits A et B et la retenue entrante RE ; les sorties sont le bit résultat S et la retenue sortante RS. Il est facile de déterminer S et RS au vu de leur fonction, sans avoir besoin de leur table de vérité : •
S est la somme des trois bits A, B et RE, et on sait depuis la section 3 que c’est un XOR à trois entrées : S = A ⊕ B ⊕ RE.
•
RS est la retenue de cette addition, et vaut 1 lorsqu’il y a au moins deux valeurs à 1 dans le triplet (a, b, c) : c’est donc la fonction majorité déjà vue en section 3, RS = A·B + A ·RE + B·RE. module FULLADDER(A,B,RE: S,RS) S = /A*/B*RE+/A*B*/RE+A*/B*/RE+A*B*RE ; RS = A*B + A*RE + B*RE ; end module
Figure II.31. Additionneur complet : la somme est un XOR, et il y a retenue lorsqu’une majorité des entrées vaut 1.
8.4. Additionneur ripple-carry Un additionneur ripple carry réalise littéralement la méthode d’addition colonne par colonne, en commençant par une demi-addition des poids faibles et en enchaînant ensuite l’addition complète des bits suivants. Sur n bits, il nécessitera un demi-additionneur, et n − 1 additionneurs complets. Sur 4 bits par exemple, il prend la forme de la figure II.32. La description en langage SHDL est très simple : il suffit de combiner les écritures d’un demi-additionneur et de 3 additionneurs complets (figure II.33). Ce type d’additionneur est simple, mais lent : il faut attendre que toutes les retenues se soient propagées depuis le bit de poids le plus faible jusqu’au bit de poids le plus fort pour que le calcul soit terminé. Or un calcul d’addition est un calcul combinatoire, donc qui peut être effectué en théorie en une étape de propagation, sous forme d’une somme de minterms,
8. Circuits combinatoires réutilisables
39
Figure II.32. Addition ripple carry 4 bits. Le rang 0 est calculé avec un demi-additionneur, et les autres rangs avec des additionneurs complets. module ripplecarry4(a3,b2,a1,a0,b3,b2,b1,b0: s3,s2,s1,s0,carry) halfadder(a0,b0:s0,c0); fulladder(a1,b1,c0:s1,c1); fulladder(a2,b2,c1:s2,c2); fulladder(a3,b3,c2:s3,carry); end module module halfadder(a,b: s,cout) s = /a*b+a*/b; cout = a*b; end module module fulladder(a,b,cin: s,cout) s = /a*/b*cin+/a*b*/cin+a*/b*/cin+a*b*cin; cout = a*b + a*cin + b*cin; end module
Figure II.33. Écriture SHDL d’un additionneur ripple-carry 4 bits.On combine un module halfadder et 3 modules fulladder. et sans tous ces termes intermédiaires que représentent les retenues. Mais les équations à écrire deviennent très complexes dès le rang 3, et sont donc impraticables. Entre ces deux extrêmes, il est nécessaire de concevoir des additionneurs de complexité raisonnable pour un nombre de bits supérieur à 8, et avec des temps de propagation qui ne croissent pas linéairement avec ce nombre de bits. On va voir en section 8.5 comment les additionneurs carry lookahead parviennent à ce compromis.
8.5. Additionneur carry-lookahead On a vu en section 8.1 que c’était la propagation de la retenue qui ralentissait le calcul d’une somme dans un additionneur ripple carry. Si on calcule an..a0 + bn..b0, on a pour chaque additionneur complet : si = ai ⊕ bi ⊕ ci
CHAPITRE II. ÉLÉMENTS DE LOGIQUE COMBINATOIRE
40 ci = ai bi + ai ci + bi ci
Calculer s3 par exemple nécessite de calculer c3, donc s2, donc c2, etc. La méthode carry lookahead part du constat qu’on peut facilement déterminer à chaque étage s’il y aura une retenue ou non, en raisonnant en termes de retenue propagée et de retenue générée. Considérons l’addition des deux termes suivants : P
P
P
G
G
P
P
1
0
1
1
1
0
0
1
0
1
0
1
1
1
0
0
Un ’G’ est placé au dessus d’une colonne de rang i lorsque les termes à ajouter ai et bi valent tous deux ’1’ : on est alors sûr qu’une retenue sera ’Générée’ pour l’étage suivant. Sinon, un ’P’ est placé lorsqu’un des deux termes vaut ’1’ : si une retenue arrive depuis l’étage précédent, alors cet étage va la ’Propager’, puisqu’on sera alors sûr qu’il y a au moins deux ’1’ parmi les trois bits à additionner à ce rang. En raisonnant uniquement sur les ’P’ et les ’G’, on trouve facilement quels étages vont émettre une retenue. Ainsi sur l’exemple, l’étage 0 peut propager une retenue, mais aucune n’a encore été générée. Les étages 3 et 4 génèrent une retenue, et les étages suivants 5, 6 et 7 la propagent. Les termes Gi et Pi sont très faciles à calculer : Gi = ai ·bi Pi = ai + bi Un module autonome peut prendre en entrées les valeurs des Gi et Pi de tous les rangs et les utiliser pour calculer les retenues entrantes de chaque additionneur complet, selon le schéma général de la figure II.34. On notera que les retenues sortantes cout des additionneurs complets ne sont pas exploitées, puisque c’est le module carry_lookahead qui s’occupe du calcul de toutes les retenues. Il faut maintenant réaliser un module carry_lookahead qui effectue ce calcul dans le temps le plus court. L’idée générale est qu’il y a une retenue au rang i si Gi = 1 ou si Pi = 1 et si une retenue existe au rang i − 1 : ci + 1 = Gi + ci ·Pi On trouve : c1 = G0 + cin·P0
c2 = G1 + c1·P1 = G1 + G0 ·P1 + cin·P0 ·P1
c3 = G2 + c2 ·P2 = G2 + G1·P2 + G0 ·P1·P2 + cin·P0 ·P1·P2
c4 = G3 + c3·P3 = G3 + G2 ·P3 + G1·P2 ·P3 + G0 ·P1·P2 ·P3 + cin·P0 ·P1·P2 ·P3 Il ne faut pas se leurrer : il y a là aussi une cascade dans les calculs, mais elle porte sur des termes plus simples que si on avait opéré directement sur les terme ai et bi. Au delà
8. Circuits combinatoires réutilisables
41
Figure II.34. Schéma général d’un additionneur carry-lookahead.On calcule pour chaque rang i deux bits Gi et Pi qui indiquent si cet étage va générer une retenue, ou en propager une de l’étage précédent. Un module spécifique carry_lookahead calcule rapidement à partir des Gi et Pi les retenues ci pour tous les rangs. de 7 à 8 bits, les équations du module carry_lookahead deviennent trop complexes pour être implémentées sous forme de sommes de termes, et des signaux intermédiaires doivent être introduits. Si on considère la somme de produits comme unité de calcul et de propagation (hypothèse d’implémentation dans un CPLD ou un FPGA), le calcul complet d’une somme nécessite 1temps pour le calcul des Gi et Pi, 1temps pour le calcul des retenues, et 1temps pour l’additionneur complet, soit un total de 3 temps de propagation, contre 4 pour l’additionneur ripple carry. Le gain est faible, mais il augmente à mesure que le nombre de bits grandit. L’écriture complète d’un tel additionneur 4 bits en langage SHDL est donnée figure II.35. Association d’additionneurs carry-lookahead La technique de l’additionneur carry-lookahead ne peut pas être utilisée au delà de 7 à 8 bits, mais on souhaite continuer à l’exploiter sur des additionneurs de plus grande taille. On peut subdiviser les mots à additionner en groupes de 4 bits par exemple, et effectuer dans chaque groupe une addition carry-lookahead. Se pose alors le problème de l’association de ces groupes : va-t-on se contenter de les chaîner en ripple carry ? On peut en fait appliquer à nouveau la technique carry-lookahead à l’échelle des groupes. Chaque additionneur carry-lookahead sur 4 bits CLAi va générer deux signaux GGi (group generate) et GPi (group propagate). GGi indiquera que le groupe génèrera
CHAPITRE II. ÉLÉMENTS DE LOGIQUE COMBINATOIRE
42
module full_adder(a, b, rin : s, rout) s = /a*/b*rin+/a*b*/rin+a*/b*/rin+a*b*rin; rout = a*b+a*rin+b*rin; end module module carry_lookahead(G3,P3,G2,P2,G1,P1,G0,P0,c0:c4,c3,c2,c1) c1=G0+P0*c0; c2=G1+P1*G0+P1*P0*c0; c3=G2+P2*G1+P2*P1*G0+P2*P1*P0*c0; c4=G3+P3*G2+P3*P2*G1+P3*P2*P1*G0+P3*P2*P1*P0*c0; end module module cla4(a3..a0,cin,b3..b0:s3..s0,cout) G3..G0 = a3..a0 * b3..b0; P3..P0 = a3..a0 + b3..b0; carry_lookahead(G3,P3,G2,P2,G1,P1,G0,P0,cin:cout,c3,c2,c1); full_adder(a3,b3,c3:s3,c4_); full_adder(a2,b2,c2:s2,c3_); full_adder(a1,b1,c1:s1,c2_); full_adder(a0,b0,cin:s0,c1_); end module
Figure II.35. Écriture SHDL d’un additionneur 4 bits carry-lookahead. On notera l’écriture vectorielle pour les équations des termes Gi et Pi. nécessairement une retenue et GPi indiquera que si une retenue arrive dans ce groupe (par son entrée cin), elle sera propagée à travers lui au groupe suivant. La même méthode est ainsi appliquée à l’échelle des groupes, chaque groupe jouant ici le même rôle que jouait un étage de 1 bit dans l’additionneur carry-lookahead ordinaire. Le même module carry_lookahead est d’ailleurs employé pour calculer rapidement les retenues c 4 , c 8, c12 et c16 qui sont envoyées aux différents groupes. La figure II.36 montre un exemple d’addition , et la figure II.37 présente l’organisation générale d’un tel groupement. #3
#2
#1
#0
0000 0000 1010 0100
cin=0
0000 0000 0101 1100 0000 0001 0000 0000 GG=0 GP=1
GG=1 GP=0
c8=1
c4=1
Figure II.36. Exemple d’addition 16 bits avec un additionneur group carry-lookahead.Le groupe #0 génère une retenue,qui est propagée au travers du groupe #1, et dont l’effet vient se terminer dans le groupe #2. Il nous reste seulement à trouver les équations de GG et GP. GG indique qu’une retenue est générée par le groupe, c’est à dire générée an niveau du bit 3, ou générée au niveau du bit 2 et propagée au rang 3, etc. : GG = G3+G2*P3+G1*P2*P3+G0*P1*P2*P3
8. Circuits combinatoires réutilisables
43
Figure II.37. Schéma général d’un groupement de 4 additionneurs 4 bits carry-lookahead. Chaque additionneur fournit deux signaux GG et GP qui indiquent s’il génère ou propage une retenue pour le groupe suivant.Un module carry_lookahead effectue le calcul rapide des retenues entrantes de chaque groupe à partir de ces signaux. GP indique qu’une retenue arrivant par cin sera propagée tout au long du groupe, c’est à dire qu’à chaque rang de bit i il y a au moins un ’1’sur ai ou bi, c’est à dire encore qu’à chaque rang i on a Pi = 1 : GP = P0*P1*P2*P3
La figure II.39 donne l’ensemble complet des équations SHDL de cet additionneur. Les modules carry_lookahead et fulladder sont les même qu’à la figure II.35, cla4 a été légèrement modifié puisqu’il produit maintenant les signaux GG et GP.
8.6. Soustraction Problématique de la soustraction Comme l’addition, la soustraction peut être effectuée bit à bit, en commençant par le bit de poids faible (figure II.38).
0
0 1
A[3..0]
1
B[3..0]
0 1
S[3..0]
0
1
0
0
0
1
0
1
1
1
1
0
1
0
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
CHAPITRE II. ÉLÉMENTS DE LOGIQUE COMBINATOIRE
44
module cla16(a15..a0,b15..b0,cin:s15..s0,cout) cla4(a3..a0,b3..b0,cin:s3..s0,c4_,GG0,GP0); cla4(a7..a4,b7..b4,c4:s7..s4,c8_,GG1,GP1); cla4(a11..a8,b11..b8,c8:s11..s8,c12_,GG2,GP2); cla4(a15..a12,b15..b12,c12:s15..s12,c16_,GG3,GP3); carry_lookahead(GG3,GP3,GG2,GP2,GG1,GP1,GG0,GP0,cin:cout,c12,c8,c4); end module module cla4(a3,a2,a1,a0,b3,b2,b1,b0,cin:s3,s2,s1,s0,cout,GG,GP) G3..G0 = a3..a0 * b3..b0; P3..P0 = a3..a0 + b3..b0; carry_lookahead(G3,P3,G2,P2,G1,P1,G0,P0:cout,c2,c1,c0); full_adder(a3,b3,c2:s3,cout3); full_adder(a2,b2,c1:s2,cout2); full_adder(a1,b1,c0:s1,cout1); full_adder(a0,b0,cin:s0,cout0); GG = G3+G2*P3+G1*P2*P3+G0*P1*P2*P3; GP = P0*P1*P2*P3; end module
Figure II.39. Écriture SHDL d’un additionneur 16 bits carry-lookahead. Les modules cla4, carry_lookahead et fulladder sont ceux utilisés dans la version 4 bits, cla4 ayant été légèrement modifié pour produire les signaux GG et GP.
petit que bi, ou plus exactement lorsque ai est plus petit que bi plus le bit d’emprunt de l’étage précédent. Chaque rang de soustraction est un circuit combinatoire à 3 entrées et 2 sorties appelé soustracteur complet dont la table de vérité est donnée figure II.40. On a noté EE l’emprunt entrant, qui vient du rang précédent et ES l’emprunt sortant qui est passé au rang suivant.
A
B
EE
S
ES
0
0
0
0
0
0
0
1
1
1
0
1
0
1
1
0
1
1
0
1
1
0
0
1
0
1
0
1
0
0
1
1
0
0
0
1
1
1
1
1
Figure II.40. Table de vérité d’un soustracteur complet. On soustrait B à A ; EE est l’emprunt entrant et ES l’emprunt sortant. On voit immédiatement que S est le XOR des trois entrées A, B et EE. L’emprunt sortant ES vaut 1 lorsque A vaut 0 et qu’un des deux B ou EE vaut 1, ou lorsque A vaut 1 et que B et − − EE valent tous les deux 1. On trouve donc ES = A·B + A·EE + EE·B.
8. Circuits combinatoires réutilisables
45
Le schéma du soustracteur complet et son écriture SHDL sont donnés figure II.41. module FULLSUB(A,B,EE: S,ES) S = /A*/B*EE+/A*B*/EE+A*/B*/EE+A*B*EE; ES = /A*B + /A*EE + B*EE; end module
Figure II.41. Soustracteur complet : la différence est un XOR, et il y a retenue lorsqu’une majorité des signaux (/A,B,EE) vaut 1. Soustracteur ripple-borrow Comme un additionneur ripple-carry, un soustracteur ripple-borrow est formé par chaînage de plusieurs étages de soustracteurs complets. Comme lui il est très simple de conception, mais présente aussi les mêmes inconvénients de lenteur dus à la propagation des signaux d’emprunt. Les figures II.42 et II.43 montrent un additionneur 4 bits ripple-borrow et l’écriture SHDL associée.
Figure II.42. Schéma général d’un soustracteur 4 bits ripple-borrow. Transformation d’un additionneur en soustracteur On peut faire la soustraction A − B en effectuant l’addition A + ( − B). − B est obtenu en prenant le complément à 2 de B, c’est à dire le complément à 1 de B auquel on ajoute 1. L’ajout +1 peut se faire en exploitant un signal de retenue entrante, comme on peut le voir sur la figure II.44. Cette figure montre un additionneur/soustracteur : si ADD/SUB vaut 0 il effectue une addition ; s’il vaut 1 les n XORs effectuent le complément à 1 du vecteur B, et le forçage à 1 de la retenue entrante va conduire à ajouter 1, donc à construire le complément à 2 de B, donc à réaliser une soustraction. On perd malheureusement le signal de retenue entrante ; la retenue sortante cout par contre a bien le sens d’une retenue pour l’addition, et son inverse
46
CHAPITRE II. ÉLÉMENTS DE LOGIQUE COMBINATOIRE module rippleborrow4(a3..a0,b3..b0,ee: s3..s0,es) fullsub(a0,b0,ee:s0,e1); fullsub(a1,b1,e1:s1,e2); fullsub(a2,b2,e2:s2,e3); fullsub(a3,b3,e3:s3,es); end module module fullsub(a,b,ee:s,es) s = /a*/b*ee+/a*b*/ee+a*/b*/ee+a*b*ee; es = /a*b + /a*ee + b*ee; end module
Figure II.43. Équations SHDL d’un soustracteur 4 bits ripple-borrow.
Figure II.44. Additionneur/soustracteur réalisé à partir d’un additionneur. Lorsque l’entrée ADD/ SUB vaut 0 l’addition A + B est réalisée ; lorsqu’elle vaut 1, les XORs calculent le complément à 1 de B et le forçage à 1 de la retenue entrante cin créé le complément à 2, d’où la soustraction. le sens d’un emprunt, d’où l’inversion finale commandée par le signal ADD/SUB. La figure II.45 donne le code SHDL associé pour un additionneur/soustracteur 16 bits ; elle reprend le module additionneur carry-lookahead 16 bits cla16 étudié à la section précédente. module addsub16(a[15..0],b[15..0],addsub: s[15..0],cout) bb[15..0] = /addsub*b[15..0] + addsub*/b[15..0]; cla16(a[15..0],bb[15..0],addsub:s[15..0],co); cout=/addsub*co+addsub*/co; end module
Figure II.45. Équations SHDL d’un additionneur/soustracteur 16 bits. On réutilise l’additionneur carry-lookahead 16 bits étudié auparavant.
47
8.7. Multiplicateur systolique Problématique de la multiplication On souhaite multiplier deux nombres de n bits non signés. Le résultat est sur 2n bits, puisque la multiplication des deux plus grands nombres sur n bits donne : (2 − 1)(2 − 1) = 22n − 2·2 + 1 < 22n − 1 n
n
n
La problématique est la même que pour l’addition : puisqu’il s’agit d’un calcul combinatoire, il peut se faire en théorie sous forme d’une somme de minterms, en une étape de propagation (en considérant une fois encore que l’unité de propagation est la somme de produit). Mais bien sûr en pratique, les équations à implémenter seraient beaucoup trop complexes. En binaire, la méthode la plus directe pour effectuer une multiplication consiste à la poser comme à l’école (figure II.46). 0 1 1 0 ai
X 1 1 0 1 0 1 1 0 0 0 0 0 0 1 1 0 0 1 1 0
bi
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 X
1 0
0 0
0 1
0
1
1
1
0
1
1 1
0
1 0 1 1
1 1 1
1
0
1
1
X
1
1
1
1
0
0
1
0
0
1
1
1
1
1
1
0
1
1
0
1
1
1
1
1
1
1
0
1
1
0
1
1
0
1
0
0
0
1
0
1 1
1
0
1
1
1
0
(a)
1
1
1
0
1
(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
0
1
1
a
b
cin
1
full adder
0
s
cout
0
1
1
Figure II.48. Multiplicateur systolique : chaque cellule est un additionneur complet. la même disposition que la somme des termes partiels, avec un recadrage à droite. On remarquera la dernière rangée, qui fournit les bits de poids forts du résultat final s7..s4. Les bits de poids faibles s3..s0 proviennent quant à eux des cellules de la colonne de droite. Le texte SHDL correspondant à ce schéma est donné figure II.50. On voit facilement que ce multiplicateur 4x4 nécessite 6 temps de propagation (unité : somme de termes). De façon plus générale, un multiplicateur systolique n bits x n bits nécessite n+2 temps de propagation. C’est donc une méthode assez efficace, qui est facile à implémenter dans des circuits matriciels tels que CPLDs et FPGAs.
8.8. Division entière Problématique de la division Comme pour la multiplication, on va essayer de concevoir un réseau de cellules qui réalise une division non signée en utilisant la méthode euclidienne. Prenons un exemple (figure II.51). On a réalisé ici une division non signée 8 bits / 4 bits -> 8 bits. Le dividende est p7..p0
49
Figure II.49. Schéma général d’un multiplicateur systolique 4 bits x 4 bits vers 8 bits. = 11010101012 = 213 et le diviseur est d3..d0 = 10112 = 11; on trouve un quotient q7..q0 = 000100112 = 19 et un reste r3..r0 = 01002 = 4. On voit que le nombre d’étages de cette division est égal à la largeur du dividende, 8 dans l’exemple. À chaque étape, on effectue une soustraction entre un terme courant et le diviseur. Au départ, ce terme courant est le poids fort du dividende complété de 4 zéros à gauche. Le diviseur est également complété d’un zéro à gauche et c’est une soustraction sur 5 bits qui est réalisée. Il y a alors deux cas : •
la soustraction donne un résultat négatif, signalé par le bit d’emprunt à 1; on ne doit alors pas prendre son résultat pour le terme courant, mais le laisser tel quel. On lui rajoutera à droite un bit supplémentaire du dividende, à l’étage suivant. On ajoute un zéro à droite au quotient.
50 module multsyst(a[3..0], b[3..0]: s[7..0]) // première rangée ; produit s0 p30=a3*b0; p20=a2*b0; p10=a1*b0; p00=a0*b0; fulladder(0,0,p30:s30,c30); fulladder(0,0,p20:s20,c20); fulladder(0,0,p10:s10,c10); fulladder(0,0,p00:s0,c00); // deuxième rangée ; produit s1 p31=a3*b1; p21=a2*b1; p11=a1*b1; p01=a0*b1; fulladder(c30,0,p31:s31,c31); fulladder(c20,s30,p21:s21,c21); fulladder(c10,s20,p11:s11,c11); fulladder(c00,s10,p01:s1,c01); // troisième rangée ; produit s2 p32=a3*b2; p22=a2*b2; p12=a1*b2; p02=a0*b2; fulladder(c31,0,p32:s32,c32); fulladder(c21,s31,p22:s22,c22); fulladder(c11,s21,p12:s12,c12); fulladder(c01,s11,p02:s2,c02); // quatrième rangée ; produit s3 p33=a3*b3; p23=a2*b3; p13=a1*b3; p03=a0*b3; fulladder(c32,0,p33:s33,c33); fulladder(c22,s32,p23:s23,c23); fulladder(c12,s22,p13:s13,c13); fulladder(c02,s12,p03:s3,c03); // rangée du total final pour s7..s4 fulladder(0,c33,0:s7,c3x); fulladder(s33,c23,0:s6,c2x); fulladder(s23,c13,0:s5,c1x); fulladder(s13,c03,0:s4,c0x); end module
Figure II.50. Écriture SHDL d’un multiplicateur systolique 4 bits x 4 bits. •
la soustraction donne un résultat positif, signalé par le bit d’emprunt à 0; on remplace le terme courant par le résultat de cette soustraction, et on lui ajoutera à droite un bit supplémentaire du dividende, à l’étage suivant. On ajoute un 1 à droite au quotient.
Le reste r3..r0 est la valeur du terme courant après la dernière étape. Structure du diviseur On souhaite ainsi réaliser un diviseur 2n bits / n bits -> 2n bits; on va prendre ici comme dans l’exemple n = 4, mais la technique est bien sûr généralisable à toute valeur de n. La figure II.53 montre la structure nécessaire du diviseur. À chaque étage on réalise la soustraction entre le terme issu de l’étage précédent et d3,..d0. On utilise pour cela des soustracteurs 5 bits sans retenue entrante, et avec une retenue sortante; on a vu aux sections précédentes comment les réaliser. À chaque étage il faut aussi construire le terme courant, en fonction des deux cas examinés dans la discussion précédente et déterminés par la valeur du bit d’emprunt de la soustraction. On devra pour cela utiliser des multiplexeurs dont la commande sera le bit d’emprunt. La figure II.54 donne le code SHDL complet associé à ce diviseur.
51 p7
p0 d3
1 1 0 1 0 1 0 1 0 1 0 1 1 0 0 0 0
q7=0
1
1
q5=0
1 0 1 1 0 0 0 1 0 0 1 1 q0 q7
0 0 1 1 0 0 0 0 1 1 0 1 0 1 1
q6=0
d0
1 1 0 0 0 0 0 1 1 0 0 1 0 1 1 1
q4=1
1 1 0 1 1 0 1 1 0 1 0 1 0 1 1 0
q3=0
0 0 0 1 0 0 0 1 0 1 1 1
q2=0
q1=1 q0=1
1 1 0 0 1 0 1 0 0 1 0 1 0 1 1 1
1 1 1 1 0 1 0 0 1 0 0 1 0 1 1 0
0 0 1 1 1 1 0 1 0 1 1 0
0 0 1 0 0 r3 r0
Figure II.51. Exemple de division euclidienne en binaire.
8.9. Comparateurs L’opération de comparaison entre nombres entiers est fondamentale dans un ordinateur. Si on souhaite comparer deux nombres de n bits, l’interface générale qu’on peut utiliser est celle de la figure II.52.
Figure II.52. Interface d’un comparateur sur n bits. Une sortie indique que A et B sont égaux, une autre indique que A > B. Une sortie SUP indique que A > B, l’autre EQ indique que A = B. Avoir ces deux sorties permet de tout savoir sur les positions respectives de A et B : •
A > B : SUP = 1.
52
Figure II.53. Structure du diviseur non signé 8 bits / 4 bits -> 8 bits. À chaque étage on teste si on peut soustraire le diviseur au terme courant.Les bits du quotient sont les inverses des bits d’emprunt des soustractions; le reste est la valeur du terme courant après la dernière étape. •
A ≥ B : SUP = 1 ou EQ = 1.
•
A = B : EQ = 1
•
A < B : SUP = 0 et EQ = 0.
•
A ≤ B : SUP = 0.
Il faut maintenant savoir si les nombres que l’on compare sont des nombres entiers naturels, ou des nombres relatifs codés en complément à 2. En effet, si A = 1111 et B = 0101, une
53 module div8(p7..p0,d3..d0:q7..q0,r3..r0) // soustracteur sub5(0,0,0,0,p7,0,d3,d2,d1,d0:x74,x73,x72,x71,x70,nq7); q7=/nq7; // multiplexeur s74=nq7*z+/nq7*x73; s73=nq7*z+/nq7*x73; s72=nq7*z+/nq7*x72; s71=nq7*z+/nq7*x71; s70=nq7*p7+/nq7*x70; sub5(s73,s72,s71,s70,p6,0,d3,d2,d1,d0:x64,x63,x62,x61,x60,nq6); q6=/nq6; s64=nq6*s73+/nq6*x64; s63=nq6*s72+/nq6*x63; s62=nq6*s71+/nq6*x62; s61=nq6*s70+/nq6*x61; s60=nq6*p6+/nq6*x60; ... // code analogue pour q4,...,q1 ... sub5(s13,s12,s11,s10,p0,0,d3,d2,d1,d0:x04,x03,x02,x01,x00,nq0); q0=/nq0; s04=nq0*s13+/nq0*x04; r3=nq0*s12+/nq0*x03; r2=nq0*s11+/nq0*x02; r1=nq0*s10+/nq0*x01; r0=nq0*p0+/nq0*x00; end module
Figure II.54. Écriture SHDL d’un diviseur non signé 8 bits / 4 bits -> 4 bits comparaison non signée va donner A > B (car A = 15 et B = 5), alors qu’une comparaison signée donnera A < B (car A = − 1 et B = + 5). Comparateur non signé Supposons que A et B soient codés en binaire pur, par exemple sur 4 bits. On commence d’abord par comparer leurs poids forts A3 et B3 ; si celui de A est plus grand que celui de B, on peut conclure SUP = 1 ; sinon SUP ne peut valoir 1 que si A3 = B3 et que si A2..A0 > B 2..B0. On a ainsi une définition récursive de SUP : SUP = (A3 > B3) + (A3 = B3)·(A2..A0 > B2..B0) SUP = (A3 > B3) + (A3 = B3)·((A2 > B2) + (A2 = B2)·(A1..A0 > B1..B0)) SUP = (A3 > B3) + (A3 = B3)·((A2 > B2) + (A2 = B2)·((A1 > B1) + (A1 = B1)·(A0 > B0))) − Ai > B1 correspond en fait à l’unique situation Ai = 1 et Bi = 0 et s’exprime donc Ai Bi. Pour Ai = Bi, on peut utiliser le fait qu’un XOR est un opérateur de différence, et donc que son − −− inverse est un opérateur de coïncidence : (Ai = Bi ) = Ai ⊕ Bi = Ai ·Bi + Ai ·Bi − − − − − − − SUP = A3B3 + A3 ⊕ B3·(A2B2 + A2 ⊕ B2·(A1B1 + A1 ⊕ B1·A0B0)) Le code SHDL correspondant est :
54 module cmpu4(a[3..0], b[3..0]: sup, eq) sup=a[3]*/b[3]+eq3*sup2; eq3=a[3]*b[3]+/a[3]*/b[3]; sup2=a[2]*/b[2]+eq2*sup1; eq2=a[2]*b[2]+/a[2]*/b[2]; sup1=a[1]*/b[1]+eq1*a[0]*/b[0]; eq1=a[1]*b[1]+/a[1]*/b[1]; eq0=a[0]*b[0]+/a[0]*/b[0]; eq=eq3*eq2*eq1*eq0; end module
Comparateur signé Une comparaison signée est un peu plus délicate qu’une comparaison non signée ; l’essentiel de la comparaison se joue en examinant les signes an − 1 et bn − 1 des termes A = an − 1..a0 et B = bn − 1..b0 à comparer : •
si A < 0 (an − 1 = 1) et B > 0 (bn − 1 = 0) alors la comparaison est immédiate : SUP = 0 et EQ = 0.
•
si A > 0 (an − 1 = 0) et B < 0 (bn − 1 = 1) alors la comparaison est immédiate : SUP = 1 et EQ = 0.
•
si A > 0 (an − 1 = 0) et B > 0 (bn − 1 = 0) alors il suffit de comparer les n − 1 bits de poids faibles.
•
si A < 0 (an − 1 = 1) et B < 0 (bn − 1 = 1) alors il suffit également de comparer les n − 1 bits de poids faibles. En effet, les nombres de l’intervalle [2n − 1,0] s’écrivent en complément à 2 : [1000..000, 1000..001, 1111..111] et on voit que les n − 1 bits de poids faible évoluent de façon croissante.
On a représenté sur la figure II.56 un comparateur signé sur 5 bits utilisant un compteur non signé de 4 bits. Un multiplexeur 4 vers 1 sépare les 4 situations décrites. module cmps5(a[4..0],b[4..0]: sup, eq) cmpu4(a[3..0], b[3..0]: sup1, eq1); mux4(sup1, 1, 0, sup1, a4, b4: sup); eq=a4*b4*eq1+/a4*/b4*eq1; eq=eq4*eq1; end module
Figure II.55. Écriture SHDL d’un comparateur signé sur 5 bits. Assemblage de comparateurs non signés Il est facile de chaîner 2 comparateurs non signés de n et m bits pour former un comparateur non signé de n + m bits (figure II.57). On compare d’abord les n bits de poids forts ; si ceux de A sont plus grands que ceux de
55
Figure II.56. Comparateur signé sur 5 bits. Si les signes a4 et b4 sont différents, la comparaison est immédiate ; s’ils égaux on compare en non signé les 4 bits de poids faibles.
Figure II.57. Association de deux comparateurs n bits non signés. Le résultat forme un comparateur 2n bits avec le même interface. B, le SUP final est asserté au travers du OU. Si les poids forts de A et ceux de B sont égaux, on compare les m bits de poids faibles et on conclue. La sortie finale EQ est quant à elle assertée lorsque les deux sorties EQ sont assertées.On a représenté en figure II.58 le code SHDL d’un comparateur non signé sur 10 bits, formé par l’assemblage de deux comparateurs non signés de 5 bits. La sortie de module EQ a été essentielle ici pour permettre d’associer les circuits. Par ailleurs, le nouveau module formé expose le même interface que ses parties : A et B en entrées, et SUP et EQ en sorties, ce qui permet à nouveau et récursivement d’associer ces circuits. Comme dans les disciplines logicielles de l’informatique, il est intéressant de trouver le bon interface à un module, qui va permettre une bonne réutilisation.
56 module cmpu(a[9..0], b[9..0]: sup, eq) cmpu5(a[9..5], b[9..5]: sup1, eq1); cmpu5(a[4..0], b[4..0]: sup2,eq2); eq=eq1*eq2; sup=sup1+eq1*sup2; end module
Figure II.58. Écriture SHDL d’un comparateur non signé sur 10 bits formé par association de deux comparateurs non signés de 5 bits.
9. Exercices corrigés 9.1. Exercice 1 : simplifications algébriques Énoncé Simplifier algébriquement : 1.
− − − − F1 = ABC + AC + AB + A B C
2.
−− − F2 = ABC D + ABD + AC + BC + AC
3.
− − − − − F3 = A + BC + ADE + ABCDE + BCDE + ACD Solution 1.
2.
− − − − 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 −− − 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 − F3 = A + BC − F3 = A + BC − F3 = A + BC − F3 = A + BC
57 − − − + BCDE + CD (absorption entre A et ACD) −− + D(BC E + C) − − + D(BE + C) (absorption de C) − + BDE + CD
Avec des tables de Kanaugh, des simplifications non adjascentes donneraient le même résultat.
9.2. Exercice 2 : utilisation des tables de Karnaugh Énoncé Concevoir un circuit qui détecte la présence d’un chiffre décimal (entre 0 et 9) sur ses entrées A, B, C, D. Solution La table de vérité de ce détecteur donne ’1’ pour les valeurs de (0,0,0,0) à (1,0,0,1), et ’0’ pour les valeurs suivantes (figure II.59).
A 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1
B 0 0 0 0 1 1 1 1 0 0 0 0 1 1 1 1
C 0 0 1 1 0 0 1 1 0 0 1 1 0 0 1 1
D 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1
S 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0
Figure II.59. Table de vérité d’un détecteur de chiffre décimal. On peut maintenant dessiner la table de Karnaugh correspondante, et chercher le plus petit nombre de regroupements qui recouvrent tous les ’1’ (figure II.60). Il y a deux regroupements, ce qui donne l’expression simplifiée :
CHAPITRE II. ÉLÉMENTS DE LOGIQUE COMBINATOIRE
58
B.C.D A,B 0,0 0,1 1,1 1,0
C,D 0,0
1
1
0,1
1
1
1,1
1
1
1,0
1
1
1
A
Figure II.60. Table de Karnaugh avec les regroupements pour le détecteur de chiffres décimaux. − −− S = A + BC D On peut trouver le même résultat de façon algébrique : − − S = ((A, B, C, D) ≤ 8) + ((A, B, C, D) = 9)) = A + ABCD Par absorption de A : − − S = A + BCD
9.3. Exercice 3 : analyse inductive des tables de vérité Énoncé Concevoir les transcodeurs suivants : 1.
Binaire pur vers Gray réfléchi.
2.
Gray réfléchi vers binaire pur.
Solution La figure II.61montre la table de vérité qui relie une valeur binaire (X,Y,Z,T) à un code de Gray (A,B,C,D). Le code de Gray a été construit selon la méthode récursive décrite à la section 5.4. On pourrait dessiner des tables de Karnaugh, mais elles ne donneraient rien ici. Par ailleurs on va voir que les codes de Gray sont très étroitement associés à l’opérateur XOR, et on va essayer de deviner les relations directement. 1.
Binaire pur vers Gray réfléchi. On voit tout de suite que A = X. B est égal à Y pendant la première moitié de la − table, puis il est égal à Y. ’Être dans la première moitié de la table’ est gouverné par X ; en résumé B est égal à Y avec une inversion commandée par X. C’est XOR l’opérateur d’inversion commandée, donc : B = X ⊕ Y. De façon analogue, on remarque que C est égal à Z lorsque Y vaut 0, et est égal à
9. Exercices corrigés
59 X 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1
Y 0 0 0 0 1 1 1 1 0 0 0 0 1 1 1 1
Z 0 0 1 1 0 0 1 1 0 0 1 1 0 0 1 1
T 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1
A 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1
B 0 0 0 0 1 1 1 1 1 1 1 1 0 0 0 0
C 0 0 1 1 1 1 0 0 0 0 1 1 1 1 0 0
D 0 1 1 0 0 1 1 0 0 1 1 0 0 1 1 0
Figure II.61. Binaire ↔ code de Gray. − Z lorsque Y vaut 1, donc : C = Y ⊕ Z. On trouve de même D = Z ⊕ T. 2.
Gray réfléchi vers binaire pur. On a bien sûr X = A, et on voit aussi que Y = A ⊕ B. Par contre pour Z, il est bien − égal à C ou C, mais l’inversion n’est pas simplement commandée par B. Il y a inversion durant le deuxième et le troisième quart de la table : cela évoque un XOR. Il y a en fait inversion lorsque A ⊕ B = 1, donc : Z = A ⊕ B ⊕ C. Par induction on devine, et on vérifie ensuite, que T = A ⊕ B ⊕ C ⊕ D.
9.4. Exercice 4 : circuit incrémenteur Énoncé Concevoir un circuit combinatoire prenant en entrée un nombre binaire a3 … a0 et donnant en sortie b3 …b0 la valeur d’entrée incrémentée de 1. Écrire le code SHDL correspondant. Solution L’algorithme de base de l’incrémentation a été donné en section 4. Le bit de poids faible b0 est toujours inversé par rapport à a0. Ensuite, bn est l’inverse de an si et seulement si tous les bits qui sont à la droite de an valent 1, soit lorsque an − 1…a0 = 1. On a donc une inversion commandée par cette valeur, et on a vu en section 4 que le XOR est la porte adaptée à cette opération : bn = an ⊕ (an − 1…a0)
CHAPITRE II. ÉLÉMENTS DE LOGIQUE COMBINATOIRE
60 Finalement, sur 4 bits : b0 = a0
b1 = a1 ⊕ a0
b2 = a2 ⊕ (a1a0 )
b3 = a3 ⊕ (a2 a1a0 ) Exprimé sous forme de sommes de termes :
b0 = a0 b =a− a +− aa 1
1 0
3
3 2 1 0
1 0
a2 a1a0 + a2 − a1a0 = − a2 a1a0 + a2 (− a1 + − a0 ) = − a2 a1a0 + a2 − a1 + a2 − a0 b2 = − b =− aaaa +a− aaa =− aaaa +a− a +a− a +a− a 3 2 1 0
3 2 1 0
3 2
3 1
Finalement, le module SHDL correspondant est :
module incr(a3..a0: b3..b0) b0=/a0; b1=/a1*a0+a1*/a0; b2=/a2*a1*a0+a2*/a1+a2*/a0; b3=/a3*a2*a1*a0+a3*/a2+a3*/a1+a3*/a0; end module
3 0
Chapitre III Éléments de logique séquentielle 1. Définition Un circuit est dit séquentiel, si les valeurs de ses sorties ne dépendent pas que des valeurs de ses entrées. C’est donc le contraire d’un circuit combinatoire, dont les valeurs des sorties ne dépendaient que de celles de ses entrées, avec une propagation sans retour arrière des signaux des entrées vers les sorties. À l’inverse, les sorties d’un circuit séquentiel dépendent non seulement des valeurs des entrées au moment présent, mais aussi d’un état interne qui dépend de l’historique des valeurs d’entrées précédentes. En vertu de ce que nous avions démontré pour les circuits combinatoires, cela implique un rebouclage vers l’arrière de certains signaux internes au circuit (figure III.1).
entrées
sorties
... ...
Figure III.1. Circuit séquentiel : certains signaux internes ou sorties rebouclent en arrière. Ce type de circuit peut donner lieu à des fonctionnements très complexes ; il peut également être instable dans certaines configurations. Plus encore que pour les circuits combinatoires, des méthodes pour maîtriser leur complexité sont nécessaires. Une des voies pour la simplification de leur conception consiste à synchroniser les changements d’états sur les fronts d’une horloge unique et globale, et cela conduit à l’étude des circuits séquentiels dits synchrones purs, qu’on étudiera principalement dans ce livre.
61
CHAPITRE III. ÉLÉMENTS DE LOGIQUE SÉQUENTIELLE
62
2. Latch RS La notion de circuit séquentiel avec ses états internes incorpore la notion de mémoire (retour sur son passé). La cellule mémoire la plus simple est le latch RS (RS étant les initiales de Reset-Set), parfois appelé bistable (figure III.2. module latch_rs(s,r: q) q=/nq*/r ; nq=/q*/s ; end module
Figure III.2. Latch RS, cellule mémoire de 1 bit. Ce circuit a clairement des connexions qui reviennent en arrière. Il s’agit d’une cellule de mémorisation de 1 bit, dont le mode d’emploi est le suivant : 1.
2.
3.
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. − 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). − pour mémoriser 1 (c’est à dire que les sorties soient dans l’état Q, Q = (1, 0) lorsque la cellule reviendra dans le mode ’lecture’), il faut appliquer un 1 sur S (set), puis le remettre à 0. Quelqu’ait été son état antérieur, la cellule se retrouve alors dans l’état − auto-stable Q, Q = (1, 0).
Ce circuit est bien séquentiel : ses sorties ne dépendent pas uniquement de ses entrées. La notion d’état interne est ici clairement présente sous la forme d’un bit mémorisé. Graphe d’états Les modes de fonctionnement décrits précédemment peuvent être résumés dans le graphe de la figure III.3. Un tel graphe est appelé graphe d’état du circuit ; il synthétise complètement son fonctionnement. Les deux noeuds correspondent aux deux états stables du circuit ; les arcs montrent les passages du circuit d’un état vers un autre lors d’un changement des valeurs des entrées. La valeur de la sortie Q est associée à chacun des deux états : 0 pour l’état a et 1 pour l’état b. On ne représente que les états stables du circuit, et non les configurations intermédiaires fugitives entre deux états.
2. Latch RS
63 Q=0 (R, S) = (1, 0)
(R, S) = (0, 0)
a
(R, S) = (0, 1) (R, S) = (1, 0) (R, S) = (0, 1)
(R, S) = (0, 0) b Q=1
Figure III.3. Graphe d’états d’un latch RS. Les noeuds correspondent aux états stables du circuit, et les arcs représentent les transitions entre les états.
3. Fronts et niveaux ; signaux d’horloge Le niveau d’un signal, c’est sa valeur 0 ou 1. On parle de front lors d’un changement de niveau : front montant lorsque le signal passe de 0 à 1, front descendant lorsqu’il passe de 1 à 0. En pratique, ces transitions ne sont pas tout à fait instantanées, mais leur durée est très inférieure aux temps de propagation des portes (figure III.4). niveau ’1’
1
1 niveau ’0’
0
0 t front montant : dérivée très positive
t
front descendant : dérivée très négative
Figure III.4. Fronts et niveaux d’un signal réel, et leur équivalent logique simplifié. Une horloge est un signal périodique, produit à partir d’un quartz ou d’un réseau RC. Il produit donc une succession périodique de fronts montants et/ou descendants. Comme en physique ou en mathématiques, on emploie les termes de fréquence et de période pour qualifier la chronologie d’un signal d’horloge ; elle a généralement une forme carrée, mais ce n’est pas obligatoire.
4. Circuits séquentiels synchrones et asynchrones : définitions
CHAPITRE III. ÉLÉMENTS DE LOGIQUE SÉQUENTIELLE
64 Circuits asynchrones purs
On appelle circuits séquentiels asynchrones purs des circuits séquentiels sans signaux d’horloge, et dans lesquels ce sont les changements de valeur des entrées qui sont à la source des changements d’états. Le latch RS est un exemple de circuit asynchrone pur : il n’est pas gouverné par une horloge, et ce sont les changements des entrées qui provoquent les changements d’état. Circuits synchrones purs On appelle circuit séquentiels synchrones purs des circuits séquentiels qui possèdent une horloge unique, et dont l’état interne se modifie précisément après chaque front (montant ou descendant) de l’horloge. Les circuits séquentiels synchrones purs sont plus simples à étudier et à concevoir que les circuits séquentiels asynchrones purs, car le moment de la transition est défini de l’extérieur et sur un seul signal. Ils peuvent par contre être moins rapides qu’un équivalent asynchrone, et consommer plus d’énergie. En effet, on se rappelle (section 3) que les circuits CMOS consomment essentiellement du courant lors des changements d’états. Un circuit séquentiel synchrone consommera ainsi du courant à chaque front d’horloge, contrairement à un circuit asynchrone pur. Cet effet est toutefois à relativiser avec certains circuits CMOS dont on a beaucoup abaissé la tension d’alimentation, et pour lesquels le courant de fuite (consommé au repos) est proche du courant de commutation (consommé lors des changements). Autres circuits séquentiels Les circuits séquentiels qui ne sont, ni synchrones purs, ni asynchrones purs, n’ont pas de dénomination particulière. Ils sont souvent l’union de plusieurs sous-ensembles qui sont chacun gouvernés de façon synchrone par une horloge, mais l’interface entre ces sous-domaines est souvent la cause de problèmes de fiabilité. On n’étudiera pas de tels circuits dans cet ouvrage.
5. Graphes d’états Considérons le fonctionnement du circuit séquentiel synchrone de la figure III.5, détecteur de la séquence ’1,0,1’ :
E
S
CLK
Figure III.5. Détecteur de séquence 1,0,1. Une suite de chiffres binaires arrive sur E, et les instants d’échantillonnage (moments où la valeur de E est prise en compte) sont les fronts montants de CLK. On souhaite que la sortie S vaille 1 si et seulement si les deux dernières valeurs de E sont : 1,0,1. Cette spécification est en fait imprécise, car elle ne dit pas exactement à quel moment par rapport à l’horloge CLK la sortie doit prendre sa valeur. Il y a deux possibilités :
5. Graphes d’états 1.
65
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 1
...100
0
c S=0 0
a ...000
S=0 1
0
...010
S=1 f
1
0
...101
b ...001
e
0
S=0
1
1 1 S=0
...011
g
0
d 1
S=0
0
...110
S=0 h
1
...111
Figure III.6. Détecteur de séquence 1,0,1 de type Moore, non simplifié. Chaque état est associé à la réception d’une certaine séquence des 3 derniers bits. Seul l’état g provoque la sortie S = 1. La valeur de la sortie S est clairement associée aux états, et ne peut donc changer qu’avec eux. Les changements d’état se produisent à chaque front de l’horloge CLK, qui n’est pas représentée sur la graphe, mais dont la présence est implicite. Un arc libellé ’0’ par exemple indique un changement d’état lorsque E = 0. 2.
la sortie dépend de l’état interne du circuit et de ses entrées, et on dit alors qu’il s’agit d’un schéma de MEALY. Pour le détecteur de séquence par exemple, la sortie S pourrait valoir 1 dès que E passe à 1 pour la deuxième fois, avant même que sa valeur ne soit ’officiellement’ échantillonnée au prochain front d’horloge. Cela conduit au graphe de MEALY de la figure III.7. L’arc libellé ’1/0’ de a vers b par exemple indique un changement d’état de a vers b lorsque E = 1, avec une valeur de la sortie S=0. Mais attention : le changement d’état ne se produira qu’au prochain front d’horloge, alors que le changement de sortie se produit immédiatement après le changement des entrées.
Différences entre les circuits de MOORE et de MEALY La principale différence tient dans le fait que dans un circuit de MEALY, on obtient les sorties un temps d’horloge plus tôt. Par exemple dans la transition de c vers f pour E = 1, la sortie S = 1 sera échantillonnable (= mémorisée et utilisable) au front d’horloge suivant, en même temps que l’état courant passera à f . Dans le circuit de MOORE, la sortie passera à 1 après le front d’horloge suivant, et ne sera stable et échantillonnable qu’au front d’horloge encore ultérieur, c’est à dire un temps plus tard que dans le circuit de MEALY.
CHAPITRE III. ÉLÉMENTS DE LOGIQUE SÉQUENTIELLE
66 0/0
e
0/0
...100
1/0
0/0
c 0/0 0/0
1/0
a ...000
...010
0/0
...101
b ...001
f
1/1 1/0
1/1
1/0 0/0
g ...110
d
0/0
...011
1/0 h
1/0
...111
Figure III.7. Détecteur de séquence 1,0,1 de type Mealy, non simplifié. Pour certains types de circuits, il est plus facile de dessiner un graphe de MOORE, qui correspond à une vision plus ’statique’ du problème. La bonne nouvelle est qu’il existe une méthode automatique de transformation d’un graphe de MOORE en graphe de MEALY, lorsque l’on veut profiter des avantages de ce dernier. Transformation d’un graphe de Moore en graphe de Mealy On voit sur la figure III.8 comment un arc dans un graphe de MOORE se transforme en un arc dans un graphe de MEALY équivalent. S a
e
b
a’
e/S
b’
Figure III.8. Un arc d’un graphe de MOORE transformé en un arc équivalent d’un graphe de MEALY. Si avec l’entrée e on va en a associé à une sortie S, alors cette sortie peut être directement attachée à l’arc : e/S. On a donc une méthode automatique pour transformer les graphes de MOORE en graphes de MEALY, très utile car les graphes de MOORE sont souvent plus faciles à concevoir. C’est elle qu’on a utilisée par exemple pour transformer le graphe de MOORE de la figure III.6 en graphe de MEALY (figure III.7). Transformation d’un graphe de Mealy en graphe de Moore Ce n’est pas toujours possible, et donc aucune méthode générale ne peut exister. Les circuits de MEALY sont donc intrinsèquement plus riches que ceux de MOORE.
6. Tables de transitions Les tables de transitions, encore appelées tables de Huffman ne sont rien d’autre qu’une version tabulaire des graphes de transitions. Par exemple, les tables associées aux graphes de MOORE et de MEALY du détecteur de séquence sont représentées figure III.9 et III.10.
6. Tables de transitions
67
avant état a a b b c c d d e e f f g g h h
E 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1
après état a b c d e f g h a b c d e f g h
état a b c d e f g h
S 0 0 0 0 0 1 0 0
Figure III.9. Table de transitions du graphe de MOORE pour le détecteur de séquence 1,0,1. À chaque arc du graphe correspond une ligne dans la table. On notera la table complémentaire,qui donne les sorties associées à chaque état. avant état a a b b c c d d e e f f g g h h
E 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1
après état a b c d e f g h a b c d e f g h
S 0 0 0 0 0 1 0 0 0 0 0 0 0 1 0 0
Figure III.10. Table de transitions du graphe de MEALY pour le détecteur de séquence 1,0,1. À chaque transition du graphe correspond une ligne de la table.La valeur de la sortie S est associée à chaque transition. Simplification des tables de transition Une table de transitions permet notamment de repérer facilement des états équivalents. Un groupe G d’états sont dits équivalents si, pour chaque combinaison possible des entrées, ils conduisent à des états du groupe G avec les mêmes sorties. On peut alors diminuer le nombre d’états en remplaçant tous les états du groupe G par un seul, puis tenter de rappliquer
CHAPITRE III. ÉLÉMENTS DE LOGIQUE SÉQUENTIELLE
68
cette procédure de réduction sur l’ensemble d’états réduit. Par exemple, sur la table de transitions précédente associée au diagramme de MOORE, on constate que a et e sont équivalents, que c et g sont équivalents, que d et h sont équivalents. Attention : b et f ne sont pas équivalents car, bien qu’ils aient mêmes états suivants, ils sont associés à des sorties différentes. On peut réécrire la table de transitions en supprimant les lignes associées à e, g et h, et en remplaçant partout e par a, g par c, h par d (figure III.11).
avant état a a b b c c d d f f
E 0 1 0 1 0 1 0 1 0 1
après état a b c d a f c d c d
état a b c d f
S 0 0 0 0 1
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 état a a b b c c f f
E 0 1 0 1 0 1 0 1
après état a b c b a f c b
état a b c f
S 0 0 0 1
Figure III.12. Table de transitions du détecteur de séquence 1,0,1 après une deuxième phase de simplification. Le processus de simplification s’arrête ici. Finalement, 4 états suffisent pour ce détecteur de séquence. Ceux-ci ont perdu la signification qu’ils avaient initialement dans le graphe à 8 états. Le graphe simplifié est représenté figure III.13. Cette procédure de simplification s’applique également aux graphes de MEALY, en cherchant des identités dans les couples état/sortie. Sur l’exemple précédent, on constate que a et e sont équivalents, que b et f sont équivalents, que c et g sont équivalents, et que d et h sont équivalents. En procédant aux suppressions et substitutions associées, on trouve la
6. Tables de transitions
69 1
1
0 S=0 0
a
1
0
b
1
c
S=0
S=0
f S=1
0
Figure III.13. Graphe de MOORE simplifié du détecteur de séquence 1,0,1. nouvelle table (figure III.14). avant état a a b b c c d d
E 0 1 0 1 0 1 0 1
après état a b c d a b c d
S 0 0 0 0 0 1 0 0
Figure III.14. Table de transitions de MEALY pour le détecteur de séquence 1,0,1, après une première phase de simplification. Cette fois, on voit que b et d sont équivalents (figure III.15). avant état a a b b c c
E 0 1 0 1 0 1
après état a b c b a b
S 0 0 0 0 0 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
CHAPITRE III. ÉLÉMENTS DE LOGIQUE SÉQUENTIELLE
70 0/0
a
1/0 1
b
0/0 1/1
c
0/0
Figure III.16. Graphe de MEALY simplifié du détecteur de séquence 1,0,1. les briques de base à utiliser. Une bascule mémorise un seul bit et permet de représenter seulement deux états distincts, mais l’utilisation d’un vecteur de n bascules permet de coder jusqu’à 2n états distincts. Par exemple, pour le détecteur de séquence analysé à la section précédente, le graphe de Moore simplifié comportait 4 états, et un vecteur de deux bascules va permettre de les encoder. Par ailleurs, pour que tout un vecteur de bascules évolue de façon fiable d’état en état, il est souhaitable que chacune d’elles ait une entrée d’horloge reliée à une horloge commune (circuit synchrone), et ne puisse changer d’état qu’au moment précis d’un front de cette horloge (montant ou descendant), et jamais entre deux fronts, même si les autres entrées du circuit sont modifiées plusieurs fois entre temps. De tels circuits sont de conception difficile ; on les présente dans cette section.
7.1. Latch RS actif sur un niveau d’horloge Dans le but de rendre synchrone le latch RS vu précédemment, on peut limiter la période d’écriture (figure III.17).
Figure III.17. Latch RS. L’état de ce circuit est modifiable lorsque H = 1, c’est à dire sur un niveau du signal H. Tant que H est au niveau 1, les modifications de S et de R vont avoir un effet sur l’état du bistable. Un tel latch peut avoir certaines applications, mais il n’est pas adapté à la réalisation de circuits séquentiels synchrones, où les changements d’états de leurs différentes parties doivent se produire de façon parfaitement synchronisée, au moment précis défini par un front d’une horloge commune. La section suivante va présenter un tel circuit.
7. Bascules synchrones
71
7.2. Bascule active sur front d’horloge Nous avons donc besoin de composants séquentiels qui changent d’état sur un front d’horloge, et seulement à ce moment précis. Le latch RS vu à la section précédente ne remplissait pas cette condition, puisque le bistable pouvait changer de valeur tant que H était au niveau 1. Le circuit de la figure III.18, qui réalise ce qu’on va appeler une bascule D (D pour ’delay’), ne change d’état quant à lui que sur le front descendant de l’horloge H.
module basculeD(h,d: q) s=/h*/r*/ns ; ns=/s*/d ; r=/nr*/h ; nr=/ns*/r ; q=/r*/nq ; nq=/s*/q ; end module
Figure III.18. Bascule D synchrone : l’état du bistable q ne change qu’au moment où h passe de 1 à 0. La compréhension détaillée du fonctionnement de ce circuit est difficile, et l’utilisation d’un simulateur tel que celui de SHDL peut y aider. On peut déjà remarquer que lorsque h vaut 1, on a nécessairement R=0 et S=0, et donc le bistable final qui produit q est dans l’état de repos : tant que h vaut 1, l’état interne q ne peut pas changer, même si d change de valeur un nombre quelconque de fois. Lorsque h passe de 1 à 0, on peut montrer que le bistable − du haut va stocker la valeur de D et la placer sur R, et que celui du bas va stocker la valeur de D et la placer sur S. On a donc au moment du front descendant un ’set’ ou un ’reset’ du bistable final, dans le sens de la mémorisation de la donnée d en entrée. Enfin, et c’est le point important, on peut montrer également que lorsque h est maintenu à 0, les valeurs de R et de S ne peuvent pas changer même si d change, et donc les changements de d n’affectent pas le bistable final. On a donc bien démontré la propriété essentielle de ce circuit, qui est que son état interne q est affecté durant la période très courte où l’horloge h passe de 1 à 0, c’est à dire durant un front descendant de celle ci, et que tous les changements sur d en dehors de cette période n’ont pas d’effet sur q.
7.3. Bascule D Une telle bascule D est représentée couramment par le schéma de la figure ?? On a indiqué également l’écriture SHDL associée à cette bascule. Le signal de remise à zéro RST n’était pas présent sur la figure III.18. Il s’agit d’une remise à zéro asynchrone, qui provoque le forçage à 0 du contenu de la bascule, sans qu’il y ait besoin d’un front d’horloge. Tant que le signal RST est actif (sur niveau 1 sur la figure), la bascule est forcée à 0 et son contenu ne peut pas évoluer. Il s’agit du même signal reset que celui qui est présent sur votre micro-ordinateur, votre téléphone portable, etc. : en
CHAPITRE III. ÉLÉMENTS DE LOGIQUE SÉQUENTIELLE
72
module test(e,h,reset: x) x:=e ; x.clk=h ; x.rst=reset ; end module
Figure III.19. Représentation habituelle d’une bascule D synchrone. l’appliquant, vous remettez dans l’état 0 tous les composants séquentiels de votre machine. Du point de vue graphique, le triangle devant l’entrée CLK indique un signal d’horloge. S’il était précédé d’un rond, cela indiquerait une horloge active sur front descendant ; en son absence elle est active sur front montant. De la même façon, un rond devant l’entrée RST indique une activité sur niveau bas. La figure III.20 illustre ces différentes situations.
module test(e,h,reset: x) x:=e ; x.clk=/h ; x.rst=reset ; end module
module test(e,h,reset: x) x:=e ; x.clk=h ; x.rst=reset ; end module
module test(e,h,reset: x) x:=e ; x.clk=/h ; x.rst=/reset ; end module
(a)
(b)
(c)
Figure III.20. Différentes bascules D. (a) horloge sur front descendant. (b) reset actif sur niveau bas. (c) horloge front descendant et reset actif sur niveau bas. Bascules et langage SHDL En SHDL, la notation ’:=’ est appelée affectation séquentielle, et les suffixes ’.clk’ et ’.rst’ servent à repérer les signaux d’horloge et de remise à zéro. Des horloges actives sur front descendant, ou des reset actifs sur niveau bas se traduisent par un signe ’/’ devant le nom du signal d’horloge ou de reset. Par ailleurs, on notera qu’on ne peut pas écrire quelque chose comme : x.clk = a*b ; // incorrect
Cela impliquerait l’existence d’une bascule et d’une porte combinatoire, ce qui doit se traduire par deux équations distinctes : x.clk = i ; i = a*b ;
Lorsqu’on écrit x.clk = /h ;, on ne fait pas appel à une porte supplémentaire d’inversion : on utilise une bascule active sur front descendant, et donc on n’a pas à créer de
7. Bascules synchrones
73
ligne supplémentaire. Pour les mêmes raisons, on ne doit pas écrire : x:=a*b ;, mais : x := i ; i = a*b ;
On a tout de même la possibilité d’écrire x:=/i ;, qui correspond à une bascule D qui charge l’inverse de son entrée, et qui se traduit par la présence d’un rond devant l’entrée D (figure III.21).
module test(e,h,reset:x) x:=e ; x.clk=/h ; x.rst=reset ; end module
module test(e,h,reset:x) x:=/e ; x.clk=h ; x.rst=reset ; end module
(a)
(b)
Figure III.21. Les deux formes possibles d’une bascule D. (a) entrée normale. (b) entrée inversée. Équation d’évolution L’équation d’évolution d’une bascule, c’est l’équation de la valeur que prendra l’état après le front d’horloge. Dans le cas de la bascule D c’est tout simplement la valeur présente à son entrée : Q := D
7.4. Bascule T Tout comme la bascule D, la bascule T (trigger) mémorise également un bit de façon synchrone, mais elle a des modalités différentes d’utilisation. Son schéma, une table de transitions synthétique et l’écriture SHDL associée sont donnés figure III.22. L’état mémorisé de la bascule T s’inverse (après le front d’horloge) si et seulement si son entrée T vaut 1. Lorsque son entrée T vaut 0, cet état reste inchangé.Elle est donc adaptée aux problématiques de changements d’une valeur et non à un simple stockage comme la bascule D. En dehors des variations sur les fronts d’horloge et la ligne de reset asynchrone, les deux seules versions possibles de la bascule T sont données figure III.23. Équation d’évolution − − Q := T Q + T Q
CHAPITRE III. ÉLÉMENTS DE LOGIQUE SÉQUENTIELLE
74
avant
après
T
Q
Q
0
x
1
x
x −x
module test(e,h,reset: x) x:=/e*x+e*/x ; x.clk=h ; x.rst=reset ; end module
Figure III.22. Bascule T synchrone. Lorsque l’entrée T est à 0 elle ne change pas d’état ; lorsque l’entrée T vaut 1 son état s’inverse.
module test(e,h,reset: x) x:=/e*x+e*/x ; x.clk=h ; x.rst=reset ; end module
module test(e,h,reset: x) x:=e*x+/e*/x ; x.clk=h ; x.rst=reset ; end module
(a)
(b)
Figure III.23. Les deux formes possibles d’une bascule T. (a) entrée normale. (b) entrée inversée. On notera la différence d’écriture en langage SHDL. Le premier terme exprime bien que, si T est à 0, Q ne change pas, et que si T est à 1, Q s’inverse. On retrouve l’écriture SHDL de l’affectation séquentielle.
7.5. Bascule JK Comme les bascules D et T, la bascule JK (Jack-Kilby) mémorise un bit de façon synchrone. Son schéma, sa table de transitions simplifiée et l’écriture SHDL associée sont donnés figure III.24.
avant
après
J
K
Q
Q
0
0
x
x
0
1
-
0
1
0
-
1
1
x
1 −x
module test(reset,h,j,k:x) x:=/k*x+j*/x ; x.clk=h ; x.rst=reset ; end module
Figure III.24. Bascule JK synchrone,table de transitions simplifiée et écriture SHDL.Selon les valeurs de ses entrées J et K, elle permet le forçage de son état à 0 ou à 1, mais aussi la conservation ou l’inversion de son état.
7. Bascules synchrones
75
Elle fonctionne de façon analogue au latch RS, J jouant le rôle de S et K de R. Lorsque J et K sont à 0, l’état de la bascule ne change pas au front d’horloge. Si on veut faire une mise à 1ou une mise à 0, on applique 1sur J (respectivement sur K) puis on applique un front d’horloge. De plus, mettre à la fois J et K à 1 est autorisé, et l’état de la bascule s’inverse au front d’horloge. Équation d’évolution − − Q := K Q + J Q Elle est moins immédiatement évidente que celle des bascules D et T. On vérifie que dans tous les cas, on a le résultat attendu : •
− − si J = 0 et K=0, K Q + J Q = 0 + Q = Q
•
− − si J = 0 et K=1, K Q + J Q = 0 + 0 = 0
•
− − − si J = 1 et K=0, K Q + J Q = Q + Q = 1
•
− − − − si J = 1 et K=1, K Q + J Q = 0 + Q = Q
La bascule JK existe également avec des entrées J et K inversées. La figure III.25 montre ces différentes formes, et l’écriture SHDL associée.
7.6. Choix des bascules La bascule D est clairement adaptée aux situations où on doit stocker un bit présent. La bascule T est adaptée aux situations qui se posent en termes de changement ou d’inversion. La bascule JK semble plus versatile, et elle est d’ailleurs souvent appelée bascule universelle. Par analogie avec les portes combinatoires, on pourrait se demander si cette bascule JK est ’plus puissante’ que les bascules D ou T, c’est à dire s’il est possible de fabriquer avec elle des circuits que les autres ne pourraient pas faire. La réponse est non. Ces trois types de bascules ont une puissance d’expression équivalente : elle mémorisent un bit, et elles ne diffèrent que par les modalités de stockage de ce bit. Par exemple, on vérifie facilement qu’on peut fabriquer une bascule JK avec une bascule T et réciproquement (figure III.26). Dans le cas (a), il est clair qu’en reliant les deux entrées J et K, la bascule reste dans le même état si t vaut 0, et elle s’inverse si t vaut 1, ce qui correspond bien au fonctionnement d’une bascule T. Dans le cas (b), on a figuré la solution avec un multiplexeur pour plus de clarté. La valeur mise en entrée de la bascule T dépend de la valeur du couple j, k, et on vérifie qu’on a le résultat souhaité dans tous les cas : 1
si jk = 00, on place 0 sur l’entrée de la bascule T et elle ne changera pas de valeur au front d’horloge.
2
Si jk = 10, on place sur l’entrée de la bascule T l’inverse de son état interne, et il est facile de voir que cela conduit à la mise à 1 de la bascule au prochain front d’horloge.
3
Si jk = 01, on place l’état courant de la bascule T sur son entrée, ce qui conduit à la
CHAPITRE III. ÉLÉMENTS DE LOGIQUE SÉQUENTIELLE
76
module test(reset,h,j,k: x) x:=/k*x+j*/x ; x.clk=h ; x.rst=reset ; end module
module test(reset,h,j,k: x) x:=/k*x+/j*/x ; x.clk=h ; x.rst=reset ; end module
(a)
(b)
module test(reset,h,j,k: x) x:=k*x+j*/x ; x.clk=h ; x.rst=reset ; end module
module test(reset,h,j,k: x) x:=k*x+/j*/x ; x.clk=h ; x.rst=reset ; end module
(c)
(d)
Figure III.25. Les différentes formes possibles d’une bascule JK et leurs écritures SHDL associées. (a) entrée normales. (b) entrée J inversée. (c) entrée K inversée. (d) entrées J et K inversées.
(a)
(b)
Figure III.26. (a) construction d’une bascule T avec une bascule JK,(b) construction d’une bascule JK avec une bascule T. forcer à 0 au prochain front. 4
Enfin si jk = 11, on place 1 en entrée de la bascule T ce qui va déclencher son inversion.
7. Bascules synchrones
77
7.7. Schéma général d’un circuit séquentiel synchrone de type MOORE La figure III.27 montre le schéma général d’un circuit séquentiel synchrone de type MOORE. L’état interne est mémorisé dans un vecteur de n bits (2n état au plus) constitué de bascules de type quelconque. Pour que ce circuit soit bien de type synchrone pur, toutes les horloges des bascules ont bien été reliées ensemble directement à l’horloge générale, de façon à garantir un changement d’état de toutes les bascules exactement au même moment. Les propriétés des bascules garantissent que cet état ne peut pas changer entre deux fronts d’horloge, même si les entrées du circuit changent plusieurs fois entre temps. Par ailleurs on voit que les sorties ne dépendent que de l’état interne, étant un simple transcodage combinatoire de celui-ci. entrées CLK
état interne
m
Q
transcodeur
Q
p
sorties
Q
n
Figure III.27. Schéma général d’un circuit séquentiel synchrone de type MOORE.
7.8. Schéma général d’un circuit séquentiel synchrone de type MEALY La figure III.28 montre le schéma général d’un circuit séquentiel synchrone de type MEALY. Il s’agit également d’un circuit synchrone pur, dans lequel les horloges de toutes les bascules sont reliées entre elles à l’horloge générale.Avec un vecteur d’état composé de n bascules, on peut également coder au plus 2n états différents, et la mécanique de changement d’états est analogue à celle d’un circuit de type MOORE. La seule différence est dans le calcul des sorties : on voit qu’elles ne dépendent pas que de l’état interne, mais qu’elles dépendent aussi des entrées. C’est ce qui va leur permettre d’obtenir les mêmes sorties qu’un circuit de MOORE, un temps plus tôt.
8. Synthèse d’un circuit séquentiel synchrone 8.1. Étapes de la synthèse d’un circuit séquentiel synchrone Lors de la synthèse d’un circuit séquentiel synchrone à l’aide de bascules, qu’il soit de type MOORE ou de type MEALY, on obtiendra le circuit le plus simple en suivant les étapes suivantes : 1
dessin du graphe d’états : il permet une spécification claire du circuit.
CHAPITRE III. ÉLÉMENTS DE LOGIQUE SÉQUENTIELLE
78 entrées
m n
CLK
Q
transcodeur
p sorties
Q
Q
état interne
Figure III.28. Schéma général d’un circuit séquentiel synchrone de type MEALY. 2
table de transitions : forme plus lisible du graphe, qui permet de vérifier qu’aucune transition n’est oubliée, et prépare la phase de simplification.
3
simplification de la table de transitions : on applique la méthode vue précédemment, parfois en plusieurs étapes.
4
détermination du nombre de bascules : le nombre d’états du graphe simplifié permet d’établir un nombre minimum n de bascules à employer. On ne choisit pas encore le type des bascules à employer, car la phase d’assignation fournira de nouvelles informations.
5
assignation des états : pour chaque état, un vecteur unique de n bits est assigné, appelé vecteur d’état. Des règles heuristiques d’assignation permettent de trouver celle qui conduira à des calculs simples.
6
table de transitions instanciée : on réécrit la table de transitions, en remplaçant chaque symbole d’état par le vecteur associé.
7
choix des bascules : en observant la table de transitions instanciée, certains types de bascules peuvent être préférés. On emploiera une bascule D lorsqu’un état suivant est la recopie d’une valeur de l’état précédent, une bascule T lorsque l’état suivant est l’inverse d’un état précédent ; dans les autres cas, la bascule JK peut être employée.
8
calcul des entrées des bascules et calcul des sorties du circuit.
8.2. Synthèse du détecteur de séquence, version MOORE A titre d’exemple, on va détailler la synthèse du circuit détecteur de la séquence 1,0,1 décrit précédemment, en version MOORE. On reprend donc toutes les étapes énumérées en introduction. Graphe d’état, table de transitions, simplification Ces trois étapes ont été réalisées en section 6, et ont abouties au graphe simplifié et à la table de transitions des figures III.29 et III.30.
8. Synthèse d’un circuit séquentiel synchrone
79 1
1
0 S=0 0
a
1
0
b
1
c
S=0
S=0
f S=1
0
Figure III.29. Graphe de MOORE simplifié du détecteur de séquence 1,0,1.
avant état a a b b c c f f
E 0 1 0 1 0 1 0 1
après état a b c b a f c b
état a b c f
S 0 0 0 1
Figure III.30. Table de transitions simplifiée du détecteur de séquence 1,0,1. Détermination du nombre de bascules Il y a quatre états à coder, donc 2 bascules seront employées, dont on appellera les sorties X et Y. Assignation des états La phase d’assignation consiste donc à affecter un vecteur d’état à chacun des états du circuit ; ici un couple (X,Y) pour chacun des 4 états. N’importe quelle assignation peut être employée. Néanmoins, on peut toujours affecter à l’état initial le vecteur d’état (0,0,…), qui sera forcé lors d’un RESET asynchrone du circuit. Par ailleurs, certaines assignations donnent lieu à des calculs plus simples que d’autres. On les trouve souvent en appliquant les règles heuristiques suivantes, par ordre de priorité décroissante : 1
rendre adjacents les états de départ qui ont même état d’arrivée dans le graphe.
2
rendre adjacents les états d’arrivée qui ont même état de départ dans le graphe.
3
rendre adjacents les états qui ont mêmes sorties.
L’idée est de minimiser le nombre de bits qui changent (état ou sorties) lors des changements d’états. Appliqué à notre circuit, la première règle préconise d’associer a et c, a et f, b et f ; la deuxième règle préconise d’associer a et b, b et c, a et f ; la troisième règle préconise d’associer a, b et c. La figure III.31 propose une assignation qui respecte au mieux ces règles.
CHAPITRE III. ÉLÉMENTS DE LOGIQUE SÉQUENTIELLE
80
X 0 Y 0 a 1
c
1 f b
Figure III.31. Assignation des états : chaque état est associé à une configuration binaire des bascules. Table de transitions instanciée On recopie la table de transitions précédente, en remplaçant chaque symbole d’état par son vecteur d’état (figure III.32).
avant XY 00 00 11 11 01 01 10 10
E 0 1 0 1 0 1 0 1
après XY 00 11 01 11 00 10 01 11
XY 00 11 01 10
S 0 0 0 1
Figure III.32. Table de transitions instanciée. Choix des bascules En observant la table instanciée, on constate que X à l’état suivant reproduit l’entrée E : une bascule D s’impose pour X. Dans le cas de Y, il reproduit presque la valeur de X de l’état précédent, sauf pour la deuxième ligne. A titre d’exemple, on va choisir une bascule JK. Calcul des entrées des bascules et de la sortie du circuit Plusieurs méthodes sont possibles pour ces calculs. Celle qui sera employée ici est basée sur une réécriture de la table de transitions, à laquelle on rajoute les entrées des bascules, ici DX pour la bascule X et JY,KY pour la bascule Y (figure III.33). En fait on connaissait déjà le résultat pour X : DX = E, qui est la raison pour laquelle − on avait choisi une bascule D. Pour Y, on trouve facilement que JY = E + X et KY = X. Par − ailleurs, on a S = X Y. La synthèse est terminée, et notre séquenceur se réduit au schéma de la figure III.34. On notera l’écriture des affectations séquentielles de x et y : x := e est l’équation d’évolution de la bascule D, et y := x*y+jy*/y est une équation de la forme q := /k*q+j*/q, pour laquelle l’entrée K est inversée.
8.3. Synthèse du détecteur de séquence, version MEALY On effectue ici la synthèse du même détecteur de séquence 1,0,1, cette fois en version
8. Synthèse d’un circuit séquentiel synchrone
XY 00 00 11 11 01 01 10 10
avant DX 0 1 0 1 0 1 0 1
E 0 1 0 1 0 1 0 1
JY 0 1 * * * * 1 1
KY * * 0 0 1 1 * *
81
après XY 00 11 01 11 00 10 01 11
XY 00 11 01 10
S 0 0 0 1
Figure III.33. Table de transitions instanciée avec les entrées des bascules choisies. module seq101(rst,h,e: s) x := e ; x.clk = h ; x.rst = rst ; y := x*y+jy*/y ; y.clk = h ; y.rst = rst ; jy = e+x ; s = x*/y ; end module
Figure III.34. Schéma final synthétisé du détecteur de séquence 1,0,1 (type MOORE) et description SHDL associée. MEALY. Il permettra d’obtenir la sortie un front d’horloge avant la version MOORE, et on peut espérer un circuit encore plus simple puisque son graphe simplifié comporte un état de moins que celui de MOORE. Graphe d’état, table de transitions, simplification Ces trois étapes ont été réalisées en section 6, et ont abouties au graphe simplifié et à la table de transitions des figures III.35 et III.36. 0/0
a
1/0 1
b
0/0 1/1
c
0/0
Figure III.35. Graphe de MEALY simplifié du détecteur de séquence 1,0,1. Détermination du nombre de bascules Il y a trois états à coder, donc 2 bascules seront employées, dont on appellera les sorties X et Y.
CHAPITRE III. ÉLÉMENTS DE LOGIQUE SÉQUENTIELLE
82 avant état a a b b c c
E 0 1 0 1 0 1
après état a b c b a b
S 0 0 0 0 0 1
Figure III.36. Table de transitions simplifiée du détecteur de séquence 1,0,1. Assignation des états La phase d’assignation consiste donc à affecter un vecteur d’état à chacun des états du circuit ; ici un couple (X,Y) pour chacun des 3 états. Afin d’obtenir les résultats les plus simples possibles, on essaie d’appliquer au mieux les heuristiques d’assignation décrites à la section précédente. Appliquée à notre circuit, la première règle préconise d’associer a et c ; la deuxième règle préconise d’associer a et b. La figure III.37 propose une assignation qui respecte au mieux ces règles. X 0 Y 0 a 1
1 b
c
Figure III.37. Assignation des états : chaque état est associé à une configuration binaire des bascules. Table de transitions instanciée On recopie la table de transitions précédente, en remplaçant chaque symbole d’état par son vecteur d’état (figure III.38). avant XY 00 00 10 10 01 01
E 0 1 0 1 0 1
après XY 00 10 01 10 00 10
S 0 0 0 0 0 1
Figure III.38. Table de transitions instanciée. Choix des bascules En observant la table instanciée, on constate que X à l’état suivant reproduit l’entrée E : une bascule D s’impose pour X. Dans le cas de Y, il n’y a qu’une ligne où Y passe à 1 ; on peut choisir également une bascule D, dont on appellera l’entrée DY.
8. Synthèse d’un circuit séquentiel synchrone
83
Calcul des entrées des bascules et de la sortie du circuit Ici encore on va réécrire la table de transitions, à laquelle on va rajouter les entrées des bascules, ici DX pour la bascule X et DY pour la bascule Y (figure III.39).
XY 00 00 01 01 10 10
E 0 1 0 1 0 1
avant DX DY 0 0 1 0 0 1 1 0 0 0 1 0
XY 00 10 01 10 00 10
S 0 0 0 0 0 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 0,0 0,1 1,1 1,0
E 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.
CHAPITRE III. ÉLÉMENTS DE LOGIQUE SÉQUENTIELLE
84
9. Chronologie d’un circuit séquentiel synchrone 9.1. Temps de setup, de hold et de propagation des bascules Parmi les caractéristiques temporelles d’une bascule figurent les temps de setup et de hold. Le temps de setup est la durée avant le front d’horloge durant laquelle les entrées de la bascule doivent être stables pour que le basculement se passe correctement. Le temps de hold est la durée analogue après le front d’horloge. L’union des deux définit une période autour du front d’horloge durant laquelle les entrées doivent être stables, faute de quoi le basculement ne se produirait pas comme prévu. Le temps de propagation quant à lui est le temps après lequel on est garanti d’avoir la nouvelle valeur de la bascule, après la période d’instabilité de la transition. Toutes ces durées sont illustrées sur la figure III.42. front d’horloge
t setup
hold temps de propagation
période où les entrées de la bascule doivent être stables
valeurs stables de l’état suivant
Figure III.42. Temps de setup, de hold et de propagation d’une bascule.
9.2. Chronologie détaillée d’une transition. La lecture de cette section n’est pas nécessaire pour qui veut concevoir un circuit séquentiel. Il lui suffit d’admettre qu’après le front d’horloge, au moment où les bascules du circuit changent éventuellement d’état, on passe sans coup férir des valeurs de l’état précédent aux valeurs de l’état suivant. On peut aussi chercher à comprendre en détail ce qui se passe lors d’une transition, et même craindre qu’elle ne se passe mal. Même si le front d’horloge est le même pour toutes les bascules, les valeurs des signaux de l’état précédent ne peuvent-elles pas interférer avec celles de l’état suivant ? C’est en effet possible dans certaines conditions. Considérons par exemple le schéma de la figure III.43. Si la bascule a a une commutation beaucoup plus rapide que la bascule b, la valeur de a va changer durant la période de hold de la bascule b, provoquant ainsi un fonctionnement incorrect. Le plus souvent, c’est le temps de setup qui est violé, lorsqu’on augmente la vitesse d’horloge à un rythme tel qu’un front d’horloge arrive alors que les entrées des bascules, qui sont des fonctions combinatoires des entrées et de l’état courant, n’ont pas eu un temps suffisant pour se stabiliser.
9. Chronologie d’un circuit séquentiel synchrone
85
Figure III.43. Si la bascule a est trop rapide, le temps de hold de b sera violé. En pratique, on emploie des bascules aux caractéristiques temporelles analogues.
9.3. Bascules maître-esclave Une façon de résoudre le problème de timing précédent consiste à séparer clairement le moment de capture (d’échantillonnage) des valeurs de l’état précédent du moment où on libère les valeurs pour l’état suivant, plutôt que de compter sur des délais de propagation trop serrés. On utilise pour cela des bascules spéciales appelées bascules maître-esclave, câblées selon le modèle de la figure III.44.
Figure III.44. Bascule maître-esclave. On capture les valeurs de l’état précédent au front montant de l’horloge, et on libère les valeurs pour l’état suivant au front descendant. Sur le front montant de l’horloge h, les valeurs issues de l’état précédent sont prises en compte, sans être libérées encore vers les sorties, et donc sans risque de rétroaction néfaste. Plus tard, sur le front descendant de h, les nouvelles valeurs des bascules associées à l’état suivant sont libérées. Les bascules maître-esclave peuvent être employées à peu près partout où sont employées des bascules ordinaires, mais il faut cette fois bien contrôler les deux fronts de l’horloge, et non un seul.
10. Circuits séquentiels réutilisables On présente dans cette section des circuits séquentiels utilisés dans la conception des microprocesseurs ou des microcontrôleurs. Nous continuons notre travail de modularité, en créant des briques réutilisables dans d’autres constructions ; ici encore une interface adapté permettra une récursivité ou une interconnexion aisés.
CHAPITRE III. ÉLÉMENTS DE LOGIQUE SÉQUENTIELLE
86
10.1. Compteur binaire Compteur de base Un compteur binaire sur n bits incrémente à chaque front d’horloge une valeur de n bits. On pourrait d’abord penser à créer un registre de n bits avec des bascules D, puis à relier leurs entrées à un circuit d’incrémentation, mais il y a une solution plus directe et efficace. On se rappelle en effet (section 4) l’algorithme de comptage : un bit est recopié avec inversion si tous les bits à sa droite valent 1, et le bit de poids faible est toujours inversé. Cette description en termes d’inversion conduit à une conception à base de bascules T, dans laquelle l’entrée T d’une bascule est reliée au ET de tous les bits qui sont à sa droite. La figure III.45 montre un exemple d’un tel compteur sur 4 bits.
module cpt4(rst,h: a,b,c,d) d := /d ; d.clk = h ; d.rst = rst ; c := /d*c+d*/c ; c.clk = h ; c.rst = rst ; b := /tb*b+tb*/b ; b.clk = h ;
b.rst = rst ; tb = c*d ; a := /ta*a+ta*/a ; a.clk = h ; a.rst = rst ; ta = b*c*d ; end module
Figure III.45. Compteur binaire 4 bits. Un bit est inversé lorsque tous les bits situés à sa droite valent 1. Ce compteur compte bien sûr modulo 2n : après la valeur ’1111’, suit la valeur ’0000’ sans qu’on puisse être prévenu de ce débordement. On notera l’affectation séquentielle SHDL utilisée pour la bascule de poids faible d : d := /d;, qui se traduit par une bascule T avec un ’1’sur l’entrée T. Pour le bit c, l’écriture c := d*c+/d*/c; se traduit également par une bascule T, mais avec une inversion avant
son entrée. Les autres bascules T ont une écriture directement tirée de l’équation d’évolution standard d’une bascule T. Dans la foulée, on peut également créer un circuit décompteur selon la même méthode : un bit est inversé lorsque tous les bits qui sont à sa droite valent 0 (figure III.46). Ajout d’une entrée de validation Ces circuits comptent ou décomptent en permanence, et il n’y a aucun moyen de suspendre et de reprendre leur comptage. On va maintenant ajouter une entrée de validation en (pour ’enable’) qui ne permettra au compteur de compter que si elle vaut ’1’. Lorsqu’elle vaudra ’0’, le compteur restera figé sur la dernière valeur produite, malgré l’arrivée de fronts
10. Circuits séquentiels réutilisables
module dec4(rst,h: a,b,c,d) d := /d ; d.clk = h ; d.rst = rst ; c := d*c+/d*/c ; c.clk = h ; c.rst = rst ; b := /tb*b+tb*/b ;
87
b.clk = h ; b.rst = rst ; tb = /c*/d ; a := /ta*a+ta*/a ; a.clk = h ; a.rst = rst ; ta = /b*/c*/d ; end module
Figure III.46. Décompteur binaire 4 bits.Un bit est inversé lorsque tous les bits situés à sa droite valent 0. d’horloge sur h. Il suffit pour cela d’ajouter une entrée de validation à chaque bascule T. Le langage SHDL autorise l’emploi de l’écriture : q.ena = en; qui réalise exactement cela, et qu’il suffit d’employer pour chacune des bascules du compteur. Si on ne dispose que de bascules sans entrée de validation, la transformation de la figure III.47 permet de l’obtenir.
module bascten(rst,h,en,t:q) q := /tq*q+tq*/q ; q.clk = h ; q.rst = rst ; tq = en*t ; end module
Figure III.47. Bascule T avec entrée de validation. Ajout d’une remise à zéro synchrone On souhaite maintenant ajouter une entrée sclr de remise à zéro synchrone, qui remet le compteur à zéro au prochain front d’horloge. Elle n’est bien sûr pas de même nature que l’entrée de reset asynchrone, qui n’est là que pour l’initialisation. Par ailleurs il ne faut pas essayer d’utiliser ce reset asynchrone pour implémenter notre remise à zéro synchrone. La règle d’or des circuits séquentiels synchrones purs, c’est de relier entre-elles toutes les horloges et de relier entre eux tous les signaux de reset asynchrone, sans chercher à les modifier. Nous sommes heureux de pouvoir réinitialiser à tout coup notre ordinateur personnel en appuyant sur le bouton de reset, sans que cela ne dépende d’une condition plus ou moins bien pensée par un concepteur peu rigoureux. Comme pour l’entrée de validation, le langage SHDL a une écriture q.sclr = sclr; qui fait exactement ce dont nous avons besoin. Si on ne dispose pas de bascules T avec remise à zéro synchrone, la figure III.48 remplira cette fonction.
CHAPITRE III. ÉLÉMENTS DE LOGIQUE SÉQUENTIELLE
88
module tclr(rst,h,sclr,t:q) q := /tq*q+tq*/q ; q.clk = h ; q.rst = rst ; tq = /sclr*t+sclr*q ; end module
Figure III.48. Bascule T avec remise à zéro synchrone. On notera l’écriture SHDL tq = /sclr*t+sclr*q; qui conduit à l’utilisation d’un multiplexeur 2 vers 1. Si sclr vaut 0, la bascule T reçoit son entrée t normalement ; si sclr vaut 1, la bascule T reçoit q en entrée, qui provoque la remise à 0 au prochain front d’horloge. En effet, si la bascule avait l’état 0, en ayant 0 en entrée elle reste à l’état 0 ; si elle avait l’état 1, en recevant 1 en entrée, elle s’inverse et passe à 0 : dans tous les cas elle passe à zéro. Chaînage de modules de comptage Nous souhaitons maintenant chaîner plusieurs modules de comptage de n bits afin de former des compteurs de m × n bits. L’algorithmique de comptage par bloc est analogue à celle par bits : un bloc incrémente sa valeur si tous les blocs qui sont à sa droite sont à leur valeur maximum. Il nous manque donc pour chaque bloc un signal qui indique qu’il a atteint la valeur maximum. Pour économiser l’usage de portes ET, on va plutôt produire un signal ripl qui vaut 1 lorsque le compteur est à la valeur maximum et que en = 1. Pour chaîner les modules, il suffit maintenant de relier le ripl de l’un au en de l’autre. La figure III.50 montre le chaînage de 4 modules de 4 bits pour former un compteur 16 bits.
10.2. Diviseur de fréquence On souhaite construire un circuit qui fournisse des signaux d’horloge de fréquences différentes et réglables, à partir d’une horloge de base. Dans beaucoup de système réels, y compris nos ordinateurs de bureau, les signaux d’horloge sont bien souvent divisés avant d’être effectivement exploités. On utilisera aussi un diviseur de fréquence dans le timer de notre processeur CRAPS, pour régler la vitesse de la base de temps. Plus précisément on souhaite que la fréquence du signal de sortie soit divisée par 2n, n étant une valeur fournie sur une entrée du circuit. L’interface du diviseur est donné figure III.49.
Figure III.49. Interface du circuit diviseur de fréquence.La sortie s est un signal de même forme que h, avec une fréquence divisée par 2n
10. Circuits séquentiels réutilisables
module cpt4(rst,h,en:a,b,c,d,ripl) d:=/en*d+en*/d ; d.clk=h ; d.rst=rst ; c:=/tc*c+tc*/c ; c.clk=h ; c.rst=rst ; tc=en*d ; b:=/tb*b+tb*/b ; b.clk=h ; b.rst=rst ; tb=en*c*d ; a:=/ta*a+ta*/a ; a.clk=h ; a.rst=rst ; ta=en*b*c*d ; ripl=en*a*b*c*d ; end module
89
module cpt16(rst,h,en:s15..s0) cpt4(rst,h,en:s3,s2,s1,s0,ripl1) cpt4(rst,h,ripl1:s7,s6,s5,s4,ripl2) cpt4(rst,h,ripl2:s11,s10,s9,s8,ripl3) cpt4(rst,h,ripl3:s15,s14,s13,s12,ripl) end module
Figure III.50. Compteur 16 bits formé avec 4 compteurs 4 bits. La sortie ripl d’un module indique qu’il est à la valeur maximum et que tous ceux qui sont à sa droite sont à leur maximum. Le module qui est à sa gauche a son entrée en reliée à ce signal, et ne peut s’incrémenter que lorsqu’il vaut 1. On va tirer parti du principe qu’une bascule T dont on relie l’entrée à 1 produit sur sa sortie un signal rectangulaire dont la fréquence est égale à la fréquence d’horloge divisée par 2. On peut s’en convaincre en examinant le chronogramme de la figure III.51.
CLK
Q
Figure III.51. Chronogramme d’évolution d’une bascule T dont on a forcé l’entrée T à 1. La sortie Q a une fréquence égale à la fréquence de l’horloge divisée par 2. On voit en effet sur le chronogramme que la sortie de la bascule s’inverse, donc change de demi-période, à chaque front montant de l’horloge, c’est à dire toutes les deux demi-périodes de l’horloge. Il y a bien un rapport de 1 sur 2 entre les deux signaux. On va ainsi créer une chaîne de trois de ces bascules T avec des entrées forcées à 1, afin d’obtenir les 4 fréquences d’horloge successives : f (la fréquence de l’horloge générale clk), f / 2, f / 4 et f / 8. On utilisera un multiplexeur 2 vers 4 commandé par l’entrée n1..n0 pour
CHAPITRE III. ÉLÉMENTS DE LOGIQUE SÉQUENTIELLE
90
sélectionner parmi elles celle qui est désirée. On trouvera figures III.52 et III.53 le schéma associé et son codage en langage SHDL. Ce sera la seule fois dans tout l’ouvrage où on aura fait une entorse au principe de base des circuits séquentiels synchrones qui consiste à relier entre-eux tous les signaux d’horloge pour toutes les bascules. Mais ici, cela est fait précisément pour produire un signal d’horloge, qui sera possiblement exploité quant à lui de façon strictement synchrone.
Figure III.52. Structure du circuit diviseur de fréquence. Une suite de bascules T sont chaînées, divisant par 2 la fréquence à chaque étage. module divfreq(rst,clk,d1..d0: s) q0 := /q0 ; q0.clk = clk ; q0.rst = rst ; q1 := /q1 ; q1.clk = q0 ; q1.rst = rst ; q2 := /q2 ; q2.clk = q1 ; q2.rst = rst ; s = /d1*/d0*clk + /d1*d0*q0 + d1*/dO*q1 + d1*d0*q2 ; end module
Figure III.53. Écriture SHDL du module diviseur de fréquence.
10.3. Registres Un registre de n bits est un ensemble de n bascules D synchrones mises en parallèle et partageant la même horloge (figure III.54). On notera l’écriture vectorielle de type d[31..d0]. Elle est strictement équivalente à l’écriture d31..d0 qui a été utilisée jusqu’ici ; elle est plus courte lorsque le nom du signal est long, car il n’est mentionné qu’une fois. Registre avec ligne de sélection Un registre peut être équipé d’une ligne de sélection qui doit être activée si l’on veut faire une écriture ; ce type de signal a souvent pour nom en (enable), ou cs (chip select) ou
10. Circuits séquentiels réutilisables
91 // Description SHDL d’un registre 32 bits module reg32(d[31..0],h,reset: q[31..0]) q[31..0] := d[31..0] ; q[31..0].clk = h ; q[31..0].rst = reset ; END MODULE
Figure III.54. Registre de 32 bits avec son écriture SHDL. encore ce (chip enable). On a vu à la section précédente que le langage SHDL disposait de l’écriture .ena pour prendre en compte directement un tel signal. La figure III.55 met en oeuvre cette écriture pour notre registre de 32 bits.
// description SHDL directe module reg32_2(reset,h,en,d31..d0: q31..q0) q[31..0] := d[31..0] ; q[31..0].clk = clk ; q[31..0].rst = reset ; q[31..0].ena = en ; end module
Figure III.55. Registre avec entrée de sélection, écriture SHDL directe. Pour mieux comprendre son fonctionnement, on peut le réaliser avec un multiplexeur et par rebouclage de la sortie sur l’entrée. La figure III.56 montre cette construction, avec le code SHDL correspondant.
module reg32_3(reset,h,en,d31..d0: q31..q0) reg32(reset,h,in[31..0]: out[31..0]) ; in[31..0] = d[31..0]*en + q[31..0]*/en ; q[31..0] = out[31..0] ; end module
Figure III.56. Registre avec entrée de sélection construit avec un multiplexeur. Lorsque l’entrée en est à 0, le multiplexeur reconduit la valeur du registre en entrée : il ne change alors pas de valeur au prochain front d’horloge. Lorsque en vaut 1, c’est la valeur présente sur les entrées d31..d0 qui est aiguillée par le multiplexeur, puis mémorisée.
CHAPITRE III. ÉLÉMENTS DE LOGIQUE SÉQUENTIELLE
92
Cette construction est bien sûr moins performante qu’avec des bascules D déjà équipées d’une entrée de validation ; en particulier elle demande un temps de setup plus long dû à la propagation au travers du multiplexeur.
11. Exercices corrigés 11.1. Exercice 1 : système de sécurité Énoncé Réaliser un système de mise en marche de sécurité de machine dangereuse.Il est équipé de deux entrées A et B et la mise en marche est contrôlée par la sortie M. Il faut pour cela que la procédure suivante soit respectée : 1
A et B doivent être relâchés initialement ;
2
appuyer sur A,
3
appuyer sur B : la machine se met en marche.
Toute autre manipulation arrête ou laisse la machine arrêtée ; il faut ensuite reprendre la procédure au point 1 pour la mettre en marche. Solution On va adopter une solution de type MOORE. On fera également une conception synchrone, c’est à dire qu’on supposera présente une horloge suffisamment rapide pour ne rater aucun événement sur A et B. Le graphe d’état de ce problème est donné figure III.57. 00 M=0
a
10
11 11
10
b
01 11
M=1
00 01
00 10
00
c
M=0
01
e M=0
Figure III.57. Graphe d’état du système de sécurité, type MOORE. En partant de l’état a, on progresse jusqu’à l’état c pour lequel M=1 si on respecte la séquence de mise en marche. Sur ce chemin, on tombe dans l’état d’erreur e dès qu’on s’écarte de la procédure. Lorsqu’on est en e, on y reste tant que A et B ne sont pas relâchés tous les deux, conformément à la spécification de l’énoncé. On réécrit ce graphe sous une forme tabulaire pour chercher s’il est simplifiable (figure III.58).
11. Exercices corrigés
93
avant état A a 0 a 0 a 1 a 1 b 0 b 0 b 1 b 1 c 0 c 0 c 1 c 1 e 0 e 0 e 1 e 1
B 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1
après état a e b e e e b c e e e c a e e e
état a b c e
M 0 0 1 0
Figure III.58. Table de transitions du graphe de sécurité. Aucune simplification n’est possible ; le circuit possède donc un nombre minimal d’états de 4. Il faut donc 2 bascules pour coder cet état, dont on appellera les sorties X et Y. Vient maintenant l’étape d’assignation, durant laquelle nous devons choisir une configuration des bascules X et Y pour chacun des états. On assignera à l’état a le vecteur XY=00, associer au reset des bascules. Pour les autres, il est souhaitable pour obtenir des calculs plus simples de respecter les heuristiques d’assignation, qui recommandent de placer côte à côte les états fortement reliés dans le graphe. On peut par exemple choisir l’assignation de la figure III.59. X 0 Y 0 a 1
e
1 b c
Figure III.59. Assignation des états du système de sécurité. On peut maintenant réécrire la table de transition, en remplaçant les états symbolique par leur vecteur associé (figure III.60). Aucune bascule ne semble particulièrement adaptée à ces changements d’état. À titre d’exemple, on va utiliser deux bascules T. On peut à nouveau écrire la table de transition instanciée, en rajoutant 2 colonnes TX et TY qui sont les entrées T des bascules X et Y (figure III.61). Il suffit maintenant de trouver les formules algébriques pour TX et TY, en fonction de X, Y, A et B. On peut pour cela utiliser des tables de Karnaugh (figure III.62). On trouve : 1 2
−−− − − TX = ABX Y + AX + BXY −−− − − − TY = ABXY + BY X + AX Y
CHAPITRE III. ÉLÉMENTS DE LOGIQUE SÉQUENTIELLE
94
avant XY 00 00 00 00 10 10 10 10 11 11 11 11 01 01 01 01
A 0 0 1 1 0 0 1 1 0 0 1 1 0 0 1 1
B 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1
après XY 00 01 10 01 01 01 10 11 01 01 01 11 00 01 01 01
XY 00 10 11 01
S 0 0 1 0
Figure III.60. Table de transitions instanciée du graphe de sécurité.
avant XY 00 00 00 00 10 10 10 10 11 11 11 11 01 01 01 01
A 0 0 1 1 0 0 1 1 0 0 1 1 0 0 1 1
B 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1
TX 0 0 1 0 1 1 0 0 1 1 1 0 0 0 0 0
TY 0 1 0 1 1 1 0 1 0 0 0 0 1 0 0 0
après XY 00 01 10 01 01 01 10 11 01 01 01 11 00 01 01 01
XY 00 10 11 01
S 0 0 1 0
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 0,0 0,1 1,1 1,0
A,B
0,0 0,1 1,1 1,0
A,B
0,0
1
1
0,0
0,1
1
1
0,1
1
1
1,1
1
1
1,1 1,0
1
1
1
1
1,0
Figure III.62. Simplification des équations du système de sécurité.
11. Exercices corrigés 3
95
M = XY
11.2. Exercice 2 : compteur/décompteur Énoncé Concevoir un circuit séquentiel synchrone avec une entrée e et 4 sorties a,b,c,d, qui compte en binaire sur a,b,c,d lorsque e=0 et qui décompte lorsque e=1. Solution On pourrait concevoir ce circuit en construisant un graphe d’états, une table de transitions, etc. Mais cette méthode ne serait pas générique et avec 4 bits le graphe serait déjà de grande taille avec 16 états. Il est préférable ici de combiner deux circuits que l’on connaît déjà, le compteur et le décompteur. On a déjà présenté les compteurs et les algorithmes de comptage et de décomptage en binaire (section 4). On réalise un compteur binaire 4 bits avec 4 bascules T ; la bascule de poids faible a pour entrée 1 car elle change d’état à chaque front d’horloge ; les autres bascules ont pour entrée le ET des bits qui sont à leur droite. Un décompteur se fait également avec 4 bascules T ; la bascule de poids faible change également à chaque front ; les autres bascules changent d’état lorsque tous les bits qui sont à leur droite sont des 0. La synthèse des deux est donc simple : la bascule de poids faible est commune, car elle change d’état à chaque front d’horloge dans les deux cas. Pour les autres bits, il s’agit également de faire un ET dans les deux cas, mais des bits qui sont à droite pour le comptage et de l’inverse de ces mêmes bits pour le décomptage. Il suffit donc de mettre un inverseur commandé (un XOR, voir section 4) par le signal e devant chacun des bits à prendre en compte dans le ET. Cela conduit au schéma de la figure III.63, avec un code SHDL immédiat.
11.3. Exercice 3 : utilisation des bascules Énoncé Construire une bascule D avec une bascule T, puis une bascule T avec une bascule D. On pourra utiliser des circuits combinatoires. Solution 1
Bascule D avec une bascule T. On peut se poser une question simple : comment mettre à 0 une bascule T ? Il suffit de mettre la valeur Q de la bascule sur son entrée T. Si elle valait 0 elle y reste et si elle valait 1 elle s’inverse et passe à 0. Pour mettre à 1 une bascule T, on voit aussi facilement qu’il suffit d’appliquer l’inverse de Q sur l’entrée T. Dans les deux cas, on a appliqué sur l’entrée T la valeur Q de la bascule, avec une inversion commandée par la valeur D à mémoriser : XOR(D,Q).
CHAPITRE III. ÉLÉMENTS DE LOGIQUE SÉQUENTIELLE
96
module cptdec(rst, h, e : a, b, c, d) // bascule a a := /ta*a+ta*/a; a.clk = clk; a.rst = rst; ta = nb * nc * nd; // bascule b b := /tb*b+tb*/b; b.clk = clk; b.rst = rst; tb = nc * nd; // bascule c c := /tc*c+tc*/c; c.clk = clk; c.rst = rst; tc = nd; // bascule d d := /d; d.clk = clk; d.rst = rst; // inversion de a,b,c,d // commandée par e na = a*/e + /a*e; nb = b*/e + /b*e; nc = c*/e + /c*e; nd = d*/e + /d*e; end module
Figure III.63. Compteur/décompteur : schéma et écriture SHDL.
2
Bascule T avec une bascule D. La solution est ici plus directe : il suffit d’appliquer sur D l’équation d’évolution de la − − bascule T, qui est Q := T Q + T Q.
11. Exercices corrigés
97
Chapitre IV Éléments fonctionnels d’un processeur Le présent chapitre décrit les modules combinatoires et séquentiels les plus communément utilisés dans les architectures d’ordinateurs, permettant ainsi la transition avec le chapitre suivant décrivant en détail l’architecture d’un processeur 32 bits. Les circuits que nous allons étudier à partir de maintenant sont trop complexes pour pouvoir être analysés et synthétisés dans leur ensemble à l’aide des méthodes tabulaires et algébriques vues aux chapitres précédents. Comme en logiciel, le concept de module va être utilisé pour circonscrire et donc maîtriser cette complexité ; il prendra même parfois une forme récursive, lorsqu’un module sera défini par l’assemblage de modules semblables, mais avec un nombre d’entrées et de sorties plus petit. Le découpage en modules de l’ensemble d’un système complexe va quant à lui faire appel à des méthodes plus intuitives, que nous allons tenter d’introduire au travers de nombreux exemples. Les microprocesseurs et microcontrôleurs sont des circuits séquentiels synchrones complexes, dans lesquels nous opérerons ce découpage en modules, chaque partie étant un circuit combinatoire ou séquentiel standard qui a souvent une structure récursive au sens défini précédemment. Par exemple on va voir que l’Unité Arithmétique et Logique (UAL), qui fait les calculs au sein d’un processeur, a la même structure quel que soit le nombre de bits des données qu’elle traite, et qu’on pourra associer des ’tranches’d’UAL pour construire des UALs de plus grosse capacité. Les concepteurs de processeurs peuvent donc faire appel à ce concept de modularité récursive lors de l’augmentation de la taille des données par exemple, en minimisant les temps de conception. On présente également dans cette partie une nouvelle notion électrique, celle des sorties en haute impédance, qui facilite la gestion de bus de grande taille. On présente aussi les circuits mémoire, qui sont au sens strict des circuits séquentiels, mais qui ne se réduisent pas à des assemblages de bascules ou de latchs. Une section entière leur est consacrée, vu leur importance dans les systèmes informatiques et la diversité croissante de leurs types. Une fois achevée la compréhension de ces modules de base, la conception d’un processeur va ressembler à un jeu de construction avec des blocs qui vont s’emboîter harmonieusement.
1. Sorties haute-impédance Certains circuits possèdent des sorties dites trois états (tri-state), c’est à dire qu’en plus de pouvoir être dans l’état ’0’ ou l’état ’1’ (c’est à dire dans un état électrique de ’basse impédance’), elles peuvent être dans un troisième état dit de ’haute-impédance’, souvent noté High-Z. Lorsqu’une sortie est en haute-impédance, tout se passe comme si elle n’était plus connectée, car elle ne produit plus ni ne consomme plus aucun courant. Cette propriété permettra de relier directement entre-elles plusieurs sorties de ce type, sous réserve de 98
1. Sorties haute-impédance
99
garantir qu’au plus une seule de ces sorties produise du courant à un moment donné (sous peine de court-circuit !). Les circuits ayant des sorties trois états possèdent en interne des composants appelés buffer trois-états, qui se représentent tels que sur la figure IV.1.
E
S
E
S
// écriture SHDL S = E:OE ; VALIDE
VALIDE
Figure IV.1. Buffer 3-états - La sortie est en haute-impédance tant que VALIDE = 0. La ligne VALIDE qui arrive sur le côté du triangle, souvent notée OE (pour ’Output Enable’), commande l’état de la sortie. Tant que cette ligne est inactive, la sortie reste dans l’état High-Z. Du point de vue de l’écriture SHDL, cette commande Output Enable sur un signal tel que S s’indique par l’ajout après le nom du signal d’une commande telle que :OE Si on considère cet état High-Z comme un troisième état logique (ce qui n’est pas très exact sur le plan mathématique), on a la table de vérité de la figure IV.2. E
OE
S
*
0
High-Z
0
1
0
1
1
1
Figure IV.2. Table de vérité d’un buffer 3-états. On peut illustrer l’utilisation de ces buffers lors de la réalisation d’un multiplexeur (figure IV.3). Les sorties des deux buffers 3-états sont reliées entre-elles, car leurs lignes OE s’activent de façon mutuellement exclusive. L’idée de ce schéma est que, soit on doit laisser passer le courant qui vient de A (avec l’interrupteur commandé que forme le buffer trois états), soit on doit le laisser passer depuis B. Comme ces deux conditions sont mutuellement exclusives, on peut relier les sorties des deux buffers en toute sécurité. On économise ainsi le circuit OU du schéma classique de droite, qui n’opérait jamais avec ses deux entrées à 1 simultanément. On notera l’écriture SHDL telle que : S = A:/C;. L’équipotentielle s’appelle S, mais deux sources de courant peuvent y imposer leur potentiel ; on les note alors A:/C et B:C. On voit sur la figure IV.4 une configuration électrique particulière de ce circuit : la commande C étant à 1, seul le buffer 3-états du bas va être dans un état de basse impédance, et va imposer son état électrique à la sortie S. Le buffer trois états du haut a sa sortie en haute impédance et n’entre pas en conflit avec l’autre pour l’établissement du potentiel de S ; tout se passe comme s’il était déconnecté du circuit, ce qui a été figuré par les deux lignes brisées.
CHAPITRE IV. ÉLÉMENTS FONCTIONNELS D’UN PROCESSEUR
100
C A
0 S
B
1
C
A A C
S
S
B
B
MODULE MUX_1(A,B,C :S) S = A:/C ; S = B:C ; END MODULE
MODULE MUX_2(A,B,C :S) S1 = /C * A ; S2 = C * B ; S = S1 + S2 ; END MODULE
Figure IV.3. Multiplexeur réalisé à l’aide de buffers 3-états, comparé à sa réalisation en logique combinatoire classique. On économise le ’ou’ final.
A=0
High−Z
C=1 B=1
S=1 1
Figure IV.4. Fonctionnement du multiplexeur. Dans cette configuration, c’est le buffer du bas qui impose son potentiel. Si on relie ensemble deux sorties de buffers trois états, et qu’à un moment donné leurs lignes OE sont à 1en même temps, le court circuit est réel, et les circuits se mettent à chauffer dangereusement, entraînant parfois la destruction ou l’endommagement du plus faible. Cela peut se produire dans des réalisations composées de plusieurs circuits intégrés, dont les sorties trois états sont reliées entre elles. Cela peut se produire également à l’intérieur d’un seul circuit comme un FPGA, dans lequel des lignes internes à trois états peuvent être reliées entre elles lors de la programmation - configuration. On évite parfois le pire avec un bon odorat, en détectant l’odeur de cuisson de la poussière présente à la surface des circuits à des températures avoisinant parfois les 100 degrés Celcius, et en débranchant en hâte l’alimentation ! C’est en fait la seule situation dangereuse pour les composants lorsque l’on fait de la logique digitale. Tant qu’on n’utilise pas de composants avec des sorties trois états, rien ne peut être endommagé si on prend soin de ne relier chaque sortie qu’à des entrées. Avec les
1. Sorties haute-impédance
101
composants ayant des sorties trois états, il faudra impérativement connecter leurs lignes OE à des circuits tels que des décodeurs (voir section 3) qui vont garantir l’exclusion mutuelle entre les sorties reliées entre elles.
2. Les bus parallèles 2.1. Vocabulaire et concepts On appelle bus parallèle un ensemble d’équipotentielles électriques qui interconnectent plusieurs modules à l’intérieur d’un câblage. On voit figure IV.5 comment on représente graphiquement un bus parallèle de largeur m. Le trait est épaissi par rapport à un signal simple, et une barre surmontée d’un nombre indique la largeur du bus. On montre m
m
Figure IV.5. Les deux représentations graphiques d’un bus parallèle de largeur m. également figure IV.6 les façons habituelles de représenter sur un schéma les sous-bus d’un bus donné. 16
data[15..0]
16
data[15..0]
5
5 data[15..11]
data[15..11] 5 data[15..0]
16
5 data[15..11]
16
data[10..0]
11 11
Figure IV.6. Exemples de scindements / regroupements de bus. On trouve des bus parallèles à tous les niveaux d’une architecture d’ordinateur : dans les structures internes du processeur, entre le processeur et les autres composants présents sur la carte mère, etc. Ils peuvent être présents partout où plusieurs composants doivent échanger des données. Un bus large va permettre de transférer en une transaction une donnée de grande taille, mais va occuper beaucoup de place sur les circuits, et donc coûter plus cher. La même donnée pourra être copiée en deux transactions successives, avec un bus deux fois moins large ; un compromis est donc à trouver entre coût et vitesse de traitement. Pour que des modules puissent dialoguer sur un même bus, sur le plan électrique, il faut
CHAPITRE IV. ÉLÉMENTS FONCTIONNELS D’UN PROCESSEUR
102
adopter bien sûr les mêmes tensions d’alimentation, courants de sortie et d’entrée, etc. Mais il faut aussi empêcher les courts-circuits sur les lignes de données, lorsque plusieurs modules ont la possibilité d’y écrire. Par ailleurs, un protocole de communication est indispensable, afin de régler la chronologie des échanges sur le bus. Dans ce domaine, comme dans beaucoup d’autres de l’architecture des ordinateurs, on peut classer ces protocoles en deux catégories : les synchrones et les asynchrones. Dans ces deux catégories, un grand nombre de protocoles différents existent, dont l’étude sort du cadre de ce livre. Les protocoles synchrones sont les plus répandus et les plus simples, car ils règlent tous les échanges sur un signal d’horloge unique (qui fait partie du bus).
2.2. Exemple de bus synchrone simple Afin d’illustrer quelques aspects de la communication sur un bus synchrone, considérons le circuit de la figure IV.7. Le bus est composé de 11 lignes : 8 lignes de esclaves maitre a[7..0]
b[7..0]
c[7..0]
d[7..0]
8
8
8
8
compteur 2 bits
8 2
bus
8
n1*n0
8
n1*/n0
8
/n1*n0
8
/n1*/n0
8
data[7..0]
2
n[1..0] clk
Figure IV.7. Exemple de bus synchrone, à un maître et 4 esclaves. Les sorties des esclaves sont des buffers 3-états, dont les lignes OE s’excluent mutuellement. données data[7..0] et 3 lignes de contrôle n[1..0] et clk. Le bloc de gauche joue le rôle de maître, c’est à dire qu’il dirige les échanges sur le bus. C’est lui qui génère les signaux de contrôle, gouvernés par l’horloge clk qui cadence tous les échanges. À chaque front de clk, le maître génère à l’aide d’un compteur un nouveau numéro sur les lignes n[1..0]. Les lignes OE de chaque esclave sont associées à un numéro spécifique ; par exemple l’esclave dont la ligne OE est reliée à n1*/n0 est associé au numéro 2 (10 en binaire), et ne placera sa donnée b[7..0] sur le bus que si ce numéro est présent sur les lignes n[1..0] du sous-bus de contrôle. Dans ce protocole synchrone particulièrement simple, les 4 esclaves placent à tour de rôle leur donnée sur le sous-bus data[7..0], gouvernés par le module maître. Ici le bus est monodirectionnel, les échanges allant toujours dans le sens esclave vers maître ; certains protocoles pourront être bidirectionnels. Il n’y a ici qu’un seul maître, donc pas de possibilité de conflit. S’il y avait plusieurs maîtres, une logique d’arbitrage serait nécessaire pour décider quel maître peut prendre le contrôle du bus en cas de requêtes simultanées. Il s’agit d’un protocole à une phase, puisqu’un échange de données est effectué en un cycle d’horloge. Des protocoles complexes peuvent nécessiter de nombreuses phases pour réaliser un échange, plusieurs phases pouvant être nécessaires pour que le maître décrive les paramètres de sa demande à l’esclave, et plusieurs autres pouvant être nécessaires pour que
2. Les bus parallèles
103
l’esclave transmette la donnée demandée.
2.3. Exemple de bus parallèle, externe et normalisé : le bus PCI Pour mieux comprendre la problématique des bus, on peut penser au bus PCI parallèle présent sur la plupart des ordinateurs personnels et des stations de travail (même s’il est progressivement remplacé maintenant par le bus PCI Express, plus rapide). Lorsqu’on ouvre une de ces machines, on voit sur la carte mère plusieurs connecteurs exactement identiques, comportant deux fois 94 broches, disposés parallèlement à quelques centimètres d’intervalle.On peut parfois apercevoir sur le circuit imprimé une partie des interconnexions entre ces connecteurs. Elles sont très simples : les broches de même position sont toutes reliées entre elles pour tous les connecteurs. Il y a donc 188 pistes sur le circuit imprimé qui relient entre elles les 188 broches de chaque connecteur (figure IV.8).
Figure IV.8. Bus PCI, avec 4 connecteurs à 188 broches. Si on considère maintenant une carte placée dans un de ces connecteurs, comme une carte d’acquisition vidéo, ou une carte réseau, il faut comprendre que ces 188 signaux sont son unique moyen de communication avec le reste du système, et qu’il faut donc trouver un moyen de dialoguer, rationnel à la fois sur le plan électrique et sur le plan logique. Pour le bus PCI comme pour tous les bus externes standardisés, ce protocole de communication doit être très général, utilisable par des cartes de types variés, en nombre non fixé à l’avance, qui peuvent être par exemple principalement émettrices de données (cartes d’acquisition vidéo), ou principalement réceptrices (cartes audio), ou les deux (cartes réseau).
3. Décodeurs Un décodeur est un circuit possédant n entrées et 2n sorties numérotées (figure IV.9). À tout moment, une et une seule sortie est active : celle dont le numéro correspond à la valeur binaire présente sur les n entrées. Le décodeur traduit donc la valeur d’entrée en une information de position spatiale.
1 1 0 1
E2 E1 E0 EN
S0 S1 S2 S3 S4 S5 S6 S7
0 0 0 0 0 0 1 0
MODULE DECODER3TO8(EN,E2..E0 :S7..S0) S7=EN*E2*E1*E0 ; S6=EN*E2*E1*/E0 ; S5=EN*E2*/E1*E0 ; S4=EN*E2*/E1*/E0 ; S3=EN*/E2*E1*E0 ; S2=EN*/E2*E1*/E0 ; S1=EN*/E2*/E1*E0 ; S0=EN*/E2*/E1*/E0 ; END MODULE
Figure IV.9. Décodeur 3 vers 8.Avec la valeur 6 en entrée (110 en binaire),la sortie numéro 6 est activée. Un décodeur peut être utilisé pour n’activer qu’au plus un composant 3-états à la fois, puisqu’au plus une seule de ses sorties est active à la fois.
CHAPITRE IV. ÉLÉMENTS FONCTIONNELS D’UN PROCESSEUR
104
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 011 100 101 110 111
E2 E1 E0
Figure IV.10. Un décodeur 3 vers 8 utilisé pour réaliser un OU exclusif à 3 entrées.
4. Multiplexeurs Ces sont des circuits d’aiguillage pour les signaux logiques. Un multiplexeur possède n 2 entrées de données, n entrées de commandes, et une seule sortie. On indique sur la 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 0 0
E0
1 0 1 1 1
E3
E1 E2 S
E4 E5 E6 E7 C2..C0
1
MODULE MUX8TO1(E7..E0,C2..C0 :S) S=/C2*/C1*/C0*E0+ /C2*/C1*C0*E1+ /C2*C1*/C0*E2+ /C2*C1*C0*E3+ C2*/C1*/C0*E4+ C2*/C1*C0*E5+ C2*C1*/C0*E6+ C2*C1*C0*E7; 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
32
MODULE MUX2TO1_32(A31..A0,B31..B0,C :S31..S0) S31..S0 = /C*A31..A0 + C*B31..B0 ; END MODULE
S31..S0 B31..B0 C
Figure IV.12. Multiplexeur 2 vers 1, aiguillant des bus de 32 bits. Un multiplexeur 2 vers 1 implémente matériellement une situation de type si-alors-sinon ; plus précisément : si C alors S=A sinon S=B (figure IV.13). C’est une remarque qui peut sembler banale, mais qui en fait va permettre d’organiser le découpage en modules d’un système complexe, dès lors qu’un tel genre de situation aura été identifié. n A
1
n S
n B
0
C
Figure IV.13. Un multiplexeur 2 vers 1implémente une situation ’si-alors-sinon’ : si C alors S = A sinon S = B. Un multiplexeur 2n vers 1 peut également être utilisé directement pour implémenter des fonctions combinatoires à n entrées. Il suffit de relier les entrées de la fonction à réaliser aux entrées de commande du multiplexeur : chaque combinaison va conduire à un aiguillage spécifique, et il n’y a plus qu’à mettre la valeur 0 ou 1 attendue sur l’entrée de donnée correspondante du multiplexeur. Il n’y a cette fois aucun composant supplémentaire à rajouter, et on peut voir figure IV.14 l’implémentation d’une fonction XOR à 3 entrées avec un décodeur 8 vers 1. Bien sûr, cette solution n’est pas optimale en nombre de transistors utilisés, puisque toutes les entrées forcées à 0 sont reliées à des portes ET inutilisées.
5. Encodeurs de priorité Un encodeur de priorité possède 2n entrées et n sorties. Les entrées sont numérotées, et correspondent à des événements de priorité croissante. On verra en particulier comment utiliser les encodeurs de priorité pour gérer l’arrivée d’interruptions simultanées dans un processeur, telles que les événements réseau, les événements disque, les événements USB ou clavier ou souris, etc. La sortie NUM contient le numéro de l’entrée activée la plus prioritaire, c’est à dire de numéro le plus élevé. Une autre sortie (ACT sur la figure) peut aussi indiquer s’il y a au moins
CHAPITRE IV. ÉLÉMENTS FONCTIONNELS D’UN PROCESSEUR
106
0 1 1
0 1 2
0 1 0 0 1
3 4 5 6 7
XOR3(E2..E0)
E2..E0
Figure IV.14. Un multiplexeur 8 vers 1 utilisé pour réaliser un OU exclusif à 3 entrées. une entrée active. Le schéma de la figure IV.15 montre un tel encodeur pour 23 entrées avec les entrées 0, 3 et 6 activées, et la valeur binaire 6 placée sur les sorties. 1 0 0 1 0 0 1 0
E0 NUM2..0 E1 E2 E3 encodeur de E4 priorité E5 E6 ACT E7
3 110 (6)
MODULE ENCODEUR(E[7..0]: NUM[2..0],ACT) NUM2=E7+E6+E5+E4 ; NUM1=E7+E6+/E5*/E4*E3+/E5*/E4*E2 ; NUM0=E7+/E6*E5+/E6*/E4*E3+/E6*/E4*/E2*E1 ; ACT=E7+E6+E5+E4+E3+E2+E1+E0 ; END MODULE
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. : 1 −−− NUM2 1 1 − 1 −− E7E6 E7 E6E5 + + = ×E7 + × × NUM1 1 1 0 0 × E7E6E5E4 0 1 0 NUM0 1 0 −−−−− 0 −−−−−− 0 −−−− + 1 × E7E6E5E4E3 + 1 × E7E6E5E4E3E2 + 0 × E7E6E5E4E3E2E1 1 0 1
5. Encodeurs de priorité
107
E7
E6
E5
E4
E3
E2
E1
E0
NUM2..0
ACT
1
*
*
*
*
*
*
*
111
1
0
1
*
*
*
*
*
*
110
1
0
0
1
*
*
*
*
*
101
1
0
0
0
1
*
*
*
*
100
1
0
0
0
0
1
*
*
*
011
1
0
0
0
0
0
1
*
*
010
1
0
0
0
0
0
0
1
*
001
1
0
0
0
0
0
0
0
1
000
1
0
0
0
0
0
0
0
0
000
0
Figure IV.16. Table de vérité condensée d’un encodeur de priorités à 8 entrées. − −− −−− NUM2 E7 E7E6 E7E6E5 E7E6E5E4 0 NUM1 = E7 + − 0 + E7E6 + − − NUM0 E7 0 E7E6E5 0 0 0 0 −−−− −−−− 0 + E7E6E5E4E3 + − + E7E6E5E4E3E2 −−−−−− −−− E7 E6 E5 E4 E3 E2E1 − 0 E7E6E5E4E3 − −− −−− E7 + E7E6 + E7E6E5 + E7E6E5E4 NUM2 − − − − − − − − − − NUM1 = E7 + E7E6 + E7E6E5E4E3 + E7E6E5E4E3E2 − −−−− −−−−−− NUM0 E7 + − E7E6E5 + E7E6E5E4E3 + E7E6E5E4E3E2E1 Le théorème d’absorption s’applique plusieurs fois, et on obtient finalement : E7 + E6 + E5 + E4 NUM2 −− −− NUM1 = E7 + E6 + E5E4E3 + E5E4E2 −− −−− NUM0 E7 + − E6E5 + E6E4E3 + E6E4E2E1 L’écriture SHDL d’un encodeur à 8 entrées est donnée figure IV.15. On reprendra à l’exercice 11.3 le calcul des équations des signaux NUM2..0.
6. Circuit d’extension de signe Un circuit d’extension de signe de p bits vers n bits, p < n est un circuit combinatoire qui transforme un nombre signé codé en complément à 2 sur p bits en ce même nombre codé en complément à 2 sur un nombre de bits n plus grand. Considérons par exemple une extension de signe de 4 bits vers 8 bits, et quelques cas particuliers : •
00112 (= 3) sera converti en 000000112
•
11112 (= -1) sera converti en 111111112
•
10002 (= -8) sera converti en 111110002
CHAPITRE IV. ÉLÉMENTS FONCTIONNELS D’UN PROCESSEUR
108
On voit donc que le mot de n bits est formé de la recopie du mot de p bits, complété à gauche de n − p bits tous égaux à 0 ou à 1 selon le signe du nombre. On recopie en fait n − p fois le bit de signe du mot de départ, c’est à dire le bit de rang p − 1 (figure IV.17). 1010
1111 1010
Figure IV.17. Opération d’extension de signe. On duplique les p bits d’entrée et on ajoute à gauche n-p bits de signe. La figure IV.18 montre à titre d’exemple le code SHDL correspondant à un circuit d’extension de signe de 13 bits vers 32 bits. module signext13(e12..e0: s31..s0) s12..s0 = e12..e0; s31..s13 = e12; end module
Figure IV.18. Écriture SHDL d’un circuit d’extension de signe 13 bits vers 32 bits. Les 13 bits de poids faibles sont recopiés et on ajoute 19 bits de poids forts, tous égaux au bit de signe e12 du nombre de départ.
7. Décaleur à barillet Le décaleur à barillet (barrel shifter) décale un mot binaire de n bits vers la gauche ou vers la droite, d’un nombre variable de bits. Le sens du décalage est défini par une commande R (pour right) et le nombre de bits du décalage est donné dans une commande NB sur p bits (figure IV.19). C’est un circuit directement employé à l’exécution des instructions de décalage et de rotation des processeurs, telles que les instructions sll et slr de CRAPS que p nous verrons au chapitre suivant. Généralement, n = 2 : un décaleur à barillet sur 8 bits aura une valeur de décalage codée sur 3 bits ; un décaleur sur 32 bits aura une valeur de décalage sur 5 bits, etc. Sur 8 bits par exemple, la figure IV.20 présente la table de vérité condensée d’un tel circuit, avec une valeur décalage sur 3 bits qui permet tous les décalages de 0 à 7 dans le sens droit (R = 1) comme dans le sens gauche (R = 0). Un tel circuit est difficile à concevoir directement ; par ailleurs, si on voulait le réaliser sous forme d’un seul étage de propagation - ce qui est possible en théorie - les équations seraient complexes, et ce d’autant plus que les valeurs de n et p seraient grandes. À l’inverse, une conception modulaire et récursive est possible de façon très simple, si on procède par étage, avec des décalages par puissances de 2 successives. Cette organisation générale est dite de décalage à barillet, et elle est montrée figure IV.21 pour un décaleur 8
7. Décaleur à barillet
109
Figure IV.19. Interface général d’un décaleur à barillet.Le mot de n bits est décalé à droite (resp. gauche) si R = 1 (resp. 0), d’un nombre de bits NB. Les bits qui sortent sont perdus, et les bits entrants sont des 0. R * 0 0 0 0 0 0 0 1 1 1 1 1 1 1
nb 000 001 0 10 0 11 10 0 10 1 110 111 001 0 10 0 11 10 0 10 1 110 111
e7 e7 e7 e7 e7 e7 e7 e7 e7 e7 e7 e7 e7 e7 e7
e6 e6 e6 e6 e6 e6 e6 e6 e6 e6 e6 e6 e6 e6 e6
e5 e5 e5 e5 e5 e5 e5 e5 e5 e5 e5 e5 e5 e5 e5
entrées e4 e3 e4 e3 e4 e3 e4 e3 e4 e3 e4 e3 e4 e3 e4 e3 e4 e3 e4 e3 e4 e3 e4 e3 e4 e3 e4 e3 e4 e3
e2 e2 e2 e2 e2 e2 e2 e2 e2 e2 e2 e2 e2 e2 e2
e1 e1 e1 e1 e1 e1 e1 e1 e1 e1 e1 e1 e1 e1 e1
e0 e0 e0 e0 e0 e0 e0 e0 e0 e0 e0 e0 e0 e0 e0
e7 e6 e5 e4 e3 e2 e1 e0 0 0 0 0 0 0 0
e6 e5 e4 e3 e2 e1 e0 0 e7 0 0 0 0 0 0
e5 e4 e3 e2 e1 e0 0 0 e6 e7 0 0 0 0 0
sorties e4 e3 e3 e2 e2 e1 e1 e0 e0 0 0 0 0 0 0 0 e5 e4 e6 e5 e7 e6 0 e7 0 0 0 0 0 0
e2 e1 e0 0 0 0 0 0 e3 e4 e5 e6 e7 0 0
e1 e0 0 0 0 0 0 0 e2 e3 e4 e5 e6 e7 0
e0 0 0 0 0 0 0 0 e1 e2 e3 e4 e5 e6 e7
Figure IV.20. Table de vérité condensée d’un décaleur à barillet sur 8 bits. bits. Tous les décaleurs ont leurs lignes R reliées ensemble, et donc opèrent dans le même sens. Ils sont tous équipés d’une ligne CS, et un décalage n’est effectué que si CS = 1. Ils effectuent chacun un nombre de décalages fixe, 4, 2 ou 1. Leur organisation en barillet permet d’effectuer toutes les valeurs de décalage entre 0 et 7. Le premier niveau effectue un décalage de 4 bits ou non, selon que nb[2] = 1 ou non. Le second niveau effectue ou non un décalage de 2 bits, selon la valeur de nb[1]. Le dernier niveau effectue un décalage de 1 bit, selon la valeur de nb[0]. Par exemple si nb = 101 et si R = 0, le premier niveau va décaler de 4 bits vers la gauche (sa ligne CS est à 1), transformant E = (e7, e6, e5, e4, e3, e2, e1, e0) en (e3, e2, e1, e0, 0, 0, 0, 0). Le second niveau va laisser passer ce vecteur sans le transformer. Le niveau du bas va effectuer un décalage de 1, transformant le vecteur en (e2, e1, e0, 0, 0, 0, 0, 0). Au total, c’est donc bien 4 + 1 = 5 décalages à gauche qui ont été effectués. Bien sûr, une telle organisation est généralisable à un nombre quelconque de bits. Un
110
CHAPITRE IV. ÉLÉMENTS FONCTIONNELS D’UN PROCESSEUR
Figure IV.21. Organisation en 3 niveaux (barillets) d’un décaleur 8 bits. Chaque niveau décale dans un sens ou un autre d’un nombre de positions égal à une puissance de deux, si sa ligne CS est à 1. Leurs combinaisons permettent toutes les valeurs de décalage. décaleur 32 bits serait constitué de 5 étages, formés de décaleurs fixes de 16, 8, 4, 2 et 1 bits. L’accroissement en complexité et en temps de propagation augmente donc en log(n), ce qui est très satisfaisant. Il nous reste seulement à résoudre le problème de la fabrication des décaleurs sur un nombre de positions fixes. Ce sont des circuits combinatoires simples ; voici par exemple le code SHDL d’un décaleur 2 bits : module shift2(r,cs,e7,e6,e5,e4,e3,e2,e1,e0 :s7,s6,s5,s4,s3,s2,s1,s0) s7=e7*/cs+cs*/r*e5 ; s6=e6*/cs+cs*/r*e4 ; s5=e5*/cs+cs*/r*e3+cs*r*e7 ; s4=e4*/cs+cs*/r*e2+cs*r*e6 ; s3=e3*/cs+cs*/r*e1+cs*r*e5 ; s2=e2*/cs+cs*/r*e0+cs*r*e4 ; s1=e1*/cs+cs*r*e3 ; s0=e0*/cs+cs*r*e2 ; end module
Les termes en /cs recopient l’entrée sur la sortie de même rang, et il n’y a donc pas de décalage. Les termes en cs*/r gèrent le décalage à gauche de 2 positions, et les termes en cs*r gèrent le décalage à droite de 2 positions. Finalement, le code SHDL complet d’un décaleur à barillet sur 8 bits est donné figure IV.22.
7. Décaleur à barillet
111
;; Décaleur à barillet 8 bits. ;; r = sens décalage (1=droite); amplitude = nb2..0 module barrelshifter8(r,nb2,nb1,nb0,e7,e6,e5,e4,e2,e1,e0:s7,s6,s5,s4,s3,s2,s1,s0) shift4(r,nb2,e7,e6,e5,e4,e3,e2,e1,e0:s7,x6,x5,x4,x3,x2,x1,x0); shift2(r,nb1,x7,x6,x5,x4,x3,x2,x1,x0:y7,y6,y5,y4,y3,y2,y1,y0); shift1(r,nb0,y7,y6,y5,y4,y3,y2,y1,y0:s7,s6,s5,s4,s3,s2,s1,s0); end module ;; Décale de 4 bits si cs=1, laisse inchangé sinon. r = sens de décalage (1=droite) module shift4(r,cs,e7,e6,e5,e4,e3,e2,e1,e0:s7,s6,s5,s4,s3,s2,s1,s0) s7=e7*/cs+cs*/r*e3 ; s6=e6*/cs+cs*/r*e2 ; s5=e5*/cs+cs*/r*e1 ; s4=e4*/cs+cs*/r*e0 ; s3=e3*/cs+cs*r*e7 ; s2=e2*/cs+cs*r*e6 ; s1=e1*/cs+cs*r*e5 ; s0=e0*/cs+cs*r*e4 ; end module ;; Décale de 2 bits si cs=1, laisse inchangé sinon. r = sens de décalage (1=droite) module shift2(r,cs,e7,e6,e5,e4,e3,e2,e1,e0:s7,s6,s5,s4,s3,s2,s1,s0) s7=e7*/cs+cs*/r*e5 ; s6=e6*/cs+cs*/r*e4 ; s5=e5*/cs+cs*/r*e3+cs*r*e7 ; s4=e4*/cs+cs*/r*e2+cs*r*e6 ; s3=e3*/cs+cs*/r*e1+cs*r*e5 ; s2=e2*/cs+cs*/r*e0+cs*r*e4 ; s1=e1*/cs+cs*r*e3 ; s0=e0*/cs+cs*r*e2 ; end module ;; Décale de 1 bit si cs=1, laisse inchangé sinon. r = sens de décalage (1=droite) module shift1(r,cs,e7,e6,e5,e4,e3,e2,e1,e0:s7,s6,s5,s4,s3,s2,s1,s0) s7=e7*/cs+cs*/r*e6 ; s6=e6*/cs+cs*/r*e5+cs*r*e7 ; s5=e5*/cs+cs*/r*e4+cs*r*e6 ; s4=e4*/cs+cs*/r*e3+cs*r*e5 ; s3=e3*/cs+cs*/r*e2+cs*r*e4 ; s2=e2*/cs+cs*/r*e1+cs*r*e3 ; s1=e1*/cs+cs*/r*e0+cs*r*e2 ; s0=e0*/cs+cs*r*e1 ; end module
Figure IV.22. Code SHDL complet d’un décaleur à barillet sur 8 bits.Chaque décaleur fixe de 4, 2 et 1 bits est présent sous forme de module séparé.
8. L’unité arithmétique et logique 8.1. Fonction d’une UAL Dans un processeur, on regroupe souvent dans la même unité fonctionnelle les différents opérateurs d’arithmétique entière (additionneurs,multiplieurs, etc.), les opérateurs de logique booléenne (AND, OR, etc.) et les opérations de décalage et de rotation de bits. On appelle un tel module unité arithmétique et logique (UAL), et elle a la forme de la figure IV.23 suivante : Les opérations que réalise l’UAL ont une arité de 2, et parfois 1. Les opérandes sont présentés sur deux bus A et B de même largeur ; on indique sur F le code d’une opération à effectuer (par exemple, une addition), et le résultat est disponible sur le bus de sortie F(A,B) après un certain temps de propagation des calculs. Les flags ou indicateurs, notés souvent
CHAPITRE IV. ÉLÉMENTS FONCTIONNELS D’UN PROCESSEUR
112 A
B
N,Z,V,C
F
UAL
F(A,B)
Figure IV.23. Unité arithmétique et logique.F code l’opération à effectuer ; les opérandes sont A et B et le résultat est S. Les indicateurs N,Z,V,C donnent des informations sur la nature du résultat. N, Z, V, C donnent des informations sur le résultat de l’opération. N indique que le résultat est négatif, Z indique qu’il est nul, V indique un débordement et C indique la présence d’une retenue. Il faut noter que l’UAL est un opérateur combinatoire : il n’a pas d’horloge, et le résultat d’une opération n’est disponible qu’après un temps variable (mais borné).
8.2. UAL par tranches de 1 bit Assez souvent, une UAL de n bits est formée de l’association de n UAL de 1 bit, appelées tranches de bit (bit slice). Considérons par exemple les schémas des figures IV.24 et IV.25 d’une UAL 1 bit qui effectue l’une des 4 opérations : ET logique, OU logique, complément ou addition sur les deux entrées A et B.
A 2
B F1 F0
F1,F0
CIN
UAL
0 0
AND
1 bit
0 1
OR
1 0 1 1
NOT A ADD
S
COUT
Figure IV.24. Unité arithmétique et logique de 1 bit. CIN et COUT ne sont utilisés que pour l’opération d’addition. CIN est une retenue entrante et COUT une retenue sortante : lorsque l’addition est sélectionnée (F1F0=11), c’est la somme A + B + CIN qui est effectuée, et le résultat est disponible sur S, avec une éventuelle retenue sortante placée sur COUT. On voit que l’équation du résultat S est un
8. L’unité arithmétique et logique
113
F1,F0
A B
// 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 ; END MODULE
0 1
// majorité à 3 entrées MODULE MAJ3(X,Y,Z :MAJ) MAJ=X*Y+X*Z+Y*Z; END MODULE
S
2
CIN
MODULE UAL1BIT(F1,F0,A,B,CIN :S, COUT) MUX4TO1(E3,E2,E1,E0,F1,F0 :S); E0=A*B ; E1=A+B ; E2=/A ; E3=/A*/B*CIN+/A*B*/CIN+A*/B*/CIN+A*B*CIN ; MAJ3(A,B,CIN :COUT) END MODULE
3 COUT MAJ3
Figure IV.25. Structure interne et description SHDL de l’UAL 1 bit. multiplexeur, commandé par les quatre combinaisons de F1et F0.Les quatre termes associés à l’addition représentent le ou exclusif entre A, B et CIN. Les termes correspondants aux autres opérations parlent d’eux-mêmes ; on voit que seule l’opération de complémentation est unaire. La figure IV.26 montre l’assemblage de 4 tranches d’UAL 1 bit, pour former une UAL de 4 bits. Le module traitant les bits d’indice 0 a une retenue entrante forcée à 0 ; ensuite la retenue sortante d’un module devient la retenue entrante du module suivant, jusqu’à la retenue finale CARRY.
2 F1,F0
0
A0
B0
A
B
B1
A
B
F1,F0
F1,F0
CIN
A1
A2
B2
A
B
UAL
UAL
1 bit
1 bit
1 bit
S
S0
CIN
S
S1
COUT
CIN
B3
A
B
F1,F0
F1,F0
UAL COUT
A3
S
UAL 1 bit COUT
S2
CIN
S
COUT
COUT
S3
MODULE UAL4BIT(F1,F0,A3..A0,B3..B0 :S3..S0,CARRY) UAL1BIT(F1,F0,A0,B0,0:S0,C0); UAL1BIT(F1,F0,A1,B1,C0:S1,C1); UAL1BIT(F1,F0,A2,B2,C1:S2,C2); UAL1BIT(F1,F0,A3,B3,C2:S3,CARRY); END MODULE
Figure IV.26. Unité arithmétique de 4 bits formée par assemblage de 4 modules d’UAL 1 bit. Bien sûr, cette technique a ses limites. Elle ne peut pas implémenter des fonctions qui opèrent globalement sur les n bits, telles que les décalages ou les multiplications et divisions. Par ailleurs, l’addition est particulièrement inefficace à cause de la propagation de la retenue, et en particulier si le nombre n de modules est grand. Pour n = 32 par exemple, le calcul
CHAPITRE IV. ÉLÉMENTS FONCTIONNELS D’UN PROCESSEUR
114
d’une addition prendra 32 × p où p est le temps de calcul de l’addition sur l’UAL 1 bit. On peut tout de même dans ce cas adopter la même approche avec de meilleurs résultats, avec des blocs élémentaires de 4 bits par exemple, dans lesquels on aura optimisé le calcul de la retenue.
9. Circuit timer/PWM Principe Un circuit timer/PWM génère un signal cyclique rectangulaire dont les paramètres sont programmables. Ces paramètres sont : 1
la période P, exprimée en nombre de cycles de l’horloge générale.
2
la durée D à l’intérieur de cette période où la sortie vaut 0, exprimée en nombre de cycles.
La figure IV.27 illustre la forme du signal recherché.
N
P
Figure IV.27. Signal périodique généré par un circuit timer/PWM. Le terme PWM (Pulse Width Modulation : modulation en largeur d’impulsion) fait référence à une technique utilisée fréquemment pour moduler l’énergie fournie à certains dispositifs tels que des lampes, des moteurs à courant continu, etc. Plutôt que de leur fournir une tension variable, difficile à produire avec précision, on leur fournit un signal périodique dont on fait varier le rapport cyclique entre les moments où la tension est maximale et ceux où la tension est nulle (P − N sur la figure). La fréquence 1 doit être assez élevée pour éviter P P les accoups ou les clignotements, mais pas trop élevée pour éviter d’éventuels effets néfastes. Par exemple, les LEDs à haute luminosité qui équipent les feux de stop et arrière des voitures ont leur luminosité commandée de cette manière. Le circuit sera plutôt appelé ’timer’ si on n’exploite que les fronts montants ou descendants du signal de sortie, pour générer une interruption par exemple. Organisation Pour réaliser un timer/PWM sur n bits, on utilise un compteur n bits et deux comparateurs n bits, qui comparent en permanence la valeur du compteur aux deux paramètres P et N (figure IV.28). Le compteur dispose d’une remise à zéro synchrone (voir section 10.1), qui est activée dès que le compteur arrive à la valeur P. Ainsi il va compter 0, 1, …, P, c’est à dire avec une période de P+1 cycles d’horloge.
9. Circuit timer/PWM
115
Figure IV.28. Organisation d’un timer/PWM sur n bits. La valeur d’un compteur n bits est comparée aux valeurs de N et P ; quand il atteint P il est remis à 0 ; quand il dépasse N la valeur de la sortie m change. La bascule D finale prévient d’éventuels glitchs. Durant cette période P+1, la valeur du compteur est comparée à la valeur de N, et la sortie m est le reflet de cette comparaison. La sortie m n’est pas directement le résultat combinatoire de la comparaison, mais on le fait passer au travers d’une bascule D, pour deux raisons : 1
il pourrait être affecté de glitchs dans certaines situations ; en le faisant passer au travers d’une bascule D on est certain d’avoir un signal parfaitement propre.
2
son timing est exactement aligné avec les fronts d’horloge, ce qui améliore la précision.
La figure IV.29 donne le code SHDL d’un tel circuit timer/PWM sur 4 bits. On réutilise les circuits de comptage et de comparaison déjà décrits en SHDL aux sections 10.1 et 8.9. La période p est égale à p3…p0 + 1 en nombre de cycles de l’horloge clk ; la valeur n3…n0 définit la valeur n. Le timing précis de ce module est défini par le chronogramme figure IV.30.
10. RAMs et ROMs 10.1. Introduction Cette section est consacrée aux circuits utilisés dans la mémoire centrale des ordinateurs, qui contient les programmes et les données manipulés par les programmes. L’information y est stockée sous forme de mots de taille fixe, dont la largeur dépend de l’architecture utilisée. Ces mots mémoire sont ordonnés et possèdent chacun une adresse, sous forme d’un nombre. On peut donc voir une mémoire comme un ruban de cases numérotées :
CHAPITRE IV. ÉLÉMENTS FONCTIONNELS D’UN PROCESSEUR
116
// période = p3..p0, m à 1 après n3..n0 module pwm(rst,clk,en,p3..p0,n3..n0: m) cpt4(rst,clk,eq,en :c3..c0); cmp4(c3..c0,p3..p0 :sup,eq); cmp4(c3..c0,n3..n0 :supn,eqn); m:=dm ; m.clk=clk ; m.rst=rst ; dm=supn+eqn ; end module // compteur 4 bits avec raz synchrone // utilise des bascules T avec raz sync. module cpt4(rst,clk,sclr,en :a,b,c,d) tclr(rst,clk,sclr,en :d); tclr(rst,clk,sclr,tc :c); tc=en*d ; tclr(rst,clk,sclr,tb :b); tb=en*c*d ; tclr(rst,clk,sclr,ta :a); ta=en*b*c*d ; end module
// comparateur 4 bits non signé module cmp4(a3..a0,b3..b0 :sup3,eq) eq=eq3*eq2*eq1*eq0 ; sup3=a3*/b3+eq3*sup2 ; eq3=a3*b3+/a3*/b3 ; sup2=a2*/b2+eq2*sup1 ; eq2=a2*b2+/a2*/b2 ; sup1=a1*/b1+eq1*a0*/b0 ; eq1=a1*b1+/a1*/b1 ; eq0=a0*b0+/a0*/b0 ; end module // bascule T avec raz synchrone module tclr(rst,clk,sclr,t :q) q:=/t1*q+t1*/q ; q.clk=clk ; q.rst=rst ; t1=/sclr*t+sclr*q ; end module
Figure IV.29. Écriture SHDL d’un timer/PWM sur 4 bits.
cpt
0
1
2
3
4
5
0
m n3..n0 = 2 p = 6 (p3..p0 = 5)
Figure IV.30. Exemple de chronologie des signaux du timer/PWM sur 4 bits. données adresses
... 0
1
2
3
4
5
6
7
8
n−2 n−1 n
… ou comme un tableau : donnée = mem[adresse],
adresse ∈ [0, n].
De telles mémoires se classent en deux grands types, selon qu’elles sont en lecture seule ou en lecture-écriture : les RAMs (Random Access Memory) peuvent être lues et écrites, et l’attribut random indique que ces lectures-écritures peuvent se faire à des suites d’adresses totalement quelconques, par opposition à des mémoires de type séquentiel (comme les disques durs) qui font des séries d’accès à des adresses consécutives. Les ROMs (Read Only
10. RAMs et ROMs
117
Memory) sont fonctionnellement identiques aux RAMs, mais ne permettent que la lecture et pas l’écriture. Utilisées dans la mémoire centrale d’un ordinateur, les RAMs pourront stocker les programmes et les données, alors que les ROMs vont seulement stocker des programmes invariables, comme par exemple le programme exécuté au démarrage de la machine, et stocké dans la ROM dite de BIOS. Dans certains ordinateurs plus anciens, tout le système d’exploitation était stocké en ROM, ce qui permettait d’avoir un système incorruptible et au démarrage rapide. Une RAM peut être statique ou dynamique. Chaque bit mémoire d’une RAM statique (SRAM) est constitué d’une bascule, et conserve son état tant qu’elle est alimentée. A l’inverse, chaque bit d’une RAM dynamique (DRAM) est composé d’une capacité, qui doit être rafraîchie périodiquement par une électronique séparée. Les RAMs statiques ont un taux d’intégration plus faible que les RAM dynamiques, puisqu’un bit mémoire nécessite 6 transistors dans un cas, et une capacité plus un transistor dans l’autre. Une RAM peut être synchrone ou asynchrone, une RAM synchrone étant en fait une RAM asynchrone à laquelle on a ajouté une machine à états finis synchrone qui place les commandes de lecture et d’écriture dans un pipeline, afin de permettre d’accepter une nouvelle commande avant que la précédente n’ait été complétée. Les barrettes de RAM de nos ordinateurs personnels sont des SDRAM, c’est à dire des RAM dynamiques synchrones, fonctionnant à des fréquences de 200MHz et plus. Elles sont souvent de type DDR (double data rate), quand les fronts montants et descendants de l’horloge sont exploités pour les changements d’état. Dans beaucoup d’autres appareils (assistants personnels, consoles de jeux, etc.), la RAM est de type statique asynchrone (SRAM), sous la forme de circuits intégrés. Les ROMs existent également dans un grand nombre de types différents, principalement selon la façon dont on peut programmer leur contenu (invariable, par définition). Il y a d’abord les ROMs programmées par masque à l’usine ; elles sont produites en grand nombre avec un faible coût à l’unité, mais leur contenu ne peut jamais être mis à jour ultérieurement. Les PROMs (Programmable Rom) sont programmables par un appareil spécial, qui généralement détruit par un courant fort une liaison interne correspondant à un bit. Les EPROMs (Erasable PROM) fonctionnent de la même façon, mais possèdent une fenêtre transparente et peuvent être effacées par une exposition d’une vingtaine de minutes aux rayons ultraviolets. Elles sont maintenant souvent remplacées par des EEPROMs (Electrically EPROM), reprogrammables électriquement. Les mémoires Flash sont également une forme de mémoires effaçables électriquement, mais on réserve généralement le terme EEPROM aux mémoires capables d’effacer à l’échelle du mot, et le terme ’mémoires Flash’ à celles qui effacent à l’échelle de blocs. Dans les deux cas, le temps d’effacement est long par rapport au temps de lecture, et elles ne peuvent pas être considérées comme une forme spéciale de RAM. On trouve de la mémoire EEPROM et flash dans les assistants personnels, dans les sticks mémoire sur ports USB, pour le stockage du firmware de nombreux appareils (BIOS d’ordinateurs, lecteurs de DVD, tuners satellites, etc.)
10.2. Mode d’emploi des mémoires statiques asynchrones Les mémoires statiques ont généralement une organisation et un mode d’emploi
CHAPITRE IV. ÉLÉMENTS FONCTIONNELS D’UN PROCESSEUR
118
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 m
DIN
DOUT
n
ADR W CE
Figure IV.31. Interface d’un boîtier de RAM statique asynchrone de 2m mots de n bits,avec séparation des données lues et des données à écrire. Le mode d’emploi de ce circuit est le suivant : Lecture d’un mot mémoire 1.
activer la ligne CE (Chip Enable), placer l’adresse du mot sur les m lignes d’adresse ADR et garder inactive la ligne W (write).
2.
après un certain temps appelé temps d’accès en lecture, le mot mémoire (de largeur n) placé à cette adresse est disponible sur les lignes DOUT (Data Out).
Écriture d’un mot mémoire 1.
activer la ligne CE (Chip Enable), placer l’adresse du mot sur les m lignes d’adresse ADR, placer la donnée à écrire sur les n lignes DIN (DataIn) et activer la ligne W (write).
2.
après un certain temps appelé temps d’écriture, le mot (de largeur n) est écrit en mémoire, et pourra être lu ultérieurement.
Le temps d’écriture est généralement voisin du temps d’accès en lecture ; ces temps varient d’une dizaine de nanosecondes pour les mémoires statiques les plus rapides à une centaine de nanosecondes. En pratique, les boîtiers disponibles mettent en commun les lignes DIN et DOUT, de façon à minimiser le nombre de broches, et à faciliter l’interconnexion de plusieurs boîtiers. De tels circuits ont l’interface et la structure suivante : Une entrée supplémentaire OE (Output Enable) est nécessaire, qui fonctionne de la façon décrite page 98. Lors de l’écriture d’un mot mémoire, la ligne OE doit être inactive, et la donnée à écrire peut être placée sur DATA sans risque de court-circuit. Lors d’une lecture en mémoire, la ligne OE doit être activée après le temps d’accès en lecture pour que la donnée lue soit disponible sur DATA. On verra plus loin comment interconnecter ces circuits ; les précautions déjà évoquées doivent être respectées ici, et en particulier le risque
10. RAMs et ROMs
m
ADR
119
n
n
DATA
m
ADR
W
DIN
n
DOUT
DATA
ADR
CE
W
W
OE
CE
CE
OE
Figure IV.32. Interface d’un boîtier de RAM statique asynchrone de 2m mots de n bits,avec données lues et écrites communes. qu’il y a d’avoir à la fois OE active et une donnée présente en entrée de DATA, qui peut conduire à la destruction du circuit.
10.3. Structure interne d’une mémoire statique asynchrone Une mémoire statique de 2m mots de n bits est réalisée sous forme d’une matrice de 2 colonnes et n lignes de bascules D. Pour montrer un exemple facilement descriptible en 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. m
A1 A0
A1 A0
A1 A0
D
OE
CLK
D
OE
Q
OE
CLK
D
Q
OE
mot #3
CLK
D
CE
CE
CE
CE
mot #2
mot #1
mot #0
CLK
W
W
W
W
A1 A0
Q
OE
Q
CE W 8 DATA
MODULE RAM4X8(A[1..0],DIN[7..0],CE,W,OE: DOUT[7..0]) SEL0 = /A0*/A1 ; SEL1 = /A0*A1 ; SEL2 = A0*/A1 ; SEL3 = A0*A1 ; CLK[3..0] = SEL[3..0] * W ; OEG = OE * CE * /W ; OE_R[3..0] = OEG * SEL[3..0] ; REG8(DIN[7..0],CLK0,SEL0,OE_R0:DOUT[7..0]); REG8(DIN[7..0],CLK1,SEL1,OE_R1:DOUT[7..0]); REG8(DIN[7..0],CLK2,SEL2,OE_R2:DOUT[7..0]); REG8(DIN[7..0],CLK3,SEL3,OE_R3:DOUT[7..0]); END MODULE
Figure IV.33. Implémentation en SHDL d’une RAM statique asynchrone de 4 mots de 8 bits.
CHAPITRE IV. ÉLÉMENTS FONCTIONNELS D’UN PROCESSEUR
120
Les sorties des registres sont reliées entre elles, mais il n’y a pas de risque de court-circuit car leurs lignes OE sont indirectement reliées à quatre termes SEL3..SEL0 mutuellement exclusifs. Lorsqu’une adresse i est présentée sur A1,A0, seul le registre d’indice i va avoir sa ligne CE activée, c’est à dire que lui seul va être sélectionné. S’il s’agit d’une lecture (W = 0), il va fournir son contenu sur sa sortie si OE = 1. S’il s’agit d’une écriture, le front montant sur W va créer un front montant sur l’entrée CLK de la bascule, et donc écrire la donnée placée sur DATA dans le registre. En fait, chacun de ces registres doit être vu comme une colonne de plusieurs bascules D, ce qui donne en réalité l’organisation matricielle évoquée en début de section, qui dans l’exemple comporterait 8 lignes et 4 colonnes. Ce type d’organisation est idéal pour une réalisation industrielle ; ajouter une ligne d’adresse revient à multiplier par deux le nombre de colonnes, tout en gardant la même structure générale. C’est ainsi que, la technologie s’améliorant, la taille des puces mémoire continue de croître à peut près d’un facteur 2 tous les 18 mois, c’est à dire à la vitesse prévue par la loi de Moore.
10.4. Mode d’emploi des mémoires dynamiques asynchrones Les mémoires dynamiques ont généralement une organisation légèrement différente des mémoires statiques, même si cette différence n’est pas à attribuer à leur nature statique ou dynamique. Leur interface est celui de la figure IV.34. n m
DIN
DOUT
n
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 m m 2 x 2 = 22m cellules mémoire. La présentation des numéros de ligne et de colonne est synchronisée par les signaux RAS et CAS. RAS signifie sélection de ligne d’adresse (Row Address Select); CAS signifie sélection de colonne d’adresse (Column Address Selection).Le boîtier est inactif tant que RAS et CAS sont inactifs. Les chronologies de lecture et d’écriture sont montrées figures IV.35 et IV.36. Pour une lecture ou une écriture, on place d’abord un numéro de ligne sur ADR, qui est obtenu en prenant la moitié de l’adresse complète, généralement les poids forts et on active ensuite la ligne RAS (front descendant). On place ensuite sur ADR l’autre moitié de l’adresse complète, généralement les poids faibles, et on active ensuite la ligne CAS (front descendant). En cas de lecture (W = 1) la donnée sera disponible sur DOUT ; en cas
10. RAMs et ROMs ADR
RAS
121
11111 00000 00000 11111
numéro ligne
numéro colonne
111111 000000 000000 111111
CAS W
DOUT
11111111111111111111 00000000000000000000 00000000000000000000 11111111111111111111 donnée lue
Figure IV.35. Chronogramme de lecture dans une mémoire dynamique. ADR
RAS
11111 00000 00000 11111
numéro ligne
numéro colonne
111111 000000 000000 111111
CAS W
DIN
11111111111111111111 00000000000000000000 00000000000000000000 11111111111111111111 donnée à écrire
Figure IV.36. Chronogramme d’écriture dans une mémoire dynamique. d’écriture il faudra placer la donnée sur DIN. Un cycle spécifique de rafraîchissement existe également, qui opère sur des lignes entières de cellules. Chaque ligne doit être rafraîchie typiquement toutes les 5ms, et cela n’est pas fait automatiquement par le circuit : c’est la logique de commande qui est autour qui doit s’en charger. Un tel mode de fonctionnement correspond à la structure interne de la figure IV.37. On voit que, lorsque le numéro de ligne est présenté sur ADR et qu’un front descendant est appliqué sur RAS, ce numéro de ligne est stocké dans le registre de ligne. Sa valeur est décodée et sélectionne une des 2m lignes de la matrice. De la même façon, lorsque le numéro de colonne est présenté sur ADR et qu’un front descendant est appliqué sur CAS, ce numéro est stocké dans le registre de colonne, dont la valeur est également décodée et sélectionne une des colonnes. Une des cellules mémoire est ainsi sélectionnée. En cas de lecture (W = 1) la donnée sera disponible sur DOUT ; en cas d’écriture il faudra placer la donnée sur DIN. La mise en oeuvre de ces circuits est plus complexe que celle des mémoires asynchrones, puisqu’on ne peut plus présenter l’adresse entière directement. On est obligé de la présenter en deux fois, généralement poids forts d’abord (numéro de ligne) et poids faibles ensuite (numéro de colonne), ce qui peut être fait à l’aide d’un multiplexeur.
CHAPITRE IV. ÉLÉMENTS FONCTIONNELS D’UN PROCESSEUR
122 m ADR
m
m
CAS
registre + décodeur ligne
registre + décodeur colonne
amplificateurs n RAS
W
DIN
n DOUT
Figure IV.37. Structure interne d’une mémoire dynamique de 22m mots de n bits.
11. Exercices corrigés 11.1. Exercice 1 : association de décodeurs Énoncé Associer deux décodeurs 2 vers 4 avec entrée de validation, pour former un décodeur 3 vers 8 avec entrée de validation. On pourra ajouter quelques composants combinatoires simples si nécessaire. Solution Posons le problème en termes plus visibles. On cherche en fait à compléter la figure suivante :
11. Exercices corrigés
123
E2
E1
E1
E0
E0
EN
S3
S7
S2
S6
S1
S5
S0
S4
S3
S3
S2
S2
S1
S1
S0
S0
#1 E1 E0 EN EN
#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 : E2
E1
E1
E0
S3 S2 S1
E0 EN
S0
S7 S6 S5 S4
#1 E1 E0 EN
S3 S2 S1
EN
S0
S3 S2 S1 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 :
CHAPITRE IV. ÉLÉMENTS FONCTIONNELS D’UN PROCESSEUR
124
E2
E1
E1
E0
S3 S2 S1
E0 EN
S0
S7 S6 S5 S4
#1 E1 E0 EN
S3 S2 S1
EN
S0
S3 S2 S1 S0
#2
Bien sûr, puisque le module construit a lui même une entrée de validation, il peut être associé à un autre pour former un décodeur 4 vers 16, etc. On a ici un bel exemple de cascadabilité (ou récursivité matérielle).
11.2. Exercice 2 : fonctions combinatoires booléennes réalisées avec des décodeurs, multiplexeurs ou mémoires Énoncé Réaliser la fonction majorité à trois entrées avec : un décodeur 3 vers 8, ou un multiplexeur 8 vers 1, ou une ROM 8 × 1. Solution Le circuit qu’on cherche à implémenter possède trois entrées et une sortie. On rappelle que la fonction majorité à trois entrées vaut 1 si et seulement si au moins deux de ses entrées (sur trois) sont à 1 ; elle est utilisée notamment pour le calcul de la retenue dans les additionneurs. D’un point de vue algébrique, si on appelle E2,E1,E0 les entrées, les 4 − − minterms souhaités dans le résultat sont : E2 ∗ E1 ∗ E0, E2 ∗ E1 ∗ E0, E2 ∗ E1 ∗ E0, E2 ∗ − E1 ∗ E0. Avec un décodeur 3 vers 8, il suffit de relier les trois entrées aux entrées du décodeur. Chacune des 8 sorties est associée à chacun des 8 minterms possibles pour une fonction combinatoire à 3 entrées. On relie ensuite par un OU les 4 sorties correspondant aux 4 minterms désirés.
E2 E1 E0
000 001 010 011 100 101 110 111
Figure IV.38. Fonction majorité à 3 entrées réalisée avec un décodeur 3 vers 8.
11. Exercices corrigés
125
Avec un multiplexeur 8 vers 1 (figure IV.39), on sait que les entrées doivent être reliées aux lignes de commande, qui sont bien au nombre de 3 pour un multiplexeur 8 vers 1. Chacune des 8 entrées du multiplexeur correspond à un des 8 minterms possibles, et on force un 1 sur chaque entrée associée à un minterm qu’on souhaite inclure.
0 0 0
0 1 2
1 0 1 1 1
3 4 5 6 7
MAJ3(E2..E0)
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 E1 E0
1 1
000 : 0 ADR 001 : 0 DATA 010 : 0 011 : 1 100 : 0 101 : 1 EN 110 : 1 111 : 1 OE
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.
CHAPITRE IV. ÉLÉMENTS FONCTIONNELS D’UN PROCESSEUR
126
11.3. Exercice 3 : Équations d’un encodeur de priorité à 8 entrées Énoncé Écrire sous forme d’une somme de termes les plus simples possible les équations des sorties d’un encodeur de priorité à 8 entrées. Solution On se reportera à la page 106 pour l’usage d’un encodeur de priorité. On rappelle la table de vérité d’un tel encodeur :
E7
E6
E5
E4
E3
E2
E1
E0
NUM2..0
ACT
1
*
*
*
*
*
*
*
111
1
0
1
*
*
*
*
*
*
110
1
0
0
1
*
*
*
*
*
101
1
0
0
0
1
*
*
*
*
100
1
0
0
0
0
1
*
*
*
011
1
0
0
0
0
0
1
*
*
010
1
0
0
0
0
0
0
1
*
001
1
0
0
0
0
0
0
0
1
000
1
0
0
0
0
0
0
0
0
000
0
NUM2 vaut 1 dès lors qu’une des entrées E4, E5, E6 ou E7 est active : NUM2=E7+E6+E5+E4. NUM1 vaut 1 pour deux groupes de lignes dans la table de vérité. On a : NUM1 = E7 + /E7*E6 + /E7*/E6*/E5*/E4*E3 + /E7*/E6*/E5*/E4*/E3*E2 D’après la loi d’absorption, la variable E7 disparaît de plusieurs termes : NUM1 = E7 + E6 + /E6*/E5*/E4*E3 + /E6*/E5*/E4*/E3*E2 E7 ayant disparu, la loi d’absorption s’applique pour E6, puis sur E3 : NUM1 = E7 + E6 + /E5*/E4*E3 + /E5*/E4*/E3*E2 NUM1 = E7 + E6 + /E5*/E4*E3 + /E5*/E4*E2 On procède de façon analogue pour la simplification de NUM0 : NUM0 = E7 + /E7*/E6*E5 + /E7*/E6*/E5*/E4*E3 + /E7*/E6*/E5*/E4*/E3*/E2*E1 NUM0 = E7 + /E6*E5 + /E6*/E5*/E4*E3 + /E6*/E5*/E4*/E3*/E2*E1 NUM0 = E7 + /E6*E5 + /E6*/E4*E3 + /E6*/E4*/E3*/E2*E1 NUM0 = E7 + /E6*E5 + /E6*/E4*E3 + /E6*/E4*/E2*E1 On retrouve ainsi les équations vues plus haut à la figure IV.15.
11. Exercices corrigés
127
11.4. Exercice 4 : association de mémoires statiques asynchrones Énoncé Associer deux mémoires asynchrones de 2n mots de m bits pour former une mémoire de 2n mots de 2m bits, puis une mémoire de 2n + 1 mots de m bits. Solution première association Il faut d’abord remarquer que dans les deux cas, la mémoire obtenue a bien un nombre total de bits mémoire égal à la somme des bits mémoire des deux parties. Chaque boîtier initial possède m × 2n bits mémoire, et leur somme fait donc m × 2n + 1. Dans la première association, on aura 2n mots de 2m bits soit 2 × m × 2n = m × 2n + 1 ; dans la deuxième on aura aussi les m × 2n + 1 bits. Pour former une mémoire de 2n mots de 2m bits, il faut mettre essentiellement toutes les lignes des deux boîtiers en commun, à l’exception des lignes de données qu’il faut mettre en parallèle. Cela donne le circuit de la figure IV.41.
m Data
2xm Data
RAM
n ADR
ADR n
2 mots CS
CS
OE W
OE
de m bits
W
m Data n
RAM ADR n
2 mots CS
de m bits
OE R/W
Figure IV.41. Association de mémoires asynchrones, avec doublement de la largeur des données. Lorsqu’un accès mémoire (lecture ou écriture) est effectué, les deux boîtiers sont sélectionnés en même temps à la même adresse, et fournissent ou acceptent simultanément les deux moitiés de largeur m de la donnée globale de largeur 2m. deuxième association Pour former une mémoire de 2n + 1 mots de m bits, il va falloir mettre en commun les m lignes de données, et donc il ne va plus être possible de sélectionner les deux boîtiers en même temps, sous peine de court-circuit.Pour une moitié des adresses, il faudra sélectionner
CHAPITRE IV. ÉLÉMENTS FONCTIONNELS D’UN PROCESSEUR
128
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
ADR
n+1
m Data
Data n
addr[n−1..0]
ADR
addr[n]
RAM n
CS
0
2 mots de m bits
OE
1:2
W 1 CS CS
m Data n ADR
OE
RAM n
CS OE W
2 mots de m bits
W
Figure IV.42. Association de mémoires asynchrones, avec doublement du nombre d’adresses. Ce bit est mis en entrée d’un décodeur 1 vers 2 : s’il vaut 0, c’est le boîtier mémoire du haut qui est sélectionné ; s’il vaut 1 c’est le boîtier du bas. Tout cela suppose que le signal CS global soit actif, car s’il ne l’est pas, le décodeur ne sélectionne aucun boîtier Le signal OE suit un traitement particulier : pour chaque boîtier, il ne doit être actif que si le OE global est actif, et si le boîtier est sélectionné (CS = 1). On est alors assuré qu’aucun court-circuit n’est possible, puisque les OE des deux boîtiers sont alors mutuellement exclusifs. Dans cette configuration, l’espace des adresses de l’ensemble est [ 0 , 2n + 1 − 1] ; n, la première moitié [0 , 2n − 1] correspond au premier boîtier ; la seconde [2 2n + 1 − 1] correspond au second. Si on avait utilisé le bit de poids faible de l’adresse globale comme entrée du décodeur, la distribution des adresses entre les boîtiers aurait été différente : l’un aurait contenu les adresses paires, et l’autre les adresses impaires.
Chapitre V Programmation du processeur 32 bits CRAPS/SPARC Le langage machine (et sa forme équivalente symbolique dite assembleur) est exécuté directement par le processeur, et il est à ce titre l’intermédiaire obligé de tous les composants logiciels d’un ordinateur. Même s’il est vrai que peu de gens programment directement en assembleur, son étude est éclairante pour la compréhension de la relation entre logiciel et matériel. Le but de ce présent chapitre est d’initier à la programmation en assembleur du processeur CRAPS au travers de programmes de différentes natures, de façon à ensuite mieux comprendre son architecture et son fonctionnement.
1. Le langage machine, à la frontière entre logiciel et matériel Langage machine et ISA Les instructions qu’exécute un processeur sont écrites dans un langage appelé langage machine, et sont formées de codes écrits dans des mots mémoires consécutifs ; on appelle ISA (Instruction Set Architecture) ce langage et son organisation. L’informatique utilise un grand nombre de langages différents : des langages de programmation tels que C ou Java pour les programmeurs, des langages interprétés tels que les langages de macro des suites bureautiques, des langages de gestion de bases de données, etc. Mais la particularité du langage machine, c’est d’être le langage intermédiaire en lequel tous les autres langages vont être traduits, en une ou plusieurs étapes. Il est situé à la frontière entre le processeur et les logiciels, entre hardware et software, et le processeur est l’interpréteur de ce langage (figure V.1), celui qui met en action ses instructions. ISA générale ou spécialisée ? Bien sûr, il serait plus efficace d’avoir un processeur qui exécute directement un langage évolué, tout au moins pour les programmes écrits dans ce langage. De tels processeurs existent ou ont existé, notamment pour le langage Lisp, et plus récemment pour le langage intermédiaire (bytecode) utilisé en Java. Ces deux langages sont généralistes et peuvent être utilisés dans toutes les situations concrètes de l’informatique, et l’espoir des concepteurs de ces processeurs était d’élever au niveau de ces langages la frontière entre matériel et logiciel. Mais le bytecode Java exploite une machine à base de pile, peu adaptée à la compilation d’autres langages de programmation tels que C. Finalement, le compromis généralement adopté lors de la conception d’un nouveau processeur est le développement d’une ISA adaptée à la compilation de la plupart des langages de programmation courants .
129
CHAPITRE V. PROGRAMMATION DU PROCESSEUR 32 BITS CRAPS/SPARC
130
gestionnaire de bases de données (langage SQL)
programme en Javascript
interprétation
programme en langage C
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é directement par le processeur. Évolution des ISA Comme toute structure située à une frontière, l’ISA est un équilibre entre des forces opposées. Les concepteurs de compilateurs souhaitent une ISA régulière et possédant le plus de possibilités en termes d’adressage mémoire, d’opérations arithmétiques, etc., alors que les architectes des processeurs veulent une ISA simple à implémenter efficacement. Une force importante qui limite l’apparition de nouvelles ISAs (de nouveaux langages machine) est la demande des clients d’une compatibilité ascendante d’un processeur d’une génération à l’autre, de façon à pouvoir faire fonctionner sans changement leurs anciens programmes. Cette force se manifeste de façon très puissante avec les processeurs de PC (dont l’ISA est souvent désignée par x86, parce que les noms des processeurs se terminent par 86 : 8086, 80286, etc.), pour laquelle tout changement conduisant à une non compatibilité est exclu. Ainsi, les processeurs x86 les plus récents continuent à manifester la bizarrerie d’écriture en mémoire inversée de type little-endian, parce qu’elle était présente à l’origine sur le processeur 8 bits Intel 8080 pour des motifs de simplicité de conception, et qu’elle s’est transmise pour raison de compatibilité tout le long de la chaîne 8080, 8085, 8086, 80286, 80386, Pentium, Pentium 2, etc. jusqu’aux processeurs des PCs actuels. D’autres processeurs moins connus ont également une lignée très longue et respectable. On peut citer le processeur ARM, un des premiers processeurs 32 bits commercialisés, qui fait évoluer son ISA de façon ascendante depuis les années 80 jusqu’à maintenant, et qui équipe un grand nombre de téléphones portables, les consoles de jeux portables Nintendo et un grand nombre d’appareils mobiles. Processeurs CISC et RISC Une nouvelle génération de processeurs est apparue dans les années 80, caractérisée par un jeu d’instruction limité à l’essentiel, d’où leur nom de processeurs RISC (Reduced Instruction Set Computer). Par ailleurs, leur ISA est généralement très régulière, contient beaucoup de registres utilisateurs, et les instructions sont codées sous forme d’un mot mémoire unique. Tout cela rend heureux les architectes du processeur, qui peuvent concevoir des architectures très efficaces pour cette ISA, et ne rend pas trop malheureux les
1. Le langage machine, à la frontière entre logiciel et matériel
131
concepteurs de compilateurs, qui ont un peu plus de difficulté à traduire les programmes, mais qui apprécient le gain en vitesse d’exécution. Par opposition, les anciens processeurs tels que les x86 ont été rebaptisés processeurs CISC (Complex Instruction Set Computer), et si ce n’était l’énorme investissement dû au succès du PC avec l’ISA x86, les processeurs RISC auraient connus un développement plus immédiat et plus large. On les trouve néanmoins dans la plupart des stations de travail, dans les processeurs de Macintosh, et dans les consoles de jeux Sony PS[123], avec les performances que l’on sait.
2. Ressources du programmeur CRAPS/SPARC Le langage machine de CRAPS est un sous ensemble du langage machine du SPARC de SUN (au cas où vous ne l’auriez pas encore remarqué, ’CRAPS’ = ’SPARC’ à l’envers !), avec un certain nombre de limitations. C’est donc un langage de type RISC, qui sera compatible avec le SPARC au niveau binaire. Néanmoins il n’exploite pas la notion SPARC de fenêtre de registres et il n’a pas d’instruction d’arithmétique flottante.
Figure V.2. Le simulateur du processeur CRAPS. Il permet de développer des programmes et d’observer leur fonctionnement logiciel et matériel jusqu’à l’échelle du bit. Une fois au point, ils peuvent être transférés en mémoire et exécutés par le processeur réel. Afin de rendre plus concrètes les descriptions qui sont suivre, on va progressivement programmer le petit algorithme suivant qui fait la somme des 10 éléments d’un tableau : résultat <- 0 ; pour i de 0 à 9 faire résultat <- résultat + TAB[i] ; fin pour
2.1. Types de données de CRAPS CRAPS est un processeur 32 bits : cela signifie que les mots mémoire qu’il manipule le plus directement ont 32 bits de large, et que tous ses registres sont de 32 bits. Il peut néanmoins lire et écrire en mémoire des mots de 8 bits (octets) avec des instructions d’accès mémoire spécifiques. Les entiers signés sont codés en complément à 2 et les opérations arithmétiques disponibles sont l’addition et la soustraction sur 32 bits, la multiplication 16 bits X 16 bits vers 32 bits et la division 32 bits / 16 bits vers 32 bits. Il ne dispose pas de registres pour les nombres flottants, ni d’instruction d’arithmétique flottante. Si des calculs sur des réels doivent être effectués, ils doivent être exécutés par des librairies logicielles spécialisées. Aucun autre type de données n’est explicitement supporté. Les chaînes de caractères, les listes etc. devront être assemblées et manipulées en combinant les opérations sur les octets et les mots de 32 bits.
CHAPITRE V. PROGRAMMATION DU PROCESSEUR 32 BITS CRAPS/SPARC
132
2.2. Les registres de CRAPS Les registres banalisés CRAPS possède 32 registres directement accessibles au programmeur (figure V.3). On toujours 0
%r0 %r1 %r2
registres banalisés
... pointeur de pile utilisé par call
%r29 %r30 %r31
Figure V.3. Les registres du processeur CRAPS. Certains ont déjà des fonctions assignées. les note %r0, … %r31. Ils sont tous accessibles librement en lecture et en écriture, mais %r0 a un statut spécial : il vaut toujours 0 en lecture, quelles que soient les écritures qu’on fasse dedans. Cette bizarrerie est en fait très commode et on verra par la suite comment elle est exploitée. %r30 sera généralement utilisé comme pointeur de pile (mais c’est une convention qui n’est pas imposée par le matériel), et %r31 sert à stocker l’adresse de retour des instructions call. On utilisera souvent un troisième registre tel que %r29 comme pointeur de stack frame (voir plus loin). Si on excepte donc ces 4 registres spéciaux, le programmeur dispose de 28 registres qu’il peut utiliser à sa guise dans ses opérations locales, ce qui est un grand confort et permet de limiter grandement le nombre d’accès à la mémoire centrale. Notre algorithme se transforme de la façon suivante : %r1 <- 0; pour %r2 de 0 à 9 faire %r3 <- TAB[%r2] ; %r1 <- %r1 + %r3 ; fin pour
Trois registres utilisateurs sont déjà mobilisés, et ce n’est qu’un début ! On voit l’intérêt qu’offre au programmeur une ISA fournissant beaucoup de registres utilisateurs. Les registres spéciaux Les autres registres ne sont généralement pas accessibles directement par le programmeur et sont utilisés pour la gestion du fonctionnement interne du processeur. On trouve : •
%pc (Program Counter), qui contient l’adresse de l’instruction en cours d’exécution.
•
%ir (Instruction Register), qui contient l’instruction en cours d’exécution.
2. Ressources du programmeur CRAPS/SPARC •
133
%psr (Program Status Register) qui contient des informations sur l’état de l’exécution. Son format est donné figure V.4, et son rôle détaillé sera décrit dans des sections ultérieures. icc N Z V C 31
23
S
ET
8 7
5
PIL
20
11
0
icc = codes conditions (N, Z, V, C) PIL = niveau d’exécution courant S = bit superviseur ET = enable trap = exceptions autorisées
Figure V.4. %psr. (Program Status Register)
•
%tbr (Trap Base Register), qui contient l’adresse de la table des exceptions. Son rôle détaillé sera décrit dans les sections ultérieures.
2.3. Le modèle mémoire de CRAPS Comme la plupart des processeurs actuels, l’ISA de CRAPS voit la mémoire sous forme d’un ruban d’octets situés à des adresses consécutives (figure V.5). ...
octets adresse
0
1
2
3
4
32
32
32
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
CHAPITRE V. PROGRAMMATION DU PROCESSEUR 32 BITS CRAPS/SPARC
134
32 bits occupe nécessairement 4 octets, les valeurs du tableau sont espacées de 4 octets en 4 octets en mémoire centrale, à des adresses croissantes à partir de l’adresse TAB de début du tableau. Les 4 octets qui forment le nombre TAB[i] pour un indice i quelconque sont ainsi situés de l’adresse TAB + 4i à l’adresse TAB + 4i + 3 (figure V.6). élément i octets adresses
...
élément i+1 ...
... TAB
TAB+4i
TAB+4i+3
Figure V.6. Implantation mémoire d’un tableau de nombres de 32 bits. Notre algorithme devient : %r1 <- 0 ; %r4 <- adresse de TAB ; pour %r2 de 0 à 9 faire %r3 <- mem[%r4+4*%r2,...,%r4+4*%r2+3] ; %r1 <- %r1 + %r3 ; fin pour
Pour éviter d’avoir à effectuer la multiplication par 4, on peut incrémenter %r4 de 4 à chaque tour de boucle : %r1 <- 0 ; %r4 <- adresse de TAB ; pour %r2 de 0 à 9 faire %r3 <- mem[%r4,...,%r4+3] ; %r1 <- %r1 + %r3 ; %r4 <- %r4 + 4 ; fin pour
Écriture en mémoire big-endian et little-endian Un détail d’implémentation n’a pas été précisé. On a dit qu’un mot de 32 bits était stocké en mémoire centrale sous forme de 4 octets consécutifs à des adresses i, i + 1, i + 2, i + 3, mais on n’a pas précisé exactement dans quel ordre. Bien sûr, par des opérations fines d’écriture au niveau de l’octet, notre programme pourrait les stocker dans n’importe lequel des 24 = 16 ordres possibles. Mais un processeur 32 bits est là pour manipuler directement des mots de 32 bits, et en particulier pour lire et écrire en mémoire des mots de 32 bits, et il fait cela selon un ordre qui lui est propre. En pratique, seuls deux tels ordres de stockage sont utilisés. L’ordre dit big-endian, le plus naturel, est utilisé par la grande majorité des processeurs, à l’exception (très notable) des processeurs x86. Il consiste à stocker les octets de telle façon que le mot mémoire se lise dans l’ordre naturel, si on visualise le ruban mémoire de gauche à droite. Ainsi pour stocker en mémoire le mot de 32 bits 0x11223344, on stockera à des adresses croissantes les octets 0x11, 0x22, 0x33, 0x44. L’ordre dit little-endian, utilisé par les processeurs x86, stockera ce même mot dans l’ordre inverse 0x44, 0x33, 0x22, 0x11 (figure V.7).
2. Ressources du programmeur CRAPS/SPARC ...
octets adresse
0
...
octets adresse
...
0
...
135
0x11 0x22 0x33 0x44 i
i+1
i+2
i+1
i+2
big−endian
...
little−endian
i+3
0x44 0x33 0x22 0x11 i
...
i+3
Figure V.7. Un même mot de 32 bits 0x11223344 stocké en mémoire centrale dans l’ordre big-endian et little-endian. Cet ordre de stockage peu naturel des processeurs x86 prend son origine dans le processeur Intel 8080, qui stockait déjà un mot de 16 bits en inversant les deux octets, par commodité de conception. Pour des raison de compatibilité ascendante de leur ISA, Intel a été obligé de conserver cet état de fait dans les successeurs 8 bits du 8080, et même de l’étendre de façon logique au stockage des mots de 32 bits, puis de 64 bits dans leurs dernières versions de Pentium. Les termes étranges big-endian et little-endian proviennent des ’Voyages de Gulliver’ d’Oliver Swift, lorsque Gulliver est au pays des politiciens. Une bataille politique fait rage sur la façon de casser son oeuf à la coque, entre le parti des ’big-endians’, partisans de le casser par le gros bout, et celui des ’little-endians’ partisans du petit bout ! Cette différence d’ordre de stockage est une source d’incompatibilité majeure entre les processeurs x86 et les autres. On peut noter que le SPARC peut modifier son ordre de stockage à la volée, par le positionnement d’un bit de contrôle spécial.
2.4. Les instructions de CRAPS Toutes les instructions de CRAPS sont constituées d’un seul mot de 32 bits. Cette caractéristique des instructions d’avoir une taille fixe d’un mot est typique des processeurs RISC, et elle facilite grandement certains aspects de leur conception. Les instructions de CRAPS sont formées de plusieurs groupes, chacun ayant une certaine structure de codage (figure V.8). La valeur du champ op permet d’identifier immédiatement le groupe de l’instruction. À l’intérieur des groupes 2 et 3, ce sont les champs op2 et op3 qui permettent de distinguer les sous-groupes. Ces champs op, op2, op3 sont appelés opcodes et sont un moyen de classer les instructions. Le tableau de la figure V.9 donne la signification de leurs valeurs pour le processeur CRAPS. Toutes les instructions du SPARC n’ont pas été reprises, mais on a conservé les mêmes valeurs d’opcodes pour celles qui l’ont été. Les différentes combinaisons possibles entre ces valeurs d’opcodes sur les formats d’instructions conduisent au tableau d’instructions de la figure V.10. Ce tableau est remarquablement réduit ; SPARC/CRAPS mérite véritablement son qualificatif de processeur RISC. Les versions suivantes du SPARC, notamment l’UltraSparc II sur 64 bits, vont ajouter à cet ensemble beaucoup de formats d’instructions supplémentaires, et perdre de cette élégante simplicité.
CHAPITRE V. PROGRAMMATION DU PROCESSEUR 32 BITS CRAPS/SPARC
136
Format 1 : call
op 0 1
disp30 Format 2 : sethi et branchements
op 0 0
rd
op2
imm22
2a
cond
op2
disp22
2b
op 0 0 0
Format 3 : accès mémoire, instructions arithmétiques
op 1t
rd
op3
rs1
0 −− −− − − − −
1t
rd
op3
rs1
1
rs2
simm13
3a 3b
t = 1 : accès mémoire, t = 0 : instruction arithmétique
Figure V.8. Structure binaire des différents groupes d’instructions de CRAPS.
op2
Instr.
op3 (t = 0)
Instr.
op3 (t = 1)
Instr.
cond
Branchement
010
branch
000000
add
000000
ld
1000
ba
100
sethi
010000
addcc
000001
ldub
0001
be
000100
sub
000100
st
1001
bne
010100
subcc
000101
stb
0101
bcs
011010
umulcc
1101
bcc
010001
andcc
1110
bpos
010010
orcc
0110
bneg
010011
xorcc
0111
bvs
010111
xnorcc
1111
bvc
110101
sll
1010
bg
110110
srl
0010
ble
111000
jmpl
1011
bge
0011
bl
1100
bgu
0100
bleu
Figure V.9. Significations des valeurs d’opcodes. Langage assembleur Dans ce tableau V.10, les instructions ont été écrites, non pas en langage machine qui aurait été peu compréhensible, mais dans une écriture symbolique équivalente dite écriture assembleur. Pour chaque processeur, le constructeur fournit le descriptif complet
2. Ressources du programmeur CRAPS/SPARC
137
load/store ld [%ri+reg/cst],%rj
lecture mémoire 32 bits
ldub [%ri+reg/cst],%rk
lecture mémoire 8 bits
st %ri,[%rj+reg/cst]
écriture mémoire 32 bits
stb %ri,[%rj+reg/cst]
lecture mémoire 8 bits
contrôle call addr
appel de sous-programme
jmpl addr, %ri
jump and link
ba addr
branchement inconditionnel
bcc addr
branchement si condition cc vraie système rdpsr
lecture de %psr
wrpsr
écriture dans %psr
wrtbr
écriture dans %tbr
arithmétiques et logiques addcc %ri,reg/cst,%rj
addition %rj←%ri+reg/cst
subcc %ri,reg/cst,%rj
soustraction %rj←%ri-reg/cst
umulcc %ri,reg/cst,%rj
multiplication %rj←%ri*reg/cst
udivcc %ri,reg/cst,%rj
division %rj←%ri/reg/cst
andcc %ri,reg/cst,%rj
AND %rj←%ri and reg/cst
orcc %ri,reg/cst,%rj
OR %rj←%ri or reg/cst
xorcc %ri,reg/cst,%rj
XOR %rj←%ri xor reg/cst
xnorcc %ri,reg/cst,%rj
XNOR %rj←%ri xnor reg/cst
sll %ri,reg/cst,%rj
décalage à gauche %rj←%ri << reg/cst
srl %ri,reg/cst,%rj
décalage à droite %rj←%ri >> reg/cst
sethi val22, %ri
%ri21..0 ← val22
reg/cst : registre ou constante signée sur 13 bits val22 : constante non signée sur 22 bits
Figure V.10. Les groupes d’instructions du processeur CRAPS. du jeu d’instruction en langage machine, mais aussi une écriture assembleur conseillée équivalente. Les développeurs peuvent alors écrire des programmes assembleurs, dits encore assembleurs, dont le rôle est de traduire un texte source écrit en langage assembleur, en la suite des codes machine correspondants. Le terme assembleur désigne donc à la fois le langage du processeur (sous sa forme symbolique) et le programme qui traduit un texte source du langage assembleur vers le langage machine. La figure V.11 montre une
CHAPITRE V. PROGRAMMATION DU PROCESSEUR 32 BITS CRAPS/SPARC
138
instruction écrite en assembleur, et son code machine équivalent. addcc %r2, 10, %r3
↔
10.00011.010000.00010.1.0000000001010
Figure V.11. Une instruction en assembleur CRAPS/SPARC et le code machine correspondant. Instructions synthétiques Certaines instructions semblent manquer. Par exemple, il n’y a pas d’instruction de copie d’un registre vers un autre, ou d’instruction qui compare 2 registres, ou encore d’instruction qui mette à zéro un registre. En fait, il n’est pas nécessaire que ces instructions existent, car elles sont des cas particuliers d’instructions plus générales. Par exemple la mise à 0 d’un registre %ri peut se faire par l’instruction orcc %r0, %r0, %ri. On exploite ici le fait que %r0 vaut toujours 0 ; le OR de 0 avec %r0 (qui vaut 0) est ainsi stocké dans %ri, ce qui est le résultat souhaité. Dans la syntaxe du processeur CRAPS/SPARC figurent ainsi des instructions dites synthétiques, qui sont en fait remplacées au moment de l’assemblage par la ou les instructions équivalentes. Le tableau V.12 montre l’ensemble des instructions synthétiques disponibles pour CRAPS. Initialisation d’un registre par une constante L’instruction synthétique set val,%ri sert à initialiser un registre %ri avec une constante dite immédiate, c’est à dire fournie directement dans le programme. Cette instruction synthétique est remplacée par deux instructions sethi val31..10,%ri et orcc %ri,val9..0,%ri qui vont effectivement bien faire cette affectation, en positionnant d’abord les 22 bits de poids fort avec sethi, puis les 10 bits de poids faibles avec orcc. Le fait d’avoir à exécuter deux instructions pour initialiser un registre à une valeur immédiate est typique des processeurs RISC. Ceux-ci ayant des instructions de la taille de leur mot mémoire, ils ne peuvent pas dans une seule instruction fournir une donnée immédiate de cette taille. On notera la présence de l’instruction synthétique setq (rajoutée par rapport au SPARC) qui permet de faire une telle initialisation en une seule instruction lorsque la constante est petite (13 bits signée, c’est à dire entre -4096 et +4095). Elle est équivalente à orcc %r0, val, %ri qui tire encore parti du fait que %r0 vaut 0. On notera également la très classique instruction mov %r1,%rj qui en réalité est : orcc %ri,%r0,%rj,réalisant bien la copie du contenu de %ri dans %rj. Nous pouvons maintenant mettre un certain nombre d’instructions CRAPS en face des lignes de notre petit algorithme : clr set
%r1 TAB, %r4
; %r1 <- 0 ; ; %r4 <- adresse de TAB ;
pour %r2 de 0 à 9 faire ld addcc addcc
[%r4], %r3 %r1, %r3, %r1 %r4, 4, %r4
; %r3 <- mem[%r4,...,%r4+3]; ; %r1 <- %r1 + %r3 ; ; %r5 <- %r5 + 4 ;
2. Ressources du programmeur CRAPS/SPARC
139
Instruction
Effet
Implémentation
clr %ri
met à zéro %ri
orcc %r0, %r0, %ri
mov %ri,%rj
copie %ri dans %rj
orcc %ri, %r0, %rj
inccc %ri
incrémente %ri
addcc %ri, 1, %ri
deccc %ri
décrémente %ri
subcc %ri, 1, %ri
notcc %ri,%rj
%rj <- complément de %ri
xnorcc %ri, %ri, %rj
set val31..0, %ri
copie val dans %ri
sethi val31..10, %ri orcc %ri, val9..0, %ri
setq val, %ri
copie val dans %ri
orcc %r0, val, %ri
( − 4096 ≤ val ≤ 4095) cmp %ri, %rj
compare %ri et %rj
subcc %ri, %rj, %r0
tst %ri
teste nullité et signe de %ri
orcc %ri, %r0, %r0
negcc %ri
inverse %ri
subcc %r0, %ri, %ri
notcc %ri,%rj
complémente %ri en %rj
xnorcc %ri, %r0, %rj
nop
no operation
sethi 0,%r0
jmp %ri
branchement à l’adresse absolue %ri
jmpl %ri, %r0
ret
retour de procédure terminale
jmpl %r31+4, %r0
push %ri
empile %ri
sub %r30, 4, %r30 st %ri, [%r30]
pop %ri
dépile %ri
ld [%r30], %ri add %r30, 4, %r30
Figure V.12. Les instructions synthétiques de CRAPS. Elles sont traduites en une écriture équivalente.
fin pour
On peut remarquer la syntaxe des commentaires : un caractère ’;’et tout ce qui le suit jusqu’à la fin de la ligne est ignoré, de façon analogue à la séquence ’//’ du C/C++. Dans ce programme, ne manquent que les instructions qui vont permettre d’implémenter la structure de contrôle pour … fin pour. Toutes les autres instructions sont de nature séquentielle, c’est à dire qu’elles s’exécutent dans l’ordre où elles sont présentes en mémoire, disposées à des adresses croissantes de 4 en 4.
140
CHAPITRE V. PROGRAMMATION DU PROCESSEUR 32 BITS CRAPS/SPARC
2.5. Ruptures de séquence en CRAPS Bien sûr, si un processeur n’était doté que d’instructions séquentielles, il serait incapable d’exécuter la moindre tâche un peu complexe, puisqu’incapable d’implémenter des boucles ou des structures de contrôle de type tant que. Il faut donc absolument qu’il soit doté d’instructions dites de rupture de séquence, qui permettent, non seulement de ’sauter’ à une instruction qui n’est pas la suivante, mais aussi de faire ce saut de façon conditionnelle, en fonction des résultats des opérations précédentes. Un registre spécial de CRAPS/SPARC appelé %pc (program counter), non directement accessible au programmeur, contient en permanence l’adresse de l’instruction en cours d’exécution. À la fin de l’exécution d’une instruction séquentielle, %pc est systématiquement incrémenté de 4, de sorte qu’au cycle d’exécution suivant, le contrôle est passé à l’instruction suivante en mémoire, placée 4 octets plus loin. Branchements inconditionnels Pour effectuer un saut inconditionnel tel que ba addr, il suffit que cette instruction n’effectue pas l’incrémentation de %pc de 4, mais au contraire ’force’ la valeur de l’adresse addr dans %pc. Ainsi au cycle d’exécution suivant, le processeur chargera l’instruction située à cette nouvelle adresse, et le contrôle sera dont transféré à ce point dans le programme. Branchements conditionnels Ce saut peut également être fait de façon conditionnelle avec les instruction bcc telles que bne, beq, bpos, etc. A chacune de ces instructions est associé un test booléen ; si ce test est vérifié, le saut est effectué (%pc est alors affecté avec l’adresse de saut) ; sinon %pc est incrémenté de 4, c’est à dire que le saut n’est pas fait et que le contrôle continue en séquence dans le programme, à l’instruction qui suit ce branchement. Le test en question est fait sur la valeur de 4 bits N, Z, V, C appelés indicateurs ou flags, qui sont positionnés après certaines instructions arithmétiques et logiques. Ils ont les significations suivantes : •
N indique que le résultat de l’opération est négatif, c’est à dire que son poids fort est à 1.
•
Z indique que le résultat de l’opération est nul.
•
V indique qu’il y a eu débordement d’une addition ou d’une soustraction signée.
•
C indique qu’il y a eu une retenue lors d’une addition ou emprunt lors d’une soustraction.
Les instructions qui affectent les flags ont un nom qui se termine généralement par ’cc’ : addcc, xorcc, etc. Pour être sûr des flags qui sont affectés ou non par une instruction, il faut se reporter à sa description détaillée, présente à la fin de l’ouvrage. Par exemple les instructions ld et ldub de lecture en mémoire affectent les flags Z et N selon que la valeur lue est (ou non) nulle ou négative (respectivement). Ce choix est contraire à celui du SPARC, pour lequel les instructions de lecture en mémoire n’affectent pas les flags. Nous avons jugé quant à nous qu’une lecture en mémoire est souvent suivie d’un test sur la nullité ou le signe de la valeur, et donc qu’il était intéressant d’avoir les flags positionnés.
2. Ressources du programmeur CRAPS/SPARC
141
Les instructions de branchement conditionnel bcc effectuent le saut pour certaines combinaisons de valeurs des flags. Les tableaux des 3 figures V.13 à V.15 les décrivent, le premier tableau présentant les branchements conditionnels les plus simples qui ne font intervenir qu’un seul flag, le second présentant les branchements associés à une arithmétique signée et le troisième les branchements associés à une arithmétique non signée. instruction
opération
test
ba
Branch Always
1
beq (synonyme : bz)
Branch on Equal
Z
bne (synonyme : bnz)
Branch on Not Equal
bneg (synonyme : bn)
Branch on Negative
bpos (synonyme : bnn)
Branch on Positive
bcs
Branch on Carry Set
bcc
Branch on Carry Clear
bvs
Branch on Overflow Set
bvc
Branch on Overflow Clear
not Z N not N C not C V not V
Figure V.13. Branchements conditionnels associés à un seul flag. instruction
opération
bg
Branch on Greater
bge
Branch on Greater or Equal
bl
Branch on Less
ble
Branch on Less or Equal
test not (Z or (N xor V)) not (N xor V) (N xor V) Z or (N xor V)
Figure V.14. Branchements conditionnels associés à une arithmétique signée. instruction
opération
bgu
Branch on Greater Unsigned
bcc
Branch on greater than, or equal, unsigned
bcs
Branch on less than, unsigned
bleu
Branch on Less or Equal Unsigned
test not (Z or C) not C C Z or C
Figure V.15. Branchements conditionnels associés à une arithmétique non signée. Les branchements du premier tableau qui ne font intervenir qu’un seul flag peuvent être utilisés dans la plupart des situations simples, et on peut en les combinant n’utiliser qu’eux dans des situations plus complexes. Les branchements associés à l’arithmétique signée et non signée des deux tableaux suivants ne se comprennent que si on suppose qu’une comparaison entre deux valeurs vient
CHAPITRE V. PROGRAMMATION DU PROCESSEUR 32 BITS CRAPS/SPARC
142
d’être faite, c’est à dire dans un programme de la forme : cmp bxx
%ri, reg/val saut
bxx représente une des instructions de branchement conditionnel, et reg/val représente un registre ou une constante. L’instruction synthétique cmp %ri,reg/val est équivalente à subcc %ri,reg/val,%r0 c’est à dire qu’elle effectue la soustraction %ri - reg/val dans le but d’obtenir, non pas la valeur du résultat en tant que telle (qui est éliminée dans %r0), mais plutôt la valeur des flags après cette soustraction. Ceux-ci permettent en effet de tout savoir sur les positions respectives de %ri et de reg/val :
•
%ri = reg/val ⇔ %ri − reg/val = 0, soit Z.
•
− %ri > reg/val ⇔ %ri − reg/val > 0, soit N = 0 et Z = 0, soit N + Z.
•
− %ri ≥ reg/val ⇔ %ri − reg/val ≥ 0, soit N.
•
%ri < reg/val ⇔ %ri − reg/val < 0, soit N.
•
%ri ≤ reg/val ⇔ %ri − reg/val ≤ 0, soit N = 1 ou Z = 1, soit Z + N.
En arithmétique signée, on doit remplacer N par (N + V) dans ces équations pour tenir compte des situations où on compare des grands nombres en valeur absolue, et où la soustraction provoque un débordement mais qui permet tout de même de conclure. Cela conduit alors aux équations du tableau V.14. En arithmétique non signée, on doit remplacer N par C, car C va avoir la même valeur que N lors de la soustraction de petites valeurs, mais va également permettre de conclure dans des situations où la soustraction a une grande amplitude et change le signe du résultat, telles que la comparaison de 0x00000001 et 0xFFFFFFFF : leur soustraction donne 0x00000002, avec N=0 et C=1. Seul C indique dans tous les cas qu’il a fallu emprunter pour soustraire les deux arguments. Cela conduit aux équations du tableau V.15. Maintenant, nous sommes prêts à terminer la programmation de notre algorithme : ;;;;;;;;;; somme des éléments clr %r1 set TAB, %r4 clr %r2 loop: ld [%r4], %r3 addcc %r1, %r3, %r1 addcc %r4, 4, %r4 inccc %r2 cmp %r2, 9 bleu loop
d’un tableau ;;;;;;;;; ; %r1 <- 0 ; ; %r4 <- adresse de TAB ; ; pour i de 0 à 9 ; %r3 <- mem[%r4,...,%r4+3]; ; %r1 <- %r1 + %r3 ; ; %r5 <- %r5 + 4 ;
; fin pour
%r2 est initialisé à 0 au début de la boucle pour … fin pour. En fin de boucle, %r2 est incrémenté (incc %r2), puis comparé à 9. En toute rigueur il faut utiliser bleu puisqu’on fait un test ≤ non signé, mais bpos aurait fonctionné également car les valeurs des indices restent petites.
2. Ressources du programmeur CRAPS/SPARC
143
Ici l’indice i n’a été utilisé que comme un compteur. On peut raccourcir le programme en initialisant %r2 à 10, et en le décrémentant à chaque tour de boucle jusqu’à 0 : ;;;;;;;;
loop:
somme des éléments d’un tableau ;;;;;;;; clr %r1 ; %r1 <- 0 ; set TAB, %r4 ; %r4 <- adresse de TAB ; set 10, %r2 ; compteur à 10 ld [%r4], %r3 ; %r3 <- mem[%r4,...,%r4+3]; addcc %r1, %r3, %r1 ; %r1 <- %r1 + %r3 ; addcc %r4, 4, %r4 ; %r5 <- %r5 + 4 ; deccc %r2 ; décrémente compteur bne loop ; reboucle tq compteur <> 0
On n’a rien gagné en nombre d’instructions brut, car l’instruction synthétique set 10,%r2 occupe en réalité 2 instructions. Mais on a fait passer le corps de la boucle de 5 à 4 instructions, soit un gain en vitesse de 20%. L’optimisation est négligeable sur ce programme, mais elle peut devenir significative quand les boucles sont parcourues un grand nombre de fois. Branchements relatifs Pour tous les branchements que nous venons de voir, ba et bcc, ce n’est pas l’adresse absolue du saut qui est codée, mais un déplacement relatif à cette adresse. Si on reprend le codage binaire de ces instructions (figure V.8), on voit que le branchement est codé dans le champ disp22 qui code un déplacement signé, en nombre de mots. Le déplacement est signé, ce qui signifie qu’une valeur négative conduira à un saut en arrière dans le programme, et un déplacement positif à un saut en avant. Le déplacement est codé en nombre de mots, c’est à dire qu’il faut le multiplier par 4 pour obtenir le déplacement en nombre d’octets. Ainsi, puisque l’instruction en cours d’exécution a son adresse stockée dans le registre %pc, le saut conduira à faire l’affectation : %pc ← %pc + 4 * disp22 Par exemple, l’instruction de notre programme bne loop décrit un saut (conditionnel) de 4 instructions en arrière, soit un saut à une adresse plus petite de 16. C’est la valeur -4 qui est codée, soit : 00.0.1001.010.1111111111111111111100
bne loop
Coder le saut sous forme d’un déplacement présente deux avantages : 1
c’est plus économique en mémoire, puisque tout le saut est codé en une instruction d’un mot.
2
l’instruction obtenue est translatable, c’est à dire qu’un programme qui ne comprend que de telles instructions peut être placé n’importe où en mémoire. C’est une caractéristique importante des programmes lorsqu’ils doivent être chargés par le système d’exploitation à une place libre en mémoire.
Le saut effectué par ces instructions ne peut pas être fait d’une extrémité de la mémoire à l’autre. On peut voir sur le format binaire des instructions figure V.8 que le déplacement est codé sur 22 bits, et donc on ne peut pas faire un saut de plus de 221 instructions en avant ou
CHAPITRE V. PROGRAMMATION DU PROCESSEUR 32 BITS CRAPS/SPARC
144
en arrière. En pratique c’est une valeur très suffisante tant que le saut est fait à l’intérieur d’un même programme. Branchements absolus Dans certaines situations, on peut avoir le besoin de se brancher à une adresse absolue, et non une adresse locale et relative au bloc de code courant. Le système d’exploitation a ce besoin, lorsque son scheduler de tâches va enlever le contrôle à une tâche pour le donner à une autre par exemple, ou lorsqu’il va exécuter un sous-programme d’interruption. Nous en verrons des exemples plus loin dans ce chapitre. L’instruction jmpl (jump and link) remplit cette fonction. Elle a la forme : jmpl %ri+%rj/cst, %rk
L’adresse de saut est %ri+%rj/cst ; dans %rk est stockée l’adresse de l’instruction jmpl, c’est à dire l’adresse d’où l’on part. Si on désire faire un saut simple à l’adresse contenue dans %ri, on écrira : jmpl %ri, %r0
Comme d’habitude dans cette écriture, le terme %rj/cst a disparu car il a été choisi égal à %r0 ou à la constante 0, au choix. Cette expression peut s’écrire de façon plus simple avec l’instruction synthétique jmp %ri.
2.6. Les modes d’adressage de CRAPS Un mode d’adressage est un type d’accès aux données pour le processeur. Pour CRAPS, ils sont au nombre de 4 : 1
l’adressage registre : la donnée est dans un registre, spécifié par son numéro. Par exemple : addcc %r1, %r2, %r3
Ici les 3 données manipulées sont dans des registres. 2
l’adressage immédiat : l’instruction elle-même, dans son codage en langage machine, contient la donnée. Par exemple : addcc %r1, 2, %r3
Ici la deuxième donnée (2) est codée dans le champ simm13 de l’instruction. 3
l’adressage direct (ou absolu) : l’instruction contient l’adresse de la donnée. Par exemple : ld [600], %r1
%r1 est chargé avec la donnée située en mémoire à l’adresse 600. Cette adresse 600 est codée dans l’instruction elle-même (un peu comme dans un adressage immédiat), dans son champ simm13. Comme cette valeur ne peut pas dépasser 4095, ce mode d’adressage est quasiment inutilisable dans CRAPS.
2. Ressources du programmeur CRAPS/SPARC 4
145
l’adressage indirect : l’instruction contient l’adresse de l’adresse de la donnée. Par exemple : ld [%r1], %r2
%r2 est chargé avec la donnée située à l’adresse contenue dans %r1. ’L’adresse de l’adresse’est ici le numéro du registre (1) qui contient l’adresse de la donnée. Ce mode d’adressage est indispensable pour accéder aux éléments d’un tableau de taille non connue a priori. C’est par exemple celui qu’on a utilisé dans notre programme de calcul de la somme des éléments d’un tableau, pour accéder aux nombres en mémoire. 5
l’adressage indirect avec déplacement. Par exemple : ld [%r1-2], %r2
Ce cas est similaire au précédent, mais on ajoute au contenu du registre une constante (ici -2) pour obtenir l’adresse de la donnée.
3. Directives d’un programme assembleur Si on cherche à assembler effectivement, à l’aide de CRAPSEMU par exemple, le programme de somme des éléments d’un tableau étudié à la section précédente, le symbole TAB sera déclaré comme indéterminé. Il est en effet censé représenter l’adresse de début du tableau, mais rien n’a été spécifié pour l’initialiser. Une directive dans un programme assembleur est une ligne qui ne correspond pas à une instruction, mais qui spécifie : 1
la localisation en mémoire d’un bloc d’instructions ou de données.
2
la réservation et/ou l’initialisation d’un bloc de données.
Pour notre tableau TAB, nous avons besoin de spécifier à la fois son adresse, et son contenu initial, si on suppose qu’il est donné et non qu’il est le résultat d’un calcul antérieur. Le programme suivant contient les directives nécessaires : ;;;;;;;;;; zone de programme .org 0
loop:
clr set clr ld addcc addcc inccc cmp bleu ba
%r1 TAB, %r4 %r2 [%r4], %r3 %r1, %r3, %r1 %r4, 4, %r4 %r2 %r2, 9 loop *
;;;;;;;;;;
;;;;;;;;;
; ; ; ; ; ;
%r1 <%r4 <pour i %r3 <%r1 <%r5 <-
0 ; adresse de TAB ; de 0 à 9 mem[%r4,...,%r4+3]; %r1 + %r3 ; %r5 + 4 ;
; fin pour
zone de données
;;;;;;;;;
CHAPITRE V. PROGRAMMATION DU PROCESSEUR 32 BITS CRAPS/SPARC
146
TAB
.org .word
0x1000 12, -4, 5, 8, 3, 10, 4, -1, 1, 9
Adresse d’assemblage La directive .org change l’adresse d’assemblage courante. Avec .org 0, le programme sera assemblé à partir de l’adresse 0. Le tableau TAB sera situé plus loin en mémoire, à partir de l’adresse 0x1000, grâce à la directive .org 0x1000. Allocation et initialisation de mots en mémoire La directive .word fournit une liste de nombres, écrits en décimal, hexadécimal ou binaire, qui seront placés en mémoire à partir de l’adresse d’assemblage courante. Dans notre programme exemple, cela nous permet de créer un tableau et de l’initialiser statiquement (c’est à dire avant le lancement du programme) avec 10 mots de 32 bits. Il occupera 40 octets, de 0x1000 à 0x1027 inclus. Voici ce que donne le listing d’assemblage complet, qui imprime toutes les adresses mémoire (colonne de gauche) et leur contenu : 00000000 00000000 00000004 00000008 00000008 0000000C 00000010 00000014 00000018 0000001C 00000020 00000024
82900000 12000002 88912000 84900000 C6010000 82804003 88812004 8480A001 80A0A009 08BFFFFB 10800000
00001000 00001000 00001004 00001008 0000100C 00001010 00001014 00001018 0000101C 00001020 00001024
0000000C FFFFFFFC 00000005 00000008 00000003 0000000A 00000004 FFFFFFFF 00000001 00000009
loop:
TAB
.org
0
clr set
%r1 TAB, %r4
clr ld addcc addcc inccc cmp bleu ba
%r2 [%r4], %r3 %r1, %r3, %r1 %r4, 4, %r4 %r2 %r2, 9 loop *
.org .word
0x1000 12,-4,5,8,3,10,4,-1,1,9
Par exemple à l’adresse 0x1000 on trouve 0x0000000C, c’est à dire 12 ; à l’adresse 0x1004 on trouve 0xFFFFFFFC, c’est à dire -4, etc.
3. Directives d’un programme assembleur
147
Allocation d’une chaîne de caractères Si on souhaite allouer une chaîne de caractères ou n’importe quelle séquence d’octets en mémoire, on utilisera la directive .byte. Un exemple fera mieux qu’un long discours : MESSAGE
.byte
’Tapez un nombre : ’, 10, 13, 0
.byte est suivi d’un nombre quelconque d’arguments séparés par des virgules. Ces arguments peuvent être, soit des nombres entre 0 et 255 (comme 10, 13 et 0), soit des chaînes de caractères qui seront remplacées par la suite de leurs codes ASCII. Dans l’exemple, les octets suivants seront placés en mémoire à partir de l’adresse TEXT : 00001000 00001000 00001004 00001008 0000100C 00001010
54977065 7A20756E 206E6F6D 62726520 3A0A0D00
TEXT
.org .byte
0x1000 ’Tapez un nombre :’,10,13,0
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 TAB
= .word
15 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
= set
15 2*N+4, %r1
,tout se passe comme si on avait écrit : set
34, %r1
CHAPITRE V. PROGRAMMATION DU PROCESSEUR 32 BITS CRAPS/SPARC
148
Réservation d’un zone non initialisée en mémoire La directive .malloc
réserve sans les initialiser octets en mémoire. Elle correspond en fait à un simple déplacement de l’adresse d’assemblage octets plus loin. Elle permet de réserver la place pour un tableau, ou n’importe quelle autre structure de données, qui sera initialisée dynamiquement par programme, au lieu d’être définie statiquement avec des directives .word et .byte.
4. Exemple de programme : crible d’Ératosthène Afin d’illustrer de façon plus parlante les éléments de programmation en assembleur qui viennent d’être présentés, on va écrire un programme qui recherche tous les nombres premiers inférieurs à une certaine limite N, par la méthode du crible d’Eratosthène. Algorithme Cette méthode consiste à construire un tableau TAB[i], 0 ≤ i ≤ n dans lequel TAB[i] = 0 signifie que i est premier, et TAB[i] = 1 signifie que i n’est pas premier. Pour cela on remplit d’abord tout le tableau de zéros. Ensuite, à partir de i = 2, on met à 1 TAB[2 ∗ i], TAB[3 ∗ i], … jusqu’à la fin du tableau, puisque 2 ∗ i, 3 ∗ i, … ne sont pas premiers. On cherche ensuite, après 2, le premier indice i du tableau pour lequel TAB[i] = 0. En l’occurrence c’est 3, qui est nécessairement premier car sinon il aurait été coché précédemment. On met alors à 1 TAB[2i], TAB[3i], … jusqu’à la fin du tableau, puisque 2i, 3i, … ne sont pas premiers. On recommence ensuite avec la prochaine valeur de i pour laquelle TAB [i ] = 0, qui est nécessairement première, etc. On peut arrêter ce processus dès que i atteint √ N, car on peut facilement montrer qu’alors tous les nombres non premiers ont déjà nécessairement été marqués à 1 dans le tableau. On peut traduire cette méthode par l’algorithme suivant : pour i de 0 à N-1 TAB[i] <- 0 ; fin pour pour i de 2 à racine(N) si TAB[i] = 0 alors j <- 2*i ; tant que (j < N) TAB[j] <- 1 ; j <- j + i ; fin tq fin si fin pour
Programmation D’après l’algorithme, les éléments du tableau TAB sont seulement les valeurs 0 ou 1. Un tableau de bits aurait fourni le codage le plus compact, mais aurait été difficile à manipuler. Un tableau de mots de 32 bits aurait été le plus facile d’accès, mais cela aurait été un gaspillage de mémoire un peu inutile. On va donc utiliser un tableau d’octets, facile à
4. Exemple de programme : crible d’Ératosthène
149
accéder, et qui occupera quatre fois moins d’espace en mémoire qu’un tableau de mots de 32 bits. On va d’abord mettre en place les directives de déclaration, notamment l’allocation des N octets du tableau : N RAM
= =
1000 0x1000000
TAB
.org RAM .malloc N
; adresse en RAM ; allocation de N octets
La valeur de N est définie de façon symbolique (ici avec 1000) ; il suffit de changer cette ligne pour changer toutes les références à ce maximum. De même, le symbole RAM définit le début de la zone de mémoire RAM sur la machine ; on va supposer ici que c’est l’adresse 0x1000000. La directive .org RAM déplace l’adresse d’assemblage au début de la zone de RAM, de sorte que le tableau TAB sera situé au début de cette zone. La directive .malloc N réserve un espace de N octets, sans l’initialiser, de sorte que l’adresse d’assemblage suivant cette directive est RAM+N. Ainsi le programme dispose de N octets à partir de TAB, dans lesquels il va stocker les éléments du tableau. Passons maintenant à la mise à zéro des éléments du tableau : N RAM
erato loop
TAB
= =
1000 0x1000000
.org set clr stb inccc set cmp bcs ...
0 TAB,%r1 %r2 %r0,[%r1+%r2] %r2 N,%r5 %r2,%r5 loop
; pour i de 0 à N-1 ; TAB[i] <- 0 ; élément suivant
.org RAM .malloc N
; fin pour
; adresse en RAM ; allocation de N octets
On a implémenté strictement l’algorithme, avec %r2 comme indice dans le tableau et %r1 comme pointeur sur le début du tableau. Pour écrire dans le tableau, on ne peut employer qu’une des deux instructions d’écriture en mémoire, st ou stb. Puisqu’on écrit seulement un octet, c’est stb qu’il faut employer. La forme générale de cette instruction étant : stb %ri, [%rj+cst/%rk], le registre à écrire sera nécessairement %r0 (qui vaut 0) et l’adresse de l’octet à écrire sera %r1+%r2 (adresse de début du tableau + indice dans le tableau). Avec les choix qui ont été faits avant sur le type du tableau, on est nécessairement conduits à l’instruction : stb
%r0,[%r1+%r2]
; TAB[i] <- 0
CHAPITRE V. PROGRAMMATION DU PROCESSEUR 32 BITS CRAPS/SPARC
150
Les trois instructions suivantes sont relatives à la recherche de l’indice suivant. On commence d’abord par incrémenter l’indice : inccc
%r2
; élément suivant
Il faut ensuite vérifier qu’il est inférieur à la limite N − 1 : set cmp
N,%r5 %r2,%r5
L’instruction synthétique cmp %r2,N n’est pas possible ici. Elle équivaut en effet à l’instruction subcc %r2, N, %r0 et la valeur de N est trop grande pour la constante possible à cette place dans le cas général, car elle est limitée à 13 bits, c’est à dire à l’intervalle [ − 4096, 4095]. On doit donc passer par un registre intermédiaire %r5, avec lequel on fait la comparaison. Les flags sont alors positionnés en fonction de la valeur de %r 2 − %r5, c’est à dire de i − N. Puisque l’algorithme mentionne une relation d’inégalité, c’est la valeur du flag N qui nous intéresse. On a : N = 1⇔ i−N < 0 ⇔ i < N ⇔ i ≤ N −1 On doit donc utiliser l’instruction bneg, qui effectue le branchement vers le début de la boucle si N = 1 : bneg
loop
; fin pour
En fait, puisqu’il s’agit d’une comparaison non signée, il est même préférable d’utiliser le branchement bcs, qui fait une comparaison de type inférieur strict, non signée. Elle permettra en effet d’étendre l’algorithme à des valeurs de N très grandes, jusqu’au maximum possible sur 32 bits. On écrira : bcs
loop
; fin pour
On vient de traduire littéralement l’algorithme qui était donné, à la façon d’un compilateur. On peut néanmoins remarquer que l’indice %r2 n’a pas besoin d’être utilisé de façon croissante dans cette partie du programme. On peut optimiser ce code en initialisant %r2 à N − 1 et en le décrémentant à chaque tour de boucle avant de tester s’il est positif ou nul. Cela donne : . loop
set set stb deccc bpos
TAB, %r1 N-1, %r2 %r0,[%r1+%r2] %r2 loop
; ; ; ;
pour i de N-1 à 0 TAB[i] <- 0 élément suivant 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 . loopi
nexti
setq 2,%r2 ... ... inccc %r2 set RN,%r5 cmp %r2,%r5 bleu loopi
151 ; pour i de 2 a racine(N)
; fin pour
On remarquera l’usage du setq pour initialiser une petite constante, plus économique que set qui utilise deux instructions. Dans le corps de la boucle, on doit ensuite lire l’élément TAB[i] du tableau, et choisir une autre valeur de i si cet élément de tableau vaut 1 : . loopi
setq ldub bne ...
2,%r2 ; pour i de 2 a racine(N) [%r1+%r2], %r6 nexti ; si TAB[i] = 0
L’élément de tableau est lu en mémoire, c’est donc une instruction ld ou ldub qui doit être utilisée, ici ldub puisque c’est une lecture d’octet. Le tableau commence à l’adresse %r1 et on lit l’élément d’indice %r2 : il se trouve donc à l’adresse %r1+%r2. Il est lu dans %r6 et cette lecture modifie les flags (voir détail de l’instruction ldub annexe B). L’instruction bne nexti provoque la recherche d’un nouvel indice i si Z = 0, c’est à dire si %r6 est différent de 0, donc égal à 1 ici. L’instruction j <- 2*i~; s’implémente très simplement sous forme d’une addition : addcc %r2, %r2, %r3
; j <- 2*i
On a ensuite une structure de contrôle tant que (j < N), qu’on va traduire : loopj
set N,%r5 cmp %r3,%r5 bpos nexti ; tant que (j < N) ... ... ba loopj ; fin tq
Elle est analogue à l’implémentation de la structure pour … fin pour, mais le test est fait en tête de boucle. %r5 est, ici encore, utilisé très temporairement pour stocker N, trop grand pour être manipulé a priori comme une ’petite’ constante sur 13 bits. Le corps de la boucle se traduit ensuite directement : check
setq stb addcc
1, %r4 %r4, [%r1+%r3] ; TAB[i] <- 1 %r3, %r2, %r3 ; j <- j + i
CHAPITRE V. PROGRAMMATION DU PROCESSEUR 32 BITS CRAPS/SPARC
152
Finalement, le programme complet est donné figure V.16. On notera l’instruction d’arrêt sous forme de boucle infinie (’*’ représente l’adresse de l’instruction courante). N RN RAM
erato loop
loopi
loopj
check
nexti
TAB
= = =
1000 32 0x1000000
.org set set stb deccc bpos
0 TAB, %r1 N-1, %r2 %r0, [%r1+%r2] %r2 loop
setq ldub bne addcc set cmp bpos setq stb addcc ba inccc set cmp bleu
2, %r2 [%r1+%r2], %r6 nexti %r2, %r2, %r3 N, %r5 %r3, %r5 nexti 1, %r4 %r4, [%r1+%r3] %r3, %r2, %r3 loopj %r2 RN, %r5 %r2, %r5 loopi
; pour i de 2 a rac(N)
ba
*
; boucle infinie (arrêt)
.org RAM .malloc N
; ; ; ;
pour i de N-1 à 0 TAB[i] <- 0 élément suivant fin pour
; si TAB[i] = 0 ; j <- 2*i
; tant que (j < N) ; TAB[i] <- 1 ; j <- j + i
; fin pour
; adresse en RAM ; allocation de N octets
Figure V.16. Programme complet du crible d’Ératosthène en CRAPS/SPARC. Il construit un tableau d’octets tel qu’un élément d’indice i valant 0 indique que i est premier.
5. Appels de sous-programmes terminaux Pour le moment, nous n’avons écrit que des programmes monolithiques, utilisables une seule fois. Pourtant, il serait utile qu’un programme tel que la somme des éléments d’un tableau puisse être utilisé plusieurs fois sur différents tableau durant l’exécution d’un programme, sans pour autant avoir à dupliquer son code. CRAPS fournit un tel mécanisme sous la forme des deux instructions call et ret : 1
call sauvegarde l’adresse courante d’exécution dans le registre %r31,
puis provoque un branchement à l’adresse spécifiée, où est situé le sous-programme qu’on souhaite appeler. 2
ret provoque le branchement à l’adresse %r31+4. Si le contenu de %r31 n’a pas été modifié depuis l’appel call, l’exécution reprend à l’adresse qui suit celle de l’instruction call.
5. Appels de sous-programmes terminaux
153
Tout se passe comme si call était une instruction de somme de tableaux. On montre sur le programme suivant comment notre programme de somme des éléments d’un tableau a été transformé en sous-programme, et invoqué sur deux tableaux différents : ;;;;;;;;;; programme principal set TAB1, %r4 ; call tabsum ; set TAB2, %r4 ; call tabsum ; ba * ;
;;;;;;;;; arg. 1er appel 1er appel arg. 2eme appel 2eme appel arrêt
;;;;;;;;;; tabsum clr clr loop: ld addcc addcc inccc cmp bleu ret
sous-programme %r1 %r2 [%r4], %r3 %r1, %r3, %r1 %r4, 4, %r4 %r2 %r2, 9 loop
;;;;;;;;; %r1 <- 0 ; pour i de 0 à 9 %r3 <- mem[%r4,...,%r4+3]; %r1 <- %r1 + %r3 ; %r5 <- %r5 + 4 ;
;;;;;;;;;; .org TAB1 .word TAB2 .word
zone de données ~;;;;;;;;; 0x1000 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 2, 4, 6, 8, 10, 12, 14, 16, 18, 20
; ; ; ; ;
; fin pour
Ce mécanisme call … ret est appelé appel de sous-programmes terminaux, c’est à dire de sous-programmes situés au dernier niveau d’appel. On l’aura sans doute déjà compris, il n’est pas possible avec ce mécanisme d’appeler un autre sous-programme à partir du corps d’un sous-programme appelé, puisque la première adresse de retour serait écrasée par la seconde dans %r31. Il nous manque maintenant un mécanisme qui permette de sauvegarder cette valeur ; il est présenté à la section suivante.
6. Utilisation de la pile Principe Une pile est une structure de données qui se manipule uniquement avec les deux opérations suivantes : 1
PUSH data : sauvegarde (empile) data dans la pile.
2
POP : enlève le sommet de la pile, et le renvoie.
On peut sauvegarder a priori autant de données qu’on le souhaite ; l’inconvénient est qu’on ne peut pas récupérer n’importe quand une donnée sauvegardée : on doit avant avoir récupéré toutes celles qui ont été empilées après elle. Une telle pile pourrait être implémentée de plusieurs façons, à commencer par une pile
CHAPITRE V. PROGRAMMATION DU PROCESSEUR 32 BITS CRAPS/SPARC
154
matérielle à base d’un grand tableau de registres. La méthode la plus simple (mais aussi la plus lente) est de stocker la pile sous forme de mots consécutifs dans la mémoire centrale, et d’utiliser un registre qui pointe sur la dernière valeur empilée, appelé pointeur de pile. Dans le cas de CRAPS, on utilisera le registre %r30, et les opérations push et pop existent déjà sous forme d’instructions synthétiques : push %ri
:
sub %r30, 4, %r30 st %ri, [%r30]
pop %ri
:
ld [%r30], %ri add %r30, 4, %r30
La figure V.17 montre ce qu’on observe en mémoire, après quatre opérations successives d’empilement et de dépilement. (a)
(b)
(c)
fond de pile
%r30 7
%r30 −4
%r30
initialement
−8 5
5
set 5, %r1 push %r1
set 7, %r1 push %r1
(d)
(e)
%r30 %r30
7 5
pop %r2
9 %r2
5
set 9, %r1 push %r1
Figure V.17. Opérations successives d’empilement et de dépilement. En disposant le ruban mémoire de haut en bas, on observe que les opérations d’empilement se traduisent visuellement par un empilement des valeurs les unes sur les autres : le 7 par dessus le 5, etc. Réciproquement un dépilement se traduit par la saisie de la valeur au sommet, c’est à dire celle qui est pointée par %r30. On notera cependant que la valeur dépilée n’est pas effacée de la mémoire, mais qu’elle est laissée telle quelle. Cela n’a aucune importance, car elle n’est plus accessible par des opération pop, et elle peut être écrasée par une opération push ultérieure. C’est ce qui se passe lors du push de la valeur 9 (situation (e)).
6. Utilisation de la pile
155
La pile de CRAPS, comme celle de la plupart des processeurs, a les deux propriétés suivantes : 1
lors d’un empilement, le sommet de la pile ’remonte’, c’est à dire que la valeur du pointeur de pile diminue.
2
le pointeur de pile pointe toujours sur la dernière valeur empilée.
Ces indications sont utiles notamment lorsqu’on débogue un programme et qu’on souhaite savoir précisément ce que la pile contient. Allocation de l’espace de pile La pile étant en mémoire centrale, doit être dans une zone de RAM. Par ailleurs, il faut prévoir qu’avec les empilements successifs elle va ’grignoter’ de la place en mémoire, en progressant vers les adresses basses (figure V.18). RAM adresse 0
programme, données
pile fond de pile
Figure V.18. La pile grandit en mémoire en progressant vers les adresses basses. Elle peut déborder sur des zones de programme ou de données. Il y a donc un danger potentiel d’écrasement d’une zone de programmes ou de données par la pile, si celle-ci grandit trop. Lorsqu’on utilise une pile dans un programme, il faut donc initialiser le pointeur de pile en prenant en compte la position des autres données en mémoire, et la taille maximale que la pile peut prendre au cours de l’exécution des programmes. Sauvegarde de l’adresse de retour de sous-programme Un premier usage que l’on peut faire d’une pile est de résoudre notre problème de sauvegarde de l’adresse de retour lors d’un appel de sous-programme de type call … ret. La solution est très simple : 1
juste avant l’appel call, il faut empiler l’adresse de retour %r31.
CHAPITRE V. PROGRAMMATION DU PROCESSEUR 32 BITS CRAPS/SPARC
156 2
juste après le retour du call, on dépile dans %r31 cette adresse.
Ainsi, même si %r31 est modifié dans l’appel du sous-programme, on est sûr de retrouver la valeur de %r31 que l’on avait avant le call, c’est à dire l’adresse de retour (sous réserve que la pile soit au même niveau au retour qu’au départ). On peut illustrer cette méthode par le petit programme suivant : RAM STACK
= =
0x1000000 RAM+100
; 100 octets réservés
;;;; ;;;;
calcul de 2*5+4 par appels de ssprog1 et ssprog2 set STACK, %r30 ; main: set 5, %r1 ; set 4, %r2 adr1: call ssprog1 ; ba * ;
successifs ;;;; ;;;;; initialisation %r30 init. arguments d’appel appel ss programme arrêt (boucle infinie)
;;;;;; calcule 2*%r1+%r2, résultat dans %r3 ;;;;;; ssprog1: push %r31 ; empile adresse retour adr2: call ssprog2 ; calcul de 2*%r1 pop %r31 ; restaure adresse retour addcc %r1, %r2, %r3 ; ajoute %r2 ret ; retour à main ;;;;;; calcule 2*%r1, résultat dans %r1 ssprog2: addcc %r1, %r1, %r1 ret
;;;;;;
Avant toute chose, il faut initialiser le pointeur de pile. Ici il est placé 100 octets après le début de la zone de RAM, laissant la possibilité de 100/4 empilements. main appelle ssprog1, qui va faire le calcul 2x + y. Ce n’est pas la peine ici de sauvegarder %r31, car on ne vient de nulle part et donc on n’a pas d’adresse de retour à préserver. %r31 prend pour valeur adr1, qu’il va falloir préserver si on veut revenir dans main à la fin. ssprog1 appelle ssprog2 pour faire le calcul du double. Avant de partir, il sauvegarde son adresse adr1 de retour à main, qui sera écrasée sinon. En effet à l’exécution de call ssprog2, la valeur de %r31 qui était adr1 est écrasée, et remplacée par adr2. Au ret de ssprog2 le contrôle reprend à l’adresse adr2+4, après l’appel call ssprog2. L’exécution de pop %r31 redonne à %r31 la valeur adr1 qu’il avait avant l’appel, et le retour à main redevient possible. Appels de fonctions récursives Cette méthode ouvre d’un seul coup la possibilité de réaliser des programmes récursifs et mutuellement récursifs, puisque les sauvegardes/restaurations des adresses de retour peuvent être faites en nombre quelconque dans la pile. On va l’illustrer par un calcul de plus grand commun diviseur (PGCD), qui s’obtient par l’algorithme récursif suivant : fonction int pgcd(int x, int y)
6. Utilisation de la pile
157
si (x = y) alors val <- x ; sinon si (x > y) alors val <- pgcd(x-y, y) ; sinon val <- pgcd(x, y-x) ; return val ; fin fonction
Par exemple, PGCD(78,143) = PGCD(78,65) = PGCD(13,65) = PGCD(13,52) = PGCD(13,39) = PGCD(13,26) = PGCD(13,13) = 13. Comme par ailleurs 78 = 2×3×13 et 143 = 11×13, on vérifie bien que 13 est leur plus grand commun diviseur. Pour le programmer récursivement en CRAPS, il nous suffit à l’intérieur du sous-programme pgcd de rappeler par call le sous-programme pgcd, en prenant soin de sauvegarder dans la pile tout l’environnement local nécessaire au calcul, c’est à dire : 1
les valeurs de registres que l’on souhaite conserver au travers de cet appel.
2
l’adresse de retour %r31.
La figure V.19 donne le listing complet du programme. Le programme est tout à fait simple et lisible, traduction littérale de l’algorithme récursif. Seule l’adresse de retour est sauvegardée avant l’appel récursif, car il n’y a pas de registre particulier à sauvegarder à ce moment. Si le calcul nécessite n appels récursifs, la pile va grossir progressivement jusqu’à occuper n mots en mémoire, adresses de retour sauvegardées à chaque appel. Ensuite, n exécutions de l’instruction ret vont remettre la pile à son niveau initial. L’utilisation de la récursivité dans le cas de ce programme est maladroite, car elle est assez inefficace à l’exécution, vu le grand nombre d’accès mémoire effectués. L’algorithme peut très simplement être traduit de façon itérative : fonction int pgcd(int x, int y) tant que (x <> y) faire si (x > y) alors x <- x - y ; sinon y <- y -x ; fin si fin tq ; return x ; fin fonction
Cet algorithme se traduit directement par le programme de la figure V.20. La leçon à tirer de cet exemple est qu’il ne faut pas abuser de la récursivité dans les
CHAPITRE V. PROGRAMMATION DU PROCESSEUR 32 BITS CRAPS/SPARC
158
RAM STACK
main
pgcd:
skip:
sup:
retour
= =
0x1000000 0x1000100
.org set set set call ba
0 STACK, %r6 78, %r1 143, %r2 pgcd *
; ; ; ; ;
init pointeur pile valeur de x valeur de y appel pgcd arrêt
;; calcule pgcd(x,y) ;; in : x = %r1, y = %r2 ;; out : résultat dans %r3 cmp %r2, %r1 bne skip ; x=y ? ; x = y : return x mov %r1, %r3 ; val <- x ba retour ; retour 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 ; 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 ret
Figure V.19. Programme récursif de calcul du PGCD.Le programme est très lisible,au prix d’une exécution plus lente. fonctions de bas niveau, si un algorithme itératif est disponible. Elle est en effet gourmande en temps et en espace occupé en mémoire. La récursivité est par contre indispensable dans les fonctions de haut niveau, pour des raisons de clarté et de modularité dont la discussion sort du cadre de cet ouvrage. Enfin on peut noter que lorsqu’on paramètre la compilation d’un programme pour qu’il produise un code plus efficace, il cherche entre autres techniques à dérécursiver les récursions terminales comme celles de notre fonction PGCD, afin de les rendre itératives.
7. Techniques de programmation diverses Multiplication entière par une puissance de 2 Sur notre machine CRAPS, l’opération de multiplication entière occupera un nombre de cycles relativement important. Dans le cas assez fréquent où l’on souhaite multiplier par une puissance de 2, on peut utiliser plus efficacement une opération de décalage à gauche sll. À chaque décalage d’un cran à gauche (avec introduction d’un 0 à droite), la valeur décalée est multipliée par 2, qu’elle soit codée en binaire pur (non signée) ou en complément à deux (signée).
7. Techniques de programmation diverses
RAM STACK
main
pgcd:
skip:
sup:
= = .org set set call ba
159
0x1000000 0x1000100 0 78, %r1 143, %r2 pgcd *
; ; ; ;
valeur de x valeur de y appel pgcd arrêt
;; calcule pgcd(x,y) ;; in : x = %r1, y = %r2 ;; out : résultat dans %r3 cmp %r2, %r1 ; tant que x <> y bne skip ; x=y ? ; x = y : return x mov %r1, %r3 ; val <- x ret ; retour bneg sup ; x>y ? ; x < y subcc %r2, %r1, %r2 ; y <- y - x ba pgcd ; x > y subcc %r1, %r2, %r1 ; x <- x - y ba pgcd
Figure V.20. Programme itératif de calcul du PGCD. Il est plus efficace que la version récursive car il n’y a plus d’accès en mémoire. Division entière par une puissance de 2 De la même façon, on peut vouloir calculer le quotient de la division entière d’un nombre N entier signé par un diviseur entier positif égal à une puissance de 2, de façon plus rapide que l’instruction de division udivcc. On utilise pour cela l’instruction de décalage à droite srl : chaque décalage à droite est équivalent à une division par 2. Par exemple, si %r1 est positif, srl %r1, 4, %r2 le divise par 16 et place le résultat dans %r2. Cette technique ne fonctionne pas si %r1 est négatif, car srl va introduire des ’0’ par la gauche, et de négatif il va devenir positif, ce qui est absurde. L’instruction sra (shift right arithmetic) est là pour cela : au lieu de faire rentrer systématiquement un ’0’ par la gauche, elle fait entrer le bit de poids fort du registre en cours de décalage, en préservant ainsi son signe. La figure V.21 illustre cette opération. Ainsi donc sra %r1, 4, %r2 divise %r1 par 16 dans tous les cas, même s’il est négatif.
8. Langages à structure de blocs et ’stack-frames’ On s’intéresse dans cette section aux problèmes que posent aux compilateurs les langages à structure de blocs tels que C, Java, etc. lors de la phase de génération de code. Un compilateur est en effet essentiellement un traducteur du langage source vers un langage cible qui est généralement le langage assembleur du processeur visé. On cherche dans cette section quelles méthodes ces compilateurs mettent en oeuvre de façon systématique pour
CHAPITRE V. PROGRAMMATION DU PROCESSEUR 32 BITS CRAPS/SPARC
160
sra : décalage arithmétique
0 srl : décalage logique
Figure V.21. Décalage à droite logique et arithmétique. Dans le décalage arithmétique, le signe de l’opérande est préservé. implémenter la notion de bloc, ainsi que lors des appels de fonctions. Implémentation des blocs sous forme de stack-frames Les programmes écrits dans des langages tels que C et Java sont organisés en blocs, encadrés par des symboles tels que {... } ou begin … end. À l’intérieur de chaque bloc on peut déclarer de nouvelles variables, dont la portée est limitée au bloc. Généralement un bloc est associé à une structure de contrôle (boucle for ou while) ou au corps d’une fonction. On peut néanmoins créer un bloc à tout moment dans un programme, et y déclarer les variables locales de son choix : leur portée sera limitée à ce bloc. Ainsi en langage C, on peut écrire le petit programme (inutile) suivant : int main(int argn,char** args) { int x = 1 ; int y = 10 ; printf("x=%d\n", x) ; { int x = 2 ; int z = x + y ; printf("x=%d\n", x) ; } printf("x=%d\n", x) ; }
À l’exécution, il va produire l’affichage : x=1 x=2 x=1
Il y a deux blocs dans ce programme : celui associé au corps de la procédure main, et un bloc interne situé au beau milieu, et qui n’est là que pour illustrer notre propos. La portée de la variable x située dans le bloc interne est limitée à ce bloc, et elle a temporairement pris la place de la variable x du bloc plus externe. La variable z n’est visible que dans le bloc interne, et la variable y est visible dans les deux blocs. La pile est utilisée pour mettre en place cette visibilité temporaire des variables. Lors de l’entrée dans un bloc, le compilateur crée un stack-frame dans la pile, c’est à dire une zone
8. Langages à structure de blocs et ’stack-frames’
161
de mémoire dans laquelle seront placées les valeurs des variables déclarées dans le bloc. Les stack-frames associés aux deux blocs de notre programme pourraient être implantés comme sur la figure V.22. pile
z = 12 x=2 pied frame 2
stack frame 2
prec. y = 10 x=1
pied frame 1
stack frame 1
prec.
Figure V.22. Stack-frames associés aux deux blocs de code du programme.Chaque variable locale a sa place dans le stack-frame associé au bloc. On remarquera le pointeur vers le stack-frame précédent. Un registre est généralement affecté au pointage au pied du stack-frame courant, et les accès aux différents éléments du stack-frame se font très simplement par adressage indirect avec déplacement. Par exemple, si on utilise %r29 comme pointeur de stack-frame, on aura le mécanisme d’adressage de la figure V.23. pile %r29−8 %r29−4 %r29
z = 12 x=2
stack frame 2
%r29 prec. y = 10 x=1
stack frame 1
prec.
Figure V.23. Utilisation du registre %r29 comme pointeur à la base du stack-frame courant. Les accès aux variables locales se font par adressage indirect avec déplacement. Le code assembleur traduisant ce programme pourrait être :
CHAPITRE V. PROGRAMMATION DU PROCESSEUR 32 BITS CRAPS/SPARC
162 STACK
=
0x1000100
main:
;; initialisation pointeur de pile set STACK, %sp ;; création du stack-frame push %r29 ; mov %sp, %r29 ; add %sp, 2*4, %sp ; setq st setq st
1, %r1 %r1, [%r29-4] 10, %r1 %r1, [%r29-8]
1 empile val. prec.de %r29 %r29 reste fixe au pied du stack frame réserve 2 mots dans la pile
; x <- 1 ; y <- 10
;; création du stack-frame push %r29 ; mov %sp, %r29 ; add %sp, 2*4, %sp ;
2 empile val. prec.de %r29 %r29 reste fixe au pied du stack frame réserve 2 mots dans la pile
setq 2, %r1 st %r1, [%r29-4] ; x <- 2 ;; recherche de la valeur de y par double indirection ld [%r29], %r2 ; %r2 <- "@stack-frame 1" ld [%r2-8], %r1 ; %r1 <- y ld [%r29-4], %r3 ; %r3 <- x addcc %r1, %r3, %r4 ; %r4 <- x + y st %r4, [%r29-8] ; stockage de z ...
La création d’un stack frame consiste essentiellement à réserver la place nécessaire aux variables locales ; il s’agit d’une simple soustraction sur la valeur de %sp. À chaque création d’un stack-frame, la valeur courante de %r29 est empilée, et %r29 est positionné au pied du stack-frame.L’accès à une variable locale à ce bloc se fait directement par référence à %r29 ; par exemple l’accès à x dans le stack-frame 2 se fait par [%r29-4]. Plus délicat est l’accès à la variable y dans l’expression z = x + y ; en effet y n’appartient pas au bloc courant, mais au bloc de rang inférieur. La référence à la variable y est correcte, mais le compilateur doit dans ce cas accéder à y par une double indirection, en allant chercher d’abord l’adresse du stack-frame de rang inférieur, puis en faisant à partir de là un accès indirect avec déplacement. Selon les cas, plusieurs de ces indirections peuvent être nécessaires. Appel de fonction récursive Appeler une fonction, c’est créer dynamiquement un stack-frame qui contiendra toutes les données manipulées par elle, à savoir : 1
les paramètres d’appel,
2
la valeur de retour,
3
les variables locales au bloc principal de la fonction,
4
l’adresse de retour à l’instruction d’appel.
Ainsi, chaque nouvel appel aura son propre contexte de travail sous forme d’un stack-frame, et les appels successifs ne mélangeront pas leurs variables locales et les paramètres d’appel. À la sortie d’un appel de fonction, la valeur renvoyée par la fonction sera récupérée avant que l’espace du stack-frame ne soit désalloué de la pile (figure V.24). On récupérera
8. Langages à structure de blocs et ’stack-frames’
163
également l’adresse de retour, valeur de %r31 dans le cas de CRAPS. pile
var. locale #m ... var. locale #1 val. renvoyee
stack frame
param #n ... param #1 @ retour @ prec.
Figure V.24. Stack-frame créé lors d’un appel de fonction. On y trouve les paramètres d’appel, l’emplacement de la valeur à renvoyer, les variables locales et l’adresse de retour.
9. Programmation des entrées/sorties Jusqu’ici nous n’avons écrit que des programmes ’autistes’ : ils n’ont effectué aucun échange avec l’extérieur. Les données sur lesquelles ils travaillaient leurs étaient fournies directement sous forme de zones de mémoire préremplies avec les valeurs du problème et ils produisaient des résultats sous forme de données écrites en RAM. On va considérer que l’interface d’un ordinateur avec l’extérieur est fait au travers d’un ensemble de lignes digitales, chacune d’elle étant à un moment donné une entrée ou une sortie. Si c’est une entrée, le processeur doit être capable de lire son état 0 ou 1 ; si c’est une sortie il doit pouvoir forcer son état à 0 ou à 1. Des amplificateurs ou des atténuateurs en entrée ou en sortie peuvent permettre la lecture ou la commande de courants très forts ou très faibles ; même la commande ou l’acquisition de signaux analogiques rentre dans ce cadre, puisqu’on peut utiliser des convertisseurs numérique/analogique qui vont faire la conversion d’un monde vers l’autre. Certaines de ces lignes sont des entrées ou des sorties sans pouvoir changer de nature : ce sera par exemple le cas des sorties des circuits appelés timer/PWM chargés de produire des signaux paramétrables de forme rectangulaire. Certaines lignes d’usage plus souple seront des lignes d’entrée/sortie générales, qui pourront être configurées individuellement et par programme en entrée ou en sortie. Instructions spéciales d’entrées/sorties Sur certains processeurs existent des instructions spéciales d’entrées/sorties. Le x86 par exemple possède une instruction IN AL,PORT qui lit un octet de PORT vers AL et une instruction OUT PORT,AL qui écrit un octet de AL vers PORT. PORT désigne un numéro de port, élément parmi un espace d’adressage spécial, propre aux entrées/sorties. Les écritures
164
CHAPITRE V. PROGRAMMATION DU PROCESSEUR 32 BITS CRAPS/SPARC
sur des numéros de port particuliers mettent en activité les périphériques associés tels que contrôleurs USB, contrôleurs de bus PCI Express, etc. Entrées/sorties mappées en mémoire Sur d’autres processeurs tels que CRAPS/SPARC, il n’existe pas de telles instructions spécialisées. Comment alors peuvent-ils faire pour interagir avec l’extérieur ? Ils utilisent en fait un mécanisme appelé entrées/sorties mappées en mémoire : c’est en lisant et en écrivant à certains emplacements mémoire particuliers, que le processeur va dialoguer avec les périphériques. L’avantage de cette méthode, c’est qu’il n’y a pas d’instructions spéciales à utiliser et que, en CRAPS par exemple, de simples ld et st suffiront. Son inconvénient, c’est de gaspiller certains emplacements de l’espace d’adressage mémoire, qui ne peuvent plus être utilisés pour de la véritable RAM ou ROM. La machine CRAPS, telle qu’elle sera décrite en détail au chapitre suivant, et telle qu’elle est utilisable au travers du simulateur CrapsEmu et de son implantation dans un FPGA, est équipée d’un timer/PWM et de 32 lignes d’entrée/sortie générales. Ces deux périphériques sont donc mappés en mémoire et on va décrire maintenant la façon de les programmer.
9.1. Programmation des lignes d’entrées/sorties générales CRAPS est équipé de 32 lignes d’entrées/sorties générales, qu’on nommera IOi, 0 ≤ i ≤ 31. Par ’générales’, il faut comprendre que chaque ligne peut être configurée individuellement en entrée ou en sortie, et ensuite lue ou écrite à volonté. On pourra par exemple les utiliser de la façon décrite figure V.25.
CRAPS
IO[3]
1K
IO[2]
1K
IO[1]
220
IO[0]
220
Vcc
Vcc
Figure V.25. Exemple d’utilisation des lignes d’entrées/sorties générales de CRAPS. Les lignes IO[0] et IO[1] doivent être configurées en sorties, et elles servent ici à commander l’allumage de diodes électroluminescentes : si une de ces lignes est à ’0’, la diode correspondante est éteinte ; si elle est à ’1’, un courant d’environ 20mA traverse la diode et l’allume. Les lignes IO[2] et IO[3] doivent être configurées en entrées, et servent à l’acquisition de l’état de deux interrupteurs. Lorsqu’un des interrupteurs est ouvert, la ligne correspondante lit ’1’(car la résistance de 1K induit un courant entrant de 5mA si Vcc=5v) ; s’il est fermé elle lit ’0’.
9. Programmation des entrées/sorties
165
Adresses mémoire utilisées Dans la configuration que nous allons décrire, les lignes d’entrées/sorties de CRAPS sont mappées aux adresses de 0x600000 à 0x60000C selon le tableau de la figure V.26. adresse
W
opération
0x600000
0
lire l’état des entrées
0x600000
1
écrire sur les sorties
0x600004
0
lire configuration lignes
0x600004
1
configurer les lignes
0x600008
0
lire interruptions mémorisées
0x600008
1
raz des interruptions mémorisées
0x60000C
0
lire sens du front d’interruption (0 = front montant)
0x60000C
1
écrire sens du front d’interruption
Figure V.26. Tableau de configuration et d’utilisation des entrées/sorties de CRAPS. W=1 indique une écriture.La configuration des lignes se fait par écriture en 0x600004 ; la lecture et l’écriture des lignes se fait par lecture et écriture à l’adresse 0x600000. La lecture et l’écriture en 0x600008 et 0x60000C concernent la gestion des interruptions (section 10). Les valeurs 0x600000, 0x600004 etc. ne dépendent pas du processeur CRAPS, mais de la façon dont il est interfacé avec les circuits mémoire et d’entrées/sorties. On détaillera complètement cet aspect de ce qu’on appelle le décodage des adresses au chapitre suivant. Par contre, on reportera à la section 10 l’utilisation des lignes d’entrées/sorties comme lignes d’interruption, au travers de la lecture et de l’écriture aux adresses 0x600008 et 0x60000C. Configuration des lignes D’après le tableau V.26, la configuration se fait en écrivant à l’adresse 0x600002 un mot de 32 bits val[31..0]. La règle de configuration est simple : si val[i] vaut 0, la ligne IO[i] sera configurée en entrée ; s’il vaut 1 la ligne sera configurée en sortie. Ainsi pour configurer les lignes IO[0] et IO[1] en sorties et toutes les autres en entrées, on écrira le mot mémoire 000..00001132 en 0x600004, par exemple avec le code suivant : BASEIO
=
0x600000
; adresse de base des IO
set set st
BASEIO, %r1 0b11, %r2 ; IO[0..1] sorties, les autres en entrées %r2, [%r1+4] ; écriture configuration
Lecture/écriture sur les lignes Pour lire l’état des entrées, il suffit de lire en 0x600000, et de tester dans le mot lu les bits des rangs correspondants aux entrées. Pour positionner l’état des sorties, il suffit d’écrire en 0x600000 un mot dont les bits correspondent aux valeurs que l’on souhaite placer sur les lignes.
CHAPITRE V. PROGRAMMATION DU PROCESSEUR 32 BITS CRAPS/SPARC
166
Par exemple sur le schéma de la figure V.25, pour lire l’état de l’interrupteur situé sur la ligne IO[2] et allumer la diode située sur IO[0] si cet interrupteur est ouvert (IO[2]=1) on écrira le code suivant : BASEIO
=
0x600000
; adresse de base des IO
set set st
BASEIO, %r1 0b0011, %r2 %r2, [%r1+4]
; IO[0..1] sorties, IO[2..3] entrées ; écriture configuration
ld andcc srl st
[%r1], %r3 %r3, 0b100, %r3 %r3, 2, %r3 %r3, [%r1]
; ; ; ;
lecture des IO en entrées isolation du bit 2 déplacement de ce bit au rang 0 écriture sur les IO en sorties
Exemple : programmation d’un jeu de ’pile ou face’ Le programme de la figure V.27 exploite le câblage des entrées/sorties de la figure V.25, et fait allumer en alternance l’une des deux LEDs situées sur IO[0] et IO[1]. Lorsque le joueur appuie sur l’interrupteur situé sur IO[2], l’état des LEDs se fige, faisant apparaître une des deux combinaisons.
BASEIO
loop
=
0x600000
; adresse de base des IO
set set st
BASEIO, %r1 0b011, %r2 %r2, [%r1+4]
; IO[0..1] sorties, IO[2] entrée ; écriture configuration
setq 0b10, %r4 ; état des LEDs st %r4, [%r1] ; écriture état des LEDs ld [%r1], %r3 ; lecture des IO en entrées andcc %r3, 0b100, %r3 ; isolation du bit 2 bne loop ; attend qu’il vaille 1 ;; interrupteur ouvert : on inverse l’état des LEDs xorcc %r4, 0b11, %r4 ; inversion par xor st %r4, [%r1] ; écriture état des LEDs ba loop ; reboucle éternellement
Figure V.27. Programme d’un jeu de ’pile ou face’. Les LEDs #0 et #1 s’allument en alternance, et un appui sur l’interrupteur #2 fige la configuration. Les commentaires du programme parlent d’eux-mêmes. On notera l’inversion par le xor des deux bits de poids faibles de l’état des LEDs dans %r4.
9.2. Programmation du timer/PWM Principe général CRAPS possède un timer/PWM qui lui permet de générer sur une ligne de sortie dédiée, des signaux rectangulaires dont la période et le rapport cyclique sont programmables. La forme du signal est donnée figure V.28.
9. Programmation des entrées/sorties
167
N
P
t DT = periode horloge timer
Figure V.28. Forme et paramètres du signal de sortie du timer/PWM de CRAPS. Les usages d’un timer/PWM sont nombreux. Dans un système multi-tâches, la sortie d’un timer sert à déclencher une interruption qui effectue la commutation entre les différentes tâches. Il fonctionne à une fréquence d’environ 50 hertz, ce qui donne à l’utilisateur l’illusion que ses différents programmes fonctionnent en parallèle. On s’en sert également pour contrôler avec précision l’énergie communiquée à un appareil de type lampe ou moteur à courant continu (après amplification), celle-ci étant proportionnelle au rapport cyclique P − N . P Registres de commande Il s’agit d’un timer 16 bits, c’est à dire que les paramètres P et N sont codés sur 16 bits, et représentent un nombre de cycles de l’horloge du timer. L’horloge du timer est obtenue à partir de l’horloge générale, dont la fréquence peut éventuellement être prédivisée par un facteur prediv codé sur 4 bits, d’où 16 valeurs de prédivision. Si f est la fréquence de l’horloge générale, la fréquence du timer sera programmable parmi les 16 valeurs : f , f /2, …, f /32768. C’est elle qui définit le pas minimal de fonctionnement du timer. Le timer possède en interne deux registres qui stockent ces valeurs nécessaires à son fonctionnement. Ils sont appelés commande #1 et commande #2 (figure V.29). 31 registre commande #1
16 15 N
31 registre commande #2
0 P
16
1 0
prediv
start
Figure V.29. Structure des registres de commande du timer de CRAPS. Du point de vue du programmeur, le timer/PWM de CRAPS est mappé aux adresses 0x400000 à 0x400008 selon le tableau de la figure V.30. La configuration du timer se fait par écriture dans le registre de commande #1 pour les valeurs de P et de N, et dans le registre de commande #2 pour la valeur de prediv. Le lancement du timer se fait par écriture d’un ’1’ dans le bit 0 du registre de commande #2. À tout moment du fonctionnement du timer, il peut être arrêté par écriture d’un ’0’ dans
CHAPITRE V. PROGRAMMATION DU PROCESSEUR 32 BITS CRAPS/SPARC
168 adresse
W
opération
0x400000
0
lire le registre de commande #1 (P + N)
0x400000
1
écrire le registre de commande #1 (P + N)
0x400004
0
lire le registre de commande #2 (prediv + start)
0x400004
1
écrire le registre de commande #2 (prediv + start)
0x400008
0
lire bit d’interruption timer
0x400008
1
raz bit d’interruption timer
Figure V.30. Tableau de configuration et d’utilisation du timer/PWM de CRAPS. W=1 indique une écriture.La configuration des valeurs de P et N se fait par écriture en 0x400000 ; le lancement du timer et la valeur de prédivision de l’horloge se font par écriture en 0x400004.La lecture et l’écriture en 0x400008 concerne l’utilisation du timer comme source d’interruption (voir section 10). ce bit. Si par exemple on souhaite générer un signal carré de période 100 cycles d’horloge, on écrira le code suivant : BASETIMER
=
0x400000
; adresse de base du timer
set set st
BASETIMER, %r1 0x00320064, %r2 %r2, [%r1]
; %r1 = base timer ; N=50 & P=100 ; écriture commande #1
set 0x00000001, %r2 ; st %r2, [%r1+4] ; ;; ----> lancement du timer ;; ... st %r0, [%r1+4] ; ld [%r1], %r3 ;
prediv=0 & start=1 écriture commande #2
arrêt du timer lecture du compteur
%r1 pointe au début de la zone mémoire [0x400000,400008[ et toutes les références mémoire sont faites par rapport à lui : [%r1] pour commande #1, [%r1+4] pour commande #2. La valeur écrite dans commande #1 est 0x00320064, soit N=50 et P=100. On va voir que le facteur de prédivision est ici de 1, donc la période sera bien de 100 cycles d’horloge, et le timer sera à 0 durant les 50 premiers cycles, puis à 1 les 50 cycles suivants. La valeur écrite dans commande #2 est 0x00000001, soit prediv=0 et start=1 : le facteur de prédivision est 1, et la mise à 1 du bit start provoque le démarrage du timer. Plus loin dans le programme, le timer est arrêté par mise à 0 de ce bit, et la valeur courante du compteur est lue et stockée dans %r4. Exemple d’utilisation : commande d’un servo-moteur Un servo-moteur est composé d’un moteur à courant continu avec un ensemble d’engrenages démultiplicateurs qui lui donnent un couple très élevé. Il est muni d’une électronique de commande qui permet de contrôler avec précision, non pas la vitesse de rotation, mais la position angulaire de son axe, qui n’a un débattement que de 180° (figure V.31).
9. Programmation des entrées/sorties
169
180 degrés
0 degrés
Figure V.31. Servo-moteur. La rotation de l’axe est très démultipliée et a un couple élevé. La commande ajuste la position angulaire de l’axe, qui peut varier de 0° à 180°. La commande se fait sur un seul fil, à la norme TTL (0 ou 5v), et consiste en une impulsion périodique dont la largeur code la position angulaire de l’axe (figure V.32).
1 ms < d < 2 ms
1 ms = 0 degrés 2 ms = 180 degrés
période 20ms
Figure V.32. Forme du signal de commande d’un servo-moteur.Il est périodique de période 20ms ; l’impulsion a une largeur comprise entre 1ms et 2ms, qui code la position angulaire de l’axe entre 0° et 180°. La période du signal reste constante à 20ms (valeur non critique) ; ce qui varie, c’est la largeur de l’impulsion, entre 1ms et 2ms. Dans cet intervalle il y a proportionnalité entre la largeur de l’impulsion et la position angulaire, de 0° pour une largeur de 1ms à 180° pour une largeur de 2ms. Détermination de la fréquence de l’horloge du timer On souhaite avoir environ 1000 pas de précision sur la commande, c’est à dire qu’on souhaite faire varier la largeur de l’impulsion de 1 ms à 2 ms par pas de 1/1000 ms. C’est −6 donc cette valeur de 1/1000 ms qui doit être la période de base du timer : DT ∼ − 10 s. On suppose que l’horloge générale est de 50 MHz, soit une période de DH = 2·10− 8s. Le rapport de prédivision de DH doit donc être DT / DH = 50. La puissance de 2 la plus proche est 64, soit une valeur de prediv de 6. Finalement la véritable fréquence de l’horloge sera : DT = 64DH = 1, 28·10− 6s P restera constant égal à 20 ms / 1, 28·10− 6 s = 15625 pour assurer la période générale de 20 ms du signal transmis au servo-moteur, et N variera entre 12501et 14063 pour faire varier la largeur de l’impulsion entre 1 ms et 2 ms, par 1562 pas. La figure V.33 donne un sous-programme de positionnement du servo-moteur, auquel on donne comme paramètre l’angle de positionnement dans %r1 en dixièmes de degrés (de 0 à 1800).
CHAPITRE V. PROGRAMMATION DU PROCESSEUR 32 BITS CRAPS/SPARC
170
;; commande de servo-moteur BASETIMER = 0x400000
main
cmdservo
; adresse de base du timer
.org 0 ;; main : exemple d’utilisation set 900, %r1 ; %r1 <- 90 degrés call cmdservo ba * ;; sous-programme de commande du servo ;; in : %r1=angle en dixièmes de degrés umulcc %r1, 1561, %r3 ; %r3 = 1561 * angle udivcc %r3, 1800, %r3 ; %r3 = 1561*angle/1800 set 12501, %r1 add %r3, %r1, %r3 ; %r3 = 12501+1561*angle/1800 sll %r3, 16, %r3 ; décalage de 16 positions à gauche set 15625, %r1 ; valeur de P or %r1, %r3, %r3 ; fusion de N et P dans %r3 set BASETIMER, %r2 ; %r2 = base timer st %r3, [%r2] ; écriture commande #1 set 0b1101, %r3 ; prediv=6, start=1 st %r3, [%r2+4] ; écriture commande #2 ret
Figure V.33. Sous-programme de commande de servo-moteur. Le sous-programme cmdservo effectue d’abord une mise à l’échelle de la valeur de %r1 (de 0 à 1800) : une transformation affine remplace %r1 par une valeur entre 12501 et 14063, qui est la valeur qui doit être placée dans le registre N du timer/PWM. On notera que les constantes 1561et 1800 ont été utilisées directement comme constantes immédiates dans les instructions umulcc %r1, 1561, %r3 et udivcc %r3, 1800, %r3 car elles sont codables sur 13 bits (voir format des instructions en section 2.4). Cela n’est pas le cas par contre pour la constante 15625, qui ne tient pas sur 13 bits et qui doit d’abord être mise dans un registre par une instruction synthétique set. On comprend tout l’intérêt de commander un servo-moteur par un tel circuit timer/PWM : le signal périodique est généré de façon autonome et le processeur n’a besoin d’intervenir que lors d’un changement de position ou d’une demande d’arrêt ou de lancement, qui se traduisent par une simple écriture en mémoire.
10. Programmation des exceptions 10.1. Définitions Une exception est un événement exceptionnel qui intervient au cours du fonctionnement du processeur.La prise en compte d’une exception se traduit par l’exécution d’un sous-programme qui lui est associé, appelé gestionnaire d’exception. On pourrait dire qu’une exception est un appel de sous-programme inattendu. On classe les exceptions en deux groupes, selon que leur cause est interne ou externe.
10. Programmation des exceptions
171
Les traps Les traps sont des exceptions à cause interne, provoqués par l’exécution d’une instruction par le processeur. Mieux qu’un long discours, la liste des traps du processeur CRAPS fera comprendre leur nature : •
instruction illégale. Le processeur tente d’exécuter une instruction dont le code machine ne correspond à aucune instruction connue.
•
bus error. Le processeur tente d’effectuer un accès mémoire à une adresse où aucune mémoire n’est implantée.
•
division par 0. Le processeur exécute une instruction de division et le diviseur est nul.
•
erreur d’alignement. Le processeur tente d’effectuer un accès mémoire à une adresse qui n’est pas un multiple de 4 si par exemple il s’agit d’un processeur 32 bits. La plupart des processeurs exigent en effet que la lecture ou l’écriture des mots respectent de telles contraintes d’alignement.
•
instruction privilégiée. Le processeur tente d’exécuter une instruction privilégiée (réservée aux programmes en mode superviseur) mais il n’est pas en mode superviseur.
On dit de telles exceptions qu’elles sont synchrones, car elles surviennent à un endroit prévisible du programme. Elles correspondent généralement à un fonctionnement incorrect de celui-ci et conduisent souvent à l’arrêt de la tâche associée. Les traps sont ainsi l’équivalent matériel des exceptions logicielles, qui elles aussi se déclenchent en cas d’exécution incorrecte d’instructions, et qui ont un gestionnaire associé. Les interruptions Une interruption est une exception à cause externe, généralement liée à un événement d’entrée/sortie. Par exemple : •
la lecture de données par un disque est terminée et le contrôleur de disques envoie au processeur une interruption pour l’en informer.
•
un paquet de réseau vient d’arriver.
•
un événement clavier ou souris vient de se produire.
•
un changement est intervenu sur une des lignes d’entrées/sorties.
•
un événement est intervenu sur la chaîne USB.
Les interruptions sont dites asynchrones, car elles surviennent à des moments totalement imprévisibles du fonctionnement du processeur, et non en rapport avec des instructions précises. Le processeur est prévenu de l’occurrence de l’interruption par l’activation d’une ligne externe spécifique, souvent commune à plusieurs ou à toutes les interruptions. C’est seulement ensuite que le type de l’interruption sera identifié, et qu’un sous-programme
172
CHAPITRE V. PROGRAMMATION DU PROCESSEUR 32 BITS CRAPS/SPARC
associé sera exécuté, et ce d’une façon qui devra rester transparente pour le programme principal qui était en cours d’exécution à ce moment. Par exemple lorsque vous appuyez sur une touche de votre clavier d’ordinateur, aucun programme n’attend spécifiquement et activement cette frappe. Lors de la frappe, une interruption est générée par le contrôleur de clavier vers le processeur, et celui-ci suspend le programme en cours d’exécution, et appelle un sous-programme gestionnaire de cette interruption, dont le rôle va être d’obtenir du contrôleur de clavier le code de la touche frappée et de le placer dans un buffer situé au niveau du système d’exploitation. L’exécution de ce sous-programme sera très rapide et on verra qu’elle sera faite de façon transparente pour le programme qui a été interrompu. Dans CRAPS, il n’y a que deux sources d’interruption : le timer et les lignes d’entrées/sorties. On pourra programmer le timer pour qu’il déclenche une interruption à chaque front montant de sa sortie, et cela permettra par exemple d’implanter un scheduler de tâches ou une horloge temps réel. On pourra également programmer les lignes d’entrées/sorties pour déclencher une interruption à l’arrivée d’un front montant ou descendant (sens programmable au choix) sur une ligne configurée en entrée. Une interruption = un coup de sonnette Pour mieux faire comprendre les traps et les interruptions, on peut employer une analogie domestique. Supposons que nous soyons en train de faire un gâteau, en suivant les instructions d’une recette : ce sera l’équivalent du programme principal en cours d’exécution. La plupart des instructions de la recette sont sans problème : remuer la pâte, etc. Un petit nombre d’entre-elles peuvent mal se passer : si on doit casser un oeuf mais qu’il n’est pas frais ; si on doit mettre une pincée de sel mais qu’on n’a plus de sel, etc. Ces problèmes spécifiques sont associés à des instructions spécifiques, ce sont l’équivalent des traps. Le plus souvent, leur prise en compte consistera à abandonner la tâche en cours. Supposons maintenant que pendant que nous faisons cette recette nous attendions la visite du facteur, mais que notre sonnette soit en panne. Comme le moment précis de cette visite n’est pas défini, cela va nous obliger à interrompre sans cesse notre recette pour aller regarder par la fenêtre. Cela sera pénalisant pour la recette, mais aussi pas très efficace : le facteur peut très bien être venu et reparti en pensant qu’il n’y avait personne, si on laisse passer trop de temps entre deux coups d’oeil. Avec une sonnette, qui joue le rôle d’une ligne d’interruption, on peut réaliser la recette sans penser sans cesse au facteur. Si un coup de sonnette survient, on terminera l’instruction en cours, (par exemple, il faut terminer de casser un oeuf si on a déjà commencé) et on cochera sur la recette l’endroit où on s’est arrêté. On réagira ensuite à l’interruption par une procédure adaptée : aller à la porte, ouvrir, saluer, récupérer éventuellement des données, etc. De retour de cette procédure, dont le traitement aura été court, on reprendra la recette à l’instruction qui suit celle qu’on avait cochée. Cette analogie permet même de présenter le problème des niveaux de priorité entre exceptions. Comment doit-on faire si par exemple le téléphone sonne alors qu’on est en train de répondre au visiteur à la porte ? Si on juge que le téléphone est plus prioritaire que la porte d’entrée, alors on interrompra le dialogue en cours avec le visiteur pour effectuer la procédure associée au téléphone. Une fois qu’on aura fini de répondre, on reprendra le dialogue avec le visiteur là où on l’avait arrêté, pour finalement revenir au programme principal (la recette), dont l’exécution n’aura pas été perturbée.
10. Programmation des exceptions
173
Niveaux de priorité Ainsi, à chaque exception (trap ou interruption) est associé un niveau de priorité, codé généralement sous forme d’un nombre entier. Par ailleurs le processeur entretient dans un registre d’état une valeur de niveau courant d’exécution. Lorsque le processeur exécute un programme à un niveau p, il ne peut être interrompu que par une exception de niveau q supérieur à p. Lorsque cette interruption est prise en compte et que son gestionnaire est exécuté, le processeur élève son niveau d’exécution à q de façon à ne plus pouvoir être interrompu que par des exceptions de niveau supérieur à q. Lorsque le processeur sort du gestionnaire de cette exception, il reprend le niveau d’exécution qu’il avait avant l’appel. Le programme principal s’exécute au niveau 0, et il est par conséquent interruptible par toutes les exceptions. La figure V.34 montre un exemple de scénario d’exécution du programme principal et de deux gestionnaires d’exception de niveaux de priorité différents. priorité
gestionnaire excep.#13
6
gestionnaire excep.#25
3
programme principal
0
Figure V.34. Niveaux de priorités et gestionnaires d’exceptions. Le programme principal est interrompu par l’exception #25,qui lui même est interrompu par l’exception #13; chaque interruption est transparente pour celui qui est interrompu. Dans CRAPS, il y a 16 niveaux de priorité, codés sous forme d’un entier de 4 bits. Le niveau d’exécution courant du processeur est stocké dans le champ PIL du registre %psr (voir section 2.2). Au reset, le niveau d’exécution PIL est à 0. Numéro de l’exception À chaque exception est également associé un numéro de type, unique par type d’exception, qui va servir à localiser l’adresse de son gestionnaire d’exception.Ce numéro n’a en principe pas de rapport direct avec le niveau de priorité de l’exception. Dans CRAPS, comme dans le SPARC, le numéro de type d’exception est codé sur 8 bits, et il y a donc 256 sortes d’exceptions différentes. Seul un sous-ensemble des exceptions du SPARC a été implémenté, donné figure V.35. Le cas de reset est particulier : il est listé dans le tableau comme l’exception la plus prioritaire, mais elle n’a pas de numéro. En fait, l’occurrence d’un reset matériel va provoquer la remise à zéro de tous les registres, y compris %pc, et le contrôle va reprendre en mode non superviseur à l’adresse 0, avec un niveau d’exécution de 0, ce qui
CHAPITRE V. PROGRAMMATION DU PROCESSEUR 32 BITS CRAPS/SPARC
174
exception
priorité
numéro de type (tt)
reset
15
voir texte
bus error
14
14
instruction privilégiée
13
13
instruction illégale
12
12
division par zéro
11
11
erreur d’alignement
10
10
interruption timer
8
8
interruption entrées/sorties
7
7
Figure V.35. Tableau des exceptions, de leurs priorités et de leurs numéros de type. est le comportement souhaité. Le reset est considéré comme une exception sur un plan ’théorique’, mais il est géré d’une façon différente en pratique. Par ailleurs, afin de simplifier la structure du sous-sytème d’exceptions, les numéros de type d’exception et le niveau de priorité ont été rendus égaux. Gestionnaires d’exceptions Lorsqu’une exception est prise en compte, un sous-programme appelé gestionnaire d’exception ou handler d’exception est appelé, associé au numéro de type tt de l’exception. Il existe généralement une table de vecteurs d’exception qui contient les adresses de tous les gestionnaires d’exception, classée par numéro de type tt. Cette table est initialisée lors du chargement du système d’exploitation, ou elle peut être en ROM. Dans CRAPS, lorsqu’une exception est prise en compte, son numéro tt est copié dans le champ tt du registre %tbr. On rappelle figure V.36 sa structure, déjà présentée section 2.2. L’adresse ainsi formée est unique par numéro tt, et les adresses possibles sont espacées TBA (trap base address) 31
tt 12 11
zéros 4
0
TBA = poids forts adresse table des exceptions tt = trap type = numero de l’exception
Figure V.36. %tbr (Trap Base Register). de 16 en 16, puisque les 4 bits de poids faibles valent 0. À cette adresse sont placés, non pas l’adresse du gestionnaire comme c’est le cas dans beaucoup de processeurs, mais les premières instructions elles-mêmes du gestionnaire associé à l’exception. Comme il n’y a a-priori la place que pour 4 instructions (16 octets), celui-ci doit très vite effectuer un saut vers un autre emplacement en mémoire pour continuer son exécution.
10.2. Prise en compte d’une exception en CRAPS On va maintenant décrire exactement comment les exceptions sont prises en compte pour le processeur CRAPS, sachant que la procédure est globalement la même pour les autres processeurs, mais que de nombreux détails peuvent varier. Celle du CRAPS est une
10. Programmation des exceptions
175
simplification de celle du SPARC, mais en conserve les traits les plus importants. Conditions de prise en compte d’une exception Une exception est prise en compte si : •
ET = 1 (trap enable = 1) et
•
le niveau de priorité de l’exception est strictement supérieur au niveau courant d’exécution. (champ PIL du registre %psr)
Si une exception se produit pendant que ET = 0, elle est ignorée et non mémorisée ; tout se passe comme si elle n’avait jamais eu lieu. Si une exception se produit pendant que ET = 1 mais que son niveau est inférieur ou égal au niveau d’exécution courant, elle est mémorisée et sera prise en compte lorsque le niveau d’exécution redescendra au niveau ou au dessous du niveau de l’exception. Par contre si durant cette période une exception de même numéro se produit, elle sera perdue car celle qui est mémorisée en attente empêchera la mémorisation de la nouvelle. Procédure matérielle de prise en compte de l’exception Une fois les conditions de prise en compte réunies, la procédure suivant, ininterruptible, est exécutée automatiquement par le processeur (hardware) : 1
le processeur termine l’instruction en cours.
2
le processeur passe en mode superviseur : S ← 1. Le gestionnaire d’interruption aura ainsi accès à toutes les instructions.
3
%pc et %psr sont sauvegardés dans la pile.
4
le niveau de priorité de l’exception est copié dans le champ PIL du registre %psr, élevant ainsi le niveau d’exécution du processeur.
5
l’adresse du gestionnaire d’exception est calculée par la formule : %tbr + 16 * numéro de type.
6
le contrôle est passé à l’adresse du gestionnaire de l’exception : %pc ← adresse gestionnaire. On notera qu’à cet endroit, il n’y a la place que pour 4 instructions et qu’un saut vers un autre emplacement est nécessaire.
Contenu du gestionnaire d’exception L’écriture d’un gestionnaire d’exception est une tâche délicate ; elle est généralement réservée aux concepteurs des systèmes d’exploitation. Sur certaines plates-formes comme Linux, il est possible à un programmeur averti de le faire ; c’est bien sûr possible aussi sur la machine CRAPS. S’il s’agit d’un gestionnaire de trap, c’est à dire d’une exception provoquée par l’exécution incorrecte d’une instruction d’un programme, le code associé va généralement consister en l’abandon de la tâche en cours. L’écriture de ce code sort du cadre de cette discussion.
176
CHAPITRE V. PROGRAMMATION DU PROCESSEUR 32 BITS CRAPS/SPARC
S’il s’agit du gestionnaire d’une interruption, il faut faire en sorte que son exécution soit transparente pour le programme en cours de fonctionnement. Cela nécessite plusieurs conditions : •
tous les registres que ce gestionnaire va utiliser doivent être sauvegardés (dans la pile) dès l’entrée dans le gestionnaire et doivent être restaurés juste avant d’en sortir.
•
le gestionnaire doit être terminé par l’instruction RETE.
L’instruction RETE dépile les valeurs de %pc et %psr qui avaient été empilées automatiquement par le processeur lors de la prise en compte de l’interruption, puis incrémente %pc de 4. Les deux aspects cruciaux à comprendre sont, d’une part que la restauration de %pc permet le retour à l’instruction qui suit celle qui a été interrompue, d’autre part que la restauration de %psr permet de retrouver les valeurs de départ du niveau d’exécution et du bit superviseur. Au retour du gestionnaire d’exception, le programme principal n’a aucun moyen de savoir qu’il a été interrompu, car rien n’a changé pour lui entre le moment de l’interruption et celui du retour.
10.3. Exemple 1 : horloge temps réel À titre d’exemple, on va mettre en place une horloge qui permettra à un programme d’obtenir une heure d’une précision parfaite, sous forme d’une fonction getTimeMillis. À chaque appel de cette fonction, on obtiendra dans %r1 le nombre de millisecondes écoulées depuis la mise en route de la machine ; cette valeur s’incrémentera de façon exacte entre chaque appel, sans que le programme principal en cours d’exécution ne s’en occupe explicitement. On va pour cela programmer le timer pour qu’il fournisse des impulsions toutes les millisecondes, et le déclarer comme source d’interruption. C’est en effet possible dans CRAPS comme on peut le voir dans le tableau de la figure V.35 et cette interruption a le numéro 8 et la valeur de priorité 8.
10.4. Exemple 2 : programmation d’un scheduler de tâches en CRAPS La figure V.38 montre un programme complet de scheduler de tâches pour CRAPS, qui utilise le timer pour fournir une source d’interruption régulière pour effectuer la commutation d’une tâche à la suivante. Le programme est initialisé avec 4 tâches prog0,… prog3 qui effectuent infiniment l’inversion et la désinversion d’une chaîne de caractères ; chacune d’elles possède son propre espace de pile. Le scheduler entretient le numéro de la tâche en cours d’exécution, qui sert d’index dans une table de pointeurs de pile, TABSP. Lors de l’arrivée d’une interruption timer, le gestionnaire de cette exception sauvegarde le contexte d’exécution de la tâche qui va être suspendue dans la propre pile de la tâche. %pc et %psr ont déjà été sauvegardés par le traitement de l’exception ; il sauvegarde ensuite tous les registres.Le pointeur de pile de cette tâche en cours de suspension est sauvegardé dans TABSP, le numéro de la tâche courante est incrémenté (modulo 4) et le pointeur de pile de la tâche qui est réveillé est lu. Son contexte est relu à partir de sa propre pile et l’instruction finale rete va restaurer dans %pc l’adresse courante d’exécution de cette tâche.
10. Programmation des exceptions RAM STACK TIMER_BASE TT_TIMER
= = = =
loop
getTimeMillis
TABEXCEP
HDL_TIMER:
TIME
0x1000000 0x1000100 0x400000 8
177 ; début de la RAM ; sommet de la pile ; adresse de base du timer ; numéro de type de l’interruption timer
set STACK, %sp ; initialise sommet de pile ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; installe la table des exceptions ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; set TABEXCEP, %r1 wrtbr %r1 ; initialisation champ TBA de %tbr ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; initialisation timer ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; set BASETIMER, %r1 ; %r1 = base timer set 0x00320064, %r2 ; N=50 & P=100 st %r2, [%r1] ; écriture commande #1 set 0x00000001, %r2 ; prediv=0 & start=1 st %r2, [%r1+4] ; écriture commande #2 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; boucle infinie ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; call getTimeMillis ba loop ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; fonction getTimeTicks : renvoie dans %r1 le nombre ; de millisecondes écoulées depuis reset ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; set TIME, %r1 ld [%r1], %r1 ret ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; table des exceptions ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; .org TABEXCEP + 2*TT_TIMER ; adresse handler timer .word HDL_TIMER ; branchement à un endroit plus spacieux ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; timer handler : incrementation du mot mémoire TIME ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; sauvegarde de %r1 et %r2 subcc %sp, 2, %sp st %r1, [%sp] subcc %sp, 2, %sp st %r2, [%sp] set BASETIMER, %r1 setq 1, %r2 st %r2, [%r1+8] ; raz int. timer set TIME, %r1 ld [%r1], %r2 ; lecture de TIME inc %r2 ; incrémentation st %r2, [%r1] ; stockage de la valeur incrémentée ;; restauration de %r2 et %r1 ld [%sp], %r2 addcc %sp, 2, %sp ld [%sp], %r1 addcc %sp, 2, %sp rete .org RAM .word 0
Figure V.37. Programme d’horloge temps réel par interruptions.
178
CHAPITRE V. PROGRAMMATION DU PROCESSEUR 32 BITS CRAPS/SPARC
RAM STACK0 STACK1 STACK2
= 0x1000000 = 0x1000100 = 0x1000200 = 0x1000300 ;; installe la table des vecteurs d’exceptions set 0xF000, %sp set TABEXCEP, %r1 wrtbr %r1 ;; installe les tâches à exécuter set prog1, %r1 setq 1, %r2 call creer_tache set prog2, %r1 setq 2, %r2 call creer_tache ;; lancement du timer qui cadence le scheduling set 0x4000, %r2 ; %r2 = timer base address set 1000, %r1 ; periode = 1000 st %r1, [%r2+2] set 0b11, %r1 ; start + cyclic st %r1, [%r2] ; -> lance le scheduler ; lancement de la première tâche set TABSP, %r1 ld [%r1], %sp ; lecture de son %sp ba prog0 ;;; cree une nouvelle tâche de numéro %r2 et ;;; d’adresse de depart %r1 creer_tache set TABSP, %r3 addcc %r2, %r2, %r4 ld [%r3+%r4], %sp ; lecture du %sp subcc %sp, 2, %sp st %r1, [%sp] ; empilement %pc setq 7, %r5 ; empile 7 x 0 loop subcc %sp, 2, %sp st %r0, [%sp] dec %r5 bne loop st %sp, [%r3+%r4] ; sauve %sp dans TABSP ret
TABEXCEP
HDL_TIMER:
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; exception table ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; .org TABEXCEP + 2*3 ; handler timer jmp HDL_TIMER ; %pc et %psr deja empiles dans la pile ; du process en cours ; sauvegarde dans cette pile ; les registres %r1..%r5,%r7 addcc %sp, -2, %sp st %r1, [%sp] addcc %sp, -2, %sp st %r2, [%sp] addcc %sp, -2, %sp st %r3, [%sp] addcc %sp, -2, %sp st %r4, [%sp] addcc %sp, -2, %sp st %r5, [%sp] addcc %sp, -2, %sp st %r7, [%sp] ; raz int.timer set BASETIMER, %r1 setq 1, %r2 st %r2, [%r1+8] ; sauvegarde du %sp de la tâche courante ; dans TABSP set NUMPROC, %r1 ld [%r1], %r2 addcc %r2, %r2, %r3 set TABSP, %r4 st %sp, [%r4+%r3]
; incrémentation numéro tâche courante modulo 3 inc %r2 subcc %r2, 3, %r0 bneg sav clr %r2 sav st %r2, [%r1] ; restauration depuis TABSP du %sp de ; la tâche suivante addcc %r2, %r2, %r3 ld [%r4+%r3], %sp ; restauration des registres %r1..%r5,%r7 ; depuis la nouvelle pile ld [%sp],%r7 addcc %sp, 2, %sp ld [%sp],%r5 addcc %sp, 2, %sp ld [%sp],%r4 addcc %sp, 2, %sp ld [%sp],%r3 addcc %sp, 2, %sp ld [%sp],%r2 addcc %sp, 2, %sp ld [%sp],%r1 addcc %sp, 2, %sp ; restauration des anciens %psr et %pc ; donc retour de la d’ou on etait parti rete ;;;;;;;;;;;
TACHES UTILISATEURS
;;;;; tâche #0 : inversion et desinversion de STR0 prog0: set STR0, %r1 l0: call inverse ba l0 ;;;;; tâche #1 : inversion et desinversion de STR1 prog1: set STR1, %r1 l1: call inverse ba l1 ;;;;; tâche #2 : inversion et desinversion de STR2 prog2: set STR2, %r1 l2: call inverse ba l2 inverse: rev1
rev2 rev3
outrev
clr sethi ldub tst be inc ba clr dec cmp bpos ldub ldub stb stb dec inc ba ret
%r3 0,%r4 [%r1+%r3], %r4 %r4 rev2 %r3 rev1 %r5 %r3 %r5, %r3 outrev [%r1+%r3], %r4 [%r1+%r5], %r2 %r4, [%r1+%r5] %r2, [%r1+%r3] %r3 %r5 rev3
; i <- 0
; tq (str[i]<>0) ; ; ; ;
i <- i + 1 fin tq j <- 0 i <- i - 1
; ; ; ; ; ; ; ;
tq (i>j) x <- str[i] y <- str[j] str[i] <- y str[j] <- x i <- i - 1 j <- j + 1 fin tq
TABSP
.org RAM ; numero de la tâche courante .word 0 ; table des tâches .word STACK0, STACK1, STACK2
STR0 STR1 STR2
; chaînes pour inversion en RAM .byte ’abcdefg’,0 .byte ’123456789’,0 .byte ’xyz’,0
NUMPROC
Figure V.38. Programme complet du scheduler de tâches.
11. Exercices corrigés
;;;;;;;;;;
11. Exercices corrigés
179
11.1. Exercice 1 : multiplication programmée Énoncé Écrire un sous-programme qui effectue la multiplication de deux nombres non signés de 16 bits placés dans les poids faibles de %r1 et %r2 respectivement, avec un résultat sur 32 bits dans %r3. Solution On pourrait mettre en place une boucle qu’on effectuerait %r1 fois, et dans laquelle on cumulerait %r2 dans %r3. Cette méthode serait simple, mais particulièrement inefficace pour de grandes valeurs de %r1. On va plutôt utiliser une méthode inspirée de la figure II.46, dans laquelle on prend les bits de %r2 un par un en commençant par le poids faible, et on cumule dans %r3 (initialisé à 0) la valeur de %r1, que l’on décale vers la gauche à chaque étape. Cela correspond à l’algorithme suivant : ; calcul du produit A x B résultat <- 0 ; tant que (B <> 0) faire b0 <- poids_faible(B) ; décaler B d’un bit vers la droite ; si b0 = 1 faire résultat <- résultat + A ; fin si décaler A d’un bit vers la gauche ; fin tq
Le programme donné figure V.39 est une traduction littérale de l’algorithme. On notera le test bne noadd qui n’est pas fait immédiatement après l’instruction andcc %r2, 1, %r0 qui positionne le flag Z ; cela est possible car l’instruction qui la suit srl %r2, 1, %r2 ne modifie pas les flags.
11.2. Exercice 2 : système de sécurité Énoncé On souhaite utiliser les lignes d’entrées/sorties de CRAPS pour réaliser un système de mise en marche de machine dangereuse. Les lignes IO[0] et IO[1] seront configurées en entrées ; on supposera qu’elles sont reliées à des boutons poussoirs appelés A et B respectivement, équipés d’un dispositif anti-rebond. La ligne IO[2] sera configurée en sortie, et commandera la mise en marche de la machine. On demande d’écrire un programme qui fonctionne en permanence, et qui déclenche la mise en marche de la machine lorsque la procédure suivante est respectée : 1
A et B doivent être relâchés initialement ;
2
appuyer sur A,
3
appuyer sur B : la machine se met en marche.
CHAPITRE V. PROGRAMMATION DU PROCESSEUR 32 BITS CRAPS/SPARC
180
main
mulu16 loop
noadd fin
;; programme principal de test set 17, %r1 ; A <- 17 set 14, %r2 ; B <- 14 call mulu16 ; calcul de A x B ba * ; arrêt ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; multiplication non signé ;; calcule %r1.L x %r2.L, résultat dans %r3 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; clr %r3 ; résultat <- 0 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 sll %r1, 1, %r1 ; décale A à gauche ba loop ; fin tq 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 M=0
a
10
11 11
10
b
01 11
M=1
00 01
00 10
00
c
M=0
01
e M=0
Figure V.40. Graphe d’états du système de sécurité. À chaque état dans le graphe correspondra une position dans le programme. Cela conduit à l’algorithme suivant : configurer IO[1] (A) et IO[0] (B) en entrées, IO[2] (M) en sortie ; label a: écrire 0 sur M ;
11. Exercices corrigés
181
label a1: lire état de A et B ; cas (A,B) (0,0): aller en a1 ; (1,0): aller en b ; autre: aller en e ; fin cas label b: écrire 0 sur M ; label b1: lire état de A et B ; cas (A,B) (1,0): aller en b1 ; (1,1): aller en c ; autre: aller en e ; fin cas label c: écrire 1 sur M ; label c1: lire état de A et B ; cas (A,B) (1,1): aller en c1 ; autre: aller en e ; fin cas
Cet algorithme se traduit de façon littérale par le programme donné figure V.41.
11.3. Exercice 3 : inversion d’une chaîne de caractères Énoncé Écrire un sous-programme qui inverse ’sur place’ une chaîne de caractères ASCII située en RAM et pointée par %r1. Solution Les caractères sont ici stockés sous forme de codes ASCII dans des octets consécutifs. On emploiera donc les instruction ldub et stb pour les lire et les écrire en mémoire. L’idée de l’algorithme à implémenter est de conserver un pointeur en début de chaîne et d’en placer un autre en fin de chaîne. On intervertit alors les caractères qu’ils pointent, et on les déplace d’un cran l’un vers l’autre. On recommence ensuite l’échange et le déplacement jusqu’à ce qu’ils se rencontrent. Cela donne plus précisément l’algorithme suivant : i <- 0 ; tant que (str[i] <> 0) faire i <- i + 1 ; fin tq ; i <- i - 1 ; j <- 0 ;
CHAPITRE V. PROGRAMMATION DU PROCESSEUR 32 BITS CRAPS/SPARC
182
IOBASE
=
0x600000
main
set setq st
IOBASE, %r1 0b100, %r2 %r2, [%r1+2]
; adresse de base I/O dans %r1 ; configuration IO[1..0] entrées ; IO[2] sortie
a:
setq st ld andcc subcc be subcc bne setq st ld andcc subcc be subcc bne setq st ld andcc subcc be setq st ld andcc subcc bne ba
0b000, %r2 %r2, [%r1] [%r1], %r2 %r2, 0b11, %r2, 0b00, a1 %r2, 0b10, e 0b000, %r2 %r2, [%r1] [%r1], %r2 %r2, 0b11, %r2, 0b10, b1 %r2, 0b11, e 0b100, %r2 %r2, [%r1] [%r1], %r2 %r2, 0b11, %r2, 0b11, c1 0b000, %r2 %r2, [%r1] [%r1], %r2 %r2, 0b11, %r2, 0b00, e1 a
; M <- 0
a1:
b: b1:
c: c1:
e: e1:
; adresse de base entrées/sorties
%r2 %r0 %r0
%r2 %r0 %r0
%r2 %r0
%r2 %r0
; lecture état de A et B ; isolation des bits 0 et 1 ; (A,B) = (0,0) : aller en a1 ; (A,B) = (1,0) : aller en b ; sinon aller en e ; ; M <- 0 ; lecture état de A et B ; isolation des bits 0 et 1 ; (A,B) = (1,0) : aller en b1 ; (A,B) = (1,1) : aller en c ; sinon aller en e ; ; ; ; ;
M <- 1 lecture état de A et B isolation des bits 0 et 1 (A,B) = (1,1) : aller en c1 sinon aller en e
; ; ; ;
M <- 0 lecture état de A et B isolation des bits 0 et 1 (A,B) <> (0,0) : aller en e1
; sinon aller en a
Figure V.41. Programme de mise en marche de sécurité pour machine dangereuse. tant que (i > j) faire x <- str[i]; y <- str[j]; str[i] <- y ; str[j] <- x ; i <- i - 1 ; j <- j + 1 ; fin tq ;
La traduction littérale de cet algorithme est donnée figure V.42.
11. Exercices corrigés
183
RAM STACK
= =
main
;; programme de test set STACK, %sp set STR, %r1 call reverse ba *
reverse
clr sethi ldub tst be inc ba clr dec cmp bpos ldub ldub stb stb dec inc ba ret
%r3 0,%r4 [%r1+%r3], %r4 %r4 rev2 %r3 rev1 %r5 %r3 %r5, %r3 outrev [%r1+%r3], %r4 [%r1+%r5], %r2 %r4, [%r1+%r5] %r2, [%r1+%r3] %r3 %r5 rev3
.org .byte
RAM ’azertyuiop’,0
rev1
rev2 rev3
STR
0x10000000 RAM + 0x1000
; adresse début de RAM ; adresse sommet pile
; i <- 0
; tq (str[i]<>0) ; ; ; ;
i <- i + 1 fin tq j <- 0 i <- i - 1
; ; ; ; ; ; ; ;
tq (i>j) x <- str[i] y <- str[j] str[i] <- y str[j] <- x i <- i - 1 j <- j + 1 fin tq
; chaîne de test
Figure V.42. Sous-programme d’inversion sur place d’une chaîne de caractères.
11.4. Exercice 4 : conversion binaire / ASCII Énoncé Écrire un sous-programme qui convertit un nombre entier placé dans %r1, codé en binaire non signé, en une chaîne de caractères ASCII qui en est sa représentation décimale, à partir de l’adresse mémoire contenue dans %r2. Solution On applique un algorithme connu sous le nom de ’schéma de Horner’, qui consiste à diviser le nombre par 10, à conserver le reste, puis à diviser à nouveau le quotient par 10 et à garder le reste, et ainsi de suite jusqu’à ce que le nombre soit plus petit que 10 ; la suite des restes obtenus forme la suite inversée des chiffres décimaux cherchés. Plus précisément, l’algorithme est :
CHAPITRE V. PROGRAMMATION DU PROCESSEUR 32 BITS CRAPS/SPARC
184
i <- 0 ; tant que (n >= 10) faire (q,r) <- n / 10 ; n <- q ; str[i] <- r + ’0’; i <- i + 1 ; fin tq str[i] <- n + ’0’; i <- i + 1 ; str[i] <- 0 ; inverser sur place str ;
La traduction littérale de cet algorithme est donnée figure V.43. RAM STACK
= =
main
;; programme de test set STACK, %sp set 1234, %r1 set STR, %r2 call bin2ascii ba *
bin2ascii b1
clr cmp bneg
%r5 %r1, 10 b2
udivcc sll srl srl
%r5, %r5, %r3, %r5,
10, 16, 16, 16,
addcc stb inc ba addcc stb inc stb
%r3, %r3, %r5 b1 %r1, %r1, %r5 %r0,
’0’, %r3 [%r2+%r5]
mov call ret
%r2, %r1 reverse
.org .byte
RAM ’azertyuiop’,0
b2
STR
0x10000000 RAM + 0x1000
; adresse début de RAM ; adresse sommet pile
; i <- 0 ; tq (n>=10)
%r5 %r3 %r3 %r5
’0’, %r1 [%r2+%r5] [%r2+%r5]
; r dans %r3 ; n <- q
; str[i] <- r + ’0’ ; i <- i + 1 ; fin tq ; str[i] <- n + ’0’ ; i <- i + 1 ; str[i] <- 0
; chaîne de test
Figure V.43. Programme de conversion binaire vers décimal.
185
Chapitre VI Construction du processeur 32 bits CRAPS Nous avons appris à programmer le processeur CRAPS dans le chapitre précédent, afin d’avoir une idée plus précise de la nature des opérations qu’il va devoir effectuer. On va maintenant réaliser effectivement CRAPS, uniquement par l’assemblage de modules qui ont été présentés au chapitre 4 ; les principes de modularité qui ont été mis en avant dans cet ouvrage sont donc effectivement mis en oeuvre d’un bout à l’autre de la conception du processeur. Au delà de la construction de ce processeur particulier, ce sont les principes de base de la conception de tous les processeurs et microcontrôleurs qui seront abordés.
1. Les registres CRAPS possède 32 registres banalisés pour le programmeur : %r0, …, %r31, ainsi que des registres utilisés pour son fonctionnement : •
%pc : program counter, qui contient l’adresse de l’instruction en cours d’exécution.
•
%ir : instruction register, qui contient l’instruction en cours d’exécution.
•
%psr : program status register, qui contient les flags et des informations relatives aux exceptions.
•
%tbr : trap base register, qui contient l’adresse de la table des exceptions.
À cette liste on ajoutera deux registres qu’on appellera %tmp1 et %tmp2, inaccessibles au programmeur, qui seront utilisés par le processeur lors de l’exécution de certaines instructions. Pour uniformiser les accès, on a donné également des numéros à ces derniers registres, dans l’intervalle [32, 63]. Ainsi, tous les registres sont désignés par un numéro codable sur 6 bits. Les numéros des registres non banalisés de CRAPS sont : •
%tmp1 & %tmp2 : %r32 & %r33
•
%tbr : %r60
•
%psr : %r61
•
%pc : %r62
•
%ir : %r63
186
1. Les registres
187
Le sous-ensemble des registres a la structure générale montrée à la figure VI.1.
0
0 64
décodeur 1 bus D
dsel[63..0]
63 6
%r0
1
%r1
2
%r2
64
0 1 décodeur
6
bus B
breg[5..0]
bsel[63..0] 63
...
CLK dreg[5..0] 30
%r14
64
31
%r15
asel[63..0]
32
%tmp1
33
%tmp2
60
%tbr
0 1 décodeur
6
bus A
areg[5..0]
63
32
61 %psr
NZVC
62 63 %ir
op
setNZ,setVC
32
32
PIL
%pc rd
rs1
rs2
4
4
Bus D dbus[31..0]
Bus A Bus B abus[31..0] bbus[31..0] N,Z,V,C (UAL) N,Z,V,C
Figure VI.1. Le sous-ensemble des registres du processeur CRAPS. Le programmeur n’a un accès banalisé qu’aux 32 premiers d’entre-eux. On voit trois bus de 32 bits sur cette figure, qui sont les trois grands bus du processeur sur lesquels circuleront toutes les données manipulées. Les bus A et B Sur les bus A et B on peut faire venir les valeurs de deux registres simultanément : il suffit de mettre leurs numéros sur areg et breg, respectivement.Par exemple, si on souhaite avoir %tbr sur le bus A et %r1 sur le bus B, on place 60 sur areg et 1 sur breg. Les valeurs des deux registres apparaissent alors sur les bus A et B respectivement, de façon asynchrone. Les deux bus fonctionnent indépendamment et en parallèle. Le bus D Sur le bus D (qui sera souvent un bus de données, d’où son nom) on place une valeur qu’on souhaite écrire dans un registre. On doit placer le numéro de ce registre dans dreg, et envoyer un front montant sur l’horloge clk : la valeur sur le bus D est alors écrite dans le registre de numéro dreg. Le registre %psr et l’écriture des flags On l’a vu au chapitre précédent, les flags N, Z, V, C sont les valeurs des bits 23 à 20 du registre %psr, c’est à dire du registre %r61. Pourtant ils seront positionnés, tantôt par la procédure ’normale’ d’écriture en %r61 (par exemple lors de la restauration de %psr à un retour de gestionnaire d’exception), mais aussi également lors de l’activation de signaux
188
CHAPITRE VI. CONSTRUCTION DU PROCESSEUR 32 BITS CRAPS
dédiés setN, setZ et setVC. On désire par exemple que, lorsque setVC vaut 1 au front d’horloge, les bits V et C mémorisent les valeurs V_UAL et C_UAL qui proviennent de l’UAL, et cela concurremment à l’écriture standard dans un autre registre (dont le numéro est sur dreg). V et C sont groupés, car ils sont toujours positionnés ensemble par des instructions telles que addcc. N et Z ne sont pas groupés, car une opération comme umulcc ne positionne que Z. Les cas particuliers des registres %r0 et %r34 à %r38 Du chapitre précédent sur la programmation de CRAPS on connaît déjà le registre %r0 et son usage très pratique. Il doit nécessairement être traité de façon particulière : il sera par exemple inutile de prévoir des bascules de mémorisation pour lui, et on devra faire en sorte que sa lecture donne toujours 0. De la même façon, il sera nécessaire de disposer des constantes 2, 4, 8, 16 et 24 lors du séquencement des instructions. La constante 4 sera utilisée par exemple lors de l’incrémentation de %pc de 4 pour le passage à l’instruction suivante. Les autres constantes ont des usages encore plus spécifiques, qui seront décrits plus loin. On a choisi de faire en sorte que ces constantes soient les valeurs des registres %r34 (= 2), %r35 (= 4), %r36 (= 8), %r37 (= 16) et %r38 (= 24) ; de même que pour %r0, l’écriture dans ces registres spéciaux sera sans effet, et leur lecture renverra toujours ces valeurs constantes. Structure d’un registre ordinaire On voit figure VI.2 comment est construit un registre ordinaire, c’est à dire avec un indice i qui n’est, ni 0 (pour %r0), ni 34 à 38 (pour les constantes), ni 61 (pour %psr).
Figure VI.2. Structure d’un registre ordinaire. Un registre est bien composé de 32 bascules D. Une valeur de 1pour le signal dsel[i] indique qu’une écriture est souhaitée dans le registre d’indice i. Comme dsel[i] est
1. Les registres
189
branché sur l’entrée de validation des bascules, les bascules vont se charger avec les valeurs présentes sur les lignes dbus[31..0], c’est à dire le bus D. En ce qui concerne la lecture sur les bus A et B, elle s’effectue bien de façon asynchrone par l’intermédiaire des buffers 3-états du bas de la figure. Quand par exemple asel[i] est activée (c’est à dire que areg[5..0] = i), les registres vont placer leurs valeurs sur les lignes abus[31..0], c’est à dire le bus A. Il n’y aura pas de conflit avec les autres registres, car les lignes qui activent les buffers 3-états sont les sorties d’un même décodeur, et donc toutes mutuellement exclusives. Cela conduit à l’écriture SHDL suivante, pour %r1 par exemple : // %r1 r1[31..0] := dbus[31..0]; r1[31..0].clk = clk; r1[31..0].rst = rst; r1[31..0].ena = dsel1; abus[31..0] = r1[31..0]:asel1; bbus[31..0] = r1[31..0]:bsel1;
Structure de %psr Pour le registre %r61 (%psr), seuls les bits 23..20 (N,Z,V,C), 11..7 (PIL), 7 (S) et 5 (ET) sont mémorisés. Les bits N,Z,V,C peuvent être écrits de deux façons : 1
directement par écriture dans %r61 (dsel61 = 1).
2
par activation de setN, setZ ou setVC.
La figure VI.3 montre sur le bit 23 (flag N) comment cela peut être implémenté.
Figure VI.3. Structure du bit N du registre %psr. Si on a, ni dsel23 = 1, ni setN = 1, le registre ne peut pas changer de valeur au front d’horloge (entrée en à 0). Si setN = 1, on voit que le registre va se charger avec la valeur N_UAL en provenance de l’UAL. Cette écriture, qui aura lieu au front d’horloge, pourra avoir lieu en même temps qu’une écriture normale dans un autre registre. Si setN = 0 et dsel23
190
CHAPITRE VI. CONSTRUCTION DU PROCESSEUR 32 BITS CRAPS
= 1, on retombe dans le cas de l’écriture ’normale’ dans %r61, avec mémorisation du bit dbus23 en provenance du bus D. Les sorties sur les bus A et B sont identiques aux autres registres ; on notera que le bit N (mémorisé) sort par ailleurs directement vers l’extérieur. Cette gestion du bit N s’écrit en SHDL : // %r61 = %psr, bit N (bit #23) dr[23] = dsel61*dbus23+setN*N_UAL; // N ena23 = dsel61 + setN; r61[23] = dr[23]; r61[23].ena = ena23; r61[23].clk = clk; r61[23].rst = rst; abus[23] = r61[23]:asel61; bbus[23] = r61[23]:bsel61;
Lecture et écriture dans %r0 et %r34 à %r38. On rappelle que l’écriture dans les registres %r0 et %r34 à %r38 doit être sans effet et que la lecture doit renvoyer les valeurs constantes 0, 2, 4, 8, 16 et 24 respectivement. Aucune bascule ne doit leur être associée, puisqu’il n’y a rien à stocker. Lorsque leurs lignes de sélection sont activées, les valeurs constantes doivent être placées directement sur le bus par l’intermédiaire d’un buffer 3-états (figure VI.4).
Figure VI.4. Structure des registres %r0 et %r34 à %r38. L’écriture dans ces registres est sans effet, et la lecture renvoie des valeurs constantes. Finalement on peut écrire les équations SHDL de tout le sous-système de registres (figure VI.5). Il est nécessaire de dupliquer le même bloc de lignes autant de fois qu’il y a de registres, car le langage ne permet pas d’autre raccourci que l’écriture vectorielle simple à une dimension.
2. L’unité arithmétique et logique L’unité arithmétique et logique (UAL) est le sous-système du processeur CRAPS qui va effectuer, non seulement les calculs explicitement associés aux instructions arithmétiques et logiques du programme en cours d’exécution, mais aussi tous les calculs implicitement demandés par leur mise en oeuvre, comme par exemple l’incrémentation de %pc de 4 après l’exécution d’une instruction séquentielle, ou le calcul du déplacement lors d’un saut relatif (type ba), etc.
2. L’unité arithmétique et logique
module registres(rst,clk, areg5..areg0,breg5..breg0, dreg5..dreg0, dbus31..dbus0,setN,setZ,setVC, N_UAL,Z_UAL,V_UAL,C_UAL : abus31..abus0, bbus31..bbus0, N,Z,V,C) // décodeurs 6 vers 64 decodeur6to64(areg5..areg0:asel63..asel0); decodeur6to64(breg5..breg0:bsel63..bsel0); decodeur6to64(dreg5..dreg0:dsel63..dsel0); // %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; ... // idem pour %r2..r33; %r60,%r62..%r63 ... // %r0 = 0 abus[31..0] = 0:asel0; bbus[31..0] = 0:bsel0; // %r34 = 2 abus[31..0] = 2:asel34; bbus[31..0] = 2:bsel34; ... // idem pour %r35 = 4,.., %r38 = 24 ...
191
// %r61 = %psr D_N = dsel61*dbus23+setN*N_UAL; // N EN_N = dsel61 + setN; r61[23] := D_N; r61[23].ena = EN_N; D_Z = dsel61*dbus22+setZ*Z_UAL; // Z EN_Z = dsel61 + setZ; r61[22] := D_Z; r61[22].ena = EN_Z; EN_VC = dsel61 + setVC; D_V = dsel61*dbus21+setVC*V_UAL; // V D_C = dsel61*dbus20+setVC*C_UAL; // C r61[21] := D_V; r61[20] := D_C; r61[21..20].ena = EN_VC; r61[11..7,5] := dbus[11..7,5]; // PIL & S & ET r61[11..7,5].ena = dsel[11..7,5]; r61[23..22,11..7,5].clk = clk; r61[23..22,11..7,5].rst = rst; abus[23..22,11..7,5] = r61[23..22,11..7,5]:asel61; bbus[23..22,11..7,5] = r61[23..22,11..7,5]:bsel61; end module
Figure VI.5. Écriture SHDL du sous-système des registres. La figure VI.6 montre l’interface de ce sous-système. C’est un circuit asynchrone pur, sans horloge. Les bus d’entrée et le bus de sortie ont une largeur de 32 bits ; la commande cmd_ual qui indique l’opération à effectuer est codée sur 6 bits. Les signaux N,Z,V,C sortants donnent des indications sur le résultat de l’opération qui vient d’être effectuée ; les signaux setN, setZ et setVC indiquent si ces flags doivent être copiés dans les bits N,Z,V,C du registre %psr au prochain front d’horloge. Un dernier signal div0 indique une tentative de division par 0.
Figure VI.6. Interface de l’UAL.
2.1. Fonctions de l’UAL On peut énumérer dans un tableau toutes les opérations de l’UAL, avec leurs codes
CHAPITRE VI. CONSTRUCTION DU PROCESSEUR 32 BITS CRAPS
192
associés (figure VI.7). Ces codes sont sur 6 bits alors que 5 bits suffiraient ; cela est dû au fait que l’on a rendus égaux les codes du champ op3 des instructions arithmétiques et logiques (voir figure V.8 du chapitre précédent) avec les codes de l’opération correspondante de l’UAL, ce qui introduira une simplification de conception. Par exemple la valeur 010000 du champ op3 d’une instruction addcc est également le code de l’opération d’addition dans l’UAL. cmd_ual
opération
setN
setZ
setVC
010000
ADDCC, addition
1
1
1
010100
SUBCC, soustraction
1
1
1
011010
UMULCC, multiplication non-signée
0
1
0
001110
UDIVCC, division non-signée
0
1
0
010001
ANDCC, et logique
1
1
0
010010
ORCC, ou logique
1
1
0
010011
XORCC, xor logique
1
1
0
010111
XNORCC, xnor logique
1
1
0
110101
SLL, shift gauche logique
0
0
0
110110
SRL, shift droit logique
0
0
0
000000
ADD, addition
0
0
0
000100
SUB, soustraction
0
0
0
100000
SIGNEXT13, extension de signe bus A, 13 bits → 32 bits
0
0
0
100001
SIGNEXT22, extension de signe bus A, 22 bits → 32 bits
0
0
0
100010
SIGNEXT30, extension de signe bus A, 30 bits → 32 bits
0
0
0
101000
NOPB, no operation bus B
0
0
0
111000
INSBYTE0, insertion d’un octet dans un mot
0
0
0
111001
INSBYTE1, insertion d’un octet dans un mot
0
0
0
111010
INSBYTE2, insertion d’un octet dans un mot
0
0
0
111011
INSBYTE3, insertion d’un octet dans un mot
0
0
0
Figure VI.7. Table des opérations de l’UAL. Les codes des opérations arithmétiques et logiques sont identiques aux valeurs du champ op3 dans les instructions de calcul. La première partie du tableau concerne des opérations directement associées aux fonctions des instructions, bien que certaines seront aussi utilisées pour d’autres tâches internes à l’exécution. La deuxième partie ne concerne que ces opérations internes : l’addition et la soustraction sans positionnement des flags sont utilisées pour modifier la valeur du compteur ordinal par exemple ; l’extension de signe bus A, 13 → 32 bits, consiste à prendre l’entrée A, à conserver les 13 bits de poids faibles et à remplacer tous les autres bits de poids forts par des zéros ou des uns, selon que le nombre sur 13 bits était positif ou négatif (respectivement),réalisant ainsi une extension de signe. Cette opération sera nécessaire pour extraire la constante de 13 bits qui est incluse dans une instruction arithmétique ou logique avec constante immédiate, telle que addcc %r1, 5, %r2. Les deux autres opérations d’extension de signe fonctionnent de manière analogue sur 22 et 30 bits, et servent à extraire
2. L’unité arithmétique et logique
193
les valeurs de déplacement dans les instructions de branchement et dans l’instruction call. La commande NOPB consiste à laisser passer la valeur présente sur le bus B vers la sortie, sans changement. Enfin les commandes INSBYTE0,…,INSBYTE3, qui seront utilisées lors des instructions stb, insèrent l’octet présent sur le bus B dans le mot présent sur le bus A, en position 0, 1, 2 ou 3 respectivement.
2.2. Structure de l’UAL On pourrait concevoir une UAL en associant 32 tranches d’UAL de 1 bits, comme cela a été présenté à la section 8, mais ce serait trop inefficace. On va plutôt chercher à associer des modules efficaces tels que l’additionneur carry-lookahead, le multiplicateur systolique, etc. dont on a étudié la conception au chapitre 2. On suppose donc qu’on dispose de : •
un additionneur/soustracteur 32 bits d’interface SHDL : addsub32(a31..a0,b31..b0,op:s31..s0,N,Z,V,C) On pourra utiliser la technique vue en section 8.6 pour mettre en commun un additionneur 32 bits pour les deux opérations. op=0 indique qu’on fait une addition ; op=1 une soustraction. Les signaux N, Z, V, C ont la signification habituelle.
•
un multiplicateur 16 bits x 16 bits → 32 bits non signé, d’interface SHDL : mul32(a15..a0,b15..b0:s31..s0,Z) Seul un indicateur de nullité Z a un sens dans le contexte d’une multiplication non signée, pour laquelle aucun débordement n’est possible.
•
un diviseur 16 bits / 16 bits, avec calcul du quotient sur 16 bits et calcul du reste sur 16 bits, d’interface SHDL : div32(a31..a0,b15..b0:q15..q0,r15..r0,Z,DIV0) Cette fois, on a une indication supplémentaire qui détecte une division par 0.
•
un décaleur à barillet 32 bits, d’interface SHDL : shift32(a31..a0,sens,nb4..nb0:s31..s0) L’amplitude du décalage est codée dans nb4..nb0, de 0 à 31 bits ; le sens du décalage est codé dans sens (0=gauche, 1=droite).
•
un circuit d’extension de signe, d’interface SHDL : signext(a12..a0,b1,b0:s31..s0) On a déjà étudié ces circuits auparavant; ils effectuent le transcodage d’un nombre signé codé en complément à 2, d’une certaine largeur de bits à une largeur de bits plus grande, en recopiant autant de fois que nécessaire le bit de signe sur la gauche. Le présent module est paramétrable : si b1,b0 = 00 (respectivement 01, 10) il effectue l’extension de 13 (respectivement 22, 30) vers 32 bits.
194 •
CHAPITRE VI. CONSTRUCTION DU PROCESSEUR 32 BITS CRAPS un circuit d’insertion d’octet, d’interface SHDL : insbyte(a31..a0,b7..b0,n1,n0:s31..s0) Ce circuit recopie a31..a0 vers s31..s0, sauf un groupe de 8 bits dont la position est donnée par n1,n0 (de 0 à 3), qui est le numéro de l’octet dans le mot (0 représentant les rangs de 31 à 24 et 3 représentant les rangs de 7 à 0), les bits copiés étant alors ceux de b7..b0. L’écriture d’un tel module est très simple et peut être faite à titre d’exercice.
La figure VI.8 montre comment associer tous ces modules en une seule UAL. Un décodeur (en haut à droite du schéma) produit les signaux addsub, etc. qui indiquent le type d’opération à effectuer. Chaque module de calcul est suivi de buffers trois-états qui sont activés par la sortie correspondante du décodeur ; il est donc garanti que ces buffers n’entreront pas en conflit. Par ailleurs le décodeur a une entrée de sélection CS qui est reliée au signal OE : si OE=0, aucune sortie du décodeur n’est active et donc tout le bus S est en haute impédance. Enfin le décodeur produit les signaux setN, setZ, setVC qui indiquent, selon le code de l’opération, si les flags correspondants ont un sens. Les sorties des buffers trois-états sont toutes reliées à la sortie S, qui est donc la synthèse entre tous ces signaux. Si par exemple le code de l’opération est cmd_ual=010011 (xor) et si OE=1, alors seule la sortie xor du décodeur sera activée ; le buffer trois-états associé à l’opération xor laissera alors passer sur la sortie S le résultat de l’opération. Comme le module addsub exécute à la fois l’addition et la soustraction, la sortie est contrôlée par le signal addsub qui regroupe les 4 codes associés à des opérations d’addition et de soustraction dans la table VI.7. Le choix entre addition et soustraction sur l’entrée op du module addsub est relié au signal cmd_ual[2] : en effet, si on examine les 4 codes en question, le bit 2 vaut 0 pour les 2 opérations d’addition, et 1 pour les 2 opérations de soustraction. De façon analogue le décaleur à barillet shifter a sa sortie contrôlée par le signal shift qui regroupe les opérations sll et srl ; c’est le signal cmd_ual[1] qui va faire la distinction et être relié à l’entrée sens du décaleur, car entre les deux opérations de décalage, c’est le bit 1 du code qui indique le sens. On notera que l’entrée nb (le nombre de bits à décaler) est reliée aux 5 bits de poids faibles de l’entrée B31..B0, pour une valeur de décalage entre 0 et 31. En ce qui concerne le flag N, il n’a de sens que pour les opérations add/sub, and, or, xor, xnor ; il n’a pas de sens pour la multiplication et la division, qui sont non signées ici. Les modules associés produisent ainsi les signaux N1,…,N5 respectivement, qui sont en fait constitués du bit de poids fort du résultat. Ces signaux sont ensuite synthétisés en un seul signal N en fonction de l’opération effectivement demandée sur cmd_ual. Un traitement identique est réalisé pour produire le bit Z, qui a un sens pour les mêmes opérations que N, mais aussi pour la multiplication et la division. Chaque module génère ainsi un signal Z1,…,Z7 ; le module ZERO sert à produire ce signal pour les opérations logiques. Les flags V et C quant à eux ne peuvent être produits que par le module addsub, qui les exporte donc directement. La figure VI.9 donne l’écriture complète du module d’UAL en langage SHDL.
2. L’unité arithmétique et logique
195
Figure VI.8. Structure de l’UAL.
3. Les bus du processeur CRAPS Il nous faut maintenant examiner comment les grands sous-systèmes de CRAPS
196
CHAPITRE VI. CONSTRUCTION DU PROCESSEUR 32 BITS CRAPS module ual(rst,clk, A31..A0, B31..B0, cmd_ual5...cmd_ual0, OE: S31..S0, setN,setZ,setVC, N,Z,V,C, div0) // decodage de l’opération addsub=/cmd_ual5*cmd_ual4*/cmd_ual3*/cmd_ual1*/cmd_ual0 + /cmd_ual5*/cmd_ual4*/cmd_ual3*/cmd_ual1*/cmd_ual0; mul=/cmd_ual5*cmd_ual4*cmd_ual3*/cmd_ual2*cmd_ual1*/cmd_ual0; div=/cmd_ual5*/cmd_ual4*cmd_ual3*cmd_ual2*cmd_ual1*/cmd_ual0; and=/cmd_ual5*cmd_ual4*/cmd_ual3*/cmd_ual2*/cmd_ual1*cmd_ual0; or=/cmd_ual5*cmd_ual4*/cmd_ual3*/cmd_ual2*cmd_ual1*/cmd_ual0; xor=/cmd_ual5*cmd_ual4*/cmd_ual3*/cmd_ual2*cmd_ual1*cmd_ual0; xnor=/cmd_ual5*cmd_ual4*/cmd_ual3*cmd_ual2*cmd_ual1*cmd_ual0; shift=cmd_ual5*cmd_ual4*/cmd_ual3*cmd_ual2*cmd_ual0; sext=cmd_ual5*/cmd_ual4*/cmd_ual3*/cmd_ual2; // opérations addsub32(A31..A0,B31..B0,op:S1_31..S1_0,N1,Z1,V,C); mul32(A15..A0,B15..B0:S2_31..S2_0,Z2); div32(A31..A0,B15..B0:S3_31..S3_16,S3_15..S3_0,Z3,div0); shift32(A31..A0,cmd_ual0,A4..A0:S4_31..S4_0); S5_31..S5_0=A31..A0*B31..B0; // and ZERO(S5_31..S5_0:Z4); S6_31..S6_0=A31..A0+B31..B0; // or ZERO(S6_31..S6_0:Z5); S7_31..S7_0=A31..A0+/B31..B0+/A31..A0+B31..B0; // xor ZERO(S7_31..S7_0:Z6); S8_31..S8_0=A31..A0+/B31..B0+/A31..A0+B31..B0; // xnor ZERO(S8_31..S8_0:Z7); signext(A29..A0,B1,B0:S9_31..S9_0); // gestion du bus trois états S31..S0 S31..S0 = S1_31..S1_0:addsub; S31..S0 = S2_31..S2_0:mul; S31..S0 = S3_31..S3_0:div; S31..S0 = S4_31..S4_0:shift; S31..S0 = S5_31..S5_0:and; S31..S0 = S6_31..S6_0:or; S31..S0 = S7_31..S7_0:xor; S31..S0 = S8_31..S8_0:xnor; S31..S0 = S9_31..S9_0:sext; // gestion des flags setN=addsub+and+or+xor+xnor; setZ=addsub+mul+div+and+or+xor+xnor; setVC=addsub; N=addsub*N1+and*N2+or*N3+xor*N4+xnor*N5; Z=addsub*Z1+mul*Z2+div*Z3+and*Z4+or*Z5+xor*Z6+xnor*Z7; end module
Figure VI.9. Écriture SHDL de l’UAL du processeur CRAPS. communiquent entre eux. CRAPS utilise trois bus de 32 bits (figure VI.10) : •
Le bus A, monodirectionnel : seul le bloc des registres peut y déposer une valeur. Il est relié à une des entrées de l’UAL, mais une dérivation part également vers le sous-système mémoire, où il servira de bus d’adresses. Il porte bien son nom : bus A comme bus d’adresses.
3. Les bus du processeur CRAPS
197
clk
registres 32
32
Bus A
32
Bus B
Bus D
vers sous−système d’exceptions
cmd_ual[5..0]
UAL
(OE = excep_mode)
6 OE = /read * /excep_mode
Bus D bus de données
Bus A bus d’adresses
vers mémoire centrale (OE = read * /excep_mode)
Figure VI.10. Les bus du processeur CRAPS. •
Le bus B, monodirectionnel. Il sert uniquement à fournir un second argument pour l’UAL.
•
Le bus D, bi-directionnel. C’est sur lui que circulent toutes les données : bus D pour bus de données. Sa valeur est lue par le bloc des registres pour stockage dans un registre au front d’horloge, et/ou par le sous-système mémoire pour écriture en mémoire.
Le bus D est celui dont la gestion est la plus délicate. En effet, trois sources différentes peuvent y déposer une valeur : 1
l’UAL, pour communiquer le résultat de ses calculs. Son écriture sur le bus est commandée par : OE=/read*/excep_mode.
2
le sous-système mémoire, qui y déposera la valeur des données lues en mémoire. Ce dépôt sera commandé par OE=read*/excep_mode.
3
le sous-système des exceptions, qui pourra y déposer le numéro de l’exception dans certaines circonstances. Ce dépôt est commandé par OE=excep_mode.
Même si nous ne connaissons pas encore le rôle exact des signaux excep_mode et read, il est clair que les trois équations des signaux OE de dépôt de valeurs sur le bus D sont mutuellement exclusives ; aucun court-circuit n’est donc possible.
CHAPITRE VI. CONSTRUCTION DU PROCESSEUR 32 BITS CRAPS
198
4. Structure du sous-système mémoire Le sous-système mémoire ne fait pas partie du processeur à proprement parler. Sur nos ordinateurs de bureau, il est extérieur au processeur, placé sur la carte-mère, sous forme de barrettes mémoire et de circuits de décodages. Mais c’est un élément central dont l’importance est telle que nous allons étudier en détail celui qui est associé à la cartographie mémoire que nous avons vue au chapitre consacré à la programmation de CRAPS. On expliquera notamment les phénomènes suivants, visibles du point de vue du programmeur : •
les adresses sont des numéros d’octets (alors qu’on manipule des mots de 32 bits, de 4 octets de large),
•
la ROM commence à l’adresse 0x0, la RAM à l’adresse 0x1000000, etc.
•
certaines adresses semblent des ’miroirs’ d’autres : par exemple 0x400000 et 0x400010,
•
l’accès à des adresses non implantées provoque l’exception bus_error.
La cartographie mémoire exacte qu’on souhaite mettre en oeuvre est donnée figure VI.11. On y retrouve les adresses vues dans les exemples de programmation : RAM en 0x1000000, timer en 0x400000, entrées/sorties en 0x600000.
[0000 0000 − 0000 FFFF]
[0040 0000 − 0040 000B] [0060 0000 − 0060 000F]
[0100 0000 − 01FF FFFF]
ROM (64K)
commande #1 commande #2 IT timer
0040 0000
données
0060 0000
direction ITs memorisees sens front ITs
0060 0004
0040 0004 0040 0008
timer entrées / sorties
0060 0008 0060 000C
RAM (16 Mo)
Figure VI.11. Cartographie mémoire de CRAPS. La structure du sous-système mémoire qui met en oeuvre cette cartographie est donnée figure VI.12. Décodage des adresses Le décodeur situé sur la gauche du schéma prend en entrée les 16 bits de poids faibles du bus d’adresse et produit les signaux de décodage ROM, RAM, etc. Chacun de ces signaux correspond au décodage de la plage mémoire associée au boîtier correspondant. Précisément :
4. Structure du sous-système mémoire
199
Figure VI.12. Structure du sous-système mémoire. 1
la ROM de 64Ko occupe le segment [0x00000000,0x00010000[. Le signal ROM qui indique son décodage vaut 1 lorsque les 16 bits de poids forts de l’adresse sont tous à 0: ROM = /abus31*/abus30*...*/abus16
2
la RAM de 16Mo occupe le segment [0x01000000,0x02000000[. Le signal RAM qui décode ces adresses vaut 1 lorsque les 8 bits de poids forts de l’adresse sont égaux à 000000012 : RAM = /abus31*/abus30*...*/abus25*abus24
CHAPITRE VI. CONSTRUCTION DU PROCESSEUR 32 BITS CRAPS
200 3
le timer occupe le segment [0x00400000,0x0040000C[. Par souci de simplification, on ne va effectuer qu’un décodage partiel de ces adresses en ne détectant que le fait que les 12 bits de poids forts valent 0x004. Cela conduit à l’équation du signal timer suivante : timer = /abus31*/abus30*...*/abus23*abus22*/abus21*/abus20
4
le circuit d’entrées/sorties occupe le segment [0x00600000,0x00600010[. De façon analogue au timer, on ne détectera que le fait que les 16 bits de poids forts sont égaux à 0x0060. Cela conduit à l’équation du signal io suivante : io = /abus31*/abus30*...*/abus23*abus22*abus21*/abus20
5
toutes les autres valeurs des 12 bits de poids forts activeront la ligne bus_error, qui indiquera donc que l’adresse présente sur le bus ne correspond à aucun circuit implanté dans l’espace d’adressage. Ce signal devra déclencher une exception de niveau 14.
Lecture en mémoire Les sorties du décodeur sont reliées aux boîtiers qui leurs correspondent, expliquant ainsi pourquoi chacun d’eux est activé par les adresses indiquées sur la cartographie mémoire figure VI.11. On notera que l’entrée de sélection CS du décodeur est reliée au OU des signaux read et write, c’est à dire qu’aucun des quatre circuits ne sera activé si aucune lecture ni écriture n’est demandée par le processeur. Si une lecture est demandée (read = 1) et que le processeur n’est pas en train de gérer une exception, alors : 1
OE=1 (en bas à gauche du schéma). On avait déjà étudié l’équation de OE : OE = /excep_mode*read, qui indique qu’un des circuits mémoire va déposer une
valeur sur le bus. 2
CS=1 et OE=1 pour le boîtier mémoire activé par l’adresse présente, en vertu du ET situé devant l’entrée OE de chaque circuit.
Ainsi, le circuit mémoire sélectionné par l’adresse va déposer une valeur sur le bus D. Écriture en mémoire Si une lecture est demandée (write = 1) alors on voit que, pour le circuit activé par l’adresse présente, CS = 1, OE = 0 et W = 1 : le circuit va déclencher une écriture à l’adresse présente sur ses entrées ADR. Oubliés, les deux bits de poids faibles de l’adresse ! On va étudier enfin la façon dont les lignes d’adresses arrivent sur les circuits mémoire. On constate d’abord que, nulle part sur le schéma, n’apparaissent les 2 bits de poids faibles de l’adresse, abus1..abus0 ! Par exemple, pour le circuit de ROM, il y a bien 14 lignes d’adresses puisqu’il contient 16K mots, mais ce ne sont pas les bits abus[13..0] qui sont utilisés, mais abus[15..2]. Si par exemple une lecture à l’adresse 0x00000008
4. Structure du sous-système mémoire
201
est effectuée, la ROM verra quant à elle l’adresse 0x0002. En fait, toute adresse entre 0x00000008 et 0x0000000B sera vue de la même façon par le circuit de ROM : accès au mot mémoire numéro 2. Mais il faut se souvenir qu’il est interdit de faire des lectures et des écritures en mémoire à des adresses qui ne sont pas des multiples de 4, sous peine de déclencher l’exception ’erreur d’alignement’ (numéro de type = 10, voir figure V.35). Par contre, c’est autorisé dans le cas des instructions ldub et stb dont on verra plus loin comment elles sont gérées. De la même façon, l’adresse qui est envoyée à la RAM est obtenue par la formule : adr_ram[21..0] = (abus[31..0] − 0x01000000)/ 4 L’adresse avec laquelle la RAM est adressée s’obtient d’abord par une translation par rapport à l’adresse de base 0x01000000, et cette valeur est divisée par 4. Par exemple, un programmeur qui effectue une lecture ou une écriture à l’adresse 0x01000010 va en réalité adresser la RAM au mot mémoire (de 32 bits) d’indice 5 (0x01000010 - 0x01000000) / 4). La RAM n’a jamais entendu parler de l’adresse 0x01000000 et ne pensait sans doute pas qu’il existait des adresses aussi grandes (!) : elle ne connaît que des adresses de 0 à 0xFFFFFF. De même la RAM croit que tous les mots mémoire ont 32 bits de large ; elle serait étonnée d’apprendre que les adresses manipulées par les programmeurs sont des numéros d’octets. C’est encore plus frappant pour les circuits timer et d’entrées/sorties, qui ne possèdent que 2 lignes d’adresse. De leur point de vue il n’existe que 4 mots mémoire, d’indices 0 à 3. C’est le système de décodage qui fait apparaître ces mots aux adresses 0x00400000 à 0x0040000F pour le timer, et 0x00600000 à 0x0060000F pour les entrées/sorties. Finalement, l’écriture SHDL de l’ensemble est donnée figure VI.14. On y fait référence à des modules mémoire ROM et RAM prédéfinis.
5. Structure du circuit d’entrées/sorties On va détailler dans cette partie la structure du circuit d’entrées/sorties et on va montrer comment il peut avoir le mode de fonctionnement qu’on a déjà décrit dans le chapitre consacré à la programmation. Ils sont résumés dans le tableau VI.13. On rappelle que le circuit a l’interface de la figure VI.15. ADR
W
opération
00
0
lire l’état des entrées
00
1
écrire sur les sorties
01
0
lire configuration lignes
01
1
configurer les lignes
Figure VI.13. Spécification de fonctionnement du circuit d’entrées/sorties. Il peut être considéré comme la juxtaposition de 32 circuits de 1 bits ; la structure pour un bit i est montrée en figure VI.16.
202
CHAPITRE VI. CONSTRUCTION DU PROCESSEUR 32 BITS CRAPS module memoire(rst,clk,abus[31..0],dbus[31..0],read,write,excep_mode, timer_out, io[31..0], bus_error) decode_mem(abus[31..16],cs_mem:cs_rom,cs_timer,cs_io,cs_ram,bus_error); cs_mem=read+write; rom16Kx32(abus[15..2],cs_rom,oe_rom,dbus[31..0]); oe_rom=cs_rom*OE; ram4Mx32(abus[23..2],cs_ram,oe_ram,write,dbus[31..0]); oe_ram=cs_ram*OE; timer_pwm(abus[3..2],cs_timer,oe_timer,write,dbus[31..0],timer_out); oe_timer=cs_timer*OE; io(abus[3..2],cs_io,oe_io,write,dbus[31..0],io[31..0]); oe_io=cs_io*OE; OE=/excep_mode*read; end module module decode_mem(adr[15..0],CS:ROM,timer,io,RAM,bus_error) commun=/adr15*/adr14*/adr13*/adr12*/adr11*/adr10*/adr9; ROM=CS*commun*/adr8*/adr7*/adr6*/adr5*/adr4*/adr3*/adr2*/adr1*/adr0; RAM=CS*commun*adr8; timer=CS*commun*/adr8*/adr7*adr6*/adr5*/adr4; io=CS*commun*/adr8*/adr7*adr6*adr5*/adr4; bus_error=CS*/ROM*/RAM*/timer*/io; end module
Figure VI.14. Écriture SHDL du sous-système mémoire.
Figure VI.15. Interface du circuit d’entrées/sorties. La bascule D du haut mémorise le bit dir[i] qui vaut 1 lorsqu’on souhaite que la ligne io[i] soit configurée en sortie et 0 lorsqu’on souhaite qu’elle soit une entrée. La bascule ayant comme entrée de sélection le signal CS*W*/ADR[1]*ADR[0], mémorisera le bit data[i] si et seulement si l’écriture est demandée pour ADR = 01. Du point de vue du programmeur, cela correspond à une écriture à l’adresse 0x600004 comme on l’a expliqué à la section 4. De la même façon, la bascule D du bas mémorise le bit DATA[i], lorsqu’une écriture est demandée pour ADR = 00 ; du point de vue du programmeur cela correspond à une écriture à l’adresse 0x600000. Maintenant, dir[i] gouverne bien le fait que la ligne io[i] est une entrée ou une sortie : si dir[i] = 1, le buffer trois-états de droite fait de io[i] une sortie, dont la valeur est justement le signal out[i], ce qui est le comportement spécifié tableau VI.13. Si dir[i] = 0, le buffer trois-états de droite isole io[i] de out[i] ; si maintenant une lecture est demandée pour ADR = 00, la condition CS*/W*OE*/ADR[1]*/ADR[0] vaut 1
5. Structure du circuit d’entrées/sorties
203
Figure VI.16. Structure du circuit d’entrées/sorties,pour un bit i. et io[i] passe sur la ligne DATA[i]. Du point de vue du programmeur, cela équivaut à une lecture à l’adresse 0x600000 et elle lui retourne l’état des 32 lignes d’entrées/sorties. Enfin, si une lecture est demandée pour ADR[1..0] = 01, le buffer trois-états du bas laisse passer sur DATA[i] la valeur dir[i] : du point de vue du programmeur, une lecture à l’adresse 0x600004 lui renvoie l’état de configuration des entrées/sorties. On a ainsi expliqué tous les aspects du fonctionnement du circuit d’entrées/sorties du tableau VI.13. Les autres aspects relatifs aux exceptions seront décrits section 7. Finalement, l’écriture SHDL de l’ensemble est donnée figure VI.17.
6. Structure du circuit timer/PWM Le circuit timer/PWM est un bel exemple de construction modulaire, par assemblage du module timer/PWM étudié à la section 9 et du module diviseur de fréquence étudié section 10.2. On l’a déjà utilisé du point de vue du programmeur ; sa spécification est redonnée tableau VI.18 et on rappelle son interface figure VI.19. Sur la figure VI.20, les registres de 32 bits situés à gauche fonctionnent de la manière habituelle : celui du haut mémorise le mot de commande #1 lorsqu’une écriture pour ADR = 00 est demandée, ce qui correspond à une écriture en 0x400000 du point de vue du programmeur. Le registre 16 bits en dessous et la bascule D tout en bas mémorisent prediv et start respectivement, lorsqu’une écriture pour ADR = 01 est demandée, soit
CHAPITRE VI. CONSTRUCTION DU PROCESSEUR 32 BITS CRAPS
204
module io(rst,clk,ADR[1..0],CS,OE,W,DATA[31..0],io[31..0]) ENA_IO = CS*W*/ADR1*/ADR0; ENA_DIR = CS*W*/ADR1*ADR0; // écriture dans dir[i] dir[31..0] := DATA[31..0]; dir[31..0].ena = ENA_DIR dir[31..0].clk = clk; dir[31..0].rst = rst; // écriture dans out[i] out[31..0] := DATA[31..0]; out[31..0].ena = ENA_IO; out[31..0].clk = clk; out[31..0].rst = rst; // sortie sur io[i] io[31..0] = out[31..0]:dir[31..0]; // lecture de io[i] OE_IO = CS*/W*OE*/ADR1*/ADR0; DATA[31..0] = io[31..0]:OE_IO; // lecture de dir[i] OE_DIR = CS*/W*OE*/ADR1*ADR0; DATA[31..0] = dir[31..0]:OE_DIR; end module
Figure VI.17. Écriture SHDL du circuit d’entrées/sorties. ADR
W
opération
00
0
lire le registre de commande #1 (P + N)
00
1
écrire le registre de commande #1 (P + N)
01
0
lire le registre de commande #2 (prediv + start)
01
1
écrire le registre de commande #2 (prediv + start)
Figure VI.18. Spécifications du circuit timer/PWM.
Figure VI.19. Interface du circuit timer/PWM. une écriture en 0x400004 pour le programmeur. La valeur de prediv est mise en entrée d’un circuit diviseur de fréquence, qui divise la fréquence de l’horloge générale clk. La sortie du diviseur de fréquence devient l’horloge de comptage d’un module timer identique à celui étudié section 9. On lui transmet également les parties N et P du mot de commande #1 ; par ailleurs son entrée de sélection est reliée au signal start et il ne fonctionne donc que si ce bit est à 1.
6. Structure du circuit timer/PWM
205
Figure VI.20. Structure du circuit timer/PWM. Enfin, une lecture demandée pour ADR = 00 active le premier buffer trois-états du bas et laisse passer la valeur de la commande #1 sur DATA (lecture en 0x400000 pour le programmeur). Une lecture pour ADR = 01 (0x400004 pour le programmeur) donne la valeur de la commande #2. Ainsi, tous les aspects du fonctionnement du circuit timer/PWM spécifiés tableau VI.18 sont expliqués par cette construction. Finalement, l’écriture SHDL de l’ensemble est donnée figure VI.21.
7. Structure du sous-système d’exceptions On va décrire dans cette section la structure complète du sous-ensemble effectuant la gestion des exceptions. On a déjà décrit en détails (section 10) dans quelles cironstances une exception (trap ou interruption) était déclenchée, puis prise en compte. Plusieurs aspects de cette prise en compte sont effectués par le matériel : 1
la détermination de l’exception qui a le niveau de priorité le plus élevé.
2
la mémorisation des interruptions timer et entrées/sorties, le temps qu’elles soient prises en compte.
3
la possibilité de remettre à zéro par programme les interruptions timer et entrées/sorties mémorisées.
4
la possibilité de lire par programme les interruptions mémorisées.
La figure VI.22 montre le schéma complet qui prend en compte tous ces aspects.
206
CHAPITRE VI. CONSTRUCTION DU PROCESSEUR 32 BITS CRAPS module timer_pwm(rst,clk,ADR[1..0],CS,OE,W,DATA[31..0],out) ENA_CMD1 = CS*W*/ADR1*/ADR0; ENA_CMD2 = CS*W*/ADR1*ADR0; // écriture dans P[i] P[15..0] := DATA[31..16]; P[15..0].ena = ENA_CMD1; P[15..0].clk = clk; P[15..0].rst = rst; // écriture dans N[i] N[15..0] := DATA[15..0]; N[15..0].ena = ENA_CMD1; N[15..0].clk = clk; N[15..0].rst = rst; // écriture dans prediv[i] prediv[3..0] := DATA[19..16]; prediv[3..0].ena = ENA_CMD2; prediv[3..0].clk = clk; prediv[3..0].rst = rst; // écriture dans start start := DATA[0]; start.ena = ENA_CMD2; start.clk = clk; start.rst = rst; //diviseur de fréquence divfreq(rst,clk,prediv[3..0]: h); // timer/PWM pwm(rst,h,start,P[15..0],N[15..0]: out); // lecture du mot de commande #1 OE_CMD1 = CS*/W*OE*/ADR1*/ADR0; DATA[31..16] = P[15..0]:OE_CMD1; DATA[15..0] = N[15..0]:OE_CMD1; // lecture du mot de commande #2 OE_CMD2 = CS*/W*OE*/ADR1*ADR0; DATA[19..16] = prediv[3..0]:OE_CMD2; DATA[0] = start:OE_CMD2; end module
Figure VI.21. Écriture SHDL du circuit timer/PWM. Détermination de l’exception la plus prioritaire Cette détermination est faite dans la partie droite du schéma de la figure VI.22. On y trouve le circuit encodeur de priorité, qui reçoit les signaux de détection des différentes exceptions. On retrouve les numéros de priorité de chacune d’elles : 14 pour bus_error, 13 pour instr_priv (viol de privilège instruction), etc. L’occurrence d’une interruption timer est signalée par timer_int et a la priorité 8 ; les interruptions d’entrées/sorties sont signalées par le vecteur io_int[31..0] ; elles sont toutes reliées à un OR pour en faire la synthèse au niveau 7. La sortie de l’encodeur de priorité donne le numéro de l’exception la plus prioritaire et cette valeur est ensuite comparée au niveau courant d’exécution PIL, valeur issue d’un champ du registre d’état %psr. Une demande de prise en compte d’exception excep_req n’est faite que si le numéro de l’exception la plus prioritaire est plus élevé que le numéro d’exécution courant.
7. Structure du sous-système d’exceptions
207
Figure VI.22. Structure du sous-système d’exceptions. Masquage des interruptions On se souvient (section 10) que les interruptions sont ignorées lorsque le bit ET du registre %psr est à 0. C’est bien le cas sur le schéma : les deux sources d’interruption timer_out (en provenance du circuit timer) et io[31..0] (en provenance du circuit d’entrées/sorties) passent d’abord par un AND avec le signal ET ; si ET = 0 l’occurrence de l’interruption est purement et simplement ignorée. Mémorisation des interruptions Les interruptions timer et entrées/sorties ont besoin d’être mémorisées, car ce sont des occurrences fugitives de fronts montants ou descendants. Des latchs RS asynchrones sont alors le moyen le plus adapté : le vecteur de 32 latchs du haut mémorise les interruptions sur les entrées/sorties et le latch du bas mémorise une interruption timer. Le signal timer_out, après passage au travers du AND avec ET, va sur l’entrée S du latch : l’arrivée d’un front montant sur timer_out va se traduire par la mémorisation
CHAPITRE VI. CONSTRUCTION DU PROCESSEUR 32 BITS CRAPS
208
d’un 1 dans le latch. De la même façon, chaque signal io[i] passe d’abord au travers du AND, puis au travers d’un XOR qui va éventuellement l’inverser, selon la valeur du signal dir_ioint[i] qui permet de régler le sens du front (montant ou descendant) qui va déclencher l’interruption. Commandes de gestion des interruptions mémorisées Le tableau de la figure VI.23 résume toutes les opérations qui doivent être rendues possibles par programme pour gérer les interruptions timer et entrées/sorties.Chacune d’elle est mise en place par un élément du schéma général. adresse
read
write
opération
0x600008
1
0
lire interruptions entrées/sorties mémorisées
0x600008
0
1
raz des interruptions entrées/sorties mémorisées
0x60000C
1
0
lire sens du front d’interruption (0 = front montant)
0x60000C
0
1
écrire sens du front d’interruption
0x400008
1
0
lire bit d’interruption timer
0x400008
0
1
raz bit d’interruption timer
Figure VI.23. Commandes de gestion des interruptions timer et entrées/sorties. La lecture des interruptions d’entrées/sorties mémorisées est faite par le buffer trois-états du bas du schéma qui a pour entrée io_int[31..0]. Cette lecture a lieu lorsque cs_io*read*/excep_mode*abus[3]*/abus[2], c’est à dire pour une adresse qui active cs_io (le segment [0x600000,0x600010[) et qui se termine par 10XX : 0x600008. La remise à zéro des interruptions d’entrées/sorties mémorisées est réalisée par le registre dont les sorties se nomment reset_ioint[31..0] ; elles vont en effet sur les entrées R des latchs de mémorisation des interruptions d’entrées/sorties. Cette écriture a lieu lorsque cs_io*write*/excep_mode*abus[3]*/abus[2], c’est à dire à la même adresse que la lecture précédente : 0x600008. Les sens des fronts des interruptions d’entrées/sorties sont configurables individuellement par écriture dans le registre dont les sorties se nomment dir_ioint[31..0] ; elles commandent l’inversion éventuelle des signaux en provenance du circuit d’entrées/sorties io[31..0]. Cette écriture a lieu pour cs_io*write*/excep_mode*abus[3]*abus[2], c’est à dire 0x60000C. Le bit d’interruption timer timer_int arrive sur un des buffers trois-états du bas ; il est lu pour cs_timer*write*/excep_mode*abus[3]*/abus[2], c’est à dire 0x400008. La remise à zéro du latch de mémorisation de cette interruption se fait par écriture dans la bascule dont la sortie se nomme reset_tint, à l’adresse 0x400008. Enfin, la valeur du niveau d’exception le plus élevé est placée automatiquement sur le bus dès que excep_mode = 1 ; on se souvient (figure VI.10) qu’il s’agit de la troisième source d’écriture sur le bus de données dbus, qui se produira lorsqu’une exception sera effectivement prise en compte et qu’il s’agira d’obtenir son niveau pour élever le niveau d’exécution du processeur.
8. Séquencement des instructions
209
8. Séquencement des instructions 8.1. Problématique du séquencement Nous avons jusqu’ici décrit ce qui s’appelle la micromachine, c’est à dire la réunion des différents sous-systèmes registres, mémoire, UAL et exceptions. Cette micromachine se manoeuvre avec des microcommandes, dont on peut donner la liste complète pour la machine CRAPS : •
read et write pour l’accès à la mémoire.
•
areg, breg et dreg pour l’accès aux registres.
•
cmd_ual pour la commande de l’opération UAL.
•
excep_mode pour obtenir le niveau de priorité de l’exception en cours de traitement.
C’est tout ! Tous les autres signaux, abus, bbus, dbus, etc. ne sont pas des commandes ; ce sont des éléments de la micromachine, qui sont indirectement modifiés par les microcommandes listées ci-dessus. On appelle séquenceur le sous-système du processeur chargé de générer les bonnes séquences de microcommandes en fonction de l’instruction en cours d’exécution. Il fonctionne avec le reste du processeur selon l’organisation de la figure VI.24.
microcommandes
séquenceur
%ir
micromachine
N,Z,V,C excep_req
CLK
Figure VI.24. Le séquenceur envoie des microcommandes en fonction de l’instruction en cours, de la valeur des flags et de l’éventuelle présence d’une interruption. Le séquenceur est l’âme du processeur : sans lui la micromachine reste inerte. À chaque cycle d’horloge, le séquenceur présente une nouvelle combinaison de microcommandes, afin de poursuivre l’exécution de l’instruction en cours. On peut le comparer à un pianiste et les microcommandes à des touches de piano : à chaque tempo il effectue un accord, c’est à dire qu’il actionne plusieurs microcommandes en parallèle. Il effectue le séquencement des instructions l’une après l’autre, le registre %pc lui servant à connaître l’emplacement de la prochaine instruction à séquencer.
210
CHAPITRE VI. CONSTRUCTION DU PROCESSEUR 32 BITS CRAPS
Le début du séquencement d’une instruction s’appelle la phase FETCH ; il consiste à aller charger dans le registre %ir l’instruction en mémoire centrale située à l’adresse %pc. Cette partie initiale du séquencement est fixe, car le séquenceur ne sait pas encore à quelle instruction il va avoir à faire. À l’issue de cette phase, l’instruction est disponible dans %ir et le séquenceur y a accès. Il peut ainsi catégoriser l’instruction parmi des groupes dont le séquencement va être commun. Pour les instructions de branchement conditionnel, le séquencement correct nécessite qu’il dispose de la valeur des flags. Il ne faut pas oublier les exceptions : lorsque l’une d’elle se produit (excep_req = 1), elle affecte le séquencement d’une manière qui sera décrite plus loin.
8.2. Séquenceurs câblés et microprogrammés Le séquenceur est un circuit séquentiel synchrone parfaitement classique et on pourra le réaliser en utilisant les méthodes présentées au chapitre 3, une fois qu’on aura spécifié exactement son graphe d’états. On utilisera alors des portes et des bascules pour réaliser la machine de changements d’états dont les entrées seront les signaux %ir, excep_req et les flags ; un transcodeur générera les microcommandes qui seront les sorties du circuit. On parle dans ce cas de séquenceur câblé ; c’est bien sûr la méthode qui assure le séquencement le plus rapide et qui occupe le moins de surface sur la puce. Pour les processeurs complexes et en particulier pour les processeurs CISC, le graphe peut devenir tellement complexe qu’il est préférable d’utiliser une autre approche dite de séquenceur microprogrammé dont l’étude détaillée sort du cadre de cet ouvrage. On peut tout de même dire qu’alors, le séquenceur est lui même un petit processeur qui exécute des sortes de routines associées à chaque instruction, contenues dans une mémoire interne appelée mémoire de microprogramme.Cette approche est plus souple que l’approche câblée car on peut facilement modifier le séquenceur en modifiant les microprogrammes, mais elle ajoute bien sûr un surcoût en temps et en espace. Dans le cas de CRAPS, le graphe d’états est suffisamment simple pour permettre sans difficulté un séquenceur câblé ; cela est essentiellement dû au fait que son jeu d’instructions de type RISC est particulièrement réduit et régulier. Le graphe d’états complet est trop grand pour être présenté d’un seul morceau et il sera donné par parties, d’abord pour la phase FETCH commune à toutes les instructions, puis pour chacun des groupes d’instructions. Il s’agit d’un graphe de MEALY, qui est plus adapté qu’un graphe de MOORE car, en fournissant au plus tôt les valeurs des sorties, les parties asynchrones comme l’UAL et la mémoire peuvent commencer leur travail plus tôt.
8.3. Séquencement de la phase FETCH La phase FETCH forme le début commun au séquencement de toutes les instructions. Elle consiste à lire en mémoire à l’adresse contenue dans %pc, puis à stocker la valeur lue dans le registre %ir (figure VI.25). L’état de départ s’appelle fetch ; c’est également l’état vers lequel on retourne à la fin de l’exécution de toute instruction. On va de fetch vers decode seulement si excep_req=0, c’est à dire si aucune exception n’est en attente de traitement. Sinon il faut aller vers le sous-graphe de gestion des exceptions qui sera décrit plus loin. Si donc le processeur n’a pas d’exception en attente, il doit aller lire le mot mémoire
8. Séquencement des instructions
211
excep_req = 0 / read=1, write=0, ual=000000 (ADD), excep_mode=0 areg=62, breg=0, creg=63
op = 00 op = 01
fetch
vers sous−graphe de séquencement de SETHI et des instructions de branchement vers sous−graphe de séquencement de CALL
decode op = 10
except_req = 1 vers sous−graphe de séquencement des exceptions
op = 11
vers sous−graphe de séquencement des instructions de calcul vers sous−graphe de séquencement des instructions d’accès memoire
Figure VI.25. Sous-graphe de séquencement de la phase FETCH. situé à l’adresse contenue dans le registre %pc (= %r62). Pour cela, il faut se débrouiller pour placer la valeur de %pc sur le bus A : c’est ce qui est fait en plaçant 62 sur areg. On place également une valeur quelconque sur breg et on commande une opération quelconque sur l’UAL, qui toutefois ne modifie pas les flags (ici ADD). read est également forcé à 1, ce qui déclenche immédiatement (avant le prochain front d’horloge) la lecture en mémoire à l’adresse pc. À l’arrivée du prochain front d’horloge, si la période a été assez longue, la mémoire aura placé son résultat sur le bus D, et il sera écrit dans le registre %ir (= %r63), puisqu’on a placé 63 sur la commande dreg. Si la mémoire est un peu lente par rapport à la fréquence de l’horloge, on peut rajouter un état d’attente intermédiaire entre les états fetch et decode. C’est technique pourra être utilisée également avec l’UAL lors des calculs de multiplication et de division qui prennent un temps important. Arrivé à l’état decode, le séquencement va être orienté vers des sous-graphes différents selon le groupe auquel appartient l’instruction courante, maintenant contenue dans %ir. Si on se réfère à la structure des instructions vue à la section 2.4, ce groupe est très simplement identifié par le champ op constitué des deux bits de poids forts de %ir.
8.4. Séquencement d’une instruction de calcul Les instructions dont le séquencement est le plus simple sont les instructions de calcul arithmétique et logique. Elles sont repérée à partir de l’état decode par le fait que le champ op de %ir vaut 102. (figure VI.26). Il y a néanmoins deux cas, selon que le deuxième argument de l’instruction est une valeur immédiate (comme dans addcc %r1, 5, %r2) ou un registre (comme dans addcc %r1, %r2, %r3). Ces deux cas sont distingués par le bit 13 de %psr, comme on le voit sur le graphe. Instruction de calcul avec une constante comme deuxième argument Si %ir[13]=1, le deuxième argument est une constante immédiate sur 13 bits, qui est logée à l’intérieur même de l’instruction %ir. On va alors l’extraire de %ir et la copier dans le registre temporaire %tmp1, puis faire ensuite l’opération entre le registre de numéro rs1 et tmp1, pour enfin mettre le résultat dans le registre de numéro rd. rs1 et rd sont
CHAPITRE VI. CONSTRUCTION DU PROCESSEUR 32 BITS CRAPS
212
op = 10, %ir[13]=1 / read=0, write=0, excep_mode=0 cmd_ual=SIGNEXT13 areg=63, breg=0, dreg=32
...
fetch
decode op = 10, %ir[13]=0 / read=0, write=0, oe_num=01 cmd_ual=op3 areg=rs1, breg=rs2, dreg=rd
null / read=0, write=0, oe_num=01 cmd_ual=ADD areg=62, breg=35, dreg=62
pc+4
calc_imm
null / read=0, write=0, oe_num=01 cmd_ual=op3 areg=rs1, breg=32, dreg=rd
Figure VI.26. Sous-graphe de séquencement d’une instruction de calcul. des champs de 5 bits du registre %ir qui contiennent les numéros des registres traités par l’instruction (voir figure 2.4). L’extraction de la constante de 13 bits est effectuée entre l’état decode et l’état calc_imm, par envoi du registre %ir sur le bus A (areg = 63) et par application de la commande cmd_ual = SIGNEXT13 sur l’UAL. On a déjà vu (section 2.2) que cette opération avait pour effet de ne garder que les 13 bits de poids faibles de la valeur (ici %ir) et d’effectuer une extension du signe. La constante ainsi ’nettoyée’, est copiée dans %tmp1 (dreg = 32) au prochain front d’horloge. Avant de revenir à l’état fetch pour exécuter l’instruction suivante, il faut incrémenter le registre %pc de 4, pour qu’il pointe sur l’instruction suivante. C’est ce qui est fait à partir de l’état pc+4, qui effectue une addition sans modification des flags (cmd_ual = ADD) entre la valeur de %pc (areg = 62) et la valeur du registre %r35 (breg = 35) dont on se rappelle qu’il vaut toujours 4 (voir section 1). Le résultat est stocké dans %pc (dreg = 32) avant de retourner à l’état fetch, prêt pour l’exécution de l’instruction suivante qui se trouve en séquence. Instruction de calcul avec un registre comme deuxième argument Lorsque le deuxième argument de l’instruction de calcul est un registre (%ir[13] = 0), il n’est pas nécessaire d’avoir cet état intermédiaire calc_imm ; on peut directement demander l’opération entre les registres, ce qui est fait dans la transition entre decode et pc+4 : cmd_ual = op3, areg = rs1, breg = rs2, dreg = rd. On incrémente ensuite %pc de 4 pour passer à l’instruction suivante.
8.5. Séquencement des instructions d’accès mémoire Les instructions d’accès mémoire ont des arguments similaires à ceux des instructions de calcul ; ils partagent d’ailleurs le même format d’instruction (format 3, figure V.8). Séquencement des instructions ld et ldub La figure VI.27 montre le sous-graphe de séquencement de ces deux instructions. Le séquencement de ld est simple; le séquencement de ldub l’est moins et occupe l’essentiel du bas de la figure. Le début du séquencement est le même que pour une instruction de calcul. L’adresse
8. Séquencement des instructions
213 op = 11, op3 = ld+ldub, %ir[13] = 1 / read=0, write=0, excep_mode=0 cmd_ual=SIGNEXT13 areg=63, breg=0, dreg=32
excep_req = 0 / ...
fetch
ld_imm
decode op = 11, op3 = ld+ldub, %ir[13] = 0 / read=0, write=0, excep_mode=0 cmd_ual=ADD areg=rs1, breg=rs2, dreg=32
null / read=0, write=0, excep_mode=0 cmd_ual=ADD areg=62, breg=35, dreg=62
pc+4
op3 = ld / read=1, write=0, excep_mode=0 cmd_ual=ADD areg=32, breg=0, dreg=rd
null / read=0, write=0, excep_mode=0 cmd_ual=ADD areg=rs1, breg=32, dreg=32
read
op3 = ldub, op3 = ldub, op3 = ldub, op3 = ldub, %tmp1[1,0]=11 / %tmp1[1,0]=10 / %tmp1[1,0]=01 / %tmp1[1,0]=00 / null / read=1, write=0 read=1, write=0 read=1, write=0 read=1, write=0 read=0, write=0 excep_mode = 0, excep_mode = 0, excep_mode = 0, excep_mode = 0, excep_mode = 0, cmd_ual=ADD cmd_ual=ADD cmd_ual=ADD cmd_ual=ADD cmd_ual=SRL areg=32, breg=0 areg=32, breg=0 areg=32, breg=0 areg=32, breg=38 areg=32, breg=0 dreg=32 dreg=32 dreg=32 dreg=32 dreg=rd
byte0 null / read=0, write=0 excep_mode = 0, cmd_ual=SRL areg=32, breg=38 dreg=rd
byte1 null / read=0, write=0 excep_mode = 0, cmd_ual=SLL areg=32, breg=36 dreg=32
byte2 null / read=0, write=0 excep_mode = 0, cmd_ual=SLL areg=32, breg=37 dreg=32
byte3
null / read=0, write=0 excep_mode = 0, cmd_ual=SLL areg=32, breg=38 dreg=32
ldshift
Figure VI.27. Sous-graphe de séquencement des instructions ld et ldub. de la donnée à lire est le résultat d’une addition, dont le deuxième terme peut être un registre (comme dans ld [%r1+%r2], %r3) ou une constante immédiate (comme dans ldub [%r1-5], %r2). Cette différence est également distinguée par le bit %ir[13], ce qui conduit aux deux branches à partir de decode. Une fois en read, l’adresse de la donnée à lire en mémoire est dans le registre %tmp1 (numéro 32). À partir de read, si l’instruction est ld et non ldub, les choses sont simples : lire le mot en mémoire et le stocker dans le registre de numéro rd, ce qui est fait entre read et pc+4 ; il ne reste plus qu’à incrémenter %pc de 4. Si l’instruction est ldub, il va falloir lire le mot mémoire de 32 bits situé à l’adresse contenue dans %tmp1 et isoler parmi les 4 octets qui composent ce mot, celui qui est désigné par l’instruction ldub. On rappelle (voir annexe B) que ldub place dans les 8 bits de poids faibles du registre destination l’octet d’adresse désignée et force à 0 les 24 bits de poids forts. On rappelle également que lors d’un accès mémoire, les circuits mémoire ne reçoivent pas les 2 bits de poids faibles de l’adresse (voir section 4) et par conséquent lisent 4 octets à la fois. Si on considère par exemple la situation de la figure VI.28 et qu’on souhaite lire l’octet E6 situé à l’adresse 0x1000005, la demande de lecture à cette adresse va lire tout le mot mémoire de 32 bits 0x7AE6329F qui entoure cet octet, à partir de l’adresse 0x1000004 qui est le multiple de 4 le plus petit avant 0x1000005. C’est cette lecture de 32 bits qui est faite entre l’état read et les états byte0, byte1, etc. Le choix de passer vers byte0, byte1, etc. est fait à partir de la valeur des deux bits de
214
CHAPITRE VI. CONSTRUCTION DU PROCESSEUR 32 BITS CRAPS
7A
E6
1000004
32
9F 1000008
Figure VI.28. Situation de lecture pour l’instruction ldub. L’adresse lue étant 0x1000005, on souhaite isoler l’octet E6 du reste du mot. poids faibles de l’adresse contenue dans %tmp1. Cela implique d’ailleurs que le schéma de la figure VI.24 a été légèrement simplifié et qu’il y a en réalité les deux signaux %tmp[1,0] qui vont également de la micromachine vers le séquenceur, nécessaires dans les instructions ldub et stb pour repérer l’octet à isoler. Dans le cas de ldub, cette isolation de l’octet sur les 8 bits de poids faibles du registre destination, avec remplissage à gauche de 24 zéros, est réalisée à l’aide des commandes SRL et SLL de l’UAL. Pour notre exemple, si on veut extraire E6 du mot 7AE6329F, il suffit de le décaler à gauche de 8 positions : E6329F00 (8 zéros entrent par la droite), puis de le décaler à droite de 24 position : 000000E6 (24 zéros entrent par la gauche). Pour faire ces différents décalages, il est nécessaire de disposer des constantes 8, 16 et 24. C’est dans ce but qu’on a fait en sorte que ces constantes soient les valeurs des registres %r36, %r37 et %r38 respectivement (voir section 1). À partir de byte0, seul un décalage à droite de 24 positions est nécessaire. À partir de byte1, byte2 et byte3, un décalage à gauche préalable de 8, 16 et 24 positions (respectivement) est nécessaire. Séquencement des instructions st et stb La figure VI.29 montre le sous-graphe de séquencement de ces deux instructions. Après l’état decode, il y a à nouveau la bifurcation entre les instructions avec une valeur immédiate (telle que st %r1, [%r2+12]) et celles sans constante immédiate (telle que stb %r1, [%r2+%r4]). Comme pour ld/ldub, on arrive à l’état w_read après avoir calculé dans %tmp1 (= %r32) la somme de ces deux arguments, qui forme l’adresse où doit être effectuée l’écriture. Dans le cas d’une écriture standard stb, on va directement de l’état w_read à l’état pc+4 en effectuant la commande d’écriture write=1. On place l’adresse d’écriture sur le bus A avec areg=32 (=%tmp1); on place la valeur à écrire sur le bus D en la faisant venir sur le bus B (breg=rd) puis en lui faisant traverser l’UAL sans changement avec la commande NOPB. Après l’écriture en mémoire, qui est supposée durer ici un seul cycle d’horloge, on passe à l’état pc+4 pour le passage à l’instruction suivante. Le cas d’une écriture dans un octet (stb) est beaucoup plus complexe et inefficace, car on a pris le parti dans l’architecture de CRAPS de ne pas faciliter cette instruction par des connexions spécialisées entre la mémoire et le bus D. On se rappelle que cette écriture doit se traduire par la modification d’un seul octet en mémoire, c’est à dire la modification de 8 bits à l’intérieur d’un mot mémoire de 32 bits, tous les autres bits restants inchangés. La seule façon de faire consiste à : 1.
lire à l’adresse %tmp1 le mot de 32 bits dans lequel se situe l’octet à modifier et le placer dans le registre temporaire %tmp2.
2.
remplacer au sein de %tmp2 les 8 bits concernés par les 8 bits de poids faibles du registre à écrire.On utilisera pour cela les commandes INSBYTE0, etc. de l’UAL, spécialement
8. Séquencement des instructions
215 op = 11, op3 = st+stb, %ir[13] = 1 / read=0, write=0, excep_mode=0 cmd_ual=SIGNEXT13 areg=63, breg=0, dreg=32
excep_req = 0 / ...
fetch
decode
null / read=0, write=0, excep_mode=0 cmd_ual=ADD areg=62, breg=33, dreg=62
pc+4
null / read=0, write=1 excep_mode=0 cmd_ual=NOPB areg=32, breg=33, dreg=0
st_imm
op = 11, op3 = st+stb, %ir[13] = 0 / read=0, write=0, excep_mode=0 cmd_ual=ADD areg=rs1, breg=rs2, dreg=32
null / read=0, write=0, excep_mode=0 cmd_ual=ADD areg=rs1, breg=32, dreg=32
op3 = st / read=0, write=1, excep_mode=0 w_read cmd_ual=NOPB areg=32, breg=rd, dreg=0 op3 = stb / read=1, write=0, excep_mode=0 cmd_ual=ADD areg=32, breg=0, dreg=33
insbyte op3 = stb, tmp1[1..0]=00 / read=0, write=0 excep_mode=0 cmd_ual=INSBYTE0 areg=rs2, breg=33, dreg=33
op3 = stb, tmp1[1..0]=10 / read=0, write=0 excep_mode=0 cmd_ual=INSBYTE2 areg=rs2, breg=33, dreg=33 op3 = stb, tmp1[1..0]=01 / read=0, write=0 op3 = stb, tmp1[1..0]=11 / excep_mode=0 read=0, write=0 cmd_ual=INSBYTE1 excep_mode=0 areg=rs2, breg=33, dreg=33 cmd_ual=INSBYTE3 areg=rs2, breg=33, dreg=33
write
Figure VI.29. Sous-graphe de séquencement des instructions st et stb. conçues à cet effet. 3.
réécrire tout le mot de 32 bits %tmp2 à l’adresse d’origine, toujours contenue dans %tmp1.
L’étape 1est réalisée entre les états w_read et insbyte, par lancement d’une commande de lecture à l’adresse %tmp1 (areg=32 et read=1) et stockage du résultat dans le registre %tmp2 (=%r33). On l’aura compris, c’est tout un mot de 32 bits qui est lu, c’est à dire 4 octets. L’étape 2 est réalisée entre insbyte et write, avec 4 chemins possibles selon le numéro de l’octet à modifier ; ce numéro est constitué des deux bits de poids faibles de l’adresse %tmp1. L’étape 3 de réécriture est faite entre write et pc+4 : placement de l’adresse %tmp1 sur le bus A (areg=32) et placement de la donnée à écrire %tmp2 sur le bus D en la plaçant sur le bus B (breg=33) et en appliquant la commande NOPB pour lui faire traverser l’UAL.
8.6. Séquencement des instructions de branchement Avec les instructions de branchement il n’y a que deux cas possibles : soit le branchement doit être fait, auquel cas il faut ajouter à la valeur de %pc le déplacement, préalablement multiplié par 4 ; soit le branchement ne doit pas être fait auquel cas il faut passer à l’instruction suivante en ajoutant 4 à %pc. Ces deux cas correspondent aux deux flèches qui partent de l’état decode ; le
CHAPITRE VI. CONSTRUCTION DU PROCESSEUR 32 BITS CRAPS
216
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
dispx4
null / read=0, write=0 excep_mode=0 cmd_ual=SLL areg=32, breg=34, dreg=32
branch
Figure VI.30. Sous-graphe de séquencement des instructions de branchement.
8.7. Réalisation du séquenceur Tous les sous-graphes de séquencement n’ont pas été présentés ; ils utilisent tous les techniques qui viennent d’être présentées. Le lecteur est encouragé à les réaliser à titre d’exercice. On pourrait également concevoir sans difficultés le circuit séquentiel synchrone, de type MEALY, qui implémente le séquenceur de CRAPS. On utiliserait pour cela les techniques présentées au chapitre 3 ; il est d’une complexité modeste, avec moins de 50 états et ses quelques signaux d’entrées et de sorties. Son étude détaillée serait lourde et d’un intérêt limité ; il a été effectivement mis en oeuvre sur une carte à base de FPGA XILINX Spartan 3. Il fonctionne à une fréquence d’horloge de 25 MHz ce qui lui donne une puissance de calcul raisonnable, mais elle pourrait être grandement augmentée par l’utilisation de techniques avancées qui sortent du cadre de cet ouvrage.
9. Exercices corrigés
9. Exercices corrigés
217
9.1. Exercice 1 : décodage d’adresse Énoncé Modifier le câblage du sous-système mémoire de CRAPS pour que la RAM soit vue par le programmeur à partir de l’adresse 0x02000000. Solution Il ne s’agit ici que de modifier le signal de décodage RAM produit par le sous-module decod_mem.
On imposait que les 8 bits de poids forts de l’adresse soient : 000000012, c’est à dire une adresse de la forme 0000 0001 xxxx xxxx xxxx xxxx xxxx xxxx, c’est à dire encore en hexadécimal une adresse de l’intervalle [0x01000000,0x01FFFFFF].En notation SHDL, on a l’écriture suivante, où adr[15..0] représentent les 16 bits de poids forts de l’adresse : RAM=CS*/adr15*/adr14*/adr13*/adr12*/adr11*/adr10*/adr9*adr8;
On désire maintenant que les adresses d’implantation de la RAM soient celles du segment [0x02000000,0x02FFFFFF], c’est à dire que les 8 bits de poids forts de l’adresse soient 000000102. Il suffit donc de modifier la fin de l’équation de décodage de la RAM : RAM=CS*/adr15*/adr14*/adr13*/adr12*/adr11*/adr10*adr9*/adr8;
9.2. Exercice 2 : construction d’un circuit d’entrées/sorties simplifié Énoncé Concevoir un circuit d’entrées/sorties au fonctionnement simplifié : 1
il possède 32 lignes d’entrées ins[31..0] et 32 lignes de sorties outs[31..0],
2
l’écriture à l’adresse 0x600000 (pour le programmeur) projète la valeur écrite sur outs,
3
la lecture à l’adresse 0x600000 fournit les valeurs échantillonnées sur ins,
4
les changements de valeurs sur les entrées ne déclenchent aucune interruption.
Solution L’adresse 0x00600000 à laquelle réagit le circuit est celle qui est vue par le programmeur; son utilisation en lecture ou en écriture conduit à l’activation de la ligne CS par la logique de décodage. Comme le circuit ne réagit qu’à cette seule adresse, il n’a plus besoin d’aucune ligne d’adresse (alors qu’il en utilisait 2 dans sa version initiale), ce qui conduit à l’interface de la figure VI.31 et au mode d’utilisation de la figure VI.32. Finalement, le schéma qui réalise ces fonctions s’obtient par simplification du schéma du circuit initial (figure VI.33).
CHAPITRE VI. CONSTRUCTION DU PROCESSEUR 32 BITS CRAPS
218
Figure VI.31. Interface du circuit d’entrées/sorties. W
opération
0
lire l’état des entrées
1
écrire sur les sorties
Figure VI.32. Spécification de fonctionnement du circuit d’entrées/sorties.
Figure VI.33. Structure du circuit d’entrées/sorties,pour un bit i. L’écriture en 0x00600000 provoque CS*W = 1, soit un stockage de DATA[i] dans la bascule, avec sortie de la valeur sur outs[i]. La lecture en 0x00600000 provoque CS*/W = 1, soit le passage de la valeur présente sur ins[i] sur le bus de données DATA[i].
Références [1] A. Cezes et J. Delacroix. Architecture des Machines et des Systèmes Informatiques. Dunod. 2nd Édition, 2005. [2] Hennessy and Patterson. Computer Organisation and Design. Morgan Kaufman. Second Edition, 1998. [3] J.-J. Schwarz. Architecture des Ordinateurs. Eyrolles. 2nd Edition, 2005. [4] R. Strandh et I. Durand. Architecture de l’ordinateur. Dunod, 2005. [5] Andrew S. Tanenbaum. Architecture de l’ordinateur. Pearson Education, 2005. [6] John F. Wakely. Digital Design. Prentice Hall. 5th Edition, 2000. [7] P. Zanela et Y. Ligier. Architecture et Technologie des Ordinateurs. Dunod, 2005.
219
Annexe A Tables diverses Puissances de 2 2− 1
0,5
21
2
2− 2
0,25
22
4
2− 3
0,125
23
8
2− 4
0,0625
24
16
2− 5
0,03125
25
32
2− 6
0,015625
26
64
2− 7
0,0078125
27
128
2− 8
0,00390625
28
256
2− 9
0,001953125
29
512
2 − 10
0,0009765625
210
1024
2 − 11
0,00048828125
211
2048
2 − 12
0,000244140625
212
4096
2 − 13
0,0001220703125
213
8192
2 − 14
0,00006103515625
214
16384
2 − 15
0,000030517578125
215
32768
2 − 16
0,0000152587890625
216
65536
unités milliseconde (ms)
10− 3 s
Kilo (K)
3 210 ∼ − 10
microseconde (µs)
10− 6 s
Mega (M)
6 220 ∼ − 10
nanoseconde (ns)
10− 9 s
Giga (G)
9 230 ∼ − 10
picoseconde (ps)
10− 12 s
Tera (T)
12 240 ∼ − 10
220
Annexe B CRAPS : guide du programmeur Le langage assembleur du processeur CRAPS est largement inspiré de celui du SPARC version 8. Il n’implémente pas la notion de fenêtre de registres et les instructions qui suivent les branchements n’ont pas de statut spécial. Il ne possède pas d’unité de calcul flottant. B.1. Instructions synthétiques Les instructions du tableau suivant sont appelées instructions synthétiques; ce sont des cas particuliers d’instructions plus générales. Plusieurs d’entre-elles s’appuient sur le fait que %r0 vaut toujours 0. Instruction
Effet
Implémentation
clr %ri
met à zéro %ri
orcc %r0, %r0, %ri
mov %ri,%rj
copie %ri dans %rj
orcc %ri, %r0, %rj
inccc %ri
incrémente %ri
addcc %ri, 1, %ri
deccc %ri
décrémente %ri
subcc %ri, 1, %ri
notcc %ri,%rj
%rj <- complément de %ri
xnorcc %ri, %ri, %rj
set val31..0, %ri
copie val dans %ri
sethi val31..10, %ri orcc %ri, val9..0, %ri
setq val, %ri
copie val dans %ri
orcc %r0, val, %ri
( − 4096 ≤ val ≤ 4095) cmp %ri, %rj
compare %ri et %rj
subcc %ri, %rj, %r0
tst %ri
teste nullité et signe de %ri
orcc %ri, %r0, %r0
negcc %ri
inverse %ri
subcc %r0, %ri, %ri
notcc %ri,%rj
complémente %ri en %rj
xnorcc %ri, %r0, %rj
nop
no operation
sethi 0,%r0
221
ANNEXE B. CRAPS : GUIDE DU PROGRAMMEUR
222
jmp %ri
branchement à l’adresse absolue %ri
jmpl %ri, %r0
ret
retour de procédure terminale
jmpl %r31+4, %r0
push %ri
empile %ri
sub %r30, 4, %r30 st %ri, [%r30]
pop %ri
dépile %ri
ld [%r30], %ri add %r30, 4, %r30
B.1. Instructions synthétiques
223
B.2. Jeu d’instructions du processeur CRAPS Les instructions marquées d’un † sont privilégiées, et ne peuvent être exécutées que si le bit superviseur dans %psr est à 1. Instruction : add Description : Effectue l’addition en complément à deux des deux opérandes, et place le résultat dans l’opérande résultat. La retenue C n’est pas ajoutée aux arguments. Les flags (condition codes, cc) ne sont pas modifiés par cette opération, contrairement à addcc. Flags affectés : aucun Exemple : add %r1, 5, %r1 Ajoute 5 au contenu de %r1; ne modifie pas les flags. Instruction : addcc Description : Effectue l’addition en complément à deux des deux opérandes, et place le résultat dans l’opérande résultat. La retenue C n’est pas ajoutée aux arguments. Les flags (condition codes, cc) sont positionnés conformément au résultat (voir add). Flags affectés : N, Z, V, C Exemple : addcc %r1, 5, %r1 Ajoute 5 au contenu de %r1, et positionne les flags. Instruction : andcc Description : Effectue un ET logique bit à bit entre les opérandes sources, et place le résultat dans l’opérande résultat. Les flags (condition codes, cc) sont positionnés conformément au résultat. Flags affectés : N, Z Exemple : andcc %r1, %r2, %r3 Effectue le ET logique bit à bit entre les contenus de %r1 et %r2, et place le résultat dans %r3 Instruction : ba Description : Se branche à l’adresse obtenue en ajoutant 4 x disp22 à l’adresse de l’instruction courante (ba elle-même). disp22 peut être négatif, ce qui correspond à un branchement à un point antérieur du programme. Flags affectés : aucun Exemple : ba label Se branche à label. Le déplacement disp22 codé dans l’instruction est égal à la distance (en mots) qui sépare l’instruction courante de l’adresse label. Instruction : bcc Description : Si la condition cc est vérifiée, se branche à l’adresse obtenue en ajoutant 4 x disp22 à l’adresse de l’instruction courante. Si la condition n’est pas vérifiée, passe à l’instruction suivante en séquence. disp22 peut être négatif, ce qui correspond à un branchement à un point antérieur du programme. On trouvera en annexe B.3 les tables complètes des conditions de test possibles. Flags affectés : aucun Exemple : bcs label
224
ANNEXE B. CRAPS : GUIDE DU PROGRAMMEUR
Se branche à label si C vaut 1. C’est le déplacement relatif entre label et l’adresse courante de l’instruction, en nombre de mots, qui est codé dans le champ disp22 de l’instruction. Instruction : call Description : Appelle un sous-programme et stocke l’adresse de l’instruction courante (celle du call lui-même) dans %r31. Le champ disp30 du code machine contient une valeur de déplacement par rapport à l’adresse de l’instruction courante, comptée en mots. Autrement dit, l’adresse de la prochaine instruction à exécuter est calculée en ajoutant 4 x disp30 à l’adresse de l’instruction courante (dans %pc). disp30 peut bien sûr être négatif. Flags affectés : aucun Exemple : call ssprog Sauvegarde %pc dans %r31, puis se branche au sous-programme qui commence à l’adresse ssprog. Instruction : jmpl Description : Jump and Link: retour d’un sous-programme. Effectue un saut à l’adresse définie par l’opérande source, et sauve l’adresse de l’instruction courante (le jmpl) dans l’opérande résultat. Flags affectés : aucun Exemple : jmpl %r31 + 4, %r0 Retour d’un sous-programme, équivalent à l’instruction synthétique ret. La valeur du PC ayant été sauvegardée dans %r31 lors du call précédent, l’adresse de retour doit effectivement être %r31 + 4. L’adresse courante est éliminée dans %r0. Instruction : ld Description : Load word. Charge un registre à partir d’un mot de 32 bits de la mémoire centrale, soit 4 octets consécutifs. L’adresse doit être alignée en mémoire sur un mot, c’est à dire qu’elle doit être un multiple de 4, sinon une exception align_error est générée. L’adresse est calculée en ajoutant le contenu du registre du champ rs1 au contenu du champ rs2 ou de la valeur immédiate contenue dans le champ simm13, selon le cas. Flags affectés : N et Z. Exemple : ld [%r2+%r3], %r1 Copie dans le registre %r1 les 4 octets en mémoire commençant à l’adresse obtenue en additionnant les valeurs des registres %r2 et %r3. Le premier octet (celui d’adresse %r2 + %r3) sera copié dans les poids forts de %r1, le quatrième octet (d’adresse %r2 + %r3 + 3) sera copié dans les poids faibles de %r1. Instruction : ldub Description : Load unsigned byte. Charge les 8 bits de poids faibles d’un registre depuis la mémoire centrale. L’adresse peut être quelconque. Elle est calculée en ajoutant le contenu du registre du champ rs1 au contenu du champ rs2 ou à valeur immédiate contenue dans le champ simm13, selon le cas. Les 24 bits de poids forts du registre sont forcés à 0. Flags affectés : N et Z. Exemple : ldub [%r2+6], %r1 Copie l’octet situé à l’adresse %r2 + 6 dans les 8 bits de poids faibles du registre %r1, et force à 0 les 24 bits de poids forts.
B.2. Jeu d’instructions du processeur CRAPS
225
Instruction : orcc Description : Effectue un OU logique bit à bit entre les opérandes sources, et place le résultat dans l’opérande résultat. Les flags (condition codes, cc) sont positionnés conformément au résultat. Flags affectés : N, Z Exemple : orcc %r1, 1, %r1 Positionne à 1 le bit de poids le plus faible de %r1 et laisse tous les autres inchangés. Instruction : rdpsr † Description : Lit le registre d’état %psr et le copie dans un registre %ri. Flags affectés : aucun Exemple : rdpsr %r1 Lit %psr et le copie dans %r1 Instruction : rete Description : Défait les empilements de %pc et de %psr qui sont réalisés lors de la prise en compte d’une exception. %psr reprend donc sa valeur initiale (et en particulier le niveau d’exécution pil), et %pc reprend la valeur qu’il avait au moment du départ. Cette instruction doit être placée à la fin de chaque handler d’exception. Flags affectés : potentiellement tous, lors du dépilement de %psr Exemple : rete Instruction : sethi Description : Positionne les 22 bits de poids forts d’un registre, et force à zéro les 10 bits de poids faibles. Flags affectés : aucun Exemple : sethi 0xE2F1, %r1 Positionne les 22 bits de poids forts de %r1 à E2F116 et force à 0 les 10 bits de poids faibles. Instruction : sll Description : Décale le contenu d’un registre vers la gauche d’un nombre de positions désigné par le deuxième argument, compris entre 0 et 31. Si ce nombre est fourni sous forme d’une valeur de registre, seuls les 5 bits de poids faibles sont utilisés. Flags affectés : aucun Exemple : sll %r1, 7, %r2 Décale le contenu de %r1 vers la gauche de 7 bits, avec insertion de 7 zéros par la droite, et stocke le résultat dans %r2. Aucun flag n’est affecté. Instruction : srl Description : Décale le contenu d’un registre vers la droite d’un nombre de positions désigné par le deuxième argument, compris entre 0 et 31. Si ce nombre est fourni sous forme d’une valeur de registre, seuls les 5 bits de poids faibles sont utilisés. Flags affectés : aucun Exemple : srl %r1, 7, %r2 Décale le contenu de %r1 vers la droite de 7 bits, avec insertion de 7 zéros par la gauche, et stocke le résultat dans %r2. Aucun flag n’est affecté.
226
ANNEXE B. CRAPS : GUIDE DU PROGRAMMEUR
Instruction : st Description : Stocke le contenu d’un registre en mémoire centrale. L’adresse doit être alignée en mémoire sur un mot, c’est à dire qu’elle doit être un multiple de 4. L’adresse est calculée en ajoutant le contenu du registre du champ rs1 au contenu du champ rs2 ou de la valeur contenue dans le champ simm13, selon le cas. Le champ rd est utilisé pour désigner le registre source. Flags affectés : aucun Exemple : st %r1, [%r2] Copie le contenu du registre %r1 dans la case mémoire dont l’adresse est la valeur de %r2. Dans ce cas particulier, rs2 = 0, et l’instruction pourrait être écrite : st %r1, [%r2+%r0] Instruction : stb Description : Stocke les 8 bits de poids faibles d’un registre, à une adresse quelconque en mémoire centrale. Seul un octet de la mémoire est affecté. L’adresse est calculée en ajoutant le contenu du registre du champ rs1 au contenu du champ rs2 ou de la valeur contenue dans le champ simm13, selon le cas. Le champ rd est utilisé pour désigner le registre source. Flags affectés : aucun Exemple : stb %r1, [%r2+5] Copie les 8 bits de poids faibles de %r1 dans la case mémoire dont l’adresse est la valeur de %r2+5. Seul cet octet est affecté en mémoire. Instruction : sub Description : Effectue la soustraction en complément à deux des deux opérandes, et place le résultat dans l’opérande résultat. les flags (condition codes, cc) ne sont pas affectés. Flags affectés : aucun Exemple : sub %r1, 5, %r1 Soustrait 5 au contenu de %r1, et ne modifie pas les flags. Instruction : subcc Description : Effectue la soustraction en complément à deux des deux opérandes, et place le résultat dans l’opérande résultat. Les flags (condition codes, cc) sont positionnés conformément au résultat. Flags affectés : N, Z, V, C Exemple : subcc %r1, 5, %r1 Soustrait 5 au contenu de %r1, et positionne les flags. Instruction : udivcc Description : Unsigned divide. Effectue une division non signée 32 bits / 16 bits avec calcul du quotient et du reste. Le quotient est placé dans les 16 bits de poids forts du registre destination, le reste dans les 16 bits de poids faibles. Flags affectés : Z Exemple : udiv %r7, %r1, %r6 Effectue la division entière non signée entre les 16 bits de poids faibles de %r7 et les 16 bits de poids faible de %r1, et place le quotient dans les 16 bits de poids forts de %r6 et le reste dans les 16 bits de poids faibles de %r6. Instruction : umulcc Description : Unsigned multiply. Effectue une multiplication non signée 16 bits x 16 bits vers 32 bits. Seuls les 16 bits de poids faibles des deux opérandes source sont pris en compte
B.2. Jeu d’instructions du processeur CRAPS
227
dans le calcul. Flags affectés : Z Exemple : umul %r7, %r1, %r6 Copie dans %r6 le résultat de la multiplication non signée entre les 16 bits de poids faibles de %r7 et les 16 bits de poids faibles de %r1. Instruction : wrpsr † Description : Copie dans %psr (Processor Status Register) le contenu d’un registre %ri. Flags affectés : aucun Exemple : wrpsr %r1 Copie dans %psr le contenu de %r1. Instruction : wrtbr † Description : Copie dans %tbr (Trap Base Register) le contenu d’un registre %ri. Flags affectés : aucun Exemple : wrtbr %r1 Copie dans %tbr le contenu de %r1. Instruction : xnorcc Description : Effectue un XNOR logique (inverse du XOR, encore appelé coïncidence) bit à bit entre les opérandes sources et place le résultat dans l’opérande résultat. Les flags (condition codes, cc) sont positionnés conformément au résultat. Flags affectés : N, Z Exemple : xnorcc %r7, %r1, %r6 Copie dans %r6 le xnor calculé bit à bit entre %r7 et %r1 et positionne les flags N et Z en conséquence. Instruction : xorcc Description : Effectue un XOR logique bit à bit entre les opérandes sources et place le résultat dans l’opérande résultat. Les flags (condition codes, cc) sont positionnés conformément au résultat. Flags affectés : N, Z Exemple : xorcc %r7, %r1, %r6 Copie dans %r6 le xor calculé bit à bit entre %r7 et %r1, et positionne les flags N et Z en conséquence.
ANNEXE B. CRAPS : GUIDE DU PROGRAMMEUR
228
B.3. Tables des branchements conditionnels
instruction
opération
test
ba
Branch Always
1
beq (synonyme : bz)
Branch on Equal
Z
bne (synonyme : bnz)
Branch on Not Equal
bneg (synonyme : bn)
Branch on Negative
bpos (synonyme : bnn)
Branch on Positive
bcs
Branch on Carry Set
bcc
Branch on Carry Clear
bvs
Branch on Overflow Set
bvc
Branch on Overflow Clear
not Z N not N C not C V not V
Figure B.1. Branchements conditionnels associés à un seul flag. instruction
opération
bg
Branch on Greater
bge
Branch on Greater or Equal
bl
Branch on Less
ble
Branch on Less or Equal
test not (Z or (N xor V)) not (N xor V) (N xor V) Z or (N xor V)
Figure B.2. Branchements conditionnels associés à une arithmétique signée. instruction
opération
bgu
Branch on Greater Unsigned
bcc
Branch on greater than, or equal, unsigned
bcs
Branch on less than, unsigned
bleu
Branch on Less or Equal Unsigned
test not (Z or C) not C C Z or C
Figure B.3. Branchements conditionnels associés à une arithmétique non signée.
B.4. Format binaire des instructions de CRAPS
B.4. Format binaire des instructions de CRAPS Format 1 : call
op 0 1
disp30 Format 2 : sethi et branchements
op 0 0
229
rd
op2
imm22
2a
cond
op2
disp22
2b
op 0 0 0
Format 3 : accès mémoire, instructions arithmétiques
op 1t
rd
op3
rs1
0 −− −− − − − −
1t
rd
op3
rs1
1
rs2
simm13
t = 1 : accès mémoire, t = 0 : instruction arithmétique
op2
Instr.
op3 (t = 0)
Instr.
op3 (t = 1)
Instr.
cond
Branchement
010
branch
000000
add
000000
ld
1000
ba
100
sethi
010000
addcc
000001
ldub
0001
be
000100
sub
000100
st
1001
bne
010100
subcc
000101
stb
0101
bcs
011010
umulcc
1101
bcc
010001
andcc
1110
bpos
010010
orcc
0110
bneg
010011
xorcc
0111
bvs
010111
xnorcc
1111
bvc
110101
sll
1010
bg
110110
srl
0010
ble
111000
jmpl
1011
bge
0011
bl
1100
bgu
0100
bleu
3a 3b
ANNEXE B. CRAPS : GUIDE DU PROGRAMMEUR
230
B.5. Directives de l’assembleur CRAPS Syntaxe .org
val32
Rôle force à val d’assemblage
la
nouvelle
adresse
ex : data .org 0x8000 label
.eq
val32
associe val 3 2 à label dans la table des symboles
label
=
val32
idem ex : NB
.eq
5
.word
val32(, val32) ∗
alloue et initialise des mots mémoire de 32 bits consécutifs .word 2,3,5,7 ex : list
.byte
vbyte(, vbyte) ∗
alloue et initialise en mémoire des octets consécutifs ex text .byte 10,13,"erreur",0
.malloc
n
alloue sans initialiser n octets consécutifs ex : buffer .malloc 100
.global
sym
Fait de sym un symbole global, visible par les autres modules ex : .global strout
val32 représente un nombre de l’intervalle [0, 232 − 1], ou de l’intervalle [ − 232 , 232 − 1]. vbyte représente une ou plusieurs descriptions d’octets : un nombre de [0, 255] ou une suite de caractères entre apostrophes.
B.5. Directives de l’assembleur CRAPS
231
B.6. Cartographie mémoire de CRAPS [0000 0000 − 0000 FFFF]
[0040 0000 − 0040 000B] [0060 0000 − 0060 000F]
[0100 0000 − 01FF FFFF]
ROM (64K)
commande #1 commande #2 IT timer
0040 0000
données
0060 0000
direction ITs memorisees sens front ITs
0060 0008
0040 0004 0040 0008
timer entrées / sorties
RAM (16 Mo)
B.7. Programmation des entrées/sorties adresse
W
opération
0x600000
0
lire l’état des entrées
0x600000
1
écrire sur les sorties
0x600004
0
lire configuration lignes
0x600004
1
configurer les lignes
0x600008
0
lire interruptions mémorisées
0x600008
1
raz des interruptions mémorisées
0x60000C
0
lire sens du front d’interruption (0 = front montant)
0x60000C
1
écrire sens du front d’interruption
B.8. Programmation du timer adresse
W
opération
0x400000
0
lire le registre de commande #1 (P + N)
0x400000
1
écrire le registre de commande #1 (P + N)
0x400004
0
lire le registre de commande #2 (prediv + start)
0x400004
1
écrire le registre de commande #2 (prediv + start)
0x400008
0
lire bit d’interruption timer
0x400008
1
raz bit d’interruption timer
0060 0004
0060 000C
ANNEXE B. CRAPS : GUIDE DU PROGRAMMEUR
232 B.9. Liste des exceptions exception
priorité
numéro de type (tt)
reset
15
voir texte
bus error
14
14
instruction privilégiée
13
13
instruction illégale
12
12
division par zéro
11
11
erreur d’alignement
10
10
interruption timer
8
8
interruption entrées/sorties
7
7
Glossaire accès aléatoire
algèbre de Boole
Pour une mémoire, indique la possibilité de faire des opérations successives de lecture et-ou d’écriture à des adresses quelconques, dans n’importe quel ordre. Pour un fichier, indique la possibilité d’effectuer une suite d’accès en lecture ou en écriture à des enregistrements situés à des positions quelconques.Voir aussi accès séquentiel.
Voir Boole.
ASCII American Standard Code for International Interchange. Codage des caractères anglo-saxons sur 7 bits. La plupart des codages de caractères (par exemple ISO-Latin 1) sont des extensions sur 8 bits de l’encodage ASCII.
ASIC
accès direct mémoire
Un ASIC (Application Specific Integrated Circuit) est un circuit intégré réalisé pour une application spécifique, contrairement aux PLDs et FPGAs qui sont reprogrammables et peuvent réaliser un ensemble varié de fonctions. Un ASIC est un peu plus rapide que son équivalent PLD ou FPGA, mais il doit être produit en grandes quantités pour que son coût à l’unité soit raisonnable.
Voir DMA.
accès séquentiel Serial access. Mode de lecture ou d’écriture de données qui suit un ordre de rangement préétabli.
adressage absolu Voir adressage direct.
adressage direct
assembleur (langage)
Utilisé en langage d’assemblage et en langage machine. Mode d’adressage dans lequel l’adresse (fixe) de la donnée en mémoire est fournie dans l’instruction. Est aussi appelé adressage absolu.
Language symbolique de programmation associé à un processeur, qui est en fait une expression directe plus lisible d’un programme écrit en langage machine.
adressage immédiat
assembleur (programme)
Utilisé en langage d’assemblage et en langage machine. Mode d’adressage dans lequel la donnée est fournie dans l’instruction elle même. Aucun autre accès en mémoire n’est nécessaire.
Programme de traduction qui convertit en langage machine un programme écrit en langage assembleur.
asynchrone Se dit d’un circuit ou d’un composant qui n’est pas synchrone, c’est à dire dont les sorties changent peu de temps après que les entrées aient changées, et non après un front d’horloge.
AGP Advanced Graphics Port. Port de communication spécialisé entre le processeur graphique et la carte mère, opérant à un débit de 522 MBs. 233
GLOSSAIRE
234
big-endian Voir little-endian.
bistable Voir latch.
bit Forme abrégée de ’binary digit’. Information représentée par un symbole à deux valeurs, généralement notées 0 et 1, associées aux deux états d’un dispositif.
bogue Traduction officielle française de ’bug’. Voir bug.
Boole Georges Boole (1815-1864), mathématicien anglais, publie en 1954 ses travaux sur une algèbre permettant de représenter des informations factuelles binaires telles que ’La porte est fermée’ ou ’La porte n’est pas fermée’. L’algèbre de Boole a été ensuite développée par Shannon sous la forme que nous connaissons aujourd’hui.
buffer Se dit d’un élément logiciel ou matériel. Mémoire ou partie de mémoire permettant le stockage temporaire de données entre deux organes.
bug Défaut d’un programme se manifestant par des anomalies de fonctionnement.
bus Ensemble de lignes électriques véhiculant un groupe de signaux particuliers.
byte Forme anglaise du mot ’octet’. Groupe de 8 bits.
carte mère Circuit imprimé sur lequel est situé le
processeur, la mémoire centrale et les contrôleurs des principaux périphériques et bus d’un ordinateur. Des cartes additionnelles (carte graphique, carte son, etc.) viennent s’enficher sur les connecteurs qui y sont présents (PCI, AGP, USB, etc.)
chargeur Module du système d’exploitation responsable du placement en mémoire centrale d’un programme à exécuter.
CISC Complex Instruction Set Computer. Ce terme a été créé en même temps et pour s’opposer au terme RISC. Désigne les processeurs aux jeux d’instructions complexes et de longueurs variables, qui utilisent généralement un séquenceur microprogrammé pour s’exécuter. L’Intel Pentium, le Motorola 68000 sont des exemples de processeurs CISC.
CMOS Complementary Metal Oxyde Semiconductor. Technologie de fabrication de circuits intégrés utilisée pour les processeurs, les microcontrôleurs et les mémoires statiques. Ils se distinguent par leur fort taux d’intégration, leur faible dissipation thermique, et le fait qu’ils ne consomment du courant significativement que lors des changements d’état.
codage par champs Façon de coder les instructions d’un langage machine, dans laquelle une succession hiérarchique de champs de bits dans l’instruction permet de la décoder et de l’exécuter.
compilateur Traducteur d’un langage vers un autre, généralement d’un langage évolué vers un code objet proche du langage machine.
CPLD
Glossaire
235
Complex Programmable Logic Device. Un CPLD est un PLD de grande capacité.
CPU Central Processing Unit. Souvent utilisé comme synonyme de processeur. Voir unité centrale.
éditeur de texte Programme permettant l’édition de fichiers contenant uniquement des lignes de texte, sans aucune indication de formatage. L’encodage des caractères peut être ASCII, un encodage ISO sur 8 bits ou Unicode.
édition de lien
debugger Outil logiciel permettant d’exécuter un programme sous contrôle, en y plaçant des points d’arrêt et en permettant l’inspection de l’état des variables et de la mémoire.
décodage Voir décodeur.
décodeur Circuit traduisant une adresse en une information d’appartenance à un sous-espace d’adresse.
DMA Direct Memory Acces = accès direct mémoire. Dispositif lié à la mémoire qui permet d’y faire des lectures et-ou des écritures sans passer par l’unité centrale. Les disques durs, les cartes graphiques, les cartes son fonctionnent le plus souvent en DMA.
DRAM Voir mémoire dynamique.
EBCDIC Ancien codage de caractères tombé en désuétude, qui était employé dans les machines de gestion.
échantillonnage Mémorisation de la valeur d’un signal (0 ou 1 en logique digitale) à un moment précis, généralement déterminé par le front montant ou descendant d’un autre signal.
éditeur de lien Voir édition de lien.
Opération de fusion de plusieurs fichiers de code objet pour produire un fichier de code exécutable.
EEPROM Electrically Erasable PROM. Mémoire morte reprogrammable, effaçable électriquement par octet.
enregistrement Dans un fichier, se dit d’un élément logique de celui-ci. Dans un programme, synonyme de structure de données.
entrance Voir fan-in.
EPROM Erasable PROM. Est parfois appelé REPROM (REprogramable PROM). Mémoire morte reprogrammable munie d’une petite fenêtre de quartz, et effaçable par une exposition aux ultra-violets.
exception Événement exceptionnel qui intervient pendant le déroulement d’un programme. Les exceptions à cause interne sont appelées traps (division par 0, débordement arithmétique, bus error, etc.) et les exception à cause externe sont appelées (fin interruptions d’entrée/sortie disque, réseau ; événement clavier, USB ; etc.). La prise en compte de l’exception provoque l’exécution d’un sous-programme associé appelé gestionnaire d’exception.
fan-in
GLOSSAIRE
236 Caractérise le courant qu’absorbe l’entrée d’une porte logique, mesuré en nombre de telles entrées qui peuvent être reliées à une sortie de même technologie.
fan-out Caractérise le courant maximum que peut générer la sortie d’une porte logique, mesuré en nombre d’entrées de même technologie qui peuvent lui être connectées.
flag Voir indicateur.
flash Voir mémoire flash.
FPGA Field Programmable Gate Array. Type de circuit logique reconfigurable basé sur la technologie des ’gate arrays’ (matrices de portes). Ils sont généralement de plus grande capacité que les PLDs ; leur configuration est stockée dans une RAM qui doit être rechargée à chaque mise sous tension.
génération de code Phase finale lors de la compilation d’un programme, durant laquelle le code assembleur associé au programme source est effectivement généré.
gestionnaire d’exception Sous-programme associé à une exception, qui est exécuté à chaque occurrence de l’exception, de façon transparente pour le programme en cours.
glitch Un glitch est une erreur fugitive dans un système informatique. Dans les circuits combinatoires ou séquentiels, c’est un état fugitif 0 ou 1 qui apparaît sur un signal, à un moment où il devrait être stable, et qui est dû à un problème de
différence de temps de propagation sur des sous-termes d’un terme complexe. Les tables de Karnaugh permettent de détecter et d’éliminer les glitchs des expressions combinatoires.
handler d’exception Voir gestionnaire d’exception.
Harvard (modèle de) Organisation générale d’un ordinateur caractérisée par la présence de deux mémoires séparées pour le stockage du programme et des données. Voir Von Neumann (modèle de).
harware Partie matérielle d’un ordinateur.
haute impédance Se dit d’une sortie d’un circuit logique. Lorsqu’elle est dans cet état, tout se passe comme si elle était déconnectée du circuit, car elle ne produit ni ne consomme plus aucun courant. Des sorties qui peuvent se mettre en état haute impédance peuvent être reliées entre elles directement (sur un bus par exemple), pourvu qu’une logique de décodage garantisse qu’au plus une seule de ces sorties est source de courant à un moment donné (sous peine de court-circuit).
HDL Harware Description Language. Langage de description de circuits, logique ou analogiques. Certains sont structurels, dérivant en détails la composition et la structure de tous leurs composants, tandis que d’autres sont fonctionnels, décrivant les relations fonctionnelles entre les flux d’entrées/sorties.
hexadécimal Système de comptage en base 16. Les chiffres sont : 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, A, B, C, D, E, F. On l’utilise comme écriture plus lisible du binaire, en regroupant les bits par
Glossaire paquets de 4.
hold (temps de) Durée après le front d’horloge durant laquelle les entrées d’une bascule doivent être stables pour que le basculement se passe correctement. Voir setup.
237 circuits reconfigurables tels que FPGAs et PLDs.
langage machine Language qu’exécute un microprocesseur, formé d’une suite d’instructions binaires.
latch
indicateur Bit mémorisé par un processeur, fournissant une indication sur un calcul qui vient d’être effectué par l’unité arithmétique et logique. Ils s’appellent le plus souvent N (negative), Z (zero), V (overflow), C (Carry).
interpréteur Programme ou dispositif matériel fonctionnant cycliquement, en décodant et exécutant à chaque cycle une nouvelle instruction d’un programme. Un processeur est toujours un interpréteur (matériel) de son langage machine.
interruption Evénement asynchrone, déclenché par l’environnement matériel d’un ordinateur (disque, réseau, souris, etc.), destiné à prévenir le processeur d’une condition particulière qui doit être traitée rapidement en suspendant le programme en cours d’exécution de manière transparente. Les interruptions sont des exceptions à cause interne.
ISA (bus) Industry Standard Architecture. Nom d’un bus de communication pour compatible PC, maintenant obsolète.
ISA (instructions) Instruction Set Architecture. Jeu d’instructions d’un microprocesseur.
JTAG Norme internationale permettant l’interconnexion et la configuration de
Cellule de mémorisation de 1 bit.
link Voir édition de lien.
little-endian, big-endian Ces termes sont issus des ’Voyages de Gulliver’ de Swift, lorsque Gulliver est au pays des politiciens. Une guerre d’opinion fait rage à propos de la manière de manger son oeuf à la coque, entre le parti des ’big-endian’ qui pense qu’on doit le casser par le gros bout, et le parti des ’little-endian’ qui veut commencer par le petit. En informatique, désigne deux conventions d’écriture d’un nombre binaire sur plusieurs octets successifs. Dans l’écriture big-endian, les octets sont copiés en terminant par les 8 bits de poids les plus faibles ; dans l’écriture little-endian on termine par les 8 bits de poids fort. L’écriture little-endian (la moins naturelle) n’est actuellement utilisée que dans les processeurs x86 d’Intel, et elle est une des principales sources d’incompatibilité entre structures et fichiers de données issues de machines différentes.
load-store Se dit d’une architecture d’ordinateur qui possède un petit nombre d’instructions de transfert entre la mémoire et les registres de type load et store, et dont toutes les autres instructions opèrent sur les registres internes au processeur. C’est souvent une caractéristique des processeurs RISC.
loader Voir chargeur.
GLOSSAIRE
238
logiciel Voir software.
nécessite plusieurs autres composants autour de lui pour fonctionner.
mappée en mémoire
microprogramme
Se dit des entrées/sorties d’un processeur, lorsque celui-ci ne peut agir sur elles que par des opérations de lecture ou d’écriture en mémoire, à des adresses dédiées. Un tel type de programmation des entrées/sorties s’oppose à l’utilisation d’instructions spéciales d’entrées/sorties comme sur les x86 par exemple.
Ensemble d’instructions placées dans une mémoire interne au processeur, non accessible au programmeur, qui permet l’exécution des instructions non câblées du langage machine.
mémoire dynamique
modes d’adressage Méthodes d’accès aux données pour un microprocesseur. Voir par exemple adressage direct.
Mémoire à accès aléatoire dont les cellules de mémorisation sont des capacités mémoire. Les mémoires dynamiques ont un taux d’intégration plus élevé que les mémoires statiques, mais elles nécessitent une électronique de rafraîchissement du contenu des cellules.
module
mémoire flash
Mémoire à accès aléatoire dont les cellules de mémorisation sont des bistables.
Gordon Moore est un des fondateurs d’Intel Corporation. Il a remarqué que tous les 18 mois environ, la puissance de calcul et la capacité mémoire des ordinateurs sont multipliées par deux. Cette progression a été appelée loi de Moore, et elle est encore valable actuellement.
mémoire tampon
multiprocesseur
Mémoire morte reprogrammable, effaçable électriquement par blocs.
mémoire statique
Voir buffer.
microcontrôleur C’est un circuit intégré unique, qui contient à la fois le microprocesseur, sa mémoire centrale et des circuits d’entrées/sorties. Il fonctionne donc de façon autonome avec un minimum de composants extérieurs, et ce sont ces circuits qui équipent nos voitures, jouets, appareils électroménagers, etc.
microprocesseur Processeur dont tous les éléments sont rassemblés en un seul circuit intégré. Il n’est généralement pas autonome et
En software, bloc de code réutilisable. En hardware, circuit réutilisable. Dans les deux cas, c’est un outil fondamental de maîtrise de la complexité.
Moore (loi de)
Se dit d’un ordinateur qui possède plusieurs processeurs centraux.
octet Byte en anglais. Groupe de 8 bits.
PCI Bus de communication entre un processeur et des cartes additionnelles, fonctionnant à une fréquence typique de 33 MHz. Il a été conçu par Intel, qui en a placé tous les brevets dans le domaine publique.
PLD Programmable Logic Device. Circuit logique dont les fonctions combinatoire
Glossaire
239
ou séquentielles sont programmables. On appelle souvent PLD uniquement les circuits dont la configuration est stockée dans une EEPROM ou une mémoire flash, et qui sont opérationnels dès la mise sous tension, contrairement aux FPGAs.
processeur
Se dit d’une partie de code machine qui peut s’exécuter où qu’elle soit placée en mémoire ; qui peut donc être relogée. Tous les compilateurs produisent un code relogeable, pour que le chargeur puisse le placer en mémoire à l’endroit le plus favorable.
Organe principal d’un ordinateur, qui permet d’interpréter les instructions de son langage machine.
REPROM
progiciel
Reduced Instruction Set Computer. Se dit de processeurs possédant des instructions en nombre limité, de taille fixe, et qui les exécutent généralement en un cycle d’horloge.
Ensemble de programmes professionnels, documentés et livrés sous forme d’un package.
Voir EPROM.
RISC
PROM Programable ROM. Mémoire morte programmable dont les bits sont analogues à des fusibles que l’on fait fondre par application d’un courant élevé lors de la programmation.
ROM Read Only Memory. Mémoire morte, accessible en lecture seulement.
routine Voir sous-programme.
RAM Random Acces Memory. Mémoire vive, accessible en lecture et en écriture.
random access
SDRAM Synchronous Dynamic RAM : RAM dynamique synchrone.
Voir accès aléatoire.
setup (temps de)
Voir enregistrement.
Durée avant le front d’horloge durant laquelle les entrées d’une bascule doivent être stables pour que le basculement se passe correctement. Voir hold.
record réentrant Se dit d’un programme dont une seule copie en mémoire peut être exploitée par plusieurs processus dans un système multitâches, sans que leur données ne se confondent. Tous les compilateurs produisent automatiquement du code réentrant.
software
registre
sous-programme
Circuit synchrone de mémorisation de plusieurs bits.
Partie de programme réutilisable. On l’invoque par une instruction spécialisée, et on lui communique en général des arguments par la pile ou dans des registres.
relogeable
Logiciel. Tout ce qui concerne la programmation d’un ordinateur, par opposition à hardware.
sortance Voir fan-out.
GLOSSAIRE
240 Lorsque son exécution est terminée, une instructions spéciale reprend le contrôle dans le programme appelant après l’instruction d’appel.
translatable
stack frame
Éxception à cause interne, provoquée par l’exécution d’une instruction, telle que division par 0, débordement arithmétique, bus error, etc.
Portion de la pile d’un programme en cours d’exécution, dans laquelle sont stockées les valeurs associées au bloc en cours (généralement les paramètres d’appel et les variables locales d’un sous-programme).
statique
Voir relogeable.
trap
tri-state Voir haute-impédance.
trois-états Voir mémoire statique.
Voir haute-impédance.
synchrone
TTL
Se dit d’un circuit qui possède une entrée d’horloge qui synchronise toutes ses opérations.
Transistor Transistor Logic. Technologie de réalisation de circuits logiques, dont la caractéristique principale est sa vitesse d’exécution. On lui préfère actuellement la technologie CMOS pour les circuits à forte intégration.
tampon Voir buffer.
temps d’accès
Unicode
Se dit d’un dispositif de mémorisation, disque ou mémoire centrale. Temps maximum de réalisation d’une opération de lecture ou d’écriture.
Nom d’un système de codage de caractères et d’idéogrammes dont l’ambition est de permettre de prendre en compte de façon unifiée la plupart des langues écrites du monde. Ce système fait la différence entre les codes-points, nombres entiers associés de façon unique à chaque caractère ou idéogramme, et l’encodage de ces codes-points, suite d’octets qui les représentent en mémoire. UTF-8 est le nom de l’encodage le plus répandu.
temps de propagation Durée qui s’écoule entre le moment où les entrées d’un circuit changent et le moment où ses sorties prennent une nouvelle valeur stable.
timer Circuit périphérique à un microprocesseur, qui lui fournit des impulsions à intervalles réguliers pour lui donner une unité de temps.
unité centrale
transistor
USB
Élément actif de base des portes logiques. Il est dans ce cas employé en mode commutation, c’est à dire en tout ou rien. L’Intel 8088 comportait 29000 transistors ; le Pentium II 7,5 millions.
Universal Serial Bus. Bus de connexion de périphériques série. C’est un bus en étoile, qui nécessite des hubs de connexion. Les périphériques peuvent être branchés à chaud. Dans son mode lent, il permet des
Partie de l’ordinateur qui effectue l’interprétation et le traitement des instructions.
Glossaire échanges à 1.5 MBs
VLSI Very Large Scale Integration. Technique de réalisation de circuits intégrés qui permet de mettre sur la même puce des millions de transistors, en utilisant des méthodes lithographiques.
Von Neumann (modèle de) Organisation générale d’un ordinateur caractérisée par la présence d’une unité de calcul, une unité de contrôle et d’une unique mémoire centrale qui contient à la fois les instructions du programme et les données manipulées. Voir Harvard (modèle de).
x86 Terme générique désignant un membre de la famille Intel dans la descendance 8080, 8085, 8086, 80186, 80286, …, 80x86 (x étant maintenant supérieur à 7). À partir du 80586, Intel a commencé à utiliser également le terme commercial de Pentium.
241
Index ABEL, 27 addition, 37 additionneur carry-lookahead, 39 complet, 37, 47 group carry-lookahead, 41 ripple-carry, 38 adressage direct, 145 immédiat, 145 indirect, 145 registre, 144 adresse, 115 algèbre de Boole, 3, 18, 22 allocation, 146 and, 21 ASCII, 10 ASIC, 26 asynchrone, 63 barrel shifter, 108 bascule D, 71 JK, 74 maître-esclave, 85 synchrone, 69 T, 73 big-endian, 130, 134 binaire pur, 11 bistable, 62 bit, 3 bloc, 160 Boole, 3, 18 booléen, 18 branchement absolu, 144 conditionnel, 140 inconditionnel, 140 relatif, 143 buffer 3-états, 99 bus, 2, 101, 187, 195 d’adresses, 195
bus (suite) de données, 195 synchrone, 102 système, 2 carte mère, 2 cartographie, 198 cercle des nombres, 13 circuit combinatoire, 18 séquentiel, 61 CISC, 130 CMOS, 3, 6 code code point, 10 de Gray de Gray, 14, 30 combinaison interdite, 31 comparaison, 14 comparateur, 50 compatibilité ascendante, 130 complément à 2, 12 compteur, 86 contrôleur de périphérique, 2 CPLD, 29 CRAPS, 129 débordement, 14 décaleur à barillet, 108 décodage, 198 décodeur, 103 décompteur, 86 décrémentation, 8 demi-additionneur, 37 détecteur de séquence, 64 directive, 145 diviseur de fréquence, 88 division, 48 ECL, 5 EEPROM, 29, 117 emprunt, 43 encodeur de priorité, 105 entrées-sorties, 163, 201 EPROM, 117 242
Index équation d’évolution, 73 Eratosthène, 184 et, 21 état équivalent, 67 exception, 170, 205 extension de signe, 107 FETCH, 210 flag, 111 fonctionnel, 27 FPGA, 26, 29 front d’horloge, 63 descendant, 63 montant, 63 GAL, 29 génération de code, 159 gestionnaire d’exception, 170, 174 giga, 7 glitch, 32 graphe graphe d’état, 62 graphe de Mealy, 65 graphe de Moore, 64 Gray, 30 handler d’exception, 174 hardware, 129 haute-impédance, 98 HDL, 26 hexadécimal, 9 high-Z, 98 hold, 84 horloge, 63 IEEE 754, 6 implicant premier, 36 essentiel, 36 imprimeur, 11 incrémentation, 8 indicateur, 111 instruction, 2 de rupture de séquence, 140 synthétique, 138 interpréteur, 129 interruption, 171, 207 inverseur commandé, 25 ISA, 129 ISO_8859, 10
243 JTAG, 29 Karnaugh, 30 kilo, 7 langage langage machine, 2 assembleur assembleur, 129, 135 machine, 129 latch, 62, 70 lecteur, 11 little-endian, 130, 134 LSB, 7 maître-esclave, 85 majorité, 22 mappé en mémoire, 133, 164 masquage, 207 MDL, 27 Mealy, 65, 77 méga, 7 mémoire, 62, 198 centrale, 1 dynamique, 117, 120 flash, 29, 117 statique, 6, 117 microcommande, 209 micromachine, 209 minterm, 21, 30 MLI, 114 mode d’adressage, 144 Moore, 64, 77 mot, 6 MSB, 7 multiplexeur, 26, 99, 104 multiplicateur systolique, 47 multiplication, 47 NAND, 23 niveau, 63 niveau de priorité, 172 non, 20 NOR, 23 not, 20 octet, 6 opérateur booléen, 20 complet, 24 logique, 20 OR, 21 ou, 21
244 PALASM, 27 parité, 24 PCI, 103 périphérique, 2 pile, 153 PLD, 26, 29 poids faible, 7 fort, 7 pointeur de pile, 153 porte et, 21 logique, 20 non, 20 ou, 21 processeur, 2 PROM, 117 PWM, 114, 166, 203 Quine-Mc Cluskey, 34 RAM, 116 récursif, 156, 162 réfléchi, 14 registre, 2, 90, 132, 186 regroupement, 31 RISC, 130 ROM, 116 scheduler de tâches, 176 SDRAM, 117 séquenceur séquenceur.cablé séquenceur.microprogrammé séquenceur, 209 servo-moteur, 168 setup, 84 Shannon, 18 SHDL, 26, 27 simplification, 31, 35 software, 129 sous-programme, 152 terminal, 153 soustracteur complet, 44 ripple borrow, 45 soustraction, 43 SPARC, 131 stack-frame, 159 structurel, 27
I NDEX symbole, 147 synchrone, 63 table de Huffman, 66 de Karnaugh, 30 de transitions, 66 de vérité, 20 temps temps d’accès, 118 de hold, 84 de propagation, 4 de setup, 84 théorème d’absorption, 22 de De Morgan, 22 timer, 114, 166, 203 tranche de bits, 112 transistor, 5 trap, 170 trois états, 98 TTL, 3, 5 UAL, 111, 190 UNICODE, 10 unité arithmétique et logique arithmétique et logique, 111, 190 de calcul, 2 de contrôle, 2 UTF-8, 10 vecteur d’exception, 174 VHDL, 27 VLSI, 5 Von-Neumann, 1 XOR, 24, 38, 44