In This Issue...
Programming the 65816
Last month we expected to have ready for this issue a review of David Eyes' new book on programming the 65816 microprocessor. Well the books still haven't arrived, despite the passing of two promised shipping dates, so we're still waiting to see when they will really be available and what they come out like. We are accepting orders (about 20 so far!) and will send out the books and publish a review as soon as they arrive from Prentice-Hall.
quikLoading AppleWorks
For you quikLoader owners who are also using AppleWorks (or for you AppleWorks enthusiasts who want your computer to instantly start up in AppleWorks), Southern California Research Group can now produce a set of quikLoader EPROMs from your configured AppleWorks program disks. The price for the EPROMs and the programming service is $89.50. For more information call SCRG at (805) 529-2082.
For some reason, we have until now avoided this subject. Many versions of RAM disks have been created and published in various magazines. The programs always seemed to me to be rather long and involved for what they really had to do. Recently a friend typed one in from Nibble, prompting me to try my hand.
The so-called "language card" is really the 16K RAM area. In //e and //c computers it is not a separate card at all, just the top 16K of the motherboard RAM. It received the monicker of "language card" because it was first sold as a separate card with the Pascal language system. The RAM in this area is not directly addressable, because the top 16K of Apple's address space is normally allocated to I/O ($C000-CFFF) and ROM ($D000-FFFF).
By flipping a few software-controlled switches the address range from $D000 through $FFFF can be made to point at the 16K RAM instead of ROM. Furthermore, the addresses from $D000 through $DFFF can be pointed at either of two 4K banks. If you have an Apple II or II+ with a 16K RAM card you already know this, of course.
Some programs use the language card under DOS, and some do not. Some which do are Integer BASIC, S-C Macro Assembler, Visicalc, Magicalc, Big Mac, and Merlin. If you are just using Applesoft to run your own programs, the language card is not used.
If the card is otherwise idle, that RAM could be used to simulate a small disk drive. My program sets it up as a 64 sector drive, with 60 sectors available for files. One sector is used for the VTOC, and three sectors are used for the catalog. You can save up to 21 files into the disk, or one file of up to 60 sectors.
One of the first questions I had to answer was where to put the program. Naturally, it ended up at $300. This is almost always my first choice, because it is so easy. If I find some substantial reasons, I try harder and find some other place in RAM for my programs. The ramdisk code could be placed inside DOS itself, on top of the RWTS format code. Another choice might be to use up one page of the language card for the bulk of the code, using only a few lines of code inside RWTS to switch it on and off. I like this idea, but it does deprive me of one sector out of 60. Anyway, for now let's just leave it at $300.
Another choice to be made is how to link into DOS. Many hard disks and other ramdisks do it by placing a JMP or JSR instruction at the beginning of RWTS ($BD00-BD02). This works very well, but it would be nice to be able to use both our ramdisk and any hard disk also. Therefore, I figured out a way to chain my ramdisk together with my Sider hard disk. The method should be compatible with all the ramdisks and hard disks which patch in at $BD00.
The program is broken into two parts. The first part installs the ramdisk, and the second part performs the reads and writes. The installer loads and executes at $4000, but of course you could change it to whatever you wish.
I use six page zero locations. These are all locations which are used by regular RWTS, so it is all right for me to use them. I don't even need to save the original data and restore it when I am finished.
Lines 1090-1150 copy the read/write part down to $300-3B4. I actually copy a few extra bytes, but no harm done. I do have to be careful not to write any bytes above $3CF, because $3D0-3FF is already used by DOS and the monitor.
Lines 1160-1230 save the current contents of $BD00-BD02, and place a JMP to my ramdisk code there. Any future calls to RWTS will be vectored to my code down in page 3.
Lines 1250 and 1260 may look ridiculous, if you have not tried programming the language card before. The software-controlled switches ("soft switches") in the Apple are designed so that you have to make two references to address $C083 to turn it on and un-protect it. Two references to $C08B turn on the card also, but with the other 4K bank at $D000.
Lines 1270-1340 store zeroes in every byte from $D000-D3FF. In my scheme, those four pages are equivalent to four sectors (track $11, sectors 0-3). Now that I have mentioned that, why not tell you how I have laid out the whole 16K?
Bank Addresses Trk Sectors ---------------------------- C083 D000-D3FF $11 0-3 C083 D400-DFFF $01 4-F C08B D000-DFFF $02 0-F E000-EFFF $03 0-F F000-FFFF $04 0-F
Lines 1350-1420 chain the three catalog sectors together. I have set up track $11 sector 3 as the first catalog sector, sector 2 as the second, and sector 1 as the third and last. This is the same kind of chain DOS makes on a real disk, but shorter.
Lines 1430-1500, together with the two data lines at 1550 and 1560, fill in the non-zero bytes in the VTOC sector. This table driven technique takes somewhat fewer bytes than direct code. I know, because the first time I wrote it the direct way: LDA, STA, LDA, STA, etc. The code as it now is plus the tables takes 45 bytes. The other way it takes 42 bytes just for the STA instructions. If I use LDA #$xx for each of the different values, that is another 16 bytes. So, I saved about 13 bytes. The TBLX line gives the offsets into the $D000 page, and the TBLA line gives the data value which should be stored at each one. I use a 00 offset to indicate the end of the list.
Line 1580 tells the assembler to start assembling code to be executed at $300, but to keep putting the object code bytes in a continuous stream. Since we are writing the code on a target file (see line 1030), the whole program is on one file. RAMDISK.IMAG gets the value $4076, which is what the program counter is BEFORE the .PH directive takes effect. At line 1590 RAMDISK.REAL gets the value $300.
When a program calls RWTS, it is usually through as JSR $B7B5 instruction. The code at $B7B5 disables the interrupts and then does a JSR $BD00. We put our hook at $BD00, so the code jumps to $306, my label LITTLE.RAM.DISK. Lines 1650 and 1660 are the code which normally is executed at $BD00-BD03. They store the IOB address.
Lines 1670-1700 pick up the slot number out of the IOB. This is actually the slot number times 16. If the caller has specified slot 3, he wants to read or write the ramdisk. Any other slot, we need to let regular RWTS do the work. Lines 1710-1750 copy the original contents back to $BD00-BD02. Then I can call RWTS again, and this time it won't come back until it has done its job. Lines 1760-1780 restore Y and A as they were before we got involved, and re-call RWTS. When RWTS is finished, lines 1790-1830 put my hook back into $BD00-BD02. You might wonder if I should be saving and restoring the Y- and A-registers here. I originally did, saving them before line 1790 and restoring them before 1840. Then I realized that the normal contents of Y and A after visiting RWTS are not meaningful. Only the carry status bit is important, as it signifies whether there was an error or not.
If the caller specified slot 3, he wants to talk to our ramdisk. Lines 1860-1900 check to make sure he specified drive 1. If not, we call it an error. I funneled all of the messages through .99, setting the error byte in the IOB to $40. This causes DOS to say there was an I/O error.
I used an EOR #1 rather than CMP #1 at line 189~ so that if the drive was correct, we would also have 0 in the A-register. At some point I need to store 0 into RAMP, and this saves me a LDA #0 instruction. Then line 1910 can set RAMP to 0.
Lines 1930-1970 pick up the sector number the caller specified, and checks it for proper range. It must be from 0 to 15 to be valid. For the time being I save it in a handier location, RAMP+1.
Lines 1980-2020 and 2110-2120 check the track value. I will accept tracks 1-4 and $11, but no others. I have to accept $11, because that is where DOS always expects the VTOC to be, and where the catalog almost always is. The other four tracks could be anything I want, just so they are not $11. Since I am only using 4 sectors of track 11 for VTOC and catalog, I want the others to be usable for files. DOS refuses to allocate any sectors to files in track 11 unless we patch some code in the file manager, so I just put the rest of that bank of ram in another track.
Lines 2040-2060 make sure that if the caller wants track $11, his sector number is not bigger than 3. Lines 2130-2170 make sure that if the caller wants track 1, his sector number is not less than 4. If the track is either $11 or 1, lines 2070-2090 set us up to use the $C083 bank at $D000, with the sector specifying which page in that bank to use.
If the caller wants track 2, 3, or 4 then lines 2250-2310 set up the $C08B side, and compute the page number according to the table given above.
All this may be academic, because we have yet to look at the opcode. We are only implementing read and write, so if the opcode is something else we give an error. Lines 2340-2390 check the opcode, and also set the carry status for read or clear carry for write.
Lines 2400-2420 write enable the ramcard and select the proper $D000 bank. The value in the X-register is either 0 or 8, so we are either addressing $C083 or $C08B twice. We don't really need to write enable it unless the opcode was WRITE, but it doesn't hurt anything.
Lines 2430-2460 clear the error byte in the IOB. I could save two bytes by doing this above, just after line 1910.
Lines 2470-2530 pick up the caller's buffer address and store it in a pointer in page zero. I don't do any range checking on the buffer address, but then neither does RWTS.
Lines 2540-2550 set Y=0 to start the read or write loop, and then branch to the read loop if carry was set. Lines 2570-2610 comprise the write loop, and lines 2620-2660 the read loop.
Finally, line 2670 turns the language card back off. Then we clear carry status to indicate no errors, and return.
And that is how you make a ramdisk. If you have a bigger RAM card, it probably came with a ramdisk program. But if not, you ought to be able to see how to extend this program to handle larger amounts of memory.
1 .LIF 1000 *SAVE S.LITTLE RAM DISK 1010 *-------------------------------- 1020 .OR $4000 1030 .TF B.LITTLE RAM DISK 1040 *-------------------------------- 1050 RAMP .EQ $3C,3D 1060 BUFP .EQ $3E,3F 1070 IOB .EQ $48,49 1080 *-------------------------------- 1090 INSTALL 1100 LDY #0 COPY CODE TO PAGE 3 1110 .0 LDA RAMDISK.IMAG,Y 1120 STA RAMDISK.REAL,Y 1130 INY 1140 CPY #$D0 NOT PAST $3CF 1150 BCC .0 1160 *---INSTALL DOS HOOK------------- 1170 LDY #2 1180 .1 LDA $BD00,Y 1190 STA OLD.BD00,Y 1200 LDA NEW.BD00,Y 1210 STA $BD00,Y 1220 DEY 1230 BPL .1 1240 *---INIT VTOC & CATALOG---------- 1250 LDA $C083 1260 LDA $C083 1270 INY Y=0 1280 TYA 1290 .2 STA $D000,Y CLEAR VTOC 1300 STA $D100,Y CLEAR THREE CATALOG PAGES 1310 STA $D200,Y ...ROOM FOR 21 FILES 1320 STA $D300,Y 1330 INY 1340 BNE .2 1350 *---CATALOG CHAIN---------------- 1360 LDA #$11 SIMULATED TRACK 11 1370 STA $D201 1380 STA $D301 1390 INY Y=1 1400 STY $D202 POINT TO 3RD CATALOG SECTOR 1410 INY Y=2 1420 STY $D302 POINT TO 2ND CATALOG SECTOR 1430 *---FINISH THE VTOC-------------- 1440 LDY #0 USE TABLES FOR VTOC 1450 .3 LDX TBLX,Y INDEX INTO VTOC 1460 BEQ .4 ...FINISHED 1470 LDA TBLA,Y 1480 STA $D000,X 1490 INY 1500 BNE .3 ...ALWAYS 1510 *-------------------------------- 1520 .4 LDA $C082 BACK TO MOTHERBOARD ROM 1530 RTS 1540 *-------------------------------- 1550 TBLX .HS 01.02.27.34.35.37.3C.3D.40.41.44.45.48.49.00 1560 TBLA .HS 11.03.7A.23.10.01.FF.F0.FF.FF.FF.FF.FF.FF 1570 *-------------------------------- 1580 RAMDISK.IMAG .PH $300 1590 RAMDISK.REAL 1600 *-------------------------------- 1610 OLD.BD00 .BS 3 1620 NEW.BD00 JMP LITTLE.RAM.DISK 1630 *-------------------------------- 1640 LITTLE.RAM.DISK 1650 STY IOB 1660 STA IOB+1 1670 LDY #1 LOOK AT SLOT NUMBER 1680 LDA (IOB),Y 1690 CMP #$30 RAMDISK IN SLOT 3 1700 BEQ RAM.DISK.SELECTED 1710 LDY #2 1720 .1 LDA OLD.BD00,Y 1730 STA $BD00,Y 1740 DEY 1750 BPL .1 1760 LDY IOB 1770 LDA IOB+1 1780 JSR $BD00 1790 LDY #2 1800 .2 LDA NEW.BD00,Y 1810 STA $BD00,Y 1820 DEY 1830 BPL .2 1840 RTS 1850 *-------------------------------- 1860 RAM.DISK.SELECTED 1870 INY LOOK AT DRIVE 1880 LDA (IOB),Y 1890 EOR #1 MUST BE DRIVE 1 1900 BNE .99 ...NOT DRIVE 1, ERROR 1910 STA RAMP LO-BYTE OF RAMPAGE 1920 *-------------------------------- 1930 LDY #5 GET SECTOR # 1940 LDA (IOB),Y 1950 CMP #16 1960 BCS .99 BAD T/S 1970 STA RAMP+1 1980 DEY GET TRACK # 1990 LDA (IOB),Y 2000 BEQ .99 INVALID TRACK # 2010 CMP #$11 IS IT VTOC TRACK? 2020 BNE .2 NOT TRACK 17 2030 *---TRACK 17--------------------- 2040 LDA RAMP+1 GET SECTOR # 2050 CMP #4 MUST BE 0-3 2060 BCS .99 NOT VALID T/S 2070 .1 ORA #$D0 FORM HI-BYTE OF ADDRESS 2080 LDX #0 C083 BANK 2090 BEQ .4 ...ALWAYS 2100 *---TRACK 1-4-------------------- 2110 .2 CMP #5 OTHERWISE MUST BE TRACK 1-4 2120 BCS .99 NOT VALID T/S 2130 CMP #1 TRACK 1? 2140 BNE .3 ...NO 2150 LDA RAMP+1 GET SECTOR # 2160 CMP #4 MUST BE 4-F 2170 BCS .1 ...GOOD 2180 *---ERROR------------------------ 2190 .99 LDY #13 2200 LDA #$40 2210 STA (IOB),Y 2220 SEC 2230 RTS 2240 *-------------------------------- 2250 .3 ASL CHANGE 2,3,4 TO 20,30,40 2260 ASL 2270 ASL 2280 ASL 2290 ADC #$B0 ... TO D0,E0,F0 2300 ORA RAMP+1 MERGE SECTOR 2310 LDX #8 C08B BANK 2320 .4 STA RAMP+1 2330 *-------------------------------- 2340 LDY #12 LOOK AT OPCODE 2350 LDA (IOB),Y 2360 BEQ .99 ...NOT RD OR WRT 2370 CMP #3 IS IT RD OR WRT? 2380 BCS .99 ...NO, IGNORE 2390 LSR SET CARRY IF READ, CLR IF WRT 2400 *---SELECT RAMCARD BANK---------- 2410 LDA $C083,X 2420 LDA $C083,X 2430 *---CLEAR ERROR CODE------------- 2440 LDY #13 2450 LDA #0 2460 STA (IOB),Y 2470 *---GET BUFFER ADDRESS----------- 2480 LDY #8 2490 LDA (IOB),Y 2500 STA BUFP 2510 INY 2520 LDA (IOB),Y 2530 STA BUFP+1 2540 LDY #0 2550 BCS .6 ...READ 2560 *---WRITE A SECTOR--------------- 2570 .5 LDA (BUFP),Y 2580 STA (RAMP),Y 2590 INY 2600 BNE .5 2610 BEQ .7 ...ALWAYS 2620 *---READ A SECTOR---------------- 2630 .6 LDA (RAMP),Y 2640 STA (BUFP),Y 2650 INY 2660 BNE .6 2670 .7 LDA $C082 BACK TO MOTHERBOARD ROM 2680 CLC 2690 RTS 2700 *-------------------------------- 2710 .EP 2720 .LIF |
After three burglaries or attempts here at the office, and four at Bill's house, we have been looking into ways to make our Apples a little more secure. There are a variety of products available these days, some involving special furniture that locks up around computer, and others consisting of brackets that lock the equipment to the desk top. These solutions seem too expensive and limiting for our purposes. We are always shifting the computers and monitors around to install or remove cards or to connect or disconnect some accessory. And four computers here in the office and two more at our houses mean that the system had better be inexpensive.
Well we have found what looks to be the answer: the Kablit Security System, from Secure-It, Inc. This is 10 feet of 3/16" steel cable with a high-quality padlock-type lock and an assortment of special hardware to attach the cable to your computer, monitor, disk drives, printer, or whatever. The connectors attach using the normal case screws of your equipment, so in most cases there is no need to drill holes or otherwise tear things up. There are specific kits for the Apple //c and the Macintosh.
The list price of the Kablit Security System is $49.95; we will be offering them for $45 + shipping.
When using a hard disk with ProDOS it is often useful to use the MLI QUIT call to go from one application to another. However, if you are deep within a subdirectory the QUIT code makes you retype the entire Prefix if you want to shorten it. To allow the use of the right arrow during the QUIT call do the following:
UNLOCK PRODOS BLOAD PRODOS,A$2000,TSYS CALL-151 5764:75 (for ProDOS 1.1.1 -- use 5964 for 1.0.1) BSAVE PRODOS,A$2000,TSYS LOCK PRODOS
This changes the input call to $FD75 which allows right arrow input. There is one drawback: now to restore the prompted prefix you must press ESCape when asked for the Pathname of the next application.
The puzzle, published last month, was to write a program which would fill all RAM from $0000 through $BFFF with the same value. What value is your choice.
The listing of my solution follows. It executes at $9966, which is inside the middle DOS buffer. To get it there, you can BLOAD or BRUN it. A few seconds after the screen fill's up with "Y" characters, the program has completely filled RAM from $0000 through $BFFD with $99.
Lines 1080-1200 fill all the RAM not occupied by my program (addresses $0000-$98FF and $99C8-$BFFF) with $99. I first fill the RAM from $99C8 up, and then from $0000 up through $98FF. You have to forgive the self-modifying code in a puzzle solution like this.
Lines 1210-1280 store a NOP and a JMP $0000 at the end of RAM. Lines 1320-1350 store $99 into $9900-$9999. It's getting hot in here!
Lines 1390-1590 get executed more than once. The first time, they store $99 into $999B-$99A3, and $99A5. By this time every byte from $0000 through $99A5 is set $99. All those bytes can be executed as "STA $9999,Y" instructions, and the JMP $0000 we placed at the end of RAM will do just that. When we get back up to line 1430, at $99A9, we start moving Y again and store $99 into $99A6-99AC and $99AE. It progressively keeps covering itself up, and eventually it is all gone:
999B 999C 999D 99A6 999E 99A7 99AF 999F 99A8 99B0 99A0 99A9 99B1 99B7 99A1 99AA 99B2 99B8 99A2 99AB 99B3 99B9 99BD 99A3 99AC 99B4 99BA 99BE 99C1 ... 99A5 99A3 99B6 99BC 99C0 99C3 99C5 99C6
1000 *SAVE S.RAMFILL ADAM 1010 *-------------------------------- 1020 * ADAM LEVIN'S SOLUTION TO THE PUZZLE 1030 *-------------------------------- 1040 .OR $9966 MUST START HERE 1050 .TF B.PAINTER 1060 *-------------------------------- 1070 PAINTER 1080 LDY #END-1 1090 LDA #$99 STORE $99 FROM END OF PROGRAM 1100 COAT1 STA $9900,Y THROUGH $BFFF 1110 INY 1120 BNE COAT1 1130 INC COAT1+2 NEXT PAGE 1140 LDX COAT1+2 1150 CPX #$C0 REACHED $BFFF YET? 1160 BNE .2 ...NOT YET 1170 LDX #0 WRAP AROUND AND STORE FROM 1180 STX COAT1+2 $0000 THRU $HERE 1190 .2 CPX #$99 HAVE WE COME FULL CIRCLE? 1200 BNE COAT1 ...NO, KEEP PAINTING 1210 LDY #$EA ...YES, NOW PATCH END OF RAM 1220 STY $BFFB FOR WRAPPING AROUND 1230 STY $BFFC 1240 LDY #$4C NOP, JMP $0000 1250 STY $BFFD 1260 LDY #0 1270 STY $BFFE 1280 STY $BFFF 1290 *-------------------------------- 1300 * PAINT $9900-HERE 1310 *-------------------------------- 1320 COAT2 STA $9900,Y 1330 INY 1340 CPY #COAT2+2 1350 BCC COAT2 1360 *-------------------------------- 1370 * TRY TO GET OUT WITHOUT LEAVING FOOTPRINTS! 1380 *-------------------------------- 1390 LDY #2 SET INDEX TO POINT TO $999B 1400 STA $9999,Y 1410 INY $999C 1420 STA $9999,Y 1430 INY $999D 1440 STA $9999,Y 1450 INY $999E 1460 STA $9999,Y 1470 INY $999F 1480 STA $9999,Y 1490 INY $99A0 1500 STA $9999,Y 1510 INY $99A1 1520 STA $9999,Y 1530 INY $99A2 1540 STA $9999,Y 1550 INY $99A3 1560 STA $9999,Y 1570 INY $99A4 1580 INY $99A5 1590 END STA $9999,Y 1600 *-------------------------------- |
Bob S-C's solution
The program loads at $800, but actually executes at $100. Lines 1030-1080 move the filler program down to $100 and jump to it. This solution fills all of RAM from $0000-BFFF with $48, which is a "PHA" instruction.
To keep from running off the end of RAM into the I/O space, I took advantage of the fact that the keyboard register can be read at both $C000 and $C001. Lines 1140-1160 wait until you type a zero key ("0"). The ASCII code for "0" is $B0. Two $B0 values in a row at $C000 and $C001 will dis-assemble as a BCS to $BFB2. Hence my solution finishes with an infinite loop running from $BFB2 to $C001.
Lines 1170-1290 fill RAM from $200-$BFFF with $48's, which are "PHA" opcodes. Lines 1300-1330 do the same with page zero.
Line 1350 jumps to $200, which means that the PHA opcodes start being executed. Since the stack is only 256 bytes long, and since the stack pointer wraps around, by the time the PHA at $2FF has executed all of page 1 will have been filled with $48. Since carry is set, when execution reaches $C000 the processor will go into that infinite loop I mentioned above.
1000 *SAVE S.RAMFILL RBSC 1010 *-------------------------------- 1020 .OR $800 1030 MOVER LDY #LENGTH MOVE "FILLER" PROGRAM 1040 .1 LDA MY.FILLER,Y TO EXECUTION AREA 1050 STA FILLER,Y AT $100... 1060 DEY 1070 BPL .1 1080 JMP FILLER NOW START FILLING! 1090 *-------------------------------- 1100 * FOLLOWING CODE EXECUTES AT $100... 1110 *-------------------------------- 1120 MY.FILLER .PH $100 1130 FILLER 1140 .1 LDA $C000 WAIT UNTIL "0" TYPED 1150 CMP #$B0 ($B0 IS ALSO BCS OPCODE) 1160 BNE .1 1170 *---FILL $200-$BFFF-------------- 1180 LDY #0 1190 STY 0 1200 LDA #2 1210 STA 1 1220 LDA #$48 PHA OPCODE 1230 .2 STA (0),Y 1240 INY 1250 BNE .2 1260 INC 1 1270 LDX 1 1280 CPX #$C0 UNTIL $BFFF 1290 BCC .2 1300 *---FILL PAGE ZERO--------------- 1310 .3 STA 0,Y 1320 INY 1330 BNE .3 1340 *---FILL PAGE ONE---------------- 1350 JMP $200 1360 LENGTH .EQ *-FILLER 1370 .EP 1380 *-------------------------------- |
David Johnson's solution
My solution uses the power of the 65802. There was no restriction to the 6502 mentioned in the puzzle last month. All 49152 locations of motherboard RAM are filled with $DB, which happens to be the opcode value for the "STP" opcode. STP means "stop the processor", so once all RAM is filled it quits!
I use the MVP instruction to do the actual filling. The MVP instruction is located at $0000. I first put $DB into $BFFF. Then I set up the registers so that MVP will copy $BFFF into $BFFE, then $BFFE into $BFFD, and so on down to copying $0001 into $0000. By this time the MVP runs out, and the processor executes the STP opcode at $0003.
The 2nd and 3rd bytes of the MVP opcode specify which 64K memory banks to use; on a 65802 these don't do anything, because the bank addresses don't get out of the chip. On a 65816 my program won't work correctly, because the bank bytes will be changed at the end. First the Source bank address will be changed, so that a byte will be copied from $DB.0002 into $00.0001. Now the Destination Bank Address is changed, to we don't know what: we will finally copy $DB.0001 into $xx.0000. That last byte-move could be catastrophic (who knows, since we don't have any 65816-based systems yet?). Anyway, my program works fine in an Apple equipped with a 65802.
1000 *SAVE DAVID JOHNSON'S FILLER 1010 *-------------------------------- 1020 * SOLUTION TO PUZZLE BY DAVID C. JOHNSON 1030 *-------------------------------- 1040 .OP 65802 I got mine! 1050 *-------------------------------- 1060 .OR $00 1070 *-------------------------------- 1080 paint mvp 0,0 fill $BFFE-$0000 from $BFFF 1090 *-------------------------------- 1100 START LDA #$DB "STP" OPCODE 1110 STA $BFFF SEED FOR THE "MVP" INSTRUCTION 1120 CLC GET INTO NATIVE MODE 1130 XCE 1140 REP #$30 16-BIT REGISTERS 1150 LDX ##$BFFF Source Address = $BFFF 1160 TXY 1170 DEY Destination Address = $BFFE 1180 TYA # Bytes -1 to be "moved" 1190 BRA paint MVP must be at $0000 1200 *-------------------------------- |
Sometimes we want to do something special with the object code generated by the S-C Macro Assembler. Maybe write it directly into an EPROM programmer, send it out through a serial port, or store it into some special device. One such device is the Douglas Electronics Writable ROM Board, which appears to the Apple as 2K of RAM at $C800 but brings out a cable that plugs right into a 2716 EPROM socket. With this card we can test the assembled code instantly in the target machine, without the delay and hassle of programming and transferring an EPROM.
There are a couple of hitches along the way. The assembler normally protects everything above $BFFF from code storage, and we need some special code because we have to temporarily switch off any other card using $C800, switch on the WROM Board, write a byte, and switch the WROM Board off again.
Fortunately, Version 2.0 of the S-C Macro Assembler has some special features for cases just like this. There are parameters at the beginning of the assembler to unprotect a specified area of memory, and each byte generated is passed through an Object Vector on its way to storage, so we can intercept the byte and do our memory switching before passing it back to the assembler.
Since the object code is going to be stored in successive memory locations pointed to by the Target Address, we can just use the Macro Assembler's normal STORE.OBJECT.BYTE routine. The address of STORE.OBJECT.BYTE is in the JMP instruction at OBJECT.VECTOR, so it's easy to get that address, plug it into our code, and then install our address in OBJECT.VECTOR. If we needed to do something different with the object code, like storing each byte into the same hardware register, we would do that instead at the line labelled CALL.
Writable ROM Board, by Douglas Electronics, 718 Marina Blvd., San Leandro, CA 94577. (415) 483-8770. $95.
1000 *SAVE S.WROMWRITE 1010 *-------------------------------- 1020 LC .EQ 1 1 if $D000 assembler 1030 WROMSLOT .EQ 7 1040 *-------------------------------- 1050 .DO LC 1060 OBJECT.VECTOR .EQ $D012 1070 UNPROTECT.LOW .EQ $D024 1080 UNPROTECT.HIGH .EQ $D026 1090 .ELSE 1100 OBJECT.VECTOR .EQ $1012 1110 UNPROTECT.LOW .EQ $1024 1120 UNPROTECT.HIGH .EQ $1026 1130 .FIN 1140 1150 WRITECARD .EQ WROMSLOT*$10+$C080 1160 CARDOFF .EQ WROMSLOT*$10+$C081 1170 C800.OFF .EQ $CFFF 1180 1190 TARGET.LOW .EQ $C800 1200 TARGET.HIGH .EQ $CFFF 1210 *-------------------------------- 1220 .OR $300 1230 * .TF WROMWRITE 1240 INSTALL 1250 .DO LC 1260 BIT $C083 1270 BIT $C083 1280 .FIN 1290 LDA /TARGET.LOW 1300 STA UNPROTECT.LOW+1 1310 LDA #TARGET.LOW 1320 STA UNPROTECT.LOW 1330 LDA /TARGET.HIGH 1340 STA UNPROTECT.HIGH+1 1350 LDA #TARGET.HIGH 1360 STA UNPROTECT.HIGH 1370 LDA OBJECT.VECTOR+2 1380 STA CALL+2 1390 LDA OBJECT.VECTOR+1 1400 STA CALL+1 1410 LDA /CARDON 1420 STA OBJECT.VECTOR+2 1430 LDA #CARDON 1440 STA OBJECT.VECTOR+1 1450 .DO LC 1460 BIT $C080 1470 .FIN 1480 RTS 1490 *-------------------------------- 1500 CARDON BIT C800.OFF 1510 BIT WRITECARD 1520 CALL JSR $FFFF 1530 BIT CARDOFF 1540 RTS |
We still have a small supply of the original release of this highly-praised development tool for the Macintosh. (Even Jerry Pournelle had good words for it.) I say original edition, because they are now at version 1.2, with 1.3 scheduled in January.
Mainstay has told us that there is little real difference in the various versions, not enough to influence your decision as to where to buy. And they also have a policy that they will provide your first upgrade absolutely free. All you need to do is fill in your registration card, make a backup copy of MacASM to use in the interim, and send them your original MacASM disk.
Their current end-user price is $125. Note that ours are still being sold at the intorductory price of $100. Wow! It's a steal!
After reading Mark Jackson's article on improving the ProDOS QUIT code, I though it would be nice to have a commented listing of that program. The listing which follows is just that.
The ProDOS QUIT code is booted into $D100-D3FF in the alternate $D000 bank (the one you get by diddling $C083). Normally ProDOS MLI stays in the $C08B side. When a program issues the QUIT call (MLI code $65), the contents of $D100-D3FF are copied to $1000-12FF; then ProDOS jumps to $1000.
If you BLOAD the SYS file named PRODOS from a bootable ProDOS 1.1.1 disk, and examine it, you will find that it is laid out in eight parts. The first part is a relocator, which copies the other seven parts into their normal homes. Like this:
Position Position as loaded copied to --------------------------------------------------- 2000-29FF --- Relocator 2A00-2BFF Aux 200-3FF /RAM/ driver 2C00-2C7F FF00-FF7F /RAM/ driver 2C80-2CFF nowhere All zeroes 2D00-4DFF D000-F0FF MLI Kernel 4E00-4EFF BF00-BFFF System Global Page 4F00-4F7F D742-D7BD Thunderclock driver 4F80-4FFF FF80-FFFF Interrupt Code 5000-56FF F800-FEFF Device Drivers 5700-59FF D100-D3FF(alt) QUIT Code zeroes F100-F7FF
The part I am interested in right now is the QUIT code, which is at $5700-$59FF in the PRODOS file.
The QUIT code is not written very efficiently. For some reason, there are two completely separate editing programs: one for the prefix, and another for the pathname. (And as Mark points out, neither one is very handy.) Even the code that initializes the BITMAP is inefficient.
1000 *SAVE S.PRODOS.QUIT 1010 *-------------------------------- 1020 CH .EQ $24 1030 CV .EQ $25 1040 ERRCOD .EQ $DE 1050 *-------------------------------- 1060 BUF .EQ $0280 1070 *-------------------------------- 1080 SYSTEM .EQ $2000 1090 *-------------------------------- 1100 MLI .EQ $BF00 1110 BITMAP .EQ $BF58 1120 *-------------------------------- 1130 KEY .EQ $C000 1140 S80STOREOFF .EQ $C000 1150 S80OFF .EQ $C00C 1160 SALTON .EQ $C00F 1170 STROBE .EQ $C010 1180 ROM .EQ $C082 1190 *-------------------------------- 1200 HOME .EQ $FC58 1210 CLREOL .EQ $FC9C 1220 RDKEY .EQ $FD0C 1230 CROUT .EQ $FD8E 1240 COUT .EQ $FDED 1250 SETKBD .EQ $FE89 1260 SETVID .EQ $FE93 1270 BELL .EQ $FF3A 1280 *-------------------------------- 1290 .MA MLI 1300 JSR MLI 1310 .DA #$]1,]2 1320 .EM 1330 *-------------------------------- 1340 .OR $1000 1350 .TA $5700 1360 *-------------------------------- 1370 PRODOS.QUIT 1380 LDA ROM TURN ON THE MONITOR ROM 1390 JSR SETVID GET BACK TO GOOD OLD-FASHIONED 1400 JSR SETKBD DOWN-HOME 40 COLUMN DISPLAY 1410 STA S80OFF 1420 STA SALTON Know what I mean, Vern? 1430 STA S80STOREOFF 1440 *---PREPARE BITMAP--------------- 1450 LDX #$17 1460 LDA #1 Mark $BFxx in use 1470 STA BITMAP,X 1480 DEX 1490 LDA #0 Most pages are free 1500 .1 STA BITMAP,X 1510 DEX 1520 BPL .1 1530 LDA #$CF $0000-01FF, $0400-07FF in use 1540 STA BITMAP 1550 *---DISPLAY PREFIX--------------- 1560 GET.PREFIX 1570 JSR HOME 1580 JSR CROUT 1590 LDA #Q.PRFX 1600 STA MSG.ADDR 1610 LDA /Q.PRFX 1620 STA MSG.ADDR+1 1630 JSR PRINT.MESSAGE 1640 LDA #3 VTAB 4 1650 STA CV 1660 JSR CROUT MAKE IT 5 1670 >MLI C7,PREFIX.PARM 1680 LDX BUF # CHARS IN PREFIX 1690 LDA #0 MARK END OF PREFIX WITH 00 1700 STA BUF+1,X SO OUR MESSAGE PRINTER WILL 1710 LDA #BUF+1 PRINT IT. 1720 STA MSG.ADDR 1730 LDA /BUF+1 1740 STA MSG.ADDR+1 1750 JSR PRINT.MESSAGE 1760 *---GET NEW PREFIX--------------- 1770 LDX #0 1780 DEC CV MOVE CURSOR TO BEGINNING OF LINE 1790 JSR CROUT 1800 NEXT.PREFIX.CHAR 1810 JSR RDKEY 1820 CMP #$8D 1830 BEQ SET.NEW.PREFIX ...ACCEPT WHAT IS ON SCREEN 1840 PHA ERASE PREFIX FROM SCREEN 1850 JSR CLREOL 1860 PLA 1870 CMP #$9B IS CHAR <ESCAPE>? 1880 BEQ GET.PREFIX ...YES, START ALL OVER 1890 CMP #$98 IS CHAR CTRL-X? 1900 START.PREFIX.OVER 1910 BEQ GET.PREFIX ...START ALL OVER 1920 CMP #$89 IS CHAR <TAB>? 1930 BEQ .3 ...YES, RING BELL 1940 CMP #$88 IS CHAR BACKSPACE? 1950 BNE .2 ...NO, APPEND TO LINE 1960 CPX #0 ...BACKSPACE, UNLESS AT BEGINNING 1970 BEQ .1 AT BEGINNING ALREADY 1980 DEC CH BACK UP 1990 DEX 2000 .1 JSR CLREOL CHOP OFF AFTER CURSOR 2010 JMP NEXT.PREFIX.CHAR 2020 .2 BCS .4 OTHER CONTROL CHAR < $88 2030 .3 JSR BELL 2040 JMP NEXT.PREFIX.CHAR 2050 .4 CMP #"Z"+1 2060 BCC .5 ...NOT LOWER CASE 2070 AND #$DF CONVERT LOWER CASE TO UPPER 2080 .5 CMP #"." ALLOW PERIOD, SLASH, DIGITS 2090 BCC .3 ...TOO SMALL 2100 CMP #"Z"+1 ALLOW LETTERS 2110 BCS .3 ...TOO LARGE 2120 CMP #"9"+1 2130 BCC .6 ...PERIOD, SLASH, OR DIGIT 2140 CMP #"A" 2150 BCC .3 ...NOT A LEGAL CHARACTER 2160 .6 INX 2170 CPX #$27 2180 BCS START.PREFIX.OVER ...TOO LONG 2190 STA BUF,X 2200 JSR COUT ECHO THE CHARACTER 2210 JMP NEXT.PREFIX.CHAR 2220 *-------------------------------- 2230 SET.NEW.PREFIX 2240 CPX #0 DID WE CHANGE IT? 2250 BEQ GET.PATHNAME ...NO 2260 STX BUF ...YES, SO TELL SYSTEM 2270 >MLI C6,PREFIX.PARM 2280 BCC GET.PATHNAME ...NO ERRORS 2290 JSR BELL DING, DONG! 2300 LDA #0 SET .EQ. STATUS 2310 PFXOVR BEQ START.PREFIX.OVER ...ALWAYS 2320 *-------------------------------- 2330 GET.PATHNAME 2340 JSR HOME 2350 START.PATHNAME.OVER 2360 JSR CROUT 2370 LDA #Q.PATH 2380 STA MSG.ADDR 2390 LDA /Q.PATH 2400 STA MSG.ADDR+1 2410 JSR PRINT.MESSAGE 2420 LDA #3 VTAB 4 2430 STA CV 2440 JSR CROUT MAKE IT 5 2450 LDX #0 2460 NEXT.PATHNAME.CHAR 2470 LDA #$FF CURSOR CHARACTER 2480 JSR COUT 2490 DEC CH BACK UP OVER CURSOR 2500 .1 LDA KEY 2510 BPL .1 ...WAIT TILL KEY PRESSED 2520 STA STROBE 2530 CMP #$9B <ESCAPE>? 2540 BNE .2 ...NO 2550 LDA CH IF AT BEGINNING, GET PREFIX OVER 2560 BNE GET.PATHNAME ...ELSE GET PATHNAME OVER 2570 BEQ PFXOVR 2580 .2 CMP #$98 CONTROL-X? 2590 .3 BEQ GET.PATHNAME 2600 CMP #$89 TAB KEY? 2610 BEQ .5 ...YES 2620 CMP #$88 BACKSPACE? 2630 BNE .4 ...NO 2640 JMP BACKSPACE.IN.PATHNAME 2650 *-------------------------------- 2660 .4 BCS .6 2670 .5 JSR BELL ...INVALID CHAR, RING BELL 2680 JMP NEXT.PATHNAME.CHAR 2690 *-------------------------------- 2700 .6 CMP #$8D 2710 BEQ SET.NEW.PATHNAME 2720 CMP #"Z"+1 2730 BCC .7 2740 AND #$DF CHANGE LOWER CASE TO UPPER 2750 .7 CMP #"." ACCEPT DOT, SLASH, OR DIGIT 2760 BCC .5 ...TOO SMALL 2770 CMP #"Z"+1 ACCEPT LETTERS 2780 BCS .5 ...TOO LARGE 2790 CMP #"9"+1 2800 BCC .8 ...DOT, SLASH, OR DIGIT 2810 CMP #"A" 2820 BCC .5 ...NOT A VALID CHARACTER 2830 .8 PHA CLEAR BEYOND THIS POINT 2840 JSR CLREOL 2850 PLA 2860 JSR COUT ECHO THE NEW CHARACTER 2870 INX 2880 CPX #$27 2890 BCS .3 ...NAME TOO LONG 2900 STA BUF,X APPEND CHAR TO NAME 2910 JMP NEXT.PATHNAME.CHAR 2920 *-------------------------------- 2930 SET.NEW.PATHNAME 2940 LDA #" " 2950 JSR COUT 2960 STX BUF 2970 >MLI C4,FILE.INFO.PARM 2980 BCC .1 ...NO ERRORS 2990 JMP PROCESS.ERROR 3000 *-------------------------------- 3010 .1 LDA FILTYP FILE.INFO.PARM+4 3020 CMP #$FF 3030 BEQ .2 "SYS" FILE 3040 LDA #1 3050 JMP PROCESS.ERROR 3060 *-------------------------------- 3070 .2 LDA #0 3080 STA CL.REF CLOSE.PARM+1, REF NO. 3090 >MLI CC,CLOSE.PARM 3100 BCC .3 ...NO ERROR 3110 JMP PROCESS.ERROR 3120 *-------------------------------- 3130 .3 LDA ACBITS FILE.INFO.PARM+3 3140 AND #1 3150 BNE .4 ...OKAY TO READ IT 3160 LDA #$27 3170 JMP PROCESS.ERROR 3180 *-------------------------------- 3190 .4 >MLI C8,OPEN.PARM 3200 BCC .5 ...NO ERRORS 3210 JMP PROCESS.ERROR 3220 *-------------------------------- 3230 .5 LDA OP.REF OPEN.PARM+5, REF NO. 3240 STA RD.REF READ.PARM+1, REF NO. 3250 STA EF.REF EOF.PARM+1, REF NO. 3260 >MLI D1,EOF.PARM 3270 BCC .6 ...NO ERRORS 3280 JMP PROCESS.ERROR 3290 *-------------------------------- 3300 .6 LDA FIL.SZ+2 EOF.PARM+4 3310 BEQ .7 ...NOT TOO LONG 3320 LDA #$27 3330 JMP PROCESS.ERROR 3340 *-------------------------------- 3350 .7 LDA FIL.SZ EOF.PARM+2 3360 STA READ.PARM+4 3370 LDA FIL.SZ+1 EOF.PARM+3 3380 STA READ.PARM+5 3390 >MLI CA,READ.PARM 3400 PHP 3410 >MLI CC,CLOSE.PARM 3420 BCC .9 3430 PLP 3440 .8 JMP PROCESS.ERROR 3450 *-------------------------------- 3460 .9 PLP 3470 BCS .8 3480 JMP SYSTEM 3490 *-------------------------------- 3500 BACKSPACE.IN.PATHNAME 3510 LDA CH UNLESS ALREADY AT BEGINNING 3520 BEQ .1 ...WE WERE 3530 DEX 3540 LDA #" " 3550 JSR COUT 3560 DEC CH 3570 DEC CH 3580 JSR COUT 3590 DEC CH 3600 .1 JMP NEXT.PATHNAME.CHAR 3610 *-------------------------------- 3620 PRINT.MESSAGE 3630 LDX #0 3640 MSG.LP LDA MSG.LP,X 3650 MSG.ADDR .EQ *-2 3660 BEQ .1 3670 ORA #$80 3680 JSR COUT 3690 INX 3700 BNE MSG.LP 3710 .1 RTS 3720 *-------------------------------- 3730 PROCESS.ERROR 3740 STA ERRCOD 3750 LDA #12 VTAB 13 3760 STA CV 3770 JSR CROUT MAKE IT 14 3780 LDA ERRCOD 3790 CMP #1 3800 BNE .1 3810 LDA #ERQT.1 3820 STA MSG.ADDR 3830 LDA /ERQT.1 3840 STA MSG.ADDR+1 3850 BNE .3 3860 .1 CMP #$40 3870 BEQ .2 3880 CMP #$44 3890 BEQ .2 3900 CMP #$45 3910 BEQ .2 3920 CMP #$46 3930 BEQ .2 3940 LDA #ERQT.2 3950 STA MSG.ADDR 3960 LDA /ERQT.2 3970 STA MSG.ADDR+1 3980 BNE .3 ...ALWAYS 3990 .2 LDA #ERQT.3 4000 STA MSG.ADDR 4010 LDA /ERQT.3 4020 STA MSG.ADDR+1 4030 .3 JSR PRINT.MESSAGE 4040 LDA #0 VTAB 1 4050 STA CV 4060 JMP START.PATHNAME.OVER 4070 *-------------------------------- 4080 Q.PRFX .AS -/ENTER PREFIX (PRESS "RETURN" TO ACCEPT)/ 4090 .HS 00 4100 Q.PATH .AS -/ENTER PATHNAME OF NEXT APPLICATION/ 4110 .HS 00 4120 ERQT.1 .HS 87 4130 .AS -/NOT A TYPE "SYS" FILE/ 4140 .HS 00 4150 ERQT.2 .HS 87 4160 .AS -"I/O ERROR " 4170 .HS 00 4180 ERQT.3 .HS 87 4190 .AS -"FILE/PATH NOT FOUND " 4200 .HS 00 4210 *-------------------------------- 4220 FILE.INFO.PARM 4230 .DA #10 4240 .DA BUF 4250 ACBITS .HS 00 4260 FILTYP .HS 00 4270 .BS 13 4280 *-------------------------------- 4290 OPEN.PARM 4300 .DA #3 4310 .DA BUF 4320 .DA $1800 BUFFER ADDR 4330 OP.REF .BS 1 REF NO. 4340 *-------------------------------- 4350 CLOSE.PARM 4360 .DA #1 4370 CL.REF .BS 1 REF NO. 4380 *-------------------------------- 4390 READ.PARM 4400 .DA #4 4410 RD.REF .BS 1 REF NO. 4420 .DA $2000 BUFFER ADDR 4430 .BS 2 # BYTES TO READ 4440 .BS 2 # ACTUALLY READ 4450 *-------------------------------- 4460 EOF.PARM 4470 .DA #2 4480 EF.REF .BS 1 REF NO. 4490 FIL.SZ .BS 3 EOF POSITION 4500 *-------------------------------- 4510 PREFIX.PARM 4520 .DA #1 4530 .DA BUF 4540 *-------------------------------- 4550 .LIF |
One of the advantages of assembly language is that data can be manipulated easily at the bit and byte level. This leads to efficiencies in both speed and memory usage which cannot be matched with most higher-level languages.
We can pack more than one data item into the same byte. For example, I may use the first three bits of a byte to indicate which of eight colors to use, and the other five bits to indicated position on a 32-pixel line. There are endless examples. Since we need to be able to store into and retrieve from bit-fields within bytes, all of the microprocessors include opcodes which make it possible.
To merge two values together which already are "clean", we simply use the ORA opcode. For example, if I have data for field A in VAL.A as xxx00000 and data for field B in VAL.B as 000xxxxx, I merge them like this:
LDA VAL.A ORA VAL.B
By "clean" I mean that all the bits in VAL.A and VAL.B which are not part of the field values are already zero. If they are not, then we must first strip out those bits with the AND opcode:
LDA VAL.A AND #$E0 STA TEMP LDA VAL.B AND #$1F ORA TEMP
There is another way, which is shorter and faster and does not need TEMP. However, it is harder to figure out why it works.
LDA VAL.A EOR VAL.B AND #$1F EOR VAL.A
Can you explain it? I was so unsure of myself when I first ran into this technique that I devised a test program. My test tries all 256 values of VAL.A and VAL.B, with all possible contiguous fields from 1 bit for VAL.A to 7 bits for VAL.A. Probably overkill, but it runs in a few seconds.
My program prints out the two field masks for each of the seven field sizes, so that I can tell it is running. If the two methods for merging get the same results, that is the only output. If they do not, indicating that one method or the other does not work, I print out more data.
While I was writing the program I tried several variations, such as printing all the results whether they agreed or not. In order to be able to look at that volume of output reasonably, I added a PAUSE subroutine which enabled me to stop the output by tapping any key, restart it the same way, and abort by tapping the RETURN key.
The code for the first merging method is in lines 1310-1380; that for the second at lines 1400-1450.
The test was conclusive. I tried every possible combination, and both methods always give the same results. Looking back, I can see that the whole test was unnecessary; the second method will OBVIOUSLY produce the same results. Now I see it. Do you?
1000 *SAVE MERGE FIELDS IN A BYTE 1010 *-------------------------------- 1020 CROUT .EQ $FD8E 1030 PRBYTE .EQ $FDDA 1040 COUT .EQ $FDED 1050 *-------------------------------- 1060 FIELD.A .EQ $00 1070 FIELD.B .EQ $01 1080 VAL.A .EQ $02 1090 VAL.B .EQ $03 1100 MERGE.1 .EQ $04 1110 MERGE.2 .EQ $05 1120 *-------------------------------- 1130 T 1140 *---FOR FIELD= 80,7F TO 7F,80---- 1150 LDA #$7F DEFINE FIELDS AS 1,7 1160 STA FIELD.B 1170 LDA #$80 1180 STA FIELD.A 1190 *---FOR A=0 TO MAX VAL----------- 1200 .1 LDA #0 1210 STA VAL.A 1220 JSR CROUT 1230 LDA FIELD.A 1240 JSR PRBYTESP 1250 LDA FIELD.B 1260 JSR PRBYTE 1270 *---FOR B=0 TO MAX VAL----------- 1280 .2 LDA #0 1290 STA VAL.B 1300 1310 *---MERGE FIRST METHOD----------- 1320 .3 LDA VAL.A 1330 AND FIELD.A 1340 STA MERGE.1 1350 LDA VAL.B 1360 AND FIELD.B 1370 ORA MERGE.1 1380 STA MERGE.1 1390 1400 *---MERGE SECOND METHOD---------- 1410 LDA VAL.A 1420 EOR VAL.B 1430 AND FIELD.B 1440 EOR VAL.A 1450 STA MERGE.2 1460 1470 *---PRINT RESULTS, IF NOT EQUAL-- 1480 CMP MERGE.1 1490 BEQ .4 1500 JSR CROUT 1510 LDA FIELD.A 1520 JSR PRBYTESP 1530 LDA VAL.A 1540 JSR PRBYTESP 1550 LDA VAL.B 1560 JSR PRBYTESP 1570 LDA MERGE.1 1580 JSR PRBYTESP 1590 LDA MERGE.2 1600 JSR PRBYTE 1610 JSR PAUSE 1620 *---NEXT B----------------------- 1630 .4 INC VAL.B 1640 BNE .3 1650 *---NEXT A----------------------- 1660 INC VAL.A 1670 BNE .2 1680 *---NEXT FIELD------------------- 1690 SEC 1700 ROR FIELD.A 1710 LSR FIELD.B 1720 BNE .1 CONTINUE 1730 RTS FINISHED 1740 *-------------------------------- 1750 PRBYTESP 1760 JSR PRBYTE 1770 LDA #$A0 1780 JMP COUT 1790 *-------------------------------- 1800 PAUSE LDA $C000 1810 BPL .3 1820 STA $C010 1830 CMP #$8D 1840 BNE .2 1850 .1 PLA 1860 PLA 1870 RTS 1880 .2 LDA $C000 1890 BPL .2 1900 STA $C010 1910 CMP #$8D 1920 BEQ .1 1930 .3 RTS 1940 *-------------------------------- |
William O'Ryan's method (October 1985 AAL) of modifying old Apples to accept 65C02s looks like a very reliable fix. I notice no negative consequences in RAM or video timing. I do however recommend switching to 150 nanosecond motherboard RAM.
Apple motherboard RAM read access is CAS' limited, meaning TCAC (delay from CAS' falling to read data valid) is the critical RAM chip specification. In an Apple with O'Ryan's fix, RAM chips have 140 nsec minus 74LS139 pin 1 to pins 4,5,6 high/low propagation delay to get RAM read data valid after CAS' falls at the RAM chips. This means TCAC needs to be 119 nsec or less with a typical LS139. TCAC specifications are 100 nsec for 150 nsec RAM and 135 nsec for 200 nsec RAM, so 150 nsec or faster chips should be installed to be within RAM chip specifications with O'Ryan's fix.
A given Apple II may work with O'Ryan's fix and 200 nsec RAM chips, but operation may not be reliable over a wide range of room temperatures. Again I say, O'Ryan's fix calls for 150 nsec RAM chips. To operate with slower chips is asking for trouble.
Incidentally, 16K RAM chips don't cost as much as they used to. The cheapest 150 nsec 16K RAM chips I can find in my current mail order catalogs are 45 cents apiece at Jameco Electronics, 1355 Shoreway Rd., Belmont, CA 94002. [Slower ones were $65.00 apiece in 1978!]
As an alternative to replacing slow motherboard RAM chips, one can replace the 74LS139 at F2 with a 74S139. This changes the TCAC requirement with O'Ryan's fix to 133 nsec for a typical S139, and to 130 nsec for a worst case S139. These are barely less than the 135 nsec specification of 200 nsec RAM, so operation with 200 nsec RAM is probably reliable.
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 $14 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.)