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 :

  1. un premier octet qui constitue le préfixe, qui est le même pour toutes les instructions préfixées et vaut CB16,
  2. 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 ou C) 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 et H 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

  1. 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ération Opcode contient également des éléments correspondant aux instructions préfixées, qui sont identifiés par leur sorte (kind), qui vaut PREFIXED. Pour ces instructions, la valeur stockée dans l'attribut encoding 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);
    
  2. Augmentation de la méthode dispatch

    Bien entendu, la gestion des nouvelles instructions se fait en augmentant le switch de la méthode dispatch 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;
    
  3. 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.

  4. 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 classe Alu 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 fanion C.

    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'instruction ADC. Dès lors, la valeur du dernier argument à passer à la méthode add 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 fanion C.

      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 et SUB_A_HLR.

  5. 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, à savoir SCF et CCF. Pour cette raison, elles ont également été regroupées dans une famille, nommée SCCF.

    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 fanion C, 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.

  6. 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 :

    1. lisent la valeur 8 bits suivant leur opcode,
    2. interprétent cette valeur comme une valeur signée, en complément à deux (!),
    3. ajoutent cette valeur signée à la valeur actuelle du registre SP,
    4. modifient les fanions selon le schéma 00HC, où la valeur des fanions H et C est celle correspondant à l'addition des 8 bits de poids faible des deux arguments,
    5. stockent le résultat de cette addition.

    La seule différence est que l'instruction ADD stocke le résultat dans le registre SP, tandis que LD le stocke dans la paire de registres HL. 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é dans SP (s'il vaut 0) ou HL (s'il vaut 1).

  7. Famille des modifications de bits

    Les instruction RES et SET 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 nommer CHG (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 et SET, vous verrez que le bit 6 de leur opcode est celui qui permet de les distinguer : il vaut 0 pour les instructions RES, 1 pour les instructions SET.

  8. 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 registre F 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 type int. Il est donc avantageux d'offrir, dans la classe Cpu, 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'argument vf est toujours un entier contenant une paire valeur/fanions retournée par l'une des méthodes de la classe Alu) :

    • 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 registre F,
    • void setRegFlags(Reg r, int vf), qui combine les effets de setRegFromAlu et setFlags,
    • 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 registres HL, puis extrait les fanions stockés dans la paire et les place dans le registre F.

    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 registre F, 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 registre F avec ceux contenus dans la paire vf, en fonction des quatre derniers paramètres, qui correspondent chacun à un fanion, et stocke le résultat dans le registre F.

    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 dans vf),
    • CPU, qui utilise le fanion du processeur (contenu dans le registre F).

    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 fanion N vaille 1, les fanions Z et H soient ceux contenus dans vf, tandis que le fanion C 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 par Z1H- selon la notation utilisée plus haut, par exemple DEC [HL].

  9. 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 et SET,
    • la valeur à attribuer au bit (bit 6) à modifier, pour les instructions RES et SET.

    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 fanion C.

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é !