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

CPU: Implementation of CP d8 Immediate Comparison

Date:2025-12-20 StepID:0161 State: ✅ VERIFIED

Summary

Step 0160's debug instrumentation successfully identified the missing opcode causing the deadlock:0xFE (CP d8)inPC: 0x02B4. Immediate comparison statement implementedCP d8, which compares register A with an unmodified 8-bit immediate value A, updating only the flags. This instruction is critical for the game's conditional flow control. In addition, the behavior of the case was changeddefaultofexit(1)to a non-fatal warning to allow the emulation to continue and detect other missing opcodes.

Hardware Concept

The instructionCP d8(Compare A with immediate 8-bit value) is one of the most fundamental instructions of any CPU. It works like a"ghost subtraction":

  • Internally, calculateA - d8.
  • Update the flags(Z, N, H, C) based on the result of that subtraction.
  • But it does NOT save the result.The registry valueTOremains intact.

Why is it so critical?It's the program's way of asking questions. After aCP, the code uses a conditional jump statement (JR Z, JR NZ, JP C, etc.) to make a decision:

  • CP 0x0Afollowed byJR Z,...means: "Is A equal to 10? If so, jump."
  • CP 0x00followed byJR NZ, ...means: "Is A different from zero? If so, jump."

The Root Cause of Deadlock:The game, after its cleaning loops, reachedPC: 0x02B4and I needed to ask a crucial question to decide what to do next. But our CPU didn't know how to "compare". fell into thedefault, returned 0 cycles, and the emulator froze in time. The screen was blank because the game could never make the decision to start copying the graphics.

Reference:Pan Docs - CPU Instruction Set, section "CP A, n" (opcode 0xFE).

Implementation

Opcode implemented0xFE (CP d8)in C++ CPU and modified case behaviordefaultto allow the emulation to continue detecting other missing opcodes.

Created/Modified Components

  • CPU.cpp:Added case0xFEin the opcode switch that reads the next byte and callsalu_cp().
  • CPU.cpp:Modified the casedefaultto use warning instead ofexit(1).
  • test_core_cpu_compares.py:Created new tests file with 4 test cases forCP d8.

Implementation of Opcode 0xFE

The helperalu_cp()already existed in the code (previously implemented for other comparison opcodes likeCP B, CP C, etc.). All that was left was to add the specific case forCP d8:

case 0xFE://CP d8 (Compare A with immediate 8-bit value)
{
    // CP d8: Compare A with an 8-bit immediate value
    // Reads the next byte from memory and compares it with A
    // Does not modify A, only updates flags
    uint8_t value = fetch_byte();
    alu_cp(value);
    cycles_ += 2;  // 1 M-Cycle for opcode, 1 M-Cycle for reading d8
    return 2;
}

Modification of the Default Case

Once the critical opcode was identified and corrected, the behavior of the case was changeddefaultto allow the emulation to continue:

default:
    // Opcode not implemented - WARNING (non-fatal)
    // Now that we have identified and corrected the critical opcode (0xFE),
    // switch to warning mode to allow the emulation to continue
    // and detect other missing opcodes without crashing
    printf("[CPU WARN] Opcode not implemented: 0x%02X on PC: 0x%04X. Returning 0 cycles.\n", opcode, current_pc);
    cycles = 0; // Return 0 to signal a problem without crashing
    break;

Design Decisions

Timing: CP d8consumes 2 M-Cycles: 1 to read the opcode and 1 to read the immediate byted8. This is consistent with other 8-bit immediate instructions such asADD A, d8eitherSUB d8.

Reused Helper:The helper was usedalu_cp()already existing, which correctly implements the flag comparison and update logic according to the hardware specification.

Warning mode:changeexit(1)a warning allows the emulation to continue and detect other missing opcodes without needing to recompile and run repeatedly.

Affected Files

  • src/core/cpp/CPU.cpp- Added case0xFEin the opcodes switch and modified the casedefaultto use warning.
  • tests/test_core_cpu_compares.py- Created new test file with 4 test cases forCP d8.

Tests and Verification

A new test file was created specifically for comparison instructions:tests/test_core_cpu_compares.py.

  • Command executed: pytest tests/test_core_cpu_compares.py -v
  • Expected result:4 tests passing (test_cp_d8_equal, test_cp_d8_less, test_cp_d8_greater, test_cp_d8_half_borrow)

Test code (key fragment):

def test_cp_d8_equal(self, setup):
    """Checks CP d8 when A == value (Z=1)"""
    cpu, mmu, regs = setup
    regs.a = 0x42
    regs.pc = 0x0100
    
    # CP d8: Compare A with 0x42
    mmu.write(0x0100, 0xFE) # Opcode CP d8
    mmu.write(0x0101, 0x42) # Immediate value: 0x42
    
    cycles = cpu.step()
    
    # Verify that A did not change
    assert regs.a == 0x42, f"A should not change, it is {regs.a}"
    
    # Check flags
    assert regs.flag_z is True, "Z must be active (A == value)"
    assert regs.flag_n is True, "N must be active (it is subtraction)"
    assert regs.flag_c is False, "C must be off (A >= value)"
    
    # Check PC advanced
    assert regs.pc == 0x0102, f"PC must be 0x0102, it is 0x{regs.pc:04X}"
    
    # Check cycles
    assert cycles == 2, f"CP d8 must consume 2 M-Cycles, consumed {cycles}"

Native Validation:The tests validate the compiled C++ module directly, verifying that the native implementation works correctly.

Verification in Emulator:When runningpython main.py roms/tetris.gb, the emulator should advance beyondPC: 0x02B4and continue execution, possibly finding the next missing opcode or starting to copy graphics to VRAM.

Sources consulted

Educational Integrity

What I Understand Now

  • CP (Compare):It is a "phantom subtraction" that updates flags without modifying register A. It is essential for conditional flow control.
  • Logical Deadlock:When the CPU encounters an unimplemented opcode and returns 0 cycles, the emulation time does not advance, causing the game to freeze.
  • Directed instrumentation:The technique of addingexit(1)in the casedefaultallowed us to identify exactly which opcode was missing, demonstrating the effectiveness of the "fail-fast" techniques in development.
  • Comparison flags:The flags Z, N, H, C are calculated the same as in subtraction, but the result is not saved. Z indicates equality, C indicates "less than".

What remains to be confirmed

  • Emulator Preview:Once implementedCP d8, the emulator should advance beyondPC: 0x02B4. If it finds another missing opcode, the new warning mode will report it without crashing.
  • Rendering:If the emulator advances far enough, it should start copying graphics to VRAM, which could result in us finally seeing something on the screen.

Hypotheses and Assumptions

Confirmed hypothesis:The opcode0xFE (CP d8)was indeed the culprit of the deadlock. The debugging instrumentation worked perfectly, identifying the problem immediately and clearly.

Assumption:WithCP d8implemented, the emulator should move significantly further in execution. If there are more opcodes missing, the new warning mode will report them without the need to recompile and run repeatedly.

Next Steps

  • [x] Recompile the C++ module with.\rebuild_cpp.ps1
  • [x] Run the tests withpytest tests/test_core_cpu_compares.py -v
  • [ ] Run the emulator withpython main.py roms/tetris.gband verify that it advances beyondPC: 0x02B4
  • [ ] If warnings appear for other missing opcodes, implement them sequentially
  • [ ] Check if the emulator starts copying graphics to VRAM
  • [ ] Check if something finally appears on the screen