Volume 5 -- Issue 6 | March 1985

In This Issue...

- Shortening the DOS File Buffer Builder
- 65C02s in Old Apples
- Improved DOS 3.3 Number Parsing & Lower-Case DOS Commands
- The Oki 6203 Multiply/Divide Chip
- A Disassembler for the 65816
- Finding Memory Size in ProDOS

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 Builder | Bob 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:

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 Apples | Jim 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 char- acters 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 follow 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 Chip | Bob 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 65816 | Bob 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. ]]

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

[[ 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 ProDOS | Bob 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.)