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.
- 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)
- C++20 compatible compiler (GCC 10+, Clang 12+, MSVC 2019+)
- CMake 3.20 or higher
mkdir build
cd build
cmake ..
cmake --build .BUILD_EXAMPLES: Build example programs (default: ON)BUILD_TESTS: Build test suite (default: ON)
cmake -DCMAKE_BUILD_TYPE=Release ..
cmake --build .#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;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.)
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
✅ 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
See the examples/ directory for:
simple_example.cpp- Basic usage demonstrationcomprehensive_test.cpp- Tests for all instruction categories
// 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);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.
- ✅ 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
Basic test (exits on success or failure):
cd build
./klaus_functional_testDebug test (includes execution trace and detailed failure information):
cd build
./klaus_debug_testThe 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
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.
If a test fails:
- Note the PC address where it got stuck
- Look up that address in
tests/functional_tests/bin_files/6502_functional_test.lst - The listing shows which instruction was being tested and what was expected
- The debug test shows the last 50 instructions executed before failure
See tests/functional_tests/TEST_GUIDE.md for comprehensive testing documentation.
Run the included examples:
cd build
./simple_example
./comprehensive_testMIT License - See LICENSE file for details
- 6502.org - 6502 documentation and resources
- Visual 6502 - Transistor-level 6502 simulation
- NESdev Wiki - 6502 opcode reference