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

16-bit Arithmetic and Conditional Returns

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

Summary

Implemented 16-bit arithmetic operations (INC/DEC of even registers and ADD HL, rr) and conditional returns (RET NZ, RET Z, RET NC, RET C). The critical peculiarity is that INC/DEC 16 bits DO NOT affect any flags (unlike 8 bits), which is essential for loops that decrement counters without corrupting flags from previous comparisons. ADD HL, rr updates flags H and C but does NOT touch Z, another special hardware behavior. Conditional returns allow implement subroutines with conditional logic. Complete suite of TDD tests (24 tests) validating all the functionalities. The emulator can now execute complex loops such as Tetris DX initialization loops.

Hardware Concept

16-bit INC/DEC - DOES NOT affect Flags:Unlike increments/decrements 8-bit that update flags Z, N, H (but not C), 16-bit versions (INC BC, DEC DE, etc.)They DO NOT modify any flag. This is a classic trap in emulation. They are used to travel memory or decrement counters in loops without corrupting the flag state of a previous comparison (as CP). For example, in a loop that doesDEC BCand then check if BC is 0 usingLD A, B; OR C; JR NZ, loop, heDEC BCYou should not touch the flags so that the comparison works correctly.

ADD HL, rr - Special Flags:The sum of 16 bitsADD HL, BC/DE/HL/SPIt has a peculiar behavior with flags:

  • Z (Zero):IT IS NOT TOUCHED (it remains as it was before the operation).
  • N (Subtract):Always 0 (it is a sum).
  • H (Half-Carry):It is activated if there is carry from bit 11 to 12 (12-bit overflow).
  • C (Carry):It is activated if there is carry of bit 15 (16-bit overflow).

The Half-Carry in ADD HL is calculated on 12 bits (not 8 as in 8-bit ADD), because the hardware checks for overflow of the 12-bit nibble (bits 0-11). This is different from the Half-Carry of 8-bit operations that are calculated on 4 bits.

Conditional Returns:Similar to conditional jumps (JR NZ, etc.), there are conditional returns (RET NZ, RET Z, RET NC, RET C) that only execute the return if it is true a condition. If the condition is true, they consume 5 M-Cycles (20 T-Cycles). If it is false, they consume 2 M-Cycles (8 T-Cycles). They are used to implement subroutines that make decisions before returning.

Implementation

Added 16 new opcodes to the dispatch table:

  • INC 16-bit:0x03 (BC), 0x13 (DE), 0x23 (HL), 0x33 (SP)
  • DEC 16-bit:0x0B (BC), 0x1B (DE), 0x2B (HL), 0x3B (SP)
  • ADD HL, rr:0x09 (BC), 0x19 (DE), 0x29 (HL), 0x39 (SP)
  • Conditional RET:0xC0 (NZ), 0xC8 (Z), 0xD0 (NC), 0xD8 (C)

Components created/modified

  • src/cpu/core.py: Added handlers for 16-bit INC/DEC (8 handlers), helper_add_hl_16bit()for ADD HL, rr (4 handlers), and handlers for conditional returns (4 handlers). All with exhaustive documentation on the behavior of flags.
  • tests/test_cpu_math16.py: New file with 24 unit tests organized into 4 classes:TestInc16Bit(5 tests),TestDec16Bit(5 tests),TestAddHL16Bit(6 tests), andTestConditionalReturn(8 tests).

Design decisions

Helper_add_hl_16bit():A generic helper was created to avoid duplication of code between the 4 handlers of ADD HL, rr. The helper handles the flag logic (especially the 12-bit Half-Carry) centrally, facilitating maintenance and bug fixes.

Verification of flags in tests:The tests emphasize verifying that 16-bit INC/DEC They do NOT touch flags (especially Z and C), and that ADD HL does not touch Z. These are critical points that can cause subtle bugs if implemented incorrectly.

Explicit wrap-around:All 16-bit operations use explicit masking with& 0xFFFFto ensure correct wrap-around, following the pattern established in the code.

Affected Files

  • src/cpu/core.py- Added 16 new opcodes and helper handlers_add_hl_16bit()
  • tests/test_cpu_math16.py- New file with 24 unit tests

Tests and Verification

Exhaustive validation through unit tests:

  • Unit tests:24 tests intest_cpu_math16.py, everyone passing (100% success).
  • Critical specific tests:
    • test_inc_bc_no_flags: Verify that INC BC does not modify flags (especially Z)
    • test_dec_bc_no_flags: Verify that DEC BC does not modify flags
    • test_add_hl_bc: Check Half-Carry in bit 11 for ADD HL, BC
    • test_add_hl_no_z_flag: Verifies that ADD HL does not touch Z even when result is 0
    • test_ret_nz_takenandtest_ret_nz_not_taken: Verify conditional cycles (5 vs 2 M-Cycles)
  • Verification with ROM:Tetris DX now runs correctly untilDEC DE(0x1B) and moves further into the code before encountering an unimplemented opcode (0x7A = LD A, D).
  • Complete suite:All 136 tests in the project pass correctly.

Sources consulted

Note: The implementation follows the Pan Docs specifications for the exact behavior of flags in 16-bit operations, especially the peculiarities that INC/DEC 16-bit do not affect flags and that ADD HL does not touch Z.

Educational Integrity

What I Understand Now

  • 16-bit INC/DEC do not touch flags:This key difference with 8-bit It is critical for loops that use 16-bit counters. If the flags changed, it would be corrupted the state of previous comparisons.
  • ADD HL, rr and the Z flag:It is very curious that ADD HL does not touch Z even when the result is 0. This means that Z must be preserved from the previous operation, which It is useful for loops that combine 16-bit arithmetic with comparisons.
  • Half-Carry in 12 bits:In ADD HL, the Half-Carry is calculated on 12 bits (bits 0-11), not over 4 bits as in 8-bit operations. This reflects the architecture internal hardware.
  • Conditional returns:They are essential for implementing subroutines that take decisions. Conditional timing (5 vs 2 M-Cycles) is important for accurate emulation.

What remains to be confirmed

  • Behavior of flags in ADD HL, HL:Although it is implemented according to documentation, I have not been able to verify directly with real hardware. Unit tests They validate the expected behavior according to Pan Docs.
  • Exact conditional RET timing:The 5 M-Cycles when taking the return and 2 M-Cycles when not taken are documented, but I have not verified with real hardware. The tests validate the expected behavior.

Hypotheses and Assumptions

No critical assumption:Implementation faithfully follows specifications from Pan Docs for the behavior of flags and timing. All decisions are supported by technical documentation.

Validation through tests:Although I don't have access to real hardware, the complete suite of unit tests (24 specific tests + 136 total tests) validates the expected behavior according to documentation. The fact that Tetris DX proceeds correctly until it encounters an unimplemented opcode suggests that the implementation is correct.

Next Steps

  • [ ] Implement more loading instructions between missing registers (LD r, r'), such as LD A, D (0x7A)
  • [ ] Implement additional logical operations (OR, AND) that are common in loops
  • [ ] Continue progressing with Tetris DX to identify the next critical instructions
  • [ ] Consider implementing more variants of ADD/SUB for more complex arithmetic operations