Skip to content

algodesigner/z80

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

24 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Z80 CPU Emulator

A standalone Z80 CPU emulator with built-in debugger, forked from the RunCPM Project (implemented by 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

make all

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

Running the Example

./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:

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:

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

#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

#include <stdio.h>
#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:

gcc -o example z80.c console.c example.c
printf "c\n" | ./example  # Press 'c' to continue execution

Using Intercept Functions

#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:

// 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:

// 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

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

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

Windows (MinGW)

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

Custom Build Options

The Makefile can be extended with additional compiler flags:

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

About

Z80 CPU Emulator (with built-in debugger)

Topics

Resources

Stars

Watchers

Forks

Contributors

Languages