Apple Assembly Line
Volume 2 -- Issue 8 May 1982

In This Issue...

Macro Assembler in EPROM

A number of you have asked about the possiblility of getting the S-C Macro Assembler in EPROM to put on an Apple Firmware card, or a CCS 12K Eprom card. If you want to do it, and know how to modify the firmware card to accept EPROMs, I will send a set of 5 EPROMs containing the assembler for $64. If you also need the monitor in EPROM, add another $16. The assumption will be that you have Applesoft on the mother board, and that you already own the S-C Macro Assembler on disk.

Advertising in AAL

Due to the increased costs of printing more than 1600 copies per month, and with the desire to limit the percentage of advertising pages to less than 30% each month, I have decided to raise the page rate again.

For the June 1982 issue, the price will be $50 for a full page, $30 for a half page. So-called "classified" ads, of up to forty words, will be $5.

The Best Book So Far for Beginners

Roger Wagner's book for beginners wanting to learn assembly language programming is now out, at $19.95. (My price is only $18.) Called "Assembly Lines: The Book", it began as simply a reprint of the series Roger writes for Softalk Magazine. But there is a lot more material in the book, and 100 pages of Appendices. Appendix B, 70 pages, is a very lucid description of every 6502 opcode. If you rank yourself as a beginning assembly language programmer, this book will be a tremendous help.

Good Price on the NEC printers

I can ship you an NEC PC-8023A-C dot matrix printer for only $595. I believe the normal list price is $795, but mail order prices are generally less. I also have the Grappler interface card and cable, configured for the NEC printer, for only $150 (normally $175, I think). And if you want both printer and interface at the same time, the combined price is only $695.

I have two of these printers, and like them better than my Epson MX-80. Why? Faster: 100cps instead of 80cps. Fully equipped: standard features include graphics, tractor feed, and friction feed. Handier: the friction feed is just like a typewriter, platen and all; and option switches, should you wish to change them, are accessible without removing any screws. I run one of them with an Epson parallel interface, and the other with the Grappler.

If you would rather have a Spinwriter (that is what this newsletter is printed on), call me for prices.

Vinyl Diskette Pages for your S-C Assembler Binder

I am having 1000 special pages manufactured. They will fit the binder that comes with the Macro Assembler, and will hold one diskette each and a 3x5 index card. For $6 I'll send you ten of them. For $12 I'll send them in a binder. For $36 you can have a binder with ten blank diskettes in vinyl pages. The binder is also just right for storing back issues of AAL.


Secret RWTS Caller Inside DOS Bill Parker

I found a portion of code tucked away in DOS that will perform an RWTS call for you, doing away with the necessity of finding a place to put a controlling subroutine, an IOB, etc.

As you know, RWTS (Read/Write Track and Sector) gives the programmer the ability to read a sector from any specified track and put it in a buffer in RAM. It also allows the programmer to manipulate the buffer and write it back out to any specified track and sector on the disk.

In this 48K DOS routine, which happens to be the same for 3.3 as well as 3.2, all the programmer has to do is to plug in the track and sector desired and whether he wants to read it from disk to the buffer, or write it from the buffer to the disk. (The buffer is a fixed 256-byte location beginning at $B4BB (46267).) A simple CALL 45111 or a JSR $B037 will then perform the transfer. (You must remember to restore the original Read/Write code back to "2" when you are finished, so that the system can write to the directory when it needs to).

Here is a disassembled and commented version of the routine, which (for lack of a better term) I have named "WRTDIR". This should aid in the development of programs that need to examine or alter the contents of a disk.

This routine, which normally writes a directory sector to the disk from the buffer at $B4BB.B5BA (46267-46522), can be used as a general RWTS utility by plugging in:

           Value         Name  $Loc    Loc
     Read/Write (1/2)     RW   $B041  45121
     Track No. ($0-$22)   TK   $B397  45975
     Sector No. ($0-$F)   SC   $B398  45976

Then call 45111 or JSR $B037 and set RW to 2 when done.

 1000           .OR $B037
 1010           .TF B.WRTDIR
 1020  *--------------------------------
 1030  BUFSTHI  .EQ $AAC6
 1040  BUFSTLO  .EQ $AAC5
 1050  CALLRWTS .EQ $B052
 1060  IOBBUF   .EQ $B7F0
 1070  RW       .EQ 2
 1080  SC       .EQ $B398
 1090  TK       .EQ $B397
 1100  *--------------------------------
 1110  WRTDIR   JSR SETBUFAD
 1120           LDX TK
 1130           LDY SC
 1140           LDA #RW
 1150           JMP CALLRWTS
 1160  *--------------------------------
 1170  SETBUFAD LDA BUFSTLO     PUT BUFFER'S
 1180           STA IOBBUF      STARTING ADDRESS IN
 1190           LDA BUFSTHI     INPUT OUTPUT BLOCK
 1200           STA IOBBUF+1
 1210           RTS

Some Patches for the S-C Macro Assembler Bob Sander-Cederlof

1. Loading the Language Card version: When you type "EXEC LOAD LCASM", the language card is loaded with a copy of the monitor from the Apple mother board, and with the file S-C.ASM.MACRO.LC. The EXEC file also makes a small modification to the memory image depending on which language you have on the mother board.

If you have serial number M-5275 or earlier, the EXEC file does not do the final step of turning on the Assembler. I expected you to type the DOS command "INT" (if Applesoft is on the mother board) or "FP" (if Integer BASIC is on the mother board) to enter the Assembler.

You can make a minor change to the EXEC file to make it automatically turn on the assembler after loading. The next to the last line of the EXEC file is now "C082"; change it to "C080" for automatic turn-on.

Here is a step-by-step procedure for the change. Try it on a COPY of the master disk, in case you make a mistake.

  1. Get into the S-C Macro Assembler, either regular of language card version, it doesn't matter which).
  2. Type "AUTO". The Assembler will print ":1000 " and wait for input.
  3. Type five (5) backspaces (left arrow) to position the cursor right after the prompt.
  4. Type "EXEC LOAD LCASM", and the Assembler will load the EXEC file into memory. You will see a list of line numbers on the screen.
  5. Type five backspaces and the word "MANUAL" to turn off the auto-line-number mode.
  6. LIST the lines (type "LIST").
  7. See line 1090? It should be "C082". Type "1090 C080" to change it.
  8. Type "TEXT LOAD LCASM" to save the modified version.
  9. That's all there is to it!

By the way, Bob Potts (from the Bank Of Louisville) was here last week. He brought along a Corvus 5-meg drive, so we put the Language Card version of the Assembler on it. For some reason which we can't explain, the EXEC file hangs up after the BLOAD (but only if the language card has not been loaded since power up). We changed the EXEC file slightly, and it always worked. Instead of doing the BLOAD while in the monitor, we did it from Applesoft. Here is the new version of the file:

     REM LOAD S-C MACRO ASSEMBLER
     REM INTO THE LANGUAGE CARD
     CALL-151
     C081 C081
     F800<F800.FFFFM
     BLOAD S-C.ASM.MACRO.LC
     300:A9 4C CD 00 E0 F0 12 8D 00 E0 A9 00 8D 01 E0 A9 D0 8D 02 E0 A9 CB 8D D1 03 60
     300G
     C080
     3D3G

You may also want to change the HELLO file to include an option to EXEC LOAD LCASM automatically.

2. If you have a Language Card, and your Assembler has a serial number of about M-5030 or earlier, the memory limits are not set up properly in all cases.

Check your copy of S-C.ASM.MACRO.LC by loading it into the language card and typing "$D2C6" from inside the assembler. If you don't have $A0 there, then you need to install this patch:

     1.  Type in these monitor commands:

         :$D2C6:A0 0C
         :$D2C8:20 1E D3 A9 00 91 58 85
         :$D2D0:D9 AD 00 E0 C9 4C F0 08

     2.  Type "BSAVE S-C.ASM.MACRO.LC,A$D000,L$231F".

3. If you have serial number M-5287 or earlier, a more difficult to apply patch is needed to correct a problem in printing the symbol table. If you are using the .TI directive, and if you have several lines of local labels in the symbol table listing, and if the page break comes between two such lines, the listing is messed up in a disastrous way.

To fix the S-C.ASM.MACRO, do the following (very carefully):

     1.  Get into the assembler by typing "BRUN S-C.ASM.MACRO".

     2.  Type the following monitor commands:
         :$2AF<26AF.26D5M
         :$26B1<2AF.2D5M
         :$26AF:84 2F
         :$26C1:CA

     3.  Type "BSAVE S-C.ASM.MACRO,A$1000,L$21D3"

To repair the language card version, do the following:

     1.  EXEC LOAD LCASM, and get into the assembler by typing 
     INT (unless you already made the change to the EXEC file 
     noted above).

     2.  Type the following monitor commands:
         :$C083 C083
         :$2AF<E7FB.E821M
         :$E7FD<2AF.2D5M
         :$E7FB:84 2F
         :$E80D:16

     3.  Type "BSAVE S-C.ASM.MACRO.LC,A$D000,L$231F"

If these patches and my instructions seem too difficult, you can send me $2.50 and your S-C Macro Assembler diskette; I will update it with the new HELLO program, the new LOAD LCASM file, and the patched copies of the assembler.


Benchmarking Block MOVES William R. Savoie

While working on my new soon-to-be-released data file system, I came to see the importance of a speedy 6502 block move routine. I have resisted "moves" like the coming of winter. I have a super two pass sort routine that is very fast. Providing a person has less than 2000 files there is no need to do much file moving, since only pointers need move. (My system has a 64K card, giving me a 112K system.)

Unfortunately, the real world needs some 10,000 or more files, and these of course must be sorted too. By physically moving the files as directed by the sorted pointers, and then moving all this to disk, it is possible to use a merge sort to get the whole job done in the least amount of time. With this preamble behind us, let's get on the move!

I have benchmarked three approaches to moving blocks: the monitor move down (located at $FE2C) which I'm sure you all have used, and its variation, a similar move up routine. Next is the Applesoft block move, and third is a self modifying move which I call "Quick Move".

To ease such a tedious undertaking, I have included a BASIC connection to pass variables and determine the benchmark precision. To help further, I have added a hex converter, a memory dump routine, and an automatic 3D0G vector using the ctrl-Y command from the monitor. To help with the problem of what block of memory was moved where, I wrote a memory fill routine. This acts to place the memory address back into memory, on two-byte boundaries. You can easily read memory to see where it came from.

My first benchmark was a block move of 10,000 bytes made 100 times. The next was a move of 10,240 bytes, again made 100 times. Here are the conditions and resulting times:

                          mon up  mon dn     AS       QM
Case I    Lo=18674=$48F0    47      53      17.2     15.3   seconds
Case II   Lo=18432=$4800    48.7    54.7    16.7     14.7

Note: 0.5 seconds of these values due to BASIC overhead.

As you can see, the old monitor move is not made for high speed moves. For one thing, a two-byte subtraction is carried out for each byte that is moved. It is much more efficient to do the subtraction only once, before you start. A closer look shows that it is faster to move more data, providing you move a whole number of memory pages! The time needed to move the "extra" 240 bytes was negative 0.5 seconds for the Applesoft block move and negative 0.6 seconds for the "Quick Move". There was no sensitivity to start and destination boundaries. "Quick Move" was 3.7 times faster than the monitor move!

I tried putting the first half of Quick Move on page zero at $A0, but the speed improvement was only 0.7 seconds (about 5%) over the time it took when located at $3000.

As a further note, each move routine requires its own parameter organization. If files are to be moved and not lost, attention must be paid to exact specification of end points and lengths.

  100  GOSUB 300: REM INITIALIZE BINARY PROGRAM
  105  A = 0: B = 0: C = 0: J = 0
  106  TEXT : HOME :
       PRINT : PRINT " BLOCK MOVE TESTING"; SPC( 8)"LOOP = ";L + 1:
       PRINT : PRINT "(0) END" SPC( 13)"(1) TEST OVERHEAD":
       PRINT : PRINT "(2) M.MOVE UP" SPC( 7)"(3) M.MOVE DOWN"
  107  PRINT : PRINT "(4) A.MOVE UP" SPC( 7)"(5) QUICK MOVE DOWN":
       PRINT : PRINT "(6) HEX DUMP" SPC( 8)"(7) ADDRESS FILL"
  108  PRINT : PRINT "(8) HEX CONVERT" SPC( 5)"(9) EDIT":
       PRINT : PRINT "(10) MONITOR WITH CTRL-Y RETURN": PRINT 
  109  PRINT "(11) SET CALL LOOP = 100": PRINT 
  110  PRINT "PICK TEST NUMBER (0 TO 11) ";:
       INPUT I: IF I < 0 OR I > 11 THEN 110
  112  HI = 28672: REM $7000 HI BLOCK
  114  LO = 18672: REM $48F0 LO BLOCK
  115  LO = 18432: REM $4800 LO BLOCK
  118  LE = 2056:  REM $808 LENGTH OF MOVE
  130  ON I GOSUB 150,160,170,180,190,200,210,220,230,240,250
  134  IF I = 0 THEN  END 
  136  IF I > 5 THEN  PRINT :
       PRINT "HIT ANY KEY ";: GET A$: GOTO 105
  140  PRINT  CHR$ (7);I;" DONE ";J;" TIMES":
       GET A$: GOTO 105
  150  A = LO: B = LO: C = LO + 1:
       FOR J = 0 TO L:
       CALL BASE,A,B,C:
       NEXT :
       RETURN :
       REM TIME BASIC
  160  A = LO:
       B = HI:
       C = HI + LE:
       FOR J = 0 TO L:
       CALL BASE,A,B,C:
       NEXT :
       RETURN :
       REM M.MOVE UP
  170  A = LO:
       B = HI:
       C = LO - LE - 1:
       FOR J = 0 TO L:
       CALL BASE,A,B,C:
       NEXT :
       RETURN :
       REM M.MOVE DOWN
  180  A = HI:
       B = LO:
       C = HI + LE:
       FOR J = 0 TO L:
       CALL BASE + 3,A,B,C:
       NEXT :
       RETURN :
       REM A.MOVE UP
  190  A = LO:
       B = LO - LE:
       C = HI - LO:
       FOR J = 0 TO L:
       CALL BASE + 6,A,B,C:
       NEXT :
       RETURN :
       REM QUICK MOVE DOWN
  200  CALL BASE + 9,LO - LE - 32,0,HI + LE + 32:
       RETURN 
  210  CALL BASE + 12,LO - LE - 16,HI + LE + 16,0:
       RETURN 
  220  HOME :
       VTAB 8:
       PRINT "INPUT DECIMAL NUMBER ";:
       INPUT N:
       CALL BASE + 15,N:
       RETURN :
       REM HEX CONVERTER
  230  TEXT :
       HOME :
       POKE 33,33:
       LIST :
       END 
  240  HOME :
       FOR I = 1017 TO 1018:
       A =  PEEK (I - 40):
       POKE I,A:
       NEXT :
       POKE 1016,76:
       POKE 72,0:
       CALL  - 151
  250  L = 99: GOTO 105
  300  HIMEM: 12287: REM $2FFF
  305  BASE = 12288:
       IF  PEEK (BASE) = 76 THEN  RETURN :
       REM .OR $3000
  310  D$ =  CHR$ (13) +  CHR$ (4)
  320  PRINT D$"BLOAD B.BLOCK MOVE BENCHMARKS"
  330  PRINT : RETURN 


 1000  *******************************
 1010  *                             *
 1020  *  BENCHMARKING BLOCK MOVES   *
 1030  *                             *
 1040  *  BY WILLIAM R. SAVOIE  3/82 *
 1050  *   LIMERICK TRAINING CENTER  *
 1060  *  C/O GENERAL PHYSICS CORP.  *
 1070  *  341 LONGVIEW RD., LINFIELD *
 1080  *   PENNSYLVANIA   ZIP 19468  *
 1090  *******************************
 1100  
 1110  
 1120  *-------------------------------*
 1130  * APPLE II PAGE ZERO MEMORY USE *
 1140  *-------------------------------*
 1150  
 1160  A1L    .EQ $3C      MONITOR
 1170  A1H    .EQ $3D       USE
 1180  A2L    .EQ $3E       FOR
 1190  A2H    .EQ $3F       BLOCK
 1200  A3L    .EQ $40       MOVE
 1210  A3H    .EQ $41
 1220  A4L    .EQ $42
 1230  A4H    .EQ $43
 1240  FACMO  .EQ $A0      FP REGISTER
 1250  FACLO  .EQ $A1      FP REGISTER
 1260  
 1270  *-------------------------------*
 1280  * OTHER APPLE II MEMORY MAPPING *
 1290  *-------------------------------*
 1300  
 1310  BLTU   .EQ $D39B    BLOCK TRANSFER
 1320  FRMNUM .EQ $DD67    FORMULA=>NUM
 1330  COMA   .EQ $DEBE    CHECK COMA
 1340  AYINT  .EQ $E10C    MAKE INTEGER
 1350  PRNTX  .EQ $F944    PRINT X
 1360  NXTA1  .EQ $FCBA    INCR POINTER
 1370  PRBYTE .EQ $FDDA    PRINT A
 1380  MOVE   .EQ $FE2C    MONITOR MOVE
 1390  
 1400  
 1410  *-------------------------------*
 1430       .OR $3000
 1440       .TF B.BLOCK MOVE BENCHMARKS
 1450  *-------------------------------*
 1460  
 1470  *  THIS CODE ALLOWS SIMPLE ENTRY WITHOUT THE & COMMAND
 1480  BEGIN  JMP MONITOR.MOVE
 1490         JMP APPLESOFT.MOVE
 1500         JMP QUICK.MOVE
 1510         JMP DUMP     HEX OUTPUT
 1520         JMP FILL     LABLE MEMORY
 1530  
 1540  * TO HELP A HEX CONVERTER
 1550  CONVERT
 1560         JSR GETVAR   GET VARIABLE
 1570         JSR PRNTX    HI BYTE OUT
 1580         LDA FACLO    GET LOW BYTE
 1590         JMP PRBYTE   HEX OUTPUT
 1600         .PG
 1610  * THIS CODE FILLS MEMORY WITH IT'S OWN ADDRESS
 1620  * WHICH IS VERY USEFULL FOR CHECKING BLOCK MOVES
 1630  FILL   JSR GM       GET VARIABLES
 1640  .01    LDY #$01     TWO BYTE OFFSET
 1650         LDA A1L      GET LOW BYTE
 1660         STA (A1L),Y  WRITE ADDRESS LO
 1670         DEY          MOVE LEFT
 1680         LDA A1H      GET HI ADDRES
 1690         STA (A1L),Y  WRITE TO MEMORY
 1700         JSR NXTA1    INCREMENT PTR
 1710         JSR NXTA1    TWICE
 1720         BCC .01      GO TELL DONE
 1730         RTS
 1740  
 1750  * A UTILITY DUMP T0 SEE MEMORY FROM BASIC
 1760  DUMP   JSR GM       GET VARS
 1770         LDA $C010    CLEAR STROBE
 1780  .01    LDA $C000    GET KEY IF ONE
 1790         BPL .03      GO DUMP HEX
 1800         LDA $C010    CLEAR STROBE
 1810  .02    LDA $C000    READ KEY AGAIN
 1820         BPL .02      WAIT FOR KEY
 1830         CMP #$8D     'RETURN' KEY?
 1840         BEQ .04      EXIT
 1850         LDA $C010    CLEAR STROBE
 1860  .03    JSR $FDA3    8 HEX OUT
 1870         LDA A2L      END LOW
 1880         CMP $A1      DESIRED LOW
 1890         LDA A2H      HI TOO
 1900         SBC $A0      ENOUGH?
 1910         BCC .01      GO TELL DONE
 1920  .04    RTS
 1930  
 1940  * GET VARIABLE FROM BASIC
 1950  * MUST BE <65357 OR SYNTAX ERR
 1960  * PLACE IN REGISTERS X AND A
 1970  
 1980  GETVAR JSR COMA     MUST SEE COMA
 1990         JSR FRMNUM   GET NUMBER
 2000         JSR AYINT    MAKE INTEGER
 2010         LDX FACMO    HI BYTE
 2020         LDA FACLO    LOW BYTE
 2030         RTS
 2040  
 2050  
 2060  * GET BASIC VARIABLES INTO THE
 2070  * MONITORS WORK REGISTERS USED BY
 2080  * COMMANDS M,V,G,L,S,T,-,+,..ETC
 2090  
 2100  GM     JSR GETVAR   BLOCK START
 2110         STA A1L      LOW BYTE
 2120         STX A1H      HI BYTE
 2130         JSR GETVAR   BLOCK END
 2140         STA A2L      LOW
 2150         STX A2H      HI BYTE
 2160         JSR GETVAR   DEST. START
 2170         STA A4L      LOW
 2180         STX A4H      HI
 2190         RTS
 2200         .PG
 2210  *-------------------------------*
 2220  * THE OLD MONITOR MOVE          *
 2230  * MOVE BLOCK OF MEMORY UP/DOWN  *
 2240  *-------------------------------*
 2250  
 2260  MONITOR.MOVE
 2270         JSR GM       SET UP MOVE
 2280         LDA A1L      START LOW
 2290         CMP A4L      END LOW
 2300         LDA A1H      START HI
 2310         SBC A4H      WHICH BIGGER?
 2320         BCS MOVEDN   GO DOWN IN MEM
 2330  
 2340  MOVEUP LDY #$00     CLEAR INDEX
 2350  .01    LDA (A2L),Y    GET DATA
 2360         STA (A4L),Y    PUT DATA
 2370         LDA A4L      GET INDEX
 2380         BNE .02      PAGE CROSS?
 2390         DEC A4H      HI BYTE
 2400  .02    DEC A4L      LOW BYTE
 2410         LDA A2L
 2420         CMP A1L      END YET?
 2430         LDA A2H
 2440         SBC A1H
 2450         LDA A2L
 2460         BNE .03      PAGE CROSS?
 2470         DEC A2H      HI BYTE
 2480  .03    DEC A2L      LOW BYTE
 2490         BCS .01      GO TELL DONE
 2500         RTS
 2510  
 2520  MOVEDN LDY #$00     CLEAR INDEX
 2530         JMP MOVE     MONITOR MOVE
 2540  
 2550  
 2560  *------------------------------*
 2570  * AND ALONG CAME THE PEOPLE AT *
 2580  * MICROSOFT WITH THEIR MOVE    *
 2590  *------------------------------*
 2600  
 2610  APPLESOFT.MOVE
 2620         JSR GETVAR   HI ADDRESS OF BLOCK TO MOVE
 2630         STA $96      LOW
 2640         STX $97      HI BYTE
 2650         JSR GETVAR   BLOCK END
 2660         PHA          SAVE TELL LAST
 2670         TXA          NEED 9B,9C
 2680         PHA          TO GETVARS
 2690         JSR GETVAR   HI ADDRESS OF DISTINATION
 2700         STA $94      LO&HI BYTES
 2710         STX $95
 2720  
 2730  * WE USED $9B,9C TO GET THE TWO BYTE FP VALUE
 2740         PLA          END OF BLOCK
 2750         STA $9C      HI BYTE
 2760         PLA
 2770         STA $9B      LOW BYTE TOO
 2780         SEC          SUBTRACT COMMING
 2790         JMP BLTU     A.MOVE
 2800         .PG
 2810  *-------------------*
 2820  * MOVING IN RAM CAN *
 2830  * BE EVEN FASTER    *
 2840  *-------------------*
 2850  
 2860  QUICK.MOVE
 2870         JSR GETVAR   GET START
 2880         STA .01+1    LOW BYTE
 2890         STA .06+1    COPY HERE TOO
 2900         STX .01+2    HI
 2910         JSR GETVAR   DESTINATION
 2920         STA .02+1    LO
 2930         STA .07+1    COPY HERE TOO
 2940         STX .02+2    HI BYTE
 2950         JSR GETVAR   END ADDRESS
 2960         TXA          SET TEST FOR
 2970         BEQ .05       MOVE<256?
 2980  
 2990  * X=PAGE NUMBERS TO MOVE
 3000         LDY #$00     INDEX=0
 3010  .01    LDA $4800,Y  SOURCE
 3020  .02    STA $4000,Y  DESTINATION
 3030         INY          NEXT BYTE
 3040         BNE .01      SMALL MOVE
 3050  .03    INC .01+2    HI SOURCE
 3060  .04    INC .02+2    HI DESTINATION
 3070         DEX          DONE?
 3080         BNE .01      Y=0 SO MOVE PAGE
 3090  
 3100  * SET UP REMAINING MOVE
 3110  .05    LDY $A1      LOW BYTE LENGTH
 3120         BEQ .08      GO IF NONE
 3130         LDA .01+2    COPY HI BYTE
 3140         STA .06+2    FOR SOURCE
 3150         LDA .02+2     AND
 3160         STA .07+2     DESTINATION
 3170  
 3180  * NOW WITH X=0 START MOVING LOW BYTE OF LENGTH
 3185  * (Y) = REMAINING BYTES TO MOVE
 3190  .06    LDA $4800,X  SOURCE
 3200  .07    STA $4000,X  DESTINATION
 3210         INX          NEXT
 3220         DEY          MOVE ENOUGH?
 3230         BNE .06      GO TELL DONE
 3240  .08    RTS
 3250         .LIST OFF

New AED Features Bob Sander-Cederlof

Bill Linn, author of AED, stopped by the other day. Bill is a Vice President at Cullinane Corporation, and was in Dallas for a user convention. Since it was Sunday, and we are both earnest Christians, he spent the morning with Becky and me and the kids at church. Later we all went out for an excellent lunch at the local Harvey House.

He brought the latest version of AED along, and showed me all the new features. AED now has keyboard macros! They are user definable, but he has predefined quite a few for you. If you type two escapes in a row, the top 18 lines of the screen are used to display a menu of all the currently defined escape macros. Escape followed by some other character inserts the corresponding text string at the current cursor position.

There is a utility program on the disk for use in defining your own macro strings, and it is very easy to use. In fact, you use AED editing to modify simple DATA statements within the utility itself. When you are through with your changes, the utility modifies the macro table within AED and on the disk.

Again I say, if you are not fully satisfied with your current stable of Applesoft programming aids, you owe it to yourself to buy AED. It will save you countless hours of frustrating retyping as you create and edit and restructure and debug and modify Applesoft programs.


Another Recursive Macro Lee Meador

Last month I sent Bob a recursive macro definition that he put in the AAL for everyone to see. In case you have forgotten what recursive means, let me explain it somewhat. If you have a macro that calls itself under certain conditions, that macro is called 'recursive'. It's kind of like the plastic cup I had when I was little. There was a picture on the cup of a little bear sitting in a high-chair. The bear was holding a plastic cup and on the cup was a picture of a little bear in a high-chair holding a plastic cup with a picture of a bear in a high-chair holding a cup with a picture of a bear......

I always used to wonder how many bears there were and how big the littlest one was. Now, when we are using recursion in our macros we want to be sure we know that there is a last little bear -- a last call -- a way of leaving the lowest level of recursion. Otherwise (since each recursive call uses up some memory/stack space) we will soon run out of room to store the return information and BOOM goes the assembly.

My macro uses the principle of 'divide and conquer' to allocate a chosen number of bytes all of which hold the value we want them to have. We might use this macro with a table of 128 bytes. All non-alphabetic characters (codes $0 to $40, plus assorted others) will have the value $FF in their corresponding bytes in the table. All alphabetics will have a number indicating their relative frequency in English text. We could set up the table with lines and lines of hex strings for all the non-alphabetics. Or we could let the program filter out the alphabetics and use a shorter table. But for the sake of the example let us assume we need the program to be as fast as possible and memory space is no object.

Here is the macro definition I came up with:

     .MA DB          Macro name is "DB"
     .DO ]1<2        If only one left,
     .DA ]2             generate a data byte
     .ELSE           If more than one left,
     >DB ]1/2,]2        call DB for half of them,
     >DB ]1+1/2,]2      and call DB again for the other half
     .FIN
     .EM

Here is the table I talked about:

     >DB $41,#$FF   65 bytes filled with $FF
     .HS 00000000   upper case (fill in your own frequencies)
     ......
     >DB 5,#$FF     5 bytes filled with $FF
     .HS 00000000   lower case
     ......
     >DB 5,#$FF     5 bytes filled with $FF

Here is a sample program to use such a table:

     LDA CHARACTER.BYTE    get character
     TAY
     LDA TABLE,Y           get frequency
     ......                and then you have to use it

The macro calls itself to set up half of the area desired and then calls itself again to set up the other half. The adding of one to the second call makes sure that both odd and even values for the first parameter will work. If the call only needs one byte to be set up then a .DA is used to take care of it. That provides the end of the little bears -- when the first parameter is one.

When I needed a macro like this my first idea was to have each recursive call take care of one byte and then call itself to take care of the rest. If the macro was called with zero repetitions then nothing would be done except end the macro. The problem with that method is the amount of stack space used as the recursion goes to very deep levels. The method used in the example will only recurse, for example, 8 levels to generate 127 bytes of data.

By the way, notice that you must put the pound sign (#) on the second parameter if you want to generate single bytes. Leaving it off will generate two-byte values of data. I chose that method to make the macro more flexible. You might want to put the pound sign (#) inside the macro to make it safer in case you always want to generate single bytes of data. Also, you can use calculated values like #'F+$80 to generate tables of some character value.

The assembly of recursive macros produces quite a few extra lines in the listing, so after checking it out you will probably want to turn off the listing of the macro expansion with ".LIST MOFF". Here is a sample listing with the macro expansion listing on:

               1000  *--------------------------------
               1010  *      LEE MEADOR'S SECOND RECURSIVE MACRO
               1020  *--------------------------------
               1030         .MA DB
               1040         .DO ]1<2
               1050         .DA ]2
               1060         .ELSE
               1070         >DB ]1/2,]2
               1080         >DB ]1+1/2,]2
               1090         .FIN
               1100         .EM
               1110  *--------------------------------
    0800-      1120         >DB 3,#0
               0000>         .DO 3<2
               0000>         .ELSE
    0800-      0000>         >DB 3/2,#0
               0000>         .DO 3/2<2
    0800- 00   0000>>         .DA #0
               0000>>         .ELSE
               0000>>         .FIN
    0801-      0000>         >DB 3+1/2,#0
               0000>>         .DO 3+1/2<2
               0000>>         .ELSE
    0801-      0000>>         >DB 3+1/2/2,#0
               0000>>>         .DO 3/2/2<2
    0801- 00   0000>>>         .DA #0
               0000>>>         .ELSE
               0000>>>         .FIN
    0802-      0000>>         >DB 3+1/2+1/2,#0
               0000>>>         .DO 3/2+1/2<2
    0802- 00   0000>>>         .DA #0
               0000>>>         .ELSE
               0000>>>         .FIN
               0000>>         .FIN
               0000>         .FIN

RWTS CALLER (Reading a Whole Track) Bill Morgan

Here is a routine to directly call RWTS (Read/Write Track/Sector), the subroutine in DOS that actually reads for or writes to the disk. Many programs use a routine like this to handle disk I/O, without all the time-consuming overhead of the DOS file manager.

All you need to do to use RWTS directly is to place certain information into an Input/Output control Block (IOB), and tell RWTS where the IOB is. Following is an explanation of the IOB (the addresses are those of DOS's own IOB; you can use it yourself, or build your own wherever is convenient):

     Address        Description

     $B7E8      Table type, always $01
     $B7E9      Slot number times 16, usually $60
     $B7EA      Drive number, $01 or $02
     $B7EB      Volume number expected, $00 matches anything
     $B7EC      Track number, $00 through $22
     $B7ED      Sector number, $00 through $0F
     $B7EE-F    Address of Device Characteristics Table,
                $B7FB for DOS's own DCT
     $B7F0-1    Address of buffer, wherever you want
     $B7F2      Not used
     $B7F3      Byte count if partial sector, $00 normally
     $B7F4      Command     $00 = SEEK
                            $01 = READ
                            $02 = WRITE
                            $04 = FORMAT
     $B7F5      Error Code  $00 = No errors
                            $08 = Error in initialization
                            $10 = Write protect error
                            $20 = Volume mismatch
                            $40 = Drive error
     $B7F6      Last volume number
     $B7F7      Last slot number
     $B7F8      Last drive number

The Device Characteristics Table (whose address is at $B7EE,EF) is a four-byte block containing information about the disk drive. For a standard Apple Disk II this block always contains $00 01 EF D8.

For our purposes, the most important items in the IOB are track, sector, buffer address, and command. By manipulating these, you can read any sector of the disk into any area of memory. All you need to do is set up the IOB, load the A- and Y-registers with the address of the IOB, and JSR $3D9. And if you decide to use the file manager's IOB, you can even set up the A- and Y-registers by a simple JSR $3E3.

RWTS will read the track and sector you choose into your 256-byte buffer. If there was a disk error, RWTS will return with the carry bit set and an error code stored in the IOB. It is then up to the user's program to decide what to do about the error. If there was no error, carry will be clear.

This month we'll set up a short program using the RWTS Caller to read an entire track of the disk into 16 consecutive pages of memory. DOS stores information on a track starting at sector $0F and working back to sector $00, so we must read a sector into the buffer, decrement the sector in the IOB, and increment the buffer pointer.

RWTS.CALLER:

Lines 1680-1850 set up the IOB, transferring values from the program variables.

Lines 1870-1890 load the address of the IOB and call RWTS.

Lines 1900-1910 are necessary to avoid confusing the system monitor. (RWTS and the monitor both use location $48.)

Lines 1960-2030 ring a warning if a disk error occurred, and display the error code.

TRACK READ:

Lines 1260-1350 initialize the variables and call input routines.

Lines 1370-1440 read the sectors from $0f through $00 into the buffer. Line 1410 will end the program if an error occurred.

Line 1460 will become a display routine. (Or, whatever processing you want to do on the buffer.)

Lines 1500-1650 will become input routines; right now they just set the track, buffer and command variables.

CAUTIONS:

1) These routines have very little error-checking. It is very easy to make a trivial error and lose information from a diskette. Always test an RWTS-calling program on a diskette yhou don't care about.

2) If you store information on a blank area of a diskette using these techniques, DOS doesn't know you have taken some space. Unless you modify the VTOC to show that sectors are used, DOS can overwrite your data. (What's a VTOC?, you say. Volume Table of Contents. We'll go into that another time.)

More:

There is more about RWTS on pages 94-98 of Apple DOS Manual, and a goldmine of information in Beneath Apple DOS, by Don Worth and Pieter Lechner. (Quality Software, 1981.)

 1000  *SAVE TRACK READ
 1010  *--------------------------------
 1020  SLOT    .EQ $00     $60 OR $70
 1030  DRIVE   .EQ $01     1 OR 2
 1040  VOLUME  .EQ $02     0 = DON'T CARE
 1050  TRACK   .EQ $03     $00 TO $22
 1060  SECTOR  .EQ $04     $00 TO $0F
 1070  BUFFER  .EQ $05,06   
 1080  COMMAND .EQ $07     1 = READ, 2 = WRITE
 1090  PREG    .EQ $48
 1100  *
 1110  RWTS    .EQ $3D9
 1120  *
 1130  IOB         .EQ $B7E8    DOS'S OWN IOB
 1140  IOB.SLOT    .EQ $B7E9
 1150  IOB.DRIVE   .EQ $B7EA
 1160  IOB.VOLUME  .EQ $B7EB
 1170  IOB.TRACK   .EQ $B7EC
 1180  IOB.SECTOR  .EQ $B7ED
 1190  IOB.BUFFER  .EQ $B7F0,F1
 1200  IOB.COMMAND .EQ $B7F4
 1210  IOB.ERROR   .EQ $B7F5
 1220  *
 1230  PRBYTE .EQ $FDDA
 1240  COUT   .EQ $FDED
 1250  *--------------------------------
 1260  SETUP
 1270         LDA #$60
 1280         STA SLOT     SLOT 6
 1290         LDA #$01
 1300         STA DRIVE    DRIVE 1
 1310         LDA #$00
 1320         STA VOLUME   ANY VOLUME
 1330         JSR GET.TRACK
 1340         JSR GET.BUFFER
 1350         JSR GET.COMMAND
 1360  *--------------------------------
 1370  READ.TRACK
 1380         LDA #$0F     START AT SECTOR $0F
 1390         STA SECTOR
 1400  .1     JSR RWTS.CALLER   READ ONE SECTOR
 1410         BCS EXIT     EXIT IF ERROR
 1420         INC BUFFER+1 NEXT BUFFER PAGE
 1430         DEC SECTOR   NEXT SECTOR
 1440         BPL .1       NOT DONE, READ NEXT SECTOR
 1450  *--------------------------------
 1460  DISPLAY
 1470  *--------------------------------
 1480  EXIT   RTS
 1490  *--------------------------------
 1500  GET.TRACK
 1510         LDA #$11
 1520         STA TRACK    TRACK $11 (DIRECTORY)
 1530         RTS
 1540  *--------------------------------
 1550  GET.BUFFER
 1560         LDA #0
 1570         STA BUFFER   BUFFER AT $4000
 1580         LDA #$40
 1590         STA BUFFER+1
 1600         RTS
 1605         .PG
 1610  *--------------------------------
 1620  GET.COMMAND
 1630         LDA #1
 1640         STA COMMAND  READ
 1650         RTS
 1660  *--------------------------------
 1670  RWTS.CALLER
 1680         LDA SLOT     TRANSFER
 1690         STA IOB.SLOT  VALUES
 1700         LDA DRIVE      INTO
 1710         STA IOB.DRIVE   IOB
 1720         LDA VOLUME
 1730         STA IOB.VOLUME
 1740         LDA TRACK
 1750         STA IOB.TRACK
 1760         LDA SECTOR
 1770         STA IOB.SECTOR
 1780         LDA COMMAND
 1790         STA IOB.COMMAND
 1800         LDA BUFFER
 1810         STA IOB.BUFFER
 1820         LDA BUFFER+1
 1830         STA IOB.BUFFER+1
 1840         LDA #$00
 1850         STA IOB.ERROR
 1860  *--------------------------------
 1870         LDY #IOB     LOAD IOB
 1880         LDA /IOB     ADDRESS
 1890         JSR RWTS     CALL RWTS
 1900         LDA #$00
 1910         STA PREG     SOOTHE MONITOR
 1920         BCS ERROR.HANDLER
 1930         RTS
 1940  *--------------------------------
 1950  ERROR.HANDLER
 1960         LDA #$87     BELL
 1970         JSR COUT     RING
 1980         JSR COUT        ING
 1990         JSR COUT          ING
 2000         LDA IOB.ERROR
 2010         JSR PRBYTE   DISPLAY ERROR CODE
 2020         SEC          EXIT WITH CARRY SET
 2030         RTS

Reading the Game Buttons Jim Kassel

Recently I was asked to come up with a machine language subroutine that involved using the Apple game buttons. Fortunately for me at the time, I forgot that my paddles were not plugged in. I was in for a rude awakening because when I tested the program, it said that all of the buttons were constantly being pushed!

I needed some additional programming to check whether the buttons were even plugged in. The problem occurs because the Apple button logic returns the same value for a pushed button as for a missing button. To get technical: in TTL logic, when an input pin of an IC chip is left unconnected, the chip thinks the pin is at a logic "1". The Apple buttons supply a logic "1" when they are plugged in and pushed. Hence, the hardware cannot tell the difference between a plugged-in-pushed-down button and a missing button.

What the hardware does know for sure is when a button is plugged in and not pushed. This is the only case in which a logic "0" is developed. I had to use this knowledge to write a program which could tell what a logic "1" really means. Since an installed unpushed button does unambiguously announce its presence by a "0" in bit 7 of the input byte, I could make a mask indicating which buttons appear to be installed.

I started by writing the GET.BUTTON.STATUS subroutine. It reads each of the three buttons, and packs the three bit-7's into one byte. The way I wrote it, bit 7 of the returned byte represents button 1, bit 6 is button 2, and bit 5 is button 3. If a button is installed and not pushed, the corresponding bit will be "1"; otherwise it will be "0".

Look at the listing, lines 1250-1350, and I'll describe how GET.BUTTON.STATUS works. I used an indexed loop, where X goes from 2 to 0, step -1, addressing all three of the buttons. Only bit 7 of a button byte is significant. I invert this bit (line 1280), and shift it into the carry status bit (line 1290). Then line 1300 rolls the bit into GB.PUSH. After all three have been read and rolled, I pick up GB.PUSH and zero out the lower five bits at line 1340.

Now lets look at the other three subroutines. GAME.BUTTON.INITIALIZE simply clears out GB.STAT. We have to start with GB.STAT = 0, so that as we discover each installed button we can set its bit. Call this subroutine once at the beginning of your overall run.

GAME.BUTTON.INSTALLED reads the current button status; remember that a "1" here indicates an installed but unpushed button. So line 1140 merges all "1" bits into GB.STAT. You need to call this subroutine several times, at time intervals of several seconds at least, to be sure that every installed button is noticed at least once. (The first few times you call it, you might be pushing down an installed button; then finally you let go, so this subroutine sees the button.)

GAME.BUTTON.PUSHED reads the current button status, and with a little boolean logic comes out with the final result: a "1" indicating an installed and pushed button, and a "0" meaning either a missing button or an unpushed button. The result is in the A-register, and also in GB.PUSH.

Here is a truth table of the logic involved:

     GB.STAT        CURRENT READING    EOR  AND
                                       STAT STAT
     0 (no button)  0 (none or pushed)  0    0
     0 (no button)  1 (unpushed)        1    0
     1 (button)     0 (none or pushed)  1    1
     1 (button)     1 (unpushed)        0    0

There are other possible complications in reading buttons, which I have not handled here. You might want to "debounce" the buttons, so that you don't get false indications of multiple pushes when the button begins to make or break contact. And, you might want to guarantee that any action which happens from a pushed button only happens once per push.

Lines 1400-1910 demonstrate the usage of the subroutines. After clearing the screen, the buttons are continuously monitored until you press any key on the keyboard. The status of each button will be displayed on the screen: button 1 on the to line, button 2 on the second line, and button 3 on the third line. If you hold a button pushed and then start the program, it will say "not installed" until you release the button; from then on it will track the button properly.

If you have the shift key mod installed in button 3, it will say "not installed" until you press the shift key; from then on it will say "pushed" when you are not pushing, and "not pushed" when you are. This is because the sense of the shift key is the opposite of the normal game paddle buttons.

 1000  *--------------------------------
 1010  *      GAME BUTTON SUBROUTINES
 1020  *--------------------------------
 1030  GAME.BUTTON .EQ $C061  BASE ADDRESS
 1040  *--------------------------------
 1050         .OR $800
 1060  *--------------------------------
 1070  GAME.BUTTON.INITIALIZE
 1080         LDA #0
 1090         STA GB.STAT
 1100         RTS
 1110  *--------------------------------
 1120  GAME.BUTTON.INSTALLED
 1130         JSR GET.BUTTON.STATUS
 1140         ORA GB.STAT  SET BITS OF ANY BUTTONS
 1150         STA GB.STAT  PLUGGED IN AND NOT PUSHED
 1160         RTS
 1170  *--------------------------------
 1180  GAME.BUTTON.PUSHED
 1190         JSR GET.BUTTON.STATUS
 1200         EOR GB.STAT  MASK OUT BUTTONS WHICH
 1210         AND GB.STAT    ARE NOT PLUGGED IN
 1220         STA GB.PUSH
 1230         RTS
 1240  *--------------------------------
 1250  GET.BUTTON.STATUS
 1260         LDX #2
 1270  .1     LDA GAME.BUTTON,X
 1280         EOR #$80     INVERT SENSE
 1290         ASL          INTO CARRY BIT
 1300  .2     ROR GB.PUSH
 1310         DEX          NEXT BUTTON
 1320         BPL .1
 1330         LDA GB.PUSH
 1340         AND #$E0     CLEAR EXTRANEOUS BITS
 1350         RTS
 1360  *--------------------------------
 1370  GB.STAT .BS 1
 1380  GB.PUSH .BS 1
 1390  *--------------------------------
 1400  MON.CV     .EQ $25
 1410  MON.HOME   .EQ $FC58
 1420  MON.VTAB   .EQ $FC22
 1430  MON.CLREOL .EQ $FC9C
 1440  MON.COUT   .EQ $FDED
 1450  TEST.MASK  .EQ $00
 1460  *--------------------------------
 1470  TEST   JSR MON.HOME
 1480         JSR GAME.BUTTON.INITIALIZE
 1490  .1     LDA #0
 1500         STA MON.CV
 1510         JSR MON.VTAB
 1520         JSR GAME.BUTTON.INSTALLED
 1530         JSR GAME.BUTTON.PUSHED
 1540         LDA #$84
 1550         STA TEST.MASK
 1560  .2     LDA TEST.MASK
 1570         AND GB.PUSH
 1580         BNE .3       PUSHED
 1590         LDA TEST.MASK
 1600         AND GB.STAT
 1610         BNE .4       NOT PUSHED
 1620         LDY #QTGONE-QTS   NOT INSTALLED
 1630         .HS 2C
 1640  .3     LDY #QTPUSHED-QTS
 1650         .HS 2C
 1660  .4     LDY #QTNOTPSH-QTS
 1670         JSR MSGOUT
 1680         LSR TEST.MASK
 1690         BCC .2
 1700         LDA $C000
 1710         BPL .1
 1720         STA $C010
 1730         RTS
 1740         BCS .1       ...ALWAYS
 1750  *--------------------------------
 1760  MSGOUT LDA QTS,Y
 1770         PHA
 1780         ORA #$80
 1790         JSR MON.COUT
 1800         INY
 1810         PLA
 1820         BPL MSGOUT
 1830         JSR MON.CLREOL
 1840         LDA #$8D
 1850         JMP MON.COUT
 1860  *--------------------------------
 1870  QTS
 1880  QTPUSHED   .AT /PUSHED/
 1890  QTNOTPSH   .AT /NOT PUSHED/
 1900  QTGONE     .AT /NOT INSTALLED/
 1910  *--------------------------------

Macro Branch Library R. F. O'Brien

When I received my copy of the S-C Macro Assembler, my first task was to make up a set of branch macro definitions to use in all my programs. This set will finally eliminate the need to check usage of BCC, BCS, etc., and generally make the programs more readable.

There are six branch-on-tests: >BLT, >BLE, >BGE, >BGT, >BEQ, AND >BNE. All of these would normally be used with two parameters, e.g. >BGT P1,P2...reads: if contents of accumulator is greater than P1 then branch to P2. The first four of these can be gainfully used with only one parameter, after a comparison. Sample program:

     LDA #$8D
     CMP #$8E
     >BLT THERE ... results in a branch to THERE

While >BEQ and >BNE are defined so as to work with only one parameter, there is no reason to so use them - it is easier to just use BEQ and BNE.

The macro >BRA (BRanch Always) is used with one parameter - any others are ignored. >BRA LABEL causes a jump to LABEL, up to +- 127 bytes away. To overcome this limitation I decided to put the macro facility to good use to provide for easy branching to any part of a program - this is necessary for writing relocatable code. I settled for a two-paramter code >JMP:

>JMP P1,P2 ... where P1 is the intermediate or final label you wish to branch to and P2 is the label for this instruction.

Instructions such as the foregoing are inserted anywhere you wish in the program (within 127 bytes of each other) to allow for unlimited branching whilst retaining relocatable code. The following is an example of how you might use the >JMP code:

     1000 A    etc.
               (more code....)
     2000      >JMP A,B
               (more code....)
     3000      >BRA B

When a program designed as the above is run it will simulate an absolute jump to A. The >BRA B will branch to label B, which contains a >BRA A. This sequence of instructions is transparent to the rest of the program, as the first instruction in the >JMP is to skip around the the >BRA within the definition.

This use of macro definitions can easily be extended to the X and Y registers; simply substitute CPX's or CPY's for the CMP's.

Following are some examples of these macros at work:

     >BLT #3,THERE....if (A) is less than 3 then go THERE
     >BGT $40,THIS....if (A) is greater than ($40) then go THIS
     >BEQ #'A,THAT....if (A) is equal to $41 then go THAT

To use these macros in all your programs, place the command .IN MACRO.BRANCH.LIBRARY at the beginning of your source program.

 1000  *  MACRO BRANCH LIBRARY
 1010  *  BY R.F. 0'BRIEN
 1020  *--------------------------------
 1030  *   >BLT P1 (,P2)   BRANCH IF (A) < P1...TO P2
 1040         .MA BLT
 1050         .DO ]#>1
 1060         CMP ]1
 1070         BCC ]2
 1080         .ELSE
 1090         BCC ]1
 1100         .FIN
 1110         .EM
 1120  *--------------------------------
 1130  *   >BLE P1 (,P2)   BRANCH IF (A)<=P1...TO P2
 1140         .MA BLE
 1150         .DO ]#>1
 1160         CMP ]1
 1170         BEQ ]2
 1180         BCC ]2
 1190         .ELSE
 1200         BEQ ]1
 1210         BCC ]1
 1220         .FIN
 1230         .EM
 1240  *--------------------------------
 1250  *   >BGE P1 (,P2)   BRANCH IF (A)>=P1...TO P2
 1260         .MA BGE
 1270         .DO ]#>1
 1280         CMP ]1
 1290         BCS ]2
 1300         .ELSE
 1310         BCS ]1
 1320         .FIN
 1330         .EM
 1340  *--------------------------------
 1350  *   >BGT P1 (,P2)   BRANCH IF (A)>P1...TO P2
 1360         .MA BGT
 1370         .DO ]#>1
 1380         CMP ]1
 1390         BEQ :1
 1400         BCS ]2
 1410  :1
 1420         .ELSE
 1430         BEQ :1
 1440         BCS ]1
 1450  :1
 1460         .FIN
 1470         .EM
 1480  *--------------------------------
 1490  *   >BRA P1         BRANCH ALWAYS...TO P1
 1500         .MA BRA
 1510         CLV
 1520         BVC ]1
 1530         .EM
 1540  *--------------------------------
 1550  *   >BEQ P1 (,P2)   BRANCH IF (A)=P1...TO P2
 1560         .MA BEQ
 1570         .DO ]#>1
 1580         CMP ]1
 1590         BEQ ]2
 1600         .ELSE
 1610         BEQ ]1
 1620         .FIN
 1630         .EM
 1640  *--------------------------------
 1650  *   >BNE P1 (,P2)   BRANCH IF (A)<>P1...TO P2
 1660         .MA BNE
 1670         .DO ]#>1
 1680         CMP ]1
 1690         BNE ]2
 1700         .ELSE
 1710         BNE ]1
 1720         .FIN
 1730         .EM
 1740  *--------------------------------
 1750  *   >JMP P1,P2      BRANCH ALWAYS TO P1 BY
 1760  *                   BRANCHING TO P2 (SEE ARTICLE)
 1770         .MA JMP
 1780         CLV
 1790         BVC :1
 1800  ]2     CLV
 1810         BVC ]1
 1820  :1
 1830         .EM

Apple Assembly Line is published monthly by S-C SOFTWARE, P. O. Box 280300, Dallas, TX 75228. Phone (214) 324-2050 Subscription rate is $15 per year, in the USA, sent Second Class Mail; $18 per year sent First Class Mail in USA, Canada, and Mexico; $28 per year sent Air Mail to other countries. Back issues are available for $1.50 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.)