Volume 1 -- Issue 10 | July 1981

In This Issue...

- The Lower Case Apple
- Screen Printer
- Restoring Clobbered Page 3 Pointers
- Corrections to Variable Cross Reference Program
- Step-Trace Utility

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:

- control-Z: Shift and Shift-lock
- control-K: Left bracket and Left Brace
- control-L: Backslash and Vertical Bar
- control-O: Underline and Rubout

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