Hardware & Timing

Game Boy Color clock architecture, interrupt sources, and the timing foundations that everything else builds on.

Clock Architecture

The Game Boy’s master clock runs at 4.194304 MHz (222 Hz). All subsystem timing derives from this clock by division:

SubsystemDerivationRate
CPU (Normal)4194304 / 14.19 MHz (1 M-cycle = 4 T-cycles)
CPU (CGB Double)8388608 / 18.39 MHz
PPU dot clock4194304 / 14.19 MHz
VBlank4194304 / 70224~59.7 Hz
Timer (TAC clock/256)4194304 / 6553664 Hz
DIV register4194304 / 25616384 Hz

VBlank and the timer are phase-locked but at rates that never align into a clean integer ratio — VBlank is dictated by LCD timing (a non-power-of-2 cycle count), while the timer uses clean binary division.

VBlank: 70224 Cycles

The PPU draws the screen in a fixed cycle:

PhaseLinesDots/lineTotal dots
Visible (Mode 0/2/3)14445665664
VBlank (Mode 1)104564560
Total15445670224

70224 is not a power of 2. It’s the product of LCD requirements: 154 scanlines (144 visible + 10 blanking) at 456 dots each. The horizontal and vertical blanking periods are necessary to give the hardware time to reset beam position — a constraint inherited from CRT-era display design.

This gives a frame rate of 4194304 / 70224 = 59.7275 Hz.

Timer Interrupt

The timer is configured via three registers:

RegisterAddressFunction
DIV0xFF04Divider register (increments at 16384 Hz, read-only effectively)
TIMA0xFF05Timer counter (increments at selected rate, fires IRQ on overflow)
TMA0xFF06Timer modulo (TIMA reloads from TMA on overflow)
TAC0xFF07Timer control: bit 2 = enable, bits 1–0 = clock select

TAC Clock Select

TAC bits 1–0Input clockRate
00CPU clock / 10244096 Hz
01CPU clock / 16262144 Hz
10CPU clock / 6465536 Hz
11CPU clock / 25616384 Hz

GB Studio Timer Configuration

GB Studio sets TAC = 0x07 (enabled, clock/256 = 16384 Hz). The TMA value differs by hardware:

The music_play_isr() handler fires at this rate but only processes music every 4th call (++counter & 3), giving an effective 64 Hz for hUGE_dosound(). See Sound — hUGE Timing for details.

Why 64 Hz for Music?

The hUGE tracker driver runs at 64 Hz rather than VBlank’s ~59.7 Hz for three reasons:

  1. Tempo accuracy. Tracker BPM calculations assume 64 ticks/second (powers of 2). At 59.7 Hz, every song would play ~7% too slow and drift over time.
  2. Note table calibration. The 72-entry note table and subpattern timing are calibrated for 64 Hz. Running at VBlank rate would detune everything.
  3. Clean binary division. 64 Hz divides from the master clock via powers of 2 (4194304 / 216 = 64), giving exact, jitter-free timing. VBlank’s 70224-cycle period has no such clean relationship.
Approximating 64 Hz from VBlank code

Plugin code in state_update() runs at VBlank rate (~59.7 Hz). To match the 64 Hz music tick rate, use a fractional accumulator with 16/15 ratio: 60 × 16/15 ≈ 64. Every 15 VBlank frames, inject one extra tick (16 ticks over 15 frames).

Interrupt Sources

The Game Boy CPU supports 5 interrupt sources, each with a fixed vector address:

PriorityVectorSourceIF/IE bitTypical use
0 (highest)0x0040VBlankBit 0OAM DMA, scroll register updates, frame sync
10x0048LCD STATBit 1LYC match (scanline effects), HBlank, Mode transitions
20x0050TimerBit 2Music driver (hUGE), SFX player
30x0058SerialBit 3Link cable communication
4 (lowest)0x0060JoypadBit 4Wake from STOP mode

In GB Studio, VBlank and Timer are always active. LCD STAT is used for scanline effects (parallax scrolling, split-screen via LYC). Serial and Joypad interrupts are rarely used.

Interrupt Nesting

By default, interrupts are disabled while an ISR executes (IME cleared on entry). GB Studio’s VBL_isr re-enables interrupts (ei) before calling into the GBDK runtime, allowing the timer ISR to fire during VBlank processing. This is how the music driver maintains its 64 Hz tick rate even during heavy VBlank work.

CPU Timing Basics

PropertyDMGCGB (Normal)CGB (Double)
Clock speed4.19 MHz4.19 MHz8.39 MHz
M-cycles/frame175561755635112
Cycles in VBlank114011402280
Budget per scanline114 M-cycles114 M-cycles228 M-cycles

GB Studio runs the CGB in double-speed mode by default, giving twice the CPU budget per frame. However, PPU timing remains the same — VBlank is still ~59.7 Hz and scanlines are still 456 dots.

VBlank budget

You have 1140 M-cycles (DMG) or 2280 M-cycles (CGB double) during VBlank to perform safe VRAM writes, OAM DMA, and register updates. Outside VBlank, writing to VRAM is restricted to HBlank (Mode 0) and OAM is only accessible during HBlank/VBlank.

Memory Map Overview

Address RangeSizeRegion
0x0000–0x3FFF16 KBROM Bank 0 (fixed)
0x4000–0x7FFF16 KBROM Bank N (switchable)
0x8000–0x9FFF8 KBVRAM (2 banks on CGB)
0xA000–0xBFFF8 KBExternal RAM (cartridge SRAM)
0xC000–0xDFFF8 KBWRAM (8 banks on CGB)
0xE000–0xFDFFEcho RAM (mirror of WRAM, don’t use)
0xFE00–0xFE9F160 BOAM (40 sprite entries × 4 bytes)
0xFF00–0xFF7F128 BI/O Registers (joypad, timer, sound, LCD, DMA)
0xFF80–0xFFFE127 BHRAM (fast access, used for OAM DMA routine)
0xFFFF1 BIE register (interrupt enable)

Sound Register Range

0xFF10–0xFF3F — all sound channel control registers and wave RAM live in the I/O register space. See Sound — Channel Registers for the full breakdown.

VRAM Access Safety

VRAM (0x8000–0x9FFF) is only accessible to the CPU during specific PPU modes. Writing during Mode 3 (pixel transfer) silently fails on real hardware. Reading during Mode 3 returns 0xFF on DMG. Getting VRAM access right is one of the most common sources of hard-to-debug visual corruption.

PPU Modes and VRAM Access

ModeNameDurationVRAM AccessOAM Access
Mode 0HBlank~204 dotsYesYes
Mode 1VBlank4560 dots (10 lines)YesYes
Mode 2OAM Scan~80 dotsYesNo
Mode 3Pixel Transfer~172 dots (varies)NoNo

Common Patterns — Ranked by Safety

1. Inside VBL_isr (Safest)

Code executing inside VBL_isr is guaranteed to be in VBlank. No checks needed.

void VBL_isr(void) NONBANKED {
    // ... stock GB Studio code ...
    VRAM[addr] = value;      // always safe
    val = VRAM[addr];        // always returns correct data
}

2. VBlank Wait with Interrupts Disabled (Safe for Setup)

Wait for VBlank by polling LY_REG with interrupts disabled. Gives 4560 dots (~1140 M-cycles on DMG, ~2280 on CGB double) of safe access.

__critical {
    while (LY_REG < 144);    // spin until VBlank starts
    val = VRAM[addr];         // safe: 4560 dots available
    VRAM[addr] = new_val;     // safe
}

__critical compiles to di / ei — prevents ISRs from firing between the LY check and the memory access. Without __critical, an ISR can preempt between the check and the access, leaving you in Mode 3 when you thought you were in VBlank.

3. wait_vram() — NOT Safe for Critical Operations

wait_vram() has an ISR race condition

wait_vram() checks the STAT register for Mode 0/1, but an ISR can fire between the STAT check and your memory access. By the time the CPU executes the load/store instruction, the PPU may have entered Mode 3.

// DANGEROUS — ISR race condition
wait_vram();            // STAT says we're in HBlank...
                        // ← ISR fires here, PPU advances to Mode 3
val = VRAM[addr];       // returns 0xFF on DMG, stale data on CGB

// ALSO DANGEROUS — even with __critical, HBlank is too short
__critical {
    wait_vram();         // waits for Mode 0 (HBlank)
    val = VRAM[addr];    // SDCC generates extra instructions between check and access
}                        // HBlank is only ~204 dots — may overflow into Mode 2/3

The problem is worse during dialog: the VBlank ISR performs heavy work (text rendering, palette writes), widening the window where ISRs can preempt your STAT check.

Failure Symptoms

SymptomLikely Cause
Black lines/pixels on spritesVRAM read returned 0xFF, stored as “original” data
Tile corruption after animationWrite during Mode 3 silently dropped
Intermittent glitches that worsen during dialogISR race with wait_vram()
Works in emulator but not on hardwareMost emulators don’t enforce Mode 3 restrictions
Hardware-dependent timing

These issues are often intermittent and hardware-dependent. CGB double-speed mode has twice the CPU budget per scanline, making races less frequent — but not impossible. Always test on DMG hardware or an accurate emulator (SameBoy, Gambatte) for VRAM timing issues.