FILES command and memory in CoCo Disk BASIC

Updates:

  • 2023-02-14 – See the comments from William Astle for more background on the memory usage.

Hello once again from the land of Disk BASIC. Today we look at another command: FILES

Device numbers in Color BASIC are hard-coded, meaning a specific number goes to a specific device. Here are some common device numbers:

  • #-2 – printer
  • #-1 – cassette
  • #0 – screen
  • #1 to #15 – disk

When Disk BASIC is added to the system, an extra 2K of memory is reserved for disk functionality. This memory is located directly after the 32-column screen memory (1024-1536) and it looks like this:

This is why a disk-based CoCo has less memory available for programs than a non-disk CoCo.

BASIC also reserves four pages of graphics memory (6144 bytes total) after this, which is why a BASIC program starts at 9729 in memory on a disk-based system. Memory locations 25 and 26 track where a BASIC program begins:

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

While you can use the PCLEAR command to increase reserved graphics memory to eight pages maximum (PCLEAR 8), you cannot get rid of all of them. Due to an oversight or bug in Extended BASIC, “PCLEAR 0” is not valid. You always have to have at least 1536 bytes set aside for graphics, even if you aren’t using them. (Thus why I think this is a bug.) Look at my PCLEAR 0 article for details on how to perform a PCLEAR 0 and get the most memory for BASIC.

For this article, however, I wanted to dive a bit in to how memory is being used for Disk BASIC. If BASIC starts at 9729, and graphics memory is 6144 bytes before it, and the 32-column screen ends at 1536, I should be able to figure out how much memory is reserved for Disk BASIC.

Subtracting the size of graphics memory from the top of BASIC tells me where graphics memory should begin:

PRINT 9729-1-6144
3584

I subtract one there because the BASIC program starts at 9729, meaning the last byte of graphics memory is actually at 9728, one byte earlier.

And since I know the 32-column screen ends at 1535, bytes 1536 to 3135 should be for disk use:

PRINT 3584-1536
2048

Disk Basic is using memory from 1536 to 3584, followed by the graphics memory starting at 3585.

As the FILES manual entry states, by default there are two disk buffers (devices) reserved – #1 and #2. If you need more, you use the FILES command. You can go all the way to FILES 15 and have the ability to open fifteen files at the same time!

If you have enough memory, that is.

On startup, a Disk BASIC system has 22823 bytes available for BASIC. That includes room for two disk devices. If you aren’t using them, you can type FILES 0 and get some extra memory for BASIC:

Above, you can subtract the “after” memory 23335 minus the “before” memory 23823 and get 512. Disk device #1 and #2 take up 512 bytes.

And this tells me that manual entry is possibly wrong since it says “If you do not use FILES, the computer reserves enough memory space for two buffers (Buffer 1 and 2), and reserves a total of 256 bytes for those buffers.” (emphasis mine)

If FILES 2 reserves 256, going to FILES 0 should have only increased memory by 256 — not 512. Shouldn’t it?

I decided to write a program to show the memory after each amount of FILES buffers. I quickly learned that when you use the FILES command, it erases variables. There are other things in BASIC that will erase variables, like PCLEAR. It looks like BASIC just clears out variables rather than relocate them if they change.

Since I could not use a variable, doing this with a FOR/NEXT loop was not possible. So, BRUTE FORCE FOR THE WIN!

10 FILES 0:PRINT"FILES";0;MEM
20 FILES 1:PRINT"FILES";1;MEM
30 FILES 2:PRINT"FILES";2;MEM
40 FILES 3:PRINT"FILES";3;MEM
50 FILES 4:PRINT"FILES";4;MEM
60 FILES 5:PRINT"FILES";5;MEM
70 FILES 6:PRINT"FILES";6;MEM
80 FILES 7:PRINT"FILES";7;MEM
90 FILES 8:PRINT"FILES";8;MEM
100 FILES 9:PRINT"FILES";9;MEM
110 FILES 10:PRINT"FILES";10;MEM
120 FILES 11:PRINT"FILES";11;MEM
130 FILES 12:PRINT"FILES";12;MEM
140 FILES 13:PRINT"FILES";13;MEM
150 FILES 14:PRINT"FILES";14;MEM
160 FILES 14:PRINT"FILES";15;MEM;
170 GOTO 170

The semicolon at the end of the PRINT in line 160, and the endless loop GOTO on 170 just keep all fifteen lines on the screen without scrolling the top one off for the “OK” prompt. Running it gives me this:

Let’s look at that in text form, and I’ll highlight some odd entries:

  • FILES 0 – 22953
  • FILES 1 – 22953
  • FILES 2 – 22441
  • FILES 3 – 22441
  • FILES 4 – 21929
  • FILES 5 – 21417
  • FILES 6 – 21417
  • FILES 7 – 20905
  • FILES 8 – 20905
  • FILES 9 – 20393
  • FILES 10 – 20393
  • FILES 11 – 19881
  • FILES 12 – 19881
  • FILES 13 – 19369
  • FILES 14 – 19369
  • FILES 15 – 19369

The values immediately look odd, since memory doesn’t change between FILES 0 and FILES 1. But, FILES 0 seems to work. If you do that, you can’t OPEN “O”,#1,”FILES” without getting a ?DN ERROR (device number).

When FILES 2 happens, memory goes down by 512 bytes. FILES 3 doesn’t change anything, so it looks like it is allocating two buffers at a time, even if you just wanted one.

FILES 3 to FILES 4 goes down by 512 then FILES 4 to FILES 5 goes down by 512 as well.

That’s clearly not a pattern. In the above list, FILES 4 and FILES 13 to 15 stand out. For FILES 4, it jumps 512 for just that one addition device number, and for 13-15 they all report the same amount of memory.

I do not understand why. But, now that I know this, I can see you might as well use FILES 15 even if you only wanted FILES 13 because they take the same amount of memory.

Or do they?

Not-so-top secret FILES

There is a second option to FILES which is total size for how much memory is reserved for the buffers. But, it doesn’t work quite like I would expect. For example, if you do:

FILES 15,1000
PRINT MEM

…you should see 18215. 1000 bytes are being reserved for all fifteen buffers. But then if you do…

FILES 15,2000
PRINT MEM

…you might expect it to be 1000 less (17215) but on my system I see 17191 – 1024 bytes less. That’s 1K, so perhaps it’s just rounding to some multiple allocation size. We can test…

0 'FILESMEM2.BAS
10 FILES 15,100:PRINT"FILES 15,100";MEM
20 FILES 15,200:PRINT"FILES 15,200";MEM
30 FILES 15,300:PRINT"FILES 15,300";MEM
40 FILES 15,400:PRINT"FILES 15,400";MEM
50 FILES 15,500:PRINT"FILES 15,500";MEM
60 FILES 15,600:PRINT"FILES 15,600";MEM
70 FILES 15,700:PRINT"FILES 15,700";MEM
80 FILES 15,800:PRINT"FILES 15,800";MEM
90 FILES 15,900:PRINT"FILES 15,900";MEM
100 FILES 15,1000:PRINT"FILES 15,1000";MEM
110 FILES 15,1100:PRINT"FILES 15,1100";MEM
120 FILES 15,1200:PRINT"FILES 15,1200";MEM
130 FILES 15,1300:PRINT"FILES 15,1300";MEM
140 FILES 15,1400:PRINT"FILES 15,1400";MEM
150 FILES 15,1500:PRINT"FILES 15,1500";MEM;
160 GOTO 160

That wonderful brute force program shows us something interesting:

Even though each allocation is only asking for 100 bytes more, we only see increases in multiples of 512. That must be the allocation size Disk BASIC is using. This may mean that when we are allocating more buffers, the actual space they need in not a multiple of 512 so that produces the odd increases the first example demonstrated.

Here is a simple program that demonstrates this:

0 'MEMALLOC.BAS
10 MU=0:MA=0:SZ=512
20 PRINT "MEMORY:";MU;"OF";MA
30 INPUT "ALLOCATE";A
40 IF MU+A>MA THEN PRINT "ADDING";SZ:MA=MA+SZ
50 MU=MU+A
60 GOTO 20

If you run this program, it starts off with 0 bytes used (MU) of an allocation of 0 (MA). It asks how much you want to allocate, and you can type in 100 bytes (A). If there isn’t enough memory allocated (MA) to fit another 100 bytes, it will add a new block of 512 (SZ) and continue.

I have not looked through the Disk BASIC manual to see if this is explained, nor have I looked at Disk Basic Unravelled, but I expect the answer is found in one of those.

For now, let’s just understand that using FILES with different values changes how much memory is reserved.

FILES this under…

With that understanding, the FILES command may be tricky to use efficiently in a complex program. Much like the CLEAR command reserving string space, knowing how much memory you need may take some thought if you don’t have enough free memory to just specify “a bunch.”

In a future article, I’d like to explore more about how these buffers are used, but in general it’s probably safe to assume that when we specify a record size for a direct access disk file:

OPEN "D",#1,"USERLOG",256

…and then use GET to read a record, that record has to go somewhere in memory. Above, with a record size of 256, I suspect we’d need at least a 256 byte buffer. Indeed, if you try to do this, you will get an ?OB ERROR (out of buffer space):

10 FILES 1,256
20 OPEN "D",#1,"USERLOG",300
...

But, changing the FILES command to specify 300 bytes for the buffer allows it to work:

10 FILES 1,300
20 OPEN "D",#1,"USERLOG",300
...

Oh. Maybe it is that easy to understand after all? And I suppose when we use multiple buffers (like reading from #1 and writing to #2), we’d need double that amount…

10 FILES 2,600
20 OPEN "D",#1,"OLDFILE",300
30 OPEN "D",#2,"NEWFILE",300
...

Indeed, FILES 2,600 works, but FILES 2,599 will show ?OB ERROR.

In a way, I’m surprised. I kind of expected the memory would be checked until you actually went to GET something, but I guess it pre-allocates.

This leaves me with another… What about FIELD? That command allows a record to be split up in to different fields and assigned to different variables. For example, if I wanted the 300 byte record to contain three 100 byte fields, I’d add:

10 FILES 2,600
20 OPEN "D",#1,"OLDFILE",300
25 FIELD #1,100 AS A$,100 AS B$,100 AS C$
30 OPEN "D",#2,"NEWFILE",300
...

…and this seems to work fine, so it doesn’t appear FIELD needs any extra space. (Note to self: look up how that works in the Unraveled disassembly book.)

I suppose this rabbit hole is pretty deep right now, so I’ll end this article by saying…

Until next time…

5 thoughts on “FILES command and memory in CoCo Disk BASIC

  1. William Astle

    The reason for the discrete jumps in memory usage is because the graphics pages MUST be 512 byte aligned for hardware reasons. So regardless whether the end of the allocated stuff for disk basic is at 3072 or 3583, the first graphics page has to start at 3584. It’s not optional. If PCLEAR 0 did exist, then when PCLEAR 0 was in effect, that rounding to 512 byte boundaries wouldn’t be necessary, but, as you say, it doesn’t work. It’s not a bug, exactly, since there are design reasons why it is that way, but it’s definitely a design flaw.

    Now some notes about the memory allocation itself:

    Disk Basic needs a “file control block” or FCB for each available file. That FCB contains some control data followed by a 256 byte sector buffer which collects output (“O” mode), or holds the current input sector data (“I”) or used as an I/O buffer (“R”/”D”). There is one more FCB allocated than you ask for. That extra one is used for system purposes like LOAD and SAVE.

    Then the random buffer space is just a chunk of space much like string space. When you open a random file, a chunk of buffer space exactly the size of the record size of the file is allocated. This is where the current record lives. FIELDed strings also live here which is why you need to use LSET or RSET to set those strings; otherwise the string gets moved to string space and the newly set value doesn’t appear in the random file record buffer. It’s also why you can’t RSET or LSET an arbitrary string. This is why FIELD doesn’t use up any additional space. It’s just pointing string variables into the already allocated bytes. Incidentally, you can have multiple sets of FIELD strings for the same file with different field sizes even.

    For random files, when you GET a record, the contents of that is split from the file and copied into the record buffer for the file. If you wondering why it doesn’t just use the sector buffer in the FCB, it’s because records can 1) be longer than a sector and 2) span multiple sectors. When you PUT a record, the current contents of the record buffer is written out to the file, again using the FCB sector buffer.

    Reply
      1. William Astle

        Just do multiple field commands. Doing one doesn’t invalidate the previous one so they can be active simultaneously. Like a union type in C. At least if memory serves anyway.

        Reply
  2. Pingback: The Coco Nation News stories for Episode 302, February 25, 2023 -

Leave a Reply to William AstleCancel reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.