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
0signifie que le fanion vaut toujours 0, - un
1signifie que le fanion vaut toujours 1, - un
-(tiret) signifie que le fanion ne change pas de valeur, - une lettre (
Z,N,HouC) 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
ZetHsont déterminés en fonction des fanions produits par l'UAL, - le fanion
Nest mis à 1, - le fanion
Cne 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
Opcodecorrespondant à une instructions préfixée. En effet, l'énumérationOpcodecontient é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'attributencodingest 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
switchde la méthodedispatchafin 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_R8INC A/INC B/ … /INC LINC_HLRINC [HL]INC_R16SPINC BC/INC DE/INC HL/INC SPADD_HL_R16SPADD HL, BC/ADD HL, DE/ … /ADD HL, SPDEC_R8DEC A/DEC B/ … /DEC LDEC_HLRDEC [HL]DEC_R16SPDEC BC/DEC DE/DEC HL/DEC SPCP_A_N8CP A, 0/CP A, 1/ … /CP A, $FFCP_A_R8CP A, A/CP A, B/ … /CP A, LCP_A_HLRCP A, [HL]AND_A_N8AND A, 0/AND A, 1/ … /AND A, $FFAND_A_R8AND A, A/AND A, B/ … /AND A, LAND_A_HLRAND A, [HL]OR_A_N8OR A, 0/OR A, 1/ … /OR A, $FFOR_A_R8OR A, A/OR A, B/ … /OR A, LOR_A_HLROR A, [HL]XOR_A_N8XOR A, 0/XOR A, 1/ … /XOR A, $FFXOR_A_R8XOR A, A/XOR A, B/ … /XOR A, LXOR_A_HLRXOR A, [HL]CPLCPLROTCARLCA/RRCAROTARLA/RRAROTC_R8RLC A/RRC A/ … /RLC L/RRC LROT_R8RL A/RR A/ … /RL L/RR LROTC_HLRRLC [HL]/RRC [HL]ROT_HLRRL [HL]/RR [HL]SWAP_R8SWAP A/SWAP B/ … /SWAP LSWAP_HLRSWAP [HL]SLA_R8SLA A/SLA B/ … /SLA LSRA_R8SRA A/SRA B/ … /SRA LSRL_R8SRL A/SRL B/ … /SRL LSLA_HLRSLA [HL]SRA_HLRSRA [HL]SRL_HLRSRL [HL]BIT_U3_R8BIT 0, A/BIT 1, A/ … /BIT 7, LBIT_U3_HLRBIT 0, [HL]/BIT 1, [HL]/ … /BIT 7, [HL]DAADAALes 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_N8contient 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
addde la classeAluqui 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
ADDet 1 pour l'instructionADC. Dès lors, la valeur du dernier argument à passer à la méthodeaddest 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_N8etSUB_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, à savoirSCFetCCF. 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
Cpeut 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_S8contient 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 fanionsHetCest 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
ADDstocke le résultat dans le registreSP, tandis queLDle 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
RESetSETpermettent 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_R8RES 0, A/SET 0, A/ … /SET 7, LCHG_U3_HLRRES 0, [HL]/SET 0, [HL]/ … /SET 7, [HL]En examinant l'encodage des instructions
RESetSET, 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
Fconstitue 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 registreFest complexe et varie d'une instruction à l'autre.Pour mémoire, l'UAL est modélisée dans ce projet par la classe
Aludont 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'argumentvfest 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 desetRegFromAluetsetFlags,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 registreFavec 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
FlagSrcest 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
Fpour que le fanionNvaille 1, les fanionsZetHsoient ceux contenus dansvf, tandis que le fanionCest 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,RESetSET, - la valeur à attribuer au bit (bit 6) à modifier, pour les instructions
RESetSET.
De plus, écrire une fonction permettant de combiner le fanion
Cet 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
Cpupour 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é !