Friday, August 4, 2017

Color BASIC Program On A Cartridge?

Wow! It's been a while since I posted on this blog. I guess the podcast has been keeping me busy, among other things. Anyway, I'm back for now so let's talk about something cool and retro... :-)

Games On Cartridge

Those of you familiar with my Fahrfall project will recall that I ultimately packaged Fahrfall as a ROM cartridge, one of the available mechanisms for distributing software for the Tandy Color Computer (aka CoCo). I even went so far as to produce silicon molds and to cast cartridge cases made of plastic resin for that project. For me, no other means of distributing software seems as cool and "retro" as using a ROM cartridge, and I am very pleased with the results of that project. Nevertheless, producing resin cast plastic cartridges was not without its problems.

3D-printed plastic projects are 'all the rage' these days, but 3D-printing still has plenty of its own problems: filament is expensive, surfaces usually have visible texture marks, the layered construction often leads to "delamination" and other structural problems, and printing times are so long that bulk production runs become at least impractical. With these issues in mind, some time ago I partnered with Boisy Pitre and Mike Rowen to produce injection-molded cartridge cases for the CoCo. These cases are beautiful, strong, and despite considerable setup costs fairly cheap in large quantities.

Having produced a large number of cartridge cases, my partners and I have explored a variety of ways to market them to the community. My preferred approach is to wrap cases around software releases, and I have done so with Fahrfall and a number of other titles both from myself and from others. To date all of these titles have been written either in assembly language or C, resulting in machine language programs running directly from ROM on the CoCo. But many CoCo authors are skilled in Color BASIC (Extended or otherwise).

BASIC Games Too!

Ultimately, ROM is just another storage medium. So, it should come as no surprise that a BASIC program could be distributed on a ROM cartridge as well. No version of Color BASIC supports loading programs from ROM, so a little trickery is required to make the CoCo aware of the program stored in ROM. So, what has to be done?

Long ago there was a web page called "Paul D's CoCo Pages". This was nearly lost after Yahoo bought GeoCities, but fortunately an archive exists. The page in question describes a technique for loading a BASIC program from a ROM cartridge. Boisy and I had also sought advice from Robert Gault on this topic, and he proposed a technique that was substantially the same. I had some trouble replicating the procedure described on the "Paul D" page, but below I'll summarize what worked for me.

Extraction Procedure

Start with loading the BASIC program on the CoCo. Then you will need to determine the start and end of the RAM used to store the actual BASIC program itself in memory:

A=PEEK(25)
B=PEEK(26)
C=PEEK(27)
D=PEEK(28)
S=256*A+B
E=256*C+D-1

Now you will need to save that as a memory image (i.e. not as a normal BASIC program). Here I will presume that one is using an emulator and capturing the CSAVEM to a file called cartprog.cas:

CSAVEM "CARTPROG",S,E,0

Now on the machine running the CoCo emulator, we will use the cecb comand from the Toolshed project to extract the binary image file of the CoCo's memory:

cecb copy -2 -b cartprog.cas,CARTPROG cartprog

Building the ROM Image

The Color BASIC ROM will jump to the beginning of cartridge ROM as part of the machine's initialization. We need to put some machine code there to do the job of loading the BASIC program. The assembly language program I am using for this task is shown below. It is based upon the loader program shown on the original "Paul D" page, but with some minor changes to suit my way of working:

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    #52        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    #21077     LOAD LETTERS "RU"
        STD    ,X++
        LDD    #19968     LOAD "N" AND END
        STD    ,X++
        LDB    #4         INDICATE 4 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


This program is assembled and a "raw" format image is produced, called "loader"...

lwasm -9 -l -f raw -o loader loader.asm

The BASIC program image "cartprog" is concatenated with "loader" to produce the data for programming into the cartridge ROM:

cat loader cartprog > eprom.dat # DOS/Windows command differs

How Does It Work?

Once the auto-start cartridge is detected, Color BASIC transfers control to the loader program listed above. The "loader" program performs some simple initializations for the Color BASIC interpreter before proceeding to copy the BASIC program to the CoCo's program memory. This includes setting the start address of the BASIC program.

The "loader" program starts reading from the location labeled BASLOD. There it will read the value zero as shown, and it will write it to the location defined by BASADR. After the first zero, "loader" continues with the next location which will correspond to the first value from "cartprog". The sequence of reading and writing byte-by-byte continues until 3 consecutive zeroes are read. This corresponds to the end of the BASIC program. (The original "Paul D" version looks for 4 zeroes, but AFAICT that is incorrect.) At this point, the end address of the BASIC program (and the end addresses for BASIC variables and arrays) is set for the Color BASIC interpreter.

Finally, the "loader" program manipulates the keyboard input buffer used by Color BASIC in order to force a "RUN" command to be executed immediately. Then, control is transferred to the Color BASIC interpreter and the loaded BASIC program is now executed.

So, there it is -- a BASIC program loaded from a ROM cartridge on a Tandy Color Computer. Now Color BASIC programmers can publish their programs on ROM cartridge, with all of the prestige, durability, and collector value that entails. That includes ROM cartridges with extra hardware, like my CoCo Games Master Cartridge. But that is another story -- you'll just have to stay tuned!

10 comments:

  1. Awesome post. I appreciate that you included the toolshed and lwasm commands. There certainly are some good programs in BASIC that would be worthwhile on a cart.

    ReplyDelete
  2. Excellent blog post! I like how you included a listing of your Assembly language loader program. I agree with Mike, there are definitely some handy BASIC programs I wouldn't mind flashing onto a ROM cart for easy access.

    ReplyDelete
  3. Errata Notice #1

    The ending value for the CSAVEM was taking an extra byte. The text has been changed above to reflect a "-1" in the calculation of the value "E" used in the CSAVEM command.

    ReplyDelete
  4. Errata Notice #2

    The definition of BASADR at the top of the "loader" program was incorrect. The correct value ("7680", corresponding to the default load address for Extended Color BASIC) is there now.

    ReplyDelete
  5. Errata Notice #3

    The "loader" program was incorrectly decrementing the Y register before storing the end of the BASIC program at the LODDON label, resulting in BASIC thinking the program was too short. This has been removed.

    ReplyDelete
  6. Errata Notice #4

    The "loader" program was not setting the end address of variables or arrays for BASIC. This was resulting in potential program memory corruption, depending on variable usage by the BASIC program. Setting these values to match the end of the BASIC program itself has been added.

    ReplyDelete
  7. That was my initial thought. That seems to cause problems with the initialization of variables. The interpreter expects variables to follow the program text, but now the text is up in ROM -- confusion. I haven't dug into the interpreter enough to cite 'chapter and verse' on the code, but it does not seem to work.

    ReplyDelete
  8. The above technique will not work with programs written specifically for the BASIC included on the CoCo3. For an extended version of this technique that will work with those programs, please see here:

    http://retrotinker.blogspot.com/2017/08/coco3-programs-on-cartridge-too.html

    ReplyDelete
  9. This comment has been removed by the author.

    ReplyDelete
  10. Just for the record, I created a ROM images as you described, but it wouldn't work when testing in XROAR. You informed me that XROAR doesn't like ROMs not padded to a power of 2. My test ROM was 458 bytes. IN Linux I did the following:

    $ dd if=/dev/zero bs=512 count=1 of=myrom.ccc
    $ dd if=eprom.dat of=myrom.ccc conv=notrunc

    This made the file 512bytes, and everything ran as expected! Thanks John!

    ReplyDelete