Search code examples
assemblyavrinteger-arithmetic

Does AVR subtraction of equal numbers set the SREG C-flag?


Short background: The BRLO (Branch if Lower) instruction is stated to 1. ... perform the branch if the C-flag == 1; 2. ... perform the branch if Rd < Rr (from a CP instruction)

My question: I will use 4-bit numbers for demonstration. Suppose that Rd = Rs = 3 = 0011. From point 2 above, the branch should NOT happen. The CP instruction performs Rd - Rr. IF we use two's complement for the subtraction, we have 0011 - 0011 = 0011 + 1100 + 0001 = 0000 WITH a C-flag = 1. Point 1 above therefore contradicts point 2.

Assuming that I am wrong: where did I mess up?

Thanks


Solution

  • In short, operations which give the same result are not always equivalent operations. So, your assumption, that 0011 - 0011 = 0011 + 1100 + 0001 and will have the same carry flag value - is wrong

    Although A + (~B + 1) gives you the same result as A - B it is not the same, and will not update the flags in the same way.

    In fact, you forgot one thing. In your example:

    0011 + 1100 + 0001 = 0000 - is wrong. Because 0011 + 1100 + 0001 = 10000

    You forget that A + (~B + 1) equals to A - B only when considering the two's complement overflow.

    Now you can see the carry flag will be very opposite in the both operations. In fact, A + ~B + 1 will have the overflow (in either first or second addition) then and only then, when A - B have no borrow (underflow).

    When performing addition C flag means the overflow has happened, i.e. one should be carried out from at left of the leftmost bit.

    I.e. if you are adding:

      0110
    + 1101
      ----
     10011
      ^^^^ the result
     ^ the carry flag
    

    That means, if you conduct addition on a longer number, composed of several registers, you have to add one to the higher part. E.g.

     add r16, r18 // adds r18 to r16, C flag is set only when overflow happened
     adc r17, r19 // adds r19 to r18, and add one if C flag is set. updates the C flag by the overflow again
     // in result r17:r16 = r17:r16 + r19:r18
    

    When doing subtraction, C flag means the borrow happened from the left of the leftmost bit, and higher part of the number, therefore, should be decreased by one

     *  *  - borrow positions
      0110
    - 1101
      ----
      0001
     ^ carry flag is set when borrowed at left from the leftmost positions.
    

    In assembler you have the sbc instructions, which makes the subtraction as the sub instruction, but decreases result by one if carry flag is set

     sub r16, r18 // subtracts r18 from r16, C flag is set only when underflow happened
     sbc r17, r19 // subtracts r19 from r18, and subtracts one more if C flag is set. updates the C flag by the underflow again
     // in result r17:r16 = r17:r16 - r19:r18
    

    So, answering your question: when you subtract the number from itself, the value of carry flag is cleared, because no underflow has happened.

    AVR architecture has a simple branching mechanics. It has 8 flags bit and every branch instruction makes branch depending on value of only one bit from those.

    So, BRLO and BRCS both are mnemonics for the same machine code, which makes branch if carry flag is set. Carry flag is set only when you subtract a higher unsigned value from a lower one.

    If you want to make signed comparison, then you have to use the BRLT instruction, which looks at the S flag, which equals to the exclusive OR of flags N and V. N flag is set when operation result is a negative signed number (higher bit is set). V flag means the signed overflow has happened. Their exclusive or combination says when a greater signed number was subtracted from a lesser one.