Search code examples
armgnu-assembler

'Wrong' usage of carry flag in ARM subtract instructions?


The ARM subtraction instructions with carry (SBC, RSC) interpret the carry flag (C) as:

  • 0 means borrow
  • 1 means no borrow

Why carry flag C is inversed to make the arithmetic?

SBC R0, R1, R2 @ R0 = R1 - R2 - !C

Solution

  • We know from grade school that

    a = b - c = b + (-c)
    

    and from elementary programming classes that to negate a number in twos complement we invert a number and add one so

    a = b + (~c) + 1
    

    And that works out perfectly because when we feed the adder logic our values we invert the second operand and invert the carry in. (there is no subtract logic you use an adder to do subtraction)

    So some processor implementations choose to invert the carry out. You are already inverting stuff to do a subtract, and the raw carry out of the msbit on a normal subtract (without borrow) is a 1, when there is a borrow the carry out is a 0, so inverting the carry out you can then call it a "borrow" instead of a carry (or instead of a /borrow or !borrow or borrow_n)

    Which ever way you do it, if you have a subtract with borrow, then you need to either invert or not invert the carry in depending your design choices for the carry out on a subtract (normal without borrow).

    So subtraction is

    a = b + (~c) + 1
    

    And when you cascade that subtraction (sbc) then if there was no borrow then

    a = b + (~c) + 1
    

    But if there was a borrow then that next level needs to have a 1 removed so the sbc becomes

    a = b + (~c) 
    

    The carry in is a zero in that case. So from the adder logic perspective when you do a subtract with borrow you naturally have to invert the second operand, but you carry in a 0 if you have to borrow and carry in a 1 if you dont.

    The arm docs on a SBC say

    Rd = Rd - Rm - NOT(C Flag)

    So if we need to take one away due to a prior borrow then C needed to be a 0 else C is a 1 going in.

    The subtractions all basically say

    C Flag = NOT BorrowFrom(operation)

    So if we need to borrow then borrow from is true so C flag is not true or a 0. If we dont need to borrow, then borrow from is false a 0, and not borrow from is a 1. So that all matches up, if we needed to borrow then going into SBC C needs to be 0.

    So basically ARM does not appear to modify the carry out from the adder. And so it does not need to invert the carry in on an SBC.