Comment fonctionnent les émulateurs? Quand je vois des émulateurs NES/SNES ou C64, cela me stupéfie.
Devez-vous imiter le processeur de ces machines en interprétant ses instructions de montage particulières? Quoi d'autre va dedans? Comment sont-ils généralement conçus?
Pouvez-vous donner un conseil à une personne intéressée par l'écriture d'un émulateur (en particulier un système de jeu)?
L'émulation est une zone à multiples facettes. Voici les idées de base et les composants fonctionnels. Je vais le casser en morceaux, puis compléter les détails via des modifications. Bon nombre des choses que je vais décrire vont nécessiter une connaissance du fonctionnement interne des processeurs - la connaissance de l'assemblage est nécessaire. Si je suis un peu trop vague sur certaines choses, posez des questions afin que je puisse continuer à améliorer cette réponse.
L'émulation fonctionne en gérant le comportement du processeur et des composants individuels. Vous construisez chaque composant du système, puis connectez les composants comme le font les câbles dans le matériel.
Il existe trois façons de gérer l’émulation du processeur:
Avec tous ces chemins, vous avez le même objectif général: exécuter un morceau de code pour modifier l'état du processeur et interagir avec le "matériel". L'état du processeur est une agglomération des registres du processeur, des gestionnaires d'interruption, etc. pour une cible de processeur donnée. Pour le 6502, vous auriez un nombre entier de 8 bits représentant des registres: A
, X
, Y
, P
et S
; vous auriez également un registre PC
de 16 bits.
Avec interprétation, vous commencez par IP
(pointeur d'instruction - également appelé PC
, compteur de programme) et lisez l'instruction en mémoire. Votre code analyse cette instruction et utilise cette information pour modifier l'état du processeur tel que spécifié par votre processeur. Le problème principal de l'interprétation est que c'est very slow; chaque fois que vous manipulez une instruction donnée, vous devez la décoder et effectuer l'opération requise.
Avec la recompilation dynamique, vous parcourez le code comme une interprétation, mais au lieu de simplement exécuter des opcodes, vous créez une liste d'opérations. Une fois que vous avez atteint une instruction de branche, 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 frappez à nouveau un groupe d'instructions donné, il vous suffit d'exécuter le code à partir du cache. (Au fait, la plupart des gens ne font pas réellement une liste d'instructions mais les compilent en code machine à la volée - cela rend plus difficile l'optimisation, mais c'est hors du champ de cette réponse, sauf si suffisamment de personnes sont intéressées)
Avec la recompilation statique, vous procédez de la même manière que dans la recompilation dynamique, mais vous suivez les branches. Vous créez un bloc 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:
Celles-ci se combinent pour rendre la recompilation statique complètement irréalisable dans 99% des cas. Pour plus d’informations, Michael Steil a effectué d’excellentes recherches sur la recompilation statique - la meilleure que j’ai vue.
L’autre côté de l’émulation du processeur est la manière dont vous interagissez avec le matériel. Cela a vraiment deux côtés:
Certaines plates-formes - notamment les anciennes consoles telles que la NES, la SNES, etc. - exigent que votre émulateur ait un minutage strict pour être totalement compatible. Avec le NES, vous avez le PPU (unité de traitement de pixels) 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.
Les interruptions constituent le mécanisme principal avec lequel la CPU communique avec le matériel. Généralement, vos composants matériels informeront le processeur des interruptions dont il se soucie. C’est assez simple: lorsque votre code génère une interruption donnée, vous consultez la table des gestionnaires d’interruptions et appelez le rappel approprié.
Il y a deux côtés pour émuler un périphérique matériel donné:
Prenons le cas d'un disque dur. La fonctionnalité est émulée en créant le stockage de sauvegarde, 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 (par exemple, des parties de la mémoire surveillées par le dispositif pour détecter les modifications à effectuer) et des interruptions. Pour un disque dur, vous pouvez avoir une zone mappée en mémoire dans laquelle vous placez des commandes de lecture, des écritures, etc., puis relisez ces données.
J'entrerais dans les détails, mais il y a un million de façons d'y aller. Si vous avez des questions spécifiques ici, n'hésitez pas à demander et je vais ajouter l'info.
Je pense avoir donné une assez bonne introduction, mais il y a une tonne de zones supplémentaires. Je suis plus qu'heureux d'aider avec toutes les questions; J'ai été très vague dans la plupart des cas simplement en raison de l'immense complexité.
Cela fait bien plus d'un an que cette réponse a été soumise et, avec toute l'attention qu'elle mérite, j'ai pensé qu'il était temps de mettre à jour certaines choses.
La chose la plus excitante dans l’émulation en ce moment est probablement libcp , qui a été créée par Michael Steil. C'est une librairie destinée à supporter un grand nombre de cœurs de processeurs, qui utilisent LLVM pour la recompilation (statique et dynamique!). Son potentiel est énorme et je pense que cela produira de grandes choses pour l'émulation.
emu-docs a également été porté à mon attention, qui héberge un excellent référentiel de documentation système, très utile pour l’émulation. Je n'ai pas passé beaucoup de temps là-bas, mais il semble qu'ils disposent d'excellentes ressources.
Je suis heureux que ce message ait été utile et j'espère pouvoir enfin me sortir de mon cul et finir mon livre sur le sujet d'ici la fin de l'année/au début de l'année prochaine.
Un certain Victor Moya del Barrio a rédigé sa thèse sur ce sujet. Beaucoup de bonnes informations sur 152 pages. Vous pouvez télécharger le PDF ici .
Si vous ne souhaitez pas vous inscrire avec scribd , vous pouvez rechercher Google pour le titre PDF, "Etude des techniques de programmation par émulation" . Il existe différentes sources pour le fichier PDF.
L’émulation peut sembler décourageante, mais elle est en réalité assez facile à simuler.
Tout processeur a généralement une spécification bien écrite décrivant les états, les interactions, etc.
Si vous ne vous souciez pas du tout de la performance, vous pouvez facilement émuler la plupart des processeurs plus anciens 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 (easy), de maintenir l'état de mémoire (easy), et de 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 pouvez également émuler les traductions en mémoire, la mise en cache, etc., mais c'est faisable.
En fait, de nombreux fabricants de microprocesseurs et de processeurs testent des programmes sur un émulateur de puce, puis sur la puce elle-même, ce qui les aide à déterminer s’il existe des problèmes dans les spécifications de la puce ou dans l’implémentation réelle de la puce dans le matériel. Par exemple, il est possible d'écrire une spécification de puce qui entraînerait des blocages. Lorsqu'une échéance survient 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 grave que l'implémentation de la puce.
Bien sûr, les émulateurs pour les jeux vidéo se soucient généralement des performances, ils 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 vieux 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 SNES ou Atari 2600, étant donné que lorsque ces systèmes étaient populaires, le libre accès à chaque cartouche aurait été un rêve devenu réalité.
Je sais que cette question est un peu ancienne, mais je voudrais ajouter quelque chose à la discussion. La plupart des réponses ici concernent des émulateurs interprétant les instructions machine des systèmes qu’ils émulent.
Cependant, il existe une exception très connue appelée "UltraHLE" ( article de WIKIpedia ). UltraHLE, l'un des émulateurs les plus célèbres jamais créés, a imité les jeux commerciaux Nintendo 64 (offrant des performances décentes sur les ordinateurs à la maison) à un moment où il était largement considéré impossible de le faire. En fait, Nintendo produisait encore de nouveaux titres pour la Nintendo 64 lors de la création d’UltraHLE!
Pour la première fois, j'ai vu des articles sur les émulateurs dans des magazines imprimés où je ne les avais jamais vus que discutés sur le Web.
Le concept d'UltraHLE consistait à rendre l'impossible possible en émulant des appels de bibliothèque C au lieu d'appels au niveau de la machine.
La tentative d'Imran Nazar d'écrire un émulateur Gameboy en JavaScript mérite d'être examinée.
Après avoir créé mon propre émulateur du micro-ordinateur BBC des années 80 (type VBeeb dans Google), il y a un certain nombre de choses à savoir.
En pratique, vous cherchez généralement à écrire pour la rapidité et la fidélité de l'émulation. En effet, le logiciel sur le système cible s'exécutera (peut-être) plus lentement que le matériel d'origine sur le système source. Cela peut limiter le choix du langage de programmation, des compilateurs, du système cible, etc.
De plus, vous devez limiter ce que vous êtes prêt à émuler. Par exemple, il n'est pas nécessaire d'émuler l'état de tension des transistors dans un microprocesseur, mais il est probablement nécessaire d'imiter l'état de l'ensemble de registres du microprocesseur.
En règle générale, plus le niveau de détail de l'émulation est petit, plus vous resterez fidèle au système d'origine.
Enfin, les informations pour les systèmes plus anciens peuvent être incomplètes ou inexistantes. Il est donc essentiel de mettre la main sur l’équipement d’origine, ou au moins de distinguer un autre bon émulateur écrit par quelqu'un d'autre!
Oui, vous devez interpréter tout le désordre binaire du code machine "à la main". De plus, la plupart du temps, vous devez également simuler un matériel exotique qui n'a pas d'équivalent sur la machine cible.
L’approche simple consiste à interpréter les instructions une par une. Cela fonctionne bien, mais c'est lent. La recompilation est une approche plus rapide: convertir le code machine source en code machine cible. C’est plus compliqué, car la plupart des instructions ne mapperont pas un à un. Au lieu de cela, vous devrez créer des solutions de rechange qui impliquent du code supplémentaire. Mais au final c'est beaucoup plus rapide. La plupart des émulateurs modernes le font.
Lorsque vous développez un émulateur, vous interprétez l’ensemble processeur sur lequel le système travaille (Z80, 8080, CPU PS, etc.).
Vous devez également émuler tous les périphériques du système (sortie vidéo, contrôleur).
Vous devriez commencer à écrire des émulateurs pour les systèmes simpe comme le bon vieux Game Boy (qui utilise un processeur Z80, je ne me trompe pas) OR pour C64.
Les émulateurs sont très difficiles à créer car il faut simuler de nombreux hacks (comme dans les effets inhabituels), des problèmes de timing, etc.
Pour un exemple, voir http://queue.acm.org/detail.cfm?id=1755886 .
Cela vous montrera également pourquoi vous "avez besoin" d’un processeur multi-GHz pour émuler un processeur à 1 MHz.
Consultez également Emulators.com de Darek Mihocka pour des conseils avisés sur l’optimisation des instructions pour les JIT, ainsi que de nombreux autres avantages pour la création d’émulateurs efficaces.
Je n'ai jamais fait quelque chose d'assez sophistiqué pour imiter une console de jeu, mais j'ai suivi un cours une fois dans le but d'écrire un émulateur pour la machine décrite dans Andrew Tanenbaums Structured Computer Organization . C'était amusant et cela m'a donné beaucoup de moments aha. Vous voudrez peut-être prendre ce livre avant de plonger dans l'écriture d'un véritable émulateur.
Des conseils pour imiter un vrai système ou votre propre chose? Je peux dire que les émulateurs fonctionnent en émulant tout le matériel. Peut-être pas dans le circuit (déplacer des bits comme le ferait le HW; déplacer l’octet est le résultat final, donc copier l’octet convient). Les émulateurs sont très difficiles à créer car il faut simuler de nombreux hacks (comme dans les effets inhabituels), des problèmes de timing, etc. Si un élément (entrée) est erroné, tout le système peut le faire ou, au mieux, avoir un bug/problème.
Émulateur de périphérique source partagé contient un code source pouvant être construit pour un émulateur PocketPC/Smartphone (nécessite Visual Studio, fonctionne sous Windows). J'ai travaillé sur les versions V1 et V2 de la version binaire.
Il aborde de nombreux problèmes d’émulation: - traduction efficace des adresses d’un invité virtuel à l’hôte virtuel - compilation JIT du code invité - simulation de périphériques tels que des adaptateurs réseau, des écrans tactiles et audio - intégration de l’UI, pour clavier et souris de l’hôte - sauvegarde/restauration d'état, pour la simulation de la reprise du mode basse consommation
Pour ajouter la réponse fournie par @Cody Brocious
Dans le contexte de la virtualisation dans lequel vous émulez un nouveau système (CPU, E/S, etc.) sur une machine virtuelle, vous pouvez voir les catégories suivantes d’émulateurs.
Interprétation: bochs est un exemple d’interprète, c’est un émulateur de PC x86; chaque instruction du système invité doit être traduite dans un autre jeu d’instructions (de l’ISA hôte) pour produire l’effet souhaité. ne cache rien pour que chaque instruction répète le même cycle.
Emalator dynamique: Qemu est un émulateur dynamique. La traduction à la volée de l'instruction d'invité met également en cache les résultats. La meilleure partie est d'exécuter autant d'instructions que possible directement sur le système hôte afin que l'émulation soit plus rapide. De plus, comme l'a mentionné Cody, il divise le code en blocs (1 seul flux d'exécution).
Émulateur statique: Pour autant que je sache, aucun émulateur statique ne peut être utile pour la virtualisation.
J'ai écrit un article sur l'émulation de la système Chip-8 en JavaScript .
C'est un bon point de départ car le système n'est pas très compliqué, mais vous apprenez toujours comment fonctionnent les opcodes, la pile, les registres, etc.
J'écrirai bientôt un guide plus long pour la NDA.
Comment je commencerais l'émulation.
1.Obtenez des livres basés sur la programmation de bas niveau, vous en aurez besoin pour le système d'exploitation "feint" de la console de jeu Nintendo ...
2.Obtenez des livres sur l'émulation spécifiquement, et peut-être le développement OS. (Vous ne ferez pas un os, mais le plus proche.
3. Regardez certains émulateurs open source, en particulier ceux du système pour lequel vous souhaitez créer un émulateur.
4.copiez des extraits du code plus complexe dans votre IDE/compliler. Cela vous évitera d'écrire un long code. C’est ce que je fais pour le développement os, utilise un district de linux