Skip to content
RetrogamingDeep Dive Published Updated 6 min readViews unavailable

Game Boy Color Double Speed: KEY1, STOP, and Clock Domains

Switch only on CGB hardware with KEY1, compare the requested speed first, and separate doubled CPU clocks from LCD, audio, DMA, and serial timing.

Game Boy Color double speed is not a switch that makes every part of the handheld run twice as fast. It changes selected clock domains, uses STOP as the switching instruction, and temporarily interrupts CPU execution. A game that assumes the LCD, sound, serial link, and CPU all share one doubled clock will drift or break even if its instruction loop appears to run twice as quickly.

This guide focuses on the KEY1 handshake, safe code boundaries, and the subsystems whose rates change and those that do not. The distinction matters both when writing CGB software and when implementing an emulator’s clock tree.

KEY1 arms the change; STOP performs it

In CGB mode, the KEY1 register at $FF4D exposes two useful bits. Bit 7 is a read-only flag for the current speed: 0 means normal and 1 means double speed. Bit 0 is the read/write prepare flag. Setting it does not immediately change the clock; a STOP instruction performs the transition. Afterward, bit 0 clears automatically and the current-speed flag reports the other mode. A normal-speed startup is documented.

First compare the current-speed flag with the speed the program wants. A request for the mode already active should not arm KEY1 and execute STOP needlessly. GBDK 2020 version 4.5.0 documents cpu_fast() and cpu_slow() as setters, and its implementation checks the current KEY1 speed flag before switching. The example also makes the comparison explicit so the decision is visible at the call site. When switching, keep the transition code short and follow a routine appropriate to the development library or the specific hardware manual.

For Game Boy Color software written with GBDK 2020, its documented helpers hide the direct register sequence:

#include <gb/gb.h>
#include <gb/cgb.h>

void request_double_speed(uint8_t enable)
{
    if (_cpu != CGB_TYPE)
        return;

    uint8_t currently_double = (KEY1_REG & KEY1F_DBLSPEED) != 0;
    if ((enable != 0) == (currently_double != 0))
        return;

    if (enable)
        cpu_fast();
    else
        cpu_slow();
}

This uses GBDK’s documented runtime model check and cpu_fast() / cpu_slow() interface. Build for the Game Boy target and make sure the cartridge header enables the CGB mode your program expects; merely running on CGB-shaped hardware does not make every ROM operate in CGB mode. The cited API documentation is for GBDK 2020 version 4.5.0. It says the helpers temporarily disable interrupts and then re-enable them. In that exact release, the implementation also writes zero to IF before STOP, clearing pending interrupt requests; it restores the prior IE value, not the old IF flags. Do not call it inside a critical section that depends on interrupts remaining disabled, or as though pending requests survive the transition, without checking the implementation and preserving the application’s interrupt policy.

The Nintendo routine and the emulator-facing model

Nintendo’s Game Boy Programming Manual describes the low-level operation as a STOP executed after setting KEY1 bit 0. Its recommended switching flow first checks bit 7 so the program changes to the intended mode rather than blindly toggling. The manual’s example also clears IF and IE and writes $30 to the joypad register before STOP. Pan Docs gives a concise sequence that clears IE, writes $30 to JOYP, arms KEY1, and executes STOP. Treat those as documented routine details, not boilerplate to copy around a library helper without understanding what its code does.

For an emulator, model a change of clock state, not a global multiplication of elapsed time. Pan Docs documents the CPU, timer/divider, serial port, and OAM DMA as running twice as fast in double-speed mode. It lists the LCD video controller, HBlank DMA to VRAM, and sound timings/frequencies as remaining at normal speed. That split is why M-cycle, T-cycle, and LCD-dot counts need explicit units in an emulator: two CPU machine cycles can fit into the time that one previously occupied while the display continues on its own cadence.

The side effects matter too. The divider is reset around a speed transition, which can affect timer phase and divider-driven audio behavior; consult the timer model separately rather than adding an arbitrary TIMA increment. The serial port’s fast mode and the system double-speed multiplier are separate facts: calculate the effective transfer rate from both control bits and the selected clock source. Likewise, OAM DMA’s relationship to CPU cycles differs from VRAM HDMA, whose stated wall-clock transfer duration stays unchanged.

STOP adds a timing and display boundary

A speed switch is not instantaneous execution with a new multiplier. Current Pan Docs reports that the CPU pauses for 2,050 M-cycles (8,200 T-cycles) after STOP and describes frozen VRAM/OAM access-lock behavior whose visible effect depends on the PPU mode. It also marks the question of interrupts during that interval as unresolved. Nintendo’s programming manual does not describe those same detailed PPU effects.

For software, avoid placing a switch in the middle of a timing-sensitive audio, serial, raster, or display operation unless that behavior is intentional and tested. Double speed also consumes more power, so switching at deliberate mode boundaries, such as a title screen or a computation-heavy menu, is easier to reason about than changing clocks in the middle of every frame.

For emulation, retain this source distinction in tests and documentation. Pan Docs is an actively maintained reverse-engineered reference, while Nintendo’s manual is the first-party programming document; do not silently present every observed timing behavior as a universal Nintendo guarantee. Pin the target hardware revision, emulator test ROM, and cycle unit when you rely on details around STOP, interrupt service, or PPU locking.

Debug by clock domain

When a transition appears to hang or desynchronize a game, log KEY1 before and after the instruction, the armed bit, CPU speed, divider state, timer phase, interrupt registers, PPU mode, and serial control/rate. A screenshot alone cannot distinguish a display-clock error from a CPU-clock error.

Useful checks include:

  • On CGB-capable hardware in CGB mode, request normal speed while already normal and double speed while already double; neither should perform an unnecessary toggle.
  • Switch once in each direction and verify KEY1 bit 7 and automatic clearing of bit 0.
  • Count CPU cycles and divider ticks over the same LCD frame; only the documented domains should change.
  • Compare normal and double speed for timer interrupts, serial transfer, OAM DMA duration, LCD line/frame timing, and sound frequency.
  • Trigger switches in different PPU modes in an emulator and mark which behavior comes from a first-party manual versus reverse-engineered documentation or a test ROM.
  • Run the GBDK snippet on a CGB-mode build and on a DMG-compatible target; confirm the guard avoids attempting the CGB-specific helper path.

If only serial timing fails, inspect the serial speed-select bit and master/slave clock source before altering the global CPU ratio. If audio pitch changes, check whether the sound generator was incorrectly tied to the CPU clock. If a frame lasts half as long in the emulator, the LCD clock was probably doubled along with the CPU.

Double speed is best understood as a selective clock-domain change mediated by KEY1 and STOP, not as a whole-console fast-forward command. Keeping that boundary explicit makes game code, timing tools, and emulator tests much easier to verify.

Related:

Sources:

Comments