968 votes

Comment fonctionnent les émulateurs et comment sont-ils écrits ?

Comment fonctionnent les émulateurs ? Quand je vois des émulateurs de NES/SNES ou de C64, je suis stupéfait.

http://www.tommowalker.co.uk/snemzelda.png

Faut-il émuler le processeur de ces machines en interprétant ses instructions d'assemblage particulières ? Quels sont les autres éléments à prendre en compte ? Comment sont-elles généralement conçues ?

Pouvez-vous donner des conseils à quelqu'un qui souhaite écrire un émulateur (en particulier un système de jeu) ?

1124voto

Cody Brocious Points 24042

L'émulation est un domaine aux multiples facettes. Voici les idées de base et les composants fonctionnels. Je vais le décomposer en morceaux et compléter les détails par des modifications. La plupart des choses que je vais décrire nécessiteront des connaissances sur le fonctionnement interne des processeurs - des connaissances en assemblage sont nécessaires. Si je suis un peu trop vague sur certaines choses, veuillez poser des questions afin que je puisse continuer à améliorer cette réponse.

L'idée de base :

L'émulation fonctionne en manipulant le comportement du processeur et des composants individuels. Vous construisez chaque pièce individuelle du système, puis vous connectez les pièces comme le font les fils dans le matériel.

Émulation de processeur :

Il existe trois façons de gérer l'émulation du processeur :

  • Interprétation
  • Recompilation dynamique
  • Recompilation statique

Avec tous ces chemins, vous avez le même objectif global : exécuter un morceau de code pour modifier l'état du processeur et interagir avec le "matériel". L'état du processeur est un conglomérat de registres du processeur, de gestionnaires d'interruptions, etc. pour une cible processeur donnée. Pour le 6502, vous avez un certain nombre d'entiers de 8 bits représentant les registres : A , X , Y , P et S ; vous auriez également une PC registre.

Avec l'interprétation, vous commencez par le IP (pointeur d'instruction -- également appelé PC , compteur de programme) et lire l'instruction en mémoire. Votre code analyse cette instruction et utilise cette information pour modifier l'état du processeur comme spécifié par ce dernier. Le problème principal de l'interprétation est qu'elle est très lente ; chaque fois que vous manipulez une instruction donnée, vous devez la décoder et effectuer l'opération requise.

Avec la recompilation dynamique, on itère sur le code comme pour l'interprétation, mais au lieu de se contenter d'exécuter des opcodes, on établit une liste d'opérations. Lorsque vous atteignez une instruction de branchement, vous compilez cette liste d'opérations en code machine pour votre plate-forme hôte, puis vous mettez en cache ce code compilé et l'exécutez. Ensuite, lorsque vous atteignez à nouveau un groupe d'instructions donné, il vous suffit d'exécuter le code du cache. (BTW, la plupart des gens ne font pas réellement une liste d'instructions mais les compilent en code machine à la volée - cela rend l'optimisation plus difficile, mais cela sort du cadre de cette réponse, à moins que cela n'intéresse suffisamment de personnes).

Avec la recompilation statique, vous faites la même chose que dans la recompilation dynamique, mais vous suivez les branches. Vous finissez par construire un morceau de code qui représente tout le code du programme, qui peut ensuite être exécuté sans autre interférence. Ce serait un excellent mécanisme s'il n'y avait pas les problèmes suivants :

  • Le code qui n'est pas dans le programme au départ (par exemple, compressé, crypté, généré/modifié au moment de l'exécution, etc.) ne sera pas recompilé et ne fonctionnera donc pas.
  • Il a été prouvé que trouver tout le code dans un binaire donné est équivalent au Problème d'arrêt

Ces éléments se combinent pour rendre la recompilation statique complètement infaisable dans 99 % des cas. Pour plus d'informations, Michael Steil a effectué d'excellentes recherches sur la recompilation statique - les meilleures que j'ai vues.

L'autre aspect de l'émulation de processeur est la manière dont vous interagissez avec le matériel. Cela a vraiment deux aspects :

  • Calendrier du processeur
  • Traitement des interruptions

Le timing du processeur :

Certaines plateformes - en particulier les anciennes consoles comme la NES, la SNES, etc. - exigent que votre émulateur ait un timing strict pour être totalement compatible. Avec la NES, vous avez le PPU (pixel processing unit) qui exige que le CPU mette des pixels dans sa mémoire à des moments précis. Si vous utilisez l'interprétation, vous pouvez facilement compter les cycles et émuler le bon timing ; avec la recompilation dynamique/statique, les choses sont beaucoup plus complexes.

Traitement des interruptions :

Les interruptions sont le principal mécanisme par lequel l'unité centrale communique avec le matériel. En général, vos composants matériels indiquent à l'unité centrale les interruptions qui l'intéressent. C'est assez simple : lorsque votre code déclenche une interruption donnée, vous consultez la table de gestion des interruptions et appelez le callback approprié.

Émulation matérielle :

L'émulation d'un périphérique matériel donné présente deux aspects :

  • Emulation de la fonctionnalité de l'appareil
  • Emulation des interfaces réelles du dispositif

Prenez le cas d'un disque dur. La fonctionnalité est émulée en créant le stockage de secours, les routines de lecture/écriture/formatage, etc. Cette partie est généralement très simple.

L'interface réelle de l'appareil est un peu plus complexe. Il s'agit généralement d'une combinaison de registres mappés en mémoire (c'est-à-dire des parties de la mémoire que le dispositif surveille pour détecter les changements afin d'émettre des signaux) et d'interruptions. Pour un disque dur, vous pouvez avoir une zone de mémoire où vous placez les commandes de lecture, d'écriture, etc. et où vous lisez ensuite ces données.

J'entrerais bien dans les détails, mais il y a des millions de façons de le faire. Si vous avez des questions spécifiques, n'hésitez pas à me les poser et je les ajouterai.

Ressources :

Je pense que j'ai donné une assez bonne introduction ici, mais il y a tonne de zones supplémentaires. Je suis plus qu'heureux d'aider si vous avez des questions ; j'ai été très vague dans la plupart des cas, simplement en raison de l'immense complexité.

Liens Wikipedia obligatoires :

Ressources générales d'émulation :

  • Zophar -- C'est ici que j'ai commencé à m'intéresser à l'émulation, en téléchargeant d'abord des émulateurs, puis en pillant leurs immenses archives de documentation. C'est la meilleure ressource absolue que vous puissiez avoir.
  • NGEmu -- Pas beaucoup de ressources directes, mais leurs forums sont imbattables.
  • RomHacking.net -- La section des documents contient des ressources concernant l'architecture des machines pour les consoles les plus populaires.

Projets d'émulateur à référencer :

  • IronBabel -- Il s'agit d'une plateforme d'émulation pour .NET, écrite en Nemerle et recompilant le code en C# à la volée. Clause de non-responsabilité : C'est mon projet, alors pardonnez la publicité éhontée.
  • BSnes -- Un émulateur SNES génial dont le but est d'obtenir une précision parfaite des cycles.
  • MAME -- Le site émulateur d'arcade. Grande référence.
  • 6502asm.com -- Il s'agit d'un émulateur JavaScript 6502 avec un petit forum sympa.
  • dynarec'd 6502asm -- C'est un petit hack que j'ai fait en un jour ou deux. J'ai pris l'émulateur existant de 6502asm.com et je l'ai modifié pour recompiler dynamiquement le code en JavaScript pour une augmentation massive de la vitesse.

Références de recompilation du processeur :

  • Les recherches sur la recompilation statique menées par Michael Steil (cité plus haut) ont abouti à la publication de cet article et vous pouvez trouver les sources et autres ici .

Addendum :

Cela fait plus d'un an que cette réponse a été soumise et avec toute l'attention qu'elle a reçue, je me suis dit qu'il était temps de mettre certaines choses à jour.

Peut-être que la chose la plus excitante dans l'émulation en ce moment est libcpu lancé par le susnommé Michael Steil. Il s'agit d'une bibliothèque destinée à supporter un grand nombre de cœurs de CPU, qui utilisent LLVM pour la recompilation (statique et dynamique !). Elle a un énorme potentiel et je pense qu'elle fera de grandes choses pour l'émulation.

emu-docs a également été porté à mon attention, qui abrite un grand dépôt de documentation système, très utile à des fins d'émulation. Je n'y ai pas passé beaucoup de temps, mais il semble qu'ils aient beaucoup de bonnes ressources.

Je suis heureux que cet article ait été utile, et j'espère pouvoir me bouger le cul et terminer mon livre sur le sujet d'ici la fin de l'année ou le début de l'année prochaine.

126voto

mdm Points 3025

Un type nommé Victor Moya del Barrio a écrit sa thèse sur ce sujet. Beaucoup de bonnes informations sur 152 pages. Vous pouvez télécharger le PDF ici .

Si vous ne voulez pas vous inscrire auprès de scribd vous pouvez chercher le titre du PDF sur Google, "Etude des techniques de programmation par émulation" . Il y a plusieurs sources différentes pour le PDF.

43voto

Uri Points 50687

L'émulation peut sembler intimidante, mais elle est en fait plus facile que la simulation.

Tout processeur possède généralement une spécification bien écrite qui décrit les états, les interactions, etc.

Si vous ne vous souciez pas du tout des performances, vous pouvez facilement émuler la plupart des anciens processeurs en utilisant des programmes orientés objet très élégants. Par exemple, un processeur X86 aurait besoin de quelque chose pour maintenir l'état des registres (facile), quelque chose pour maintenir l'état de la mémoire (facile), et quelque chose qui prendrait chaque commande entrante et l'appliquerait à l'état actuel de la machine. Si vous voulez vraiment de la précision, vous devez également émuler les traductions de mémoire, la mise en cache, etc. mais c'est faisable.

En fait, de nombreux fabricants de micropuces et de processeurs testent les programmes sur un émulateur de la puce, puis sur la puce elle-même, ce qui leur permet de déterminer si les spécifications de la puce ou son implémentation matérielle posent problème. Par exemple, il est possible d'écrire une spécification de puce qui entraînerait des blocages, et lorsqu'une échéance se produit dans le matériel, il est important de voir si elle peut être reproduite dans la spécification, car cela indique un problème plus important que quelque chose dans l'implémentation de la puce.

Bien sûr, les émulateurs de jeux vidéo se soucient généralement des performances et n'utilisent donc pas d'implémentations naïves. Ils incluent également du code qui s'interface avec le système d'exploitation du système hôte, par exemple pour utiliser le dessin et le son.

Compte tenu des performances très lentes des anciens jeux vidéo (NES/SNES, etc.), l'émulation est assez facile sur les systèmes modernes. En fait, il est encore plus étonnant de pouvoir télécharger un jeu de tous les jeux de la SNES ou de l'Atari 2600, sachant qu'à l'époque où ces systèmes étaient populaires, avoir accès gratuitement à toutes les cartouches aurait été un rêve devenu réalité.

29voto

Je sais que cette question est un peu ancienne, mais je voudrais ajouter quelque chose à la discussion. La plupart des réponses sont centrées sur les émulateurs qui interprètent les instructions machine des systèmes qu'ils émulent.

Toutefois, il existe une exception très connue à cette règle, appelée "UltraHLE" ( Article de WIKIpedia ). UltraHLE, l'un des émulateurs les plus célèbres jamais créés, a émulé des jeux commerciaux de la Nintendo 64 (avec des performances décentes sur les ordinateurs personnels) à une époque où il était largement considéré comme impossible de le faire. En fait, Nintendo produisait encore de nouveaux titres pour la Nintendo 64 lorsque UltraHLE a été créé !

Pour la première fois, j'ai vu des articles sur les émulateurs dans des magazines imprimés alors qu'auparavant, je n'en avais vu que sur le web.

Le concept d'UltraHLE était de rendre possible l'impossible en émulant les appels de la bibliothèque C au lieu des appels au niveau de la machine.

22voto

Julio Points 1737

La tentative d'Imran Nazar d'écrire un livre sur l'histoire de l'Europe vaut le coup d'œil. Gameboy en JavaScript.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X