S-C Macro Assembler ProDOS
We will begin shipping the ProDOS version of the S-C Macro Assembler in July, so we are now accepting advance orders. There is more to the ProDOS version than just a change of operating systems. The new upgrade includes a couple of major new features:
.INB (INclude Blocked) directive -- This works just like .IN, except that only one disk block at a time is overlaid into memory. Allows assembly of much larger files, with only a minor speed penalty. .AC (Ascii Compressed) directive -- Generates compressed strings from a string between delimiters, according to rule tables. Very complex, but worth the effort if you have a lot of messages and need to save memory.
The price of the ProDOS version alone will be $100. The up- grade from DOS Version 2.0 to ProDOS will be $30. The upgrade from DOS Version 1.0 or 1.1 to ProDOS will be $50, and will include DOS Version 2.0. The initial purchase price of the DOS 3.3 and ProDOS versions together will be $120. These are introductory prices which may well be raised in a few months.
65802's Are Here!
After many months of manufacturing delays, Western Design Center is shipping 65802 and 65816 microprocessors. We recently received a final production '802, and it's now happily processing away in Bob's oldest Apple II (#219). You can order the chips from WDC for $95.00. Call (602) 962-4545.
Note About Alliance Computers
Back in January Alliance Computers advertised 65802's for $50.00, but couldn't fill the orders because the chips didn't exist yet. Some of their formerly unhappy customers tell me that their orders have now arrived, so Alliance is taking care of their customers. You can reach Alliance Computers at P.O. Box 408, Corona, NY 11368.
For years now, I have been working on a debugger for the Apple. Lately I have been adding a hex string search capability to it.
I needed one so I could look through the Apple IIc (ProDOS) utilites to see how it squirrels away in the alternate page screen holes user specified default settings for the serial ports. These are used at PR#1 or 2 time to simulate the dip switches on the Super Serial Card in a IIe. Without setting them you always get 9600 bps, etc. (Imagewriter settings, that is). I (and I assume other AAL readers) want a little routine for DOS 3.3 hello that will allow the user's defaults to be put away the same as the IIc utility does.
Well, that routine is not ready yet. However, the search utility is rather interesting in its own right.
I was just going to code up a straight hex search, but then I mentioned it to my computer science graduate son, David. He was horrified that I would waste my time on anything so crude. That's what I get for bringing up a programmer! David insisted that I should instead code an implementation of Boyer and Moore's algorithm, which appeared in the October 1977 issue of the Communications of the ACM. [A more recent reference is in the book "Algorithms", by Robert Sedgewick, (Addison-Wesley Publishing Co., 1983, 551 pages) on pages 249-252.]
Well, I read the article and it seemed like a challenge. Besides it looked like a real time saver, and could also be used for character string searches. The code here has been excerpted from my debugger, and then worked over by Bob S-C.
The "conventional" or "brute-force" search technique aligns the search pattern with the left end of the string to be searched through and compares one byte at a time, from left to right, until either the entire pattern is compared successfully or a mismatch occurs. In the latter case the search window is moved one byte to the right, and the comparing process is repeated.
Without any knowledge about the contents of the search pattern, the most the window can be moved is one place to the right. Boyer-Moore owes its speed advantage to the fact that it uses context (i.e. knowledge about the contents of the pattern to be searched for) to increase the distance that the search window can be advanced when a mismatch occurs. Thus efficiency increases as the length of the pattern increases, which does not happen in a conventional search.
The cost of this benefit (there always is a cost) is that a table (called DELTA1 in the CACM article and DELTA.TABLE in my program) is required to store this context information, 256 bytes in this implementation. One byte is needed in the table for every possible value of the characters in the string to be searched.
If a particular byte appears in the search pattern, then the corresponding DELTA table entry contains the distance that the rightmost occurrence of that byte is from the left end of the pattern. All other entries contain the value -1. When a mismatch occurs, the DELTA table entry corresponding to that byte from the text being searched is used to compute how far to advance the search window. If that byte does not appear anywhere in the pattern, then the search window can be advanced by the full length of the pattern.
Since moving the search window, and the associated testing for finished, take most of the time in any searching technique, saving time here can be extremely beneficial, and explains why Boyer and Moore should be complimented.
My program uses the control-Y monitor command, in the form
adr1.adr2^Y <hexstring>
The two addresses specify the start and end of the area to be searched. "^Y" stands for "control-Y". The hex string may be separated from the control-Y by one or more spaces, if you desire. Since the control-Y doesn't show up on the screen, I usually type at least one space before the hex string. The hex string itself is a continuous string of hex digits, with no imbedded spaces. Here is an example that will search from $800 to $BFFF for "BERNARD":
800.BFFF^Y 4245524E415244
The program will list the starting addresses of any and all complete matches that are found.
The maximum length of the hex string is limited by the monitor input buffer. Since the longest command you can type is less than 256, and you have to use around ten characters for the addresses and control-Y, that puts an upper limit of less than 246 hex digits in your command. Each byte of the search pattern (or "key") is made up of two hex digits, so the maximum hex string will be less than 123 bytes long.
I assigned DELTA.TABLE to the area $02D0.03CF. Since I scan and collect the search pattern right in the monitor keyboard buffer at $0200, after converting to hex bytes it will run no higher than $027F.
Actually, I only implemented a simplified version of Boyer and Moore's procedure. The CACM article also discusses a second table, DELTA2, which is filled with additional context information regarding "terminating substrings" of the search pattern. In cases where a partial mismatch occurs, it may be possible to advance the search window farther than the DELTA1 table would indicate. However, since such situations occur in less than 20% of the cases, David allowed that the potential additional speed did not justify the time and effort and the additional table and code space that would have been required, and he gave me a passing grade on my effort without it. The incorporation of this additional capability, and changes to make the program an ASCII search, are left "as a exercise for the reader."
My program must go through several steps. First it has to find and pack up the search key. Next it must build the DELTA table. And finally the search can be performed.
Lines 1290-1360 will be executed when you BRUN the program. They install the control-Y vector and jump into the monitor, just as though you entered with CALL-151.
When you enter the search command, the Apple monitor parses the command line up to and including the control-Y, and then branches to my code at line 1380. The two addresses will have been converted and stuffed into A1 ($3C,3D) and A2 ($3E,3F). A variable named YSAV (at $34) contains the index to the next character following the control-Y.
Lines 1400-1440 skip over any blanks you may have typed between the control-Y and the first hex digit. Actually, the Y-register gets incremented once too often, so lines 1460-1470 decrement Y and save it; now YSAV points to the first hex digit in the search key.
The next problem I had to solve was to differentiate odd from even length strings and arrange them properly, adding a leading zero when an odd number of hex digits is input. Lines 1490-1530 search for the end of the hex string; if there are no digits at all, we are finished and line 1530 returns for the next monitor command.
This is a nice place to insert a brief description of the NXTCHAR subroutine, found in lines 2460-2590. NXTCHAR picks up the next character from the input buffer, and tests to see if it is a hex digit. If so, it returns either $00-09 or $FA-FF in the A-register, and carry will be clear. If not a hex digit, it returns with carry set. If we got a digit, the Y-register indexing the input buffer will have been advanced.
Lines 1550-1590 compute the key length. Since two digits make a byte, the number of digits in the hex string divided by two gives the number of bytes. But I actually want to use the byte-count-minus-one. Also I need to adjust for odd or even length strings. Lines 1600-1650 take care of these details. If the count was odd, I jump into the middle of the packing loop so that a leading zero gets inserted.
Lines 1670-1800 comprise the packing loop. NXTCHAR will return with carry set when we try to get a digit beyond the end of the key, so line 1680 is the only test in the loop. Lines 1670-1730 retrieve a left-hand digit and store it in the buffer. Lines 1740-1800 do the same for right-hand digits. Key bytes are stored starting at $0200, so they never catch up to the advancing retrieval of digits.
Line 1810 sets YSAV to point to the first character past the end of the hex string. This will usually be a carriage return, or another monitor command. Unless it is beyond $2CF, the monitor will correctly continue parsing whatever is in the buffer when we are through searching. At $2D0 and beyond, the DELTA table will clobber any further characters.
Now we come to the Boyer-Moore part. Lines 1820-1870 initialize the DELTA table to all -1 values, which is what we want for any bytes not present in the key. When the loop finishes, X=0 again.
Lines 1880-1970 scan through the search key from left to right, and store into DELTA the index of the rightmost occurrence of each value in the key. For example, if the key is "4245524E415244" ("BERNARD" again), the DELTA values will be:
DELTA+$41: 4 DELTA+$42: 0 DELTA+$44: 6 DELTA+$45: 1 DELTA+$4E: 3 DELTA+$52: 5 (also at 2, but 5 is rightmost) all others: -1
We'll continue with this example after a brief look at the rest of the code.
Lines 1980-2040 back up the end pointer, which has been patiently waiting all this time in A2L and A2H. We subtract the key length (in bytes, not digits) from the end pointer, so that we will not try to match the key any further than necessary. We could do this inside the search loop, but it will run faster if we do it once before the loop.
Lines 2050-2440 perform the search. I inserted lines 2070-2110 inside the loop to printout the search window start address each time through the loop. This helps me to make sure it is working, and to explain how. Of course you should remove these five lines before using the routine for real problems. Notice they are all marked "<<<DEBUG>>>".
Lines 2120-2170 check whether the beginning of the search window has moved past the end of the area to be searched. If so, we are finished.
Lines 2180-2240 compare bytes from the key and the search window. If the entire key matches, we fall out of the loop into lines 2250-2300, where the address of the match will be printed. After a successful match the search window will be moved one byte to the right by lines 2370-2430, and we will begin the SEARCH.LOOP again.
Notice that the key is compared from right-to-left, not left- to-right. This is a critical part of the Boyer-Moore method. If a key byte does not match a search-window byte, we branch to line 2320. The byte from the search window is in the A-register. Lines 2320-2370 compute how far we can advance the search window, based on just what character we DID find in the search window, and how far into the key we had already matched.
To see how this works, let's continue the "BERNARD" example. Suppose the text we are searching is "THERE ARE FEW ST. BERNARDS IN SAN BERNARDINO." The key will be BERNARD, entered in hex as shown above. We first try to match BERNARD at the beginning of the text. We start at the right end, matching the "D" of the key with "A" of the text. The match fails, so we look up the "A" value in the DELTA table, which is 4. We subtract the delta value (4) from the current key index (6) and add the result (6-4=2) to the search window address. Note that this has the result of aligning the "A" of BERNARD with the "A" in the text.
Back to the top, and we now try to match the "D" of BERNARD to the "E" at the end of "ARE". Failure again! This time the DELTA value is 1, and we are still at position 6 in the key: index-delta is 5, so we advance the window by 5. This lines up the "E" of BERNARD with the E of the text. The next attempted match will find a blank in the text, which does not occur in the key at all. The delta value for blank is -1: 6-(-1)=7, so we will advance the window by 7. Now the window is up to "ST. BER" in the text.
When we compare "D" of BERNARD to "R" in the text, we fail again. The delta value for R is 5. There are two R's in BERNARD, but the rightmost one is at index 5. We can move the search window by 6-5=1. Next we try "D" against "N". The delta value of "N" is 3, so we can move the window 6-3=3 bytes. This time we have found "BERNARD"!
If you count it all up, we have compared the "D" of BERNARD with only six characters, and already we are at the first occurrence of the whole key in the text. A conventional search would have tried to match the first character of the key ("B") with all 18 characters in the text which precede the first "B" of the text. We have saved 13 times around the main loop! Of course, our loop is a tiny bit longer, but the end result is faster.
Here is a step-by-step picture of the entire search, which finds BERNARD twice:
THERE ARE FEW ST. BERNARDS IN SAN BERNARDINO. BERNARD BERNARD BERNARD BERNARD BERNARD BERNARD (success!) BERNARD BERNARD BERNARD BERNARD (success!) BERNARD BER... (end)
I have tacked two more examples onto the end of the source code, at lines 2620-2690. You can play with them. The five <<<DEBUG>>> lines will print out the window address at each step, so you can see how the search progresses. Remember to take those lines out before you make a production version of the program.
If you decide to include this search algorithm in your own private debugger program, like I am, you might want to add the ability to use an ASCII string for the key. You could use a quotation mark after the control-Y to signal the packer loop that an ASCII string follows. You might also want to add single-byte wildcard characters, and/or the ability to ignore the high-order bit of each byte matched.
Perhaps the Boyer-Moore algorithm would be even more useful in a data base program, a word processor, or other context in which you are searching through huge quantities of text for relatively interesting keys. My example should get you started, and my son will be proud of you!
1000 *SAVE S.HEX.SEARCH 1010 *-------------------------------- 1020 * MEMORY SEARCH FOR HEX STRING 1030 * BY BOB BERNARD, MAY 17, 1985 1040 * MODIFIED BY BOB S-C, MAY 27TH 1050 * ADR1.ADR2^YXXXXXXXXXXXX 1060 * ("^Y" MEANS CONTROL-Y) 1070 * 1080 * SEARCH MEMORY FROM ADR1 THRU ADR2 1090 * LOOKING FOR REFERENCES TO 1100 * THE HEX STRING, XXXXXXXXXX 1110 * 1120 *-------------------------------- 1130 YSAV .EQ $34 1140 A1L .EQ $3C,3D START OF SEARCH AREA 1150 A2L .EQ $3E,3F END OF SEARCH AREA 1160 KEY.LENGTH .EQ $40 (MONITOR'S A3L) 1170 *-------------------------------- 1180 KBDBUF .EQ $0200 THRU $2CF 1190 DELTA.TABLE .EQ $02D0 THRU $3CF 1200 USRADR .EQ $03F8 CTL-Y JUMPS HERE 1210 *-------------------------------- 1220 PRINTAX .EQ $F941 1230 CROUT .EQ $FD8E NEW LINE 1240 MONZ .EQ $FF69 MONITOR, NO BELL 1250 *-------------------------------- 1260 .OR $0800 1270 .TF B.HEX.SEARCH 1280 *-------------------------------- 1290 HEX.SEARCH 1300 LDA #$4C JMP OPCODE 1310 STA USRADR STUFF INTO CNTL-Y EXIT LOC 1320 LDA #SEARCH LO ADR 1330 STA USRADR+1 1340 LDA /SEARCH HI ADR 1350 STA USRADR+2 1360 JMP MONZ MONITOR, NO BELL 1370 *-------------------------------- 1380 SEARCH 1390 *---SKIP LEADING BLANKS---------- 1400 LDY YSAV NEXT VALID KBDBUF CHAR 1410 .1 LDA KBDBUF,Y GET CHAR FROM 1420 INY KEYBOARD BUFFER 1430 CMP #" " SKIP LEADING BLANKS 1440 BEQ .1 1450 *---MARK KEY START--------------- 1460 DEY 1470 STY YSAV WHERE SCAN STARTS 1480 *---FIND END OF KEY-------------- 1490 .2 JSR NXTCHAR 1500 BCC .2 ...HEX DIGIT 1510 CPY YSAV CHECK FOR NULL KEY 1520 BNE .3 ...NOT NULL 1530 RTS NULL KEY 1540 *---COMPUTE KEY LENGTH----------- 1550 .3 TYA 1560 SBC YSAV 1570 1580 LSR 1590 STA KEY.LENGTH 1600 LDY YSAV 1610 LDX #0 1620 STX KBDBUF (IN CASE ODD COUNT) 1630 BCS .5 ...ODD NUMBER OF BYTES 1640 *---ADJUST FOR EVEN LENGTH------- 1650 DEC KEY.LENGTH MAKE EVEN LENGTH ONE LESS 1660 *---LEFT NYBBLE------------------ 1670 .4 JSR NXTCHAR 1680 BCS .6 END OF KEY 1690 ASL 1700 ASL 1710 ASL 1720 ASL 1730 STA KBDBUF,X LEFT HALF DEST CHAR 1740 *---RIGHT NYBBLE----------------- 1750 .5 JSR NXTCHAR 1760 AND #$0F 1770 ORA KBDBUF,X MERGE HI NIBBLE 1780 STA KBDBUF,X 1790 INX 1800 BNE .4 ...ALWAYS 1810 .6 STY YSAV 1820 *---INIT ALL DELTAS=-1 ---------- 1830 LDX #0 1840 LDA #-1 1850 .7 STA DELTA.TABLE,X 1860 INX 1870 BNE .7 ...256 OF THEM 1880 *---DELTA(KEY(I))=I-------------- 1890 LDY #0 FOR I=0 TO KEYLEN 1900 .8 LDA KBDBUF,Y DELTA(K) = DISTANCE FROM LEFT END 1910 TAX OF RIGHT-MOST OCCURENCE OF 1920 TYA 8-BIT VALUE "K" IN KEY. 1930 STA DELTA.TABLE,X 1940 INY NEXT I 1950 CPY KEY.LENGTH 1960 BCC .8 1970 BEQ .8 1980 *---ADJUST END OF SEARCH--------- 1990 SEC 2000 LDA A2L 2010 SBC KEY.LENGTH 2020 STA A2L 2030 BCS SEARCH.LOOP 2040 DEC A2L+1 2050 *-------------------------------- 2060 SEARCH.LOOP 2070 LDA A1L+1 <<<DEBUG>>> 2080 LDX A1L <<<DEBUG>>> 2090 JSR PRINTAX <<<DEBUG>>> 2100 LDA #"-" <<<DEBUG>>> 2110 JSR $FDED <<<DEBUG>>> 2120 LDA A2L CHECK AGAINST 2130 CMP A1L UPPER BOUND 2140 LDA A2L+1 FOR SEARCH 2150 SBC A1L+1 2160 BCS .1 A1<=A2, NOT FINISHED 2170 RTS A1>A2, FINISHED 2180 *---COMPARE IN THIS POSITION----- 2190 .1 LDY KEY.LENGTH FOR I=KEYLEN TO 0 2200 .2 LDA (A1L),Y CHECK BYTES FROM 2210 CMP KBDBUF,Y RIGHT TO LEFT 2220 BNE .3 ...DID NOT MATCH 2230 DEY NEXT I 2240 BPL .2 2250 *---MATCH FOUND------------------ 2260 LDX A1L PRINT ADR 2270 LDA A1L+1 WHERE MATCH 2280 JSR PRINTAX WAS FOUND 2290 JSR CROUT NEW LINE 2300 JMP .4 2310 *---ADVANCE SEARCH POINTER------- 2320 .3 TAX STRING CHAR JUST LOOKED AT 2330 TYA I 2340 CLC 2350 SBC DELTA.TABLE,X 2360 BPL .5 ...VALUE IS POSITIVE 2370 .4 LDA #0 ...ADVANCE BY 1 2380 .5 SEC COMPENSATE 2390 ADC A1L 2400 STA A1L 2410 BCC SEARCH.LOOP 2420 INC A1L+1 2430 BNE SEARCH.LOOP ...ALWAYS, UNLESS WE 2440 RTS ...RAN OFF THE END OF MEMORY 2450 *-------------------------------- 2460 NXTCHAR 2470 LDA KBDBUF,Y NEXT ACTIVE CHAR 2480 INY 2490 EOR #$B0 CONVERT ASCII TO DIGIT 2500 CMP #10 0..9? 2510 BCC .1 YES 2520 ADC #$88 SHIFT RANGE FOR A-F TEST 2530 CMP #$FA A..F? 2540 BCS .1 YES. EXIT CC 2550 SEC NOT HEX CHAR 2560 DEY BACK UP INDEX 2570 RTS 2580 .1 CLC SIGNAL HEX CHAR 2590 RTS 2600 *-------------------------------- 2610 END .BS $A00-* 2620 TEST.STRING 2630 .AS /XXXXXXXCOCACACACACACACACACACACAC/ 2640 * TRY A00.A1F^Y 43414341434143 2650 * SHOULD GET A09-A0B-A0D-A0F-A11-A13-A15-A17-A19 2660 *-------------------------------- 2670 TS2 .AS /A STRING SEARCHING EXAMPLE CONSISTING OF SIMPLE TEXT/ 2680 * TRY A20.A53^Y48494E47 2690 *-------------------------------- |
In some graphics situations you need a square root subroutine (it is probably the fault of Pythagoras). Since the screen coordinates are integers, a short and fast integer square root subroutine can be handy.
The following program is probably not in the "fast" category, but it is indeed short. It can produce the integer value of the square root of any integer from 0 through 65535. The program uses the method of subtracting successive odd numbers.
Every perfect square (N*N, where N is an integer) is the sum of a series of odd numbers from 1 through 2*N-1. Thus 4=1+3, 25=1+3+5+7+9, etc.
The program starts by subtracting 1, then 3, then 5, and so on until the remainder is negative. When the remainder goes negative, the last odd number subtracted was 2*N+1, so we can get the square root by dividing that odd number by 2.
I set up the routine so I could test it with an Applesoft pro- gram. You can POKE the low 8-bits of a number at 768 ($300), the high 8-bits at 769, and CALL 772. Upon return, PEEK(770)+ 256*PEEK(771) gives you the integer value of the square root.
I used a couple of tricks in the code. For one, the variable ODD is always an even number. Since I preface the subtraction with CLC, a "borrow" is assumed, so it has the effect of sub- tracting the odd number which is one larger than the even number in ODD. This save a LDA #1 instruction after line 1090.
In lines 1190-1230, I add 2 to the even number in ODD. But you can see that line 1200 is ADC #1. This adds 2 because carry happens to be set.
1000 *SAVE S.SQRT16 1010 *-------------------------------- 1020 .OR $300 1030 ARG .BS 2 1040 ODD .BS 2 1050 *-------------------------------- 1060 SQRT LDX ARG+1 X = HI BYTE HI 1070 LDY ARG Y = LO BYTE LO 1080 LDA #0 START ODD=0 1090 STA ODD+1 1100 .1 STA ODD 1110 CLC BORROW ON, SUBTRACT (ODD+1) 1120 TYA LO 1130 SBC ODD 1140 TAY 1150 TXA HI 1160 SBC ODD+1 1170 TAX 1180 BCC .2 ...ODD>REMAINDER, FINISHED 1190 LDA ODD CARRY SET, ADD 2 TO ODD 1200 ADC #1 1210 BCC .1 ...NEXT 1220 INC ODD+1 1230 BNE .1 ...ALWAYS ...ALWAYS 1240 .2 LSR ODD+1 SQRT IS (ODD/2) 1250 ROR ODD 1260 RTS 1270 *-------------------------------- |
You can use the following Applesoft program to test the square root subroutine.
1000 REM TEST SQRT16 1005 W = 256:A = 768:B = 769:C = 770:D = 771:E = 772 1010 FOR XH = 0 TO 255: PRINT XH"-";: FOR XL = 0 TO 255 1030 POKE A,XL: POKE B,XH: CALL E 1040 Y = PEEK (D) * W + PEEK (C) 1050 IF Y < > INT ( SQR (XH * W + XL)) THEN PRINT : PRINT XH * W + L,Y 1060 NEXT : NEXT
Sandy Greenfarb wrote the other day that he had received a 65802 and plugged it into his Basis 108 with success.
He has been trying various permutations of the new opcodes and modes, and discovered some stones are better left unturned:
"The following programs should both print the letter "A" on the screen. However, the one on the left works, while the one on the right hangs up the computer."
Works Hangs Up -------- -------- CLC CLC XCE XCE LDA #"A LDA #"A JSR $FDF0 JSR $FDED SEC SEC XCE XCE RTS RTS
The only difference in the two programs is that the unsuccessful one weaves its way through DOS. I looked at the DOS code it goes through, and at first glance it appears there should be NO PROBLEMS associated with executing all this code in 65802 mode, since both 16-bit modes are off.
However, for some reason it still hangs up. Actually, it might not always hang: it depends on what is in page zero at the corresponding position as the stack pointer in page one.
I do not know why, but the TXS instruction transfers the entire 16-bit value of X to S when you are in the 65802 mode, regardless of the status of the M and X bits. Since M and X are both 1, the high byte of the X-register is 00. Therefore the TXS instruction at $9FB9 in DOS clears the high byte of the S-register. The RTS at $9FC4 then uses a return address from page zero, rather than page one.
I tried various experiments to see how TXS and TSX worked, and also examined TXA and TAX. In my humble opinion, the 65802 is inconsistent here. If you are in 65802 mode with M and X = 1, TXA does not modify the high byte of the A-register. This is what I expect and what I want. But TXS does modify the high byte of the S-register, contrary to my expectations.
Of course, as long as you know exactly how the chip works it really doesn't matter a lot. The problems come when we ASSUME we know how it works, but are wrong. The best antidote for these kind of assumptions, at least until a definitive reference manual for the chip is published, is trial and error.
I have had my 65802 for about six months now, and still have had no problems whatsoever with compatibility as long as I stay in normal 6502 mode. If I leave it in 65802 and go charging through a program written for the 6502 mode, I expect I will run into trouble.
Have you ever wondered what's happening when the Apple goes off into nothingness? If your answer is yes, then this short utility will help you find out.
I was recently debugging an assembly language program and ran into this problem. The program seemed to work for almost all the input data, but occasionally would hang. After several frustrating hours trying to simulate the event, I decided that an interrupt trace utility would solve my problems. Later when I had this utility working, it was easy to see why the program was hanging.
This utility consists of a pushbutton addition to the Apple which connects to the interrupt request line (IRQ) of the 6502 and an interrupt service routine which is in page three. When the interrupt pushbutton is depressed, the interrupt service routine displays the program counter and all the registers on the bottom line of the screen. It also displays a flashing cursor and waits for an "S" or "G" from the keyboard to stop or resume execution.
I have mounted a pushbutton switch at the upper right hand side of the keyboard in the center of the styling surface. For a temporary installation I suggest leaving the pushbutton on a flexible lead. The wiring is easily done with 30 gage wire wrap wire. One side of the pushbutton is connected to ground. You may solder a wire to any convenient ground point on the top of the circuit board. Or, for a temporary installation, you could stick a wire into pin 8 of the game I/O connector.
The other side of the pushbutton is connected to the IRQ signal. I found that signal at pin 4 of the 6502. Remove the 6502 from the socket and strip the insulation from the end of the 30 gage wire. Insert it in the socket for the 6502 in pin 4 and replace the 6502 to retain the wire. Route the wire along the chips for a neat installation.
For a temporary hookup, Bob S-C suggests folding a 3-by-5 card in half, and triming it so that the folded edge just fits into an empty slot. Then, while power to the Apple is off, slip one wire into the space between the card and pin 26 (ground) and the other wire between the card and pin 30 (IRQ). Both of these wires will be on the power-supply side of the card: pin 26 is at the back edge, and pin 30 is the fifth from the back. Once the wires are inserted, you may wish to tape them down.
Enter the routine at address $300 and BSAVE it. When you want to debug a hanging program, first BRUN the INTERRUPT TRACE utility. This installs the utility at address $300 to $3CA. Pressing the pushbutton will cause an immediate display of the current program counter and registers. The utility will wait with a blinking cursor for a "G" or "S" from the keyboard to continue or enter monitor.
Sometimes the program you're investigating may not respond to the pushbutton. This is because somewhere in the program interrupts have been disabled with the SEI command ($78). You must search through the entire program and replace these with a CLI instruction ($58). Make sure that each $78 found is not data in the program and is a valid instruction before you replace it.
The next time that you have a problem with your Apple "hanging" for no apparent reason, use this utility to see where the 6502 "is". It may help solve those "hard to debug programs".
When you run the program, the SETUP routine (from $300 to $30B) sets the interrupt vector location and then enables interrupts. When the pushbutton is depressed, the IRQ line (pin 4 on the 6502) is pulled low. At the completion of the current instruction, the program counter high, program counter low, and processor status are pushed on the stack. Interrupt disable is automatically set and the program counter is loaded with the contents of $FFFE and $FFFF. In the Autostart monitor ROM the program counter is set to $FA40 where the monitor interrupt service routine is located. (In the old monitor the identical routine is at $FA86) This routine saves the accumulator in $45 and examines the processor status register to see if the inter- rupt was caused by a BRK command. Remember, the BRK command shares the same vector location with the interrupt for software simulation of interrupts. If the interrupt was not caused by BREAK then a JMP indirect to location ($3FE) is performed.
Lines 1280-1290 save the X- and Y-registers. The accumulator has already been saved by the monitor interrupt routine.
Lines 1300-1350 copy the register display titles to the bottom of the screen. Of course, if your program happened to be in one of the full-screen graphics modes, this line will not be visible. If you have a //e, you can add code to sense the graphics mode, save it, switch to text mode; then you will have to restore it all when you type "G" to continue after the interrupt. The new enhanced //e ROMs automatically handle saving and restoring all the bank switched memory, but they still leave the graphics modes up to the programmer.
Lines 1360-1510 convert the values of the five registers and store them into the bottom line. I add 3 to the S-register value before displaying it, so you see the value before the IRQ code pushed PC and S onto the stack. I start with the Y-register pointing at the point on the bottom line where the A-register should be displayed. The DISPLAY.HEX subroutine advances the Y-register by 5, so it is always ready for displaying the next register.
Lines 1520-1590 display the PC-register. This value is taken from the stack, where the IRQ automatically saved it.
Lines 1600-1750 wait for you to type "G" or "S". While waiting, the last character on the bottom line is flashed to remind you to type. If you type "G", lines 1760-1800 restore the registers and return to the interrupted program. If you type "S", line 1820 takes you to the monitor.
1000 *SAVE S.IRQ TRAPPER 1010 *-------------------------------- 1020 * INTERRUPT TRACE UTILITY 1030 * 1040 * BY: CHARLES H. PUTNEY 1050 * 18 QUINNS ROAD 1060 * SHANKILL 1070 * COUNTY DUBLIN 1080 * IRELAND 1090 *-------------------------------- 1100 A.REG .EQ $45 A-REG SAVE AREA USED BY MONITOR 1110 STACK .EQ $100 STACK PAGE 1120 INTVEC .EQ $3FE INTERRUPT VECTOR 1130 BOTTOM.LINE .EQ $7D0 LINE 24 OF TEXT SCREEN 1140 *-------------------------------- 1150 KEYBD .EQ $C000 KEYBOARD DATA 1160 KEYSTB .EQ $C010 KEYBOARD STROBE 1170 MNTR .EQ $FF69 MONITOR ENTRY POINT (CALL -151) 1180 *-------------------------------- 1190 .OR $300 PAGE THREE 1200 *-------------------------------- 1210 SETUP LDA #INT LOAD IRQ VECTOR 1220 STA INTVEC LOW BYTE 1230 LDA /INT 1240 STA INTVEC+1 HIGH BYTE 1250 CLI ALLOW IRQ'S 1260 RTS 1270 *-------------------------------- 1280 INT STX XREG SAVE X (A-REG SAVED BY MONITOR) 1290 STY YREG SAVE Y 1300 *---DISPLAY REG TITLES----------- 1310 LDX #39 PUT UP MESSAGE LINE 1320 .1 LDA TITLES,X GET MESSAGE CHAR 1330 STA BOTTOM.LINE,X PUT ON SCREEN 1340 DEX 1350 BPL .1 DONE ? 1360 *---DISPLAY REG VALUES----------- 1370 LDY #10 START OF REG DISPLAY AREA 1380 LDA A.REG ...A-REG 1390 JSR DISPLAY.HEX 1400 LDA XREG ...X-REG 1410 JSR DISPLAY.HEX 1420 LDA YREG ...Y-REG 1430 JSR DISPLAY.HEX 1440 TSX GET STACK POINTER 1450 INX POINT AT PROCESSOR STATUS 1460 LDA STACK,X ...P-REG 1470 JSR DISPLAY.HEX 1480 INX ADJUST S-REG 1490 INX 1500 TXA ...S-REG AS WAS BEFORE INTERRUPT 1510 JSR DISPLAY.HEX 1520 *---DISPLAY PC-REG--------------- 1530 LDY #0 START OF PC-REG DISPLAY 1540 LDA STACK,X GET PC HIBYTE 1550 JSR DISPLAY.HEX 1560 DEX 1570 LDY #2 1580 LDA STACK,X GET PC LOBYTE 1590 JSR DISPLAY.HEX 1600 *---WAIT FOR "S" OR "G"---------- 1610 .2 LDA KEYBD KEY PRESSED ? 1620 BPL .3 NO 1630 STA KEYSTB CLEAR THE KEY 1640 CMP #"G" GO AHEAD ? 1650 BEQ .4 YES 1660 CMP #"S" STOP ? 1670 BEQ .5 YES 1680 .3 DEX BLINK CURSOR 1690 BNE .3 1700 DEY LONGER DELAY 1710 BNE .3 1720 LDA BOTTOM.LINE+39 LAST CHAR ON SCREEN 1730 EOR #$80 INVERT IT 1740 STA BOTTOM.LINE+39 REPLACE IT 1750 BNE .2 BRANCH ALWAYS 1760 *---"G" TYPED, RETURN------------ 1770 .4 LDA A.REG RESTORE THE REGISTERS 1780 LDX XREG 1790 LDY YREG 1800 RTI BACK TO WORK 1810 *---"S" TYPED, SO STOP----------- 1820 .5 JMP MNTR ENTER THE MONITOR 1830 *-------------------------------- 1840 DISPLAY.HEX 1850 PHA SAVE THE A-REG 1860 LSR SHIFT INTO LOWER NIBBLE 1870 LSR 1880 LSR 1890 LSR 1900 JSR DIGIT MAKE IT A DIGIT 1910 STA BOTTOM.LINE,Y SHOW HIGH NIBBLE 1920 PLA GET A-REG AGAIN 1930 AND #$0F MASK IT 1940 JSR DIGIT MAKE IT A DIGIT 1950 STA BOTTOM.LINE,Y SHOW LOWER NIBBLE 1960 INY 1970 INY 1980 INY 1990 RTS 2000 *-------------------------------- 2010 DIGIT INY 2020 ORA #$B0 ADD NUMBER ZERO 2030 CMP #$BA IS IT A LETTER 2040 BCC .1 NO - DONE 2050 ADC #$6 6 PLUS CARRY MAKES A 2060 .1 RTS 2070 *-------------------------------- 2080 TITLES .AS -/ - A= X= Y= P= S= / 2090 *-------------------------------- 2100 XREG .DA #*-* X REGISTER SAVE AREA 2110 YREG .DA #*-* Y REGISTER SAVE AREA 2120 *-------------------------------- |
Bob's s;ingle byte converter (see Jan 85 issue, pages 31-32) can be shortened by one byte. The left column is from Bob's code, the right a shorter version:
1040 .1 LDX #"0" 1050 .2 CMP DECTBL,Y 1060 BCC .3 1070 SBC DECTBL,Y 1080 INX 1090 BNE .2 | .1 LDX #"0"-1 SEC .2 SBC DECTBL,Y INX BCS .2 ADC DECTBL,Y |
I also tried a different approach, using the decimal mode to count tens, then printing the tens as a hex value with the monitor routine at $FDDA and the remainder (units digit) with $FDED. This routine takes longer time, but does not need to use the X-register.
1000 *SAVE S.LOVE'S CONVERSION 1010 *-------------------------------- 1020 .LIST OFF 1030 *-------------------------------- 1040 BYTE .EQ $00 1050 COUT .EQ $FDED 1060 CROUT .EQ $FD8E 1070 PRBYTE .EQ $FDDA 1080 *-------------------------------- 1090 * COMMAND 1100 *-------------------------------- 1110 P LDA #0 1120 STA BYTE 1130 .1 JSR PRINT.000.255 1140 JSR CROUT 1150 INC BYTE 1160 LDA BYTE 1170 BNE .1 1180 RTS 1190 *-------------------------------- 1200 * PRINT.000.255 1210 *-------------------------------- 1220 PRINT.000.255 1230 LDY #0 1240 SEC 1250 .1 SBC #10 1260 PHP 1270 PHA 1280 TYA 1290 SED 1300 ADC #0 1310 TAY 1320 PLA 1330 PLP 1340 BCS .1 1350 ADC #"0+10 1360 PHA 1370 TYA 1380 JSR PRBYTE 1390 PLA 1400 JMP COUT 1410 *-------------------------------- 1420 SC 1430 LDY #"0" 1440 TAX 1450 BEQ .3 1460 LDA #0 1470 SED 1480 .2 CLC 1490 ADC #1 1500 BCC .25 1510 INY 1520 .25 DEX 1530 BNE .2 1540 CLD 1550 .3 PHA 1560 TYA 1570 JSR COUT 1580 PLA 1590 JMP $FDDA 1600 *-------------------------------- 1610 T LDA #0 1620 .1 STA BYTE 1630 JSR SC 1640 LDA #" " 1650 JSR $FDED 1660 LDA BYTE 1670 JSR $FDDA 1680 JSR CROUT 1690 LDA $C000 1700 BPL .2 1710 STA $C010 1720 .3 LDA $C000 1730 BPL .3 1740 .2 STA $C010 1750 LDA BYTE 1760 CLC 1770 ADC #1 1780 BNE .1 1790 RTS 1800 *-------------------------------- |
Here is a very elementary introduction to Apple Assembly Language programming by that old master Bob Bishop, along with Linda Gross- berger and Harry Vertelney. This 150+ page book and its companion diskette gently and humorously guide the beginning programmer into the realm of machine code. A "Cardboard Computer" introduces the concepts of registers, machine instructions, addressing, and branching. This background is then applied to the Apple's 6502 and the surrounding computer. AppleVisions is a nice place for the absolute beginner to start, especially the younger programmers interested in finding out what assembly language is.
AppleVisions. Addison-Wesley, 1985. $39.95 including diskette.
If and when you decide to upgrade to the new enhanced //e ROMs (which Apple sells for $70 along with a 65C02), you will probably have to turn your old ROMs over to the store that makes the switch. Reportedly, Apple is binding the stores with a contract that forces them to collect all the old chips.
That is VERY unfortunate. It could lead to wild shouting and panic, when you discover some of your favorite old software no longer works.
The upgrade consists of three parts:
The new firmware does NOT use any of the new features in the 65C02, so you could use it without the new cpu chip. Furthermore, there is no absolute requirement to have the new character generator installed. The new firmware is much better than the old, having lost some bugs and speeded up the 80-column scrolling and added lower-case support to Applesoft (among other things). It is compatible with the 6502, the 65C02, and the new 65802.
I personally do not yet have any use for the mouse characters, and do not expect to. Don Lancaster, in the June 1985 issue of "Computer Shopper", tells how to connect a 2764 EPROM in the character generator socket. The 2764 can hold two complete character sets, because it has twice the capacity of the 2732 normally in that socket. However, the socket has only 24 holes and the 2764 has 28 pins! Don shows how to wire this up with a socket adapter, and use a toggle switch to select either half.
And now Apple has "sort of" released an even more enhanced set of firmware, with debugging stuff built in. You may not see them on the open market for some time, but I like them even better than the standard enhanced ROMs. The "debug" ROMs add an absolute RESET (ctrl-RESET with solid apple), 16-byte hex display in the monitor when in 80-column mode, display of both hex and ASCII values of each byte in a memory dump, and the ability to use all monitor commands on both main and auxiliary memory. The disassembler and miniassembler are both present, and enhanced to include the 65C02 extensions.
The CD and EF ROM sockets are compatible with 2764 EPROMs. You can also use 27128s, which have twice the space. Pin 26 on the 2764 is always tied to +5 volts. On a 27128, pin 26 selects the top or bottom half of the 16K bytes inside. You can burn one set of firmware in one half, and the other set in the other half. Then bend out pin 26 a little, so that it does not go into the socket when you insert the chip. Attach a clip lead to the bent-out pin, and connect the other end to either +5 volts or ground, to select the half you want at any given time.
You can connect it to a toggle switch, or just stick the bare end of a wire into the game paddle connector. If you use the game socket on the motherboard, pin 1 is +5 volts and pin 8 is ground. Or stick a wire into one of the annunciator outputs (pins 12, 13, 14, and 15) so you can flip back and forth between firmware sets by software control.
It can be a little tricky to make a copy of the ROM firmware and get it into RAM or on a disk, so that you can later burn it in your own EPROM. Especially in the Cxxx part. My approach, since I have more than one Apple, is to put my SCRG PromGramer card in a different machine. Then one by one I can read the //e ROMs and burn them into the appropriate 27128s. This a lot faster than trying to figure out how to flip all the //e soft switches so as to get at the different banks of Cx ROM code.
I have recently seen 27128s priced as low as $5 and as high as $20, in the back of Byte magazine. It is well worth it to invest in a PromGramer, at $140, and an EPROM eraser ($50 to $100 from Logical Devices in Florida, see Byte ads). You can keep your Apple standard for commercial software, and still have your own private firmware on the motherboard at the flip of a switch!
Anyone who has ever used Applesoft eventually realizes that the most powerful statement in the language is CALL. It allows you to get to the Monitor for instance (however, Extended Debugging Monitor users have a better way). When writing a program in BASIC, you invariably will want to do something that is at best difficult and often impossible to code using the other Applesoft statements.
The solution to this type of situation is to speak to your Apple in its native tongue. There are several way this can be done. Ampersand (&) routines are a popular technique. The USR( function even has its uses. The most logical way, for me, is the CALL statement.
Using CALL neatly transfers control from the Applesoft interpreter to whatever you want to do in machine language. The one disadvantage to CALL is that the processor's registers do not contain useful data when your machine code gets control.
The CALL utility presented in this article will allow you to specify, as part of the CALL statement, the contents of any or all of the registers upon entry of your machine language subroutine. You assign the register contents with LET-like structures. Obviously you can only fit an 8 bit value into the 8 bit registers and the program counter value will probably be a 16 bit number. Here's how the CALL statement should be written:
CALL 768,PC=word,A=byte,X=byte,Y=byte,P=byte
The expressions "word" and "byte" may be any valid Applesoft numeric expression. This is because the utility calls routines internal to Applesoft to evaluate the expressions. If an expression results in a value larger than the register to which it is being assigned, or isn't numeric, or is invalid, you will get one of the usual errors. The commas shown separating the register assignments are required (syntax error if comma missing). The equals characters ("=") are also required. The register names ( PC, A, X, Y, & P) must be upper case on older Apples, while the newer firmware will convert lower case for you (or in spite of you). The register assignments may appear, after the first comma, in any order and need not all be specified. Unspecified registers will be loaded with their last used value. Previously unused registers default to zero, except the P-register which defaults to $04 in order to set the interrupt disable flag.
The program is well commented, but I'll add one more note of caution. Readers with Apples containing reqular 6502s (not 65C02s or 65802s) should avoid re-assembling the code with the label PC.Sav's bytes falling across a page boundary ($XXFF).
The program was written using the ProDOS version of the S-C Macro Assembler 2.0, while I was beta testing it for Bob. It works GREAT!
1000 *SAVE S.CALL.UTIL 1010 1020 * 6/13/85 dcj 1030 1040 * CALL 768{,pc=word,a=byte,x=byte,y=byte,p=byte} 1050 1060 *-------------------------------- 1070 1080 .OR $300 1090 .TF CU 1100 1110 EQ.TOK .EQ $D0 Applesoft '=' token 1120 1130 CHRGET .EQ $B1 -$C8 advance TXTPTR & fetch chr 1140 CHRGOT .EQ $B7 just fetch chr 1150 1160 FRMNUM .EQ $DD67 evaluate FP expression (FAC) 1170 SYNCHR .EQ $DEC0 require chr in Acc syntax @ TXTPTR 1180 SYNERR .EQ $DEC9 syntax error 1190 GETBYT .EQ $E6F8 evaluate 8 bits @ TXTPTR (X-reg) 1200 GETADR .EQ $E752 convert FAC to 16 bits in Acc & Y-reg (hi/lo) 1210 1220 *-------------------------------- 1230 1240 CALL.UTIL 1250 1260 JSR CHRGOT get chr after call adr expression 1270 CMP #',' comma indicates more stuff follows 1280 BEQ .1 =>go continue parsing 1290 LDA P.SAV load registers 1300 PHA (P-reg via stack) 1310 LDA ACC.SAV 1320 LDX X.SAV 1330 LDY Y.SAV 1340 PLP 1350 JMP (PC.SAV) go 4 it! 1360 1370 * we got something to parse 1380 1390 .1 JSR CHRGET get chr after comma 1400 CMP #'A' (as in 'Acc') 1410 BEQ .2 =>go get '=' & byte for Acc 1420 CMP #'X' (as in 'X-reg') 1430 BEQ .3 =>go get '=' & byte for X-reg 1440 CMP #'Y' (as in 'Y-reg') 1450 BEQ .4 =>go get '=' & byte for Y-reg 1460 CMP #'P' (as in P-reg or Program Counter) 1470 BEQ .5 =>go get '=' or 'C='... 1480 JMP SYNERR razz 1490 1500 * pickup Acc byte 1510 1520 .2 JSR .7 require '=' (@ next) & fetch byte exp 1530 STX ACC.SAV stuff it 1540 BVC CALL.UTIL ...always 1550 1560 * pickup X-reg byte 1570 1580 .3 JSR .7 require '=' (@ next) & fetch byte exp 1590 STX X.SAV stuff it 1600 BVC CALL.UTIL ...always 1610 1620 * pickup Y-reg byte 1630 1640 .4 JSR .7 require '=' (@ next) & fetch byte exp 1650 STX Y.SAV stuff it 1660 BVC CALL.UTIL ...always 1670 1680 * Finish parsing 'P=' or 'PC=' 1690 1700 .5 JSR CHRGET advance to next chr position & fetch it 1710 CMP #'C' (as in 'Program Counter') 1720 BEQ .6 =>go get '=' & 16 bits for PC 1730 1740 * pickup P-reg byte 1750 1760 JSR .10 require '=' @ current chr position 1770 JSR .8 fetch byte expression 1780 STX P.SAV stuff it 1790 BVC CALL.UTIL ...always 1800 1810 * pickup PC word 1820 1830 .6 JSR .9 require '=' @ next chr position 1840 JSR FRMNUM fletch FP expression 1850 JSR GETADR convert FP expression to Acc & Y-reg (hi/lo) 1860 STY PC.SAV stuff 'em 1870 STA PC.SAV+1 1880 JMP CALL.UTIL no flag known... 1890 1900 .7 JSR .9 require '=' @ next chr position 1910 1920 .8 JSR GETBYT fetch byte expression (2 X-reg) 1930 CLV to force branch 1940 RTS 1950 1960 .9 JSR CHRGET 1st advance to next chr position 1970 1980 .10 LDA #EQ.TOK require '=' before register expressions 1990 JMP SYNCHR (SYNTAX ERROR IF '=' NOT FOUND) 2000 2010 *-------------------------------- 2020 2030 ACC.SAV .DA #$00 2040 X.SAV .DA #$00 2050 Y.SAV .DA #$00 2060 P.SAV .DA #$04 2070 PC.SAV .DA $0000 2080 2090 *-------------------------------- |
Gerald Ferrier (Princeton, Minnesota) wrote to point out that we somewhow omitted a double-handful of subroutines from our lengthy series on 18-digit arithmetic for Applesoft. With apologies to you all, and thanks to Gerald, here they are:
1000 *SAVE DP18.MOVE.SUBS 2020 *-------------------------------- 2030 * MOVE (Y,A) INTO DAC. YA IS UNPACKED 2040 *-------------------------------- 2050 MOVE.YA.DAC.1 2060 STA PNTR 2070 STY PNTR+1 2080 LDY #10 MOVE 11 BYTES 2090 .1 LDA (PNTR),Y 2100 STA DAC,Y 2110 DEY 2120 BPL .1 2130 LDA DAC.EXPONENT 2140 STA DAC.SIGN 2150 AND #$7F 2160 STA DAC.EXPONENT 2170 RTS 2180 *-------------------------------- 2190 * MOVE (Y,A) INTO ARG. YA IS UNPACKED 2200 *-------------------------------- 2210 MOVE.YA.ARG.1 2220 STA PNTR 2230 STY PNTR+1 2240 LDY #10 MOVE 11 BYTES 2250 .1 LDA (PNTR),Y 2260 STA ARG,Y 2270 DEY 2280 BPL .1 2290 LDA ARG.EXPONENT 2300 STA ARG.SIGN 2310 AND #$7F 2320 STA ARG.EXPONENT 2330 RTS 2340 *-------------------------------- 2350 * MOVE DAC TO (Y,A) WITHOUT PACKING 2360 *-------------------------------- 2370 MOVE.DAC.YA.1 2380 STA PNTR 2390 STY PNTR+1 2400 LDA DAC.EXPONENT 2410 BPL .0 2420 JMP DAC.YA.O.U OVER- OR UNDER-FLOW 2430 .0 BIT DAC.SIGN 2440 BPL .1 POSITIVE 2450 ORA #$80 NEGATIVE 2460 .1 LDY #0 2470 .2 STA (PNTR),Y 2480 INY 2490 LDA DAC,Y 2500 CPY #11 11 BYTES 2510 BCC .2 2520 RTS 2530 *-------------------------------- 2540 MOVE.DAC.TEMP1 2550 LDA #DP.TEMP1 2560 LDY /DP.TEMP1 2570 JMP MOVE.DAC.YA.1 2580 *-------------------------------- 2590 MOVE.TEMP1.ARG 2600 LDA #DP.TEMP1 2610 LDY /DP.TEMP1 2620 JMP MOVE.YA.ARG.1 2630 *-------------------------------- 2640 MOVE.TEMP1.DAC 2650 LDA #DP.TEMP1 2660 LDY /DP.TEMP1 2670 JMP MOVE.YA.DAC.1 2680 *-------------------------------- 2690 MOVE.DAC.TEMP2 2700 LDA #DP.TEMP2 2710 LDY /DP.TEMP2 2720 JMP MOVE.DAC.YA.1 2730 *-------------------------------- 2740 MOVE.TEMP2.DAC 2750 LDA #DP.TEMP2 2760 LDY /DP.TEMP2 2770 JMP MOVE.YA.DAC.1 2780 *-------------------------------- 2790 MOVE.TEMP2.ARG 2800 LDA #DP.TEMP2 2810 LDY /DP.TEMP2 2820 JMP MOVE.YA.ARG.1 2830 *-------------------------------- 2840 MOVE.TEMP3.DAC 2850 LDA #DP.TEMP3 2860 LDY /DP.TEMP3 2870 JMP MOVE.YA.DAC.1 2880 *-------------------------------- 2890 MOVE.TEMP3.ARG 2900 LDA #DP.TEMP3 2910 LDY /DP.TEMP3 2920 JMP MOVE.YA.ARG.1 2930 *-------------------------------- 2940 MOVE.DAC.TEMP3 2950 LDA #DP.TEMP3 2960 LDY /DP.TEMP3 2970 JMP MOVE.DAC.YA.1 2980 *-------------------------------- |
Apple Assembly Line is published monthly by S-C SOFTWARE CORPORATION, P.O. Box 280300, Dallas, Texas 75228. Phone (214) 324-2050. Subscription rate is $18 per year in the USA, sent Bulk Mail; add $3 for First Class postage in USA, Canada, and Mexico; add $14 postage for other countries. Back issues are available for $1.80 each (other countries add $1 per back issue for postage).
All material herein is copyrighted by S-C SOFTWARE CORPORATION,
all rights reserved. (Apple is a registered trademark of Apple Computer, Inc.)