¿Cómo funcionan los emuladores y cómo se escriben? [cerrado]
¿Cómo funcionan los emuladores? Cuando veo emuladores de NES/SNES o C64, me asombra.
¿Hay que emular el procesador de esas máquinas interpretando sus particulares instrucciones de montaje? ¿Qué más implica? ¿Cómo se diseñan normalmente?
¿Puedes dar algún consejo a alguien interesado en escribir un emulador (particularmente un sistema de juego)?
La emulación es un área multifacética. Aquí están las ideas básicas y los componentes funcionales. Lo dividiré en pedazos y luego completaré los detalles mediante ediciones. Muchas de las cosas que voy a describir requerirán conocimiento del funcionamiento interno de los procesadores; es necesario tener conocimientos de ensamblaje. Si soy un poco vago en ciertas cosas, haga preguntas para poder continuar mejorando esta respuesta.
Idea básica:
La emulación funciona manejando el comportamiento del procesador y los componentes individuales. Usted construye cada pieza individual del sistema y luego conecta las piezas de manera muy similar a como lo hacen los cables en el hardware.
Emulación de procesador:
Hay tres formas de manejar la emulación del procesador:
- Interpretación
- Recompilación dinámica
- Recompilación estática
Con todas estas rutas, tiene el mismo objetivo general: ejecutar un fragmento de código para modificar el estado del procesador e interactuar con el "hardware". El estado del procesador es un conglomerado de registros del procesador, controladores de interrupciones, etc. para un objetivo de procesador determinado. Para el 6502, tendría una cantidad de números enteros de 8 bits que representan registros: A
, X
, Y
, P
y S
; también tendrías un PC
registro de 16 bits.
Con la interpretación, usted comienza en el IP
(puntero de instrucción, también llamado PC
contador de programa) y lee la instrucción de la memoria. Su código analiza esta instrucción y utiliza esta información para alterar el estado del procesador según lo especificado por su procesador. El problema central de la interpretación es que es muy lenta; cada vez que maneja una instrucción determinada, debe decodificarla y realizar la operación requerida.
Con la recompilación dinámica, se itera sobre el código de forma muy parecida a la interpretación, pero en lugar de simplemente ejecutar códigos de operación, se crea una lista de operaciones. Una vez que llega a una instrucción de bifurcación, compila esta lista de operaciones en código de máquina para su plataforma host, luego almacena en caché este código compilado y lo ejecuta. Luego, cuando vuelva a acceder a un grupo de instrucciones determinado, solo tendrá que ejecutar el código desde el caché. (Por cierto, la mayoría de las personas en realidad no hacen una lista de instrucciones, sino que las compilan en código de máquina sobre la marcha; esto hace que sea más difícil de optimizar, pero eso está fuera del alcance de esta respuesta, a menos que haya suficientes personas interesadas)
Con la recompilación estática, haces lo mismo que en la recompilación dinámica, pero sigues las ramas. Terminas construyendo un fragmento de código que representa todo el código del programa, que luego se puede ejecutar sin más interferencias. Este sería un gran mecanismo si no fuera por los siguientes problemas:
- El código que no está en el programa para empezar (por ejemplo, comprimido, cifrado, generado/modificado en tiempo de ejecución, etc.) no se volverá a compilar, por lo que no se ejecutará.
- Se ha demostrado que encontrar todo el código en un binario determinado equivale al problema de detención.
Estos se combinan para hacer que la recompilación estática sea completamente inviable en el 99% de los casos. Para obtener más información, Michael Steil ha realizado una gran investigación sobre la recompilación estática, la mejor que he visto.
La otra cara de la emulación de procesador es la forma en que interactúa con el hardware. Esto realmente tiene dos lados:
- Sincronización del procesador
- Manejo de interrupciones
Sincronización del procesador:
Ciertas plataformas, especialmente consolas más antiguas como NES, SNES, etc., requieren que su emulador tenga una sincronización estricta para ser completamente compatible. Con la NES, tienes la PPU (unidad de procesamiento de píxeles) que requiere que la CPU coloque píxeles en su memoria en momentos precisos. Si utiliza la interpretación, puede contar ciclos fácilmente y emular el tiempo adecuado; con la recompilación dinámica/estática, las cosas son mucho más complejas.
Manejo de interrupciones:
Las interrupciones son el mecanismo principal por el que la CPU se comunica con el hardware. Generalmente, sus componentes de hardware le dirán a la CPU qué interrupciones le interesan. Esto es bastante sencillo: cuando su código genera una interrupción determinada, mira la tabla del controlador de interrupciones y llama a la devolución de llamada adecuada.
Emulación de hardware:
Hay dos aspectos de la emulación de un dispositivo de hardware determinado:
- Emulando la funcionalidad del dispositivo.
- Emulando las interfaces reales del dispositivo
Tomemos el caso de un disco duro. La funcionalidad se emula mediante la creación del almacenamiento de respaldo, rutinas de lectura/escritura/formato, etc. Esta parte es generalmente muy sencilla.
La interfaz real del dispositivo es un poco más compleja. Generalmente se trata de una combinación de registros mapeados en memoria (por ejemplo, partes de la memoria que el dispositivo observa en busca de cambios para realizar señales) e interrupciones. Para un disco duro, es posible que tenga un área asignada de memoria donde coloque comandos de lectura, escritura, etc., y luego vuelva a leer estos datos.
Entraría en más detalles, pero hay un millón de maneras de hacerlo. Si tiene alguna pregunta específica aquí, no dude en preguntar y agregaré la información.
Recursos:
Creo que he dado una introducción bastante buena aquí, pero hay un montón de áreas adicionales. Estaré más que feliz de ayudar con cualquier pregunta; He sido muy vago en la mayor parte de esto simplemente debido a la inmensa complejidad.
Enlaces obligatorios de Wikipedia:
- Emulador
- Recompilación dinámica
Recursos generales de emulación:
- Zophar : Aquí es donde comencé con la emulación, primero descargando emuladores y eventualmente saqueando sus inmensos archivos de documentación. Este es absolutamente el mejor recurso que puedas tener.
- NGEmu : no hay muchos recursos directos, pero sus foros son inmejorables.
- RomHacking.net : la sección de documentos contiene recursos sobre la arquitectura de la máquina para consolas populares.
Proyectos de emulador a los que hacer referencia:
- IronBabel : es una plataforma de emulación para .NET, escrita en Nemerle y que recompila el código en C# sobre la marcha. Descargo de responsabilidad: este es mi proyecto, así que perdonen el descaro.
- BSnes : un increíble emulador de SNES con el objetivo de lograr una precisión de ciclo perfecta.
- MAME : el emulador de arcade. Gran referencia.
- 6502asm.com : este es un emulador de JavaScript 6502 con un pequeño foro interesante.
- dynarec'd 6502asm : este es un pequeño truco que hice durante uno o dos días. Tomé el emulador existente de 6502asm.com y lo cambié para recompilar dinámicamente el código en JavaScript para aumentar enormemente la velocidad.
Referencias de recompilación del procesador:
- La investigación sobre la recopilación estática realizada por Michael Steil (mencionada anteriormente) culminó en este artículo y puede encontrar la fuente y demás aquí .
Apéndice:
Ha pasado más de un año desde que se envió esta respuesta y con toda la atención que ha recibido, pensé que era hora de actualizar algunas cosas.
Quizás lo más interesante en emulación en este momento es libcpu , iniciado por el ya mencionado Michael Steil. Es una biblioteca destinada a admitir una gran cantidad de núcleos de CPU, que utilizan LLVM para la recompilación (¡estática y dinámica!). Tiene un potencial enorme y creo que hará grandes cosas para la emulación.
También me han llamado la atención sobre emu-docs , que alberga un gran depósito de documentación del sistema, que es muy útil para fines de emulación. No he pasado mucho tiempo allí, pero parece que tienen muchos recursos excelentes.
Me alegra que esta publicación haya sido útil y espero poder moverme y terminar mi libro sobre el tema para fin de año o principios del próximo.
Un tal Víctor Moya del Barrio escribió su tesis sobre este tema. Mucha buena información en 152 páginas. Puedes descargar el PDF aquí .
Si no desea registrarse en scribd , puede buscar en Google el título del PDF, "Estudio de las técnicas de programación en emulación" . Hay un par de fuentes diferentes para el PDF.
La emulación puede parecer desalentadora, pero en realidad es bastante más fácil que la simulación.
Cualquier procesador suele tener una especificación bien escrita que describe estados, interacciones, etc.
Si no le importaba en absoluto el rendimiento, podría emular fácilmente la mayoría de los procesadores más antiguos utilizando programas orientados a objetos muy elegantes. Por ejemplo, un procesador X86 necesitaría algo para mantener el estado de los registros (fácil), algo para mantener el estado de la memoria (fácil) y algo que tomara cada comando entrante y lo aplicara al estado actual de la máquina. Si realmente quisiera precisión, también emularía traducciones de memoria, almacenamiento en caché, etc., pero eso es factible.
De hecho, muchos fabricantes de microchips y CPU prueban programas con un emulador del chip y luego con el chip mismo, lo que les ayuda a descubrir si hay problemas en las especificaciones del chip o en la implementación real del chip en el hardware. Por ejemplo, es posible escribir una especificación de chip que provocaría interbloqueos, y cuando ocurre una fecha límite en el hardware, es importante ver si se puede reproducir en la especificación, ya que eso indica un problema mayor que algo en la implementación del chip.
Por supuesto, los emuladores de videojuegos generalmente se preocupan por el rendimiento, por lo que no utilizan implementaciones ingenuas, y también incluyen código que interactúa con el sistema operativo del sistema anfitrión, por ejemplo, para usar dibujos y sonidos.
Teniendo en cuenta el rendimiento muy lento de los videojuegos antiguos (NES/SNES, etc.), la emulación es bastante sencilla en los sistemas modernos. De hecho, es aún más sorprendente que puedas descargar un conjunto de todos los juegos de SNES o de cualquier juego de Atari 2600, considerando que cuando estos sistemas eran populares, tener acceso gratuito a todos los cartuchos habría sido un sueño hecho realidad.
Sé que esta pregunta es un poco antigua, pero me gustaría agregar algo a la discusión. La mayoría de las respuestas aquí se centran en que los emuladores interpretan las instrucciones de la máquina de los sistemas que emulan.
Sin embargo, existe una excepción muy conocida llamada "UltraHLE" ( artículo de WIKIpedia ). UltraHLE, uno de los emuladores más famosos jamás creados, emulaba juegos comerciales de Nintendo 64 (con un rendimiento decente en computadoras domésticas) en un momento en el que se consideraba imposible hacerlo. De hecho, Nintendo todavía estaba produciendo nuevos títulos para Nintendo 64 cuando se creó UltraHLE.
Por primera vez, vi artículos sobre emuladores en revistas impresas, donde antes solo los había visto discutidos en la web.
El concepto de UltraHLE era hacer posible lo imposible emulando llamadas a la biblioteca C en lugar de llamadas a nivel de máquina.
Algo que vale la pena echarle un vistazo es el intento de Imran Nazar de escribir un emulador de Gameboy en JavaScript.