Cycle accurate (state accurate) Intel 8080 emulator written in C99.
For an explanation of the Intel 8080 system and its instruction set, refer to this document.
Requirements:
- C compiler
- CMake 3.29+
In the root of the project, run:
./build.sh
Requirements:
- C compiler
- CMake 3.29+
- curl
- wget
If you want to run the tests, run instead:
./build_and_run_test.sh
This will download some tests from https://altairclone.com and run all of them in the emulator. This will take a while, specially 8080EXM.COM. The output must be something like this:
In config.h.in there are some flags that I use for debug and are disabled by default. These are:
- I8080EMU_USE_MAX_MEMORY: configures the machine with 32 KB of ROM and 32 KB of RAM.
- I8080EMU_EMULATOR_TRACE: prints each state the current PC and instruction opcode.
- I8080EMU_ROM_WRITABLE: makes the ROM writable to load test programs on it.
- I8080EMU_CYCLE_DUMP: every cycle (not every state) dumps the contents of the CPU components. Note that this and EMULATOR_TRACE will notably slow down the emulator.
- I8080EMU_RUN_TESTS: reads the first parameter of the program and loads it into the ROM memory.
This emulator simulates the execution of instructions cycle by cycle and state by state. I followed the original manual that I documented here.
For instructions that have some idle states, these states are simulated by doing nothing.
There are some instruction operations that take two states, and there are normally memory accesses. For example:
- B2 -> DDD: read the byte 2 of instruction from memory and write it into DDD register.
- B2 -> TMP: read the byte 2 of instruction from memory and write it into TMP.
- (TMP) -> DATA BUS: read the content of TMP and write it into memory.
- etc.
In these operations if they are memory reads, I use the first state to put the address into the address bus and the second to call readMemory that reads the content of the memory and put it into the data bus. If the operation is a memory write, in the first state I put the data into the data bus and the address into the address bus, and in the second state I call writeMemory that writes the content of the data bus into the memory address.
There are some operations with registers that use the address latch and the incrementer/decrementer circuit in the real hardware. I simulate these operations in two steps. This is inefficient, but I have tried to simulate accurately the behavior of the circuit. For example, the copy to HL from SP in the SPHL instruction is made by first copying the HL register pair to the address latch and then copying the content of the address latch to the SP register:
// State 4
machine->cpu.addressLatch = machine->cpu.HL.pair;
// State 5
machine->cpu.SP = machine->cpu.addressLatch;
The increments and decrements by one are generally done in one state using the address latch too:
// State 2
machine->cpu.addressLatch = machine->cpu.PC;
machine->cpu.PC = machine->cpu.addressLatch + 1U;
I have implemented the interrupt, halt and hold states, but they aren't tested in the tests I found.
In the hold sequences I let the current instruction terminate before going into the hold state, instead of doing the DMA operation and finish the instruction in parallel.
