This project is educational and Open Source. No code is copied from other emulators. Implementation based solely on technical documentation and permitted tests.
Final Debug: Reactivating the CPU Trace to Catch the Loop
Summary
The Step 0204 VRAM sensor has confirmed that the CPU never attempts to write to the video memory. This means that the emulator is trapped in a software logic loop (a "wait loop") at the start of the ROM execution, before any graphics routines. To identify this loop, we reactivate the CPU trace system to capture the first 200 instructions executed since boot, revealing the infinite loop pattern and allowing us to understand what hardware condition we are not meeting.
Hardware Concept: Control Flow Analysis
If the CPU does not advance, it is because it is executing a conditional jump (J.R., J.P., CALL, RET) which always takes her back to the same point. By looking at the sequence of instructions, we will identify the loop (ex: "Read register X, Compare with Y, Jump if not equal").
Common Game Boy boot loops include:
- Joypad Loop:
LD A, (FF00)→BIT...→JR...(Waiting for a button to be released). - Timer Loop:
LD A, (FF04)→CP...→JR...(Waiting for the timer to advance). - V-Blank Loop:
LDH A, (44)(Lee LY) →CP 90(Compare with 144) →JR NZ(Skip if not VBlank). - Checksum Loop:Memory reading and mathematical comparisons.
The last pattern that repeats in the trace will be our culprit. By seeing the exact sequence of instructions, we will be able to identify which register or flag the game is checking and why it fails.
Implementation
We implement a simple layout system inCPU::step()which prints the first 200 instructions executed. The trace captures the state of the CPU before each instruction is executed, including:
- Instruction counter (0-199)
- Current Program Counter (PC)
- Opcode to be executed
- Status of all main records (AF, BC, DE, HL, SP)
Modified components
src/core/cpp/CPU.cpp: Added tracing system with static variables to control the instruction limit.
Implemented code
The trace is implemented just before the opcode fetch, to capture the PC before it is modified:
// --- CPU TRACE (Step 0205) ---
// Static variables for trace control
static int debug_trace_counter = 0;
static const int DEBUG_TRACE_LIMIT = 200;
// Print the first N instructions to identify the boot loop
if (debug_trace_counter< DEBUG_TRACE_LIMIT) {
uint8_t opcode_preview = mmu_->read(regs_->pc);
printf("[CPU TRACE %03d] PC: 0x%04X | Opcode: 0x%02X | AF: 0x%04X | BC: 0x%04X | DE: 0x%04X | HL: 0x%04X | SP: 0x%04X\n",
debug_trace_counter, regs_->pc, opcode_preview, regs_->af, regs_->get_bc(), regs_->get_de(), regs_->get_hl(), regs_->sp);
debug_trace_counter++;
}
// --------------------------------
Design decisions
- Limit of 200 instructions:Enough to capture several cycles of a repeating loop without flooding the console.
- Static variables:They allow you to maintain the status of the counter between calls to
step()without needing to modify the class interface. - Previous reading of the opcode:We read the opcode directly from memory before calling
fetch_byte()so as not to modify the PC before printing the status. - Inclusion of all records:The complete state of the registers allows you to identify which values the loop is comparing.
Affected Files
src/core/cpp/CPU.cpp- Added layout system with#include <cstdio>and static control variables.
Tests and Verification
To verify the layout:
- Recompile the C++ module:Execute
.\rebuild_cpp.ps1to compile the changes. - Run the emulator:Execute
python main.py roms/tetris.gb > cpu_trace.logto redirect the output to a file. - Parse the output:Look for repetitive patterns in the log that indicate the infinite loop.
Compiled C++ module validation:The trace is executed within the compiled C++ code, ensuring that we capture the actual execution flow of the emulated CPU.
Sources consulted
- Bread Docs:CPU Instruction Set
- Implementation based on general knowledge of LR35902 architecture and emulator debugging techniques.
Educational Integrity
What I Understand Now
- Control flow analysis:Infinite loops in emulation are usually caused by conditional jumps that are never fulfilled, waiting for a hardware condition that is not being emulated correctly.
- Layout instructions:Capturing the state of the CPU before each instruction allows you to identify repetitive patterns that reveal waiting loops.
- Output limitation:It is critical to limit the number of traces to avoid overwhelming the console and hanging the terminal, especially in infinite loops.
What remains to be confirmed
- Loop pattern:Once the trace is executed, we need to analyze the output to identify which instructions are repeated and what condition they are waiting for.
- Root cause:After identifying the loop, we need to determine which hardware component is not working correctly (Timer, PPU, Joypad, etc.).
Hypotheses and Assumptions
We assume that the loop is caused by an unmet hardware condition, not by an error in the implementation of the instructions. The layout will allow us to confirm or refute this hypothesis.
Next Steps
- [ ] Run the emulator with tracing activated and analyze the output.
- [ ] Identify the repetitive pattern in the first 200 instructions.
- [ ] Determine what hardware condition the loop is waiting for.
- [ ] Implement the fix for the missing or incorrect hardware component.