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