Z80 emulator in Rust

After having impelemented CHIP-8 in Rust, I was interested in emulation with a more realistic system. I chose the Z80 as a next target, picking the virtual machine's commercial counterpart from the late 70s 8-bit CPUs. z80-rs is a Rust emulator for a bare-minimum Z80 computer.

Z80 is only a (micro)processor, requiring some additional hardware to compose a full computer. For required external parts, I assumed:

with no other external hardware.

In the case of commercial products, what emulation means becomes unclear. This emulator operates at a layer mostly abstracted, executing an interpreter loop.

  1. Z80 machine code is deserialized into instruction types (documented below)
  2. The instruction computes the subsequent machine state with output, one emulator tick per instruction.

This might be better thought of as a binary translation layer. This does miss details, of varying practicality:

Testing

I'm not sure the name of this general testing approach, but it's an effective one I learned in a compilers course. An input directory contains integration tests expected to pass or fail, with corresponding output for the result or error.

Interactivity was tested in my following project, a Z80 Forth interpreter.

Types

Below are the types that make up the Z80 machine state and its instructions.

8-bit registers

pub enum R { A, F, B, C, D, E, H, L, I, R, Ixl, Ixh, Iyl, Iyh, }

"sandwich registers", read/write 2x8-bit registers as 1x16-bit register

pub enum Sr { Af, Bc, De, Hl, Sp, Pc, Ix, Iy, }

program counter states

pub enum Pc { I, // increment by Pc instruction length Im8, // 8-bit immediate Im16, // 16-bit immediate J(u16), // absolute Jr(i8), // relative jump }

indexing memory

pub enum MemAddr { Imm, // immediate value dereference, (**) Reg(Sr), // register value dereference, (HL), (BC), etc. }

opcode argument with 8-bit length

// // AND R // | // +------ 8-bit register argument // // LD R, * // | | // | +---- u8 immediate argument // +------- 8-bit register argument // // LD (**), A // | | // | +- 8-bit register argument // +------- u16 memory index pub enum Arg8 { U8, Reg(R), Mem(MemAddr), MemOffset(Sr), }

opcode argument with 16-bit length

// // DEC HL // | // +------ 16-bit register argument // // LD HL, ** // | | // | +--- u16 immediate argument // +------- 16-bit register argument pub enum Arg16 { U16, /* 16 bit immediate argument */ Reg(Sr), Mem(MemAddr), }

opcode argument for flags

pub enum ArgF { True, // always (jr *) F(Flag), // conditional (jr z, *) Nf(Flag), // opposite conditonal (jr nz, *) }