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

Control of Interrupts, XOR and 16-bit Loads

Date:2025-12-16 StepID:0010 State: Verified

Summary

Implementation of critical system control instructions and necessary logical operations so that Tetris DX continues running. Added IME (Interrupt Master Enable) attribute to the CPU to handle interrupts. Implemented DI (0xF3) and EI (0xFB) opcodes to disable/enable interrupts, XOR A (0xAF) as optimization to set register A to zero, and the 16-bit immediate load instructions LD SP, d16 (0x31) and LD HL, d16 (0x21). These instructions are essential for system initialization. Complete test suite TDD (13 tests) validating all implemented functionalities.

Hardware Concept

IME (Interrupt Master Enable):It is not a directly accessible register, but a internal CPU "switch" that controls whether interrupts are enabled or not. When IME is enabled (True), the CPU can process interrupts (VBlank, Timer, Serial, Joypad, etc.). When disabled (False), interrupts are ignored. Games often disable interrupts at startup with DI to configure hardware without interruptions, and then reactivate them with EI when they are ready.

DI (Disable Interrupts - 0xF3):Disable interrupts by setting IME to False. This instruction is critical for system initialization, as it allows you to configure the hardware without interruptions interfering.

EI (Enable Interrupts - 0xFB):Enable interrupts by setting IME to True.Important note:On real hardware, EI has a delay of 1 instruction. This means that interrupts are not activated immediately, but after executing the next instruction. For now, we implemented immediate activation for simplicity. Later, when we implement complete interrupt handling, we will add this delay.

XOR A (0xAF) - Historical Optimization:Perform the XOR operation between record A and itself: A = A ^ A. Since any value XORed with itself is always 0, this statement sets the register A to zero efficiently. The developers used `XOR A` instead of `LD A, 0` because:

  • Occupies fewer bytes:1 byte vs 2 bytes (opcode + operand)
  • Consumes fewer cycles:1 cycle vs 2 cycles
  • It's faster:On older hardware, logical operations were faster than loads

Flags in logical operations (XOR):XOR always sets the flags N (Subtract), H (Half-Carry) and C (Carry) to 0. The Z (Zero) flag depends on the result: if the result is 0, Z is activated; if not, it is deactivated. In the case of XOR A, the result is always 0, so Z is always activated.

LD SP, d16 (0x31) and LD HL, d16 (0x21):These instructions load an immediate value of 16 bits in a 16 bit register. Reads the next 2 bytes of memory in Little-Endian format and load into the specified record. These instructions are critical for system initialization, since games usually configure SP (Stack Pointer) and HL (memory pointer) at the beginning of the program.

Fountain:Pan Docs - CPU Instruction Set (DI, EI, XOR, LD)

Implementation

Attribute addedime(bool) to the CPU class to control the state of the interrupts. 5 new opcodes were implemented insrc/cpu/core.py:

Components created/modified

  • src/cpu/core.py:Added attributeimeand 5 new opcodes (DI, EI, XOR A, LD SP d16, LD HL d16)
  • tests/test_cpu_control.py:Complete TDD test suite (13 tests)

Design decisions

1. IME as a boolean attribute:It was decided to use a simple boolean (self.ime: bool) instead of a full registry, since IME is not directly accessible and only has two states (on/off).

2. IME initialization to False:By default, IME is initialized to False for security. Although after the actual boot ROM, IME is usually enabled, games often disable it explicitly at startup with DI, so initializing to False is safer.

3. EI without delay (for now):On real hardware, EI has a delay of 1 instruction. For now, we implemented immediate activation for simplicity. Later, when we implement the complete interrupt handling, we will add this delay.

4. XOR A as a documented optimization:It was explicitly documented why XOR A is a historical optimization (fewer bytes, fewer cycles, faster). This helps to understand the game code ancients who use this technique.

5. Flags in XOR:The flag update in XOR A: Z was always correctly implemented is activated (result is always 0), N, H and C are always deactivated (XOR is not subtraction, it has no carry).

Affected Files

  • src/cpu/core.py- Added IME attribute and 5 new opcodes
  • tests/test_cpu_control.py- Complete TDD test suite (13 tests)

Tests and Verification

Created a complete suite of integration tests intests/test_cpu_control.py:

  • test_di_disables_interrupts:Verify that DI disables IME
  • test_ei_enables_interrupts:Verify that EI activates IME
  • test_di_ei_sequence:Check DI sequence followed by EI
  • test_xor_a_zeros_accumulator:Verify that XOR A sets A to zero
  • test_xor_a_sets_zero_flag:Verify that XOR A always activates Z
  • test_xor_a_clears_other_flags:Verify that XOR A disables N, H and C
  • test_xor_a_with_different_values:Verify that XOR A always returns 0 with any value
  • test_ld_sp_d16_loads_immediate_value:Verify that LD SP, d16 loads value correctly
  • test_ld_sp_d16_with_different_values:Check LD SP, d16 with different values
  • test_ld_hl_d16_loads_immediate_value:Verify that LD HL, d16 loads value correctly
  • test_ld_hl_d16_with_different_values:Check LD HL, d16 with different values
  • test_ld_sp_d16_advances_pc:Verify that LD SP, d16 advances PC correctly
  • test_ld_hl_d16_advances_pc:Verify that LD HL, d16 advances PC correctly
  • 13 tests in total, all passing ✅

Manual validation:The emulator was run withpython3 main.py tetris_dx.gbc --debugand it was observed that the system can now execute more instructions before stopping.

Test results with real ROM (Tetris DX):

  • ROM Loading:✅ File uploaded successfully (524,288 bytes, 512 KB)
  • Header Parsing:✅ Title "TETRIS DX", Type 0x03 (MBC1), ROM 512 KB, RAM 8 KB
  • System initialization:✅ Viboy initialized successfully with ROM
  • Post-Boot State:✅ PC and SP were initialized correctly (PC=0x0100, SP=0xFFFE)
  • First instruction (0x0100):✅ NOP (0x00) executed successfully, PC advanced to 0x0101 (1 cycle)
  • Second instruction (0x0101):✅ JP nn (0xC3) executed correctly, jumped to 0x0150 (4 cycles)
  • Third instruction (0x0150):DI (0xF3) executed correctly, IME disabled, PC advanced to 0x0151 (1 cycle)
  • Debug mode:✅ Traces correctly show PC, opcode, logs and cycles consumed
  • Stopping for unimplemented opcode:✅ System stops correctly at 0x0151 with opcode 0xE0 (LDH (n), A - Load A into I/O) not implemented
  • Total cycles executed:6 cycles (1 + 4 + 1)
  • Progress:✅ The system now runs3 instructions(previously only 2) before stopping

Important remarks:

  • The DI instruction (0xF3) was executed correctly, confirming that interrupt handling is working.
  • The next unimplemented opcode is 0xE0 (LDH (n), A), which is an I/O memory write instruction. This instruction writes the value of register A to address (0xFF00 + n), where n is the next byte. It is a critical instruction for communication with the Game Boy's I/O ports.
  • The emulator is progressing correctly: it now executes 3 instructions before stopping (previously only 2), confirming that the new implementations are working correctly.

Sources consulted

Note: Implementation based on general knowledge of LR35902 architecture and Pan Docs technical documentation.

Educational Integrity

What I Understand Now

  • IME (Interrupt Master Enable):Not an accessible register, but an internal "switch" of the CPU that controls whether interrupts are enabled. DI turns it off, EI turns it on.
  • XOR Optimization A:Developers used XOR A instead of LD A, 0 because it takes up less space bytes (1 vs 2), consumes fewer cycles (1 vs 2), and is faster on older hardware.
  • Flags in logical operations:XOR always sets N, H and C to 0. The Z flag depends on the result. In XOR A, the result is always 0, so Z is always activated.
  • 16-bit immediate loading:LD SP, d16 and LD HL, d16 read 2 bytes in Little-Endian format and load them into the specified registry. They are critical for system initialization.

What remains to be confirmed

  • EI delay:On real hardware, EI has a delay of 1 instruction. For now, we implement immediate activation. Later, when we implement full interrupt handling, we will add this delay.
  • Complete interrupt handling:For now we only control IME, but it remains to be implemented the IF (Interrupt Flag) and IE (Interrupt Enable) register, and the actual handling of interrupts in the main loop.
  • ✅ Validation with real ROMs: FILLED- Successfully validated with tetris_dx.gbc (Actual Game Boy Color ROM). The system now executes 3 instructions (NOP, JP nn, DI) before stopping at 0x0151 with opcode 0xE0 (LDH (n), A) not implemented. The DI instruction was executed successfully, confirming that interrupt control works. The next step is to implement LDH (n), A (0xE0) to continue with system initialization.

Hypotheses and Assumptions

Assumption 1:For now, we assume that initializing IME to False is safe, since games They usually disable it explicitly at startup with DI. If there are problems in the future, we can change the initialization.

Assumption 2:We implement EI without delay for now for simplicity. Later, when Let's implement full interrupt handling, we'll add the 1 instruction delay that you have in real hardware.

Next Steps

  • [ ] PRIORITY:Implement opcode 0xE0 (LDH (n), A - Load A into I/O) - The next instruction needed by Tetris DX
  • [ ] Implement more opcodes depending on what the emulator finds (probably LD (HL), A, DEC, LDH A, (n), etc.)
  • [ ] Implement 1 instruction delay in EI when full interrupt handling is implemented
  • [ ] Implement IF (Interrupt Flag) and IE (Interrupt Enable) registers
  • [ ] Implement full interrupt handling in the main loop