Sharp LR35902 CPU Reference

Overview

The Sharp LR35902 is the CPU used in the Game Boy and Game Boy Color. It is a hybrid design based on the Intel 8080 and Zilog Z80, combining features from both while removing some instructions and modifying others. This implementation provides a reusable CPU core for Game Boy emulation.

Implementation: crates/core/src/cpu_lr35902.rs

Architecture

Registers

The LR35902 has eight 8-bit registers that can be used as pairs:

  • A (Accumulator): Primary register for arithmetic and logic operations
  • F (Flags): Processor status flags
  • B, C: 8-bit registers (can be paired as BC)
  • D, E: 8-bit registers (can be paired as DE)
  • H, L: 8-bit registers (can be paired as HL for memory addressing)
  • SP (Stack Pointer): 16-bit pointer to stack
  • PC (Program Counter): 16-bit pointer to current instruction

Register Pairs

Registers can be accessed as 16-bit pairs:

  • AF: A (high byte) and F (low byte)
  • BC: B (high byte) and C (low byte)
  • DE: D (high byte) and E (low byte)
  • HL: H (high byte) and L (low byte)

Flags Register (F)

Z N H C - - - -
│ │ │ │
│ │ │ └─ Carry flag
│ │ └─── Half Carry flag (bit 3 to bit 4)
│ └───── Subtract flag (for BCD subtraction)
└─────── Zero flag

The lower 4 bits of the flags register are always 0.

Note: The flags are different from both 8080 and Z80:

  • No Sign or Parity/Overflow flags
  • Half Carry instead of Auxiliary Carry
  • Subtract flag for BCD operations

Usage

Systems using the LR35902 must implement the MemoryLr35902 trait:

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

Example

use emu_core::cpu_lr35902::{CpuLr35902, MemoryLr35902};

struct GameBoySystem {
    ram: [u8; 65536],
}

impl MemoryLr35902 for GameBoySystem {
    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;
    }
}

let system = GameBoySystem { ram: [0; 65536] };
let mut cpu = CpuLr35902::new(system);
cpu.reset();

Instruction Set

The LR35902 instruction set is based on the 8080 and Z80 but with significant differences.

Data Transfer

  • LD: Load (replaces 8080's MOV, MVI, LDA, STA, etc.)
  • LDH: Load to/from high memory ($FF00+n)
  • PUSH/POP: Stack operations
  • LDI: Load with increment (HL++)
  • LDD: Load with decrement (HL--)

Arithmetic

  • ADD: Add to accumulator
  • ADC: Add with carry
  • SUB: Subtract from accumulator
  • SBC: Subtract with carry
  • INC: Increment
  • DEC: Decrement
  • DAA: Decimal adjust accumulator (BCD)
  • CPL: Complement accumulator

Logical

  • AND: Logical AND
  • OR: Logical OR
  • XOR: Logical XOR
  • CP: Compare (SUB without storing result)

Rotate and Shift

  • RLA: Rotate A left through carry
  • RLCA: Rotate A left
  • RRA: Rotate A right through carry
  • RRCA: Rotate A right
  • RLC: Rotate left through carry
  • RL: Rotate left
  • RRC: Rotate right through carry
  • RR: Rotate right
  • SLA: Shift left arithmetic
  • SRA: Shift right arithmetic
  • SRL: Shift right logical
  • SWAP: Swap nibbles

Bit Operations

  • BIT: Test bit
  • SET: Set bit
  • RES: Reset (clear) bit

Jumps

  • JP: Jump
  • JP cc: Conditional jump (NZ, Z, NC, C)
  • JR: Relative jump
  • JR cc: Conditional relative jump

Calls and Returns

  • CALL: Call subroutine
  • CALL cc: Conditional call
  • RET: Return from subroutine
  • RET cc: Conditional return
  • RETI: Return from interrupt
  • RST: Restart (8 vectors)

Stack

  • PUSH: Push register pair
  • POP: Pop register pair
  • ADD SP,n: Add signed immediate to SP
  • LD HL,SP+n: Load HL with SP + signed immediate

Control

  • NOP: No operation
  • HALT: Halt until interrupt
  • STOP: Stop CPU and display
  • DI: Disable interrupts
  • EI: Enable interrupts
  • CCF: Complement carry flag
  • SCF: Set carry flag

Addressing Modes

  1. Register: LD A,B
  2. Immediate: LD A,$42
  3. Register Indirect: LD A,(HL)
  4. Immediate Extended: LD A,($1234)
  5. High Memory: LDH A,($FF00+C), LDH A,($FF00+n)
  6. Relative: For JR instructions (signed 8-bit offset)

Differences from 8080

Removed 8080 instructions:

  • No I/O instructions (IN/OUT)
  • No direct register pair arithmetic (DAD)
  • No exchange instructions (XCHG, XTHL)

Modified instructions:

  • Different opcode encoding
  • Different flag behavior
  • Simplified instruction set

New instructions:

  • LDH (load high memory)
  • LDI/LDD (load with increment/decrement)
  • ADD SP,n (add to stack pointer)
  • Bit manipulation (BIT, SET, RES)
  • SWAP (swap nibbles)
  • STOP (low power mode)

Differences from Z80

Removed Z80 features:

  • No IX/IY index registers
  • No shadow registers (AF', BC', DE', HL')
  • No block operations (LDIR, CPIR, etc.)
  • No extended instructions (except CB prefix)

Different flag behavior:

  • Only 4 flags vs Z80's 8
  • Different flag updates for many instructions

New instructions:

  • LDH for fast I/O access
  • LDI/LDD variants
  • Different CB-prefixed instruction encoding

Interrupts

The Game Boy CPU supports 5 interrupts:

  1. V-Blank (INT $40): Vertical blank
  2. LCD STAT (INT $48): LCD controller
  3. Timer (INT $50): Timer overflow
  4. Serial (INT $58): Serial transfer complete
  5. Joypad (INT $60): Button press

Interrupt Handling

  1. Interrupts are controlled by IE (Interrupt Enable) register
  2. IF (Interrupt Flag) register shows pending interrupts
  3. IME (Interrupt Master Enable) flag globally enables/disables
  4. When an interrupt occurs:
    • Push PC to stack
    • Set PC to interrupt vector
    • Clear IME flag
  5. Use RETI to return and re-enable interrupts

Memory Map (Game Boy Context)

  • $0000-$3FFF: ROM Bank 0 (16KB)
  • $4000-$7FFF: ROM Bank 1-N (16KB switchable)
  • $8000-$9FFF: Video RAM (8KB)
  • $A000-$BFFF: External RAM (8KB)
  • $C000-$DFFF: Work RAM (8KB)
  • $E000-$FDFF: Echo RAM (mirror of C000-DDFF)
  • $FE00-$FE9F: Object Attribute Memory (OAM)
  • $FF00-$FF7F: I/O Registers
  • $FF80-$FFFE: High RAM (HRAM)
  • $FFFF: Interrupt Enable register

Timing

Instructions take between 4 and 24 cycles (1-6 machine cycles).

Machine Cycle: 4 clock cycles = 1 T-state

Common timings:

  • Simple operations: 4 cycles (1 M-cycle)
  • Register loads: 8 cycles (2 M-cycles)
  • Memory access: 8-12 cycles
  • Conditional jumps: 12 cycles (taken) or 8 cycles (not taken)
  • Calls: 24 cycles
  • CB-prefixed: 8-16 cycles

Special Features

High Memory Access (LDH)

Fast access to I/O registers at $FF00-$FFFF:

LDH A,($FF00+$44)  ; Read LCDC Y-coordinate
LDH ($FF00+$01),A  ; Write to serial data

Load with Increment/Decrement

Efficient for copying data:

LDI A,(HL)   ; A = (HL), HL++
LDD (HL),A   ; (HL) = A, HL--

HALT Bug

On early Game Boy models, there's a bug where HALT with IME=0 and pending interrupt causes PC to fail to increment. Emulators must reproduce this for accuracy.

STOP Instruction

STOP halts the CPU and display until a button is pressed. Used for power saving and switching between normal/double speed mode (Game Boy Color).

Implementation Notes

Cycle Accuracy

For accurate Game Boy emulation, instruction timing must be precise. Some games rely on exact timing for:

  • Graphics synchronization
  • Audio generation
  • Input polling

CB-Prefixed Instructions

Instructions with CB prefix are two-byte opcodes:

  • First byte: $CB
  • Second byte: Actual opcode

These include bit operations and additional rotate/shift instructions.

Interrupt Priority

When multiple interrupts occur simultaneously, they are serviced in priority order (V-Blank highest, Joypad lowest).

Systems Using LR35902

This CPU core is used by:

  • Game Boy - crates/systems/gb/
  • Game Boy Color - crates/systems/gb/ (same CPU, double speed mode)

References