================================= Chapter 4: Programming Techniques ================================= This section describes some techniques other than macros which may be of help to the programmer. Branch Tables Pseudo-Subroutine =============================== Suppose a program consists of several separate routines, any of which may be executed depending upon some initial condition (such as a number passed in a register). One way to code this would be to check each condition sequentially and branch to the routines accordingly as follows: .. code-block:: none CONDITION = CONDITION 1? IF YES BRANCH TO ROUTINE 1 CONDITION = CONDITION 2? IF YES BRANCH TO ROUTINE 2 . . . BRANCH TO ROUTINE N A sequence as above is inefficient, and can be improved by using a branch table. The logic at the beginning of the branch table program computes a pointer into the branch table. The branch table itself consists of a list of starting addresses for the routines to be branched to. Using the pointer, the branch table program loads the selected routine's starting address into the address bytes of a jump instruction, then executes the jump. For example, consider a program that executes one of eight routines depending on which bit of the accumulator is set: .. code-block:: none Jump to routine 1 if the accumulator holds 00000001 " " " 2 " " " " 00000010 " " " 3 " " " " 00000100 " " " 4 " " " " 00001000 " " " 5 " " " " 00010000 " " " 6 " " " " 00100000 " " " 7 " " " " 01000000 " " " 8 " " " " 10000000 A program that provides the above logic is given at the end of this section. The program is termed a "pseudo-subroutine" because it is treated as a subroutine by the programmer (i.e., it appears just once in memory), but it is entered via a regular JUMP instruction rather than via a CALL instruction. This is possible because the branch routine controls subsequent execution, and will never return to the instruction following the call: .. scan-figure:: 54 0.52 0.7 0.93 0.95 :alt: Main program jumps to branch table program, which jumps to one of the routines .. code-block:: none Label Code Operand START: LXI H,BTBL ; Registers H and L will ; point to branch table. GTBIT: RAR JC GETAD INX H ; (H,L)=(H,L)+2 to INX H ; point to next address ; in branch table. JMP GTBIT GETAD: MOV E,M ; A one bit was found. INX H ; Get address in D and ; E. MOV D,M XCHG ; Exchange D and E ; with H and L. PCHL ; Jump to routine ; address. . . BTBL: DW ROUT1 ; Branch table. Each DW ROUT2 ; entry is a two-byte ; address DW ROUT3 ; held least significant DW ROUT4 ; byte first. DW ROUT5 DW ROUT6 DW ROUT7 DW ROUT8 The control routine at START uses the H and L registers as a pointer into the branch table (BTBL) corresponding to the bit of the accumulator that is set. The routine at GETAD then transfers the address held in the corresponding branch table entry to the H and L registers via the D and E registers, and then uses a PCHL instruction, thus transferring control to the selected routine. Subroutines =========== Frequently, a group of instructions must be repeated many times in a program. As we have seen in Chapter 3, it is sometimes helpful to define a macro to produce these groups. If a macro becomes too lengthy or must be repeated many times, however, better economy can be obtained by using subroutines. A subroutine is coded like any other group of assembly language statements, and is referred to by its name, which is the label of the first instruction. The programmer references a subroutine by writing its name in the operand field of a CALL instruction. When the CALL is executed, the address of the next sequential instruction after the CALL is pushed onto the stack (see the section on the Stack Pointer in Chapter 1), and program execution proceeds with the first instruction of the subroutine. When the subroutine has completed its work, a RETURN instruction is executed, which causes the top address in the stack to be popped into the program counter, causing program execution to continue with the instruction following the CALL. Thus, one copy of a subroutine may be called from many different points in memory, preventing duplication of code. Example: Subroutine MINC increments a 16-bit number held least-significant-byte first in two consecutive memory locations, and then returns to the instruction following the last CALL statement executed. The address of the number to be incremented is passed in the H and L registers. .. code-block:: none Label Code Operand Comment MINC: INR M ; Increment low-order byte RNZ ; If non-zero, return to ; calling routine INX H ; Address high-order byte INR M ; Increment high-order byte RET ; Return unconditionally Assume MINC appears in the following program: .. scan-figure:: 55 0.5 0.40 0.93 0.63 :alt: Two calls to MINC from 2C00 and 2EF0 When the first call is executed, address 2C03H is pushed onto the stack indicated by the stack pointer, and control is transferred to 3C00H. Execution of either RETURN statement in MINC will cause the top entry to be popped off the stack into the program counter, causing execution to continue at 2C03H (since the CALL statement is three bytes long). .. scan-figure:: 55 0.5 0.745 0.93 0.95 :erase: 0.50 0.74 0.70 0.762 :alt: Stack before CALL, while MINC executes, and after RETURN .. todo:: The stack diagram is transcribed as printed (``scans/page-55.png``). It shows the pushed return address as bytes 2C and 00 (2C00H), but the text says 2C03H is pushed. The low byte should probably be 03. When the second call is executed, address 2EF3H is pushed onto the stack, and control is again transferred to MINC. This time, either RETURN instruction will cause execution to resume at 2EF3H. Note that MINC could have called another subroutine during its execution, causing another address to be pushed onto the stack. This can occur as many times as necessary, limited only by the size of memory available for the stack. Note also that any subroutine could push data onto the stack for temporary storage without affecting the call and return sequences as long as the same amount of data is popped off the stack before executing a RETURN statement. Transferring Data To Subroutines -------------------------------- A subroutine often requires data to perform its operations. In the simplest case, this data may be transferred in one or more registers. Subroutine MINC in the last section, for example, receives the memory address which it requires in the H and L registers. Sometimes it is more convenient and economical to let the subroutine load its own registers. One way to do this is to place a list of the required data (called a parameter list) in some data area of memory, and pass the address of this list to the subroutine in the H and L registers. For example, the subroutine ADSUB expects the address of a three-byte parameter list in the H and L registers. It adds the first and second bytes of the list, and stores the result in the third byte of the list: .. code-block:: none Label Code Operand Comment LXI H,PLIST ; Load H and L with ; addresses of the param- ; eter list CALL ADSUB ; Call the subroutine RET1: -- . PLIST: DB 6 ; First number to be added DB 8 ; Second number to be ; added DS 1 ; Result will be stored here . LXI H,LIST2 ; Load H and L registers CALL ADSUB ; for another call to ADSUB RET2: -- . LIST2: DB 10 DB 35 DS 1 . ADSUB: MOV A,M ; Get first parameter INX H ; Increment memory ; address MOV B,M ; Get second parameter ADD B ; Add first to second INX H ; Increment memory ; address MOV M,A ; Store result at third ; parameter store RET ; Return unconditionally The first time ADSUB is called, it loads the A and B registers from PLIST and PLIST+1 respectively, adds them, and stores the result in PLIST+2. Return is then made to the instruction at RET1. First call to ADSUB: .. scan-figure:: 56 0.52 0.18 0.93 0.355 :alt: First call to ADSUB: H,L point to PLIST The second time ADSUB is called, the H and L registers point to the parameter list LIST2. The A and B registers are loaded with 10 and 35 respectively, and the sum is stored at LIST2 + 2. Return is then made to the instruction at RET2. Second call to ADSUB: .. scan-figure:: 56 0.52 0.505 0.93 0.68 :alt: Second call to ADSUB: H,L point to LIST2 Note that the parameter lists PLIST and LIST2 could appear anywhere in memory without altering the results produced by ADSUB. This approach does have its limitations, however. As coded, ADSUB must receive a list of two and only two numbers to be added, and they must be contiguous in memory. Suppose we wanted a subroutine (GENAD) which would add an arbitrary number of bytes, located anywhere in memory, and leave the sum in the accumulator. This can be done by passing the subroutine a parameter list which is a list of *addresses* of parameters, rather than the parameters themselves, and signifying the end of the parameter list by a number whose first byte is FFH (assuming that no parameters will be stored above address FF00H). Call to GENAD: .. scan-figure:: 57 0.06 0.085 0.48 0.29 :alt: GENAD parameter list of addresses pointing to PARM1-PARM4 .. todo:: The GENAD diagram (``scans/page-57.png``) shows PARM1 = 8, but the program below defines ``PARM1: DB 6``. This inconsistency is in the original. As implemented below, GENAD saves the current sum (beginning with zero) in the C register. It then loads the address of the first parameter into the D and E registers. If this address is greater than or equal to FF00H, it reloads the accumulator with the sum held in the C register and returns to the calling routine. Otherwise, it loads the parameter into the accumulator and adds the sum in the C register to the accumulator. The routine then loops back to pick up the remaining parameters. .. code-block:: none Label Code Operand Comment LXI H,PLIST ; Calling program CALL GENAD . PLIST: DW PARM1 ; List of parameter addresses DW PARM2 DW PARM3 DW PARM4 DW 0FFFFH ; Terminator . PARM1: DB 6 PARM4: DB 16 . PARM3: DB 13 . PARM2: DB 82 . GENAD: XRA A ; Clear accumulator LOOP: MOV C,A ; Save current total in C MOV E,M ; Get low order address byte ; of first parameter INX H MOV A,M ; Get high order address byte ; of first parameter CPI 0FFH ; Compare to FFH JZ BACK ; If equal, routine is complete MOV D,A ; D and E now address parameter LDAX D ; Load accumulator with parameter ADD C ; Add previous total INX H ; Increment H and L to point ; to next parameter address JMP LOOP ; Get next parameter BACK: MOV A,C ; Routine done--restore total RET ; Return to calling routine Note that GENAD could add any combination of the parameters with no change to the parameters themselves. The sequence: .. code-block:: none LXI H,PLIST CALL GENAD . PLIST: DW PARM4 DW PARM1 DW 0FFFFH would cause PARM1 and PARM4 to be added, no matter where in memory they might be located (excluding addresses above FF00H). Many variations of parameter passing are possible. For example, if it was necessary to allow parameters to be stored at any address, a calling program could pass the total number of parameters as the first parameter; the subroutine would load this first parameter into a register and use it as a counter to determine when all parameters had been accepted. Software Multiply and Divide ============================ The multiplication of two unsigned 8-bit data bytes may be accomplished by one of two techniques: repetitive addition, or use of a register shifting operation. Repetitive addition provides the simplest, but slowest, form of multiplication. For example, 2AH·74H may be generated by adding 74H to the (initially zeroed) accumulator 2AH times. Using shift operations provides faster multiplication. Shifting a byte left one bit is equivalent to multiplying by 2, and shifting a byte right one bit is equivalent to dividing by 2. The following process will produce the correct 2-byte result of multiplying a one byte multiplicand by a one byte multiplier: (a) Test the least significant bit of the multiplier. If zero, go to step b. If one, add the multiplicand to the *most* significant byte of the result. (b) Shift the entire two-byte result right one bit position. (c) Repeat steps a and b until all 8 bits of the multiplier have been tested. For example, consider the multiplication: 2AH·3CH=9D8H .. code-block:: none HIGH-ORDER BYTE LOW-ORDER BYTE MULTIPLIER MULTIPLICAND OF RESULT OF RESULT Start 00111100 00101010 00000000 00000000 Step 1 a -------------------------------- b 00000000 00000000 Step 2 a -------------------------------- b 00000000 00000000 Step 3 a -------------------------------- 00101010 00000000 b 00010101 00000000 Step 4 a -------------------------------- 00111111 00000000 b 00011111 10000000 Step 5 a -------------------------------- 01001001 10000000 b 00100100 11000000 Step 6 a -------------------------------- 01001110 11000000 b 00100111 01100000 Step 7 a -------------------------------- b 00010011 10110000 Step 8 a -------------------------------- b 00001001 11011000 Step 1: Test multiplier 0-bit; it is 0, so shift 16-bit result right one bit. Step 2: Test multiplier 1-bit; it is 0, so shift 16-bit result right one bit. Step 3: Test multiplier 2-bit; it is 1, so add 2AH to high-order byte of result and shift 16-bit result right one bit. Step 4: Test multiplier 3-bit; it is 1, so add 2AH to high-order byte of result and shift 16-bit result right one bit. Step 5: Test multiplier 4-bit; it is 1, so add 2AH to high-order byte of result and shift 16-bit result right one bit. Step 6: Test multiplier 5-bit; it is 1, so add 2AH to high-order byte of result and shift 16-bit result right one bit. Step 7: Test multiplier 6-bit; it is 0, so shift 16-bit result right one bit. Step 8: Test multiplier 7-bit; it is 0, so shift 16-bit result right one bit. The result produced is 09D8. The process works for the following reason: The result of any multiplication may be written: .. math:: :label: eq-4-1 \text{BIT7}\cdot\text{MCND}\cdot 2^7 + \text{BIT6}\cdot\text{MCND}\cdot 2^6 + \ldots + \text{BIT0}\cdot\text{MCND}\cdot 2^0 where BIT0 through BIT8 are the bits of the multiplier (each equal to zero or one), and MCND is the multiplicand. For example: .. code-block:: none MULTIPLICAND MULTIPLIER 00001010 · 00000101 = 0·0AH·2^7 + 0·0AH·2^6 + 0·0AH·2^5 + 0·0AH·2^4 + 0·0AH·2^3 + 1·0AH·2^2 + 0·0AH·2^1 + 1·0AH·2^0 = 00101000 + 00001010 = 00110010 = 50 (decimal) Adding the multiplicand to the high-order byte of the result is the same as adding MCND·2\ :sup:`8` to the full 16-bit result; shifting the 16-bit result one position to the right is equivalent to multiplying the result by 2\ :sup:`-1` (dividing by 2). Therefore, step one above produces: .. math:: (\text{BIT0}\cdot\text{MCND}\cdot 2^8)\cdot 2^{-1} Step two produces: .. math:: ((\text{BIT0}\cdot\text{MCND}\cdot 2^8)\cdot 2^{-1} + (\text{BIT1}\cdot\text{MCND}\cdot 2^8))\cdot 2^{-1} = \text{BIT0}\cdot\text{MCND}\cdot 2^6 + \text{BIT1}\cdot\text{MCND}\cdot 2^7 And so on, until step eight produces: .. math:: \text{BIT0}\cdot\text{MCND}\cdot 2^0 + \text{BIT1}\cdot\text{MCND}\cdot 2^1 + \ldots + \text{BIT7}\cdot\text{MCND}\cdot 2^7 which is equivalent to Equation 1 above, and therefore is the correct result. Since the multiplication routine described above uses a number of important programming techniques, a sample program is given with comments. The program uses the B register to hold the most significant byte of the result, and the C register to hold the least significant byte of the result. The 16-bit right shift of the result is performed by two rotate-right-through-carry instructions: Zero carry and then rotate B .. scan-figure:: 59 0.51 0.2 0.93 0.31 :alt: Zero carry and then rotate B Then rotate C to complete the shift .. scan-figure:: 59 0.51 0.36 0.93 0.45 :alt: Then rotate C to complete the shift Register D holds the multiplicand, and register C originally holds the multiplier. .. code-block:: none MULT: MVI B,0 ; Initialize most significant byte ; of result MVI E,9 ; Bit counter MULT0: MOV A,C ; Rotate least significant bit of RAR ; multiplier to carry and shift MOV C,A ; low-order byte of result DCR E JZ DONE ; Exit if complete MOV A,B JNC MULT1 ADD D ; Add multiplicand to high- ; order byte of result if bit ; was a one MULT1: RAR ; Carry=0 here; shift high- ; order byte of result MOV B,A JMP MULT0 DONE: An analogous procedure is used to divide an unsigned 16-bit number by an unsigned 8-bit number. Here, the process involves subtraction rather than addition, and rotate-left instructions instead of rotate-right instructions. The program uses the B and C registers to hold the most and least significant byte of the dividend respectively, and the D register to hold the divisor. The 8-bit quotient is generated in the C register, and the remainder is generated in the B register. .. code-block:: none DIV: MVI E,9 ; Bit counter MOV A,B DIV0: MOV B,A MOV A,C ; Rotate carry into C ; register; rotate next ; most significant bit ; to carry MOV C,A DCR E JZ DIV2 MOV A,B ; Rotate most significant RAL ; bit to high-order JNC DIV1 ; quotient SUB D ; Subtract divisor & loop JMP DIV0 DIV1: SUB D ; Subtract divisor. If ; less than high-order JNC DIV0 ; quotient, loop. ADD D ; Otherwise, add it back JMP DIV0 DIV2: RAL MOV E,A MVI A,0FFH ; Complement the quotient XRA C MOV C,A MOV A,E RAR DONE: .. todo:: The DIV listing is transcribed as printed (``scans/page-60.png``). The comment on ``MOV A,C`` ("Rotate carry into C register; rotate next most significant bit to carry") describes a rotate, but no ``RAL`` follows it in the listing. This may be a misprint in the original. Multibyte Addition and Subtraction ================================== The carry bit and the ADC (add with carry) instructions may be used to add unsigned data quantities of arbitrary length. Consider the following addition of two three-byte unsigned hexadecimal numbers: .. code-block:: none 32AF8A + 84BA90 ------ B76A1A This addition may be performed on the 8080 by adding the two low-order bytes of the numbers, then adding the resulting carry to the two next-higher-order bytes, and so on: .. scan-figure:: 60 0.10 0.705 0.45 0.825 :alt: Adding byte by byte with carry The following routine will perform this multibyte addition, making these assumptions: The C register holds the length of each number to be added (in this case, 3). The numbers to be added are stored from low-order byte to high-order byte beginning at memory locations FIRST and SECND, respectively. The result will be stored from low-order byte to high-order byte beginning at memory location FIRST, replacing the original contents of these locations. .. scan-figure:: 60 0.52 0.12 0.93 0.39 :alt: FIRST and SECND before and after multibyte addition .. code-block:: none Label Code Operand Comment MADD: LXI B,FIRST ; B and C address FIRST LXI H,SECND ; H and L address SECND XRA A ; Clear carry bit LOOP: LDAX B ; Load byte of FIRST ADC M ; Add byte of SECND ; with carry STAX B ; Store result at FIRST DCR C ; Done if C = 0 JZ DONE INX B ; Point to next byte of ; FIRST INX H ; Point to next byte of ; SECND JMP LOOP ; Add next two bytes DONE: -- . FIRST: DB 90H DB 0BAH DB 84H SECND: DB 8AH DB 0AFH DB 32H Since none of the instructions in the program loop affect the carry bit except ADC, the addition with carry will proceed correctly. When location DONE is reached, bytes FIRST through FIRST+2 will contain 1A6AB7, which is the sum shown at the beginning of this section arranged from low-order to high-order byte. The carry (or borrow) bit and the SBB (subtract with borrow) instruction may be used to subtract unsigned data quantities of arbitrary length. Consider the following subtraction of two two-byte unsigned hexadecimal numbers: .. code-block:: none 1301 - 0503 ---- 0DFE This subtraction may be performed on the 8080 by subtracting the two low-order bytes of the numbers, then using the resulting carry bit to adjust the difference of the two higher-order bytes if a borrow occurred (by using the SBB instruction). Low-order subtraction (carry bit = 0 indicating no borrow): .. code-block:: none 00000001 = 01H 11111101 = -(03H+carry) -------- 0 11111110 = 0FEH, the low-order result | +--> carry out = 0, setting the Carry bit = 1, indicating a borrow High-order subtraction: .. code-block:: none 00010011 = 13H 11111010 = -(05H+carry) -------- 1 00001101 | +--> carry out = 1, resetting the Carry bit indicating no borrow Whenever a borrow has occurred, the SBB instruction increments the subtrahend by one, which is equivalent to borrowing one from the minuend. In order to create a multibyte subtraction routine, it is necessary only to duplicate the multibyte addition routine of this section, changing the ADC instruction to an SBB instruction. The program will then subtract the number beginning at SECND from the number beginning at FIRST, placing the result at FIRST. Decimal Addition ================ Any 4-bit data quantity may be treated as a decimal number as long as it represents one of the decimal digits from 0 through 9, and does not contain any of the bit patterns representing the hexadecimal digits A through F. In order to preserve this decimal interpretation when performing addition, the value 6 must be added to the 4-bit quantity whenever the addition produces a result between 10 and 15. This is because each 4-bit data quantity can hold 6 more combinations of bits than there are decimal digits. Decimal addition is performed on the 8080 by letting each 8-bit byte represent two 4-bit decimal digits. The bytes are summed in the accumulator in standard fashion, and the DAA (decimal adjust accumulator) instruction is then used as in Section 3, to convert the 8-bit binary result to the correct representation of 2 decimal digits. The settings of the carry and auxiliary carry bits also affect the operation of the DAA, permitting the addition of decimal numbers longer than two digits. To perform the decimal addition: .. code-block:: none 2985 + 4936 ---- 7921 the process works as follows: (1) Clear the Carry and add the two lowest-order digits of each number (remember that each 2 decimal digits are represented by one byte). .. code-block:: none 85 = 10000101B 36 = 00110110B carry = 0 --------- 0 10111011B | | | +--> Auxiliary Carry = 0 +--> Carry = 0 The accumulator now contains BBH. (2) Perform a DAA operation. Since the rightmost four bits are ≥ 10D, 6 will be added to the accumulator. .. code-block:: none Accumulator = 10111011B 6 = 0110B --------- 11000001B Since the leftmost 4 bits are now ≥ 10, 6 will be added to these bits, setting the Carry bit. .. code-block:: none Accumulator = 11000001B 6 = 0110 B ---------- 1 00100001B | +--> Carry bit = 1 The accumulator now contains 21H. Store these two digits. (3) Add the next group of two digits: .. code-block:: none 29 = 00101001B 49 = 01001001B carry = 1 --------- 0 01110011B | | | +--> Auxiliary Carry = 1 +--> Carry = 0 The accumulator now contains 73H. (4) Perform a DAA operation. Since the Auxiliary Carry bit is set, 6 will be added to the accumulator. .. code-block:: none Accumulator = 01110011B 6 = 0110B ---------- 0 01111001B | +--> carry bit = 0 Since the leftmost 4 bits are < 10 and the Carry bit is reset, no further action occurs. Thus, the correct decimal result 7921 is generated in two bytes. A routine which adds decimal numbers, then, is exactly analogous to the multibyte addition routine MADD of the last section, and may be produced by inserting the instruction DAA after the ADC M instruction of that example. Each iteration of the program loop will add two decimal digits (one byte) of the numbers. Decimal Subtraction =================== Each 4-bit data quantity may be treated as a decimal number as long as it represents one of the decimal digits 0 through 9. The DAA (decimal adjust accumulator) instruction may be used to permit subtraction of one byte (representing a 2-digit decimal number) from another, generating a 2-digit decimal result. In fact, the DAA permits subtraction of multidigit decimal numbers. The process consists of generating the hundred's complement of the subtrahend digit (the difference between the subtrahend digit and 100 decimal), and adding the result to the minuend digit. For instance, to subtract 34D from 56D, the hundred's complement of 34D (100D-34D=66D) is added to 56D, producing 122D, which when truncated to 8 bits gives 22D, the correct result. If a borrow was generated by the previous subtraction, the 99's complement of the subtrahend digit is produced to compensate for the borrow. In detail, the procedure for subtracting one multi-digit decimal from another is as follows: (1) Set the Carry bit = 1 indicating no borrow. (2) Load the accumulator with 99H, representing the number 99 decimal. (3) Add zero to the accumulator with carry, producing either 99H or 9AH, and resetting the Carry bit. (4) Subtract the subtrahend digits from the accumulator, producing either the 99's or 100's complement. (5) Add the minuend digits to the accumulator. (6) Use the DAA instruction to make sure the result in the accumulator is in decimal format, and to indicate a borrow in the Carry bit if one occurred. Save this result. (7) If there are more digits to subtract, go to step 2. Otherwise, stop. Example: Perform the decimal subtraction: .. code-block:: none 4358D - 1362D ----- 2996D (1) Set carry = 1. (2) Load accumulator with 99H. (3) Add zero with carry to the accumulator, producing 9AH. .. code-block:: none Accumulator = 10011001B 0 = 00000000B Carry = 1 --------- 10011010B = 9AH (4) Subtract the subtrahend digits 62H from the accumulator. .. code-block:: none Accumulator = 10011010B -62H = 10011110B ---------- 1 00111000B (5) Add the minuend digits 58H to the accumulator. .. code-block:: none Accumulator = 00111000B 58H = 01011000B ---------- 0 10010000B = 90H | | | +--> Auxiliary Carry = 1 +--> Carry = 0 (6) DAA converts accumulator to 96H (since Auxiliary Carry = 1) and leaves Carry bit = 0 indicating that a borrow occurred. (7) Load accumulator with 99H. (8) Add zero with carry to accumulator, leaving accumulator = 99H. (9) Subtract the subtrahend digits 13H from the accumulator. .. code-block:: none Accumulator = 10011001B -13H = 11101101B ---------- 1 10000110B (10) Add the minuend digits 43H to the accumulator. .. code-block:: none Accumulator = 10000110B 43H = 01000011B ---------- 0 11001001B = C9H | | | +--> Auxiliary Carry = 0 +--> Carry = 0 (11) DAA converts accumulator to 29H and sets the carry bit = 1, indicating no borrow occurred. Therefore, the result of subtracting 1362D from 4358D is 2996D. .. note:: In the scan (``scans/page-62.png``), "-62H" and "-13H" are printed as 62H and 13H with a bar over them (complement notation). The following subroutine will subtract one 16-digit decimal number from another using the following assumptions: The minuend is stored least significant (2) digits first beginning at location MINU. The subtrahend is stored least significant (2) digits first beginning at location SBTRA. The result will be stored least significant (2) digits first, replacing the minuend. .. code-block:: none Label Code Operand Comment DSUB: LXI D,MINU ; D and E address minuend LXI H,SBTRA ; H and L address subtra- ; hend MVI C,8 ; Each loop subtracts 2 ; digits (one byte), ; therefore program will ; subtract 16 digits. STC ; Set Carry indicating ; no borrow LOOP: MVI A,99H ; Load accumulator ; with 99H. ACI 0 ; Add zero with Carry SUB M ; Produce complement ; of subtrahend XCHG ; Switch D and E with ; H and L ADD M ; Add minuend DAA ; Decimal adjust ; accumulator MOV M,A ; Store result XCHG ; Reswitch D and E ; with H and L DCR C ; Done if C = 0 JZ DONE INX D ; Address next byte ; of minuend INX H ; Address next byte ; of subtrahend JMP LOOP ; Get next 2 decimal digits DONE: NOP Altering Macro Expansions ========================= This section describes how a macro may be written such that identical references to the macro produce different expansions. As a useful example of this, consider a macro SBMAC which needs to call a subroutine SUBR to perform its function. One way to provide the macro with the necessary subroutine would be to include a separate copy of the subroutine in any program which contains the macro. A better method is to let the macro itself generate the subroutine during the first macro expansion, but skip the generation of the subroutine on any subsequent expansion. This may be accomplished as follows: Consider the following program section which consists of one global set statement and the definition of SBMAC (dashes indicate those assembly language statements necessary to the program, but irrelevant to this discussion): .. code-block:: none Label Code Operand FIRST SET 0FFH SBMAC MACRO -- -- CALL SUBR -- -- IF FIRST FIRST SET 0 JMP OUT SUBR:: -- -- RET OUT: NOP ENDIF ENDM The symbol FIRST is set to FFH, then the macro SBMAC is defined. The first time SBMAC is referenced, the expansion produced will be the following: .. code-block:: none Label Code Operand SBMAC -- -- CALL SUBR -- -- IF FIRST FIRST SET 0 JMP OUT SUBR: -- -- RET OUT: NOP Since FIRST is non-zero when encountered during this expansion, the statements between the IF and ENDIF are assembled into the program. The first statement thus assembled sets the value of FIRST to 0, while the remaining statements are the necessary subroutine SUBR and a jump around the subroutine. When this portion of the program is executed, the subroutine SUBR will be called, but program execution will not flow into the subroutine's definition. On any subsequent reference to SBMAC in the program, however, the following expansion will be produced: .. code-block:: none Label Code Operand SBMAC -- -- CALL SUBR -- -- IF FIRST Since FIRST is now equal to zero, the IF statement ends the macro expansion and does not cause the subroutine to be generated again. The label SUBR is known during this expansion because it was defined globally (followed by two colons in the definition).