Simulador 68k

El simulador a mitad del tutorial de factorial recursivo, con el código fuente, el panel de registros, un volcado de memoria y la pila.

Un emulador funcional del Motorola 68000, con su ensamblador y su depurador, todo corriendo en el navegador sin instalar nada y sin servidor. Escribes ensamblador en la página, pulsas Assemble & Load y recorres tu programa instrucción a instrucción mientras puedes ver cómo los registros, el volcado de memoria y la pila se actualizan en vivo.

  • 64 instrucciones, 108 mnemónicos contando las familias Bcc, DBcc y Scc
  • 14 modos de direccionamiento, con su codificación
  • 203 tests unitarios y 3 de navegador
  • 6 tutoriales de dificultad creciente
  • 0 dependencias en tiempo de ejecución

El origen del proyecto

En una asignatura de la universidad aprendimos el lenguaje ensamblador del 68k y lo probamos en un simulador y en una CPU real del laboratorio. A diferencia de lenguajes de alto nivel, lo interesante del ensamblador en mi opinión es lo cerca que está del código máquina, lo siguiente a los unos y ceros que podemos entender los humanos. Es un cambio enorme poder ver cómo cambian en memoria los datos y las instrucciones de los programas.

Entonces empecé a escribir un simulador, empezando por simplemente un HTML con forma de tabla, con los registros a un lado y la memoria al otro, de forma que cuando se lanzara una instrucción se pudiera “ver” cómo modificaba la memoria. Había probado previamente el simulador Easy68k y me propuse hacerlo en JS/TS para poder abrirlo en un navegador en cualquier parte.

Qué hace

  • Escribir y ejecutar tu propio código. El editor ensambla en el sitio. Los errores vuelven con su número de línea, y no se carga nada hasta que el programa ensambla limpio.
  • Step o Run. Step ejecuta una instrucción, Run sigue hasta un STOP o una parada. El panel “Last step” desensambla lo que se acaba de ejecutar, los registros que han cambiado se resaltan, y los bytes de memoria recién escritos también.
  • Ver la pila. Un panel de pila en vivo, que es lo que hace fáciles de seguir las subrutinas y la recursión.
  • Seis tutoriales, desde valores inmediatos y registros hasta bucles con DBcc, arrays, subrutinas, marcos de pila y un factorial recursivo, comentados línea a línea.
  • S-records de entrada y de salida. Cargar un fichero S19 o S37 existente, o ensamblar tu fuente hasta uno.

Usos de la IA en este proyecto: test automáticos -> encontrar bugs -> corregir código -> documentar y explicarme por qué.

MULU / MULSEl codificador leía el destino del primer operando y el origen del segundo, al revés que cualquier otra instrucción de dos operandos. MULU D1,D0 multiplicaba silenciosamente sobre el registro equivocado.
A7 en modo supervisorEl manejo de registros de dirección leía y escribía A[7] directamente, pero en modo supervisor el puntero de pila activo es el SSP. Todo lo que usaba SP como registro de direccionamiento normal tocaba una ranura fantasma que nadie volvía a leer.
Cadenas en DC.BEl cálculo de tamaño contaba mal los bytes de un literal de cadena, así que todas las etiquetas posteriores caían en la dirección equivocada. El programa ensamblaba sin un solo error y luego se metía en terreno sin sentido.
Separación de operandosPartir por comas ignoraba las comillas, así que DC.B 'Hola, 68000!' se rompía por la mitad.
Etiquetas con nombre de mnemónicoUna etiqueta escrita SUB: se interpretaba como una instrucción SUB.

Los tests corrían originalmente con QUnit en una pestaña del navegador, lo que significaba que npm test abría una ventana y terminaba con éxito pasara lo que pasara dentro. Ahora corren con Vitest en Node, en aproximadamente un segundo, con un código de salida que significa algo, y tres tests de Playwright manejan la página de verdad para que la interfaz también quede cubierta. La CI ejecuta ambos en cada push, y el sitio se despliega desde ese mismo workflow.

Lo que no está

El 68230 PI/T está documentado en el repositorio pero nunca llegó a implementarse, así que el ejemplo de los LEDs por interrupciones ensambla y se ejecuta mientras sus direcciones de periférico se comportan como RAM normal. Faltan DIVU y DIVS, y con ellos las instrucciones BCD, CHK, TRAPV y CMPM. Los ciclos se cuentan pero de forma aproximada: esto es un emulador funcional, no uno preciso en tiempos.

Conclusión

El proyecto no está terminado y seguramente seguiré actualizando funcionalidades, corrigiendo bugs y quizá mejorando el diseño :). Me ha parecido una dulce ironía el usar un lenguaje de alto nivel para simular en un navegador lenguaje ensamblador de una CPU de hace más de 40 años.

Áreas:Electrónica y bajo nivel

TypeScriptMotorola 68kEmulaciónEnsambladorVitestPlaywright