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.