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.
¿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:
- El 8080 corre a la velocidad de su computadora
dividida entre un número que no he calculado.
- Este procesador confuso fue creado usando
código confuso producido por una mente confusa,
no se dañó a ningún cerebro durante su producción,
excepto aquellos que trataron de leer el código.
- La versión original de este código fue comida
por mi perro C++.
- Traté de hacerlo sencillo de entender, utiliza
solamente cuatro palabras clave del C.
- También descubrí que las llaves son muy útiles
para comentar.
- ¿Porqué molestarse con prototipos?, todos los
buenos programadores de C pueden desarrollar sus
programas usando una sola función.
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