Instructions arithmétiques et logiques
Gameboj – Étape 4
1 Introduction
Le but de cette étape est de continuer l'écriture de la classe modélisant le processeur du Game Boy en ajoutant la gestion de toutes les instructions arithmétiques et logiques.
Par rapport aux instructions examinées à l'étape précédente, celles de cette étape ont deux particularités : d'une part, certaines d'entre elles sont encodées au moyen d'un préfixe, et d'autre part, elles modifient toutes les fanions du processeur stockés dans le registre F
. Il convient donc de décrire rapidement ces deux aspects.
1.1 Instructions préfixées
Dans la description de l'encodage des instructions à l'étape précédente, il était dit que le premier octet de l'encodage d'une instruction, nommé son opcode, suffisait à lui seul pour identifier l'instruction.
Cela n'est toutefois pas exact, car il existe une catégorie d'instructions, qu'on appelle les instructions préfixées, dont l'opcode est constitué de deux octets :
- un premier octet qui constitue le préfixe, qui est le même pour toutes les instructions préfixées et vaut CB16,
- un second octet qui constitue le véritable opcode de l'instruction.
Toutes les instructions préfixées s'encodent au moyen de deux octets exactement. En d'autres termes, elles ne sont jamais suivies d'arguments 8 ou 16 bits comme certaines des instructions non-préfixées.
1.2 Fanions
Nous l'avons vu, le processeur stocke les fanions de l'UAL (Z
, N
, H
et C
) dans les 4 bits de poids fort du registre F
, dont les 4 bits de poids faible valent toujours 0.
De manière générale, les fanions stockés dans le registre F
sont ceux produits par la dernière instruction arithmétique ou logique exécutée par le processeur. Par exemple, lorsqu'une instruction d'addition (ADD
) est exécutée, les 4 fanions produits par l'UAL sont copiés dans le registre F
. Les autres instructions, p.ex. celles de chargement et de stockage présentées à l'étape précédente, n'ont aucun effet sur les fanions.
Malheureusement, il existe aussi des instructions dont le comportement est plus complexe, et consiste à modifier seulement un sous-ensemble des fanions contenus dans le registre F
, les autres étant laissés tels quels.
Dès lors, l'effet de chacune des instructions sur les fanions stockés dans le registre F
est spécifié au moyen d'une extension de la notation utilisée à l'étape 2. La valeur de chacun des fanions après l'exécution d'une instruction est donnée par une lettre, selon la convention suivante :
- un
0
signifie que le fanion vaut toujours 0, - un
1
signifie que le fanion vaut toujours 1, - un
-
(tiret) signifie que le fanion ne change pas de valeur, - une lettre (
Z
,N
,H
ouC
) signifie que le fanion est modifié en fonction de la valeur retournée par l'UAL.
Par exemple, l'effet de l'instruction de décrémentation (DEC
) sur les fanions stockés dans le registre F
est spécifié comme étant Z1H-
, qui signifie que :
- les fanions
Z
etH
sont déterminés en fonction des fanions produits par l'UAL, - le fanion
N
est mis à 1, - le fanion
C
ne change pas.
Au passage, il faut noter que même si DEC
effectue une soustraction, son effet sur les fanions stockés dans le registre F
n'est pas tout à fait le même que celui de l'instruction de soustraction SUB
. Il faut donc bien prêter attention aux spécifications données plus bas, car elles réservent certaines surprises !
1.3 Instructions arithmétiques et logiques
Dans la description de la syntaxe, les mêmes conventions que celles de l'étape précédente sont utilisées, auxquelles s'ajoutent :
e8
- représente une valeur signée de 8 bits, c-à-d interprétée en complément à deux,
n3
- représente une valeur de 3 bits.
Dans la description de l'effet, les mêmes conventions que celles de l'étape précédente sont utilisées, auxquelles s'ajoute :
e
- représente la valeur 8 bits suivant l'opcode de l'instruction, mais interprétée en complément à deux.
Les différentes instructions à traiter dans cette étape sont décrites dans les sections qui suivent.
1.4 Additions
Les instructions d'addition 8 bits, données dans la table ci-dessous, ont un comportement assez évident et ne sont donc pas décrites plus en détail. Notez juste que ADC
représente une addition avec retenue, cette dernière provenant du fanion C
.
Assembleur | Opcode | Effet | ZNHC |
---|---|---|---|
ADD A, n8 |
11000110 |
A += n |
Z0HC |
ADD A, r8 |
10000rrr |
A += r |
Z0HC |
ADD A, [HL] |
10000110 |
A += BUS[HL] |
Z0HC |
ADC A, n8 |
11001110 |
A += n + C |
Z0HC |
ADC A, r8 |
10001rrr |
A += r + C |
Z0HC |
ADC A, [HL] |
10001110 |
A += BUS[HL] + C |
Z0HC |
INC r8 |
00rrr100 |
r += 1 |
Z0H- |
INC [HL] |
00110100 |
BUS[HL] += 1 |
Z0H- |
Les deux instructions de la table qui suit permettent d'ajouter une valeur 16 bits à une paire de registres. Notez que pour ces deux instructions, si les bits rr
encodant la paire de registres valent 11
, alors c'est le registre SP
qui est modifié, et pas la paire AF
. D'autre part, les fanions H
et C
produits par la première instruction correspondent à l'addition des 8 bits de poids fort.
Assembleur | Opcode | Effet | ZNHC |
---|---|---|---|
ADD HL, r16 |
00rr1001 |
HL += r |
-0HC |
INC r16 |
00rr0011 |
r += 1 |
---- |
Les deux instructions qui suivent permettent d'additioner une valeur signée au pointeur de pile (SP
) et de stocker le résultat dans SP
lui-même ou dans la paire HL
. Notez que dans ce cas, et contrairement au cas précédent, les fanions H
et C
produits correspondent à l'addition des 8 bits de poids faible.
Assembleur | Opcode | Effet | ZNHC |
---|---|---|---|
ADD SP, e8 |
11101000 |
SP = SP + e |
00HC |
LD HL, SP + e8 |
11111000 |
HL = SP + e |
00HC |
Notez que la seconde de ces instructions est nommée LD
, mais dans la mesure où elle effectue une addition et que son comportement est quasi-identique à celui de la première de ces instructions, nous avons décidé de la traiter ici.
1.5 Soustractions et comparaisons
Les instructions de soustraction et de décrémentation 8 bits ne réservent pas non plus de surprise particulière. Là encore, il convient de noter l'existence de l'instruction SBC
qui effectue une soustraction avec emprunt, ce dernier provenant du fanion C
.
Assembleur | Opcode | Effet | ZNHC |
---|---|---|---|
SUB A, n8 |
11010110 |
A -= n |
Z1HC |
SUB A, r8 |
10010rrr |
A -= r |
Z1HC |
SUB A, [HL] |
10010110 |
A -= BUS[HL] |
Z1HC |
SBC A, n8 |
11011110 |
A -= n + C |
Z1HC |
SBC A, r8 |
10011rrr |
A -= r + C |
Z1HC |
SBC A, [HL] |
10011110 |
A -= BUS[HL] + C |
Z1HC |
DEC r8 |
00rrr101 |
r -= 1 |
Z1H- |
DEC [HL] |
00110101 |
BUS[HL] -= 1 |
Z1H- |
Les instructions de comparaison listées dans la table qui suit sont totalement équivalentes aux instructions de soustraction correspondantes, la seule différence étant que le résultat de l'opération est ignoré, seuls les fanions étant gardés.
Par exemple, les deux instructions suivantes :
SUB A, 5 CP A, 5
effectuent exactement le même calcul, la seule différence est que la première modifie le registre A
et les fanions, alors que la seconde ne modifie que les fanions, le contenu du registre A
restant inchangé.
L'idée des instructions de comparaison est que les fanions produits par une soustraction donnent des informations au sujet de la relation qui existe entre les deux valeurs soustraites. Ainsi, dans l'exemple ci-dessus, le fanion Z
sera vrai ssi A
contient 5, tandis que le fanion C
sera vrai ssi A
contient une valeur strictement plus petite que 5.
Assembleur | Opcode | Effet | ZNHC |
---|---|---|---|
CP A, n8 |
11111110 |
aucun | Z1HC |
CP A, r8 |
10111rrr |
aucun | Z1HC |
CP A, [HL] |
10111110 |
aucun | Z1HC |
L'instruction de la table qui suit permet de décrémenter une paire de registres contenant une valeur 16 bits. Notez que si les deux bits rr
encodant la paire valent 11
, alors c'est le registre SP
qui est décrémenté, et pas la paire AF
.
Assembleur | Opcode | Effet | ZNHC |
---|---|---|---|
DEC r16 |
00rr1011 |
r -= 1 |
---- |
1.6 Opérations bit à bit
Les opérations bit à bit, listées dans la table ci-dessous, fonctionnent exactement comme en Java et ne sont donc pas décrites en plus de détails.
Assembleur | Opcode | Effet | ZNHC |
---|---|---|---|
AND A, n8 |
11100110 |
A &= n |
Z010 |
AND A, r8 |
10100rrr |
A &= r |
Z010 |
AND A, [HL] |
10100110 |
A &= BUS[HL] |
Z010 |
OR A, n8 |
11110110 |
A |= n |
Z000 |
OR A, r8 |
10110rrr |
A |= r |
Z000 |
OR A, [HL] |
10110110 |
A |= BUS[HL] |
Z000 |
XOR A, n8 |
11101110 |
A ^= n |
Z000 |
XOR A, r8 |
10101rrr |
A ^= r |
Z000 |
XOR A, [HL] |
10101110 |
A ^= BUS[HL] |
Z000 |
CPL |
00101111 |
A = ~A |
-11- |
1.7 Décalages
Les opérations de décalage, listées dans la table ci-dessous, fonctionnent également comme les opérateurs Java correspondants.
Assembleur | Opcode | Effet | ZNHC |
---|---|---|---|
SLA r8 |
00100rrr |
r <<= 1 |
Z00C |
SLA [HL] |
00100110 |
BUS[HL] <<= 1 |
Z00C |
SRA r8 |
00101rrr |
r >>= 1 |
Z00C |
SRA [HL] |
00101110 |
BUS[HL] >>= 1 |
Z00C |
SRL r8 |
00111rrr |
r >>>= 1 |
Z00C |
SRL [HL] |
00111110 |
BUS[HL] >>>= 1 |
Z00C |
1.8 Rotations
Le processeur du Game Boy offre deux groupes d'instructions de rotation, chacun décrit par une table ci-dessous.
Prenez garde au fait que les noms officiels de ces instructions ont été terriblement mal choisis ! En effet, les instructions qui effectuent une rotation à travers la retenue (rotate through carry) n'ont pas de préfixe C
, tandis que les autres ont un préfixe C
.
Par exemple, l'instruction RL
effectue une rotation à gauche, à travers la retenue, tandis que l'instruction RLC
effectue aussi une rotation à gauche, mais pas à travers la retenue. Gardez bien cela en tête !
Le premier groupe d'instructions de rotation est constitué d'instructions non préfixées, travaillant exclusivement sur l'accumulateur. La fonction rot
utilisée dans la description de leur effet correspond à la fonction rotate
de la classe Alu
.
Assembleur | Opcode | Effet | ZNHC |
---|---|---|---|
RLCA |
00000111 |
A = rot(←, A) |
000C |
RRCA |
00001111 |
A = rot(→, A) |
000C |
RLA |
00010111 |
A = rot(←, A, C) |
000C |
RRA |
00011111 |
A = rot(→, A, C) |
000C |
Le second groupe d'instructions de rotation est constitué d'instructions préfixées qui travaillent sur un registre 8 bits quelconque ou sur une valeur en mémoire.
Assembleur | Opcode | Effet | ZNHC |
---|---|---|---|
RLC r8 |
00000rrr |
r = rot(←, r) |
Z00C |
RLC [HL] |
00000110 |
BUS[HL] = rot(←, BUS[HL]) |
Z00C |
RRC r8 |
00001rrr |
r = rot(→, r) |
Z00C |
RRC [HL] |
00001110 |
BUS[HL] = rot(→, BUS[HL]) |
Z00C |
RL r8 |
00010rrr |
r = rot(←, r, C) |
Z00C |
RL [HL] |
00010110 |
BUS[HL] = rot(←, BUS[HL], C) |
Z00C |
RR r8 |
00011rrr |
r = rot(→, r, C) |
Z00C |
RR [HL] |
00011110 |
BUS[HL] = rot(→, BUS[HL], C) |
Z00C |
SWAP r8 |
00110rrr |
r = swap(r) |
Z000 |
SWAP [HL] |
00110110 |
BUS[HL] = swap(BUS[HL]) |
Z000 |
Notez que comme ces instructions sont préfixées, la valeur donnée dans la colonne Opcode est celle du second octet de leur encodage, le premier étant toujours égal au préfixe (CB16).
1.9 Opérations sur les bits
Trois instructions permettent de travailler sur les bits individuels d'une valeur 8 bits contenue dans un registre ou en mémoire. Là aussi, il s'agit d'instructions préfixées.
Assembleur | Opcode | Effet | ZNHC |
---|---|---|---|
BIT n3, r8 |
01nnnrrr |
aucun | Z01- |
BIT n3, [HL] |
01nnn110 |
aucun | Z01- |
SET n3, r8 |
11nnnrrr |
r |= 1 << n |
---- |
SET n3, [HL] |
11nnn110 |
BUS[HL] |= 1 << n |
---- |
RES n3, r8 |
10nnnrrr |
r &= ~(1 << n) |
---- |
RES n3, [HL] |
10nnn110 |
BUS[HL] &= ~(1 << n) |
---- |
Notez que l'instruction BIT
teste si un bit d'index donné vaut 0 ou 1 et stocke le résultat dans le fanion Z
, qui vaut 1 ssi le bit en question vaut 0.
1.10 Instructions diverses
L'instruction DAA
permet de corriger la valeur de l'accumulateur suite à une opération arithmétique effectuée sur les valeurs décimales codées en binaire.
Assembleur | Opcode | Effet | ZNHC |
---|---|---|---|
DAA |
00100111 |
A = bcdAdjust(A, N, H, C) |
Z-0C |
Les instructions SCF
et CCF
permettent de manipuler le fanion C
en le forçant à 1 (pour SCF
, qui signifie set carry flag) ou en l'inversant (pour CCF
, qui signifie complement carry flag).
Assembleur | Opcode | Effet | ZNHC |
---|---|---|---|
SCF |
00110111 |
aucun | -001 |
CCF |
00111111 |
aucun | -00C |
Prenez bien garde au fait que le fanion C
produit par l'instruction CCF
est le complément du fanion C
d'origine. C-à-d que si le fanion C
vaut 0 avant l'exécution d'une instruction CCF
, il faut 1 après, et inversément.
2 Mise en œuvre Java
2.1 Enumération Opcode
Lors de la rédaction de cette étape, nous avons remarqué quelques possibilités d'amélioration de l'énumération Opcode
. Pour cette raison, nous vous fournissons une archive Zip contenant une nouvelle version du fichier Opcode.java
. Il vous faut l'importer dans votre projet, en vous assurant que la nouvelle version du fichier remplace la précédente.
Notez que les changements apportés à l'énumération Opcode
concernent uniquement cette étape-ci et n'ont donc aucun impact sur le code de l'étape 3.
2.2 Classe Cpu
Le code à écrire pour cette étape n'a qu'un seul but : gérer les instructions arithmétiques et logiques décrites plus haut. Il va sans dire que le comportement de presque toutes ces instructions a déjà été mis en œuvre dans la classe Alu
écrite à l'étape 2.
Nous vous donnons une fois encore quelques conseils de programmation. Libre à vous de les suivre ou non, mais pensez à soigner le style de votre programme !
2.2.1 Conseils de programmation
- Gestion des instructions préfixées
Certaines des instructions à traiter dans le cadre de cette étape sont préfixées, c-à-d que leur encodage commence par un octet constant et valant CB16 (le préfixe) suivi de l'octet qui constitue l'opcode réel de l'instruction.
Cela complique légèrement le processus de décodage d'une instruction, car il n'est plus possible de l'effectuer uniquement au moyen du premier octet. En effet, si celui-ci est égal au préfixe, il faut utiliser le second octet.
Cela dit, une fois cette petite distinction faite, il est aussi possible d'utiliser une table pour trouver l'objet
Opcode
correspondant à une instructions préfixée. En effet, l'énumérationOpcode
contient également des éléments correspondant aux instructions préfixées, qui sont identifiés par leur sorte (kind
), qui vautPREFIXED
. Pour ces instructions, la valeur stockée dans l'attributencoding
est bien entendu le second octet de leur encodage, celui qui suit le préfixe.Si vous avez suivi nos conseils de programmation donnés à l'étape précédente, une table dédiée aux instructions préfixées peut se construire très facilement ainsi :
private static final Opcode[] PREFIXED_OPCODE_TABLE = buildOpcodeTable(Opcode.Kind.PREFIXED);
- Augmentation de la méthode
dispatch
Bien entendu, la gestion des nouvelles instructions se fait en augmentant le
switch
de la méthodedispatch
afin d'y ajouter les familles d'instructions décrites plus bas. Pour faciliter votre travail, nous vous fournissons ci-dessous la liste des cas à traiter pour cette étape sous la forme de code Java que vous pouvez simplement copier dans votre projet.// Add case ADD_A_R8: { } break; case ADD_A_N8: { } break; case ADD_A_HLR: { } break; case INC_R8: { } break; case INC_HLR: { } break; case INC_R16SP: { } break; case ADD_HL_R16SP: { } break; case LD_HLSP_S8: { } break; // Subtract case SUB_A_R8: { } break; case SUB_A_N8: { } break; case SUB_A_HLR: { } break; case DEC_R8: { } break; case DEC_HLR: { } break; case CP_A_R8: { } break; case CP_A_N8: { } break; case CP_A_HLR: { } break; case DEC_R16SP: { } break; // And, or, xor, complement case AND_A_N8: { } break; case AND_A_R8: { } break; case AND_A_HLR: { } break; case OR_A_R8: { } break; case OR_A_N8: { } break; case OR_A_HLR: { } break; case XOR_A_R8: { } break; case XOR_A_N8: { } break; case XOR_A_HLR: { } break; case CPL: { } break; // Rotate, shift case ROTCA: { } break; case ROTA: { } break; case ROTC_R8: { } break; case ROT_R8: { } break; case ROTC_HLR: { } break; case ROT_HLR: { } break; case SWAP_R8: { } break; case SWAP_HLR: { } break; case SLA_R8: { } break; case SRA_R8: { } break; case SRL_R8: { } break; case SLA_HLR: { } break; case SRA_HLR: { } break; case SRL_HLR: { } break; // Bit test and set case BIT_U3_R8: { } break; case BIT_U3_HLR: { } break; case CHG_U3_R8: { } break; case CHG_U3_HLR: { } break; // Misc. ALU case DAA: { } break; case SCCF: { } break;
- Familles
Dans le but de simplifier la mise en œuvre de la classe
Cpu
, les instructions ont été regroupées en familles, comme cela a été expliqué à l'étape précédente. Les familles qui ne requièrent pas d'explication particulière sont listées dans la table ci-dessous, similaire à celle donnée à l'étape précédente.Famille Instruction(s) en assembleur INC_R8
INC A
/INC B
/ … /INC L
INC_HLR
INC [HL]
INC_R16SP
INC BC
/INC DE
/INC HL
/INC SP
ADD_HL_R16SP
ADD HL, BC
/ADD HL, DE
/ … /ADD HL, SP
DEC_R8
DEC A
/DEC B
/ … /DEC L
DEC_HLR
DEC [HL]
DEC_R16SP
DEC BC
/DEC DE
/DEC HL
/DEC SP
CP_A_N8
CP A, 0
/CP A, 1
/ … /CP A, $FF
CP_A_R8
CP A, A
/CP A, B
/ … /CP A, L
CP_A_HLR
CP A, [HL]
AND_A_N8
AND A, 0
/AND A, 1
/ … /AND A, $FF
AND_A_R8
AND A, A
/AND A, B
/ … /AND A, L
AND_A_HLR
AND A, [HL]
OR_A_N8
OR A, 0
/OR A, 1
/ … /OR A, $FF
OR_A_R8
OR A, A
/OR A, B
/ … /OR A, L
OR_A_HLR
OR A, [HL]
XOR_A_N8
XOR A, 0
/XOR A, 1
/ … /XOR A, $FF
XOR_A_R8
XOR A, A
/XOR A, B
/ … /XOR A, L
XOR_A_HLR
XOR A, [HL]
CPL
CPL
ROTCA
RLCA
/RRCA
ROTA
RLA
/RRA
ROTC_R8
RLC A
/RRC A
/ … /RLC L
/RRC L
ROT_R8
RL A
/RR A
/ … /RL L
/RR L
ROTC_HLR
RLC [HL]
/RRC [HL]
ROT_HLR
RL [HL]
/RR [HL]
SWAP_R8
SWAP A
/SWAP B
/ … /SWAP L
SWAP_HLR
SWAP [HL]
SLA_R8
SLA A
/SLA B
/ … /SLA L
SRA_R8
SRA A
/SRA B
/ … /SRA L
SRL_R8
SRL A
/SRL B
/ … /SRL L
SLA_HLR
SLA [HL]
SRA_HLR
SRA [HL]
SRL_HLR
SRL [HL]
BIT_U3_R8
BIT 0, A
/BIT 1, A
/ … /BIT 7, L
BIT_U3_HLR
BIT 0, [HL]
/BIT 1, [HL]
/ … /BIT 7, [HL]
DAA
DAA
Les familles restantes sont décrites plus en détail dans les sections suivantes.
- Familles des additions et soustractions
Les familles des additions et soustractions contiennent à la fois les instructions d'additions/soustractions « normales » et celles avec retenue/emprunt. Par exemple, la famille
ADD_A_N8
contient les deux instructions suivantes :ADD A, n8 ADC A, n8
La raison de ce choix est que toutes les deux peuvent être mises en œuvre au moyen d'un appel à la méthode
add
de la classeAlu
qui prend trois arguments. La valeur du dernier de ces arguments, qui représente la valeur initiale de la retenue, peut être déterminé au moyen de deux bits : le bit 3 de l'encodage l'opcode, et le fanionC
.En effet, en comparant l'encodage de l'opcode des deux instructions susmentionnées, vous verrez qu'il ne diffère qu'au niveau du bit 3, qui vaut 0 pour l'instruction
ADD
et 1 pour l'instructionADC
. Dès lors, la valeur du dernier argument à passer à la méthodeadd
est donnée par la table suivante, où les colonnes représentent les deux valeurs possibles du bit 3 de l'opcode et les lignes représentent les deux valeurs possibles du fanionC
.0 1 0 0 0 1 0 1 Cette propriété est également vraie pour les instructions de soustraction et s'applique donc aux familles
ADD_A_R8
,ADD_A_N8
,ADD_A_HLR
,SUB_A_R8
,SUB_A_N8
etSUB_A_HLR
. - Famille des instructions de manipulation du fanion
C
Une propriété très similaire à celle des instructions d'addition et de soustraction existe pour les instructions qui manipulent le fanion
C
, à savoirSCF
etCCF
. Pour cette raison, elles ont également été regroupées dans une famille, nomméeSCCF
.En comparant l'encodage de leur opcode, vous verrez que la nouvelle valeur du fanion
C
peut se déterminer en fonction du bit 3 de l'opcode et de la valeur actuelle du fanionC
, selon la table suivante :0 1 0 1 1 1 1 0 Observez que cette table est l'exact complément de la précédente.
- Famille des additions aux pointeur de pile
La famille
LD_HLSP_S8
contient les deux instructions suivantes :ADD SP, e8 LD HL, SP + e8
On pourrait penser que ces instructions sont totalement différentes, mais ce n'est pas le cas car les deux :
- lisent la valeur 8 bits suivant leur opcode,
- interprétent cette valeur comme une valeur signée, en complément à deux (!),
- ajoutent cette valeur signée à la valeur actuelle du registre
SP
, - modifient les fanions selon le schéma
00HC
, où la valeur des fanionsH
etC
est celle correspondant à l'addition des 8 bits de poids faible des deux arguments, - stockent le résultat de cette addition.
La seule différence est que l'instruction
ADD
stocke le résultat dans le registreSP
, tandis queLD
le stocke dans la paire de registresHL
. En comparant l'encodage de l'opcode de ces deux instructions, on constate que le bit 4 permet de déterminer si le résultat doit être stocké dansSP
(s'il vaut 0) ouHL
(s'il vaut 1). - Famille des modifications de bits
Les instruction
RES
etSET
permettent respectivement de mettre à 0 ou à 1 un bit donné d'une valeur. Vu leur similarité, il paraît raisonnable de les placer dans des familles communes, que nous avons choisi de nommerCHG
(pour change). Il existe deux de ces familles, la première qui modifie un bit d'une valeur stockée dans un registre, la seconde qui modifie un bit d'une valeur stockée en mémoire. La table ci-dessous les décrit.Famille Instruction(s) en assembleur CHG_U3_R8
RES 0, A
/SET 0, A
/ … /SET 7, L
CHG_U3_HLR
RES 0, [HL]
/SET 0, [HL]
/ … /SET 7, [HL]
En examinant l'encodage des instructions
RES
etSET
, vous verrez que le bit 6 de leur opcode est celui qui permet de les distinguer : il vaut 0 pour les instructionsRES
, 1 pour les instructionsSET
. - Gestion des fanions
La gestion des fanions stockés dans le registre
F
constitue l'une des principales difficultés de cette étape. En effet, la manière dont les fanions produits par l'UAL doivent être combinés avec ceux stockés dans le registreF
est complexe et varie d'une instruction à l'autre.Pour mémoire, l'UAL est modélisée dans ce projet par la classe
Alu
dont les méthodes retournent conceptuellement une paire composée du résultat de l'opération et des 4 fanions. Les éléments de cette paire sont empaquetés dans un entier de typeint
. Il est donc avantageux d'offrir, dans la classeCpu
, des méthodes privées prenant un tel entier et stockant certains de ses éléments à différents endroits. Les méthodes suivantes couvrent les cas les plus fréquents (l'argumentvf
est toujours un entier contenant une paire valeur/fanions retournée par l'une des méthodes de la classeAlu
) :void setRegFromAlu(Reg r, int vf)
, qui extrait la valeur stockée dans la paire donnée et la place dans le registre donné,void setFlags(int valueFlags)
, qui extrait les fanions stockés dans la paire donnée et les place dans le registreF
,void setRegFlags(Reg r, int vf)
, qui combine les effets desetRegFromAlu
etsetFlags
,void write8AtHlAndSetFlags(int vf)
, qui extrait la valeur stockée dans la paire donnée et l'écrit sur le bus à l'adresse contenue dans la paire de registresHL
, puis extrait les fanions stockés dans la paire et les place dans le registreF
.
Ces méthodes ne sont malheureusement pas utilisables directement pour les instructions qui ne modifient qu'un sous-ensemble des fanions stockés dans le registre
F
. Pour ces instructions-là, il est utile d'offrir une méthode permettant de combiner arbitrairement les 4 fanions retournés par l'UAL avec ceux stockés dans le registreF
, en ayant de plus la possibilité de les forcer à une valeur donnée. Cette méthode pourrait ressembler à :void combineAluFlags(int vf, FlagSrc z, FlagSrc n, FlagSrc h, FlagSrc c)
, qui combine les fanions stockés dans le registreF
avec ceux contenus dans la pairevf
, en fonction des quatre derniers paramètres, qui correspondent chacun à un fanion, et stocke le résultat dans le registreF
.
Le type
FlagSrc
est un type énuméré contenant 4 valeurs qui correspondent aux 4 sources possible d'un fanion, et qui sont :V0
, qui force le fanion à 0,V1
, qui force le fanion à 1,ALU
, qui utilise le fanion fourni par l'UAL (contenu dansvf
),CPU
, qui utilise le fanion du processeur (contenu dans le registreF
).
Par exemple l'appel
combineAluFlags(vf, FlagSrc.ALU, // Z FlagSrc.V1, // N FlagSrc.ALU, // H FlagSrc.CPU); // C
modifie le registre
F
pour que le fanionN
vaille 1, les fanionsZ
etH
soient ceux contenus dansvf
, tandis que le fanionC
est laissé à sa valeur actuelle. Un tel appel est utile dans la mise en œuvre de toute instruction dont l'effet sur les fanions est décrit parZ1H-
selon la notation utilisée plus haut, par exempleDEC [HL]
. - Extraction de paramètres
Tout comme pour l'étape précédente, il est conseillé de définir des méthodes permettant d'extraire certains paramètres d'un ou plusieurs bits de l'opcode d'une instruction. Pour les familles d'instructions décrites ci-dessus, des méthodes permettant d'extraire les informations suivantes sont utiles :
- la direction de rotation (bit 3), pour toutes les familles regroupant des instructions de rotation à gauche et à droite,
- l'index du bit à tester ou modifier (bits 3 à 5), pour les instructions
BIT
,RES
etSET
, - la valeur à attribuer au bit (bit 6) à modifier, pour les instructions
RES
etSET
.
De plus, écrire une fonction permettant de combiner le fanion
C
et le bit 3 de l'opcode selon l'une des deux tables données plus haut (aux §2.2.1.4 et §2.2.1.5) permet de simplifier la gestion des instructions d'addition, de soustraction et de manipulation du fanionC
.
2.3 Tests
Comme pour l'étape précédente, nous ne vous fournissons plus de tests mais un fichier de vérification de signatures contenu dans une archive Zip à importer dans votre projet. Comme l'interface publique de la classe Cpu
ne change pas, ce fichier de signature sert uniquement à vérifier que vous avez bien importé les fichiers AddressMap
et Opcode
dans votre projet — ce que nous avions malheureusement oublié de faire précédemment.
3 Résumé
Pour cette étape, vous devez :
- augmenter la classe
Cpu
pour y ajouter la gestion des instructions arithmétiques et logiques, comme décrit plus haut, - rendre votre code au plus tard le 16 mars 2018 à 16h30, via le système de rendu.
Ce rendu est un rendu testé, auquel 18 points sont attribués, au prorata des tests unitaires passés avec succès.
N'attendez surtout pas le dernier moment pour effectuer votre rendu, car vous n'êtes pas à l'abri d'imprévus. Souvenez-vous qu'aucun retard, aussi insignifiant soit-il, ne sera toléré !