Apple Assembly Line
Volume 5 -- Issue 6March 1985

In This Issue...

Videx Ultraterm Driver

We've just completed a Videx Ultraterm display driver for S-C Macro Assembler Version 2.0, so now you fine-print fans can use the assembler with that card's high-density modes. (My favorite is the 48 x 80 inverse mode.) As with the other Version 2.0 drivers, complete source code is supplied so you can tailor the card's performance to your tastes.

This driver is included on all Version 2.0 disks after number 1274. Those of you with lower serial numbers can return your original disk for updating. Please include $1.00 to cover postage and handling. We have also corrected several minor assembler bugs in the last month, so those of you with serial numbers below 1252 might want to update your disks as well.

Quarterly Disk #18

I'd also like to remind you that AAL Quarterly Disk #18 is now ready. This disk contains all of the source code from the January through March '85 issues, including the final install- ments of DP18 and this month's 65816 disassembler. That's many hours' worth of typing saved, at a cost of only $15. Remember that we also sell a year's subscription to the Quarterly disks for only $45. That's four disks for the price of three!

All material herein is copyrighted by S-C SOFTWARE CORPORATION, all rights reserved. (Apple is a registered trademark of Apple Computer, Inc.)


Shortening the DOS File Buffer BuilderBob Sander-Cederlof

Lately I have been looking through DOS for subroutines that can be shrunk. There seem to be a lot of them, or at least I have been lucky in finding some easy ones with little trouble. Elsewhere this month I show how to shrink the numeric input conversion routine, saving enough bytes to make room for a useful new feature.

Yesterday I happened across the file buffer initializer, which starts at $A7D4 and goes up to $A850. Scanning quickly through the code it looked a likely candidate for the shrinking process. If you take a quick peek, you'll see that it starts out with an SEC instruction that is totally unnecessary. Already we have shaved off one byte!

The DOS file buffers are each 595 bytes, linked together with a chain of pointers. There are normally three buffers, starting at $9600, $9853, and $9AA6. (If you have "Beneath Apple DOS", look on page 6-13 for some explanation.) Each buffer contains a 256 byte area for data, another 256 byte area for a track/ sector list, a 30-character filename, a 45-byte working area for the DOS File Manager, and 4 2-byte pointers. There is a two-byte pointer kept at $9D00,9D01 which points at the first character of the filename in the highest buffer. This is normally $9CD3. Here is a picture of the normal three buffers, all chained together: buffer diagrams

The file buffer initializer gets called during the boot procedure, and by the MAXFILES command processor. There are two input parameters: the start of buffers address at $9D00, and the number of file buffers at $AA57. The job of the initializer is to fill in the four address values at the top of each buffer, to store a 00 byte in the first character of the filename of each buffer, and to store a new value in the HIMEM variable for the current language. Here's the way it was, without comments.

  1000 *SAVE S.INIT BUFFERS
  1010 *--------------------------------
  1020 PNTR       .EQ $40,41
  1030 HIMEM      .EQ $4C,4D
  1040 FP.STRINGS .EQ $6F,70
  1050 FP.HIMEM   .EQ $73,74
  1060 PP         .EQ $CA,CB
  1070 *--------------------------------
  1080 BUF.START         .EQ $9D00
  1090 NO.FILES          .EQ $AA57
  1100 TEMP              .EQ $AA63
  1110 ACTIVE.BASIC.FLAG .EQ $AAB6
  1120 *--------------------------------
  1130        .OR $A7D4
  1140        .TA $08D4
  1150 *--------------------------------
  1160 INIT.FILE.BUFFERS
  1170        SEC
  1180        LDA BUF.START
  1190        STA PNTR  
  1200        LDA BUF.START+1
  1210        STA PNTR+1  
  1220        LDA NO.FILES
  1230        STA TEMP
  1240 *--------------------------------
  1250 .1     LDY #0
  1260        TYA
  1270        STA (PNTR),Y
  1280        LDY #$1E
  1290        SEC
  1300        LDA PNTR  
  1310        SBC #$2D
  1320        STA (PNTR),Y
  1330        PHA
  1340        LDA PNTR+1  
  1350        SBC #0
  1360        INY
  1370        STA (PNTR),Y
  1380        TAX
  1390        DEX
  1400        PLA
  1410        PHA
  1420        INY
  1430        STA (PNTR),Y
  1440        TXA
  1450        INY
  1460        STA (PNTR),Y
  1470        TAX
  1480        DEX
  1490        PLA
  1500        PHA
  1510        INY
  1520        STA (PNTR),Y
  1530        INY
  1540        TXA
  1550        STA (PNTR),Y
  1560        DEC TEMP
  1570        BEQ .2
  1580        TAX
  1590        PLA
  1600        SEC
  1610        SBC #$26
  1620        INY
  1630        STA (PNTR),Y
  1640        PHA
  1650        TXA
  1660        SBC #0
  1670        INY
  1680        STA (PNTR),Y
  1690        STA PNTR+1  
  1700        PLA
  1710        STA PNTR  
  1720        JMP .1
  1730 *--------------------------------
  1740 .2     PHA
  1750        LDA #0
  1760        INY
  1770        STA (PNTR),Y
  1780        INY
  1790        STA (PNTR),Y
  1800        LDA ACTIVE.BASIC.FLAG
  1810        BEQ .3
  1820        PLA
  1830        STA FP.HIMEM+1  
  1840        STA FP.STRINGS+1  
  1850        PLA
  1860        STA FP.HIMEM  
  1870        STA FP.STRINGS  
  1880        RTS
  1890 *--------------------------------
  1900 .3     PLA
  1910        STA HIMEM+1  
  1920        STA PP+1  
  1930        PLA
  1940        STA HIMEM  
  1950        STA PP  
  1960        RTS
  1970 *--------------------------------

I rearranged the code, kept mental track of carry status, optimized register usage, and lopped off 11 bytes. Speed is no issue, because it is not a time critical operation anyway, but mine may be a tad quicker. Compare the two versions, and you can learn a few tricks for your own use.

  1000 *SAVE S.INIT BUFFERS (S-C)
  1010 *--------------------------------
  1020 *   REPLACEMENT FOR DOS 3.3 CODE
  1030 *      (SAVES 11 BYTES, NO CHANGE IN FUNCTION)
  1040 *--------------------------------
  1050 PNTR              .EQ $40,41
  1060 HIMEM             .EQ $4C,4D
  1070 FP.STRINGS        .EQ $6F,70
  1080 FP.HIMEM          .EQ $73,74
  1090 PP                .EQ $CA,CB
  1100 *--------------------------------
  1110 BUF.START         .EQ $9D00
  1120 NO.FILES          .EQ $AA57
  1130 TEMP              .EQ $AA63
  1140 ACTIVE.BASIC.FLAG .EQ $AAB6
  1150 *--------------------------------
  1160        .OR $A7D4
  1170        .TA $08D4
  1180 *--------------------------------
  1190 INIT.FILE.BUFFERS
  1200        LDA NO.FILES      DO (NO.FILES) TIMES
  1210        STA TEMP          USE TEMP FOR COUNTER
  1220        LDA BUF.START     POINT TO FIRST BUFFER
  1230        LDX BUF.START+1
  1240 *--------------------------------
  1250 .1     STA PNTR
  1260        STX PNTR+1
  1270        LDY #0            Store zero over 1st char of
  1280        TYA               filename to mark it as a
  1290        STA (PNTR),Y      free buffer.
  1300 *---FILL IN 3 PNTRS--------------
  1310        SEC               COMPUTE LOW BYTE OF POINTERS
  1320        LDA PNTR  
  1330        SBC #$2D
  1340        LDY #$1E          ...FMW ADDR
  1350        STA (PNTR),Y
  1360        LDY #$20          ...TSL ADDR
  1370        STA (PNTR),Y
  1380        LDY #$22          ...DATA ADDR 
  1390        STA (PNTR),Y
  1400        PHA
  1410        LDA PNTR+1        COMPUTE HIGH BYTE OF FMW ADDR
  1420        SBC #0
  1430        LDY #$1F          ...FMW ADDR
  1440        STA (PNTR),Y
  1450        SBC #1
  1460        LDY #$21          ...TSL ADDR
  1470        STA (PNTR),Y
  1480        SBC #1
  1490        LDY #$23          ...DATA ADDR
  1500        STA (PNTR),Y
  1510 *---IS THAT THE LAST BUFFER?-----
  1520        INY               POINT AT FWD LINK LO-BYTE
  1530        TAX          SAVE HI BYTE OF DATA ADDR
  1540        DEC TEMP
  1550        BEQ .2       ...NO MORE BUFFERS
  1560 *---BUILD LINK TO NEXT BUFFER----
  1570        PLA          GET LO BYTE
  1580        SBC #$26     ADDR OF FILENAME IN NEXT BUFFER
  1590        STA (PNTR),Y      ...LO BYTE
  1600        PHA               SAVE ON STACK
  1610        TXA               GET HI BYTE
  1620        SBC #0
  1630        INY               ...HI BYTE
  1640        STA (PNTR),Y
  1650        TAX               SAVE IN X
  1660        PLA               GET LO BYTE AGAIN
  1670        BCS .1            ...ALWAYS
  1680 *---SET FORWARD PNTR = 0000------
  1690 .2     LDA #0
  1700        STA (PNTR),Y
  1710        INY
  1720        STA (PNTR),Y
  1730 *---SET HIMEM AND EMPTY BLOCK----
  1740        LDA ACTIVE.BASIC.FLAG
  1750        BEQ .3            INTEGER BASIC
  1760        STX FP.HIMEM+1    APPLESOFT
  1770        STX FP.STRINGS+1  
  1780        PLA
  1790        STA FP.HIMEM  
  1800        STA FP.STRINGS  
  1810        RTS
  1820 .3     STX HIMEM+1  
  1830        STX PP+1  
  1840        PLA
  1850        STA HIMEM  
  1860        STA PP  
  1870        RTS
  1880 *--------------------------------

I found it even more interesting to re-write this program using the 65802 capabilities. The 16-bit registers save a lot of byte shuffling, and eliminate the need for TEMP and PNTR. What's more, instead of saving only 11 bytes over the original DOS 3.3 version, this time I whacked out 46 bytes! And it could be made even smaller, if we could make some assumptions about the CPU status.

In general, we don't know whether we are in 65802 or 6502 mode until we peek at the "hidden" status bit (the E-bit). In the process of peeking we may change it, and may also change the M- and X-bits. Lines 1190 save the current status, flip into '802 mode and save the status again. The first PHP is there in case we were already in '802 mode. If we were, it saves the M- and X- bits and they will be restored by the PLP at line 1620. The second PHP saves the status of the mysterious E-bit (the XCE opcode swaps E and C). Lines 1600-1610 pull this saved status and do another XCE, restoring E to what it was when this sub- routine was called. If we could ASSUME that we were called in '802 mode, we could delete lines 1190-1210 and lines 1610-1620 (saving 5 more bytes). Or, if we could be sure we were always called from 6502 mode, we could delete 1190, 1220, and 1620, and change line 1600 to ".4 SEC" (saving 3 bytes). Probably better never to assume, at least until we are a lot more familiar with this marvelous chip.

The XCE instruction swaps the C- and E-bits, but that is not necessarily all. The M- and X-bits always come up in the 8-bit mode after an XCE. Therefore in line 1240, the LDX will load $00 into the high byte of the X-register and the number of buffers into the low byte. In line 1250 I turn on 16-bit mode for both indexing and memory-accumulator operations, and I will keep it that way until the PLP at line 1600.

6502 programs are always full of page zero pointer addressing modes, but in 65802 programs we may see a lot less of them. Now we can load a whole 16-bit address into the X- or Y- register.

       Instead of:             We can write:
       -----------             -------------
       LDA BUF.PNTR
       STA PNTR
       LDA BUF.PNTR+1
       STA PNTR+1
       LDY #$1E                LDY BUF.PNTR
       LDA DATA...             LDA DATA...
       STA (PNTR),Y            STA $1E,Y

Lines 1280-1290 zero the first byte of the filename. As an "extra" feature now, the second byte is also zeroed. In lines 1300-1380 I can compute and store the three area pointers in a very straightforward manner. It now occurs to me that by swapping the roles of the X- and Y-registers I could save six more bytes, since the STA $offset,X instructions would assemble in two bytes rather than three. (The only problem might be that the D-register must = $0000 for this to work.)

Since I don't have to use the X-register to hold temporary values during the buffer creation loop, I can use it instead to count buffers. Lines 1400-1410 do the counting.

If we have not just built the last buffer, lines 1420-1460 set the "next buffer" link address and branch back to build another buffer.

Lines 1470-1500 save the address of the data area in the X- register and store 0000 in the link address for the last buffer. The data area address is going to be the new HIMEM value.

Lines 1510-1590 store the new HIMEM value for the currently selected language. If we are in Applesoft, the string area normally bumps against HIMEM; we now empty that area, because HIMEM may have moved. If we are in Integer BASIS or the S-C Assembler (which fools DOS into believing it is I/B), the source program nestles against HIMEM; it is therefore emptied by storing the HIMEM value into PP.

Won't it be nice when we all have 65802's and can USE these new code segments? It may not be as long as you think. In the mean time, maybe we can develop our expertise. And we can carve enough holes in DOS to leave room for some great new features.

  1000 *SAVE S.INIT BUFFERS (802)
  1010        .OP 65816
  1020 *--------------------------------
  1030 *   REPLACEMENT FOR DOS 3.3 CODE
  1040 *      (SAVES 46 BYTES, NO CHANGE IN FUNCTION)
  1050 *--------------------------------
  1060 HIMEM             .EQ $4C,4D
  1070 FP.STRINGS        .EQ $6F,70
  1080 FP.HIMEM          .EQ $73,74
  1090 PP                .EQ $CA,CB
  1100 *--------------------------------
  1110 BUF.START         .EQ $9D00
  1120 NO.FILES          .EQ $AA57
  1130 ACTIVE.BASIC.FLAG .EQ $AAB6
  1140 *--------------------------------
  1150        .OR $A7D4
  1160        .TA $08D4
  1170 *--------------------------------
  1180 INIT.FILE.BUFFERS
  1190        PHP               SAVE CURRENT STATUS AND
  1200        CLC               TURN ON 802 MODE
  1210        XCE
  1220        PHP
  1230 *--------------------------------
  1240        LDX NO.FILES      DO (NO.FILES) TIMES
  1250        REP #$30          16-BIT OPERATIONS
  1260        LDY BUF.START     POINT TO FIRST BUFFER
  1270 *--------------------------------
  1280 .1     LDA ##0           STORE ZERO OVER 1ST & 2ND CHARS 
  1290        STA 0,Y           OF FILENAME TO FREE BUFFER
  1300 *---FILL IN 3 PNTRS--------------
  1310        SEC               COMPUTE LOW BYTE OF POINTERS
  1320        TYA               FROM FILENAME ADDR
  1330        SBC ##$2D
  1340        STA $1E,Y         ...FMW ADDR
  1350        SBC ##$100
  1360        STA $20,Y         ...TSL ADDR
  1370        SBC ##$100
  1380        STA $22,Y         ...DATA ADDR
  1390 *---IS THAT THE LAST BUFFER?-----
  1400        DEX
  1410        BEQ .2       ...NO MORE BUFFERS
  1420 *---BUILD LINK TO NEXT BUFFER----
  1430        SBC ##$26    ADDR OF FILENAME IN NEXT BUFFER
  1440        STA $24,Y
  1450        TAY          BASE ADDRESS FOR NEXT BUFFER
  1460        BRA .1            ...ALWAYS
  1470 *---SET FORWARD PNTR = 0000------
  1480 .2     TAX          SAVE HIMEM VALUE
  1490        LDA ##0
  1500        STA $24,Y
  1510 *---SET HIMEM AND EMPTY BLOCK----
  1520        LDA ACTIVE.BASIC.FLAG
  1530        AND ##$FF
  1540        BEQ .3            INTEGER BASIC
  1550        STX FP.HIMEM      APPLESOFT
  1560        STX FP.STRINGS
  1570        BRA .4
  1580 .3     STX HIMEM         INTEGER BASIC
  1590        STX PP
  1600 .4     PLP
  1610        XCE
  1620        PLP
  1630        RTS
  1640 *--------------------------------

And here is an even shorter one, that did not appear in the printed edition.

  1000 *SAVE S.INIT BUFFERS (802) X/Y
  1010        .OP 65816
  1020 *--------------------------------
  1030 *   REPLACEMENT FOR DOS 3.3 CODE
  1040 *      (SAVES 52 BYTES, NO CHANGE IN FUNCTION)
  1050 *--------------------------------
  1060 HIMEM             .EQ $4C,4D
  1070 FP.STRINGS        .EQ $6F,70
  1080 FP.HIMEM          .EQ $73,74
  1090 PP                .EQ $CA,CB
  1100 *--------------------------------
  1110 BUF.START         .EQ $9D00
  1120 NO.FILES          .EQ $AA57
  1130 ACTIVE.BASIC.FLAG .EQ $AAB6
  1140 *--------------------------------
  1150        .OR $A7D4
  1160        .TA $08D4
  1170 *--------------------------------
  1180 INIT.FILE.BUFFERS
  1190        PHP               SAVE CURRENT STATUS AND
  1200        CLC               TURN ON 802 MODE
  1210        XCE
  1220        PHP
  1230 *--------------------------------
  1240        LDY NO.FILES      DO (NO.FILES) TIMES
  1250        REP #$30          16-BIT OPERATIONS
  1260        LDX BUF.START     POINT TO FIRST BUFFER
  1270 *--------------------------------
  1280 .1     LDA ##0           STORE ZERO OVER 1ST & 2ND CHARS 
  1290        STA 0,X           OF FILENAME TO FREE BUFFER
  1300 *---FILL IN 3 PNTRS--------------
  1310        SEC               COMPUTE LOW BYTE OF POINTERS
  1320        TXA               FROM FILENAME ADDR
  1330        SBC ##$2D
  1340        STA $1E,X         ...FMW ADDR
  1350        SBC ##$100
  1360        STA $20,X         ...TSL ADDR
  1370        SBC ##$100
  1380        STA $22,X         ...DATA ADDR
  1390 *---IS THAT THE LAST BUFFER?-----
  1400        DEY
  1410        BEQ .2       ...NO MORE BUFFERS
  1420 *---BUILD LINK TO NEXT BUFFER----
  1430        SBC ##$26    ADDR OF FILENAME IN NEXT BUFFER
  1440        STA $24,X
  1450        TAX          BASE ADDRESS FOR NEXT BUFFER
  1460        BRA .1            ...ALWAYS
  1470 *---SET FORWARD PNTR = 0000------
  1480 .2     TAY          SAVE HIMEM VALUE
  1490        LDA ##0
  1500        STA $24,X
  1510 *---SET HIMEM AND EMPTY BLOCK----
  1520        LDA ACTIVE.BASIC.FLAG
  1530        AND ##$FF
  1540        BEQ .3            INTEGER BASIC
  1550        STY FP.HIMEM      APPLESOFT
  1560        STY FP.STRINGS
  1570        BRA .4
  1580 .3     STY HIMEM         INTEGER BASIC
  1590        STY PP
  1600 .4     PLP
  1610        XCE
  1620        PLP
  1630        RTS
  1640 *--------------------------------

65C02s in Old ApplesJim Sather

I read Andrew Jackson's 12/84 AAL comments on 65C02 operation in an Apple II with interest since I had looked into the same subject while doing research for "Understanding the Apple IIe". I share Mr. Jackson's conclusion that the problem is short read data setup time from motherboard RAM, but I disagree with his analysis and conclusion that a 65C02 only gets a setup of 25 nsec in an Apple II.

The motherboard RAM read data setup time in an Apple II is

        70 nsec (one 14M period)
  minus LS174 pin 9 to data out propagation delay (B5/B8 latch)
  minus LS257 data propagation delay (B6/B7 mux)
  minus 8304 or 8T28 data propagation delay (H10/H11 driver)
   plus MPU PHASE 0 to PHASE 2 propagation delay
   plus 74LS08 propagation delay (B11 PHASE 0 gate).

Longer PHASE 0 - PHASE 2 delays result in longer read data setup time, not shorter. With the 6502s and 65C02s I have experimented with, PHASE 0 to PHASE 2 delay has always been in the 20-40 nsec region. Whatever the variation, I have found no NCR or GTE 65C02 that will work in my Apple II.

Taking all delays into account, the motherboard read data setup time for a 6502 or 65C02 is about 65 nsec. This is not good enough for 1 MHz 6502/65C02 specifications but it is good enough for 2 MHz 6502/65C02 specifications. In other words, the Apple II does not meet the read data setup spec of the 1 MHz 6502 that it was manufactured with. Based on this fact, the 100 nsec read data setup spec of 1 MHz 6502s is unrealistically conservative.

But why won't a 2 MHz 65C02 run in the Apple II if it requires only 50 nsec setup time and it gets 65 nsec? The answer, in my opinion, is that NCR and GTE 2 MHz 65C02s do not operate to spec. With certain instruction sequences, they require more than 50 (and, in fact, more than 65) nsec read data setup time. The instruction sequences that bomb are VERY limited, so the 65C02 only gets into trouble when a certain few code sequences are executed. The 65C02 symptom in the Apple II is, therefore, that most things work, but some don't.

Efforts to improve 65C02 operation in the Apple II can be concentrated on decreasing data delays (by replacing the LS174s and LS257s with equivalent devices from a faster logic family) or increasing MPU data clock delays (by adding TTL devices in series with the MPU PHASE 0 input). Possible reduction in data delays is limited, so increased MPU PHASE 0 delay is tempting. Be forewarned, though, that 6502 PHASE 2 is already very late for peripheral slot and serial input mux data transfer, and that such data transfer already depends on the long bleed off time of data from the floating data bus. It is certainly feasible that some Apples with heavy data bus loads will begin to show bugs if any MPU PHASE 0 delay is introduced. But in all probability, you can increase the MPU PHASE 0 delay in a given Apple until MPU PHASE 2 falls concurrently with RAM SELECT' after access to an address above $C00F in the Apple II. This point is 60 nsec after peripheral slot PHASE 0 falls in my Apple II.

AAL readers may be interested in the following excerpt from "Understanding the Apple IIe". It details some features of the 65C02 which are not clear from the data sheet and describes instruction sequences that I have found that make NCR and GTE 65C02s bomb in an Apple II. Note particularly that I have a Rockwell 1 MHz 65C02 that operates without a hitch in my Apple II. This may be a lucky coincidence, or Rockwell 65C02s may not have the read data setup problems of the NCR and GTE chips.

[ Following is an excerpt from "Understanding the Apple //e", copyright (c) 1985 by Quality Software, published here by permission of Quality Software. ]

THE 65C02 MICROPROCESSOR

A recent development in the 6502 world has been the introduction of the 65C02 MPU. This MPU (manufactured by NCR, Rockwell, and alternate sources) is fabricated using CMOS technology, instead of the NMOS used in the 6502. The general advantage of CMOS over NMOS is lower power consumption, but the 65C02 also has some new instructions which make it operationally more powerful than its NMOS brother. A 65C02 can execute any 6502 program that doesn't depend on fine instruction execution timing, but a 6502 cannot execute 65C02 programs that utilize the new 65C02 instructions.

Apple uses the 65C02 MPU in the Apple //c microcomputer, and they intend to convert the Apple //e over to the 65C02. The plan is to retrofit older Apple //e's with the 65C02 as part of the firmware upgrade package described in Chapter 6. This will maximize compatiblity betweeen the Apple //e and the Apple //c, and make it possible to write shorter and faster Apple //e assembly language programs. Because the Apple //e may become a 65C02 based computer in the future, some data on the 65C02 is given here and in other parts of "Understanding the Apple //e".

The 65C02 improvements consist of the addition of new instructions and addressing modes, and the removal of some old 6502 bugs. For the most part, differences between the 6502 and 65C02 are well documented in the partial NCR 65C02 data sheet in Appendix C at the back of this book. Descriptions here will therefore be limited to a few points whose ramifications are not made entriely clear by the data sheet. Please note also that details of 65C02 instruction execution are given in Tables 4.3 and 4.4 in an application note later in this chapter.

First, the NCR and Rockwell 65C02s are not identical. The Rockwell chip executes some instructions that are not part of the NCR 65C02 repertoire. These are the zero page instructions RMBn (Reset Memory Bit n) and SMBn (Set Memory Bit n), and the zero page relative branch instructions BBRn (Branch on Bit n Reset) and BBSn (Branch on Bit n Set). The opcodes of these Rockwell instructions ($X7 and $XF) represent NOPs in the NCR chip. Apple appears to be using NCR compatible 65C02s in its computers, but the Rockwell chip works fine in the Apple //e. Please refer to Tables 4.3 and 4.4 for details of the additional Rockwell instructions.

The READY line of a 6502 will not halt the MPU during a write cycle, but the 65C02 READY line will. This raises the question, "what happens to the Apple IIe data bus if READY is pulled low during a write cycle and is held low for a number of following write cycles?" If the 65C02 attempts to control the data bus constantly for a series of wait state write cycles, it will compete with motherboard RAM for control of the data bus near the end of PHASE 1. Investigation shows that this is not a problem. During a long series of wait state write cycles, the 65C02 control the data bus only during that portion of the machine cycle in which it controls the data bus during a normal write cycle. Therefore, its data bus connection is at high impedance during the majority of PHASE 1 in all wait state write cycles, and motherboard RAM is free to control the data bus near the end of PHASE 1.

The fact that interrupts do not cause abortion of a BREAK instruction is listed as an operational enhancement of the 65C02 on page 3 of the data sheet. The data sheet is referring to non-maskable interrupts, not interrupt requests. In a 6502 or 65C02, IRQ' falling after a BREAK op code fetch does not interfere with BREAK execution. However, if NMI' falls after a BREAK op code fetch and before the interrupt vector is fetched in a 6502, then the NMI' interrupt vector is fetched, and the NMI' handler is executed. An RTI at the end of the NMI' handler causes return to the address (plus two) of the BREAK instruction and probable program crashing. This bug is fixed in the 65C02. As the data sheet indicates, NMI' falling during BREAK execution results in NMI' execution after BREAK execution is complete.

The NCR data sheet refers to the new increment accumulator and decrement accumulator instrucions as INA and DEA. I don't know why they do this, because these instructions are clearly just new addressing modes of the INC and DEC instructions. The new mnemonics should be INC A and DEC A or just INC and DEC as given in the Rockwell data sheet. The addition of the INC and DEC accumulator addressing modes means these instructions have all the addressing modes of the other 6502 read-modify-write instructions (ASL, LSR, ROL, and ROR).

Another notable feature of the 65C02 data sheet is the 5000- microsecond maximum cycle time in the AC characteristics table on page 3. I take this to mean that you can stop the clock for a guaranteed minimum of 5000 microseconds with PHASE 0 high, but not with PHASE 0 low. The Rockwell data sheet is more specific about the difference. It states: "The input clock can be held in the high state indefinitely; however, if the input clock is held in the low state longer than 5 microseconds, internal register and data status can be lost". The significance is that, when the Apple IIe DMA' line is held low, it forces the PHASE 0 input to the MPU to a low state. I therefore conclude that long term continuous DMA in the Apple IIe cannot be performed with a 65C02 any easier than it can with a 6502. In either case, long term continuous DMA can only be performed by pulling DMA' low after the MPU has been stopped via READY low, and only after the X4 and X5 Apple IIe motherboard jumpers have been configured so the MPU clock is not stopped when DMA' is pulled low.

A feature of the 65C02 that does not show up in the NCR data sheet is that the new BIT immediate instruction operates differently than BIT in the other addressing modes. In the other addressing modes, BIT sets the negative, overflow, and zero flags based respectively on operand bit 7, operand bit 6, and the result of Accumulator AND operand. The 65C02 BIT immediate instruction affects only the zero flag, not the negative and overflow flags.

A final point about 65C02 operation that I'd like to make is mildly speculative. The 65C02 is pin compatible with the 6502, and was designed as a direct but more powerful substitute for the 6502. To make it work in the Apple IIe, you simply remove the 6502 and plug in the 65C02. However, the 65C02 does not work reliably in the older Apple II. I believe that the reason for this is that the 65C02 (or at least an NCR 65C02) requires read data to be set up longer than a 6502 operating at the same frequency. RAM read data in the Apple II becomes valid at the MPU (about 60 nsec before PHASE 2 falls) much later than it does in the Apple IIe (about 250 nsec before PHASE 2 falls). Whereas the 6502 can handle the short RAM read data set up time, the 65C02 seems to have trouble with it.

I have performed limited experiments with 65C02s in an Apple II. Basically, I found that two NCR 65C02As (2 MHz?) and one NCR compatible GTE G65SC02P-2 (2 MHz) caused intermittent program crashing that got worse as the peripheral card data bus load was increased. The Rockwell R65C02P1 (1 MHz) that I tried caused no program crashes. The NCR 65C02 program drashes occurred only with certain data bus sequences. If an RTS instruction is preceded by a NOP or SBC, and the Apple II video data preceding the RTS opcode fetch is $A0, $A2, or $A9 then the carry flag is set during otherwise normal execution of the RTS instruction. This unwanted setting of the carry flag occurred as mentioned with all three NCR type chips. One of the chips also set the carry flag if the video data preceding the RTS was $89, and another one also set the carry flag if the video data preceding RTS was $89 or $E9. Note that $89, $A0, $A2, $A9, and $E9 are all immediate mode 65C02 instructions.

In these experiments, I did not conclusively prove that the problem with the 65C02 in the Apple II is short set up time of RAM read data. This is merely a highly educated guess upon which I would be willing to bet a paycheck (if only I had one). Setting the data up quicker definitely helps, because the bugs mentioned in the previous paragraph do not exist when the program resides in a 16K RAM card whose read data becomes valid just after Q3 falls during PHASE 0. In any case, I am suspicious of the validity of the NCR claim of 50-nsec minimum read data set up time in its 65C02.


Improved DOS 3.3 Number Parsing
& Lower-Case DOS Commands
Bob Sander-Cederlof

Whether Apple knows or not, cares or not, likes it or not, DOS 3.3 is still alive. And still the system of choice to most of their loyal customers.

The //c and new //e ROMs patch Applesoft so that lower case keywords and commands can be typed without penalty. However, since they are promoting ProDOS and do not care about DOS, they did nothing to give DOS the freedom to accept lower case commands. I am constantly chafing at the necessity of popping the shift lock key up and down, (down for DOS and up for word processing). Surely a very small patch would do the trick.

I looked around and found the subroutine DOS uses to pick characters out of the command buffer, at $A193-$A1AD. Six bytes of new code inserted right before the CMP #$AC at $A1A1 would do it. If I put a JSR to a patch in place of the STX $AA5D at $A19E, a ten-byte patch subroutine would solve my problem.

But where do I get a ten-byte hole to fit this patch into? All the holes I know about have already been used now, and I really don't want to eliminate any existing features. The only solution is to find some loosely written code and rewrite it with compactness as the major criterion.

The code to be recoded must be relatively unused. That is, not likely to be called at internal places by sneaky software. I found a likely candidate in the number conversion subroutine used in parsing DOS commands. This subroutine occupies from $A1B9 through $A228. I ran a cross reference on the outer shell portion of DOS ($9D84-$A883) using Rak-Ware's DISASM program, and verified that there are no entry points into this code except at the beginning. It is called from only two places, $A0AA and $A127.

Here is a commented disassembly of the subroutine:

  1000 *SAVE S.DOS NUMIN
  1010 *--------------------------------
  1020 NUML   .EQ $44
  1030 NUMH   .EQ $45
  1040 *--------------------------------
  1050 GNNB   .EQ $A1A4
  1060 *--------------------------------
  1070        .OR $A1B9
  1080        .TA $09B9
  1090 *--------------------------------
  1100 *      RETURN .CC. WITH NUMBER IN A,X
  1110 *          OR .CS. IF BAD SYNTAX
  1120 *--------------------------------
  1130 CONVERT.NUMBER.IN.WBUF
  1140        LDA #0       INIT NUMBER = 0
  1150        STA NUML  
  1160        STA NUMH  
  1170        JSR GNNB     GET NEXT NON-BLANK CHAR
  1180        PHP
  1190        CMP #"$"     HEX OR DECIMAL?
  1200        BEQ .6       ...HEX
  1210        PLP
  1220        JMP .2       ...DECIMAL (OR NONE)
  1230 *---NEXT CHAR OF DECIMAL #-------
  1240 .1     JSR GNNB     GET NEXT NON-BLANK CHAR
  1250 .2     BNE .3       ...NOT COMMA OR CR
  1260        LDX NUML     END OF NUMBER
  1270        LDA NUMH     VALUE IN A,X
  1280        CLC          SIGNAL VALID NUMBER
  1290        RTS          RETURN
  1300 *---CONVERT DECIMAL NUMBER-------
  1310 .3     SEC          CONVERT CHAR TO DIGIT
  1320        SBC #$B0
  1330        BMI .4       ...NOT DIGIT
  1340        CMP #$0A
  1350        BCS .4       ...NOT DIGIT
  1360        JSR .5       SHIFT VALUE 1 LEFT
  1370        ADC NUML     2*VALUE + DIGIT
  1380        TAX
  1390        LDA #$00
  1400        ADC NUMH  
  1410        TAY
  1420        JSR .5       SHIFT VALUE 1 LEFT
  1430        JSR .5       SHIFT VALUE 1 LEFT
  1440        TXA          ...+ 8*VALUE
  1450        ADC NUML  
  1460        STA NUML  
  1470        TYA
  1480        ADC NUMH  
  1490        STA NUMH  
  1500        BCC .1       ...NO OVERFLOW
  1510 .4     SEC          SIGNAL BAD CHAR OR OVERFLOW
  1520        RTS
  1530 *---SHIFT VALUE 1 BIT LEFT-------
  1540 .5     ASL NUML  
  1550        ROL NUMH  
  1560        RTS
  1570 *---CONVERT HEX NUMBER-----------
  1580 .6     PLP          POP USELESS STATUS
  1590 .7     JSR GNNB     GET NEXT NON-BLANK CHAR
  1600        BEQ .2       ...END OF NUMBER
  1610        SEC          CONVERT ASCII TO DIGIT
  1620        SBC #$B0
  1630        BMI .4       ...NOT A DIGIT
  1640        CMP #$0A
  1650        BCC .8       ...0-9
  1660        SBC #$07     TRY LETTERS
  1670        BMI .4       ...NOT A DIGIT
  1680        CMP #$10
  1690        BCS .4       ...NOT A DIGIT
  1700 .8     LDX #4       SHIFT VALUE 4 BITS LEFT
  1710 .9     JSR .5       SHIFT VALUE 1 LEFT
  1720        DEX
  1730        BNE .9
  1740        ORA NUML     MERGE VALUE WITH NEW DIGIT
  1750        STA NUML  
  1760        JMP .7       ...NEXT DIGIT
  1770 *--------------------------------

Lines 1530-2120 of the following listinga show my revised version, which is sixteen bytes shorter. It is also a little faster, though that is not important. As far as I can tell, no features are changed. There is room for my ten-byte lower-case patch and six bytes to spare!

Compare the two versions to see where I found the extra bytes. Part of the savings was gained by using a better algorithm for reducing an ASCII character to a hex or decimal digit. Changing the order of the sections of the program saved more bytes, by eliminating JMPs and "branch always" ops. I kept the same local labels in the new version to aid you in locating similar sections.

It is always nice to be able to make a self-installing patch, so I dug out the April 83 issue of AAL for Bill Morgan's PATCHER program. I found the source on a Quarterly Disk, and merged it with the new number parser. Then I added my lower case patch, and glued it all together. The listing that follows is the result. If the program is BRUN it will install the new parser and the lower case filter automatically.

  1000 *SAVE S.DOS NUMIN (RBSC)
  1010 *--------------------------------
  1020 NUML   .EQ $44
  1030 NUMH   .EQ $45
  1040 *--------------------------------
  1050 GNNB   .EQ $A1A4
  1060 *--------------------------------
  1070        .OR $A1B9
  1080        .TA $09B9
  1090 *--------------------------------
  1100 *      RETURN .CC. WITH NUMBER IN A,X
  1110 *          OR .CS. IF BAD SYNTAX
  1120 *--------------------------------
  1130 CONVERT.NUMBER.IN.WBUF
  1140        LDY #0       INIT NUMBER = 0
  1150        STY NUML     (AND LEAVE Y=0 TOO)
  1160        STY NUMH  
  1170        JSR GNNB     GET NEXT NON-BLANK CHAR
  1180        BEQ .2       ...NO NUMBER, RETURN 0
  1190        CMP #"$"     HEX OR DECIMAL?
  1200        BEQ .7       ...HEX
  1210 *---CONVERT DECIMAL NUMBER-------
  1220 .3     EOR #$B0     CONVERT CHAR TO DIGIT
  1230        CMP #10
  1240        BCS .4       ...NOT DIGIT
  1250        ASL NUML     SHIFT VALUE 1 LEFT
  1260        ROL NUMH
  1270        ADC NUML     2*VALUE + DIGIT
  1280        TAX
  1290        TYA          A = Y = 0
  1300        ADC NUMH  
  1310        PHA
  1320        ASL NUML     SHIFT VALUE 1 LEFT
  1330        ROL NUMH
  1340        ASL NUML     SHIFT VALUE 1 LEFT
  1350        ROL NUMH
  1360        TXA          ...+ 8*VALUE
  1370        ADC NUML  
  1380        STA NUML  
  1390        PLA
  1400        ADC NUMH  
  1410        STA NUMH  
  1420        BCS .4       ...OVERFLOW
  1430 .1     JSR GNNB     GET NEXT NON-BLANK CHAR
  1440        BNE .3       ...NOT COMMA OR CR
  1450 *---NUMBER IS FINISHED-----------
  1460 .2     LDX NUML     END OF NUMBER
  1470        LDA NUMH     VALUE IN A,X
  1480        CLC          SIGNAL VALID NUMBER
  1490        RTS          RETURN
  1500 *---MERGE NEXT HEX DIGIT---------
  1510 .8     ASL          POSITION DIGIT
  1520        ASL
  1530        ASL
  1540        ASL
  1550        LDX #4       SHIFT VALUE 4 BITS LEFT
  1560 .9     ASL          SHIFT DIGIT INTO VALUE
  1570        ROL NUML
  1580        ROL NUMH
  1590        DEX
  1600        BNE .9
  1610 *---CONVERT HEX NUMBER-----------
  1620 .7     JSR GNNB     GET NEXT NON-BLANK CHAR
  1630        BEQ .2       ...END OF NUMBER
  1640        EOR #$B0     CONVERT ASCII TO DIGIT
  1650        CMP #10      0...9?
  1660        BCC .8       ...YES, 0-9
  1670        ADC #$88     SHIFT RANGE FOR A-F TEST
  1680        CMP #$FA     A...F?
  1690        BCS .8       ...A-F
  1700 *---SYNTAX ERROR-----------------
  1710 .4     SEC          SIGNAL BAD CHAR OR OVERFLOW
  1720        RTS
  1730 *--------------------------------
  1740        .OR $800
  1750 TEST   JSR $FD67
  1760        TXA
  1770        BEQ .1
  1780        LDX #0
  1790        STX $AA5D
  1800        JSR CONVERT.NUMBER.IN.WBUF
  1810        JSR $F941
  1820        JMP TEST
  1830 .1     RTS

The following was on the Quarterly Disk, but did not make it into the printed edition:

     100  REM PATCH DOS FOR LOWER CASE
     110  READ N: IF N = 0 THEN  END 
     120  READ B: FOR I = 1 TO N: READ D: POKE B,D:B = B + 1: NEXT 
     130  GOTO 110
     1000  DATA  112,41401,160,0,132,68,132,69,32,164,161,240,46,201,16
           4,240,62,73,176,201,10,176,73,6,68,38,69,101,68,170,152,101,6
           9,72,6,68,38,69,6,68,38,69,138,101,68,133,68,104,101,69,133,6
           9,176,42,32,164,161,208
     1010  DATA  214,166,68,165,69,24,96,10,10,10,10,162,4,10,38,68,38,
           69,202,208,248,32,164,161,240,231,73,176,201,10,144,231,105,1
           36,201,250,176,225,56,96,142,93,170,201,224,144,2,41,223,96,0
           ,0,0,0,0,0 
     1020  DATA  3,41374,32,25,162
     1030  DATA 0
  1000 *SAVE S.DOS LC PATCHES
  1010 *--------------------------------
  1020 PNTR   .EQ $00,01
  1030 PATCH  .EQ $02,03
  1040 *--------------------------------
  1050        .OR $300
  1060        .TF B.DOS LC PATCHES
  1070 *--------------------------------
  1080 PATCHER
  1090        LDA #PATCHES-1
  1100        STA PNTR
  1110        LDA /PATCHES-1
  1120        STA PNTR+1
  1130        LDY #0
  1140  
  1150 .1     JSR GET.BYTE LENGTH OF NEXT PATCH
  1160        BEQ .4       FINISHED
  1170        TAX          SAVE LENGTH IN X
  1180        JSR GET.BYTE ADDRESS OF PATCH
  1190        STA PATCH
  1200        JSR GET.BYTE
  1210        STA PATCH+1
  1220  
  1230 .2     JSR GET.BYTE
  1240        STA (PATCH),Y
  1250        INC PATCH
  1260        BNE .3
  1270        INC PATCH+1
  1280 .3     DEX
  1290        BNE .2
  1300        BEQ .1    ...ALWAYS
  1310  
  1320 .4     RTS
  1330 *--------------------------------
  1340 GET.BYTE
  1350        INC PNTR
  1360        BNE .1
  1370        INC PNTR+1
  1380 .1     LDA (PNTR),Y
  1390        RTS
  1400 *--------------------------------
  1410 NUML   .EQ $44
  1420 NUMH   .EQ $45
  1430 *--------------------------------
  1440 GNNB   .EQ $A1A4
  1450 *--------------------------------
  1460 PATCHES
  1470        .DA #P1.LENGTH,$A1B9
  1480        .PH $A1B9
  1490 *--------------------------------
  1500 *      RETURN .CC. WITH NUMBER IN A,X
  1510 *          OR .CS. IF BAD SYNTAX
  1520 *--------------------------------
  1530 CONVERT.NUMBER.IN.WBUF
  1540        LDY #0       INIT NUMBER = 0
  1550        STY NUML     (AND LEAVE Y=0 TOO)
  1560        STY NUMH  
  1570        JSR GNNB     GET NEXT NON-BLANK CHAR
  1580        BEQ .2       ...NO NUMBER, RETURN 0
  1590        CMP #"$"     HEX OR DECIMAL?
  1600        BEQ .7       ...HEX
  1610 *---CONVERT DECIMAL NUMBER-------
  1620 .3     EOR #$B0     CONVERT CHAR TO DIGIT
  1630        CMP #10
  1640        BCS .4       ...NOT DIGIT
  1650        ASL NUML     SHIFT VALUE 1 LEFT
  1660        ROL NUMH
  1670        ADC NUML     2*VALUE + DIGIT
  1680        TAX
  1690        TYA          A = Y = 0
  1700        ADC NUMH  
  1710        PHA
  1720        ASL NUML     SHIFT VALUE 1 LEFT
  1730        ROL NUMH
  1740        ASL NUML     SHIFT VALUE 1 LEFT
  1750        ROL NUMH
  1760        TXA          ...+ 8*VALUE
  1770        ADC NUML  
  1780        STA NUML  
  1790        PLA
  1800        ADC NUMH  
  1810        STA NUMH  
  1820        BCS .4       ...OVERFLOW
  1830 .1     JSR GNNB     GET NEXT NON-BLANK CHAR
  1840        BNE .3       ...NOT COMMA OR CR
  1850 *---NUMBER IS FINISHED-----------
  1860 .2     LDX NUML     END OF NUMBER
  1870        LDA NUMH     VALUE IN A,X
  1880        CLC          SIGNAL VALID NUMBER
  1890        RTS          RETURN
  1900 *---MERGE NEXT HEX DIGIT---------
  1910 .8     ASL          POSITION DIGIT
  1920        ASL
  1930        ASL
  1940        ASL
  1950        LDX #4       SHIFT VALUE 4 BITS LEFT
  1960 .9     ASL          SHIFT DIGIT INTO VALUE
  1970        ROL NUML
  1980        ROL NUMH
  1990        DEX
  2000        BNE .9
  2010 *---CONVERT HEX NUMBER-----------
  2020 .7     JSR GNNB     GET NEXT NON-BLANK CHAR
  2030        BEQ .2       ...END OF NUMBER
  2040        EOR #$B0     CONVERT ASCII TO DIGIT
  2050        CMP #10      0...9?
  2060        BCC .8       ...YES, 0-9
  2070        ADC #$88     SHIFT RANGE FOR A-F TEST
  2080        CMP #$FA     A...F?
  2090        BCS .8       ...A-F
  2100 *---SYNTAX ERROR-----------------
  2110 .4     SEC          SIGNAL BAD CHAR OR OVERFLOW
  2120        RTS
  2130 *--------------------------------
  2140 GNC.LC.PATCH
  2150        STX $AA5D
  2160        CMP #$E0
  2170        BCC .1
  2180        AND #$DF
  2190 .1     RTS
  2200 *--------------------------------
  2210        .BS $A229-*
  2220 *--------------------------------
  2230 P1.LENGTH .EQ *-$A1B9
  2240        .EP
  2250 *--------------------------------
  2260        .DA #3,$A19E
  2270        .PH $A19E
  2280        JSR GNC.LC.PATCH
  2290        .EP
  2300 *--------------------------------
  2310        .DA #0       END OF PATCHES
  2320 *--------------------------------

The Oki 6203 Multiply/Divide ChipBob Sander-Cederlof

If you really need to multiply or divide in a hurry, the Oki 6203 may be the ticket. This device sells for about $7, and can be almost directly connected to the Apple bus. All you need is one inverter and a prototyping board.

Assuming you built a little card with the device on it, with its two address lines connected to Apple's A0 and A1 lines, you could multiply two 8-bit numbers for a 16-bit product like this:

       MUL.6203 STA SLOT*16+$C080   1ST OPERAND
                STY SLOT*16+$C081   2ND OPERAND
                LDA #2              MULTIPLY COMMAND
                STA SLOT*16+$C083   COMMAND REGISTER
                NOP                 DELAY FOR RESULT
                LDA SLOT*16+$C081   HI-BYTE OF PRODUCT
                LDY SLOT*16+$C082   LO-BYTE OF PRODUCT
                RTS

A very similar program can divide a 16-bit value by an 8-bit value, producing a quotient and remainder. The time for the multiply is only 22 cycles (plus the JSR and RTS if you make a subroutine), and 24 cycles for the divide.

(Please don't try to order the chip from us, because we don't sell chips.)


A Disassembler for the 65816Bob Sander-Cederlof

When I first got my Apple, there were no books around for learning 6502 assembly language. It took me about 3 months to locate and buy a copy of the 6502 programmer's manual from MOS Technology. About the same time I found a book by William Barden that briefly covered the 8080, 6800, and 6502. But the way I really learned the 6502 was by using Woz's L command in the Apple monitor.

Of course there were no printers or printer interfaces around in those days either, so I spent hours upon hours copying 20 lines at a time off the screen. I wrote down a lot of the monitor, and all of the floating point package and Sweet-16 from the tail end of the Integer BASIC ROM. Fortunately, Apple has never gotten around to eliminating the fabulous L-command from the monitor.

In fact, they have even augmented it. The //c version includes patches to allow disassembly of the additional opcodes and address modes of the 65C02. Since Rak-Ware's DISASM calls on the ROM disassembler to decipher each line of code, the //c version automatically grows to accomodate the 65C02.

Now, what about the 65802 and 65816? It's about time someone wrote a disassembler for that. Someone? Why not me?

It's not easy. On the one hand there is the pressure of competition. Woz's code is SO compact! On the other hand, the new chip is SO complex! It is even ambiguous. There is absolutely no way for a 65816 disassembler to know whether an immediate-mode instruction is two or three bytes long. Only by executing the programming, and tracing it line-by-line, can we tell. And even then, it is possible that a tricky programmer might set up code so that it can be interpreted both ways, depending on other conditions.

To make a long story a little shorter, I did it. You guessed that of course. My solution to the ambiguity problem was to put the burden on the person using it. My solution to the complexity problem was to use extensive tables. My solution to the competition with Woz was to do my best and let him keep his well-deserved glory.

In fact, I started by carefully analyzing Woz's code. The trail starts at $FE9E in the monitor ROM. That short piece of code calls INSTDSP at $F8D0 twenty times to disassemble 20 lines of code. If you take a peek ahead to my listing, lines 1390-1400 patch the language card copy of the monitor inside the L-command loop, so that instead of calling $F8D0 twenty times it calls my disassembler at $0B67 twenty times. (If you are using the language card version of the S-C Macro Assembler, there is a copy of the monitor in the language card too.)

BRUNning the 65816 disassembler will install this little patch and toggle the immediate-mode size flag. Thereafter each 800G command will toggle the state of the immediate-mode size flag. In one state this flag causes immediate mode instructions to be disassembled as 2-byte instructions; in the other, 3-byte instructions.

The tables are quite complicated, and difficult to type in accurately. Therefore I used macros and let the S-C Macro Assembler do the dirty work. The first table starts at line 1500, and consists of the packed names of the single byte opcodes. The macro at lines 1210-1290 defines how the packing is done. The calling line is of the form ">ON A,B,C,D" where the A, B, and C parameters are the three letters of the opcode name. The D parameter is the letter "A" on those opcodes which might also be multiple-byte: ASL, DEC, INC, LSR, ROR, and ROL.

The packing algorithm is almost the same as the one Woz used in the monitor. Each character is represented by five bits, so that three letters take only 15 bits. The macro sets L1, L2, and L3 to the ASCII value (less 64) of the letters of the opcode name. The .SE directive is used for this so that each invocation of the macro can redefine these variables. This compresses the letters from the range $41...5A to $01...1A. Then the .DA line uses multiplication and addition to pack up the compressed letters. Since arithmetic expressions are parsed by the S-C Macro Assembler in a strict left-to-right fashion, "L1*32+L2*32+L3*2" packs them together.

The "ON" macro also generates a label for the opcode name value by using the opcode name, together with the 4th parameter when present. These names are referred to by another table later on.

The second table is just like the first, but with the names of the longer opcodes instead. Notice that ASL, DEC, etc are in this table too, but without the 4th parameter.

The third and fourth tables have 256 entries, one for every possible opcode byte. Each entry is only one byte long, so each table is 256 bytes. Woz used several smaller tables, because the 6502 didn't use every possible opcode value. The 65816 does define an opcode name for every possible value.

The OPINDEX table uses two macros: "OXA" for single byte opcodes, and "OXB" for longer opcodes. Each entry is a pointer to the name in the OPNAMES.A or OPNAMES.B tables. The pointer is divided by two, leaving room for a flag bit which tells which of the two tables the name is in.

The entries in the OPFORMAT table are offsets into the FMTBL. These are all multiples of 2, because the FMTBL entries are two bytes each.

FMTBL contains coded information indicating how many bytes comprise the instruction and operand, and what the address mode looks like in assembly language. The length can be from two to four bytes, and is coded as 1...3 in the last two bits. The rest of the bits tell which special characters to print and where to print the value of the operand bytes. Single byte opcodes don't have any entries in this table.

One more table, the last one: FMTSTR. This defines the meaning of the bits in FMTBL. Note that the characters are the same as the ones in the various comment lines within FMTBL, only in reverse order.

Finally, we get to the code. The 20-line disassembler calls INSTDSP at line 6180. This starts by calling INSDS1 at line 5760. INSDS1 and INSDS2 are kept as defined points because other software sometimes calls these two points. If you wanted to modify Rak-Ware's DISASM, for example, you would probably need these.

Lines 5760-5840 print the address of the next opcode, and "- ". Lines 5850-5860 pick up that opcode byte. If you enter at INSDS2, have the opcode byte already in the A-register. Lines 5870-5980 dig into the tables to get the opcode name, format, and length for single-byte opcodes. Lines 6000-6160 do the same for longer opcodes. The differences for longer opcodes are several: the second opname table is used, the format is gotten from the tables, and the immediate-mode size flag is used to determine the length of immediate mode opcodes.

Lines 6200-6300 print out the 1-4 bytes of the opcode in hex. If there are less than four bytes, enough blanks are printed so that we always end up in the same position. Lines 6310-6400 unpack the opcode name and print it out. If the opcode is single byte, lines 6410-6420 find out and send us back home (we are finished with this line).

Lines 6430-6450 test the format to detect MVP, MVN, and relative address mode instructions. These special cases are handled by lines 6690-7050. All other operand formats are handled by lines 6470-6680. I see now that I could have put lines 6470-6480 back before line 6430, so that the blank separating the opname from the operand was printed before splitting on the mode. Then lines 6700-6710 could be deleted, saving five bytes. Of course line 6720 would then receive the ".9" label.

Lines 6500-6520 shift out one bit at a time of the format bit string. The corresponding index counts down in the X-register from 10 to 0, and picks a format character from FMTSTR to print. After the character is printed, two special cases are looked for. If the character was "#", meaning immediate mode, and if the immediate-mode size flag indicates long immediates, another "#" is printed. If the character was "$", it is time to print the operand in hex, as two, four, or six digits (lines 6620-6650).

Relative addresses may be either 8-bit or 16-bit. Lines 6780-6820 start the computation for 8-bit values, and call on a monitor routine to finish the printing. Lines 6840-6950 do the same for 16-bit relatives. (There are no two-bit relatives here, no matter what the family tree has borne.)

Finally, lines 6970-7050 print out the two bank bytes for the MVP and MVN instructions. This is different from the way you write MVP and MVN for assembly by the S-C Macro Assembler. In the assembler you write "MVP addr1,addr2", where both addresses are 24-bit values. The bank bytes come from the high byte of each 24-bit address. To be compatible with the assembler I should change lines 6970-7050 to print out "0000" after each bank byte.

It seems like a worthy project for someone to incorporate my program into Rak-Ware's DISASM, or perhaps a new similar product. If so, that someone should figure out a neat interactive way to control the immediate-mode size flag. How about it, Bob?

  1000  .LIF
  1010        .TI 76,65816 DISASSEMBLER.................FEBRUARY 14, 1985...........
  1020 *SAVE S.65816 DISASM
  1030 *--------------------------------
  1040 IMM.SIZE .EQ $00
  1050 LMNEM    .EQ $2C
  1060 RMNEM    .EQ $2D
  1070 FORMATL  .EQ $2E
  1080 LENGTH   .EQ $2F
  1090 FORMATH  .EQ $30
  1100 PCL      .EQ $3A
  1110 PCH      .EQ $3B
  1120 *--------------------------------
  1130 SCRN2  .EQ $F879
  1140 RELADR .EQ $F938
  1150 PRNTAX .EQ $F941
  1160 PRBLNK .EQ $F948
  1170 PRBL2  .EQ $F94A
  1180 PCADJ  .EQ $F953
  1190 CROUT  .EQ $FD8E
  1200 PRBYTE .EQ $FDDA
  1210 COUT   .EQ $FDED
  1220 *--------------------------------
  1230        .MA ON
  1240        .LIST OFF
  1250 L1     .SE ']1-64
  1260 L2     .SE ']2-64
  1270 L3     .SE ']3-64
  1280 *      .LIST ON
  1290 ]1]2]3]4 .DA L1*32+L2*32+L3*2
  1300        .EM
  1310 *--------------------------------
  1320        .MA OXA
  1330        .DA #]1-OPNAMES.A/2+128
  1340        .EM
  1350 *--------------------------------
  1360        .MA OXB
  1370        .DA #]1-OPNAMES.B/2
  1380        .EM
  1390 *--------------------------------
  1400 T      LDA $C083
  1410        LDA $C083
  1420        LDA #INSTDSP
  1430        STA $FE65
  1440        LDA /INSTDSP
  1450        STA $FE66
  1460        LDA IMM.SIZE
  1470        EOR #$FF
  1480        STA IMM.SIZE
  1490        RTS
  1500 *--------------------------------
  1510 OPNAMES.A
  1520        >ON A,S,L,A
  1530        >ON B,R,K
  1540        >ON C,L,C
  1550        >ON C,L,D
  1560        >ON C,L,I
  1570        >ON C,L,V
  1580        >ON C,O,P
  1590        >ON D,E,C,A
  1600        >ON D,E,X
  1610        >ON D,E,Y
  1620        >ON I,N,C,A
  1630        >ON I,N,X
  1640        >ON I,N,Y
  1650        >ON L,S,R,A
  1660        >ON N,O,P
  1670        >ON P,H,A
  1680        >ON P,H,B
  1690        >ON P,H,D
  1700        >ON P,H,K
  1710        >ON P,H,P
  1720        >ON P,H,X
  1730        >ON P,H,Y
  1740        >ON P,L,A
  1750        >ON P,L,B
  1760        >ON P,L,D
  1770        >ON P,L,P
  1780        >ON P,L,X
  1790        >ON P,L,Y
  1800        >ON R,O,L,A
  1810        >ON R,O,R,A
  1820        >ON R,T,I
  1830        >ON R,T,L
  1840        >ON R,T,S
  1850        >ON S,E,C
  1860        >ON S,E,D
  1870        >ON S,E,I
  1880        >ON S,T,P
  1890        >ON T,A,X
  1900        >ON T,A,Y
  1910        >ON T,C,D
  1920        >ON T,C,S
  1930        >ON T,D,C
  1940        >ON T,S,C
  1950        >ON T,S,X
  1960        >ON T,X,A
  1970        >ON T,X,S
  1980        >ON T,X,Y
  1990        >ON T,Y,A
  2000        >ON T,Y,X
  2010        >ON W,A,I
  2020        >ON W,D,M
  2030        >ON X,B,A
  2040        >ON X,C,E
  2050 *--------------------------------
  2060 OPNAMES.B
  2070        >ON A,D,C
  2080        >ON A,N,D
  2090        >ON A,S,L
  2100        >ON B,C,C
  2110        >ON B,C,S
  2120        >ON B,E,Q
  2130        >ON B,I,T
  2140        >ON B,M,I
  2150        >ON B,N,E
  2160        >ON B,P,L
  2170        >ON B,R,A
  2180        >ON B,R,L
  2190        >ON B,V,C
  2200        >ON B,V,S
  2210        >ON C,M,P
  2220        >ON C,P,X
  2230        >ON C,P,Y
  2240        >ON D,E,C
  2250        >ON E,O,R
  2260        >ON I,N,C
  2270        >ON J,M,L
  2280        >ON J,M,P
  2290        >ON J,S,L
  2300        >ON J,S,R
  2310        >ON L,D,A
  2320        >ON L,D,X
  2330        >ON L,D,Y
  2340        >ON L,S,R
  2350        >ON M,V,N
  2360        >ON M,V,P
  2370        >ON O,R,A
  2380        >ON P,E,A
  2390        >ON P,E,I
  2400        >ON P,E,R
  2410        >ON R,E,P
  2420        >ON R,O,L
  2430        >ON R,O,R
  2440        >ON S,B,C
  2450        >ON S,E,P
  2460        >ON S,T,A
  2470        >ON S,T,X
  2480        >ON S,T,Y
  2490        >ON S,T,Z
  2500        >ON T,R,B
  2510        >ON T,S,B

That's enough of that. The assembly listing of that table expands to about 4 pages, so here's a hex dump of OPNAMES.A and OPNAMES.B (By the way, OPNAMES.B .EQ $881):

[[ in the printed edition, a hex dump of OPNAMES.A and OPNAMES.B appeared here instead of the source code above. ]] 8503-1

And the OPINDEX table runs about 7 pages, so another hex dump: 8503-2

[[ in the printed edition, a hex dump of OPINDEX appeared here instead of the following source code. ]]

  2520 *--------------------------------
  2530 OPINDEX
  2540 *---0X---------------------------
  2550        >OXA BRK
  2560        >OXB ORA
  2570        >OXA COP
  2580        >OXB ORA
  2590        >OXB TSB
  2600        >OXB ORA
  2610        >OXB ASL
  2620        >OXB ORA
  2630        >OXA PHP
  2640        >OXB ORA
  2650        >OXA ASLA
  2660        >OXA PHD
  2670        >OXB TSB
  2680        >OXB ORA
  2690        >OXB ASL
  2700        >OXB ORA
  2710 *---1X---------------------------
  2720        >OXB BPL
  2730        >OXB ORA
  2740        >OXB ORA
  2750        >OXB ORA
  2760        >OXB TRB
  2770        >OXB ORA
  2780        >OXB ASL
  2790        >OXB ORA
  2800        >OXA CLC
  2810        >OXB ORA
  2820        >OXA INCA
  2830        >OXA TCS
  2840        >OXB TRB
  2850        >OXB ORA
  2860        >OXB ASL
  2870        >OXB ORA
  2880 *---2X---------------------------
  2890        >OXB JSR
  2900        >OXB AND
  2910        >OXB JSL
  2920        >OXB AND
  2930        >OXB BIT
  2940        >OXB AND
  2950        >OXB ROL
  2960        >OXB AND
  2970        >OXA PLP
  2980        >OXB AND
  2990        >OXA ROLA
  3000        >OXA PLD
  3010        >OXB BIT
  3020        >OXB AND
  3030        >OXB ROL
  3040        >OXB AND
  3050 *---3X---------------------------
  3060        >OXB BMI
  3070        >OXB AND
  3080        >OXB AND
  3090        >OXB AND
  3100        >OXB BIT
  3110        >OXB AND
  3120        >OXB ROL
  3130        >OXB AND
  3140        >OXA SEC
  3150        >OXB AND
  3160        >OXA DECA
  3170        >OXA TSC
  3180        >OXB BIT
  3190        >OXB AND
  3200        >OXB ROL
  3210        >OXB AND
  3220 *---4X---------------------------
  3230        >OXA RTI
  3240        >OXB EOR
  3250        >OXA WDM
  3260        >OXB EOR
  3270        >OXB MVP
  3280        >OXB EOR
  3290        >OXB LSR
  3300        >OXB EOR
  3310        >OXA PHA
  3320        >OXB EOR
  3330        >OXA LSRA
  3340        >OXA PHK
  3350        >OXB JMP
  3360        >OXB EOR
  3370        >OXB LSR
  3380        >OXB EOR
  3390 *---5X---------------------------
  3400        >OXB BVC
  3410        >OXB EOR
  3420        >OXB EOR
  3430        >OXB EOR
  3440        >OXB MVN
  3450        >OXB EOR
  3460        >OXB LSR
  3470        >OXB EOR
  3480        >OXA CLI
  3490        >OXB EOR
  3500        >OXA PHY
  3510        >OXA TCD
  3520        >OXB JMP
  3530        >OXB EOR
  3540        >OXB LSR
  3550        >OXB EOR
  3560 *---6X---------------------------
  3570        >OXA RTS
  3580        >OXB ADC
  3590        >OXB PER
  3600        >OXB ADC
  3610        >OXB STZ
  3620        >OXB ADC
  3630        >OXB ROR
  3640        >OXB ADC
  3650        >OXA PLA
  3660        >OXB ADC
  3670        >OXA RORA
  3680        >OXA RTL
  3690        >OXB JMP
  3700        >OXB ADC
  3710        >OXB ROR
  3720        >OXB ADC
  3730 *---7X---------------------------
  3740        >OXB BVS
  3750        >OXB ADC
  3760        >OXB ADC
  3770        >OXB ADC
  3780        >OXB STZ
  3790        >OXB ADC
  3800        >OXB ROR
  3810        >OXB ADC
  3820        >OXA SEI
  3830        >OXB ADC
  3840        >OXA PLY
  3850        >OXA TDC
  3860        >OXB JMP
  3870        >OXB ADC
  3880        >OXB ROR
  3890        >OXB ADC
  3900 *---8X---------------------------
  3910        >OXB BRA
  3920        >OXB STA
  3930        >OXB BRL
  3940        >OXB STA
  3950        >OXB STY
  3960        >OXB STA
  3970        >OXB STX
  3980        >OXB STA
  3990        >OXA DEY
  4000        >OXB BIT
  4010        >OXA TXA
  4020        >OXA PHB
  4030        >OXB STY
  4040        >OXB STA
  4050        >OXB STX
  4060        >OXB STA
  4070 *---9X---------------------------
  4080        >OXB BCC
  4090        >OXB STA
  4100        >OXB STA
  4110        >OXB STA
  4120        >OXB STY
  4130        >OXB STA
  4140        >OXB STX
  4150        >OXB STA
  4160        >OXA TYA
  4170        >OXB STA
  4180        >OXA TXS
  4190        >OXA TXY
  4200        >OXB STZ
  4210        >OXB STA
  4220        >OXB STZ
  4230        >OXB STA
  4240 *---AX---------------------------
  4250        >OXB LDY
  4260        >OXB LDA
  4270        >OXB LDX
  4280        >OXB LDA
  4290        >OXB LDY
  4300        >OXB LDA
  4310        >OXB LDX
  4320        >OXB LDA
  4330        >OXA TAY
  4340        >OXB LDA
  4350        >OXA TAX
  4360        >OXA PLB
  4370        >OXB LDY
  4380        >OXB LDA
  4390        >OXB LDX
  4400        >OXB LDA
  4410 *---BX---------------------------
  4420        >OXB BCS
  4430        >OXB LDA
  4440        >OXB LDA
  4450        >OXB LDA
  4460        >OXB LDY
  4470        >OXB LDA
  4480        >OXB LDX
  4490        >OXB LDA
  4500        >OXA CLV
  4510        >OXB LDA
  4520        >OXA TSX
  4530        >OXA TYX
  4540        >OXB LDY
  4550        >OXB LDA
  4560        >OXB LDX
  4570        >OXB LDA
  4580 *---CX---------------------------
  4590        >OXB CPY
  4600        >OXB CMP
  4610        >OXB REP
  4620        >OXB CMP
  4630        >OXB CPY
  4640        >OXB CMP
  4650        >OXB DEC
  4660        >OXB CMP
  4670        >OXA INY
  4680        >OXB CMP
  4690        >OXA DEX
  4700        >OXA WAI
  4710        >OXB CPY
  4720        >OXB CMP
  4730        >OXB DEC
  4740        >OXB CMP
  4750 *---DX---------------------------
  4760        >OXB BNE
  4770        >OXB CMP
  4780        >OXB CMP
  4790        >OXB CMP
  4800        >OXB PEI
  4810        >OXB CMP
  4820        >OXB DEC
  4830        >OXB CMP
  4840        >OXA CLD
  4850        >OXB CMP
  4860        >OXA PHX
  4870        >OXA STP
  4880        >OXB JML
  4890        >OXB CMP
  4900        >OXB DEC
  4910        >OXB CMP
  4920 *---EX---------------------------
  4930        >OXB CPX
  4940        >OXB SBC
  4950        >OXB SEP
  4960        >OXB SBC
  4970        >OXB CPX
  4980        >OXB SBC
  4990        >OXB INC
  5000        >OXB SBC
  5010        >OXA INX
  5020        >OXB SBC
  5030        >OXA NOP
  5040        >OXA XBA
  5050        >OXB CPX
  5060        >OXB SBC
  5070        >OXB INC
  5080        >OXB SBC
  5090 *---FX---------------------------
  5100        >OXB BEQ
  5110        >OXB SBC
  5120        >OXB SBC
  5130        >OXB SBC
  5140        >OXB PEA
  5150        >OXB SBC
  5160        >OXB INC
  5170        >OXB SBC
  5180        >OXA SED
  5190        >OXB SBC
  5200        >OXA PLX
  5210        >OXA XCE
  5220        >OXB JSR
  5230        >OXB SBC
  5240        >OXB INC
  5250        >OXB SBC

The assembly listing of OPFORMAT is around a page and a half, so we'll just LIST this one:

  5260 *--------------------------------
  5270 OPFORMAT
  5280 F.0    .HS 00.14.00.1C.02.02.02.20.00.00.00.00.04.04.04.06
  5290 F.1    .HS 26.16.12.1E.02.08.08.22.00.10.00.00.04.0A.0A.0C
  5300 F.2    .HS 04.14.06.1C.02.02.02.20.00.00.00.00.04.04.04.06
  5310 F.3    .HS 26.16.12.1E.08.08.08.22.00.10.00.00.0A.0A.0A.0C
  5320 F.4    .HS 00.14.00.1C.24.02.02.20.00.00.00.00.04.04.04.06
  5330 F.5    .HS 26.16.12.1E.24.08.08.22.00.10.00.00.06.0A.0A.0C
  5340 F.6    .HS 00.14.28.1C.02.02.02.20.00.00.00.00.18.04.04.06
  5350 F.7    .HS 26.16.12.1E.08.08.08.22.00.10.00.00.1A.0A.0A.0C
  5360 F.8    .HS 26.14.28.1C.02.02.02.20.00.00.00.00.04.04.04.06
  5370 F.9    .HS 26.16.12.1E.08.08.0E.22.00.10.00.00.04.0A.0A.0C
  5380 F.A    .HS 00.14.00.1C.02.02.02.20.00.00.00.00.04.04.04.06
  5390 F.B    .HS 26.16.12.1E.08.08.0E.22.00.10.00.00.0A.0A.10.0C
  5400 F.C    .HS 00.14.00.1C.02.02.02.20.00.00.00.00.04.04.04.06
  5410 F.D    .HS 26.16.12.1E.02.08.08.22.00.10.00.00.18.0A.0A.0C
  5420 F.E    .HS 00.14.00.1C.02.02.02.20.00.00.00.00.04.04.04.06
  5430 F.F    .HS 26.16.12.1E.08.08.08.22.00.10.00.00.1A.0A.0A.0C
 

For a complete source listing of this program send a legal-size self-addressed envelope with 2 ounces postage. Or, order Quarterly Disk #18 for $15 to get S.65816.DISASM along with all the rest of the source code from the last three issues on disk.

[[ Obviously the entire source code is here in this web edition. ]]

  5440 *--------------------------------
  5450 FMTBL
  5460 *-----# > ( $ , X S ) , Y $ - - - LL
  5470  .DA %1.0.0.1.0.0.0.0.0.0.0.0.0.0.01 -- IMMEDIATE    00
  5480  .DA %0.0.0.1.0.0.0.0.0.0.0.0.0.0.01 -- DIRECT       02
  5490  .DA %0.0.0.1.0.0.0.0.0.0.0.0.0.0.10 -- ABS          04
  5500  .DA %0.0.0.1.0.0.0.0.0.0.0.0.0.0.11 -- LONG         06
  5510 *-----# > ( $ , X S ) , Y $ - - - LL
  5520  .DA %0.0.0.1.1.1.0.0.0.0.0.0.0.0.01 -- DIRECT,X     08
  5530  .DA %0.0.0.1.1.1.0.0.0.0.0.0.0.0.10 -- ABS,X        0A
  5540  .DA %0.0.0.1.1.1.0.0.0.0.0.0.0.0.11 -- LONG,X       0C
  5550 *-----# > ( $ , X S ) , Y $ - - - LL
  5560  .DA %0.0.0.1.1.0.0.0.0.1.0.0.0.0.01 -- DIRECT,Y     0E
  5570  .DA %0.0.0.1.1.0.0.0.0.1.0.0.0.0.10 -- ABS,Y        10
  5580 *-----# > ( $ , X S ) , Y $ - - - LL
  5590  .DA %0.0.1.1.0.0.0.1.0.0.0.0.0.0.01 -- IND          12
  5600  .DA %0.0.1.1.1.1.0.1.0.0.0.0.0.0.01 -- INDX         14
  5610  .DA %0.0.1.1.0.0.0.1.1.1.0.0.0.0.01 -- INDY         16
  5620 *-----# > ( $ , X S ) , Y $ - - - LL
  5630  .DA %0.0.1.1.0.0.0.1.0.0.0.0.0.0.10 -- INDABS       18
  5640  .DA %0.0.1.1.1.1.0.1.0.0.0.0.0.0.10 -- INDABSX      1A
  5650 *-----# > ( $ , X S ) , Y $ - - - LL
  5660  .DA %0.0.0.1.1.0.1.0.0.0.0.0.0.0.01 -- STK          1C
  5670  .DA %0.0.1.1.1.0.1.1.1.1.0.0.0.0.01 -- STKY         1E
  5680 *-----# > ( $ , X S ) , Y $ - - - LL
  5690  .DA %0.1.1.1.0.0.0.1.0.0.0.0.0.0.01 -- INDLONG      20
  5700  .DA %0.1.1.1.0.0.0.1.1.1.0.0.0.0.01 -- INDLONGY     22
  5710  .DA %0.0.0.1.0.0.0.0.1.0.1.0.0.0.10 -- MVN & MVP    24
  5720  .DA %0.0.0.0.0.0.0.0.0.0.1.0.0.0.01 -- RELATIVE     26
  5730  .DA %0.0.0.0.0.0.0.0.0.0.1.0.0.0.10 -- LONG RELA.   28
  5740 *--------------------------------
  5750 FMTSTR .AS -/$Y,)SX,$(>#/
  5760 *--------------------------------
  5770 INSDS1 JSR CROUT
  5780        LDA PCH
  5790        JSR PRBYTE
  5800        LDA PCL
  5810        JSR PRBYTE
  5820        LDA #"-"
  5830        JSR COUT
  5840        LDA #" "
  5850        JSR COUT
  5860        LDY #0
  5870        LDA (PCL),Y  GET OPCODE
  5880 INSDS2 TAY          SAVE IN Y-REG
  5890        LDA OPINDEX,Y
  5900        ASL
  5910        TAX
  5920        BCC .1       ...NOT SINGLE BYTE OPCODE
  5930        LDA OPNAMES.A,X
  5940        STA RMNEM
  5950        LDA OPNAMES.A+1,X
  5960        STA LMNEM
  5970        LDA #0
  5980        STA LENGTH
  5990        RTS
  6000 *--------------------------------
  6010 .1     LDA OPNAMES.B,X
  6020        STA RMNEM
  6030        LDA OPNAMES.B+1,X
  6040        STA LMNEM
  6050        LDX OPFORMAT,Y
  6060        LDA FMTBL+1,X
  6070        STA FORMATH
  6080        LDA FMTBL,X
  6090        STA FORMATL
  6100        AND #3
  6110        STA LENGTH
  6120        TXA          CHECK IF IMMEDIATE
  6130        BNE .2       ...NO
  6140        BIT IMM.SIZE CHECK IF 16-BIT MODE
  6150        BPL .2       ...NO
  6160        INC LENGTH   ...YES
  6170 .2     RTS
  6180 *--------------------------------
  6190 INSTDSP
  6200        JSR INSDS1
  6210        LDY #0
  6220 .1     LDA (PCL),Y
  6230        JSR PRBYTE
  6240        LDX #1       PRINT 1 BLANK
  6250 .2     JSR PRBL2
  6260        CPY LENGTH
  6270        INY
  6280        BCC .1
  6290        LDX #3
  6300        CPY #4
  6310        BCC .2
  6320 *---PRINT MNEMONIC---------------
  6330        LDY #3
  6340 .3     LDA #6
  6350 .4     ASL RMNEM
  6360        ROL LMNEM
  6370        ROL
  6380        BPL .4
  6390        JSR COUT
  6400        DEY
  6410        BNE .3
  6420        LDY LENGTH
  6430        BEQ .8       ...SINGLE BYTE OPCODE
  6440        LDA FORMATL
  6450        AND #$20     SEE IF SPECIAL
  6460        BNE .9       ...YES, MOVES OR RELATIVES
  6470 *---PRINT NORMAL OPERANDS--------
  6480        LDA #" "
  6490        JSR COUT
  6500        LDX #10      11 FORMAT BITS
  6510 .5     ASL FORMATL
  6520        ROL FORMATH
  6530        BCC .7
  6540        LDA FMTSTR,X
  6550        JSR COUT
  6560        CMP #"#"
  6570        BNE .55
  6580        BIT IMM.SIZE
  6590        BPL .7
  6600        JSR COUT
  6610 .55    CMP #"$"
  6620        BNE .7
  6630 .6     LDA (PCL),Y
  6640        JSR PRBYTE
  6650        DEY
  6660        BNE .6
  6670 .7     DEX
  6680        BPL .5
  6690 .8     RTS
  6700 *---SPECIAL CASES----------------
  6710 .9     LDA #" "
  6720        JSR COUT
  6730        LDA #"$"
  6740        JSR COUT
  6750        LDA FORMATL
  6760        BMI .11      MVN & MVP
  6770        DEY          DISTINGUISH RELATIVES
  6780        BNE .10      16-BIT RELATIVE
  6790 *---8-BIT RELATIVE---------------
  6800        INY          8-BIT RELATIVE
  6810        LDA (PCL),Y  GET 8-BIT OFFSET
  6820        SEC
  6830        JMP RELADR
  6840 *---16-BIT RELATIVE--------------
  6850 .10    LDA (PCL),Y  LOW BYTE OF OFFSET
  6860        STA FORMATL
  6870        INY
  6880        LDA (PCL),Y  HIGH BYTE OF OFFSET
  6890        STA FORMATH
  6900        JSR PCADJ
  6910        CLC
  6920        ADC FORMATL
  6930        TAX
  6940        TYA
  6950        ADC FORMATH
  6960        JMP PRNTAX
  6970 *---MVN & MVP--------------------
  6980 .11    LDA (PCL),Y
  6990        JSR PRBYTE
  7000        LDA #","
  7010        JSR COUT
  7020        LDA #"$"
  7030        JSR COUT
  7040        DEY
  7050        LDA (PCL),Y
  7060        JMP PRBYTE
  7070 *--------------------------------
  7080 TT     LDY #0
  7090        LDA #$C0
  7100        STA PCL
  7110        LDA #2       $2C0...$3C3
  7120        STA PCH
  7130 .1     TYA
  7140        STA $2C0,Y
  7150        INY
  7160        BNE .1
  7170        STY $3C0
  7180        INY
  7190        STY $3C1
  7200        INY
  7210        STY $3C2
  7220 .2     JSR INSTDSP
  7230        LDY #0
  7240        LDA (PCL),Y
  7250        CMP #$FF
  7260        BEQ .3
  7270 .4     LDA $C000
  7280        BPL .4
  7290        STA $C010
  7300        INC PCL
  7310        BNE .2
  7320        INC PCH
  7330        BNE .2       ...ALWAYS
  7340 .3     RTS
  7350 *--------------------------------

Finding Memory Size in ProDOSBob Sander-Cederlof

On page 6-63 of Beneath Apple ProDOS there is a small piece of code designed to determine how much memory there is:

       LDA $BF98
       ASL
       ASL
       BIT 0
       BPL SMLMEM      48K
       BVS MEM128      128K
       ...             otherwise 64K

The code will not work. The BIT 0 will test bits 7 and 6 of memory location $0000, which have nothing whatsoever to do with how much memory is in your machine. What was intended was to test bits 7 and 6 of the A-register, or in other words bits 5 and 4 of $BF98. Here is one way you can do that:

       LDA $BF98
       ASL
       ASL
       ASL
       BCC SMLMEM      48K
       BMI MEM128      128K
       ...             OTHERWISE 64K

Notice that not only does this perform the test correctly, it is also one byte shorter!

If you insist on using the same number of bytes, here is another way to test those bits:

       LDA $BF98
       AND #%00110000  ISOLATE BITS 5 AND 4
       CMP #%00100000
       BCC SMLMEM      48K
       BNE MEM128      128K
       ...             OTHERWISE 64K

If any of you have discovered any other problems with the sample code in this book, pass them along.


Apple Assembly Line is published monthly by S-C SOFTWARE CORPORATION, P.O. Box 280300, Dallas, Texas 75228. Phone (214) 324-2050. Subscription rate is $18 per year in the USA, sent Bulk Mail; add $3 for First Class postage in USA, Canada, and Mexico; add $12 postage for other countries. Back issues are available for $1.80 each (other countries add $1 per back issue for postage).

All material herein is copyrighted by S-C SOFTWARE CORPORATION, all rights reserved. (Apple is a registered trademark of Apple Computer, Inc.)