Apple Assembly Line Volume 1 -- Issue 1 October, 1980 Welcome to the premier issue of the Apple Assembly Line! This new monthly newsletter is dedicated to the many Apple owners using assembly language, or who would like to learn how. Articles will include commented disassemblies of Apple ROM routines, DOS, and other commercial software; how to augment and modify existing products; beginner's lessons in assembly language; handy subroutines every programmer needs in his tool kit; and many more. In this issue you will find a tutorial on efficient ways to increment and decrement multiple-byte values, a very powerful subroutine for formatting messages on the screen, and patch code for the S-C ASSEMBLER II Version 4.0 to "adapt" it to the Paymar Lower-Case Adapter. There is also an article describing a recently reported error found in ALL 6502 chips, and a brief announcement of some new products from S-C SOFTWARE. Since there will be a lot of source code printed in this and forthcoming issues of the Apple Assembly Line, I plan to offer quarterly diskettes containing all published source code (in the format of the S-C ASSEMBLER II Version 4.0) at a nominal price. How does $15 per quarter sound? Of course, you can always type it in.... The articles should be considered copy-righted, but feel free to use the code in any way you can. It is printed here for your enlightenment, entertainment, and for your USE. I hope you find it all helpful. I do not know all there is to know about the 6502, or the Apple, or about anything! Nor do I have an infinite amount of time. Therefore, I will be happy to accept articles and programs from you. I may print them exactly as you write them, or I may modify them first. In any case, you will get credit, and the satisfaction of knowing you are helping many others in their conquest of the computer. If you know others who should be receiving this newsletter, spread the word! If you are not subscribing yet, then send your $12 today! If you have any comments about the content, format, or whatever, write now! Or, you can call me during reasonable at (214) 324-2050. Sincerely, <> Bob Sander-Cederlof How to Add and Subtract One --------------------------- I suppose there are as many ways to do it as there are programmers. Some are short and fast, some long and slow, some neat, some sloppy. Adding one to a number is called "incrementing", and subtracting one is called "decrementing". The 6502 has two instructions for these two functions: INC and DEC. (For the moment I will overlook the four instructions for doing the same to the X and Y registers: INX, INY, DEX, and DEY.) It is easy to see how to use them on single-byte values; with a little more trouble we can also use them for values of two or more bytes. Single-Byte Values: Here are five different ways to increment a single byte: Methods 1 and 2: Add 1 CLC SEC LDA VALUE LDA VALUE ADC #1 ADC #0 STA VALUE STA VALUE Method 3 and 4: Subtract (-1) SEC CLC LDA VALUE LDA VALUE SBC #$FF SBC #$FE STA VALUE STA VALUE Method 5: Use the INC instruction INC VALUE Here are five similar ways to decrement a value: Method 1 and 2: Subtract 1 SEC CLC LDA VALUE LDA VALUE SBC #1 SBC #0 STA VALUE STA VALUE Method 3 and 4: Add (-1) CLC SEC LDA VALUE LDA VALUE ADC #$FF ADC #$FE STA VALUE STA VALUE Method 5: Use the DEC instruction DEC VALUE There are times when any of the above may be justified, depending on the state of the A-register and the Carry Status bit. Multi-Byte Values: Incrementing a two-byte value is a very common practice in 6502 programs. Here are two methods: Method 1: Add 1 CLC LDA VALL LOW BYTE ADC #1 STA VALL LDA VALH HIGH BYTE ADC #0 STA VALH Method 2: Use the INC instruction INC VALL INCREMENT LOW BYTE BNE .1 IF NOT ZERO, THEN NO CARRY INC VALH INCREMENT HIGH BYTE .1 ..... Of course, there are many variations on these methods. It is easy to see how to extend these two methods to more than two bytes. Here is a three-byte version of Method 2: INC VALL INCREMENT LOW BYTE BNE .1 UNLESS ZERO, NO CARRY INC VALM INCREMENT MIDDLE BYTE BNE .1 UNLESS ZERO, NO FURTHER CARRY INC VALH INCREMENT HIGH BYTE .1 .... Believe it or not, there is one disadvantage to using Method 2, in some circumstances. Sometimes code is required to have a constant running time; then, Method 1 is the one to use. But most of the time, Method 2 is the best. How about subtracting one? Here are two ways to do it to a two-byte value: Method 1: Subtract 1 SEC LDA VALL SBC #1 STA VALL LDA VALH SBC #0 STA VALH Method 2: Use the DEC instruction LDA VALL SEE IF NEED TO BORROW BNE .1 NO DEC VALH YES .1 DEC VALL Which one do you like better? It is still a matter of taste, unless the amount of memory used or time consumed is very important. There are also different side effects, such as the final state of the carry status. INC and DEC do not change the carry status, while of course ADC and SBC do. You may wish to preserve carry through the process, making the INC/DEC code preferable. Or, you may wish to know the resulting carry status after incrementing or decrementing for some reasong; then you should use the ADC/SBC code. Back to subtracting one...how about doing it to a three-byte value? We just add three more lines: LDA VALL SEE IF NEED TO BORROW BNE .2 NO LDA VALM SEE IF NEED TO BORROW AGAIN BNE .1 NO DEC VALH BORROW FROM HIGH BYTE .1 DEC VALM BORROW FROM MIDDLE BYTE .2 DEC VALL Easier than you though, right? You would not believe the many strange ways I have seen this operation coded in commercial software (even some released by Apple themselves!). Yet it seems to me that this method is the same way we would do it with pencil and paper in decimal arithmetic. Think how you would do this: 123040 -1 ------ xxxxxx If you think of each digit as though it were a byte...isn't the algorithm the same? Now it is time for all of us to go back over the programs we wrote during the past three years for the Apple, and replace a lot of old code! New Products from S-C SOFTWARE ------------------------------ As many of you know, because you have already bought it, version 4.0 of the S-C Assembler II is now on the market. With this new version, the price has gone up from $35 to $55. An upgrade kit for owners of previous versions is only $22.50 Now another new version is available, for those of you without disks! Tape Version 4.0 requires only 16K RAM and a cassette drive. The price is $45 for the complete package, or $22.50 for an upgrade kit from the previous tape version. All of the new features of Disk Version 3.2 and 4.0 are included, except those which require a disk drive. For the time being, the manual consists of a copy of the disk version 4.0 manuals, with a single sheet describing the differences in the tape version. Purchasers of tape version 4.0 will be able to upgrade to the disk version when they get a disk drive, for only $12.50. And still another version of the assembler! This one is a cross assembler for the Motorola 6800, 6801, and 6802 microprocessors. It has all the features of the S-C Assembler II Disk Version 4.0, but the source language accepted is that of the 6800 family rather than the 6502. The price for this package is only $300, which is less than a month of time-sharing services for an equivalent capability would cost! An Apple, a ROM blower from Mountain Hardware, and the S-C Assembler II-6800 are all you need for a full-blown development system. General Message Printing Subroutine ----------------------------------- Formatting a series of nice messages or screens-full of messages is hard enough to do in Applesoft...but in assembly language it can really be a difficult job. And it seems to take so much memory to do the equivalent of VTAB, HTAB, HOME, and PRINT. I was recently motivated to do something about this for a large, verbose program. I designed a general subroutine for printing text, which can print all 128 chracters of ASCII, plus do some fancy footwork on the way. Embedded control codes in the text to be printed perform such handy functions as HTAB, VTAB, HOME, NORMAL, INVERSE, Clear to End of LIne, Clear to End of Page, Two-Second Delay, and Repeat. All characters to be printed directly are entered with the high-order bit set to one; bytes with the high order bit zero are control codes. Comments in lines 1250-1350 of the listing show what the codes are. To simplify the calling sequence, a table of message addresses is built along with the messages themselves. To print a specific message, merely load the message index number into the A-register (LDA #0 for the first message, LDA #1 for the second, etc.), and JSR MESSAGE.PRINTER. Some sample messages are given in the listing, starting at line 2240. There are a lot of unused control codes, which you can use to augment the subroutine. I am planning to add a code to switch to a HI-RES TEXT driver, for writing text on either of the two Hi-Res screens. You can probably think of a lot of useful ones yourself. The point is that this type of subroutine can simplify programming of an interactive program, and save memory too. 1000 *--------------------------------- 1010 MON.CH .EQ $24 1020 MON.CV .EQ $25 1030 MON.VTAB .EQ $FC22 1040 MON.CLREOP .EQ $FC42 1050 MON.HOME .EQ $FC58 1060 MON.CLREOL .EQ $FC9C 1070 MON.WAIT .EQ $FCA8 1080 MON.COUT .EQ $FDED 1090 MON.NORMAL .EQ $FE84 1100 MON.INVERSE .EQ $FE80 1110 *--------------------------------- 1120 MSG.PNTR .EQ $18,19 1130 MSG.SCANNER .EQ $1A 1140 *--------------------------------- 1150 * MESSAGE PRINTER 1160 * 1170 * CALL: 1180 * (A) = MESSAGE # (0-N) 1190 * JSR MESSAGE.PRINTER 1200 * 1210 * ACTION: 1220 * 1. FINDS SPECIFIED MESSAGE 1230 * 2. PRINTS ON THE SCREEN 1240 * 3. INTERPRETS CHARACTERS AS FOLLOWS: 1250 * $00 END OF MESSAGE 1260 * $01-28 HTAB 1-40 1270 * $40-57 VTAB 1-24 1280 * $60 CLEAR SCREEN, HOME CURSOR 1290 * $61XXYY REPEAT CHARACTER YY, XX TIMES 1300 * $62 DELAY ABOUT TWO SECONDS 1310 * $63 NORMAL MODE 1320 * $64 INVERSE MODE 1330 * $65 CLEAR TO END OF LINE 1340 * $66 CLEAR TO END OF SCREEN 1350 * $80-FF PRINT AS IS 1360 * 1370 *--------------------------------- 1380 MESSAGE.PRINTER 1390 ASL DOUBLE MSG NUMBER TO GET INDEX 1400 TAY 1410 LDA MESSAGE.ADDRESS.TABLE,Y 1420 STA MSG.PNTR 1430 LDA MESSAGE.ADDRESS.TABLE+1,Y 1440 STA MSG.PNTR+1 1450 LDA #0 1460 STA MSG.SCANNER 1470 .1 JSR GET.NEXT.CHAR.FROM.MESSAGE 1480 BNE .3 1490 RTS $00: EOM 1500 .3 BPL .5 SPECIAL ACTION 1510 JSR MON.COUT PRINT THE CHARACTER 1520 .4 JMP .1 1530 *--------------------------------- 1540 .5 CMP #$40 CHECK FOR VTAB 1550 BCS .6 YES 1560 CMP #$29 IN RANGE FOR HTAB? 1570 BCS .4 NO, IGNORE 1580 STA MON.CH 1590 DEC MON.CH 1600 BCC .4 ...ALWAYS 1610 *--------------------------------- 1620 .6 CMP #$58 IN RANGE FOR VTAB? 1630 BCS .7 NO 1640 AND #$1F MASK VALUE 1650 STA MON.CV YES 1660 JSR MON.VTAB 1670 JMP .4 1680 *--------------------------------- 1690 .7 EOR #$60 CHECK FOR TOKENS 1700 CMP #7 $60 THROUGH $66 1710 BCS .4 NOT TOKEN, SO IGNORE 1720 ASL MAKE DUBLE INDEX 1730 TAX 1740 LDA /.4-1 PUT RETURN ON STACK 1750 PHA TO SIMULATE A JSR ADDR,X 1760 LDA #.4-1 1770 PHA 1780 LDA MSGTKNTBL+1,X 1790 PHA 1800 LDA MSGTKNTBL,X 1810 PHA 1820 RTS 1830 *--------------------------------- 1840 MSGTKNTBL 1850 .DA MON.HOME-1 1860 .DA MSG.REPEAT-1 1870 .DA LONG.DELAY-1 1880 .DA MON.NORMAL-1 1890 .DA MON.INVERSE-1 1900 .DA MON.CLREOL-1 1910 .DA MON.CLREOP-1 1920 *--------------------------------- 1930 MSG.REPEAT 1940 JSR GET.NEXT.CHAR.FROM.MESSAGE 1950 TAX NUMBER OF MULTIPLES 1960 JSR GET.NEXT.CHAR.FROM.MESSAGE 1970 .1 JSR MON.COUT 1980 DEX 1990 BNE .1 2000 RTS 2010 *--------------------------------- 2020 LONG.DELAY 2030 LDY #12 2040 .1 JSR MON.WAIT DELAY 167309 CYCLES 2050 DEY 2060 BNE .1 2070 RTS 2080 *--------------------------------- 2090 GET.NEXT.CHAR.FROM.MESSAGE 2100 LDY MSG.SCANNER 2110 LDA (MSG.PNTR),Y 2120 INC MSG.SCANNER 2130 BNE .1 2140 INC MSG.PNTR+1 2150 .1 CMP #0 2160 RTS 2170 *--------------------------------- 2180 MESSAGE.ADDRESS.TABLE 2190 .DA MSG0 2200 .DA MSG1 2210 .DA MSG2 2220 .DA MSG3 2230 *--------------------------------- 2240 MSG0 .HS 60 HOME SCREEN 2250 * CELL 1 -- VOCABULARY CHECK 2260 .HS 64 INVERSE MODE 2270 .HS 6129AD 4A DASHES 2280 .HS 28ADAD 2 DASHES 2290 .HS 28ADAD 2300 .HS 28ADAD 2 DASHES 2310 .HS 28ADAD 2 DASHES 2320 .HS 28ADAD 2 DASHES 2330 .HS 28ADAD 2 DASHES 2340 .HS 286129AD 41 DASHES 2350 .HS 63 NORMAL MODE 2360 .HS 4205 VTAB 3, HTAB 5 2370 .AS -/DEMONSTRATION OF MESSAGE PRINTER/ 2380 .HS 440F VTAB 5, HTAB 15 2390 .AS -/S-C SOFTWARE/ 2400 .HS 450E VTAB 6, HTAB 14 2410 .AS -/P. O. BOX 5537/ 2420 .HS 460B VTAB 7, HTAB 11 2430 .AS -/RICHARDSON, TX 75080/ 2440 .HS 4A VTAB 11 2450 .HS 00 2460 *--------------------------------- 2470 MSG1 .HS 490166 VTAB 10, HTAB 1, CLR EOP 2480 .AS -/SELECT ONE: / 2490 .HS 00 2500 *--------------------------------- 2510 MSG2 .HS 570165 VTAB 24, HTAB 1, CLR EOL 2520 .HS 64 INVERSE MODE 2530 .AS -/ FOR MENU, FOR MORE / 2540 .HS 6300 NORMAL MODE, EOM 2550 *--------------------------------- 2560 MSG3 .HS 87878D 2570 .AS -/***SYNTAX ERROR/ 2580 .HS 8D00 Using the Paymar Lower-Case Adapter Bob Matzinger with S-C Assembler II Version 4.0 817-275-2910 ------------------------------------------------------------ Since purchasing the Paymar adapter, I have spent a lot of time adapting software to effectively use it! The program geven here will adapt the version 4.0 of Bob Sander-Cederlof's assembler to allow lower-case comments. The two patches at lines 1340 and 1390 have to be entered, and the body of the patch loaded at $300. Once installed, typing a control-A will toggle the shift-lock; control-S will perform a single-character upper-case shift; control-K, -L, and -O give access to the characters normally missing from the Appple keyboard. Only comments can be entered in lower-case. Further modification to the assembler would be required to allow commands, labels, and opcodes to be entered in lowr- or mixed-case. 1000 *--------------------------------- 1010 * Lower case conversion for 1020 * S-C ASSEMBLER II Version 4.0 1030 * Copyright 1980 by S-C SOFTWARE 1040 * Complete with 126 ASCII characters 1050 *--------------------------------- 1060 * The CTRL-A and CTRL-S keys are used similar to 1070 * shift and lock keys on a standard typewriter. 1080 * 1090 * CTRL-A is the shift-lock key. 1100 * Each time CTRL-A is pressed the case 1110 * will toggle to the opposite mode. 1120 * 1130 * CTRL-S makes the following character 1140 * enter in upper-case. 1150 *--------------------------------- 1160 * REMEMBER! 1170 * All commands and mnemonic entries 1180 * must be in UPPER case! 1190 * Use lower case only for comments! 1200 *--------------------------------- 1210 CTRLA .EQ $81 SHIFT LOCK 1220 CTRLK .EQ $8B [ or { 1230 CTRLL .EQ $8C \ or | 1240 CTRLO .EQ $8F _ or rubout 1250 CTRLS .EQ $93 SHIFT 1260 *--------------------------------- 1270 * Remember: 1280 * shift M yields ] or } 1290 * shift N yields ^ or ~ 1300 * shift P yields @ or ` 1310 RDKEY .EQ $FD0C 1320 *--------------------------------- 1330 .OR $1380 1340 .TF LC.PATCH1 1350 JSR LC 1360 *--------------------------------- 1370 .OR $139A 1380 .TF LC.PATCH2 1390 AND #$FF 1400 *--------------------------------- 1410 .OR $300 1420 * CAUTION: Do not assemble your programs into 1430 * $0300 up. You will destroy this routine!!! 1440 LC JSR RDKEY 1450 CMP #CTRLA 1460 BEQ LOCK 1470 CMP #CTRLS 1480 BNE CHECK 1490 SHIFT LDA #0 1500 STA LCKFLG 1510 SHIFT1 LDA #0 1520 STA CASE 1530 BEQ LC ...ALWAYS 1540 LOCK LDA LCKFLG 1550 EOR #1 1560 STA LCKFLG 1570 BNE SHIFT1 1580 LDA #$20 1590 STA CASE 1600 BNE LC ...ALWAYS 1610 CHECK CMP #CTRLK 1620 BEQ SPEC 1630 CMP #CTRLL 1640 BEQ SPEC 1650 CMP #CTRLO 1660 BNE CONV 1670 SPEC ORA #$50 1680 CONV CMP #$C0 1690 BCC RETURN 1700 ORA CASE 1710 RETURN PHA 1720 LDA LCKFLG 1730 BNE OUT 1740 LDA #$20 1750 STA CASE 1760 OUT PLA 1770 RTS 1780 LCKFLG .DA #0 1790 CASE .DA #$20 1800 *--------------------------------- 1810 * Written by Bob Matzinger 1820 * September 6, 1980 1830 *--------------------------------- Hardware Error in ALL 6502 Chips! --------------------------------- INTERFACE, the newsletter of Rockwell International (P. O. Box 3669, RC 55, Anaheim, CA 92803), Issue No. 2, is the source for the following information. It should be noted by all Apple owners working in assembly language, because it could cause an almost unfindable bug! There is an error in the JUMP INDIRECT instruction of ALL 6500 family CPU chips, no matter where they were made. This means the error is present in ALL APPLES. This fatal error occurs only when the low byte of the indirect pointer location happens to be $FF, as in JMP ($08FF). Normally, the processor should fetch the low-order address byte from location $08FF, increment the program counter to $0900, and then fecth the high-order address byte from $0900. Instead, the high-order byte of the program counter never gets incremented! The high-order address byte gets loaded from $0800 instead of $0900! For this reason, your program should NEVER include an instruction of the type JMP ($xxFF). Try this example to satisfy yourself that you understand the problem: insert the following data from the monitor. *800:09 *810:6C FF 08 (this is JMP ($08FF) *8FF:50 0A (pointer *A50:00 (BRK instruction we SHOULD reach) *950:00 (BRK instruction we DO reach!) Execute the instruction at $0810 by typing 810G. If the JMP indirect worked correctly, it would branch to location $0A50 and execute the BRK instruction there. However, since the JMP indirect instruction has this serious flaw, it will actually branch to the BRK instruction at $0950! Since it is very difficult to predict the final address of all pointers in a large assembly language program, unless they are all grouped in a block at the beginning of the program, I suggest that you take special measures to protect yourself against this hardware problem. (One measure, of course, was suggested in that sentence.) My favorite method is to avoid using the JMP indirect instruction. It takes too long to set it up in most cases anyway. I prefer to push the branch address (less one) onto the stack, and RTS to effect the branch. This allows me to create the effect of an indexed JMP. For example, suppose a command character is being decoded. I process it into a value in the A-register between 0 and N-1 (for N commands), and do the following: ASL Double to create index TAX for address table LDA JUMP.TABLE+1,X High order byte PHA of branch address LDA JUMP.TABLE,X Low order byte PHA of branch address RTS The jump table looks like this: JUMP.TABLE .DA COMMANDA-1 The "-1" is .DA COMMANDB-1 on each line .DA COMMANDC-1 because the RTS .DA COMMANDD-1 adds one before et cetera branching. This trick was described by Steve Wozniak in an article in BYTE magazine back in 1977 or 1978. It is also used by him in the Apple monitor code, and in SWEET-16. In both of these cases, he has arranged all the command processors to be in the same page, so that the high order byte of the address can be loaded into the A-register with a load-A-immediate, and the jump table can be only one-byte-per-command. See your Apple ROMs at locations $FFBE-FFCB (jump table at $FFE3-FFF9) and in SWEET-16 at $F69E, F6A0, F684-F6B8 (jump table at $F6E3-F702). You can extend this idea of an indexed JMP instruction into a simulated indexed JSR instruction. All you have to do is first push onto the stack the return address (less one), and then the branch address (less one). I use this trick in the Message.Printer program described elsewhere in this issue.