Category Archives: Color BASIC

Color BASIC string abuse – part 1

See also: part 1 and part 2.

This program demonstrates Color BASIC’s string garbage collection. As strings are manipulated, string memory used will continue to grow until BASIC decides to clean up the string space and move things around a bit.

The program creates a string array and fills each entry up with string data. Using code that checks how much string space is remaining, it will delete old strings if string memory is low.

Watching it run reveals some curious things…

The Program

On an Extended or Disk BASIC system, you will need to do a PCLEAR 1 to get enough memory to run it.

  • Initialization
    • Line 5 – We CLEAR enough string space to hold 100 lines at their largest size of 255 characters. Note that when you use DIM to set an array size, it is base-0. Doing a DIM A$(99) gives you A$(0) to A$(99) — 100 elements. Thus, the CLEAR uses 100, but the DIM in the next line uses 99.
    • Line 10 – MX represents the maximum number of lines in the array.
    • Line 20 – DIM A$(MX) creates an array of 0-99 entries (100). AL is set to 0, and will be the line (array entry) we will ADD next. DL will be used to track the next line (array entry) we need to delete. DL is set to -1 for “nothing to delete yet.”
  • Main Loop
    • Line 30 – SZ is set to the length of the string we want to add. It has an unused RND at the start, but then SZ is hard-coded to 255. The program was designed to test random lengths of strings, but for this demo, we will be overriding that and making every string the maximum size.
    • Line 40 – GOSUB 1000 goes to a routine that will ADD a line. Then we GOTO 30 to create a new line and do it again.
  • Add New String subroutine
    • Line 1010 checks to see if the ADD line has caught up to the DELETE line. If it has, we GOSUB 2000 to handle deleting the delete line.
    • Line 1020 – GOSUB 3000 will return the amount of free string space in Z. If Z is less than 104, GOSUB 2000 is called to delete a string.
    • Line 1030 – It prints how long of a string is being added to which line, followed by the current free string space before the add.
    • Line 1040 – The string is added to the A$ array at position AL.
    • Line 1050 – AL (add line) is incremented by 1, moving it to the next line of the array. If AL is larger than the MX (max array entries), it will wrap around to entry 0.
    • Line 1070 – Returns.
  • Delete Old String subroutine
    • Line 2010 – Print which line is about to be deleted (set to “”).
    • Line 2020 – The A$ entry at position DL is set to “”, releasing the string memory it was using.
    • Line 2030 – DL (delete line) is incremented by one. If DL is larger than MX (max array entries), it will wrap around to entry 0.
    • Line 2040 – GOSUB 5000 is a pause routine so we can see what just happened.
  • Get Free String Space routine
    • Line 3010 – Z is calculated as the difference between the FRETOP (start of string storage) memory location and the STRTAB (start of string variables) in the reserved string area. This gives us how many bytes of unused string memory is available.
    • Line 2030 – Returns.
  • Pause subroutine
    • Line 510 – Print the message “[PAUSE]” with no carriage return at the end.
    • Line 5020 – If INKEY$ returns nothing (no key pressed), keep checking.
    • Line 5030 – Prints a string of 7 CHR$(8)s (backspace character) which will erase over the “[PAUSE]” prompt.

Based on the intent, one might think that running this would fill strings up to around entry 100, and then it would start deleting the old string

But is that what it will do?

Let’s run it and find out.

5 CLEAR 100*255
10 MX=99
20 DIM A$(MX):AL=0:DL=-1
30 SZ=RND(255):SZ=255
40 A$=STRING$(SZ,"X")
50 GOSUB 1000:GOTO 30
999 END

1000 REM
1001 REM ADD NEW STRING
1002 REM
1005 IF AL=DL THEN GOSUB 2000
1006 GOSUB 3000:IF Z<1024 THEN GOSUB 2000
1010 PRINT "ADDING";LEN(A$)"BYTES AT";AL;Z
1020 A$(AL)=A$
1030 AL=AL+1:IF AL>MX THEN AL=0
1040 IF DL=-1 THEN DL=0
1050 RETURN

2000 REM
2001 REM DELETE OLD STRING
2002 REM
2010 PRINT ,"DELETING";DL
2020 A$(DL)=""
2030 DL=DL+1:IF DL>MX THEN DL=0
2035 GOSUB5000
2040 RETURN

3000 REM
3001 REM GET FREE STRING SPACE
3002 REM
3010 Z=(PEEK(&H23)*256+PEEK(&H24))-(PEEK(&H21)*256+PEEK(&H22)):RETURN

5000 REM
5001 REM PAUSE
5002 REM
5010 PRINT"[PAUSE]";
5020 IF INKEY$="" THEN 5020
5030 PRINT STRING$(7,8);:RETURN

Running the Program

After a PCLEAR 1 and RUN, the program starts filling lines and we can see string memory going down. When it gets to line 47, there is only 1275 bytes of string space left.

And if we check those values, the difference between each one isn’t the 255 we might have expected. We clearly added a new 255 byte string, but memory went from 7905 to 7395 (510 bytes less) and continued down to 1785 to 1275 (510 bytes less). It appears each time we add a 255 byte string, it takes 510 bytes of string memory.

As we get to line 47, we must have had less than 1024 bytes of string memory left and the DELETE LINE subroutine is called. It deletes the oldest line, which is currently line 0. That should free up 255 bytes of string memory.

After pressing a key, we see the next few entries:

After deleting 0, memory has gone from 1275 to 756 (5120 bytes), so DELETE is called again. This time it deletes line 1.

We press a key and let it scroll a bit more until the next DELETE:

Here we see that, at some point, some string cleanup was done and our free string space grew larger. The trend of reducing by 510 bytes for each ned 255 byte string added continues…

And the process repeats, until eventually we roll over at line 99.

From here on, things get a bit more predictable, with a DELETE happening almost every line — though sometimes every two lines.

Eventually things settle in to a pattern of basically DELETING for every line ADDED.

Is it broken? Is it just poorly implemented? Or is it behaving exactly like it is supposed to?

Tune in next time for the exciting conclusion…

The mystery of MEMSIZ and why CoCo 3 BASIC has one more byte of it.

This is just a (hopefully) short article following up on some tangents I went on while learning about Color BASIC string memory and VARTPR and other related (and nonrelated) items.

The story so far…

Color BASIC has reserved string memory that is located at the very top of the 32K that is accessible by BASIC. There are three 16-bit pointers maintained by BASIC that contain the starting address for this memory, the address where the next string will be inserted (sorta), and the end of this memory.

The Color BASIC Unraveled book called them FRETOP, STRTAB and MEMSIZ.

You can get the address in BASIC by using PEEK(x)*256+PEEK(y) with these memory locations:

  • FRETOP – 33 and 34
  • STRTAB – 35 and 36
  • MEMSIZ – 39 and 40

When digging in to this, I noticed the the MEMSIZ location returned on a 32K CoCo was 32766, and on a 16K it was 16382, and on a 4K CoCo it was 4094. The actual last byte of RAM was one byte higher (32767, 16383, and 4095), making me wonder if this was a mistake.

Indeed, when I checked on a CoCo 3, which had newer patches to BASIC done by Microware, the 32K value “correctly” reported 32767 as would have expected.

Will the real MEMSIZ please stand up?

When I posted about this on the Color Computer mailing list, William “Lost Wizard” Astle was once again one of the first to chime in with an explanation:

… That missing byte is needed so VAL can work on a string stored at the very top of string space. It needs to temporarily put a NUL byte after the string so the number parser knows when to stop. After executing, VAL restores the byte to its original value. Things would fail if the byte after the string data was ROM though. On the Coco3, that byte is in RAM so it didn’t need an extra reserved byte. I suspect, however,  that Microware didn’t know that, or thought it was a bug in the original, or just didn’t notice when they replaced the now useless memory scan with a simple LDX. So, it’s not a bug in the original ROM and, accidental or not, … the behavior on the Coco3 [is] also correct.

– William Astle, 7/14/2022

This led me back to the ROM disassembly, where I did indeed locate where the VAL command will load a byte, store 0 there, then restore the original value later.

Color BASIC Unraveled, page B38.

Prove it or lose it.

Since Man cannot code on faith alone, Art “ADOS” Flexser provided a simple way to prove this to be true:

You can demonstrate the need for the extra byte on a 32/64K CoCo 1/2 by the following:

Change the top of string space to $7FFF rather than the usual $7FFE by POKE 40, 255:CLEAR, upping the LSB of MEMSIZ by one.

Then try PRINT VAL(“&H5”) and you get the rather surprising answer of 94! Why 94? Because that’s decimal for hex 5E, and the “E” has arisen from the first byte of Extended Basic at $8000.

On a CoCo 3, the top of string space is already at $7FFF, so you just need to put it into ROM/RAM mode with POKE&HFFDE,0 before entering PRINT VAL(“&H5”) to get the answer of 94.

Art Flexser, 7/17/2022

And there you have it — the reason why MEMSIZE is not actually pointing to the end of usable BASIC memory on a CoCo 1 or 2, and why it still works on a CoCo 3 after being changed to actually point to the end of usable BASIC memory.

The more you know …

Thanks, William and Art. Your knowledge is greatly apprecaited.

Until next time…

Color BASIC info memory locations.

From the “wish I knew then what I (almost) know now” department..

On the Color Computer, the first 1K (1024 bytes) of RAM is reserved for the BASIC ROM code to use. In this article, we will look at some memory locations that pertain to our BASIC code, variables, arrays and strings.

Here are the locations as described in the Color BASIC Unraveled book:

Color BASIC Unraveled, page A1.

Each location contains a 16-bit number which is the address in memory of that information. Here are the values we are interested in, translated to decimal:

  • TXTTAB – 25 and 26
  • VARTAB – 27 and 28
  • ARYTAB – 29 and 30
  • ARYEND – 31 and 32
  • FRETOP – 33 and 34
  • STRTAB – 35 and 36
  • MEMSIZ – 39 and 40

To turn two bytes in to the 16-bit address, we take the PEEK of the first byte, multiply it by 256, then add the PEEK of the second byte. For TXTTAB (Beginning of BASIC program) we would do:

PRINT PEEK(25)*256+PEEK(26)

That value returned will be different depending on the configuration of the Color Computer.

Let’s dig in to what each of these locations means.

TXTTAB: Beginning of BASIC program – 25/26 (&H19/&H1A)

This location represents where in RAM the BASIC program starts.

PRINT PEEK(25)*256+PEEK(26)

On a CoCo with only Color BASIC (no Extended or Disk BASIC), storage for a BASIC program will be right after the 512 bytes of screen memory. The CoCo’s screen is stored at 1024 to 1535, so BASIC programs load at 1536.

START   END   SIZE   DESC
-----  -----  -----  --------------
    0   1023   1024  ROM USE
 1024   1535    512  TEXT SCREEN
 1536  32767  31232  BASIC PROGRAM

With Extended BASIC added, some RAM after the text screen is used for high resolution graphics screens. By default, 6K is reserved on startup, but more or less can be specified using the PCEAR command. Since the smallest graphics screen using 1.5K (1536 bytes), allocation is done in multiples of that size. Thus, on startup, Extended BASIC has four 1.5K pages reserved for graphics. This means BASIC would start at 7668 on a CoCo with Extended BASIC.

START   END   SIZE   DESC
-----  -----  -----  --------------
    0   1023   1024  ROM USE
 1024   1535    512  TEXT SCREEN
 1536   7679   6144  HI-RES GFX
 7680  32767  25088  BASIC PROGRAM

With Disk BASIC, there is an additional 2K of RAM used just after screen memory at 1536, with the high resolution graphics screens after that.

START   END   SIZE   DESC
-----  -----  -----  --------------
    0   1023   1024  ROM USE
 1024   1535    512  TEXT SCREEN
 1536   3583   2048  DISK ROM USE
 3584   9727   6144  HI-RES GFX
 9728  32767  23040  BASIC PROGRAM

NOTE about “START”: As I calculated this, I see that the starting location for BASIC is actually one byte higher than I expected. On a 4K CoCo, I expected BASIC to start at 1536, but PEEK(25)*256+PEEK(27) shows 1537. On an Extended BASIC CoCo, those peeks show 7681 instead of 7680. And on a Disk BASIC system, they show 9729 instead of 9728. I am not sure why it is off by one, but I’m sure someone smarter than I will have the answer in a comment.

NOTE about “END”: You will notice in my table the end location for BASIC is listed as 32766. That would only be true if the machine has 32K or 64K. If the machine had only 4K or 16K, it would be less. (Basically, MEMSIZE – START OF BASIC PROGRAM). And then it’s also off by by one byte for some reason (4094 on a 4K machine, 16382 on a 16K machine, and 32766 on a 32K machine). I’ll have to look in to this. Maybe I’m the one who is off…

NOTE about BASIC Memory: You may note I just said a 32K and 64K CoCo will show the same location for the end of BASIC memory. This is because reasons. See this article for a discussion about getting the most memory for BASIC, or just read this excerpt:

64K NOTE: The reason BASIC memory is the same for 32K and 64K is due to legacy designs. The 6809 processor can only address 16-bits of memory space (64K). The BASIC ROMs started in memory at $8000 (32768, the 32K halfway mark). This allowed the first 32K to be RAM for programs, and the upper 32K was for BASIC ROM, Extended BASIC ROM, Disk BASIC ROM and Program Pak ROMs. Early CoCo hackers figured out how to piggy-pack 32K RAM chips to get 64K RAM in a CoCo, but by default that RAM was “hidden” under the ROM address space. In assembly language, you could map out the ROMs and access the full 64K of RAM. But, since a BASIC program needed the BASIC ROMs, only the first 32K was available.

this article

VARTAB: Start of Variables – 27/28 (&H1B/&H1C)

This location represents where the variable table begins.

PRINT PEEK(27)*256+PEEK(28)

Variables are stored directly after the BASIC program, so subtracting the value here from the value at 25/26 will give you the size of your program:

PRINT (PEEK(27)*256+PEEK(28))-(PEEK(25)*256+PEEK(26))

Note the use of parenthesis around each calculation. I was originally not using them, and was getting bad results because I made, as William Astle noted, “an elementary arithmetic error.”

Math is hard. Just ask Barbie.

Variable Table

The variable table is a series of 7 byte entries. The first two bytes are the variable name.

  • Variable Name (bytes 1 and 2) – If a variable is “LN”, the first two bytes would be “LN”. But, if the variable is a string such as LN$, the first byte would be “L” and the second byte would be the value of “L” + 128 (high bit set).
  • Variable Value (bytes 3-7) or String Descriptor (bytes 3, and 5-6)
    • If the variable is a number, the next five bytes are a floating point representation of the value.
    • If the variable is a string (high bit of the second name byte is set), the five bytes will be a string descriptor entry that contains the size and location of the string data bytes. A string descriptor only uses three of those five bytes. The first byte will be the length of the string (0-255), the next byte is unused, then the third and fourth bytes are the address of the string data in memory (located in reserved string space, to be discussed later in this article).
VARIABLE TABLE ENTRY
----------------------
[L][N][x][x][x][x][x] - numeric variable "LN"

Here is a short program that will print out all the variables. The variables MUST be declared before this code runs. If any new variables are created inside the FOR/NEXT loop, it will not work as expected since the table will have been changed.

0 ' SHOWVARS.BAS
5 DIM VS,VE,L,VN$
10 VS=PEEK(27)*256+PEEK(28)
20 VE=PEEK(29)*256+PEEK(30)
30 FOR L=VS TO VE-5 STEP 7
40 VN$=CHR$(PEEK(L))+CHR$(PEEK(L+1) AND 127)
50 IF PEEK(L+1) AND 128 THEN VN$=VN$+"$"
60 PRINT VN$;
70 FOR I=0 TO 4
80 PRINT TAB(5*I+6);PEEK(L+2+I);
90 NEXT:PRINT
100 NEXT

Running this will show a list of all the variables in use, and the five bytes after the name.

Above, the VN$ entry shows that it is a 3 byte string located in memory at location 127*256+236 (32748). More on this later in the article.

You can test this code further by declaring more variables before line 10, such as with the DIM statement, or just adding declarations like A=42 or ZZ$=”TOP”.

6 DIM A,B,C,D,A$,B$,AB$,ZZ$
7 AB$="ABBA":ZZ$="TOP"

ARYTAB: Start of Arrays – 29/30 (&H1D/&H1E)

This location represents where arrays will be stored.

PRINT PEEK(29)*256+PEEK(30)

And if we know the start…

ARYEND: End of Arrays (+1) – 31/32 (&H&1F/&H20)

And this location represents where arrays end (though it returns one byte past the last entry of the arrays).

PRINT PEEK(31)*256+PEEK(32)

Arrays

This part gets a bit complicated and messy, so unless you plan on doing stuff with arrays, feel free to skip this section completely… :)

Arrays get stored in their own block of memory. Each array entry starts with a 5-byte header that contains the name of the array (2-bytes), the length of the array (2-bytes) and how many dimensions the array is (1-byte). It looks like this:

ARRAY TABLE ENTRY HEADER (5 bytes)
--------------------
[  ][  ][  ][  ][  ] - numeric array "NM"
  N   M  AB  CD  #D  - ABCD = memory used (header to end)
                       #D = number of dimension
                            DIM(X)=1 DIM(X,Y)=2

This is followed by one or more 2-byte entries that contain the dimension size for each dimension. A one dimensional array such as DIM X(10) would have two bytes representing 11. Remember that in BASIC, DIMensions are base 0. DIM X(10) gives you X(0) through X(10) for a total of 11 entries.

Since an array has to have at least one entry, the header will really always have at least 7 bytes. Here is an example for a two dimension array like DIM X(10,20)

ARRAY TABLE ENTRY HEADER (5 bytes + at 2 bytes per each dimension)
------------------------------------
[  ][  ][  ][  ][  ][  ][  ][  ][  ]- numeric array "LN"
  N   M  AB  CD  #D  D1  D1  D2  D2 - ABCD = memory used
                                    - #D = number of dimensions
                                    - D1D1 = number of dimension 1 entries
                                    - D2D2 = number of dimension 1 entries

And, dimensions are backwards from how they are entered in BASIC. If you had this:

DIM AB(1,2,3,4)

You would have an array table entry that would be 15 bytes and look like this:

[65][66] [xx][xx] [4] [0][5] [0][4] [0][3] [0][2] [0][1]

[65][66] - name "AB"
        [xx][xx] - total bytes used from header to end
                [4] - four dimensions
                   [0][5] - DIM (x,x,x,4)
                         [0][4] - DIM (x,x,3,x)
                               [0][3] - DIM (x,2,x,x)
                                     [0][2] - DIM (1,x,x,x)

After each header are a series of 5-byte entries (one for each element in the array) in the same format as variables – either 5-bytes representing a floating point number, or 5-bytes that make up the string descriptor (size and location of string data).

The blocks of entries are in the order listed in the array table. i.e, if you have DIM(1,2) you will first have 3 5-byte entries for the second dimension DIM(x,2) followed by 2 5-byte entries for the first dimension DIM(1,x).

I think.

Here is a program that shows the Array Table:

0 ' SHOWARRAYS.BAS
5 DIM SA,EA,L,AN$,I,AL
6 DIM X(1),Y(10),XY(2,3),ZZ(10,15,20),A$(10),UL$(10,3)

10 SA=PEEK(29)*256+PEEK(30)
20 EA=PEEK(31)*256+PEEK(32)
30 IF SA=EA THEN PRINT "NO ARRAYS":END
40 PRINT "ARRAYS FROM";SA;"TO";EA
45 PRINT "NAME TSIZE DIM ..  ..  ..  .."
50 L=SA
60 AN$=CHR$(PEEK(L))+CHR$(PEEK(L+1) AND 127)
70 IF PEEK(L+1) AND 128 THEN AN$=AN$+"$"
80 PRINT AN$;
90 AL=PEEK(L+2)*256+PEEK(L+3)
100 PRINT TAB(4);AL;
110 PRINT TAB(11);PEEK(L+4);
120 FOR I=0 TO PEEK(L+4)-1
130 PRINT TAB(14+I*4);PEEK(L+5+I*2)*256+PEEK(L+5+I*2+1);
140 NEXT:PRINT
150 L=L+AL:IF L<EA THEN 60

And, with a bit of work, a program could be written to dump the 5-byte entrees for each array. I’ll add that to my “TODO” list…

FRETOP: Start of String Storage – 33/34 (&H21/&H22)

This location represents where reserved string memory begins. String space is reserved using the CLEAR command, with 200 bytes allocated by default. Strings are stored at the top of memory. Thus, on startup, this should print 200 bytes less than the end of BASIC memory:

PRINT PEEK(33)*256+PEEK(34)

If you change the amount of reserved string space, this number will change accordingly. Here we start out with 200 bytes reserved for strings, then change that to 0 bytes, then to 500 bytes:

If you wanted to know how much room is available for a BASIC program plus variables, you could take this value and subtract the start of BASIC location:

PRINT (PEEK(33)*256+PEEK(34))-(PEEK(25)*256+PEEK(26))

However, there is still some extra overhead so this value won’t match with what PRINT MEM shows. I guess it’s better to just PRINT MEM:

Maybe we’ll figure out what those “missing” 17 bytes are being used for.

STRTAB: Start of String Variables – 35/36 (&H22/&H24)

This location represents where the next string will be stored. Sorta.

PRINT PEEK(35)*256+PEEK(36)

Didn’t we just cover this? Not exactly. FRETOP shows where the string memory starts, but strings are actually stored from the top of memory, and grow downward. Meaning, if you have 200 bytes reserved, and place a ten byte string there (A$=”1234567890″), it will be located at the END of that 200 bytes, not at the start. This value points to where strings are stored within the reserved area.

If string memory were set to 16 bytes by using CLEAR 16, string memory would look like this:

FRETOP
 |
[.][.][.][.][.][.][.][.][.][.][.][.][.][.][.][.]
                                              |
                                           STRTAB

If we created a 5 byte string like A$=”12345″, it would then look like this:

FRETOP
 |
[.][.][.][.][.][.][.][.][.][.][.][1][2][3][4][5]
                               |
                            STRTAB

If we wanted to know how much string space is available, we could subtract FRETOP from STRTAB:

PRINT (PEEK(35)*256+PEEK(36))-(PEEK(33)*256+PEEK(34))

If we added another string of ten byte string like B$=”ABCDEFGHIJ”, it would look like:

FRETOP
 |
[.][A][B][C][D][E][F][G][H][I][J][1][2][3][4][5]
 |
STRTAB

At this point, we could add another one byte string, but anything longer would result in an ?OS ERROR (out of string space).

Here is a test program (and notice we are adding +”” to the end of each string. If that isn’t done, the string will be stored inside the BASIC program itself, and not in string memory.)

10 ' STRTAB.BAS
20 CLEAR 16
30 GOSUB 110
40 PRINT "ADDING 5 BYTE STRING"
50 A$="12345"+"":GOSUB 110
60 PRINT "ADDING 10 BYTE STRING"
70 B$="ABCDEFGHIJ"+"":GOSUB 110
80 PRINT "ADDING 2 BYTE STRING"
90 C$="??"+"":GOSUB 110
100 END
110 PRINT PEEK(33)*256+PEEK(34);
120 PRINT TAB(6);PEEK(35)*256+PEEK(36);
130 PRINT TAB(20);"FREE:";(PEEK(35)*256+PEEK(36))-(PEEK(33)*256+PEEK(34))
140 RETURN

Which leaves us with one final location…

MEMSIZ: Top of String Space – 39/40 (&H27/&H28)

This location represents where the end of string storage is, which will also be the end of RAM available to BASIC.

PRINT PEEK(39)*256+PEEK(40)

Using our diagram from STRTAB, let’s add MEMSIZ:

FRETOP                                     MEMSIZ
 |                                            |
[.][.][.][.][.][.][.][.][.][.][.][.][.][.][.][.]
                                              |
                                           STRTAB

MEMSIZE should never move, as it is the highest memory location available to BASIC. FRETOP will move, depending on how many bytes are reserved for strings using the CLEAR command.

After the two strings in the previous example were added, it would look like this:

FRETOP                                     MEMSIZ
 |                                            |
[.][A][B][C][D][E][F][G][H][I][J][1][2][3][4][5]
 |
STRTAB

This means we should be able to subtract STRTAB from MEMSIZ and know how many bytes of string space we are actually using:

PRINT (PEEK(39)*256+PEEK(40))-(PEEK(35)*256+PEEK(36))

Modifying the previous test program, we can print used/total string space at each step:

10 ' MEMSIZ.BAS
20 CLEAR 16
30 GOSUB 110
40 PRINT "ADDING 5 BYTE STRING"
50 A$="12345"+"":GOSUB 110
60 PRINT "ADDING 10 BYTE STRING"
70 B$="ABCDEFGHIJ"+"":GOSUB 110
80 PRINT "ADDING 2 BYTE STRING"
90 C$="??"+"":GOSUB 110
100 END
110 PRINT PEEK(33)*256+PEEK(34);
120 PRINT TAB(6);PEEK(35)*256+PEEK(36);
130 PRINT TAB(14);(PEEK(39)*256+PEEK(40))-(PEEK(35)*256+PEEK(36));
140 PRINT ;"/";
150 PRINT (PEEK(39)*256+PEEK(40))-(PEEK(33)*256+PEEK(34));
160 PRINT "USED"
170 RETURN

Yes … but what’s the point?

With these memory locations, we can determine how large a BASIC program is. We can detect how much string space is reserved, and calculate how much is being used or is free. We can figure out how much variable storage is being used, as well as array storage.

Most of these locations are never meant to be set by a program, but there is one exception. The CLEAR command can also be used to prevent BASIC from growing past a certain memory location. If you use a second parameter such as CLEAR 200,16000, it changes the MEMSIZ to that value.

10 ' CLEARIT.BAS
20 GOSUB 200
30 PRINT "CLEAR TO 16000"
40 CLEAR 200,16000
50 GOSUB 200
60 PRINT "CLEAR TO 10000"
70 CLEAR 200,10000
80 GOSUB 200
90 PRINT "CLEAR TO 32767"
100 CLEAR 200,32767
110 GOSUB 200
120 END
200 PRINT PEEK(39)*256+PEEK(40)
210 RETURN

The CLEAR command erases all variables, so there would be nothing for BASIC to relocate. Instead, this just changes MEMSIZE and adjusts the string values FRETOP and STRTOP and accordingly.

10 ' CLEARIT2.BAS
15 PRINT "FRETOP";TAB(10);"STRTAB";TAB(20);"MEMSIZ"
20 GOSUB 200
30 PRINT "CLEAR TO 16000"
40 CLEAR 200,16000
50 GOSUB 200
60 PRINT "CLEAR TO 10000"
70 CLEAR 200,10000
80 GOSUB 200
90 PRINT "CLEAR TO 32767"
100 CLEAR 200,32767
110 GOSUB 200
120 END
200 PRINT PEEK(33)*256+PEEK(34);
210 PRINT TAB(10);PEEK(35)*256+PEEK(36);
220 PRINT TAB(20);PEEK(39)*256+PEEK(40)
230 RETURN

While it would be possible to manually POKE those six values and accomplish the same thing, there could always be unintended consequences. (But for fun, a BASIC program could be written that moves the string memory somewhere else, then POKEs the values to update it so BASIC keeps running.)

Conclusion

There might be some interesting things one could do by monitoring string space closely… Perhaps all the research I did for this article may be leading somewhere…

Until then…


BONUS CONTENT

Here is the program I used to calculate the tables in this article.

0 ' MEMINFO.BAS

10 GOSUB 1500
20 ZL=0:ZS=1024:ZD$="ROM USE":GOSUB 1000
30 ZS=512:ZD$="TEXT SCREEN":GOSUB 1000
40 ZS=0:ZD$="BASIC PROGRAM":GOSUB 1000
50 GOSUB 2000

60 GOSUB 1500
70 ZL=0:ZS=1024:ZD$="ROM USE":GOSUB 1000
80 ZS=512:ZD$="TEXT SCREEN":GOSUB 1000
85 ZS=6144:ZD$="HI RES GFX":GOSUB 1000
90 ZS=0:ZD$="BASIC PROGRAM":GOSUB 1000
100 GOSUB 2000

110 GOSUB 1500
120 ZL=0:ZS=1024:ZD$="ROM USE":GOSUB 1000
130 ZS=512:ZD$="TEXT SCREEN":GOSUB 1000
135 ZS=2048:ZD$="DISK ROM USE":GOSUB 1000
140 ZS=6144:ZD$="HI RES GFX":GOSUB 1000
150 ZS=0:ZD$="BASIC PROGRAM":GOSUB 1000
160 GOSUB 2000

999 GOTO 999
1000 ' PRINT MEM INFO
1001 ' ZL=START LOCATION
1002 ' ZS=SIZE
1010 IF ZS=0 THEN ZS=32768-ZL
1020 PRINT USING("##### ##### ##### ");ZL;ZL+ZS-1;ZS;
1030 PRINT ZD$
1040 ZL=ZL+ZS:RETURN

1500 ' PRINT MEM INFO HEADER
1510 PRINT "STRT   END  SIZE  DESC"
1520 PRINT "----- ----- ----- -------------"
1530 RETURN

2000 ' WAIT FOR KEY
2010 IF INKEY$="" THEN 2010
2020 RETURN

Color BASIC and string concatenation

The C programming language has a few standard library functions that deal with strings, namely strcpy() (string copy) and strcat() (string concatenate).

Microsoft BASIC has similar string manipulation features built in to the language. For example, to copy an 8 character “string” in to a buffer (array of chars):

char buffer1[80];

strcpy(buffer1, "12345678");

printf("%s\n", buffer1);

In BASIC, you do not need to allocate space for individual strings. Color BASIC allows doing whatever you want with a string provided it is 255 characters or less, and provided the total string space is large enough. By default, Color BASIC reserves 200 bytes for string storage. If you wanted to strcpy() “12345678” to a string variable, you would just do:

BUFFER1$="12345678"

Yes, that’s legal, but Color BASIC only recognizes the first two characters of a string name, so in reality, that is just like doing:

BU$="12345678"

If you need more than the default 200 bytes, the CLEAR command will reserve more string storage. For example, “CLEAR 500” or “CLEAR 10000”.

“CLEAR 500” would let you have five 100 character strings, or 500 one character strings.

And, keep in mind, strings stored in PROGRAM MEMORY do not use this space. For example, if you reduced string space to only 9 bytes, then tried to make a 10 byte string direct from BASIC:

CLEAR 9
A$="1234567890"
?OS ERROR

The wonderful “?OS ERROR” (Out of String Space).

BUT, if strings are declared inside PROGRAM data, BASIC references them from within your program instead of string memory:

5 CLEAR 9
10 A$="1234567890"
20 B$="1234567890"
30 C$="1234567890"
40 D$="1234567890"
50 E$="1234567890"

Yes, that actually works. If you would like to know more, please see my String Theory series.

But I digress…

The other common C string function is strcat(), which appends a string at the end of another:

char buffer1[80];

strcpy(buffer1, "12345678");
strcat(buffer1, "plus this");

printf("%s\n", buffer1);

That code would COPY “12345678” in to the buffer1 memory, then concatenate “plus this” to the end of it, printing out “12345678plus this”.

In BASIC, string concatenation is done by adding the strings together, such as:

A$="12345678"
A$=A$+"plus this"

PRINT A$

Color BASIC allows for strings to be up to 255 characters long, and no more:

The wonderful “?LS ERROR” (Length of String).

Make it Bigger!

In something I am writing, I started out with an 8 character string, and wanted to duplicate it until it was 64 characters long. I did it like this:

10 A$="12345678"
20 A$=A$+A$+A$+A$+A$+A$+A$+A$

In C, if you tried to strcat() a buffer on top of the buffer, it would not work like that.

#include <stdlib.h>
#include <stdio.h>
#include <string.h>

int main(int argc, char **argv)
{
    char buffer1[80];
    
    // buffer1$ = "12345678"
    strcpy(buffer1, "12345678");
    // Result: buffer1="12345678"
    printf ("%s\n", buffer1);
    
    // buffer1$ = buffer1$ + buffer1$
    strcat(buffer1, buffer1);
    // Result: buffer1$="1234567812345678"
    printf ("%s\n", buffer1);

    // buffer1$ = buffer1$ + buffer1$    
    strcat(buffer1, buffer1);
    // Result: buffer1$="12345678123456781234567812345678"
    printf ("%s\n", buffer1);

    return EXIT_SUCCESS;
}

As you can see, each strcat() copies all of the previous buffer to the end of the buffer, doubling the size each time.

The same thing happens if you do it step by step in BASIC:

A$="12345678"
REM RESULT: A$="12345678"

A$=A$+A$
REM RESULT: A$="1234567812345678"

A$=A$+A$
REM RESULT: A$="12345678123456781234567812345678"

But, saying “A$=A$+A$+A$+A$” is not the same as saying “A$=A$+A$ followed by A$=A$+A$”

For example, if you add two strings three times, you get a doubling of string size at each step:

5 CLEAR 500
10 A$="12345678"
20 A$=A$+A$
30 A$=A$+A$
40 A$=A$+A$
50 PRINT A$

Above creates a 64 character string (8 added to 8 to make 16, then 16 added to 16 to make 32, then 32 added to 32 to make 64).

BUT, if you had done the six adds on one line:

5 CLEAR 500
10 A$="12345678"
20 A$=A$+A$ + A$+A$ + A$+A$
50 PRINT A$

…you would get a 48 character string (8 characters, added 6 times).

In C, using strcat(buffer, buffer) with the same buffer has a doubling effect each time, just like A$=A$+A$ does in BASIC each time.

And, adding a bunch of strings together like…

A$=A$+A$+A$+A$+A$+A$+A$+A$ '64 characters

…could also be done in three doubling steps like this:

A$=A$+A$:A$=A$+A$:A$=A$+A$ ' 64 characters

Two different ways to concatenate strings together to make a longer string. Which one should we use?

Must … Benchmark …

In my code, I just added my 8 character A$ up 8 times to make a 64 character string. Then I started thinking about it. And here we go…

Using my standard benchmark program, we will declare an 8 character string then add it together 8 times to make a 64 character string. Over and over and over, and time the results.

0 REM strcat1.BAS
5 DIM TE,TM,B,A,TT
10 FORA=0TO3:TIMER=0:TM=TIMER
20 FORB=0TO1000
30 A$="12345678"
40 A$=A$+A$+A$+A$+A$+A$+A$+A$
70 NEXT
80 TE=TIMER-TM:PRINTA,TE
90 TT=TT+TE:NEXT:PRINTTT/A:END

This produces an average of 1235.

Now we switch to the doubling three times approach:

0 REM strcat2.BAS
5 DIM TE,TM,B,A,TT
10 FORA=0TO3:TIMER=0:TM=TIMER
20 FORB=0TO1000
30 A$="12345678"
40 A$=A$+A$:A$=A$+A$:A$=A$+A$
70 NEXT
80 TE=TIMER-TM:PRINTA,TE
90 TT=TT+TE:NEXT:PRINTTT/A:END

This drops the time down to 888!

Adding separately three times versus add together eight times is a significant speed improvement.

Based on what I learned when exploring string theory (and being shocked when I realized how MID$, LEFT$ and RIGHT$ worked), I believe every time you do a string add, there is a new string created:

“A$=A$+A$+A$+A$+A$+A$+A$+A$” creates eight strings along the way.

“A$=A$+A$:A$=A$+A$:A$=A$+A$” creates six.

No wonder it is faster.

Looks like I need to go rewrite my experiment.

Until next time…

PRINT MEM is not accurate?

While writing up my article on Color BASIC memory, I ran across something I was unaware of. Special thanks to William “Lost Wizard” Astle for his responses on the CoCo mailing list that got me pointed in the right direction…

BASIC memory looks like this:

+-------------+ 0
| SYSTEM USE  |
+-------------+ 1024
| TEXT SCREEN |
+-------------+ 1536
| DISK USE    |
+-------------+
| HI-RES GFX  |
+-------------+
| BASIC PROG  |
+-------------+
| VARIABLES   |
+-------------+
| ARRAYS      |
+-------------+
| FREE MEMORY |
+-------------+
| STRINGS     |
+-------------+ 32767

Without Disk BASIC, “DISK USE” is not there, and without Extended BASIC, neither is “HI-RES GFX”.

To determine free memory available to BASIC (for program, variables or arrays), you would subtract the end of “ARRAYS” from the start of “STRINGS.” The locations that store this are at &H1F-&H20 (31-32) and &H21-&H22 (33-34).

Color BASIC Unraveled, page A1.

Each of those two byte locations holds a 16-bit address to some area of RAM. You can get the values by taking the first byte, multiplying it by 256, and adding the second byte.

PRINT "MEMSIZE:";PEEK(&H27)*256+PEEK(&H28)

Here is a program that prints out some pertinent information:

0 ' BASINFO2.BAS

10 ' START OF BASIC PROGRAM
20 BS=PEEK(25)*256+PEEK(26)

30 ' START OF VARIABLES
40 VS=PEEK(27)*256+PEEK(28)

50 ' START OF ARRAYS
60 SA=PEEK(29)*256+PEEK(30)

70 ' END OF ARRAYS (+1)
80 EA=PEEK(31)*256+PEEK(32)

90 ' START OF STRING STORAGE
100 SS=PEEK(33)*256+PEEK(34)

110 ' START OF STRING VARIABLES
120 SV=PEEK(35)*256+PEEK(36)

130 ' TOP OF STRING SPACE/MEMSIZ
140 ST=PEEK(39)*256+PEEK(40)

150 PRINT "PROG  SIZE";(VS-BS),"STR SPACE";(ST-SS)
170 PRINT "ARRAY SIZE";(EA-SA)," STR USED";(ST-SV)
180 PRINT " VARS SIZE";(SA-VS)," FREE MEM";(SS-EA)

999 END

And here is that PRINT code, without using any variables. It could be made a subroutine that you could GOSUB to and see status of your memory usage:

10 PRINT "PROG  SIZE";(PEEK(27)*256+PEEK(28))-(PEEK(25)*256+PEEK(26)),;
20 PRINT "STR SPACE";(PEEK(39)*256+PEEK(40))-(PEEK(33)*256+PEEK(34))
30 PRINT "ARRAY SIZE";(PEEK(31)*256+PEEK(32))-(PEEK(29)*256+PEEK(30)),;
40 PRINT " STR USED";(PEEK(39)*256+PEEK(40))-(PEEK(35)*256+PEEK(36))
50 PRINT " VARS SIZE";(PEEK(29)*256+PEEK(30))-(PEEK(27)*256+PEEK(28)),;
60 PRINT " FREE MEM";(PEEK(33)*256+PEEK(34))-(PEEK(31)*256+PEEK(32))

There are a few mysteries here. First, if I wanted to get the size of my program, I would take MEMSIZE and subtract ARYEND. That would be this:

PRINT (PEEK(33)*256+PEEK(34))-(PEEK(31)*256+PEEK(32))

But that disagrees with that PRINT MEM shows:

There are some bytes missing somewhere. This made me wonder which was correct, so I consulted the PRINT MEM code in Color BASIC Unravelled. It had comments that shed some light on what is going on:

Color BASIC Unraveled, page B34.

“This is not a true indicator of free memory because BASIC requires a STKBUF size buffer for the stack for which MEM does not allow.”

– Color BASIC Unraveled, page B34.

On page A1, I see the definition for STKBUF:

STKBUF  EQU  58    STACK BUFFER ROOM

I see code in the ROM that takes this in to consideration, adding the value of ARYEND plus the value of STKBUF.

Color BASIC Unraveled, page B20.

That routine is used to test if enough memory is available for something, and if there isn’t, it returns the ?OM ERROR.

But the difference I get is 12 bytes, not 58. What it BOTSTK? It looks like it is just before the pointer variables I have been working with. I just did not know what it was.

0017    BOTSTK  RMB  2    BOTTOM OF STACK AT LAST CHECK

&H17 would be memory location 23. Let’s see where it is by doing…

PRINT PEEK(23)*256+PEEK(24)

That gives me a location about 30 bytes before FRETOP (start of string storage). Here is the program I am using:

0 ' BASPTRS.BAS

10 ' BOTTOM OF STACK
20 SP=PEEK(23)*256+PEEK(24)

30 ' START OF BASIC PROGRAM
40 BS=PEEK(25)*256+PEEK(26)

50 ' START OF VARIABLES
60 VS=PEEK(27)*256+PEEK(28)

70 ' START OF ARRAYS
80 SA=PEEK(29)*256+PEEK(30)

90 ' END OF ARRAYS (+1)
100 EA=PEEK(31)*256+PEEK(32)

110 ' START OF STRING STORAGE
120 SS=PEEK(33)*256+PEEK(34)

130 ' START OF STRING VARIABLES
140 SV=PEEK(35)*256+PEEK(36)

150 ' TOP OF STRING SPACE/MEMSIZ
160 ST=PEEK(39)*256+PEEK(40)

180 PRINT "START OF PROG",BS;(VS-BS)
190 PRINT "START OF VARS",VS;(SA-VS)

200 PRINT "START OF ARRAYS",SA;(EA-SA)
210 PRINT "END OF ARRAYS+1",EA

215 PRINT "BOTTOM OF STACK",SP

229 PRINT "START/STR STORE",SS;(ST-SS)
230 PRINT "START/STR VARS",SV;(ST-SV)
240 PRINT "TOP OF STRINGS",ST

250 PRINT "FREE MEMORY",(SP-EA)

999 END

William clarified a bit of this in a follow-up post.

Actually, currently available memory would be approximately the difference between BOTSTK and ARYEND less the STKBUF amount. MEM neglects to add the STKBUF adjustment in when it calculates the difference. Total usable memory under current PMODE/FILES/CLEAR settings would be the difference between BOTSTK and TXTTAB.

Note that the stack (and consequently the BOTSTK value) will grow downward from FOR loops, GOSUB, and expression evaluation. Expression evaluation can use a surprising amount of stack space depending on how many times it has to save state to evaluate a higher precedence operation, how many function calls are present, etc.

ARYEND is the top of all the memory used by the program itself, PMODE graphics pages, disk buffers and file descriptors, scalar variables, and arrays.

When calculating OM errors, it takes ARYEND, adds the amount of memory requested, adds the STKBUF amount, and then compares that with the current stack pointer. It does the comparison by storing S since you can’t directly compare two registers.

STKBUF is 58 for whatever reason. That’s sufficient to allow for a full interrupt frame (12 bytes) plus a buffer so routines can use the stack for saving registers, routine calls, etc., within reason, without having to continually do OM checks. It does this to prevent corrupting variables when memory is tight. Even so, there may be a few routines in Disk Basic that may still cause the stack to overflow into variable space when memory is very tight.

– William Astle, 7/8/2022

So how can we test? Using the XRoar emulator, I started with a standard 64K CoCo with Disk BASIC. PRINT MEM returns 22832 bytes free.

On startup, a disk-based CoCo has 22823 bytes available for BASIC.

For testing, I’ll create one line of BASIC that is just a REM statement:

10 REM

Now PRINT MEM shows 22817 bytes free. That is 6 bytes less, which is two bytes for where the next line will be (38 7), two bytes for the line number (00 10), the token for REM (130), and a NULL byte (0). Each new line number takes up 5 bytes plus whatever the content of the line is.

Now I want to reserve most of that memory for strings. I’ll do CLEAR 22800. After this, PRINT MEM shows 207… That can’t be right. Shouldn’t it really be 17?

In this case, the original PRINT MEM was already counting 200 bytes reserved for strings. When I did CLEAR 22800, it was release that 200 bytes, then redoing it as 22800 (so, 228600+200).

Let’s start over…

Okay, so let’s redo this. I restarted the CoCo back to where PRINT MEM shows 22823.

The next thing I did was type CLEAR 0 to reserve NO string space, then check PRINT MEM. It shows 23023 — the largest BASIC program I can have on this disk-based machine, unless I deallocate some graphics memory (6K is reserved for that, by default).

Now I enter in the “10 REM” line, and PRINT MEM again. It shows 23017 — 6 bytes less, as expected.

I try to type CLEAR 23017, which gives ?OM ERROR.

I back that off a bit to CLEAR 22960. PRINT MEM shows ?OM ERROR. And now I have hosed BASIC. Even CLEAR 0 now returns ?OM ERROR.

Is this a bug? I can still EDIT my line 10, and LIST it, but while CLEAR by itself works, it changes nothing. I’ve managed to consume so much memory, I can’t run the command I need to give it back.

Let’s start over again…

CoCo reset. CLEAR 0 entered. PRINT MEM shows 22823. 10 REM typed. PRINT MEM shows 23017.

This time I’ll try CLEAR 22950 to give BASIC a bit more room. PRINT MEM shows 67.

If this is accurate, I should be able to add 67 characters to the end of that REM statement.

I type “EDIT 10”, then “X” to extend to the end. I enter ten characters, then ENTER.

10 REM1234567890

PRINT MEM gives me an ?OM ERROR.

Now, the program still LISTS and would run (if it did anything), but clearly other things are broken.

I EDIT 10 and try to add another ten characters.

10 REM12345678901234567890

?OM ERROR.

And now, LINE 10 is completely gone! EDIT must try to “add” a line when it’s done, and if there’s no memory, the line is … erased?

PRINT MEM still shows 67, even though the “10 REM” seems to be gone. Where did that memory go? I try adding a total of 15 characters after the REM:

10 REM123456789012345

That works. PRINT MEM shows ?OM ERROR.

Let’s start over again again.

I reboot the CoCo, then CLEAR 22950. PRINT MEM shows 73 (there is no line number yet).

This “should” mean I could enter a line (5 bytes) that has 73-5=68 characters. But, if I tried to type anything longer than 16, I get ?OM ERROR (REM token is one byte, then 15 characters).

This tells me that when PRINT MEM shows me 67, I really only had 5+16=21 bytes available. 67-21=46 bytes difference.

Looking back at my BASIC POINTERS program output:

…I see the calculated free memory was 22042, versus PRINT MEM showing 22038. That is only four bytes different.

Questions keep stacking up

It is clear you cannot use PRINT MEM to know how much room you will have for a BASIC program. It is more of a guideline, within 50 or so bytes.

The wildcard is probably this mysterious (to me) “stack” that BASIC needs for operation. Depending on what is going on, the stack in-use may be larger or smaller, but the max limit is 58 bytes. And, STKBOT notes that is is “bottom of stack at last check” so it may not be reflecting the actual stack at the particular moment when the code looked at it.

I really don’t have any conclusions from this. When I started writing, I expected I’d find a set of PEEKs I could subtract to get a more accurate PRINT MEM value.

But I failed.

Anyone have any suggestions?

Until then…

Invisible lines in Color BASIC

Updates:

  • 2022-09-19 – John K pointed out a typo in the program listing, which has been corrected.

This one comes from Carl England. We met him at the 1990 Atlanta CoCoFest. That was the first time Sub-Etha Software appeared anywhere, and we couldn’t afford a full booth so we split on with Carl. He will always be the SuperBoot guy to me (my all time favorite utility), but most may know him as the programmer being The Defeater – a disk copy tool that could make clones of copy protected disk. (Download some of his items here.)

Carl popped up in some Facebook comments recently with this odd bit of code.

10 PRINT"THIS LINE IS INVISIBLE":'%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
20 FORI=PEEK(25)*256+PEEK(26)TOPEEK(27)*256+PEEK(28)
30 IFPEEK(I)=37THENPOKEI,8
40 NEXT

Line 10 is a PRINT statement, followed by a comment and a bunch of percent symbols.

Line 20 is a loop that goes from the Start of BASIC to the End of BASIC.

Line 30 uses PEEK to look for a 37, which is the ASCII code for a percent sign. If it finds one, it POKEs it to 8 — a backspace.

Line 40 is just the NEXT for the loop.

If you run this program, it will modify line to and replace all those %’s with backspace characters. Then, if you LIST it, BASIC displays the line then immediately backspaces over it, too quick to see, making it look like there is no line there.

You could RUN this to modify the program, then DELete lines 20-40, and save it to tape or disk. You could then have a program you could load and run that would print a message, but LISTing it would appear as if nothing was there.

Carl said he used this to hide lines in some of his programs, but mentioned you could still see the code if you sent it to a printer using LLIST. Also, if you knew there was a line 10, you could EDIT 10 and SPACE through the line, revealing the code. I suppose you could SPACE until you got to the colon and then Hack the rest of the line, removing those backspace.

Fun.

Until next time…

Disk BASIC and drive numbers BEFORE the filename?

In a follow up to an article I posted about undocumented syntax, here we go again…

When all you had was a cassette recorder, loading and saving was simple:

CLOAD "PROGRAM"

CSAVE "PROGRAM"

When all you had was a single disk drive, loading and saving was simple:

LOAD "PROGRAM.BAS"

SAVE "PROGRAM.BAS"

When you had more than one disk drive, loading and saving was still simple… Just add the drive number:

LOAD "PROGRAM.BAS:0"

LOAD "PROGRAM.BAS:1"

COPY "PROGRAM.BAS:0" TO "PROGRAM.BAS:1"

And we liked it that way…

0:, 1:, 2: and 3:???

Over in the CoCo Discord chat, I just learned from William “Lost Wizard” Astle that the drive number can also be put at the start of the filename:

SAVE "1:PROGRAM.BAS"

LOAD "1:PROGRAM.BAS"
Disk BASIC drive numbers … BEFORE the file???

Huh??? He points out that DOS modifications, such as RGB-DOS/HDB-DOS, do not support this.

And it’s just weird.

Checksums and zeros and XMODEM and randomness.

A year or two ago, I ran across some C code at my day that finally got me to do an experiment…

When I was first using a modem to dial in to BBSes, it was strictly a text-only interface. No pictures. No downloads. Just messages. (Heck a physical bulletin board at least would let you put pictures on it! Maybe whoever came up with the term BBS was just forward thinking?)

The first program I ever had that sent a program over the modem was DFT (direct file transfer). It was magic.

Later, I got one that used a protocol known as XMODEM. It seems like warp speed compared to DFT!

XMODEM would send a series of bytes, followed by a checksum of those bytes, then the other end would calculate a checksum over the received bytes and compare. If they matched, it went on to the next series of bytes… If it did not, it would resend those bytes.

Very simple. And, believe it or not, checksums are still being used by modern programmers today, even though newer methods have been created (such as CRC).

Checking the sum…

A checksum is simple the value you get when you add up all the bytes of some data. Checksum values are normally not floating point, so they will be limited to a fixed range. For example, an 8-bit checksum (using one byte) can hold a value of 0 to 255. A 16-bit checksum (2 bytes) can hold a value of 0-65535. Since checksums can be much higher values, especially if using an 8-bit checksum, the value just rolls over.

For example, if the current checksum calculated value is 250 for an 8-bit checksum, and the next byte being counted is a 10, the checksum would be 250+10, but that exceeds what a byte can hold. The value just rolls over, like this:

250 + 10: 251, 252, 253, 254, 255, 0, 1, 2, 3, 4

Thus, the checksum after adding that 10 is now 4.

Here is a simple 8-bit checksum routine for strings in Color BASIC:

0 REM CHKSUM8.BAS
10 INPUT "STRING";A$
20 GOSUB 100
30 PRINT "CHECKSUM IS";CK
40 GOTO 10

100 REM 8-BIT CHECKSUM ON A$
110 CK=0
120 FOR A=1 TO LEN(A$)
130 CK=CK+ASC(MID$(A$,A,1))
140 IF CK>255 THEN CK=CK-255
150 NEXT
160 RETURN

Line 140 is what handles the rollover. If we had a checksum of 250 and the next byte was a 10, it would be 260. That line would detect it, and subtract 255, making it 4. (The value starts at 0.)

The goal of a checksum is to verify data and make sure it hasn’t been corrupted. You send the data and checksum. The received passes the data through a checksum routine, then compares what it calculated with the checksum that was sent with the message. If they do not match, the data has something wrong with it. If they do match, the data is less likely to have something wrong with it.

Double checking the sum.

One of the problems with just adding (summing) up the data bytes is that two swapped bytes would still create the same checksum. For example “HELLO” would have the same checksum as “HLLEO”. Same bytes. Same values added. Same checksum.

A good 8-bit checksum.

However, if one byte got changed, the checksum would catch that.

A bad 8-bit checksum.

It would be quite a coincidence if two data bytes got swapped during transfer, but I still wouldn’t use a checksum on anything where lives were at stake if it processed a bad message because the checksum didn’t catch it ;-)

Another problem is that if the value rolls over, that means a long message or a short message could cause the same checksum. In the case of an 8-bit checksum, and data bytes that range from 0-255, you could have a 255 byte followed by a 1 byte and that would roll over to 0. A checksum of no data would also be 0. Not good.

Checking the sum: Extreme edition

A 16-bit or 32-bit checksum would just be a larger number, reducing how often it could roll over.

For a 16-bit value, ranging from 0-65535, you could hold up to 257 bytes of value 255 before it would roll over:

255 * 257 = 65535

But if the data were 258 bytes of value 255, it would roll over:

255 * 258 = 65790 -> rollover to 255.

Thus, a 258-byte message of all 255s would have the same checksum as a 1-byte message of a 255.

To update the Color BASIC program for 16-bit checksum, change line 140 to be:

140 IF CK>65535 THEN CK=CK-65535

Conclusion

Obviously, an 8-bit checksum is rather useless, but if a checksum is all you can do, at least use a 16-bit checksum. If you were using the checksum for data packets larger than 257 bytes, maybe a 48-bit checksum would be better.

Or just use a CRC. They are much better and catch things like bytes being out of order.

But I have no idea how I’d write one in BASIC.

One more thing…

I almost forgot what prompted me to write this. I found some code that would flag an error if the checksum value was 0. When I first saw that, I thought “but 0 can be a valid checksum!”

For example, if there was enough data bytes that caused the value to roll over from 65535 to 0, that would be a valid checksum. To avoid any large data causing value to add up to 0 and be flagged bad, I added a small check for the 16-bit checksum validation code:

if ((checksum == 0) && (datasize < 258)) // Don't bother doing this.
{
    // checksum appears invalid.
}
else if (checksum != dataChecksum)
{
    // checksum did not match.
}
else
{
    // guess it must be okay, then! Maybe...
}

But, what about a buffer full of 00s? The checksum would also be zero, which would be valid.

Conclusion: Don’t error check for a 0 checksum.

Better yet, use something better than a checksum…

Until next time…

I found hidden stuff in my own game.

On August 21, 1994 I began writing a space invaders game for the Radio Shack TRS-80 Color Computer. The game was written in 6809 assembly language, and ran under the Microware OS-9 operating sytem as opposed to the ROM-based Disk Extended Color BASIC.

It did not initially start out as an OS-9 game. It started out as a NitrOS9 game. NitrOS9 was (and still is) a greatly optimized and enhanced version of the stock OS-9 for the Color Computer. It was initially a set of patches that took advantage of the hidden features of the Hitatchi 6309 chip. Many of us did CPU swaps in our CoCo 3s specifically to be able to run this faster version of OS-9. Years later, NitrOS-9 was backported to run on a stock 6809 and the project continues today with the Ease of Use edition where it comes ready to run and bundled with all kinds of utilities, applications, and games. (I think my game is even on there.)

http://www.lcurtisboyle.com/nitros9/nitros9.html

But I digress.

The reason I chose to write a game was after learning about a new system call that NitrOS9 added. It allowed mapping in graphics screen memory so a program could directly access it — just like from BASIC. With that in mind, I wrote a simple demo that had a peace-sign space ship that could move left and right and fire (multishots!), as well as a scrolling star background.

I believe my game demo source might have been published in The International OS-9 Underground magazine at some point.

As soon as I figure out how to make WordPress allow uploading a .asm source file, I’ll share it here.

But I digress. Again.

Invaders09 Secrets

Version 1.00 was completed on September 24, 1994. It was first sold at the 1994 Atlanta CoCoFest. I don’t remember how many copies the game sold over its lifetime, but I do know it was not enough to retire on. :)

On December 26, 1994, version 1.01 was released. This contained code by Robert Gault that allowed the game to work on machines with more than 512K memory. (Robert was also responsible for code that allowed the game to work on stock OS-9, as well.)

A big update happened on January 29, 1995, when the game was upgraded from a 4-color screen to glorious 16 colors.

1.03 was completed on February 4, 1995 and included bug fixes.

Almost twenty years later, to the day, I did a 1.04 update. The title screen text removed my old P.O. Box from Texas, and replaced it with an e-mail address. I also added the “secret” command line option to the help screen, so it would no longer be secret. There had also been a bug that caused the fonts to sometimes fail to load, which I found and fixed. There were also some bad bits in the graphics I had never noticed (but could see clearly on a modern monitor) which were corrected.

Something old. Something new?

I pulled up this source code today and was looking at it to see what all I’d have to do to convert it to run under Disk Extended Color BASIC. I’d have to learn about keyboard and joystick reading in assembly, as well as how to map in graphics screens. I’d also have to take care of the blips and boops, and create my own graphical text engine for displaying game and title screen messages.

I don’t know how to do any of that, yet.

But I did discover something I have no recollection of… The game contains its own font data, which it loads when the game first runs. (Note to self: Better check and make sure the game cleans that font up and deallocates it when the game exits.)

The font data is a series of fcb byte entries like these:

* 91 [
 fcb 126, 64, 64, 96, 96, 96, 126, 0
* 92 \
 fcb 64,64,32,24,12,6,6,0
* 93 ]
 fcb 126, 2, 2, 6, 6, 6, 126, 0
* 94 up arrow
 fcb 24,52,98,0,0,0,0,0
* 95 _
 fcb 0, 0, 0, 0, 0, 0, 0, 255
* 96 `
 fcb 96, 48, 0, 0, 0, 0, 0, 0
* 97 a
 fcb 0, 0, 62, 2, 126, 98, 126, 0

The fonts are 8×8 pixels in size, so each entry has 8 bytes to represent the character.

At the top of the font data was a comment that caught my attention:

* hidden stuff in the font! :)

Hidden stuff? What did I hide? It appears there was repeating data in the first 32 font characters before the SPACE at 32:

font
 fcb $05,$00,$08,$00,$08,$04,$00 <- GPBufLoad Stuff
 fcb 0,87,81,119,20,23,0,0  * hidden stuff in the font! :)
 fcb 0,87,81,119,20,23,0,0
 fcb 0,87,81,119,20,23,0,0
 fcb 0,87,81,119,20,23,0,0
 fcb 0,87,81,119,20,23,0,0
 fcb 0,87,81,119,20,23,0,0
 fcb 0,87,81,119,20,23,0,0
 fcb 0,87,81,119,20,23,0,0
 fcb 0,87,81,119,20,23,0,0
 fcb 0,87,81,119,20,23,0,0
 fcb 0,87,81,119,20,23,0,0
 fcb 0,87,81,119,20,23,0,0
 fcb 0,87,81,119,20,23,0,0
 fcb 0,87,81,119,20,23,0,0
 fcb 0,87,81,119,20,23,0,0
 fcb 0,87,81,119,20,23,0,0
 fcb 0,87,81,119,20,23,0,0
 fcb 0,87,81,119,20,23,0,0
 fcb 0,87,81,119,20,23,0,0
 fcb 0,87,81,119,20,23,0,0
 fcb 0,87,81,119,20,23,0,0
 fcb 0,87,81,119,20,23,0,0
 fcb 0,87,81,119,20,23,0,0
 fcb 0,87,81,119,20,23,0,0
 fcb 0,87,81,119,20,23,0,0
 fcb 0,87,81,119,20,23,0,0
 fcb 0,87,81,119,20,23,0,0
 fcb 0,87,81,119,20,23,0,0
 fcb 0,87,81,119,20,23,0,0
 fcb 0,87,81,119,20,23,0,0
 fcb 0,87,81,119,20,23,0,0
 fcb 0,87,81,119,20,23,0,0
* 32 (space)
 fcb 0, 0, 0, 0, 0, 0, 0, 0

I was curious what that was, so I searched to see if I could find an only bitmap font editor. Sure enough, someone has one for doing Commodore fonts:

https://petscii.krissz.hu/

I went there, and was able to recreate this “hidden stuff” in the font:

https://petscii.krissz.hu/

I had hidden a teeny tiny “42” in the font character set… Something no one would ever see, and that I had forgotten about.

Sub-Etha Software had other hidden 42s in other programs we distributed. I bet I’ve forgotten about some of them, as well…

But wait, there’s more … BASIC!

I took the code I wrote to display VIC-20 font data on a CoCo and updated it a bit, with this font data.

Invaders09 font data displayed on PMODE 0 under Extended Color BASIC.

You can adjust the WD variable in line 10 based on what PMODE you want to see it in. Change that to 32 and PMODE 4 and you get it in the size it would be on a CoCo 32-column screen. Use 16 and that will work with PMODE 0 or PMODE 2. (PMODE 1 and 3 are color modes and just look weird since they take the 8 bits and turn them in to four 2-bit color pixels).

Enjoy…

0 REM INVADERS09 CHARSET
10 WD=16 '16=PMODE 0/2, 32=4
20 PMODE 0,1:PCLS:SCREEN 1,1
30 L=1536+2048:C=0
40 FOR R=0 TO 7:READ D:IF D=-1 THEN 999
50 POKE L+(WD*R),D:NEXT
60 L=L+1:C=C+1:IF C>=WD THEN C=0:L=L+(WD*8)
70 GOTO 40
999 GOTO 999
1000 ' hidden stuff in the font! :)
1010 DATA 0,87,81,119,20,23,0,0
1020 DATA 0,87,81,119,20,23,0,0
1030 DATA 0,87,81,119,20,23,0,0
1040 DATA 0,87,81,119,20,23,0,0
1050 DATA 0,87,81,119,20,23,0,0
1060 DATA 0,87,81,119,20,23,0,0
1070 DATA 0,87,81,119,20,23,0,0
1080 DATA 0,87,81,119,20,23,0,0
1090 DATA 0,87,81,119,20,23,0,0
1100 DATA 0,87,81,119,20,23,0,0
1110 DATA 0,87,81,119,20,23,0,0
1120 DATA 0,87,81,119,20,23,0,0
1130 DATA 0,87,81,119,20,23,0,0
1140 DATA 0,87,81,119,20,23,0,0
1150 DATA 0,87,81,119,20,23,0,0
1160 DATA 0,87,81,119,20,23,0,0
1170 DATA 0,87,81,119,20,23,0,0
1180 DATA 0,87,81,119,20,23,0,0
1190 DATA 0,87,81,119,20,23,0,0
1200 DATA 0,87,81,119,20,23,0,0
1210 DATA 0,87,81,119,20,23,0,0
1220 DATA 0,87,81,119,20,23,0,0
1230 DATA 0,87,81,119,20,23,0,0
1240 DATA 0,87,81,119,20,23,0,0
1250 DATA 0,87,81,119,20,23,0,0
1260 DATA 0,87,81,119,20,23,0,0
1270 DATA 0,87,81,119,20,23,0,0
1280 DATA 0,87,81,119,20,23,0,0
1290 DATA 0,87,81,119,20,23,0,0
1300 DATA 0,87,81,119,20,23,0,0
1310 DATA 0,87,81,119,20,23,0,0
1320 DATA 0,87,81,119,20,23,0,0
1330 ' 32 (space)
1340 DATA 0, 0, 0, 0, 0, 0, 0, 0
1350 DATA 16, 16, 24, 24, 24, 0, 24, 0
1360 DATA 102, 102, 204, 0, 0, 0, 0, 0
1370 DATA 68, 68, 255, 68, 255, 102, 102, 0
1380 DATA 24, 126, 64, 126, 6, 126, 24, 0
1390 DATA 98, 68, 8, 16, 49, 99, 0, 0
1400 DATA 62, 32, 34, 127, 98, 98, 126, 0
1410 DATA 56, 56, 24, 48, 0, 0, 0, 0
1420 DATA 12, 24, 48, 48, 56, 28, 12, 0
1430 DATA 48, 56, 28, 12, 12, 24, 48, 0
1440 DATA 0, 24, 36, 90, 36, 24, 0, 0
1450 DATA 0, 24, 24, 124, 16, 16, 0, 0
1460 DATA 0, 0, 0, 0, 0, 48, 48, 96
1470 DATA 0, 0, 0, 126, 0, 0, 0, 0
1480 DATA 0, 0, 0, 0, 0, 48, 48, 0
1490 ' 47 /
1500 DATA 2, 2, 4, 24, 48, 96, 96, 0
1510 DATA 126, 66, 66, 70, 70, 70, 126, 0
1520 DATA 8, 8, 8, 24, 24, 24, 24, 0
1530 DATA 126, 66, 2, 126, 96, 98, 126, 0
1540 DATA 124, 68, 4, 62, 6, 70, 126, 0
1550 DATA 124, 68, 68, 68, 126, 12, 12, 0
1560 DATA 126, 64, 64, 126, 6, 70, 126, 0
1570 DATA 126, 66, 64, 126, 70, 70, 126, 0
1580 DATA 62, 2, 2, 6, 6, 6, 6, 0
1590 DATA 60, 36, 36, 126, 70, 70, 126, 0
1600 DATA 126, 66, 66, 126, 6, 6, 6, 0
1610 DATA 0, 24, 24, 0, 24, 24, 0, 0
1620 DATA 0, 24, 24, 0, 24, 24, 48, 0
1630 DATA 6, 12, 24, 48, 28, 14, 7, 0
1640 DATA 0, 0, 126, 0, 126, 0, 0, 0
1650 DATA 112, 56, 28, 6, 12, 24, 48, 0
1660 DATA 126, 6, 6, 126, 96, 0, 96, 0
1670 ' 64
1680 DATA 60, 66, 74, 78, 76, 64, 62, 0
1690 DATA 60, 36, 36, 126, 98, 98, 98, 0
1700 DATA 124, 68, 68, 126, 98, 98, 126, 0
1710 DATA 126, 66, 64, 96, 96, 98, 126, 0
1720 DATA 124, 66, 66, 98, 98, 98, 124, 0
1730 DATA 126, 64, 64, 124, 96, 96, 126, 0
1740 DATA 126, 64, 64, 124, 96, 96, 96, 0
1750 DATA 126, 66, 64, 102, 98, 98, 126, 0
1760 DATA 66, 66, 66, 126, 98, 98, 98, 0
1770 DATA 16, 16, 16, 24, 24, 24, 24, 0
1780 DATA 4, 4, 4, 6, 6, 70, 126, 0
1790 DATA 68, 68, 68, 126, 98, 98, 98, 0
1800 DATA 64, 64, 64, 96, 96, 96, 124, 0
1810 DATA 127, 73, 73, 109, 109, 109, 109, 0
1820 DATA 126, 66, 66, 98, 98, 98, 98, 0
1830 DATA 126, 66, 66, 98, 98, 98, 126, 0
1840 DATA 126, 66, 66, 126, 96, 96, 96, 0
1850 DATA 126, 66, 66, 66, 66, 78, 126, 0
1860 DATA 124, 68, 68, 126, 98, 98, 98, 0
1870 DATA 126, 66, 64, 126, 6, 70, 126, 0
1880 DATA 126, 16, 16, 24, 24, 24, 24, 0
1890 DATA 66, 66, 66, 98, 98, 98, 126, 0
1900 DATA 98, 98, 98, 102, 36, 36, 60, 0
1910 DATA 74, 74, 74, 106, 106, 106, 126, 0
1920 DATA 66, 66, 66, 60, 98, 98, 98, 0
1930 DATA 66, 66, 66, 126, 24, 24, 24, 0
1940 DATA 126, 66, 6, 24, 96, 98, 126, 0
1950 ' 91 [
1960 DATA 126, 64, 64, 96, 96, 96, 126, 0
1970 ' 92 \
1980 DATA 64,64,32,24,12,6,6,0
1990 ' 93 ]
2000 DATA 126, 2, 2, 6, 6, 6, 126, 0
2010 ' 94 up arrow
2020 DATA 24,52,98,0,0,0,0,0
2030 ' 95 _
2040 DATA 0, 0, 0, 0, 0, 0, 0, 255
2050 ' 96 `
2060 DATA 96, 48, 0, 0, 0, 0, 0, 0
2070 ' 97 a
2080 DATA 0, 0, 62, 2, 126, 98, 126, 0
2090 DATA 64, 64, 126, 70, 70, 70, 126, 0
2100 DATA 0, 0, 126, 66, 96, 98, 126, 0
2110 DATA 2, 2, 126, 66, 70, 70, 126, 0
2120 DATA 0, 0, 124, 68, 124, 98, 126, 0
2130 DATA 62, 34, 32, 120, 48, 48, 48, 0
2140 DATA 0, 0, 126, 66, 98, 126, 2, 62
2150 DATA 64, 64, 126, 66, 98, 98, 98, 0
2160 DATA 16, 0, 16, 16, 24, 24, 24, 0
2170 DATA 0, 2, 0, 2, 2, 2, 98, 126
2180 DATA 96, 96, 100, 68, 126, 70, 70, 0
2190 DATA 16, 16, 16, 16, 24, 24, 24, 0
2200 DATA 0, 0, 98, 126, 74, 106, 106, 0
2210 DATA 0, 0, 126, 66, 98, 98, 98, 0
2220 DATA 0, 0, 126, 66, 98, 98, 126, 0
2230 DATA 0, 0, 126, 66, 66, 126, 96, 96
2240 DATA 0, 0, 126, 66, 78, 126, 2, 2
2250 DATA 0, 0, 124, 96, 96, 96, 96, 0
2260 DATA 0, 0, 126, 64, 126, 6, 126, 0
2270 DATA 16, 16, 126, 16, 24, 24, 24, 0
2280 DATA 0, 0, 66, 66, 98, 98, 126, 0
2290 DATA 0, 0, 98, 98, 98, 36, 24, 0
2300 DATA 0, 0, 66, 74, 106, 126, 36, 0
2310 DATA 0, 0, 98, 126, 24, 126, 98, 0
2320 DATA 0, 0, 98, 98, 98, 36, 24, 112
2330 DATA 0, 0, 126, 108, 24, 50, 126, 0
2340 DATA 14, 24, 24, 112, 24, 24, 14, 0
2350 DATA 24, 24, 24, 0, 24, 24, 24, 0
2360 DATA 112, 24, 24, 14, 24, 24, 112, 0
2370 DATA 50, 126, 76, 0, 0, 0, 0, 0
2380 DATA 102, 51, 153, 204, 102, 51, 153, 204
2390 DATA 102, 51, 153, 204, 102, 51, 153, 204
2400 DATA -1

Until next time…

GOTO, GOSUB, Stack Overflows and 6809 stack jumping.

While wandering through the Color/Extended/Disk BASIC Unraveled books trying to figure out how the RAM hooks worked, I came across a technique that I had never used.

So of course I’m going to digress with a bunch of other stuff first.

GOTO and GOSUB

In BASIC, you can run code using GOTO or GOSUB. GOTO jumps to a specific line number and runs from there. If that code needs to get back to the main loop, it has to do so with another GOTO.

10 REM MAIN LOOP
20 A$=INKEY$:IF A$="" THEN 20
30 IF A$="L" THEN GOTO 100
40 IF A$="R" THEN GOTO 200
50 GOTO 10

100 REM MOVE LEFT
...
190 GOTO 10

200 REM MOVE RIGHT
...
290 GOTO 10

This is fine for code that does one specific thing at one specific place, but the routines at 100 and 200 could not be used anywhere else in the program unless after such use they always resumed running at line 10.

GOSUB is often a better option, since it eliminates the need for the subroutine to know where it must GOTO at the end:

10 REM MAIN LOOP
20 A$=INKEY$:IF A$="" THEN 20
30 IF A$="L" THEN GOSUB 100
40 IF A$="R" THEN GOSUB 200
50 GOTO 10

100 REM MOVE LEFT
...
190 RETURN

200 REM MOVE RIGHT
...
290 RETURN

There are inefficiencies to the above code, as well as some potential problems, but it’s good enough for an example.

When GOSUB is seen, BASIC remembers the exact spot after the line number and saves it somewhere. It then jumps to that line number, and when a RETURN is seen, it retrieves the saved location and jumps back there to continue executing.

The location is saved on a stack, so you can GOSUB from a GOSUB from a GOSUB, as long as there is enough memory to remember all those locations.

Stack Notes

Think of the stack like a stack of POST-IT(tm) notes. When a GOSUB happens, the return location is written on a piece of paper, then that paper is placed somewhere. If another GOSUB is seen, that location is written on paper and then stuck on top of the previous one, and so on. You end up with a stack of locations. When a RETURN is seen, it grabs the top piece of paper and returns to that location, then that paper is discarded.

10 PRINT "TEST START"
20 GOSUB 100
30 PRINT "TEST END"
40 END

100 REM FIRST
110 PRINT "  FIRST START"
120 GOSUB 200
130 PRINT "  FIRST END"
140 RETURN

200 REM SECOND
210 PRINT "    SECOND START"
220 PRINT "    SECOND END"
230 RETURN

Running that program prints:

TEST START
  FIRST START
    SECOND START
    SECOND END
  FIRST END
TEST END

Test calls First which calls Second. When Second returns, it returns back to First. When First returns, it returns back to Start.

If you ever leave a GOSUB with a GOTO, that return location is still there, saved, and that memory is never returned to the BASIC program. This will crash a program:

10 PRINT X
20 X=X+1
30 GOSUB 10

Each GOSUB adds a return location to the BASIC stack, and since the program is recursively calling itself without ever RETURNing, it will eventually run out of BASIC stack space. In the test I just did, I received an ?OM ERROR (out of memory) at count 3247. On a system with less RAM available (smaller RAM, larger program, etc.) that will happen more often.

This is a STACK OVERFLOW, and languages like C, assembly, etc. can all have them. (I assume that’s where the Q&A site www.stackoverflow.com got its name from.)

Some environments have stack checking, and they will terminate the offending program with an error message when this happens. This is what happened with the ?OM ERROR. Beyond BASIC, operating systems generally take care of this stack checking. Programs written in C or 6809 assembly running under OS-9 most certainly will get terminated with a stack overflow if they try to use more than the OS reserved for them. (Ah, if I only understood this way back then. I just knew to keep adding more memory to a command until it ran without crashing…)

Assembly GOTO and GOSUB

In 6809 assembly, a GOTO equivalent would be like a BRx branch instruction or a JMP jump instruction. The earlier BASIC example might look like this in CoCo assembly:

mainloop
  jsr [$a002]   * Call ROM POLCAT routine, key comes back in A.
  beq mainloop  * If A="", GOTO mainloop.
  cmpa 'L       * Compare A to character "L".
  beq moveleft  * If A="L", GOTO moveleft.
  cmpa 'R       * Compare A to character "R".
  beq moveright * If A="R", GOTO moveright.
  bra mainloop  * GOTO mainloop.

moveleft
  ...
  bra mainloop  * GOTO mainloop.

moveright
  ...
   bra mainloop * GOTO mainloop.

For very simple logic, assembly can be quite similar to BASIC.

GOSUB would be BSR branch subroutine or JSR jump subroutine operation. Here is what the second BASIC example might look like in assembly:

  jsr [$a002]     * Call ROM POLCAT routine, key comes back in A.
  beq mainloop    * If A="", GOTO mainloop.
  cmpa 'L         * Compare A to character "L".
  bsr moveleft    * If A="L", GOSUB moveleft.
  cmpa 'R         * Compare A to character "R".
  bsr moveright   * If A="R", GOSUB moveright.
  bra mainloop    * GOTO mainloop.

moveleft
  ...
  rts             * RETURN.

moveright
  ...
  rts             * RETURN.

Very simple code like this would be a good way for a BASIC programmer to tip-toe in to the land of assembly language. It’s quite fun, until you realize how much work is needed for anything that is not as simple ;-)

And now the third example… Since assembly does not have a PRINT command, I created a simple subroutine that uses the ROM CHROUT routine to print out whatever character is in the A register.

* lwasm jsrtest.asm -fbasic -ojsrtest.bas --map

    org $3f00

start
    * 10 PRINT "TEST START"
    ldx #teststartmsg   * X=Start of message.
    jsr print           * GOSUB print.

    * 20 GOSUB 100
    jsr first           * GOSUB first.
    ldx #testendmsg     * X=Start of message.
    
    * 30 PRINT "TEST END"
    jsr print           * GOSUB print.
    
    * 40 END
    rts                 * RETURN

first
    * 110 PRINT "  FIRST START"
    ldx #firststartmsg  * X=Start of message.
    jsr print           * GOSUB print.

    * 120 GOSUB 200
    jsr second

    * 130 PRINT "  FIRST END"
    ldx #firstendmsg    * X=Start of message.
    jsr print           * GOSUB print.
    
    * 140 RETURN
    rts                 * RETURN

second
    * 210 PRINT "    SECOND START"
    ldx #secondstartmsg * X=Start of message.
    jsr print           * GOSUB print.
    
    * 230 PRINT "    SECOND END"
    ldx #secondendmsg   * X=Start of message.
    jsr print           * GOSUB print.
    
    * 240 RETURN
    rts                 * RETURN

* PRINT subroutine. Prints the string pointed to by X.
print
    lda ,x+
    beq done
    jsr [$a002]
    bra print
done
    lda #13
    jsr [$a002]
    rts

* Data storage for the string messages.
teststartmsg
    fcc "TEST START"
    fcb 0

testendmsg
    fcc "TEST END"
    fcb 0

firststartmsg
    fcc "  FIRST START"
    fcb 0

firstendmsg
    fcc "  FIRST END"
    fcb 0

secondstartmsg
    fcc "    SECOND START"
    fcb 0

secondendmsg
    fcc "    SECOND END"
    fcb 0

Here is a BASIC loader for the above assembly routine. You can load and RUN this, then type EXEC &H3F00 to run it.

10 READ A,B
20 IF A=-1 THEN 70
30 FOR C = A TO B
40 READ D:POKE C,D
50 NEXT C
60 GOTO 10
70 END
80 DATA 16128,16267,142,63,62,189,63,45,189,63,16,142,63,73,189,63,45,57,142,63,82,189,63,45,189,63,32,142,63,96,189,63,45,57,142,63,108,189,63,45,142,63,125,189,63,45,57,166,128,39,6,173,159,160,2,32,246,134,13,173,159,160,2,57,84,69,83,84,32
90 DATA 83,84,65,82,84,0,84,69,83,84,32,69,78,68,0,32,32,70,73,82,83,84,32,83,84,65,82,84,0,32,32,70,73,82,83,84,32,69,78,68,0,32,32,32,32,83,69,67,79,78,68,32,83,84,65,82,84,0,32,32,32,32,83,69,67,79,78,68,32,69,78,68,0,-1,-1

Stack Overflow in assembly

Just for fun… Here is the GOSUB crash program in assembly. 99% of this code is just a crappy routine I had to write to print out a decimal number.

    org $3f00

start
    ldx #0              * X=0
loop
    * 10 PRINT X
    jsr printx          * GOSUB printx.

    * 20 X=X+1
    leax 1,x            * X=X+1

    * 30 GOSUB 10
    bsr loop            * GOSUB loop.

    rts                 * Return to BASIC.

*
* Crappy routine I just put together to try to print out a decimal number.
*
printx
    * Init buffer to 000000.
    lda #'0
    sta numberstring
    sta numberstring+1
    sta numberstring+2
    sta numberstring+3
    sta numberstring+4
    sta numberstring+5
  
    * X is our counter.
    tfr x,d         * Copy X to D

tenthousands    
    cmpd #10000
    blt thousands
    subd #10000
    inc numberstring
    bra tenthousands

thousands
    cmpd #1000
    blt hundreds
    subd #1000
    inc numberstring+1
    bra thousands

hundreds
    cmpd #100
    blt tens
    subd #100
    inc numberstring+2
    bra hundreds

tens
    cmpd #10
    blt ones
    subd #10
    inc numberstring+3
    bra hundreds

ones
    cmpd #0
    blt print
    subd #1
    inc numberstring+4

print
    ldy #numberstring
printloop
    lda ,y+
    jsr [$a002]
    cmpy #bufferend
    bne printloop

    lda #13
    jsr [$a002]
    rts

numberstring fcb 5  * Holds 00000-99999
bufferend equ numberstring+5

Thank you for ignoring my poorly-coded “printx” subroutine.

When I run this, it crashes after printing 08141. I believe it is a much smaller number than the BASIC one because it has much less memory for the stack. Since this program starts in memory at the 32K mark (&H3F00), the stack has from end of RAM (&HFF00) down to the end of this program. As the stack grows, without stack checking, it eventually overwrites the running assembly code, crashing the computer.

Let’s pretend we never did that.

What are we learning?

At the start of this article, I mentioned something I just learned from looking at other assembly code. I learned how to get out of an assembly GOSUB routine without needing to return. Just like BASIC, calling a subroutine recursively will cause a crash. Unlike BASIC, there is no stack checking when running raw 6809 code without an operating system, so it can really crash BASIC and require a reset of the computer.

There is a way to GOTO out of an assembly routine without leaving that GOSUB program counter memory on the stack. You simply move the stack pointer by 2 places.

For example, say you had assembly code that was like this BASIC:

10 GOSUB 100
100 GOSUB 200
200 ...

The stack would look like this:

      <- Next GOSUB would be stored here.
[200] <- Top of stack. RETURN would use this.
[100]
[ 10]

BASIC has no way to throw away whatever GOSUB entry is on the top of the stack, but it is simple to do in assembly just by adding 2 to the S (stack pointer) register.

start
    jsr first    * GOSUB first.
    rts          * RETURN

first
    jsr second   * GOSUB second.
    rts          * RETURN

second
    leas 2,S     * Move stack pointer down two bytes.

    rts          * RETURN

By the time the code gets to “second”, the assembly stack should look like this:

         <- Next bsr/jsr would be stored here.
[first]  <- Top of stack. RTS would use this.
[start]

When the second routine does “leas 2,s”, the stack pointer moves down and it looks like this:

         
[xxxxx]  <- Next bsr/jsr would be stored here.
[start]  <- Top of stack. RTS would use this.

Side Note: Data on the stack is never erased, but will be overwritten the next time something is stored there. The [xxxxx] is actually still [first].

Now if the subroutine does an RTS, it will be returning to start and not first. Thus, if you add that to the assembly and run it, the output will be:

TEST START
  FIRST START
    SECOND START
    SECOND END
TEST END

I do not know of a legal way to do the same in BASIC, but I am sure there is some POKE that could be done to achieve the same thing.

The Microsoft BASIC ROMs do this trick often, when patching in new routines that override some function.

And now it’s time for a brain break.

Until next time…