4

I've been running Blarggs CPU tests through my Gameboy emulator, and the op r,r test shows that my ADC instruction is not working properly, but that ADD is. My understanding is that the only difference between the two is adding the existing carry flag to the second operand before addition. As such, my ADC code is the following:

void Emu::add8To8Carry(BYTE &a, BYTE b) //4 cycles - 1 byte
{
    if((Flags >> FLAG_CARRY) & 1)
        b++;
    FLAGCLEAR_N;
    halfCarryAdd8_8(a, b); //generates H flag based on addition
    carryAdd8_8(a, b); //generates C flag appropriately
    a+=b;
    if(a == 0)
        FLAGSET_Z;
    else
        FLAGCLEAR_Z;
}

I entered the following into a test ROM:

06 FE 3E 01 88

Which leaves A with the value 0 (Flags = B0) when the carry flag is set, and FF (Flags = 00) when it is not. This is how it should work, as far as my understanding goes. However, it still fails the test.

From my research, I believe that flags are affected in an identical manner to ADD. Literally the only change in my code from the working ADD instruction is the addition of the flag check/potential increment in the first two lines, which my test code seems to prove works.

Am I missing something? Perhaps there's a peculiarity with flag states between ADD/ADC? As a side note, SUB instructions also pass, but SBC fails in the same way.

Thanks

Triforcer
  • 127
  • 9

1 Answers1

4

The problem is that b is an 8 bit value. If b is 0xff and carry is set then adding 1 to b will set it to 0 and won't generate carry if added with a >= 1. You get similar problems with the half carry flag if the lower nybble is 0xf.

This might be fixed if you call halfCarryAdd8_8(a, b + 1); and carryAdd8_8(a, b + 1); when carry is set. However, I suspect that those routines also take byte operands so you may have to make changes to them internally. Perhaps by adding the carry as a separate argument so that you can do tmp = a + b + carry; without overflow of b. But I can only speculate without the source to those functions.

On a somewhat related note, there's a fairly simple way to check for carry over all the bits:

int sum = a + b;
int no_carry_sum = a ^ b;
int carry_into = sum ^ no_carry_sum;
int half_carry = carry_into & 0x10;
int carry = carry_info & 0x100;

How does that work? Consider that bitwise "xor" gives the expected result of each bit if there is no carry going in to that bit: 0 ^ 0 == 0, 1 ^ 0 == 0 ^ 1 == 1 and 1 ^ 1 == 0. By xoring sum with no_carry_sum we get the bits where the sum differs from the bit-by-bit addition. sum is only different whenever there is a carry into a particular bit position. Thus both the half carry and carry bits can be obtained with almost no overhead.

George Phillips
  • 4,144
  • 24
  • 24
  • 1
    I saw this issue, too. To implement, you could do: if input carry is set, increment a, if a==0, set c flag, then do a modified "add a,b" that cannot reset the c flag, but can set it. Then the regular "add a,b" could reset the c flag and call that same modified add. – Zeda Feb 07 '17 at 19:21
  • 2
    Thanks, both. As I was already checking for H/C by ANDing the result with 0x10 / 0x100 respectively, I just needed to add the carry-in _after_ summing them to preserve the flags correctly. The instructions now pass the tests! – Triforcer Feb 07 '17 at 19:55