Intel 8080 emulator. 19th IOCCC. Best of Show.

After winning the IOCCC for the first time, I had the idea of writing an emulator of the 8080 processor in 2000 characters of C, after patterning experimentally the more than 200 instructions and doing measures of byte count, I realized that it was possible and I made it. Then I added CP/M support as a extra feature. I was completely astonished when I won Best of Show of 19th IOCCC :).
This program was one of three winning entries I sent for the 19th edition, the other two were a graphical chess program and a knight's tour solver.

Source code

This program emulates a complete Intel® 8080 processor, along with a teletype and a disk controller, just like at the start of the personal computers revolution (circa 1975).
Here is the source code, written in C language:
                               #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)); }

How to compile it

First, download the source code from here. It requires an *NIX compatible system (find porting notes below).
To compile use:
    cc toledo2.c -o toledo2
The emulator needs an initial memory image to do something usable, so it will need two files (c_basic.bin and c_bios.bin). Rename c_basic.bin to C and run the emulator, and et voila! you have the public domain Palo Alto Tiny BASIC (by Li-Chen Wang), published on the very first volume of the now extinct Dr. Dobb's Journal magazine.
Type using uppercase letters, here are three example programs, press Enter after each line:
   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
Press Ctrl+Z to quit, by the way, the segmentation fault is normal at this point.
All good programmers started learning BASIC, now, what about a CP/M emulator?
Download the following file (not included because of possible copyright and blah, blah):
  http://www.retroarchive.org/cpm/os/KAYPROII.ZIP
Extract CPM64.COM from the SOURCE directory, and copy it to files named A and B (these will be the disk drives). Now rename the provided c_bios.bin to C and run the emulator.
Now you have a running CP/M operating system!, with two files on A: drive, HALT.COM to stop the emulator (so it closes drives) and IMPORT.COM to introduce new files.
To get a complete CP/M system, you will need the following files from the KAYPROII.ZIP, SOURCE directory:
  ASM.COM  DDT.COM   DUMP.COM   ED.COM   LOAD.COM
  PIP.COM  STAT.COM  SUBMIT.COM XSUB.COM
To import them, you must run the emulator with an argument, by example:
  prog DDT.COM
When the A> prompt appears, do:
  IMPORT DDT.COM
When it ends, do HALT, so the file is saved, and you can start the same process with another file.
The WS33-KPR directory of the KAYPROII.ZIP file also contains a Wordstar version that works with this emulator. There is also an interesting game, the classical Adventure by Crowther and Woods in the ADVENTUR directory, at that time was amazing that a microcomputer could contain such big game.
At this time I have tested successfully the following software from www.retroarchive.org:
Languages
  http://www.retroarchive.org/cpm/lang/c80v30.zip
  http://www.retroarchive.org/cpm/lang/Mbasic.com

Spreadsheet
  http://www.retroarchive.org/cpm/business/MULTPLAN.ZIP

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

Utilities
  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
Some programs require installation to configure the terminal, locate ANSI or VT-100.

A little course on CP/M

The CP/M user's manuals are available on www.retroarchive.org. But if you remember how to use MS-DOS then you can use CP/M very easily. Here is a little reference of the command line:
Internal commands:
A:              Change current drive to A
B:              Change current drive to B
DIR             List files in drive
DIR *.TXT       List all files with TXT extension
TYPE FILE.TXT   Shows content of FILE.TXT
ERA FILE.TXT    Erases file FILE.TXT
USER 1          Change to user 1 (0-15 available)
                It is something as subdirectories.
                So you can separate your files.

External commands:
STAT            Show used/free space on drive
STAT *.*        Show file sizes.
DDT PROG.COM    Debug PROG.COM.
                To quit use Ctrl+C

                Dump address 0100 (hex):
                  D0100
Emulator running Wordstar under CP/M
Emulator running Wordstar under CP/M.

What is an 8080?

It is simply the little brother of the Zilog Z80, it has no extended registers (AF', BC', DE', HL', IX or IY), no relative jumps, and every instruction beginning with CB, DD, ED or FD doesn't exist.
The flags are only S (Sign, bit 7), Z (Zero, bit 6), P (Parity, bit 2) and C (Carry, bit 0).
The 8080 processor was created by Federico Faggin and Masatoshi Shima in 1974, afterwards both would design the Zilog Z80 in 1976, these two processors were pretty important and influential for the rise of microcomputers.

Porting it

It is easy if your platform has getch/kbhit and ANSI terminal
    read    -->  Z=kbhit()?getch():0
    write   -->  putchar(7[o])
    system  -->  nothing
Also add the following to trap Ctrl-C:
    #include <signal.h>
    signal(SIGINT, SIG_IGN);
On PC/DOS you will need to add ANSI.SYS to CONFIG.SYS
In *NIX the min 0 for stty is required, circa 2001 it was not required.

How it works (SPOILER)

The l array contains the 64K memory, it is initialized with a boot image loaded from the 'C' file, the program counter is the c pointer, and registers are on o[]. The main loops reads every op-code and separates it in one of three common forms, a lot of trinary operators selects the instruction.
Execution starts at 0000, you can write your own boot or monitor program, or even your own operating system playing with the 'C' file.
   o[0] = B register   o[1] = C register
   o[2] = D register   o[3] = E register
   o[4] = H register   o[5] = L register
   o[6] = Flags        o[7] = A or accumulator
The following instructions do peripheral operation:
   76           Quits emulator
   DB 00        Reads key pressed status
   DB 01        Reads key
   DB 02        Reads byte from file (Carry=EOF)
   D3 xx        Writes byte from acc. to console
   ED ED 02     Reads sector (128 bytes)
   ED ED 03     Writes sector (128 bytes)

   Memory addresses:

   FBFA = Low source/target address
   FBFB - High source/target address
   FBFC - Sector (0-127)
   FBFD - Cylinder (low byte)
   FBFE - Cylinder (high byte)
   FBFF - Drive (1/2)
The BIOS is tailor made for this emulator and helps to simplify it. Though the size of each virtual hard disk drive can reach 1 GB, CP/M only handles up to 8 MB.

Other notes:

And now it is 2024

This emulator was developed eighteen years ago when the computers had 32-bit processors and it used a hole in the C language syntax where you could pass a pointer on an integer. In fact, this is the IOCCC objective: make C compilers to do things these shouldn't be supposed to do.
However, the C compilers for 64-bit processors don't allow it anymore as pointers are 64-bit and the int types are 32-bit, so compilers stop with an error (in especial in macOS because clang).
The source code in this webpage is already updated to use FILE * instead of int, and this way we brought CP/M to year 2024, hehe.
If you are curious about it, the previous version of the emulator can be downloaded here or in the IOCCC website. In 2020, Mark Abene sent me a workaround for x86-64 computers:
    gcc -static toledo2.c -o toledo2
But this doesn't work in macOS.

Useful links:

Last modified: Feb/06/2024