bla bla du début
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 ?
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 :
-
devkitarm r20 à l'adresse http://sourceforge.net/project/showfiles.php?group_id=114505&package_id=124207&release_id=481692
-
libnds 20070503 à l'adresse http://sourceforge.net/project/showfiles.php?group_id=114505&package_id=151608&release_id=505770
-
libgba 20060720 à l'adresse http://sourceforge.net/project/showfiles.php?group_id=114505&package_id=124007&release_id=433345
-
libfat 20070127 à l'adresse http://sourceforge.net/project/showfiles.php?group_id=114505&package_id=197264&release_id=481700
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 ?
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.

Soit, mais ... C'est quoi exactement émuler une machine ?
Rapidement mais concrétement ^^, cela se résume aux principes décrits ci dessous
L'interprétation
Tantque (CPU fonctionne) je lis l'opcode j'interpréte l'opcode FinTantque
Facile non ? Bon ok, vous le verrez plus en détail dans les autres journées de ce tutoriel :D !
La recompilation statique
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
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 ?
- 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 ?
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 DEVKITPRO := /d/ndsdev/
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 ?
Gérer les opérations CPU
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 ;)
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
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
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 |




