Apple Assembly Line
Volume 6 -- Issue 10July 1986

In This Issue...

65816 Books

65816/65802 Assembly Language Programming is here! We have Michael Fischer's book in stock and have filled all the back orders for the first substantial book on programming the 658xx chips. We can now ship your copy immediately, for only $18 + shipping, so give us a call.

Simon & Schuster reports that Programming the 65816, by David Eyes, will be ready to ship in about two weeks. This time it sounds believable, so next month you should be reading another notice like the one above. Our thanks to all of you who have been so patiently waiting for this long-delayed title.

A New Debugger

You may have noticed the ads in a couple of recent issues of AAL for a debugger called Quick Tracy. This program proved to be just the thing Bill needed while working on the UniDisk boot program in this issue. Quick Tracy co-resides with the DOS Version 2.0 S-C Macro Assembler, taking up 2K of memory, and you enter all the debugging commands directly from the assembler. You can single-step or trace through your code and set breakpoints on any register's contents (including PC and Stack) or the state of any status flag. This is a useful utility for only $35. Contact Eric Trehus at (408) 379-0563.


New ProDOS Program SelectorBob Sander-Cederlof

In the November 1985 issue of Apple Assembly Line I printed a complete commented listing of the ProDOS QUIT code. This code resides at $D100-D3FF inside ProDOS, and is downloaded to $1000 and executed by the $65 MLI call.

The BYE command in BASIC.SYSTEM and SCASM.SYSTEM both call ProDOS MLI with call number $65, and so do many other system programs. For some reason FILER has its own quit code, which operates slightly differently from MLI-$65, but not really better. No one seems to particularly like MLI-$65, but they usually learn to live with it. That is, unless they purchase Catalyst, MouseFiler, or one of the other commercially-available ProDOS program selectors.

Not wanting to buy three or four different program selectors until I found one I liked, I decided to try writing my own. It replaces the standard QUIT code inside ProDOS, so that MLI-$65 downloads and executes my new code. My program first lists all of the on-line volume names, so that you can select a volume. You perform the selection by moving the cursor-bar with the arrow keys, and pressing RETURN. ESCAPE makes the program re-do the list of volume names, in case you want to change diskettes. Once a volume is selected, all of the system (SYS) and directory (DIR) filenames in that volume will be listed. Again, you use the arrow keys and RETURN to select either a system program to be executed, or a sub-directory to display. Just a few quick keystrokes and you are in a new application!

Here is an example of the volume name display:

       S/D  VOLUME NAME

       3/2  RAM
       7/1  HARD1
       7/2  HARD2
       6/1  UTILITIES

       USE ARROWS AND <RETURN> TO SELECT
       USE <ESCAPE> TO TRY AGAIN

And here is an example of a filename display:

       /HARD1

       SYS -- PRODOS
       SYS -- SCASM.SYSTEM
       SYS -- BASIC.SYSTEM
       SYS -- CONVERT
       SYS -- UTIL.SYSTEM
       DIR -- ASM1
       DIR -- ASM2
       DIR -- SCI
       DIR -- FSE
       DIR -- XREF
       DIR -- SCWP
       DIR -- TIMEMASTER
       DIR -- THUNDERCLOCK
       DIR -- PHASOR
       DIR -- MINTERMS
       DIR -- DP18
       <<<MORE>>>

       USE ARROWS AND <RETURN> TO SELECT
       USE <ESCAPE> TO TRY AGAIN

All of the SYS files are listed first, and then all of the DIR files, regardless of the order within the directory. This makes it easier to find the file you are looking for. If there are more than 16 filenames to display, the first 16 will be listed, followed by the word "<<<MORE>>>". When you use the arrow keys to move beyond the bottom of the list, if there are more filenames, the list will scroll up to make room for the next name on the screen. When the top name listed is not the first name in the list, the word "<<<MORE>>>" will be displayed above the list. Actually, it is easier to use than it is to describe.

I have gotten so used to an 80-column display now that I decided to make the menu in that mode. Lines 1425-1430 initialize the 80-column display for an enhanced Apple //e or //c. If you want to use some other configuration, or just like 40-columns better, replace those two lines with the following:

       1421    JSR $FE93
       1422    JSR $FE89
       1423    STA $C00C
       1424    STA $C00F
       1425    STA $C000
       1426 .2 JSR HOME

The six lines above make QUITTER a little too long to fit in three pages, so you need to make room for it somehow. I suggest putting the variables from lines 4870-4950 into page zero, say at $06-$0E. This will make the code assemble shorter, so it still fits between $D100 and $D3FF inside ProDOS.

An alternative is to make a further modification to ProDOS. The subroutine which downloads the QUIT code is at $FCE5-FD3A inside ProDOS. It is very inefficient, so there is ample room for adding features. However, by merely changing the LDX #3 at $FD06 to LDX #4, you can make it download four pages instead of three. When you BLOAD PRODOS at $2000, the LDX #3 is found at $4C06. Since the QUIT code is at the end of the PRODOS file, you can write a longer QUIT program if you wish. You also need to change the $03 at $2233 to $04, so that the boot code will install QUIT where it belongs.

Walking through the New QUITTER

The comments in lines 1010-1090 explain how to install the new QUITTER inside the PRODOS system file. Just in case there is an error, I recommend you try this first on a disk you can afford to lose. It all works here, but there's many a slip 'twixt the cup and the lip!

Line 1320 switches on the motherboard ROM code, so that we can use Apple monitor routines. Lines 1330-1410 clear out the memory bitmap in the ProDOS System Global Page. We have to do that so we can load another system file. Once the bitmap has been cleared, it is not safe to try to return to whatever system program was operating before QUITTER was entered. Anyway, the RESET vector has already been pointed at QUITTER, so it is pretty difficult to get out of QUITTER. If you wish, you could add a feature that allows aborting the QUIT call, but be aware that the memory bitmap will have been messed up.

Lines 1420-1590 display a list of all the volumes currently on-line, and allow you to move the cursor bar up and down the list. The subroutine DISPLAY.VOLUMES lists the volume names, displaying the one under the cursor in inverse mode. The subroutine GET.KEY accepts the four arrow keys, RETURN, and ESCAPE. The left and up arrows move the cursor bar up, while the right and down arrows move the cursor bar down. GET.KEY is a little complicated, since it also handles windowing for long lists of filenames.

The subroutine READ.THE.FILE, called from line 1610, reads in an entire volume directory or sub-directory. ProDOS has the built-in ability to read directories just as though they were regular files, so READ.THE.FILE is pretty simple: it merely OPENs the file, READs it, and CLOSEs it. Lines 2030-2150 perform the additional task of appending the current volume or filename to the previous prefix.

Lines 1640-1710 clear the screen and display the pathname of the selected directory, in preparation for display a file menu. Lines 1720-1800 collect a list of pointers to all of the SYS and DIR files in the directory, using the SCAN.DIRECTORY subroutine. SCAN.DIRECTORY appends a pointer to a list of pointers in DIRBUF for each file it finds of the specified type.

Lines 1810-1900 display the SYS and DIR files found in the directory. If there are more than 16 files, the word "<<<MORE>>>" will be displayed after the 16th name. Moving the cursor bar down will scroll the list up, so that you can see the rest of the filenames. If you press ESCAPE or RESET, it all starts over collecting volume names. If you press RETURN when the cursor bar is on a DIR file, the directory name will be added to the current prefix and a new filename list will appear.

If you press RETURN when the cursor bar is on a SYS file, lines 1950-1990 will load the system file and start it running. Lines 1950-1960 set the system prefix to the directory the system file is in. Lines 1970-1980 read the file into RAM starting at $2000, and if there are no errors we blast-off with a JMP $2000. If there ARE errors, the program just starts over.

  1000 *SAVE NEW.QUIT.CODE
  1010 *--------------------------------
  1020 *    Installation:
  1030 *      1.  BLOAD PRODOS,TSYS,A$2000
  1040 *      2.  BLOAD B.NEW.QUITTER,A$5700
  1050 *      3.  BSAVE PRODOS,TSYS,A$2000,L$3A00
  1060 *    Location:
  1070 *      In PRODOS file:   $5700-59FF
  1080 *      In ProDOS image:  $D100-D3FF
  1090 *      For execution:    $1000-12FF
  1100 *--------------------------------
  1110 *    Code which downloads the QUIT code resides at
  1120 *      $FCE5-FD3A.  This is loaded from $4BE5-4C3A.
  1130 *--------------------------------
  1140 BPNTR  .EQ $00,01
  1150 SPNTR  .EQ $02,03
  1160 DPNTR  .EQ $04,05
  1170 CV     .EQ $25
  1180 INVFLG .EQ $32
  1190 *--------------------------------
  1200 HOME   .EQ $FC58
  1210 CLREOL .EQ $FC9C
  1220 COUT   .EQ $FDED
  1230 CROUT  .EQ $FD8E
  1240 *--------------------------------
  1250 MLI    .EQ $BF00
  1260 BITMAP .EQ $BF58
  1270 *--------------------------------
  1280        .OR $1000
  1290        .TF B.NEW.QUITTER
  1300 *--------------------------------
  1310 QUITTER
  1320        LDA $C082    MOTHERBOARD ROMS
  1330        LDX #$16
  1340        LDA #0       PREPARE VIRGIN BITMAP
  1350 .1     STA BITMAP,X
  1360        DEX
  1370        BNE .1
  1380        INX          X=1, LOCKOUT $BF00 PAGE
  1390        STX BITMAP+$17
  1400        LDA #$CF
  1410        STA BITMAP
  1420 *---LIST VOLUME NAMES------------
  1425 .2     LDA #$99     CTRL-Y
  1430        JSR $C300    SET I/O HOOKS, 80-COL MODE, CLEAR SCREEN
  1440        LDY #Q.SDV
  1450        JSR MSG
  1460        JSR CLOSE.ALL.FILES
  1470        JSR MLI
  1480        .DA #$C5,ONLINE
  1490        LDY #0
  1500        STY MAX.DIRPNT
  1510        STY DIR.START
  1520        STY PATHNAME
  1530 .3     STY SEL.LINE
  1540        JSR DISPLAY.VOLUMES
  1550        LDY #Q.VHELP
  1560        JSR MSG
  1570        JSR GET.KEY
  1580        BCC .3       ...ARROW KEYS
  1590        BNE .2       ...ESCAPE KEY
  1600 *---READ DIRECTORY---------------
  1610 .4     JSR READ.THE.FILE
  1620        BCS .7
  1630 *---PRINT PATHNAME---------------
  1640        JSR HOME
  1650        LDY #0
  1660 .5     LDA PATHNAME+1,Y
  1670        ORA #$80
  1680        JSR COUT
  1690        INY
  1700        CPY PATHNAME
  1710        BCC .5
  1720 *---COLLECT FILENAMES------------
  1730        LDX #0
  1740        LDA #$FF          FIRST JUST "SYS" FILES
  1750        JSR SCAN.DIRECTORY
  1760        LDA #$0F          THEN JUST "DIR" FILES
  1770        JSR SCAN.DIRECTORY
  1771        TXA          SEE IF ANY FILES FOUND
  1772        BEQ .2       ...NO, BACK TO THE TOP
  1780        LDA #0       MARK END OF LIST
  1790        STA DIRBUF+256,X
  1800        STX MAX.DIRPNT
  1810 *---LIST THE FILENAMES-----------
  1820        TAY          Y=0
  1830        STY DIR.START
  1840 .6     STY SEL.LINE
  1850        JSR DISPLAY.FILES
  1860        LDY #Q.VHELP
  1870        JSR MSG
  1880        JSR GET.KEY
  1890        BCC .6       ...ARROW KEYS
  1900        BNE .2       ...ESCAPE KEY
  1910        LDY #$10
  1920        LDA (SPNTR),Y     GET FILE TYPE
  1930        BPL .4            DIRECTORY ($0F)
  1940 *---SYS FILE, LOAD & EXECUTE-----
  1950        JSR MLI      SET PREFIX
  1960        .DA #$C6,PATH
  1970        JSR READ.THE.FILE
  1980        BCS .7       ...ERROR IN READING
  1990        JMP BUFFER
  2000 .7     JMP QUITTER
  2010 *--------------------------------
  2020 READ.THE.FILE
  2030        LDY #0       APPEND CURRENTLY SELECTED NAME
  2040        LDA (SPNTR),Y     GET LENGTH OF NAME
  2050        AND #$0F
  2060        STA LENGTH
  2070        LDX PATHNAME      CURRENT LENGTH
  2080        LDA #'/'
  2090 .1     INX
  2100        INY
  2110        STA PATHNAME,X
  2120        LDA (SPNTR),Y
  2130        DEC LENGTH
  2140        BPL .1
  2150        STX PATHNAME
  2160        JSR MLI           OPEN THE FILE
  2170        .DA #$C8,OPEN
  2180        BCS RF.ERR
  2190        LDA O.REF         FILE REFERENCE NUMBER
  2200        STA R.REF
  2210        JSR MLI           READ THE WHOLE FILE
  2220        .DA #$CA,READ
  2221        BCC CLOSE.ALL.FILES
  2222        CMP #$4C          IS IT JUST EOF?
  2223        SEC
  2230        BNE RF.ERR        ...NO
  2240 CLOSE.ALL.FILES
  2250        JSR MLI           CLOSE THE FILE
  2260        .DA #$CC,CLOSE
  2270 RF.ERR RTS
  2280 *--------------------------------
  2290 SCAN.DIRECTORY
  2300        STA CURTYP        TYPE WE ARE COLLECTING
  2310        LDA #0            START WITH FIRST BLOCK
  2320 .1     STA CURBLK
  2330        LDA #BUFFER+4     FIRST 4 BYTES OF BLOCK SKIPPED
  2340        STA DPNTR
  2350        CLC               COMPUTE PAGE OF PNTR
  2360        LDA /BUFFER+4
  2370        ADC CURBLK
  2380        STA DPNTR+1
  2390        LDA ENTCNT
  2400        STA LENGTH
  2410 *--------------------------------
  2420 .2     LDY #0
  2430        LDA (DPNTR),Y
  2440        AND #$F0
  2450        BEQ .4       ...DELETED FILE
  2460        CMP #$E0     ...HEADER?
  2470        BCS .4       ...YES
  2480        LDY #$10
  2490        LDA (DPNTR),Y     LOOK AT FILE TYPE
  2500        CMP CURTYP
  2510        BNE .4            ...NOT CURRENT TYPE
  2520 *---DIR or SYS file--------------
  2530 .3     LDA DPNTR
  2540        STA DIRBUF,X
  2550        LDA DPNTR+1
  2560        STA DIRBUF+256,X
  2570        INX
  2580 *---ADVANCE TO NEXT ENTRY--------
  2590 .4     CLC
  2600        LDA DPNTR
  2610        ADC ENTLEN
  2620        STA DPNTR
  2630        BCC .5
  2640        INC DPNTR+1
  2650 .5     DEC LENGTH        AT END OF BLOCK YET?
  2660        BNE .2            ...NO, CONTINUE IN BLOCK
  2670        CLC
  2680        LDA CURBLK
  2690        ADC #2
  2700        CMP ACTLEN+1
  2710        BCC .1            ...YES, READ NEXT BLOCK
  2720 *--------------------------------
  2730        RTS
  2740 *--------------------------------
  2750 CLOSE  .DA #1,#0
  2760 ONLINE .DA #2,#0,BUFFER
  2770 OPEN   .DA #3,PATHNAME,OPNBUF
  2780 O.REF  .BS 1
  2790 READ   .DA #4
  2800 R.REF  .BS 1
  2810        .DA BUFFER,$9F00
  2820 ACTLEN .BS 2
  2830 PATH   .DA #1,PATHNAME
  2840 *--------------------------------
  2850 DISPLAY.VOLUMES
  2860        JSR SETUP.DISPLAY.LOOP
  2870        LDA #BUFFER
  2880        STA BPNTR
  2890        LDA /BUFFER
  2900        STA BPNTR+1
  2910 *--------------------------------
  2920 .1     LDY #0
  2930        LDA (BPNTR),Y
  2940        AND #$0F
  2950        BEQ .3       ...NO VOLUME HERE
  2960 *--------------------------------
  2970        JSR CHECK.FOR.SEL.LINE
  2980 *--------------------------------
  2990 .2     LDA (BPNTR),Y     GET UNIT NUMBER
  3000        LSR               ISOLATE SLOT NUMBER
  3010        LSR
  3020        LSR
  3030        LSR
  3040        AND #7
  3050        ORA #"0"
  3060        JSR COUT          PRINT SLOT NUMBER
  3070        LDA #"/"
  3080        JSR COUT
  3090        LDA (BPNTR),Y     GET UNIT NUMBER AGAIN
  3100        ASL               SET CARRY IF DRIVE 2
  3110        LDA #"1"          ASSUME DRIVE 1
  3120        ADC #0            CHANGE TO 2 IF TRUE
  3130        JSR COUT
  3140        LDA #" "     PRINT TWO SPACES
  3150        JSR COUT
  3160        JSR COUT
  3170        JSR PRINT.BPNTR.NAME
  3180 *--------------------------------
  3190 .3     CLC               POINT TO NEXT VOLUME NAME
  3200        LDA BPNTR
  3210        ADC #16
  3220        STA BPNTR
  3230        BCC .4
  3240        INC BPNTR+1
  3250 .4     DEC LENGTH        ANY MORE LEFT?
  3260        BNE .1            ...YES
  3270        RTS
  3280 *--------------------------------
  3290 PRINT.BPNTR.NAME
  3300        LDY #0
  3310        LDA (BPNTR),Y      GET NAME LENGTH
  3320        AND #$0F
  3330        TAX
  3340 .1     INY          PRINT THE VOLUME NAME
  3350        LDA (BPNTR),Y
  3360        ORA #$80
  3370        JSR COUT
  3380        DEX
  3390        BNE .1
  3400 *--------------------------------
  3410 .2     LDA #" "     PRINT TRAILING BLANKS
  3420        JSR COUT
  3430        INY
  3440        CPY #16
  3450        BCC .2
  3460        LDA #$FF     NORMAL MODE NOW
  3470        STA INVFLG
  3480        INC MAX.LINE      COUNT THE LINE
  3490        JMP CROUT
  3500 *--------------------------------
  3510 GET.KEY
  3520 .1     LDA $C000    READ KEY FROM KEYBOARD
  3530        BPL .1
  3540        STA $C010    CLEAR THE STROBE
  3550        CMP #$8D
  3560        BEQ .2       <RETURN>
  3570        CMP #$88     <--
  3580        BEQ .3
  3590        CMP #$95     -->
  3600        BEQ .7
  3610        CMP #$8A     DOWN ARROW
  3620        BEQ .7
  3630        CMP #$8B     UP ARROW
  3640        BEQ .3
  3650        CMP #$9B     ESCAPE
  3660        BNE .1       GET ANOTHER CHARACTER
  3670        LDA #$9B     ...SET .NE.
  3680 .2     RTS
  3690 *---<UP OR LEFT ARROW>-----------
  3700 .3     LDY SEL.LINE      CURRENT BRIGHT LINE
  3710        BNE .6            ...NOT TOP LINE
  3720        LDY DIR.START     ARE WE DISPLAYING THE FIRST ONE?
  3730        BEQ .5            ...YES
  3740        DEC DIR.START     ...NO, MOVE TOWARD FIRST LINE
  3750 .4     LDY #0            MAKE FIRST LINE BRIGHT
  3760        CLC
  3770        RTS
  3780 .5     LDY MAX.LINE      MAKE LAST LINE BRIGHT
  3790 .6     DEY
  3800        CLC
  3810        RTS
  3820 *---<DOWN OR RIGHT ARROW>--------
  3830 .7     LDY SEL.LINE      CURRENT BRIGHT LINE
  3840        INY               MOVE TOWARD LAST LINE
  3850        CPY MAX.LINE      BEYOND END OF SCREEN?
  3860        BCC .8            ...NO
  3870        LDA MAX.DIRPNT    ...YES, CHECK IF SHOWING LAST LINE
  3880        SBC #17
  3890        BCC .4            ...YES
  3900        CMP DIR.START
  3910        BCC .4            ...YES
  3920        INC DIR.START     ...NO, MOVE TOWARD LAST LINE
  3930        LDY SEL.LINE
  3940        CLC
  3950 .8     RTS
  3960 *--------------------------------
  3970 DISPLAY.FILES
  3980        JSR SETUP.DISPLAY.LOOP
  3990        LDA DIR.START
  4000        STA DIR.INDEX
  4010        JSR CLEAR.LINE.OR.PRINT.MORE.MSG
  4020 *--------------------------------
  4030 .1     LDX DIR.INDEX
  4040        LDY DIRBUF+256,X
  4050        BEQ .4       ...END OF LIST
  4060        STY BPNTR+1
  4070        LDA DIRBUF,X
  4080        STA BPNTR
  4090        JSR CHECK.FOR.SEL.LINE
  4100 *--------------------------------
  4110 .2     LDY #$10
  4120        LDA (BPNTR),Y
  4130        BMI .3       ...SYS FILE
  4140        LDY #Q.DIR
  4150        .HS 2C
  4160 .3     LDY #Q.SYS
  4170        JSR MSG
  4180        JSR PRINT.BPNTR.NAME
  4190 *--------------------------------
  4200        INC DIR.INDEX
  4210        DEC LENGTH
  4220        BNE .1
  4230 .4     LDA DIR.INDEX
  4240        CMP MAX.DIRPNT
  4250 *--------------------------------
  4260 CLEAR.LINE.OR.PRINT.MORE.MSG
  4270        BEQ .1       CLEAR LINE
  4280        LDY #Q.MORE
  4290        BNE MSG      ...ALWAYS
  4300 .1     JSR CLREOL
  4310        JMP CROUT
  4320 *--------------------------------
  4330 SETUP.DISPLAY.LOOP
  4340        LDA #16      MAX 16 LINES IN LIST
  4350        STA LENGTH
  4360        LDY #0
  4370        STY MAX.LINE
  4380        INY          SAME AS VTAB 3, HTAB 1
  4390        STY CV
  4400        JMP CROUT
  4410 *--------------------------------
  4420 CHECK.FOR.SEL.LINE
  4430        LDA MAX.LINE      SEE IF CURRENT LINE SHOULD
  4440        CMP SEL.LINE           BE INVERSE MODE
  4450        BNE .1            ...NO
  4460        LDA BPNTR         ...YES, SO SETUP POINTER
  4470        STA SPNTR
  4480        LDA BPNTR+1
  4490        STA SPNTR+1
  4500        LDA #$3F                  & SET INVERSE MODE
  4510        STA INVFLG
  4520 .1     RTS
  4530 *--------------------------------
  4540 MSG1   JSR COUT
  4550        INY
  4560 MSG    LDA QTS,Y
  4570        BNE MSG1
  4580        RTS
  4590 *--------------------------------
  4600 QTS    .EQ *
  4610 Q.SDV  .EQ *-QTS
  4620        .AS -"S/D  VOLUME NAME"
  4630        .HS 00
  4640 Q.VHELP .EQ *-QTS
  4650        .HS 8D
  4660        .AS -/USE ARROWS AND <RETURN> TO SELECT/
  4670        .HS 8D
  4680        .AS -/USE <ESCAPE> TO TRY AGAIN/
  4690        .HS 8D
  4700        .HS 00
  4710 Q.SYS  .EQ *-QTS
  4720        .AS -/SYS -- /
  4730        .HS 00
  4740 Q.DIR  .EQ *-QTS
  4750        .AS -/DIR -- /
  4760        .HS 00
  4770 Q.MORE .EQ *-QTS
  4780        .AS -/<<<MORE>>>/
  4790 Q.CR   .EQ *-QTS
  4800        .HS 8D00
  4810 *--------------------------------
  4820            .DUMMY
  4830            .OR $800
  4840 OPNBUF     .BS 1024
  4850 DIRBUF     .BS 512
  4860 PATHNAME   .EQ $280
  4870 DIR.INDEX  .BS 1
  4880 DIR.START  .BS 1
  4890 MAX.DIRPNT .BS 1
  4900 SEL.LINE   .BS 1
  4910 MAX.LINE   .BS 1
  4920 UNIT       .BS 1
  4930 LENGTH     .BS 1
  4940 CURTYP     .BS 1
  4950 CURBLK     .BS 1
  4960            .ED
  4970 *--------------------------------
  4980 BUFFER     .EQ $2000
  4990 ENTLEN     .EQ BUFFER+$23   ENTRY LENGTH
  5000 ENTCNT     .EQ BUFFER+$24   # ENTRIES PER BLOCK
  5010 *--------------------------------

The QUIT-code Installer

As I just mentioned, the code which downloads the QUIT-code from $D100-D3FF to $1000-12FF is located at $FCE5 inside ProDOS 1.1.1. Here is a commented listing of that code.

  1000 *SAVE S.FCE5.FD3A
  1010 *--------------------------------
  1020        .OR $FCE5
  1030        .TA $800
  1040 DOWNLOAD.QUITTER
  1050        LDA $C083    SWITCH IN CORRECT D000 BANK
  1060        LDA $C083
  1070 *--------------------------------
  1080        LDA $00      SAVE 00...03 ON STACK
  1090        PHA
  1100        LDA $01
  1110        PHA
  1120        LDA $02
  1130        PHA
  1140        LDA $03
  1150        PHA
  1160 *---SETUP POINTERS FOR MOVING----
  1170        LDA /$1000   Destination Pointer
  1180        STA $03
  1190        LDA /$D100   Source Pointer-
  1200        STA $01
  1210        LDA #0
  1220        STA $00
  1230        STA $02
  1240 *--------------------------------
  1250        TAY          Y=0
  1260        LDX #3       Move 3 Pages
  1270 .1     DEY
  1280        LDA ($00),Y
  1290        STA ($02),Y
  1300        TYA
  1310        BNE .1       ...More in same page
  1320        INC $01
  1330        INC $03      Advance to next pages
  1340        DEX          Count the page
  1350        BNE .1       ...Copy another page
  1360 *---Restore $03...$00------------
  1370        PLA
  1380        STA $03
  1390        PLA
  1400        STA $02
  1410        PLA
  1420        STA $01
  1430        PLA
  1440        STA $00
  1450 *--------------------------------
  1460        LDA $C08B    Select normal D000 bank
  1470        LDA $C08B
  1480 *---Set up RESET Vector----------
  1490        LDA #$1000   Lo-byte
  1500        STA $3F2
  1510        LDA /$1000   Hi-byte
  1520        STA $3F3
  1530        EOR #$A5     Power-up byte
  1540        STA $3F4
  1550 *--------------------------------
  1560        JMP $1000
  1570 *--------------------------------

The program above can be written in a lot less space, as follows:

  1580    
  1590 *--------------------------------
  1600        .OR $FCE5
  1610        .TA $900
  1620 SC.DOWNLOAD.QUITTER
  1630        LDA $C083    Select D000 bank
  1640 *--------------------------------
  1650        LDY #0
  1660 .1     LDA $D100,Y
  1670        STA $1000,Y
  1680        LDA $D200,Y
  1690        STA $1100,Y
  1700        LDA $D300,Y
  1710        STA $1200,Y
  1720        INY
  1730        BNE .1
  1740 *--------------------------------
  1750        LDA $C08B    Select normal D000 bank
  1760 *---Set up RESET Vector----------
  1770        STY $3F2     RESET Vector Lo-byte
  1780        LDA /$1000   Hi-byte
  1790        STA $3F3
  1800        EOR #$A5     Power-up byte
  1810        STA $3F4
  1820 *--------------------------------
  1830        JMP $1000
  1840 *--------------------------------

Using DP18 Under ProDOSBill Morgan

A customer called up the other day to order the DP18 Source Code package, but he wanted it only if it ran under ProDOS. (That's 18-digit Binary Coded Decimal arithmetic for Applesoft.) Well, we hadn't tried to move it over before, but it didn't sound like too much of a problem, so I gave it a shot. It did turn out to be quite easy.

I first tried simply CONVERTing all the files over, including the binary object code, and RUNning the example programs. That almost worked! The DP18 arithmetic all operated just right, but the scheme of moving the Applesoft program up and BLOADing DP18 at $803 ran into a little trouble. The forward pointers in each line of the program weren't set up properly. Bob then pointed out to me that it's very easy to install a program between BASIC.SYSTEM and the buffers, so that might be the way to go in this situation. All it took was a little arithmetic to figure out that DP18 needs $1C pages and should therefore have an origin of $7E00. The .OR directive was the only line inside DP18 that I had to change!

After that I needed only two more things: a short machine language program to get the buffer from BI, issue the BLOAD command, and set the ampersand vector; and a one-line Applesoft routine that checks the vector to find out if DP18 is already installed and call the loader if not.

Here's the Applesoft routine:

 10  IF  PEEK (1014) + 256 *  PEEK (1015) <  > 32563 THEN
    PRINT  CHR$ (4)"BRUN INSTALL.DP18"

And here's all there is to the loader:

  1000 *SAVE S.INSTALL.DP18
  1010 *--------------------------------
  1020 BUFFER      .EQ $200
  1030  
  1040 AMPERSAND   .EQ $3F6
  1050  
  1060 DP.LINK     .EQ $7E00
  1070 AMP.LINK    .EQ $7E02
  1080  
  1090 DOS.COMMAND .EQ $BE03
  1100 GET.BUFFER  .EQ $BEF5
  1110 FREE.BUFFER .EQ $BEF8
  1120  
  1130 COUT1       .EQ $FDF0
  1140 *--------------------------------
  1150        .OR $300
  1160 *      .TF INSTALL.DP18
  1170  
  1180 T      JSR FREE.BUFFER   kick others out
  1190        LDA #$1C
  1200        JSR GET.BUFFER    get 28 pages
  1210        BCS ERROR
  1220        CMP #$7E          must be at $7E00
  1230        BNE ERROR
  1240  
  1250        LDX #LENGTH
  1260 .1     LDA COMMAND,X     "BLOAD DP18"
  1270        STA BUFFER,X
  1280        DEX
  1290        BPL .1
  1300        JSR DOS.COMMAND   do it
  1310        BCS ERROR
  1320  
  1330        LDX #1
  1340 .2     LDA AMPERSAND,X   save old vector
  1350        STA AMP.LINK,X
  1360        LDA DP.LINK,X     & point to DP18
  1370        STA AMPERSAND,X
  1380        DEX
  1390        BPL .2
  1400 EXIT   RTS
  1410  
  1420 ERROR  LDX #0
  1430 .1     LDA MESSAGE,X     show error message
  1440        BEQ EXIT
  1450        JSR COUT1
  1460        INX
  1470        BNE .1
  1480 *--------------------------------
  1490 MESSAGE .HS 8D
  1500         .AS -/Error loading DP18/
  1510         .HS 8D00
  1520  
  1530 COMMAND .AS -/BLOAD DP18/
  1540         .HS 8D
  1550 LENGTH  .EQ *-COMMAND-1
  1560 *--------------------------------

Booting DOS 3.3 from the UniDisk 3.5Bill Morgan

A couple of months ago I presented an RWTS so DOS 3.3 could read and write Apple's new UniDisk 3.5. At that time I promised the additional code so we could boot DOS from the new device. Well, it took a little longer than I had planned, but here are the changes necessary so FORMAT.UNIDISK can produce a bootable disk.

There are two parts to the problem. We must first come up with a way to write a DOS image on the disk during formatting, and we must have a boot sector that can read in the new image and start it up. Since we have 32 sectors in track 0, with 30 of them available for the DOS image, I looked up Bob S-C's article from the April '86 issue on stuffing DOS into fewer sectors for faster booting. In that article he pointed out that we don't need to deal with pages $B3-B4 and $BB, since they're used for buffers, and that these days we can assume that all machines have at least 48K of RAM, so we don't need the relocator code from a DOS master. Eliminating these areas shaves the DOS image down to 32 sectors, but I still needed to get rid of two more to avoid carrying the boot code around inside DOS.

One more was easy: page $BC is just buffer in my RWTS. Then I noticed that nearly all of the code in pages $B6 and $B7 is used only by INIT and the boot process, and could therefore be done away with. It turns out that the only necessary part of $B6 is the various APPEND and VERIFY patches from $B65D-B6B2. Similarly, the only vital things in page $B7 are the RWTS entry at $B7B5-B7C1, a buffer clearing routine at $B7D6-B7DE, and RWTS's IOB and DCT from $B7E8-B7FE. I also decided to keep the read/write a group of pages code from $B793-B7B4, since some programs borrow that routine. So, I came up with some routines to pack the necessary areas in $B7 into the unused space in $B6 and thereby save another page.

Since we're booting in from the Protocol Converter, we don't immediately have the ability to read individual sectors. All we can do is read blocks, or pairs of sectors, and the DOS image doesn't conveniently split up into even blocks. Here's how I ended up mapping DOS pages into blocks:

       0 - Boot     4 - A3-A4    8 - AB-AC    C - B3,B6
       1 - 9D-9E    5 - A5-A6    9 - AD-AE    D - B8-B9
       2 - 9F-A0    6 - A7-A8    A - AF-B0    E - BA,BD
       3 - A1-A2    7 - A9-AA    B - B1-B2    F - BE-BF

Note that this formatter uses the DOS image in memory. You need to make sure that this DOS contains all of the patches you want to use, and ONLY the patches that can be used from bootup time. For example, I got into trouble when I initialized a disk with a DOS patched to use my memory expansion card as a RAM disk. Those patches depend on some code being in place in page zero of the alternate banks of the memory card, and that code wasn't there at boot time. The answer is to initialize the disk without these patches, and then make BRUN RAMDRIVE part of the HELLO program. You may have to make some such adjustment for your own system.

If you need to install a bootable DOS image on a disk formatted with the old version of this program, all you have to do is delete lines 1140-1690 and replace line 1710 with:

       LDA #1
       STA DRIVE

This program doesn't automatically save a HELLO program after formatting, so after it's done you need to LOAD your HELLO from another disk and SAVE it onto the freshly formatted one.

To create this new version you need to make three changes in S.FORMAT.UNIDISK: First, since ERROR is going to be too far away for a direct branch, replace lines 1220-1230 with these:

       1220       BCC .1
       1225       JMP ERROR
       1230 .1    LDA #2

Second, we need to replace DO.BOOT.SECTOR with DO.DOS.IMAGE, to write a complete DOS on the disk. Delete lines 1700-1790 and replace them with the following:

  1700 DO.DOS.IMAGE
  1710        INC DRIVE         that WAS drive one
  1720        LDA #0
  1730        STA BUFFER        buffer on even page
  1740        STA TRACK         all in track zero
  1750        LDX #$1F          start at sector $1F
  1760 .1     STX SECTOR        set sector
  1770        LDA PAGE.TABLE,X  get next page
  1780        CMP #$AA          special case?
  1790        BNE .2
  1800        JSR HANDLE.AA
  1810 .2     CMP #$B6          other special case?
  1820        BNE .3
  1830        JSR HANDLE.B6
  1840 .3     STA BUFFER+1      set buffer page
  1850        JSR CALL.RWTS     write it
  1860        LDX SECTOR
  1870        DEX               done?
  1880        BPL .1
  1890        RTS
  1900 *--------------------------------
  1910 HANDLE.AA
  1920        JSR MOVE.PAGE.TO.BUFFER
  1930        LDY #29
  1940 .1     LDA STARTUP.NAME,Y install HELLO name
  1950        STA MY.BUFFER+$75,Y
  1960        DEY
  1970        BPL .1
  1980        LDA #0            mark last command
  1990        STA MY.BUFFER+$5F as INIT
  2000        LDA /MY.BUFFER    point BUFFER here
  2010        RTS
  2020 *--------------------------------
  2030 HANDLE.B6
  2040        JSR MOVE.PAGE.TO.BUFFER
  2050        LDX #0
  2060 .1     LDY B7.TABLE,X    get segment length
  2070        BEQ .5            0 marks end
  2080        INX               next byte
  2090        LDA B7.TABLE,X    get offset into $B7
  2100        STA .3            stuff into LDA
  2110 .2     LDA $B700         get byte from $B7
  2120 .3     .EQ *-2
  2130        STA MY.BUFFER     put it in with $B6
  2140 .4     .EQ *-2
  2150        INC .3            next source byte
  2160        INC .4            next destination
  2170        DEY               done with segment?
  2180        BNE .2
  2190        INX               next byte in table
  2200        BNE .1         ...always
  2210  
  2220 .5     LDA /MY.BUFFER    point BUFFER here
  2230        RTS
  2240  
  2250 B7.TABLE
  2260        .DA #$2F,#$B793   R/W group & call RWTS
  2270        .DA #$9,#$B7D6    zero a buffer
  2280        .DA #$17,#$B7E8   IOB & DCT
  2290        .DA #0
  2300 *--------------------------------
  2310 MOVE.PAGE.TO.BUFFER
  2320        STA .2       A has either $AA or $B6
  2330        LDY #0
  2340 .1     LDA $FF00,Y
  2350 .2     .EQ *-1
  2360        STA MY.BUFFER,Y
  2370        INY
  2380        BNE .1
  2390        RTS
  2400 *--------------------------------
  2410 PAGE.TABLE
  2420        .DA /BOOT.IMAGE,/BOOT.IMAGE+$100
  2430        .HS 9D.9E.9F.A0.A1.A2.A3.A4
  2440        .HS A5.A6.A7.A8.A9.AA.AB.AC
  2450        .HS AD.AE.AF.B0.B1.B2.B3
  2460        .HS B6.B8.B9.BA.BD.BE.BF
  2470  
  2480 STARTUP.NAME
  2490        .AS -/HELLO/
  2500        .BS STARTUP.NAME+30-*," "

The main body of DO.DOS.IMAGE just writes the DOS pages listed in PAGE.TABLE onto sectors 0-$1F of track 0, providing special handling for pages $AA and $B6. In page $AA we must install a startup program name into the filename buffer and then force the last-command-executed index to its INIT value, so the coldstart code will work properly after a boot. Page $B6 is a little more involved. We need to move certain segments from page $B7 in with $B6, using a table containing the lengths and addresses of the segments.

And the last step is installing some real boot code in place of the dummy message. Just delete lines 2200-2390 of the old program and put in the rest of the new code:

  2900 *--------------------------------
  2910  
  2920        .BS *+255/256*256-*
  2930  
  2940 COLDSTART  .EQ $9D84
  2950 IOB.SLOT   .EQ $B7E9
  2960 IOB.DRIVE  .EQ $B7EA
  2970 IOB.OSLOT  .EQ $B7F7
  2980 IOB.ODRIVE .EQ $B7F8
  2990 MY.COMPARE .EQ $BEB0     }
  3000 MY.READ    .EQ $BF49     }  inside RWTS.3.5
  3010 MY.WRITE   .EQ $BF6B     }
  3020 SETKBD     .EQ $FE89
  3030 SETVID     .EQ $FE93
  3040 *--------------------------------
  3050 BOOT.IMAGE
  3060        .PH $800
  3070        .DA #1
  3080 BOOT   TXA               save slot*16
  3082        PHA
  3090        LSR
  3092        LSR
  3094        LSR
  3096        LSR
  3100        ORA #$C0          form Cslot
  3110        PHA               save it
  3120        STA .1+2          fix entry address
  3130        STA .2+2          fix call address
  3140 .1     LDA $FFFF         get ProDOS entry
  3150        CLC
  3160        ADC #3
  3170        STA .2+1          set PC entry
  3180  
  3190 .2     JSR $FFFF         call PC
  3200        .DA #1            READ
  3210        .DA PARMLIST
  3220        INC PC.BUFFER+1   ready for next block
  3230        INC PC.BUFFER+1 
  3240        INC BLOCK         next block
  3250        LDA BLOCK
  3260        CMP #$10          we only want $1-F
  3270        BCC .2
  3280  
  3290 RELOCATE.PAGES
  3300        LDX #0
  3310 .1     LDA $BA00,X       relocate packed pages
  3320        STA $BF00,X
  3330        LDA $B900,X
  3340        STA $BE00,X
  3350        LDA $B800,X
  3360        STA $BD00,X
  3370        LDA $B700,X
  3380        STA $BA00,X
  3390        LDA $B600,X
  3400        STA $B900,X
  3410        LDA $B500,X
  3420        STA $B800,X
  3430        LDA $B400,X
  3440        STA $B600,X
  3450        INX
  3460        BNE .1
  3470  
  3480 FIX.B7 LDY TABLE.B7,X    get segment size
  3490        BEQ FIX.SLOT.REFS done?
  3500        INX
  3510        LDA TABLE.B7,X    get $B7 offset
  3520        STA .3            into STA
  3530 .1     LDA $B600
  3540 .2     .EQ *-2
  3550        STA $B700
  3560 .3     .EQ *-2
  3570        INC .2            next source
  3580        INC .3            next destination
  3590        DEY
  3600        BNE .1            segment done?
  3610        INX
  3620        BNE FIX.B7     ...always
  3630  
  3640 FIX.SLOT.REFS
  3650        PLA               get Cslot
  3660        STA MY.READ
  3670        STA MY.WRITE
  3680        PLA               get slot*16
  3690        STA IOB.SLOT
  3700        STA IOB.OSLOT
  3710        STA MY.COMPARE
  3720        LDA #1
  3730        STA IOB.DRIVE
  3740        STA IOB.ODRIVE
  3750        LDX #$FF          new stack
  3760        TXS
  3770        JSR SETKBD        set  I/O hooks
  3780        JSR SETVID
  3790        JMP COLDSTART     & go to DOS
  3800 *--------------------------------
  3810 PARMLIST .DA #3
  3820          .DA #1
  3830 PC.BUFFER .DA $9D00
  3840 BLOCK    .DA <1
  3850  
  3860 TABLE.B7 .DA #$2F,#$B793
  3870          .DA #$9,#$B7D6
  3880          .DA #$17,#$B7E8
  3890          .DA #0
  3900  
  3910        .EP
  3920 BOOT.SIZE .EQ *-BOOT.IMAGE
  3930 *--------------------------------
  3940        .BS BOOT.IMAGE+$200-*
  3950 MY.BUFFER

Here we take note of which slot we're booting from and set up the Protocol Converter calls to read blocks 1-F into memory starting at $9D00. Then we move the last seven pages up to their real locations and unpack the $B7 information from page $B6. The last step is to plug the current slot and drive info into the IOB, so DOS will know where to look for the HELLO program, and the correct slot location into MY.READ, MY.WRITE and MY.COMPARE. Note that these addresses are inside RWTS 3.5 and may change if you modify that program.


How Many Cycles Does "BRL" Take?Bob Sander-Cederlof

There seems to be some disagreement among the various references for the 65802/16 chip as to how many cycles some of the opcodes take. One in particular that is wrong in almost all places is the BRL (BRanch Long) opcode.

BRL is an unconditional branch, just like BRA. Neither opcode is found in the 6502. BRA (BRanch Always) is found in both the 65C02 and the 65802/16 chips. BRL is only in the 65802/16. BRA takes a single-byte relative offset, just like the conditional branch instructions; therefore, BRA can only branch within -127 to +128 bytes from the instruction. BRL takes a two-byte relative offset, so it can branch anywhere within the 64K bank of memory containing the instruction.

Most references, including the data sheets from Western Design Center and GTE, say that the BRL opcode takes 3 cycles. One reference even went so far as to claim the JMP opcode was obsolete, because BRL and JMP both took 3 cycles and 3 bytes, and BRL had the advantage of being a "run-anywhere" instruction.

However, David Eyes claims in his book that BRL takes 4 cycles.

Naturally, the final authority is the chip itself. But how do you measure the timing of one little instruction? One way is to use a logic analyzer (a very expensive piece of hardware that can give a cycle-by-cycle description of whatever electronic circuit it is attached to). Another is to write a program that will run a loop millions or billions of times, and time it with a stopwatch. The loop first has to be timed without the opcode you are examining, and later with the opcode; then you subtract and divide to get the number of cycles the opcode took.

I thought of a simpler way. I just use my ears! I wrote a loop that toggled the speaker, and put a known 3-cycle opcode inside the loop. Then I duplicated the loop using a BRL instruction in place of the known 3-cycle opcode. When the program runs, the first loop makes a certain tone until I press a key. Then control passes to the second loop. If the second loop makes the same tone, then BRL also takes 3 cycles. If the second tone is higher, BRL takes less than 3 cycles; if lower, then more than 3 cycles. Here is the program:

  1000 *SAVE S.TEST.BRL.TIMING
  1010        .OP 65802
  1020 *--------------------------------
  1030 TEST.BRL.TIMING
  1040 .1     LDA $C030    PLAY A TONE 
  1050        LDY #128
  1060 .2     DEY
  1070        JMP .3       KNOWN TO BE 3 CYCLES
  1080 .3     BNE .2
  1090        LDA $C000    REPEAT TONE UNTIL KEYPRESS
  1100        BPL .1
  1110        STA $C010
  1120 *---NOW MAKE TONE WITH "BRL"---
  1130 .4     LDA $C030
  1140        LDY #128
  1150 .5     DEY
  1160        BRL .6       IF 4 CYCLES, LOWER PITCH TONE
  1170 .6     BNE .5
  1180        LDA $C000    REPEAT TONE UNTIL KEYPRESS
  1190        BPL .4
  1200        STA $C010
  1210        RTS
  1220 *--------------------------------

The known 3-cycle intruction is the JMP at line 1070. The BRL is inside the second loop at line 1160. When I ran the program, it produced a lower pitch for the BRL-loop. So, BRL takes more than 3 cycles. Is it 4 cycles?

I replaced line 1070 with two NOP lines, which takes 4 cycles altogether. Then I ran the program again, and both loops made the same pitch. Voila! BRL does take 4 cycles. Just to be sure, I made some more tests. They all told the same story.

Then I noticed that the references also disagree on the BRA opcode. Most say it takes 2 cycles. Since the other relative branches only take 2 cycles when they DON'T branch, and BRA never does that, something had to be wrong. In a 6502, 65C02, or 65802/16 Emulation mode, other relative branches take 3 cycles to branch when the target is in the same page as the next byte after the instruction itself, and 4 cycles when the target is in a different page. In 65802/16 Native mode, the conditional relative branches never take more than 3 cycles.

I modified my program to set up page-boundary crossings in various combinations and tested the BRA opcode. It always takes 3 cycles in Native mode. In Emulation mode or older chips it takes 3 or 4 cycles, just like the conditional branches.

If you wonder about the timing of other opcodes, you can set up similar tests.


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.)