Émuler la Gameboy

Dans cet article, nous allons étudier le fonctionnement de la Gameboy (nom de code « DMG-01 »). Ce ne sera qu’une courte introduction sur le sujet, afin de construire votre culture, et pouvoir continuer seul en autonomie votre étude du sujet, peut-être même construire un émulateur !

Je n’aborderai pas le fonctionnement exact des APIs (audio et vidéo) fournit par Nintendo ; je ne vais introduire que le CPU et la mémoire virtuelle.

L’émulation est un monde curieux, intéressant et subtil. Nombreux sont les passionnés et les curieux qui créent un émulateur.

Ces passionnés sont rassemblés sous l’étendard d’un subreddit : /r/EmuDev. Vous trouverez des réponses à vos questions ainsi qu’un Discord. La communauté est très ouverte et agréable !

 

Les documentations

Voici la plupart des documentations utiles. Nous allons les rencontrer à nouveau par la suite de cet article, et apprendre à les comprendre ensemble. Notez bien que certaines de ces documentations possèdent des erreurs. Il est important de relier les informations entre chaque documentation.

 

Gameboy ou Gameboy Color ?

Vous pouvez le constater, les deux consoles sont très similaires ! Le processeur est le même, seul sa fréquence a été doublé. Le « framework » interne est également le même à la différence près de l’ajout des couleurs dans la Gameboy Color.

C’est pour cela qu’il est commun de concevoir un émulateur capable d’émuler les deux consoles. Nous n’allons étudier que la Gameboy original, mais la vaste majorité des informations spécifiés ici sont également valable pour la Gameboy Color.

 

Le processeur

Tout commence avec le processeur. Le nom officiel du processeur de la Gameboy est Sharp LR25902. Vous vous en doutez, ce processeur n’est en rien similaire aux processeurs modernes. Son fonctionnement est beaucoup plus simple à comprendre !

Notez que ce processeur est très proche du Z80, et partages nombreuses de ses caractéristiques.

Son horloge est fixée sur 4.19Mhz. Cela signifie que le processeur effectue 4,19 millions de cycles par secondes. Sur cette puce, on peut considérer que 4 cycles représentent une opération. Par simplicité, il est commun d’appeler 4 cycles d’horloge : « 1 cycle machine ».

C’est un processeur 8 bits, cela signifie que ses instructions sont codées sur 8 bits (1 octet) et qu’il est capable de lire et écrire 1 octet par cycle machine dans la mémoire.

Prenons pour exemple l’instruction qui permet de copier la valeur du registre B dans le registre A (instruction 0x78, LD A,B): cela prendra 1 cycle machine, donc 4 cycles d’horloge sois environ 1 us.

Par contre, l’ALU fonctionne sur 4 bits. Cela signifie que la Gameboy ne sait faire des additions, soustractions, et opérations logiques que sur 4 bits. Lors d’une addition entre deux nombres 8 bits, l’ALU fera l’addition des 4 premiers bits puis l’addition des 4 derniers bits. Voici un petit article très intéressant sur le sujet.

 

Les registres

Le processeur possède des registres. Ce sont des variables intégrées dans le processeur, elles sont très rapides à lire et écrire car elles ne nécessitent aucun accès à la mémoire vive[1]. Voici la liste des registres :

Figure 1 Capture de GBCPUman.pdf

Un total de 10 registres sont donc disponible, certains étant spécifiques.

  • A: le résultat des opérations mathématiques et utilisation libre
  • F: différents flags relatifs aux opérations mathématiques et utilisation libre
  • B, C, D, E: utilisation libre
  • H et : enregistrer des adresses (16-bits) et utilisation libre
  • SP: Stack Pointer (16-bits), représente l’adresse basse de la stack
  • PC: Program Counter (16-bits), représente l’adresse de la prochaine instruction

 

En interne, les registres ont tous une taille de 16 bits. C’est pourquoi les registres A et F sont également lisible sous la forme AF.

Notez que la Gameboy fonctionne en little-endian, comme les systèmes modernes. Cela signifie que le bit le plus petit est à droite. Ainsi, le bit qui vaut 1 est le « dernier bit de F », et le bit qui vaut 32768 est le « premier bit de A » (AFformant un nombre 16 bits).

 

Les instructions

Tout processeur possède ce qu’on appelle un instruction set[2]. Sur les ordinateurs, l’instruction set est en général x86_64[3]. Pour détailler un petit peu, «x86[4]» est le nom de l’instruction set original, et «_64» représente l’extension 64 bits.

Dans les téléphones et microcontrôleurs, nous retrouvons ARM, qui spécifie une collection d’instruction sets (ARMv7 et ARMv8 par exemple) ainsi qu’une circuiterie de référence fournit par ARM lui-même (les fameux « Cortex-M4 » et autres). ARM a l’avantage d’être moins gourmand en énergie, c’est pourquoi nous retrouvons des puces ARM dans tous nos téléphones, consoles de jeux portable, et bientôt dans nos ordinateurs !

Petite note : dans le cas présent, « 64 bits » signifie que l’adressage mémoire (entre autre) est codé sur 64 bits, nous laissant la liberté d’adresser un total de 2^64 adresses différentes. C’est pour cela que les systèmes 64-bits ne sont pas limité à 4Go de ram comme les systèmes 32-bits[5].

Vous pouvez visualiser toutes les instructions du processeur de la Gameboy ici : http://www.pastraiser.com/cpu/gameboy/gameboy_opcodes.html. Ne vous inquiétez pas, je vais tout vous expliquer !

Le processeur de la Gameboy lit les instructions les unes après les autres depuis la mémoire de la cartouche. Une instruction n’est rien d’autre qu’un nombre ! Ces instructions sont localisées à un endroit précis dans la mémoire, on appelle cela une adresse.

Le processeur possède un registre PC. Le PC est un nombre 16-bits qui représente l’adresse de la prochaine instruction à exécuter. PC signifie Program Counter.

Le processeur effectue indéfiniment la boucle suivante :

  • Fetch: lire l’instruction pointé par PC, puis incrémenter PC
  • Decode: savoir ce que représente cette instruction
  • Execute: exécuter l’instruction

 

Le processeur lit donc l’instruction, et il tombe par exemple sur l’octet 0x78. Le tableau que j’ai donné plus haut indique :

  • LD A,B représente l’action de l’instruction. Une grande partie des instructions est du type LD. Cela signifie Load: une assignation. Ici, nous copions la valeur du registre B dans le registre A. Le pattern est donc : LD DEST,SRC.
  • Le 4 à droite représente le nombre de cycle d’horloge. L’instruction dure 4 cycles horloge, donc 1 cycle machine.
  • Le 1 à gauche représente la longueur de l’instruction dans la mémoire (la ROM, la cartouche). Cette instruction ne prend aucun paramètre donc elle a une longueur de 1 octet : l’octet 0x78. Il existe des instructions qui prennent en paramètre un nombre ou une adresse. (Comme une fonction en programmation)
  • Cette instruction est Ce nombre se nomme un opcode.

 

Il existe de nombreuses instructions différentes, voici une liste succincte :

  • LD : Load, assigner une valeur.
  • ADD, SUB, INC, DEC: Opérations mathématiques
  • AND, OR, XOR: Logique binaire
  • PUSH, POP: Manipuler la stack
  • CP, JP, JZ: Logique de contrôle (comme les if en programmation)
  • CALL, RET: Appeler et quitter une fonction
  • EI, DI: Activer ou désactiver les interruptions
  • NOP: une instruction qui ne fait rien (oui, c’est utile !)

 

La fréquence du processeur

Le processeur a une fréquence fixe et connue : 4194304 Hz. Nous connaissons donc la durée d’un cycle d’horloge !

F étant la fréquence du processeur, le résultat étant la durée d’un cycle d’horloge en secondes.

Ainsi, une instruction qui dure 4 cycles durera environ  .

 

Les interruptions

Les interruptions sont présentes dans tous processeurs et microcontrôleurs. Lorsqu’un événement hardware a lieu, une interruption est levée. Par exemple, appuyer sur un bouton ferme un circuit électrique qui lève ensuite une interruption hardware.

Il existe ce que nous appelons un interrupt vector table (IVT). La IVT est toujours localisé au tout début du programme, dès le premier octet, et sa structure est spécifié dans la datasheet du processeur. Vous pouvez retrouver les spécifications de la IVT de la Gameboy ici au chapitre 2.5.4.

Chaque type d’interruption[6] a sa propre adresse. Ces adresses représentent des instructions exécutables.

Lorsqu’une interruption est levée, PC[7] est PUSH[8], puis l’adresse de l’interruption est assignée à PC. Les instructions qui s’exécutent sont désormais celle localisées à l’adresse de l’interruption.

Cela permet par exemple de spécifier un comportement spécifique lorsqu’un bouton est appuyé par l’utilisateur, ce qui rend possible de rendre le jeu interactif.

Lorsque vous avez terminé votre comportement spécifique, vous pouvez alors appeler l’instruction RETI qui permet de POP PC afin de reprendre le programme normalement.

 

En résumé :

  • Le jeu s’exécute…
  • Soudainement, un bouton est appuyé. Une interruption est levée.
  • Si les interruptions sont activées[9], PUSH PC, puis PC = 0x0060.
  • Les interruptions sont maintenant désactivées.
  • Les instructions à partir de 0x0060 s’exécutent.
  • L’instruction RETI est appelée.
  • POP PC automatique
  • Les interruptions sont alors réactivées et l’exécution précédant l’interruption reprends.

 

La mémoire

L’espace d’adressage de la Gameboy est codé sur 16-bits. Cela signifie que les adresses sont comprises entre 0x0 et 0xFFFF inclus.

Figure 2 Capture de GBCPUman.pdf

 

Cette image représente la répartition de l’espace d’adressage.

  • 32kB Cartridge : Les données de la cartouche de jeu. Parfois, les cartouches de jeu contiendront plus de 32kB de données. C’est pourquoi la seconde moitié est « switchable» : imaginez que la cartouche est découpé en tranche de 16kB, et l’on peut choisir la tranche à lire dans l’espace [0x4000;0x8000[.
  • Video RAM et Sprite Memory : Toute la mémoire dédiée à la gestion de la vidéo et des sprites. C’est un sujet que nous n’aborderons pas ici.
  • Internal RAM et Echo of Internal RAM : la mémoire vive tel que vous la connaissez. Celle-ci est dupliqué en deux tranches, exactement identiques[10].
  • Switchable RAM Bank: Les cartouches de jeu peuvent intégrer des électroniques supplémentaires. Notamment, celle-ci peuvent être équipé d’extension de mémoire vive ! Après configuration, c’est à cet endroit que nous pouvons y accéder.

Vous l’aurez remarqué, tout ceci est une mémoire virtuelle. Le MMU se charge de transformer ces adresses virtuelles vers des adresses physiques auprès du bon périphérique.

 

La procédure de démarrage

Vous connaissez probablement l’écran de démarrage de la Gameboy, le très célèbre logo Nintendo :

Lorsque la Gameboy se lance, un bootloader spécifique est chargé, contenu directement dans la Gameboy et non dans la cartouche de jeu.

Le bootloader fais 256 octets. Lors de la mise sous-alimentation de la console, le bootloader est automatiquement disponible dès l’adresse 0x0000 jusque l’adresse 0x00FF et la première instruction à s’exécuter se trouve à l’adresse 0x0000. Une fois que le bootloader a terminé ses préparations et souhaite lancer le jeu contenu dans la cartouche, celui-ci écrit dans un registre spécial afin de s’auto-désactiver, et rendre le binaire de la cartouche disponible dans l’adressage [0x0000;0x00FF[.

Le bootloader a été récupéré grâce à un microscope ! La ROM qui contient le bootloader est en réalité une série de « fusibles » ouvrant ou fermant le circuit électrique. Chaque fusible représente un bit : il suffit donc de lire l’état visuel des 2048 fusibles afin de récupérer les 256 octets qui représentent le bootloader.

Afin de d’interdire la vente de jeux Gameboy non-licenciés par Nintendo, ceux-ci ont contraint les développeurs d’intégrer le logo Nintendo (comme sur l’image ci-dessus) dans la cartouche. L’intégrité du logo est vérifiée par le bootloader, afin de procéder au lancement du jeu ou alors bloquer la console.

En effet, sauf autorisation écrite de la part de Nintendo, il est illégal d’embarquer le logo de Nintendo dans son jeu ! Ainsi, si le jeu n’est pas licencié par Nintendo, sa mise en vente est illégale.

Nous pouvons retrouver ce système ainsi que le fonctionnement du bootloader dans le brevet déposé par Nintendo sur le sujet :

Je vous invite à lire ce document, afin d’en apprendre davantage sur le bootloader de la Gameboy. Je vous recommande de lire en bas de page le contenu exact du bootloader, celui-ci est en assembleur et est très intéressant à comprendre.

 

Martin Hammerchmidt

 

 

[1] La mémoire vive se trouve hors du processeur. Communiquer avec la mémoire vive introduit donc une latence. Sur les systèmes modernes, cette latence est 10 à 100 fois plus élevé que l’accès à un registre.

[2] Une collection de commandes que le processeur est capable de comprendre et exécuter afin de faire fonctionner nos logiciels.

[3] https://en.wikipedia.org/wiki/X86-64 https://software.intel.com/sites/default/files/managed/a4/60/325383-sdm-vol-2abcd.pdf

[4] https://en.wikipedia.org/wiki/X86

[5] https://superuser.com/questions/372881/is-there-a-technical-reason-why-32-bit-windows-is-limited-to-4gb-of-ram

[6] L’interruption d’un TIMER, l’interruption d’un bouton par l’utilisateur, …

[7] PC représente l’adresse de la prochaine instruction à exécuter (avant l’interruption)

[8] PC est PUSH sur la stack. Le prochain POP retournera la valeur originale de PC.

[9] Je vous laisse étudier les documentations donnés en en-tête pour découvrir ce qu’il se passe si les interruptions ne sont pas activés.

[10] Pourquoi ? https://www.reddit.com/r/asm/comments/2wq4qi/how_is_echo_ram_used/