The Apple Assembly Line is still growing! I now am sending out over 300 copies per month! It is also growing in size, as you can see: this is the first 20 page issue.
In This Issue...
Second "Disk of the Quarter"
The second AALDQ is ready! If you would like to have the source code on disk in S-C Assembler II Version 4.0 format for all the programs which have appeared in AAL issues 4, 5, and 6, then send me $15. I will send you the disk, and you already have the documentation. DQ#1, covering issues 1, 2, and 3, is also still available at the same price.
Some New Books about the 6502
Apple Machine Language, by Don Inman and Kurt Inman, published by Reston (a Prentice-Hall Company). Hard cover, 296 pages, $14.95. If you are an absolute beginner, this is the book for you. You start by typing in an Applesoft program which helps you POKE in machine language code, and CALL it. Most of the examples involve lo-res graphics and sound. One chapter describes the Apple Mini-Assembler (which resides in the Integer BASIC ROMs). They never get around to a real assembler.
Practical Microcomputer Programming: the 6502, by W. J. Weller, published by Northern Technology Books. Hard cover, 459 pages, $32.95. Over 110 pages of the book are devoted to a listing of an assembler and a debugging package. A coupon inside the back cover can be redeemed for a tape copy which will run on the Apple II. By adding $7.50 to the coupon, you can get a disk version. The package can be loaded from the disk, but there is no capability for keeping source or object files on disk.
The old saying, "You can't tell the players without a scorecard," is certainly true for program debugging, and sometimes the only way is to look into memory and see what is there. The Apple II Monitor has a memory dump command, but I found it inadequate: it's formatted for a 40-column screen, it doesn't show ASCII codes, and getting output on a printer is a hassle.
So I sat down and wrote a quick assembly language memory dump modeled after a System/360 core dump (remember when computer memory was called "core"?), with both hex and ASCII. My first attempt took up more than one page of memory and was trapped where I assembled it by absolute internal references. I massaged it until it fit in less than a page and made it relocatable ("run anywhere") by making all internal jumps into relative branches. (A "page" in 6502 jargon is 256 bytes, with addresses running from xx00 through xxFF.)
Next I decided to add a printer feature; while I was at it I made it use 80 columns on the printer, 40 on the screen.
Next I made it print the bytes in groups of four, with a space between every four bytes. Sixteen bytes are printed per line on the screen, 32 on an 80-column printer. Spacing in groups of four makes it easier to spot certain address locations. If a byte value is a printable ASCII code, I print the character above the hexadecimal value. (Values $00-$1F and $80-$9F do not print.)
Then I wanted options to browze one screenful at a time, and backup when I passed the place I wanted to look at.
You probably think that by now the program is at least two, and maybe more, pages long. Not so! All the while I was able to keep it in only one page (which doesn't say much for my original code).
The end result (after 21 versions!) is listed here for your examination and pleasure.
Operating Instructions: BRUN the program anywhere in memory that you have a free page (256 bytes). When the "?" prompt appears, enter the address of the memory you want to dump in any of the following ways. After the address or address range, type the return key.
S.E | To dump memory from S to E on the screen. |
S-E | To dump memory from S to E on the printer. |
S,E | To dump memory from S to E on the screen, but pauses after each screenful; press space bar to continue, or press control-C to stop. |
S | To dump from S, pausing after each line; press space bar to dump next line, press letter "B" to back up one line, or press control-C to stop. |
1000 *--------------------------------- 1010 * 1020 * APPLE II RELOCATABLE MEMORY DUMP PROGRAM 1030 * BY ROBERT H. BERNARD 1040 * 35 DOGWOOD LANE 1050 * WESTPORT, CT 06880 1060 * 1070 * JANUARY 17, 1981 1080 * 1090 * COMMERCIAL RIGHTS RESERVED 1100 * 1110 *--------------------------------- 1120 * MONITOR ROM ROUTINES 1130 *--------------------------------- 1140 MON.COUT .EQ $FDED 1150 MON.RDKEY .EQ $FD0C 1160 MON.GTLNZ .EQ $FD67 1170 MON.ZMODE .EQ $FFC7 1180 MON.GETNUM .EQ $FFA7 1190 MON.CROUT .EQ $FD8E 1200 MON.PRNTYX .EQ $F940 1210 MON.PRBL2 .EQ $F94A 1220 MON.PRBYTE .EQ $FDDA 1230 MON.MON .EQ $FF65 1240 MON.HOME .EQ $FC58 1250 MON.SETMOD .EQ $FE18 1260 MON.OUTPOR .EQ $FE95 SET OUTPUT PORT TO SLOT (A) 1270 MON.SETVID .EQ $FE93 SET VIDEO 1280 *--------------------------------- 1290 * I/O ADDRESSES 1300 *--------------------------------- 1310 KBD .EQ $C000 KEYBOARD 1320 KBSTRB .EQ $C010 KBD RESET STROBE 1330 *--------------------------------- 1340 * PAGE-ZERO VARIABLES 1350 *--------------------------------- 1360 PGCNT .EQ $2E LINES LEFT THIS PAGE 1370 ITEMCT .EQ $30 ITEMS PER LINE 1380 OPTION .EQ $31 SAME AS MON "MODE" 1390 PROMPT .EQ $33 LOC OF GETLN PROMPT CHAR 1400 YSAV .EQ $34 POINTER TO IN BUFFER 1410 FRADRL .EQ $3C STARTING ADR LO ORDER 1420 FRADRH .EQ $3D ..HI ORDER 1430 TOADRL .EQ $3E ENDING ADR LO ORDER 1440 TOADRH .EQ $3F ..HI ORDER 1450 *--------------------------------- 1460 * USER-CHANGEABLE PARAMETERS 1470 *--------------------------------- 1480 SCITMS .EQ 16 BYTES PER LINE SCREEN 1490 PRITMS .EQ 32 BYTES PER LINE PRINTER 1500 ITMSPG .EQ 8 ITEMS PER PAGE 1510 PRSLOT .EQ 1 PRINTER SLOT 1520 *--------------------------------- 1530 .OR $0800 1540 *--------------------------------- 1550 MEMDMP JSR MON.SETVID SET PR#0 1560 LDA #$BF '?' FOR BOUNDS 1570 STA PROMPT SET PROMPT CHAR 1580 JSR MON.GTLNZ CR, THEN GET INPUT 1590 JSR MON.ZMODE SET HEX DECODE MODE 1600 JSR MON.GETNUM 1610 STY YSAV REMEMBER SCAN POS. 1620 CPX #0 ANY ADR SCANNED? 1630 BNE .3 YES 1640 RTS NO. TERMINATE 1650 .DA MON.MON MONITOR ENTRY (IN CASE YOU WANT 1660 * TO CHANGE RETURN TO "JMP MON.MON") 1670 * 1680 .3 LDA #-SCITMS BYTES PER SCREEN LINE 1690 STA ITEMCT ITEMS PER LINE 1700 JSR MON.SETMOD SET TO SCAN 2ND ARG 1710 CMP #$AD IS OPTION = '-' ? 1720 BNE .2 NO. CHECK OTHERS 1730 INC OPTION MAKE '.' 1740 LDA #PRSLOT PRINTER SLOT NO 1750 JSR MON.OUTPOR SET OUTPUT PORT 1760 LDA #-PRITMS BYTES PER PRINTER LINE 1770 STA ITEMCT ITEMS PER LINE 1780 BNE .1 GO GET 2ND ARG 1790 * 1800 .2 CMP #$AE '.' ? 1810 BEQ .1 YES. 2 ARGS 1820 CMP #$AC ','? 1830 BNE SETPGL ONLY ONE ARG 1840 .1 LDY YSAV PTR TO IN BUFFER 1850 JSR MON.GETNUM SCAN 2ND ARG 1860 STY YSAV PTR TO IN BUFFER 1870 SETPGL LDA #ITMSPG ITEMS PER PAGE 1880 STA PGCNT 1890 * 1900 NEXTLN JSR MON.CROUT SKIP A LINE 1910 LDA ITEMCT -ITEMS PER LINE 1920 AND FRADRL STARTING ADR 0 MOD ITEMCT 1930 STA FRADRL 1940 TAX 1950 LDY FRADRH ..TO PRINT 1960 JSR MON.PRNTYX PRINT IT IN HEX 1970 LDX ITEMCT NO OF BYTES THIS LINE 1980 LDY #0 POINTER 1990 BEQ NOBLNK DON'T SPACE FIRST TIME 2000 * 2010 CHKKEY LDA KBD KEY DOWN? 2020 BPL CKDONE NO 2030 LDA KBSTRB YES. CLEAR KEYBOARD 2040 SEC PREPARE FOR 2050 MDMP2 BCS MEMDMP JMP TO START 2060 * 2070 NXTCHR TYA TEST FOR 2080 AND #$03 0 MOD 4 2090 BNE NOBLNK 2100 LDA #$A0 2110 JSR MON.COUT PRINT A BLANK 2120 NOBLNK LDA #$A0 2130 JSR MON.COUT PRINT A BLANK 2140 LDA (FRADRL),Y GET CHAR TO PRINT 2150 CMP #$20 CNTRL CHAR? 2160 BCC .1 YES. SUBSTITUTE BLANK 2170 CMP #$80 CNTRL CHAR? 2180 BCC .2 NO. OK TO PRINT 2190 CMP #$A0 CNTRL CHAR? 2200 BCS .2 NO. OK TO PRINT 2210 .1 LDA #$A0 SUBSTITUTE BLANK 2220 .2 JSR MON.COUT 2230 INY POINT AT NEXT 2240 INX DONE ON THIS LINE? 2250 BNE NXTCHR NO 2260 JSR MON.CROUT YES. CR 2270 * PREPARE TO PRINT SAME ITEMS IN HEX 2280 LDX #3 2290 JSR MON.PRBL2 OUTPUT (X) BLANKS 2300 LDX ITEMCT ITEMS PER LINE 2310 LDY #0 POINTER 2320 BEQ NXTHEX (JMP) 2330 * 2340 SETPL1 BCS SETPGL JUMP TO SET PG LENGTH 2350 CKOPT CMP #$AC NO. OPTION=',' ? 2360 NXTLN1 BNE NEXTLN NO. JUMP TO PRINT 2370 CKDONE LDA FRADRL TEST IF DONE 2380 CMP TOADRL 2390 LDA FRADRH 2400 SBC TOADRH 2410 BCC NEXTLN FROM < TO 2420 MDMP1 BCS MDMP2 JMP TO START 2430 * 2440 NXTHEX TYA TEST FOR 2450 AND #$03 0 MOD 4 2460 BNE .1 IF NOT, SKIP BLANK 2470 LDA #$A0 2480 JSR MON.COUT PRINT A BLANK 2490 .1 LDA (FRADRL),Y BYTE TO OUTPUT 2500 JSR MON.PRBYTE OUTPUT IN HEX 2510 INY NEXT 2520 INX DONE ON THIS LINE? 2530 BNE NXTHEX NO 2540 JSR MON.CROUT YES. CR 2550 * ADVANCE DUMP ADDRESS 2560 SEC PREPARE FOR SUBTRACT 2570 LDA FRADRL INCREMENT ADDRESS 2580 SBC ITEMCT -ITEMS PER LINE 2590 STA FRADRL 2600 BCC .2 NO CARRY 2610 INC FRADRH PAGE BOUNDARY 2620 BEQ MDMP1 END OF MEMORY 2630 .2 LDA OPTION 2640 CMP #$AE '.'? (OPTION 1) 2650 BEQ CHKKEY NO. CHECK IF KEY DOWN 2660 CHKPAG DEC PGCNT PAGE END? 2670 BNE CKOPT NO. CHECK OPTION 2680 PAUSE JSR MON.RDKEY GET A CHAR 2690 CMP #$83 CNTRL-C? 2700 BEQ MDMP1 YES. START OVER 2710 CMP #$C2 WAS CHAR READ A 'B'? 2720 BEQ BACKUP YES 2730 LDA OPTION 2740 CMP #$AC OPTION=',' ? 2750 BEQ SETPL1 YES 2760 ADVNCE INC PGCNT ONE MORE TIME 2770 BNE NXTLN1 JMP TO NXTLN 2780 * 2790 BACKUP LDA FRADRL CARRY IS SET 2800 SBC #144 BACKUP SCITMS*(ITMSPG+1) BYTES 2810 STA FRADRL SAVE LO ORDER 2820 BCS .1 NO CARRY 2830 DEC FRADRH PROPOGATE CARRY 2840 .1 JSR MON.HOME CLEAR SCREEN 2850 SEC SIMULATE JMP 2860 BCS SETPL1 ..TO SETPGL 2870 * 2880 ZZSIZE .EQ *-MEMDMP PROGRAM SIZE 9999 .LIF |
The 6502 has 104 so-called unused opcodes. The various charts and reference manuals I have checked either leave them blank or call them "unused", "no-operation", or "future expansion". The 6502 has been around since 1976; I think we have waited long enough to know there will be no "expansion". But are they really unused? Do they have any effect if we try to execute them? Are they really no-ops? If so, how many bytes does the processor assume for each one?
These questions had never bothered me until I was looking through some disassembled memory and thought I found evidence of someone USING the "unused". It turned out they were not, but my curiosity was aroused. Just for fun, I built a little test routine and tried out the $FF opcode. Lo and behold! The 6502 thinks it is a 3-byte instruction, and it changes the A-register and some status bits!
About 45 minutes later I pinned it down: FFxxyy performs exactly the same as the two instructions FExxyy and FDxxyy. It is just as though I had executed one and then the other. In other words, anywhere in a program I find:
INC VARIABLE,X SBC VARIABLE,X |
I can substitute:
.HS FF .DA VARIABLE !!!! |
You might wonder if I will ever find that sequence. I did try writing a program to demonstrate its use. It has the advantage of saving 3 bytes, and 4 clock cycles. (The SBC instruction is executed DURING the 7 cycles of the INC instruction!)
TEST LDX INDEX LDA #10 FOR COUNTER(X)=10 TO 39 STA COUNTER,X .1 LDA COUNTER,X GET COUNTER(X) JSR $FDDA PRINT IT OUT (OR WHATEVER) LDA #39 LIMIT .HS FF DO INC AND SBC .DA COUNTER ON COUNTER,X BCS .1 NEXT RTS |
Are there any more? Before I could rest my curiosity, I had spent at least ten more hours, and had figured out what all 104 "unused opcodes" really do!
The center-fold chart shows the fruit of my detective work. The shaded opcodes are the "unused" ones. I don't know if every 6502 behaves the same as mine or not. Mine appears to be made by Synertek, and has a date code of 7720 (20th week of 1977). It could be that later versions or chips from other sources (MOS Technology or Rockwell) are different. If you find yours to be different, please let me know!
Twelve of the opcodes, all in column "x2", hang up the 6502; the only way to get out is to hit RESET or turn off the machine.
There are 27 opcodes which appear to have no effect on any registers or on memory. These could be called "NOP", but some of them are considered by the 6502 to have 2 or 3 bytes. I have labeled them "nop", "nop2", and "nop3" to distinguish how many bytes the 6502 thinks it is using. You could call nop2 "always skip one byte" and nop3 "always skip two bytes".
The action most of the rest perform can be deduced by looking at the other opcodes in the same row. For example, all of the xF column (except 8F and 9F) perform two instructions together: first the corresponding xE opcode, and then the corresponding xD opcode. In the same way, most of the opcodes in column x7 combine the x6 and x5 opcodes. The x3 column mirrors the x7 and xF columns, but with different addressing modes. And finally, the xB column mimics the other three columns, but with more exceptions. Most of the exceptions are in the 8x and 9x rows.
A few of the opcodes seem especially interesting and potentially useful. For example, A3xx performs three steps: first it loads xx into the X-register; then using this new value of X, it moves the byte addressed by (xx,X) into both the A- and X- registers. Another way of looking at this one is to say that whatever value xx has is doubled; then the two pagezero bytes at 2*xx and 2*xx+1 are used as the address for loading the A- and X-registers. You could use this for something, couldn't you?
There are five instructions which form the logical product of the A- and X-registers (without disturbing either register) and store the result in memory. If we call this new instruction "SAX", for "Store A&X", we have:
83 SAX (z,X) 8F SAX a 87 SAX z 9F SAX a,X 97 SAX z,Y |
We get seven forms of the combination which shift a memory location using ASL, and then inclusive OR the results into A with an ORA instruction. If we call this new instruction ALO, we have:
03 ALO (z,X) 1B ALO a,Y 13 ALO (z),Y 0F ALO a 07 ALO z 1F ALO a,X 17 ALO z,X |
The same seven forms occur for the combinations ROL-AND, LSR-EOR, and ROR-ADC. Note that if you don't care what happens to the A-register, and the status register, these 28 instructions make two extra addressing modes available to the shift instructions: (z,X) and (z),Y.
Opcodes 4B and 6B might also be useful. You can do an AND-immediate followed by LSR or ROR on the A-register.
Opcodes 93, 9B, and 9E are really weird! It took a lot of head-scratching to figure out what they do.
93 Forms the logical product of the A-register and byte the at z+1 (which I call "hea") and stores it at (z),Y. 9B Forms the logical product of the A- and X- registers, and stores the result in the S- register (stack pointer)! Ouch! Then it takes up the third byte of the instruction (yy from 9B xx yy) and adds one to it (I call it "hea+1"). Then it forms the logical product of the new S-register and "hea+1" and stores the result at "a,Y". Whew! 9E Forms the logical product of the X-register and "hea+1" and stores the result at "a,Y". |
We get six forms of the new "LAX" instruction, which loads the same value into both the A- and X-registers:
B3 LAX (z),Y AB LAX #v A7 LAX z AF LAX a B7 LAX z,Y BF LAX a,Y |
I skipped over BB, because it is another extremely weird one. It forms the logical product of the byte at "a,Y" and S-register, and stores the result in the A-, X-, and S-registers. No wonder they didn't tell us about it!
Right under that one is the CB instruction. Well, good buddy (please excuse the CB talk!), it forms the logical product of the A- and X-registers, subtracts the immediate value (second byte of CB xx), and puts the result into the X-register.
The Cx and Dx rows provide us with seven forms that do a DEC on a memory byte, and then CMP the result with the A-register. Likewise, the Ex and Fx rows give us seven forms that perform INC followed by SBC.
It is a good thing to be aware that the so-called "unused" opcodes can be quite dangerous if they are accidentally executed. If your program goes momentarily wild and executes some data, chances are something somewhere will get strangely clobbered.
Since all of the above information was deduced by testing and observation, I cannot be certain that I am 100% correct. I may have overlooked or mis-interpreted some results, or even made a clerical error. Furthermore, as I said before, my 6502 may be different from yours. You can test your own, to see if it works like mine.
And if the whole exercise seems academic to you, you can at least enjoy the first legible and complete hexadecimal opcode chart for the 6502.
x0 x1 x2 x3 ----------------------------------------------------------- 0x BRK ORA (z,X) hang ASL (z,X) ORA (z,X) 1x BPL r ORA (z),Y hang ASL (z),Y ORA (z),Y 2x JSR a AND (z,X) hang ROL (z,X) AND (z,X) 3x BMI r AND (z),Y hang ROL (z),Y AND (z),Y 4x RTI EOR (z,X) hang LSR (z,X) EOR (z,X) 5x BVC r EOR (z),Y hang LSR (z),Y EOR (z),Y 6x RTS ADC (z,X) hang ROR (z,X) ADC (z,X) 7x BVS r ADC (z),Y hang ROR (z),Y ADC (z),Y 8x nop2 STA (z,X) nop2 A&X --> (z,X) 9x BCC r STA (z),Y hang A&hea --> (z),Y Ax LDY #v LDA (z,X) LDX #v LDX #v LDA (z,X) LDX (z,X) Bx BCS r LDA (z),Y hang LDA (z),Y LDX (z),Y Cx CPY #v CMP (z,X) nop2 DEC (z,X) CMP (z,X) Dx BNE r CMP (z),Y hang DEC (z),Y CMP (z),Y Ex CPX #v SBC (z,X) nop2 INC (z,X) SBC (z,X) Fx BEQ r SBC (z),Y hang INC (z),Y SBC (z),Y |
x4 x5 x6 x7 ----------------------------------------------------------- 0x nop2 ORA z ASL z ASL z ORA z 1x nop2 ORA z,X ASL z,X ASL z,X ORA z,X 2x BIT z AND z ROL z ROL z AND z 3x nop2 AND z,X ROL z,X ROL z,X AND z,X 4x nop2 EOR z LSR z LSR z EOR z 5x nop2 EOR z,X LSR z,X LSR z,X EOR z,X 6x nop2 ADC z ROR z ROR z ADC z 7x nop2 ADC z,X ROR z,X ROR z,X ADC z,X 8x STY z STA z STX z A&X --> z 9x STY z,X STA z,X STX z,Y A&X --> z,Y Ax LDY z LDA z LDX z LDX z LDA z Bx LDY z,X LDA z,X LDX z,Y LDX z,Y LDA z,Y Cx CPY z CMP z DEC z DEC z CMP z Dx nop2 CMP z,X DEC z,X DEC z,X CMP z,X Ex CPX z SBC z INC z INC z SBC z Fx nop2 SBC z,X INC z,X INC z,X SBC z,X |
x8 x9 xA xB ----------------------------------------------------------- 0x PHP ORA #v ASL AND #v 1x CLC ORA a,Y nop ASL a,Y ORA a,Y 2x PLP AND #v ROL AND #v 3x SEC AND a,Y nop ROL a,Y AND a,Y 4x PHA EOR #v LSR AND #v LSR 5x CLI EOR a,Y nop LSR a,Y EOR a,Y 6x PLA ADC #v ROR AND #v ROR 7x SEI ADC a,Y nop ROR a,Y ADC a,Y 8x DEY nop2 TXA #v&X --> A 9x TYA STA a,Y TXS A&X-->S S&hea+1 --> a,Y Ax TAY LDA #v TAX LDA #v TAX Bx CLV LDA a,Y TSX a,Y & S -->AXS Cx INY CMP #v DEX A&X-#v --> X Dx CLD CMP a,Y nop DEC a,Y CMP a,Y Ex INX SBC #v NOP SBC #v Fx SED SBC a,Y nop INC a,Y SBC a,Y |
xC xD xE xF ----------------------------------------------------------- 0x nop3 ORA a ASL a ASL a ORA a 1x nop3 ORA a,X ASL a,X ASL a,X ORA a,X 2x BIT a AND a ROL a ROL a AND a 3x nop3 AND a,X ROL a,X ROL a,X AND a,X 4x JMP a EOR a LSR a LSR a EOR a 5x nop3 EOR a,X LSR a,X LSR a,X EOR a,X 6x JMP (a) ADC a ROR a ROR a ADC a 7x nop3 ADC a,X ROR a,X ROR a,X ADC a,X 8x STY a STA a STX a A&X --> a 9x nop3 STA a,X X&hea+1 A&X --> a,Y --> a,X Ax LDY a LDA a LDX a LDX a LDA a Bx LDY a,X LDA a,X LDX a,Y LDX a,Y LDA a,Y Cx CPY a CMP a DEC a DEC a CMP a Dx nop3 CMP a,X DEC a,X DEC a,X CMP a,X Ex CPX a SBC a INC a INC a SBC a Fx nop3 SBC a,X INC a,X INC a,X SBC a,X |
Key to the instruction table | |
A A-register (Accumulator) S S-register (Stack Pointer) X X-register Y Y-register a 2-byte absolute address r 1-byte relative address v 1-byte immediate value z 1-byte pagezero address --> "result is stored in" | hea high-byte of effective address 93: the byte at z+1 9B: 3rd byte of instruction 9E: 3rd byte of instruction & and-function (logical product) hang computer hangs up, only way to regain control is to hit RESET nop 1-byte instruction, no operation nop2 2-byte instruction, no operation nop3 3-byte instruction, no operation |
I wanted to combine the EDITASM code by Mike Laumer and Lee Meador's COPY command. But to do so I really needed the extra memory available in the RAM card. Here is how I did it.
Assembler Modifications:
1) Address $101C, which originally held a JSR $1F80, now does a JSR $24CC, whee Bank #1 of the language card is turned on with a "dummy" LDY $C088, then the JSR $1F80 is performed and finally a RTS returns control to $101F where the initialization routine continues.
2) Address $1063 (modified from "b.EDIT2") does a JMP $24D3, where the subroutines "NEW.NML" and "MY.NM>" from "B.EDIT2" now reside.
3) Address $1079 does a JMP now instead of a JSR (no change from "B.EDIT2")
4) Address $1125 is now a RTS (no change from "B.EDIT2") I added the two NOPs at $1126 and $1127, because I don't like to leave "strange" code dangling.
5) Address $1246 now contains COPy instead of LOAd as a command and its associated address directs it to $d000, 1 byte before the COPY routine at $D001. The reason that COPY doesn't start at $D000 is obscure and warrants an explanation. If it were to start at $D000, the command table address would be $CFFF, the mere referencing of which is a signal to the APPLE II its peripheral cards! If an 80-column card were in use, for example, it could be turned off each time you tried to do a COPY!
6) Address $126E (incorrectly identified as $2746 in Lee Meador's article, AAL 1/81), now contains EDIt instead of SAVe. It points to $D13B, 1 byte before the EDITASM routine at $D13C. Thanks, Lee, for the terrific idea!
7) Addresses $24D3-$24E3 now contain code which resided at $0824-$0834 in "B.EDIT2".
8) Addresses $24E4-$24EA now contain code which resided at $0835-$083B in "B.EDIT2".
9) I changed Mike Laumer's <ctrl E> to <ctrl N>, to match Neil Konzen's PLE convention.
10) I also wanted an "instant recall" feature for the "last line edited". Typing EDIT without any argument works exactly as described by Mike, UNTIL a line has been edited. Then typing EDIT will cause that last edited line to be re-displayed. It doesn't "forget" that line # until a new line # is used as an EDIt argument.
SO...I have both COPY and EDIT capability, with all of the normal RAM space still available for assembled code! There's plenty of space left on the language card for future expansion (although there's only about $10 bytes unused in the $24EB area!). Well aren't you excited? I sure was! - what are you waiting for? Get coding!!
B.EDIT2 Modifications:
1) Change the following lines:
1060 .OR $D13C 1070 .TF EDITASM A$D13C Please use this name! |
2) Delete lines 1360-1700
3) Add the following lines:
1360 NEW.NML .EQ $24D3 1370 MY.NML .EQ $24D9 1380 NEXT .EQ $D4E4,$D4E5 1390 END .EQ $24E6,$24E7 1400 CHAR .EQ $24E8 1410 EDPTR .EQ $24E9 1420 FKEY .EQ $24EA |
4) If you want EDIT to provide "instant recall," change line 1740:
1740 BMI .4 YES, .4 ! |
5) If you want <ctrl N> as "go to end of line command", change line 4330:
4330 .DA #$8E, E.END-1 |
6) SAVE EDITASM.SOURCE (or any name)
7) ASM the code
COPY Modifications:
1) Add the following lines:
1292 .OR $D001 1294 .TF BMC A$D001 Please use this name! |
2) SAVE BLOCK MOVE/COPY.SOURCE (or any name)
3) ASM the code
Now...key in "Modifier EXEC Maker" below. If you have an APPLE II Plus, delete lines 40, 45, and 50. SAVE it, then RUN it to create the textfile "ASMDISK 4.0 MODIFIER". Now EXEC the file which will do all the hard work! I have my "Hello" program do the EXECuting! Good luck!
Program to make a file "ASMDISK 4.0 MODIFIER":
1 REM THIS PROGRAM MAKES A FILE 2 REM 'ASMDISK 4.0 MODIFIER' 3 REM WHICH IS THEN EXECUTABLE 4 REM CREATED 1/10/81 5 REM BY 6 REM C. J. WELMAN 7 REM SANTA ANA CA 92707 10 D$ = CHR$(4) 20 PRINT D$;"OPEN ASMDISK 4.0 MODIFIER" 30 PRINT D$;"WRITE ASMDISK 4.0 MODIFIER" 40 REM GET INTO MAIN BOARD ROM--OTHERWISE ASSEMBLER 45 REM BOMBS WHEN CALLED 50 PRINT "INT" 60 REM TURN ON 'MON' TO SEE ACTION 70 PRINT "MON C,I,O" 80 REM BLOAD THE ASSEMBLER 90 PRINT "BLOAD ASMDISK 4.0" 100 REM GET INTO THE MONITOR 110 PRINT "CALL-151" 120 REM UNLOCK BANK 1 OF LANG CARD 130 PRINT "C089" 140 PRINT "C089" 150 REM BLOAD BLOCK MOVE & COPY 160 PRINT "BLOAD BMC A$D001,A$D001" 170 REM BLOAD EDITASM 180 PRINT "BLOAD EDITASM A$D13C,A$DI3C" 190 REM WRITE PROTECT LANG CARD 200 PRINT "C08A" 210 REM ADD ROUTINE TO TURN ON BANK 1 OF LANGUAGE 215 REM CARD EACH TIME $1010 IS CALLED 220 PRINT "101C:20 CC 24" 230 PRINT "24CC:AC 88 C0 20 80 1F 60" 240 REM MODIFY ASSEMBLER TO ADD 'NEW.NML'/'MY.NML' 242 REM ROUTINES & LOCAL VARIABLES FROM 'B.EDIT2' 244 REM (NOT LOCATED THERE ANY MORE) 250 PRINT "24D3:20 D9 24 4C 26 10 A0 00 20 8D 12 20 4A 11" 260 PRINT "24E1:4C 66 10 00 00 00 00 00 00 00" 270 REM PATCH ASSEMBLER 'NML' SECTION 280 PRINT "1063:4C D3 24" 290 PRINT "1078:4C" 300 PRINT "1125:60 EA EA" 310 REM MODIFY ASSEMBLER COMMAND TABLE TO REPLACE 315 REM 'LOAD' WITH 'COPY' & 'SAVE' WITH 'EDIT' 320 PRINT "1246:43 4F 50 00 D0" 330 PRINT "126E:45 44 49 3B D1" 340 REM MODIFY ASSEMBLER TO ADD '.DA WITH COMMA" 350 PRINT "20D4:4C B0 24" 360 PRINT "20D7:4C C7 24" 370 PRINT "20DA:4C B5 24" 380 PRINT "24B0:A5 DB 20 FA 19 A5 DC 20 FA 19 20 8B 12 C9 2C" 390 PRINT "24BF:F0 03 4C 8E 18 4C B5 20 A5 DB 18 90 EB" 400 REM PATCH 'PRT' VECTOR TO POINT TO SYMBOL TABLE AT $1E4E 410 PRINT "1009:4C 4E 1E" 420 REM TURN OF 'MON' NOW 430 PRINT "NOMON C,I,O" 440 REM JUMP TO ASSEMBLER 450 PRINT "1000G" 460 PRINT D$;"CLOSE ASMDISK 4.0 MODIFIER" |
Here is my HELLO program:
10 D$ = "": REM BLIND <CTRL-D> 20 TEXT : CALL - 936 30 PRINT "S-C ASSEMBLER II VERSION 4.0" 40 PRINT "---------------------------------" 50 PRINT "S-C SOFTWARE COPYRIGHT 7--9-80" 60 VTAB 4 70 PRINT D$;"EXEC ASMDISK 4.0 MODIFIER" 80 END |
I promised in the original AAL flyer that I would print dis-assemblies of things like DOS. Here is the first installment. RWTS is described in some detail in the DOS Reference Manual, pages 94-98.
There are not too many differences between the various versions of RWTS. Each one, from 3.1 to 3.2 to 3.2.1 to 3.3, seems mainly to clean up errors of the previous ones. I will probably print some DOS 3.3 listings in the future, as well as more of 3.2.1.
There is a bug in the 3.2.1 version (a bad address), at line 2200. It works anyway, but it is sloppy. Another problem I have discovered the hard way: the "previous slot #" in the IOB should be a slot that has a disk controller in it. If not, RWTS may do strange things to whatever is in that slot. I put in "0", and it turned on my language card! Zap! No more Applesoft!
1000 * .LIF 1010 *--------------------------------- 1020 * DOS 3.2.1 DISASSEMBLY $BD00-BE9F 1030 * BOB SANDER-CEDERLOF 3-3-81 1040 *--------------------------------- 1050 CURRENT.TRACK .EQ $478 1060 DRIVE.1.TRACK .EQ $478 THRU 47F (INDEX BY SLOT) 1070 DRIVE.2.TRACK .EQ $4F8 THRU 4FF (INDEX BY SLOT) 1080 SEARCH.COUNT .EQ $4F8 1090 RETRY.COUNT .EQ $578 1100 SLOT .EQ $5F8 1110 SEEK.COUNT .EQ $6F8 1120 *--------------------------------- 1130 PHASE.OFF .EQ $C080 1140 PHASE.ON .EQ $C081 1150 MOTOR.OFF .EQ $C088 1160 MOTOR.ON .EQ $C089 1170 ENABLE.DRIVE.1 .EQ $C08A 1180 ENABLE.DRIVE.2 .EQ $C08B 1190 Q6L .EQ $C08C 1200 Q6H .EQ $C08D 1210 Q7L .EQ $C08E 1220 Q7H .EQ $C08F 1230 *--------------------------------- 1240 SECTOR .EQ $2D 1250 TRACK .EQ $2E 1260 VOLUME .EQ $2F 1270 DRIVE.NO .EQ $35 1280 DCT.PNTR .EQ $3C,3D 1290 BUF.PNTR .EQ $3E,3F 1300 MOTOR.TIME .EQ $46,47 1310 IOB.PNTR .EQ $48,49 1320 *--------------------------------- 1330 PRE.NYBBLE .EQ $B800 1340 WRITE.SECTOR .EQ $B86A 1350 READ.SECTOR .EQ $B8FD 1360 READ.ADDRESS .EQ $B965 1370 POST.NYBBLE .EQ $B9C1 1380 SEEK.TRACK.ABSOLUTE .EQ $BA1E 1390 *--------------------------------- 1400 ERR.WRITE.PROTECT .EQ $10 1410 ERR.WRONG.VOLUME .EQ $20 1420 ERR.BAD.DRIVE .EQ $40 1430 *--------------------------------- 1440 .OR $BD00 1450 .TA $800 1460 *--------------------------------- 1470 RWTS STY IOB.PNTR SAVE ADDRESS OF IOB 1480 STA IOB.PNTR+1 1490 LDY #2 1500 STY SEEK.COUNT UP TO 2 RE-CALIBRATIONS 1510 LDY #4 1520 STY SEARCH.COUNT 1530 LDY #1 POINT AT SLOT# IN IOB 1540 LDA (IOB.PNTR),Y SLOT# FOR THIS OPERATION 1550 TAX 1560 LDY #15 POINT AT PREVIOUS SLOT# 1570 CMP (IOB.PNTR),Y SAME SLOT? 1580 BEQ .3 YES 1590 TXA SAVE NEW SLOT ON STACK 1600 PHA 1610 LDA (IOB.PNTR),Y GET OLD SLOT# 1620 TAX 1630 PLA STORE NEW SLOT # 1640 PHA INTO OLD SLOT# SPOT 1650 STA (IOB.PNTR),Y 1660 *--------------------------------- 1670 * SEE IF OLD MOTOR STILL SPINNING 1680 *--------------------------------- 1690 LDA Q7L,X GO INTO READ MODE 1700 .1 LDY #8 IF DATA DOES NOT CHANGE 1710 LDA Q6L,X FOR 96 MICROSECONDS, 1720 .2 CMP Q6L,X THEN THE DRIVE IS STOPPED 1730 BNE .1 WOOPS! IT CHANGED! 1740 DEY TIME UP YET? 1750 BNE .2 NO, KEEP CHECKING 1760 PLA GET NEW SLOT # AGAIN 1770 TAX 1780 *--------------------------------- 1790 .3 LDA Q7L,X SET UP TO READ 1800 LDA Q6L,X 1810 LDA Q6L,X GET CURRENT DATA 1820 PHA 7 CYCLE DELAY 1830 PLA 1840 STX SLOT 1850 CMP Q6L,X SEE IF DATA CHANGED 1860 PHP SAVE ANSWER ON STACK 1870 LDA MOTOR.ON,X TURN ON MOTOR 1880 LDY #6 COPY POINTERS INTO PAGE ZERO 1890 .4 LDA (IOB.PNTR),Y 1900 STA DCT.PNTR-6,Y 1910 INY DCT.PNTR .EQ $3C,3D 1920 CPY #10 BUF.PNTR .EQ $3E,3F 1930 BNE .4 1940 LDY #3 GET MOTOR ON TIME FROM DCT 1950 LDA (DCT.PNTR),Y 1960 STA MOTOR.TIME+1 HIGH BYTE ONLY 1970 LDY #2 GET DRIVE # 1980 LDA (IOB.PNTR),Y 1990 LDY #16 SEE IF SAME AS OLD DRIVE# 2000 CMP (IOB.PNTR),Y 2010 BEQ .5 YES 2020 STA (IOB.PNTR),Y UPDATE OLD DRIVE # 2030 PLP SET Z STATUS 2040 LDY #0 TO FLAG MOTOR OFF 2050 PHP 2060 .5 ROR CHECK LSB OF DRIVE # 2070 BCC .6 DRIVE 2 2080 LDA ENABLE.DRIVE.1,X 2090 BCS .7 ...ALWAYS 2100 .6 LDA ENABLE.DRIVE.2,X 2110 .7 ROR DRIVE.NO SET SIGN BIT IF DRIVE 1 2120 PLP WAS MOTOR PROBABLY OFF? 2130 PHP 2140 BNE .9 NO, DEFINITELY ON 2150 *--------------------------------- 2160 * DELAY FROM 150 TO 180 MILLISECONDS, 2170 * DEPENDING ON WHAT GARBAGE IS IN A-REG 2180 *--------------------------------- 2190 LDY #7 YES, WAIT A WHILE 2200 .8 JSR $BA7F ***BUG!!!*** SHOULD BE $BA7B 2210 DEY BUT IT WORKS ANYWAY.... 2220 BNE .8 2230 LDX SLOT RESTORE SLOT# 2240 *--------------------------------- 2250 .9 LDY #4 GET TRACK # 2260 LDA (IOB.PNTR),Y 2270 JSR SEEK.TRACK 2280 PLP WAS MOTOR DEFINITELY ON? 2290 BNE PROCESS.COMMAND YES, MOTOR ON 2300 *--------------------------------- 2310 * MOTOR WAS OFF, SO WAIT REST OF MOTOR ON TIME 2320 * FOR APPLE DISK II, MOTOR ON TIME IS 1 SECOND. 2330 * PART OF THIS TIME IS COUNTED DOWN WHILE SEEKING 2340 * FOR THE TRACK. 2350 *--------------------------------- 2360 .10 LDY #18 ABOUT 100 MICROSECONDS PER TRIP 2370 .11 DEY 2380 BNE .11 2390 INC MOTOR.TIME 2400 BNE .10 2410 INC MOTOR.TIME+1 2420 BNE .10 2430 *--------------------------------- 2440 * MOTOR ON AND UP TO SPEED, SO LET'S 2450 * FIND OUT WHAT THE COMMAND IS AND DO IT! 2460 *--------------------------------- 2470 PROCESS.COMMAND 2480 LDY #12 GET COMMAND 2490 LDA (IOB.PNTR),Y 2500 BEQ .8 NULL COMMAND, LET'S LEAVE 2510 CMP #4 FORMAT? 2520 BEQ .9 YES 2530 ROR SET CARRY=1 IF READ, =0 IF WRITE 2540 PHP SAVE ON STACK 2550 BCS .1 READ 2560 JSR PRE.NYBBLE WRITE 2570 .1 LDY #48 UP TO 48 RETRIES 2580 STY RETRY.COUNT 2590 .2 LDX SLOT GET SLOT NUMBER AGAIN 2600 JSR READ.ADDRESS 2610 BCC .5 GOOD ADDRESS READ 2620 .21 DEC RETRY.COUNT 2630 BPL .2 KEEP TRYING 2640 .3 LDA CURRENT.TRACK GET TRACK WE WANTED 2650 PHA SAVE IT 2660 LDA #96 PRETEND TO BE ON TRACK 96 2670 JSR SETUP.TRACK 2680 DEC SEEK.COUNT 2690 BEQ .6 NO MORE RE-CALIBRATES 2700 LDA #4 2710 STA SEARCH.COUNT 2720 LDA #0 LOOK FOR TRACK 0 2730 JSR SEEK.TRACK 2740 PLA GET TRACK WE REALLY WANT 2750 .4 JSR SEEK.TRACK 2760 JMP .1 2770 *--------------------------------- 2780 .5 LDY $2E TRACK# IN ADDRESS HEADER 2790 CPY CURRENT.TRACK 2800 BEQ .10 FOUND RIGHT TRACK 2810 LDA CURRENT.TRACK 2820 PHA SAVE TRACK WE REALLY WANT 2830 TYA SET UP TRACK WE ACTUALLY FOUNG 2840 JSR SETUP.TRACK 2850 PLA TRACK WE WANT 2860 DEC SEARCH.COUNT 2870 BNE .4 TRY AGAIN 2880 BEQ .3 TRY TO RE-CALIBRATE AGAIN 2890 *--------------------------------- 2900 * DRIVE ERROR, CANNOT FIND TRACK 2910 *--------------------------------- 2920 .6 PLA REMOVE CURRENT.TRACK 2930 LDA #ERR.BAD.DRIVE 2940 .7 PLP 2950 JMP ERROR.HANDLER 2960 *--------------------------------- 2970 * NULL COMMAND, ON THE WAY OUT.... 2980 *--------------------------------- 2990 .8 BEQ RWTS.EXIT 3000 *--------------------------------- 3010 * FORMAT COMMAND 3020 *--------------------------------- 3030 .9 LDY #3 GET VOLUME# WANTED 3040 LDA (IOB.PNTR),Y 3050 STA VOLUME SET IN PLACE AND GO FORMAT 3060 JMP FORMAT 3070 *--------------------------------- 3080 * READ OR WRITE COMMAND 3090 *--------------------------------- 3100 .10 LDY #3 GET VOLUME# WANTED 3110 LDA (IOB.PNTR),Y 3120 PHA SAVE DESIRED VOLUME# ON STACK 3130 LDA VOLUME 3140 LDY #14 STORE ACTUAL VOLUME NUMBER FOUND 3150 STA (IOB.PNTR),Y 3160 PLA GET DESIRED VOLUME# AGAIN 3170 BEQ .11 IF =0, DON'T CARE 3180 CMP VOLUME SEE IF RIGHT VOLUME 3190 BEQ .11 YES 3200 LDA #ERR.WRONG.VOLUME 3210 BNE .7 UH OH! 3220 *--------------------------------- 3230 .11 LDY #5 GET SECTOR# WANTED 3240 LDA SECTOR AND THE ONE WE FOUND 3250 CMP (IOB.PNTR),Y AND COMPARE THEM. 3260 BNE .21 NOT THE RIGHT SECTOR 3270 PLP GET COMMAND FLAG AGAIN 3280 BCC WRITE 3290 JSR READ.SECTOR 3300 PHP SAVE RESULT; IF BAD, WILL BE COMMAND 3310 BCS .21 BAD READ 3320 PLP THROW AWAY 3330 JSR POST.NYBBLE 3340 LDX SLOT 3350 RWTS.EXIT 3360 CLC 3370 .HS 24 "BIT" TO SKIP NEXT INSTRUCTION 3380 *--------------------------------- 3390 ERROR.HANDLER 3400 SEC INDICATE AN ERROR 3410 LDY #13 STORE ERROR CODE 3420 STA (IOB.PNTR),Y 3430 LDA MOTOR.OFF,X 3440 RTS 3450 *--------------------------------- 3460 WRITE JSR WRITE.SECTOR 3470 BCC RWTS.EXIT 3480 LDA #ERR.WRITE.PROTECT 3490 BCS ERROR.HANDLER ...ALWAYS 3500 *--------------------------------- 3510 * SEEK TRACK SUBROUTINE 3520 * (A) = TRACK# TO SEEK 3530 * (DRIVE.NO) IS NEGATIVE IF DRIVE 1 3540 * AND POSITIVE IF DRIVE 2 3550 *--------------------------------- 3560 SEEK.TRACK 3570 PHA SAVE TRACK# 3580 LDY #1 CHECK DEVICE CHARACTERISTICS TABLE 3590 LDA (DCT.PNTR),Y FOR TYPE OF DISK 3600 ROR SET CARRY IF TWO PHASES PER TRACK 3610 PLA GET TRACK# AGAIN 3620 BCC .1 ONE PHASE PER TRACK 3630 ASL TWO PHASES PER TRACK, SO DOUBLE IT 3640 JSR .1 FIND THE TRACK 3650 LSR CURRENT.TRACK DIVIDE IT BACK DOWN 3660 RTS 3670 *--------------------------------- 3680 .1 STA TRACK 3690 JSR GET.SLOT.IN.Y 3700 LDA DRIVE.1.TRACK,Y 3710 BIT DRIVE.NO WHICH DRIVE? 3720 BMI .2 DRIVE 1 3730 LDA DRIVE.2.TRACK,Y 3740 .2 STA CURRENT.TRACK WHERE WE ARE RIGHT NOW 3750 LDA TRACK WHERE WE WANT TO BE 3760 BIT DRIVE.NO WHICH DRIVE? 3770 BMI .3 DRIVE 1 3780 STA DRIVE.2.TRACK,Y DRIVE 2 3790 BPL .4 ...ALWAYS 3800 .3 STA DRIVE.1.TRACK,Y 3810 .4 JMP SEEK.TRACK.ABSOLUTE 3820 *--------------------------------- 3830 * CONVERT SLOT*16 TO SLOT IN Y-REG 3840 *--------------------------------- 3850 GET.SLOT.IN.Y 3860 TXA SLOT*16 FROM X-REG 3870 LSR 3880 LSR 3890 LSR 3900 LSR 3910 TAY SLOT INTO Y 3920 RTS 3930 *--------------------------------- 3940 * SET UP CURRENT TRACK LOCATION 3950 * IN DRIVE.1.TRACK OR DRIVE.2.TRACK VECTORS, 3960 * INDEXED BY SLOT NUMBER. 3970 * 3980 * (A) = TRACK# TO BE SET UP 3990 *--------------------------------- 4000 SETUP.TRACK 4010 PHA SAVE TRACK # WE WANT TO SET UP 4020 LDY #2 GET DRIVE NUMBER FROM IOB 4030 LDA (IOB.PNTR),Y 4040 ROR SET CARRY IF DRIVE 1, CLEAR IF 2 4050 ROR DRIVE.NO MAKE NEGATIVE IF 1, POSITIVE IF 2 4060 JSR GET.SLOT.IN.Y 4070 PLA GET TRACK # 4080 ASL DOUBLE IT 4090 BIT DRIVE.NO WHICH DRIVE? 4100 BMI .1 DRIVE 1 4110 STA DRIVE.2.TRACK,Y 4120 BPL .2 ...ALWAYS 4130 .1 STA DRIVE.1.TRACK,Y 4140 .2 RTS 4150 *--------------------------------- 4160 FORMAT |
Here is yet another way to add new commands to Version 4.0. You are somewhat familiar with the use of the & in Applesoft. This little program patches the assembler so that you can add as many new commands as you wish.
I have shown as examples the EDIT, COPY, and SYM commands. You need to fill in the correct starting address in lines 1250 and 1260.
Use the .TF directive to direct the object code to a file. Then use BRUN to install the patch. Lines 1100-1120 patch the assembler to hook in the code at lines 3010-3100. After it is hooked in, make a new copy of the assembler by using BSAVE ASMDISK 4.0 WITH &,A$FD7,L$.... (Fill in the appropriate length, depending on what else you have added to the assembler in the past.)
1000 *--------------------------------- 1010 * & COMMAND INTERFACE 1020 * 1030 * &<COMMAND STRING> 1040 * 1050 *--------------------------------- 1060 * 1070 * ORIGIN MUST BE SET SO THAT LAST BYTE 1080 * IS AT $0FFF. 1085 .OR $FD1 1090 *--------------------------------- 1100 LDA #AMPERSAND.INTERFACE-$103D 1110 STA $103C 1120 RTS 1130 *--------------------------------- 1140 JMP $1000 1150 *--------------------------------- 1160 AOPTBL .HS 0503 1170 .AS /EDI/ 1180 .DA EDIT-1 1190 .AS /COP/ 1200 .DA COPY-1 1210 .AS /SYM/ 1220 .DA STPRNT-1 1230 .HS 00 END OF TABLE 1240 *--------------------------------- 1250 EDIT .EQ $1010 1260 COPY .EQ $1010 1270 STPRNT .EQ $1E4E 3000 *--------------------------------- 3010 AMPERSAND.INTERFACE 3020 CMP #'& 3030 BEQ .1 3040 JMP $1063 3050 .1 LDA #AOPTBL 3060 STA $02 3070 LDA /AOPTBL 3080 STA $03 3090 LDA #1 3100 JMP $1047 |