MOS 6502 CPU Reference

Overview

The MOS 6502 is an 8-bit microprocessor that was widely used in home computers and gaming consoles during the 1970s and 1980s. This implementation provides a reusable, generic 6502 CPU core that can be used by any system (NES, Atari 2600, Apple II, etc.) by implementing the Memory6502 trait.

Implementation: crates/core/src/cpu_6502.rs

Architecture

Registers

  • A (Accumulator): 8-bit general-purpose register for arithmetic and logic operations
  • X (Index Register X): 8-bit index register for indexed addressing modes
  • Y (Index Register Y): 8-bit index register for indexed addressing modes
  • SP (Stack Pointer): 8-bit pointer to stack (points to 0x0100 + SP)
  • PC (Program Counter): 16-bit pointer to current instruction
  • Status: 8-bit processor status register (flags: NV-BDIZC)

Status Register Flags

NV-BDIZC
││ ││││└─ Carry flag
││ │││└── Zero flag
││ ││└─── IRQ Disable flag
││ │└──── Decimal mode flag (not used on NES)
││ └───── Break flag
│└────── (unused, always 1)
└─────── Overflow flag
  N────── Negative flag

Memory Map

  • $0000-$00FF: Zero Page (fast access)
  • $0100-$01FF: Stack (grows downward from $01FF)
  • $0200-$FFFF: General memory (system-specific)

Interrupt Vectors

  • $FFFA-$FFFB: NMI vector
  • $FFFC-$FFFD: RESET vector
  • $FFFE-$FFFF: IRQ/BRK vector

Usage

Systems using the 6502 must implement the Memory6502 trait:

pub trait Memory6502 {
    fn read(&self, addr: u16) -> u8;
    fn write(&mut self, addr: u16, val: u8);
}

Example

use emu_core::cpu_6502::{Cpu6502, Memory6502};

// Implement Memory6502 for your system
struct MySystem {
    ram: [u8; 65536],
}

impl Memory6502 for MySystem {
    fn read(&self, addr: u16) -> u8 {
        self.ram[addr as usize]
    }
    
    fn write(&mut self, addr: u16, val: u8) {
        self.ram[addr as usize] = val;
    }
}

// Create and use CPU
let system = MySystem { ram: [0; 65536] };
let mut cpu = Cpu6502::new(system);
cpu.reset();
cpu.step();

Instruction Set

The 6502 has 56 instructions organized into the following categories:

Data Transfer

  • LDA: Load Accumulator
  • LDX: Load X Register
  • LDY: Load Y Register
  • STA: Store Accumulator
  • STX: Store X Register
  • STY: Store Y Register
  • TAX: Transfer A to X
  • TAY: Transfer A to Y
  • TXA: Transfer X to A
  • TYA: Transfer Y to A
  • TSX: Transfer SP to X
  • TXS: Transfer X to SP

Arithmetic

  • ADC: Add with Carry
  • SBC: Subtract with Carry
  • INC: Increment Memory
  • INX: Increment X
  • INY: Increment Y
  • DEC: Decrement Memory
  • DEX: Decrement X
  • DEY: Decrement Y

Logical

  • AND: Logical AND
  • ORA: Logical OR
  • EOR: Exclusive OR
  • BIT: Bit Test

Shift/Rotate

  • ASL: Arithmetic Shift Left
  • LSR: Logical Shift Right
  • ROL: Rotate Left
  • ROR: Rotate Right

Comparison

  • CMP: Compare Accumulator
  • CPX: Compare X Register
  • CPY: Compare Y Register

Branches

  • BCC: Branch if Carry Clear
  • BCS: Branch if Carry Set
  • BEQ: Branch if Equal (Zero Set)
  • BNE: Branch if Not Equal (Zero Clear)
  • BMI: Branch if Minus (Negative Set)
  • BPL: Branch if Plus (Negative Clear)
  • BVC: Branch if Overflow Clear
  • BVS: Branch if Overflow Set

Jumps/Subroutines

  • JMP: Jump
  • JSR: Jump to Subroutine
  • RTS: Return from Subroutine

Stack

  • PHA: Push Accumulator
  • PHP: Push Processor Status
  • PLA: Pull Accumulator
  • PLP: Pull Processor Status

Status Flags

  • CLC: Clear Carry
  • CLD: Clear Decimal
  • CLI: Clear IRQ Disable
  • CLV: Clear Overflow
  • SEC: Set Carry
  • SED: Set Decimal
  • SEI: Set IRQ Disable

Interrupts

  • BRK: Force Break
  • RTI: Return from Interrupt

Other

  • NOP: No Operation

Addressing Modes

The 6502 supports 13 addressing modes:

  1. Implied: Instruction operates on register (e.g., TAX)
  2. Accumulator: Operates on accumulator (e.g., ASL A)
  3. Immediate: Operand is next byte (e.g., LDA #$42)
  4. Zero Page: Address in zero page (e.g., LDA $42)
  5. Zero Page,X: Zero page indexed by X (e.g., LDA $42,X)
  6. Zero Page,Y: Zero page indexed by Y (e.g., LDX $42,Y)
  7. Absolute: 16-bit address (e.g., LDA $4200)
  8. Absolute,X: Absolute indexed by X (e.g., LDA $4200,X)
  9. Absolute,Y: Absolute indexed by Y (e.g., LDA $4200,Y)
  10. Indirect: Only for JMP (e.g., JMP ($4200))
  11. Indexed Indirect: (Zero Page,X) (e.g., LDA ($42,X))
  12. Indirect Indexed: (Zero Page),Y (e.g., LDA ($42),Y)
  13. Relative: For branch instructions (signed 8-bit offset)

Systems Using 6502

This CPU core is used by the following systems in Hemulator:

  • NES (Nintendo Entertainment System) - crates/systems/nes/
  • Atari 2600 (6507 variant) - crates/systems/atari2600/

Implementation Notes

Cycle Accuracy

The implementation tracks cycle counts for all instructions. Some instructions take additional cycles based on:

  • Page boundary crossings (certain addressing modes)
  • Branch taken vs. not taken
  • Specific timing requirements

Undocumented Instructions

The current implementation focuses on official/documented 6502 instructions. Undocumented opcodes may be added as needed for specific system compatibility.

Decimal Mode

The Decimal (D) flag is implemented but may not be used by all systems. For example, the NES's 6502 variant (RP2A03) has the decimal mode circuitry disabled.

References