This project is educational and Open Source. No code is copied from other emulators. Implementation based solely on technical documentation and permitted tests.
Debug: Instrument default to Capture Unknown Opcodes
Summary
The case was implementeddefaultof the opcode switch on the C++ CPU to detect and explicitly report which unimplemented opcode is causing the logical deadlock. The previous diagnosis confirmed thatL.Y.is stuck at 0 because the CPU repeatedly returns 0 cycles, indicating that it is executing an unknown opcode in an infinite loop. The implemented solution adds aprintfandexit(1)in the casedefaultso that the emulator ends immediately and shows the exact opcode and PC where the problem occurs.
Hardware Concept
In a Game Boy emulator, when the CPU encounters an opcode that is not implemented, it must handle it somehow. In our case, the casedefaultThe switch returned 0 cycles, which caused a logical deadlock:
- The CPU runs an unknown opcode and returns 0 cycles.
- The timing engine in Python accumulates 0 cycles, so it never reaches the threshold of
CYCLES_PER_SCANLINE. - As a result,
L.Y.(scan line) never moves forward, getting stuck at 0. - The main loop continues running (that's why we see the Heartbeat), but the emulation time does not advance.
This instrumentation technique is known as "fail-fast" or "fail-loud": instead of failing silently, the program terminates immediately with a clear message identifying exactly which opcode is missing. This is especially useful during development, when all 256 base opcodes (plus the 256 CB prefix) have not yet been implemented.
Reference:Standard debugging technique in emulation - "Opcode Trap" or "Unimplemented Instruction Handler".
Implementation
The file was modifiedsrc/core/cpp/CPU.cppto add debugging instrumentation in the casedefaultof the opcode switch.
Modifications Made
- Inclusion of headers:was added
#include <cstdlib>to useexit(). - Default instrumentation:Replaced the 0 cycle silent return with a
printfshowing the opcode and PC, followed byexit(1)to terminate the execution immediately.
Implemented Code
The casedefaultnow it has this structure:
default:
// Opcode not implemented - DEBUG INSTRUMENTATION
// This is the "silver bullet" to detect missing opcodes
// We print the opcode and PC, then finish the execution immediately
printf("[CPU FATAL] Opcode not implemented: 0x%02X on PC: 0x%04X\n", opcode, current_pc);
// We stop the emulation abruptly so that the message is the last thing we see
exit(1);
return 0; // It won't run, but it's good practice
Design Decisions
was chosenexit(1)instead of throwing an exception or using logging because:
- Immediacy:Terminates the program immediately, preventing the console from flooding with repeated messages.
- Clarity:The error message is the last thing you see, making it easier to identify.
- Simplicity:It does not require exception handling in Python code, simplifying the debugging flow.
- Debugging:It is a standard technique in emulator development to quickly identify missing opcodes.
Note:This instrumentation is temporary and will be removed or converted to an optional debug mode once all opcodes are implemented.
Affected Files
src/core/cpp/CPU.cpp- Added#include <cstdlib>and modified the casedefaultof the opcode switch.
Tests and Verification
This modification does not require traditional unit tests, as it is a debugging tool. Validation will be done by running the emulator:
- Recompile command:
.\rebuild_cpp.ps1 - Execute command:
python main.py roms/tetris.gb - Expected result:The emulator should immediately terminate with a message like:
[CPU FATAL] Opcode not implemented: 0xXX on PC: 0xYYYY
This message will identify exactly which opcode is missing to be implemented, allowing you to continue debugging in a targeted manner.
Sources consulted
- Standard debugging technique in emulation: "Opcode Trap" or "Unimplemented Instruction Handler"
- "Fail-Fast" principle in software development
Educational Integrity
What I Understand Now
- Logical Deadlock:When the CPU returns 0 cycles repeatedly, the emulation time does not advance, causing
L.Y.get stuck. - Debugging instrumentation:Adding specific code to detect and report issues during development is standard practice.
- Fail-fast:Ending immediately with a clear message is more useful than failing silently or generating massive logs.
What remains to be confirmed
- Missing opcode:Once the emulator is run with this instrumentation, we will know exactly which opcode is not implemented and is causing the deadlock.
- Execution context:The PC where the problem occurs will give us context as to where in the ROM code this opcode is being executed.
Hypotheses and Assumptions
Main hypothesis:The CPU is executing an unimplemented opcode in an infinite loop, causing it to return 0 cycles repeatedly. This instrumentation will definitively confirm or refute this hypothesis.
Next Steps
- [ ] Recompile the C++ module with
.\rebuild_cpp.ps1 - [ ] Run the emulator with
python main.py roms/tetris.gb - [ ] Identify the missing opcode from the error message
- [ ] Implement missing opcode in C++ CPU
- [ ] Verify that the emulator advances past the blocking point
- [ ] Update the status of this entry to VERIFIED once confirmed