Z80ASM-DD 2.4 ---> FIXES TO Z80ASM-2.0 DD 9/12/87 <<< Z80ASMDD.DOC >>> by Dianna Dearborn September 12,1987 This document outlines a number of fixes and enhancements to Z80ASM 2.0 and identifies remaining the bugs that I'm aware of. FIXES: -in JR and DJNZ relative addressing instruction operands to allow the use of symbolic label references -in JR and DJNZ instructions to allow the use of the $ symbol to represent current reference counter value. -in the LABEL: character validation test to allow the use of the $ symbol in labels -in valid label test and error reporting routine -in the "IBM" pseudo-op ENHANCEMENTS: -in JR and DJNZ instructions, a displacement range test was added to trap operand expression which evaluated outside the -126 to +129 byte limits. -a routine was added to eliminate the initial page eject on the listing output REMAINING BUGS: -in reserved word test for LABELS: -in the reference value of the last LABEL: used in a source file. OTHER: -a discussion is included on some of the syntax rules of Z80ASM discovered along the way. BACKGROUND: Some weeks ago, I started to do some serious Z80 assembly language programming. I downloaded a copy of Z80ASM from a RCP/M and started to write code. My programs crashed immediately. First thing that happened was the assembler got caught in an 5 Z80ASM-DD 2.4 ---> FIXES TO Z80ASM-2.0 DD 9/12/87 infinite loop and hung up. I narrowed that down to the use of a dollar $ign in label names. Second, there was a bug when using the dollar $ign symbol convention to indicate the current value of the reference counter in operand expressions in JR and DJNZ relative addressing instructions, ie., "JR $+12H"; it worked as expected in JP and CALL instructions. I found that using "JR +12H" would work. Finally, using LABLES: in the operands of JR as DJNZ instructions crashed, (ie., JR LOOP). Again, they worked with JP and CALL operands. I found no way around this bug. I was left with the choice of either counting the number of bytes of intervening instructions to calculate the relative offsets (too much work, prone to error and difficult to modify) or using only JP instructions. Neither was acceptable so I started to debug the assembler. THE BUGS AND THE FIXES: $ in LABLE$: was indeed an infinite loop probably caused by a typo in the original source code. The fix was simply to patch in the proper address in the JP instruction. Relative displacement errors were caused by a complete lapse of thought in the mind of the programmer. While evaluating the operand expressions, JP and CALL instructions create a two byte operand that is the absolute destination address of the jump. Z80ASM handles them by getting the reference counter of the current instruction, in the case of "JP $+12H", and adding to it the value of the expression, and in the case of "JP LABEL:", by getting the address of the label from the symbol table and placing it in the operand. Both methods are combined in such instructions as "JP LABEL:+12H". JR and DJNZ relative addressing instructions were handled in the same manor by the same routine as the absolute addressing instructions and the seemingly random displacement values in their operands turned out to be the low byte of the absolute address of the instruction's destination. Relative addressing instructions require that the signed DIFFERENCE between the current address and the destination address be placed in the operand which will then be added to the PROGRAM COUNTER of the Z80 prior to the jump. To accomplish this, I had to add code to Z80ASM. After fixing these three bugs, I found that there was, of course, no trap for displacement range errors. This creates the possibility of writing an operand expression that evaluates out of range but looks correct upon cursory examination and then sends your program to never-never land. These errors can be quite difficult to detect. I added a range test that is performed after the evaluation of the operand expression and used the $ symbol, appropriately enough, to flag assembly listing lines with range errors then forced the operand to 00. 6 Z80ASM-DD 2.4 ---> FIXES TO Z80ASM-2.0 DD 9/12/87 Along the way I discovered that the reserved word and valid label length tests did not seem to work. The labels were only tested on the first pass to validate them before putting them into the symbol table but the results were not saved for the second pass. I changed the routine so that now at least the label length is tested on the second pass and the results are passed on to the listing, however, using a reserved word or defining a label twice will still cause your program to crash! without warning you of the error. What actually happens is that on the first pass when a reserved word or redefined label is detected, the label is not added to the symbol table and the reference counter is not updated. On the second pass, the error goes undetected, the op code and operand are evaluated, the reference counter is updated and all symbolic addresses from the line in error are off by the number of bytes in the op code of the error line. I thought about disabling the test altogether, however, this type of error is easier to detect than one line with a reserved word where it doesn't belong because every symbolic reference pass the bad line points to a wrong address. The fix is messy and I need to get on with my project so I'll save this one for another day -- at least you were warned!! The other remaining label error has to do with the last label used in the assembly. For some reason, the reference address (or EQUate value) of the last entry in the symbol table gets trashed and, when referenced, will give erroneous results. the way around that is to make sure that the last label used does not get referenced. I do this by labeling the 'END' statement. My last line in the assembly is usually: THEEND END 100H Again, I'll fix this bug when I get more time to work on it. While plowing through the Z80ASM code I found a new to me pseudo-op called "IBM". It turned out to be an ascii $tring formating instruction for messages. The routine had three major bugs and I'm certain that it was never tested after it was written. I sorted it out to what I think the programmer had in mind. Anyhow, it now works according to the syntax rules discussed below. And, lastly, the initial page eject for the listing was a real irritation to me. I tested for page 1 and skipped the form feed in the header. The page number is stored as one byte of two BCD digits (maximum of 99) and "rolls over" at page 100 to 00. If your program is more than 100 pages long, then page 101 will not eject but page 102 will catch up. This is untested and probably will remain so!! Principally, I tried to keep Z80ASM in tact as much as possible. In some places, I corrected the original code without 7 Z80ASM-DD 2.4 ---> FIXES TO Z80ASM-2.0 DD 9/12/87 altering the rest of the routine. In the label routine, I altered the order of routine and changed only the JP addresses. The rest of the changes required the addition of new code. To make room for the new code, I moved the main look-up table at the end of the program to higher memory, staying within the last page, and added the new routines to the end of the program. TESTING: I've tried to test Z80ASMDD thoroughly with benchmark programs and others that I've written, however, I am sure that the original programmer thought he (she?) did also -- at least well enough to write a bigger and better assembler. So help me. Run the assembler on some of your successful programs and compare them. If there is any bugs, please let me know. I'll try to fix all of the real ones. EPILOG: This was fun for me (she's crazy!!). I didn't know the insides of assemblers before I started and now I have an appreciation for both assemblers in general and, particularly, the work of 'LCS' -- I learned a lot! I suppose the ultimate irony, after all this work, would be someone telling me that there is a more effecient, fully featured, bug-free assembler readily available in the public domain!! I have one more feature to add when I have time -- I feel lost without a symbol table so I'll dump that to the listing some day. The way I get around that now is call DDT with a null program and check out the symbol table left in memory starting at 22A2H. About the version number, I worked it up to 2.4 to keep track of my work and, vainly enough, added my initials to easily identify this version with the fixes from any others in RCP/M land. From the skeletons left in the code, it's become obvious that other features were planned for Z80ASM but never implemented. Maybe I'll work on them some day. Perhaps add a routine to allow lower case alpha enclosed in quotes. If you have any ideas for new features or know of other bugs, please share them with me and I'll see what I can do. I'd like to hear from any users of this version -- tell me about your experiences, both the good and the bad. Send E-mail to: DIANNA DEARBORN DATA TECH RCP/M 408-238-9621. SAN JOSE, CA. 8 Z80ASM-DD 2.4 ---> FIXES TO Z80ASM-2.0 DD 9/12/87 NOTES ON SYNTAX FOR Z80ASM-DD 2.4 DISCRIPTION OF Z80ASM: Z80ASM is a two pass assembler. The first pass creates the symbol table and the second pass does the assembly. The assembler accepts as input an .ASM source file written in ZILOG assembly language and outputs both an Intel formatted object file and an assembly listing to the printer in real time, either of which may be disabled. This assembler does not create any intermediate files, or files. The symbol table is created starting at 22A2H and may be expanded up to CCP base without harm. There is no trap for symbol tables that overwrite CCP. The maximum number of symbolic entries is dependent on memory available with the system configuration and the aggregate length of each symbol plus 4 bytes per symbol. All program I/O is done through FDOS calls to 0005H. All read, write and working buffers, except the symbol table, are internal to the program. This assembler was written in 8080 assembly language probably as a bootstrap to writing a more effecient and fully featured Z80 assembler. INVOKING Z80ASM: The format is: A>Z80ASM [.LH ] The assembler expects an input source file named .ASM. The optional .LH extension is the (L)isting output and (H)ex file output defeat option mechanism. The true state of the option (defeated) is invoked by typing 'N' in the appropriate location, any other character enables the function. Without the extension both Listing output and Hex file output functions are enabled. When the listing function is disabled, only assembly line errors will be listed. For example: .N will disable the listing function but create a .HEX file as output. .xN will disable the .HEX file output but a listing is printed. Where x is any permissible CP/M .typ char except a space SOURCE CODE FILE LIMITATIONS: Source code files may be of any length acceptable to CP/M, however, there is a practical limit to the size of the symbol table which is not written to a file but held in memory between 22A2H and CCP base. 1 Z80ASM-DD 2.4 ---> FIXES TO Z80ASM-2.0 DD 9/12/87 SOURCE CODE LINES: Source code lines may not exceed 80 characters including CR & LF and must be terminated by LF (0AH). Most routines test for CR to exit. LOWER/UPPER CASE TRANSLATION: Presently, ALL lower case ascii characters are translated to upper case, including strings enclosed by single quote marks. OPCODES AND OPERANDS: Opcodes may NOT start in column 1. Opcodes and their operands follow the rules established by Zilog in the Z80-Assembly Language Programming Manual. RESERVED WORDS: Certain symbolic names are reserved as key words in the operand fields. They are: Register names: A, B, C, D, E, H, L, AF, BC, DE, HL, AF' Flag Conditions: C, NC, Z, NZ, M, P, PO, PE Using a reserved word as a smybolic reference in the operand field will give erroneous operand values. EXPRESSIONS: Expressions are operand entries and may be unary (single term) or binary. Elements of the expression are connected by the arithmetic operators + (addition), - (subtraction) , and * (multiplication). Expression elements may be numbers which begin with a numeric digit, symbolic references which begin with an alpha charater (LABELS: and EQUates), $ symbol for the current value of the reference counter and a single ascii character enclosed in single quote marks which equates to a hexadecimal number. Expressions are terminated by any other characters, usually a space or comma. Expressions are evaluated from left to right, parentheses are not permitted. Expression evaluation results are limited to 2 bytes (16 bits) in length with a maximum unsigned value of 65,535. NUMBERS: Numbers MUST begin with a numeric digit, leading zeroes will suffice. Numbers may be written in hexadecimal, decimal or binary bases. Hexadecimal numbers must be followed immediately by the 'H' radix indicator, Binary numbers must be followed immediately by the 'B' radix indicator. Decimal numbers are not followed by a radix letter indicator. 2 Z80ASM-DD 2.4 ---> FIXES TO Z80ASM-2.0 DD 9/12/87 LABELS: Labels MUST start in column 1. A label is a string of one or more characters and must begin with an alpha character. Valid characters are A-Z, 0-9, $, period (.) and trailing colon (:). Labels are terminated by any other character, usually a space or comma. Labels may be up to 11 characters in length (excluding colons), all characters are unique. Trailing colons are optional and ignored by the assembler (ie., LABELS: = LABELS ). Labels may not be a reserved word. Examples of valid labels are: LABEL LABEL: LABEL$ LONG.LABEL LONGERLABEL WARNING: at present the reserved word trap does not work. An opcode or reserved word in an expression or starting in column 1 will cause your program to crash! Also, the last label stored in the symbol table will give erroneous values, therefore, DO NOT reference the last label in the body of a program. PSEUDO-OPS: Pseudo-ops are instructions to the assembler and as such do not generate object code. Psuedo-ops are summerized below: ORG e Sets the reference counter to the value of expression e. EQU e Sets the value of a label to e. Can occur only once per label. END e Signifies the end of source code -- all code after END is ignored. Expression e evaluates to the program starting address for .HEX files loaded with LOAD (e = 100H is normal) EJECT Forces a new page in listing. DEFS e Reserves e bytes of memory. Advances reference counter RC = RC + e. DEFB e Defines one byte of memory to equal value e. DEFB '$tring' Defines n bytes of memory as the ascii equivalent of the characters in the $tring enclosed by single quote marks. n equals the number of chars in the string. DEFW e Defines two bytes of memory to be equal to the value of e. e is stored in memory as low byte -- high byte, ie., CRLF EQU 0D0AH... DEFW CRLF... produces 0A, 0D in consecutive bytes of memory. 3 Z80ASM-DD 2.4 ---> FIXES TO Z80ASM-2.0 DD 9/12/87 IBM e Defines n bytes of memory as the hex equiva- lent of the ascii elements in the expression e where n is the number of elements and takes the form: IBM c1,c2,'$tring',c3,...,cn where values of c are the hex representation of ascii characters without base indicator (H) and elements are seperated by a comma. Strings are enclosed by single quotes and each character of a string is considred an element. Single quotes may be included by defining cx = 27 (H). Symbolic equivalents are not allowed (ie., CR EQU 0DH...IBM CR,..). e is terminated by a CR. Usage example, a $ terminated message: IBM 0D,0A,'MESSAGE',0D,0A,'$' where e is an expression as defined above. MACRO, CONDitional, EXTERNAL and GLOBAL pseudo-ops and LOGICAL operators are not implemented in this version of Z80ASM. COMMENT LINES: Comment lines are defined as any string following a semicolon (;) in a line. Comments may start in any column and are ignored by the assembler. ASSEMBLER ERROR MESSAGES: Assembly error messages are one character codes which are placed in the first column of the line containing the error in the output listing. L Label length error -- label > 11 characters long M Label is either a reserved word or previously defined (not implemented in version 2.4) O "Overflow" -- indicates that the op code as spelled was not found in reference table. U Undefined label in the operand field $ Displacement range error -- range = -127 < expression <+130 DD 4