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

Indirect Memory and Increment/Decrement

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

Summary

Implementation of indirect addressing using HL as memory pointer, LDI/LDD operations (automatic pointer increment/decrement) and unary increment/decrement operations (INC/DEC) with correct handling of flags. Critical helpers implemented_inc_nand_dec_nthat update flags Z, N, H but do NOT touch the C (Carry) flag, an important hardware quirk LR35902 which many emulators fail to implement. These opcodes are essential for cleaning loops of memory that games run at startup (memset). Complete suite of TDD tests validating memory indirection, wrap-around of pointers and correct behavior of flags in INC/DEC.

Hardware Concept

Indirect Addressing (HL):When an instruction uses(H.L.), I don't know uses the value of the HL register directly, but HL is used as amemory addressto read or write. It is analogous to a pointer in C:*ptr. For example, if HL contains0xC000and we executeRHP (HL), A, we write the value of A in the address memory0xC000, not in the HL record itself.

LDI / LDD (Load with Increment/Decrement):These instructions are "Swiss army knives" that combine a memory operation with automatic increment or decrement of the pointer. For example,RHP (HL+), A(LDI) writes A to the address pointed to by HL and increments HL by a single cycle. This is ideal for quick memory copy or initialization loops (memset), since it avoids having to increment the pointer manually in a separate statement.

Flags in INC/DEC (CRITICAL):The operationsINCandDECof 8 bits affect the Z (Zero), N (Subtract) and H (Half-Carry) flags, butThey do NOT affect the C flag (Carry). This is an important quirk of the LR35902 hardware that many emulators fail at when implementing, breaking the conditional logic that depends on keeping the C flag intact for increment/decrement operations.

  • Flag H in INC:It is activated if there is an overflow from bit 3 to 4 (low nibble). Example:0x0F + 1 = 0x10activates H because there was carry from bit 3 to 4.
  • Flag H in DEC:It is activated if there is a loan from bit 4 to 3 (low nibble). Example:0x10 - 1 = 0x0Factivates H because there was borrowing from bit 4 to 3.
  • Flag C: DO NOT TOUCH, even if there is overflow (INC 0xFF -> 0x00) or underflow (DEC 0x00 -> 0xFF). This preservation of the C flag is critical for code that uses INC/DEC in loops with C based conditions.

Why does this unlock Tetris?Tetris (and most Game Boy games) use memory cleanup loops at startup that do something like:

LD HL, 0xDFFF ; End of RAM
XOR A ; A = 0
loop:
LD (HL-), A ; Write 0 to RAM and move the pointer down
BIT 7, H ; Check if you have reached the end...
JR NZ, loop ; Repeat

WithoutRHP (HL-), A(LDD) implemented, the emulator cannot execute this loop memory cleaning.

Implementation

Generic helpers implemented_inc_nand_dec_nthat encapsulate the increment/decrement logic with correct flag management. These helpers are reused in all INC/DEC opcodes of individual registers (B, C, D, E, H, L, A).

Components created/modified

  • ALU Helpers: _inc_n(val)and_dec_n(val)- Increase/decrease an 8-bit value, they update flags Z, N, H, but DO NOT touch C.
  • Indirect memory opcodes:
    • 0x77: RHP (HL), A- Write A in the address pointed by HL
    • 0x22: RHP (HL+), A(LDI) - Write A in (HL) and increment HL
    • 0x32: RHP (HL-), A(LDD) - Write A in (HL) and decrement HL
    • 0x2A: LD A, (HL+)(LDI) - Read from (HL) to A and increment HL
  • Increment/decrement opcodes:
    • 0x04: INC B
    • 0x05: DEC B
    • 0x0C: INC C
    • 0x0D: DEC C
    • 0x3C: INC A
    • 0x3D: DEC A
  • Tests: test_cpu_memory_ops.py- Complete suite of memory tests hint and INC/DEC flags

Design decisions

Generic Helpers vs. duplicate code:Helpers were created_inc_nand_dec_nto avoid duplicating the flag update logic in each INC/DEC opcode. This facilitates maintenance and ensures consistency. All 8-bit INC/DEC opcodes use these helpers, which guarantees that the behavior of flags is identical in all cases.

Explicit preservation of the C flag:It was explicitly documented in code and tests that INC/DEC DO NOT touch the C flag. This is critical because many developers (and emulators) assume incorrectly that any arithmetic operation must update all flags. The tests They explicitly verify that C is preserved even in overflow/underflow cases.

Wrap-around of pointers:LDI/LDD operations apply explicit wrap-around using& 0xFFFFto ensure that HL is always in the valid 16-bit range. This allows loops to work correctly even if they reach space limits of addresses.

Affected Files

  • src/cpu/core.py- Added helpers_inc_nand_dec_n, and handlers for indirect memory and INC/DEC opcodes
  • tests/test_cpu_memory_ops.py- New suite of tests for indirect memory and behavior of flags in INC/DEC. Fixed to use addresses outside ROM area (0x8000+) to allow writing test code.

Tests and Verification

A complete test suite was created intest_cpu_memory_ops.pywhich validates:

  • Basic indirect memory: RHP (HL), Awrite correctly in the address pointed to by HL without modifying HL
  • LDI (increment): RHP (HL+), AandLD A, (HL+)write/read and increment HL correctly, including wrap-around (0xFFFF -> 0x0000)
  • LDD (decrement): RHP (HL-), Awrite and decrement HL correctly, including wrap-around (0x0000 -> 0xFFFF)
  • INC with flags:Normal cases, Half-Carry (0x0F -> 0x10), and overflow (0xFF -> 0x00) verifying that C does NOT change
  • DEC with flags:Normal cases, Half-Borrow (0x10 -> 0x0F), and underflow checking that C does NOT change
  • Preservation of C:Explicit tests verifying that C is preserved even when is active before INC/DEC
  • Variants:Tests for INC/DEC of B, C, A verifying consistent behavior

Unit tests:14 tests in pytest covering all the critical cases mentioned previously. The tests follow the TDD pattern established in the project.All tests pass correctlyafter fixing an initial problem with memory address usage.

Fix applied:Initially, the tests attempted to write code in0x0100(ROM area 0x0000-0x7FFF), but the MMU reads from the cartridge in that area, not from internal memory. This caused the tests to read0xFFinstead of written opcodes. It was corrected changing all tests to use addresses outside the ROM area (0x8000+), where writing works correctly. This fix documents an important aspect of memory mapping: ROM areas are read-only from the program's perspective, while RAM/VRAM allow reading and writing.

Sources consulted

  • Bread Docs:CPU Instruction Set- Behavior of flags in INC/DEC, indirect addressing
  • Bread Docs:CPU Registers and Flags- Detailed description of flags and behavior of arithmetic operations
  • Implementation based on standard technical documentation of the LR35902 hardware. No code was consulted from other emulators to maintain clean-room integrity.

Educational Integrity

What I Understand Now

  • Indirect addressing:I understand that(H.L.)means "the value at the memory address pointed to by HL", not "the value of HL". It's like using a pointer in C.
  • Flags in INC/DEC:I understand that 8-bit INC/DEC does NOT touch the C flag, even with overflow/underflow. This preservation is critical and many emulators fail it. The Half-Carry (H) is calculated differently in INC (carry from bit 3 to 4) vs DEC (borrow from bit 4 to 3).
  • LDI/LDD:I understand that these instructions are optimizations for loops, combining memory operation with pointer update in a single cycle. LDI increases, LDD decreases.
  • Wrap-around:I understand that 16-bit pointers do wrap-around using& 0xFFFF. This is important for loops that reach the limits of the space. addresses.

What remains to be confirmed

  • INC/DEC of HL (16 bits):INC HL and DEC HL need to be implemented, which are different (they do not affect flags). These are useful for loops that need to increment/decrement pointers 16 bit.
  • INC/DEC of (HL):INC (HL) and DEC (HL) that increase/decrement need to be implemented the value in memory pointed to by HL. These also affect flags Z, N, H but not C.
  • Validation with test ROMs:It would be ideal to validate with redistributable test ROMs Try memory cleanup loops to confirm that the behavior is correct.
  • Exact timing:For now we use approximate M-Cycles. The exact timing of LDI/LDD It might differ slightly on the actual hardware, but for most cases it should be correct.
  • Memory mapping in tests:Confirmed that the ROM area (0x0000-0x7FFF) is only reading from the cartridge, while the RAM/VRAM areas (0x8000+) allow writing. Tests must use addresses outside of ROM in order to write test code.

Hypotheses and Assumptions

The implementation of flags in INC/DEC is based on standard technical documentation (Pan Docs) that indicates explicitly that C is not touched. The tests verify this behavior, but I have not been able to validate it directly with real hardware. The preservation of C is a widely documented feature. known from the LR35902 hardware, but it is easy to miss if you do not carefully read the documentation.

The behavior of Half-Carry in DEC (activated when the low nibble is 0, indicating borrow) is implemented based on the logic of how borrowing works in binary subtraction. If the nibble low is 0 and we decrement, we need to borrow from the high nibble, activating H. This logic is consistent with how Half-Carry works in normal subtraction.

Lesson learned on memory mapping:During the development of the tests, we discovered that the MMU has a different behavior for reading and writing in the ROM area (0x0000-0x7FFF). Reading is always done from the cartridge (if it exists), while writing is done in memory internal, but is not visible in subsequent readings. This is consistent with how real hardware works: the cartridge ROM is read-only from the program perspective. The tests were corrected to use addresses outside the ROM area (0x8000+) where the writing works correctly. This discovery reinforces the importance of understanding the complete memory mapping of the system.

Next Steps

  • [ ] ImplementBIT 7, H(BIT instruction) needed for Tetris cleanup loop
  • [ ] Implement more INC/DEC opcodes (D, E, H, L, (HL)) to complete the set
  • [ ] Implement INC HL / DEC HL (16 bits, do not affect flags) for pointer loops
  • [ ] Run Tetris trace to verify that the cleanup loop is working correctly
  • [ ] Validate with redistributable test ROMs if available