As stated in part one, I decided to write my 8080 emulation code as a library that could be used to emulate more 8080 based systems in the future. This post gives an overview of the Intel 8080 and then delves into the library and its implementation details. If you just want to see the code, check it out here, otherwise, read on.
The 8080 had a 16-bit address bus and an 8-bit data bus. It had seven 8-bit general purpose registers: A, B, C, D, E, H and L. The program counter and stack pointer were each 16 bits wide. Although the general purpose registers were each 8 bits, there were instructions to support operating on them in pairs (BC, DE, HL) as if each pair was a 16-bit register.
Additionally, many instructions supported operating directly on memory through
the 16-bit address stored in register pair HL. This is referred to in
documentation and assembly code as “register” M. For example,
MOV M, A moves
the contents of the accumulator to memory at the address stored in register pair
The 8080 also maintained a flags register which contained five status flags:
- Sign: Set to the 7th bit of the result of an arithmetic operation
- Zero: Set if the result of an operation is zero
- Auxiliary Carry: Set if there is a carry out of bit 3 while performing an
arithmetic operation, this flag only affects one instruction –
- Parity: Set if the result of an operation has an even number of set bits, unset otherwise
- Carry: Set if there is a carry out of bit 7 when performing addition, or no carry if performing subtraction, also modified by rotation instructions
The more observant among you will probably notice that these are the same flags found in the lower eight bits of a modern x86’s processor’s FLAGS register. Indeed, the FLAGS register on a modern x86 CPU dates back to this register.
The 8080 had an 8-bit data bus, thus 28=256 possible opcodes. Twelve opcodes are undocumented but perform the same job as other instructions.
Interrupt handling on the 8080 was fairly straightforward. Unlike the forward
compatible Z80, which had maskable
and non-maskable interrupts, the 8080 only had one type of interrupt. An
interrupting device would pull the INT line high and write an opcode to the
8080’s data bus. This would cause that opcode to be executed after the currently
executing instruction finished. While this opcode could be anything, in
practice, it was usually one of the eight single byte restart instructions
RST 7, which would call one of the eight restart subroutines at
The 8080 also had support for binary coded
decimal operations, in
which it treated an 8-bit number as a two digit base ten number, using the lower
four bits as the ones digit and the upper four bits as the tens digit. To work with
BCD numbers, an additional instruction –
DAA (decimal adjust accumulator) was
used to adjust the contents of the accumulator to the correct BCD form after an
arithmetic operation was performed between two BCD values. This differs from the
BCD implementation on processors such as the MOS
6502, which had an internal
BCD flag that caused all arithmetic operations to convert results to BCD by on
their own, without the need for an extra instruction.
Since I hadn’t used the language for a large personal project before, I decided to go back to basics and write lib8080 (as I’m calling it) in plain old C99.
All information about an emulated 8080 is stored in an
i8080 struct, which
has the following format:
At its core, the beating heart of lib8080 is the same as most CPU emulators – a large switch statement to call the correct emulation function for the current opcode.
Most instructions for the 8080 are encoded in a way that makes CPU emulation
fairly uncomplicated. For example, each of the 8080’s 63
instructions are encoded as follows:
bit 7 bit 0 | | | | v v 0 1 X X X Y Y Y X X X = Destination Register Y Y Y = Source Register 0 0 0 = Register B 0 0 1 = Register C 0 1 0 = Register D 0 1 1 = Register E 1 0 0 = Register H 1 0 1 = Register L 1 1 0 = Memory Reference M 1 1 1 = Register A
This makes implementation a breeze for the most part. For example, the following
four-line function handles every
Unlike some other microprocessors, the 8080 did not use memory mapped I/O to
communicate with external devices. Instead it had two specialized instructions,
OUT. These instructions supported interfacing with up to 256 external
devices. The format of these instructions was
IN d8 and
OUT d8, where
is an 8-bit device number.
IN instruction was executed, the device number to read from was placed
on the upper and lower eight bits of the 8080’s address bus, the 8080’s DBIN
(data bus in) line was pulled high, and the external device placed one byte on
the 8080’s data bus, which was read into the accumulator.
Similarly, when an
OUT instruction was executed, the contents of the
accumulator were placed on the 8080’s data bus, the 8080’s WR (write) line was
pulled low (WR is active low) and the device number to write to was placed on
the upper and lower eight bits of the address bus. The appropriate external
device could then read the contents of the accumulator from the data bus.
lib8080 emulates this functionality using function pointers. Inside the
struct are two function pointers,
output_handler. When an
OUT instruction is executed, if the appropriate function pointer is
NULL, the function it points to will be invoked to handle input or output
of data respectively. This allows for projects to emulate external devices
easily using a callback function.
As a simple example, here’s how you could use lib8080 to emulate a device that
always writes the byte
0x12 on I/O port zero and reads the latest byte written
on I/O port zero into a global variable.
To achieve a high level of accuracy, lots of unit testing was required. Looking around at C unit testing frameworks, I found most were either too complex or too simple for my needs, so I created my own, AttoUnit. While a full discussion on AttoUnit is a blog post of its own, I created it to be header only, provide just what I needed for this project and keep boilerplate to an absolute minimum.
I wrote tests for each instruction as I implemented it. Each instruction is
unit tested at least once to verify basic behaviour, and most are tested several
times to check edge cases. As an example, here’s the test for
MOV B,D (heavily
commented for demonstration purposes):
As it currently stands, lib8080 has 1061 assertions in 421 unit test cases. For comparison purposes, the entire library is ~1300 lines.
In addition to unit testing, I wanted to test the 8080 implementation as a whole. Thankfully, I managed to find several 8080 test programs from the 80s and 90s that very comprehensively test the 8080. These are CP/M binaries so they required a small amount of CP/M emulation, but that turned out to be very simple.
CP/M was an early operating system that, like modern operating systems,
abstracted away hardware details so that programmers could write code easier.
These abstractions were accessed through BDOS calls, which followed exactly the
same basic idea as system calls on modern operating systems. When a program
wanted to make a BDOS call, it loaded the BDOS function number into the 8080’s C
register and jumped to memory address
0x05. Note that the code for the program
you were running in CP/M was loaded at address
0x100. The first 256 bytes were
reserved for CP/M, so address
0x05 was not a location in a user loaded
The test programs I came across only needed BDOS calls 2 (write character to console) and 9 (write string to console) to work, so I emulated them with a very simple C program.
Now when I load a test binary, eg.
CPUTEST.COM with my simple CP/M emulator, I
DIAGNOSTICS II V1.2 - CPU TEST COPYRIGHT (C) 1981 - SUPERSOFT ASSOCIATES ABCDEFGHIJKLMNOPQRSTUVWXYZ CPU IS 8080/8085 BEGIN TIMING TEST END TIMING TEST CPU TESTS OK
As an interesting side note, from the small amount of info I can find online, SuperSoft Associates is a defunct software company located in Illinois that was in business in the 80s.
I set up Travis to run all these test binaries alongside the unit tests mentioned previously.
Implementing and testing all of this was roughly a month of work, but it was a lot of fun. The extra effort put into testing was well worth it. With a project this well tested, I’m confident the emulation is essentially perfect. If you’re interested in using lib8080 in a project of your own, check out the API documentation for usage details.
With an emulated 8080, all that’s needed to get Altair BASIC running now is an emulated teletype. I’ll discuss implementing one in part three.