Hice un emulador de NES. Esto es lo que aprendí sobre la Nintendo original.

Recientemente creé mi propio emulador de NES . Lo hice principalmente por diversión y para aprender sobre cómo funcionaba el NES. Aprendí algunas cosas interesantes, así que escribí este artículo para compartir. Ya hay mucha documentación por ahí, así que esto solo pretende resaltar algunos datos interesantes. Advertencia : ¡esto será muy técnico!

Mi emulador puede grabar GIF animados. Aquí estoy jugando a Donkey Kong.

La CPU

El NES usó el MOS 6502 (a 1.79 MHz) como su CPU. El 6502 es un microprocesador de 8 bits que fue diseñado en 1975. (¡Hace cuarenta años!) Este chip era muy popular: también se usaba en los modelos Atari 2600 y 800, Apple I y II, Commodore 64, VIC-20, BBC Micro y más. De hecho, una revisión del 6502 (el 65C02 ) todavía está en producción hoy.

Los 6502 tenían relativamente pocos registros (A, X e Y) y eran registros de propósito especial. Sin embargo, sus instrucciones tenían varios modos de direccionamiento, incluido un modo de "página cero" que podía hacer referencia a las primeras 256 palabras ($ 0000 – $ 00FF) en la memoria. Estos códigos de operación requerían menos bytes en la memoria del programa y menos ciclos de CPU durante la ejecución. Una forma de ver esto es que un desarrollador puede tratar estos 256 espacios como "registros".

El 6502 no tenía instrucciones de multiplicar o dividir. Y, por supuesto, sin punto flotante. Había un modo BCD (Decimal codificado en binario) pero esto estaba deshabilitado en la versión NES del chip, posiblemente debido a problemas de patentes.

El 6502 tenía una pila de 256 bytes sin detección de desbordamiento.

El 6502 tenía 151 códigos de operación (de un posible 256). Los 105 valores restantes son códigos de operación ilegales / no documentados. Muchos de ellos bloquean el procesador. Pero algunos de ellos realizan resultados posiblemente útiles por coincidencia. Como tal, muchos de estos han recibido nombres basados ??en lo que hacen.

El 6502 tenía al menos un error de hardware, con saltos indirectos. JMP (<addr>) no funcionaría correctamente si <addr> fuera de la forma $ xxFF. Al leer dos bytes desde la dirección especificada, no llevaría el desbordamiento FF-> 00 en xx. Por ejemplo, leería $ 10FF y $ 1000 en lugar de $ 10FF y $ 1100.

Mapa de memoria

El 6502 tenía un espacio de direcciones de 16 bits, por lo que podría hacer referencia a hasta 64 KB de memoria. Pero, el NES tenía solo 2 KB de RAM, en direcciones de $ 0000 a $ 0800. El resto del espacio de direcciones era para acceder al PPU, la APU, el cartucho del juego, los dispositivos de entrada, etc.

Algunas líneas de dirección no estaban conectadas, por lo que los bloques grandes del espacio de direcciones realmente reflejan otras direcciones. Por ejemplo, $ 1000 a $ 1800 refleja la memoria RAM en $ 0000 a $ 0800. Escribir a $ 1000 equivale a escribir a $ 0000.

¡ES PELIGROSO IR SOLO! TOMA ESTO.

El PPU (Unidad de procesamiento de imágenes)

El PPU generó la salida de video para el NES. A diferencia de la CPU, el chip PPU fue especialmente diseñado para NES. El PPU funcionó a 3 veces la frecuencia de la CPU. Cada ciclo del PPU genera un píxel mientras se procesa.

El PPU podría representar una capa de fondo y hasta 64 sprites. Los sprites pueden tener 8×8 o 8×16 píxeles. El fondo se puede desplazar tanto en el eje X como en el eje Y. Admitía desplazamiento "fino" (un píxel a la vez). Esto fue un gran problema en aquel entonces.

Tanto el fondo como los sprites estaban hechos de 8×8 fichas. Las tablas de patrones en la ROM del cartucho definieron estos mosaicos. Los patrones solo especificaron dos bits del color. Los otros dos bits provienen de una tabla de atributos. Una tabla de nombre especificó qué fichas van donde en el fondo. Con todo, parece enrevesado en comparación con los estándares actuales. Tuve que explicarle a mi compañero de trabajo que no era "solo un mapa de bits".

El fondo estaba formado por 32 x 30 = 960 de estos mosaicos de 8×8. El desplazamiento se implementó al representar más de uno de estos fondos de 32 x 30, cada uno con un desplazamiento. Si se desplaza tanto en el eje X como en el eje Y, se pueden ver hasta cuatro de estos fondos. Sin embargo, el NES solo admite dos, por lo que se usaron diferentes modos de reflejo para la duplicación horizontal o vertical.

El PPU contenía 256 bytes de OAM – Memoria de atributo de objeto – que almacenaba los atributos de sprite para los 64 sprites. Los atributos incluyen la coordenada X e Y del sprite, el número de mosaico del sprite y un conjunto de banderas que especificaron dos bits del color del sprite, especificó si el sprite aparece delante o detrás de la capa de fondo y permite voltear el sprite vertical y / u horizontalmente El NES admitió una copia DMA de la CPU para copiar rápidamente un fragmento de 256 bytes a todo el OAM. Este acceso directo fue aproximadamente cuatro veces más rápido que la copia manual de los bytes.

Aunque el PPU admite 64 sprites, solo 8 se pueden mostrar en una sola línea de escaneo. Se establecería un indicador de desbordamiento para que el programa pudiera manejar una situación con demasiados sprites en una línea. Esta es la razón por la cual los sprites parpadean cuando hay muchas cosas sucediendo en el juego. Además, hubo un error de hardware que ocasionó que el indicador de desbordamiento a veces no funcionara correctamente.

Muchos juegos harían cambios a mitad del cuadro para que el PPU hiciera una cosa por una parte de la pantalla y otra por la otra, a menudo utilizada para desplazamiento dividido o representación de una barra de puntuación. Esto requirió un tiempo preciso y saber exactamente cuántos ciclos de CPU utiliza cada instrucción. Cosas como esta hacen que la emulación sea difícil.

El PPU tenía una forma primitiva de detección de colisión: si el primer sprite (zeroth) intersectaba el fondo, se establecería un indicador que indicaría un "golpe de sprite cero". Solo uno de esos golpes podría ocurrir por cuadro.

El NES tenía una paleta incorporada de 54 colores distintos: estos eran los únicos colores disponibles. No fue RGB; los colores en la paleta básicamente escupieron una señal particular de croma y luminancia al televisor.

La paleta de colores NES.

La APU (Unidad de procesamiento de audio)

La APU soportaba dos canales de onda cuadrada, un canal de onda triangular, un canal de ruido y un canal de modulación delta.

Para reproducir sonidos, el programa del juego escribiría en registros específicos (direcciones en la memoria) para configurar estos canales.

Los canales de onda cuadrada soportan control de frecuencia y duración, barridos de frecuencia y sobres de volumen.

El canal de ruido usó un registro de desplazamiento de realimentación lineal para generar ruido pseudoaleatorio.

El canal de modulación delta podría reproducir muestras de la memoria. La música SMB3 tiene un sonido de batería de metal que usó el DMC. TMNT3 tenía voces como "cowabunga" que usaban el DMC.

Lucha del globo

Mappers

El espacio de direcciones reservado para el cartucho restringía los juegos a 32 KB de memoria de programa y 8 KB de memoria de caracteres (tablas de patrones). Esto fue bastante limitante, por lo que las personas se volvieron creativas e implementaron mapeadores.

Un asignador es hardware en el cartucho que puede realizar la conmutación bancaria para intercambiar el nuevo programa o la memoria de caracteres en el espacio de memoria direccionable. El programa podría controlar esta conmutación bancaria escribiendo a direcciones específicas que apuntaban al hardware del asignador.

Los diferentes cartuchos de juegos implementaron este cambio bancario de diferentes maneras, por lo que hay docenas de mapeadores diferentes. Así como un emulador debe emular el hardware NES, también debe emular los mapeadores de cartuchos. Sin embargo, aproximadamente el 90% de todos los juegos de NES usan uno de los seis mapeadores más comunes.

Archivos ROM

Un archivo .nes ROM contiene los bancos de memoria de programa y los bancos de memoria de caracteres del cartucho. Tiene un encabezado pequeño que especifica qué mapper utilizó el juego y qué modo de reflejo de video utilizó. También especifica si la memoria RAM con respaldo de batería estaba presente en el cartucho.

Conclusión

Ha sido divertido aprender sobre el NES. Estoy impresionado con lo que las personas pudieron lograr con un hardware tan limitado. Me da ganas de escribir un juego de estilo de 8 bits ahora …

Escribí mi emulador en Go usando OpenGL + GLFW para video y PortAudio para audio. El código está en GitHub, así que compruébalo:

https://github.com/fogleman/nes

Mi favorito: Super Mario Bros. 3

Aprende más