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.
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:
- The 8080 runs at your computer speed divided
between a number that I have not calculated.
- This obfuscated processor was created using
obfuscated code produced by an obfuscated mind,
no brains were harmed during its production,
except those that tried to read the code.
- The original version of this code was eaten
by my C++ dog.
- I intended to make it simple to understand,
it uses only four C keywords.
- Also I discovered that braces are very useful
for commenting.
- Why to bother with prototypes?, every good C
programmer can develop its C programs using
only one function.
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