![]() | Login | ![]() | ![]() |
![]() | Sasfépu | ![]() | ![]() |
![]() | Interviews | ![]() | ![]() |
![]() | Emulateurs | ![]() | ![]() |
![]() | Projets | ![]() | ![]() |
Un émulateur0. Introduction Ce document n'a pas de grandes prétentions, il existe uniquement parce que n'ayant jamais développé d'émulateur auparavant, j'aurais bien aimé en trouver un comme celui-ci sur la toile. Il ne détient pas l'unique vérité et contient uniquement ma démarche de débutant dans le domaine pour construire mon premier émulateur. 1. Qu'est ce qu'un émulateur ? Un programme qui reproduit le comportement d'une machine et de ses périphériques sur une autre dans ses moindres détails, ceci incluant les éventuels bogues. Très important aussi, une émulation réussie simule le fonctionnement de la machine à sa vitesse d'origine exacte. Un émulateur ZX Spectrum qui tourne à la vitesse d'une GameCube ne sera pas parfait !
2. La machine Il faut choisir une machine à émuler et uniquement une. Choisir une gamme (par exemple : tous les hector) complique beaucoup trop la tâche ! Toutes similaires qu'elles soient en apparence, elles ont des différences qu'il est difficile d'intégrer au premier jet.
Coeur de l'ordinateur, c'est lui qui va animer les programmes inséré en mémoire par l'utilisateur.
Dans tout ordinateur, on trouve de la mémoire RAM (vive et volatile), et ROM (morte uniquement lisible et persistante). Cette mémoire est accédée par le processeur qui exécute les instructions qu'on lui soumet.
Ou plutôt l'équipement qui va permettre à l'ordinateur de faire du bruit, de la musique pour les plus doués. Il se caractérise sur HECTOR par une puce reliée et controlée
par le processeur.
Tout ce qui est connecté à l'ordinateur est un périphérique !
Il permet à l'utilisateur de transmettre des données à l'ordinateur pour qu'il les traite. Le fait d'appuyer sur une ou plusieurs touches "envoie" des informations à la machine qui va réagir en fonction de ce que l'électronicien qui a construit le circuit afait ert de ce qui a été programmé dans la ROM par le constructeur. ![]() Le clavier a l'air banal mais représente bien un module à part, bien souvent, il ne se contente pas uniquement de transmettre le code de la touche pressée.
Ici nous parlons plutôt tu tube cathodique et des circuits reliés au processeur pour le gérer. En effet, pour, par exemple, afficher un point à l'écran plusieurs circuits interviennent. Le canon à électrons qui projette son faisceau sur la paroi phosporée du tube est controlé par les signaux envoyés par l'ordinateur qui a du exécuter plusieurs instructions avant d'avoir pu obtenir un résultat visible.
Les joysticks sont assez particuliers sur hector, ils disposent de potentiomètres, en plus des traditionnels boutons "feu"
Ce périphérique-ci fait l'interface entre l'ordinateur et la cassette, media de stockage de masse, persistant. On stocke des données sous forme analogique (son) que l'on relit et convertit au format numérique compréhensible par la machine. ![]()
Le lecteur de disquettes est presque comme le lecteru de cassettes à la différence de son organisation et qu'il dispose souvent d'un processeur et mémoire propre (Ceci est vrai pour les lecteurs commodore et hector)
Les cartouches comportent une mémoire (ROM) dans laquelle est enregistré
un programme. Pour le hector la seule cartouche référencé
jusqu'à présent est celle qui contient le basic. Sur hector, on
la branche sur le port paralèlle.
Les différents ports qui équipe la machine servent à brancher des périphériques pour communiquer avec l'ordinateur. Sur Hector, on a, pour les derniers modèles, l'interface parallèle et les prises joystick. 3.0 La légalité L'émulateur, le programme en lui même, ne fait que reproduire le comportement d'une machine, il ne possède pas de code protégé commercialement. Par contre, les roms et logiciels sont propriétés de la société qui détient les droits. Vous devez posséder l'original pour pouvoir utiliser la rom ou le jeu et vous n'avez pas le droit de désassembler la ROM ou les logiciels, mais juste d'utiliser.
4. Choix techniques
Pourquoi pas Windows ???? Deux réponses possibles au choix: 1. J'aime pas Windows :) et il y a déjà beaucoup de choses fonctionnant dessus. 2. Le développer sur U*ix ne permet pas forcément de s'adresser à une énorme population mais cette plateforme à l'avantage d'accroitre la portabilité. En effet, développer pour U*ix permet de voir fonctionner son émulateur sur tous les linux, AIX, Solaris, HP-UX, Mac OS-X, etc... sans adaptations majeures.
Le langage utilisé ici est le C. Pourquoi ? Parce qu'un langage comme l'assembleur est rapide mais pas du tout portable et difficilement maintenable, le C++ trop lent, Java encore plus lent. Le C par contre offre de bonnes possibilités de portage et d'évolution du code sans être trop lent à exécuter.
Unix et C, il faut aussi une interface graphique commune ou disponible gratuitement sur toutes les variantes de ce système d'exploitation. Gtk2 ou Kde auraient été possibles, mais compiler KDE ou Gtk2 sur AIX 4.3.3, "c'est l'aventure" ! Motif v2 a l'avantage d'être livré avec les Unix commerciaux et d'être disponible gratuitement sur les autres plateformes. 5.0 Principes de l'émulateur (comment faire ?)
La machine commence à fonctionner dès qu'on la branche sur le secteur et qu'on appuie sur le bouton "Marche". Pour un émulateur c'est pareil, on lance l'émulateur et il commence à exécuter des instructions jusqu'à ce qu'on en sorte. En gros cela revient à avoir une boucle principale ou l'on effectue l'émulation de la machine.
Maintenant la grande question est: "Par quel bout commencer la réalisation de l'émulateur ?" Sans conteste, la première chose à faire est lire ! Effectivement, comment émuler une machine dont on ne connait rien ? Il faut se documenter sur les internes de la machine, le type de microprocesseur, la taille mémoire, son fonctionenement interne. Personnellement, je pense qu'il ne sert à rien de démarrer sans avoir au minimum une connaissance au moins basique de l'assembleur de la machine que l'on veut émuler et du type de processeur de l'ordinateur. Une fois que l'on dispose de ces informations, il faut savoir programmer dans
un langage adapté à ce que l'on veut faire, connaitre le logo
ou comment coder une macro excel ne vous permettra pas de réaliser un
émulateur (ou alors vous êtes très très fort et je
désire faire votre connaissance Une derniere chose à savoir est comment programmer la partie graphique de l'émulateur. Si vous aller développer sur Windows, il vous faudra surement maîtriser la programmation de DirectX, sous Unix celle de X11 (ou tout autre API graphique). Ah oui, j'oubliais: Vouloir commencer à programmer un émulateur
de Console dernier cri n'est pas une bonne idée
L'émulation du processeur est en théorie simple, c'est la partie qui prend le plus de temps si vous décidez de tout coder depuis le début car il faut vous procurer une documentation qui décrit l'ensemble des instructions comprises par le processeur et les effets qu'elles ont sur lui. Pour le Hector, le processeur est un Z80 de la société Zilog il comprend les registres A,F,B,C,D,E,H,L et leurs images ,IY,IX,R, un pointeur de pile (SP ou Stack Pointer) et un compteur de programme (PC ou Program Counter). Le processeur exécute l'instruction pointée par le registre PC (16 bits).
Le processeur va d'abord traiter l'instruction stockée en mémoire à l'adresse 0x5FE2 (nombre en base 16) qui es LD A,0 qui lui indique qu'il faut mettre la valeur 0 dans le registre A. Le PC passe alors à l'instruction suivante et exécute un XOR A, un OU logique. Cette opération est l'équivalente à A = A | A soit un résultat de 0. A ce moment là, le Z80 va aussi mettre à jour le registre F qui est un registre spécial qui contient plusieurs indicateurs (flags), dans le cas d'une opération retournant un résultat nul, le "flag" (en fait un bit) correspondant est positionné à 1. Il faut savoir que chaque instruction met du temps à être exécutée par le microprocesseur, celui-ci varie en fonction de la complexité de l'instruction. Donc on se retrouve a créer une structure qui regroupe plusieurs choses: Les différents registres et d'autres informations de contrôle.
On voit bien que chaque registre de 16 bits est composé d'une "paire" d'octet, structure définie avant, ceci uniquement pour des facilités d'accès pour le programmeur. On aurait pu écrire ou
mais à l'usage, c'est beaucoup moins pratique. Après on peut aussi définir quelques macros pour pouvoir accéder aux registres facilement sans taper des kilomètres de code:
Ceci fait, on regarde combien d'instructions comprend le microprocesseur. Chaque instruction est représenté par une valeur ou plus, ci-dessous un bout de code assembleur Z80. A droite les instructions "décodées", lisibles par le commun des mortels, à gauche se trouvent les valeurs qui représentent l'instruction :
On peut donc les classer par leur valeur respective. On fait un structure "enum" qui va permettre de les référencer facilement:
Certaines de ces instructions positionnent des flags dans le registre F lorsque elles sont exécutées par exemple le flag de signe ou le flag Zéro lorsque le résultat de l'opération est 0, on pourrait créer une routine qui en fonction du résultat stocké dans A, retourne ou positionne les bon flags, mais ce ne serait pas très optimisé ! Le plus simple est de créer un tableau de la taille du nombre de valeurs que peut prendre le registre A et d'y mettre les flags Zero et Signe correspondants à chaque valeur:
On peut ainsi, lors de l'exécution d'une instruction qui peut avoir un effet sur le registre d'état du processeur, savoir quelle est la valeur est à utiliser pour ces deux flags. Pour le temps (Tstate) que prend chaque instruction lors de son traitement, on fait de même: un tableau qui contient les temps de chacune d'elles. Les flags zéro et signe ne sont pas les seuls existant, d'autres comme la retenue ou le bit de parité existent aussi, tout ceci est lié au processeur que vous tentez d'emuler et est contenu dans ses spécifications. Une fois que l'on a tous les tableaux nécessaires, on peut continuer et Créer plusieurs fonctions qui vont simuler le fonctionnement du processeur: Il me vient à l'esprit trois fonctions différentes : Step, Init et, Interruption. Step va exécuter une instruction et retourner le nombre d'octets pris par l'instruction, Init va initialiser à 0 tous les registres du processeur et Interruption va être appelé lors de la génération d'une interruption. La fonction Step se résume à un switch() géant !:
Pour la fonction "Interruption" on vérifie plusieurs choses: Les interruptions sont-elles désactivées, est-ce une interruption non maskable que l'on doit traiter, et si oui, quel vecteur "adresse" d'interruption dois-je utiliser ?
Ca peut paraitre compliqué au départ mais c'est une fonction qui, tout en restant indispensable, n'est pas forcément utile tout au départ de votre développement ! A ces trois fonctions on peut en rajouter d'autres qui facilitent par la suite la manipulation du processeur virtuel. Des fonctions qui permettent de changer l'état du processeur (qui modifient la variable "state") par exemple.
Les ordinateurs actuels ont une quantité astronomique de mémoire lorsqu'on la compare à la quantité des machines construites dans les années 80. Ceci nous facilite la tâche car il nous suffit de créer un tableau d'octets et de le considérer comme l'espace d'adressage de l'ordinateur.
Voila pour l'espace d'adressage, maintenant , la ROM ! Ce programme inséré sur une eprom est généralement adressé par le processeur à partir de l'adresse 0x0000, dans le cas du hector 1 cette rom a une taille de 4 kilo-octets. Il suffit de la mettre dans le tableau créé plus tôt.
Sur certaines machines comme le hector1, les périphériques sont accessibles d'une manière simple: on peut leur "parler" en écrivant ou lisant à des adresses en mémoire (différentes suivant la machine). Une autre raison pour laquelle il est nécessaure de vérifier les écritures en mémoire est que certaines adresses ont des miroirs à d'autres adresses, ou ne sont que lisibles ,etc... Il faut donc contrôler toutes les lectures ou écritures que l'on fait en mémoire. Pour cela on crée deux fonctions: WRMEM pour écrire et RDMEM pour lire.
Sur hector l'écran correspond à une zone mémoire qui court de 0x4000 à 0x49A0 en basse résolution. Tout ce que l'on écrit dans cette zone la se retrouve affiché à l'écran ! L'octet écrit là, se retrouve affiché à l'écran mais pas comme on se l'imagine: Sur hector un octet code pour 4 pixels différents et contigus. Les Hector ont huit couleurs à leur disposition et peuvent en utiliser quatre simultanément. Cette palette que est stockée sur deux octets (0x1000 et 0x1800) indique au hector quelle est la couleur de la gamme a utiliser pour le numéro d'index 0, 1, 2 et 3. L'octet écrit dans la mémoire vidéo est composés d'un numéro d'index pour chaque pixel "contenu"dans l'octet. Un exemple: La palette comporte quatre couleurs et est initialisée comme suit:
Si l'on veut afficher quatre pixels Jaune,noir,blanc et rouge si faut écrire en mémoire vidéo un octet rempli de la façon suivante :
Il se peut parfois qu'on tombe sur des particularités de la machines qui ne sont pas référencées dans les documentations et ce n'est que lors de la programmation de l'émulateur que l'on s'en aperçoit! En effet, sur hector, en basse résolution nous avons 0x49A0 - 0x4000 = 0x9A0 octets. Un octet codant pour 4 pixels, nous devrions voir 0x9A0 * 4 = 9856 pixels. Mais dans la documentation de la machine on lit que la résolution est de 77 lignes par 113 pixels ce qui ne fait que 8701 pixels ! Mais où sont donc passés les 1155 pixels manquants ? La première version de l'émulateur montrait tous les pixels: ![]() On voit sur le coté droit de l'écran des pixels sans organisation cohérente, en réalité, sur un vrai hector, ils sont masqués. Il faut que la fonction de mise à jour de la mémoire de notre émulateur en tienne compte : mettre à jour la mémoire mais ne pas afficher ces pixels. Sans entrer dans les détails, lorsque l'on dessine l'écran, on effectue juste un modulo sur l'adresse de l'octet à tracer et si il se trouve dans cette zone on ne fait rien.
Le clavier n'est pas forcément "banal" et il faut aussi connaitre son fonctionnement. Sur hector le fait de presser une touche met un bit à 0 et non pas à 1 comme on aurait tendance à le penser. Le code de la touche pressée est aussi stockée en mémoire et un autre octet est modifié pour signaler que l'on a bien pressé une touche ! Sous X-Window, cette routine d'émulation reste assez simple, on attache à l'évènement "une touche est pressée" et à "une touche est relachée" un bout de code qui va réaliser toutes les actions décrites ci-dessus. On récupère le code des touches récemment pressées et on regarde ensuite quel type d'évènement nous arrive. Si c'est un éventement "touche enfoncée" (KeyPress) ou touche relachée (KeyRelease), on agit !
Toujours sur hector, les touches ne modifient pas toutes un bit aux mêmes adresses mémoires, en effet, elles sont organisées par "lignes" de huit touches.
Par exemple, la pression sur la touche N fera changer d'état le bit numéro 4 de l'octet 0x3805 (0xF7).
Le chargement d'un jeu compose de plusieurs phases: La bande contient des données sous format analogique qui sont converties au format numérique par le circuit relié au magnétophone, ceci fait, le hector va reconstruire les différents octets lus et les stocker en mémoire avant de les exécuter. Sur hector c'est à l'adresse 0x3000 que ça se passe ! Cet octet est composé de deux parties: La première longue d'un bit (bit 7) change d'état à chaque passage de la courbe en 0 (voir pour plus d'informations la page sur la conversion des cassettes hector en fichiers binaires) et les 7 autres bits font office de compteur matériel. Lorsque le bit 7 change d'état on regarde à quel stade est le compteur matériel, suivant la valeur on sait alors si on a affaire à un 1, un 0 ou un cycle de synchronisation "gap". L'émulation consiste à faire "croire" à la ROM que cette adresse est bien reliée au circuit de conversion. Dans la fonction RDMEM vue précédemment on ajoute un case La fonction GetBit nous permet de lire le dump du logiciel que l'on a sélectionné auparavant et de fournir la bonne valeur lorsque l'on vient lire le contenu de l'adresse 0x3000 6. Conclusion et remerciements 50Ko de texte pour juste survoler le sujet! En effet, d'autre types d'émulations existent et il y a encore beaucoup à dire dessus. Merci à Jean Fontayne pour l'aide et les indispensables informations procurées sur les hector et merci à Romuald Liné pour les conseils, les corrections et les bonnes idées ! Si vous désirez apporter commentaires, remarques ou désirez que
telle ou telle section soit étoffée, n'hésitez pas écrivez. 7. Postscriptum - Hemulector , le premier émulateur de Hector 1 ! La version "alpha" de cet émulateur est disponible pour les systèmes d'exploitation suivants:
| |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Framework PHP ©2002-2003 Stéphane Vanlierde |