CODINGCLASSEURS WEKA ★ Comment exploiterr toutes les ressources et augmenter les performances de votre AMSTRAD CPC ★

4/2.5.1 Initiation au langage machineCoding Classeurs Weka

4/2 - Assembleur Z80 : Définitions et rappels de base

4/2.5 - Cours de programmation

4/2.5.1 Initiation au langage machine

Le langage machine et l'assembleur font presque toujours peur à l'amateur ayant abordé l'informatique par la programmation en Basic. Il est vrai que le listing d'un logiciel machine n'a rien de très engageant, et que les manuels d'accompagnement des micro-ordinateurs décrivent les instructions donnant accès au langage machine comme réservées à des programmeurs avertis. Seulement, amateur averti, il faut bien le devenir un jour !

La partie 4, chapitre 2 contient toutes les données nécessaires pour programmer correctement l'Amstrad en assembleur, à condition d'avoir déjà une certaine habitude du travail sur microprocesseur.

Cette initiation a pour but de rendre le langage machine accessible à l'amateur uniquement familier du Basic : après avoir assimilé cette « mise à niveau », il pourra attaquer de plain-pied la découverte de toutes les subtilités de l'assembleur sur Amstrad.

I. Assembleur ou langage machine ?

La confusion est fréquente entre « assembleur » et « langage machine ». Il faut dire que la différence est assez subtile, mais importante.

L'Amstrad est bâti autour d'un composant électronique très évolué, le « microprocesseur » Z 80. Il s'agit d'un véritable petit ordinateur à lui tout seul, mais assez rudimentaire : il ne possède que très peu de mémoire (l'équivalent de quelques caractères) et ne peut être programmé que dans un langage très « rustique », le langage machine.

Pour obtenir un Amstrad, on complète le Z 80 par des périphériques et de la mémoire : une mémoire vive ou RAM permettant d'emmagasiner programmes et données, mais aussi une mémoire morte ou ROM contenant un énorme programme en langage machine.

Celui-ci commence à s'exécuter dès la mise sous tension de la machine, et en gère complètement le fonctionnement : c'est lui qui vient lire le clavier, qui forme des images sur l'écran, et qui veille à la bonne organisation du contenu de la mémoire. C'est lui aussi qui « interprète » les ordres Basic que vous frappez au clavier ou que vous chargez à partir d'une cassette ou d'une disquette : chaque commande ou instruction est « traduite » en langage machine puisque ce n'est que sous cette forme que le Z 80 peut l'exécuter.

Le langage Basic équipant votre Amstrad est donc une combinaison particulière d'instructions en langage machine, dont les possibilités sont larges, mais tout de même limitées : certaines tâches sont laborieuses ou même impossibles en Basic.

D'autres langages « évolués » (Logo, Forth, etc.) représentent d'autres combinaisons d'instructions Z 80, qui présentent certains avantages et certains inconvénients par rapport au Basic. La grande force de ces langages évolués dont le Basic est le plus répandu est leur facilité d'apprentissage : les instructions sont formulées pratiquement « en clair », à l'aide de mots anglais très simples et de règles peu contraignantes. Les erreurs commises sont signalées au programmeur qui peut les corriger commodément.

Les choses sont très différentes en langage machine : l'équivalent du programme Basic d'une ligne 10 GOTO 10 s'écrira :

195 64 156 ou C3 40 9C

C'est déjà moins parlant, mais il y a pire : il ne suffit pas de le frapper au clavier puis de faire RUN pour le lancer ! Il faut réfléchir pour déterminer à quel endroit de la mémoire on ira l'implanter, et « appeler » cette « adresse » pour déclencher l'exécution. En cas d'erreur ou de « bouclage », on ne pourra pas reprendre « la main » par un BREAK : l'ordinateur sera bloqué ou « planté » et pour le débloquer, il faudra effacer le programme !

Pas question non plus de faire LIST pour examiner le programme : il faut aller inspecter la mémoire à l'aide d'un logiciel « moniteur » ou avec des PEEK I

La contrepartie de cette lourdeur est que l'on peut réaliser en langage machine des opérations inaccessibles au Basic : seules l'imagination et la compétence du programmeur limitent les possibilités logicielles, pourvu que l'on ne cherche pas à aller plus loin que ne le permet le matériel (le langage machine ne fera pas apparaître de la couleur sur un écran monochrome I) Egalement, à possibilités égales, un programme machine est bien plus rapide que son équivalent Basic : n'oublions pas que l'interpréteur Basic doit traduire chaque instruction en langage machine avant de l'exécuter... Si une boucle FOR-NEXT renouvelle mille fois la même tâche, la séquence de lignes correspondante sera traduite mille fois : c'est neuf cent quatre-vingt-dix-neuf fois de trop, ou même mille fois puisqu'il est possible d'écrire directement cette « routine » en langage machine I Cette économie de temps s'accompagne aussi d'une importante diminution d'encombrement mémoire : bien que plus rapide, un programme machine occupera considérablement moins de place que son équivalent Basic.

Le langage machine se prête donc particulièrement bien à l'écriture de logiciels exceptionnellement performants, mais au prix d'un travail considérable. Une forme de simplification consiste à écrire le langage machine non plus « à la main » comme nous allons apprendre à le faire, mais à l'aide d'un logiciel « assembleur » : il s'agit d'un programme écrit dans un langage a priori quelconque, qui peut « traduire » en langage machine des instructions écrites à l'aide de « mnémoniques », abréviations rappelant un peu les « mots-clés » du Basic. En assembleur, notre petit programme exemple s'écrirait : JP 40000, ce qui est déjà plus parlant si on considère que JP est l'abréviation du mot anglais JUMP qui signifie « sauter » !

Un bon logiciel assembleur est généralement accompagné d'un « désas-sembleur » capable de reconstituer un « listing » de mnémoniques à partir du programme machine terminé (utile par exemple pour étudier le contenu de la ROM de l'Amstrad, ou tout simplement pour contrôler le travail de l'assembleur).

Un débogueur est également utile, permettant de modifier un programme machine sans le récrire en entier, et de le faire « tourner » pas à pas en évitant les blocages sur erreurs.

Ces puissants outils transforment l'Amstrad en un véritable « système de développement de Z 80 », analogue à ce qu'utilisent les programmeurs professionnels. Pour les rentabiliser, il faut toutefois avoir à écrire des logiciels machine de plusieurs centaines ou milliers d'octets, ce qui suppose une profonde maîtrise de la programmation du Z 80.

Si vous lisez ces lignes, c'est fort probablement parce que vous ne maîtrisez précisément pas cette discipline ! Commençons donc par le commencement : entre, le « tout Basic » et le « tout assembleur » existe un domaine relativement facile à explorer, et qui peut vous apprendre beaucoup. 

Cherchons donc à écrire de courts « sous-programmes » ou « routines » en langage machine, que le Basic pourra « appeler » pour « sous-traiter » des tâches qu'il ne sait pas exécuter, ou qu'il exécute dans de mauvaises conditions.

Si alors les horizons ouverts par la programmation machine vous séduisent, vous souhaiterez probablement cesser d'assembler vos instructions à la main et vous vous offrirez un assembleur-désassembleur-debugger... Mais ne brûlez pas les étapes : c'est en forgeant que l'on devient forgeron, mais c'est aussi en assemblant manuellement de courtes routines que l'on assimile les notions de base sans lesquelles on ne pourra rien tirer de bon d'un assembleur, aussi performant soit-il.

II. Votre première routine machine

Puisque nous avons vu que l'Amstrad attend des instructions Basic dès sa mise sous tension, il est clair que nous allons devoir « partir » du Basic pour accéder au langage machine.

Nous utiliserons donc les instructions suivantes :

  • POKE pour « implanter » des « octets » en mémoire
  • PEEK pour aller lire en mémoire
  • MEMORY pour réserver des zones de mémoire pour le langage machine
  • CALL pour « appeler » nos routines en langage machine

    L'Amstrad venant d'être mis sous tension, frappons la commande :

PRINT HIMEM ( + ENTER)

Une valeur apparaît, par exemple 43903, qui indique l'adresse mémoire (c'est-à-dire le numéro de « case ») maximale dans laquelle le Basic peut  avoir à écrire : en dessous de 43903, donc, la mémoire n'est pas « sûre » et ce qu'on y écrit risque tôt ou tard de se trouver « écrasé » par autre chose.

Protégeons donc une zone qui nous sera strictement réservée, en frappant :

MEMORY 39999 (+ ENTER)

Il est facile de vérifier que HIMEM vaut dorénavant 39999 : toute la zone mémoire entre 40000 et 43903 soit plus de 3 Ko est maintenant à notre disposition sans surprise possible.

Un programme machine n'est rien d'autre qu'une suite d'octets, c'est-à-dire de valeurs comprises entre 0 et 255 (en décimal) ou entre 0 et FF (en hexadécimal) ou encore entre 00000000 et 11111111 (en binaire).

Notre exemple (195 64 156) n'échappe pas à cette règle même s'il ne comporte que trois octets en tout et pour tout !

Il s'agit donc de l'implanter en mémoire, puis de l'exécuter. Faisons donc successivement :

POKE 40000, 195 (+ ENTER)
POKE 40001, 64 (+ ENTER)
POKE 40002, 156 (+ ENTER)

Nos trois octets de programme machine sont désormais rangés à la suite les uns des autres à partir de l'adressse 40000, qu'il ne s'agit plus d'oublier car elle devra être spécifiée pour « lancer » l'exécution.

Faites NEW si vous voulez pour bien montrer que vos octets sont en sécurité, puis :

PRINT PEEK (40000) ( + ENTER)
PRINT PEEK (40001) (+ ENTER)
PRINT PEEK (40002) (+ ENTER)

Notre programme est bien là !

Pour le lancer, il suffit de faire :

CALL 40000 ( + ENTER)

Allons-y, et constatons qu'il ne se passe rien. Ce qui est pire, c'est que le clavier n'agit plus et que même la touche BREAK est inopérante : l'ordinateur est « planté » ! Ce n'est pas grave, mais pour reprendre « la main », il va falloir faire un « reset », c'est-à-dire un CTRL-SHIFT-ESC qui effacera toute la mémoire, y compris notre programme : n'oublions pas qu'en faisant un CALL, nous avons arrêté l'exécution du programme « interpréteur Basic » de la ROM pour travailler complètement « sans filet ». Perdre un programme de trois octets n'est rien, en perdre un de 300 ou 500 octets, représentant des heures de travail, est une tout autre affaire : il faudra être prudent lorsque vous en arriverez là et effectuer des sauvegardes !

En fait, ce qui est arrivé était voulu : ce programme est une « boucle » utilisant l'instruction de « saut » JP NN.

195 est le code de l'instruction JP (jump), 64 et 156 indiquent l'adresse à laquelle il faut faire « sauter » l'exécution, soit 64 +(256x 156) = 40000.

Notre CALL 40000 a donc appelé une instruction appelant elle-même sa propre exécution !

En Basic, un RUN déclenchant l'exécution de 10 GOTO 10 aurait le même effet, à ceci près que BREAK serait efficace, et que cette ligne occupe largement plus de trois octets en mémoire. Toutes les instructions machine n'occupent pas le même nombre d'octets : elles sont plus ou moins longues selon leur complexité. Cependant, toute suite d'octets ne correspond pas nécessairement à un programme machine vraisemblable : un CALL vers une adresse mémoire quelconque a toutes les chances de « planter » l'ordinateur : sachez exactement ce que vous faites... Certaines adresses de la ROM correspondent toutefois à des routines préprogrammées qu'il peut être utile d'exploiter (Voir partie 4, chap. 2.7).

III. Un programme machine plus utile

Pratique pour faire comprendre un certain nombre de notions importantes, ce premier programme n'a certes pas une grande utilité pratique !

Attaquons-nous donc maintenant à la résolution d'un problème précis, difficile à traiter en Basic. Vous connaissez certainement le code ASCII, qui affecte un octet déterminé à chaque caractère susceptible d'être affiché par l'Amstrad : en décimal, le « A » est représenté par l'octet 65, et le « Z » par 90, par exemple. (Voir les fonctions BASIC, ASC et CHR$.)

Le tableau suivant rappelle qu'un octet peut être représenté sous la forme de huit « bits » pouvant être soit à 1 soit à 0, et dont chacun possède un « poids » bien précis : celui de droite, le moins « significatif », vaut 1 lorsqu'il est à 1 et évidemment 0 lorsqu'il est à 0.

  27 26 25 24 23 22 21 20
  128 64 32 16 8 4 2 1
A 0 1 0 0 0 0 0 1 65
Z 0 1 0 1 1 0 1 0 90

 

Celui de gauche, le plus significatif, vaut 128 lorsqu'il est à 1 et toujours 0 lorsqu'il est à 0. De droite à gauche, les huit bits de l'octet valent respectivement 1, 2, 4, 8, 16, 32, 64 et 128, soit un total maximal de 255 s'ils sont tous à 1.

Or, 127 combinaisons suffisent largement pour coder tous les caractères de l'alphabet informatique normalisé : lettres majuscules et minuscules, chiffres et signes divers. Sept bits suffisent donc, et si l'on persiste à travailler avec des octets de huit bits (le matériel est fait pour cela), le bit de poids fort (128) reste normalement à 0.

Il a été imaginé de se servir de ce huitième bit pour contrôler les altérations pouvant survenir dans les transmissions de données : ce bit, appelé bit de parité, est mis à 1 ou à 0 selon le nombre de bits qui sont eux-mêmes à 1 dans le « septet » représentant le caractère. Le Minitel, par exemple, travaille en parité paire : le huitième bit est positionné de façon à ce que le nombre total de bits à 1 dans l'octet soit pair. A l'arrivée, on compte les bits à 1 et si le nombre trouvé est impair, on déduit qu'il y a eu erreur dans la transmission !

Bien évidemment, cela n'a rien à voir avec la « parité » de la valeur décimale de l'octet, au sens arithmétique du terme. Le tableau suivant montre le résultat de ce traitement sur les octets valant, en décimal, de 0 à 8. Cette valeur d'origine se trouve augmentée de 128 lorsque le bit de parité doit être mis à 1. En Basic, l'adjonction du bit de parité serait une opération lourde et lente. En langage machine, une routine de douze octets suffit, soit l'équivalent d'une seule très courte ligne de Basic.

 
Bit de parité 27 26 25 24 23 22 21 20
Avant 128 64 32 16 8 4 2 1 Après
0 0 0 0 0 0 0 0 0 0
1 1 0 0 0 0 0 0 1 129
2 1 0 0 0 0 0 1 0 130
3 0 0 0 0 0 0 1 1 3
4 1 0 0 0 0 1 0 0 132
5 0 0 0 0 0 1 0 1 5
6 0 0 0 0 0 1 1 0 6
7 1 0 0 0 0 1 1 1 135
8 0 0 0 0 1 0 0 1 9
9 11 0 0 0 1 0 0 0 136
etc.

 

Principe de codage en parité paire : le huitième bit de l'octet sert de bit de parité

Etudions donc en détail ce programme sous une forme qui vous sera bientôt familière :

40000 LD A,0 62 0
40002 AND A 167
40003 JP PE 40008 234 72 156
40006 SET 7,A 203 255
40008 LD 40001,A 50 65 156
40011 RET 201

La colonne de gauche indique l'adresse mémoire à laquelle est implanté le premier octet (ou l'octet unique) de chaque instruction : la première instruction comportant deux octets, on comprend pourquoi on passe de 40000 à 40002 !

Par contre, la seconde instruction ne possédant qu'un octet, on passe de 40002 à 40003.

Insistons bien sur le fait qu'une routine machine est en général écrite pour être implantée à partir d'une adresse mémoire bien déterminée : la déplacer équivaudrait à renuméroter les lignes d'un programme Basic sans corriger les GOTO et les GOSUB !

Certaines techniques de programmation permettent d'écrire des routines « relogeables », pouvant fonctionner n'importe où en mémoire, mais on ne peut pas toujours les employer.

La seconde colonne donne le mnémonique de l'instruction, c'est-à-dire la description très abrégée de ce qu'elle fait : c'est sous cette forme qu'on programme en « assembleur ».

Pour programmer « à la main » en langage machine, il faut faire le travail du logiciel assembleur à l'aide d'un crayon et de papier : aller chercher dans les recueils d'instructions les codes opératoires (octets) correspondant à chaque mnémonique, et calculer à l'aide de tables ou à la machine les octets servant à « pointer » des adresses mémoire. C'est long et fastidieux, mais extrêmement formateur : pour bien utiliser un assembleur, il faut savoir comment il fonctionne !

La première instruction du programme charge la valeur 0 dans le « registre » A du Z 80. Ce microprocesseur possède plusieurs de ces registres, 22 pour être précis. On trouvera leur description précise en partie 4, chapitre 2.2, mais sachez pour le moment que le plus utilisé est A, nommé « accumulateur ».

En fait, la valeur 0 sera remplacée juste avant l'exécution par l'octet à traiter : il suffira pour cela de faire, en Basic, POKE 40001, (octet) juste avant le CALL 40000.

Vous pouvez donc mettre n'importe quelle valeur inférieure à 256 à la place de 0, mais ne touchez pas à 62 qui est le code opératoire de l'instruction LD, A,N (LOAD A with N) qui charge A avec N.

L'instruction suivante, AND A, est utilisée de façon particulière : normalement, AND effectue un ET logique bit à bit entre l'octet contenu dans A, et celui contenu dans le registre spécifié. Ici, le ET est effectué entre le contenu de A et le contenu de A ! Bien évidemment, le contenu de A n'est pas modifié, mais l'exécution de l'instruction AND positionne le « drapeau » de parité du Z 80 : cet indicateur se « souviendra » donc de la parité du contenu de A, c'est-à-dire s'il contient un nombre pair ou impair de bits.

L'instruction suivante fonctionne comme un IF-THEN en Basic : JP PE 40008 (jump to 40008 if parity even) déclenche un saut à l'instruction logée à l'adresse 40008 si et seulement si le drapeau de parité est positionné sur « pair ». Dans le cas contraire, c'est l'instruction suivante qui est exécutée, celle qui est logée en 40006.

234 est le code opératoire de JP PE NN, tandis que 72 et 156 représentent l'adresse 40008 (72 + (256 x 156)) : l'octet le moins significatif de l'adresse est toujours placé en premier, suivi de l'octet le plus significatif, dont le « poids » est 256.

Si la parité est impaire (nombre impair de bits), l'instruction SET 7,A est exécutée : elle force à 1 le bit de poids fort (N° 7) du registre A, ce qui rend pair le nombre total de bits.

Si la parité était déjà paire, c'est l'instruction LD 40001 ,A qui est exécutée : le contenu de A est chargé dans l'adresse mémoire 40001 (65 + (256 x 156)) où le Basic pourra venir le chercher par un PEEK 40001.

Encore faut-il pour cela revenir en Basic, ce qui est déclenché par l'instruction RET, équivalente au RETURN du Basic. Le programme suivant se charge à la fois de l'implantation en mémoire de cette routine (lignes 10 à 60), et de son exploitation pratique dignes 100 à 140).

10 MEMORY 39999
20 DATA 62,0,167,234,72,156,203,255,50,65, 156,201
30 FOR f=40000 TO 40011
40 READ c
50 POKE f, c
60 NEXT f
30 REM (c)1988 Patrick GUEULLE
100 FOR f=0 TO 127
110 POKE 40001,f
120 CALL 40000
130 PRINT f ; " ". PEEK(40001)
140 NEXT f


Programme Basic d'implantation et de lancement

Dans un premier temps, de la place mémoire est protégée, bien plus qu'il n'en faut d'ailleurs, mais au diable l'avarice !

Les douze octets, rangés dans une ligne DATA, sont alors implantés en mémoire par une suite de POKE déclenchés par une boucle FOR-NEXT opérant directement sur les adresses mémoire 40000 à 40011.

La routine peut maintenant être appelée à volonté par un simple CALL 40000, à charge pour l'utilisateur de placer d'abord l'octet à traiter en 40001 par un POKE.

C'est ce que fait la seconde partie du programme, qui affiche à l'écran les octets 0 à 127 enrichis d'un bit de parité paire.

Comparez donc le début de cette liste avec la colonne de droite du tableau page 6 et, si le cœur vous en dit, essayez de faire aussi bien et aussi rapide sous Basic : bonne chance !

 

 

 
Mnémonique Code Equivalent Basic

ADD A,B
ADD A,N
CALL NN
CALL NZ,NN 
CALL Z,NN
CP B
CP N
DEC A
DEC B
IN A,(N)
INC A 
INC B
JP NN
JP NZ,NN
JP Z,NN
LD A,B
LD A,N
LD A,(NN)
LD B,A
LD B,N
LD (NN),A
NOP
OUT(N),A 
RET
RET NZ
RET Z
SBC A,B
SBC A,N
SUB B
SUB N

128
198
205
196
204
184
254
061
005
219
060
004
195
194
202
120
062
058
071
006
050
000
211
201
192
200
152
222
144
214

LET A = A + B
LET A = A + OMS
GOSUB (OMS + 256 x OPS)
IF Z = 0 THEN GOSUB (OMS + 256 x OPS)
IF Z = 1 THEN GOSUB (OMS + 256 x OPS)
IF B = A THEN LET Z - 1
IF A = N THEN LET Z = 1
LET A = A - 1
LET B = B - 1
IN (N)
LET A = A + 1
LET B = B + 1
GOTO (OMS + 256 x OPS)
IF Z= 0 THEN GOTO (OMS + 256 x OPS)
IF Z = 1 THEN GOTO (OMS + 256 x OPS)
LET A = B
LET A = OMS
LET A = PEEK (OMS + 256 x OPS)
LET B = A
LET B = OMS
POKE (OMS + 256 x POS),A
(REM)
OUT N,A
RETURN
IF Z = 0 THEN RETURN
IF Z = 1 THEN RETURN
LET A = A - B - Cy
LET A = A - OMS - Cy
LET A = A - B
LET A = A - OMS

OMS = octet le moins significatif (poids 1)          d'une valeur comprise entre 0 et 65535
OPS = octet le plus significatif (poids 256)        /

Cela ne vous empêche évidemment pas de recourir à des instructions plus spécifiques, mais il vous faudra alors faire l'effort de comprendre leur fonctionnement dans le détail, ce qui n'est pas toujours simple !

La partie 4, chapitre 2.3 est là pour vous y aider, ainsi que le tableau suivant qui donne la signification des principales abréviations entrant dans la composition de mnémoniques d'assemblage.

Pour incorporer dans un programme les instructions que vous aurez choisies, vous devrez vous reporter à une table des codes opératoires donnant les octets à utiliser pour représenter, en mémoire, chaque instruction.
Signification des abréviations utilisées dans les mnémoniques
Abréviation Signification Equivalence Basic
ADC
ADD
AND
BIT
CALL
CP
CC
DAA
DEC
DI
DJNZ
EI
EX
EXX
HALT
IM
IN
INC
IND
INI
JP ou JR
LD
LDD
LDI
NEG
NOP
OR
OTD
OTI
OUT
OUTD
OUTI
POP
PUSH
RES
RET
RL
RR
RST
SBC
SCF
SET
SLA
SRA
SRL
SUB
XOR
Addition avec retenue
Addition sans retenue
ET logique bit à bit
Lecture d'un bit spécifié d'un octet donné
Appel sous-programme
Comparaison
Complémentation
Conversion en BCD
Décrémentation
Annulation interruptions
Décrémenter B et brancher si B = 0
Autorisation interruptions
Echanger deux opérandes
Echange complexe
Arrêt machine
Choix mode interruption
Entrée par port
Incrémentation
Entrée + décrémentation
Entrée + incrémentation
Branchement
Chargement
Chargement + décrémentation
Chargement + incrémentation
Complémentation à 2
Pas d'opération
OU logique bit à bit
Sortie + décrémentation
Sortie + incrémentation
Sortie sur port
Sortie + décrémentation
Sortie + incrémentation
Récupérer données pile
Entrer données pile
Mettre bit spécifié à 0
Retour au prog principal
Permutation de bits à gauche
Permutation de bits à droite
Repartir à adresse spécifiée
Soustraction avec retenue
Forcer la retenue à 1
Mettre à 1 bit spécifié
Décalage de bits à gauche
Décalage de bits à droite
Décalage de bits à droite
Soustraction sans retenue
OU exclusif bit à bit
+
+
AND
spécial
GOSUB
IF-THEN
spécial
spécial
LET L = L - 1
spécial
LET B = B - 1 et GOTO
spécial
double LET
spécial
STOP
spécial
IN
LET L = L + 1
spécial
spécial
GOTO
LET
spécial
spécial
spécial
(REM)
OR
spécial
spécial
OUT
spécial
spécial
spécial
spécial
spécial (LET)
RETURN
spécial
spécial
spécial (RUN)
-
spécial (LET)
spécial (LET)
spécial
spécial
spécial
-
spécial

Les premières lettres des mnémoniques des instructions Z 80 sont l'abréviation de mots anglais décrivant la fonction réalisée. Il suffit d'en connaître la signification, ainsi que les conventions de notation des opérandes (registres, cellules mémoire, ports) pour comprendre l'essentiel du fonctionnement de l'instruction. Pour les détails, il faudra bien sûr consulter le recueil complet des instructions Z 80 (chapitre 4/2.3 de cet ouvrage).
Abréviation Signification

( )
A
B
C
D
E
H
L
N

IX
IY
HL
BC
DE
SP
d

NN
Z
NZ
C
NC
M
P
AF
AF'
NC
P0
PE
0
...
7
10H
18H
20H
28H
30H
38H
8
( )
'

Cellule mémoire dont l'adresse est stockée, sous la forme OMS OPS, dans les deux registres ou les deux nombres mentionnés entre les parenthèses
Registre A (accumulateur)
Registre B
Registre C
Registre D
Registre E
Registre H
Registre L
Valeur numérique (octet) contenue dans l'instruction après le code opératoire

Registre d'index IX (à deux octets)
Registre d'index IY (à deux octets)
Paire de registres H et L utilisés ensemble
Paire de registres B et C utilisés ensemble
Paire de registres D et E utilisés ensemble
Registre SP (pointeur de pile)
Déplacement (quantité contenue, sous forme d'un octet, dans une instruction et intervenant selon des modalités diverses, dans le calcul d'une adresse (à étudier cas par cas).

Paire de valeurs numériques (2 octets OMS et OPS)
Si drapeau Z à 1 (résultat nul ou égalité)
Si drapeau Z à 0 (résultat 0 ou inégalité)
Si drapeau C à 1 (retenue)
Si drapeau C à 0 (pas de retenue)
Si drapeau S à 1 (résultat négatif)
Si drapeau S à 0 (résultat positif)
Paire de registres A et F
Paire de reaistres A' et F'
Si drapeau C à 0 (pas de retenue)
Si drapeau P à 0 (résultat pair ou pas de dépassement)
Si drapeau P à 1 (résultat impair ou dépassement)
Bit N° 0 de l'octet spécifié (registre)
Bit N° 7 de l'octet spécifié (registre)
Adresse 16 (après RST)
Adresse 24
Adresse 32
Adresse 40
Adresse 48
Adresse 56
Adresse 8
Numéro d'un port d'entrée-sortie
Indique un registre du second groupe

Ces abréviations sont employées dans la suite des mnémoniques pour en préciser le sens, ou pour désigner les opérandes (registres ou cellules mémoire sur lesquels agit l'instruction).

 

Une telle table existe en annexe 2 (partie 11 ), un classement alphabétique facilitant la recherche d'après les mnémoniques.

Les codes opératoires y sont cependant présentés en hexadécimal, forme universellement adoptée par les programmeurs avertis mais qui risque de dérouter encore un peu plus le débutant en langage machine !

La table de conversion de la page suivante est donc là pour permettre d'opérer rapidement une conversion entre des octets exprimés en décimal ou en « hexa » : vérifiez par exemple que 124 en décimal correspond à 7C en hexa, et vice-versa.

Une petite table annexe permet les conversions entre « chiffres » hexa, et « nibbles » ou demi-octets de quatre bits : sachant que 7 correspond à 0111 et C à 1100, on déduit immédiatement que 7C (ou 124) vaut 01111100 en binaire !

Si vous décidez de travailler en décimal, vous devrez calculer la valeur des 2 octets représentant l'adresse. Prenons l'exemple de l'adresse 40000 : cherchez le multiple de 256 immédiatement inférieur à 40000, soit 39936 = 156 x 256. Soustrayez 39936 à 40000, ce qui donne 64: codée sur deux octets, l'adresse 40000 se compose donc de 64 (octet le moins significatif, de « poids » 1) et de 156 (octet le plus significatif, de « poids » 256).

Convertis en hexadécimal, ces deux octets s'écrivent respectivement 40 et 9C, ce qui permet d'écrire l'adresse 40000 sous la forme hexadécimale 9C40.

Table de conversion décimal - hexadécimal

L'adresse suivante, 40001 en décimal, s'écrirait donc 9D40 en hexadécimal : à vous de choisir ce qui vous semble le plus pratique, mais vous viendrez sûrement tôt ou tard à l'hexa !

Un autre exemple :

Le programme suivant est un autre exemple de la facilité avec laquelle un programme Basic peut faire exécuter une tâche par une routine écrite en langage machine et incorporée dans son listing.

Il s'agit d'un petit logiciel de codage et décodage de messages secrets, très simplifié mais dont le principe est intéressant à étudier car il peut être largement extrapolé.

Un texte est entré en machine sous la forme d'une chaîne m$: chaque caractère de cette chaîne est représenté en mémoire par un octet, c'est-à-dire huit bits. L'idée consiste à modifier la position de ces bits dans l'octet grâce à une « permutation circulaire ». Une telle opération serait extrêmement lourde et lente en Basic, mais ne pose aucun problème en assembleur, des instructions machine existant tout spécialement à cet effet.

 

★ NOTE: 9e Complément

Page précédente : 4/2.5 - Cours de programmation

CPCrulez[Content Management System] v8.75-desktop/cache
Page créée en 150 millisecondes et consultée 227 fois

L'Amstrad CPC est une machine 8 bits à base d'un Z80 à 4MHz. Le premier de la gamme fut le CPC 464 en 1984, équipé d'un lecteur de cassettes intégré il se plaçait en concurrent  du Commodore C64 beaucoup plus compliqué à utiliser et plus cher. Ce fut un réel succès et sorti cette même années le CPC 664 équipé d'un lecteur de disquettes trois pouces intégré. Sa vie fut de courte durée puisqu'en 1985 il fut remplacé par le CPC 6128 qui était plus compact, plus soigné et surtout qui avait 128Ko de RAM au lieu de 64Ko.