⚠️ 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.

The State of GENESIS: Post-BIOS CPU Register Initialization

Date:2025-12-20 StepID:0190 State: ✅ VERIFIED

Summary

The emulator is fully synchronized, but the screen is still blank because the CPU goes into an error loop. Final diagnosis reveals that this is due to an incorrect initial CPU state. Our emulator does not initialize the CPU registers (especially the Flags, F register) to the specific values ​​that the official Boot ROM would have left, causing the game's first few conditional checks to fail.

This Step implements the "Post-BIOS" state directly in the constructor.CoreRegistersin C++, ensuring that the emulator boots with a CPU state identical to that of a real Game Boy. Critical values ​​include the active Z flag (Z=1), which is essential for the first conditional instructions in the startup code to take the correct path.

Hardware Concept: The State of the CPU Post-Boot ROM

The Game Boy's 256-byte Boot ROM not only initializes the peripherals (PPU, Timer, Joypad), but also leaves the CPU registers in a very specific state before transferring control to the cartridge code at address0x0100.

On a real Game Boy, when the console is turned on:

  1. The Boot ROM runs from0x0000until0x00FF.
  2. The Boot ROM performs hardware checksums (cartridge checksum, timer, joypad).
  3. Boot ROM initializes the CPU registers to specific values.
  4. The Boot ROM transfers control to the cartridge code in0x0100through a jump.

The Fundamental Problem:Our emulator does not run a Boot ROM. Instead, we initialize the CPU registers to zero (or simple values). The game code, when booting into0x0100, immediately executes conditional statements likeJR Z, some_error_loopthat expect the Z flag to be in a specific state (e.g.Z=1) that the BIOS would have left. Since our registers start in a "clean" and incorrect state, the jump condition fails, and the CPU is sent to a section of code other than the one for displaying the logo. Goes into a "safe fail" loop, turns off the background (LCDC=0x80), and it stays there, waiting indefinitely.

Post-BIOS values ​​for DMG (according to Pan Docs - "Power Up Sequence"):

  • AF=0x01B0(that is to say,A = 0x01andF = 0xB0). F=0xB0meansZ=1, N=0, H=1, C=1.
  • BC=0x0013
  • DE = 0x00D8
  • HL=0x014D
  • SP = 0xFFFE
  • PC = 0x0100

The initial state ofFlag Z (Z=1)is probably the most critical, since the first instructions are usually conditional jumps based on this flag. If the Z flag is not in the correct state, the game may enter an error loop instead of executing the normal boot routine.

Implementation

The solution is to move the Post-BIOS initialization directly to the constructor.CoreRegistersin C++, eliminating the need for manual initialization in Python. This ensures that each instance ofPyRegistersis automatically created with the correct status.

Modifying the CoreRegisters Constructor

The builder ofCoreRegistersinsrc/core/cpp/Registers.cppnow initialize all registers with Post-BIOS values:

CoreRegisters::CoreRegisters() :
    a(0x01),
    b(0x00),
    c(0x13),
    d(0x00),
    e(0xD8),
    h(0x01),
    l(0x4D),
    f(0xB0), // Flags: Z=1, N=0, H=1, C=1 (0xB0 = 10110000)
    pc(0x0100),
    sp(0xFFFE)
{
    // Post-BIOS initialization completed in the initialization list
    // These values simulate the exact state that the Boot ROM leaves on the CPU
    // before transferring control to the cartridge code at 0x0100
}

Simplification of _initialize_post_boot_state in viboy.py

The method_initialize_post_boot_stateinsrc/viboy.pynow it just checks that the Post-BIOS state was set correctly, removing all redundant mappings. The registers are already initialized correctly in the C++ constructor.

Design Decisions

  • Initialization in the Constructor:Post-BIOS initialization is done in the C++ constructor to ensure that it is always set correctly, without relying on Python code that could be forgotten or executed in the wrong order.
  • DMG values:We use Post-BIOS values ​​for DMG (Game Boy Classic) because our C++ PPU only supports DMG for now. The recordA=0x01indicates DMG, which makes games behave like they do on a gray Game Boy.
  • Flag Z Critical:The Z flag is explicitly set to1because it is essential for the first conditional checks of the game's boot code.

Affected Files

  • src/core/cpp/Registers.cpp- Modified the constructor to initialize registers with Post-BIOS DMG values
  • src/viboy.py- Simplified_initialize_post_boot_stateto eliminate redundant initialization
  • tests/test_core_registers_initial_state.py- New test file to validate the initial Post-BIOS state

Tests and Verification

A new tests file was createdtests/test_core_registers_initial_state.pywith three tests that validate the initial Post-BIOS state:

  • test_registers_post_bios_state:Verify that all registers are initialized with the correct Post-BIOS values.
  • test_registers_post_bios_state_consistency:Verifies that the values ​​of the individual registers are consistent with the 16-bit pairs.
  • test_registers_flag_z_critical:Specifically check that the Z flag is set, as it is critical for the first few conditional checks.

Test results:

$ pytest tests/test_core_registers_initial_state.py -v
============================= test session starts =============================
platform win32 -- Python 3.13.5, pytest-9.0.2, pluggy-1.6.0
collecting ... collected 3 items

tests/test_core_registers_initial_state.py::test_registers_post_bios_state PASSED [ 33%]
tests/test_core_registers_initial_state.py::test_registers_post_bios_state_consistency PASSED [ 66%]
tests/test_core_registers_initial_state.py::test_registers_flag_z_critical PASSED [100%]

============================== 3 passed in 0.06s ==============================

Compiled C++ module validation:The tests directly validate the compiled C++ module (viboy_core), verifying that the constructor ofCoreRegisterscorrectly initializes registers with Post-BIOS values.

Key test code:

def test_registers_post_bios_state():
    """Verify that the CPU registers are initialized with their Post-BIOS values for DMG."""
    regs = PyRegisters()
    
    # Check individual 8-bit registers
    assert regs.a == 0x01
    assert regs.f == 0xB0
    
    # Check 16-bit pairs
    assert regs.af == 0x01B0
    assert regs.bc == 0x0013
    assert regs.de == 0x00D8
    assert regs.hl == 0x014D
    
    # Check 16-bit registers
    assert regs.sp == 0xFFFE
    assert regs.pc == 0x0100
    
    # Check individual flags
    assert regs.flag_z is True
    assert regs.flag_n is False
    assert regs.flag_h is True
    assert regs.flag_c is True

Sources consulted

Educational Integrity

What I Understand Now

  • Post-BIOS Status:Boot ROM leaves the CPU registers in a very specific state before transferring control to the cartridge code. This state is not arbitrary; games depend on it for their first conditional checks.
  • Flag Z Critical:The Z flag is especially important because many of the first instructions in the startup code are conditional jumps based on this flag. If the Z flag is not in the correct state, the game may enter an error loop.
  • Initialization in the Constructor:Moving Post-BIOS initialization to the C++ constructor ensures that it is always set correctly, without relying on Python code that could be forgotten or executed in the wrong order.

What remains to be confirmed

  • Real Emulator Behavior:We need to run the emulator with a real ROM (ex: Tetris) to verify that the correct Post-BIOS state allows the game to run the normal boot routine instead of entering an error loop.
  • Post-BIOS values ​​for CGB:Post-BIOS settings for Game Boy Color (CGB) are different. When we implement full support for CGB, we will need to initialize the registers with different values ​​(ex:A=0x11for CGB).

Hypotheses and Assumptions

Main Hypothesis:With the correct Post-BIOS state, the emulator should be able to execute the game's boot code correctly, passing all conditional checks and finally reaching the routine that copies the logo graphics to VRAM. This is the final piece of the puzzle that should solve the persistent white screen issue.

Next Steps

  • [ ] Run the emulator with a real ROM (ex: Tetris) to verify that the correct Post-BIOS state allows the game to run the normal boot routine
  • [ ] Verify that the Nintendo logo appears on the screen (if the Post-BIOS status is correct, the game should copy the graphics to the VRAM and activate bit 0 of the LCDC)
  • [ ] If the logo appears, celebrate success and document the result in the next Step
  • [ ] If the screen is still blank, investigate other possible problems (e.g. graphics copy routine, LCDC activation, etc.)