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:
| Subsystem | Derivation | Rate |
|---|---|---|
| CPU (Normal) | 4194304 / 1 | 4.19 MHz (1 M-cycle = 4 T-cycles) |
| CPU (CGB Double) | 8388608 / 1 | 8.39 MHz |
| PPU dot clock | 4194304 / 1 | 4.19 MHz |
| VBlank | 4194304 / 70224 | ~59.7 Hz |
| Timer (TAC clock/256) | 4194304 / 65536 | 64 Hz |
| DIV register | 4194304 / 256 | 16384 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:
| Phase | Lines | Dots/line | Total dots |
|---|---|---|---|
| Visible (Mode 0/2/3) | 144 | 456 | 65664 |
| VBlank (Mode 1) | 10 | 456 | 4560 |
| Total | 154 | 456 | 70224 |
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:
| Register | Address | Function |
|---|---|---|
DIV | 0xFF04 | Divider register (increments at 16384 Hz, read-only effectively) |
TIMA | 0xFF05 | Timer counter (increments at selected rate, fires IRQ on overflow) |
TMA | 0xFF06 | Timer modulo (TIMA reloads from TMA on overflow) |
TAC | 0xFF07 | Timer control: bit 2 = enable, bits 1–0 = clock select |
TAC Clock Select
| TAC bits 1–0 | Input clock | Rate |
|---|---|---|
00 | CPU clock / 1024 | 4096 Hz |
01 | CPU clock / 16 | 262144 Hz |
10 | CPU clock / 64 | 65536 Hz |
11 | CPU clock / 256 | 16384 Hz |
GB Studio Timer Configuration
GB Studio sets TAC = 0x07 (enabled, clock/256 = 16384 Hz). The TMA value differs by hardware:
- CGB:
TMA = 0x80→ TIMA counts 128 ticks before overflow → 16384 / 128 = 128 Hz - DMG:
TMA = 0xC0→ TIMA counts 64 ticks before overflow → 16384 / 64 = 256 Hz
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:
- 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.
- Note table calibration. The 72-entry note table and subpattern timing are calibrated for 64 Hz. Running at VBlank rate would detune everything.
- 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.
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:
| Priority | Vector | Source | IF/IE bit | Typical use |
|---|---|---|---|---|
| 0 (highest) | 0x0040 | VBlank | Bit 0 | OAM DMA, scroll register updates, frame sync |
| 1 | 0x0048 | LCD STAT | Bit 1 | LYC match (scanline effects), HBlank, Mode transitions |
| 2 | 0x0050 | Timer | Bit 2 | Music driver (hUGE), SFX player |
| 3 | 0x0058 | Serial | Bit 3 | Link cable communication |
| 4 (lowest) | 0x0060 | Joypad | Bit 4 | Wake 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
| Property | DMG | CGB (Normal) | CGB (Double) |
|---|---|---|---|
| Clock speed | 4.19 MHz | 4.19 MHz | 8.39 MHz |
| M-cycles/frame | 17556 | 17556 | 35112 |
| Cycles in VBlank | 1140 | 1140 | 2280 |
| Budget per scanline | 114 M-cycles | 114 M-cycles | 228 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.
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 Range | Size | Region |
|---|---|---|
0x0000–0x3FFF | 16 KB | ROM Bank 0 (fixed) |
0x4000–0x7FFF | 16 KB | ROM Bank N (switchable) |
0x8000–0x9FFF | 8 KB | VRAM (2 banks on CGB) |
0xA000–0xBFFF | 8 KB | External RAM (cartridge SRAM) |
0xC000–0xDFFF | 8 KB | WRAM (8 banks on CGB) |
0xE000–0xFDFF | — | Echo RAM (mirror of WRAM, don’t use) |
0xFE00–0xFE9F | 160 B | OAM (40 sprite entries × 4 bytes) |
0xFF00–0xFF7F | 128 B | I/O Registers (joypad, timer, sound, LCD, DMA) |
0xFF80–0xFFFE | 127 B | HRAM (fast access, used for OAM DMA routine) |
0xFFFF | 1 B | IE 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
| Mode | Name | Duration | VRAM Access | OAM Access |
|---|---|---|---|---|
| Mode 0 | HBlank | ~204 dots | Yes | Yes |
| Mode 1 | VBlank | 4560 dots (10 lines) | Yes | Yes |
| Mode 2 | OAM Scan | ~80 dots | Yes | No |
| Mode 3 | Pixel Transfer | ~172 dots (varies) | No | No |
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() 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
| Symptom | Likely Cause |
|---|---|
| Black lines/pixels on sprites | VRAM read returned 0xFF, stored as “original” data |
| Tile corruption after animation | Write during Mode 3 silently dropped |
| Intermittent glitches that worsen during dialog | ISR race with wait_vram() |
| Works in emulator but not on hardware | Most emulators don’t enforce Mode 3 restrictions |
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.