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

Final Debug: Reactivating the CPU Trace to Hunt the Logic Loop

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

Summary

The "VRAM Sensor" of Step 0194 has confirmed with certainty that the CPUnever tries to write to VRAM. Even though the emulator ran for several seconds and hundreds of frames, the message[VRAM WRITE DETECTED!] never appeared.

Since alldeadlockshardware issues have been resolved (L.Y.cycles correctly), the only possible explanation is that the CPU is stuck in ainfinite logic loopin the ROM code itself, before reaching the routine that copies the graphics to the VRAM.

This Step reactivates the CPU's tracing system in C++ to capture the sequence of instructions that make up the infinite loop, identify the pattern, and deduce the exit condition that is not being met.

Engineering Concept: Software Loop Isolation

We have gone from debugging our emulator to debugging the ROM itself that runs on it. We need to see the assembly code it is running to understand its logic. A trace of the last executed instructions will show us a repeating pattern of addresses.PC.

When analyzing theopcodesIn those addresses, we can deduce what the game is checking. Are you expecting a specific value in an I/O register that we haven't initialized correctly? Are you checking a flag that our ALU calculates subtly incorrectly in an edge case? The trace will tell us.

Principle of the Triggered Layout:Instead of tracing from the beginning (which would generate too much noise), we activate tracing when thePCreaches0x0100(start of cartridge code). This gives us a clear window into the game's code execution, without the noise of the BIOS initialization code.

Instruction Limit:We configure the trace to capture the first 200 instructions after activation. This is enough to see a clear loop pattern. If the loop is longer, we can increase the limit, but 200 is usually enough to identify the pattern.

Implementation

We've reintroduced the plotting logic we used in Steps 0169-0170, but this time set it to fire at the start of the cartridge code and give us a clear window into execution.

Modified components

  • src/core/cpp/CPU.cpp: Added include<cstdio>and layout system in the methodstep()

Layout Code

The trace uses static variables to maintain state between calls:

// --- Variables for CPU Plotting (Step 0195) ---
static bool debug_trace_activated = false;
static int debug_instruction_counter = 0;
// A limit of 200 is enough to see a loop pattern.
static const int DEBUG_INSTRUCTION_LIMIT = 200;

// In the CPU constructor, reset the trace state
CPU::CPU(MMU* mmu, CoreRegisters* registers) : /*...*/ {
    debug_trace_activated = false;
    debug_instruction_counter = 0;
}

// In the step() method, before fetch_byte():
uint16_t current_pc = regs_->pc;

// --- Layout Logic (Step 0195) ---
// We start tracing from the beginning to catch the loop.
if (!debug_trace_activated && current_pc >= 0x0100) {
    debug_trace_activated = true;
    printf("--- [CPU TRACE ACTIVATED at PC: 0x%04X] ---\n", current_pc);
}

if (debug_trace_activated && debug_instruction_counter< DEBUG_INSTRUCTION_LIMIT) {
    uint8_t opcode_for_trace = mmu_->read(current_pc);
    printf("[CPU TRACE %d] PC: 0x%04X | Opcode: 0x%02X\n", debug_instruction_counter, current_pc, opcode_for_trace);
    debug_instruction_counter++;
}
// --- End of Layout ---

Design decisions

  • Activation at 0x0100:The cartridge code starts at0x0100. We enable tracing here to avoid noise from the BIOS initialization code.
  • Limit of 200 instructions:It is enough to see a loop pattern. If the loop is longer, we can easily increase the limit.
  • Reading the opcode before fetch:We read the opcode directly from memory usingmmu_->read(current_pc)beforefetch_byte()consume it. This gives us the exact opcode to be executed.

Affected Files

  • src/core/cpp/CPU.cpp- Added include<cstdio>and layout system in the methodstep()
  • docs/bitacora/entries/2025-12-20__0195__debug-final-reactivation-trace-cpu-hunt-logical-loop.html- New log entry
  • docs/bitacora/index.html- Updated with new entry
  • REPORT_PHASE_2.md- Updated with Step 0195

Tests and Verification

The verification of this Step is mainly about compiling and running the emulator. The expected result is that the CPU trace shows a repeating pattern of address addresses.PCwhich form the infinite loop.

Verification Process

  1. Recompile the C++ module: .\rebuild_cpp.ps1
    • Result: ✅ Successful compilation (with expected minor warnings)
  2. Run the emulator: python main.py roms/tetris.gb
    • The emulator should run normally. The user must press a key to loop the Joypad.
  3. Observe the console:The trace will search for the message[CPU TRACE ACTIVATED at PC: 0xXXXX]followed by the first 200 instructions executed.

C++ Compiled Module Validation

The emulator uses the compiled C++ module (viboy_core), which contains the layout system implemented inCPU::step(). Each executed instruction will go through this method and will be traced if applicable.

Expected Result

The CPU trace will show us the loop. For example, we could see something like:

[CPU TRACE 195] PC: 0x00A5 | Opcode: 0xE0
[CPU TRACE 196] PC: 0x00A7 | Opcode: 0xE6
[CPU TRACE 197] PC: 0x00A8 | Opcode: 0x20
[CPU TRACE 198] PC: 0x00A5 | Opcode: 0xE0
[CPU TRACE 199] PC: 0x00A7 | Opcode: 0xE6

This pattern will tell us that the instructions in0x00A5, 0x00A7and0x00A8they form the loop. By looking at what those opcodes do (e.g.LDH, AND, JR NZ), we will be able to deduce the exact condition that is failing and apply the final fix.

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

  • CPU trace:Instruction tracing is a fundamental tool for debugging emulators. It allows us to see exactly what instructions the CPU is executing and in what order.
  • Loop Patterns:Infinite loops in low-level code manifest as repeating patterns of loop addresses.PC. Identifying these patterns is the first step in understanding what condition is failing.
  • Problem Isolation:We have eliminated all hardware causes (deadlocks, synchronization, etc.). The remaining problem is a pure software loop, which requires assembly code analysis.

What remains to be confirmed

  • Loop Pattern:We need to run the emulator and analyze the trace to identify the exact loop pattern.
  • Output Condition:Once the loop is identified, we need to deduce what condition the game is expecting and why it is not being met.
  • Final Correction:Once the condition is identified, we will need to apply the corresponding correction (registry initialization, flag correction, etc.).

Hypotheses and Assumptions

Main Hypothesis:The game is waiting for a specific value in an I/O register that we haven't initialized correctly, or it's checking a flag that our ALU calculates subtly incorrectly in an edge case.

Assumption:The loop is short enough (less than 200 instructions) that we can capture it completely with our current limit. If the loop is longer, we can easily increase the limit.

Next Steps

  • [ ] Run the emulator and analyze the CPU trace
  • [ ] Identify the repetitive pattern of addressesPCthat form the loop
  • [ ] Analyze the opcodes at those addresses to deduce what condition the game is expecting
  • [ ] Apply the corresponding correction (register initialization, flag correction, etc.)
  • [ ] Verify that the game exits the loop and begins writing to VRAM