Flow B: CPU instruction and memory access cycle

The heart of SameBoy's accuracy: every memory access is a bus cycle that advances all hardware in lockstep.

Trigger

Frontend calls GB_run(gb)GB_cpu_run(gb).

Architecture diagram

GB_run() └─ GB_cpu_run() ├─ [STOP mode?] → GB_advance_cycles(4/8), return ├─ [HALT / interrupt dispatch] │ └─ cycle_read/write → GB_advance_cycles per M-cycle └─ Fetch opcode at PC ├─ opcodes[opcode](gb, opcode) // 256-entry table ├─ flush_pending_cycles() └─ GB_advance_cycles(remaining) cycle_read(addr): if pending_cycles: GB_advance_cycles(pending_cycles) data = GB_read_memory(gb, addr) pending_cycles = 4 return data GB_advance_cycles(cycles): [timing.c:511] GB_joypad_run(gb, cycles) GB_apu_run(gb, false) GB_display_run(gb, cycles, false) GB_dma_run(gb) ir_run(gb, cycles) rtc_run(gb, cycles)
GB_cpu_run — entry and branches

GB_cpu_run handles several paths before normal opcode fetch:

  1. STOP mode (gb->stopped): advance 4 cycles, check JOYP to wake
  2. Interrupt service when IME && (IE & IF): push PC, jump to vector, clear IF bit — with OAM bug side effects on stack operations
  3. HALT wake without IME: exit halt, optionally start HDMA
  4. Normal: read opcode byte, optional HDMA trigger, execution_callback hook, dispatch opcodes[opcode]

IME toggle from EI instruction is deferred: ime_toggle applies at start of next instruction.

GB_cpu_run start

cycle_read / cycle_write — bus-cycle timing

Each memory access:

  1. Flushes pending_cycles via GB_advance_cycles (advances PPU/APU/timer by prior instruction cycles)
  2. Sets address_bus
  3. Calls GB_read_memory or GB_write_memory
  4. Sets pending_cycles = 4 (one M-cycle = 4 T-cycles in 8MHz units after speed adjustment)

Bus conflicts: For I/O writes in FF00–FF7F, cycle_write selects a per-model conflict_map (DMG, CGB, CGB double-speed, SGB) determining whether CPU and PPU "see" old or new values when accessing the same register in the same cycle.

cycle_read · conflict maps

GB_read_memory — address decoding

Top-level decode in GB_read_memory (memory.c:776):

RangeHandlerNotes
0000–7FFFread_mbc_romCartridge ROM, bank switched
8000–9FFFread_vramVRAM; PPU may block during mode 3
A000–BFFFread_mbc_ramExternal RAM or RTC registers
C000–DFFFread_ramWRAM (banked on CGB)
E000–FDFFEcho of C000–DDFF
FE00–FE9FOAMBlocked during DMA and certain PPU modes
FF00–FF7Fread_high_memoryI/O registers
FF80–FFFEHRAM
FFFFIE register

I/O reads fan out: GB_IO_LY syncs PPU first; APU registers via GB_apu_read; joypad via GB_joypad_read.

Cheats: read_memory_callback can intercept; cheat hash may override values.

GB_read_memory

GB_write_memory — side effects

Notable write side effects:

  • 0xFF46 (DMA) — starts OAM DMA, sets dma_cycles
  • 0xFF40 (LCDC) — may call GB_lcd_off, schedule frame repeat
  • 0xFF41 (STAT) — GB_STAT_update
  • 0xFF04–07 — timer/divider; GB_emulate_timer_glitch on TAC change
  • 0xFF4D (KEY1) — CGB speed switch countdown
  • MBC range 0000–7FFF, A000–BFFF — write_mbc updates bank pointers

GB_trigger_oam_bug called on specific inc/dec patterns affecting OAM region (DMG only).

Important data structures per cycle
  • gb->pending_cycles — T-cycles to advance before next bus access
  • gb->address_bus, gb->data_bus — open bus emulation
  • gb->cycles_since_run — returned by GB_run (8 MHz units)
  • gb->accessed_oam_row — for OAM corruption tracking

What breaks if changed incorrectly

  • Skipping GB_advance_cycles in cycle_read → PPU/APU desync, Acid2 failures
  • Wrong conflict map → raster effects (SCX/STAT glitches) break
  • Calling GB_read_memory without PPU sync on LY/STAT → wrong mode interrupts
  • Removing OAM bug → oam_bug-2.gb test fails