4

I'm debugging my 8080 emulator and I've gone the route of comparing it to the output of another tested emulator to see where I went wrong. When calculating the auxiliary carry flag, I used this algorithm for subtraction:

// a and b are uint8_t, cf_.ac is the auxiliary flag
a &= 0x0F; // lower nibble
b &= 0x0F; // lower nibble
cf_.ac = (a < b); // there must be borrow if minuend is less than subtrahend

On the other hand, the emulator I'm comparing with uses this algorithm:

// hf is the auxiliary flag, a and val are uint8_t
const uint16_t result = c->a - val;
c->hf = ~(c->a ^ result ^ val) & 0x10;

Our emulators are mismatching after this instruction:

Instructions Ran: 32
Program Counter: 022A
Memory Immediate: 0xFE
Instruction: CPI D8 0
Registers (B/C/D/E/H/L/A): 00 00 00 00 00 00 F5
Flags (Z/S/P/C/AC): 0 1 1 1 1
Stack Pointer: 7AD
Cycles: 305

where my auxiliary flag is not set (0xF5 - 0x00 shouldn't set the AF flag?) while theirs is set. I don't completely understand their algorithm so I am not able logically tell if it's wrong. Is my algorithm faulty or is theirs?

  • 3
    I'm voting to close this question as off-topic because debugging new code for new products isn't exactly RC stuff. Please narrow it down to questions about classic hardware, not modern coding - until then you might as well want to give Ken Shirriff's great blog article about the 8085 flag handling a read. – Raffzahn Jun 5 at 22:43
  • 8
    @Raffzahn Programming oldie things is about oldie things. This site is about oldie things. The rule you want to apply, does not exist. – peterh Jun 6 at 1:22
  • 3
    I agree with Raffzahn. The essence of this question is "debug my code". It would be fine to ask about how the 8080 auxiliary carry flag worked, but don't bring your own project into the question or it will be off-topic. – DrSheldon Jun 6 at 3:45
  • 2
    This is about getting an emulator for an ancient CPU working. The question is about the correct behaviour of one of the status flags. It absolutely does belong on this site. – JeremyP Jun 6 at 9:07
  • 3
    In my mind this could be rephrased as an appropriate question, if it went along the lines of "a piece of documentation 1 says that Z80 command CPI does such and such flag this way, whereas a piece of documentation 2 says something contradictory. Please advise, which way is correct." As is, this is not an appropriate question (I do not understand how this works, I have two sets of code that seem to not do the same, tell me how to fix this) that I am voting to close as well. – introspec Jun 6 at 9:26
4

The auxiliary carry, or in z80 terms the half carry, records whether there was carry (or borrow) between bit 3 and bit 4 in the preceding arithmetic operation.

CPI means compare immediate, so it uses the ALU to subtract the immediate value from the accumulator, then keeps the flags but throws away the result.

Probably the relevant clue compared to the choice you've posted: CPI doesn't pay any regard to the carry flag. It's like SUI, not SBI.

  • Sorry my bad. I should note that the dif_flags function takes the cy argument as an optional one; the default value is 0, which is what my cpi function uses. – David Tran Jun 5 at 23:33
  • Can somebody explain how this answers the question? Why is the half carry different between the two emulators when the code in each case gives the same value for the auxiliary carry flag? – JeremyP Jun 6 at 9:27
  • @JeremyP: your question seems to be much the same topic as you are debating with Raffzahn above — whether just discussing the retro aspect (i.e. the definition of auxiliary carry) is appropriate or whether dissection of the supplied code is appropriate. Although not exactly, as this answer covers something the posted code originally included: carry. Which was a reasonable guess as to the divergence. But I'll decline to extend the other discussion into a second comment area. – Tommy Jun 6 at 12:32
3

Is my algorithm faulty or is theirs?

What their algorithm does is check to see if there has been a carry from bit 3 to bit 4. If you add two bits (even subtraction is done by taking the twos complement and adding), the answers are as follows

0 + 0 = 0
0 + 1 = 1
1 + 0 = 1
1 + 1 = 0 (+ carry)

So bit 4 of the result should be the same as bit 4 of the accumulator exclusive or with bit 4 of the immediate operand i.e.

(c->a ^ val) & 0x10

However, a carry from bit 3 would change that. So a carry will have occurred if

((c->a ^ val) & 0x10) != (result & 0x10)

!= is the same as exclusive or for single bits (and booleans) so you can rewrite the above as

((c->a ^ val) & 0x10) ^ (result & 0x10)

which is clearly the same as

(c->a ^ val ^ result) & 0x10)

They then invert that to get the half carry flag, their reasoning being (I think) that they did a subtraction rather than a twos complement addition.

~(c->a ^ val ^ result) & 0x10)

If you use that formula on the Intel example (documented here under the SUI description), which is 0 - 1, it gives the half carry flag as 0 which is correct according to Intel.

  • So if the half carry flag shouldn't be set, does that make their algorithm wrong? As their half carry flag is set – David Tran Jun 6 at 16:18
  • @DavidTran No. The half carry should not be set when a borrow is required as in 00 - 01. And it should therefore be set when a borrow is not required as in F5 - 00. – JeremyP Jun 6 at 18:10
  • Oh wow I had it backwards this whole time... so the flag is set when there is no borrow? That's weird. Is that the same case for the carry flag? – David Tran Jun 7 at 3:29
  • @DavidTran No. The documentation specifically states that the carry flag is the inverse of the carry out from the two's complement sum when you are doing a subtraction. I just think that the logic to invert the flag is missing for the half carry. – JeremyP Jun 7 at 8:53
2

Ассоrding to this reverse-engineered verilog source of the KR580VM80A (an USSR clone of i8080), auxilliary carry is set as if it is always an addition:

psw_ac <= (c[3] & ~id_xra & ~id_ora & ~id_rxc) | (id_ana & (x[3] | r[3])); // line 922

while the normal carry flag is set to either non-inverted or inverted carry from the carry chain:

           if (id_cmp | id_sbb | id_sub) // line 930
              psw_c <= ~(t2133 | id_rxc & x[0]);
           if (id_dad | id_sha | id_adc | id_add)
              psw_c <= t2133 | id_rxc & x[0];

where

     t2133    <= ~id_rxc & c[7]; // line 888

The reason behind that is that auxilliary carry flag is used only in DAA, which in turn corrects only the result of addition, as it is stated in the instruction set manual.

So the correct way to calculate auxilliary carry in i8080 is to treat both operands as addends.

  • 2
    That's interesting because the Z80 diverges here; you can DAA after a SUB and expect to get the proper outcome — the subtract flag even exists specifically so that DAA knows what it is following. So, I guess, beware emulations and test suites for the 8080 that face relied too heavily on the much-more-available Z80 documentation. – Tommy Jun 6 at 14:36

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.