Apple Assembly Line
Volume 6 -- Issue 2 November 1985

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.

Little DOS RAM Disk in Language Card Bob Sander-Cederlof

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
  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
  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
  1560 TBLA   .HS 11.03.7A.23.10.01.FF.F0.FF.FF.FF.FF.FF.FF
  1570 *--------------------------------
  1580 RAMDISK.IMAG .PH $300
  1600 *--------------------------------
  1610 OLD.BD00   .BS 3
  1630 *--------------------------------
  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
  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 *--------------------------------
  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

Kablit Security System Bob Sander-Cederlof

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.

An Easier QUIT from ProDOS Mark Jackson
Chicago, IL

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:

5764:75      (for ProDOS 1.1.1 -- use 5964 for 1.0.1)

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.

Solutions to Adam Levin's Painting Puzzle Adam Levin, et al

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:

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

  1010 *--------------------------------
  1020        .OR $800
  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
  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.

  1010 *--------------------------------
  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
  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 *--------------------------------

Using the Object Vector in S-C Macro Assembler Bill Morgan

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.

  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
  1090      .ELSE
  1100 OBJECT.VECTOR  .EQ $1012
  1110 UNPROTECT.LOW  .EQ $1024
  1120 UNPROTECT.HIGH .EQ $1026
  1130      .FIN
  1150 WRITECARD   .EQ WROMSLOT*$10+$C080
  1160 CARDOFF     .EQ WROMSLOT*$10+$C081
  1170 C800.OFF    .EQ $CFFF
  1190 TARGET.LOW  .EQ $C800
  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

Note on Mainstay MACASM for the Macintosh Bob Sander-Cederlof

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!

Commented Listing of ProDOS QUIT Code Bob Sander-Cederlof

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.

  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 *--------------------------------
  1380        LDA ROM      TURN ON THE MONITOR ROM
  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---------------
  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
  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
  1790        JSR CROUT
  1810        JSR RDKEY
  1820        CMP #$8D
  1840        PHA          ERASE PREFIX FROM SCREEN
  1850        JSR CLREOL
  1860        PLA
  1870        CMP #$9B     IS CHAR <ESCAPE>?
  1890        CMP #$98     IS CHAR CTRL-X?
  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
  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
  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
  2190        STA BUF,X
  2210        JMP NEXT.PREFIX.CHAR
  2220 *--------------------------------
  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
  2320 *--------------------------------
  2340        JSR HOME
  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
  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
  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
  2650 *--------------------------------
  2660 .4     BCS .6
  2670 .5     JSR BELL     ...INVALID CHAR, RING BELL
  2690 *--------------------------------
  2700 .6     CMP #$8D
  2710        BEQ SET.NEW.PATHNAME
  2720        CMP #"Z"+1
  2730        BCC .7
  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
  2870        INX
  2880        CPX #$27
  2890        BCS .3       ...NAME TOO LONG
  2900        STA BUF,X    APPEND CHAR TO NAME
  2920 *--------------------------------
  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 *--------------------------------
  3520        BEQ .1       ...WE WERE
  3530        DEX
  3540        LDA #" "
  3550        JSR COUT
  3560        DEC CH  
  3570        DEC CH  
  3580        JSR COUT
  3590        DEC CH  
  3610 *--------------------------------
  3630        LDX #0
  3650 MSG.ADDR   .EQ *-2
  3660        BEQ .1
  3670        ORA #$80
  3680        JSR COUT
  3690        INX
  3700        BNE MSG.LP
  3710 .1     RTS
  3720 *--------------------------------
  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  
  4070 *--------------------------------
  4090        .HS 00
  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 *--------------------------------
  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 *--------------------------------
  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 *--------------------------------
  4520        .DA #1
  4530        .DA BUF
  4540 *--------------------------------
  4550        .LIF

Two Ways to Merge Fields in a Byte Bob Sander-Cederlof

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?

  1010 *--------------------------------
  1020 CROUT  .EQ $FD8E
  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
  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
  1400 *---MERGE SECOND METHOD----------
  1410        LDA VAL.A
  1420        EOR VAL.B
  1430        AND FIELD.B
  1440        EOR VAL.A
  1450        STA MERGE.2
  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 *--------------------------------
  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 *--------------------------------

Comments on O'Ryan's 65C02 Mod for Apple II Jim Sather

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