Skip to content

mikedaley/MOS6502

Repository files navigation

MOS6502

My first attempt at my own NMOS/CMOS 6502 CPU emulator written in modern C++20/23. There are MANY of these already developed, but this one is mine and something I wanted to create for myself.

Features

  • Cycle Accurate: Every memory access and operation consumes the correct number of cycles
  • Complete Opcode Support: All 256 opcodes including 105 undocumented/illegal instructions
  • Modern C++: Uses C++20/23 features for maximum performance and type safety
  • Standalone Design: Template-based CPU class with callback interface for memory access
  • Zero Overhead: Compile-time optimization with concepts, constexpr, and template metaprogramming
  • Debug Support: Built-in disassembler for instruction inspection
  • Portable: Cross-platform support (Linux, macOS, Windows)

Requirements

  • C++20 compatible compiler (GCC 10+, Clang 12+, MSVC 2019+)
  • CMake 3.20 or higher

Building

mkdir build
cd build
cmake ..
cmake --build .

Build Options

  • BUILD_EXAMPLES: Build example programs (default: ON)
  • BUILD_TESTS: Build test suite (default: ON)

Release Build with Optimizations

cmake -DCMAKE_BUILD_TYPE=Release ..
cmake --build .

Quick Start

#include <MOS6502/CPU6502.hpp>
#include <array>

// Simple memory implementation
std::array<uint8_t, 65536> memory{};

auto read = [&](uint16_t addr) { return memory[addr]; };
auto write = [&](uint16_t addr, uint8_t val) { memory[addr] = val; };

// Create CPU with callbacks
MOS6502::CPU6502 cpu(read, write);

// Set up reset vector
memory[0xFFFC] = 0x00;
memory[0xFFFD] = 0x02; // Reset to $0200

// Load a simple program
memory[0x0200] = 0xA9; // LDA #$42
memory[0x0201] = 0x42;

// Reset and execute
cpu.reset();
uint32_t cycles = cpu.executeInstruction();

// Debug support
std::cout << cpu.disassembleCurrentInstruction() << std::endl;
std::cout << "A=" << static_cast<int>(cpu.getA()) << std::endl;

Architecture

The emulator implements the NMOS 6502 CPU with:

  • 8-bit accumulator (A), X and Y index registers
  • 8-bit stack pointer, 16-bit program counter
  • 8-bit status register with 7 flags (N, V, B, D, I, Z, C)
  • 13 addressing modes
  • All special cases (page boundary bugs, RMW behavior, etc.)

Performance

Optimized for maximum performance with:

  • Template-based design for compile-time optimization
  • constexpr lookup tables
  • Inline hot paths
  • Branch prediction hints
  • Cache-friendly data structures
  • Link-time optimization (LTO)
  • Profile-guided optimization (PGO) support

Implementation Status

Complete

  • All 13 addressing modes with cycle-accurate timing
  • All 151 legal opcodes fully implemented
  • All major undocumented opcodes (SLO, RLA, SRE, RRA, SAX, LAX, DCP, ISC, ANC, ALR, ARR, and illegal NOPs)
  • JAM opcodes (halt CPU)
  • IRQ/NMI interrupt handling
  • Disassembler with all 256 opcodes
  • Page boundary bug in indirect JMP
  • Branch timing with page crossing detection
  • RMW (Read-Modify-Write) instruction timing
  • BCD mode support in ADC/SBC

Examples

See the examples/ directory for:

  • simple_example.cpp - Basic usage demonstration
  • comprehensive_test.cpp - Tests for all instruction categories

API Reference

CPU Methods

// Execution
uint32_t executeInstruction();  // Execute one instruction, returns cycles
uint32_t step();                 // Step one cycle (currently aliases executeInstruction)

// Interrupts
void reset();                    // Reset CPU (reads vector from $FFFC)
void nmi();                      // Trigger NMI (processed on next execute)
void irq();                      // Trigger IRQ (if interrupt flag clear)

// State inspection
uint64_t getTotalCycles() const;
std::string disassembleCurrentInstruction() const;
std::string disassembleAt(uint16_t address) const;

// Register access
uint8_t getA/X/Y/SP/P() const;
uint16_t getPC() const;
void setA/X/Y/SP/P(uint8_t);
void setPC(uint16_t);

// Status flags
bool getFlag(StatusFlag) const;
void setFlag(StatusFlag, bool);

Testing

Klaus Dormann Functional Test Suite

The emulator uses Klaus Dormann's comprehensive 6502 functional test suite, which tests all valid opcodes and addressing modes. This is the industry-standard test for 6502 emulators.

What the Tests Validate

  • ✅ All 56 legal 6502 opcodes
  • ✅ All 13 addressing modes
  • ✅ Status flag behavior (N, V, Z, C, I, D)
  • ✅ Stack operations (push/pull)
  • ✅ Decimal mode (BCD arithmetic)
  • ✅ Branch instructions and timing
  • ✅ Jump and subroutine calls
  • ✅ Load/store operations
  • ✅ Arithmetic and logical operations
  • ✅ Shifts and rotates
  • ✅ Increment/decrement operations

Running the Tests

Basic test (exits on success or failure):

cd build
./klaus_functional_test

Debug test (includes execution trace and detailed failure information):

cd build
./klaus_debug_test

The debug version provides:

  • Instruction-by-instruction execution history (last 50 instructions)
  • Complete CPU state dump on failure
  • Status flag decoding
  • Memory read/write statistics
  • Direct reference to the listing file for debugging

Success Criteria

The test passes when the CPU reaches address $3469 and enters the success loop. The test will execute approximately 30 million instructions before reaching success.

Debugging Failed Tests

If a test fails:

  1. Note the PC address where it got stuck
  2. Look up that address in tests/functional_tests/bin_files/6502_functional_test.lst
  3. The listing shows which instruction was being tested and what was expected
  4. The debug test shows the last 50 instructions executed before failure

See tests/functional_tests/TEST_GUIDE.md for comprehensive testing documentation.

Custom Examples

Run the included examples:

cd build
./simple_example
./comprehensive_test

License

MIT License - See LICENSE file for details

References

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors