Apple Assembly   Line
Volume 1 -- Issue 10 July 1981

In This Issue...

Renewing Subscriptions

The 4-digit number in the upper right corner of your mailing label is the expiration date of your subscription. The first two digits are the year, and the last two digits are the month of the last issue you have paid for.

If your label says "8109", now is the time to renew to be sure of uninterrupted service.

Beneath Apple DOS

In the few weeks since I sent out last month's AAL, with the review of this book, I have sold 85 copies! My apologies if your shipment was delayed a little. Last Friday at 3:30 a shipment of 100 copies arrived; at 5:45 I took about 50 packages to the UPS station. Another 10 went out by mail this morning. A lot of work, but a lot of fun too.

I expect another shipment of 100 copies about the time you get this newsletter, so go ahead and order your copy if you have been waiting.

Using Firmware Card in Slot 4

Are you tired of getting "LANGUAGE NOT AVAILABLE" errors? Do you have a 16K RAM card, and also an old Firmware Card with one of the Basics on it? You can patch DOS to allow the Firmware Card to be put in slot 4, and still keep your RAM card in slot 0 for Pascal or whatever. With DOS loaded, type CALL -151 to get to the monitor; then patch:

     *A5B8:C0
     *A5C0:C1

Get back into Basic (3D0G), and INIT a disk with the modified DOS. If you have a disk utility program, you can patch the DOS image on an existing disk the same way. (From Michael W. Sanders, Decatur, GA)


The Lower Case Apple Bob Matzinger

It occured to me that, since I have installed a Dan Paymar Lower Case Adapter, there ought to be a better way to generate lower case characters than by RAM-resident software.

The major problem is the F8 ROM. The CAPTST routine at $FD7E will not allow lower case characters to pass; if they get this far, they will be converted to upper case here. I cannot figure a reason for this routine, since the Apple will not generate lower case codes in the first place!

Anyway, there are only two ways I know of to avoid CAPTST: write my own line input subroutine (I want to avoid that!), or burn a new F8 ROM. All I would have to change is one lousy byte, at $FD83, from $DF to $FF. Seems like a waste of time...or is it? Maybe, since I am going to the trouble of burning the ROM, I can add some routines to extend the capabilities of my keyboard to access ALL of the ASCII characters.

That is what I decided to do. But! How do I make it transparent? It should not interfere with or be interfered by any program or language.

Within the monitor routines there are two that are not used; in fact, they were removed when the Autostart ROM came about. These are the 16-bit multiply and divide routines from $FB60 through $FBC0. I can insert my new code there.

I also need two RAM locations for shift lock and case flags. I must find two locations that would probably NOT be used by any other program. There are a number of location in zero page that are not normally used; the bottom of the stack and the top of the input buffer might not be used. Checking that out, however, I have found that most other people have thought of these locations already. Where can I go?

I found two bytes not used by anyone, inside the screen buffer area. They are reserved for the board plugged into slot 6, which in my case is the disk controller. The disk controller does not use locations $077E and $07FE ($0778+slot# and $07F8+slot#). More than likely, nobody would use these locations (at least that is what I am gambling on).

Now that I have room for flags, the next step is to write the routines to fit between $FB60 and $FBC0, and set up calls to them. I have to be careful not to change any other routines. Here is what I want:

  1. Upon RESET, initialize to upper case.
  2. Have a shift and shift-lock routine.
  3. Be able to enter all ASCII characters.

When RESET is pressed, or when the Apple is turned on, the 6502 microprocessor executes a JMP indirect using the address at $FFFC and $FFFD. This effectively jumps to $FF59 in the monitor which is the reset routine. The reset routine calls INIT at $FB2F, which in turn ends with a JMP VTAB at $FB5D. If I change that last instruction, it can fall into the area formerly occupied by the multiply routine. How convenient! I'll put the code there to set upper case mode.

Most programs written for use with the Paymar Adapter have their own input routines. The monitor routines are not used. Therefore my changes should have no adverse effect on these programs.

The next thing I had to decide was which control-keys to use for shift, shift-lock, and the three characters not available from the standard Apple keyboard. I didn't want to use the escape key, since it is used by so many other programs. I finally chose these:

One final problem to overcome is passing the cursor over a lower case character. The cursor, in the normal monitor, makes the character under the cursor flash. A lower case character will flash in upper case, so you cannot tell whether it was lower or upper case without moving the cursor. I decided to make lower case characters under the cursor display as inverse upper case, rather than flashing. That way there is no doubt.

Now how do we get the patches into the ROM? First we need to get a copy of the standard ROM code into RAM. Then assemble the patches, and save the patched copy on disk. From inside the S-C Assembler II, type:

     :$6800<F800.FFFFM             (copy monitor into RAM)
     :ASM                          (assemble the patches)
     :BSAVE F8 EPROM,A$6800,L$800  (save patched monitor)

After the patches had been made, I used ROMWRITER, by Mountain Hardware, to burn a 2716 EPROM. This EPROM was then inserted, with appropriate adaptation, in the F8 socket on my Apple mother board.

[NOTE: A 2716 EPROM WILL NOT DIRECTLY REPLACE THE F8 ROM. EITHER THE MOTHER BOARD CIRCUITRY MUST BE MODIFIED OR AN APPROPRIATE SOCKET ADAPTER MUST BE USED.]

If you have a 16K RAM card, you can try the patched monitor without burning a ROM. After the patches have been assembled into the standard copy at $6800, type the following:

     :$C081 C081                 (write enable RAM card)
     :$F800<6800.6FFFM           (copy new monitor up)
     :$C080                      (turn on RAM version)

After putting the patched monitor into the RAM card, you have to patch the assembler to turn off its own CAPTST, if you want to see the lower case stuff work inside the assembler. Type:

     :$139B:FF

This will make the assembler allow lower case characters to be typed in, but they are only legal in comments.

Some more words of caution. These patches are for the "old" monitor ROM. They will not work in the Autostart ROM. My choice of control-K and control-L may upset some users. Control-K is used as a monitor command equivalent for IN#slot, and control-L is used to generate a form-feed on some printers. I can always go to BASIC for the IN#slot, and my printer has a button for form-feed. I feel that the full upper-lower case ability is much more desirable.

WHEN ALL ELSE FAILS, READ THE INSTRUCTIONS AGAIN!

 1000  * LOWER CASE F8 ROM.1
 1010  *---------------------------------
 1020  * THESE PATCHES ARE FOR THE "OLD" F8 ROM.  THEY
 1030  * WILL NOT WORK INTO THE AUTOSTART ROM MONITOR
 1040  * ROUTINES.
 1050  *
 1060  * OPERATION: $6800<F800.FFFFM
 1070  *            ASM  (ASSEMBLE THIS CODE)
 1080  *            BSAVE F8 EPROM,A$6800,L$0800
 1090  *---------------------------------
 1100  CTRLK  .EQ $8B      LEFT BRACKET OR BRACE
 1110  CTRLL  .EQ $8C      BACKSLASH OR VERTICAL BAR
 1120  CTRLO  .EQ $8F      UNDERLINE OR RUBOUT
 1130  CTRLZ  .EQ $9A      SHIFT OR SHIFT LOCK
 1140  CASE   .EQ $77E     FOR DOS IN SLOT 6
 1150  LCKFLG .EQ $7FE     FOR DOS IN SLOT 6
 1160  KYSTRB .EQ $C010
 1170  VTAB   .EQ $FC22
 1180  RDKEY  .EQ $FD0C
 1190  *---------------------------------
 1200  PATCH1 .OR $FB5D
 1210         .TA $6B5D
 1220  *
 1230  SETCAS LDY #0       PART OF RESET ROUTINE TO INIT
 1240         STY CASE     UPPER CASE MODE
 1250         INY
 1260         STY LCKFLG
 1270         JMP VTAB
 1280  *---------------------------------
 1290  PATCH2 .OR $FD2B
 1300         .TA $6D2B
 1310  *
 1320         JMP LCADAP   FROM KEYIN ROUTINE TO LOWER
 1330         NOP          CASE "ADAPTER"
 1340  *---------------------------------
 1350  PATCH3 .OR $FD82
 1360         .TA $6D82
 1370  *
 1380         AND #$FF     ALLOW LOWER CASE TO PASS
 1390  *---------------------------------
 1400  PATCH4 .OR $FD11
 1410         .TA $6D11
 1420  *
 1430         JSR FORM     DISPLAY CHARACTERS UNDER THE
 1440         NOP          CURSOR CORRECTLY
 1450  *---------------------------------
 1460  * THE CTRL-Z KEY IS USED LIKE THE SHIFT KEY ON A
 1470  * TYPEWRITER:  ONE CTRL-Z WILL ENTER ONE UPPER
 1480  * CASE CHARACTER AND THEN RETURN TO LOWER CASE.
 1490  *
 1500  * TWO CTRL-Z'S IN SUCCESSION WILL PERFORM A
 1510  * "SHIFT-LOCK".  IF THE MODE WAS LOWER CASE,
 1520  * TWO CTRL-Z'S WILL LOCK IN UPPER CASE; IF THE
 1530  * MODE WAS UPPER CASE, TWO CTRL-Z'S WILL LOCK
 1540  * IN LOWER CASE.
 1550  *---------------------------------
 1560  PATCH5 .OR $FB69
 1570         .TA $6B69
 1580  *
 1590  LCADAP BIT KYSTRB   CLEAR KEYBOARD
 1600         CMP #CTRLZ   SEE IF "SHIFT"
 1610         BNE .4       NO, TRY OTHER TESTS
 1620         LDA LCKFLG
 1630         EOR #$80     FLIP BIT 7 (CTRLZ FLAG)
 1640         BMI .1       NEGATIVE IF FIRST CTRL-Z
 1650         EOR #$01     FLIP BIT 0 (LOCK FLAG)
 1660  .1     STA LCKFLG
 1670         BEQ .2       ...IF LOCK FLAG IS CLEAR
 1680         LDA #0       SET UPPER CASE
 1690         BEQ .3       ...ALWAYS
 1700  .2     LDA #$20     SET LOWER CASE
 1710  .3     STA CASE
 1720         JMP RDKEY
 1730  .4     CMP #CTRLK
 1740         BEQ .5
 1750         CMP #CTRLL
 1760         BEQ .5
 1770         CMP #CTRLO
 1780         BNE .6
 1790  .5     ORA #$50     CONVERT TO SPECIAL CHARS
 1800  .6     CMP #$C0     MERGE CASE IF ALPHA
 1810         BCC .7       NOT ALPHA
 1820         ORA CASE
 1830  .7     PHA          SAVE MODIFIED CHAR
 1840         LDA LCKFLG
 1850         BPL .8       ...IF Z-FLAG CLEAR
 1860         LDA #0       CLEAR Z AND LOCK FLAGS
 1870         STA LCKFLG
 1880  .8     BNE .9       ...IF LOCK FLAG IS SET
 1890         LDA #$20     SET LOWER CASE
 1900         STA CASE
 1910  .9     PLA          RETRIEVE MODIFIED CHAR
 1920         RTS
 1930         BRK
 1940         BRK
 1950  *---------------------------------
 1960  * CURSOR DISPLAY FOR EDITING
 1970  *
 1980  FORM   CMP #$E0     IS IT LOWER CASE?
 1990         BCS .1       YES, SO BRANCH
 2000         AND #$3F     ALL CHARACTERS (EXCEPT LOWER
 2010         ORA #$40     CASE) ARE FLASHED
 2020         RTS
 2030  .1     EOR #$E0     MAKE LOWER CASE INTO
 2040         RTS          INVERSE UPPER CASE
 2050  *---------------------------------
 2055  * WRITTEN:  NOVEMBER 1, 1980
 2060  * REVISED:  JUNE 25, 1981
 2070  *  AUTHOR:  BOB MATZINGER
 2080  *           P. O. BOX 13446
 2090  *           ARLINGTON, TX 76013
 2100  *           (817) 265-8122
 2110  *---------------------------------

Screen Printer Bob Sander-Cederlof

Last month I alluded to my trouble in getting a screen printing subroutine to work with the Apple Parallel Interface. I finally got it going, and now it doesn't look hard at all.

The program is set up to be loaded and started with a BRUN command. This doesn't start any printing, however. The initial code just puts a hook address into location $38 and $39, and passes them to DOS. Henceforth, all character-input calls will have to go through my routine at lines 1260-1320.

The SCRN.PRNT subroutine looks at each input character to see if it is a control-P (ascii code = $90). If not, the character is passed on to whatever program tried to read a character. If it is a control-P, the current contents of the screen are printed.

(My printer is in slot 1; if you are using a different slot, change lines 1110 and 1120.)

The actual printing subroutine is really straightforward. It consists of four parts: 1) save current registers and cursor position; 2) initialize Apple Parallel Interface temporaries; 3) print each line of the screen on the printer; and 4) restore the cursor position and registers.

Lines 1350-1410 save the A-, X-, and Y-registers on the stack, followed by the cursor horizontal position. I pushed them on the stack rather than allocate temporaries, but either way will work. Using the stack saves a few bytes of code and 4 bytes of temporary memory, but it takes a few more cycles if you are worried about speed.

Lines 1420-1490 initialize the temporaries used by the code in Apple's Parallel Interface ROM. These temporaries are actually inside the screen buffer memory (between $0400 and $07FF), but they are in bytes that do not get displayed. (There are 64 bytes in the screen buffer that do not get displayed, and which are used by interface cards for temporary memory. These are $478-47F, $4F8-4FF, $578-57F, $5F8-5FF, $678-67F, $6F8-6FF, $778-77F, and $7F8-7FF.) For more information on how the Parallel Interface uses these temporaries, see your manual.

Lines 1500-1670 actually print the screen contents. The X-register is used as a line counter, and runs from 0 to 23. See lines 1500, 1510, and 1650-1670. This is quite analogous to a BASIC statement like FOR I=0 TO 23.

Inside the X-loop, line 1520 computes a new base address for the current line. Then the Y-register is used as a column counter. Lines 1530 and 1600-1620 control the Y-loop. Inside the Y-loop, each character of the line is picked up in turn. Lines 1550-1580 convert inverse or flashing characters to normal ASCII codes for printing. Line 1590 calls on the Parallel Interface program to print one character. (The entry at $Cx02 assumes all temporaries are already set up.) At the end of each line, lines 1630 and 1640 send a carriage return to the printer.

Lines 1680-1700 restore the cursor position and base address pointer, and lines 1710-1750 restore the 6502 registers.

I wrote this program, lines 1340-1760, as a subroutine even though it could have been in-line. I did it so that you can call it directly from your Applesoft or Integer BASIC program, with a "CALL 793". This feature makes the very-valuable screen printer even more useful.

 1000  *---------------------------------
 1010  *      SCREEN PRINTER
 1020  *---------------------------------
 1030  MON.CH     .EQ $24
 1040  MON.BASL   .EQ $28,29
 1050  MON.BASCAL .EQ $FBC1
 1060  MON.VTAB   .EQ $FC22
 1070  MON.RDKEY  .EQ $FD0C
 1080  MON.KEYIN  .EQ $FD1B
 1090  DOS.REHOOK .EQ $3EA
 1100  *---------------------------------
 1110  SLOT   .EQ 1
 1120  PRINT  .EQ $C102    $C002+SLOT*256
 1130  MSTRT  .EQ $5F8+SLOT
 1140  MODE   .EQ $678+SLOT
 1150  ESCHAR .EQ $6F8+SLOT
 1160  FLAGS  .EQ $778+SLOT
 1170  *---------------------------------
 1180         .OR $300
 1190  *---------------------------------
 1200         LDA #SCRN.PRNT
 1210         STA $38
 1220         LDA /SCRN.PRNT
 1230         STA $39
 1240         JMP DOS.REHOOK
 1250  *---------------------------------
 1260  SCRN.PRNT
 1270         JSR MON.KEYIN  GET CHAR
 1280         CMP #$90     CONTROL-P?
 1290         BNE .1
 1300         JSR SCREEN.PRINTER
 1310         JMP MON.RDKEY
 1320  .1     RTS
 1330  *---------------------------------
 1340  SCREEN.PRINTER
 1350         PHA          SAVE REGS
 1360         TXA
 1370         PHA
 1380         TYA
 1390         PHA
 1400         LDA MON.CH   SAVE CH
 1410         PHA
 1420         LDA #40      SET UP APPLE CONTROLLER ROM
 1430         STA MSTRT    TEMPORARIES
 1440         LDA #0
 1450         STA MODE
 1460         LDA #$89
 1470         STA ESCHAR
 1480         LDA #1
 1490         STA FLAGS
 1500         LDX #0       START AT LINE 0
 1510  .1     TXA
 1520         JSR MON.BASCAL  COMPUTE BASE POINTER FOR LINE
 1530         LDY #0       START AT CHAR 0
 1540  .2     LDA (MON.BASL),Y
 1550  .3     CMP #$A0     MAP FLASH AND INVERSE TO NORMAL
 1560         BCS .4
 1570         ADC #$40
 1580         BNE .3       ...ALWAYS
 1590  .4     JSR PRINT
 1600         INY          NEXT CHARACTER
 1610         CPY #40      END OF LINE?
 1620         BCC .2       NO
 1630         LDA #$8D     YES, PRINT CARRIAGE RETURN
 1640         JSR PRINT
 1650         INX          NEXT LINE
 1660         CPX #24      END OF SCREEN
 1670         BCC .1       NO
 1680         PLA          YES, RESTORE CH
 1690         STA MON.CH
 1700         JSR MON.VTAB   RESTORE BASE POINTER
 1710         PLA          RESTORE REGS
 1720         TAY
 1730         PLA
 1740         TAX
 1750         PLA
 1760         RTS

Restoring Clobbered Page 3 Pointers Preston R. Black, M.D.

Here's a very short (14 byte) program which you might find useful. As you know, DOS writes the page 3 vectors (between $3D0 and $3FF) as the last step in the bootstrap process. This is done by copying a portion of DOS onto this area. The image remains in memory and can be used to rewrite the vectors if they are clobbered.

If you have a 48K Apple, the routine which copies the vector data starts at $9E25. My program temporarily patches DOS to isolate the vector-copier, by storing an RTS opcode at the end of the loop ($9E30). After calling the loop, the original value of $9E30 is restored.

I put the subroutine at $BCD0 inside DOS, abecause this area is not used by DOS. It can be placed on all slave diskettes you INIT after patching DOS. With this subroutine installed, you can use all of page 3 for your assembly language program. Once your program is finished, you can JMP $BCD0 to restore $3D0-$3FF to its normal state.

Here is the program, written to assemble into $0CD0-0CDD. After assembly is complete, you can move it into DOS with the monitor command

     :$BCD0<CD0.CDDM   (if issued from inside S-C Assembler II
     or
     *BCD0<CD0.CDD     (if you do it from the monitor.
 1000  *---------------------------------
 1010  *      RESTORE PAGE 3 VECTORS
 1020  *      ----------------------
 1030  *
 1040  *      PRESTON R. BLACK, M.D.
 1050  *      12 JUNE 1981
 1060  *---------------------------------
 1070         .OR $BCD0
 1080         .TA $0CD0
 1090  *---------------------------------
 1100  RESTORE.PAGE.3.VECTORS
 1110         LDA #$60     RTS OPCODE
 1120         STA $9E30
 1130         JSR $9E25
 1140         LDA #$AD     ORIGINAL DATA
 1150         STA $9E30
 1160         RTS

On second thought, 12 bytes is enough. Rather than patching the DOS code to make a subroutine, I can just put a program up at $BCD0 which looks like the code at $9E25. Here is the shorter version:

 1000  *---------------------------------
 1010  *      RESTORE PAGE 3 VECTORS
 1020  *      ----------------------
 1030  *
 1040  *      PRESTON R. BLACK, M.D.
 1050  *      29 JUNE 1981
 1060  *---------------------------------
 1070         .OR $BCD0
 1080         .TA $0CD0
 1090  *---------------------------------
 1100  RESTORE.PAGE.3.VECTORS
 1110         LDX #$3FF-$3D0 # BYTES TO BE COPIED
 1120  .1     LDA $9E51,X    ADDRESS OF VECTORS INSIDE DOS
 1130         STA $3D0,X     VECTOR AREA
 1140         DEX
 1150         BPL .1
 1160         RTS

Corrections to
Variable Cross Reference Program
Bob Sander-Cederlof

The Variable Cross Reference program I printed in issue #2 (November, 1980) had at least three bugs. One of them was reported a long time ago, but I had no idea what the cause was until today. The other two were never reported by anyone, but I discovered their presence and cause today. Eventful day!

Bug #1: After using the VCR program, the first line number LISTed by a subsequent LIST command printed out with all sorts of extra fractional digits. Strange! I finally tracked it down to a page zero location which VCR used. Location $A4 is left with a non-zero value, but Applesoft expects and requires it to be zero. If it is not zero, the floating point multiply subroutine gives wrong answers. The multiplication failure ruins the first number printed after running VCR.

Solution to Bug #1: Add the following two lines to the VCR program.

     1452         LDA #0       CLEAR $A4 FOR APPLESOFT
     1454         STA $A4

Bug #2: The logic for terminating the main program loop (lines 1400-1460) was wrong, and resulted in sometimes adding a phony variable.

Solution to Bug #2: Delete line 1810, and change or add the following lines.

     1650         LDY #3       CAPTURE POINTER AND LINE #
     1692         LDA DATA+1   TEST FOR END
     1694         BEQ .3       YES
     1820 .3      RTS

Bug #3: If your program contained a PRINT statement with a quoted string not separated from a variable by a semi-colon or comma, the GET.NEXT.VARIABLE subroutine would invent new variable names from inside the quoted string! For example, the line PRINT D$"OPEN FILE" would add variables OP (for OPEN) and FI (for FILE).

Solution to Bug #3: Change or add the following lines.

     2752         BEQ .6       YES
     2754         CMP #'"      QUOTATION MARK?
     2762         LDA PNTR     BACK UP PNTR OVER QUOTE MARK
     2763         BNE .7
     2764         DEC PNTR+1
     2765 .7      DEC PNTR
     2766         RTS
     2770 .6      LDA VARNAM+2 SET HIGH BIT

If you have typed in the VCR program, or bought the Quarterly Disk #1 which contained the source, you should now go back and fix these three bugs. (All the line numbers above fit in with the program as printed last November.) Copies of the Quarterly Disk #1 with a serial number of 44 or higher already have been fixed.


Step-Trace Utility Bob Sander-Cederlof

The Motive:

"Not that it was that good, mind you! But we needed something, and they should not have yanked it out without providing some other way to debug machine language programs."

When Apple converted over to the Autostart ROM, they not only removed the hardly-ever-used 16-bit multiply and divide subroutines. They also stripped the S and T commands, which left assembly language programmers naked. How can you possibly debug complicated 6502 code without at least a single step capability?

Several programs are now on the market, in the $50 price range, which give you step, trace, breakpoints, stack display, et cetera. "John's Debugger", from John Broderick & Associates, 8635 Shagrock, Dallas, TX 75238 is one. Someone called me from Augusta, GA, yesterday to tell me about a similar package he has written and wants to market (I'll be reviewing this one; it may become an S-C SOFTWARE product). I saw another ad this month somewhere, but I cannot find it now.

But I wanted to do something special this month for the Assembly Line, so here is a limited STEP-TRACE program...free!

The Manner:

It is set up as a BRUNnable file, to load at $0800. If you want to load it somewhere else, you can put in an origin directive (.OR). The code executed when you BRUN the file (lines 1390-1460) merely installs the "control-Y vector". This enables the control-Y monitor command, which is a user-definable command.

Once the control-Y vector is loaded, you have two new commands. If you type a memory address and a control-Y (and a carriage return), the instruction at that memory address will be disassembled and displayed on line 23. The flashing cursor will be positioned at the end of the disassembled instruction. Just above the cursor, on line 22, you will see the current register contents. Line 24 is an inverse mode line which labels the registers, and reminds you of the options you have.

At this point you can type one of the five register names (A, X, Y, S, or P), or a space, or a carriage return. If you type a carriage return, the trace is aborted and you are returned to the assembler. If you type a space, the disassembled instruction will be exectuted. The new register contents will be displayed, the screen will scroll up, and the next instruction will be disassembled on line 23. If you type a register name, the cursor will be moved under that register. You can type in a new value for the register, and then hit a space for the next register or a return to get ready to execute again.

If you want to step through a little faster, hold down the space bar and the repeat key.

Once you have terminated the trace (by typing a carriage return), you can restart where you stopped by typing a control-Y and a carriage return. Since there is no address given, STEP-TRACE will begin where you stopped the last time. You can stop the trace, do some monitor commands, and then start tracing again.

Two warnings: I wrote STEP-TRACE to be used from inside the S-C ASSEMBLER II. That means all monitor commands, including the control-Y, need to be preceded by a dollar sign ($). If you want to use STEP-TRACE directly from the monitor, and not return inside the assembler after stopping, you need to change line 3500. It now says JMP $3D0, which restarts DOS and the assembler. Change it to JMP $FF69, which restarts the monitor. Line 3470 requires the .DA modification published in the December 1980 issue of AAL. If you haven't installed that yet, then rewrite line 3470 as five separate lines; if you don't, it will assemble without error but it will be WRONG!

The Method:

Now let's look through the listing, and see how it works. When the monitor decodes the control-Y command, the address you typed (if any) is loaded into $3C,3D in page zero. Then the monitor branches to $3F8, where we have already loaded a JMP STEP.TRACE instruction. We step into the action at line 1510.

Lines 1520-1570: the X-register is zero if no address was typed. In this case, we skip around the code to copy the address into MON.PC. If there was an address, copy it into MON.PC.

Lines 1580-1630: Set the stack pointer to $FF, giving the whole stack to the program under test. Move the cursor to the bottom of the screen and print a carriage return.

Lines 1650-1680: Call on subroutines to display the current register values (from the SAVE.AREA at line 4350-4400), disassemble the instruction pointed to by MON.PC, and wait on you to type something on the keyboard. This last subroutine does not return unless you type a space, indicating you want to execute the disassembled instruction.

Lines 1690-1860: Clear the XQT.AREA to NOP instructions. Get the stack pointer from the SAVE.AREA. Pick up the opcode byte, and see if it is one we have to interpret rather than execute (BRK, JSR, RTI, JMP, RTS, or JMP indirect). If so, jump to the appropriate code for each opcode.

Lines 1870-2010: Get the instruction length (less one) in Y, so we can copy the instruction into XQT.AREA. See if the opcode is one of the relative branches; if so, change the displacement to $04, so that we can execute it inside XQT.AREA. Copy the instruction bytes into XQT.AREA. Restore the registers from the SAVE.AREA, restoring status (P-register last of all.

Lines 2030-2160: Execute the instruction. Unless it is a relative branch instruction which branches, jump to did.not.branch. Relative branches which branch go to line 2100, where the effective address is computed and stored in MON.PC.

Lines 2180-2190: A BRK instruction displays the registers and returns to the assembler (aborts STEP-TRACE).

Lines 2210-2250: The RTI instruction checks the stack pointer; if there are not three bytes left on the stack, STEP-TRACE is aborted. If there are three left, the next byte is pulled off the stack and stored in the SAVE.AREA for the P-register. The rest of the RTI instruction is the same as an RTS istruction.

Lines 2260-2350: The RTS instruction checks the stack pointer; if there are not two bytes left on the stacke, STEP-TRACE is aborted. If there are two left, they are pulled off and stored in MON.PC.

Lines 2370-2470: The JSR instruction picks up the current MON.PC, adds two, and pushes the result on the stack. The new stack ponter value is saved in SAVE.AREA. Then a JMP instruction is simulated.

Lines 2480-2490: Simulate a JMP instruction by copying the address into MON.PC.

Lines 2500-2530: Simulate a JMP indirect instruction. Copy the address contained in the two bytes pointed to by the instruction address into MON.PC.

Lines 2550-2640: After a normal executed instruction, save all the registers in SAVE.AREA. Be sure the processor is in binary mode (not decimal).

Lines 2650-2690: Add the instruction length to MON.PC, and go back to get the next instruction.

Lines 2710-2800: Using the current MON.PC as a pointer, pick up the two bytes pointed to and put them into MON.PC. This is used by the JSR, JMP, and JMP indirect processors.

Lines 2820-2930: Set cursor position to line 23, column 27, and wait for you to type a key. If you type a carriage return, abort STEP-TRACE. If you type a space, return to whoever called WAIT.ON.KEYBOARD.

Lines 2940-2990: See if you typed a register name (letter A, X, Y, S, or P). If not, go back and wait till you type something else. If so, go on to line 3000.

Lines 3000-3100: Set inverse mode, position the cursor to the selected register column, and display the current contents of that register in inverse mode. Switch back to normal mode.

Lines 3110-3340: Wait again for you type a character on the keyboard. If you type a hexadecimal digit, shift the current register contents one digit position to the left, and add in the digit you just typed. (You can type as many digits as you want to; the last two you type will be the new contents.) If you type a space or a carriage return, branch to line 3350 or 3400.

Lines 3350-3390: You typed a space, so move over to the next register. If you just modified the S-register, move back to the A-register.

Lines 3400-3440: You typed a carriage return, so scroll up the screen and go back to the top of WAIT.ON.KEYBOARD.

Lines 3450-3470: REG.NAMES defines the register names. REG.INDEX is an index into REG.NAMES and REG.CH. REG.CH is a list of column positions for each of the registers. (If you have not installed the .DA modification from AAL Volume 1, Issue 3, you need to spread the data values out on five separate lines.)

Lines 3490-3500: Clear from the cursor to the end of screen, and return through DOS to the assembler. Change line 3500 if you want to go somewhere else after leaving the STEP-TRACE.

Lines 3540-3590: Adds the contents of the A-register to MON.PC.

Lines 3630-3740: Displays the register contents from SAVE.AREA.

Lines 3810-3840: Prints MON.PC and a dash. This is called by the disassembly subroutine.

Lines 3880-4330: Disassembles the instruction starting at MON.PC. This code is very similar to code in the Apple monitor ROM at $F882. It is modified slightly to change the spacing, so that there will be room for the register display on the same line.

Lines 4440-4480: A test program for you to try STEPping through. Another neat program to trace is at $FCA8 in the monitor (a delay loop).

 1000  *---------------------------------
 1010  *      STEP-TRACE UTILITY
 1020  *---------------------------------
 1030  MON.WNDBTM .EQ $23
 1040  MON.CH     .EQ $24
 1050  MON.CV     .EQ $25
 1060  LMNEM      .EQ $2C
 1070  RMNEM      .EQ $2D
 1080  MON.FORMAT .EQ $2E
 1090  MON.LENGTH .EQ $2F
 1100  MON.PC     .EQ $3A,3B
 1110  MON.A1     .EQ $3C,3D
 1120  MON.A2     .EQ $3E,3F
 1130  *---------------------------------
 1140  DOS.REENTRY .EQ $3D0
 1150  Y.VECTOR    .EQ $3F8
 1160  BASE.LINE24 .EQ $7D0
 1170  MON.INSDS2  .EQ $F88E
 1180  MON.INSTDSP .EQ $F8D0
 1190  MON.PRADDR  .EQ $F90C
 1200  MON.PRBLNK  .EQ $F948
 1210  MON.PRBL2   .EQ $F94A
 1220  MNEML       .EQ $F9C0
 1230  MNEMH       .EQ $FA00
 1240  MON.VTAB    .EQ $FC22
 1250  MON.CLREOP  .EQ $FC42
 1260  MON.SCROLL  .EQ $FC70
 1270  MON.CLREOL  .EQ $FC9C
 1280  MON.RDKEY   .EQ $FD0C
 1290  MON.CROUT   .EQ $FD8E
 1300  MON.PRYX3   .EQ $FD99
 1310  MON.PRBYTE  .EQ $FDDA
 1320  MON.COUT    .EQ $FDED
 1330  MON.SETINV  .EQ $FE80
 1340  MON.SETNORM .EQ $FE84
 1350  *---------------------------------
 1360  KEYBOARD   .EQ $C000
 1370  STROBE     .EQ $C010
 1380  *---------------------------------
 1390  STEP.TRACE.SETUP
 1400         LDA #$4C     'JMP' OPCODE
 1410         STA Y.VECTOR
 1420         LDA #STEP.TRACE
 1430         STA Y.VECTOR+1
 1440         LDA /STEP.TRACE
 1450         STA Y.VECTOR+2
 1451         LDA #0       CLEAR USER STATUS REGISTER
 1452         STA SAVE.P
 1460         RTS
 1470  *---------------------------------
 1480  *      (Y)          SINGLE STEP AT CURRENT PC
 1490  *      ADR(Y)       SINGLE STEP AT ADR
 1500  *---------------------------------
 1510  STEP.TRACE
 1520         TXA          X=0 IF NO ADDRESSES
 1530         BEQ .1       NO ADDRESSES
 1540         LDA MON.A1   ONE OR TWO ADDRESSES
 1550         STA MON.PC
 1560         LDA MON.A1+1
 1570         STA MON.PC+1
 1580  .1     LDX #$FF     USER GETS WHOLE STACK
 1590         TXS
 1600         STX SAVE.S
 1610         LDA #23
 1620         STA MON.CV
 1630         JSR MON.CROUT
 1640  *---------------------------------
 1650  TRACE.LOOP
 1660         JSR DISPLAY.REGISTERS
 1670         JSR DISASSEMBLE ONE INSTRUCTION
 1680         JSR WAIT.ON.KEYBOARD
 1690         LDA #$EA     'NOP' OPCODE
 1700         STA XQT.AREA+1
 1710         STA XQT.AREA+2
 1720         LDX SAVE.S
 1730         TXS
 1740         LDY #0
 1750         LDA (MON.PC),Y  GET USER OPCODE
 1760         BEQ X.BRK    'BRK' OPCODE
 1770         CMP #$20     'JSR' OPCODE
 1780         BEQ X.JSR
 1790         CMP #$40     'RTI' OPCODE
 1800         BEQ X.RTI
 1810         CMP #$4C     'JMP' OPCODE
 1820         BEQ X.JMP
 1830         CMP #$60     'RTS' OPCODE
 1840         BEQ X.RTS
 1850         CMP #$6C     'JMP ()' OPCODE
 1860         BEQ X.JMPI
 1870         LDY MON.LENGTH  # BYTES IN INSTRUCTION
 1880         AND #$1F     IF RELATIVE BRANCH, CHANGE
 1890         EOR #$14     DISPLACEMENT TO $04
 1900         CMP #$04     FOR XQT AREA
 1910         BEQ .2
 1920  .1     LDA (MON.PC),Y  COPY INSTRUCTION INTO XQT AREA
 1930  .2     STA XQT.AREA,Y
 1940         DEY
 1950         BPL .1
 1960         LDA SAVE.P   RESTORE ALL REGISTERS
 1970         PHA
 1980         LDA SAVE.A
 1990         LDX SAVE.X
 2000         LDY SAVE.Y
 2010         PLP
 2020  *---------------------------------
 2030  XQT.AREA
 2040         NOP          USER'S OPCODE GOES HERE
 2050         NOP
 2060         NOP
 2070         JMP DID.NOT.BRANCH
 2080  *---------------------------------
 2090  *   RELATIVE BRANCHES THAT DO BRANCH COME HERE
 2091         CLD
 2100         CLC
 2110         LDY #1       GET ORIGINAL DISPLACEMENT
 2120         LDA (MON.PC),Y
 2130         BPL .1       POSITIVE DISPLACEMENT
 2140         DEC MON.PC+1 DECREMENT HI-BYTE IF NEGATIVE
 2150  .1     JSR ADD.A.TO.PC
 2160         JMP UPDATE.PC
 2170  *---------------------------------
 2180  X.BRK  JSR DISPLAY.REGISTERS
 2190  RTRN.JMP  JMP RETURN
 2200  *---------------------------------
 2210  X.RTI  TSX
 2220         CPX #$FD
 2230         BCS RTRN.JMP
 2240         PLA          SIMULATE RTI BY GETTING
 2250         STA SAVE.P   STATUS FROM STACK
 2260  X.RTS  TSX
 2270         CPX #$FE
 2280         BCS RTRN.JMP
 2290         PLA          SIMULATE RTS BY GETTING
 2300         STA MON.PC   PC FROM STACK
 2310         PLA
 2320         STA MON.PC+1
 2330         TSX
 2340         STX SAVE.S
 2350         JMP UPDATE.PC
 2360  *---------------------------------
 2370  X.JSR  CLC          UPDATE PC AND PUSH ON STACK
 2380         LDA MON.PC
 2390         ADC #2
 2400         TAY          SAVE LO-BYTE FOR NOW
 2410         LDA MON.PC+1
 2420         ADC #0
 2430         PHA          PUSH HI-BYTE
 2440         TYA
 2450         PHA          PUSH LO-BYTE
 2460         TSX
 2470         STX SAVE.S
 2480  X.JMP  JSR GET.NEW.PC
 2490         JMP TRACE.LOOP
 2500  X.JMPI JSR GET.NEW.PC
 2510         LDY #0
 2520         JSR GET.NEW.PC.0
 2530         JMP TRACE.LOOP
 2540  *---------------------------------
 2550  DID.NOT.BRANCH
 2560         STA SAVE.A   SAVE ALL REGISTERS
 2570         STX SAVE.X
 2580         STY SAVE.Y
 2590         PHP
 2600         PLA
 2610         STA SAVE.P
 2620         TSX
 2630         STX SAVE.S
 2640         CLD
 2650  UPDATE.PC
 2660         SEC          0=1, 1=2, 2=3
 2670         LDA MON.LENGTH
 2680         JSR ADD.A.TO.PC
 2690         JMP TRACE.LOOP
 2700  *---------------------------------
 2710  GET.NEW.PC
 2720         LDY #1       GET NEW PC FROM INSTRUCTION
 2730  GET.NEW.PC.0
 2740         LDA (MON.PC),Y
 2750         TAX          SAVE LO-BYTE FOR NOW
 2760         INY
 2770         LDA (MON.PC),Y
 2780         STA MON.PC+1 NEW HI-BYTE
 2790         STX MON.PC   NEW LO-BYTE
 2800         RTS
 2810  *---------------------------------
 2820  WAIT.ON.KEYBOARD
 2830         LDA #22      LINE 23
 2840         STA MON.CV
 2850         LDA #26      COLUMN 27
 2860         STA MON.CH
 2870         JSR MON.VTAB
 2880         JSR MON.RDKEY
 2890         CMP #$8D
 2900         BEQ RETURN
 2910         CMP #$A0
 2920         BNE .1       REGISTER NAME
 2930         RTS
 2940  .1     LDY #4
 2950  .2     CMP REG.NAMES,Y
 2960         BEQ .3
 2970         DEY
 2980         BPL .2
 2990         BMI WAIT.ON.KEYBOARD
 3000  .3     STY REG.INDEX
 3010  .4     JSR MON.SETINV
 3020         LDA #22
 3030         STA MON.CV
 3040         JSR MON.VTAB
 3050         LDY REG.INDEX
 3060         LDA REG.CH,Y
 3070         STA MON.CH
 3080         LDA SAVE.AREA,Y
 3090         JSR MON.PRBYTE
 3100         JSR MON.SETNORM
 3110  .5     LDA KEYBOARD
 3120         BPL .5
 3130         STA STROBE
 3140         CMP #$A0     BLANK?
 3150         BEQ .8       YES
 3160         CMP #$8D     RETURN?
 3170         BEQ .9       YES
 3180         EOR #$B0
 3190         CMP #10
 3200         BCC .6       DIGIT
 3210         ADC #$88
 3220         CMP #$FA
 3230         BCC .5       NOT DIGIT, SO IGNORE
 3240  .6     LDY #3
 3250         ASL
 3260         ASL 
 3270         ASL
 3280         ASL
 3290         LDX REG.INDEX
 3300  .7     ASL
 3310         ROL SAVE.AREA,X
 3320         DEY
 3330         BPL .7
 3340         BMI .4       ...ALWAYS
 3350  .8     LDY REG.INDEX 
 3360         DEY
 3370         BPL .3
 3380         LDY #4
 3390         BNE .3       ...ALWAYS
 3400  .9     LDA #23
 3410         STA MON.WNDBTM
 3420         JSR MON.SCROLL
 3430         INC MON.WNDBTM
 3440         JMP WAIT.ON.KEYBOARD
 3450  REG.NAMES .AS -/SPYXA/
 3460  REG.INDEX .BS 1
 3470  REG.CH    .DA #38,#35,#32,#29,#26
 3480  *---------------------------------
 3490  RETURN JSR MON.CLREOP
 3500         JMP DOS.REENTRY
 3510  *---------------------------------
 3520  *      ADD (A) TO MON.PC
 3530  *---------------------------------
 3540  ADD.A.TO.PC
 3550         ADC MON.PC
 3560         STA MON.PC
 3570         BCC .1
 3580         INC MON.PC+1
 3590  .1     RTS
 3600  *---------------------------------
 3610  *      DISPLAY REGISTERS
 3620  *---------------------------------
 3630  DISPLAY.REGISTERS
 3640         LDA #26
 3650         STA MON.CH
 3660         LDX #4
 3670         BNE .2
 3680  .1     LDA #$A0
 3690         JSR MON.COUT
 3700  .2     LDA SAVE.AREA,X
 3710         JSR MON.PRBYTE
 3720         DEX
 3730         BPL .1
 3740         RTS
 3750  *---------------------------------
 3760  BOTTOM.LINE .AS / <SPC>=NEXT  <RET>=QUIT   A  X  Y  P  S /
 3770         .HS 00
 3780  *---------------------------------
 3790  *      PRINT PC AND DASH
 3800  *---------------------------------
 3810  PRINT.PC
 3820         LDX MON.PC
 3830         LDY MON.PC+1
 3840         JMP MON.PRYX3
 3850  *---------------------------------
 3860  *      DISASSEMBLE NEXT OPCODE
 3870  *---------------------------------
 3880  DISASSEMBLE
 3890         JSR PRINT.PC
 3900         LDY #0
 3910         LDA (MON.PC),Y GET OPCODE
 3920         JSR MON.INSDS2
 3930         PHA          SAVE MNEMONIC TABLE INDEX
 3940  .1     LDA (MON.PC),Y
 3950         JSR MON.PRBYTE
 3960         LDX #1       PRINT ONE BLANK
 3970  .2     JSR MON.PRBL2
 3980         CPY MON.LENGTH
 3990         INY
 4000         BCC .1
 4010         LDX #3
 4020         CPY #3
 4030         BCC .2
 4040         PLA          GET MNEMONIC TABLE INDEX
 4050         TAY
 4060         LDA MNEML,Y
 4070         STA LMNEM
 4080         LDA MNEMH,Y
 4090         STA RMNEM
 4100  .3     LDA #0
 4110         LDY #5
 4120  .4     ASL RMNEM    SHIFT 5 BITS OF CHARACTER INTO A
 4130         ROL LMNEM
 4140         ROL
 4150         DEY
 4160         BNE .4
 4170         ADC #$BF
 4180         JSR MON.COUT
 4190         DEX
 4200         BNE .3
 4210         LDA #$A0     PRINT BLANK
 4220         JSR MON.COUT
 4230         JSR MON.PRADDR
 4240         JSR MON.CLREOL
 4250         JSR MON.CROUT
 4260         LDY #39
 4270  .5     LDA BOTTOM.LINE,Y
 4280         AND #$3F
 4290         STA BASE.LINE24,Y
 4300         DEY
 4310         BPL .5
 4320         DEC MON.CV
 4330         RTS
 4340  *---------------------------------
 4350  SAVE.AREA
 4360  SAVE.S .BS 1
 4370  SAVE.P .BS 1
 4380  SAVE.Y .BS 1
 4390  SAVE.X .BS 1
 4400  SAVE.A .BS 1
 4410  *---------------------------------
 4420  *      TEST PROGRAM
 4430  *---------------------------------
 4440  TEST   JSR TEST1
 4450         BRK
 4460  TEST1  JSR TEST2
 4470  TEST2  JSR TEST3
 4480  TEST3  RTS

Apple Assembly Line is published monthly by S-C SOFTWARE, P. O. Box 5537, Richardson, TX 75080. 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.)