Reputation: 59
I am studying the instructions present in the arm architecture and I came across concept of conditionally executable instructions like MOVEQ, CMPNE, MOVGT etc., I understand that for conditionally executable instructions, the decision whether to execute the instruction or not is made based on flags N (negative), Z (zero), C (carry), and V (overflow).
For e.g. to test if two values in registers R1 and R2 are equal or not, the following compare instruction is executed -> CMP R1, R2, and the flag Z is monitored to arrive at the conclusion i.e., if A and B are equal then Z = 1 (because A-B is zero) else Z = 0.
Similarly, there are other conditions and there are respective flag values that are looked up. The table lists those condition code and the corresponding flags.
My question is how are these flags computed? For e.g. When the condition is GE (greater than or equal) why are the flags Z and V expected to have same values i.e., (Z=V)?
Upvotes: 0
Views: 1072
Reputation: 71606
#include <stdio.h>
int main ( void )
{
unsigned int N,C,V,Z;
unsigned int ra,rb,rc;
unsigned int rd,re;
for(ra=0;ra<8;ra++)
{
for(rb=0;rb<8;rb++)
{
rc=(ra&7)+((~rb)&7)+1;
rd=(ra&3)+((~rb)&3)+1;
rd=(rd>>2)&1;
rd^=((rc>>3)&1);
N=(rc>>2)&1;
C=(rc>>3)&1;
Z=0; if((rc&7)==0) Z=1;
V=rd;
for(re=4;re;re>>=1) if(re&ra) printf("1"); else printf("0");
printf(" - ");
for(re=4;re;re>>=1) if(re&rb) printf("1"); else printf("0");
printf(" = ");
for(re=4;re;re>>=1) if(re&rc) printf("1"); else printf("0");
printf(" N %u C %u V %u Z %u",N,C,V,Z);
if(N==V) printf(" NV"); else printf(" ");
if((C==1)&&(Z==0)) printf(" Cz");
printf("\n");
}
}
return(0);
}
What works for 3 bits works for 32, works for 99 works for 111 (usually easier to do this with 4 bits not three, but that makes for more output to deal with, more bits gives you more signed positive and negative numbers)
001 - 000 = 001 N 0 C 1 V 0 Z 0 NV Cz
001 - 001 = 000 N 0 C 1 V 0 Z 1 NV
001 - 010 = 111 N 1 C 0 V 0 Z 0
001 - 011 = 110 N 1 C 0 V 0 Z 0
SIGNED less than is when N!=V that extra 1 at the end is if N==V
So these (above) are positive numbers within the signed world 1 - 0 is greater than 1 - 1 is equal to 1 - 2 is less than as is 1 - 3. When less than N is not equal to V when not less than (greater than or equal) N == V.
UNSIGNED higher (unsigned greater than) is when C is set and Z is zero. which is the 1 - 0 case.
This is important to understand and this does not work for all architectures, x86 may not work this way. Some architectures the carry out is inverted into a borrow flag for subtraction, other architectures it goes out unmodified and is not considered a borrow flag. I have not modified it here and I am doing a subtraction.
If we didn't see this C set case for 1 - 0 that would indicate that we needed to invert it into a borrow. Basically if C is set on a subtract then it is an UNSIGNED greater than or equal as shown in your table or at least the one in the ARM ARM that I prefer to read from.
The if zero not set means that it is excluding the equal out of greater than or equal leaving (unsigned) greater than.
Some architectures you don't need to get too complicated, for example:
UNSIGNED
a - b c set a >= b
a - b c clear a < b
b - a c set b >= a so a <= b
b - a c clear b < a so a > b
With the single carry flag and ordering of operands you can determine unsigned greater than or less than with or without equal you don't need the z flag for this.
101 - 101 = 000 N 0 C 1 V 0 Z 1 NV
101 - 110 = 111 N 1 C 0 V 0 Z 0
101 - 111 = 110 N 1 C 0 V 0 Z 0
-3 - -3 = 0 NV
-3 - -2 = -1
-3 - -1 = -2
-3 is equal to -3 so NV indicates signed greater than or equal
-3 is less than -2 so N!=V
-3 is less tahn -1 so N!=V
If you want to know the whys of it then I will let you ponder that. I learned that V is computed by comparing the carry in to the carry out of the msbit if they differ V is set otherwise not. The other way to compute it which can make more sense visually is if the msbit of the operands is the same and the msbit of the result is different then V is set otherwise not.
Basically if the sign of the operands is the same and the sign of the result is different then signed overflow. so combining the negative (sign) flag (of the result) and comparing it to a flag that is determined by the signs of the inputs and output, you can start to see how they are related. You can go through by hand with some 4 bit cases for example and see this in action.
I have written this answer at SO multiple times in different ways and sometimes I mess up the program that dumps the table, so hopefully I got it right first time this time.
Now there may be exceptions to the rule for flags, but in general twos complement is used. Subtraction is done based on what we learned in grade school: a - b = a + -b, and then in beginning programming class we learned to "twos complement" (negate) a number you invert and add one so a - b = a + (~b) +1, which fits very nicely into logic because addition is done with three input two output blobs to add one bit you need carry in, operand a, operand b, carry out and result. The lsbit doesn't have carry out from a prior bit so you hardcode it. For addition you hardcode it to a 0 subtraction you invert or hardcode it to a 1, for subtraction you invert the second operand on the way into the adder, all works very very nicely. Anyway, in general understanding there may be some exceptions
C is the carry bit, it is the carry out of the msbit
N is the msbit of the result, indicates negative if viewed as a signed number
Z is set if the result is zero, otherwise not
V is set if the carry in and carry out of msbit don't match otherwise not.
Some architectures use different letters names. C is both carry out and unsigned overflow. V is signed overflow some architectures just call it overflow but it is specific to when the programmer considers the numbers to be signed. C is the overflow flag when the numbers are considered to be unsigned. Best to think and talk about it as signed overflow and not just overflow.
As mentioned above some architectures invert the carry out if the operation was a subtract making it indicate a borrow happened (b was greater than a).
Unlike most other instruction sets, ARM does not set the flag on every little operation you can do an add operation and NOT set the flags or you can do an ADD operation and set the flags, this is so that you can use the condition field feature of the instruction set, you can do an operation (cmp, sub, add, tst, whatever) where you allow one or more flags to be set and then you can have a series of instructions that don't modify the flags but use them:
cmp r0,r1
movne r2,#1
moveq r2,#0
In other architectures you would need to incur branches with pipe side effects
cmp r0,r1
beq over
mov r2,#1
b skip
over:
mov r2,#0
skip:
Pretty cool feature, but not enough bits in thumb to use it and they dropped it in aarch64.
Mips is the opposite and its siblings openrisc, risc-v, etc. instead of carrying flags across instructions and having to deal with pipe issues that way, you do the operation AND act on it in the same instruction compare and branch if equal, compare and branch if not equal. the flags are computed the same it is just how stored, for how long and how used that differs.
Upvotes: 1