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
- Register:
LD A,B - Immediate:
LD A,$42 - Register Indirect:
LD A,(HL) - Immediate Extended:
LD A,($1234) - High Memory:
LDH A,($FF00+C),LDH A,($FF00+n) - 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:
- V-Blank (INT $40): Vertical blank
- LCD STAT (INT $48): LCD controller
- Timer (INT $50): Timer overflow
- Serial (INT $58): Serial transfer complete
- Joypad (INT $60): Button press
Interrupt Handling
- Interrupts are controlled by IE (Interrupt Enable) register
- IF (Interrupt Flag) register shows pending interrupts
- IME (Interrupt Master Enable) flag globally enables/disables
- When an interrupt occurs:
- Push PC to stack
- Set PC to interrupt vector
- Clear IME flag
- 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
- Game Boy CPU Manual - Complete instruction reference
- Pan Docs - Comprehensive Game Boy technical documentation
- Game Boy Opcode Table - Complete opcode listing
- GBEDG - Game Boy emulator development guide