An open API service indexing awesome lists of open source software.

https://github.com/algodesigner/z80

Z80 CPU Emulator (with built-in debugger)
https://github.com/algodesigner/z80

cpu-emulator emulator z80 z80-emulator

Last synced: 6 days ago
JSON representation

Z80 CPU Emulator (with built-in debugger)

Awesome Lists containing this project

README

          

# Z80 CPU Emulator

A standalone Z80 CPU emulator with built-in debugger, forked from the [RunCPM Project](https://github.com/MockbaTheBorg/RunCPM) (implemented by [MockbaTheBorg](https://github.com/MockbaTheBorg)). This library provides a complete Z80 processor implementation that can be used independently or integrated into computer simulation projects.

## Features

- **Complete Z80 instruction set** - Implements all documented Z80 instructions
- **Built-in debugger** - Interactive debugger with step-by-step execution, register inspection, and memory viewing
- **Multiple CPU instances** - Support for creating multiple Z80 instances in the same process
- **Clean API** - Simple C interface for easy integration
- **Platform independent** - Written in standard C with minimal dependencies
- **Intercept functionality** - Custom callback for instruction-level monitoring and control

## Project Structure

- `z80.h` - Public API and type definitions
- `z80.c` - Z80 CPU implementation (4,600+ lines) - **modify this file to implement I/O**
- `console.h` / `console.c` - Terminal I/O and debugger interface
- `globals.h` - Global definitions and macros
- `main.c` - Example usage
- `Makefile` - Build configuration

**Note:** This project is structured as a standalone emulator rather than a library. To customize I/O behavior, you need to modify the `cpu_out()` and `cpu_in()` functions in `z80.c` directly.

## Quick Start

### Building

```bash
make all
```

This will compile the example program `main` which demonstrates basic usage.

### Running the Example

```bash
./main
```

The example creates a Z80 CPU instance, enables debug mode, sets an intercept function, and runs the CPU until the program counter reaches address 5.

## API Documentation

### Core Functions

#### `z80* z80_new(void)`
Creates and initializes a new Z80 CPU instance. Returns a pointer to the CPU structure.

#### `void z80_destroy(z80* cpu)`
Destroys a Z80 CPU instance and frees associated resources.

#### `void z80_run(z80* cpu)`
Starts execution of the Z80 CPU. The CPU will run until its status changes (e.g., via an intercept function or debugger command).

#### `void z80_set_intercept(z80* cpu, void* ctx, void (*intercept)(void* ctx))`
Sets an intercept function that is called before each instruction execution. The intercept function can examine or modify CPU state.

### CPU State Structure

The `z80` structure contains all CPU registers and state:

```c
typedef struct {
int32_t pcx; /* external view of PC */
int32_t af; /* AF register */
int32_t bc; /* BC register */
int32_t de; /* DE register */
int32_t hl; /* HL register */
int32_t ix; /* IX register */
int32_t iy; /* IY register */
int32_t pc; /* program counter */
int32_t sp; /* SP register */
int32_t af1; /* alternate AF register */
int32_t bc1; /* alternate BC register */
int32_t de1; /* alternate DE register */
int32_t hl1; /* alternate HL register */
int32_t iff; /* Interrupt Flip Flop */
int32_t ir; /* Interrupt (upper) / Refresh (lower) register */
int32_t status; /* Status: 0=running, 1=end request, 2=back to CCP */
int32_t debug; /* Debug mode flag */
// ... internal state
} z80;
```

### Memory and I/O Interface

The Z80 emulator includes a 64KB RAM array as part of the CPU structure (`cpu->RAM[65536]`). Memory access is handled internally through macros:

- `_RamRead(address)` - Read byte from memory
- `_RamWrite(address, value)` - Write byte to memory
- `_RamRead16(address)` - Read 16-bit word from memory
- `_RamWrite16(address, value)` - Write 16-bit word to memory

For I/O operations, you need to implement these functions:

```c
void cpu_out(z80 *cpu, uint32_t port, uint32_t value);
uint32_t cpu_in(z80 *cpu, uint32_t port);
```

The default implementations in `z80.c` are stubs that return default values. You should replace them with your actual I/O implementation.

## Usage Examples

### Basic CPU Instantiation

```c
#include "z80.h"

int main() {
z80* cpu = z80_new();
cpu->debug = 1; // Enable debug mode
z80_run(cpu);
z80_destroy(cpu);
return 0;
}
```

### Simple Example Without I/O Modification

```c
#include
#include "z80.h"

/* Intercept function to stop execution at address 5 */
static void intercept(void *ctx) {
z80 *cpu = ctx;
if (cpu->pc == 5) {
printf("\nReached address 5, stopping execution.\n");
cpu->status = 1;
}
}

int main() {
z80 *cpu = z80_new();
cpu->debug = 1; // Enable debug mode
z80_set_intercept(cpu, cpu, intercept);
z80_run(cpu);
z80_destroy(cpu);
return 0;
}
```

Compile and run:
```bash
gcc -o example z80.c console.c example.c
printf "c\n" | ./example # Press 'c' to continue execution
```

### Using Intercept Functions

```c
#include "z80.h"

static void my_intercept(void* ctx) {
z80* cpu = ctx;
if (cpu->pc == 0x100) {
printf("Reached address 0x100\n");
cpu->status = 1; // Stop execution
}
}

int main() {
z80* cpu = z80_new();
z80_set_intercept(cpu, cpu, my_intercept);
z80_run(cpu);
z80_destroy(cpu);
return 0;
}
```

### Implementing I/O

To implement I/O, you need to modify the `cpu_out()` and `cpu_in()` functions in `z80.c`:

```c
// In z80.c, replace the existing cpu_out function:
void cpu_out(z80 *cpu, const uint32_t Port, const uint32_t Value) {
// Handle output to port
switch (Port) {
case 0x00:
// Write to console
putchar(Value & 0xFF);
break;
// Add more port handlers as needed
}
}

// In z80.c, replace the existing cpu_in function:
uint32_t cpu_in(z80 *cpu, const uint32_t Port) {
// Handle input from port
switch (Port) {
case 0x00:
// Read from keyboard
int ch = getchar();
return (ch == EOF) ? 0xFF : ch;
// Add more port handlers as needed
}
return 0xFF; // Default value
}
```

### Accessing Memory

Since memory is internal to the CPU structure, you can access it directly:

```c
// Write a program to memory
uint8_t program[] = {0x3E, 0x41, 0xD3, 0x00, 0x76}; // LD A,0x41; OUT (0),A; HALT
for (int i = 0; i < sizeof(program); i++) {
cpu->RAM[i] = program[i];
}

// Read from memory
uint8_t value = cpu->RAM[0x1000];

// Write to memory
cpu->RAM[0x2000] = 0x55;
```

## Debugger Usage

When debug mode is enabled (`cpu->debug = 1`), the emulator enters an interactive debugger after each instruction. The debugger displays:

```
BC:0000 DE:0000 HL:0000 AF:0000 : [........]
IX:0000 IY:0000 SP:0000 PC:0000 : NOP
Command|? :
```

### Available Commands

Press `?` in the debugger to see all available commands:

**Lowercase commands:**
- `t` - Trace to the next instruction (single step)
- `c` - Continue execution (exit debug mode)
- `b` - Dump memory pointed to by BC register
- `d` - Dump memory pointed to by DE register
- `h` - Dump memory pointed to by HL register
- `p` - Dump the memory page PC points to (PC & 0xFF00)
- `s` - Dump the memory page SP points to (SP & 0xFF00)
- `x` - Dump the memory page IX points to (IX & 0xFF00)
- `y` - Dump the memory page IY points to (IY & 0xFF00)
- `l` - Disassemble 16 instructions from current PC
- `q` - Quit simulation (set CPU status to 1)

**Uppercase commands:**
- `B` - Set breakpoint at specified address
- `C` - Clear breakpoint
- `D` - Dump memory at specified address
- `L` - Disassemble at specified address
- `T` - Step over a CALL instruction
- `W` - Set a byte/word watch at specified address

**Note:** Pressing Enter (no command) will single step to the next instruction.

### Example Debug Session

```
BC:0000 DE:0000 HL:0000 AF:0000 : [........]
IX:0000 IY:0000 SP:0000 PC:0000 : NOP
Command|? : l
0000 : NOP
0001 : NOP
0002 : NOP
0003 : NOP
0004 : NOP
0005 : NOP
0006 : NOP
0007 : NOP
0008 : NOP
0009 : NOP
000A : NOP
000B : NOP
000C : NOP
000D : NOP
000E : NOP
000F : NOP

BC:0000 DE:0000 HL:0000 AF:0000 : [........]
IX:0000 IY:0000 SP:0000 PC:0000 : NOP
Command|? : B
Addr: 0100
Breakpoint set to 0100

BC:0000 DE:0000 HL:0000 AF:0000 : [........]
IX:0000 IY:0000 SP:0000 PC:0000 : NOP
Command|? : c
```

## Integration Guide

### Step 1: Include the Z80 Library

Copy `z80.h`, `z80.c`, `console.h`, `console.c`, and `globals.h` to your project.

### Step 2: Implement I/O Functions

Provide implementations for `cpu_in()` and `cpu_out()` functions. Memory is already built into the CPU structure.

### Step 3: Initialize and Run

```c
z80* cpu = z80_new();
// Configure memory/I/O callbacks
// Load program into memory
z80_run(cpu);
z80_destroy(cpu);
```

### Step 4: Handle Debugging (Optional)

Set `cpu->debug = 1` to enable the built-in debugger for development.

## Building for Different Platforms

### Linux/macOS

```bash
gcc -o my_emulator z80.c console.c my_code.c
```

### Windows (MinGW)

```bash
gcc -o my_emulator.exe z80.c console.c my_code.c
```

### Custom Build Options

The Makefile can be extended with additional compiler flags:

```makefile
CFLAGS = -O2 -Wall -Wextra
```

## Testing

The project includes a basic test in `main.c` that demonstrates CPU instantiation and debug mode. For comprehensive testing:

1. Create test programs in Z80 machine code
2. Load them into memory via direct access to `cpu->RAM[]`
3. Set the program counter and run
4. Verify register states and memory contents

## Limitations

- **I/O stubs**: The default I/O implementations are empty stubs
- **Fixed 64KB memory**: Memory is fixed at 64KB within the CPU structure
- **Instruction timing**: Cycle-accurate timing is not implemented
- **Interrupt handling**: Basic support exists but may need enhancement for specific use cases
- **Platform-specific console**: The console code uses termios and may need adaptation for Windows

## Contributing

Contributions are welcome! Please ensure:

1. Code follows the existing style (spaces, not tabs)
2. New features include appropriate documentation
3. Changes don't break existing functionality

## License

MIT License

Copyright (c) 2017 Mockba the Borg

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

## Acknowledgments

- **MockbaTheBorg** for the original RunCPM implementation
- **Zilog** for the Z80 microprocessor architecture
- **Contributors** to the RunCPM project