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

Debug: Reimplementation of Triggered Trace to Overcome Initialization Loops

Date:2025-12-20 StepID:0166 State: 🔍 DRAFT

Summary

Analysis of the Step 0165 trace confirmed that the CPU is not in an infinite loop due to a bug, but is correctly executing a very long memory clearing initialization routine. Our fixed length tracing method (200 instructions from PC=0x0100) is inefficient for seeing the code being executedafterof this routine. This Step reimplements the "triggered" tracing system to automatically activate only when the Program Counter (PC) exceeds address 0x0300, allowing us to capture critical hardware configuration code that occurs after cleanup routines.

Hardware Concept: Initialization Routines (BIOS/Game)

Before any game can display graphics or accept input, it must set the "stage." This involves an initialization sequence that typically includes:

  1. Disable interrupts:To prevent external events from interrupting the critical initialization sequence.
  2. Configure the Stack Pointer:Establish a valid memory area for stack operations.
  3. Clean RAM:Zero out large areas such as WRAM (Work RAM) and HRAM (High RAM) to ensure a known and predictable state. This is done with very fast nested loops that can consume thousands of CPU cycles.
  4. Configure hardware registers:PPU (LCDC, BGP, SCY, SCX), APU, Timer, etc.
  5. Copy graphic data (tiles):Transfer the tiles from the ROM to the VRAM.
  6. Activate the screen and interruptions:Finally, enable rendering and events.

Our emulator is correctly executing step 3 (memory cleaning). The previous layout strategy captured these cleaning routines, which are boring and repetitive. Our new strategy is to let the CPU run at full speed through these routines and start recording at step 4, where the hardware configuration we're really interested in occurs.

The problem of the fixed layout:If we start tracing from PC=0x0100 with a limit of 200 instructions, we mainly capture cleanup loops. These loops can execute thousands of instructions before a single pixel is drawn. Our 200 instruction limit barely scratches the surface of this routine.

The solution of the triggered path:We set a "breakpoint" at address 0x0300, which is a safe bet to be beyond the main cleanup routines. The CPU executes silently until it reaches this address, and then we start recording instructions. This allows us to see exactly what hardware registers the game is trying to configure.

Implementation

Changed plotting constants insrc/core/cpp/CPU.cppto change the activation point and the limit of registered instructions.

Modifications in CPU.cpp

Updated the following static constants (lines 7-13):

  • DEBUG_TRIGGER_PC:Changed from0x0100to0x0300. This address is beyond typical memory cleanup routines.
  • DEBUG_INSTRUCTION_LIMIT:Reduced from200to100. Since we are now capturing more relevant code, we don't need as many instructions to identify the problem.

The triggered trace logic was already implemented correctly in the methodstep()(lines 471-484). We just needed to adjust the parameters:

// Static variables for diagnostic logging with system "triggered"
// The trigger is activated after the memory cleaning routines (0x0300)
// to capture critical hardware configuration code
static const uint16_t DEBUG_TRIGGER_PC = 0x0300; // Plot activation address (after cleanup)
static bool debug_trace_activated = false;      // Activation flag
static int debug_instruction_counter = 0;       // Post-activation counter
static const int DEBUG_INSTRUCTION_LIMIT = 100;  // Post-activation limit (directed, shorter)

The activation logic remains the same:

  1. When the PC reaches or exceedsDEBUG_TRIGGER_PC, an activation message is printed and the flag is activateddebug_trace_activated.
  2. As long as the trace is active and we have not reached the limit, each instruction is recorded with its PC and opcode.
  3. The counter is incremented until it reachesDEBUG_INSTRUCTION_LIMIT.

Design Decisions

Why 0x0300?This address is a safe bet based on the knowledge that cleanup routines typically occupy the first 512-768 bytes of memory. 0x0300 (768 decimal) is far enough away to be outside of these routines, but close enough to capture the initial hardware configuration.

Why reduce the limit to 100?Now that we are capturing more relevant code, 100 instructions should be enough to identify the next unimplemented opcode that is blocking rendering. If we need more, we can increase the limit in the future.

Affected Files

  • src/core/cpp/CPU.cpp- Modification of layout constants (lines 7-13). Change ofDEBUG_TRIGGER_PCfrom 0x0100 to 0x0300 and reduction ofDEBUG_INSTRUCTION_LIMITfrom 200 to 100.

Tests and Verification

This change does not affect the functionality of the CPU, it only modifies the behavior of the debugging system. Existing tests must continue to pass without modifications.

Command Executed

pytest -v

Expected Result

All tests should pass, since we only modify debugging constants that do not affect the emulation logic.

Layout Validation

To validate that the new layout system works correctly, the emulator will be run with a test ROM:

python main.py roms/tetris.gb

Expected behavior:

  • The console remains silent while the CPU runs the cleanup loops at full speed.
  • When the PC reaches 0x0300, the message appears:--- [CPU TRACE TRIGGERED at PC: 0x0300] ---
  • The next 100 instructions are registered with your PC and opcode.
  • This new trace should reveal the hardware configuration opcodes (probably related to LCDC, BGP, SCY, SCX).

Sources consulted

Note: Knowledge about initialization routines comes from empirical observation of previous traces and general knowledge of embedded systems architecture.

Educational Integrity

What I Understand Now

  • Initialization routines:Game Boy games run very long memory cleanup routines before setting up the hardware. These routines can consume thousands of CPU cycles.
  • Directed layout:Instead of capturing all instructions from the beginning, it is more efficient to use a "breakpoint" system that is activated only when the PC reaches a specific address.
  • Debugging optimization:Reducing noise in traces allows us to identify real problems more quickly.

What remains to be confirmed

  • Optimal trigger direction:0x0300 is an estimate. It may need to be adjusted if the ROMs have longer or shorter wipe routines.
  • Opcodes not implemented:The new trace will reveal which opcodes remain to be implemented that are blocking graphics rendering.

Hypotheses and Assumptions

Main hypothesis:Address 0x0300 is far enough from the cleanup routines to capture hardware configuration code, but close enough not to lose relevant information.

Assumption:The test ROMs (Tetris, Mario) follow a similar initialization pattern, so 0x0300 should work for all of them.

Next Steps

  • [ ] Recompile the C++ module with.\rebuild_cpp.ps1
  • [ ] Run the emulator withpython main.py roms/tetris.gband analyze the new trace
  • [ ] Identify the hardware configuration opcodes in the trace (LCDC, BGP, SCY, SCX, etc.)
  • [ ] Implement missing opcodes that are blocking rendering
  • [ ] Verify that the emulator advances beyond the initialization routines