CP/M Assembly Language Part IV: Flags by Eric Meyer Last time we learned how to make an assembly language program talk. I hope this was entertaining enough to keep your interest a while. There are many dozen 8080 instructions, and you'll need to know a lot more of them before you can attempt more sophisticated tasks. This time we'll learn about that mysterious "F" register, the Flags: what they are, and how to use them to make decisions. But first (you'll see why) we must learn a little more arithmetic. 1. Addition and Subtraction We've already seen the INC and DEC instructions, which add or subtract 1 from any register. The accumulator ("A" register) can also do fancier arithmetic. The ADI and SUI (add and subtract immediate) instructions can be used to add or subtract a value from the accumulator. For example: ADI 12 would add 12 to whatever was in "A" before. Similarly, the ADD and SUB instructions add or subtract the contents of any other register from the accumulator; thus: SUB C would subtract the value in "C" from the value in "A". You can even use the instructions ADD A, which adds A to itself (doubles A), and SUB A, which subtracts it from itself (leaving, oddly enough, zero). 2. The Flags The really powerful feature of a computer program is that it can make decisions; depending on the data you supply, it doesn't have to execute the same way every time. No doubt you're already familiar with statements like: 210 IF RESULT<0 THEN GOTO 1000 The same sort of thing can be done in assembly language, but it's more cumbersome, and it happens in stages. You perform some operation, like arithmetic or comparison, and one or more flags are set as a byproduct; then you can JMP or CALL conditionally, depending on the state of the flags. The "F" register contains 8 bits, each of which can be thought of as a little flag that's either on or off, yes or no. The two most-commonly used flags are called Zero and Carry, or Z and C for short. Basically, the Zero flag is set when an operation results in a register going to zero; and the Carry (or borrow) flag is set when an accumulator operation results in overflow or underflow. Each flag retains its status until changed by the next operation that affects it. As a result, for each arithmetic operation, you have to learn exactly how it affects the flags. Here's a summary so far: INR, DCR -- Z means register is now 0 C NOT affected ADD, ADI -- Z means A is now 0 C means overflow (>255) SUB, SUI -- Z means A is now 0 C means underflow (<0) Occasionally this is real confusing. Notice, for example, that while SUI 1 affects the Carry flag, DCR A (which is otherwise identical) does not. Note also that 16-bit operations INX and DCX do not affect any of the flags. If you want to know whether a 16-bit number has been decremented to zero, you have to do something devious, like add its two bytes together and then look at the Z flag. 3. Comparison The comparison operation is just like subtraction, but it doesn't change the accumulator; it only changes the flags. The instruction is CMP for a register, or CPI for an immediate data value. Thus, for example, CPI 12 affects the flags exactly as SUI 12 would have, but without actually doing the subtraction. In particular, if the value in A had actually been 12, the Zero flag would now be set. Thus the Z flag tends to mean "Yes, that's it", as well as literally "Zero". Similarly, if the value in "A" had been less than 12, the Carry flag would now be set. So the C flag tends to mean "Less than", as well as literally "Carry/borrow". 4. Conditional Branches It should now come as no surprise that there's a whole set of JMP and CALL instructions that execute conditionally, according to the status of the flags. Some of these are: JZ -- JMP if Z flag set CZ -- CALL if Z flag set JNZ -- JMP if Z flag Not set CNZ -- CALL if Z flag Not set JC -- JMP if C flag set CC -- CALL if C flag set JNC -- JMP if C flag Not set CNC -- CALL if C flag Not set In order to illustrate, let's rewrite a program from Part III to show that your computer really cares: BDOS EQU 0005H ORG 0100H MVI C,9 ;print string LXI D,QUESTN ;(this one) CALL BDOS MVI C,1 ;get input CALL BDOS ;character is now in A LXI D,YESMSG ;start with YESMSG CPI 'Y' ;was input "Y"? JZ YES ;jump to YES if so NO: LXI D,NOMSG ;NO, change to NOMSG YES: MVI C,9 CALL BDOS ;print the answer RET ;all done ;data QUESTN: DB 'Are you excited? (Y/N) $' YESMSG: DB ' Great!$' NOMSG: DB ' Hm, too bad.$' END As you can see, after asking the question and getting one character from the keyboard, the program sets up to reply with YESMSG. It then compares the character to "Y": if it matches, the Z flag is set, so it JMPs to YES and prints YESMSG. If it doesn't match, it falls through to NO instead, and switches to NOMSG. 5. Loops Another very common use of the Z flag is in loops. Suppose you had expanded the BYTEMOV program of Part III to copy 10 characters; you would have had to write: CALL MOVBYT, CALL MOVBYT, ... 10 times. Now you have a far better way to do this: MVI B,10 ;do ten times LOOP: CALL MOVBYT ;move a byte DCR B ;count down JNZ LOOP ;loop if not zero yet Obviously, you can have the loop execute as many times as needed just by changing the value in "B". This is the assembly language equivalent of a FOR...NEXT or WHILE loop. 6. The Carry Flag: 16-bit Arithmetic Let's end as we began, learning instructions for arithmetic. You can also add and subtract 16-bit (word) values, although it's not quite as simple as with single registers. For addition, the H-L registers can function as a sort of "accumulator": the instruction DAD (double add) adds the contents of a register pair to H-L. Thus DAD B adds B-C to H-L. Similarly DAD D adds D-E. (You can even use the instruction DAD H, which adds H-L to itself.) The DAD instruction does not affect the Z flag; but it does use the C flag to indicate whether (16-bit) overflow occurred. There is no "immediate" 16-bit add; you have to load the value into a register pair, then add it. Subtraction is harder; the Z80 CPU has a double-subtract instruction, but the 8080 does not. You have to do it one byte at a time, in the accumulator. If you wanted, say, to subtract D-E from H-L, you might try the following: MOV A,L ;get L (the low byte) SUB E ;subtract E from it MOV L,A ;and put it back MOV A,H ;get H (the high byte) SUB D ;subtract D from it MOV H,A ;and put it back Unfortunately, this would sometimes not work, because it doesn't allow "borrowing" from the "H" register in the event that "E" is bigger than "L". The second subtraction (SUB D) isn't affected by the outcome of the first. (Imagine subtracting 9 from 26; if you forget to borrow, you get 27 instead of 17.) This is where the Carry flag comes in. Remember that if "E" is bigger, the "SUB E" instruction is going to give a result modulo 256, and the C flag will be set. Then, you need to remember to subtract an extra 1 from the high byte if the C flag is set. This is where a new set of arithmetic instructions comes in: ADD, ADI ---> ADC, ACI : "add with carry" SUB, SUI ---> SBB, SBI : "subtract with borrow" These add (or subtract) 1 more if the C flag is set. If you change the "SUB D" above to "SBB D", the problem will be solved. This is why the C flag is called "Carry", and functions as it does. 7. Coming Up . . . Next time we'll look at the remaining big mystery: the stack. Then we can begin to write some useful routines.