⚠️ Clean-Room / Educational

This project is educational and Open Source. No code is copied from other emulators. Implementation based solely on technical documentation and permitted tests.

CGB Post-Boot Clean-Room y Diagnóstico

Date:2026-01-01 StepID:0404 State: VERIFIED

Summary

Clean-room implementation of explicit separation between DMG (Classic Game Boy) and CGB (Game Boy Color) modes for correct initialization of I/O registers according to Pan Docs Power Up Sequence. Added enumHardwareMode(DMG/CGB) in MMU with automatic detection by reading byte 0x0143 of the ROM header. Function implementedinitialize_io_registers()which configures PPU registers, APU, interrupts and CGB-specific registers (VBK, KEY1, SVBK, BCPS/BCPD, OCPS/OCPD, HDMA) according to the current mode. Cython wrappers exposeset_hardware_mode(), get_hardware_mode()andinitialize_io_registers()to Python. Auto detection works correctly: Tetris DX detected as CGB (flag=0x80), logs initialized for CGB mode. System prepared for targeted diagnostic instrumentation (next steps) to identify which registers/conditions are blocking Zelda DX/Pokémon Red on initialization.

Hardware Concept (Pan Docs - Power Up Sequence, CGB Registers)

DMG vs CGB Differences in Power Up Sequence

Classic Game Boy (DMG) and Game Boy Color (CGB) have different initialization sequences. ThePower Up SequenceDocumented in Pan Docs specifies the initial values ​​of the I/O registers after the system boots (with or without Boot ROM).

Common Registries (DMG and CGB)

  • LCDC (0xFF40): 0x91 (LCD ON, BG ON, Window OFF, BG Tilemap 0x9800)
  • STAT (0xFF41): 0x85 (bits 3-7 writable, bits 0-2 controlled by PPU)
  • SCY/SCX (0xFF42/0xFF43): 0x00 (scroll in initial position)
  • BGP (0xFF47): 0xFC (DMG palette: 11221100, white to black)
  • OBP0/OBP1 (0xFF48/0xFF49): 0xFF (sprite palettes)
  • WY/WX (0xFF4A/0xFF4B): 0x00 (Window in initial position)
  • APU Registers (0xFF10-0xFF26): Specific values ​​per channel
  • IF (0xFF0F): 0x01 (initial VBlank interrupt request)
  • IE (0xFFFF): 0x00 (no interrupts initially enabled)

CGB Specific Records

CGB introduces additional records that do NOT exist in DMG:

  • VBK (0xFF4F): VRAM Bank Select. Initial value: 0x00 (bank 0 by default). Allows you to select between 2 VRAM banks of 8KB each.
  • KEY1 (0xFF4D): Prepare Speed ​​Switch. Initial value: 0x00 (normal mode, not double-speed). Allows you to change between 4.19 MHz (normal) and 8.38 MHz (double-speed).
  • SVBK (0xFF70): WRAM Bank Select. Initial value: 0x01 (bank 1 by default). CGB has 8 WRAM banks (4KB each), bank 0 always mapped to 0xC000-0xCFFF.
  • BCPS/BCPD (0xFF68/0xFF69): BG Palette Specification/Data. CGB has 8 BG palettes, each with 4 15-bit colors (BGR555 format). Initial value: 0x00 (index 0, no auto-increment).
  • OCPS/OCPD (0xFF6A/0xFF6B): OBJ Palette Specification/Data. Similar to BG palettes but for sprites. Initial value: 0x00.
  • HDMA1-HDMA5 (0xFF51-0xFF55): Horizontal/General DMA. Allows you to copy data from ROM/RAM to VRAM efficiently. Initial value: 0xFF (idle).

CGB Mode Detection from the ROM Header

The byte0x0143of the cartridge header indicates CGB compatibility:

Worth Meaning Emulator Mode
0x80 CGB functionality (works in DMG too) CGB (preferred)
0xC0 CGB only (only works in CGB) CGB (mandatory)
Others DMG only (Classic Game Boy) DMG

Fountain: Pan Docs - Cartridge Header, 0143 - CGB Flag.

Why this is critical for Zelda DX/Pokémon Red

CGB games expect Color-specific registers to be initialized correctly. Without a clear DMG/CGB separation, the emulator can:

  • Initializing CGB records with incorrect values ​​(or not initializing them at all).
  • Relying excessively onBGP (0xFF47)when CGB games use CGB palettes (BCPS/BCPD).
  • Not configuring VRAM/WRAM banking correctly, causing data corruption.
  • Does not support features such as double-speed mode or HDMA.

Implementing this step ensures that when loading a CGB ROM, all CGB registers are initialized to correct values ​​according to Pan Docs, eliminating a possible cause of a crash on initialization.

Implementation

1. Hardware Mode Enum (MMU.hpp)

/**
 * Step 0404: Hardware Mode - Hardware mode (DMG vs CGB)
 *Source: Pan Docs - Power Up Sequence, CGB Registers
 */
enum class HardwareMode {
    DMG, // Classic Game Boy (monochrome)
    CGB // Game Boy Color
};

2. Mode Management Methods (MMU.hpp)

// private member
HardwareMode hardware_mode_;  // Current hardware mode (DMG or CGB)

// public methods
void set_hardware_mode(HardwareMode mode);
HardwareMode get_hardware_mode() const;
void initialize_io_registers();

3. Initialization of I/O Registers (MMU.cpp)

Functioninitialize_io_registers()which configures registers according to the mode:

void MMU::initialize_io_registers() {
    bool is_cgb = (hardware_mode_ == HardwareMode::CGB);
    
    // ===== PPU / Video =====
    memory_[0xFF40] = 0x91; //LCDC
    memory_[0xFF41] = 0x85; // STAT
    memory_[0xFF42] = 0x00; //SCY
    memory_[0xFF43] = 0x00; //SCX
    memory_[0xFF45] = 0x00; // LYC
    memory_[0xFF46] = 0xFF; // DMA
    memory_[0xFF47] = 0xFC; // BGP
    memory_[0xFF48] = 0xFF; // OBP0
    memory_[0xFF49] = 0xFF; // OBP1
    memory_[0xFF4A] = 0x00; //WY
    memory_[0xFF4B] = 0x00; // WX
    
    // ===== CGB-Specific Registers =====
    if (is_cgb) {
        memory_[0xFF4F] = 0x00; // VBK
        memory_[0xFF4D] = 0x00; //KEY1
        memory_[0xFF70] = 0x01; // SVBK
        memory_[0xFF68] = 0x00; // BCPS
        memory_[0xFF69] = 0x00; // BCPD
        memory_[0xFF6A] = 0x00; // OCPS
        memory_[0xFF6B] = 0x00; // OCPD
        memory_[0xFF51] = 0xFF; // HDMA1
        memory_[0xFF52] = 0xFF; // HDMA2
        memory_[0xFF53] = 0xFF; // HDMA3
        memory_[0xFF54] = 0xFF; // HDMA4
        memory_[0xFF55] = 0xFF; // HDMA5
    }
    
    // ===== Sound (APU) =====
    // ... (registers NR10-NR52) ...
    
    // ===== Interrupts =====
    memory_[0xFF0F] = 0x01; // IF
    memory_[0xFFFF] = 0x00; //IE
}

4. Automatic CGB Mode Detection (MMU.cpp - load_rom)

// Read byte 0x0143 from the header (CGB Flag)
uint8_t cgb_flag = (size > 0x0143) ? data[0x0143] : 0x00;
bool is_cgb_rom = (cgb_flag == 0x80 || cgb_flag == 0xC0);

if (is_cgb_rom) {
    set_hardware_mode(HardwareMode::CGB);
    printf("[MMU] CGB ROM detected (flag=0x%02X). Hardware mode: CGB\n", cgb_flag);
} else {
    set_hardware_mode(HardwareMode::DMG);
    printf("[MMU] DMG ROM detected (flag=0x%02X). Hardware mode: DMG\n", cgb_flag);
}

5. Cython Wrappers (mmu.pxd, mmu.pyx)

Function exposure to Python:

# mmu.pxd
cdef enum class HardwareMode:
    DMG
    CGB

cdef cppclass MMU:
    #...existing methods...
    void set_hardware_mode(HardwareMode mode)
    HardwareMode get_hardware_mode()
    void initialize_io_registers()

# mmu.pyx
def set_hardware_mode(self, str mode):
    """Set the hardware mode (DMG or CGB)"""
    if mode.upper() == "DMG":
        self._mmu.set_hardware_mode(mmu.HardwareMode.DMG)
    elif mode.upper() == "CGB":
        self._mmu.set_hardware_mode(mmu.HardwareMode.CGB)
    else:
        raise ValueError(f"Invalid mode: {mode}")

def get_hardware_mode(self):
    """Gets the current hardware mode"""
    cdef mmu.HardwareMode mode = self._mmu.get_hardware_mode()
    return "CGB" if mode == mmu.HardwareMode.CGB else "DMG"

def initialize_io_registers(self):
    """Initializes I/O registers according to the current mode"""
    self._mmu.initialize_io_registers()

Affected Files

  • src/core/cpp/MMU.hpp-EnumHardwareMode, method declaration, memberhardware_mode_.
  • src/core/cpp/MMU.cpp- Constructor initializes DMG mode, implementationset_hardware_mode(), get_hardware_mode(), initialize_io_registers(), automatic detection inload_rom().
  • src/core/cython/mmu.pxd- Enum declaration and methods for Cython.
  • src/core/cython/mmu.pyx- Python wrappers for hardware mode management.
  • docs/bitacora/entries/2026-01-01__0404__cgb-postboot-cleanroom-y-diagnostico.html- Documentation created (this page).
  • docs/bitacora/index.html- Index updated with new entry.
  • docs/report_phase_2/part_00_steps_0370_0402.md- Updated report with entry from Step 0404.

Tests and Verification

Compilation

$python3 setup.py build_ext --inplace
=== SUCCESSFUL COMPILATION ===
copying build/lib.linux-x86_64-cpython-312/viboy_core.cpython-312-x86_64-linux-gnu.so ->

Result: Successful compilation without errors (only minor printf format warnings).

Automatic Detection Test (Tetris DX - CGB)

$ timeout 5s python3 main.py roms/tetris_dx.gbc > logs/step0404_test_cgb_detection.log 2>&1
$ head -n 50 logs/step0404_test_cgb_detection.log

Relevant output:

[MMU] I/O registers initialized for DMG mode # Initial constructor
[MMU] Configured hardware mode: CGB # Auto Detect
[MMU] I/O registers initialized for CGB mode # Reset for CGB
[MMU] CGB ROM detected (flag=0x80). Hardware mode: CGB
[MBC] ROM loaded: 524288 bytes (32 banks) | Type: 0x03

Result: ✅ Correct automatic detection. Tetris DX (flag=0x80) detected as CGB, registers initialized for CGB mode.

C++ Compiled Module Validation

Confirmed that C++ functions (set_hardware_mode, get_hardware_mode, initialize_io_registers) are called correctly from Python via Cython wrappers. No segmentation errors, no crashes.

Sources consulted

Educational Integrity

What I Understand Now

  • DMG/CGB separation is essential: It is not enough to "support CGB". The system should behave differently depending on the mode.
  • Power Up Sequence varies: CGB has additional registers (VBK, KEY1, SVBK, Paddles, HDMA) that must be initialized correctly.
  • Byte 0x0143 is reliable: The ROM header clearly indicates whether it is DMG (other values), CGB compatible (0x80), or CGB only (0xC0).
  • CGB vs BGP palettes: CGB games use 15-bit paddles (BGR555) via BCPS/BCPD, not the BGP DMG register (0xFF47).
  • Auto detect works: Tetris DX (0x80) detected as CGB, logs initialized correctly according to Pan Docs.

What remains to be confirmed

  • Diagnostic instrumentation: Add critical register monitors (VBK, KEY1, SVBK, BCPS, HDMA5) to detect what condition blocks Zelda DX/Pokémon (Plan Task 0404-2).
  • Rendering with CGB palettes: Confirm that the PPU uses CGB palettes (BCPS/BCPD) instead of BGP when in CGB mode (Plan Task 0404-3).
  • Extensive testing: Verify that Zelda DX/Pokémon Red objectively improve with this separation (metrics: unique_tile_ids, tiledata_nonzero, gameplay_state) (Plan Task 0404-4).
  • Double-speed mode: Confirm behavior of KEY1 when CGB games switch to 8.38 MHz mode (not critical for initialization, but important for compatibility).

Hypotheses and Assumptions

  • Main hypothesis: The DMG/CGB separation will eliminate some initialization crashes in Zelda DX/Pokémon, but there may still be problems with CGB palette rendering (to be validated in Tasks 0404-2, 0404-3, 0404-4).
  • Assumption: Pan Docs values ​​for Power Up Sequence are precise enough. If problems persist, it may be necessary to adjust specific values ​​according to real hardware behavior (requires real Boot ROM or tests on physical hardware).

Next Steps

  • [x]Task 0404-1: Clearly separate "DMG post-boot" vs "CGB post-boot" (completed in this step).
  • [ ] Task 0404-2: Instrumentation to detect "what condition is expected" Zelda DX/Pokémon (VBK/KEY1/SVBK/BCPS/HDMA5/LYC/IE/IF critical register monitors).
  • [ ] Task 0404-3: Rendering adjustment for CGB (confirm that PPU uses CGB palettes instead of BGP).
  • [ ] Task 0404-4: Controlled tests with Tetris DX (baseline), Zelda DX, Pokémon Red (metric analysis with context limits).
  • [ ] Task 0404-5: Complete documentation of Step 0404 with findings from all tasks.