github.com/khchehab/go8080

An Intel 8080 emulator written in Go.


Keywords
emudev, emulation, emulator, go, golang, intel8080
License
MIT
Install
go get github.com/khchehab/go8080

Documentation

go8080

Go Reference Go Report Card License: MIT Go Version

A cycle-accurate Intel 8080 CPU emulator written in Go.

Overview

go8080 implements the full Intel 8080 instruction set, including all arithmetic, logic, branching, stack, I/O, and control instructions. It is designed as a reusable library — consumers provide their own memory and I/O implementations and wire them into the CPU. The emulator has been validated against the standard 8080 CPU test ROM suite.

Installation

go get github.com/khchehab/go8080

Usage

The library requires two interface implementations:

Memory — provides the CPU's 64 KB address space:

type Memory interface {
    Read(address uint16) uint8
    Write(address uint16, value uint8)
}

IO — handles the 256-port I/O bus (used by IN/OUT instructions):

type IO interface {
    Read(port uint8) uint8
    Write(port, value uint8)
}

Create a CPU with NewCPU, then drive it with Step or Run:

mem := NewMyMemory()
io  := NewMyIO()

cpu, err := go8080.NewCPU(0x0100, 0xF000, mem, io)
if err != nil {
    log.Fatal(err)
}

// Run until HLT or PC wraps to 0x0000
if err := cpu.Run(); err != nil {
    log.Fatal(err)
}

If your program does not use IN/OUT instructions, pass nil for the IO argument.

Stepping manually

Step executes one instruction and returns the number of cycles it consumed. This is useful when you need to synchronise the CPU with external hardware, such as a video chip that fires interrupts at fixed cycle intervals:

for !cpu.Halted() {
    cycles, err := cpu.Step()
    if err != nil {
        log.Fatal(err)
    }
    totalCycles += uint64(cycles)

    if totalCycles >= cyclesPerHalfFrame {
        cpu.Interrupt(0xCF) // RST 1
        totalCycles = 0
    }
}

Interrupts

Call Interrupt with a full RST opcode byte to request a hardware interrupt:

cpu.Interrupt(0xCF) // RST 1 — jumps to 0x0008
cpu.Interrupt(0xD7) // RST 2 — jumps to 0x0010

The interrupt is held pending until the next Step call where interrupts are enabled (EI) and the one-instruction EI delay has expired. This matches real 8080 hardware behaviour.

Reading CPU state

All registers and flags are exposed as read-only methods:

cpu.A()   // accumulator
cpu.B(), cpu.C(), cpu.D(), cpu.E(), cpu.H(), cpu.L()
cpu.BC(), cpu.DE(), cpu.HL(), cpu.AF() // register pairs
cpu.PC()  // program counter
cpu.SP()  // stack pointer
cpu.PSW() // flags register

cpu.GetSign()
cpu.GetZero()
cpu.GetCarry()
cpu.GetAuxiliaryCarry()
cpu.GetParity()

cpu.Halted()
cpu.InterruptEnabled()
cpu.Cycles() // total clock cycles accumulated

Debug output

Debug writes a single-line trace of the current CPU state to any io.Writer:

cpu.Debug(os.Stdout)
// PC: 0x0100, A: 0x00, F: 0x02, B: 0x00, ...  0x31 - LXI SP,d16

Test ROM results

The emulator passes all standard Intel 8080 CPU test ROMs:

ROM Result
TST8080.COM Pass
8080PRE.COM Pass
CPUTEST.COM Pass
8080EXM.COM Pass
8080EXER.COM Pass

Architecture notes

  • Full 64 KB flat address space via the Memory interface
  • 256-port I/O bus via the IO interface
  • Flags packed in a single byte (S, Z, AC, P, C) with fixed bits enforced on every write
  • Step returns the cycle count for the executed instruction; Cycles returns the running total
  • Conditional CALL and RET return the correct cycle count for both the taken and not-taken paths
  • Interrupt handling with EI delay: an interrupt requested after EI is not serviced until the instruction following EI completes

Acknowledgements

  • superzazu/8080 — provided the CP/M test ROMs used to validate the emulator, as well as a well-structured reference implementation that served as useful documentation during development.
  • pastraiser.com Intel 8080 Opcodes — provided a clear and comprehensive opcode table covering instruction encoding, byte lengths, cycle counts, and flag effects, which was invaluable throughout the implementation.

License

MIT