Here is a tip for users of 8080 macro assemblers which I've never seen in print, but which I've found to be useful. It pertains to the use of the standard condition codes (Z, NZ, C, NC, M, P, PO, PE) as parameters of a macro instruction. Let's say you've written a useful macro to do something or other. As an example most CP/M programmers are familiar with, let's consider the CPM macro. It usually goes something like this: CPM MACRO FUNC,VALUE IF NOT NUL VALUE LXI D,VALUE ;;Load D&E with value. ENDIF IF NOT NUL FUNC MVI C,FUNC ;;Load C reg with Function. ENDIF CALL BDOS ENDM The examples are written for the Digital Research MAC assembler, but would probably be similar for others. To use the macro, one might say: CPM CON,CR ;Send carriage return to console Assuming that CON had been EQUated to 2, the CP/M function for console output, and CR had been EQUated to 0DH, the macro would expand to: LXI D,0DH MVI C,02H CALL 0005H which would send a carriage return to the console. Similarly, if KEY had been EQUated to 1, the console input function, the macro call CPM KEY would expand to: MVI C,01H CALL 0005H which would get a keyboard character into register A. This macro is in fact used quite frequently by many programmers who work with CP/M. Note that the IF's prevent unneeded code from being assembled where the parameter is not supplied. Now consider a case where we want to read a console character only when the carry flag is set. Perhaps the carry flag indicates an error condition, and we want the program to pause. The usual way of doing this is: ... ;Code to set carry on error JNC OK ;Jump around input routine CPM KEY ;Get a character from console. OK: ... ;Continue with operation. To save programmer time, we can modify the CPM macro to allow a condition code to be specified as a third parameter. Then we could write lines like: CPM KEY,,C ;Get char if Carry set ...or... PUSH PSW ;Save output char. MOV E,A ;Move to E for output CPM CON ;Output it POP PSW ;Get back character CPI CR ;Was it a carriage return? CPM CON,LF,Z ;If so, follow with Line Feed. ... without having to code the jump instructions which are necessary to avoid executing the macro code. We do this by coding the jump instruction into the macro itself. This is made easier by the fact that most macro assemblers will allow us to use the value of an opcode as data. For compatability with Intel standards, it should be coded within parentheses (required by some assemblers) and if we're using MAC, should have spaces around the opcode to avoid a little-known glitch in MAC. Let's work with an even simpler macro to see how this might operate. We'll invent the JUMPIF macro, which does nothing but cause a conditional jump. JUMPIF MACRO COND,ADDR DB ( J&COND ) DW ADDR ENDM In this simple example, coding the line: JUMPIF NC,EOJ would expand to: DB ( JNC ) DW EOJ "Big deal!", you say. Why not just code: JNC EOJ Well, we want to work the condition code into a larger macro, and generate a jump around the inline code if the condition is false. Ah, but there's the rub. In order for the macro to work properly, the jump instruction has to jump if the condition given is NOT true, and fall through to the inline code if it IS true. To illustrate, let's invent the opposite of the JUMPIF macro, the JMPUNLES (Jump Unless) macro. We could do something like this: JMPUNLES MACRO COND,ADDR J&COND LABL1 ;Jump around the next instr. JMP ADDR ;Do the real jump. LABL1: ENDM But this requires the use of two jump instructions where we know only one is required. In practice, of course, LABL1 would be declared LOCAL or we could only use this macro once per program without getting duplicate label errors. There is a way out. In all 8080 instructions involving condition codes, there is a one bit difference between a condition code and it's opposite, and it is always the same bit. The bit used is the bit with a value of 8. Compare the JZ instruction with the JNZ instruction: 1 1 0 0 1 0 1 0 Jump If Zero 1 1 0 0 0 0 1 0 Jump If Not Zero --- --- --- --- --- --- --- --- 128 64 32 16 *8* 4 2 1 Bit Values Changing this one bit changes a JC to a JNC, a JP to a JM, and a JPO to a JPE. The bit has the same function in the conditional call and return instructions as well. Combining all these facts, we can write our CPM macro with the condition code parameter. It comes out looking like this: CPM MACRO FUNC,VALUE,COND LOCAL LABLX ;Generate one-time label IF NOT NUL COND ;Generate jump only when needed DB ( J&COND ) XOR 8 ;Change condition code to its ; opposite. DW LABLX ;Address field of jump instr. ENDIF IF NOT NUL VALUE LXI D,VALUE ENDIF IF NOT NUL FUNC MVI C,FUNC ENDIF CALL BDOS LABLX: ;Jump here if COND wasn't true. Continue... ENDM Coding CPM CON,LF,NZ would cause the opcode to be expanded as: DB ( JNZ ) XOR 8 which is the same as: DB ( JZ ) Note that the COND parameter could have been any of the valid 8080 condition codes: Z, NZ, C, NC, P, M, PO, PE. Also, we could use conditional calls ( C&COND ) or conditional returns ( R&COND) and changed the sense of the condition by XORing the opcode with 8 to change the necessary bit. This technique can be quite useful in macros of all types to allow the use of condition codes on the same line as the macro call, saving programmer time, and lessening the chances of error. For your information, here is a list of 8080 opcodes which are changed into their opposites by XORing with 8. Although the ones with condition codes will probably be the most useful, some of the others may come in handy some day. The instruction on the left has the "8's" bit as a 0, on the right as a 1. STAX LDAX RLC RRC RAL RAR INX DCX SHLD LHLD JNC JC JNZ JZ JP JM JPO JPE CNC CC CNZ CZ CP CM CPO CPE RNC RC RNZ RZ RP RM RPO RPE DI EI OUT IN Do yourself a favor and use this trick only where it will genuinely improve efficiency, not just to make simple code hard to read. Happy Hacking! Gary P. Novosielski (201) 935-4087 Eve's. iciency, not just to make simple code hard to read. Happy Hacking!