I'm having trouble understanding some instructions like the sub
instruction are defined according to the manual as an AddWithCarry
operation where the carry is set to a hard coded value of 1
:
bits(datasize) result;
bits(datasize) operand1 = if n == 31 then SP[]<datasize-1:0> else X[n, datasize];
bits(datasize) operand2;
operand2 = NOT(imm);
(result, -) = AddWithCarry(operand1, operand2, '1');
if d == 31 then
SP[] = ZeroExtend(result, 64);
else
X[d, datasize] = result;
The AddWithCarry
operation is defined as such:
(bits(N), bits(4)) AddWithCarry(bits(N) x, bits(N) y, bit carry_in)
integer unsigned_sum = UInt(x) + UInt(y) + UInt(carry_in);
integer signed_sum = SInt(x) + SInt(y) + UInt(carry_in);
bits(N) result = unsigned_sum<N-1:0>; // same value as signed_sum<N-1:0>
bit n = result<N-1>;
bit z = if IsZero(result) then '1' else '0';
bit c = if UInt(result) == unsigned_sum then '0' else '1';
bit v = if SInt(result) == signed_sum then '0' else '1';
return (result, n:z:c:v);
Wouldn't passing 1
as carry all the time and looking by the definition of AddWithCarry
, make the subtraction operation subtract 1
from all operations as-well?
I know when we write stuff like:
sub sp, sp, #0x20
We actually only subtract 32 bytes from sp
so what's up with the carry bit in this operation?
This also confused me at first. The key is the line before: it isn't
operand2 = -imm
as you might expect, butoperand2 = NOT(imm)
, i.e. bitwise not (one's complement). In two's complement arithmetic, you can readily check thatNOT(imm) = -imm - 1
. So the carry being set to 1 effectively computesx + (-imm - 1) + 1
which is indeedx - imm
.It's sort of an artifact of the way their pseudocode language is set up: their integer types are pure mathematical integers able to represent any number whatsoever, and arithmetic operators are defined only on such types. But here, the operands are of type
bits(n)
, simply a bitstring, for which they define logical operators only. So writingoperand2 = -imm
wouldn't be well-formed. They'd have to say something likeoperand2 = (-SInt(imm))<n-1:0>
which would be even more confusing.It might also reflect a way that a simple ALU could implement addition instructions. Rather than needing separate units for ADD, SUB, ADC, etc, you just need one that does add-with-carry. So ADC would connect the carry input of this unit to the actual C bit of the NZCV register; ADD would connect it to ground. SUB would pass the first input through an inverter, and connect the carry input to VDD. SBC does the same with the inverter and connects the carry input to the C flag. (Note this results in subtraction treating the C flag like a true carry, as opposed to x86 where SUB inverts the sense of the carry flag to make it behave like a borrow.)