9

Despite owning several Apple IIe's, I've never gotten around to doing any major programming on them. I hope to change that.

Anyway, how did programmers actually use the expanded RAM seeing that a 6502 can only address 16 bits of address space.

I know there are things called soft switches but how did they work? For example, how would you store data into the upper RAM?

Finally, did programmers typically execute code from the expanded RAM or did they mostly use it for data storage?

3 Answers 3

6

I hate to give only a link but the brevity of the Apple IIe tech reference on this is hard to top. See page 12, table 4-6 and, specific for the auxiliary memory, page 17, table 4-7 of that PDF.

The function is simple. You read a data byte from a specific I/O location ($C000..$CFFF range), the circuit detects this and flips the soft switch, changing the chip select signals of the RAM chips until you flip it again by reading another I/O location. The value read has no purpose, it's only the address on the bus which selects a function.

Note: The following point is disputed, but I leave it for the author to correct.

Also note the 6502 is special as it cannot write a byte without reading it first. So writing a specific location will also work but gives a double trigger. It doesn't matter the way the Apple II is designed, so this is only a minor detail.

15
  • 2
    The write always doing a double access is a myth. Various 6502 instructions do between 0 and 4 strobes, but e.g. STA ABS does one. Nov 27, 2016 at 13:33
  • 3
    I think the myth originated from the fact that the addressing mode used by BASIC's "poke" command does strobes. On the other hand, I'm puzzled by the persistence of the myth that "poke -16336,0" would produce two clicks, since flipping the speaker and then flipping it back 1us later would not produce any clicks by any normal definition I'm aware of.
    – supercat
    Nov 27, 2016 at 22:08
  • @supercat Indeed. The original Apple II manual from 1979 seems to have started this myth, though most later manuals are better because they explicitly mention indexed store operations. But the 65C02 fixed that for STA (zp),Y which is used by POKE. Nov 29, 2016 at 3:24
  • @NickWestgate: I find the decision to change the behavior of indexed stores on the 650x curious, since some hardware designs may have relied upon the read-then-write behavior (e.g. to generate an early chip-select signal), and in some cases the out-of-sequence fetch of the next instruction could have cause problems of its own. More interesting, though, was the decision to add a cycle to a couple of opcodes that could most likely have done just fine without them (ADC/SBC when D flag is set, and JMP (ind) which could have loaded the program counter first with the supplied address and...
    – supercat
    Nov 29, 2016 at 4:15
  • ...then pretended it was processing an ordinary JMP instruction [fetching the two bytes of the new address from PC and PC+1].
    – supercat
    Nov 29, 2016 at 4:17
7

You need to be very careful programming for this because switching read mode between main and aux banks affects everything the Apple II is doing, not just reading data for your program but also reading memory for running your program as well as accessing the BASIC ROM and the Monitor ROM.

Fortunately Apple provided a solution for this using what's called AUXMOVE in the tech reference listed in Janka's answer. This can copy data from main to aux, aux to main, without itself crashing.

There is also XFER, which transfers control of running programs between main and aux memory.

Running code in other memory banks is all the more complex because the zero page and stack areas are handled differently. Code running in aux memory can use the main ZP/stack or the aux ZP/stack, and this can crash programs due to things becoming inconsistent between them.

If you decide to use the alternate ZP/stack for your code running in aux banks, you need to remember to switch back to the main ZP/stack before transferring control back to code / ROMs in the main bank, or they will likely crash.

 

Also this question is more complex if you are talking about systems with more than 128k as there needs to be a way to choose which of the many different banks you want to access. One example is the Applied Engineering RamWorks. These other memory expansion systems will each have their own special way to access their additional memory.

Applied Engineering RamWorks III advertisement: http://apple2online.com/web_documents/AE%20RAMworks%20III%20Brochure.pdf

It is potentially possible to try to detect what hardware is installed, what amount is available, and then use the appropriate memory access methods to match the installed hardware.

All this is made much simpler on the IIgs which provides a way to access more than 64k directly, with extended 24-bit addressing opcodes.

 

A simple example of the problems:

  • Boot Apple IIe, IIc, IIgs
  • CALL -151
  • 300:8D 00 C0 8D 03 C0 8D 05 C0 8D 02 C0 8D 04 C0 60
  • 300G
  • Crash!

 

 300: 8D 00 C0   STA $C000   ; Make sure 80STORE is off
 303: 8D 03 C0   STA $C003   ; Read from AUX
 306: 8D 05 C0   STA $C005   ; Write to AUX
 309: 8D 02 C0   STA $C002   ; Read from MAIN
 30C: 8D 04 C0   STA $C004   ; Write to MAIN
 30F: 60         RTS

This crashes immediately after writing to $C003 to enable reading from the AUX bank, because the program counter is now pointing at $306 in the AUX bank, it tries to read the next line of program code from there, which contains nothing (00 00....) and the program crashes. (The bank modes get reset to normal by the Monitor when the error line prints.)

 

A really dumb and inefficient programming workaround that I discovered is to simply duplicate your code in the same memory locations in both the main and aux banks. So now you can freely toggle back and forth between reading or writing to the main and aux banks and your program won't crash.

Same code as before, but using AUXMOVE to copy $300 to $30F to the AUX bank before it runs:

  • Boot Apple IIe
  • CALL -151
  • 300:8D 00 C0 8D 03 C0 8D 05 C0 8D 02 C0 8D 04 C0 60
  • 310:A9 00 85 3C 85 42 A9 10 85 3E A9 03 85 3D 85 43 85 3F 38 20 11 C3 60
  • 310G
  • 300G
  • Works! Exits with no errors.

 

310: A9 00      LDA #$00
312: 85 3C      STA $3C
314: 85 42      STA $42
316: A9 10      LDA #$10
318: 85 3E      STA $3E
31A: A9 03      LDA #$03
31C: 85 3D      STA $3D
31E: 85 43      STA $43
320: 85 3F      STA $3F
322: 38         SEC
323: 20 11 C3   JSR $C311
326: 60         RTS

I'm sure there are much more elegant ways to do this, but if you are just starting out, and just want to get the job done, without being a super-brain expert at it, and saving bytes and all that, this does work.

(The "elegant" solution involves storing your program code above $CFFF because $200-BFFF is the region affected by $C003/$C005 $C002/$C004, but then you must deal with swapping out either the BASIC ROM or the Monitor ROM. Ugh.)

Just make sure that if you use this hack and you are hand-coding in the monitor, that you run AUXMOVE before running your code, or you will have two different programs that are interleaved together across the two memory banks, whenever it toggles back and forth between reading the MAIN or AUX bank.

Self modifying counting code is tricky due to swapping back and forth between reading and writing in MAIN or AUX. It would be better to use the zero page for counters, as it is not swapped out by $C003/$C005. Using memory above $CFFF for your program avoids these problems.

Also, before your program code exits, be sure to write to $C002 and $C004 to go back to read/write from the Main bank.

 

For completeness, I believe this is how this is done using the RAM located at $D000-FFFF to store you main/aux swapping code without it itself getting screwed up.

300: AD 89 CA    LDA $C089 ; Double read: Read ROM, write RAM
303: AD 89 CA    LDA $C089 ; $D000-FFFF, use bank 1 $D000-DFFF

306: A0 00       LDY #$00  ; ------
308: A9 00       LDA #$00  ; Use Monitor MemCopy to copy
30A: 85 3C       STA $3C   ; the Monitor ROM $F800-FFFF
30C: 85 42       STA $42   ; to RAM at $F800-FFFF in
30E: A9 FF       LDA #$FF  ; preparation to read/write
310: 85 3E       STA $3E   ; $D000-FFFF without it.
312: 85 3F       STA $3F   ;
314: A9 F8       LDA #$F8  ;
316: 85 3D       STA $3D   ;
318: 85 43       STA $43   ;
31A: 20 2C FE    JSR $FE2C ;-------

31D: AD 8B C0    LDA $C08B ; Double read: Read/Write RAM
320: AD 8B C0    LDA $C08B ; $D000-$FFFF, use bank 1 $D000-DFFF

323: A0 00       LDY #$00  ; ------
325: A9 43       LDA #$50  ;
327: 85 3C       STA $3C   ; This is running the copied
329: A9 03       LDA #$03  ; Monitor routines in RAM to
32B: 85 3D       STA $3D   ; use MemCopy again, this time
32D: A9 56       LDA #$60  ; to copy from $350-$35F to
32F: 85 3E       STA $3E   ; $D000 bank 1.
331: A9 03       LDA #$03  ;
333: 85 3F       STA $3F   ;
335: A9 00       LDA #$00  ;
337: 85 42       STA $42   ;
339: A9 D0       LDA #$D0  ;
33B: 85 43       STA $43   ;
33D: 20 2C FE    JSR $FE2C ; ------

340: 4C 00 D0    JSR $D000 ; Run copied code at $D000

                           ; When code above $D000 returns, do
343: AD 8A C0    LDA $C08A ; Read ROM, no write, $D000 bank 1
346: 60          RTS       ; to go back to normal

350: 8D 00 C0    STA $C000 ; ------
353: 8D 03 C0    STA $C003 ; Same as before, switch to
356: 8D 05 C0    STA $C005 ; read/write aux memory, then
359: 8D 02 C0    STA $C002 ; switch back to main memory
35C: 8D 04 C0    STA $C004 ; -----
35F: 60          RTS

(Whoops found a bug.... you can't switch back to reading from ROM while running code above $D000, that dun work.)

After the aux/main switching code is copied up to $D000, the memory area from $300-$35F can be overwritten and reused for something else.

Once the code has been loaded above $CFFF, if you are using BASIC, you only need a short stub to access it and the copied Monitor.

6000: AD 8B C0    LDA $C08B ; Double read: Read/Write RAM
6003: AD 8B C0    LDA $C08B ; $D000-$FFFF, use bank 1 $D000-DFFF
6006: 20 00 D0    JSR $D000
6009: AD 8A C0    LDA $C08A ; Reactivate BASIC in ROM
600C: 60          RTS

Also in this example I am copying the Monitor ROM to RAM, because I am hand-assembling and coding this directly without using a formal compiler. If an error occurs with $D000-FFFF as RAM and you didn't copy the Monitor ROM to RAM, the system will just simply freeze because the ROM routines needed to even display an error message are no longer present. It makes hand-debugging a real pain to not keep the ROM code available in RAM.

4
  • Would it be practical to put the data-access code at the start of the stack, since that region's unaffected by bank switching?
    – supercat
    Dec 2, 2016 at 18:07
  • I don't know if the stack area can be used like that. It sounds very risky to use a system-reserved area for code. Dec 3, 2016 at 11:44
  • Very few programs come anywhere close to using all of the stack.
    – supercat
    Dec 3, 2016 at 23:44
  • A few problems with using the stack (interrupts etc) but the biggest one is space. The best option is LC RAM (AUX LC for programs using ProDOS) because it shares the same switch as ZP/stack, so you can switch main RAM from there. That's really needed if you want to do DHIRES graphics with page-flipping. Apr 20, 2018 at 6:48
3

The other answers discussing how to flip the soft switches to access the AUX RAM are absolutely correct. However, there is also a very easy way to make use of the AUX RAM without getting too crazy: just use the RAM disk in ProDOS.

What problem are you trying to solve that needs additional memory? If it is creating a cache, constructing large tables or otherwise having a lot of data on hand without hitting the disk, you could put it all into the /RAM drive. The advantage here is that you don't have to worry about the different mechanisms of accessing the other banks as it is taken care of for you by ProDOS. Also, you may not have to trash what is in /RAM by disconnecting the RAM disk (a pre-requisite for using AUX RAM) and as an added bonus IRQs work just fine when accessing /RAM unlike the 5.25" driver which has to disable them.

Perhaps not as fast as writing direct access 6502 code, but arguably a heck of a lot easier and can be done from BASIC without too much difficulty.

3
  • Is it possible for a program to e.g. use aux ram regions from $2000-$5FFF for double-hi-res graphics, while still being able to use the remaining portion as a RAM disk? What would a program have to do to mark off $2000-$5FFF for such purposes?
    – supercat
    Jan 9, 2021 at 21:14
  • @supercat - interesting idea, but might have a catch. First raw idea: I assume that if you start from a clean /RAM volume, if you start creating a large random access file some range of it will definitely correspond with $2000-$5FFF. In that case, you should be able to go into DHGR mode and use random-access writes to the right offsets in that file. The catch could be that blocks in /RAM probably have header/footer information for previous/next blocks (like on floppies) and could introduce bad bytes into your DHGR. Someone with more knowledge of how /RAM works would have to answer that.
    – bjb
    Jan 13, 2021 at 17:34
  • If there are a couple of addresses that PRODOS uses to set the start/end addresses of the portion of aux ram that it treats as a RAM disk, that would suggest the possibility of setting the start of aux ram to $4000 or $6000 and reformatting aux RAM as a smaller RAM disk. Such a thing should be simple for PRODOS to support, but I have no idea if it does. Incidentally, another useful variant would be if PRODOS could relocate part of itself or its buffers into the $C00-$2000 region, freeing up non-aux RAM.
    – supercat
    Jan 13, 2021 at 17:59

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .