This project is educational and Open Source. No code is copied from other emulators. Implementation based solely on technical documentation and permitted tests.
Extended Outage Audit and DMA
Summary
Extension of emulator diagnostic instrumentation to capture the entire execution flow of interrupt handlers and monitor critical DMA and VRAM operations. Increased Handler Sniper limit to 500 instructions, detection of RET (0xC9) in addition to RETI (0xD9), implementation of specific monitor for DMA OAM triggering ([DMA-TRIGGER]) and temporal monitor without filters for VRAM ([VRAM-TOTAL]).
Hardware Concept
Interrupt Handlers:When an interrupt is triggered on the Game Boy, the CPU automatically jumps to a specific interrupt vector (ex: 0x0040 for V-Blank). The code in that vector (the "handler") must execute completely and end with a RETI (Return and Enable Interrupts, 0xD9) instruction that restores the previous state and re-enables the interrupts. Some poorly implemented handlers may use RET (0xC9) instead of RETI, which does not enable interrupts and can cause problems.
DMA (Direct Memory Access):The DMA register (0xFF46) allows 160 bytes to be transferred from any memory address to OAM (Object Attribute Memory, 0xFE00-0xFE9F) in a single cycle. When a value is written to 0xFF46, the hardware automatically copies 160 bytes from the address (value<< 8) a OAM. Esta operación es crítica para cargar sprites en el juego.
VRAM (Video RAM):The 0x8000-0x9FFF region contains the tile and tilemap data that the PPU reads for rendering. Monitoring all writes to VRAM (without filters) helps detect any suspicious activity that may be causing rendering issues.
Fountain:Pan Docs - "Interrupts", "DMA Transfer", "VRAM (Video RAM)"
Implementation
Four major improvements were made to diagnostic instrumentation:
1. Handler's Sniper Limit Increase
The Handler Sniper limit ([HANDLER-EXEC]) was increased from 100 to 500 instructions to capture the entire handler execution flow until return. This is necessary because some handlers may be longer than expected and we need to see the entire execution flow.
Modified code in CPU.cpp:
// Trace instructions inside the handler
// --- Step 0286: Increased limit to 500 instructions to capture full flow ---
if (in_vblank_handler && handler_step_count< 500) {
// ... código de rastreo ...
}
2. Detection of RET (0xC9) in addition to RETI (0xD9)
Added detection of the RET (0xC9) instruction in addition to RETI (0xD9) to identify handlers that terminate without enabling IME. This is important because some poorly implemented handlers can use RET instead of RETI, which causes interrupts to not be reset correctly.
Modified code in CPU.cpp:
// --- Step 0286: Detection of RET (0xC9) in addition to RETI (0xD9) ---
// Some handlers can terminate RET without enabling IME, which is a bug
// but we must detect it to understand the complete flow of the handler
if (op == 0xD9) {
printf("[HANDLER-EXIT] RETI detected on PC:0x%04X. End of handler trace.\n", original_pc);
in_vblank_handler = false;
} else if (op == 0xC9) {
printf("[HANDLER-EXIT] RET detected on PC:0x%04X (WITHOUT IME enabled). End of handler trace.\n", original_pc);
in_vblank_handler = false;
}
3. DMA OAM Trigger Monitor ([DMA-TRIGGER])
A specific monitor was implemented that detects when DMA is activated to transfer data to OAM. The monitor reports the source address, the range of addresses to be copied, and the PC where DMA was activated.
Code added in MMU.cpp:
// --- Step 0286: DMA OAM Trigger Monitor ([DMA-TRIGGER]) ---
// Detects when DMA is activated to transfer data to OAM (0xFE00-0xFE9F)
// The DMA copies 160 bytes from the address (value<< 8) a OAM
// Fuente: Pan Docs - "DMA Transfer": Escritura en 0xFF46 inicia transferencia
printf("[DMA-TRIGGER] DMA activado: Source=0x%02X00 (0x%04X-0x%04X) ->OAM (0xFE00-0xFE9F) | PC:0x%04X\n",
value, static_cast<uint16_t>(value) << 8, (static_cast<uint16_t>(value) << 8) + 159, debug_current_pc);
4. Temporal Monitor Without Filters for VRAM ([VRAM-TOTAL])
Implemented a temporary monitor that captures ALL writes to VRAM without filters to detect any suspicious activity. This monitor complements the existing [VRAM-VIBE] monitor that filters common initialization values.
Code added in MMU.cpp:
// --- Step 0286: Temporal Monitor Without Filters for VRAM ([VRAM-TOTAL]) ---
// Captures ALL writes to VRAM without filters to detect any suspicious activity.
// This monitor is temporary and is used for diagnostics when there are problems loading graphics.
// Source: Pan Docs - "VRAM (Video RAM)": 0x8000-0x9FFF contains Tile Data and Tile Maps
static int vram_total_count = 0;
if (vram_total_count< 500) { // Límite alto para capturar actividad completa
printf("[VRAM-TOTAL] Write %04X=%02X PC:%04X (Bank:%d)\n",
addr, value, debug_current_pc, current_rom_bank_);
vram_total_count++;
}
Design Decisions
- Limit of 500 instructions:500 was chosen as a balance between capturing long handlers and avoiding log saturation. If a handler needs more than 500 instructions, there is probably a more serious problem.
- RET Detection:It was decided to detect RET in addition to RETI to identify potential bugs in the game code, although technically RET should not be used in interrupt handlers.
- Temporary VRAM Monitor:It was implemented as a temporary monitor (with a limit of 500 reports) to avoid log saturation. Once the problem is identified, this monitor can be disabled or reduced.
Affected Files
src/core/cpp/CPU.cpp- Handler's Sniper limit increased and RET detectionsrc/core/cpp/MMU.cpp- Implementation of [DMA-TRIGGER] and [VRAM-TOTAL] monitors
Tests and Verification
The instrumentation was validated by compiling and running the emulator. The monitors generate logs that can be analyzed to diagnose interrupt, DMA, and VRAM problems.
Compiled C++ module validation:The changes compiled correctly without syntax errors or warnings. The instrumentation runs at runtime and generates logs when the monitored conditions are activated.
Compile command:
python setup.py build_ext --inplace
Result:Successful build without errors.
Sources consulted
- Bread Docs:Interrupts- RETI outage vectors and behavior
- Bread Docs:DMA Transfer- DMA register operation (0xFF46)
- Bread Docs:VRAM (Video RAM)- Memory region 0x8000-0x9FFF
Educational Integrity
What I Understand Now
- Interrupt Handlers:Handlers must terminate RETI to restore state and re-enable interrupts. RET is not sufficient because it does not enable IME.
- DMA:The DMA register allows 160 bytes to be transferred to OAM in a single cycle. It is critical for loading sprites into the game.
- Instrumentation:Diagnostic monitors should run before any early returns to capture all runs, even when there are interruptions.
What remains to be confirmed
- Real behavior of long handlers:Is it common for handlers to have more than 100 instructions? The 500 limit should capture most cases.
- DMA Frequency:How often is DMA triggered in real games? The [DMA-TRIGGER] monitor will help us understand this.
- VRAM writing patterns:The [VRAM-TOTAL] monitor will help us identify suspicious writing patterns that may be causing rendering problems.
Hypotheses and Assumptions
We assume that a limit of 500 instructions is enough to capture most interrupt handlers. If we find longer handlers, we can increase the limit or investigate why they are so long.
We assume that detecting RET in handlers is useful for identifying bugs, although technically RET should not be used in interrupt handlers. If we find many cases of RET, it may indicate a deeper problem in interrupt emulation.
Next Steps
- [ ] Analyze logs generated by the new monitors to identify behavior patterns
- [ ] Verify that interrupt handlers terminate correctly with RETI
- [ ] Confirm that the DMA is activated at the expected times during the game execution
- [ ] Review VRAM write patterns to identify potential graphics loading issues
- [ ] Adjust monitor limits if necessary based on collected data