9

I'm in the process of writing an emulator for the Intel 8080.

The description of the CMP instruction from Intel's 8080 programming manual (see page 20) says the following:

The specified byte is compared to the contents of the accumulator. The comparison is performed by internally subtracting the contents of REG from the accumulator (leaving both unchanged) and setting the condition bits according to the result. In particular, the Zero bit is set if the quantities are equal, and reset if they are unequal. Since a subtract operation is performed, the Carry bit will be set if there is no carry out of bit 7, indicating that the contents of REG are greater than the contents of the accumulator, and reset otherwise.

The following example is given:

Example 1: Assume that the accumulator contains the number 0AH and the E register contains the number 05H. Then the instruction CMP E performs the following internal subtractions:

   Accumulator   = 0AH = 0 0 0 0 1 0 1 0
+  (-E Register) = -5H = 1 1 1 1 1 0 1 1
                         ---------------
                       1 0 0 0 0 0 1 0 1
                       ^
                       |
                       +---- carry = 1, causing the carry bit to be reset

The accumulator still contains 0AH and the E register still contains 05H; however, the Carry bit is reset and the zero bit reset, indicating E less than A.

This makes sense to me. If there is a carry out as a result of adding the 2's complement of a number to the accumulator, the number is less than the accumulator.

This seems to break however if you compare with zero. For example, suppose the accumulator contains 10 (0AH) and the E register contains 0. Then the instruction CMP E performs the following subtraction:

   Accumulator   = 0AH = 0 0 0 0 1 0 1 0
+  (-E Register) = -0H = 0 0 0 0 0 0 0 0
                         ---------------
                       0 0 0 0 0 1 0 1 0
                       ^
                       |
                       +---- carry = 0, causing the carry bit to be set

Here it seems that the carry bit would be set due to there being no carry out of the high order bit position (as is the case with subtraction instructions on the 8080). This however would indicate that 0 is greater than 10.

I know that the carry bit should be reset in my example comparison above, but what's causing it to be reset? Is there some sort of internal circuitry in the 8080 that special cases 0 for subtraction operations?

  • 2
    Most processors do subtraction by inverting the subtrahend and setting the carry in bit rather than fully calculating the negative. I don't know about the 8080 specifically though. – user3570736 May 6 '18 at 20:39
  • Ah yes, this would cause an overflow and the correct behavior. – linuxuser May 6 '18 at 22:41
  • It would probably have been better for the documentation to have described it as the carry bit being set if there's no borrow out of bit 7. So since 00001010 - 00000000 doesn't require borrowing out of the highest bit, the carry bit will be set. Or to put it another way, negating one of the operands, adding, and complementing the carry doesn't perform the same operation as subtraction. – Ross Ridge May 7 '18 at 1:00
11

The documentation makes the mistake of trying to explain the compare instruction in terms of how the 8080 functions internally and then compounds it by getting that explanation wrong.

Check out this reverse engineering of the 8085 ALU for the low-level details of how it works. The 8085 is very similar to the 8080 so I doubt the 8080 is any different.

However, the key observation is that early ALUs on 8 bit CPUs are implemented as a collection of single bit ALUs. The various arithmetic (and logical) instructions choose different settings of control lines to choose the ALU operation. Note that a compare is the same as a subtract except that the result is not written back to A register. A subtract requests that the input bit to each single-bit ALU is flipped. And since a compare is a subtract without carry the carry into bit 0 is set to 0. However, the subtract also requests that the input carry be flipped.

Put it all together and you'll see that a CPI 0 ends up adding 0xFF and carry = 1 to A register. This will always generate a carry which is then flipped as they described resulting in always yielding carry 0.

2

Exact answer to this question requires hardware developer knowledge on the 8080 and related CPU family, it may happen than even those dealing with emulation may not know it.

Two things come to mind:

  1. exception: second operand of CMP is zero (no need in subtraction at all -> first operand goes to result), thus Carry flag will always be 0;
  2. sign extension: CPU may treat two's complement of the zero as 1_00000000, and this "Carry from making 2's complement" inverts the resulting Carry.
0

CISC comparison (CMP) is typically subtraction without carry/borrow that sets only condition flags, result is discarded.

The subtraction-with-borrow style, used in Intel main CPU dynasty (8080, 8086 and so on), PDP-11 and many other computers (despite some of them call it as subtraction with carry), can be described as follows:

Let M is minuend, S is subtrahend, B is previous borrow flag (in Carry bit). Instead of calculating M - S - B, it really calculates M - S - B + 2^N, but as M + (2^N-1 - S) + (1-B), or, M + ~S + ~B, where ^ means power, N is number of bits in arguments, ~ is bitwise NOT. Then, (N+1) bits of result are split into lesser N bits of final result, and a single upper bit that is inverted again and put into Carry bit in Flags a.k.a. Program Status Word (PSW), meaning borrow happening.

Examples, using N=8 (a single octet arguments):

M=5, S=3, B=0: 0000_0101₂ (5) + 1111_1100₂ (~3) + 1 = 1_0000_0010₂, so, difference is 2, borrow is 0.

M=5, S=3, B=1: 0000_0101₂ (5) + 1111_1100₂ (~3) + 0 = 1_0000_0001₂, so, difference is 1, borrow is 0.

M=5, S=8, B=0: 0000_0101₂ (5) + 1111_0111₂ (~-8) + 1 = 0_1111_1101₂, so, difference is 253, borrow is 1 (because MSB of 9-bit result was 0), meaning that result is negative and 1 shall be subtracted from the next higher byte.

and, for your question,

M=10, S=0, B=0: 0000_1010₂ (10) + 1111_1111₂ (~0) + 1 = 1_0000_1010₂, so, difference is 10, borrow is 0 (because MSB of 9-bit result was 1). This is like 0 is converted to 256 before summation. (To compare with: case M=10, S=0, B=1 will result in difference = 9, borrow = 0. Here 0 is converted to 255, because of borrow=1 before subtraction.)

Some architectures, namely Mostek 6502 and all ARM CPUs, use "true carry" subtraction implementation that Carry flag from PSW is not inverted before and after subtraction, so, C=1 means no borrow and C=0 means borrow.

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.