Retro486: un 486DX2-66 en el navegador
Abres una página y tienes un 486DX2-66 con FreeDOS. Retro486 lo hace en JavaScript puro a 7,2 MIPS: qué emula, qué pierde frente a v86 y cómo probarlo.
1. Un PC 486 que se abre en una pestaña
A finales de octubre de 2026 apareció en GitHub Retro486, un emulador de PC que no se instala: se abre como una página web. Dentro hay un Intel 80486DX2-66, su FPU, una placa AT con chipset OPTi, ranuras ISA y VLB, tarjeta VGA, Sound Blaster 16, controladora IDE y disquetera. Arranca SeaBIOS de verdad, carga FreeDOS 1.3 y llega a un prompt de DOS.
Lo llamativo no es que exista otro emulador. Es que está escrito en JavaScript puro, con módulos ES6 y sin una sola dependencia: ni WebAssembly, ni Emscripten, ni un npm install para compilar. Abres index.html con un servidor estático y tienes un PC de 1992 en una pestaña.
La demo pública se sirve desde GitHub Pages y el repositorio ocupa unos 13 MB, la mayor parte BIOS y una imagen de disquete de FreeDOS. En el momento de escribir esto tiene 48 estrellas y seis forks, y se publicó el 21 de octubre de 2026.
Entre el Linux en JavaScript de Fabrice Bellard y este 486 completo hay quince años. El último paso no es de velocidad: es de alcance.
2. Qué emula Retro486: un 486DX2-66 con su placa y sus tarjetas
El primer 486 llegó en 1989 con el i486DX a 25 MHz: el primer x86 con caché y FPU en el mismo chip. En 1992 Intel duplicó el reloj interno sin tocar el bus y nació el DX2-66: 66 MHz de núcleo, 33 MHz de bus, más de un millón de transistores, 8 KB de caché unificada, FPU integrada, 168 pines PGA y 5 voltios. Fue el procesador de la transición: la máquina con la que se escribía en Windows 3.1, se jugaba a DOOM y se aprendía a configurar una tarjeta VLB. Por eso el emulador elige ese modelo y no un 386: no es un 486 cualquiera, es el que casi todo el mundo recuerda.
El emulador no simula «un 486» en abstracto: simula una máquina concreta, con sus jumpers y sus tarjetas. Esto es lo que declara el panel de la placa:
| Componente | Qué emula |
|---|---|
| CPU | Intel 80486DX2-66 con FPU 80487 integrada |
| Placa | ASUS/OPTi 495SX, Socket 3, cinco ranuras ISA/VLB |
| Memoria | 16 MB en cuatro SIMM FPM de 4 MB a 60 ns |
| BIOS | AMI/SeaBIOS de 128 KB y VGA BIOS de Bochs |
| Vídeo | IBM VGA/ET4000 con 256 KB de VRAM y RAMDAC de 80 MHz |
| Sonido | Sound Blaster 16 (CT1741, OPL3 YMF262, DAC YAC512) |
| E/S | GoldStar Prime 2C: IDE/ATAPI, disquetera y dos UART 16550 |
| Diagnóstico | Tarjeta POST de puerto 80h y altavoz de PC |
Seis capas, todas en JavaScript. El navegador pone el canvas, el audio y el almacenamiento; el resto es código que se puede leer.
La placa se puede mirar por dentro en la pestaña Motherboard & Slots: los SIMM, el chipset, el Dallas DS1287, el POST y las cinco ranuras con sus tarjetas. Al seleccionar una, aparece su mapa de puertos de E/S, sus IRQ y sus jumpers.
La placa emulada, con la telemetría del bus en tiempo real. No es una ilustración: los valores cambian mientras el sistema arranca.
3. Cómo se emula una CPU en JavaScript
Un emulador de CPU es un bucle que lee bytes, decodifica la instrucción y cambia registros y memoria. La pregunta interesante no es si se puede hacer en JavaScript, sino a qué velocidad y con qué fidelidad.
3.1 El reloj no existe: 120.000 instrucciones por fotograma
Retro486 no ejecuta instrucciones a 66 MHz. Ejecuta un lote fijo por fotograma y deja que el navegador marque el ritmo. El núcleo está en runBatch, en js/cpu_486.js:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
runBatch(cycles = 10000) {
let remaining = cycles;
const chunk = 5120;
while (remaining > 0) {
const n = remaining > chunk ? chunk : remaining;
this.bus.stepPeripherals();
this.cpui386_step(n);
remaining -= n;
if (this.halt) {
this.bus.uticksNudge = (this.bus.uticksNudge + 8000) >>> 0;
break;
}
}
// ...
}
El valor por defecto de cyclesPerFrame son 120.000 instrucciones. A 60 fotogramas por segundo salen 7,2 millones de instrucciones por segundo, que es exactamente lo que marca el panel de diagnóstico. Con el selector de velocidad se puede bajar a 25.000 (etiquetado 4,77 MHz) o subir a 240.000 (etiquetado 66 MHz):
| Preset | Instrucciones por fotograma | Equivalente a 60 Hz |
|---|---|---|
| 4,77 MHz | 25.000 | 1,5 MIPS |
| 12 MHz | 60.000 | 3,6 MIPS |
| 33 MHz | 120.000 | 7,2 MIPS |
| 66 MHz | 240.000 | 14,4 MIPS |
MIPS no es MHz. Un 486DX2-66 real ejecuta como mucho una instrucción por ciclo de núcleo, y en la práctica bastante menos: 66 millones de instrucciones por segundo es un techo teórico, no una medida. El emulador no pretende igualarlo; pretende que el software antiguo se comporte de forma reconocible.
Ese detalle explica muchas cosas. Si el emulador ejecuta 7,2 millones de instrucciones por segundo, un programa que en un 486 tardaba un segundo tardará aquí varias veces más, salvo que el cuello de botella sea la E/S. Y explica también por qué la velocidad se ajusta con un desplegable y no con un reloj: no hay reloj que ajustar.
3.2 Memoria, paginación y coma flotante
El núcleo emula modos real, protegido de 16 y 32 bits y V86, con paginación de 32 bits (CR0.PG, CR3), una TLB de 512 entradas, conmutación de tareas por TSS y las instrucciones que añadió el 486: CPUID, BSWAP, CMPXCHG, XADD e INVLPG. Sin paginación no arrancaría SeaBIOS, que pasa a modo protegido antes de ceder el control al gestor de arranque.
La FPU es un módulo aparte, fpu_487.js, de unos 38 KB. Emula la pila x87 y sus operaciones sobre los números de JavaScript, que son de coma flotante de 64 bits: la precisión de 80 bits del x87 original no está, y eso se nota en programas que dependen de ella.
El panel de diagnóstico muestra lo que casi ningún emulador muestra: registros, contador de instrucciones y memoria física. En 0xB8000 se lee el texto que está en pantalla.
3.3 Por qué JavaScript puro y no WebAssembly
Podría haber sido WebAssembly. De hecho, el emulador de referencia del navegador, v86, compila x86 a WASM en caliente y es mucho más rápido. Retro486 elige lo contrario: un intérprete en JavaScript que se puede abrir en DevTools, poner un punto de interrupción en una instrucción y leer.
La ventaja está en el acceso, no en el rendimiento. No hay toolchain, no hay artefacto que compilar, no hay una capa de bytes entre el código y el comportamiento. Si quieres entender cómo se emula MOV, abres cpu_486.js y lo ves. La desventaja también es clara: un intérprete de JavaScript no compite con un JIT, y por eso el techo son 7,2 MIPS con el preset por defecto.
El tamaño del proyecto está repartido de forma muy legible:
| Fichero | Tamaño | Qué contiene |
|---|---|---|
js/cpu_486.js | 232 KB | Intérprete del 486, modos, paginación y TLB |
js/fpu_487.js | 38 KB | Pila y operaciones x87 |
js/retro_bus.js | 59 KB | Chipset, buses, DMA y ranuras |
js/cards/multio_card.js | 48 KB | IDE/ATAPI, disquetera y UART |
js/cards/vga_card.js | 40 KB | Modos de vídeo y VBE |
js/cards/soundblaster_card.js | 9 KB | DSP, DMA y OPL3 |
js/app.js | 22 KB | Bucle de fotograma y coordinación |
Ese reparto es la mejor documentación del proyecto: cada periférico es un fichero y cada fichero se puede leer en una tarde.
4. Chipset y buses: ISA a 8,33 MHz y VLB a 33 MHz
Una CPU sola no arranca un PC. Hacen falta interrupciones, temporizadores, reloj de tiempo real y un teclado. Retro486 emula el chipset clásico:
- Dos 8259A en cascada, con las 15 IRQ del AT.
- Un 8254 con sus tres canales, el altavoz de PC y el temporizador que SeaBIOS espera en el puerto
0x3CC. - Un MC146818 (aquí, un Dallas DS1287) con CMOS y NVRAM.
- Un 8042 para teclado y ratón, con la puerta A20 en los puertos
0x92y0x60. - DMA 8237 para la Sound Blaster y la disquetera.
Los buses son la parte que más se nota en el rendimiento y la que peor se emula. El ISA de 8 y 16 bits va a 8,33 MHz: unos 16 MB/s teóricos en 16 bits, la mitad en la práctica. El VLB es un bus local de 32 bits que nació en 1992 atado al 486, corre a la frecuencia del bus (33 MHz) y da unos 132 MB/s teóricos. Fue la respuesta de los fabricantes de clones a la lentitud del ISA para vídeo, y desapareció en cuanto llegó el PCI.
La placa visible no es la placa del código. La interfaz dibuja una OPTi 495SX de 1993, pero
retro_bus.jsincluye también un puente PCI i440FX/PIIX3, que es de la era Pentium Pro. No es un fallo que rompa nada: es la señal de que esto es una aproximación didáctica, no una réplica exacta de un modelo.
Las tarjetas son módulos con una interfaz común (adapter_card.js): VGA, EGA, CGA, Sound Blaster, Multi-I/O, POST, altavoz y NE2000. Se pueden cambiar de ranura, tocar sus jumpers y hasta escribir una tarjeta nueva en JavaScript desde el Card Studio. Esa modularidad es lo que convierte el proyecto en una plataforma para experimentar, no solo en un emulador cerrado.
5. VGA y Sound Blaster 16 en el navegador
La tarjeta de vídeo emula modos de texto (03h), CGA (04h y 06h), los planar de EGA/VGA (0Dh, 0Eh, 10h y 12h) con pel panning y split screen, el modo 13h de 320×200 y 256 colores, el modo X y la extensión VBE con framebuffer lineal. Encima hay un renderizador de CRT con fósforo verde, ámbar o blanco papel, scanlines, desmagnetización y pantalla completa. No es solo estética: el filtro ayuda a que 640×400 se lean como se leían.
El sonido es una Sound Blaster 16 con DSP, DMA y OPL3, conectada a WebAudio. La tarjeta tiene jumpers de IRQ, DMA y puerto, igual que la de verdad, y el emulador sintetiza el YMF262 en JavaScript.
El almacenamiento pasa por la controladora Multi-I/O: dos canales IDE/ATAPI, disquetera y puertos serie. Los discos, las ROM y las escrituras se guardan en IndexedDB y localStorage, de modo que si instalas algo en C: y recargas la página, sigue ahí. También se puede montar una imagen propia y arrancar desde ella. Y si lo tuyo es escribir software de la época en vez de ejecutarlo, los compiladores de BASIC que siguen vivos son otra puerta de entrada.
6. Arrancar FreeDOS en local, sin build
La forma más rápida es la demo pública: jandej.github.io/e486-web. Arranca sola con el disquete de FreeDOS 1.3 y pide idioma, fecha y hora; con dos Enter llegas al prompt.
Para tenerlo en local, sin build:
1
2
3
git clone https://github.com/jandej/e486-web.git
cd e486-web
python3 -m http.server 8080
Y abres http://localhost:8080/. No hay que compilar nada: el navegador carga los módulos ES6 directamente.
En la demo de este artículo monté un disquete propio con el kernel de FreeDOS, COMMAND.COM y un FDAUTO.BAT mínimo, para no pasar por el instalador. Con VER, MEM y DIR se ve lo que hay:
6.1 Del POST al prompt
Cuando pulsas Cold Reset, la CPU emulada arranca en 0xFFFFFFF0 y empieza el ritual de cualquier PC: SeaBIOS hace el POST (los códigos que salen en la tarjeta de puerto 80h), monta la tabla de vectores, pasa a modo protegido, lee el primer sector del disquete en 0x7C00 y comprueba la firma 0xAA55. Ese sector carga KERNEL.SYS, el kernel de FreeDOS lee FDCONFIG.SYS y lanza COMMAND.COM, que a su vez ejecuta el FDAUTO.BAT.
Es un buen sitio para ver dónde falla un emulador. En mi primer disquete olvidé copiar COMMAND.COM a la raíz y el kernel arrancó hasta mostrar un Bad or missing Command Interpreter en mayúsculas, exactamente el mismo mensaje que veías en un PC de 1994 cuando el disco estaba mal. El emulador no había fallado: el disquete estaba incompleto. Con el intérprete en su sitio, el prompt aparece y todo lo demás es DOS.
Grabación real: arranque desde un disquete de FreeDOS, prompt de DOS, VER, MEM con los 16 MB y DIR del disquete. El emulador ejecuta el mismo código que se ve en pantalla.
MEM dentro del emulador: 640 KB convencionales, 15 MB extendidos y un programa máximo de 501 KB. Los números salen del chipset emulado, no de una tabla.
7. ¿Es fiel? Lo que Retro486 pierde frente a v86 y 86Box
Retro486 no es un emulador cycle-accurate, y no lo pretende. Ejecuta lotes de instrucciones por fotograma, avanza los periféricos en bloques de 5.120 instrucciones y no modela la temporización de cada acceso a memoria. Eso es suficiente para arrancar un sistema operativo y ejecutar software de la época, y no lo es para medir ciclos, escribir demos con sincronía de vídeo o validar rutinas de timing.
Para un juego de 1993, eso es suficiente: el bucle principal espera al retorno vertical y el emulador avanza lo bastante rápido. Para una demo que cuenta ciclos, no. La regla práctica es la de siempre: si el software dependía de un temporizador de hardware para algo crítico, aquí puede comportarse distinto; si dependía de la BIOS y del DOS, funcionará.
Cuatro proyectos, cuatro equilibrios distintos entre velocidad, fidelidad y facilidad para mirar el código.
La comparación justa:
- v86 traduce x86 a WebAssembly en caliente, con un intérprete que detecta páginas calientes y genera módulos. Es mucho más rápido, y también mucho más complejo. Su CPU llega a nivel Pentium 4 con SSE3; la de Retro486 se queda en el 486.
- PCjs también está escrito en JavaScript y emula de 8088 a 80386 con un depurador excelente. Es la referencia para estudiar máquinas antiguas, pero no llega al 486.
- 86Box y PCem emulan hardware de bajo nivel, con BIOS reales y temporización mucho más cuidada. Son la opción para fidelidad, a cambio de un programa nativo y bastante más peso.
- DOSBox emula DOS a alto nivel: no necesita BIOS ni pretende ser fiel, pero hace que un juego arranque. Busca otra cosa.
El repositorio no tiene licencia. A fecha de hoy no hay fichero
LICENSE, así que el código está bajo copyright por defecto: puedes leerlo y ejecutarlo, pero no reutilizarlo sin permiso. Si quieres basarte en él, pídelo antes. Las BIOS que incluye (SeaBIOS y la VGA de Bochs) y FreeDOS sí tienen licencias libres.
8. Preservación y enseñanza: para qué sirve un PC en JavaScript
Hay tres razones, y ninguna es la nostalgia.
Preservación accesible. Un enlace sustituye a instalar un emulador, descargar una BIOS y configurar una máquina. Eso deja en cero el coste de mirar cómo funcionaba un PC de 1992. Es la misma idea que hace que un archivo de desarrollo de Palm OS siga teniendo valor: el software no se conserva solo con el binario, se conserva cuando alguien puede ejecutarlo.
Enseñanza visible. La pestaña de diagnóstico muestra registros, contador de instrucciones y memoria física; la placa muestra IRQ y puertos; la telemetría muestra el bus. Un estudiante puede ver la puerta A20, la pila x87 o el vector de interrupciones sin un laboratorio. Eso no lo da un emulador opaco.
Código que se puede leer. 232 KB de CPU en un fichero de JavaScript, sin dependencias, es un objeto de estudio manejable. Se puede poner un console.log en una instrucción y ver qué pasa. Es la misma virtud que hace interesantes los compiladores pequeños: la barrera de entrada es baja.
Y hay una cuarta, más de fondo: el navegador se ha convertido en la máquina virtual universal. Ya no hace falta elegir sistema operativo para ejecutar software de 1992; hace falta una pestaña. Eso tiene costes (rendimiento, aislamiento, memoria) y ventajas enormes (distribución, enlaces, actualizaciones). Proyectos como este exploran dónde está la frontera.
Si te interesa la preservación de plataformas muertas, el archivo de Palm OS que conté en el post del archivo de desarrollo de Palm OS es el mismo problema con otro sistema.
9. Tres cosas que probar
- Arranca FreeDOS y mira
MEM. Es la forma más rápida de comprobar que el chipset emulado responde: 640 KB convencionales, el resto en XMS. - Cambia la velocidad en System & Diagnostics. Baja a 25.000 instrucciones por fotograma y mira cómo se arrastra el prompt; súbelas a 240.000 y compara. No es un reloj, pero se comporta como si lo fuera.
- Toca una tarjeta. Quita la Sound Blaster, arranca y comprueba qué software deja de sonar. Ahí se entiende qué era un jumper y por qué el Plug and Play fue una revolución.
10. Enlaces y vídeos de interés
10.1 Artículos
- Intel486 DX2 Microprocessor Data Book — el manual original de 1992: caché de 8 KB, FPU integrada y bus de 33 MHz.
- SeaBIOS — el BIOS libre que arranca el emulador, con su documentación.
- FreeDOS — el DOS libre que corre dentro, con imágenes de disquete y su kernel.
- JSLinux — el experimento de Fabrice Bellard que abrió el camino en 2011.
10.2 Vídeos
Un 486DX2-66 real montado y arrancado: la referencia física de lo que el emulador intenta reproducir.
Demo de v86, el otro gran emulador x86 de navegador: sirve para ver la diferencia entre compilar a WebAssembly e interpretar en JavaScript.



