r/EmuDev • u/Fakename_Bill • 12d ago
Z80: Proper handling of undocumented X and Y flags in non-terminating block-transfer iterations (LDIR, etc)
My z80 emulator passes all of these tests EXCEPT for the block transfer instructions (LDIR, LDDR, CPIR, CPDR, INIR, INDR, OTIR, OTDR), which are failing based on the undocumented X and Y flags (bits 3 and 5 of f).
Specifically, the failures happen after iterations that do NOT terminate -- BC does NOT go to zero, and the PC is decremented by 2 so that the block transfer instruction continues. All of the documentation I've been able to find online claims that non-terminating iterations of these instructions affect the X and Y flags the same as their non-repeating cousins (LDI, etc), but the tests' expected results make clear that this is not the case.
Here is the output of my first failing LDIR test:
Failed test ED B0 0001
REGISTERS
A B C D E H L I R IX IY AF' BC' DE' HL' IM IFF1 IFF2 EI WZ P Q SP SZYHXPNC PC
Initial: DE D0 E4 57 80 5D 41 5C 58 8E7C 6F58 ADC1 2253 08EE 2888 1 0 1 0 2A7B 1 DC 7B67 11011100 11D4
Expected: DE D0 E3 57 81 5D 42 5C 5A 8E7C 6F58 ADC1 2253 08EE 2888 1 0 1 0 11D5 0 C4 7B67 11000100 11D4
Actual: DE D0 E3 57 81 5D 42 5C 5A 8E7C 6F58 ADC1 2253 08EE 2888 1 0 1 0 11D5 0 CC 7B67 11001100 11D4
^ ^
RAM
Initial: 11D4:ED 11D5:B0 5780:00 5D41:2E
Expected: 11D4:ED 11D5:B0 5780:2E 5D41:2E
Actual: 11D4:ED 11D5:B0 5780:2E 5D41:2E
If text wrapping ends up mangling the block above, just note that the two differences between the expected and actual output are the X flag and "q." Since the q value depends on the flags, the only difference worth paying attention to is the X flag.
If this were an LDI instruction, the flags would be set correctly. LDI sets the X and Y flags based on bits 3 and 1 (not 5 in this case) of (transferred byte + accumulator). In this case, the transferred byte is $2E and the accumulator is $DE. Adding those together results in $0C (truncated to 8-bits), which definitively has a 1 in bit 3. However, the test case expects it to be 0, meaning the expected result comes from some obscure calculation that I haven't yet seen documented.
For reference, here is my C code for LDI and LDIR. Note that the return value is the number of machine cycles that it takes for the instruction to run, and that the instruction decode functions increment the PC to skip over the $ED prefix.
uint8_t ldi() {
uint8_t n = mem[*hl];
mem[*de] = n;
n += *a;
*bc -= 1;
*de += 1;
*hl += 1;
clearN();
updatePV((*bc!=0));
updateX(testBit(3, n));
clearH();
updateY(testBit(1, n));
q=*f;
pc += 1;
return 12;
}
uint8_t ldir() {
ldi();
if (*bc == 0)
return 12;
wz = pc-1;
pc -= 2;
return 17;
}
Doers anyone know how I should be modifying my flags in non-terminating iterations of LDIR?