Apple Assembly   Line
Volume 2 -- Issue 1 October 1981

In This Issue...

Source Code for S-C Assembler II Version 4.0

At long last, I have decided to start selling the source code for my assembler. So many of you have asked for it! I am sure you understand my reluctance; after all, with a wife and five kids to support, and most of our income coming from this one product....

If I have your registration card for Version 4.0 on file, or some other proof-of-purchase, I will send you a disk with all of the commented source code on it. You can study it, assemble it, modify it, et cetera; just don't start selling it! With your check for $95, you will need to include the following signed declaration:

"I am purchasing the source code of S-C Assembler II Version 4.0 with the understanding that it is proprietary information belonging to S-C SOFTWARE. The disk, and any copies or listings I may make of it, are only for my own personal use."

Another Way Out of the Assembler

James Church, from Trumbull, CT, writes that he has found a way to get from the assembler into Applesoft, without wiping out an Applesoft program.

The normal way to leave is by typing FP, and then PR#0. This of course clears any Applesoft program from memory. But by typing $AAB6:40, $E003G, and PR#0 you can enter Applesoft softly.


Sifting Primes Faster and Faster Bob Sander-Cederlof

Benchmark programs are sometimes useful for selecting between various processors. Quite a few articles have been published which compare and rank the various Z-80, 8080, 6800, and 6502 systems based on the speed with which they execute a given BASIC program. Some of us cannot resist the impulse to show them up by recoding the benchmark in our favorite language on our favorite processor, using our favorite secret tricks for trimming microseconds.

"A High-Level Language Benchmark" (by Jim Gilbreath, BYTE, September, 1981, pages 180-198) is just such an article. Jim compared execution time in Assembly, Forth, Basic, Fortran, COBOL, PL/I, C, and other languages; he used all sorts of computers, including the above four, the Motorola 68000, the DEC PDP 11/70, and more. He used a short program which finds the 1899 primes between 3 and 16384 by means of a sifting algorithm (Sieve of Eratosthenes).

His article includes table after table of comparisons. Some of the key items of interest to me were:

     Language and Machine                    Seconds

     Assembly Language 68000 (8 MHz)            1.12
     Assembly Language Z80                      6.80
     Digital Research PL/I (Z80)               14.0
     Microsoft BASIC Compiler (Z80)            18.6
     FORTH 6502                               265.
     Apple UCSD Pascal                        516.
     Apple Integer BASIC                     2320.
     Applesoft BASIC                         2806.
     Microsoft COBOL Version 2.2 (Z80)       5115.

There is a HUGE error in the data above; I don't know if it is the only one or not. The time I measured for the Apple Integer BASIC version was only 188 seconds, not 2320 seconds! How could he be so far off? His data is obviously wrong, because Integer BASIC in his data is too close to the same speed as Applesoft.

I also don't know why they neglected to show what the 6502 could do with an assembly language version. Or maybe I do....were they ashamed?

William Robert Savoie, an Apple owner from Tennessee, sent me a copy of the article along with his program. He "hand-compiled" the BASIC version of the benchmark program, with no special tricks at all. His program runs in only 1.39 seconds! That is almost as fast as the 8 MHz Motorola 68000 system! The letter that accompanied his program challenged anyone to try to speed up his program.

How could I pass up a challenge like that? I wrote my own version of the program, and cut the time to 0.93 seconds! Then I made one small change to the algorithm, and produced exactly the same results in only 0.74 seconds!

Looking back at Jim Gilbreath's article, he concludes that efficient, powerful high-level languages are THE way to go. He eschews the use of assembly language for any except the most drastic requirements, because he could not see a clear speed advantage. He points out the moral that a better algorithm is superior to a faster CPU. (Note that his algorithm is by no means the fastest one, by the way.)

Here is Gilbreath's algorithm, in Integer BASIC:

     >LIST
        10 S=8190: DIM F(8191): N=0
        20 FOR I=0 TO S: F(I)=1: NEXT I
        30 FOR I=0 TO S: IF F(I)=0 THEN 80
        40 P=I+I+3: K=I+P
        50 IF K>S THEN 70
        60 F(K)=0: K=K+P: GOTO 50
        70 N=N+1: REM PRINT P;" ";
        80 NEXT I
        90 PRINT: PRINT N;" PRIMES": END

The REM tagged onto the end of line 70, if changed to a real PRINT statement, will print the list of prime numbers as they are generated. Of course printing them was not included in any of the time measurements. According to my timing, printing adds 12 seconds to the program.

I modified the algorithm to take advantage of some more prior knowledge about sifting: There is no need to go through the loop in lines 50 and 60 if P is greater than 127 (the largest prime no bigger than the square root of 16384). This means changing line 40 to read:

     40 P=I+I+3 : IF P>130 THEN 70 : K=I+P

This change cut the time for the program from 188 seconds to 156 seconds. My assembly language version of the original algorithm ran in 0.93 seconds, or 202 times faster; the better algorithm ran in 0.74 seconds, or almost 211 times faster.

William Savoie has done a magnificent job in hand-compiling the first program. He ran the program 100 times in a loop, so that he could get an accurate time using his Timex watch. Here is the listing of his program.

 1000         .LIF
 1010  *---------------------------------
 1020  *      SIEVE PROGRAM:
 1030  *      CALCULATES FIRST 1899 PRIMES IN 1.39 SECONDS!
 1040  *
 1050  *      INSPIRED BY JIM GILBREATH, BYTE, 9/81
 1060  *
 1070  *      WRITTEN BY WILLIAM ROBERT SAVOIE
 1080  *                 4405 DELASHMITT RD. APT 15
 1090  *                 HIXSON, TENN  37343
 1100  *---------------------------------
 1110  BUFF   .EQ $3500    START OF BUFFER (#BUFF=0)
 1120  SIZE   .EQ 8189     SIZE OF FLAG ARRAY
 1130  *---------------------------------
 1140  *      PAGE-ZERO VARIABLES
 1150  *---------------------------------
 1160  INDEX  .EQ $06      PAGE ZERO INDEX (LOCATION FOR I)
 1170  PRIME  .EQ $08      PRIME LOCATION
 1180  KVAR   .EQ $19      K VARIABLE 
 1190  CVAR   .EQ $1B      COUNT OF PRIME
 1200  ARRAY  .EQ $1D      ARRAY POINTER
 1210  SAVE   .EQ $1F      COUNT LOOP
 1220  *---------------------------------
 1230  *      ROM ROUTINES
 1240  *---------------------------------
 1250  HOME   .EQ $FC58    CLEAR VIDEO
 1260  CR     .EQ $FD8E    CARRIAGE RETURN
 1270  LINE   .EQ $FD9E    PRINT "-"
 1280  PRINTN .EQ $F940    PRINT 2 BYTE NUMBER IN HEX
 1290  BELL   .EQ $FBE2    SOUND BELL WHEN DONE
 1300  *---------------------------------
 1310  * RUN PROGRAM 100 TIMES FOR ACCURATE TIME MEASUREMENTS!
 1320  *---------------------------------
 1330  START  JSR HOME     CLEAR SCREEN
 1340         JSR CR       CARRIAGE RETURN
 1350         LDA #100     LOOP 100 TIMES
 1360         STA SAVE     SET COUNTER
 1370  .01    JSR GO       RUN PRIME
 1380         DEC SAVE     DECREASE SAVE
 1390         BNE .01      LOOP
 1400         JSR PRINT    PRINT COUNT
 1410         JSR BELL     READ WATCH!
 1420         RTS
 1430  *---------------------------------
 1440  *      RESET VARIABLES
 1450  *---------------------------------
 1460  GO     LDY #00      CLEAR INDEX
 1470         STY CVAR     CLEAR COUNT VARIABLE
 1480         STY CVAR+1   HI BYTE TOO
 1490         STY INDEX    CLEAR INDEX
 1500         STY INDEX+1  HI BYTE TOO
 1510         STY ARRAY    LOW BYTE OF ARRAY
 1520         LDA /BUFF    GET BUFFER LOCATION
 1530         STA ARRAY+1  SET ARRAY POINTER
 1540         LDA #$01     LOAD WITH ONE
 1550         LDX /SIZE    LOAD STOP BYTE
 1560         INX          MAKE PAGE LARGER
 1570  *---------------------------------
 1580  *      SET EACH ELEMENT IN ARRAY TO ONE
 1590  *---------------------------------
 1600  SET    STA (ARRAY),Y  SET MEMORY
 1610         DEY          NEXT LOCATION
 1620         BNE SET      GO 256 TIMES
 1630         INC ARRAY+1  MOVE ARRAY INDEX
 1640         DEX          TEST END
 1650         BNE SET      GO TELL END
 1660  
 1670  * SET ARRAY INDEX AT START OF BUFFER
 1680         LDA #BUFF    SET BUFFER LOCATION
 1690         STA ARRAY    IN ARRAY POINTER LOW
 1700         LDA /BUFF    SET BUFFER LOCATION
 1710         STA ARRAY+1  IN ARRAY POINTER
 1720         JMP FORIN    ENTER SIEVE ALGORITHM
 1730  
 1740  * SCAN ENTIRE ARRAY AND PROBAGATE LAST PRIME
 1750  FORNXT INC INDEX    INCREASE LOW BYTE
 1760         BNE FORIN    GO IF < 256
 1770         INC INDEX+1  INCREASE HI BYTE
 1780  FORIN  LDA INDEX    GET INDEX TO ARRAY
 1790         CLC          READY ADD
 1800         STA ARRAY    SAVE LOW BYTE
 1810         LDA INDEX+1  GET HI BYTE
 1820         ADC /BUFF    ADD BUFFER LOCATION
 1830         STA ARRAY+1  SET POINTER
 1840         LDY #00      CLEAR Y REGISTER
 1850         LDA (ARRAY),Y  GET ARRAY VALUE 
 1860         BEQ FORNXT   GO IF FLAG=0 SINCE NOT PRIME
 1870  * CALCULATE NEXT PRIME NUMBER WITH P=I+I+3
 1880         LDA INDEX    MAKE P=I+3
 1890         ADC #03      ADD THREE
 1900         STA PRIME
 1910         LDA INDEX+1
 1920         ADC #00      ADD CARRY
 1930         STA PRIME+1
 1940  * NOW P=I+3
 1950         LDA PRIME
 1960         ADC INDEX    MAKE P=P+I 
 1970         STA PRIME
 1980         LDA PRIME+1
 1990         ADC INDEX+1  ADD HI BYTE
 2000         STA PRIME+1  SAVE P
 2010  
 2020  * NOW CALCULATE K=I+PRIME (CLEAR BEYOND PRIME)
 2030         LDA INDEX    ADD I TO P
 2040         ADC PRIME
 2050         STA KVAR     SAVE IN K
 2060         LDA INDEX+1
 2070         ADC PRIME+1  ADD HI BYTE TOO
 2080         STA KVAR+1   SAVE K VALUE
 2090  
 2100  * SEE IF K > SIZE AND MODIFY ARRAY IF NOT
 2110  .02    LDA KVAR     GET K VAR
 2120         SEC          SET CARRY FOR SUB
 2130         SBC #SIZE    SUBTRACT SIZE
 2140         LDA KVAR+1   GET HI BYTE
 2150         SBC /SIZE    SUBTRACT TOO
 2160         BCS .03      GO IF K < SIZE
 2170  * ASSIGN ARRAY(K)=0 SINCE PRIME CAN BE ADDED TO MAKE NUMBER
 2180  * THEREFORE THIS CANNOT BE PRIME! (PROBAGATE THROUGH ARRAY)
 2190         LDA KVAR     GET INDEX TO ARRAY
 2200         STA ARRAY    SAVE LOW BYTE
 2210         LDA KVAR+1   GET HI BYTE
 2220         ADC /BUFF    ADD BUFFER OFFSET
 2230         STA ARRAY+1  SAVE ARRAY INDEX
 2240         LDA #00      CLEAR A
 2250         TAY          AND Y REGISTER
 2260         STA (ARRAY),Y  CLEAR ARRAY LOCATION
 2270  * CREATE NEW K FROM K=K+PRIME (MOVE THROUGH ARRAY)
 2280         LDA KVAR     GET K LOW
 2290         ADC PRIME    ADD PRIME
 2300         STA KVAR     SAVE K
 2310         LDA KVAR+1   NOW ADD HI BYTES
 2320         ADC PRIME+1
 2330         STA KVAR+1
 2340         JMP .02      LOOP TELL ARRAY DONE
 2350  * NOW COUNT PRIMES FOUND  (C=C+1)
 2360  .03
 2370  * --NOTE-- DELETE NEXT LINE TO TIME PROGRAM (JSR PRINTP)
 2380         JSR PRINTP   PRINT PRIME
 2390         INC CVAR     ADD ONE
 2400         BNE .04      GO IF NO OVERFLOW
 2410         INC CVAR+1   HI BYTE COUNTER
 2420  .04    LDA INDEX    GET INDEX
 2430  * TEST TO SEE IF WE HAVE INDEXED THROUGH ENTIRE ARRAY
 2440         SBC #SIZE    SUBTRACT SIZE
 2450         LDA INDEX+1  GET HI BYTE TOO
 2460         SBC /SIZE    SUBTRACT HI BYTE
 2470         BCC FORNXT   CONTINUE?
 2480         RTS
 2490  *---------------------------------
 2500  * PRINT THE NUMBER OF PRIMES FOUND
 2510  *---------------------------------
 2520  PRINT  LDY CVAR+1   GET HI BYTE OF COUNT
 2530         LDX CVAR
 2540         JSR PRINTN   PRINT PRIMES FOUND
 2550         RTS          JOB DONE, RETURN
 2560  *---------------------------------
 2570  *      PRINT THE PRIME NUMBER (OPTIONAL)
 2580  *---------------------------------
 2590  PRINTP LDY PRIME+1  HI BYTE 
 2600         LDX PRIME
 2610         JSR PRINTN
 2620         JSR LINE     VIDEO "-" OUT
 2630         SEC
 2640         RTS
Here is a listing of my fastest version.
 1000  *---------------------------------
 1010  * SIEVE PROGRAM:
 1020  * CALCULATES FIRST 1899 PRIMES IN .74 SECONDS!
 1030  *
 1040  * INSPIRED BY JIM GILBREATH
 1050  *   (SEE BYTE MAGAZINE, 9/81, PAGES 180-198.)
 1060  * AND BY WILLIAM ROBERT SAVOIE
 1070  *   4405 DELASHMITT RD. APT 15
 1080  *   HIXSON, TENN  37343
 1090  *---------------------------------
 1100  ARRAY  .EQ $3500    FLAG BYTE ARRAY
 1110  SIZE   .EQ 8192     SIZE OF FLAG ARRAY
 1120  *---------------------------------
 1130  * PAGE-ZERO VARIABLES
 1140  *---------------------------------
 1150  A.PNTR .EQ $06,07   POINTER TO FLAG ARRAY FOR OUTER LOOP
 1160  B.PNTR .EQ $08,09   POINTER TO FLAG ARRAY FOR INNER LOOP
 1170  PRIME  .EQ $1B,1C   LATEST PRIME NUMBER
 1180  COUNT  .EQ $1D,1E   # OF PRIMES SO FAR
 1190  TIMES  .EQ $1F      COUNT LOOP
 1200  *---------------------------------
 1210  * APPLE ROM ROUTINES USED
 1220  *---------------------------------
 1230  PRINTN .EQ $F940    PRINT 2 BYTE NUMBER FROM MONITOR
 1240  HOME   .EQ $FC58    CLEAR VIDEO
 1250  CR     .EQ $FD8E    CARRIAGE RETURN
 1260  LINE   .EQ $FD9E    PRINT "-"
 1270  BELL   .EQ $FBE2    SOUND BELL WHEN DONE
 1280  *---------------------------------
 1290  * RUN PROGRAM 100 TIMES FOR ACCURATE TIME MEASUREMENTS!
 1300  *---------------------------------
 1310  START  JSR HOME     CLEAR SCREEN
 1320         LDA #100     LOOP 100 TIMES
 1330         STA TIMES    SET COUNTER
 1340  .1     JSR GENERATE.PRIMES
 1350         LDA $400     TOGGLE SCREEN FOR VISIBLE INDICATOR
 1360         EOR #$80     OF ACTION
 1370         STA $400
 1380         DEC TIMES
 1390         BNE .1       LOOP
 1400         JSR BELL     READ WATCH!
 1410         LDY COUNT+1  GET HI BYTE OF COUNT
 1420         LDX COUNT
 1430         JSR PRINTN   PRINT PRIMES FOUND
 1440         RTS
 1450  *---------------------------------
 1460  *      GENERATE THE PRIMES
 1470  *---------------------------------
 1480  GENERATE.PRIMES
 1490         LDY #0       CLEAR INDEX
 1500         STY COUNT    CLEAR COUNT VARIABLE
 1510         STY COUNT+1
 1520         STY A.PNTR   SET UP POINTER FOR OUTER LOOP
 1530         LDA /ARRAY
 1540         STA A.PNTR+1
 1550         LDA #1       LOAD WITH ONE
 1560         LDX /SIZE      NUMBER OF PAGES TO STORE IN
 1570  *---------------------------------
 1580  * SET EACH ELEMENT IN ARRAY TO ONE
 1590  *---------------------------------
 1600  .1     STA (A.PNTR),Y  SET FLAG TO 1
 1610         INY          NEXT LOCATION
 1620         BNE .1       GO 256 TIMES
 1630         INC A.PNTR+1 POINT AT NEXT PAGE
 1640         DEX          NEXT PAGE
 1650         BNE .1       MORE PAGES
 1660  *---------------------------------
 1670  * SCAN ENTIRE ARRAY, LOOKING FOR A PRIME
 1680  *---------------------------------
 1690         LDA /ARRAY   SET A.PNTR TO BEGINNING AGAIN
 1700         STA A.PNTR+1
 1710  .2     LDY #0       CLEAR INDEX
 1720         LDA (A.PNTR),Y  LOOK AT NEXT FLAG
 1730         BEQ .6       NOT PRIME, ADVANCE POINTER
 1740  *---------------------------------
 1750  * CALCULATE CURRENT INDEX INTO FLAG ARRAY
 1760  *---------------------------------
 1770         SEC
 1780         LDA A.PNTR+1
 1790         SBC /ARRAY
 1800         TAX          SAVE HI-BYTE OF INDEX
 1810         LDA A.PNTR   LO-BYTE OF INDEX
 1820  *---------------------------------
 1830  * CALCULATE NEXT PRIME NUMBER WITH P=I+I+3
 1840  *---------------------------------
 1850         ASL          DOUBLE THE INDEX
 1860         TAY
 1870         TXA          HI-BYTE OF INDEX
 1880         ROL
 1890         TAX
 1900         TYA          NOW ADD 3
 1910         ADC #3
 1920         STA PRIME
 1930         BCC .3
 1940         INX
 1950  .3     STX PRIME+1
 1960  *---------------------------------
 1970  * FOLLOWING 4 LINES CHANGE ALGORITHM SLIGHTLY
 1980  * TO SPEED IT UP FROM .93 TO .74 SECONDS
 1990  *---------------------------------
 2000         TXA          TEST HIGH BYTE
 2010         BNE .5       PRIME > SQRT(16384)
 2020         CPY #127
 2030         BCS .5       PRIME > SQRT(16384)
 2040  *---------------------------------
 2050  * NOW CLEAR EVERY P-TH ENTRY AFTER P
 2060  *---------------------------------
 2070         LDY #0
 2080         LDA A.PNTR   USE CURRENT OUTER POINTER FOR INNER POINTER
 2090         STA B.PNTR
 2100         LDA A.PNTR+1
 2110         STA B.PNTR+1
 2120         CLC          BUMP ARRAY POINTER BY P
 2130  .4     LDA B.PNTR   BUMP TO NEXT SLOT
 2140         ADC PRIME
 2150         STA B.PNTR
 2160         LDA B.PNTR+1
 2170         ADC PRIME+1
 2180         STA B.PNTR+1
 2190         CMP /ARRAY+SIZE     SEE IF BEYOND END OF ARRAY
 2200         BCS .5       YES, FINISHED CLEARING
 2210         TYA          NO, CLEAR ENTRY IN ARRAY
 2220         STA (B.PNTR),Y
 2230         BEQ .4       ...ALWAYS
 2240  *---------------------------------
 2250  * NOW COUNT PRIMES FOUND  (C=C+1)
 2260  *---------------------------------
 2270  .5
 2280  *      JSR PRINTP   PRINT PRIME
 2290         INC COUNT
 2300         BNE .6 
 2310         INC COUNT+1
 2320  *---------------------------------
 2330  * ADVANCE OUTER POINTER AND TEST IF FINISHED
 2340  *---------------------------------
 2350  .6     INC A.PNTR
 2360         BNE .7
 2370         INC A.PNTR+1
 2380  .7     LDA A.PNTR+1
 2390         CMP /ARRAY+SIZE
 2400         BCC .2
 2410         RTS
 2420  *---------------------------------
 2430  * OPTIONAL PRINT PRIME SUBROUTINE
 2440  *---------------------------------
 2450  PRINTP LDY PRIME+1  HI BYTE 
 2460         LDX PRIME
 2470         JSR PRINTN   PRINT DECIMAL VAL
 2480         JSR LINE     VIDEO "-" OUT
 2490         RTS

Michael R. Laumer, of Carrollton, Texas, has been working for about a year on a full-scale compiler for the Integer BASIC language. He has it nearly finished now, so just for fun he used it to compile the algorithm from Gilbreath's article. Mike used a slightly different form of the Integer BASIC program than I did, which took 238 seconds to execute. But the compiled version ran in only 20 seconds! If you are interested in compiling Integer BASIC programs, you can write to Mike at Laumer Research, 1832 School Road, Carrollton, TX 75006.

If you want to, you can easily cut the time of my program from 0.74 to about .69 seconds. Lines 1600-1650 in my program set each byte in ARRAY to $01. If I don't mind the extra program length, I can rewrite this loop to run in about 42 milliseconds instead of the over 90 it now takes. Here is how I would do it:

     .1     STA ARRAY,Y
            STA ARRAY+$100,Y
            STA ARRAY+$200,Y
            STA ARRAY+$300,Y          TOTAL OF 32
             .                       LINES LIKE THESE
             .
             .
            STA ARRAY+$1E00,Y
            STA ARRAY+$1F00,Y
            INY
            BNE .1

If you can find a way to implement the same program in less than 0.69 seconds, you are hereby challenged to do so!


6809 Cross Assembler Bob Sander-Cederlof

Chris Wiggs, of Rockford, IL, has developed a cross assembler for the 6809 which runs in the Apple. In fact, it is really a set of patches to the S-C Assembler II Version 4.0. If you BLOAD your copy of the assembler, and then BRUN his patch file, and BSAVE the result, you have a brand new assembler for 6809 code.

It is set up to work with "The Mill". Typing MGO turns on the mill and starts 6809 code executing, while the Apple's 6502 is left in a waiting loop.

Chris has authorized me to distribute these patches. For only $20 you will get a disk which includes all of the source code for the patches (in S-C Assembler II Version 4.0 format), the already-assembled patch file, a sample 6809 program, and some instructions (in the form of an assembly source file of comments).

I have not put this program through any rigorous test, but Chris is using it himself and is satisfied that it is working correctly. Anyway, you will actually have the SOURCE code, so you can make any further changes you wish with ease.

You might also study how he did it, and then write a cross assembler for some other chip, such as Z-80, 68000, 1802, TMS7000, or whatever.

Here is a sample 6809 assembly:

                 1000 *---------------------------------
                 1010        6809 MULTI-PRECISION ADDITION SUBROUTINE
                 1020        FROM "6809 ASSEMBLY LANGUAGE PROGRAMMING",
                 1030             LANCE LEVENTHAL, OSBORN/MCGRAW-HILL
                 1040             PAGE 11-7
                 1050 *---------------------------------
                 1060        CALL: JSR MPAD
                 1070              .DA #N    NUMBER OF BYTES TO ADD
                 1080              .DA ARG1  ADDRESS OF FIRST ARGUMENT
                 1090              .DA ARG2  ADDRESS OF SECOND ARGUMENT
                 1100              .DA SUM   ADDRESS FOR SUM
                 1110 *---------------------------------
 0800- 34 77     1120 MPADD  PSHS X,Y,U,A,B,CCR  SAVE ALL REGISTERS
 0802- EE 69     1130        LDU  9,S    ACCESS PARAMETER LIST
 0804- 37 34     1140        PULU X,Y,B  GET LENGTH AND ADDRESS OF ARGS
 0806- EE C4     1150        LDU  ,U     GET ADDRESS OF SUM
 0808- 1C FE     1160        ANDCC #$FE  CLEAR CARRY TO START SUM
 080A- A6 80     1170 .1     LDA  ,X+    GET BYTE FROM 1ST ARG
 080C- A0 A0     1180        ADCA ,Y+    ADD BYTE FROM 2ND ARG
 080E- A7 C0     1190        STA  ,U+    STORE BYTE IN SUM
 0810- 5A        1200        DECB        ALL BYTES ADDED?
 0811- 26 F7     1210        BNE .1      NOT YET
 0813- EE 69     1220        LDU  9,S    ADJUST RETURN ADDRESS PAST
 0815- 33 47     1230        LEAU 7,U       THE ARGUMENT LIST
 0817- EF 69     1240        STU  9,S
 0819- 35 F7     1250        PULS PC,U,Y,X,B,A,CCR  RESTORE REGISTERS, RETURN
 
 SYMBOL TABLE
 
 0800- MPADD
 .01=080A

Extending the Apple Monitor Bob Sander-Cederlof

Just as the creators of Applesoft included the wonderful "&" statement to allow language extensions, so also Steve Wozniak included a means for adding new monitor commands. The "control-Y" command branches to a user-defined machine language routine, which can supplement the existing commands in the Monitor ROM.

The control-Y command executes your subroutine starting at $3F8. All there is room for at $3F8 is a JMP to where your subroutine is REALLY stored. When you boot DOS, a JMP $FF65 instruction is inserted at $3F8, setting the control-Y command to merely re-enter the monitor. By changing the address of that JMP instruction, you can have it jump to your own code. If you look ahead at the listing of MONITOR EXTENSIONS, lines 1170-1210 store the address of my CTRLY subroutine into the JMP instruction.

I have thought of at least three features that I miss all the time in the monitor. (I just now thought of several more, but they will have to wait for another article.)

  1. The monitor already includes the ability to add and subtract single-byte values, and print the single-byte result. I would like to be able to do this with 16-bit values.
  2. The monitor can already dump memory in hexadecimal, but I want to see it as ASCII characters also. There is room on the screen for both at once.
  3. The monitor can already disassemble code to the screen, 20 lines at a time. If I want more than 20 lines, I can type "LLLLLL", one L for each 20 lines. But I would like to be able to just specify the beginning and ending addresses for the disassembly, like I do for the hexadecimal printout.

If you enter the MONITOR EXTENSIONS program, these three functions will be added to the monitor. To add or subtract two values, type the two values separated by "+" or "-"; then type control-Y, and carriage return. To dump in combined hex and ASCII, type the beginning and ending addresses separated by a period, then control-Y and carriage return. To disassemble a range of memory, type the beginning and ending addresses separated by a period, then control-Y, "L", and a carriage return.

Looking again at the listing, lines 1230-1340 figure out which of the above command options you have typed in. When the monitor branches to $3F8, the following conditions have been set up:

     (A) = 0 if only one address was typed;
         = code for separator character if two addresses
           were typed.

     (X) = 0 if no hex digit typed immediately before the
             control-Y;
         = 1 if any hex digits immediately before the
             control-Y.

     (Y) = 0

     ($34) = index into input buffer of next character after
             the control-Y.

Up to five 16-bit variables (called A1, A2, A3, A4, and A5) are filled from the hexadecimal values in the command. If you type a "<" after the first value, then that value will be stored in A4 and A5 (A4 is at $42,43; A5 at $44,45). If you type a ".", "+", "-", or ":" after a hexadecimal value, then that value will be stored in A1 and A3 (A1 is at $3C,3D; A3 at $40,41). If you type a hexadecimal value immediately before the control-Y, then that value will be stored in A2 (which is at $3E,3F).

Looking again at lines 1230-1340, I branch to SUB if the separator is "-", or ADD if it is "+". If the separator is a colon, I just return; I don't have any control-Y command which accepts a colon separator. If the separator is not any of the above, then either there was no separator, or it was a period. In both of these cases, I want to dump memory. If the character after the control-Y is not "L", then I want a combined hex-ASCII dump; if it is "L", I want disassembly. Line 1340 increments the buffer pointer so that the "L" command will not be re-executed by the regular monitor routine after my control-Y routine is finished.

Lines 1360-1450 control the disassembly option. I used a monitor subroutine to copy the beginning address from A1 into PC. Then I wrote a loop that calles the monitor routine to disassemble one line, and then checks to see if we have reached the ending address. Compare this to the code in the monitor ROM at $FE5E through $FE74. There is one trick in this code. I wanted to compare PC to END.ADDR, and continue if PC was less than or equal to END.ADDR. The normal comparison technique would either SET carry at line 1390, but I CLEARed it. This has the same affect as using one less than the value in PC as the first comparand. I needed this, because BCC at line 1440 only branches if the first comparand is LESS THAN the second one. In other words, since it is difficult to implement IF PC <= END.ADDR THEN ..., I implemented IF PC-1 < END.ADDR THEN ....

Lines 1470-1780 perform the combined hex-ASCII dump. I must give credit to Hugh McKinney, of Dunwoody, GA, for some of the ideas in this code. Just for fun, I set it up to always print complete rows of eight bytes; the starting address is rounded down to the nearest multiple of 8, and the ending address is rounded up. This means that typing just one address will get you eight, also.

I had to make a judgment about what characters to display for the ASCII portion of the dump. There are 256 possible values, and only 96 printing characters. In fact, if you don't have a lower case adapter, your screen only shows 64 printing characters (unless you count inverse and flashing characters as different; in that case you have 192). I decided to display control characters (codes 00-1F and 80-9F) as flashing characters (codes 40-5F). Codes 60-7F and E0-FF display as lower case characters if you have a lower case adapter. Codes 20-5F and A0-DF display as normal video characters (the standard upper case set). If you want a different mapping, change lines 1660-1690 to do it your way.

Lines 1800-1930 perform the 16-bit addition and subtraction in the normal way. Lines 1940-1980 print out an equal sign, and the value.

If you get really ambitious, you might try programming for your Apple II Plus the S and T commands that Apple removed from the Autostart ROM. You can just about copy the code right out of the reference manual. You might also like to add a memory move command that will work correctly even when the target area overlaps the source area.

 1000  *---------------------------------
 1010  *      MONITOR EXTENSIONS
 1020  *---------------------------------
 1030  MON.YSAV   .EQ $34
 1040  PC         .EQ $3A,3B
 1050  BGN.ADDR   .EQ $3C,3D
 1060  END.ADDR   .EQ $3E,3F
 1070  WBUF       .EQ $200
 1080  MON.PRNTYX .EQ $F940
 1090  MON.NXTA1  .EQ $FCBA
 1100  MON.XAM8   .EQ $FDA3
 1110  MON.COUT   .EQ $FDED
 1120  MON.LIST   .EQ $FE63
 1130  MON.A1PC   .EQ $FE75
 1140  *---------------------------------
 1150         .OR $300
 1160  *---------------------------------
 1170  SETUP  LDA #CTRLY
 1180         STA $3F9
 1190         LDA /CTRLY
 1200         STA $3FA
 1210         RTS
 1220  *---------------------------------
 1230  CTRLY  CMP #$AD     MINUS?
 1240         BEQ SUB
 1250         CMP #$AB     PLUS?
 1260         BEQ ADD
 1270         CMP #$BA     COLON?
 1280         BEQ RETURN
 1290         LDY MON.YSAV LOOK BEYOND CONTROL-Y
 1300         LDA WBUF,Y
 1310         LDY #0
 1320         CMP #'L+$80
 1330         BNE DUMP
 1340         INC MON.YSAV
 1350  *---------------------------------
 1360  DISASM JSR MON.A1PC
 1370  .1     LDA #1       DISASSEMBLE ONE LINE
 1380         JSR MON.LIST
 1390         CLC
 1400         LDA PC
 1410         SBC END.ADDR
 1420         LDA PC+1
 1430         SBC END.ADDR+1
 1440         BCC .1
 1450  RETURN RTS
 1460  *---------------------------------
 1470  DUMP   LDA END.ADDR
 1480         ORA #7       FINISH LAST ROW OF 8
 1490         STA PC
 1500         LDA END.ADDR+1
 1510         STA PC+1
 1520         LDA BGN.ADDR START WITH FULL ROW OF 8
 1530         AND #$F8
 1540         STA BGN.ADDR
 1550  .1     JSR MON.XAM8
 1560         SEC          BACK UP POINTER FOR ROW
 1570         LDA BGN.ADDR
 1580         SBC #8
 1590         STA BGN.ADDR
 1600         BCS .2       NO BORROW
 1610         DEC BGN.ADDR+1
 1620  .2     LDA #$A0     PRINT BLANK
 1630         JSR MON.COUT
 1640  .3     LDY #0
 1650         LDA (BGN.ADDR),Y
 1660         ORA #$80     MAKE NORMAL VIDEO
 1670         CMP #$A0     SEE IF PRINTABLE
 1680         BCS .4       YES
 1690         EOR #$C0     MAKE CONTROLS INTO FLASHING ALPHA
 1700  .4     JSR MON.COUT PRINT IT
 1710         JSR MON.NXTA1  ADVANCE POINTER
 1720         BCC .3       MORE ON THIS ROW
 1730         LDA BGN.ADDR
 1740         CMP PC       SEE IF FINISHED WITH DUMP
 1750         LDA BGN.ADDR+1
 1760         SBC PC+1
 1770         BCC .1       NO
 1780         RTS          YES
 1790  *---------------------------------
 1800  SUB    SEC
 1810         LDA BGN.ADDR
 1820         SBC END.ADDR
 1830         TAX
 1840         LDA BGN.ADDR+1
 1850         SBC END.ADDR+1
 1860         JMP AS1
 1870  *---------------------------------
 1880  ADD    CLC
 1890         LDA BGN.ADDR
 1900         ADC END.ADDR
 1910         TAX
 1920         LDA BGN.ADDR+1
 1930         ADC END.ADDR+1
 1940  AS1    TAY
 1950         
 1960         LDA #$BD     EQUAL SIGN
 1970         JSR MON.COUT
 1980         JMP MON.PRNTYX

Errata Bob Sander-Cederlof

Volume 1, Issue 12 (Sep 1981) page 8: Line 1120 in the CHRGET/CHRGOT subroutine should be BCS instead of BEQ.

Volume 1, Issue 7 (Apr 1981) page 8: Insert the following lines:

     1331       TXA          LINE LENGTH
     1332       TAY          IN Y-REG FOR LOOP COUNT
     1333 .2    LDA $200,Y   STRIP SIGN-BITS FROM EACH BYTE
     1334       AND #$7F
     1335       STA $200,Y
     1336       DEY
     1337       BPL .2

This patch is necessary because characters Applesoft strings are supposed to have the sign-bit clear. Everything is fine unless you try compare input strings with constant strings.


DOS Disassembly: $B052-B0B5 AND $B35F-B7FF Bob Sander-Cederlof

Everything from $B800 through $BFFF has now been covered in previous issues of AAL. Also, the 3.3 boot ROM was covered in the August issue. In this issue I present the rest of the boot code and part of the File Manager (FM).

Lines 1000-1570 are a subroutine inside FM which calls RWTS. The main entry at line 1170 assumes (A)=opcode, (X)=track, and (Y)=sector. A subsidiary entry at line 1200 assumes (A)=opcode, and track and sector were already set up. The valid opcodes are SEEK=0, READ=1, WRITE=2, and FORMAT=4.

Lines 1580-1970 are the various exits from FM. Upon exit, (A)=error code and CARRY status is set if there was an error, clear if not.

Lines 1980-2560 are various buffers, constants, and variables for FM. Notice there are some apparently unused bytes in this area.

Lines 2570-3690 are what is written on track 0 sector 0. It loads and executes BOOT.STAGE1 at $0800 (execution starts at $0801). This code reads in RWTS and BOOT.STAGE2. Since most of this area was unused, patches to solve the APPEND problem are here (lines 3020-3640).

Lines 3700-4080 are BOOT.STAGE2, which read in the rest of DOS and jump to $9D84.

Routines to write the DOS image on tracks 0-2, to enter RWTS with interrupts disabled, and to clear a 256-byte buffer are in lines 4090-4990.

Lines 5100-5300 are the IOB and DCT used by FM for all calls to RWTS. The contents of these are described in the DOS Reference Manual pages 95-98.

 1000  *---------------------------------
 1010  *      DOS 3.2.1/3.3 FILE MANAGER $B052-B0B5
 1020  *---------------------------------
 1030         .OR $B052
 1040         .TA $0852
 1050  *---------------------------------
 1060  MON.STATUS .EQ $48
 1070  IOB.ADDR   .EQ $AAC1
 1080  SAVE.FMW   .EQ $AE7E
 1090  RWTS       .EQ $BD00
 1100  MON.INIT   .EQ $FB2F
 1110  MON.HOME   .EQ $FC58
 1120  MON.PRBYTE .EQ $FDDA
 1130  MON.COUT   .EQ $FDED
 1140  MON.SETKBD .EQ $FE89
 1150  MON.SETVID .EQ $FE93
 1160  *---------------------------------
 1170  CALL.RWTS
 1180         STX IOB.TRACK
 1190         STY IOB.SECTOR
 1200  CALL.RWTS.1
 1210         STA IOB.OPCODE (SEEK=0, READ=1, WRITE=2, FORMAT=4)
 1220         CMP #2       OPCODE="WRITE"?
 1230         BNE .1
 1240         ORA FMW.FLAGS SET "LAST OP WAS WRITE" FLAG
 1250         STA FMW.FLAGS
 1260  .1     LDA FMW.VOLUME
 1270         EOR #$FF     UN-COMPLEMENT THE VOLUME #
 1280         STA IOB.VOLUME
 1290         LDA FMW.SLOT16  SLOT # TIMES 16
 1300         STA IOB.SLOT16
 1310         LDA FMW.DRIVE   DRIVE #
 1320         STA IOB.DRIVE
 1330         LDA FMW.SECTSZ  SECTOR LENGTH IN BYTES
 1340         STA IOB.SECTSZ
 1350         LDA FMW.SECTSZ+1
 1360         STA IOB.SECTSZ+1
 1370         LDA #1       SET TABLE TYPE
 1380         STA IOB.TYPE
 1390         LDY IOB.ADDR GET ADDRESS OF IOB
 1400         LDA IOB.ADDR+1
 1410         JSR ENTER.RWTS   PERFORM THE OPERATION
 1420         LDA IOB.ACTVOL   VOUME # FOUND
 1430         STA FMP.DATA+2
 1440         LDA #$FF     RESET VOLUME EXPECTED IN IOB
 1450         STA IOB.VOLUME
 1460         BCS .2       CARRY SET IF RWTS ERROR
 1470         RTS          RETURN TO CALLER
 1480  .2     LDA IOB.ERROR  GET ERROR CODE
 1490         LDY #7       ERR=7 IF VOLUME MISMATCH
 1500         CMP #$20     VOLUME MISMATCH?
 1510         BEQ .3       YES
 1520         LDY #4       ERR=4 IF WRITE PROTECTED
 1530         CMP #$10     WRITE PROTECTED?
 1540         BEQ .3       YES
 1550         LDY #8       ERR=8 (I/O ERROR) FOR ALL OTHERS
 1560  .3     TYA          ERR IN A-REG
 1570         JMP FM.EXIT.ERROR
 1580  *---------------------------------
 1590  *      DOS 3.3 FILE MANAGER $B35F-B5FF
 1600  *---------------------------------
 1610                .OR $B35F
 1620                .TA $0B5F
 1630  FM.EXIT.ERR1  LDA #1 "LANGUAGE NOT AVAILABLE"
 1640                BNE FM.EXIT.ERROR
 1650  FM.EXIT.ERR2  LDA #2 "RANGE ERROR" (OPCODE)
 1660                BNE FM.EXIT.ERROR
 1670  FM.EXIT.ERR3  LDA #3 "RANGE ERROR" (SUBCODE)
 1680                BNE FM.EXIT.ERROR
 1690  FM.EXIT.ERR4  LDA #4 "WRITE PROTECTED"
 1700                BNE FM.EXIT.ERROR
 1710  FM.EXIT.ERR5  LDA #5 "END OF DATA"
 1720                BNE FM.EXIT.ERROR
 1730  FM.EXIT.ERR6  LDA #6 "FILE NOT FOUND"
 1740                BNE FM.EXIT.ERROR
 1750  FM.EXIT.ERR9  JMP $BFED "DISK FULL"
 1760                NOP
 1770  FM.EXIT.ERR10 LDA #10 "FILE LOCKED"
 1780                BNE FM.EXIT.ERROR
 1790  *---------------------------------
 1800  FM.EXIT.GOOD
 1810         LDA FMP.RETURN  GET RETURN CODE (ZERO)
 1820         CLC          SIGNAL NO ERROR
 1830         BCC FM.EXIT  ...ALWAYS
 1840  *---------------------------------
 1850  FM.EXIT.ERROR
 1860         SEC
 1870  *---------------------------------
 1880  FM.EXIT
 1890         PHP          SAVE STATUS ON STACK
 1900         STA FMP.RETURN  RETURN CODE
 1910         LDA #0       CLEAR MONITOR STATUS (JUST IN CASE)
 1920         STA MON.STATUS
 1930         JSR SAVE.FMW SAVE FM WORKAREA IN FILE BUFFER
 1940         PLP          RETRIEVE STATUS FROM STACK
 1950         LDX FMS.STACK RESTORE STACK POINTER
 1960         TXS
 1970         RTS          RETURN TO WHOEVER CALLED FM
 1980  *---------------------------------
 1990  *      SCRATCH AREA
 2000  *---------------------------------
 2010  FMS.TS.CD  .BS 2    T/S OF CURRENT DIRECTORY SECTOR
 2020             .BS 2    ?
 2030  FMS.STACK  .BS 1    S-REG WHEN FM CALLED
 2040  FMS.DIRNDX .BS 1    VARIOUS USES
 2050             .BS 1       "     "
 2060             .BS 2    ?
 2070             .HS 0000FFFF  USED BY INIT TO CLEAR VTOC ENTRY
 2080  *---------------------------------
 2090         .DA #1,#10,#100  DECIMAL CONVERSION TABLE
 2100         .AS -/TIABSRAB/  FILE TYPE CODES
 2110         .AS -/ EMULOV KSID/  MSG SPELLED BACKWARDS
 2120  *---------------------------------
 2130  *      VTOC SECTOR BUFFER
 2140  *---------------------------------
 2150         .BS 256
 2160  *---------------------------------
 2170  *      DIRECTORY SECTOR BUFFER
 2180  *---------------------------------
 2190         .BS 256
 2200  *---------------------------------
 2210  *      FILE MANAGER PARAMETERS
 2220  *---------------------------------
 2230  FMP.OPCODE    .BS 1
 2240  FMP.SUBCOD    .BS 1
 2250  FMP.DATA      .BS 8 USE DEPENDS ON OPCODE
 2260  FMP.RETURN    .BS 1 ERROR CODE
 2270                .BS 1 ?
 2280  FMP.PNTR.WORK .BS 2 ADDR OF WORKAREA IN FILE BUFFER
 2290  FMP.PNTR.TS   .BS 2 ADDR OF T/S LIST IN FILE BUFFER
 2300  FMP.PNTR.DATA .BS 2 ADDR OF DATA IN FILE BUFFER
 2310                .BS 4 ?
 2320  *---------------------------------
 2330  *      FILE MANAGER WORKAREA
 2340  *---------------------------------
 2350  FMW.TS.TS1  .BS 2 T/S OF FIRST T/S LIST SECTOR
 2360  FMW.TS.TSC  .BS 2 T/S OF CURRENT T/S LIST SECTOR
 2370  FMW.FLAGS   .BS 1 CHECKPOINT FLAGS
 2380  FMW.TS.DATA .BS 2 T/S OF CURRENT DATA SECTOR
 2390              .BS 2 DIRECTORY SECTOR INDEX
 2400              .BS 2  # SECTORS PER TS LIST
 2410              .BS 2  1ST SECTOR
 2420              .BS 2  LAST SECTOR+1
 2430              .BS 2  CURRENT SECTOR
 2440  FMW.SECTSZ  .BS 2  SECTOR SIZE IN BYTES
 2450              .BS 4 FILE POSITION
 2460              .BS 2 RECORD LENGTH FROM OPEN
 2470              .BS 2  RECORD NUMBER
 2480              .BS 2  BYTE OFFSET INTO RECORD
 2490              .BS 2  # SECTORS IN FILE
 2500              .BS 6 SECTOR ALLOCATION AREA
 2510  FMW.FILTYP  .BS 1
 2520  FMW.SLOT16  .BS 1
 2530  FMW.DRIVE   .BS 1
 2540  FMW.VOLUME  .BS 1  (COMPLEMENT FORM)
 2550  FMW.TRACK   .BS 1
 2560              .BS 5  <NOT USED>
 2570  *---------------------------------
 2580  *      STAGE 1 OF BOOT (EXECUTES AT $0800)
 2590  *---------------------------------
 2600         .OR $800
 2610         .TA $E00
 2620  BOOT.STAGE1
 2630         .HS 01
 2640  *   COMES HERE AFTER EACH SECTOR IS READ
 2650         LDA $27      NEXT PAGE TO READ INTO
 2660         CMP #9       FIRST TIME HERE?
 2670         BNE .1       NO, SKIP OVER INITIALIZATION
 2680         LDA $2B      SLOT*16
 2690         LSR          GET SLOT #
 2700         LSR
 2710         LSR
 2720         LSR
 2730         ORA #$C0     BUILD ADDRESS INTO ROM
 2740         STA $3F      FOR READING A SECTOR
 2750         LDA #$5C
 2760         STA $3E
 2770         CLC
 2780         LDA BT1.ADDR+1  COMPUTE ADDRESS OF LAST PAGE
 2790         ADC BT1.N       TO BE READ
 2800         STA BT1.ADDR+1
 2810  .1     LDX BT1.N    # PAGES LEFT TO READ - 1
 2820         BMI .2       FINISHED
 2830         LDA SECTOR.NUMBER,X  CONVERT TO PHYSICAL SECTOR #
 2840         STA $3D
 2850         DEC BT1.N
 2860         LDA BT1.ADDR+1
 2870         STA $27
 2880         DEC BT1.ADDR+1
 2890         LDX $2B      SLOT*16
 2900         JMP ($3E)    READ NEXT SECTOR
 2910  .2     INC BT1.ADDR+1  POINT AT STAGE 2 LOADER
 2920         INC BT1.ADDR+1
 2930         JSR MON.SETKBD
 2940         JSR MON.SETVID
 2950         JSR MON.INIT
 2960         LDX $2B      SLOT*16
 2970         JMP (BT1.ADDR)
 2980  *---------------------------------
 2990  SECTOR.NUMBER
 3000         .HS 000D0B0907050301
 3010         .HS 0E0C0A080604020F
 3020  *---------------------------------
 3030  *      DOS 3.3 PATCHES FOR APPEND AND VERIFY
 3040  *---------------------------------
 3050         .OR $B65D
 3060         .TA $0E5D
 3070  APPEND.FLAG .BS 1
 3080  PATCH.DOS33.1
 3090         JSR $A764    LOCATE AND FREE FILE BUFFER
 3100         BCS .1
 3110         LDA #0       CLEAR APPEND FLAG
 3120         TAY
 3130         STA APPEND.FLAG
 3140         STA ($40),Y
 3150  .1     LDA FMP.RETURN
 3160         JMP $A6D2
 3170  *---------------------------------
 3180  PATCH.DOS33.2
 3190         LDA APPEND.FLAG
 3200         BEQ .1
 3210         INC FMP.DATA
 3220         BNE .1
 3230         INC FMP.DATA+1
 3240  .1     LDA #0       CLEAR APPEND FLAG
 3250         STA APPEND.FLAG
 3260         JMP $A546
 3270  *---------------------------------
 3280  PATCH.DOS33.3
 3290         STA FMP.SUBCOD
 3300         JSR $A6A8
 3310         JSR $A2EA
 3320         JMP $A27D
 3330  *---------------------------------
 3340  PATCH.DOS33.4
 3350         LDY #19      LOOK AT FILE POSITION
 3360  .1     LDA ($42),Y
 3370         BNE .4       NOT AT 0000
 3380         INY
 3390         CPY #23
 3400         BNE .1       TEST 4 BYTES
 3410         LDY #25
 3420  .2     LDA ($42),Y
 3430         STA FMP.DATA-25,Y
 3440         INY
 3450         CPY #29      MOVE 4 BYTES
 3460         BNE .2
 3470  .3     JMP $A6BC
 3480  .4     LDX #$FF
 3490         STX APPEND.FLAG
 3500         BNE .3       ...ALWAYS
 3510         .BS 29       <NOT USED>
 3520  *---------------------------------
 3530  *      STRANGE CODE IN THE MIDDLE OF NOWHERE
 3540  *---------------------------------
 3550         JSR MON.HOME  CLEAR SCREEN
 3560         LDA #$C2      PRINT "B01-00"
 3570         JSR MON.COUT
 3580         LDA #1
 3590         JSR MON.PRBYTE
 3600         LDA #$AD
 3610         JSR MON.COUT
 3620         LDA #0
 3630         JSR MON.PRBYTE
 3640         RTS
 3650         .BS 21       <NOT USED>
 3660         .OR $08FD
 3670         .TA $0EFD
 3680  BT1.ADDR .DA $3600
 3690  BT1.N    .DA #9      
 3700  *---------------------------------
 3710  *      SECOND STAGE OF BOOT
 3720  *---------------------------------
 3730         .OR $B700
 3740         .TA $0F00
 3750  BOOT.STAGE2
 3760         STX IOB.SLOT16
 3770         STX IOB.PRVSLT
 3780         LDA #1
 3790         STA IOB.PRVDRV
 3800         STA IOB.DRIVE
 3810         LDA BT.N
 3820         STA BT.CNT
 3830         LDA #2
 3840         STA IOB.TRACK
 3850         LDA #4
 3860         STA IOB.SECTOR
 3870         LDY BT.BT1+1
 3880         DEY
 3890         STY IOB.BUFFER+1
 3900         LDA #1
 3910         STA IOB.OPCODE
 3920         TXA          SLOT*16
 3930         LSR          GET SLOT #
 3940         LSR
 3950         LSR
 3960         LSR
 3970         TAX
 3980         LDA #0
 3990         STA $4F8,X
 4000         STA $478,X
 4010         JSR RW.PAGES
 4020         LDX #$FF
 4030         TXS          EMPTY STACK
 4040         STX IOB.VOLUME
 4050         JMP $BFC8    PATCH TO SETVID AND CLOBBER
 4060  *                   THE LANGUAGE CARD, IF IN SLOT 0
 4070         JSR MON.SETKBD
 4080         JMP $9D84    DOS HARD ENTRY
 4090  *---------------------------------
 4100  *      WRITE DOS IMAGE ON TRACKS 0-2
 4110  *---------------------------------
 4120  WRITE.DOS.IMAGE
 4130         LDA BT.BT1+1 COMPUTE # OF PAGES
 4140         SEC
 4150         SBC IOB.BUFFER+1
 4160         STA BT.CNT
 4170         LDA BT.BT1+1 START AT END, WORK BACKWARD
 4180         STA IOB.BUFFER+1
 4190         DEC IOB.BUFFER+1
 4200         LDA #2       START ON TRACK 2
 4210         STA IOB.TRACK
 4220         LDA #4       SECTOR 4
 4230         STA IOB.SECTOR
 4240         LDA #2
 4250         STA IOB.OPCODE
 4260         JSR RW.PAGES WRITE STAGE2 PART OF DOS
 4270         LDA BT.BT1+1 SET UP BOOT SECTOR IMAGE
 4280         STA BT1.ADDR+1+$B600-$0800
 4290         CLC          COMPUTE STARTING ADDRESS OF WRITE
 4300         ADC #9
 4310         STA IOB.BUFFER+1
 4320         LDA #10      WRITE 10 PAGES
 4330         STA BT.CNT
 4340         SEC
 4350         SBC #1
 4360         STA BT1.N+$B600-$0800
 4370         STA IOB.SECTOR
 4380         JSR RW.PAGES WRITE SECTORS 9-0 ON TRACK 0
 4390         RTS
 4400  *---------------------------------
 4410         .HS 000000000000   <NOT USED>
 4420  *---------------------------------
 4430  *      READ/WRITE A GROUP OF PAGES
 4440  *
 4450  *      BT.CNT       # OF SECTORS TO READ/WRITE
 4460  *      IOB          SET UP FOR FIRST TS TO R/W
 4470  *---------------------------------
 4480  RW.PAGES
 4490         LDA BT.IOB+1 GET IOB ADDRESS
 4500         LDY BT.IOB
 4510         JSR ENTER.RWTS  READ/WRITE ONE SECTOR
 4520         LDY IOB.SECTOR  IGNORE ERRORS IF ANY
 4530         DEY             BACK UP SECTOR #
 4540         BPL .1          STILL IN SAME TRACK
 4550         LDY #15         START WITH SECTOR 15 IN NEXT TRACK
 4560         NOP
 4570         NOP
 4580         DEC IOB.TRACK   BACKWARD THROUGH THE TRACKS
 4590  .1     STY IOB.SECTOR
 4600         DEC IOB.BUFFER+1  DOWN ONE PAGE IN MEMORY
 4610         DEC BT.CNT      ANY MORE PAGES TO DO?
 4620         BNE RW.PAGES    YES
 4630         RTS             NO, RETURN
 4640  *---------------------------------
 4650  *      ENTER RWTS
 4660  *---------------------------------
 4670  ENTER.RWTS
 4680         PHP          SAVE STATUS ON STACK
 4690         SEI          DISABLE INTERRUPTS
 4700         JSR RWTS     CALL RWTS
 4710         BCS .1       ERROR RETURN
 4720         PLP          RESTORE STATUS
 4730         CLC          SIGNAL NO RWTS ERROR
 4740         RTS          RETURN TO CALLER
 4750  .1     PLP          RESTORE STATUS
 4760         SEC          SIGNAL RWTS ERROR
 4770         RTS          RETURN TO CALLER
 4780  *---------------------------------
 4790  *      SET UP RWTS TO WRITE DOS
 4800  *---------------------------------
 4810  SETUP.WRITE.DOS
 4820         LDA FMP.SUBCOD  IMAGE ADDRESS
 4830         STA IOB.BUFFER+1
 4840         LDA #0
 4850         STA IOB.BUFFER
 4860         LDA FMW.VOLUME  VOLUME #
 4870         EOR #$FF        UNCOMPLEMENT IT
 4880         STA IOB.VOLUME
 4890         RTS
 4900  *---------------------------------
 4910  *      CLEAR 256 BYTES STARTING AT ($42,43)
 4920  *---------------------------------
 4930  ZERO.CURRENT.BUFFER
 4940         LDA #0
 4950         TAY
 4960  .1     STA ($42),Y
 4970         INY
 4980         BNE .1
 4990         RTS
 5000  *---------------------------------
 5010  *      PARAMETERS FOR SECOND STAGE OF BOOT PROCESS
 5020  *---------------------------------
 5030         .BS 1        <NOT USED>
 5040  BT.N   .DA #27      # OF PAGES TO R/W (PARAMETER)
 5050  BT.CNT .BS 1        # OF PAGES TO R/W (VARIABLE)
 5060  BT.1S  .DA #10      1ST SECTOR # IN THIS STAGE
 5070         .BS 1
 5080  BT.IOB .DA IOB      ADDRESS OF IOB
 5090  BT.BT1 .DA BOOT.STAGE1+$B600-$0800  ADDR OF 1ST STAGE BOOT
 5100  *---------------------------------
 5110  *      IOB FOR RWTS CALLS
 5120  *---------------------------------
 5130  IOB
 5140  IOB.TYPE   .BS 1    0--MUST BE $01
 5150  IOB.SLOT16 .BS 1    1--SLOT # TIMES 16
 5160  IOB.DRIVE  .BS 1    2--DRIVE # (1 OR 2)
 5170  IOB.VOLUME .BS 1    3--DESIRED VOL # (0 MATCHES ANY)
 5180  IOB.TRACK  .BS 1    4--TRACK # (0 TO 34)
 5190  IOB.SECTOR .BS 1    5--SECTOR # (0 TO 15)
 5200  IOB.PNTDCT .DA DCT  6--ADDRESS OF DCT
 5210  IOB.BUFFER .BS 2    8--ADDRESS OF DATA
 5220  IOB.SECTSZ .BS 2   10--# BYTES IN A SECTOR
 5230  IOB.OPCODE .BS 1   12--0=SEEK, 1=READ, 2=WRITE, OR 4=FORMAT
 5240  IOB.ERROR  .BS 1   13--ERROR CODE: 0, 8, 10, 20, 40, 80
 5250  IOB.ACTVOL .BS 1   14--ACTUAL VOLUME # FOUND
 5260  IOB.PRVSLT .BS 1   15--PREVIOUS SLOT #
 5270  IOB.PRVDRV .BS 1   16--PREVIOUS DRIVE #
 5280             .BS 2
 5290  DCT    .HS 0001EFD8
 5300         .BS 1

Apple Assembly Line is published monthly by S-C SOFTWARE, P. O. Box 280300, Dallas, TX 75228. Phone (214) 324-2050 Subscription rate is $12/year, in the U.S.A., Canada, and Mexico. Other countries add $6/year for extra postage. Back issues are available for $1.20 each (other countries add $1 per back issue for postage). All material herein is copyrighted by S-C SOFTWARE, all rights reserved. Unless otherwise indicated, all material herein is authored by Bob Sander-Cederlof. (Apple is a registered trademark of Apple Computer, Inc.)