Emuladores SNES a pocos píxeles de la perfección absoluta


Estamos tan cerca de crear un emulador que pueda recrear perfectamente todas las funciones del hardware real y el software SNES.

En los últimos 15 años, como codificador del emulador bsnes, intenté perfeccionar la emulación de Super Nintendo, pero ahora nos enfrentamos al último problema: la sincronización precisa de los ciclos de reloj de los procesadores de video SNES. Para alcanzar esta etapa final de precisión de la emulación, se requiere la ayuda de toda la comunidad, y espero su apoyo. Pero primero, te diré lo que ya hemos logrado.

Estado actual


Hoy, la situación con la emulación SNES es muy buena. Además de los periféricos inusuales que resisten la emulación (por ejemplo, un club de golf con un sensor de luz , un simulador de bicicleta y un módem de acceso telefónico, utilizado en Japón para las apuestas de carreras de caballos en Japón), todos los juegos SNES con licencia oficial son totalmente jugables, y ningún juego tiene problemas obvios.

La emulación SNES se volvió tan precisa que incluso tuve que dividir el emulador en dos versiones: higan , que se esfuerza por lograr una precisión absoluta y coherencia con la documentación del hardware, y bsnes , que se esfuerza por la velocidad, amplias capacidades y facilidad de uso.

Recientemente, en el campo de la emulación SNES, se han recibido muchos logros interesantes, que incluyen:


… ¡y mucho más!

Entonces, ¿está hecho? ¿Todos trabajaron bien, adiós, y gracias por el pescado? Bueno, no del todo.

Hoy en día, hemos logrado precisión en el nivel de ritmo de casi todos los componentes SNES. Las únicas excepciones fueron los PPU (unidad de procesamiento de imágenes, módulos de procesamiento de imágenes) utilizados para generar cuadros de video transmitidos a la pantalla. Tenemos sobre todo saber cómo funcionan PPU, pero para algunas funciones que tenemos que utilizar conjeturas, lo que conduce a una precisión imperfecta.

En una escala general, los problemas restantes son bastante menores. Si no se esfuerza por lograr la idealidad absolutamente perfecta de la emulación por amor al arte, entonces no puedo convencerlo de la necesidad de mejorar aún más la emulación de PPU. Como en cualquier campo, cuanto más cerca estemos del ideal, menor será el rendimiento.

Pero puedo decir por qué esto es importante para : este es el trabajo de toda mi vida, y no quiero que diga que estuve tan cerca de terminar sin dar el último paso. Estoy envejeciendo y no soy eterno. Quiero que se resuelva la última pieza del rompecabezas, de modo que, una vez retirado, esté seguro de que el legado de SNES es confiable y está completamente preservado gracias a la emulación. Quiero decir que el problema está resuelto .

Si todavía está intrigado, continúe leyendo para familiarizarse con los antecedentes de un problema y las soluciones que le ofrezco.

Modelado de arquitectura SNES


Comencemos enumerando los componentes que componen SNES:


Diagrama del sistema Super NES.

Las flechas indican las direcciones en las que varios procesadores SNES pueden intercambiar datos entre sí, y las líneas punteadas indican las conexiones a los chips de memoria.

Lo más importante para nosotros ahora es notar que la salida de video y sonido se transmite directamente desde PPU y DSP. Esto significa que actúan como "cajas negras", y no podemos ver lo que sucede dentro de ellos. Más tarde será importante para nosotros.

Exactitud


Imagine que emulamos el comando de CPU "multiplicar", que toma dos registros (variables), los multiplica, recibe el resultado y varios indicadores que indican el estado del resultado (por ejemplo, desbordamiento ).

Podemos escribir un programa que multiplique cualquier valor posible de 0 a 255 como factor y multiplicador. Entonces podemos derivar los resultados de multiplicación numérica y de bandera. Por lo tanto, obtenemos dos tablas de 65 536 elementos.

Al analizar estas tablas, podemos determinar con precisión cómo y dónde se establecen los resultados de los cálculos de la CPU de una manera determinada. Luego podemos modificar los emuladores para que al ejecutar la misma prueba obtengamos exactamente las mismas tablas al mismo tiempo.

Ahora digamos que la CPU puede hacer una multiplicación de 16 bits x 16 bits. Al probar todos los valores posibles, se generarán 4 mil millones de resultados, que son casi imposibles de probar en un período de tiempo razonable. Si la CPU tiene multiplicaciones de 32 bits x 32 bits, en la práctica no será posible probar todas las combinaciones de valores de entrada antes de la muerte térmica del Universo (al menos en el nivel actual de tecnología).

En tales casos, actuamos en las pruebas de forma más selectiva e intentamos determinar cuándo las banderas pueden cambiar exactamente, cuándo pueden desbordarse los resultados, etc. De lo contrario, tendríamos que ejecutar pruebas que nunca terminarían.

La multiplicación es una operación bastante trivial, pero el mismo principio puede extenderse a todo el proceso de ingeniería inversa, incluidas las operaciones más complejas, por ejemplo, la transmisión de datos a través de DMA (acceso directo a la memoria) durante el retorno horizontal del haz. Creamos pruebas que intentan determinar qué sucede en casos límite y luego verificamos si nuestra emulación se comporta de manera idéntica al comportamiento de SNES real.

Generadores de señal y latidos


SNES tiene dos generadores de señal (osciladores): un oscilador de cristal que funciona a una frecuencia de aproximadamente 21 MHz (controla los módulos CPU y PPU), y un resonador cerámico que opera a una frecuencia de aproximadamente 24 MHz, que controla SMP y DSP. En los coprocesadores de cartucho, a veces se usa un oscilador de cristal de 21 MHz y, a veces, sus propios generadores de señal que funcionan con otras frecuencias.


Recrear esta placa de circuito Super Famicom en código es más difícil de lo que parece.

El reloj es el elemento básico de la sincronización de cualquier sistema, y ​​SNES está diseñado para realizar diversas tareas con ciertas frecuencias e intervalos de tiempo.

Si imagina un reloj de 100 hertzios, será un dispositivo con una salida binaria que cambia a un estado lógico alto de la señal (por ejemplo, +5 V), y luego a un estado bajo de la señal (0 V o tierra) 100 veces por segundo. Es decir, cada segundo el voltaje en la salida fluctuará 200 veces: aumentará 100 veces y bajará 100 veces la parte frontal de la señal del reloj.

Un ciclo de reloj generalmente se considera una transición completa, es decir, un ciclo de 100 Hz generará 100 ciclos de reloj por segundo. Algunos sistemas requieren una distinción entre los bordes ascendente y descendente, y para ellos dividimos el ciclo en semiciclos para indicar cada fase (alta o baja) de la señal del reloj.

La tarea más importante de un emulador preciso es completar las tareas exactamente de la misma manera y exactamente al mismo tiempo que en un equipo real. Sin embargo, no es muy importante cómo se realizan las tareas. Lo único importante es que el emulador, que recibe las mismas señales de entrada, genera las mismas señales de salida al mismo tiempo que en el hardware real.

Tiempos


A veces las operaciones llevan tiempo. Tomemos, por ejemplo, la multiplicación en la CPU SNES. En lugar de pausar y esperar a que se complete la multiplicación, la CPU SNES calcula el resultado de la multiplicación un bit a la vez en segundo plano durante ocho ciclos de reloj de los códigos de operación de la CPU. Potencialmente, esto permite que el código realice otras tareas mientras espera que se complete la multiplicación.

Lo más probable es que cualquier software comercial espere estos ocho ciclos, porque si intenta leer el resultado antes de que esté listo, obtendremos un resultado parcialmente completado. Sin embargo, antes de que los emuladores de SNES proporcionaran resultados correctos al instante , sin esperar estos ciclos de reloj adicionales.

Cuando los fanáticos de las consolas comenzaron a crear y probar software auto-escrito en emuladores, esta discrepancia comenzó a causar ciertos problemas. Parte del software, por ejemplo, muchos de los primeros hacks de ROM de Super Mario World , funcionaban correctamente solo en estos viejos emuladores, pero no en el hardware SNES real. Esto sucedió porque se desarrollaron teniendo en cuenta la obtención instantánea (no confiable desde el punto de vista del equipo real) de los resultados de la multiplicación.

En el proceso de mejorar los emuladores, se rompió la compatibilidad del software anterior y, por lo tanto, tuvimos que agregar opciones de compatibilidad a los nuevos emuladores para no perder estos programas. Sí, no importa cuán surrealista suene, ¡pero hoy los emuladores deben emular a otros emuladores!

La conveniencia de este retraso de la multiplicación en la CPU radica en el hecho de que es muy predecible: ocho ciclos de cálculos de reloj comienzan inmediatamente después de la solicitud de la operación de multiplicación. Al escribir un código que lee los resultados después de cada ciclo, pudimos verificar que la CPU SNES utiliza el algoritmo Booth para la multiplicación .

Sincronización de reloj


Otras operaciones no son fáciles de modelar porque se ejecutan de forma asíncrona en segundo plano. Uno de estos casos es la actualización DRAM del procesador central SNES.

Durante la representación de cada línea ráster, toda la CPU SNES en una determinada etapa suspende su funcionamiento durante un corto período de tiempo mientras se actualiza el contenido del chip RAM. Esto es necesario porque para reducir el costo en SNES, se usó RAM dinámica (en lugar de estática) como memoria principal de la CPU. Para guardar el contenido de la RAM dinámica, debe actualizarse periódicamente.


Crear un emulador verdaderamente perfecto no es suficiente para garantizar la jugabilidad de los tres mil quinientos juegos de SNES. También es necesario lograr la simulación de cada función del sistema con una precisión táctil perfecta.

El factor clave en el análisis de los tiempos exactos de estas operaciones fue la posibilidad de utilizar contadores de PPU horizontales y verticales. Estos contadores realizan incrementos y se reinician después de cada recorrido de haz horizontal y vertical inverso. Sin embargo, su precisión es solo una cuarta parte de la frecuencia del generador de señal de CPU SNES; en otras palabras, el contador horizontal aumenta cada cuatro ciclos de reloj.

Leyendo varias veces los valores de los contadores, pude determinar con qué cuarto del ciclo del reloj está alineado el contador. Combinando este conocimiento con funciones especialmente creadas que pueden dar un paso hacia el número exacto de ciclos de reloj indicado por el usuario, pude hacer coincidir perfectamente la CPU SNES con cualquier posición exacta del ciclo de reloj que necesito.

Gracias a un recorrido iterativo de muchos ciclos de reloj, pude determinar cuándo ciertas operaciones están sucediendo exactamente (por ejemplo, actualizar DRAM, transmitir HDMA, interrumpir el sondeo, etc.). Después de eso, podría recrear exactamente todo esto en emulación.

Chip SMPLa consola SNES también tiene sus propios temporizadores, y también se realizó una ingeniería inversa exitosa para este procesador. Puedo dedicar un artículo completo solo al registro SMP TEST, que permite a los programadores controlar el divisor de frecuencia SMP y su temporizador, sin mencionar otras cosas terribles. Bastará decir que no fue un proceso fácil y rápido, pero al final ganamos.

Recopilamos coprocesadores



El chip SuperFX es solo uno de los muchos coprocesadores de cartucho que el emulador SNES puede manejar.

Hay un montón de coprocesadores SNES utilizados dentro de varios cartuchos de juegos que también necesitamos domesticar. Desde CPU individuales de uso general como SuperFX y SA-1 , procesadores de señal digital como DSP-1 y Cx4 hasta aceleradores de descompresión como S-DD1 y SPC7110, o relojes en tiempo real Sharp y Epson, y mucho más ...

Esto significa que el emulador SNES debe hacer frente a la instrucción SuperFX y los cachés de píxeles; con el esquema de resolución de conflictos del bus de memoria SA-1 (que permite que las CPU SNES y SA-1 utilicen los mismos chips ROM y RAM simultáneamente); con firmware integrado DSP-1 y Cx4; con codificadores aritméticos basados ​​en predicción S-DD1 y SPC7110; así como con casos límite impares de BCD (decimal codificado en binario) en generadores en tiempo real. Lento pero seguro, utilizando todas las técnicas para determinar la corrección y los tiempos descritos anteriormente, logramos aprender a emular todos estos chips casi a la perfección.

Se necesitó mucho esfuerzo y miles de dólares para quitar las cubiertas de los chips y el firmware de los procesadores de señal digital utilizados en diferentes juegos. En un caso, la emulación NEC uPD772x permitida¡usa el código de higan para salvar la voz del difunto Stephen Hawking! .

En otro caso, necesitábamos aplicar ingeniería inversa a un conjunto completo de instrucciones para la arquitectura Hitachi HG51B, porque nadie había publicado la documentación para esta arquitectura. En otro caso, resultó que un juego ( Hayazashi Nidan Morita Shougi 2 ) tiene una poderosa CPU ARM6 de 32 bits con una frecuencia de 21 MHz, que acelera el juego shogi japonés.

Salvar todos los coprocesadores SNES resultó ser un proceso a largo plazo, lleno de dificultades y sorpresas.

Procesamiento de señales digitales


El chip Sony S-DSP (Procesador de señal digital), que no debe confundirse con el coprocesador de cartucho DSP-1, generó un sonido SNES único. En este chip, se conectaron ocho canales de audio con codificación ADPCM de 4 bits, lo que aseguró la creación de una señal estéreo de 16 bits.

Exteriormente, y del diagrama del sistema presentado anteriormente, al principio parece que el DSP es una "caja negra": ajustamos los canales de sonido y los parámetros del mezclador, después de lo cual el chip genera el sonido transmitido a los altavoces.

Pero una función importante permitió al desarrollador bajo el apodo blargg realizar una ingeniería inversa completa de este chip: era un búfer de eco. El SNES DSP tiene una función que mezcla la salida de muestras anteriores para crear un efecto de eco. Esto sucede al final del proceso de generación de sonido (aparte del último indicador de bloqueo de sonido, que se puede usar para apagar toda la salida de sonido).

Al escribir el código con la sincronización correcta de las medidas y rastrear el eco resultante, pudimos determinar el orden exacto de las operaciones realizadas por el DSP para generar de cada muestra y creando un sonido perfecto y precisión de ritmo.

Guardar PPU


Todo esto nos llevó a la última parte del esquema arquitectónico SNES: chips PPU-1 y PPU-2. Gracias a John McMaster, tenemos escaneos de los chips S-PPU1 (revisión 1) y S-PPU2 (revisión 3) con un aumento de veinte veces.


Veinte veces el escaneo del cristal del primer PPU SNES ...


... y el segundo PPU.

Ambos escaneos de cristal nos permiten saber que los chips obviamente no son CPU de uso general, ni son arquitecturas especializadas que ejecutan códigos de operación desde la ROM interna del programa de firmware. Estos son circuitos lógicos separados con lógica codificada que reciben señales entrantes de diferentes registros y memoria, y crean una señal de video para el monitor, una línea de trama a la vez.

Las PPU siguen siendo el último obstáculo para emular SNES porque, a diferencia de todos los componentes descritos anteriormente, las PPU son en realidad una caja negra. Podemos configurarlos en cualquier estado, pero la CPU SNES no puede monitorear directamente lo que generan.

Si utilizamos nuestro ejemplo anterior con la multiplicación como analogía, imagine que solicitó el resultado 3 * 7, pero en lugar de la respuesta binaria, obtendrá una imagen analógica difusa de los números "21" en la pantalla. Cualquiera que ejecute su software podrá ver 21, pero no puede escribir un programa de prueba para verificar automáticamente si ve la respuesta correcta. La verificación manual de una persona de dichos resultados no se puede escalar a más de varios miles de pruebas, y se necesitarán millones para maximizar el comportamiento de la PPU.

Sé lo que pensaba: "¿Pero es más fácil usar una tarjeta de captura, realizar el procesamiento de imágenes, compararlas aproximadamente con la imagen en la pantalla digital del emulador y realizar pruebas basadas en esto?"

Pues sí, es posible! Especialmente si la prueba es verificar dos grandes números que ocupan toda la pantalla.

Pero, ¿qué pasa si las pruebas tienen muchos matices y estamos tratando de reconocer la diferencia de color de un medio tono de un píxel? ¿Qué sucede si queremos ejecutar un millón de pruebas en orden, y no siempre sabemos lo que vamos a generar, pero aun así queremos comparar el resultado con el resultado de nuestra emulación?

Nada supera la conveniencia y precisión con los datos digitales: un flujo preciso de bits que solo pueden coincidir o no coincidir. La naturaleza analógica de una señal CRT no puede proporcionarnos esto.

¿Por qué es importante?


Con la excepción de un juego ( Air Strike Patrol ), todo el software SNES con licencia oficial (debería haberse basado) en cadenas ráster. Estos juegos no intentan cambiar el estado de representación de PPU en el medio de la línea ráster renderizada actual (un truco de los programadores se llama "efecto ráster"). Esto significa que los tiempos de ejecución de la gran mayoría de los juegos no tienen que ser particularmente precisos; Si tiene tiempo para la siguiente línea ráster completa, entonces todo está en orden.

Pero esto es importante para un solo juego.




Esta serie de imágenes muestra un complejo efecto de emulación utilizado en el mensaje "Buena suerte" de Air Strike Patrol .

En las imágenes de arriba, puede ver el texto cuadro por cuadro "Buena suerte" de Air Strike Patrol . El juego lo implementa cambiando la posición de desplazamiento vertical de la capa de fondo 3 (BG3). Sin embargo, la pantalla del tablero a la izquierda (donde puedes ver que el jugador tiene 39 misiles) también está en la misma capa de fondo.

El juego logra realizar esta separación cambiando la posición del desplazamiento BG3 en cada línea de trama después de mostrar el tablero izquierdo, pero antes de que el texto "Buena suerte" comience a reproducirse. Esto se puede hacer porque fuera del tablero y el texto, BG3 es transparente y no hay nada que dibujar entre estos dos puntos, independientemente del valor del registro de desplazamiento vertical. Este comportamiento nos muestra que los registros de desplazamiento se pueden cambiar en cualquier etapa de la representación.


Esta pequeña sombra debajo del avión causó muchos dolores de cabeza al desarrollador de emuladores obsesionado con la precisión.

La imagen de arriba muestra la infame sombra de un avión. Este efecto se representa cambiando el registro de brillo de la pantalla con ondas cortas sobre cinco líneas de trama.

Durante el juego, puedes ver que esta sombra es bastante caótica. En la imagen de arriba, se parece un poco a la letra "c", pero su forma en cada línea de trama cambia en longitud y punto de inicio con cada cuadro. Los desarrolladores de Air Strike Patrol simplemente describieron aproximadamente dónde debería aparecer la sombra y resolvieron este problema directamente. En la mayoría de los casos esto funciona.

La correcta emulación de tal comportamiento requiere un tiempo perfecto, lo cual es absolutamente extremadamente difícil de obtener en el emulador .


En la pantalla de pausa de Air Strike Patrol , se usan efectos de trama que no se usaron intencionalmente en ningún otro juego de SNES.

Ahora hablemos de la pantalla de pausa. Enciende BG3 mientras dibuja un borde amarillo-negro a la izquierda y lo apaga nuevamente durante el mismo borde a la derecha para dibujar líneas grises en la pantalla. También alterna alternativamente a través del marco las líneas de trama en las que se muestran estas líneas grises para crear el efecto de una fluctuación de fase superpuesta.

Si amplía la imagen emulada que se muestra arriba, notará que durante el par de líneas de trama en la esquina izquierda de estas líneas grises hay varios píxeles faltantes. Sucedió porque mi emulación PPU es 100% imperfecta en ciclos de reloj. En este caso, provoca el efecto de habilitar BG3 un poco más tarde de lo que debería.

Puedo cambiar fácilmente los tiempos para que esta imagen se muestre correctamente. Pero es probable que dicho cambio afecte negativamente a otros juegos que cambian los registros de visualización de PPU en el medio de la línea de trama. Aunque Air Strike Patrol es el único juego que hace esto a propósito, hay al menos una docena de juegos en los que esto sucede por casualidad (tal vez IRQ se dispara en ellos demasiado pronto o más tarde).

A veces, esto provoca daños notables breves en la imagen, a los que no se presta atención durante el desarrollo (por ejemplo, en Full Throttle Racingdurante la transición entre la tienda y el juego). A veces, la grabación se realiza mientras la pantalla es transparente y, por lo tanto, no causa anomalías visuales (por ejemplo, como en el caso de mostrar el estado de HP en Dai Kaijuu Monogatari II ). Pero incluso estos casos de borde "invisibles" pueden causar problemas en la representación menos precisa de las líneas de trama que se utilizan en los emuladores más productivos.

Incluso si ignora Air Strike Patrol , todos estos efectos de trama aleatorios (pero válidos) en el software SNES no le permiten diseñar funcionalmente un renderizador PPU que genere la línea de trama completa con una precisión de reloj perfecta.

En el caso de bsnes durante los años de prueba y error, hemos creado una lista de dichos juegos con "efectos raster". También creamos posiciones de representación individuales que permiten una representación mucho más rápida basada en líneas de trama para mostrar correctamente todos estos juegos (a excepción de Air Strike Patrol , por supuesto). Pero, en esencia, este es un montón de trucos desagradables para nosotros, diseñados para juegos específicos.

También tengo un procesador de PPU basado en reloj que no necesita todos estos hacks, pero de vez en cuando crea pequeñas diferencias (de uno a cuatro píxeles) con la representación de este equipo, como en la captura de pantalla anterior de Air Strike Patrol .

Registros de cierre interno


La razón de todas estas pequeñas fallas se reduce a sincronizar tiempos.

Digamos que SNES representa su famoso modo 7 , que es una transformación de textura afinada con cambios de parámetros en cada línea de trama. Para determinar cualquier píxel de la pantalla, debe realizar cálculos similares:

px = a * clip (hoffset - hcenter) + b * clip (voffset - vcenter) +
b * y + (centro << 8)

py = c * clip (hoffset - hcenter) + d * clip (voffset - vcenter) +
d * y + (vcenter << 8)

SNES real no podrá completar todas estas seis multiplicaciones lo suficientemente rápido para cada píxel que se representa en el cuadro. Pero ninguno de estos valores cambia para cada píxel (o, al menos, no debería cambiar), por lo que solo necesitamos calcular px y py una vez al comienzo de cada línea de trama. Es decir, PPU almacena en caché resultados estáticos en pestillos, que son esencialmente copias de registros PPU. En el futuro, pueden transformarse o permanecer sin cambios.

Luego, las coordenadas x, y se transforman en el modo 7 de la siguiente manera:

ox = (px + a * x) >> 8

oy = (py + c * x) >> 8

Aunque x varía para cada píxel, sabemos que el incremento se realiza en uno cada vez. Gracias al almacenamiento de unidades internas, simplemente podemos agregar valores constantes a y c a ox y oy para cada píxel, en lugar de realizar dos multiplicaciones para cada píxel.

Entonces surge la pregunta ante nosotros: ¿en qué posición particular del ciclo del reloj lee la PPU los valores de a y c de los registros externos de PPU a los que tiene acceso la CPU?

Si los tomamos demasiado pronto, esto puede romper algunos juegos. Si lo tomamos demasiado tarde, puede romper otros juegos.

La forma más fácil es esperar los informes de errores y ajustar estas posiciones para solucionar problemas en cada juego específico. Pero en este caso, nunca encontraremos las posiciones exactas , solo sus aproximaciones.

Y cada vez que cambiamos una de estas variables, no es realista volver a probar los tres mil quinientos juegos de la biblioteca SNES para detectar el deterioro que podrían provocar nuestros cambios.

Fuera de la sartén al fuego



Interpretación artística del proceso de eliminación de errores de emulación.

Un estilo similar de metodología de prueba, "haremos que el juego en el que estamos interesados ​​trabajemos a cualquier costo" conduzca al fenómeno, al que llamo emulación "desde el fuego, pero hacia el fuego".

Al comienzo del desarrollo de la emulación SNES, cuando surgieron problemas en el juego, cualquier corrección en este juego que permitió que funcionara fue aceptada y agregada al emulador. Esta solución necesariamente rompió algún otro juego. Y luego corrigieron este juego, después del cual se rompió el tercero. Arreglar el tercer juego nuevamente rompió el primero. Esto continuó por muchos años.

El error aquí fue que los desarrolladores intentaron tener en cuenta solo una variable a la vez. Supongamos que tenemos un juego, y para que funcione, los eventos deben ocurrir entre las medidas 20 y 120. No sabemos la medida exacta, así que simplemente elija 70, exactamente en el medio.

Más tarde, recibimos un informe de error en otro juego, y determinamos que para que este juego funcione , el valor de la medida debe estar entre 10 y 60. Así que ahora lo cambiamos a 40, que funciona para ambos juegos. ¡Suena lógico!

¡Pero luego aparece el tercer juego, en el que el evento debería funcionar entre las medidas 80 y 160! Ahora no podemos hacer que los tres juegos funcionen al mismo tiempo con el mismo valor.

Esto obligó a los desarrolladores de emuladores a crear hacks para juegos específicos. Los codificadores no quieren lanzar un emulador en el que no puedas ejecutar Mario , Zelda o Metroid . Por lo tanto, para el caso general, se usa el ciclo de reloj 40, pero al cargar Metroid, forzamos el valor de tiempo a 100.

¿Cómo es esto posible? ¿Por qué dos juegos necesitan valores diferentes? Esto sucede porque no solo una variable está involucrada aquí. El tiempo que utilizó anteriormente para desencadenar otro evento puede afectar el valor de tiempo que se requiere para el próximo evento.

Imagine esto en forma de una expresión algebraica simple:

2x + y = 120

Puedes resolverlo tomando x = 10, y = 100. O x = 20, y = 80. O x = 30, y = 60. Si solo pensamos en el valor de x, que le permite ejecutar simultáneamente un conjunto de juegos, entonces perdemos el hecho de que, de hecho, el problema puede estar en la y incorrecta.

Las primeras versiones de emuladores para aumentar la compatibilidad simplemente redefinieron el valor de x dependiendo del juego en ejecución. Tales hacks de juegos individuales persistieron, incluso si luego se descubrió el valor único correcto de x. Por lo que el y problema nunca será resuelto!

Sin embargo, en el caso de SNES, no hay una o dos variables involucradas simultáneamente. La consola SNES PPU solo tiene 52 registros externos, que son aproximadamente 130 parámetros. En el proceso de renderizar una sola línea de trama, están involucrados los 130 de estos parámetros y un número desconocido de registros internos y pestillos. Esta es demasiada información para que alguien externo pueda darse cuenta del estado completo de la PPU en un momento determinado.

Este aspecto de la emulación no es obvio para los no iniciados, pero es muy justo: la precisión no es igual a la compatibilidad. Podemos crear un emulador con una precisión del 99 por ciento, capaz de ejecutar el 10% de los juegos. Y puedes escribir un emulador con una precisión del 80% que ejecute el 98% de los juegos. A veces, una implementación correcta a corto plazo rompe los juegos populares. Este es un sacrificio necesario si está tratando de lograr una precisión del 100% y una compatibilidad del 100%.

Resolver el problema


Llegamos a la etapa actual de la emulación de PPU gracias al razonamiento deductivo y los resultados en el mundo real.

Sabemos que dos PPU tienen acceso a dos chips VRAM. Sabemos que pueden leer de cada chip un número conocido de bytes de datos por línea de trama. Conocemos los detalles aproximados de cómo funciona cada uno de los modos de video SNES. Y en base a esto, podemos esbozar un patrón generalizado de cómo se vería la arquitectura. Por ejemplo, aquí hay un breve ejemplo de cómo pueden funcionar los primeros tres modos de video SNES:

if (io.bgMode == 0) {

bg4.fetchNameTable ();

bg3.fetchNameTable ();

bg2.fetchNameTable ();

bg1.fetchNameTable ();

bg4.fetchCharacter (0);

bg3.fetchCharacter (0);

bg2.fetchCharacter (0);

bg1.fetchCharacter (0);

}

if (io.bgMode == 1) {

bg3.fetchNameTable ();

bg2.fetchNameTable ();

bg1.fetchNameTable ();

bg3.fetchCharacter (0);

bg2.fetchCharacter (0);

bg2.fetchCharacter (1);

bg1.fetchCharacter (0);

bg1.fetchCharacter (1);

}

if (io.bgMode == 2) {

bg2.fetchNameTable ();

bg1.fetchNameTable ();

bg3.fetchOffset(0);

bg3.fetchOffset(8);

bg2.fetchCharacter(0);

bg2.fetchCharacter(1);

bg1.fetchCharacter(0);

bg1.fetchCharacter(1);

}


PPU revela a un observador externo solo una pequeña parte de su estado: banderas horizontales y verticales hacia atrás (supresión horizontal / vertical), recuentos de píxeles horizontales y verticales y banderas de superposición de mosaicos en el intervalo para sprites. Esto no es tanto, pero repito: cada pequeño elemento del estado accesible para el observador nos ayuda.

La VRAM (RAM de video, memoria de video) del chip PPU durante el renderizado está cerrada para las CPU SNES, incluso para la lectura. Pero resultó que OAM (memoria de sprites) y CGRAM (memoria de paleta) están abiertas. El truco es que en este momento, la PPU controla el bus de direcciones. Por lo tanto, al leer OAM y CGRAM durante el renderizado de pantalla, puedo observar lo que la PPU obtiene de estos dos bloques de memoria en un momento tan crítico.

Estas no son todas las piezas del rompecabezas, pero son suficientes para que pueda implementar los patrones prácticamente correctos para obtener sprites.

Utilizando patrones de acceso para OAM y CGRAM abiertos, indicadores de PPU, observaciones generales (es decir, conjeturas) de informes de error para diferentes juegos y razonamiento deductivo, pudimos crear procesadores de PPU basados ​​en reloj que pueden lanzar casi perfectamente todos los juegos lanzados.

Pero la situación sigue siendo precaria: si alguien comienza a crear juegos caseros utilizando el tiempo preciso de los ticks y los efectos de trama, entonces todos nuestros emuladores modernos no podrán manejar esto. Incluyendo implementaciones de software y hardware basadas en FPGA.

Debo decir claramente: hoy todosolo conocen el orden interno de operaciones y el comportamiento de ajuste en los chips PPU de la consola SNES. Nadie sabe cómo emularlos perfectamente. Por ahora.

Soluciones posibles


¿Qué haremos con esto? ¿Cómo determinar el orden exacto de las operaciones en una PPU si, desde el punto de vista de la CPU SNES, es una "caja negra"?

Veo cuatro opciones posibles: analizadores lógicos, salida de video digital en modo de prueba, elevadores y extracción de cubiertas de chips.

Analizadores lógicos


Si observa los escaneos de cristales de PPU que se muestran arriba, notará áreas negras en los bordes del chip. Estas son las plataformas que se conectan a los contactos de los chips.

Estos pines almacenan el estado de los chips PPU durante cada ciclo de reloj. Aquí puede encontrar la dirección actual a la que los chips acceden al chip de memoria de video, los valores de los datos transferidos de una PPU a la segunda y mucho más.

Esta información no está disponible para el código que se ejecuta en la CPU SNES, pero proporciona observaciones valiosas sobre el orden interno de las operaciones de PPU.


Conectar las PPU de la consola Super NES a un analizador lógico similar puede ser la clave de la caja negra.

El problema crítico de los analizadores lógicos es que no son muy convenientes de administrar: si intenta muestrear datos en vivo de un sistema en funcionamiento, obtendremos una secuencia de resultados que es bastante difícil de descifrar. Encontrará el mismo problema si intenta analizar la salida RGB analógica del sistema: para capturar estos datos, deberá realizar manualmente cada una de las pruebas. Tal sistema no es muy bueno para crear pruebas de regresión automatizadas reproducibles.

Salida de video digital en modo de prueba


Recientemente, a través de un escaneo de rodajas de cristal con un aumento de 20x, se descubrió un modo de prueba secreto en los chips PPU de la consola SNES. Si realiza una pequeña modificación de hardware, ¡ la PPU comenzará a emitir una señal RGB digital de 15 bits !

¡Esto es casi lo que necesitamos! Sin embargo, este modo tiene problemas, porque el famoso modo 7 no puede mostrar la imagen correcta. Parece que esta función no se ha completado completamente.

Además, para implementar este método, aún se requieren modificaciones manuales de las consolas SNES y un mecanismo apropiado para capturar y analizar la salida en modo de prueba. Sin embargo, a diferencia de la solución con la captura de una señal RGB analógica, dicha señal digital puede someterse a pruebas automáticas, lo que nos permite completar rápidamente una gran cantidad de trabajo en ingeniería inversa PPU.

Risas


Dado que las PPU son estáticas, podríamos eliminar los chips PPU de una consola SNES en funcionamiento y conectarlos a una placa de prototipos o una placa de circuito personalizada junto con dos chips VRAM. Después de eso, puede colocar un microcontrolador entre la PPU y la interfaz USB, y conectar la interfaz a la PC, lo que permitirá que el codificador programe todos los registros de memoria de video y PPU externos. Además, el codificador podrá controlar manualmente los ciclos de reloj de PPU y leer las señales resultantes en los conectores de E / S, registros y en la memoria de PPU en cada ciclo de reloj.

Al modificar el emulador de software para que genere los mismos valores internos de los conectores de E / S, podríamos comparar directamente el hardware real con la emulación, incluso en tiempo real. Sin embargo, este será un trabajo muy duro porque todavía no podemos ver las operaciones internas de PPU.

Eliminación de la cubierta


Por último, la solución más extrema es seguir estudiando el cristal quitando la cubierta del chip. Ya tenemos escaneos de cristal con un aumento de 20x, pero su resolución no es suficiente para analizar y recrear circuitos lógicos individuales, como se hizo en el proyecto Visual 6502 . Si podemos obtener los escaneos de cristal de ambas PPU con un aumento de 100x, entonces podemos comenzar el arduo trabajo de compilar circuitos PPU y convertirlos en tablas de conexión o código VHDL. Luego pueden usarse directamente en FPGA, así como portarse a C ++ u otro lenguaje de programación, aplicable para crear emuladores de software.

Un especialista que había hecho esto antes me dio una estimación aproximada: tomaría aproximadamente 600 horas mapear ambas PPU. Esta tarea es mucho más alta que el nivel de "Vamos a recaudar dinero recaudando fondos y pagándole a alguien", e idealmente cae en la categoría "Esperemos que alguien muy talentoso con un conjunto único de habilidades quiera ayudarnos voluntariamente".

Por supuesto, esto no significa que no estaría contento de recompensar financieramente a alguien por su ayuda, puedo pagar los detalles necesarios y el trabajo.

Solicitud de ayuda


Para resumir: llegué lo más lejos posible en mi proyecto de emulador SNES, y necesito ayuda para completar esta tarea final. Si has leído hasta el final, ¡entonces quizás quieras ayudar! ¡Cualquier apoyo, incluida la participación en el proyecto bsnes en GitHub , o cualquier documentación de investigación sobre el funcionamiento interno de los chips PPU, será de gran valor para nosotros!

¡Gracias por leer y por su apoyo! Ha sido un honor para mí durante quince años ser miembro de la comunidad de emulación SNES.

All Articles