The FB6 file format


This format is meant as an extension of the Editor/Assembler option 5 format, i.e. the files contain a "memory image" together with some information on where and how to load it. It's completely backward compatible with the EA5 format and the Gramkracker format, but adds extra options at the end of the file. These options are in the form of a tagged list, which will allow for adding new options in the future.

In case you wonder, FB6 was coined by just incrementing by one each character in EA5. But it could also mean "Fully Bloated".

The overall file stucture is the following:

CONT    BYTE  >xx        continuation flag
FLAG    BYTE  >xx        target id
ADDR    DATA  >xxxx      loading address
SIZE    DATA  >xxxx      number of bytes to load
CODE    BSS   SIZE       code to be loaded
        EVEN
TAGS    BSS   >xxxx      tagged list of options
As you can see, the 6-byte header is the same as with EA5 and GK format.

CONT can be >00 or >FF. If it is >FF loading should proceed with another file. With EA5, the new file name is obtained by incrementing the last character in the file name. With GK format, the second file name is produced by appending a '1' to the first file name. This additional character is subsequently incremented if more files are to come.

FLAG indicates both the format and the target. If it is >00 or >FF, it denotes EA5 format and the target is always CPU memory. If it is between >01 and >08, the target is GROM 0 through 7, respectively (GROMs are >2000 bytes apart). If FLAG is between >09 and >18, the target is "cartridge" ROM banks 0 through 15, respectively (and the address should thus be in the range >6000-7FFF). Other values are reserved.

ADDR is the address where the code is to be loaded.

SIZE is the number of bytes to load.

CODE is the data to be loaded. It is a pure memory image, i.e. it must be loaded at the indicated address to work properly, and cannot be linked with other pieces of code at loading time. Some of the new options in the FB6 format allow to overcome these limitations.

TAGS is the list of FB6 options, which I shall describe below. It starts on a word boundary, so if SIZE is an uneven number, a >00 byte will be appended to the code.
 

Option list

This list implements the new options provided by the FB6 format. Each option is preceded with a identification tag and a size byte. Options can occur in any order, with a couple of exceptions. The general format of an option is:
TAG    BYTE >xx           option identifier
SIZE   BYTE >xx           size of option in words (0=size in next word)
OPTS   BSS (2*SIZE)-2     data used to implement the option (if any)
TAG is a one-byte identifier that describes the option. A loader that does not recognize an option can either skip it, issue a warning, or abort with an error.

SIZE is the size of the option in words. It does include the tag and size bytes, so it is always at least 1. A value of zero means that the option  is bigger than 255 words, and that the size is provided in the next word.

OPTS depends on the options. Some will have nothing here, most will have data needed to implement the option.

Below is a description of the currently available options.
 

FB6 identifier

TAG    BYTE >FB           
SIZE   BYTE >01
This is one of the rare exceptions to the rule that options can occur in any order. This must be the first option in the list, as it allows the loader to determine whether the file format is indeed FB6.
 

End-of-list

TAG    BYTE >00
SIZE   BYTE >01
And here is the other exception to the above rule: this tag marks the end of the option list. The loader will not consider data located after this tag.
 

Flags

TAG    BYTE >F1
SIZE   BYTE >02
FLAGS  DATA >xxxx
This option serves as a "table of contents" for the loader. It lists various properties of the code to be loaded, and indicates which option is to be found among the list. It is optional, but if present it should be placed towards the top of the list, to give the loader an advanced warning of what is to come. There could be more than one word of flags, although only one is defined at the moment.

Flag values are:

>0001: must load in RAM or GRAM (no EEPROM)
>0002: must be loaded in GRAM/GROM
 

Absolute page number

TAG    BYTE >  
SIZE   BYTE >02
PAGE   DATA >xxxx     page number (device-specific)
This option is used in case the target device has a paged memory (e.g. the IDE card SRAM, cartridge ROMs, SAMS memory card, etc) . It specifies which page the code is to be loaded into. The page number is device-specific.

If the code is to be loaded in GRAM/GROM, PAGE contains the GROM base to be used, i.e. >9800, >9804, ... ,>983C.
 

Relative page number

TAG    BYTE >  
SIZE   BYTE >02
PAGE   DATA >xxxx   page number (linker-specific)
This option is similar to the above, except that the page number is arbitrary. The loader is free to use any page it wishes in the target device, but it must keep track of this page number. The purpose of this option is to allow linking between files loaded in different pages. Another file may use the REF option to find out in which page the present file was loaded.

For GRAM/GROM files, the page number will be replaced with a GROM base to be chosen by the loader.
 

Relocation list

TAG    BYTE >  
SIZE   BYTE >xx           size of the whole list (in words) + 1
ADDR   DATA >xxxx         intended address
OFFSET DATA >xxxx,>xxxx   locations to be patched
This option allows you to write code that can be loaded at any location. As explained below for plugin routines, assembly code only runs at the location is was meant for, unless special precautions are taken at the time of writing. An alternative solution is for the linker to provide a list of addresses that need to be fixed if the code is to be run at a different address than the intended one. If the loading address does not match the intended address indicated in this option, the loader will add the difference to every location in the offset list (the location is obtained by adding the actual loading address to the offset).

For instance, suppose you wrote the following program

       AORG >A000
A000   MOV  R11,@THERE         >C020,>A014
A004   BL   @SUB               >0A20,>A00E
A008   MOV  @THERE,R11         >C80B,>A014
A00C   B    *R11               >045B

SUB    LI   R0,THERE           >0200,>A014
A012   B    *R11               >045B

THERE  DATA 0                  >0000
This program will only run properly when loaded at >A000, because it expects SUB to be located at >A00E. But assume you provide the following relocation list:
TAG    BYTE >  
SIZE   BYTE >06           size of the option (i.e. list size + 2)
ADDR   DATA >A000         intended address
OFFSET DATA >0002,>0006,>000A,>0010
Now the code can be loaded at, say,  >B000 and the loader will patch it as follows:
B000   MOV  R11,@THERE         >C020,>B014 <-- patched
B004   BL   @SUB               >0A20,>B00E <-- patched
B008   MOV  @THERE,R11         >C80B,>B014 <-- patched
B00C   B    *R11               >045B

SUB    LI   R0,THERE           >0200,>B014 <-- patched
B012   B    *R11               >045B

THERE  DATA 0                  >0000
The drawback of this method is that the relocation list can become fairly bulky. So you may want to make a compromise and use it together with the indexing technique described for plugins. Patch only the instructions that cannot be indexed, such as immediate instructions, or BLWP vectors.
 

Absolute DEF list

TAG    BYTE >  
SIZE   BYTE >xx         size of the whole list (in words) + 1
VALUE  DATA >xxxx       symbol value
NAME   TEXT 'NAME  '    symbol name, always 6 chars
This option is used to provide one or more symbols to the loader. It will allow for linking with other files containing the REF option. The example above shows only one symbol, but there could be more. SIZE is used to decide when the end of the list has been reached. The loader will build a symbol table containing the names and values provided, and use it at the end of the loading process to patch all REFs.

Reserved names:

$SEG is used to specify a segment number for the code contained in the current file. If no such label is provided, the loader just assigns segment numbers sequentially: >0001 for the first file, >0002 for the second, etc.
 

Relative DEF list

TAG    BYTE >  
SIZE   BYTE >xx         size of the whole list (in words) + 1
OFFSET DATA >xxxx       symbol offset
NAME   TEXT 'NAME  '    symbol name, always 6 chars
This option is identical to the above one, except that symbols values are offsets rather than absolute values. The loader will add the loading address of the code to the value placed in the symbol table. This allows DEFined symbols corresponding to locations within a relocatable program.
 

REF chain

TAG    BYTE >  
SIZE   BYTE >xx         size of the whole list (in words) + 1
OFFSET DATA >xxxx       first appearance of the symbol
NAME   TEXT 'NAME  '    symbol name, always 6 chars
This option is used in conjuction with the DEF one, to link together files that may be loaded at different places. It works pretty much like for an EA3 loader: the OFFSET word indicates where in the code is the first occurence of this symbol. That word will contain the offset of the second occurence, etc. The final word will contain >0000 (which means that the very first word in the code cannot be a REFed symbol). The loader will add the provided symbols to its symbol table and patch the code after loading. It will "walk the chain", substituting the value provided by the corresponding DEF. If no DEF with the same name is loaded, an error will be issued.

Reserved names:

$Sxxxx is used to get the loading address of a given segment. The segment number is encoded in the label name, e.g. $S0012 for segment 18 (which is >12 in hexadecimal notation).
$Pxxxx is used  to get the absolute page number assigned by the loader to a given relative page. The relative page number is encoded in the label name, e.g. $P0001 for page 1.
 

REF list

TAG    BYTE >  
SIZE   BYTE >xx          size of the whole list (in words) + 1
NAME   TEXT "NAME  "     symbol name, always 6 chars
OFFSET DATA >xxxx,>xxxx  occurences of this symbol
       DATA >FFFF        end of list for this symbol
This option is used pretty much like the above one, except that no chain exists in the code. Instead, a list of locations to be patched is provided (in the form of offsets, i.e. the loaded will add the loading address to each of these). Each list ends with a >FFFF offset. There may be more than one symbol, the loader use SIZE to decide if there are more.

The same names as for the REF chain option are reserved.
 

Device ID

TAG    BYTE >  
SIZE   BYTE >xx      size of the string (in words) + 1
ID     TEXT 'NAME'   device identifier
       EVEN          append a zero if ID contains an uneven number of chars
This option is used to specify a particular target device. The loader should check whether the device is present in the system, and abort with an error if it isn't. Another option exists that provides a device-recognition routine, so that one can write code for devices that the loader does not know.
 

Plugin routines

The following options provide "plugins", i.e. short assembly routines that allow the loader to handle devices it doesn't know of. These routines will be loaded "somewhere" in CPU memory and called with a BL. It ensues that you should write them in such a way that they can run at any address! The proper way to do this is to assemble them to run at >A000 and  index any address located within your code with R9. Upon calling, the loader will place into R9 the offset between the real loading address and >A000. For instance:
       AORG >A000
WRONG  MOV  R11,@THERE    this will NOT work
       BL   @SUB
       MOV  @THERE,R11
       B    *R11

SUB    LI   R0,THERE
       B    *R11

THERE  DATA 0
The above code will only execute properly at >A000, because it references SUB and THERE as absolute addresses. Since it is ulikely to be loaded there, it will fail miserably: when branching at SUB is will actually branch at >A00E, no matter where SUB was actually loaded. The proper way to code this is:
       AORG >A000
RIGHT  MOV  R11,@THERE(R9)   this will work
       BL   @SUB(R9)
       MOV  @THERE(R9),R11
       B    *R11

SUB    LI   R0,THERE        this is the wrong address
       A    R9,R0           correct address in R0
       B    *R11

THERE  DATA 0
By indexing every address with R9, one allows the routine to work anywhere in memory. Note that we had to add an extra instruction after the LI because immediate instructions cannot be indexed.
 

Find device plugin

TAG    BYTE >  
SIZE   BYTE >xx      size of the routine (in words) + 1
CODE   ...           assembly routine
This plugin provides a routine that checks for the presence of its target device. It is entered with the following registers:

R0: loading flags  (first word in file header)
R1: loading address (second word)
R2: loading size (third word)
R9: offset
R11: return address
R12: >FFFF

Your routine should scan the system looking for the presence of the intended device. If it is found, place its CRU in R12 (if any) and return with B *R11. If it is not found, return with INCT R11, B *R11.What the loader will do in this case depends on the value in R12: if it is >FFFF the loader will abort with a "Device not found" error, if it is >0000 a warning will be issued but loading will proceed anyway, if it is >0001 loading skips to the next file (if any).
 

Find free page plugin

TAG    BYTE >  
SIZE   BYTE >xx      size of the routine (in words) + 1
CODE   ...           assembly routine#
This plugin routine will be called by the loader when it encounters a "relative page" tag. The routine should provide in R0 the absolute number of a page suitable to load the code described by R1 and R2. The registers will contain:

R0: relative page number
R1: loading address
R2: loading size
R9: offset
R11: return address
R12: device CRU

If a suitable page can be found, return its number in R0. If not, return with INCT R11, B *R11 and an error code in R0 (see above).
 

Enable device plugin

TAG    BYTE >  
SIZE   BYTE >xx      size of the routine (in words) + 1
CODE   ...           assembly routine#
This plugin routine will be called before the loader begins writing to a device, and after it is done (with a different value in R3). It can be used for several purposes, such as to unprotect a write-protected device. It may also be called by the loaders to change the current page in the memory area designated by R1. The registers will contain the following values:

R0: loading page (>FFFF if none)
R1: loading address
R2: loading size
R3: >0000 to begin, >FFFF to end, >0001 to change pages
R9: offset
R11: return address
R12: device CRU
 

Write word plugin

TAG    BYTE >  
SIZE   BYTE >xx      size of the routine (in words) + 1
CODE   ...           assembly routine#
This plugin is used with devices that require a special operation to write a word, as is the case with an EEPROM for instance. Your routine is in charge of writing the word (or byte, as the case may be). Return with B *R11 if all went well, and with INCT R11, B *R11 if a problem occured. In the latter case, an error code can be placed into R0, add >8000 to the code to cause the loader to issue a warning rather than aborting. Upon calling, the registers will contain the following values:

R0: loading page (>FFFF if none)
R1: loading address
R2: >0000 write a word, >0001 write a byte
R3: word (or byte) to write. If it's a byte, it will be in the most significant byte if R3.
R9: offset
R11: return address
R12: device CRU
 
 

Last updated: 31/10/2007
 
 

Back to the TI-99/4A Tech Pages