While working on the Pac Man transcode I was feeling a little cramped for space since the CoCo 3 has 64k of addressable space for the 6809 but loading a program that wont fit in memory between $E00 to $7FFF is difficult because the CoCo 3 uses some calls from the Disk Basic and Extended Basic while loading and if your program is too big it will over write the Extended Basic and Disk Basic routines and will lock up the computer.
There is some great info on William Astle’s page specifically his pdf on different ways to load programs into the CoCo. I used this info and most of his two stage loader programs to make a generic loader that you can simply add to the beginning of your program and no longer worry about loading a huge file onto the CoCo3. It as simple as assemble your program as usual, use the cat command to join the bigloader and your program into a new program and use that new program on the CoCo3. Easy…
The technique is a little hard to wrap your head around but here goes… The first program is the main loader it is a small program loaded into RAM at $2000 and while it’s loaded it also overwrites the end of the close routine to make it run itself instead of writing OK on the screen. So once the program is loaded it auto starts and it copies all the data within it’s own large file into memory pages 0 to 7 (or more if you use the tricks mentioned below). The first bit of code that is loaded is actually a secondary program. Since it was loaded in RAM page 0 we know exactly where it is and when we finish copying all of our big program to RAM pages 0 to 7 we then make Block 6 use page 0 and execute the code which is now in RAM at $C000. This code then copies the rest of itself to address $FD00 and then executes it from there. This second program goes uses block 6 which is $C000-$DFFF as the place to read your program in from. While it loads the program it handles all the ORG statements from your assembly language program and all the loading locations and even handles code that would write to RAM $C000-$DFFF so that when the loading is done your code will be exactly where it should be. Your program will be autoexecuted after it’s loaded.
The loader allows you to use memory from $0000 to $FCFF for your program, that’s 64768 bytes! Since it handles ORG statements you could actually use other pages in the CoCo3’s RAM to load even more data from your program. For example if you wanted to load some picture data into RAM pages $23-$25:
ORG $FFA0 FCB $23 ORG $FFA1 FCB $24 ORG $FFA2 FCB $25
ORG $0000 Your data for graphics or whatever from $0000 to $5FFF go here
Just remember to put the pages back to normal before your code is written to those memory Blocks, unless you are doing something fancy…
ORG $FFA0 FCB $38 ORG $FFA1 FCB $39 ORG $FFA2 FCB $3A
Also keep in mind that the loader uses blocks 5 & 6 so don’t use them if you are doing special loads like the above example.
Other things to keep in mind:
-This over writes Extended Basic, Color Basic, Disk Basic and Super Extended Basic so your program will not be able to use these ROMS once it’s loaded.
-The loader uses high speed mode after loading from disk/tape (yeah it should work with a 64k tape based program too) so your program will be executed in high speed mode.
-The Stack is set to $FE00 which is probably the best place for it, since you can’t use $FD00 to $FDFF for your code because the actual loader uses this space. The RAM at $FD00 to FDFF is usable after your program is loaded if you want or it can become a nice space for the stack if left alone.
-RAM used for your code must be between $0000 and $FCFF, if it is bigger the $FCFF it will overwrite the loader code, there is no checking for this in the loader program.
To use the loader you simply copy the BigLoader to the start of your program using a binary copy tool. First assemble your huge program for example we call it myprogram.bin. With OSX and Linux use (cygwin under windows or a similar program) the cat command as follows:
cat bigloader.bin myprogram.bin > bigprogram.bin
This simply adds the loader to the start of your program. Under RSDOS all you need is load the new bigprogram.bin since it now contains the loader and your program code.
Below is the source code for the loader programs if you modify the code for Part 2 it will change the position of where it thinks your program is and you will need to adjust it accordingly also it must be created as a RAW file not a regular CoCo binary.
You can download the program ready to use from here
Now I have some more room I can get back to Pac Man…
ORG $2000 LOADER LDD #$176 ; restore close routine to be polite STD $A42E CLRB ; initialize block counter LOADER1 STB $FFA2 ; set MMU LDX #$4000 ; init load pointer LOADER2 JSR $A176 ; get byte from file TST <$70 ; EOF? BNE LOADER3 ; branch if so STA ,X+ ; save byte in memory CMPX #$6000 ; end of MMU block? BLO LOADER2 ; continue if not INCB ; next MMU block BRA LOADER1 ; set MMU and continue loading LOADER3 JSR $A42D ; close file to be polite LDA #$3A ; Put BLK 2 back to default STA $FFA2 ; page of $3A ORCC #$50 ; disable interrupts to prevent crash CLR $FFA6 ; stage two which decodes the loaded program will run from $C000 ; set CPU memory block 5 to physical memory 0 STA $FFD9 ; go to turbo mode JMP $C000 ; transfer control to payload (stage two) ; this installs the close trap ORG $A42E FDB LOADER ; "LOADER" not really required END LOADER
ORG $C000 LDX #Pointer LDY #$FD00 ! LDD ,X++ STD ,Y++ CMPY #$FE00 BNE < JMP $FD00 Pointer: ORG $FD00 LOADER LDX #$C0EB ; point to start of payload in logical block 6 CLRA ; initialize block counter STA $FFA6 ; initialize MMU point to $C000 where we will decode from LDS #$FE00 ; Stack location LOADER0 BSR GETBYTE ; fetch block flag TSTB BNE LOADER2 ; branch if postamble BSR GETBYTE ; fetch block length STB TWORD BSR GETBYTE STB TWORD+1 LDY TWORD ; fetch block count BSR GETBYTE ; fetch load address STB TWORD2 BSR GETBYTE STB TWORD2+1 LDU TWORD2 ; fetch destination address CMPU #$E000 BHS LOADER1 ; If new block is going to load at or above $E000 then load as normal it wont effect $C000-$DFFF PSHS D LDD TWORD ADDD TWORD2 ; end location in RAM CMPD #$C000 BHS LoadHi ; if writing to mem is going to hit our block then handle it PULS D LOADER1 BSR GETBYTE ; read a byte to its destination Loader1b: STB ,U+ LEAY -1,Y ; finished block? BNE LOADER1 ; continue if not BRA LOADER0 ; handle next block LOADER2 BSR GETBYTE ; ignore two bytes BSR GETBYTE BSR GETBYTE ; fetch execute address STB TWORD BSR GETBYTE STB TWORD+1 LDB #$3E STB $FFA6 ; Put $C000 back to normal it could have code in it now JMP [TWORD] ; transfer control to main program GETBYTE LDB ,X+ ; fetch byte CMPX #$E000 ; end of block? BEQ > RTS ! ; return if not LDX #$C000 ; reset pointer to start of block INCA ; bump block number STA $FFA6 ; set MMU RTS ; return LoadHi: CLR FlagBigE CMPD #$E000 PULS D BLO > ; If it is going to hit $E000 then we have to check when we hit it INC FlagBigE ; Other wise we will be good copying the rest of this block ! BSR GETBYTE ; read a byte to its destination CMPU #$C000 BHS LoadOther STB ,U+ LEAY -1,Y ; finished block? BNE < ; continue if not LBRA LOADER0 ; handle next block * Swap writing blocks and pointer from $C000 to $DFFF range to $A000 to $BFFF * when done then swap them back LoadOther: ! PSHS B LDB #$3E STB $FFA5 ; set block 5 to block 4 position PULS B LEAU -$2000,U TST FlagBigE BNE Continue1 ; Go handle blocks that end at or over $E000 BRA Continue0 ! BSR GETBYTE ; read a byte to its destination Continue0: STB ,U+ LEAY -1,Y ; finished block? BNE < ; continue if not LDB #$3D ; Put block 5 back to normal STB $FFA5 ; page $3D LBRA LOADER0 ; handle next block * Swap writing blocks and pointer from $C000 to $DFFF range to $A000 to $BFFF * when we reach $C000 (really $E000) then swap them back and jump back to normal routine above OverE000: ! BSR GETBYTE ; read a byte to its destination CMPU #$C000 BEQ > Continue1: STB ,U+ LEAY -1,Y ; finished block? BNE < ; continue if not ! PSHS B LDB #$3D ; Put block 5 back to normal STB $FFA5 ; page $3D PULS B LDU #$E000 LBRA Loader1b ; continue as per normal now... FlagBigE: FCB 0 TWORD: ; scratch locations FDB 0 TWORD2: FDB 0 ; read byte from A,X and bump pointer PAYLOAD EQU * ; payload starts here