A fully functional 6502 microprocessor emulator written entirely in PHP, packaged as a Composer library for building custom 6502-based systems. Features two CPU cores (NMOS 6502 and WDC 65C02) that can be paired with different system implementations.
Install via Composer:
composer require andrewthecoder/6502-emulator- MOS 6502 - Original MOS Technology 6502 with 56 documented instructions and illegal opcodes
- WDC 65C02 - Western Design Center 65C02 with 70 instructions including BRA, STZ, PHX/PHY, bit manipulation, and more
- Hybrid execution model combining JSON-driven and custom handler-based instruction processing
- Interrupt support (NMI, IRQ, RESET) with proper edge/level triggering
- CPU monitoring for debugging and profiling with instruction tracing and cycle counting
- Comprehensive PHPDoc documentation for IDE support
This library provides two different 6502 CPU implementations:
The MOS 6502 refers to the original microprocessor designed by MOS Technology in 1975. It uses NMOS (N-channel Metal-Oxide-Semiconductor) fabrication technology. While you may see it referred to as "NMOS 6502" in technical documentation, the official product name is simply MOS 6502 or 6502.
Characteristics:
- 56 documented instructions
- Includes illegal/undocumented opcodes used by some software
- Hardware bugs (JMP indirect page boundary bug, decimal mode doesn't set N/V/Z flags)
- Found in: Commodore 64, Apple II/II+, NES, Atari 2600/5200/7800
The WDC 65C02 (or simply 65C02) is an enhanced version designed by Western Design Center using CMOS (Complementary Metal-Oxide-Semiconductor) fabrication technology. The "C" in the name literally stands for CMOS. This version fixed bugs from the original and added new instructions.
Characteristics:
- 70 instructions (14 additional instruction types)
- Fixed hardware bugs from the original
- Better power efficiency (CMOS technology)
- New addressing modes and bit manipulation instructions
- Found in: Apple IIc/IIe, later Commodore models, modern 6502-based systems
Use andrewthecoder\MOS6502\CPU when:
- Emulating systems that require the original chip (NES, C64, original Apple II)
- You need illegal opcodes for compatibility
- You want hardware-accurate behavior including bugs
Use andrewthecoder\WDC65C02\CPU when:
- Building modern 6502-based projects
- You want the additional instructions (BRA, STZ, etc.)
- You need the bug fixes (JMP indirect, decimal mode)
- Emulating systems that use the 65C02 (Apple IIe, IIc)
<?php
require 'vendor/autoload.php';
// Import core interfaces (shared by both CPUs)
use andrewthecoder\Core\BusInterface;
use andrewthecoder\Core\StatusRegister;
// Choose your CPU variant:
// For MOS 6502 (original with illegal opcodes):
use andrewthecoder\MOS6502\CPU;
// OR for WDC 65C02 (enhanced with additional instructions):
// use andrewthecoder\WDC65C02\CPU;
// Implement a simple bus with 64KB RAM
class SimpleBus implements BusInterface
{
private array $memory = [];
public function read(int $address): int
{
return $this->memory[$address & 0xFFFF] ?? 0;
}
public function write(int $address, int $value): void
{
$this->memory[$address & 0xFFFF] = $value & 0xFF;
}
public function readWord(int $address): int
{
$low = $this->read($address);
$high = $this->read($address + 1);
return ($high << 8) | $low;
}
public function tick(): void
{
// Called after each CPU cycle
}
}
// Create CPU with your bus
$bus = new SimpleBus();
$cpu = new CPU($bus);
// Load a simple program: LDA #$42, STA $00
$bus->write(0x8000, 0xA9); // LDA #$42
$bus->write(0x8001, 0x42);
$bus->write(0x8002, 0x85); // STA $00
$bus->write(0x8003, 0x00);
// Set reset vector
$bus->write(0xFFFC, 0x00);
$bus->write(0xFFFD, 0x80);
// Run
$cpu->reset();
$cpu->executeInstruction();
$cpu->executeInstruction();
echo sprintf("Value at $00: 0x%02X\n", $bus->read(0x00)); // 0x42For developing this library itself or running the included examples:
-
Clone the repository:
git clone https://github.com/your-username/6502-Emulator.git cd 6502-Emulator -
Install dependencies:
composer install
-
Optional: Install cc65 toolchain for assembling 6502 programs: Download from official cc65 website and place
ca65andld65inbin/
The emulator uses a modular, reusable architecture with three main namespaces:
andrewthecoder\Core - Shared interfaces and components:
- BusInterface - Contract for system buses (memory-mapped I/O)
- RAMInterface - Contract for RAM implementations
- ROMInterface - Contract for ROM implementations
- PeripheralInterface - Contract for memory-mapped peripherals
- StatusRegister - CPU status flags (NV-BDIZC)
- Opcode - Opcode metadata container
andrewthecoder\MOS6502 - MOS 6502 (Original):
- CPU - MOS Technology 6502 with 56 documented instructions
- Includes illegal/undocumented opcodes
- Hardware-accurate bugs (JMP indirect page boundary, decimal mode flags)
- InstructionRegister - MOS 6502 opcode definitions
- InstructionInterpreter - JSON-driven instruction execution
- CPUMonitor - Optional debugging tool
- Instructions/ - Complex instruction handlers (ADC/SBC, branches, etc.)
andrewthecoder\WDC65C02 - WDC 65C02 (Enhanced):
- CPU - WDC 65C02S with 70 instructions
- Additional instructions: BRA, STZ, PHX/PHY/PLX/PLY, TRB/TSB, WAI/STP, bit manipulation
- Bug fixes (JMP indirect, decimal mode)
- InstructionRegister - WDC 65C02 opcode definitions
- InstructionInterpreter - JSON-driven instruction execution
- CPUMonitor - Optional debugging tool
- Instructions/ - Complex instruction handlers including CMOS-specific opcodes
To create a custom 6502 system:
- Install the package via Composer
- Choose your CPU variant (MOS6502 or WDC65C02)
- Implement
BusInterfacewith your desired memory map - Optionally implement
RAMInterface,ROMInterface, andPeripheralInterface - Instantiate
CPUwith your bus
See docs/CPU_CORE_ARCHITECTURE.md for detailed instructions and examples.
The project uses PHPUnit for unit testing. To run the test suite:
./vendor/bin/phpunitPHPStan is used for static analysis. To check the codebase:
./vendor/bin/phpstan analyse src- Comprehensive PHPDoc - All public methods and classes are fully documented
- Type Safety - Strict typing throughout with detailed array type annotations
- Test Coverage - 56 tests covering CPU operations, addressing modes, and peripherals (coming soon)
src/
├── Core/ # andrewthecoder\Core - Shared components
│ ├── BusInterface.php # Bus contract
│ ├── RAMInterface.php # RAM contract
│ ├── ROMInterface.php # ROM contract
│ ├── PeripheralInterface.php # Peripheral contract
│ ├── StatusRegister.php # CPU status flags
│ └── Opcode.php # Opcode metadata
│
├── MOS6502/ # andrewthecoder\MOS6502 - MOS 6502 CPU
│ ├── CPU.php # Main MOS 6502 CPU
│ ├── InstructionRegister.php # MOS 6502 opcode registry
│ ├── InstructionInterpreter.php # JSON-driven execution
│ ├── CPUMonitor.php # Debugging tool
│ ├── opcodes.json # MOS 6502 opcodes
│ └── Instructions/ # Instruction handlers
│ ├── Arithmetic.php # ADC, SBC
│ ├── IllegalOpcodes.php # Undocumented opcodes
│ └── ...
│
├── WDC65C02/ # andrewthecoder\WDC65C02 - WDC 65C02 CPU
│ ├── CPU.php # Main WDC 65C02 CPU
│ ├── InstructionRegister.php # WDC 65C02 opcode registry
│ ├── InstructionInterpreter.php # JSON-driven execution
│ ├── CPUMonitor.php # Debugging tool
│ ├── opcodes.json # WDC 65C02 opcodes
│ └── Instructions/ # Instruction handlers
│ ├── CMOS65C02.php # CMOS-specific opcodes
│ └── ...
│
After installing via Composer, you can use the CPU core to build any 6502-based system:
<?php
use andrewthecoder\Core\BusInterface;
use andrewthecoder\MOS6502\CPU;
class MyBus implements BusInterface {
private array $ram = [];
public function read(int $address): int {
return $this->ram[$address & 0xFFFF] ?? 0;
}
public function write(int $address, int $value): void {
$this->ram[$address & 0xFFFF] = $value & 0xFF;
}
public function readWord(int $address): int {
return $this->read($address) | ($this->read($address + 1) << 8);
}
public function tick(): void {
// Update peripherals, check interrupts, etc.
}
}
$cpu = new CPU(new MyBus());
$cpu->reset();<?php
use andrewthecoder\Core\BusInterface;
use andrewthecoder\Core\PeripheralInterface;
class MyBus implements BusInterface {
private RAM $ram;
private ROM $rom;
private array $peripherals = [];
public function addPeripheral(PeripheralInterface $peripheral): void {
$this->peripherals[] = $peripheral;
}
public function read(int $address): int {
// Check peripherals first
foreach ($this->peripherals as $peripheral) {
if ($peripheral->handlesAddress($address)) {
return $peripheral->read($address);
}
}
// Fall through to RAM/ROM
return $address < 0x8000
? $this->ram->read($address)
: $this->rom->read($address);
}
// ... write(), readWord(), tick() implementations
}Contributions are welcome! The modular architecture makes it easy to:
- Add new 6502-based systems (see
docs/CPU_CORE_ARCHITECTURE.md) - Implement additional peripherals
- Improve emulation accuracy
- Add more test coverage
- Enhance the hybrid JSON/PHP execution model
This project is licensed under the MIT License - see the LICENSE file for details.
- 6502.org - For comprehensive 6502 documentation
- cc65 project - For the assembler and toolchain
- Claude Code - Documentation, README, code comments, and PHPDocs were generated and refined using Claude Code by Anthropic