bla bla du début

Bien, vous avez donc choisi de regarder le premier jour de ce tutoriel, bonne initiative :D !

Hélas pour vous, ce n'est pas aujourd'hui que vous réaliserez un émulateur AMIGA sur votre DS ou GBA... Continuez à lire, cela peut quand même être intéressant ...

OK, mais bon ... Je dois connaître quoi pour suivre ce cours ?

Héhé ... on va donc commencer par les outils à connaître et à installer sur son ordinateur.
Tout d'abord, il faut avoir installé le devkitarm pour faire fonctionner ces cours. Je vous rappelle qu'il se trouve à l'adresse http://www.devkitpro.org.
Vous devez aussi avoir la libnds, qui se trouve à la même adresse pour développer la partie DS de l'émulateur et vous savez quoi ;), la libgba pour la partie GBA !.
J'ai pris la release 20 du devkitarm, pas la dernière, voici donc les liens directs vers cette release : Nous utiliserons Ideas comme émulateur pour la partie DS, ce dernier peut être téléchargé sur ce site à la page émulateurs sur DS et pour la partie GBA .... ok, j'abrège, bref ... STOP ... VBA à la page émulateurs sur GBA ;-).
J'en vois qui disent "whoua l'autre, le naze, il sait même pas que no$gba émule la DS mais aussi la GBA, il aurait pu prendre un seul émulateur ^^" et moi je réponds "STOP, essayez avec no$gba, vous verrez et écoutez la suite en silence !".Hum, donc, nous utiliserons vba pour la partie GBA car Ideas ne reconnait pas cette dernière pour émuler la partie GBA de la DS. Pour la partie DS, l'avantage d'Ideas sur les autres émulateurs est qu'il est compatible avec le drivers R4 de la libfat et peut donc être mis en place rapidement.

Ensuite, ma foi, je ne vais pas rentrer dans le détail de la compilation via le devkitarm, il faut donc savoir comment ce dernier s'utilise et s'y connaître un minimum en language C.

Halte, ici le juge, est-ce légal votre truc ?

Alors voilà la première question avant de se lancer dans quelque chose, ai-je le droit de le faire ? Je répondrais que c'est légal tant que l'on a pas obtenu les informations de façon ... illégale ! (cf mon petit doigt et le cryptage DVD ou MPEG4 ...). Bref, oui, on a le droit de réaliser des émulateurs et de les distribuer, rien ne l'interdit, du moment que le code n'est pas un code dont les droits sont déposés et que l'émulateur n'est pas livré avec des fichiers sous licence de telle ou telle société.
Par exemple, la société responsable, à l'heure actuelle, des ordinateurs Amstrad, permet à tout le monde de distribuer le BIOS (Basic Input Output System) de l'amstrad avec son émulateur, sans risquer d'être poursuivi pour copie illicite.

Le BIOS est en fait la mémoire contenu dans l'ordinateur, qui s'exécute au démarrage de ce dernier. Il y en a dans d'autres d'équipements informatiques, comme par exemple dans les consoles de jeux (cf la Master System et Alex Kidd qui se lance si aucune cartouche n'est insérée) et dans beaucoup, beaucoup de matériels.
La tâche du Bios est de fournir le support à tous les périphériques qui sont utilisés sur le matériel émulé. Nous allons donc, bien entendu, dans ces pages, faire croire au BIOS que les différents périphériques de la machine émulée sont là :), histoire de bien faire fonctionner le tout !

Soit, mais ... C'est quoi exactement émuler une machine ?

Alors voilà la chose la plus importante et qui va être le coeur de notre système, qu'est-ce que nous devons faire ?

Rapidement mais concrétement ^^, cela se résume aux principes décrits ci dessous

L'interprétation

C'est ce que l'on va faire ! En fait, on va devenir le processeur de notre machine émulée et donc, réaliser les actions suivantes
Tantque (CPU fonctionne)
  je lis l'opcode
  j'interpréte l'opcode
FinTantque
Sachant que l'opcode est la valeur lue par le CPU (processeur) et qu'il doit interpréter suivant la table des opcodes (début d'instruction élémentaire du CPU) émulés qu'il connait.
Facile non ? Bon ok, vous le verrez plus en détail dans les autres journées de ce tutoriel :D !

La recompilation statique

Cela devient plus compliqué que notre méthode précédente ... En effet, ici, le principe consiste à transformer le code assembleur (language de bas niveau utilisé par le processeur) de la machine émulée dans le code assembleur de notre machine mais avant l'éxécution de notre émulateur ... Bref, il faut donc bien connaître l'assembleur sur les 2 machines pour s'en sortir et nous, nous avons dis que nous ferions simple, bref, on utilisera pas cette technique :D !
De plus, cette dernière n'est pas simple à réaliser car il faut fabriquer le fichier avant exécution, on rencontre donc très peu d'émulateurs qui utilisent cette technique.

La recompilation dynamique

C'est en fait la même chose que la version statique, mais on le fait pendant l'exécution de l'émulation, ce qui demande encore plus de savoir faire !
Toutefois, nous ferons un peu de recompilation dynamique via les écritures directement dans la mémoire écran de la DS, comme si c'était la mémoire écran de notre machine émulée, donc on l'utilisera un peu :D !

OK, mais je trouve comment des infos sur la machine à émuler ?

Voilà, on a maintenant notre machine à émuler et on souhaite attaquer la connaissance de cette dernière .. . car ... on ne peut pas émuler une machine si on ne sait pas comment elle travaille, intéragit avec ses différents composants, ce qu'elle contient, etc ...
  • Le meilleur lien est tout d'abord le wikipedia pour connaitre une machine, en français, comme en anglais ! Je n'ai hélas rien trouvé dans la version française, nous nous limiterons donc aux informations de la version anglaise.
  • Ensuite, on peut aussi se baser sur les informations données par Mess et Mame si la machine à émuler rentre dans ces catégories là. En effet, ces derniers recherchent l'émulation parfaite et contiennent donc des informations importantes sur les adressages de la mémoire de la machine émulée, sur les différents composants de la machine, voir les périphériques utilisables par la machine.
  • Enfin, une recherche sur Google ou tout autre moteur de recherche peut aussi aider, et permet ainsi de trouver des pages intéressantes comme sur cette page (en anglais), ou encore celle là pour le jeu d'instructions du chip 8, ou encore là.

Bien, j'ai les infos, mais je commence comment sur ma DS ou ma GBA pour coder un émulateur ?

Bien, coté DS,pour ce premier jour consistant à une prise de contact, nous allons donc juste présenter comment les 2 processeurs de la DS sont exploités pour réaliser notre émulateur.
L'arm7, tout d'abord, comme à son habitude, va se borner à gérer les touches, le stylet et le son que l'on va vouloir produire avec notre émulateur.
Le code de l'arm7 se résume à un simple fichier main.c contenant trois parties :

1) la gestion du son, limité dans notre cas au fait de jouer un fichier de type WAV sans répétition.

2) la gestion du stylet, déclenché par l'interruption VCOUNT (au bout de 80 lignes écrans), permettant de remplir la structure IPC (structure de communication entre l'arm7 et l'arm9).

3) le déclenchement du son, réalisé par l'interruption VBLANK (à chaque fin d'affichage écran donc) , qui se limite à regarder si l'arm9 demande à l'arm7 de jouer un son.

4) le programme principal, qui prépare les interruptions et attend que ces dernières se déclenchent :) !
 



L'arm9 sera le coeur de notre émulateur et va donc se charger l'émuler le CPU de notre machine ainsi que de mettre à jour le graphisme. Sans rentrer dans les détails pour l'instant, on peut tout de même voir qu'il nous faut un certain nombre de fonctions (contrairement à la version arm7, je détaillerais ces fonctions dans chaque cours, ces dernières allant évoluer au fur et à mesure des cours).

1) l'initialisation de la mémoire, contenant toutes les variables que l'on va utiliser avec leur valeur par défaut.

2) l'initialisation de notre émulateur, contenant l'initialisation de la FATLIB (pour charger les fichiers ensuite).

3) la boucle principale de notre émulateur, qui se borne à rien faire pour l'instant :)

4) le programme principal, qui, si l'initialisation s'est bien passée, réalise l'exécution de notre émulateur.

Le code source complet, avec les répertoires, est disponible en bas de cette page. Il faut mettre ce dernier à la racine de votre disque dur.

Voici le résultat de l'exécution de cet émulateur, qui pour l'instant ne fait rien ^^.


Attention au makefile principal, il contient le chemin vers le devkitarm et demande donc à être modifié suivant votre configuration (dans la mienne, le devkitarm se trouve sur le disque D: de ma machine, dans un répertoire nommé ndsdev).

export DEVKITARM := /d/ndsdev/devkitARM
export DEVKITPRO := /d/ndsdev/
On notera aussi, dans les makefile des répertoire arm7 et arm9, que la compilation n'utilise pas le flag -g, ce dernier ne servant qu'à ajouter les informations de debugging, cela prend de place et de temps d'exécution pour rien.
Enfin, la compilation de l'arm9 se fait avec la directive -march=armv5te -mtune=arm946e-s, CPU plus proche de la réalité que celui utilisé dans le makefile par défaut. Il a aussi l'avantage de gagner du temps lors de l'exécution de notre émulateur.

Coté GBA, c'est à peu prés la même chose sauf que n'ous n'avons que l'arm 7 à programmer bien entendu. Aussi, nous utiliserons GBFS et non la LIBFAT. Je vous laisse regarder le code qui ressemble beaucoup au code de l'arm9 de la partie DS de notre émulateur.

D'accord, mais , le CPU à émuler, il doit réagir comment ?

On en veut encore ? On est pas parti en courant après cette petite démonstration ^^ :) Ok, en fait, il y a 2 choses importantes à réaliser. Il faut savoir gérer les opérations reconnues par le CPU (que l'on nommera opcode) et surtout gérer les accès mémoires, c'est à dire comment le CPU attend que la mémoire de la machine émulée réagisse. Nous allons donc regarder tout cela en détail.

Gérer les opérations CPU

Nous verrons dans les cours suivants comment cela se passe mais il faut déja savoir qu'un CPU utilise un certain nombre d'emplacements mémoires internes, que l'on nomme des registres. Ces registres doivent être utilisés pour réaliser des opérations bien précises et ont donc un rôle important à jouer dans notre émulation.
Les 2 plus connus, dans tous les CPUs, sont :
  • le PC, pour Programme Counter, c'est en fait lui qui garde en mémoire l'endroit où se trouve l'instruction que l'on exécute
  • le SP, pour Stack Pointer, est en fait celui qui garde en mémoire l'endroit que l'on appele "la pile", c'est à dire une zone particulière où est sauvegardée les données que l'on utilise pour les restituer ensuite suivant les besoins. La pile est très importante et doit être bien dimensionnée, sinon, il arrive que l'on plante notre programme à cause d'un débordement de pile ;)
Aussi, on a besoin d'un troisième registre, le SR, pour Status Register, qui garde en mémoire l'état des dernières opérations réalisées, pour savoir si une opération donne un résultat nul, on encore une opération qui dépasse le nombre le plus élevé supporté par notre CPU, etc ...

Enfin, nous aurons aussi besoin d'une variable qui va compter le temps que prend chaque instruction exécutée dans une unité de temps que l'on appelle des cycles d'horloge, c'est à dire des temps de bases de notre CPU.
Les cycles d'horloge permettent de faire "battre le coeur" de notre CPU à la vitesse qu'il doit s'exécuter normalement et ainsi de laisser le temps à notre émulateur de raffraîchir l'écran, de gérer les touches, etc ....
Par exemple, un Z80 cadencé à 4Mhz battera donc à 4000000 cycles d'horloge par seconde. Sachant qu'un écran est à 60 Hz, soit 60 fois mis à jour par seconde, nous aurons donc 4000000/60 cycles avant de raffraichir un écran complet et donc de faire un nouveau cycle d'émulation.
Il va de soit que toute la difficulté de l'émulation est d'arriver à tenir cette cadence (à 60Hz ou une autre fréquence suivant le taux de raffraichissement demandé par l'émulateur).
Je me permets donc de vous introduire maintenant (le premier qui rigole sort et va chez le directeur prendre sa punition !) le petit bout de code qui va vérifier que l'on tient bien le rythme par rapport à l'émulation demandée.

Ce code, assez simple, compte le nbre de frames à chaque tour d'émulation, si on dépasse les 60, on compare au compteur mis à jour dans l'interruption VBL de la DS ou de la GBA (qui est déclenchée 60 fois par seconde aussi) et on affiche le pourcentage de perte ou de gain par rapport à ça, simple non ?
Bien entendu, vous verrez qu'en exécutant le code, on ne dépasse jamais les 100% (ou presque ...), pourquoi, tout simplement car on attend l'interruption VBL de la DS après l'affichage !
Cette intruction d'attente swiWaitForVBlank(); (ou VBlankIntrWait(); pour la GBA) peut ^tre mise en commentaire pour voir la vitesse réelle de l'émulateur sans attendre (attention, cela SPEED !!!))
Si on veut raffraîchir l'écran à chaque ligne, on divisera encore ce temps par le nombre de lignes gérées par le processeur graphique de notre machine émulée.
Vous avez donc compris qu'une émulation précise d'un machine oblige à compter le temps que prend chaque instruction émulée, en nombre de cycle d'horloge.

Pour résumer, notre émulation peut se synthétiser à faire les opérations suivantes :
PC = PCInitial;
SP = SPinitiale;
Cycles = 0;
 ...
while(CPUfonctionne) {
  OpCode=LireMemoire(PC++);
  switch(OpCode) {
    case OpCode1:
       ...
      Cycles += temps de l'instruction OpCode1;
      break;
    case OpCode2:
       ...
       Cycles += temps de l'instruction OpCode2;
      break;
  }
  if (Cycles>CYCLESPARTRAME) {
    GereTouches();
    GereEcran();
    ...
  }
}

LireMemoire est la fonction qui va regarder dans la mémoire de la machine émulée la valeur de l'opcode et la stocker dans ce dernier. Ensuite, suivant les opcodes voulus, on exécute telle ou telle chose. Mais ... parlons plus en détail justement de cette gestion de la mémoire maintenant !

Gérer les accès mémoires

Il faut savoir que 2 grandes familles de mémoire existent : la RAM pour Random Access Memory qui est accessible en lecture et écriture et la ROM pour Read Only Memory et qui n'est accessible qu'en lecture. Il est important de connaître le type et l'implémentation de ces dernières avant tout début d'émulation de notre machine, afin de savoir comment réagir suivant tel ou tel type de mémoire.
La mémoire émulée peut se résumer à 2 choses : lire la mémoire et écrire dans la mémoire. Nous devrons donc émuler ses 2 choses qui peuvent se compliquer suivant les machines à émuler comme nous le verrons ensuite.
Donnee=Memoire[Adresse1]; // Lecture de l'adresse Adresse1 
Memoire[Adresse2]=Donnee; // Ecriture à l'adresse Adresse2
Par contre, cela peut se compliquer car, sur certaines machines, la mémoire peut être paginée, c'est à dire réagir suivant la valeur d'une donnée appelée "page" et donc être différente suivant que "page" vaut telle ou telle valeure.
On peut aussi à avoir à gérer ce que l'on appelle de la mémoire "miroire", c'est à dire que l'écriture à une adresse X se propage à une autre adresse Y car cette dernière est une adresse mémoire miroire de X.
Certaines machines, afin de protéger leur code, s'amusent aussi à écrire en mémoire ROM, chose impossible par défaut, nous devons donc prendre soin d'empécher l'écriture dans ce type de mémoire.
Enfin, la mémoire peut aussi être autre chose qu'une simple adresse, comme la mémoire écran, qui permet d'afficher quelque chose sur ce dernier :). C'est ce que l'on appele la mémoire des Entrées / Sorties. La gestion des touches rentre bien entendue dans cette dernière catégorie.

C'est pour cette raison que j'ai réalisé la fonction LireMemoire (et on aura son pendant EcrireMemoire) plutôt qu'une écriture directe dans le tableau Memoire[], car il faut bien entendu gérer tous les cas que nous avons vu ci dessus.

Voilà, nous avons maintenant les bases pour débuter notre émulateur de Chip8, il nous reste à savoir ce que dernier possède comme processeur et adressage mémoire.
Pour compléter cette prise de connaissance, on pourrait aussi parler de l'optimisation possible de notre code pour accélérer l'émulation ou encore de la différence entre low et big endian, mais nous n'en avons pas besoin pour débuter notre émulateur, nous le verrons donc dans un prochain cours ;) !



Pour info, je me suis inspiré de la page de Marat sur la façon d'écrire un émulateur pour réaliser cet article.
Hal8000 Jour 1  (136 fois)23 Jan 2008 | size: 150 kB