Tuesday, August 15, 2017

CoCo3 programs on a cartridge too?

Not long after the previous article, someone asked me about making the ROM cartridge loading technique work on a BASIC program written specifically for the CoCo3. Although the issue hadn't occurred to me before that, I immediately suspected that the existing technique would not work in that situation. A little experimentation revealed that to be true...

What's The Deal?

I knew that the CoCo3 had a more complicated boot process than its predecessors. The CoCo3 boot-up involves copying code from ROM to RAM, patching some existing BASIC routines, and adding some new ones designed to take advantage of CoCo3 hardware enhancements. I also knew that to preserve some compatibility with the original CoCo and the CoCo2, ROM cartridge programs were given an environment that looked much more like those earlier machines. In other words, the ROM cartridge environment doesn't get access to the CoCo3 extensions to BASIC. So, a BASIC program written specifically for the CoCo3 won't work with the ROM cartridge environment as our existing technique would try to use it.

Finding The Solution

Understanding the CoCo3 boot process requires some understanding of the actual code in the CoCo3 ROMs. This could be done the hard way, manipulating the CoCo3 registers to expose the actual ROM contents, dumping those contents, then analyzing them with f9dasm or some other disassembler. In this case, however, that work has already been done for us in the Unravelled Series. Referring to Super Extended Basic Unravelled II gives a good disassembly of the code in question and some useful commentary as well.

I had never had cause to look so closely at the CoCo3 boot sequence. I was confused and believed that the Color BASIC and Extended BASIC ROMs in the CoCo3 were essentially untouched from the CoCo and CoCo2 equivalents, and that the CoCo3 ROM got control in a chained fashion similar to how Disk Extended BASIC gets control from Extended Color BASIC. This misunderstanding led to some initial puzzlement until I was able to clear-up the situation in my mind.

In reality, there are some changes to the original Color BASIC and Extended BASIC ROMs that are actually burned into the hardware, as detailed by Super Extended Basic Unravelled II. Among these changes, the startup code is changed to transfer control to the CoCo3 ROM early in the boot process. This code then copies the BASIC ROM contents to RAM and patches the code as required for the CoCo3. After this is done, the patched ROM code (now in RAM) is entered and BASIC initializes itself. Late in this process the cartridge autostart sequence is enabled. When the cartridge autostart is detected, then the CoCo3 re-enables the ROMs and jumps to the code at the start of the cartridge.

The point here is easy to miss -- the CoCo3 has already initialized itself, including copying its patched BASIC code to RAM. But then it purposefully turns-off access to the RAM with the patched code in favor of accessing the ROM with the more-or-less original CoCo2 BASIC. It's no wonder that CoCo3 BASIC programs won't run!

As an experiment, I hacked-together a simple BASIC-in-a-cart program that would run and then exit. From there I verified that by default, I could not access CoCo3-specific BASIC commands (e.g. HSCREEN). But if I executed the POKE to turn-off the ROMs and re-enable the RAM first, I could access the CoCo3 BASIC commands with no problem!

New Loader

So now we must consider modifications to the "loader" program. My first thought was to add the equivalent of the required POKE as an assembly language instruction in the program. But that won't work -- since the loader is executing from ROM, swapping-out the ROM for whatever is in RAM at the same address after the swap will almost certainly cause the CPU to do something weird, foolish, or just wrong. What we really need is a way to execute the POKE after the "loader" has already executed, but before the BASIC program is RUN...

BASADR  EQU     7680

        ORG     49152
LOADER  LDA     #85        SET WARM RESET
        STA     113
        LDD     #32960     SET EXTENDED BASIC RESET VECTOR
        STD     114
        JSR     47452      SET UP PARAMETERS FOR BASIC
        LDA     #53        RESTORE INTERRUPTS
        STA     65283      THAT ARE
        LDA     #5         DISABLED ON
        STA     65315      CARTRIDGE AUTO START
BASIN   LDX     #BASLOD
        LDY     #BASADR    THIS IS LOAD ADDRESS OF BASIC (S)
        STY     25         SAVE IT IN BASIC START
        INC     26         CORRECT THE ACTUAL START VALUE
TNSFER  CLRB               SET END COUNTER TO  ZERO
TNSFR2  LDA     ,X+        GET FIRST BYTE FROM ROMPAK
        STA     ,Y+        TRANSFER BYTE TO BASIC RAM
        BNE     TNSFR2     NON ZERO DATA, KEEP TRANSFERRING
ENDCHK  INCB               ZERO DATA DETECTED, INCREMENT COUNTER
        CMPB    #3         IS THERE 3 CONSECUTIVE ZERO'S?
        BEQ     LODDON     IF YES, STOP TRANSFER
        LDA     ,X+        LOAD NEXT ROMPAK BYTE AFTER ZERO
        STA     ,Y+        TRANSFER BYTE TO BASIC RAM
        BNE     TNSFER     NON ZERO DATA, RETURN TO MAIN LOOP
        BRA     ENDCHK     ZERO DATA, INC COUNTER, STAY IN LOOP
LODDON  STY     27         SAVE END ADDRESS FOR BASIC
        STY     29         SAVE VAR START AT END OF TEXT
        STY     31         SAVE ARRAY START AT END OF VAR/TEXT
AUTRUN  LDX     #733       BASIC LINE INPUT BUFFER
        LDD     #$504f     LOAD CHARS "PO"
        STD     ,x++
        LDD     #$4b45     LOAD CHARS "KE"
        STD     ,x++
        LDD     #$2648     LOAD CHARS "&H"
        STD     ,x++
        LDD     #$4646     LOAD CHARS "FF"
        STD     ,x++
        LDD     #$4446     LOAD CHARS "DF"
        STD     ,x++
        LDD     #$2c30     LOAD CHARS ",0"
        STD     ,x++
        LDA     #$3a       LOAD CHAR ":"
        STA     ,x+
        LDD     #21077     LOAD LETTERS "RU"
        STD     ,X++
        LDD     #19968     LOAD "N" AND END
        STD     ,X++

        LDB     #17        INDICATE 17 CHARACTERS
        CLRA
        STA     112        SET CONSOLE IN BUFFER FLAG
        LDX     #732       POINT TO LINE INPUT BUFFER
        JMP     44159      START MAIN BASIC LOOP
BASLOD  EQU     *          BASIC PROGRAM DATA STARTS HERE
        FCB     $00
        END


Wrap-Up

As you can see, the solution is a bit clever -- just add the POKE to the same line of BASIC input that causes the program to RUN! Aside from that, the process is the same as described in the earlier blog post -- enter the BASIC program, save the in-memory image of the program to emulated cassette, extract the file, append it to the assembled "loader" program above, burn it to an EPROM, and use that in a cartridge. I used Robert Gault's PALETTE.BAS program as a test subject for the technique described, and it works perfectly for me.

For now, I'll leave the actual BASIC conversion work as an exercise for the reader. Perhaps in the future I will seek permission to post converted versions of PALETTE.BAS or some other BASIC programs, along with pre-assembled "loader" images both for the CoCo/CoCo2 and the CoCo3. If you want to see that, then for now you'll just have to stay tuned...

3 comments:

  1. The title, of course, should be "CoCo3 BASIC programs on a cartridge too?" Oh, well...hopefully anyone reading the blog will figure it out... :-)

    ReplyDelete
  2. That's all it took? Nice solution. I'll have to test this. Thank you!

    ReplyDelete
  3. I don't think ANY hardware uses address &HFFDF in the coco1 or coco2. I do not think the early coco's used this address at all. Have you tried using this version on the coco1's and coco2's???

    ReplyDelete