l'assembleur  [livre #4061]

par  pierre maurette




Annexes

15.1 Annexe A – La numération

Introduire le binaire, donc les bases de la numération, à l'aide de quelques belles formules d'arithmétique serait rapide. Ce serait malheureusement inutile, puisque si ces notions mathématiques vous sont familières, alors vous n'avez aucun besoin d'explications sur les systèmes de numération.

D'un autre côté, le temps passé avec les bits et autres symboles hexadécimaux en programmant, spécialement en assembleur, mais également en Pascal, C/C++, Fortran, Java, etc. justifie un effort de clarification. Le binaire et l'hexadécimal sont de ces notions qui, si elles restent floues, vont polluer le reste de l'apprentissage. De plus, s'il existe des calculatrices et des programmes qui se chargent des conversions, il faut bien appréhender le sujet pour éviter des erreurs grossières.

Nous avons donc choisi une approche pragmatique, dans laquelle le système décimal est tout d'abord présenté en profondeur. Ensuite seulement, l'accent sera mis sur ce qui nous intéresse, le micro-ordinateur et ses bases binaire et hexadécimale, et sur la représentation particulière dans ces bases des nombres négatifs.

15.1.1 Décimal

Analysons notre façon habituelle de compter 243 moutons dans un enclos, par exemple. Pas de nombres négatifs pour l'instant. Et uniquement des entiers, puisque nous comptons des moutons et non des gigots ou des côtelettes.

Le mot français que nous prononçons 243 représente le nombre de moutons. Nous écrivons ce nombre à l'aide des chiffres  2, 4 et 3, c'est-à-dire des signes typographiques. Nous disposons de dix chiffres, qui constituent un sous-alphabet : 0, 1, 2, 3, 4, 5, 6, 7, 8, 9. L'ordre dans cette liste est important, ainsi que le fait qu'elle débute par 0 et progresse par incréments de 1.

Il y a dix chiffres. Pour écrire nos nombres, nous utilisons donc un système décimal , ou à base 10 . Écrire 243 revient à additionner 2  centaines , 4  dizaines et 3  unités , donc à effectuer l’opération : (2 x 100) + (4 x 10) + (3 x 1).

Remarque

Puissances d'un entier

En arithmétique, nous disons qu'un nombre entier est élevé à la puissance N quand il est multiplié N-1 fois par lui-même. 5 à la puissance 3, noté 5 3 et dit cinq au cube vaut donc cinq multiplié deux fois par lui-même, donc 5 x 5 x 5, donc 125. La puissance 2, c’est-à-dire multiplier un nombre par lui-même, est nommée le carré , comme la puissance 3 est le cube . L'analogie géométrique est claire. Par convention et logiquement, N  0 vaut toujours 1 et N  1 toujours N. Tout ceci n'est que définitions. Pour info, extraire la racine carrée de l'entier P n'a rien à voir avec l'orthodontie : il s'agit de trouver N dont le carré est P, soit de résoudre l'équation en N : P = N x N.

Les quantités 1, 10, 100 sont les puissances successives de la base, chacune d'elles étant obtenue en multipliant la précédente par la base. 10 à la puissance n est donc égal à (10 x 10 x ... x 10), 10 apparaissant n fois et se note 10  N . Donc, 10  1 vaut 10.

Pour que tout cela soit plus joli, et pour d'autres bonnes raisons qui leur sont propres, les mathématiciens ont posé comme nous l'avons signalé que 10  0  = 1 et même que (n'importe quoi)  0  = 1. Munis de ce savoir, revenons à nos moutons. Il y en a :

(2 x 10  2 ) + (4 x 10  1 ) + (3 x 10  0 ) = 243. Les braves bêtes sont toutes là !

Dans l'écriture d'un nombre, le chiffre le plus à droite, ici le 3, est celui des unités. Nous disons que c'est le chiffre le moins significatif .

Remarque

Notation explicite de la base utilisée

Il n'est pas nécessaire habituellement, quand la base 10 est utilisée, de le préciser explicitement. Il en sera autrement avec les autres bases. La façon générale de noter qu'un nombre est exprimé dans la base b est (nombre) b , comme dans (243) 10 . Ceci est une convention mathématique que nous appliquerons rarement en informatique : nous utiliserons le plus souvent la notation du langage utilisé pour exprimer un nombre dans une base autre que la base par défaut, généralement 10 : 4521h exprime en assembleur MASM de l'hexadécimal.

Depuis l'école maternelle, nous savons tous compter et additionner à l'aide de tables d'addition et en faisant des retenues . Une table d'addition est un ensemble de phrases, par exemple :

2 plus 2 font 4 ou 9 plus 7 font 16, je pose 6 et je retiens 1 .

Un beau jour, nous avons cessé de dire je sais compter jusqu'à... en nous apercevant que nous pouvions compter jusqu'au suivant, etc.

Si nous avions disposé d'un compteur de moutons à quatre chiffres, de type kilométrique, il aurait affiché, en fin de comptage, 0243.

Avec ce compteur limité à quatre chiffres, nous ne pouvons pas compter plus de 9 999 moutons. Ce qui fait, en n'omettant pas le 0000, 10 000 nombres différents. Remarquons que 10 000 est égal à 10 x 10 x 10 x 10, soit 10  4 ou 10 à la puissance 4. La base à la puissance du nombre de chiffres.

À chaque fois que nous ajouterons un chiffre au compteur, nous multiplions le nombre de valeurs représentables par 10, la base. Tout simplement parce que le compteur prend toutes ses anciennes valeurs pour chacune des 10 valeurs du nouveau chiffre.

À part ces cas particuliers du compteur ou de l'afficheur, le nombre maximal de valeurs représentables est une question rarement cruciale dans nos numérations ordinaires. Et nous évitons d’ajouter ou de conserver des 0 en tête des nombres écrits.

Dans la vie courante, le nombre 0 n'est pas absolument nécessaire : c'est déjà de l'abstraction mathématique. Il est même préférable de dire : il n'a pas de mouton, plutôt que : il a zéro mouton. Mais le chiffre 0 est indispensable : dans l'écriture d'un nombre, il garde la place. Sans lui, comment écrire 1 204 moutons ?

Voyons, avant d'en terminer avec les décimaux, quelques règles arithmétiques que nous employons machinalement :

Ajouter un 0 à la droite d'un nombre (un décalage vers la gauche avec remplissage par un 0) revient à le multiplier par 10, la base.

Retirer un chiffre à la droite d'un nombre (un décalage vers la droite) revient à une division entière par 10, la base.

Le chiffre le moins significatif est perdu dans cette dernière opération. C'est le reste de la division par 10, la base. Corollaire : un nombre terminé par un 0 est divisible par 10, la base. Nous disons qu'un nombre entier est divisible par un autre quand le reste est nul.

Ces trois dernières règles ont un sens général, en cela qu'elles vont s'appliquer à d'autres bases que 10. Ce qui n'est pas le cas pour d'autres, liées à la base 10, comme les règles de divisibilité par 3 et 5.

Enfin, pourquoi 10 chiffres ? Il est courant de se référer aux nombres des doigts des deux mains. Ils représentent surtout un bon compromis entre le nombre de signes à mémoriser et la compacité du nombre écrit.

Une fois choisi le nombre de signes (les dix chiffres, la base) utilisables, si nous souhaitons de plus que chaque nombre ait une représentation et que celle-ci soit unique, c'est la meilleure solution, peut-être la seule selon certains critères, sur le plan de l'efficacité et du bon sens, que nous venons de décrire pour passer d'une représentation écrite à base de signes à une quantité entière représentée.

Mais le choix de la base reste libre.

15.1.2 Binaire

Nous avons défini la signification en entiers naturels positifs d'un nombre écrit en décimal, en base 10. Le but est d'introduire d'autres bases, et essentiellement le binaire , ou base 2 , en procédant par analogie et opposition avec le décimal. Il faudra penser à cette analogie à chaque difficulté de compréhension.

Notre seul objectif est l'application aux microprocesseurs, pas la généralisation mathématique. Les deux chiffres de la base 2 seront donc les deux états 0 et 1 des fils ou des cases mémoires de nos machines, dans leur interprétation numérique la plus logique. Nous considérons donc des fils qui, groupés par 4, 8, 16, 32 ou 64 bits, correspondent à une valeur entière.

Nous voyons déjà, de par ces groupes de largeur fixe, deux différences entre le décimal et le binaire : d’une part, le nombre maximal de valeurs représentables, dans un nombre de bits donné, a de l'importance. Par ailleurs, il est bon de conserver les chiffres, ou bits, à 0 à gauche du nombre. Ainsi, 00000101, 5 écrit en binaire 8 bits, ne sera pas tout à fait équivalent à 0000000000000101, 5 en binaire 16 bits.

Ce que nous venons d'affirmer est mathématiquement incohérent ; il n'y a pas de différence de ce type entre décimal et binaire. En fait, la limitation est due à l'ordinateur et non au système binaire. Mais, peu importe, puisque nous allons souvent nous trouver réellement confrontés à un problème de dépassement de capacité. Représenter 1 204 moutons sur un ordinateur 8 bits est un vrai problème.

Pour le reste, l'analogie est totale. Les mots unités, dizaines et centaines disparaissent, mais les puissances successives de la base demeurent, et 1, 10, 100, etc., deviennent 1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024, etc. Le 1 joue le rôle du 9, dans un comptage par exemple, en ce sens qu'après 1 nous repassons à 0 avec un report. La table d'addition est plus courte, mais sera utilisée de la même façon.

Addition binaire
figure a1.01 Addition binaire [the .swf]

La seconde addition représente un cas de dépassement de capacité. Dans l'ordinateur, le positionnement à 1 d'un bit particulier, CF, nous permettra de traiter cet événement. Sinon, le résultat serait 147 + 114 = 5.

Nous devons ici signaler une règle, par ailleurs facile à prouver et valable pour tous les systèmes de numérations, base 10 et base 16 compris : quand l'addition de deux chiffres génère une retenue, celle-ci ne peut être que de 1 au maximum. Mieux, quand deux chiffres et une retenue éventuelle de 1 sont additionnés, cela reste vrai. Pas de démonstration, mais un exemple en décimal :

9 + 9 + 1 [retenue antérieure] donne 19, donc 9 et 1 de retenue.

Idem en binaire :

1 + 1 + 1 [retenue antérieure] donne 11, donc 1 et 1 de retenue.

Donc, 1 bit, le CF, suffit pour des additions sur 8, 16, 32... bits, y compris l'addition ADC, Add with Carry. Ce point de détail qui semble anodin justifie un grand nombre d'instructions de notre microprocesseur.

Les nombres pairs (= divisibles par la base) seront ceux terminés par le bit 0. Le dernier bit à droite, le moins significatif ou LSB (Least Significant Bit) est donc le reste de la division par deux. De la même façon, le reste de la division par 4 est le nombre représenté par les deux bits de droite, etc. Analogie : les décimaux terminés par 0 sont divisibles par 10. Les deux derniers chiffres d'un nombre décimal sont le reste de sa division par 100.

Un décalage d'un bit vers la gauche en complétant par un 0 est équivalent à une multiplication par 2. De 2 bits, par 4, etc. Analogie : en ajoutant un 0 à droite d'un décimal, il est multiplié par 10, par 100 en ajoutant deux 0, etc.

De même, un décalage vers la droite donne les valeurs divisées par 2, puis 4 puis 8, etc., avec perte du reste. Analogie : décaler vers la droite, c’est-à-dire faire sauter le chiffre le plus à droite d'un décimal, revient à le diviser par 10 avec perte du reste.

Il existe en assembleur, ou même en langage de haut niveau, des instructions agissant directement sur les bits, comme les instructions de décalages qui viennent d'être évoquées ou les instructions logiques bit à bit. Ces instructions se comprennent mieux avec à l'esprit la représentation binaire de l'opérande, mais celui-ci pourra être exprimé dans la base de son choix. Par exemple, les lignes :

mov eax, 12445
shl eax, 4 

représentent une façon plus efficace de coder une multiplication par 16 du registre EAX, qui contient la valeur 12445, ici exprimée en décimal, si le dépassement n'est pas à craindre.

Il nous arrive d'entendre que le choix de la base de numération n'est que purement conventionnel, que l'ordinateur n'est pas plus binaire que décimal ou hexadécimal. C'est faux, le microprocesseur que nous connaissons est plus binaire que décimal. Sur 8 bits par exemple, nous pourrons représenter 255 valeurs différentes. Ce nombre est une réalité qui n'a rien de conventionnel. Ces limites appartiennent bien au monde du binaire, et non du décimal. Certaines machines à calculer mécaniques sont par essence décimales, et les nombres 255 et 256 n'y ont rien de particulier.

15.1.3 Hexadécimal, octal

Plus une base sera grande (10 par rapport à 2), plus l'écriture des nombres sera compacte. La base 2 est par essence bien adaptée à décrire le fonctionnement de l'ordinateur, mais la taille des nombres la rend souvent inexploitable. Il a donc fallu se tourner vers une représentation dans une base plus grande. Pourquoi autre chose que la base 10, notre base maternelle ? C'est ce que nous allons voir maintenant.

Pour construire un système de numération à base 100, la principale difficulté serait la définition et la mémorisation de 100 symboles différents. Imaginons que nous ayons accompli ce travail, à l'aide d'un bon CD-Rom de cliparts par exemple, et affirmons que les symboles se notent Sy[n] , avec n allant de 0 à 99. Sy[0] est 0, par exemple, et nécessairement. Sy[79] pourrait aussi bien représenter un ibis, un peuplier ou la croix du Languedoc.

Convertir un nombre, (124578) 10 par exemple, de base 10 en base 100 se fera très simplement, deux chiffres par deux chiffres. Dans notre cas, par la suite des trois symboles Sy[12] , Sy[45] et Sy[78] , ce qui pourrait donner Vautour Tamanoir Autobus en hiéroglyphes modernes. Le passage inverse est tout aussi simple.

Le passage d’une base à une autre est simple quand la base la plus grande est elle-même une puissance de la base la plus petite. La base la plus grande pourrait être 100, 1 000, 10 000, etc., dans le cas de la base 10. En d'autres termes, chaque symbole dans la base la plus grande recouvrira exactement  2, 3 puis 4 symboles de la base la plus petite. Fatalement, les bases puissances d’une autre s’écriront toujours 10, 100, 1 000, etc., dans cette base.

Les bases 10 et 2 n'ont pas ce type de rapport. Comparez 133 à son expression binaire 10000101. Il n’y a pas de moyen simple de passer de l’une à l’autre, et réciproquement. Pire, il est impossible de déterminer exactement à quels chiffres d’une base correspondent quels chiffres dans l’autre.

Les premières bases puissances de la base 2 seront 4, 8 et 16 (100, 1 000 et 10 000, si écrit en binaire). La base 10, 1010 en binaire, n'en fait pas partie.

La base 4 n’existe pas réellement. Elle admettrait les 4 chiffres 0, 1, 2 et 3. La traduction de la base 2 vers la base 4 se fait simplement par paquets de 2 bits. 10000101 en binaire deviendrait 2011 en base 4. Dans cette hypothétique base 4, chaque chiffre serait représenté par deux fils.

La base 8 existe ; elle a une certaine importance historique et s’appelle l’octal. Elle utilise les 8 chiffres 0, 1, 2, 3, 4, 5, 6 et 7. 10000101 en binaire devient 205 en octal. Le chiffre octal de base est représenté par 3 fils. C’est bien là le problème de l’octal, qui explique que ce système soit aujourd’hui obsolète. Ce trio de fils ne correspond à rien de nos jours.

Il en va tout autrement de la base 16, ou hexadécimal. Ce système admet les 16 chiffres 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, A, B, C, D, E, et F. 10000101 en binaire devient 85 en hexadécimal. De binaire à hexadécimal ou octal et réciproquement, la conversion se fera par paquets de 3 ou 4 bits, très simplement :

Conversion binaire/hexadécimal/octal chiffre par chiffre

Valeur

Chiffre hexa

Quartet binaire

Chiffre Octal

Trio binaire

0

0

0000

0

000

1

1

0001

1

001

2

2

0010

2

010

3

3

0011

3

011

4

4

0100

4

100

5

5

0101

5

101

6

6

0110

6

110

7

7

0111

7

111

8

8

1000

(10)

 

9

9

1001

(11)

 

10

A

1010

(12)

 

11

B

1011

(13)

 

12

C

1100

(14)

 

13

D

1101

(15)

 

14

E

1110

(16)

 

15

F

1111

(17)

 

S'il vous arrivait d'avoir besoin d'effectuer des conversions binaire/hexadécimal de façon répétitive, il serait utile de crayonner une partie de ce tableau sur un coin de feuille.

C'est le F qui joue en hexadécimal le rôle du 9 en décimal. La valeur du F est 15 en décimal, et s'écrit 1111 en binaire. Plus généralement, un nombre composé uniquement de F en hexadécimal correspondra à un nombre binaire dont tous les bits sont à 1. Ou à -1, mais ne compliquons pas pour l'instant. Nous sommes entre entiers naturels, positifs.

Le chiffre hexadécimal correspond à 4 bits en binaire, donc à 4 fils sur les bus. Nous pouvons donc dire que, pour nous informaticiens, l’hexadécimal est une façon pratique et compacte de noter la réalité binaire. Cela est d’autant plus vrai que les largeurs usuelles des bus de micro-ordinateurs et des microprocesseurs associés sont multiples de 4, puisque ce sont des puissances de 2 : 8, 16, 32 sont des largeurs connues sur les micro-ordinateurs. À ce sujet, pensez qu'il existe des bus internes plus larges, et que justement ils font 64, 128, 256 bits de largeur.

En revanche, comme le binaire, l’hexadécimal a peu de rapports avec le décimal. Pas de conversion simple, en d’autres termes pas de rapport entre un chiffre ou un groupe de chiffres entre une base et l’autre. En synthèse, nous pouvons dire :

  Nous sommes décimaux dans notre expression et visualisation des chiffres.

  Nos ordinateurs sont binaires.

  Parler d’hexadécimal est une autre façon de parler de binaire.

Ce que le binaire peut faire mieux que l'hexadécimal, c'est bien entendu traduire la valeur d'un ou plusieurs bits particuliers dans un mot.

L'hexadécimal est très utilisé pour représenter des adresses. La correspondance entre un chiffre et 4 fils est très pratique pour mettre en valeur, dans une adresse, la notion de page.

Pour terminer voici un résumé du nombre de valeurs possibles et des limites dans les trois largeurs de mots classiques :

Quelques valeurs à retenir...

Base

8 bits

16 bits

32 bits

Valeurs possibles

256 = 2 8

65536 = 2  16

4294967296 = 2  32

Décimal

0 à 255

0 à 65 535

0 à 4294967295

Décimal si signé

-128 à +127

-32768 à +32767

-2147483648 à +2147483647

Hexadécimal

00 à FF

0000 à FFFF

00000000 à FFFFFFFF

Binaire

0. [8].0 à 1. [8].1

0. [16].0 à 1. [16].1

0. [32].0 à 1.[32].1

Vous y voyez apparaître une représentation de nombres négatifs. C'est justement l'objet de la section suivante, pas nécessairement la plus simple.

15.1.4 Les entiers négatifs

Nous avons vu la représentation naturelle des entiers positifs ou nuls à partir de bus de 8, 16, 32 ou 64 bits de largeur : représentation binaire, celle de l’ordinateur. Nous avons également vu que la représentation hexadécimale se déduisait bit à bit de la représentation binaire.

Nous savons qu’en décimal, ou plus exactement en langage courant, nous exprimons qu’un nombre est négatif en le faisant précéder du signe -, le signe +, par défaut, étant plus rarement indiqué. Indiquer un signe + est souvent une façon d'exprimer que ce nombre est signé, que nous nous situons dans l'ensemble des entiers relatifs.

En binaire, tout doit tenir dans les bits dont nous disposons. Remarquons que le signe, + ou -, s'exprime complètement sur un seul bit. Complètement signifie sans gaspillage d'informations. Vous avez vu ou verrez que les entiers BCD de la FPU codent le signe sur un octet complet : il y a là gaspillage d'informations.

Dans le microprocesseur, sont implémentées des instructions d'addition, incrémentation et décrémentation ( ADD , INC , DEC ). Leur comportement aux passages de la limite de capacité est celui du compteur kilométrique, passage de la valeur maximale à zéro en montant, l'inverse en descendant. Ce comportement est ce qu'il y a de moins mauvais, puisque la gestion du dépassement par retenue, à l'aide de l'indicateur CF (et de l'instruction ADC ), est très facile.

Ce fonctionnement en non signé est donc très cohérent, et les concepteurs de microprocesseurs désiraient le conserver, mais souhaitaient ajouter une possibilité de traiter facilement des entiers signés. Les raisons sont nombreuses et indiscutables de conserver le choix entre deux modes, alors qu'une représentation signée pourrait couvrir tous les cas de calcul.

Attention

Phrase clé

En fait de traitement d'entiers signés, le microprocesseur va continuer à travailler sur ses mots binaires comme s'ils étaient exclusivement positifs ; et nous allons au besoin interpréter différemment ces mots pour faire du calcul signé avec un minimum d'ajustements. C'est un point crucial dans la compréhension et nous y revenons dans le fil de l'ouvrage dès qu'un exemple nous en donne l'occasion. Que les initiés nous en excusent.

Rappelons que la valeur absolue d'un nombre est tout simplement sa valeur sans le signe, donc un nombre positif. La valeur absolue de x se note |x|. Nous écrivons donc par exemple : |-13|=|13|=13. Un entier signé est, de la façon la plus générale, décrit par son signe + ou - et par sa valeur absolue.

Il a donc été recherché une représentation des nombres signés, ou plus exactement une interprétation signée des nombres binaires que nous connaissons : le nombre binaire 10000101 vaut 133 en binaire non signé, que nous avons appelé représentation naturelle. Que devra-t-il valoir dans la représentation signée ? Jouons aux savants pendant quelques lignes. Pourquoi ? Parce que la solution qui a été retenue semble inutilement compliquée, par rapport à une solution qui nous vient immédiatement à l'esprit et que nous buttons souvent sur ce flou. Le développement est fait sur 8 bits dans un premier temps. Qu'attendons-nous d'une bonne représentation en entiers négatifs ?

Nous voulons pouvoir représenter tous les nombres. Or, il semble bien qu'il y ait "autant" de nombres positifs que de nombres négatifs. Donc, sur 8 bits, nous ne devrions plus représenter que 128 nombres positifs à peu près, pour en représenter à peu près autant de négatifs.

Nous aimerions bien que les nombres positifs représentés en signé le soient de la même façon qu'en non signé. De plus, nous voudrions pouvoir facilement déduire la valeur sur 16 bits de celle sur 8 bits, etc.

Il serait pratique que nous puissions immédiatement déterminer le signe d'un nombre par la valeur d'un bit et, si ce bit était le MSB, le plus à gauche, nos habitudes seraient respectées.

Il faut que les opérations ADD , INC et DEC donnent le résultat attendu sur ces nombres, sans autre modification, autant que faire se peut. Ce point demande par exemple qu'incrémenter un négatif décrémente sa valeur absolue.

Le mathématicien qui se réveille nous souffle qu'il serait préférable que, dans la plage représentée, chaque valeur ait une et une seule représentation. Donc, une seule représentation du zéro.

Il serait enfin souhaitable que le fonctionnement en compteur kilométrique soit conservé, sans ajouter de rupture supplémentaire.

Commençons par tester la représentation intuitivement la plus évidente : réserver le MSB (bit de poids le plus fort) à la représentation du signe, 0 pour + et 1 pour -, en lui donnant le nom de bit de signe, les bits restants exprimant la valeur absolue en binaire naturel. Testons rapidement cette solution :

12 + (-12) va donner -24, décrémenter 0 donne -127, incrémenter -21 donne -22, le reste à l'avenant. De plus, nous voyons qu'il y a un +0 et un -0, donc deux valeurs pour 0. Nous sommes donc corrects sur les points 1, 2 et 3, et calamiteux sur les autres.

Cherchons donc autre chose, avec un semblant de méthode. Sur 8 bits, si pN est un nombre, nommons nN = -1 x pN son opposé et cherchons au minimum à respecter le critère pN + nN = 0. En respectant cela, nous additionnons un nombre et la représentation de son inverse, avec l'opérateur d'addition normal, qui ne connaît pas la notion de nombre négatif. C'est donc en réalité 256, qui vaut 0 sur 8 bits, que nous allons atteindre.

Donc : pN + nN = 256 = 255 + 1. Donc, pN + (nN - 1) = 255. 255, c'est tous les bits à 1. Or, que faut-il ajouter à un bit pour obtenir 1 ? Son inverse, puisque 1 + 0 = 1 et 0 + 1 = 1. Il faut donc que (nN - 1) soit l'inverse bit à bit de pN, NOT pN pour parler assembleur. Donc nN = (NOT pN) + 1.

Vous lirez parfois les expressions complément à 1 pour l'inverse bit à bit NOT pN, et complément à 2 pur l'inverse nN = - pN.

Testons la définition suivante :

Entiers signés – Calcul
figure a1.02 Entiers signés – Calcul [the .swf]

L'inverse de l'inverse d'un nombre est ce nombre lui-même. L'inverse de 0 est 0. Le 0 est unique. Le MSB joue le rôle de bit de signe. Nous vérifions que tous les critères semblent respectés. Rappelons que nous ne prouvons rien, au sens mathématique ; nous testons sur huit bits une solution qui fonctionne. Sinon, ça se saurait.

Cette définition, pour beaucoup d'entre nous, n'apparaît pas naturelle. Avant de l'admettre définitivement, étudions-la graphiquement :

Entiers signés – Répartition
figure a1.03 Entiers signés – Répartition [the .swf]

Les deux premières colonnes représentent le contenu d'une mémoire 8 bits, en binaire puis en hexadécimal, quand elle est incrémentée à partir de l'état tous les bits à 0. C'est le contenu physique de la mémoire. La troisième colonne représente la traduction naturelle en décimal non signé. Les traits horizontaux un peu plus épais représentent une rupture dans la progression naturelle du comptage. Pour l'instant, la seule rupture était attendue, elle marque le passage de la valeur maximale, 255 sur 8 bits, vers le 0.

La quatrième colonne représente la mauvaise représentation en entiers signés. Les défauts apparaissent clairement : plusieurs 0, ruptures deux fois plus nombreuses, les instructions INC et DEC fonctionnent parfois à l'envers...

La cinquième colonne représente la traduction en complément à 2. C'est là que nous nous apercevons que c'est la bonne méthode, et la seule. La sixième colonne est simplement un décalage de la cinquième, pour peut-être une lecture plus facile. Les valeurs s'incrémentent naturellement, sauf lors d'une seule rupture indispensable.

Les microprocesseurs possèdent un indicateur, CF (pour Carry Flag), qui est positionné lorsqu'une opération, addition par exemple, fait passer au-delà de la rupture 0 vers 255. Il est ensuite facile d'en tenir compte pour réagir. Pour traiter les nombres négatifs, il a suffi d’ajouter un autre indicateur, OF (pour Overflow Flag). Celui-ci est positionné au passage de la rupture 127 vers -128, qui est en binaire naturel la rupture 127 vers 128. OF n'est pas le bit ou flag de signe SF qui, lui, recopie simplement le MSB. Ces flags CF et OF sont toujours positionnés. C'est au programmeur d'en tenir compte ou non. En ce sens, répétons-le encore, il n'y a pas, ou peu, d'arithmétique signée dans le microprocesseur. Il y a simplement une circuiterie qui va grandement faciliter le travail du programmeur.

Deux petits défauts de cette représentation signée :

Obtenir l'inverse d'un nombre n'est pas immédiat. En assembleur x86, c'est NEG  qui donnera l'inverse d'un nombre au sens arithmétique, c'est le complément à 2. NOT  inverse un mot bit à bit, c'est le complément à 1.

De même, la valeur absolue d'un nombre n'est pas de façon simple dans ses bits. Pour info, voici une méthode élégante bien connue pour extraire cette valeur absolue, sur 8 et 16 bits :

cwd
xor  ax, dx
sub  ax, dx

et :

cbw
xor al, ah
sub al, ah

Une méthode plus "normale" serait :

or   ax, ax
jge   suite
neg  ax
suite :

 

Voici un petit listing (sous Delphi), dont le rôle est d'afficher les interprétations signées et non signées en parallèle et de mettre en évidence le comportement de OF et CF :

var
  i, CFlag, OFlag : Integer;
  b: Byte;     // 8 bits non signés
  s: Shortint; // 8 bits signés
begin
  b := 0;
  repeat
    OFlag := 0;
    CFlag := 0;
    asm
      add b, 1
      jno @suite1
      mov OFlag,1
      @suite1:
      jnc @fin
      mov CFlag, 1
      @fin:
      mov al, b
      mov s, al
  end; //asm
    MemoSortie.Lines.Add(IntToStr(CFlag) + 
                   '  ' + IntToStr(OFlag) + 
                   '  ' + IntToStr(b)+ 
                   '  ' + IntToStr(s));
  until b = 0;

b est un entier non signé, s un entier signé. L'assembleur est utilisé pour "transporter" la valeur brute, les bits, depuis b vers s. C'est Delphi qui va effectuer l'interprétation. Donc, nous ne prouvons rien, nous vérifions. Le source et l'exécutable sont sur le CD-Rom.

Il reste à voir comment tout cela se comporte sur des mots de 8, 16 et 32 bits. Le problème est d'étendre un mot sur un plus grand nombre de bits. Les valeurs positives, en signé et non signé, se complètent tout simplement par des 0. Les valeurs négatives se complètent par des 1, donc des F en hexadécimal. Cela est appelé la propagation du bit de signe , et c'est un (tout petit) problème de programmation courant. L'explication découle de la définition du complément à 2. Quelques exemples suffiront pour se fixer les idées :

Extension de format en non signé

Interprétation décimale

Binaire 8 bits

Binaire 16 bits

Hexa 8 bits

Hexa 32 bits

18

0001 0010

0000 0000 0001 0010

12

00000012

215

1101 0111

0000 0000 1101 0111

D7

000000D7

 

 

Extension de format en signé – Propagation du signe

Interprétation décimale

Binaire 8 bits

Binaire 16 bits

Hexa 8 bits

Hexa 32 bits

18

0001 0010

0000 0000 0001 0010

12

00000012

-41

1101 0111

1111 1111 1101 0111

D7

FFFFFFD7

 

15.1.5 La saisie et l'expression orale

Nous parlons de saisie avant d’aborder la conversion, parce qu'une saisie bien menée permet très souvent d'éviter la conversion.

Dire ou écrire qu'il est possible de représenter 256 valeurs, de 0 à 255, avec 8 bits en binaire, n'est pas ambigu. Le système décimal est le seul système d'échange usuel entre nous. Évitons également les rébus du style : "Six s'écrit cent dix en binaire", au profit de "Six s'écrit (zéro) un, un, zéro en binaire". Un nombre se prononce en décimal. Dans une autre base, il s'écrit ou il s'épelle.

Le langage machine est binaire. En assembleur ou en langage de haut niveau, une étape préalable d'analyse du source, assimilable à un préprocesseur, permet la saisie dans différents systèmes de numérations, y compris par exemple un caractère à la place de son code ASCII.

Il faut bien choisir la base en fonction du type de la donnée :

  Les 243 moutons n'ont aucune raison de devenir (F3) 16 , pas plus que (11110011) 2 .

  Les masques pourront être saisis en binaire : and al, 00000100b suggère une extraction du bit 2.

  Les adresses et autres quantités à connotation informatique se saisiront en hexadécimal.

  Un bon format de saisie peut remplacer un commentaire dans le source :

mov al, ‘S’
and al, 00000100b

est mieux que :

mov al, 83  ; lettre S dans al
and al, 4   ; extraction du bit 2  

Dans le cas d'une saisie en binaire ou hexadécimal, pensez à laisser les 0 nécessaires à gauche.

Il est généralement impossible d’insérer des espaces séparateurs entre les groupes de 4 chiffres, ce qui serait pourtant pratique. Vous pouvez toujours saisir ou vérifier en insérant ces espaces, puis les ôter avant compilation.

Syntaxes courantes

La syntaxe de saisie des constantes numériques varie selon les compilateurs ou assembleurs. Il existe néanmoins des… constantes :

Sous MASM, il est possible de définir une base par défaut, à l'aide de la directive .RADIX  suivie d'un nombre entre 2 et 16. Cette étendue de choix dépasse largement nos besoins.

Le décimal est souvent le système par défaut, qui sera saisi tel quel, mais qui pourra éventuellement être suffixé d’un d ou D, voire d'un t ou T : dans mov ax, 1245d , la constante est bien (1245) 10 et non (1245D) 16 .

L’hexadécimal peut, selon les cas, être préfixé d’un $, ou d’un 0x, ou encore suffixé d’un h ou H :

$1DF3, 0x1DF3, 1DF3h, 1DF3H.

L'octal demande une lettre o ou O, q ou Q, parfois en suffixe. Parfois, dans le cas de compilateurs C, il suffit qu'une constante commence par un 0 (zéro) pour être interprétée comme de l'octal. Comme dans 012, qui interprété en octal vaut 10. Piège.

Le binaire, quand sa saisie directe est autorisée, demande un b ou B, y ou Y en suffixe.

Dans le cas d'un suffixe, si une constante ne doit pas commencer par une lettre, sous peine d’être interprétée comme un nom de variable, il faut lui ajouter un 0 initial :

mov ax, ABCDh ne se compile pas, mais mov ax, 0ABCDh fonctionne.

L'archaïque utilitaire DEBUG est assez dangereux sur ce plan. Il n'admet que des constantes hexadécimales, sans préfixe ni suffixe. Si vous ajoutez par erreur un d, un D, un b ou un B en suffixe, avec l’intention de saisir une constante en décimal ou en binaire, il sera interprété comme un chiffre hexadécimal. Si cela ne provoque pas de dépassement de la taille maximale, aucune erreur ne sera signalée. Mais peut-être n'utilisez-vous pas DEBUG de façon habituelle...

15.1.6 Les conversions

Malgré les diverses possibilités de saisie et les quelques outils présentés plus loin, vous devrez parfois convertir à la main ou écrire une routine de conversion.

Un changement de base ne modifie pas vraiment un nombre vers un autre nombre, mais plutôt une chaîne de caractères vers une autre. Certaines chaînes sont considérées comme des nombres, par exemple le contenu d'une variable pour le microprocesseur ou la représentation décimale pour nous.

Les conversions Binaire-Hexa, dans les deux sens, se traitent très facilement, puisqu'il y a correspondance directe entre paquets de chiffres, ou de bits, comme nous l'avons déjà vu. Nous pouvons ajouter l'octal à cette catégorie.

Pour les autres conversions papier-crayon, nous partons toujours de la base 10 ou nous y arrivons. Si, exceptionnellement, nous devions passer par exemple de la base 5 à la base 7, nous passerions par la base 10. C'est à priori la seule dans laquelle nous savons travailler à la main. En programmation, il en irait autrement.

Base 10 vers autre base

À partir de la base 10 : nous procédons par divisions successives du nombre par la base cible. Les restes successifs représentent le résultat. Deux exemples suffiront :

3731 à convertir en hexadécimal :

3731 / 16 = 233 reste  
3
 233 / 16 =  14 reste  
9
  14 / 16 =   0 reste 
14

14 correspond au chiffre E en hexa. Le résultat est donc E93h (soit 1110 1001 0011 en binaire, à l'aide du tableau).

Le même, en binaire :

3731 / 2 = 1865 reste 
1
1865 / 2 =  932 reste 
1
 932 / 2 =  466 reste 
0
 466 / 2 =  233 reste 
0
 
 233 / 2 =  116 reste 
1
 116 / 2 =   58 reste 
0
  58 / 2 =   29 reste 
0
  29 / 2 =   14 reste 
1
 
  14 / 2 =    7 reste 
0
   7 / 2 =    3 reste 
1
   3 / 2 =    1 reste 
1
   1 / 2 =    0 reste 
1

Le résultat est donc bien 1110 1001 0011, soit E93h en hexadécimal, à l'aide toujours du tableau.

Base quelconque vers base 10

Pour aller vers la base 10 à partir d'un nombre xn ... x2 x1 x0 (dans le cas de l'hexadécimal, les xi vont de 0 à F, F valant 15) en base X, nous appliquons simplement la définition d'une base :

Résultat = (xn x X 
n
) +... +  (x2 x X 
2
) + (x1 x X) + x0.

Soit E93h à convertir en base 10 :

E donne 14. Le résultat est donc (14 x 16 x 16) + (9 x 16) + 3 = 3731.

Pour convertir un binaire en décimal, le procédé est le même. Mais avec un peu d'habitude, il est possible d'aller plus rapidement :

Une méthode simplifiée
figure a1.04 Une méthode simplifiée [the .swf]

Dans ce premier exemple, nous passons tout d'abord à l'hexadécimal. Puis il nous suffit de calculer mentalement 10 + (7 x 16), puisque Ah vaut 10 et 7h vaut 7.

Une autre méthode simplifiée
figure a1.05 Une autre méthode simplifiée [the .swf]

Méthode intéressante, surtout si peu de bits sont allumés. Il suffit d'énumérer les puissances de 2, à partir de 1 pour le LSB et de ne conserver que celles qui sont en face d'un bit à 1.

15.1.7 Les outils

Nous avons vu qu'il n'est pas très courant d'avoir à convertir entre bases. C'est peut-être en phase d'apprentissage que vous aurez le plus besoin de pratiquer. Vous avez malgré tout besoin d'un utilitaire de conversion.

Vous trouverez sur internet quelques bons freewares. Attention, rares sont ceux réellement plus efficaces que la Calculatrice de Windows.

C'est peut-être le bon moment pour écrire votre propre utilitaire. Pensez alors à suffisamment modulariser et paramétrer vos fonctions pour pouvoir à l'occasion les réutiliser. Cette démarche présente un intérêt pédagogique certain.

La Calculatrice de Windows, en particulier dans ses dernières versions pour XP ou 2000, est suffisante pour un usage occasionnel. Elle propose, outre le décimal, le binaire, l'hexadécimal et même l'octal, mais ne permet de 0 à gauche dans aucune base. Elle a parfois des comportements étranges :

La Calculatrice de Windows, version XP
figure a1.06 La Calculatrice de Windows, version XP

Le signe - devant le nombre binaire est surtout gênant parce que la Calculatrice utilise par ailleurs le complément à 2. Un nombre décimal négatif est converti correctement en binaire et en hexadécimal, La touche +/- de changement de signe fonctionne, dans la largeur de bus sélectionnée. Mais il n'est pas possible de saisir en binaire un nombre considéré comme négatif pour, par exemple, connaître sa valeur. Il est alors nécessaire de ruser : soit le nombre 10010100b , qui en arithmétique signée vaut -108. Saisissons-le en binaire. Si nous passons en décimal, c’est 148, son interprétation en non signé, qui va s’afficher. En revanche si, avant le passage en décimal, nous inversons le signe du nombre binaire par la touche +/-, le résultat en décimal sera 108. Une bonne calculatrice multibase peut difficilement faire l'économie d'une touche signé/non signé.

Excel dispose, dans la macro complémentaire Utilitaires d'analyse , d’une série de fonctions ( HEXDEC , DECHEX , BINHEX , OCTHEX , etc.) de conversion. Un dépannage, tout au plus. Ce sont plus des macros texte qu'une véritable représentation numérique.

Si vous possédez une calculette scientifique, même bon marché, qui gère les bases, ne cherchez pas plus loin ; avec un crayon et une feuille de papier, c’est idéal. Vérifiez toutefois son comportement sur des entiers signés et son ergonomie : la saisie de caractères hexadécimaux alphabétiques est parfois d'une complexité rédhibitoire.

Ce que vous pouvez retenir, c'est qu'un utilitaire ou une calculatrice ne peut pas deviner si vous travaillez en mode signé ou non signé : un bon produit devra donc proposer ce choix et permettre de le modifier à tout instant. Sinon, ce ne sera qu'un produit approximatif.

15.2 Annexe B – Notions de logique

La logique intervient à trois niveaux au moins, dans notre apprentissage :

Dans la conception du micro-ordinateur, au chapitre Structure d'un micro-ordinateur . Il s'agit alors d'électronique, de logique câblée.

Dans la présentation du jeu d'instructions, plus particulièrement des instructions logiques bit à bit que sont NOT , OR , AND et XOR . Il s'agit de logique ou algèbre de Boole, et en programmation, sa maîtrise est un préalable à celle des masques.

Dans la conception des programmes. Vous aurez à réagir à certaines conditions ou combinaisons de conditions. C'est encore de la logique booléenne ou tout simplement de la logique de tous les jours.

Les objets concernés dans ces trois domaines sont respectivement des niveaux électriques, des bits et des assertions. Les raisonnements et les résultats sont interchangeables dans une large mesure. En fait, quelle que soit la nature du problème, il y a plusieurs (essentiellement trois) façons de l'aborder. La solution d'un problème de la vie courante peut fort bien être facilitée par une table de vérité ; un peu de bon sens suffit souvent en électronique logique.

Nous utilisons un mélange de termes anglais et français pour désigner les fonctions logiques. Ce choix est discutable. Nous avons toutefois décidé, comme dans l’ensemble de l’ouvrage, de privilégier à chaque fois que cela est possible le rapport avec le reste de la documentation et les mnémoniques de l’assembleur.

À notre décharge, constatons que l’utilisation du OR à la place du OU lève toute ambiguïté : il s’agit du OR de l’ordinateur, donc d’un OU INCLUSIF.

Explicitons en langage courant les quatre fonctions logiques donnant lieu à une instruction bit à bit dans le jeu d'instructions de la famille de processeurs x86.

Ces fonctions sont en première approche des boîtes acceptant un ou deux objets logiques à l'entrée et prenant, en fonction de la valeur de ces objets, une valeur logique résultante en sortie. Chaque mot est important :

  AND  : la sortie n'est vraie que si les deux entrées sont vraies. Pour louer une automobile, vous devez posséder le permis de conduire depuis plus d'un an ET être âgé de plus de 21 ans.

  OR  : la sortie est vraie si au moins une des entrées est vraie. Pour avoir une réduction sur les transports urbains, vous devez avoir plus de 60 ans OU des revenus faibles. À 72 ans et sans revenus, elle vous sera bien sûr également accordée.

  XOR  : la sortie est vraie si une des entrées est vraie, mais pas les deux. Fromage ou dessert serait un mauvais exemple, puisque le restaurateur acceptera que vous ne preniez ni l'un ni l'autre. Disons qu'une porte doit être ouverte OU fermée.

  NOT  : la sortie n'est vraie que si l'entrée est fausse. (NOT ouvert) = fermé, pour la porte, celle de l'exemple précédent qui ne peut pas être entrebâillée.

(A OR (NOT A)) est toujours vrai ; du moins dans la logique que nous utilisons, puisqu'il existe une logique aristotélicienne comme il existe une géométrie euclidienne. Nos ordinateurs sont euclidiens et aristotéliciens.

Qu'exprime le restaurateur quand il inscrit sur la carte "Fromage ou Dessert" ? Que vous devez obligatoirement prendre du fromage ou un dessert ? Non, ou alors c'est votre maman. Ce qu'il exige de vous, c'est que vous ne preniez pas les deux. Ce qui s'écrirait : NOT(Fromage AND Dessert), c'est-à-dire qu'il veut au moins que vous ne preniez pas de fromage ou que vous ne preniez pas de dessert, c'est-à-dire : (NOT Fromage) OR (NOT Dessert). Nous venons de redécouvrir en partie le théorème de De Morgan :

NOT(A AND B) = (NOT A) OR (NOT B) et NOT(A OR B) = (NOT A) AND (NOT B)

Si cela n'est pas suffisant, vous pouvez tenter la représentation par des ensembles, souvent très parlante. Le patatoïde A est en réalité l'ensemble des éléments qui vérifient A, et ainsi de suite. La zone grise correspond alors à l'ensemble des éléments qui vérifient la relation. La relation d'implication B=>A est un peu différente, puisqu'elle décrit plus une propriété géométrique avérée ou non qu'une zone, mais se comprend parfaitement.

Vue graphique de fonctions logiques
figure a2.01 Vue graphique de fonctions logiques [the .swf]

Vous serez peut-être un jour confronté à des tests psychotechniques, dans lesquels il vous est précisé, parmi d'autres détails croustillants, que tous les chauves aiment la couleur bleue et où l'on finit par vous demander si la voiture est rouge, si elle ne l'est pas ou enfin si vous ne pouvez fichtrement rien en dire. Un conseil : si votre instinct est fatigué, n'hésitez pas à crayonner une table de vérité, un De Morgan ou quelques patatoïdes. C'est redoutablement efficace. Le tout est de bien modéliser, en particulier l'implication. Tous les chauves aiment la couleur bleue se traduit par :

  IL est chauve => IL aime la couleur bleue.

  L'ensemble des chauves est entièrement contenu dans l'ensemble de ceux qui aiment la couleur bleue.

  IL aime la couleur bleue n'entraîne rien de particulier sur la calvitie de IL.

  IL n'aime pas la couleur bleue => IL n'est pas chauve.

Une fonction logique combinatoire   est une boîte possédant une ou plusieurs entrées logiques, et donnant un état logique en sortie qui ne dépend que de la combinaison des entrées, ce qui définit la logique combinatoire. Si cet état dépend également de l'état antérieur, il s'agira de logique séquentielle . Rappelons que entrées et sorties peuvent être ici des signaux électriques, des bits, voire des assertions, la boîte un circuit logique, une instruction, un groupe d'instructions par exemple. S'il y a plusieurs sorties à une boîte, nous définirons une fonction pour chacune d'entre elles.

L'état de la sortie ne dépendant que des entrées ; pour définir la fonction, il suffira de recenser toutes les combinaisons possibles des entrées et de renseigner l'état de la sortie correspondant. C'est ce tableau qui est appelé une table de vérité . Cette définition est l'inverse d'une définition intuitive. Voyons tout de suite un exemple :

Table de vérité du AND
figure a2.02 Table de vérité du AND [the .swf]

La sortie ne sera à 1 que si les deux entrées sont à 1 ; c'est ce que nous attendions d'une fonction AND . Dans la représentation V et F, nous voyons que V correspond à 1 et F à 0. Les deux tables sont représentées dans un ordre inverse. Généralement, les compilateurs considèrent le 0 comme false et toute autre valeur comme true . Les booléens n’étant pas reconnus par le C, il faut les déclarer en macros dans certains compilateurs (anciens). Il est élégant d’utiliser :

#define false 0
#define true !false

Avec 2 entrées, le nombre de cas sera de 4. Il est de 2 avec une seule entrée. Il est facile de passer à 3, le nombre de cas sera de 8. Construisons cette table :

Table à 3 entrées avec calcul
figure a2.03 Table à 3 entrées avec calcul [the .swf]

Pour passer à 4 entrées/16 lignes, nous voyons qu'il faut prendre le bloc 8 x 3, le dupliquer et lui ajouter une colonne composée de huit 0 puis de huit 1. Vous remarquez que si vous lisez les colonnes des entrées ligne par ligne, comme des nombres binaires, vous comptez de 0 à 7. Tout cela est cohérent. Si notre système comprend 12 entrées, le nombre de lignes de la table de vérité sera de 4096. Bien avant d'atteindre ce nombre, il faudra penser à utiliser d'autres méthodes.

Nous en avons profité pour mettre en évidence le rôle de la table de vérité dans le calcul. L'expression S à calculer est décomposée en sous-expressions, dont le résultat est connu. La dernière opération est le XOR entre les deux résultats intermédiaires. Seules les vraies entrées, indépendantes, déterminent le nombre de cas différents à étudier.

Une fonction à deux entrées est entièrement définie par sa table de vérité, par la valeur des 4 bits de la sortie. Chaque combinaison des X de la sortie définira une fonction différente. Or, il y a 16 combinaisons possibles de 4 bits, donc 16 fonctions. Cette systématique est une bonne habitude en logique et en assembleur. C'est une approche de mathématicien, mais cet art n'est nullement indispensable. De la même façon, nous recensons 2 fonctions à une seule entrée. Les fonctions à 1 et 2 entrées suffisent à comprendre et à construire les autres.

Notre démarche va maintenant être de lister ces fonctions, en construisant leur table de vérité, et de voir ensuite si elles ressemblent à quelque chose de connu.

Les fonctions à 1 entrée
figure a2.04 Les fonctions à 1 entrée [the .swf]

Seule NOT a une véritable signification, elle correspond en électronique à un inverseur et à l'instruction bit à bit NOT . L'identité recopie l’entrée sur la sortie. C’est en électronique un buffer. Les fonctions Nulle et Unité ne veulent pas dire grand-chose pour nous. Elles intéressent le formalisme mathématique, qui ne dira pas qu’un traitement ne fait rien, mais qu’il est égal à la fonction identité.

Les fonctions à 2 entrées
figure a2.05 Les fonctions à 2 entrées [the .swf]

Nulle et Unité ont déjà été commentées. Nous trouvons ensuite A et B, qui recopient en sortie une de leurs entrées. Ce sont les fonctions Identité vues au-dessus. Les mêmes surmontées d’une barre de négation se disent NOT A et NOT B et leur interprétation est évidente.

Les trois fonctions AND , OR et XOR ont déjà été commentées ; ce sont les seules à avoir une correspondance directe dans le jeu d’instructions. Nous allons revenir sur XOR .

Aussi importantes sont NAND  et NOR , respectivement un AND et un OR suivis d’un inverseur. Elles sont surtout connues des électroniciens. Le NAND est la porte logique la plus simple à réaliser technologiquement. Elles possèdent toutes deux la propriété de permettre de construire par assemblage toutes les autres fonctions. Dans le langage courant, NOR est le NI, c’est-à-dire ni l’un ni l’autre. NAND se dirait : pas tous les deux. Elles ont donc une vraie signification.

Sur le A sans B et le B sans A, rien à dire de particulier. Il devrait en être de même pour A=>B et B=>A, qui se disent A implique B, et l’inverse. Nous aurions pu, dans la foulée, les appeler pas A sans b et pas B sans A, et cela aurait peut-être mieux valu. Le rapport entre la notion intuitive d’implication est clair, mais alors, que représente la sortie S ? Une sortie à 0 voudrait dire que le couple en entrée n’est pas POSSIBLE. Il s’agit d’un débat philosophique, certainement passionnant mais qui aujourd’hui ne peut que nous embrouiller. De l’implication, retenons la représentation par des ensembles et le fait que si A entraîne B, alors B faux entraîne que A est faux. Et rien d’autre.

Reste la table intitulée EGAUX . Nous constatons effectivement que la sortie est à 1 si et seulement si les deux entrées sont égales. Nous remarquons de même que cette fonction est la fonction XOR inversée, qu’elle est à XOR ce que NAND est à AND . La sortie de XOR est à 1 quand et seulement quand ses deux entrées sont différentes.

Le XOR (voyez également cette instruction, dans la présentation du jeu d’instructions) est bardé de propriétés intéressantes. Nous voyons au chapitre Structure d'un micro-ordinateur   comment il peut être considéré comme un inverseur programmable : il inverse un bit ou le laisse inchangé, selon l'état de l'autre entrée considérée alors comme entrée de commande. Dans le même chapitre, nous voyons qu'il est parfois nommé demi-additionneur, puisqu'il fournit en sortie la somme de ses deux entrées, la retenue devant être obtenue par un AND . Enfin, si nous XORons deux fois, bit à bit, un mot de n bits, avec le même mot, dit la clé, nous retrouvons le mot initial. Le premier XOR est un codage, le second un décodage. Quel que soit X, (X XOR X) = 0 . Il est courant de mettre une mémoire ou un registre de cette façon, en assembleur :

mov eax, 0    ; pas bon
xor eax, eax  ; efficace, élegant, voire chébran

Les AND et OR sont très utilisés dans les opérations de masquage. Il existe plusieurs types de masquages :

  Les forçages à 1, où un ou plusieurs bits d'un mot sont positionnés à 1, les autres bits restant inchangés.

  Les forçages à 0, où un ou plusieurs bits d'un mot sont positionnés à 0, les autres bits restant inchangés.

  Les extractions pour test de nullité, où un (ou plusieurs, plus rarement) sont préservés, les autres étant positionnés à 0.

  Les extractions où un (ou plusieurs, plus rarement) sont préservés, les autres étant positionnés à 1, d'usage moins fréquent.

Sur un seul bit, les règles suivantes, évidentes, se déduisent des tables de vérité :

  (X AND 0) = (0 AND X) = 0 ;

  (X AND 1) = (1 AND X) = X ;

  (X OR 0) = (0 OR X) = X ;

  (X OR 1) = (1 OR X) = 1 ;

  (X AND X) = (X OR X) = X.

Des quatre premières règles se déduit le tableau suivant, concernant les effets de masquage sur un seul bit :

Comportement des OR et AND dans un masquage

Instruction

Valeur du bit

Effet

OR

1

Forçage à 1

OR

0

Préserve

AND

1

Préserve

AND

0

Forçage à 0

Nous en déduisons, au niveau des mots :

  Forçage de bits particuliers à 0 : AND avec un masque composé de 1, sauf les bits cibles à 0.

  Forçage de bits particuliers à 1 : OR avec un masque composé de 0, sauf les bits cibles à 1.

  Test de la nullité d'un bit : AND avec un masque composé de 0, sauf le bit cible à 1.

Ce sont les cas les plus fréquents, à appliquer sans avoir à trop réfléchir.

OR avec un masque composé de 1, sauf les bits cibles à 0 conduira à un résultat égal à -1 ( FFFFh sur 16 bits par exemple) si le, ou tous les, bits cibles sont à 1.

Exceptionnellement, il est possible de tester la nullité ou la non-nullité simultanée des bits cibles.

Enfin, signalons que certaines opérations panachées ne sont pas possibles : forçage des bits 0, 1 et 2 à 010, par exemple.

 

15.3 Annexe C – Little Endian, Big Endian, implantation des données en mémoire

Nous abordons ici les notions little endian et big endian, byte-order et byte-sexual, le problème NUXI , c'est-à-dire l'implantation des données en mémoire. C'est un sujet qui, très souvent, perturbe le programmeur : ces détails embrument notre cerveau, alors qu'ils peuvent être négligés dans presque tous les cas. Ce problème est de même nature que celui de la mémoire qui décroît quand la pile croît.

Il semble que, chez la plupart des humains, les problèmes haut/bas et droite/gauche (latéralisation) soient particulièrement mal vécus par la cervelle. C'est donc de telles nuisances mentales qu'il faut tenter d’enrayer une fois pour toutes.

Le mot français indien , qu'il soit océan, asiatique ou américain, se traduit par l'anglais indian .

La règle

Sur un microprocesseur très primitif, tous les registres ont une largeur imposée, le mot. Le bus de données a la largeur d’un mot ; la mémoire est accessible mot par mot, les opérations ne jouent que sur des opérandes mots et le résultat est un mot. Dans cette configuration, chaque mot est en mémoire à son adresse, un point c'est tout. Quant à la question de l'ordre des bits dans le mot, elle n'a pas de sens. Il est même possible de croiser les fils du bus de données d'un plan mémoire sans conséquences.

Imaginons maintenant une génération postérieure : une mémoire accessible octet par octet et des registres de largeur 32 bits, ou 4 octets, des dwords. Cette architecture a certainement de bonnes raisons d'être dans certain cas, mais elle est extrêmement pernicieuse. Le fait que les dwords puissent se chevaucher n'est pas une bonne chose.

La question de l'endianité, au cœur de ce chapitre, est celle de l'ordre de stockage en mémoire des octets constituant le dword. Cette notion prend un sens, par rapport au cas élémentaire, puisqu'il est possible d'accéder à des morceaux d'un dword d'adresse A, une adresse pouvant pointer en son sein, aux adresses A+1, A+2 et A+3. Voyons les deux solutions de stockage qui se présentent :

L'alternative endian
figure a3.01 L'alternative endian [the .swf]

La mémoire est représentée de façon intuitive, croissant vers le haut et vers la droite. La valeur à placer en mémoire est 12345678h . Dans les schémas de ce chapitre, le terme msb désigne plutôt un octet, le most significant byte . De même pour lsb .

Décrivons la règle choisie par Intel. C'est le choix de gauche. Si la mémoire est écrite octet par octet, en ordre croissant, alors l'octet 78h , de poids faible, est écrit en premier, et les autres en suivant jusqu'à l'octet de poids fort. Cet octet est le petit bout de la donnée. Nous avons donc écrit la donnée petit bout en premier, little-end-first en anglais. D'où little endian. Nous vous ferons grâce des explications absolument parallèles  qui aboutissent à big endian.

Rappel : dans l'architecture IA, jusqu'à ses plus récentes versions, la mémoire est accessible par octet. Nous pouvons dire que l'octet est l' atome du transfert mémoire. Réaffirmons donc clairement que, en assembleur, il est possible d'écrire une donnée 32 bits, le contenu de EAX par exemple, à une adresse quelconque, à l'octet près, en mémoire. Cela est vrai même s'il n'est pas souhaitable d'écrire n'importe où. La notion d'alignement n'est ici qu'une règle d'optimisation, certes importante.

Nous retiendrons qu'en little endian/Intel, l'adresse d'une donnée est également l'adresse de son octet faible. Et celle de son octet fort en big endian.

Il est fréquent de lire que la convention little endian complique inutilement une notion qui était évidente : big endian serait la façon naturelle de stocker des données en mémoire. Faisons la même manipulation que précédemment, mais sur la donnée 00000078h  :

L'alternative endian sur 00000078h
figure a3.02 L'alternative endian sur 00000078h [the .swf]

Testons le code suivant (C++Builder) :

byte * pb;
unsigned long dw = 0x00000078;
pb = (byte*) &dw;
Edit1->Text = IntToStr((int)dw);
Edit2->Text = IntToStr(*pb);

Par *pb , nous créons une variable octet, à la même adresse qu'une variable dw de 32 bits initialisée à 78h , soit 120. Elles affichent toutes deux cette valeur. En big endian, *pb aurait valu 0. C'est *(pb+2) qui aurait été un octet de valeur 78h . Cette dernière phrase n'a pas été testée.

Les langages de haut niveau proposent des opérateurs permettant d'extraire les parties haute et basse d'une variable. Leur utilisation rendra le code portable, à l'inverse de celle des pointeurs.

Donc, si big endian est typographiquement plus naturel, little endian semble avoir des arguments à faire valoir.

Il est peut-être utile de revenir sur un point : l'effet endian, l'image de la mémoire, dépend de deux facteurs :

  La taille de la donnée primitive, de l'atome mémoire : l'octet dans le PC.

  La taille de la donnée traitée si elle est différente de celle de l'atome qui, dans le PC, peut varier dans de grandes proportions, de 16 à 128 bits.

Parler d'une routine de conversion big-little sur PC est incomplet. Il faut préciser : une routine de conversion big-little sur PC pour des données de 32 bits.

Influence de la taille de la donnée
figure a3.03 Influence de la taille de la donnée [the .swf]

Cette convention est généralement transparente, puisque, sur une même machine, la même est utilisée lors de chaque transfert, en lecture comme en écriture. Elle prend de l'importance lors d'échanges particuliers de données, et dès que nous souhaitons accéder à une partie de la donnée, ce qui reste rare en programmation normale . Elle est à considérer lors de l'inspection de la mémoire.

Vous constaterez que les outils afférents, les débogueurs en particulier, permettent toujours de paramétrer la taille de la donnée inspectée. Devant des octets 01 02 03 04 05 06 07 08, ces outils afficheront 01 02 03 04 05 06 07 08 en mode byte, 0201 0403 0605 0807 en mode word, 04030201 08070605 en mode double-word.

Il peut arriver (mais certainement pas dans les machines qui nous intéressent) que l'ordre des bits prenne une importance. Ce point correspond aux mots anglais consistent  et inconsistent . L'architecture Intel est Consistent Little endian.

Ces notions se retrouvent dans les télécommunications et les réseaux. L'unité de transport est un paquet de mots. Le choix existe dans ce cas également entre les deux endians. Imaginons un ordinateur big endian transmettant un fichier texte à un ordinateur little endian, sur un réseau dont la taille de donnée de base transmise est le mot de 16 bits. Le texte UNIX sera alors reçu comme NUXI. D'où le terme "NUXI problem". Avec des mots de 32 bits, ce serait le "XINU problem". De bons titres pour une série B.

Attention, l'immense majorité des échanges inter-plates-formes, réseaux ou autres, se passent de façon transparente, à l'aide des couches logicielles successives. Ce sont justement les programmeurs de certaines de ces couches qui seront impliqués dans le problème endian.

Avec DEBUG

Si vous ne souhaitez pas effectuer de manipulation complexe, lancez DEBUG et saisissez :

a 100
mov ax, 1030
mov bx, 0204
add ax, bx
mov word ptr[110], ax

Ne vous préoccupez pas d'instruction de fin, désassemblez puis lancez le mode Trace pour 4 instructions et enfin affichez la mémoire.

Séance de test minimale
figure a3.04 Séance de test minimale

Le mov ax, 1030 se code en B8 30 10 . C'est un effet de bord de little endian ; mais, en y réfléchissant un peu, c'était plus que prévisible : un automate doit être câblé quelque part dans la CPU, prêt à transférer une donnée 16 bits en little endian. Il est donc évident qu'une donnée immédiate doit être dans le code objet dans le même format que celui qu'il aura en mémoire ou que ce sera plus facile pour le moins. C'est peut-être à cette occasion que little endian semble le plus tordu.

Les résultats du mode Trace n'appellent pas de commentaires, la dernière instruction désassemblée, un JB ou toute autre instruction, n'étant que le fruit du hasard et ne sera pas exécutée.

Nous constatons que 1234h a bien été stocké en mémoire au format little endian, 34h en 0110 et 12h en 0111.

Avec DELPHI

Nous allons saisir un programme de test sous Delphi, à partir du squelette présenté par ailleurs. Reportez-vous comme d'habitude au CD-Rom. Nous avons utilisé comme donnée primitive une variable I64_0 de type Int64 , à priori propre à Delphi. Mais, en fait, les architectures 64 bits arrivent à grands pas et ce format Int64 est conforme à une norme de fait. L'assembleur, s'il n'implémente pas dans son jeu d'instructions standard (hors FPU, MMX et autres SSE) d'instruction MOV sur 64 bits, propose CDQ, qui étend une donnée signée 32 bits sur 64 bits dans EDX:EAX . Dans cette norme, un entier 64 bits est implanté au format little endian et passe en registres dans EDX:EAX . La manipulation consiste à initialiser la variable I64_0 à la valeur 8F7E6D5C4B3A2910h , et à initialiser ensuite une batterie de variables de taille inférieure à partir de I64_0 . Nous y ajouterons quelques lignes de code pour rapidement faire le tour de l'implantation des données les plus classiques en mémoire.

Voici le listing de notre programme de test :

var
I8_0, I8_1  :Byte;
I16_0, I16_1:Word;
I32_0, I32_1:Longword;
I64_0       :Int64;
ch_0        :Pchar;
begin
  try
    with MemoSortie.Lines do begin
    I64_0 := $8F7E6D5C4B3A2910;
    ch_0  := 'UNIX';
    asm
      mov eax, dword ptr[I64_0 + 0]
      mov I32_0, eax
      mov eax, dword ptr[I64_0 + 4]
      mov I32_1, eax
      mov ax, word ptr[I32_0 + 0]
      mov I16_0, ax
      mov ax, word ptr[I32_0 + 2]
      mov I16_1, ax
      mov al, byte ptr[I64_0 + 0]
      mov I8_0, al
      mov al, byte ptr[I64_0 + 5]
      mov I8_1, al
 
      lea eax, byte ptr[ch_0]
      mov eax, [eax]
      end;
    Add(IntToHex(I64_0, 16) + #$0D + #$0A);
    Add('I32_0: ' + IntToHex(I32_0, 8));
    Add('I32_1: ' + IntToHex(I32_1, 8) + #$0D + #$0A);
    Add('I16_0: ' + IntToHex(I16_0, 4));
    Add('I16_1: ' + IntToHex(I16_1, 4) + #$0D + #$0A);
    Add('I8_0 : ' + IntToHex(I8_0, 2));
    Add('I8_1 : ' + IntToHex(I8_1, 2) + #$0D + #$0A);
    Add('ch_0 : ' + ch_0);
    end;

Observez la facilité apportée par l'assembleur intégré. Pour obtenir le même résultat en code Pascal pur, ce serait beaucoup plus long et moins lisible, voire impossible par manipulation directe sur les pointeurs. En C++, c’est un peu plus facile. Ce commentaire ne constitue pas un point négatif pour Pascal Objet : c'est un langage fortement typé, plus rigoureux que C++. L'assembleur BASM est justement là pour ce type de manipulations.

Lançons le programme, cliquons sur le bouton Action 1 et observons.

Résultat attendu...
figure a3.05 Résultat attendu...

Le résultat est en effet conforme à ce que nous attendions, à notre représentation de la mémoire. Nous constatons que l'adresse d'une donnée de taille supérieure à l'octet est celle de son octet de poids faible. Nous constatons également que les autres octets de la donnée occupent des adresses supérieures. Ce qui se traduit bien dans la représentation suivante, qui pourra faire office de synthèse.

Représentation des données
figure a3.06 Représentation des données [the .swf]

Remarquez que nous avons choisi, dans cette illustration, de représenter la mémoire à l'envers .

 

15.4 Annexe D – Cross-assembleurs et microcontrôleurs

Si vous lisez ces lignes, c’est que vous êtes attiré par la programmation en assembleur sur PC. Professionnellement, mais peut-être en amateur, parce que vous avez une âme de bidouilleur et le désir de tout contrôler dans un développement. Là, vous êtes frustré. Un jeu d’instructions pléthorique, des temps d’exécution imprévisibles, un programme qui s’exécute aux instants où Windows le veut bien, donc une programmation temps réel impossible. Peut-être possédez-vous un aquarium, un train électrique ou un labo photo, la vraie photo qui empeste l’hydroquinone et l'hyposulfite. Si, de plus, vous étiez lecteur du Haut Parleur , aucun doute, ce chapitre est fait pour vous.

Cross-assembleurs

Un cross-assembleur, comme un cross-compilateur, est tout simplement un assembleur qui ne s’exécute pas sur la machine cible. Nous pouvons d’ailleurs imaginer que la machine cible n’existe pas encore (il faut bien assembler les ROM quelque part) ou que ce soit une carte de gestion de porte de garage, aux capacités d’édition de texte fort limitées.

Un assembleur est un programme prenant en entrée un fichier texte et générant un fichier objet. Ce programme n’a aucune raison d’être écrit en assembleur. Il existe même des dizaines d’assembleurs, rudimentaires ou non, écrits en Basic. Donc, dans leurs versions ligne de commande, porter un assembleur d’un environnement à un autre n’est pas un problème. Simplement, le cross-développement micro/micro, PC-MAC le plus souvent, présente peu d’intérêt.

Le fait est que le PC est une excellente machine de bureau, qu’il a envahi la planète ; il est donc souvent choisi comme terminal de saisie et de développement. Très certainement pour dégrossir des applications devant fonctionner sur de gros systèmes. Certaines remarques sur le respect ou non de la norme IEEE 754 par la FPU peuvent nous laisser supposer des prédéveloppements en Fortran, destinés à des applications s’exécutant sur des machines plus puissantes.

Nous trouvons du cross-assemblage vers d’anciennes machines 8 bits, à base de 6502 ou de Z80, souvent lié à des émulations de ces vieux coucous sympathiques. La seule motivation de ces exercices est le plaisir. À chacun ses soirées Casimir ou Goldorak.

L’industrie de l’électronique fait largement appel au PC en tant qu’outil de développement. Nous avons signalé que toute fonction logique, même très complexe, peut s’intégrer dans une simple puce. C’est par exemple le cas des chipsets de nos PC. Il existe des circuits logiques non terminés, un peu comme une (xx)ROM vierge. Ces circuits, les réseaux logiques programmables, sont constitués de milliers de portes logiques de base, les connexions entre elles restant à réaliser. Vu de l’extérieur, c’est donc un énorme tableau de connexions. Sur le PC, s’exécutent des programmes, des compilateurs, qui permettent de passer de fonctions logiques saisies graphiquement à une liste de connexions à obtenir. Certaines versions, parfois onéreuses, de ces circuits sont reprogrammables. Une fois obtenue une programmation adéquate, à l’issue d’une phase de développement/débogage, une version industrielle à faible coût du circuit pourra être mise en production. Il existe également les DSP, processeurs digitaux de signal, dont il faut programmer les fonctions.

Mais, le domaine qui nous intéresse particulièrement est celui des microcontrôleurs. Nous savons qu’il s’agit ni plus ni moins que de microprocesseurs dotés de mémoire, d’entrées/sorties et plus généralement de la circuiterie annexe, permettant de concevoir un système opérationnel avec très peu de composants externes. Certains de ces circuits ont accédé à la célébrité du fait de l’influence bénéfique qu’ils avaient sur la qualité de l’image et du son émis par certaines chaînes payantes du réseau hertzien. Il semble que d’autres jouent un rôle un peu analogue par rapport à des consoles de jeu.

Le développement autour de ces composants se fait généralement sur une plaquette d'essai, soit standard soit développée pour le projet. Il existe généralement, d'un même modèle, des versions de développement et des versions de production. Seules les premières nous intéressent ici. Il en existe plusieurs types, différents par la façon d'écrire et d'effacer la mémoire programme. Il a même existé des modèles piggy back, affublés sur leur dos d'un support pour un circuit d'EPROM.

La solution la plus confortable pour le développeur, devenue courante et bon marché, est d'avoir sur un seul circuit de test une version Flash EPROM, programmable sur place. Cette carte est en liaison série avec un PC. Sur le PC fonctionne un cross-assembleur plus ou moins évolué, qui génère une image binaire de la ROM. Cette image est instantanément chargée sur la carte et le microcontrôleur est reprogrammé. Le débogage est ainsi rendu confortable. Ce qui est presque dommage, puisque c'était un des derniers domaines où il fallait réfléchir avant de coder.

Dans cette famille des microcontrôleurs, un produit sort nettement du lot : la gamme des PIC du fabricant Microchip.

Les PIC de Microchip

Outre l'étendue de la gamme et la qualité des produits, le dynamisme commercial de Microchip est pour beaucoup dans le grand succès de la gamme PIC. En plus, bien entendu, de la qualité des circuits et de leur prix particulièrement attractif.

Microchip a pris le parti de ne pas réserver son support aux seuls professionnels. Le site (en anglais) est particulièrement bien fait, malgré une grande richesse : presque tout est téléchargeable et, à une ou deux exceptions près, gratuitement.

Page d'accueil de www.microchip.com
figure a4.01 Page d'accueil de www.microchip.com

Sur le site, figurent des ressources pédagogiques, toute la documentation, au format .pdf , de tous les circuits disponibles, et enfin et surtout le logiciel de développement intégré MPLab. Ce logiciel possède un mode de simulation qui permet même de s'amuser, avant d'avoir fait l'acquisition de quoi que ce soit. C'est du bonheur…

De plus, il existe sur l'Internet en français une bonne activité autour de ces circuits, et au moins un bon site de référence. En revanche, pas de microchip.fr, semble-t-il.

Pour caractériser cette famille de circuits, nous dirons qu'ils sont à jeu d'instructions réduit RISC , architecture Harvard, et le nombre de références qui peut sembler immense est tempéré par une grande modularité : si une automobile propose 4 options indépendantes, cela donne 16 modèles. S'il y a 16 couleurs de peinture, vous arrivez à 256 références. 3 motorisations, 768. Les PIC ne sont disponibles qu'en noir, mais avec de nombreuses options.

L'architecture Harvard, rappelons-le, sépare physiquement le programme des données. Dans les données, sont compris tous les registres, dont les entrées/sorties.

Pour pouvoir réellement jouer avec ces circuits, il faudra résoudre le problème de la carte de tests et éventuellement du programmateur. Pour ce dernier point, il semble qu'il soit devenu inutile grâce aux versions Flash. En plus des cartes proposées par Microchip, il existe, de-ci de-là dans le commerce et dans la presse, des cartes d'évaluation. Nous avons relevé chez Électronique Diffusion un kit PICCOLO : le circuit imprimé est proposé pour 2,30 €, le total devrait se situer entre 20 et 30 €. Ces cartes peuvent facilement être réalisées par n'importe quel amateur un peu équipé : celle dont nous allons parler est faite en procédé classique, transferts en simple face et reste aérée. Elle mesure 100 x 160 mm et embarque même le transformateur d'alimentation.

Nous n'allons bien entendu pas détailler ces circuits. Pour illustrer cette présentation, nous avons utilisé la version 5.70 de MPLab. Il s'agit d'une version compatible Windows 3.1, mais qui fonctionne parfaitement sous Windows XP. Nous ne l'avons toutefois testée qu'en simulation sous cet environnement. Une version 6 est disponible, du vrai Windows cette fois-ci ; mais, pour l’instant, tous les PIC n’ont pas été implémentés.

Nous avons utilisé du matériel pédagogique en provenance de l'école Don Bosco, à Tournai en Belgique, avec l'autorisation de l'auteur, Jean-François Dedecker, professeur d’électronique. Qu’il en soit remercié. Nous reproduisons pour information les schémas d’implantation et électronique de cette carte ; vous pourrez juger de la simplicité. Seuls deux circuits actifs sont à ajouter, et encore s’agit-il des transceiver RS232 et du port série synchrone, utilisable en I2C par exemple.

Schéma de principe
figure a4.02 Schéma de principe [the .swf]

 

Schéma d’implantation
figure a4.03 Schéma d’implantation

Le circuit choisi est la référence 16F874. C’est un circuit Flash, en boîtier DIL 40 (à l’ancienne, le même que le 8088). Malgré ces caractéristiques, il est proposé à 13 € chez un revendeur grand public de province. Résumons ses caractéristiques :

  Jeu d’instructions RISC , 35 instructions seulement, effectuées en un seul cycle, deux pour les sauts. Horloge jusqu’à 20 MHz.

  368 mots de 8 bits de données en RAM, 256 mots de 8 bits de données en EEPROM. Cela peut sembler peu, mais c’est bien dans ce type d’applications que la RAM pourra contenir les résultats de calculs ; l’EEPROM, sauvegardée, permettra de retrouver les valeurs importantes au redémarrage.

  Le programme occupe une zone de mémoire Flash de 8 Kmots de 14 bits. La taille du mot varie avec les modèles de la gamme ; chaque instruction est ainsi codée sur un seul mot.

  Le code peut être protégé contre la curiosité par un mot de passe. Un circuit de chien de garde est prévu en interne. En gros, si le processeur fonctionne normalement, il va effectuer une action régulièrement. Si cette action n’est pas effectuée, il peut en être déduit qu’il est bloqué. Il sera par exemple relancé par un reset.

  En termes d’entrées/sorties, il embarque 3 timers, des ports parallèles, un port série asynchrone, un port série synchrone. Il peut facilement gérer une liaison via tel ou tel protocole industriel de terrain. Puisque nous y sommes, remarquons que ce circuit sera tout à fait adapté pour rendre intelligent un capteur. Il gère de plus des E/S analogiques et la modulation de largeur d’impulsion, PWM.

Pour plus de détails, ainsi que la cartographie mémoire relativement compliquée, reportez-vous au CD-Rom et au fichier .pdf adéquat.

Pour ce circuit et cette implantation, nous disposons d’un certain nombre de programmes débogués. Nous en choisissons un, pour sa simplicité. L’intérêt de la démarche est de tester sous simulateur un programme qui n’a pas été écrit dans cette optique.

;**********************************************************************
;* DEDECKER Jean-François                                             *
;*          Programme de test                                         *
;*          allumage d'une LED et d'un buzzer                         *
;*          sur appui d'une touche                                    *
;*                                                                    *
;*                                                     08/02/02       *
;**********************************************************************
;
;
;Définitions et inclusions
;*************************
 
        list    p=16f874, c=90, n=60
        include "p16f874.inc"
BUZ     equ     0
 
;programme principal
;*******************
 
        org 0
        goto 5
        org 5
 
debut
        banksel TRISB           ;se placer dans la bonne page
        movlw   0H              ;mettre
        movwf   TRISB           ;      le port b
        banksel PORTB           ;                en
        clrf    PORTB           ;                   sortie
 
        banksel TRISC           ;se placer dans la bonne page
        bcf     TRISC,BUZ       ;mettre le buzzer en sortie
                                ;(on aurait pu faire un BUZ equ 0)
 
waiton
        banksel PORTC           ;se placer dans la bonne page
        btfsc   PORTC,2         ;tester si le bouton est appuye
        goto    waiton          ;si non, retester
        call    allum           ;si oui, allumer led et buzzer
                                ;et attendre que le bouton soit relache
waitof
        banksel PORTC           ;
        btfss   PORTC,2         ;tester si le bouton est relache
        goto    waitof          ;si non, retester
        call    eteint          ;si oui, eteindre led et buzzer
        goto    waiton          ;et attendre que le bouton soit appuye
 
allum
        banksel PORTB           ;
        bsf     PORTB,0         ;allume la led 0
        bsf     PORTC,BUZ       ;allume le buzzer
        return
 
eteint
        banksel PORTB           ;
        bcf     PORTB,0         ;eteint la led 0
        bcf     PORTC,BUZ       ;eteint le buzzer
        return
 
 
        end

Les langages assembleur, c’est un peu comme les chats et les chiens : nous passons notre temps à en commenter les différences, mais en réalité c’est très ressemblant. Celui-ci fait presque exception à première vue.

Le vecteur RESET est à 0, d’où le goto 5 à cette adresse.

Les tests btfss – qui se lit Bit Test du registre F et Saute (skip) si Set (à 1) –, par exemple, sont un peu particuliers : ils ne font rien ou ne sautent qu’une instruction. C’est ce qui explique qu’aucun opérande n’indique d’adresse de saut. Les fonctions test et saut sont ainsi séparées.

Lançons MPLab, puis chargeons simplement, par File / Open , button.asm . Vérifions les options dans Options / Development Mode . Nous devons choisir le PIC16F874, ainsi que valider l’outil simulation :

Paramétrages de base
figure a4.04 Paramétrages de base

Négligeons les avertissements quand nous cliquons sur OK. Même sans avoir créé de projet, allons dans Project / Build Node  ; dans la fenêtre qui apparaît, paramétrez conformément à la capture d’écran.

Options de construction
figure a4.05 Options de construction

La compilation se termine sur un joyeux Build completed successfully . Il ne faut pas se laisser troubler par les deux messages qui ne sont que des warnings.

Examinons maintenant de plus près la barre d’outils en dessous de la barre de menus. D’abord, se souvenir de Windows 3.1 : point d’info-bulles d’aide, mais une barre d’état en bas de la fenêtre. C’est là qu’apparaît un résumé du rôle du bouton que survole la souris.

Le bouton le plus à gauche, au symbolisme imprécis, sert à basculer la barre d’outils d’une configuration à une autre, lui-même étant de toutes les configurations. Sinon, cela ne peut pas marcher. Choisissons-en une qui comprenne au moins les boutons  ROM , RAM , SFR et les traces de pas. Cliquons sur SFR et RAM ou même simplement les registres SFR, dans un premier temps. Arrangeons les fenêtres au mieux, puis commençons à nous promener dans le code source. Pas à pas, donc par les boutons traces de pas. Vous avez de façon habituelle le choix entre les deux modes de pas à pas : tracer dans les CALL ou pas. Découvrez le fonctionnement du programme et de MPLab ; il n’y a pas de grosse difficulté.

En plein débogage
figure a4.06 En plein débogage

Vous aurez sans doute envie de mettre en commentaire, comme sur la capture d’écran, la ligne goto waitof . Faites-le donc et acceptez la reconstruction qui vous sera proposée. Vous voyez s’allumer et s’éteindre led et buzzer.

Il y a beaucoup de choses à découvrir dans ce programme. Remarquez que le débogage de ce type d’applications de cette manière n’est qu’un minimum. Vous découvrirez, dans les menus, l’aide, la documentation, qu’il existe des produits logiciels et matériels permettant d’émuler et de déboguer sur carte l’ensemble de la gamme.

Voilà, il est maintenant temps de mettre le fer à souder en chauffe et d’aller chez votre pucier habituel faire l’emplette de quelques composants. Là au moins, vous ne vous plaindrez pas de ne rien contrôler.

Envoyer un message à l'auteur

se connecter pour pouvoir écrire

Ce site est créé et maintenu (laborieusement) par Pierre Maurette
hébergé par 1 & 1