cpu6502

package module
v0.0.4 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Nov 12, 2025 License: MIT Imports: 2 Imported by: 0

README

Sixty502 - 6502 CPU Emulator

A comprehensive and accurate 6502 microprocessor emulator written in Go. This library provides a cycle-accurate implementation of the MOS Technology 6502 CPU, including all official instructions, addressing modes, and many unofficial/illegal opcodes.

Features

Core CPU Implementation
  • Multiple CPU Variants: Support for NMOS 6502 (Rev A and Rev B+), CMOS 65C02, and Ricoh 2A03/2A07 (NES) variants
  • Complete 6502 Instruction Set: All 151 official instructions implemented
  • All Addressing Modes: Immediate, Zero Page, Absolute, Indexed, Indirect, and Relative addressing
  • Unofficial Opcodes: Support for many undocumented/illegal 6502 instructions
  • Cycle-Accurate Timing: Precise cycle counting including page boundary crossing penalties
  • Status Flags: Full implementation of all processor status flags (N, V, U, B, D, I, Z, C)
  • Variant-Specific Decimal Mode: Accurate BCD arithmetic with variant-specific behavior
  • Interrupt Handling: IRQ, NMI, and BRK interrupt support with proper vector handling
  • Hardware Bug Emulation: Accurate emulation of historical hardware quirks:
    • Indirect JMP page boundary bug (NMOS variants)
    • ROR instruction quirk on Rev A (behaves like ASL)
Architecture
  • Bus Interface: Clean separation between CPU and memory through a Bus interface
  • Method-Based Design: Uses Go method expressions for efficient instruction dispatch
  • Comprehensive Testing: Extensive test suite covering all instructions and edge cases
Debugging & Development Tools
  • Disassembler: Built-in disassembly functionality for code analysis
  • State Inspection: Methods to examine CPU registers, flags, and execution state
  • Cycle Counting: Total cycle tracking for performance analysis
  • Instruction Lookup: Access to the complete instruction table for tooling

Installation

go get github.com/drewwalton19216801/sixty502

Quick Start

package main

import (
    "fmt"
    "github.com/drewwalton19216801/sixty502"
)

// Simple memory implementation
type SimpleBus struct {
    ram [65536]uint8
}

func (b *SimpleBus) Read(addr uint16) uint8 {
    return b.ram[addr]
}

func (b *SimpleBus) Write(addr uint16, data uint8) {
    b.ram[addr] = data
}

func main() {
    // Create bus and CPU (defaults to NMOS 6502)
    bus := &SimpleBus{}
    cpu := cpu6502.NewCPU(bus)
    
    // Load a simple program: LDA #$42, STA $0200, BRK
    bus.Write(0x8000, 0xA9) // LDA immediate
    bus.Write(0x8001, 0x42) // Value $42
    bus.Write(0x8002, 0x8D) // STA absolute
    bus.Write(0x8003, 0x00) // Low byte of address
    bus.Write(0x8004, 0x02) // High byte of address
    bus.Write(0x8005, 0x00) // BRK
    
    // Set reset vector
    bus.Write(0xFFFC, 0x00) // Reset vector low
    bus.Write(0xFFFD, 0x80) // Reset vector high
    
    // Reset and run
    cpu.Reset()
    
    // Execute until instruction completes
    for cpu.RemainingCycles() > 0 {
        cpu.Clock()
    }
    
    // Inspect CPU state
    state := cpu.GetStateSnapshot()
    fmt.Printf("CPU State: %s\n", state)
    fmt.Printf("Value at $0200: $%02X\n", bus.Read(0x0200))
}

Architecture Overview

Bus Interface

The CPU communicates with memory through a simple Bus interface:

type Bus interface {
    Read(addr uint16) uint8
    Write(addr uint16, data uint8)
}

This design allows for flexible memory implementations, from simple RAM to complex memory-mapped I/O systems.

Instruction System

Instructions are defined using method expressions for efficient dispatch:

type Instruction struct {
    Name             string           // Mnemonic (e.g., "LDA")
    Operate          func(*CPU) uint8 // Function to execute the instruction's logic (accepts *CPU)
    AddrMode         func(*CPU) uint8 // Function to calculate the address and fetch data (accepts *CPU)
    AddrModeType     AddrModeType     // Type of addressing mode
    Cycles           uint8            // Base cycles for this instruction/mode
    Length           uint8            // Length of the instruction in bytes
    Illegal          bool             // Whether this is an official or unofficial/illegal opcode
    PageCrossPenalty bool             // Whether to add +1 cycle on page boundary cross
}
Addressing Modes

All 13 addressing modes are implemented:

  • IMP: Implied (no operand)
  • IMM: Immediate (#value)
  • ZP0: Zero Page ($00-FF)
  • ZPX: Zero Page, X indexed
  • ZPY: Zero Page, Y indexed
  • REL: Relative (for branches)
  • ABS: Absolute ($0000-FFFF)
  • ABX: Absolute, X indexed
  • ABY: Absolute, Y indexed
  • IND: Indirect (for JMP)
  • IZX: Indirect, X indexed
  • IZY: Indirect, Y indexed

Testing

The library includes comprehensive tests covering:

  • All official instructions and addressing modes
  • Flag behavior and edge cases
  • Cycle accuracy including page boundary crossing
  • Decimal mode arithmetic
  • Interrupt handling
  • Unofficial opcodes
  • Stack operations
  • Branch instructions

Run the test suite:

go test -v

Examples

The repository includes working examples demonstrating various features:

Running the Examples
# Basic example - simple program execution
cd examples/basic
go run main.go

# Memory-mapped I/O example
cd examples/memory-mapped
go run main.go
Available Examples
  • examples/basic/ - Demonstrates basic CPU usage with a simple counting program

    • Shows how to load a program into memory
    • Demonstrates setting up reset vectors
    • Shows state inspection and cycle counting
  • examples/memory-mapped/ - Demonstrates memory-mapped I/O

    • Shows ROM/RAM/IO memory separation
    • Demonstrates writing to memory-mapped I/O ports
    • Example outputs "HELLO" via I/O port
Creating Your Own Examples

Use the examples as templates for your own programs:

// 1. Implement the Bus interface
type MyBus struct {
    ram [65536]uint8
}

func (b *MyBus) Read(addr uint16) uint8 { return b.ram[addr] }
func (b *MyBus) Write(addr uint16, data uint8) { b.ram[addr] = data }

// 2. Create CPU and load program
bus := &MyBus{}
cpu := cpu6502.NewCPU(bus)

// 3. Set reset vector and reset CPU
bus.Write(0xFFFC, 0x00)
bus.Write(0xFFFD, 0x80)
cpu.Reset()

// 4. Execute
for cpu.RemainingCycles() > 0 {
    cpu.Clock()
}

Advanced Features

CPU Configuration

Multiple ways to create and configure a CPU instance:

// Simple creation with defaults (NMOS 6502)
cpu := cpu6502.NewCPU(bus)

// Specify variant
cpu := cpu6502.NewCPUWithVariant(bus, cpu6502.VariantRicoh2A03)

// Full configuration
config := cpu6502.DefaultConfig()
config.Variant = cpu6502.VariantCMOS65C02
config.StrictMode = true  // Halt on illegal opcodes
config.EnableDecimalMode = false
cpu := cpu6502.NewCPUWithConfig(bus, config)

// Builder pattern for fluent configuration
cpu := cpu6502.NewBuilder(bus).
    WithVariant(cpu6502.VariantCMOS65C02).
    WithStrictMode().
    DisableDecimalMode().
    Build()
CPU Variants

The emulator supports multiple 6502 variants with accurate behavior differences:

// NMOS 6502 (Rev B+) - Original chip with documented bugs (ROR works correctly)
cpu := cpu6502.NewCPUWithVariant(bus, cpu6502.VariantNMOS6502)

// NMOS 6502 Rev A - Early revision with ROR hardware quirk
cpu := cpu6502.NewCPUWithVariant(bus, cpu6502.VariantNMOS6502RevA)

// CMOS 65C02 - Enhanced version with bug fixes
cpu := cpu6502.NewCPUWithVariant(bus, cpu6502.VariantCMOS65C02)

// Ricoh 2A03 - NES/Famicom CPU (no decimal mode)
cpu := cpu6502.NewCPUWithVariant(bus, cpu6502.VariantRicoh2A03)

// Ricoh 2A07 - PAL NES CPU (no decimal mode)
cpu := cpu6502.NewCPUWithVariant(bus, cpu6502.VariantRicoh2A07)

// Check variant capabilities
if cpu.Variant().SupportsDecimalMode() {
    // Decimal mode available
}
if cpu.Variant().HasIndirectJMPBug() {
    // Has page boundary bug in JMP ($xxFF)
}
if cpu.Variant().HasRORQuirk() {
    // ROR behaves like ASL (Rev A only)
}
Decimal Mode

Variant-specific BCD (Binary Coded Decimal) arithmetic:

// Only works on variants that support decimal mode (NMOS, CMOS)
cpu.setFlag(cpu6502.D, true) // Enable decimal mode
// ADC and SBC will now perform BCD arithmetic
// Ricoh variants ignore the D flag and always use binary mode
Disassembly

Built-in disassembler for code analysis:

disassembly := cpu.Disassemble(0x8000, 0x8010)
for addr, line := range disassembly {
    fmt.Printf("$%04X: %s\n", addr, line)
}
State Inspection

Comprehensive state inspection with accessor methods:

// Get complete state snapshot
state := cpu.GetStateSnapshot()
fmt.Println(state)  // Human-readable output

// Access individual state components
fmt.Printf("PC: $%04X\n", state.PC)
fmt.Printf("A: $%02X X: $%02X Y: $%02X\n", state.A, state.X, state.Y)
fmt.Printf("Flags: %s\n", cpu6502.FormatFlags(state.P))

// Use accessor methods
fmt.Printf("Total cycles: %d\n", cpu.TotalCycles())
fmt.Printf("Remaining cycles: %d\n", cpu.RemainingCycles())
fmt.Printf("Current opcode: $%02X\n", cpu.CurrentOpcode())

// Check instruction legality
if cpu.IsIllegalOpcode(0x02) {
    fmt.Println("Opcode $02 is illegal")
}

Advanced Usage

Custom Memory Mapping

Implement complex memory systems with memory-mapped I/O:

type MemoryMappedBus struct {
    ram [0x8000]uint8  // RAM: $0000-$7FFF
    rom [0x8000]uint8  // ROM: $8000-$FFFF
    ioPort uint8       // Memory-mapped I/O at $6000
}

func (b *MemoryMappedBus) Read(addr uint16) uint8 {
    switch {
    case addr < 0x6000:
        return b.ram[addr]
    case addr == 0x6000:
        // Memory-mapped I/O read
        return b.ioPort
    case addr < 0x8000:
        return b.ram[addr]
    default:
        // ROM area
        return b.rom[addr-0x8000]
    }
}

func (b *MemoryMappedBus) Write(addr uint16, data uint8) {
    switch {
    case addr < 0x6000:
        b.ram[addr] = data
    case addr == 0x6000:
        // Memory-mapped I/O write
        b.ioPort = data
        fmt.Printf("I/O Port write: $%02X\n", data)
    case addr < 0x8000:
        b.ram[addr] = data
    default:
        // ROM writes are ignored
    }
}
Interrupt Handling

Handle IRQ and NMI interrupts with proper timing:

// Level-triggered IRQ
cpu.SetIRQ(true)  // Assert IRQ line
for i := 0; i < 100; i++ {
    cpu.Clock()
}
cpu.SetIRQ(false) // Clear IRQ line

// Edge-triggered NMI (falling edge)
cpu.SetNMI(true)   // Set NMI line high
cpu.SetNMI(false)  // Falling edge triggers NMI

// Check for pending interrupts
if cpu.HasPendingInterrupt() {
    fmt.Println("Interrupt pending")
}

// Set interrupt vectors in memory
bus.Write(0xFFFA, 0x00) // NMI vector low
bus.Write(0xFFFB, 0xF0) // NMI vector high -> $F000
bus.Write(0xFFFE, 0x00) // IRQ vector low
bus.Write(0xFFFF, 0xF2) // IRQ vector high -> $F200
Error Handling

Configure how the CPU handles errors:

// Strict mode - halt on illegal opcodes
cpu := cpu6502.NewBuilder(bus).
    WithStrictMode().
    Build()

for {
    if err := cpu.Clock(); err != nil {
        fmt.Printf("Execution halted: %v\n", err)
        break
    }
}

// Custom error handler
type CustomErrorHandler struct{}

func (h *CustomErrorHandler) HandleError(err *cpu6502.CPUError) error {
    switch err.Type {
    case cpu6502.ErrorIllegalOpcode:
        fmt.Printf("Illegal opcode $%02X at $%04X\n", err.Opcode, err.PC)
        return nil // Continue execution
    default:
        return err // Halt on other errors
    }
}

cpu := cpu6502.NewBuilder(bus).
    WithErrorHandler(&CustomErrorHandler{}).
    Build()
Performance Monitoring

Track execution statistics and optimize performance:

// Track execution time
startCycles := cpu.TotalCycles()
runProgram(cpu)
endCycles := cpu.TotalCycles()
fmt.Printf("Executed %d cycles\n", endCycles - startCycles)

// Monitor instruction cache performance
hits, misses, hitRate := cpu.InstructionCacheStats()
fmt.Printf("Cache: %.2f%% hit rate (%d hits, %d misses)\n",
    hitRate*100, hits, misses)

// Invalidate cache after self-modifying code
bus.Write(0x8000, 0xEA) // Modify code
cpu.InvalidateInstructionCache()

// Profile instruction execution
type InstructionProfiler struct {
    counts map[string]int
}

func (p *InstructionProfiler) Profile(cpu *cpu6502.CPU) {
    instr := cpu.GetCurrentInstruction()
    if instr != nil {
        p.counts[instr.Name]++
    }
}

Performance

The emulator is designed for accuracy over raw speed, but still provides good performance:

  • Cycle-accurate timing
  • Efficient instruction dispatch using method expressions
  • Minimal memory allocations during execution
  • Suitable for real-time emulation of 6502-based systems
Instruction Cache

An optional instruction cache optimizes repeated instruction fetches in tight loops:

// Cache is enabled by default
cpu := cpu6502.NewCPU(bus)

// Disable cache via configuration
config := cpu6502.DefaultConfig()
config.EnableInstructionCache = false
cpu := cpu6502.NewCPUWithConfig(bus, config)

// Or via builder
cpu := cpu6502.NewBuilder(bus).
    DisableInstructionCache().
    Build()

// Runtime control
cpu.DisableInstructionCache()
cpu.EnableInstructionCache()
cpu.InvalidateInstructionCache() // Clear cache after self-modifying code

// Get cache statistics
hits, misses, hitRate := cpu.InstructionCacheStats()
fmt.Printf("Cache hit rate: %.2f%%\n", hitRate*100)

The cache provides:

  • High hit rates on tight loops (90%+ typical)
  • Direct-mapped design with 256 entries
  • Transparent operation - no behavioral changes
  • Statistics tracking for performance analysis
  • Invalidation support for self-modifying code

Compatibility

This emulator accurately emulates multiple 6502 variants:

  • NMOS 6502 (Rev B+) - Original MOS Technology chip (Apple II, Commodore 64, Atari 8-bit)
    • Supports decimal mode with NMOS-specific N/V flag behavior
    • Includes the indirect JMP page boundary bug
    • ROR instruction works correctly
  • NMOS 6502 Rev A - Early revision (rare in production systems)
    • Supports decimal mode with NMOS-specific N/V flag behavior
    • Includes the indirect JMP page boundary bug
    • ROR instruction hardware quirk: ROR behaves like ASL (shifts left, doesn't update carry)
  • CMOS 65C02 - Enhanced Western Design Center version (Apple IIc, IIe enhanced)
    • Supports decimal mode with improved N/V flag behavior
    • Fixes the indirect JMP page boundary bug
    • All instructions work correctly
  • Ricoh 2A03 - NES/Famicom CPU (NTSC)
    • Decimal mode disabled (D flag ignored)
    • Includes the indirect JMP page boundary bug
    • ROR instruction works correctly
  • Ricoh 2A07 - PAL NES CPU
    • Decimal mode disabled (D flag ignored)
    • Includes the indirect JMP page boundary bug
    • ROR instruction works correctly

Contributing

Contributions are welcome! Areas for improvement include:

  • Additional unofficial opcode implementations
  • Performance optimizations
  • More comprehensive test cases
  • Documentation improvements
  • Example programs and demos

License

This project is licensed under the MIT License - see the LICENSE file for details.

Acknowledgments

This emulator is based on extensive research of the 6502 architecture and behavior. Special thanks to the 6502 community for documentation and testing resources.

Documentation

Overview

Package cpu6502 provides a cycle-accurate emulator for the MOS Technology 6502 microprocessor and its variants.

The 6502 is an 8-bit microprocessor that was widely used in home computers and game consoles during the 1970s and 1980s, including the Apple II, Commodore 64, and Atari 2600.

This implementation supports:

  • All 151 official 6502 instructions
  • Multiple CPU variants (NMOS 6502, CMOS 65C02, Ricoh 2A03)
  • Cycle-accurate timing including page boundary crossing
  • Decimal mode (BCD) arithmetic
  • Interrupt handling (IRQ, NMI, BRK)
  • Many unofficial/illegal opcodes

Basic usage:

bus := &SimpleBus{}
cpu := cpu6502.NewCPU(bus)
cpu.Reset()
for cpu.RemainingCycles() > 0 {
    if err := cpu.Clock(); err != nil {
        log.Fatal(err)
    }
}

Package cpu6502 provides type definitions for the 6502 CPU emulator.

This file contains all core type definitions, constants, and enums used throughout the emulator. Having types in a dedicated file provides:

  • A single source of truth for all data structures
  • Easy reference for developers
  • Better documentation organization
  • Reduced clutter in the main CPU implementation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func FormatFlags

func FormatFlags(p Flags) string

FormatFlags returns a human-readable string representation of processor flags.

The format is "NVUBDIZC" where each letter represents a flag:

  • N: Negative
  • V: Overflow
  • U: Unused (always 1)
  • B: Break
  • D: Decimal
  • I: Interrupt Disable
  • Z: Zero
  • C: Carry

Set flags are shown as their letter, cleared flags as '.'

Example: "N.U.D.Z." means N, U, D, and Z flags are set

Types

type AddrModeType

type AddrModeType uint8

AddrModeType represents the addressing mode of an instruction

const (
	AddrModeIMP AddrModeType = iota // Implied
	AddrModeIMM                     // Immediate
	AddrModeZP0                     // Zero Page
	AddrModeZPX                     // Zero Page, X
	AddrModeZPY                     // Zero Page, Y
	AddrModeREL                     // Relative
	AddrModeABS                     // Absolute
	AddrModeABX                     // Absolute, X
	AddrModeABY                     // Absolute, Y
	AddrModeIND                     // Indirect
	AddrModeIZX                     // Indexed Indirect
	AddrModeIZY                     // Indirect Indexed
)

func (AddrModeType) String

func (a AddrModeType) String() string

String returns the addressing mode name for debugging

type Bus

type Bus interface {
	// Read returns the byte at the specified address.
	//
	// The address space is 16-bit (0x0000-0xFFFF). Implementations
	// must handle all possible addresses, even if they map to the
	// same physical memory or return constant values.
	//
	// For memory-mapped I/O, reads may have side effects (e.g.,
	// clearing interrupt flags, advancing hardware state).
	Read(addr uint16) uint8

	// Write stores a byte at the specified address.
	//
	// The address space is 16-bit (0x0000-0xFFFF). Implementations
	// may ignore writes to read-only regions (ROM) or trigger
	// hardware behavior for memory-mapped I/O addresses.
	//
	// For memory-mapped I/O, writes may trigger immediate hardware
	// actions (e.g., sending data to a device, updating graphics).
	Write(addr uint16, data uint8)
}

Bus defines the interface for memory access.

Implementations of this interface provide the CPU with access to memory and memory-mapped I/O. The interface is intentionally simple to allow for flexible implementations.

Memory Mapping Patterns

The Bus interface supports various memory mapping strategies:

  • Simple RAM: Direct array access for the full 64KB address space
  • Banked Memory: Multiple memory banks switched via control registers
  • Memory-Mapped I/O: Special addresses that trigger hardware behavior
  • ROM/RAM Combinations: Read-only regions mixed with writable RAM

Implementation Guidelines

Implementations should:

  • Handle all 64KB addresses (0x0000-0xFFFF)
  • Return consistent values for reads (no side effects unless intended)
  • Complete operations quickly (the CPU expects cycle-accurate timing)
  • Consider thread safety if used in concurrent contexts

Example Implementation

Simple 64KB RAM:

type SimpleBus struct {
    ram [65536]uint8
}

func (b *SimpleBus) Read(addr uint16) uint8 {
    return b.ram[addr]
}

func (b *SimpleBus) Write(addr uint16, data uint8) {
    b.ram[addr] = data
}

Memory-mapped I/O example:

type IOBus struct {
    ram    [65536]uint8
    output io.Writer
}

func (b *IOBus) Write(addr uint16, data uint8) {
    if addr == 0x6000 {
        // Write to output device
        b.output.Write([]byte{data})
    } else {
        b.ram[addr] = data
    }
}

type CPU

type CPU struct {
	// Registers (public for direct access)
	A  uint8  // Accumulator
	X  uint8  // X Index Register
	Y  uint8  // Y Index Register
	SP uint8  // Stack Pointer (relative to $0100)
	PC uint16 // Program Counter
	P  Flags  // Processor Status Register (Flags)
	// contains filtered or unexported fields
}

CPU represents a MOS Technology 6502 microprocessor.

The CPU executes instructions fetched from memory via the Bus interface. It maintains internal registers (A, X, Y, SP, PC, P) and provides cycle-accurate emulation of the 6502 instruction set.

The CPU operates in a fetch-decode-execute cycle:

  1. Fetch opcode from memory at PC
  2. Decode opcode using lookup table
  3. Execute addressing mode calculation
  4. Execute instruction operation
  5. Update cycle counter

Example:

cpu := cpu6502.NewCPU(bus)
cpu.Reset()
for {
    if err := cpu.Clock(); err != nil {
        break
    }
}

func NewCPU

func NewCPU(bus Bus) *CPU

NewCPU creates a new 6502 CPU instance with default configuration.

The CPU is initialized with:

  • All registers cleared
  • Stack pointer at $FD
  • Status flags: U and I set
  • NMOS 6502 variant
  • Logging error handler

The CPU must be reset before execution:

cpu := NewCPU(bus)
cpu.Reset() // Loads PC from reset vector at $FFFC/FD

For custom configuration, use NewCPUWithConfig or the builder pattern.

Parameters:

  • bus: The memory bus interface

Returns a new CPU instance ready to be reset and executed.

func NewCPUWithConfig

func NewCPUWithConfig(bus Bus, config CPUConfig) *CPU

NewCPUWithConfig creates a new CPU with full configuration.

This function provides complete control over CPU initialization. It applies the configuration and initializes the CPU's internal state.

Parameters:

  • bus: The memory bus interface
  • config: The configuration to apply

Returns a new CPU instance with the specified configuration.

Example:

config := cpu6502.DefaultConfig()
config.Variant = cpu6502.VariantCMOS65C02
config.StrictMode = true
cpu := cpu6502.NewCPUWithConfig(bus, config)

func NewCPUWithErrorHandler

func NewCPUWithErrorHandler(bus Bus, handler ErrorHandler) *CPU

NewCPUWithErrorHandler creates a new CPU with custom error handler.

This is a convenience function for the common case of only needing to change the error handler from the default.

Parameters:

  • bus: The memory bus interface
  • handler: The error handler to use

Returns a new CPU instance with the specified error handler.

Example:

handler := &MyCustomErrorHandler{}
cpu := cpu6502.NewCPUWithErrorHandler(bus, handler)

func NewCPUWithVariant

func NewCPUWithVariant(bus Bus, variant CPUVariant) *CPU

NewCPUWithVariant creates a new CPU with specified variant.

This is a convenience function for the common case of only needing to change the CPU variant from the default.

Parameters:

  • bus: The memory bus interface
  • variant: The CPU variant to emulate

Returns a new CPU instance with the specified variant.

Example:

cpu := cpu6502.NewCPUWithVariant(bus, cpu6502.VariantCMOS65C02)

func NewCPUWithVariantAndErrorHandler

func NewCPUWithVariantAndErrorHandler(bus Bus, variant CPUVariant, handler ErrorHandler) *CPU

NewCPUWithVariantAndErrorHandler creates a new CPU with specified variant and error handler.

This is a convenience function for the common case of needing to change both the variant and error handler.

Parameters:

  • bus: The memory bus interface
  • variant: The CPU variant to emulate
  • handler: The error handler to use

Returns a new CPU instance with the specified configuration.

Example:

handler := &MyCustomErrorHandler{}
cpu := cpu6502.NewCPUWithVariantAndErrorHandler(bus,
    cpu6502.VariantCMOS65C02, handler)

func (*CPU) ABS

func (c *CPU) ABS() uint8

ABS - Absolute addressing mode.

The full 16-bit address is specified in the next two bytes (low byte first, then high byte - little endian).

Example: LDA $1234

  • Opcode at PC
  • Low byte $34 at PC+1
  • High byte $12 at PC+2
  • Loads value from address $1234

Returns 0 (no page cross possible - address is explicit).

func (*CPU) ABX

func (c *CPU) ABX() uint8

ABX - Absolute,X addressing mode.

The X register is added to the 16-bit base address to form the effective address. If the addition crosses a page boundary, returns 1 to indicate an extra cycle may be needed.

Example: LDA $1234,X (with X=$10)

  • Base address $1234
  • Effective address: $1234 + $10 = $1244
  • No page cross (both in page $12)

Page cross example: LDA $12FF,X (with X=$02)

  • Base address $12FF
  • Effective address: $12FF + $02 = $1301
  • Page cross! ($12 -> $13)
  • Returns 1 for potential extra cycle

Returns 1 if page boundary crossed, 0 otherwise.

func (*CPU) ABY

func (c *CPU) ABY() uint8

ABY - Absolute,Y addressing mode.

Similar to ABX, but uses the Y register instead of X.

Example: LDA $1234,Y (with Y=$10)

  • Effective address: $1234 + $10 = $1244

Returns 1 if page boundary crossed, 0 otherwise.

func (*CPU) ADC

func (c *CPU) ADC() uint8

ADC - Add with Carry

Performs A = A + M + C, where:

A = Accumulator
M = Memory operand
C = Carry flag (0 or 1)

In binary mode (D=0):

  • Standard 8-bit addition with carry
  • C flag set if result > 255 (unsigned overflow)
  • V flag set if signed overflow occurs
  • Z flag set if result is zero
  • N flag set if bit 7 of result is 1

In decimal mode (D=1):

  • BCD (Binary Coded Decimal) addition
  • Each nibble represents 0-9 (not 0-F)
  • Adjustments made when nibble exceeds 9
  • N/V flags based on binary intermediate result (NMOS behavior)
  • C flag set if BCD result > 99
  • Z flag set if BCD result is 00

func (*CPU) AND

func (c *CPU) AND() uint8

AND - Logical AND Performs a bitwise AND between the accumulator and memory. Result is stored in the accumulator. Flags affected: Z, N

func (*CPU) ASL

func (c *CPU) ASL() uint8

ASL - Arithmetic Shift Left Shifts all bits left one position. Bit 0 is set to 0. The original bit 7 is shifted into the Carry flag. Flags affected: C, Z, N

func (*CPU) BCC

func (c *CPU) BCC() uint8

BCC - Branch if Carry Clear Branches if the Carry flag is 0. Flags affected: None

func (*CPU) BCS

func (c *CPU) BCS() uint8

BCS - Branch if Carry Set Branches if the Carry flag is 1. Flags affected: None

func (*CPU) BEQ

func (c *CPU) BEQ() uint8

BEQ - Branch if Equal (Zero Set) Branches if the Zero flag is 1. Flags affected: None

func (*CPU) BIT

func (c *CPU) BIT() uint8

BIT - Bit Test Tests bits in memory with the accumulator without storing the result. The Z flag is set based on the AND result (A & M). The N flag is set to bit 7 of the memory value. The V flag is set to bit 6 of the memory value. Flags affected: Z, N, V

func (*CPU) BMI

func (c *CPU) BMI() uint8

BMI - Branch if Minus (Negative Set) Branches if the Negative flag is 1. Flags affected: None

func (*CPU) BNE

func (c *CPU) BNE() uint8

BNE - Branch if Not Equal (Zero Clear) Branches if the Zero flag is 0. Flags affected: None

func (*CPU) BPL

func (c *CPU) BPL() uint8

BPL - Branch if Plus (Negative Clear) Branches if the Negative flag is 0. Flags affected: None

func (*CPU) BRK

func (c *CPU) BRK() uint8

BRK - Break Triggers a software interrupt (IRQ). Pushes PC+2 and processor status (with B and U flags set) onto the stack, sets the I flag, and jumps to the IRQ vector at $FFFE/F.

Note: The 6502 increments PC twice after fetching the BRK opcode, so the pushed PC points two bytes after the BRK instruction. Flags affected: B (set in pushed copy only), I (set in actual register)

func (*CPU) BVC

func (c *CPU) BVC() uint8

BVC - Branch if Overflow Clear Branches if the Overflow flag is 0. Flags affected: None

func (*CPU) BVS

func (c *CPU) BVS() uint8

BVS - Branch if Overflow Set Branches if the Overflow flag is 1. Flags affected: None

func (*CPU) CLC

func (c *CPU) CLC() uint8

CLC - Clear Carry Flag Sets the Carry flag to 0. Flags affected: C

func (*CPU) CLD

func (c *CPU) CLD() uint8

CLD - Clear Decimal Mode Flag Sets the Decimal mode flag to 0, disabling BCD arithmetic. Flags affected: D

func (*CPU) CLI

func (c *CPU) CLI() uint8

CLI - Clear Interrupt Disable Flag Sets the Interrupt disable flag to 0, enabling IRQ interrupts. Flags affected: I

func (*CPU) CLV

func (c *CPU) CLV() uint8

CLV - Clear Overflow Flag Sets the Overflow flag to 0. Flags affected: V

func (*CPU) CMP

func (c *CPU) CMP() uint8

CMP - Compare Accumulator Compares the accumulator with a memory value by subtracting (A - M). The result is not stored, only flags are affected. C flag is set if A >= M (no borrow needed). Z flag is set if A == M. N flag is set based on bit 7 of the result. Flags affected: C, Z, N

func (*CPU) CPX

func (c *CPU) CPX() uint8

CPX - Compare X Register Compares the X register with a memory value by subtracting (X - M). The result is not stored, only flags are affected. C flag is set if X >= M (no borrow needed). Z flag is set if X == M. N flag is set based on bit 7 of the result. Flags affected: C, Z, N

func (*CPU) CPY

func (c *CPU) CPY() uint8

CPY - Compare Y Register Compares the Y register with a memory value by subtracting (Y - M). The result is not stored, only flags are affected. C flag is set if Y >= M (no borrow needed). Z flag is set if Y == M. N flag is set based on bit 7 of the result. Flags affected: C, Z, N

func (*CPU) ClearNMI

func (c *CPU) ClearNMI()

ClearNMI clears the pending NMI.

This is called internally after NMI is serviced. It should not normally be called by user code, as the CPU handles this automatically.

func (*CPU) Clock

func (c *CPU) Clock() error

Clock executes one clock cycle of the CPU.

This method should be called repeatedly to execute instructions. Each instruction takes multiple cycles to complete. The CPU tracks remaining cycles internally and fetches the next instruction when the current one completes.

Returns an error if an unrecoverable error occurs (e.g., illegal opcode in strict mode). The error can be handled or ignored based on the configured error handler.

Example:

for {
    if err := cpu.Clock(); err != nil {
        log.Printf("CPU error: %v", err)
        break
    }
}

func (*CPU) CurrentOpcode

func (c *CPU) CurrentOpcode() uint8

CurrentOpcode returns the opcode of the currently executing instruction.

This is the opcode that was fetched at the start of the current instruction's execution. It remains valid until the next instruction is fetched.

Returns the current opcode byte (0x00-0xFF).

Example:

opcode := cpu.CurrentOpcode()
instr := cpu.LookupInstruction(opcode)
fmt.Printf("Executing: %s\n", instr.Name)

func (*CPU) DEC

func (c *CPU) DEC() uint8

DEC - Decrement Memory Subtracts 1 from the value at the memory location. Flags affected: Z, N

func (*CPU) DEX

func (c *CPU) DEX() uint8

DEX - Decrement X Register Subtracts 1 from the X register. Flags affected: Z, N

func (*CPU) DEY

func (c *CPU) DEY() uint8

DEY - Decrement Y Register Subtracts 1 from the Y register. Flags affected: Z, N

func (*CPU) DisableInstructionCache

func (c *CPU) DisableInstructionCache()

DisableInstructionCache disables the instruction cache

func (*CPU) Disassemble

func (c *CPU) Disassemble(startAddr, endAddr uint16) map[uint16]string

Disassemble disassembles instructions in the specified memory range.

This method reads memory and decodes instructions, producing a map of addresses to disassembled instruction strings. The format matches common 6502 assembler syntax.

Parameters:

  • startAddr: Starting address to disassemble
  • endAddr: Ending address to disassemble (inclusive)

Returns a map where keys are instruction addresses and values are disassembled instruction strings.

Format examples:

  • "LDA #$42" (immediate)
  • "STA $1234" (absolute)
  • "BNE $8010" (relative, shows target address)
  • "JMP ($FFFC)" (indirect)

The disassembler handles:

  • All addressing modes correctly
  • Relative branch target calculation
  • Multi-byte operands (little-endian)
  • Illegal opcodes (marked with *)

Example:

disasm := cpu.Disassemble(0x8000, 0x8010)
for addr := uint16(0x8000); addr <= 0x8010; {
    if instr, ok := disasm[addr]; ok {
        fmt.Printf("$%04X: %s\n", addr, instr)
        // Advance by instruction length
        addr += uint16(cpu.LookupInstruction(cpu.read(addr)).Length)
    }
}

func (*CPU) EOR

func (c *CPU) EOR() uint8

EOR - Exclusive OR Performs a bitwise XOR between the accumulator and memory. Result is stored in the accumulator. Flags affected: Z, N

func (*CPU) EnableInstructionCache

func (c *CPU) EnableInstructionCache()

EnableInstructionCache enables the instruction cache

func (*CPU) GetCurrentInstruction

func (c *CPU) GetCurrentInstruction() *Instruction

GetCurrentInstruction returns a pointer to the current instruction definition. Returns nil if no instruction has been fetched yet.

func (*CPU) GetState

func (c *CPU) GetState() string

GetState returns a formatted string representation of the CPU state.

This provides a human-readable view of the CPU state, useful for debugging and logging. The format is similar to common 6502 debuggers.

Format: PC:XXXX A:XX X:XX Y:XX P:XX[FLAGS] SP:XX CYC:NNNN (INSTR $XX)

Where FLAGS is an 8-character string showing each flag:

  • N: Negative
  • V: Overflow
  • U: Unused (always set)
  • B: Break
  • D: Decimal
  • I: Interrupt Disable
  • Z: Zero
  • C: Carry

Returns a formatted string describing the current state.

Example output:

PC:8000 A:42 X:10 Y:20 P:24[..U.D...] SP:FD CYC:1234 (LDA $A9)

func (*CPU) GetStateSnapshot

func (c *CPU) GetStateSnapshot() State

GetStateSnapshot returns a snapshot of the current CPU state.

This captures all relevant CPU state in a single struct, useful for:

  • Debugging and logging
  • Implementing save states
  • Testing and verification
  • Comparing states across executions

The snapshot includes:

  • All registers (A, X, Y, SP, PC, P)
  • Cycle counters
  • Current opcode and instruction
  • Interrupt state

Returns a State struct containing the complete CPU state.

Example:

state := cpu.GetStateSnapshot()
fmt.Printf("PC: $%04X, A: $%02X\n", state.PC, state.A)

func (*CPU) HasPendingInterrupt

func (c *CPU) HasPendingInterrupt() bool

HasPendingInterrupt returns true if any interrupt is pending.

This checks for:

  • Pending NMI (always serviceable)
  • Asserted IRQ with I flag clear (maskable)

Returns true if an interrupt will be serviced at the next instruction boundary.

Example:

if cpu.HasPendingInterrupt() {
    fmt.Println("Interrupt will be serviced soon")
}

func (*CPU) IMM

func (c *CPU) IMM() uint8

IMM - Immediate addressing mode.

The operand is the byte immediately following the opcode. The effective address is the current PC value.

Example: LDA #$42

  • Opcode at PC
  • Operand $42 at PC+1
  • Loads the literal value $42 into A

Returns 0 (no page cross possible).

func (*CPU) IMP

func (c *CPU) IMP() uint8

IMP - Implied addressing mode.

The operand is implied by the instruction itself. No additional bytes are read from memory. Used by instructions like:

  • Register transfers (TAX, TXA, etc.)
  • Stack operations (PHA, PLA, etc.)
  • Flag operations (CLC, SEC, etc.)

Example: TAX (Transfer A to X)

  • No operand needed
  • Operation is implied

Returns 0 (no page cross possible).

func (*CPU) INC

func (c *CPU) INC() uint8

INC - Increment Memory Adds 1 to the value at the memory location. Flags affected: Z, N

func (*CPU) IND

func (c *CPU) IND() uint8

IND - Indirect addressing mode.

Used only by JMP instruction. The operand is a 16-bit address that points to the actual target address (pointer to pointer).

NMOS 6502 Bug: If the low byte of the pointer is $FF, the high byte is fetched from $xx00 instead of $xx00+1, due to a hardware bug. The CMOS 65C02 fixes this bug.

Example: JMP ($1234)

  • Pointer address $1234 specified in instruction
  • Low byte of target read from $1234
  • High byte of target read from $1235
  • Jump to the constructed 16-bit address

Bug example (NMOS only): JMP ($12FF)

  • Low byte read from $12FF
  • High byte read from $1200 (not $1300!)
  • This is the famous indirect JMP bug

Returns 0 (no page cross possible).

func (*CPU) INX

func (c *CPU) INX() uint8

INX - Increment X Register Adds 1 to the X register. Flags affected: Z, N

func (*CPU) INY

func (c *CPU) INY() uint8

INY - Increment Y Register Adds 1 to the Y register. Flags affected: Z, N

func (*CPU) IZX

func (c *CPU) IZX() uint8

IZX - Indexed Indirect addressing mode (Indirect,X).

The X register is added to the zero page address to get a pointer address. The actual operand address is then read from this pointer. All arithmetic wraps within the zero page.

Example: LDA ($40,X) with X=$05

  • Base zero page address $40
  • Add X: $40 + $05 = $45
  • Read pointer from $0045 (low) and $0046 (high)
  • If pointer = $1234, load from $1234

Wrapping example: LDA ($FF,X) with X=$02

  • Pointer address: ($FF + $02) & $FF = $01
  • Read pointer from $0001 and $0002
  • Note: wraps within zero page

Returns 0 (no page cross possible in pointer calculation).

func (*CPU) IZY

func (c *CPU) IZY() uint8

IZY - Indirect Indexed addressing mode (Indirect),Y.

A zero page address points to a base address. The Y register is then added to this base address to get the effective address. If the addition crosses a page boundary, returns 1.

Example: LDA ($40),Y with Y=$10

  • Read pointer from $0040 (low) and $0041 (high)
  • If pointer = $1234, base address = $1234
  • Add Y: $1234 + $10 = $1244
  • Load from $1244

Page cross example: LDA ($40),Y with Y=$10

  • Pointer at $40 = $12FF
  • Add Y: $12FF + $10 = $130F
  • Page cross! ($12 -> $13)
  • Returns 1 for potential extra cycle

Returns 1 if page boundary crossed, 0 otherwise.

func (*CPU) InstructionCacheStats

func (c *CPU) InstructionCacheStats() (hits, misses uint64, hitRate float64)

InstructionCacheStats returns cache performance statistics

func (*CPU) InterruptRequest deprecated

func (c *CPU) InterruptRequest()

InterruptRequest is deprecated. Use SetIRQ(true) instead.

This method is kept for backward compatibility with existing code. It immediately asserts the IRQ line and attempts to handle the interrupt if conditions allow.

Deprecated: Use SetIRQ(true) for proper interrupt handling.

func (*CPU) InvalidateInstructionCache

func (c *CPU) InvalidateInstructionCache()

InvalidateInstructionCache clears the instruction cache Call this after self-modifying code or when loading new programs

func (*CPU) IsIllegalOpcode

func (c *CPU) IsIllegalOpcode(opcode uint8) bool

IsIllegalOpcode returns true if the given opcode is illegal/unofficial.

Illegal opcodes are undocumented instructions that exist due to the 6502's internal logic but were not officially supported. Some programs use these for various purposes.

Parameters:

  • opcode: The opcode to check (0x00-0xFF)

Returns true if the opcode is illegal, false if it's official.

Example:

if cpu.IsIllegalOpcode(0x04) {
    fmt.Println("This is an illegal NOP")
}

func (*CPU) JMP

func (c *CPU) JMP() uint8

JMP - Jump Sets the program counter to the specified address. This is an unconditional jump. Flags affected: None

func (*CPU) JSR

func (c *CPU) JSR() uint8

JSR - Jump to Subroutine Pushes the return address (PC-1) onto the stack and jumps to the subroutine. The return address points to the last byte of the JSR instruction, so RTS will increment it to return to the next instruction. Flags affected: None

func (*CPU) LDA

func (c *CPU) LDA() uint8

LDA - Load Accumulator Loads a value from memory into the accumulator. Flags affected: Z, N

func (*CPU) LDX

func (c *CPU) LDX() uint8

LDX - Load X Register Loads a value from memory into the X register. Flags affected: Z, N

func (*CPU) LDY

func (c *CPU) LDY() uint8

LDY - Load Y Register Loads a value from memory into the Y register. Flags affected: Z, N

func (*CPU) LSR

func (c *CPU) LSR() uint8

LSR - Logical Shift Right Shifts all bits right one position. Bit 7 is set to 0. The original bit 0 is shifted into the Carry flag. Flags affected: C, Z, N

func (*CPU) LastError

func (c *CPU) LastError() *CPUError

LastError returns the last error that occurred during execution.

This provides access to detailed error information including:

  • Error type
  • Opcode that caused the error
  • Program counter at the time of error
  • Descriptive message

Returns nil if no error has occurred, or a pointer to the last CPUError.

Example:

if err := cpu.Clock(); err != nil {
    if cpuErr := cpu.LastError(); cpuErr != nil {
        fmt.Printf("Error at $%04X: %s\n", cpuErr.PC, cpuErr.Message)
    }
}

func (*CPU) LookupInstruction

func (c *CPU) LookupInstruction(opcode uint8) Instruction

LookupInstruction returns the instruction definition for a given opcode.

This provides access to the instruction's metadata including:

  • Name (mnemonic)
  • Cycle count
  • Addressing mode
  • Whether it's an illegal opcode

Parameters:

  • opcode: The opcode to look up (0x00-0xFF)

Returns the Instruction struct for the given opcode.

Example:

instr := cpu.LookupInstruction(0xA9) // LDA immediate
fmt.Printf("%s takes %d cycles\n", instr.Name, instr.Cycles)

func (*CPU) LookupTable

func (c *CPU) LookupTable() [256]Instruction

LookupTable exposes the instruction lookup table.

This provides direct access to the 256-entry instruction table, primarily intended for tools like disassemblers or UI displays.

Each entry contains:

  • Name: Instruction mnemonic
  • Operate: Function pointer to implementation
  • AddrMode: Function pointer to addressing mode
  • AddrModeType: Addressing mode enum
  • Cycles: Base cycle count
  • Length: Instruction length in bytes
  • PageCrossPenalty: Whether page cross adds a cycle
  • Illegal: Whether this is an unofficial opcode

Returns a copy of the lookup table array.

Example:

table := cpu.LookupTable()
for opcode, instr := range table {
    if !instr.Illegal {
        fmt.Printf("$%02X: %s (%d cycles)\n",
            opcode, instr.Name, instr.Cycles)
    }
}

Warning: Use with caution. Modifying the returned array does not affect the CPU's internal table (it's a copy).

func (*CPU) NOP

func (c *CPU) NOP() uint8

NOP - No Operation Does nothing. Takes 2 cycles. Some unofficial opcodes are also NOPs with different addressing modes and cycle counts, but they all use this same implementation. Flags affected: None

func (*CPU) NonMaskableInterrupt deprecated

func (c *CPU) NonMaskableInterrupt()

NonMaskableInterrupt is deprecated. Use SetNMI(false) after SetNMI(true) instead.

This method is kept for backward compatibility with existing code. It creates a falling edge on the NMI line by setting it high then low, which triggers the NMI interrupt.

Deprecated: Use SetNMI(true) followed by SetNMI(false) for proper NMI handling.

func (*CPU) ORA

func (c *CPU) ORA() uint8

ORA - Logical OR (Inclusive) Performs a bitwise OR between the accumulator and memory. Result is stored in the accumulator. Flags affected: Z, N

func (*CPU) Opcode

func (c *CPU) Opcode() uint8

Opcode returns the last fetched opcode. Useful for debugging/halt conditions. Deprecated: Use CurrentOpcode() instead

func (*CPU) PHA

func (c *CPU) PHA() uint8

PHA - Push Accumulator Pushes the accumulator value onto the stack. Stack pointer is decremented after the push. Flags affected: None

func (*CPU) PHP

func (c *CPU) PHP() uint8

PHP - Push Processor Status Pushes the processor status register (flags) onto the stack. The B and U flags are set in the pushed copy but not in the actual register. Stack pointer is decremented after the push. Flags affected: None

func (*CPU) PLA

func (c *CPU) PLA() uint8

PLA - Pull Accumulator Pulls a value from the stack into the accumulator. Stack pointer is incremented before the pull. Flags affected: Z, N

func (*CPU) PLP

func (c *CPU) PLP() uint8

PLP - Pull Processor Status Pulls a value from the stack into the processor status register (flags). The B and U flags are preserved from the current register, not the pulled value. Stack pointer is incremented before the pull. Flags affected: All (except B and U which are preserved)

func (*CPU) REL

func (c *CPU) REL() uint8

REL - Relative addressing mode.

Used exclusively by branch instructions. The operand is a signed 8-bit offset (-128 to +127) relative to the address of the next instruction (PC after reading the offset).

The effective address is calculated as: PC + offset If bit 7 of the offset is set, it's treated as negative.

Example: BEQ $10 (at address $8000)

  • Opcode at $8000
  • Offset $10 at $8001
  • PC after reading = $8002
  • Branch target = $8002 + $10 = $8012

Negative example: BEQ $FE (at address $8000)

  • Offset $FE = -2 in two's complement
  • PC after reading = $8002
  • Branch target = $8002 + (-2) = $8000

Returns 0 (page cross is handled by branch instructions).

func (*CPU) ROL

func (c *CPU) ROL() uint8

ROL - Rotate Left Shifts all bits left one position. The Carry flag is shifted into bit 0. The original bit 7 is shifted into the Carry flag. Flags affected: C, Z, N

func (*CPU) ROR

func (c *CPU) ROR() uint8

ROR - Rotate Right Shifts all bits right one position. The Carry flag is shifted into bit 7. The original bit 0 is shifted into the Carry flag. Flags affected: C, Z, N

func (*CPU) ROR_RevA added in v0.0.4

func (c *CPU) ROR_RevA() uint8

ROR_RevA - Rotate Right (Rev A Hardware Quirk Version) The original Rev A NMOS 6502 didn't have proper ROR circuitry. Instead of rotating right, it behaves like ASL (Arithmetic Shift Left):

  • Shifts left instead of right (like ASL)
  • Shifts a zero in instead of C (like ASL)
  • Doesn't update C (unlike ASL, the carry flag is not modified)

This quirk was fixed in Rev B and later revisions. Flags affected: Z, N (C is NOT affected, unlike normal ASL)

func (*CPU) RTI

func (c *CPU) RTI() uint8

RTI - Return from Interrupt Pulls the processor status and program counter from the stack. This returns control from an interrupt handler. The B flag is cleared and U flag is set in the restored status. Flags affected: All (restored from stack)

func (*CPU) RTS

func (c *CPU) RTS() uint8

RTS - Return from Subroutine Pulls the return address from the stack and increments it. This returns control to the instruction after the JSR. Flags affected: None

func (*CPU) RemainingCycles

func (c *CPU) RemainingCycles() uint8

RemainingCycles returns the number of cycles remaining for the current instruction.

Each instruction takes multiple cycles to complete. This method returns how many cycles are left before the next instruction will be fetched.

Returns 0 if no instruction is currently executing (ready for next instruction).

Example:

for cpu.RemainingCycles() > 0 {
    cpu.Clock()
}

func (*CPU) Reset

func (c *CPU) Reset()

Reset initializes the CPU to its power-on state.

This method:

  • Clears all registers (A, X, Y)
  • Sets stack pointer to $FD
  • Sets status flags to U | I
  • Loads PC from reset vector at $FFFC/FD
  • Takes 8 cycles to complete

The reset vector should be set in memory before calling Reset:

bus.Write(0xFFFC, 0x00) // Low byte
bus.Write(0xFFFD, 0x80) // High byte -> PC = $8000
cpu.Reset()

func (*CPU) SBC

func (c *CPU) SBC() uint8

SBC - Subtract with Carry (Borrow)

func (*CPU) SEC

func (c *CPU) SEC() uint8

SEC - Set Carry Flag Sets the Carry flag to 1. Flags affected: C

func (*CPU) SED

func (c *CPU) SED() uint8

SED - Set Decimal Mode Flag Sets the Decimal mode flag to 1, enabling BCD arithmetic. Flags affected: D

func (*CPU) SEI

func (c *CPU) SEI() uint8

SEI - Set Interrupt Disable Flag Sets the Interrupt disable flag to 1, disabling IRQ interrupts. Note: NMI interrupts cannot be disabled. Flags affected: I

func (*CPU) STA

func (c *CPU) STA() uint8

STA - Store Accumulator Stores the accumulator value to memory. Flags affected: None

func (*CPU) STX

func (c *CPU) STX() uint8

STX - Store X Register Stores the X register value to memory. Flags affected: None

func (*CPU) STY

func (c *CPU) STY() uint8

STY - Store Y Register Stores the Y register value to memory. Flags affected: None

func (*CPU) SetCycles

func (c *CPU) SetCycles(cycles uint8)

SetCycles sets the number of cycles remaining.

This method is primarily for testing and debugging purposes. It allows manual control of the cycle counter, which can be useful for testing timing-sensitive code.

Parameters:

  • cycles: Number of cycles to set

Warning: Modifying the cycle counter during normal execution can lead to incorrect timing and behavior.

func (*CPU) SetIRQ

func (c *CPU) SetIRQ(asserted bool)

SetIRQ sets the IRQ line state.

IRQ is level-triggered: it will be serviced as long as the line is asserted and the I (Interrupt Disable) flag is clear. The interrupt is checked between instructions, not during instruction execution.

Parameters:

  • asserted: true to assert IRQ, false to clear it

Example:

// Assert IRQ (e.g., from a timer)
cpu.SetIRQ(true)

// Later, clear IRQ after handling
cpu.SetIRQ(false)

Note: The interrupt will only be serviced if:

  • The I flag is clear (interrupts enabled)
  • No instruction is currently executing (cycles == 0)
  • Not already handling an interrupt

func (*CPU) SetNMI

func (c *CPU) SetNMI(asserted bool)

SetNMI sets the NMI line state.

NMI is edge-triggered: it will be serviced on a falling edge (high to low transition). Once triggered, the NMI is pending until serviced, even if the line goes high again.

Parameters:

  • asserted: true to set line high, false to set line low

Example:

// Trigger NMI with falling edge
cpu.SetNMI(true)  // Set high
cpu.SetNMI(false) // Set low - triggers NMI

Note: The interrupt will be serviced at the next instruction boundary, regardless of the I flag state.

func (*CPU) TAX

func (c *CPU) TAX() uint8

TAX - Transfer Accumulator to X Copies the accumulator value to the X register. Flags affected: Z, N

func (*CPU) TAY

func (c *CPU) TAY() uint8

TAY - Transfer Accumulator to Y Copies the accumulator value to the Y register. Flags affected: Z, N

func (*CPU) TSX

func (c *CPU) TSX() uint8

TSX - Transfer Stack Pointer to X Copies the stack pointer value to the X register. Flags affected: Z, N

func (*CPU) TXA

func (c *CPU) TXA() uint8

TXA - Transfer X to Accumulator Copies the X register value to the accumulator. Flags affected: Z, N

func (*CPU) TXS

func (c *CPU) TXS() uint8

TXS - Transfer X to Stack Pointer Copies the X register value to the stack pointer. Flags affected: None

func (*CPU) TYA

func (c *CPU) TYA() uint8

TYA - Transfer Y to Accumulator Copies the Y register value to the accumulator. Flags affected: Z, N

func (*CPU) TotalCycles

func (c *CPU) TotalCycles() uint64

TotalCycles returns the total number of cycles executed since CPU creation.

This counter increments with each clock cycle and is useful for:

  • Performance profiling
  • Timing synchronization with other components
  • Debugging timing-sensitive issues

The counter wraps around at 2^64 cycles (effectively never in practice).

Example:

start := cpu.TotalCycles()
// ... execute some code ...
elapsed := cpu.TotalCycles() - start
fmt.Printf("Executed %d cycles\n", elapsed)

func (*CPU) Variant

func (c *CPU) Variant() CPUVariant

Variant returns the CPU variant

func (*CPU) XXX

func (c *CPU) XXX() uint8

XXX - Illegal Opcode Handler Called when an illegal/unofficial opcode is encountered. Logs an error message and returns 1 to prevent infinite loops. The actual error handling behavior depends on the configured error handler. Flags affected: None

func (*CPU) ZP0

func (c *CPU) ZP0() uint8

ZP0 - Zero Page addressing mode.

The operand is located in the zero page (addresses $0000-$00FF). Only one byte is needed to specify the address, making this mode faster and more compact than absolute addressing.

Example: LDA $42

  • Opcode at PC
  • Zero page address $42 at PC+1
  • Loads value from address $0042

Returns 0 (no page cross possible - always in zero page).

func (*CPU) ZPX

func (c *CPU) ZPX() uint8

ZPX - Zero Page,X addressing mode.

Similar to ZP0, but the X register is added to the zero page address. The result wraps around within the zero page (no carry to high byte).

Example: LDA $42,X (with X=$10)

  • Opcode at PC
  • Base address $42 at PC+1
  • Effective address: ($42 + $10) & $FF = $52
  • Loads value from address $0052

Wrapping example: LDA $FF,X (with X=$02)

  • Effective address: ($FF + $02) & $FF = $01
  • Wraps to $0001, not $0101

Returns 0 (no page cross possible - wraps within zero page).

func (*CPU) ZPY

func (c *CPU) ZPY() uint8

ZPY - Zero Page,Y addressing mode.

Similar to ZPX, but uses the Y register instead of X. Only used by LDX and STX instructions.

Example: LDX $42,Y (with Y=$10)

  • Effective address: ($42 + $10) & $FF = $52
  • Loads value from address $0052 into X

Returns 0 (no page cross possible - wraps within zero page).

type CPUBuilder

type CPUBuilder struct {
	// contains filtered or unexported fields
}

CPUBuilder provides a fluent interface for CPU configuration.

The builder pattern allows for readable, chainable configuration:

cpu := cpu6502.NewBuilder(bus).
    WithVariant(cpu6502.VariantCMOS65C02).
    WithStrictMode().
    DisableDecimalMode().
    Build()

This is more convenient than manually creating a CPUConfig struct when you only need to change a few settings from the defaults.

func NewBuilder

func NewBuilder(bus Bus) *CPUBuilder

NewBuilder creates a new CPU builder with default configuration.

Parameters:

  • bus: The memory bus interface for the CPU

Returns a builder that can be configured using method chaining.

Example:

builder := cpu6502.NewBuilder(bus)
cpu := builder.WithVariant(cpu6502.VariantCMOS65C02).Build()

func (*CPUBuilder) Build

func (b *CPUBuilder) Build() *CPU

Build creates the configured CPU.

Returns a new CPU instance with the configured settings.

Example:

cpu := cpu6502.NewBuilder(bus).
    WithVariant(cpu6502.VariantCMOS65C02).
    WithStrictMode().
    Build()

func (*CPUBuilder) DisableDecimalMode

func (b *CPUBuilder) DisableDecimalMode() *CPUBuilder

DisableDecimalMode disables decimal mode.

This is useful for emulating systems that don't support decimal mode (like the Ricoh 2A03 in the NES) or for slight performance improvements.

Returns the builder for method chaining.

Example:

builder.DisableDecimalMode()

func (*CPUBuilder) DisableInstructionCache

func (b *CPUBuilder) DisableInstructionCache() *CPUBuilder

DisableInstructionCache disables the instruction cache.

This should be used when emulating self-modifying code or when cache invalidation would be too complex to manage.

Returns the builder for method chaining.

Example:

builder.DisableInstructionCache()

func (*CPUBuilder) WithErrorHandler

func (b *CPUBuilder) WithErrorHandler(handler ErrorHandler) *CPUBuilder

WithErrorHandler sets a custom error handler.

Parameters:

  • handler: The error handler to use

Returns the builder for method chaining.

Example:

handler := &MyCustomErrorHandler{}
builder.WithErrorHandler(handler)

func (*CPUBuilder) WithInstructionCacheSize

func (b *CPUBuilder) WithInstructionCacheSize(size int) *CPUBuilder

WithInstructionCacheSize sets the instruction cache size.

Note: Currently the cache size is fixed at 256 entries. This method is reserved for future use.

Parameters:

  • size: The desired cache size

Returns the builder for method chaining.

func (*CPUBuilder) WithStrictMode

func (b *CPUBuilder) WithStrictMode() *CPUBuilder

WithStrictMode enables strict mode.

In strict mode, the CPU halts execution when encountering illegal opcodes instead of continuing with default behavior.

Returns the builder for method chaining.

Example:

builder.WithStrictMode()

func (*CPUBuilder) WithVariant

func (b *CPUBuilder) WithVariant(variant CPUVariant) *CPUBuilder

WithVariant sets the CPU variant.

Parameters:

  • variant: The CPU variant to emulate

Returns the builder for method chaining.

Example:

builder.WithVariant(cpu6502.VariantCMOS65C02)

type CPUConfig

type CPUConfig struct {
	// Variant specifies the CPU variant (NMOS, CMOS, Ricoh)
	Variant CPUVariant

	// ErrorHandler defines how errors are handled
	ErrorHandler ErrorHandler

	// StrictMode halts execution on illegal opcodes
	StrictMode bool

	// EnableDecimalMode allows disabling decimal mode even on variants that support it
	EnableDecimalMode bool

	// EnableInstructionCache enables instruction caching for performance
	EnableInstructionCache bool

	// InstructionCacheSize sets the cache size (default: 256 entries)
	InstructionCacheSize int
}

CPUConfig holds configuration options for CPU creation.

This structure provides fine-grained control over CPU behavior, including variant selection, error handling, and performance options.

Configuration Options

Variant: Selects the CPU variant (NMOS 6502, CMOS 65C02, Ricoh 2A03)

  • Affects instruction behavior and bug emulation
  • Default: VariantNMOS6502

ErrorHandler: Defines how errors are handled during execution

  • LoggingErrorHandler: Logs errors but continues execution
  • StrictErrorHandler: Halts execution on any error
  • Custom handlers can be implemented

StrictMode: Halts execution on illegal opcodes

  • Overrides ErrorHandler for illegal opcode errors
  • Useful for debugging or strict compatibility testing

EnableDecimalMode: Controls decimal mode support

  • Some variants (Ricoh 2A03) don't support decimal mode
  • Can be disabled for performance or compatibility

EnableInstructionCache: Enables instruction caching

  • Improves performance by caching instruction lookups
  • Should be disabled for self-modifying code

InstructionCacheSize: Sets cache size (currently fixed at 256)

  • Reserved for future use

Example usage:

config := cpu6502.DefaultConfig()
config.Variant = cpu6502.VariantCMOS65C02
config.StrictMode = true
cpu := cpu6502.NewCPUWithConfig(bus, config)

func DefaultConfig

func DefaultConfig() CPUConfig

DefaultConfig returns a configuration with sensible defaults.

Default configuration:

  • Variant: NMOS 6502 (original)
  • ErrorHandler: LoggingErrorHandler (logs to default logger)
  • StrictMode: false (continues on errors)
  • EnableDecimalMode: true
  • EnableInstructionCache: true
  • InstructionCacheSize: 256

This configuration is suitable for most emulation scenarios and provides a good balance between accuracy and performance.

type CPUError

type CPUError struct {
	Type    ErrorType
	Opcode  uint8
	PC      uint16
	Message string
}

CPUError represents an error during CPU execution

func (*CPUError) Error

func (e *CPUError) Error() string

Error implements the error interface for CPUError

type CPUVariant

type CPUVariant int

CPUVariant represents different 6502 processor variants

const (
	// VariantNMOS6502 is the original NMOS 6502 (1975)
	// Used in: Apple II, Commodore 64, Atari 2600/800, BBC Micro
	// Features: All documented bugs, decimal mode supported
	// Note: This represents Rev B and later which have working ROR
	VariantNMOS6502 CPUVariant = iota

	// VariantNMOS6502RevA is the original Rev A NMOS 6502
	// This early revision had a hardware bug where ROR was not implemented
	// and performed a modified ROL operation instead
	// Features: ROR quirk, all other NMOS bugs, decimal mode supported
	VariantNMOS6502RevA

	// VariantCMOS65C02 is the CMOS 65C02 (1982)
	// Used in: Apple IIc, Apple IIe (enhanced), later systems
	// Features: Bug fixes, additional instructions, lower power
	VariantCMOS65C02

	// VariantRicoh2A03 is the NES/Famicom CPU (1983)
	// Used in: Nintendo Entertainment System, Famicom
	// Features: No decimal mode, integrated APU, different timing
	VariantRicoh2A03

	// VariantRicoh2A07 is the PAL NES CPU
	// Same as 2A03 but with PAL timing
	VariantRicoh2A07
)

func (CPUVariant) HasIndirectJMPBug

func (v CPUVariant) HasIndirectJMPBug() bool

HasIndirectJMPBug returns true if the variant has the indirect JMP page boundary bug

func (CPUVariant) HasRORQuirk added in v0.0.4

func (v CPUVariant) HasRORQuirk() bool

HasRORQuirk returns true if the variant has the ROR hardware bug. Only the original Rev A NMOS 6502 had this quirk where ROR didn't have proper circuitry and performed a modified ROL operation instead. This was fixed in Rev B and was never present in Ricoh or CMOS variants.

func (CPUVariant) String

func (v CPUVariant) String() string

String returns the variant name

func (CPUVariant) SupportsDecimalMode

func (v CPUVariant) SupportsDecimalMode() bool

SupportsDecimalMode returns true if the variant supports decimal mode

type ErrorHandler

type ErrorHandler interface {
	HandleError(err *CPUError) error
}

ErrorHandler defines how the CPU handles errors

type ErrorType

type ErrorType uint8

ErrorType categorizes CPU errors

const (
	// ErrorIllegalOpcode indicates an illegal/unofficial opcode was encountered
	ErrorIllegalOpcode ErrorType = iota
	// ErrorInvalidState indicates the CPU is in an invalid state
	ErrorInvalidState
	// ErrorBusError indicates a bus access error occurred
	ErrorBusError
)

type Flags

type Flags uint8

Flags represents the processor status register.

The 6502 has 8 status flags that indicate the result of operations:

  • N (Negative): Set if result is negative (bit 7 = 1)
  • V (Overflow): Set if signed overflow occurred
  • U (Unused): Always set to 1
  • B (Break): Set when BRK instruction executed
  • D (Decimal): Enables BCD arithmetic mode
  • I (Interrupt Disable): When set, IRQ interrupts are ignored
  • Z (Zero): Set if result is zero
  • C (Carry): Set if unsigned overflow/borrow occurred
const (
	C Flags = 1 << 0 // Carry Bit
	Z Flags = 1 << 1 // Zero
	I Flags = 1 << 2 // Disable Interrupts
	D Flags = 1 << 3 // Decimal Mode (rarely used, often ignored in NES emu)
	B Flags = 1 << 4 // Break Command
	U Flags = 1 << 5 // Unused (always 1)
	V Flags = 1 << 6 // Overflow
	N Flags = 1 << 7 // Negative
)

type Instruction

type Instruction struct {
	Name             string           // Mnemonic (e.g., "LDA")
	Operate          func(*CPU) uint8 // Function to execute the instruction's logic (accepts *CPU)
	AddrMode         func(*CPU) uint8 // Function to calculate the address and fetch data (accepts *CPU)
	AddrModeType     AddrModeType     // Type of addressing mode
	Cycles           uint8            // Base cycles for this instruction/mode
	Length           uint8            // Length of the instruction in bytes
	Illegal          bool             // Whether this is an official or unofficial/illegal opcode
	PageCrossPenalty bool             // Whether to add +1 cycle on page boundary cross
}

Instruction represents a single 6502 instruction with its associated metadata.

Each instruction consists of:

  • Name: The mnemonic (e.g., "LDA", "STA")
  • Operate: Function that executes the instruction's logic
  • AddrMode: Function that calculates the effective address
  • AddrModeType: Enum identifying the addressing mode
  • Cycles: Base number of cycles the instruction takes
  • Length: Size of the instruction in bytes (including opcode)
  • Illegal: Whether this is an unofficial/illegal opcode
  • PageCrossPenalty: Whether to add +1 cycle on page boundary cross

type InstructionCache

type InstructionCache struct {
	// contains filtered or unexported fields
}

InstructionCache provides fast lookup for recently executed instructions.

The cache improves performance by avoiding repeated lookups in the main instruction table. It uses a direct-mapped strategy with 256 entries, indexed by the low byte of the program counter.

Cache Strategy

The cache uses PC & 0xFF as the index, which provides good locality for:

  • Tight loops (instructions repeat at similar addresses)
  • Sequential code (nearby instructions share cache lines)
  • Subroutines (local code patterns)

Performance Characteristics

Typical hit rates:

  • Tight loops: 95-99% (excellent)
  • Sequential code: 60-80% (good)
  • Random jumps: 20-40% (poor, but rare)

Cache Invalidation

The cache should be invalidated when:

  • Self-modifying code is detected
  • New program is loaded into memory
  • Memory is modified in executable regions

Example usage:

cache := NewInstructionCache()
if instr, hit := cache.Lookup(pc, opcode); hit {
    // Use cached instruction
} else {
    // Look up in main table and cache result
    instr := &lookupTable[opcode]
    cache.Store(pc, opcode, instr)
}

func NewInstructionCache

func NewInstructionCache() *InstructionCache

NewInstructionCache creates a new instruction cache.

The cache is initially empty (all entries invalid) and statistics are zeroed. The cache is ready to use immediately.

func (*InstructionCache) Invalidate

func (ic *InstructionCache) Invalidate()

Invalidate clears the entire cache.

This marks all cache entries as invalid and resets statistics to zero. Call this after self-modifying code execution or when loading a new program into memory.

Example:

// After writing to executable memory
cpu.InvalidateInstructionCache()

func (*InstructionCache) Lookup

func (ic *InstructionCache) Lookup(pc uint16, opcode uint8) (*Instruction, bool)

Lookup attempts to find an instruction in the cache.

Returns the cached instruction and true if found, or nil and false if not found or the cache entry is invalid. Updates hit/miss statistics.

The lookup uses the low byte of PC as the cache index and verifies that the cached opcode matches the requested opcode.

Parameters:

  • pc: Program counter value (low byte used as cache index)
  • opcode: The opcode to look up

Returns:

  • instruction: Pointer to cached instruction (nil if not found)
  • hit: true if instruction was found in cache

func (*InstructionCache) Stats

func (ic *InstructionCache) Stats() (hits, misses uint64, hitRate float64)

Stats returns cache performance statistics.

Returns:

  • hits: Number of successful cache lookups
  • misses: Number of failed cache lookups
  • hitRate: Percentage of successful lookups (0.0 to 1.0)

The hit rate is calculated as hits / (hits + misses). Returns 0.0 if no lookups have been performed yet.

Example:

hits, misses, rate := cache.Stats()
fmt.Printf("Cache: %d hits, %d misses, %.1f%% hit rate\n",
    hits, misses, rate*100)

func (*InstructionCache) Store

func (ic *InstructionCache) Store(pc uint16, opcode uint8, instruction *Instruction)

Store adds an instruction to the cache.

Stores the instruction at the cache index determined by the low byte of the PC. Any existing entry at that index is replaced.

Parameters:

  • pc: Program counter value (low byte used as cache index)
  • opcode: The opcode being cached
  • instruction: Pointer to the instruction definition

type InstructionCacheEntry

type InstructionCacheEntry struct {
	// contains filtered or unexported fields
}

InstructionCacheEntry represents a cached instruction lookup result.

The cache uses a direct-mapped strategy where the low byte of the PC is used as the cache index. Each entry stores the opcode and instruction pointer, along with a validity flag.

type LoggingErrorHandler

type LoggingErrorHandler struct {
	Logger *log.Logger
}

LoggingErrorHandler logs errors but continues execution

func (*LoggingErrorHandler) HandleError

func (h *LoggingErrorHandler) HandleError(err *CPUError) error

HandleError logs the error and continues execution

type State

type State struct {
	A               uint8  // Accumulator
	X               uint8  // X Index Register
	Y               uint8  // Y Index Register
	SP              uint8  // Stack Pointer
	PC              uint16 // Program Counter
	P               Flags  // Processor Status Register
	Cycles          uint8  // Cycles remaining for current instruction
	TotalCycles     uint64 // Total cycles executed since creation
	Opcode          uint8  // Current opcode being executed
	Instruction     string // Current instruction mnemonic
	InInterrupt     bool   // Whether currently handling an interrupt
	InterruptVector uint16 // Vector being used for current interrupt
}

State represents a snapshot of CPU state at a point in time.

This structure is useful for:

  • Debugging and inspection
  • Save states in emulators
  • Testing and verification
  • Logging execution traces

func (State) String

func (s State) String() string

String returns a human-readable representation of the state

type StrictErrorHandler

type StrictErrorHandler struct{}

StrictErrorHandler halts execution on any error

func (*StrictErrorHandler) HandleError

func (h *StrictErrorHandler) HandleError(err *CPUError) error

HandleError returns the error, halting execution

Directories

Path Synopsis
examples
basic command
Package main demonstrates basic CPU usage
Package main demonstrates basic CPU usage
memory-mapped command
Package main demonstrates memory-mapped I/O
Package main demonstrates memory-mapped I/O

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL