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

Misc Instructions Implementation (DAA, CPL, SCF, CCF)

Date:2025-12-24 StepID:0271 State: draft

Summary

This Step implements the missing miscellaneous base block instructions:DAA(0x27),CPL(0x2F),SCF(0x37) andCCF(0x3F). Diagnosing Step 0270 revealed that the emulator was falling into an infinite loop ofRST 38(opcode0xFF), the "Blue Screen" of the Game Boy.

The root cause wasPC desynchronizationdue to missing instructions. If a critical instruction is missing such asDAA(Decimal Adjust Accumulator), the game calculates wrong memory addresses and crashes. Pokémon uses BCD (Binary Coded Decimal) arithmetic intensively for health, money, and points. YeahDAAis not implemented, the calculations go wrong, the game doesJP HLto a wrong address, you land in an empty memory area (full of0xFF), and enters an infinite loop ofRST 38.

They were implemented4 new instructions: DAA(0x27) - the most complex, adjusts A to BCD after addition/subtraction;CPL(0x2F) - complements A (inverts bits);SCF(0x37) - activate the Carry flag; andCCF(0x3F) - inverts the Carry flag.

Hardware Concept

Miscellaneous instructions are special operations that do not fit into the standard categories of Load, Arithmetic, Flow Control, etc. They are "rare" but vital for the correct functioning of many games.

DAA (Decimal Adjust Accumulator) - 0x27

DAAIt is the most complex instruction to emulate correctly. Sets register A to be a valid BCD (Binary Coded Decimal) number after addition or subtraction.

What is BCD?BCD is a coding system where each decimal digit (0-9) is represented by 4 bits. For example, the decimal number 45 is represented as0100 0101(4 on high nibble, 5 on low nibble).

Why is DAA necessary?When you add two BCD numbers, the result may not be a valid BCD. For example, if you add 0x09 + 0x01 = 0x0A, the low nibble is 0xA (10 in decimal), which is not a valid BCD digit.DAAadjusts the result by adding 0x06 to the low nibble if it is greater than 9, or to the high nibble if it is greater than 0x99.

DAA logic:

  • After addition (N=0):
    • If C=1 or A > 0x99: adds 0x60 and activates C
    • If H=1 or (A & 0x0F) > 0x09: sum 0x06
  • After subtraction (N=1):
    • If C=1: subtract 0x60
    • If H=1: subtract 0x06

Flags:Z according to result, N (preserved), H=0 (always), C (updated if there was overflow).

Timing:1 M-Cycle (4 cycles).

CPL (Complement A) - 0x2F

CPLinverts all bits of register A (A = ~A). It is equivalent to doing a bitwise logical NOT.

Example:If A = 0b10101010, afterCPL, A = 0b01010101.

Flags:Z (preserved), N=1, H=1, C (preserved).

Timing:1 M-Cycle (4 cycles).

SCF (Set Carry Flag) - 0x37

SCFactivates the Carry flag (C = 1). It is useful to initialize the carry before ADC (Add with Carry) or SBC (Subtract with Carry) operations.

Flags:Z (preserved), N=0, H=0, C=1.

Timing:1 M-Cycle (4 cycles).

CCF (Complement Carry Flag) - 0x3F

CCFinverts the Carry flag (C = !C). It is useful for changing the state of the carry without knowing its current value.

Flags:Z (preserved), N=0, H=0, C=!C.

Timing:1 M-Cycle (4 cycles).

Why are they critical?

If these instructions are missing or poorly implemented:

  1. The game tries to do BCD calculations usingDAA.
  2. If not implemented, the CPU treats that byte as an unrecognized opcode (or NOP),but it doesn't set A.
  3. The game continues running with incorrect values ​​in A.
  4. Memory address calculations go wrong (e.g.JP HLjumps to a wrong direction).
  5. The game lands in an empty memory zone (full of0xFF).
  6. Lee0xFF, executeRST 38, push the PC to the stack, jump to0038, read0xFFagain...RST 38 infinite loop.

Fountain:Pan Docs - "CPU Instruction Set", "DAA Instruction", "CPL Instruction", "SCF Instruction", "CCF Instruction"

Implementation

4 new instructions were implemented in the methodstep()ofCPU.cpp, inserted in the appropriate places on the switch according to their opcodes.

DAA (0x27)

case 0x27://DAA (Decimal Adjust Accumulator)
{
    uint16_t a = regs_->a;
    bool n = regs_->get_flag_n();
    bool h = regs_->get_flag_h();
    bool c = regs_->get_flag_c();
    
    if (!n) { // After addition
        if (c || a > 0x99) {
            a += 0x60;
            regs_->set_flag_c(true);
        }
        if (h || (a & 0x0F) > 0x09) {
            a += 0x06;
        }
    } else { // After subtraction
        if (c) {
            a -= 0x60;
        }
        if (h) {
            a -= 0x06;
        }
    }
    
    regs_->a = static_cast<uint8_t>(a);
    regs_->set_flag_z(regs_->a == 0);
    regs_->set_flag_h(false);  // H is always cleared in DAA
    // C is kept or set if there was overflow in the setting (already updated above)
    
    cycles_ += 1;  // DAA consumes 1 M-Cycle
    return 1;
}

CPL (0x2F)

case 0x2F://CPL (Complement A)
{
    regs_->a = ~regs_->a;
    // Flags: Z (preserved), N=1, H=1, C (preserved)
    regs_->set_flag_n(true);
    regs_->set_flag_h(true);
    // Z and C are not modified
    cycles_ += 1;  // CPL consumes 1 M-Cycle
    return 1;
}

SCF (0x37)

case 0x37://SCF (Set Carry Flag)
{
    // Flags: Z (preserved), N=0, H=0, C=1
    regs_->set_flag_n(false);
    regs_->set_flag_h(false);
    regs_->set_flag_c(true);
    // Z is not modified
    cycles_ += 1;  // SCF consumes 1 M-Cycle
    return 1;
}

CCF (0x3F)

case 0x3F://CCF (Complement Carry Flag)
{
    // Flags: Z (preserved), N=0, H=0, C=!C
    regs_->set_flag_n(false);
    regs_->set_flag_h(false);
    regs_->set_flag_c(!regs_->get_flag_c());
    // Z is not modified
    cycles_ += 1;  // CCF consumes 1 M-Cycle
    return 1;
}

Design Decisions

  • DAA Implementation:The logic of DAA is complex and depends on the N flag (whether it was addition or subtraction) and the H and C flags. It was implemented exactly following the Pan Docs specification to ensure compatibility with games that use BCD.
  • Flag preservation:CPL, SCF and CCF preserve the Z flag, which is critical to maintain the correct state of the previous comparisons.
  • Code organization:The instructions were inserted into the appropriate places on the switch based on their opcodes to maintain consistency and ease of maintenance.
  • Precise timing:All instructions consume 1 M-Cycle (4 cycles) according to Pan Docs.

Affected Files

  • src/core/cpp/CPU.cpp- Added 4 new miscellaneous instructions in the methodstep():
    • DAA (0x27) - 1 M-Cycle - Set A to BCD after addition/subtraction
    • CPL (0x2F) - 1 M-Cycle - Complement A (inverts bits)
    • SCF (0x37) - 1 M-Cycle - Activate the Carry flag
    • CCF (0x3F) - 1 M-Cycle - Inverts the Carry flag

Tests and Verification

Compiled C++ module validation:The instructions were implemented directly in C++ and require recompilation.

Compile command:

.\rebuild_cpp.ps1

Test command:

python main.py roms/pkmn.gb

Expected verifications:

  • The infinite loop ofRST 38 (PC:0038) should disappear.
  • The game should advance past the waiting loop and show the intro (stars, Game Freak, Gengar).
  • YeahDAAwas the problem (main suspect), this will stabilize the system definitively.

Note:Complete unit tests can be implemented in a future Step, following the pattern of existing tests. It would be especially important to testDAAwith different combinations of flags and values ​​of A.

Sources consulted

Educational Integrity

What I Understand Now

  • RST loop 38:If the game "derails" and jumps to an empty area, read0xFF, executeRST 38, push the PC to the stack, jump to0038, read0xFFagain (yes0038does not have valid code), push again... This causes a Stack Overflow (the SP goes down until it turns around).
  • DAA and BCD:Pokémon uses BCD arithmetic intensively for health, money, and points. YeahDAAis not implemented, the calculations go wrong, the game doesJP HLto a wrong address, and lands in an empty memory area (full of0xFF).
  • PC desynchronization:When an instruction is missing, the CPU can "derail" (desynchronize from the correct instruction stream). This occurs when the game expects an instruction to do something specific (such as setting A to BCD), but because it is not implemented, it acts as a NOP, causing subsequent calculations to go wrong.

What remains to be confirmed

  • Validation with real ROMs:We need to run the emulator with Pokémon Red and verify that the loopRST 38disappears and the game progresses correctly.
  • Unit tests:Implement complete unit tests that validate the behavior ofDAAwith different combinations of flags and values ​​of A, especially borderline cases (additions that produce carry, subtractions that produce borrow, etc.).

Hypotheses and Assumptions

We assume that the lack ofDAAwas the main cause of the infinite loop ofRST 38. If the problem persists after this Step, we will need to investigate other possible causes (such as other missing instructions or memory management problems).

Next Steps

  • [ ] Recompile the C++ module with.\rebuild_cpp.ps1
  • [ ] Run the emulator with Pokémon Red and verify that the loopRST 38disappear
  • [ ] Verify that the game progresses past the waiting loop and shows the intro (stars, Game Freak, Gengar)
  • [ ] If the problem persists, investigate other possible causes (other missing instructions, memory management problems, etc.)
  • [ ] Implement full unit tests toDAAwith different combinations of flags and values ​​of A (optional, can be a future Step)