the Removers
Accueil du site > en Français > les Articles > la Programmation > Les branchements conditionnels

Assembleur 68000

Les branchements conditionnels

... peuvent vous changer la vie !

vendredi 6 mai 2005, par Stabylo

Qui ne connaît pas le fameux triptyque if-then-else, commun à tous les langages de programmation ? En assembleur 68000, les choses sont un peu différentes. Cela peut sembler déroutant au premier abord, mais il s’agit en réalité d’un véritable atout de ce langage.

Le branchement conditionnel est l’un des piliers des programmes informatiques. Sous sa forme la plus simple, il s’agit d’effectuer deux actions distinctes suite à une question dont la réponse est oui ou non. Dans les langages de programmation évolués, cela se représente avec les trois instruction si, alors et sinon.

Ce qu’il y a de similaire en assembleur 68000, c’est qu’à une instruction de test, fait suite une instruction de branchement conditionnel. Ainsi armé, le créateur de programme dispose donc bien de la fonction recherchée, c’est-à-dire orienter le programme vers deux branches d’exécution distinctes. Là où se situe la différence, c’est dans la forme des instructions de test qui posent la question et dans celle des instructions de branchement qui permettent de distinguer la réponse.

 Les instructions de test

La particularité des instruction 68000, c’est que pratiquement toutes les instructions donnent lieu à un test. Cette particularité est souvent étrangère aux débutants qui ne pensent pouvoir s’en sortir qu’avec les instructions de test (tst) et de comparaison (cmp). Pourtant, le créateur averti saura repenser son programme pour tirer parti des tests réalisés gratuitement par la grande majorité des autres opérations.

La comparaison (cmp) et la soustraction (sub)

Pour illustrer ce fait, savez-vous quelle est la différence entre l’instruction cmp (comparaison) et sub (soustraction) ? J’en vois parmi vous qui ouvrent de grands yeux pour s’interroger sur mon état de santé mentale, ceux-ci se disent sans doute que ces opérations n’ont strictement rien à voir.

En vérité, les opérations sont certes distinctes, mais les instruction sont pourtant pratiquement identiques. La seule distinction réside dans le fait que cmp « oublie » le résultat de la soustraction alors que sub l’écrit dans l’opérande de destination. Etonnant ? Oui, mais en vérité, ce fonctionnement est tout à fait logique.

Au lieu de comparer deux nombres entre eux, la stratégie de l’instruction cmp consiste à comparer leur différence à zéro. C’est donc une soustraction qui est faite et les indicateurs d’état [1] sont positionnés de la même manière qu’ils le seraient avec sub.

Le test (tst) et la copie (move)

Dans le même ordre d’idée, l’instruction move (copie) effectue toujours un test de l’opérande copiée.

Attention toutefois, l’instruction movea de copie vers un registre d’adresse se comporte différemment ; à l’inverse de move, l’instruction movea ne modifie aucun indicateur d’état [1], donc elle n’effectue pas de test.

Tirer parti de ces tests gratuits

Les techniques d’optimisation de l’assembleur 68000 font beaucoup appel aux tests gratuits effectués par les instructions ordinaires.

Le créateur de programme cherche donc souvent des astuces dans les conclusions indirectes que l’on peut tirer des résultats des opérations. La plupart du temps, il utilise des instructions d’une manière qui ne correspond pas à leur usage premier ; c’est sa valeur ajoutée, tirée de sa créativité.

L’énoncé de techniques d’optimisations fondées sur ces principes dépasse cependant très largement l’objet de cet article. C’est pourquoi nous les laisserons de côté ce sujet pour rester concentrés sur le bon usage des instructions de test et de branchement.

 Les instructions de branchement sur condition simple

L’instruction de branchement principale du 68000 est communément appelée « Bcc », mais elle ne s’écrit pratiquement jamais ainsi. Dans cette appellation, l’abréviation cc n’est là que pour signifier qu’à sa place doit être précisé l’un des 16 « Codes de Condition » du 68000.

Pour commencer, voici les huit codes de condition simples.

Code de condition  Condition de branchement
cc (Carry Clear) C=0
cs (Carry Set) C=1
ne (Not Equal) Z=0
eq (EQual) Z=1
pl (PLus) N=0
mi (MInus) N=1
vc (oVerflow Clear) V=0
vs (oVerflow Set) V=1

Ces codes de condition sont plus « simples » que leur congénères que nous verrons ensuite car chacun d’entre eux permet de prendre une décision à partir d’un seul indicateur d’état. Pour comprendre le choix qui va être fait, il suffit donc de connaître la signification de l’indicateur associé, choisi parmi les quatre suivants.

Carry, la retenue

Le bit C vaut 1 lorsque l’opération précédente a donné lieu à une retenue. Par exemple, l’addition sur un octet de $F0+$F0 vaut $1E0 ; le résultat sur un octet — qui ne peut s’écrire que sur deux chiffres hexadécimaux — est donc $E0 avec une retenue, Carry vaut alors 1.

oVerflow, le dépassement

Le bit V vaut 1 lorsque l’opération précédente a donné lieu à un dépassement de capacité. Cela n’a de sens que lorsqu’on interprète les données en entrée comme des nombres signés. Pour reprendre l’addition précédente, $E0+$E0 signifie (-16)+(-16). Le résultat -32 est correctement représenté par $E0 donc il n’y a pas de dépassement de capacité. En revanche, lorsqu’on additionne $90+$90, c’est-à-dire (-112)+(-112), le résultat $120, soit -144, s’écrit $20 sur un octet. Cette valeur $20 est interprétée comme le nombre 32, donc elle ne représente pas correctement le résultat de l’addition. Le bit V est alors positionné à 1.

Si la conversion entre $90 et -112 vous surprend, notez que l’interprétation des nombres signés et non signés est abordée plus précisément ci-dessous.

Zero, la nullité

Le bit Z vaut 1 lorsque le résultat de l’opération précédente est nul. C’est le cas lorsqu’on additionne deux nombres qui s’annulent, mais également lorsqu’on efface une donnée avec clr ou lorsqu’on copie une donnée nulle avec move.

Negative, la négativité

Le bit N vaut 1 lorsque le résultat de l’opération précédente est négatif, c’est-à-dire lorsque son bit de poids fort vaut 1. Il s’agit du 8e bit pour les opérations sur un octet, du 16e bit pour les opérations sur un mot et du 32e pour celles sur un mot long.

 Les instructions de branchement sur condition arithmétique

La seconde catégorie de codes de condition s’utilise pour prendre des décisions sur le résultat s’opérations arithmétiques. Les opérations arithmétiques sont — entre autres — l’addition, la soustraction, la multiplication et la division, mais aussi les décalages arithmétiques (asl et asr) et comme nous l’avons vu dans la première partie, la comparaison puisqu’elle fonctionne comme une soustraction !

Les codes de condition arithmétique sont donc très utiles pour les comparaisons. Cependant, il est important de savoir si les nombres manipulées doivent être interprétés comme signés ou non signés.

Interprétation des nombres signés et non signés

Cette distinction entre les nombres binaires repose les deux manières différentes de les interpréter. Le tableau suivant les illustre pour les valeurs prises par un octet.

Énumérations Interprétation signée Interprétation non signée
Valeurs hexadécimales $00,$01,...,$FF $80,$81,...,$FF,$00,$01,...,$7F
Valeurs décimales équivalentes 0...255 -128,-127,...,-1,0,1,...,+127

Comme vous le voyez, les codages hexadécimaux couvrent la même étendue de valeurs, mais en revanche les interprétations diffèrent. Dans le cas signé, $88 signifie -120, tandis que dans le cas non signé, le même $88 signifie 136.

Si on se limite aux nombres positifs (de 0 à 127), il n’y a pas de différence entre ces deux interprétations. En revanche, il en va autrement lorsque l’on manipule l’autre moitié des valeurs.

Pour l’opération de comparaison, cette différence s’illustre ainsi :

  • Si on compare $A0 à $B0
    • non signé : 160 < 176,
    • signé : -60 < -50,
      les deux comparaisons parviennent au même résultat.
  • En revanche, si on compare $70 à $90
    • non signée : 112 < 144,
    • signé : 112 > -112,
      cette fois, le résultat des comparaisons est différent.

Au cours de la création d’un programme, il est donc important de bien cerner le type de données que l’on manipule afin de choisir le bon code de condition. Sans cela, certaines données risqueront de donner lieu à un branchement erroné et vous aurez des surprises !

Les codes de condition arithmétique

Selon l’interprétation des nombres, les conditions arithmétiques se divisent en deux groupes jumeaux. En premier lieu viennent les conditions d’arithmétique signée.

Code de condition Condition de branchement après « cmp d0,d1 »
gt (Greather Than) d1 > d0
ge (Greather or Equal) d1 >= d0
le (Lesser Than) d1 <= d0
lt (Lesser or Equal) d1 < d0

Ensuite, viennent les conditions d’arithmétique non signée.

Code de condition Condition de branchement après « cmp d0,d1 »
hi (HIgher) = cc d1 > d0
hs (Higher or Same) d1 >= d0
ls (Lower or Same) d1 <= d0
lo (LOwer) = cs d1 < d0

Avez-vous remarqué ? Nous avons déjà parlé des branchements bhi et blo puisqu’il d’agit d’une autre façon de nommer bcc et bcs. Attention, cette double identité n’est pas qu’une curiosité, il s’agit d’un piège propre à dérouter les débutants ! En effet, ceux-ci sont parfois loin d’imaginer que bcc et bcs servent à comparer des nombres, et donc ils ne les utilisent pas.

A la place, ils s’arrangent pour avoir toujours des nombres positifs afin de pouvoir utiliser des bgt et blt au noms moins louches. Cependant, cela les oblige à travailler en 16 bits pour des comparaisons sur 8 et en 32 bits pour des comparaisons sur 16. Jusqu’ici tout va bien, mais quand se présente un comparaison non signée sur 32 bits, ils font « aïe ! » parce qu’ils sont coincés. Dans la vraie vie, ce problème se pose rarement et c’est ce qui les sauve. Mais ils continuent longtemps à mettre inutilement leurs registres à zéro avant d’y mettre les nombres non signés qu’ils veulent comparer avec des codes de condition d’arithmétique signée. C’est peu élégant.

 Vous vous coucherez plus intelligents ce soir

Par ailleurs, il existe des cas où l’utilisation d’une comparaison non signée permet d’optimiser le code. Il en va ainsi pour les comparaisons de clipping.

Les algorithmes de clipping consistent généralement à comparer des coordonnées x et y à des bornes inférieures et supérieures qu’il ne faut pas dépasser. Dans le cas général, l’algorithme consiste à effectuer les quatre comparaison suivantes.

  • x < xmin ?
  • x > xmax ?
  • y < ymin ?
  • y > ymax ?

Mais bien souvent, nous somme dans le cas particulier où xmin=0 et ymin=0, et il est possible d’exploiter cette particularité pour n’avoir que deux comparaisons à faire.

Si nos coordonnées sont codées sur des mots de 16 bits comme c’est habituellement le cas, x<0 signifie que x est compris entre -1 et -32768, c’est-à-dire entre $FFFF et $8000. Interprétés comme non signés, ces nombres valent 65535 et 32768, et sachant que xmax est positif et donc inférieur à $7FFF, la seule comparaison non signée x<xmax nous assure que x est positif.

Les deux comparaisons non signées suivantes suffisent donc.

  • x < xmax ?
  • y < ymax ?

En tirant parti de notre cas particulier, nous avons multiplié la vitesse de notre algorithme de clipping par deux. Souvenez-vous en !

 Conclusion

L’utilisation adéquate des codes de conditions permet de créer des programmes assembleur plus précis et parfois plus optimisés. Tirer parti des astuces entre comparaisons signées et comparaisons non signées est une chose qu’aucun compilateur ne pourra faire à votre place. Laissez libre court à votre créativité !

Notes

[1] Les indicateurs d’état du 68000 sont des bits contenus dans le registre CCR — Code Condition Register. Il s’agit de N pour Negative, Z pour Zero, V pour oVferflow (le dépassement) et C pour Carry (la retenue). Ces indicateurs sont modifiés par les résultats des instructions, puis servent de base pour vérifier les conditions des branchements conditionnels.

SPIP | squelette | | Plan du site | Suivre la vie du site RSS 2.0