Emulador Intel 8080. 19º IOCCC. Best of Show.

Después de ganar el IOCCC por primera vez, tuve la idea de escribir un emulador del procesador 8080 en 2000 caracteres de C, después de hacer patrones experimentales de las más de 200 instrucciones y medir el conteo de bytes, me di cuenta de que si era posible y lo hice. Posteriormente agregué soporte CP/M como una característica extra. Me sorprendí gratamente cuando supe que era el ganador de lo mejor del concurso del 19º IOCCC :).
Este programa fue una de tres participaciones ganadoras que envié al 19º IOCCC, las otras dos fueron un programa de ajedrez gráfico y un solucionador al problema de desplazar un caballo de ajedrez por los 64 escaques.

Código fuente

Este programa emula un procesador completo Intel® 8080, junto con un teletipo y un controlador de discos, tal como al inicio de la revolución de las computadoras personales (alrededor de 1975).
Aquí está el código fuente, escrito en lenguaje C:
                               #include <stdio.h>
           #define n(o,p,e)=y=(z=a(e)%16 p x%16 p o,a(e)p x p o),h(
                                #define s 6[o]
             #define p z=l[d(9)]|l[d(9)+1]<<8,1<(9[o]+=2)||++8[o]
                                #define Q a(7)
           #define w 254>(9[o]-=2)||--8[o],l[d(9)]=z,l[1+d(9)]=z>>8
                               #define O )):((
                  #define b (y&1?~s:s)>>"\6\0\2\7"[y/2]&1?0:(
                               #define S )?(z-=
                    #define a(f)*((7&f)-6?&o[f&7]:&l[d(5)])
                               #define C S 5 S 3
                       #define D(E)x/8!=16+E&198+E*8!=x?
                             #define B(C)fclose((C))
                       #define q (c+=2,0[c-2]|1[c-2]<<8)
                          #define m x=64&x?*c++:a(x),
                         #define A(F)=fopen((F),"rb+")
                    unsigned char o[10],l[78114],*c=l,*k=l
                          #define d(e)o[e]+256*o[e-1]
#define h(l)s=l>>8&1|128&y|!(y&255)*64|16&z|2,y^=y>>4,y^=y<<2,y^=~y>>1,s|=y&4
+64506; FILE *u, *v, *e, *V; int x,y,z,Z; main(r,U)char**U;{

     { { { } } }       { { { } } }       { { { } } }       { { { } } }
    { { {   } } }     { { {   } } }     { { {   } } }     { { {   } } }
   { { {     } } }   { { {     } } }   { { {     } } }   { { {     } } }
   { { {     } } }   { { {     } } }   { { {     } } }   { { {     } } }
   { { {     } } }   { { {     } } }   { { {     } } }   { { {     } } }
    { { {   } } }    { { {     } } }    { { {   } } }    { { {     } } }
      { { ; } }      { { {     } } }      { { ; } }      { { {     } } }
    { { {   } } }    { { {     } } }    { { {   } } }    { { {     } } }
   { { {     } } }   { { {     } } }   { { {     } } }   { { {     } } }
   { { {     } } }   { { {     } } }   { { {     } } }   { { {     } } }
   { { {     } } }   { { {     } } }   { { {     } } }   { { {     } } }
    { { {   } } }     { { {   } } }     { { {   } } }     { { {   } } }
     { { { } } }       { { { } } }       { { { } } }       { { { } } }

                                   for(v A((u A((e A((r-2?0:(V A(1[U])),"C")
),system("stty raw -echo min 0"),fread(l,78114,1,e),B(e),"B")),"A")); 118-(x
=*c++); (y=x/8%8,z=(x&199)-4 S 1 S 1 S 186 S 2 S 2 S 3 S 0,r=(y>5)*2+y,z=(x&
207)-1 S 2 S 6 S 2 S 182 S 4)?D(0)D(1)D(2)D(3)D(4)D(5)D(6)D(7)(z=x-2 C C C C
C C C C+129 S 6 S 4 S 6 S 8 S 8 S 6 S 2 S 2 S 12)?x/64-1?((0 O a(y)=a(x) O 9
[o]=a(5),8[o]=a(4) O 237==*c++?((int (*)())(2-*c++?fwrite:fread))(l+*k+1[k]*
256,128,1,(fseek(e=5[k]-1?u:v,((3[k]|4[k]<<8)<<7|2[k])<<7,Q=0),e)):0 O y=a(5
),z=a(4),a(5)=a(3),a(4)=a(2),a(3)=y,a(2)=z O c=l+d(5) O y=l[x=d(9)],z=l[++x]
,x[l]=a(4),l[--x]=a(5),a(5)=y,a(4)=z O 2-*c?Z||read(0,&Z,1),1&*c++?Q=Z,Z=0:(
Q=!!Z):(c++,Q=r=V?fgetc(V):-1,s=s&~1|r<0) O++c,write(1,&7[o],1) O z=c+2-l,w,
c=l+q O p,c=l+z O c=l+q O s^=1 O Q=q[l] O s|=1 O q[l]=Q O Q=~Q O a(5)=l[x=q]
,a(4)=l[++x] O s|=s&16|9<Q%16?Q+=6,16:0,z=s|=1&s|Q>159?Q+=96,1:0,y=Q,h(s<<8)
O l[x=q]=a(5),l[++x]=a(4) O x=Q%2,Q=Q/2+s%2*128,s=s&~1|x O Q=l[d(3)]O x=Q  /
128,Q=Q*2+s%2,s=s&~1|x O l[d(3)]=Q O s=s&~1|1&Q,Q=Q/2|Q<<7 O Q=l[d(1)]O s=~1
&s|Q>>7,Q=Q*2|Q>>7 O l[d(1)]=Q O m y n(0,-,7)y) O m z=0,y=Q|=x,h(y) O m z=0,
y=Q^=x,h(y) O m z=Q*2|2*x,y=Q&=x,h(y) O m Q n(s%2,-,7)y) O m Q n(0,-,7)y)  O
m Q n(s%2,+,7)y) O m Q n(0,+,7)y) O z=r-8?d(r+1):s|Q<<8,w O p,r-8?o[r+1]=z,r
[o]=z>>8:(s=~40&z|2,Q=z>>8) O r[o]--||--o[r-1]O a(5)=z=a(5)+r[o],a(4)=z=a(4)
+o[r-1]+z/256,s=~1&s|z>>8 O ++o[r+1]||r[o]++O o[r+1]=*c++,r[o]=*c++O z=c-l,w
,c=y*8+l O x=q,b z=c-l,w,c=l+x) O x=q,b c=l+x) O b p,c=l+z) O a(y)=*c++O r=y
,x=0,a(r)n(1,-,y)s<<8) O r=y,x=0,a(r)n(1,+,y)s<<8))));
system("stty cooked echo"); B((B((V?B(V):0,u)),v)); }

Como compilarlo

Primero descargue el código fuente desde aquí. Se requiere un sistema compatible con *NIX (busque debajo notas para transportarlo).
Para compilarlo utilice:
    cc toledo2.c -o toledo2
El emulador requiere una imagen inicial de memoria para hacer algo útil, así que se necesitan dos archivos (c_basic.bin y c_bios.bin). Renombre c_basic.bin a C y ejecute el emulador, y ¡et voila! tiene usted el Palo Alto Tiny BASIC de dominio público (por Li-Chen Wang), publicado en el antiquísimo primer volumen de la ahora desaparecida revista Dr. Dobb's Journal.
Teclee usando mayúsculas, aquí hay tres programas de ejemplo, oprima Enter después de cada línea:
   10 PRINT "Hello, world!"
   LIST
   RUN

   10 FOR A=1 TO 10       10 INPUT A
   20 PRINT A             20 INPUT B
   30 NEXT A              30 PRINT A+B
   LIST                   LIST
   RUN                    RUN
Oprima Ctrl+Z para salir, por cierto, el error de segmentación en este punto es normal.
Todos los buenos programadores comenzaron aprendiendo BASIC, ahora, ¿qué tal un emulador CP/M?
Descargue el siguiente archivo (no incluído debido a posible copyright y bla, bla):
  http://www.retroarchive.org/cpm/os/KAYPROII.ZIP
Extraiga CPM64.COM del directorio SOURCE, y copielo a archivos nombrados A y B (estos serán los discos duros). Ahora renombre el archivo c_bios.bin a C y ejecute el emulador.
¡Ahora tiene un sistema operativo CP/M funcional! Y han aparecido dos archivos en el disco A:, HALT.COM para detener el emulador (y asegurarse de que cierre los discos) e IMPORT.COM para introducir nuevos archivos.
Para obtener un sistema CP/M completo, necesitará los siguientes archivos del directorio SOURCE de KAYPROII.ZIP:
  ASM.COM  DDT.COM   DUMP.COM   ED.COM   LOAD.COM
  PIP.COM  STAT.COM  SUBMIT.COM XSUB.COM
Para importarlos, debe ejecutar el emulador con un argumento, por ejemplo:
  prog DDT.COM
Cuando aparezca la petición A>, escriba:
  IMPORT DDT.COM
Cuando termine, haga HALT, así el archivo es salvado, y puede continuar el proceso con otro archivo.
El archivo KAYPROII.ZIP contiene también una versión de Wordstar que funciona con este emulador.
El directorio WS33-KPR del archivo KAYPROII.ZIP contiene también una versión de Wordstar que funciona con este emulador. Además hay un juego interesante en el directorio ADVENTUR, el clásico Adventure por Crowther y Woods, en aquella época era sorprendente que una microcomputadora pudiera contener un juego tan grande.
Hasta este momento he probado exitosamente el siguiente software de www.retroarchive.org:
Lenguajes
  http://www.retroarchive.org/cpm/lang/c80v30.zip
  http://www.retroarchive.org/cpm/lang/Mbasic.com

Hoja de cálculo
  http://www.retroarchive.org/cpm/business/MULTPLAN.ZIP

Juegos
  http://www.retroarchive.org/cpm/games/zork123_80.zip

Utilidades
  http://www.retroarchive.org/cpm/cdrom/CPM/UTILS/ARC-LBR/UNARC16.ARK
  http://www.retroarchive.org/cpm/cdrom/CPM/UTILS/ARC-LBR/UNARC16.MSG
  http://www.retroarchive.org/cpm/cdrom/CPM/UTILS/ARC-LBR/DELBR12.ARK
  http://www.retroarchive.org/cpm/cdrom/CPM/UTILS/SQUSQ/USQ-20.COM
  http://www.retroarchive.org/cpm/cdrom/CPM/UTILS/SQUSQ/UNCR8080.LBR
  http://www.retroarchive.org/cpm/cdrom/CPM/UTILS/DIRUTL/XDIR3-12.LBR
  http://www.retroarchive.org/cpm/cdrom/CPM/UTILS/DIRUTL/CU.LBR
  http://www.retroarchive.org/cpm/cdrom/CPM/UTILS/FILCPY/SWEEP40.LBR
Algunos programas requieren instalación para configurar la terminal, busque ANSI o VT-100.

Un pequeño curso de CP/M

Los manuales del CP/M en inglés están disponibles en www.retroarchive.org. Pero si usted recuerda como usar MS-DOS entonces puede utilizar CP/M muy fácilmente. Aquí hay una pequeña referencia de la línea de órdenes:
Órdenes internas:
A:              Cambia unidad actual a A
B:              Cambia unidad actual a B
DIR             Lista los archivos de la unidad
DIR *.TXT       Lista archivos con extensión TXT
TYPE FILE.TXT   Muestra contenido de FILE.TXT
ERA FILE.TXT    Borra el archivo FILE.TXT
USER 1          Cambia a usuario 1 (0-15 disponible)
                Son como subdirectorios para que
                usted pueda separar sus archivos.

Órdenes externas:
STAT            Muestra espacio usado/libre en unidad
STAT *.*        Muestra tamaños de archivos.
DDT PROG.COM    Depurar PROG.COM.
                Para salir use Ctrl+C

                Listar dirección 0100 (hex):
                  D0100
El emulador ejecutando Wordstar bajo CP/M
El emulador ejecutando Wordstar bajo CP/M.

¿Qué es un 8080?

Sencillamente el hermano menor del Zilog Z80, no tiene registros extendidos (AF', BC', DE', HL', IX or IY), no tiene saltos relativos, y cada instrucción que comienza con CB, DD, ED o FD no existe.
Las banderas son únicamente S (Signo, bit 7), Z (Cero, bit 6), P (Paridad, bit 2) y C (Acarreo, bit 0).
El procesador 8080 fue creado por Federico Faggin y Masatoshi Shima en 1974, posteriormente ambos diseñaron el Zilog Z80 en 1976, los dos procesadores fueron muy importantes e influyentes en el surgimiento de las microcomputadoras.

Transportándolo

Es fácil si su plataforma tiene getch/kbhit y terminal ANSI
    read    -->  Z=kbhit()?getch():0
    write   -->  putchar(7[o])
    system  -->  nada
También añada lo siguiente para bloquear Ctrl-C:
    #include <signal.h>
    signal(SIGINT, SIG_IGN);
En PC/DOS puede requerir añadir ANSI.SYS a CONFIG.SYS
En *NIX el min 0 en stty es requerido, alrededor del año 2001 no era requerido.

Como trabaja (aguafiestas)

El arreglo l contiene la memoria de 64K, y es inicializado con una imagen de arranque cargada del archivo 'C', el contador de programa es el apuntador c, y los registros están en o[]. El bucle principal lee cada código de operación y lo separa en una de tres formas comunes, muchos operadores trinarios seleccionan la instrucción.
La ejecución comienza en 0000, usted puede escribir su propio programa de arranque o monitor, o incluso su propio sistema operativo jugando con el archivo 'C'.
   o[0] = Registro B   o[1] = Registro C
   o[2] = Registro D   o[3] = Registro E
   o[4] = Registro H   o[5] = Registro L
   o[6] = Banderas     o[7] = A o acumulador
Las siguientes instrucciones realizan operación de periféricos:
   76           Termina el emulador
   DB 00        Lee estatus de tecla oprimida
   DB 01        Lee tecla
   DB 02        Lee byte de archivo (Carry=Fin de archivo)
   D3 xx        Escribe byte de acumulador a consola
   ED ED 02     Lee un sector (128 bytes)
   ED ED 03     Escribe un sector (128 bytes)

   Direcciones de memoria:

   FBFA . Dirección baja fuente/destino
   FBFB - Dirección alta fuente/destino
   FBFC - Sector (0-127)
   FBFD - Cilindro (byte bajo)
   FBFE - Cilindro (byte alto)
   FBFF - Unidad (1/2)
El BIOS está especialmente diseñado para este emulador y ayuda a simplificarlo. Aunque el tamaño de cada uno de los discos duros virtuales puede alcanzar 1 GB, el CP/M solamente maneja hasta 8 MB.

Otras notas:

Y ahora es 2024

Este emulador fue escrito hace 18 años cuando las computadoras tenían procesadores de 32 bits y aprovechaba un agujero de la sintaxis del C en que se puede pasar un entero como un apuntador ya que eran de la misma anchura de bits. De hecho es el objetivo del IOCCC forzar las compiladores a hacer cosas que no se deberían hacer.
Sin embargo los compiladores de C para los procesadores de 64 bits ya no lo permiten porque un apuntador es de 64 bits y un int es de 32 bits, así que se detienen con un error (en especial en macOS que usa clang).
El código fuente en la parte superior ya está actualizado para usar FILE * en vez de int, y así llevamos CP/M al año 2024, jeje.
Si tienes curiosidad, la versión anterior del emulador se puede bajar aquí o en el sitio del IOCCC. En 2020, Mark Abene informó una forma de evitar el crash en máquinas x86-64:
    gcc -static toledo2.c -o toledo2
Pero esto no funciona en macOS.

Ligas útiles:

Última modificación: 06-feb-2024