Skip to content

Fix SBC HL half-carry with carry-in - #218

Merged
davidly merged 1 commit into
davidly:mainfrom
gloveboxes:fix/x80-sbc-hl-half-carry
Nov 9, 2026
Merged

davidly merged 1 commit into
davidly:mainfrom
gloveboxes:fix/x80-sbc-hl-half-carry

Conversation

@gloveboxes

@gloveboxes gloveboxes commented Nov 9, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

  • Calculate the SBC HL half-carry using the masked RHS plus carry-in, avoiding loss of a borrow when the low 12 bits overflow or the 16-bit RHS wraps.
  • Add Z80 core regressions for RHS values 0x0FFF and 0xFFFF with carry set.

Validation

  • \�0cmake --build build/dcc_debug_host_tests --target debugger-z80-core-test --parallel\�0
  • \�0ctest --test-dir build/dcc_debug_host_tests --output-on-failure -R '^debugger-z80-core$'\�0

x80: sbc hl,rp sets the wrong H (half-carry) flag when carry-in is set

Summary

z80_op_sub_16 in src/dcc_debug_host/x80.cxx computes the H flag incorrectly
for SBC HL,rr when the carry flag is set on entry. The emulated flag differs
from real Z80 behaviour for operand values whose low 12 bits are 0xFFF.

Affected code

z80_op_sub_16( uint16_t lhs, uint16_t rhs, bool borrow ), used by SBC HL,rr
(ED 42/52/62/72).

if ( borrow )
    rhs++;
reg.fAuxCarry = ( ( rhs & 0xfff ) > ( lhs & 0xfff ) );

The carry-in is folded into rhs before the 12-bit mask. When
(rhs & 0xfff) == 0xfff, the increment carries out of the 12-bit field and the
masked value becomes 0, so the comparison no longer sees the borrow. When
rhs == 0xFFFF, the 16-bit increment wraps to 0 for the same reason.

Expected behaviour

H is set when there is a borrow from bit 12:
(rhs & 0xFFF) + carry_in > (lhs & 0xFFF).

Reproduction

    scf                 ; CF = 1
    ld   hl,0FFFh
    ld   de,0FFFh
    sbc  hl,de          ; HL = 0FFFFh
  • Expected: H = 1 (0xFFF + 1 > 0xFFF), N = 1, C = 1.
  • Actual: H = 0.

Also reproduced with de = 0FFFFh and carry set. The error was found by
the arithmetic soak vectors in Z80ROMlessSBC (math_soak_z80_test.py), which
compare flags against a reference after sbc hl,rr.

Fix

Add the carry after masking instead of before:

reg.fAuxCarry = ( ( ( rhs & 0xfff ) + ( borrow ? 1 : 0 ) ) > ( lhs & 0xfff ) );

One-line change; rhs is no longer modified. Verified against the soak
vectors (all flags/registers) after the change.

Notes

The same file is vendored in Z80ROMlessSBC (src/cpm/z80_debug_host/x80.cxx)
and must stay byte-identical with this copy; both carry the same fix.

Add Z80 core regressions for 12-bit operand overflow and 16-bit RHS wrap.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@davidly
davidly merged commit 32d3e46 into davidly:main Nov 9, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants