CoCo 3 Loader for big programs

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…

Part1:

        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

Part 2:

        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
Advertisements
This entry was posted in CoCo Programming, Uncategorized. Bookmark the permalink.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s